Compare commits

...

944 Commits

Author SHA1 Message Date
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 4d4447779f.
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 f074709fc9.

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 987799f8fb.
2015-09-19 22:51:12 +09:00
Junegunn Choi
987799f8fb Revert "0.10.6"
This reverts commit d2f3604c1d.
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
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 c601fc6437.
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 6166e2dd80.
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 7b6a27cb5e.
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
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 cc9938d4c9.
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
Junegunn Choi
fe22213b51 Remove gif link 2014-04-14 16:48:54 +09:00
Junegunn Choi
aab42eaaba Update Vim plugin example 2014-04-12 20:02:04 +09:00
Junegunn Choi
16031b0d54 [Vim] Allow vertical split of tmux window 2014-04-12 19:53:39 +09:00
Junegunn Choi
ded184daaf Typo 2014-04-12 19:52:25 +09:00
Junegunn Choi
ecf90bd25b Update README 2014-04-06 15:25:58 +09:00
Junegunn Choi
d82e38adc1 0.8.3 2014-04-05 12:56:10 +09:00
Junegunn Choi
af677e7e35 Vim plugin: do not enable tmux-integration if version < 1.7 2014-04-04 12:43:29 +09:00
Junegunn Choi
6ad38bdad3 Update example: suppress error message from fc on bash (#37)
`'fc' -l 1` generated an error message on bash
2014-04-04 10:26:38 +09:00
Junegunn Choi
8b80136a87 Merge pull request #37 from wellle/zsh-history
Feed all zsh history into fzf
2014-04-03 23:14:28 +09:00
Christian Wellenbrock
97de919152 Feed all zsh history into fzf 2014-04-03 16:11:51 +02:00
Junegunn Choi
0eafa725b9 Fix test code indentation 2014-04-03 14:53:47 +09:00
Junegunn Choi
fa212efe5f Fix ranking when multiple regions overlap
e.g.
  Match region #1: [-----------]
  Match region #2:       [---]
  Match region #3:         [------]
2014-04-03 14:51:01 +09:00
Junegunn Choi
a9056ce90c Add gif showing tmux integration 2014-04-03 01:29:21 +09:00
Junegunn Choi
16682a3f92 Update fe example as the exit status from -0 has changed (#36) 2014-04-03 01:20:22 +09:00
Junegunn Choi
02c01c81a0 Improve -0 and -1 as suggested in #36
- Make -0 and -1 work without -q
- Change exit status to 0 when exiting with -0
2014-04-03 01:06:40 +09:00
Junegunn Choi
22d3929ae3 Implement --select-1 and --exit-0 (#27, #36) 2014-04-02 21:41:57 +09:00
Junegunn Choi
ab9fbf1967 Allow --nth option to take multiple indexes (comma-separated) 2014-04-02 01:49:07 +09:00
Junegunn Choi
608ec2b806 set -o nonomatch for zsh (#34)
Avoid error message in an empty directory
2014-04-01 21:39:40 +09:00
Junegunn Choi
e5ae4f0ef6 Do not load interactive parts when not required (#34) 2014-04-01 20:55:26 +09:00
Junegunn Choi
67ba87d390 Avoid CTRL-T error when default shell != zsh (#34) 2014-04-01 20:49:54 +09:00
Junegunn Choi
77d45cb173 Avoid starting interactive bash (#34) 2014-04-01 20:48:15 +09:00
Junegunn Choi
d83febea46 Merge pull request #35 from junegunn/fix-tmux-on-linux
Fix #34: tmux integration on Linux
2014-04-01 17:58:19 +09:00
Junegunn Choi
546a315884 Fix #34: tmux integration on Linux 2014-04-01 08:55:16 +00:00
Junegunn Choi
af616457e3 Use -p option of split-window instead of manual calculation 2014-03-31 13:48:53 +09:00
Junegunn Choi
1a100a2919 No need for screenrow() 2014-03-31 13:36:58 +09:00
Junegunn Choi
a85bb93b69 Fix use of screenrow when tmux height is given in % 2014-03-31 13:22:52 +09:00
Junegunn Choi
057eda060c Installation on other shells 2014-03-31 10:15:56 +09:00
Junegunn Choi
48f9ee6763 Update install script 2014-03-31 01:01:23 +09:00
Junegunn Choi
52b74abb99 Merge pull request #32 from junegunn/nth
Add --nth and --delimiter option
2014-03-30 15:19:05 +09:00
Junegunn Choi
ec4b8a59fa Implement --nth and --delimiter option 2014-03-30 15:12:04 +09:00
Junegunn Choi
cf8dbf8047 Allow setting tmux split height in % 2014-03-28 17:15:45 +09:00
Junegunn Choi
995d380200 Merge pull request #30 from junegunn/keybinding-tmux-split
Make CTRL-T use tmux split when possible
2014-03-28 15:32:23 +09:00
Junegunn Choi
ae86cdf09a Make CTRL-T use tmux split when possible 2014-03-28 15:28:10 +09:00
Junegunn Choi
2b346659a0 Vim plugin: tmux integration 2014-03-28 00:58:07 +09:00
Junegunn Choi
49081711a9 Execute clear before fzf 2014-03-26 01:34:59 +09:00
Junegunn Choi
e7439ce193 Major update to Vim plugin 2014-03-25 19:55:52 +09:00
Junegunn Choi
b8e438b6be Prefer pre-existing function/alias in Vim plugin 2014-03-25 12:05:57 +09:00
Junegunn Choi
678e950b6d Use --reverse option in fco example (#29) 2014-03-20 10:38:53 +09:00
Junegunn Choi
9ea651f1cd Merge pull request #29 from wellle/fix/fco
Fix small typo in Readme
2014-03-20 10:33:47 +09:00
Christian Wellenbrock
bd98a08b89 Fix small typo in Readme 2014-03-20 00:07:47 +01:00
Junegunn Choi
f02bb4fdac Add fe command to examples section as suggested in #27 2014-03-20 01:57:57 +09:00
Junegunn Choi
0a8352a5cd Quote $1 in vimf example (#26) 2014-03-19 21:57:03 +09:00
Junegunn Choi
737423995d Merge pull request #28 from wellle/ignore-dsstore
Add .DS_Store to .gitignore
2014-03-19 21:19:28 +09:00
Christian Wellenbrock
2916bf7ee4 Add .DS_Store to .gitignore 2014-03-19 13:14:20 +01:00
Junegunn Choi
fa54c5d9b0 Merge pull request #26 from wellle/vimf-query
Add --query parameter to fzf invocation in vimf function
2014-03-19 21:09:52 +09:00
Christian Wellenbrock
693b6651b4 Add --query parameter to fzf invocation in vimf function 2014-03-19 12:30:42 +01:00
Junegunn Choi
5c71ecb267 Implement C-Y (yank) 2014-03-15 16:55:20 +09:00
Junegunn Choi
1ba50eba98 Fix gemspec
Reference:
16ead977fa
2014-03-14 18:25:55 +09:00
Junegunn Choi
2c8a256b13 Update README and install
- Unset multi-select option with +m
2014-03-14 17:53:23 +09:00
Junegunn Choi
f4c5aa03d7 Update README and install script
- Added examples: fbr and fco
- Always use local variables
2014-03-14 17:46:55 +09:00
Junegunn Choi
c6acb2a639 Update README 2014-03-13 15:28:01 +09:00
Junegunn Choi
2296013174 Add ALT-C keybinding for bash 2014-03-13 14:29:27 +09:00
Junegunn Choi
8a3e8c2d81 Install curses gem in user's home directory 2014-03-13 11:01:35 +09:00
Junegunn Choi
ae84d8c7a4 Update README 2014-03-09 11:52:35 +09:00
Junegunn Choi
dbd627c38a Update README: Remove section on --disable-gems
This is automatically set in install script. It may only cause unnecessary
confusion.
2014-03-09 10:46:34 +09:00
Junegunn Choi
d172c3ce03 Update README 2014-03-09 10:43:59 +09:00
Junegunn Choi
9904f5354e Add --black for terminals incapable of use_default_colors
See the discussion in #18.

Use --black option to use black background regardless of the default
background color of the terminal. Also, this option can be used to fix
rendering issues on terminals that don't support use_default_colors (man
3 default_colors). Depending on the terminal, use_default_colors may or
may not succeed, but the Ruby version of it always returns nil, it's
currently not possible to automatically enable this option.
2014-03-09 04:09:09 +09:00
Junegunn Choi
f345bf7983 Shift-left/right on OSX 2014-03-08 01:55:48 +09:00
Junegunn Choi
875f9b6534 Reduce timeout to 0.1 sec 2014-03-08 01:55:11 +09:00
Junegunn Choi
871dfb709d Introduce escape time-out for better handling of escape sequences 2014-03-08 01:28:32 +09:00
Junegunn Choi
19e24bd644 Home/End/PgUp/PgDn/Del/(Ins) 2014-03-08 01:02:32 +09:00
Junegunn Choi
457a240457 Add option to disable 256-color output (related #18) 2014-03-07 17:34:11 +09:00
Junegunn Choi
bbf4567dd8 Allow command/control-click/wheel
e.g. urxvt
2014-03-07 17:30:44 +09:00
Junegunn Choi
27d3b52843 Update gem version 2014-03-07 01:37:33 +09:00
Junegunn Choi
dcb4694ec1 Reimplement mouse input without using Curses.getch 2014-03-06 20:52:46 +09:00
Junegunn Choi
2fb8ae010f Completely remove mouse support
Since the version 0.7.0, fzf internally used Curses.getch() call to take user
input, which allowed it to support mouse input as well. However it has turned
out that Curses.getch() has introduced glitches that cannot be easily handled
(e.g. Try resize the terminal). So I finally decided that it's not worth the
trouble and drop the mouse support.
2014-03-06 12:21:09 +09:00
Junegunn Choi
65ae6cabb5 Rename variables 2014-03-05 22:41:45 +09:00
Junegunn Choi
86a66da04d Synchronize getch calls to reduce screen glitches 2014-03-05 19:07:59 +09:00
Junegunn Choi
d66b02b0cd Disable typeahead optimization in Ruby 1.8 2014-03-05 18:00:20 +09:00
Junegunn Choi
b3182c3304 Performance optimization: batch application of input chars 2014-03-05 11:21:20 +09:00
Junegunn Choi
2dbca00bfb Implement --extended-exact option (#24) 2014-03-04 21:29:45 +09:00
Junegunn Choi
b22fd6de6d Fix #22. Keybindings for vi-mode bash. 2014-03-04 18:53:29 +09:00
Junegunn Choi
245ee42763 Update installation instruction 2014-03-04 11:25:50 +09:00
Junegunn Choi
98bef4600c Merge pull request #20 from wellle/zsh-history-fc
Use `fc` instead of `history` to avoid `oh-my-zsh` alias
2014-02-26 19:03:01 +09:00
Christian Wellenbrock
f5d53b94fe Use fc instead of history to avoid omz alias 2014-02-26 10:56:44 +01:00
Junegunn Choi
00c8a68430 Unalias history on zsh (related #19) 2014-02-26 11:46:30 +09:00
Junegunn Choi
c1be834ff9 Merge pull request #19 from wellle/zsh_history
Feed all zsh history into fzf (not only most recent)
2014-02-25 23:41:54 +09:00
Christian Wellenbrock
2c0dc2f3b1 Feed all zsh history into fzf (not only most recent) 2014-02-25 15:40:52 +01:00
Junegunn Choi
1c94fef720 Update version number 2014-02-20 15:17:29 +09:00
Junegunn Choi
b711d76b8e Choose to use 256-colors when $TERM includes 256 (related: #18)
It turned out that Curses.can_change_color? returns false when $TERM is
set to screen-256color, which is perfectly capable of rendering 256
colors.
2014-02-20 13:38:04 +09:00
Junegunn Choi
4396ab7548 Do not set key bindings in non-interactive shell 2014-02-15 01:29:16 +09:00
Junegunn Choi
2b8c2b9f2a CTRL-R for bash: Unset $HISTTIMEFORMAT 2014-02-13 16:47:53 +09:00
Junegunn Choi
426284c87e Change CTRL-T binding to include directories 2014-02-07 18:41:05 +09:00
Junegunn Choi
089691faaf Cache the result as sorted 2014-02-02 21:41:08 +09:00
Junegunn Choi
301290663d Add -f (--filter) option (#15)
This commit adds --filter option so that fzf can be used as a simple unix
filter instead of being an interactive fuzzy finder.
2014-02-02 01:45:44 +09:00
Junegunn Choi
1155da7e1c Install curses 1.0.0 2014-02-02 01:42:04 +09:00
Junegunn Choi
eca0a99fb4 Proper handling of typeahead arrow keys
To reproduce: `sleep 2; fzf` and press arrow keys
2014-02-01 10:07:59 +09:00
Junegunn Choi
96215c4619 CTRL-L to clear and redraw the screen 2014-02-01 02:05:58 +09:00
Junegunn Choi
b2d2be55ef init_screen must be called within render block 2014-01-31 15:56:37 +09:00
73 changed files with 14386 additions and 1349 deletions

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

@@ -0,0 +1,29 @@
<!-- Check all that apply [x] -->
- Category
- [ ] fzf binary
- [ ] fzf-tmux script
- [ ] Key bindings
- [ ] Completion
- [ ] Vim
- [ ] Neovim
- [ ] Etc.
- OS
- [ ] Linux
- [ ] Mac OS X
- [ ] Windows
- [ ] 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 ...
-->

4
.gitignore vendored
View File

@@ -1,2 +1,6 @@
bin
src/fzf/fzf-*
gopath
pkg
Gemfile.lock
.DS_Store

24
.travis.yml Normal file
View File

@@ -0,0 +1,24 @@
language: ruby
rvm:
- 2.2.0
install:
- sudo apt-get update
- sudo apt-get install -y libncurses-dev lib32ncurses5-dev libgpm-dev
- 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=1.9a-1~ppa1~p
- sudo apt-get install -y zsh fish
script: |
export GOPATH=~/go
export FZF_BASE=$GOPATH/src/github.com/junegunn/fzf
mkdir -p $GOPATH/src/github.com/junegunn
ln -s $(pwd) $FZF_BASE
cd $FZF_BASE/src && make test fzf/fzf-linux_amd64 install &&
cd $FZF_BASE/bin && ln -sf fzf-linux_amd64 fzf-$(./fzf --version)-linux_amd64 &&
cd $FZF_BASE && yes | ./install && rm -f fzf &&
tmux new "ruby test/test_go.rb > out && touch ok" && cat out && [ -e ok ]

469
CHANGELOG.md Normal file
View File

@@ -0,0 +1,469 @@
CHANGELOG
=========
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.

439
README.md
View File

@@ -1,72 +1,90 @@
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)
===
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)
It was heavily inspired by [ctrlp.vim](https://github.com/kien/ctrlp.vim) and
the likes.
Pros
----
Requirements
------------
fzf requires Ruby (>= 1.8.5).
- No dependencies
- Blazingly fast
- The most comprehensive feature set
- Flexible layout using tmux panes
- Batteries included
- Vim/Neovim plugin, key bindings and fuzzy auto-completion
Installation
------------
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 git
Clone this repository 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:
### Using Homebrew
- `fzf` executable
- Key bindings (`CTRL-T`, `CTRL-R`, etc.)
- Fuzzy auto-completion for bash
On OS X, you can use [Homebrew](http://brew.sh/) to install fzf.
### Install as Vim plugin
```sh
brew install fzf
You can use any Vim plugin manager to install fzf for Vim. If you don't use one,
I recommend you try [vim-plug](https://github.com/junegunn/vim-plug).
# Install shell extensions
/usr/local/opt/fzf/install
```
1. [Install vim-plug](https://github.com/junegunn/vim-plug#usage)
2. Edit your .vimrc
### Vim plugin
call plug#begin()
Plug 'junegunn/fzf'
" ...
call plug#end()
You can manually add the directory to `&runtimepath` as follows,
3. Run `:PlugInstall`
```vim
" If installed using git
set rtp+=~/.fzf
" If installed using Homebrew
set rtp+=/usr/local/opt/fzf
```
But it's recommended that you use a plugin manager like
[vim-plug](https://github.com/junegunn/vim-plug).
```vim
Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
```
### 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`
- vim-plug: `:PlugUpdate fzf`
Usage
-----
```
usage: fzf [options]
Options
-m, --multi Enable multi-select
-x, --extended Extended-search mode
-q, --query=STR Initial query
-s, --sort=MAX Maximum number of matched items to sort (default: 1000)
+s, --no-sort Do not sort the result. Keep the sequence unchanged.
-i Case-insensitive match (default: smart-case match)
+i Case-sensitive match
+c, --no-color Disable colors
--no-mouse Disable mouse
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
selected item to STDOUT.
@@ -82,104 +100,104 @@ 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
```sh
history | fzf +s
- `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
#### Search syntax
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
!rmx`
| 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` |
| `!rmx` | inverse-fuzzy-match | Items that do not match `rmx` |
| `!'fire` | inverse-exact-match | Items that do not include `fire` |
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.
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$
```
### Key binding
#### Environment variables
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.
- `FZF_DEFAULT_COMMAND`
- Default command to use when input is tty
- e.g. `export FZF_DEFAULT_COMMAND='ag -g ""'`
- `FZF_DEFAULT_OPTS`
- Default options
- e.g. `export FZF_DEFAULT_OPTS="--reverse --inline-info"`
The following readline key bindings should also work as expected.
Examples
--------
- CTRL-A / CTRL-E
- CTRL-B / CTRL-F
- CTRL-W / CTRL-U
- ALT-B / ALT-F
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.
If you enable multi-select mode with `-m` option, you can select multiple items
with TAB or Shift-TAB key.
`fzf-tmux` script
-----------------
You can also use mouse. Click on an item to select it or shift-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 |
Useful examples
---------------
[fzf-tmux](bin/fzf-tmux) is a bash script that opens fzf in a tmux pane.
```sh
# vimf - Open selected file in Vim
vimf() {
FILE=$(fzf) && vim "$FILE"
}
# usage: fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS]
# (-[udlr]: up/down/left/right)
# fd - cd to selected directory
fd() {
DIR=$(find ${1:-*} -path '*/\.*' -prune -o -type d -print 2> /dev/null | fzf) && cd "$DIR"
}
# select git branches in horizontal split below (15 lines)
git branch | fzf-tmux -d 15
# fda - including hidden directories
fda() {
DIR=$(find ${1:-.} -type d 2> /dev/null | fzf) && cd "$DIR"
}
# fh - repeat history
fh() {
eval $(history | fzf +s | sed 's/ *[0-9]* *//')
}
# fkill - kill process
fkill() {
ps -ef | sed 1d | fzf -m | awk '{print $2}' | xargs kill -${1:-9}
}
# 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.
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
- `CTRL-T` - Paste the selected file path(s) into the command line
- `CTRL-R` - Paste the selected command from history into the command line
The source code can be found in `~/.fzf.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
- Sort is disabled by default to respect chronological ordering
- Press `CTRL-R` again to toggle sort
- 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
The source code can be found in `~/.fzf.zsh`.
If you're on a tmux session, fzf will start in a split pane. You may disable
this tmux integration by setting `FZF_TMUX` to 0, or change the height of the
pane with `FZF_TMUX_HEIGHT` (e.g. `20`, `50%`).
Auto-completion
---------------
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.
Disclaimer: *Auto-completion feature is currently experimental, it can change
over time*
### bash
Fuzzy completion for bash and zsh
---------------------------------
#### Files and directories
@@ -230,6 +248,14 @@ ssh **<TAB>
telnet **<TAB>
```
#### Environment variables / Aliases
```sh
unset **<TAB>
export **<TAB>
unalias **<TAB>
```
#### Settings
```sh
@@ -238,18 +264,38 @@ export FZF_COMPLETION_TRIGGER='~~'
# Options to fzf command
export FZF_COMPLETION_OPTS='+c -x'
# Use ag instead of the default find command for listing candidates.
# - The first argument to the function is the base path to start traversal
# - Note that ag only lists files not directories
# - See the source code (completion.{bash,zsh}) for the details.
_fzf_compgen_path() {
ag -g "" "$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.)
```sh
# There are also _fzf_path_completion and _fzf_dir_completion
complete -F _fzf_file_completion -o default -o bashdefault doge
```
Usage as Vim plugin
-------------------
If you install fzf as a Vim plugin, `:FZF` command will be added.
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
@@ -260,83 +306,126 @@ If you install fzf as a Vim plugin, `:FZF` command will be added.
" With options
:FZF --no-sort -m /tmp
" Bang version starts in fullscreen instead of using tmux pane or Neovim split
:FZF!
```
You can override the source command which produces input to 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. Refer to [the wiki page][fzf-config] for
customization.
[fzf-config]: https://github.com/junegunn/fzf/wiki/Configuring-FZF-command-(vim)
#### `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 | 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` (*Neovim only*) | 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) |
Examples can be found on [the wiki
page](https://github.com/junegunn/fzf/wiki/Examples-(vim)).
#### `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`, and `g:fzf_history_dir` like `:FZF`.
```vim
let g:fzf_source = 'find . -type f'
command! -bang MyStuff
\ call fzf#run(fzf#wrap('my-stuff', {'dir': '~/my-stuff'}, <bang>0))
```
And you can predefine default options to fzf command.
```vim
let g:fzf_options = '--no-color --extended'
```
For more advanced uses, you can call `fzf#run` function as follows.
```vim
:call fzf#run('tabedit', '-m +c')
```
Most of the time, you will prefer native Vim plugins with better integration
with Vim. The only reason one might consider using fzf in Vim is its speed. For
a very large list of files, fzf is significantly faster and it does not block.
Tips
----
### Faster startup with `--disable-gems` options
#### Rendering issues
If you're running Ruby 1.9 or above, you can improve the startup time with
`--disable-gems` option to Ruby.
If you have any rendering issues, check the following:
- `time ruby ~/bin/fzf -h`
- 0.077 sec
- `time ruby --disable-gems ~/bin/fzf -h`
- 0.025 sec
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`.
You can define fzf function with the option as follows:
#### Respecting `.gitignore`, `.hgignore`, and `svn:ignore`
[ag](https://github.com/ggreer/the_silver_searcher) or
[pt](https://github.com/monochromegane/the_platinum_searcher) will do the
filtering:
```sh
fzf() {
ruby --disable-gems ~/bin/fzf "$@"
}
export -f fzf
# Feed the output of ag into fzf
ag -g "" | fzf
# Setting ag as the default source for fzf
export FZF_DEFAULT_COMMAND='ag -g ""'
# Now fzf (w/o pipe) will use ag instead of find
fzf
# To apply the command to CTRL-T as well
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
```
However, this is automatically set up in your .bashrc and .zshrc if you use the
bundled [install](https://github.com/junegunn/fzf/blob/master/install) script.
### Incorrect display on Ruby 1.8
It is reported that the output of fzf can become unreadable on some terminals
when it's running on Ruby 1.8. If you experience the problem, upgrade your Ruby
to 1.9 or above. Ruby 1.9 or above is also required for 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`.
If you don't want to exclude hidden files, use the following command:
```sh
export FZF_DEFAULT_OPTS="--sort 20000"
export FZF_DEFAULT_COMMAND='ag --hidden --ignore .git -g ""'
```
#### `git ls-tree` for fast traversal
If you're running fzf in a large git repository, `git ls-tree` can boost up the
speed of the traversal.
```sh
export FZF_DEFAULT_COMMAND='
(git ls-tree -r --name-only HEAD ||
find . -path "*/\.*" -prune -o -type f -print -o -type l -print |
sed s/^..//) 2> /dev/null'
```
#### Fish shell
It's [a known bug of fish](https://github.com/fish-shell/fish-shell/issues/1362)
that it doesn't allow reading from STDIN in command substitution, which means
simple `vim (fzf)` won't work as expected. The workaround is to store the result
of fzf to a temporary file.
```sh
fzf > $TMPDIR/fzf.result; and vim (cat $TMPDIR/fzf.result)
```
License
-------
MIT
[MIT](LICENSE)
Author
------
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

179
bin/fzf-tmux Executable file
View File

@@ -0,0 +1,179 @@
#!/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)
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
[[ -n "$COLUMNS" ]] && max=$COLUMNS || max=$(tput cols)
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" ]] || [[ "$lines" -le 15 ]]; then
"$fzf" "${args[@]}"
exit $?
fi
# 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 swap-pane -t $original_window \; \
select-window -t $original_window \; \
kill-window -t $tmp_window \; \
resize-pane -Z
fi
}
trap cleanup EXIT SIGINT SIGTERM
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
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 "cd $(printf %q "$PWD");$envs 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 $argsf" $swap \
> /dev/null 2>&1
cat <&0 > $fifo1 &
fi
cat $fifo2
exit "$(cat $fifo3)"

1114
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.7.0'
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.add_runtime_dependency 'curses', '~> 1.0.0'
end

498
install
View File

@@ -1,184 +1,408 @@
#!/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!"
exit 1
fi
[[ "$@" =~ --pre ]] && version=0.15.3 pre=1 ||
version=0.15.3 pre=0
# 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
auto_completion=
key_bindings=
update_config=2
binary_arch=
allow_legacy=
echo "OK ($ruby)"
help() {
cat << EOF
usage: $0 [OPTIONS]
# 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
--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
--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|--pre) ;;
*)
echo "unknown option: $opt"
help
exit 1
;;
esac
done
cd "$(dirname "${BASH_SOURCE[0]}")"
fzf_base="$(pwd)"
ask() {
# If stdin is a tty, we are "interactive".
# non-interactive shell: wait for a linefeed
# interactive shell: continue after a single keypress
read_n=$([ -t 0 ] && echo "-n 1")
read -p "$1 ([y]/n) " $read_n -r
echo
[[ $REPLY =~ ^[Nn]$ ]]
}
check_binary() {
echo -n " - Checking fzf executable ... "
local output
output=$("$fzf_base"/bin/fzf --version 2>&1)
if [ $? -ne 0 ]; then
echo "Failed to install 'curses' gem."
echo "Try installing it as root: sudo gem install curses"
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
}
symlink() {
echo " - Creating symlink: bin/$1 -> bin/fzf"
(cd "$fzf_base"/bin &&
rm -f fzf &&
ln -sf $1 fzf)
if [ $? -ne 0 ]; then
binary_error="Failed to create symlink"
return 1
fi
}
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 && curl -fL $1 | tar -xz
}
try_wget() {
command -v wget > /dev/null && wget -O - $1 | tar -xz
}
download() {
echo "Downloading bin/fzf ..."
if [ $pre = 0 ]; then
if [ -x "$fzf_base"/bin/fzf ]; then
echo " - Already exists"
check_binary && return
fi
if [ -x "$fzf_base"/bin/$1 ]; then
symlink $1 && 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=https://github.com/junegunn/fzf-bin/releases/download/$version/${1}.tgz
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 $1 ]; then
binary_error="Failed to download ${1}"
return
fi
chmod +x $1 && symlink $1 && check_binary
}
# Try to download binary executable
archi=$(uname -sm)
binary_available=1
binary_error=""
case "$archi" in
Darwin\ x86_64) download fzf-$version-darwin_${binary_arch:-amd64} ;;
Darwin\ i*86) download fzf-$version-darwin_${binary_arch:-386} ;;
Linux\ x86_64) download fzf-$version-linux_${binary_arch:-amd64} ;;
Linux\ i*86) download fzf-$version-linux_${binary_arch:-386} ;;
*) binary_available=0 binary_error=1 ;;
esac
install_ruby_fzf() {
if [ -z "$allow_legacy" ]; then
ask "Do you want to install legacy Ruby version instead?" && exit 1
fi
echo "Installing legacy Ruby version ..."
# ruby executable
echo -n "Checking Ruby executable ... "
ruby=$(command -v ruby)
if [ $? -ne 0 ]; then
echo "ruby executable not found !!!"
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"
# System ruby is preferred
system_ruby=/usr/bin/ruby
if [ -x $system_ruby ] && [ $system_ruby != "$ruby" ]; then
$system_ruby --disable-gems -rcurses -e0 2> /dev/null
[ $? -eq 0 ] && ruby=$system_ruby
fi
echo "OK ($ruby)"
# Curses-support
echo -n "Checking Curses support ... "
"$ruby" -rcurses -e0 2> /dev/null
if [ $? -eq 0 ]; then
fzf_cmd="$ruby --disable-gems $fzf_base/fzf"
echo "OK"
else
echo "Not found"
echo "Installing 'curses' gem ... "
if (( EUID )); then
/usr/bin/env gem install curses --user-install
else
/usr/bin/env gem install curses
fi
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."
fi
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 -rcurses -e0 2> /dev/null
if [ $? -eq 0 ]; then
fzf_cmd="$ruby --disable-gems $fzf_base/fzf"
else
fzf_cmd="$ruby $fzf_base/fzf"
fi
else
echo "< 1.9"
fzf_cmd="$ruby $fzf_base/fzf"
fi
else
echo "< 1.9"
fzf_cmd="$ruby $fzf_base/fzf"
# Create fzf script
echo -n "Creating wrapper script for fzf ... "
rm -f "$fzf_base"/bin/fzf
echo "#!/bin/sh" > "$fzf_base"/bin/fzf
echo "$fzf_cmd \"\$@\"" >> "$fzf_base"/bin/fzf
chmod +x "$fzf_base"/bin/fzf
echo "OK"
}
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/src/fzf) ... "
if [ -z "${GOPATH-}" ]; then
export GOPATH="${TMPDIR:-/tmp}/fzf-gopath"
mkdir -p "$GOPATH"
fi
if go get -u github.com/junegunn/fzf/src/fzf; then
echo "OK"
cp "$GOPATH/bin/fzf" "$fzf_base/bin/"
else
echo "Failed to build binary ..."
install_ruby_fzf
fi
else
echo "go executable not found. Cannot build binary ..."
install_ruby_fzf
fi
fi
[[ "$*" =~ "--bin" ]] && exit 0
# 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
has_zsh=$(command -v zsh > /dev/null && echo 1 || echo 0)
shells=$([ $has_zsh -eq 1 ] && echo "bash zsh" || echo "bash")
for shell in $shells; do
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
# Man path
# --------
if [[ ! "\$MANPATH" == *$fzf_base/man* && -d "$fzf_base/man" ]]; then
export MANPATH="\$MANPATH:$fzf_base/man"
fi
# Auto-completion
# ---------------
$fzf_completion
EOF
if [ $key_bindings -eq 0 ]; then
if [ $shell = bash ]; then
cat >> $src << "EOF"
# Key bindings
# ------------
# Required to refresh the prompt after fzf
bind '"\er": redraw-current-line'
# CTRL-T - Paste the selected file path into the command line
__fsel() {
find * -path '*/\.*' -prune \
-o -type f -print \
-o -type l -print 2> /dev/null | fzf -m | while read item; do
printf '%q ' "$item"
done
echo
}
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"'
# CTRL-R - Paste the selected command from history into the command line
bind '"\C-r": " \C-e\C-u$(history | fzf +s | sed \"s/ *[0-9]* *//\")\e\C-e\er"'
$fzf_key_bindings
EOF
else
cat >> $src << "EOF"
# Key bindings
# ------------
# CTRL-T - Paste the selected file path(s) into the command line
fzf-file-widget() {
local FILES
local IFS="
"
FILES=($(
find * -path '*/\.*' -prune \
-o -type f -print \
-o -type l -print 2> /dev/null | fzf -m))
unset IFS
FILES=$FILES:q
LBUFFER="${LBUFFER%% #} $FILES"
zle redisplay
}
zle -N fzf-file-widget
bindkey '^T' fzf-file-widget
# ALT-C - cd into the selected directory
fzf-cd-widget() {
cd "${$(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=$(history | fzf +s | sed "s/ *[0-9]* *//")
zle redisplay
}
zle -N fzf-history-widget
bindkey '^R' fzf-history-widget
EOF
fi
fi
echo "OK"
done
echo
for shell in bash zsh; do
rc=~/.${shell}rc
src="source ~/.fzf.${shell}"
# fish
has_fish=$(command -v fish > /dev/null && echo 1 || echo 0)
if [ $has_fish -eq 1 ]; 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"
if [ $(grep -F "$src" $rc | wc -l) -gt 0 ]; then
echo " - Not added (already being sourced)"
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:-}"
echo "Update $file:"
echo " - $line"
[ -f "$file" ] || touch "$file"
if [ $# -lt 4 ]; then
lno=$(\grep -nF "$line" "$file" | sed 's/:.*//' | tr '\n' ' ')
else
lno=$(\grep -nF "$pat" "$file" | sed 's/:.*//' | tr '\n' ' ')
fi
if [ -n "$lno" ]; then
echo " - Already exists: line #$lno"
else
if [ $update -eq 1 ]; then
echo >> "$file"
echo "$line" >> "$file"
echo " + Added"
else
echo " ~ Skipped"
fi
fi
echo
set +e
}
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 = 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
if [ $key_bindings -eq 1 ] && [ $has_fish -eq 1 ]; then
bind_file=~/.config/fish/functions/fish_user_key_bindings.fish
append_line $update_config "fzf_key_bindings" "$bind_file"
fi
To uninstall fzf, simply remove the added line.
EOF
if [ $update_config -eq 1 ]; then
echo 'Finished. Restart your shell or reload config file.'
echo ' source ~/.bashrc # bash'
[ $has_zsh -eq 1 ] && echo " source ${ZDOTDIR:-~}/.zshrc # zsh"
[ $has_fish -eq 1 ] && [ $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'

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

@@ -0,0 +1,54 @@
.ig
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.
..
.TH fzf-tmux 1 "Sep 2016" "fzf 0.15.3" "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

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

@@ -0,0 +1,509 @@
.ig
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.
..
.TH fzf 1 "Sep 2016" "fzf 0.15.3" "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
.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 "--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
.BI "--jump-labels=" "CHARS"
Label characters for \fBjump\fR and \fBjump-accept\fR
.SS Layout
.TP
.B "--reverse"
Reverse orientation
.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.
.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
\fBprompt \fRPrompt
\fBpointer \fRPointer to the current line
\fBmarker \fRMulti-select marker
\fBspinner \fRStreaming input indicator
\fBheader \fRHeader
.RE
.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 is the placeholder for the quoted string of the
current line.
.RS
e.g. \fBfzf --preview="head -$LINES {}"\fR
.RE
.TP
.BI "--preview-window=" "[POSITION][:SIZE[%]][: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.
.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.
.RS
e.g. \fBfzf --expect=ctrl-v,ctrl-t,alt-s,f1,f2,~,@\fR
.RE
.TP
.B "--read0"
Read input delimited by ASCII NUL character instead of newline character
.TP
.B "--print0"
Print output delimited by ASCII NUL character instead of newline character
.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
.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
.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.
.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
\fIalt-[a-z]\fR
\fIf[1-10]\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
\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-multi(...)\fR (see below for the details)
\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
\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-sort\fR (equivalent to \fB--toggle-sort\fR)
\fBtoggle-up\fR \fIbtab (shift-tab)\fR
\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
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
\fB{}\fR is the placeholder for the quoted string of the current line.
If the command contains parentheses, 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.
.RE
\fBexecute-multi(...)\fR is an alternative action that executes the command
with the selected entries when multi-select is enabled (\fB--multi\fR). With
this action, \fB{}\fR is replaced with the quoted strings of the selected
entries separated by spaces.
.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) 2013 Junegunn Choi
" Copyright (c) 2016 Junegunn Choi
"
" MIT License
"
@@ -21,39 +21,551 @@
" 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:exec = expand('<sfile>:h:h').'/fzf'
let s:default_layout = { 'down': '~40%' }
let s:layout_keys = ['window', 'up', 'down', 'left', 'right']
let s:fzf_go = expand('<sfile>:h:h').'/bin/fzf'
let s:install = expand('<sfile>:h:h').'/install'
let s:installed = 0
let s:fzf_tmux = expand('<sfile>:h:h').'/bin/fzf-tmux'
function! s:escape(path)
return substitute(a:path, ' ', '\\ ', 'g')
let s:cpo_save = &cpo
set cpo&vim
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'
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 s:shellesc(s:exec)
endfunction
function! fzf#run(command, ...)
let cwd = getcwd()
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') && 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').'"'
endfunction
function! s:escape(path)
return escape(a:path, ' $%#''"\')
endfunction
" Upgrade legacy options
function! s:upgrade(dict)
let copy = copy(a:dict)
if has_key(copy, 'tmux')
let copy.down = remove(copy, 'tmux')
endif
if has_key(copy, 'tmux_height')
let copy.down = remove(copy, 'tmux_height')
endif
if has_key(copy, 'tmux_width')
let copy.right = remove(copy, 'tmux_width')
endif
return copy
endfunction
function! s:error(msg)
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 && fnamemodify(a:target, ':p') ==# 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 len(a:lines) > 1
augroup fzf_swap
autocmd SwapExists * let v:swapchoice='o'
\| call s:warn('fzf: E325: swap file exists: '.expand('<afile>'))
augroup END
endif
try
let args = copy(a:000)
if len(args) > 0 && isdirectory(expand(args[-1]))
let dir = remove(args, -1)
execute 'chdir '.s:escape(dir)
endif
let argstr = join(args)
let tf = tempname()
let prefix = exists('g:fzf_source') ? g:fzf_source.'|' : ''
let fzf = executable(s:exec) ? s:exec : 'fzf'
let options = empty(argstr) ? get(g:, 'fzf_options', '') : argstr
execute "silent !".prefix.fzf.' '.options." > ".tf
if !v:shell_error
for line in readfile(tf)
if !empty(line)
execute a:command.' '.s:escape(line)
endif
endfor
endif
let empty = empty(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 exists('#BufEnter') && isdirectory(item)
doautocmd BufEnter
endif
endfor
finally
execute 'chdir '.s:escape(cwd)
redraw!
silent! call delete(tf)
let &autochdir = autochdir
silent! autocmd! fzf_swap
endtry
endfunction
command! -nargs=* -complete=dir FZF call fzf#run('silent e', <f-args>)
" [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
" 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, get(g:, 'fzf_layout', s:default_layout))
endif
endif
" History: g:fzf_history_dir
let opts.options = get(opts, 'options', '')
if len(name) && len(get(g:, 'fzf_history_dir', ''))
let dir = expand(g:fzf_history_dir)
if !isdirectory(dir)
call mkdir(dir, 'p')
endif
let opts.options = join(['--history', s:escape(dir.'/'.name), 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! fzf#run(...) abort
try
let oshell = &shell
set shell=sh
if has('nvim') && len(filter(range(1, bufnr('$')), 'bufname(v:val) =~# ";#FZF"'))
call s:warn('FZF is already running!')
return []
endif
let dict = exists('a:1') ? s:upgrade(a:1) : {}
let temps = { 'result': tempname() }
let optstr = get(dict, 'options', '')
try
let fzf_exec = s:fzf_exec()
catch
throw v:exception
endtry
if !has_key(dict, 'source') && !empty($FZF_DEFAULT_COMMAND)
let temps.source = tempname()
call writefile(split($FZF_DEFAULT_COMMAND, "\n"), temps.source)
let dict.source = (empty($SHELL) ? 'sh' : $SHELL) . ' ' . s:shellesc(temps.source)
endif
if has_key(dict, 'source')
let source = dict.source
let type = type(source)
if type == 1
let prefix = source.'|'
elseif type == 3
let temps.input = tempname()
call writefile(source, temps.input)
let prefix = 'cat '.s:shellesc(temps.input).'|'
else
throw 'invalid source type'
endif
else
let prefix = ''
endif
let tmux = (!has('nvim') || get(g:, 'fzf_prefer_tmux', 0)) && s:tmux_enabled() && s:splittable(dict)
let command = prefix.(tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
if has('nvim') && !tmux
return s:execute_term(dict, command, temps)
endif
let lines = tmux ? s:execute_tmux(dict, command, temps) : s:execute(dict, command, temps)
call s:callback(dict, lines)
return lines
finally
let &shell = oshell
endtry
endfunction
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, s:shellesc(s:fzf_tmux), size, (has_key(a:dict, 'source') ? '' : '-'))
endfunction
function! s:splittable(dict)
return s:present(a:dict, 'up', 'down', 'left', 'right')
endfunction
function! s:pushd(dict)
if s:present(a:dict, 'dir')
let cwd = 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 = getcwd()
return 1
endif
return 0
endfunction
augroup fzf_popd
autocmd!
autocmd WinEnter * call s:dopopd()
augroup END
function! s:dopopd()
if !exists('w:fzf_prev_dir') || exists('*haslocaldir') && !haslocaldir()
return
endif
execute 'lcd' s:escape(w:fzf_prev_dir)
unlet w:fzf_prev_dir
endfunction
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
let s:launcher = function('s:xterm_launcher')
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, temps) abort
call s:pushd(a:dict)
silent! !clear 2> /dev/null
let escaped = 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
let command = printf(fmt, "'".substitute(escaped, "'", "'\"'\"'", 'g')."'")
else
let command = escaped
endif
execute 'silent !'.command
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) 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 = 'cd '.s:escape(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
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
if s:present(a:dict, 'window')
execute a:dict.window
else
execute (tabpagenr()-1).'tabnew'
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 [ppos, winopts] = s:split(a:dict)
let fzf = { 'buf': bufnr('%'), '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
" FIXME: Can't re-enter normal mode from terminal mode
" execute "normal! \<c-^>"
b #
" 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
call termopen(a:command . ';#FZF', fzf)
finally
if s:present(a:dict, 'dir')
lcd -
endif
endtry
setlocal nospell bufhidden=wipe nobuflisted
setf fzf
startinsert
return []
endfunction
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
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_prev_dir = a:dict.prev_dir
endif
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_prev_dir = a:dict.prev_dir
call s:dopopd()
endif
endfunction
let s:default_action = {
\ 'ctrl-t': 'tab split',
\ 'ctrl-x': 'split',
\ 'ctrl-v': 'vsplit' }
function! s:cmd(bang, ...) abort
let args = copy(a:000)
let opts = {}
if len(args) && isdirectory(expand(args[-1]))
let opts.dir = substitute(remove(args, -1), '\\\(["'']\)', '\1', 'g')
endif
call fzf#run(fzf#wrap('FZF', extend({'options': join(args)}, opts), a:bang))
endfunction
command! -nargs=* -complete=dir -bang FZF call s:cmd(<bang>0, <f-args>)
let &cpo = s:cpo_save
unlet s:cpo_save

314
shell/completion.bash Normal file
View File

@@ -0,0 +1,314 @@
#!/bin/bash
# ____ ____
# / __/___ / __/
# / /_/_ / / /_
# / __/ / /_/ __/
# /_/ /___/_/-completion.bash
#
# - $FZF_TMUX (default: 1)
# - $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
###########################################################
_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
-i +i
-n --nth
-d --delimiter
+s --no-sort
--tac
--tiebreak
--bind
-m --multi
--no-mouse
--color
--black
--reverse
--no-hscroll
--inline-info
--prompt
-q --query
-1 --select-1
-0 --exit-0
-f --filter
--print-query
--expect
--toggle-sort
--sync
--cycle
--history
--history-size
--header
--header-lines
--margin"
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_TMUX:-1}" != 0 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
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/%\//}"
tput sc
matches=$(eval "$1 $(printf %q "$dir")" | $fzf $FZF_COMPLETION_OPTS $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
tput rc
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_TMUX:-1}" != 0 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
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}}
tput sc
selected=$(cat | $fzf $FZF_COMPLETION_OPTS $1 -q "$cur" | $post | tr '\n' ' ')
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
tput rc
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_TMUX:-1}" != 0 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
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_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 '*') \
<(command grep -oE '^[^ ]+' ~/.ssh/known_hosts | tr ',' '\n' | 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
if [ "$_fzf_completion_loaded" != '0.11.3' ]; then
# Really wish I could use associative array but OSX comes with bash 3.2 :(
eval $(complete | command grep '\-F' | command grep -v _fzf_ |
command grep -E " ($(echo $d_cmds $a_cmds $x_cmds | sed 's/ /|/g' | sed 's/+/\\+/g'))$" | _fzf_orig_completion_filter)
export _fzf_completion_loaded=0.11.3
fi
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 plusdirs"
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

194
shell/completion.zsh Normal file
View File

@@ -0,0 +1,194 @@
#!/bin/zsh
# ____ ____
# / __/___ / __/
# / /_/_ / / /_
# / __/ / /_/ __/
# /_/ /___/_/-completion.zsh
#
# - $FZF_TMUX (default: 1)
# - $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
###########################################################
__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_TMUX:-1} -eq 1 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
setopt localoptions nonomatch
dir="$base"
while [ 1 ]; do
if [ -z "$dir" -o -d ${~dir} ]; then
leftover=${base/#"$dir"}
leftover=${leftover/#\/}
[ -z "$dir" ] && dir='.'
[ "$dir" != "/" ] && dir="${dir/%\//}"
dir=${~dir}
matches=$(eval "$compgen $(printf %q "$dir")" | ${=fzf} ${=FZF_COMPLETION_OPTS} ${=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_TMUX:-1} -eq 1 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
_fzf_feed_fifo "$fifo"
matches=$(cat "$fifo" | ${=fzf} ${=FZF_COMPLETION_OPTS} ${=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' "$@" < <(
cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host' | command grep -v '*') \
<(command grep -oE '^[^ ]+' ~/.ssh/known_hosts | tr ',' '\n' | 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
# 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_TMUX:-1} -eq 1 ] && fzf="fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%}" || fzf="fzf"
matches=$(ps -ef | sed 1d | ${=fzf} ${=FZF_COMPLETION_OPTS} -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

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

@@ -0,0 +1,125 @@
# Key bindings
# ------------
__fzf_select__() {
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | sed 1d | cut -b3-"}"
eval "$cmd | fzf -m $FZF_CTRL_T_OPTS" | while read -r item; do
printf '%q ' "$item"
done
echo
}
if [[ $- =~ i ]]; then
__fzfcmd() {
[ "${FZF_TMUX:-1}" != 0 ] && 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__)\"; 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 . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \
-o -type d -print 2> /dev/null | sed 1d | cut -b3-"}"
dir=$(eval "$cmd | $(__fzfcmd) +m $FZF_ALT_C_OPTS") && printf 'cd %q' "$dir"
}
__fzf_history__() (
local line
shopt -u nocaseglob nocasematch
line=$(
HISTTIMEFORMAT= history |
eval "$(__fzfcmd) +s --tac +m -n2..,.. --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS" |
command grep '^ *[0-9]') &&
if [[ $- =~ H ]]; then
sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line"
else
sed 's/^ *\([0-9]*\)\** *//' <<< "$line"
fi
)
__fzf_use_tmux__() {
[ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-1}" != 0 ] && [ ${LINES:-40} -gt 15 ]
}
[ $BASH_VERSINFO -gt 3 ] && __use_bind_x=1 || __use_bind_x=0
__fzf_use_tmux__ && __use_tmux=1 || __use_tmux=0
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 [ $__use_bind_x -eq 1 ]; then
bind -x '"\C-t": "fzf-file-widget"'
elif [ $__use_tmux -eq 1 ]; 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 [ $__use_bind_x -eq 1 ]; then
bind -x '"\C-t": "fzf-file-widget"'
elif [ $__use_tmux -eq 1 ]; 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
unset -v __use_tmux __use_bind_x
fi

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

@@ -0,0 +1,71 @@
# Key bindings
# ------------
function fzf_key_bindings
# Due to a bug of fish, we cannot use command substitution,
# so we use temporary file instead
if [ -z "$TMPDIR" ]
set -g TMPDIR /tmp
end
function __fzf_escape
while read item
echo -n (echo -n "$item" | sed -E 's/([ "$~'\''([{<>})])/\\\\\\1/g')' '
end
end
function fzf-file-widget
set -q FZF_CTRL_T_COMMAND; or set -l FZF_CTRL_T_COMMAND "
command find -L . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | sed 1d | cut -b3-"
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)" -m $FZF_CTRL_T_OPTS > $TMPDIR/fzf.result"
and for i in (seq 20); commandline -i (cat $TMPDIR/fzf.result | __fzf_escape) 2> /dev/null; and break; sleep 0.1; end
commandline -f repaint
rm -f $TMPDIR/fzf.result
end
function fzf-history-widget
history | eval (__fzfcmd) +s +m --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS > $TMPDIR/fzf.result
and commandline (cat $TMPDIR/fzf.result)
commandline -f repaint
rm -f $TMPDIR/fzf.result
end
function fzf-cd-widget
set -q FZF_ALT_C_COMMAND; or set -l FZF_ALT_C_COMMAND "
command find -L . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \
-o -type d -print 2> /dev/null | sed 1d | cut -b3-"
# Fish hangs if the command before pipe redirects (2> /dev/null)
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)" +m $FZF_ALT_C_OPTS > $TMPDIR/fzf.result"
[ (cat $TMPDIR/fzf.result | wc -l) -gt 0 ]
and cd (cat $TMPDIR/fzf.result)
commandline -f repaint
rm -f $TMPDIR/fzf.result
end
function __fzfcmd
set -q FZF_TMUX; or set FZF_TMUX 1
if [ $FZF_TMUX -eq 1 ]
if set -q FZF_TMUX_HEIGHT
echo "fzf-tmux -d$FZF_TMUX_HEIGHT"
else
echo "fzf-tmux -d40%"
end
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
end

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

@@ -0,0 +1,68 @@
# 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 . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | sed 1d | cut -b3-"}"
setopt localoptions pipefail 2> /dev/null
eval "$cmd | $(__fzfcmd) -m $FZF_CTRL_T_OPTS" | while read item; do
echo -n "${(q)item} "
done
local ret=$?
echo
return $ret
}
__fzfcmd() {
[ ${FZF_TMUX:-1} -eq 1 ] && 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
# ALT-C - cd into the selected directory
fzf-cd-widget() {
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . \\( -path '*/\\.*' -o -fstype 'dev' -o -fstype 'proc' \\) -prune \
-o -type d -print 2> /dev/null | sed 1d | cut -b3-"}"
setopt localoptions pipefail 2> /dev/null
cd "${$(eval "$cmd | $(__fzfcmd) +m $FZF_ALT_C_OPTS"):-.}"
local ret=$?
zle reset-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 pipefail 2> /dev/null
selected=( $(fc -l 1 | eval "$(__fzfcmd) +s --tac +m -n2..,.. --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS -q ${(q)LBUFFER}") )
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

40
src/Dockerfile.android Normal file
View File

@@ -0,0 +1,40 @@
FROM ubuntu:14.04
MAINTAINER Junegunn Choi <junegunn.c@gmail.com>
# apt-get
RUN apt-get update && apt-get -y upgrade && \
apt-get install -y --force-yes git curl build-essential
# Install Go 1.4
RUN cd / && curl \
https://storage.googleapis.com/golang/go1.4.2.linux-amd64.tar.gz | \
tar -xz && mv go go1.4 && \
sed -i 's@#define PTHREAD_KEYS_MAX 128@@' /go1.4/src/runtime/cgo/gcc_android_arm.c
ENV GOROOT /go1.4
ENV PATH /go1.4/bin:$PATH
RUN cd / && \
curl -O http://dl.google.com/android/ndk/android-ndk-r10e-linux-x86_64.bin && \
chmod 755 /android-ndk* && /android-ndk-r10e-linux-x86_64.bin && \
mv android-ndk-r10e /android-ndk
RUN cd /android-ndk && bash ./build/tools/make-standalone-toolchain.sh --platform=android-21 --install-dir=/ndk --arch=arm
ENV NDK_CC /ndk/bin/arm-linux-androideabi-gcc
RUN cd $GOROOT/src && \
CC_FOR_TARGET=$NDK_CC GOOS=android GOARCH=arm GOARM=7 ./make.bash
RUN cd / && curl \
http://ftp.gnu.org/gnu/ncurses/ncurses-5.9.tar.gz | \
tar -xz && cd /ncurses-5.9 && \
./configure CC=$NDK_CC CFLAGS="-fPIE -march=armv7-a -mfpu=neon -mhard-float -Wl,--no-warn-mismatch" LDFLAGS="-march=armv7-a -Wl,--no-warn-mismatch" --host=arm-linux --enable-overwrite --enable-const --without-cxx-binding --without-shared --without-debug --enable-widec --enable-ext-colors --enable-ext-mouse --enable-pc-files --with-pkg-config-libdir=$PKG_CONFIG_LIBDIR --without-manpages --without-ada --disable-shared --without-tests --prefix=/ndk/sysroot/usr --with-default-terminfo-dirs=/usr/share/terminfo --with-terminfo-dirs=/usr/share/terminfo ac_cv_header_locale_h=n ac_cv_func_getpwent=no ac_cv_func_getpwnam=no ac_cv_func_getpwuid=no && \
sed -i 's@#define HAVE_LOCALE_H 1@/* #undef HAVE_LOCALE_H */@' include/ncurses_cfg.h && \
make && \
sed -i '0,/echo.*/{s/echo.*/exit 0/}' misc/run_tic.sh && \
make install && \
mv /ndk/sysroot/usr/lib/libncursesw.a /ndk/sysroot/usr/lib/libncurses.a
# Default CMD
CMD cd /fzf/src && /bin/bash

24
src/Dockerfile.arch Normal file
View File

@@ -0,0 +1,24 @@
FROM base/archlinux:2014.07.03
MAINTAINER Junegunn Choi <junegunn.c@gmail.com>
# apt-get
RUN pacman-key --populate archlinux && pacman-key --refresh-keys
RUN pacman-db-upgrade && pacman -Syu --noconfirm base-devel git
# Install Go 1.4
RUN cd / && curl \
https://storage.googleapis.com/golang/go1.4.2.linux-amd64.tar.gz | \
tar -xz && mv go go1.4
ENV GOROOT /go1.4
ENV PATH /go1.4/bin:$PATH
# For i386 build
RUN echo '[multilib]' >> /etc/pacman.conf && \
echo 'Include = /etc/pacman.d/mirrorlist' >> /etc/pacman.conf && \
pacman-db-upgrade && yes | pacman -Sy gcc-multilib lib32-ncurses && \
cd $GOROOT/src && GOARCH=386 ./make.bash
# Default CMD
CMD cd /fzf/src && /bin/bash

32
src/Dockerfile.centos Normal file
View File

@@ -0,0 +1,32 @@
FROM centos:centos6
MAINTAINER Junegunn Choi <junegunn.c@gmail.com>
# yum
RUN yum install -y git gcc make tar glibc-devel glibc-devel.i686 \
ncurses-devel ncurses-static ncurses-devel.i686 \
gpm-devel gpm-static libgcc.i686
# Install Go 1.4
RUN cd / && curl \
https://storage.googleapis.com/golang/go1.4.2.linux-amd64.tar.gz | \
tar -xz && mv go go1.4
# Install Go 1.7
RUN cd / && curl \
https://storage.googleapis.com/golang/go1.7.linux-amd64.tar.gz | \
tar -xz && mv go go1.7
# Install RPMs for building static 32-bit binary
RUN curl ftp://ftp.pbone.net/mirror/ftp.centos.org/6.8/os/i386/Packages/ncurses-static-5.7-4.20090207.el6.i686.rpm -o rpm && rpm -i rpm && \
curl ftp://ftp.pbone.net/mirror/ftp.centos.org/6.8/os/i386/Packages/gpm-static-1.20.6-12.el6.i686.rpm -o rpm && rpm -i rpm
ENV GOROOT_BOOTSTRAP /go1.4
ENV GOROOT /go1.7
ENV PATH /go1.7/bin:$PATH
# For i386 build
RUN cd $GOROOT/src && GOARCH=386 ./make.bash
# Default CMD
CMD cd /fzf/src && /bin/bash

22
src/Dockerfile.ubuntu Normal file
View File

@@ -0,0 +1,22 @@
FROM ubuntu:14.04
MAINTAINER Junegunn Choi <junegunn.c@gmail.com>
# apt-get
RUN apt-get update && apt-get -y upgrade && \
apt-get install -y --force-yes git curl build-essential libncurses-dev libgpm-dev
# Install Go 1.4
RUN cd / && curl \
https://storage.googleapis.com/golang/go1.4.2.linux-amd64.tar.gz | \
tar -xz && mv go go1.4
ENV GOROOT /go1.4
ENV PATH /go1.4/bin:$PATH
# For i386 build
RUN apt-get install -y lib32ncurses5-dev && \
cd $GOROOT/src && GOARCH=386 ./make.bash
# Default CMD
CMD cd /fzf/src && /bin/bash

21
src/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.

116
src/Makefile Normal file
View File

@@ -0,0 +1,116 @@
ifndef GOOS
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
GOOS := darwin
else ifeq ($(UNAME_S),Linux)
GOOS := linux
endif
endif
SOURCES := $(wildcard *.go */*.go)
ROOTDIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
BINDIR := $(shell dirname $(ROOTDIR))/bin
GOPATH := $(shell dirname $(ROOTDIR))/gopath
SRCDIR := $(GOPATH)/src/github.com/junegunn/fzf/src
DOCKEROPTS := -i -t -v $(ROOTDIR):/fzf/src
BINARY32 := fzf-$(GOOS)_386
BINARY64 := fzf-$(GOOS)_amd64
BINARYARM7 := fzf-$(GOOS)_arm7
VERSION := $(shell awk -F= '/version =/ {print $$2}' constants.go | tr -d "\" ")
RELEASE32 := fzf-$(VERSION)-$(GOOS)_386
RELEASE64 := fzf-$(VERSION)-$(GOOS)_amd64
RELEASEARM7 := fzf-$(VERSION)-$(GOOS)_arm7
export GOPATH
UNAME_M := $(shell uname -m)
ifeq ($(UNAME_M),x86_64)
BINARY := $(BINARY64)
else ifeq ($(UNAME_M),i686)
BINARY := $(BINARY32)
else
$(error "Build on $(UNAME_M) is not supported, yet.")
endif
all: fzf/$(BINARY)
release: test fzf/$(BINARY32) fzf/$(BINARY64)
-cd fzf && cp $(BINARY32) $(RELEASE32) && tar -czf $(RELEASE32).tgz $(RELEASE32)
cd fzf && cp $(BINARY64) $(RELEASE64) && tar -czf $(RELEASE64).tgz $(RELEASE64) && \
rm -f $(RELEASE32) $(RELEASE64)
$(SRCDIR):
mkdir -p $(shell dirname $(SRCDIR))
ln -s $(ROOTDIR) $(SRCDIR)
deps: $(SRCDIR) $(SOURCES)
cd $(SRCDIR) && go get
android-build: $(SRCDIR)
cd $(SRCDIR) && GOARCH=arm GOARM=7 CGO_ENABLED=1 go get
cd $(SRCDIR)/fzf && GOARCH=arm GOARM=7 CGO_ENABLED=1 go build -a -ldflags="-w -extldflags=-pie" -o $(BINARYARM7)
cd $(SRCDIR)/fzf && cp $(BINARYARM7) $(RELEASEARM7) && tar -czf $(RELEASEARM7).tgz $(RELEASEARM7) && \
rm -f $(RELEASEARM7)
test: deps
SHELL=/bin/sh go test -v ./...
install: $(BINDIR)/fzf
uninstall:
rm -f $(BINDIR)/fzf $(BINDIR)/$(BINARY)
clean:
cd fzf && rm -f fzf-*
fzf/$(BINARY32): deps
cd fzf && GOARCH=386 CGO_ENABLED=1 go build -a -ldflags -w -tags "$(TAGS)" -o $(BINARY32)
fzf/$(BINARY64): deps
cd fzf && go build -a -ldflags -w -tags "$(TAGS)" -o $(BINARY64)
$(BINDIR)/fzf: fzf/$(BINARY) | $(BINDIR)
cp -f fzf/$(BINARY) $(BINDIR)
cd $(BINDIR) && ln -sf $(BINARY) fzf
$(BINDIR):
mkdir -p $@
docker-arch:
docker build -t junegunn/arch-sandbox - < Dockerfile.arch
docker-ubuntu:
docker build -t junegunn/ubuntu-sandbox - < Dockerfile.ubuntu
docker-centos:
docker build -t junegunn/centos-sandbox - < Dockerfile.centos
docker-android:
docker build -t junegunn/android-sandbox - < Dockerfile.android
arch: docker-arch
docker run $(DOCKEROPTS) junegunn/$@-sandbox \
sh -c 'cd /fzf/src; /bin/bash'
ubuntu: docker-ubuntu
docker run $(DOCKEROPTS) junegunn/$@-sandbox \
sh -c 'cd /fzf/src; /bin/bash'
centos: docker-centos
docker run $(DOCKEROPTS) junegunn/$@-sandbox \
sh -c 'cd /fzf/src; /bin/bash'
linux: docker-centos
docker run $(DOCKEROPTS) junegunn/centos-sandbox \
/bin/bash -ci 'cd /fzf/src; make TAGS=static release'
ubuntu-android: docker-android
docker run $(DOCKEROPTS) junegunn/android-sandbox \
sh -c 'cd /fzf/src; /bin/bash'
android: docker-android
docker run $(DOCKEROPTS) junegunn/android-sandbox \
/bin/bash -ci 'cd /fzf/src; GOOS=android make android-build'
.PHONY: all deps release test install uninstall clean \
linux arch ubuntu centos docker-arch docker-ubuntu docker-centos \
android-build docker-android ubuntu-android android

100
src/README.md Normal file
View File

@@ -0,0 +1,100 @@
fzf in Go
=========
<img src="https://cloud.githubusercontent.com/assets/700826/5725028/028ea834-9b93-11e4-9198-43088c3f295d.gif" height="463" alt="fzf in go">
This directory contains the source code for the new fzf implementation in
[Go][go].
Upgrade from Ruby version
-------------------------
The install script has been updated to download the right binary for your
system. If you already have installed fzf, simply git-pull the repository and
rerun the install script.
```sh
cd ~/.fzf
git pull
./install
```
Otherwise, follow [the instruction][install] as before. You can also install
fzf using Homebrew if you prefer that way.
Motivations
-----------
### No Ruby dependency
There have always been complaints about fzf being a Ruby script. To make
matters worse, Ruby 2.1 removed ncurses binding from its standard libary.
Because of the change, users running Ruby 2.1 or above are forced to build C
extensions of curses gem to meet the requirement of fzf. The new Go version
will be distributed as an executable binary so it will be much more accessible
and should be easier to setup.
### Performance
Many people have been surprised to see how fast fzf is even when it was
written in Ruby. It stays quite responsive even for 100k+ lines, which is
well above the size of the usual input.
The new Go version, of course, is significantly faster than that. It has all
the performance optimization techniques used in Ruby implementation and more.
It also doesn't suffer from [GIL][gil], so the search performance scales
proportional to the number of CPU cores. On my MacBook Pro (Mid 2012), the new
version was shown to be an order of magnitude faster on certain cases. It also
starts much faster though the difference may not be noticeable.
Build
-----
```sh
# Build fzf executables and tarballs
make release
# Install the executable to ../bin directory
make install
# Build executables and tarballs for Linux using Docker
make linux
```
Test
----
Unit tests can be run with `make test`. Integration tests are written in Ruby
script that should be run on tmux.
```sh
# Unit tests
make test
# Install the executable to ../bin directory
make install
# Integration tests
ruby ../test/test_go.rb
```
Third-party libraries used
--------------------------
- [ncurses][ncurses]
- [mattn/go-runewidth](https://github.com/mattn/go-runewidth)
- Licensed under [MIT](http://mattn.mit-license.org/2013)
- [mattn/go-shellwords](https://github.com/mattn/go-shellwords)
- Licensed under [MIT](http://mattn.mit-license.org/2014)
License
-------
[MIT](LICENSE)
[install]: https://github.com/junegunn/fzf#installation
[go]: https://golang.org/
[gil]: http://en.wikipedia.org/wiki/Global_Interpreter_Lock
[ncurses]: https://www.gnu.org/software/ncurses/
[req]: http://golang.org/doc/install
[termbox]: https://github.com/nsf/termbox-go

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

@@ -0,0 +1,700 @@
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 (
"fmt"
"strings"
"unicode"
"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, clear bool) (int, []int16) {
if slab != nil && cap(slab.I16) > offset+size {
slice := slab.I16[offset : offset+size]
if clear {
for idx := range slice {
slice[idx] = 0
}
}
return offset + size, slice
}
return offset, make([]int16, size)
}
func alloc32(offset int, slab *util.Slab, size int, clear bool) (int, []int32) {
if slab != nil && cap(slab.I32) > offset+size {
slice := slab.I32[offset : offset+size]
if clear {
for idx := range slice {
slice[idx] = 0
}
}
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)))
}
type Algo func(caseSensitive bool, forward bool, input util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int)
func FuzzyMatchV2(caseSensitive 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).
N := input.Length()
M := len(pattern)
switch M {
case 0:
return Result{0, 0, 0}, posArray(withPos, M)
case 1:
return ExactMatchNaive(caseSensitive, forward, input, pattern[0:1], withPos, slab)
}
// 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, forward, input, pattern, withPos, slab)
}
// Reuse pre-allocated integer slice to avoid unnecessary sweeping of garbages
offset16 := 0
offset32 := 0
// Bonus point for each position
offset16, B := alloc16(offset16, slab, N, false)
// The first occurrence of each character in the pattern
offset32, F := alloc32(offset32, slab, M, false)
// Rune array
offset32, T := alloc32(offset32, slab, N, false)
// Phase 1. Check if there's a match and calculate bonus for each point
pidx, lastIdx, prevClass := 0, 0, charNonWord
for idx := 0; idx < N; idx++ {
char := input.Get(idx)
var class charClass
if char <= unicode.MaxASCII {
class = charClassOfAscii(char)
} else {
class = charClassOfNonAscii(char)
}
if !caseSensitive && class == charUpper {
if char <= unicode.MaxASCII {
char += 32
} else {
char = unicode.To(unicode.LowerCase, char)
}
}
T[idx] = char
B[idx] = bonusFor(prevClass, class)
prevClass = class
if pidx < M {
if char == pattern[pidx] {
lastIdx = idx
F[pidx] = int32(idx)
pidx++
}
} else {
if char == pattern[M-1] {
lastIdx = idx
}
}
}
if pidx != M {
return Result{-1, -1, 0}, nil
}
// Phase 2. Fill in score matrix (H)
// Unlike the original algorithm, we do not allow omission.
width := lastIdx - int(F[0]) + 1
offset16, H := alloc16(offset16, slab, width*M, false)
// Possible length of consecutive chunk at each position.
offset16, C := alloc16(offset16, slab, width*M, false)
maxScore, maxScorePos := int16(0), 0
for i := 0; i < M; i++ {
I := i * width
inGap := false
for j := int(F[i]); j <= lastIdx; j++ {
j0 := j - int(F[0])
var s1, s2, consecutive int16
if j > int(F[i]) {
if inGap {
s2 = H[I+j0-1] + scoreGapExtention
} else {
s2 = H[I+j0-1] + scoreGapStart
}
}
if pattern[i] == T[j] {
var diag int16
if i > 0 && j0 > 0 {
diag = H[I-width+j0-1]
}
s1 = diag + scoreMatch
b := B[j]
if i > 0 {
// j > 0 if i > 0
consecutive = C[I-width+j0-1] + 1
// Break consecutive chunk
if b == bonusBoundary {
consecutive = 1
} else if consecutive > 1 {
b = util.Max16(b, util.Max16(bonusConsecutive, B[j-int(consecutive)+1]))
}
} else {
consecutive = 1
b *= bonusFirstCharMultiplier
}
if s1+b < s2 {
s1 += B[j]
consecutive = 0
} else {
s1 += b
}
}
C[I+j0] = consecutive
inGap = s1 < s2
score := util.Max16(util.Max16(s1, s2), 0)
if i == M-1 && (forward && score > maxScore || !forward && score >= maxScore) {
maxScore, maxScorePos = score, j
}
H[I+j0] = score
}
if DEBUG {
if i == 0 {
fmt.Print(" ")
for j := int(F[i]); j <= lastIdx; j++ {
fmt.Printf(" " + string(input.Get(j)) + " ")
}
fmt.Println()
}
fmt.Print(string(pattern[i]) + " ")
for idx := int(F[0]); idx < int(F[i]); idx++ {
fmt.Print(" 0 ")
}
for idx := int(F[i]); 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
}
fmt.Printf("%2d ", p)
}
fmt.Println()
}
}
// Phase 3. (Optional) Backtrace to find character positions
pos := posArray(withPos, M)
j := int(F[0])
if withPos {
i := M - 1
j = maxScorePos
preferMatch := true
for {
I := i * width
j0 := j - int(F[0])
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, 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)
}
}
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, forward bool, text util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
if len(pattern) == 0 {
return Result{0, 0, 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)
}
}
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, 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, 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
}
// 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)
}
}
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, 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, 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 char != r {
return Result{-1, -1, 0}, nil
}
}
lenPattern := len(pattern)
score, _ := calculateScore(caseSensitive, text, pattern, 0, lenPattern, false)
return Result{0, lenPattern, score}, nil
}
// SuffixMatch performs suffix-match
func SuffixMatch(caseSensitive 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 char != r {
return Result{-1, -1, 0}, nil
}
}
lenPattern := len(pattern)
sidx := trimmedLen - lenPattern
eidx := trimmedLen
score, _ := calculateScore(caseSensitive, text, pattern, sidx, eidx, false)
return Result{sidx, eidx, score}, nil
}
// EqualMatch performs equal-match
func EqualMatch(caseSensitive 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
}
runesStr := text.ToString()
if !caseSensitive {
runesStr = strings.ToLower(runesStr)
}
if runesStr == string(pattern) {
return Result{0, lenPattern, (scoreMatch+bonusBoundary)*lenPattern +
(bonusFirstCharMultiplier-1)*bonusBoundary}, nil
}
return Result{-1, -1, 0}, nil
}

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

@@ -0,0 +1,166 @@
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) {
if !caseSensitive {
pattern = strings.ToLower(pattern)
}
res, pos := fun(caseSensitive, forward, util.RunesToChars([]rune(input)), []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 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)
}

173
src/ansi.go Normal file
View File

@@ -0,0 +1,173 @@
package fzf
import (
"bytes"
"regexp"
"strconv"
"strings"
"unicode/utf8"
"github.com/junegunn/fzf/src/curses"
)
type ansiOffset struct {
offset [2]int32
color ansiState
}
type ansiState struct {
fg int
bg int
attr curses.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() {
ansiRegex = regexp.MustCompile("\x1b.[0-9;]*.")
}
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})
}
idx := 0
for _, offset := range ansiRegex.FindAllStringIndex(str, -1) {
prev := str[idx:offset[0]]
output.WriteString(prev)
if proc != nil && !proc(prev, state) {
return "", nil, nil
}
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(utf8.RuneCount(output.Bytes()))
}
if newState.colored() {
// Append new offset
state = newState
newLen := int32(utf8.RuneCount(output.Bytes()))
offsets = append(offsets, ansiOffset{[2]int32{newLen, newLen}, *state})
} else {
// Discard state
state = nil
}
}
idx = offset[1]
}
rest := str[idx:]
if len(rest) > 0 {
output.WriteString(rest)
if state != nil {
// Update last offset
(&offsets[len(offsets)-1]).offset[1] = int32(utf8.RuneCount(output.Bytes()))
}
}
if proc != nil {
proc(rest, state)
}
if len(offsets) == 0 {
return output.String(), nil, state
}
return output.String(), &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[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 = curses.Bold
case 2:
state.attr = curses.Dim
case 4:
state.attr = curses.Underline
case 5:
state.attr = curses.Blink
case 7:
state.attr = curses.Reverse
case 0:
init()
default:
if num >= 30 && num <= 37 {
state.fg = num - 30
} else if num >= 40 && num <= 47 {
state.bg = num - 40
} else if num >= 90 && num <= 97 {
state.fg = num - 90 + 8
} else if num >= 100 && num <= 107 {
state.bg = num - 100 + 8
}
}
case 1:
switch num {
case 5:
state256++
default:
state256 = 0
}
case 2:
*ptr = num
state256 = 0
}
}
}
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/curses"
)
func TestExtractColor(t *testing.T) {
assert := func(offset ansiOffset, b int32, e int32, fg int, bg int, bold bool) {
var attr curses.Attr
if bold {
attr = curses.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: {}", 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[mworld"
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)
})
}

53
src/cache.go Normal file
View File

@@ -0,0 +1,53 @@
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
}
// Find is called to lookup ChunkCache
func (cc *ChunkCache) Find(chunk *Chunk, key string) ([]*Result, bool) {
if len(key) == 0 || !chunk.IsFull() {
return nil, false
}
cc.mutex.Lock()
defer cc.mutex.Unlock()
qc, ok := cc.cache[chunk]
if ok {
list, ok := (*qc)[key]
if ok {
return list, true
}
}
return nil, false
}

40
src/cache_test.go Normal file
View File

@@ -0,0 +1,40 @@
package fzf
import "testing"
func TestChunkCache(t *testing.T) {
cache := NewChunkCache()
chunk2 := make(Chunk, chunkSize)
chunk1p := &Chunk{}
chunk2p := &chunk2
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, found := cache.Find(chunk1p, "foo")
if found {
t.Error("Cached disabled for non-empty chunks", found, cached)
}
}
{
cached, found := cache.Find(chunk2p, "foo")
if !found || len(cached) != 1 {
t.Error("Expected 1 item cached", found, cached)
}
}
{
cached, found := cache.Find(chunk2p, "bar")
if !found || len(cached) != 2 {
t.Error("Expected 2 items cached", found, cached)
}
}
{
cached, found := cache.Find(chunk1p, "foobar")
if found {
t.Error("Expected 0 item cached", found, cached)
}
}
}

93
src/chunklist.go Normal file
View File

@@ -0,0 +1,93 @@
package fzf
import "sync"
// Chunk is a list of Item pointers whose size has the upper limit of chunkSize
type Chunk []*Item // >>> []Item
// ItemBuilder is a closure type that builds Item object from a pointer to a
// string and an integer
type ItemBuilder func([]byte, int) *Item
// ChunkList is a list of Chunks
type ChunkList struct {
chunks []*Chunk
count int
mutex sync.Mutex
trans ItemBuilder
}
// NewChunkList returns a new ChunkList
func NewChunkList(trans ItemBuilder) *ChunkList {
return &ChunkList{
chunks: []*Chunk{},
count: 0,
mutex: sync.Mutex{},
trans: trans}
}
func (c *Chunk) push(trans ItemBuilder, data []byte, index int) bool {
item := trans(data, index)
if item != nil {
*c = append(*c, item)
return true
}
return false
}
// IsFull returns true if the Chunk is full
func (c *Chunk) IsFull() bool {
return len(*c) == 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) + len(*(cs[len(cs)-1]))
}
// Push adds the item to the list
func (cl *ChunkList) Push(data []byte) bool {
cl.mutex.Lock()
defer cl.mutex.Unlock()
if len(cl.chunks) == 0 || cl.lastChunk().IsFull() {
newChunk := Chunk(make([]*Item, 0, chunkSize))
cl.chunks = append(cl.chunks, &newChunk)
}
if cl.lastChunk().push(cl.trans, data, cl.count) {
cl.count++
return true
}
return false
}
// Snapshot returns immutable snapshot of the ChunkList
func (cl *ChunkList) Snapshot() ([]*Chunk, int) {
cl.mutex.Lock()
defer cl.mutex.Unlock()
ret := make([]*Chunk, len(cl.chunks))
copy(ret, cl.chunks)
// Duplicate the last chunk
if cnt := len(ret); cnt > 0 {
ret[cnt-1] = ret[cnt-1].dupe()
}
return ret, cl.count
}
func (c *Chunk) dupe() *Chunk {
newChunk := make(Chunk, len(*c))
for idx, ptr := range *c {
newChunk[idx] = ptr
}
return &newChunk
}

79
src/chunklist_test.go Normal file
View File

@@ -0,0 +1,79 @@
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(s []byte, i int) *Item {
return &Item{text: util.ToChars(s), index: int32(i * 2)}
})
// 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 len(*chunk1) != 2 {
t.Error("Snapshot should contain only two items")
}
if (*chunk1)[0].text.ToString() != "hello" || (*chunk1)[0].index != 0 ||
(*chunk1)[1].text.ToString() != "world" || (*chunk1)[1].index != 2 {
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 len(*snapshot[2]) != 2 {
t.Error("Unexpected number of items")
}
cl.Push([]byte("hello"))
cl.Push([]byte("world"))
lastChunkCount := len(*snapshot[len(snapshot)-1])
if lastChunkCount != 2 {
t.Error("Unexpected number of items:", lastChunkCount)
}
}

68
src/constants.go Normal file
View File

@@ -0,0 +1,68 @@
package fzf
import (
"time"
"github.com/junegunn/fzf/src/util"
)
const (
// Current version
version = "0.15.3"
// Core
coordinatorDelayMax time.Duration = 100 * time.Millisecond
coordinatorDelayStep time.Duration = 10 * time.Millisecond
// Reader
defaultCommand = `find . -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | sed s/^..//`
readerBufferSize = 64 * 1024
// Terminal
initialDelay = 20 * time.Millisecond
initialDelayTac = 100 * time.Millisecond
spinnerDuration = 200 * time.Millisecond
maxPatternLength = 100
// 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`~;:,<.>/?'\"!@#$%^&*()[{]}-_=+"
)
// fzf events
const (
EvtReadNew util.EventType = iota
EvtReadFin
EvtSearchNew
EvtSearchProgress
EvtSearchFin
EvtHeader
EvtClose
)
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) 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.
*/
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) {
sort := opts.Sort > 0
sortCriteria = opts.Criteria
if opts.Version {
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
}
ansiProcessorRunes := func(data []rune) (util.Chars, *[]ansiOffset) {
return util.RunesToChars(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.RunesToChars([]rune(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.RunesToChars([]rune(trimmed)), nil
}
}
ansiProcessorRunes = func(data []rune) (util.Chars, *[]ansiOffset) {
return ansiProcessor([]byte(string(data)))
}
}
// Chunk list
var chunkList *ChunkList
header := make([]string, 0, opts.HeaderLines)
if len(opts.WithNth) == 0 {
chunkList = NewChunkList(func(data []byte, index int) *Item {
if len(header) < opts.HeaderLines {
header = append(header, string(data))
eventBox.Set(EvtHeader, header)
return nil
}
chars, colors := ansiProcessor(data)
return &Item{
index: int32(index),
text: chars,
colors: colors}
})
} else {
chunkList = NewChunkList(func(data []byte, index int) *Item {
tokens := Tokenize(util.ToChars(data), opts.Delimiter)
trans := Transform(tokens, opts.WithNth)
if len(header) < opts.HeaderLines {
header = append(header, string(joinTokens(trans)))
eventBox.Set(EvtHeader, header)
return nil
}
textRunes := joinTokens(trans)
item := Item{
index: int32(index),
origText: &data,
colors: nil}
trimmed, colors := ansiProcessorRunes(textRunes)
item.text = trimmed
item.colors = colors
return &item
})
}
// Reader
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
if !streamingFilter {
reader := Reader{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, 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 := Reader{
func(runes []byte) bool {
item := chunkList.trans(runes, 0)
if item != nil {
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) {
defer events.Clear()
for evt, value := range *events {
switch evt {
case EvtReadNew, EvtReadFin:
reading = reading && evt == EvtReadNew
snapshot, count := chunkList.Snapshot()
terminal.UpdateCount(count, !reading)
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)
}
}
}
})
if delay && reading {
dur := util.DurWithin(
time.Duration(ticks)*coordinatorDelayStep,
0, coordinatorDelayMax)
time.Sleep(dur)
}
}
}

711
src/curses/curses.go Normal file
View File

@@ -0,0 +1,711 @@
package curses
/*
#include <ncurses.h>
#include <locale.h>
#cgo !static LDFLAGS: -lncurses
#cgo static LDFLAGS: -l:libncursesw.a -l:libtinfo.a -l:libgpm.a -ldl
#cgo android static LDFLAGS: -l:libncurses.a -fPIE -march=armv7-a -mfpu=neon -mhard-float -Wl,--no-warn-mismatch
SCREEN *c_newterm () {
return newterm(NULL, stderr, stdin);
}
*/
import "C"
import (
"fmt"
"os"
"strings"
"syscall"
"time"
"unicode/utf8"
)
const (
Bold = C.A_BOLD
Dim = C.A_DIM
Blink = C.A_BLINK
Reverse = C.A_REVERSE
Underline = C.A_UNDERLINE
)
type Attr C.int
// 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
Invalid
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
AltEnter
AltSpace
AltSlash
AltBS
AltA
AltB
AltC
AltD
AltE
AltF
AltZ = AltA + 'z' - 'a'
)
// Pallete
const (
ColNormal = iota
ColPrompt
ColMatch
ColCurrent
ColCurrentMatch
ColSpinner
ColInfo
ColCursor
ColSelected
ColHeader
ColBorder
ColUser // Should be the last entry
)
const (
doubleClickDuration = 500 * time.Millisecond
colDefault = -1
colUndefined = -2
)
type ColorTheme struct {
UseDefault bool
Fg int16
Bg int16
DarkBg int16
Prompt int16
Match int16
Current int16
CurrentMatch int16
Spinner int16
Info int16
Cursor int16
Selected int16
Header int16
Border int16
}
type Event struct {
Type int
Char rune
MouseEvent *MouseEvent
}
type MouseEvent struct {
Y int
X int
S int
Down bool
Double bool
Mod bool
}
var (
_buf []byte
_in *os.File
_color func(int, Attr) C.int
_colorMap map[int]int
_prevDownTime time.Time
_clickY []int
_screen *C.SCREEN
Default16 *ColorTheme
Dark256 *ColorTheme
Light256 *ColorTheme
FG int
CurrentFG int
BG int
DarkBG int
)
type Window struct {
win *C.WINDOW
Top int
Left int
Width int
Height int
}
func NewWindow(top int, left int, width int, height int, border bool) *Window {
win := C.newwin(C.int(height), C.int(width), C.int(top), C.int(left))
if border {
attr := _color(ColBorder, 0)
C.wattron(win, attr)
C.box(win, 0, 0)
C.wattroff(win, attr)
}
return &Window{
win: win,
Top: top,
Left: left,
Width: width,
Height: height,
}
}
func EmptyTheme() *ColorTheme {
return &ColorTheme{
UseDefault: true,
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 init() {
_prevDownTime = time.Unix(0, 0)
_clickY = []int{}
_colorMap = make(map[int]int)
Default16 = &ColorTheme{
UseDefault: true,
Fg: 15,
Bg: 0,
DarkBg: C.COLOR_BLACK,
Prompt: C.COLOR_BLUE,
Match: C.COLOR_GREEN,
Current: C.COLOR_YELLOW,
CurrentMatch: C.COLOR_GREEN,
Spinner: C.COLOR_GREEN,
Info: C.COLOR_WHITE,
Cursor: C.COLOR_RED,
Selected: C.COLOR_MAGENTA,
Header: C.COLOR_CYAN,
Border: C.COLOR_BLACK}
Dark256 = &ColorTheme{
UseDefault: true,
Fg: 15,
Bg: 0,
DarkBg: 236,
Prompt: 110,
Match: 108,
Current: 254,
CurrentMatch: 151,
Spinner: 148,
Info: 144,
Cursor: 161,
Selected: 168,
Header: 109,
Border: 59}
Light256 = &ColorTheme{
UseDefault: true,
Fg: 15,
Bg: 0,
DarkBg: 251,
Prompt: 25,
Match: 66,
Current: 237,
CurrentMatch: 23,
Spinner: 65,
Info: 101,
Cursor: 161,
Selected: 168,
Header: 31,
Border: 145}
}
func attrColored(pair int, a Attr) C.int {
var attr C.int
if pair > ColNormal {
attr = C.COLOR_PAIR(C.int(pair))
}
return attr | C.int(a)
}
func attrMono(pair int, a Attr) C.int {
var attr C.int
switch pair {
case ColCurrent:
if a&C.A_BOLD == C.A_BOLD {
attr = C.A_REVERSE
}
case ColMatch:
attr = C.A_UNDERLINE
case ColCurrentMatch:
attr = C.A_UNDERLINE | C.A_REVERSE
}
if a&C.A_BOLD == C.A_BOLD {
attr = attr | C.A_BOLD
}
return attr
}
func MaxX() int {
return int(C.COLS)
}
func MaxY() int {
return int(C.LINES)
}
func getch(nonblock bool) int {
b := make([]byte, 1)
syscall.SetNonblock(int(_in.Fd()), nonblock)
_, err := _in.Read(b)
if err != nil {
return -1
}
return int(b[0])
}
func Init(theme *ColorTheme, black bool, mouse bool) {
{
in, err := os.OpenFile("/dev/tty", syscall.O_RDONLY, 0)
if err != nil {
panic("Failed to open /dev/tty")
}
_in = in
// Break STDIN
// syscall.Dup2(int(in.Fd()), int(os.Stdin.Fd()))
}
C.setlocale(C.LC_ALL, C.CString(""))
_screen = C.c_newterm()
if _screen == nil {
fmt.Println("Invalid $TERM: " + os.Getenv("TERM"))
os.Exit(2)
}
C.set_term(_screen)
if mouse {
C.mousemask(C.ALL_MOUSE_EVENTS, nil)
}
C.noecho()
C.raw() // stty dsusp undef
if theme != nil {
C.start_color()
var baseTheme *ColorTheme
if C.tigetnum(C.CString("colors")) >= 256 {
baseTheme = Dark256
} else {
baseTheme = Default16
}
initPairs(baseTheme, theme, black)
_color = attrColored
} else {
_color = attrMono
}
}
func override(a int16, b int16) C.short {
if b == colUndefined {
return C.short(a)
}
return C.short(b)
}
func initPairs(baseTheme *ColorTheme, theme *ColorTheme, black bool) {
fg := override(baseTheme.Fg, theme.Fg)
bg := override(baseTheme.Bg, theme.Bg)
if black {
bg = C.COLOR_BLACK
} else if theme.UseDefault {
fg = colDefault
bg = colDefault
C.use_default_colors()
}
if theme.UseDefault {
FG = colDefault
BG = colDefault
} else {
FG = int(fg)
BG = int(bg)
C.assume_default_colors(C.int(override(baseTheme.Fg, theme.Fg)), C.int(bg))
}
currentFG := override(baseTheme.Current, theme.Current)
darkBG := override(baseTheme.DarkBg, theme.DarkBg)
CurrentFG = int(currentFG)
DarkBG = int(darkBG)
C.init_pair(ColPrompt, override(baseTheme.Prompt, theme.Prompt), bg)
C.init_pair(ColMatch, override(baseTheme.Match, theme.Match), bg)
C.init_pair(ColCurrent, currentFG, darkBG)
C.init_pair(ColCurrentMatch, override(baseTheme.CurrentMatch, theme.CurrentMatch), darkBG)
C.init_pair(ColSpinner, override(baseTheme.Spinner, theme.Spinner), bg)
C.init_pair(ColInfo, override(baseTheme.Info, theme.Info), bg)
C.init_pair(ColCursor, override(baseTheme.Cursor, theme.Cursor), darkBG)
C.init_pair(ColSelected, override(baseTheme.Selected, theme.Selected), darkBG)
C.init_pair(ColHeader, override(baseTheme.Header, theme.Header), bg)
C.init_pair(ColBorder, override(baseTheme.Border, theme.Border), bg)
}
func Close() {
C.endwin()
C.delscreen(_screen)
}
func GetBytes() []byte {
c := getch(false)
_buf = append(_buf, byte(c))
for {
c = getch(true)
if c == -1 {
break
}
_buf = append(_buf, byte(c))
}
return _buf
}
// 27 (91 79) 77 type x y
func mouseSequence(sz *int) Event {
if len(_buf) < 6 {
return Event{Invalid, 0, nil}
}
*sz = 6
switch _buf[3] {
case 32, 36, 40, 48, // mouse-down / shift / cmd / ctrl
35, 39, 43, 51: // mouse-up / shift / cmd / ctrl
mod := _buf[3] >= 36
down := _buf[3]%2 == 0
x := int(_buf[4] - 33)
y := int(_buf[5] - 33)
double := false
if down {
now := time.Now()
if now.Sub(_prevDownTime) < doubleClickDuration {
_clickY = append(_clickY, y)
} else {
_clickY = []int{y}
}
_prevDownTime = now
} else {
if len(_clickY) > 1 && _clickY[0] == _clickY[1] &&
time.Now().Sub(_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 := _buf[3] >= 100
s := 1 - int(_buf[3]%2)*2
x := int(_buf[4] - 33)
y := int(_buf[5] - 33)
return Event{Mouse, 0, &MouseEvent{y, x, s, false, false, mod}}
}
return Event{Invalid, 0, nil}
}
func escSequence(sz *int) Event {
if len(_buf) < 2 {
return Event{ESC, 0, nil}
}
*sz = 2
switch _buf[1] {
case 13:
return Event{AltEnter, 0, nil}
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(_buf) < 3 {
return Event{Invalid, 0, nil}
}
*sz = 3
switch _buf[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 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(_buf) < 4 {
return Event{Invalid, 0, nil}
}
*sz = 4
switch _buf[2] {
case 50:
if len(_buf) == 5 && _buf[4] == 126 {
*sz = 5
switch _buf[3] {
case 48:
return Event{F9, 0, nil}
case 49:
return Event{F10, 0, nil}
}
}
// Bracketed paste mode \e[200~ / \e[201
if _buf[3] == 48 && (_buf[4] == 48 || _buf[4] == 49) && _buf[5] == 126 {
*sz = 6
return Event{Invalid, 0, nil}
}
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 _buf[3] {
case 126:
return Event{Home, 0, nil}
case 53, 55, 56, 57:
if len(_buf) == 5 && _buf[4] == 126 {
*sz = 5
switch _buf[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(_buf) != 6 {
return Event{Invalid, 0, nil}
}
*sz = 6
switch _buf[4] {
case 50:
switch _buf[5] {
case 68:
return Event{Home, 0, nil}
case 67:
return Event{End, 0, nil}
}
case 53:
switch _buf[5] {
case 68:
return Event{SLeft, 0, nil}
case 67:
return Event{SRight, 0, nil}
}
} // _buf[4]
} // _buf[3]
} // _buf[2]
} // _buf[2]
} // _buf[1]
if _buf[1] >= 'a' && _buf[1] <= 'z' {
return Event{AltA + int(_buf[1]) - 'a', 0, nil}
}
return Event{Invalid, 0, nil}
}
func GetChar() Event {
if len(_buf) == 0 {
_buf = GetBytes()
}
if len(_buf) == 0 {
panic("Empty _buffer")
}
sz := 1
defer func() {
_buf = _buf[sz:]
}()
switch _buf[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 ESC:
return escSequence(&sz)
}
// CTRL-A ~ CTRL-Z
if _buf[0] <= CtrlZ {
return Event{int(_buf[0]), 0, nil}
}
r, rsz := utf8.DecodeRune(_buf)
if r == utf8.RuneError {
return Event{ESC, 0, nil}
}
sz = rsz
return Event{Rune, r, nil}
}
func (w *Window) Close() {
C.delwin(w.win)
}
func (w *Window) Enclose(y int, x int) bool {
return bool(C.wenclose(w.win, C.int(y), C.int(x)))
}
func (w *Window) Move(y int, x int) {
C.wmove(w.win, C.int(y), C.int(x))
}
func (w *Window) MoveAndClear(y int, x int) {
w.Move(y, x)
C.wclrtoeol(w.win)
}
func (w *Window) Print(text string) {
C.waddstr(w.win, C.CString(strings.Map(func(r rune) rune {
if r < 32 {
return -1
}
return r
}, text)))
}
func (w *Window) CPrint(pair int, a Attr, text string) {
attr := _color(pair, a)
C.wattron(w.win, attr)
w.Print(text)
C.wattroff(w.win, attr)
}
func Clear() {
C.clear()
}
func Endwin() {
C.endwin()
}
func Refresh() {
C.refresh()
}
func (w *Window) Erase() {
C.werase(w.win)
}
func (w *Window) Fill(str string) bool {
return C.waddstr(w.win, C.CString(str)) == C.OK
}
func (w *Window) CFill(str string, fg int, bg int, a Attr) bool {
attr := _color(PairFor(fg, bg), a)
C.wattron(w.win, attr)
ret := w.Fill(str)
C.wattroff(w.win, attr)
return ret
}
func (w *Window) Refresh() {
C.wnoutrefresh(w.win)
}
func DoUpdate() {
C.doupdate()
}
func PairFor(fg int, bg int) int {
key := (fg << 8) + bg
if found, prs := _colorMap[key]; prs {
return found
}
id := len(_colorMap) + ColUser
C.init_pair(C.short(id), C.short(fg), C.short(bg))
_colorMap[key] = id
return id
}

14
src/curses/curses_test.go Normal file
View File

@@ -0,0 +1,14 @@
package curses
import (
"testing"
)
func TestPairFor(t *testing.T) {
if PairFor(30, 50) != PairFor(30, 50) {
t.Fail()
}
if PairFor(-1, 10) != PairFor(-1, 10) {
t.Fail()
}
}

7
src/fzf/main.go Normal file
View File

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

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()
}

59
src/history_test.go Normal file
View File

@@ -0,0 +1,59 @@
package fzf
import (
"os/user"
"testing"
)
func TestHistory(t *testing.T) {
maxHistory := 50
// Invalid arguments
user, _ := user.Current()
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)
}
}
{ // Append lines
h, _ := NewHistory("/tmp/fzf-history", maxHistory)
for i := 0; i < maxHistory+10; i++ {
h.append("foobar")
}
}
{ // Read lines
h, _ := NewHistory("/tmp/fzf-history", 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("/tmp/fzf-history", maxHistory)
h.append("barfoo")
h.append("")
h.append("foobarbaz")
}
{ // Read lines again
h, _ := NewHistory("/tmp/fzf-history", 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")
}
}

39
src/item.go Normal file
View File

@@ -0,0 +1,39 @@
package fzf
import (
"github.com/junegunn/fzf/src/util"
)
// Item represents each input line
type Item struct {
index int32
text util.Chars
origText *[]byte
colors *[]ansiOffset
transformed []Token
}
// Index returns ordinal index of the Item
func (item *Item) Index() int32 {
return item.index
}
// 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 += len(*chunk)
}
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)[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].rank
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())
return &Result{
item: &Item{text: util.RunesToChars([]rune(str))},
rank: rank{index: rand.Int31()}}
}
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))
}
}
}

1121
src/options.go Normal file

File diff suppressed because it is too large Load Diff

406
src/options_test.go Normal file
View File

@@ -0,0 +1,406 @@
package fzf
import (
"fmt"
"testing"
"github.com/junegunn/fzf/src/curses"
"github.com/junegunn/fzf/src/util"
)
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(util.RunesToChars([]rune("-*--*---**---")), 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(util.RunesToChars([]rune("-*--*---**---")), 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,ALT-enter,alt-SPACE", "")
check := func(i int, s string) {
if pairs[i] != s {
t.Errorf("%s != %s", pairs[i], s)
}
}
if len(pairs) != 11 {
t.Error(11)
}
check(curses.CtrlZ, "ctrl-z")
check(curses.AltZ, "alt-z")
check(curses.F2, "f2")
check(curses.AltZ+'@', "@")
check(curses.AltA, "Alt-a")
check(curses.AltZ+'!', "!")
check(curses.CtrlA+'g'-'a', "ctrl-G")
check(curses.AltZ+'J', "J")
check(curses.AltZ+'g', "g")
check(curses.AltEnter, "ALT-enter")
check(curses.AltSpace, "alt-SPACE")
// Synonyms
pairs = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
if len(pairs) != 9 {
t.Error(9)
}
check(curses.CtrlM, "Return")
check(curses.AltZ+' ', "space")
check(curses.Tab, "tab")
check(curses.BTab, "btab")
check(curses.ESC, "esc")
check(curses.Up, "up")
check(curses.Down, "down")
check(curses.Left, "left")
check(curses.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(curses.Tab, "Ctrl-I")
check(curses.PgUp, "page-up")
check(curses.PgDn, "Page-Down")
check(curses.Home, "Home")
check(curses.End, "End")
check(curses.AltBS, "Alt-BSpace")
check(curses.SLeft, "shift-left")
check(curses.SRight, "shift-right")
check(curses.BTab, "shift-tab")
check(curses.CtrlM, "Enter")
check(curses.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, curses.AltZ+',', ",")
pairs = parseKeyChords(",,a,b", "")
checkN(len(pairs), 3)
check(pairs, curses.AltZ+'a', "a")
check(pairs, curses.AltZ+'b', "b")
check(pairs, curses.AltZ+',', ",")
pairs = parseKeyChords("a,b,,", "")
checkN(len(pairs), 3)
check(pairs, curses.AltZ+'a', "a")
check(pairs, curses.AltZ+'b', "b")
check(pairs, curses.AltZ+',', ",")
pairs = parseKeyChords("a,,,b", "")
checkN(len(pairs), 3)
check(pairs, curses.AltZ+'a', "a")
check(pairs, curses.AltZ+'b', "b")
check(pairs, curses.AltZ+',', ",")
pairs = parseKeyChords("a,,,b,c", "")
checkN(len(pairs), 4)
check(pairs, curses.AltZ+'a', "a")
check(pairs, curses.AltZ+'b', "b")
check(pairs, curses.AltZ+'c', "c")
check(pairs, curses.AltZ+',', ",")
pairs = parseKeyChords(",,,", "")
checkN(len(pairs), 1)
check(pairs, curses.AltZ+',', ",")
}
func TestBind(t *testing.T) {
check := func(action actionType, expected actionType) {
if action != expected {
t.Errorf("%d != %d", action, expected)
}
}
checkString := func(action string, expected string) {
if action != expected {
t.Errorf("%d != %d", action, expected)
}
}
keymap := defaultKeymap()
execmap := make(map[int]string)
check(actBeginningOfLine, keymap[curses.CtrlA])
parseKeymap(keymap, execmap,
"ctrl-a:kill-line,ctrl-b:toggle-sort,c:page-up,alt-z:page-down,"+
"f1:execute(ls {}),f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
"alt-a:execute@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};"+
",,:abort,::accept,X:execute:\nfoobar,Y:execute(baz)")
check(actKillLine, keymap[curses.CtrlA])
check(actToggleSort, keymap[curses.CtrlB])
check(actPageUp, keymap[curses.AltZ+'c'])
check(actAbort, keymap[curses.AltZ+','])
check(actAccept, keymap[curses.AltZ+':'])
check(actPageDown, keymap[curses.AltZ])
check(actExecute, keymap[curses.F1])
check(actExecute, keymap[curses.F2])
check(actExecute, keymap[curses.F3])
check(actExecute, keymap[curses.F4])
checkString("ls {}", execmap[curses.F1])
checkString("echo {}, {}, {}", execmap[curses.F2])
checkString("echo '({})'", execmap[curses.F3])
checkString("less {}", execmap[curses.F4])
checkString("echo (,),[,],/,:,;,%,{}", execmap[curses.AltA])
checkString("echo (,),[,],/,:,@,%,{}", execmap[curses.AltB])
checkString("\nfoobar,Y:execute(baz)", execmap[curses.AltZ+'X'])
for idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} {
parseKeymap(keymap, execmap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char))
checkString("foobar", execmap[curses.AltZ+int([]rune(fmt.Sprintf("%d", idx%10))[0])])
}
parseKeymap(keymap, execmap, "f1:abort")
check(actAbort, keymap[curses.F1])
}
func TestColorSpec(t *testing.T) {
theme := curses.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 != *curses.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 *curses.Dark256 == *customized {
t.Errorf("colors should not be equivalent")
}
customized.Fg = curses.Dark256.Fg
customized.Bg = curses.Dark256.Bg
if *curses.Dark256 == *customized {
t.Errorf("colors should now be equivalent")
}
customized = parseTheme(theme, "fg:231,dark,bg:232")
if customized.Fg != curses.Dark256.Fg || customized.Bg == curses.Dark256.Bg {
t.Errorf("color not customized")
}
if customized.UseDefault {
t.Errorf("not using default colors")
}
if !curses.Dark256.UseDefault {
t.Errorf("using default colors")
}
}
func TestParseNilTheme(t *testing.T) {
var theme *curses.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] != expected {
t.Error()
}
}
check([]string{}, curses.CtrlN, actDown)
check([]string{}, curses.CtrlP, actUp)
check([]string{"--bind=ctrl-n:accept"}, curses.CtrlN, actAccept)
check([]string{"--bind=ctrl-p:accept"}, curses.CtrlP, actAccept)
hist := "--history=/tmp/fzf-history"
check([]string{hist}, curses.CtrlN, actNextHistory)
check([]string{hist}, curses.CtrlP, actPreviousHistory)
check([]string{hist, "--bind=ctrl-n:accept"}, curses.CtrlN, actAccept)
check([]string{hist, "--bind=ctrl-n:accept"}, curses.CtrlP, actPreviousHistory)
check([]string{hist, "--bind=ctrl-p:accept"}, curses.CtrlN, actNextHistory)
check([]string{hist, "--bind=ctrl-p:accept"}, curses.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.position == posRight &&
opts.Preview.size.percent == true &&
opts.Preview.size.size == 50) {
t.Error()
}
opts = optsFor("--preview", "cat {}", "--preview-window=left:15:hidden")
if !(opts.Preview.command == "cat {}" &&
opts.Preview.hidden == true &&
opts.Preview.position == posLeft &&
opts.Preview.size.percent == false &&
opts.Preview.size.size == 15+2) {
t.Error(opts.Preview)
}
opts = optsFor("--preview-window=left:15:hidden", "--preview-window=down")
if !(opts.Preview.command == "" &&
opts.Preview.hidden == false &&
opts.Preview.position == posDown &&
opts.Preview.size.percent == true &&
opts.Preview.size.size == 50) {
t.Error(opts.Preview)
}
}

394
src/pattern.go Normal file
View File

@@ -0,0 +1,394 @@
package fzf
import (
"regexp"
"strings"
"github.com/junegunn/fzf/src/algo"
"github.com/junegunn/fzf/src/util"
)
// fuzzy
// 'exact
// ^exact-prefix
// exact-suffix$
// !not-fuzzy
// !'not-exact
// !^not-exact-prefix
// !not-exact-suffix$
type termType int
const (
termFuzzy termType = iota
termExact
termPrefix
termSuffix
termEqual
)
type term struct {
typ termType
inv bool
text []rune
caseSensitive bool
origText []rune
}
type termSet []term
// Pattern represents search pattern
type Pattern struct {
fuzzy bool
fuzzyAlgo algo.Algo
extended bool
caseSensitive bool
forward bool
text []rune
termSets []termSet
cacheable bool
delimiter Delimiter
nth []Range
procFun map[termType]algo.Algo
}
var (
_patternCache map[string]*Pattern
_splitRegex *regexp.Regexp
_cache ChunkCache
)
func init() {
_splitRegex = regexp.MustCompile("\\s+")
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, forward bool,
cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
var asString string
if extended {
asString = strings.Trim(string(runes), " ")
} else {
asString = string(runes)
}
cached, found := _patternCache[asString]
if found {
return cached
}
caseSensitive := true
termSets := []termSet{}
if extended {
termSets = parseTerms(fuzzy, caseMode, 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 {
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,
forward: forward,
text: []rune(asString),
termSets: termSets,
cacheable: cacheable,
nth: nth,
delimiter: delimiter,
procFun: make(map[termType]algo.Algo)}
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, str string) []termSet {
tokens := _splitRegex.Split(str, -1)
sets := []termSet{}
set := termSet{}
switchSet := false
for _, token := range tokens {
typ, inv, text := termFuzzy, false, token
lowerText := strings.ToLower(text)
caseSensitive := caseMode == CaseRespect ||
caseMode == CaseSmart && text != lowerText
if !caseSensitive {
text = lowerText
}
origText := []rune(text)
if !fuzzy {
typ = termExact
}
if text == "|" {
switchSet = false
continue
}
if strings.HasPrefix(text, "!") {
inv = true
text = text[1:]
}
if strings.HasPrefix(text, "'") {
// Flip exactness
if fuzzy {
typ = termExact
text = text[1:]
} else {
typ = termFuzzy
text = text[1:]
}
} else if strings.HasPrefix(text, "^") {
if strings.HasSuffix(text, "$") {
typ = termEqual
text = text[1 : len(text)-1]
} else {
typ = termPrefix
text = text[1:]
}
} else if strings.HasSuffix(text, "$") {
typ = termSuffix
text = text[:len(text)-1]
}
if len(text) > 0 {
if switchSet {
sets = append(sets, set)
set = termSet{}
}
set = append(set, term{
typ: typ,
inv: inv,
text: []rune(text),
caseSensitive: caseSensitive,
origText: origText})
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)
}
// CacheKey is used to build string to be used as the key of result cache
func (p *Pattern) CacheKey() 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].origText))
}
}
return strings.Join(cacheableTerms, " ")
}
// 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, found := _cache.Find(chunk, cacheKey); found {
return cached
}
}
// Prefix/suffix cache
var space []*Result
Loop:
for idx := 1; idx < len(cacheKey); idx++ {
// [---------| ] | [ |---------]
// [--------| ] | [ |--------]
// [-------| ] | [ |-------]
prefix := cacheKey[:len(cacheKey)-idx]
suffix := cacheKey[idx:]
for _, substr := range [2]*string{&prefix, &suffix} {
if cached, found := _cache.Find(chunk, *substr); found {
space = cached
break Loop
}
}
}
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 _, item := range *chunk {
if match, _, _ := p.MatchItem(item, 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, trimLen, pos := p.extendedMatch(item, withPos, slab); len(offsets) == len(p.termSets) {
return buildResult(item, offsets, bonus, trimLen), offsets, pos
}
return nil, nil, nil
}
offset, bonus, trimLen, pos := p.basicMatch(item, withPos, slab)
if sidx := offset[0]; sidx >= 0 {
offsets := []Offset{offset}
return buildResult(item, offsets, bonus, trimLen), offsets, pos
}
return nil, nil, nil
}
func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset, int, int, *[]int) {
input := p.prepareInput(item)
if p.fuzzy {
return p.iter(p.fuzzyAlgo, input, p.caseSensitive, p.forward, p.text, withPos, slab)
}
return p.iter(algo.ExactMatchNaive, input, p.caseSensitive, p.forward, p.text, withPos, slab)
}
func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Offset, int, int, *[]int) {
input := p.prepareInput(item)
offsets := []Offset{}
var totalScore int
var totalTrimLen int
var allPos *[]int
if withPos {
allPos = &[]int{}
}
for _, termSet := range p.termSets {
var offset Offset
var currentScore int
var trimLen int
matched := false
for _, term := range termSet {
pfun := p.procFun[term.typ]
off, score, tLen, pos := p.iter(pfun, input, term.caseSensitive, p.forward, term.text, withPos, slab)
if sidx := off[0]; sidx >= 0 {
if term.inv {
continue
}
offset, currentScore, trimLen = off, score, tLen
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, trimLen = Offset{0, 0}, 0, 0
matched = true
continue
}
}
if matched {
offsets = append(offsets, offset)
totalScore += currentScore
totalTrimLen += trimLen
}
}
return offsets, totalScore, totalTrimLen, allPos
}
func (p *Pattern) prepareInput(item *Item) []Token {
if item.transformed != nil {
return item.transformed
}
var ret []Token
if len(p.nth) == 0 {
ret = []Token{Token{text: &item.text, prefixLength: 0, trimLength: int32(item.text.TrimLength())}}
} else {
tokens := Tokenize(item.text, p.delimiter)
ret = Transform(tokens, p.nth)
}
item.transformed = ret
return ret
}
func (p *Pattern) iter(pfun algo.Algo, tokens []Token, caseSensitive bool, forward bool, pattern []rune, withPos bool, slab *util.Slab) (Offset, int, int, *[]int) {
for _, part := range tokens {
if res, pos := pfun(caseSensitive, 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, int(part.trimLength), pos
}
}
return Offset{-1, -1}, 0, -1, nil
}

188
src/pattern_test.go Normal file
View File

@@ -0,0 +1,188 @@
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,
"| 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 != termFuzzy || !terms[4][0].inv ||
terms[5][0].typ != termExact || !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 != termFuzzy || !terms[8][3].inv {
t.Errorf("%s", terms)
}
for idx, termSet := range terms[:8] {
term := termSet[0]
if len(term.text) != 3 {
t.Errorf("%s", term)
}
if idx > 0 && len(term.origText) != 4+idx/5 {
t.Errorf("%s", term)
}
}
for _, term := range terms[8] {
if len(term.origText) != 4 {
t.Errorf("%s", term)
}
}
}
func TestParseTermsExtendedExact(t *testing.T) {
terms := parseTerms(false, CaseSmart,
"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, "' $ ^ !' !^ !$")
if len(terms) != 0 {
t.Errorf("%s", terms)
}
}
func TestExact(t *testing.T) {
defer clearPatternCache()
clearPatternCache()
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, true, true,
[]Range{}, Delimiter{}, []rune("'abc"))
res, pos := algo.ExactMatchNaive(
pattern.caseSensitive, pattern.forward, util.RunesToChars([]rune("aabbcc abc")), 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, true, true, []Range{}, Delimiter{}, []rune("^AbC$"))
match := func(str string, sidxExpected int, eidxExpected int) {
res, pos := algo.EqualMatch(
pattern.caseSensitive, pattern.forward, util.RunesToChars([]rune(str)), 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, true, true, []Range{}, Delimiter{}, []rune("abc"))
clearPatternCache()
pat2 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, true, true, []Range{}, Delimiter{}, []rune("Abc"))
clearPatternCache()
pat3 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, true, true, []Range{}, Delimiter{}, []rune("abc"))
clearPatternCache()
pat4 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, true, true, []Range{}, Delimiter{}, []rune("Abc"))
clearPatternCache()
pat5 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, true, true, []Range{}, Delimiter{}, []rune("abc"))
clearPatternCache()
pat6 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, 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, true, true, []Range{}, Delimiter{}, []rune("jg"))
tokens := Tokenize(util.RunesToChars([]rune("junegunn")), Delimiter{})
trans := Transform(tokens, []Range{Range{1, 1}})
origBytes := []byte("junegunn.choi")
for _, extended := range []bool{false, true} {
chunk := Chunk{
&Item{
text: util.RunesToChars([]rune("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[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) {
pat := BuildPattern(true, algo.FuzzyMatchV2, extended, CaseSmart, 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: %s, actual: %s (%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 bar baz", true)
test(true, "foo !bar", "foo", false)
test(true, "foo !bar baz", "foo baz", false)
test(true, "foo | bar baz", "baz", false)
test(true, "foo | bar | baz", "", false)
test(true, "foo | bar !baz", "", false)
test(true, "| | | foo", "foo", true)
}

72
src/reader.go Normal file
View File

@@ -0,0 +1,72 @@
package fzf
import (
"bufio"
"io"
"os"
"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
}
// ReadSource reads data from the default command or from standard input
func (r *Reader) ReadSource() {
if util.IsTty() {
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
if len(cmd) == 0 {
cmd = defaultCommand
}
r.readFromCommand(cmd)
} else {
r.readFromStdin()
}
r.eventBox.Set(EvtReadFin, nil)
}
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)
if len(bytea) > 0 {
if err == nil {
bytea = bytea[:len(bytea)-1]
}
if r.pusher(bytea) {
r.eventBox.Set(EvtReadNew, nil)
}
}
if err != nil {
break
}
}
}
func (r *Reader) readFromStdin() {
r.feed(os.Stdin)
}
func (r *Reader) readFromCommand(cmd string) {
listCommand := util.ExecCommand(cmd)
out, err := listCommand.StdoutPipe()
if err != nil {
return
}
err = listCommand.Start()
if err != nil {
return
}
defer listCommand.Wait()
r.feed(out)
}

56
src/reader_test.go Normal file
View File

@@ -0,0 +1,56 @@
package fzf
import (
"testing"
"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}
// Check EventBox
if eb.Peek(EvtReadNew) {
t.Error("EvtReadNew should not be set yet")
}
// Normal command
reader.readFromCommand(`echo abc && echo def`)
if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" {
t.Errorf("%s", strs)
}
// Check EventBox again
if !eb.Peek(EvtReadNew) {
t.Error("EvtReadNew should be set yet")
}
// Wait should return immediately
eb.Wait(func(events *util.Events) {
if _, found := (*events)[EvtReadNew]; !found {
t.Errorf("%s", events)
}
events.Clear()
})
// EventBox is cleared
if eb.Peek(EvtReadNew) {
t.Error("EvtReadNew should not be set yet")
}
// Failing command
reader.readFromCommand(`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 be set")
}
}

240
src/result.go Normal file
View File

@@ -0,0 +1,240 @@
package fzf
import (
"math"
"sort"
"github.com/junegunn/fzf/src/curses"
"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 int
attr curses.Attr
index int32
}
type rank struct {
points [4]uint16
index int32
}
type Result struct {
item *Item
rank rank
}
func buildResult(item *Item, offsets []Offset, score int, trimLen int) *Result {
if len(offsets) > 1 {
sort.Sort(ByOrder(offsets))
}
result := Result{item: item, rank: rank{index: item.index}}
numChars := item.text.Length()
minBegin := 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)
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:
// If offsets is empty, trimLen will be 0, but we don't care
val = util.AsUint16(trimLen)
case byBegin:
if validOffsetFound {
whitePrefixLen := 0
for idx := 0; idx < numChars; idx++ {
r := item.text.Get(idx)
whitePrefixLen = idx
if idx == minBegin || r != ' ' && r != '\t' {
break
}
}
val = util.AsUint16(minBegin - whitePrefixLen)
}
case byEnd:
if validOffsetFound {
val = util.AsUint16(1 + numChars - maxEnd)
}
}
result.rank.points[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() rank {
return rank{index: 0, points: [4]uint16{math.MaxUint16, 0, 0, 0}}
}
func (result *Result) colorOffsets(matchOffsets []Offset, color int, attr curses.Attr, current bool) []colorOffset {
itemColors := result.item.Colors()
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
if fg == -1 {
if current {
fg = curses.CurrentFG
} else {
fg = curses.FG
}
}
bg := ansi.color.bg
if bg == -1 {
if current {
bg = curses.DarkBG
} else {
bg = curses.BG
}
}
colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)},
color: curses.PairFor(fg, bg),
attr: ansi.color.attr | 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]).rank, (*a[j]).rank, 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]).rank, (*a[j]).rank, true)
}
func compareRanks(irank rank, jrank rank, tac bool) bool {
for idx := 0; idx < 4; idx++ {
left := irank.points[idx]
right := jrank.points[idx]
if left < right {
return true
} else if left > right {
return false
}
}
return (irank.index <= jrank.index) != tac
}

123
src/result_test.go Normal file
View File

@@ -0,0 +1,123 @@
package fzf
import (
"math"
"sort"
"testing"
"github.com/junegunn/fzf/src/curses"
"github.com/junegunn/fzf/src/util"
)
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) rank {
return rank{
points: [4]uint16{vals[0], vals[1], vals[2], vals[3]},
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(&Item{text: util.RunesToChars(strs[0]), index: 1}, []Offset{}, 2, 3)
if item1.rank.points[0] != math.MaxUint16-2 || // Bonus
item1.rank.points[1] != 3 || // Length
item1.rank.points[2] != 0 || // Unused
item1.rank.points[3] != 0 || // Unused
item1.item.index != 1 {
t.Error(item1.rank)
}
// Only differ in index
item2 := buildResult(&Item{text: util.RunesToChars(strs[0])}, []Offset{}, 2, 3)
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(&Item{index: 2}, []Offset{Offset{1, 3}, Offset{5, 7}}, 3, 0)
item4 := buildResult(&Item{index: 2}, []Offset{Offset{1, 2}, Offset{6, 7}}, 4, 0)
item5 := buildResult(&Item{index: 2}, []Offset{Offset{1, 3}, Offset{5, 7}}, 5, 0)
item6 := buildResult(&Item{index: 2}, []Offset{Offset{1, 2}, Offset{6, 7}}, 6, 0)
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, curses.Bold}},
ansiOffset{[2]int32{30, 32}, ansiState{3, 7, 0}},
ansiOffset{[2]int32{33, 40}, ansiState{4, 8, curses.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}]
colors := item.colorOffsets(offsets, 99, 0, true)
assert := func(idx int, b int32, e int32, c int, bold bool) {
var attr curses.Attr
if bold {
attr = curses.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, curses.ColUser, false)
assert(1, 5, 15, 99, false)
assert(2, 15, 20, curses.ColUser, false)
assert(3, 22, 25, curses.ColUser+1, true)
assert(4, 25, 35, 99, false)
assert(5, 35, 40, curses.ColUser+2, true)
}

1489
src/terminal.go Normal file

File diff suppressed because it is too large Load Diff

248
src/tokenizer.go Normal file
View File

@@ -0,0 +1,248 @@
package fzf
import (
"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
trimLength 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 []util.Chars, begin int) []Token {
ret := make([]Token, len(tokens))
prefixLength := begin
for idx, token := range tokens {
// NOTE: &tokens[idx] instead of &tokens
ret[idx] = Token{&tokens[idx], int32(prefixLength), int32(token.TrimLength())}
prefixLength += token.Length()
}
return ret
}
const (
awkNil = iota
awkBlack
awkWhite
)
func awkTokenizer(input util.Chars) ([]util.Chars, int) {
// 9, 32
ret := []util.Chars{}
prefixLength := 0
state := awkNil
numChars := input.Length()
begin := 0
end := 0
for idx := 0; idx < numChars; idx++ {
r := input.Get(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.Slice(begin, end))
state, begin, end = awkBlack, idx, idx+1
}
}
}
if begin < end {
ret = append(ret, input.Slice(begin, end))
}
return ret, prefixLength
}
// Tokenize tokenizes the given string with the delimiter
func Tokenize(text util.Chars, 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(text.Split(*delimiter.str), 0)
}
// FIXME performance
var tokens []string
if delimiter.regex != nil {
str := text.ToString()
for len(str) > 0 {
loc := delimiter.regex.FindStringIndex(str)
if loc == nil {
loc = []int{0, len(str)}
}
last := util.Max(loc[1], 1)
tokens = append(tokens, str[:last])
str = str[last:]
}
}
asRunes := make([]util.Chars, len(tokens))
for i, token := range tokens {
asRunes[i] = util.RunesToChars([]rune(token))
}
return withPrefixLengths(asRunes, 0)
}
func joinTokens(tokens []Token) []rune {
ret := []rune{}
for _, token := range tokens {
ret = append(ret, token.text.ToRunes()...)
}
return ret
}
// 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.RunesToChars(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.RunesToChars([]rune{})
case 1:
merged = *parts[0]
default:
runes := []rune{}
for _, part := range parts {
runes = append(runes, part.ToRunes()...)
}
merged = util.RunesToChars(runes)
}
var prefixLength int32
if minIdx < numTokens {
prefixLength = tokens[minIdx].prefixLength
} else {
prefixLength = 0
}
transTokens[idx] = Token{&merged, prefixLength, int32(merged.TrimLength())}
}
return transTokens
}

114
src/tokenizer_test.go Normal file
View File

@@ -0,0 +1,114 @@
package fzf
import (
"testing"
"github.com/junegunn/fzf/src/util"
)
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(util.RunesToChars([]rune(input)), Delimiter{})
if tokens[0].text.ToString() != "abc: " || tokens[0].prefixLength != 2 || tokens[0].trimLength != 4 {
t.Errorf("%s", tokens)
}
// With delimiter
tokens = Tokenize(util.RunesToChars([]rune(input)), delimiterRegexp(":"))
if tokens[0].text.ToString() != " abc:" || tokens[0].prefixLength != 0 || tokens[0].trimLength != 4 {
t.Errorf("%s", tokens)
}
// With delimiter regex
tokens = Tokenize(util.RunesToChars([]rune(input)), delimiterRegexp("\\s+"))
if tokens[0].text.ToString() != " " || tokens[0].prefixLength != 0 || tokens[0].trimLength != 0 ||
tokens[1].text.ToString() != "abc: " || tokens[1].prefixLength != 2 || tokens[1].trimLength != 4 ||
tokens[2].text.ToString() != "def: " || tokens[2].prefixLength != 8 || tokens[2].trimLength != 4 ||
tokens[3].text.ToString() != "ghi " || tokens[3].prefixLength != 14 || tokens[3].trimLength != 3 {
t.Errorf("%s", tokens)
}
}
func TestTransform(t *testing.T) {
input := " abc: def: ghi: jkl"
{
tokens := Tokenize(util.RunesToChars([]rune(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(util.RunesToChars([]rune(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"))
}

43
src/update_assets.rb Executable file
View File

@@ -0,0 +1,43 @@
#!/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 }.each do |file|
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

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")
}
}

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

@@ -0,0 +1,156 @@
package util
import (
"unicode/utf8"
)
type Chars struct {
runes []rune
bytes []byte
}
// ToChars converts byte array into rune array
func ToChars(bytea []byte) Chars {
var runes []rune
ascii := true
numBytes := len(bytea)
for i := 0; i < numBytes; {
if bytea[i] < utf8.RuneSelf {
if !ascii {
runes = append(runes, rune(bytea[i]))
}
i++
} else {
if ascii {
ascii = false
runes = make([]rune, i, numBytes)
for j := 0; j < i; j++ {
runes[j] = rune(bytea[j])
}
}
r, sz := utf8.DecodeRune(bytea[i:])
i += sz
runes = append(runes, r)
}
}
if ascii {
return Chars{bytes: bytea}
}
return Chars{runes: runes}
}
func RunesToChars(runes []rune) Chars {
return Chars{runes: runes}
}
func (chars *Chars) Get(i int) rune {
if chars.runes != nil {
return chars.runes[i]
}
return rune(chars.bytes[i])
}
func (chars *Chars) Length() int {
if chars.runes != nil {
return len(chars.runes)
}
return len(chars.bytes)
}
// TrimLength returns the length after trimming leading and trailing whitespaces
func (chars *Chars) TrimLength() int {
var i int
len := chars.Length()
for i = len - 1; i >= 0; i-- {
char := chars.Get(i)
if char != ' ' && char != '\t' {
break
}
}
// Completely empty
if i < 0 {
return 0
}
var j int
for j = 0; j < len; j++ {
char := chars.Get(j)
if char != ' ' && char != '\t' {
break
}
}
return i - j + 1
}
func (chars *Chars) TrailingWhitespaces() int {
whitespaces := 0
for i := chars.Length() - 1; i >= 0; i-- {
char := chars.Get(i)
if char != ' ' && char != '\t' {
break
}
whitespaces++
}
return whitespaces
}
func (chars *Chars) ToString() string {
if chars.runes != nil {
return string(chars.runes)
}
return string(chars.bytes)
}
func (chars *Chars) ToRunes() []rune {
if chars.runes != nil {
return chars.runes
}
runes := make([]rune, len(chars.bytes))
for idx, b := range chars.bytes {
runes[idx] = rune(b)
}
return runes
}
func (chars *Chars) Slice(b int, e int) Chars {
if chars.runes != nil {
return Chars{runes: chars.runes[b:e]}
}
return Chars{bytes: chars.bytes[b:e]}
}
func (chars *Chars) Split(delimiter string) []Chars {
delim := []rune(delimiter)
numChars := chars.Length()
numDelim := len(delim)
begin := 0
ret := make([]Chars, 0, 1)
for index := 0; index < numChars; {
if index+numDelim <= numChars {
match := true
for off, d := range delim {
if chars.Get(index+off) != d {
match = false
break
}
}
// Found the delimiter
if match {
incr := Max(numDelim, 1)
ret = append(ret, chars.Slice(begin, index+incr))
index += incr
begin = index
continue
}
} else {
// Impossible to find the delimiter in the remaining substring
break
}
index++
}
if begin < numChars || len(ret) == 0 {
ret = append(ret, chars.Slice(begin, numChars))
}
return ret
}

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

@@ -0,0 +1,82 @@
package util
import "testing"
func TestToCharsNil(t *testing.T) {
bs := Chars{bytes: []byte{}}
if bs.bytes == nil || bs.runes != nil {
t.Error()
}
rs := RunesToChars([]rune{})
if rs.bytes != nil || rs.runes == nil {
t.Error()
}
}
func TestToCharsAscii(t *testing.T) {
chars := ToChars([]byte("foobar"))
if chars.ToString() != "foobar" || chars.runes != nil {
t.Error()
}
}
func TestCharsLength(t *testing.T) {
chars := ToChars([]byte("\tabc한글 "))
if 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 int) {
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)
}
func TestSplit(t *testing.T) {
check := func(str string, delim string, tokens ...string) {
input := ToChars([]byte(str))
result := input.Split(delim)
if len(result) != len(tokens) {
t.Errorf("Invalid Split result for '%s': %d tokens found (expected %d): %s",
str, len(result), len(tokens), result)
}
for idx, token := range tokens {
if result[idx].ToString() != token {
t.Errorf("Invalid Split result for '%s': %s (expected %s)",
str, result[idx].ToString(), token)
}
}
}
check("abc:def::", ":", "abc:", "def:", ":")
check("abc:def::", "-", "abc:def::")
check("abc", "", "a", "b", "c")
check("abc", "a", "a", "bc")
check("abc", "ab", "ab", "c")
check("abc", "abc", "abc")
check("abc", "abcd", "abc")
check("", "abcd", "")
}

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()
defer b.cond.L.Unlock()
if len(b.events) == 0 {
b.cond.Wait()
}
callback(&b.events)
}
// Set turns on the event type on the box
func (b *EventBox) Set(event EventType, value interface{}) {
b.cond.L.Lock()
defer b.cond.L.Unlock()
b.events[event] = value
if _, found := b.ignore[event]; !found {
b.cond.Broadcast()
}
}
// 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()
defer b.cond.L.Unlock()
_, ok := b.events[event]
return ok
}
// Watch deletes the events from the ignore list
func (b *EventBox) Watch(events ...EventType) {
b.cond.L.Lock()
defer b.cond.L.Unlock()
for _, event := range events {
delete(b.ignore, event)
}
}
// Unwatch adds the events to the ignore list
func (b *EventBox) Unwatch(events ...EventType) {
b.cond.L.Lock()
defer b.cond.L.Unlock()
for _, event := range events {
b.ignore[event] = true
}
}
// 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)}
}

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

@@ -0,0 +1,108 @@
package util
// #include <unistd.h>
import "C"
import (
"math"
"os"
"os/exec"
"time"
)
// 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 int(C.isatty(C.int(os.Stdin.Fd()))) != 0
}
// ExecCommand executes the given command with $SHELL
func ExecCommand(command string) *exec.Cmd {
shell := os.Getenv("SHELL")
if len(shell) == 0 {
shell = "sh"
}
return exec.Command(shell, "-c", command)
}

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)
}
}

148
test/fzf.vader Normal file
View File

@@ -0,0 +1,148 @@
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 cwd = getcwd()
let result = fzf#run({ '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_go.rb'], result
AssertEqual getcwd(), cwd
Execute (fzf#run with Funcref command):
let g:ret = []
function! g:FzfTest(e)
call add(g:ret, a:e)
endfunction
let result = sort(fzf#run({ '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' }))
AssertEqual ['hi'], result
Execute (fzf#run with list source):
let result = sort(fzf#run({ 'source': ['hello', 'world'], 'options': '-f e' }))
AssertEqual ['hello'], result
let result = sort(fzf#run({ 'source': ['hello', 'world'], 'options': '-f o' }))
AssertEqual ['hello', 'world'], result
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'
Execute (Cleanup):
unlet g:dir
Restore

View File

@@ -1,429 +0,0 @@
#!/usr/bin/env ruby
# encoding: utf-8
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
end
def test_environment_variables
# Deprecated
ENV['FZF_DEFAULT_SORT'] = '20000'
fzf = FZF.new []
assert_equal 20000, fzf.sort
ENV['FZF_DEFAULT_OPTS'] = '-x -m -s 10000 -q " hello world " +c --no-mouse'
fzf = FZF.new []
assert_equal 10000, fzf.sort
assert_equal ' hello world ',
fzf.query.get
assert_equal true, fzf.extended
assert_equal true, fzf.multi
assert_equal false, fzf.color
assert_equal false, fzf.mouse
end
def test_option_parser
# Long opts
fzf = FZF.new %w[--sort=2000 --no-color --multi +i --query hello --extended --no-mouse]
assert_equal 2000, fzf.sort
assert_equal true, fzf.multi
assert_equal false, fzf.color
assert_equal false, fzf.mouse
assert_equal 0, fzf.rxflag
assert_equal 'hello', fzf.query.get
assert_equal true, fzf.extended
fzf = FZF.new %w[--sort=2000 --no-color --multi +i --query hello
--no-sort -i --color --no-multi]
assert_equal nil, fzf.sort
assert_equal false, fzf.multi
assert_equal true, fzf.color
assert_equal true, fzf.mouse
assert_equal 1, fzf.rxflag
assert_equal 'hello', fzf.query.get
assert_equal false, fzf.extended
# Short opts
fzf = FZF.new %w[-s 2000 +c -m +i -qhello -x]
assert_equal 2000, fzf.sort
assert_equal true, fzf.multi
assert_equal false, fzf.color
assert_equal 0, fzf.rxflag
assert_equal 'hello', fzf.query.get
assert_equal true, fzf.extended
# Left-to-right
fzf = FZF.new %w[-s 2000 +c -m +i -qhello -x
-s 3000 -c +m -i -q world +x]
assert_equal 3000, fzf.sort
assert_equal false, fzf.multi
assert_equal true, fzf.color
assert_equal 1, fzf.rxflag
assert_equal 'world', fzf.query.get
assert_equal false, fzf.extended
fzf = FZF.new %w[--query hello +s -s 2000 --query=world]
assert_equal 2000, fzf.sort
assert_equal 'world', fzf.query.get
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
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.new([]).sort_by_rank(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.new([]).sort_by_rank(xmatcher.match(list, '0 1', '', '')))
assert_equal(
[["_01_", [[1, 3], [0, 4]]],
["0____1", [[0, 6], [1, 3]]],
["0_____1", [[0, 7], [1, 3]]],
["0______1", [[0, 8], [1, 3]]],
["___01___", [[3, 5], [0, 2]]],
["____0_1", [[4, 7], [0, 2]]]],
FZF.new([]).sort_by_rank(xmatcher.match(list, '01 __', '', '')))
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
end

1555
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