Compare commits

..

67 Commits
0.5.1 ... 0.8.0

Author SHA1 Message Date
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
Junegunn Choi
7280e8ebc2 Merge pull request #17 from junegunn/mouse
Add mouse support
2014-01-30 07:37:01 -08:00
Junegunn Choi
c7e86ad4f1 Add --no-mouse option to replace FZF_MOUSE_ENABLED 2014-01-30 15:41:44 +09:00
Junegunn Choi
f2b2c022be Update gem version 2014-01-30 14:47:51 +09:00
Junegunn Choi
7747daa9ec Merge branch 'master' into mouse 2014-01-30 03:14:13 +09:00
Junegunn Choi
c2943e7681 Fix incompatible encoding regexp match from width call 2014-01-30 03:12:12 +09:00
Junegunn Choi
d5fc03d867 Update README 2014-01-30 02:51:30 +09:00
Junegunn Choi
b0eca20dc2 Minor refactoring 2014-01-30 02:51:06 +09:00
Junegunn Choi
aad335475c Shift-click and wheel 2014-01-30 01:01:31 +09:00
Junegunn Choi
c3676bf986 Make install script prefer system ruby 2014-01-29 11:04:07 +09:00
Junegunn Choi
6fb4b6d097 Do not move vcursor on select using mouse 2014-01-29 02:10:08 +09:00
Junegunn Choi
6aa168833b Ruby 1.8 compatibility 2014-01-29 02:08:18 +09:00
Junegunn Choi
0d83cae2ec Implement mouse support 2014-01-28 19:02:55 +09:00
Junegunn Choi
773d9976a0 Use Curses.getch to support mouse (WIP) 2014-01-28 02:58:20 +09:00
Junegunn Choi
3723829b0a Add FZF_DEFAULT_OPTS and update command-line options 2014-01-22 12:03:17 +09:00
Junegunn Choi
13cb198b5c Update README 2014-01-14 16:51:52 +09:00
Junegunn Choi
79f645aa6c Update README 2014-01-07 17:07:02 +09:00
Junegunn Choi
42d479d071 --version 2013-12-28 02:25:24 +09:00
Junegunn Choi
d7f50b1e41 Fix typo in install script 2013-12-26 01:54:29 +09:00
Junegunn Choi
39eb85596c Fix error on Rubinius 2013-12-26 01:43:20 +09:00
Junegunn Choi
bff7e9edf5 Should not --disable-gems when curses gem is used (#14) 2013-12-26 01:39:17 +09:00
Junegunn Choi
98ccc03a21 Update README.md 2013-12-26 01:15:46 +09:00
Junegunn Choi
3b668ed448 Install curses gem when not found (#14) 2013-12-26 01:06:46 +09:00
Junegunn Choi
33b28be941 Make host name completion require trigger sequence (#13) 2013-12-23 23:16:07 +09:00
Junegunn Choi
76fe23b928 Fix host completion to include ssh_config entries (#13) 2013-12-22 20:45:35 +09:00
Junegunn Choi
622c54f4a3 Update gem version (0.6.0)
- Smart-case pattern matching
- CTRL-Q
2013-12-22 16:00:06 +09:00
Junegunn Choi
e09993f919 Update README 2013-12-22 00:36:39 +09:00
Junegunn Choi
7ee6fd1f6d Make install script to add key bindings as well 2013-12-22 00:18:41 +09:00
Junegunn Choi
2dca6f0cb2 Update Last update 2013-12-20 16:13:38 +09:00
Junegunn Choi
159dd7f069 Implement smart-case match (#12) 2013-12-20 15:30:48 +09:00
Junegunn Choi
b30f21e074 CTRL-Q to terminate the finder (#11) 2013-12-20 14:01:28 +09:00
Junegunn Choi
636c86cf6f Update bash host completion for ssh and telnet commands 2013-12-20 11:18:28 +09:00
Junegunn Choi
5483e41b2a Update README 2013-12-14 22:30:09 +09:00
Junegunn Choi
1c89994c94 Suppress warnings on old version of Ruby 2013-12-11 01:03:47 +09:00
Junegunn Choi
e1bc4b983e Update gem version 2013-12-11 01:00:11 +09:00
Junegunn Choi
cb3645ea95 Fix ^.*$ pattern matching in extended-search mode (#9) 2013-12-09 14:46:06 +09:00
6 changed files with 818 additions and 303 deletions

232
README.md
View File

@@ -16,8 +16,6 @@ fzf requires Ruby (>= 1.8.5).
Installation Installation
------------ ------------
### Using install script
Clone this repository and run Clone this repository and run
[install](https://github.com/junegunn/fzf/blob/master/install) script. [install](https://github.com/junegunn/fzf/blob/master/install) script.
@@ -26,46 +24,22 @@ git clone https://github.com/junegunn/fzf.git ~/.fzf
~/.fzf/install ~/.fzf/install
``` ```
The script will generate `~/.fzf.bash` and `~/.fzf.zsh` and update your The script will setup:
`.bashrc` and `.zshrc` to load them.
### Manual installation - `fzf` executable
- Key bindings (`CTRL-T`, `CTRL-R`, etc.)
Or you can just download - Fuzzy auto-completion for bash
[fzf executable](https://raw.github.com/junegunn/fzf/master/fzf) and put it
somewhere in your search $PATH.
```sh
mkdir -p ~/bin
wget https://raw.github.com/junegunn/fzf/master/fzf -O ~/bin/fzf
chmod +x ~/bin/fzf
```
### Install as Ruby gem
fzf can be installed as a Ruby gem
```
gem install fzf
```
It's a bit easier to install and update the script but the Ruby gem version
takes slightly longer to start.
### Install as Vim plugin ### Install as Vim plugin
You can use any Vim plugin manager to install fzf for Vim. If you don't use one, Once you have cloned the repository, add the following line to your .vimrc.
I recommend you try [vim-plug](https://github.com/junegunn/vim-plug).
1. [Install vim-plug](https://github.com/junegunn/vim-plug#usage) ```vim
2. Edit your .vimrc set rtp+=~/.fzf
```
call plug#begin() Or you may use any Vim plugin manager, such as
Plug 'junegunn/fzf' [vim-plug](https://github.com/junegunn/vim-plug).
" ...
call plug#end()
3. Run `:PlugInstall`
Usage Usage
----- -----
@@ -73,13 +47,23 @@ Usage
``` ```
usage: fzf [options] usage: fzf [options]
Options
-m, --multi Enable multi-select -m, --multi Enable multi-select
-x, --extended Extended-search mode -x, --extended Extended-search mode
-e, --extended-exact Extended-search mode (exact match)
-q, --query=STR Initial query -q, --query=STR Initial query
-s, --sort=MAX Maximum number of matched items to sort. Default: 1000 -f, --filter=STR Filter mode. Do not start interactive finder.
-s, --sort=MAX Maximum number of matched items to sort (default: 1000)
+s, --no-sort Do not sort the result. Keep the sequence unchanged. +s, --no-sort Do not sort the result. Keep the sequence unchanged.
-i Case-insensitive match (default: smart-case match)
+i Case-sensitive match +i Case-sensitive match
+c, --no-color Disable colors +c, --no-color Disable colors
+2, --no-256 Disable 256-color
--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 fzf will launch curses-based finder, read the list from STDIN, and write the
@@ -119,6 +103,10 @@ The following readline key bindings should also work as expected.
If you enable multi-select mode with `-m` option, you can select multiple items If you enable multi-select mode with `-m` option, you can select multiple items
with TAB or Shift-TAB key. with TAB or Shift-TAB key.
You can also use mouse. Double-click on an item to select it or shift-click (or
ctrl-click) to select multiple items. Use mouse wheel to move the cursor up and
down.
### Extended-search mode ### Extended-search mode
With `-x` or `--extended` option, fzf will start in "extended-search mode". With `-x` or `--extended` option, fzf will start in "extended-search mode".
@@ -135,46 +123,11 @@ such as: `^music .mp3$ sbtrkt !rmx`
| `'wild` | Items that include `wild` | exact-match (quoted) | | `'wild` | Items that include `wild` | exact-match (quoted) |
| `!'fire` | Items that do not include `fire` | inverse-exact-match | | `!'fire` | Items that do not include `fire` | inverse-exact-match |
Usage as Vim plugin If you don't need fuzzy matching and do not wish to "quote" every word, start
------------------- fzf with `-e` or `--extended-exact` option.
If you install fzf as a Vim plugin, `:FZF` command will be added. Useful examples
---------------
```vim
" Look for files under current directory
:FZF
" Look for files under your home directory
:FZF ~
" With options
:FZF --no-sort -m /tmp
```
You can override the source command which produces input to fzf.
```vim
let g:fzf_source = 'find . -type f'
```
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.
Useful bash examples
--------------------
```sh ```sh
# vimf - Open selected file in Vim # vimf - Open selected file in Vim
@@ -189,7 +142,7 @@ fd() {
# fda - including hidden directories # fda - including hidden directories
fda() { fda() {
DIR=$(find ${1:-*} -type d 2> /dev/null | fzf) && cd "$DIR" DIR=$(find ${1:-.} -type d 2> /dev/null | fzf) && cd "$DIR"
} }
# fh - repeat history # fh - repeat history
@@ -203,67 +156,28 @@ fkill() {
} }
``` ```
bash key bindings Key bindings for command line
----------------- -----------------------------
```sh The install script will setup the following 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 ### bash
fsel() {
find ${1:-*} | 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 - `CTRL-T` - Paste the selected file path(s) into the command line
bind '"\C-r": " \C-e\C-u$(history | fzf +s | sed \"s/ *[0-9]* *//\")\e\C-e\er"' - `CTRL-R` - Paste the selected command from history into the command line
```
zsh widgets The source code can be found in `~/.fzf.bash`.
-----------
```sh ### zsh
# 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 - `CTRL-T` - Paste the selected file path(s) into the command line
fzf-cd-widget() { - `CTRL-R` - Paste the selected command from history into the command line
cd "${$(find * -path '*/\.*' -prune \ - `ALT-C` - cd into the selected directory
-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 The source code can be found in `~/.fzf.zsh`.
fzf-history-widget() {
LBUFFER=$(history | fzf +s | sed "s/ *[0-9]* *//")
zle redisplay
}
zle -N fzf-history-widget
bindkey '^R' fzf-history-widget
```
Auto-completion (experimental) Auto-completion
------------------------------ ---------------
Disclaimer: *Auto-completion feature is currently experimental, it can change Disclaimer: *Auto-completion feature is currently experimental, it can change
over time* over time*
@@ -312,11 +226,11 @@ kill -9 <TAB>
#### Host names #### Host names
For ssh and telnet commands, fuzzy completion for host names is provided. The For ssh and telnet commands, fuzzy completion for host names is provided. The
names are extracted from /etc/hosts file. names are extracted from /etc/hosts and ~/.ssh/config.
```sh ```sh
ssh <TAB> ssh **<TAB>
telnet <TAB> telnet **<TAB>
``` ```
#### Settings #### Settings
@@ -335,6 +249,44 @@ TODO :smiley:
(Pull requests are appreciated.) (Pull requests are appreciated.)
Usage as Vim plugin
-------------------
If you install fzf as a Vim plugin, `:FZF` command will be added.
```vim
" Look for files under current directory
:FZF
" Look for files under your home directory
:FZF ~
" With options
:FZF --no-sort -m /tmp
```
You can override the source command which produces input to fzf.
```vim
let g:fzf_source = 'find . -type f'
```
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 Tips
---- ----
@@ -367,6 +319,20 @@ 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 to 1.9 or above. Ruby 1.9 or above is also required for displaying Unicode
characters. 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`.
```sh
export FZF_DEFAULT_OPTS="--sort 20000"
```
License License
------- -------

435
fzf
View File

@@ -7,12 +7,13 @@
# / __/ / /_/ __/ # / __/ / /_/ __/
# /_/ /___/_/ Fuzzy finder for your shell # /_/ /___/_/ Fuzzy finder for your shell
# #
# URL: https://github.com/junegunn/fzf # Version: 0.8.0 (March 8, 2014)
# Author: Junegunn Choi
# License: MIT
# Last update: December 5, 2013
# #
# Copyright (c) 2013 Junegunn Choi # Author: Junegunn Choi
# URL: https://github.com/junegunn/fzf
# License: MIT
#
# Copyright (c) 2014 Junegunn Choi
# #
# MIT License # MIT License
# #
@@ -39,9 +40,17 @@ require 'thread'
require 'curses' require 'curses'
require 'set' require 'set'
unless String.method_defined? :force_encoding
class String
def force_encoding *arg
self
end
end
end
class FZF class FZF
C = Curses C = Curses
attr_reader :rxflag, :sort, :color, :multi, :query attr_reader :rxflag, :sort, :color, :ansi256, :mouse, :multi, :query, :filter, :extended
class AtomicVar class AtomicVar
def initialize value def initialize value
@@ -54,10 +63,8 @@ class FZF
end end
def set value = nil def set value = nil
if block_given? @mutex.synchronize do
@mutex.synchronize { @value = yield @value } @value = block_given? ? yield(@value) : value
else
@mutex.synchronize { @value = value }
end end
end end
@@ -67,43 +74,71 @@ class FZF
end end
def initialize argv, source = $stdin def initialize argv, source = $stdin
@rxflag = Regexp::IGNORECASE @rxflag = nil
@sort = ENV.fetch('FZF_DEFAULT_SORT', 1000).to_i @sort = ENV.fetch('FZF_DEFAULT_SORT', 1000).to_i
@color = true @color = true
@ansi256 = true
@multi = false @multi = false
@xmode = false @mouse = true
@extended = nil
@filter = nil
argv = argv.dup argv =
if opts = ENV['FZF_DEFAULT_OPTS']
require 'shellwords'
Shellwords.shellwords(opts) + argv
else
argv.dup
end
while o = argv.shift while o = argv.shift
case o case o
when '--version' then version
when '-h', '--help' then usage 0 when '-h', '--help' then usage 0
when '-m', '--multi' then @multi = true when '-m', '--multi' then @multi = true
when '-x', '--extended' then @xmode = true when '+m', '--no-multi' then @multi = false
when '-x', '--extended' then @extended = :fuzzy
when '+x', '--no-extended' then @extended = nil
when '-i' then @rxflag = Regexp::IGNORECASE
when '+i' then @rxflag = 0 when '+i' then @rxflag = 0
when '+s', '--no-sort' then @sort = nil when '-c', '--color' then @color = true
when '+c', '--no-color' then @color = false when '+c', '--no-color' then @color = false
when '-2', '--256' then @ansi256 = true
when '+2', '--no-256' then @ansi256 = false
when '--mouse' then @mouse = true
when '--no-mouse' then @mouse = false
when '+s', '--no-sort' then @sort = nil
when '-q', '--query' when '-q', '--query'
usage 1, 'query string required' unless query = argv.shift usage 1, 'query string required' unless query = argv.shift
@query = AtomicVar.new query.dup @query = AtomicVar.new query.dup
when /^-q(.*)$/, /^--query=(.*)$/ when /^-q(.*)$/, /^--query=(.*)$/
@query = AtomicVar.new($1) @query = AtomicVar.new($1)
when '-f', '--filter'
usage 1, 'query string required' unless query = argv.shift
@filter = query
when /^-f(.*)$/, /^--filter=(.*)$/
@filter = $1
when '-s', '--sort' when '-s', '--sort'
usage 1, 'sort size required' unless sort = argv.shift usage 1, 'sort size required' unless sort = argv.shift
usage 1, 'invalid sort size' unless sort =~ /^[0-9]+$/ usage 1, 'invalid sort size' unless sort =~ /^[0-9]+$/
@sort = sort.to_i @sort = sort.to_i
when /^-s([0-9]+)$/, /^--sort=([0-9]+)$/ when /^-s([0-9]+)$/, /^--sort=([0-9]+)$/
@sort = $1.to_i @sort = $1.to_i
when '-e', '--extended-exact' then @extended = :exact
when '+e', '--no-extended-exact' then @extended = nil
else else
usage 1, "illegal option: #{o}" usage 1, "illegal option: #{o}"
end end
end end
@source = source @source = source.clone
@mtx = Mutex.new @mtx = Mutex.new
@cv = ConditionVariable.new @cv = ConditionVariable.new
@events = {} @events = {}
@new = [] @new = []
@queue = Queue.new @queue = Queue.new
@pending = nil
unless @filter
@query ||= AtomicVar.new('') @query ||= AtomicVar.new('')
@cursor_x = AtomicVar.new(@query.length) @cursor_x = AtomicVar.new(@query.length)
@matches = AtomicVar.new([]) @matches = AtomicVar.new([])
@@ -113,31 +148,74 @@ class FZF
@spinner = AtomicVar.new('-\|/-\|/'.split(//)) @spinner = AtomicVar.new('-\|/-\|/'.split(//))
@selects = AtomicVar.new({}) # ordered >= 1.9 @selects = AtomicVar.new({}) # ordered >= 1.9
@main = Thread.current @main = Thread.current
@stdout = $stdout.clone
@plcount = 0 @plcount = 0
end end
end
def start def start
if @filter
start_reader(false).join
filter_list @new
else
@stdout = $stdout.clone
$stdout.reopen($stderr) $stdout.reopen($stderr)
start_reader true
init_screen init_screen
start_reader
start_renderer start_renderer
start_search start_search
start_loop start_loop
end end
end
def filter_list list
matches = get_matcher.match(list, @filter, '', '')
if @sort && matches.length <= @sort
matches = sort_by_rank(matches)
end
matches.each { |m| puts m.first }
end
def get_matcher
if @extended
ExtendedFuzzyMatcher.new @rxflag, @extended
else
FuzzyMatcher.new @rxflag
end
end
def version
File.open(__FILE__, 'r') do |f|
f.each_line do |line|
if line =~ /Version: (.*)/
$stdout.puts "fzf " << $1
exit
end
end
end
end
def usage x, message = nil def usage x, message = nil
$stderr.puts message if message $stderr.puts message if message
$stderr.puts %[usage: fzf [options] $stderr.puts %[usage: fzf [options]
Options
-m, --multi Enable multi-select -m, --multi Enable multi-select
-x, --extended Extended-search mode -x, --extended Extended-search mode
-e, --extended-exact Extended-search mode (exact match)
-q, --query=STR Initial query -q, --query=STR Initial query
-s, --sort=MAX Maximum number of matched items to sort. Default: 1000 -f, --filter=STR Filter mode. Do not start interactive finder.
-s, --sort=MAX Maximum number of matched items to sort (default: 1000)
+s, --no-sort Do not sort the result. Keep the sequence unchanged. +s, --no-sort Do not sort the result. Keep the sequence unchanged.
-i Case-insensitive match (default: smart-case match)
+i Case-sensitive match +i Case-sensitive match
+c, --no-color Disable colors] +c, --no-color Disable colors
+2, --no-256 Disable 256-color
--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")] + $/ + $/
exit x exit x
end end
@@ -347,7 +425,7 @@ class FZF
tokens << [line[b...e], true] tokens << [line[b...e], true]
index = e index = e
end end
tokens << [line[index..-1], false] tokens << [line[index..-1], false] if index < line.length
tokens.reject { |pair| pair.first.empty? } tokens.reject { |pair| pair.first.empty? }
end end
@@ -389,10 +467,12 @@ class FZF
} }
end end
if RUBY_VERSION.split('.').map { |e| e.rjust(3, '0') }.join > '001009' AFTER_1_9 = RUBY_VERSION.split('.').map { |e| e.rjust(3, '0') }.join >= '001009'
if AFTER_1_9
@@wrx = Regexp.new '\p{Han}|\p{Katakana}|\p{Hiragana}|\p{Hangul}' @@wrx = Regexp.new '\p{Han}|\p{Katakana}|\p{Hiragana}|\p{Hangul}'
def width str def width str
str.gsub(@@wrx, ' ').length str.gsub(@@wrx, ' ').length rescue str.length
end end
def trim str, len, left def trim str, len, left
@@ -434,6 +514,7 @@ class FZF
def init_screen def init_screen
C.init_screen C.init_screen
C.mousemask C::ALL_MOUSE_EVENTS if @mouse
C.start_color C.start_color
dbg = dbg =
if C.respond_to?(:use_default_colors) if C.respond_to?(:use_default_colors)
@@ -446,7 +527,7 @@ class FZF
C.noecho C.noecho
if @color if @color
if C.can_change_color? && ENV['TERM'].to_s =~ /256/ if @ansi256 && ENV['TERM'].to_s =~ /256/
C.init_pair 1, 110, dbg C.init_pair 1, 110, dbg
C.init_pair 2, 108, dbg C.init_pair 2, 108, dbg
C.init_pair 3, 254, 236 C.init_pair 3, 254, 236
@@ -485,9 +566,11 @@ class FZF
end | (bold ? C::A_BOLD : 0) end | (bold ? C::A_BOLD : 0)
end end
end end
C.refresh
end end
def start_reader def start_reader curses
stream = stream =
if @source.tty? if @source.tty?
if default_command = ENV['FZF_DEFAULT_COMMAND'] if default_command = ENV['FZF_DEFAULT_COMMAND']
@@ -506,12 +589,12 @@ class FZF
emit(:new) { @new << line.chomp } emit(:new) { @new << line.chomp }
end end
emit(:loaded) { true } emit(:loaded) { true }
@spinner.clear @spinner.clear if curses
end end
end end
def start_search def start_search
matcher = (@xmode ? ExtendedFuzzyMatcher : FuzzyMatcher).new @rxflag matcher = get_matcher
searcher = Thread.new { searcher = Thread.new {
lists = [] lists = []
events = {} events = {}
@@ -554,8 +637,7 @@ class FZF
if new_search && !lists.empty? if new_search && !lists.empty?
q, cx = events.delete(:key) || [q, 0] q, cx = events.delete(:key) || [q, 0]
empty = matcher.empty?(q) empty = matcher.empty?(q)
matches = fcache[q] ||= unless matches = fcache[q]
begin
found = [] found = []
skip = false skip = false
cnt = 0 cnt = 0
@@ -572,12 +654,12 @@ class FZF
matcher.match(list, q, q[0, cx], q[cx..-1])) matcher.match(list, q, q[0, cx], q[cx..-1]))
end end
next if skip next if skip
@sort ? found : found.reverse matches = @sort ? found : found.reverse
end
if !empty && @sort && matches.length <= @sort if !empty && @sort && matches.length <= @sort
matches = sort_by_rank(matches) matches = sort_by_rank(matches)
end end
fcache[q] = matches
end
# Atomic update # Atomic update
@matches.set matches @matches.set matches
@@ -652,10 +734,172 @@ class FZF
nil nil
end end
def vselect &prc
@vcursor.set { |v| @vcursors << v; prc.call v }
update_list false
end
def num_unicode_bytes chr
# http://en.wikipedia.org/wiki/UTF-8
if chr & 0b10000000 > 0
bytes = 0
7.downto(2) do |shift|
break if (chr >> shift) & 0x1 == 0
bytes += 1
end
bytes
else
1
end
end
def read_nb chars = 1, default = nil, tries = 10
tries.times do |_|
begin
return @tty.read_nonblock(chars).ord
rescue Exception
sleep 0.01
end
end
default
end
def read_nbs
ords = []
while ord = read_nb
ords << ord
end
ords
end
def get_mouse
case ord = read_nb
when 32, 36, 40, 48, # mouse-down / shift / cmd / ctrl
35, 39, 43, 51 # mouse-up / shift / cmd / ctrl
x = read_nb - 33
y = read_nb - 33
{ :event => (ord % 2 == 0 ? :click : :release),
:x => x, :y => y, :shift => ord >= 36 }
when 96, 100, 104, 112, # scroll-up / shift / cmd / ctrl
97, 101, 105, 113 # scroll-down / shift / cmd / ctrl
read_nb(2)
{ :event => :scroll, :diff => (ord % 2 == 0 ? -1 : 1), :shift => ord >= 100 }
else
# e.g. 40, 43, 104, 105
read_nb(2)
nil
end
end
def get_input actions
@tty ||= IO.open(IO.sysopen('/dev/tty'), 'r')
if pending = @pending
@pending = nil
return pending
end
str = ''
while true
ord =
if str.empty?
@tty.getc.ord
else
begin
ord = @tty.read_nonblock(1).ord
if (nb = num_unicode_bytes(ord)) > 1
ords = [ord]
(nb - 1).times do |_|
ords << @tty.read_nonblock(1).ord
end
# UTF-8 TODO Ruby 1.8
ords.pack('C*').force_encoding('UTF-8')
else
ord
end
rescue Exception
return str
end
end
ord =
case read_nb(1, :esc)
when 91
case read_nb(1, nil)
when 68 then ctrl(:b)
when 67 then ctrl(:f)
when 66 then ctrl(:j)
when 65 then ctrl(:k)
when 90 then :stab
when 50 then read_nb; :ins
when 51 then read_nb; :del
when 53 then read_nb; :pgup
when 54 then read_nb; :pgdn
when 49
case read_nbs
when [59, 50, 68] then ctrl(:a)
when [59, 50, 67] then ctrl(:e)
when [126] then ctrl(:a)
end
when 52 then read_nb; ctrl(:e)
when 72 then ctrl(:a)
when 70 then ctrl(:e)
when 77
get_mouse
end
when 'b', 98 then :alt_b
when 'f', 102 then :alt_f
when :esc then :esc
else next
end if ord == 27
return ord if ord.nil? || ord.is_a?(Hash)
if actions.has_key?(ord)
if str.empty?
return ord
else
@pending = ord
return str
end
else
unless ord.is_a? String
ord = [ord].pack('U*')
end
str << ord if ord =~ /[[:print:]]/
end
end
end
class MouseEvent
DOUBLE_CLICK_INTERVAL = 0.5
attr_reader :v
def initialize v = nil
@c = 0
@v = v
@t = Time.at 0
end
def v= v
@c = (@v == v && within?) ? @c + 1 : 0
@v = v
@t = Time.now
end
def double? v
@c == 1 && @v == v && within?
end
def within?
(Time.now - @t) < DOUBLE_CLICK_INTERVAL
end
end
def start_loop def start_loop
got = nil got = nil
begin begin
tty = IO.open(IO.sysopen('/dev/tty'), 'r')
input = @query.get.dup input = @query.get.dup
cursor = input.length cursor = input.length
backword = proc { backword = proc {
@@ -671,78 +915,91 @@ class FZF
ctrl(:u) => proc { input = input[cursor..-1]; cursor = 0 }, ctrl(:u) => proc { input = input[cursor..-1]; cursor = 0 },
ctrl(:a) => proc { cursor = 0; nil }, ctrl(:a) => proc { cursor = 0; nil },
ctrl(:e) => proc { cursor = input.length; nil }, ctrl(:e) => proc { cursor = input.length; nil },
ctrl(:j) => proc { @vcursor.set { |v| @vcursors << v; v - 1 }; update_list false }, ctrl(:j) => proc { vselect { |v| v - 1 } },
ctrl(:k) => proc { @vcursor.set { |v| @vcursors << v; v + 1 }; update_list false }, ctrl(:k) => proc { vselect { |v| v + 1 } },
ctrl(:w) => proc { ctrl(:w) => proc {
pcursor = cursor pcursor = cursor
backword.call backword.call
input = input[0...cursor] + input[pcursor..-1] input = input[0...cursor] + input[pcursor..-1]
}, },
127 => proc { input[cursor -= 1] = '' if cursor > 0 }, ctrl(:h) => proc { input[cursor -= 1] = '' if cursor > 0 },
9 => proc { |o| ctrl(:i) => proc { |o|
if @multi && sel = pick if @multi && sel = pick
if @selects.has_key? sel if @selects.has_key? sel
@selects.delete sel @selects.delete sel
else else
@selects[sel] = 1 @selects[sel] = 1
end end
@vcursor.set { |v| vselect { |v| v + case o
@vcursors << v when :stab then 1
v + (o == :stab ? 1 : -1) when :sclick then 0
} else -1
update_list false end }
end end
}, },
:left => proc { cursor = [0, cursor - 1].max; nil }, ctrl(:b) => proc { cursor = [0, cursor - 1].max; nil },
:right => proc { cursor = [input.length, cursor + 1].min; nil }, ctrl(:f) => proc { cursor = [input.length, cursor + 1].min; nil },
ctrl(:l) => proc { render { C.clear; C.refresh }; update_list true },
:del => proc { input[cursor] = '' if input.length > cursor },
:pgup => proc { vselect { |_| max_items } },
:pgdn => proc { vselect { |_| 0 } },
:alt_b => proc { backword.call; nil }, :alt_b => proc { backword.call; nil },
:alt_f => proc { :alt_f => proc {
cursor += (input[cursor..-1].index(/(\S\s)|(.$)/) || -1) + 1 cursor += (input[cursor..-1].index(/(\S\s)|(.$)/) || -1) + 1
nil nil
}, },
} }
actions[ctrl(:b)] = actions[:left]
actions[ctrl(:f)] = actions[:right]
actions[ctrl(:h)] = actions[127]
actions[ctrl(:n)] = actions[ctrl(:j)]
actions[ctrl(:p)] = actions[ctrl(:k)] actions[ctrl(:p)] = actions[ctrl(:k)]
actions[ctrl(:g)] = actions[ctrl(:c)] = actions[:esc] actions[ctrl(:n)] = actions[ctrl(:j)]
actions[:stab] = actions[9] actions[:stab] = actions[ctrl(:i)]
actions[127] = actions[ctrl(:h)]
actions[ctrl(:q)] = actions[ctrl(:g)] = actions[ctrl(:c)] = actions[:esc]
emit(:key) { [@query.get, cursor] } unless @query.empty? emit(:key) { [@query.get, cursor] } unless @query.empty?
mouse = MouseEvent.new
while true while true
@cursor_x.set cursor @cursor_x.set cursor
render { print_input } render { print_input }
ord = tty.getc.ord if key = get_input(actions)
ord = upd = actions.fetch(key, proc { |val|
case ord = (tty.read_nonblock(1).ord rescue :esc) case val
when 91 when String
case (tty.read_nonblock(1).ord rescue nil) input.insert cursor, val
when 68 then :left cursor += val.length
when 67 then :right when Hash
when 66 then ctrl(:j) event = val[:event]
when 65 then ctrl(:k) case event
when 90 then :stab when :click, :release
else next x, y, shift = val.values_at :x, :y, :shift
end if y == cursor_y
when 'b'.ord then :alt_b cursor = [0, [input.length, x - 2].min].max
when 'f'.ord then :alt_f elsif x > 1 && y <= max_items
when :esc then :esc tv = max_items - y - 1
else next
end if ord == 27
upd = actions.fetch(ord, proc { |ord| case event
char = [ord].pack('U*') when :click
if char =~ /[[:print:]]/ vselect { |_| tv }
input.insert cursor, char actions[ctrl(:i)].call(:sclick) if shift
cursor += 1 mouse.v = tv
when :release
if !shift && mouse.double?(tv)
actions[ctrl(:m)].call
end end
}).call(ord) end
end
when :scroll
diff, shift = val.values_at :diff, :shift
actions[ctrl(:i)].call(:sclick) if shift
actions[ctrl(diff > 0 ? :j : :k)].call
end
end
}).call(key)
# Dispatch key event # Dispatch key event
emit(:key) { [@query.set(input.dup), cursor] } if upd emit(:key) { [@query.set(input.dup), cursor] } if upd
end end
end
ensure ensure
C.close_screen C.close_screen
if got if got
@@ -770,14 +1027,18 @@ class FZF
q.empty? q.empty?
end end
def rxflag_for q
@rxflag || (q =~ /[A-Z]/ ? 0 : Regexp::IGNORECASE)
end
def fuzzy_regex q def fuzzy_regex q
@regexp[q] ||= begin @regexp[q] ||= begin
q = q.downcase if @rxflag != 0 q = q.downcase if @rxflag == Regexp::IGNORECASE
Regexp.new(query_chars(q).inject('') { |sum, e| Regexp.new(query_chars(q).inject('') { |sum, e|
e = Regexp.escape e e = Regexp.escape e
sum << (e.length > 1 ? "(?:#{e}).*?" : # FIXME: not equivalent sum << (e.length > 1 ? "(?:#{e}).*?" : # FIXME: not equivalent
"#{e}[^#{e}]*?") "#{e}[^#{e}]*?")
}, @rxflag) }, rxflag_for(q))
end end
end end
@@ -807,9 +1068,10 @@ class FZF
end end
class ExtendedFuzzyMatcher < FuzzyMatcher class ExtendedFuzzyMatcher < FuzzyMatcher
def initialize rxflag def initialize rxflag, mode = :fuzzy
super super rxflag
@regexps = {} @regexps = {}
@mode = mode
end end
def empty? q def empty? q
@@ -829,21 +1091,30 @@ class FZF
case w case w
when '' when ''
nil nil
when /^\^(.*)\$$/
Regexp.new('^' << sanitize(Regexp.escape($1)) << '$', rxflag_for(w))
when /^'/ when /^'/
w.length > 1 ? if @mode == :fuzzy && w.length > 1
Regexp.new(sanitize(Regexp.escape(w[1..-1])), rxflag) : nil exact_regex w[1..-1]
elsif @mode == :exact
exact_regex w
end
when /^\^/ when /^\^/
w.length > 1 ? w.length > 1 ?
Regexp.new('^' << sanitize(Regexp.escape(w[1..-1])), rxflag) : nil Regexp.new('^' << sanitize(Regexp.escape(w[1..-1])), rxflag_for(w)) : nil
when /\$$/ when /\$$/
w.length > 1 ? w.length > 1 ?
Regexp.new(sanitize(Regexp.escape(w[0..-2])) << '$', rxflag) : nil Regexp.new(sanitize(Regexp.escape(w[0..-2])) << '$', rxflag_for(w)) : nil
else else
fuzzy_regex w @mode == :fuzzy ? fuzzy_regex(w) : exact_regex(w)
end, invert ] end, invert ]
}.select { |pair| pair.first } }.select { |pair| pair.first }
end end
def exact_regex w
Regexp.new(sanitize(Regexp.escape(w)), rxflag_for(w))
end
def match list, q, prefix, suffix def match list, q, prefix, suffix
regexps = parse q regexps = parse q
# Look for prefix cache # Look for prefix cache

View File

@@ -31,12 +31,12 @@ _fzf_opts_completion() {
} }
_fzf_generic_completion() { _fzf_generic_completion() {
local cur base dir leftover matches local cur base dir leftover matches trigger
COMPREPLY=() COMPREPLY=()
FZF_COMPLETION_TRIGGER=${FZF_COMPLETION_TRIGGER:-**} trigger=${FZF_COMPLETION_TRIGGER:-**}
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
if [[ ${cur} == *"$FZF_COMPLETION_TRIGGER" ]]; then if [[ ${cur} == *"$trigger" ]]; then
base=${cur:0:${#cur}-${#FZF_COMPLETION_TRIGGER}} base=${cur:0:${#cur}-${#trigger}}
eval base=$base eval base=$base
dir="$base" dir="$base"
@@ -96,11 +96,12 @@ _fzf_kill_completion() {
fi fi
} }
_fzf_host_completion() { _fzf_telnet_completion() {
local cur prev selected local cur selected trigger
trigger=${FZF_COMPLETION_TRIGGER:-**}
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}" [[ ${cur} == *"$trigger" ]] || return 1
[ "$cur" = '-l' -o "$prev" = '-l' ] && return 1 cur=${cur:0:${#cur}-${#trigger}}
tput sc tput sc
selected=$(grep -v '^\s*\(#\|$\)' /etc/hosts | awk '{print $2}' | sort -u | fzf $FZF_COMPLETION_OPTS -q "$cur") selected=$(grep -v '^\s*\(#\|$\)' /etc/hosts | awk '{print $2}' | sort -u | fzf $FZF_COMPLETION_OPTS -q "$cur")
@@ -112,6 +113,26 @@ _fzf_host_completion() {
fi 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 complete -F _fzf_opts_completion fzf
# Directory # Directory
@@ -141,7 +162,6 @@ done
complete -F _fzf_kill_completion -o nospace -o default -o bashdefault kill complete -F _fzf_kill_completion -o nospace -o default -o bashdefault kill
# Host completion # Host completion
for cmd in "ssh telnet"; do complete -F _fzf_ssh_completion -o default -o bashdefault ssh
complete -F _fzf_host_completion -o default -o bashdefault $cmd complete -F _fzf_telnet_completion -o default -o bashdefault telnet
done

View File

@@ -1,7 +1,7 @@
# coding: utf-8 # coding: utf-8
Gem::Specification.new do |spec| Gem::Specification.new do |spec|
spec.name = 'fzf' spec.name = 'fzf'
spec.version = '0.5.1' spec.version = '0.8.0'
spec.authors = ['Junegunn Choi'] spec.authors = ['Junegunn Choi']
spec.email = ['junegunn.c@gmail.com'] spec.email = ['junegunn.c@gmail.com']
spec.description = %q{Fuzzy finder for your shell} spec.description = %q{Fuzzy finder for your shell}
@@ -12,4 +12,6 @@ Gem::Specification.new do |spec|
spec.bindir = '.' spec.bindir = '.'
spec.files = %w[fzf.gemspec] spec.files = %w[fzf.gemspec]
spec.executables = 'fzf' spec.executables = 'fzf'
spec.add_runtime_dependency 'curses', '~> 1.0.0'
end end

134
install
View File

@@ -10,23 +10,44 @@ if [ $? -ne 0 ]; then
echo "ruby executable not found!" echo "ruby executable not found!"
exit 1 exit 1
fi fi
echo "OK"
# 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
echo "OK ($ruby)"
# Curses-support # Curses-support
echo -n "Checking Curses support ... " echo -n "Checking Curses support ... "
/usr/bin/env ruby -e "begin; require 'curses'; rescue Exception; exit 1; end" "$ruby" -e "$curses_check"
if [ $? -ne 0 ]; then if [ $? -eq 0 ]; then
echo "Your ruby does not support 'curses'" echo "OK"
else
echo "Not found"
echo "Installing 'curses' gem ... "
/usr/bin/env gem install curses -v 1.0.0
if [ $? -ne 0 ]; then
echo "Failed to install 'curses' gem."
echo "Try installing it as root: sudo gem install curses"
exit 1 exit 1
fi
fi fi
echo "OK"
# Ruby version # Ruby version
echo -n "Checking Ruby version ... " echo -n "Checking Ruby version ... "
/usr/bin/env ruby -e 'exit RUBY_VERSION >= "1.9"' "$ruby" -e 'exit RUBY_VERSION >= "1.9"'
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
echo ">= 1.9" echo ">= 1.9"
"$ruby" --disable-gems -e "$curses_check"
if [ $? -eq 0 ]; then
fzf_cmd="$ruby --disable-gems $fzf_base/fzf" fzf_cmd="$ruby --disable-gems $fzf_base/fzf"
else
fzf_cmd="$ruby $fzf_base/fzf"
fi
else else
echo "< 1.9" echo "< 1.9"
fzf_cmd="$ruby $fzf_base/fzf" fzf_cmd="$ruby $fzf_base/fzf"
@@ -38,6 +59,12 @@ echo
[[ ! $REPLY =~ ^[Nn]$ ]] [[ ! $REPLY =~ ^[Nn]$ ]]
auto_completion=$? auto_completion=$?
# Key-bindings
read -p "Do you want to add key bindings? ([y]/n) " -n 1 -r
echo
[[ ! $REPLY =~ ^[Nn]$ ]]
key_bindings=$?
echo echo
for shell in bash zsh; do for shell in bash zsh; do
echo -n "Generate ~/.fzf.$shell ... " echo -n "Generate ~/.fzf.$shell ... "
@@ -49,14 +76,103 @@ for shell in bash zsh; do
fi fi
cat > $src << EOF cat > $src << EOF
# Setup fzf function
# ------------------
unalias fzf 2> /dev/null unalias fzf 2> /dev/null
fzf() { fzf() {
$fzf_cmd "\$@" $fzf_cmd "\$@"
} }
export -f fzf > /dev/null export -f fzf > /dev/null
# Auto-completion
# ---------------
$fzf_completion $fzf_completion
EOF EOF
if [ $key_bindings -eq 0 ]; then
if [ $shell = bash ]; then
cat >> $src << "EOF"
# Key bindings
# ------------
if [[ $- =~ i ]]; then
__fsel() {
find * -path '*/\.*' -prune \
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | fzf -m | while read item; do
printf '%q ' "$item"
done
echo
}
if [ -z "$(set -o | grep '^vi.*on')" ]; then
# Required to refresh the prompt after fzf
bind '"\er": redraw-current-line'
# CTRL-T - Paste the selected file path into the command line
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$(HISTTIMEFORMAT= history | fzf +s | sed \"s/ *[0-9]* *//\")\e\C-e\er"'
else
bind '"\C-x\C-e": shell-expand-line'
bind '"\C-x\C-r": redraw-current-line'
# CTRL-T - Paste the selected file path into the command line
# - FIXME: Selected items are attached to the end regardless of cursor position
bind '"\C-t": "\eddi$(__fsel)\C-x\C-e\e0P$a \C-x\C-r"'
# CTRL-R - Paste the selected command from history into the command line
bind '"\C-r": "\eddi$(HISTTIMEFORMAT= history | fzf +s | sed \"s/ *[0-9]* *//\")\C-x\C-e\e$a\C-x\C-r"'
fi
fi
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 d -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=$(fc -l 1 | fzf +s | sed "s/ *[0-9]* *//")
zle redisplay
}
zle -N fzf-history-widget
bindkey '^R' fzf-history-widget
EOF
fi
fi
echo "OK" echo "OK"
done done
@@ -77,9 +193,9 @@ for shell in bash zsh; do
done done
cat << EOF cat << EOF
Finished. Reload your .bashrc or .zshrc to take effect. Finished. Reload your .bashrc or .zshrc.
source ~/.bashrc # bash" source ~/.bashrc # bash
source ~/.zshrc # zsh" source ~/.zshrc # zsh
To uninstall fzf, simply remove the added line. To uninstall fzf, simply remove the added line.
EOF EOF

View File

@@ -7,43 +7,90 @@ ENV['FZF_EXECUTABLE'] = '0'
load 'fzf' load 'fzf'
class TestFZF < MiniTest::Unit::TestCase 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 def test_default_options
fzf = FZF.new [] fzf = FZF.new []
assert_equal 1000, fzf.sort assert_equal 1000, fzf.sort
assert_equal false, fzf.multi assert_equal false, fzf.multi
assert_equal true, fzf.color assert_equal true, fzf.color
assert_equal Regexp::IGNORECASE, fzf.rxflag assert_equal nil, fzf.rxflag
assert_equal true, fzf.mouse
begin
ENV['FZF_DEFAULT_SORT'] = '1500'
fzf = FZF.new []
assert_equal 1500, fzf.sort
ensure
ENV.delete 'FZF_DEFAULT_SORT'
end 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 +2 --no-mouse -f "goodbye world"'
fzf = FZF.new []
assert_equal 10000, fzf.sort
assert_equal ' hello world ',
fzf.query.get
assert_equal 'goodbye world',
fzf.filter
assert_equal :fuzzy, fzf.extended
assert_equal true, fzf.multi
assert_equal false, fzf.color
assert_equal false, fzf.ansi256
assert_equal false, fzf.mouse
end end
def test_option_parser def test_option_parser
# Long opts # Long opts
fzf = FZF.new %w[--sort=2000 --no-color --multi +i --query hello] fzf = FZF.new %w[--sort=2000 --no-color --multi +i --query hello
--filter=howdy --extended-exact --no-mouse --no-256]
assert_equal 2000, fzf.sort assert_equal 2000, fzf.sort
assert_equal true, fzf.multi assert_equal true, fzf.multi
assert_equal false, fzf.color assert_equal false, fzf.color
assert_equal false, fzf.ansi256
assert_equal false, fzf.mouse
assert_equal 0, fzf.rxflag assert_equal 0, fzf.rxflag
assert_equal 'hello', fzf.query.get assert_equal 'hello', fzf.query.get
assert_equal 'howdy', fzf.filter
assert_equal :exact, fzf.extended
fzf = FZF.new %w[--sort=2000 --no-color --multi +i --query hello
--filter a --filter b --no-256
--no-sort -i --color --no-multi --256]
assert_equal nil, fzf.sort
assert_equal false, fzf.multi
assert_equal true, fzf.color
assert_equal true, fzf.ansi256
assert_equal true, fzf.mouse
assert_equal 1, fzf.rxflag
assert_equal 'b', fzf.filter
assert_equal 'hello', fzf.query.get
assert_equal nil, fzf.extended
# Short opts # Short opts
fzf = FZF.new %w[-s 2000 +c -m +i -qhello] fzf = FZF.new %w[-s 2000 +c -m +i -qhello -x -fhowdy +2]
assert_equal 2000, fzf.sort assert_equal 2000, fzf.sort
assert_equal true, fzf.multi assert_equal true, fzf.multi
assert_equal false, fzf.color assert_equal false, fzf.color
assert_equal false, fzf.ansi256
assert_equal 0, fzf.rxflag assert_equal 0, fzf.rxflag
assert_equal 'hello', fzf.query.get assert_equal 'hello', fzf.query.get
assert_equal 'howdy', fzf.filter
assert_equal :fuzzy, fzf.extended
# Left-to-right # Left-to-right
fzf = FZF.new %w[-qhello -s 2000 --no-sort -q world] fzf = FZF.new %w[-s 2000 +c -m +i -qhello -x -fgoodbye +2
assert_equal nil, fzf.sort -s 3000 -c +m -i -q world +x -fworld -2]
assert_equal 3000, fzf.sort
assert_equal false, fzf.multi
assert_equal true, fzf.color
assert_equal true, fzf.ansi256
assert_equal 1, fzf.rxflag
assert_equal 'world', fzf.query.get assert_equal 'world', fzf.query.get
assert_equal 'world', fzf.filter
assert_equal nil, fzf.extended
fzf = FZF.new %w[--query hello +s -s 2000 --query=world] fzf = FZF.new %w[--query hello +s -s 2000 --query=world]
assert_equal 2000, fzf.sort assert_equal 2000, fzf.sort
@@ -152,15 +199,59 @@ class TestFZF < MiniTest::Unit::TestCase
# TODO : partial_cache # TODO : partial_cache
end 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 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]]]], assert_equal [['Fruit', [[0, 5]]]],
FZF::FuzzyMatcher.new(0).match(%w[Fruit Grapefruit], 'Fruit', '', '').sort FZF::FuzzyMatcher.new(0).match(%w[Fruit Grapefruit], 'Fruit', '', '').sort
# Case-insensitive match (+i)
assert_equal [["Fruit", [[0, 5]]], ["Grapefruit", [[5, 10]]]], assert_equal [["Fruit", [[0, 5]]], ["Grapefruit", [[5, 10]]]],
FZF::FuzzyMatcher.new(Regexp::IGNORECASE). FZF::FuzzyMatcher.new(Regexp::IGNORECASE).
match(%w[Fruit Grapefruit], 'Fruit', '', '').sort match(%w[Fruit Grapefruit], 'Fruit', '', '').sort
end 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 def test_extended_fuzzy_matcher
matcher = FZF::ExtendedFuzzyMatcher.new Regexp::IGNORECASE matcher = FZF::ExtendedFuzzyMatcher.new Regexp::IGNORECASE
list = %w[ list = %w[
@@ -197,6 +288,11 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal list.length, match.call('j', '').length assert_equal list.length, match.call('j', '').length
assert_equal list.length - 1, 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 0, match.call('!j', '').length
@@ -286,6 +382,25 @@ class TestFZF < MiniTest::Unit::TestCase
FZF.new([]).sort_by_rank(xmatcher.match(list, '01 __', '', ''))) FZF.new([]).sort_by_rank(xmatcher.match(list, '01 __', '', '')))
end end
def test_extended_exact_mode
exact = FZF::ExtendedFuzzyMatcher.new Regexp::IGNORECASE, :exact
fuzzy = FZF::ExtendedFuzzyMatcher.new Regexp::IGNORECASE, :fuzzy
list = %w[
extended-exact-mode-not-fuzzy
extended'-fuzzy-mode
]
assert_equal 2, fuzzy.match(list, 'extended', '', '').length
assert_equal 2, fuzzy.match(list, 'mode extended', '', '').length
assert_equal 2, fuzzy.match(list, 'xtndd', '', '').length
assert_equal 2, fuzzy.match(list, "'-fuzzy", '', '').length
assert_equal 2, exact.match(list, 'extended', '', '').length
assert_equal 2, exact.match(list, 'mode extended', '', '').length
assert_equal 0, exact.match(list, 'xtndd', '', '').length
assert_equal 1, exact.match(list, "'-fuzzy", '', '').length
assert_equal 2, exact.match(list, "-fuzzy", '', '').length
end
if RUBY_PLATFORM =~ /darwin/ if RUBY_PLATFORM =~ /darwin/
NFD = '한글' NFD = '한글'
def test_nfc def test_nfc
@@ -332,5 +447,30 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal ["a", "b", "c", "\xFF", "d", "e", "f"], assert_equal ["a", "b", "c", "\xFF", "d", "e", "f"],
FZF::UConv.split("abc\xFFdef") FZF::UConv.split("abc\xFFdef")
end end
# ^$ -> matches empty item
def test_format_empty_item
fzf = FZF.new []
item = ['', [[0, 0]]]
line, offsets = fzf.convert_item item
tokens = fzf.format line, 80, offsets
assert_equal [], tokens
end
def test_mouse_event
interval = FZF::MouseEvent::DOUBLE_CLICK_INTERVAL
me = FZF::MouseEvent.new nil
me.v = 10
assert_equal false, me.double?(10)
assert_equal false, me.double?(20)
me.v = 20
assert_equal false, me.double?(10)
assert_equal false, me.double?(20)
me.v = 20
assert_equal false, me.double?(10)
assert_equal true, me.double?(20)
sleep interval
assert_equal false, me.double?(20)
end
end end