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

Compare commits

...

42 Commits
0.8.5 ... 0.8.7

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

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

This patch adds vi-command mode mappings that simply drop back into vi-insert mode ("i") and then trigger the primary mapping.
2014-06-23 17:14:16 +02:00
Junegunn Choi
9ce43d46f6 Guide on running fzf with MacVim and iTerm2 (#65) 2014-06-23 23:30:00 +09:00
Junegunn Choi
de09656197 Merge pull request #57 from sencer/master
Use `command find` rather than plain `find`
2014-06-19 00:37:36 +09:00
Sencer Selcuk
3827a1b09e Use command find rather than plain find
Aliases are expanded in shell scripts, and one may have an alias
for the `find` command that conflicts with fzf. So make sure fzf
is using real find command rather than the alias.
2014-06-18 11:33:40 -04:00
6 changed files with 508 additions and 250 deletions

View File

@@ -81,12 +81,14 @@ usage: fzf [options]
+2, --no-256 Disable 256-color
--black Use black background
--reverse Reverse orientation
--prompt=STR Input prompt (default: '> ')
Scripting
-q, --query=STR Start the finder with the given query
-1, --select-1 Automatically select the only match
-0, --exit-0 Exit immediately when there's no match
-f, --filter=STR Filter mode. Do not start interactive finder.
--print-query Print query as the first line
Environment variables
FZF_DEFAULT_COMMAND Default command to use when input is tty
@@ -268,6 +270,14 @@ ssh **<TAB>
telnet **<TAB>
```
#### Environment variables / Aliases
```sh
unset **<TAB>
export **<TAB>
unalias **<TAB>
```
#### Settings
```sh
@@ -322,6 +332,11 @@ let g:fzf_launcher = 'xterm -e bash -ic %s'
let g:fzf_launcher = 'urxvt -geometry 120x30 -e sh -c %s'
```
If you're running MacVim on OSX, I recommend you to use iTerm2 as the launcher.
Refer to the [this wiki
page](https://github.com/junegunn/fzf/wiki/fzf-with-MacVim-and-iTerm2) to see
how to set up.
### `fzf#run([options])`
For more advanced uses, you can call `fzf#run()` function which returns the list

241
fzf
View File

@@ -7,7 +7,7 @@
# / __/ / /_/ __/
# /_/ /___/_/ Fuzzy finder for your shell
#
# Version: 0.8.5 (Jun 15, 2014)
# Version: 0.8.7 (Aug 17, 2014)
#
# Author: Junegunn Choi
# URL: https://github.com/junegunn/fzf
@@ -36,8 +36,13 @@
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
require 'thread'
begin
require 'curses'
rescue LoadError
$stderr.puts 'curses gem is not installed. Try `gem install curses`.'
exit 1
end
require 'thread'
require 'set'
unless String.method_defined? :force_encoding
@@ -50,27 +55,30 @@ end
class FZF
C = Curses
attr_reader :rxflag, :sort, :nth, :color, :black, :ansi256, :reverse,
:mouse, :multi, :query, :select1, :exit0, :filter, :extended
attr_reader :rxflag, :sort, :nth, :color, :black, :ansi256, :reverse, :prompt,
:mouse, :multi, :query, :select1, :exit0, :filter, :extended,
:print_query
class AtomicVar
def initialize value
@value = value
@mutex = Mutex.new
def sync
@shr_mtx.synchronize { yield }
end
def get
@mutex.synchronize { @value }
def get name
sync { instance_variable_get name }
end
def set value = nil
@mutex.synchronize do
@value = block_given? ? yield(@value) : value
end
def geta(*names)
sync { names.map { |name| instance_variable_get name } }
end
def method_missing sym, *args, &blk
@mutex.synchronize { @value.send(sym, *args, &blk) }
def call(name, method, *args)
sync { instance_variable_get(name).send(method, *args) }
end
def set name, value = nil
sync do
instance_variable_set name,
(block_given? ? yield(instance_variable_get(name)) : value)
end
end
@@ -89,6 +97,9 @@ class FZF
@nth = nil
@delim = nil
@reverse = false
@prompt = '> '
@shr_mtx = Mutex.new
@print_query = false
argv =
if opts = ENV['FZF_DEFAULT_OPTS']
@@ -124,9 +135,9 @@ class FZF
when '+0', '--no-exit-0' then @exit0 = false
when '-q', '--query'
usage 1, 'query string required' unless query = argv.shift
@query = AtomicVar.new query.dup
@query = query
when /^-q(.*)$/, /^--query=(.*)$/
@query = AtomicVar.new($1)
@query = $1
when '-f', '--filter'
usage 1, 'query string required' unless query = argv.shift
@filter = query
@@ -148,6 +159,13 @@ class FZF
@sort = sort.to_i
when /^-s([0-9]+)$/, /^--sort=([0-9]+)$/
@sort = $1.to_i
when '--prompt'
usage 1, 'prompt string required' unless prompt = argv.shift
@prompt = prompt
when /^--prompt=(.*)$/
@prompt = $1
when '--print-query' then @print_query = true
when '--no-print-query' then @print_query = false
when '-e', '--extended-exact' then @extended = :exact
when '+e', '--no-extended-exact' then @extended = nil
else
@@ -156,22 +174,26 @@ class FZF
end
@source = source.clone
@mtx = Mutex.new
@evt_mtx = Mutex.new
@cv = ConditionVariable.new
@events = {}
@new = []
@queue = Queue.new
@pending = nil
@rev_dir = @reverse ? -1 : 1
unless @filter
@query ||= AtomicVar.new('')
@cursor_x = AtomicVar.new(@query.length)
@matches = AtomicVar.new([])
@count = AtomicVar.new(0)
@vcursor = AtomicVar.new(0)
@vcursors = AtomicVar.new(Set.new)
@spinner = AtomicVar.new('-\|/-\|/'.split(//))
@selects = AtomicVar.new({}) # ordered >= 1.9
# Shared variables: needs protection
@query ||= ''
@matches = []
@count = 0
@xcur = @query.length
@ycur = 0
@yoff = 0
@dirty = Set.new
@spinner = '-\|/-\|/'.split(//)
@selects = {} # ordered >= 1.9
@main = Thread.current
@plcount = 0
end
@@ -206,15 +228,18 @@ class FZF
filter_list @new
else
start_reader
emit(:key) { q = @query.get; [q, q.length] } unless empty = @query.empty?
query = get(:@query)
emit(:key) { [query, query.length] } unless empty = query.empty?
if @select1 || @exit0
start_search do |loaded, matches|
len = empty ? @count.get : matches.length
len = empty ? get(:@count) : matches.length
if loaded
if @select1 && len == 1
puts @query if @print_query
puts empty ? matches.first : matches.first.first
exit 0
elsif @exit0 && len == 0
puts @query if @print_query
exit 0
end
end
@@ -235,6 +260,7 @@ class FZF
end
def filter_list list
puts @filter if @print_query
matches = matcher.match(list, @filter, '', '')
if @sort && matches.length <= @sort
matches = FZF.sort(matches)
@@ -306,12 +332,14 @@ class FZF
+2, --no-256 Disable 256-color
--black Use black background
--reverse Reverse orientation
--prompt=STR Input prompt (default: '> ')
Scripting
-q, --query=STR Start the finder with the given query
-1, --select-1 Automatically select the only match
-0, --exit-0 Exit immediately when there's no match
-f, --filter=STR Filter mode. Do not start interactive finder.
--print-query Print query as the first line
Environment variables
FZF_DEFAULT_COMMAND Default command to use when input is tty
@@ -320,7 +348,7 @@ class FZF
end
def emit event
@mtx.synchronize do
@evt_mtx.synchronize do
@events[event] = yield
@cv.broadcast
end
@@ -338,39 +366,44 @@ class FZF
end if str
end
def addstr_safe str
C.addstr str.gsub("\0", '')
str = str.gsub("\0", '') rescue str
C.addstr str
end
def print_input
C.setpos cursor_y, 0
C.clrtoeol
cprint '> ', color(:prompt, true)
cprint @prompt, color(:prompt, true)
C.attron(C::A_BOLD) do
C.addstr @query.get
C.addstr get(:@query)
end
end
def print_info msg = nil
C.setpos cursor_y(1), 0
C.clrtoeol
prefix =
if spinner = @spinner.first
cprint spinner, color(:spinner, true)
if spin_char = call(:@spinner, :first)
cprint spin_char, color(:spinner, true)
' '
else
' '
end
C.attron color(:info, false) do
C.addstr "#{prefix}#{@matches.length}/#{@count.get}"
sync do
C.addstr "#{prefix}#{@matches.length}/#{@count}"
if (selected = @selects.length) > 0
C.addstr " (#{selected})"
end
end
C.addstr msg if msg
end
end
def refresh
C.setpos cursor_y, 2 + width(@query[0, @cursor_x.get])
query, xcur = geta(:@query, :@xcur)
C.setpos cursor_y, @prompt.length + width(query[0, xcur])
C.refresh
end
@@ -580,12 +613,12 @@ class FZF
begin
while true
@mtx.synchronize do
@evt_mtx.synchronize do
while true
events.merge! @events
if @events.empty? # No new events
@cv.wait @mtx
@cv.wait @evt_mtx
next
end
@events.clear
@@ -594,8 +627,8 @@ class FZF
if events[:new]
lists << @new
@count.set { |c| c + @new.length }
@spinner.set { |spinner|
set(:@count) { |c| c + @new.length }
set(:@spinner) { |spinner|
if e = spinner.shift
spinner.push e
end; spinner
@@ -619,10 +652,10 @@ class FZF
cnt = 0
lists.each do |list|
cnt += list.length
skip = @mtx.synchronize { @events[:key] }
skip = @evt_mtx.synchronize { @events[:key] }
break if skip
if !empty && (progress = 100 * cnt / @count.get) < 100 && Time.now - started_at > 0.5
if !empty && (progress = 100 * cnt / get(:@count)) < 100 && Time.now - started_at > 0.5
render { print_info " (#{progress}%)" }
end
@@ -641,7 +674,7 @@ class FZF
end
# Atomic update
@matches.set matches
set(:@matches, matches)
end#new_search
callback = nil if callback &&
@@ -660,14 +693,44 @@ class FZF
end
def pick
items = @matches[0, max_items]
curr = [0, [@vcursor.get, items.length - 1].min].max
[*items.fetch(curr, [])][0]
sync do
[*@matches.fetch(@ycur, [])][0]
end
end
def constrain offset, cursor, count, height
original = [offset, cursor]
diffpos = cursor - offset
# Constrain cursor
cursor = [0, [cursor, count - 1].min].max
# Ceil
if cursor > offset + (height - 1)
offset = cursor - (height - 1)
# Floor
elsif offset > cursor
offset = cursor
end
# Adjustment
if count - offset < height
offset = [0, count - height].max
cursor = [0, [offset + diffpos, count - 1].min].max
end
[[offset, cursor] != original, offset, cursor]
end
def update_list wipe
render do
items = @matches[0, max_items]
pos, items = sync {
changed, @yoff, @ycur =
constrain(@yoff, @ycur, @matches.length, max_items)
wipe ||= changed
[@ycur - @yoff, @matches[@yoff, max_items]]
}
# Wipe
if items.length < @plcount
@@ -678,20 +741,18 @@ class FZF
end
@plcount = items.length
maxc = C.cols - 3
vcursor = @vcursor.set { |v| [0, [v, items.length - 1].min].max }
cleanse = Set[vcursor]
@vcursors.set { |vs|
cleanse.merge vs
dirty = Set[pos]
set(:@dirty) do |vs|
dirty.merge vs
Set.new
}
end
items.each_with_index do |item, idx|
next unless wipe || cleanse.include?(idx)
next unless wipe || dirty.include?(idx)
row = cursor_y(idx + 2)
chosen = idx == vcursor
chosen = idx == pos
selected = @selects.include?([*item][0])
line, offsets = item
tokens = format line, maxc, offsets
tokens = format line, C.cols - 3, offsets
print_item row, tokens, chosen, selected
end
print_info
@@ -720,7 +781,10 @@ class FZF
end
def vselect &prc
@vcursor.set { |v| @vcursors << v; prc.call v }
sync do
@dirty << @ycur - @yoff
@ycur = prc.call @ycur
end
update_list false
end
@@ -824,6 +888,8 @@ class FZF
case read_nbs
when [59, 50, 68] then ctrl(:a)
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)
end
when 52 then read_nb; ctrl(:e)
@@ -885,7 +951,7 @@ class FZF
def start_loop
got = nil
begin
input = @query.get.dup
input = call(:@query, :dup)
cursor = input.length
yanked = ''
mouse_event = MouseEvent.new
@@ -912,8 +978,8 @@ class FZF
},
ctrl(:a) => proc { cursor = 0; nil },
ctrl(:e) => proc { cursor = input.length; nil },
ctrl(:j) => proc { vselect { |v| v - (@reverse ? -1 : 1) } },
ctrl(:k) => proc { vselect { |v| v + (@reverse ? -1 : 1) } },
ctrl(:j) => proc { vselect { |v| v - @rev_dir } },
ctrl(:k) => proc { vselect { |v| v + @rev_dir } },
ctrl(:w) => proc {
pcursor = cursor
backword.call
@@ -924,24 +990,26 @@ class FZF
ctrl(:h) => proc { input[cursor -= 1] = '' if cursor > 0 },
ctrl(:i) => proc { |o|
if @multi && sel = pick
sync do
if @selects.has_key? sel
@selects.delete sel
else
@selects[sel] = 1
end
end
vselect { |v| v + case o
when :stab then 1
when :sclick then 0
else -1
end * (@reverse ? -1 : 1) }
end * @rev_dir }
end
},
ctrl(:b) => proc { cursor = [0, cursor - 1].max; nil },
ctrl(:f) => proc { cursor = [input.length, cursor + 1].min; nil },
ctrl(:l) => proc { render { C.clear; C.refresh }; update_list true },
:del => proc { input[cursor] = '' if input.length > cursor },
:pgup => proc { vselect { |_| max_items } },
:pgdn => proc { vselect { |_| 0 } },
:pgup => 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_f => proc {
cursor += (input[cursor..-1].index(/(\S\s)|(.$)/) || -1) + 1
@@ -958,10 +1026,10 @@ class FZF
when :click, :release
x, y, shift = val.values_at :x, :y, :shift
y = @reverse ? (C.lines - 1 - y) : y
if y == cursor_y
cursor = [0, [input.length, x - 2].min].max
if y == C.lines - 1
cursor = [0, [input.length, x - @prompt.length].min].max
elsif x > 1 && y <= max_items
tv = max_items - y - 1
tv = get(:@yoff) + max_items - y - 1
case event
when :click
@@ -979,6 +1047,7 @@ class FZF
actions[ctrl(:i)].call(:sclick) if shift
actions[ctrl(diff > 0 ? :j : :k)].call
end
nil
end
}
}
@@ -989,23 +1058,25 @@ class FZF
actions[ctrl(:q)] = actions[ctrl(:g)] = actions[ctrl(:c)] = actions[:esc]
while true
@cursor_x.set cursor
set(:@xcur, cursor)
render { print_input }
if key = get_input(actions)
upd = actions.fetch(key, actions[:default]).call(key)
# Dispatch key event
emit(:key) { [@query.set(input.dup), cursor] } if upd
emit(:key) { [set(:@query, input.dup), cursor] } if upd
end
end
ensure
C.close_screen
q, selects = geta(:@query, :@selects)
@stdout.puts q if @print_query
if got
if @selects.empty?
if selects.empty?
@stdout.puts got
else
@selects.each do |sel, _|
selects.each do |sel, _|
@stdout.puts sel
end
end
@@ -1032,26 +1103,32 @@ class FZF
def tokenize str
@tokens_cache[str] ||=
begin
unless @delim
# AWK default
prefix_length = str[/^\s+/].length rescue 0
[prefix_length, (str.strip.scan(/\S+\s*/) rescue [])]
prefix_length = (str.index(/\S/) || 0) rescue 0
tokens = str.scan(/\S+\s*/) rescue []
else
prefix_length = 0
[prefix_length, (str.scan(@delim) rescue [])]
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
def do_match str, pat
if @nth
prefix_length, tokens = tokenize str
@nth.each do |n|
if (range = tokens[n]) && (token = range.join) &&
(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)
tokenize(str).each do |pair|
prefix_length, token = pair
if md = token.match(pat) rescue nil
return MatchData.new(md.offset(0).map { |o| o + prefix_length })
end
end
nil

View File

@@ -8,12 +8,35 @@
# - $FZF_COMPLETION_TRIGGER (default: '**')
# - $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() {
local cur prev opts
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts="-m --multi -x --extended -s --sort +s +i +c --no-color"
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
--sort|-s)
@@ -30,8 +53,9 @@ _fzf_opts_completion() {
return 0
}
_fzf_generic_completion() {
local cur base dir leftover matches trigger
_fzf_path_completion() {
local cur base dir leftover matches trigger cmd orig
cmd=$(echo ${COMP_WORDS[0]} | sed 's/[^a-z0-9_=]/_/g')
COMPREPLY=()
trigger=${FZF_COMPLETION_TRIGGER:-**}
cur="${COMP_WORDS[COMP_CWORD]}"
@@ -61,25 +85,63 @@ _fzf_generic_completion() {
dir=$(dirname "$dir")
[[ "$dir" =~ /$ ]] || dir="$dir"/
done
else
shift
shift
orig=$(eval "echo \$_fzf_orig_completion_$cmd")
[ -n "$orig" ] && type "$orig" > /dev/null 2>&1 && $orig "$@"
fi
}
_fzf_list_completion() {
local cur selected trigger cmd src ret
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
orig=$(eval "echo \$_fzf_orig_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
}
_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" \
"-m"
"-m" "$@"
}
_fzf_file_completion() {
_fzf_generic_completion \
_fzf_path_completion \
"-name .git -prune -o -name .svn -prune -o -type f -print -o -type l -print" \
"-m"
"-m" "$@"
}
_fzf_dir_completion() {
_fzf_generic_completion \
_fzf_path_completion \
"-name .git -prune -o -name .svn -prune -o -type d -print" \
""
"" "$@"
}
_fzf_kill_completion() {
@@ -97,64 +159,69 @@ _fzf_kill_completion() {
}
_fzf_telnet_completion() {
local cur selected trigger
trigger=${FZF_COMPLETION_TRIGGER:-**}
cur="${COMP_WORDS[COMP_CWORD]}"
[[ ${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_list_completion '+m' "$@" << "EOF"
grep -v '^\s*\(#\|$\)' /etc/hosts | awk '{if (length($2) > 0) {print $2}}' | sort -u
EOF
}
_fzf_ssh_completion() {
local cur selected trigger
trigger=${FZF_COMPLETION_TRIGGER:-**}
cur="${COMP_WORDS[COMP_CWORD]}"
[[ ${cur} == *"$trigger" ]] || return 1
cur=${cur:0:${#cur}-${#trigger}}
tput sc
selected=$(cat \
<(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | grep -i ^host) \
<(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_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
EOF
}
_fzf_env_var_completion() {
_fzf_list_completion '-m' "$@" << "EOF"
declare -xp | sed 's/=.*//' | sed 's/.* //'
EOF
}
_fzf_alias_completion() {
_fzf_list_completion '-m' "$@" << "EOF"
alias | sed 's/=.*//' | sed 's/.* //'
EOF
}
# fzf options
complete -F _fzf_opts_completion fzf
d_cmds="cd pushd rmdir"
f_cmds="
awk cat diff diff3
emacs ex file ftp g++ gcc gvim head hg java
javac ld less more mvim patch perl python ruby
sed sftp sort source tail tee uniq vi view vim wc"
a_cmds="
basename bunzip2 bzip2 chmod chown curl cp dirname du
find git grep gunzip gzip hg jar
ln ls mv open rm rsync scp
svn tar unzip zip"
x_cmds="kill ssh telnet unset unalias export"
# Preserve existing completion
if [ "$_fzf_completion_loaded" != '0.8.6-1' ]; then
# Really wish I could use associative array but OSX comes with bash 3.2 :(
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)
export _fzf_completion_loaded=0.8.6-1
fi
if type _completion_loader > /dev/null 2>&1; then
_fzf_completion_loader=1
fi
# Directory
for cmd in "cd pushd rmdir"; do
for cmd in $d_cmds; do
complete -F _fzf_dir_completion -o default -o bashdefault $cmd
done
# File
for cmd in "
awk cat diff diff3
emacs ex file ftp g++ gcc gvim head hg java
javac ld less more mvim patch perl python ruby
sed sftp sort source tail tee uniq vi view vim wc"; do
for cmd in $f_cmds; do
complete -F _fzf_file_completion -o default -o bashdefault $cmd
done
# Anything
for cmd in "
basename bunzip2 bzip2 chmod chown curl cp dirname du
find git grep gunzip gzip hg jar
ln ls mv open rm rsync scp
svn tar unzip zip"; do
for cmd in $a_cmds; do
complete -F _fzf_all_completion -o default -o bashdefault $cmd
done
@@ -165,3 +232,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_telnet_completion -o default -o bashdefault telnet
# 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

75
install
View File

@@ -28,7 +28,11 @@ if [ $? -eq 0 ]; then
else
echo "Not found"
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
echo
echo "Failed to install 'curses' gem."
@@ -98,7 +102,7 @@ EOF
# Key bindings
# ------------
__fsel() {
find * -path '*/\.*' -prune \
command find * -path '*/\.*' -prune \
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | fzf -m | while read item; do
@@ -122,7 +126,7 @@ __fsel_tmux() {
__fcd() {
local dir
dir=$(find ${1:-*} -path '*/\.*' -prune -o -type d -print 2> /dev/null | fzf +m) && printf 'cd %q' "$dir"
dir=$(command find ${1:-*} -path '*/\.*' -prune -o -type d -print 2> /dev/null | fzf +m) && printf 'cd %q' "$dir"
}
__use_tmux=0
@@ -140,7 +144,7 @@ if [ -z "$(set -o | grep '^vi.*on')" ]; then
fi
# 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
bind '"\ec": " \C-e\C-u$(__fcd)\e\C-e\er\C-m"'
@@ -155,12 +159,15 @@ else
else
bind '"\C-t": "\e$a \eddi$(__fsel)\C-x\C-e\e0Px$a \C-x\C-r\exa "'
fi
bind -m vi-command '"\C-t": "i\C-t"'
# 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"'
# ALT-C - cd into the selected directory
bind '"\ec": "\eddi$(__fcd)\C-x\C-e\C-x\C-r\C-m"'
bind -m vi-command '"\ec": "i\ec"'
fi
unset __use_tmux
@@ -174,7 +181,7 @@ EOFZF
# CTRL-T - Paste the selected file path(s) into the command line
__fsel() {
set -o nonomatch
find * -path '*/\.*' -prune \
command find * -path '*/\.*' -prune \
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | fzf -m | while read item; do
@@ -207,7 +214,7 @@ bindkey '^T' fzf-file-widget
# ALT-C - cd into the selected directory
fzf-cd-widget() {
cd "${$(set -o nonomatch; find * -path '*/\.*' -prune \
cd "${$(set -o nonomatch; command find * -path '*/\.*' -prune \
-o -type d -print 2> /dev/null | fzf):-.}"
zle reset-prompt
}
@@ -216,7 +223,7 @@ bindkey '\ec' fzf-cd-widget
# CTRL-R - Paste the selected command from history into the command line
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 -N fzf-history-widget
@@ -247,46 +254,58 @@ EOFZF
echo -n "Generate ~/.config/fish/functions/fzf_key_bindings.fish ... "
cat > ~/.config/fish/functions/fzf_key_bindings.fish << "EOFZF"
function fzf_key_bindings
function __fzf_select
find * -path '*/\.*' -prune \
# Due to a bug of fish, we cannot use command substitution,
# so we use temporary file instead
if [ -z "$TMPDIR" ]
set -g TMPDIR /tmp
end
function __fzf_list
command find * -path '*/\.*' -prune \
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | fzf -m | while read item
echo -n (echo -n "$item" | sed 's/ /\\\\ /g')' '
-o -type l -print 2> /dev/null
end
function __fzf_list_dir
command find * -path '*/\.*' -prune -o -type d -print 2> /dev/null
end
function __fzf_escape
while read item
echo -n (echo -n "$item" | sed -E 's/([ "$~'\''([{<>})])/\\\\\\1/g')' '
end
echo
end
function __fzf_ctrl_t
if [ -n "$TMUX_PANE" -a "$FZF_TMUX" != "0" ]
tmux split-window (__fzf_tmux_height) "fish -c 'fzf_key_bindings; __fzf_ctrl_t_tmux \\$TMUX_PANE'"
else
__fzf_select > $TMPDIR/fzf.result
and commandline -i (cat $TMPDIR/fzf.result)
__fzf_list | fzf -m > $TMPDIR/fzf.result
and commandline -i (cat $TMPDIR/fzf.result | __fzf_escape)
commandline -f repaint
rm -f $TMPDIR/fzf.result
end
end
function __fzf_ctrl_t_tmux
__fzf_select > $TMPDIR/fzf.result
and tmux send-keys -t $argv[1] (cat $TMPDIR/fzf.result)
__fzf_list | fzf -m > $TMPDIR/fzf.result
and tmux send-keys -t $argv[1] (cat $TMPDIR/fzf.result | __fzf_escape)
rm -f $TMPDIR/fzf.result
end
function __fzf_ctrl_r
if history | fzf +s +m > $TMPDIR/fzf.result
commandline (cat $TMPDIR/fzf.result)
else
history | fzf +s +m > $TMPDIR/fzf.result
and commandline (cat $TMPDIR/fzf.result)
commandline -f repaint
end
rm -f $TMPDIR/fzf.result
end
function __fzf_alt_c
find * -path '*/\.*' -prune -o -type d -print 2> /dev/null | fzf +m > $TMPDIR/fzf.result
if [ (cat $TMPDIR/fzf.result | wc -l) -gt 0 ]
cd (cat $TMPDIR/fzf.result)
end
# Fish hangs if the command before pipe redirects (2> /dev/null)
__fzf_list_dir | fzf +m > $TMPDIR/fzf.result
[ (cat $TMPDIR/fzf.result | wc -l) -gt 0 ]
and cd (cat $TMPDIR/fzf.result)
commandline -f repaint
rm -f $TMPDIR/fzf.result
end
@@ -336,6 +355,12 @@ done
if [ $key_bindings -eq 0 -a $has_fish -eq 1 ]; then
bind_file=~/.config/fish/functions/fish_user_key_bindings.fish
append_line "fzf_key_bindings" "$bind_file"
echo ' * Due to a known bug of fish, you may have issues running fzf on fish.'
echo ' * If that happens, try the following:'
echo ' - Remove ~/.config/fish/functions/fzf.fish'
echo ' - Place fzf executable in a directory included in $PATH'
echo
fi
cat << EOF

View File

@@ -25,22 +25,27 @@ let s:min_tmux_width = 10
let s:min_tmux_height = 3
let s:default_tmux_height = '40%'
let s:launcher = 'xterm -e bash -ic %s'
let s:fzf_rb = expand('<sfile>:h:h').'/fzf'
let s:cpo_save = &cpo
set cpo&vim
function! s:fzf_exec()
if !exists('s:exec')
call system('type fzf')
if v:shell_error
let s:fzf_rb = expand('<sfile>:h:h').'/fzf'
if executable(s:fzf_rb)
let s:exec = s:fzf_rb
else
echoerr 'fzf executable not found'
finish
endif
let s:exec = executable(s:fzf_rb) ? s:fzf_rb : ''
else
let s:exec = 'fzf'
endif
return s:fzf_exec()
elseif empty(s:exec)
unlet s:exec
throw 'fzf executable not found'
else
return s:exec
endif
endfunction
function! s:tmux_enabled()
if has('gui_running')
@@ -71,6 +76,11 @@ function! fzf#run(...) abort
let dict = exists('a:1') ? a:1 : {}
let temps = { 'result': tempname() }
let optstr = get(dict, 'options', '')
try
let fzf_exec = s:fzf_exec()
catch
throw v:exception
endtry
if has_key(dict, 'source')
let source = dict.source
@@ -87,7 +97,7 @@ function! fzf#run(...) abort
else
let prefix = ''
endif
let command = prefix.s:exec.' '.optstr.' > '.temps.result
let command = prefix.fzf_exec.' '.optstr.' > '.temps.result
if s:tmux_enabled() && s:tmux_splittable(dict)
return s:execute_tmux(dict, command, temps)
@@ -127,19 +137,29 @@ function! s:execute(dict, command, temps)
execute 'silent !'.command
redraw!
if v:shell_error
echohl Error
" Do not print error message on exit status 1
if v:shell_error > 1
echohl ErrorMsg
echo 'Error running ' . command
endif
return []
else
return s:callback(a:dict, a:temps, 0)
endif
endfunction
function! s:execute_tmux(dict, command, temps)
if has_key(a:dict, 'dir')
let command = 'cd '.s:escape(a:dict.dir).' && '.a:command
function! s:env_var(name)
if exists('$'.a:name)
return a:name . "='". substitute(expand('$'.a:name), "'", "'\\\\''", 'g') . "' "
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 has_key(a:dict, 'dir')
let command = 'cd '.s:escape(a:dict.dir).' && '.command
endif
let splitopt = '-v'

View File

@@ -5,6 +5,7 @@ require 'curses'
require 'timeout'
require 'stringio'
require 'minitest/autorun'
require 'tempfile'
$LOAD_PATH.unshift File.expand_path('../..', __FILE__)
ENV['FZF_EXECUTABLE'] = '0'
load 'fzf'
@@ -27,12 +28,14 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal true, fzf.color
assert_equal false, fzf.black
assert_equal true, fzf.ansi256
assert_equal '', fzf.query.get
assert_equal '', fzf.query
assert_equal false, fzf.select1
assert_equal false, fzf.exit0
assert_equal nil, fzf.filter
assert_equal nil, fzf.extended
assert_equal false, fzf.reverse
assert_equal '> ', fzf.prompt
assert_equal false, fzf.print_query
end
def test_environment_variables
@@ -43,12 +46,12 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal nil, fzf.nth
ENV['FZF_DEFAULT_OPTS'] =
'-x -m -s 10000 -q " hello world " +c +2 --select-1 -0 ' +
'--no-mouse -f "goodbye world" --black --nth=3,-1,2 --reverse'
'-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'
fzf = FZF.new []
assert_equal 10000, fzf.sort
assert_equal ' hello world ',
fzf.query.get
fzf.query
assert_equal 'goodbye world',
fzf.filter
assert_equal :fuzzy, fzf.extended
@@ -60,6 +63,7 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal true, fzf.select1
assert_equal true, fzf.exit0
assert_equal true, fzf.reverse
assert_equal true, fzf.print_query
assert_equal [2..2, -1..-1, 1..1], fzf.nth
end
@@ -67,7 +71,8 @@ class TestFZF < MiniTest::Unit::TestCase
# Long opts
fzf = FZF.new %w[--sort=2000 --no-color --multi +i --query hello --select-1
--exit-0 --filter=howdy --extended-exact
--no-mouse --no-256 --nth=1 --reverse]
--no-mouse --no-256 --nth=1 --reverse --prompt (hi)
--print-query]
assert_equal 2000, fzf.sort
assert_equal true, fzf.multi
assert_equal false, fzf.color
@@ -75,20 +80,23 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal false, fzf.black
assert_equal false, fzf.mouse
assert_equal 0, fzf.rxflag
assert_equal 'hello', fzf.query.get
assert_equal 'hello', fzf.query
assert_equal true, fzf.select1
assert_equal true, fzf.exit0
assert_equal 'howdy', fzf.filter
assert_equal :exact, fzf.extended
assert_equal [0..0], fzf.nth
assert_equal true, fzf.reverse
assert_equal '(hi)', fzf.prompt
assert_equal true, fzf.print_query
# Long opts (left-to-right)
fzf = FZF.new %w[--sort=2000 --no-color --multi +i --query=hello
--filter a --filter b --no-256 --black --nth -1 --nth -2
--select-1 --exit-0 --no-select-1 --no-exit-0
--no-sort -i --color --no-multi --256
--reverse --no-reverse]
--reverse --no-reverse --prompt (hi) --prompt=(HI)
--print-query --no-print-query]
assert_equal nil, fzf.sort
assert_equal false, fzf.multi
assert_equal true, fzf.color
@@ -97,12 +105,14 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal true, fzf.mouse
assert_equal 1, fzf.rxflag
assert_equal 'b', fzf.filter
assert_equal 'hello', fzf.query.get
assert_equal 'hello', fzf.query
assert_equal false, fzf.select1
assert_equal false, fzf.exit0
assert_equal nil, fzf.extended
assert_equal [-2..-2], fzf.nth
assert_equal false, fzf.reverse
assert_equal '(HI)', fzf.prompt
assert_equal false, fzf.print_query
# Short opts
fzf = FZF.new %w[-s2000 +c -m +i -qhello -x -fhowdy +2 -n3 -1 -0]
@@ -111,7 +121,7 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal false, fzf.color
assert_equal false, fzf.ansi256
assert_equal 0, fzf.rxflag
assert_equal 'hello', fzf.query.get
assert_equal 'hello', fzf.query
assert_equal 'howdy', fzf.filter
assert_equal :fuzzy, fzf.extended
assert_equal [2..2], fzf.nth
@@ -129,7 +139,7 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal true, fzf.ansi256
assert_equal false, fzf.black
assert_equal 1, fzf.rxflag
assert_equal 'world', fzf.query.get
assert_equal 'world', fzf.query
assert_equal false, fzf.select1
assert_equal false, fzf.exit0
assert_equal 'world', fzf.filter
@@ -565,40 +575,47 @@ class TestFZF < MiniTest::Unit::TestCase
end
end
def test_select_1
stream = stream_for "Hello\nWorld"
def assert_fzf_output opts, given, expected
stream = stream_for given
output = StringIO.new
begin
$stdout = output
FZF.new(%w[--query=ol --select-1], stream).start
FZF.new(opts, stream).start
rescue SystemExit => e
assert_equal 0, e.status
assert_equal 'World', output.string.chomp
assert_equal expected, output.string.chomp
ensure
$stdout = STDOUT
end
end
def test_filter
{
%w[--filter=ol] => 'World',
%w[--filter=ol --print-query] => "ol\nWorld",
}.each do |opts, expected|
assert_fzf_output opts, "Hello\nWorld", expected
end
end
def test_select_1
{
%w[--query=ol --select-1] => 'World',
%w[--query=ol --select-1 --print-query] => "ol\nWorld",
}.each do |opts, expected|
assert_fzf_output opts, "Hello\nWorld", expected
end
end
def test_select_1_without_query
stream = stream_for "Hello World"
output = StringIO.new
begin
$stdout = output
FZF.new(%w[--select-1], stream).start
rescue SystemExit => e
assert_equal 0, e.status
assert_equal 'Hello World', output.string.chomp
ensure
$stdout = STDOUT
end
assert_fzf_output %w[--select-1], 'Hello World', 'Hello World'
end
def test_select_1_ambiguity
stream = stream_for "Hello\nWorld"
begin
Timeout::timeout(3) do
Timeout::timeout(2) do
FZF.new(%w[--query=o --select-1], stream).start
end
flunk 'Should not reach here'
@@ -609,33 +626,16 @@ class TestFZF < MiniTest::Unit::TestCase
end
def test_exit_0
stream = stream_for "Hello\nWorld"
output = StringIO.new
begin
$stdout = output
FZF.new(%w[--query=zz --exit-0], stream).start
rescue SystemExit => e
assert_equal 0, e.status
assert_equal '', output.string
ensure
$stdout = STDOUT
{
%w[--query=zz --exit-0] => '',
%w[--query=zz --exit-0 --print-query] => 'zz',
}.each do |opts, expected|
assert_fzf_output opts, "Hello\nWorld", expected
end
end
def test_exit_0_without_query
stream = stream_for ""
output = StringIO.new
begin
$stdout = output
FZF.new(%w[--exit-0], stream).start
rescue SystemExit => e
assert_equal 0, e.status
assert_equal '', output.string
ensure
$stdout = STDOUT
end
assert_fzf_output %w[--exit-0], '', ''
end
def test_ranking_overlap_match_regions
@@ -648,5 +648,53 @@ class TestFZF < MiniTest::Unit::TestCase
['1 3 4 2', [[0, 24], [12, 17]]],
], FZF.sort(FZF::ExtendedFuzzyMatcher.new(nil).match(list, '12 34', '', ''))
end
def test_constrain
fzf = FZF.new []
# [#**** ]
assert_equal [false, 0, 0], fzf.constrain(0, 0, 5, 100)
# *****[**#** ... ] => [**#******* ... ]
assert_equal [true, 0, 2], fzf.constrain(5, 7, 10, 100)
# [**********]**#** => ***[*********#]**
assert_equal [true, 3, 12], fzf.constrain(0, 12, 15, 10)
# *****[**#** ] => ***[**#****]
assert_equal [true, 3, 5], fzf.constrain(5, 7, 10, 7)
# *****[**#** ] => ****[**#***]
assert_equal [true, 4, 6], fzf.constrain(5, 7, 10, 6)
# ***** [#] => ****[#]
assert_equal [true, 4, 4], fzf.constrain(10, 10, 5, 1)
# [ ] #**** => [#]****
assert_equal [true, 0, 0], fzf.constrain(-5, 0, 5, 1)
# [ ] **#** => **[#]**
assert_equal [true, 2, 2], fzf.constrain(-5, 2, 5, 1)
# [***** #] => [****# ]
assert_equal [true, 0, 4], fzf.constrain(0, 7, 5, 10)
# **[***** #] => [******# ]
assert_equal [true, 0, 6], fzf.constrain(2, 10, 7, 10)
end
def test_invalid_utf8
tmp = Tempfile.new('fzf')
tmp << 'hello ' << [0xff].pack('C*') << ' world' << $/ << [0xff].pack('C*')
tmp.close
begin
Timeout::timeout(1) do
FZF.new(%w[-n..,1,2.. -q^ -x], File.open(tmp.path)).start
end
rescue Timeout::Error
end
ensure
tmp.unlink
end
end