Compare commits

...

50 Commits
0.5.0 ... 0.7.1

Author SHA1 Message Date
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
Junegunn Choi
04ebaddf5e 0.5.1 2013-12-08 02:09:50 +09:00
Junegunn Choi
45e1f1ae57 Last update: December 5, 2013 2013-12-05 13:29:52 +09:00
Junegunn Choi
c1d5f7cef7 Do not use 256-color if not supported (#8) 2013-12-05 12:17:21 +09:00
Junegunn Choi
df663c4e41 Improve bash completion
- kill completion: do not even start fzf on non-empty word
- host completion: start fzf with initial query
2013-11-29 23:42:00 +09:00
Junegunn Choi
d3742782f3 Fix a typo in README 2013-11-29 18:09:51 +09:00
Junegunn Choi
faff17b2a9 Hostname completion for ssh and telnet commands 2013-11-29 18:08:22 +09:00
Junegunn Choi
9a3cddc92e Apply FZF_COMPLETION_OPTS to kill completion 2013-11-29 17:53:30 +09:00
Junegunn Choi
bd2763d863 Add bash completion for kill command 2013-11-29 17:50:53 +09:00
Junegunn Choi
b2bb22d883 A minor update to install script 2013-11-29 13:42:13 +09:00
Junegunn Choi
ad8ec7f387 Encourage use of function instead of alias (exportability) 2013-11-29 13:40:31 +09:00
Junegunn Choi
cf0ca8578c Update bash key binding example 2013-11-27 10:20:27 +09:00
Junegunn Choi
07aee79bd8 Update examples and bash completion
- Use tput sc/rc instead of redraw-current-line
- Escape selected items with printf
2013-11-27 01:14:36 +09:00
Junegunn Choi
344b57fe33 grep -F 2013-11-26 19:05:20 +09:00
Junegunn Choi
18a2fbf54a Fix install script (use export-able function instead of alias) 2013-11-26 19:01:01 +09:00
6 changed files with 694 additions and 335 deletions

341
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,31 +24,11 @@ git clone https://github.com/junegunn/fzf.git ~/.fzf
~/.fzf/install ~/.fzf/install
``` ```
The script will add an alias to fzf and auto-completion support to your The script will setup:
`.bashrc` and `.zshrc`.
### 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
@@ -73,13 +51,20 @@ Usage
``` ```
usage: fzf [options] usage: fzf [options]
-m, --multi Enable multi-select Options
-x, --extended Extended-search mode -m, --multi Enable multi-select
-q, --query=STR Initial query -x, --extended Extended-search mode
-s, --sort=MAX Maximum number of matched items to sort. Default: 1000 -q, --query=STR Initial query
+s, --no-sort Do not sort the result. Keep the sequence unchanged. -s, --sort=MAX Maximum number of matched items to sort (default: 1000)
+i Case-sensitive match +s, --no-sort Do not sort the result. Keep the sequence unchanged.
+c, --no-color Disable colors -i Case-insensitive match (default: smart-case match)
+i Case-sensitive match
+c, --no-color Disable colors
--no-mouse Disable mouse
Environment variables
FZF_DEFAULT_COMMAND Default command to use when input is tty
FZF_DEFAULT_OPTS Defaults options. (e.g. "-x -m --sort 10000")
``` ```
fzf will launch curses-based finder, read the list from STDIN, and write the fzf will launch curses-based finder, read the list from STDIN, and write the
@@ -119,6 +104,9 @@ 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. Click on an item to select it or shift-click to select
multiple items. Use mouse wheel to move the cursor up and down.
### Extended-search mode ### 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,6 +123,129 @@ 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 |
Useful examples
---------------
```sh
# vimf - Open selected file in Vim
vimf() {
FILE=$(fzf) && vim "$FILE"
}
# fd - cd to selected directory
fd() {
DIR=$(find ${1:-*} -path '*/\.*' -prune -o -type d -print 2> /dev/null | fzf) && cd "$DIR"
}
# fda - including hidden directories
fda() {
DIR=$(find ${1:-.} -type d 2> /dev/null | fzf) && cd "$DIR"
}
# fh - repeat history
fh() {
eval $(history | fzf +s | sed 's/ *[0-9]* *//')
}
# fkill - kill process
fkill() {
ps -ef | sed 1d | fzf -m | awk '{print $2}' | xargs kill -${1:-9}
}
```
Key bindings for command line
-----------------------------
The install script will setup the following key bindings.
### bash
- `CTRL-T` - Paste the selected file path(s) into the command line
- `CTRL-R` - Paste the selected command from history into the command line
The source code can be found in `~/.fzf.bash`.
### zsh
- `CTRL-T` - Paste the selected file path(s) into the command line
- `CTRL-R` - Paste the selected command from history into the command line
- `ALT-C` - cd into the selected directory
The source code can be found in `~/.fzf.zsh`.
Auto-completion
---------------
Disclaimer: *Auto-completion feature is currently experimental, it can change
over time*
### bash
#### Files and directories
Fuzzy completion for files and directories can be triggered if the word before
the cursor ends with the trigger sequence which is by default `**`.
- `COMMAND [DIRECTORY/][FUZZY_PATTERN]**<TAB>`
```sh
# Files under current directory
# - You can select multiple items with TAB key
vim **<TAB>
# Files under parent directory
vim ../**<TAB>
# Files under parent directory that match `fzf`
vim ../fzf**<TAB>
# Files under your home directory
vim ~/**<TAB>
# Directories under current directory (single-selection)
cd **<TAB>
# Directories under ~/github that match `fzf`
cd ~/github/fzf**<TAB>
```
#### Process IDs
Fuzzy completion for PIDs is provided for kill command. In this case
there is no trigger sequence, just press tab key after kill command.
```sh
# Can select multiple processes with <TAB> or <Shift-TAB> keys
kill -9 <TAB>
```
#### Host names
For ssh and telnet commands, fuzzy completion for host names is provided. The
names are extracted from /etc/hosts and ~/.ssh/config.
```sh
ssh **<TAB>
telnet **<TAB>
```
#### Settings
```sh
# Use ~~ as the trigger sequence instead of the default **
export FZF_COMPLETION_TRIGGER='~~'
# Options to fzf command
export FZF_COMPLETION_OPTS='+c -x'
```
### zsh
TODO :smiley:
(Pull requests are appreciated.)
Usage as Vim plugin Usage as Vim plugin
------------------- -------------------
@@ -173,145 +284,6 @@ 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 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. a very large list of files, fzf is significantly faster and it does not block.
Useful bash examples
--------------------
```sh
# vimf - Open selected file in Vim
vimf() {
FILE=$(fzf) && vim "$FILE"
}
# fd - cd to selected directory
fd() {
DIR=$(find ${1:-*} -path '*/\.*' -prune -o -type d -print 2> /dev/null | fzf) && cd "$DIR"
}
# fda - including hidden directories
fda() {
DIR=$(find ${1:-*} -type d 2> /dev/null | fzf) && cd "$DIR"
}
# fsel - Select multiple files in the given path
fsel() {
find ${1:-*} | fzf -m | while read item; do
echo -n "\"$item\" "
done
echo
}
# fh - repeat history
fh() {
eval $(history | fzf +s | sed 's/ *[0-9]* *//')
}
# fkill - kill process
fkill() {
ps -ef | sed 1d | fzf -m | awk '{print $2}' | xargs kill -${1:-9}
}
# (Assuming you don't use the default CTRL-T and CTRL-R)
# CTRL-T - Paste the selected file path into the command line
bind '"\er": redraw-current-line'
bind '"\C-t": " \C-u \C-a\C-k$(fzf)\e\C-e\C-y\C-a\C-y\ey\C-h\C-e\er"'
# CTRL-R - Paste the selected command from history into the command line
bind '"\C-r": " \C-e\C-u$(history | fzf +s | sed \"s/ *[0-9]* *//\")\e\C-e\er"'
```
zsh widgets
-----------
```sh
# CTRL-T - Paste the selected file path(s) into the command line
fzf-file-widget() {
local FILES
local IFS="
"
FILES=($(
find * -path '*/\.*' -prune \
-o -type f -print \
-o -type l -print 2> /dev/null | fzf -m))
unset IFS
FILES=$FILES:q
LBUFFER="${LBUFFER%% #} $FILES"
zle redisplay
}
zle -N fzf-file-widget
bindkey '^T' fzf-file-widget
# ALT-C - cd into the selected directory
fzf-cd-widget() {
cd "${$(find * -path '*/\.*' -prune \
-o -type d -print 2> /dev/null | fzf):-.}"
zle reset-prompt
}
zle -N fzf-cd-widget
bindkey '\ec' fzf-cd-widget
# CTRL-R - Paste the selected command from history into the command line
fzf-history-widget() {
LBUFFER=$(history | fzf +s | sed "s/ *[0-9]* *//")
zle redisplay
}
zle -N fzf-history-widget
bindkey '^R' fzf-history-widget
```
Auto-completion (experimental)
------------------------------
Disclaimer: *Auto-completion feature is currently experimental, it can change
over time*
### bash
Fuzzy completion can be triggered if the word before the cursor ends
with the trigger sequence which is by default `**`.
- `COMMAND [DIRECTORY/][FUZZY_PATTERN]**<TAB>`
#### Examples
```sh
# Files under current directory
# - You can select multiple items with TAB key
vim **<TAB>
# Files under parent directory
vim ../**<TAB>
# Files under parent directory that match `fzf`
vim ../fzf**<TAB>
# Files under your home directory
vim ~/**<TAB>
# Directories under current directory (single-selection)
cd **<TAB>
# Directories under ~/github that match `fzf`
cd ~/github/fzf**<TAB>
```
#### Settings
```sh
# Use ~~ as the trigger sequence instead of the default **
export FZF_COMPLETION_TRIGGER='~~'
# Options to fzf command
export FZF_COMPLETION_OPTS='+c -x'
```
### zsh
TODO :smiley:
(Pull requests are appreciated.)
Tips Tips
---- ----
@@ -325,14 +297,17 @@ If you're running Ruby 1.9 or above, you can improve the startup time with
- `time ruby --disable-gems ~/bin/fzf -h` - `time ruby --disable-gems ~/bin/fzf -h`
- 0.025 sec - 0.025 sec
Define fzf alias with the option as follows: You can define fzf function with the option as follows:
```sh ```sh
alias fzf='ruby --disable-gems ~/bin/fzf' fzf() {
ruby --disable-gems ~/bin/fzf "$@"
}
export -f fzf
``` ```
This is automatically set up in your .bashrc and .zshrc if you use the bundled However, this is automatically set up in your .bashrc and .zshrc if you use the
[install](https://github.com/junegunn/fzf/blob/master/install) script. bundled [install](https://github.com/junegunn/fzf/blob/master/install) script.
### Incorrect display on Ruby 1.8 ### Incorrect display on Ruby 1.8
@@ -341,6 +316,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
------- -------

300
fzf
View File

@@ -7,10 +7,11 @@
# / __/ / /_/ __/ # / __/ / /_/ __/
# /_/ /___/_/ Fuzzy finder for your shell # /_/ /___/_/ Fuzzy finder for your shell
# #
# URL: https://github.com/junegunn/fzf # Version: 0.7.1 (January 31, 2014)
# Author: Junegunn Choi #
# License: MIT # Author: Junegunn Choi
# Last update: November 24, 2013 # URL: https://github.com/junegunn/fzf
# License: MIT
# #
# Copyright (c) 2013 Junegunn Choi # Copyright (c) 2013 Junegunn Choi
# #
@@ -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, :mouse, :multi, :query, :extended
class AtomicVar class AtomicVar
def initialize value def initialize value
@@ -67,21 +76,34 @@ 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
@multi = false @multi = false
@xmode = false @extended = false
@mouse = true
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 '-h', '--help' then usage 0 when '--version' then version
when '-m', '--multi' then @multi = true when '-h', '--help' then usage 0
when '-x', '--extended' then @xmode = true when '-m', '--multi' then @multi = true
when '+i' then @rxflag = 0 when '+m', '--no-multi' then @multi = false
when '+s', '--no-sort' then @sort = nil when '-x', '--extended' then @extended = true
when '+c', '--no-color' then @color = false when '+x', '--no-extended' then @extended = false
when '-i' then @rxflag = Regexp::IGNORECASE
when '+i' then @rxflag = 0
when '-c', '--color' then @color = true
when '+c', '--no-color' then @color = false
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
@@ -98,7 +120,7 @@ class FZF
end end
end end
@source = source @source = source.clone
@mtx = Mutex.new @mtx = Mutex.new
@cv = ConditionVariable.new @cv = ConditionVariable.new
@events = {} @events = {}
@@ -120,24 +142,42 @@ class FZF
def start def start
$stdout.reopen($stderr) $stdout.reopen($stderr)
init_screen render { init_screen }
start_reader start_reader
start_renderer start_renderer
start_search start_search
start_loop start_loop
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]
-m, --multi Enable multi-select Options
-x, --extended Extended-search mode -m, --multi Enable multi-select
-q, --query=STR Initial query -x, --extended Extended-search mode
-s, --sort=MAX Maximum number of matched items to sort. Default: 1000 -q, --query=STR Initial query
+s, --no-sort Do not sort the result. Keep the sequence unchanged. -s, --sort=MAX Maximum number of matched items to sort (default: 1000)
+i Case-sensitive match +s, --no-sort Do not sort the result. Keep the sequence unchanged.
+c, --no-color Disable colors] -i Case-insensitive match (default: smart-case match)
+i Case-sensitive match
+c, --no-color Disable colors
--no-mouse Disable mouse
Environment variables
FZF_DEFAULT_COMMAND Default command to use when input is tty
FZF_DEFAULT_OPTS Defaults options. (e.g. "-x -m --sort 10000")] + $/ + $/
exit x exit x
end end
@@ -347,7 +387,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
@@ -392,7 +432,7 @@ class FZF
if RUBY_VERSION.split('.').map { |e| e.rjust(3, '0') }.join > '001009' if RUBY_VERSION.split('.').map { |e| e.rjust(3, '0') }.join > '001009'
@@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 +474,11 @@ class FZF
def init_screen def init_screen
C.init_screen C.init_screen
if @mouse
C.mouseinterval 0
C.mousemask C::ALL_MOUSE_EVENTS
end
C.stdscr.keypad(true)
C.start_color C.start_color
dbg = dbg =
if C.respond_to?(:use_default_colors) if C.respond_to?(:use_default_colors)
@@ -443,10 +488,11 @@ class FZF
C::COLOR_BLACK C::COLOR_BLACK
end end
C.raw C.raw
C.nonl
C.noecho C.noecho
if @color if @color
if C.can_change_color? if C.can_change_color? && 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
@@ -498,6 +544,7 @@ class FZF
exit 1 exit 1
end end
else else
$stdin.reopen IO.open(IO.sysopen('/dev/tty'), 'r')
@source @source
end end
@@ -511,7 +558,7 @@ class FZF
end end
def start_search def start_search
matcher = (@xmode ? ExtendedFuzzyMatcher : FuzzyMatcher).new @rxflag matcher = (@extended ? ExtendedFuzzyMatcher : FuzzyMatcher).new @rxflag
searcher = Thread.new { searcher = Thread.new {
lists = [] lists = []
events = {} events = {}
@@ -554,26 +601,25 @@ 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 lists.each do |list|
lists.each do |list| cnt += list.length
cnt += list.length skip = @mtx.synchronize { @events[:key] }
skip = @mtx.synchronize { @events[:key] } break if skip
break if skip
if !empty && (progress = 100 * cnt / @count.get) < 100 && Time.now - started_at > 0.5 if !empty && (progress = 100 * cnt / @count.get) < 100 && Time.now - started_at > 0.5
render { print_info " (#{progress}%)" } render { print_info " (#{progress}%)" }
end
found.concat(q.empty? ? list :
matcher.match(list, q, q[0, cx], q[cx..-1]))
end end
next if skip
@sort ? found : found.reverse found.concat(q.empty? ? list :
matcher.match(list, q, q[0, cx], q[cx..-1]))
end end
next if skip
matches = fcache[q] = @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)
@@ -637,6 +683,7 @@ class FZF
def start_renderer def start_renderer
Thread.new do Thread.new do
begin begin
refresh
while blk = @queue.shift while blk = @queue.shift
blk.call blk.call
refresh refresh
@@ -652,10 +699,32 @@ 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 test_mouse st, *states
states.any? { |s| s & st > 0 }
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,74 +740,115 @@ 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|
@vcursors << v v + case o
v + (o == :stab ? 1 : -1) when :select then 0
when C::KEY_BTAB then 1
else -1
end
} }
update_list false
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 },
: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[C::KEY_UP] = actions[ctrl(:p)] = actions[ctrl(:k)]
actions[ctrl(:f)] = actions[:right] actions[C::KEY_DOWN] = actions[ctrl(:n)] = actions[ctrl(:j)]
actions[ctrl(:h)] = actions[127] actions[C::KEY_LEFT] = actions[ctrl(:b)]
actions[ctrl(:n)] = actions[ctrl(:j)] actions[C::KEY_RIGHT] = actions[ctrl(:f)]
actions[ctrl(:p)] = actions[ctrl(:k)] actions[C::KEY_BTAB] = actions[:select] = actions[ctrl(:i)]
actions[ctrl(:g)] = actions[ctrl(:c)] = actions[:esc] actions[C::KEY_BACKSPACE] = actions[127] = actions[ctrl(:h)]
actions[:stab] = actions[9] 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?
pmv = nil
while true while true
@cursor_x.set cursor @cursor_x.set cursor
render { print_input } render { print_input }
ord = tty.getc.ord C.stdscr.timeout = -1
ord = ch = C.getch
case ord = (tty.read_nonblock(1).ord rescue :esc)
when 91
case (tty.read_nonblock(1).ord rescue nil)
when 68 then :left
when 67 then :right
when 66 then ctrl(:j)
when 65 then ctrl(:k)
when 90 then :stab
else next
end
when 'b'.ord then :alt_b
when 'f'.ord then :alt_f
when :esc then :esc
else next
end if ord == 27
upd = actions.fetch(ord, proc { |ord| case ch
char = [ord].pack('U*') when C::KEY_MOUSE
if char =~ /[[:print:]]/ if m = C.getmouse
input.insert cursor, char st = m.bstate
if test_mouse(st, C::BUTTON1_PRESSED, C::BUTTON1_RELEASED)
if m.y == cursor_y
# TODO Wide-characters
cursor = [0, [input.length, m.x - 2].min].max
elsif m.x > 1 && m.y <= max_items
vselect { |v|
tv = max_items - m.y - 1
if test_mouse(st, C::BUTTON1_RELEASED)
if test_mouse(st, C::BUTTON_SHIFT)
ch = :select
elsif pmv == tv
ch = ctrl(:m)
end
pmv = tv
end
tv
}
end
elsif test_mouse(st, 0x8000000, C::BUTTON2_PRESSED)
ch = C::KEY_DOWN
elsif test_mouse(st, C::BUTTON4_PRESSED)
ch = C::KEY_UP
end
end
when 27
C.stdscr.timeout = 0
ch =
case ch2 = C.getch
when 'b' then :alt_b
when 'f' then :alt_f
when nil then :esc
else
ch2
end
end
upd = actions.fetch(ch, proc { |ch|
if ch.is_a?(Fixnum)
# Ruby 1.8
if (ch.chr rescue '') =~ /[[:print:]]/
ch = ch.chr
elsif (nch = num_unicode_bytes(ch)) > 1
chs = [ch]
(nch - 1).times do |i|
chs << C.getch
end
# UTF-8 TODO Ruby 1.8
ch = chs.pack('C*').force_encoding('UTF-8')
end
end
if ch.is_a?(String) && ch =~ /[[:print:]]/
input.insert cursor, ch
cursor += 1 cursor += 1
end end
}).call(ord) }).call(ch)
# Dispatch key event # Dispatch key event
emit(:key) { [@query.set(input.dup), cursor] } if upd emit(:key) { [@query.set(input.dup), cursor] } if upd
@@ -770,14 +880,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
@@ -829,15 +943,17 @@ 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 ? 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[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 fuzzy_regex w
end, invert ] end, invert ]

View File

@@ -31,13 +31,12 @@ _fzf_opts_completion() {
} }
_fzf_generic_completion() { _fzf_generic_completion() {
local cur prev opts 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]}"
prev="${COMP_WORDS[COMP_CWORD-1]}" if [[ ${cur} == *"$trigger" ]]; then
if [[ ${cur} == *"$FZF_COMPLETION_TRIGGER" ]]; then base=${cur:0:${#cur}-${#trigger}}
base=${cur:0:${#cur}-${#FZF_COMPLETION_TRIGGER}}
eval base=$base eval base=$base
dir="$base" dir="$base"
@@ -46,12 +45,9 @@ _fzf_generic_completion() {
leftover=${base/#"$dir"} leftover=${base/#"$dir"}
leftover=${leftover/#\/} leftover=${leftover/#\/}
[ "$dir" = './' ] && dir='' [ "$dir" = './' ] && dir=''
tput sc
matches=$(find "$dir"* $1 2> /dev/null | fzf $FZF_COMPLETION_OPTS $2 -q "$leftover" | while read item; do matches=$(find "$dir"* $1 2> /dev/null | fzf $FZF_COMPLETION_OPTS $2 -q "$leftover" | while read item; do
if [[ ${item} =~ \ ]]; then printf '%q ' "$item"
echo -n "\"$item\" "
else
echo -n "$item "
fi
done) done)
matches=${matches% } matches=${matches% }
if [ -n "$matches" ]; then if [ -n "$matches" ]; then
@@ -59,6 +55,7 @@ _fzf_generic_completion() {
else else
COMPREPLY=( "$cur" ) COMPREPLY=( "$cur" )
fi fi
tput rc
return 0 return 0
fi fi
dir=$(dirname "$dir") dir=$(dirname "$dir")
@@ -85,6 +82,57 @@ _fzf_dir_completion() {
"" ""
} }
_fzf_kill_completion() {
[ -n "${COMP_WORDS[COMP_CWORD]}" ] && return 1
local selected
tput sc
selected=$(ps -ef | sed 1d | fzf -m $FZF_COMPLETION_OPTS | awk '{print $2}' | tr '\n' ' ')
tput rc
if [ -n "$selected" ]; then
COMPREPLY=( "$selected" )
return 0
fi
}
_fzf_telnet_completion() {
local cur selected trigger
trigger=${FZF_COMPLETION_TRIGGER:-**}
cur="${COMP_WORDS[COMP_CWORD]}"
[[ ${cur} == *"$trigger" ]] || return 1
cur=${cur:0:${#cur}-${#trigger}}
tput sc
selected=$(grep -v '^\s*\(#\|$\)' /etc/hosts | awk '{print $2}' | sort -u | fzf $FZF_COMPLETION_OPTS -q "$cur")
tput rc
if [ -n "$selected" ]; then
COMPREPLY=("$selected")
return 0
fi
}
_fzf_ssh_completion() {
local cur selected trigger
trigger=${FZF_COMPLETION_TRIGGER:-**}
cur="${COMP_WORDS[COMP_CWORD]}"
[[ ${cur} == *"$trigger" ]] || return 1
cur=${cur:0:${#cur}-${#trigger}}
tput sc
selected=$(cat \
<(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | grep -i ^host) \
<(grep -v '^\s*\(#\|$\)' /etc/hosts) | \
awk '{print $2}' | sort -u | fzf $FZF_COMPLETION_OPTS -q "$cur")
tput rc
if [ -n "$selected" ]; then
COMPREPLY=("$selected")
return 0
fi
}
complete -F _fzf_opts_completion fzf complete -F _fzf_opts_completion fzf
# Directory # Directory
@@ -110,7 +158,10 @@ for cmd in "
complete -F _fzf_all_completion -o default -o bashdefault $cmd complete -F _fzf_all_completion -o default -o bashdefault $cmd
done done
bind '"\e\e": complete' # Kill completion
bind '"\er": redraw-current-line' complete -F _fzf_kill_completion -o nospace -o default -o bashdefault kill
bind '"\C-i": "\e\e\er"'
# Host completion
complete -F _fzf_ssh_completion -o default -o bashdefault ssh
complete -F _fzf_telnet_completion -o default -o bashdefault telnet

View File

@@ -1,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.0' spec.version = '0.7.1'
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

186
install
View File

@@ -1,76 +1,184 @@
#!/bin/bash #!/bin/bash
cd `dirname $BASH_SOURCE` cd `dirname $BASH_SOURCE`
FZF_BASE=`pwd` fzf_base=`pwd`
# ruby executable # ruby executable
echo -n "Checking Ruby executable ... " echo -n "Checking Ruby executable ... "
RUBY=`which ruby` ruby=`which ruby`
if [ $? -ne 0 ]; then 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"
exit 1 else
echo "Not found"
echo "Installing 'curses' gem ... "
/usr/bin/env gem install curses
if [ $? -ne 0 ]; then
echo "Failed to install 'curses' gem."
echo "Try installing it as root: sudo gem install curses"
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"
FZF_ALIAS="alias fzf='$RUBY --disable-gems $FZF_BASE/fzf'" "$ruby" --disable-gems -e "$curses_check"
if [ $? -eq 0 ]; then
fzf_cmd="$ruby --disable-gems $fzf_base/fzf"
else
fzf_cmd="$ruby $fzf_base/fzf"
fi
else else
echo "< 1.9" echo "< 1.9"
FZF_ALIAS="alias fzf='$RUBY $FZF_BASE/fzf' # fzf" fzf_cmd="$ruby $fzf_base/fzf"
fi fi
# Auto-completion # Auto-completion
read -p "Do you want to add auto-completion support? (y/n) " -n 1 -r read -p "Do you want to add auto-completion support? ([y]/n) " -n 1 -r
echo 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
for shell in bash zsh; do
echo -n "Generate ~/.fzf.$shell ... "
src=~/.fzf.${shell}
fzf_completion="source $fzf_base/fzf-completion.${shell}"
if [ $auto_completion -ne 0 ]; then
fzf_completion="# $fzf_completion"
fi
cat > $src << EOF
# Setup fzf function
# ------------------
unalias fzf 2> /dev/null
fzf() {
$fzf_cmd "\$@"
}
export -f fzf > /dev/null
# Auto-completion
# ---------------
$fzf_completion
EOF
if [ $key_bindings -eq 0 ]; then
if [ $shell = bash ]; then
cat >> $src << "EOF"
# Key bindings
# ------------
# Required to refresh the prompt after fzf
bind '"\er": redraw-current-line'
# CTRL-T - Paste the selected file path into the command line
__fsel() {
find * -path '*/\.*' -prune \
-o -type f -print \
-o -type l -print 2> /dev/null | fzf -m | while read item; do
printf '%q ' "$item"
done
echo
}
bind '"\C-t": " \C-u \C-a\C-k$(__fsel)\e\C-e\C-y\C-a\C-y\ey\C-h\C-e\er"'
# CTRL-R - Paste the selected command from history into the command line
bind '"\C-r": " \C-e\C-u$(history | fzf +s | sed \"s/ *[0-9]* *//\")\e\C-e\er"'
EOF
else
cat >> $src << "EOF"
# Key bindings
# ------------
# CTRL-T - Paste the selected file path(s) into the command line
fzf-file-widget() {
local FILES
local IFS="
"
FILES=($(
find * -path '*/\.*' -prune \
-o -type f -print \
-o -type l -print 2> /dev/null | fzf -m))
unset IFS
FILES=$FILES:q
LBUFFER="${LBUFFER%% #} $FILES"
zle redisplay
}
zle -N fzf-file-widget
bindkey '^T' fzf-file-widget
# ALT-C - cd into the selected directory
fzf-cd-widget() {
cd "${$(find * -path '*/\.*' -prune \
-o -type d -print 2> /dev/null | fzf):-.}"
zle reset-prompt
}
zle -N fzf-cd-widget
bindkey '\ec' fzf-cd-widget
# CTRL-R - Paste the selected command from history into the command line
fzf-history-widget() {
LBUFFER=$(history | fzf +s | sed "s/ *[0-9]* *//")
zle redisplay
}
zle -N fzf-history-widget
bindkey '^R' fzf-history-widget
EOF
fi
fi
echo "OK"
done
echo echo
for shell in bash zsh; do for shell in bash zsh; do
rc=~/.${shell}rc rc=~/.${shell}rc
src="source ~/.fzf.${shell}"
echo "Update $rc:" echo "Update $rc:"
echo " - $src"
# Install fzf alias if [ $(grep -F "$src" $rc | wc -l) -gt 0 ]; then
echo "- Add fzf alias:" echo " - Not added (already being sourced)"
echo " - $FZF_ALIAS"
if [ $(grep "alias fzf=" $rc | wc -l) -gt 0 ]; then
echo " - (X) fzf alias already exists"
else else
echo $FZF_ALIAS >> $rc echo $src >> $rc
echo " - Added." echo " - Added"
fi
# Install auto-completion support
if [ $AUTO_COMPLETION -eq 0 ]; then
FZF_COMPLETION="source $FZF_BASE/fzf-completion.${shell}"
echo "- Add auto-completion support"
echo " - $FZF_COMPLETION"
if [ $(grep "source.*fzf-completion" $rc | wc -l) -gt 0 ]; then
echo " - (X) fzf-completion.${shell} already being sourced"
else
echo $FZF_COMPLETION >> $rc
echo " - Added."
fi
fi fi
echo echo
done done
echo "Finished. Reload your .bashrc or .zshrc to take effect." cat << EOF
echo " source ~/.bashrc # bash" Finished. Reload your .bashrc or .zshrc.
echo " source ~/.zshrc # zsh" source ~/.bashrc # bash
echo source ~/.zshrc # zsh
echo "To uninstall fzf, simply remove the added lines."
To uninstall fzf, simply remove the added line.
EOF

View File

@@ -7,43 +7,78 @@ 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
end
begin def test_environment_variables
ENV['FZF_DEFAULT_SORT'] = '1500' # Deprecated
fzf = FZF.new [] ENV['FZF_DEFAULT_SORT'] = '20000'
assert_equal 1500, fzf.sort fzf = FZF.new []
ensure assert_equal 20000, fzf.sort
ENV.delete 'FZF_DEFAULT_SORT'
end ENV['FZF_DEFAULT_OPTS'] = '-x -m -s 10000 -q " hello world " +c --no-mouse'
fzf = FZF.new []
assert_equal 10000, fzf.sort
assert_equal ' hello world ',
fzf.query.get
assert_equal true, fzf.extended
assert_equal true, fzf.multi
assert_equal false, fzf.color
assert_equal false, fzf.mouse
end 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 --extended --no-mouse]
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.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 true, fzf.extended
fzf = FZF.new %w[--sort=2000 --no-color --multi +i --query hello
--no-sort -i --color --no-multi]
assert_equal nil, fzf.sort
assert_equal false, fzf.multi
assert_equal true, fzf.color
assert_equal true, fzf.mouse
assert_equal 1, fzf.rxflag
assert_equal 'hello', fzf.query.get
assert_equal false, fzf.extended
# Short opts # Short opts
fzf = FZF.new %w[-s 2000 +c -m +i -qhello] fzf = FZF.new %w[-s 2000 +c -m +i -qhello -x]
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 0, fzf.rxflag assert_equal 0, fzf.rxflag
assert_equal 'hello', fzf.query.get assert_equal 'hello', fzf.query.get
assert_equal true, 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
assert_equal nil, fzf.sort -s 3000 -c +m -i -q world +x]
assert_equal 3000, fzf.sort
assert_equal false, fzf.multi
assert_equal true, fzf.color
assert_equal 1, fzf.rxflag
assert_equal 'world', fzf.query.get assert_equal 'world', fzf.query.get
assert_equal false, 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 +187,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 +276,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
@@ -332,5 +416,14 @@ 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
end end