Compare commits

...

50 Commits
0.8.4 ... 0.8.6

Author SHA1 Message Date
Junegunn Choi
5390616694 [bash-completion] Export _fzf_orig_completion_xxx 2014-07-07 00:02:09 +09:00
Junegunn Choi
daf08f801f [fish] Fix fish key binding issues (#60)
Although a major overhaul is ongoing (#67), it is not yet finished and
cannot be considered stable enough for the next release. This commit
fixes a few apparent issues with small change to the current
implementation.

- Fixed error when $TMPDIR is not defined
- Better escaping of file/directory names
- Splitted functions to workaround fish bug
2014-07-06 20:51:51 +09:00
Junegunn Choi
4e2a1fe5c8 Merge pull request #75 from junegunn/issue-72
[bash-completion] Fail back to original completion
2014-07-05 03:06:10 +09:00
Junegunn Choi
03f155484c [bash-completion] Merge eval statements into one 2014-07-04 21:05:46 +09:00
Junegunn Choi
89298a8d23 [vim] Do not print error message on exit status 1 2014-07-04 18:35:04 +09:00
Junegunn Choi
3b14c5230c [bash-completion] Fail back to original completion (#72) 2014-07-04 18:30:54 +09:00
Junegunn Choi
91401514ab Merge pull request #71 from junegunn/issue-70
Add options: --prompt and --print-query
2014-07-01 01:23:07 +09:00
Junegunn Choi
91d986b6c0 Update README (--print-query) 2014-06-30 12:24:40 +09:00
Junegunn Choi
4d72bd098a Add --print-query option (#70) 2014-06-30 12:23:37 +09:00
Junegunn Choi
502973ff75 Add --prompt option (#70) 2014-06-30 12:00:59 +09:00
Junegunn Choi
3e91c189ae [vim] Defer type fzf to reduce startup time 2014-06-27 21:03:25 +09:00
Junegunn Choi
b0f80b686c chmod +x fzf 2014-06-27 20:51:54 +09:00
Junegunn Choi
b824928b0b Merge pull request #69 from junegunn/scrollable
Make the list scrollable
2014-06-27 13:32:04 +09:00
Junegunn Choi
ccca34f9f7 Minor refactoring 2014-06-27 12:35:30 +09:00
Junegunn Choi
b5350b24ff Avoid unnecessary redraw 2014-06-27 08:28:32 +09:00
Junegunn Choi
56ace10a37 Fix mouse-click on --reverse mode 2014-06-27 00:05:01 +09:00
Junegunn Choi
72ec0a3408 Add test cases for result scroll 2014-06-26 19:40:29 +09:00
Junegunn Choi
05118cc440 Minor corrections
- Suppress warning message on Ruby 1.8.5
- Remove unnecessary code
2014-06-26 15:35:04 +09:00
Junegunn Choi
e392da20e8 Make scrollable (#68) 2014-06-26 12:51:40 +09:00
Junegunn Choi
6e69339f6b Merge pull request #66 from patspam/master
Add vi-command keymap mappings
2014-06-24 00:18:09 +09:00
Patrick Donelan
30cdc06bcd Add vi-command keymap mappings
fzf does not currently define vi-command mode mappings. This is particularly annoying for <C-r>, which opens bash's old-fashioned recursive history search.

This patch adds vi-command mode mappings that simply drop back into vi-insert mode ("i") and then trigger the primary mapping.
2014-06-23 17:14:16 +02:00
Junegunn Choi
9ce43d46f6 Guide on running fzf with MacVim and iTerm2 (#65) 2014-06-23 23:30:00 +09:00
Junegunn Choi
de09656197 Merge pull request #57 from sencer/master
Use `command find` rather than plain `find`
2014-06-19 00:37:36 +09:00
Sencer Selcuk
3827a1b09e Use command find rather than plain find
Aliases are expanded in shell scripts, and one may have an alias
for the `find` command that conflicts with fzf. So make sure fzf
is using real find command rather than the alias.
2014-06-18 11:33:40 -04:00
Junegunn Choi
61ba8d5a11 Add a small delay when search is interrupted
Search is interrupted when the query string has changed. This frequently
happens when the user is actively typing in a query. This (rather
arbitrary) delay is introduced not to start the next search immediately,
which is likely to be interrupted as well. The result of it is that fzf
feels more responsive.
2014-06-15 18:35:47 +09:00
Junegunn Choi
4a3a5ee70d [vim] External terminal emulator for GVim 2014-06-15 12:15:39 +09:00
Junegunn Choi
f58a53a001 Fix mouse click on --reverse mode 2014-06-15 04:32:21 +09:00
Junegunn Choi
65c1b53275 [vim] Options to xterm command 2014-06-15 03:18:29 +09:00
Junegunn Choi
0b43f988c7 [vim] Enable fzf in GVim using xterm 2014-06-15 03:06:26 +09:00
Junegunn Choi
f8e357fa19 Extend --nth option to take ranges
As discussed in #55
2014-06-14 00:27:34 +09:00
Junegunn Choi
c3a4e4cd23 Implement CTRL-D 2014-06-12 23:43:09 +09:00
Junegunn Choi
9dac12cb32 Remove duplicate examples from README
As discussed in #54
2014-06-12 23:28:59 +09:00
Junegunn Choi
d76a3646b7 Update Vim example: Rename functions
See: ftp://ftp.vim.org/pub/vim/patches/7.4/7.4.260
2014-06-09 10:06:07 +09:00
Junegunn Choi
d7c734acd6 Ignore regex error inside trim call (#51) 2014-06-07 01:17:38 +09:00
Junegunn Choi
ed13fc8618 Fix fzf-history-widget (#48) 2014-05-29 11:12:48 +09:00
Junegunn Choi
edcd7c6aa6 Remove UTF-8 NFD conversion
We have iconv.
2014-05-29 01:08:44 +09:00
Junegunn Choi
b0fdd6db99 Merge pull request #47 from cskeeters/master
[zsh-keybinding] Remove tailing substitution
2014-05-29 00:20:08 +09:00
Chad Skeeters
edf27f47f2 removed tailing substitution causing all trailing space to be removed when extendedglob is set 2014-05-28 10:16:07 -05:00
Junegunn Choi
3b218b77eb Merge pull request #46 from takac/install-update
Fix fzf-history-widget to strip `*` from history lines when using tmux and fc
2014-05-26 16:59:56 +09:00
Tom Cammann
1e02471940 Update install
Update sed regex to strip "*" from history lines when using tmux and fc
e.g. "637* ls -a"
2014-05-26 08:56:47 +01:00
Junegunn Choi
1b9dadb3d3 Avoid unnecessary confirmation 2014-05-22 02:34:20 +09:00
Junegunn Choi
c3827dea10 Add linewise user confirmation 2014-05-22 02:26:59 +09:00
Junegunn Choi
6a1b916598 OK 2014-05-22 02:24:13 +09:00
Junegunn Choi
a2c7b001d5 Update version/date 2014-05-21 10:43:30 +09:00
Junegunn Choi
3c6e938bb1 Fix arrow keys on zsh widget
Fixes the problem reported by @elemakil. For some reason unknown,
sometimes the escape sequences of arrow keys are prefixed by 27-79
instead of the ordinary 27-91.
2014-05-21 10:22:17 +09:00
Junegunn Choi
5a0afc5fea Merge branch 'aboettger-master' 2014-05-21 01:16:49 +09:00
Junegunn Choi
f37be006c3 Update uninstall script 2014-05-21 01:16:42 +09:00
Andreas Böttger
459c332351 Some improvements 2014-05-20 17:05:02 +02:00
Andreas Böttger
153a87d84a uninstall script 2014-05-20 14:17:03 +02:00
Junegunn Choi
05da892cd2 On writing fzf-tmux combo 2014-05-18 11:01:30 +09:00
7 changed files with 603 additions and 456 deletions

113
README.md
View File

@@ -65,8 +65,9 @@ usage: fzf [options]
-e, --extended-exact Extended-search mode (exact match) -e, --extended-exact Extended-search mode (exact match)
-i Case-insensitive match (default: smart-case match) -i Case-insensitive match (default: smart-case match)
+i Case-sensitive match +i Case-sensitive match
-n, --nth=[-]N[,..] Comma-separated list of field indexes for limiting -n, --nth=N[,..] Comma-separated list of field index expressions
search scope (positive or negative integers) 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) -d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style)
Search result Search result
@@ -80,12 +81,14 @@ usage: fzf [options]
+2, --no-256 Disable 256-color +2, --no-256 Disable 256-color
--black Use black background --black Use black background
--reverse Reverse orientation --reverse Reverse orientation
--prompt=STR Input prompt (default: '> ')
Scripting Scripting
-q, --query=STR Start the finder with the given query -q, --query=STR Start the finder with the given query
-1, --select-1 Automatically select the only match -1, --select-1 Automatically select the only match
-0, --exit-0 Exit immediately when there's no match -0, --exit-0 Exit immediately when there's no match
-f, --filter=STR Filter mode. Do not start interactive finder. -f, --filter=STR Filter mode. Do not start interactive finder.
--print-query Print query as the first line
Environment variables Environment variables
FZF_DEFAULT_COMMAND Default command to use when input is tty FZF_DEFAULT_COMMAND Default command to use when input is tty
@@ -123,6 +126,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-H / CTRL-D
- CTRL-W / CTRL-U / CTRL-Y - CTRL-W / CTRL-U / CTRL-Y
- ALT-B / ALT-F - ALT-B / ALT-F
@@ -173,12 +177,6 @@ fd() {
cd "$dir" 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 - repeat history
fh() { fh() {
eval $(([ -n "$ZSH_NAME" ] && fc -l 1 || history) | fzf +s | sed 's/ *[0-9]* *//') eval $(([ -n "$ZSH_NAME" ] && fc -l 1 || history) | fzf +s | sed 's/ *[0-9]* *//')
@@ -188,33 +186,6 @@ 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=$(
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 For more examples, see [the wiki
@@ -318,7 +289,7 @@ TODO :smiley:
Usage as Vim plugin 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[!]` ### `:FZF[!]`
@@ -342,6 +313,22 @@ 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 height can be adjusted with `g:fzf_tmux_height` (default: '40%'). However, the
bang version (`:FZF!`) will always start in fullscreen. 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'
```
If you're running MacVim on OSX, I recommend you to use iTerm2 as the launcher.
Refer to the [this wiki
page](https://github.com/junegunn/fzf/wiki/fzf-with-MacVim-and-iTerm2) to see
how to set up.
### `fzf#run([options])` ### `fzf#run([options])`
For more advanced uses, you can call `fzf#run()` function which returns the list For more advanced uses, you can call `fzf#run()` function which returns the list
@@ -350,7 +337,7 @@ of the selected items.
`fzf#run()` may take an options-dictionary: `fzf#run()` may take an options-dictionary:
| Option name | Type | Description | | Option name | Type | Description |
| ------------- | ------------- | ------------------------------------------------------------------ | | --------------- | ------------- | ------------------------------------------------------------------ |
| `source` | string | External command to generate input to fzf (e.g. `find .`) | | `source` | string | External command to generate input to fzf (e.g. `find .`) |
| `source` | list | Vim list as input to fzf | | `source` | list | Vim list as input to fzf |
| `sink` | string | Vim command to handle the selected item (e.g. `e`, `tabe`) | | `sink` | string | Vim command to handle the selected item (e.g. `e`, `tabe`) |
@@ -359,6 +346,7 @@ of the selected items.
| `dir` | string | Working directory | | `dir` | string | Working directory |
| `tmux_width` | number/string | Use tmux vertical split with the given height (e.g. `20`, `50%`) | | `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%`) | | `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 #### Examples
@@ -386,7 +374,8 @@ nnoremap <silent> <Leader>C :call fzf#run({
\ "substitute(fnamemodify(v:val, ':t'), '\\..\\{-}$', '', '')"), \ "substitute(fnamemodify(v:val, ':t'), '\\..\\{-}$', '', '')"),
\ 'sink': 'colo', \ 'sink': 'colo',
\ 'options': '+m', \ 'options': '+m',
\ 'tmux_width': 20 \ 'tmux_width': 20,
\ 'launcher': 'xterm -geometry 20x30 -e bash -ic %s'
\ })<CR> \ })<CR>
``` ```
@@ -395,20 +384,20 @@ handy mapping that selects an open buffer.
```vim ```vim
" List of buffers " List of buffers
function! g:buflist() function! BufList()
redir => ls redir => ls
silent ls silent ls
redir END redir END
return split(ls, '\n') return split(ls, '\n')
endfunction endfunction
function! g:bufopen(e) function! BufOpen(e)
execute 'buffer '. matchstr(a:e, '^[ 0-9]*') execute 'buffer '. matchstr(a:e, '^[ 0-9]*')
endfunction endfunction
nnoremap <silent> <Leader><Enter> :call fzf#run({ nnoremap <silent> <Leader><Enter> :call fzf#run({
\ 'source': reverse(g:buflist()), \ 'source': reverse(BufList()),
\ 'sink': function('g:bufopen'), \ 'sink': function('BufOpen'),
\ 'options': '+m', \ 'options': '+m',
\ 'tmux_height': '40%' \ 'tmux_height': '40%'
\ })<CR> \ })<CR>
@@ -487,6 +476,39 @@ fzf() {
} }
``` ```
### 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 ### Fish shell
It's [a known bug of fish](https://github.com/fish-shell/fish-shell/issues/1362) It's [a known bug of fish](https://github.com/fish-shell/fish-shell/issues/1362)
@@ -516,6 +538,13 @@ fzf works on [Cygwin](http://www.cygwin.com/) and
[MSYS2](http://sourceforge.net/projects/msys2/). You may need to use `--black` [MSYS2](http://sourceforge.net/projects/msys2/). You may need to use `--black`
option on MSYS2 to avoid rendering issues. 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 License
------- -------

381
fzf
View File

@@ -7,7 +7,7 @@
# / __/ / /_/ __/ # / __/ / /_/ __/
# /_/ /___/_/ Fuzzy finder for your shell # /_/ /___/_/ Fuzzy finder for your shell
# #
# Version: 0.8.4 (May 17, 2014) # Version: 0.8.6 (Jun 30, 2014)
# #
# Author: Junegunn Choi # Author: Junegunn Choi
# URL: https://github.com/junegunn/fzf # URL: https://github.com/junegunn/fzf
@@ -50,27 +50,30 @@ end
class FZF class FZF
C = Curses C = Curses
attr_reader :rxflag, :sort, :nth, :color, :black, :ansi256, :reverse, attr_reader :rxflag, :sort, :nth, :color, :black, :ansi256, :reverse, :prompt,
:mouse, :multi, :query, :select1, :exit0, :filter, :extended :mouse, :multi, :query, :select1, :exit0, :filter, :extended,
:print_query
class AtomicVar def sync
def initialize value @shr_mtx.synchronize { yield }
@value = value
@mutex = Mutex.new
end end
def get def get name
@mutex.synchronize { @value } sync { instance_variable_get name }
end end
def set value = nil def geta(*names)
@mutex.synchronize do sync { names.map { |name| instance_variable_get name } }
@value = block_given? ? yield(@value) : value
end
end end
def method_missing sym, *args, &blk def call(name, method, *args)
@mutex.synchronize { @value.send(sym, *args, &blk) } sync { instance_variable_get(name).send(method, *args) }
end
def set name, value = nil
sync do
instance_variable_set name,
(block_given? ? yield(instance_variable_get(name)) : value)
end end
end end
@@ -89,6 +92,9 @@ class FZF
@nth = nil @nth = nil
@delim = nil @delim = nil
@reverse = false @reverse = false
@prompt = '> '
@shr_mtx = Mutex.new
@print_query = false
argv = argv =
if opts = ENV['FZF_DEFAULT_OPTS'] if opts = ENV['FZF_DEFAULT_OPTS']
@@ -124,19 +130,18 @@ class FZF
when '+0', '--no-exit-0' then @exit0 = false when '+0', '--no-exit-0' then @exit0 = false
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 = query
when /^-q(.*)$/, /^--query=(.*)$/ when /^-q(.*)$/, /^--query=(.*)$/
@query = AtomicVar.new($1) @query = $1
when '-f', '--filter' when '-f', '--filter'
usage 1, 'query string required' unless query = argv.shift usage 1, 'query string required' unless query = argv.shift
@filter = query @filter = query
when /^-f(.*)$/, /^--filter=(.*)$/ when /^-f(.*)$/, /^--filter=(.*)$/
@filter = $1 @filter = $1
when '-n', '--nth' when '-n', '--nth'
usage 1, 'field number required' unless nth = argv.shift usage 1, 'field expression required' unless nth = argv.shift
usage 1, 'invalid field number' if nth.to_i == 0
@nth = parse_nth nth @nth = parse_nth nth
when /^-n([0-9,-]+)$/, /^--nth=([0-9,-]+)$/ when /^-n([0-9,-\.]+)$/, /^--nth=([0-9,-\.]+)$/
@nth = parse_nth $1 @nth = parse_nth $1
when '-d', '--delimiter' when '-d', '--delimiter'
usage 1, 'delimiter required' unless delim = argv.shift usage 1, 'delimiter required' unless delim = argv.shift
@@ -149,6 +154,13 @@ class FZF
@sort = sort.to_i @sort = sort.to_i
when /^-s([0-9]+)$/, /^--sort=([0-9]+)$/ when /^-s([0-9]+)$/, /^--sort=([0-9]+)$/
@sort = $1.to_i @sort = $1.to_i
when '--prompt'
usage 1, 'prompt string required' unless prompt = argv.shift
@prompt = prompt
when /^--prompt=(.*)$/
@prompt = $1
when '--print-query' then @print_query = true
when '--no-print-query' then @print_query = false
when '-e', '--extended-exact' then @extended = :exact when '-e', '--extended-exact' then @extended = :exact
when '+e', '--no-extended-exact' then @extended = nil when '+e', '--no-extended-exact' then @extended = nil
else else
@@ -157,32 +169,46 @@ class FZF
end end
@source = source.clone @source = source.clone
@mtx = Mutex.new @evt_mtx = Mutex.new
@cv = ConditionVariable.new @cv = ConditionVariable.new
@events = {} @events = {}
@new = [] @new = []
@queue = Queue.new @queue = Queue.new
@pending = nil @pending = nil
@rev_dir = @reverse ? -1 : 1
unless @filter unless @filter
@query ||= AtomicVar.new('') # Shared variables: needs protection
@cursor_x = AtomicVar.new(@query.length) @query ||= ''
@matches = AtomicVar.new([]) @matches = []
@count = AtomicVar.new(0) @count = 0
@vcursor = AtomicVar.new(0) @xcur = @query.length
@vcursors = AtomicVar.new(Set.new) @ycur = 0
@spinner = AtomicVar.new('-\|/-\|/'.split(//)) @yoff = 0
@selects = AtomicVar.new({}) # ordered >= 1.9 @dirty = Set.new
@spinner = '-\|/-\|/'.split(//)
@selects = {} # ordered >= 1.9
@main = Thread.current @main = Thread.current
@plcount = 0 @plcount = 0
end end
end end
def parse_nth nth def parse_nth nth
nth.split(',').map { |n| nth.split(',').map { |expr|
ni = n.to_i x = proc { usage 1, "invalid field expression: #{expr}" }
usage 1, "invalid field number: #{n}" if ni == 0 first, second = expr.split('..', 2)
ni 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 end
@@ -197,15 +223,18 @@ class FZF
filter_list @new filter_list @new
else else
start_reader start_reader
emit(:key) { q = @query.get; [q, q.length] } unless empty = @query.empty? query = get(:@query)
emit(:key) { [query, query.length] } unless empty = query.empty?
if @select1 || @exit0 if @select1 || @exit0
start_search do |loaded, matches| start_search do |loaded, matches|
len = empty ? @count.get : matches.length len = empty ? get(:@count) : matches.length
if loaded if loaded
if @select1 && len == 1 if @select1 && len == 1
puts @query if @print_query
puts empty ? matches.first : matches.first.first puts empty ? matches.first : matches.first.first
exit 0 exit 0
elsif @exit0 && len == 0 elsif @exit0 && len == 0
puts @query if @print_query
exit 0 exit 0
end end
end end
@@ -226,6 +255,7 @@ class FZF
end end
def filter_list list def filter_list list
puts @filter if @print_query
matches = matcher.match(list, @filter, '', '') matches = matcher.match(list, @filter, '', '')
if @sort && matches.length <= @sort if @sort && matches.length <= @sort
matches = FZF.sort(matches) matches = FZF.sort(matches)
@@ -281,8 +311,9 @@ class FZF
-e, --extended-exact Extended-search mode (exact match) -e, --extended-exact Extended-search mode (exact match)
-i Case-insensitive match (default: smart-case match) -i Case-insensitive match (default: smart-case match)
+i Case-sensitive match +i Case-sensitive match
-n, --nth=[-]N[,..] Comma-separated list of field indexes for limiting -n, --nth=N[,..] Comma-separated list of field index expressions
search scope (positive or negative integers) 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) -d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style)
Search result Search result
@@ -296,12 +327,14 @@ class FZF
+2, --no-256 Disable 256-color +2, --no-256 Disable 256-color
--black Use black background --black Use black background
--reverse Reverse orientation --reverse Reverse orientation
--prompt=STR Input prompt (default: '> ')
Scripting Scripting
-q, --query=STR Start the finder with the given query -q, --query=STR Start the finder with the given query
-1, --select-1 Automatically select the only match -1, --select-1 Automatically select the only match
-0, --exit-0 Exit immediately when there's no match -0, --exit-0 Exit immediately when there's no match
-f, --filter=STR Filter mode. Do not start interactive finder. -f, --filter=STR Filter mode. Do not start interactive finder.
--print-query Print query as the first line
Environment variables Environment variables
FZF_DEFAULT_COMMAND Default command to use when input is tty FZF_DEFAULT_COMMAND Default command to use when input is tty
@@ -309,123 +342,8 @@ class FZF
exit x exit x
end 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 def emit event
@mtx.synchronize do @evt_mtx.synchronize do
@events[event] = yield @events[event] = yield
@cv.broadcast @cv.broadcast
end end
@@ -449,33 +367,37 @@ class FZF
def print_input def print_input
C.setpos cursor_y, 0 C.setpos cursor_y, 0
C.clrtoeol C.clrtoeol
cprint '> ', color(:prompt, true) cprint @prompt, color(:prompt, true)
C.attron(C::A_BOLD) do C.attron(C::A_BOLD) do
C.addstr @query.get C.addstr get(:@query)
end end
end end
def print_info msg = nil def print_info msg = nil
C.setpos cursor_y(1), 0 C.setpos cursor_y(1), 0
C.clrtoeol C.clrtoeol
prefix = prefix =
if spinner = @spinner.first if spin_char = call(:@spinner, :first)
cprint spinner, color(:spinner, true) cprint spin_char, color(:spinner, true)
' ' ' '
else else
' ' ' '
end end
C.attron color(:info, false) do C.attron color(:info, false) do
C.addstr "#{prefix}#{@matches.length}/#{@count.get}" sync do
C.addstr "#{prefix}#{@matches.length}/#{@count}"
if (selected = @selects.length) > 0 if (selected = @selects.length) > 0
C.addstr " (#{selected})" C.addstr " (#{selected})"
end end
end
C.addstr msg if msg C.addstr msg if msg
end end
end end
def refresh def refresh
C.setpos cursor_y, 2 + width(@query[0, @cursor_x.get]) query, xcur = geta(:@query, :@xcur)
C.setpos cursor_y, @prompt.length + width(query[0, xcur])
C.refresh C.refresh
end end
@@ -558,7 +480,7 @@ class FZF
width = width str width = width str
diff = 0 diff = 0
while width > len 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] str = left ? str[1..-1] : str[0...-1]
diff += 1 diff += 1
end end
@@ -685,12 +607,12 @@ class FZF
begin begin
while true while true
@mtx.synchronize do @evt_mtx.synchronize do
while true while true
events.merge! @events events.merge! @events
if @events.empty? # No new events if @events.empty? # No new events
@cv.wait @mtx @cv.wait @evt_mtx
next next
end end
@events.clear @events.clear
@@ -699,8 +621,8 @@ class FZF
if events[:new] if events[:new]
lists << @new lists << @new
@count.set { |c| c + @new.length } set(:@count) { |c| c + @new.length }
@spinner.set { |spinner| set(:@spinner) { |spinner|
if e = spinner.shift if e = spinner.shift
spinner.push e spinner.push e
end; spinner end; spinner
@@ -724,17 +646,20 @@ class FZF
cnt = 0 cnt = 0
lists.each do |list| lists.each do |list|
cnt += list.length cnt += list.length
skip = @mtx.synchronize { @events[:key] } skip = @evt_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 / get(:@count)) < 100 && Time.now - started_at > 0.5
render { print_info " (#{progress}%)" } render { print_info " (#{progress}%)" }
end end
found.concat(q.empty? ? list : found.concat(q.empty? ? list :
matcher.match(list, q, q[0, cx], q[cx..-1])) matcher.match(list, q, q[0, cx], q[cx..-1]))
end end
next if skip if skip
sleep 0.1
next
end
matches = @sort ? found : found.reverse matches = @sort ? found : found.reverse
if !empty && @sort && matches.length <= @sort if !empty && @sort && matches.length <= @sort
matches = FZF.sort(matches) matches = FZF.sort(matches)
@@ -743,7 +668,7 @@ class FZF
end end
# Atomic update # Atomic update
@matches.set matches set(:@matches, matches)
end#new_search end#new_search
callback = nil if callback && callback = nil if callback &&
@@ -762,14 +687,44 @@ class FZF
end end
def pick def pick
items = @matches[0, max_items] sync do
curr = [0, [@vcursor.get, items.length - 1].min].max [*@matches.fetch(@ycur, [])][0]
[*items.fetch(curr, [])][0] end
end
def constrain offset, cursor, count, height
original = [offset, cursor]
diffpos = cursor - offset
# Constrain cursor
cursor = [0, [cursor, count - 1].min].max
# Ceil
if cursor > offset + (height - 1)
offset = cursor - (height - 1)
# Floor
elsif offset > cursor
offset = cursor
end
# Adjustment
if count - offset < height
offset = [0, count - height].max
cursor = [0, [offset + diffpos, count - 1].min].max
end
[[offset, cursor] != original, offset, cursor]
end end
def update_list wipe def update_list wipe
render do render do
items = @matches[0, max_items] pos, items = sync {
changed, @yoff, @ycur =
constrain(@yoff, @ycur, @matches.length, max_items)
wipe ||= changed
[@ycur - @yoff, @matches[@yoff, max_items]]
}
# Wipe # Wipe
if items.length < @plcount if items.length < @plcount
@@ -780,20 +735,18 @@ class FZF
end end
@plcount = items.length @plcount = items.length
maxc = C.cols - 3 dirty = Set[pos]
vcursor = @vcursor.set { |v| [0, [v, items.length - 1].min].max } set(:@dirty) do |vs|
cleanse = Set[vcursor] dirty.merge vs
@vcursors.set { |vs|
cleanse.merge vs
Set.new Set.new
} end
items.each_with_index do |item, idx| items.each_with_index do |item, idx|
next unless wipe || cleanse.include?(idx) next unless wipe || dirty.include?(idx)
row = cursor_y(idx + 2) row = cursor_y(idx + 2)
chosen = idx == vcursor chosen = idx == pos
selected = @selects.include?([*item][0]) selected = @selects.include?([*item][0])
line, offsets = convert_item item line, offsets = item
tokens = format line, maxc, offsets tokens = format line, C.cols - 3, offsets
print_item row, tokens, chosen, selected print_item row, tokens, chosen, selected
end end
print_info print_info
@@ -822,7 +775,10 @@ class FZF
end end
def vselect &prc def vselect &prc
@vcursor.set { |v| @vcursors << v; prc.call v } sync do
@dirty << @ycur - @yoff
@ycur = prc.call @ycur
end
update_list false update_list false
end end
@@ -911,7 +867,7 @@ class FZF
ord = ord =
case read_nb(1, :esc) case read_nb(1, :esc)
when 91 when 91, 79
case read_nb(1, nil) case read_nb(1, nil)
when 68 then ctrl(:b) when 68 then ctrl(:b)
when 67 then ctrl(:f) when 67 then ctrl(:f)
@@ -987,7 +943,7 @@ class FZF
def start_loop def start_loop
got = nil got = nil
begin begin
input = @query.get.dup input = call(:@query, :dup)
cursor = input.length cursor = input.length
yanked = '' yanked = ''
mouse_event = MouseEvent.new mouse_event = MouseEvent.new
@@ -996,7 +952,13 @@ class FZF
} }
actions = { actions = {
:esc => proc { exit 1 }, :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 { ctrl(:m) => proc {
got = pick got = pick
exit 0 exit 0
@@ -1008,8 +970,8 @@ class FZF
}, },
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 - (@reverse ? -1 : 1) } }, ctrl(:j) => proc { vselect { |v| v - @rev_dir } },
ctrl(:k) => proc { vselect { |v| v + (@reverse ? -1 : 1) } }, ctrl(:k) => proc { vselect { |v| v + @rev_dir } },
ctrl(:w) => proc { ctrl(:w) => proc {
pcursor = cursor pcursor = cursor
backword.call backword.call
@@ -1020,24 +982,26 @@ class FZF
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
sync do
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
end
vselect { |v| v + case o vselect { |v| v + case o
when :stab then 1 when :stab then 1
when :sclick then 0 when :sclick then 0
else -1 else -1
end * (@reverse ? -1 : 1) } end * @rev_dir }
end end
}, },
ctrl(:b) => proc { cursor = [0, cursor - 1].max; nil }, ctrl(:b) => proc { cursor = [0, cursor - 1].max; nil },
ctrl(:f) => proc { cursor = [input.length, cursor + 1].min; nil }, ctrl(:f) => proc { cursor = [input.length, cursor + 1].min; nil },
ctrl(:l) => proc { render { C.clear; C.refresh }; update_list true }, ctrl(:l) => proc { render { C.clear; C.refresh }; update_list true },
:del => proc { input[cursor] = '' if input.length > cursor }, :del => proc { input[cursor] = '' if input.length > cursor },
:pgup => proc { vselect { |_| max_items } }, :pgup => proc { vselect { |v| v + @rev_dir * (max_items - 1) } },
:pgdn => proc { vselect { |_| 0 } }, :pgdn => proc { vselect { |v| v - @rev_dir * (max_items - 1) } },
: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
@@ -1053,10 +1017,11 @@ class FZF
case event case event
when :click, :release when :click, :release
x, y, shift = val.values_at :x, :y, :shift x, y, shift = val.values_at :x, :y, :shift
if y == cursor_y y = @reverse ? (C.lines - 1 - y) : y
cursor = [0, [input.length, x - 2].min].max if y == C.lines - 1
cursor = [0, [input.length, x - @prompt.length].min].max
elsif x > 1 && y <= max_items elsif x > 1 && y <= max_items
tv = max_items - y - 1 tv = get(:@yoff) + max_items - y - 1
case event case event
when :click when :click
@@ -1074,6 +1039,7 @@ class FZF
actions[ctrl(:i)].call(:sclick) if shift actions[ctrl(:i)].call(:sclick) if shift
actions[ctrl(diff > 0 ? :j : :k)].call actions[ctrl(diff > 0 ? :j : :k)].call
end end
nil
end end
} }
} }
@@ -1084,23 +1050,25 @@ class FZF
actions[ctrl(:q)] = actions[ctrl(:g)] = actions[ctrl(:c)] = actions[:esc] actions[ctrl(:q)] = actions[ctrl(:g)] = actions[ctrl(:c)] = actions[:esc]
while true while true
@cursor_x.set cursor set(:@xcur, cursor)
render { print_input } render { print_input }
if key = get_input(actions) if key = get_input(actions)
upd = actions.fetch(key, actions[:default]).call(key) upd = actions.fetch(key, actions[:default]).call(key)
# Dispatch key event # Dispatch key event
emit(:key) { [@query.set(input.dup), cursor] } if upd emit(:key) { [set(:@query, input.dup), cursor] } if upd
end end
end end
ensure ensure
C.close_screen C.close_screen
q, selects = geta(:@query, :@selects)
@stdout.puts q if @print_query
if got if got
if @selects.empty? if selects.empty?
@stdout.puts got @stdout.puts got
else else
@selects.each do |sel, _| selects.each do |sel, _|
@stdout.puts sel @stdout.puts sel
end end
end end
@@ -1120,7 +1088,7 @@ class FZF
end end
def initialize nth, delim def initialize nth, delim
@nth = nth && nth.map { |n| n > 0 ? n - 1 : n } @nth = nth
@delim = delim @delim = delim
@tokens_cache = {} @tokens_cache = {}
end end
@@ -1142,8 +1110,9 @@ class FZF
prefix_length, tokens = tokenize str prefix_length, tokens = tokenize str
@nth.each do |n| @nth.each do |n|
if (token = tokens[n]) && (md = token.match(pat) rescue nil) if (range = tokens[n]) && (token = range.join) &&
prefix_length += (tokens[0...n] || []).join.length (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 } offset = md.offset(0).map { |o| o + prefix_length }
return MatchData.new(offset) return MatchData.new(offset)
end end
@@ -1176,7 +1145,7 @@ class FZF
def fuzzy_regex q def fuzzy_regex q
@regexp[q] ||= begin @regexp[q] ||= begin
q = q.downcase if @rxflag == Regexp::IGNORECASE 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 e = Regexp.escape e
sum << (e.length > 1 ? "(?:#{e}).*?" : # FIXME: not equivalent sum << (e.length > 1 ? "(?:#{e}).*?" : # FIXME: not equivalent
"#{e}[^#{e}]*?") "#{e}[^#{e}]*?")
@@ -1234,7 +1203,7 @@ class FZF
when '' when ''
nil nil
when /^\^(.*)\$$/ when /^\^(.*)\$$/
Regexp.new('^' << sanitize(Regexp.escape($1)) << '$', rxflag_for(w)) Regexp.new('^' << Regexp.escape($1) << '$', rxflag_for(w))
when /^'/ when /^'/
if @mode == :fuzzy && w.length > 1 if @mode == :fuzzy && w.length > 1
exact_regex w[1..-1] exact_regex w[1..-1]
@@ -1243,10 +1212,10 @@ class FZF
end end
when /^\^/ when /^\^/
w.length > 1 ? 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 /\$$/ when /\$$/
w.length > 1 ? 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 else
@mode == :fuzzy ? fuzzy_regex(w) : exact_regex(w) @mode == :fuzzy ? fuzzy_regex(w) : exact_regex(w)
end, invert ] end, invert ]
@@ -1254,7 +1223,7 @@ class FZF
end end
def exact_regex w def exact_regex w
Regexp.new(sanitize(Regexp.escape(w)), rxflag_for(w)) Regexp.new(Regexp.escape(w), rxflag_for(w))
end end
def match list, q, prefix, suffix def match list, q, prefix, suffix

View File

@@ -31,7 +31,8 @@ _fzf_opts_completion() {
} }
_fzf_generic_completion() { _fzf_generic_completion() {
local cur base dir leftover matches trigger local cur base dir leftover matches trigger cmd orig
cmd=$(echo ${COMP_WORDS[0]} | sed 's/[^a-z0-9_=]/_/g')
COMPREPLY=() COMPREPLY=()
trigger=${FZF_COMPLETION_TRIGGER:-**} trigger=${FZF_COMPLETION_TRIGGER:-**}
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
@@ -61,25 +62,30 @@ _fzf_generic_completion() {
dir=$(dirname "$dir") dir=$(dirname "$dir")
[[ "$dir" =~ /$ ]] || dir="$dir"/ [[ "$dir" =~ /$ ]] || dir="$dir"/
done done
else
shift
shift
orig=$(eval "echo \$_fzf_orig_completion_$cmd")
[ -n "$orig" ] && type "$orig" > /dev/null && $orig "$@"
fi fi
} }
_fzf_all_completion() { _fzf_all_completion() {
_fzf_generic_completion \ _fzf_generic_completion \
"-name .git -prune -o -name .svn -prune -o -type d -print -o -type f -print -o -type l -print" \ "-name .git -prune -o -name .svn -prune -o -type d -print -o -type f -print -o -type l -print" \
"-m" "-m" "$@"
} }
_fzf_file_completion() { _fzf_file_completion() {
_fzf_generic_completion \ _fzf_generic_completion \
"-name .git -prune -o -name .svn -prune -o -type f -print -o -type l -print" \ "-name .git -prune -o -name .svn -prune -o -type f -print -o -type l -print" \
"-m" "-m" "$@"
} }
_fzf_dir_completion() { _fzf_dir_completion() {
_fzf_generic_completion \ _fzf_generic_completion \
"-name .git -prune -o -name .svn -prune -o -type d -print" \ "-name .git -prune -o -name .svn -prune -o -type d -print" \
"" "" "$@"
} }
_fzf_kill_completion() { _fzf_kill_completion() {
@@ -133,28 +139,43 @@ _fzf_ssh_completion() {
fi fi
} }
# fzf options
complete -F _fzf_opts_completion fzf complete -F _fzf_opts_completion fzf
d_cmds="cd pushd rmdir"
f_cmds="
awk cat diff diff3
emacs ex file ftp g++ gcc gvim head hg java
javac ld less more mvim patch perl python ruby
sed sftp sort source tail tee uniq vi view vim wc"
a_cmds="
basename bunzip2 bzip2 chmod chown curl cp dirname du
find git grep gunzip gzip hg jar
ln ls mv open rm rsync scp
svn tar unzip zip"
# Preserve existing completion
if [ "$_fzf_completion_loaded" != '0.8.6' ]; then
# Really wish I could use associative array but OSX comes with bash 3.2 :(
eval $(complete | grep '\-F' | grep -v _fzf_ |
grep -E -w "$(echo $d_cmds $f_cmds $a_cmds | sed 's/ /|/g' | sed 's/+/\\+/g')" |
sed -E 's/.*-F *([^ ]*).* ([^ ]*)$/export _fzf_orig_completion_\2=\1;/' |
sed 's/[^a-z0-9_= ;]/_/g')
export _fzf_completion_loaded=0.8.6
fi
# Directory # Directory
for cmd in "cd pushd rmdir"; do for cmd in $d_cmds; do
complete -F _fzf_dir_completion -o default -o bashdefault $cmd complete -F _fzf_dir_completion -o default -o bashdefault $cmd
done done
# File # File
for cmd in " for cmd in $f_cmds; do
awk cat diff diff3
emacs ex file ftp g++ gcc gvim head hg java
javac ld less more mvim patch perl python ruby
sed sftp sort source tail tee uniq vi view vim wc"; do
complete -F _fzf_file_completion -o default -o bashdefault $cmd complete -F _fzf_file_completion -o default -o bashdefault $cmd
done done
# Anything # Anything
for cmd in " for cmd in $a_cmds; do
basename bunzip2 bzip2 chmod chown curl cp dirname du
find git grep gunzip gzip hg jar
ln ls mv open rm rsync scp
svn tar unzip zip"; do
complete -F _fzf_all_completion -o default -o bashdefault $cmd complete -F _fzf_all_completion -o default -o bashdefault $cmd
done done
@@ -165,3 +186,4 @@ complete -F _fzf_kill_completion -o nospace -o default -o bashdefault kill
complete -F _fzf_ssh_completion -o default -o bashdefault ssh complete -F _fzf_ssh_completion -o default -o bashdefault ssh
complete -F _fzf_telnet_completion -o default -o bashdefault telnet complete -F _fzf_telnet_completion -o default -o bashdefault telnet
unset cmd d_cmds f_cmds a_cmds

77
install
View File

@@ -98,7 +98,7 @@ EOF
# Key bindings # Key bindings
# ------------ # ------------
__fsel() { __fsel() {
find * -path '*/\.*' -prune \ command 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 | while read item; do -o -type l -print 2> /dev/null | fzf -m | while read item; do
@@ -122,7 +122,7 @@ __fsel_tmux() {
__fcd() { __fcd() {
local dir local dir
dir=$(find ${1:-*} -path '*/\.*' -prune -o -type d -print 2> /dev/null | fzf +m) && printf 'cd %q' "$dir" dir=$(command find ${1:-*} -path '*/\.*' -prune -o -type d -print 2> /dev/null | fzf +m) && printf 'cd %q' "$dir"
} }
__use_tmux=0 __use_tmux=0
@@ -140,7 +140,7 @@ if [ -z "$(set -o | grep '^vi.*on')" ]; then
fi 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 +m -n..,1,2.. | sed \"s/ *[0-9]* *//\")\e\C-e\er"'
# ALT-C - cd into the selected directory # ALT-C - cd into the selected directory
bind '"\ec": " \C-e\C-u$(__fcd)\e\C-e\er\C-m"' bind '"\ec": " \C-e\C-u$(__fcd)\e\C-e\er\C-m"'
@@ -155,12 +155,15 @@ else
else else
bind '"\C-t": "\e$a \eddi$(__fsel)\C-x\C-e\e0Px$a \C-x\C-r\exa "' bind '"\C-t": "\e$a \eddi$(__fsel)\C-x\C-e\e0Px$a \C-x\C-r\exa "'
fi fi
bind -m vi-command '"\C-t": "i\C-t"'
# 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 +m -n..,1,2.. | sed \"s/ *[0-9]* *//\")\C-x\C-e\e$a\C-x\C-r"'
bind -m vi-command '"\C-r": "i\C-r"'
# ALT-C - cd into the selected directory # ALT-C - cd into the selected directory
bind '"\ec": "\eddi$(__fcd)\C-x\C-e\C-x\C-r\C-m"' bind '"\ec": "\eddi$(__fcd)\C-x\C-e\C-x\C-r\C-m"'
bind -m vi-command '"\ec": "i\ec"'
fi fi
unset __use_tmux unset __use_tmux
@@ -174,7 +177,7 @@ EOFZF
# CTRL-T - Paste the selected file path(s) into the command line # CTRL-T - Paste the selected file path(s) into the command line
__fsel() { __fsel() {
set -o nonomatch set -o nonomatch
find * -path '*/\.*' -prune \ command 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 | while read item; do -o -type l -print 2> /dev/null | fzf -m | while read item; do
@@ -198,7 +201,7 @@ if [ -n "$TMUX_PANE" -a ${FZF_TMUX:-1} -ne 0 -a ${LINES:-40} -gt 15 ]; then
} }
else else
fzf-file-widget() { fzf-file-widget() {
LBUFFER="${LBUFFER%% #}$(__fsel)" LBUFFER="${LBUFFER}$(__fsel)"
zle redisplay zle redisplay
} }
fi fi
@@ -207,7 +210,7 @@ bindkey '^T' fzf-file-widget
# ALT-C - cd into the selected directory # ALT-C - cd into the selected directory
fzf-cd-widget() { fzf-cd-widget() {
cd "${$(set -o nonomatch; find * -path '*/\.*' -prune \ cd "${$(set -o nonomatch; command find * -path '*/\.*' -prune \
-o -type d -print 2> /dev/null | fzf):-.}" -o -type d -print 2> /dev/null | fzf):-.}"
zle reset-prompt zle reset-prompt
} }
@@ -216,7 +219,7 @@ bindkey '\ec' fzf-cd-widget
# CTRL-R - Paste the selected command from history into the command line # CTRL-R - Paste the selected command from history into the command line
fzf-history-widget() { 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 redisplay
} }
zle -N fzf-history-widget zle -N fzf-history-widget
@@ -241,52 +244,64 @@ function fzf
$fzf_cmd \$argv $fzf_cmd \$argv
end end
EOFZF EOFZF
echo "ok" echo "OK"
if [ $key_bindings -eq 0 ]; then if [ $key_bindings -eq 0 ]; then
echo -n "Generate ~/.config/fish/functions/fzf_key_bindings.fish ... " echo -n "Generate ~/.config/fish/functions/fzf_key_bindings.fish ... "
cat > ~/.config/fish/functions/fzf_key_bindings.fish << "EOFZF" cat > ~/.config/fish/functions/fzf_key_bindings.fish << "EOFZF"
function fzf_key_bindings function fzf_key_bindings
function __fzf_select # Due to a bug of fish, we cannot use command substitution,
find * -path '*/\.*' -prune \ # so we use temporary file instead
if [ -z "$TMPDIR" ]
set -g TMPDIR /tmp
end
function __fzf_list
command 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 | while read item -o -type l -print 2> /dev/null
echo -n (echo -n "$item" | sed 's/ /\\\\ /g')' ' end
function __fzf_list_dir
command find * -path '*/\.*' -prune -o -type d -print 2> /dev/null
end
function __fzf_escape
while read item
echo -n (echo -n "$item" | sed -E 's/([ "$~'\''([{<>})])/\\\\\\1/g')' '
end end
echo
end end
function __fzf_ctrl_t function __fzf_ctrl_t
if [ -n "$TMUX_PANE" -a "$FZF_TMUX" != "0" ] 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'" tmux split-window (__fzf_tmux_height) "fish -c 'fzf_key_bindings; __fzf_ctrl_t_tmux \\$TMUX_PANE'"
else else
__fzf_select > $TMPDIR/fzf.result __fzf_list | fzf -m > $TMPDIR/fzf.result
and commandline -i (cat $TMPDIR/fzf.result) and commandline -i (cat $TMPDIR/fzf.result | __fzf_escape)
commandline -f repaint
rm -f $TMPDIR/fzf.result rm -f $TMPDIR/fzf.result
end end
end end
function __fzf_ctrl_t_tmux function __fzf_ctrl_t_tmux
__fzf_select > $TMPDIR/fzf.result __fzf_list | fzf -m > $TMPDIR/fzf.result
and tmux send-keys -t $argv[1] (cat $TMPDIR/fzf.result) and tmux send-keys -t $argv[1] (cat $TMPDIR/fzf.result | __fzf_escape)
rm -f $TMPDIR/fzf.result rm -f $TMPDIR/fzf.result
end end
function __fzf_ctrl_r function __fzf_ctrl_r
if history | fzf +s +m > $TMPDIR/fzf.result history | fzf +s +m > $TMPDIR/fzf.result
commandline (cat $TMPDIR/fzf.result) and commandline (cat $TMPDIR/fzf.result)
else
commandline -f repaint commandline -f repaint
end
rm -f $TMPDIR/fzf.result rm -f $TMPDIR/fzf.result
end end
function __fzf_alt_c function __fzf_alt_c
find * -path '*/\.*' -prune -o -type d -print 2> /dev/null | fzf +m > $TMPDIR/fzf.result # Fish hangs if the command before pipe redirects (2> /dev/null)
if [ (cat $TMPDIR/fzf.result | wc -l) -gt 0 ] __fzf_list_dir | fzf +m > $TMPDIR/fzf.result
cd (cat $TMPDIR/fzf.result) [ (cat $TMPDIR/fzf.result | wc -l) -gt 0 ]
end and cd (cat $TMPDIR/fzf.result)
commandline -f repaint commandline -f repaint
rm -f $TMPDIR/fzf.result rm -f $TMPDIR/fzf.result
end end
@@ -310,7 +325,7 @@ function fzf_key_bindings
bind \ec '__fzf_alt_c' bind \ec '__fzf_alt_c'
end end
EOFZF EOFZF
echo "ok" echo "OK"
fi fi
fi fi
@@ -336,6 +351,12 @@ done
if [ $key_bindings -eq 0 -a $has_fish -eq 1 ]; then if [ $key_bindings -eq 0 -a $has_fish -eq 1 ]; then
bind_file=~/.config/fish/functions/fish_user_key_bindings.fish bind_file=~/.config/fish/functions/fish_user_key_bindings.fish
append_line "fzf_key_bindings" "$bind_file" append_line "fzf_key_bindings" "$bind_file"
echo ' * Due to a known bug of fish, you may have issues running fzf on fish.'
echo ' * If that happens, try the following:'
echo ' - Remove ~/.config/fish/functions/fzf.fish'
echo ' - Place fzf executable in a directory included in $PATH'
echo
fi fi
cat << EOF cat << EOF
@@ -345,7 +366,7 @@ Finished. Restart your shell or reload config file.
EOF EOF
[ $has_fish -eq 1 ] && echo " fzf_key_bindings # fish"; cat << EOF [ $has_fish -eq 1 ] && echo " fzf_key_bindings # fish"; cat << EOF
To uninstall fzf, simply remove the added lines. Use uninstall script to remove fzf.
For more information, see: https://github.com/junegunn/fzf For more information, see: https://github.com/junegunn/fzf
EOF EOF

View File

@@ -24,24 +24,34 @@
let s:min_tmux_width = 10 let s:min_tmux_width = 10
let s:min_tmux_height = 3 let s:min_tmux_height = 3
let s:default_tmux_height = '40%' let s:default_tmux_height = '40%'
let s:launcher = 'xterm -e bash -ic %s'
let s:fzf_rb = expand('<sfile>:h:h').'/fzf'
let s:cpo_save = &cpo let s:cpo_save = &cpo
set cpo&vim set cpo&vim
call system('type fzf') function! s:fzf_exec()
if v:shell_error if !exists('s:exec')
let s:fzf_rb = expand('<sfile>:h:h').'/fzf' call system('type fzf')
if executable(s:fzf_rb) if v:shell_error
let s:exec = s:fzf_rb let s:exec = executable(s:fzf_rb) ? s:fzf_rb : ''
else else
echoerr 'fzf executable not found'
finish
endif
else
let s:exec = 'fzf' let s:exec = 'fzf'
endif endif
return s:fzf_exec()
elseif empty(s:exec)
unlet s:exec
throw 'fzf executable not found'
else
return s:exec
endif
endfunction
function! s:tmux_enabled() function! s:tmux_enabled()
if has('gui_running')
return 0
endif
if exists('s:tmux') if exists('s:tmux')
return s:tmux return s:tmux
endif endif
@@ -63,14 +73,14 @@ function! s:escape(path)
endfunction endfunction
function! fzf#run(...) abort 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 dict = exists('a:1') ? a:1 : {}
let temps = { 'result': tempname() } let temps = { 'result': tempname() }
let optstr = get(dict, 'options', '') let optstr = get(dict, 'options', '')
try
let fzf_exec = s:fzf_exec()
catch
throw v:exception
endtry
if has_key(dict, 'source') if has_key(dict, 'source')
let source = dict.source let source = dict.source
@@ -87,7 +97,7 @@ function! fzf#run(...) abort
else else
let prefix = '' let prefix = ''
endif endif
let command = prefix.s:exec.' '.optstr.' > '.temps.result let command = prefix.fzf_exec.' '.optstr.' > '.temps.result
if s:tmux_enabled() && s:tmux_splittable(dict) if s:tmux_enabled() && s:tmux_splittable(dict)
return s:execute_tmux(dict, command, temps) return s:execute_tmux(dict, command, temps)
@@ -118,9 +128,20 @@ endfunction
function! s:execute(dict, command, temps) function! s:execute(dict, command, temps)
call s:pushd(a:dict) call s:pushd(a:dict)
silent !clear 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! redraw!
if v:shell_error if v:shell_error
" Do not print error message on exit status 1
if v:shell_error > 1
echohl ErrorMsg
echo 'Error running ' . command
endif
return [] return []
else else
return s:callback(a:dict, a:temps, 0) return s:callback(a:dict, a:temps, 0)

View File

@@ -27,12 +27,14 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal true, fzf.color assert_equal true, fzf.color
assert_equal false, fzf.black assert_equal false, fzf.black
assert_equal true, fzf.ansi256 assert_equal true, fzf.ansi256
assert_equal '', fzf.query.get assert_equal '', fzf.query
assert_equal false, fzf.select1 assert_equal false, fzf.select1
assert_equal false, fzf.exit0 assert_equal false, fzf.exit0
assert_equal nil, fzf.filter assert_equal nil, fzf.filter
assert_equal nil, fzf.extended assert_equal nil, fzf.extended
assert_equal false, fzf.reverse assert_equal false, fzf.reverse
assert_equal '> ', fzf.prompt
assert_equal false, fzf.print_query
end end
def test_environment_variables def test_environment_variables
@@ -43,12 +45,12 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal nil, fzf.nth assert_equal nil, fzf.nth
ENV['FZF_DEFAULT_OPTS'] = ENV['FZF_DEFAULT_OPTS'] =
'-x -m -s 10000 -q " hello world " +c +2 --select-1 -0 ' + '-x -m -s 10000 -q " hello world " +c +2 --select-1 -0 ' <<
'--no-mouse -f "goodbye world" --black --nth=3,-1,2 --reverse' '--no-mouse -f "goodbye world" --black --nth=3,-1,2 --reverse --print-query'
fzf = FZF.new [] fzf = FZF.new []
assert_equal 10000, fzf.sort assert_equal 10000, fzf.sort
assert_equal ' hello world ', assert_equal ' hello world ',
fzf.query.get fzf.query
assert_equal 'goodbye world', assert_equal 'goodbye world',
fzf.filter fzf.filter
assert_equal :fuzzy, fzf.extended assert_equal :fuzzy, fzf.extended
@@ -60,14 +62,16 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal true, fzf.select1 assert_equal true, fzf.select1
assert_equal true, fzf.exit0 assert_equal true, fzf.exit0
assert_equal true, fzf.reverse assert_equal true, fzf.reverse
assert_equal [3, -1, 2], fzf.nth assert_equal true, fzf.print_query
assert_equal [2..2, -1..-1, 1..1], 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 --select-1 fzf = FZF.new %w[--sort=2000 --no-color --multi +i --query hello --select-1
--exit-0 --filter=howdy --extended-exact --exit-0 --filter=howdy --extended-exact
--no-mouse --no-256 --nth=1 --reverse] --no-mouse --no-256 --nth=1 --reverse --prompt (hi)
--print-query]
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
@@ -75,20 +79,23 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal false, fzf.black 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
assert_equal true, fzf.select1 assert_equal true, fzf.select1
assert_equal true, fzf.exit0 assert_equal true, fzf.exit0
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 assert_equal [0..0], fzf.nth
assert_equal true, fzf.reverse assert_equal true, fzf.reverse
assert_equal '(hi)', fzf.prompt
assert_equal true, fzf.print_query
# Long opts (left-to-right) # Long opts (left-to-right)
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 --black --nth -1 --nth -2 --filter a --filter b --no-256 --black --nth -1 --nth -2
--select-1 --exit-0 --no-select-1 --no-exit-0 --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] --reverse --no-reverse --prompt (hi) --prompt=(HI)
--print-query --no-print-query]
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
@@ -97,12 +104,14 @@ class TestFZF < MiniTest::Unit::TestCase
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
assert_equal false, fzf.select1 assert_equal false, fzf.select1
assert_equal false, fzf.exit0 assert_equal false, fzf.exit0
assert_equal nil, fzf.extended assert_equal nil, fzf.extended
assert_equal [-2], fzf.nth assert_equal [-2..-2], fzf.nth
assert_equal false, fzf.reverse assert_equal false, fzf.reverse
assert_equal '(HI)', fzf.prompt
assert_equal false, fzf.print_query
# Short opts # Short opts
fzf = FZF.new %w[-s2000 +c -m +i -qhello -x -fhowdy +2 -n3 -1 -0] fzf = FZF.new %w[-s2000 +c -m +i -qhello -x -fhowdy +2 -n3 -1 -0]
@@ -111,10 +120,10 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal false, fzf.color assert_equal false, fzf.color
assert_equal false, fzf.ansi256 assert_equal false, fzf.ansi256
assert_equal 0, fzf.rxflag assert_equal 0, fzf.rxflag
assert_equal 'hello', fzf.query.get assert_equal 'hello', fzf.query
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 assert_equal [2..2], fzf.nth
assert_equal true, fzf.select1 assert_equal true, fzf.select1
assert_equal true, fzf.exit0 assert_equal true, fzf.exit0
@@ -129,28 +138,33 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal true, fzf.ansi256 assert_equal true, fzf.ansi256
assert_equal false, fzf.black 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
assert_equal false, fzf.select1 assert_equal false, fzf.select1
assert_equal false, fzf.exit0 assert_equal false, fzf.exit0
assert_equal 'world', fzf.filter assert_equal 'world', fzf.filter
assert_equal nil, fzf.extended assert_equal nil, fzf.extended
assert_equal [4, 5], fzf.nth assert_equal [3..3, 4..4], fzf.nth
rescue SystemExit => e rescue SystemExit => e
assert false, "Exited" assert false, "Exited"
end end
def test_invalid_option 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 assert_raises(SystemExit) do
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
@@ -450,58 +464,11 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal 2, exact.match(list, "-fuzzy", '', '').length assert_equal 2, exact.match(list, "-fuzzy", '', '').length
end 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 # ^$ -> matches empty item
def test_format_empty_item def test_format_empty_item
fzf = FZF.new [] fzf = FZF.new []
item = ['', [[0, 0]]] item = ['', [[0, 0]]]
line, offsets = fzf.convert_item item line, offsets = item
tokens = fzf.format line, 80, offsets tokens = fzf.format line, 80, offsets
assert_equal [], tokens assert_equal [], tokens
end end
@@ -534,43 +501,67 @@ class TestFZF < MiniTest::Unit::TestCase
[list[0], [[2, 5]]], [list[0], [[2, 5]]],
[list[1], [[9, 17]]]], matcher.match(list, 'is', '', '') [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[1], [[8, 9]]]], matcher.match(list, 'f', '', '')
assert_equal [[list[0], [[8, 9]]]], matcher.match(list, 's', '', '') 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', '', '') assert_equal [[list[0], [[19, 20]]]], matcher.match(list, 'r', '', '')
# Comma-separated # 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', '', '') assert_equal [[list[0], [[19, 20]]], [list[1], [[3, 4]]]], matcher.match(list, 'r', '', '')
# Ordered # 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', '', '') assert_equal [[list[0], [[3, 4]]], [list[1], [[3, 4]]]], matcher.match(list, 'r', '', '')
regex = FZF.build_delim_regex "\t" 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', '', '') 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 [], matcher.match(list, 'r', '', '')
assert_equal [[list[1], [[9, 17]]]], matcher.match(list, 'is', '', '') assert_equal [[list[1], [[9, 17]]]], matcher.match(list, 'is', '', '')
# Negative indexing # 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], [[3, 6]]]], matcher.match(list, 'rt', '', '')
assert_equal [[list[0], [[2, 5]]], [list[1], [[9, 17]]]], matcher.match(list, 'is', '', '') assert_equal [[list[0], [[2, 5]]], [list[1], [[9, 17]]]], matcher.match(list, 'is', '', '')
# Regex delimiter # Regex delimiter
regex = FZF.build_delim_regex "[ \t]+" 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) 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', '', '') assert_equal [[list[0], [[1, 2]]], [list[1], [[8, 9]]]], matcher.match(list, 'f', '', '')
end 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 def stream_for str
StringIO.new(str).tap do |sio| StringIO.new(str).tap do |sio|
sio.instance_eval do sio.instance_eval do
@@ -583,34 +574,41 @@ class TestFZF < MiniTest::Unit::TestCase
end end
end end
def test_select_1 def assert_fzf_output opts, given, expected
stream = stream_for "Hello\nWorld" stream = stream_for given
output = StringIO.new output = StringIO.new
begin begin
$stdout = output $stdout = output
FZF.new(%w[--query=ol --select-1], stream).start FZF.new(opts, stream).start
rescue SystemExit => e rescue SystemExit => e
assert_equal 0, e.status assert_equal 0, e.status
assert_equal 'World', output.string.chomp assert_equal expected, output.string.chomp
ensure ensure
$stdout = STDOUT $stdout = STDOUT
end end
end end
def test_filter
{
%w[--filter=ol] => 'World',
%w[--filter=ol --print-query] => "ol\nWorld",
}.each do |opts, expected|
assert_fzf_output opts, "Hello\nWorld", expected
end
end
def test_select_1
{
%w[--query=ol --select-1] => 'World',
%w[--query=ol --select-1 --print-query] => "ol\nWorld",
}.each do |opts, expected|
assert_fzf_output opts, "Hello\nWorld", expected
end
end
def test_select_1_without_query def test_select_1_without_query
stream = stream_for "Hello World" assert_fzf_output %w[--select-1], 'Hello World', 'Hello World'
output = StringIO.new
begin
$stdout = output
FZF.new(%w[--select-1], stream).start
rescue SystemExit => e
assert_equal 0, e.status
assert_equal 'Hello World', output.string.chomp
ensure
$stdout = STDOUT
end
end end
def test_select_1_ambiguity def test_select_1_ambiguity
@@ -627,33 +625,16 @@ class TestFZF < MiniTest::Unit::TestCase
end end
def test_exit_0 def test_exit_0
stream = stream_for "Hello\nWorld" {
output = StringIO.new %w[--query=zz --exit-0] => '',
%w[--query=zz --exit-0 --print-query] => 'zz',
begin }.each do |opts, expected|
$stdout = output assert_fzf_output opts, "Hello\nWorld", expected
FZF.new(%w[--query=zz --exit-0], stream).start
rescue SystemExit => e
assert_equal 0, e.status
assert_equal '', output.string
ensure
$stdout = STDOUT
end end
end end
def test_exit_0_without_query def test_exit_0_without_query
stream = stream_for "" assert_fzf_output %w[--exit-0], '', ''
output = StringIO.new
begin
$stdout = output
FZF.new(%w[--exit-0], stream).start
rescue SystemExit => e
assert_equal 0, e.status
assert_equal '', output.string
ensure
$stdout = STDOUT
end
end end
def test_ranking_overlap_match_regions def test_ranking_overlap_match_regions
@@ -666,5 +647,39 @@ class TestFZF < MiniTest::Unit::TestCase
['1 3 4 2', [[0, 24], [12, 17]]], ['1 3 4 2', [[0, 24], [12, 17]]],
], FZF.sort(FZF::ExtendedFuzzyMatcher.new(nil).match(list, '12 34', '', '')) ], FZF.sort(FZF::ExtendedFuzzyMatcher.new(nil).match(list, '12 34', '', ''))
end end
def test_constrain
fzf = FZF.new []
# [#**** ]
assert_equal [false, 0, 0], fzf.constrain(0, 0, 5, 100)
# *****[**#** ... ] => [**#******* ... ]
assert_equal [true, 0, 2], fzf.constrain(5, 7, 10, 100)
# [**********]**#** => ***[*********#]**
assert_equal [true, 3, 12], fzf.constrain(0, 12, 15, 10)
# *****[**#** ] => ***[**#****]
assert_equal [true, 3, 5], fzf.constrain(5, 7, 10, 7)
# *****[**#** ] => ****[**#***]
assert_equal [true, 4, 6], fzf.constrain(5, 7, 10, 6)
# ***** [#] => ****[#]
assert_equal [true, 4, 4], fzf.constrain(10, 10, 5, 1)
# [ ] #**** => [#]****
assert_equal [true, 0, 0], fzf.constrain(-5, 0, 5, 1)
# [ ] **#** => **[#]**
assert_equal [true, 2, 2], fzf.constrain(-5, 2, 5, 1)
# [***** #] => [****# ]
assert_equal [true, 0, 4], fzf.constrain(0, 7, 5, 10)
# **[***** #] => [******# ]
assert_equal [true, 0, 6], fzf.constrain(2, 10, 7, 10)
end
end end

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