Compare commits

...

42 Commits
0.4.0 ... 0.5.1

Author SHA1 Message Date
Junegunn Choi
04ebaddf5e 0.5.1 2013-12-08 02:09:50 +09:00
Junegunn Choi
45e1f1ae57 Last update: December 5, 2013 2013-12-05 13:29:52 +09:00
Junegunn Choi
c1d5f7cef7 Do not use 256-color if not supported (#8) 2013-12-05 12:17:21 +09:00
Junegunn Choi
df663c4e41 Improve bash completion
- kill completion: do not even start fzf on non-empty word
- host completion: start fzf with initial query
2013-11-29 23:42:00 +09:00
Junegunn Choi
d3742782f3 Fix a typo in README 2013-11-29 18:09:51 +09:00
Junegunn Choi
faff17b2a9 Hostname completion for ssh and telnet commands 2013-11-29 18:08:22 +09:00
Junegunn Choi
9a3cddc92e Apply FZF_COMPLETION_OPTS to kill completion 2013-11-29 17:53:30 +09:00
Junegunn Choi
bd2763d863 Add bash completion for kill command 2013-11-29 17:50:53 +09:00
Junegunn Choi
b2bb22d883 A minor update to install script 2013-11-29 13:42:13 +09:00
Junegunn Choi
ad8ec7f387 Encourage use of function instead of alias (exportability) 2013-11-29 13:40:31 +09:00
Junegunn Choi
cf0ca8578c Update bash key binding example 2013-11-27 10:20:27 +09:00
Junegunn Choi
07aee79bd8 Update examples and bash completion
- Use tput sc/rc instead of redraw-current-line
- Escape selected items with printf
2013-11-27 01:14:36 +09:00
Junegunn Choi
344b57fe33 grep -F 2013-11-26 19:05:20 +09:00
Junegunn Choi
18a2fbf54a Fix install script (use export-able function instead of alias) 2013-11-26 19:01:01 +09:00
Junegunn Choi
39af56cf8f Revert "Reduce the number of Curses.refresh calls"
This reverts commit 2d3a0a1034
(which doesn't make any noticeable difference)
2013-11-24 20:40:23 +09:00
Junegunn Choi
2d3a0a1034 Reduce the number of Curses.refresh calls 2013-11-24 13:40:02 +09:00
Junegunn Choi
655fa5d9aa Update query line after update_list call
This commit is the workaround for the curses issue where the query
string on the screen is truncated after the cursor when the list is
updated: e.g. `aaac|bbb`
2013-11-24 12:56:26 +09:00
Junegunn Choi
9a49a29c7f Fix bash completion (~/abc/def/ghi**)
~/abc/def/ghi** should match ghi under ~/abc/def/, not ~/abc/def*
2013-11-23 20:37:53 +09:00
Junegunn Choi
89ae45cda4 Merge branch 'master' of github.com:junegunn/fzf
Conflicts:
	fzf-completion.bash
2013-11-23 20:16:46 +09:00
Junegunn Choi
f660ad35b2 Improve bash completion: [DIRECTORY/][FUZZY_PATTERN]**<TAB> 2013-11-23 20:12:14 +09:00
Junegunn Choi
c61738ae43 Bump up gem version 2013-11-23 20:09:02 +09:00
Junegunn Choi
c4dec4d34b Add -q option (initial query) 2013-11-23 19:21:02 +09:00
Junegunn Choi
a797604255 -o default as well as -o bashdefault 2013-11-21 11:38:14 +09:00
Junegunn Choi
25840d3bc7 -o bashdefault instead of -o default 2013-11-21 10:49:23 +09:00
Junegunn Choi
4745d50931 Add CTRL-G and ESC (C-[) as abort key (#7) 2013-11-20 21:18:51 +09:00
Junegunn Choi
04bf3abe99 Fix bash completion example 2013-11-20 15:22:25 +09:00
Junegunn Choi
57f7963eee Remove obsolete lines 2013-11-20 14:02:29 +09:00
Junegunn Choi
2fa21e5dd6 Remove obsolete lines 2013-11-20 14:01:13 +09:00
Junegunn Choi
9c4c37aa36 Adjust completion types (all/file/dir) 2013-11-20 12:28:41 +09:00
Junegunn Choi
2540c9062f The last argument doesn't have to be a path 2013-11-20 10:46:53 +09:00
Junegunn Choi
f28274109f Update Vim plugin to take path argument 2013-11-20 10:31:33 +09:00
Junegunn Choi
724724bd8c Extend the list of commands for fzf-completion 2013-11-20 02:23:30 +09:00
Junegunn Choi
64541cb5f8 Fix install script (source ~/.xxxrc has no effect) 2013-11-20 02:10:19 +09:00
Junegunn Choi
179b00ed6c Reload .bashrc/.zshrc after installation 2013-11-20 01:57:24 +09:00
Junegunn Choi
a9fd496691 Merge pull request #6 from junegunn/completion
Prototype implementation of bash auto-completion
2013-11-19 08:44:30 -08:00
Junegunn Choi
b14c57e656 Update README 2013-11-20 01:42:57 +09:00
Junegunn Choi
fa5617e076 Implement bash auto-completion with fzf 2013-11-20 01:29:36 +09:00
Junegunn Choi
e52a1d5fad Update bash example 2013-11-18 17:36:54 +09:00
Junegunn Choi
423e26b0c9 Better handling of NFD chars 2013-11-17 12:32:38 +09:00
Junegunn Choi
84921df0e3 Fix extended-search on non-darwin env 2013-11-17 11:47:52 +09:00
Junegunn Choi
6a5e1de6f3 Fix missing NFD conversion in extended-search mode 2013-11-17 11:20:06 +09:00
Junegunn Choi
90adda73b0 Update Vim plugin
Changes:
- Rename g:fzf_command to g:fzf_source
- Support multi-select mode
- Add fzf#run(vim_command, fzf_args) function

Todo:
- Faster startup with --disable-gems option when available
2013-11-17 02:41:10 +09:00
8 changed files with 523 additions and 106 deletions

175
README.md
View File

@@ -16,8 +16,24 @@ fzf requires Ruby (>= 1.8.5).
Installation Installation
------------ ------------
Download [fzf executable](https://raw.github.com/junegunn/fzf/master/fzf) and ### Using install script
put it somewhere in your search $PATH.
Clone this repository and run
[install](https://github.com/junegunn/fzf/blob/master/install) script.
```sh
git clone https://github.com/junegunn/fzf.git ~/.fzf
~/.fzf/install
```
The script will generate `~/.fzf.bash` and `~/.fzf.zsh` and update your
`.bashrc` and `.zshrc` to load them.
### Manual installation
Or you can just download
[fzf executable](https://raw.github.com/junegunn/fzf/master/fzf) and put it
somewhere in your search $PATH.
```sh ```sh
mkdir -p ~/bin mkdir -p ~/bin
@@ -25,20 +41,6 @@ wget https://raw.github.com/junegunn/fzf/master/fzf -O ~/bin/fzf
chmod +x ~/bin/fzf chmod +x ~/bin/fzf
``` ```
Or you can just clone this repository and run
[install](https://github.com/junegunn/fzf/blob/master/install) script.
```sh
git clone https://github.com/junegunn/fzf.git
fzf/install
```
Make sure that ~/bin is included in $PATH.
```sh
export PATH=$PATH:~/bin
```
### Install as Ruby gem ### Install as Ruby gem
fzf can be installed as a Ruby gem fzf can be installed as a Ruby gem
@@ -71,12 +73,13 @@ Usage
``` ```
usage: fzf [options] usage: fzf [options]
-m, --multi Enable multi-select -m, --multi Enable multi-select
-x, --extended Extended-search mode -x, --extended Extended-search mode
-s, --sort=MAX Maximum number of matched items to sort. Default: 1000 -q, --query=STR Initial query
+s, --no-sort Do not sort the result. Keep the sequence unchanged. -s, --sort=MAX Maximum number of matched items to sort. Default: 1000
+i Case-sensitive match +s, --no-sort Do not sort the result. Keep the sequence unchanged.
+c, --no-color Disable colors +i Case-sensitive match
+c, --no-color Disable colors
``` ```
fzf will launch curses-based finder, read the list from STDIN, and write the fzf will launch curses-based finder, read the list from STDIN, and write the
@@ -104,7 +107,7 @@ history | fzf +s
### Key binding ### Key binding
Use CTRL-J and CTRL-K (or CTRL-N and CTRL-P) to change the selection, press Use CTRL-J and CTRL-K (or CTRL-N and CTRL-P) to change the selection, press
enter key to select the item. CTRL-C will terminate the finder. enter key to select the item. CTRL-C, CTRL-G, or ESC will terminate the finder.
The following readline key bindings should also work as expected. The following readline key bindings should also work as expected.
@@ -138,14 +141,32 @@ Usage as Vim plugin
If you install fzf as a Vim plugin, `:FZF` command will be added. If you install fzf as a Vim plugin, `:FZF` command will be added.
```vim ```vim
" Look for files under current directory
:FZF :FZF
:FZF --no-sort
" Look for files under your home directory
:FZF ~
" With options
:FZF --no-sort -m /tmp
``` ```
You can override the command which produces input to fzf. You can override the source command which produces input to fzf.
```vim ```vim
let g:fzf_command = 'find . -type f' let g:fzf_source = 'find . -type f'
```
And you can predefine default options to fzf command.
```vim
let g:fzf_options = '--no-color --extended'
```
For more advanced uses, you can call `fzf#run` function as follows.
```vim
:call fzf#run('tabedit', '-m +c')
``` ```
Most of the time, you will prefer native Vim plugins with better integration Most of the time, you will prefer native Vim plugins with better integration
@@ -180,12 +201,23 @@ 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}
} }
```
# (Assuming you don't use the default CTRL-T and CTRL-R) bash key bindings
-----------------
```sh
# Required to refresh the prompt after fzf
bind '"\er": redraw-current-line'
# CTRL-T - Paste the selected file path into the command line # CTRL-T - Paste the selected file path into the command line
bind '"\er": redraw-current-line' fsel() {
bind '"\C-t": " \C-u \C-a\C-k$(fzf)\e\C-e\C-y\C-a\C-y\ey\C-h\C-e\er"' find ${1:-*} | fzf -m | while read item; do
printf '%q ' "$item"
done
echo
}
bind '"\C-t": " \C-u \C-a\C-k$(fsel)\e\C-e\C-y\C-a\C-y\ey\C-h\C-e\er"'
# CTRL-R - Paste the selected command from history into the command line # CTRL-R - Paste the selected command from history into the command line
bind '"\C-r": " \C-e\C-u$(history | fzf +s | sed \"s/ *[0-9]* *//\")\e\C-e\er"' bind '"\C-r": " \C-e\C-u$(history | fzf +s | sed \"s/ *[0-9]* *//\")\e\C-e\er"'
@@ -195,7 +227,7 @@ zsh widgets
----------- -----------
```sh ```sh
# CTRL-T - Paste the selected file(s) path into the command line # CTRL-T - Paste the selected file path(s) into the command line
fzf-file-widget() { fzf-file-widget() {
local FILES local FILES
local IFS=" local IFS="
@@ -230,6 +262,79 @@ zle -N fzf-history-widget
bindkey '^R' fzf-history-widget bindkey '^R' fzf-history-widget
``` ```
Auto-completion (experimental)
------------------------------
Disclaimer: *Auto-completion feature is currently experimental, it can change
over time*
### bash
#### Files and directories
Fuzzy completion for files and directories can be triggered if the word before
the cursor ends with the trigger sequence which is by default `**`.
- `COMMAND [DIRECTORY/][FUZZY_PATTERN]**<TAB>`
```sh
# Files under current directory
# - You can select multiple items with TAB key
vim **<TAB>
# Files under parent directory
vim ../**<TAB>
# Files under parent directory that match `fzf`
vim ../fzf**<TAB>
# Files under your home directory
vim ~/**<TAB>
# Directories under current directory (single-selection)
cd **<TAB>
# Directories under ~/github that match `fzf`
cd ~/github/fzf**<TAB>
```
#### Process IDs
Fuzzy completion for PIDs is provided for kill command. In this case
there is no trigger sequence, just press tab key after kill command.
```sh
# Can select multiple processes with <TAB> or <Shift-TAB> keys
kill -9 <TAB>
```
#### Host names
For ssh and telnet commands, fuzzy completion for host names is provided. The
names are extracted from /etc/hosts file.
```sh
ssh <TAB>
telnet <TAB>
```
#### Settings
```sh
# Use ~~ as the trigger sequence instead of the default **
export FZF_COMPLETION_TRIGGER='~~'
# Options to fzf command
export FZF_COMPLETION_OPTS='+c -x'
```
### zsh
TODO :smiley:
(Pull requests are appreciated.)
Tips Tips
---- ----
@@ -243,12 +348,18 @@ If you're running Ruby 1.9 or above, you can improve the startup time with
- `time ruby --disable-gems ~/bin/fzf -h` - `time ruby --disable-gems ~/bin/fzf -h`
- 0.025 sec - 0.025 sec
Define fzf alias with the option as follows: You can define fzf function with the option as follows:
```sh ```sh
alias fzf='ruby --disable-gems ~/bin/fzf' fzf() {
ruby --disable-gems ~/bin/fzf "$@"
}
export -f fzf
``` ```
However, this is automatically set up in your .bashrc and .zshrc if you use the
bundled [install](https://github.com/junegunn/fzf/blob/master/install) script.
### Incorrect display on Ruby 1.8 ### Incorrect display on Ruby 1.8
It is reported that the output of fzf can become unreadable on some terminals It is reported that the output of fzf can become unreadable on some terminals

119
fzf
View File

@@ -10,7 +10,7 @@
# URL: https://github.com/junegunn/fzf # URL: https://github.com/junegunn/fzf
# Author: Junegunn Choi # Author: Junegunn Choi
# License: MIT # License: MIT
# Last update: November 17, 2013 # Last update: December 5, 2013
# #
# Copyright (c) 2013 Junegunn Choi # Copyright (c) 2013 Junegunn Choi
# #
@@ -41,7 +41,7 @@ require 'set'
class FZF class FZF
C = Curses C = Curses
attr_reader :rxflag, :sort, :color, :multi attr_reader :rxflag, :sort, :color, :multi, :query
class AtomicVar class AtomicVar
def initialize value def initialize value
@@ -67,20 +67,36 @@ class FZF
end end
def initialize argv, source = $stdin def initialize argv, source = $stdin
usage 0 unless (%w[--help -h] & argv).empty? @rxflag = Regexp::IGNORECASE
@rxflag = argv.delete('+i') ? 0 : Regexp::IGNORECASE @sort = ENV.fetch('FZF_DEFAULT_SORT', 1000).to_i
@sort = %w[+s --no-sort].map { |e| argv.delete e }.compact.empty? ? @color = true
ENV.fetch('FZF_DEFAULT_SORT', 1000).to_i : nil @multi = false
@color = %w[+c --no-color].map { |e| argv.delete e }.compact.empty? @xmode = false
@multi = !%w[-m --multi].map { |e| argv.delete e }.compact.empty?
@xmode = !%w[-x --extended].map { |e| argv.delete e }.compact.empty? argv = argv.dup
rest = argv.join ' ' while o = argv.shift
if sort = rest.match(/(-s|--sort=?) ?([0-9]+)/) case o
usage 1 unless @sort when '-h', '--help' then usage 0
@sort = sort[2].to_i when '-m', '--multi' then @multi = true
rest = rest.delete sort[0] when '-x', '--extended' then @xmode = true
when '+i' then @rxflag = 0
when '+s', '--no-sort' then @sort = nil
when '+c', '--no-color' then @color = false
when '-q', '--query'
usage 1, 'query string required' unless query = argv.shift
@query = AtomicVar.new query.dup
when /^-q(.*)$/, /^--query=(.*)$/
@query = AtomicVar.new($1)
when '-s', '--sort'
usage 1, 'sort size required' unless sort = argv.shift
usage 1, 'invalid sort size' unless sort =~ /^[0-9]+$/
@sort = sort.to_i
when /^-s([0-9]+)$/, /^--sort=([0-9]+)$/
@sort = $1.to_i
else
usage 1, "illegal option: #{o}"
end
end end
usage 1 unless rest.empty?
@source = source @source = source
@mtx = Mutex.new @mtx = Mutex.new
@@ -88,8 +104,8 @@ class FZF
@events = {} @events = {}
@new = [] @new = []
@queue = Queue.new @queue = Queue.new
@cursor_x = AtomicVar.new(0) @query ||= AtomicVar.new('')
@query = AtomicVar.new('') @cursor_x = AtomicVar.new(@query.length)
@matches = AtomicVar.new([]) @matches = AtomicVar.new([])
@count = AtomicVar.new(0) @count = AtomicVar.new(0)
@vcursor = AtomicVar.new(0) @vcursor = AtomicVar.new(0)
@@ -111,11 +127,13 @@ class FZF
start_loop start_loop
end end
def usage x def usage x, message = nil
$stderr.puts message if message
$stderr.puts %[usage: fzf [options] $stderr.puts %[usage: fzf [options]
-m, --multi Enable multi-select -m, --multi Enable multi-select
-x, --extended Extended-search mode -x, --extended Extended-search mode
-q, --query=STR Initial query
-s, --sort=MAX Maximum number of matched items to sort. Default: 1000 -s, --sort=MAX Maximum number of matched items to sort. Default: 1000
+s, --no-sort Do not sort the result. Keep the sequence unchanged. +s, --no-sort Do not sort the result. Keep the sequence unchanged.
+i Case-sensitive match +i Case-sensitive match
@@ -137,21 +155,21 @@ class FZF
NFC_END = NFC_BEGIN + CHOSUNGS * JUNGSUNGS * JONGSUNGS NFC_END = NFC_BEGIN + CHOSUNGS * JUNGSUNGS * JONGSUNGS
def self.nfd str def self.nfd str
ret = '' str.split(//).map do |c|
str.split(//).each do |c|
cp = c.ord cp = c.ord
if cp >= NFC_BEGIN && cp < NFC_END if cp >= NFC_BEGIN && cp < NFC_END
chr = ''
idx = cp - NFC_BEGIN idx = cp - NFC_BEGIN
cho = CHOSUNG + idx / JJCOUNT cho = CHOSUNG + idx / JJCOUNT
jung = JUNGSUNG + (idx % JJCOUNT) / JONGSUNGS jung = JUNGSUNG + (idx % JJCOUNT) / JONGSUNGS
jong = JONGSUNG + idx % JONGSUNGS jong = JONGSUNG + idx % JONGSUNGS
ret << cho << jung chr << cho << jung
ret << jong if jong != JONGSUNG chr << jong if jong != JONGSUNG
chr
else else
ret << c c
end end
end end
ret
end end
def self.to_nfc arr def self.to_nfc arr
@@ -214,8 +232,12 @@ class FZF
end end
class Matcher class Matcher
def convert_query q def query_chars q
UConv.nfd(q).split(//) UConv.nfd(q)
end
def sanitize q
UConv.nfd(q).join
end end
end end
else else
@@ -224,9 +246,13 @@ class FZF
end end
class Matcher class Matcher
def convert_query q def query_chars q
q.split(//) q.split(//)
end end
def sanitize q
q
end
end end
end end
@@ -420,7 +446,7 @@ class FZF
C.noecho C.noecho
if @color if @color
if C.can_change_color? if C.can_change_color? && ENV['TERM'].to_s =~ /256/
C.init_pair 1, 110, dbg C.init_pair 1, 110, dbg
C.init_pair 2, 108, dbg C.init_pair 2, 108, dbg
C.init_pair 3, 254, 236 C.init_pair 3, 254, 236
@@ -604,6 +630,7 @@ class FZF
print_item row, tokens, chosen, selected print_item row, tokens, chosen, selected
end end
print_info print_info
print_input
end end
end end
@@ -629,14 +656,13 @@ class FZF
got = nil got = nil
begin begin
tty = IO.open(IO.sysopen('/dev/tty'), 'r') tty = IO.open(IO.sysopen('/dev/tty'), 'r')
input = '' input = @query.get.dup
cursor = 0 cursor = input.length
backword = proc { backword = proc {
cursor = (input[0, cursor].rindex(/\s\S/) || -1) + 1 cursor = (input[0, cursor].rindex(/\s\S/) || -1) + 1
} }
actions = { actions = {
:nop => proc { nil }, :esc => proc { exit 1 },
ctrl(:c) => proc { exit 1 },
ctrl(:d) => proc { exit 1 if input.empty? }, ctrl(:d) => proc { exit 1 if input.empty? },
ctrl(:m) => proc { ctrl(:m) => proc {
got = pick got = pick
@@ -680,30 +706,30 @@ class FZF
actions[ctrl(:h)] = actions[127] actions[ctrl(:h)] = actions[127]
actions[ctrl(:n)] = actions[ctrl(:j)] actions[ctrl(:n)] = actions[ctrl(:j)]
actions[ctrl(:p)] = actions[ctrl(:k)] actions[ctrl(:p)] = actions[ctrl(:k)]
actions[ctrl(:g)] = actions[ctrl(:c)] = actions[:esc]
actions[:stab] = actions[9] actions[:stab] = actions[9]
emit(:key) { [@query.get, cursor] } unless @query.empty?
while true while true
@cursor_x.set cursor @cursor_x.set cursor
render { print_input } render { print_input }
ord = tty.getc.ord ord = tty.getc.ord
ord = ord =
case ord = tty.getc.ord case ord = (tty.read_nonblock(1).ord rescue :esc)
when 91 when 91
case tty.getc.ord case (tty.read_nonblock(1).ord rescue nil)
when 68 then :left when 68 then :left
when 67 then :right when 67 then :right
when 66 then ctrl(:j) when 66 then ctrl(:j)
when 65 then ctrl(:k) when 65 then ctrl(:k)
when 90 then :stab when 90 then :stab
else :nop else next
end end
when 'b'.ord when 'b'.ord then :alt_b
:alt_b when 'f'.ord then :alt_f
when 'f'.ord when :esc then :esc
:alt_f else next
else
ord
end if ord == 27 end if ord == 27
upd = actions.fetch(ord, proc { |ord| upd = actions.fetch(ord, proc { |ord|
@@ -747,9 +773,10 @@ class FZF
def fuzzy_regex q def fuzzy_regex q
@regexp[q] ||= begin @regexp[q] ||= begin
q = q.downcase if @rxflag != 0 q = q.downcase if @rxflag != 0
Regexp.new(convert_query(q).inject('') { |sum, e| Regexp.new(query_chars(q).inject('') { |sum, e|
e = Regexp.escape e e = Regexp.escape e
sum << "#{e}[^#{e}]*?" sum << (e.length > 1 ? "(?:#{e}).*?" : # FIXME: not equivalent
"#{e}[^#{e}]*?")
}, @rxflag) }, @rxflag)
end end
end end
@@ -804,13 +831,13 @@ class FZF
nil nil
when /^'/ when /^'/
w.length > 1 ? w.length > 1 ?
Regexp.new(Regexp.escape(w[1..-1]), rxflag) : nil Regexp.new(sanitize(Regexp.escape(w[1..-1])), rxflag) : nil
when /^\^/ when /^\^/
w.length > 1 ? w.length > 1 ?
Regexp.new('^' << Regexp.escape(w[1..-1]), rxflag) : nil Regexp.new('^' << sanitize(Regexp.escape(w[1..-1])), rxflag) : nil
when /\$$/ when /\$$/
w.length > 1 ? w.length > 1 ?
Regexp.new(Regexp.escape(w[0..-2]) << '$', rxflag) : nil Regexp.new(sanitize(Regexp.escape(w[0..-2])) << '$', rxflag) : nil
else else
fuzzy_regex w fuzzy_regex w
end, invert ] end, invert ]

147
fzf-completion.bash Normal file
View File

@@ -0,0 +1,147 @@
#!/bin/bash
# ____ ____
# / __/___ / __/
# / /_/_ / / /_
# / __/ / /_/ __/
# /_/ /___/_/-completion.bash
#
# - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty)
_fzf_opts_completion() {
local cur prev opts
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts="-m --multi -x --extended -s --sort +s +i +c --no-color"
case "${prev}" in
--sort|-s)
COMPREPLY=( $(compgen -W "$(seq 2000 1000 10000)" -- ${cur}) )
return 0
;;
esac
if [[ ${cur} =~ ^-|\+ ]]; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
return 0
}
_fzf_generic_completion() {
local cur base dir leftover matches
COMPREPLY=()
FZF_COMPLETION_TRIGGER=${FZF_COMPLETION_TRIGGER:-**}
cur="${COMP_WORDS[COMP_CWORD]}"
if [[ ${cur} == *"$FZF_COMPLETION_TRIGGER" ]]; then
base=${cur:0:${#cur}-${#FZF_COMPLETION_TRIGGER}}
eval base=$base
dir="$base"
while [ 1 ]; do
if [ -z "$dir" -o -d "$dir" ]; then
leftover=${base/#"$dir"}
leftover=${leftover/#\/}
[ "$dir" = './' ] && dir=''
tput sc
matches=$(find "$dir"* $1 2> /dev/null | fzf $FZF_COMPLETION_OPTS $2 -q "$leftover" | while read item; do
printf '%q ' "$item"
done)
matches=${matches% }
if [ -n "$matches" ]; then
COMPREPLY=( "$matches" )
else
COMPREPLY=( "$cur" )
fi
tput rc
return 0
fi
dir=$(dirname "$dir")
[[ "$dir" =~ /$ ]] || dir="$dir"/
done
fi
}
_fzf_all_completion() {
_fzf_generic_completion \
"-name .git -prune -o -name .svn -prune -o -type d -print -o -type f -print -o -type l -print" \
"-m"
}
_fzf_file_completion() {
_fzf_generic_completion \
"-name .git -prune -o -name .svn -prune -o -type f -print -o -type l -print" \
"-m"
}
_fzf_dir_completion() {
_fzf_generic_completion \
"-name .git -prune -o -name .svn -prune -o -type d -print" \
""
}
_fzf_kill_completion() {
[ -n "${COMP_WORDS[COMP_CWORD]}" ] && return 1
local selected
tput sc
selected=$(ps -ef | sed 1d | fzf -m $FZF_COMPLETION_OPTS | awk '{print $2}' | tr '\n' ' ')
tput rc
if [ -n "$selected" ]; then
COMPREPLY=( "$selected" )
return 0
fi
}
_fzf_host_completion() {
local cur prev selected
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
[ "$cur" = '-l' -o "$prev" = '-l' ] && return 1
tput sc
selected=$(grep -v '^\s*\(#\|$\)' /etc/hosts | awk '{print $2}' | sort -u | fzf $FZF_COMPLETION_OPTS -q "$cur")
tput rc
if [ -n "$selected" ]; then
COMPREPLY=("$selected")
return 0
fi
}
complete -F _fzf_opts_completion fzf
# Directory
for cmd in "cd pushd rmdir"; do
complete -F _fzf_dir_completion -o default -o bashdefault $cmd
done
# File
for cmd in "
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
done
# Anything
for cmd in "
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
done
# Kill completion
complete -F _fzf_kill_completion -o nospace -o default -o bashdefault kill
# Host completion
for cmd in "ssh telnet"; do
complete -F _fzf_host_completion -o default -o bashdefault $cmd
done

9
fzf-completion.zsh Normal file
View File

@@ -0,0 +1,9 @@
#!/bin/zsh
# ____ ____
# / __/___ / __/
# / /_/_ / / /_
# / __/ / /_/ __/
# /_/ /___/_/-completion.zsh
#
# TODO

View File

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

85
install
View File

@@ -1,7 +1,86 @@
#!/bin/bash #!/bin/bash
cd `dirname $BASH_SOURCE` cd `dirname $BASH_SOURCE`
mkdir -p ~/bin fzf_base=`pwd`
ln -sf `pwd`/fzf ~/bin/fzf
chmod +x ~/bin/fzf # ruby executable
echo -n "Checking Ruby executable ... "
ruby=`which ruby`
if [ $? -ne 0 ]; then
echo "ruby executable not found!"
exit 1
fi
echo "OK"
# Curses-support
echo -n "Checking Curses support ... "
/usr/bin/env ruby -e "begin; require 'curses'; rescue Exception; exit 1; end"
if [ $? -ne 0 ]; then
echo "Your ruby does not support 'curses'"
exit 1
fi
echo "OK"
# Ruby version
echo -n "Checking Ruby version ... "
/usr/bin/env ruby -e 'exit RUBY_VERSION >= "1.9"'
if [ $? -eq 0 ]; then
echo ">= 1.9"
fzf_cmd="$ruby --disable-gems $fzf_base/fzf"
else
echo "< 1.9"
fzf_cmd="$ruby $fzf_base/fzf"
fi
# Auto-completion
read -p "Do you want to add auto-completion support? ([y]/n) " -n 1 -r
echo
[[ ! $REPLY =~ ^[Nn]$ ]]
auto_completion=$?
echo
for shell in bash zsh; do
echo -n "Generate ~/.fzf.$shell ... "
src=~/.fzf.${shell}
fzf_completion="source $fzf_base/fzf-completion.${shell}"
if [ $auto_completion -ne 0 ]; then
fzf_completion="# $fzf_completion"
fi
cat > $src << EOF
unalias fzf 2> /dev/null
fzf() {
$fzf_cmd "\$@"
}
export -f fzf > /dev/null
$fzf_completion
EOF
echo "OK"
done
echo
for shell in bash zsh; do
rc=~/.${shell}rc
src="source ~/.fzf.${shell}"
echo "Update $rc:"
echo " - $src"
if [ $(grep -F "$src" $rc | wc -l) -gt 0 ]; then
echo " - Not added (already being sourced)"
else
echo $src >> $rc
echo " - Added"
fi
echo
done
cat << EOF
Finished. Reload your .bashrc or .zshrc to take effect.
source ~/.bashrc # bash"
source ~/.zshrc # zsh"
To uninstall fzf, simply remove the added line.
EOF

View File

@@ -23,23 +23,37 @@
let s:exec = expand('<sfile>:h:h').'/fzf' let s:exec = expand('<sfile>:h:h').'/fzf'
function! s:fzf(args) function! s:escape(path)
return substitute(a:path, ' ', '\\ ', 'g')
endfunction
function! fzf#run(command, ...)
let cwd = getcwd()
try try
let tf = tempname() let args = copy(a:000)
let prefix = exists('g:fzf_command') ? g:fzf_command.'|' : '' if len(args) > 0 && isdirectory(expand(args[-1]))
let fzf = executable(s:exec) ? s:exec : 'fzf' let dir = remove(args, -1)
execute "silent !".prefix.fzf." ".a:args." > ".tf execute 'chdir '.s:escape(dir)
endif
let argstr = join(args)
let tf = tempname()
let prefix = exists('g:fzf_source') ? g:fzf_source.'|' : ''
let fzf = executable(s:exec) ? s:exec : 'fzf'
let options = empty(argstr) ? get(g:, 'fzf_options', '') : argstr
execute "silent !".prefix.fzf.' '.options." > ".tf
if !v:shell_error if !v:shell_error
let file = join(readfile(tf), '') for line in readfile(tf)
if !empty(file) if !empty(line)
execute 'silent e '.file execute a:command.' '.s:escape(line)
endif endif
endfor
endif endif
finally finally
silent! call delete(tf) execute 'chdir '.s:escape(cwd)
redraw! redraw!
silent! call delete(tf)
endtry endtry
endfunction endfunction
command! -nargs=* FZF call s:fzf(<q-args>) command! -nargs=* -complete=dir FZF call fzf#run('silent e', <f-args>)

View File

@@ -25,22 +25,35 @@ class TestFZF < MiniTest::Unit::TestCase
def test_option_parser def test_option_parser
# Long opts # Long opts
fzf = FZF.new %w[--sort=2000 --no-color --multi +i] fzf = FZF.new %w[--sort=2000 --no-color --multi +i --query hello]
assert_equal 2000, fzf.sort assert_equal 2000, fzf.sort
assert_equal true, fzf.multi assert_equal true, fzf.multi
assert_equal false, fzf.color assert_equal false, fzf.color
assert_equal 0, fzf.rxflag assert_equal 0, fzf.rxflag
assert_equal 'hello', fzf.query.get
# Short opts # Short opts
fzf = FZF.new %w[-s 2000 +c -m +i] fzf = FZF.new %w[-s 2000 +c -m +i -qhello]
assert_equal 2000, fzf.sort assert_equal 2000, fzf.sort
assert_equal true, fzf.multi assert_equal true, fzf.multi
assert_equal false, fzf.color assert_equal false, fzf.color
assert_equal 0, fzf.rxflag assert_equal 0, fzf.rxflag
assert_equal 'hello', fzf.query.get
# Left-to-right
fzf = FZF.new %w[-qhello -s 2000 --no-sort -q world]
assert_equal nil, fzf.sort
assert_equal 'world', fzf.query.get
fzf = FZF.new %w[--query hello +s -s 2000 --query=world]
assert_equal 2000, fzf.sort
assert_equal 'world', fzf.query.get
rescue SystemExit => e
assert false, "Exited"
end end
def test_invalid_option def test_invalid_option
[%w[-s 2000 +s], %w[yo dawg]].each do |argv| [%w[--unknown], %w[yo dawg]].each do |argv|
assert_raises(SystemExit) do assert_raises(SystemExit) do
fzf = FZF.new argv fzf = FZF.new argv
end end
@@ -293,8 +306,25 @@ class TestFZF < MiniTest::Unit::TestCase
def test_nfd def test_nfd
nfc = '한글' nfc = '한글'
nfd = FZF::UConv.nfd(nfc) nfd = FZF::UConv.nfd(nfc)
assert_equal 6, nfd.length assert_equal 2, nfd.length
assert_equal NFD, nfd 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
end end