Compare commits

...

48 Commits
0.8.6 ... 0.8.9

Author SHA1 Message Date
Junegunn Choi
4ceb520c1d Merge pull request #115 from JackDanger/sleep-when-curses-is-unavailable
Sleep when curses is unavailable
2015-01-02 15:28:33 +09:00
Jack Danger Canty
d761ea5158 Sleep when curses is unavailable
When the curses gem is not installed and the session is running inside
tmux the user will see a flash of an opened and closed tmux pane but
will not have a chance to read the error message.
2015-01-01 22:23:37 -08:00
Junegunn Choi
7ba93d9f83 Merge pull request #113 from thedrow/patch-1
Use travis' new build workers
2014-12-29 01:03:04 +09:00
Omer Katz
b34f93f307 Use travis' new build workers
They boot faster and since we don't use root we can use them.
2014-12-28 17:11:13 +02:00
Junegunn Choi
ec040d82dd Improve word motions: ALT-B, ALT-F, ALT-D, ALT-BS (#112) 2014-12-24 13:27:39 +09:00
Junegunn Choi
00190677d4 Add support for ALT-D and ALT-BS key bindings
https://github.com/junegunn/fzf/issues/111#issuecomment-67832143
2014-12-23 12:22:19 +09:00
Junegunn Choi
d38f7a5eb5 Merge pull request #109 from brettanomyces/reorder_fish_history
Reverse the order of fish history
2014-12-13 11:10:29 +09:00
brettanomyces
ee433ef6e9 reverse history for fish shell 2014-12-13 11:54:35 +13:00
Junegunn Choi
d89c9e94ba Handle dynamically loaded completion functions (#107 / #79) 2014-12-05 00:24:25 +09:00
Junegunn Choi
7e2dfef930 Merge pull request #106 from jagajaga/master
Change `/bin/bash` to `/usr/bin/env bash`
2014-12-01 18:19:33 +09:00
Arseniy Seroka
0296fcb5cd bash -> env bash 2014-11-30 23:04:15 +03:00
Junegunn Choi
80819f3c44 Merge pull request #104 from junegunn/add-with-nth
Add --with-nth option
2014-11-04 23:30:11 +09:00
Junegunn Choi
7571baadb4 Fix test failure on Ruby 1.8.7
Hashes are unordered on Ruby 1.8
2014-11-04 19:32:31 +09:00
Junegunn Choi
da03a66e69 Add test cases for --with-nth option 2014-11-04 19:01:15 +09:00
Junegunn Choi
3c47b7fa5f Fix --with-nth option on --multi 2014-11-03 23:58:10 +09:00
Junegunn Choi
ba9365c438 Fix --with-nth option on Ruby 1.8 2014-11-03 23:48:37 +09:00
Junegunn Choi
db37e67575 Skip failing tests on Ruby 1.8 2014-11-01 14:52:29 +09:00
Junegunn Choi
76a3ef8c37 Add --with-nth option (#102) 2014-11-01 14:49:05 +09:00
Junegunn Choi
6fd6fff3a6 [vim] Ignore 'dir' option if empty
This makes it easier to override FZF command like follows:

    autocmd VimEnter * command! -nargs=? -bang -complete=dir FZF call fzf#run({
          \ 'sink': 'tabe',
          \ 'dir': <q-args>,
          \ 'options': '-m',
          \ 'tmux_height': empty('<bang>') ? '40%' : '' })
2014-10-15 13:22:00 +09:00
Junegunn Choi
d1387bf512 Use IO.console when possible 2014-10-07 11:49:40 +09:00
Junegunn Choi
4c923a2d19 [uninstall] Remove both patterns of source command (#97)
- `[ -f ~/.fzf.${shell} ] && source ~/.fzf.${shell}"`
- `source ~/.fzf.${shell}"`
2014-09-18 19:21:58 +09:00
Junegunn Choi
4ee85f11e8 [install] Join line numbers when multiple matches found 2014-09-18 19:03:01 +09:00
Junegunn Choi
829c7f909c Merge branch 'mjwhitta-master' 2014-09-18 18:49:43 +09:00
Miles Whittaker
990fa00660 Check before sourcing, no longer need to remove 2014-09-18 00:01:39 -04:00
Miles Whittaker
77592825f0 Sometimes users prefer . instead of source
So only check for file name
2014-09-17 23:55:28 -04:00
Miles Whittaker
ce53b9b2a5 Ignore user-defined grep aliases 2014-09-14 00:53:53 -04:00
Junegunn Choi
175fe158ed Add vim-plug recipe 2014-09-02 13:06:05 +09:00
Junegunn Choi
80efafcceb Fix ALT-C keybinding to include symlinked directories
Related #95.
2014-08-31 03:22:51 +09:00
Junegunn Choi
b241409e4b Merge pull request #95 from Neki/topic/resolve_symlinks
Follow symlinks when using bash autocompletion.
2014-08-30 22:33:21 +09:00
Benoît Faucon
11967be017 Follow symlinks when using bash autocompletion. 2014-08-30 14:56:30 +02:00
Junegunn Choi
6ee811ea03 Update version 2014-08-17 02:21:34 +09:00
Junegunn Choi
d5e7303a25 Change --nth option for CTRL-R key binding (#90)
Remove `1` from --nth option. With the change you can no more use `$`
anchor to match the tail of a command index. But it makes search
around 15% faster.

    jg@jg:~> time cat history | fzf +s -n..,1,2.. -f fzf > /dev/nul
    real    0m2.929s
    user    0m2.766s
    sys     0m0.154s

    jg@jg:~> time cat history | fzf +s -n2..,.. -f fzf > /dev/null
    real    0m2.535s
    user    0m2.422s
    sys     0m0.112s
2014-08-17 00:29:57 +09:00
Junegunn Choi
2924fd3e23 Add regression test case for #91 2014-08-17 00:22:22 +09:00
Junegunn Choi
75b44aac13 Ignore UTF-8 Error (#91) 2014-08-16 19:52:56 +09:00
Junegunn Choi
86c73105ee Improve performance of --nth option (#90 contd.) 2014-08-15 04:01:37 +09:00
Junegunn Choi
2d00abc7cb Improve performance of --nth option (#90) 2014-08-15 03:02:07 +09:00
Junegunn Choi
1e07b3b1c2 [vim] Apply FZF_DEFAULT_{OPTS,COMMAND} when using tmux splits (#87)
Fixed escaping bug of the previous commit
2014-08-08 03:23:24 +09:00
Junegunn Choi
4313c1c25c Revert "[vim] Apply FZF_DEFAULT_{OPTS,COMMAND} when using tmux splits (#87)"
This reverts commit cc9938d4c9.
2014-08-08 03:13:40 +09:00
Junegunn Choi
cc9938d4c9 [vim] Apply FZF_DEFAULT_{OPTS,COMMAND} when using tmux splits (#87) 2014-08-08 02:45:11 +09:00
Junegunn Choi
a54784cd53 Display 'gem install curses' when curses cannot be loaded 2014-07-27 01:08:30 +09:00
Junegunn Choi
22989b0488 Update version number 2014-07-18 13:21:15 +09:00
Junegunn Choi
892aa1e78b Merge pull request #80 from wilywampa/master
Add control + left/right key mappings
2014-07-18 13:20:42 +09:00
Jacob Niehus
b9ab7d2413 Add control + left/right key mappings 2014-07-17 21:09:21 -07:00
Junegunn Choi
69b2a0a733 Suppress error message from bash-completion 2014-07-18 00:25:12 +09:00
Junegunn Choi
13cd4ed546 Handle dynamically loaded completion functions (#79)
On Ubuntu/Debian, completion functions can be dynamically loaded via
_completion_loader. Since those functions are not visible when
fzf-completion.bash is loaded, we need this special hack to make it
possible to fail back to the original completion function when trigger
sequence is not found.
2014-07-18 00:22:49 +09:00
Sencer Selcuk
7261d3afcd allow installation with sudo privileges 2014-07-15 12:12:05 +09:00
Junegunn Choi
84fc73ad9c [bash-completion] unset / unalias / export 2014-07-14 12:48:31 +09:00
Junegunn Choi
4103f5c3cc [bash-completion] Remove -E option from sed
Old versions of sed does not have -E option
2014-07-11 01:09:06 +09:00
9 changed files with 534 additions and 184 deletions

10
.travis.yml Normal file
View File

@@ -0,0 +1,10 @@
language: ruby
sudo: false
rvm:
- "1.8.7"
- "1.9.3"
- "2.0.0"
- "2.1.1"
install: gem install curses minitest

View File

@@ -51,8 +51,12 @@ Once you have cloned the repository, add the following line to your .vimrc.
set rtp+=~/.fzf set rtp+=~/.fzf
``` ```
Or you may use any Vim plugin manager, such as Or you may use [vim-plug](https://github.com/junegunn/vim-plug) to manage fzf
[vim-plug](https://github.com/junegunn/vim-plug). inside Vim:
```vim
Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': 'yes \| ./install' }
```
Usage Usage
----- -----
@@ -68,6 +72,7 @@ usage: fzf [options]
-n, --nth=N[,..] Comma-separated list of field index expressions -n, --nth=N[,..] Comma-separated list of field index expressions
for limiting search scope. Each can be a non-zero for limiting search scope. Each can be a non-zero
integer or a range expression ([BEGIN]..[END]) integer or a range expression ([BEGIN]..[END])
--with-nth=N[,..] Transform the item using index expressions for search
-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
@@ -270,6 +275,14 @@ ssh **<TAB>
telnet **<TAB> telnet **<TAB>
``` ```
#### Environment variables / Aliases
```sh
unset **<TAB>
export **<TAB>
unalias **<TAB>
```
#### Settings #### Settings
```sh ```sh
@@ -464,7 +477,7 @@ speed of the traversal.
```sh ```sh
# Copy the original fzf function to __fzf # Copy the original fzf function to __fzf
declare -f __fzf > /dev/null || declare -f __fzf > /dev/null ||
eval "$(echo "__fzf() {"; declare -f fzf | grep -v '^{' | tail -n +2)" eval "$(echo "__fzf() {"; declare -f fzf | \grep -v '^{' | tail -n +2)"
# Use git ls-tree when possible # Use git ls-tree when possible
fzf() { fzf() {

View File

@@ -6,3 +6,4 @@ Rake::TestTask.new(:test) do |test|
test.verbose = true test.verbose = true
end end
task :default => :test

152
fzf
View File

@@ -7,7 +7,7 @@
# / __/ / /_/ __/ # / __/ / /_/ __/
# /_/ /___/_/ Fuzzy finder for your shell # /_/ /___/_/ Fuzzy finder for your shell
# #
# Version: 0.8.6 (Jun 30, 2014) # Version: 0.8.9 (Dec 24, 2014)
# #
# Author: Junegunn Choi # Author: Junegunn Choi
# URL: https://github.com/junegunn/fzf # URL: https://github.com/junegunn/fzf
@@ -36,8 +36,14 @@
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
begin
require 'curses'
rescue LoadError
$stderr.puts 'curses gem is not installed. Try `gem install curses`.'
sleep 1
exit 1
end
require 'thread' require 'thread'
require 'curses'
require 'set' require 'set'
unless String.method_defined? :force_encoding unless String.method_defined? :force_encoding
@@ -48,11 +54,34 @@ unless String.method_defined? :force_encoding
end end
end end
class String
attr_accessor :orig
def tokenize delim, nth
unless delim
# AWK default
prefix_length = (index(/\S/) || 0) rescue 0
tokens = scan(/\S+\s*/) rescue []
else
prefix_length = 0
tokens = scan(delim) rescue []
end
nth.map { |n|
if n.begin == 0 && n.end == -1
[prefix_length, tokens.join]
elsif part = tokens[n]
[prefix_length + (tokens[0...(n.begin)] || []).join.length,
part.join]
end
}.compact
end
end
class FZF class FZF
C = Curses C = Curses
attr_reader :rxflag, :sort, :nth, :color, :black, :ansi256, :reverse, :prompt, 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 :print_query, :with_nth
def sync def sync
@shr_mtx.synchronize { yield } @shr_mtx.synchronize { yield }
@@ -90,6 +119,7 @@ class FZF
@exit0 = false @exit0 = false
@filter = nil @filter = nil
@nth = nil @nth = nil
@with_nth = nil
@delim = nil @delim = nil
@reverse = false @reverse = false
@prompt = '> ' @prompt = '> '
@@ -143,6 +173,11 @@ class FZF
@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 '--with-nth'
usage 1, 'field expression required' unless nth = argv.shift
@with_nth = parse_nth nth
when /^--with-nth=([0-9,-\.]+)$/
@with_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
@delim = FZF.build_delim_regex delim @delim = FZF.build_delim_regex delim
@@ -176,6 +211,7 @@ class FZF
@queue = Queue.new @queue = Queue.new
@pending = nil @pending = nil
@rev_dir = @reverse ? -1 : 1 @rev_dir = @reverse ? -1 : 1
@stdout = $stdout.clone
unless @filter unless @filter
# Shared variables: needs protection # Shared variables: needs protection
@@ -195,7 +231,7 @@ class FZF
end end
def parse_nth nth def parse_nth nth
nth.split(',').map { |expr| ranges = nth.split(',').map { |expr|
x = proc { usage 1, "invalid field expression: #{expr}" } x = proc { usage 1, "invalid field expression: #{expr}" }
first, second = expr.split('..', 2) first, second = expr.split('..', 2)
x.call if !first.empty? && first.to_i == 0 || x.call if !first.empty? && first.to_i == 0 ||
@@ -210,6 +246,7 @@ class FZF
Range.new(*[first, second].map { |e| e > 0 ? e - 1 : e }) Range.new(*[first, second].map { |e| e > 0 ? e - 1 : e })
} }
ranges == [0..-1] ? nil : ranges
end end
def FZF.build_delim_regex delim def FZF.build_delim_regex delim
@@ -217,6 +254,10 @@ class FZF
Regexp.compile "(?:.*?#{delim})|(?:.+?$)" Regexp.compile "(?:.*?#{delim})|(?:.+?$)"
end end
def burp string, orig = nil
@stdout.puts(orig || string.orig || string)
end
def start def start
if @filter if @filter
start_reader.join start_reader.join
@@ -231,7 +272,7 @@ class FZF
if loaded if loaded
if @select1 && len == 1 if @select1 && len == 1
puts @query if @print_query puts @query if @print_query
puts empty ? matches.first : matches.first.first burp(empty ? matches.first : matches.first.first)
exit 0 exit 0
elsif @exit0 && len == 0 elsif @exit0 && len == 0
puts @query if @print_query puts @query if @print_query
@@ -314,6 +355,7 @@ class FZF
-n, --nth=N[,..] Comma-separated list of field index expressions -n, --nth=N[,..] Comma-separated list of field index expressions
for limiting search scope. Each can be a non-zero for limiting search scope. Each can be a non-zero
integer or a range expression ([BEGIN]..[END]) integer or a range expression ([BEGIN]..[END])
--with-nth=N[,..] Transform the item using index expressions for search
-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
@@ -361,7 +403,8 @@ class FZF
end if str end if str
end end
def addstr_safe str def addstr_safe str
C.addstr str.gsub("\0", '') str = str.gsub("\0", '') rescue str
C.addstr str
end end
def print_input def print_input
@@ -514,7 +557,6 @@ class FZF
end end
def init_screen def init_screen
@stdout = $stdout.clone
$stdout.reopen($stderr) $stdout.reopen($stderr)
C.init_screen C.init_screen
@@ -589,14 +631,28 @@ class FZF
end end
Thread.new do Thread.new do
if @with_nth
while line = stream.gets
emit(:new) { @new << transform(line) }
end
else
while line = stream.gets while line = stream.gets
emit(:new) { @new << line.chomp } emit(:new) { @new << line.chomp }
end end
end
emit(:loaded) { true } emit(:loaded) { true }
@spinner.clear if @spinner @spinner.clear if @spinner
end end
end end
def transform line
line = line.chomp
mut = (line =~ / $/ ? line : line + ' ').
tokenize(@delim, @with_nth).map { |e| e.last }.join('').sub(/ *$/, '')
mut.orig = line
mut
end
def start_search &callback def start_search &callback
Thread.new do Thread.new do
lists = [] lists = []
@@ -688,7 +744,8 @@ class FZF
def pick def pick
sync do sync do
[*@matches.fetch(@ycur, [])][0] item = @matches[@ycur]
item.is_a?(Array) ? item[0] : item
end end
end end
@@ -835,7 +892,13 @@ class FZF
end end
def get_input actions def get_input actions
@tty ||= IO.open(IO.sysopen('/dev/tty'), 'r') @tty ||=
begin
require 'io/console'
IO.console
rescue LoadError
IO.open(IO.sysopen('/dev/tty'), 'r')
end
if pending = @pending if pending = @pending
@pending = nil @pending = nil
@@ -882,6 +945,8 @@ class FZF
case read_nbs case read_nbs
when [59, 50, 68] then ctrl(:a) when [59, 50, 68] then ctrl(:a)
when [59, 50, 67] then ctrl(:e) when [59, 50, 67] then ctrl(:e)
when [59, 53, 68] then :alt_b
when [59, 53, 67] then :alt_f
when [126] then ctrl(:a) when [126] then ctrl(:a)
end end
when 52 then read_nb; ctrl(:e) when 52 then read_nb; ctrl(:e)
@@ -891,8 +956,10 @@ class FZF
get_mouse get_mouse
end end
when 'b', 98 then :alt_b when 'b', 98 then :alt_b
when 'd', 100 then :alt_d
when 'f', 102 then :alt_f when 'f', 102 then :alt_f
when :esc then :esc when :esc then :esc
when 127 then :alt_bs
else next else next
end if ord == 27 end if ord == 27
@@ -948,7 +1015,20 @@ class FZF
yanked = '' yanked = ''
mouse_event = MouseEvent.new mouse_event = MouseEvent.new
backword = proc { backword = proc {
cursor = (input[0, cursor].rindex(/\s\S/) || -1) + 1 cursor = (input[0, cursor].rindex(/[^[:alnum:]][[:alnum:]]/) || -1) + 1
nil
}
forward = proc {
cursor += (input[cursor..-1].index(/([[:alnum:]][^[:alnum:]])|(.$)/) || -1) + 1
nil
}
rubout = proc { |regex|
pcursor = cursor
cursor = (input[0, cursor].rindex(regex) || -1) + 1
if pcursor > cursor
yanked = input[cursor...pcursor]
input = input[0...cursor] + input[pcursor..-1]
end
} }
actions = { actions = {
:esc => proc { exit 1 }, :esc => proc { exit 1 },
@@ -972,12 +1052,7 @@ class FZF
ctrl(:e) => proc { cursor = input.length; nil }, ctrl(:e) => proc { cursor = input.length; nil },
ctrl(:j) => proc { vselect { |v| v - @rev_dir } }, ctrl(:j) => proc { vselect { |v| v - @rev_dir } },
ctrl(:k) => proc { vselect { |v| v + @rev_dir } }, ctrl(:k) => proc { vselect { |v| v + @rev_dir } },
ctrl(:w) => proc { ctrl(:w) => proc { rubout.call /\s\S/ },
pcursor = cursor
backword.call
yanked = input[cursor...pcursor] if pcursor > cursor
input = input[0...cursor] + input[pcursor..-1]
},
ctrl(:y) => proc { actions[:default].call yanked }, ctrl(:y) => proc { actions[:default].call yanked },
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|
@@ -986,7 +1061,7 @@ class FZF
if @selects.has_key? sel if @selects.has_key? sel
@selects.delete sel @selects.delete sel
else else
@selects[sel] = 1 @selects[sel] = sel.orig
end end
end end
vselect { |v| v + case o vselect { |v| v + case o
@@ -1002,10 +1077,19 @@ class FZF
:del => proc { input[cursor] = '' if input.length > cursor }, :del => proc { input[cursor] = '' if input.length > cursor },
:pgup => proc { vselect { |v| v + @rev_dir * (max_items - 1) } }, :pgup => proc { vselect { |v| v + @rev_dir * (max_items - 1) } },
:pgdn => proc { vselect { |v| v - @rev_dir * (max_items - 1) } }, :pgdn => proc { vselect { |v| v - @rev_dir * (max_items - 1) } },
:alt_b => proc { backword.call; nil }, :alt_bs => proc { rubout.call /[^[:alnum:]][[:alnum:]]/ },
:alt_b => proc { backword.call },
:alt_d => proc {
pcursor = cursor
forward.call
if cursor > pcursor
yanked = input[pcursor...cursor]
input = input[0...pcursor] + input[cursor..-1]
cursor = pcursor
end
},
:alt_f => proc { :alt_f => proc {
cursor += (input[cursor..-1].index(/(\S\s)|(.$)/) || -1) + 1 forward.call
nil
}, },
:default => proc { |val| :default => proc { |val|
case val case val
@@ -1066,10 +1150,10 @@ class FZF
@stdout.puts q if @print_query @stdout.puts q if @print_query
if got if got
if selects.empty? if selects.empty?
@stdout.puts got burp got
else else
selects.each do |sel, _| selects.each do |sel, orig|
@stdout.puts sel burp sel, orig
end end
end end
end end
@@ -1094,27 +1178,15 @@ class FZF
end end
def tokenize str def tokenize str
@tokens_cache[str] ||= @tokens_cache[str] ||= str.tokenize(@delim, @nth)
unless @delim
# AWK default
prefix_length = str[/^\s+/].length rescue 0
[prefix_length, (str.strip.scan(/\S+\s*/) rescue [])]
else
prefix_length = 0
[prefix_length, (str.scan(@delim) rescue [])]
end
end end
def do_match str, pat def do_match str, pat
if @nth if @nth
prefix_length, tokens = tokenize str tokenize(str).each do |pair|
prefix_length, token = pair
@nth.each do |n| if md = token.match(pat) rescue nil
if (range = tokens[n]) && (token = range.join) && return MatchData.new(md.offset(0).map { |o| o + prefix_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 }
return MatchData.new(offset)
end end
end end
nil nil

View File

@@ -8,12 +8,35 @@
# - $FZF_COMPLETION_TRIGGER (default: '**') # - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty) # - $FZF_COMPLETION_OPTS (default: empty)
_fzf_orig_completion_filter() {
sed 's/.*-F *\([^ ]*\).* \([^ ]*\)$/export _fzf_orig_completion_\2=\1;/' |
sed 's/[^a-z0-9_= ;]/_/g'
}
_fzf_opts_completion() { _fzf_opts_completion() {
local cur prev opts local cur prev opts
COMPREPLY=() COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}" prev="${COMP_WORDS[COMP_CWORD-1]}"
opts="-m --multi -x --extended -s --sort +s +i +c --no-color" opts="
-x --extended
-e --extended-exact
-i +i
-n --nth
-d --delimiter
-s --sort +s
-m --multi
--no-mouse
+c --no-color
+2 --no-256
--black
--reverse
--prompt
-q --query
-1 --select-1
-0 --exit-0
-f --filter
--print-query"
case "${prev}" in case "${prev}" in
--sort|-s) --sort|-s)
@@ -30,8 +53,25 @@ _fzf_opts_completion() {
return 0 return 0
} }
_fzf_generic_completion() { _fzf_handle_dynamic_completion() {
local cur base dir leftover matches trigger cmd orig local cmd orig ret
cmd="$1"
shift
orig=$(eval "echo \$_fzf_orig_completion_$cmd")
if [ -n "$orig" ] && type "$orig" > /dev/null 2>&1; then
$orig "$@"
elif [ -n "$_fzf_completion_loader" ]; then
_completion_loader "$@"
ret=$?
eval $(complete | \grep "\-F.* $cmd$" | _fzf_orig_completion_filter)
source $BASH_SOURCE
return $ret
fi
}
_fzf_path_completion() {
local cur base dir leftover matches trigger cmd
cmd=$(echo ${COMP_WORDS[0]} | sed 's/[^a-z0-9_=]/_/g') cmd=$(echo ${COMP_WORDS[0]} | sed 's/[^a-z0-9_=]/_/g')
COMPREPLY=() COMPREPLY=()
trigger=${FZF_COMPLETION_TRIGGER:-**} trigger=${FZF_COMPLETION_TRIGGER:-**}
@@ -47,7 +87,7 @@ _fzf_generic_completion() {
leftover=${leftover/#\/} leftover=${leftover/#\/}
[ "$dir" = './' ] && dir='' [ "$dir" = './' ] && dir=''
tput sc tput sc
matches=$(find "$dir"* $1 2> /dev/null | fzf $FZF_COMPLETION_OPTS $2 -q "$leftover" | while read item; do matches=$(find -L "$dir"* $1 2> /dev/null | fzf $FZF_COMPLETION_OPTS $2 -q "$leftover" | while read item; do
printf '%q ' "$item" printf '%q ' "$item"
done) done)
matches=${matches% } matches=${matches% }
@@ -65,25 +105,48 @@ _fzf_generic_completion() {
else else
shift shift
shift shift
orig=$(eval "echo \$_fzf_orig_completion_$cmd") _fzf_handle_dynamic_completion "$cmd" "$@"
[ -n "$orig" ] && type "$orig" > /dev/null && $orig "$@" fi
}
_fzf_list_completion() {
local cur selected trigger cmd src
read -r src
cmd=$(echo ${COMP_WORDS[0]} | sed 's/[^a-z0-9_=]/_/g')
trigger=${FZF_COMPLETION_TRIGGER:-**}
cur="${COMP_WORDS[COMP_CWORD]}"
if [[ ${cur} == *"$trigger" ]]; then
cur=${cur:0:${#cur}-${#trigger}}
tput sc
selected=$(eval "$src | fzf $FZF_COMPLETION_OPTS $1 -q '$cur'" | tr '\n' ' ')
selected=${selected% }
tput rc
if [ -n "$selected" ]; then
COMPREPLY=("$selected")
return 0
fi
else
shift
_fzf_handle_dynamic_completion "$cmd" "$@"
fi fi
} }
_fzf_all_completion() { _fzf_all_completion() {
_fzf_generic_completion \ _fzf_path_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_path_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_path_completion \
"-name .git -prune -o -name .svn -prune -o -type d -print" \ "-name .git -prune -o -name .svn -prune -o -type d -print" \
"" "$@" "" "$@"
} }
@@ -103,40 +166,27 @@ _fzf_kill_completion() {
} }
_fzf_telnet_completion() { _fzf_telnet_completion() {
local cur selected trigger _fzf_list_completion '+m' "$@" << "EOF"
trigger=${FZF_COMPLETION_TRIGGER:-**} \grep -v '^\s*\(#\|$\)' /etc/hosts | awk '{if (length($2) > 0) {print $2}}' | sort -u
cur="${COMP_WORDS[COMP_CWORD]}" EOF
[[ ${cur} == *"$trigger" ]] || return 1
cur=${cur:0:${#cur}-${#trigger}}
tput sc
selected=$(grep -v '^\s*\(#\|$\)' /etc/hosts | awk '{print $2}' | sort -u | fzf $FZF_COMPLETION_OPTS -q "$cur")
tput rc
if [ -n "$selected" ]; then
COMPREPLY=("$selected")
return 0
fi
} }
_fzf_ssh_completion() { _fzf_ssh_completion() {
local cur selected trigger _fzf_list_completion '+m' "$@" << "EOF"
trigger=${FZF_COMPLETION_TRIGGER:-**} cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | \grep -i ^host | \grep -v '*') <(\grep -v '^\s*\(#\|$\)' /etc/hosts) | awk '{print $2}' | sort -u
cur="${COMP_WORDS[COMP_CWORD]}" EOF
[[ ${cur} == *"$trigger" ]] || return 1 }
cur=${cur:0:${#cur}-${#trigger}}
tput sc _fzf_env_var_completion() {
selected=$(cat \ _fzf_list_completion '-m' "$@" << "EOF"
<(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | grep -i ^host) \ declare -xp | sed 's/=.*//' | sed 's/.* //'
<(grep -v '^\s*\(#\|$\)' /etc/hosts) | \ EOF
awk '{print $2}' | sort -u | fzf $FZF_COMPLETION_OPTS -q "$cur") }
tput rc
if [ -n "$selected" ]; then _fzf_alias_completion() {
COMPREPLY=("$selected") _fzf_list_completion '-m' "$@" << "EOF"
return 0 alias | sed 's/=.*//' | sed 's/.* //'
fi EOF
} }
# fzf options # fzf options
@@ -153,15 +203,18 @@ a_cmds="
find git grep gunzip gzip hg jar find git grep gunzip gzip hg jar
ln ls mv open rm rsync scp ln ls mv open rm rsync scp
svn tar unzip zip" svn tar unzip zip"
x_cmds="kill ssh telnet unset unalias export"
# Preserve existing completion # Preserve existing completion
if [ "$_fzf_completion_loaded" != '0.8.6' ]; then if [ "$_fzf_completion_loaded" != '0.8.6-1' ]; then
# Really wish I could use associative array but OSX comes with bash 3.2 :( # Really wish I could use associative array but OSX comes with bash 3.2 :(
eval $(complete | grep '\-F' | grep -v _fzf_ | eval $(complete | \grep '\-F' | \grep -v _fzf_ |
grep -E -w "$(echo $d_cmds $f_cmds $a_cmds | sed 's/ /|/g' | sed 's/+/\\+/g')" | \grep -E " ($(echo $d_cmds $f_cmds $a_cmds $x_cmds | sed 's/ /|/g' | sed 's/+/\\+/g'))$" | _fzf_orig_completion_filter)
sed -E 's/.*-F *([^ ]*).* ([^ ]*)$/export _fzf_orig_completion_\2=\1;/' | export _fzf_completion_loaded=0.8.6-1
sed 's/[^a-z0-9_= ;]/_/g') fi
export _fzf_completion_loaded=0.8.6
if type _completion_loader > /dev/null 2>&1; then
_fzf_completion_loader=1
fi fi
# Directory # Directory
@@ -186,4 +239,9 @@ 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 # Environment variables / Aliases
complete -F _fzf_env_var_completion -o default -o bashdefault unset
complete -F _fzf_env_var_completion -o default -o bashdefault export
complete -F _fzf_alias_completion -o default -o bashdefault unalias
unset cmd d_cmds f_cmds a_cmds x_cmds

46
install
View File

@@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
cd `dirname $BASH_SOURCE` cd `dirname $BASH_SOURCE`
fzf_base=`pwd` fzf_base=`pwd`
@@ -28,7 +28,11 @@ if [ $? -eq 0 ]; then
else else
echo "Not found" echo "Not found"
echo "Installing 'curses' gem ... " echo "Installing 'curses' gem ... "
/usr/bin/env gem install curses -v 1.0.0 --user-install if (( EUID )); then
/usr/bin/env gem install curses --user-install
else
/usr/bin/env gem install curses
fi
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo echo
echo "Failed to install 'curses' gem." echo "Failed to install 'curses' gem."
@@ -122,13 +126,13 @@ __fsel_tmux() {
__fcd() { __fcd() {
local dir local dir
dir=$(command find ${1:-*} -path '*/\.*' -prune -o -type d -print 2> /dev/null | fzf +m) && printf 'cd %q' "$dir" dir=$(command find -L ${1:-*} -path '*/\.*' -prune -o -type d -print 2> /dev/null | fzf +m) && printf 'cd %q' "$dir"
} }
__use_tmux=0 __use_tmux=0
[ -n "$TMUX_PANE" -a ${FZF_TMUX:-1} -ne 0 -a ${LINES:-40} -gt 15 ] && __use_tmux=1 [ -n "$TMUX_PANE" -a ${FZF_TMUX:-1} -ne 0 -a ${LINES:-40} -gt 15 ] && __use_tmux=1
if [ -z "$(set -o | grep '^vi.*on')" ]; then if [ -z "$(set -o | \grep '^vi.*on')" ]; then
# Required to refresh the prompt after fzf # Required to refresh the prompt after fzf
bind '"\er": redraw-current-line' bind '"\er": redraw-current-line'
@@ -140,7 +144,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 +m -n..,1,2.. | sed \"s/ *[0-9]* *//\")\e\C-e\er"' bind '"\C-r": " \C-e\C-u$(HISTTIMEFORMAT= history | fzf +s +m -n2..,.. | 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"'
@@ -158,7 +162,7 @@ else
bind -m vi-command '"\C-t": "i\C-t"' 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 +m -n..,1,2.. | sed \"s/ *[0-9]* *//\")\C-x\C-e\e$a\C-x\C-r"' bind '"\C-r": "\eddi$(HISTTIMEFORMAT= history | fzf +s +m -n2..,.. | sed \"s/ *[0-9]* *//\")\C-x\C-e\e$a\C-x\C-r"'
bind -m vi-command '"\C-r": "i\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
@@ -210,7 +214,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; command find * -path '*/\.*' -prune \ cd "${$(set -o nonomatch; command find -L * -path '*/\.*' -prune \
-o -type d -print 2> /dev/null | fzf):-.}" -o -type d -print 2> /dev/null | fzf):-.}"
zle reset-prompt zle reset-prompt
} }
@@ -219,7 +223,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 +m -n..,1,2.. | sed "s/ *[0-9*]* *//") LBUFFER=$(fc -l 1 | fzf +s +m -n2..,.. | sed "s/ *[0-9*]* *//")
zle redisplay zle redisplay
} }
zle -N fzf-history-widget zle -N fzf-history-widget
@@ -264,7 +268,7 @@ function fzf_key_bindings
end end
function __fzf_list_dir function __fzf_list_dir
command find * -path '*/\.*' -prune -o -type d -print 2> /dev/null command find -L * -path '*/\.*' -prune -o -type d -print 2> /dev/null
end end
function __fzf_escape function __fzf_escape
@@ -290,8 +294,16 @@ function fzf_key_bindings
rm -f $TMPDIR/fzf.result rm -f $TMPDIR/fzf.result
end end
function __fzf_reverse
if which tac > /dev/null
tac $argv
else
tail -r $argv
end
end
function __fzf_ctrl_r function __fzf_ctrl_r
history | fzf +s +m > $TMPDIR/fzf.result history | __fzf_reverse | fzf +s +m > $TMPDIR/fzf.result
and commandline (cat $TMPDIR/fzf.result) and commandline (cat $TMPDIR/fzf.result)
commandline -f repaint commandline -f repaint
rm -f $TMPDIR/fzf.result rm -f $TMPDIR/fzf.result
@@ -312,7 +324,7 @@ function fzf_key_bindings
else else
set height 40% set height 40%
end end
if echo $height | grep -q -E '%$' if echo $height | \grep -q -E '%$'
echo "-p "(echo $height | sed 's/%$//') echo "-p "(echo $height | sed 's/%$//')
else else
echo "-l $height" echo "-l $height"
@@ -333,19 +345,23 @@ append_line() {
echo "Update $2:" echo "Update $2:"
echo " - $1" echo " - $1"
[ -f "$2" ] || touch "$2" [ -f "$2" ] || touch "$2"
line=$(grep -nF "$1" "$2" | sed 's/:.*//') if [ $# -lt 3 ]; then
line=$(\grep -nF "$1" "$2" | sed 's/:.*//' | tr '\n' ' ')
else
line=$(\grep -nF "$3" "$2" | sed 's/:.*//' | tr '\n' ' ')
fi
if [ -n "$line" ]; then if [ -n "$line" ]; then
echo " - Already exists (line #$line)" echo " - Already exists: line #$line"
else else
echo "$1" >> "$2" echo "$1" >> "$2"
echo " - Added" echo " + Added"
fi fi
echo echo
} }
echo echo
for shell in bash zsh; do for shell in bash zsh; do
append_line "source ~/.fzf.${shell}" ~/.${shell}rc append_line "[ -f ~/.fzf.${shell} ] && source ~/.fzf.${shell}" ~/.${shell}rc "~/.fzf.${shell}"
done done
if [ $key_bindings -eq 0 -a $has_fish -eq 1 ]; then if [ $key_bindings -eq 0 -a $has_fish -eq 1 ]; then

View File

@@ -113,7 +113,7 @@ function! s:tmux_splittable(dict)
endfunction endfunction
function! s:pushd(dict) function! s:pushd(dict)
if has_key(a:dict, 'dir') if !empty(get(a:dict, 'dir', ''))
let a:dict.prev_dir = getcwd() let a:dict.prev_dir = getcwd()
execute 'chdir '.s:escape(a:dict.dir) execute 'chdir '.s:escape(a:dict.dir)
endif endif
@@ -148,11 +148,18 @@ function! s:execute(dict, command, temps)
endif endif
endfunction endfunction
function! s:execute_tmux(dict, command, temps) function! s:env_var(name)
if has_key(a:dict, 'dir') if exists('$'.a:name)
let command = 'cd '.s:escape(a:dict.dir).' && '.a:command return a:name . "='". substitute(expand('$'.a:name), "'", "'\\\\''", 'g') . "' "
else else
let command = a:command return ''
endif
endfunction
function! s:execute_tmux(dict, command, temps)
let command = s:env_var('FZF_DEFAULT_OPTS').s:env_var('FZF_DEFAULT_COMMAND').a:command
if !empty(get(a:dict, 'dir', ''))
let command = 'cd '.s:escape(a:dict.dir).' && '.command
endif endif
let splitopt = '-v' let splitopt = '-v'

View File

@@ -1,14 +1,59 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# encoding: utf-8 # encoding: utf-8
require 'rubygems'
require 'curses' require 'curses'
require 'timeout' require 'timeout'
require 'stringio' require 'stringio'
require 'minitest/autorun' require 'minitest/autorun'
require 'tempfile'
$LOAD_PATH.unshift File.expand_path('../..', __FILE__) $LOAD_PATH.unshift File.expand_path('../..', __FILE__)
ENV['FZF_EXECUTABLE'] = '0' ENV['FZF_EXECUTABLE'] = '0'
load 'fzf' load 'fzf'
class MockTTY
def initialize
@buffer = ''
@mutex = Mutex.new
@condv = ConditionVariable.new
end
def read_nonblock sz
@mutex.synchronize do
take sz
end
end
def take sz
if @buffer.length >= sz
ret = @buffer[0, sz]
@buffer = @buffer[sz..-1]
ret
end
end
def getc
sleep 0.1
while true
@mutex.synchronize do
if char = take(1)
return char
else
@condv.wait(@mutex)
end
end
end
end
def << str
@mutex.synchronize do
@buffer << str
@condv.broadcast
end
self
end
end
class TestFZF < MiniTest::Unit::TestCase class TestFZF < MiniTest::Unit::TestCase
def setup def setup
ENV.delete 'FZF_DEFAULT_SORT' ENV.delete 'FZF_DEFAULT_SORT'
@@ -24,6 +69,7 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal nil, fzf.rxflag assert_equal nil, fzf.rxflag
assert_equal true, fzf.mouse assert_equal true, fzf.mouse
assert_equal nil, fzf.nth assert_equal nil, fzf.nth
assert_equal nil, fzf.with_nth
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
@@ -46,7 +92,7 @@ class TestFZF < MiniTest::Unit::TestCase
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 --print-query' '--no-mouse -f "goodbye world" --black --with-nth=3,-3..,2 --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 ',
@@ -64,13 +110,14 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal true, fzf.reverse assert_equal true, fzf.reverse
assert_equal true, fzf.print_query assert_equal true, fzf.print_query
assert_equal [2..2, -1..-1, 1..1], fzf.nth assert_equal [2..2, -1..-1, 1..1], fzf.nth
assert_equal [2..2, -3..-1, 1..1], fzf.with_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 --prompt (hi) --no-mouse --no-256 --nth=1 --with-nth=.. --reverse --prompt (hi)
--print-query] --print-query]
assert_equal 2000, fzf.sort assert_equal 2000, fzf.sort
assert_equal true, fzf.multi assert_equal true, fzf.multi
@@ -85,6 +132,7 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal 'howdy', fzf.filter assert_equal 'howdy', fzf.filter
assert_equal :exact, fzf.extended assert_equal :exact, fzf.extended
assert_equal [0..0], fzf.nth assert_equal [0..0], fzf.nth
assert_equal nil, fzf.with_nth
assert_equal true, fzf.reverse assert_equal true, fzf.reverse
assert_equal '(hi)', fzf.prompt assert_equal '(hi)', fzf.prompt
assert_equal true, fzf.print_query assert_equal true, fzf.print_query
@@ -167,13 +215,12 @@ class TestFZF < MiniTest::Unit::TestCase
end end
end end
# FIXME Only on 1.9 or above
def test_width def test_width
fzf = FZF.new [] fzf = FZF.new []
assert_equal 5, fzf.width('abcde') assert_equal 5, fzf.width('abcde')
assert_equal 4, fzf.width('한글') assert_equal 4, fzf.width('한글')
assert_equal 5, fzf.width('한글.') assert_equal 5, fzf.width('한글.')
end end if RUBY_VERSION >= '1.9'
def test_trim def test_trim
fzf = FZF.new [] fzf = FZF.new []
@@ -186,7 +233,7 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal ['가나a', 6], fzf.trim('가나ab라마바사.', 5, false) assert_equal ['가나a', 6], fzf.trim('가나ab라마바사.', 5, false)
assert_equal ['가나ab', 5], fzf.trim('가나ab라마바사.', 6, false) assert_equal ['가나ab', 5], fzf.trim('가나ab라마바사.', 6, false)
assert_equal ['가나ab', 5], fzf.trim('가나ab라마바사.', 7, false) assert_equal ['가나ab', 5], fzf.trim('가나ab라마바사.', 7, false)
end end if RUBY_VERSION >= '1.9'
def test_format def test_format
fzf = FZF.new [] fzf = FZF.new []
@@ -562,28 +609,41 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal [[list[0], [[8, 9]]]], matcher.match(list, '^s', '', '') assert_equal [[list[0], [[8, 9]]]], matcher.match(list, '^s', '', '')
end end
def stream_for str def stream_for str, delay = 0
StringIO.new(str).tap do |sio| StringIO.new(str).tap do |sio|
sio.instance_eval do sio.instance_eval do
alias org_gets gets alias org_gets gets
def gets def gets
org_gets.tap { |e| sleep 0.5 unless e.nil? } org_gets.tap { |e| sleep(@delay) unless e.nil? }
end
def reopen _
end end
end end
sio.instance_variable_set :@delay, delay
end end
end end
def assert_fzf_output opts, given, expected def assert_fzf_output opts, given, expected
stream = stream_for given stream = stream_for given
output = StringIO.new output = stream_for ''
def sorted_lines line
line.split($/).sort
end
begin begin
tty = MockTTY.new
$stdout = output $stdout = output
FZF.new(opts, stream).start fzf = FZF.new(opts, stream)
fzf.instance_variable_set :@tty, tty
thr = block_given? && Thread.new { yield tty }
fzf.start
thr && thr.join
rescue SystemExit => e rescue SystemExit => e
assert_equal 0, e.status assert_equal 0, e.status
assert_equal expected, output.string.chomp assert_equal sorted_lines(expected), sorted_lines(output.string)
ensure ensure
$stdout = STDOUT $stdout = STDOUT
end end
@@ -612,15 +672,12 @@ class TestFZF < MiniTest::Unit::TestCase
end end
def test_select_1_ambiguity def test_select_1_ambiguity
stream = stream_for "Hello\nWorld"
begin begin
Timeout::timeout(3) do Timeout::timeout(0.5) do
FZF.new(%w[--query=o --select-1], stream).start assert_fzf_output %w[--query=o --select-1], "hello\nworld", "should not match"
end end
flunk 'Should not reach here' rescue Timeout::Error
rescue Exception => e
Curses.close_screen Curses.close_screen
assert_instance_of Timeout::Error, e
end end
end end
@@ -637,6 +694,32 @@ class TestFZF < MiniTest::Unit::TestCase
assert_fzf_output %w[--exit-0], '', '' assert_fzf_output %w[--exit-0], '', ''
end end
def test_with_nth
source = "hello world\nbatman"
assert_fzf_output %w[-0 -1 --with-nth=2,1 -x -q ^worl],
source, 'hello world'
assert_fzf_output %w[-0 -1 --with-nth=2,1 -x -q llo$],
source, 'hello world'
assert_fzf_output %w[-0 -1 --with-nth=.. -x -q llo$],
source, ''
assert_fzf_output %w[-0 -1 --with-nth=2,2,2,..,1 -x -q worlworlworlhellworlhell],
source, 'hello world'
assert_fzf_output %w[-0 -1 --with-nth=1,1,-1,1 -x -q batbatbatbat],
source, 'batman'
end
def test_with_nth_transform
fzf = FZF.new %w[--with-nth 2..,1]
assert_equal 'my world hello', fzf.transform('hello my world')
assert_equal 'my world hello', fzf.transform('hello my world')
assert_equal 'my world hello', fzf.transform('hello my world ')
fzf = FZF.new %w[--with-nth 2,-1,2]
assert_equal 'my world my', fzf.transform('hello my world')
assert_equal 'world world world', fzf.transform('hello world')
assert_equal 'world world world', fzf.transform('hello world ')
end
def test_ranking_overlap_match_regions def test_ranking_overlap_match_regions
list = [ list = [
'1 3 4 2', '1 3 4 2',
@@ -681,5 +764,87 @@ class TestFZF < MiniTest::Unit::TestCase
# **[***** #] => [******# ] # **[***** #] => [******# ]
assert_equal [true, 0, 6], fzf.constrain(2, 10, 7, 10) assert_equal [true, 0, 6], fzf.constrain(2, 10, 7, 10)
end end
def test_invalid_utf8
tmp = Tempfile.new('fzf')
tmp << 'hello ' << [0xff].pack('C*') << ' world' << $/ << [0xff].pack('C*')
tmp.close
begin
Timeout::timeout(0.5) do
FZF.new(%w[-n..,1,2.. -q^ -x], File.open(tmp.path)).start
end
rescue Timeout::Error
Curses.close_screen
end
ensure
tmp.unlink
end
def test_with_nth_mock_tty
# Manual selection with input
assert_fzf_output ["--with-nth=2,1"], "hello world", "hello world" do |tty|
tty << "world"
tty << "hell"
tty << "\r"
end
# Manual selection without input
assert_fzf_output ["--with-nth=2,1"], "hello world", "hello world" do |tty|
tty << "\r"
end
# Manual selection with input and --multi
lines = "hello world\ngoodbye world"
assert_fzf_output %w[-m --with-nth=2,1], lines, lines do |tty|
tty << "o"
tty << "\e[Z\e[Z"
tty << "\r"
end
# Manual selection without input and --multi
assert_fzf_output %w[-m --with-nth=2,1], lines, lines do |tty|
tty << "\e[Z\e[Z"
tty << "\r"
end
# ALT-D
assert_fzf_output %w[--print-query], "", "hello baby = world" do |tty|
tty << "hello world baby"
tty << alt(:b) << alt(:b) << alt(:d)
tty << ctrl(:e) << " = " << ctrl(:y)
tty << "\r"
end
# ALT-BACKSPACE
assert_fzf_output %w[--print-query], "", "hello baby = world " do |tty|
tty << "hello world baby"
tty << alt(:b) << alt(127.chr)
tty << ctrl(:e) << " = " << ctrl(:y)
tty << "\r"
end
# Word-movements
assert_fzf_output %w[--print-query], "", "ello!_orld!~ foo=?" do |tty|
tty << "hello_world==baby?"
tty << alt(:b) << ctrl(:d)
tty << alt(:b) << ctrl(:d)
tty << alt(:b) << ctrl(:d)
tty << alt(:f) << '!'
tty << alt(:f) << '!'
tty << alt(:d) << '~'
tty << " foo=bar foo=bar"
tty << ctrl(:w)
tty << alt(127.chr)
tty << "\r"
end
end
def alt chr
"\e#{chr}"
end
def ctrl char
char.to_s.ord - 'a'.ord + 1
end
end end

View File

@@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
confirm() { confirm() {
while [ 1 ]; do while [ 1 ]; do
@@ -18,18 +18,24 @@ remove() {
} }
remove_line() { remove_line() {
src=$(readlink "$2") src=$(readlink "$1")
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
echo "Remove from $2 ($src):" echo "Remove from $1 ($src):"
else else
src=$2 src=$1
echo "Remove from $2:" echo "Remove from $1:"
fi fi
shift
line_no=1 line_no=1
match=0 match=0
while [ 1 ]; do while [ -n "$1" ]; do
line=$(sed -n "$line_no,\$p" "$src" | grep -m1 -nF "$1") || break line=$(sed -n "$line_no,\$p" "$src" | \grep -m1 -nF "$1")
if [ $? -ne 0 ]; then
shift
line_no=1
continue
fi
line_no=$(( $(sed 's/:.*//' <<< "$line") + line_no - 1 )) line_no=$(( $(sed 's/:.*//' <<< "$line") + line_no - 1 ))
content=$(sed 's/^[0-9]*://' <<< "$line") content=$(sed 's/^[0-9]*://' <<< "$line")
match=1 match=1
@@ -50,12 +56,14 @@ remove_line() {
for shell in bash zsh; do for shell in bash zsh; do
remove ~/.fzf.${shell} remove ~/.fzf.${shell}
remove_line "source ~/.fzf.${shell}" ~/.${shell}rc remove_line ~/.${shell}rc \
"[ -f ~/.fzf.${shell} ] && source ~/.fzf.${shell}" \
"source ~/.fzf.${shell}"
done done
bind_file=~/.config/fish/functions/fish_user_key_bindings.fish bind_file=~/.config/fish/functions/fish_user_key_bindings.fish
if [ -f "$bind_file" ]; then if [ -f "$bind_file" ]; then
remove_line "fzf_key_bindings" "$bind_file" remove_line "$bind_file" "fzf_key_bindings"
fi fi
if [ -d ~/.config/fish/functions ]; then if [ -d ~/.config/fish/functions ]; then