#!/usr/bin/env ruby
# encoding: utf-8
# frozen_string_literal: true
# rubocop:disable Metrics/LineLength
# rubocop:disable Metrics/MethodLength
require 'minitest/autorun'
require 'fileutils'
require 'English'
require 'shellwords'
require 'erb'
require 'tempfile'
TEMPLATE = DATA . read
UNSETS = %w[
FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS
FZF_CTRL_T_COMMAND FZF_CTRL_T_OPTS
FZF_ALT_C_COMMAND
FZF_ALT_C_OPTS FZF_CTRL_R_OPTS
fish_history
] . freeze
DEFAULT_TIMEOUT = 20
FILE = File . expand_path ( __FILE__ )
BASE = File . expand_path ( '..' , __dir__ )
Dir . chdir ( BASE )
FZF = " FZF_DEFAULT_OPTS= FZF_DEFAULT_COMMAND= #{ BASE } /bin/fzf "
class NilClass
def include? ( _str )
false
end
def start_with? ( _str )
false
end
def end_with? ( _str )
false
end
end
def wait
since = Time . now
while Time . now - since < DEFAULT_TIMEOUT
return if yield
sleep 0 . 05
end
raise 'timeout'
end
class Shell
class << self
def bash
@bash || =
begin
bashrc = '/tmp/fzf.bash'
File . open ( bashrc , 'w' ) do | f |
f . puts ( ERB . new ( TEMPLATE ) . result ( binding ) )
end
" bash --rcfile #{ bashrc } "
end
end
def zsh
@zsh || =
begin
zdotdir = '/tmp/fzf-zsh'
FileUtils . rm_rf ( zdotdir )
FileUtils . mkdir_p ( zdotdir )
File . open ( " #{ zdotdir } /.zshrc " , 'w' ) do | f |
f . puts ( ERB . new ( TEMPLATE ) . result ( binding ) )
end
" ZDOTDIR= #{ zdotdir } zsh "
end
end
def fish
UNSETS . map { | v | v + '= ' } . join + 'fish'
end
end
end
class Tmux
attr_reader :win
def initialize ( shell = :bash )
@win = go ( %W[ new-window -d -P -F # I #{ Shell . send ( shell ) } ] ) . first
go ( %W[ set-window-option -t #{ @win } pane-base-index 0 ] )
return unless shell == :fish
send_keys ( 'function fish_prompt; end; clear' , :Enter )
self . until ( & :empty? )
end
def kill
go ( %W[ kill-window -t #{ win } ] )
end
def send_keys ( * args )
target =
if args . last . is_a? ( Hash )
hash = args . pop
go ( %W[ select-window -t #{ win } ] )
" #{ win } . #{ hash [ :pane ] } "
else
win
end
go ( %W[ send-keys -t #{ target } ] + args . map ( & :to_s ) )
end
def paste ( str )
system ( 'tmux' , 'setb' , str , ';' , 'pasteb' , '-t' , win , ';' , 'send-keys' , '-t' , win , 'Enter' )
end
def capture ( pane = 0 )
go ( %W[ capture-pane -p -t #{ win } . #{ pane } ] ) . reverse . drop_while ( & :empty? ) . reverse
end
def until ( refresh = false , pane = 0 )
lines = nil
begin
wait do
lines = capture ( pane )
class << lines
def counts
lazy
. map { | l | l . scan %r{ ^. ([0-9]+) \ /([0-9]+)( \ (([0-9]+) \ ))? } }
. reject ( & :empty? )
. first & . first & . map ( & :to_i ) & . values_at ( 0 , 1 , 3 ) || [ 0 , 0 , 0 ]
end
def match_count
counts [ 0 ]
end
def item_count
counts [ 1 ]
end
def select_count
counts [ 2 ]
end
def any_include? ( val )
method = val . is_a? ( Regexp ) ? :match : :include?
select { | line | line . send method , val } . first
end
end
yield ( lines ) . tap do | ok |
send_keys 'C-l' if refresh && ! ok
end
end
rescue StandardError
puts $ERROR_INFO . backtrace
puts '>' * 80
puts lines
puts '<' * 80
raise
end
lines
end
def prepare
tries = 0
begin
self . until do | lines |
send_keys ' ' , 'C-u' , 'hello' , :Left , :Right
lines [ - 1 ] . end_with? ( 'hello' )
end
rescue StandardError
( tries += 1 ) < 5 ? retry : raise
end
send_keys 'C-u'
end
private
def go ( args )
IO . popen ( [ 'tmux' ] + args ) { | io | io . readlines ( chomp : true ) }
end
end
class TestBase < Minitest :: Test
TEMPNAME = '/tmp/output'
attr_reader :tmux
def tempname
@temp_suffix || = 0
[ TEMPNAME ,
caller_locations . map ( & :label ) . find { | l | l =~ / ^test_ / } ,
@temp_suffix ] . join '-'
end
def writelines ( path , lines )
File . unlink path while File . exist? path
File . open ( path , 'w' ) { | f | f << lines . join ( $INPUT_RECORD_SEPARATOR ) + $INPUT_RECORD_SEPARATOR }
end
def readonce
wait { File . exist? ( tempname ) }
File . read ( tempname )
ensure
File . unlink tempname while File . exist? ( tempname )
@temp_suffix += 1
tmux . prepare
end
def fzf ( * opts )
fzf! ( * opts ) + " > #{ tempname } .tmp; mv #{ tempname } .tmp #{ tempname } "
end
def fzf! ( * opts )
opts = opts . map do | o |
case o
when Symbol
o = o . to_s
o . length > 1 ? " -- #{ o . tr ( '_' , '-' ) } " : " - #{ o } "
when String , Numeric
o . to_s
end
end . compact
" #{ FZF } #{ opts . join ' ' } "
end
end
class TestGoFZF < TestBase
def setup
super
@tmux = Tmux . new
end
def teardown
@tmux . kill
end
def test_vanilla
tmux . send_keys " seq 1 100000 | #{ fzf } " , :Enter
tmux . until { | lines | lines . last =~ / ^> / && lines [ - 2 ] =~ / ^ 100000 / }
lines = tmux . capture
assert_equal ' 2' , lines [ - 4 ]
assert_equal '> 1' , lines [ - 3 ]
assert_equal ' 100000/100000' , lines [ - 2 ]
assert_equal '>' , lines [ - 1 ]
# Testing basic key bindings
tmux . send_keys '99' , 'C-a' , '1' , 'C-f' , '3' , 'C-b' , 'C-h' , 'C-u' , 'C-e' , 'C-y' , 'C-k' , 'Tab' , 'BTab'
tmux . until do | lines |
'> 3910' == lines [ - 4 ] &&
' 391' == lines [ - 3 ] &&
' 856/100000' == lines [ - 2 ] &&
'> 391' == lines [ - 1 ]
end
tmux . send_keys :Enter
assert_equal '3910' , readonce . chomp
end
def test_fzf_default_command
tmux . send_keys fzf . sub ( 'FZF_DEFAULT_COMMAND=' , " FZF_DEFAULT_COMMAND='echo hello' " ) , :Enter
tmux . until { | lines | lines . last =~ / ^> / }
tmux . send_keys :Enter
assert_equal 'hello' , readonce . chomp
end
def test_fzf_default_command_failure
tmux . send_keys fzf . sub ( 'FZF_DEFAULT_COMMAND=' , 'FZF_DEFAULT_COMMAND=false' ) , :Enter
tmux . until { | lines | lines [ - 2 ] . include? ( 'Command failed: false' ) }
tmux . send_keys :Enter
end
def test_key_bindings
tmux . send_keys " #{ FZF } -q 'foo bar foo-bar' " , :Enter
tmux . until { | lines | lines . last =~ / ^> / }
# CTRL-A
tmux . send_keys 'C-A' , '('
tmux . until { | lines | lines . last == '> (foo bar foo-bar' }
# META-F
tmux . send_keys :Escape , :f , ')'
tmux . until { | lines | lines . last == '> (foo) bar foo-bar' }
# CTRL-B
tmux . send_keys 'C-B' , 'var'
tmux . until { | lines | lines . last == '> (foovar) bar foo-bar' }
# Left, CTRL-D
tmux . send_keys :Left , :Left , 'C-D'
tmux . until { | lines | lines . last == '> (foovr) bar foo-bar' }
# META-BS
tmux . send_keys :Escape , :BSpace
tmux . until { | lines | lines . last == '> (r) bar foo-bar' }
# CTRL-Y
tmux . send_keys 'C-Y' , 'C-Y'
tmux . until { | lines | lines . last == '> (foovfoovr) bar foo-bar' }
# META-B
tmux . send_keys :Escape , :b , :Space , :Space
tmux . until { | lines | lines . last == '> ( foovfoovr) bar foo-bar' }
# CTRL-F / Right
tmux . send_keys 'C-F' , :Right , '/'
tmux . until { | lines | lines . last == '> ( fo/ovfoovr) bar foo-bar' }
# CTRL-H / BS
tmux . send_keys 'C-H' , :BSpace
tmux . until { | lines | lines . last == '> ( fovfoovr) bar foo-bar' }
# CTRL-E
tmux . send_keys 'C-E' , 'baz'
tmux . until { | lines | lines . last == '> ( fovfoovr) bar foo-barbaz' }
# CTRL-U
tmux . send_keys 'C-U'
tmux . until { | lines | lines . last == '>' }
# CTRL-Y
tmux . send_keys 'C-Y'
tmux . until { | lines | lines . last == '> ( fovfoovr) bar foo-barbaz' }
# CTRL-W
tmux . send_keys 'C-W' , 'bar-foo'
tmux . until { | lines | lines . last == '> ( fovfoovr) bar bar-foo' }
# META-D
tmux . send_keys :Escape , :b , :Escape , :b , :Escape , :d , 'C-A' , 'C-Y'
tmux . until { | lines | lines . last == '> bar( fovfoovr) bar -foo' }
# CTRL-M
tmux . send_keys 'C-M'
tmux . until { | lines | lines . last !~ / ^> / }
end
def test_file_word
tmux . send_keys " #{ FZF } -q '--/foo bar/foo-bar/baz' --filepath-word " , :Enter
tmux . until { | lines | lines . last =~ / ^> / }
tmux . send_keys :Escape , :b
tmux . send_keys :Escape , :b
tmux . send_keys :Escape , :b
tmux . send_keys :Escape , :d
tmux . send_keys :Escape , :f
tmux . send_keys :Escape , :BSpace
tmux . until { | lines | lines . last == '> --///baz' }
end
def test_multi_order
tmux . send_keys " seq 1 10 | #{ fzf :multi } " , :Enter
tmux . until { | lines | lines . last =~ / ^> / }
tmux . send_keys :Tab , :Up , :Up , :Tab , :Tab , :Tab , # 3, 2
'C-K' , 'C-K' , 'C-K' , 'C-K' , :BTab , :BTab , # 5, 6
:PgUp , 'C-J' , :Down , :Tab , :Tab # 8, 7
tmux . until { | lines | lines [ - 2 ] . include? '(6)' }
tmux . send_keys 'C-M'
assert_equal %w[ 3 2 5 6 8 7 ] , readonce . split ( $INPUT_RECORD_SEPARATOR )
end
def test_multi_max
tmux . send_keys " seq 1 10 | #{ FZF } -m 3 --bind A:select-all,T:toggle-all --preview 'echo [{+}]/{}' " , :Enter
tmux . until { | lines | lines . item_count == 10 }
tmux . send_keys '1'
tmux . until do | lines |
lines [ 1 ] . include? ( '[1]/1' ) && lines [ - 2 ] . include? ( '2/10' )
end
tmux . send_keys 'A'
tmux . until do | lines |
lines [ 1 ] . include? ( '[1 10]/1' ) && lines [ - 2 ] . include? ( '2/10 (2/3)' )
end
tmux . send_keys :BSpace
tmux . until { | lines | lines [ - 2 ] . include? ( '10/10 (2/3)' ) }
tmux . send_keys 'T'
tmux . until do | lines |
lines [ 1 ] . include? ( '[2 3 4]/1' ) && lines [ - 2 ] . include? ( '10/10 (3/3)' )
end
%w[ T A ] . each do | key |
tmux . send_keys key
tmux . until do | lines |
lines [ 1 ] . include? ( '[1 5 6]/1' ) && lines [ - 2 ] . include? ( '10/10 (3/3)' )
end
end
tmux . send_keys :BTab
tmux . until do | lines |
lines [ 1 ] . include? ( '[5 6]/2' ) && lines [ - 2 ] . include? ( '10/10 (2/3)' )
end
[ :BTab , :BTab , 'A' ] . each do | key |
tmux . send_keys key
tmux . until do | lines |
lines [ 1 ] . include? ( '[5 6 2]/3' ) && lines [ - 2 ] . include? ( '10/10 (3/3)' )
end
end
tmux . send_keys '2'
tmux . until { | lines | lines [ - 2 ] . include? ( '1/10 (3/3)' ) }
tmux . send_keys 'T'
tmux . until do | lines |
lines [ 1 ] . include? ( '[5 6]/2' ) && lines [ - 2 ] . include? ( '1/10 (2/3)' )
end
tmux . send_keys :BSpace
tmux . until { | lines | lines [ - 2 ] . include? ( '10/10 (2/3)' ) }
tmux . send_keys 'A'
tmux . until do | lines |
lines [ 1 ] . include? ( '[5 6 1]/1' ) && lines [ - 2 ] . include? ( '10/10 (3/3)' )
end
end
def test_with_nth
[ true , false ] . each do | multi |
tmux . send_keys " (echo ' 1st 2nd 3rd/';
echo ' first second third/' ) |
#{fzf multi && :multi, :x, :nth, 2, :with_nth, '2,-1,1'}",
:Enter
tmux . until { | lines | lines [ - 2 ] . include? ( '2/2' ) }
# Transformed list
lines = tmux . capture
assert_equal ' second third/first' , lines [ - 4 ]
assert_equal '> 2nd 3rd/1st' , lines [ - 3 ]
# However, the output must not be transformed
if multi
tmux . send_keys :BTab , :BTab
tmux . until { | lines | lines [ - 2 ] . include? ( '(2)' ) }
tmux . send_keys :Enter
assert_equal [ ' 1st 2nd 3rd/' , ' first second third/' ] , readonce . split ( $INPUT_RECORD_SEPARATOR )
else
tmux . send_keys '^' , '3'
tmux . until { | lines | lines [ - 2 ] . include? ( '1/2' ) }
tmux . send_keys :Enter
assert_equal [ ' 1st 2nd 3rd/' ] , readonce . split ( $INPUT_RECORD_SEPARATOR )
end
end
end
def test_scroll
[ true , false ] . each do | rev |
tmux . send_keys " seq 1 100 | #{ fzf rev && :reverse } " , :Enter
tmux . until { | lines | lines . include? ' 100/100' }
tmux . send_keys ( * Array . new ( 110 ) { rev ? :Down : :Up } )
tmux . until { | lines | lines . include? '> 100' }
tmux . send_keys :Enter
assert_equal '100' , readonce . chomp
end
end
def test_select_1
tmux . send_keys " seq 1 100 | #{ fzf :with_nth , '..,..' , :print_query , :q , 5555 , :'1' } " , :Enter
assert_equal %w[ 5555 55 ] , readonce . split ( $INPUT_RECORD_SEPARATOR )
end
def test_exit_0
tmux . send_keys " seq 1 100 | #{ fzf :with_nth , '..,..' , :print_query , :q , 555_555 , :'0' } " , :Enter
assert_equal [ '555555' ] , readonce . split ( $INPUT_RECORD_SEPARATOR )
end
def test_select_1_exit_0_fail
[ :'0' , :'1' , % i [ 1 0 ] ] . each do | opt |
tmux . send_keys " seq 1 100 | #{ fzf :print_query , :multi , :q , 5 , * opt } " , :Enter
tmux . until { | lines | lines . last =~ / ^> 5 / }
tmux . send_keys :BTab , :BTab , :BTab
tmux . until { | lines | lines [ - 2 ] . include? ( '(3)' ) }
tmux . send_keys :Enter
assert_equal %w[ 5 5 50 51 ] , readonce . split ( $INPUT_RECORD_SEPARATOR )
end
end
def test_query_unicode
tmux . paste " (echo abc; echo $' \\ 352 \\ 260 \\ 200 \\ 353 \\ 202 \\ 230 \\ 353 \\ 213 \\ 244') | #{ fzf :query , " $' \\ 352 \\ 260 \\ 200 \\ 353 \\ 213 \\ 244' " } "
tmux . until { | lines | lines [ - 2 ] . include? '1/2' }
tmux . send_keys :Enter
assert_equal [ '가나다' ] , readonce . split ( $INPUT_RECORD_SEPARATOR )
end
def test_sync
tmux . send_keys " seq 1 100 | #{ fzf! :multi } | awk '{print $1 $1}' | #{ fzf :sync } " , :Enter
tmux . until { | lines | lines [ - 1 ] == '>' }
tmux . send_keys 9
tmux . until { | lines | lines [ - 2 ] == ' 19/100' }
tmux . send_keys :BTab , :BTab , :BTab
tmux . until { | lines | lines [ - 2 ] . include? ( '(3)' ) }
tmux . send_keys :Enter
tmux . until { | lines | lines [ - 1 ] == '>' }
tmux . send_keys 'C-K' , :Enter
assert_equal [ '9090' ] , readonce . split ( $INPUT_RECORD_SEPARATOR )
end
def test_tac
tmux . send_keys " seq 1 1000 | #{ fzf :tac , :multi } " , :Enter
tmux . until { | lines | lines [ - 2 ] . include? '1000/1000' }
tmux . send_keys :BTab , :BTab , :BTab
tmux . until { | lines | lines [ - 2 ] . include? ( '(3)' ) }
tmux . send_keys :Enter
assert_equal %w[ 1000 999 998 ] , readonce . split ( $INPUT_RECORD_SEPARATOR )
end
def test_tac_sort
tmux . send_keys " seq 1 1000 | #{ fzf :tac , :multi } " , :Enter
tmux . until { | lines | lines [ - 2 ] . include? '1000/1000' }
tmux . send_keys '99'
tmux . until { | lines | lines [ - 2 ] . include? '28/1000' }
tmux . send_keys :BTab , :BTab , :BTab
tmux . until { | lines | lines [ - 2 ] . include? ( '(3)' ) }
tmux . send_keys :Enter
assert_equal %w[ 99 999 998 ] , readonce . split ( $INPUT_RECORD_SEPARATOR )
end
def test_tac_nosort
tmux . send_keys " seq 1 1000 | #{ fzf :tac , :no_sort , :multi } " , :Enter
tmux . until { | lines | lines [ - 2 ] . include? '1000/1000' }
tmux . send_keys '00'
tmux . until { | lines | lines [ - 2 ] . include? '10/1000' }
tmux . send_keys :BTab , :BTab , :BTab
tmux . until { | lines | lines [ - 2 ] . include? ( '(3)' ) }
tmux . send_keys :Enter
assert_equal %w[ 1000 900 800 ] , readonce . split ( $INPUT_RECORD_SEPARATOR )
end
def test_expect
test = lambda do | key , feed , expected = key |
tmux . send_keys " seq 1 100 | #{ fzf :expect , key } ; sync " , :Enter
tmux . until { | lines | lines [ - 2 ] . include? '100/100' }
tmux . send_keys '55'
tmux . until { | lines | lines [ - 2 ] . include? '1/100' }
tmux . send_keys ( * feed )
tmux . prepare
assert_equal [ expected , '55' ] , readonce . split ( $INPUT_RECORD_SEPARATOR )
end
test . call 'ctrl-t' , 'C-T'
test . call 'ctrl-t' , 'Enter' , ''
test . call 'alt-c' , % i [ Escape c ]
test . call 'f1' , 'f1'
test . call 'f2' , 'f2'
test . call 'f3' , 'f3'
test . call 'f2,f4' , 'f2' , 'f2'
test . call 'f2,f4' , 'f4' , 'f4'
test . call 'alt-/' , % i [ Escape / ]
%w[ f5 f6 f7 f8 f9 f10 ] . each do | key |
test . call 'f5,f6,f7,f8,f9,f10' , key , key
end
test . call '@' , '@'
end
def test_expect_print_query
tmux . send_keys " seq 1 100 | #{ fzf '--expect=alt-z' , :print_query } " , :Enter
tmux . until { | lines | lines [ - 2 ] . include? '100/100' }
tmux . send_keys '55'
tmux . until { | lines | lines [ - 2 ] . include? '1/100' }
tmux . send_keys :Escape , :z
assert_equal [ '55' , 'alt-z' , '55' ] , readonce . split ( $INPUT_RECORD_SEPARATOR )
end
def test_expect_printable_character_print_query
tmux . send_keys " seq 1 100 | #{ fzf '--expect=z --print-query' } " , :Enter
tmux . until { | lines | lines [ - 2 ] . include? '100/100' }
tmux . send_keys '55'
tmux . until { | lines | lines [ - 2 ] . include? '1/100' }
tmux . send_keys 'z'
assert_equal %w[ 55 z 55 ] , readonce . split ( $INPUT_RECORD_SEPARATOR )
end
def test_expect_print_query_select_1
tmux . send_keys " seq 1 100 | #{ fzf '-q55 -1 --expect=alt-z --print-query' } " , :Enter
assert_equal [ '55' , '' , '55' ] , readonce . split ( $INPUT_RECORD_SEPARATOR )
end
def test_toggle_sort
[ '--toggle-sort=ctrl-r' , '--bind=ctrl-r:toggle-sort' ] . each do | opt |
tmux . send_keys " seq 1 111 | #{ fzf " -m +s --tac #{ opt } -q11 " } " , :Enter
tmux . until { | lines | lines [ - 3 ] . include? '> 111' }
tmux . send_keys :Tab
tmux . until { | lines | lines [ - 2 ] . include? '4/111 -S (1)' }
tmux . send_keys 'C-R'
tmux . until { | lines | lines [ - 3 ] . include? '> 11' }
tmux . send_keys :Tab
tmux . until { | lines | lines [ - 2 ] . include? '4/111 +S (2)' }
tmux . send_keys :Enter
assert_equal %w[ 111 11 ] , readonce . split ( $INPUT_RECORD_SEPARATOR )
end
end
def test_unicode_case
writelines tempname , %w[ с тр о К А 1 С Т Р О К А 2 с тр о ка 3 С тр о ка 4]
assert_equal %w[ С Т Р О К А 2 С тр о ка 4] , ` #{ FZF } -fС < #{ tempname } ` . split ( $INPUT_RECORD_SEPARATOR )
assert_equal %w[ с тр о К А 1 С Т Р О К А 2 с тр о ка 3 С тр о ка 4] , ` #{ FZF } -fс < #{ tempname } ` . split ( $INPUT_RECORD_SEPARATOR )
end
def test_tiebreak
input = %w[
- - foobar - - - - - - - -
- - - - - foobar - - -
- - - - foobar - -
- - - - - - - foobar -
]
writelines tempname , input
assert_equal input , ` #{ FZF } -ffoobar --tiebreak=index < #{ tempname } ` . split ( $INPUT_RECORD_SEPARATOR )
by_length = %w[
- - - - foobar - -
- - - - - foobar - - -
- - - - - - - foobar -
- - foobar - - - - - - - -
]
assert_equal by_length , ` #{ FZF } -ffoobar < #{ tempname } ` . split ( $INPUT_RECORD_SEPARATOR )
assert_equal by_length , ` #{ FZF } -ffoobar --tiebreak=length < #{ tempname } ` . split ( $INPUT_RECORD_SEPARATOR )
by_begin = %w[
- - foobar - - - - - - - -
- - - - foobar - -
- - - - - foobar - - -
- - - - - - - foobar -
]
assert_equal by_begin , ` #{ FZF } -ffoobar --tiebreak=begin < #{ tempname } ` . split ( $INPUT_RECORD_SEPARATOR )
assert_equal by_begin , ` #{ FZF } -f"!z foobar" -x --tiebreak begin < #{ tempname } ` . split ( $INPUT_RECORD_SEPARATOR )
assert_equal %w[
- - - - - - - foobar -
- - - - foobar - -
- - - - - foobar - - -
- - foobar - - - - - - - -
] , ` #{ FZF } -ffoobar --tiebreak end < #{ tempname } ` . split ( $INPUT_RECORD_SEPARATOR )
assert_equal input , ` #{ FZF } -f"!z" -x --tiebreak end < #{ tempname } ` . split ( $INPUT_RECORD_SEPARATOR )
end
def test_tiebreak_index_begin
writelines tempname , [
'xoxxxxxoxx' ,
'xoxxxxxox' ,
'xxoxxxoxx' ,
'xxxoxoxxx' ,
'xxxxoxox' ,
' xxoxoxxx'
]
assert_equal [
'xxxxoxox' ,
' xxoxoxxx' ,
'xxxoxoxxx' ,
'xxoxxxoxx' ,
'xoxxxxxox' ,
'xoxxxxxoxx'
] , ` #{ FZF } -foo < #{ tempname } ` . split ( $INPUT_RECORD_SEPARATOR )
assert_equal [
'xxxoxoxxx' ,
'xxxxoxox' ,
' xxoxoxxx' ,
'xxoxxxoxx' ,
'xoxxxxxoxx' ,
'xoxxxxxox'
] , ` #{ FZF } -foo --tiebreak=index < #{ tempname } ` . split ( $INPUT_RECORD_SEPARATOR )
# Note that --tiebreak=begin is now based on the first occurrence of the
# first character on the pattern
assert_equal [
' xxoxoxxx' ,
'xxxoxoxxx' ,
'xxxxoxox' ,
'xxoxxxoxx' ,
'xoxxxxxoxx' ,
'xoxxxxxox'
] , ` #{ FZF } -foo --tiebreak=begin < #{ tempname } ` . split ( $INPUT_RECORD_SEPARATOR )
assert_equal [
' xxoxoxxx' ,
'xxxoxoxxx' ,
'xxxxoxox' ,
'xxoxxxoxx' ,
'xoxxxxxox' ,
'xoxxxxxoxx'
] , ` #{ FZF } -foo --tiebreak=begin,length < #{ tempname } ` . split ( $INPUT_RECORD_SEPARATOR )
end
def test_tiebreak_begin_algo_v2
writelines tempname , [
'baz foo bar' ,
'foo bar baz'
]
assert_equal [
'foo bar baz' ,
'baz foo bar'
] , ` #{ FZF } -fbar --tiebreak=begin --algo=v2 < #{ tempname } ` . split ( $INPUT_RECORD_SEPARATOR )
end
def test_tiebreak_end
writelines tempname , [
'xoxxxxxxxx' ,
'xxoxxxxxxx' ,
'xxxoxxxxxx' ,
'xxxxoxxxx' ,
'xxxxxoxxx' ,
' xxxxoxxx'
]
assert_equal [
' xxxxoxxx' ,
'xxxxoxxxx' ,
'xxxxxoxxx' ,
'xoxxxxxxxx' ,
'xxoxxxxxxx' ,
'xxxoxxxxxx'
] , ` #{ FZF } -fo < #{ tempname } ` . split ( $INPUT_RECORD_SEPARATOR )
assert_equal [
'xxxxxoxxx' ,
' xxxxoxxx' ,
'xxxxoxxxx' ,
'xxxoxxxxxx' ,
'xxoxxxxxxx' ,
'xoxxxxxxxx'
] , ` #{ FZF } -fo --tiebreak=end < #{ tempname } ` . split ( $INPUT_RECORD_SEPARATOR )
assert_equal [
'xxxxxoxxx' ,
' xxxxoxxx' ,
'xxxxoxxxx' ,
'xxxoxxxxxx' ,
'xxoxxxxxxx' ,
'xoxxxxxxxx'
] , ` #{ FZF } -fo --tiebreak=end,length,begin < #{ tempname } ` . split ( $INPUT_RECORD_SEPARATOR )
end
def test_tiebreak_length_with_nth
input = %w[
1 :hell
123 :hello
12345 :he
1234567 :h
]
writelines tempname , input
output = %w[
1 :hell
12345 :he
123 :hello
1234567 :h
]
assert_equal output , ` #{ FZF } -fh < #{ tempname } ` . split ( $INPUT_RECORD_SEPARATOR )
# Since 0.16.8, --nth doesn't affect --tiebreak
assert_equal output , ` #{ FZF } -fh -n2 -d: < #{ tempname } ` . split ( $INPUT_RECORD_SEPARATOR )
end
def test_invalid_cache
tmux . send_keys " (echo d; echo D; echo x) | #{ fzf '-q d' } " , :Enter
tmux . until { | lines | lines [ - 2 ] . include? '2/3' }
tmux . send_keys :BSpace
tmux . until { | lines | lines [ - 2 ] . include? '3/3' }
tmux . send_keys :D
tmux . until { | lines | lines [ - 2 ] . include? '1/3' }
tmux . send_keys :Enter
end
def test_invalid_cache_query_type
command = %[ (echo 'foo$bar'; echo 'barfoo'; echo 'foo^bar'; echo "foo'1-2"; seq 100) | #{ fzf } ]
# Suffix match
tmux . send_keys command , :Enter
tmux . until { | lines | lines . match_count == 104 }
tmux . send_keys 'foo$'
tmux . until { | lines | lines . match_count == 1 }
tmux . send_keys 'bar'
tmux . until { | lines | lines . match_count == 1 }
tmux . send_keys :Enter
# Prefix match
tmux . prepare
tmux . send_keys command , :Enter
tmux . until { | lines | lines . match_count == 104 }
tmux . send_keys '^bar'
tmux . until { | lines | lines . match_count == 1 }
tmux . send_keys 'C-a' , 'foo'
tmux . until { | lines | lines . match_count == 1 }
tmux . send_keys :Enter
# Exact match
tmux . prepare
tmux . send_keys command , :Enter
tmux . until { | lines | lines . match_count == 104 }
tmux . send_keys " '12 "
tmux . until { | lines | lines . match_count == 1 }
tmux . send_keys 'C-a' , 'foo'
tmux . until { | lines | lines . match_count == 1 }
end
def test_smart_case_for_each_term
assert_equal 1 , ` echo Foo bar | #{ FZF } -x -f "foo Fbar" | wc -l ` . to_i
end
def test_bind
tmux . send_keys " seq 1 1000 | #{ fzf '-m --bind=ctrl-j:accept,u:up,T:toggle-up,t:toggle' } " , :Enter
tmux . until { | lines | lines [ - 2 ] . end_with? '/1000' }
tmux . send_keys 'uuu' , 'TTT' , 'tt' , 'uu' , 'ttt' , 'C-j'
assert_equal %w[ 4 5 6 9 ] , readonce . split ( $INPUT_RECORD_SEPARATOR )
end
def test_bind_print_query
tmux . send_keys " seq 1 1000 | #{ fzf '-m --bind=ctrl-j:print-query' } " , :Enter
tmux . until { | lines | lines [ - 2 ] . end_with? '/1000' }
tmux . send_keys 'print-my-query' , 'C-j'
assert_equal %w[ print-my-query ] , readonce . split ( $INPUT_RECORD_SEPARATOR )
end
def test_bind_replace_query
tmux . send_keys " seq 1 1000 | #{ fzf '--print-query --bind=ctrl-j:replace-query' } " , :Enter
tmux . send_keys '1'
tmux . until { | lines | lines [ - 2 ] . end_with? '272/1000' }
tmux . send_keys 'C-k' , 'C-j'
tmux . until { | lines | lines [ - 2 ] . end_with? '29/1000' }
tmux . until { | lines | lines [ - 1 ] . end_with? '> 10' }
end
def test_long_line
data = '.' * 256 * 1024
File . open ( tempname , 'w' ) do | f |
f << data
end
assert_equal data , ` #{ FZF } -f . < #{ tempname } ` . chomp
end
def test_read0
lines = ` find . ` . split ( $INPUT_RECORD_SEPARATOR )
assert_equal lines . last , ` find . | #{ FZF } -e -f "^ #{ lines . last } $" ` . chomp
assert_equal (
lines . last ,
` find . -print0 | #{ FZF } --read0 -e -f "^ #{ lines . last } $" ` . chomp
)
end
def test_select_all_deselect_all_toggle_all
tmux . send_keys " seq 100 | #{ fzf '--bind ctrl-a:select-all,ctrl-d:deselect-all,ctrl-t:toggle-all --multi' } " , :Enter
tmux . until { | lines | lines [ - 2 ] . include? '100/100' }
tmux . send_keys :BTab , :BTab , :BTab
tmux . until { | lines | lines [ - 2 ] . include? '(3)' }
tmux . send_keys 'C-t'
tmux . until { | lines | lines [ - 2 ] . include? '(97)' }
tmux . send_keys 'C-a'
tmux . until { | lines | lines [ - 2 ] . include? '(100)' }
tmux . send_keys :Tab , :Tab
tmux . until { | lines | lines [ - 2 ] . include? '(98)' }
tmux . send_keys '100'
tmux . until { | lines | lines . match_count == 1 }
tmux . send_keys 'C-d'
tmux . until { | lines | lines [ - 2 ] . include? '(97)' }
tmux . send_keys 'C-u'
tmux . until { | lines | lines . match_count == 100 }
tmux . send_keys 'C-d'
tmux . until { | lines | ! lines [ - 2 ] . include? '(' }
tmux . send_keys :BTab , :BTab
tmux . until { | lines | lines [ - 2 ] . include? '(2)' }
tmux . send_keys 0
tmux . until { | lines | lines [ - 2 ] . include? '10/100' }
tmux . send_keys 'C-a'
tmux . until { | lines | lines [ - 2 ] . include? '(12)' }
tmux . send_keys :Enter
assert_equal %w[ 1 2 10 20 30 40 50 60 70 80 90 100 ] ,
readonce . split ( $INPUT_RECORD_SEPARATOR )
end
def test_history
history_file = '/tmp/fzf-test-history'
# History with limited number of entries
begin
File . unlink history_file
rescue
nil
end
opts = " --history= #{ history_file } --history-size=4 "
input = %w[ 00 11 22 33 44 ] . map { | e | e + $INPUT_RECORD_SEPARATOR }
input . each do | keys |
tmux . send_keys " seq 100 | #{ fzf opts } " , :Enter
tmux . until { | lines | lines [ - 2 ] . include? '100/100' }
tmux . send_keys keys
tmux . until { | lines | lines [ - 2 ] . include? '1/100' }
tmux . send_keys :Enter
readonce
end
assert_equal input [ 1 .. - 1 ] , File . readlines ( history_file )
# Update history entries (not changed on disk)
tmux . send_keys " seq 100 | #{ fzf opts } " , :Enter
tmux . until { | lines | lines [ - 2 ] . include? '100/100' }
tmux . send_keys 'C-p'
tmux . until { | lines | lines [ - 1 ] . end_with? '> 44' }
tmux . send_keys 'C-p'
tmux . until { | lines | lines [ - 1 ] . end_with? '> 33' }
tmux . send_keys :BSpace
tmux . until { | lines | lines [ - 1 ] . end_with? '> 3' }
tmux . send_keys 1
tmux . until { | lines | lines [ - 1 ] . end_with? '> 31' }
tmux . send_keys 'C-p'
tmux . until { | lines | lines [ - 1 ] . end_with? '> 22' }
tmux . send_keys 'C-n'
tmux . until { | lines | lines [ - 1 ] . end_with? '> 31' }
tmux . send_keys 0
tmux . until { | lines | lines [ - 1 ] . end_with? '> 310' }
tmux . send_keys :Enter
readonce
assert_equal %w[ 22 33 44 310 ] . map { | e | e + $INPUT_RECORD_SEPARATOR } , File . readlines ( history_file )
# Respect --bind option
tmux . send_keys " seq 100 | #{ fzf opts + ' --bind ctrl-p:next-history,ctrl-n:previous-history' } " , :Enter
tmux . until { | lines | lines [ - 2 ] . include? '100/100' }
tmux . send_keys 'C-n' , 'C-n' , 'C-n' , 'C-n' , 'C-p'
tmux . until { | lines | lines [ - 1 ] . end_with? ( '33' ) }
tmux . send_keys :Enter
ensure
File . unlink history_file
end
def test_execute
output = '/tmp/fzf-test-execute'
opts = %[ --bind "alt-a:execute(echo /{}/ >> #{ output } ),alt-b:execute [ echo /{}{}/ >> #{ output } ] ,C:execute:echo /{}{}{}/ >> #{ output } " ]
wait = - > ( exp ) { tmux . until { | lines | lines [ - 2 ] . include? exp } }
writelines tempname , %w[ foo'bar foo"bar foo$bar ]
tmux . send_keys " cat #{ tempname } | #{ fzf opts } ; sync " , :Enter
wait [ '3/3' ]
tmux . send_keys :Escape , :a
wait [ '/3' ]
tmux . send_keys :Escape , :a
wait [ '/3' ]
tmux . send_keys :Up
tmux . send_keys :Escape , :b
wait [ '/3' ]
tmux . send_keys :Escape , :b
wait [ '/3' ]
tmux . send_keys :Up
tmux . send_keys :C
wait [ '3/3' ]
tmux . send_keys 'barfoo'
wait [ '0/3' ]
tmux . send_keys :Escape , :a
wait [ '/3' ]
tmux . send_keys :Escape , :b
wait [ '/3' ]
tmux . send_keys :Enter
readonce
assert_equal %w[ /foo'bar/ /foo'bar/
/ foo"barfoo"bar / / foo"barfoo"bar /
/ foo$barfoo$barfoo$bar / ] ,
File . readlines ( output ) . map ( & :chomp )
ensure
begin
File . unlink output
rescue
nil
end
end
def test_execute_multi
output = '/tmp/fzf-test-execute-multi'
opts = %[ --multi --bind "alt-a:execute-multi(echo {}/{+} >> #{ output } ; sync)" ]
writelines tempname , %w[ foo'bar foo"bar foo$bar foobar ]
tmux . send_keys " cat #{ tempname } | #{ fzf opts } " , :Enter
tmux . until { | lines | lines [ - 2 ] . include? '4/4' }
tmux . send_keys :Escape , :a
tmux . until { | lines | lines [ - 2 ] . include? '/4' }
tmux . send_keys :BTab , :BTab , :BTab
tmux . send_keys :Escape , :a
tmux . until { | lines | lines [ - 2 ] . include? '/4' }
tmux . send_keys :Tab , :Tab
tmux . send_keys :Escape , :a
tmux . until { | lines | lines [ - 2 ] . include? '/4' }
tmux . send_keys :Enter
tmux . prepare
readonce
assert_equal [ %( foo'bar/foo'bar ) ,
%( foo'bar foo"bar foo$bar/foo'bar foo"bar foo$bar ) ,
%( foo'bar foo"bar foobar/foo'bar foo"bar foobar ) ] ,
File . readlines ( output ) . map ( & :chomp )
ensure
begin
File . unlink output
rescue
nil
end
end
def test_execute_plus_flag
output = tempname + '.tmp'
begin
File . unlink output
rescue
nil
end
writelines tempname , [ 'foo bar' , '123 456' ]
tmux . send_keys " cat #{ tempname } | #{ FZF } --multi --bind 'x:execute-silent(echo {+}/{}/{+2}/{2} >> #{ output } )' " , :Enter
execute = lambda do
tmux . send_keys 'x' , 'y'
tmux . until { | lines | lines [ - 2 ] . include? '0/2' }
tmux . send_keys :BSpace
tmux . until { | lines | lines [ - 2 ] . include? '2/2' }
end
tmux . until { | lines | lines [ - 2 ] . include? '2/2' }
execute . call
tmux . send_keys :Up
tmux . send_keys :Tab
execute . call
tmux . send_keys :Tab
execute . call
tmux . send_keys :Enter
tmux . prepare
readonce
assert_equal [
%( foo bar/foo bar/bar/bar ) ,
%( 123 456/foo bar/456/bar ) ,
%( 123 456 foo bar/foo bar/456 bar/bar )
] , File . readlines ( output ) . map ( & :chomp )
rescue
begin
File . unlink output
rescue
nil
end
end
def test_execute_shell
# Custom script to use as $SHELL
output = tempname + '.out'
begin
File . unlink output
rescue
nil
end
writelines tempname ,
[ '#!/usr/bin/env bash' , " echo $1 / $2 > #{ output } " , 'sync' ]
system " chmod +x #{ tempname } "
tmux . send_keys " echo foo | SHELL= #{ tempname } fzf --bind 'enter:execute:{}bar' " , :Enter
tmux . until { | lines | lines [ - 2 ] . include? '1/1' }
tmux . send_keys :Enter
tmux . until { | lines | lines [ - 2 ] . include? '1/1' }
tmux . send_keys 'C-c'
tmux . prepare
assert_equal [ " -c / 'foo'bar " ] , File . readlines ( output ) . map ( & :chomp )
ensure
begin
File . unlink output
rescue
nil
end
end
def test_cycle
tmux . send_keys " seq 8 | #{ fzf :cycle } " , :Enter
tmux . until { | lines | lines [ - 2 ] . include? '8/8' }
tmux . send_keys :Down
tmux . until { | lines | lines [ - 10 ] . start_with? '>' }
tmux . send_keys :Down
tmux . until { | lines | lines [ - 9 ] . start_with? '>' }
tmux . send_keys :Up
tmux . until { | lines | lines [ - 10 ] . start_with? '>' }
tmux . send_keys :PgUp
tmux . until { | lines | lines [ - 10 ] . start_with? '>' }
tmux . send_keys :Up
tmux . until { | lines | lines [ - 3 ] . start_with? '>' }
tmux . send_keys :PgDn
tmux . until { | lines | lines [ - 3 ] . start_with? '>' }
tmux . send_keys :Down
tmux . until { | lines | lines [ - 10 ] . start_with? '>' }
end
def test_header_lines
tmux . send_keys " seq 100 | #{ fzf '--header-lines=10 -q 5' } " , :Enter
2 . times do
tmux . until do | lines |
lines [ - 2 ] . include? ( '/90' ) &&
lines [ - 3 ] == ' 1' &&
lines [ - 4 ] == ' 2' &&
lines [ - 13 ] == '> 50'
end
tmux . send_keys :Down
end
tmux . send_keys :Enter
assert_equal '50' , readonce . chomp
end
def test_header_lines_reverse
tmux . send_keys " seq 100 | #{ fzf '--header-lines=10 -q 5 --reverse' } " , :Enter
2 . times do
tmux . until do | lines |
lines [ 1 ] . include? ( '/90' ) &&
lines [ 2 ] == ' 1' &&
lines [ 3 ] == ' 2' &&
lines [ 12 ] == '> 50'
end
tmux . send_keys :Up
end
tmux . send_keys :Enter
assert_equal '50' , readonce . chomp
end
def test_header_lines_reverse_list
tmux . send_keys " seq 100 | #{ fzf '--header-lines=10 -q 5 --layout=reverse-list' } " , :Enter
2 . times do
tmux . until do | lines |
lines [ 0 ] == '> 50' &&
lines [ - 4 ] == ' 2' &&
lines [ - 3 ] == ' 1' &&
lines [ - 2 ] . include? ( '/90' )
end
tmux . send_keys :Up
end
tmux . send_keys :Enter
assert_equal '50' , readonce . chomp
end
def test_header_lines_overflow
tmux . send_keys " seq 100 | #{ fzf '--header-lines=200' } " , :Enter
tmux . until do | lines |
lines [ - 2 ] . include? ( '0/0' ) &&
lines [ - 3 ] . include? ( ' 1' )
end
tmux . send_keys :Enter
assert_equal '' , readonce . chomp
end
def test_header_lines_with_nth
tmux . send_keys " seq 100 | #{ fzf '--header-lines 5 --with-nth 1,1,1,1,1' } " , :Enter
tmux . until do | lines |
lines [ - 2 ] . include? ( '95/95' ) &&
lines [ - 3 ] == ' 11111' &&
lines [ - 7 ] == ' 55555' &&
lines [ - 8 ] == '> 66666'
end
tmux . send_keys :Enter
assert_equal '6' , readonce . chomp
end
def test_header
tmux . send_keys " seq 100 | #{ fzf " --header \" $(head -5 #{ FILE } ) \" " } " , :Enter
header = File . readlines ( FILE ) . take ( 5 ) . map ( & :strip )
tmux . until do | lines |
lines [ - 2 ] . include? ( '100/100' ) &&
lines [ - 7 .. - 3 ] . map ( & :strip ) == header &&
lines [ - 8 ] == '> 1'
end
end
def test_header_reverse
tmux . send_keys " seq 100 | #{ fzf " --header \" $(head -5 #{ FILE } ) \" --reverse " } " , :Enter
header = File . readlines ( FILE ) . take ( 5 ) . map ( & :strip )
tmux . until do | lines |
lines [ 1 ] . include? ( '100/100' ) &&
lines [ 2 .. 6 ] . map ( & :strip ) == header &&
lines [ 7 ] == '> 1'
end
end
def test_header_reverse_list
tmux . send_keys " seq 100 | #{ fzf " --header \" $(head -5 #{ FILE } ) \" --layout=reverse-list " } " , :Enter
header = File . readlines ( FILE ) . take ( 5 ) . map ( & :strip )
tmux . until do | lines |
lines [ - 2 ] . include? ( '100/100' ) &&
lines [ - 7 .. - 3 ] . map ( & :strip ) == header &&
lines [ 0 ] == '> 1'
end
end
def test_header_and_header_lines
tmux . send_keys " seq 100 | #{ fzf " --header-lines 10 --header \" $(head -5 #{ FILE } ) \" " } " , :Enter
header = File . readlines ( FILE ) . take ( 5 ) . map ( & :strip )
tmux . until do | lines |
lines [ - 2 ] . include? ( '90/90' ) &&
lines [ - 7 ... - 2 ] . map ( & :strip ) == header &&
lines [ - 17 ... - 7 ] . map ( & :strip ) == ( 1 .. 10 ) . map ( & :to_s ) . reverse
end
end
def test_header_and_header_lines_reverse
tmux . send_keys " seq 100 | #{ fzf " --reverse --header-lines 10 --header \" $(head -5 #{ FILE } ) \" " } " , :Enter
header = File . readlines ( FILE ) . take ( 5 ) . map ( & :strip )
tmux . until do | lines |
lines [ 1 ] . include? ( '90/90' ) &&
lines [ 2 ... 7 ] . map ( & :strip ) == header &&
lines [ 7 ... 17 ] . map ( & :strip ) == ( 1 .. 10 ) . map ( & :to_s )
end
end
def test_header_and_header_lines_reverse_list
tmux . send_keys " seq 100 | #{ fzf " --layout=reverse-list --header-lines 10 --header \" $(head -5 #{ FILE } ) \" " } " , :Enter
header = File . readlines ( FILE ) . take ( 5 ) . map ( & :strip )
tmux . until do | lines |
lines [ - 2 ] . include? ( '90/90' ) &&
lines [ - 7 ... - 2 ] . map ( & :strip ) == header &&
lines [ - 17 ... - 7 ] . map ( & :strip ) == ( 1 .. 10 ) . map ( & :to_s ) . reverse
end
end
def test_cancel
tmux . send_keys " seq 10 | #{ fzf '--bind 2:cancel' } " , :Enter
tmux . until { | lines | lines [ - 2 ] . include? ( '10/10' ) }
tmux . send_keys '123'
tmux . until { | lines | lines [ - 1 ] == '> 3' && lines [ - 2 ] . include? ( '1/10' ) }
tmux . send_keys 'C-y' , 'C-y'
tmux . until { | lines | lines [ - 1 ] == '> 311' }
tmux . send_keys 2
tmux . until { | lines | lines [ - 1 ] == '>' }
tmux . send_keys 2
tmux . prepare
end
def test_margin
tmux . send_keys " yes | head -1000 | #{ fzf '--margin 5,3' } " , :Enter
tmux . until { | lines | lines [ 4 ] == '' && lines [ 5 ] == ' y' }
tmux . send_keys :Enter
end
def test_margin_reverse
tmux . send_keys " seq 1000 | #{ fzf '--margin 7,5 --reverse' } " , :Enter
tmux . until { | lines | lines [ 1 + 7 ] == ' 1000/1000' }
tmux . send_keys :Enter
end
def test_margin_reverse_list
tmux . send_keys " yes | head -1000 | #{ fzf '--margin 5,3 --layout=reverse-list' } " , :Enter
tmux . until { | lines | lines [ 4 ] == '' && lines [ 5 ] == ' > y' }
tmux . send_keys :Enter
end
def test_tabstop
writelines tempname , [ " f \t oo \t ba \t r \t baz \t barfooq \t ux " ]
{
1 = > '> f oo ba r baz barfooq ux' ,
2 = > '> f oo ba r baz barfooq ux' ,
3 = > '> f oo ba r baz barfooq ux' ,
4 = > '> f oo ba r baz barfooq ux' ,
5 = > '> f oo ba r baz barfooq ux' ,
6 = > '> f oo ba r baz barfooq ux' ,
7 = > '> f oo ba r baz barfooq ux' ,
8 = > '> f oo ba r baz barfooq ux' ,
9 = > '> f oo ba r baz barfooq ux'
} . each do | ts , exp |
tmux . prepare
tmux . send_keys %( cat #{ tempname } | fzf --tabstop= #{ ts } ) , :Enter
tmux . until ( true ) do | lines |
exp . start_with? lines [ - 3 ] . to_s . strip . sub ( / \ . \ .$ / , '' )
end
tmux . send_keys :Enter
end
end
def test_with_nth_basic
writelines tempname , [ 'hello world ' , 'byebye' ]
assert_equal (
'hello world ' ,
` #{ FZF } -f"^he hehe" -x -n 2.. --with-nth 2,1,1 < #{ tempname } ` . chomp
)
end
def test_with_nth_ansi
writelines tempname , [ " \x1b [33mhello \x1b [34;1mworld \x1b [m " , 'byebye' ]
assert_equal (
'hello world ' ,
` #{ FZF } -f"^he hehe" -x -n 2.. --with-nth 2,1,1 --ansi < #{ tempname } ` . chomp
)
end
def test_with_nth_no_ansi
src = " \x1b [33mhello \x1b [34;1mworld \x1b [m "
writelines tempname , [ src , 'byebye' ]
assert_equal (
src ,
` #{ FZF } -fhehe -x -n 2.. --with-nth 2,1,1 --no-ansi < #{ tempname } ` . chomp
)
end
def test_exit_0_exit_code
` echo foo | #{ FZF } -q bar -0 `
assert_equal 1 , $CHILD_STATUS . exitstatus
end
def test_invalid_option
lines = ` #{ FZF } --foobar 2>&1 `
assert_equal 2 , $CHILD_STATUS . exitstatus
assert lines . include? ( 'unknown option: --foobar' ) , lines
end
def test_filter_exitstatus
# filter / streaming filter
[ '' , '--no-sort' ] . each do | opts |
assert ` echo foo | #{ FZF } -f foo #{ opts } ` . include? ( 'foo' )
assert_equal 0 , $CHILD_STATUS . exitstatus
assert ` echo foo | #{ FZF } -f bar #{ opts } ` . empty?
assert_equal 1 , $CHILD_STATUS . exitstatus
end
end
def test_exitstatus_empty
{ '99' = > '0' , '999' = > '1' } . each do | query , status |
tmux . send_keys " seq 100 | #{ FZF } -q #{ query } ; echo --$?-- " , :Enter
tmux . until { | lines | lines [ - 2 ] =~ %r{ [10]/100 } }
tmux . send_keys :Enter
tmux . until { | lines | lines . last . include? " -- #{ status } -- " }
end
end
def test_default_extended
assert_equal '100' , ` seq 100 | #{ FZF } -f "1 00$" ` . chomp
assert_equal '' , ` seq 100 | #{ FZF } -f "1 00$" +x ` . chomp
end
def test_exact
assert_equal 4 , ` seq 123 | #{ FZF } -f 13 ` . lines . length
assert_equal 2 , ` seq 123 | #{ FZF } -f 13 -e ` . lines . length
assert_equal 4 , ` seq 123 | #{ FZF } -f 13 +e ` . lines . length
end
def test_or_operator
assert_equal %w[ 1 5 10 ] , ` seq 10 | #{ FZF } -f "1 | 5" ` . lines . map ( & :chomp )
assert_equal %w[ 1 10 2 3 4 5 6 7 8 9 ] ,
` seq 10 | #{ FZF } -f '1 | !1' ` . lines . map ( & :chomp )
end
def test_hscroll_off
writelines tempname , [ '=' * 10_000 + '0123456789' ]
[ 0 , 3 , 6 ] . each do | off |
tmux . prepare
tmux . send_keys " #{ FZF } --hscroll-off= #{ off } -q 0 < #{ tempname } " , :Enter
tmux . until { | lines | lines [ - 3 ] . end_with? ( ( 0 .. off ) . to_a . join + '..' ) }
tmux . send_keys '9'
tmux . until { | lines | lines [ - 3 ] . end_with? '789' }
tmux . send_keys :Enter
end
end
def test_partial_caching
tmux . send_keys 'seq 1000 | fzf -e' , :Enter
tmux . until { | lines | lines [ - 2 ] == ' 1000/1000' }
tmux . send_keys 11
tmux . until { | lines | lines [ - 2 ] == ' 19/1000' }
tmux . send_keys 'C-a' , " ' "
tmux . until { | lines | lines [ - 2 ] == ' 28/1000' }
tmux . send_keys :Enter
end
def test_jump
tmux . send_keys " seq 1000 | #{ fzf " --multi --jump-labels 12345 --bind 'ctrl-j:jump' " } " , :Enter
tmux . until { | lines | lines [ - 2 ] == ' 1000/1000' }
tmux . send_keys 'C-j'
tmux . until { | lines | lines [ - 7 ] == '5 5' }
tmux . until { | lines | lines [ - 8 ] == ' 6' }
tmux . send_keys '5'
tmux . until { | lines | lines [ - 7 ] == '> 5' }
tmux . send_keys :Tab
tmux . until { | lines | lines [ - 7 ] == ' >5' }
tmux . send_keys 'C-j'
tmux . until { | lines | lines [ - 7 ] == '5>5' }
tmux . send_keys '2'
tmux . until { | lines | lines [ - 4 ] == '> 2' }
tmux . send_keys :Tab
tmux . until { | lines | lines [ - 4 ] == ' >2' }
tmux . send_keys 'C-j'
tmux . until { | lines | lines [ - 7 ] == '5>5' }
# Press any key other than jump labels to cancel jump
tmux . send_keys '6'
tmux . until { | lines | lines [ - 3 ] == '> 1' }
tmux . send_keys :Tab
tmux . until { | lines | lines [ - 3 ] == '>>1' }
tmux . send_keys :Enter
assert_equal %w[ 5 2 1 ] , readonce . split ( $INPUT_RECORD_SEPARATOR )
end
def test_jump_accept
tmux . send_keys " seq 1000 | #{ fzf " --multi --jump-labels 12345 --bind 'ctrl-j:jump-accept' " } " , :Enter
tmux . until { | lines | lines [ - 2 ] == ' 1000/1000' }
tmux . send_keys 'C-j'
tmux . until { | lines | lines [ - 7 ] == '5 5' }
tmux . send_keys '3'
assert_equal '3' , readonce . chomp
end
def test_pointer
pointer = '>>'
tmux . send_keys " seq 10 | #{ fzf " --pointer ' #{ pointer } ' " } " , :Enter
tmux . until { | lines | lines [ - 2 ] == ' 10/10' }
lines = tmux . capture
# Assert that specified pointer is displayed
assert_equal " #{ pointer } 1 " , lines [ - 3 ]
end
def test_pointer_with_jump
pointer = '>>'
tmux . send_keys " seq 10 | #{ fzf " --multi --jump-labels 12345 --bind 'ctrl-j:jump' --pointer ' #{ pointer } ' " } " , :Enter
tmux . until { | lines | lines [ - 2 ] == ' 10/10' }
tmux . send_keys 'C-j'
# Correctly padded jump label should appear
tmux . until { | lines | lines [ - 7 ] == '5 5' }
tmux . until { | lines | lines [ - 8 ] == ' 6' }
tmux . send_keys '5'
lines = tmux . capture
# Assert that specified pointer is displayed
assert_equal " #{ pointer } 5 " , lines [ - 7 ]
end
def test_marker
marker = '>>'
tmux . send_keys " seq 10 | #{ fzf " --multi --marker ' #{ marker } ' " } " , :Enter
tmux . until { | lines | lines [ - 2 ] == ' 10/10' }
tmux . send_keys :BTab
lines = tmux . capture
# Assert that specified marker is displayed
assert_equal " #{ marker } 1 " , lines [ - 3 ]
end
def test_preview
tmux . send_keys %( seq 1000 | sed s/^2$// | #{ FZF } -m --preview 'sleep 0.2; echo {{}-{+}}' --bind ?:toggle-preview ) , :Enter
tmux . until { | lines | lines [ 1 ] . include? ( ' {1-1}' ) }
tmux . send_keys :Up
tmux . until { | lines | lines [ 1 ] . include? ( ' {-}' ) }
tmux . send_keys '555'
tmux . until { | lines | lines [ 1 ] . include? ( ' {555-555}' ) }
tmux . send_keys '?'
tmux . until { | lines | ! lines [ 1 ] . include? ( ' {555-555}' ) }
tmux . send_keys '?'
tmux . until { | lines | lines [ 1 ] . include? ( ' {555-555}' ) }
tmux . send_keys :BSpace
tmux . until { | lines | lines [ - 2 ] . start_with? ' 28/1000' }
tmux . send_keys 'foobar'
tmux . until { | lines | ! lines [ 1 ] . include? ( '{' ) }
tmux . send_keys 'C-u'
tmux . until { | lines | lines . match_count == 1000 }
tmux . until { | lines | lines [ 1 ] . include? ( ' {1-1}' ) }
tmux . send_keys :BTab
tmux . until { | lines | lines [ 1 ] . include? ( ' {-1}' ) }
tmux . send_keys :BTab
tmux . until { | lines | lines [ 1 ] . include? ( ' {3-1 }' ) }
tmux . send_keys :BTab
tmux . until { | lines | lines [ 1 ] . include? ( ' {4-1 3}' ) }
tmux . send_keys :BTab
tmux . until { | lines | lines [ 1 ] . include? ( ' {5-1 3 4}' ) }
end
def test_preview_hidden
tmux . send_keys %( seq 1000 | #{ FZF } --preview 'echo {{}-{}-$FZF_PREVIEW_LINES-$FZF_PREVIEW_COLUMNS}' --preview-window down:1:hidden --bind ?:toggle-preview ) , :Enter
tmux . until { | lines | lines [ - 1 ] == '>' }
tmux . send_keys '?'
tmux . until { | lines | lines [ - 2 ] =~ / {1-1-1-[0-9]+} / }
tmux . send_keys '555'
tmux . until { | lines | lines [ - 2 ] =~ / {555-555-1-[0-9]+} / }
tmux . send_keys '?'
tmux . until { | lines | lines [ - 1 ] == '> 555' }
end
def test_preview_size_0
begin
File . unlink tempname
rescue
nil
end
tmux . send_keys %( seq 100 | #{ FZF } --reverse --preview 'echo {} >> #{ tempname } ; echo ' --preview-window 0 ) , :Enter
tmux . until { | lines | lines . item_count == 100 && lines [ 1 ] == ' 100/100' && lines [ 2 ] == '> 1' }
tmux . until { | _ | %w[ 1 ] == File . readlines ( tempname ) . map ( & :chomp ) }
tmux . send_keys :Down
tmux . until { | lines | lines [ 3 ] == '> 2' }
tmux . until { | _ | %w[ 1 2 ] == File . readlines ( tempname ) . map ( & :chomp ) }
tmux . send_keys :Down
tmux . until { | lines | lines [ 4 ] == '> 3' }
tmux . until { | _ | %w[ 1 2 3 ] == File . readlines ( tempname ) . map ( & :chomp ) }
end
def test_preview_flags
tmux . send_keys %( seq 10 | sed 's/^/:: /; s/$/ /' |
#{FZF} --multi --preview 'echo {{2}/{s2}/{+2}/{+s2}/{q}/{n}/{+n}}'), :Enter
tmux . until { | lines | lines [ 1 ] . include? ( '{1/1 /1/1 //0/0}' ) }
tmux . send_keys '123'
tmux . until { | lines | lines [ 1 ] . include? ( '{////123//}' ) }
tmux . send_keys 'C-u' , '1'
tmux . until { | lines | lines . match_count == 2 }
tmux . until { | lines | lines [ 1 ] . include? ( '{1/1 /1/1 /1/0/0}' ) }
tmux . send_keys :BTab
tmux . until { | lines | lines [ 1 ] . include? ( '{10/10 /1/1 /1/9/0}' ) }
tmux . send_keys :BTab
tmux . until { | lines | lines [ 1 ] . include? ( '{10/10 /1 10/1 10 /1/9/0 9}' ) }
tmux . send_keys '2'
tmux . until { | lines | lines [ 1 ] . include? ( '{//1 10/1 10 /12//0 9}' ) }
tmux . send_keys '3'
tmux . until { | lines | lines [ 1 ] . include? ( '{//1 10/1 10 /123//0 9}' ) }
end
def test_preview_file
tmux . send_keys %[ (echo foo bar; echo bar foo) | #{ FZF } --multi --preview 'cat {+f} {+f2} {+nf} {+fn}' --print0 ] , :Enter
tmux . until { | lines | lines [ 1 ] . include? ( 'foo barbar00' ) }
tmux . send_keys :BTab
tmux . until { | lines | lines [ 1 ] . include? ( 'foo barbar00' ) }
tmux . send_keys :BTab
tmux . until { | lines | lines [ 1 ] . include? ( 'foo barbar foobarfoo0101' ) }
end
def test_preview_q_no_match
tmux . send_keys %( : | #{ FZF } --preview 'echo foo {q}' ) , :Enter
tmux . until { | lines | lines . match_count == 0 }
tmux . until { | lines | ! lines [ 1 ] . include? ( 'foo' ) }
tmux . send_keys 'bar'
tmux . until { | lines | lines [ 1 ] . include? ( 'foo bar' ) }
tmux . send_keys 'C-u'
tmux . until { | lines | ! lines [ 1 ] . include? ( 'foo' ) }
end
def test_preview_q_no_match_with_initial_query
tmux . send_keys %( : | #{ FZF } --preview 'echo foo {q}{q}' --query foo ) , :Enter
tmux . until { | lines | lines . match_count == 0 }
tmux . until { | lines | lines [ 1 ] . include? ( 'foofoo' ) }
end
def test_no_clear
tmux . send_keys " seq 10 | fzf --no-clear --inline-info --height 5 > #{ tempname } " , :Enter
prompt = '> < 10/10'
tmux . until { | lines | lines [ - 1 ] == prompt }
tmux . send_keys :Enter
tmux . until { | _ | %w[ 1 ] == File . readlines ( tempname ) . map ( & :chomp ) }
tmux . until { | lines | lines [ - 1 ] == prompt }
end
def test_info_hidden
tmux . send_keys 'seq 10 | fzf --info=hidden' , :Enter
tmux . until { | lines | lines [ - 2 ] == '> 1' }
end
def test_change_top
tmux . send_keys %( seq 1000 | #{ FZF } --bind change:top ) , :Enter
tmux . until { | lines | lines . match_count == 1000 }
tmux . send_keys :Up
tmux . until { | lines | lines [ - 4 ] == '> 2' }
tmux . send_keys 1
tmux . until { | lines | lines [ - 3 ] == '> 1' }
tmux . send_keys :Up
tmux . until { | lines | lines [ - 4 ] == '> 10' }
tmux . send_keys 1
tmux . until { | lines | lines [ - 3 ] == '> 11' }
tmux . send_keys :Enter
end
def test_accept_non_empty
tmux . send_keys %( seq 1000 | #{ fzf '--print-query --bind enter:accept-non-empty' } ) , :Enter
tmux . until { | lines | lines . match_count == 1000 }
tmux . send_keys 'foo'
tmux . until { | lines | lines [ - 2 ] . include? '0/1000' }
# fzf doesn't exit since there's no selection
tmux . send_keys :Enter
tmux . until { | lines | lines [ - 2 ] . include? '0/1000' }
tmux . send_keys 'C-u'
tmux . until { | lines | lines [ - 2 ] . include? '1000/1000' }
tmux . send_keys '999'
tmux . until { | lines | lines [ - 2 ] . include? '1/1000' }
tmux . send_keys :Enter
assert_equal %w[ 999 999 ] , readonce . split ( $INPUT_RECORD_SEPARATOR )
end
def test_accept_non_empty_with_multi_selection
tmux . send_keys %( seq 1000 | #{ fzf '-m --print-query --bind enter:accept-non-empty' } ) , :Enter
tmux . until { | lines | lines . match_count == 1000 }
tmux . send_keys :Tab
tmux . until { | lines | lines [ - 2 ] . include? '1000/1000 (1)' }
tmux . send_keys 'foo'
tmux . until { | lines | lines [ - 2 ] . include? '0/1000' }
# fzf will exit in this case even though there's no match for the current query
tmux . send_keys :Enter
assert_equal %w[ foo 1 ] , readonce . split ( $INPUT_RECORD_SEPARATOR )
end
def test_accept_non_empty_with_empty_list
tmux . send_keys %( : | #{ fzf '-q foo --print-query --bind enter:accept-non-empty' } ) , :Enter
tmux . until { | lines | lines [ - 2 ] . strip == '0/0' }
tmux . send_keys :Enter
# fzf will exit anyway since input list is empty
assert_equal %w[ foo ] , readonce . split ( $INPUT_RECORD_SEPARATOR )
end
def test_preview_update_on_select
tmux . send_keys ( %( seq 10 | fzf -m --preview 'echo {+}' --bind a:toggle-all ) ,
:Enter )
tmux . until { | lines | lines . item_count == 10 }
tmux . send_keys 'a'
tmux . until { | lines | lines . any? { | line | line . include? '1 2 3 4 5' } }
tmux . send_keys 'a'
tmux . until { | lines | lines . none? { | line | line . include? '1 2 3 4 5' } }
end
def test_escaped_meta_characters
input = << ~ EOF
foo ^ bar
foo $bar
foo! bar
foo ' bar
foo bar
bar foo
EOF
writelines tempname , input . lines . map ( & :chomp )
assert_equal input . lines . count , ` #{ FZF } -f'foo bar' < #{ tempname } ` . lines . count
assert_equal input . lines . count - 1 , ` #{ FZF } -f'^foo bar$' < #{ tempname } ` . lines . count
assert_equal [ 'foo bar' ] , ` #{ FZF } -f'foo \\ bar' < #{ tempname } ` . lines . map ( & :chomp )
assert_equal [ 'foo bar' ] , ` #{ FZF } -f'^foo \\ bar$' < #{ tempname } ` . lines . map ( & :chomp )
assert_equal input . lines . count - 1 , ` #{ FZF } -f'!^foo \\ bar$' < #{ tempname } ` . lines . count
end
def test_inverse_only_search_should_not_sort_the_result
# Filter
assert_equal ( %w[ aaaaa b ccc ] ,
` printf '%s \n ' aaaaa b ccc BAD | #{ FZF } -f '!bad' ` . lines . map ( & :chomp ) )
# Interactive
tmux . send_keys ( %[ printf '%s \n ' aaaaa b ccc BAD | #{ FZF } -q '!bad' ] , :Enter )
tmux . until { | lines | lines . item_count == 4 && lines . match_count == 3 }
tmux . until { | lines | lines [ - 3 ] == '> aaaaa' }
tmux . until { | lines | lines [ - 4 ] == ' b' }
tmux . until { | lines | lines [ - 5 ] == ' ccc' }
end
def test_preview_correct_tab_width_after_ansi_reset_code
writelines tempname , [ " \x1b [31m+ \x1b [m \t \x1b [32mgreen " ]
tmux . send_keys " #{ FZF } --preview 'cat #{ tempname } ' " , :Enter
tmux . until { | lines | lines [ 1 ] . include? ( '+ green' ) }
end
def test_phony
tmux . send_keys %( seq 1000 | #{ FZF } --query 333 --phony --preview 'echo {} {q}' ) , :Enter
tmux . until { | lines | lines . match_count == 1000 }
tmux . until { | lines | lines [ 1 ] . include? ( '1 333' ) }
tmux . send_keys 'foo'
tmux . until { | lines | lines . match_count == 1000 }
tmux . until { | lines | lines [ 1 ] . include? ( '1 333foo' ) }
end
def test_reload
tmux . send_keys %( seq 1000 | #{ FZF } --bind 'change:reload ( seq {q} ) ,a:reload ( seq 100 ) ,b:reload:seq 200' --header-lines 2 --multi 2 ) , :Enter
tmux . until { | lines | lines . match_count == 998 }
tmux . send_keys 'a'
tmux . until { | lines | lines . item_count == 98 && lines . match_count == 98 }
tmux . send_keys 'b'
tmux . until { | lines | lines . item_count == 198 && lines . match_count == 198 }
tmux . send_keys :Tab
tmux . until { | lines | lines [ - 2 ] . include? ( '(1/2)' ) }
tmux . send_keys '555'
tmux . until { | lines | lines . item_count == 553 && lines . match_count == 1 }
tmux . until { | lines | ! lines [ - 2 ] . include? ( '(1/2)' ) }
end
def test_reload_even_when_theres_no_match
tmux . send_keys %( : | #{ FZF } --bind 'space:reload ( seq 10 ) ' ) , :Enter
tmux . until { | lines | lines . item_count . zero? }
tmux . send_keys :Space
tmux . until { | lines | lines . item_count == 10 }
end
def test_clear_list_when_header_lines_changed_due_to_reload
tmux . send_keys %( seq 10 | #{ FZF } --header 0 --header-lines 3 --bind 'space:reload ( seq 1 ) ' ) , :Enter
tmux . until { | lines | lines . any? { | line | line . include? ( '9' ) } }
tmux . send_keys :Space
tmux . until { | lines | lines . none? { | line | line . include? ( '9' ) } }
end
def test_clear_query
tmux . send_keys %( : | #{ FZF } --query foo --bind space:clear-query ) , :Enter
tmux . until { | lines | lines . item_count . zero? }
tmux . until { | lines | lines . last . include? ( '> foo' ) }
tmux . send_keys 'C-a' , 'bar'
tmux . until { | lines | lines . last . include? ( '> barfoo' ) }
tmux . send_keys :Space
tmux . until { | lines | lines . last == '>' }
end
def test_clear_selection
tmux . send_keys %( seq 100 | #{ FZF } --multi --bind space:clear-selection ) , :Enter
tmux . until { | lines | lines . match_count == 100 }
tmux . send_keys :Tab
tmux . until { | lines | lines [ - 2 ] . include? ( '(1)' ) }
tmux . send_keys 'foo'
tmux . until { | lines | lines . match_count . zero? }
tmux . until { | lines | lines [ - 2 ] . include? ( '(1)' ) }
tmux . send_keys :Space
tmux . until { | lines | lines . match_count . zero? }
tmux . until { | lines | ! lines [ - 2 ] . include? ( '(1)' ) }
end
def test_backward_delete_char_eof
tmux . send_keys " seq 1000 | #{ fzf " --bind 'bs:backward-delete-char/eof' " } " , :Enter
tmux . until { | lines | lines [ - 2 ] == ' 1000/1000' }
tmux . send_keys '11'
tmux . until { | lines | lines [ - 1 ] == '> 11' }
tmux . send_keys :BSpace
tmux . until { | lines | lines [ - 1 ] == '> 1' }
tmux . send_keys :BSpace
tmux . until { | lines | lines [ - 1 ] == '>' }
tmux . send_keys :BSpace
tmux . prepare
end
def test_strip_xterm_osc_sequence
%W[ \x07 \x1b \\ ] . each do | esc |
writelines tempname , [ %( printf $1" \e ]4;3;rgb:aa/bb/cc #{ esc } "$2 ) ]
File . chmod ( 0 o755 , tempname )
tmux . prepare
tmux . send_keys (
%( echo foo bar | #{ FZF } --preview ' #{ tempname } {2} {1}' ) , :Enter
)
tmux . until { | lines | lines . any_include? ( 'bar foo' ) }
tmux . send_keys :Enter
end
end
def test_keep_right
tmux . send_keys ( " seq 10000 | #{ FZF } --read0 --keep-right " , :Enter )
tmux . until { | lines | lines . any_include? ( '9999 10000' ) }
end
end
module TestShell
def setup
@tmux = Tmux . new shell
tmux . prepare
end
def teardown
@tmux . kill
end
def set_var ( name , val )
tmux . prepare
tmux . send_keys " export #{ name } =' #{ val } ' " , :Enter
tmux . prepare
end
def unset_var ( name )
tmux . prepare
tmux . send_keys " unset #{ name } " , :Enter
tmux . prepare
end
def test_ctrl_t
set_var 'FZF_CTRL_T_COMMAND' , 'seq 100'
retries do
tmux . prepare
tmux . send_keys 'C-t'
tmux . until { | lines | lines . item_count == 100 }
end
tmux . send_keys :Tab , :Tab , :Tab
tmux . until { | lines | lines . any_include? ' (3)' }
tmux . send_keys :Enter
tmux . until { | lines | lines . any_include? '1 2 3' }
tmux . send_keys 'C-c'
end
def test_ctrl_t_unicode
writelines tempname , [ 'fzf-unicode 테스트1' , 'fzf-unicode 테스트2' ]
set_var 'FZF_CTRL_T_COMMAND' , " cat #{ tempname } "
retries do
tmux . prepare
tmux . send_keys 'echo ' , 'C-t'
tmux . until { | lines | lines . item_count == 2 }
end
tmux . send_keys 'fzf-unicode'
tmux . until { | lines | lines . match_count == 2 }
tmux . send_keys '1'
tmux . until { | lines | lines . match_count == 1 }
tmux . send_keys :Tab
tmux . until { | lines | lines . select_count == 1 }
tmux . send_keys :BSpace
tmux . until { | lines | lines . match_count == 2 }
tmux . send_keys '2'
tmux . until { | lines | lines . match_count == 1 }
tmux . send_keys :Tab
tmux . until { | lines | lines . select_count == 2 }
tmux . send_keys :Enter
tmux . until { | lines | lines . join . match ( / echo.*fzf-unicode.*1.*fzf-unicode.*2 / ) }
tmux . send_keys :Enter
tmux . until { | lines | lines . any_include? ( / ^fzf-unicode.*1.*fzf-unicode.*2 / ) }
end
def test_alt_c
lines = retries do
tmux . prepare
tmux . send_keys :Escape , :c
tmux . until { | lines | lines . match_count . positive? }
end
expected = lines . reverse . select { | l | l . start_with? '>' } . first [ 2 .. - 1 ]
tmux . send_keys :Enter
tmux . prepare
tmux . send_keys :pwd , :Enter
tmux . until { | lines | lines [ - 1 ] . end_with? ( expected ) }
end
def test_alt_c_command
set_var 'FZF_ALT_C_COMMAND' , 'echo /tmp'
tmux . prepare
tmux . send_keys 'cd /' , :Enter
retries do
tmux . prepare
tmux . send_keys :Escape , :c
tmux . until { | lines | lines . item_count == 1 }
end
tmux . send_keys :Enter
tmux . prepare
tmux . send_keys :pwd , :Enter
tmux . until { | lines | lines [ - 1 ] . end_with? '/tmp' }
end
def test_ctrl_r
tmux . prepare
tmux . send_keys 'echo 1st' , :Enter ; tmux . prepare
tmux . send_keys 'echo 2nd' , :Enter ; tmux . prepare
tmux . send_keys 'echo 3d' , :Enter ; tmux . prepare
3 . times { tmux . send_keys 'echo 3rd' , :Enter ; tmux . prepare }
tmux . send_keys 'echo 4th' , :Enter
retries do
tmux . prepare
tmux . send_keys 'C-r'
tmux . until { | lines | lines . match_count . positive? }
end
tmux . send_keys 'e3d'
# Duplicates removed: 3d (1) + 3rd (1) => 2 matches
tmux . until { | lines | lines . match_count == 2 }
tmux . until { | lines | lines [ - 3 ] . end_with? 'echo 3d' }
tmux . send_keys 'C-r'
tmux . until { | lines | lines [ - 3 ] . end_with? 'echo 3rd' }
tmux . send_keys :Enter
tmux . until { | lines | lines [ - 1 ] == 'echo 3rd' }
tmux . send_keys :Enter
tmux . until { | lines | lines [ - 1 ] == '3rd' }
end
def test_ctrl_r_multiline
tmux . send_keys 'echo "foo' , :Enter , 'bar"' , :Enter
tmux . until { | lines | lines [ - 2 .. - 1 ] == [ 'foo' , 'bar' ] }
retries do
tmux . prepare
tmux . send_keys 'C-r'
tmux . until { | lines | lines [ - 1 ] == '>' }
end
tmux . send_keys 'foo bar'
tmux . until { | lines | lines [ - 3 ] . end_with? 'bar"' }
tmux . send_keys :Enter
tmux . until { | lines | lines [ - 1 ] . end_with? 'bar"' }
tmux . send_keys :Enter
tmux . until { | lines | lines [ - 2 .. - 1 ] == [ 'foo' , 'bar' ] }
end
def test_ctrl_r_abort
skip " doesn't restore the original line when search is aborted pre Bash 4 " if shell == :bash && / (?<= version ) \ d+ / . match ( ` #{ Shell . bash } --version ` ) . to_s . to_i < 4
%w[ foo ' " ] . each do | query |
retries do
tmux . prepare
tmux . send_keys ( :Space , 'C-e' , 'C-u' , query )
tmux . until { | lines | lines [ - 1 ] . start_with? query }
tmux . send_keys 'C-r'
tmux . until { | lines | lines [ - 1 ] == " > #{ query } " }
end
tmux . send_keys 'C-g'
tmux . until { | lines | lines [ - 1 ] . start_with? query }
end
end
def retries ( times = 3 )
( times - 1 ) . times do
begin
return yield
rescue RuntimeError
end
end
yield
end
end
module CompletionTest
def test_file_completion
FileUtils . mkdir_p '/tmp/fzf-test'
FileUtils . mkdir_p '/tmp/fzf test'
( 1 .. 100 ) . each { | i | FileUtils . touch " /tmp/fzf-test/ #{ i } " }
[ 'no~such~user' , '/tmp/fzf test/foobar' , '~/.fzf-home' ] . each do | f |
FileUtils . touch File . expand_path ( f )
end
tmux . prepare
tmux . send_keys 'cat /tmp/fzf-test/10**' , :Tab
tmux . until { | lines | lines . match_count . positive? }
tmux . send_keys ' !d'
tmux . until { | lines | lines . match_count == 2 }
tmux . send_keys :Tab , :Tab
tmux . until { | lines | lines . select_count == 2 }
tmux . send_keys :Enter
tmux . until ( true ) do | lines |
lines [ - 1 ] . include? ( '/tmp/fzf-test/10' ) &&
lines [ - 1 ] . include? ( '/tmp/fzf-test/100' )
end
# ~USERNAME**<TAB>
tmux . send_keys 'C-u'
tmux . send_keys " cat ~ #{ ENV [ 'USER' ] } ** " , :Tab
tmux . until { | lines | lines . match_count . positive? }
tmux . send_keys " '.fzf-home "
tmux . until { | lines | lines . select { | l | l . include? '.fzf-home' } . count > 1 }
tmux . send_keys :Enter
tmux . until ( true ) do | lines |
lines [ - 1 ] . end_with? ( '.fzf-home' )
end
# ~INVALID_USERNAME**<TAB>
tmux . send_keys 'C-u'
tmux . send_keys 'cat ~such**' , :Tab
tmux . until ( true ) { | lines | lines . any_include? 'no~such~user' }
tmux . send_keys :Enter
tmux . until ( true ) { | lines | lines [ - 1 ] . end_with? ( 'no~such~user' ) }
# /tmp/fzf\ test**<TAB>
tmux . send_keys 'C-u'
tmux . send_keys 'cat /tmp/fzf\ test/**' , :Tab
tmux . until { | lines | lines . match_count . positive? }
tmux . send_keys 'foobar$'
tmux . until { | lines | lines . match_count == 1 }
tmux . send_keys :Enter
tmux . until ( true ) { | lines | lines [ - 1 ] . end_with? ( '/tmp/fzf\ test/foobar' ) }
# Should include hidden files
( 1 .. 100 ) . each { | i | FileUtils . touch " /tmp/fzf-test/.hidden- #{ i } " }
tmux . send_keys 'C-u'
tmux . send_keys 'cat /tmp/fzf-test/hidden**' , :Tab
tmux . until ( true ) { | lines | lines . match_count == 100 && lines . any_include? ( '/tmp/fzf-test/.hidden-' ) }
tmux . send_keys :Enter
ensure
[ '/tmp/fzf-test' , '/tmp/fzf test' , '~/.fzf-home' , 'no~such~user' ] . each do | f |
FileUtils . rm_rf File . expand_path ( f )
end
end
def test_file_completion_root
tmux . send_keys 'ls /**' , :Tab
tmux . until { | lines | lines . match_count . positive? }
tmux . send_keys :Enter
end
def test_dir_completion
( 1 .. 100 ) . each do | idx |
FileUtils . mkdir_p " /tmp/fzf-test/d #{ idx } "
end
FileUtils . touch '/tmp/fzf-test/d55/xxx'
tmux . prepare
tmux . send_keys 'cd /tmp/fzf-test/**' , :Tab
tmux . until { | lines | lines . match_count . positive? }
tmux . send_keys :Tab , :Tab # Tab does not work here
tmux . send_keys 55
tmux . until { | lines | lines . match_count == 1 }
tmux . send_keys :Enter
tmux . until ( true ) { | lines | lines [ - 1 ] == 'cd /tmp/fzf-test/d55/' }
tmux . send_keys :xx
tmux . until { | lines | lines [ - 1 ] == 'cd /tmp/fzf-test/d55/xx' }
# Should not match regular files (bash-only)
if self . class == TestBash
tmux . send_keys :Tab
tmux . until { | lines | lines [ - 1 ] == 'cd /tmp/fzf-test/d55/xx' }
end
# Fail back to plusdirs
tmux . send_keys :BSpace , :BSpace , :BSpace
tmux . until { | lines | lines [ - 1 ] == 'cd /tmp/fzf-test/d55' }
tmux . send_keys :Tab
tmux . until { | lines | lines [ - 1 ] == 'cd /tmp/fzf-test/d55/' }
end
def test_process_completion
tmux . send_keys 'sleep 12345 &' , :Enter
lines = tmux . until { | lines | lines [ - 1 ] . start_with? '[1]' }
pid = lines [ - 1 ] . split . last
tmux . prepare
tmux . send_keys 'C-L'
tmux . send_keys 'kill ' , :Tab
tmux . until { | lines | lines . match_count . positive? }
tmux . send_keys 'sleep12345'
tmux . until { | lines | lines . any_include? 'sleep 12345' }
tmux . send_keys :Enter
tmux . until ( true ) { | lines | lines [ - 1 ] . include? " kill #{ pid } " }
ensure
if pid
begin
Process . kill 'KILL' , pid . to_i
rescue
nil
end
end
end
def test_custom_completion
tmux . send_keys '_fzf_compgen_path() { echo "$1"; seq 10; }' , :Enter
tmux . prepare
tmux . send_keys 'ls /tmp/**' , :Tab
tmux . until { | lines | lines . match_count == 11 }
tmux . send_keys :Tab , :Tab , :Tab
tmux . until { | lines | lines . select_count == 3 }
tmux . send_keys :Enter
tmux . until ( true ) { | lines | lines [ - 1 ] == 'ls /tmp 1 2' }
end
def test_unset_completion
tmux . send_keys 'export FZFFOOBAR=BAZ' , :Enter
tmux . prepare
# Using tmux
tmux . send_keys 'unset FZFFOOBR**' , :Tab
tmux . until { | lines | lines . match_count == 1 }
tmux . send_keys :Enter
tmux . until { | lines | lines [ - 1 ] . include? 'unset FZFFOOBAR' }
tmux . send_keys 'C-c'
# FZF_TMUX=1
new_shell
tmux . send_keys 'unset FZFFOOBR**' , :Tab , pane : 0
tmux . until ( false , 1 ) { | lines | lines . match_count == 1 }
tmux . send_keys :Enter
tmux . until { | lines | lines [ - 1 ] . include? 'unset FZFFOOBAR' }
end
def test_file_completion_unicode
FileUtils . mkdir_p '/tmp/fzf-test'
tmux . paste " cd /tmp/fzf-test; echo -n test3 > $'fzf-unicode \\ 355 \\ 205 \\ 214 \\ 354 \\ 212 \\ 244 \\ 355 \\ 212 \\ 2701'; echo -n test4 > $'fzf-unicode \\ 355 \\ 205 \\ 214 \\ 354 \\ 212 \\ 244 \\ 355 \\ 212 \\ 2702' "
tmux . prepare
tmux . send_keys 'cat fzf-unicode**' , :Tab
tmux . until { | lines | lines . match_count == 2 }
tmux . send_keys '1'
tmux . until { | lines | lines . match_count == 1 }
tmux . send_keys :Tab
tmux . until { | lines | lines . select_count == 1 }
tmux . send_keys :BSpace
tmux . until { | lines | lines . match_count == 2 }
tmux . send_keys '2'
tmux . until { | lines | lines . select_count == 1 }
tmux . send_keys :Tab
tmux . until { | lines | lines . select_count == 2 }
tmux . send_keys :Enter
tmux . until ( true ) { | lines | lines . any_include? 'cat' }
tmux . send_keys :Enter
tmux . until { | lines | lines [ - 1 ] . include? 'test3test4' }
end
def test_custom_completion_api
tmux . send_keys 'eval "_fzf$(declare -f _comprun)"' , :Enter
%w[ f g ] . each do | command |
tmux . prepare
tmux . send_keys " #{ command } b** " , :Tab
tmux . until do | lines |
lines . item_count == 2 && lines . match_count == 1 &&
lines . any_include? ( " prompt- #{ command } " ) &&
lines . any_include? ( " preview- #{ command } -bar " )
end
tmux . send_keys :Enter
tmux . until { | lines | lines [ - 1 ] . include? ( " #{ command } #{ command } barbar " ) }
tmux . send_keys 'C-u'
end
ensure
tmux . prepare
tmux . send_keys 'unset -f _fzf_comprun' , :Enter
end
end
class TestBash < TestBase
include TestShell
include CompletionTest
def shell
:bash
end
def new_shell
tmux . prepare
tmux . send_keys " FZF_TMUX=1 #{ Shell . bash } " , :Enter
tmux . prepare
end
def test_dynamic_completion_loader
tmux . paste 'touch /tmp/foo; _fzf_completion_loader=1'
tmux . paste '_completion_loader() { complete -o default fake; }'
tmux . paste 'complete -F _fzf_path_completion -o default -o bashdefault fake'
tmux . send_keys 'fake /tmp/foo**' , :Tab
tmux . until { | lines | lines . match_count . positive? }
tmux . send_keys 'C-c'
tmux . prepare
tmux . send_keys 'fake /tmp/foo'
tmux . send_keys :Tab , 'C-u'
tmux . prepare
tmux . send_keys 'fake /tmp/foo**' , :Tab
tmux . until { | lines | lines . match_count . positive? }
end
end
class TestZsh < TestBase
include TestShell
include CompletionTest
def shell
:zsh
end
def new_shell
tmux . send_keys " FZF_TMUX=1 #{ Shell . zsh } " , :Enter
tmux . prepare
end
end
class TestFish < TestBase
include TestShell
def shell
:fish
end
def new_shell
tmux . send_keys 'env FZF_TMUX=1 fish' , :Enter
tmux . send_keys 'function fish_prompt; end; clear' , :Enter
tmux . until ( & :empty? )
end
def set_var ( name , val )
tmux . prepare
tmux . send_keys " set -g #{ name } ' #{ val } ' " , :Enter
tmux . prepare
end
end
__END__
# Setup fzf
# ---------
if [ [ ! " $PATH " == * < %= BASE % > / bin* ]]; then
export PATH = " ${PATH:+${PATH}:}<%= BASE %>/bin "
fi
# Auto-completion
# ---------------
[ [ $- == * i * ] ] && source " <%= BASE %>/shell/completion.<%= __method__ %> " 2 > / dev /nu ll
# Key bindings
# ------------
source " <%= BASE %>/shell/key-bindings.<%= __method__ %> "
PS1 = PROMPT_COMMAND = HISTFILE = HISTSIZE = 100
unset < %= UNSETS . join ( ' ' ) % >
# Old API
_fzf_complete_f ( ) {
_fzf_complete " +m --multi --prompt \" prompt-f> \" " " $@ " < < (
echo foo
echo bar
)
}
# New API
_fzf_complete_g ( ) {
_fzf_complete + m - - multi - - prompt " prompt-g> " - - " $@ " < < (
echo foo
echo bar
)
}
_fzf_complete_f_post ( ) {
awk '{print "f" $0 $0}'
}
_fzf_complete_g_post ( ) {
awk '{print "g" $0 $0}'
}
[ - n " $BASH " ] && complete - F _fzf_complete_f - o default - o bashdefault f
[ - n " $BASH " ] && complete - F _fzf_complete_g - o default - o bashdefault g
_comprun ( ) {
local command = $1
shift
case " $command " in
f ) fzf " $@ " - - preview 'echo preview-f-{}' ; ;
g ) fzf " $@ " - - preview 'echo preview-g-{}' ; ;
* ) fzf " $@ " ; ;
esac
}