Compare commits

...

37 Commits
0.1.0 ... 0.3.1

Author SHA1 Message Date
Junegunn Choi
ff34c6b272 Merge pull request #5 from Vifon/zsh-widgets
new zsh widgets
2013-11-10 16:11:29 -08:00
Wojciech Siewierski
b2ac52462c zsh widget fix
* fzf-cd-widget properly escapes the special characters
2013-11-10 18:57:47 +01:00
Wojciech Siewierski
a6f7caf20d new zsh widgets
+ new fzf-cd-widget for running cd

* fzf-file-widget now properly escapes the special characters and supports
  multi-selection

* fzf-history-widget replaces the current line instead of appending to it
2013-11-10 18:28:59 +01:00
Junegunn Choi
1e9e597837 Merge pull request #4 from Vifon/patch-1
superfluous backslash in zle widget removed
2013-11-10 06:34:21 -08:00
Wojciech Siewierski
0dc725d09c superfluous backslash in zle widget removed 2013-11-10 15:31:10 +01:00
Junegunn Choi
1eceb6a4b9 FZF_DEFAULT_SORT 2013-11-10 20:22:57 +09:00
Junegunn Choi
8777a495bc Shift-TAB on multi-select mode 2013-11-10 20:19:57 +09:00
Junegunn Choi
83825dbbd3 Add Tips section 2013-11-10 20:03:38 +09:00
Junegunn Choi
1ac19a2097 Update README 2013-11-10 11:53:13 +09:00
Junegunn Choi
833c6e1eeb Update example 2013-11-10 04:00:27 +09:00
Junegunn Choi
8a0a3f9bf5 Do not sort the result if query is empty 2013-11-10 03:57:10 +09:00
Junegunn Choi
ddf6e5ef1e Implement multi-select mode (#3) 2013-11-10 03:56:18 +09:00
Junegunn Choi
11a1010e9e Add zsh widget examples 2013-11-09 01:16:39 +09:00
Junegunn Choi
75b666bf54 Update README (replace backticks with $())
As reported in #2, backticks on oh-my-zsh (with TERM=xterm*)
makes fzf run twice. This should be a bug of oh-my-zsh,
but for now using $() seems to be a quick workaround.
2013-11-06 20:54:02 +09:00
Junegunn Choi
3f73554a9e Fix FZF_DEFAULT_COMMAND 2013-11-05 21:05:34 +09:00
Junegunn Choi
dc67420319 Make fzf.vim separately installable 2013-11-05 13:45:23 +09:00
Junegunn Choi
f2d8e7e3ee Update README 2013-11-05 00:11:06 +09:00
Junegunn Choi
de8116b1cf Fix error on Linux with Ruby 1.8 2013-11-04 10:38:16 +09:00
Junegunn Choi
1460e0a10b 0.2.0 2013-11-03 22:51:00 +09:00
Junegunn Choi
c46dad465f Fix invalid return when query string is empty 2013-11-03 22:49:12 +09:00
Junegunn Choi
0df647b2a7 Strip null bytes in the string 2013-11-03 22:15:52 +09:00
Junegunn Choi
69d6b58f88 Do not block on --no-sort 2013-11-03 22:01:25 +09:00
Junegunn Choi
8e305edcf2 Add --no-color (+c) option 2013-11-03 11:45:14 +09:00
Junegunn Choi
c326e363eb Premature optimization is root of all fun 2013-11-03 10:43:48 +09:00
Junegunn Choi
d1298b8fff Reduce memory footprint 2013-11-03 04:05:32 +09:00
Junegunn Choi
2a0e0ded2a Remove Gemfile 2013-11-03 01:11:36 +09:00
Junegunn Choi
7cecf648eb Optimize left/right trimming 2013-11-03 00:06:50 +09:00
Junegunn Choi
c3c94ea889 Fix long item display 2013-11-02 20:58:23 +09:00
Junegunn Choi
94f0c3d22b Bump up gem version to 0.1.3 2013-11-02 20:41:29 +09:00
Junegunn Choi
d717096ee3 Update README 2013-11-02 20:40:25 +09:00
Junegunn Choi
1629fe079a Improve display
- CJK wide character support
- Progress reporting for long-running queries (> 0.5sec)
2013-11-02 20:12:39 +09:00
Junegunn Choi
6a9970c98e FZF_DEFAULT_COMMAND 2013-11-02 12:56:43 +09:00
Junegunn Choi
682583e88f 0.1.1 2013-11-01 16:13:12 +09:00
Junegunn Choi
fd2472d11c Ignore empty file path 2013-11-01 16:07:46 +09:00
Junegunn Choi
311c4a36e2 Remove initial delay 2013-11-01 02:02:00 +09:00
Junegunn Choi
b98fba4cf1 Gradually increase delay upto 0.2 seconds 2013-11-01 01:59:23 +09:00
Junegunn Choi
a03b5c8c42 gem install fzf 2013-11-01 01:12:46 +09:00
5 changed files with 287 additions and 134 deletions

View File

@@ -1,4 +0,0 @@
source 'https://rubygems.org'
# Specify your gem's dependencies in fzf.gemspec
gemspec

119
README.md
View File

@@ -16,7 +16,8 @@ fzf requires Ruby (>= 1.8.5).
Installation Installation
------------ ------------
Download fzf executable and put it somewhere in your search $PATH. 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
@@ -38,15 +39,21 @@ Make sure that ~/bin is included in $PATH.
export PATH=$PATH:~/bin export PATH=$PATH:~/bin
``` ```
Install as Vim plugin ### Install as Ruby gem
---------------------
fzf was not designed to be a Vim plugin, but you can use it as one. The only fzf can be installed as a Ruby gem
reason one might consider using fzf in Vim is its speed. For a very large list
of files, fzf is significantly faster than native Vim plugins.
You can use any Vim plugin manager to install fzf as a Vim plugin. If you don't ```
use one, I recommend you try [vim-plug](https://github.com/junegunn/vim-plug). gem install fzf
```
It's a bit easier to install and update the script but the Ruby gem version
takes slightly longer to start.
### Install as Vim plugin
You can use any Vim plugin manager to install fzf for Vim. If you don't use one,
I recommend you try [vim-plug](https://github.com/junegunn/vim-plug).
1. [Install vim-plug](https://github.com/junegunn/vim-plug#usage) 1. [Install vim-plug](https://github.com/junegunn/vim-plug#usage)
2. Edit your .vimrc 2. Edit your .vimrc
@@ -64,9 +71,11 @@ Usage
``` ```
usage: fzf [options] usage: fzf [options]
-m, --multi Enable multi-select
-s, --sort=MAX Maximum number of matched items to sort. Default: 500 -s, --sort=MAX Maximum number of matched items to sort. Default: 500
+s, --no-sort Keep the sequence unchanged. +s, --no-sort Do not sort the result. Keep the sequence unchanged.
+i Case-sensitive match +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
@@ -77,10 +86,11 @@ find * -type f | fzf > selected
``` ```
Without STDIN pipe, fzf will use find command to fetch the list of Without STDIN pipe, fzf will use find command to fetch the list of
files (excluding hidden ones). files excluding hidden ones. (You can override the default command with
`FZF_DEFAULT_COMMAND`)
```sh ```sh
vim `fzf` vim $(fzf)
``` ```
If you want to preserve the exact sequence of the input, provide `--no-sort` (or If you want to preserve the exact sequence of the input, provide `--no-sort` (or
@@ -101,6 +111,9 @@ The following readline key bindings should also work as expected.
- CTRL-B / CTRL-F - CTRL-B / CTRL-F
- CTRL-W / CTRL-U - CTRL-W / CTRL-U
If you enable multi-select mode with `-m` option, you can select multiple items
with TAB or Shift-TAB key.
Usage as Vim plugin Usage as Vim plugin
------------------- -------------------
@@ -117,23 +130,27 @@ You can override the command which produces input to fzf.
let g:fzf_command = 'find . -type f' let g:fzf_command = 'find . -type f'
``` ```
Most of the time, you will prefer native Vim plugins with better integration
with Vim. The only reason one might consider using fzf in Vim is its speed. For
a very large list of files, fzf is significantly faster and it does not block.
Useful bash examples Useful bash examples
-------------------- --------------------
```sh ```sh
# vimf - Open selected file in Vim # vimf - Open selected file in Vim
vimf() { vimf() {
FILE=`fzf` && vim "$FILE" FILE=$(fzf) && vim "$FILE"
} }
# fd - cd to selected directory # fd - cd to selected directory
fd() { fd() {
DIR=`find ${1:-*} -path '*/\.*' -prune -o -type d -print 2> /dev/null | fzf` && cd "$DIR" DIR=$(find ${1:-*} -path '*/\.*' -prune -o -type d -print 2> /dev/null | fzf) && cd "$DIR"
} }
# fda - including hidden directories # fda - including hidden directories
fda() { fda() {
DIR=`find ${1:-*} -type d 2> /dev/null | fzf` && cd "$DIR" DIR=$(find ${1:-*} -type d 2> /dev/null | fzf) && cd "$DIR"
} }
# fh - repeat history # fh - repeat history
@@ -143,14 +160,84 @@ fh() {
# fkill - kill process # fkill - kill process
fkill() { fkill() {
ps -ef | sed 1d | fzf | awk '{print $2}' | xargs kill -${1:-9} ps -ef | sed 1d | fzf -m | awk '{print $2}' | xargs kill -${1:-9}
} }
# CTRL-T - Open fuzzy finder and paste the selected item to the command line # (Assuming you don't use the default CTRL-T and CTRL-R)
# CTRL-T - Paste the selected file path into the command line
bind '"\er": redraw-current-line' bind '"\er": redraw-current-line'
bind '"\C-t": " \C-u \C-a\C-k$(fzf)\e\C-e\C-y\C-a\C-y\ey\C-h\C-e\er"' bind '"\C-t": " \C-u \C-a\C-k$(fzf)\e\C-e\C-y\C-a\C-y\ey\C-h\C-e\er"'
# CTRL-R - Paste the selected command from history into the command line
bind '"\C-r": " \C-e\C-u$(history | fzf +s | sed \"s/ *[0-9]* *//\")\e\C-e\er"'
``` ```
zsh widgets
-----------
```sh
# CTRL-T - Paste the selected file(s) path into the command line
fzf-file-widget() {
local FILES
local IFS="
"
FILES=($(
find * -path '*/\.*' -prune \
-o -type f -print \
-o -type l -print 2> /dev/null | fzf -m))
unset IFS
FILES=$FILES:q
LBUFFER="${LBUFFER%% #} $FILES"
zle redisplay
}
zle -N fzf-file-widget
bindkey '^T' fzf-file-widget
# ALT-C - cd into the selected directory
fzf-cd-widget() {
cd "${$(find * -path '*/\.*' -prune \
-o -type d -print 2> /dev/null | fzf):-.}"
zle reset-prompt
}
zle -N fzf-cd-widget
bindkey '\ec' fzf-cd-widget
# CTRL-R - Paste the selected command from history into the command line
fzf-history-widget() {
LBUFFER=$(history | fzf +s | sed "s/ *[0-9]* *//")
zle redisplay
}
zle -N fzf-history-widget
bindkey '^R' fzf-history-widget
```
Tips
----
### Faster startup with `--disable-gems` options
If you're running Ruby 1.9 or above, you can improve the startup time with
`--disable-gems` option to Ruby.
- `time ruby ~/bin/fzf -h`
- 0.077 sec
- `time ruby --disable-gems ~/bin/fzf -h`
- 0.025 sec
Define fzf alias with the option as follows:
```sh
alias fzf='ruby --disable-gems ~/bin/fzf'
```
### Incorrect display on Ruby 1.8
It is reported that the output of fzf can become unreadable on some terminals
when it's running on Ruby 1.8. If you experience the problem, upgrade your Ruby
to 1.9 or above. Ruby 1.9 or above is also required for displaying Unicode
characters.
License License
------- -------

236
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: October 29, 2013 # Last update: November 10, 2013
# #
# Copyright (c) 2013 Junegunn Choi # Copyright (c) 2013 Junegunn Choi
# #
@@ -38,9 +38,11 @@
def usage x def usage x
puts %[usage: fzf [options] puts %[usage: fzf [options]
-m, --multi Enable multi-select
-s, --sort=MAX Maximum number of matched items to sort. Default: 500. -s, --sort=MAX Maximum number of matched items to sort. Default: 500.
+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
+c, --no-color Disable colors]
exit x exit x
end end
@@ -49,7 +51,10 @@ $stdout.reopen($stderr)
usage 0 unless (%w[--help -h] & ARGV).empty? usage 0 unless (%w[--help -h] & ARGV).empty?
@rxflag = ARGV.delete('+i') ? 0 : Regexp::IGNORECASE @rxflag = ARGV.delete('+i') ? 0 : Regexp::IGNORECASE
@sort = (ARGV.delete('+s') || ARGV.delete('--no-sort')) ? nil : 500 @sort = %w[+s --no-sort].map { |e| ARGV.delete e }.compact.empty? ?
ENV.fetch('FZF_DEFAULT_SORT', 500).to_i : nil
@color = %w[+c --no-color].map { |e| ARGV.delete e }.compact.empty?
@multi = !%w[-m --multi].map { |e| ARGV.delete e }.compact.empty?
rest = ARGV.join ' ' rest = ARGV.join ' '
if sort = rest.match(/(-s|--sort=?) ?([0-9]+)/) if sort = rest.match(/(-s|--sort=?) ?([0-9]+)/)
usage 1 unless @sort usage 1 unless @sort
@@ -72,6 +77,7 @@ require 'curses'
@cursor_x = 0 @cursor_x = 0
@vcursor = 0 @vcursor = 0
@events = {} @events = {}
@selects = {} # ordered >= 1.9
case RUBY_PLATFORM case RUBY_PLATFORM
when /darwin/ when /darwin/
@@ -104,7 +110,7 @@ when /darwin/
ret ret
end end
def self.nfc str, offset def self.nfc str, b = 0, e = 0
ret = '' ret = ''
omap = [] omap = []
pend = [] pend = []
@@ -132,7 +138,7 @@ when /darwin/
ret << c ret << c
end end
end end
return [ret, offset.map { |o| omap[o] || (omap.last + 1) }] return [ret, omap[b] || 0, omap[e] || ((omap.last || 0) + 1)]
end end
end end
@@ -165,7 +171,7 @@ def max_items; C.lines - 2; end
def cursor_y; C.lines - 1; end def cursor_y; C.lines - 1; end
def cprint str, col def cprint str, col
C.attron(col) do C.attron(col) do
C.addstr str C.addstr str.gsub("\0", '')
end if str end if str
end end
@@ -178,7 +184,7 @@ def print_input
end end
end end
def print_info progress = true, msg = nil def print_info selected, msg = nil
@fan ||= '-\|/-\|/'.split(//) @fan ||= '-\|/-\|/'.split(//)
C.setpos cursor_y - 1, 0 C.setpos cursor_y - 1, 0
C.clrtoeol C.clrtoeol
@@ -191,14 +197,14 @@ def print_info progress = true, msg = nil
' ' ' '
end end
C.attron color(:info, false) do C.attron color(:info, false) do
progress &&= "#{prefix}#{@matches.length}/#{@count}" C.addstr "#{prefix}#{@matches.length}/#{@count}"
C.addstr progress if progress C.addstr " (#{selected})" if selected > 0
C.addstr msg if msg C.addstr msg if msg
end end
end end
def refresh def refresh
C.setpos cursor_y, 2 + ulen(@query[0, @cursor_x]) C.setpos cursor_y, 2 + width(@query[0, @cursor_x])
C.refresh C.refresh
end end
@@ -207,15 +213,35 @@ def ctrl char
end end
if RUBY_VERSION.split('.').map { |e| e.rjust(3, '0') }.join > '001009' if RUBY_VERSION.split('.').map { |e| e.rjust(3, '0') }.join > '001009'
def ulen str @wrx = Regexp.new '\p{Han}|\p{Katakana}|\p{Hiragana}|\p{Hangul}'
@urx ||= Regexp.new '\p{Han}|\p{Katakana}|\p{Hiragana}|\p{Hangul}' def width str
str.gsub(@urx, ' ').length str.gsub(@wrx, ' ').length
end
def trim str, len, left
width = width str
diff = 0
while width > len
width -= (left ? str[0, 1] : str[-1, 1]) =~ @wrx ? 2 : 1
str = left ? str[1..-1] : str[0...-1]
diff += 1
end
[str, diff]
end end
else else
def ulen str def width str
str.length str.length
end end
def trim str, len, left
diff = str.length - len
if diff > 0
[left ? str[diff..-1] : str[0...-diff], diff]
else
[str, 0]
end
end
class String class String
def ord def ord
self.unpack('c').first self.unpack('c').first
@@ -240,6 +266,8 @@ dbg =
end end
C.raw C.raw
C.noecho C.noecho
if @color
if C.can_change_color? if C.can_change_color?
fg = ENV.fetch('FZF_FG', 252).to_i fg = ENV.fetch('FZF_FG', 252).to_i
bg = ENV.fetch('FZF_BG', 236).to_i bg = ENV.fetch('FZF_BG', 236).to_i
@@ -265,10 +293,26 @@ def color sym, bold = false
:match!, :fan, :info, :red].index(sym) + 1) | :match!, :fan, :info, :red].index(sym) + 1) |
(bold ? C::A_BOLD : 0) (bold ? C::A_BOLD : 0)
end end
else
def color sym, bold = false
case sym
when :chosen
bold ? C::A_REVERSE : 0
when :match
C::A_UNDERLINE
when :match!
C::A_REVERSE | C::A_UNDERLINE
else
0
end | (bold ? C::A_BOLD : 0)
end
end
@read = @read =
if $stdin.tty? if $stdin.tty?
if !`which find`.empty? if default_command = ENV['FZF_DEFAULT_COMMAND']
IO.popen(default_command)
elsif !`which find`.empty?
IO.popen("find * -path '*/\\.*' -prune -o -type f -print -o -type l -print 2> /dev/null") IO.popen("find * -path '*/\\.*' -prune -o -type f -print -o -type l -print 2> /dev/null")
else else
exit 1 exit 1
@@ -290,20 +334,18 @@ searcher = Thread.new {
events = {} events = {}
fcache = {} fcache = {}
matches = [] matches = []
selects = {}
mcount = 0 # match count mcount = 0 # match count
plcount = 0 # prev list count plcount = 0 # prev list count
q = '' q = ''
vcursor = 0 vcursor = 0
zz = [0, 0] delay = -5
started = false
begin begin
while true while true
wait_for_completion = nil
@mtx.synchronize do @mtx.synchronize do
while true while true
events.merge! @events events.merge! @events
wait_for_completion = !@sort && !events[:loaded]
if @events.empty? # No new events if @events.empty? # No new events
@cv.wait @mtx @cv.wait @mtx
@@ -313,52 +355,57 @@ searcher = Thread.new {
break break
end end
if !wait_for_completion && events[:new] if events[:new]
@lists << [@new, {}] @lists << [@new, {}]
@count += @new.length @count += @new.length
@new = [] @new = []
fcache = {} fcache = {}
end end
if events[:select]
selects = @selects.dup
end
end#mtx end#mtx
if wait_for_completion new_search = events[:key] || events.delete(:new)
@smtx.synchronize do user_input = events[:key] || events[:vcursor] || events.delete(:select)
print_info false, " +#{@new.length}" progress = 0
print_input started_at = Time.now
refresh
sleep 0.1
end
next
end
new_search = events[:key] || events[:new]
user_input = events[:key] || events[:vcursor]
if new_search && !@lists.empty? if new_search && !@lists.empty?
events.delete :new
q = events.delete(:key) || q q = events.delete(:key) || q
regexp = q.empty? ? nil :
Regexp.new(convert_query(q).inject('') { |sum, e| unless q.empty?
q = q.downcase if @rxflag != 0
regexp = Regexp.new(convert_query(q).inject('') { |sum, e|
e = Regexp.escape e e = Regexp.escape e
sum << "#{e}[^#{e}]*?" sum << "#{e}[^#{e}]*?"
}, @rxflag) }, @rxflag)
end
matches = fcache[q] ||= matches = fcache[q] ||=
begin begin
@smtx.synchronize do
print_info true, ' ..'
print_input
refresh
end unless q.empty?
found = [] found = []
skip = false skip = false
cnt = 0
@lists.each do |pair| @lists.each do |pair|
@mtx.synchronize { skip = @events[:key] } list, cache = pair
cnt += list.length
@mtx.synchronize {
skip = @events[:key]
progress = (100 * cnt / @count)
}
break if skip break if skip
list, cache = pair found.concat(cache[q] ||= q.empty? ? list : begin
found.concat(cache[q] ||= begin if progress < 100 && Time.now - started_at > 0.5
@smtx.synchronize do
print_info selects.length, " (#{progress}%)"
refresh
end
end
prefix, suffix = @query[0, @cursor_x], @query[@cursor_x..-1] || '' prefix, suffix = @query[0, @cursor_x], @query[@cursor_x..-1] || ''
prefix_cache = suffix_cache = nil prefix_cache = suffix_cache = nil
@@ -372,13 +419,9 @@ searcher = Thread.new {
partial_cache = [prefix_cache, suffix_cache].compact.sort_by { |e| e.length }.first partial_cache = [prefix_cache, suffix_cache].compact.sort_by { |e| e.length }.first
(partial_cache ? partial_cache.map { |e| e.first } : list).map { |line| (partial_cache ? partial_cache.map { |e| e.first } : list).map { |line|
if regexp
# Ignore errors: e.g. invalid byte sequence in UTF-8 # Ignore errors: e.g. invalid byte sequence in UTF-8
md = line.match(regexp) rescue nil md = line.match(regexp) rescue nil
md ? [line, md.offset(0)] : nil md && [line, *md.offset(0)]
else
[line, zz]
end
}.compact }.compact
end) end)
end end
@@ -387,19 +430,16 @@ searcher = Thread.new {
end end
mcount = matches.length mcount = matches.length
if @sort && mcount <= @sort if @sort && mcount <= @sort && !q.empty?
matches.replace matches.sort_by { |pair| matches.replace matches.sort_by { |triple|
line, offset = pair line, b, e = triple
[offset.last - offset.first, line.length, line] [b ? (e - b) : 0, line.length, line]
} }
end end
end#new_search end#new_search
# This small delay reduces the number of partial lists # This small delay reduces the number of partial lists
if started && !user_input sleep((delay = [20, delay + 5].min) * 0.01) unless user_input
sleep 0.2
end
started = true
if events.delete(:vcursor) || new_search if events.delete(:vcursor) || new_search
@mtx.synchronize do @mtx.synchronize do
@@ -419,38 +459,45 @@ searcher = Thread.new {
end end
end end
maxc = C.cols - 5 maxc = C.cols - 3
matches[0, max_items].each_with_index do |item, idx| matches[0, max_items].each_with_index do |item, idx|
next if !new_search && !((vcursor-1)..(vcursor+1)).include?(idx) next if !new_search && !((vcursor-1)..(vcursor+1)).include?(idx)
line, offset = convert_item item line, b, e = convert_item item
b ||= 0
e ||= 0
row = cursor_y - idx - 2 row = cursor_y - idx - 2
chosen = idx == vcursor chosen = idx == vcursor
b, e = offset
if line.length > maxc # Overflow
diff = e - (maxc - 2) if width(line) > maxc
if diff > 2 ewidth = width(line[0...e])
line = '..' + line[diff..-1] # Stri..
b -= diff - 2 if ewidth <= maxc - 2
b = [2, b].max line, _ = trim line, maxc - 2, false
line << '..'
# ..ring
else else
line = line[0, maxc] + '..' # ..ri..
line = line[0...e] + '..' if ewidth < width(line) - 2
line, diff = trim line, maxc - 2, true
b += 2 - diff
e += 2 - diff
b = [2, b].max
line = '..' + line
end end
end end
if line.length > maxc
line = line[0, maxc] + '..'
end
C.setpos row, 0 C.setpos row, 0
C.clrtoeol C.clrtoeol
cprint chosen ? '>' : ' ', color(:red, true) cprint chosen ? '>' : ' ', color(:red, true)
cprint ' ', chosen ? color(:chosen) : 0 selected = selects.include?([*item][0])
cprint selected ? '>' : ' ',
chosen ? color(:chosen) : (selected ? color(:red, true) : 0)
C.attron color(:chosen, true) if chosen C.attron color(:chosen, true) if chosen
e = [e, maxc].min if b < e
if b < maxc && b < e
C.addstr line[0, b] C.addstr line[0, b]
cprint line[b...e], color(chosen ? :match! : :match, chosen) cprint line[b...e], color(chosen ? :match! : :match, chosen)
C.attron color(:chosen, true) if chosen C.attron color(:chosen, true) if chosen
@@ -461,11 +508,10 @@ searcher = Thread.new {
C.attroff color(:chosen, true) if chosen C.attroff color(:chosen, true) if chosen
end end
print_info if !@lists.empty? || events[:loaded] print_info selects.length if !@lists.empty? || events[:loaded]
print_input
refresh refresh
end end
end end#while
rescue Exception => e rescue Exception => e
main.raise e main.raise e
end end
@@ -482,7 +528,7 @@ begin
ctrl(:d) => proc { exit 1 if input.empty? }, ctrl(:d) => proc { exit 1 if input.empty? },
ctrl(:m) => proc { ctrl(:m) => proc {
@mtx.synchronize do @mtx.synchronize do
got = @matches.fetch(@vcursor, [])[0] got = [*@matches.fetch(@vcursor, [])][0]
end end
exit 0 exit 0
}, },
@@ -497,6 +543,18 @@ begin
cursor = ridx cursor = ridx
}, },
127 => proc { input[cursor -= 1] = '' if cursor > 0 }, 127 => proc { input[cursor -= 1] = '' if cursor > 0 },
9 => proc { |o|
emit(:select) {
if sel = [*@matches.fetch(@vcursor, [])][0]
if @selects.has_key? sel
@selects.delete sel
else
@selects[sel] = 1
end
@vcursor = [0, @vcursor + (o == :stab ? 1 : -1)].max
end
} if @multi
},
:left => proc { cursor = [0, cursor - 1].max }, :left => proc { cursor = [0, cursor - 1].max },
:right => proc { cursor = [input.length, cursor + 1].min }, :right => proc { cursor = [input.length, cursor + 1].min },
} }
@@ -505,8 +563,16 @@ begin
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[:stab] = actions[9]
while true while true
# Update user input
@smtx.synchronize do
@cursor_x = cursor
print_input
refresh
end
ord = tty.getc.ord ord = tty.getc.ord
if ord == 27 if ord == 27
ord = tty.getc.ord ord = tty.getc.ord
@@ -516,6 +582,7 @@ begin
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
else :nop else :nop
end end
end end
@@ -531,16 +598,15 @@ begin
# Dispatch key event # Dispatch key event
emit(:key) { @query = input.dup } emit(:key) { @query = input.dup }
# Update user input
@smtx.synchronize do
@cursor_x = cursor
print_input
refresh
end
end end
ensure ensure
C.close_screen C.close_screen
stdout.puts got if got if got
@selects.delete got
@selects.each do |sel, _|
stdout.puts sel
end
stdout.puts got
end
end end

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.1.0' spec.version = '0.3.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}

View File

@@ -27,9 +27,13 @@ function! s:fzf(args)
try try
let tf = tempname() let tf = tempname()
let prefix = exists('g:fzf_command') ? g:fzf_command.'|' : '' let prefix = exists('g:fzf_command') ? g:fzf_command.'|' : ''
execute "silent !".prefix."/usr/bin/env ruby ".s:exec." ".a:args." > ".tf let fzf = executable(s:exec) ? s:exec : 'fzf'
execute "silent !".prefix.fzf." ".a:args." > ".tf
if !v:shell_error if !v:shell_error
execute 'silent e '.join(readfile(tf), '') let file = join(readfile(tf), '')
if !empty(file)
execute 'silent e '.file
endif
endif endif
finally finally
silent! call delete(tf) silent! call delete(tf)