m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-15 06:43:47 -05:00

Compare commits

...

30 Commits
0.8.7 ... 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
9 changed files with 403 additions and 152 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
----- -----
@@ -61,38 +65,39 @@ Usage
usage: fzf [options] usage: fzf [options]
Search Search
-x, --extended Extended-search mode -x, --extended Extended-search mode
-e, --extended-exact Extended-search mode (exact match) -e, --extended-exact Extended-search mode (exact match)
-i Case-insensitive match (default: smart-case match) -i Case-insensitive match (default: smart-case match)
+i Case-sensitive match +i Case-sensitive match
-n, --nth=N[,..] Comma-separated list of field 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])
-d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style) --with-nth=N[,..] Transform the item using index expressions for search
-d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style)
Search result Search result
-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.
Interface Interface
-m, --multi Enable multi-select with tab/shift-tab -m, --multi Enable multi-select with tab/shift-tab
--no-mouse Disable mouse --no-mouse Disable mouse
+c, --no-color Disable colors +c, --no-color Disable colors
+2, --no-256 Disable 256-color +2, --no-256 Disable 256-color
--black Use black background --black Use black background
--reverse Reverse orientation --reverse Reverse orientation
--prompt=STR Input prompt (default: '> ') --prompt=STR Input prompt (default: '> ')
Scripting Scripting
-q, --query=STR Start the finder with the given query -q, --query=STR Start the finder with the given query
-1, --select-1 Automatically select the only match -1, --select-1 Automatically select the only match
-0, --exit-0 Exit immediately when there's no match -0, --exit-0 Exit immediately when there's no match
-f, --filter=STR Filter mode. Do not start interactive finder. -f, --filter=STR Filter mode. Do not start interactive finder.
--print-query Print query as the first line --print-query Print query as the first line
Environment variables Environment variables
FZF_DEFAULT_COMMAND Default command to use when input is tty FZF_DEFAULT_COMMAND Default command to use when input is tty
FZF_DEFAULT_OPTS Defaults options. (e.g. "-x -m --sort 10000") FZF_DEFAULT_OPTS Defaults options. (e.g. "-x -m --sort 10000")
``` ```
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
@@ -472,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

192
fzf
View File

@@ -7,7 +7,7 @@
# / __/ / /_/ __/ # / __/ / /_/ __/
# /_/ /___/_/ Fuzzy finder for your shell # /_/ /___/_/ Fuzzy finder for your shell
# #
# Version: 0.8.7 (Aug 17, 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
@@ -40,6 +40,7 @@ begin
require 'curses' require 'curses'
rescue LoadError rescue LoadError
$stderr.puts 'curses gem is not installed. Try `gem install curses`.' $stderr.puts 'curses gem is not installed. Try `gem install curses`.'
sleep 1
exit 1 exit 1
end end
require 'thread' require 'thread'
@@ -53,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 }
@@ -95,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 = '> '
@@ -148,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
@@ -181,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
@@ -200,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 ||
@@ -215,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
@@ -222,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
@@ -236,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
@@ -312,39 +348,40 @@ class FZF
$stderr.puts %[usage: fzf [options] $stderr.puts %[usage: fzf [options]
Search Search
-x, --extended Extended-search mode -x, --extended Extended-search mode
-e, --extended-exact Extended-search mode (exact match) -e, --extended-exact Extended-search mode (exact match)
-i Case-insensitive match (default: smart-case match) -i Case-insensitive match (default: smart-case match)
+i Case-sensitive match +i Case-sensitive match
-n, --nth=N[,..] Comma-separated list of field 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])
-d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style) --with-nth=N[,..] Transform the item using index expressions for search
-d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style)
Search result Search result
-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.
Interface Interface
-m, --multi Enable multi-select with tab/shift-tab -m, --multi Enable multi-select with tab/shift-tab
--no-mouse Disable mouse --no-mouse Disable mouse
+c, --no-color Disable colors +c, --no-color Disable colors
+2, --no-256 Disable 256-color +2, --no-256 Disable 256-color
--black Use black background --black Use black background
--reverse Reverse orientation --reverse Reverse orientation
--prompt=STR Input prompt (default: '> ') --prompt=STR Input prompt (default: '> ')
Scripting Scripting
-q, --query=STR Start the finder with the given query -q, --query=STR Start the finder with the given query
-1, --select-1 Automatically select the only match -1, --select-1 Automatically select the only match
-0, --exit-0 Exit immediately when there's no match -0, --exit-0 Exit immediately when there's no match
-f, --filter=STR Filter mode. Do not start interactive finder. -f, --filter=STR Filter mode. Do not start interactive finder.
--print-query Print query as the first line --print-query Print query as the first line
Environment variables Environment variables
FZF_DEFAULT_COMMAND Default command to use when input is tty FZF_DEFAULT_COMMAND Default command to use when input is tty
FZF_DEFAULT_OPTS Defaults options. (e.g. "-x -m --sort 10000")] + $/ + $/ FZF_DEFAULT_OPTS Defaults options. (e.g. "-x -m --sort 10000")] + $/ + $/
exit x exit x
end end
def emit event def emit event
@@ -520,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
@@ -595,14 +631,28 @@ class FZF
end end
Thread.new do Thread.new do
while line = stream.gets if @with_nth
emit(:new) { @new << line.chomp } while line = stream.gets
emit(:new) { @new << transform(line) }
end
else
while line = stream.gets
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 = []
@@ -694,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
@@ -841,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
@@ -899,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
@@ -956,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 },
@@ -980,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|
@@ -994,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
@@ -1010,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
@@ -1074,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
@@ -1102,25 +1178,7 @@ class FZF
end end
def tokenize str def tokenize str
@tokens_cache[str] ||= @tokens_cache[str] ||= str.tokenize(@delim, @nth)
begin
unless @delim
# AWK default
prefix_length = (str.index(/\S/) || 0) rescue 0
tokens = str.scan(/\S+\s*/) rescue []
else
prefix_length = 0
tokens = str.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 end
def do_match str, pat def do_match str, pat

View File

@@ -53,8 +53,25 @@ _fzf_opts_completion() {
return 0 return 0
} }
_fzf_handle_dynamic_completion() {
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() { _fzf_path_completion() {
local cur base dir leftover matches trigger cmd orig 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:-**}
@@ -70,7 +87,7 @@ _fzf_path_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% }
@@ -88,13 +105,12 @@ _fzf_path_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 2>&1 && $orig "$@"
fi fi
} }
_fzf_list_completion() { _fzf_list_completion() {
local cur selected trigger cmd src ret local cur selected trigger cmd src
read -r src read -r src
cmd=$(echo ${COMP_WORDS[0]} | sed 's/[^a-z0-9_=]/_/g') cmd=$(echo ${COMP_WORDS[0]} | sed 's/[^a-z0-9_=]/_/g')
trigger=${FZF_COMPLETION_TRIGGER:-**} trigger=${FZF_COMPLETION_TRIGGER:-**}
@@ -113,16 +129,7 @@ _fzf_list_completion() {
fi fi
else else
shift shift
orig=$(eval "echo \$_fzf_orig_completion_$cmd") _fzf_handle_dynamic_completion "$cmd" "$@"
if [ -n "$orig" ] && type "$orig" > /dev/null; 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
fi fi
} }
@@ -160,13 +167,13 @@ _fzf_kill_completion() {
_fzf_telnet_completion() { _fzf_telnet_completion() {
_fzf_list_completion '+m' "$@" << "EOF" _fzf_list_completion '+m' "$@" << "EOF"
grep -v '^\s*\(#\|$\)' /etc/hosts | awk '{if (length($2) > 0) {print $2}}' | sort -u \grep -v '^\s*\(#\|$\)' /etc/hosts | awk '{if (length($2) > 0) {print $2}}' | sort -u
EOF EOF
} }
_fzf_ssh_completion() { _fzf_ssh_completion() {
_fzf_list_completion '+m' "$@" << "EOF" _fzf_list_completion '+m' "$@" << "EOF"
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 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
EOF EOF
} }
@@ -201,8 +208,8 @@ x_cmds="kill ssh telnet unset unalias export"
# Preserve existing completion # Preserve existing completion
if [ "$_fzf_completion_loaded" != '0.8.6-1' ]; 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 " ($(echo $d_cmds $f_cmds $a_cmds $x_cmds | sed 's/ /|/g' | sed 's/+/\\+/g'))$" | _fzf_orig_completion_filter) \grep -E " ($(echo $d_cmds $f_cmds $a_cmds $x_cmds | sed 's/ /|/g' | sed 's/+/\\+/g'))$" | _fzf_orig_completion_filter)
export _fzf_completion_loaded=0.8.6-1 export _fzf_completion_loaded=0.8.6-1
fi fi

34
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`
@@ -126,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'
@@ -214,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
} }
@@ -268,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
@@ -294,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
@@ -316,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"
@@ -337,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
@@ -158,7 +158,7 @@ endfunction
function! s:execute_tmux(dict, command, temps) function! s:execute_tmux(dict, command, temps)
let command = s:env_var('FZF_DEFAULT_OPTS').s:env_var('FZF_DEFAULT_COMMAND').a:command let command = s:env_var('FZF_DEFAULT_OPTS').s:env_var('FZF_DEFAULT_COMMAND').a:command
if has_key(a:dict, 'dir') if !empty(get(a:dict, 'dir', ''))
let command = 'cd '.s:escape(a:dict.dir).' && '.command let command = 'cd '.s:escape(a:dict.dir).' && '.command
endif endif

View File

@@ -1,6 +1,7 @@
#!/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'
@@ -10,6 +11,49 @@ $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'
@@ -25,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
@@ -47,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 ',
@@ -65,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
@@ -86,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
@@ -168,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 []
@@ -187,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 []
@@ -563,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
@@ -613,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(2) 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
@@ -638,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',
@@ -688,13 +770,81 @@ class TestFZF < MiniTest::Unit::TestCase
tmp << 'hello ' << [0xff].pack('C*') << ' world' << $/ << [0xff].pack('C*') tmp << 'hello ' << [0xff].pack('C*') << ' world' << $/ << [0xff].pack('C*')
tmp.close tmp.close
begin begin
Timeout::timeout(1) do Timeout::timeout(0.5) do
FZF.new(%w[-n..,1,2.. -q^ -x], File.open(tmp.path)).start FZF.new(%w[-n..,1,2.. -q^ -x], File.open(tmp.path)).start
end end
rescue Timeout::Error rescue Timeout::Error
Curses.close_screen
end end
ensure ensure
tmp.unlink tmp.unlink
end 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