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

Compare commits

...

24 Commits
0.8.5 ... 0.8.6

Author SHA1 Message Date
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 347 additions and 188 deletions

View File

@@ -81,12 +81,14 @@ usage: fzf [options]
+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: '> ')
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
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
@@ -322,6 +324,11 @@ let g:fzf_launcher = 'xterm -e bash -ic %s'
let g:fzf_launcher = 'urxvt -geometry 120x30 -e sh -c %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])` ### `fzf#run([options])`
For more advanced uses, you can call `fzf#run()` function which returns the list 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 # /_/ /___/_/ Fuzzy finder for your shell
# #
# Version: 0.8.5 (Jun 15, 2014) # Version: 0.8.6 (Jun 30, 2014)
# #
# Author: Junegunn Choi # Author: Junegunn Choi
# URL: https://github.com/junegunn/fzf # URL: https://github.com/junegunn/fzf
@@ -50,27 +50,30 @@ end
class FZF class FZF
C = Curses C = Curses
attr_reader :rxflag, :sort, :nth, :color, :black, :ansi256, :reverse, 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
class AtomicVar def sync
def initialize value @shr_mtx.synchronize { yield }
@value = value end
@mutex = Mutex.new
end
def get def get name
@mutex.synchronize { @value } sync { instance_variable_get name }
end end
def set value = nil def geta(*names)
@mutex.synchronize do sync { names.map { |name| instance_variable_get name } }
@value = block_given? ? yield(@value) : value end
end
end
def method_missing sym, *args, &blk def call(name, method, *args)
@mutex.synchronize { @value.send(sym, *args, &blk) } 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
end end
@@ -89,6 +92,9 @@ class FZF
@nth = nil @nth = nil
@delim = nil @delim = nil
@reverse = false @reverse = false
@prompt = '> '
@shr_mtx = Mutex.new
@print_query = false
argv = argv =
if opts = ENV['FZF_DEFAULT_OPTS'] if opts = ENV['FZF_DEFAULT_OPTS']
@@ -124,9 +130,9 @@ class FZF
when '+0', '--no-exit-0' then @exit0 = false when '+0', '--no-exit-0' then @exit0 = false
when '-q', '--query' when '-q', '--query'
usage 1, 'query string required' unless query = argv.shift usage 1, 'query string required' unless query = argv.shift
@query = AtomicVar.new query.dup @query = query
when /^-q(.*)$/, /^--query=(.*)$/ when /^-q(.*)$/, /^--query=(.*)$/
@query = AtomicVar.new($1) @query = $1
when '-f', '--filter' when '-f', '--filter'
usage 1, 'query string required' unless query = argv.shift usage 1, 'query string required' unless query = argv.shift
@filter = query @filter = query
@@ -148,6 +154,13 @@ class FZF
@sort = sort.to_i @sort = sort.to_i
when /^-s([0-9]+)$/, /^--sort=([0-9]+)$/ when /^-s([0-9]+)$/, /^--sort=([0-9]+)$/
@sort = $1.to_i @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', '--extended-exact' then @extended = :exact
when '+e', '--no-extended-exact' then @extended = nil when '+e', '--no-extended-exact' then @extended = nil
else else
@@ -155,25 +168,29 @@ class FZF
end end
end end
@source = source.clone @source = source.clone
@mtx = Mutex.new @evt_mtx = Mutex.new
@cv = ConditionVariable.new @cv = ConditionVariable.new
@events = {} @events = {}
@new = [] @new = []
@queue = Queue.new @queue = Queue.new
@pending = nil @pending = nil
@rev_dir = @reverse ? -1 : 1
unless @filter unless @filter
@query ||= AtomicVar.new('') # Shared variables: needs protection
@cursor_x = AtomicVar.new(@query.length) @query ||= ''
@matches = AtomicVar.new([]) @matches = []
@count = AtomicVar.new(0) @count = 0
@vcursor = AtomicVar.new(0) @xcur = @query.length
@vcursors = AtomicVar.new(Set.new) @ycur = 0
@spinner = AtomicVar.new('-\|/-\|/'.split(//)) @yoff = 0
@selects = AtomicVar.new({}) # ordered >= 1.9 @dirty = Set.new
@main = Thread.current @spinner = '-\|/-\|/'.split(//)
@plcount = 0 @selects = {} # ordered >= 1.9
@main = Thread.current
@plcount = 0
end end
end end
@@ -206,15 +223,18 @@ class FZF
filter_list @new filter_list @new
else else
start_reader 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 if @select1 || @exit0
start_search do |loaded, matches| start_search do |loaded, matches|
len = empty ? @count.get : matches.length len = empty ? get(:@count) : matches.length
if loaded if loaded
if @select1 && len == 1 if @select1 && len == 1
puts @query if @print_query
puts empty ? matches.first : matches.first.first puts empty ? matches.first : matches.first.first
exit 0 exit 0
elsif @exit0 && len == 0 elsif @exit0 && len == 0
puts @query if @print_query
exit 0 exit 0
end end
end end
@@ -235,6 +255,7 @@ class FZF
end end
def filter_list list def filter_list list
puts @filter if @print_query
matches = matcher.match(list, @filter, '', '') matches = matcher.match(list, @filter, '', '')
if @sort && matches.length <= @sort if @sort && matches.length <= @sort
matches = FZF.sort(matches) matches = FZF.sort(matches)
@@ -306,12 +327,14 @@ class FZF
+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: '> ')
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
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
@@ -320,7 +343,7 @@ class FZF
end end
def emit event def emit event
@mtx.synchronize do @evt_mtx.synchronize do
@events[event] = yield @events[event] = yield
@cv.broadcast @cv.broadcast
end end
@@ -344,33 +367,37 @@ class FZF
def print_input def print_input
C.setpos cursor_y, 0 C.setpos cursor_y, 0
C.clrtoeol C.clrtoeol
cprint '> ', color(:prompt, true) cprint @prompt, color(:prompt, true)
C.attron(C::A_BOLD) do C.attron(C::A_BOLD) do
C.addstr @query.get C.addstr get(:@query)
end end
end end
def print_info msg = nil def print_info msg = nil
C.setpos cursor_y(1), 0 C.setpos cursor_y(1), 0
C.clrtoeol C.clrtoeol
prefix = prefix =
if spinner = @spinner.first if spin_char = call(:@spinner, :first)
cprint spinner, color(:spinner, true) cprint spin_char, color(:spinner, true)
' ' ' '
else else
' ' ' '
end end
C.attron color(:info, false) do C.attron color(:info, false) do
C.addstr "#{prefix}#{@matches.length}/#{@count.get}" sync do
if (selected = @selects.length) > 0 C.addstr "#{prefix}#{@matches.length}/#{@count}"
C.addstr " (#{selected})" if (selected = @selects.length) > 0
C.addstr " (#{selected})"
end
end end
C.addstr msg if msg C.addstr msg if msg
end end
end end
def refresh 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 C.refresh
end end
@@ -580,12 +607,12 @@ class FZF
begin begin
while true while true
@mtx.synchronize do @evt_mtx.synchronize do
while true while true
events.merge! @events events.merge! @events
if @events.empty? # No new events if @events.empty? # No new events
@cv.wait @mtx @cv.wait @evt_mtx
next next
end end
@events.clear @events.clear
@@ -594,8 +621,8 @@ class FZF
if events[:new] if events[:new]
lists << @new lists << @new
@count.set { |c| c + @new.length } set(:@count) { |c| c + @new.length }
@spinner.set { |spinner| set(:@spinner) { |spinner|
if e = spinner.shift if e = spinner.shift
spinner.push e spinner.push e
end; spinner end; spinner
@@ -619,10 +646,10 @@ class FZF
cnt = 0 cnt = 0
lists.each do |list| lists.each do |list|
cnt += list.length cnt += list.length
skip = @mtx.synchronize { @events[:key] } skip = @evt_mtx.synchronize { @events[:key] }
break if skip 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}%)" } render { print_info " (#{progress}%)" }
end end
@@ -641,7 +668,7 @@ class FZF
end end
# Atomic update # Atomic update
@matches.set matches set(:@matches, matches)
end#new_search end#new_search
callback = nil if callback && callback = nil if callback &&
@@ -660,14 +687,44 @@ class FZF
end end
def pick def pick
items = @matches[0, max_items] sync do
curr = [0, [@vcursor.get, items.length - 1].min].max [*@matches.fetch(@ycur, [])][0]
[*items.fetch(curr, [])][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 end
def update_list wipe def update_list wipe
render do 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 # Wipe
if items.length < @plcount if items.length < @plcount
@@ -678,20 +735,18 @@ class FZF
end end
@plcount = items.length @plcount = items.length
maxc = C.cols - 3 dirty = Set[pos]
vcursor = @vcursor.set { |v| [0, [v, items.length - 1].min].max } set(:@dirty) do |vs|
cleanse = Set[vcursor] dirty.merge vs
@vcursors.set { |vs|
cleanse.merge vs
Set.new Set.new
} end
items.each_with_index do |item, idx| items.each_with_index do |item, idx|
next unless wipe || cleanse.include?(idx) next unless wipe || dirty.include?(idx)
row = cursor_y(idx + 2) row = cursor_y(idx + 2)
chosen = idx == vcursor chosen = idx == pos
selected = @selects.include?([*item][0]) selected = @selects.include?([*item][0])
line, offsets = item line, offsets = item
tokens = format line, maxc, offsets tokens = format line, C.cols - 3, offsets
print_item row, tokens, chosen, selected print_item row, tokens, chosen, selected
end end
print_info print_info
@@ -720,7 +775,10 @@ class FZF
end end
def vselect &prc def vselect &prc
@vcursor.set { |v| @vcursors << v; prc.call v } sync do
@dirty << @ycur - @yoff
@ycur = prc.call @ycur
end
update_list false update_list false
end end
@@ -885,7 +943,7 @@ class FZF
def start_loop def start_loop
got = nil got = nil
begin begin
input = @query.get.dup input = call(:@query, :dup)
cursor = input.length cursor = input.length
yanked = '' yanked = ''
mouse_event = MouseEvent.new mouse_event = MouseEvent.new
@@ -912,8 +970,8 @@ class FZF
}, },
ctrl(:a) => proc { cursor = 0; nil }, ctrl(:a) => proc { cursor = 0; nil },
ctrl(:e) => proc { cursor = input.length; nil }, ctrl(:e) => proc { cursor = input.length; nil },
ctrl(:j) => proc { vselect { |v| v - (@reverse ? -1 : 1) } }, ctrl(:j) => proc { vselect { |v| v - @rev_dir } },
ctrl(:k) => proc { vselect { |v| v + (@reverse ? -1 : 1) } }, ctrl(:k) => proc { vselect { |v| v + @rev_dir } },
ctrl(:w) => proc { ctrl(:w) => proc {
pcursor = cursor pcursor = cursor
backword.call backword.call
@@ -924,26 +982,28 @@ class FZF
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|
if @multi && sel = pick if @multi && sel = pick
if @selects.has_key? sel sync do
@selects.delete sel if @selects.has_key? sel
else @selects.delete sel
@selects[sel] = 1 else
@selects[sel] = 1
end
end end
vselect { |v| v + case o vselect { |v| v + case o
when :stab then 1 when :stab then 1
when :sclick then 0 when :sclick then 0
else -1 else -1
end * (@reverse ? -1 : 1) } end * @rev_dir }
end end
}, },
ctrl(:b) => proc { cursor = [0, cursor - 1].max; nil }, ctrl(:b) => proc { cursor = [0, cursor - 1].max; nil },
ctrl(:f) => proc { cursor = [input.length, cursor + 1].min; nil }, ctrl(:f) => proc { cursor = [input.length, cursor + 1].min; nil },
ctrl(:l) => proc { render { C.clear; C.refresh }; update_list true }, ctrl(:l) => proc { render { C.clear; C.refresh }; update_list true },
:del => proc { input[cursor] = '' if input.length > cursor }, :del => proc { input[cursor] = '' if input.length > cursor },
:pgup => proc { vselect { |_| max_items } }, :pgup => proc { vselect { |v| v + @rev_dir * (max_items - 1) } },
:pgdn => proc { vselect { |_| 0 } }, :pgdn => proc { vselect { |v| v - @rev_dir * (max_items - 1) } },
:alt_b => proc { backword.call; nil }, :alt_b => proc { backword.call; nil },
:alt_f => proc { :alt_f => proc {
cursor += (input[cursor..-1].index(/(\S\s)|(.$)/) || -1) + 1 cursor += (input[cursor..-1].index(/(\S\s)|(.$)/) || -1) + 1
nil nil
}, },
@@ -958,10 +1018,10 @@ class FZF
when :click, :release when :click, :release
x, y, shift = val.values_at :x, :y, :shift x, y, shift = val.values_at :x, :y, :shift
y = @reverse ? (C.lines - 1 - y) : y y = @reverse ? (C.lines - 1 - y) : y
if y == cursor_y if y == C.lines - 1
cursor = [0, [input.length, x - 2].min].max cursor = [0, [input.length, x - @prompt.length].min].max
elsif x > 1 && y <= max_items elsif x > 1 && y <= max_items
tv = max_items - y - 1 tv = get(:@yoff) + max_items - y - 1
case event case event
when :click when :click
@@ -979,6 +1039,7 @@ class FZF
actions[ctrl(:i)].call(:sclick) if shift actions[ctrl(:i)].call(:sclick) if shift
actions[ctrl(diff > 0 ? :j : :k)].call actions[ctrl(diff > 0 ? :j : :k)].call
end end
nil
end end
} }
} }
@@ -989,23 +1050,25 @@ class FZF
actions[ctrl(:q)] = actions[ctrl(:g)] = actions[ctrl(:c)] = actions[:esc] actions[ctrl(:q)] = actions[ctrl(:g)] = actions[ctrl(:c)] = actions[:esc]
while true while true
@cursor_x.set cursor set(:@xcur, cursor)
render { print_input } render { print_input }
if key = get_input(actions) if key = get_input(actions)
upd = actions.fetch(key, actions[:default]).call(key) upd = actions.fetch(key, actions[:default]).call(key)
# Dispatch key event # Dispatch key event
emit(:key) { [@query.set(input.dup), cursor] } if upd emit(:key) { [set(:@query, input.dup), cursor] } if upd
end end
end end
ensure ensure
C.close_screen C.close_screen
q, selects = geta(:@query, :@selects)
@stdout.puts q if @print_query
if got if got
if @selects.empty? if selects.empty?
@stdout.puts got @stdout.puts got
else else
@selects.each do |sel, _| selects.each do |sel, _|
@stdout.puts sel @stdout.puts sel
end end
end end

View File

@@ -31,7 +31,8 @@ _fzf_opts_completion() {
} }
_fzf_generic_completion() { _fzf_generic_completion() {
local cur base dir leftover matches trigger local cur base dir leftover matches trigger cmd orig
cmd=$(echo ${COMP_WORDS[0]} | sed 's/[^a-z0-9_=]/_/g')
COMPREPLY=() COMPREPLY=()
trigger=${FZF_COMPLETION_TRIGGER:-**} trigger=${FZF_COMPLETION_TRIGGER:-**}
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
@@ -61,25 +62,30 @@ _fzf_generic_completion() {
dir=$(dirname "$dir") dir=$(dirname "$dir")
[[ "$dir" =~ /$ ]] || dir="$dir"/ [[ "$dir" =~ /$ ]] || dir="$dir"/
done done
else
shift
shift
orig=$(eval "echo \$_fzf_orig_completion_$cmd")
[ -n "$orig" ] && type "$orig" > /dev/null && $orig "$@"
fi fi
} }
_fzf_all_completion() { _fzf_all_completion() {
_fzf_generic_completion \ _fzf_generic_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_generic_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_generic_completion \
"-name .git -prune -o -name .svn -prune -o -type d -print" \ "-name .git -prune -o -name .svn -prune -o -type d -print" \
"" "" "$@"
} }
_fzf_kill_completion() { _fzf_kill_completion() {
@@ -133,28 +139,43 @@ _fzf_ssh_completion() {
fi fi
} }
# fzf options
complete -F _fzf_opts_completion fzf 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"
# Preserve existing completion
if [ "$_fzf_completion_loaded" != '0.8.6' ]; then
# Really wish I could use associative array but OSX comes with bash 3.2 :(
eval $(complete | grep '\-F' | grep -v _fzf_ |
grep -E -w "$(echo $d_cmds $f_cmds $a_cmds | sed 's/ /|/g' | sed 's/+/\\+/g')" |
sed -E 's/.*-F *([^ ]*).* ([^ ]*)$/export _fzf_orig_completion_\2=\1;/' |
sed 's/[^a-z0-9_= ;]/_/g')
export _fzf_completion_loaded=0.8.6
fi
# Directory # Directory
for cmd in "cd pushd rmdir"; do for cmd in $d_cmds; do
complete -F _fzf_dir_completion -o default -o bashdefault $cmd complete -F _fzf_dir_completion -o default -o bashdefault $cmd
done done
# File # File
for cmd in " for cmd in $f_cmds; do
awk cat diff diff3
emacs ex file ftp g++ gcc gvim head hg java
javac ld less more mvim patch perl python ruby
sed sftp sort source tail tee uniq vi view vim wc"; do
complete -F _fzf_file_completion -o default -o bashdefault $cmd complete -F _fzf_file_completion -o default -o bashdefault $cmd
done done
# Anything # Anything
for cmd in " for cmd in $a_cmds; do
basename bunzip2 bzip2 chmod chown curl cp dirname du
find git grep gunzip gzip hg jar
ln ls mv open rm rsync scp
svn tar unzip zip"; do
complete -F _fzf_all_completion -o default -o bashdefault $cmd complete -F _fzf_all_completion -o default -o bashdefault $cmd
done done
@@ -165,3 +186,4 @@ 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

65
install
View File

@@ -98,7 +98,7 @@ EOF
# Key bindings # Key bindings
# ------------ # ------------
__fsel() { __fsel() {
find * -path '*/\.*' -prune \ command find * -path '*/\.*' -prune \
-o -type f -print \ -o -type f -print \
-o -type d -print \ -o -type d -print \
-o -type l -print 2> /dev/null | fzf -m | while read item; do -o -type l -print 2> /dev/null | fzf -m | while read item; do
@@ -122,7 +122,7 @@ __fsel_tmux() {
__fcd() { __fcd() {
local dir 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 __use_tmux=0
@@ -155,12 +155,15 @@ else
else else
bind '"\C-t": "\e$a \eddi$(__fsel)\C-x\C-e\e0Px$a \C-x\C-r\exa "' bind '"\C-t": "\e$a \eddi$(__fsel)\C-x\C-e\e0Px$a \C-x\C-r\exa "'
fi fi
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 -n..,1,2.. | 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 # ALT-C - cd into the selected directory
bind '"\ec": "\eddi$(__fcd)\C-x\C-e\C-x\C-r\C-m"' bind '"\ec": "\eddi$(__fcd)\C-x\C-e\C-x\C-r\C-m"'
bind -m vi-command '"\ec": "i\ec"'
fi fi
unset __use_tmux unset __use_tmux
@@ -174,7 +177,7 @@ EOFZF
# CTRL-T - Paste the selected file path(s) into the command line # CTRL-T - Paste the selected file path(s) into the command line
__fsel() { __fsel() {
set -o nonomatch set -o nonomatch
find * -path '*/\.*' -prune \ command find * -path '*/\.*' -prune \
-o -type f -print \ -o -type f -print \
-o -type d -print \ -o -type d -print \
-o -type l -print 2> /dev/null | fzf -m | while read item; do -o -type l -print 2> /dev/null | fzf -m | while read item; do
@@ -207,7 +210,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; find * -path '*/\.*' -prune \ cd "${$(set -o nonomatch; command find * -path '*/\.*' -prune \
-o -type d -print 2> /dev/null | fzf):-.}" -o -type d -print 2> /dev/null | fzf):-.}"
zle reset-prompt zle reset-prompt
} }
@@ -247,46 +250,58 @@ EOFZF
echo -n "Generate ~/.config/fish/functions/fzf_key_bindings.fish ... " echo -n "Generate ~/.config/fish/functions/fzf_key_bindings.fish ... "
cat > ~/.config/fish/functions/fzf_key_bindings.fish << "EOFZF" cat > ~/.config/fish/functions/fzf_key_bindings.fish << "EOFZF"
function fzf_key_bindings function fzf_key_bindings
function __fzf_select # Due to a bug of fish, we cannot use command substitution,
find * -path '*/\.*' -prune \ # 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 f -print \
-o -type d -print \ -o -type d -print \
-o -type l -print 2> /dev/null | fzf -m | while read item -o -type l -print 2> /dev/null
echo -n (echo -n "$item" | sed 's/ /\\\\ /g')' ' 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 end
echo
end end
function __fzf_ctrl_t function __fzf_ctrl_t
if [ -n "$TMUX_PANE" -a "$FZF_TMUX" != "0" ] 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'" tmux split-window (__fzf_tmux_height) "fish -c 'fzf_key_bindings; __fzf_ctrl_t_tmux \\$TMUX_PANE'"
else else
__fzf_select > $TMPDIR/fzf.result __fzf_list | fzf -m > $TMPDIR/fzf.result
and commandline -i (cat $TMPDIR/fzf.result) and commandline -i (cat $TMPDIR/fzf.result | __fzf_escape)
commandline -f repaint
rm -f $TMPDIR/fzf.result rm -f $TMPDIR/fzf.result
end end
end end
function __fzf_ctrl_t_tmux function __fzf_ctrl_t_tmux
__fzf_select > $TMPDIR/fzf.result __fzf_list | fzf -m > $TMPDIR/fzf.result
and tmux send-keys -t $argv[1] (cat $TMPDIR/fzf.result) and tmux send-keys -t $argv[1] (cat $TMPDIR/fzf.result | __fzf_escape)
rm -f $TMPDIR/fzf.result rm -f $TMPDIR/fzf.result
end end
function __fzf_ctrl_r function __fzf_ctrl_r
if history | fzf +s +m > $TMPDIR/fzf.result history | fzf +s +m > $TMPDIR/fzf.result
commandline (cat $TMPDIR/fzf.result) and commandline (cat $TMPDIR/fzf.result)
else commandline -f repaint
commandline -f repaint
end
rm -f $TMPDIR/fzf.result rm -f $TMPDIR/fzf.result
end end
function __fzf_alt_c function __fzf_alt_c
find * -path '*/\.*' -prune -o -type d -print 2> /dev/null | fzf +m > $TMPDIR/fzf.result # Fish hangs if the command before pipe redirects (2> /dev/null)
if [ (cat $TMPDIR/fzf.result | wc -l) -gt 0 ] __fzf_list_dir | fzf +m > $TMPDIR/fzf.result
cd (cat $TMPDIR/fzf.result) [ (cat $TMPDIR/fzf.result | wc -l) -gt 0 ]
end and cd (cat $TMPDIR/fzf.result)
commandline -f repaint commandline -f repaint
rm -f $TMPDIR/fzf.result rm -f $TMPDIR/fzf.result
end end
@@ -336,6 +351,12 @@ done
if [ $key_bindings -eq 0 -a $has_fish -eq 1 ]; then if [ $key_bindings -eq 0 -a $has_fish -eq 1 ]; then
bind_file=~/.config/fish/functions/fish_user_key_bindings.fish bind_file=~/.config/fish/functions/fish_user_key_bindings.fish
append_line "fzf_key_bindings" "$bind_file" 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 fi
cat << EOF cat << EOF

View File

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

View File

@@ -27,12 +27,14 @@ class TestFZF < MiniTest::Unit::TestCase
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
assert_equal '', fzf.query.get assert_equal '', fzf.query
assert_equal false, fzf.select1 assert_equal false, fzf.select1
assert_equal false, fzf.exit0 assert_equal false, fzf.exit0
assert_equal nil, fzf.filter assert_equal nil, fzf.filter
assert_equal nil, fzf.extended assert_equal nil, fzf.extended
assert_equal false, fzf.reverse assert_equal false, fzf.reverse
assert_equal '> ', fzf.prompt
assert_equal false, fzf.print_query
end end
def test_environment_variables def test_environment_variables
@@ -43,12 +45,12 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal nil, fzf.nth assert_equal nil, fzf.nth
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' '--no-mouse -f "goodbye world" --black --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 ',
fzf.query.get fzf.query
assert_equal 'goodbye world', assert_equal 'goodbye world',
fzf.filter fzf.filter
assert_equal :fuzzy, fzf.extended assert_equal :fuzzy, fzf.extended
@@ -60,6 +62,7 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal true, fzf.select1 assert_equal true, fzf.select1
assert_equal true, fzf.exit0 assert_equal true, fzf.exit0
assert_equal true, fzf.reverse assert_equal true, fzf.reverse
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
end end
@@ -67,7 +70,8 @@ class TestFZF < MiniTest::Unit::TestCase
# 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] --no-mouse --no-256 --nth=1 --reverse --prompt (hi)
--print-query]
assert_equal 2000, fzf.sort assert_equal 2000, fzf.sort
assert_equal true, fzf.multi assert_equal true, fzf.multi
assert_equal false, fzf.color assert_equal false, fzf.color
@@ -75,20 +79,23 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal false, fzf.black assert_equal false, fzf.black
assert_equal false, fzf.mouse assert_equal false, fzf.mouse
assert_equal 0, fzf.rxflag 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.select1
assert_equal true, fzf.exit0 assert_equal true, fzf.exit0
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 true, fzf.reverse assert_equal true, fzf.reverse
assert_equal '(hi)', fzf.prompt
assert_equal true, fzf.print_query
# Long opts (left-to-right) # Long opts (left-to-right)
fzf = FZF.new %w[--sort=2000 --no-color --multi +i --query=hello fzf = FZF.new %w[--sort=2000 --no-color --multi +i --query=hello
--filter a --filter b --no-256 --black --nth -1 --nth -2 --filter a --filter b --no-256 --black --nth -1 --nth -2
--select-1 --exit-0 --no-select-1 --no-exit-0 --select-1 --exit-0 --no-select-1 --no-exit-0
--no-sort -i --color --no-multi --256 --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 nil, fzf.sort
assert_equal false, fzf.multi assert_equal false, fzf.multi
assert_equal true, fzf.color assert_equal true, fzf.color
@@ -97,12 +104,14 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal true, fzf.mouse assert_equal true, fzf.mouse
assert_equal 1, fzf.rxflag assert_equal 1, fzf.rxflag
assert_equal 'b', fzf.filter 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.select1
assert_equal false, fzf.exit0 assert_equal false, fzf.exit0
assert_equal nil, fzf.extended assert_equal nil, fzf.extended
assert_equal [-2..-2], fzf.nth assert_equal [-2..-2], fzf.nth
assert_equal false, fzf.reverse assert_equal false, fzf.reverse
assert_equal '(HI)', fzf.prompt
assert_equal false, fzf.print_query
# Short opts # Short opts
fzf = FZF.new %w[-s2000 +c -m +i -qhello -x -fhowdy +2 -n3 -1 -0] fzf = FZF.new %w[-s2000 +c -m +i -qhello -x -fhowdy +2 -n3 -1 -0]
@@ -111,7 +120,7 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal false, fzf.color assert_equal false, fzf.color
assert_equal false, fzf.ansi256 assert_equal false, fzf.ansi256
assert_equal 0, fzf.rxflag assert_equal 0, fzf.rxflag
assert_equal 'hello', fzf.query.get assert_equal 'hello', fzf.query
assert_equal 'howdy', fzf.filter assert_equal 'howdy', fzf.filter
assert_equal :fuzzy, fzf.extended assert_equal :fuzzy, fzf.extended
assert_equal [2..2], fzf.nth assert_equal [2..2], fzf.nth
@@ -129,7 +138,7 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal true, fzf.ansi256 assert_equal true, fzf.ansi256
assert_equal false, fzf.black assert_equal false, fzf.black
assert_equal 1, fzf.rxflag 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.select1
assert_equal false, fzf.exit0 assert_equal false, fzf.exit0
assert_equal 'world', fzf.filter assert_equal 'world', fzf.filter
@@ -565,36 +574,43 @@ class TestFZF < MiniTest::Unit::TestCase
end end
end end
def test_select_1 def assert_fzf_output opts, given, expected
stream = stream_for "Hello\nWorld" stream = stream_for given
output = StringIO.new output = StringIO.new
begin begin
$stdout = output $stdout = output
FZF.new(%w[--query=ol --select-1], stream).start FZF.new(opts, stream).start
rescue SystemExit => e rescue SystemExit => e
assert_equal 0, e.status assert_equal 0, e.status
assert_equal 'World', output.string.chomp assert_equal expected, output.string.chomp
ensure ensure
$stdout = STDOUT $stdout = STDOUT
end end
end end
def test_select_1_without_query def test_filter
stream = stream_for "Hello World" {
output = StringIO.new %w[--filter=ol] => 'World',
%w[--filter=ol --print-query] => "ol\nWorld",
begin }.each do |opts, expected|
$stdout = output assert_fzf_output opts, "Hello\nWorld", expected
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 end
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
assert_fzf_output %w[--select-1], 'Hello World', 'Hello World'
end
def test_select_1_ambiguity def test_select_1_ambiguity
stream = stream_for "Hello\nWorld" stream = stream_for "Hello\nWorld"
begin begin
@@ -609,33 +625,16 @@ class TestFZF < MiniTest::Unit::TestCase
end end
def test_exit_0 def test_exit_0
stream = stream_for "Hello\nWorld" {
output = StringIO.new %w[--query=zz --exit-0] => '',
%w[--query=zz --exit-0 --print-query] => 'zz',
begin }.each do |opts, expected|
$stdout = output assert_fzf_output opts, "Hello\nWorld", expected
FZF.new(%w[--query=zz --exit-0], stream).start
rescue SystemExit => e
assert_equal 0, e.status
assert_equal '', output.string
ensure
$stdout = STDOUT
end end
end end
def test_exit_0_without_query def test_exit_0_without_query
stream = stream_for "" assert_fzf_output %w[--exit-0], '', ''
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
end end
def test_ranking_overlap_match_regions def test_ranking_overlap_match_regions
@@ -648,5 +647,39 @@ class TestFZF < MiniTest::Unit::TestCase
['1 3 4 2', [[0, 24], [12, 17]]], ['1 3 4 2', [[0, 24], [12, 17]]],
], FZF.sort(FZF::ExtendedFuzzyMatcher.new(nil).match(list, '12 34', '', '')) ], FZF.sort(FZF::ExtendedFuzzyMatcher.new(nil).match(list, '12 34', '', ''))
end 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
end end