m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-14 22:33:47 -05:00

Compare commits

...

30 Commits
0.8.0 ... 0.8.2

Author SHA1 Message Date
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
9 changed files with 631 additions and 153 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
pkg pkg
Gemfile.lock Gemfile.lock
.DS_Store

200
README.md
View File

@@ -27,7 +27,7 @@ git clone https://github.com/junegunn/fzf.git ~/.fzf
The script will setup: The script will setup:
- `fzf` executable - `fzf` executable
- Key bindings (`CTRL-T`, `CTRL-R`, etc.) - Key bindings (`CTRL-T`, `CTRL-R`, and `ALT-C`) for bash and zsh
- Fuzzy auto-completion for bash - Fuzzy auto-completion for bash
### Install as Vim plugin ### Install as Vim plugin
@@ -53,12 +53,15 @@ usage: fzf [options]
-e, --extended-exact Extended-search mode (exact match) -e, --extended-exact Extended-search mode (exact match)
-q, --query=STR Initial query -q, --query=STR Initial query
-f, --filter=STR Filter mode. Do not start interactive finder. -f, --filter=STR Filter mode. Do not start interactive finder.
-n, --nth=[-]N Match only in the N-th token of the item
-d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style)
-s, --sort=MAX Maximum number of matched items to sort (default: 1000) -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-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 +2, --no-256 Disable 256-color
--black Use black background
--no-mouse Disable mouse --no-mouse Disable mouse
Environment variables Environment variables
@@ -88,7 +91,7 @@ If you want to preserve the exact sequence of the input, provide `--no-sort` (or
history | fzf +s history | fzf +s
``` ```
### Key binding ### Keys
Use CTRL-J and CTRL-K (or CTRL-N and CTRL-P) to change the selection, press 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. enter key to select the item. CTRL-C, CTRL-G, or ESC will terminate the finder.
@@ -97,7 +100,7 @@ The following readline key bindings should also work as expected.
- CTRL-A / CTRL-E - CTRL-A / CTRL-E
- CTRL-B / CTRL-F - CTRL-B / CTRL-F
- CTRL-W / CTRL-U - CTRL-W / CTRL-U / CTRL-Y
- ALT-B / ALT-F - ALT-B / ALT-F
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
@@ -132,17 +135,22 @@ Useful examples
```sh ```sh
# vimf - Open selected file in Vim # vimf - Open selected file in Vim
vimf() { vimf() {
FILE=$(fzf) && vim "$FILE" local file
file=$(fzf --query="$1") && vim "$file"
} }
# fd - cd to selected directory # fd - cd to selected directory
fd() { fd() {
DIR=$(find ${1:-*} -path '*/\.*' -prune -o -type d -print 2> /dev/null | fzf) && cd "$DIR" local dir
dir=$(find ${1:-*} -path '*/\.*' -prune \
-o -type d -print 2> /dev/null | fzf +m) &&
cd "$dir"
} }
# fda - including hidden directories # fda - including hidden directories
fda() { fda() {
DIR=$(find ${1:-.} -type d 2> /dev/null | fzf) && cd "$DIR" local dir
dir=$(find ${1:-.} -type d 2> /dev/null | fzf +m) && cd "$dir"
} }
# fh - repeat history # fh - repeat history
@@ -154,6 +162,53 @@ fh() {
fkill() { fkill() {
ps -ef | sed 1d | fzf -m | awk '{print $2}' | xargs kill -${1:-9} ps -ef | sed 1d | fzf -m | awk '{print $2}' | xargs kill -${1:-9}
} }
# fbr - checkout git branch
fbr() {
local branches branch
branches=$(git branch) &&
branch=$(echo "$branches" | fzf +s +m) &&
git checkout $(echo "$branch" | sed "s/.* //")
}
# fco - checkout git commit
fco() {
local commits commit
commits=$(git log --pretty=oneline --abbrev-commit --reverse) &&
commit=$(echo "$commits" | fzf +s +m -e) &&
git checkout $(echo "$commit" | sed "s/ .*//")
}
# ftags - search ctags
ftags() {
local line
[ -e tags ] &&
line=$(grep -v "^!" tags | cut -f1-3 | cut -c1-80 | fzf --nth=1) &&
$EDITOR $(cut -f2 <<< "$line")
}
# fq1 [QUERY]
# - Immediately select the file when there's only one match.
# If not, start the fuzzy finder as usual.
fq1() {
local lines
lines=$(fzf --filter="$1" --no-sort)
if [ -z "$lines" ]; then
return 1
elif [ $(wc -l <<< "$lines") -eq 1 ]; then
echo "$lines"
else
echo "$lines" | fzf --query="$1"
fi
}
# fe [QUERY]
# - Open the selected file with the default editor
# (Bypass fuzzy finder when there's only one match)
fe() {
local file
file=$(fq1 "$1") && ${EDITOR:-vim} "$file"
}
``` ```
Key bindings for command line Key bindings for command line
@@ -161,20 +216,17 @@ Key bindings for command line
The install script will setup the following key bindings. The install script will setup the following key bindings.
### bash ### 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
The source code can be found in `~/.fzf.bash`.
### zsh
- `CTRL-T` - Paste the selected file path(s) into the command line - `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-R` - Paste the selected command from history into the command line
- `ALT-C` - cd into the selected directory - `ALT-C` - cd into the selected directory
The source code can be found in `~/.fzf.zsh`. If you're on a tmux session, `CTRL-T` will launch fzf in a new split-window. You
may disable this tmux integration by setting `FZF_TMUX` to 0, or change the
height of the window with `FZF_TMUX_HEIGHT` (e.g. `20`, `50%`).
The source code can be found in `~/.fzf.bash` and in `~/.fzf.zsh`.
Auto-completion Auto-completion
--------------- ---------------
@@ -252,7 +304,11 @@ TODO :smiley:
Usage as Vim plugin Usage as Vim plugin
------------------- -------------------
If you install fzf as a Vim plugin, `:FZF` command will be added. (fzf is a command-line utility, naturally it is only accessible in terminal Vim)
### `:FZF[!]`
If you have set up fzf for Vim, `:FZF` command will be added.
```vim ```vim
" Look for files under current directory " Look for files under current directory
@@ -265,59 +321,101 @@ If you install fzf as a Vim plugin, `:FZF` command will be added.
:FZF --no-sort -m /tmp :FZF --no-sort -m /tmp
``` ```
You can override the source command which produces input to fzf. Note that the environment variables `FZF_DEFAULT_COMMAND` and `FZF_DEFAULT_OPTS`
also apply here.
If you're on a tmux session, `:FZF` will launch fzf in a new split-window whose
height can be adjusted with `g:fzf_tmux_height` (default: '40%'). However, the
bang version (`:FZF!`) will always start in fullscreen.
### `fzf#run([options])`
For more advanced uses, you can call `fzf#run()` function which returns the list
of the selected items.
`fzf#run()` may take an options-dictionary:
| Option name | Type | Description |
| ----------- | ------------- | ------------------------------------------------------------------- |
| `source` | string | External command to generate input to fzf (e.g. `find .`) |
| `source` | list | Vim list as input to fzf |
| `sink` | string | Vim command to handle the selected item (e.g. `e`, `tabe`) |
| `sink` | funcref | Reference to function to process each selected item |
| `options` | string | Options to fzf |
| `dir` | string | Working directory |
| `tmux` | number/string | Use tmux split if possible with the given height (e.g. `20`, `50%`) |
#### Examples
If `sink` option is not given, `fzf#run` will simply return the list.
```vim ```vim
let g:fzf_source = 'find . -type f' let items = fzf#run({ 'options': '-m +c', 'dir': '~', 'source': 'ls' })
``` ```
And you can predefine default options to fzf command. But if `sink` is given as a string, the command will be executed for each
selected item.
```vim ```vim
let g:fzf_options = '--no-color --extended' " Each selected item will be opened in a new tab
let items = fzf#run({ 'sink': 'tabe', 'options': '-m +c', 'dir': '~', 'source': 'ls' })
``` ```
For more advanced uses, you can call `fzf#run` function as follows. We can also use a Vim list as the source as follows:
```vim ```vim
:call fzf#run('tabedit', '-m +c') " Choose a color scheme with fzf
nnoremap <silent> <Leader>C :call fzf#run({
\ 'source':
\ map(split(globpath(&rtp, "colors/*.vim"), "\n"),
\ "substitute(fnamemodify(v:val, ':t'), '\\..\\{-}$', '', '')"),
\ 'sink': 'colo',
\ 'options': '+m',
\ 'tmux': 15
\ })<CR>
``` ```
Most of the time, you will prefer native Vim plugins with better integration `sink` option can be a function reference. The following example creates a
with Vim. The only reason one might consider using fzf in Vim is its speed. For handy mapping that selects an open buffer.
a very large list of files, fzf is significantly faster and it does not block.
```vim
" List of buffers
function! g:buflist()
redir => ls
silent ls
redir END
return split(ls, '\n')
endfunction
function! g:bufopen(e)
execute 'buffer '. matchstr(a:e, '^[ 0-9]*')
endfunction
nnoremap <silent> <Leader><Enter> :call fzf#run({
\ 'source': g:buflist(),
\ 'sink': function('g:bufopen'),
\ 'options': '+m +s',
\ 'tmux': 15
\ })<CR>
```
Tips 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 If you have any rendering issues, check the followings:
`--disable-gems` option to Ruby.
- `time ruby ~/bin/fzf -h` 1. Make sure `$TERM` is correctly set. fzf will use 256-color only if it
- 0.077 sec contains `256` (e.g. `xterm-256color`)
- `time ruby --disable-gems ~/bin/fzf -h` 2. If you're on screen or tmux, `$TERM` should be either `screen` or
- 0.025 sec `screen-256color`
3. Some terminal emulators (e.g. mintty) have problem displaying default
You can define fzf function with the option as follows: 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
```sh `FZF_DEFAULT_OPTS` for further convenience.
fzf() { 4. If you still have problem, try `--no-256` option or even `--no-color`.
ruby --disable-gems ~/bin/fzf "$@" 5. Ruby 1.9 or above is required for correctly displaying unicode characters.
}
export -f fzf
```
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 ### Ranking algorithm

9
ext/mkrf_conf.rb Normal file
View File

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

172
fzf
View File

@@ -7,7 +7,7 @@
# / __/ / /_/ __/ # / __/ / /_/ __/
# /_/ /___/_/ Fuzzy finder for your shell # /_/ /___/_/ Fuzzy finder for your shell
# #
# Version: 0.8.0 (March 8, 2014) # Version: 0.8.2 (March 30, 2014)
# #
# Author: Junegunn Choi # Author: Junegunn Choi
# URL: https://github.com/junegunn/fzf # URL: https://github.com/junegunn/fzf
@@ -50,7 +50,8 @@ end
class FZF class FZF
C = Curses C = Curses
attr_reader :rxflag, :sort, :color, :ansi256, :mouse, :multi, :query, :filter, :extended attr_reader :rxflag, :sort, :nth, :color, :black, :ansi256,
:mouse, :multi, :query, :filter, :extended
class AtomicVar class AtomicVar
def initialize value def initialize value
@@ -78,10 +79,13 @@ class FZF
@sort = ENV.fetch('FZF_DEFAULT_SORT', 1000).to_i @sort = ENV.fetch('FZF_DEFAULT_SORT', 1000).to_i
@color = true @color = true
@ansi256 = true @ansi256 = true
@black = false
@multi = false @multi = false
@mouse = true @mouse = true
@extended = nil @extended = nil
@filter = nil @filter = nil
@nth = nil
@delim = nil
argv = argv =
if opts = ENV['FZF_DEFAULT_OPTS'] if opts = ENV['FZF_DEFAULT_OPTS']
@@ -104,6 +108,8 @@ class FZF
when '+c', '--no-color' then @color = false when '+c', '--no-color' then @color = false
when '-2', '--256' then @ansi256 = true when '-2', '--256' then @ansi256 = true
when '+2', '--no-256' then @ansi256 = false when '+2', '--no-256' then @ansi256 = false
when '--black' then @black = true
when '--no-black' then @black = false
when '--mouse' then @mouse = true when '--mouse' then @mouse = true
when '--no-mouse' then @mouse = false when '--no-mouse' then @mouse = false
when '+s', '--no-sort' then @sort = nil when '+s', '--no-sort' then @sort = nil
@@ -117,6 +123,17 @@ class FZF
@filter = query @filter = query
when /^-f(.*)$/, /^--filter=(.*)$/ when /^-f(.*)$/, /^--filter=(.*)$/
@filter = $1 @filter = $1
when '-n', '--nth'
usage 1, 'field number required' unless nth = argv.shift
usage 1, 'invalid field number' if nth.to_i == 0
@nth = nth.to_i
when /^-n(-?[1-9][0-9]*)$/, /^--nth=(-?[1-9][0-9]*)$/
@nth = $1.to_i
when '-d', '--delimiter'
usage 1, 'delimiter required' unless delim = argv.shift
@delim = FZF.build_delim_regex delim
when /^-d(.+)$/, /^--delimiter=(.+)$/
@delim = FZF.build_delim_regex $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]+$/
@@ -152,6 +169,11 @@ class FZF
end end
end end
def FZF.build_delim_regex delim
Regexp.compile(delim) rescue (delim = Regexp.escape(delim))
Regexp.compile "(?:.*?#{delim})|(?:.+?$)"
end
def start def start
if @filter if @filter
start_reader(false).join start_reader(false).join
@@ -178,9 +200,9 @@ class FZF
def get_matcher def get_matcher
if @extended if @extended
ExtendedFuzzyMatcher.new @rxflag, @extended ExtendedFuzzyMatcher.new @rxflag, @extended, @nth, @delim
else else
FuzzyMatcher.new @rxflag FuzzyMatcher.new @rxflag, @nth, @delim
end end
end end
@@ -205,12 +227,15 @@ class FZF
-e, --extended-exact Extended-search mode (exact match) -e, --extended-exact Extended-search mode (exact match)
-q, --query=STR Initial query -q, --query=STR Initial query
-f, --filter=STR Filter mode. Do not start interactive finder. -f, --filter=STR Filter mode. Do not start interactive finder.
-n, --nth=[-]N Match only in the N-th token of the item
-d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style)
-s, --sort=MAX Maximum number of matched items to sort (default: 1000) -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-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 +2, --no-256 Disable 256-color
--black Use black background
--no-mouse Disable mouse --no-mouse Disable mouse
Environment variables Environment variables
@@ -517,7 +542,7 @@ class FZF
C.mousemask C::ALL_MOUSE_EVENTS if @mouse C.mousemask C::ALL_MOUSE_EVENTS if @mouse
C.start_color C.start_color
dbg = dbg =
if C.respond_to?(:use_default_colors) if !@black && C.respond_to?(:use_default_colors)
C.use_default_colors C.use_default_colors
-1 -1
else else
@@ -900,8 +925,10 @@ class FZF
def start_loop def start_loop
got = nil got = nil
begin begin
input = @query.get.dup input = @query.get.dup
cursor = input.length cursor = input.length
yanked = ''
mouse_event = MouseEvent.new
backword = proc { backword = proc {
cursor = (input[0, cursor].rindex(/\s\S/) || -1) + 1 cursor = (input[0, cursor].rindex(/\s\S/) || -1) + 1
} }
@@ -912,7 +939,11 @@ class FZF
got = pick got = pick
exit 0 exit 0
}, },
ctrl(:u) => proc { input = input[cursor..-1]; cursor = 0 }, ctrl(:u) => proc {
yanked = input[0...cursor] if cursor > 0
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 { vselect { |v| v - 1 } }, ctrl(:j) => proc { vselect { |v| v - 1 } },
@@ -920,8 +951,10 @@ class FZF
ctrl(:w) => proc { ctrl(:w) => proc {
pcursor = cursor pcursor = cursor
backword.call backword.call
yanked = input[cursor...pcursor] if pcursor > cursor
input = input[0...cursor] + input[pcursor..-1] input = input[0...cursor] + input[pcursor..-1]
}, },
ctrl(:y) => proc { actions[:default].call yanked },
ctrl(:h) => proc { input[cursor -= 1] = '' if cursor > 0 }, ctrl(:h) => proc { input[cursor -= 1] = '' if cursor > 0 },
ctrl(:i) => proc { |o| ctrl(:i) => proc { |o|
if @multi && sel = pick if @multi && sel = pick
@@ -948,6 +981,39 @@ class FZF
cursor += (input[cursor..-1].index(/(\S\s)|(.$)/) || -1) + 1 cursor += (input[cursor..-1].index(/(\S\s)|(.$)/) || -1) + 1
nil nil
}, },
:default => proc { |val|
case val
when String
input.insert cursor, val
cursor += val.length
when Hash
event = val[:event]
case event
when :click, :release
x, y, shift = val.values_at :x, :y, :shift
if y == cursor_y
cursor = [0, [input.length, x - 2].min].max
elsif x > 1 && y <= max_items
tv = max_items - y - 1
case event
when :click
vselect { |_| tv }
actions[ctrl(:i)].call(:sclick) if shift
mouse_event.v = tv
when :release
if !shift && mouse_event.double?(tv)
actions[ctrl(:m)].call
end
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
}
} }
actions[ctrl(:p)] = actions[ctrl(:k)] actions[ctrl(:p)] = actions[ctrl(:k)]
actions[ctrl(:n)] = actions[ctrl(:j)] actions[ctrl(:n)] = actions[ctrl(:j)]
@@ -956,45 +1022,12 @@ class FZF
actions[ctrl(:q)] = actions[ctrl(:g)] = actions[ctrl(:c)] = actions[:esc] 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 }
if key = get_input(actions) if key = get_input(actions)
upd = actions.fetch(key, proc { |val| upd = actions.fetch(key, actions[:default]).call(key)
case val
when String
input.insert cursor, val
cursor += val.length
when Hash
event = val[:event]
case event
when :click, :release
x, y, shift = val.values_at :x, :y, :shift
if y == cursor_y
cursor = [0, [input.length, x - 2].min].max
elsif x > 1 && y <= max_items
tv = max_items - y - 1
case event
when :click
vselect { |_| tv }
actions[ctrl(:i)].call(:sclick) if shift
mouse.v = tv
when :release
if !shift && mouse.double?(tv)
actions[ctrl(:m)].call
end
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
@@ -1014,10 +1047,55 @@ class FZF
end end
end end
class Matcher
class MatchData
def initialize n
@n = n
end
def offset _
@n
end
end
def initialize nth, delim
@nth = nth && (nth > 0 ? nth - 1 : nth)
@delim = delim
@tokens_cache = {}
end
def tokenize str
@tokens_cache[str] ||=
unless @delim
# AWK default
prefix_length = str[/^\s+/].length rescue 0
[prefix_length, (str.strip.scan(/\S+\s*/) rescue [])]
else
prefix_length = 0
[prefix_length, (str.scan(@delim) rescue [])]
end
end
def do_match str, pat
if @nth
prefix_length, tokens = tokenize str
if (token = tokens[@nth]) && (md = token.match(pat) rescue nil)
prefix_length += (tokens[0...@nth] || []).join.length
offset = md.offset(0).map { |o| o + prefix_length }
MatchData.new offset
end
else
str.match(pat) rescue nil
end
end
end
class FuzzyMatcher < Matcher class FuzzyMatcher < Matcher
attr_reader :caches, :rxflag attr_reader :caches, :rxflag
def initialize rxflag def initialize rxflag, nth = nil, delim = nil
super nth, delim
@caches = Hash.new { |h, k| h[k] = {} } @caches = Hash.new { |h, k| h[k] = {} }
@regexp = {} @regexp = {}
@rxflag = rxflag @rxflag = rxflag
@@ -1061,15 +1139,15 @@ class FZF
cache[q] ||= (partial_cache ? cache[q] ||= (partial_cache ?
partial_cache.map { |e| e.first } : list).map { |line| partial_cache.map { |e| e.first } : list).map { |line|
# Ignore errors: e.g. invalid byte sequence in UTF-8 # Ignore errors: e.g. invalid byte sequence in UTF-8
md = line.match(regexp) rescue nil md = do_match(line, regexp)
md && [line, [md.offset(0)]] md && [line, [md.offset(0)]]
}.compact }.compact
end end
end end
class ExtendedFuzzyMatcher < FuzzyMatcher class ExtendedFuzzyMatcher < FuzzyMatcher
def initialize rxflag, mode = :fuzzy def initialize rxflag, mode = :fuzzy, nth = nil, delim = nil
super rxflag super rxflag, nth, delim
@regexps = {} @regexps = {}
@mode = mode @mode = mode
end end
@@ -1131,7 +1209,7 @@ class FZF
offsets = [] offsets = []
regexps.all? { |pair| regexps.all? { |pair|
regexp, invert = pair regexp, invert = pair
md = line.match(regexp) rescue nil md = do_match(line, regexp)
if md && !invert if md && !invert
offsets << md.offset(0) offsets << md.offset(0)
elsif !md && invert elsif !md && invert

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.8.0' spec.version = '0.8.2'
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}
@@ -13,5 +13,5 @@ Gem::Specification.new do |spec|
spec.files = %w[fzf.gemspec] spec.files = %w[fzf.gemspec]
spec.executables = 'fzf' spec.executables = 'fzf'
spec.add_runtime_dependency 'curses', '~> 1.0.0' spec.extensions += ['ext/mkrf_conf.rb']
end end

104
install
View File

@@ -29,10 +29,13 @@ if [ $? -eq 0 ]; then
else else
echo "Not found" echo "Not found"
echo "Installing 'curses' gem ... " echo "Installing 'curses' gem ... "
/usr/bin/env gem install curses -v 1.0.0 /usr/bin/env gem install curses -v 1.0.0 --user-install
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo
echo "Failed to install 'curses' gem." echo "Failed to install 'curses' gem."
echo "Try installing it as root: sudo gem install curses" if [[ $(uname -r) =~ 'ARCH' ]]; then
echo "Make sure that base-devel package group is installed."
fi
exit 1 exit 1
fi fi
fi fi
@@ -92,12 +95,12 @@ EOF
if [ $key_bindings -eq 0 ]; then if [ $key_bindings -eq 0 ]; then
if [ $shell = bash ]; then if [ $shell = bash ]; then
cat >> $src << "EOF" cat >> $src << "EOFZF"
# Key bindings # Key bindings
# ------------ # ------------
if [[ $- =~ i ]]; then if [[ $- =~ i ]]; then
__fsel() { read -r -d '' __fsel <<'EOF'
find * -path '*/\.*' -prune \ find * -path '*/\.*' -prune \
-o -type f -print \ -o -type f -print \
-o -type d -print \ -o -type d -print \
@@ -105,50 +108,102 @@ __fsel() {
printf '%q ' "$item" printf '%q ' "$item"
done done
echo echo
EOF
__fsel() {
eval "$__fsel"
} }
__fsel_tmux() {
local height lines
height=${FZF_TMUX_HEIGHT:-40%}
lines=${LINES:-40}
if [[ $height =~ %$ ]]; then
height=${height:0:${#height}-1}
height=$(( height * lines / 100 ))
fi
tmux split-window -l $height "tmux send-keys -t $TMUX_PANE \"\$($__fsel)\""
}
__fcd() {
local dir
dir=$(find ${1:-*} -path '*/\.*' -prune -o -type d -print 2> /dev/null | fzf +m) && printf 'cd %q' "$dir"
}
__use_tmux=0
[ -n "$TMUX_PANE" -a ${FZF_TMUX:-1} -ne 0 -a ${LINES:-40} -gt 15 ] && __use_tmux=1
if [ -z "$(set -o | grep '^vi.*on')" ]; then if [ -z "$(set -o | grep '^vi.*on')" ]; then
# Required to refresh the prompt after fzf # Required to refresh the prompt after fzf
bind '"\er": redraw-current-line' bind '"\er": redraw-current-line'
# CTRL-T - Paste the selected file path into the command 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"' if [ $__use_tmux -eq 1 ]; then
bind '"\C-t": " \C-u \C-a\C-k$(__fsel_tmux)\e\C-e\C-y\C-a\C-d\C-y\ey\C-h"'
else
bind '"\C-t": " \C-u \C-a\C-k$(__fsel)\e\C-e\C-y\C-a\C-y\ey\C-h\C-e\er"'
fi
# CTRL-R - Paste the selected command from history into the command line # 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"' bind '"\C-r": " \C-e\C-u$(HISTTIMEFORMAT= history | fzf +s | sed \"s/ *[0-9]* *//\")\e\C-e\er"'
# ALT-C - cd into the selected directory
bind '"\ec": " \C-e\C-u$(__fcd)\e\C-e\er\C-m"'
else else
bind '"\C-x\C-e": shell-expand-line' bind '"\C-x\C-e": shell-expand-line'
bind '"\C-x\C-r": redraw-current-line' bind '"\C-x\C-r": redraw-current-line'
# CTRL-T - Paste the selected file path into the command line # CTRL-T - Paste the selected file path into the command line
# - FIXME: Selected items are attached to the end regardless of cursor position # - 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"' if [ $__use_tmux -eq 1 ]; then
bind '"\C-t": "\e$a \eddi$(__fsel_tmux)\C-x\C-e\e0P$xa"'
else
bind '"\C-t": "\e$a \eddi$(__fsel)\C-x\C-e\e0Px$a \C-x\C-r"'
fi
# CTRL-R - Paste the selected command from history into the command line # 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"' bind '"\C-r": "\eddi$(HISTTIMEFORMAT= history | fzf +s | sed \"s/ *[0-9]* *//\")\C-x\C-e\e$a\C-x\C-r"'
# ALT-C - cd into the selected directory
bind '"\ec": "\eddi$(__fcd)\C-x\C-e\C-x\C-r\C-m"'
fi fi
unset __use_tmux
fi fi
EOF EOFZF
else else
cat >> $src << "EOF" cat >> $src << "EOFZF"
# Key bindings # Key bindings
# ------------ # ------------
# CTRL-T - Paste the selected file path(s) into the command line # CTRL-T - Paste the selected file path(s) into the command line
fzf-file-widget() { read -r -d '' __fsel <<'EOF'
local FILES find * -path '*/\.*' -prune \
local IFS="
"
FILES=($(
find * -path '*/\.*' -prune \
-o -type f -print \ -o -type f -print \
-o -type d -print \ -o -type d -print \
-o -type l -print 2> /dev/null | fzf -m)) -o -type l -print 2> /dev/null | fzf -m | while read item; do
unset IFS printf '%q ' "$item"
FILES=$FILES:q done
LBUFFER="${LBUFFER%% #} $FILES" echo
zle redisplay EOF
}
if [ -n "$TMUX_PANE" -a ${FZF_TMUX:-1} -ne 0 -a ${LINES:-40} -gt 15 ]; then
fzf-file-widget() {
local height lines
height=${FZF_TMUX_HEIGHT:-40%}
lines=${LINES:-40}
if [[ $height =~ %$ ]]; then
height=${height:0:${#height}-1}
height=$(( height * lines / 100 ))
fi
tmux split-window -l $height "tmux send-keys -t $TMUX_PANE \"\$($__fsel)\""
}
else
fzf-file-widget() {
LBUFFER="${LBUFFER%% #}$(eval "$__fsel")"
zle redisplay
}
fi
zle -N fzf-file-widget zle -N fzf-file-widget
bindkey '^T' fzf-file-widget bindkey '^T' fzf-file-widget
@@ -169,7 +224,7 @@ fzf-history-widget() {
zle -N fzf-history-widget zle -N fzf-history-widget
bindkey '^R' fzf-history-widget bindkey '^R' fzf-history-widget
EOF EOFZF
fi fi
fi fi
@@ -183,8 +238,9 @@ for shell in bash zsh; do
echo "Update $rc:" echo "Update $rc:"
echo " - $src" echo " - $src"
if [ $(grep -F "$src" $rc | wc -l) -gt 0 ]; then line=$(grep -nF "$src" $rc | sed 's/:.*//')
echo " - Not added (already being sourced)" if [ -n "$line" ]; then
echo " - Already exists (line #$line)"
else else
echo $src >> $rc echo $src >> $rc
echo " - Added" echo " - Added"
@@ -198,5 +254,7 @@ Finished. Reload your .bashrc or .zshrc.
source ~/.zshrc # zsh source ~/.zshrc # zsh
To uninstall fzf, simply remove the added line. To uninstall fzf, simply remove the added line.
For more information, see: https://github.com/junegunn/fzf
EOF EOF

View File

@@ -1,4 +1,4 @@
" Copyright (c) 2013 Junegunn Choi " Copyright (c) 2014 Junegunn Choi
" "
" MIT License " MIT License
" "
@@ -21,39 +21,178 @@
" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION " OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. " WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
let s:exec = expand('<sfile>:h:h').'/fzf' let s:min_tmux_height = 3
let s:default_tmux_height = '40%'
let s:cpo_save = &cpo
set cpo&vim
call system('type fzf')
if v:shell_error
let s:fzf_rb = expand('<sfile>:h:h').'/fzf'
if executable(s:fzf_rb)
let s:exec = s:fzf_rb
else
echoerr 'fzf executable not found'
finish
endif
else
let s:exec = 'fzf'
endif
function! s:shellesc(arg)
return '"'.substitute(a:arg, '"', '\\"', 'g').'"'
endfunction
function! s:escape(path) function! s:escape(path)
return substitute(a:path, ' ', '\\ ', 'g') return substitute(a:path, ' ', '\\ ', 'g')
endfunction endfunction
function! fzf#run(command, ...) function! fzf#run(...) abort
let cwd = getcwd() if has('gui_running')
try echohl Error
let args = copy(a:000) echo 'GVim is not supported'
if len(args) > 0 && isdirectory(expand(args[-1])) return []
let dir = remove(args, -1) endif
execute 'chdir '.s:escape(dir) let dict = exists('a:1') ? a:1 : {}
let temps = { 'result': tempname() }
let optstr = get(dict, 'options', '')
if has_key(dict, 'source')
let source = dict.source
let type = type(source)
if type == 1
let prefix = source.'|'
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 endif
let argstr = join(args) else
let tf = tempname() let prefix = ''
let prefix = exists('g:fzf_source') ? g:fzf_source.'|' : '' endif
let fzf = executable(s:exec) ? s:exec : 'fzf' let command = prefix.s:exec.' '.optstr.' > '.temps.result
let options = empty(argstr) ? get(g:, 'fzf_options', '') : argstr
execute "silent !".prefix.fzf.' '.options." > ".tf if exists('$TMUX') && has_key(dict, 'tmux') &&
if !v:shell_error \ dict.tmux > 0 && winheight(0) >= s:min_tmux_height
for line in readfile(tf) return s:execute_tmux(dict, command, temps)
if !empty(line) else
execute a:command.' '.s:escape(line) return s:execute(dict, command, temps)
endif
endfunction
function! s:pushd(dict)
if has_key(a:dict, 'dir')
let a:dict.prev_dir = getcwd()
execute 'chdir '.s:escape(a:dict.dir)
endif
endfunction
function! s:popd(dict)
if has_key(a:dict, 'prev_dir')
execute 'chdir '.s:escape(remove(a:dict, 'prev_dir'))
endif
endfunction
function! s:execute(dict, command, temps)
call s:pushd(a:dict)
silent !clear
execute 'silent !'.a:command
redraw!
if v:shell_error
return []
else
return s:callback(a:dict, a:temps, 0)
endif
endfunction
function! s:execute_tmux(dict, command, temps)
if has_key(a:dict, 'dir')
let command = 'cd '.s:escape(a:dict.dir).' && '.a:command
else
let command = a:command
endif
if type(a:dict.tmux) == 1
if a:dict.tmux =~ '%$'
let height = screenrow() * str2nr(a:dict.tmux[0:-2]) / 100
else
let height = str2nr(a:dict.tmux)
endif
else
let height = a:dict.tmux
endif
let s:pane = substitute(
\ system(
\ printf(
\ 'tmux split-window -l %d -P -F "#{pane_id}" %s',
\ height, s:shellesc(command))), '\n', '', 'g')
let s:dict = a:dict
let s:temps = a:temps
augroup fzf_tmux
autocmd!
autocmd VimResized * nested call s:tmux_check()
augroup END
endfunction
function! s:tmux_check()
let panes = split(system('tmux list-panes -a -F "#{pane_id}"'), '\n')
if index(panes, s:pane) < 0
augroup fzf_tmux
autocmd!
augroup END
call s:callback(s:dict, s:temps, 1)
redraw
endif
endfunction
function! s:callback(dict, temps, cd)
if !filereadable(a:temps.result)
let lines = []
else
if a:cd | call s:pushd(a:dict) | endif
let lines = readfile(a:temps.result)
if has_key(a:dict, 'sink')
for line in lines
if type(a:dict.sink) == 2
call a:dict.sink(line)
else
execute a:dict.sink.' '.s:escape(line)
endif endif
endfor endfor
endif endif
finally endif
execute 'chdir '.s:escape(cwd)
redraw! for tf in values(a:temps)
silent! call delete(tf) silent! call delete(tf)
endtry endfor
call s:popd(a:dict)
return lines
endfunction endfunction
command! -nargs=* -complete=dir FZF call fzf#run('silent e', <f-args>) function! s:cmd(bang, ...) abort
let args = copy(a:000)
let opts = {}
if len(args) > 0 && isdirectory(expand(args[-1]))
let opts.dir = remove(args, -1)
endif
if !a:bang
let opts.tmux = get(g:, 'fzf_tmux_height', s:default_tmux_height)
endif
call fzf#run(extend({ 'sink': 'e', 'options': join(args) }, opts))
endfunction
command! -nargs=* -complete=dir -bang FZF call s:cmd('<bang>' == '!', <f-args>)
let &cpo = s:cpo_save
unlet s:cpo_save

37
test/fzf.vader Normal file
View File

@@ -0,0 +1,37 @@
Execute (Setup):
let g:dir = fnamemodify(g:vader_file, ':p:h')
Log 'Test directory: ' . g:dir
Execute (fzf#run with dir option):
let result = fzf#run({ 'options': '--filter=vdr', 'dir': g:dir })
AssertEqual ['fzf.vader'], result
let result = sort(fzf#run({ 'options': '--filter e', 'dir': g:dir }))
AssertEqual ['fzf.vader', 'test_fzf.rb'], result
Execute (fzf#run with Funcref command):
let g:ret = []
function! g:proc(e)
call add(g:ret, a:e)
endfunction
let result = sort(fzf#run({ 'sink': function('g:proc'), 'options': '--filter e', 'dir': g:dir }))
AssertEqual ['fzf.vader', 'test_fzf.rb'], result
AssertEqual ['fzf.vader', 'test_fzf.rb'], sort(g:ret)
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 (Cleanup):
unlet g:dir
Restore

View File

@@ -27,8 +27,10 @@ class TestFZF < MiniTest::Unit::TestCase
ENV['FZF_DEFAULT_SORT'] = '20000' ENV['FZF_DEFAULT_SORT'] = '20000'
fzf = FZF.new [] fzf = FZF.new []
assert_equal 20000, fzf.sort assert_equal 20000, fzf.sort
assert_equal nil, fzf.nth
ENV['FZF_DEFAULT_OPTS'] = '-x -m -s 10000 -q " hello world " +c +2 --no-mouse -f "goodbye world"' ENV['FZF_DEFAULT_OPTS'] =
'-x -m -s 10000 -q " hello world " +c +2 --no-mouse -f "goodbye world" --black --nth=3'
fzf = FZF.new [] fzf = FZF.new []
assert_equal 10000, fzf.sort assert_equal 10000, fzf.sort
assert_equal ' hello world ', assert_equal ' hello world ',
@@ -39,38 +41,44 @@ class TestFZF < MiniTest::Unit::TestCase
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.ansi256
assert_equal true, fzf.black
assert_equal false, fzf.mouse assert_equal false, fzf.mouse
assert_equal 3, fzf.nth
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] --filter=howdy --extended-exact --no-mouse --no-256 --nth=1]
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.ansi256
assert_equal false, fzf.black
assert_equal false, fzf.mouse 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 'howdy', fzf.filter
assert_equal :exact, fzf.extended assert_equal :exact, fzf.extended
assert_equal 1, fzf.nth
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 a --filter b --no-256 --filter a --filter b --no-256 --black --nth 2
--no-sort -i --color --no-multi --256] --no-sort -i --color --no-multi --256]
assert_equal nil, fzf.sort assert_equal nil, fzf.sort
assert_equal false, fzf.multi assert_equal false, fzf.multi
assert_equal true, fzf.color assert_equal true, fzf.color
assert_equal true, fzf.ansi256 assert_equal true, fzf.ansi256
assert_equal true, fzf.black
assert_equal true, fzf.mouse assert_equal true, fzf.mouse
assert_equal 1, fzf.rxflag assert_equal 1, fzf.rxflag
assert_equal 'b', fzf.filter assert_equal 'b', fzf.filter
assert_equal 'hello', fzf.query.get assert_equal 'hello', fzf.query.get
assert_equal nil, fzf.extended assert_equal nil, fzf.extended
assert_equal 2, fzf.nth
# Short opts # Short opts
fzf = FZF.new %w[-s 2000 +c -m +i -qhello -x -fhowdy +2] fzf = FZF.new %w[-s 2000 +c -m +i -qhello -x -fhowdy +2 -n3]
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
@@ -79,18 +87,21 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal 'hello', fzf.query.get assert_equal 'hello', fzf.query.get
assert_equal 'howdy', fzf.filter assert_equal 'howdy', fzf.filter
assert_equal :fuzzy, fzf.extended assert_equal :fuzzy, fzf.extended
assert_equal 3, fzf.nth
# Left-to-right # Left-to-right
fzf = FZF.new %w[-s 2000 +c -m +i -qhello -x -fgoodbye +2 fzf = FZF.new %w[-s 2000 +c -m +i -qhello -x -fgoodbye +2 -n3 -n4
-s 3000 -c +m -i -q world +x -fworld -2] -s 3000 -c +m -i -q world +x -fworld -2 --black --no-black]
assert_equal 3000, fzf.sort assert_equal 3000, fzf.sort
assert_equal false, fzf.multi assert_equal false, fzf.multi
assert_equal true, fzf.color assert_equal true, fzf.color
assert_equal true, fzf.ansi256 assert_equal true, fzf.ansi256
assert_equal false, fzf.black
assert_equal 1, fzf.rxflag assert_equal 1, fzf.rxflag
assert_equal 'world', fzf.query.get assert_equal 'world', fzf.query.get
assert_equal 'world', fzf.filter assert_equal 'world', fzf.filter
assert_equal nil, fzf.extended assert_equal nil, fzf.extended
assert_equal 4, fzf.nth
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
@@ -105,6 +116,12 @@ class TestFZF < MiniTest::Unit::TestCase
fzf = FZF.new argv fzf = FZF.new argv
end end
end end
assert_raises(SystemExit) do
fzf = FZF.new %w[--nth=0]
end
assert_raises(SystemExit) do
fzf = FZF.new %w[-n 0]
end
end end
# FIXME Only on 1.9 or above # FIXME Only on 1.9 or above
@@ -472,5 +489,46 @@ class TestFZF < MiniTest::Unit::TestCase
sleep interval sleep interval
assert_equal false, me.double?(20) assert_equal false, me.double?(20)
end end
def test_nth_match
list = [
' first second third',
'fourth fifth sixth',
]
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE
assert_equal list, matcher.match(list, 'f', '', '').map(&:first)
assert_equal [
[list[0], [[2, 5]]],
[list[1], [[9, 17]]]], matcher.match(list, 'is', '', '')
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, 2
assert_equal [[list[1], [[8, 9]]]], matcher.match(list, 'f', '', '')
assert_equal [[list[0], [[8, 9]]]], matcher.match(list, 's', '', '')
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, 3
assert_equal [[list[0], [[19, 20]]]], matcher.match(list, 'r', '', '')
regex = FZF.build_delim_regex "\t"
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, 1, regex
assert_equal [[list[0], [[3, 10]]]], matcher.match(list, 're', '', '')
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, 2, regex
assert_equal [], matcher.match(list, 'r', '', '')
assert_equal [[list[1], [[9, 17]]]], matcher.match(list, 'is', '', '')
# Negative indexing
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, -1, regex
assert_equal [[list[0], [[3, 6]]]], matcher.match(list, 'rt', '', '')
assert_equal [[list[0], [[2, 5]]], [list[1], [[9, 17]]]], matcher.match(list, 'is', '', '')
# Regex delimiter
regex = FZF.build_delim_regex "[ \t]+"
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, 1, regex
assert_equal [list[1]], matcher.match(list, 'f', '', '').map(&:first)
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, 2, regex
assert_equal [[list[0], [[1, 2]]], [list[1], [[8, 9]]]], matcher.match(list, 'f', '', '')
end
end end