Compare commits

...

44 Commits
0.8.3 ... 0.8.5

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

241
README.md
View File

@@ -5,8 +5,6 @@ fzf is a general-purpose fuzzy finder for your shell.
![](https://raw.github.com/junegunn/i/master/fzf.gif)
([tmux integration!](https://cloud.githubusercontent.com/assets/700826/2593609/3ec13962-ba83-11e3-88d3-f9f95bd8a64b.gif))
It was heavily inspired by [ctrlp.vim](https://github.com/kien/ctrlp.vim) and
the likes.
@@ -26,14 +24,24 @@ git clone https://github.com/junegunn/fzf.git ~/.fzf
~/.fzf/install
```
In case you don't have git installed:
```sh
mkdir -p ~/.fzf
curl -L https://github.com/junegunn/fzf/archive/master.tar.gz |
tar xz --strip-components 1 -C ~/.fzf
~/.fzf/install
```
The script will setup:
- `fzf` executable
- Key bindings (`CTRL-T`, `CTRL-R`, and `ALT-C`) for bash and zsh
- Fuzzy auto-completion for bash
- `fzf` function (bash, zsh, fish)
- Key bindings (`CTRL-T`, `CTRL-R`, and `ALT-C`) (bash, zsh, fish)
- Fuzzy auto-completion (bash)
If you don't use bash or zsh, you have to manually place fzf executable in a
directory included in `$PATH`. Key bindings are not yet supported.
If you don't use any of the aforementioned shells, you have to manually place
fzf executable in a directory included in `$PATH`. Key bindings and
auto-completion will not be available in that case.
### Install as Vim plugin
@@ -57,8 +65,9 @@ usage: fzf [options]
-e, --extended-exact Extended-search mode (exact match)
-i Case-insensitive match (default: smart-case match)
+i Case-sensitive match
-n, --nth=[-]N[,..] Comma-separated list of field indexes for limiting
search scope (positive or negative integers)
-n, --nth=N[,..] Comma-separated list of field index expressions
for limiting search scope. Each can be a non-zero
integer or a range expression ([BEGIN]..[END])
-d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style)
Search result
@@ -71,6 +80,7 @@ usage: fzf [options]
+c, --no-color Disable colors
+2, --no-256 Disable 256-color
--black Use black background
--reverse Reverse orientation
Scripting
-q, --query=STR Start the finder with the given query
@@ -114,6 +124,7 @@ The following readline key bindings should also work as expected.
- CTRL-A / CTRL-E
- CTRL-B / CTRL-F
- CTRL-H / CTRL-D
- CTRL-W / CTRL-U / CTRL-Y
- ALT-B / ALT-F
@@ -164,12 +175,6 @@ fd() {
cd "$dir"
}
# fda - including hidden directories
fda() {
local dir
dir=$(find ${1:-.} -type d 2> /dev/null | fzf +m) && cd "$dir"
}
# fh - repeat history
fh() {
eval $(([ -n "$ZSH_NAME" ] && fc -l 1 || history) | fzf +s | sed 's/ *[0-9]* *//')
@@ -179,41 +184,16 @@ fh() {
fkill() {
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=$(
awk 'BEGIN { FS="\t" } !/^!/ {print toupper($4)"\t"$1"\t"$2"\t"$3}' tags |
cut -c1-80 | fzf --nth=1,2
) && $EDITOR $(cut -f3 <<< "$line") -c "set nocst" \
-c "silent tag $(cut -f2 <<< "$line")"
}
```
For more examples, see [the wiki
page](https://github.com/junegunn/fzf/wiki/examples).
Key bindings for command line
-----------------------------
The install script will setup the following key bindings.
### bash/zsh
The install script will setup the following key bindings for bash, zsh, and
fish.
- `CTRL-T` - Paste the selected file path(s) into the command line
- `CTRL-R` - Paste the selected command from history into the command line
@@ -223,7 +203,13 @@ 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`.
If you use vi mode on bash, you need to add `set -o vi` *before* `source
~/.fzf.bash` in your .bashrc, so that it correctly sets up key bindings for vi
mode.
If you want to customize the key bindings, consider editing the
installer-generated source code: `~/.fzf.bash`, `~/.fzf.zsh`, and
`~/.config/fish/functions/fzf_key_bindings.fish`.
Auto-completion
---------------
@@ -301,7 +287,7 @@ TODO :smiley:
Usage as Vim plugin
-------------------
(fzf is a command-line utility, naturally it is only accessible in terminal Vim)
(Note: To use fzf in GVim, an external terminal emulator is required.)
### `:FZF[!]`
@@ -325,6 +311,17 @@ 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.
In GVim, you need an external terminal emulator to start fzf with. `xterm`
command is used by default, but you can customize it with `g:fzf_launcher`.
```vim
" This is the default. %s is replaced with fzf command
let g:fzf_launcher = 'xterm -e bash -ic %s'
" Use urxvt instead
let g:fzf_launcher = 'urxvt -geometry 120x30 -e sh -c %s'
```
### `fzf#run([options])`
For more advanced uses, you can call `fzf#run()` function which returns the list
@@ -332,15 +329,17 @@ 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%`) |
| Option name | Type | Description |
| --------------- | ------------- | ------------------------------------------------------------------ |
| `source` | string | External command to generate input to fzf (e.g. `find .`) |
| `source` | list | Vim list as input to fzf |
| `sink` | string | Vim command to handle the selected item (e.g. `e`, `tabe`) |
| `sink` | funcref | Reference to function to process each selected item |
| `options` | string | Options to fzf |
| `dir` | string | Working directory |
| `tmux_width` | number/string | Use tmux vertical split with the given height (e.g. `20`, `50%`) |
| `tmux_height` | number/string | Use tmux horizontal split with the given height (e.g. `20`, `50%`) |
| `launcher` | string | External terminal emulator to start fzf with (Only used in GVim) |
#### Examples
@@ -366,9 +365,10 @@ 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
\ 'sink': 'colo',
\ 'options': '+m',
\ 'tmux_width': 20,
\ 'launcher': 'xterm -geometry 20x30 -e bash -ic %s'
\ })<CR>
```
@@ -377,25 +377,29 @@ handy mapping that selects an open buffer.
```vim
" List of buffers
function! g:buflist()
function! BufList()
redir => ls
silent ls
redir END
return split(ls, '\n')
endfunction
function! g:bufopen(e)
function! 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
\ 'source': reverse(BufList()),
\ 'sink': function('BufOpen'),
\ 'options': '+m',
\ 'tmux_height': '40%'
\ })<CR>
```
### Articles
- [fzf+vim+tmux](http://junegunn.kr/2014/04/fzf+vim+tmux)
Tips
----
@@ -428,6 +432,113 @@ This limit can be adjusted with `-s` option, or with the environment variable
export FZF_DEFAULT_OPTS="--sort 20000"
```
### Respecting `.gitignore`, `.hgignore`, and `svn:ignore`
[ag](https://github.com/ggreer/the_silver_searcher) or
[pt](https://github.com/monochromegane/the_platinum_searcher) will do the
filtering:
```sh
# Feed the output of ag into fzf
ag -l -g "" | fzf
# Setting ag as the default source for fzf
export FZF_DEFAULT_COMMAND='ag -l -g ""'
# Now fzf (w/o pipe) will use ag instead of find
fzf
```
### `git ls-tree` for fast traversal
If you're running fzf in a large git repository, `git ls-tree` can boost up the
speed of the traversal.
```sh
# Copy the original fzf function to __fzf
declare -f __fzf > /dev/null ||
eval "$(echo "__fzf() {"; declare -f fzf | grep -v '^{' | tail -n +2)"
# Use git ls-tree when possible
fzf() {
if [ -n "$(git rev-parse HEAD 2> /dev/null)" ]; then
FZF_DEFAULT_COMMAND="git ls-tree -r --name-only HEAD" __fzf "$@"
else
__fzf "$@"
fi
}
```
### Using fzf with tmux splits
It isn't too hard to write your own fzf-tmux combo like the default
CTRL-T key binding. (Or is it?)
```sh
# This is a helper function that splits the current pane to start the given
# command ($1) and sends its output back to the original pane with any number of
# optional keys (shift; $*).
fzf_tmux_helper() {
[ -n "$TMUX_PANE" ] || return
local cmd=$1
shift
tmux split-window -p 40 \
"bash -c \"\$(tmux send-keys -t $TMUX_PANE \"\$(source ~/.fzf.bash; $cmd)\" $*)\""
}
# This is the function we are going to run in the split pane.
# - "find" to list the directories
# - "sed" will escape spaces in the paths.
# - "paste" will join the selected paths into a single line
fzf_tmux_dir() {
fzf_tmux_helper \
'find * -path "*/\.*" -prune -o -type d -print 2> /dev/null |
fzf --multi |
sed "s/ /\\\\ /g" |
paste -sd" " -' Space
}
# Bind CTRL-X-CTRL-D to fzf_tmux_dir
bind '"\C-x\C-d": "$(fzf_tmux_dir)\e\C-e"'
```
### Fish shell
It's [a known bug of fish](https://github.com/fish-shell/fish-shell/issues/1362)
that it doesn't allow reading from STDIN in command substitution, which means
simple `vim (fzf)` won't work as expected. The workaround is to store the result
of fzf to a temporary file.
```sh
function vimf
if fzf > $TMPDIR/fzf.result
vim (cat $TMPDIR/fzf.result)
end
end
function fe
set tmp $TMPDIR/fzf.result
fzf --query="$argv[1]" --select-1 --exit-0 > $tmp
if [ (cat $tmp | wc -l) -gt 0 ]
vim (cat $tmp)
end
end
```
### Windows
fzf works on [Cygwin](http://www.cygwin.com/) and
[MSYS2](http://sourceforge.net/projects/msys2/). You may need to use `--black`
option on MSYS2 to avoid rendering issues.
### Handling UTF-8 NFD paths on OSX
Use iconv to convert NFD paths to NFC:
```sh
find . | iconv -f utf-8-mac -t utf8//ignore | fzf
```
License
-------

206
fzf
View File

@@ -7,7 +7,7 @@
# / __/ / /_/ __/
# /_/ /___/_/ Fuzzy finder for your shell
#
# Version: 0.8.3 (April 3, 2014)
# Version: 0.8.5 (Jun 15, 2014)
#
# Author: Junegunn Choi
# URL: https://github.com/junegunn/fzf
@@ -50,7 +50,7 @@ end
class FZF
C = Curses
attr_reader :rxflag, :sort, :nth, :color, :black, :ansi256,
attr_reader :rxflag, :sort, :nth, :color, :black, :ansi256, :reverse,
:mouse, :multi, :query, :select1, :exit0, :filter, :extended
class AtomicVar
@@ -88,6 +88,7 @@ class FZF
@filter = nil
@nth = nil
@delim = nil
@reverse = false
argv =
if opts = ENV['FZF_DEFAULT_OPTS']
@@ -114,6 +115,8 @@ class FZF
when '--no-black' then @black = false
when '--mouse' then @mouse = true
when '--no-mouse' then @mouse = false
when '--reverse' then @reverse = true
when '--no-reverse' then @reverse = false
when '+s', '--no-sort' then @sort = nil
when '-1', '--select-1' then @select1 = true
when '+1', '--no-select-1' then @select1 = false
@@ -130,10 +133,9 @@ class FZF
when /^-f(.*)$/, /^--filter=(.*)$/
@filter = $1
when '-n', '--nth'
usage 1, 'field number required' unless nth = argv.shift
usage 1, 'invalid field number' if nth.to_i == 0
usage 1, 'field expression required' unless nth = argv.shift
@nth = parse_nth nth
when /^-n([0-9,-]+)$/, /^--nth=([0-9,-]+)$/
when /^-n([0-9,-\.]+)$/, /^--nth=([0-9,-\.]+)$/
@nth = parse_nth $1
when '-d', '--delimiter'
usage 1, 'delimiter required' unless delim = argv.shift
@@ -176,10 +178,20 @@ class FZF
end
def parse_nth nth
nth.split(',').map { |n|
ni = n.to_i
usage 1, "invalid field number: #{n}" if ni == 0
ni
nth.split(',').map { |expr|
x = proc { usage 1, "invalid field expression: #{expr}" }
first, second = expr.split('..', 2)
x.call if !first.empty? && first.to_i == 0 ||
second && !second.empty? && (second.to_i == 0 || second.include?('.'))
first = first.empty? ? 1 : first.to_i
second = case second
when nil then first
when '' then -1
else second.to_i
end
Range.new(*[first, second].map { |e| e > 0 ? e - 1 : e })
}
end
@@ -278,8 +290,9 @@ class FZF
-e, --extended-exact Extended-search mode (exact match)
-i Case-insensitive match (default: smart-case match)
+i Case-sensitive match
-n, --nth=[-]N[,..] Comma-separated list of field indexes for limiting
search scope (positive or negative integers)
-n, --nth=N[,..] Comma-separated list of field index expressions
for limiting search scope. Each can be a non-zero
integer or a range expression ([BEGIN]..[END])
-d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style)
Search result
@@ -292,6 +305,7 @@ class FZF
+c, --no-color Disable colors
+2, --no-256 Disable 256-color
--black Use black background
--reverse Reverse orientation
Scripting
-q, --query=STR Start the finder with the given query
@@ -305,121 +319,6 @@ class FZF
exit x
end
case RUBY_PLATFORM
when /darwin/
module UConv
CHOSUNG = 0x1100
JUNGSUNG = 0x1161
JONGSUNG = 0x11A7
CHOSUNGS = 19
JUNGSUNGS = 21
JONGSUNGS = 28
JJCOUNT = JUNGSUNGS * JONGSUNGS
NFC_BEGIN = 0xAC00
NFC_END = NFC_BEGIN + CHOSUNGS * JUNGSUNGS * JONGSUNGS
def self.nfd str
str.split(//).map do |c|
cp = c.ord
if cp >= NFC_BEGIN && cp < NFC_END
chr = ''
idx = cp - NFC_BEGIN
cho = CHOSUNG + idx / JJCOUNT
jung = JUNGSUNG + (idx % JJCOUNT) / JONGSUNGS
jong = JONGSUNG + idx % JONGSUNGS
chr << cho << jung
chr << jong if jong != JONGSUNG
chr
else
c
end
end
end
def self.to_nfc arr
[NFC_BEGIN + arr[0] * JJCOUNT +
(arr[1] || 0) * JONGSUNGS +
(arr[2] || 0)].pack('U*')
end
if String.method_defined?(:each_char)
def self.split str
str.each_char.to_a
end
else
def self.split str
str.split('')
end
end
def self.nfc str, offsets = []
ret = ''
omap = []
pend = []
split(str).each_with_index do |c, idx|
cp =
begin
c.ord
rescue Exception
next
end
omap << ret.length
unless pend.empty?
if cp >= JUNGSUNG && cp < JUNGSUNG + JUNGSUNGS
pend << cp - JUNGSUNG
next
elsif cp >= JONGSUNG && cp < JONGSUNG + JONGSUNGS
pend << cp - JONGSUNG
next
else
omap[-1] = omap[-1] + 1
ret << to_nfc(pend)
pend.clear
end
end
if cp >= CHOSUNG && cp < CHOSUNG + CHOSUNGS
pend << cp - CHOSUNG
else
ret << c
end
end
ret << to_nfc(pend) unless pend.empty?
return [ret,
offsets.map { |pair|
b, e = pair
[omap[b] || 0, omap[e] || ((omap.last || 0) + 1)] }]
end
end
def convert_item item
UConv.nfc(*item)
end
class Matcher
def query_chars q
UConv.nfd(q)
end
def sanitize q
UConv.nfd(q).join
end
end
else
def convert_item item
item
end
class Matcher
def query_chars q
q.split(//)
end
def sanitize q
q
end
end
end
def emit event
@mtx.synchronize do
@events[event] = yield
@@ -428,7 +327,11 @@ class FZF
end
def max_items; C.lines - 2; end
def cursor_y; C.lines - 1; end
def cursor_y offset = 0
@reverse ? (offset) : (C.lines - 1 - offset)
end
def cprint str, col
C.attron(col) do
addstr_safe str
@@ -448,7 +351,7 @@ class FZF
end
def print_info msg = nil
C.setpos cursor_y - 1, 0
C.setpos cursor_y(1), 0
C.clrtoeol
prefix =
if spinner = @spinner.first
@@ -550,7 +453,7 @@ class FZF
width = width str
diff = 0
while width > len
width -= (left ? str[0, 1] : str[-1, 1]) =~ @@wrx ? 2 : 1
width -= ((left ? str[0, 1] : str[-1, 1]) =~ @@wrx ? 2 : 1) rescue 1
str = left ? str[1..-1] : str[0...-1]
diff += 1
end
@@ -726,7 +629,10 @@ class FZF
found.concat(q.empty? ? list :
matcher.match(list, q, q[0, cx], q[cx..-1]))
end
next if skip
if skip
sleep 0.1
next
end
matches = @sort ? found : found.reverse
if !empty && @sort && matches.length <= @sort
matches = FZF.sort(matches)
@@ -766,7 +672,7 @@ class FZF
# Wipe
if items.length < @plcount
@plcount.downto(items.length) do |idx|
C.setpos cursor_y - idx - 2, 0
C.setpos cursor_y(idx + 2), 0
C.clrtoeol
end
end
@@ -781,10 +687,10 @@ class FZF
}
items.each_with_index do |item, idx|
next unless wipe || cleanse.include?(idx)
row = cursor_y - idx - 2
row = cursor_y(idx + 2)
chosen = idx == vcursor
selected = @selects.include?([*item][0])
line, offsets = convert_item item
line, offsets = item
tokens = format line, maxc, offsets
print_item row, tokens, chosen, selected
end
@@ -903,7 +809,7 @@ class FZF
ord =
case read_nb(1, :esc)
when 91
when 91, 79
case read_nb(1, nil)
when 68 then ctrl(:b)
when 67 then ctrl(:f)
@@ -988,7 +894,13 @@ class FZF
}
actions = {
:esc => proc { exit 1 },
ctrl(:d) => proc { exit 1 if input.empty? },
ctrl(:d) => proc {
if input.empty?
exit 1
elsif cursor < input.length
input = input[0...cursor] + input[(cursor + 1)..-1]
end
},
ctrl(:m) => proc {
got = pick
exit 0
@@ -1000,8 +912,8 @@ class FZF
},
ctrl(:a) => proc { cursor = 0; nil },
ctrl(:e) => proc { cursor = input.length; nil },
ctrl(:j) => proc { vselect { |v| v - 1 } },
ctrl(:k) => proc { vselect { |v| v + 1 } },
ctrl(:j) => proc { vselect { |v| v - (@reverse ? -1 : 1) } },
ctrl(:k) => proc { vselect { |v| v + (@reverse ? -1 : 1) } },
ctrl(:w) => proc {
pcursor = cursor
backword.call
@@ -1021,7 +933,7 @@ class FZF
when :stab then 1
when :sclick then 0
else -1
end }
end * (@reverse ? -1 : 1) }
end
},
ctrl(:b) => proc { cursor = [0, cursor - 1].max; nil },
@@ -1045,6 +957,7 @@ class FZF
case event
when :click, :release
x, y, shift = val.values_at :x, :y, :shift
y = @reverse ? (C.lines - 1 - y) : y
if y == cursor_y
cursor = [0, [input.length, x - 2].min].max
elsif x > 1 && y <= max_items
@@ -1112,7 +1025,7 @@ class FZF
end
def initialize nth, delim
@nth = nth && nth.map { |n| n > 0 ? n - 1 : n }
@nth = nth
@delim = delim
@tokens_cache = {}
end
@@ -1134,8 +1047,9 @@ class FZF
prefix_length, tokens = tokenize str
@nth.each do |n|
if (token = tokens[n]) && (md = token.match(pat) rescue nil)
prefix_length += (tokens[0...n] || []).join.length
if (range = tokens[n]) && (token = range.join) &&
(md = token.sub(/\s+$/, '').match(pat) rescue nil)
prefix_length += (tokens[0...(n.begin)] || []).join.length
offset = md.offset(0).map { |o| o + prefix_length }
return MatchData.new(offset)
end
@@ -1168,7 +1082,7 @@ class FZF
def fuzzy_regex q
@regexp[q] ||= begin
q = q.downcase if @rxflag == Regexp::IGNORECASE
Regexp.new(query_chars(q).inject('') { |sum, e|
Regexp.new(q.split(//).inject('') { |sum, e|
e = Regexp.escape e
sum << (e.length > 1 ? "(?:#{e}).*?" : # FIXME: not equivalent
"#{e}[^#{e}]*?")
@@ -1226,7 +1140,7 @@ class FZF
when ''
nil
when /^\^(.*)\$$/
Regexp.new('^' << sanitize(Regexp.escape($1)) << '$', rxflag_for(w))
Regexp.new('^' << Regexp.escape($1) << '$', rxflag_for(w))
when /^'/
if @mode == :fuzzy && w.length > 1
exact_regex w[1..-1]
@@ -1235,10 +1149,10 @@ class FZF
end
when /^\^/
w.length > 1 ?
Regexp.new('^' << sanitize(Regexp.escape(w[1..-1])), rxflag_for(w)) : nil
Regexp.new('^' << Regexp.escape(w[1..-1]), rxflag_for(w)) : nil
when /\$$/
w.length > 1 ?
Regexp.new(sanitize(Regexp.escape(w[0..-2])) << '$', rxflag_for(w)) : nil
Regexp.new(Regexp.escape(w[0..-2]) << '$', rxflag_for(w)) : nil
else
@mode == :fuzzy ? fuzzy_regex(w) : exact_regex(w)
end, invert ]
@@ -1246,7 +1160,7 @@ class FZF
end
def exact_regex w
Regexp.new(sanitize(Regexp.escape(w)), rxflag_for(w))
Regexp.new(Regexp.escape(w), rxflag_for(w))
end
def match list, q, prefix, suffix

View File

@@ -1,7 +1,7 @@
# coding: utf-8
Gem::Specification.new do |spec|
spec.name = 'fzf'
spec.version = '0.8.3'
spec.version = '0.8.4'
spec.authors = ['Junegunn Choi']
spec.email = ['junegunn.c@gmail.com']
spec.description = %q{Fuzzy finder for your shell}

136
install
View File

@@ -12,10 +12,9 @@ if [ $? -ne 0 ]; then
fi
# 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
$system_ruby --disable-gems -rcurses -e0 2> /dev/null
[ $? -eq 0 ] && ruby=$system_ruby
fi
@@ -23,7 +22,7 @@ echo "OK ($ruby)"
# Curses-support
echo -n "Checking Curses support ... "
"$ruby" -e "$curses_check"
"$ruby" -rcurses -e0 2> /dev/null
if [ $? -eq 0 ]; then
echo "OK"
else
@@ -45,7 +44,7 @@ echo -n "Checking Ruby version ... "
"$ruby" -e 'exit RUBY_VERSION >= "1.9"'
if [ $? -eq 0 ]; then
echo ">= 1.9"
"$ruby" --disable-gems -e "$curses_check"
"$ruby" --disable-gems -rcurses -e0 2> /dev/null
if [ $? -eq 0 ]; then
fzf_cmd="$ruby --disable-gems $fzf_base/fzf"
else
@@ -73,7 +72,7 @@ for shell in bash zsh; do
echo -n "Generate ~/.fzf.$shell ... "
src=~/.fzf.${shell}
fzf_completion="source $fzf_base/fzf-completion.${shell}"
fzf_completion="[[ \$- =~ i ]] && source $fzf_base/fzf-completion.${shell}"
if [ $auto_completion -ne 0 ]; then
fzf_completion="# $fzf_completion"
fi
@@ -89,7 +88,7 @@ export -f fzf > /dev/null
# Auto-completion
# ---------------
[[ \$- =~ i ]] && $fzf_completion
$fzf_completion
EOF
@@ -137,11 +136,11 @@ if [ -z "$(set -o | grep '^vi.*on')" ]; then
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"'
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 \C-h"'
fi
# CTRL-R - Paste the selected command from history into the command line
bind '"\C-r": " \C-e\C-u$(HISTTIMEFORMAT= history | fzf +s | sed \"s/ *[0-9]* *//\")\e\C-e\er"'
bind '"\C-r": " \C-e\C-u$(HISTTIMEFORMAT= history | fzf +s +m -n..,1,2.. | 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"'
@@ -154,11 +153,11 @@ else
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"'
bind '"\C-t": "\e$a \eddi$(__fsel)\C-x\C-e\e0Px$a \C-x\C-r\exa "'
fi
# CTRL-R - Paste the selected command from history into the command line
bind '"\C-r": "\eddi$(HISTTIMEFORMAT= history | fzf +s | sed \"s/ *[0-9]* *//\")\C-x\C-e\e$a\C-x\C-r"'
bind '"\C-r": "\eddi$(HISTTIMEFORMAT= history | fzf +s +m -n..,1,2.. | 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"'
@@ -199,7 +198,7 @@ if [ -n "$TMUX_PANE" -a ${FZF_TMUX:-1} -ne 0 -a ${LINES:-40} -gt 15 ]; then
}
else
fzf-file-widget() {
LBUFFER="${LBUFFER%% #}$(__fsel)"
LBUFFER="${LBUFFER}$(__fsel)"
zle redisplay
}
fi
@@ -217,7 +216,7 @@ 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]* *//")
LBUFFER=$(fc -l 1 | fzf +s +m -n..,1,2.. | sed "s/ *[0-9*]* *//")
zle redisplay
}
zle -N fzf-history-widget
@@ -231,29 +230,122 @@ EOFZF
echo "OK"
done
echo
for shell in bash zsh; do
rc=~/.${shell}rc
src="source ~/.fzf.${shell}"
# fish
has_fish=0
if [ -n "$(which fish)" ]; then
has_fish=1
echo -n "Generate ~/.config/fish/functions/fzf.fish ... "
mkdir -p ~/.config/fish/functions
cat > ~/.config/fish/functions/fzf.fish << EOFZF
function fzf
$fzf_cmd \$argv
end
EOFZF
echo "OK"
echo "Update $rc:"
echo " - $src"
line=$(grep -nF "$src" $rc | sed 's/:.*//')
if [ $key_bindings -eq 0 ]; then
echo -n "Generate ~/.config/fish/functions/fzf_key_bindings.fish ... "
cat > ~/.config/fish/functions/fzf_key_bindings.fish << "EOFZF"
function fzf_key_bindings
function __fzf_select
find * -path '*/\.*' -prune \
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | fzf -m | while read item
echo -n (echo -n "$item" | sed 's/ /\\\\ /g')' '
end
echo
end
function __fzf_ctrl_t
if [ -n "$TMUX_PANE" -a "$FZF_TMUX" != "0" ]
tmux split-window (__fzf_tmux_height) "fish -c 'fzf_key_bindings; __fzf_ctrl_t_tmux \\$TMUX_PANE'"
else
__fzf_select > $TMPDIR/fzf.result
and commandline -i (cat $TMPDIR/fzf.result)
rm -f $TMPDIR/fzf.result
end
end
function __fzf_ctrl_t_tmux
__fzf_select > $TMPDIR/fzf.result
and tmux send-keys -t $argv[1] (cat $TMPDIR/fzf.result)
rm -f $TMPDIR/fzf.result
end
function __fzf_ctrl_r
if history | fzf +s +m > $TMPDIR/fzf.result
commandline (cat $TMPDIR/fzf.result)
else
commandline -f repaint
end
rm -f $TMPDIR/fzf.result
end
function __fzf_alt_c
find * -path '*/\.*' -prune -o -type d -print 2> /dev/null | fzf +m > $TMPDIR/fzf.result
if [ (cat $TMPDIR/fzf.result | wc -l) -gt 0 ]
cd (cat $TMPDIR/fzf.result)
end
commandline -f repaint
rm -f $TMPDIR/fzf.result
end
function __fzf_tmux_height
if set -q FZF_TMUX_HEIGHT
set height $FZF_TMUX_HEIGHT
else
set height 40%
end
if echo $height | grep -q -E '%$'
echo "-p "(echo $height | sed 's/%$//')
else
echo "-l $height"
end
set -e height
end
bind \ct '__fzf_ctrl_t'
bind \cr '__fzf_ctrl_r'
bind \ec '__fzf_alt_c'
end
EOFZF
echo "OK"
fi
fi
append_line() {
echo "Update $2:"
echo " - $1"
[ -f "$2" ] || touch "$2"
line=$(grep -nF "$1" "$2" | sed 's/:.*//')
if [ -n "$line" ]; then
echo " - Already exists (line #$line)"
else
echo $src >> $rc
echo "$1" >> "$2"
echo " - Added"
fi
echo
}
echo
for shell in bash zsh; do
append_line "source ~/.fzf.${shell}" ~/.${shell}rc
done
if [ $key_bindings -eq 0 -a $has_fish -eq 1 ]; then
bind_file=~/.config/fish/functions/fish_user_key_bindings.fish
append_line "fzf_key_bindings" "$bind_file"
fi
cat << EOF
Finished. Reload your .bashrc or .zshrc.
Finished. Restart your shell or reload config file.
source ~/.bashrc # bash
source ~/.zshrc # zsh
EOF
[ $has_fish -eq 1 ] && echo " fzf_key_bindings # fish"; cat << EOF
To uninstall fzf, simply remove the added line.
Use uninstall script to remove fzf.
For more information, see: https://github.com/junegunn/fzf
EOF

View File

@@ -21,8 +21,10 @@
" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
let s:min_tmux_width = 10
let s:min_tmux_height = 3
let s:default_tmux_height = '40%'
let s:launcher = 'xterm -e bash -ic %s'
let s:cpo_save = &cpo
set cpo&vim
@@ -41,6 +43,10 @@ else
endif
function! s:tmux_enabled()
if has('gui_running')
return 0
endif
if exists('s:tmux')
return s:tmux
endif
@@ -62,11 +68,6 @@ function! s:escape(path)
endfunction
function! fzf#run(...) abort
if has('gui_running')
echohl Error
echo 'GVim is not supported'
return []
endif
let dict = exists('a:1') ? a:1 : {}
let temps = { 'result': tempname() }
let optstr = get(dict, 'options', '')
@@ -88,14 +89,19 @@ function! fzf#run(...) abort
endif
let command = prefix.s:exec.' '.optstr.' > '.temps.result
if s:tmux_enabled() && has_key(dict, 'tmux') &&
\ dict.tmux > 0 && winheight(0) >= s:min_tmux_height
if s:tmux_enabled() && s:tmux_splittable(dict)
return s:execute_tmux(dict, command, temps)
else
return s:execute(dict, command, temps)
endif
endfunction
function! s:tmux_splittable(dict)
return
\ min([&columns, get(a:dict, 'tmux_width', 0)]) >= s:min_tmux_width ||
\ min([&lines, get(a:dict, 'tmux_height', get(a:dict, 'tmux', 0))]) >= s:min_tmux_height
endfunction
function! s:pushd(dict)
if has_key(a:dict, 'dir')
let a:dict.prev_dir = getcwd()
@@ -112,9 +118,17 @@ endfunction
function! s:execute(dict, command, temps)
call s:pushd(a:dict)
silent !clear
execute 'silent !'.a:command
if has('gui_running')
let launcher = get(a:dict, 'launcher', get(g:, 'fzf_launcher', s:launcher))
let command = printf(launcher, "'".substitute(a:command, "'", "'\"'\"'", 'g')."'")
else
let command = a:command
endif
execute 'silent !'.command
redraw!
if v:shell_error
echohl Error
echo 'Error running ' . command
return []
else
return s:callback(a:dict, a:temps, 0)
@@ -128,17 +142,25 @@ function! s:execute_tmux(dict, command, temps)
let command = a:command
endif
if type(a:dict.tmux) == 1 && a:dict.tmux =~ '%$'
let height = '-p '.a:dict.tmux[0:-2]
let splitopt = '-v'
if has_key(a:dict, 'tmux_width')
let splitopt = '-h'
let size = a:dict.tmux_width
else
let height = '-l '.a:dict.tmux
let size = get(a:dict, 'tmux_height', get(a:dict, 'tmux'))
endif
if type(size) == 1 && size =~ '%$'
let sizeopt = '-p '.size[0:-2]
else
let sizeopt = '-l '.size
endif
let s:pane = substitute(
\ system(
\ printf(
\ 'tmux split-window %s -P -F "#{pane_id}" %s',
\ height, s:shellesc(command))), '\n', '', 'g')
\ 'tmux split-window %s %s -P -F "#{pane_id}" %s',
\ splitopt, sizeopt, s:shellesc(command))), '\n', '', 'g')
let s:dict = a:dict
let s:temps = a:temps

View File

@@ -32,6 +32,7 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal false, fzf.exit0
assert_equal nil, fzf.filter
assert_equal nil, fzf.extended
assert_equal false, fzf.reverse
end
def test_environment_variables
@@ -43,7 +44,7 @@ class TestFZF < MiniTest::Unit::TestCase
ENV['FZF_DEFAULT_OPTS'] =
'-x -m -s 10000 -q " hello world " +c +2 --select-1 -0 ' +
'--no-mouse -f "goodbye world" --black --nth=3,-1,2'
'--no-mouse -f "goodbye world" --black --nth=3,-1,2 --reverse'
fzf = FZF.new []
assert_equal 10000, fzf.sort
assert_equal ' hello world ',
@@ -58,14 +59,15 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal false, fzf.mouse
assert_equal true, fzf.select1
assert_equal true, fzf.exit0
assert_equal [3, -1, 2], fzf.nth
assert_equal true, fzf.reverse
assert_equal [2..2, -1..-1, 1..1], fzf.nth
end
def test_option_parser
# Long opts
fzf = FZF.new %w[--sort=2000 --no-color --multi +i --query hello --select-1
--exit-0 --filter=howdy --extended-exact
--no-mouse --no-256 --nth=1]
--no-mouse --no-256 --nth=1 --reverse]
assert_equal 2000, fzf.sort
assert_equal true, fzf.multi
assert_equal false, fzf.color
@@ -78,13 +80,15 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal true, fzf.exit0
assert_equal 'howdy', fzf.filter
assert_equal :exact, fzf.extended
assert_equal [1], fzf.nth
assert_equal [0..0], fzf.nth
assert_equal true, fzf.reverse
# Long opts (left-to-right)
fzf = FZF.new %w[--sort=2000 --no-color --multi +i --query=hello
--filter a --filter b --no-256 --black --nth -1 --nth -2
--select-1 --exit-0 --no-select-1 --no-exit-0
--no-sort -i --color --no-multi --256]
--no-sort -i --color --no-multi --256
--reverse --no-reverse]
assert_equal nil, fzf.sort
assert_equal false, fzf.multi
assert_equal true, fzf.color
@@ -97,7 +101,8 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal false, fzf.select1
assert_equal false, fzf.exit0
assert_equal nil, fzf.extended
assert_equal [-2], fzf.nth
assert_equal [-2..-2], fzf.nth
assert_equal false, fzf.reverse
# Short opts
fzf = FZF.new %w[-s2000 +c -m +i -qhello -x -fhowdy +2 -n3 -1 -0]
@@ -109,7 +114,7 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal 'hello', fzf.query.get
assert_equal 'howdy', fzf.filter
assert_equal :fuzzy, fzf.extended
assert_equal [3], fzf.nth
assert_equal [2..2], fzf.nth
assert_equal true, fzf.select1
assert_equal true, fzf.exit0
@@ -129,23 +134,28 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal false, fzf.exit0
assert_equal 'world', fzf.filter
assert_equal nil, fzf.extended
assert_equal [4, 5], fzf.nth
assert_equal [3..3, 4..4], fzf.nth
rescue SystemExit => e
assert false, "Exited"
end
def test_invalid_option
[%w[--unknown], %w[yo dawg]].each do |argv|
[
%w[--unknown],
%w[yo dawg],
%w[--nth=0],
%w[-n 0],
%w[-n 1..2..3],
%w[-n 1....],
%w[-n ....3],
%w[-n 1....3],
%w[-n 1..0],
%w[--nth ..0],
].each do |argv|
assert_raises(SystemExit) do
fzf = FZF.new argv
end
end
assert_raises(SystemExit) do
fzf = FZF.new %w[--nth=0]
end
assert_raises(SystemExit) do
fzf = FZF.new %w[-n 0]
end
end
# FIXME Only on 1.9 or above
@@ -445,58 +455,11 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal 2, exact.match(list, "-fuzzy", '', '').length
end
if RUBY_PLATFORM =~ /darwin/
NFD = '한글'
def test_nfc
assert_equal 6, NFD.length
assert_equal ["한글", [[0, 1], [1, 2]]],
FZF::UConv.nfc(NFD, [[0, 3], [3, 6]])
nfd2 = 'before' + NFD + 'after'
assert_equal 6 + 6 + 5, nfd2.length
nfc, offsets = FZF::UConv.nfc(nfd2, [[4, 14], [9, 13]])
o1, o2 = offsets
assert_equal 'before한글after', nfc
assert_equal 're한글af', nfc[(o1.first...o1.last)]
assert_equal '글a', nfc[(o2.first...o2.last)]
end
def test_nfd
nfc = '한글'
nfd = FZF::UConv.nfd(nfc)
assert_equal 2, nfd.length
assert_equal 6, nfd.join.length
assert_equal NFD, nfd.join
end
def test_nfd_fuzzy_matcher
matcher = FZF::FuzzyMatcher.new 0
assert_equal [], matcher.match([NFD + NFD], '할', '', '')
match = matcher.match([NFD + NFD], '글글', '', '')
assert_equal [[NFD + NFD, [[3, 12]]]], match
assert_equal ['한글한글', [[1, 4]]], FZF::UConv.nfc(*match.first)
end
def test_nfd_extended_fuzzy_matcher
matcher = FZF::ExtendedFuzzyMatcher.new 0
assert_equal [], matcher.match([NFD], "'글글", '', '')
match = matcher.match([NFD], "'한글", '', '')
assert_equal [[NFD, [[0, 6]]]], match
assert_equal ['한글', [[0, 2]]], FZF::UConv.nfc(*match.first)
end
end
def test_split
assert_equal ["a", "b", "c", "\xFF", "d", "e", "f"],
FZF::UConv.split("abc\xFFdef")
end
# ^$ -> matches empty item
def test_format_empty_item
fzf = FZF.new []
item = ['', [[0, 0]]]
line, offsets = fzf.convert_item item
line, offsets = item
tokens = fzf.format line, 80, offsets
assert_equal [], tokens
end
@@ -529,43 +492,67 @@ class TestFZF < MiniTest::Unit::TestCase
[list[0], [[2, 5]]],
[list[1], [[9, 17]]]], matcher.match(list, 'is', '', '')
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [2]
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [1..1]
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]
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [2..2]
assert_equal [[list[0], [[19, 20]]]], matcher.match(list, 'r', '', '')
# Comma-separated
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [3, 1]
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [2..2, 0..0]
assert_equal [[list[0], [[19, 20]]], [list[1], [[3, 4]]]], matcher.match(list, 'r', '', '')
# Ordered
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [1, 3]
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [0..0, 2..2]
assert_equal [[list[0], [[3, 4]]], [list[1], [[3, 4]]]], matcher.match(list, 'r', '', '')
regex = FZF.build_delim_regex "\t"
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [1], regex
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [0..0], regex
assert_equal [[list[0], [[3, 10]]]], matcher.match(list, 're', '', '')
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [2], regex
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [1..1], 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
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [-1..-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
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [0..0], regex
assert_equal [list[1]], matcher.match(list, 'f', '', '').map(&:first)
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [2], regex
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [1..1], regex
assert_equal [[list[0], [[1, 2]]], [list[1], [[8, 9]]]], matcher.match(list, 'f', '', '')
end
def test_nth_match_range
list = [
' first second third',
'fourth fifth sixth',
]
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [1..2]
assert_equal [[list[0], [[8, 20]]]], matcher.match(list, 'sr', '', '')
assert_equal [], matcher.match(list, 'fo', '', '')
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [1..-1, 0..0]
assert_equal [[list[0], [[8, 20]]]], matcher.match(list, 'sr', '', '')
assert_equal [[list[1], [[0, 2]]]], matcher.match(list, 'fo', '', '')
matcher = FZF::ExtendedFuzzyMatcher.new Regexp::IGNORECASE, :fuzzy, [0..0, 1..2]
assert_equal [], matcher.match(list, '^t', '', '')
matcher = FZF::ExtendedFuzzyMatcher.new Regexp::IGNORECASE, :fuzzy, [0..1, 2..2]
assert_equal [[list[0], [[16, 17]]]], matcher.match(list, '^t', '', '')
matcher = FZF::ExtendedFuzzyMatcher.new Regexp::IGNORECASE, :fuzzy, [1..-1]
assert_equal [[list[0], [[8, 9]]]], matcher.match(list, '^s', '', '')
end
def stream_for str
StringIO.new(str).tap do |sio|
sio.instance_eval do

70
uninstall Executable file
View File

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