Compare commits

...

1248 Commits

Author SHA1 Message Date
b9e9d20074 Merge branch 'master' of https://github.com/junegunn/fzf 2017-11-27 11:25:52 -05:00
Junegunn Choi
1e5bd55672
[install] Change the order of case patterns for $archi (#1060)
/cc @ehandal
2017-11-27 15:44:19 +09:00
Jan Edmund Lazo
37d4015d56 [vim] Don't use :terminal on msys2 or Cygwin (#1155)
Close #1152

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

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

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

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

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

    A hostname or address may optionally be enclosed within ‘[’ and ‘]’
    brackets then followed by ‘:’ and a non-standard port number.
2017-10-19 22:04:32 +09:00
Junegunn Choi
0b33dc6ce1
0.17.1 2017-10-16 01:58:57 +09:00
Junegunn Choi
64a6ced62e
Do not immediately check --height option on Windows (#1082) 2017-10-15 19:02:05 +09:00
Junegunn Choi
438f6c96cd
Fix compilation error of Windows binary 2017-10-15 18:32:59 +09:00
Junegunn Choi
6ae085f974
Add link to Windows wiki page
Related: #1072
/cc @janlazo
2017-10-15 17:44:25 +09:00
Junegunn Choi
cb8e97274e
Update README to add an example of _fzf_compgen_dir
/cc @chrisjohnson

Close #1067
Close #1083
2017-10-14 16:18:46 +09:00
Jan Edmund Lazo
c4185e81e8
Fix ExecCommandWith for cmd.exe in Windows (#1072)
Close #1018

Run the command as is in cmd.exe with no parsing and escaping.
Explicity set cmd.SysProcAttr so execCommand does not escape the command.
Technically, the command should be escaped with ^ for special characters,
including ". This allows cmd.exe commands to be chained together.

See https://github.com/neovim/neovim/pull/7343#issuecomment-333350201

This commit also updates quoteEntry to use strings.Replace instead of
strconv.Quote which escapes more than \ and ".
2017-10-14 15:26:37 +09:00
Ionel Cristian Mărieș
0580fe9046 Don't do shell quoting for weird chars (#1079)
* Don't do shell quoting for weird chars

This would prevent tabs from being escaped as `$'\t'` (definitely not what I would want to see as initial value in the search).

* Do different escape.
2017-10-10 12:27:01 +09:00
Junegunn Choi
1b1bc9ea36
[install] Download arm8 binaries on Linux aarch64
Close #1060
2017-10-08 03:52:55 +09:00
Junegunn Choi
c2614467cf
[neovim] Fix Neovim plugin to use terminal instead of --height
Close #1066
Close #1068
2017-09-30 22:13:43 +09:00
Junegunn Choi
077ae51f05
[vim] Use Vim 8 terminal when appropriate
Close #1055
2017-09-29 01:11:00 +09:00
Junegunn Choi
ee40212e97
Update FZF_DEFAULT_COMMAND
- Use bash for `set -o pipefail`
- Fall back to simpler find command when the original command failed

Related: #1061
2017-09-28 23:05:02 +09:00
Ricardo González
7f5f6efbac [fzf-tmux] Executes fzf from fzf-tmux with a process name (#1056) 2017-09-28 22:23:44 +09:00
Josh Pencheon
45d4c57d91 [completion] Include host aliases in ssh completion (#1062) 2017-09-27 00:18:01 +09:00
Robert Orzanna
41e0208335 Update Homebrew/Linuxbrew instructions (#1052) 2017-09-17 17:12:20 +09:00
Lawrence Wu
2f8238342b [install] Don't touch dotfiles if not requested (#1048) 2017-09-11 09:18:26 +09:00
Junegunn Choi
e1582b8323
Clean up renderer code
Remove code that is no longer relevant after the removal of ncurses
renderer. This commit also fixes background color issue on tcell-based
FullscreenRenderer (Windows).
2017-09-09 23:20:42 +09:00
Junegunn Choi
7cfa6f0265
Fix custom foreground color inside preview window (addendum)
This fixes foreground color inside preview window when the text has ANSI
attributes except for foreground color.

Close #1046
2017-09-08 18:33:17 +09:00
Junegunn Choi
e3973c74e7
Fix custom foreground color inside preview window
Close #1046
2017-09-08 18:18:54 +09:00
Junegunn Choi
a8deca2dd9
[vim] Update README-VIM: fzf can run inside GVim 2017-09-07 12:42:40 +09:00
Junegunn Choi
a78ade1771
Update link to performance chart 2017-09-07 12:38:34 +09:00
Jan Edmund Lazo
79d2ef4616 [vim] Do not pathshorten prompt in cygwin (#1043)
Prevents the following case:
before pathshorten - /usr/bin
after pathshorten - /u/bin
piped to cmd.exe - U:/bin
2017-09-07 11:03:26 +09:00
Junegunn Choi
5edc3f755c
[vim] Update FZF command not set up lengthy prompt on narrow screen
Port of e7928d154a

Since :FZF does not enable preview window, we determine based on full
&columns instead of &columns / 2.
2017-09-07 11:01:40 +09:00
Junegunn Choi
288976310b
Update g:fzf_colors example 2017-09-06 10:44:25 +09:00
Junegunn Choi
58b5be8ab6
0.17.0-2 2017-09-05 13:40:58 +09:00
Jan Edmund Lazo
26d7896877 [vim] Bind Ctrl-J in Vim terminal to fix enter key
Temporary workaround for non-Windows environment

Reference:
https://github.com/vim/vim/issues/1998
https://github.com/junegunn/fzf/pull/1019#issuecomment-327008348
2017-09-05 13:29:46 +09:00
Jan Edmund Lazo
fd6bc7308f [vim] Use s:execute_term in Windows
IMPORTANT:
cmd.exe and powershell are fine in default Windows terminal.
cmd.exe prompt is broken on ConEmu because it natively supports ucs-2 only.
utf-16 support is exclusive to .Net (ie. powershell).
utf-8 supports requires chcp, external program, but does not fix the cmd.exe prompt.
Use powershell on ConEmu to avoid corrupted text on display
2017-09-05 13:29:46 +09:00
Jan Edmund Lazo
6c41c95f28 [vim] s:execute_term works in GVim on Windows
Requirements:
- compiled with +terminal
- has patch-8.0.995
- has('gui_running') returns 1
2017-09-05 13:29:46 +09:00
Jan Edmund Lazo
446e04469d [neovim] use batchfile for s:execute_term in Windows 2017-09-05 13:29:46 +09:00
Michael Smith
5097e563df [neovim] Fix terminal buffer marker on Windows
Original Patch: a9bf29b65e
2017-09-05 13:29:46 +09:00
Jan Edmund Lazo
c7ad97c641 [neovim] use terminal in Windows for v0.2.1+ 2017-09-05 13:29:46 +09:00
Junegunn Choi
9516fe3324
[install] Add --no-{bash,zsh,fish}
Close #1040
2017-09-03 11:45:22 +09:00
Junegunn Choi
20cdbac8c3
[install] Ignore user-defined grep aliases 2017-09-03 11:38:22 +09:00
Junegunn Choi
e3e7b3360c
Delete ncurses implementation 2017-09-02 03:19:50 +09:00
Junegunn Choi
655dfb8328
[fzf-tmux] Remove cat command
Close #1039
2017-09-01 18:46:00 +09:00
Mike Hearn
9b9c67b768 [fzf-tmux] Add pane_height/pane_width fallback (#1037) 2017-09-01 11:16:00 +09:00
Junegunn Choi
5b7457ff08
[install] Wait for a linefeed when asking for confirmation
Close #1035
2017-09-01 02:45:48 +09:00
Junegunn Choi
48adad5454
[neovim] Set &shell to sh (again) after opening a new window
Close #1031
2017-08-30 18:58:28 +09:00
Jack O'Connor
b27dc3eb17 [vim] Add parens around piped source commands (#1029)
Previously a command like `echo a && echo b` would get transformed into
`echo a && echo b | fzf`, which only pipes the output of the second
command. Adding parentheses around the source command avoids this issue,
and works on both Unix and Windows.
2017-08-28 22:32:13 +09:00
Junegunn Choi
e89eebb7ba
0.17.0 2017-08-27 03:32:21 +09:00
Junegunn Choi
fee404399a
Make --expect additive
Similarly to --bind or --color.

--expect used to replace the previously specified keys, and
fzf#wrap({'options': '--expect=f1'}) wouldn't work as expected. It
forced us to come up with some ugly hacks like the following:

13b27c45c8/autoload/fzf/vim.vim (L1086)
2017-08-27 02:19:56 +09:00
Junegunn Choi
6b4805ca1a
Optimize rank comparison on x86 (little-endian) 2017-08-27 01:46:11 +09:00
Junegunn Choi
159699b5d7
Remove an unnecessary code branch 2017-08-26 20:09:46 +09:00
Junegunn Choi
af809c9661
Minor refactorings 2017-08-26 03:24:42 +09:00
Junegunn Choi
329de8f416
[fzf-tmux] Execute trap with bash instead of the default shell
Close #1007
2017-08-26 02:51:19 +09:00
Junegunn Choi
e825b07e85
[neovim] Allow running FZF in multiple windows
Close #1023
2017-08-26 01:56:49 +09:00
Junegunn Choi
71fdb99a07
Remove bound checkings in inner loops 2017-08-26 01:28:39 +09:00
Junegunn Choi
55ee4186aa
Ignore EvtReadNew if EvtReadFin is already set 2017-08-20 14:30:17 +09:00
Junegunn Choi
941b0a0ff7
Minor optimization of FuzzyMatchV2
Calculate the first row of the score matrix during phase 2
2017-08-20 12:29:11 +09:00
Junegunn Choi
6aae12288e
Extract debug code from FuzzyMatchV2 2017-08-20 12:29:11 +09:00
Junegunn Choi
302cc552ef
Remove unused clear arguments of alloc16 and alloc32 2017-08-20 12:29:11 +09:00
Junegunn Choi
a2a4df0886
Pass util.Chars by pointer 2017-08-20 12:29:11 +09:00
Jan Edmund Lazo
3399e39968 [vim] Escape backslashes in fzf#shellescape (#1021) 2017-08-20 12:28:36 +09:00
Junegunn Choi
87874bba88
Remove redundant read event when --sync is used 2017-08-20 01:58:51 +09:00
Junegunn Choi
c304fc4333
Delay slab allocation 2017-08-19 12:14:48 +09:00
Junegunn Choi
6977cf268f
Limit search scope of uppercase letter 2017-08-18 05:30:13 +09:00
Junegunn Choi
931c78a70c
Short-circuit ANSI processing if no ANSI codes are found
Rework of 656963e. Makes --ansi processing around 20% faster on plain
strings without ANSI codes.
2017-08-18 03:04:11 +09:00
Junegunn Choi
8d23646fe6
Revert "Short-circuit ANSI processing if no ANSI codes are found"
This reverts commit 656963e01805efccc788e7e2d83a4bcfaa01ee7b.
2017-08-17 19:12:44 +09:00
Junegunn Choi
656963e018
Short-circuit ANSI processing if no ANSI codes are found 2017-08-17 19:12:06 +09:00
Junegunn Choi
644277faf1
Linuxbrew can install fzf
Close #1017
2017-08-17 16:57:02 +09:00
Junegunn Choi
0558dfee79
Remove count field from ChunkList 2017-08-16 12:26:06 +09:00
Junegunn Choi
487c8fe88f
Make Reader event notification asynchronous
Instead of notifying the event coordinator (EventBox) whenever a new
line is arrived, start a background goroutine that periodically does the
task. Atomic.StoreInt32 is much cheaper than mutex synchronization
that happens during EventBox update.
2017-08-16 03:33:48 +09:00
Junegunn Choi
0d171ba1d8
Remove special nilItem 2017-08-15 01:10:41 +09:00
Junegunn Choi
2069bbc8b5
[vim] Allow Funcref in g:fzf_action
https://github.com/junegunn/fzf.vim/issues/185
2017-08-14 16:23:18 +09:00
Jan Edmund Lazo
053d628b53 Add MinGW 64 to install fzf in Windows 64-bit (#1015) 2017-08-13 23:20:06 +09:00
Junegunn Choi
6bc592e6c9
Update FuzzyMatchV1 to use skip optimization used in V2 2017-08-12 00:28:30 +09:00
Junegunn Choi
6c76d8cd1c
Disallow escaping of meta characters except for spaces
https://github.com/junegunn/fzf/issues/444#issuecomment-321719604
2017-08-11 13:09:33 +09:00
Junegunn Choi
a09e411936
Treat | as proper query when it can't be an OR operator 2017-08-11 00:07:18 +09:00
Junegunn Choi
02a7b96f33
Treat $ as proper search query
When $ is the leading character in a query, it's probably not meant to
be an anchor.
2017-08-10 23:59:52 +09:00
Junegunn Choi
e55e029ae8
Build cache key for a pattern only once 2017-08-10 23:18:52 +09:00
Junegunn Choi
6b18b144cf
Fix escaping of meta characters after ' or ! prefix
https://github.com/junegunn/fzf/issues/444#issuecomment-321432803
2017-08-10 12:40:53 +09:00
Junegunn Choi
6d53089cc1
Allow escaping term starting with |
Close #444
2017-08-09 23:33:37 +09:00
Junegunn Choi
e85a8a68d0
Allow escaping meta characters with backslashes
One can escape meta characters in extended-search mode with backslashes.

  Prefixes:
    \'
    \!
    \^

  Suffix:
    \$

  Term separator:
    \<SPACE>

To keep things simple, we are not going to support escaping of escaped
sequences (e.g. \\') for matching them literally.

Since this is a breaking change, we will bump the minor version.

Close #444
2017-08-09 23:28:47 +09:00
Junegunn Choi
dc55e68524
Remove unnecessary SCP (Save Cursor Position)
It is reported that it can have an unwanted side effect of clearing the
screen on terminal emulators that do not properly support it.

Patch suggested by @arya.

Close #1011
2017-08-09 01:58:29 +09:00
Junegunn Choi
462c68b625
[vim] Fix issues with other plugins changing working directory
Close #1005
2017-08-09 01:54:01 +09:00
Junegunn Choi
999d374f0c
Fix invalid cache lookups 2017-08-08 13:23:33 +09:00
Junegunn Choi
b208aa675e
Update Travis build to run on Trusty 2017-08-05 04:28:43 +09:00
Junegunn Choi
2b98fee136
Fix Travis CI build
tcell build is commented out as it doesn't reliably respond to tmux
send-keys.
2017-08-05 04:01:17 +09:00
Junegunn Choi
e5e75efebc
[vim] Fix vader test cases 2017-08-04 19:25:06 +09:00
Junegunn Choi
4a4fef2daf
Update performance comparison chart 2017-08-04 09:28:29 +09:00
Junegunn Choi
ecb6b234cc
0.16.11 2017-08-02 02:50:28 +09:00
Junegunn Choi
39dbc8acdb
Exit 2 instead of panic when failed to open /dev/tty 2017-08-02 02:50:26 +09:00
Junegunn Choi
a56489bc7f
Remove non-exclusive access to ChunkList field 2017-08-02 00:09:00 +09:00
Junegunn Choi
99927c7071
Modify loop conditions in checkAscii function 2017-08-01 22:04:42 +09:00
Junegunn Choi
3e28403978
[man] Add note on --no- convention
Close #1003
2017-08-01 21:34:44 +09:00
Junegunn Choi
37370f057f
Do not use defer in performance-sensitive contexts 2017-08-01 03:44:55 +09:00
Junegunn Choi
f4b46fad27
Inline function calls in a tight loop
Manually inline function calls in a tight loop as Go compiler does not
inline non-leaf functions. It is observed that this unpleasant code
change resulted up to 10% performance improvement.
2017-08-01 03:44:38 +09:00
Junegunn Choi
9d2c6a95f4
Revert "[bash] Do not append space when path completion is cancelled"
This reverts commit 376a76d1d3c39a1686b6ac7a6118c1f80af3178e as it
affects normal completion
2017-07-31 14:08:17 +09:00
Junegunn Choi
376a76d1d3
[bash] Do not append space when path completion is cancelled
Close #990
2017-07-30 21:51:44 +09:00
Jan Edmund Lazo
1fcc07e54e
[vim] Fix escape of backslash in s:shortpath
Close #1000
2017-07-30 20:05:01 +09:00
Junegunn Choi
8db3345c2f
Optimize exact match by applying the same trick for fuzzy match 2017-07-30 18:16:54 +09:00
Junegunn Choi
69aa2fea68
Optimize fuzzy search performance for ASCII strings 2017-07-30 17:31:50 +09:00
Junegunn Choi
298749bfcd
Update README 2017-07-29 17:12:46 +09:00
Junegunn Choi
f1f31baae1
Update README: Missing TOC 2017-07-29 17:10:00 +09:00
Junegunn Choi
e1c8f19e8f
Update README: Advanced topics 2017-07-29 17:09:05 +09:00
Junegunn Choi
5e302c70e9
Update README: rg intead of pt 2017-07-29 17:09:05 +09:00
Junegunn Choi
4c5a679066
Make deselect-all instantaneous 2017-07-28 13:13:03 +09:00
Andrew Halberstadt
41f0b2c354 Add MinGW on Windows to install script (#998)
Running uname -sm yields:
MINGW32_NT-6.2 i686
2017-07-28 12:22:33 +09:00
Junegunn Choi
a0a3c349c9
Update preview window when selection has changed
Close #995
2017-07-28 01:39:25 +09:00
Alexey Shamrin
bc3983181d Update fish comments, because 2.6.0 was released (#991) 2017-07-25 19:10:34 +09:00
Junegunn Choi
980b58ef5a
Update README
Removed outdated animated GIF.
2017-07-23 22:07:24 +09:00
Junegunn Choi
a2604c0963
[nvim] Disable number in fzf buffer
https://github.com/junegunn/fzf.vim/issues/396#issuecomment-317214036

One can override the setting on FileType fzf autocmd.
2017-07-23 13:12:15 +09:00
Junegunn Choi
6dbc108da2
0.16.10 2017-07-21 18:41:11 +09:00
Junegunn Choi
bd98f988f0
Further reduce unnecessary rune array conversion
I was too quick to release 0.16.9, this commit makes --ansi processing
even faster.
2017-07-21 17:31:11 +09:00
Junegunn Choi
06301c7847
Fix regression: ANSI color in preview window not cleared 2017-07-21 16:44:59 +09:00
Junegunn Choi
18a1aeaa91
0.16.9 2017-07-21 00:08:55 +09:00
Junegunn Choi
c9f16b6430
Avoid unconditionally storsing input as runes
When --with-nth is used, fzf used to preprocess each line and store the
result as rune array, which was wasteful if the line only contains ascii
characters.
2017-07-20 02:44:30 +09:00
Junegunn Choi
bc9d2abdb6
Improve preview window rendering
- Fix incorrect display of the last line when more than a line is
  wrapped above
- Avoid unnecessary flickering of the window
2017-07-19 22:47:15 +09:00
Junegunn Choi
28810c178f
Optimize ANSI code scanner
This change gives 5x speed improvement
2017-07-19 21:49:41 +09:00
Junegunn Choi
a9e64efe45
Fix regression: output printed on alternate screen 2017-07-19 13:17:06 +09:00
Junegunn Choi
6b5886c034
Adjust --no-clear option for repetitive relaunching
Related: https://gist.github.com/junegunn/4963bab6ace453f7f529d2d0e01b1d85

Close #974
2017-07-18 21:10:49 +09:00
Junegunn Choi
7727ad43af
[vim] Use fnameescape to escape command line arguments
Fix https://github.com/junegunn/fzf.vim/issues/404

Thanks to @janlazo.
2017-07-18 16:33:58 +09:00
Junegunn Choi
bbe10f4f77
Consolidate Result and rank structs
By not storing item index twice, we can cut down the size of Result
struct and now it makes more sense to store and pass Results by values.
Benchmarks show no degradation of performance by additional pointer
indirection for looking up index.
2017-07-18 03:14:33 +09:00
Junegunn Choi
5e72709613
Speed up initial scanning with bitwise AND operation 2017-07-18 02:17:05 +09:00
Junegunn Choi
9e85cba0d0
Reduce memory footprint of Item struct 2017-07-16 23:34:32 +09:00
Junegunn Choi
4b59ced08f
Add gopath to gitignore 2017-07-16 23:34:32 +09:00
Junegunn Choi
8dbdd55730
Refactor cache lookup
- Remove multiple mutex locks in partial cache lookup
- Simplify return values
2017-07-16 23:34:32 +09:00
Junegunn Choi
6725151a99
Remove unnecessary copy of Chunk slice 2017-07-16 23:34:32 +09:00
Junegunn Choi
d4f3d5a164
Remove pointer indirection by changing Chunk definition 2017-07-16 23:34:32 +09:00
Tom Fitzhenry
7b5ccc45bc [fish] Fix ctrl-r regression in versions <2.4 (#972)
Close #966
2017-07-15 18:50:23 +09:00
Jan Edmund Lazo
940214a1a2 [neovim] Fix lcd when fzf job exits on Windows (#970)
Related: #960 (relative filepaths)
2017-07-10 02:06:13 +09:00
Jan Edmund Lazo
68bd410159 [vim] Don't pipe FZF_DEFAULT_COMMAND in Windows (#969)
Related #960, #552
2017-07-09 13:08:16 +09:00
Junegunn Choi
b13fcfd831
Add missing --no-expect flag 2017-07-04 23:02:15 +09:00
Junegunn Choi
07ef2b051c
Print [ERROR] on info line when the default command failed
With zero result.

Related: https://github.com/junegunn/fzf.vim/issues/22#issuecomment-311869805
2017-07-01 01:13:15 +09:00
Junegunn Choi
3fc795340d
Fix test failulre with non-zero pane-base-index 2017-07-01 01:05:47 +09:00
John Nguyen
70cfa6af13 [fish] Accept starting dir for <M-c> key binding (#944)
This also modifies <C-t> behaviour.
The longest file path in the input is used as root directory for `find`
command. The remainder of the input is passed to fzf's --query as a
initial search parameters.
2017-06-25 21:16:15 +09:00
Tom Fitzhenry
dbcaec59ae [fish] Support multiline commands (#954)
Fix found by @amosbird at https://github.com/junegunn/fzf/issues/953#issuecomment-310309055

closes #440
2017-06-25 21:09:51 +09:00
Junegunn Choi
faedae708e
Fix FZF_CTRL_T_COMMAND example for fish
See #944
2017-06-23 01:50:45 +09:00
Junegunn Choi
0c66521b23
Fix handling of bracketed paste mode
fzf should immediately continue consuming the buffer after discarding
bracketed paste mode sequence.

Close #951
2017-06-22 02:35:57 +09:00
Junegunn Choi
bf92862459
Update man page: missing name "border" for --color 2017-06-20 14:15:11 +09:00
John Nguyen
1a68698d76 [fish] Fix <C-t> completion for current dir search (#946)
If "." is given as the argument to begin <C-t> completion, the leading
"." is not correctly removed. In general, if user selects a fzf
completion, the current token should be "consumed".
2017-06-12 18:24:45 +09:00
Junegunn Choi
842a73357c
[fish] Fix CTRL-T with paths that don't start with ./
Close #943
2017-06-10 13:35:24 +09:00
Junegunn Choi
5efdeccdbb
[vim] Expand 'dir' on Cygwin to handle Windows-style paths
See https://github.com/junegunn/fzf/pull/933#discussion_r120011934

Close https://github.com/junegunn/fzf.vim/pull/386
2017-06-09 12:00:59 +09:00
Jan Edmund Lazo
050777b8c4 [vim] Uncomment test case to escape % in cmd.exe (#941) 2017-06-08 10:25:35 +09:00
Uri Gorelik
a4d78e2200 Update CHANGELOG with unix-line-discard+top (#940)
Also change the example binding for `unix-word-rubout` to *ctrl-w* instead of *ctrl-u*
2017-06-08 10:02:34 +09:00
Junegunn Choi
b49f22cdf9
0.16.8 2017-06-05 23:21:50 +09:00
Jan Edmund Lazo
7e483b0c25 [vim] Add support for Cygwin (#933) 2017-06-05 13:54:47 +09:00
Junegunn Choi
3cf9ae04c7
[fzf-tmux] Fix cleanup of temporary files
Close #935
2017-06-04 23:24:57 +09:00
Junegunn Choi
bf0cb4bfe2
Use find as the default command on Cygwin environment 2017-06-04 16:23:47 +09:00
Junegunn Choi
773133c4ce
[vim] Allow running install --bin on Cygwin 2017-06-04 15:15:46 +09:00
Junegunn Choi
ca0b3b6fd7
Fixes for Cygwin
- Update install script to download Windows binary if $TERM == cygwin
- Unset TERM if $TERM == cygwin (#933)
- Always use cmd.exe instead of $SHELL when running commands
2017-06-03 19:47:53 +09:00
Junegunn Choi
f4731c0514
Merge branch 'master' into devel 2017-06-03 19:42:26 +09:00
Junegunn Choi
34f16e5b7d
Fix Makefile and install script for the new project layout 2017-06-02 18:19:21 +09:00
Junegunn Choi
83e9af6601
Add git revision to --version output 2017-06-02 17:59:12 +09:00
Junegunn Choi
8bbf9335e1
Restructuring: main package in project root 2017-06-02 17:59:01 +09:00
Junegunn Choi
159f30b37f
Merge branch 'glide' of https://github.com/hinshun/fzf into hinshun-glide 2017-06-02 13:35:40 +09:00
Junegunn Choi
2e3dc75425
Fix inconsistent tiebreak scores when --nth is used
Make sure to consistently calculate tiebreak scores based on the
original line.

This change may not be preferable if you filter aligned tabular input on
a subset of columns using --nth. However, if we calculate length
tiebreak only on the matched components instead of the entire line, the
result can be very confusing when multiple --nth components are
specified, so let's keep it simple and consistent.

Close #926
2017-06-02 13:25:35 +09:00
Edgar Lee
7d3575b362
Use glide to handle go dependencies 2017-06-01 17:08:47 -07:00
Junegunn Choi
35d407021c
[vim] Replace invalid s:escape calls with fzf#shellescape 2017-05-31 23:59:11 +09:00
Junegunn Choi
076f49d447
[vim] Make sure to delete temporary batchfile on Windows 2017-05-31 10:03:23 +09:00
Junegunn Choi
0665fe0413
[vim] Remove unnecessary ternary expression
Related: https://github.com/junegunn/fzf.vim/issues/378
2017-05-31 10:02:04 +09:00
Jan Edmund Lazo
669a6fee40 [vim] Use utf-8 for cmd.exe (#929) 2017-05-31 09:56:01 +09:00
Jan Edmund Lazo
8aab0fc189 [vim] Replace s:fzf_shellescape and s:shellesc with fzf#shellescape (#916) 2017-05-29 10:06:06 +09:00
Junegunn Choi
5d6eb5bfd6
Respect ANSI color state from the previous line in preview output 2017-05-28 02:26:42 +09:00
Junegunn Choi
cf4711d878
Fix display of tab characters in --prompt 2017-05-26 19:02:49 +09:00
Junegunn Choi
21d664d670
Update extra bash completion example 2017-05-25 19:09:04 +09:00
Tw
ab182e276b Use read syscall directly to get character (#931)
Due to go std lib uses poller for os.File introducing in this commit:
c05b06a12d
There are two changes to watch out:
1. os.File.Fd will always return a blocking fd except on bsd.
2. os.File.Read won't return EAGAIN error for nonblocking fd.

So
For 1, we just get tty's fd in advance and then set its block mode.
For 2, we use read syscall directly to get what we wanted error(EAGAIN).

Fix issue #910.

Signed-off-by: Tw <tw19881113@gmail.com>
2017-05-25 01:36:59 +09:00
Junegunn Choi
96a3250152
Update test case for --cycle 2017-05-24 13:20:13 +09:00
Junegunn Choi
f5746002fd
Do not "--cycle" on page-up/page-down
Close #928
2017-05-24 02:43:39 +09:00
Junegunn Choi
e1e3339770
Implement bindable "change" event and "top" action
# Move cursor to the top result whenever the query string is changed
    fzf --bind change:top

Close #925
2017-05-22 17:07:05 +09:00
Junegunn Choi
3a5086796d
[vim] Prevent 'wildignore' from affecting expand() (#917) 2017-05-22 01:23:59 +09:00
Junegunn Choi
11300913a4
[vim] Do not expand s:fzf_go
expand() may return an empty string depending on the value of
&wildignore. Since expand('<sfile>') always returns an absolute path, we
can remove expand() call here. Close #917.
2017-05-22 01:04:04 +09:00
Aurelien Rainone
e65f14cbed Update README: Add table of contents (#927) 2017-05-20 19:08:56 +09:00
Theodore Dubois
6898849e3e Mention that the fish bug has been fixed (#912) 2017-05-05 10:48:28 +09:00
Junegunn Choi
2d61691bb2
0.16.7 2017-04-30 11:54:40 +09:00
Junegunn Choi
eba9e04e2e
Export FZF_PREVIEW_HEIGHT instead of FZF_HEIGHT
https://github.com/junegunn/fzf.vim/issues/361
2017-04-30 11:36:23 +09:00
Junegunn Choi
33f32de690
Merge branch 'master' into devel 2017-04-30 11:23:42 +09:00
Junegunn Choi
93b8f61551
[vim] Export $FZF_HEIGHT for previewer scripts
Preview script cannot properly determine the height of fzf finder if
`--height` option is used.

https://github.com/junegunn/fzf.vim/issues/361
2017-04-30 11:18:56 +09:00
Junegunn Choi
7f17a9d1b4
Update mattn/go-shellwords 2017-04-30 00:47:44 +09:00
Junegunn Choi
d34e4cf698
Support CTRL-Z (SIGSTOP) 2017-04-28 22:58:08 +09:00
Junegunn Choi
6b592137b9
Add support for ctrl-alt-[a-z] key chords
Close #906
2017-04-28 02:36:36 +09:00
Junegunn Choi
d5e72bf55d
Update README-VIM: options as list (#896) 2017-04-28 02:09:55 +09:00
Junegunn Choi
5677e5e133
[fish] Fix ~/.config/fish/functions/fish_user_key_bindings.fish
Install script will create the file with the proper function body only
if the file doesn't exist. If it already exists, it will try to append
`fzf_key_bindings` as before.

Close #851
2017-04-28 01:57:38 +09:00
Jan Edmund Lazo
7a11a06cbd [vim] Use backslash for Windows filepaths (#896)
- Fix shellescaping issues for filepaths
    - Supports both forward slashes or backslashes
    - Paths with spaces
- Use jobstart for neovim in s:execute (Windows)
    - https://github.com/neovim/neovim/pull/6497
- Make 2 s:fzf_shellescape functions
    - (Windows) Substitute \" with \\" to escape the last backslash
    - (Default) Regular shellescape
- Support list 'options'
- Add "@echo off" to the batchfile used to execute fzf
2017-04-22 11:30:51 +09:00
Junegunn Choi
a5862d4b9c
[bash-completion] Use -o dirnames instead of -o plusdirs
Close #903
Related #135
2017-04-11 22:21:16 +09:00
Junegunn Choi
a50909e806
Correction: fzf no longer depends on ncurses 2017-04-06 02:08:49 +09:00
Kouki Higashikawa
7c8f7d3f20 [fzf-tmux] Close with exit code 130 when tmux pane is killed
Fix #796
2017-04-03 11:49:54 +09:00
Junegunn Choi
9078197446
Add --version to --help output and man page
Close #888
Close #894
2017-04-02 11:30:22 +09:00
Junegunn Choi
0fe07cf9fe
Update README.md
Add PayPal donation button
2017-04-02 10:47:06 +09:00
Junegunn Choi
2216169ca1
Update doc/fzf.txt accordingly 2017-04-01 12:19:39 +09:00
Junegunn Choi
50e989ca85
Update example in README-VIM 2017-04-01 12:06:25 +09:00
Junegunn Choi
fa1fc3d855
Add vim doc
Close #893
2017-04-01 12:00:30 +09:00
五所和哉
bbe696e925 [fzf-tmux] Fix issue with zoomed pane on fish (#891) 2017-04-01 11:09:46 +09:00
Miodrag Milić
5d12f523a3 Add chocolatey upgrade instruction to Readme (#890) 2017-03-30 01:59:41 +09:00
Daniel Hahler
d295d20dc4 fzf#run: improve "is already running" message (#885)
This displays the buffer(s) in this case, which is useful when FZF got
stuck, and you have to manually remove the buffer.
2017-03-27 13:41:39 +09:00
Sam Van Den Berge
2ba10071c9 Add support for IPv6 addresses in ssh completion (#877)
Signed-off-by: Sam Van Den Berge <sam@drgt.net>
2017-03-21 01:06:13 +09:00
Christian Sturm
505dc0491b Make install script to work with non GNU tar (#871) 2017-03-10 23:22:37 +09:00
Junegunn Choi
54a4ab0f26
Add Chocolatey instruction
Thanks to @majkinetor. Close #869.
2017-03-07 23:03:14 +09:00
Junegunn Choi
e03e91477b
0.16.6 2017-03-05 03:05:06 +09:00
Junegunn Choi
88ac397158
Add test case for --no-clear 2017-03-04 14:26:47 +09:00
Junegunn Choi
6fd4be580b
Use alternate screen only when the value of height is 100%
Do not automatically decide to use alternate screen when the value of
height exceeds the height of the terminal.

    # Use alternate screen
    fzf
    fzf --height 100%
    fzf --no-height

    # Still use current screen
    fzf --height 10000
2017-03-04 14:09:36 +09:00
Junegunn Choi
53348feb89
Add --no-clear option 2017-03-04 11:29:31 +09:00
Junegunn Choi
337cdbb37c
[zsh] Use setopt noposixbuiltins instead of emulate -L zsh
Close #858
3a6af27586 (commitcomment-21135641)
2017-03-03 19:09:29 +09:00
Junegunn Choi
05fdf91fc5
Revert "[zsh] emulate -L zsh to avoid issues with incompatible options"
This reverts commit 3a6af27586c65bb9243c5af7c43a40619503db52.
2017-03-03 18:57:22 +09:00
Junegunn Choi
c387689d1c
[shell] Enable sorting by default in CTRL-R
CTRL-R binding used to start with --no-sort to list the matched commands
in chronological order. However, it has been a constant source of
confusion. Let's enable it by default from now on. The sorted result
shouldn't be too confusing as we use --tiebreak=index.
2017-03-03 12:20:01 +09:00
Junegunn Choi
cb9238dc4e
Display -S if sort is disabled and toggle-sort is used
This is to address a common confusion that one does not realize that
sorting is intentionally turned off by default and can be enabled by
a bind key.
2017-03-03 02:26:30 +09:00
Junegunn Choi
a484811f78
[vim] Capitalize exception messages 2017-03-02 14:17:59 +09:00
Junegunn Choi
111d1934c4
[vim] Throw error if g:fzf_layout is incorrectly used
https://github.com/junegunn/fzf.vim/issues/327
https://github.com/junegunn/fzf.vim/issues/317
2017-03-02 14:14:57 +09:00
Junegunn Choi
972fb1a29d
Suppress ANSI colors in preview window if --no-color is set 2017-03-02 12:49:51 +09:00
Junegunn Choi
3a6af27586
[zsh] emulate -L zsh to avoid issues with incompatible options
Close #858
2017-03-01 16:07:04 +09:00
Junegunn Choi
c89ac341e4
Clear background even if background color is not set
This is needed when fzf is started from inside a program (e.g. Vim)
and it uses a different background color than the terminal.

- https://github.com/junegunn/fzf.vim/issues/325
- https://github.com/junegunn/fzf.vim/issues/300
2017-03-01 16:00:08 +09:00
Junegunn Choi
cd59e5d07b
[neovim] Set 'dir' to the current direcotry
Close https://github.com/junegunn/fzf.vim/issues/308
2017-02-25 23:52:56 +09:00
Junegunn Choi
0b940e4b2b
Redraw item if query string has changed 2017-02-24 02:30:11 +09:00
Junegunn Choi
b29375c844
[vim] Minor refactoring 2017-02-19 20:53:12 +09:00
Junegunn Choi
76d3f6d248
[vim] Escape ! when using :! to execute command
- call fzf#run({'source': "echo '!'"})
- call fzf#run({'source': "echo '!'", 'down': '40%'})

Close https://github.com/junegunn/fzf.vim/issues/315
2017-02-19 20:47:44 +09:00
Junegunn Choi
e87a85a179
0.16.5 2017-02-19 01:40:25 +09:00
Junegunn Choi
11407bf656
Exclude sysfs in find commands 2017-02-19 01:33:13 +09:00
Junegunn Choi
c82fb3c9b9
Add toggle-preview-wrap action 2017-02-18 23:49:00 +09:00
Junegunn Choi
309e1d8619
Properly truncate long query string 2017-02-18 23:17:29 +09:00
Junegunn Choi
c2db67c1c0
[vim] Prepend @echo off to $FZF_DEFAULT_COMMAND on Windows (#847) 2017-02-18 21:58:03 +09:00
Junegunn Choi
9526594905
[vim] Fix FZF_DEFAULT_COMMAND on Windows
Close #847. Patch submitted by @wontoncc.
2017-02-18 18:17:37 +09:00
Junegunn Choi
3d74d277aa
Use cut instead of sed in the default command 2017-02-17 13:07:45 +09:00
Junegunn Choi
fc274c2ba4
[vim] Do not escape % when using system() instead of !
Close https://github.com/junegunn/fzf.vim/issues/309
2017-02-17 10:20:39 +09:00
Pierre Neidhardt
ce43ea9f42 [shell] Replace sed with -mindepth 1 and cut (#844) 2017-02-16 17:18:01 +09:00
Junegunn Choi
21da02fac2
Fix indentation 2017-02-14 22:30:09 +09:00
Junegunn Choi
19569bd5c5
Move cursor to the top-left when returning to alternate screen
Fix broken preview border. Reported by Thomas Sattler.

    fzf --bind 'enter:execute(date)' --preview=date --reverse
2017-02-14 22:28:04 +09:00
Daniel Gray
afa25d8c57 [zsh] Do not cd when cancelling alt+c keybind (#840) 2017-02-09 14:05:02 +09:00
Junegunn Choi
1ba7acf4bd
[fzf-tmux] Fix race condition when using -l/-u on zoomed panes
Using a dummy command that works as the barrier.
2017-02-08 14:55:51 +09:00
Prabir Shrestha
a847fe8754 Use "type" instead of "cat" on windows (#836) 2017-02-07 14:42:08 +09:00
Junegunn Choi
5bb18b6441
Remove Dockerfiles and clean up Makefile
Due to the recent removal of ncurses dependency, we can cross-compile
binaries for different platforms without virtual machines.
2017-02-06 21:15:29 +09:00
Junegunn Choi
876c233a26
Remove Ruby version
Related #832
2017-02-06 21:06:12 +09:00
Junegunn Choi
ee5aeb80a4
0.16.4 2017-02-05 16:17:54 +09:00
Junegunn Choi
02ceae15a2
[vim] Download instruction for Windows 2017-02-05 02:07:54 +09:00
Junegunn Choi
e514739280
Fix failing test case 2017-02-04 22:49:17 +09:00
Junegunn Choi
72265298f9
[vim] Apply --no-height when running fzf in full screen mode
To override --height option in FZF_DEFAULT_OPTS
2017-02-04 21:52:05 +09:00
Junegunn Choi
4b700192c1
Add --border option to draw horizontal lines above and below the finder
Goes well with --height
2017-02-04 21:51:22 +09:00
Junegunn Choi
fe83589ade
Add test case for --tiebreak=begin 2017-02-03 02:14:14 +09:00
Junegunn Choi
fcf63c74f1
Fix --tiebreak=begin with algo v2
Due to performance consideration, FuzzyMatchV2 does not return the exact
positions of the matching characters by default. However, the ommission
caused `--tiebreak=begin` to produce inaccurate result in some cases.

  (echo baz foo bar; echo foo bar baz) | fzf --tiebreak=begin -fbar | head -1

  # Expected: foo bar baz
  # Actual:   baz foo bar

This commit fixes the problem by using the end offset which is
guaranteed to be correct.
2017-02-02 13:46:46 +09:00
Junegunn Choi
c95bb109c8
Suppress CSI codes in the output 2017-02-02 13:14:27 +09:00
Junegunn Choi
bd9c46ee34
Update ANSI processor to strip ^H along with its preceding character 2017-02-02 13:00:41 +09:00
Junegunn Choi
736aeaa1d3
Update go-runewidth
https://github.com/junegunn/go-runewidth/pull/1

/cc @joshuarubin
2017-02-02 10:08:56 +09:00
Junegunn Choi
dd1f26522c
Fix caching scheme when --exact is set and '-prefix is used 2017-02-01 02:06:56 +09:00
Kassio Borges
712b7b2188 [vim] Expose buffer variable with the current fzf setup (#828)
Exposing the `b:fzf` variable will be useful to get information about
which command is being executed on the current fzf window. With that,
now, it's possible to use the current command name on the statusline:

```viml
au User FzfStatusLine call <SID>fzf_statusline()

function! s:fzf_statusline()
  let fzf_cmd_name = get(b:fzf, 'name', 'FZF')
  let &l:statusline = '> '.fzf_cmd_name
endfunction
```
2017-02-01 01:06:52 +09:00
Junegunn Choi
5b749e2d5c
Update documentation 2017-01-31 21:43:41 +09:00
Junegunn Choi
d85a69a709
0.16.3 2017-01-30 01:53:17 +09:00
Junegunn Choi
a425e96fb2
[vim] g:fzf_prefer_tmux for choosing fzf-tmux over --height
https://github.com/junegunn/fzf.vim/issues/296
2017-01-30 01:47:30 +09:00
Junegunn Choi
7763fdf6ba
Update man pages 2017-01-30 01:27:12 +09:00
Junegunn Choi
dd156b59fc
Fix display issues with execute action
- Move cursor to the top-left corner when starting a command in
  alternate screen
- Fix cursor position when returning to alternate screen when fzf is
  running in full screen mode
2017-01-30 01:08:07 +09:00
Junegunn Choi
36dceecd58
Add support for ctrl-space key
Close #825
2017-01-28 02:54:47 +09:00
Junegunn Choi
421b9b271a
Add execute-silent action
Close #823
2017-01-27 18:56:41 +09:00
Junegunn Choi
ed57dcb924
Extend placeholder expression for multiple selections
Close #788
2017-01-27 16:38:42 +09:00
Junegunn Choi
95c77bfb98
Use --bind instead of --toggle-sort
Related #822
2017-01-26 11:54:08 +09:00
Junegunn Choi
2e3e721344
Merge branch 'devel' 2017-01-26 11:52:24 +09:00
Junegunn Choi
da2c28d5c2
Add --read0 and --print0 to --help output
Close #822
2017-01-26 11:41:20 +09:00
Junegunn Choi
dbddee9de9
[fish] Add toggle-sort back to CTRL-R (#759) 2017-01-25 10:21:14 +09:00
Junegunn Choi
8731d75607
Recalculate the width of trimmed line
Close #821
2017-01-25 02:39:49 +09:00
Junegunn Choi
f2ce233a6d
0.16.2 2017-01-24 00:37:47 +09:00
Junegunn Choi
6a75e30941
Allow invisible preview window (--preview-window 0)
Close #820
2017-01-24 00:23:16 +09:00
Junegunn Choi
a3244c4892
Delete every line below the cursor 2017-01-23 22:07:18 +09:00
Junegunn Choi
a5ad8fd3bd
Minor refactoring 2017-01-23 12:55:13 +09:00
Junegunn Choi
deccdb1ec5
Cursor postition response can be preceded by user key strokes 2017-01-23 12:55:11 +09:00
Junegunn Choi
12a43b5e62
Disable mouse if failed to query cursor position 2017-01-23 12:55:04 +09:00
Junegunn Choi
e1291aa6d2
Fix make deps to see the right git dir 2017-01-23 12:10:43 +09:00
Junegunn Choi
bb26f32ac7
Allow build on OpenBSD/FreeBSD/Android
Close #497
2017-01-22 18:51:04 +09:00
Junegunn Choi
4d928001b8
Update release script to upload assets in parallel 2017-01-22 18:33:30 +09:00
Junegunn Choi
c4baa6a10c
Update man page: 24-bit color 2017-01-22 18:33:03 +09:00
Junegunn Choi
71dec3dc5e
Fix bug where screen is not properly cleared on toggle-preview 2017-01-22 17:43:27 +09:00
Junegunn Choi
e5017c0431
Remove unnecesasry test case 2017-01-22 17:41:47 +09:00
Junegunn Choi
cbb5134874
[vim] Use 24-bit colors if termguicolors is set 2017-01-22 14:40:30 +09:00
Junegunn Choi
ff248d566d
Drop ncurses dependency
Close #818
2017-01-22 14:13:37 +09:00
Junegunn Choi
6ccc12c332
Use alternate screen if --height needs the entire screen
- Remove unnecessary scrolling
- Allow us to use `--height 100%` under Neovim terminal for 24-bit colors

Related:
- #789
- https://github.com/neovim/neovim/issues/4151
2017-01-22 05:26:38 +09:00
Junegunn Choi
2a669e9a17
Clear lines even when background color is not set
Also revert the workaround in Vim plugin introduced in fa7c897.

Related: #814
2017-01-22 03:19:50 +09:00
Junegunn Choi
5130abe76f
Merge branch 'master' into devel 2017-01-22 03:10:06 +09:00
Junegunn Choi
fa7c8977a8
[vim] tput el to clear the last line
Close #814

Not grouping commands to avoid errors on non-standard shells.
2017-01-22 03:03:26 +09:00
Junegunn Choi
24fa183297
make deps 2017-01-22 02:54:19 +09:00
Junegunn Choi
131aa5dd15
Composable actions in --bind
Close #816
2017-01-22 02:32:49 +09:00
Junegunn Choi
a06ccc928f
Fix flakies 2017-01-21 04:17:51 +09:00
Junegunn Choi
d09ad13208
[zsh] Workaround trailing esacped space bug in go-shellwords
https://github.com/mattn/go-shellwords/issues/3

Close #812
2017-01-21 03:59:36 +09:00
Junegunn Choi
8ac37d5927
[shell] Do not override --reverse in CTRL-R
Close #807
2017-01-17 18:09:29 +09:00
Junegunn Choi
7ef0e50507
[bash/zsh] Remove unused --reverse in CTRL-R binding
Related #807
2017-01-17 11:58:25 +09:00
Junegunn Choi
62ab8ece5e
0.16.1 2017-01-16 12:27:40 +09:00
Junegunn Choi
8e2e63f9b9
Propertly fill window with background color
Close #805
2017-01-16 12:27:32 +09:00
Junegunn Choi
f96173cbe4
Add -L flag to the default find command
Close #781
2017-01-16 12:01:58 +09:00
Amos Bird
11015df52f Add half-page-{up,down} actions (#784) 2017-01-16 11:58:13 +09:00
Junegunn Choi
05ed57a9f0 Merge pull request #794 from junegunn/devel
0.16.0
2017-01-16 02:43:56 +09:00
Junegunn Choi
4bece04207
0.16.0 2017-01-16 02:39:37 +09:00
Junegunn Choi
ede7bfb901
Optimize LightRenderer for slow terminals 2017-01-16 02:26:36 +09:00
Junegunn Choi
44d3faa048
[completion] Restore --height option for kill completion 2017-01-15 22:02:04 +09:00
Junegunn Choi
e0036b5ad2
Add --filepath-word option
Close #802
2017-01-15 19:42:28 +09:00
Junegunn Choi
208d4f2173
[shell] Make layout configurable via $FZF_DEFAULT_OPTS and $FZF_{KEY}_OPTS 2017-01-15 16:15:51 +09:00
Junegunn Choi
dc3957ce79
[completion] Add preview window to kill completion 2017-01-15 15:06:37 +09:00
Junegunn Choi
4ecb7f3a16
Replace --normalize with --literal and enable normalization by default
Ref #790
2017-01-15 13:22:09 +09:00
Junegunn Choi
03f5ef08c8
Use crypto/ssh/terminal instead of external stty command 2017-01-15 13:10:59 +09:00
Junegunn Choi
2720816266
[vim] Use /dev/tty as STDIN when using --height w/o explicit source 2017-01-15 04:33:03 +09:00
Justin M. Keyes
1896aa1748 s:common_sink(): Avoid duplicate BufEnter. (#803)
Later versions of Vim/Nvim handle `:edit <dir>` inside try-catch.

e13b9afe12
https://github.com/vim/vim/pull/1375
2017-01-14 20:55:30 +09:00
Junegunn Choi
5b68027bee
Fix $FZF_COMPLETION_OPTS evaluation
Close #799
2017-01-14 01:10:34 +09:00
Junegunn Choi
48863ac55c
Update invalid $TERM test case 2017-01-14 01:04:17 +09:00
Junegunn Choi
d64828ce6d
Print error message to stderr on unexpected exit 2017-01-11 23:01:56 +09:00
Junegunn Choi
2aa739be81
Fix bug where occurrence of the pattern in header lines are highlighted 2017-01-11 22:47:26 +09:00
Junegunn Choi
9977a3e9fc
Make preview renderer suspend early on line wrap 2017-01-11 22:13:40 +09:00
Junegunn Choi
f8082bc53a
No need to use /bin/sh to execute stty and tput 2017-01-11 21:48:36 +09:00
Junegunn Choi
996dcb14a3
Make fzf immediately quit when failed to read /dev/tty
Close #798
2017-01-11 02:12:32 +09:00
Junegunn Choi
0c127cfdc1
No need to query row position of the cursor if mouse is disabled 2017-01-10 22:55:55 +09:00
Junegunn Choi
ae274158de
Add experimental support for 24-bit colors 2017-01-10 02:16:12 +09:00
Junegunn Choi
340af463cd
Add --min-height option for percent --height 2017-01-10 01:04:36 +09:00
Junegunn Choi
78a3f81972
Do not use \e[s and \e[u
Excerpt from http://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x361.html:

> - Save cursor position:
>   \033[s
> - Restore cursor position:
>   \033[u
>
> The latter two codes are NOT honoured by many terminal emulators. The
> only ones that I'm aware of that do are xterm and nxterm - even though
> the majority of terminal emulators are based on xterm code. As far as
> I can tell, rxvt, kvt, xiterm, and Eterm do not support them. They are
> supported on the console.

They are also unsupported by Neovim terminal.
2017-01-09 19:09:30 +09:00
Junegunn Choi
d18b8e0d2c
Retry flaky test cases 2017-01-09 13:22:24 +09:00
Junegunn Choi
6c6c0a4778
Make util.RuneWidth return 1 for non-displayable characters
Fix line wrapping in preview window
2017-01-09 10:49:05 +09:00
Junegunn Choi
a16d8f66a9
Normalize pattern string before passing it to Algo function 2017-01-09 09:52:17 +09:00
Junegunn Choi
45793d75c2
Add --normalize option to normalize latin script characters
Close #790
2017-01-09 03:12:23 +09:00
Junegunn Choi
9d545f9578
Fix update of multi-select pointer 2017-01-08 02:29:31 +09:00
Junegunn Choi
a30999a785
Prepare for 0.16.0 release 2017-01-08 02:09:56 +09:00
Junegunn Choi
1a50f1eca1
[vim] Use --height instead of fzf-tmux 2017-01-08 02:09:56 +09:00
Junegunn Choi
1448d631a7
Add --height option 2017-01-08 02:09:56 +09:00
Junegunn Choi
fd137a9e87
[bash/zsh-completion] Filter ~/.ssh/known_hosts
Close #791
2017-01-07 22:46:34 +09:00
Jan Edmund Lazo
3670273719 [vim] Use cmd.exe directly on GVim (launcher='%s') (#787) 2017-01-04 14:07:01 +09:00
Jan Edmund Lazo
6c0fd7f9ca
[vim] FZF command to handle Windows paths with spaces
- Use noshellslash for strict path expansion in fzf#run and s:cmd
  (shellescape depends on shellslash)
- Double-quote the fzf command for cmd.exe
- Add fzf#shellescape to encapsulate the logic
- Close #786
2017-01-02 02:16:25 +09:00
Jan Edmund Lazo
42a2371d26 [vim] Use cmd.exe in Windows (#785) 2017-01-01 11:48:15 +09:00
Junegunn Choi
45faad7e04
[bash] Addendum fix for #580 2017-01-01 02:23:20 +09:00
Junegunn Choi
73eacf1137
[bash-completion] Always backup existing completion definitions
_fzf_completion_loaded is no longer checked. This change increases the
load time by a few milliseconds, but I can't think of a better way to
handle the issue.

Close #783.
2016-12-31 00:30:00 +09:00
Junegunn Choi
7b0d9e1e07
Apply --tabstop to preview window 2016-12-27 01:35:09 +09:00
Pierre Neidhardt
c7b0764002 [shell] Use '-mindepth 1' to omit root folder in 'find' output (#779)
This removes the need for the 'sed' call. Faster, cleaner.
2016-12-24 12:53:07 +09:00
Daniel Hahler
847c512539 s:execute_term: switch_back: check that self.pbuf exists (#776)
With a `bufhidden=wipe` buffer (e.g. vim-startify) the buffer would not
exist anymore, resulting in an error.
2016-12-19 02:51:19 +09:00
Junegunn Choi
97330ee8fc
No need to set MANPATH
Close #774
2016-12-17 11:20:28 +09:00
Pierre Neidhardt
0508e70f9b Overhaul fish functions (#759)
Replace the "temp file" workaround with the "read" function: it's
simpler and faster.

Use proper escaping, remove the custom function.

The "file" widget uses last token as root for the "find" command.
This replaces the equivalent of '**' completion in bash/zsh.
The "$dir" non-expanded variable can be used in FZF_CTRL_T_COMMAND to
set the root.
2016-12-14 15:37:27 +09:00
Marco Hinz
8a502af4c1 Neovim: event handlers always expect three arguments (#768) 2016-12-14 01:56:53 +09:00
Junegunn Choi
c60bfb2b0f
[neovim] Keep alternate file unchanged
Close https://github.com/junegunn/fzf.vim/issues/265
2016-12-11 22:32:59 +09:00
Junegunn Choi
16b5902aa2
Fix Linux build (#756) 2016-12-05 02:27:38 +09:00
Junegunn Choi
a442fe0fd0
Truncate long lines in preview window
Add `:wrap` to --preview-window to wrap lines instead

Close #756
2016-12-05 02:13:59 +09:00
Junegunn Choi
ab9ae4f643
[vim] Fix path display in FZF when cwd is ~ 2016-12-03 01:13:56 +09:00
Junegunn Choi
d9a51030ea
[vim] Display relative path in prompt 2016-12-02 21:07:23 +09:00
Junegunn Choi
67026718c1
Add BUILD.md 2016-11-27 15:16:53 +09:00
Junegunn Choi
a71c471405
0.15.9 2016-11-26 12:36:24 +09:00
Junegunn Choi
3858086047
Always print scroll indicator in preview window 2016-11-26 12:34:16 +09:00
Junegunn Choi
dffef3d9f3
Update build instructions for ncurses 6 and tcell
Close #357
Close #738
2016-11-26 11:41:57 +09:00
Junegunn Choi
de1c6b8727
[tcell] 24-bit color support
TAGS=tcell make install

    printf "\x1b[38;2;100;200;250mTRUECOLOR\x1b[m\n" |
        TERM=xterm-truecolor fzf --ansi
2016-11-26 00:36:38 +09:00
Junegunn Choi
6f17f412ba
Workaround for rendering glitch in case of short-lived input process
: | fzf --preview 'echo foo'
2016-11-25 14:05:37 +09:00
Junegunn Choi
746961bf43
[ncurses6] Suppress tui.Italic on ncurses 5 2016-11-24 13:42:14 +09:00
Junegunn Choi
182a6d99fd
[ncurses6] Support italics 2016-11-24 00:13:10 +09:00
Junegunn Choi
af31088481
[ncurses6] Use wcolor_set to support more than 256 color pairs
To build fzf with ncurses 6 on macOS:

    brew install homebrew/dupes/ncurses
    LDFLAGS="-L/usr/local/opt/ncurses/lib" make install
2016-11-24 00:12:43 +09:00
Junegunn Choi
43425158f4
Make escape delay configurable via ncurses standard $ESCDELAY
Also reduce the default delay to 50ms. We should not set it to 0ms as it
breaks escape sequences on WSL. If 50ms is not enough, one can increase
the delay by setting $ESCDELAY to a larger value.
2016-11-23 02:28:03 +09:00
Junegunn Choi
8524ea7441
Do not ignore resize event from ncurses and tcell 2016-11-23 01:58:46 +09:00
Junegunn Choi
6a65006f55
0.15.8 2016-11-19 23:13:26 +09:00
Junegunn Choi
d75ed841a9
Fix --no-bold on --no-color 2016-11-19 23:12:28 +09:00
Junegunn Choi
3cd2547e91
Reduce ESC delay to 100ms 2016-11-19 23:03:27 +09:00
Junegunn Choi
8c661d4e8c
Revamp escape sequence processing for WSL
Also add support for alt-[0-9] and f1[12]
2016-11-19 22:42:15 +09:00
Junegunn Choi
4b332d831e
Add --no-bold option 2016-11-15 23:57:32 +09:00
Junegunn Choi
22487810ba
Update README: link to wiki page 2016-11-15 23:44:04 +09:00
Junegunn Choi
c49e65d926
[shell] Fix pruning condition of find command for CTRL-T and ALT-C
`-fstype dev` is invalid. It's devfs on macOS and devtmpfs on Linux.
2016-11-15 01:52:54 +09:00
Junegunn Choi
2e8814bb57
Add WSL to .github/ISSUE_TEMPLATE.md 2016-11-14 12:26:46 +09:00
Junegunn Choi
dc557c0d4c
Update ANSI processor to handle more VT-100 escape sequences
The updated regular expression should include not all but most of the
frequently used ANSI sequences. Close #735.
2016-11-14 02:15:23 +09:00
Junegunn Choi
a2beb159f1
0.15.7 2016-11-09 12:41:46 +09:00
Junegunn Choi
7ce427ff47
Fix panic when color is disabled and header lines contain ANSI colors
Close #732
2016-11-09 12:05:45 +09:00
Junegunn Choi
a221c672fb
0.15.6 2016-11-09 01:45:27 +09:00
Junegunn Choi
f87d382ec8
Fix --color=bw on tcell build 2016-11-09 01:45:06 +09:00
Junegunn Choi
3dfc020fac Merge pull request #730 from laur89/master
Minor README markup fix
2016-11-09 00:06:42 +09:00
Laur Aliste
2d87896939 Minor README markup fix. 2016-11-08 15:41:46 +01:00
Junegunn Choi
2192d8d816
GOOS=windows make release 2016-11-08 03:32:41 +09:00
Junegunn Choi
d206949f62
Wait for additional keys after ESC for up to 100ms
Close #661
2016-11-08 03:07:26 +09:00
Junegunn Choi
4accc69022
Fix flaky test cases 2016-11-08 02:19:05 +09:00
Junegunn Choi
898d8d94c8
Fix issues in tcell renderer and Windows build
- Fix display of CJK wide characters
- Fix horizontal offset of header lines
- Add support for keys with ALT modifier, shift-tab, page-up and down
- Fix util.ExecCommand to properly parse command-line arguments
- Fix redraw on resize
- Implement Pause/Resume for execute action
- Remove runtime check of GOOS
- Change exit status to 2 when tcell failed to start
- TBD: Travis CI build for tcell renderer
    - Pending. tcell cannot reliably ingest keys from tmux send-keys
2016-11-08 02:06:34 +09:00
Michael Kelley
26895da969
Implement tcell-based renderer 2016-11-07 02:32:14 +09:00
Junegunn Choi
0c573b3dff
Prepare for termbox/windows build
`TAGS=termbox make` (or `go build -tags termbox`)
2016-11-07 02:32:14 +09:00
Junegunn Choi
2cff00dce2
man fzf in README
Close #726
2016-11-01 00:39:02 +09:00
Junegunn Choi
06a6ad8bca
Update ANSI processor to ignore ^N and ^O
This reverts commit 02c6ad0e59be75981baeb1f41cb0bad03aad1c6b.
2016-10-30 12:29:29 +09:00
Junegunn Choi
02c6ad0e59
Strip ^N and ^O from preview output
https://github.com/junegunn/fzf/issues/391#issuecomment-257090266

e.g. fzf --preview 'printf "$(tput setaf 2)foo$(tput sgr0)bar\nbar\n"'
2016-10-30 11:43:06 +09:00
Junegunn Choi
9f321cbe13
Fix header lines being cleared on toggle-preview
Close #722
2016-10-28 03:13:50 +09:00
Junegunn Choi
9f30ca2923
0.15.5 2016-10-23 22:00:32 +09:00
Junegunn Choi
37f2d8f795
[vim] Respect g:fzf_colors
Close #711
2016-10-22 01:14:16 +09:00
Junegunn Choi
400e443a0a
Make test cases less susceptible to timeout errors 2016-10-22 00:01:21 +09:00
Junegunn Choi
0a8d2996dc
Set foreground color without affecting background
Close #712
2016-10-21 19:35:59 +09:00
Junegunn Choi
cfdb00b971
Allow other options to follow --color without spec 2016-10-21 19:20:16 +09:00
Junegunn Choi
9b9ad39143
[vim] Set g:loaded_fzf 2016-10-18 15:00:47 +09:00
Junegunn Choi
0541c0dbcf
Use relative position instead of absolute distance for --tiebreak=end
Fix unintuitive result where `*fzf*/install` is ranked higher than
`fzf/src/fzf/*fzf*-linux_386` on --tiebreak=end.
2016-10-18 01:13:57 +09:00
Junegunn Choi
47b11cb8b4 Merge pull request #701 from nthapaliya/zsh_script_improvements
[zsh] GNU coreutils compatibility
2016-10-14 10:00:58 +09:00
Niraj Thapaliya
d3da310b92 Use command to ignore shell function 2016-10-13 09:53:24 -06:00
Niraj Thapaliya
93e0a6a9de Gnu [ evaluates both sides of a -o condition regardless
It doesn't short circuit like we expect, causing trouble when $dir is
empty

Use shell builtin instead
2016-10-13 09:52:49 -06:00
Junegunn Choi
ac549a853a
[fzf-tmux] Fix bash condition
Fix #702
2016-10-13 10:42:26 +09:00
Junegunn Choi
053af9a1c8
[fzf-tmux/vim/nvim] Do not split small window
Close #699
2016-10-12 23:10:21 +09:00
Junegunn Choi
60112def02 Merge pull request #698 from Ambrevar/master
[fish] Yank commandline in fzf-history-widget
2016-10-12 01:54:51 +09:00
Pierre Neidhardt
2134c0c8a9 key-bindings.fish: Yank commandline in fzf-history-widget 2016-10-11 21:15:00 +05:30
Junegunn Choi
3222d62ddf
0.15.4 2016-10-04 02:17:36 +09:00
Junegunn Choi
aeb957a285
Use exact match by default for inverse search term
This is a breaking change, but I believe it makes much more sense. It is
almost impossible to predict which entries will be filtered out due to
a fuzzy inverse term. You can still perform inverse-fuzzy-match by
prepending `!'` to the term.

| Token    | Match type                 | Description                       |
| -------- | -------------------------- | --------------------------------- |
| `sbtrkt` | fuzzy-match                | Items that match `sbtrkt`         |
| `^music` | prefix-exact-match         | Items that start with `music`     |
| `.mp3$`  | suffix-exact-match         | Items that end with `.mp3`        |
| `'wild`  | exact-match (quoted)       | Items that include `wild`         |
| `!fire`  | inverse-exact-match        | Items that do not include `fire`  |
| `!.mp3$` | inverse-suffix-exact-match | Items that do not end with `.mp3` |
2016-10-04 02:09:03 +09:00
Junegunn Choi
154cf22ffa
Display scroll indicator in preview window 2016-10-04 01:40:45 +09:00
Junegunn Choi
51f532697e
Adjust maximum scroll offset
It was possible that a few lines at the bottom may not be visible if
there are lines above that span multiple lines.
2016-10-04 01:39:48 +09:00
Junegunn Choi
01b88539ba
[vim] Apply --multi and --prompt to :FZF command 2016-10-04 00:30:04 +09:00
Junegunn Choi
3066b206af
Support field index expressions in preview and execute action
Also close #679. The placeholder for the current query is {q}.
2016-10-03 14:33:28 +09:00
Junegunn Choi
04492bab10
Use unicode.IsSpace to cover more whitespace characters 2016-09-29 22:40:22 +09:00
Junegunn Choi
8b0d0342d4
0.15.3 2016-09-29 03:05:20 +09:00
Junegunn Choi
957c12e7d7
Fix SEGV when trying to render preview but the window is closed
Close #677
2016-09-29 02:53:05 +09:00
Junegunn Choi
3b5ae0f8a2
Fix failing unit tests on ANSI attributes 2016-09-29 01:06:47 +09:00
Junegunn Choi
1fc5659842
Add support for more ANSI color attributes (#674)
Dim, underline, blink, reverse
2016-09-29 00:54:27 +09:00
Junegunn Choi
1acd2adce2
Update man page: missing actions 2016-09-26 15:33:46 +09:00
Junegunn Choi
1bc223d4b3
0.15.2 2016-09-25 22:20:43 +09:00
Junegunn Choi
bef405bfa5
Ignore VT100-related escape codes 2016-09-25 19:03:08 +09:00
Junegunn Choi
0612074abe
Support high intensity colors
Close #671
2016-09-25 18:11:35 +09:00
Junegunn Choi
3bf51d8362 Merge pull request #670 from maverickwoo/fix-668
[bash-completion] Fix #668
2016-09-25 05:15:24 +09:00
Maverick Woo
2c8479a7c5 Fix #668
Handle uppercase letters in program names. This also deals with `-` and
`.`, both of which are quite common in program names, e.g., `xdg-open`
and `foo.sh`.
2016-09-24 15:39:13 -04:00
Junegunn Choi
8c8b5b313e Add preview-page-up and preview-page-down actions 2016-09-25 04:12:44 +09:00
Junegunn Choi
66d55fd893
Make preview windows scrollable
Close #669

You can use your mouse or binadble preview-up and preview-down actions
to scroll the content of the preview window.

    fzf --preview 'highlight -O ansi {}' --bind alt-j:preview-down,alt-k:preview-up
2016-09-25 02:02:00 +09:00
Junegunn Choi
7fa5e6c861
0.15.1 2016-09-21 01:28:24 +09:00
Junegunn Choi
00f96aae76
Avoid rendering delay when displaying extremely long lines
Related #666
2016-09-21 01:23:41 +09:00
Junegunn Choi
a749e6bd16
Fix temp directory in a test case 2016-09-21 01:15:35 +09:00
Junegunn Choi
791076d366
Fix panic when pattern occurs after 2^15-th column
Fix #666
2016-09-21 01:15:06 +09:00
Junegunn Choi
37f43fbb35
Add --print0 option
Related: #660
2016-09-19 01:15:38 +09:00
Junegunn Choi
401a5fd5ff
Printable character in --expect set should not affect --print-query 2016-09-18 14:34:50 +09:00
Junegunn Choi
1854922f0c
Truncate the query string if it's too long
Use hard-coded limit to keep it simple. An alternative is to dynamically
calculate the width of the visible area and use it as the limit, but it
can cause unwanted truncation of the query on screen resize/split.
2016-09-18 14:34:48 +09:00
Junegunn Choi
2fc7c18747
Revise ranking algorithm 2016-09-18 14:34:46 +09:00
Junegunn Choi
8ef2420677
Update README 2016-09-13 04:12:03 +09:00
Junegunn Choi
cf6f4d74c4 Merge pull request #657 from ishanray/patch-1
Fix typo in comment
2016-09-11 12:13:40 +09:00
ishanray
f44d40f6b4 Update algo.go 2016-09-10 23:40:55 +04:00
Junegunn Choi
1c81a58127 Merge pull request #654 from qiemem/fix-tmux-groups-dont-break-sockets
[fzf-tmux] Make fzf target correct session in group
2016-09-07 21:36:32 +09:00
Bryan Head
9baf7c4874 Make fzf target correct session in group
Fixes #643
Doesn't break #648
2016-09-06 13:03:07 -05:00
Junegunn Choi
22b089e47e
Revert "Unset TMUX before splitting window" (#648)
This reverts commit 4d4447779fe4c3ff05c2e6809c1b0c5d754d9772.
2016-08-31 14:20:29 +09:00
Junegunn Choi
b166f18220 Merge pull request #646 from qiemem/fix-tmux-groups
[fzf-tmux] Fix grouped tmux session confusion
2016-08-29 12:47:43 +09:00
Junegunn Choi
68600f6ecf Merge pull request #645 from ckafi/split-without-IFS
[zsh-completion] Split default zsh binding at the correct place
2016-08-29 12:47:14 +09:00
Bryan Head
4d4447779f Unset TMUX before splitting window
Avoids confusing grouped sessions.
Fixes #643
2016-08-28 16:57:38 -05:00
Tobias Frilling
639de4c27b Split default zsh binding at the correct place
The command substitution and following word splitting to determine the default
zle widget for ^I formerly only works if the IFS parameter contains a space. Now
it specifically splits at spaces, regardless of IFS.
2016-08-28 20:34:36 +02:00
Junegunn Choi
d87390934e
[neovim] Do not resize if the size of the screen has changed
Related #642
2016-08-28 19:27:18 +09:00
Junegunn Choi
411ec2e557
Merge branch 'joshuarubin-master' 2016-08-28 19:18:13 +09:00
Joshua Rubin
f025602841
[vim] Reset window sizes on close
Fix #520
Fix junegunn/fzf.vim#42
2016-08-28 19:17:24 +09:00
Junegunn Choi
f958c9daf5
[vim] Tilde prefix is not allowed for left or right layout 2016-08-24 01:15:35 +09:00
Junegunn Choi
b86838c2b0
0.13.5 2016-08-21 05:02:45 +09:00
Junegunn Choi
1f7d1f9b15
Update Centos Dockerfile to use Go 1.7 2016-08-21 04:54:53 +09:00
Junegunn Choi
f8fdf9618a
No need to cache the result in filtering mode (--filter) 2016-08-20 02:06:57 +09:00
Junegunn Choi
827a83efbc
Remove Offset slice from Result struct 2016-08-20 01:53:32 +09:00
Junegunn Choi
3e88849386
[vim] Fix "E706: Variable type mismatch for: arg" 2016-08-19 18:02:32 +09:00
Junegunn Choi
608c416207
Add missing sources 2016-08-19 03:27:42 +09:00
Junegunn Choi
62f6ff9d6c
[vim] Make arguments to fzf#wrap() optional
fzf#wrap([name string,] [opts dict,] [fullscreen boolean])
2016-08-19 03:05:22 +09:00
Junegunn Choi
37dc273148
Micro-optimizations
- Make structs smaller
- Introduce Result struct and use it to represent matched items instead of
  reusing Item struct for that purpose
- Avoid unnecessary memory allocation
- Avoid growing slice from the initial capacity
- Code cleanup
2016-08-19 02:39:32 +09:00
Junegunn Choi
f7f01d109e
Set the upper limit of the number of search go routines 2016-08-19 01:55:38 +09:00
Junegunn Choi
01ee335521
Remove duplicate code 2016-08-18 03:11:54 +09:00
Junegunn Choi
0e0de29b87
Inline function calls in tight loops
By only using leaf functions
2016-08-18 01:48:52 +09:00
Junegunn Choi
babf877fd6
Increase the number of go routines for search
Sort performance increases as the size of each sublist decreases (n in
nlog(n) decreases). Merger is then responsible for merging the sorted
lists in order, and since in most cases we are only interesed in the
matches in the first page on the screen so the overhead in the process
is negligible.
2016-08-18 01:46:05 +09:00
Junegunn Choi
935272824e
Setting GOMAXPROCS is no longer needed
https://golang.org/doc/go1.5
2016-08-17 02:21:33 +09:00
Junegunn Choi
3a9532c8fd
Increase read buffer size to 64KB 2016-08-16 02:06:15 +09:00
Junegunn Choi
c4c92142a6
0.13.4 2016-08-14 18:10:21 +09:00
Junegunn Choi
d4b6338102
Lint 2016-08-14 17:51:34 +09:00
Junegunn Choi
8df7d962e6
Improve rendering time of long lines 2016-08-14 17:44:11 +09:00
Junegunn Choi
41e916a511
[perf] evaluateBonus can start from sidx - 1 2016-08-14 11:58:47 +09:00
Junegunn Choi
d9c8a9a880
[perf] Remove memory copy when using string delimiter 2016-08-14 04:30:55 +09:00
Junegunn Choi
ddc7bb9064
[perf] Optimize AWK-style tokenizer for --nth
Approx. 50% less memory footprint and 40% improvement in query time
2016-08-14 02:19:29 +09:00
Junegunn Choi
1d4057c209
[perf] Avoid allocating rune array for ascii string
In the best case (all ascii), this reduces the memory footprint by 60%
and the response time by 15% to 20%. In the worst case (every line has
non-ascii characters), 3 to 4% overhead is observed.
2016-08-14 00:41:30 +09:00
Junegunn Choi
822b86942c
[test] Clear environment variables 2016-08-13 19:26:36 +09:00
Junegunn Choi
1e74dbb937
:hidden property of previous --preview-window should be cleared
Fix #636. Patch suggested by @edi9999.
2016-08-12 01:16:59 +09:00
Junegunn Choi
7cef92fffe
[vim] Delete fzf buffer even when exit status is non-zero
Fix #183
2016-08-02 03:30:17 +09:00
Junegunn Choi
42e4992f06
[vim] Make sure to delete fzf buffer
Close junegunn/fzf.vim#173 and #630
2016-08-02 02:25:02 +09:00
Junegunn Choi
a6066175c6 Merge pull request #630 from kassio/master
Remove `name` option from `termopen`.
2016-07-30 19:05:43 +09:00
Kassio Borges
27444d6b1e Remove name option from termopen.
`termopen` no longer accepts a `name` option, instead we should suffix the
command with `;#NAME`.
2016-07-29 11:10:46 +01:00
Junegunn Choi
d6a99c0391
[vim] v:shell_error can change around redraw!
Patch suggested by Mariusz Atamańczuk
2016-07-28 01:41:11 +09:00
Junegunn Choi
f787f7e651
[vim] Add fzf#wrap helper function
Close #627
2016-07-26 02:37:12 +09:00
Junegunn Choi
a7c9c08371
[vim] Make :FZF command configurable with g:fzf_layout
To make it consistent with the other commands in fzf.vim
2016-07-21 01:47:08 +09:00
Junegunn Choi
fccc93176b
0.13.3 2016-07-16 01:06:53 +09:00
Junegunn Choi
6439a138fe
[install] Build fzf if prebuilt binary doesn't work
Close #617
2016-07-16 00:36:35 +09:00
Junegunn Choi
a9a29dff4f
Fix duplicate rendering of the last line in preview window 2016-07-15 23:24:14 +09:00
Junegunn Choi
6a52f8b8dd
[zsh-completion] setopt localoptions noksh_arrays
Close #607
2016-07-15 01:26:29 +09:00
Junegunn Choi
a1049328d6
[vim] Adjust split size when --header option is set
Close #622
2016-07-14 13:35:18 +09:00
Junegunn Choi
5c2b96bd00
[vim] Fix error with multi-line $FZF_DEFAULT_COMMAND
Close #620
2016-07-13 13:15:14 +09:00
Junegunn Choi
c36413fdf6
[zsh] Suppress error message when pipefail is not supported
Close #615
2016-07-11 17:47:41 +09:00
Junegunn Choi
52cf5af91c
[test] Fix test failure on Travis CI
No guarantee in the order in which files are listed
2016-07-10 15:44:44 +09:00
Junegunn Choi
3a4e053af7
[bash] Fall back to send-keys if named paste buffer is not supported
Related: #616
2016-07-10 15:21:28 +09:00
Junegunn Choi
049bc9ec68
[fzf-tmux] Add man page 2016-07-10 14:44:00 +09:00
Junegunn Choi
b461a555b8
[fzf-tmux] Add --version and --help flags 2016-07-10 14:41:06 +09:00
Junegunn Choi
0f87b2d1e1
[fzf-tmux] Use double brackets
For consistency and (negligible) performance improvement
2016-07-10 14:34:29 +09:00
Junegunn Choi
0fb5b76c0d
[fzf-tmux] Fail fast if fzf excutable is not found 2016-07-10 14:28:58 +09:00
Junegunn Choi
0c918dd87a Merge pull request #616 from seanlaguna/master
Use tmux buffers for sending output to preserve character encoding
2016-07-10 12:29:32 +09:00
Junegunn Choi
05299a0fee
[test] Use tmux buffer in unicode test cases
Related #616
2016-07-10 12:27:01 +09:00
Sean
b36b0a91f5 use tmux buffers for sending output to preserve character encoding 2016-07-09 09:47:20 -05:00
Junegunn Choi
6081eac58a
[shell] Suppress alias/function expansion
Close #611
2016-07-07 01:40:14 +09:00
Junegunn Choi
942ba749c7
[vim] Restore working directory even when new window is opened
Close #612
2016-07-06 13:31:04 +09:00
Junegunn Choi
f941012687 Merge pull request #610 from eigengrau/master
[zsh] Re-initialize zle when widgets finish
2016-07-05 21:50:43 +09:00
Sebastian Reuße
fed5e5d5af [zsh] Re-initialize zle when widgets finish
zle automatically calls zle-line-init when it starts to read a new line. Many
Zsh setups use this hook to set the terminal into application mode, since this
will then allow defining keybinds based on the $terminfo variable (the escape
codes in said variable are only valid in application mode).

However, fzf resets the terminal into raw mode, rendering $terminfo values
invalid once the widget has finished. Accordingly, keyboard bindings defined
via $terminfo won’t work anymore.

This fixes the issue by calling zle-line-init when widgets finish. Care is taken
to not call this widget when it is undefined.

Fixes #279
2016-07-05 08:57:11 +02:00
Junegunn Choi
b864885753
[install] Make sure to unset pipefail 2016-07-04 13:05:26 +09:00
Junegunn Choi
64747c2324
[install] Fix error in install script
Close #608
2016-07-04 13:00:30 +09:00
Junegunn Choi
34965edcda
[install] Fall back to wget if curl failed
Close #605
2016-07-04 01:41:43 +09:00
Junegunn Choi
bd4377084d Merge pull request #601 from blueyed/zsh-ret-for-fzf-file-widget
zsh: pass through exit code from fzf with fzf-file-widget
2016-06-18 23:17:10 +09:00
Daniel Hahler
38a2076b89 zsh: pass through exit code from widgets
This allows to have a custom widget like the following, which would
additionally accept the line, but only in case of entries being
selected:

    fzf-file-widget-with-accept() {
      zle fzf-file-widget
      if [[ "$?" == 0 ]] && (( $#BUFFER )); then
        zle accept-line
      fi
    }
    zle     -N   fzf-file-widget-with-accept
    bindkey '\e^T' fzf-file-widget-with-accept

With this `<C-a>t` will launch fzf, and simulate the pressing of "Enter"
afterwards.
2016-06-16 20:20:29 +02:00
Junegunn Choi
5759d50d4a
0.13.2 2016-06-16 02:16:13 +09:00
Junegunn Choi
e455836cc9
Fix race condition where preview window is not properly cleared 2016-06-15 13:15:17 +09:00
Junegunn Choi
8a90f26c8a
0.13.1 2016-06-14 21:53:00 +09:00
Junegunn Choi
24e1fabf2e
Do not process ANSI codes in --preview output at once
Close #598
2016-06-14 21:52:47 +09:00
Junegunn Choi
c39c039e15
[shell] Add $FZF_CTRL_T_OPTS and $FZF_ALT_C_OPTS
Close #596
2016-06-12 20:48:23 +09:00
Junegunn Choi
07f176f426 Merge pull request #595 from aykamko/speed-up-fzf-completion
Optimize fzf_default_completion binding
2016-06-12 11:56:49 +09:00
Aleks Kamko
19339e3a6d optimize fzf_default_completion binding 2016-06-11 15:19:16 -07:00
Junegunn Choi
3e1d6a7bcf
0.13.0 2016-06-12 02:15:11 +09:00
Junegunn Choi
2bbc12063c
Add --preview and --preview-window
Close #587
2016-06-11 19:59:12 +09:00
Junegunn Choi
b8737b724b
Ignore controls chars for bracketed paste mode
Close #594
2016-06-11 12:14:34 +09:00
Junegunn Choi
d91c3a2f5e Merge pull request #593 from edi9999/master
Add fzf_prefer_tmux option
2016-06-10 22:58:58 +09:00
Edgar Hipp
fe5db5aadc Add fzf_prefer_tmux option 2016-06-10 09:05:05 +02:00
Junegunn Choi
cf9c957c66
Update test_execute_shell (#590) 2016-06-08 02:16:07 +09:00
Junegunn Choi
68b60c6d19
Update test_execute_multi (#590) 2016-06-08 02:15:22 +09:00
Junegunn Choi
3a644b16a4
Update test_execute (#590) 2016-06-08 02:04:40 +09:00
Junegunn Choi
95b34de339
[bash/zsh] Fix $FZF_CTRL_R_OPTS with option values with spaces 2016-06-08 01:30:26 +09:00
Junegunn Choi
6a431cbf49
[fzf-tmux] Escape $ in arguments
e.g. fzf-tmux -q '$PATH'

Related: #343
2016-06-08 01:27:22 +09:00
Junegunn Choi
56fb2f00b3
Use single-quoted strings in execute action
Close #590
2016-06-08 00:54:21 +09:00
Junegunn Choi
1c86aaf342
[vim/fzf-tmux] Handle fzf project directory with spaces
Close #583
2016-06-03 12:09:31 +09:00
Junegunn Choi
cfc0b18eaa
Revert "Change tmux pane title for fzf splits"
This reverts commit f074709fc96291f9a7d41d1fda9d9193c92b57e0.

Close #586. /cc @akashin
2016-06-03 12:02:21 +09:00
Junegunn Choi
412c211655
[vim] Use lcd instead of chdir
https://github.com/junegunn/fzf.vim/issues/147
2016-06-02 22:24:47 +09:00
Junegunn Choi
923feb69ab
[zsh] Fix indentation 2016-06-02 22:01:26 +09:00
Junegunn Choi
92dba7035a
Merge pull request #584 from jimbocoder/master
Take SSH completion hints from known_hosts
2016-06-02 21:58:59 +09:00
Jim Howell
b8a3ba16a2
[bash/zsh] Take SSH completion hints from known_hosts
Signed-off-by: Junegunn Choi <junegunn.c@gmail.com>
2016-06-02 21:58:01 +09:00
Junegunn Choi
cd5e4d9402 Merge pull request #582 from akashin/master
[fzf-tmux] Change tmux pane title for fzf splits
2016-06-01 17:07:07 +09:00
Andrey Kashin
f074709fc9 Change tmux pane title for fzf splits 2016-06-01 10:19:26 +03:00
Junegunn Choi
e0b29e437b
[bash] Use backticks to avoid delay with blink-matching-paren
Close #580
2016-05-29 02:11:50 +09:00
Junegunn Choi
bdb94fba7d
[zsh] Fix #579 - Locally unset globsubst 2016-05-26 00:52:06 +09:00
Junegunn Choi
2f364c62f4
0.12.2 2016-05-19 01:55:54 +09:00
Junegunn Choi
7ed9f83662
Validate jump label characters
Also extend default jump labels
2016-05-19 01:46:22 +09:00
Junegunn Choi
f498a9b3fb
Revert version number 2016-05-18 22:47:57 +09:00
Junegunn Choi
13330738b8
Do not match jump labels beyond the screen limit 2016-05-18 22:45:34 +09:00
Junegunn Choi
e53535cc61
Update default jump labels 2016-05-18 22:44:31 +09:00
Junegunn Choi
c62fc5e75c
More named keys: F5 ~ F10, ALT-/ 2016-05-18 22:25:09 +09:00
Junegunn Choi
70245ad98c
[make] Reduce the size of the binaries with -ldflags -w
Related: #555
2016-05-18 13:29:27 +09:00
Junegunn Choi
6d235bceee
Add jump and jump-accept actions for --bind
jump and jump-accept implement EasyMotion-like movement in fzf.
Suggested by @mhrebenyuk. Close #569.
2016-05-18 02:10:03 +09:00
Junegunn Choi
4adebfc856
[install] go get -u github.com/junegunn/fzf/src/fzf 2016-05-17 01:41:59 +09:00
Junegunn Choi
faccc0a410
[fzf-tmux] Escape backslash in command-line arguments 2016-05-15 17:07:34 +09:00
Junegunn Choi
9078688baf Add print-query action for --bind
Close #571
2016-05-13 00:51:15 +09:00
Junegunn Choi
9bd8b1d25f
Fix typo 2016-05-13 00:44:33 +09:00
Junegunn Choi
dd4be1da38
Allow alt-enter and alt-space for --bind (#571) 2016-05-13 00:43:50 +09:00
Junegunn Choi
66f86e1870
[fzf-tmux] Fix #562 - Check $TMUX instead of $TMUX_PANE 2016-05-11 22:08:14 +09:00
Junegunn Choi
4ab75b68dc
Fix flaky test case: test_execute
Should wait until execute action completes
2016-05-11 01:40:49 +09:00
Junegunn Choi
73cb70dbb3
Fix flaky test case: test_file_completion_unicode 2016-05-11 01:25:17 +09:00
Junegunn Choi
d082cccb6d
Fix flaky test case: test_ctrl_t_unicode
The width of the pseudo-terminal on Travis CI environment can be small
and cause the line to be wrapped.
2016-05-11 01:18:26 +09:00
Junegunn Choi
88a80e3c2c
Determine 256-color capability using tigetnum("colors")
Close #570
2016-05-11 01:07:06 +09:00
Junegunn Choi
24516bcf4d [install] Set a temporary GOPATH 2016-05-09 02:03:08 +09:00
Junegunn Choi
b4c4a642ed Update README
Close #560, #561
2016-05-03 00:07:53 +09:00
Junegunn Choi
0231617857 [neovim] Fix issues with enew and tabnew layouts
Related: #559
2016-04-28 01:25:24 +09:00
Junegunn Choi
7f64fba80f Update Makefile to allow build on i686 (#555) 2016-04-26 01:49:02 +09:00
Junegunn Choi
633aec38f5 Merge pull request #554 from gene-pavlovsky/patch-1
Fix missing reference to UNAME_M
2016-04-25 23:04:29 +09:00
Gene Pavlovsky
d1b402a23c Fix missing reference to UNAME_M
The `Build on $(UNAME_M) is not supported, yet` message was referencing an undefined UNAME_M. Fixed that.
2016-04-24 21:24:10 +03:00
Junegunn Choi
35a9aff8e1 0.12.1 2016-04-25 01:23:52 +09:00
Junegunn Choi
988c9bd9be [zsh] Fix issues with unicode characters 2016-04-25 01:04:35 +09:00
Junegunn Choi
095f31b316 [vim] Explicitly set source to FZF_DEFAULT_COMMAND
Helps when your `$SHELL` is slow.

Close #552.
2016-04-24 18:02:01 +09:00
Junegunn Choi
d86cee2a69 [bash] Export fzf-file-widget function for bash 4+ (#546)
e.g. Remapping fzf-file-widget to CTRL-X CTRL-T intead of CTRL-T

    bind -x '"\C-x\C-t": fzf-file-widget'
    bind '"\C-t": transpose-chars'
2016-04-24 14:04:15 +09:00
Junegunn Choi
e986f20a85 [fish] Use consistent function names for key bindings (#546)
- fzf-file-widget
- fzf-history-widget
- fzf-cd-widget
2016-04-24 13:56:50 +09:00
Junegunn Choi
c727ba1d99 [fzf-tmux] Do not split pane if the height is too small 2016-04-24 13:32:33 +09:00
Junegunn Choi
bb70923cd8 Fix flaky test cases 2016-04-24 04:52:01 +09:00
Junegunn Choi
772fa42dcb [fish] Fix intermittent errors on CTRL-T
Related: 23244bb
2016-04-24 04:51:35 +09:00
Junegunn Choi
85ef3263fc Fix incorrect cache reference in --exact mode (#547)
When we prepend a single quote to our query in --exact mode, we are not
supposed to limit the scope of the new search to the previous
exact-match result.
2016-04-24 03:43:24 +09:00
Junegunn Choi
4bde8de63f Apply new ranking algorithm to exact match as well 2016-04-23 19:48:06 +09:00
Junegunn Choi
654a7df9b0 [neovim] Set bufhidden and nobuflisted after opening terminal 2016-04-23 17:53:54 +09:00
Junegunn Choi
c3aa836ec0 [bash] Update completion.bash
[bash] Update completion.bash
2016-04-23 11:36:50 +09:00
Junegunn Choi
95764bef6f Merge pull request #550 from gene-pavlovsky/gene-pavlovsky-patch-2
[bash] Update key-bindings.bash
2016-04-23 11:35:27 +09:00
Gene Pavlovsky
63dbf48546 Update key-bindings.bash
Faster startup. Use internal bash globbing instead of external grep binary (adapter from Gentoo's `/etc/bash/bashrc` TERM checking). Insignificant on Linux, but on Cygwin this cuts startup time by 40 ms on my Core i7 laptop.
2016-04-23 03:44:41 +03:00
Gene Pavlovsky
e2401350a3 Update completion.bash
Fixes #548. Avoid using a subshell in _fzf_defc().
2016-04-23 03:12:15 +03:00
Junegunn Choi
e867355b2a [neovim] Restore winfixwidth and winfixheight
Fix https://github.com/junegunn/fzf.vim/issues/128
2016-04-21 00:33:30 +09:00
Junegunn Choi
b28c14b93a 0.12.0 2016-04-16 14:45:16 +09:00
Junegunn Choi
879ead210f 0.11.2 2016-04-16 14:37:16 +09:00
Junegunn Choi
2f6d23b91e Enhanced ranking algorithm
Based on the patch by Matt Westcott (@mjwestcott).
But with a more conservative approach:
- Does not use linearly increasing penalties; It is agreed upon that we
  should prefer matching characters at the beginnings of the words, but
  it's not always clear that the relevance is inversely proportional to
  the distance from the beginning.
- The approach here is more conservative in that the bonus is never
  large enough to override the matchlen, so it can be thought of as the
  first implicit tiebreak criterion.
- One may argue the change breaks the contract of --tiebreak, but the
  judgement depends on the definition of "tie".
2016-04-16 14:33:38 +09:00
Junegunn Choi
5f63a7b587 Fix flaky test case 2016-04-15 12:57:38 +09:00
Junegunn Choi
d9ce797d88 Merge pull request #540 from WChargin/bash-vimode-delay-fix
[bash] Fix vi mode pre-launch delay
2016-04-15 09:15:12 +09:00
William Chargin
12230f8043 Fix bash-vimode normal-mode cd completion 2016-04-14 13:20:21 -04:00
William Chargin
0c8de1ca44 Fix Bash+vimode pre-launch delay
Summary:
Fix adapted from [@adamheins: fzf, vi-mode, and fixing delays][1].

  [1]: https://adamheins.com/blog/fzf-vi-mode-and-fixing-delays

The basic problem is that
fzf presses <Esc> to enter vi-movement-mode
(as opposed to insert mode)
and then presses a bunch of keys to set up the buffer.
But the <Esc> keypress is also the prefix for a bunch of other commands,
so Bash will dutifully wait an excruciating half-second
before actually executing this command.
Instead, we bind <C-x><C-a>, which is unused by default
and seems reasonably unlikely to be custom-bound,
to be another way to enter vi-movement-mode;
this binding is unambiguous, so fzf can use it without delay.

This change was made by just `:s/\\e/\\C-x\\C-a/gc`
in the relevant section,
after adding the actual binding and comment at the top.
2016-04-14 13:19:05 -04:00
Junegunn Choi
89687105f4 [install] Ask before updating shell configuration files 2016-04-14 14:51:58 +09:00
Junegunn Choi
74d1694be9 Fix #541 - Print double-click when --expect=double-click is set 2016-04-14 04:18:59 +09:00
Junegunn Choi
935e986be5 [zsh] Remove unnecessary evals 2016-04-12 21:20:03 +09:00
Junegunn Choi
e5ac2ebd7c [vim] Escape $
https://github.com/junegunn/fzf.vim/issues/114
2016-04-09 21:44:07 +09:00
Junegunn Choi
8d6e13bf94 Merge pull request #535 from mjwestcott/master
Fix algorithm tests
2016-04-01 09:05:33 +09:00
Matt Westcott
2ca704405a Fix algorithm tests 2016-04-01 00:06:09 +01:00
Junegunn Choi
802c1c2937 Clean up install script
- Do not create zsh files if zsh is not installed (@adam8157)
- Use command -v instead of which (@netei)
- Reenable --pre option

Close #531
2016-03-29 22:30:55 +09:00
Junegunn Choi
3cb5fef6b6 Merge pull request #529 from mjwestcott/master
Fix typo in README.md
2016-03-28 23:21:14 +09:00
Matt Westcott
6da2e0aa1e Fix typo in README.md 2016-03-28 13:40:28 +01:00
Junegunn Choi
24f3ec7f33 Fix FZF_CTRL_R_OPTS for zsh (#526) 2016-03-23 03:02:57 +09:00
Junegunn Choi
a57b375b41 Add $FZF_CTRL_R_OPTS for overriding the default options for CTRL-R
Close #526
2016-03-23 03:00:20 +09:00
Junegunn Choi
6cc9d53978 [fzf-tmux] Fix invalid redirection 2016-03-15 21:21:21 +09:00
Junegunn Choi
df32c05833 [fzf-tmux] Fix issues on tmux 1.8 2016-03-15 21:18:15 +09:00
Junegunn Choi
c0652adf4c [fzf-tmux] tmux 1.6 compatibility
Patch submitted by @netei. Close #524.
2016-03-15 20:35:25 +09:00
Junegunn Choi
6ea760a336 Make 32-bit linux binary (partially) static (#523) 2016-03-15 20:17:29 +09:00
Junegunn Choi
f704b94603 [neovim] Open tab before current tab
Related: https://github.com/junegunn/gv.vim/issues/19
2016-03-06 13:56:29 +09:00
Junegunn Choi
444a67cafa Fix flaky test cases 2016-03-06 12:46:28 +09:00
Junegunn Choi
f91cbd5688 Add ISSUE_TEMPLATE.md
Close #500
2016-03-06 11:24:03 +09:00
Junegunn Choi
3073ca3e5a [neovim] Take total number of tab pages into account (#520)
This fixes the problem where a new tab page is not closed when the
following configuration is used:

  let g:fzf_layout = { 'window': 'execute (tabpagenr()-1)."tabnew"' }
2016-03-04 15:23:07 +09:00
Junegunn Choi
b47ab633e2 0.11.4 2016-03-03 01:57:28 +09:00
Junegunn Choi
09a2ab39fe [bash] Fix shellcheck warnings
Close #516
2016-03-02 23:59:42 +09:00
Junegunn Choi
6cf54833f7 Fix flaky test case 2016-03-02 03:29:08 +09:00
Junegunn Choi
2ccdf21a1f Add --hscroll-off=COL option
Close #513
2016-03-02 03:14:35 +09:00
Junegunn Choi
cf8afc527e Remove .gitmodules 2016-03-02 02:04:38 +09:00
Junegunn Choi
1d6f05f974 [man] Fix invalid exit status in man page
Close #511
2016-02-26 23:36:07 +09:00
Junegunn Choi
85751966e9 Merge pull request #506 from justinmk/fixvarmismatch
[vim] s:callback: Always return list.
2016-02-23 15:39:53 +09:00
Justin M. Keyes
a7bc9d5351 s:callback: Always return list.
Fixes "E706: Variable type mismatch for: ret" when an exception is
caught.
2016-02-23 00:36:36 -05:00
Junegunn Choi
42c006d07c Update install script to try "go get ..."
Related: #470, #497
2016-02-21 22:11:28 +09:00
Junegunn Choi
1b9ca314b8 Update build script
- GOPATH is no longer required
- fzf repository does not have to be in GOPATH
- Build Linux binary with Go 1.5.3
2016-02-20 21:16:24 +09:00
Junegunn Choi
e72a360337 Minor refactoring
- Slightly more efficient processing of Options
- Do not return reference type arguments that are mutated inside the
  function
- Use util.Constrain function when appropriate
2016-02-18 01:46:18 +09:00
Junegunn Choi
45108ddd53 Merge pull request #496 from noscript/master
Go 1.3 compatibility
2016-02-16 18:48:39 +09:00
Sergey Vlasov
e3401a0645 Go 1.3 compatibility 2016-02-16 11:28:40 +02:00
Junegunn Choi
26b9100709 Minor code cleanup 2016-02-16 12:35:42 +09:00
Junegunn Choi
a568120e42 Fix #494 - _fzf_complete hangs on zsh when not using tmux pane 2016-02-16 12:32:05 +09:00
Junegunn Choi
e57182c658 Merge pull request #488 from nhooyr/man-fix-redirect
[man] Remove useless `.R` macros
2016-02-12 13:29:39 +09:00
Anmol Sethi
6354dbbbdf Removed the useless .R macros
If you do `man fzf > /dev/null`, you'll get the following output

`R' is a string (producing the registered sign), not a macro.
`R' is a string (producing the registered sign), not a macro.
`R' is a string (producing the registered sign), not a macro.
`R' is a string (producing the registered sign), not a macro.
`R' is a string (producing the registered sign), not a macro.
`R' is a string (producing the registered sign), not a macro.

Removing these `.R` macros with a newline seems to have no effect on the
page but gets rid of the error.
2016-02-11 23:04:38 -05:00
Junegunn Choi
2b3e740569 [neovim] Fix error in finally block when callback failed
e.g. Opening another buffer when `set nohidden`

https://github.com/junegunn/fzf.vim/issues/77
2016-02-12 12:33:47 +09:00
Junegunn Choi
40d934e378 0.11.3 2016-02-07 11:00:10 +09:00
Junegunn Choi
e95d82748f Use $SHELL to start $FZF_DEFAULT_COMMAND (#481) 2016-02-07 01:49:29 +09:00
Junegunn Choi
30bd0b53db Fix #481 - Use $SHELL instead of sh in execute action
Note that $SHELL only points to the default shell instead of the current
shell. If you're on a non-default shell, you might want to override the
value like follows.

  SHELL=zsh fzf --bind 'enter:execute:echo $ZSH_VERSION; sleep 1'
2016-02-03 04:46:02 +09:00
Junegunn Choi
1893eca41a Handle SIGTERM gracefully (#482) 2016-02-02 17:51:21 +09:00
Junegunn Choi
82067463b8 [completion] _fzf_complete_COMMAND_post for post processing
e.g.

_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
2016-01-29 01:31:04 +09:00
Junegunn Choi
ce9c51d399 Typo 2016-01-20 01:39:55 +09:00
Junegunn Choi
96176476f3 Make fuzzy completion customizable with _fzf_compgen_{path,dir}
Notes:
- You can now override _fzf_compgen_path and _fzf_compgen_dir functions
  to use custom commands such as ag instead of find for listing
  completion candidates.
    - The first argument is the base path to start traversal
- Removed file-only completion in bash, i.e. _fzf_file_completion.
  Maintaining a list of commands that only expect files, not
  directories, is cumbersome (there are too many) and error-prone.

TBD:
- Added $FZF_COMPLETION_DIR_COMMANDS to customize the list of commands
  which use directory-only completion. The default is "cd pushd rmdir".
  Not sure if it's the best approach to address the requirement, I'll
  leave it as an undocumented feature.

Related: #406 (@thomcom), #456 (@frizinak)
2016-01-20 01:38:24 +09:00
Junegunn Choi
68c84264af Update CHANGELOG 2016-01-16 21:13:57 +09:00
Junegunn Choi
69438a55ca Update CHANGELOG: 0.11.2 2016-01-16 21:11:40 +09:00
Junegunn Choi
8695b5e319 Reduce the initial delay when --tac is not given
fzf defers the initial rendering of the screen up to 100ms if the input
stream is ongoing to prevent unnecessary redraw during the initial
phase. However, 100ms delay is quite noticeable and might give the
impression that fzf is not snappy enough. This commit reduces the
maximum delay down to 20ms when --tac is not specified, in which case
the input list quickly fills the entire screen.
2016-01-16 18:07:50 +09:00
Junegunn Choi
95970164ad 0.11.2 2016-01-14 02:54:08 +09:00
Junegunn Choi
f6c6e59a50 Add toggle-in and toggle-out for --bind
Related: #452

When `--multi` is set, tab key will bring your cursor down, and
shift-tab up. But since fzf by default draws the screen in bottom-up
fashion, one may feel that the opposite of the behavior is more
desirable and choose to customize the key bindings as follows.

    export FZF_DEFAULT_OPTS="--bind tab:toggle-up,shift-tab:toggle-down"

This configuration, however, becomes no longer straightforward when
`--reverse` is set and fzf switches to top-down layout. To address the
requirement, this commit adds `toggle-in` and `toggle-out` option which
switch direction depending on `--reverse`-ness.

    export FZF_DEFAULT_OPTS="--bind tab:toggle-out,shift-tab:toggle-in"
2016-01-14 02:35:43 +09:00
Junegunn Choi
45143f9541 Ignore leading whitespaces when calculating 'begin' index 2016-01-14 01:32:03 +09:00
Junegunn Choi
23244bb410 [fish] Fix intermittent errors on CTRL-T
This seems like a bug of fish, but sometimes when you select an item
fish complains:

"insertion mode switches can not be used when not in insertion mode"

This only happens when using tmux pane. Injecting a dummy command
somehow fixes the issue.
2016-01-14 01:12:49 +09:00
Junegunn Choi
edb647667e Change temporary file names to fix flaky tests 2016-01-14 01:12:49 +09:00
Junegunn Choi
8d3a302a17 Simplify Item structure
This commit compensates for the performance overhead from the
extended tiebreak option.
2016-01-14 01:12:49 +09:00
Junegunn Choi
1d2d32c847 Accept comma-separated list of sort criteria 2016-01-13 21:27:43 +09:00
Junegunn Choi
d635b3fd3c Update license: 2016 2016-01-13 02:16:26 +09:00
Junegunn Choi
0f281ef894 [vim] Try to make 'dir' option compatible with &autochdir
When 'dir' option is passed to fzf#run(), the current working directory
is temporarily changed to the given directory, and restored at the end.
However, this behavior is not compatible with &autochdir. This commit
introduces a heuristic to determine whether or not to restore the
previous working directory.

Related: https://github.com/junegunn/fzf.vim/issues/70
2016-01-12 01:15:36 +09:00
Junegunn Choi
b18db4733c [vim] Do not restore working directory on unexpected cwd
We should not restore the previous working directory if the current
directory has changed somehow. This can happen when &autochdir is set.
2016-01-11 18:17:13 +09:00
Junegunn Choi
6e08fe337c [nvim] setlocal nospell on terminal buffer
Close #469. `setlocal nospell` should appear before `setf fzf` to allow
customization of the option.
2016-01-09 12:08:25 +09:00
Junegunn Choi
2a2c0a0957 [fzf-tmux] Turn off remain-on-exit option
Related: https://github.com/junegunn/fzf.vim/issues/67
2016-01-07 01:42:03 +09:00
Junegunn Choi
4230b6f3c9 [fzf-tmux] Fix #466 - Make fifos writable by other users 2016-01-07 00:32:38 +09:00
Junegunn Choi
aa171b45cb Fix ubuntu-android target of Makefile 2016-01-05 02:10:40 +09:00
Junegunn Choi
661d06c90a Add regression test case for #458 2015-12-29 13:02:16 +09:00
Junegunn Choi
a9aa263d3a Merge pull request #458 from frizinak/fix-autocomplete-abs
Fix auto-completion for `/`
2015-12-29 08:22:52 +09:00
Kobe Lipkens
6208fc9cfd Fix autocompletion for absolute paths 2015-12-28 21:11:04 +01:00
Junegunn Choi
e1dd798482 [bash/zsh-completion] List hidden files as well
Close #456 and #457
2015-12-29 00:21:38 +09:00
Junegunn Choi
c8a3f6f06a Merge pull request #455 from frizinak/master
Pass FZF_DEFAULT_OPTS to non-interactive bash instance
2015-12-28 00:09:22 +09:00
Kobe Lipkens
3b9984379c Pass FZF_DEFAULT_OPTS to non-interactive bash instance 2015-12-25 21:05:25 +01:00
Junegunn Choi
a1b60b1d42 Fix Travis CI build
The size of pseudo-terminal in Travis CI environment can be small
2015-12-20 01:47:07 +09:00
Junegunn Choi
b5850ebd4c [vim] Open selected file in the current window if it's empty
Close #451
2015-12-18 12:19:29 +09:00
Junegunn Choi
ac0a62e494 Merge pull request #446 from chaoren/master
Fix CTRL-T in tmux
2015-12-13 01:11:02 +09:00
Chaoren Lin
54b4b0c56f Dynamically select which __fzf_select__ to use for tmux with bash 4+.
Instead of choosing one at initialization, choose the correct one
when it's actually called, so that the behavior is correct even after
resizing.

Bonus fixes for tmux with bash 4+:
- No extra space when cancelling CTRL-T.
- Fix cursor position problem in vi mode.
2015-12-11 10:02:35 -08:00
Chaoren Lin
033afde3b5 Fix CTRL-T in tmux with non-standard configuration.
- Don't assume ~/.fzf.bash exists.
- Source the current script for __fzf_select__.
- Forward $PATH.
2015-12-11 00:18:45 -08:00
Junegunn Choi
a07944a5bb Merge pull request #439 from pokey/master
Correct fzf-tmux tmux checking bug
2015-12-09 12:43:42 +09:00
Pokey Rule
32010055e1 Correct fzf-tmux tmux checking bug 2015-12-08 17:33:44 -08:00
Junegunn Choi
971ea2217c Merge pull request #433 from pokey/master
Support fzf-tmux when zoomed
2015-12-08 10:56:06 +09:00
Pokey Rule
d513a210c6 Support fzf-tmux when zoomed 2015-12-07 17:45:22 -08:00
Junegunn Choi
a1db64e7b1 Unset GO15VENDOREXPERIMENT in linux build env (#430) 2015-12-04 16:47:02 +09:00
Junegunn Choi
0b9c4e1e74 Remove submodules and disable GO15VENDOREXPERIMENT (#430)
Having submodules causes vim-plug or other vim plugin managers to clone
them with no real benefit to the end-users. There's currently no
compelling reason for me to use submodules.
2015-12-04 16:45:23 +09:00
Junegunn Choi
248320fa55 0.11.1 2015-12-01 00:39:45 +09:00
Junegunn Choi
d4e26707c7 GO15VENDOREXPERIMENT=1 (#430) 2015-11-30 18:41:53 +09:00
Junegunn Choi
99ea1056ac Add --tabstop option
Related: https://github.com/junegunn/fzf.vim/issues/49
2015-11-30 17:35:03 +09:00
Junegunn Choi
7bcf4effa5 Fix test failure - use absolute path 2015-11-30 17:32:40 +09:00
Junegunn Choi
e1df876b61 Merge pull request #380 from acornejo/android
Add android build: `make android`
2015-11-20 03:28:41 +09:00
Alex Cornejo
28ffb9638d add android build 2015-11-18 23:20:51 -08:00
Junegunn Choi
1c20255504 Fix typos in help message
Close #425. Thanks to @blueyed.
2015-11-19 09:58:07 +09:00
Junegunn Choi
1fd884b34f Merge pull request #423 from blueyed/zsh-fzf-completion-localoptions
zsh: fzf-completion: use noshwordsplit local option
2015-11-19 00:54:42 +09:00
Daniel Hahler
701687faab zsh: fzf-completion: use noshwordsplit local option
This also fixes the completion causing a bell / flickering in case
"shwordsplit" was not set, because then the function would return false.
2015-11-18 16:09:00 +01:00
Junegunn Choi
bbc3055feb Merge pull request #421 from blueyed/zsh-completion-grep-command
zsh completion: use \grep to skip any alias
2015-11-18 03:32:39 +09:00
Daniel Hahler
95c69083c7 zsh completion: use \grep to skip any alias 2015-11-17 18:51:29 +01:00
Junegunn Choi
57a37b5832 [bash-completion] Fix #417 - Update command list 2015-11-12 13:53:36 +09:00
Junegunn Choi
d29ae1c462 [install] Add --32 / --64 options
Related: #373
2015-11-12 13:42:56 +09:00
Junegunn Choi
df468fc482 0.11.0 2015-11-10 01:54:53 +09:00
Junegunn Choi
31278bcc68 Fix compatibility issues with OR operator and inverse terms 2015-11-10 01:54:37 +09:00
Junegunn Choi
e7e86b68f4 Add OR operator
Close #412
2015-11-09 23:58:53 +09:00
Junegunn Choi
a89d8995c3 Add execute-multi action
Close #413
2015-11-09 23:58:19 +09:00
Junegunn Choi
dbc854d5f4 Handle wide unicode characters in --prompt 2015-11-09 22:01:40 +09:00
Junegunn Choi
f1cd0e2daf [zsh] Fix #404 - Escape $ in $LBUFFER 2015-11-09 12:06:10 +09:00
Junegunn Choi
90d32bd756 [install] Fix #414 - Respect $ZDOTDIR 2015-11-09 01:48:55 +09:00
Junegunn Choi
e99731ea85 [shell] Add FZF_ALT_C_COMMAND for ALT-C (#408) 2015-11-08 00:12:12 +09:00
Junegunn Choi
15659ac6e6 Merge pull request #409 from freitass/master
[bash-completion] Add nvim to f_cmds
2015-11-07 00:23:30 +09:00
Leandro Freitas
3ef41845a9 [bash-completion] Add nvim to f_cmds 2015-11-06 11:22:35 -02:00
Junegunn Choi
c84e681581 Merge pull request #403 from JackDanger/not-relying-on-exit-status-for-ctrl-r
Not relying on exit status for CTRL-R

Patch submitted by @robinro and @JackDanger
Close #403 #242 #241 #203
2015-11-06 08:38:36 +09:00
Jack Danger Canty
c3cf3427b1 Not relying on exit status for CTRL-R
In the case that fzf-tmux returns a user-selected result but with a
non-zero exit status (which can happen if a function inside $PS1 returns
non-zero) this allows CTRL-R to continue working as expected.

Addresses #203 (Tranquility's comment)
2015-11-05 10:08:54 -08:00
Junegunn Choi
2c4f71d85b [zsh] fzf-history-widget - update local declaration 2015-11-04 21:57:18 +09:00
Junegunn Choi
c6328affae Update extended-search mode section of README 2015-11-04 03:16:36 +09:00
Junegunn Choi
aaef18295d Update FZF_DEFAULT_COMMAND example 2015-11-04 03:14:38 +09:00
Junegunn Choi
14f0d2035e Update Homebrew instructions 2015-11-04 03:13:22 +09:00
Junegunn Choi
64afff6b9a 0.10.9 2015-11-03 23:03:49 +09:00
Junegunn Choi
6bddffbca4 Setup signal handlers before ncurses initialization
This prevents fzf from missing SIGWINCH during startup which
occasionally happens with fzf-tmux
2015-11-03 23:00:34 +09:00
Junegunn Choi
81a88693c1 Make --extended default
Close #400
2015-11-03 22:49:32 +09:00
Junegunn Choi
68541e66b7 [man] double-click for --bind (#374) 2015-11-03 22:40:45 +09:00
Junegunn Choi
672b593634 Update FZF_DEFAULT_COMMAND example (#310) 2015-11-03 22:25:50 +09:00
Junegunn Choi
5769d3867d [nvim] setf fzf 2015-10-31 00:18:23 +09:00
Junegunn Choi
724ffa3756 [install] Do not download binary if it's found in $PATH (#373)
/cc @xconstruct
2015-10-26 12:31:43 +09:00
Junegunn Choi
5694b5ed30 Fix #394 - --bin option is broken 2015-10-23 17:43:34 +09:00
Junegunn Choi
a1184ceb4e Fix travis CI build 2015-10-23 15:07:16 +09:00
Junegunn Choi
02203c7739 Add command-line flags to install script
Close #392

  usage: ./install [OPTIONS]

      --help               Show this message
      --bin                Download fzf binary only
      --all                Download fzf binary and update configuration files
                           to enable key bindings and fuzzy completion
      --[no-]key-bindings  Enable/disable key bindings (CTRL-T, CTRL-R, ALT-C)
      --[no-]completion    Enable/disable fuzzy completion (bash & zsh)
      --[no-]update-rc     Whether or not to update shell configuration files
2015-10-23 15:04:32 +09:00
Junegunn Choi
4d709e0dd2 Fix #391 - Strip non-printable characters 2015-10-23 01:12:31 +09:00
Junegunn Choi
ae04f56dbd Fix --bind "double-click:execute(...)" (#374) 2015-10-13 02:36:11 +09:00
Junegunn Choi
f80ff8c917 Add bindable double-click event (#374) 2015-10-13 02:24:38 +09:00
Junegunn Choi
b4ce89bbf5 [build] Link libncursesw when building 64-bit linux binary
Close #376
2015-10-12 16:02:08 +09:00
Junegunn Choi
486b87d821 [bash-completion] Retain original completion options (#288) 2015-10-12 00:27:30 +09:00
Junegunn Choi
b3010a4624 0.10.8 2015-10-09 12:42:07 +09:00
Junegunn Choi
7d53051ec8 Merge pull request #371 from wilywampa/edit_directory
Trigger netrw autocommand when opening directory
2015-10-09 12:36:08 +09:00
Jacob Niehus
ed893c5f47 Trigger netrw autocommand when opening directory 2015-10-08 20:28:07 -07:00
Junegunn Choi
a4eb3323da Fix #370 - Panic when trying to set colors when colors are disabled 2015-10-09 12:16:47 +09:00
Junegunn Choi
1da065e50e 0.10.7 2015-10-05 23:28:24 +09:00
Junegunn Choi
86bc9d506f Fix invalid interrupt handler during execute action
Interrupt handling during execute action was not serialized and often
caused crash, failed to restore the terminal state.
2015-10-05 23:19:26 +09:00
Junegunn Choi
eee45a9578 [completion] Revamp completion API
* _fzf_complete is the helper function for custom completion
    * _fzf_complete FZF_OPTS ARGS
    * Reads the output of the source command instead of the command string
    * In zsh, you can use pipe to feed the data into the function, but
      it's not possible in bash as by doing so COMPREPLY is set from the
      subshell and thus nullified
* Change the naming convention for consistency:
    * _fzf_complete_COMMAND

e.g.

  # pass completion suggested by @d4ndo (#362)
  _fzf_complete_pass() {
    _fzf_complete '+m' "$@" < <(
      local pwdir=${PASSWORD_STORE_DIR-~/.password-store/}
      local stringsize="${#pwdir}"
      find "$pwdir" -name "*.gpg" -print |
          cut -c "$((stringsize + 1))"-  |
          sed -e 's/\(.*\)\.gpg/\1/'
    )
  }

  # Only in bash
  complete -F _fzf_complete_pass -o default -o bashdefault pass
2015-10-05 19:34:38 +09:00
Junegunn Choi
659f49a09a [fzf-tmux] Create temp files in $TMPDIR if defined 2015-10-05 13:01:09 +09:00
Junegunn Choi
8fa9e85980 [zsh-completion] Allow custom completion function
While in bash you can externally register custom completion functions
using `complete` command, it was not possible to do so in zsh without
changing completion.zsh as the name of the supported commands are
hard-coded within the code (See #362). With this commit, fzf-completion
of zsh will first look if `_fzf_COMMAND_completion` exists and calls the
function, so one can externally define completion functions for specific
commands.

This commit also tries to make the interface of (yet undocumented)
_fzf_list_completion helper function consistent across bash and zsh.

So the following code works both on bash and zsh.

    _fzf_pass_completion() {
      local pwdir=${PASSWORD_STORE_DIR-~/.password-store/}
      local stringsize="${#pwdir}"
      let "stringsize+=1"
      _fzf_list_completion '+m' "$@" << "EOF"
        find "$pwdir" -name "*.gpg" -print | cut -c "$stringsize"- | sed -e 's/\(.*\)\.gpg/\1/'
    EOF
    }

    # Only on bash
    complete -F _fzf_pass_completion -o default -o bashdefault pass

Note that the suggested convention and the interface are not yet final
and subject to change.

/cc @d4ndo
2015-10-05 01:48:45 +09:00
Junegunn Choi
92a75c9563 Use trimmed length when --nth is used with --tiebreak=length
This change improves sort ordering for aligned tabular input.
Given the following input:

    apple   juice   100
    apple   pie     200

fzf --nth=2 will now prefer the one with pie. Before this change fzf
compared "juice   " and "pie     ", both of which have the same length.
2015-10-02 18:40:20 +09:00
Junegunn Choi
7c7a30c472 Merge pull request #364 from halostatue/use-zsh-regex-module
Remove dependency on zsh/pcre module
2015-10-02 11:02:19 +09:00
Austin Ziegler
ea271cd4e2 Remove dependency on zsh/pcre module
Fixes #363.
2015-10-01 15:18:10 -04:00
Junegunn Choi
6a38d07a4c Merge pull request #361 from justinmk/swapexists
[vim] handle SwapExists
2015-09-30 16:16:18 +09:00
Justin M. Keyes
c4e5ee63bb [vim] handle SwapExists
The SwapExists dialog prevents multiple files from being opening if the
dialog occurs before all files are opened. Opening the files is more
important than showing the dialog, so choose "readonly" automatically
and continue opening files.
2015-09-30 02:48:12 -04:00
Junegunn Choi
862da2c0b1 [vim] Consistent exit status handling 2015-09-27 16:26:40 +09:00
Junegunn Choi
545370d2b3 Merge branch 'jebaum-master' 2015-09-27 15:59:04 +09:00
James Baumgarten
59220c63a6 [vim] handle exit status 1 properly (#359) 2015-09-26 16:56:52 -07:00
Junegunn Choi
86306dd45a [vim] Display proper error message when GVim launcher failed
Related: https://github.com/junegunn/fzf.vim/issues/16
2015-09-26 21:04:44 +09:00
Junegunn Choi
98d2bfa0db [install] Terminate install script when failed to update shell config
Close #354
2015-09-24 10:51:05 +09:00
Junegunn Choi
aec48f159b [neovim] Remove redraw! hack that is no longer needed 2015-09-22 16:36:48 +09:00
Junegunn Choi
ad7e433a7d Use build tags to enable static linking 2015-09-22 13:16:50 +09:00
Junegunn Choi
5a60aa5050 [vim] Display proper error message when command failed 2015-09-20 14:10:43 +09:00
Junegunn Choi
ebea470875 Build linux binary on Centos 2015-09-20 00:17:44 +09:00
Junegunn Choi
d980e00961 Revert "Revert "0.10.6""
This reverts commit 987799f8fbd2367fe99383a1ddf9ddf5d4ed253f.
2015-09-19 22:51:12 +09:00
Junegunn Choi
987799f8fb Revert "0.10.6"
This reverts commit d2f3604c1dd176b485ab82cebc956560f2e73657.
2015-09-19 22:27:09 +09:00
Junegunn Choi
d2f3604c1d 0.10.6 2015-09-19 22:18:04 +09:00
Junegunn Choi
72cc558fdc Fix travis CI build 2015-09-19 18:39:09 +09:00
Junegunn Choi
6bc3fe6e67 Build partially-static binary for linux (#350)
Instead of building a separate statically-linked binary, build
partially-static binary that only contains ncurses to avoid
compatibility issues in libc.
2015-09-19 18:33:25 +09:00
7927015a56 Merge branch 'master' of https://github.com/junegunn/fzf
Conflicts:
	install
2015-09-18 17:23:14 +00:00
Junegunn Choi
9398878048 [fzf-tmux] Exit with the same exit status as with fzf 2015-09-18 10:28:09 +09:00
Junegunn Choi
ca19762e58 Exit status 130 when fzf is terminated by the user
Related: #345
2015-09-18 10:25:07 +09:00
Junegunn Choi
8764be07e2 [vim] Ignore exit status of 2 (#345) 2015-09-18 09:59:40 +09:00
Junegunn Choi
2022a3ad96 Replace --header-file with --header (#346)
and allow using --header and --header-lines at the same time.

Close #346.
2015-09-15 19:04:53 +09:00
Junegunn Choi
65d9d416b4 Change exit status (0: OK, 1: No match, 2: Error/Interrupted)
A la grep. Close #345
2015-09-15 13:21:51 +09:00
Junegunn Choi
fa2f9f1f21 Remove flattr badge 2015-09-14 15:49:19 +09:00
Junegunn Choi
c656cfbdce Update doc 2015-09-12 13:31:07 +09:00
Junegunn Choi
de829c0938 0.10.5 2015-09-12 12:50:32 +09:00
Junegunn Choi
64443221aa Fix #344 - Backward scan when --tiebreak=end 2015-09-12 11:37:55 +09:00
Junegunn Choi
9017e29741 Make it possible to unquote the term in extended-exact mode
Close #338
2015-09-12 11:00:30 +09:00
Junegunn Choi
0a22142d88 [fzf-tmux] Fix #343 - Escape backticks in --query 2015-09-07 18:40:39 +09:00
Junegunn Choi
ac160f98a8 [gvim] Fix #342 - Should not escape launcher part of the command 2015-09-05 21:39:12 +09:00
Junegunn Choi
62e01a2a62 [vim] Escape newline character when running fzf with :!
Fixes Helptags! command from fzf.vim
2015-09-01 01:13:35 +09:00
Junegunn Choi
5660cebaf6 [zsh-completion] Temporarily unset shwordsplit (#328) 2015-09-01 00:51:28 +09:00
Junegunn Choi
a7e588ceac Merge pull request #336 from fazibear/fix-fish-streams
Fix CTRL-T on fish to work asynchronously
2015-08-30 21:21:13 +09:00
Michał Kalbarczyk
5baf1c5536 fix fish streams 2015-08-30 14:05:24 +02:00
Junegunn Choi
9a2d9ad947 0.10.4 2015-08-29 02:36:27 +09:00
Junegunn Choi
90b0cd44ac Should not strip ANSI codes when --ansi is not set 2015-08-28 21:23:10 +09:00
Junegunn Choi
698e8008df [vim] Dynamic height specification for 'up' and 'down' options
Values for 'up' and 'down' can be written with ~ prefix. Only applies
when the source is a Vim list.

    e.g. { 'source': range(10), 'down': '~40%' }
2015-08-28 18:38:47 +09:00
Junegunn Choi
1de4cc3ba8 [install] Fall back statically-linked binary on 64-bit linux
Close #322
2015-08-27 22:50:59 +09:00
Junegunn Choi
0d66ad23c6 Fix build script 2015-08-27 22:48:42 +09:00
Junegunn Choi
7f7741099b make linux-static (#322) 2015-08-27 03:28:05 +09:00
Junegunn Choi
5a72dc6922 Fix #329 - Trim ANSI codes from output when --ansi & --with-nth are set 2015-08-26 23:58:18 +09:00
Junegunn Choi
80ed02e72e Add failing test case for #329 2015-08-26 23:35:31 +09:00
Junegunn Choi
8fb31e1b4d [vim] Escape % and # when running source command with :! 2015-08-24 01:52:16 +09:00
Junegunn Choi
148f21415a Mention fzf.vim project 2015-08-22 19:33:04 +09:00
Junegunn Choi
1c31e07d34 [install] Improve error message 2015-08-19 19:42:06 +09:00
Junegunn Choi
55d566b72f Revert "[vim] Open silently"
This reverts commit c601fc6437f864c440b9d35c87604e48f2a48446.
2015-08-18 12:03:08 +09:00
Junegunn Choi
60336c7423 Remove Vim examples from README.md 2015-08-16 02:47:52 +09:00
Junegunn Choi
7ae877bd3a [vim] Handle single/double quote characters in 'dir' option 2015-08-16 00:04:45 +09:00
Junegunn Choi
c601fc6437 [vim] Open silently 2015-08-15 23:53:27 +09:00
Junegunn Choi
e5fec408c4 [vim] tab split instead of tabedit 2015-08-15 23:53:11 +09:00
Junegunn Choi
8156e9894e 0.10.3 2015-08-12 02:09:46 +09:00
Junegunn Choi
cacc212f12 [install] Prerelease of 0.10.3 2015-08-11 00:21:09 +09:00
Junegunn Choi
d0f2c00f9f Fix --with-nth performance; use simpler regular expression
Related #317
2015-08-11 00:15:41 +09:00
Junegunn Choi
766427de0c Fix --with-nth performance; avoid regex if possible
Close #317
2015-08-10 18:34:20 +09:00
Junegunn Choi
a7b75c99a5 [install] Stop installer when failed to download the binary
Close #312
2015-08-08 03:53:46 +09:00
Junegunn Choi
bae10a6582 [install] Add an extra new line character
so that it doesn't corrupt file that doesn't end with a new line
character. Close #311.
2015-08-05 23:50:38 +09:00
Junegunn Choi
c4cf90a3d2 0.10.2 2015-08-03 00:21:21 +09:00
Junegunn Choi
15c49a3e08 Fix race condition 2015-08-03 00:14:34 +09:00
Junegunn Choi
ae87f6548a GoLint 2015-08-02 23:54:53 +09:00
Junegunn Choi
7833fa7396 [install] Always download binary when --pre is set 2015-08-02 15:09:57 +09:00
Junegunn Choi
9278f3acd2 [install] Add --pre option for downloading prerelease binary 2015-08-02 15:02:12 +09:00
Junegunn Choi
e83ae34a3b Update CHANGELOG - 0.10.2 2015-08-02 14:32:34 +09:00
Junegunn Choi
e13bafc1ab Performance fix - unnecessary rune convertion on --ansi
> time cat /tmp/list | fzf-0.10.1-darwin_amd64 --ansi -fqwerty > /dev/null

    real    0m4.364s
    user    0m8.231s
    sys     0m0.820s

    > time cat /tmp/list | fzf --ansi -fqwerty > /dev/null

    real    0m4.624s
    user    0m5.755s
    sys     0m0.732s
2015-08-02 14:25:57 +09:00
Junegunn Choi
0ea66329b8 Performance tuning - eager rune array conversion
> wc -l /tmp/list2
     2594098 /tmp/list2

    > time cat /tmp/list2 | fzf-0.10.1-darwin_amd64 -fqwerty > /dev/null

    real    0m5.418s
    user    0m10.990s
    sys     0m1.302s

    > time cat /tmp/list2 | fzf-head -fqwerty > /dev/null

    real    0m4.862s
    user    0m6.619s
    sys     0m0.982s
2015-08-02 14:00:18 +09:00
Junegunn Choi
634670e3ea Lint 2015-08-02 13:11:59 +09:00
Junegunn Choi
dea60b11bc Only consider the lengths of the relevant parts when --nth is set 2015-08-01 23:13:24 +09:00
Junegunn Choi
5e90f0a57b Fix default command so that it doesn't fail on dash-prefixed files
Close #310
2015-08-01 21:51:10 +09:00
Junegunn Choi
0b4542fcdf [vim] Temporarily disable &autochdir when opening files (#306) 2015-07-29 17:55:58 +09:00
Junegunn Choi
02bd2d2adf Do not proceed if $TERM is invalid
Related #305
2015-07-28 14:35:46 +09:00
Junegunn Choi
dce6fe6f2d [fzf-tmux] Ensure that the same $TERM value is used in split
Fix #305. ncurses can crash on invalid $TERM. fzf-tmux uses bash on
a new pane so we have to make sure that the $TERM is consistent with
that of the hosting shell.
2015-07-28 14:17:25 +09:00
Junegunn Choi
fcae99f09b No need to "tmux list-panes" when obviously not on tmux (#303) 2015-07-28 00:56:03 +09:00
Junegunn Choi
fb1b026d3d Always check if the pane is zoomed
Close #303
2015-07-28 00:30:17 +09:00
Junegunn Choi
9f953fc944 Do not use tmux pane if the current pane is zoomed
Close #303
2015-07-28 00:22:04 +09:00
Junegunn Choi
909ea1a698 0.10.1 2015-07-27 00:09:07 +09:00
Junegunn Choi
7231acd442 Fix mouse scroll when --margin is set 2015-07-27 00:06:44 +09:00
Junegunn Choi
7814371a9a Revert "0.10.1"
This reverts commit 6166e2dd805b4647dbf20facab71ba0745c1ec44.
2015-07-27 00:03:14 +09:00
Junegunn Choi
6166e2dd80 0.10.1 2015-07-26 23:57:26 +09:00
Junegunn Choi
ee0c8a2635 Add --margin option
Close #299
2015-07-26 23:02:04 +09:00
Junegunn Choi
2bebddefc0 Do not print the entire --help on invalid option 2015-07-26 13:39:34 +09:00
Junegunn Choi
fdbf3d3fec Replace eof action with cancel (#289) 2015-07-23 21:05:33 +09:00
Junegunn Choi
f9136cffe6 Update man page 2015-07-23 10:45:01 +09:00
Junegunn Choi
51d84b1869 [bash] Update fzf option completion 2015-07-23 00:58:20 +09:00
Junegunn Choi
13e040baee Bind CTRL-D to the new delete-char/eof action
- CTRL-D - delete-char/eof
- DEL - delete-char
2015-07-23 00:56:03 +09:00
Junegunn Choi
cc0d5539ba Add "eof" action which closes the finder only when input is empty
Close #289
2015-07-22 22:57:48 +09:00
Junegunn Choi
b53f61fc59 Remove cbreak before raw 2015-07-22 22:36:39 +09:00
Junegunn Choi
4e0e03403e Fix --header-lines unaffected by --with-nth 2015-07-22 21:24:02 +09:00
Junegunn Choi
928fccc15b Fix header not shown when the lines go beyond the screen limit 2015-07-22 21:22:59 +09:00
Junegunn Choi
bbaa3ab8bd Update CHANGELOG 2015-07-22 14:19:55 +09:00
Junegunn Choi
5e3cb3a4ea Fix ANSI processor to handle multi-line regions 2015-07-22 14:19:45 +09:00
Junegunn Choi
f71ea5f3ea Add test cases for header and fix corner cases 2015-07-22 13:45:38 +09:00
Junegunn Choi
f469c25730 Add --header-lines option 2015-07-22 03:21:20 +09:00
Junegunn Choi
18469b6954 Adjust header color for dark color scheme 2015-07-22 03:07:27 +09:00
Junegunn Choi
d01db4862b Update documentation 2015-07-22 01:12:50 +09:00
Junegunn Choi
8b2adba8d6 Redraw of header on resize 2015-07-22 00:47:14 +09:00
Junegunn Choi
d459e9abce Add --header-file option 2015-07-22 00:38:38 +09:00
Junegunn Choi
c9abe1b1ff Show more specific error message on invalid binding 2015-07-18 02:31:35 +09:00
Junegunn Choi
a0e6147bb5 Fix #292 - Allow binding of colon and comma 2015-07-16 21:14:08 +09:00
Junegunn Choi
b0f491d3c3 Fix travis CI build
- Fix test failures on new fish 2.2.0
- Make timeout-based test cases more robust
2015-07-13 19:24:22 +09:00
Junegunn Choi
392da53f53 [bash] Make CTRL-R work when histexpand is unset (#286)
Note that it still can't handle properly multi-line commands.
Thanks to @jpcirrus for the bug report and the fix.
2015-07-13 00:22:13 +09:00
Junegunn Choi
ae72b0fb70 Merge pull request #285 from evverx/possible-retry-loop
[bash-completion] Fix g++: possible retry loop
2015-07-04 11:24:08 +09:00
Evgeny Vereshchagin
a79d080ea8 Fix g++: possible retry loop
See http://unix.stackexchange.com/q/213432/120177
2015-07-04 01:20:36 +00:00
Junegunn Choi
ec85fd552d Update README - how to use ag with CTRL-T 2015-06-30 13:17:48 +09:00
Junegunn Choi
11db046fc7 [neovim] Fix #281 - Properly close window with winnr 1 2015-06-27 14:23:51 +09:00
Junegunn Choi
938151a834 [shell] Add FZF_CTRL_T_COMMAND for CTRL-T
Close #40
2015-06-26 01:02:44 +09:00
Junegunn Choi
14e3b84073 [zsh] No need to define __fsel in non-interactive shell
Since we now use fzf-tmux instead of tmux split-window
2015-06-26 00:14:36 +09:00
Junegunn Choi
56100f0fa7 [bash] Use command \find for ALT-C
ALT-C can fail with the following aliases as pointed out in #272

    alias find='noglob find'
    alias command='command '
2015-06-25 23:54:05 +09:00
Junegunn Choi
5254ee2e2a Update documentation (#277) 2015-06-22 01:35:36 +09:00
Junegunn Choi
355d004895 [neovim] Fix error with {'window': 'enew'} (#274) 2015-06-21 21:45:10 +09:00
Junegunn Choi
a336494f5d 0.10.0 2015-06-21 17:40:36 +09:00
Junegunn Choi
8270f7f0ca Rename --null to --read0 and undocument the option
`--null` is ambiguous. For completeness' sake, we need both `--read0`
and `--print0`.

`--read0` only makes sense when the input contains multiline entries.
However, fzf currently cannot correctly display multiline entries,
I'm going to make `--read0` an undocumented feature.
2015-06-21 17:29:58 +09:00
Junegunn Choi
638a956a9e Merge pull request #272 from okapia/zsh-simplify
Use vi-fetch-history on zsh to get history line
2015-06-21 16:57:14 +09:00
Oliver Kiddle
d395ebd28f use vi-fetch-history on zsh to get history line
In addition to being simpler, it allows subsequent up/down history
or accept-line-and-down-history widgets to work.
Also allow for find being and alias if alias expansion
after command is enabled.
2015-06-21 09:21:35 +02:00
Junegunn Choi
c0d3faa84f Hide --toggle-sort from --help output
Since the same can be now achieved with --bind KEY:toggle-sort
2015-06-19 01:06:56 +09:00
Junegunn Choi
3492c8b780 Rename --history-max to --history-size
Considering HISTSIZE and HISTFILESIZE of bash
2015-06-19 01:03:25 +09:00
Junegunn Choi
a8b2c257cd Improve handling of key names
Remember the exact string given as the key name so that it's possible to
correctly handle synonyms and print the original string.
2015-06-19 00:31:48 +09:00
Junegunn Choi
5e8d8dab82 More key names for --bind 2015-06-18 02:27:50 +09:00
Junegunn Choi
b504c6eb39 Avoid intermittent test failures
by making sure that we're back on shell command-line
2015-06-18 02:09:44 +09:00
Junegunn Choi
d261c36cde Keep the spinner spinning even when the source stream is idle 2015-06-18 00:42:38 +09:00
Junegunn Choi
fe4e452d68 Add --cycle option for cyclic scrolling
Close #266
2015-06-16 23:16:34 +09:00
Junegunn Choi
d54a4fa223 Add key name "bspace" for --bind (bspace != ctrl-h) 2015-06-16 02:18:49 +09:00
Junegunn Choi
45bd323cab Allow binding CTRL-G and CTRL-Q 2015-06-16 02:17:06 +09:00
Junegunn Choi
8677dbded1 Change alternative notation for execute action (#265)
e.g. fzf --bind "ctrl-m:execute:COMMAND..." --bind ctrl-j:accept
2015-06-15 23:27:05 +09:00
Junegunn Choi
794ad5785d Fix . to match newlines as well (#265) 2015-06-15 23:11:22 +09:00
Junegunn Choi
fa5b58968e Add alternative execute notation that does not require closing char
This can be used to avoid parse errors that can happen when the command
contains the closing character. Since the command does not finish at
a certain character, the key binding should be the last one in the
group. Suggested by @tiziano88. (#265)

  e.g. fzf --bind "ctrl-m:execute=COMMAND..." --bind ctrl-j:accept
2015-06-15 23:00:38 +09:00
Junegunn Choi
e720f56ea8 Fix test code for docker build 2015-06-15 22:45:31 +09:00
Junegunn Choi
7db53e6459 Add synonyms for some keys to be used with --bind and --toggle-sort
enter (return), space, tab, btab, esc, up, down, left, right
2015-06-15 01:26:18 +09:00
Junegunn Choi
e287bd7f04 Fix Travis CI build 2015-06-14 23:44:42 +09:00
Junegunn Choi
022435a90a More alternative notations for execute action
execute(...)
    execute[...]
    execute~...~
    execute!...!
    execute@...@
    execute#...#
    execute$...$
    execute%...%
    execute^...^
    execute&...&
    execute*...*
    execute:...:
    execute;...;
    execute/.../
    execute|...|
2015-06-14 23:36:49 +09:00
Junegunn Choi
6c99cc1700 Add bind action for executing arbitrary command (#265)
e.g. fzf --bind "ctrl-m:execute(less {})"
     fzf --bind "ctrl-t:execute[tmux new-window -d 'vim {}']"
2015-06-14 12:25:08 +09:00
Junegunn Choi
fe5b190a7d Remove unnecessary regexp matches
This change does have positive effect on startup time of fzf when many
number of options are provided.

    time fzf --query=____ --filter=____ --delimiter=q --prompt=________ \
    --nth=1,2,3,4 --with-nth=1,2,3,4 --toggle-sort=ctrl-r \
    --expect=ctrl-x --tiebreak=index --color=light --bind=ctrl-t:accept \
    --history=/tmp/xxx --history-max=1000 --help

    0m0.013s -> 0m0.008s
2015-06-14 11:23:07 +09:00
Junegunn Choi
77bab51696 GoLint fix 2015-06-14 03:19:18 +09:00
Junegunn Choi
77048f3e3b Fix Travis CI build 2015-06-14 02:51:45 +09:00
Junegunn Choi
8b618f7439 Test refactoring 2015-06-14 02:44:22 +09:00
Junegunn Choi
8973207bb4 Fix Travis CI build 2015-06-14 02:13:02 +09:00
Junegunn Choi
6ad1736832 Fix ignore action 2015-06-14 02:11:27 +09:00
Junegunn Choi
9fca611c4a Add ignore action for --bind 2015-06-14 01:54:56 +09:00
Junegunn Choi
8e7164553f Add missing files from the previous commit
:(
2015-06-14 00:53:45 +09:00
Junegunn Choi
3b52811796 Add support for search history
- Add `--history` option (e.g. fzf --history ~/.fzf.history)
- Add `--history-max` option for limiting the size of the file (default 1000)
- Add `previous-history` and `next-history` actions for `--bind`
    - CTRL-P and CTRL-N are automatically remapped to these actions when
      `--history` is used

Closes #249, #251
2015-06-14 00:48:48 +09:00
Junegunn Choi
2e84b1db64 Merge pull request #264 from kassio/master
Do not rename terminal buffer
2015-06-14 00:11:10 +09:00
Kassio Borges
9f33068ab3 Avoid conflict with other neoterm plugins.
To avoid conflict with other neoterm plugins that manage terminals,
prefer named terminals.
2015-06-13 11:13:33 -03:00
Junegunn Choi
eaa3c67a5e Add actions for --bind: select-all / deselect-all / toggle-all
Close #257
2015-06-09 23:44:54 +09:00
Junegunn Choi
1b9b1d15bc Adjust --help output 2015-06-08 23:28:41 +09:00
Junegunn Choi
97f433a274 Merge branch 'dullgiulio-121-accept-nil-input' 2015-06-08 23:28:06 +09:00
Junegunn Choi
45a3655eaf Add test case for --null option 2015-06-08 23:27:50 +09:00
Junegunn Choi
81ffde92fb Merge branch '121-accept-nil-input' of https://github.com/dullgiulio/fzf into dullgiulio-121-accept-nil-input 2015-06-08 23:21:16 +09:00
Junegunn Choi
0be4cead20 Allow ^EqualMatch$ 2015-06-08 23:17:24 +09:00
Giulio Iotti
f6dd32046e add support to nil-byte separated input strings, closes #121 2015-06-08 08:38:40 +00:00
Junegunn Choi
443a80f254 Always use the same color for multi-select markers 2015-06-07 23:32:07 +09:00
Junegunn Choi
8017635a71 Merge pull request #252 from dominikh/portable-swapOutput
Use ncurses's newterm instead of swapping stdout and stderr
2015-06-07 14:31:44 +09:00
Dominik Honnef
98f62b191a Use ncurses's newterm instead of swapping stdout and stderr 2015-06-07 07:26:26 +02:00
Junegunn Choi
52771a6226 0.9.13 2015-06-03 02:09:07 +09:00
Junegunn Choi
b00bcf506e Fix #248 - Premature termination of Reader on long input 2015-06-03 01:48:02 +09:00
Junegunn Choi
fdbfe36c0b Color customization (#245) 2015-06-03 01:46:03 +09:00
Junegunn Choi
446e822723 Update CHANGELOG 2015-05-22 02:37:38 +09:00
Junegunn Choi
b68e59a24b Fix ANSI offset calculation 2015-05-22 02:20:10 +09:00
Junegunn Choi
4e0e492427 Minor refactoring 2015-05-22 00:02:14 +09:00
Junegunn Choi
8f99f8fcc6 More test cases for --bind 2015-05-21 21:06:52 +09:00
Junegunn Choi
3cdf71801e Update --help 2015-05-21 01:51:24 +09:00
Junegunn Choi
801cf9ac62 Add unbound "toggle" action for customization 2015-05-21 01:37:16 +09:00
Junegunn Choi
34946b72a5 0.9.12 2015-05-21 00:44:49 +09:00
Junegunn Choi
1592bedbe8 Custom key binding support (#238) 2015-05-21 00:32:03 +09:00
Junegunn Choi
15099eb13b Remove duplicate processing of command-line options 2015-05-20 20:42:45 +09:00
Junegunn Choi
c511b45ff6 Minor tweak in test case
It may take long for find command to spot the temporary file created on
the home directory
2015-05-20 19:47:48 +09:00
Junegunn Choi
40761b11b1 [bash] Ignore asterisk (modified) in history 2015-05-20 19:45:05 +09:00
Junegunn Choi
cca543d0cd [zsh-completion] Fix #236 - zle redisplay 2015-05-20 16:18:30 +09:00
Junegunn Choi
34e5e2dd82 [vim] Use close+bufhidden=wipe instead of bd 2015-05-14 13:29:50 +09:00
Junegunn Choi
2b7c3df66b [neovim] Check tabpagenr() as well 2015-05-14 02:19:40 +09:00
Junegunn Choi
f766531e74 [neovim] Make sure that fzf buffer is closed (#225)
- bd! leaves the window open when there's no other listed buffer
- redraw! seems to help avoid Neovim issues.
2015-05-14 02:14:21 +09:00
Junegunn Choi
7f59b42b05 [vim] Escape % # \ 2015-05-13 23:20:10 +09:00
Junegunn Choi
f41de932d6 [vim] Refocus MacVim window 2015-05-13 23:14:03 +09:00
Junegunn Choi
b4a05ff27e [bash] CTRL-R to use history-expand-line
Close #146
2015-05-13 19:13:27 +09:00
Junegunn Choi
3b91467941 Suppress error message when loading completion.{zsh,bash}
Temporary workaround for https://github.com/Homebrew/homebrew/issues/39669
2015-05-12 22:54:48 +09:00
Junegunn Choi
26d2af5ee8 [zsh-completion] Respect backslash-escaped spaces (#230) 2015-05-12 01:40:44 +09:00
Junegunn Choi
2b61dc6557 [zsh-completion] Do not overwrite $fzf_default_completion 2015-05-11 22:53:35 +09:00
Junegunn Choi
0b770cd48a [zsh-completion] Remember what ^I was originally bound to (#230) 2015-05-11 21:49:40 +09:00
Junegunn Choi
c14aa99ef6 [zsh/bash-completion] Avoid caret expansion
Close #233

setopt extendedglob on zsh caused caret in grep pattern to be expanded.
Problem identified and patch submitted by @lazywei.
2015-05-11 16:59:44 +09:00
Junegunn Choi
8f371ee81c [zsh-completion] fzf-zsh-completion -> fzf-completion 2015-05-11 13:11:42 +09:00
Junegunn Choi
3b63b39810 [zsh-completion] Allow empty prefix & trigger sequence (#232) 2015-05-11 13:06:02 +09:00
Tiziano Santoro
0cd238700c [zsh-completion] Add comment clarifying trigger expansion. (#230) 2015-05-11 10:18:28 +09:00
Tiziano Santoro
14fbe06d9e [zsh-completion] Allow specifying empty completion trigger. (#230) 2015-05-11 10:18:16 +09:00
Junegunn Choi
64949bf467 [bash-completion] Allow specifying empty completion trigger (#230) 2015-05-11 10:17:33 +09:00
Junegunn Choi
732f133940 [test] Make sure to kill background process 2015-05-10 11:24:54 +09:00
Junegunn Choi
5dc4df9570 Fix test cases 2015-05-10 05:01:52 +09:00
Junegunn Choi
7dde8dbbd9 Merge pull request #231 from robinro/head-argument-typo
[zsh] `head -n1` instead of `head -1`
2015-05-10 04:50:28 +09:00
Robin Roth
01405ad92e fix typo in argument of head
at least my version of head wants -n1 to only display the first line
2015-05-09 21:11:01 +02:00
Junegunn Choi
683abb86ef Dump screen content on test failure 2015-05-10 03:25:14 +09:00
Junegunn Choi
207aa07891 [zsh-completion] Temporarily set nonomatch (#230)
No error on ~INVALID_USERNAME**<TAB>
2015-05-10 02:54:22 +09:00
Junegunn Choi
26a141c6a6 [zsh-completion] Fix ~USERNAME** (#230) 2015-05-10 02:37:17 +09:00
Junegunn Choi
dc64568c83 [zsh-completion] Completion for unknown commands 2015-05-09 21:04:59 +09:00
Junegunn Choi
f4a595eedd Fix Travis CI build 2015-05-09 20:42:13 +09:00
Junegunn Choi
5a17a6323a [zsh-completion] "\find" to bypass alias 2015-05-09 20:36:25 +09:00
Junegunn Choi
2b8e445321 Fuzzy completion for zsh (#227) 2015-05-09 20:18:38 +09:00
Junegunn Choi
315499b1d4 Merge pull request #229 from sullyj3/master
fix typo in README.md
2015-05-09 00:36:52 +09:00
James Sully
65a2bdb01d fix typo in README.md 2015-05-09 01:26:34 +10:00
Junegunn Choi
ed8202efc6 [bash-completion] Ignore 0.0.0.0
Close #228
2015-05-08 18:16:55 +09:00
Junegunn Choi
0937bd6c16 [vim] Improve binary detection
/cc @alerque

- Ask for user confirmation before running `install --bin`
- Removed `s:fzf_rb` since `install --bin` will create a wrapper
  executable that just runs Ruby version on the platforms where prebuilt
  binaries are not available.
2015-05-03 00:58:45 +09:00
Junegunn Choi
3d26b5336c [vim] Fix #220 - Prevent error after update 2015-04-28 23:49:52 +09:00
Junegunn Choi
c8f208b96b Merge pull request #171 from oschrenk/vi-insert-mode-key-bindings-fish
Support for vi insert mode in upcoming fish 2.2.0
2015-04-26 02:17:46 +09:00
Oliver Schrenk
2e339e49b8 Support for vi insert mode in upcoming fish 2.2.0 2015-04-25 19:12:11 +02:00
Junegunn Choi
5d9107fd15 Print info after prompt on redraw
This fixes the issue where "inline-info" is not immediately rendered
when the terminal is resized.
2015-04-25 23:20:40 +09:00
Junegunn Choi
4b7c571575 Fix race condition in test case 2015-04-25 10:56:08 +09:00
Junegunn Choi
5502b68a1d Test refactoring 2015-04-25 10:40:58 +09:00
Junegunn Choi
5794fd42df Fix test code 2015-04-25 01:09:25 +09:00
Junegunn Choi
9c6e46ab15 [fzf-tmux] Fix #215 - Prepend env to avoid error on fish 2015-04-24 12:54:57 +09:00
Junegunn Choi
09d0ac0347 [vim] Update default launcher for GVim (#212)
Code submitted by @lydell
2015-04-24 12:45:39 +09:00
Junegunn Choi
22ae7adac8 Update completion for fzf itself 2015-04-23 22:43:48 +09:00
Junegunn Choi
36924d0b1c [zsh] Do not change LBUFFER on empty selection (CTRL-R) 2015-04-23 22:39:07 +09:00
Junegunn Choi
6ed9de9051 [zsh] Temporarily unset no_bang_hist for CTRL-R
Close #214
2015-04-23 22:31:23 +09:00
Junegunn Choi
857619995e [vim] Ignore E325 (#213) 2015-04-23 19:29:59 +09:00
Junegunn Choi
9310ae28ab [vim] Redraw screen after running fzf on tmux pane (#213) 2015-04-23 19:29:01 +09:00
Junegunn Choi
27e26bd1ea [vim] Add g:Fzf_launcher for funcrefs (#212) 2015-04-23 12:51:08 +09:00
Junegunn Choi
305ec3b3ce [fish] Remove buffering delay by not using subroutines
Close #169
2015-04-22 14:33:03 +09:00
Junegunn Choi
f4fe93338b Update README 2015-04-22 02:09:16 +09:00
Junegunn Choi
3b84c80d56 Update README 2015-04-22 02:07:27 +09:00
Junegunn Choi
5e120e7ab5 Update man page 2015-04-22 01:44:56 +09:00
Junegunn Choi
a4cf5510e3 0.9.11 2015-04-22 01:42:38 +09:00
Junegunn Choi
edb5ab5622 Update test cases for #203 2015-04-22 00:57:25 +09:00
Junegunn Choi
06b4f75680 Fix broken FZF_TMUX switch and update test cases (#203) 2015-04-22 00:55:39 +09:00
Junegunn Choi
318edc8c35 Apply fzf-tmux to key bindings (#203)
Note that CTRL-T on bash is still using the old trick of send-keys.
2015-04-22 00:32:18 +09:00
Junegunn Choi
651a8f8cc2 Add --inline-info option
Close #202
2015-04-21 23:50:53 +09:00
Junegunn Choi
9f64a00549 Fix double-click result when scroll offset is positive 2015-04-21 23:23:39 +09:00
Junegunn Choi
a88bf87e2a Update test case 2015-04-21 22:36:40 +09:00
Junegunn Choi
e82eb27787 Smart-case for each term in extended-search mode
Close #208
2015-04-21 22:18:05 +09:00
Junegunn Choi
3f0e6a5806 Fix #209 - Invalid mutation of input on case conversion 2015-04-21 22:10:14 +09:00
Junegunn Choi
917b1759b0 [fzf-tmux/vim] Fixes for fish (#204) 2015-04-20 22:42:12 +09:00
Junegunn Choi
16ca9c688b Revert "[fzf-tmux] Fix #204 - Escape command substitution"
This reverts commit 7b6a27cb5e15c7bb749ffc9adbc74f852c3ad6c2.
2015-04-20 16:23:15 +09:00
Junegunn Choi
7b6a27cb5e [fzf-tmux] Fix #204 - Escape command substitution 2015-04-20 15:22:59 +09:00
Junegunn Choi
869a234938 [fzf-tmux] Use bash instead of sh (#204)
The default shell can be a non-standard shell (e.g. fish)
2015-04-20 14:58:27 +09:00
Junegunn Choi
537d07c1e5 [vim] Use "system" fzf when available
1. Go binary: ../bin/fzf
2. System fzf: $(which fzf)
3. Download fzf from GitHub or create wrapper script to Ruby version (../fzf)
   when the binary for the platform is not available
4. If install script is not found or for some reason failed, try to use Ruby
   version in its expected location (../fzf)
5. If fzf is found to be a shell function, use it (type fzf)
2015-04-19 17:13:07 +09:00
Junegunn Choi
d091a2c4bb [fzf-tmux] Minor adjustment 2015-04-18 16:27:40 +09:00
Junegunn Choi
d2f95d69fb [fzf-tmux] Fix #200 - Double-quote handling
Related #199
2015-04-18 16:24:57 +09:00
Junegunn Choi
1169cc8653 0.9.10 2015-04-18 10:43:40 +09:00
Junegunn Choi
f66d94c6b0 Add --color=[dark|light|16|bw] option
- dark:  the current default for 256-color terminal
- light: color scheme for 256-color terminal with light background
- 16:    the default color scheme for 16-color terminal (`+2`)
- bw:    no colors (`+c`)
2015-04-18 02:55:17 +09:00
Junegunn Choi
2fe1e28220 Improvements in performance and memory usage
I profiled fzf and it turned out that it was spending significant amount
of time repeatedly converting character arrays into Unicode codepoints.
This commit greatly improves search performance after the initial scan
by memoizing the converted results.

This commit also addresses the problem of unbounded memory usage of fzf.
fzf is a short-lived process that usually processes small input, so it
was implemented to cache the intermediate results very aggressively with
no notion of cache expiration/eviction. I still think a proper
implementation of caching scheme is definitely an overkill. Instead this
commit introduces limits to the maximum size (or minimum selectivity) of
the intermediate results that can be cached.
2015-04-17 22:23:52 +09:00
Junegunn Choi
288131ac5a Update man page to be consistent with --help 2015-04-16 23:11:11 +09:00
Junegunn Choi
3610acec5a 0.9.9 2015-04-16 22:52:15 +09:00
Junegunn Choi
cc67d2e1cf Test case for visual indicator of --toggle sort (#194) 2015-04-16 22:39:51 +09:00
Junegunn Choi
f77ed0fb07 Fix typo in man page 2015-04-16 22:34:02 +09:00
Junegunn Choi
a30908c66a [vim] Automatically download Go binary when not found 2015-04-16 22:24:12 +09:00
Junegunn Choi
f9225f98e7 Fix sort control from Terminal 2015-04-16 22:13:31 +09:00
Junegunn Choi
2db2feea37 install --bin just for downloading the binary 2015-04-16 21:58:41 +09:00
Junegunn Choi
d1d59272a2 Add visual indication of --toggle-sort
Close #194
2015-04-16 14:46:10 +09:00
Junegunn Choi
d08542ce5d Prepare for 0.9.9 release 2015-04-16 14:34:40 +09:00
Junegunn Choi
b8904a8c3e Add --tiebreak option for customizing sort criteria
Close #191
2015-04-16 14:19:28 +09:00
Junegunn Choi
48ab87294b Add --no-hscroll option to disable horizontal scroll
Close #193
2015-04-16 12:56:01 +09:00
Junegunn Choi
3e1e75fe08 Remove unused variable 2015-04-16 10:52:04 +09:00
Junegunn Choi
120cc0aadd [vim] README: Pointer to the wiki page 2015-04-15 22:52:15 +09:00
Junegunn Choi
853012ceef [vim] Add g:fzf_action for customizing key bindings
Close #189
2015-04-15 22:49:45 +09:00
Junegunn Choi
2add45fe2f [vim] Rename g:fzf_tmux_height to g:fzf_height
Because tmux panes are not used on Neovim.
2015-04-15 22:32:45 +09:00
Junegunn Choi
b882de87ab Fix Travis CI build 2015-04-15 01:58:39 +09:00
Junegunn Choi
2d68cb8639 Fix #185 - Terminate on RuneError 2015-04-14 23:19:55 +09:00
Junegunn Choi
3a9d1df026 Fix unicode test case 2015-04-14 21:59:44 +09:00
Junegunn Choi
5c25984ea0 Fix Unicode case handling (#186) 2015-04-14 21:45:37 +09:00
Junegunn Choi
319d6ced80 [vim] Simplify :FZF
Ruby version can also accept `--expect` option although it's ignored.
2015-04-14 10:46:20 +09:00
Junegunn Choi
51a19a2804 [vim] Remove unnecessary pushd/popd in :FZF
It is already handled by its caller.
2015-04-14 10:35:51 +09:00
Junegunn Choi
a7cb1a78df Merge pull request #188 from justinmk/non-interactive-shell
install: wait for LF in non-interactive shell
2015-04-14 10:13:52 +09:00
Justin M. Keyes
d4daece76b install: wait for LF in non-interactive shell
"read -n 1 ..." ignores all but the first character of a line-delimited
stream (e.g. "yes n | ./install").
2015-04-13 20:40:46 -04:00
Junegunn Choi
3ec83babac FZF_TMUX and FZF_TMUX_HEIGHT for fuzzy completion 2015-04-14 02:12:45 +09:00
Junegunn Choi
91fc6c984b Fix fuzzy completion test 2015-04-14 02:00:50 +09:00
Junegunn Choi
a4f3d09704 Fuzzy completion using fzf-tmux 2015-04-14 01:00:39 +09:00
Junegunn Choi
40180c18ac Merge pull request #183 from qiemem/generalized-check-if-running
Check if fzf#run() is already executing
2015-04-12 02:45:14 +09:00
Bryan Head
82bea6758a Move active check to fzf#run. 2015-04-11 12:44:14 -05:00
Junegunn Choi
348731fc3b Make fzf-tmux work when fzf is not in $PATH but in the same directory
See: #181
2015-04-12 01:57:56 +09:00
Junegunn Choi
797f42ecc6 Update README 2015-04-12 00:44:41 +09:00
Junegunn Choi
8385a55bda [vim] s:pushd after s:split
It is possible that the user has an autocmd that changes the current
directory.
2015-04-11 23:47:46 +09:00
Junegunn Choi
8406cedf2d [vim] Improved compatibility with sidebar plugins (e.g. NERDtree) 2015-04-11 23:42:01 +09:00
Junegunn Choi
f22b83db6c Update README 2015-04-11 11:28:30 +09:00
Junegunn Choi
1481304d3b Suppress message from :file
Suggested by @noahfrederick
2015-04-11 11:19:22 +09:00
Junegunn Choi
2cec5c0f30 Fix typo in README 2015-04-11 09:21:23 +09:00
Junegunn Choi
4760bb7743 Merge pull request #180 from mhinz/check-if-already-running
Check if :FZF is already executing
2015-04-11 09:16:44 +09:00
Marco Hinz
c1adf0cd3d Check if :FZF is already executing
Prior to this change, you'd get a longer error message if you did:

    :FZF
    <esc>
    :FZF

The main problem being that `:file [FZF]` can be used only once.
2015-04-10 22:18:46 +02:00
Junegunn Choi
622e69ff54 [vim] Neovim compatibility (#137)
Use terminal emulator of Neovim to open fzf
2015-04-10 23:23:47 +09:00
Junegunn Choi
68503d32df [vim] Code cleanup 2015-04-04 11:55:57 +09:00
Junegunn Choi
57319f8c58 [vim] Fix #177 - :FZF with relative paths 2015-04-04 09:18:04 +09:00
Junegunn Choi
dd4d465305 Update Homebrew instruction
Close #175
2015-04-03 01:22:16 +09:00
Junegunn Choi
467a22dd36 Period. 2015-03-31 22:09:04 +09:00
Junegunn Choi
50292adacb Implement --toggle-sort option (#173) 2015-03-31 22:05:16 +09:00
Junegunn Choi
84a7499ae3 Fix #172 - Print empty line when fzf with expect finished by -1 or -0 2015-03-31 20:52:16 +09:00
Junegunn Choi
39d7177bd3 [ruby] Stub out --expect option
So that it can be used with the recent Vim plugin although extra key
bindings are not available
2015-03-31 15:40:33 +09:00
Junegunn Choi
1c65139888 Update git ls-tree example (close #168) 2015-03-30 10:15:05 +09:00
Junegunn Choi
8a4db3c004 [vim] Fix #167 - :FZF with directory 2015-03-29 11:14:16 +09:00
Junegunn Choi
cef93f700b 0.9.6 2015-03-29 04:09:45 +09:00
Junegunn Choi
0a3d3460b1 Update man page 2015-03-29 04:08:37 +09:00
Junegunn Choi
d988f3fa50 Retain ANSI background color 2015-03-29 03:12:55 +09:00
Junegunn Choi
e865144ace [vim] Implement ctrlp-compatible key bindings (#139, #96, #61) 2015-03-29 03:00:32 +09:00
Junegunn Choi
2a167aa030 Implement --expect option to support simple key bindings (#163) 2015-03-29 02:59:32 +09:00
Junegunn Choi
9cfecf7f0b Fix test failure 2015-03-28 21:37:37 +09:00
Junegunn Choi
f9d6b83f5e Drop patch number and stick with M.m.p convention 2015-03-27 12:47:42 +09:00
Junegunn Choi
ce7d4a1c53 Fix #162 - Ignore \e[K 2015-03-27 12:35:06 +09:00
Junegunn Choi
9bba6bd172 Merge pull request #158 from mrap/update-macvim-link
Updates MacVim wiki link in README
2015-03-27 09:14:02 +09:00
Michael Rapadas
4ad92e3a0b Updates MacVim wiki link in README 2015-03-26 13:28:19 -07:00
Junegunn Choi
c4bf820dc3 Update man page 2015-03-26 10:31:47 +09:00
Junegunn Choi
39f43587d0 Fix typo in man page 2015-03-26 10:28:14 +09:00
Junegunn Choi
fdaa4e9b18 Append (not prepend) bin directory to PATH
Prepending can be problematic when the user install fzf using Homebrew,
execute the install script, and later upgrade fzf with Homebrew, and do
not rerun the install script. In that case, even though the homebrew
package is upgraded, the older version will still be used.
2015-03-26 03:44:18 +09:00
Junegunn Choi
91876e98cd Avoid duplicate paths in MANPATH 2015-03-26 03:26:28 +09:00
Junegunn Choi
eb8fef0031 Add man path only when the directory exists
$fzf_base/man may not exist when installed with Homebrew.
2015-03-26 03:11:08 +09:00
Junegunn Choi
87447ddd6d Add man page (#157) 2015-03-26 03:08:39 +09:00
Junegunn Choi
9d138173be Fix #155 - Empty ANSI color code to reset color state 2015-03-23 01:24:31 +09:00
Junegunn Choi
eae53576bd Update --help message 2015-03-22 21:25:46 +09:00
Junegunn Choi
f8c49effd4 Respect "boldness" of input string 2015-03-22 17:43:28 +09:00
Junegunn Choi
618706a5f5 Fix ANSI output in the presence of multibyte characters
tree -C | fzf --ansi --tac
2015-03-22 17:22:52 +09:00
Junegunn Choi
9ffcd26d50 Update CHANGELOG - 0.9.5 2015-03-22 16:21:52 +09:00
Junegunn Choi
b431e227da Code cleanup 2015-03-22 16:05:54 +09:00
Junegunn Choi
d94dfe0876 Fix #151 - reduce initial memory footprint 2015-03-19 19:59:38 +09:00
Junegunn Choi
6130026786 Bump up the version - 0.9.5 2015-03-19 19:12:22 +09:00
Junegunn Choi
a723977b9f Fix #149 - panic on empty string filter 2015-03-19 13:06:20 +09:00
Junegunn Choi
3dddbfd8fa Fix string truncation 2015-03-19 12:14:26 +09:00
Junegunn Choi
e70a2a5817 Add support for ANSI color codes 2015-03-19 01:59:14 +09:00
Junegunn Choi
d80a41bb6d Update README
Use --depth option to avoid pulling devel branches
2015-03-18 02:19:05 +09:00
Junegunn Choi
2bebd5cdb4 Update README with fzf image 2015-03-16 02:49:09 +09:00
Junegunn Choi
7bb75b0213 Update README 2015-03-15 10:57:09 +09:00
Junegunn Choi
bc2e82efc1 [vim] Suppress error message when clear command is N/A 2015-03-13 23:04:13 +09:00
Junegunn Choi
c04e8de9b0 Make sure to start tmux pane from the current directory (#143)
- fzf-tmux
- CTRL-T of bash/zsh/fish
    - fish implementation may not work if the path contains
      double-quote characters (FIXME)
2015-03-13 22:59:23 +09:00
Junegunn Choi
4977174def [fzf-mux] Remove unnecessary env var from command 2015-03-13 22:45:28 +09:00
Junegunn Choi
5eef0acea1 Merge pull request #145 from junegunn/refactor-shell-ext
Refactor shell extensions
2015-03-13 17:44:44 +09:00
Junegunn Choi
3935aa84d8 Refactor shell extensions
- Use symlinks instead of generating the full content
- Update fish_user_paths and remove ~/.config/fish/functions/fzf.fish
- Create wrapper script for fzf when Ruby version and use it instead of
  exported function not to break fzf-tmux
2015-03-13 17:41:00 +09:00
Junegunn Choi
dd6138a655 Fix #142, #144 - Improve CTRL-R for zsh 2015-03-13 01:33:01 +09:00
Junegunn Choi
68c5bea3f8 Fix install script for platforms w/o matching Go binary (#141) 2015-03-12 10:06:15 +09:00
Junegunn Choi
0f474d541d Note on upgrade 2015-03-11 15:07:34 +09:00
Junegunn Choi
c4d59aeec4 Remove legacy test code 2015-03-11 02:16:27 +09:00
Junegunn Choi
b2c423d1ff Cleanup - no more rubygems 2015-03-11 02:12:38 +09:00
Junegunn Choi
49c752b1f7 [vim] up/down/left/right options to take boolean values
When 1 is given, 50% of the screen width or height will be used as the
default size of the pane.
2015-03-10 12:13:11 +09:00
Junegunn Choi
daa79a6df2 [vim] fzf#run with tmux panes can now return values to the caller
As they're made synchronous with the use of fzf-tmux script
2015-03-10 12:07:32 +09:00
Junegunn Choi
48e0c1e721 Ignore new options in legacy Ruby version 2015-03-10 02:16:32 +09:00
Junegunn Choi
12d81e212f [vim] Use fzf-tmux script for tmux integration 2015-03-10 01:41:35 +09:00
Junegunn Choi
c22e729d9c [fzf-tmux] Apply environment variables 2015-03-09 23:57:17 +09:00
Junegunn Choi
2b8a1c0d70 Update README - Homebrew instruction and fzf-tmux options 2015-03-09 23:40:43 +09:00
Junegunn Choi
e4b56b9702 Merge pull request #138 from junegunn/fzf-tmux-swap-pane
[fzf-tmux] Allow opening fzf on any position (up/down/left/right)
2015-03-09 23:28:53 +09:00
Junegunn Choi
789a474b28 [fzf-tmux] Allow opening fzf on any position (-u/-d/-l/-r)
The previous -w and -h will be synonyms for -r and -d respectively.
2015-03-09 12:49:26 +09:00
Junegunn Choi
fb2959c514 [fzf-tmux] Fix duplicate arguments to fzf
fzf-tmux -w -q q
fzf-tmux -w -- -q q
2015-03-08 16:40:48 +09:00
Junegunn Choi
62a28468a7 [fzf-tmux] Fix -- 2015-03-08 16:36:37 +09:00
Junegunn Choi
23dba99eda [fzf-tmux] Allow -w / -h without size argument 2015-03-08 15:08:27 +09:00
Junegunn Choi
5f62d224b0 Fix fzf-tmux script (bash 3.2 compatibility) 2015-03-07 10:07:36 +09:00
Junegunn Choi
6728870071 Merge pull request #136 from junegunn/fzf-tmux
Add fzf-tmux script
2015-03-07 10:01:23 +09:00
Junegunn Choi
87c71a3ea6 Increase timeout in test cases 2015-03-07 09:53:54 +09:00
Junegunn Choi
06ab399497 Improve how vim plugin finds fzf executable
This avoids the problem in which :FZF command silently fails when fzf
executable cannot be found in $PATH of the hosting tmux server.
2015-03-07 09:48:56 +09:00
Junegunn Choi
f7b52d2541 Use absolute path of fzf when splitting tmux window 2015-03-07 09:29:16 +09:00
Junegunn Choi
c111af0ed2 Use the term pane instead of split when not ambiguous
/cc @Tranquility
2015-03-07 09:08:41 +09:00
Junegunn Choi
07e2bd673e Update README 2015-03-06 18:57:36 +09:00
Junegunn Choi
e4ce64d10b Add fzf-tmux script 2015-03-06 18:51:50 +09:00
Junegunn Choi
5f3326a888 Deprecation alert 2015-03-06 13:21:55 +09:00
Junegunn Choi
1304428003 Update bash completion *for* fzf 2015-03-06 10:42:38 +09:00
Junegunn Choi
55828f389a Add test case for 7e2c18a 2015-03-04 13:13:11 +09:00
Junegunn Choi
7e2c18a1f6 Fix directory completion matching regular files
Related: #135
2015-03-04 13:03:54 +09:00
Junegunn Choi
79c147ed78 Fix #135 - Directory completion to append / 2015-03-04 12:59:23 +09:00
Junegunn Choi
d4b41c5e03 Merge pull request #134 from junegunn/devel
0.9.4
2015-03-01 12:35:08 +09:00
Junegunn Choi
b15a0e9650 Update CHANGELOG 2015-03-01 12:31:49 +09:00
Junegunn Choi
fe09559ee9 Build with Go 1.4.2 2015-03-01 11:49:11 +09:00
Junegunn Choi
94e8e6419f Make --filter non-blocking when --no-sort (#132)
When fzf works in filtering mode (--filter) and sorting is disabled
(--no-sort), there's no need to block until input is complete. This
commit makes fzf print the matches on-the-fly when the following
condition is met:

    --filter FILTER --no-sort [--no-tac --no-sync]

or simply:

    -f FILTER +s

This removes unnecessary delay in use cases like the following:

    fzf -f xxx +s | head -5

However, in this case, fzf processes the input lines sequentially, so it
cannot utilize multiple cores, which makes it slightly slower than the
previous mode of execution where filtering is done in parallel after the
entire input is loaded. If the user is concerned about the performance
problem, one can add --sync option to re-enable buffering.
2015-03-01 11:16:38 +09:00
Junegunn Choi
4d2d18649c Add basic test cases for shell extensions (#83)
- Key bindings for bash, zsh, and fish
- Fuzzy completion for bash (file, dir, process)
2015-03-01 03:33:56 +09:00
Junegunn Choi
c1aa5c5f33 Add --tac option and reverse display order of --no-sort
DISCLAIMER: This is a backward incompatible change
2015-02-26 01:42:15 +09:00
Junegunn Choi
4a1752d3fc 0.9.3 2015-02-18 13:19:20 +09:00
Junegunn Choi
b9b1eeffce Update Vader tests 2015-02-18 12:12:59 +09:00
Junegunn Choi
5667667d1f Add test case for --sync option 2015-02-18 12:07:54 +09:00
Junegunn Choi
f5b034095a Fix race condition in asynchronous -1 and -0 2015-02-18 00:51:44 +09:00
Junegunn Choi
95e5beb34e Update Homebrew instruction 2015-02-18 00:22:17 +09:00
Junegunn Choi
e808151c28 Make --select-1 and --exit-0 asynchronous 2015-02-18 00:08:17 +09:00
Junegunn Choi
d760b790b3 Fix typo in code 2015-02-17 19:28:10 +09:00
Junegunn Choi
1b5599972a Update installation instruction 2015-02-17 13:15:16 +09:00
Junegunn Choi
6c2ce28d0d Add --sync option 2015-02-13 12:25:19 +09:00
Junegunn Choi
ff09c275d4 Fix bash script when fzf_base contains spaces 2015-02-12 10:14:05 +09:00
Junegunn Choi
93dcd932e8 Merge pull request #123 from junegunn/fix-travis-ci
Fix Travis CI build
2015-01-29 17:44:11 +09:00
Junegunn Choi
e6a0de4094 Fix Travis CI build 2015-01-29 17:41:28 +09:00
Junegunn Choi
9f39671e65 Update README.md
Update outdated --help output
2015-01-28 01:45:34 +09:00
Junegunn Choi
423317b82a Update README.md 2015-01-28 01:18:20 +09:00
Junegunn Choi
47201c2c4d Merge pull request #122 from blueyed/improve-find-cdwidget
Improve `find` command for ALT-C: exclude proc/dev
2015-01-25 11:20:20 +09:00
Daniel Hahler
53d5d9d162 Improve find command for cd widgets: exclude proc/dev etc
When using the widget in "/", it would descend into 'dev/'.
Using '*' for the starting path would do so also with the new '-fstype'
excludes.

`cut -b3-` and `sed 1d` have been added to massage the different format
of the list.

This also uses `-L` with all calls to find, especially for the file
finders.

Ref: https://github.com/junegunn/fzf/pull/122
2015-01-25 03:09:02 +01:00
Junegunn Choi
9cb0cdb4ac 0.9.2 2015-01-24 14:49:21 +09:00
Junegunn Choi
448132c46c Fix error when --query contains wide-length characters 2015-01-24 13:26:33 +09:00
Junegunn Choi
1476fc7f3b Refactor test code 2015-01-24 13:25:11 +09:00
Junegunn Choi
71a7b3a26f Improve rendering performance by caching rune widths
Related: 8bead4a
2015-01-24 12:28:00 +09:00
Junegunn Choi
a47c06cb61 Fix update_assets script 2015-01-23 20:32:56 +09:00
Junegunn Choi
48e16edb47 Redraw and adjust upon terminal resize 2015-01-23 20:30:50 +09:00
Junegunn Choi
c35d98dc42 Nullify --nth option when it's irrelevant 2015-01-23 06:26:00 +09:00
Junegunn Choi
8bead4ae34 Improved handling of tab characters 2015-01-18 16:59:04 +09:00
Junegunn Choi
1b6cb3532d Update src/README.md 2015-01-18 16:34:10 +09:00
Junegunn Choi
0a0955755a Add note on installation 2015-01-18 16:32:37 +09:00
Junegunn Choi
a3101120fd Update install script 2015-01-17 20:40:00 +09:00
Junegunn Choi
30f9651f99 0.9.1 2015-01-17 14:15:26 +09:00
Junegunn Choi
4dcc0f10b8 Fix Travis CI build by ignoring trailing empty lines
😭
2015-01-17 13:45:56 +09:00
Junegunn Choi
3d39ab5ded Fix flaky tests 2015-01-17 13:39:11 +09:00
Junegunn Choi
c3a198d0c7 Add test cases for --select-1 and --exit-0 2015-01-17 12:37:24 +09:00
Junegunn Choi
be5c17612a Add basic test case for --reverse 2015-01-17 12:21:38 +09:00
Junegunn Choi
fe89ac8a89 Add script for updating release assets 2015-01-17 11:57:21 +09:00
Junegunn Choi
4c3ae847b6 Add test case for --with-nth + --multi 2015-01-17 11:20:17 +09:00
Junegunn Choi
5c0dc79ffa Print selected items in the order they are selected 2015-01-17 11:07:04 +09:00
Junegunn Choi
0a83705d21 Use Go 1.4.1 to build linux binaries 2015-01-17 10:57:07 +09:00
Junegunn Choi
ea22292d2c Merge pull request #117 from junegunn/fix-ctrl-y
Fix CTRL-Y key binding
2015-01-17 10:55:05 +09:00
Junegunn Choi
1990f3c992 Do not build i386 binary on Travis CI to speed up the process 2015-01-17 10:51:39 +09:00
Junegunn Choi
c0b432f7b4 Fix Travis-CI build 2015-01-17 10:39:18 +09:00
Junegunn Choi
ae3180f919 Fix CTRL-Y key binding
With tmux-based test cases
2015-01-17 06:04:59 +09:00
Junegunn Choi
62acb9adc4 Fix error with empty list and release 0.9.1-dev 2015-01-15 06:06:22 +09:00
Junegunn Choi
0b5fa56444 Remove brew target 2015-01-14 02:26:47 +09:00
Junegunn Choi
789f26b1a5 Add GIF to src/README 2015-01-14 02:16:03 +09:00
Junegunn Choi
a3068a33d5 Update install/build script from Homebrew 2015-01-14 00:02:37 +09:00
Junegunn Choi
b8c4b35415 make archive for homebrew release 2015-01-13 12:29:12 +09:00
Junegunn Choi
209a6d36ad Merge pull request #116 from junegunn/go
Rewritten in Go
2015-01-13 02:40:48 +09:00
Junegunn Choi
5c491d573a Fix fzf.{bash,zsh} when Go version is not supported 2015-01-13 02:39:00 +09:00
Junegunn Choi
2c86e728b5 Update src/README.md 2015-01-13 02:27:08 +09:00
Junegunn Choi
cd847affb7 Reorganize source code 2015-01-12 12:56:17 +09:00
Junegunn Choi
7a2bc2cada Lint 2015-01-12 03:18:40 +09:00
Junegunn Choi
9dbf6b02d2 Fix race conditions
- Wait for completions of goroutines when cancelling a search
- Remove shared access to rank field of Item
2015-01-11 23:49:12 +09:00
Junegunn Choi
1db68a3976 Avoid unnecessary update of search progress 2015-01-11 21:56:55 +09:00
Junegunn Choi
1c31352675 Update src/README.md and package comment 2015-01-11 17:01:30 +09:00
Junegunn Choi
6c3489087c Refactor Makefile and Dockerfiles 2015-01-11 14:19:50 +09:00
Junegunn Choi
313578a1a0 Improve prefix/suffix cache lookup 2015-01-11 03:53:07 +09:00
Junegunn Choi
bd7331ecf5 Remove unnecessary loop label 2015-01-11 03:45:49 +09:00
Junegunn Choi
e293cd4d08 Add test cases for ChunkCache 2015-01-11 02:20:54 +09:00
Junegunn Choi
ca4bdfb4bd Fix Transform result cache to speed up subsequent searches 2015-01-11 01:53:51 +09:00
Junegunn Choi
4f40314433 Fix --with-nth option when query is non-empty 2015-01-11 01:30:17 +09:00
Junegunn Choi
f670f4f076 Make sure that cy is properly limited 2015-01-10 14:50:24 +09:00
Junegunn Choi
6e86fee588 Change Merger implementation on --no-sort 2015-01-10 14:24:12 +09:00
Junegunn Choi
2d9b38b93e Constrain cy in vmove() 2015-01-10 14:22:00 +09:00
Junegunn Choi
b8a9861f95 Fix double click on an empty row not to close fzf 2015-01-10 12:26:11 +09:00
Junegunn Choi
188c90bf25 Fix incorrect behaviors of mouse events when --multi enabled 2015-01-10 12:21:17 +09:00
Junegunn Choi
8b02ae650c Update src/README.md 2015-01-10 01:16:13 +09:00
Junegunn Choi
b7bb100810 Improve response time by only looking at top-N items 2015-01-10 01:06:18 +09:00
Junegunn Choi
aa05bf5206 Reduce memory footprint 2015-01-09 10:42:12 +09:00
Junegunn Choi
d303c5b3eb Minor refactoring 2015-01-09 02:35:20 +09:00
Junegunn Choi
f401c42f9c Adjust initial coordinator delay 2015-01-08 22:07:04 +09:00
Junegunn Choi
efec9acd6f Fix missing mutex unlock 2015-01-08 22:04:12 +09:00
Junegunn Choi
3ed86445e1 Remove call to ncurses set_tabsize()
Not available on old verions of ncurses
2015-01-08 11:04:25 +09:00
Junegunn Choi
23f27f3ce5 Improve install script 2015-01-07 20:08:05 +09:00
Junegunn Choi
f99f66570b Add small initial delay to screen update
To avoid flickering when the input is small
2015-01-07 12:46:45 +09:00
Junegunn Choi
3e129ac68c Remove extraneous quote-escape 2015-01-07 09:59:24 +09:00
Junegunn Choi
8a0ab20a70 Update vim plugin to use Go binary 2015-01-07 01:14:35 +09:00
Junegunn Choi
b277f5ae6f Fix i386 build 2015-01-07 00:24:05 +09:00
Junegunn Choi
6109a0fe44 Refactor Makefile 2015-01-06 02:07:30 +09:00
Junegunn Choi
383f908cf7 Remove unnecessary event dispatch 2015-01-06 02:04:27 +09:00
Junegunn Choi
3e6c950e12 Build i386 binary as well 2015-01-06 02:04:06 +09:00
Junegunn Choi
ee2ee02599 Fix index out of bounds error during Transform 2015-01-05 19:32:44 +09:00
Junegunn Choi
b42dcdb7a7 Update README for Go - System requirements 2015-01-05 12:21:56 +09:00
Junegunn Choi
82156d34cc Update Makefile and install script
fzf may not run correctly on some OS even when the binary the platform
is successfully downloaded. The install script is updated to check if
the system has no problem running the executable and fall back to Ruby
version when necessary.
2015-01-05 12:21:26 +09:00
Junegunn Choi
4a5142c60b Do not sort terms when building cache key 2015-01-05 02:32:18 +09:00
Junegunn Choi
ea25e9674f Refactor install script 2015-01-05 02:17:26 +09:00
Junegunn Choi
dee0909d2b Fix mouse click offset when list is scrolled 2015-01-05 01:40:19 +09:00
Junegunn Choi
8e5ecf6b38 Update Makefile and installer to use version number 2015-01-05 01:25:54 +09:00
Junegunn Choi
7557737569 Remove outdated information from README 2015-01-05 00:52:08 +09:00
Junegunn Choi
53bce0581e Update fish function 2015-01-04 14:35:13 +09:00
Junegunn Choi
f9f9b671c5 Ask if fzf executable already exists 2015-01-04 14:29:42 +09:00
Junegunn Choi
606d33e77e Remove race conditions from screen update 2015-01-04 05:09:40 +09:00
Junegunn Choi
d2f7acbc69 Remove race conditions when accessing the last chunk 2015-01-04 05:01:13 +09:00
Junegunn Choi
0dd024a09f Remove unnecessary delay on non/defered interactive mode 2015-01-04 05:00:28 +09:00
Junegunn Choi
0a6cb62169 Fall back to Ruby version when download failed 2015-01-04 02:42:58 +09:00
Junegunn Choi
9930a1d4d9 Update install script to download tarball 2015-01-04 02:00:22 +09:00
Junegunn Choi
40d0a6347c Fix scan limit for --select-1 and --exit-0 options 2015-01-04 01:47:59 +09:00
Junegunn Choi
baad26a0fd Fix exit conditions of --select-1 and --exit-0 2015-01-04 01:36:33 +09:00
Junegunn Choi
f3177305d5 Rewrite fzf in Go 2015-01-04 00:37:29 +09:00
802ce0400f Added -y option for install script 2015-01-02 18:24:32 -05:00
Junegunn Choi
4ceb520c1d Merge pull request #115 from JackDanger/sleep-when-curses-is-unavailable
Sleep when curses is unavailable
2015-01-02 15:28:33 +09:00
Jack Danger Canty
d761ea5158 Sleep when curses is unavailable
When the curses gem is not installed and the session is running inside
tmux the user will see a flash of an opened and closed tmux pane but
will not have a chance to read the error message.
2015-01-01 22:23:37 -08:00
Junegunn Choi
7ba93d9f83 Merge pull request #113 from thedrow/patch-1
Use travis' new build workers
2014-12-29 01:03:04 +09:00
Omer Katz
b34f93f307 Use travis' new build workers
They boot faster and since we don't use root we can use them.
2014-12-28 17:11:13 +02:00
Junegunn Choi
ec040d82dd Improve word motions: ALT-B, ALT-F, ALT-D, ALT-BS (#112) 2014-12-24 13:27:39 +09:00
Junegunn Choi
00190677d4 Add support for ALT-D and ALT-BS key bindings
https://github.com/junegunn/fzf/issues/111#issuecomment-67832143
2014-12-23 12:22:19 +09:00
Junegunn Choi
d38f7a5eb5 Merge pull request #109 from brettanomyces/reorder_fish_history
Reverse the order of fish history
2014-12-13 11:10:29 +09:00
brettanomyces
ee433ef6e9 reverse history for fish shell 2014-12-13 11:54:35 +13:00
Junegunn Choi
d89c9e94ba Handle dynamically loaded completion functions (#107 / #79) 2014-12-05 00:24:25 +09:00
Junegunn Choi
7e2dfef930 Merge pull request #106 from jagajaga/master
Change `/bin/bash` to `/usr/bin/env bash`
2014-12-01 18:19:33 +09:00
Arseniy Seroka
0296fcb5cd bash -> env bash 2014-11-30 23:04:15 +03:00
Junegunn Choi
80819f3c44 Merge pull request #104 from junegunn/add-with-nth
Add --with-nth option
2014-11-04 23:30:11 +09:00
Junegunn Choi
7571baadb4 Fix test failure on Ruby 1.8.7
Hashes are unordered on Ruby 1.8
2014-11-04 19:32:31 +09:00
Junegunn Choi
da03a66e69 Add test cases for --with-nth option 2014-11-04 19:01:15 +09:00
Junegunn Choi
3c47b7fa5f Fix --with-nth option on --multi 2014-11-03 23:58:10 +09:00
Junegunn Choi
ba9365c438 Fix --with-nth option on Ruby 1.8 2014-11-03 23:48:37 +09:00
Junegunn Choi
db37e67575 Skip failing tests on Ruby 1.8 2014-11-01 14:52:29 +09:00
Junegunn Choi
76a3ef8c37 Add --with-nth option (#102) 2014-11-01 14:49:05 +09:00
Junegunn Choi
6fd6fff3a6 [vim] Ignore 'dir' option if empty
This makes it easier to override FZF command like follows:

    autocmd VimEnter * command! -nargs=? -bang -complete=dir FZF call fzf#run({
          \ 'sink': 'tabe',
          \ 'dir': <q-args>,
          \ 'options': '-m',
          \ 'tmux_height': empty('<bang>') ? '40%' : '' })
2014-10-15 13:22:00 +09:00
Junegunn Choi
d1387bf512 Use IO.console when possible 2014-10-07 11:49:40 +09:00
Junegunn Choi
4c923a2d19 [uninstall] Remove both patterns of source command (#97)
- `[ -f ~/.fzf.${shell} ] && source ~/.fzf.${shell}"`
- `source ~/.fzf.${shell}"`
2014-09-18 19:21:58 +09:00
Junegunn Choi
4ee85f11e8 [install] Join line numbers when multiple matches found 2014-09-18 19:03:01 +09:00
Junegunn Choi
829c7f909c Merge branch 'mjwhitta-master' 2014-09-18 18:49:43 +09:00
Miles Whittaker
990fa00660 Check before sourcing, no longer need to remove 2014-09-18 00:01:39 -04:00
Miles Whittaker
77592825f0 Sometimes users prefer . instead of source
So only check for file name
2014-09-17 23:55:28 -04:00
Miles Whittaker
ce53b9b2a5 Ignore user-defined grep aliases 2014-09-14 00:53:53 -04:00
Junegunn Choi
175fe158ed Add vim-plug recipe 2014-09-02 13:06:05 +09:00
Junegunn Choi
80efafcceb Fix ALT-C keybinding to include symlinked directories
Related #95.
2014-08-31 03:22:51 +09:00
Junegunn Choi
b241409e4b Merge pull request #95 from Neki/topic/resolve_symlinks
Follow symlinks when using bash autocompletion.
2014-08-30 22:33:21 +09:00
Benoît Faucon
11967be017 Follow symlinks when using bash autocompletion. 2014-08-30 14:56:30 +02:00
Junegunn Choi
6ee811ea03 Update version 2014-08-17 02:21:34 +09:00
Junegunn Choi
d5e7303a25 Change --nth option for CTRL-R key binding (#90)
Remove `1` from --nth option. With the change you can no more use `$`
anchor to match the tail of a command index. But it makes search
around 15% faster.

    jg@jg:~> time cat history | fzf +s -n..,1,2.. -f fzf > /dev/nul
    real    0m2.929s
    user    0m2.766s
    sys     0m0.154s

    jg@jg:~> time cat history | fzf +s -n2..,.. -f fzf > /dev/null
    real    0m2.535s
    user    0m2.422s
    sys     0m0.112s
2014-08-17 00:29:57 +09:00
Junegunn Choi
2924fd3e23 Add regression test case for #91 2014-08-17 00:22:22 +09:00
Junegunn Choi
75b44aac13 Ignore UTF-8 Error (#91) 2014-08-16 19:52:56 +09:00
Junegunn Choi
86c73105ee Improve performance of --nth option (#90 contd.) 2014-08-15 04:01:37 +09:00
Junegunn Choi
2d00abc7cb Improve performance of --nth option (#90) 2014-08-15 03:02:07 +09:00
Junegunn Choi
1e07b3b1c2 [vim] Apply FZF_DEFAULT_{OPTS,COMMAND} when using tmux splits (#87)
Fixed escaping bug of the previous commit
2014-08-08 03:23:24 +09:00
Junegunn Choi
4313c1c25c Revert "[vim] Apply FZF_DEFAULT_{OPTS,COMMAND} when using tmux splits (#87)"
This reverts commit cc9938d4c9eed9a5e28846b3fb1fcde56d074125.
2014-08-08 03:13:40 +09:00
Junegunn Choi
cc9938d4c9 [vim] Apply FZF_DEFAULT_{OPTS,COMMAND} when using tmux splits (#87) 2014-08-08 02:45:11 +09:00
Junegunn Choi
a54784cd53 Display 'gem install curses' when curses cannot be loaded 2014-07-27 01:08:30 +09:00
Junegunn Choi
22989b0488 Update version number 2014-07-18 13:21:15 +09:00
Junegunn Choi
892aa1e78b Merge pull request #80 from wilywampa/master
Add control + left/right key mappings
2014-07-18 13:20:42 +09:00
Jacob Niehus
b9ab7d2413 Add control + left/right key mappings 2014-07-17 21:09:21 -07:00
Junegunn Choi
69b2a0a733 Suppress error message from bash-completion 2014-07-18 00:25:12 +09:00
Junegunn Choi
13cd4ed546 Handle dynamically loaded completion functions (#79)
On Ubuntu/Debian, completion functions can be dynamically loaded via
_completion_loader. Since those functions are not visible when
fzf-completion.bash is loaded, we need this special hack to make it
possible to fail back to the original completion function when trigger
sequence is not found.
2014-07-18 00:22:49 +09:00
Sencer Selcuk
7261d3afcd allow installation with sudo privileges 2014-07-15 12:12:05 +09:00
Junegunn Choi
84fc73ad9c [bash-completion] unset / unalias / export 2014-07-14 12:48:31 +09:00
Junegunn Choi
4103f5c3cc [bash-completion] Remove -E option from sed
Old versions of sed does not have -E option
2014-07-11 01:09:06 +09:00
Junegunn Choi
5390616694 [bash-completion] Export _fzf_orig_completion_xxx 2014-07-07 00:02:09 +09:00
Junegunn Choi
daf08f801f [fish] Fix fish key binding issues (#60)
Although a major overhaul is ongoing (#67), it is not yet finished and
cannot be considered stable enough for the next release. This commit
fixes a few apparent issues with small change to the current
implementation.

- Fixed error when $TMPDIR is not defined
- Better escaping of file/directory names
- Splitted functions to workaround fish bug
2014-07-06 20:51:51 +09:00
Junegunn Choi
4e2a1fe5c8 Merge pull request #75 from junegunn/issue-72
[bash-completion] Fail back to original completion
2014-07-05 03:06:10 +09:00
Junegunn Choi
03f155484c [bash-completion] Merge eval statements into one 2014-07-04 21:05:46 +09:00
Junegunn Choi
89298a8d23 [vim] Do not print error message on exit status 1 2014-07-04 18:35:04 +09:00
Junegunn Choi
3b14c5230c [bash-completion] Fail back to original completion (#72) 2014-07-04 18:30:54 +09:00
Junegunn Choi
91401514ab Merge pull request #71 from junegunn/issue-70
Add options: --prompt and --print-query
2014-07-01 01:23:07 +09:00
Junegunn Choi
91d986b6c0 Update README (--print-query) 2014-06-30 12:24:40 +09:00
Junegunn Choi
4d72bd098a Add --print-query option (#70) 2014-06-30 12:23:37 +09:00
Junegunn Choi
502973ff75 Add --prompt option (#70) 2014-06-30 12:00:59 +09:00
Junegunn Choi
3e91c189ae [vim] Defer type fzf to reduce startup time 2014-06-27 21:03:25 +09:00
Junegunn Choi
b0f80b686c chmod +x fzf 2014-06-27 20:51:54 +09:00
Junegunn Choi
b824928b0b Merge pull request #69 from junegunn/scrollable
Make the list scrollable
2014-06-27 13:32:04 +09:00
Junegunn Choi
ccca34f9f7 Minor refactoring 2014-06-27 12:35:30 +09:00
Junegunn Choi
b5350b24ff Avoid unnecessary redraw 2014-06-27 08:28:32 +09:00
Junegunn Choi
56ace10a37 Fix mouse-click on --reverse mode 2014-06-27 00:05:01 +09:00
Junegunn Choi
72ec0a3408 Add test cases for result scroll 2014-06-26 19:40:29 +09:00
Junegunn Choi
05118cc440 Minor corrections
- Suppress warning message on Ruby 1.8.5
- Remove unnecessary code
2014-06-26 15:35:04 +09:00
Junegunn Choi
e392da20e8 Make scrollable (#68) 2014-06-26 12:51:40 +09:00
Junegunn Choi
6e69339f6b Merge pull request #66 from patspam/master
Add vi-command keymap mappings
2014-06-24 00:18:09 +09:00
Patrick Donelan
30cdc06bcd Add vi-command keymap mappings
fzf does not currently define vi-command mode mappings. This is particularly annoying for <C-r>, which opens bash's old-fashioned recursive history search.

This patch adds vi-command mode mappings that simply drop back into vi-insert mode ("i") and then trigger the primary mapping.
2014-06-23 17:14:16 +02:00
Junegunn Choi
9ce43d46f6 Guide on running fzf with MacVim and iTerm2 (#65) 2014-06-23 23:30:00 +09:00
Junegunn Choi
de09656197 Merge pull request #57 from sencer/master
Use `command find` rather than plain `find`
2014-06-19 00:37:36 +09:00
Sencer Selcuk
3827a1b09e Use command find rather than plain find
Aliases are expanded in shell scripts, and one may have an alias
for the `find` command that conflicts with fzf. So make sure fzf
is using real find command rather than the alias.
2014-06-18 11:33:40 -04:00
Junegunn Choi
61ba8d5a11 Add a small delay when search is interrupted
Search is interrupted when the query string has changed. This frequently
happens when the user is actively typing in a query. This (rather
arbitrary) delay is introduced not to start the next search immediately,
which is likely to be interrupted as well. The result of it is that fzf
feels more responsive.
2014-06-15 18:35:47 +09:00
Junegunn Choi
4a3a5ee70d [vim] External terminal emulator for GVim 2014-06-15 12:15:39 +09:00
Junegunn Choi
f58a53a001 Fix mouse click on --reverse mode 2014-06-15 04:32:21 +09:00
Junegunn Choi
65c1b53275 [vim] Options to xterm command 2014-06-15 03:18:29 +09:00
Junegunn Choi
0b43f988c7 [vim] Enable fzf in GVim using xterm 2014-06-15 03:06:26 +09:00
Junegunn Choi
f8e357fa19 Extend --nth option to take ranges
As discussed in #55
2014-06-14 00:27:34 +09:00
Junegunn Choi
c3a4e4cd23 Implement CTRL-D 2014-06-12 23:43:09 +09:00
Junegunn Choi
9dac12cb32 Remove duplicate examples from README
As discussed in #54
2014-06-12 23:28:59 +09:00
Junegunn Choi
d76a3646b7 Update Vim example: Rename functions
See: ftp://ftp.vim.org/pub/vim/patches/7.4/7.4.260
2014-06-09 10:06:07 +09:00
Junegunn Choi
d7c734acd6 Ignore regex error inside trim call (#51) 2014-06-07 01:17:38 +09:00
Junegunn Choi
ed13fc8618 Fix fzf-history-widget (#48) 2014-05-29 11:12:48 +09:00
Junegunn Choi
edcd7c6aa6 Remove UTF-8 NFD conversion
We have iconv.
2014-05-29 01:08:44 +09:00
Junegunn Choi
b0fdd6db99 Merge pull request #47 from cskeeters/master
[zsh-keybinding] Remove tailing substitution
2014-05-29 00:20:08 +09:00
Chad Skeeters
edf27f47f2 removed tailing substitution causing all trailing space to be removed when extendedglob is set 2014-05-28 10:16:07 -05:00
Junegunn Choi
3b218b77eb Merge pull request #46 from takac/install-update
Fix fzf-history-widget to strip `*` from history lines when using tmux and fc
2014-05-26 16:59:56 +09:00
Tom Cammann
1e02471940 Update install
Update sed regex to strip "*" from history lines when using tmux and fc
e.g. "637* ls -a"
2014-05-26 08:56:47 +01:00
Junegunn Choi
1b9dadb3d3 Avoid unnecessary confirmation 2014-05-22 02:34:20 +09:00
Junegunn Choi
c3827dea10 Add linewise user confirmation 2014-05-22 02:26:59 +09:00
Junegunn Choi
6a1b916598 OK 2014-05-22 02:24:13 +09:00
Junegunn Choi
a2c7b001d5 Update version/date 2014-05-21 10:43:30 +09:00
Junegunn Choi
3c6e938bb1 Fix arrow keys on zsh widget
Fixes the problem reported by @elemakil. For some reason unknown,
sometimes the escape sequences of arrow keys are prefixed by 27-79
instead of the ordinary 27-91.
2014-05-21 10:22:17 +09:00
Junegunn Choi
5a0afc5fea Merge branch 'aboettger-master' 2014-05-21 01:16:49 +09:00
Junegunn Choi
f37be006c3 Update uninstall script 2014-05-21 01:16:42 +09:00
Andreas Böttger
459c332351 Some improvements 2014-05-20 17:05:02 +02:00
Andreas Böttger
153a87d84a uninstall script 2014-05-20 14:17:03 +02:00
Junegunn Choi
05da892cd2 On writing fzf-tmux combo 2014-05-18 11:01:30 +09:00
Junegunn Choi
f6b1a6278f Add --reverse option (top-to-bottom layout) 2014-05-17 22:07:18 +09:00
Junegunn Choi
db58182483 Customization of key bindings (#40) 2014-05-06 15:39:44 +09:00
Junegunn Choi
6e9f0882da Update README (missing link to fish key bindings file) 2014-05-04 12:56:43 +09:00
Junegunn Choi
7ed18579dc set -o vi is required for vi-mode bash key bindings (#39) 2014-05-04 12:52:33 +09:00
Junegunn Choi
f250fc8f86 Fix #41: [CTRL-T] long file paths causing wrapping artifacts 2014-05-04 11:28:34 +09:00
Junegunn Choi
6eea9603c2 Fix bug in install script 2014-05-04 00:49:29 +09:00
Junegunn Choi
20915529b7 Add link to examples wiki page 2014-05-02 23:38:36 +09:00
Junegunn Choi
b3efccca81 [fish] Remove temporary file after use 2014-05-02 16:35:36 +09:00
Junegunn Choi
809d465de5 Tip on using git ls-tree (#31) 2014-05-02 12:52:06 +09:00
Junegunn Choi
7d15071c63 Fish shell support - installer / key bindings (#33) 2014-05-02 11:27:32 +09:00
Junegunn Choi
89eb1575e7 Simpler check for curses 2014-05-02 04:51:35 +09:00
Junegunn Choi
5d6ed935a4 Update README: master.tar.gz 2014-04-25 19:45:07 +09:00
Junegunn Choi
0528435386 Update README 2014-04-25 19:16:33 +09:00
85 changed files with 17695 additions and 2801 deletions

30
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,30 @@
<!-- Check all that apply [x] -->
- Category
- [ ] fzf binary
- [ ] fzf-tmux script
- [ ] Key bindings
- [ ] Completion
- [ ] Vim
- [ ] Neovim
- [ ] Etc.
- OS
- [ ] Linux
- [ ] Mac OS X
- [ ] Windows
- [ ] Windows Subsystem for Linux
- [ ] Etc.
- Shell
- [ ] bash
- [ ] zsh
- [ ] fish
<!--
### Before submitting
- Make sure that you have the latest version of fzf
- If you use tmux, make sure $TERM is set to screen or screen-256color
- For more Vim stuff, check out https://github.com/junegunn/fzf.vim
Describe your problem or suggestion from here ...
-->

6
.gitignore vendored
View File

@ -1,3 +1,9 @@
bin/fzf
bin/fzf.exe
target
pkg
Gemfile.lock
.DS_Store
doc/tags
vendor
gopath

20
.travis.yml Normal file
View File

@ -0,0 +1,20 @@
language: ruby
dist: trusty
sudo: required
matrix:
include:
- env: TAGS=
rvm: 2.3.3
# - env: TAGS=tcell
# rvm: 2.3.3
install:
- sudo add-apt-repository -y ppa:pi-rho/dev
- sudo apt-add-repository -y ppa:fish-shell/release-2
- sudo apt-get update
- sudo apt-get install -y tmux zsh fish
script: |
make test install &&
./install --all &&
tmux new "ruby test/test_go.rb > out && touch ok" && cat out && [ -e ok ]

53
BUILD.md Normal file
View File

@ -0,0 +1,53 @@
Building fzf
============
Build instructions
------------------
### Prerequisites
- `go` executable in $PATH
### Using Makefile
Makefile will set up and use its own `$GOPATH` under the project root.
```sh
# Build fzf binary for your platform in target
make
# Build fzf binary and copy it to bin directory
make install
# Build 32-bit and 64-bit executables and tarballs in target
make release
# Make release archives for all supported platforms in target
make release-all
```
### Using `go get`
Alternatively, you can build fzf directly with `go get` command without
manually cloning the repository.
```sh
go get -u github.com/junegunn/fzf
```
Third-party libraries used
--------------------------
- [mattn/go-runewidth](https://github.com/mattn/go-runewidth)
- Licensed under [MIT](http://mattn.mit-license.org)
- [mattn/go-shellwords](https://github.com/mattn/go-shellwords)
- Licensed under [MIT](http://mattn.mit-license.org)
- [mattn/go-isatty](https://github.com/mattn/go-isatty)
- Licensed under [MIT](http://mattn.mit-license.org)
- [tcell](https://github.com/gdamore/tcell)
- Licensed under [Apache License 2.0](https://github.com/gdamore/tcell/blob/master/LICENSE)
License
-------
[MIT](LICENSE)

653
CHANGELOG.md Normal file
View File

@ -0,0 +1,653 @@
CHANGELOG
=========
0.17.1
------
- Fixed custom background color of preview window (#1046)
- Fixed background color issues of Windows binary
- Fixed Windows binary to execute command using cmd.exe with no parsing and
escaping (#1072)
- Added support for `window` layout on Vim 8 using Vim 8 terminal (#1055)
0.17.0-2
--------
A maintenance release for auxiliary scripts. fzf binaries are not updated.
- Experimental support for the builtin terminal of Vim 8
- fzf can now run inside GVim
- Updated Vim plugin to better handle `&shell` issue on fish
- Fixed a bug of fzf-tmux where invalid output is generated
- Fixed fzf-tmux to work even when `tput` does not work
0.17.0
------
- Performance optimization
- One can match literal spaces in extended-search mode with a space prepended
by a backslash.
- `--expect` is now additive and can be specified multiple times.
0.16.11
-------
- Performance optimization
- Fixed missing preview update
0.16.10
-------
- Fixed invalid handling of ANSI colors in preview window
- Further improved `--ansi` performance
0.16.9
------
- Memory and performance optimization
- Around 20% performance improvement for general use cases
- Up to 5x faster processing of `--ansi`
- Up to 50% reduction of memory usage
- Bug fixes and usability improvements
- Fixed handling of bracketed paste mode
- [ERROR] on info line when the default command failed
- More efficient rendering of preview window
- `--no-clear` updated for repetitive relaunching scenarios
0.16.8
------
- New `change` event and `top` action for `--bind`
- `fzf --bind change:top`
- Move cursor to the top result whenever the query string is changed
- `fzf --bind 'ctrl-w:unix-word-rubout+top,ctrl-u:unix-line-discard+top'`
- `top` combined with `unix-word-rubout` and `unix-line-discard`
- Fixed inconsistent tiebreak scores when `--nth` is used
- Proper display of tab characters in `--prompt`
- Fixed not to `--cycle` on page-up/page-down to prevent overshoot
- Git revision in `--version` output
- Basic support for Cygwin environment
- Many fixes in Vim plugin on Windows/Cygwin (thanks to @janlazo)
0.16.7
------
- Added support for `ctrl-alt-[a-z]` key chords
- CTRL-Z (SIGSTOP) now works with fzf
- fzf will export `$FZF_PREVIEW_WINDOW` so that the scripts can use it
- Bug fixes and improvements in Vim plugin and shell extensions
0.16.6
------
- Minor bug fixes and improvements
- Added `--no-clear` option for scripting purposes
0.16.5
------
- Minor bug fixes
- Added `toggle-preview-wrap` action
- Built with Go 1.8
0.16.4
------
- Added `--border` option to draw border above and below the finder
- Bug fixes and improvements
0.16.3
------
- Fixed a bug where fzf incorrectly display the lines when straddling tab
characters are trimmed
- Placeholder expression used in `--preview` and `execute` action can
optionally take `+` flag to be used with multiple selections
- e.g. `git log --oneline | fzf --multi --preview 'git show {+1}'`
- Added `execute-silent` action for executing a command silently without
switching to the alternate screen. This is useful when the process is
short-lived and you're not interested in its output.
- e.g. `fzf --bind 'ctrl-y:execute!(echo -n {} | pbcopy)'`
- `ctrl-space` is allowed in `--bind`
0.16.2
------
- Dropped ncurses dependency
- Binaries for freebsd, openbsd, arm5, arm6, arm7, and arm8
- Official 24-bit color support
- Added support for composite actions in `--bind`. Multiple actions can be
chained using `+` separator.
- e.g. `fzf --bind 'ctrl-y:execute(echo -n {} | pbcopy)+abort'`
- `--preview-window` with size 0 is allowed. This is used to make fzf execute
preview command in the background without displaying the result.
- Minor bug fixes and improvements
0.16.1
------
- Fixed `--height` option to properly fill the window with the background
color
- Added `half-page-up` and `half-page-down` actions
- Added `-L` flag to the default find command
0.16.0
------
- *Added `--height HEIGHT[%]` option*
- fzf can now display finder without occupying the full screen
- Preview window will truncate long lines by default. Line wrap can be enabled
by `:wrap` flag in `--preview-window`.
- Latin script letters will be normalized before matching so that it's easier
to match against accented letters. e.g. `sodanco` can match `Só Danço Samba`.
- Normalization can be disabled via `--literal`
- Added `--filepath-word` to make word-wise movements/actions (`alt-b`,
`alt-f`, `alt-bs`, `alt-d`) respect path separators
0.15.9
------
- Fixed rendering glitches introduced in 0.15.8
- The default escape delay is reduced to 50ms and is configurable via
`$ESCDELAY`
- Scroll indicator at the top-right corner of the preview window is always
displayed when there's overflow
- Can now be built with ncurses 6 or tcell to support extra features
- *ncurses 6*
- Supports more than 256 color pairs
- Supports italics
- *tcell*
- 24-bit color support
- See https://github.com/junegunn/fzf/blob/master/BUILD.md
0.15.8
------
- Updated ANSI processor to handle more VT-100 escape sequences
- Added `--no-bold` (and `--bold`) option
- Improved escape sequence processing for WSL
- Added support for `alt-[0-9]`, `f11`, and `f12` for `--bind` and `--expect`
0.15.7
------
- Fixed panic when color is disabled and header lines contain ANSI colors
0.15.6
------
- Windows binaries! (@kelleyma49)
- Fixed the bug where header lines are cleared when preview window is toggled
- Fixed not to display ^N and ^O on screen
- Fixed cursor keys (or any key sequence that starts with ESC) on WSL by
making fzf wait for additional keystrokes after ESC for up to 100ms
0.15.5
------
- Setting foreground color will no longer set background color to black
- e.g. `fzf --color fg:153`
- `--tiebreak=end` will consider relative position instead of absolute distance
- Updated `fzf#wrap` function to respect `g:fzf_colors`
0.15.4
------
- Added support for range expression in preview and execute action
- e.g. `ls -l | fzf --preview="echo user={3} when={-4..-2}; cat {-1}" --header-lines=1`
- `{q}` will be replaced to the single-quoted string of the current query
- Fixed to properly handle unicode whitespace characters
- Display scroll indicator in preview window
- Inverse search term will use exact matcher by default
- This is a breaking change, but I believe it makes much more sense. It is
almost impossible to predict which entries will be filtered out due to
a fuzzy inverse term. You can still perform inverse-fuzzy-match by
prepending `!'` to the term.
0.15.3
------
- Added support for more ANSI attributes: dim, underline, blink, and reverse
- Fixed race condition in `toggle-preview`
0.15.2
------
- Preview window is now scrollable
- With mouse scroll or with bindable actions
- `preview-up`
- `preview-down`
- `preview-page-up`
- `preview-page-down`
- Updated ANSI processor to support high intensity colors and ignore
some VT100-related escape sequences
0.15.1
------
- Fixed panic when the pattern occurs after 2^15-th column
- Fixed rendering delay when displaying extremely long lines
0.15.0
------
- Improved fuzzy search algorithm
- Added `--algo=[v1|v2]` option so one can still choose the old algorithm
which values the search performance over the quality of the result
- Advanced scoring criteria
- `--read0` to read input delimited by ASCII NUL character
- `--print0` to print output delimited by ASCII NUL character
0.13.5
------
- Memory and performance optimization
- Up to 2x performance with half the amount of memory
0.13.4
------
- Performance optimization
- Memory footprint for ascii string is reduced by 60%
- 15 to 20% improvement of query performance
- Up to 45% better performance of `--nth` with non-regex delimiters
- Fixed invalid handling of `hidden` property of `--preview-window`
0.13.3
------
- Fixed duplicate rendering of the last line in preview window
0.13.2
------
- Fixed race condition where preview window is not properly cleared
0.13.1
------
- Fixed UI issue with large `--preview` output with many ANSI codes
0.13.0
------
- Added preview feature
- `--preview CMD`
- `--preview-window POS[:SIZE][:hidden]`
- `{}` in execute action is now replaced to the single-quoted (instead of
double-quoted) string of the current line
- Fixed to ignore control characters for bracketed paste mode
0.12.2
------
- 256-color capability detection does not require `256` in `$TERM`
- Added `print-query` action
- More named keys for binding; <kbd>F1</kbd> ~ <kbd>F10</kbd>,
<kbd>ALT-/</kbd>, <kbd>ALT-space</kbd>, and <kbd>ALT-enter</kbd>
- Added `jump` and `jump-accept` actions that implement [EasyMotion][em]-like
movement
![][jump]
[em]: https://github.com/easymotion/vim-easymotion
[jump]: https://cloud.githubusercontent.com/assets/700826/15367574/b3999dc4-1d64-11e6-85da-28ceeb1a9bc2.png
0.12.1
------
- Ranking algorithm introduced in 0.12.0 is now universally applied
- Fixed invalid cache reference in exact mode
- Fixes and improvements in Vim plugin and shell extensions
0.12.0
------
- Enhanced ranking algorithm
- Minor bug fixes
0.11.4
------
- Added `--hscroll-off=COL` option (default: 10) (#513)
- Some fixes in Vim plugin and shell extensions
0.11.3
------
- Graceful exit on SIGTERM (#482)
- `$SHELL` instead of `sh` for `execute` action and `$FZF_DEFAULT_COMMAND` (#481)
- Changes in fuzzy completion API
- [`_fzf_compgen_{path,dir}`](https://github.com/junegunn/fzf/commit/9617647)
- [`_fzf_complete_COMMAND_post`](https://github.com/junegunn/fzf/commit/8206746)
for post-processing
0.11.2
------
- `--tiebreak` now accepts comma-separated list of sort criteria
- Each criterion should appear only once in the list
- `index` is only allowed at the end of the list
- `index` is implicitly appended to the list when not specified
- Default is `length` (or equivalently `length,index`)
- `begin` criterion will ignore leading whitespaces when calculating the index
- Added `toggle-in` and `toggle-out` actions
- Switch direction depending on `--reverse`-ness
- `export FZF_DEFAULT_OPTS="--bind tab:toggle-out,shift-tab:toggle-in"`
- Reduced the initial delay when `--tac` is not given
- fzf defers the initial rendering of the screen up to 100ms if the input
stream is ongoing to prevent unnecessary redraw during the initial
phase. However, 100ms delay is quite noticeable and might give the
impression that fzf is not snappy enough. This commit reduces the
maximum delay down to 20ms when `--tac` is not specified, in which case
the input list quickly fills the entire screen.
0.11.1
------
- Added `--tabstop=SPACES` option
0.11.0
------
- Added OR operator for extended-search mode
- Added `--execute-multi` action
- Fixed incorrect cursor position when unicode wide characters are used in
`--prompt`
- Fixes and improvements in shell extensions
0.10.9
------
- Extended-search mode is now enabled by default
- `--extended-exact` is deprecated and instead we have `--exact` for
orthogonally controlling "exactness" of search
- Fixed not to display non-printable characters
- Added `double-click` for `--bind` option
- More robust handling of SIGWINCH
0.10.8
------
- Fixed panic when trying to set colors after colors are disabled (#370)
0.10.7
------
- Fixed unserialized interrupt handling during execute action which often
caused invalid memory access and crash
- Changed `--tiebreak=length` (default) to use trimmed length when `--nth` is
used
0.10.6
------
- Replaced `--header-file` with `--header` option
- `--header` and `--header-lines` can be used together
- Changed exit status
- 0: Okay
- 1: No match
- 2: Error
- 130: Interrupted
- 64-bit linux binary is statically-linked with ncurses to avoid
compatibility issues.
0.10.5
------
- `'`-prefix to unquote the term in `--extended-exact` mode
- Backward scan when `--tiebreak=end` is set
0.10.4
------
- Fixed to remove ANSI code from output when `--with-nth` is set
0.10.3
------
- Fixed slow performance of `--with-nth` when used with `--delimiter`
- Regular expression engine of Golang as of now is very slow, so the fixed
version will treat the given delimiter pattern as a plain string instead
of a regular expression unless it contains special characters and is
a valid regular expression.
- Simpler regular expression for delimiter for better performance
0.10.2
------
### Fixes and improvements
- Improvement in perceived response time of queries
- Eager, efficient rune array conversion
- Graceful exit when failed to initialize ncurses (invalid $TERM)
- Improved ranking algorithm when `--nth` option is set
- Changed the default command not to fail when there are files whose names
start with dash
0.10.1
------
### New features
- Added `--margin` option
- Added options for sticky header
- `--header-file`
- `--header-lines`
- Added `cancel` action which clears the input or closes the finder when the
input is already empty
- e.g. `export FZF_DEFAULT_OPTS="--bind esc:cancel"`
- Added `delete-char/eof` action to differentiate `CTRL-D` and `DEL`
### Minor improvements/fixes
- Fixed to allow binding colon and comma keys
- Fixed ANSI processor to handle color regions spanning multiple lines
0.10.0
------
### New features
- More actions for `--bind`
- `select-all`
- `deselect-all`
- `toggle-all`
- `ignore`
- `execute(...)` action for running arbitrary command without leaving fzf
- `fzf --bind "ctrl-m:execute(less {})"`
- `fzf --bind "ctrl-t:execute(tmux new-window -d 'vim {}')"`
- If the command contains parentheses, use any of the follows alternative
notations to avoid parse errors
- `execute[...]`
- `execute~...~`
- `execute!...!`
- `execute@...@`
- `execute#...#`
- `execute$...$`
- `execute%...%`
- `execute^...^`
- `execute&...&`
- `execute*...*`
- `execute;...;`
- `execute/.../`
- `execute|...|`
- `execute:...`
- This is the special form that frees you from parse errors as it
does not expect the closing character
- The catch is that it should be the last one in the
comma-separated list
- Added support for optional search history
- `--history HISTORY_FILE`
- When used, `CTRL-N` and `CTRL-P` are automatically remapped to
`next-history` and `previous-history`
- `--history-size MAX_ENTRIES` (default: 1000)
- Cyclic scrolling can be enabled with `--cycle`
- Fixed the bug where the spinner was not spinning on idle input stream
- e.g. `sleep 100 | fzf`
### Minor improvements/fixes
- Added synonyms for key names that can be specified for `--bind`,
`--toggle-sort`, and `--expect`
- Fixed the color of multi-select marker on the current line
- Fixed to allow `^pattern$` in extended-search mode
0.9.13
------
### New features
- Color customization with the extended `--color` option
### Bug fixes
- Fixed premature termination of Reader in the presence of a long line which
is longer than 64KB
0.9.12
------
### New features
- Added `--bind` option for custom key bindings
### Bug fixes
- Fixed to update "inline-info" immediately after terminal resize
- Fixed ANSI code offset calculation
0.9.11
------
### New features
- Added `--inline-info` option for saving screen estate (#202)
- Useful inside Neovim
- e.g. `let $FZF_DEFAULT_OPTS = $FZF_DEFAULT_OPTS.' --inline-info'`
### Bug fixes
- Invalid mutation of input on case conversion (#209)
- Smart-case for each term in extended-search mode (#208)
- Fixed double-click result when scroll offset is positive
0.9.10
------
### Improvements
- Performance optimization
- Less aggressive memoization to limit memory usage
### New features
- Added color scheme for light background: `--color=light`
0.9.9
-----
### New features
- Added `--tiebreak` option (#191)
- Added `--no-hscroll` option (#193)
- Visual indication of `--toggle-sort` (#194)
0.9.8
-----
### Bug fixes
- Fixed Unicode case handling (#186)
- Fixed to terminate on RuneError (#185)
0.9.7
-----
### New features
- Added `--toggle-sort` option (#173)
- `--toggle-sort=ctrl-r` is applied to `CTRL-R` shell extension
### Bug fixes
- Fixed to print empty line if `--expect` is set and fzf is completed by
`--select-1` or `--exit-0` (#172)
- Fixed to allow comma character as an argument to `--expect` option
0.9.6
-----
### New features
#### Added `--expect` option (#163)
If you provide a comma-separated list of keys with `--expect` option, fzf will
allow you to select the match and complete the finder when any of the keys is
pressed. Additionally, fzf will print the name of the key pressed as the first
line of the output so that your script can decide what to do next based on the
information.
```sh
fzf --expect=ctrl-v,ctrl-t,alt-s,f1,f2,~,@
```
The updated vim plugin uses this option to implement
[ctrlp](https://github.com/kien/ctrlp.vim)-compatible key bindings.
### Bug fixes
- Fixed to ignore ANSI escape code `\e[K` (#162)
0.9.5
-----
### New features
#### Added `--ansi` option (#150)
If you give `--ansi` option to fzf, fzf will interpret ANSI color codes from
the input, display the item with the ANSI colors (true colors are not
supported), and strips the codes from the output. This option is off by
default as it entails some overhead.
### Improvements
#### Reduced initial memory footprint (#151)
By removing unnecessary copy of pointers, fzf will use significantly smaller
amount of memory when it's started. The difference is hugely noticeable when
the input is extremely large. (e.g. `locate / | fzf`)
### Bug fixes
- Fixed panic on `--no-sort --filter ''` (#149)
0.9.4
-----
### New features
#### Added `--tac` option to reverse the order of the input.
One might argue that this option is unnecessary since we can already put `tac`
or `tail -r` in the command pipeline to achieve the same result. However, the
advantage of `--tac` is that it does not block until the input is complete.
### *Backward incompatible changes*
#### Changed behavior on `--no-sort`
`--no-sort` option will no longer reverse the display order within finder. You
may want to use the new `--tac` option with `--no-sort`.
```
history | fzf +s --tac
```
### Improvements
#### `--filter` will not block when sort is disabled
When fzf works in filtering mode (`--filter`) and sort is disabled
(`--no-sort`), there's no need to block until input is complete. The new
version of fzf will print the matches on-the-fly when the following condition
is met:
--filter TERM --no-sort [--no-tac --no-sync]
or simply:
-f TERM +s
This change removes unnecessary delay in the use cases like the following:
fzf -f xxx +s | head -5
However, in this case, fzf processes the lines sequentially, so it cannot
utilize multiple cores, and fzf will run slightly slower than the previous
mode of execution where filtering is done in parallel after the entire input
is loaded. If the user is concerned about this performance problem, one can
add `--sync` option to re-enable buffering.
0.9.3
-----
### New features
- Added `--sync` option for multi-staged filtering
### Improvements
- `--select-1` and `--exit-0` will start finder immediately when the condition
cannot be met

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

138
Makefile Normal file
View File

@ -0,0 +1,138 @@
ifndef GOOS
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
GOOS := darwin
else ifeq ($(UNAME_S),Linux)
GOOS := linux
else
$(error "$$GOOS is not defined.")
endif
endif
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
ROOT_DIR := $(shell dirname $(MAKEFILE))
GOPATH := $(ROOT_DIR)/gopath
SRC_LINK := $(GOPATH)/src/github.com/junegunn/fzf/src
VENDOR_LINK := $(GOPATH)/src/github.com/junegunn/fzf/vendor
export GOPATH
GLIDE_YAML := glide.yaml
GLIDE_LOCK := glide.lock
SOURCES := $(wildcard *.go src/*.go src/*/*.go) $(SRC_LINK) $(VENDOR_LINK) $(GLIDE_LOCK) $(MAKEFILE)
REVISION := $(shell git log -n 1 --pretty=format:%h -- $(SOURCES))
BUILD_FLAGS := -a -ldflags "-X main.revision=$(REVISION) -w -extldflags=$(LDFLAGS)" -tags "$(TAGS)"
BINARY32 := fzf-$(GOOS)_386
BINARY64 := fzf-$(GOOS)_amd64
BINARYARM5 := fzf-$(GOOS)_arm5
BINARYARM6 := fzf-$(GOOS)_arm6
BINARYARM7 := fzf-$(GOOS)_arm7
BINARYARM8 := fzf-$(GOOS)_arm8
VERSION := $(shell awk -F= '/version =/ {print $$2}' src/constants.go | tr -d "\" ")
RELEASE32 := fzf-$(VERSION)-$(GOOS)_386
RELEASE64 := fzf-$(VERSION)-$(GOOS)_amd64
RELEASEARM5 := fzf-$(VERSION)-$(GOOS)_arm5
RELEASEARM6 := fzf-$(VERSION)-$(GOOS)_arm6
RELEASEARM7 := fzf-$(VERSION)-$(GOOS)_arm7
RELEASEARM8 := fzf-$(VERSION)-$(GOOS)_arm8
# https://en.wikipedia.org/wiki/Uname
UNAME_M := $(shell uname -m)
ifeq ($(UNAME_M),x86_64)
BINARY := $(BINARY64)
else ifeq ($(UNAME_M),amd64)
BINARY := $(BINARY64)
else ifeq ($(UNAME_M),i686)
BINARY := $(BINARY32)
else ifeq ($(UNAME_M),i386)
BINARY := $(BINARY32)
else ifeq ($(UNAME_M),armv5l)
BINARY := $(BINARYARM5)
else ifeq ($(UNAME_M),armv6l)
BINARY := $(BINARYARM6)
else ifeq ($(UNAME_M),armv7l)
BINARY := $(BINARYARM7)
else
$(error "Build on $(UNAME_M) is not supported, yet.")
endif
all: target/$(BINARY)
target:
mkdir -p $@
ifeq ($(GOOS),windows)
release: target/$(BINARY32) target/$(BINARY64)
cd target && cp -f $(BINARY32) fzf.exe && zip $(RELEASE32).zip fzf.exe
cd target && cp -f $(BINARY64) fzf.exe && zip $(RELEASE64).zip fzf.exe
cd target && rm -f fzf.exe
else ifeq ($(GOOS),linux)
release: target/$(BINARY32) target/$(BINARY64) target/$(BINARYARM5) target/$(BINARYARM6) target/$(BINARYARM7) target/$(BINARYARM8)
cd target && cp -f $(BINARY32) fzf && tar -czf $(RELEASE32).tgz fzf
cd target && cp -f $(BINARY64) fzf && tar -czf $(RELEASE64).tgz fzf
cd target && cp -f $(BINARYARM5) fzf && tar -czf $(RELEASEARM5).tgz fzf
cd target && cp -f $(BINARYARM6) fzf && tar -czf $(RELEASEARM6).tgz fzf
cd target && cp -f $(BINARYARM7) fzf && tar -czf $(RELEASEARM7).tgz fzf
cd target && cp -f $(BINARYARM8) fzf && tar -czf $(RELEASEARM8).tgz fzf
cd target && rm -f fzf
else
release: target/$(BINARY32) target/$(BINARY64)
cd target && cp -f $(BINARY32) fzf && tar -czf $(RELEASE32).tgz fzf
cd target && cp -f $(BINARY64) fzf && tar -czf $(RELEASE64).tgz fzf
cd target && rm -f fzf
endif
release-all: clean test
GOOS=darwin make release
GOOS=linux make release
GOOS=freebsd make release
GOOS=openbsd make release
GOOS=windows make release
$(SRC_LINK):
mkdir -p $(shell dirname $(SRC_LINK))
ln -sf $(ROOT_DIR)/src $(SRC_LINK)
$(VENDOR_LINK):
mkdir -p $(shell dirname $(VENDOR_LINK))
ln -sf $(ROOT_DIR)/vendor $(VENDOR_LINK)
vendor: $(GLIDE_YAML)
go get -u github.com/Masterminds/glide && $(GOPATH)/bin/glide install && touch $@
test: $(SOURCES) vendor
SHELL=/bin/sh GOOS= go test -v -tags "$(TAGS)" \
github.com/junegunn/fzf/src \
github.com/junegunn/fzf/src/algo \
github.com/junegunn/fzf/src/tui \
github.com/junegunn/fzf/src/util
install: bin/fzf
clean:
rm -rf target
target/$(BINARY32): $(SOURCES) vendor
GOARCH=386 go build $(BUILD_FLAGS) -o $@
target/$(BINARY64): $(SOURCES) vendor
GOARCH=amd64 go build $(BUILD_FLAGS) -o $@
# https://github.com/golang/go/wiki/GoArm
target/$(BINARYARM5): $(SOURCES) vendor
GOARCH=arm GOARM=5 go build $(BUILD_FLAGS) -o $@
target/$(BINARYARM6): $(SOURCES) vendor
GOARCH=arm GOARM=6 go build $(BUILD_FLAGS) -o $@
target/$(BINARYARM7): $(SOURCES) vendor
GOARCH=arm GOARM=7 go build $(BUILD_FLAGS) -o $@
target/$(BINARYARM8): $(SOURCES) vendor
GOARCH=arm64 go build $(BUILD_FLAGS) -o $@
bin/fzf: target/$(BINARY) | bin
cp -f target/$(BINARY) bin/fzf
.PHONY: all release release-all test install clean

196
README-VIM.md Normal file
View File

@ -0,0 +1,196 @@
FZF Vim integration
===================
This repository only enables basic integration with Vim. If you're looking for
more, check out [fzf.vim](https://github.com/junegunn/fzf.vim) project.
(Note: To use fzf in GVim, an external terminal emulator is required.)
`:FZF[!]`
---------
If you have set up fzf for Vim, `:FZF` command will be added.
```vim
" Look for files under current directory
:FZF
" Look for files under your home directory
:FZF ~
" With options
:FZF --no-sort --reverse --inline-info /tmp
" Bang version starts fzf in fullscreen mode
:FZF!
```
Similarly to [ctrlp.vim](https://github.com/kien/ctrlp.vim), use enter key,
`CTRL-T`, `CTRL-X` or `CTRL-V` to open selected files in the current window,
in new tabs, in horizontal splits, or in vertical splits respectively.
Note that the environment variables `FZF_DEFAULT_COMMAND` and
`FZF_DEFAULT_OPTS` also apply here.
### Configuration
- `g:fzf_action`
- Customizable extra key bindings for opening selected files in different ways
- `g:fzf_layout`
- Determines the size and position of fzf window
- `g:fzf_colors`
- Customizes fzf colors to match the current color scheme
- `g:fzf_history_dir`
- Enables history feature
- `g:fzf_launcher`
- (Only in GVim) Terminal emulator to open fzf with
- `g:Fzf_launcher` for function reference
#### Examples
```vim
" This is the default extra key bindings
let g:fzf_action = {
\ 'ctrl-t': 'tab split',
\ 'ctrl-x': 'split',
\ 'ctrl-v': 'vsplit' }
" An action can be a reference to a function that processes selected lines
function! s:build_quickfix_list(lines)
call setqflist(map(copy(a:lines), '{ "filename": v:val }'))
copen
cc
endfunction
let g:fzf_action = {
\ 'ctrl-q': function('s:build_quickfix_list'),
\ 'ctrl-t': 'tab split',
\ 'ctrl-x': 'split',
\ 'ctrl-v': 'vsplit' }
" Default fzf layout
" - down / up / left / right
let g:fzf_layout = { 'down': '~40%' }
" You can set up fzf window using a Vim command (Neovim or latest Vim 8 required)
let g:fzf_layout = { 'window': 'enew' }
let g:fzf_layout = { 'window': '-tabnew' }
let g:fzf_layout = { 'window': '10split enew' }
" Customize fzf colors to match your color scheme
let g:fzf_colors =
\ { 'fg': ['fg', 'Normal'],
\ 'bg': ['bg', 'Normal'],
\ 'hl': ['fg', 'Comment'],
\ 'fg+': ['fg', 'CursorLine', 'CursorColumn', 'Normal'],
\ 'bg+': ['bg', 'CursorLine', 'CursorColumn'],
\ 'hl+': ['fg', 'Statement'],
\ 'info': ['fg', 'PreProc'],
\ 'border': ['fg', 'Ignore'],
\ 'prompt': ['fg', 'Conditional'],
\ 'pointer': ['fg', 'Exception'],
\ 'marker': ['fg', 'Keyword'],
\ 'spinner': ['fg', 'Label'],
\ 'header': ['fg', 'Comment'] }
" Enable per-command history.
" CTRL-N and CTRL-P will be automatically bound to next-history and
" previous-history instead of down and up. If you don't like the change,
" explicitly bind the keys to down and up in your $FZF_DEFAULT_OPTS.
let g:fzf_history_dir = '~/.local/share/fzf-history'
```
`fzf#run`
---------
For more advanced uses, you can use `fzf#run([options])` function with the
following options.
| Option name | Type | Description |
| -------------------------- | ------------- | ---------------------------------------------------------------- |
| `source` | string | External command to generate input to fzf (e.g. `find .`) |
| `source` | list | Vim list as input to fzf |
| `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 | Similar to `sink`, but takes the list of output lines at once |
| `options` | string/list | Options to fzf |
| `dir` | string | Working directory |
| `up`/`down`/`left`/`right` | number/string | Use tmux pane with the given size (e.g. `20`, `50%`) |
| `window` (Vim 8 / Neovim) | string | Command to open fzf window (e.g. `vertical aboveleft 30new`) |
| `launcher` | string | External terminal emulator to start fzf with (GVim only) |
| `launcher` | funcref | Function for generating `launcher` string (GVim only) |
`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
issues on different platforms.
```vim
call fzf#run({'options': '--reverse --prompt "C:\\Program Files\\"'})
call fzf#run({'options': ['--reverse', '--prompt', 'C:\Program Files\']})
```
`fzf#wrap`
----------
`fzf#wrap([name string,] [opts dict,] [fullscreen boolean])` is a helper
function that decorates the options dictionary so that it understands
`g:fzf_layout`, `g:fzf_action`, `g:fzf_colors`, and `g:fzf_history_dir` like
`:FZF`.
```vim
command! -bang MyStuff
\ call fzf#run(fzf#wrap('my-stuff', {'dir': '~/my-stuff'}, <bang>0))
```
fzf inside terminal buffer
--------------------------
The latest versions of Vim and Neovim include builtin terminal emulator
(`:terminal`) and fzf will start in a terminal buffer in the following cases:
- On Neovim
- On GVim
- On Terminal Vim with the non-default layout
- `call fzf#run({'left': '30%'})` or `let g:fzf_layout = {'left': '30%'}`
### Hide statusline
When fzf starts in a terminal buffer, you may want to hide the statusline of
the containing buffer.
```vim
autocmd! FileType fzf
autocmd FileType fzf set laststatus=0 noshowmode noruler
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
```
GVim
----
With the latest version of GVim, fzf will start inside the builtin terminal
emulator of Vim. Please note that this terminal feature of Vim is still young
and unstable and you may run into some issues.
If you have an older version of GVim, you need an external terminal emulator
to start fzf with. `xterm` command is used by default, but you can customize
it with `g:fzf_launcher`.
```vim
" This is the default. %s is replaced with fzf command
let g:fzf_launcher = 'xterm -e bash -ic %s'
" Use urxvt instead
let g:fzf_launcher = 'urxvt -geometry 120x30 -e sh -c %s'
```
If you're running MacVim on OSX, I recommend you to use iTerm2 as the
launcher. Refer to the [this wiki page][macvim-iterm2] to see how to set up.
[macvim-iterm2]: https://github.com/junegunn/fzf/wiki/On-MacVim-with-iTerm2
[License](LICENSE)
------------------
The MIT License (MIT)
Copyright (c) 2017 Junegunn Choi

732
README.md
View File

@ -1,87 +1,190 @@
fzf - Fuzzy finder for your shell
=================================
<img src="https://raw.githubusercontent.com/junegunn/i/master/fzf.png" height="170" alt="fzf - a command-line fuzzy finder"> [![travis-ci](https://travis-ci.org/junegunn/fzf.svg?branch=master)](https://travis-ci.org/junegunn/fzf) [![Donate via PayPal](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=EKYAW9PGKPD2N)
===
fzf is a general-purpose fuzzy finder for your shell.
fzf is a general-purpose command-line fuzzy finder.
![](https://raw.github.com/junegunn/i/master/fzf.gif)
<img src="https://raw.githubusercontent.com/junegunn/i/master/fzf-preview.png" width=640>
It was heavily inspired by [ctrlp.vim](https://github.com/kien/ctrlp.vim) and
the likes.
It's an interactive Unix filter for command-line that can be used with any
list; files, command history, processes, hostnames, bookmarks, git commits,
etc.
Requirements
------------
Pros
----
fzf requires Ruby (>= 1.8.5).
- Portable, no dependencies
- Blazingly fast
- The most comprehensive feature set
- Flexible layout
- Batteries included
- Vim/Neovim plugin, key bindings and fuzzy auto-completion
Table of Contents
-----------------
* [Installation](#installation)
* [Using Homebrew or Linuxbrew](#using-homebrew-or-linuxbrew)
* [Using git](#using-git)
* [As Vim plugin](#as-vim-plugin)
* [Fedora](#fedora)
* [Windows](#windows)
* [Upgrading fzf](#upgrading-fzf)
* [Building fzf](#building-fzf)
* [Usage](#usage)
* [Using the finder](#using-the-finder)
* [Layout](#layout)
* [Search syntax](#search-syntax)
* [Environment variables](#environment-variables)
* [Options](#options)
* [Examples](#examples)
* [fzf-tmux script](#fzf-tmux-script)
* [Key bindings for command line](#key-bindings-for-command-line)
* [Fuzzy completion for bash and zsh](#fuzzy-completion-for-bash-and-zsh)
* [Files and directories](#files-and-directories)
* [Process IDs](#process-ids)
* [Host names](#host-names)
* [Environment variables / Aliases](#environment-variables--aliases)
* [Settings](#settings)
* [Supported commands](#supported-commands)
* [Vim plugin](#vim-plugin)
* [Advanced topics](#advanced-topics)
* [Performance](#performance)
* [Executing external programs](#executing-external-programs)
* [Preview window](#preview-window)
* [Tips](#tips)
* [Respecting .gitignore](#respecting-gitignore)
* [git ls-tree for fast traversal](#git-ls-tree-for-fast-traversal)
* [Fish shell](#fish-shell)
* [<a href="LICENSE">License</a>](#license)
Installation
------------
Clone this repository and run
fzf project consists of the following components:
- `fzf` executable
- `fzf-tmux` script for launching fzf in a tmux pane
- Shell extensions
- Key bindings (`CTRL-T`, `CTRL-R`, and `ALT-C`) (bash, zsh, fish)
- Fuzzy auto-completion (bash, zsh)
- Vim/Neovim plugin
You can [download fzf executable][bin] alone if you don't need the extra
stuff.
[bin]: https://github.com/junegunn/fzf-bin/releases
### Using Homebrew or Linuxbrew
You can use [Homebrew](http://brew.sh/) or [Linuxbrew](http://linuxbrew.sh/)
to install fzf.
```sh
brew install fzf
# To install useful key bindings and fuzzy completion:
$(brew --prefix)/opt/fzf/install
```
### Using git
Alternatively, you can "git clone" this repository to any directory and run
[install](https://github.com/junegunn/fzf/blob/master/install) script.
```sh
git clone https://github.com/junegunn/fzf.git ~/.fzf
git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
~/.fzf/install
```
The script will setup:
### As Vim plugin
- `fzf` executable
- Key bindings (`CTRL-T`, `CTRL-R`, and `ALT-C`) for bash and zsh
- Fuzzy auto-completion for bash
If you don't use bash or zsh, you have to manually place fzf executable in a
directory included in `$PATH`. Key bindings are not yet supported.
### Install as Vim plugin
Once you have cloned the repository, add the following line to your .vimrc.
Once you have fzf installed, you can enable it inside Vim simply by adding the
directory to `&runtimepath` as follows:
```vim
" If installed using Homebrew
set rtp+=/usr/local/opt/fzf
" If installed using git
set rtp+=~/.fzf
```
Or you may use any Vim plugin manager, such as
[vim-plug](https://github.com/junegunn/vim-plug).
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 install script
Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
" Both options are optional. You don't have to install fzf in ~/.fzf
" and you don't have to run install script if you use fzf only in Vim.
```
### 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 for more information.
### Windows
Pre-built binaries for Windows can be downloaded [here][bin]. fzf is also
available as a [Chocolatey package][choco].
[choco]: https://chocolatey.org/packages/fzf
```sh
choco install fzf
```
However, other components of the project may not work on Windows. Known issues
and limitations can be found on [the wiki page][windows-wiki]. You might want
to consider installing fzf on [Windows Subsystem for Linux][wsl] where
everything runs flawlessly.
[windows-wiki]: https://github.com/junegunn/fzf/wiki/Windows
[wsl]: https://blogs.msdn.microsoft.com/wsl/
Upgrading fzf
-------------
fzf is being actively developed and you might want to upgrade it once in a
while. Please follow the instruction below depending on the installation
method used.
- git: `cd ~/.fzf && git pull && ./install`
- brew: `brew update; brew reinstall fzf`
- chocolatey: `choco upgrade fzf`
- vim-plug: `:PlugUpdate fzf`
Building fzf
------------
See [BUILD.md](BUILD.md).
Usage
-----
```
usage: fzf [options]
Search
-x, --extended Extended-search mode
-e, --extended-exact Extended-search mode (exact match)
-i Case-insensitive match (default: smart-case match)
+i Case-sensitive match
-n, --nth=[-]N[,..] Comma-separated list of field indexes for limiting
search scope (positive or negative integers)
-d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style)
Search result
-s, --sort=MAX Maximum number of matched items to sort (default: 1000)
+s, --no-sort Do not sort the result. Keep the sequence unchanged.
Interface
-m, --multi Enable multi-select with tab/shift-tab
--no-mouse Disable mouse
+c, --no-color Disable colors
+2, --no-256 Disable 256-color
--black Use black background
Scripting
-q, --query=STR Start the finder with the given query
-1, --select-1 Automatically select the only match
-0, --exit-0 Exit immediately when there's no match
-f, --filter=STR Filter mode. Do not start interactive finder.
Environment variables
FZF_DEFAULT_COMMAND Default command to use when input is tty
FZF_DEFAULT_OPTS Defaults options. (e.g. "-x -m --sort 10000")
```
fzf will launch curses-based finder, read the list from STDIN, and write the
fzf will launch interactive finder, read the list from STDIN, and write the
selected item to STDOUT.
```sh
@ -96,140 +199,140 @@ files excluding hidden ones. (You can override the default command with
vim $(fzf)
```
If you want to preserve the exact sequence of the input, provide `--no-sort` (or
`+s`) option.
#### Using the finder
- `CTRL-J` / `CTRL-K` (or `CTRL-N` / `CTRL-P`) to move cursor up and down
- `Enter` key to select the item, `CTRL-C` / `CTRL-G` / `ESC` to exit
- On multi-select mode (`-m`), `TAB` and `Shift-TAB` to mark multiple items
- Emacs style key bindings
- Mouse: scroll, click, double-click; shift-click and shift-scroll on
multi-select mode
#### Layout
fzf by default starts in fullscreen mode, but you can make it start below the
cursor with `--height` option.
```sh
history | fzf +s
vim $(fzf --height 40%)
```
### Keys
Use CTRL-J and CTRL-K (or CTRL-N and CTRL-P) to change the selection, press
enter key to select the item. CTRL-C, CTRL-G, or ESC will terminate the finder.
The following readline key bindings should also work as expected.
- CTRL-A / CTRL-E
- CTRL-B / CTRL-F
- CTRL-W / CTRL-U / CTRL-Y
- ALT-B / ALT-F
If you enable multi-select mode with `-m` option, you can select multiple items
with TAB or Shift-TAB key.
You can also use mouse. Double-click on an item to select it or shift-click (or
ctrl-click) to select multiple items. Use mouse wheel to move the cursor up and
down.
### Extended-search mode
With `-x` or `--extended` option, fzf will start in "extended-search mode".
In this mode, you can specify multiple patterns delimited by spaces,
such as: `^music .mp3$ sbtrkt !rmx`
| Token | Description | Match type |
| -------- | -------------------------------- | -------------------- |
| `^music` | Items that start with `music` | prefix-exact-match |
| `.mp3$` | Items that end with `.mp3` | suffix-exact-match |
| `sbtrkt` | Items that match `sbtrkt` | fuzzy-match |
| `!rmx` | Items that do not match `rmx` | inverse-fuzzy-match |
| `'wild` | Items that include `wild` | exact-match (quoted) |
| `!'fire` | Items that do not include `fire` | inverse-exact-match |
If you don't need fuzzy matching and do not wish to "quote" every word, start
fzf with `-e` or `--extended-exact` option.
Useful examples
---------------
Also check out `--reverse` option if you prefer "top-down" layout instead of
the default "bottom-up" layout.
```sh
# fe [FUZZY PATTERN] - Open the selected file with the default editor
# - Bypass fuzzy finder if there's only one match (--select-1)
# - Exit if there's no match (--exit-0)
fe() {
local file
file=$(fzf --query="$1" --select-1 --exit-0)
[ -n "$file" ] && ${EDITOR:-vim} "$file"
}
vim $(fzf --height 40% --reverse)
```
# fd - cd to selected directory
fd() {
local dir
dir=$(find ${1:-*} -path '*/\.*' -prune \
-o -type d -print 2> /dev/null | fzf +m) &&
cd "$dir"
}
You can add these options to `$FZF_DEFAULT_OPTS` so that they're applied by
default. For example,
# fda - including hidden directories
fda() {
local dir
dir=$(find ${1:-.} -type d 2> /dev/null | fzf +m) && cd "$dir"
}
```sh
export FZF_DEFAULT_OPTS='--height 40% --reverse --border'
```
# fh - repeat history
fh() {
eval $(([ -n "$ZSH_NAME" ] && fc -l 1 || history) | fzf +s | sed 's/ *[0-9]* *//')
}
#### Search syntax
# fkill - kill process
fkill() {
ps -ef | sed 1d | fzf -m | awk '{print $2}' | xargs kill -${1:-9}
}
Unless otherwise specified, fzf starts in "extended-search mode" where you can
type in multiple search terms delimited by spaces. e.g. `^music .mp3$ sbtrkt
!fire`
# fbr - checkout git branch
fbr() {
local branches branch
branches=$(git branch) &&
branch=$(echo "$branches" | fzf +s +m) &&
git checkout $(echo "$branch" | sed "s/.* //")
}
| Token | Match type | Description |
| -------- | -------------------------- | --------------------------------- |
| `sbtrkt` | fuzzy-match | Items that match `sbtrkt` |
| `^music` | prefix-exact-match | Items that start with `music` |
| `.mp3$` | suffix-exact-match | Items that end with `.mp3` |
| `'wild` | exact-match (quoted) | Items that include `wild` |
| `!fire` | inverse-exact-match | Items that do not include `fire` |
| `!.mp3$` | inverse-suffix-exact-match | Items that do not end with `.mp3` |
# fco - checkout git commit
fco() {
local commits commit
commits=$(git log --pretty=oneline --abbrev-commit --reverse) &&
commit=$(echo "$commits" | fzf +s +m -e) &&
git checkout $(echo "$commit" | sed "s/ .*//")
}
If you don't prefer fuzzy matching and do not wish to "quote" every word,
start fzf with `-e` or `--exact` option. Note that when `--exact` is set,
`'`-prefix "unquotes" the term.
# ftags - search ctags
ftags() {
local line
[ -e tags ] &&
line=$(
awk 'BEGIN { FS="\t" } !/^!/ {print toupper($4)"\t"$1"\t"$2"\t"$3}' tags |
cut -c1-80 | fzf --nth=1,2
) && $EDITOR $(cut -f3 <<< "$line") -c "set nocst" \
-c "silent tag $(cut -f2 <<< "$line")"
}
A single bar character term acts as an OR operator. For example, the following
query matches entries that start with `core` and end with either `go`, `rb`,
or `py`.
```
^core go$ | rb$ | py$
```
#### Environment variables
- `FZF_DEFAULT_COMMAND`
- Default command to use when input is tty
- e.g. `export FZF_DEFAULT_COMMAND='fd --type f'`
- `FZF_DEFAULT_OPTS`
- Default options
- e.g. `export FZF_DEFAULT_OPTS="--reverse --inline-info"`
#### Options
See the man page (`man fzf`) for the full list of options.
Examples
--------
Many useful examples can be found on [the wiki
page](https://github.com/junegunn/fzf/wiki/examples). Feel free to add your
own as well.
`fzf-tmux` script
-----------------
[fzf-tmux](bin/fzf-tmux) is a bash script that opens fzf in a tmux pane.
```sh
# usage: fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS]
# (-[udlr]: up/down/left/right)
# select git branches in horizontal split below (15 lines)
git branch | fzf-tmux -d 15
# select multiple words in vertical split on the left (20% of screen width)
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]`
options, so you can invariably use `fzf-tmux` in your scripts.
Alternatively, you can use `--height HEIGHT[%]` option not to start fzf in
fullscreen mode.
```sh
fzf --height 40%
```
Key bindings for command line
-----------------------------
The install script will setup the following key bindings.
The install script will setup the following key bindings for bash, zsh, and
fish.
### bash/zsh
- `CTRL-T` - Paste the selected file path(s) into the command line
- `CTRL-R` - Paste the selected command from history into the command line
- `CTRL-T` - Paste the selected files and directories onto the command line
- Set `FZF_CTRL_T_COMMAND` to override the default command
- Set `FZF_CTRL_T_OPTS` to pass additional options
- `CTRL-R` - Paste the selected command from history onto the command line
- If you want to see the commands in chronological order, press `CTRL-R`
again which toggles sorting by relevance
- Set `FZF_CTRL_R_OPTS` to pass additional options
- `ALT-C` - cd into the selected directory
- Set `FZF_ALT_C_COMMAND` to override the default command
- Set `FZF_ALT_C_OPTS` to pass additional options
If you're on a tmux session, `CTRL-T` will launch fzf in a new split-window. You
may disable this tmux integration by setting `FZF_TMUX` to 0, or change the
height of the window with `FZF_TMUX_HEIGHT` (e.g. `20`, `50%`).
If you're on a tmux session, you can start fzf in a split pane by setting
`FZF_TMUX` to 1, and change the height of the pane with `FZF_TMUX_HEIGHT`
(e.g. `20`, `50%`).
The source code can be found in `~/.fzf.bash` and in `~/.fzf.zsh`.
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.
Auto-completion
---------------
More tips can be found on [the wiki page](https://github.com/junegunn/fzf/wiki/Configuring-shell-key-bindings).
Disclaimer: *Auto-completion feature is currently experimental, it can change
over time*
### bash
Fuzzy completion for bash and zsh
---------------------------------
#### Files and directories
@ -280,6 +383,14 @@ ssh **<TAB>
telnet **<TAB>
```
#### Environment variables / Aliases
```sh
unset **<TAB>
export **<TAB>
unalias **<TAB>
```
#### Settings
```sh
@ -288,176 +399,201 @@ export FZF_COMPLETION_TRIGGER='~~'
# Options to fzf command
export FZF_COMPLETION_OPTS='+c -x'
# Use fd (https://github.com/sharkdp/fd) instead of the default find
# command for listing path candidates.
# - The first argument to the function ($1) is the base path to start traversal
# - See the source code (completion.{bash,zsh}) for the details.
_fzf_compgen_path() {
fd --hidden --follow --exclude ".git" . "$1"
}
# Use fd to generate the list for directory completion
_fzf_compgen_dir() {
fd --type d --hidden --follow --exclude ".git" . "$1"
}
```
### zsh
#### Supported commands
TODO :smiley:
On bash, fuzzy completion is enabled only for a predefined set of commands
(`complete | grep _fzf` to see the list). But you can enable it for other
commands as well like follows.
(Pull requests are appreciated.)
Usage as Vim plugin
-------------------
(fzf is a command-line utility, naturally it is only accessible in terminal Vim)
### `:FZF[!]`
If you have set up fzf for Vim, `:FZF` command will be added.
```vim
" Look for files under current directory
:FZF
" Look for files under your home directory
:FZF ~
" With options
:FZF --no-sort -m /tmp
```sh
complete -F _fzf_path_completion -o default -o bashdefault ag
complete -F _fzf_dir_completion -o default -o bashdefault tree
```
Note that the environment variables `FZF_DEFAULT_COMMAND` and `FZF_DEFAULT_OPTS`
also apply here.
Vim plugin
----------
If you're on a tmux session, `:FZF` will launch fzf in a new split-window whose
height can be adjusted with `g:fzf_tmux_height` (default: '40%'). However, the
bang version (`:FZF!`) will always start in fullscreen.
See [README-VIM.md](README-VIM.md).
### `fzf#run([options])`
Advanced topics
---------------
For more advanced uses, you can call `fzf#run()` function which returns the list
of the selected items.
### Performance
`fzf#run()` may take an options-dictionary:
fzf is fast, and is [getting even faster][perf]. Performance should not be
a problem in most use cases. However, you might want to be aware of the
options that affect the performance.
| Option name | Type | Description |
| ------------- | ------------- | ------------------------------------------------------------------ |
| `source` | string | External command to generate input to fzf (e.g. `find .`) |
| `source` | list | Vim list as input to fzf |
| `sink` | string | Vim command to handle the selected item (e.g. `e`, `tabe`) |
| `sink` | funcref | Reference to function to process each selected item |
| `options` | string | Options to fzf |
| `dir` | string | Working directory |
| `tmux_width` | number/string | Use tmux vertical split with the given height (e.g. `20`, `50%`) |
| `tmux_height` | number/string | Use tmux horizontal split with the given height (e.g. `20`, `50%`) |
- `--ansi` tells fzf to extract and parse ANSI color codes in the input and it
makes the initial scanning slower. So it's not recommended that you add it
to your `$FZF_DEFAULT_OPTS`.
- `--nth` makes fzf slower as fzf has to tokenize each line.
- `--with-nth` makes fzf slower as fzf has to tokenize and reassemble each
line.
- If you absolutely need better performance, you can consider using
`--algo=v1` (the default being `v2`) to make fzf use faster greedy
algorithm. However, this algorithm is not guaranteed to find the optimal
ordering of the matches and is not recommended.
#### Examples
[perf]: https://junegunn.kr/images/fzf-0.17.0.png
If `sink` option is not given, `fzf#run` will simply return the list.
### Executing external programs
```vim
let items = fzf#run({ 'options': '-m +c', 'dir': '~', 'source': 'ls' })
You can set up key bindings for starting external processes without leaving
fzf (`execute`, `execute-silent`).
```bash
# Press F1 to open the file with less without leaving fzf
# Press CTRL-Y to copy the line to clipboard and aborts fzf (requires pbcopy)
fzf --bind 'f1:execute(less -f {}),ctrl-y:execute-silent(echo {} | pbcopy)+abort'
```
But if `sink` is given as a string, the command will be executed for each
selected item.
See *KEY BINDINGS* section of the man page for details.
```vim
" Each selected item will be opened in a new tab
let items = fzf#run({ 'sink': 'tabe', 'options': '-m +c', 'dir': '~', 'source': 'ls' })
### Preview window
When `--preview` option is set, fzf automatically starts external process with
the current line as the argument and shows the result in the split window.
```bash
# {} is replaced to the single-quoted string of the focused line
fzf --preview 'cat {}'
```
We can also use a Vim list as the source as follows:
Since preview window is updated only after the process is complete, it's
important that the command finishes quickly.
```vim
" Choose a color scheme with fzf
nnoremap <silent> <Leader>C :call fzf#run({
\ 'source':
\ map(split(globpath(&rtp, "colors/*.vim"), "\n"),
\ "substitute(fnamemodify(v:val, ':t'), '\\..\\{-}$', '', '')"),
\ 'sink': 'colo',
\ 'options': '+m',
\ 'tmux_width': 20
\ })<CR>
```bash
# Use head instead of cat so that the command doesn't take too long to finish
fzf --preview 'head -100 {}'
```
`sink` option can be a function reference. The following example creates a
handy mapping that selects an open buffer.
Preview window supports ANSI colors, so you can use programs that
syntax-highlights the content of a file.
```vim
" List of buffers
function! g:buflist()
redir => ls
silent ls
redir END
return split(ls, '\n')
endfunction
- Highlight: http://www.andre-simon.de/doku/highlight/en/highlight.php
- CodeRay: http://coderay.rubychan.de/
- Rouge: https://github.com/jneen/rouge
function! g:bufopen(e)
execute 'buffer '. matchstr(a:e, '^[ 0-9]*')
endfunction
nnoremap <silent> <Leader><Enter> :call fzf#run({
\ 'source': reverse(g:buflist()),
\ 'sink': function('g:bufopen'),
\ 'options': '+m',
\ 'tmux_height': '40%'
\ })<CR>
```bash
# Try highlight, coderay, rougify in turn, then fall back to cat
fzf --preview '[[ $(file --mime {}) =~ binary ]] &&
echo {} is a binary file ||
(highlight -O ansi -l {} ||
coderay {} ||
rougify {} ||
cat {}) 2> /dev/null | head -500'
```
You can customize the size and position of the preview window using
`--preview-window` option. For example,
```bash
fzf --height 40% --reverse --preview 'file {}' --preview-window down:1
```
For more advanced examples, see [Key bindings for git with fzf][fzf-git].
[fzf-git]: https://junegunn.kr/2016/07/fzf-git/
Tips
----
### Rendering issues
#### Respecting `.gitignore`
If you have any rendering issues, check the followings:
1. Make sure `$TERM` is correctly set. fzf will use 256-color only if it
contains `256` (e.g. `xterm-256color`)
2. If you're on screen or tmux, `$TERM` should be either `screen` or
`screen-256color`
3. Some terminal emulators (e.g. mintty) have problem displaying default
background color and make some text unable to read. In that case, try `--black`
option. And if it solves your problem, I recommend including it in
`FZF_DEFAULT_OPTS` for further convenience.
4. If you still have problem, try `--no-256` option or even `--no-color`.
5. Ruby 1.9 or above is required for correctly displaying unicode characters.
### Ranking algorithm
fzf sorts the result first by the length of the matched substring, then by the
length of the whole string. However it only does so when the number of matches
is less than the limit which is by default 1000, in order to avoid the cost of
sorting a large list and limit the response time of the query.
This limit can be adjusted with `-s` option, or with the environment variable
`FZF_DEFAULT_OPTS`.
You can use [fd](https://github.com/sharkdp/fd),
[ripgrep](https://github.com/BurntSushi/ripgrep), or [the silver
searcher](https://github.com/ggreer/the_silver_searcher) instead of the
default find command to traverse the file system while respecting
`.gitignore`.
```sh
export FZF_DEFAULT_OPTS="--sort 20000"
```
# Feed the output of fd into fzf
fd --type f | fzf
### Respecting `.gitignore`, `.hgignore`, and `svn:ignore`
# Setting fd as the default source for fzf
export FZF_DEFAULT_COMMAND='fd --type f'
[ag](https://github.com/ggreer/the_silver_searcher) or
[pt](https://github.com/monochromegane/the_platinum_searcher) will do the
filtering:
```sh
# Feed the output of ag into fzf
ag -l -g "" | fzf
# Setting ag as the default source for fzf
export FZF_DEFAULT_COMMAND='ag -l -g ""'
# Now fzf (w/o pipe) will use ag instead of find
# Now fzf (w/o pipe) will use fd instead of find
fzf
# To apply the command to CTRL-T as well
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
```
### Windows
If you want the command to follow symbolic links, and don't want it to exclude
hidden files, use the following command:
fzf works on [Cygwin](http://www.cygwin.com/) and
[MSYS2](http://sourceforge.net/projects/msys2/). You may need to use `--black`
option on MSYS2 to avoid rendering issues.
```sh
export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git'
```
#### `git ls-tree` for fast traversal
License
-------
If you're running fzf in a large git repository, `git ls-tree` can boost up the
speed of the traversal.
MIT
```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'
```
Author
------
#### Fish shell
Junegunn Choi
Fish shell before version 2.6.0 [doesn't allow](https://github.com/fish-shell/fish-shell/issues/1362)
reading from STDIN in command substitution, which means simple `vim (fzf)`
doesn't work as expected. The workaround for fish 2.5.0 and earlier is to use
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 commandline as the
root folder for the recursive search. For instance, hitting `CTRL-T` at the end
of the following commandline
```sh
ls /var/
```
will list all files and folders under `/var/`.
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
valid directory. Example:
```sh
set -g FZF_CTRL_T_COMMAND "command find -L \$dir -type f 2> /dev/null | sed '1d; s#^\./##'"
```
[License](LICENSE)
------------------
The MIT License (MIT)
Copyright (c) 2017 Junegunn Choi

View File

@ -1,8 +0,0 @@
require "bundler/gem_tasks"
require 'rake/testtask'
Rake::TestTask.new(:test) do |test|
test.pattern = 'test/**/test_*.rb'
test.verbose = true
end

194
bin/fzf-tmux Executable file
View File

@ -0,0 +1,194 @@
#!/usr/bin/env bash
# fzf-tmux: starts fzf in a tmux pane
# usage: fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS]
fail() {
>&2 echo "$1"
exit 2
}
fzf="$(command -v fzf 2> /dev/null)" || fzf="$(dirname "$0")/fzf"
[[ -x "$fzf" ]] || fail 'fzf executable not found'
args=()
opt=""
skip=""
swap=""
close=""
term=""
[[ -n "$LINES" ]] && lines=$LINES || lines=$(tput lines) || lines=$(tmux display-message -p "#{pane_height}")
[[ -n "$COLUMNS" ]] && columns=$COLUMNS || columns=$(tput cols) || columns=$(tmux display-message -p "#{pane_width}")
help() {
>&2 echo 'usage: fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS]
Layout
-u [HEIGHT[%]] Split above (up)
-d [HEIGHT[%]] Split below (down)
-l [WIDTH[%]] Split left
-r [WIDTH[%]] Split right
(default: -d 50%)
'
exit
}
while [[ $# -gt 0 ]]; do
arg="$1"
shift
[[ -z "$skip" ]] && case "$arg" in
-)
term=1
;;
--help)
help
;;
--version)
echo "fzf-tmux (with fzf $("$fzf" --version))"
exit
;;
-w*|-h*|-d*|-u*|-r*|-l*)
if [[ "$arg" =~ ^.[lrw] ]]; then
opt="-h"
if [[ "$arg" =~ ^.l ]]; then
opt="$opt -d"
swap="; swap-pane -D ; select-pane -L"
close="; tmux swap-pane -D"
fi
else
opt=""
if [[ "$arg" =~ ^.u ]]; then
opt="$opt -d"
swap="; swap-pane -D ; select-pane -U"
close="; tmux swap-pane -D"
fi
fi
if [[ ${#arg} -gt 2 ]]; then
size="${arg:2}"
else
if [[ "$1" =~ ^[0-9]+%?$ ]]; then
size="$1"
shift
else
continue
fi
fi
if [[ "$size" =~ %$ ]]; then
size=${size:0:((${#size}-1))}
if [[ -n "$swap" ]]; then
opt="$opt -p $(( 100 - size ))"
else
opt="$opt -p $size"
fi
else
if [[ -n "$swap" ]]; then
if [[ "$arg" =~ ^.l ]]; then
max=$columns
else
max=$lines
fi
size=$(( max - size ))
[[ $size -lt 0 ]] && size=0
opt="$opt -l $size"
else
opt="$opt -l $size"
fi
fi
;;
--)
# "--" can be used to separate fzf-tmux options from fzf options to
# avoid conflicts
skip=1
continue
;;
*)
args+=("$arg")
;;
esac
[[ -n "$skip" ]] && args+=("$arg")
done
if [[ -z "$TMUX" || "$opt" =~ ^-h && "$columns" -le 40 || ! "$opt" =~ ^-h && "$lines" -le 15 ]]; then
"$fzf" "${args[@]}"
exit $?
fi
# --height option is not allowed
args+=("--no-height")
# Handle zoomed tmux pane by moving it to a temp window
if tmux list-panes -F '#F' | grep -q Z; then
zoomed=1
original_window=$(tmux display-message -p "#{window_id}")
tmp_window=$(tmux new-window -d -P -F "#{window_id}" "bash -c 'while :; do for c in \\| / - '\\;' do sleep 0.2; printf \"\\r\$c fzf-tmux is running\\r\"; done; done'")
tmux swap-pane -t $tmp_window \; select-window -t $tmp_window
fi
set -e
# Clean up named pipes on exit
id=$RANDOM
argsf="${TMPDIR:-/tmp}/fzf-args-$id"
fifo1="${TMPDIR:-/tmp}/fzf-fifo1-$id"
fifo2="${TMPDIR:-/tmp}/fzf-fifo2-$id"
fifo3="${TMPDIR:-/tmp}/fzf-fifo3-$id"
cleanup() {
\rm -f $argsf $fifo1 $fifo2 $fifo3
# Remove temp window if we were zoomed
if [[ -n "$zoomed" ]]; then
tmux display-message -p "#{window_id}" > /dev/null
tmux swap-pane -t $original_window \; \
select-window -t $original_window \; \
kill-window -t $tmp_window \; \
resize-pane -Z
fi
if [ $# -gt 0 ]; then
trap - EXIT
exit 130
fi
}
trap 'cleanup 1' SIGUSR1
trap 'cleanup' EXIT
envs="env TERM=$TERM "
[[ -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")"
mkfifo -m o+w $fifo2
mkfifo -m o+w $fifo3
# Build arguments to fzf
opts=""
for arg in "${args[@]}"; do
arg="${arg//\\/\\\\}"
arg="${arg//\"/\\\"}"
arg="${arg//\`/\\\`}"
arg="${arg//$/\\$}"
opts="$opts \"$arg\""
done
pppid=$$
echo -n "trap 'kill -SIGUSR1 -$pppid' EXIT SIGINT SIGTERM;" > $argsf
close="; trap - EXIT SIGINT SIGTERM $close"
if [[ -n "$term" ]] || [[ -t 0 ]]; then
cat <<< "\"$fzf\" $opts > $fifo2; echo \$? > $fifo3 $close" >> $argsf
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
set-window-option remain-on-exit off \;\
split-window $opt "$envs bash -c 'cd $(printf %q "$PWD"); exec -a fzf bash $argsf'" $swap \
> /dev/null 2>&1
else
mkfifo $fifo1
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; echo \$? > $fifo3 $close" >> $argsf
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
set-window-option remain-on-exit off \;\
split-window $opt "$envs bash -c 'exec -a fzf bash $argsf'" $swap \
> /dev/null 2>&1
cat <&0 > $fifo1 &
fi
cat $fifo2
exit "$(cat $fifo3)"

226
doc/fzf.txt Normal file
View File

@ -0,0 +1,226 @@
fzf.txt fzf Last change: November 19 2017
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
==============================================================================
FZF Vim integration
:FZF[!]
Configuration
Examples
fzf#run
fzf#wrap
fzf inside terminal buffer
Hide statusline
GVim
License
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.)
{1} https://github.com/junegunn/fzf.vim
:FZF[!]
==============================================================================
*:FZF*
If you have set up fzf for Vim, `:FZF` command will be added.
>
" Look for files under current directory
:FZF
" Look for files under your home directory
:FZF ~
" With options
:FZF --no-sort --reverse --inline-info /tmp
" Bang version starts fzf in fullscreen mode
:FZF!
<
Similarly to {ctrlp.vim}{2}, use enter key, CTRL-T, CTRL-X or CTRL-V to open
selected files in the current window, in new tabs, in horizontal splits, or in
vertical splits respectively.
Note that the environment variables `FZF_DEFAULT_COMMAND` and
`FZF_DEFAULT_OPTS` also apply here.
{2} https://github.com/kien/ctrlp.vim
< Configuration >_____________________________________________________________~
*fzf-configuration*
*g:fzf_action* *g:fzf_layout* *g:fzf_colors* *g:fzf_history_dir* *g:fzf_launcher*
*g:Fzf_launcher*
- `g:fzf_action`
- Customizable extra key bindings for opening selected files in different
ways
- `g:fzf_layout`
- Determines the size and position of fzf window
- `g:fzf_colors`
- Customizes fzf colors to match the current color scheme
- `g:fzf_history_dir`
- Enables history feature
- `g:fzf_launcher`
- (Only in GVim) Terminal emulator to open fzf with
- `g:Fzf_launcher` for function reference
Examples~
*fzf-examples*
>
" This is the default extra key bindings
let g:fzf_action = {
\ 'ctrl-t': 'tab split',
\ 'ctrl-x': 'split',
\ 'ctrl-v': 'vsplit' }
" An action can be a reference to a function that processes selected lines
function! s:build_quickfix_list(lines)
call setqflist(map(copy(a:lines), '{ "filename": v:val }'))
copen
cc
endfunction
let g:fzf_action = {
\ 'ctrl-q': function('s:build_quickfix_list'),
\ 'ctrl-t': 'tab split',
\ 'ctrl-x': 'split',
\ 'ctrl-v': 'vsplit' }
" Default fzf layout
" - down / up / left / right
let g:fzf_layout = { 'down': '~40%' }
" You can set up fzf window using a Vim command (Neovim or latest Vim 8 required)
let g:fzf_layout = { 'window': 'enew' }
let g:fzf_layout = { 'window': '-tabnew' }
let g:fzf_layout = { 'window': '10split enew' }
" Customize fzf colors to match your color scheme
let g:fzf_colors =
\ { 'fg': ['fg', 'Normal'],
\ 'bg': ['bg', 'Normal'],
\ 'hl': ['fg', 'Comment'],
\ 'fg+': ['fg', 'CursorLine', 'CursorColumn', 'Normal'],
\ 'bg+': ['bg', 'CursorLine', 'CursorColumn'],
\ 'hl+': ['fg', 'Statement'],
\ 'info': ['fg', 'PreProc'],
\ 'border': ['fg', 'Ignore'],
\ 'prompt': ['fg', 'Conditional'],
\ 'pointer': ['fg', 'Exception'],
\ 'marker': ['fg', 'Keyword'],
\ 'spinner': ['fg', 'Label'],
\ 'header': ['fg', 'Comment'] }
" Enable per-command history.
" CTRL-N and CTRL-P will be automatically bound to next-history and
" previous-history instead of down and up. If you don't like the change,
" explicitly bind the keys to down and up in your $FZF_DEFAULT_OPTS.
let g:fzf_history_dir = '~/.local/share/fzf-history'
<
FZF#RUN *fzf#run*
==============================================================================
For more advanced uses, you can use `fzf#run([options])` function with the
following options.
---------------------------+---------------+--------------------------------------------------------------
Option name | Type | Description ~
---------------------------+---------------+--------------------------------------------------------------
`source` | string | External command to generate input to fzf (e.g. `find.` )
`source` | list | Vim list as input to fzf
`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 | Similar to `sink` , but takes the list of output lines at once
`options` | string/list | Options to fzf
`dir` | string | Working directory
`up` / `down` / `left` / `right` | number/string | Use tmux pane with the given size (e.g. `20` , `50%` )
`window` (Vim 8 / Neovim) | string | Command to open fzf window (e.g. `verticalaboveleft30new` )
`launcher` | string | External terminal emulator to start fzf with (GVim only)
`launcher` | funcref | Function for generating `launcher` string (GVim only)
---------------------------+---------------+--------------------------------------------------------------
`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
issues on different platforms.
>
call fzf#run({'options': '--reverse --prompt "C:\\Program Files\\"'})
call fzf#run({'options': ['--reverse', '--prompt', 'C:\Program Files\']})
<
FZF#WRAP *fzf#wrap*
==============================================================================
`fzf#wrap([namestring,][optsdict,][fullscreenboolean])` is a helper
function that decorates the options dictionary so that it understands
`g:fzf_layout`, `g:fzf_action`, `g:fzf_colors`, and `g:fzf_history_dir` like
`:FZF`.
>
command! -bang MyStuff
\ call fzf#run(fzf#wrap('my-stuff', {'dir': '~/my-stuff'}, <bang>0))
<
FZF INSIDE TERMINAL BUFFER *fzf-inside-terminal-buffer*
==============================================================================
The latest versions of Vim and Neovim include builtin terminal emulator
(`:terminal`) and fzf will start in a terminal buffer in the following cases:
- On Neovim
- On GVim
- On Terminal Vim with the non-default layout
- `callfzf#run({'left':'30%'})` or `letg:fzf_layout={'left':'30%'}`
< Hide statusline >___________________________________________________________~
*fzf-hide-statusline*
When fzf starts in a terminal buffer, you may want to hide the statusline of
the containing buffer.
>
autocmd! FileType fzf
autocmd FileType fzf set laststatus=0 noshowmode noruler
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
<
GVIM *fzf-gvim*
==============================================================================
With the latest version of GVim, fzf will start inside the builtin terminal
emulator of Vim. Please note that this terminal feature of Vim is still young
and unstable and you may run into some issues.
If you have an older version of GVim, you need an external terminal emulator
to start fzf with. `xterm` command is used by default, but you can customize
it with `g:fzf_launcher`.
>
" This is the default. %s is replaced with fzf command
let g:fzf_launcher = 'xterm -e bash -ic %s'
" Use urxvt instead
let g:fzf_launcher = 'urxvt -geometry 120x30 -e sh -c %s'
<
If you're running MacVim on OSX, I recommend you to use iTerm2 as the
launcher. Refer to the {this wiki page}{3} to see how to set up.
{3} https://github.com/junegunn/fzf/wiki/On-MacVim-with-iTerm2
LICENSE *fzf-license*
==============================================================================
The MIT License (MIT)
Copyright (c) 2017 Junegunn Choi
==============================================================================
vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap:

View File

@ -1,9 +0,0 @@
require 'rubygems/dependency_installer'
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.1.0')
Gem::DependencyInstaller.new.install 'curses', '~> 1.0'
end
File.open(File.expand_path('../Rakefile', __FILE__), 'w') do |f|
f.puts 'task :default'
end

1281
fzf

File diff suppressed because it is too large Load Diff

View File

@ -1,167 +0,0 @@
#!/bin/bash
# ____ ____
# / __/___ / __/
# / /_/_ / / /_
# / __/ / /_/ __/
# /_/ /___/_/-completion.bash
#
# - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty)
_fzf_opts_completion() {
local cur prev opts
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts="-m --multi -x --extended -s --sort +s +i +c --no-color"
case "${prev}" in
--sort|-s)
COMPREPLY=( $(compgen -W "$(seq 2000 1000 10000)" -- ${cur}) )
return 0
;;
esac
if [[ ${cur} =~ ^-|\+ ]]; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
return 0
}
_fzf_generic_completion() {
local cur base dir leftover matches trigger
COMPREPLY=()
trigger=${FZF_COMPLETION_TRIGGER:-**}
cur="${COMP_WORDS[COMP_CWORD]}"
if [[ ${cur} == *"$trigger" ]]; then
base=${cur:0:${#cur}-${#trigger}}
eval base=$base
dir="$base"
while [ 1 ]; do
if [ -z "$dir" -o -d "$dir" ]; then
leftover=${base/#"$dir"}
leftover=${leftover/#\/}
[ "$dir" = './' ] && dir=''
tput sc
matches=$(find "$dir"* $1 2> /dev/null | fzf $FZF_COMPLETION_OPTS $2 -q "$leftover" | while read item; do
printf '%q ' "$item"
done)
matches=${matches% }
if [ -n "$matches" ]; then
COMPREPLY=( "$matches" )
else
COMPREPLY=( "$cur" )
fi
tput rc
return 0
fi
dir=$(dirname "$dir")
[[ "$dir" =~ /$ ]] || dir="$dir"/
done
fi
}
_fzf_all_completion() {
_fzf_generic_completion \
"-name .git -prune -o -name .svn -prune -o -type d -print -o -type f -print -o -type l -print" \
"-m"
}
_fzf_file_completion() {
_fzf_generic_completion \
"-name .git -prune -o -name .svn -prune -o -type f -print -o -type l -print" \
"-m"
}
_fzf_dir_completion() {
_fzf_generic_completion \
"-name .git -prune -o -name .svn -prune -o -type d -print" \
""
}
_fzf_kill_completion() {
[ -n "${COMP_WORDS[COMP_CWORD]}" ] && return 1
local selected
tput sc
selected=$(ps -ef | sed 1d | fzf -m $FZF_COMPLETION_OPTS | awk '{print $2}' | tr '\n' ' ')
tput rc
if [ -n "$selected" ]; then
COMPREPLY=( "$selected" )
return 0
fi
}
_fzf_telnet_completion() {
local cur selected trigger
trigger=${FZF_COMPLETION_TRIGGER:-**}
cur="${COMP_WORDS[COMP_CWORD]}"
[[ ${cur} == *"$trigger" ]] || return 1
cur=${cur:0:${#cur}-${#trigger}}
tput sc
selected=$(grep -v '^\s*\(#\|$\)' /etc/hosts | awk '{print $2}' | sort -u | fzf $FZF_COMPLETION_OPTS -q "$cur")
tput rc
if [ -n "$selected" ]; then
COMPREPLY=("$selected")
return 0
fi
}
_fzf_ssh_completion() {
local cur selected trigger
trigger=${FZF_COMPLETION_TRIGGER:-**}
cur="${COMP_WORDS[COMP_CWORD]}"
[[ ${cur} == *"$trigger" ]] || return 1
cur=${cur:0:${#cur}-${#trigger}}
tput sc
selected=$(cat \
<(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | grep -i ^host) \
<(grep -v '^\s*\(#\|$\)' /etc/hosts) | \
awk '{print $2}' | sort -u | fzf $FZF_COMPLETION_OPTS -q "$cur")
tput rc
if [ -n "$selected" ]; then
COMPREPLY=("$selected")
return 0
fi
}
complete -F _fzf_opts_completion fzf
# Directory
for cmd in "cd pushd rmdir"; do
complete -F _fzf_dir_completion -o default -o bashdefault $cmd
done
# File
for cmd in "
awk cat diff diff3
emacs ex file ftp g++ gcc gvim head hg java
javac ld less more mvim patch perl python ruby
sed sftp sort source tail tee uniq vi view vim wc"; do
complete -F _fzf_file_completion -o default -o bashdefault $cmd
done
# Anything
for cmd in "
basename bunzip2 bzip2 chmod chown curl cp dirname du
find git grep gunzip gzip hg jar
ln ls mv open rm rsync scp
svn tar unzip zip"; do
complete -F _fzf_all_completion -o default -o bashdefault $cmd
done
# Kill completion
complete -F _fzf_kill_completion -o nospace -o default -o bashdefault kill
# Host completion
complete -F _fzf_ssh_completion -o default -o bashdefault ssh
complete -F _fzf_telnet_completion -o default -o bashdefault telnet

View File

@ -1,9 +0,0 @@
#!/bin/zsh
# ____ ____
# / __/___ / __/
# / /_/_ / / /_
# / __/ / /_/ __/
# /_/ /___/_/-completion.zsh
#
# TODO

View File

@ -1,17 +0,0 @@
# coding: utf-8
Gem::Specification.new do |spec|
spec.name = 'fzf'
spec.version = '0.8.3'
spec.authors = ['Junegunn Choi']
spec.email = ['junegunn.c@gmail.com']
spec.description = %q{Fuzzy finder for your shell}
spec.summary = %q{Fuzzy finder for your shell}
spec.homepage = 'https://github.com/junegunn/fzf'
spec.license = 'MIT'
spec.bindir = '.'
spec.files = %w[fzf.gemspec]
spec.executables = 'fzf'
spec.extensions += ['ext/mkrf_conf.rb']
end

38
glide.lock generated Normal file
View File

@ -0,0 +1,38 @@
hash: d68dd0bd779ac4ffca1e0c49ca38d85f90d5d68fa8e2d5d7db70a8ce8c662ec1
updated: 2017-06-01T15:48:41.653745249-07:00
imports:
- name: github.com/gdamore/encoding
version: b23993cbb6353f0e6aa98d0ee318a34728f628b9
- name: github.com/gdamore/tcell
version: 44772c121bb7838819d3ba4a7e84c0c2d617328e
subpackages:
- encoding
- name: github.com/lucasb-eyer/go-colorful
version: c900de9dbbc73129068f5af6a823068fc5f2308c
- name: github.com/mattn/go-isatty
version: 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8
- name: github.com/mattn/go-runewidth
version: 14207d285c6c197daabb5c9793d63e7af9ab2d50
- name: github.com/mattn/go-shellwords
version: 02e3cf038dcea8290e44424da473dd12be796a8a
- name: golang.org/x/crypto
version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e
subpackages:
- ssh/terminal
- name: golang.org/x/sys
version: b90f89a1e7a9c1f6b918820b3daa7f08488c8594
subpackages:
- unix
- name: golang.org/x/text
version: 4ee4af566555f5fbe026368b75596286a312663a
subpackages:
- encoding
- encoding/charmap
- encoding/internal
- encoding/internal/identifier
- encoding/japanese
- encoding/korean
- encoding/simplifiedchinese
- encoding/traditionalchinese
- transform
testImports: []

16
glide.yaml Normal file
View File

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

555
install
View File

@ -1,260 +1,379 @@
#!/bin/bash
#!/usr/bin/env bash
cd `dirname $BASH_SOURCE`
fzf_base=`pwd`
set -u
# ruby executable
echo -n "Checking Ruby executable ... "
ruby=`which ruby`
if [ $? -ne 0 ]; then
echo "ruby executable not found!"
version=0.17.1
auto_completion=
key_bindings=
update_config=2
binary_arch=
allow_legacy=
shells="bash zsh fish"
help() {
cat << EOF
usage: $0 [OPTIONS]
--help Show this message
--bin Download fzf binary only; Do not generate ~/.fzf.{bash,zsh}
--all Download fzf binary and update configuration files
to enable key bindings and fuzzy completion
--[no-]key-bindings Enable/disable key bindings (CTRL-T, CTRL-R, ALT-C)
--[no-]completion Enable/disable fuzzy completion (bash & zsh)
--[no-]update-rc Whether or not to update shell configuration files
--no-bash Do not set up bash configuration
--no-zsh Do not set up zsh configuration
--no-fish Do not set up fish configuration
--32 Download 32-bit binary
--64 Download 64-bit binary
EOF
}
for opt in "$@"; do
case $opt in
--help)
help
exit 0
;;
--all)
auto_completion=1
key_bindings=1
update_config=1
allow_legacy=1
;;
--key-bindings) key_bindings=1 ;;
--no-key-bindings) key_bindings=0 ;;
--completion) auto_completion=1 ;;
--no-completion) auto_completion=0 ;;
--update-rc) update_config=1 ;;
--no-update-rc) update_config=0 ;;
--32) binary_arch=386 ;;
--64) binary_arch=amd64 ;;
--bin) ;;
--no-bash) shells=${shells/bash/} ;;
--no-zsh) shells=${shells/zsh/} ;;
--no-fish) shells=${shells/fish/} ;;
*)
echo "unknown option: $opt"
help
exit 1
fi
;;
esac
done
# System ruby is preferred
curses_check="begin; require 'curses'; rescue Exception; exit 1; end"
system_ruby=/usr/bin/ruby
if [ -x $system_ruby -a $system_ruby != "$ruby" ]; then
$system_ruby --disable-gems -e "$curses_check" 2> /dev/null
[ $? -eq 0 ] && ruby=$system_ruby
fi
cd "$(dirname "${BASH_SOURCE[0]}")"
fzf_base="$(pwd)"
echo "OK ($ruby)"
ask() {
while true; do
read -p "$1 ([y]/n) " -r
if [[ $REPLY =~ ^[Yy]$ ]]; then
return 1
elif [[ $REPLY =~ ^[Nn]$ ]]; then
return 0
fi
done
}
# Curses-support
echo -n "Checking Curses support ... "
"$ruby" -e "$curses_check"
if [ $? -eq 0 ]; then
echo "OK"
else
echo "Not found"
echo "Installing 'curses' gem ... "
/usr/bin/env gem install curses -v 1.0.0 --user-install
check_binary() {
echo -n " - Checking fzf executable ... "
local output
output=$("$fzf_base"/bin/fzf --version 2>&1 | awk '{print $1}')
if [ $? -ne 0 ]; then
echo
echo "Failed to install 'curses' gem."
if [[ $(uname -r) =~ 'ARCH' ]]; then
echo "Make sure that base-devel package group is installed."
echo "Error: $output"
binary_error="Invalid binary"
elif [ "$version" != "$output" ]; then
echo "$output != $version"
binary_error="Invalid version"
else
echo "$output"
binary_error=""
return 0
fi
rm -f "$fzf_base"/bin/fzf
return 1
}
link_fzf_in_path() {
if which_fzf="$(command -v fzf)"; then
echo " - Found in \$PATH"
echo " - Creating symlink: $which_fzf -> bin/fzf"
(cd "$fzf_base"/bin && rm -f fzf && ln -sf "$which_fzf" fzf)
check_binary && return
fi
return 1
}
try_curl() {
command -v curl > /dev/null &&
if [[ $1 =~ tgz$ ]]; then
curl -fL $1 | tar -xzf -
else
local temp=${TMPDIR:-/tmp}/fzf.zip
curl -fLo "$temp" $1 && unzip -o "$temp" && rm -f "$temp"
fi
}
try_wget() {
command -v wget > /dev/null &&
if [[ $1 =~ tgz$ ]]; then
wget -O - $1 | tar -xzf -
else
local temp=${TMPDIR:-/tmp}/fzf.zip
wget -O "$temp" $1 && unzip -o "$temp" && rm -f "$temp"
fi
}
download() {
echo "Downloading bin/fzf ..."
if [[ ! "$version" =~ alpha ]]; then
if [ -x "$fzf_base"/bin/fzf ]; then
echo " - Already exists"
check_binary && return
fi
link_fzf_in_path && return
fi
mkdir -p "$fzf_base"/bin && cd "$fzf_base"/bin
if [ $? -ne 0 ]; then
binary_error="Failed to create bin directory"
return
fi
local url
[[ "$version" =~ alpha ]] &&
url=https://github.com/junegunn/fzf-bin/releases/download/alpha/${1} ||
url=https://github.com/junegunn/fzf-bin/releases/download/$version/${1}
set -o pipefail
if ! (try_curl $url || try_wget $url); then
set +o pipefail
binary_error="Failed to download with curl and wget"
return
fi
set +o pipefail
if [ ! -f fzf ]; then
binary_error="Failed to download ${1}"
return
fi
chmod +x fzf && check_binary
}
# Try to download binary executable
archi=$(uname -sm)
binary_available=1
binary_error=""
case "$archi" in
Darwin\ *64) download fzf-$version-darwin_${binary_arch:-amd64}.tgz ;;
Darwin\ *86) download fzf-$version-darwin_${binary_arch:-386}.tgz ;;
Linux\ armv5*) download fzf-$version-linux_${binary_arch:-arm5}.tgz ;;
Linux\ armv6*) download fzf-$version-linux_${binary_arch:-arm6}.tgz ;;
Linux\ armv7*) download fzf-$version-linux_${binary_arch:-arm7}.tgz ;;
Linux\ armv8*) download fzf-$version-linux_${binary_arch:-arm8}.tgz ;;
Linux\ aarch64*) download fzf-$version-linux_${binary_arch:-arm8}.tgz ;;
Linux\ *64) download fzf-$version-linux_${binary_arch:-amd64}.tgz ;;
Linux\ *86) download fzf-$version-linux_${binary_arch:-386}.tgz ;;
FreeBSD\ *64) download fzf-$version-freebsd_${binary_arch:-amd64}.tgz ;;
FreeBSD\ *86) download fzf-$version-freebsd_${binary_arch:-386}.tgz ;;
OpenBSD\ *64) download fzf-$version-openbsd_${binary_arch:-amd64}.tgz ;;
OpenBSD\ *86) download fzf-$version-openbsd_${binary_arch:-386}.tgz ;;
CYGWIN*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
MINGW*\ *86) download fzf-$version-windows_${binary_arch:-386}.zip ;;
MINGW*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
*) binary_available=0 binary_error=1 ;;
esac
cd "$fzf_base"
if [ -n "$binary_error" ]; then
if [ $binary_available -eq 0 ]; then
echo "No prebuilt binary for $archi ..."
else
echo " - $binary_error !!!"
fi
if command -v go > /dev/null; then
echo -n "Building binary (go get -u github.com/junegunn/fzf) ... "
if [ -z "${GOPATH-}" ]; then
export GOPATH="${TMPDIR:-/tmp}/fzf-gopath"
mkdir -p "$GOPATH"
fi
if go get -u github.com/junegunn/fzf; then
echo "OK"
cp "$GOPATH/bin/fzf" "$fzf_base/bin/"
else
echo "Failed to build binary. Installation failed."
exit 1
fi
else
echo "go executable not found. Installation failed."
exit 1
fi
fi
# Ruby version
echo -n "Checking Ruby version ... "
"$ruby" -e 'exit RUBY_VERSION >= "1.9"'
if [ $? -eq 0 ]; then
echo ">= 1.9"
"$ruby" --disable-gems -e "$curses_check"
if [ $? -eq 0 ]; then
fzf_cmd="$ruby --disable-gems $fzf_base/fzf"
else
fzf_cmd="$ruby $fzf_base/fzf"
[[ "$*" =~ "--bin" ]] && exit 0
for s in $shells; do
if ! command -v "$s" > /dev/null; then
shells=${shells/$s/}
fi
else
echo "< 1.9"
fzf_cmd="$ruby $fzf_base/fzf"
done
if [[ ${#shells} -lt 3 ]]; then
echo "No shell configuration to be updated."
exit 0
fi
# Auto-completion
read -p "Do you want to add auto-completion support? ([y]/n) " -n 1 -r
echo
[[ ! $REPLY =~ ^[Nn]$ ]]
auto_completion=$?
if [ -z "$auto_completion" ]; then
ask "Do you want to enable fuzzy auto-completion?"
auto_completion=$?
fi
# Key-bindings
read -p "Do you want to add key bindings? ([y]/n) " -n 1 -r
echo
[[ ! $REPLY =~ ^[Nn]$ ]]
key_bindings=$?
if [ -z "$key_bindings" ]; then
ask "Do you want to enable key bindings?"
key_bindings=$?
fi
echo
for shell in bash zsh; do
for shell in $shells; do
[[ "$shell" = fish ]] && continue
echo -n "Generate ~/.fzf.$shell ... "
src=~/.fzf.${shell}
fzf_completion="source $fzf_base/fzf-completion.${shell}"
if [ $auto_completion -ne 0 ]; then
fzf_completion="[[ \$- == *i* ]] && source \"$fzf_base/shell/completion.${shell}\" 2> /dev/null"
if [ $auto_completion -eq 0 ]; then
fzf_completion="# $fzf_completion"
fi
fzf_key_bindings="source \"$fzf_base/shell/key-bindings.${shell}\""
if [ $key_bindings -eq 0 ]; then
fzf_key_bindings="# $fzf_key_bindings"
fi
cat > $src << EOF
# Setup fzf function
# ------------------
unalias fzf 2> /dev/null
fzf() {
$fzf_cmd "\$@"
}
export -f fzf > /dev/null
# Setup fzf
# ---------
if [[ ! "\$PATH" == *$fzf_base/bin* ]]; then
export PATH="\$PATH:$fzf_base/bin"
fi
# Auto-completion
# ---------------
[[ \$- =~ i ]] && $fzf_completion
$fzf_completion
# Key bindings
# ------------
$fzf_key_bindings
EOF
if [ $key_bindings -eq 0 ]; then
if [ $shell = bash ]; then
cat >> $src << "EOFZF"
# Key bindings
# ------------
__fsel() {
find * -path '*/\.*' -prune \
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | fzf -m | while read item; do
printf '%q ' "$item"
done
echo
}
if [[ $- =~ i ]]; then
__fsel_tmux() {
local height
height=${FZF_TMUX_HEIGHT:-40%}
if [[ $height =~ %$ ]]; then
height="-p ${height%\%}"
else
height="-l $height"
fi
tmux split-window $height "bash -c 'source ~/.fzf.bash; tmux send-keys -t $TMUX_PANE \"\$(__fsel)\"'"
}
__fcd() {
local dir
dir=$(find ${1:-*} -path '*/\.*' -prune -o -type d -print 2> /dev/null | fzf +m) && printf 'cd %q' "$dir"
}
__use_tmux=0
[ -n "$TMUX_PANE" -a ${FZF_TMUX:-1} -ne 0 -a ${LINES:-40} -gt 15 ] && __use_tmux=1
if [ -z "$(set -o | grep '^vi.*on')" ]; then
# Required to refresh the prompt after fzf
bind '"\er": redraw-current-line'
# CTRL-T - Paste the selected file path into the command line
if [ $__use_tmux -eq 1 ]; then
bind '"\C-t": " \C-u \C-a\C-k$(__fsel_tmux)\e\C-e\C-y\C-a\C-d\C-y\ey\C-h"'
else
bind '"\C-t": " \C-u \C-a\C-k$(__fsel)\e\C-e\C-y\C-a\C-y\ey\C-h\C-e\er"'
fi
# CTRL-R - Paste the selected command from history into the command line
bind '"\C-r": " \C-e\C-u$(HISTTIMEFORMAT= history | fzf +s | sed \"s/ *[0-9]* *//\")\e\C-e\er"'
# ALT-C - cd into the selected directory
bind '"\ec": " \C-e\C-u$(__fcd)\e\C-e\er\C-m"'
else
bind '"\C-x\C-e": shell-expand-line'
bind '"\C-x\C-r": redraw-current-line'
# CTRL-T - Paste the selected file path into the command line
# - FIXME: Selected items are attached to the end regardless of cursor position
if [ $__use_tmux -eq 1 ]; then
bind '"\C-t": "\e$a \eddi$(__fsel_tmux)\C-x\C-e\e0P$xa"'
else
bind '"\C-t": "\e$a \eddi$(__fsel)\C-x\C-e\e0Px$a \C-x\C-r"'
fi
# CTRL-R - Paste the selected command from history into the command line
bind '"\C-r": "\eddi$(HISTTIMEFORMAT= history | fzf +s | sed \"s/ *[0-9]* *//\")\C-x\C-e\e$a\C-x\C-r"'
# ALT-C - cd into the selected directory
bind '"\ec": "\eddi$(__fcd)\C-x\C-e\C-x\C-r\C-m"'
fi
unset __use_tmux
fi
EOFZF
else
cat >> $src << "EOFZF"
# Key bindings
# ------------
# CTRL-T - Paste the selected file path(s) into the command line
__fsel() {
set -o nonomatch
find * -path '*/\.*' -prune \
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | fzf -m | while read item; do
printf '%q ' "$item"
done
echo
}
if [[ $- =~ i ]]; then
if [ -n "$TMUX_PANE" -a ${FZF_TMUX:-1} -ne 0 -a ${LINES:-40} -gt 15 ]; then
fzf-file-widget() {
local height
height=${FZF_TMUX_HEIGHT:-40%}
if [[ $height =~ %$ ]]; then
height="-p ${height%\%}"
else
height="-l $height"
fi
tmux split-window $height "zsh -c 'source ~/.fzf.zsh; tmux send-keys -t $TMUX_PANE \"\$(__fsel)\"'"
}
else
fzf-file-widget() {
LBUFFER="${LBUFFER%% #}$(__fsel)"
zle redisplay
}
fi
zle -N fzf-file-widget
bindkey '^T' fzf-file-widget
# ALT-C - cd into the selected directory
fzf-cd-widget() {
cd "${$(set -o nonomatch; find * -path '*/\.*' -prune \
-o -type d -print 2> /dev/null | fzf):-.}"
zle reset-prompt
}
zle -N fzf-cd-widget
bindkey '\ec' fzf-cd-widget
# CTRL-R - Paste the selected command from history into the command line
fzf-history-widget() {
LBUFFER=$(fc -l 1 | fzf +s | sed "s/ *[0-9]* *//")
zle redisplay
}
zle -N fzf-history-widget
bindkey '^R' fzf-history-widget
fi
EOFZF
fi
fi
echo "OK"
done
echo
for shell in bash zsh; do
rc=~/.${shell}rc
src="source ~/.fzf.${shell}"
# fish
if [[ "$shells" =~ fish ]]; then
echo -n "Update fish_user_paths ... "
fish << EOF
echo \$fish_user_paths | \grep $fzf_base/bin > /dev/null
or set --universal fish_user_paths \$fish_user_paths $fzf_base/bin
EOF
[ $? -eq 0 ] && echo "OK" || echo "Failed"
echo "Update $rc:"
echo " - $src"
line=$(grep -nF "$src" $rc | sed 's/:.*//')
if [ -n "$line" ]; then
echo " - Already exists (line #$line)"
mkdir -p ~/.config/fish/functions
if [ -e ~/.config/fish/functions/fzf.fish ]; then
echo -n "Remove unnecessary ~/.config/fish/functions/fzf.fish ... "
rm -f ~/.config/fish/functions/fzf.fish && echo "OK" || echo "Failed"
fi
fish_binding=~/.config/fish/functions/fzf_key_bindings.fish
if [ $key_bindings -ne 0 ]; then
echo -n "Symlink $fish_binding ... "
ln -sf "$fzf_base/shell/key-bindings.fish" \
"$fish_binding" && echo "OK" || echo "Failed"
else
echo $src >> $rc
echo " - Added"
echo -n "Removing $fish_binding ... "
rm -f "$fish_binding"
echo "OK"
fi
fi
append_line() {
set -e
local update line file pat lno
update="$1"
line="$2"
file="$3"
pat="${4:-}"
lno=""
echo "Update $file:"
echo " - $line"
if [ -f "$file" ]; then
if [ $# -lt 4 ]; then
lno=$(\grep -nF "$line" "$file" | sed 's/:.*//' | tr '\n' ' ')
else
lno=$(\grep -nF "$pat" "$file" | sed 's/:.*//' | tr '\n' ' ')
fi
fi
if [ -n "$lno" ]; then
echo " - Already exists: line #$lno"
else
if [ $update -eq 1 ]; then
[ -f "$file" ] && echo >> "$file"
echo "$line" >> "$file"
echo " + Added"
else
echo " ~ Skipped"
fi
fi
echo
set +e
}
create_file() {
local file="$1"
shift
echo "Create $file:"
for line in "$@"; do
echo " $line"
echo "$line" >> "$file"
done
echo
}
if [ $update_config -eq 2 ]; then
echo
ask "Do you want to update your shell configuration files?"
update_config=$?
fi
echo
for shell in $shells; do
[[ "$shell" = fish ]] && continue
[ $shell = zsh ] && dest=${ZDOTDIR:-~}/.zshrc || dest=~/.bashrc
append_line $update_config "[ -f ~/.fzf.${shell} ] && source ~/.fzf.${shell}" "$dest" "~/.fzf.${shell}"
done
cat << EOF
Finished. Reload your .bashrc or .zshrc.
source ~/.bashrc # bash
source ~/.zshrc # zsh
To uninstall fzf, simply remove the added line.
For more information, see: https://github.com/junegunn/fzf
EOF
if [ $key_bindings -eq 1 ] && [[ "$shells" =~ fish ]]; then
bind_file=~/.config/fish/functions/fish_user_key_bindings.fish
if [ ! -e "$bind_file" ]; then
create_file "$bind_file" \
'function fish_user_key_bindings' \
' fzf_key_bindings' \
'end'
else
append_line $update_config "fzf_key_bindings" "$bind_file"
fi
fi
if [ $update_config -eq 1 ]; then
echo 'Finished. Restart your shell or reload config file.'
[[ "$shells" =~ bash ]] && echo ' source ~/.bashrc # bash'
[[ "$shells" =~ zsh ]] && echo " source ${ZDOTDIR:-~}/.zshrc # zsh"
[[ "$shells" =~ fish ]] && [ $key_bindings -eq 1 ] && echo ' fzf_key_bindings # fish'
echo
echo 'Use uninstall script to remove fzf.'
echo
fi
echo 'For more information, see: https://github.com/junegunn/fzf'

9
main.go Normal file
View File

@ -0,0 +1,9 @@
package main
import "github.com/junegunn/fzf/src"
var revision string
func main() {
fzf.Run(fzf.ParseOptions(), revision)
}

54
man/man1/fzf-tmux.1 Normal file
View File

@ -0,0 +1,54 @@
.ig
The MIT License (MIT)
Copyright (c) 2017 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
..
.TH fzf-tmux 1 "Oct 2017" "fzf 0.17.1" "fzf-tmux - open fzf in tmux split pane"
.SH NAME
fzf-tmux - open fzf in tmux split pane
.SH SYNOPSIS
.B fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS]
.SH DESCRIPTION
fzf-tmux is a wrapper script for fzf that opens fzf in a tmux split pane. It is
designed to work just like fzf except that it does not take up the whole
screen. You can safely use fzf-tmux instead of fzf in your scripts as the extra
options will be silently ignored if you're not on tmux.
.SH OPTIONS
.SS Layout
(default: \fB-d 50%\fR)
.TP
.B "-u [height[%]]"
Split above (up)
.TP
.B "-d [height[%]]"
Split below (down)
.TP
.B "-l [width[%]]"
Split left
.TP
.B "-r [width[%]]"
Split right

599
man/man1/fzf.1 Normal file
View File

@ -0,0 +1,599 @@
.ig
The MIT License (MIT)
Copyright (c) 2017 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
..
.TH fzf 1 "Oct 2017" "fzf 0.17.1" "fzf - a command-line fuzzy finder"
.SH NAME
fzf - a command-line fuzzy finder
.SH SYNOPSIS
fzf [options]
.SH DESCRIPTION
fzf is a general-purpose command-line fuzzy finder.
.SH OPTIONS
.SS Search mode
.TP
.B "-x, --extended"
Extended-search mode. Since 0.10.9, this is enabled by default. You can disable
it with \fB+x\fR or \fB--no-extended\fR.
.TP
.B "-e, --exact"
Enable exact-match
.TP
.B "-i"
Case-insensitive match (default: smart-case match)
.TP
.B "+i"
Case-sensitive match
.TP
.B "--literal"
Do not normalize latin script letters for matching.
.TP
.BI "--algo=" TYPE
Fuzzy matching algorithm (default: v2)
.br
.BR v2 " Optimal scoring algorithm (quality)"
.br
.BR v1 " Faster but not guaranteed to find the optimal result (performance)"
.br
.TP
.BI "-n, --nth=" "N[,..]"
Comma-separated list of field index expressions for limiting search scope.
See \fBFIELD INDEX EXPRESSION\fR for the details.
.TP
.BI "--with-nth=" "N[,..]"
Transform the presentation of each line using field index expressions
.TP
.BI "-d, --delimiter=" "STR"
Field delimiter regex for \fB--nth\fR and \fB--with-nth\fR (default: AWK-style)
.SS Search result
.TP
.B "+s, --no-sort"
Do not sort the result
.TP
.B "--tac"
Reverse the order of the input
.RS
e.g. \fBhistory | fzf --tac --no-sort\fR
.RE
.TP
.BI "--tiebreak=" "CRI[,..]"
Comma-separated list of sort criteria to apply when the scores are tied.
.br
.br
.BR length " Prefers line with shorter length"
.br
.BR begin " Prefers line with matched substring closer to the beginning"
.br
.BR end " Prefers line with matched substring closer to the end"
.br
.BR index " Prefers line that appeared earlier in the input stream"
.br
.br
- Each criterion should appear only once in the list
.br
- \fBindex\fR is only allowed at the end of the list
.br
- \fBindex\fR is implicitly appended to the list when not specified
.br
- Default is \fBlength\fR (or equivalently \fBlength\fR,index)
.br
- If \fBend\fR is found in the list, fzf will scan each line backwards
.SS Interface
.TP
.B "-m, --multi"
Enable multi-select with tab/shift-tab
.TP
.B "+m, --no-multi"
Disable multi-select
.TP
.B "--no-mouse"
Disable mouse
.TP
.BI "--bind=" "KEYBINDS"
Comma-separated list of custom key bindings. See \fBKEY BINDINGS\fR for the
details.
.TP
.B "--cycle"
Enable cyclic scroll
.TP
.B "--no-hscroll"
Disable horizontal scroll
.TP
.BI "--hscroll-off=" "COL"
Number of screen columns to keep to the right of the highlighted substring
(default: 10). Setting it to a large value will cause the text to be positioned
on the center of the screen.
.TP
.B "--filepath-word"
Make word-wise movements and actions respect path separators. The following
actions are affected:
\fBbackward-kill-word\fR
.br
\fBbackward-word\fR
.br
\fBforward-word\fR
.br
\fBkill-word\fR
.TP
.BI "--jump-labels=" "CHARS"
Label characters for \fBjump\fR and \fBjump-accept\fR
.SS Layout
.TP
.BI "--height=" "HEIGHT[%]"
Display fzf window below the cursor with the given height instead of using
the full screen.
.TP
.BI "--min-height=" "HEIGHT"
Minimum height when \fB--height\fR is given in percent (default: 10).
Ignored when \fB--height\fR is not specified.
.TP
.B "--reverse"
Reverse orientation
.TP
.B "--border"
Draw border above and below the finder
.TP
.BI "--margin=" MARGIN
Comma-separated expression for margins around the finder.
.br
.br
.RS
.BR TRBL " Same margin for top, right, bottom, and left"
.br
.BR TB,RL " Vertical, horizontal margin"
.br
.BR T,RL,B " Top, horizontal, bottom margin"
.br
.BR T,R,B,L " Top, right, bottom, left margin"
.br
.br
Each part can be given in absolute number or in percentage relative to the
terminal size with \fB%\fR suffix.
.br
.br
e.g. \fBfzf --margin 10%\fR
\fBfzf --margin 1,5%\fR
.RE
.TP
.B "--inline-info"
Display finder info inline with the query
.TP
.BI "--prompt=" "STR"
Input prompt (default: '> ')
.TP
.BI "--header=" "STR"
The given string will be printed as the sticky header. The lines are displayed
in the given order from top to bottom regardless of \fB--reverse\fR option, and
are not affected by \fB--with-nth\fR. ANSI color codes are processed even when
\fB--ansi\fR is not set.
.TP
.BI "--header-lines=" "N"
The first N lines of the input are treated as the sticky header. When
\fB--with-nth\fR is set, the lines are transformed just like the other
lines that follow.
.SS Display
.TP
.B "--ansi"
Enable processing of ANSI color codes
.TP
.BI "--tabstop=" SPACES
Number of spaces for a tab character (default: 8)
.TP
.BI "--color=" "[BASE_SCHEME][,COLOR:ANSI]"
Color configuration. The name of the base color scheme is followed by custom
color mappings. Ansi color code of -1 denotes terminal default
foreground/background color. You can also specify 24-bit color in \fB#rrggbb\fR
format.
.RS
e.g. \fBfzf --color=bg+:24\fR
\fBfzf --color=light,fg:232,bg:255,bg+:116,info:27\fR
.RE
.RS
.B BASE SCHEME:
(default: dark on 256-color terminal, otherwise 16)
\fBdark \fRColor scheme for dark 256-color terminal
\fBlight \fRColor scheme for light 256-color terminal
\fB16 \fRColor scheme for 16-color terminal
\fBbw \fRNo colors
.B COLOR:
\fBfg \fRText
\fBbg \fRBackground
\fBhl \fRHighlighted substrings
\fBfg+ \fRText (current line)
\fBbg+ \fRBackground (current line)
\fBhl+ \fRHighlighted substrings (current line)
\fBinfo \fRInfo
\fBborder \fRBorder of the preview window and horizontal separators (\fB--border\fR)
\fBprompt \fRPrompt
\fBpointer \fRPointer to the current line
\fBmarker \fRMulti-select marker
\fBspinner \fRStreaming input indicator
\fBheader \fRHeader
.RE
.TP
.B "--no-bold"
Do not use bold text
.TP
.B "--black"
Use black background
.SS History
.TP
.BI "--history=" "HISTORY_FILE"
Load search history from the specified file and update the file on completion.
When enabled, \fBCTRL-N\fR and \fBCTRL-P\fR are automatically remapped to
\fBnext-history\fR and \fBprevious-history\fR.
.TP
.BI "--history-size=" "N"
Maximum number of entries in the history file (default: 1000). The file is
automatically truncated when the number of the lines exceeds the value.
.SS Preview
.TP
.BI "--preview=" "COMMAND"
Execute the given command for the current line and display the result on the
preview window. \fB{}\fR in the command is the placeholder that is replaced to
the single-quoted string of the current line. To transform the replacement
string, specify field index expressions between the braces (See \fBFIELD INDEX
EXPRESSION\fR for the details).
.RS
e.g. \fBfzf --preview="head -$LINES {}"\fR
\fBls -l | fzf --preview="echo user={3} when={-4..-2}; cat {-1}" --header-lines=1\fR
A placeholder expression starting with \fB+\fR flag will be replaced to the
space-separated list of the selected lines (or the current line if no selection
was made) individually quoted.
e.g. \fBfzf --multi --preview="head -10 {+}"\fR
\fBgit log --oneline | fzf --multi --preview 'git show {+1}'\fR
Also, \fB{q}\fR is replaced to the current query string.
Note that you can escape a placeholder pattern by prepending a backslash.
.RE
.TP
.BI "--preview-window=" "[POSITION][:SIZE[%]][:wrap][:hidden]"
Determine the layout of the preview window. If the argument ends with
\fB:hidden\fR, the preview window will be hidden by default until
\fBtoggle-preview\fR action is triggered. Long lines are truncated by default.
Line wrap can be enabled with \fB:wrap\fR flag.
If size is given as 0, preview window will not be visible, but fzf will still
execute the command in the background.
.RS
.B POSITION: (default: right)
\fBup
\fBdown
\fBleft
\fBright
.RE
.RS
e.g. \fBfzf --preview="head {}" --preview-window=up:30%\fR
\fBfzf --preview="file {}" --preview-window=down:1\fR
.RE
.SS Scripting
.TP
.BI "-q, --query=" "STR"
Start the finder with the given query
.TP
.B "-1, --select-1"
Automatically select the only match
.TP
.B "-0, --exit-0"
Exit immediately when there's no match
.TP
.BI "-f, --filter=" "STR"
Filter mode. Do not start interactive finder. When used with \fB--no-sort\fR,
fzf becomes a fuzzy-version of grep.
.TP
.B "--print-query"
Print query as the first line
.TP
.BI "--expect=" "KEY[,..]"
Comma-separated list of keys that can be used to complete fzf in addition to
the default enter key. When this option is set, fzf will print the name of the
key pressed as the first line of its output (or as the second line if
\fB--print-query\fR is also used). The line will be empty if fzf is completed
with the default enter key. If \fB--expect\fR option is specified multiple
times, fzf will expect the union of the keys. \fB--no-expect\fR will clear the
list.
.RS
e.g. \fBfzf --expect=ctrl-v,ctrl-t,alt-s --expect=f1,f2,~,@\fR
.RE
.TP
.B "--read0"
Read input delimited by ASCII NUL characters instead of newline characters
.TP
.B "--print0"
Print output delimited by ASCII NUL characters instead of newline characters
.TP
.B "--no-clear"
Do not clear finder interface on exit. If fzf was started in full screen mode,
it will not switch back to the original screen, so you'll have to manually run
\fBtput rmcup\fR to return. This option can be used to avoid flickering of the
screen when your application needs to start fzf multiple times in order.
.TP
.B "--sync"
Synchronous search for multi-staged filtering. If specified, fzf will launch
ncurses finder only after the input stream is complete.
.RS
e.g. \fBfzf --multi | fzf --sync\fR
.RE
.TP
.B "--version"
Display version information and exit
.TP
Note that most options have the opposite versions with \fB--no-\fR prefix.
.SH ENVIRONMENT VARIABLES
.TP
.B FZF_DEFAULT_COMMAND
Default command to use when input is tty
.TP
.B FZF_DEFAULT_OPTS
Default options. e.g. \fBexport FZF_DEFAULT_OPTS="--extended --cycle"\fR
.SH EXIT STATUS
.BR 0 " Normal exit"
.br
.BR 1 " No match"
.br
.BR 2 " Error"
.br
.BR 130 " Interrupted with \fBCTRL-C\fR or \fBESC\fR"
.SH FIELD INDEX EXPRESSION
A field index expression can be a non-zero integer or a range expression
([BEGIN]..[END]). \fB--nth\fR and \fB--with-nth\fR take a comma-separated list
of field index expressions.
.SS Examples
.BR 1 " The 1st field"
.br
.BR 2 " The 2nd field"
.br
.BR -1 " The last field"
.br
.BR -2 " The 2nd to last field"
.br
.BR 3..5 " From the 3rd field to the 5th field"
.br
.BR 2.. " From the 2nd field to the last field"
.br
.BR ..-3 " From the 1st field to the 3rd to the last field"
.br
.BR .. " All the fields"
.br
.SH EXTENDED SEARCH MODE
Unless specified otherwise, fzf will start in "extended-search mode". In this
mode, you can specify multiple patterns delimited by spaces, such as: \fB'wild
^music .mp3$ sbtrkt !rmx\fR
You can prepend a backslash to a space (\fB\\ \fR) to match a literal space
character.
.SS Exact-match (quoted)
A term that is prefixed by a single-quote character (\fB'\fR) is interpreted as
an "exact-match" (or "non-fuzzy") term. fzf will search for the exact
occurrences of the string.
.SS Anchored-match
A term can be prefixed by \fB^\fR, or suffixed by \fB$\fR to become an
anchored-match term. Then fzf will search for the lines that start with or end
with the given string. An anchored-match term is also an exact-match term.
.SS Negation
If a term is prefixed by \fB!\fR, fzf will exclude the lines that satisfy the
term from the result. In this case, fzf performs exact match by default.
.SS Exact-match by default
If you don't prefer fuzzy matching and do not wish to "quote" (prefixing with
\fB'\fR) every word, start fzf with \fB-e\fR or \fB--exact\fR option. Note that
when \fB--exact\fR is set, \fB'\fR-prefix "unquotes" the term.
.SS OR operator
A single bar character term acts as an OR operator. For example, the following
query matches entries that start with \fBcore\fR and end with either \fBgo\fR,
\fBrb\fR, or \fBpy\fR.
e.g. \fB^core go$ | rb$ | py$\fR
.SH KEY BINDINGS
You can customize key bindings of fzf with \fB--bind\fR option which takes
a comma-separated list of key binding expressions. Each key binding expression
follows the following format: \fBKEY:ACTION\fR
e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
.B AVAILABLE KEYS: (SYNONYMS)
\fIctrl-[a-z]\fR
\fIctrl-space\fR
\fIctrl-alt-[a-z]\fR
\fIalt-[a-z]\fR
\fIalt-[0-9]\fR
\fIf[1-12]\fR
\fIenter\fR (\fIreturn\fR \fIctrl-m\fR)
\fIspace\fR
\fIbspace\fR (\fIbs\fR)
\fIalt-enter\fR
\fIalt-space\fR
\fIalt-bspace\fR (\fIalt-bs\fR)
\fIalt-/\fR
\fItab\fR
\fIbtab\fR (\fIshift-tab\fR)
\fIesc\fR
\fIdel\fR
\fIup\fR
\fIdown\fR
\fIleft\fR
\fIright\fR
\fIhome\fR
\fIend\fR
\fIpgup\fR (\fIpage-up\fR)
\fIpgdn\fR (\fIpage-down\fR)
\fIshift-left\fR
\fIshift-right\fR
\fIdouble-click\fR
or any single character
Additionally, a special event named \fIchange\fR is available which is
triggered whenever the query string is changed.
e.g. \fBfzf --bind change:top\fR
\fBACTION: DEFAULT BINDINGS (NOTES):
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
\fBaccept\fR \fIenter double-click\fR
\fBbackward-char\fR \fIctrl-b left\fR
\fBbackward-delete-char\fR \fIctrl-h bspace\fR
\fBbackward-kill-word\fR \fIalt-bs\fR
\fBbackward-word\fR \fIalt-b shift-left\fR
\fBbeginning-of-line\fR \fIctrl-a home\fR
\fBcancel\fR
\fBclear-screen\fR \fIctrl-l\fR
\fBdelete-char\fR \fIdel\fR
\fBdelete-char/eof\fR \fIctrl-d\fR
\fBdeselect-all\fR
\fBdown\fR \fIctrl-j ctrl-n down\fR
\fBend-of-line\fR \fIctrl-e end\fR
\fBexecute(...)\fR (see below for the details)
\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
\fBjump\fR (EasyMotion-like 2-keystroke movement)
\fBjump-accept\fR (jump and accept)
\fBkill-line\fR
\fBkill-word\fR \fIalt-d\fR
\fBnext-history\fR (\fIctrl-n\fR on \fB--history\fR)
\fBpage-down\fR \fIpgdn\fR
\fBpage-up\fR \fIpgup\fR
\fBhalf-page-down\fR
\fBhalf-page-up\fR
\fBpreview-down\fR
\fBpreview-up\fR
\fBpreview-page-down\fR
\fBpreview-page-up\fR
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
\fBprint-query\fR (print query and exit)
\fBselect-all\fR
\fBtoggle\fR
\fBtoggle-all\fR
\fBtoggle+down\fR \fIctrl-i (tab)\fR
\fBtoggle-in\fR (\fB--reverse\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
\fBtoggle-out\fR (\fB--reverse\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
\fBtoggle-preview\fR
\fBtoggle-preview-wrap\fR
\fBtoggle-sort\fR
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
\fBtop\fR (move to the top result)
\fBunix-line-discard\fR \fIctrl-u\fR
\fBunix-word-rubout\fR \fIctrl-w\fR
\fBup\fR \fIctrl-k ctrl-p up\fR
\fByank\fR \fIctrl-y\fR
Multiple actions can be chained using \fB+\fR separator.
\fBfzf --bind 'ctrl-a:select-all+accept'\fR
With \fBexecute(...)\fR action, you can execute arbitrary commands without
leaving fzf. For example, you can turn fzf into a simple file browser by
binding \fBenter\fR key to \fBless\fR command like follows.
\fBfzf --bind "enter:execute(less {})"\fR
You can use the same placeholder expressions as in \fB--preview\fR.
If the command contains parentheses, fzf may fail to parse the expression. In
that case, you can use any of the following alternative notations to avoid
parse errors.
\fBexecute[...]\fR
\fBexecute~...~\fR
\fBexecute!...!\fR
\fBexecute@...@\fR
\fBexecute#...#\fR
\fBexecute$...$\fR
\fBexecute%...%\fR
\fBexecute^...^\fR
\fBexecute&...&\fR
\fBexecute*...*\fR
\fBexecute;...;\fR
\fBexecute/.../\fR
\fBexecute|...|\fR
\fBexecute:...\fR
.RS
This is the special form that frees you from parse errors as it does not expect
the closing character. The catch is that it should be the last one in the
comma-separated list of key-action pairs.
.RE
fzf switches to the alternate screen when executing a command. However, if the
command is expected to complete quickly, and you are not interested in its
output, you might want to use \fBexecute-silent\fR instead, which silently
executes the command without the switching. Note that fzf will not be
responsible until the command is complete. For asynchronous execution, start
your command as a background process (i.e. appending \fB&\fR).
.SH AUTHOR
Junegunn Choi (\fIjunegunn.c@gmail.com\fR)
.SH SEE ALSO
.B Project homepage:
.RS
.I https://github.com/junegunn/fzf
.RE
.br
.br
.B Extra Vim plugin:
.RS
.I https://github.com/junegunn/fzf.vim
.RE
.SH LICENSE
MIT

View File

@ -1,4 +1,4 @@
" Copyright (c) 2014 Junegunn Choi
" Copyright (c) 2017 Junegunn Choi
"
" MIT License
"
@ -21,201 +21,788 @@
" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
let s:min_tmux_width = 10
let s:min_tmux_height = 3
let s:default_tmux_height = '40%'
if exists('g:loaded_fzf')
finish
endif
let g:loaded_fzf = 1
let s:is_win = has('win32') || has('win64')
if s:is_win && &shellslash
set noshellslash
let s:base_dir = expand('<sfile>:h:h')
set shellslash
else
let s:base_dir = expand('<sfile>:h:h')
endif
if s:is_win
let s:term_marker = '&::FZF'
function! s:fzf_call(fn, ...)
let shellslash = &shellslash
try
set noshellslash
return call(a:fn, a:000)
finally
let &shellslash = shellslash
endtry
endfunction
" Use utf-8 for fzf.vim commands
" Return array of shell commands for cmd.exe
function! s:wrap_cmds(cmds)
return ['@echo off', 'for /f "tokens=4" %%a in (''chcp'') do set origchcp=%%a', 'chcp 65001 > nul'] +
\ (type(a:cmds) == type([]) ? a:cmds : [a:cmds]) +
\ ['chcp %origchcp% > nul']
endfunction
else
let s:term_marker = ";#FZF"
function! s:fzf_call(fn, ...)
return call(a:fn, a:000)
endfunction
function! s:wrap_cmds(cmds)
return a:cmds
endfunction
endif
function! s:shellesc_cmd(arg)
let escaped = substitute(a:arg, '[&|<>()@^]', '^&', 'g')
let escaped = substitute(escaped, '%', '%%', 'g')
let escaped = substitute(escaped, '"', '\\^&', 'g')
let escaped = substitute(escaped, '\(\\\+\)\(\\^\)', '\1\1\2', 'g')
return '^"'.substitute(escaped, '\(\\\+\)$', '\1\1', '').'^"'
endfunction
function! fzf#shellescape(arg, ...)
let shell = get(a:000, 0, &shell)
if shell =~# 'cmd.exe$'
return s:shellesc_cmd(a:arg)
endif
return s:fzf_call('shellescape', a:arg)
endfunction
function! s:fzf_getcwd()
return s:fzf_call('getcwd')
endfunction
function! s:fzf_fnamemodify(fname, mods)
return s:fzf_call('fnamemodify', a:fname, a:mods)
endfunction
function! s:fzf_expand(fmt)
return s:fzf_call('expand', a:fmt, 1)
endfunction
function! s:fzf_tempname()
return s:fzf_call('tempname')
endfunction
let s:default_layout = { 'down': '~40%' }
let s:layout_keys = ['window', 'up', 'down', 'left', 'right']
let s:fzf_go = s:base_dir.'/bin/fzf'
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
set cpo&vim
call system('type fzf')
if v:shell_error
let s:fzf_rb = expand('<sfile>:h:h').'/fzf'
if executable(s:fzf_rb)
let s:exec = s:fzf_rb
else
echoerr 'fzf executable not found'
finish
endif
else
function! s:fzf_exec()
if !exists('s:exec')
if executable(s:fzf_go)
let s:exec = s:fzf_go
elseif executable('fzf')
let s:exec = 'fzf'
endif
elseif s:is_win && !has('win32unix')
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
echo
call s:warn('Downloading fzf binary. Please wait ...')
let s:installed = 1
call system(s:install.' --bin')
return s:fzf_exec()
else
redraw
throw 'fzf executable not found'
endif
endif
return fzf#shellescape(s:exec)
endfunction
function! s:tmux_enabled()
if has('gui_running')
return 0
endif
if exists('s:tmux')
return s:tmux
endif
let s:tmux = 0
if exists('$TMUX')
if exists('$TMUX') && executable(s:fzf_tmux)
let output = system('tmux -V')
let s:tmux = !v:shell_error && output >= 'tmux 1.7'
endif
return s:tmux
endfunction
function! s:shellesc(arg)
return '"'.substitute(a:arg, '"', '\\"', 'g').'"'
function! s:escape(path)
let path = fnameescape(a:path)
return s:is_win ? escape(path, '$') : path
endfunction
function! s:escape(path)
return substitute(a:path, ' ', '\\ ', 'g')
" Upgrade legacy options
function! s:upgrade(dict)
let copy = copy(a:dict)
if has_key(copy, 'tmux')
let copy.down = remove(copy, 'tmux')
endif
if has_key(copy, 'tmux_height')
let copy.down = remove(copy, 'tmux_height')
endif
if has_key(copy, 'tmux_width')
let copy.right = remove(copy, 'tmux_width')
endif
return copy
endfunction
function! s:error(msg)
echohl ErrorMsg
echom a:msg
echohl None
endfunction
function! s:warn(msg)
echohl WarningMsg
echom a:msg
echohl None
endfunction
function! s:has_any(dict, keys)
for key in a:keys
if has_key(a:dict, key)
return 1
endif
endfor
return 0
endfunction
function! s:open(cmd, target)
if stridx('edit', a:cmd) == 0 && s:fzf_fnamemodify(a:target, ':p') ==# s:fzf_expand('%:p')
return
endif
execute a:cmd s:escape(a:target)
endfunction
function! s:common_sink(action, lines) abort
if len(a:lines) < 2
return
endif
let key = remove(a:lines, 0)
let Cmd = get(a:action, key, 'e')
if type(Cmd) == type(function('call'))
return Cmd(a:lines)
endif
if len(a:lines) > 1
augroup fzf_swap
autocmd SwapExists * let v:swapchoice='o'
\| call s:warn('fzf: E325: swap file exists: '.s:fzf_expand('<afile>'))
augroup END
endif
try
let empty = empty(s:fzf_expand('%')) && line('$') == 1 && empty(getline(1)) && !&modified
let autochdir = &autochdir
set noautochdir
for item in a:lines
if empty
execute 'e' s:escape(item)
let empty = 0
else
call s:open(Cmd, item)
endif
if !has('patch-8.0.0177') && !has('nvim-0.2') && exists('#BufEnter')
\ && isdirectory(item)
doautocmd BufEnter
endif
endfor
finally
let &autochdir = autochdir
silent! autocmd! fzf_swap
endtry
endfunction
function! s:get_color(attr, ...)
let gui = has('termguicolors') && &termguicolors
let fam = gui ? 'gui' : 'cterm'
let pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$'
for group in a:000
let code = synIDattr(synIDtrans(hlID(group)), a:attr, fam)
if code =~? pat
return code
endif
endfor
return ''
endfunction
function! s:defaults()
let rules = copy(get(g:, 'fzf_colors', {}))
let colors = join(map(items(filter(map(rules, 'call("s:get_color", v:val)'), '!empty(v:val)')), 'join(v:val, ":")'), ',')
return empty(colors) ? '' : ('--color='.colors)
endfunction
function! s:validate_layout(layout)
for key in keys(a:layout)
if index(s:layout_keys, key) < 0
throw printf('Invalid entry in g:fzf_layout: %s (allowed: %s)%s',
\ key, join(s:layout_keys, ', '), key == 'options' ? '. Use $FZF_DEFAULT_OPTS.' : '')
endif
endfor
return a:layout
endfunction
function! s:evaluate_opts(options)
return type(a:options) == type([]) ?
\ join(map(copy(a:options), 'fzf#shellescape(v:val)')) : a:options
endfunction
" [name string,] [opts dict,] [fullscreen boolean]
function! fzf#wrap(...)
let args = ['', {}, 0]
let expects = map(copy(args), 'type(v:val)')
let tidx = 0
for arg in copy(a:000)
let tidx = index(expects, type(arg), tidx)
if tidx < 0
throw 'Invalid arguments (expected: [name string] [opts dict] [fullscreen boolean])'
endif
let args[tidx] = arg
let tidx += 1
unlet arg
endfor
let [name, opts, bang] = args
if len(name)
let opts.name = name
end
" Layout: g:fzf_layout (and deprecated g:fzf_height)
if bang
for key in s:layout_keys
if has_key(opts, key)
call remove(opts, key)
endif
endfor
elseif !s:has_any(opts, s:layout_keys)
if !exists('g:fzf_layout') && exists('g:fzf_height')
let opts.down = g:fzf_height
else
let opts = extend(opts, s:validate_layout(get(g:, 'fzf_layout', s:default_layout)))
endif
endif
" Colors: g:fzf_colors
let opts.options = s:defaults() .' '. s:evaluate_opts(get(opts, 'options', ''))
" History: g:fzf_history_dir
if len(name) && len(get(g:, 'fzf_history_dir', ''))
let dir = s:fzf_expand(g:fzf_history_dir)
if !isdirectory(dir)
call mkdir(dir, 'p')
endif
let history = fzf#shellescape(dir.'/'.name)
let opts.options = join(['--history', history, opts.options])
endif
" Action: g:fzf_action
if !s:has_any(opts, ['sink', 'sink*'])
let opts._action = get(g:, 'fzf_action', s:default_action)
let opts.options .= ' --expect='.join(keys(opts._action), ',')
function! opts.sink(lines) abort
return s:common_sink(self._action, a:lines)
endfunction
let opts['sink*'] = remove(opts, 'sink')
endif
return opts
endfunction
function! s:use_sh()
let [shell, shellslash] = [&shell, &shellslash]
if s:is_win
set shell=cmd.exe
set noshellslash
else
set shell=sh
endif
return [shell, shellslash]
endfunction
function! fzf#run(...) abort
if has('gui_running')
echohl Error
echo 'GVim is not supported'
return []
try
let [shell, shellslash] = s:use_sh()
let dict = exists('a:1') ? s:upgrade(a:1) : {}
let temps = { 'result': s:fzf_tempname() }
let optstr = s:evaluate_opts(get(dict, 'options', ''))
try
let fzf_exec = s:fzf_exec()
catch
throw v:exception
endtry
if has('nvim') && !has_key(dict, 'dir')
let dict.dir = s:fzf_getcwd()
endif
if has('win32unix') && has_key(dict, 'dir')
let dict.dir = fnamemodify(dict.dir, ':p')
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
let dict = exists('a:1') ? a:1 : {}
let temps = { 'result': tempname() }
let optstr = get(dict, 'options', '')
if has_key(dict, 'source')
let source = dict.source
let type = type(source)
if type == 1
let prefix = source.'|'
let prefix = '( '.source.' )|'
elseif type == 3
let temps.input = tempname()
let temps.input = s:fzf_tempname()
call writefile(source, temps.input)
let prefix = 'cat '.s:shellesc(temps.input).'|'
let prefix = (s:is_win ? 'type ' : 'cat ').fzf#shellescape(temps.input).'|'
else
throw 'Invalid source type'
endif
else
let prefix = ''
endif
let command = prefix.s:exec.' '.optstr.' > '.temps.result
if s:tmux_enabled() && s:tmux_splittable(dict)
return s:execute_tmux(dict, command, temps)
else
return s:execute(dict, command, temps)
let prefer_tmux = get(g:, 'fzf_prefer_tmux', 0)
let use_height = has_key(dict, 'down') && !has('gui_running') &&
\ !(has('nvim') || s:is_win || has('win32unix') || s:present(dict, 'up', 'left', 'right', 'window')) &&
\ executable('tput') && filereadable('/dev/tty')
let has_vim8_term = has('terminal') && has('patch-8.0.995')
let has_nvim_term = has('nvim-0.2.1') || has('nvim') && !s:is_win
let use_term = has_nvim_term ||
\ has_vim8_term && !has('win32unix') && (has('gui_running') || s:is_win || !use_height && s:present(dict, 'down', 'up', 'left', 'right', 'window'))
let use_tmux = (!use_height && !use_term || prefer_tmux) && !has('win32unix') && s:tmux_enabled() && s:splittable(dict)
if prefer_tmux && use_tmux
let use_height = 0
let use_term = 0
endif
if use_height
let height = s:calc_size(&lines, dict.down, dict)
let optstr .= ' --height='.height
elseif use_term
let optstr .= ' --no-height'
endif
let command = prefix.(use_tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
if use_term
return s:execute_term(dict, command, temps)
endif
let lines = use_tmux ? s:execute_tmux(dict, command, temps)
\ : s:execute(dict, command, use_height, temps)
call s:callback(dict, lines)
return lines
finally
let [&shell, &shellslash] = [shell, shellslash]
endtry
endfunction
function! s:tmux_splittable(dict)
return
\ min([&columns, get(a:dict, 'tmux_width', 0)]) >= s:min_tmux_width ||
\ min([&lines, get(a:dict, 'tmux_height', get(a:dict, 'tmux', 0))]) >= s:min_tmux_height
function! s:present(dict, ...)
for key in a:000
if !empty(get(a:dict, key, ''))
return 1
endif
endfor
return 0
endfunction
function! s:fzf_tmux(dict)
let size = ''
for o in ['up', 'down', 'left', 'right']
if s:present(a:dict, o)
let spec = a:dict[o]
if (o == 'up' || o == 'down') && spec[0] == '~'
let size = '-'.o[0].s:calc_size(&lines, spec, a:dict)
else
" Legacy boolean option
let size = '-'.o[0].(spec == 1 ? '' : substitute(spec, '^\~', '', ''))
endif
break
endif
endfor
return printf('LINES=%d COLUMNS=%d %s %s %s --',
\ &lines, &columns, fzf#shellescape(s:fzf_tmux), size, (has_key(a:dict, 'source') ? '' : '-'))
endfunction
function! s:splittable(dict)
return s:present(a:dict, 'up', 'down') && &lines > 15 ||
\ s:present(a:dict, 'left', 'right') && &columns > 40
endfunction
function! s:pushd(dict)
if has_key(a:dict, 'dir')
let a:dict.prev_dir = getcwd()
execute 'chdir '.s:escape(a:dict.dir)
if s:present(a:dict, 'dir')
let cwd = s:fzf_getcwd()
if get(a:dict, 'prev_dir', '') ==# cwd
return 1
endif
let a:dict.prev_dir = cwd
execute 'lcd' s:escape(a:dict.dir)
let a:dict.dir = s:fzf_getcwd()
return 1
endif
return 0
endfunction
function! s:popd(dict)
if has_key(a:dict, 'prev_dir')
execute 'chdir '.s:escape(remove(a:dict, 'prev_dir'))
augroup fzf_popd
autocmd!
autocmd WinEnter * call s:dopopd()
augroup END
function! s:dopopd()
if !exists('w:fzf_dir') || s:fzf_getcwd() != w:fzf_dir[1]
return
endif
execute 'lcd' s:escape(w:fzf_dir[0])
unlet w:fzf_dir
endfunction
function! s:execute(dict, command, temps)
function! s:xterm_launcher()
let fmt = 'xterm -T "[fzf]" -bg "\%s" -fg "\%s" -geometry %dx%d+%d+%d -e bash -ic %%s'
if has('gui_macvim')
let fmt .= '&& osascript -e "tell application \"MacVim\" to activate"'
endif
return printf(fmt,
\ synIDattr(hlID("Normal"), "bg"), synIDattr(hlID("Normal"), "fg"),
\ &columns, &lines/2, getwinposx(), getwinposy())
endfunction
unlet! s:launcher
if s:is_win || has('win32unix')
let s:launcher = '%s'
else
let s:launcher = function('s:xterm_launcher')
endif
function! s:exit_handler(code, command, ...)
if a:code == 130
return 0
elseif a:code > 1
call s:error('Error running ' . a:command)
if !empty(a:000)
sleep
endif
return 0
endif
return 1
endfunction
function! s:execute(dict, command, use_height, temps) abort
call s:pushd(a:dict)
silent !clear
execute 'silent !'.a:command
redraw!
if v:shell_error
return []
else
return s:callback(a:dict, a:temps, 0)
if has('unix') && !a:use_height
silent! !clear 2> /dev/null
endif
let escaped = (a:use_height || s:is_win) ? a:command : escape(substitute(a:command, '\n', '\\n', 'g'), '%#!')
if has('gui_running')
let Launcher = get(a:dict, 'launcher', get(g:, 'Fzf_launcher', get(g:, 'fzf_launcher', s:launcher)))
let fmt = type(Launcher) == 2 ? call(Launcher, []) : Launcher
if has('unix')
let escaped = "'".substitute(escaped, "'", "'\"'\"'", 'g')."'"
endif
let command = printf(fmt, escaped)
else
let command = escaped
endif
if s:is_win
let batchfile = s:fzf_tempname().'.bat'
call writefile(s:wrap_cmds(command), batchfile)
let command = batchfile
let a:temps.batchfile = batchfile
if has('nvim')
let fzf = {}
let fzf.dict = a:dict
let fzf.temps = a:temps
function! fzf.on_exit(job_id, exit_status, event) dict
if s:present(self.dict, 'dir')
execute 'lcd' s:escape(self.dict.dir)
endif
let lines = s:collect(self.temps)
call s:callback(self.dict, lines)
endfunction
let cmd = 'start /wait cmd /c '.command
call jobstart(cmd, fzf)
return []
endif
elseif has('win32unix') && $TERM !=# 'cygwin'
let shellscript = s:fzf_tempname()
call writefile([command], shellscript)
let command = 'cmd.exe /C '.fzf#shellescape('set "TERM=" & start /WAIT sh -c '.shellscript)
let a:temps.shellscript = shellscript
endif
if a:use_height
let stdin = has_key(a:dict, 'source') ? '' : '< /dev/tty'
call system(printf('tput cup %d > /dev/tty; tput cnorm > /dev/tty; %s %s 2> /dev/tty', &lines, command, stdin))
else
execute 'silent !'.command
endif
let exit_status = v:shell_error
redraw!
return s:exit_handler(exit_status, command) ? s:collect(a:temps) : []
endfunction
function! s:execute_tmux(dict, command, temps)
if has_key(a:dict, 'dir')
let command = 'cd '.s:escape(a:dict.dir).' && '.a:command
function! s:execute_tmux(dict, command, temps) abort
let command = a:command
if s:pushd(a:dict)
" -c '#{pane_current_path}' is only available on tmux 1.9 or above
let command = join(['cd', fzf#shellescape(a:dict.dir), '&&', command])
endif
call system(command)
let exit_status = v:shell_error
redraw!
return s:exit_handler(exit_status, command) ? s:collect(a:temps) : []
endfunction
function! s:calc_size(max, val, dict)
let val = substitute(a:val, '^\~', '', '')
if val =~ '%$'
let size = a:max * str2nr(val[:-2]) / 100
else
let size = min([a:max, str2nr(val)])
endif
let srcsz = -1
if type(get(a:dict, 'source', 0)) == type([])
let srcsz = len(a:dict.source)
endif
let opts = get(a:dict, 'options', '').$FZF_DEFAULT_OPTS
let margin = stridx(opts, '--inline-info') > stridx(opts, '--no-inline-info') ? 1 : 2
let margin += stridx(opts, '--header') > stridx(opts, '--no-header')
return srcsz >= 0 ? min([srcsz + margin, size]) : size
endfunction
function! s:getpos()
return {'tab': tabpagenr(), 'win': winnr(), 'cnt': winnr('$'), 'tcnt': tabpagenr('$')}
endfunction
function! s:split(dict)
let directions = {
\ 'up': ['topleft', 'resize', &lines],
\ 'down': ['botright', 'resize', &lines],
\ 'left': ['vertical topleft', 'vertical resize', &columns],
\ 'right': ['vertical botright', 'vertical resize', &columns] }
let ppos = s:getpos()
try
if s:present(a:dict, 'window')
execute 'keepalt' a:dict.window
elseif !s:splittable(a:dict)
execute (tabpagenr()-1).'tabnew'
else
for [dir, triple] in items(directions)
let val = get(a:dict, dir, '')
if !empty(val)
let [cmd, resz, max] = triple
if (dir == 'up' || dir == 'down') && val[0] == '~'
let sz = s:calc_size(max, val, a:dict)
else
let sz = s:calc_size(max, val, {})
endif
execute cmd sz.'new'
execute resz sz
return [ppos, {}]
endif
endfor
endif
return [ppos, { '&l:wfw': &l:wfw, '&l:wfh': &l:wfh }]
finally
setlocal winfixwidth winfixheight
endtry
endfunction
function! s:execute_term(dict, command, temps) abort
let winrest = winrestcmd()
let pbuf = bufnr('')
let [ppos, winopts] = s:split(a:dict)
call s:use_sh()
let b:fzf = a:dict
let fzf = { 'buf': bufnr(''), 'pbuf': pbuf, 'ppos': ppos, 'dict': a:dict, 'temps': a:temps,
\ 'winopts': winopts, 'winrest': winrest, 'lines': &lines,
\ 'columns': &columns, 'command': a:command }
function! fzf.switch_back(inplace)
if a:inplace && bufnr('') == self.buf
if bufexists(self.pbuf)
execute 'keepalt b' self.pbuf
endif
" No other listed buffer
if bufnr('') == self.buf
enew
endif
endif
endfunction
function! fzf.on_exit(id, code, ...)
if s:getpos() == self.ppos " {'window': 'enew'}
for [opt, val] in items(self.winopts)
execute 'let' opt '=' val
endfor
call self.switch_back(1)
else
if bufnr('') == self.buf
" We use close instead of bd! since Vim does not close the split when
" there's no other listed buffer (nvim +'set nobuflisted')
close
endif
execute 'tabnext' self.ppos.tab
execute self.ppos.win.'wincmd w'
endif
if bufexists(self.buf)
execute 'bd!' self.buf
endif
if &lines == self.lines && &columns == self.columns && s:getpos() == self.ppos
execute self.winrest
endif
if !s:exit_handler(a:code, self.command, 1)
return
endif
call s:pushd(self.dict)
let lines = s:collect(self.temps)
call s:callback(self.dict, lines)
call self.switch_back(s:getpos() == self.ppos)
endfunction
try
if s:present(a:dict, 'dir')
execute 'lcd' s:escape(a:dict.dir)
endif
if s:is_win
let fzf.temps.batchfile = s:fzf_tempname().'.bat'
call writefile(s:wrap_cmds(a:command), fzf.temps.batchfile)
let command = fzf.temps.batchfile
else
let command = a:command
endif
let splitopt = '-v'
if has_key(a:dict, 'tmux_width')
let splitopt = '-h'
let size = a:dict.tmux_width
let command .= s:term_marker
if has('nvim')
call termopen(command, fzf)
else
let size = get(a:dict, 'tmux_height', get(a:dict, 'tmux'))
let t = term_start([&shell, &shellcmdflag, command], {'curwin': fzf.buf, 'exit_cb': function(fzf.on_exit)})
" FIXME: https://github.com/vim/vim/issues/1998
if !has('nvim') && !s:is_win
call term_wait(t, 20)
endif
if type(size) == 1 && size =~ '%$'
let sizeopt = '-p '.size[0:-2]
else
let sizeopt = '-l '.size
endif
let s:pane = substitute(
\ system(
\ printf(
\ 'tmux split-window %s %s -P -F "#{pane_id}" %s',
\ splitopt, sizeopt, s:shellesc(command))), '\n', '', 'g')
let s:dict = a:dict
let s:temps = a:temps
augroup fzf_tmux
autocmd!
autocmd VimResized * nested call s:tmux_check()
augroup END
finally
if s:present(a:dict, 'dir')
lcd -
endif
endtry
setlocal nospell bufhidden=wipe nobuflisted nonumber
setf fzf
startinsert
return []
endfunction
function! s:tmux_check()
let panes = split(system('tmux list-panes -a -F "#{pane_id}"'), '\n')
if index(panes, s:pane) < 0
augroup fzf_tmux
autocmd!
augroup END
call s:callback(s:dict, s:temps, 1)
redraw
endif
endfunction
function! s:callback(dict, temps, cd)
if !filereadable(a:temps.result)
let lines = []
else
if a:cd | call s:pushd(a:dict) | endif
let lines = readfile(a:temps.result)
if has_key(a:dict, 'sink')
for line in lines
if type(a:dict.sink) == 2
call a:dict.sink(line)
else
execute a:dict.sink.' '.s:escape(line)
endif
endfor
endif
endif
function! s:collect(temps) abort
try
return filereadable(a:temps.result) ? readfile(a:temps.result) : []
finally
for tf in values(a:temps)
silent! call delete(tf)
endfor
endtry
endfunction
call s:popd(a:dict)
function! s:callback(dict, lines) abort
" Since anything can be done in the sink function, there is no telling that
" the change of the working directory was made by &autochdir setting.
"
" We use the following heuristic to determine whether to restore CWD:
" - Always restore the current directory when &autochdir is disabled.
" FIXME This makes it impossible to change directory from inside the sink
" function when &autochdir is not used.
" - In case of an error or an interrupt, a:lines will be empty.
" And it will be an array of a single empty string when fzf was finished
" without a match. In these cases, we presume that the change of the
" directory is not expected and should be undone.
let popd = has_key(a:dict, 'prev_dir') &&
\ (!&autochdir || (empty(a:lines) || len(a:lines) == 1 && empty(a:lines[0])))
if popd
let w:fzf_dir = [a:dict.prev_dir, a:dict.dir]
endif
return lines
try
if has_key(a:dict, 'sink')
for line in a:lines
if type(a:dict.sink) == 2
call a:dict.sink(line)
else
execute a:dict.sink s:escape(line)
endif
endfor
endif
if has_key(a:dict, 'sink*')
call a:dict['sink*'](a:lines)
endif
catch
if stridx(v:exception, ':E325:') < 0
echoerr v:exception
endif
endtry
" We may have opened a new window or tab
if popd
let w:fzf_dir = [a:dict.prev_dir, a:dict.dir]
call s:dopopd()
endif
endfunction
let s:default_action = {
\ 'ctrl-t': 'tab split',
\ 'ctrl-x': 'split',
\ 'ctrl-v': 'vsplit' }
function! s:shortpath()
let short = fnamemodify(getcwd(), ':~:.')
if !has('win32unix')
let short = pathshorten(short)
endif
let slash = (s:is_win && !&shellslash) ? '\' : '/'
return empty(short) ? '~'.slash : short . (short =~ escape(slash, '\').'$' ? '' : slash)
endfunction
function! s:cmd(bang, ...) abort
let args = copy(a:000)
let opts = {}
if len(args) > 0 && isdirectory(expand(args[-1]))
let opts.dir = remove(args, -1)
let opts = { 'options': ['--multi'] }
if len(args) && isdirectory(expand(args[-1]))
let opts.dir = substitute(substitute(remove(args, -1), '\\\(["'']\)', '\1', 'g'), '[/\\]*$', '/', '')
if s:is_win && !&shellslash
let opts.dir = substitute(opts.dir, '/', '\\', 'g')
endif
if !a:bang
let opts.tmux = get(g:, 'fzf_tmux_height', s:default_tmux_height)
let prompt = opts.dir
else
let prompt = s:shortpath()
endif
call fzf#run(extend({ 'sink': 'e', 'options': join(args) }, opts))
let prompt = strwidth(prompt) < &columns - 20 ? prompt : '> '
call extend(opts.options, ['--prompt', prompt])
call extend(opts.options, args)
call fzf#run(fzf#wrap('FZF', opts, a:bang))
endfunction
command! -nargs=* -complete=dir -bang FZF call s:cmd('<bang>' == '!', <f-args>)
command! -nargs=* -complete=dir -bang FZF call s:cmd(<bang>0, <f-args>)
let &cpo = s:cpo_save
unlet s:cpo_save

324
shell/completion.bash Normal file
View File

@ -0,0 +1,324 @@
#!/bin/bash
# ____ ____
# / __/___ / __/
# / /_/_ / / /_
# / __/ / /_/ __/
# /_/ /___/_/-completion.bash
#
# - $FZF_TMUX (default: 0)
# - $FZF_TMUX_HEIGHT (default: '40%')
# - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty)
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
if ! declare -f _fzf_compgen_path > /dev/null; then
_fzf_compgen_path() {
echo "$1"
command find -L "$1" \
-name .git -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
}
fi
if ! declare -f _fzf_compgen_dir > /dev/null; then
_fzf_compgen_dir() {
command find -L "$1" \
-name .git -prune -o -name .svn -prune -o -type d \
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
}
fi
###########################################################
# To redraw line after fzf closes (printf '\e[5n')
bind '"\e[0n": redraw-current-line'
__fzfcmd_complete() {
[ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ] &&
echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
}
_fzf_orig_completion_filter() {
sed 's/^\(.*-F\) *\([^ ]*\).* \([^ ]*\)$/export _fzf_orig_completion_\3="\1 %s \3 #\2";/' |
awk -F= '{gsub(/[^A-Za-z0-9_= ;]/, "_", $1); print $1"="$2}'
}
_fzf_opts_completion() {
local cur prev opts
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts="
-x --extended
-e --exact
--algo
-i +i
-n --nth
--with-nth
-d --delimiter
+s --no-sort
--tac
--tiebreak
-m --multi
--no-mouse
--bind
--cycle
--no-hscroll
--jump-labels
--height
--literal
--reverse
--margin
--inline-info
--prompt
--header
--header-lines
--ansi
--tabstop
--color
--no-bold
--history
--history-size
--preview
--preview-window
-q --query
-1 --select-1
-0 --exit-0
-f --filter
--print-query
--expect
--sync"
case "${prev}" in
--tiebreak)
COMPREPLY=( $(compgen -W "length begin end index" -- "$cur") )
return 0
;;
--color)
COMPREPLY=( $(compgen -W "dark light 16 bw" -- "$cur") )
return 0
;;
--history)
COMPREPLY=()
return 0
;;
esac
if [[ "$cur" =~ ^-|\+ ]]; then
COMPREPLY=( $(compgen -W "${opts}" -- "$cur") )
return 0
fi
return 0
}
_fzf_handle_dynamic_completion() {
local cmd orig_var orig ret orig_cmd
cmd="$1"
shift
orig_cmd="$1"
orig_var="_fzf_orig_completion_$cmd"
orig="${!orig_var##*#}"
if [ -n "$orig" ] && type "$orig" > /dev/null 2>&1; then
$orig "$@"
elif [ -n "$_fzf_completion_loader" ]; then
_completion_loader "$@"
ret=$?
eval "$(complete | command grep "\-F.* $orig_cmd$" | _fzf_orig_completion_filter)"
source "${BASH_SOURCE[0]}"
return $ret
fi
}
__fzf_generic_path_completion() {
local cur base dir leftover matches trigger cmd fzf
fzf="$(__fzfcmd_complete)"
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
COMPREPLY=()
trigger=${FZF_COMPLETION_TRIGGER-'**'}
cur="${COMP_WORDS[COMP_CWORD]}"
if [[ "$cur" == *"$trigger" ]]; then
base=${cur:0:${#cur}-${#trigger}}
eval "base=$base"
dir="$base"
while true; do
if [ -z "$dir" ] || [ -d "$dir" ]; then
leftover=${base/#"$dir"}
leftover=${leftover/#\/}
[ -z "$dir" ] && dir='.'
[ "$dir" != "/" ] && dir="${dir/%\//}"
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" $fzf $2 -q "$leftover" | while read -r item; do
printf "%q$3 " "$item"
done)
matches=${matches% }
if [ -n "$matches" ]; then
COMPREPLY=( "$matches" )
else
COMPREPLY=( "$cur" )
fi
printf '\e[5n'
return 0
fi
dir=$(dirname "$dir")
[[ "$dir" =~ /$ ]] || dir="$dir"/
done
else
shift
shift
shift
_fzf_handle_dynamic_completion "$cmd" "$@"
fi
}
_fzf_complete() {
local cur selected trigger cmd fzf post
post="$(caller 0 | awk '{print $2}')_post"
type -t "$post" > /dev/null 2>&1 || post=cat
fzf="$(__fzfcmd_complete)"
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
trigger=${FZF_COMPLETION_TRIGGER-'**'}
cur="${COMP_WORDS[COMP_CWORD]}"
if [[ "$cur" == *"$trigger" ]]; then
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=${selected% } # Strip trailing space not to repeat "-o nospace"
printf '\e[5n'
if [ -n "$selected" ]; then
COMPREPLY=("$selected")
return 0
fi
else
shift
_fzf_handle_dynamic_completion "$cmd" "$@"
fi
}
_fzf_path_completion() {
__fzf_generic_path_completion _fzf_compgen_path "-m" "" "$@"
}
# Deprecated. No file only completion.
_fzf_file_completion() {
_fzf_path_completion "$@"
}
_fzf_dir_completion() {
__fzf_generic_path_completion _fzf_compgen_dir "" "/" "$@"
}
_fzf_complete_kill() {
[ -n "${COMP_WORDS[COMP_CWORD]}" ] && return 1
local selected fzf
fzf="$(__fzfcmd_complete)"
selected=$(command ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS --preview 'echo {}' --preview-window down:3:wrap $FZF_COMPLETION_OPTS" $fzf -m | awk '{print $2}' | tr '\n' ' ')
printf '\e[5n'
if [ -n "$selected" ]; then
COMPREPLY=( "$selected" )
return 0
fi
}
_fzf_complete_telnet() {
_fzf_complete '+m' "$@" < <(
command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' |
awk '{if (length($2) > 0) {print $2}}' | sort -u
)
}
_fzf_complete_ssh() {
_fzf_complete '+m' "$@" < <(
cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host' | command grep -v '*' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}') \
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
awk '{if (length($2) > 0) {print $2}}' | sort -u
)
}
_fzf_complete_unset() {
_fzf_complete '-m' "$@" < <(
declare -xp | sed 's/=.*//' | sed 's/.* //'
)
}
_fzf_complete_export() {
_fzf_complete '-m' "$@" < <(
declare -xp | sed 's/=.*//' | sed 's/.* //'
)
}
_fzf_complete_unalias() {
_fzf_complete '-m' "$@" < <(
alias | sed 's/=.*//' | sed 's/.* //'
)
}
# fzf options
complete -o default -F _fzf_opts_completion fzf
d_cmds="${FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir}"
a_cmds="
awk cat diff diff3
emacs emacsclient ex file ftp g++ gcc gvim head hg java
javac ld less more mvim nvim patch perl python ruby
sed sftp sort source tail tee uniq vi view vim wc xdg-open
basename bunzip2 bzip2 chmod chown curl cp dirname du
find git grep gunzip gzip hg jar
ln ls mv open rm rsync scp
svn tar unzip zip"
x_cmds="kill ssh telnet unset unalias export"
# Preserve existing completion
eval $(complete |
sed -E '/-F/!d; / _fzf/d; '"/ ($(echo $d_cmds $a_cmds $x_cmds | sed 's/ /|/g; s/+/\\+/g'))$/"'!d' |
_fzf_orig_completion_filter)
if type _completion_loader > /dev/null 2>&1; then
_fzf_completion_loader=1
fi
_fzf_defc() {
local cmd func opts orig_var orig def
cmd="$1"
func="$2"
opts="$3"
orig_var="_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}"
orig="${!orig_var}"
if [ -n "$orig" ]; then
printf -v def "$orig" "$func"
eval "$def"
else
complete -F "$func" $opts "$cmd"
fi
}
# Anything
for cmd in $a_cmds; do
_fzf_defc "$cmd" _fzf_path_completion "-o default -o bashdefault"
done
# Directory
for cmd in $d_cmds; do
_fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames"
done
unset _fzf_defc
# Kill completion
complete -F _fzf_complete_kill -o nospace -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

199
shell/completion.zsh Normal file
View File

@ -0,0 +1,199 @@
#!/bin/zsh
# ____ ____
# / __/___ / __/
# / /_/_ / / /_
# / __/ / /_/ __/
# /_/ /___/_/-completion.zsh
#
# - $FZF_TMUX (default: 0)
# - $FZF_TMUX_HEIGHT (default: '40%')
# - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty)
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
if ! declare -f _fzf_compgen_path > /dev/null; then
_fzf_compgen_path() {
echo "$1"
command find -L "$1" \
-name .git -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
}
fi
if ! declare -f _fzf_compgen_dir > /dev/null; then
_fzf_compgen_dir() {
command find -L "$1" \
-name .git -prune -o -name .svn -prune -o -type d \
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
}
fi
###########################################################
__fzfcmd_complete() {
[ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ] &&
echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
}
__fzf_generic_path_completion() {
local base lbuf compgen fzf_opts suffix tail fzf dir leftover matches
# (Q) flag removes a quoting level: "foo\ bar" => "foo bar"
base=${(Q)1}
lbuf=$2
compgen=$3
fzf_opts=$4
suffix=$5
tail=$6
fzf="$(__fzfcmd_complete)"
setopt localoptions nonomatch
dir="$base"
while [ 1 ]; do
if [[ -z "$dir" || -d ${~dir} ]]; then
leftover=${base/#"$dir"}
leftover=${leftover/#\/}
[ -z "$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
echo -n "${(q)item}$suffix "
done)
matches=${matches% }
if [ -n "$matches" ]; then
LBUFFER="$lbuf$matches$tail"
fi
zle redisplay
typeset -f zle-line-init >/dev/null && zle zle-line-init
break
fi
dir=$(dirname "$dir")
dir=${dir%/}/
done
}
_fzf_path_completion() {
__fzf_generic_path_completion "$1" "$2" _fzf_compgen_path \
"-m" "" " "
}
_fzf_dir_completion() {
__fzf_generic_path_completion "$1" "$2" _fzf_compgen_dir \
"" "/" ""
}
_fzf_feed_fifo() (
command rm -f "$1"
mkfifo "$1"
cat <&0 > "$1" &
)
_fzf_complete() {
local fifo fzf_opts lbuf fzf matches post
fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$"
fzf_opts=$1
lbuf=$2
post="${funcstack[2]}_post"
type $post > /dev/null 2>&1 || post=cat
fzf="$(__fzfcmd_complete)"
_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' ' ')
if [ -n "$matches" ]; then
LBUFFER="$lbuf$matches"
fi
zle redisplay
typeset -f zle-line-init >/dev/null && zle zle-line-init
command rm -f "$fifo"
}
_fzf_complete_telnet() {
_fzf_complete '+m' "$@" < <(
command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' |
awk '{if (length($2) > 0) {print $2}}' | sort -u
)
}
_fzf_complete_ssh() {
_fzf_complete '+m' "$@" < <(
command cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host' | command grep -v '*' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}') \
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
awk '{if (length($2) > 0) {print $2}}' | sort -u
)
}
_fzf_complete_export() {
_fzf_complete '-m' "$@" < <(
declare -xp | sed 's/=.*//' | sed 's/.* //'
)
}
_fzf_complete_unset() {
_fzf_complete '-m' "$@" < <(
declare -xp | sed 's/=.*//' | sed 's/.* //'
)
}
_fzf_complete_unalias() {
_fzf_complete '+m' "$@" < <(
alias | sed 's/=.*//'
)
}
fzf-completion() {
local tokens cmd prefix trigger tail fzf matches lbuf d_cmds
setopt localoptions noshwordsplit noksh_arrays noposixbuiltins
# http://zsh.sourceforge.net/FAQ/zshfaq03.html
# http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion-Flags
tokens=(${(z)LBUFFER})
if [ ${#tokens} -lt 1 ]; then
zle ${fzf_default_completion:-expand-or-complete}
return
fi
cmd=${tokens[1]}
# Explicitly allow for empty trigger.
trigger=${FZF_COMPLETION_TRIGGER-'**'}
[ -z "$trigger" -a ${LBUFFER[-1]} = ' ' ] && tokens+=("")
tail=${LBUFFER:$(( ${#LBUFFER} - ${#trigger} ))}
# Kill completion (do not require trigger sequence)
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 --preview 'echo {}' --preview-window down:3:wrap $FZF_COMPLETION_OPTS" ${=fzf} -m | awk '{print $2}' | tr '\n' ' ')
if [ -n "$matches" ]; then
LBUFFER="$LBUFFER$matches"
fi
zle redisplay
typeset -f zle-line-init >/dev/null && zle zle-line-init
# Trigger sequence given
elif [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
[ -z "${tokens[-1]}" ] && lbuf=$LBUFFER || lbuf=${LBUFFER:0:-${#tokens[-1]}}
if eval "type _fzf_complete_${cmd} > /dev/null"; then
eval "prefix=\"$prefix\" _fzf_complete_${cmd} \"$lbuf\""
elif [ ${d_cmds[(i)$cmd]} -le ${#d_cmds} ]; then
_fzf_dir_completion "$prefix" "$lbuf"
else
_fzf_path_completion "$prefix" "$lbuf"
fi
# Fall back to default completion
else
zle ${fzf_default_completion:-expand-or-complete}
fi
}
[ -z "$fzf_default_completion" ] && {
binding=$(bindkey '^I')
[[ $binding =~ 'undefined-key' ]] || fzf_default_completion=$binding[(s: :w)2]
unset binding
}
zle -N fzf-completion
bindkey '^I' fzf-completion

121
shell/key-bindings.bash Normal file
View File

@ -0,0 +1,121 @@
# Key bindings
# ------------
__fzf_select__() {
local cmd="${FZF_CTRL_T_COMMAND:-"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 d -print \
-o -type l -print 2> /dev/null | cut -b3-"}"
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" fzf -m "$@" | while read -r item; do
printf '%q ' "$item"
done
echo
}
if [[ $- =~ i ]]; then
__fzf_use_tmux__() {
[ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ]
}
__fzfcmd() {
__fzf_use_tmux__ &&
echo "fzf-tmux -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() {
if __fzf_use_tmux__; then
__fzf_select_tmux__
else
local selected="$(__fzf_select__)"
READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}$selected${READLINE_LINE:$READLINE_POINT}"
READLINE_POINT=$(( READLINE_POINT + ${#selected} ))
fi
}
__fzf_cd__() {
local cmd dir
cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
-o -type d -print 2> /dev/null | cut -b3-"}"
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m) && printf 'cd %q' "$dir"
}
__fzf_history__() (
local line
shopt -u nocaseglob nocasematch
line=$(
HISTTIMEFORMAT= history |
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS --tac -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m" $(__fzfcmd) |
command grep '^ *[0-9]') &&
if [[ $- =~ H ]]; then
sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line"
else
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
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"'
fi
# CTRL-R - Paste the selected command from history into the command line
bind '"\C-r": " \C-e\C-u`__fzf_history__`\e\C-e\e^\er"'
# ALT-C - cd into the selected directory
bind '"\ec": " \C-e\C-u`__fzf_cd__`\e\C-e\er\C-m"'
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
# - FIXME: Selected items are attached to the end regardless of cursor position
if [ $BASH_VERSINFO -gt 3 ]; then
bind -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
bind '"\C-r": "\C-x\C-addi`__fzf_history__`\C-x\C-e\C-x^\C-x\C-a$a\C-x\C-r"'
bind -m vi-command '"\C-r": "i\C-r"'
# 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

146
shell/key-bindings.fish Normal file
View File

@ -0,0 +1,146 @@
# Key bindings
# ------------
function fzf_key_bindings
# Store current token in $dir as root for the 'find' command
function fzf-file-widget -d "List files and folders"
set -l commandline (__fzf_parse_commandline)
set -l dir $commandline[1]
set -l fzf_query $commandline[2]
# "-path \$dir'*/\\.*'" matches hidden files/folders inside $dir but not
# $dir itself, even if hidden.
set -q 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 \
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | sed 's@^\./@@'"
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
begin
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
end
if [ -z "$result" ]
commandline -f repaint
return
else
# Remove last token from commandline.
commandline -t ""
end
for i in $result
commandline -it -- (string escape $i)
commandline -it -- ' '
end
commandline -f repaint
end
function fzf-history-widget -d "Show command history"
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
begin
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m"
set -l FISH_MAJOR (echo $FISH_VERSION | cut -f1 -d.)
set -l FISH_MINOR (echo $FISH_VERSION | cut -f2 -d.)
# history's -z flag is needed for multi-line support.
# history's -z flag was added in fish 2.4.0, so don't use it for versions
# before 2.4.0.
if [ "$FISH_MAJOR" -gt 2 -o \( "$FISH_MAJOR" -eq 2 -a "$FISH_MINOR" -ge 4 \) ];
history -z | eval (__fzfcmd) --read0 -q '(commandline)' | perl -pe 'chomp if eof' | read -lz result
and commandline -- $result
else
history | eval (__fzfcmd) -q '(commandline)' | read -l result
and commandline -- $result
end
end
commandline -f repaint
end
function fzf-cd-widget -d "Change directory"
set -l commandline (__fzf_parse_commandline)
set -l dir $commandline[1]
set -l fzf_query $commandline[2]
set -q FZF_ALT_C_COMMAND; or set -l FZF_ALT_C_COMMAND "
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
-o -type d -print 2> /dev/null | sed 's@^\./@@'"
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
begin
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
if [ -n "$result" ]
cd $result
# Remove last token from commandline.
commandline -t ""
end
end
commandline -f repaint
end
function __fzfcmd
set -q FZF_TMUX; or set FZF_TMUX 0
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
if [ $FZF_TMUX -eq 1 ]
echo "fzf-tmux -d$FZF_TMUX_HEIGHT"
else
echo "fzf"
end
end
bind \ct fzf-file-widget
bind \cr fzf-history-widget
bind \ec fzf-cd-widget
if bind -M insert > /dev/null 2>&1
bind -M insert \ct fzf-file-widget
bind -M insert \cr fzf-history-widget
bind -M insert \ec fzf-cd-widget
end
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath and rest of token'
# eval is used to do shell expansion on paths
set -l commandline (eval "printf '%s' "(commandline -t))
if [ -z $commandline ]
# Default to current directory with no --query
set dir '.'
set fzf_query ''
else
set dir (__fzf_get_dir $commandline)
if [ "$dir" = "." -a (string sub -l 1 $commandline) != '.' ]
# if $dir is "." but commandline is not a relative path, this means no file path found
set fzf_query $commandline
else
# Also remove trailing slash after dir, to "split" input properly
set fzf_query (string replace -r "^$dir/?" '' "$commandline")
end
end
echo $dir
echo $fzf_query
end
function __fzf_get_dir -d 'Find the longest existing filepath from input string'
set dir $argv
# Strip all trailing slashes. Ignore if $dir is root dir (/)
if [ (string length $dir) -gt 1 ]
set dir (string replace -r '/*$' '' $dir)
end
# Iteratively check if dir exists and strip tail end of path
while [ ! -d "$dir" ]
# If path is absolute, this can keep going until ends up at /
# If path is relative, this can keep going until entire input is consumed, dirname returns "."
set dir (dirname "$dir")
end
echo $dir
end
end

88
shell/key-bindings.zsh Normal file
View File

@ -0,0 +1,88 @@
# Key bindings
# ------------
if [[ $- == *i* ]]; then
# CTRL-T - Paste the selected file path(s) into the command line
__fsel() {
local cmd="${FZF_CTRL_T_COMMAND:-"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 d -print \
-o -type l -print 2> /dev/null | cut -b3-"}"
setopt localoptions pipefail 2> /dev/null
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read item; do
echo -n "${(q)item} "
done
local ret=$?
echo
return $ret
}
__fzf_use_tmux__() {
[ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ]
}
__fzfcmd() {
__fzf_use_tmux__ &&
echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
}
fzf-file-widget() {
LBUFFER="${LBUFFER}$(__fsel)"
local ret=$?
zle redisplay
typeset -f zle-line-init >/dev/null && zle zle-line-init
return $ret
}
zle -N fzf-file-widget
bindkey '^T' fzf-file-widget
# Ensure precmds are run after cd
fzf-redraw-prompt() {
local precmd
for precmd in $precmd_functions; do
$precmd
done
zle reset-prompt
}
zle -N fzf-redraw-prompt
# ALT-C - cd into the selected directory
fzf-cd-widget() {
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
-o -type d -print 2> /dev/null | cut -b3-"}"
setopt localoptions pipefail 2> /dev/null
local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m)"
if [[ -z "$dir" ]]; then
zle redisplay
return 0
fi
cd "$dir"
local ret=$?
zle fzf-redraw-prompt
typeset -f zle-line-init >/dev/null && zle zle-line-init
return $ret
}
zle -N fzf-cd-widget
bindkey '\ec' fzf-cd-widget
# CTRL-R - Paste the selected command from history into the command line
fzf-history-widget() {
local selected num
setopt localoptions noglobsubst noposixbuiltins pipefail 2> /dev/null
selected=( $(fc -rl 1 |
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
local ret=$?
if [ -n "$selected" ]; then
num=$selected[1]
if [ -n "$num" ]; then
zle vi-fetch-history -n $num
fi
fi
zle redisplay
typeset -f zle-line-init >/dev/null && zle zle-line-init
return $ret
}
zle -N fzf-history-widget
bindkey '^R' fzf-history-widget
fi

21
src/LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2017 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

859
src/algo/algo.go Normal file
View File

@ -0,0 +1,859 @@
package algo
/*
Algorithm
---------
FuzzyMatchV1 finds the first "fuzzy" occurrence of the pattern within the given
text in O(n) time where n is the length of the text. Once the position of the
last character is located, it traverses backwards to see if there's a shorter
substring that matches the pattern.
a_____b___abc__ To find "abc"
*-----*-----*> 1. Forward scan
<*** 2. Backward scan
The algorithm is simple and fast, but as it only sees the first occurrence,
it is not guaranteed to find the occurrence with the highest score.
a_____b__c__abc
*-----*--* ***
FuzzyMatchV2 implements a modified version of Smith-Waterman algorithm to find
the optimal solution (highest score) according to the scoring criteria. Unlike
the original algorithm, omission or mismatch of a character in the pattern is
not allowed.
Performance
-----------
The new V2 algorithm is slower than V1 as it examines all occurrences of the
pattern instead of stopping immediately after finding the first one. The time
complexity of the algorithm is O(nm) if a match is found and O(n) otherwise
where n is the length of the item and m is the length of the pattern. Thus, the
performance overhead may not be noticeable for a query with high selectivity.
However, if the performance is more important than the quality of the result,
you can still choose v1 algorithm with --algo=v1.
Scoring criteria
----------------
- We prefer matches at special positions, such as the start of a word, or
uppercase character in camelCase words.
- That is, we prefer an occurrence of the pattern with more characters
matching at special positions, even if the total match length is longer.
e.g. "fuzzyfinder" vs. "fuzzy-finder" on "ff"
````````````
- Also, if the first character in the pattern appears at one of the special
positions, the bonus point for the position is multiplied by a constant
as it is extremely likely that the first character in the typed pattern
has more significance than the rest.
e.g. "fo-bar" vs. "foob-r" on "br"
``````
- But since fzf is still a fuzzy finder, not an acronym finder, we should also
consider the total length of the matched substring. This is why we have the
gap penalty. The gap penalty increases as the length of the gap (distance
between the matching characters) increases, so the effect of the bonus is
eventually cancelled at some point.
e.g. "fuzzyfinder" vs. "fuzzy-blurry-finder" on "ff"
```````````
- Consequently, it is crucial to find the right balance between the bonus
and the gap penalty. The parameters were chosen that the bonus is cancelled
when the gap size increases beyond 8 characters.
- The bonus mechanism can have the undesirable side effect where consecutive
matches are ranked lower than the ones with gaps.
e.g. "foobar" vs. "foo-bar" on "foob"
```````
- To correct this anomaly, we also give extra bonus point to each character
in a consecutive matching chunk.
e.g. "foobar" vs. "foo-bar" on "foob"
``````
- The amount of consecutive bonus is primarily determined by the bonus of the
first character in the chunk.
e.g. "foobar" vs. "out-of-bound" on "oob"
````````````
*/
import (
"bytes"
"fmt"
"strings"
"unicode"
"unicode/utf8"
"github.com/junegunn/fzf/src/util"
)
var DEBUG bool
func indexAt(index int, max int, forward bool) int {
if forward {
return index
}
return max - index - 1
}
// Result contains the results of running a match function.
type Result struct {
// TODO int32 should suffice
Start int
End int
Score int
}
const (
scoreMatch = 16
scoreGapStart = -3
scoreGapExtention = -1
// We prefer matches at the beginning of a word, but the bonus should not be
// too great to prevent the longer acronym matches from always winning over
// shorter fuzzy matches. The bonus point here was specifically chosen that
// the bonus is cancelled when the gap between the acronyms grows over
// 8 characters, which is approximately the average length of the words found
// in web2 dictionary and my file system.
bonusBoundary = scoreMatch / 2
// Although bonus point for non-word characters is non-contextual, we need it
// for computing bonus points for consecutive chunks starting with a non-word
// character.
bonusNonWord = scoreMatch / 2
// Edge-triggered bonus for matches in camelCase words.
// Compared to word-boundary case, they don't accompany single-character gaps
// (e.g. FooBar vs. foo-bar), so we deduct bonus point accordingly.
bonusCamel123 = bonusBoundary + scoreGapExtention
// Minimum bonus point given to characters in consecutive chunks.
// Note that bonus points for consecutive matches shouldn't have needed if we
// used fixed match score as in the original algorithm.
bonusConsecutive = -(scoreGapStart + scoreGapExtention)
// The first character in the typed pattern usually has more significance
// than the rest so it's important that it appears at special positions where
// bonus points are given. e.g. "to-go" vs. "ongoing" on "og" or on "ogo".
// The amount of the extra bonus should be limited so that the gap penalty is
// still respected.
bonusFirstCharMultiplier = 2
)
type charClass int
const (
charNonWord charClass = iota
charLower
charUpper
charLetter
charNumber
)
func posArray(withPos bool, len int) *[]int {
if withPos {
pos := make([]int, 0, len)
return &pos
}
return nil
}
func alloc16(offset int, slab *util.Slab, size int) (int, []int16) {
if slab != nil && cap(slab.I16) > offset+size {
slice := slab.I16[offset : offset+size]
return offset + size, slice
}
return offset, make([]int16, size)
}
func alloc32(offset int, slab *util.Slab, size int) (int, []int32) {
if slab != nil && cap(slab.I32) > offset+size {
slice := slab.I32[offset : offset+size]
return offset + size, slice
}
return offset, make([]int32, size)
}
func charClassOfAscii(char rune) charClass {
if char >= 'a' && char <= 'z' {
return charLower
} else if char >= 'A' && char <= 'Z' {
return charUpper
} else if char >= '0' && char <= '9' {
return charNumber
}
return charNonWord
}
func charClassOfNonAscii(char rune) charClass {
if unicode.IsLower(char) {
return charLower
} else if unicode.IsUpper(char) {
return charUpper
} else if unicode.IsNumber(char) {
return charNumber
} else if unicode.IsLetter(char) {
return charLetter
}
return charNonWord
}
func charClassOf(char rune) charClass {
if char <= unicode.MaxASCII {
return charClassOfAscii(char)
}
return charClassOfNonAscii(char)
}
func bonusFor(prevClass charClass, class charClass) int16 {
if prevClass == charNonWord && class != charNonWord {
// Word boundary
return bonusBoundary
} else if prevClass == charLower && class == charUpper ||
prevClass != charNumber && class == charNumber {
// camelCase letter123
return bonusCamel123
} else if class == charNonWord {
return bonusNonWord
}
return 0
}
func bonusAt(input *util.Chars, idx int) int16 {
if idx == 0 {
return bonusBoundary
}
return bonusFor(charClassOf(input.Get(idx-1)), charClassOf(input.Get(idx)))
}
func normalizeRune(r rune) rune {
if r < 0x00C0 || r > 0x2184 {
return r
}
n := normalized[r]
if n > 0 {
return n
}
return r
}
// Algo functions make two assumptions
// 1. "pattern" is given in lowercase if "caseSensitive" is false
// 2. "pattern" is already normalized if "normalize" is true
type Algo func(caseSensitive bool, normalize bool, forward bool, input *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int)
func trySkip(input *util.Chars, caseSensitive bool, b byte, from int) int {
byteArray := input.Bytes()[from:]
idx := bytes.IndexByte(byteArray, b)
if idx == 0 {
// Can't skip any further
return from
}
// We may need to search for the uppercase letter again. We don't have to
// consider normalization as we can be sure that this is an ASCII string.
if !caseSensitive && b >= 'a' && b <= 'z' {
if idx > 0 {
byteArray = byteArray[:idx]
}
uidx := bytes.IndexByte(byteArray, b-32)
if uidx >= 0 {
idx = uidx
}
}
if idx < 0 {
return -1
}
return from + idx
}
func isAscii(runes []rune) bool {
for _, r := range runes {
if r >= utf8.RuneSelf {
return false
}
}
return true
}
func asciiFuzzyIndex(input *util.Chars, pattern []rune, caseSensitive bool) int {
// Can't determine
if !input.IsBytes() {
return 0
}
// Not possible
if !isAscii(pattern) {
return -1
}
firstIdx, idx := 0, 0
for pidx := 0; pidx < len(pattern); pidx++ {
idx = trySkip(input, caseSensitive, byte(pattern[pidx]), idx)
if idx < 0 {
return -1
}
if pidx == 0 && idx > 0 {
// Step back to find the right bonus point
firstIdx = idx - 1
}
idx++
}
return firstIdx
}
func debugV2(T []rune, pattern []rune, F []int32, lastIdx int, H []int16, C []int16) {
width := lastIdx - int(F[0]) + 1
for i, f := range F {
I := i * width
if i == 0 {
fmt.Print(" ")
for j := int(f); j <= lastIdx; j++ {
fmt.Printf(" " + string(T[j]) + " ")
}
fmt.Println()
}
fmt.Print(string(pattern[i]) + " ")
for idx := int(F[0]); idx < int(f); idx++ {
fmt.Print(" 0 ")
}
for idx := int(f); idx <= lastIdx; idx++ {
fmt.Printf("%2d ", H[i*width+idx-int(F[0])])
}
fmt.Println()
fmt.Print(" ")
for idx, p := range C[I : I+width] {
if idx+int(F[0]) < int(F[i]) {
p = 0
}
if p > 0 {
fmt.Printf("%2d ", p)
} else {
fmt.Print(" ")
}
}
fmt.Println()
}
}
func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
// Assume that pattern is given in lowercase if case-insensitive.
// First check if there's a match and calculate bonus for each position.
// If the input string is too long, consider finding the matching chars in
// this phase as well (non-optimal alignment).
M := len(pattern)
if M == 0 {
return Result{0, 0, 0}, posArray(withPos, M)
}
N := input.Length()
// Since O(nm) algorithm can be prohibitively expensive for large input,
// we fall back to the greedy algorithm.
if slab != nil && N*M > cap(slab.I16) {
return FuzzyMatchV1(caseSensitive, normalize, forward, input, pattern, withPos, slab)
}
// Phase 1. Optimized search for ASCII string
idx := asciiFuzzyIndex(input, pattern, caseSensitive)
if idx < 0 {
return Result{-1, -1, 0}, nil
}
// Reuse pre-allocated integer slice to avoid unnecessary sweeping of garbages
offset16 := 0
offset32 := 0
offset16, H0 := alloc16(offset16, slab, N)
offset16, C0 := alloc16(offset16, slab, N)
// Bonus point for each position
offset16, B := alloc16(offset16, slab, N)
// The first occurrence of each character in the pattern
offset32, F := alloc32(offset32, slab, M)
// Rune array
offset32, T := alloc32(offset32, slab, N)
input.CopyRunes(T)
// Phase 2. Calculate bonus for each point
maxScore, maxScorePos := int16(0), 0
pidx, lastIdx := 0, 0
pchar0, pchar, prevH0, prevClass, inGap := pattern[0], pattern[0], int16(0), charNonWord, false
Tsub := T[idx:]
H0sub, C0sub, Bsub := H0[idx:][:len(Tsub)], C0[idx:][:len(Tsub)], B[idx:][:len(Tsub)]
for off, char := range Tsub {
var class charClass
if char <= unicode.MaxASCII {
class = charClassOfAscii(char)
if !caseSensitive && class == charUpper {
char += 32
}
} else {
class = charClassOfNonAscii(char)
if !caseSensitive && class == charUpper {
char = unicode.To(unicode.LowerCase, char)
}
if normalize {
char = normalizeRune(char)
}
}
Tsub[off] = char
bonus := bonusFor(prevClass, class)
Bsub[off] = bonus
prevClass = class
if char == pchar {
if pidx < M {
F[pidx] = int32(idx + off)
pidx++
pchar = pattern[util.Min(pidx, M-1)]
}
lastIdx = idx + off
}
if char == pchar0 {
score := scoreMatch + bonus*bonusFirstCharMultiplier
H0sub[off] = score
C0sub[off] = 1
if M == 1 && (forward && score > maxScore || !forward && score >= maxScore) {
maxScore, maxScorePos = score, idx+off
if forward && bonus == bonusBoundary {
break
}
}
inGap = false
} else {
if inGap {
H0sub[off] = util.Max16(prevH0+scoreGapExtention, 0)
} else {
H0sub[off] = util.Max16(prevH0+scoreGapStart, 0)
}
C0sub[off] = 0
inGap = true
}
prevH0 = H0sub[off]
}
if pidx != M {
return Result{-1, -1, 0}, nil
}
if M == 1 {
result := Result{maxScorePos, maxScorePos + 1, int(maxScore)}
if !withPos {
return result, nil
}
pos := []int{maxScorePos}
return result, &pos
}
// Phase 3. Fill in score matrix (H)
// Unlike the original algorithm, we do not allow omission.
f0 := int(F[0])
width := lastIdx - f0 + 1
offset16, H := alloc16(offset16, slab, width*M)
copy(H, H0[f0:lastIdx+1])
// Possible length of consecutive chunk at each position.
offset16, C := alloc16(offset16, slab, width*M)
copy(C, C0[f0:lastIdx+1])
Fsub := F[1:]
Psub := pattern[1:][:len(Fsub)]
for off, f := range Fsub {
f := int(f)
pchar := Psub[off]
pidx := off + 1
row := pidx * width
inGap := false
Tsub := T[f : lastIdx+1]
Bsub := B[f:][:len(Tsub)]
Csub := C[row+f-f0:][:len(Tsub)]
Cdiag := C[row+f-f0-1-width:][:len(Tsub)]
Hsub := H[row+f-f0:][:len(Tsub)]
Hdiag := H[row+f-f0-1-width:][:len(Tsub)]
Hleft := H[row+f-f0-1:][:len(Tsub)]
Hleft[0] = 0
for off, char := range Tsub {
col := off + f
var s1, s2, consecutive int16
if inGap {
s2 = Hleft[off] + scoreGapExtention
} else {
s2 = Hleft[off] + scoreGapStart
}
if pchar == char {
s1 = Hdiag[off] + scoreMatch
b := Bsub[off]
consecutive = Cdiag[off] + 1
// Break consecutive chunk
if b == bonusBoundary {
consecutive = 1
} else if consecutive > 1 {
b = util.Max16(b, util.Max16(bonusConsecutive, B[col-int(consecutive)+1]))
}
if s1+b < s2 {
s1 += Bsub[off]
consecutive = 0
} else {
s1 += b
}
}
Csub[off] = consecutive
inGap = s1 < s2
score := util.Max16(util.Max16(s1, s2), 0)
if pidx == M-1 && (forward && score > maxScore || !forward && score >= maxScore) {
maxScore, maxScorePos = score, col
}
Hsub[off] = score
}
}
if DEBUG {
debugV2(T, pattern, F, lastIdx, H, C)
}
// Phase 4. (Optional) Backtrace to find character positions
pos := posArray(withPos, M)
j := f0
if withPos {
i := M - 1
j = maxScorePos
preferMatch := true
for {
I := i * width
j0 := j - f0
s := H[I+j0]
var s1, s2 int16
if i > 0 && j >= int(F[i]) {
s1 = H[I-width+j0-1]
}
if j > int(F[i]) {
s2 = H[I+j0-1]
}
if s > s1 && (s > s2 || s == s2 && preferMatch) {
*pos = append(*pos, j)
if i == 0 {
break
}
i--
}
preferMatch = C[I+j0] > 1 || I+width+j0+1 < len(C) && C[I+width+j0+1] > 0
j--
}
}
// Start offset we return here is only relevant when begin tiebreak is used.
// However finding the accurate offset requires backtracking, and we don't
// want to pay extra cost for the option that has lost its importance.
return Result{j, maxScorePos + 1, int(maxScore)}, pos
}
// Implement the same sorting criteria as V2
func calculateScore(caseSensitive bool, normalize bool, text *util.Chars, pattern []rune, sidx int, eidx int, withPos bool) (int, *[]int) {
pidx, score, inGap, consecutive, firstBonus := 0, 0, false, 0, int16(0)
pos := posArray(withPos, len(pattern))
prevClass := charNonWord
if sidx > 0 {
prevClass = charClassOf(text.Get(sidx - 1))
}
for idx := sidx; idx < eidx; idx++ {
char := text.Get(idx)
class := charClassOf(char)
if !caseSensitive {
if char >= 'A' && char <= 'Z' {
char += 32
} else if char > unicode.MaxASCII {
char = unicode.To(unicode.LowerCase, char)
}
}
// pattern is already normalized
if normalize {
char = normalizeRune(char)
}
if char == pattern[pidx] {
if withPos {
*pos = append(*pos, idx)
}
score += scoreMatch
bonus := bonusFor(prevClass, class)
if consecutive == 0 {
firstBonus = bonus
} else {
// Break consecutive chunk
if bonus == bonusBoundary {
firstBonus = bonus
}
bonus = util.Max16(util.Max16(bonus, firstBonus), bonusConsecutive)
}
if pidx == 0 {
score += int(bonus * bonusFirstCharMultiplier)
} else {
score += int(bonus)
}
inGap = false
consecutive++
pidx++
} else {
if inGap {
score += scoreGapExtention
} else {
score += scoreGapStart
}
inGap = true
consecutive = 0
firstBonus = 0
}
prevClass = class
}
return score, pos
}
// FuzzyMatchV1 performs fuzzy-match
func FuzzyMatchV1(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
if len(pattern) == 0 {
return Result{0, 0, 0}, nil
}
if asciiFuzzyIndex(text, pattern, caseSensitive) < 0 {
return Result{-1, -1, 0}, nil
}
pidx := 0
sidx := -1
eidx := -1
lenRunes := text.Length()
lenPattern := len(pattern)
for index := 0; index < lenRunes; index++ {
char := text.Get(indexAt(index, lenRunes, forward))
// This is considerably faster than blindly applying strings.ToLower to the
// whole string
if !caseSensitive {
// Partially inlining `unicode.ToLower`. Ugly, but makes a noticeable
// difference in CPU cost. (Measured on Go 1.4.1. Also note that the Go
// compiler as of now does not inline non-leaf functions.)
if char >= 'A' && char <= 'Z' {
char += 32
} else if char > unicode.MaxASCII {
char = unicode.To(unicode.LowerCase, char)
}
}
if normalize {
char = normalizeRune(char)
}
pchar := pattern[indexAt(pidx, lenPattern, forward)]
if char == pchar {
if sidx < 0 {
sidx = index
}
if pidx++; pidx == lenPattern {
eidx = index + 1
break
}
}
}
if sidx >= 0 && eidx >= 0 {
pidx--
for index := eidx - 1; index >= sidx; index-- {
tidx := indexAt(index, lenRunes, forward)
char := text.Get(tidx)
if !caseSensitive {
if char >= 'A' && char <= 'Z' {
char += 32
} else if char > unicode.MaxASCII {
char = unicode.To(unicode.LowerCase, char)
}
}
pidx_ := indexAt(pidx, lenPattern, forward)
pchar := pattern[pidx_]
if char == pchar {
if pidx--; pidx < 0 {
sidx = index
break
}
}
}
if !forward {
sidx, eidx = lenRunes-eidx, lenRunes-sidx
}
score, pos := calculateScore(caseSensitive, normalize, text, pattern, sidx, eidx, withPos)
return Result{sidx, eidx, score}, pos
}
return Result{-1, -1, 0}, nil
}
// ExactMatchNaive is a basic string searching algorithm that handles case
// sensitivity. Although naive, it still performs better than the combination
// of strings.ToLower + strings.Index for typical fzf use cases where input
// strings and patterns are not very long.
//
// Since 0.15.0, this function searches for the match with the highest
// bonus point, instead of stopping immediately after finding the first match.
// The solution is much cheaper since there is only one possible alignment of
// the pattern.
func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
if len(pattern) == 0 {
return Result{0, 0, 0}, nil
}
lenRunes := text.Length()
lenPattern := len(pattern)
if lenRunes < lenPattern {
return Result{-1, -1, 0}, nil
}
if asciiFuzzyIndex(text, pattern, caseSensitive) < 0 {
return Result{-1, -1, 0}, nil
}
// For simplicity, only look at the bonus at the first character position
pidx := 0
bestPos, bonus, bestBonus := -1, int16(0), int16(-1)
for index := 0; index < lenRunes; index++ {
index_ := indexAt(index, lenRunes, forward)
char := text.Get(index_)
if !caseSensitive {
if char >= 'A' && char <= 'Z' {
char += 32
} else if char > unicode.MaxASCII {
char = unicode.To(unicode.LowerCase, char)
}
}
if normalize {
char = normalizeRune(char)
}
pidx_ := indexAt(pidx, lenPattern, forward)
pchar := pattern[pidx_]
if pchar == char {
if pidx_ == 0 {
bonus = bonusAt(text, index_)
}
pidx++
if pidx == lenPattern {
if bonus > bestBonus {
bestPos, bestBonus = index, bonus
}
if bonus == bonusBoundary {
break
}
index -= pidx - 1
pidx, bonus = 0, 0
}
} else {
index -= pidx
pidx, bonus = 0, 0
}
}
if bestPos >= 0 {
var sidx, eidx int
if forward {
sidx = bestPos - lenPattern + 1
eidx = bestPos + 1
} else {
sidx = lenRunes - (bestPos + 1)
eidx = lenRunes - (bestPos - lenPattern + 1)
}
score, _ := calculateScore(caseSensitive, normalize, text, pattern, sidx, eidx, false)
return Result{sidx, eidx, score}, nil
}
return Result{-1, -1, 0}, nil
}
// PrefixMatch performs prefix-match
func PrefixMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
if len(pattern) == 0 {
return Result{0, 0, 0}, nil
}
if text.Length() < len(pattern) {
return Result{-1, -1, 0}, nil
}
for index, r := range pattern {
char := text.Get(index)
if !caseSensitive {
char = unicode.ToLower(char)
}
if normalize {
char = normalizeRune(char)
}
if char != r {
return Result{-1, -1, 0}, nil
}
}
lenPattern := len(pattern)
score, _ := calculateScore(caseSensitive, normalize, text, pattern, 0, lenPattern, false)
return Result{0, lenPattern, score}, nil
}
// SuffixMatch performs suffix-match
func SuffixMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
lenRunes := text.Length()
trimmedLen := lenRunes - text.TrailingWhitespaces()
if len(pattern) == 0 {
return Result{trimmedLen, trimmedLen, 0}, nil
}
diff := trimmedLen - len(pattern)
if diff < 0 {
return Result{-1, -1, 0}, nil
}
for index, r := range pattern {
char := text.Get(index + diff)
if !caseSensitive {
char = unicode.ToLower(char)
}
if normalize {
char = normalizeRune(char)
}
if char != r {
return Result{-1, -1, 0}, nil
}
}
lenPattern := len(pattern)
sidx := trimmedLen - lenPattern
eidx := trimmedLen
score, _ := calculateScore(caseSensitive, normalize, text, pattern, sidx, eidx, false)
return Result{sidx, eidx, score}, nil
}
// EqualMatch performs equal-match
func EqualMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
lenPattern := len(pattern)
if text.Length() != lenPattern {
return Result{-1, -1, 0}, nil
}
match := true
if normalize {
runes := text.ToRunes()
for idx, pchar := range pattern {
char := runes[idx]
if !caseSensitive {
char = unicode.To(unicode.LowerCase, char)
}
if normalizeRune(pchar) != normalizeRune(char) {
match = false
break
}
}
} else {
runesStr := text.ToString()
if !caseSensitive {
runesStr = strings.ToLower(runesStr)
}
match = runesStr == string(pattern)
}
if match {
return Result{0, lenPattern, (scoreMatch+bonusBoundary)*lenPattern +
(bonusFirstCharMultiplier-1)*bonusBoundary}, nil
}
return Result{-1, -1, 0}, nil
}

186
src/algo/algo_test.go Normal file
View File

@ -0,0 +1,186 @@
package algo
import (
"math"
"sort"
"strings"
"testing"
"github.com/junegunn/fzf/src/util"
)
func assertMatch(t *testing.T, fun Algo, caseSensitive, forward bool, input, pattern string, sidx int, eidx int, score int) {
assertMatch2(t, fun, caseSensitive, false, forward, input, pattern, sidx, eidx, score)
}
func assertMatch2(t *testing.T, fun Algo, caseSensitive, normalize, forward bool, input, pattern string, sidx int, eidx int, score int) {
if !caseSensitive {
pattern = strings.ToLower(pattern)
}
chars := util.ToChars([]byte(input))
res, pos := fun(caseSensitive, normalize, forward, &chars, []rune(pattern), true, nil)
var start, end int
if pos == nil || len(*pos) == 0 {
start = res.Start
end = res.End
} else {
sort.Ints(*pos)
start = (*pos)[0]
end = (*pos)[len(*pos)-1] + 1
}
if start != sidx {
t.Errorf("Invalid start index: %d (expected: %d, %s / %s)", start, sidx, input, pattern)
}
if end != eidx {
t.Errorf("Invalid end index: %d (expected: %d, %s / %s)", end, eidx, input, pattern)
}
if res.Score != score {
t.Errorf("Invalid score: %d (expected: %d, %s / %s)", res.Score, score, input, pattern)
}
}
func TestFuzzyMatch(t *testing.T) {
for _, fn := range []Algo{FuzzyMatchV1, FuzzyMatchV2} {
for _, forward := range []bool{true, false} {
assertMatch(t, fn, false, forward, "fooBarbaz1", "oBZ", 2, 9,
scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtention*3)
assertMatch(t, fn, false, forward, "foo bar baz", "fbb", 0, 9,
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+
bonusBoundary*2+2*scoreGapStart+4*scoreGapExtention)
assertMatch(t, fn, false, forward, "/AutomatorDocument.icns", "rdoc", 9, 13,
scoreMatch*4+bonusCamel123+bonusConsecutive*2)
assertMatch(t, fn, false, forward, "/man1/zshcompctl.1", "zshc", 6, 10,
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*3)
assertMatch(t, fn, false, forward, "/.oh-my-zsh/cache", "zshc", 8, 13,
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*3+scoreGapStart)
assertMatch(t, fn, false, forward, "ab0123 456", "12356", 3, 10,
scoreMatch*5+bonusConsecutive*3+scoreGapStart+scoreGapExtention)
assertMatch(t, fn, false, forward, "abc123 456", "12356", 3, 10,
scoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+bonusConsecutive+scoreGapStart+scoreGapExtention)
assertMatch(t, fn, false, forward, "foo/bar/baz", "fbb", 0, 9,
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+
bonusBoundary*2+2*scoreGapStart+4*scoreGapExtention)
assertMatch(t, fn, false, forward, "fooBarBaz", "fbb", 0, 7,
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+
bonusCamel123*2+2*scoreGapStart+2*scoreGapExtention)
assertMatch(t, fn, false, forward, "foo barbaz", "fbb", 0, 8,
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary+
scoreGapStart*2+scoreGapExtention*3)
assertMatch(t, fn, false, forward, "fooBar Baz", "foob", 0, 4,
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*3)
assertMatch(t, fn, false, forward, "xFoo-Bar Baz", "foo-b", 1, 6,
scoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+
bonusNonWord+bonusBoundary)
assertMatch(t, fn, true, forward, "fooBarbaz", "oBz", 2, 9,
scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtention*3)
assertMatch(t, fn, true, forward, "Foo/Bar/Baz", "FBB", 0, 9,
scoreMatch*3+bonusBoundary*(bonusFirstCharMultiplier+2)+
scoreGapStart*2+scoreGapExtention*4)
assertMatch(t, fn, true, forward, "FooBarBaz", "FBB", 0, 7,
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+bonusCamel123*2+
scoreGapStart*2+scoreGapExtention*2)
assertMatch(t, fn, true, forward, "FooBar Baz", "FooB", 0, 4,
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*2+
util.Max(bonusCamel123, bonusBoundary))
// Consecutive bonus updated
assertMatch(t, fn, true, forward, "foo-bar", "o-ba", 2, 6,
scoreMatch*4+bonusBoundary*3)
// Non-match
assertMatch(t, fn, true, forward, "fooBarbaz", "oBZ", -1, -1, 0)
assertMatch(t, fn, true, forward, "Foo Bar Baz", "fbb", -1, -1, 0)
assertMatch(t, fn, true, forward, "fooBarbaz", "fooBarbazz", -1, -1, 0)
}
}
}
func TestFuzzyMatchBackward(t *testing.T) {
assertMatch(t, FuzzyMatchV1, false, true, "foobar fb", "fb", 0, 4,
scoreMatch*2+bonusBoundary*bonusFirstCharMultiplier+
scoreGapStart+scoreGapExtention)
assertMatch(t, FuzzyMatchV1, false, false, "foobar fb", "fb", 7, 9,
scoreMatch*2+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary)
}
func TestExactMatchNaive(t *testing.T) {
for _, dir := range []bool{true, false} {
assertMatch(t, ExactMatchNaive, true, dir, "fooBarbaz", "oBA", -1, -1, 0)
assertMatch(t, ExactMatchNaive, true, dir, "fooBarbaz", "fooBarbazz", -1, -1, 0)
assertMatch(t, ExactMatchNaive, false, dir, "fooBarbaz", "oBA", 2, 5,
scoreMatch*3+bonusCamel123+bonusConsecutive)
assertMatch(t, ExactMatchNaive, false, dir, "/AutomatorDocument.icns", "rdoc", 9, 13,
scoreMatch*4+bonusCamel123+bonusConsecutive*2)
assertMatch(t, ExactMatchNaive, false, dir, "/man1/zshcompctl.1", "zshc", 6, 10,
scoreMatch*4+bonusBoundary*(bonusFirstCharMultiplier+3))
assertMatch(t, ExactMatchNaive, false, dir, "/.oh-my-zsh/cache", "zsh/c", 8, 13,
scoreMatch*5+bonusBoundary*(bonusFirstCharMultiplier+4))
}
}
func TestExactMatchNaiveBackward(t *testing.T) {
assertMatch(t, ExactMatchNaive, false, true, "foobar foob", "oo", 1, 3,
scoreMatch*2+bonusConsecutive)
assertMatch(t, ExactMatchNaive, false, false, "foobar foob", "oo", 8, 10,
scoreMatch*2+bonusConsecutive)
}
func TestPrefixMatch(t *testing.T) {
score := (scoreMatch+bonusBoundary)*3 + bonusBoundary*(bonusFirstCharMultiplier-1)
for _, dir := range []bool{true, false} {
assertMatch(t, PrefixMatch, true, dir, "fooBarbaz", "Foo", -1, -1, 0)
assertMatch(t, PrefixMatch, false, dir, "fooBarBaz", "baz", -1, -1, 0)
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)
}
}
func TestSuffixMatch(t *testing.T) {
for _, dir := range []bool{true, false} {
assertMatch(t, SuffixMatch, true, dir, "fooBarbaz", "Baz", -1, -1, 0)
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz", "Foo", -1, -1, 0)
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz", "baz", 6, 9,
scoreMatch*3+bonusConsecutive*2)
assertMatch(t, SuffixMatch, false, dir, "fooBarBaZ", "baz", 6, 9,
(scoreMatch+bonusCamel123)*3+bonusCamel123*(bonusFirstCharMultiplier-1))
}
}
func TestEmptyPattern(t *testing.T) {
for _, dir := range []bool{true, false} {
assertMatch(t, FuzzyMatchV1, true, dir, "foobar", "", 0, 0, 0)
assertMatch(t, FuzzyMatchV2, true, dir, "foobar", "", 0, 0, 0)
assertMatch(t, ExactMatchNaive, true, dir, "foobar", "", 0, 0, 0)
assertMatch(t, PrefixMatch, true, dir, "foobar", "", 0, 0, 0)
assertMatch(t, SuffixMatch, true, dir, "foobar", "", 6, 6, 0)
}
}
func TestNormalize(t *testing.T) {
caseSensitive := false
normalize := true
forward := true
test := func(input, pattern string, sidx, eidx, score int, funs ...Algo) {
for _, fun := range funs {
assertMatch2(t, fun, caseSensitive, normalize, forward,
input, pattern, sidx, eidx, score)
}
}
test("Só Danço Samba", "So", 0, 2, 56, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, ExactMatchNaive)
test("Só Danço Samba", "sodc", 0, 7, 89, FuzzyMatchV1, FuzzyMatchV2)
test("Danço", "danco", 0, 5, 128, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, SuffixMatch, ExactMatchNaive, EqualMatch)
}
func TestLongString(t *testing.T) {
bytes := make([]byte, math.MaxUint16*2)
for i := range bytes {
bytes[i] = 'x'
}
bytes[math.MaxUint16] = 'z'
assertMatch(t, FuzzyMatchV2, true, true, string(bytes), "zx", math.MaxUint16, math.MaxUint16+2, scoreMatch*2+bonusConsecutive)
}

424
src/algo/normalize.go Normal file
View File

@ -0,0 +1,424 @@
// Normalization of latin script letters
// Reference: http://www.unicode.org/Public/UCD/latest/ucd/Index.txt
package algo
var normalized map[rune]rune = map[rune]rune{
0x00E1: 'a', // WITH ACUTE, LATIN SMALL LETTER
0x0103: 'a', // WITH BREVE, LATIN SMALL LETTER
0x01CE: 'a', // WITH CARON, LATIN SMALL LETTER
0x00E2: 'a', // WITH CIRCUMFLEX, LATIN SMALL LETTER
0x00E4: 'a', // WITH DIAERESIS, LATIN SMALL LETTER
0x0227: 'a', // WITH DOT ABOVE, LATIN SMALL LETTER
0x1EA1: 'a', // WITH DOT BELOW, LATIN SMALL LETTER
0x0201: 'a', // WITH DOUBLE GRAVE, LATIN SMALL LETTER
0x00E0: 'a', // WITH GRAVE, LATIN SMALL LETTER
0x1EA3: 'a', // WITH HOOK ABOVE, LATIN SMALL LETTER
0x0203: 'a', // WITH INVERTED BREVE, LATIN SMALL LETTER
0x0101: 'a', // WITH MACRON, LATIN SMALL LETTER
0x0105: 'a', // WITH OGONEK, LATIN SMALL LETTER
0x1E9A: 'a', // WITH RIGHT HALF RING, LATIN SMALL LETTER
0x00E5: 'a', // WITH RING ABOVE, LATIN SMALL LETTER
0x1E01: 'a', // WITH RING BELOW, LATIN SMALL LETTER
0x00E3: 'a', // WITH TILDE, LATIN SMALL LETTER
0x0363: 'a', // , COMBINING LATIN SMALL LETTER
0x0250: 'a', // , LATIN SMALL LETTER TURNED
0x1E03: 'b', // WITH DOT ABOVE, LATIN SMALL LETTER
0x1E05: 'b', // WITH DOT BELOW, LATIN SMALL LETTER
0x0253: 'b', // WITH HOOK, LATIN SMALL LETTER
0x1E07: 'b', // WITH LINE BELOW, LATIN SMALL LETTER
0x0180: 'b', // WITH STROKE, LATIN SMALL LETTER
0x0183: 'b', // WITH TOPBAR, LATIN SMALL LETTER
0x0107: 'c', // WITH ACUTE, LATIN SMALL LETTER
0x010D: 'c', // WITH CARON, LATIN SMALL LETTER
0x00E7: 'c', // WITH CEDILLA, LATIN SMALL LETTER
0x0109: 'c', // WITH CIRCUMFLEX, LATIN SMALL LETTER
0x0255: 'c', // WITH CURL, LATIN SMALL LETTER
0x010B: 'c', // WITH DOT ABOVE, LATIN SMALL LETTER
0x0188: 'c', // WITH HOOK, LATIN SMALL LETTER
0x023C: 'c', // WITH STROKE, LATIN SMALL LETTER
0x0368: 'c', // , COMBINING LATIN SMALL LETTER
0x0297: 'c', // , LATIN LETTER STRETCHED
0x2184: 'c', // , LATIN SMALL LETTER REVERSED
0x010F: 'd', // WITH CARON, LATIN SMALL LETTER
0x1E11: 'd', // WITH CEDILLA, LATIN SMALL LETTER
0x1E13: 'd', // WITH CIRCUMFLEX BELOW, LATIN SMALL LETTER
0x0221: 'd', // WITH CURL, LATIN SMALL LETTER
0x1E0B: 'd', // WITH DOT ABOVE, LATIN SMALL LETTER
0x1E0D: 'd', // WITH DOT BELOW, LATIN SMALL LETTER
0x0257: 'd', // WITH HOOK, LATIN SMALL LETTER
0x1E0F: 'd', // WITH LINE BELOW, LATIN SMALL LETTER
0x0111: 'd', // WITH STROKE, LATIN SMALL LETTER
0x0256: 'd', // WITH TAIL, LATIN SMALL LETTER
0x018C: 'd', // WITH TOPBAR, LATIN SMALL LETTER
0x0369: 'd', // , COMBINING LATIN SMALL LETTER
0x00E9: 'e', // WITH ACUTE, LATIN SMALL LETTER
0x0115: 'e', // WITH BREVE, LATIN SMALL LETTER
0x011B: 'e', // WITH CARON, LATIN SMALL LETTER
0x0229: 'e', // WITH CEDILLA, LATIN SMALL LETTER
0x1E19: 'e', // WITH CIRCUMFLEX BELOW, LATIN SMALL LETTER
0x00EA: 'e', // WITH CIRCUMFLEX, LATIN SMALL LETTER
0x00EB: 'e', // WITH DIAERESIS, LATIN SMALL LETTER
0x0117: 'e', // WITH DOT ABOVE, LATIN SMALL LETTER
0x1EB9: 'e', // WITH DOT BELOW, LATIN SMALL LETTER
0x0205: 'e', // WITH DOUBLE GRAVE, LATIN SMALL LETTER
0x00E8: 'e', // WITH GRAVE, LATIN SMALL LETTER
0x1EBB: 'e', // WITH HOOK ABOVE, LATIN SMALL LETTER
0x025D: 'e', // WITH HOOK, LATIN SMALL LETTER REVERSED OPEN
0x0207: 'e', // WITH INVERTED BREVE, LATIN SMALL LETTER
0x0113: 'e', // WITH MACRON, LATIN SMALL LETTER
0x0119: 'e', // WITH OGONEK, LATIN SMALL LETTER
0x0247: 'e', // WITH STROKE, LATIN SMALL LETTER
0x1E1B: 'e', // WITH TILDE BELOW, LATIN SMALL LETTER
0x1EBD: 'e', // WITH TILDE, LATIN SMALL LETTER
0x0364: 'e', // , COMBINING LATIN SMALL LETTER
0x029A: 'e', // , LATIN SMALL LETTER CLOSED OPEN
0x025E: 'e', // , LATIN SMALL LETTER CLOSED REVERSED OPEN
0x025B: 'e', // , LATIN SMALL LETTER OPEN
0x0258: 'e', // , LATIN SMALL LETTER REVERSED
0x025C: 'e', // , LATIN SMALL LETTER REVERSED OPEN
0x01DD: 'e', // , LATIN SMALL LETTER TURNED
0x1D08: 'e', // , LATIN SMALL LETTER TURNED OPEN
0x1E1F: 'f', // WITH DOT ABOVE, LATIN SMALL LETTER
0x0192: 'f', // WITH HOOK, LATIN SMALL LETTER
0x01F5: 'g', // WITH ACUTE, LATIN SMALL LETTER
0x011F: 'g', // WITH BREVE, LATIN SMALL LETTER
0x01E7: 'g', // WITH CARON, LATIN SMALL LETTER
0x0123: 'g', // WITH CEDILLA, LATIN SMALL LETTER
0x011D: 'g', // WITH CIRCUMFLEX, LATIN SMALL LETTER
0x0121: 'g', // WITH DOT ABOVE, LATIN SMALL LETTER
0x0260: 'g', // WITH HOOK, LATIN SMALL LETTER
0x1E21: 'g', // WITH MACRON, LATIN SMALL LETTER
0x01E5: 'g', // WITH STROKE, LATIN SMALL LETTER
0x0261: 'g', // , LATIN SMALL LETTER SCRIPT
0x1E2B: 'h', // WITH BREVE BELOW, LATIN SMALL LETTER
0x021F: 'h', // WITH CARON, LATIN SMALL LETTER
0x1E29: 'h', // WITH CEDILLA, LATIN SMALL LETTER
0x0125: 'h', // WITH CIRCUMFLEX, LATIN SMALL LETTER
0x1E27: 'h', // WITH DIAERESIS, LATIN SMALL LETTER
0x1E23: 'h', // WITH DOT ABOVE, LATIN SMALL LETTER
0x1E25: 'h', // WITH DOT BELOW, LATIN SMALL LETTER
0x02AE: 'h', // WITH FISHHOOK, LATIN SMALL LETTER TURNED
0x0266: 'h', // WITH HOOK, LATIN SMALL LETTER
0x1E96: 'h', // WITH LINE BELOW, LATIN SMALL LETTER
0x0127: 'h', // WITH STROKE, LATIN SMALL LETTER
0x036A: 'h', // , COMBINING LATIN SMALL LETTER
0x0265: 'h', // , LATIN SMALL LETTER TURNED
0x2095: 'h', // , LATIN SUBSCRIPT SMALL LETTER
0x00ED: 'i', // WITH ACUTE, LATIN SMALL LETTER
0x012D: 'i', // WITH BREVE, LATIN SMALL LETTER
0x01D0: 'i', // WITH CARON, LATIN SMALL LETTER
0x00EE: 'i', // WITH CIRCUMFLEX, LATIN SMALL LETTER
0x00EF: 'i', // WITH DIAERESIS, LATIN SMALL LETTER
0x1ECB: 'i', // WITH DOT BELOW, LATIN SMALL LETTER
0x0209: 'i', // WITH DOUBLE GRAVE, LATIN SMALL LETTER
0x00EC: 'i', // WITH GRAVE, LATIN SMALL LETTER
0x1EC9: 'i', // WITH HOOK ABOVE, LATIN SMALL LETTER
0x020B: 'i', // WITH INVERTED BREVE, LATIN SMALL LETTER
0x012B: 'i', // WITH MACRON, LATIN SMALL LETTER
0x012F: 'i', // WITH OGONEK, LATIN SMALL LETTER
0x0268: 'i', // WITH STROKE, LATIN SMALL LETTER
0x1E2D: 'i', // WITH TILDE BELOW, LATIN SMALL LETTER
0x0129: 'i', // WITH TILDE, LATIN SMALL LETTER
0x0365: 'i', // , COMBINING LATIN SMALL LETTER
0x0131: 'i', // , LATIN SMALL LETTER DOTLESS
0x1D09: 'i', // , LATIN SMALL LETTER TURNED
0x1D62: 'i', // , LATIN SUBSCRIPT SMALL LETTER
0x2071: 'i', // , SUPERSCRIPT LATIN SMALL LETTER
0x01F0: 'j', // WITH CARON, LATIN SMALL LETTER
0x0135: 'j', // WITH CIRCUMFLEX, LATIN SMALL LETTER
0x029D: 'j', // WITH CROSSED-TAIL, LATIN SMALL LETTER
0x0249: 'j', // WITH STROKE, LATIN SMALL LETTER
0x025F: 'j', // WITH STROKE, LATIN SMALL LETTER DOTLESS
0x0237: 'j', // , LATIN SMALL LETTER DOTLESS
0x1E31: 'k', // WITH ACUTE, LATIN SMALL LETTER
0x01E9: 'k', // WITH CARON, LATIN SMALL LETTER
0x0137: 'k', // WITH CEDILLA, LATIN SMALL LETTER
0x1E33: 'k', // WITH DOT BELOW, LATIN SMALL LETTER
0x0199: 'k', // WITH HOOK, LATIN SMALL LETTER
0x1E35: 'k', // WITH LINE BELOW, LATIN SMALL LETTER
0x029E: 'k', // , LATIN SMALL LETTER TURNED
0x2096: 'k', // , LATIN SUBSCRIPT SMALL LETTER
0x013A: 'l', // WITH ACUTE, LATIN SMALL LETTER
0x019A: 'l', // WITH BAR, LATIN SMALL LETTER
0x026C: 'l', // WITH BELT, LATIN SMALL LETTER
0x013E: 'l', // WITH CARON, LATIN SMALL LETTER
0x013C: 'l', // WITH CEDILLA, LATIN SMALL LETTER
0x1E3D: 'l', // WITH CIRCUMFLEX BELOW, LATIN SMALL LETTER
0x0234: 'l', // WITH CURL, LATIN SMALL LETTER
0x1E37: 'l', // WITH DOT BELOW, LATIN SMALL LETTER
0x1E3B: 'l', // WITH LINE BELOW, LATIN SMALL LETTER
0x0140: 'l', // WITH MIDDLE DOT, LATIN SMALL LETTER
0x026B: 'l', // WITH MIDDLE TILDE, LATIN SMALL LETTER
0x026D: 'l', // WITH RETROFLEX HOOK, LATIN SMALL LETTER
0x0142: 'l', // WITH STROKE, LATIN SMALL LETTER
0x2097: 'l', // , LATIN SUBSCRIPT SMALL LETTER
0x1E3F: 'm', // WITH ACUTE, LATIN SMALL LETTER
0x1E41: 'm', // WITH DOT ABOVE, LATIN SMALL LETTER
0x1E43: 'm', // WITH DOT BELOW, LATIN SMALL LETTER
0x0271: 'm', // WITH HOOK, LATIN SMALL LETTER
0x0270: 'm', // WITH LONG LEG, LATIN SMALL LETTER TURNED
0x036B: 'm', // , COMBINING LATIN SMALL LETTER
0x1D1F: 'm', // , LATIN SMALL LETTER SIDEWAYS TURNED
0x026F: 'm', // , LATIN SMALL LETTER TURNED
0x2098: 'm', // , LATIN SUBSCRIPT SMALL LETTER
0x0144: 'n', // WITH ACUTE, LATIN SMALL LETTER
0x0148: 'n', // WITH CARON, LATIN SMALL LETTER
0x0146: 'n', // WITH CEDILLA, LATIN SMALL LETTER
0x1E4B: 'n', // WITH CIRCUMFLEX BELOW, LATIN SMALL LETTER
0x0235: 'n', // WITH CURL, LATIN SMALL LETTER
0x1E45: 'n', // WITH DOT ABOVE, LATIN SMALL LETTER
0x1E47: 'n', // WITH DOT BELOW, LATIN SMALL LETTER
0x01F9: 'n', // WITH GRAVE, LATIN SMALL LETTER
0x0272: 'n', // WITH LEFT HOOK, LATIN SMALL LETTER
0x1E49: 'n', // WITH LINE BELOW, LATIN SMALL LETTER
0x019E: 'n', // WITH LONG RIGHT LEG, LATIN SMALL LETTER
0x0273: 'n', // WITH RETROFLEX HOOK, LATIN SMALL LETTER
0x00F1: 'n', // WITH TILDE, LATIN SMALL LETTER
0x2099: 'n', // , LATIN SUBSCRIPT SMALL LETTER
0x00F3: 'o', // WITH ACUTE, LATIN SMALL LETTER
0x014F: 'o', // WITH BREVE, LATIN SMALL LETTER
0x01D2: 'o', // WITH CARON, LATIN SMALL LETTER
0x00F4: 'o', // WITH CIRCUMFLEX, LATIN SMALL LETTER
0x00F6: 'o', // WITH DIAERESIS, LATIN SMALL LETTER
0x022F: 'o', // WITH DOT ABOVE, LATIN SMALL LETTER
0x1ECD: 'o', // WITH DOT BELOW, LATIN SMALL LETTER
0x0151: 'o', // WITH DOUBLE ACUTE, LATIN SMALL LETTER
0x020D: 'o', // WITH DOUBLE GRAVE, LATIN SMALL LETTER
0x00F2: 'o', // WITH GRAVE, LATIN SMALL LETTER
0x1ECF: 'o', // WITH HOOK ABOVE, LATIN SMALL LETTER
0x01A1: 'o', // WITH HORN, LATIN SMALL LETTER
0x020F: 'o', // WITH INVERTED BREVE, LATIN SMALL LETTER
0x014D: 'o', // WITH MACRON, LATIN SMALL LETTER
0x01EB: 'o', // WITH OGONEK, LATIN SMALL LETTER
0x00F8: 'o', // WITH STROKE, LATIN SMALL LETTER
0x1D13: 'o', // WITH STROKE, LATIN SMALL LETTER SIDEWAYS
0x00F5: 'o', // WITH TILDE, LATIN SMALL LETTER
0x0366: 'o', // , COMBINING LATIN SMALL LETTER
0x0275: 'o', // , LATIN SMALL LETTER BARRED
0x1D17: 'o', // , LATIN SMALL LETTER BOTTOM HALF
0x0254: 'o', // , LATIN SMALL LETTER OPEN
0x1D11: 'o', // , LATIN SMALL LETTER SIDEWAYS
0x1D12: 'o', // , LATIN SMALL LETTER SIDEWAYS OPEN
0x1D16: 'o', // , LATIN SMALL LETTER TOP HALF
0x1E55: 'p', // WITH ACUTE, LATIN SMALL LETTER
0x1E57: 'p', // WITH DOT ABOVE, LATIN SMALL LETTER
0x01A5: 'p', // WITH HOOK, LATIN SMALL LETTER
0x209A: 'p', // , LATIN SUBSCRIPT SMALL LETTER
0x024B: 'q', // WITH HOOK TAIL, LATIN SMALL LETTER
0x02A0: 'q', // WITH HOOK, LATIN SMALL LETTER
0x0155: 'r', // WITH ACUTE, LATIN SMALL LETTER
0x0159: 'r', // WITH CARON, LATIN SMALL LETTER
0x0157: 'r', // WITH CEDILLA, LATIN SMALL LETTER
0x1E59: 'r', // WITH DOT ABOVE, LATIN SMALL LETTER
0x1E5B: 'r', // WITH DOT BELOW, LATIN SMALL LETTER
0x0211: 'r', // WITH DOUBLE GRAVE, LATIN SMALL LETTER
0x027E: 'r', // WITH FISHHOOK, LATIN SMALL LETTER
0x027F: 'r', // WITH FISHHOOK, LATIN SMALL LETTER REVERSED
0x027B: 'r', // WITH HOOK, LATIN SMALL LETTER TURNED
0x0213: 'r', // WITH INVERTED BREVE, LATIN SMALL LETTER
0x1E5F: 'r', // WITH LINE BELOW, LATIN SMALL LETTER
0x027C: 'r', // WITH LONG LEG, LATIN SMALL LETTER
0x027A: 'r', // WITH LONG LEG, LATIN SMALL LETTER TURNED
0x024D: 'r', // WITH STROKE, LATIN SMALL LETTER
0x027D: 'r', // WITH TAIL, LATIN SMALL LETTER
0x036C: 'r', // , COMBINING LATIN SMALL LETTER
0x0279: 'r', // , LATIN SMALL LETTER TURNED
0x1D63: 'r', // , LATIN SUBSCRIPT SMALL LETTER
0x015B: 's', // WITH ACUTE, LATIN SMALL LETTER
0x0161: 's', // WITH CARON, LATIN SMALL LETTER
0x015F: 's', // WITH CEDILLA, LATIN SMALL LETTER
0x015D: 's', // WITH CIRCUMFLEX, LATIN SMALL LETTER
0x0219: 's', // WITH COMMA BELOW, LATIN SMALL LETTER
0x1E61: 's', // WITH DOT ABOVE, LATIN SMALL LETTER
0x1E9B: 's', // WITH DOT ABOVE, LATIN SMALL LETTER LONG
0x1E63: 's', // WITH DOT BELOW, LATIN SMALL LETTER
0x0282: 's', // WITH HOOK, LATIN SMALL LETTER
0x023F: 's', // WITH SWASH TAIL, LATIN SMALL LETTER
0x017F: 's', // , LATIN SMALL LETTER LONG
0x00DF: 's', // , LATIN SMALL LETTER SHARP
0x209B: 's', // , LATIN SUBSCRIPT SMALL LETTER
0x0165: 't', // WITH CARON, LATIN SMALL LETTER
0x0163: 't', // WITH CEDILLA, LATIN SMALL LETTER
0x1E71: 't', // WITH CIRCUMFLEX BELOW, LATIN SMALL LETTER
0x021B: 't', // WITH COMMA BELOW, LATIN SMALL LETTER
0x0236: 't', // WITH CURL, LATIN SMALL LETTER
0x1E97: 't', // WITH DIAERESIS, LATIN SMALL LETTER
0x1E6B: 't', // WITH DOT ABOVE, LATIN SMALL LETTER
0x1E6D: 't', // WITH DOT BELOW, LATIN SMALL LETTER
0x01AD: 't', // WITH HOOK, LATIN SMALL LETTER
0x1E6F: 't', // WITH LINE BELOW, LATIN SMALL LETTER
0x01AB: 't', // WITH PALATAL HOOK, LATIN SMALL LETTER
0x0288: 't', // WITH RETROFLEX HOOK, LATIN SMALL LETTER
0x0167: 't', // WITH STROKE, LATIN SMALL LETTER
0x036D: 't', // , COMBINING LATIN SMALL LETTER
0x0287: 't', // , LATIN SMALL LETTER TURNED
0x209C: 't', // , LATIN SUBSCRIPT SMALL LETTER
0x0289: 'u', // BAR, LATIN SMALL LETTER
0x00FA: 'u', // WITH ACUTE, LATIN SMALL LETTER
0x016D: 'u', // WITH BREVE, LATIN SMALL LETTER
0x01D4: 'u', // WITH CARON, LATIN SMALL LETTER
0x1E77: 'u', // WITH CIRCUMFLEX BELOW, LATIN SMALL LETTER
0x00FB: 'u', // WITH CIRCUMFLEX, LATIN SMALL LETTER
0x1E73: 'u', // WITH DIAERESIS BELOW, LATIN SMALL LETTER
0x00FC: 'u', // WITH DIAERESIS, LATIN SMALL LETTER
0x1EE5: 'u', // WITH DOT BELOW, LATIN SMALL LETTER
0x0171: 'u', // WITH DOUBLE ACUTE, LATIN SMALL LETTER
0x0215: 'u', // WITH DOUBLE GRAVE, LATIN SMALL LETTER
0x00F9: 'u', // WITH GRAVE, LATIN SMALL LETTER
0x1EE7: 'u', // WITH HOOK ABOVE, LATIN SMALL LETTER
0x01B0: 'u', // WITH HORN, LATIN SMALL LETTER
0x0217: 'u', // WITH INVERTED BREVE, LATIN SMALL LETTER
0x016B: 'u', // WITH MACRON, LATIN SMALL LETTER
0x0173: 'u', // WITH OGONEK, LATIN SMALL LETTER
0x016F: 'u', // WITH RING ABOVE, LATIN SMALL LETTER
0x1E75: 'u', // WITH TILDE BELOW, LATIN SMALL LETTER
0x0169: 'u', // WITH TILDE, LATIN SMALL LETTER
0x0367: 'u', // , COMBINING LATIN SMALL LETTER
0x1D1D: 'u', // , LATIN SMALL LETTER SIDEWAYS
0x1D1E: 'u', // , LATIN SMALL LETTER SIDEWAYS DIAERESIZED
0x1D64: 'u', // , LATIN SUBSCRIPT SMALL LETTER
0x1E7F: 'v', // WITH DOT BELOW, LATIN SMALL LETTER
0x028B: 'v', // WITH HOOK, LATIN SMALL LETTER
0x1E7D: 'v', // WITH TILDE, LATIN SMALL LETTER
0x036E: 'v', // , COMBINING LATIN SMALL LETTER
0x028C: 'v', // , LATIN SMALL LETTER TURNED
0x1D65: 'v', // , LATIN SUBSCRIPT SMALL LETTER
0x1E83: 'w', // WITH ACUTE, LATIN SMALL LETTER
0x0175: 'w', // WITH CIRCUMFLEX, LATIN SMALL LETTER
0x1E85: 'w', // WITH DIAERESIS, LATIN SMALL LETTER
0x1E87: 'w', // WITH DOT ABOVE, LATIN SMALL LETTER
0x1E89: 'w', // WITH DOT BELOW, LATIN SMALL LETTER
0x1E81: 'w', // WITH GRAVE, LATIN SMALL LETTER
0x1E98: 'w', // WITH RING ABOVE, LATIN SMALL LETTER
0x028D: 'w', // , LATIN SMALL LETTER TURNED
0x1E8D: 'x', // WITH DIAERESIS, LATIN SMALL LETTER
0x1E8B: 'x', // WITH DOT ABOVE, LATIN SMALL LETTER
0x036F: 'x', // , COMBINING LATIN SMALL LETTER
0x00FD: 'y', // WITH ACUTE, LATIN SMALL LETTER
0x0177: 'y', // WITH CIRCUMFLEX, LATIN SMALL LETTER
0x00FF: 'y', // WITH DIAERESIS, LATIN SMALL LETTER
0x1E8F: 'y', // WITH DOT ABOVE, LATIN SMALL LETTER
0x1EF5: 'y', // WITH DOT BELOW, LATIN SMALL LETTER
0x1EF3: 'y', // WITH GRAVE, LATIN SMALL LETTER
0x1EF7: 'y', // WITH HOOK ABOVE, LATIN SMALL LETTER
0x01B4: 'y', // WITH HOOK, LATIN SMALL LETTER
0x0233: 'y', // WITH MACRON, LATIN SMALL LETTER
0x1E99: 'y', // WITH RING ABOVE, LATIN SMALL LETTER
0x024F: 'y', // WITH STROKE, LATIN SMALL LETTER
0x1EF9: 'y', // WITH TILDE, LATIN SMALL LETTER
0x028E: 'y', // , LATIN SMALL LETTER TURNED
0x017A: 'z', // WITH ACUTE, LATIN SMALL LETTER
0x017E: 'z', // WITH CARON, LATIN SMALL LETTER
0x1E91: 'z', // WITH CIRCUMFLEX, LATIN SMALL LETTER
0x0291: 'z', // WITH CURL, LATIN SMALL LETTER
0x017C: 'z', // WITH DOT ABOVE, LATIN SMALL LETTER
0x1E93: 'z', // WITH DOT BELOW, LATIN SMALL LETTER
0x0225: 'z', // WITH HOOK, LATIN SMALL LETTER
0x1E95: 'z', // WITH LINE BELOW, LATIN SMALL LETTER
0x0290: 'z', // WITH RETROFLEX HOOK, LATIN SMALL LETTER
0x01B6: 'z', // WITH STROKE, LATIN SMALL LETTER
0x0240: 'z', // WITH SWASH TAIL, LATIN SMALL LETTER
0x0251: 'a', // , latin small letter script
0x00C1: 'A', // WITH ACUTE, LATIN CAPITAL LETTER
0x00C2: 'A', // WITH CIRCUMFLEX, LATIN CAPITAL LETTER
0x00C4: 'A', // WITH DIAERESIS, LATIN CAPITAL LETTER
0x00C0: 'A', // WITH GRAVE, LATIN CAPITAL LETTER
0x00C5: 'A', // WITH RING ABOVE, LATIN CAPITAL LETTER
0x023A: 'A', // WITH STROKE, LATIN CAPITAL LETTER
0x00C3: 'A', // WITH TILDE, LATIN CAPITAL LETTER
0x1D00: 'A', // , LATIN LETTER SMALL CAPITAL
0x0181: 'B', // WITH HOOK, LATIN CAPITAL LETTER
0x0243: 'B', // WITH STROKE, LATIN CAPITAL LETTER
0x0299: 'B', // , LATIN LETTER SMALL CAPITAL
0x1D03: 'B', // , LATIN LETTER SMALL CAPITAL BARRED
0x00C7: 'C', // WITH CEDILLA, LATIN CAPITAL LETTER
0x023B: 'C', // WITH STROKE, LATIN CAPITAL LETTER
0x1D04: 'C', // , LATIN LETTER SMALL CAPITAL
0x018A: 'D', // WITH HOOK, LATIN CAPITAL LETTER
0x0189: 'D', // , LATIN CAPITAL LETTER AFRICAN
0x1D05: 'D', // , LATIN LETTER SMALL CAPITAL
0x00C9: 'E', // WITH ACUTE, LATIN CAPITAL LETTER
0x00CA: 'E', // WITH CIRCUMFLEX, LATIN CAPITAL LETTER
0x00CB: 'E', // WITH DIAERESIS, LATIN CAPITAL LETTER
0x00C8: 'E', // WITH GRAVE, LATIN CAPITAL LETTER
0x0246: 'E', // WITH STROKE, LATIN CAPITAL LETTER
0x0190: 'E', // , LATIN CAPITAL LETTER OPEN
0x018E: 'E', // , LATIN CAPITAL LETTER REVERSED
0x1D07: 'E', // , LATIN LETTER SMALL CAPITAL
0x0193: 'G', // WITH HOOK, LATIN CAPITAL LETTER
0x029B: 'G', // WITH HOOK, LATIN LETTER SMALL CAPITAL
0x0262: 'G', // , LATIN LETTER SMALL CAPITAL
0x029C: 'H', // , LATIN LETTER SMALL CAPITAL
0x00CD: 'I', // WITH ACUTE, LATIN CAPITAL LETTER
0x00CE: 'I', // WITH CIRCUMFLEX, LATIN CAPITAL LETTER
0x00CF: 'I', // WITH DIAERESIS, LATIN CAPITAL LETTER
0x0130: 'I', // WITH DOT ABOVE, LATIN CAPITAL LETTER
0x00CC: 'I', // WITH GRAVE, LATIN CAPITAL LETTER
0x0197: 'I', // WITH STROKE, LATIN CAPITAL LETTER
0x026A: 'I', // , LATIN LETTER SMALL CAPITAL
0x0248: 'J', // WITH STROKE, LATIN CAPITAL LETTER
0x1D0A: 'J', // , LATIN LETTER SMALL CAPITAL
0x1D0B: 'K', // , LATIN LETTER SMALL CAPITAL
0x023D: 'L', // WITH BAR, LATIN CAPITAL LETTER
0x1D0C: 'L', // WITH STROKE, LATIN LETTER SMALL CAPITAL
0x029F: 'L', // , LATIN LETTER SMALL CAPITAL
0x019C: 'M', // , LATIN CAPITAL LETTER TURNED
0x1D0D: 'M', // , LATIN LETTER SMALL CAPITAL
0x019D: 'N', // WITH LEFT HOOK, LATIN CAPITAL LETTER
0x0220: 'N', // WITH LONG RIGHT LEG, LATIN CAPITAL LETTER
0x00D1: 'N', // WITH TILDE, LATIN CAPITAL LETTER
0x0274: 'N', // , LATIN LETTER SMALL CAPITAL
0x1D0E: 'N', // , LATIN LETTER SMALL CAPITAL REVERSED
0x00D3: 'O', // WITH ACUTE, LATIN CAPITAL LETTER
0x00D4: 'O', // WITH CIRCUMFLEX, LATIN CAPITAL LETTER
0x00D6: 'O', // WITH DIAERESIS, LATIN CAPITAL LETTER
0x00D2: 'O', // WITH GRAVE, LATIN CAPITAL LETTER
0x019F: 'O', // WITH MIDDLE TILDE, LATIN CAPITAL LETTER
0x00D8: 'O', // WITH STROKE, LATIN CAPITAL LETTER
0x00D5: 'O', // WITH TILDE, LATIN CAPITAL LETTER
0x0186: 'O', // , LATIN CAPITAL LETTER OPEN
0x1D0F: 'O', // , LATIN LETTER SMALL CAPITAL
0x1D10: 'O', // , LATIN LETTER SMALL CAPITAL OPEN
0x1D18: 'P', // , LATIN LETTER SMALL CAPITAL
0x024A: 'Q', // WITH HOOK TAIL, LATIN CAPITAL LETTER SMALL
0x024C: 'R', // WITH STROKE, LATIN CAPITAL LETTER
0x0280: 'R', // , LATIN LETTER SMALL CAPITAL
0x0281: 'R', // , LATIN LETTER SMALL CAPITAL INVERTED
0x1D19: 'R', // , LATIN LETTER SMALL CAPITAL REVERSED
0x1D1A: 'R', // , LATIN LETTER SMALL CAPITAL TURNED
0x023E: 'T', // WITH DIAGONAL STROKE, LATIN CAPITAL LETTER
0x01AE: 'T', // WITH RETROFLEX HOOK, LATIN CAPITAL LETTER
0x1D1B: 'T', // , LATIN LETTER SMALL CAPITAL
0x0244: 'U', // BAR, LATIN CAPITAL LETTER
0x00DA: 'U', // WITH ACUTE, LATIN CAPITAL LETTER
0x00DB: 'U', // WITH CIRCUMFLEX, LATIN CAPITAL LETTER
0x00DC: 'U', // WITH DIAERESIS, LATIN CAPITAL LETTER
0x00D9: 'U', // WITH GRAVE, LATIN CAPITAL LETTER
0x1D1C: 'U', // , LATIN LETTER SMALL CAPITAL
0x01B2: 'V', // WITH HOOK, LATIN CAPITAL LETTER
0x0245: 'V', // , LATIN CAPITAL LETTER TURNED
0x1D20: 'V', // , LATIN LETTER SMALL CAPITAL
0x1D21: 'W', // , LATIN LETTER SMALL CAPITAL
0x00DD: 'Y', // WITH ACUTE, LATIN CAPITAL LETTER
0x0178: 'Y', // WITH DIAERESIS, LATIN CAPITAL LETTER
0x024E: 'Y', // WITH STROKE, LATIN CAPITAL LETTER
0x028F: 'Y', // , LATIN LETTER SMALL CAPITAL
0x1D22: 'Z', // , LATIN LETTER SMALL CAPITAL
}
// NormalizeRunes normalizes latin script letters
func NormalizeRunes(runes []rune) []rune {
ret := make([]rune, len(runes))
copy(ret, runes)
for idx, r := range runes {
if r < 0x00C0 || r > 0x2184 {
continue
}
n := normalized[r]
if n > 0 {
ret[idx] = normalized[r]
}
}
return ret
}

238
src/ansi.go Normal file
View File

@ -0,0 +1,238 @@
package fzf
import (
"bytes"
"regexp"
"strconv"
"strings"
"unicode/utf8"
"github.com/junegunn/fzf/src/tui"
)
type ansiOffset struct {
offset [2]int32
color ansiState
}
type ansiState struct {
fg tui.Color
bg tui.Color
attr tui.Attr
}
func (s *ansiState) colored() bool {
return s.fg != -1 || s.bg != -1 || s.attr > 0
}
func (s *ansiState) equals(t *ansiState) bool {
if t == nil {
return !s.colored()
}
return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr
}
var ansiRegex *regexp.Regexp
func init() {
/*
References:
- https://github.com/gnachman/iTerm2
- http://ascii-table.com/ansi-escape-sequences.php
- http://ascii-table.com/ansi-escape-sequences-vt-100.php
- http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
*/
// The following regular expression will include not all but most of the
// frequently used ANSI sequences
ansiRegex = regexp.MustCompile("(?:\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b.|[\x0e\x0f]|.\x08)")
}
func findAnsiStart(str string) int {
idx := 0
for ; idx < len(str); idx++ {
b := str[idx]
if b == 0x1b || b == 0x0e || b == 0x0f {
return idx
}
if b == 0x08 && idx > 0 {
return idx - 1
}
}
return idx
}
func extractColor(str string, state *ansiState, proc func(string, *ansiState) bool) (string, *[]ansiOffset, *ansiState) {
var offsets []ansiOffset
var output bytes.Buffer
if state != nil {
offsets = append(offsets, ansiOffset{[2]int32{0, 0}, *state})
}
prevIdx := 0
runeCount := 0
for idx := 0; idx < len(str); {
idx += findAnsiStart(str[idx:])
if idx == len(str) {
break
}
// Make sure that we found an ANSI code
offset := ansiRegex.FindStringIndex(str[idx:])
if len(offset) < 2 {
idx++
continue
}
offset[0] += idx
offset[1] += idx
idx = offset[1]
// Check if we should continue
prev := str[prevIdx:offset[0]]
if proc != nil && !proc(prev, state) {
return "", nil, nil
}
prevIdx = offset[1]
runeCount += utf8.RuneCountInString(prev)
output.WriteString(prev)
newState := interpretCode(str[offset[0]:offset[1]], state)
if !newState.equals(state) {
if state != nil {
// Update last offset
(&offsets[len(offsets)-1]).offset[1] = int32(runeCount)
}
if newState.colored() {
// Append new offset
state = newState
offsets = append(offsets, ansiOffset{[2]int32{int32(runeCount), int32(runeCount)}, *state})
} else {
// Discard state
state = nil
}
}
}
var rest string
var trimmed string
if prevIdx == 0 {
// No ANSI code found
rest = str
trimmed = str
} else {
rest = str[prevIdx:]
output.WriteString(rest)
trimmed = output.String()
}
if len(rest) > 0 && state != nil {
// Update last offset
runeCount += utf8.RuneCountInString(rest)
(&offsets[len(offsets)-1]).offset[1] = int32(runeCount)
}
if proc != nil {
proc(rest, state)
}
if len(offsets) == 0 {
return trimmed, nil, state
}
return trimmed, &offsets, state
}
func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
// State
var state *ansiState
if prevState == nil {
state = &ansiState{-1, -1, 0}
} else {
state = &ansiState{prevState.fg, prevState.bg, prevState.attr}
}
if ansiCode[0] != '\x1b' || ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' {
return state
}
ptr := &state.fg
state256 := 0
init := func() {
state.fg = -1
state.bg = -1
state.attr = 0
state256 = 0
}
ansiCode = ansiCode[2 : len(ansiCode)-1]
if len(ansiCode) == 0 {
init()
}
for _, code := range strings.Split(ansiCode, ";") {
if num, err := strconv.Atoi(code); err == nil {
switch state256 {
case 0:
switch num {
case 38:
ptr = &state.fg
state256++
case 48:
ptr = &state.bg
state256++
case 39:
state.fg = -1
case 49:
state.bg = -1
case 1:
state.attr = state.attr | tui.Bold
case 2:
state.attr = state.attr | tui.Dim
case 3:
state.attr = state.attr | tui.Italic
case 4:
state.attr = state.attr | tui.Underline
case 5:
state.attr = state.attr | tui.Blink
case 7:
state.attr = state.attr | tui.Reverse
case 0:
init()
default:
if num >= 30 && num <= 37 {
state.fg = tui.Color(num - 30)
} else if num >= 40 && num <= 47 {
state.bg = tui.Color(num - 40)
} else if num >= 90 && num <= 97 {
state.fg = tui.Color(num - 90 + 8)
} else if num >= 100 && num <= 107 {
state.bg = tui.Color(num - 100 + 8)
}
}
case 1:
switch num {
case 2:
state256 = 10 // MAGIC
case 5:
state256++
default:
state256 = 0
}
case 2:
*ptr = tui.Color(num)
state256 = 0
case 10:
*ptr = tui.Color(1<<24) | tui.Color(num<<16)
state256++
case 11:
*ptr = *ptr | tui.Color(num<<8)
state256++
case 12:
*ptr = *ptr | tui.Color(num)
state256 = 0
}
}
}
if state256 > 0 {
*ptr = -1
}
return state
}

158
src/ansi_test.go Normal file
View File

@ -0,0 +1,158 @@
package fzf
import (
"fmt"
"testing"
"github.com/junegunn/fzf/src/tui"
)
func TestExtractColor(t *testing.T) {
assert := func(offset ansiOffset, b int32, e int32, fg tui.Color, bg tui.Color, bold bool) {
var attr tui.Attr
if bold {
attr = tui.Bold
}
if offset.offset[0] != b || offset.offset[1] != e ||
offset.color.fg != fg || offset.color.bg != bg || offset.color.attr != attr {
t.Error(offset, b, e, fg, bg, attr)
}
}
src := "hello world"
var state *ansiState
clean := "\x1b[0m"
check := func(assertion func(ansiOffsets *[]ansiOffset, state *ansiState)) {
output, ansiOffsets, newState := extractColor(src, state, nil)
state = newState
if output != "hello world" {
t.Errorf("Invalid output: %s %s", output, []rune(output))
}
fmt.Println(src, ansiOffsets, clean)
assertion(ansiOffsets, state)
}
check(func(offsets *[]ansiOffset, state *ansiState) {
if offsets != nil {
t.Fail()
}
})
state = nil
src = "\x1b[0mhello world"
check(func(offsets *[]ansiOffset, state *ansiState) {
if offsets != nil {
t.Fail()
}
})
state = nil
src = "\x1b[1mhello world"
check(func(offsets *[]ansiOffset, state *ansiState) {
if len(*offsets) != 1 {
t.Fail()
}
assert((*offsets)[0], 0, 11, -1, -1, true)
})
state = nil
src = "\x1b[1mhello \x1b[mw\x1b7o\x1b8r\x1b(Bl\x1b[2@d"
check(func(offsets *[]ansiOffset, state *ansiState) {
if len(*offsets) != 1 {
t.Fail()
}
assert((*offsets)[0], 0, 6, -1, -1, true)
})
state = nil
src = "\x1b[1mhello \x1b[Kworld"
check(func(offsets *[]ansiOffset, state *ansiState) {
if len(*offsets) != 1 {
t.Fail()
}
assert((*offsets)[0], 0, 11, -1, -1, true)
})
state = nil
src = "hello \x1b[34;45;1mworld"
check(func(offsets *[]ansiOffset, state *ansiState) {
if len(*offsets) != 1 {
t.Fail()
}
assert((*offsets)[0], 6, 11, 4, 5, true)
})
state = nil
src = "hello \x1b[34;45;1mwor\x1b[34;45;1mld"
check(func(offsets *[]ansiOffset, state *ansiState) {
if len(*offsets) != 1 {
t.Fail()
}
assert((*offsets)[0], 6, 11, 4, 5, true)
})
state = nil
src = "hello \x1b[34;45;1mwor\x1b[0mld"
check(func(offsets *[]ansiOffset, state *ansiState) {
if len(*offsets) != 1 {
t.Fail()
}
assert((*offsets)[0], 6, 9, 4, 5, true)
})
state = nil
src = "hello \x1b[34;48;5;233;1mwo\x1b[38;5;161mr\x1b[0ml\x1b[38;5;161md"
check(func(offsets *[]ansiOffset, state *ansiState) {
if len(*offsets) != 3 {
t.Fail()
}
assert((*offsets)[0], 6, 8, 4, 233, true)
assert((*offsets)[1], 8, 9, 161, 233, true)
assert((*offsets)[2], 10, 11, 161, -1, false)
})
// {38,48};5;{38,48}
state = nil
src = "hello \x1b[38;5;38;48;5;48;1mwor\x1b[38;5;48;48;5;38ml\x1b[0md"
check(func(offsets *[]ansiOffset, state *ansiState) {
if len(*offsets) != 2 {
t.Fail()
}
assert((*offsets)[0], 6, 9, 38, 48, true)
assert((*offsets)[1], 9, 10, 48, 38, true)
})
src = "hello \x1b[32;1mworld"
check(func(offsets *[]ansiOffset, state *ansiState) {
if len(*offsets) != 1 {
t.Fail()
}
if state.fg != 2 || state.bg != -1 || state.attr == 0 {
t.Fail()
}
assert((*offsets)[0], 6, 11, 2, -1, true)
})
src = "hello world"
check(func(offsets *[]ansiOffset, state *ansiState) {
if len(*offsets) != 1 {
t.Fail()
}
if state.fg != 2 || state.bg != -1 || state.attr == 0 {
t.Fail()
}
assert((*offsets)[0], 0, 11, 2, -1, true)
})
src = "hello \x1b[0;38;5;200;48;5;100mworld"
check(func(offsets *[]ansiOffset, state *ansiState) {
if len(*offsets) != 2 {
t.Fail()
}
if state.fg != 200 || state.bg != 100 || state.attr > 0 {
t.Fail()
}
assert((*offsets)[0], 0, 6, 2, -1, true)
assert((*offsets)[1], 6, 11, 200, 100, false)
})
}

81
src/cache.go Normal file
View File

@ -0,0 +1,81 @@
package fzf
import "sync"
// queryCache associates strings to lists of items
type queryCache map[string][]Result
// ChunkCache associates Chunk and query string to lists of items
type ChunkCache struct {
mutex sync.Mutex
cache map[*Chunk]*queryCache
}
// NewChunkCache returns a new ChunkCache
func NewChunkCache() ChunkCache {
return ChunkCache{sync.Mutex{}, make(map[*Chunk]*queryCache)}
}
// Add adds the list to the cache
func (cc *ChunkCache) Add(chunk *Chunk, key string, list []Result) {
if len(key) == 0 || !chunk.IsFull() || len(list) > queryCacheMax {
return
}
cc.mutex.Lock()
defer cc.mutex.Unlock()
qc, ok := cc.cache[chunk]
if !ok {
cc.cache[chunk] = &queryCache{}
qc = cc.cache[chunk]
}
(*qc)[key] = list
}
// Lookup is called to lookup ChunkCache
func (cc *ChunkCache) Lookup(chunk *Chunk, key string) []Result {
if len(key) == 0 || !chunk.IsFull() {
return nil
}
cc.mutex.Lock()
defer cc.mutex.Unlock()
qc, ok := cc.cache[chunk]
if ok {
list, ok := (*qc)[key]
if ok {
return list
}
}
return nil
}
func (cc *ChunkCache) Search(chunk *Chunk, key string) []Result {
if len(key) == 0 || !chunk.IsFull() {
return nil
}
cc.mutex.Lock()
defer cc.mutex.Unlock()
qc, ok := cc.cache[chunk]
if !ok {
return nil
}
for idx := 1; idx < len(key); idx++ {
// [---------| ] | [ |---------]
// [--------| ] | [ |--------]
// [-------| ] | [ |-------]
prefix := key[:len(key)-idx]
suffix := key[idx:]
for _, substr := range [2]string{prefix, suffix} {
if cached, found := (*qc)[substr]; found {
return cached
}
}
}
return nil
}

39
src/cache_test.go Normal file
View File

@ -0,0 +1,39 @@
package fzf
import "testing"
func TestChunkCache(t *testing.T) {
cache := NewChunkCache()
chunk1p := &Chunk{}
chunk2p := &Chunk{count: chunkSize}
items1 := []Result{Result{}}
items2 := []Result{Result{}, Result{}}
cache.Add(chunk1p, "foo", items1)
cache.Add(chunk2p, "foo", items1)
cache.Add(chunk2p, "bar", items2)
{ // chunk1 is not full
cached := cache.Lookup(chunk1p, "foo")
if cached != nil {
t.Error("Cached disabled for non-empty chunks", cached)
}
}
{
cached := cache.Lookup(chunk2p, "foo")
if cached == nil || len(cached) != 1 {
t.Error("Expected 1 item cached", cached)
}
}
{
cached := cache.Lookup(chunk2p, "bar")
if cached == nil || len(cached) != 2 {
t.Error("Expected 2 items cached", cached)
}
}
{
cached := cache.Lookup(chunk1p, "foobar")
if cached != nil {
t.Error("Expected 0 item cached", cached)
}
}
}

82
src/chunklist.go Normal file
View File

@ -0,0 +1,82 @@
package fzf
import "sync"
// Chunk is a list of Items whose size has the upper limit of chunkSize
type Chunk struct {
items [chunkSize]Item
count int
}
// ItemBuilder is a closure type that builds Item object from byte array
type ItemBuilder func(*Item, []byte) bool
// ChunkList is a list of Chunks
type ChunkList struct {
chunks []*Chunk
mutex sync.Mutex
trans ItemBuilder
}
// NewChunkList returns a new ChunkList
func NewChunkList(trans ItemBuilder) *ChunkList {
return &ChunkList{
chunks: []*Chunk{},
mutex: sync.Mutex{},
trans: trans}
}
func (c *Chunk) push(trans ItemBuilder, data []byte) bool {
if trans(&c.items[c.count], data) {
c.count++
return true
}
return false
}
// IsFull returns true if the Chunk is full
func (c *Chunk) IsFull() bool {
return c.count == chunkSize
}
func (cl *ChunkList) lastChunk() *Chunk {
return cl.chunks[len(cl.chunks)-1]
}
// CountItems returns the total number of Items
func CountItems(cs []*Chunk) int {
if len(cs) == 0 {
return 0
}
return chunkSize*(len(cs)-1) + cs[len(cs)-1].count
}
// Push adds the item to the list
func (cl *ChunkList) Push(data []byte) bool {
cl.mutex.Lock()
if len(cl.chunks) == 0 || cl.lastChunk().IsFull() {
cl.chunks = append(cl.chunks, &Chunk{})
}
ret := cl.lastChunk().push(cl.trans, data)
cl.mutex.Unlock()
return ret
}
// Snapshot returns immutable snapshot of the ChunkList
func (cl *ChunkList) Snapshot() ([]*Chunk, int) {
cl.mutex.Lock()
ret := make([]*Chunk, len(cl.chunks))
copy(ret, cl.chunks)
// Duplicate the last chunk
if cnt := len(ret); cnt > 0 {
newChunk := *ret[cnt-1]
ret[cnt-1] = &newChunk
}
cl.mutex.Unlock()
return ret, CountItems(ret)
}

80
src/chunklist_test.go Normal file
View File

@ -0,0 +1,80 @@
package fzf
import (
"fmt"
"testing"
"github.com/junegunn/fzf/src/util"
)
func TestChunkList(t *testing.T) {
// FIXME global
sortCriteria = []criterion{byScore, byLength}
cl := NewChunkList(func(item *Item, s []byte) bool {
item.text = util.ToChars(s)
return true
})
// Snapshot
snapshot, count := cl.Snapshot()
if len(snapshot) > 0 || count > 0 {
t.Error("Snapshot should be empty now")
}
// Add some data
cl.Push([]byte("hello"))
cl.Push([]byte("world"))
// Previously created snapshot should remain the same
if len(snapshot) > 0 {
t.Error("Snapshot should not have changed")
}
// But the new snapshot should contain the added items
snapshot, count = cl.Snapshot()
if len(snapshot) != 1 && count != 2 {
t.Error("Snapshot should not be empty now")
}
// Check the content of the ChunkList
chunk1 := snapshot[0]
if chunk1.count != 2 {
t.Error("Snapshot should contain only two items")
}
if chunk1.items[0].text.ToString() != "hello" ||
chunk1.items[1].text.ToString() != "world" {
t.Error("Invalid data")
}
if chunk1.IsFull() {
t.Error("Chunk should not have been marked full yet")
}
// Add more data
for i := 0; i < chunkSize*2; i++ {
cl.Push([]byte(fmt.Sprintf("item %d", i)))
}
// Previous snapshot should remain the same
if len(snapshot) != 1 {
t.Error("Snapshot should stay the same")
}
// New snapshot
snapshot, count = cl.Snapshot()
if len(snapshot) != 3 || !snapshot[0].IsFull() ||
!snapshot[1].IsFull() || snapshot[2].IsFull() || count != chunkSize*2+2 {
t.Error("Expected two full chunks and one more chunk")
}
if snapshot[2].count != 2 {
t.Error("Unexpected number of items")
}
cl.Push([]byte("hello"))
cl.Push([]byte("world"))
lastChunkCount := snapshot[len(snapshot)-1].count
if lastChunkCount != 2 {
t.Error("Unexpected number of items:", lastChunkCount)
}
}

82
src/constants.go Normal file
View File

@ -0,0 +1,82 @@
package fzf
import (
"os"
"time"
"github.com/junegunn/fzf/src/util"
)
const (
// Current version
version = "0.17.1"
// Core
coordinatorDelayMax time.Duration = 100 * time.Millisecond
coordinatorDelayStep time.Duration = 10 * time.Millisecond
// Reader
readerBufferSize = 64 * 1024
readerPollIntervalMin = 10 * time.Millisecond
readerPollIntervalStep = 5 * time.Millisecond
readerPollIntervalMax = 50 * time.Millisecond
// Terminal
initialDelay = 20 * time.Millisecond
initialDelayTac = 100 * time.Millisecond
spinnerDuration = 200 * time.Millisecond
// Matcher
numPartitionsMultiplier = 8
maxPartitions = 32
progressMinDuration = 200 * time.Millisecond
// Capacity of each chunk
chunkSize int = 100
// Pre-allocated memory slices to minimize GC
slab16Size int = 100 * 1024 // 200KB * 32 = 12.8MB
slab32Size int = 2048 // 8KB * 32 = 256KB
// Do not cache results of low selectivity queries
queryCacheMax int = chunkSize / 5
// Not to cache mergers with large lists
mergerCacheMax int = 100000
// History
defaultHistoryMax int = 1000
// Jump labels
defaultJumpLabels string = "asdfghjklqwertyuiopzxcvbnm1234567890ASDFGHJKLQWERTYUIOPZXCVBNM`~;:,<.>/?'\"!@#$%^&*()[{]}-_=+"
)
var defaultCommand string
func init() {
if !util.IsWindows() {
defaultCommand = `set -o pipefail; (command find -L . -mindepth 1 \( -path '*/\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print || command find -L . -mindepth 1 -path '*/\.*' -prune -o -type f -print -o -type l -print) 2> /dev/null | cut -b3-`
} 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-"`
} else {
defaultCommand = `dir /s/b`
}
}
// fzf events
const (
EvtReadNew util.EventType = iota
EvtReadFin
EvtSearchNew
EvtSearchProgress
EvtSearchFin
EvtHeader
EvtReady
)
const (
exitOk = 0
exitNoMatch = 1
exitError = 2
exitInterrupt = 130
)

286
src/core.go Normal file
View File

@ -0,0 +1,286 @@
/*
Package fzf implements fzf, a command-line fuzzy finder.
The MIT License (MIT)
Copyright (c) 2017 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package fzf
import (
"fmt"
"os"
"time"
"github.com/junegunn/fzf/src/util"
)
/*
Reader -> EvtReadFin
Reader -> EvtReadNew -> Matcher (restart)
Terminal -> EvtSearchNew:bool -> Matcher (restart)
Matcher -> EvtSearchProgress -> Terminal (update info)
Matcher -> EvtSearchFin -> Terminal (update list)
Matcher -> EvtHeader -> Terminal (update header)
*/
// Run starts fzf
func Run(opts *Options, revision string) {
sort := opts.Sort > 0
sortCriteria = opts.Criteria
if opts.Version {
if len(revision) > 0 {
fmt.Printf("%s (%s)\n", version, revision)
} else {
fmt.Println(version)
}
os.Exit(exitOk)
}
// Event channel
eventBox := util.NewEventBox()
// ANSI code processor
ansiProcessor := func(data []byte) (util.Chars, *[]ansiOffset) {
return util.ToChars(data), nil
}
if opts.Ansi {
if opts.Theme != nil {
var state *ansiState
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
trimmed, offsets, newState := extractColor(string(data), state, nil)
state = newState
return util.ToChars([]byte(trimmed)), offsets
}
} else {
// When color is disabled but ansi option is given,
// we simply strip out ANSI codes from the input
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
trimmed, _, _ := extractColor(string(data), nil, nil)
return util.ToChars([]byte(trimmed)), nil
}
}
}
// Chunk list
var chunkList *ChunkList
var itemIndex int32
header := make([]string, 0, opts.HeaderLines)
if len(opts.WithNth) == 0 {
chunkList = NewChunkList(func(item *Item, data []byte) bool {
if len(header) < opts.HeaderLines {
header = append(header, string(data))
eventBox.Set(EvtHeader, header)
return false
}
item.text, item.colors = ansiProcessor(data)
item.text.Index = itemIndex
itemIndex++
return true
})
} else {
chunkList = NewChunkList(func(item *Item, data []byte) bool {
tokens := Tokenize(string(data), opts.Delimiter)
trans := Transform(tokens, opts.WithNth)
transformed := joinTokens(trans)
if len(header) < opts.HeaderLines {
header = append(header, transformed)
eventBox.Set(EvtHeader, header)
return false
}
item.text, item.colors = ansiProcessor([]byte(transformed))
item.text.Index = itemIndex
item.origText = &data
itemIndex++
return true
})
}
// Reader
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
if !streamingFilter {
reader := NewReader(func(data []byte) bool {
return chunkList.Push(data)
}, eventBox, opts.ReadZero)
go reader.ReadSource()
}
// Matcher
forward := true
for _, cri := range opts.Criteria[1:] {
if cri == byEnd {
forward = false
break
}
if cri == byBegin {
break
}
}
patternBuilder := func(runes []rune) *Pattern {
return BuildPattern(
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward,
opts.Filter == nil, opts.Nth, opts.Delimiter, runes)
}
matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox)
// Filtering mode
if opts.Filter != nil {
if opts.PrintQuery {
opts.Printer(*opts.Filter)
}
pattern := patternBuilder([]rune(*opts.Filter))
found := false
if streamingFilter {
slab := util.MakeSlab(slab16Size, slab32Size)
reader := NewReader(
func(runes []byte) bool {
item := Item{}
if chunkList.trans(&item, runes) {
if result, _, _ := pattern.MatchItem(&item, false, slab); result != nil {
opts.Printer(item.text.ToString())
found = true
}
}
return false
}, eventBox, opts.ReadZero)
reader.ReadSource()
} else {
eventBox.Unwatch(EvtReadNew)
eventBox.WaitFor(EvtReadFin)
snapshot, _ := chunkList.Snapshot()
merger, _ := matcher.scan(MatchRequest{
chunks: snapshot,
pattern: pattern})
for i := 0; i < merger.Length(); i++ {
opts.Printer(merger.Get(i).item.AsString(opts.Ansi))
found = true
}
}
if found {
os.Exit(exitOk)
}
os.Exit(exitNoMatch)
}
// Synchronous search
if opts.Sync {
eventBox.Unwatch(EvtReadNew)
eventBox.WaitFor(EvtReadFin)
}
// Go interactive
go matcher.Loop()
// Terminal I/O
terminal := NewTerminal(opts, eventBox)
deferred := opts.Select1 || opts.Exit0
go terminal.Loop()
if !deferred {
terminal.startChan <- true
}
// Event coordination
reading := true
ticks := 0
eventBox.Watch(EvtReadNew)
for {
delay := true
ticks++
eventBox.Wait(func(events *util.Events) {
if _, fin := (*events)[EvtReadFin]; fin {
delete(*events, EvtReadNew)
}
for evt, value := range *events {
switch evt {
case EvtReadNew, EvtReadFin:
reading = reading && evt == EvtReadNew
snapshot, count := chunkList.Snapshot()
terminal.UpdateCount(count, !reading, value.(bool))
if opts.Sync {
terminal.UpdateList(PassMerger(&snapshot, opts.Tac))
}
matcher.Reset(snapshot, terminal.Input(), false, !reading, sort)
case EvtSearchNew:
switch val := value.(type) {
case bool:
sort = val
}
snapshot, _ := chunkList.Snapshot()
matcher.Reset(snapshot, terminal.Input(), true, !reading, sort)
delay = false
case EvtSearchProgress:
switch val := value.(type) {
case float32:
terminal.UpdateProgress(val)
}
case EvtHeader:
terminal.UpdateHeader(value.([]string))
case EvtSearchFin:
switch val := value.(type) {
case *Merger:
if deferred {
count := val.Length()
if opts.Select1 && count > 1 || opts.Exit0 && !opts.Select1 && count > 0 {
deferred = false
terminal.startChan <- true
} else if val.final {
if opts.Exit0 && count == 0 || opts.Select1 && count == 1 {
if opts.PrintQuery {
opts.Printer(opts.Query)
}
if len(opts.Expect) > 0 {
opts.Printer("")
}
for i := 0; i < count; i++ {
opts.Printer(val.Get(i).item.AsString(opts.Ansi))
}
if count > 0 {
os.Exit(exitOk)
}
os.Exit(exitNoMatch)
}
deferred = false
terminal.startChan <- true
}
}
terminal.UpdateList(val)
}
}
}
events.Clear()
})
if delay && reading {
dur := util.DurWithin(
time.Duration(ticks)*coordinatorDelayStep,
0, coordinatorDelayMax)
time.Sleep(dur)
}
}
}

96
src/history.go Normal file
View File

@ -0,0 +1,96 @@
package fzf
import (
"errors"
"io/ioutil"
"os"
"strings"
)
// History struct represents input history
type History struct {
path string
lines []string
modified map[int]string
maxSize int
cursor int
}
// NewHistory returns the pointer to a new History struct
func NewHistory(path string, maxSize int) (*History, error) {
fmtError := func(e error) error {
if os.IsPermission(e) {
return errors.New("permission denied: " + path)
}
return errors.New("invalid history file: " + e.Error())
}
// Read history file
data, err := ioutil.ReadFile(path)
if err != nil {
// If it doesn't exist, check if we can create a file with the name
if os.IsNotExist(err) {
data = []byte{}
if err := ioutil.WriteFile(path, data, 0600); err != nil {
return nil, fmtError(err)
}
} else {
return nil, fmtError(err)
}
}
// Split lines and limit the maximum number of lines
lines := strings.Split(strings.Trim(string(data), "\n"), "\n")
if len(lines[len(lines)-1]) > 0 {
lines = append(lines, "")
}
return &History{
path: path,
maxSize: maxSize,
lines: lines,
modified: make(map[int]string),
cursor: len(lines) - 1}, nil
}
func (h *History) append(line string) error {
// We don't append empty lines
if len(line) == 0 {
return nil
}
lines := append(h.lines[:len(h.lines)-1], line)
if len(lines) > h.maxSize {
lines = lines[len(lines)-h.maxSize : len(lines)]
}
h.lines = append(lines, "")
return ioutil.WriteFile(h.path, []byte(strings.Join(h.lines, "\n")), 0600)
}
func (h *History) override(str string) {
// You can update the history but they're not written to the file
if h.cursor == len(h.lines)-1 {
h.lines[h.cursor] = str
} else if h.cursor < len(h.lines)-1 {
h.modified[h.cursor] = str
}
}
func (h *History) current() string {
if str, prs := h.modified[h.cursor]; prs {
return str
}
return h.lines[h.cursor]
}
func (h *History) previous() string {
if h.cursor > 0 {
h.cursor--
}
return h.current()
}
func (h *History) next() string {
if h.cursor < len(h.lines)-1 {
h.cursor++
}
return h.current()
}

73
src/history_test.go Normal file
View File

@ -0,0 +1,73 @@
package fzf
import (
"io/ioutil"
"os"
"os/user"
"runtime"
"testing"
)
func TestHistory(t *testing.T) {
maxHistory := 50
// Invalid arguments
user, _ := user.Current()
var paths []string
if runtime.GOOS == "windows" {
// GOPATH should exist, so we shouldn't be able to override it
paths = []string{os.Getenv("GOPATH")}
} else {
paths = []string{"/etc", "/proc"}
if user.Name != "root" {
paths = append(paths, "/etc/sudoers")
}
}
for _, path := range paths {
if _, e := NewHistory(path, maxHistory); e == nil {
t.Error("Error expected for: " + path)
}
}
f, _ := ioutil.TempFile("", "fzf-history")
f.Close()
{ // Append lines
h, _ := NewHistory(f.Name(), maxHistory)
for i := 0; i < maxHistory+10; i++ {
h.append("foobar")
}
}
{ // Read lines
h, _ := NewHistory(f.Name(), maxHistory)
if len(h.lines) != maxHistory+1 {
t.Errorf("Expected: %d, actual: %d\n", maxHistory+1, len(h.lines))
}
for i := 0; i < maxHistory; i++ {
if h.lines[i] != "foobar" {
t.Error("Expected: foobar, actual: " + h.lines[i])
}
}
}
{ // Append lines
h, _ := NewHistory(f.Name(), maxHistory)
h.append("barfoo")
h.append("")
h.append("foobarbaz")
}
{ // Read lines again
h, _ := NewHistory(f.Name(), maxHistory)
if len(h.lines) != maxHistory+1 {
t.Errorf("Expected: %d, actual: %d\n", maxHistory+1, len(h.lines))
}
compare := func(idx int, exp string) {
if h.lines[idx] != exp {
t.Errorf("Expected: %s, actual: %s\n", exp, h.lines[idx])
}
}
compare(maxHistory-3, "foobar")
compare(maxHistory-2, "barfoo")
compare(maxHistory-1, "foobarbaz")
}
}

44
src/item.go Normal file
View File

@ -0,0 +1,44 @@
package fzf
import (
"github.com/junegunn/fzf/src/util"
)
// Item represents each input line. 56 bytes.
type Item struct {
text util.Chars // 32 = 24 + 1 + 1 + 2 + 4
transformed *[]Token // 8
origText *[]byte // 8
colors *[]ansiOffset // 8
}
// Index returns ordinal index of the Item
func (item *Item) Index() int32 {
return item.text.Index
}
var minItem = Item{text: util.Chars{Index: -1}}
func (item *Item) TrimLength() uint16 {
return item.text.TrimLength()
}
// Colors returns ansiOffsets of the Item
func (item *Item) Colors() []ansiOffset {
if item.colors == nil {
return []ansiOffset{}
}
return *item.colors
}
// AsString returns the original string
func (item *Item) AsString(stripAnsi bool) string {
if item.origText != nil {
if stripAnsi {
trimmed, _, _ := extractColor(string(*item.origText), nil, nil)
return trimmed
}
return string(*item.origText)
}
return item.text.ToString()
}

23
src/item_test.go Normal file
View File

@ -0,0 +1,23 @@
package fzf
import (
"testing"
"github.com/junegunn/fzf/src/util"
)
func TestStringPtr(t *testing.T) {
orig := []byte("\x1b[34mfoo")
text := []byte("\x1b[34mbar")
item := Item{origText: &orig, text: util.ToChars(text)}
if item.AsString(true) != "foo" || item.AsString(false) != string(orig) {
t.Fail()
}
if item.AsString(true) != "foo" {
t.Fail()
}
item.origText = nil
if item.AsString(true) != string(text) || item.AsString(false) != string(text) {
t.Fail()
}
}

234
src/matcher.go Normal file
View File

@ -0,0 +1,234 @@
package fzf
import (
"fmt"
"runtime"
"sort"
"sync"
"time"
"github.com/junegunn/fzf/src/util"
)
// MatchRequest represents a search request
type MatchRequest struct {
chunks []*Chunk
pattern *Pattern
final bool
sort bool
}
// Matcher is responsible for performing search
type Matcher struct {
patternBuilder func([]rune) *Pattern
sort bool
tac bool
eventBox *util.EventBox
reqBox *util.EventBox
partitions int
slab []*util.Slab
mergerCache map[string]*Merger
}
const (
reqRetry util.EventType = iota
reqReset
)
// NewMatcher returns a new Matcher
func NewMatcher(patternBuilder func([]rune) *Pattern,
sort bool, tac bool, eventBox *util.EventBox) *Matcher {
partitions := util.Min(numPartitionsMultiplier*runtime.NumCPU(), maxPartitions)
return &Matcher{
patternBuilder: patternBuilder,
sort: sort,
tac: tac,
eventBox: eventBox,
reqBox: util.NewEventBox(),
partitions: partitions,
slab: make([]*util.Slab, partitions),
mergerCache: make(map[string]*Merger)}
}
// Loop puts Matcher in action
func (m *Matcher) Loop() {
prevCount := 0
for {
var request MatchRequest
m.reqBox.Wait(func(events *util.Events) {
for _, val := range *events {
switch val := val.(type) {
case MatchRequest:
request = val
default:
panic(fmt.Sprintf("Unexpected type: %T", val))
}
}
events.Clear()
})
if request.sort != m.sort {
m.sort = request.sort
m.mergerCache = make(map[string]*Merger)
clearChunkCache()
}
// Restart search
patternString := request.pattern.AsString()
var merger *Merger
cancelled := false
count := CountItems(request.chunks)
foundCache := false
if count == prevCount {
// Look up mergerCache
if cached, found := m.mergerCache[patternString]; found {
foundCache = true
merger = cached
}
} else {
// Invalidate mergerCache
prevCount = count
m.mergerCache = make(map[string]*Merger)
}
if !foundCache {
merger, cancelled = m.scan(request)
}
if !cancelled {
if merger.cacheable() {
m.mergerCache[patternString] = merger
}
merger.final = request.final
m.eventBox.Set(EvtSearchFin, merger)
}
}
}
func (m *Matcher) sliceChunks(chunks []*Chunk) [][]*Chunk {
partitions := m.partitions
perSlice := len(chunks) / partitions
if perSlice == 0 {
partitions = len(chunks)
perSlice = 1
}
slices := make([][]*Chunk, partitions)
for i := 0; i < partitions; i++ {
start := i * perSlice
end := start + perSlice
if i == partitions-1 {
end = len(chunks)
}
slices[i] = chunks[start:end]
}
return slices
}
type partialResult struct {
index int
matches []Result
}
func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
startedAt := time.Now()
numChunks := len(request.chunks)
if numChunks == 0 {
return EmptyMerger, false
}
pattern := request.pattern
if pattern.IsEmpty() {
return PassMerger(&request.chunks, m.tac), false
}
cancelled := util.NewAtomicBool(false)
slices := m.sliceChunks(request.chunks)
numSlices := len(slices)
resultChan := make(chan partialResult, numSlices)
countChan := make(chan int, numChunks)
waitGroup := sync.WaitGroup{}
for idx, chunks := range slices {
waitGroup.Add(1)
if m.slab[idx] == nil {
m.slab[idx] = util.MakeSlab(slab16Size, slab32Size)
}
go func(idx int, slab *util.Slab, chunks []*Chunk) {
defer func() { waitGroup.Done() }()
count := 0
allMatches := make([][]Result, len(chunks))
for idx, chunk := range chunks {
matches := request.pattern.Match(chunk, slab)
allMatches[idx] = matches
count += len(matches)
if cancelled.Get() {
return
}
countChan <- len(matches)
}
sliceMatches := make([]Result, 0, count)
for _, matches := range allMatches {
sliceMatches = append(sliceMatches, matches...)
}
if m.sort {
if m.tac {
sort.Sort(ByRelevanceTac(sliceMatches))
} else {
sort.Sort(ByRelevance(sliceMatches))
}
}
resultChan <- partialResult{idx, sliceMatches}
}(idx, m.slab[idx], chunks)
}
wait := func() bool {
cancelled.Set(true)
waitGroup.Wait()
return true
}
count := 0
matchCount := 0
for matchesInChunk := range countChan {
count++
matchCount += matchesInChunk
if count == numChunks {
break
}
if m.reqBox.Peek(reqReset) {
return nil, wait()
}
if time.Now().Sub(startedAt) > progressMinDuration {
m.eventBox.Set(EvtSearchProgress, float32(count)/float32(numChunks))
}
}
partialResults := make([][]Result, numSlices)
for _ = range slices {
partialResult := <-resultChan
partialResults[partialResult.index] = partialResult.matches
}
return NewMerger(pattern, partialResults, m.sort, m.tac), false
}
// Reset is called to interrupt/signal the ongoing search
func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final bool, sort bool) {
pattern := m.patternBuilder(patternRunes)
var event util.EventType
if cancel {
event = reqReset
} else {
event = reqRetry
}
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort})
}

121
src/merger.go Normal file
View File

@ -0,0 +1,121 @@
package fzf
import "fmt"
// EmptyMerger is a Merger with no data
var EmptyMerger = NewMerger(nil, [][]Result{}, false, false)
// Merger holds a set of locally sorted lists of items and provides the view of
// a single, globally-sorted list
type Merger struct {
pattern *Pattern
lists [][]Result
merged []Result
chunks *[]*Chunk
cursors []int
sorted bool
tac bool
final bool
count int
}
// PassMerger returns a new Merger that simply returns the items in the
// original order
func PassMerger(chunks *[]*Chunk, tac bool) *Merger {
mg := Merger{
pattern: nil,
chunks: chunks,
tac: tac,
count: 0}
for _, chunk := range *mg.chunks {
mg.count += chunk.count
}
return &mg
}
// NewMerger returns a new Merger
func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool) *Merger {
mg := Merger{
pattern: pattern,
lists: lists,
merged: []Result{},
chunks: nil,
cursors: make([]int, len(lists)),
sorted: sorted,
tac: tac,
final: false,
count: 0}
for _, list := range mg.lists {
mg.count += len(list)
}
return &mg
}
// Length returns the number of items
func (mg *Merger) Length() int {
return mg.count
}
// Get returns the pointer to the Result object indexed by the given integer
func (mg *Merger) Get(idx int) Result {
if mg.chunks != nil {
if mg.tac {
idx = mg.count - idx - 1
}
chunk := (*mg.chunks)[idx/chunkSize]
return Result{item: &chunk.items[idx%chunkSize]}
}
if mg.sorted {
return mg.mergedGet(idx)
}
if mg.tac {
idx = mg.count - idx - 1
}
for _, list := range mg.lists {
numItems := len(list)
if idx < numItems {
return list[idx]
}
idx -= numItems
}
panic(fmt.Sprintf("Index out of bounds (unsorted, %d/%d)", idx, mg.count))
}
func (mg *Merger) cacheable() bool {
return mg.count < mergerCacheMax
}
func (mg *Merger) mergedGet(idx int) Result {
for i := len(mg.merged); i <= idx; i++ {
minRank := minRank()
minIdx := -1
for listIdx, list := range mg.lists {
cursor := mg.cursors[listIdx]
if cursor < 0 || cursor == len(list) {
mg.cursors[listIdx] = -1
continue
}
if cursor >= 0 {
rank := list[cursor]
if minIdx < 0 || compareRanks(rank, minRank, mg.tac) {
minRank = rank
minIdx = listIdx
}
}
mg.cursors[listIdx] = cursor
}
if minIdx >= 0 {
chosen := mg.lists[minIdx]
mg.merged = append(mg.merged, chosen[mg.cursors[minIdx]])
mg.cursors[minIdx]++
} else {
panic(fmt.Sprintf("Index out of bounds (sorted, %d/%d)", i, mg.count))
}
}
return mg.merged[idx]
}

88
src/merger_test.go Normal file
View File

@ -0,0 +1,88 @@
package fzf
import (
"fmt"
"math/rand"
"sort"
"testing"
"github.com/junegunn/fzf/src/util"
)
func assert(t *testing.T, cond bool, msg ...string) {
if !cond {
t.Error(msg)
}
}
func randResult() Result {
str := fmt.Sprintf("%d", rand.Uint32())
chars := util.ToChars([]byte(str))
chars.Index = rand.Int31()
return Result{item: &Item{text: chars}}
}
func TestEmptyMerger(t *testing.T) {
assert(t, EmptyMerger.Length() == 0, "Not empty")
assert(t, EmptyMerger.count == 0, "Invalid count")
assert(t, len(EmptyMerger.lists) == 0, "Invalid lists")
assert(t, len(EmptyMerger.merged) == 0, "Invalid merged list")
}
func buildLists(partiallySorted bool) ([][]Result, []Result) {
numLists := 4
lists := make([][]Result, numLists)
cnt := 0
for i := 0; i < numLists; i++ {
numResults := rand.Int() % 20
cnt += numResults
lists[i] = make([]Result, numResults)
for j := 0; j < numResults; j++ {
item := randResult()
lists[i][j] = item
}
if partiallySorted {
sort.Sort(ByRelevance(lists[i]))
}
}
items := []Result{}
for _, list := range lists {
items = append(items, list...)
}
return lists, items
}
func TestMergerUnsorted(t *testing.T) {
lists, items := buildLists(false)
cnt := len(items)
// Not sorted: same order
mg := NewMerger(nil, lists, false, false)
assert(t, cnt == mg.Length(), "Invalid Length")
for i := 0; i < cnt; i++ {
assert(t, items[i] == mg.Get(i), "Invalid Get")
}
}
func TestMergerSorted(t *testing.T) {
lists, items := buildLists(true)
cnt := len(items)
// Sorted sorted order
mg := NewMerger(nil, lists, true, false)
assert(t, cnt == mg.Length(), "Invalid Length")
sort.Sort(ByRelevance(items))
for i := 0; i < cnt; i++ {
if items[i] != mg.Get(i) {
t.Error("Not sorted", items[i], mg.Get(i))
}
}
// Inverse order
mg2 := NewMerger(nil, lists, true, false)
for i := cnt - 1; i >= 0; i-- {
if items[i] != mg2.Get(i) {
t.Error("Not sorted", items[i], mg2.Get(i))
}
}
}

1255
src/options.go Normal file

File diff suppressed because it is too large Load Diff

423
src/options_test.go Normal file
View File

@ -0,0 +1,423 @@
package fzf
import (
"fmt"
"io/ioutil"
"testing"
"github.com/junegunn/fzf/src/tui"
)
func TestDelimiterRegex(t *testing.T) {
// Valid regex
delim := delimiterRegexp(".")
if delim.regex == nil || delim.str != nil {
t.Error(delim)
}
// Broken regex -> string
delim = delimiterRegexp("[0-9")
if delim.regex != nil || *delim.str != "[0-9" {
t.Error(delim)
}
// Valid regex
delim = delimiterRegexp("[0-9]")
if delim.regex.String() != "[0-9]" || delim.str != nil {
t.Error(delim)
}
// Tab character
delim = delimiterRegexp("\t")
if delim.regex != nil || *delim.str != "\t" {
t.Error(delim)
}
// Tab expression
delim = delimiterRegexp("\\t")
if delim.regex != nil || *delim.str != "\t" {
t.Error(delim)
}
// Tabs -> regex
delim = delimiterRegexp("\t+")
if delim.regex == nil || delim.str != nil {
t.Error(delim)
}
}
func TestDelimiterRegexString(t *testing.T) {
delim := delimiterRegexp("*")
tokens := Tokenize("-*--*---**---", delim)
if delim.regex != nil ||
tokens[0].text.ToString() != "-*" ||
tokens[1].text.ToString() != "--*" ||
tokens[2].text.ToString() != "---*" ||
tokens[3].text.ToString() != "*" ||
tokens[4].text.ToString() != "---" {
t.Errorf("%s %s %d", delim, tokens, len(tokens))
}
}
func TestDelimiterRegexRegex(t *testing.T) {
delim := delimiterRegexp("--\\*")
tokens := Tokenize("-*--*---**---", delim)
if delim.str != nil ||
tokens[0].text.ToString() != "-*--*" ||
tokens[1].text.ToString() != "---*" ||
tokens[2].text.ToString() != "*---" {
t.Errorf("%s %d", tokens, len(tokens))
}
}
func TestSplitNth(t *testing.T) {
{
ranges := splitNth("..")
if len(ranges) != 1 ||
ranges[0].begin != rangeEllipsis ||
ranges[0].end != rangeEllipsis {
t.Errorf("%s", ranges)
}
}
{
ranges := splitNth("..3,1..,2..3,4..-1,-3..-2,..,2,-2,2..-2,1..-1")
if len(ranges) != 10 ||
ranges[0].begin != rangeEllipsis || ranges[0].end != 3 ||
ranges[1].begin != rangeEllipsis || ranges[1].end != rangeEllipsis ||
ranges[2].begin != 2 || ranges[2].end != 3 ||
ranges[3].begin != 4 || ranges[3].end != rangeEllipsis ||
ranges[4].begin != -3 || ranges[4].end != -2 ||
ranges[5].begin != rangeEllipsis || ranges[5].end != rangeEllipsis ||
ranges[6].begin != 2 || ranges[6].end != 2 ||
ranges[7].begin != -2 || ranges[7].end != -2 ||
ranges[8].begin != 2 || ranges[8].end != -2 ||
ranges[9].begin != rangeEllipsis || ranges[9].end != rangeEllipsis {
t.Errorf("%s", ranges)
}
}
}
func TestIrrelevantNth(t *testing.T) {
{
opts := defaultOptions()
words := []string{"--nth", "..", "-x"}
parseOptions(opts, words)
postProcessOptions(opts)
if len(opts.Nth) != 0 {
t.Errorf("nth should be empty: %s", opts.Nth)
}
}
for _, words := range [][]string{[]string{"--nth", "..,3", "+x"}, []string{"--nth", "3,1..", "+x"}, []string{"--nth", "..-1,1", "+x"}} {
{
opts := defaultOptions()
parseOptions(opts, words)
postProcessOptions(opts)
if len(opts.Nth) != 0 {
t.Errorf("nth should be empty: %s", opts.Nth)
}
}
{
opts := defaultOptions()
words = append(words, "-x")
parseOptions(opts, words)
postProcessOptions(opts)
if len(opts.Nth) != 2 {
t.Errorf("nth should not be empty: %s", opts.Nth)
}
}
}
}
func TestParseKeys(t *testing.T) {
pairs := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ctrl-alt-a,ALT-enter,alt-SPACE", "")
check := func(i int, s string) {
if pairs[i] != s {
t.Errorf("%s != %s", pairs[i], s)
}
}
if len(pairs) != 12 {
t.Error(12)
}
check(tui.CtrlZ, "ctrl-z")
check(tui.AltZ, "alt-z")
check(tui.F2, "f2")
check(tui.AltZ+'@', "@")
check(tui.AltA, "Alt-a")
check(tui.AltZ+'!', "!")
check(tui.CtrlA+'g'-'a', "ctrl-G")
check(tui.AltZ+'J', "J")
check(tui.AltZ+'g', "g")
check(tui.CtrlAltA, "ctrl-alt-a")
check(tui.CtrlAltM, "ALT-enter")
check(tui.AltSpace, "alt-SPACE")
// Synonyms
pairs = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
if len(pairs) != 9 {
t.Error(9)
}
check(tui.CtrlM, "Return")
check(tui.AltZ+' ', "space")
check(tui.Tab, "tab")
check(tui.BTab, "btab")
check(tui.ESC, "esc")
check(tui.Up, "up")
check(tui.Down, "down")
check(tui.Left, "left")
check(tui.Right, "right")
pairs = parseKeyChords("Tab,Ctrl-I,PgUp,page-up,pgdn,Page-Down,Home,End,Alt-BS,Alt-BSpace,shift-left,shift-right,btab,shift-tab,return,Enter,bspace", "")
if len(pairs) != 11 {
t.Error(11)
}
check(tui.Tab, "Ctrl-I")
check(tui.PgUp, "page-up")
check(tui.PgDn, "Page-Down")
check(tui.Home, "Home")
check(tui.End, "End")
check(tui.AltBS, "Alt-BSpace")
check(tui.SLeft, "shift-left")
check(tui.SRight, "shift-right")
check(tui.BTab, "shift-tab")
check(tui.CtrlM, "Enter")
check(tui.BSpace, "bspace")
}
func TestParseKeysWithComma(t *testing.T) {
checkN := func(a int, b int) {
if a != b {
t.Errorf("%d != %d", a, b)
}
}
check := func(pairs map[int]string, i int, s string) {
if pairs[i] != s {
t.Errorf("%s != %s", pairs[i], s)
}
}
pairs := parseKeyChords(",", "")
checkN(len(pairs), 1)
check(pairs, tui.AltZ+',', ",")
pairs = parseKeyChords(",,a,b", "")
checkN(len(pairs), 3)
check(pairs, tui.AltZ+'a', "a")
check(pairs, tui.AltZ+'b', "b")
check(pairs, tui.AltZ+',', ",")
pairs = parseKeyChords("a,b,,", "")
checkN(len(pairs), 3)
check(pairs, tui.AltZ+'a', "a")
check(pairs, tui.AltZ+'b', "b")
check(pairs, tui.AltZ+',', ",")
pairs = parseKeyChords("a,,,b", "")
checkN(len(pairs), 3)
check(pairs, tui.AltZ+'a', "a")
check(pairs, tui.AltZ+'b', "b")
check(pairs, tui.AltZ+',', ",")
pairs = parseKeyChords("a,,,b,c", "")
checkN(len(pairs), 4)
check(pairs, tui.AltZ+'a', "a")
check(pairs, tui.AltZ+'b', "b")
check(pairs, tui.AltZ+'c', "c")
check(pairs, tui.AltZ+',', ",")
pairs = parseKeyChords(",,,", "")
checkN(len(pairs), 1)
check(pairs, tui.AltZ+',', ",")
}
func TestBind(t *testing.T) {
keymap := defaultKeymap()
check := func(keyName int, arg1 string, types ...actionType) {
if len(keymap[keyName]) != len(types) {
t.Errorf("invalid number of actions (%d != %d)", len(types), len(keymap[keyName]))
return
}
for idx, action := range keymap[keyName] {
if types[idx] != action.t {
t.Errorf("invalid action type (%d != %d)", types[idx], action.t)
}
}
if len(arg1) > 0 && keymap[keyName][0].a != arg1 {
t.Errorf("invalid action argument: (%s != %s)", arg1, keymap[keyName][0].a)
}
}
check(tui.CtrlA, "", actBeginningOfLine)
parseKeymap(keymap,
"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 {};,"+
"alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+
"x:Execute(foo+bar),X:execute/bar+baz/"+
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up")
check(tui.CtrlA, "", actKillLine)
check(tui.CtrlB, "", actToggleSort, actUp, actDown)
check(tui.AltZ+'c', "", actPageUp)
check(tui.AltZ+',', "", actAbort)
check(tui.AltZ+':', "", actAccept)
check(tui.AltZ, "", actPageDown)
check(tui.F1, "ls {}", actExecute, actAbort)
check(tui.F2, "echo {}, {}, {}", actExecute)
check(tui.F3, "echo '({})'", actExecute)
check(tui.F4, "less {}", actExecute)
check(tui.AltZ+'x', "foo+bar", actExecute)
check(tui.AltZ+'X', "bar+baz", actExecute)
check(tui.AltA, "echo (,),[,],/,:,;,%,{}", actExecuteMulti)
check(tui.AltB, "echo (,),[,],/,:,@,%,{}", actExecute)
check(tui.AltZ+'+', "++\nfoobar,Y:execute(baz)+up", actExecute)
for idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} {
parseKeymap(keymap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char))
check(tui.AltZ+int([]rune(fmt.Sprintf("%d", idx%10))[0]), "foobar", actExecute)
}
parseKeymap(keymap, "f1:abort")
check(tui.F1, "", actAbort)
}
func TestColorSpec(t *testing.T) {
theme := tui.Dark256
dark := parseTheme(theme, "dark")
if *dark != *theme {
t.Errorf("colors should be equivalent")
}
if dark == theme {
t.Errorf("point should not be equivalent")
}
light := parseTheme(theme, "dark,light")
if *light == *theme {
t.Errorf("should not be equivalent")
}
if *light != *tui.Light256 {
t.Errorf("colors should be equivalent")
}
if light == theme {
t.Errorf("point should not be equivalent")
}
customized := parseTheme(theme, "fg:231,bg:232")
if customized.Fg != 231 || customized.Bg != 232 {
t.Errorf("color not customized")
}
if *tui.Dark256 == *customized {
t.Errorf("colors should not be equivalent")
}
customized.Fg = tui.Dark256.Fg
customized.Bg = tui.Dark256.Bg
if *tui.Dark256 != *customized {
t.Errorf("colors should now be equivalent: %v, %v", tui.Dark256, customized)
}
customized = parseTheme(theme, "fg:231,dark,bg:232")
if customized.Fg != tui.Dark256.Fg || customized.Bg == tui.Dark256.Bg {
t.Errorf("color not customized")
}
}
func TestParseNilTheme(t *testing.T) {
var theme *tui.ColorTheme
newTheme := parseTheme(theme, "prompt:12")
if newTheme != nil {
t.Errorf("color is disabled. keep it that way.")
}
newTheme = parseTheme(theme, "prompt:12,dark,prompt:13")
if newTheme.Prompt != 13 {
t.Errorf("color should now be enabled and customized")
}
}
func TestDefaultCtrlNP(t *testing.T) {
check := func(words []string, key int, expected actionType) {
opts := defaultOptions()
parseOptions(opts, words)
postProcessOptions(opts)
if opts.Keymap[key][0].t != expected {
t.Error()
}
}
check([]string{}, tui.CtrlN, actDown)
check([]string{}, tui.CtrlP, actUp)
check([]string{"--bind=ctrl-n:accept"}, tui.CtrlN, actAccept)
check([]string{"--bind=ctrl-p:accept"}, tui.CtrlP, actAccept)
f, _ := ioutil.TempFile("", "fzf-history")
f.Close()
hist := "--history=" + f.Name()
check([]string{hist}, tui.CtrlN, actNextHistory)
check([]string{hist}, tui.CtrlP, actPreviousHistory)
check([]string{hist, "--bind=ctrl-n:accept"}, tui.CtrlN, actAccept)
check([]string{hist, "--bind=ctrl-n:accept"}, tui.CtrlP, actPreviousHistory)
check([]string{hist, "--bind=ctrl-p:accept"}, tui.CtrlN, actNextHistory)
check([]string{hist, "--bind=ctrl-p:accept"}, tui.CtrlP, actAccept)
}
func optsFor(words ...string) *Options {
opts := defaultOptions()
parseOptions(opts, words)
postProcessOptions(opts)
return opts
}
func TestToggle(t *testing.T) {
opts := optsFor()
if opts.ToggleSort {
t.Error()
}
opts = optsFor("--bind=a:toggle-sort")
if !opts.ToggleSort {
t.Error()
}
opts = optsFor("--bind=a:toggle-sort", "--bind=a:up")
if opts.ToggleSort {
t.Error()
}
}
func TestPreviewOpts(t *testing.T) {
opts := optsFor()
if !(opts.Preview.command == "" &&
opts.Preview.hidden == false &&
opts.Preview.wrap == false &&
opts.Preview.position == posRight &&
opts.Preview.size.percent == true &&
opts.Preview.size.size == 50) {
t.Error()
}
opts = optsFor("--preview", "cat {}", "--preview-window=left:15:hidden:wrap")
if !(opts.Preview.command == "cat {}" &&
opts.Preview.hidden == true &&
opts.Preview.wrap == true &&
opts.Preview.position == posLeft &&
opts.Preview.size.percent == false &&
opts.Preview.size.size == 15+2+2) {
t.Error(opts.Preview)
}
opts = optsFor("--preview-window=up:15:wrap:hidden", "--preview-window=down")
if !(opts.Preview.command == "" &&
opts.Preview.hidden == false &&
opts.Preview.wrap == false &&
opts.Preview.position == posDown &&
opts.Preview.size.percent == true &&
opts.Preview.size.size == 50) {
t.Error(opts.Preview)
}
opts = optsFor("--preview-window=up:15:wrap:hidden")
if !(opts.Preview.command == "" &&
opts.Preview.hidden == true &&
opts.Preview.wrap == true &&
opts.Preview.position == posUp &&
opts.Preview.size.percent == false &&
opts.Preview.size.size == 15+2) {
t.Error(opts.Preview)
}
}
func TestAdditiveExpect(t *testing.T) {
opts := optsFor("--expect=a", "--expect", "b", "--expect=c")
if len(opts.Expect) != 3 {
t.Error(opts.Expect)
}
}

402
src/pattern.go Normal file
View File

@ -0,0 +1,402 @@
package fzf
import (
"regexp"
"strings"
"github.com/junegunn/fzf/src/algo"
"github.com/junegunn/fzf/src/util"
)
// fuzzy
// 'exact
// ^prefix-exact
// suffix-exact$
// !inverse-exact
// !'inverse-fuzzy
// !^inverse-prefix-exact
// !inverse-suffix-exact$
type termType int
const (
termFuzzy termType = iota
termExact
termPrefix
termSuffix
termEqual
)
type term struct {
typ termType
inv bool
text []rune
caseSensitive bool
}
type termSet []term
// Pattern represents search pattern
type Pattern struct {
fuzzy bool
fuzzyAlgo algo.Algo
extended bool
caseSensitive bool
normalize bool
forward bool
text []rune
termSets []termSet
cacheable bool
cacheKey string
delimiter Delimiter
nth []Range
procFun map[termType]algo.Algo
}
var (
_patternCache map[string]*Pattern
_splitRegex *regexp.Regexp
_cache ChunkCache
)
func init() {
_splitRegex = regexp.MustCompile(" +")
clearPatternCache()
clearChunkCache()
}
func clearPatternCache() {
// We can uniquely identify the pattern for a given string since
// search mode and caseMode do not change while the program is running
_patternCache = make(map[string]*Pattern)
}
func clearChunkCache() {
_cache = NewChunkCache()
}
// BuildPattern builds Pattern object from the given arguments
func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool,
cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
var asString string
if extended {
asString = strings.TrimLeft(string(runes), " ")
for strings.HasSuffix(asString, " ") && !strings.HasSuffix(asString, "\\ ") {
asString = asString[:len(asString)-1]
}
} else {
asString = string(runes)
}
cached, found := _patternCache[asString]
if found {
return cached
}
caseSensitive := true
termSets := []termSet{}
if extended {
termSets = parseTerms(fuzzy, caseMode, normalize, asString)
Loop:
for _, termSet := range termSets {
for idx, term := range termSet {
// If the query contains inverse search terms or OR operators,
// we cannot cache the search scope
if !cacheable || idx > 0 || term.inv || fuzzy && term.typ != termFuzzy || !fuzzy && term.typ != termExact {
cacheable = false
break Loop
}
}
}
} else {
lowerString := strings.ToLower(asString)
caseSensitive = caseMode == CaseRespect ||
caseMode == CaseSmart && lowerString != asString
if !caseSensitive {
asString = lowerString
}
}
ptr := &Pattern{
fuzzy: fuzzy,
fuzzyAlgo: fuzzyAlgo,
extended: extended,
caseSensitive: caseSensitive,
normalize: normalize,
forward: forward,
text: []rune(asString),
termSets: termSets,
cacheable: cacheable,
nth: nth,
delimiter: delimiter,
procFun: make(map[termType]algo.Algo)}
ptr.cacheKey = ptr.buildCacheKey()
ptr.procFun[termFuzzy] = fuzzyAlgo
ptr.procFun[termEqual] = algo.EqualMatch
ptr.procFun[termExact] = algo.ExactMatchNaive
ptr.procFun[termPrefix] = algo.PrefixMatch
ptr.procFun[termSuffix] = algo.SuffixMatch
_patternCache[asString] = ptr
return ptr
}
func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet {
str = strings.Replace(str, "\\ ", "\t", -1)
tokens := _splitRegex.Split(str, -1)
sets := []termSet{}
set := termSet{}
switchSet := false
afterBar := false
for _, token := range tokens {
typ, inv, text := termFuzzy, false, strings.Replace(token, "\t", " ", -1)
lowerText := strings.ToLower(text)
caseSensitive := caseMode == CaseRespect ||
caseMode == CaseSmart && text != lowerText
if !caseSensitive {
text = lowerText
}
if !fuzzy {
typ = termExact
}
if len(set) > 0 && !afterBar && text == "|" {
switchSet = false
afterBar = true
continue
}
afterBar = false
if strings.HasPrefix(text, "!") {
inv = true
typ = termExact
text = text[1:]
}
if text != "$" && strings.HasSuffix(text, "$") {
typ = termSuffix
text = text[:len(text)-1]
}
if strings.HasPrefix(text, "'") {
// Flip exactness
if fuzzy && !inv {
typ = termExact
text = text[1:]
} else {
typ = termFuzzy
text = text[1:]
}
} else if strings.HasPrefix(text, "^") {
if typ == termSuffix {
typ = termEqual
} else {
typ = termPrefix
}
text = text[1:]
}
if len(text) > 0 {
if switchSet {
sets = append(sets, set)
set = termSet{}
}
textRunes := []rune(text)
if normalize {
textRunes = algo.NormalizeRunes(textRunes)
}
set = append(set, term{
typ: typ,
inv: inv,
text: textRunes,
caseSensitive: caseSensitive})
switchSet = true
}
}
if len(set) > 0 {
sets = append(sets, set)
}
return sets
}
// IsEmpty returns true if the pattern is effectively empty
func (p *Pattern) IsEmpty() bool {
if !p.extended {
return len(p.text) == 0
}
return len(p.termSets) == 0
}
// AsString returns the search query in string type
func (p *Pattern) AsString() string {
return string(p.text)
}
func (p *Pattern) buildCacheKey() string {
if !p.extended {
return p.AsString()
}
cacheableTerms := []string{}
for _, termSet := range p.termSets {
if len(termSet) == 1 && !termSet[0].inv && (p.fuzzy || termSet[0].typ == termExact) {
cacheableTerms = append(cacheableTerms, string(termSet[0].text))
}
}
return strings.Join(cacheableTerms, "\t")
}
// CacheKey is used to build string to be used as the key of result cache
func (p *Pattern) CacheKey() string {
return p.cacheKey
}
// Match returns the list of matches Items in the given Chunk
func (p *Pattern) Match(chunk *Chunk, slab *util.Slab) []Result {
// ChunkCache: Exact match
cacheKey := p.CacheKey()
if p.cacheable {
if cached := _cache.Lookup(chunk, cacheKey); cached != nil {
return cached
}
}
// Prefix/suffix cache
space := _cache.Search(chunk, cacheKey)
matches := p.matchChunk(chunk, space, slab)
if p.cacheable {
_cache.Add(chunk, cacheKey, matches)
}
return matches
}
func (p *Pattern) matchChunk(chunk *Chunk, space []Result, slab *util.Slab) []Result {
matches := []Result{}
if space == nil {
for idx := 0; idx < chunk.count; idx++ {
if match, _, _ := p.MatchItem(&chunk.items[idx], false, slab); match != nil {
matches = append(matches, *match)
}
}
} else {
for _, result := range space {
if match, _, _ := p.MatchItem(result.item, false, slab); match != nil {
matches = append(matches, *match)
}
}
}
return matches
}
// MatchItem returns true if the Item is a match
func (p *Pattern) MatchItem(item *Item, withPos bool, slab *util.Slab) (*Result, []Offset, *[]int) {
if p.extended {
if offsets, bonus, pos := p.extendedMatch(item, withPos, slab); len(offsets) == len(p.termSets) {
result := buildResult(item, offsets, bonus)
return &result, offsets, pos
}
return nil, nil, nil
}
offset, bonus, pos := p.basicMatch(item, withPos, slab)
if sidx := offset[0]; sidx >= 0 {
offsets := []Offset{offset}
result := buildResult(item, offsets, bonus)
return &result, offsets, pos
}
return nil, nil, nil
}
func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset, int, *[]int) {
var input []Token
if len(p.nth) == 0 {
input = []Token{Token{text: &item.text, prefixLength: 0}}
} else {
input = p.transformInput(item)
}
if p.fuzzy {
return p.iter(p.fuzzyAlgo, input, p.caseSensitive, p.normalize, p.forward, p.text, withPos, slab)
}
return p.iter(algo.ExactMatchNaive, input, p.caseSensitive, p.normalize, p.forward, p.text, withPos, slab)
}
func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Offset, int, *[]int) {
var input []Token
if len(p.nth) == 0 {
input = []Token{Token{text: &item.text, prefixLength: 0}}
} else {
input = p.transformInput(item)
}
offsets := []Offset{}
var totalScore int
var allPos *[]int
if withPos {
allPos = &[]int{}
}
for _, termSet := range p.termSets {
var offset Offset
var currentScore int
matched := false
for _, term := range termSet {
pfun := p.procFun[term.typ]
off, score, pos := p.iter(pfun, input, term.caseSensitive, p.normalize, p.forward, term.text, withPos, slab)
if sidx := off[0]; sidx >= 0 {
if term.inv {
continue
}
offset, currentScore = off, score
matched = true
if withPos {
if pos != nil {
*allPos = append(*allPos, *pos...)
} else {
for idx := off[0]; idx < off[1]; idx++ {
*allPos = append(*allPos, int(idx))
}
}
}
break
} else if term.inv {
offset, currentScore = Offset{0, 0}, 0
matched = true
continue
}
}
if matched {
offsets = append(offsets, offset)
totalScore += currentScore
}
}
return offsets, totalScore, allPos
}
func (p *Pattern) transformInput(item *Item) []Token {
if item.transformed != nil {
return *item.transformed
}
tokens := Tokenize(item.text.ToString(), p.delimiter)
ret := Transform(tokens, p.nth)
item.transformed = &ret
return ret
}
func (p *Pattern) iter(pfun algo.Algo, tokens []Token, caseSensitive bool, normalize bool, forward bool, pattern []rune, withPos bool, slab *util.Slab) (Offset, int, *[]int) {
for _, part := range tokens {
if res, pos := pfun(caseSensitive, normalize, forward, part.text, pattern, withPos, slab); res.Start >= 0 {
sidx := int32(res.Start) + part.prefixLength
eidx := int32(res.End) + part.prefixLength
if pos != nil {
for idx := range *pos {
(*pos)[idx] += int(part.prefixLength)
}
}
return Offset{sidx, eidx}, res.Score, pos
}
}
return Offset{-1, -1}, 0, nil
}

206
src/pattern_test.go Normal file
View File

@ -0,0 +1,206 @@
package fzf
import (
"reflect"
"testing"
"github.com/junegunn/fzf/src/algo"
"github.com/junegunn/fzf/src/util"
)
var slab *util.Slab
func init() {
slab = util.MakeSlab(slab16Size, slab32Size)
}
func TestParseTermsExtended(t *testing.T) {
terms := parseTerms(true, CaseSmart, false,
"aaa 'bbb ^ccc ddd$ !eee !'fff !^ggg !hhh$ | ^iii$ ^xxx | 'yyy | zzz$ | !ZZZ |")
if len(terms) != 9 ||
terms[0][0].typ != termFuzzy || terms[0][0].inv ||
terms[1][0].typ != termExact || terms[1][0].inv ||
terms[2][0].typ != termPrefix || terms[2][0].inv ||
terms[3][0].typ != termSuffix || terms[3][0].inv ||
terms[4][0].typ != termExact || !terms[4][0].inv ||
terms[5][0].typ != termFuzzy || !terms[5][0].inv ||
terms[6][0].typ != termPrefix || !terms[6][0].inv ||
terms[7][0].typ != termSuffix || !terms[7][0].inv ||
terms[7][1].typ != termEqual || terms[7][1].inv ||
terms[8][0].typ != termPrefix || terms[8][0].inv ||
terms[8][1].typ != termExact || terms[8][1].inv ||
terms[8][2].typ != termSuffix || terms[8][2].inv ||
terms[8][3].typ != termExact || !terms[8][3].inv {
t.Errorf("%s", terms)
}
for _, termSet := range terms[:8] {
term := termSet[0]
if len(term.text) != 3 {
t.Errorf("%s", term)
}
}
}
func TestParseTermsExtendedExact(t *testing.T) {
terms := parseTerms(false, CaseSmart, false,
"aaa 'bbb ^ccc ddd$ !eee !'fff !^ggg !hhh$")
if len(terms) != 8 ||
terms[0][0].typ != termExact || terms[0][0].inv || len(terms[0][0].text) != 3 ||
terms[1][0].typ != termFuzzy || terms[1][0].inv || len(terms[1][0].text) != 3 ||
terms[2][0].typ != termPrefix || terms[2][0].inv || len(terms[2][0].text) != 3 ||
terms[3][0].typ != termSuffix || terms[3][0].inv || len(terms[3][0].text) != 3 ||
terms[4][0].typ != termExact || !terms[4][0].inv || len(terms[4][0].text) != 3 ||
terms[5][0].typ != termFuzzy || !terms[5][0].inv || len(terms[5][0].text) != 3 ||
terms[6][0].typ != termPrefix || !terms[6][0].inv || len(terms[6][0].text) != 3 ||
terms[7][0].typ != termSuffix || !terms[7][0].inv || len(terms[7][0].text) != 3 {
t.Errorf("%s", terms)
}
}
func TestParseTermsEmpty(t *testing.T) {
terms := parseTerms(true, CaseSmart, false, "' ^ !' !^")
if len(terms) != 0 {
t.Errorf("%s", terms)
}
}
func TestExact(t *testing.T) {
defer clearPatternCache()
clearPatternCache()
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true,
[]Range{}, Delimiter{}, []rune("'abc"))
chars := util.ToChars([]byte("aabbcc abc"))
res, pos := algo.ExactMatchNaive(
pattern.caseSensitive, pattern.normalize, pattern.forward, &chars, pattern.termSets[0][0].text, true, nil)
if res.Start != 7 || res.End != 10 {
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
}
if pos != nil {
t.Errorf("pos is expected to be nil")
}
}
func TestEqual(t *testing.T) {
defer clearPatternCache()
clearPatternCache()
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("^AbC$"))
match := func(str string, sidxExpected int, eidxExpected int) {
chars := util.ToChars([]byte(str))
res, pos := algo.EqualMatch(
pattern.caseSensitive, pattern.normalize, pattern.forward, &chars, pattern.termSets[0][0].text, true, nil)
if res.Start != sidxExpected || res.End != eidxExpected {
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
}
if pos != nil {
t.Errorf("pos is expected to be nil")
}
}
match("ABC", -1, -1)
match("AbC", 0, 3)
}
func TestCaseSensitivity(t *testing.T) {
defer clearPatternCache()
clearPatternCache()
pat1 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("abc"))
clearPatternCache()
pat2 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("Abc"))
clearPatternCache()
pat3 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, true, []Range{}, Delimiter{}, []rune("abc"))
clearPatternCache()
pat4 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, true, []Range{}, Delimiter{}, []rune("Abc"))
clearPatternCache()
pat5 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, true, []Range{}, Delimiter{}, []rune("abc"))
clearPatternCache()
pat6 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, true, []Range{}, Delimiter{}, []rune("Abc"))
if string(pat1.text) != "abc" || pat1.caseSensitive != false ||
string(pat2.text) != "Abc" || pat2.caseSensitive != true ||
string(pat3.text) != "abc" || pat3.caseSensitive != false ||
string(pat4.text) != "abc" || pat4.caseSensitive != false ||
string(pat5.text) != "abc" || pat5.caseSensitive != true ||
string(pat6.text) != "Abc" || pat6.caseSensitive != true {
t.Error("Invalid case conversion")
}
}
func TestOrigTextAndTransformed(t *testing.T) {
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("jg"))
tokens := Tokenize("junegunn", Delimiter{})
trans := Transform(tokens, []Range{Range{1, 1}})
origBytes := []byte("junegunn.choi")
for _, extended := range []bool{false, true} {
chunk := Chunk{count: 1}
chunk.items[0] = Item{
text: util.ToChars([]byte("junegunn")),
origText: &origBytes,
transformed: &trans}
pattern.extended = extended
matches := pattern.matchChunk(&chunk, nil, slab) // No cache
if !(matches[0].item.text.ToString() == "junegunn" &&
string(*matches[0].item.origText) == "junegunn.choi" &&
reflect.DeepEqual(*matches[0].item.transformed, trans)) {
t.Error("Invalid match result", matches)
}
match, offsets, pos := pattern.MatchItem(&chunk.items[0], true, slab)
if !(match.item.text.ToString() == "junegunn" &&
string(*match.item.origText) == "junegunn.choi" &&
offsets[0][0] == 0 && offsets[0][1] == 5 &&
reflect.DeepEqual(*match.item.transformed, trans)) {
t.Error("Invalid match result", match, offsets, extended)
}
if !((*pos)[0] == 4 && (*pos)[1] == 0) {
t.Error("Invalid pos array", *pos)
}
}
}
func TestCacheKey(t *testing.T) {
test := func(extended bool, patStr string, expected string, cacheable bool) {
clearPatternCache()
pat := BuildPattern(true, algo.FuzzyMatchV2, extended, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune(patStr))
if pat.CacheKey() != expected {
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
}
if pat.cacheable != cacheable {
t.Errorf("Expected: %t, actual: %t (%s)", cacheable, pat.cacheable, patStr)
}
clearPatternCache()
}
test(false, "foo !bar", "foo !bar", true)
test(false, "foo | bar !baz", "foo | bar !baz", true)
test(true, "foo bar baz", "foo\tbar\tbaz", true)
test(true, "foo !bar", "foo", false)
test(true, "foo !bar baz", "foo\tbaz", false)
test(true, "foo | bar baz", "baz", false)
test(true, "foo | bar | baz", "", false)
test(true, "foo | bar !baz", "", false)
test(true, "| | foo", "", false)
test(true, "| | | foo", "foo", false)
}
func TestCacheable(t *testing.T) {
test := func(fuzzy bool, str string, expected string, cacheable bool) {
clearPatternCache()
pat := BuildPattern(fuzzy, algo.FuzzyMatchV2, true, CaseSmart, true, true, true, []Range{}, Delimiter{}, []rune(str))
if pat.CacheKey() != expected {
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
}
if cacheable != pat.cacheable {
t.Errorf("Invalid Pattern.cacheable for \"%s\": %v (expected: %v)", str, pat.cacheable, cacheable)
}
clearPatternCache()
}
test(true, "foo bar", "foo\tbar", true)
test(true, "foo 'bar", "foo\tbar", false)
test(true, "foo !bar", "foo", false)
test(false, "foo bar", "foo\tbar", true)
test(false, "foo 'bar", "foo", false)
test(false, "foo '", "foo", true)
test(false, "foo 'bar", "foo", false)
test(false, "foo !bar", "foo", false)
}

117
src/reader.go Normal file
View File

@ -0,0 +1,117 @@
package fzf
import (
"bufio"
"io"
"os"
"sync/atomic"
"time"
"github.com/junegunn/fzf/src/util"
)
// Reader reads from command or standard input
type Reader struct {
pusher func([]byte) bool
eventBox *util.EventBox
delimNil bool
event int32
}
// NewReader returns new Reader object
func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, delimNil bool) *Reader {
return &Reader{pusher, eventBox, delimNil, int32(EvtReady)}
}
func (r *Reader) startEventPoller() {
go func() {
ptr := &r.event
pollInterval := readerPollIntervalMin
for {
if atomic.CompareAndSwapInt32(ptr, int32(EvtReadNew), int32(EvtReady)) {
r.eventBox.Set(EvtReadNew, true)
pollInterval = readerPollIntervalMin
} else if atomic.LoadInt32(ptr) == int32(EvtReadFin) {
return
} else {
pollInterval += readerPollIntervalStep
if pollInterval > readerPollIntervalMax {
pollInterval = readerPollIntervalMax
}
}
time.Sleep(pollInterval)
}
}()
}
func (r *Reader) fin(success bool) {
atomic.StoreInt32(&r.event, int32(EvtReadFin))
r.eventBox.Set(EvtReadFin, success)
}
// ReadSource reads data from the default command or from standard input
func (r *Reader) ReadSource() {
r.startEventPoller()
var success bool
if util.IsTty() {
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
if len(cmd) == 0 {
// The default command for *nix requires bash
success = r.readFromCommand("bash", defaultCommand)
} else {
success = r.readFromCommand("sh", cmd)
}
} else {
success = r.readFromStdin()
}
r.fin(success)
}
func (r *Reader) feed(src io.Reader) {
delim := byte('\n')
if r.delimNil {
delim = '\000'
}
reader := bufio.NewReaderSize(src, readerBufferSize)
for {
// ReadBytes returns err != nil if and only if the returned data does not
// end in delim.
bytea, err := reader.ReadBytes(delim)
byteaLen := len(bytea)
if byteaLen > 0 {
if err == nil {
// get rid of carriage return if under Windows:
if util.IsWindows() && byteaLen >= 2 && bytea[byteaLen-2] == byte('\r') {
bytea = bytea[:byteaLen-2]
} else {
bytea = bytea[:byteaLen-1]
}
}
if r.pusher(bytea) {
atomic.StoreInt32(&r.event, int32(EvtReadNew))
}
}
if err != nil {
break
}
}
}
func (r *Reader) readFromStdin() bool {
r.feed(os.Stdin)
return true
}
func (r *Reader) readFromCommand(shell string, cmd string) bool {
listCommand := util.ExecCommandWith(shell, cmd)
out, err := listCommand.StdoutPipe()
if err != nil {
return false
}
err = listCommand.Start()
if err != nil {
return false
}
r.feed(out)
return listCommand.Wait() == nil
}

64
src/reader_test.go Normal file
View File

@ -0,0 +1,64 @@
package fzf
import (
"testing"
"time"
"github.com/junegunn/fzf/src/util"
)
func TestReadFromCommand(t *testing.T) {
strs := []string{}
eb := util.NewEventBox()
reader := Reader{
pusher: func(s []byte) bool { strs = append(strs, string(s)); return true },
eventBox: eb,
event: int32(EvtReady)}
reader.startEventPoller()
// Check EventBox
if eb.Peek(EvtReadNew) {
t.Error("EvtReadNew should not be set yet")
}
// Normal command
reader.fin(reader.readFromCommand("sh", `echo abc && echo def`))
if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" {
t.Errorf("%s", strs)
}
// Check EventBox again
eb.WaitFor(EvtReadFin)
// Wait should return immediately
eb.Wait(func(events *util.Events) {
events.Clear()
})
// EventBox is cleared
if eb.Peek(EvtReadNew) {
t.Error("EvtReadNew should not be set yet")
}
// Make sure that event poller is finished
time.Sleep(readerPollIntervalMax)
// Restart event poller
reader.startEventPoller()
// Failing command
reader.fin(reader.readFromCommand("sh", `no-such-command`))
strs = []string{}
if len(strs) > 0 {
t.Errorf("%s", strs)
}
// Check EventBox again
if eb.Peek(EvtReadNew) {
t.Error("Command failed. EvtReadNew should not be set")
}
if !eb.Peek(EvtReadFin) {
t.Error("EvtReadFin should be set")
}
}

226
src/result.go Normal file
View File

@ -0,0 +1,226 @@
package fzf
import (
"math"
"sort"
"unicode"
"github.com/junegunn/fzf/src/tui"
"github.com/junegunn/fzf/src/util"
)
// Offset holds two 32-bit integers denoting the offsets of a matched substring
type Offset [2]int32
type colorOffset struct {
offset [2]int32
color tui.ColorPair
attr tui.Attr
index int32
}
type Result struct {
item *Item
points [4]uint16
}
func buildResult(item *Item, offsets []Offset, score int) Result {
if len(offsets) > 1 {
sort.Sort(ByOrder(offsets))
}
result := Result{item: item}
numChars := item.text.Length()
minBegin := math.MaxUint16
minEnd := math.MaxUint16
maxEnd := 0
validOffsetFound := false
for _, offset := range offsets {
b, e := int(offset[0]), int(offset[1])
if b < e {
minBegin = util.Min(b, minBegin)
minEnd = util.Min(e, minEnd)
maxEnd = util.Max(e, maxEnd)
validOffsetFound = true
}
}
for idx, criterion := range sortCriteria {
val := uint16(math.MaxUint16)
switch criterion {
case byScore:
// Higher is better
val = math.MaxUint16 - util.AsUint16(score)
case byLength:
val = item.TrimLength()
case byBegin, byEnd:
if validOffsetFound {
whitePrefixLen := 0
for idx := 0; idx < numChars; idx++ {
r := item.text.Get(idx)
whitePrefixLen = idx
if idx == minBegin || !unicode.IsSpace(r) {
break
}
}
if criterion == byBegin {
val = util.AsUint16(minEnd - whitePrefixLen)
} else {
val = util.AsUint16(math.MaxUint16 - math.MaxUint16*(maxEnd-whitePrefixLen)/int(item.TrimLength()))
}
}
}
result.points[3-idx] = val
}
return result
}
// Sort criteria to use. Never changes once fzf is started.
var sortCriteria []criterion
// Index returns ordinal index of the Item
func (result *Result) Index() int32 {
return result.item.Index()
}
func minRank() Result {
return Result{item: &minItem, points: [4]uint16{math.MaxUint16, 0, 0, 0}}
}
func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme, color tui.ColorPair, attr tui.Attr, current bool) []colorOffset {
itemColors := result.item.Colors()
// No ANSI code, or --color=no
if len(itemColors) == 0 {
var offsets []colorOffset
for _, off := range matchOffsets {
offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: color, attr: attr})
}
return offsets
}
// Find max column
var maxCol int32
for _, off := range matchOffsets {
if off[1] > maxCol {
maxCol = off[1]
}
}
for _, ansi := range itemColors {
if ansi.offset[1] > maxCol {
maxCol = ansi.offset[1]
}
}
cols := make([]int, maxCol)
for colorIndex, ansi := range itemColors {
for i := ansi.offset[0]; i < ansi.offset[1]; i++ {
cols[i] = colorIndex + 1 // XXX
}
}
for _, off := range matchOffsets {
for i := off[0]; i < off[1]; i++ {
cols[i] = -1
}
}
// sort.Sort(ByOrder(offsets))
// Merge offsets
// ------------ ---- -- ----
// ++++++++ ++++++++++
// --++++++++-- --++++++++++---
curr := 0
start := 0
var colors []colorOffset
add := func(idx int) {
if curr != 0 && idx > start {
if curr == -1 {
colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)}, color: color, attr: attr})
} else {
ansi := itemColors[curr-1]
fg := ansi.color.fg
bg := ansi.color.bg
if theme != nil {
if fg == -1 {
if current {
fg = theme.Current
} else {
fg = theme.Fg
}
}
if bg == -1 {
if current {
bg = theme.DarkBg
} else {
bg = theme.Bg
}
}
}
colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)},
color: tui.NewColorPair(fg, bg),
attr: ansi.color.attr.Merge(attr)})
}
}
}
for idx, col := range cols {
if col != curr {
add(idx)
start = idx
curr = col
}
}
add(int(maxCol))
return colors
}
// ByOrder is for sorting substring offsets
type ByOrder []Offset
func (a ByOrder) Len() int {
return len(a)
}
func (a ByOrder) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}
func (a ByOrder) Less(i, j int) bool {
ioff := a[i]
joff := a[j]
return (ioff[0] < joff[0]) || (ioff[0] == joff[0]) && (ioff[1] <= joff[1])
}
// ByRelevance is for sorting Items
type ByRelevance []Result
func (a ByRelevance) Len() int {
return len(a)
}
func (a ByRelevance) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}
func (a ByRelevance) Less(i, j int) bool {
return compareRanks(a[i], a[j], false)
}
// ByRelevanceTac is for sorting Items
type ByRelevanceTac []Result
func (a ByRelevanceTac) Len() int {
return len(a)
}
func (a ByRelevanceTac) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}
func (a ByRelevanceTac) Less(i, j int) bool {
return compareRanks(a[i], a[j], true)
}

16
src/result_others.go Normal file
View File

@ -0,0 +1,16 @@
// +build !386,!amd64
package fzf
func compareRanks(irank Result, jrank Result, tac bool) bool {
for idx := 3; idx >= 0; idx-- {
left := irank.points[idx]
right := jrank.points[idx]
if left < right {
return true
} else if left > right {
return false
}
}
return (irank.item.Index() <= jrank.item.Index()) != tac
}

136
src/result_test.go Normal file
View File

@ -0,0 +1,136 @@
// +build !tcell
package fzf
import (
"math"
"sort"
"testing"
"github.com/junegunn/fzf/src/tui"
"github.com/junegunn/fzf/src/util"
)
func withIndex(i *Item, index int) *Item {
(*i).text.Index = int32(index)
return i
}
func TestOffsetSort(t *testing.T) {
offsets := []Offset{
Offset{3, 5}, Offset{2, 7},
Offset{1, 3}, Offset{2, 9}}
sort.Sort(ByOrder(offsets))
if offsets[0][0] != 1 || offsets[0][1] != 3 ||
offsets[1][0] != 2 || offsets[1][1] != 7 ||
offsets[2][0] != 2 || offsets[2][1] != 9 ||
offsets[3][0] != 3 || offsets[3][1] != 5 {
t.Error("Invalid order:", offsets)
}
}
func TestRankComparison(t *testing.T) {
rank := func(vals ...uint16) Result {
return Result{
points: [4]uint16{vals[0], vals[1], vals[2], vals[3]},
item: &Item{text: util.Chars{Index: int32(vals[4])}}}
}
if compareRanks(rank(3, 0, 0, 0, 5), rank(2, 0, 0, 0, 7), false) ||
!compareRanks(rank(3, 0, 0, 0, 5), rank(3, 0, 0, 0, 6), false) ||
!compareRanks(rank(1, 2, 0, 0, 3), rank(1, 3, 0, 0, 2), false) ||
!compareRanks(rank(0, 0, 0, 0, 0), rank(0, 0, 0, 0, 0), false) {
t.Error("Invalid order")
}
if compareRanks(rank(3, 0, 0, 0, 5), rank(2, 0, 0, 0, 7), true) ||
!compareRanks(rank(3, 0, 0, 0, 5), rank(3, 0, 0, 0, 6), false) ||
!compareRanks(rank(1, 2, 0, 0, 3), rank(1, 3, 0, 0, 2), true) ||
!compareRanks(rank(0, 0, 0, 0, 0), rank(0, 0, 0, 0, 0), false) {
t.Error("Invalid order (tac)")
}
}
// Match length, string length, index
func TestResultRank(t *testing.T) {
// FIXME global
sortCriteria = []criterion{byScore, byLength}
strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")}
item1 := buildResult(
withIndex(&Item{text: util.RunesToChars(strs[0])}, 1), []Offset{}, 2)
if item1.points[3] != math.MaxUint16-2 || // Bonus
item1.points[2] != 3 || // Length
item1.points[1] != 0 || // Unused
item1.points[0] != 0 || // Unused
item1.item.Index() != 1 {
t.Error(item1)
}
// Only differ in index
item2 := buildResult(&Item{text: util.RunesToChars(strs[0])}, []Offset{}, 2)
items := []Result{item1, item2}
sort.Sort(ByRelevance(items))
if items[0] != item2 || items[1] != item1 {
t.Error(items)
}
items = []Result{item2, item1, item1, item2}
sort.Sort(ByRelevance(items))
if items[0] != item2 || items[1] != item2 ||
items[2] != item1 || items[3] != item1 {
t.Error(items, item1, item1.item.Index(), item2, item2.item.Index())
}
// Sort by relevance
item3 := buildResult(
withIndex(&Item{}, 2), []Offset{Offset{1, 3}, Offset{5, 7}}, 3)
item4 := buildResult(
withIndex(&Item{}, 2), []Offset{Offset{1, 2}, Offset{6, 7}}, 4)
item5 := buildResult(
withIndex(&Item{}, 2), []Offset{Offset{1, 3}, Offset{5, 7}}, 5)
item6 := buildResult(
withIndex(&Item{}, 2), []Offset{Offset{1, 2}, Offset{6, 7}}, 6)
items = []Result{item1, item2, item3, item4, item5, item6}
sort.Sort(ByRelevance(items))
if !(items[0] == item6 && items[1] == item5 &&
items[2] == item4 && items[3] == item3 &&
items[4] == item2 && items[5] == item1) {
t.Error(items, item1, item2, item3, item4, item5, item6)
}
}
func TestColorOffset(t *testing.T) {
// ------------ 20 ---- -- ----
// ++++++++ ++++++++++
// --++++++++-- --++++++++++---
offsets := []Offset{Offset{5, 15}, Offset{25, 35}}
item := Result{
item: &Item{
colors: &[]ansiOffset{
ansiOffset{[2]int32{0, 20}, ansiState{1, 5, 0}},
ansiOffset{[2]int32{22, 27}, ansiState{2, 6, tui.Bold}},
ansiOffset{[2]int32{30, 32}, ansiState{3, 7, 0}},
ansiOffset{[2]int32{33, 40}, ansiState{4, 8, tui.Bold}}}}}
// [{[0 5] 9 false} {[5 15] 99 false} {[15 20] 9 false} {[22 25] 10 true} {[25 35] 99 false} {[35 40] 11 true}]
pair := tui.NewColorPair(99, 199)
colors := item.colorOffsets(offsets, tui.Dark256, pair, tui.AttrRegular, true)
assert := func(idx int, b int32, e int32, c tui.ColorPair, bold bool) {
var attr tui.Attr
if bold {
attr = tui.Bold
}
o := colors[idx]
if o.offset[0] != b || o.offset[1] != e || o.color != c || o.attr != attr {
t.Error(o)
}
}
assert(0, 0, 5, tui.NewColorPair(1, 5), false)
assert(1, 5, 15, pair, false)
assert(2, 15, 20, tui.NewColorPair(1, 5), false)
assert(3, 22, 25, tui.NewColorPair(2, 6), true)
assert(4, 25, 35, pair, false)
assert(5, 35, 40, tui.NewColorPair(4, 8), true)
}

16
src/result_x86.go Normal file
View File

@ -0,0 +1,16 @@
// +build 386 amd64
package fzf
import "unsafe"
func compareRanks(irank Result, jrank Result, tac bool) bool {
left := *(*uint64)(unsafe.Pointer(&irank.points[0]))
right := *(*uint64)(unsafe.Pointer(&jrank.points[0]))
if left < right {
return true
} else if left > right {
return false
}
return (irank.item.Index() <= jrank.item.Index()) != tac
}

1860
src/terminal.go Normal file

File diff suppressed because it is too large Load Diff

112
src/terminal_test.go Normal file
View File

@ -0,0 +1,112 @@
package fzf
import (
"regexp"
"testing"
"github.com/junegunn/fzf/src/util"
)
func newItem(str string) *Item {
bytes := []byte(str)
trimmed, _, _ := extractColor(str, nil, nil)
return &Item{origText: &bytes, text: util.ToChars([]byte(trimmed))}
}
func TestReplacePlaceholder(t *testing.T) {
item1 := newItem(" foo'bar \x1b[31mbaz\x1b[m")
items1 := []*Item{item1, item1}
items2 := []*Item{
newItem("foo'bar \x1b[31mbaz\x1b[m"),
newItem("foo'bar \x1b[31mbaz\x1b[m"),
newItem("FOO'BAR \x1b[31mBAZ\x1b[m")}
var result string
check := func(expected string) {
if result != expected {
t.Errorf("expected: %s, actual: %s", expected, result)
}
}
// {}, preserve ansi
result = replacePlaceholder("echo {}", false, Delimiter{}, false, "query", items1)
check("echo ' foo'\\''bar \x1b[31mbaz\x1b[m'")
// {}, strip ansi
result = replacePlaceholder("echo {}", true, Delimiter{}, false, "query", items1)
check("echo ' foo'\\''bar baz'")
// {}, with multiple items
result = replacePlaceholder("echo {}", true, Delimiter{}, false, "query", items2)
check("echo 'foo'\\''bar baz'")
// {..}, strip leading whitespaces, preserve ansi
result = replacePlaceholder("echo {..}", false, Delimiter{}, false, "query", items1)
check("echo 'foo'\\''bar \x1b[31mbaz\x1b[m'")
// {..}, strip leading whitespaces, strip ansi
result = replacePlaceholder("echo {..}", true, Delimiter{}, false, "query", items1)
check("echo 'foo'\\''bar baz'")
// {q}
result = replacePlaceholder("echo {} {q}", true, Delimiter{}, false, "query", items1)
check("echo ' foo'\\''bar baz' 'query'")
// {q}, multiple items
result = replacePlaceholder("echo {+}{q}{+}", true, Delimiter{}, false, "query 'string'", items2)
check("echo 'foo'\\''bar baz' 'FOO'\\''BAR BAZ''query '\\''string'\\''''foo'\\''bar baz' 'FOO'\\''BAR BAZ'")
result = replacePlaceholder("echo {}{q}{}", true, Delimiter{}, false, "query 'string'", items2)
check("echo 'foo'\\''bar baz''query '\\''string'\\''''foo'\\''bar baz'")
result = replacePlaceholder("echo {1}/{2}/{2,1}/{-1}/{-2}/{}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, false, "query", items1)
check("echo 'foo'\\''bar'/'baz'/'bazfoo'\\''bar'/'baz'/'foo'\\''bar'/' foo'\\''bar baz'/'foo'\\''bar baz'/{n.t}/{}/{1}/{q}/''")
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, false, "query", items2)
check("echo 'foo'\\''bar'/'baz'/'baz'/'foo'\\''bar'/'foo'\\''bar baz'/{n.t}/{}/{1}/{q}/''")
result = replacePlaceholder("echo {+1}/{+2}/{+-1}/{+-2}/{+..}/{n.t}/\\{}/\\{1}/\\{q}/{+3}", true, Delimiter{}, false, "query", items2)
check("echo 'foo'\\''bar' 'FOO'\\''BAR'/'baz' 'BAZ'/'baz' 'BAZ'/'foo'\\''bar' 'FOO'\\''BAR'/'foo'\\''bar baz' 'FOO'\\''BAR BAZ'/{n.t}/{}/{1}/{q}/'' ''")
// forcePlus
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, true, "query", items2)
check("echo 'foo'\\''bar' 'FOO'\\''BAR'/'baz' 'BAZ'/'baz' 'BAZ'/'foo'\\''bar' 'FOO'\\''BAR'/'foo'\\''bar baz' 'FOO'\\''BAR BAZ'/{n.t}/{}/{1}/{q}/'' ''")
// No match
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, false, "query", []*Item{nil, nil})
check("echo /")
// No match, but with selections
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, false, "query", []*Item{nil, item1})
check("echo /' foo'\\''bar baz'")
// String delimiter
delim := "'"
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, false, "query", items1)
check("echo ' foo'\\''bar baz'/'foo'/'bar baz'")
// Regex delimiter
regex := regexp.MustCompile("[oa]+")
// foo'bar baz
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, false, "query", items1)
check("echo ' foo'\\''bar baz'/'f'/'r b'/''\\''bar b'")
}
func TestQuoteEntryCmd(t *testing.T) {
tests := map[string]string{
`"`: `^"\^"^"`,
`\`: `^"\\^"`,
`\"`: `^"\\\^"^"`,
`"\\\"`: `^"\^"\\\\\\\^"^"`,
`&|<>()@^%!`: `^"^&^|^<^>^(^)^@^^^%^!^"`,
`%USERPROFILE%`: `^"^%USERPROFILE^%^"`,
`C:\Program Files (x86)\`: `^"C:\\Program Files ^(x86^)\\^"`,
}
for input, expected := range tests {
escaped := quoteEntryCmd(input)
if escaped != expected {
t.Errorf("Input: %s, expected: %s, actual %s", input, expected, escaped)
}
}
}

21
src/terminal_unix.go Normal file
View File

@ -0,0 +1,21 @@
// +build !windows
package fzf
import (
"os"
"os/signal"
"syscall"
)
func notifyOnResize(resizeChan chan<- os.Signal) {
signal.Notify(resizeChan, syscall.SIGWINCH)
}
func notifyStop(p *os.Process) {
p.Signal(syscall.SIGSTOP)
}
func notifyOnCont(resizeChan chan<- os.Signal) {
signal.Notify(resizeChan, syscall.SIGCONT)
}

19
src/terminal_windows.go Normal file
View File

@ -0,0 +1,19 @@
// +build windows
package fzf
import (
"os"
)
func notifyOnResize(resizeChan chan<- os.Signal) {
// TODO
}
func notifyStop(p *os.Process) {
// NOOP
}
func notifyOnCont(resizeChan chan<- os.Signal) {
// NOOP
}

242
src/tokenizer.go Normal file
View File

@ -0,0 +1,242 @@
package fzf
import (
"bytes"
"regexp"
"strconv"
"strings"
"github.com/junegunn/fzf/src/util"
)
const rangeEllipsis = 0
// Range represents nth-expression
type Range struct {
begin int
end int
}
// Token contains the tokenized part of the strings and its prefix length
type Token struct {
text *util.Chars
prefixLength int32
}
// Delimiter for tokenizing the input
type Delimiter struct {
regex *regexp.Regexp
str *string
}
func newRange(begin int, end int) Range {
if begin == 1 {
begin = rangeEllipsis
}
if end == -1 {
end = rangeEllipsis
}
return Range{begin, end}
}
// ParseRange parses nth-expression and returns the corresponding Range object
func ParseRange(str *string) (Range, bool) {
if (*str) == ".." {
return newRange(rangeEllipsis, rangeEllipsis), true
} else if strings.HasPrefix(*str, "..") {
end, err := strconv.Atoi((*str)[2:])
if err != nil || end == 0 {
return Range{}, false
}
return newRange(rangeEllipsis, end), true
} else if strings.HasSuffix(*str, "..") {
begin, err := strconv.Atoi((*str)[:len(*str)-2])
if err != nil || begin == 0 {
return Range{}, false
}
return newRange(begin, rangeEllipsis), true
} else if strings.Contains(*str, "..") {
ns := strings.Split(*str, "..")
if len(ns) != 2 {
return Range{}, false
}
begin, err1 := strconv.Atoi(ns[0])
end, err2 := strconv.Atoi(ns[1])
if err1 != nil || err2 != nil || begin == 0 || end == 0 {
return Range{}, false
}
return newRange(begin, end), true
}
n, err := strconv.Atoi(*str)
if err != nil || n == 0 {
return Range{}, false
}
return newRange(n, n), true
}
func withPrefixLengths(tokens []string, begin int) []Token {
ret := make([]Token, len(tokens))
prefixLength := begin
for idx := range tokens {
chars := util.ToChars([]byte(tokens[idx]))
ret[idx] = Token{&chars, int32(prefixLength)}
prefixLength += chars.Length()
}
return ret
}
const (
awkNil = iota
awkBlack
awkWhite
)
func awkTokenizer(input string) ([]string, int) {
// 9, 32
ret := []string{}
prefixLength := 0
state := awkNil
begin := 0
end := 0
for idx := 0; idx < len(input); idx++ {
r := input[idx]
white := r == 9 || r == 32
switch state {
case awkNil:
if white {
prefixLength++
} else {
state, begin, end = awkBlack, idx, idx+1
}
case awkBlack:
end = idx + 1
if white {
state = awkWhite
}
case awkWhite:
if white {
end = idx + 1
} else {
ret = append(ret, input[begin:end])
state, begin, end = awkBlack, idx, idx+1
}
}
}
if begin < end {
ret = append(ret, input[begin:end])
}
return ret, prefixLength
}
// Tokenize tokenizes the given string with the delimiter
func Tokenize(text string, delimiter Delimiter) []Token {
if delimiter.str == nil && delimiter.regex == nil {
// AWK-style (\S+\s*)
tokens, prefixLength := awkTokenizer(text)
return withPrefixLengths(tokens, prefixLength)
}
if delimiter.str != nil {
return withPrefixLengths(strings.SplitAfter(text, *delimiter.str), 0)
}
// FIXME performance
var tokens []string
if delimiter.regex != nil {
for len(text) > 0 {
loc := delimiter.regex.FindStringIndex(text)
if len(loc) < 2 {
loc = []int{0, len(text)}
}
last := util.Max(loc[1], 1)
tokens = append(tokens, text[:last])
text = text[last:]
}
}
return withPrefixLengths(tokens, 0)
}
func joinTokens(tokens []Token) string {
var output bytes.Buffer
for _, token := range tokens {
output.WriteString(token.text.ToString())
}
return output.String()
}
// Transform is used to transform the input when --with-nth option is given
func Transform(tokens []Token, withNth []Range) []Token {
transTokens := make([]Token, len(withNth))
numTokens := len(tokens)
for idx, r := range withNth {
parts := []*util.Chars{}
minIdx := 0
if r.begin == r.end {
idx := r.begin
if idx == rangeEllipsis {
chars := util.ToChars([]byte(joinTokens(tokens)))
parts = append(parts, &chars)
} else {
if idx < 0 {
idx += numTokens + 1
}
if idx >= 1 && idx <= numTokens {
minIdx = idx - 1
parts = append(parts, tokens[idx-1].text)
}
}
} else {
var begin, end int
if r.begin == rangeEllipsis { // ..N
begin, end = 1, r.end
if end < 0 {
end += numTokens + 1
}
} else if r.end == rangeEllipsis { // N..
begin, end = r.begin, numTokens
if begin < 0 {
begin += numTokens + 1
}
} else {
begin, end = r.begin, r.end
if begin < 0 {
begin += numTokens + 1
}
if end < 0 {
end += numTokens + 1
}
}
minIdx = util.Max(0, begin-1)
for idx := begin; idx <= end; idx++ {
if idx >= 1 && idx <= numTokens {
parts = append(parts, tokens[idx-1].text)
}
}
}
// Merge multiple parts
var merged util.Chars
switch len(parts) {
case 0:
merged = util.ToChars([]byte{})
case 1:
merged = *parts[0]
default:
var output bytes.Buffer
for _, part := range parts {
output.WriteString(part.ToString())
}
merged = util.ToChars([]byte(output.String()))
}
var prefixLength int32
if minIdx < numTokens {
prefixLength = tokens[minIdx].prefixLength
} else {
prefixLength = 0
}
transTokens[idx] = Token{&merged, prefixLength}
}
return transTokens
}

112
src/tokenizer_test.go Normal file
View File

@ -0,0 +1,112 @@
package fzf
import (
"testing"
)
func TestParseRange(t *testing.T) {
{
i := ".."
r, _ := ParseRange(&i)
if r.begin != rangeEllipsis || r.end != rangeEllipsis {
t.Errorf("%s", r)
}
}
{
i := "3.."
r, _ := ParseRange(&i)
if r.begin != 3 || r.end != rangeEllipsis {
t.Errorf("%s", r)
}
}
{
i := "3..5"
r, _ := ParseRange(&i)
if r.begin != 3 || r.end != 5 {
t.Errorf("%s", r)
}
}
{
i := "-3..-5"
r, _ := ParseRange(&i)
if r.begin != -3 || r.end != -5 {
t.Errorf("%s", r)
}
}
{
i := "3"
r, _ := ParseRange(&i)
if r.begin != 3 || r.end != 3 {
t.Errorf("%s", r)
}
}
}
func TestTokenize(t *testing.T) {
// AWK-style
input := " abc: def: ghi "
tokens := Tokenize(input, Delimiter{})
if tokens[0].text.ToString() != "abc: " || tokens[0].prefixLength != 2 {
t.Errorf("%s", tokens)
}
// With delimiter
tokens = Tokenize(input, delimiterRegexp(":"))
if tokens[0].text.ToString() != " abc:" || tokens[0].prefixLength != 0 {
t.Error(tokens[0].text.ToString(), tokens[0].prefixLength)
}
// With delimiter regex
tokens = Tokenize(input, delimiterRegexp("\\s+"))
if tokens[0].text.ToString() != " " || tokens[0].prefixLength != 0 ||
tokens[1].text.ToString() != "abc: " || tokens[1].prefixLength != 2 ||
tokens[2].text.ToString() != "def: " || tokens[2].prefixLength != 8 ||
tokens[3].text.ToString() != "ghi " || tokens[3].prefixLength != 14 {
t.Errorf("%s", tokens)
}
}
func TestTransform(t *testing.T) {
input := " abc: def: ghi: jkl"
{
tokens := Tokenize(input, Delimiter{})
{
ranges := splitNth("1,2,3")
tx := Transform(tokens, ranges)
if string(joinTokens(tx)) != "abc: def: ghi: " {
t.Errorf("%s", tx)
}
}
{
ranges := splitNth("1..2,3,2..,1")
tx := Transform(tokens, ranges)
if string(joinTokens(tx)) != "abc: def: ghi: def: ghi: jklabc: " ||
len(tx) != 4 ||
tx[0].text.ToString() != "abc: def: " || tx[0].prefixLength != 2 ||
tx[1].text.ToString() != "ghi: " || tx[1].prefixLength != 14 ||
tx[2].text.ToString() != "def: ghi: jkl" || tx[2].prefixLength != 8 ||
tx[3].text.ToString() != "abc: " || tx[3].prefixLength != 2 {
t.Errorf("%s", tx)
}
}
}
{
tokens := Tokenize(input, delimiterRegexp(":"))
{
ranges := splitNth("1..2,3,2..,1")
tx := Transform(tokens, ranges)
if string(joinTokens(tx)) != " abc: def: ghi: def: ghi: jkl abc:" ||
len(tx) != 4 ||
tx[0].text.ToString() != " abc: def:" || tx[0].prefixLength != 0 ||
tx[1].text.ToString() != " ghi:" || tx[1].prefixLength != 12 ||
tx[2].text.ToString() != " def: ghi: jkl" || tx[2].prefixLength != 6 ||
tx[3].text.ToString() != " abc:" || tx[3].prefixLength != 0 {
t.Errorf("%s", tx)
}
}
}
}
func TestTransformIndexOutOfBounds(t *testing.T) {
Transform([]Token{}, splitNth("1"))
}

44
src/tui/dummy.go Normal file
View File

@ -0,0 +1,44 @@
// +build !ncurses
// +build !tcell
// +build !windows
package tui
type Attr int
func HasFullscreenRenderer() bool {
return false
}
func (a Attr) Merge(b Attr) Attr {
return a | b
}
const (
AttrRegular Attr = Attr(0)
Bold = Attr(1)
Dim = Attr(1 << 1)
Italic = Attr(1 << 2)
Underline = Attr(1 << 3)
Blink = Attr(1 << 4)
Blink2 = Attr(1 << 5)
Reverse = Attr(1 << 6)
)
func (r *FullscreenRenderer) Init() {}
func (r *FullscreenRenderer) Pause(bool) {}
func (r *FullscreenRenderer) Resume(bool) {}
func (r *FullscreenRenderer) Clear() {}
func (r *FullscreenRenderer) Refresh() {}
func (r *FullscreenRenderer) Close() {}
func (r *FullscreenRenderer) DoesAutoWrap() bool { return false }
func (r *FullscreenRenderer) GetChar() Event { return Event{} }
func (r *FullscreenRenderer) MaxX() int { return 0 }
func (r *FullscreenRenderer) MaxY() int { return 0 }
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {}
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window {
return nil
}

907
src/tui/light.go Normal file
View File

@ -0,0 +1,907 @@
package tui
import (
"fmt"
"os"
"os/exec"
"regexp"
"strconv"
"strings"
"syscall"
"time"
"unicode/utf8"
"github.com/junegunn/fzf/src/util"
"golang.org/x/crypto/ssh/terminal"
)
const (
defaultWidth = 80
defaultHeight = 24
defaultEscDelay = 100
escPollInterval = 5
offsetPollTries = 10
)
const consoleDevice string = "/dev/tty"
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 {
fmt.Fprintln(os.Stderr, "Failed to open "+consoleDevice)
os.Exit(2)
}
return in
}
func (r *LightRenderer) stderr(str string) {
r.stderrInternal(str, true)
}
// FIXME: Need better handling of non-displayable characters
func (r *LightRenderer) stderrInternal(str string, allowNLCR bool) {
bytes := []byte(str)
runes := []rune{}
for len(bytes) > 0 {
r, sz := utf8.DecodeRune(bytes)
if r == utf8.RuneError || r < 32 &&
r != '\x1b' && (!allowNLCR || r != '\n' && r != '\r') {
runes = append(runes, '?')
} else {
runes = append(runes, r)
}
bytes = bytes[sz:]
}
r.queued += string(runes)
}
func (r *LightRenderer) csi(code string) {
r.stderr("\x1b[" + code)
}
func (r *LightRenderer) flush() {
if len(r.queued) > 0 {
fmt.Fprint(os.Stderr, r.queued)
r.queued = ""
}
}
// Light renderer
type LightRenderer struct {
theme *ColorTheme
mouse bool
forceBlack bool
clearOnExit bool
prevDownTime time.Time
clickY []int
ttyin *os.File
buffer []byte
origState *terminal.State
width int
height int
yoffset int
tabstop int
escDelay int
fullscreen bool
upOneLine bool
queued string
y int
x int
maxHeightFunc func(int) int
}
type LightWindow struct {
renderer *LightRenderer
colored bool
border BorderStyle
top int
left int
width int
height int
posx int
posy int
tabstop int
fg Color
bg Color
}
func NewLightRenderer(theme *ColorTheme, forceBlack bool, mouse bool, tabstop int, clearOnExit bool, fullscreen bool, maxHeightFunc func(int) int) Renderer {
r := LightRenderer{
theme: theme,
forceBlack: forceBlack,
mouse: mouse,
clearOnExit: clearOnExit,
ttyin: openTtyIn(),
yoffset: 0,
tabstop: tabstop,
fullscreen: fullscreen,
upOneLine: false,
maxHeightFunc: maxHeightFunc}
return &r
}
func (r *LightRenderer) fd() int {
return int(r.ttyin.Fd())
}
func (r *LightRenderer) defaultTheme() *ColorTheme {
if strings.Contains(os.Getenv("TERM"), "256") {
return Dark256
}
colors, err := exec.Command("tput", "colors").Output()
if err == nil && atoi(strings.TrimSpace(string(colors)), 16) > 16 {
return Dark256
}
return Default16
}
func (r *LightRenderer) findOffset() (row int, col int) {
r.csi("6n")
r.flush()
bytes := []byte{}
for tries := 0; tries < offsetPollTries; tries++ {
bytes = r.getBytesInternal(bytes, tries > 0)
offsets := offsetRegexp.FindSubmatch(bytes)
if len(offsets) > 2 {
return atoi(string(offsets[1]), 0) - 1, atoi(string(offsets[2]), 0) - 1
}
}
return -1, -1
}
func repeat(s string, times int) string {
if times > 0 {
return strings.Repeat(s, times)
}
return ""
}
func atoi(s string, defaultValue int) int {
value, err := strconv.Atoi(s)
if err != nil {
return defaultValue
}
return value
}
func (r *LightRenderer) Init() {
r.escDelay = atoi(os.Getenv("ESCDELAY"), defaultEscDelay)
fd := r.fd()
origState, err := terminal.GetState(fd)
if err != nil {
errorExit(err.Error())
}
r.origState = origState
terminal.MakeRaw(fd)
r.updateTerminalSize()
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
if r.fullscreen {
r.smcup()
} else {
// We assume that --no-clear is used for repetitive relaunching of fzf.
// So we do not clear the lower bottom of the screen.
if r.clearOnExit {
r.csi("J")
}
y, x := r.findOffset()
r.mouse = r.mouse && y >= 0
// When --no-clear is used for repetitive relaunching, there is a small
// time frame between fzf processes where the user keystrokes are not
// captured by either of fzf process which can cause x offset to be
// increased and we're left with unwanted extra new line.
if x > 0 && r.clearOnExit {
r.upOneLine = true
r.makeSpace()
}
for i := 1; i < r.MaxY(); i++ {
r.makeSpace()
}
}
if r.mouse {
r.csi("?1000h")
}
r.csi(fmt.Sprintf("%dA", r.MaxY()-1))
r.csi("G")
r.csi("K")
if !r.clearOnExit && !r.fullscreen {
r.csi("s")
}
if !r.fullscreen && r.mouse {
r.yoffset, _ = r.findOffset()
}
}
func (r *LightRenderer) makeSpace() {
r.stderr("\n")
r.csi("G")
}
func (r *LightRenderer) move(y int, x int) {
// w.csi("u")
if r.y < y {
r.csi(fmt.Sprintf("%dB", y-r.y))
} else if r.y > y {
r.csi(fmt.Sprintf("%dA", r.y-y))
}
r.stderr("\r")
if x > 0 {
r.csi(fmt.Sprintf("%dC", x))
}
r.y = y
r.x = x
}
func (r *LightRenderer) origin() {
r.move(0, 0)
}
func getEnv(name string, defaultValue int) int {
env := os.Getenv(name)
if len(env) == 0 {
return 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 {
return r.getBytesInternal(r.buffer, false)
}
func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
c, ok := r.getch(nonblock)
if !nonblock && !ok {
r.Close()
errorExit("Failed to read " + consoleDevice)
}
retries := 0
if c == ESC || nonblock {
retries = r.escDelay / escPollInterval
}
buffer = append(buffer, byte(c))
for {
c, ok = r.getch(true)
if !ok {
if retries > 0 {
retries--
time.Sleep(escPollInterval * time.Millisecond)
continue
}
break
}
retries = 0
buffer = append(buffer, byte(c))
}
return buffer
}
func (r *LightRenderer) GetChar() Event {
if len(r.buffer) == 0 {
r.buffer = r.getBytes()
}
if len(r.buffer) == 0 {
panic("Empty buffer")
}
sz := 1
defer func() {
r.buffer = r.buffer[sz:]
}()
switch r.buffer[0] {
case CtrlC:
return Event{CtrlC, 0, nil}
case CtrlG:
return Event{CtrlG, 0, nil}
case CtrlQ:
return Event{CtrlQ, 0, nil}
case 127:
return Event{BSpace, 0, nil}
case 0:
return Event{CtrlSpace, 0, nil}
case ESC:
ev := r.escSequence(&sz)
// Second chance
if ev.Type == Invalid {
r.buffer = r.getBytes()
ev = r.escSequence(&sz)
}
return ev
}
// CTRL-A ~ CTRL-Z
if r.buffer[0] <= CtrlZ {
return Event{int(r.buffer[0]), 0, nil}
}
char, rsz := utf8.DecodeRune(r.buffer)
if char == utf8.RuneError {
return Event{ESC, 0, nil}
}
sz = rsz
return Event{Rune, char, nil}
}
func (r *LightRenderer) escSequence(sz *int) Event {
if len(r.buffer) < 2 {
return Event{ESC, 0, nil}
}
*sz = 2
if r.buffer[1] >= 1 && r.buffer[1] <= 'z'-'a'+1 {
return Event{int(CtrlAltA + r.buffer[1] - 1), 0, nil}
}
switch r.buffer[1] {
case 32:
return Event{AltSpace, 0, nil}
case 47:
return Event{AltSlash, 0, nil}
case 98:
return Event{AltB, 0, nil}
case 100:
return Event{AltD, 0, nil}
case 102:
return Event{AltF, 0, nil}
case 127:
return Event{AltBS, 0, nil}
case 91, 79:
if len(r.buffer) < 3 {
return Event{Invalid, 0, nil}
}
*sz = 3
switch r.buffer[2] {
case 68:
return Event{Left, 0, nil}
case 67:
return Event{Right, 0, nil}
case 66:
return Event{Down, 0, nil}
case 65:
return Event{Up, 0, nil}
case 90:
return Event{BTab, 0, nil}
case 72:
return Event{Home, 0, nil}
case 70:
return Event{End, 0, nil}
case 77:
return r.mouseSequence(sz)
case 80:
return Event{F1, 0, nil}
case 81:
return Event{F2, 0, nil}
case 82:
return Event{F3, 0, nil}
case 83:
return Event{F4, 0, nil}
case 49, 50, 51, 52, 53, 54:
if len(r.buffer) < 4 {
return Event{Invalid, 0, nil}
}
*sz = 4
switch r.buffer[2] {
case 50:
if len(r.buffer) == 5 && r.buffer[4] == 126 {
*sz = 5
switch r.buffer[3] {
case 48:
return Event{F9, 0, nil}
case 49:
return Event{F10, 0, nil}
case 51:
return Event{F11, 0, nil}
case 52:
return Event{F12, 0, nil}
}
}
// Bracketed paste mode: \e[200~ ... \e[201~
if 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
r.buffer = r.buffer[6:]
*sz = 0
return r.GetChar()
}
return Event{Invalid, 0, nil} // INS
case 51:
return Event{Del, 0, nil}
case 52:
return Event{End, 0, nil}
case 53:
return Event{PgUp, 0, nil}
case 54:
return Event{PgDn, 0, nil}
case 49:
switch r.buffer[3] {
case 126:
return Event{Home, 0, nil}
case 53, 55, 56, 57:
if len(r.buffer) == 5 && r.buffer[4] == 126 {
*sz = 5
switch r.buffer[3] {
case 53:
return Event{F5, 0, nil}
case 55:
return Event{F6, 0, nil}
case 56:
return Event{F7, 0, nil}
case 57:
return Event{F8, 0, nil}
}
}
return Event{Invalid, 0, nil}
case 59:
if len(r.buffer) != 6 {
return Event{Invalid, 0, nil}
}
*sz = 6
switch r.buffer[4] {
case 50:
switch r.buffer[5] {
case 68:
return Event{Home, 0, nil}
case 67:
return Event{End, 0, nil}
}
case 53:
switch r.buffer[5] {
case 68:
return Event{SLeft, 0, nil}
case 67:
return Event{SRight, 0, nil}
}
} // r.buffer[4]
} // r.buffer[3]
} // r.buffer[2]
} // r.buffer[2]
} // r.buffer[1]
if r.buffer[1] >= 'a' && r.buffer[1] <= 'z' {
return Event{AltA + int(r.buffer[1]) - 'a', 0, nil}
}
return Event{Invalid, 0, nil}
}
func (r *LightRenderer) mouseSequence(sz *int) Event {
if len(r.buffer) < 6 || !r.mouse {
return Event{Invalid, 0, nil}
}
*sz = 6
switch r.buffer[3] {
case 32, 36, 40, 48, // mouse-down / shift / cmd / ctrl
35, 39, 43, 51: // mouse-up / shift / cmd / ctrl
mod := r.buffer[3] >= 36
down := r.buffer[3]%2 == 0
x := int(r.buffer[4] - 33)
y := int(r.buffer[5]-33) - r.yoffset
double := false
if down {
now := time.Now()
if now.Sub(r.prevDownTime) < doubleClickDuration {
r.clickY = append(r.clickY, y)
} else {
r.clickY = []int{y}
}
r.prevDownTime = now
} else {
if len(r.clickY) > 1 && r.clickY[0] == r.clickY[1] &&
time.Now().Sub(r.prevDownTime) < doubleClickDuration {
double = true
}
}
return Event{Mouse, 0, &MouseEvent{y, x, 0, down, double, mod}}
case 96, 100, 104, 112, // scroll-up / shift / cmd / ctrl
97, 101, 105, 113: // scroll-down / shift / cmd / ctrl
mod := r.buffer[3] >= 100
s := 1 - int(r.buffer[3]%2)*2
x := int(r.buffer[4] - 33)
y := int(r.buffer[5]-33) - r.yoffset
return Event{Mouse, 0, &MouseEvent{y, x, s, false, false, mod}}
}
return Event{Invalid, 0, nil}
}
func (r *LightRenderer) smcup() {
r.csi("?1049h")
}
func (r *LightRenderer) rmcup() {
r.csi("?1049l")
}
func (r *LightRenderer) Pause(clear bool) {
terminal.Restore(r.fd(), r.origState)
if clear {
if r.fullscreen {
r.rmcup()
} else {
r.smcup()
r.csi("H")
}
r.flush()
}
}
func (r *LightRenderer) Resume(clear bool) {
terminal.MakeRaw(r.fd())
if clear {
if r.fullscreen {
r.smcup()
} else {
r.rmcup()
}
r.flush()
} else if !r.fullscreen && r.mouse {
// NOTE: Resume(false) is only called on SIGCONT after SIGSTOP.
// And It's highly likely that the offset we obtained at the beginning will
// no longer be correct, so we simply disable mouse input.
r.csi("?1000l")
r.mouse = false
}
}
func (r *LightRenderer) Clear() {
if r.fullscreen {
r.csi("H")
}
// r.csi("u")
r.origin()
r.csi("J")
r.flush()
}
func (r *LightRenderer) RefreshWindows(windows []Window) {
r.flush()
}
func (r *LightRenderer) Refresh() {
r.updateTerminalSize()
}
func (r *LightRenderer) Close() {
// r.csi("u")
if r.clearOnExit {
if r.fullscreen {
r.rmcup()
} else {
r.origin()
if r.upOneLine {
r.csi("A")
}
r.csi("J")
}
} else if !r.fullscreen {
r.csi("u")
}
if r.mouse {
r.csi("?1000l")
}
r.flush()
terminal.Restore(r.fd(), r.origState)
}
func (r *LightRenderer) MaxX() int {
return r.width
}
func (r *LightRenderer) MaxY() int {
return r.height
}
func (r *LightRenderer) DoesAutoWrap() bool {
return false
}
func (r *LightRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window {
w := &LightWindow{
renderer: r,
colored: r.theme != nil,
border: borderStyle,
top: top,
left: left,
width: width,
height: height,
tabstop: r.tabstop,
fg: colDefault,
bg: colDefault}
if r.theme != nil {
w.fg = r.theme.Fg
w.bg = r.theme.Bg
}
w.drawBorder()
return w
}
func (w *LightWindow) drawBorder() {
switch w.border {
case BorderAround:
w.drawBorderAround()
case BorderHorizontal:
w.drawBorderHorizontal()
}
}
func (w *LightWindow) drawBorderHorizontal() {
w.Move(0, 0)
w.CPrint(ColBorder, AttrRegular, repeat("─", w.width))
w.Move(w.height-1, 0)
w.CPrint(ColBorder, AttrRegular, repeat("─", w.width))
}
func (w *LightWindow) drawBorderAround() {
w.Move(0, 0)
w.CPrint(ColBorder, AttrRegular, "┌"+repeat("─", w.width-2)+"┐")
for y := 1; y < w.height-1; y++ {
w.Move(y, 0)
w.CPrint(ColBorder, AttrRegular, "│")
w.cprint2(colDefault, w.bg, AttrRegular, repeat(" ", w.width-2))
w.CPrint(ColBorder, AttrRegular, "│")
}
w.Move(w.height-1, 0)
w.CPrint(ColBorder, AttrRegular, "└"+repeat("─", w.width-2)+"┘")
}
func (w *LightWindow) csi(code string) {
w.renderer.csi(code)
}
func (w *LightWindow) stderr(str string) {
w.renderer.stderr(str)
}
func (w *LightWindow) stderrInternal(str string, allowNLCR bool) {
w.renderer.stderrInternal(str, allowNLCR)
}
func (w *LightWindow) Top() int {
return w.top
}
func (w *LightWindow) Left() int {
return w.left
}
func (w *LightWindow) Width() int {
return w.width
}
func (w *LightWindow) Height() int {
return w.height
}
func (w *LightWindow) Refresh() {
}
func (w *LightWindow) Close() {
}
func (w *LightWindow) X() int {
return w.posx
}
func (w *LightWindow) Y() int {
return w.posy
}
func (w *LightWindow) Enclose(y int, x int) bool {
return x >= w.left && x < (w.left+w.width) &&
y >= w.top && y < (w.top+w.height)
}
func (w *LightWindow) Move(y int, x int) {
w.posx = x
w.posy = y
w.renderer.move(w.Top()+y, w.Left()+x)
}
func (w *LightWindow) MoveAndClear(y int, x int) {
w.Move(y, x)
// We should not delete preview window on the right
// csi("K")
w.Print(repeat(" ", w.width-x))
w.Move(y, x)
}
func attrCodes(attr Attr) []string {
codes := []string{}
if (attr & Bold) > 0 {
codes = append(codes, "1")
}
if (attr & Dim) > 0 {
codes = append(codes, "2")
}
if (attr & Italic) > 0 {
codes = append(codes, "3")
}
if (attr & Underline) > 0 {
codes = append(codes, "4")
}
if (attr & Blink) > 0 {
codes = append(codes, "5")
}
if (attr & Reverse) > 0 {
codes = append(codes, "7")
}
return codes
}
func colorCodes(fg Color, bg Color) []string {
codes := []string{}
appendCode := func(c Color, offset int) {
if c == colDefault {
return
}
if c.is24() {
r := (c >> 16) & 0xff
g := (c >> 8) & 0xff
b := (c) & 0xff
codes = append(codes, fmt.Sprintf("%d;2;%d;%d;%d", 38+offset, r, g, b))
} else if c >= colBlack && c <= colWhite {
codes = append(codes, fmt.Sprintf("%d", int(c)+30+offset))
} else if c > colWhite && c < 16 {
codes = append(codes, fmt.Sprintf("%d", int(c)+90+offset-8))
} else if c >= 16 && c < 256 {
codes = append(codes, fmt.Sprintf("%d;5;%d", 38+offset, c))
}
}
appendCode(fg, 0)
appendCode(bg, 10)
return codes
}
func (w *LightWindow) csiColor(fg Color, bg Color, attr Attr) bool {
codes := append(attrCodes(attr), colorCodes(fg, bg)...)
w.csi(";" + strings.Join(codes, ";") + "m")
return len(codes) > 0
}
func (w *LightWindow) Print(text string) {
w.cprint2(colDefault, w.bg, AttrRegular, text)
}
func cleanse(str string) string {
return strings.Replace(str, "\x1b", "?", -1)
}
func (w *LightWindow) CPrint(pair ColorPair, attr Attr, text string) {
if !w.colored {
w.csiColor(colDefault, colDefault, attrFor(pair, attr))
} else {
w.csiColor(pair.Fg(), pair.Bg(), attr)
}
w.stderrInternal(cleanse(text), false)
w.csi("m")
}
func (w *LightWindow) cprint2(fg Color, bg Color, attr Attr, text string) {
if w.csiColor(fg, bg, attr) {
defer w.csi("m")
}
w.stderrInternal(cleanse(text), false)
}
type wrappedLine struct {
text string
displayWidth int
}
func wrapLine(input string, prefixLength int, max int, tabstop int) []wrappedLine {
lines := []wrappedLine{}
width := 0
line := ""
for _, r := range input {
w := util.Max(util.RuneWidth(r, prefixLength+width, 8), 1)
width += w
str := string(r)
if r == '\t' {
str = repeat(" ", w)
}
if prefixLength+width <= max {
line += str
} else {
lines = append(lines, wrappedLine{string(line), width - w})
line = str
prefixLength = 0
width = util.RuneWidth(r, prefixLength, 8)
}
}
lines = append(lines, wrappedLine{string(line), width})
return lines
}
func (w *LightWindow) fill(str string, onMove func()) FillReturn {
allLines := strings.Split(str, "\n")
for i, line := range allLines {
lines := wrapLine(line, w.posx, w.width, w.tabstop)
for j, wl := range lines {
if w.posx >= w.Width()-1 && wl.displayWidth == 0 {
if w.posy < w.height-1 {
w.Move(w.posy+1, 0)
}
return FillNextLine
}
w.stderrInternal(wl.text, false)
w.posx += wl.displayWidth
// Wrap line
if j < len(lines)-1 || i < len(allLines)-1 {
if w.posy+1 >= w.height {
return FillSuspend
}
w.MoveAndClear(w.posy, w.posx)
w.Move(w.posy+1, 0)
onMove()
}
}
}
return FillContinue
}
func (w *LightWindow) setBg() {
if w.bg != colDefault {
w.csiColor(colDefault, w.bg, AttrRegular)
}
}
func (w *LightWindow) Fill(text string) FillReturn {
w.Move(w.posy, w.posx)
w.setBg()
return w.fill(text, w.setBg)
}
func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillReturn {
w.Move(w.posy, w.posx)
if fg == colDefault {
fg = w.fg
}
if bg == colDefault {
bg = w.bg
}
if w.csiColor(fg, bg, attr) {
defer w.csi("m")
return w.fill(text, func() { w.csiColor(fg, bg, attr) })
}
return w.fill(text, w.setBg)
}
func (w *LightWindow) FinishFill() {
w.MoveAndClear(w.posy, w.posx)
for y := w.posy + 1; y < w.height; y++ {
w.MoveAndClear(y, 0)
}
}
func (w *LightWindow) Erase() {
w.drawBorder()
// We don't erase the window here to avoid flickering during scroll
w.Move(0, 0)
}

583
src/tui/tcell.go Normal file
View File

@ -0,0 +1,583 @@
// +build tcell windows
package tui
import (
"os"
"time"
"unicode/utf8"
"runtime"
"github.com/gdamore/tcell"
"github.com/gdamore/tcell/encoding"
"github.com/mattn/go-runewidth"
)
func HasFullscreenRenderer() bool {
return true
}
func (p ColorPair) style() tcell.Style {
style := tcell.StyleDefault
return style.Foreground(tcell.Color(p.Fg())).Background(tcell.Color(p.Bg()))
}
type Attr tcell.Style
type TcellWindow struct {
color bool
top int
left int
width int
height int
lastX int
lastY int
moveCursor bool
borderStyle BorderStyle
}
func (w *TcellWindow) Top() int {
return w.top
}
func (w *TcellWindow) Left() int {
return w.left
}
func (w *TcellWindow) Width() int {
return w.width
}
func (w *TcellWindow) Height() int {
return w.height
}
func (w *TcellWindow) Refresh() {
if w.moveCursor {
_screen.ShowCursor(w.left+w.lastX, w.top+w.lastY)
w.moveCursor = false
}
w.lastX = 0
w.lastY = 0
switch w.borderStyle {
case BorderAround:
w.drawBorder(true)
case BorderHorizontal:
w.drawBorder(false)
}
}
func (w *TcellWindow) FinishFill() {
// NO-OP
}
const (
Bold Attr = Attr(tcell.AttrBold)
Dim = Attr(tcell.AttrDim)
Blink = Attr(tcell.AttrBlink)
Reverse = Attr(tcell.AttrReverse)
Underline = Attr(tcell.AttrUnderline)
Italic = Attr(tcell.AttrNone) // Not supported
)
const (
AttrRegular Attr = 0
)
func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
if _screen.Colors() >= 256 {
return Dark256
}
return Default16
}
var (
_colorToAttribute = []tcell.Color{
tcell.ColorBlack,
tcell.ColorRed,
tcell.ColorGreen,
tcell.ColorYellow,
tcell.ColorBlue,
tcell.ColorDarkMagenta,
tcell.ColorLightCyan,
tcell.ColorWhite,
}
)
func (c Color) Style() tcell.Color {
if c <= colDefault {
return tcell.ColorDefault
} else if c >= colBlack && c <= colWhite {
return _colorToAttribute[int(c)]
} else {
return tcell.Color(c)
}
}
func (a Attr) Merge(b Attr) Attr {
return a | b
}
var (
_screen tcell.Screen
)
func (r *FullscreenRenderer) initScreen() {
s, e := tcell.NewScreen()
if e != nil {
errorExit(e.Error())
}
if e = s.Init(); e != nil {
errorExit(e.Error())
}
if r.mouse {
s.EnableMouse()
} else {
s.DisableMouse()
}
_screen = s
}
func (r *FullscreenRenderer) Init() {
if os.Getenv("TERM") == "cygwin" {
os.Setenv("TERM", "")
}
encoding.Register()
r.initScreen()
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
}
func (r *FullscreenRenderer) MaxX() int {
ncols, _ := _screen.Size()
return int(ncols)
}
func (r *FullscreenRenderer) MaxY() int {
_, nlines := _screen.Size()
return int(nlines)
}
func (w *TcellWindow) X() int {
return w.lastX
}
func (w *TcellWindow) Y() int {
return w.lastY
}
func (r *FullscreenRenderer) DoesAutoWrap() bool {
return false
}
func (r *FullscreenRenderer) Clear() {
_screen.Sync()
_screen.Clear()
}
func (r *FullscreenRenderer) Refresh() {
// noop
}
func (r *FullscreenRenderer) GetChar() Event {
ev := _screen.PollEvent()
switch ev := ev.(type) {
case *tcell.EventResize:
return Event{Resize, 0, nil}
// process mouse events:
case *tcell.EventMouse:
x, y := ev.Position()
button := ev.Buttons()
mod := ev.Modifiers() != 0
if button&tcell.WheelDown != 0 {
return Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, mod}}
} else if button&tcell.WheelUp != 0 {
return Event{Mouse, 0, &MouseEvent{y, x, +1, false, false, mod}}
} else if runtime.GOOS != "windows" {
// double and single taps on Windows don't quite work due to
// the console acting on the events and not allowing us
// to consume them.
down := button&tcell.Button1 != 0 // left
double := false
if down {
now := time.Now()
if now.Sub(r.prevDownTime) < doubleClickDuration {
r.clickY = append(r.clickY, x)
} else {
r.clickY = []int{x}
r.prevDownTime = now
}
} else {
if len(r.clickY) > 1 && r.clickY[0] == r.clickY[1] &&
time.Now().Sub(r.prevDownTime) < doubleClickDuration {
double = true
}
}
return Event{Mouse, 0, &MouseEvent{y, x, 0, down, double, mod}}
}
// process keyboard:
case *tcell.EventKey:
alt := (ev.Modifiers() & tcell.ModAlt) > 0
keyfn := func(r rune) int {
if alt {
return CtrlAltA - 'a' + int(r)
}
return CtrlA - 'a' + int(r)
}
switch ev.Key() {
case tcell.KeyCtrlA:
return Event{keyfn('a'), 0, nil}
case tcell.KeyCtrlB:
return Event{keyfn('b'), 0, nil}
case tcell.KeyCtrlC:
return Event{keyfn('c'), 0, nil}
case tcell.KeyCtrlD:
return Event{keyfn('d'), 0, nil}
case tcell.KeyCtrlE:
return Event{keyfn('e'), 0, nil}
case tcell.KeyCtrlF:
return Event{keyfn('f'), 0, nil}
case tcell.KeyCtrlG:
return Event{keyfn('g'), 0, nil}
case tcell.KeyCtrlH:
return Event{keyfn('h'), 0, nil}
case tcell.KeyCtrlI:
return Event{keyfn('i'), 0, nil}
case tcell.KeyCtrlJ:
return Event{keyfn('j'), 0, nil}
case tcell.KeyCtrlK:
return Event{keyfn('k'), 0, nil}
case tcell.KeyCtrlL:
return Event{keyfn('l'), 0, nil}
case tcell.KeyCtrlM:
return Event{keyfn('m'), 0, nil}
case tcell.KeyCtrlN:
return Event{keyfn('n'), 0, nil}
case tcell.KeyCtrlO:
return Event{keyfn('o'), 0, nil}
case tcell.KeyCtrlP:
return Event{keyfn('p'), 0, nil}
case tcell.KeyCtrlQ:
return Event{keyfn('q'), 0, nil}
case tcell.KeyCtrlR:
return Event{keyfn('r'), 0, nil}
case tcell.KeyCtrlS:
return Event{keyfn('s'), 0, nil}
case tcell.KeyCtrlT:
return Event{keyfn('t'), 0, nil}
case tcell.KeyCtrlU:
return Event{keyfn('u'), 0, nil}
case tcell.KeyCtrlV:
return Event{keyfn('v'), 0, nil}
case tcell.KeyCtrlW:
return Event{keyfn('w'), 0, nil}
case tcell.KeyCtrlX:
return Event{keyfn('x'), 0, nil}
case tcell.KeyCtrlY:
return Event{keyfn('y'), 0, nil}
case tcell.KeyCtrlZ:
return Event{keyfn('z'), 0, nil}
case tcell.KeyCtrlSpace:
return Event{CtrlSpace, 0, nil}
case tcell.KeyBackspace2:
if alt {
return Event{AltBS, 0, nil}
}
return Event{BSpace, 0, nil}
case tcell.KeyUp:
return Event{Up, 0, nil}
case tcell.KeyDown:
return Event{Down, 0, nil}
case tcell.KeyLeft:
return Event{Left, 0, nil}
case tcell.KeyRight:
return Event{Right, 0, nil}
case tcell.KeyHome:
return Event{Home, 0, nil}
case tcell.KeyDelete:
return Event{Del, 0, nil}
case tcell.KeyEnd:
return Event{End, 0, nil}
case tcell.KeyPgUp:
return Event{PgUp, 0, nil}
case tcell.KeyPgDn:
return Event{PgDn, 0, nil}
case tcell.KeyBacktab:
return Event{BTab, 0, nil}
case tcell.KeyF1:
return Event{F1, 0, nil}
case tcell.KeyF2:
return Event{F2, 0, nil}
case tcell.KeyF3:
return Event{F3, 0, nil}
case tcell.KeyF4:
return Event{F4, 0, nil}
case tcell.KeyF5:
return Event{F5, 0, nil}
case tcell.KeyF6:
return Event{F6, 0, nil}
case tcell.KeyF7:
return Event{F7, 0, nil}
case tcell.KeyF8:
return Event{F8, 0, nil}
case tcell.KeyF9:
return Event{F9, 0, nil}
case tcell.KeyF10:
return Event{F10, 0, nil}
case tcell.KeyF11:
return Event{F11, 0, nil}
case tcell.KeyF12:
return Event{F12, 0, nil}
// ev.Ch doesn't work for some reason for space:
case tcell.KeyRune:
r := ev.Rune()
if alt {
switch r {
case ' ':
return Event{AltSpace, 0, nil}
case '/':
return Event{AltSlash, 0, nil}
}
if r >= 'a' && r <= 'z' {
return Event{AltA + int(r) - 'a', 0, nil}
}
if r >= '0' && r <= '9' {
return Event{Alt0 + int(r) - '0', 0, nil}
}
}
return Event{Rune, r, nil}
case tcell.KeyEsc:
return Event{ESC, 0, nil}
}
}
return Event{Invalid, 0, nil}
}
func (r *FullscreenRenderer) Pause(bool) {
_screen.Fini()
}
func (r *FullscreenRenderer) Resume(bool) {
r.initScreen()
}
func (r *FullscreenRenderer) Close() {
_screen.Fini()
}
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {
// TODO
for _, w := range windows {
w.Refresh()
}
_screen.Show()
}
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window {
// TODO
return &TcellWindow{
color: r.theme != nil,
top: top,
left: left,
width: width,
height: height,
borderStyle: borderStyle}
}
func (w *TcellWindow) Close() {
// TODO
}
func fill(x, y, w, h int, r rune) {
for ly := 0; ly <= h; ly++ {
for lx := 0; lx <= w; lx++ {
_screen.SetContent(x+lx, y+ly, r, nil, ColNormal.style())
}
}
}
func (w *TcellWindow) Erase() {
fill(w.left-1, w.top, w.width+1, w.height, ' ')
}
func (w *TcellWindow) Enclose(y int, x int) bool {
return x >= w.left && x < (w.left+w.width) &&
y >= w.top && y < (w.top+w.height)
}
func (w *TcellWindow) Move(y int, x int) {
w.lastX = x
w.lastY = y
w.moveCursor = true
}
func (w *TcellWindow) MoveAndClear(y int, x int) {
w.Move(y, x)
for i := w.lastX; i < w.width; i++ {
_screen.SetContent(i+w.left, w.lastY+w.top, rune(' '), nil, ColNormal.style())
}
w.lastX = x
}
func (w *TcellWindow) Print(text string) {
w.printString(text, ColNormal, 0)
}
func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) {
t := text
lx := 0
var style tcell.Style
if w.color {
style = pair.style().
Reverse(a&Attr(tcell.AttrReverse) != 0).
Underline(a&Attr(tcell.AttrUnderline) != 0)
} else {
style = ColNormal.style().
Reverse(a&Attr(tcell.AttrReverse) != 0 || pair == ColCurrent || pair == ColCurrentMatch).
Underline(a&Attr(tcell.AttrUnderline) != 0 || pair == ColMatch || pair == ColCurrentMatch)
}
style = style.
Blink(a&Attr(tcell.AttrBlink) != 0).
Bold(a&Attr(tcell.AttrBold) != 0).
Dim(a&Attr(tcell.AttrDim) != 0)
for {
if len(t) == 0 {
break
}
r, size := utf8.DecodeRuneInString(t)
t = t[size:]
if r < rune(' ') { // ignore control characters
continue
}
if r == '\n' {
w.lastY++
lx = 0
} else {
if r == '\u000D' { // skip carriage return
continue
}
var xPos = w.left + w.lastX + lx
var yPos = w.top + w.lastY
if xPos < (w.left+w.width) && yPos < (w.top+w.height) {
_screen.SetContent(xPos, yPos, r, nil, style)
}
lx += runewidth.RuneWidth(r)
}
}
w.lastX += lx
}
func (w *TcellWindow) CPrint(pair ColorPair, attr Attr, text string) {
w.printString(text, pair, attr)
}
func (w *TcellWindow) fillString(text string, pair ColorPair, a Attr) FillReturn {
lx := 0
var style tcell.Style
if w.color {
style = pair.style()
} else {
style = ColNormal.style()
}
style = style.
Blink(a&Attr(tcell.AttrBlink) != 0).
Bold(a&Attr(tcell.AttrBold) != 0).
Dim(a&Attr(tcell.AttrDim) != 0).
Reverse(a&Attr(tcell.AttrReverse) != 0).
Underline(a&Attr(tcell.AttrUnderline) != 0)
for _, r := range text {
if r == '\n' {
w.lastY++
w.lastX = 0
lx = 0
} else {
var xPos = w.left + w.lastX + lx
// word wrap:
if xPos >= (w.left + w.width) {
w.lastY++
w.lastX = 0
lx = 0
xPos = w.left
}
var yPos = w.top + w.lastY
if yPos >= (w.top + w.height) {
return FillSuspend
}
_screen.SetContent(xPos, yPos, r, nil, style)
lx += runewidth.RuneWidth(r)
}
}
w.lastX += lx
return FillContinue
}
func (w *TcellWindow) Fill(str string) FillReturn {
return w.fillString(str, ColNormal, 0)
}
func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
if fg == colDefault {
fg = ColNormal.Fg()
}
if bg == colDefault {
bg = ColNormal.Bg()
}
return w.fillString(str, NewColorPair(fg, bg), a)
}
func (w *TcellWindow) drawBorder(around bool) {
left := w.left
right := left + w.width
top := w.top
bot := top + w.height
var style tcell.Style
if w.color {
style = ColBorder.style()
} else {
style = ColNormal.style()
}
for x := left; x < right; x++ {
_screen.SetContent(x, top, tcell.RuneHLine, nil, style)
_screen.SetContent(x, bot-1, tcell.RuneHLine, nil, style)
}
if around {
for y := top; y < bot; y++ {
_screen.SetContent(left, y, tcell.RuneVLine, nil, style)
_screen.SetContent(right-1, y, tcell.RuneVLine, nil, style)
}
_screen.SetContent(left, top, tcell.RuneULCorner, nil, style)
_screen.SetContent(right-1, top, tcell.RuneURCorner, nil, style)
_screen.SetContent(left, bot-1, tcell.RuneLLCorner, nil, style)
_screen.SetContent(right-1, bot-1, tcell.RuneLRCorner, nil, style)
}
}

421
src/tui/tui.go Normal file
View File

@ -0,0 +1,421 @@
package tui
import (
"fmt"
"os"
"strconv"
"time"
)
// Types of user action
const (
Rune = iota
CtrlA
CtrlB
CtrlC
CtrlD
CtrlE
CtrlF
CtrlG
CtrlH
Tab
CtrlJ
CtrlK
CtrlL
CtrlM
CtrlN
CtrlO
CtrlP
CtrlQ
CtrlR
CtrlS
CtrlT
CtrlU
CtrlV
CtrlW
CtrlX
CtrlY
CtrlZ
ESC
CtrlSpace
Invalid
Resize
Mouse
DoubleClick
BTab
BSpace
Del
PgUp
PgDn
Up
Down
Left
Right
Home
End
SLeft
SRight
F1
F2
F3
F4
F5
F6
F7
F8
F9
F10
F11
F12
Change
AltSpace
AltSlash
AltBS
Alt0
)
const ( // Reset iota
AltA = Alt0 + 'a' - '0' + iota
AltB
AltC
AltD
AltE
AltF
AltZ = AltA + 'z' - 'a'
CtrlAltA = AltZ + 1
CtrlAltM = CtrlAltA + 'm' - 'a'
)
const (
doubleClickDuration = 500 * time.Millisecond
)
type Color int32
func (c Color) is24() bool {
return c > 0 && (c&(1<<24)) > 0
}
const (
colUndefined Color = -2
colDefault = -1
)
const (
colBlack Color = iota
colRed
colGreen
colYellow
colBlue
colMagenta
colCyan
colWhite
)
type FillReturn int
const (
FillContinue FillReturn = iota
FillNextLine
FillSuspend
)
type ColorPair struct {
fg Color
bg Color
id int
}
func HexToColor(rrggbb string) Color {
r, _ := strconv.ParseInt(rrggbb[1:3], 16, 0)
g, _ := strconv.ParseInt(rrggbb[3:5], 16, 0)
b, _ := strconv.ParseInt(rrggbb[5:7], 16, 0)
return Color((1 << 24) + (r << 16) + (g << 8) + b)
}
func NewColorPair(fg Color, bg Color) ColorPair {
return ColorPair{fg, bg, -1}
}
func (p ColorPair) Fg() Color {
return p.fg
}
func (p ColorPair) Bg() Color {
return p.bg
}
func (p ColorPair) is24() bool {
return p.fg.is24() || p.bg.is24()
}
type ColorTheme struct {
Fg Color
Bg Color
DarkBg Color
Prompt Color
Match Color
Current Color
CurrentMatch Color
Spinner Color
Info Color
Cursor Color
Selected Color
Header Color
Border Color
}
type Event struct {
Type int
Char rune
MouseEvent *MouseEvent
}
type MouseEvent struct {
Y int
X int
S int
Down bool
Double bool
Mod bool
}
type BorderStyle int
const (
BorderNone BorderStyle = iota
BorderAround
BorderHorizontal
)
type Renderer interface {
Init()
Pause(clear bool)
Resume(clear bool)
Clear()
RefreshWindows(windows []Window)
Refresh()
Close()
GetChar() Event
MaxX() int
MaxY() int
DoesAutoWrap() bool
NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window
}
type Window interface {
Top() int
Left() int
Width() int
Height() int
Refresh()
FinishFill()
Close()
X() int
Y() int
Enclose(y int, x int) bool
Move(y int, x int)
MoveAndClear(y int, x int)
Print(text string)
CPrint(color ColorPair, attr Attr, text string)
Fill(text string) FillReturn
CFill(fg Color, bg Color, attr Attr, text string) FillReturn
Erase()
}
type FullscreenRenderer struct {
theme *ColorTheme
mouse bool
forceBlack bool
prevDownTime time.Time
clickY []int
}
func NewFullscreenRenderer(theme *ColorTheme, forceBlack bool, mouse bool) Renderer {
r := &FullscreenRenderer{
theme: theme,
mouse: mouse,
forceBlack: forceBlack,
prevDownTime: time.Unix(0, 0),
clickY: []int{}}
return r
}
var (
Default16 *ColorTheme
Dark256 *ColorTheme
Light256 *ColorTheme
ColNormal ColorPair
ColPrompt ColorPair
ColMatch ColorPair
ColCurrent ColorPair
ColCurrentMatch ColorPair
ColSpinner ColorPair
ColInfo ColorPair
ColCursor ColorPair
ColSelected ColorPair
ColHeader ColorPair
ColBorder ColorPair
)
func EmptyTheme() *ColorTheme {
return &ColorTheme{
Fg: colUndefined,
Bg: colUndefined,
DarkBg: colUndefined,
Prompt: colUndefined,
Match: colUndefined,
Current: colUndefined,
CurrentMatch: colUndefined,
Spinner: colUndefined,
Info: colUndefined,
Cursor: colUndefined,
Selected: colUndefined,
Header: colUndefined,
Border: colUndefined}
}
func errorExit(message string) {
fmt.Fprintln(os.Stderr, message)
os.Exit(2)
}
func init() {
Default16 = &ColorTheme{
Fg: colDefault,
Bg: colDefault,
DarkBg: colBlack,
Prompt: colBlue,
Match: colGreen,
Current: colYellow,
CurrentMatch: colGreen,
Spinner: colGreen,
Info: colWhite,
Cursor: colRed,
Selected: colMagenta,
Header: colCyan,
Border: colBlack}
Dark256 = &ColorTheme{
Fg: colDefault,
Bg: colDefault,
DarkBg: 236,
Prompt: 110,
Match: 108,
Current: 254,
CurrentMatch: 151,
Spinner: 148,
Info: 144,
Cursor: 161,
Selected: 168,
Header: 109,
Border: 59}
Light256 = &ColorTheme{
Fg: colDefault,
Bg: colDefault,
DarkBg: 251,
Prompt: 25,
Match: 66,
Current: 237,
CurrentMatch: 23,
Spinner: 65,
Info: 101,
Cursor: 161,
Selected: 168,
Header: 31,
Border: 145}
}
func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
if theme == nil {
initPalette(theme)
return
}
if forceBlack {
theme.Bg = colBlack
}
o := func(a Color, b Color) Color {
if b == colUndefined {
return a
}
return b
}
theme.Fg = o(baseTheme.Fg, theme.Fg)
theme.Bg = o(baseTheme.Bg, theme.Bg)
theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg)
theme.Prompt = o(baseTheme.Prompt, theme.Prompt)
theme.Match = o(baseTheme.Match, theme.Match)
theme.Current = o(baseTheme.Current, theme.Current)
theme.CurrentMatch = o(baseTheme.CurrentMatch, theme.CurrentMatch)
theme.Spinner = o(baseTheme.Spinner, theme.Spinner)
theme.Info = o(baseTheme.Info, theme.Info)
theme.Cursor = o(baseTheme.Cursor, theme.Cursor)
theme.Selected = o(baseTheme.Selected, theme.Selected)
theme.Header = o(baseTheme.Header, theme.Header)
theme.Border = o(baseTheme.Border, theme.Border)
initPalette(theme)
}
func initPalette(theme *ColorTheme) {
idx := 0
pair := func(fg, bg Color) ColorPair {
idx++
return ColorPair{fg, bg, idx}
}
if theme != nil {
ColNormal = pair(theme.Fg, theme.Bg)
ColPrompt = pair(theme.Prompt, theme.Bg)
ColMatch = pair(theme.Match, theme.Bg)
ColCurrent = pair(theme.Current, theme.DarkBg)
ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg)
ColSpinner = pair(theme.Spinner, theme.Bg)
ColInfo = pair(theme.Info, theme.Bg)
ColCursor = pair(theme.Cursor, theme.DarkBg)
ColSelected = pair(theme.Selected, theme.DarkBg)
ColHeader = pair(theme.Header, theme.Bg)
ColBorder = pair(theme.Border, theme.Bg)
} else {
ColNormal = pair(colDefault, colDefault)
ColPrompt = pair(colDefault, colDefault)
ColMatch = pair(colDefault, colDefault)
ColCurrent = pair(colDefault, colDefault)
ColCurrentMatch = pair(colDefault, colDefault)
ColSpinner = pair(colDefault, colDefault)
ColInfo = pair(colDefault, colDefault)
ColCursor = pair(colDefault, colDefault)
ColSelected = pair(colDefault, colDefault)
ColHeader = pair(colDefault, colDefault)
ColBorder = pair(colDefault, colDefault)
}
}
func attrFor(color ColorPair, attr Attr) Attr {
switch color {
case ColCurrent:
return attr | Reverse
case ColMatch:
return attr | Underline
case ColCurrentMatch:
return attr | Underline | Reverse
}
return attr
}

20
src/tui/tui_test.go Normal file
View File

@ -0,0 +1,20 @@
package tui
import "testing"
func TestHexToColor(t *testing.T) {
assert := func(expr string, r, g, b int) {
color := HexToColor(expr)
if !color.is24() ||
int((color>>16)&0xff) != r ||
int((color>>8)&0xff) != g ||
int((color)&0xff) != b {
t.Fail()
}
}
assert("#ff0000", 255, 0, 0)
assert("#010203", 1, 2, 3)
assert("#102030", 16, 32, 48)
assert("#ffffff", 255, 255, 255)
}

45
src/update_assets.rb Executable file
View File

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

32
src/util/atomicbool.go Normal file
View File

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

View File

@ -0,0 +1,17 @@
package util
import "testing"
func TestAtomicBool(t *testing.T) {
if !NewAtomicBool(true).Get() || NewAtomicBool(false).Get() {
t.Error("Invalid initial value")
}
ab := NewAtomicBool(true)
if ab.Set(false) {
t.Error("Invalid return value")
}
if ab.Get() {
t.Error("Invalid state")
}
}

167
src/util/chars.go Normal file
View File

@ -0,0 +1,167 @@
package util
import (
"unicode"
"unicode/utf8"
"unsafe"
)
const (
overflow64 uint64 = 0x8080808080808080
overflow32 uint32 = 0x80808080
)
type Chars struct {
slice []byte // or []rune
inBytes bool
trimLengthKnown bool
trimLength uint16
// XXX Piggybacking item index here is a horrible idea. But I'm trying to
// minimize the memory footprint by not wasting padded spaces.
Index int32
}
func checkAscii(bytes []byte) (bool, int) {
i := 0
for ; i <= len(bytes)-8; i += 8 {
if (overflow64 & *(*uint64)(unsafe.Pointer(&bytes[i]))) > 0 {
return false, i
}
}
for ; i <= len(bytes)-4; i += 4 {
if (overflow32 & *(*uint32)(unsafe.Pointer(&bytes[i]))) > 0 {
return false, i
}
}
for ; i < len(bytes); i++ {
if bytes[i] >= utf8.RuneSelf {
return false, i
}
}
return true, 0
}
// ToChars converts byte array into rune array
func ToChars(bytes []byte) Chars {
inBytes, bytesUntil := checkAscii(bytes)
if inBytes {
return Chars{slice: bytes, inBytes: inBytes}
}
runes := make([]rune, bytesUntil, len(bytes))
for i := 0; i < bytesUntil; i++ {
runes[i] = rune(bytes[i])
}
for i := bytesUntil; i < len(bytes); {
r, sz := utf8.DecodeRune(bytes[i:])
i += sz
runes = append(runes, r)
}
return RunesToChars(runes)
}
func RunesToChars(runes []rune) Chars {
return Chars{slice: *(*[]byte)(unsafe.Pointer(&runes)), inBytes: false}
}
func (chars *Chars) IsBytes() bool {
return chars.inBytes
}
func (chars *Chars) Bytes() []byte {
return chars.slice
}
func (chars *Chars) optionalRunes() []rune {
if chars.inBytes {
return nil
}
return *(*[]rune)(unsafe.Pointer(&chars.slice))
}
func (chars *Chars) Get(i int) rune {
if runes := chars.optionalRunes(); runes != nil {
return runes[i]
}
return rune(chars.slice[i])
}
func (chars *Chars) Length() int {
if runes := chars.optionalRunes(); runes != nil {
return len(runes)
}
return len(chars.slice)
}
// TrimLength returns the length after trimming leading and trailing whitespaces
func (chars *Chars) TrimLength() uint16 {
if chars.trimLengthKnown {
return chars.trimLength
}
chars.trimLengthKnown = true
var i int
len := chars.Length()
for i = len - 1; i >= 0; i-- {
char := chars.Get(i)
if !unicode.IsSpace(char) {
break
}
}
// Completely empty
if i < 0 {
return 0
}
var j int
for j = 0; j < len; j++ {
char := chars.Get(j)
if !unicode.IsSpace(char) {
break
}
}
chars.trimLength = AsUint16(i - j + 1)
return chars.trimLength
}
func (chars *Chars) TrailingWhitespaces() int {
whitespaces := 0
for i := chars.Length() - 1; i >= 0; i-- {
char := chars.Get(i)
if !unicode.IsSpace(char) {
break
}
whitespaces++
}
return whitespaces
}
func (chars *Chars) ToString() string {
if runes := chars.optionalRunes(); runes != nil {
return string(runes)
}
return string(chars.slice)
}
func (chars *Chars) ToRunes() []rune {
if runes := chars.optionalRunes(); runes != nil {
return runes
}
bytes := chars.slice
runes := make([]rune, len(bytes))
for idx, b := range bytes {
runes[idx] = rune(b)
}
return runes
}
func (chars *Chars) CopyRunes(dest []rune) {
if runes := chars.optionalRunes(); runes != nil {
copy(dest, runes)
return
}
for idx, b := range chars.slice[:len(dest)] {
dest[idx] = rune(b)
}
return
}

46
src/util/chars_test.go Normal file
View File

@ -0,0 +1,46 @@
package util
import "testing"
func TestToCharsAscii(t *testing.T) {
chars := ToChars([]byte("foobar"))
if !chars.inBytes || chars.ToString() != "foobar" || !chars.inBytes {
t.Error()
}
}
func TestCharsLength(t *testing.T) {
chars := ToChars([]byte("\tabc한글 "))
if chars.inBytes || chars.Length() != 8 || chars.TrimLength() != 5 {
t.Error()
}
}
func TestCharsToString(t *testing.T) {
text := "\tabc한글 "
chars := ToChars([]byte(text))
if chars.ToString() != text {
t.Error()
}
}
func TestTrimLength(t *testing.T) {
check := func(str string, exp uint16) {
chars := ToChars([]byte(str))
trimmed := chars.TrimLength()
if trimmed != exp {
t.Errorf("Invalid TrimLength result for '%s': %d (expected %d)",
str, trimmed, exp)
}
}
check("hello", 5)
check("hello ", 5)
check("hello ", 5)
check(" hello", 5)
check(" hello", 5)
check(" hello ", 5)
check(" hello ", 5)
check("h o", 5)
check(" h o ", 5)
check(" ", 0)
}

96
src/util/eventbox.go Normal file
View File

@ -0,0 +1,96 @@
package util
import "sync"
// EventType is the type for fzf events
type EventType int
// Events is a type that associates EventType to any data
type Events map[EventType]interface{}
// EventBox is used for coordinating events
type EventBox struct {
events Events
cond *sync.Cond
ignore map[EventType]bool
}
// NewEventBox returns a new EventBox
func NewEventBox() *EventBox {
return &EventBox{
events: make(Events),
cond: sync.NewCond(&sync.Mutex{}),
ignore: make(map[EventType]bool)}
}
// Wait blocks the goroutine until signaled
func (b *EventBox) Wait(callback func(*Events)) {
b.cond.L.Lock()
if len(b.events) == 0 {
b.cond.Wait()
}
callback(&b.events)
b.cond.L.Unlock()
}
// Set turns on the event type on the box
func (b *EventBox) Set(event EventType, value interface{}) {
b.cond.L.Lock()
b.events[event] = value
if _, found := b.ignore[event]; !found {
b.cond.Broadcast()
}
b.cond.L.Unlock()
}
// Clear clears the events
// Unsynchronized; should be called within Wait routine
func (events *Events) Clear() {
for event := range *events {
delete(*events, event)
}
}
// Peek peeks at the event box if the given event is set
func (b *EventBox) Peek(event EventType) bool {
b.cond.L.Lock()
_, ok := b.events[event]
b.cond.L.Unlock()
return ok
}
// Watch deletes the events from the ignore list
func (b *EventBox) Watch(events ...EventType) {
b.cond.L.Lock()
for _, event := range events {
delete(b.ignore, event)
}
b.cond.L.Unlock()
}
// Unwatch adds the events to the ignore list
func (b *EventBox) Unwatch(events ...EventType) {
b.cond.L.Lock()
for _, event := range events {
b.ignore[event] = true
}
b.cond.L.Unlock()
}
// WaitFor blocks the execution until the event is received
func (b *EventBox) WaitFor(event EventType) {
looping := true
for looping {
b.Wait(func(events *Events) {
for evt := range *events {
switch evt {
case event:
looping = false
return
}
}
})
}
}

61
src/util/eventbox_test.go Normal file
View File

@ -0,0 +1,61 @@
package util
import "testing"
// fzf events
const (
EvtReadNew EventType = iota
EvtReadFin
EvtSearchNew
EvtSearchProgress
EvtSearchFin
EvtClose
)
func TestEventBox(t *testing.T) {
eb := NewEventBox()
// Wait should return immediately
ch := make(chan bool)
go func() {
eb.Set(EvtReadNew, 10)
ch <- true
<-ch
eb.Set(EvtSearchNew, 10)
eb.Set(EvtSearchNew, 15)
eb.Set(EvtSearchNew, 20)
eb.Set(EvtSearchProgress, 30)
ch <- true
<-ch
eb.Set(EvtSearchFin, 40)
ch <- true
<-ch
}()
count := 0
sum := 0
looping := true
for looping {
<-ch
eb.Wait(func(events *Events) {
for _, value := range *events {
switch val := value.(type) {
case int:
sum += val
looping = sum < 100
}
}
events.Clear()
})
ch <- true
count++
}
if count != 3 {
t.Error("Invalid number of events", count)
}
if sum != 100 {
t.Error("Invalid sum", sum)
}
}

12
src/util/slab.go Normal file
View File

@ -0,0 +1,12 @@
package util
type Slab struct {
I16 []int16
I32 []int32
}
func MakeSlab(size16 int, size32 int) *Slab {
return &Slab{
I16: make([]int16, size16),
I32: make([]int32, size32)}
}

113
src/util/util.go Normal file
View File

@ -0,0 +1,113 @@
package util
import (
"math"
"os"
"time"
"github.com/mattn/go-isatty"
"github.com/mattn/go-runewidth"
)
var _runeWidths = make(map[rune]int)
// RuneWidth returns rune width
func RuneWidth(r rune, prefixWidth int, tabstop int) int {
if r == '\t' {
return tabstop - prefixWidth%tabstop
} else if w, found := _runeWidths[r]; found {
return w
} else {
w := Max(runewidth.RuneWidth(r), 1)
_runeWidths[r] = w
return w
}
}
// Max returns the largest integer
func Max(first int, second int) int {
if first >= second {
return first
}
return second
}
// Max16 returns the largest integer
func Max16(first int16, second int16) int16 {
if first >= second {
return first
}
return second
}
// Max32 returns the largest 32-bit integer
func Max32(first int32, second int32) int32 {
if first > second {
return first
}
return second
}
// Min returns the smallest integer
func Min(first int, second int) int {
if first <= second {
return first
}
return second
}
// Min32 returns the smallest 32-bit integer
func Min32(first int32, second int32) int32 {
if first <= second {
return first
}
return second
}
// Constrain32 limits the given 32-bit integer with the upper and lower bounds
func Constrain32(val int32, min int32, max int32) int32 {
if val < min {
return min
}
if val > max {
return max
}
return val
}
// Constrain limits the given integer with the upper and lower bounds
func Constrain(val int, min int, max int) int {
if val < min {
return min
}
if val > max {
return max
}
return val
}
func AsUint16(val int) uint16 {
if val > math.MaxUint16 {
return math.MaxUint16
} else if val < 0 {
return 0
}
return uint16(val)
}
// DurWithin limits the given time.Duration with the upper and lower bounds
func DurWithin(
val time.Duration, min time.Duration, max time.Duration) time.Duration {
if val < min {
return min
}
if val > max {
return max
}
return val
}
// IsTty returns true is stdin is a terminal
func IsTty() bool {
return isatty.IsTerminal(os.Stdin.Fd())
}

22
src/util/util_test.go Normal file
View File

@ -0,0 +1,22 @@
package util
import "testing"
func TestMax(t *testing.T) {
if Max(-2, 5) != 5 {
t.Error("Invalid result")
}
}
func TestContrain(t *testing.T) {
if Constrain(-3, -1, 3) != -1 {
t.Error("Expected", -1)
}
if Constrain(2, -1, 3) != 2 {
t.Error("Expected", 2)
}
if Constrain(5, -1, 3) != 3 {
t.Error("Expected", 3)
}
}

38
src/util/util_unix.go Normal file
View File

@ -0,0 +1,38 @@
// +build !windows
package util
import (
"os"
"os/exec"
"syscall"
)
// ExecCommand executes the given command with $SHELL
func ExecCommand(command string) *exec.Cmd {
shell := os.Getenv("SHELL")
if len(shell) == 0 {
shell = "sh"
}
return ExecCommandWith(shell, command)
}
// ExecCommandWith executes the given command with the specified shell
func ExecCommandWith(shell string, command string) *exec.Cmd {
return exec.Command(shell, "-c", command)
}
// IsWindows returns true on Windows
func IsWindows() bool {
return false
}
// SetNonBlock executes syscall.SetNonblock on file descriptor
func SetNonblock(file *os.File, nonblock bool) {
syscall.SetNonblock(int(file.Fd()), nonblock)
}
// Read executes syscall.Read on file descriptor
func Read(fd int, b []byte) (int, error) {
return syscall.Read(int(fd), b)
}

42
src/util/util_windows.go Normal file
View File

@ -0,0 +1,42 @@
// +build windows
package util
import (
"fmt"
"os"
"os/exec"
"syscall"
)
// ExecCommand executes the given command with cmd
func ExecCommand(command string) *exec.Cmd {
return ExecCommandWith("cmd", command)
}
// ExecCommandWith executes the given command with cmd. _shell parameter is
// ignored on Windows.
func ExecCommandWith(_shell string, command string) *exec.Cmd {
cmd := exec.Command("cmd")
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: false,
CmdLine: fmt.Sprintf(` /s /c "%s"`, command),
CreationFlags: 0,
}
return cmd
}
// IsWindows returns true on Windows
func IsWindows() bool {
return true
}
// SetNonBlock executes syscall.SetNonblock on file descriptor
func SetNonblock(file *os.File, nonblock bool) {
syscall.SetNonblock(syscall.Handle(file.Fd()), nonblock)
}
// Read executes syscall.Read on file descriptor
func Read(fd int, b []byte) (int, error) {
return syscall.Read(syscall.Handle(fd), b)
}

View File

@ -1,22 +1,27 @@
Execute (Setup):
let g:dir = fnamemodify(g:vader_file, ':p:h')
unlet! g:fzf_layout g:fzf_action g:fzf_history_dir
Log 'Test directory: ' . g:dir
Save &acd
Execute (fzf#run with dir option):
let result = fzf#run({ 'options': '--filter=vdr', 'dir': g:dir })
let cwd = getcwd()
let result = fzf#run({ 'source': 'git ls-files', 'options': '--filter=vdr', 'dir': g:dir })
AssertEqual ['fzf.vader'], result
AssertEqual getcwd(), cwd
let result = sort(fzf#run({ 'options': '--filter e', 'dir': g:dir }))
AssertEqual ['fzf.vader', 'test_fzf.rb'], result
let result = sort(fzf#run({ 'source': 'git ls-files', 'options': '--filter e', 'dir': g:dir }))
AssertEqual ['fzf.vader', 'test_go.rb'], result
AssertEqual getcwd(), cwd
Execute (fzf#run with Funcref command):
let g:ret = []
function! g:proc(e)
function! g:FzfTest(e)
call add(g:ret, a:e)
endfunction
let result = sort(fzf#run({ 'sink': function('g:proc'), 'options': '--filter e', 'dir': g:dir }))
AssertEqual ['fzf.vader', 'test_fzf.rb'], result
AssertEqual ['fzf.vader', 'test_fzf.rb'], sort(g:ret)
let result = sort(fzf#run({ 'source': 'git ls-files', 'sink': function('g:FzfTest'), 'options': '--filter e', 'dir': g:dir }))
AssertEqual ['fzf.vader', 'test_go.rb'], result
AssertEqual ['fzf.vader', 'test_go.rb'], sort(g:ret)
Execute (fzf#run with string source):
let result = sort(fzf#run({ 'source': 'echo hi', 'options': '-f i' }))
@ -32,6 +37,136 @@ Execute (fzf#run with string source):
let result = sort(fzf#run({ 'source': 'echo hi', 'options': '-f i' }))
AssertEqual ['hi'], result
Execute (fzf#run with dir option and noautochdir):
set noacd
let cwd = getcwd()
call fzf#run({'source': ['/foobar'], 'sink': 'e', 'dir': '/tmp', 'options': '-1'})
" No change in working directory
AssertEqual cwd, getcwd()
call fzf#run({'source': ['/foobar'], 'sink': 'tabe', 'dir': '/tmp', 'options': '-1'})
AssertEqual cwd, getcwd()
tabclose
AssertEqual cwd, getcwd()
Execute (Incomplete fzf#run with dir option and autochdir):
set acd
let cwd = getcwd()
call fzf#run({'source': [], 'sink': 'e', 'dir': '/tmp', 'options': '-0'})
" No change in working directory even if &acd is set
AssertEqual cwd, getcwd()
Execute (fzf#run with dir option and autochdir):
set acd
let cwd = getcwd()
call fzf#run({'source': ['/foobar'], 'sink': 'e', 'dir': '/tmp', 'options': '-1'})
" Working directory changed due to &acd
AssertEqual '/', getcwd()
Execute (fzf#run with dir option and autochdir when final cwd is same as dir):
set acd
cd /tmp
call fzf#run({'source': ['/foobar'], 'sink': 'e', 'dir': '/', 'options': '-1'})
" Working directory changed due to &acd
AssertEqual '/', getcwd()
Execute (fzf#wrap):
AssertThrows fzf#wrap({'foo': 'bar'})
let opts = fzf#wrap('foobar')
Log opts
AssertEqual '~40%', opts.down
Assert opts.options =~ '--expect='
Assert !has_key(opts, 'sink')
Assert has_key(opts, 'sink*')
let opts = fzf#wrap('foobar', {}, 0)
Log opts
AssertEqual '~40%', opts.down
let opts = fzf#wrap('foobar', {}, 1)
Log opts
Assert !has_key(opts, 'down')
let opts = fzf#wrap('foobar', {'down': '50%'})
Log opts
AssertEqual '50%', opts.down
let opts = fzf#wrap('foobar', {'down': '50%'}, 1)
Log opts
Assert !has_key(opts, 'down')
let opts = fzf#wrap('foobar', {'sink': 'e'})
Log opts
AssertEqual 'e', opts.sink
Assert !has_key(opts, 'sink*')
let opts = fzf#wrap('foobar', {'options': '--reverse'})
Log opts
Assert opts.options =~ '--expect='
Assert opts.options =~ '--reverse'
let g:fzf_layout = {'window': 'enew'}
let opts = fzf#wrap('foobar')
Log opts
AssertEqual 'enew', opts.window
let opts = fzf#wrap('foobar', {}, 1)
Log opts
Assert !has_key(opts, 'window')
let opts = fzf#wrap('foobar', {'right': '50%'})
Log opts
Assert !has_key(opts, 'window')
AssertEqual '50%', opts.right
let opts = fzf#wrap('foobar', {'right': '50%'}, 1)
Log opts
Assert !has_key(opts, 'window')
Assert !has_key(opts, 'right')
let g:fzf_action = {'a': 'tabe'}
let opts = fzf#wrap('foobar')
Log opts
Assert opts.options =~ '--expect=a'
Assert !has_key(opts, 'sink')
Assert has_key(opts, 'sink*')
let opts = fzf#wrap('foobar', {'sink': 'e'})
Log opts
AssertEqual 'e', opts.sink
Assert !has_key(opts, 'sink*')
let g:fzf_history_dir = '/tmp'
let opts = fzf#wrap('foobar', {'options': '--color light'})
Log opts
Assert opts.options =~ "--history '/tmp/foobar'"
Assert opts.options =~ '--color light'
let g:fzf_colors = { 'fg': ['fg', 'Error'] }
let opts = fzf#wrap({})
Assert opts.options =~ '^--color=fg:'
Execute (fzf#shellescape with sh):
AssertEqual '''''', fzf#shellescape('', 'sh')
AssertEqual '''\''', fzf#shellescape('\', 'sh')
AssertEqual '''""''', fzf#shellescape('""', 'sh')
AssertEqual '''foobar>''', fzf#shellescape('foobar>', 'sh')
AssertEqual '''\\\"\\\''', fzf#shellescape('\\\"\\\', 'sh')
AssertEqual '''echo ''\''''a''\'''' && echo ''\''''b''\''''''', fzf#shellescape('echo ''a'' && echo ''b''', 'sh')
Execute (fzf#shellescape with cmd.exe):
AssertEqual '^"^"', fzf#shellescape('', 'cmd.exe')
AssertEqual '^"\\^"', fzf#shellescape('\', 'cmd.exe')
AssertEqual '^"\^"\^"^"', fzf#shellescape('""', 'cmd.exe')
AssertEqual '^"foobar^>^"', fzf#shellescape('foobar>', 'cmd.exe')
AssertEqual '^"\\\\\\\^"\\\\\\^"', fzf#shellescape('\\\"\\\', 'cmd.exe')
AssertEqual '^"echo ''a'' ^&^& echo ''b''^"', fzf#shellescape('echo ''a'' && echo ''b''', 'cmd.exe')
AssertEqual '^"C:\Program Files ^(x86^)\\^"', fzf#shellescape('C:\Program Files (x86)\', 'cmd.exe')
AssertEqual '^"C:/Program Files ^(x86^)/^"', fzf#shellescape('C:/Program Files (x86)/', 'cmd.exe')
AssertEqual '^"%%USERPROFILE%%^"', fzf#shellescape('%USERPROFILE%', 'cmd.exe')
Execute (Cleanup):
unlet g:dir
Restore

View File

@ -1,665 +0,0 @@
#!/usr/bin/env ruby
# encoding: utf-8
require 'curses'
require 'timeout'
require 'stringio'
require 'minitest/autorun'
$LOAD_PATH.unshift File.expand_path('../..', __FILE__)
ENV['FZF_EXECUTABLE'] = '0'
load 'fzf'
class TestFZF < MiniTest::Unit::TestCase
def setup
ENV.delete 'FZF_DEFAULT_SORT'
ENV.delete 'FZF_DEFAULT_OPTS'
ENV.delete 'FZF_DEFAULT_COMMAND'
end
def test_default_options
fzf = FZF.new []
assert_equal 1000, fzf.sort
assert_equal false, fzf.multi
assert_equal true, fzf.color
assert_equal nil, fzf.rxflag
assert_equal true, fzf.mouse
assert_equal nil, fzf.nth
assert_equal true, fzf.color
assert_equal false, fzf.black
assert_equal true, fzf.ansi256
assert_equal '', fzf.query.get
assert_equal false, fzf.select1
assert_equal false, fzf.exit0
assert_equal nil, fzf.filter
assert_equal nil, fzf.extended
end
def test_environment_variables
# Deprecated
ENV['FZF_DEFAULT_SORT'] = '20000'
fzf = FZF.new []
assert_equal 20000, fzf.sort
assert_equal nil, fzf.nth
ENV['FZF_DEFAULT_OPTS'] =
'-x -m -s 10000 -q " hello world " +c +2 --select-1 -0 ' +
'--no-mouse -f "goodbye world" --black --nth=3,-1,2'
fzf = FZF.new []
assert_equal 10000, fzf.sort
assert_equal ' hello world ',
fzf.query.get
assert_equal 'goodbye world',
fzf.filter
assert_equal :fuzzy, fzf.extended
assert_equal true, fzf.multi
assert_equal false, fzf.color
assert_equal false, fzf.ansi256
assert_equal true, fzf.black
assert_equal false, fzf.mouse
assert_equal true, fzf.select1
assert_equal true, fzf.exit0
assert_equal [3, -1, 2], fzf.nth
end
def test_option_parser
# Long opts
fzf = FZF.new %w[--sort=2000 --no-color --multi +i --query hello --select-1
--exit-0 --filter=howdy --extended-exact
--no-mouse --no-256 --nth=1]
assert_equal 2000, fzf.sort
assert_equal true, fzf.multi
assert_equal false, fzf.color
assert_equal false, fzf.ansi256
assert_equal false, fzf.black
assert_equal false, fzf.mouse
assert_equal 0, fzf.rxflag
assert_equal 'hello', fzf.query.get
assert_equal true, fzf.select1
assert_equal true, fzf.exit0
assert_equal 'howdy', fzf.filter
assert_equal :exact, fzf.extended
assert_equal [1], fzf.nth
# Long opts (left-to-right)
fzf = FZF.new %w[--sort=2000 --no-color --multi +i --query=hello
--filter a --filter b --no-256 --black --nth -1 --nth -2
--select-1 --exit-0 --no-select-1 --no-exit-0
--no-sort -i --color --no-multi --256]
assert_equal nil, fzf.sort
assert_equal false, fzf.multi
assert_equal true, fzf.color
assert_equal true, fzf.ansi256
assert_equal true, fzf.black
assert_equal true, fzf.mouse
assert_equal 1, fzf.rxflag
assert_equal 'b', fzf.filter
assert_equal 'hello', fzf.query.get
assert_equal false, fzf.select1
assert_equal false, fzf.exit0
assert_equal nil, fzf.extended
assert_equal [-2], fzf.nth
# Short opts
fzf = FZF.new %w[-s2000 +c -m +i -qhello -x -fhowdy +2 -n3 -1 -0]
assert_equal 2000, fzf.sort
assert_equal true, fzf.multi
assert_equal false, fzf.color
assert_equal false, fzf.ansi256
assert_equal 0, fzf.rxflag
assert_equal 'hello', fzf.query.get
assert_equal 'howdy', fzf.filter
assert_equal :fuzzy, fzf.extended
assert_equal [3], fzf.nth
assert_equal true, fzf.select1
assert_equal true, fzf.exit0
# Left-to-right
fzf = FZF.new %w[-s 2000 +c -m +i -qhello -x -fgoodbye +2 -n3 -n4,5
-s 3000 -c +m -i -q world +x -fworld -2 --black --no-black
-1 -0 +1 +0
]
assert_equal 3000, fzf.sort
assert_equal false, fzf.multi
assert_equal true, fzf.color
assert_equal true, fzf.ansi256
assert_equal false, fzf.black
assert_equal 1, fzf.rxflag
assert_equal 'world', fzf.query.get
assert_equal false, fzf.select1
assert_equal false, fzf.exit0
assert_equal 'world', fzf.filter
assert_equal nil, fzf.extended
assert_equal [4, 5], fzf.nth
rescue SystemExit => e
assert false, "Exited"
end
def test_invalid_option
[%w[--unknown], %w[yo dawg]].each do |argv|
assert_raises(SystemExit) do
fzf = FZF.new argv
end
end
assert_raises(SystemExit) do
fzf = FZF.new %w[--nth=0]
end
assert_raises(SystemExit) do
fzf = FZF.new %w[-n 0]
end
end
# FIXME Only on 1.9 or above
def test_width
fzf = FZF.new []
assert_equal 5, fzf.width('abcde')
assert_equal 4, fzf.width('한글')
assert_equal 5, fzf.width('한글.')
end
def test_trim
fzf = FZF.new []
assert_equal ['사.', 6], fzf.trim('가나다라마바사.', 4, true)
assert_equal ['바사.', 5], fzf.trim('가나다라마바사.', 5, true)
assert_equal ['바사.', 5], fzf.trim('가나다라마바사.', 6, true)
assert_equal ['마바사.', 4], fzf.trim('가나다라마바사.', 7, true)
assert_equal ['가나', 6], fzf.trim('가나다라마바사.', 4, false)
assert_equal ['가나', 6], fzf.trim('가나다라마바사.', 5, false)
assert_equal ['가나a', 6], fzf.trim('가나ab라마바사.', 5, false)
assert_equal ['가나ab', 5], fzf.trim('가나ab라마바사.', 6, false)
assert_equal ['가나ab', 5], fzf.trim('가나ab라마바사.', 7, false)
end
def test_format
fzf = FZF.new []
assert_equal [['01234..', false]], fzf.format('0123456789', 7, [])
assert_equal [['012', false], ['34', true], ['..', false]],
fzf.format('0123456789', 7, [[3, 5]])
assert_equal [['..56', false], ['789', true]],
fzf.format('0123456789', 7, [[7, 10]])
assert_equal [['..56', false], ['78', true], ['9', false]],
fzf.format('0123456789', 7, [[7, 9]])
(3..5).each do |i|
assert_equal [['..', false], ['567', true], ['89', false]],
fzf.format('0123456789', 7, [[i, 8]])
end
assert_equal [['..', false], ['345', true], ['..', false]],
fzf.format('0123456789', 7, [[3, 6]])
assert_equal [['012', false], ['34', true], ['..', false]],
fzf.format('0123456789', 7, [[3, 5]])
# Multi-region
assert_equal [["0", true], ["1", false], ["2", true], ["34..", false]],
fzf.format('0123456789', 7, [[0, 1], [2, 3]])
assert_equal [["..", false], ["5", true], ["6", false], ["78", true], ["9", false]],
fzf.format('0123456789', 7, [[3, 6], [7, 9]])
assert_equal [["..", false], ["3", true], ["4", false], ["5", true], ["..", false]],
fzf.format('0123456789', 7, [[3, 4], [5, 6]])
# Multi-region Overlap
assert_equal [["..", false], ["345", true], ["..", false]],
fzf.format('0123456789', 7, [[4, 5], [3, 6]])
end
def test_fuzzy_matcher
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE
list = %w[
juice
juiceful
juiceless
juicily
juiciness
juicy]
assert matcher.caches.empty?
assert_equal(
[["juice", [[0, 1]]],
["juiceful", [[0, 1]]],
["juiceless", [[0, 1]]],
["juicily", [[0, 1]]],
["juiciness", [[0, 1]]],
["juicy", [[0, 1]]]], matcher.match(list, 'j', '', '').sort)
assert !matcher.caches.empty?
assert_equal [list.object_id], matcher.caches.keys
assert_equal 1, matcher.caches[list.object_id].length
assert_equal 6, matcher.caches[list.object_id]['j'].length
assert_equal(
[["juicily", [[0, 5]]],
["juiciness", [[0, 5]]]], matcher.match(list, 'jii', '', '').sort)
assert_equal(
[["juicily", [[2, 5]]],
["juiciness", [[2, 5]]]], matcher.match(list, 'ii', '', '').sort)
assert_equal 3, matcher.caches[list.object_id].length
assert_equal 2, matcher.caches[list.object_id]['ii'].length
# TODO : partial_cache
end
def test_fuzzy_matcher_rxflag
assert_equal nil, FZF::FuzzyMatcher.new(nil).rxflag
assert_equal 0, FZF::FuzzyMatcher.new(0).rxflag
assert_equal 1, FZF::FuzzyMatcher.new(1).rxflag
assert_equal 1, FZF::FuzzyMatcher.new(nil).rxflag_for('abc')
assert_equal 0, FZF::FuzzyMatcher.new(nil).rxflag_for('Abc')
assert_equal 0, FZF::FuzzyMatcher.new(0).rxflag_for('abc')
assert_equal 0, FZF::FuzzyMatcher.new(0).rxflag_for('Abc')
assert_equal 1, FZF::FuzzyMatcher.new(1).rxflag_for('abc')
assert_equal 1, FZF::FuzzyMatcher.new(1).rxflag_for('Abc')
end
def test_fuzzy_matcher_case_sensitive
# Smart-case match (Uppercase found)
assert_equal [['Fruit', [[0, 5]]]],
FZF::FuzzyMatcher.new(nil).match(%w[Fruit Grapefruit], 'Fruit', '', '').sort
# Smart-case match (Uppercase not-found)
assert_equal [["Fruit", [[0, 5]]], ["Grapefruit", [[5, 10]]]],
FZF::FuzzyMatcher.new(nil).match(%w[Fruit Grapefruit], 'fruit', '', '').sort
# Case-sensitive match (-i)
assert_equal [['Fruit', [[0, 5]]]],
FZF::FuzzyMatcher.new(0).match(%w[Fruit Grapefruit], 'Fruit', '', '').sort
# Case-insensitive match (+i)
assert_equal [["Fruit", [[0, 5]]], ["Grapefruit", [[5, 10]]]],
FZF::FuzzyMatcher.new(Regexp::IGNORECASE).
match(%w[Fruit Grapefruit], 'Fruit', '', '').sort
end
def test_extended_fuzzy_matcher_case_sensitive
%w['Fruit Fruit$].each do |q|
# Smart-case match (Uppercase found)
assert_equal [['Fruit', [[0, 5]]]],
FZF::ExtendedFuzzyMatcher.new(nil).match(%w[Fruit Grapefruit], q, '', '').sort
# Smart-case match (Uppercase not-found)
assert_equal [["Fruit", [[0, 5]]], ["Grapefruit", [[5, 10]]]],
FZF::ExtendedFuzzyMatcher.new(nil).match(%w[Fruit Grapefruit], q.downcase, '', '').sort
# Case-sensitive match (-i)
assert_equal [['Fruit', [[0, 5]]]],
FZF::ExtendedFuzzyMatcher.new(0).match(%w[Fruit Grapefruit], q, '', '').sort
# Case-insensitive match (+i)
assert_equal [["Fruit", [[0, 5]]], ["Grapefruit", [[5, 10]]]],
FZF::ExtendedFuzzyMatcher.new(Regexp::IGNORECASE).
match(%w[Fruit Grapefruit], q, '', '').sort
end
end
def test_extended_fuzzy_matcher
matcher = FZF::ExtendedFuzzyMatcher.new Regexp::IGNORECASE
list = %w[
juice
juiceful
juiceless
juicily
juiciness
juicy
_juice]
match = proc { |q, prefix|
matcher.match(list, q, prefix, '').sort.map { |p| [p.first, p.last.sort] }
}
assert matcher.caches.empty?
3.times do
['y j', 'j y'].each do |pat|
(0..pat.length - 1).each do |prefix_length|
prefix = pat[0, prefix_length]
assert_equal(
[["juicily", [[0, 1], [6, 7]]],
["juicy", [[0, 1], [4, 5]]]],
match.call(pat, prefix))
end
end
# $
assert_equal [["juiceful", [[7, 8]]]], match.call('l$', '')
assert_equal [["juiceful", [[7, 8]]],
["juiceless", [[5, 6]]],
["juicily", [[5, 6]]]], match.call('l', '')
# ^
assert_equal list.length, match.call('j', '').length
assert_equal list.length - 1, match.call('^j', '').length
# ^ + $
assert_equal 0, match.call('^juici$', '').length
assert_equal 1, match.call('^juice$', '').length
assert_equal 0, match.call('^.*$', '').length
# !
assert_equal 0, match.call('!j', '').length
# ! + ^
assert_equal [["_juice", []]], match.call('!^j', '')
# ! + $
assert_equal list.length - 1, match.call('!l$', '').length
# ! + f
assert_equal [["juicy", [[4, 5]]]], match.call('y !l', '')
# '
assert_equal %w[juiceful juiceless juicily],
match.call('il', '').map { |e| e.first }
assert_equal %w[juicily],
match.call("'il", '').map { |e| e.first }
assert_equal (list - %w[juicily]).sort,
match.call("!'il", '').map { |e| e.first }.sort
end
assert !matcher.caches.empty?
end
def test_xfuzzy_matcher_prefix_cache
matcher = FZF::ExtendedFuzzyMatcher.new Regexp::IGNORECASE
list = %w[
a.java
b.java
java.jive
c.java$
d.java
]
2.times do
assert_equal 5, matcher.match(list, 'java', 'java', '').length
assert_equal 3, matcher.match(list, 'java$', 'java$', '').length
assert_equal 1, matcher.match(list, 'java$$', 'java$$', '').length
assert_equal 0, matcher.match(list, '!java', '!java', '').length
assert_equal 4, matcher.match(list, '!^jav', '!^jav', '').length
assert_equal 4, matcher.match(list, '!^java', '!^java', '').length
assert_equal 2, matcher.match(list, '!^java !b !c', '!^java', '').length
end
end
def test_sort_by_rank
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE
xmatcher = FZF::ExtendedFuzzyMatcher.new Regexp::IGNORECASE
list = %w[
0____1
0_____1
01
____0_1
01_
_01_
0______1
___01___
]
assert_equal(
[["01", [[0, 2]]],
["01_", [[0, 2]]],
["_01_", [[1, 3]]],
["___01___", [[3, 5]]],
["____0_1", [[4, 7]]],
["0____1", [[0, 6]]],
["0_____1", [[0, 7]]],
["0______1", [[0, 8]]]],
FZF.sort(matcher.match(list, '01', '', '')))
assert_equal(
[["01", [[0, 1], [1, 2]]],
["01_", [[0, 1], [1, 2]]],
["_01_", [[1, 2], [2, 3]]],
["0____1", [[0, 1], [5, 6]]],
["0_____1", [[0, 1], [6, 7]]],
["____0_1", [[4, 5], [6, 7]]],
["0______1", [[0, 1], [7, 8]]],
["___01___", [[3, 4], [4, 5]]]],
FZF.sort(xmatcher.match(list, '0 1', '', '')))
assert_equal(
[["_01_", [[1, 3], [0, 4]], [4, 4, "_01_"]],
["___01___", [[3, 5], [0, 2]], [4, 8, "___01___"]],
["____0_1", [[4, 7], [0, 2]], [5, 7, "____0_1"]],
["0____1", [[0, 6], [1, 3]], [6, 6, "0____1"]],
["0_____1", [[0, 7], [1, 3]], [7, 7, "0_____1"]],
["0______1", [[0, 8], [1, 3]], [8, 8, "0______1"]]],
FZF.sort(xmatcher.match(list, '01 __', '', '')).map { |tuple|
tuple << FZF.rank(tuple)
}
)
end
def test_extended_exact_mode
exact = FZF::ExtendedFuzzyMatcher.new Regexp::IGNORECASE, :exact
fuzzy = FZF::ExtendedFuzzyMatcher.new Regexp::IGNORECASE, :fuzzy
list = %w[
extended-exact-mode-not-fuzzy
extended'-fuzzy-mode
]
assert_equal 2, fuzzy.match(list, 'extended', '', '').length
assert_equal 2, fuzzy.match(list, 'mode extended', '', '').length
assert_equal 2, fuzzy.match(list, 'xtndd', '', '').length
assert_equal 2, fuzzy.match(list, "'-fuzzy", '', '').length
assert_equal 2, exact.match(list, 'extended', '', '').length
assert_equal 2, exact.match(list, 'mode extended', '', '').length
assert_equal 0, exact.match(list, 'xtndd', '', '').length
assert_equal 1, exact.match(list, "'-fuzzy", '', '').length
assert_equal 2, exact.match(list, "-fuzzy", '', '').length
end
if RUBY_PLATFORM =~ /darwin/
NFD = '한글'
def test_nfc
assert_equal 6, NFD.length
assert_equal ["한글", [[0, 1], [1, 2]]],
FZF::UConv.nfc(NFD, [[0, 3], [3, 6]])
nfd2 = 'before' + NFD + 'after'
assert_equal 6 + 6 + 5, nfd2.length
nfc, offsets = FZF::UConv.nfc(nfd2, [[4, 14], [9, 13]])
o1, o2 = offsets
assert_equal 'before한글after', nfc
assert_equal 're한글af', nfc[(o1.first...o1.last)]
assert_equal '글a', nfc[(o2.first...o2.last)]
end
def test_nfd
nfc = '한글'
nfd = FZF::UConv.nfd(nfc)
assert_equal 2, nfd.length
assert_equal 6, nfd.join.length
assert_equal NFD, nfd.join
end
def test_nfd_fuzzy_matcher
matcher = FZF::FuzzyMatcher.new 0
assert_equal [], matcher.match([NFD + NFD], '할', '', '')
match = matcher.match([NFD + NFD], '글글', '', '')
assert_equal [[NFD + NFD, [[3, 12]]]], match
assert_equal ['한글한글', [[1, 4]]], FZF::UConv.nfc(*match.first)
end
def test_nfd_extended_fuzzy_matcher
matcher = FZF::ExtendedFuzzyMatcher.new 0
assert_equal [], matcher.match([NFD], "'글글", '', '')
match = matcher.match([NFD], "'한글", '', '')
assert_equal [[NFD, [[0, 6]]]], match
assert_equal ['한글', [[0, 2]]], FZF::UConv.nfc(*match.first)
end
end
def test_split
assert_equal ["a", "b", "c", "\xFF", "d", "e", "f"],
FZF::UConv.split("abc\xFFdef")
end
# ^$ -> matches empty item
def test_format_empty_item
fzf = FZF.new []
item = ['', [[0, 0]]]
line, offsets = fzf.convert_item item
tokens = fzf.format line, 80, offsets
assert_equal [], tokens
end
def test_mouse_event
interval = FZF::MouseEvent::DOUBLE_CLICK_INTERVAL
me = FZF::MouseEvent.new nil
me.v = 10
assert_equal false, me.double?(10)
assert_equal false, me.double?(20)
me.v = 20
assert_equal false, me.double?(10)
assert_equal false, me.double?(20)
me.v = 20
assert_equal false, me.double?(10)
assert_equal true, me.double?(20)
sleep interval
assert_equal false, me.double?(20)
end
def test_nth_match
list = [
' first second third',
'fourth fifth sixth',
]
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE
assert_equal list, matcher.match(list, 'f', '', '').map(&:first)
assert_equal [
[list[0], [[2, 5]]],
[list[1], [[9, 17]]]], matcher.match(list, 'is', '', '')
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [2]
assert_equal [[list[1], [[8, 9]]]], matcher.match(list, 'f', '', '')
assert_equal [[list[0], [[8, 9]]]], matcher.match(list, 's', '', '')
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [3]
assert_equal [[list[0], [[19, 20]]]], matcher.match(list, 'r', '', '')
# Comma-separated
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [3, 1]
assert_equal [[list[0], [[19, 20]]], [list[1], [[3, 4]]]], matcher.match(list, 'r', '', '')
# Ordered
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [1, 3]
assert_equal [[list[0], [[3, 4]]], [list[1], [[3, 4]]]], matcher.match(list, 'r', '', '')
regex = FZF.build_delim_regex "\t"
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [1], regex
assert_equal [[list[0], [[3, 10]]]], matcher.match(list, 're', '', '')
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [2], regex
assert_equal [], matcher.match(list, 'r', '', '')
assert_equal [[list[1], [[9, 17]]]], matcher.match(list, 'is', '', '')
# Negative indexing
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [-1], regex
assert_equal [[list[0], [[3, 6]]]], matcher.match(list, 'rt', '', '')
assert_equal [[list[0], [[2, 5]]], [list[1], [[9, 17]]]], matcher.match(list, 'is', '', '')
# Regex delimiter
regex = FZF.build_delim_regex "[ \t]+"
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [1], regex
assert_equal [list[1]], matcher.match(list, 'f', '', '').map(&:first)
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [2], regex
assert_equal [[list[0], [[1, 2]]], [list[1], [[8, 9]]]], matcher.match(list, 'f', '', '')
end
def stream_for str
StringIO.new(str).tap do |sio|
sio.instance_eval do
alias org_gets gets
def gets
org_gets.tap { |e| sleep 0.5 unless e.nil? }
end
end
end
end
def test_select_1
stream = stream_for "Hello\nWorld"
output = StringIO.new
begin
$stdout = output
FZF.new(%w[--query=ol --select-1], stream).start
rescue SystemExit => e
assert_equal 0, e.status
assert_equal 'World', output.string.chomp
ensure
$stdout = STDOUT
end
end
def test_select_1_without_query
stream = stream_for "Hello World"
output = StringIO.new
begin
$stdout = output
FZF.new(%w[--select-1], stream).start
rescue SystemExit => e
assert_equal 0, e.status
assert_equal 'Hello World', output.string.chomp
ensure
$stdout = STDOUT
end
end
def test_select_1_ambiguity
stream = stream_for "Hello\nWorld"
begin
Timeout::timeout(3) do
FZF.new(%w[--query=o --select-1], stream).start
end
flunk 'Should not reach here'
rescue Exception => e
Curses.close_screen
assert_instance_of Timeout::Error, e
end
end
def test_exit_0
stream = stream_for "Hello\nWorld"
output = StringIO.new
begin
$stdout = output
FZF.new(%w[--query=zz --exit-0], stream).start
rescue SystemExit => e
assert_equal 0, e.status
assert_equal '', output.string
ensure
$stdout = STDOUT
end
end
def test_exit_0_without_query
stream = stream_for ""
output = StringIO.new
begin
$stdout = output
FZF.new(%w[--exit-0], stream).start
rescue SystemExit => e
assert_equal 0, e.status
assert_equal '', output.string
ensure
$stdout = STDOUT
end
end
def test_ranking_overlap_match_regions
list = [
'1 3 4 2',
'1 2 3 4'
]
assert_equal [
['1 2 3 4', [[0, 13], [16, 22]]],
['1 3 4 2', [[0, 24], [12, 17]]],
], FZF.sort(FZF::ExtendedFuzzyMatcher.new(nil).match(list, '12 34', '', ''))
end
end

1755
test/test_go.rb Normal file

File diff suppressed because it is too large Load Diff

79
uninstall Executable file
View File

@ -0,0 +1,79 @@
#!/usr/bin/env bash
confirm() {
while [ 1 ]; do
read -p "$1" -n 1 -r
echo
if [[ "$REPLY" =~ ^[Yy] ]]; then
return 0
elif [[ "$REPLY" =~ ^[Nn] ]]; then
return 1
fi
done
}
remove() {
echo "Remove $1"
rm -f "$1"
}
remove_line() {
src=$(readlink "$1")
if [ $? -eq 0 ]; then
echo "Remove from $1 ($src):"
else
src=$1
echo "Remove from $1:"
fi
shift
line_no=1
match=0
while [ -n "$1" ]; do
line=$(sed -n "$line_no,\$p" "$src" | \grep -m1 -nF "$1")
if [ $? -ne 0 ]; then
shift
line_no=1
continue
fi
line_no=$(( $(sed 's/:.*//' <<< "$line") + line_no - 1 ))
content=$(sed 's/^[0-9]*://' <<< "$line")
match=1
echo " - Line #$line_no: $content"
[ "$content" = "$1" ] || confirm " - Remove (y/n) ? "
if [ $? -eq 0 ]; then
awk -v n=$line_no 'NR == n {next} {print}' "$src" > "$src.bak" &&
mv "$src.bak" "$src" || break
echo " - Removed"
else
echo " - Skipped"
line_no=$(( line_no + 1 ))
fi
done
[ $match -eq 0 ] && echo " - Nothing found"
echo
}
for shell in bash zsh; do
remove ~/.fzf.${shell}
remove_line ~/.${shell}rc \
"[ -f ~/.fzf.${shell} ] && source ~/.fzf.${shell}" \
"source ~/.fzf.${shell}"
done
bind_file=~/.config/fish/functions/fish_user_key_bindings.fish
if [ -f "$bind_file" ]; then
remove_line "$bind_file" "fzf_key_bindings"
fi
if [ -d ~/.config/fish/functions ]; then
remove ~/.config/fish/functions/fzf.fish
remove ~/.config/fish/functions/fzf_key_bindings.fish
if [ "$(ls -A ~/.config/fish/functions)" ]; then
echo "Can't delete non-empty directory: \"~/.config/fish/functions\""
else
rmdir ~/.config/fish/functions
fi
fi