chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
# Unset fzf variables
|
||||
set -e FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS FZF_DEFAULT_OPTS_FILE FZF_TMUX FZF_TMUX_OPTS
|
||||
set -e FZF_CTRL_T_COMMAND FZF_CTRL_T_OPTS FZF_ALT_C_COMMAND FZF_ALT_C_OPTS FZF_CTRL_R_OPTS
|
||||
set -e FZF_API_KEY
|
||||
# Unset completion-specific variables
|
||||
set -e FZF_COMPLETION_OPTS FZF_EXPANSION_OPTS
|
||||
|
||||
set -gx FZF_DEFAULT_OPTS "--no-scrollbar --pointer '>' --marker '>'"
|
||||
set -gx fish_history fzf_test
|
||||
|
||||
# Add fzf to PATH
|
||||
fish_add_path <%= BASE %>/bin
|
||||
|
||||
# Source key bindings and completion
|
||||
source "<%= BASE %>/shell/key-bindings.fish"
|
||||
source "<%= BASE %>/shell/completion.fish"
|
||||
@@ -0,0 +1,398 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'bundler/setup'
|
||||
require 'minitest/autorun'
|
||||
require 'fileutils'
|
||||
require 'English'
|
||||
require 'shellwords'
|
||||
require 'erb'
|
||||
require 'tempfile'
|
||||
require 'net/http'
|
||||
require 'json'
|
||||
|
||||
TEMPLATE = File.read(File.expand_path('common.sh', __dir__))
|
||||
FISH_TEMPLATE = File.read(File.expand_path('common.fish', __dir__))
|
||||
UNSETS = %w[
|
||||
FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS
|
||||
FZF_TMUX FZF_TMUX_OPTS
|
||||
FZF_CTRL_T_COMMAND FZF_CTRL_T_OPTS
|
||||
FZF_ALT_C_COMMAND
|
||||
FZF_ALT_C_OPTS FZF_CTRL_R_OPTS
|
||||
FZF_API_KEY
|
||||
].freeze
|
||||
DEFAULT_TIMEOUT = 10
|
||||
|
||||
FILE = File.expand_path(__FILE__)
|
||||
BASE = File.expand_path('../..', __dir__)
|
||||
Dir.chdir(BASE)
|
||||
FZF = %(FZF_DEFAULT_OPTS="--no-scrollbar --gutter ' ' --pointer '>' --marker '>'" FZF_DEFAULT_COMMAND= #{BASE}/bin/fzf).freeze
|
||||
|
||||
def wait(timeout = DEFAULT_TIMEOUT)
|
||||
since = Time.now
|
||||
begin
|
||||
yield or raise Minitest::Assertion, 'Assertion failure'
|
||||
rescue Minitest::Assertion
|
||||
raise if Time.now - since > timeout
|
||||
|
||||
sleep(0.05)
|
||||
retry
|
||||
end
|
||||
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
|
||||
@fish ||=
|
||||
begin
|
||||
confdir = '/tmp/fzf-fish'
|
||||
FileUtils.rm_rf(confdir)
|
||||
FileUtils.mkdir_p("#{confdir}/fish/conf.d")
|
||||
File.open("#{confdir}/fish/conf.d/fzf.fish", 'w') do |f|
|
||||
f.puts ERB.new(FISH_TEMPLATE).result(binding)
|
||||
end
|
||||
"rm -f ~/.local/share/fish/fzf_test_history; XDG_CONFIG_HOME=#{confdir} fish"
|
||||
end
|
||||
end
|
||||
|
||||
def nushell
|
||||
@nushell ||=
|
||||
begin
|
||||
xdg_home = '/tmp/fzf-nushell-xdg'
|
||||
config_dir = "#{xdg_home}/nushell"
|
||||
FileUtils.rm_rf(xdg_home)
|
||||
FileUtils.mkdir_p(config_dir)
|
||||
|
||||
# Write env.nu to set up PATH and unset FZF variables
|
||||
File.open("#{config_dir}/env.nu", 'w') do |f|
|
||||
f.puts "$env.PATH = ($env.PATH | split row (char esep) | prepend '#{BASE}/bin')"
|
||||
UNSETS.each do |var|
|
||||
f.puts "hide-env -i #{var}"
|
||||
end
|
||||
f.puts "$env.FZF_DEFAULT_OPTS = \"--no-scrollbar --pointer '>' --marker '>'\""
|
||||
f.puts '$env.config = ($env.config | upsert history { file_format: "plaintext", max_size: 100 })'
|
||||
end
|
||||
|
||||
# Write config.nu with minimal prompt
|
||||
File.open("#{config_dir}/config.nu", 'w') do |f|
|
||||
f.puts '$env.PROMPT_COMMAND = {|| "" }'
|
||||
f.puts '$env.PROMPT_INDICATOR = ""'
|
||||
f.puts '$env.PROMPT_COMMAND_RIGHT = {|| "" }'
|
||||
f.puts '$env.config = ($env.config | upsert show_banner false)'
|
||||
f.puts "source #{BASE}/shell/key-bindings.nu"
|
||||
f.puts "source #{BASE}/shell/completion.nu"
|
||||
end
|
||||
|
||||
"unset #{UNSETS.join(' ')}; env XDG_CONFIG_HOME=#{xdg_home} XDG_DATA_HOME=#{xdg_home}/../fzf-nushell-data nu --config #{config_dir}/config.nu --env-config #{config_dir}/env.nu"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Tmux
|
||||
attr_reader :win
|
||||
|
||||
def initialize(shell = :bash)
|
||||
@shell = shell
|
||||
@win = go(%W[new-window -d -P -F #I #{Shell.send(shell)}]).first
|
||||
go(%W[set-window-option -t #{@win} pane-base-index 0])
|
||||
if shell == :fish
|
||||
send_keys 'function fish_prompt; end; clear', :Enter
|
||||
self.until(&:empty?)
|
||||
elsif shell == :nushell
|
||||
# Clear history from previous tests to avoid contamination
|
||||
FileUtils.rm_f('/tmp/fzf-nushell-xdg/nushell/history.txt')
|
||||
# Wait for nushell to be ready by polling with a marker command.
|
||||
# We use 'print "fzf-ready"' and check for a line that is exactly
|
||||
# 'fzf-ready' (not the command echo which includes 'print').
|
||||
retries = 0
|
||||
begin
|
||||
send_keys 'print "fzf-ready"', :Enter
|
||||
self.until { |lines| lines.any? { |l| l.strip == 'fzf-ready' } }
|
||||
rescue Minitest::Assertion
|
||||
retries += 1
|
||||
raise if retries > 5
|
||||
|
||||
retry
|
||||
end
|
||||
send_keys 'clear', :Enter
|
||||
self.until(&:empty?)
|
||||
end
|
||||
end
|
||||
|
||||
def kill
|
||||
go(%W[kill-window -t #{win}])
|
||||
end
|
||||
|
||||
def focus
|
||||
go(%W[select-window -t #{win}])
|
||||
end
|
||||
|
||||
def send_keys(*args)
|
||||
go(%W[send-keys -t #{win}] + args.map(&:to_s))
|
||||
end
|
||||
|
||||
# Simulate a mouse click at the given 1-based column and row using the SGR mouse protocol
|
||||
# (xterm mouse mode 1006, which fzf enables). The escape sequence is injected as literal
|
||||
# keystrokes via tmux, and fzf parses it like a real terminal mouse event.
|
||||
#
|
||||
# tmux's own mouse handling intercepts these sequences when `set -g mouse on`, so we toggle
|
||||
# mouse off for the duration of the click and restore the previous state afterwards.
|
||||
def click(col, row, button: 0)
|
||||
prev = go(%w[show-options -gv mouse]).first
|
||||
go(%w[set-option -g mouse off])
|
||||
begin
|
||||
seq = "\e[<#{button};#{col};#{row}M\e[<#{button};#{col};#{row}m"
|
||||
go(%W[send-keys -t #{win} -l #{seq}])
|
||||
ensure
|
||||
go(%W[set-option -g mouse #{prev}]) if prev && !prev.empty?
|
||||
end
|
||||
end
|
||||
|
||||
def paste(str)
|
||||
system('tmux', 'setb', str, ';', 'pasteb', '-t', win, ';', 'send-keys', '-t', win, 'Enter')
|
||||
end
|
||||
|
||||
# Paste with bracketed paste control codes so fzf sees
|
||||
# bracketed-paste-begin/end around the content
|
||||
def paste_bracketed(str)
|
||||
system('tmux', 'setb', str, ';', 'pasteb', '-p', '-t', win)
|
||||
end
|
||||
|
||||
def capture
|
||||
go(%W[capture-pane -p -J -t #{win}]).map(&:rstrip).reverse.drop_while(&:empty?).reverse
|
||||
end
|
||||
|
||||
# Raw pane capture with ANSI escape sequences preserved.
|
||||
def capture_ansi
|
||||
go(%W[capture-pane -p -J -e -t #{win}])
|
||||
end
|
||||
|
||||
# 3-bit ANSI bg code (40..47) -> color name used in --color options.
|
||||
BG_NAMES = %w[black red green yellow blue magenta cyan white].freeze
|
||||
|
||||
# Parse `tmux capture-pane -e` output into per-row bg ranges. Each row is an
|
||||
# array of [col_start, col_end, bg] tuples where bg is one of:
|
||||
# 'default'
|
||||
# 'red' / 'green' / 'blue' / ... (3-bit names)
|
||||
# 'bright-red' / ... (bright variants)
|
||||
# '256:<n>' (256-color fallback)
|
||||
# ANSI state persists across rows, matching real terminal behavior.
|
||||
def bg_ranges
|
||||
raw = go(%W[capture-pane -p -J -e -t #{win}])
|
||||
bg = 'default'
|
||||
raw.map do |row|
|
||||
cells = []
|
||||
i = 0
|
||||
len = row.length
|
||||
while i < len
|
||||
c = row[i]
|
||||
if c == "\e" && row[i + 1] == '['
|
||||
j = i + 2
|
||||
j += 1 while j < len && row[j] != 'm'
|
||||
parts = row[i + 2...j].split(';')
|
||||
k = 0
|
||||
while k < parts.length
|
||||
p = parts[k].to_i
|
||||
case p
|
||||
when 0, 49 then bg = 'default'
|
||||
when 40..47 then bg = BG_NAMES[p - 40]
|
||||
when 100..107 then bg = "bright-#{BG_NAMES[p - 100]}"
|
||||
when 48
|
||||
if parts[k + 1] == '5'
|
||||
bg = "256:#{parts[k + 2]}"
|
||||
k += 2
|
||||
elsif parts[k + 1] == '2'
|
||||
bg = "rgb:#{parts[k + 2]}:#{parts[k + 3]}:#{parts[k + 4]}"
|
||||
k += 4
|
||||
end
|
||||
end
|
||||
k += 1
|
||||
end
|
||||
i = j + 1
|
||||
else
|
||||
cells << bg
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
ranges = []
|
||||
start = 0
|
||||
cells.each_with_index do |b, idx|
|
||||
if idx.positive? && b != cells[idx - 1]
|
||||
ranges << [start, idx - 1, cells[idx - 1]]
|
||||
start = idx
|
||||
end
|
||||
end
|
||||
ranges << [start, cells.length - 1, cells.last] unless cells.empty?
|
||||
ranges
|
||||
end
|
||||
end
|
||||
|
||||
def until(refresh = false, timeout: DEFAULT_TIMEOUT)
|
||||
lines = nil
|
||||
begin
|
||||
wait(timeout) do
|
||||
lines = capture
|
||||
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?
|
||||
find { |line| line.send(method, val) }
|
||||
end
|
||||
end
|
||||
yield(lines).tap do |ok|
|
||||
send_keys 'C-l' if refresh && !ok
|
||||
end
|
||||
end
|
||||
rescue Minitest::Assertion
|
||||
puts $ERROR_INFO.backtrace
|
||||
puts '>' * 80
|
||||
puts lines
|
||||
puts '<' * 80
|
||||
raise
|
||||
end
|
||||
lines
|
||||
end
|
||||
|
||||
def prepare
|
||||
tries = 0
|
||||
begin
|
||||
if @shell == :nushell
|
||||
message = "Prepare[#{tries}]"
|
||||
send_keys 'C-u', 'C-l'
|
||||
sleep(0.2)
|
||||
send_keys ' ', 'C-u', :Enter, message
|
||||
self.until { |lines| lines[-1] == message }
|
||||
else
|
||||
self.until(true) do |lines|
|
||||
message = "Prepare[#{tries}]"
|
||||
send_keys ' ', 'C-u', :Enter, message, :Left, :Right
|
||||
sleep(0.15)
|
||||
lines[-1] == message
|
||||
end
|
||||
end
|
||||
rescue Minitest::Assertion
|
||||
(tries += 1) < 5 ? retry : raise
|
||||
end
|
||||
send_keys 'C-u', 'C-l'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def go(args)
|
||||
IO.popen(%w[tmux] + args) { |io| io.readlines(chomp: true) }
|
||||
end
|
||||
end
|
||||
|
||||
class TestBase < Minitest::Test
|
||||
TEMPNAME = Dir::Tmpname.create(%w[fzf]) {}
|
||||
FIFONAME = Dir::Tmpname.create(%w[fzf-fifo]) {}
|
||||
|
||||
def writelines(lines)
|
||||
File.write(TEMPNAME, lines.join("\n"))
|
||||
end
|
||||
|
||||
def tempname
|
||||
TEMPNAME
|
||||
end
|
||||
|
||||
def fzf_output
|
||||
@thread.join.value.chomp.tap { @thread = nil }
|
||||
end
|
||||
|
||||
def fzf_output_lines
|
||||
fzf_output.lines(chomp: true)
|
||||
end
|
||||
|
||||
def setup
|
||||
File.mkfifo(FIFONAME)
|
||||
end
|
||||
|
||||
def teardown
|
||||
FileUtils.rm_f([TEMPNAME, FIFONAME])
|
||||
end
|
||||
|
||||
alias assert_equal_org assert_equal
|
||||
def assert_equal(expected, actual)
|
||||
# Ignore info separator
|
||||
actual = actual&.sub(/\s*─+$/, '') if actual.is_a?(String) && actual&.match?(%r{\d+/\d+})
|
||||
assert_equal_org(expected, actual)
|
||||
end
|
||||
|
||||
# Run fzf with its output piped to a fifo
|
||||
def fzf(*opts)
|
||||
raise 'fzf_output not taken' if @thread
|
||||
|
||||
@thread = Thread.new { File.read(FIFONAME) }
|
||||
fzf!(*opts) + " > #{FIFONAME.shellescape}"
|
||||
end
|
||||
|
||||
def fzf!(*opts)
|
||||
opts = opts.filter_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
|
||||
"#{FZF} #{opts.join(' ')}"
|
||||
end
|
||||
end
|
||||
|
||||
class TestInteractive < TestBase
|
||||
attr_reader :tmux
|
||||
|
||||
def setup
|
||||
super
|
||||
@tmux = Tmux.new
|
||||
end
|
||||
|
||||
def teardown
|
||||
super
|
||||
@tmux.kill
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,59 @@
|
||||
set -u
|
||||
PS1= PROMPT_COMMAND= HISTFILE= HISTSIZE=100
|
||||
unset <%= UNSETS.join(' ') %>
|
||||
unset $(env | sed -n /^_fzf_orig/s/=.*//p)
|
||||
unset $(declare -F | sed -n "/_fzf/s/.*-f //p")
|
||||
|
||||
export FZF_DEFAULT_OPTS="--no-scrollbar --pointer '>' --marker '>'"
|
||||
|
||||
# Setup fzf
|
||||
# ---------
|
||||
if [[ ! "$PATH" == *<%= BASE %>/bin* ]]; then
|
||||
export PATH="${PATH:+${PATH}:}<%= BASE %>/bin"
|
||||
fi
|
||||
|
||||
# Auto-completion
|
||||
# ---------------
|
||||
[[ $- == *i* ]] && source "<%= BASE %>/shell/completion.<%= __method__ %>" 2> /dev/null
|
||||
|
||||
# Key bindings
|
||||
# ------------
|
||||
source "<%= BASE %>/shell/key-bindings.<%= __method__ %>"
|
||||
|
||||
# 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
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
Dir[File.join(__dir__, 'test_*.rb')].each { require it }
|
||||
|
||||
require 'minitest/autorun'
|
||||
+2957
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,417 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'lib/common'
|
||||
|
||||
# Process execution: execute, become, reload
|
||||
class TestExec < TestInteractive
|
||||
def test_execute
|
||||
output = '/tmp/fzf-test-execute'
|
||||
opts = %[--bind "alt-a:execute(echo /{}/ >> #{output})+change-header(alt-a),alt-b:execute[echo /{}{}/ >> #{output}]+change-header(alt-b),C:execute(echo /{}{}{}/ >> #{output})+change-header(C)"]
|
||||
writelines(%w[foo'bar foo"bar foo$bar])
|
||||
tmux.send_keys "cat #{tempname} | #{FZF} #{opts}", :Enter
|
||||
tmux.until { |lines| assert_equal 3, lines.match_count }
|
||||
|
||||
ready = ->(s) { tmux.until { |lines| assert_includes lines[-3], s } }
|
||||
tmux.send_keys :Escape, :a
|
||||
ready.call('alt-a')
|
||||
tmux.send_keys :Escape, :b
|
||||
ready.call('alt-b')
|
||||
|
||||
tmux.send_keys :Up
|
||||
tmux.send_keys :Escape, :a
|
||||
ready.call('alt-a')
|
||||
tmux.send_keys :Escape, :b
|
||||
ready.call('alt-b')
|
||||
|
||||
tmux.send_keys :Up
|
||||
tmux.send_keys :C
|
||||
ready.call('C')
|
||||
|
||||
tmux.send_keys 'barfoo'
|
||||
tmux.until { |lines| assert_equal ' 0/3', lines[-2] }
|
||||
|
||||
tmux.send_keys :Escape, :a
|
||||
ready.call('alt-a')
|
||||
tmux.send_keys :Escape, :b
|
||||
ready.call('alt-b')
|
||||
|
||||
wait do
|
||||
assert_path_exists output
|
||||
assert_equal %w[
|
||||
/foo'bar/ /foo'barfoo'bar/
|
||||
/foo"bar/ /foo"barfoo"bar/
|
||||
/foo$barfoo$barfoo$bar/
|
||||
], File.readlines(output, chomp: true)
|
||||
end
|
||||
ensure
|
||||
FileUtils.rm_f(output)
|
||||
end
|
||||
|
||||
def test_execute_multi
|
||||
output = '/tmp/fzf-test-execute-multi'
|
||||
opts = %[--multi --bind "alt-a:execute-multi(echo {}/{+} >> #{output})+change-header(alt-a),alt-b:change-header(alt-b)"]
|
||||
writelines(%w[foo'bar foo"bar foo$bar foobar])
|
||||
tmux.send_keys "cat #{tempname} | #{FZF} #{opts}", :Enter
|
||||
ready = ->(s) { tmux.until { |lines| assert_includes lines[-3], s } }
|
||||
|
||||
tmux.until { |lines| assert_equal ' 4/4 (0)', lines[-2] }
|
||||
tmux.send_keys :Escape, :a
|
||||
ready.call('alt-a')
|
||||
tmux.send_keys :Escape, :b
|
||||
ready.call('alt-b')
|
||||
|
||||
tmux.send_keys :BTab, :BTab, :BTab
|
||||
tmux.until { |lines| assert_equal ' 4/4 (3)', lines[-2] }
|
||||
tmux.send_keys :Escape, :a
|
||||
ready.call('alt-a')
|
||||
tmux.send_keys :Escape, :b
|
||||
ready.call('alt-b')
|
||||
|
||||
tmux.send_keys :Tab, :Tab
|
||||
tmux.until { |lines| assert_equal ' 4/4 (3)', lines[-2] }
|
||||
tmux.send_keys :Escape, :a
|
||||
ready.call('alt-a')
|
||||
wait do
|
||||
assert_path_exists output
|
||||
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, chomp: true)
|
||||
end
|
||||
ensure
|
||||
FileUtils.rm_f(output)
|
||||
end
|
||||
|
||||
def test_execute_plus_flag
|
||||
output = tempname + '.tmp'
|
||||
FileUtils.rm_f(output)
|
||||
writelines(['foo bar', '123 456'])
|
||||
|
||||
tmux.send_keys "cat #{tempname} | #{FZF} --multi --bind 'x:execute-silent(echo {+}/{}/{+2}/{2} >> #{output})'", :Enter
|
||||
|
||||
tmux.until { |lines| assert_equal ' 2/2 (0)', lines[-2] }
|
||||
tmux.send_keys 'xy'
|
||||
tmux.until { |lines| assert_equal ' 0/2 (0)', lines[-2] }
|
||||
tmux.send_keys :BSpace
|
||||
tmux.until { |lines| assert_equal ' 2/2 (0)', lines[-2] }
|
||||
|
||||
tmux.send_keys :Up
|
||||
tmux.send_keys :Tab
|
||||
tmux.send_keys 'xy'
|
||||
tmux.until { |lines| assert_equal ' 0/2 (1)', lines[-2] }
|
||||
tmux.send_keys :BSpace
|
||||
tmux.until { |lines| assert_equal ' 2/2 (1)', lines[-2] }
|
||||
|
||||
tmux.send_keys :Tab
|
||||
tmux.send_keys 'xy'
|
||||
tmux.until { |lines| assert_equal ' 0/2 (2)', lines[-2] }
|
||||
tmux.send_keys :BSpace
|
||||
tmux.until { |lines| assert_equal ' 2/2 (2)', lines[-2] }
|
||||
|
||||
wait do
|
||||
assert_path_exists output
|
||||
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, chomp: true)
|
||||
end
|
||||
rescue StandardError
|
||||
FileUtils.rm_f(output)
|
||||
end
|
||||
|
||||
def test_execute_shell
|
||||
# Custom script to use as $SHELL
|
||||
output = tempname + '.out'
|
||||
FileUtils.rm_f(output)
|
||||
writelines(['#!/usr/bin/env bash', "echo $1 / $2 > #{output}"])
|
||||
system("chmod +x #{tempname}")
|
||||
|
||||
tmux.send_keys "echo foo | SHELL=#{tempname} fzf --bind 'enter:execute:{}bar'", :Enter
|
||||
tmux.until { |lines| assert_equal ' 1/1', lines[-2] }
|
||||
tmux.send_keys :Enter
|
||||
tmux.until { |lines| assert_equal ' 1/1', lines[-2] }
|
||||
wait do
|
||||
assert_path_exists output
|
||||
assert_equal ["-c / 'foo'bar"], File.readlines(output, chomp: true)
|
||||
end
|
||||
ensure
|
||||
FileUtils.rm_f(output)
|
||||
end
|
||||
|
||||
def test_interrupt_execute
|
||||
tmux.send_keys "seq 100 | #{FZF} --bind 'ctrl-l:execute:echo executing {}; sleep 100'", :Enter
|
||||
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||
tmux.send_keys 'C-l'
|
||||
tmux.until { |lines| assert lines.any_include?('executing 1') }
|
||||
tmux.send_keys 'C-c'
|
||||
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||
tmux.send_keys 99
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
end
|
||||
|
||||
def test_kill_default_command_on_abort
|
||||
writelines(['#!/usr/bin/env bash',
|
||||
"echo 'Started'",
|
||||
'while :; do sleep 1; done'])
|
||||
system("chmod +x #{tempname}")
|
||||
|
||||
tmux.send_keys FZF.sub('FZF_DEFAULT_COMMAND=', "FZF_DEFAULT_COMMAND=#{tempname}"), :Enter
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
tmux.send_keys 'C-c'
|
||||
tmux.send_keys 'C-l', 'closed'
|
||||
tmux.until { |lines| assert_includes lines[0], 'closed' }
|
||||
wait { refute system("pgrep -f #{tempname}") }
|
||||
ensure
|
||||
system("pkill -9 -f #{tempname}")
|
||||
end
|
||||
|
||||
def test_kill_default_command_on_accept
|
||||
writelines(['#!/usr/bin/env bash',
|
||||
"echo 'Started'",
|
||||
'while :; do sleep 1; done'])
|
||||
system("chmod +x #{tempname}")
|
||||
|
||||
tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', "FZF_DEFAULT_COMMAND=#{tempname}"), :Enter
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
tmux.send_keys :Enter
|
||||
assert_equal 'Started', fzf_output
|
||||
wait { refute system("pgrep -f #{tempname}") }
|
||||
ensure
|
||||
system("pkill -9 -f #{tempname}")
|
||||
end
|
||||
|
||||
def test_kill_reload_command_on_abort
|
||||
writelines(['#!/usr/bin/env bash',
|
||||
"echo 'Started'",
|
||||
'while :; do sleep 1; done'])
|
||||
system("chmod +x #{tempname}")
|
||||
|
||||
tmux.send_keys "seq 1 3 | #{FZF} --bind 'ctrl-r:reload(#{tempname})'", :Enter
|
||||
tmux.until { |lines| assert_equal 3, lines.match_count }
|
||||
tmux.send_keys 'C-r'
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
tmux.send_keys 'C-c'
|
||||
tmux.send_keys 'C-l', 'closed'
|
||||
tmux.until { |lines| assert_includes lines[0], 'closed' }
|
||||
wait { refute system("pgrep -f #{tempname}") }
|
||||
ensure
|
||||
system("pkill -9 -f #{tempname}")
|
||||
end
|
||||
|
||||
def test_kill_reload_command_on_accept
|
||||
writelines(['#!/usr/bin/env bash',
|
||||
"echo 'Started'",
|
||||
'while :; do sleep 1; done'])
|
||||
system("chmod +x #{tempname}")
|
||||
|
||||
tmux.send_keys "seq 1 3 | #{fzf("--bind 'ctrl-r:reload(#{tempname})'")}", :Enter
|
||||
tmux.until { |lines| assert_equal 3, lines.match_count }
|
||||
tmux.send_keys 'C-r'
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
tmux.send_keys :Enter
|
||||
assert_equal 'Started', fzf_output
|
||||
wait { refute system("pgrep -f #{tempname}") }
|
||||
ensure
|
||||
system("pkill -9 -f #{tempname}")
|
||||
end
|
||||
|
||||
def test_reload
|
||||
tmux.send_keys %(seq 1000 | #{FZF} --bind 'change:reload(seq $FZF_QUERY),a:reload(seq 100),b:reload:seq 200' --header-lines 2 --multi 2), :Enter
|
||||
tmux.until { |lines| assert_equal 998, lines.match_count }
|
||||
tmux.send_keys 'a'
|
||||
tmux.until do |lines|
|
||||
assert_equal 98, lines.item_count
|
||||
assert_equal 98, lines.match_count
|
||||
end
|
||||
tmux.send_keys 'b'
|
||||
tmux.until do |lines|
|
||||
assert_equal 198, lines.item_count
|
||||
assert_equal 198, lines.match_count
|
||||
end
|
||||
tmux.send_keys :Tab
|
||||
tmux.until { |lines| assert_equal ' 198/198 (1/2)', lines[-2] }
|
||||
tmux.send_keys '555'
|
||||
tmux.until { |lines| assert_equal ' 1/553 (0/2)', lines[-2] }
|
||||
end
|
||||
|
||||
def test_reload_even_when_theres_no_match
|
||||
tmux.send_keys %(: | #{FZF} --bind 'space:reload(seq 10)'), :Enter
|
||||
tmux.until { |lines| assert_equal 0, lines.item_count }
|
||||
tmux.send_keys :Space
|
||||
tmux.until { |lines| assert_equal 10, lines.item_count }
|
||||
end
|
||||
|
||||
def test_reload_should_terminate_standard_input_stream
|
||||
tmux.send_keys %(ruby -e "STDOUT.sync = true; loop { puts 1; sleep 0.1 }" | fzf --bind 'start:reload(seq 100)'), :Enter
|
||||
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||
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| assert_includes lines, ' 9' }
|
||||
tmux.send_keys :Space
|
||||
tmux.until { |lines| refute_includes lines, ' 9' }
|
||||
end
|
||||
|
||||
def test_item_index_reset_on_reload
|
||||
tmux.send_keys "seq 10 | #{FZF} --preview 'echo [[{n}]]' --bind 'up:last,down:first,space:reload:seq 100'", :Enter
|
||||
tmux.until { |lines| assert_includes lines[1], '[[0]]' }
|
||||
tmux.send_keys :Up
|
||||
tmux.until { |lines| assert_includes lines[1], '[[9]]' }
|
||||
tmux.send_keys :Down
|
||||
tmux.until { |lines| assert_includes lines[1], '[[0]]' }
|
||||
tmux.send_keys :Space
|
||||
tmux.until do |lines|
|
||||
assert_equal 100, lines.match_count
|
||||
assert_includes lines[1], '[[0]]'
|
||||
end
|
||||
tmux.send_keys :Up
|
||||
tmux.until { |lines| assert_includes lines[1], '[[99]]' }
|
||||
end
|
||||
|
||||
def test_reload_should_update_preview
|
||||
tmux.send_keys "seq 3 | #{FZF} --bind 'ctrl-t:reload:echo 4' --preview 'echo {}' --preview-window 'nohidden'", :Enter
|
||||
tmux.until { |lines| assert_includes lines[1], '1' }
|
||||
tmux.send_keys 'C-t'
|
||||
tmux.until { |lines| assert_includes lines[1], '4' }
|
||||
end
|
||||
|
||||
def test_reload_and_change_preview_should_update_preview
|
||||
tmux.send_keys "seq 3 | #{FZF} --bind 'ctrl-t:reload(echo 4)+change-preview(echo {})'", :Enter
|
||||
tmux.until { |lines| assert_equal 3, lines.match_count }
|
||||
tmux.until { |lines| refute_includes lines[1], '1' }
|
||||
tmux.send_keys 'C-t'
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
tmux.until { |lines| assert_includes lines[1], '4' }
|
||||
end
|
||||
|
||||
def test_reload_sync
|
||||
tmux.send_keys "seq 100 | #{FZF} --bind 'load:reload-sync(sleep 1; seq 1000)+unbind(load)'", :Enter
|
||||
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||
tmux.send_keys '00'
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
# After 1 second
|
||||
tmux.until { |lines| assert_equal 10, lines.match_count }
|
||||
end
|
||||
|
||||
def test_reload_disabled_case1
|
||||
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload(sleep 2; seq 1000)'", :Enter
|
||||
tmux.until do |lines|
|
||||
assert_equal 100, lines.item_count
|
||||
assert_equal 1, lines.match_count
|
||||
end
|
||||
tmux.send_keys :Space
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
tmux.send_keys :BSpace
|
||||
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||
end
|
||||
|
||||
def test_reload_disabled_case2
|
||||
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload-sync(sleep 2; seq 1000)'", :Enter
|
||||
tmux.until do |lines|
|
||||
assert_equal 100, lines.item_count
|
||||
assert_equal 1, lines.match_count
|
||||
end
|
||||
tmux.send_keys :Space
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
tmux.send_keys :BSpace
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||
end
|
||||
|
||||
def test_reload_disabled_case3
|
||||
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload(sleep 2; seq 1000)+backward-delete-char'", :Enter
|
||||
tmux.until do |lines|
|
||||
assert_equal 100, lines.item_count
|
||||
assert_equal 1, lines.match_count
|
||||
end
|
||||
tmux.send_keys :Space
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
tmux.send_keys :BSpace
|
||||
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||
end
|
||||
|
||||
def test_reload_disabled_case4
|
||||
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload-sync(sleep 2; seq 1000)+backward-delete-char'", :Enter
|
||||
tmux.until do |lines|
|
||||
assert_equal 100, lines.item_count
|
||||
assert_equal 1, lines.match_count
|
||||
end
|
||||
tmux.send_keys :Space
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
tmux.send_keys :BSpace
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||
end
|
||||
|
||||
def test_reload_disabled_case5
|
||||
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload(echo xx; sleep 2; seq 1000)'", :Enter
|
||||
tmux.until do |lines|
|
||||
assert_equal 100, lines.item_count
|
||||
assert_equal 1, lines.match_count
|
||||
end
|
||||
tmux.send_keys :Space
|
||||
tmux.until do |lines|
|
||||
assert_equal 1, lines.item_count
|
||||
assert_equal 1, lines.match_count
|
||||
end
|
||||
tmux.send_keys :BSpace
|
||||
tmux.until { |lines| assert_equal 1001, lines.match_count }
|
||||
end
|
||||
|
||||
def test_reload_disabled_case6
|
||||
tmux.send_keys "seq 1000 | #{FZF} --disabled --bind 'change:reload:sleep 0.5; seq {q}'", :Enter
|
||||
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||
tmux.send_keys '9'
|
||||
tmux.until { |lines| assert_equal 9, lines.match_count }
|
||||
tmux.send_keys '9'
|
||||
tmux.until { |lines| assert_equal 99, lines.match_count }
|
||||
|
||||
# TODO: How do we verify if an intermediate empty list is not shown?
|
||||
end
|
||||
|
||||
def test_reload_and_change
|
||||
tmux.send_keys "(echo foo; echo bar) | #{FZF} --bind 'load:reload-sync(sleep 60)+change-query(bar)'", :Enter
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
end
|
||||
|
||||
def test_become_tty
|
||||
tmux.send_keys "sleep 0.5 | #{FZF} --bind 'start:reload:ls' --bind 'load:become:tty'", :Enter
|
||||
tmux.until { |lines| assert_includes lines, '/dev/tty' }
|
||||
end
|
||||
|
||||
def test_disabled_preview_update
|
||||
tmux.send_keys "echo bar | #{FZF} --disabled --bind 'change:reload:echo foo' --preview 'echo [{q}-{}]'", :Enter
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
tmux.until { |lines| assert(lines.any? { |line| line.include?('[-bar]') }) }
|
||||
tmux.send_keys :x
|
||||
tmux.until { |lines| assert(lines.any? { |line| line.include?('[x-foo]') }) }
|
||||
end
|
||||
|
||||
def test_start_on_reload
|
||||
tmux.send_keys %(echo foo | #{FZF} --header Loading --header-lines 1 --bind 'start:reload:sleep 2; echo bar' --bind 'load:change-header:Loaded' --bind space:change-header:), :Enter
|
||||
tmux.until(timeout: 1) { |lines| assert_includes lines[-3], 'Loading' }
|
||||
tmux.until(timeout: 1) { |lines| refute_includes lines[-4], 'foo' }
|
||||
tmux.until { |lines| assert_includes lines[-3], 'Loaded' }
|
||||
tmux.until { |lines| assert_includes lines[-4], 'bar' }
|
||||
tmux.send_keys :Space
|
||||
tmux.until { |lines| assert_includes lines[-3], 'bar' }
|
||||
end
|
||||
|
||||
def test_become
|
||||
tmux.send_keys "seq 100 | fzf --bind 'enter:become:seq {} | fzf'", :Enter
|
||||
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||
tmux.send_keys 999
|
||||
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||
tmux.send_keys :Enter
|
||||
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||
tmux.send_keys :BSpace
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
tmux.send_keys :Enter
|
||||
tmux.until { |lines| assert_equal 99, lines.item_count }
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,352 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'lib/common'
|
||||
|
||||
# Non-interactive tests
|
||||
class TestFilter < TestBase
|
||||
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(chomp: true)
|
||||
assert_equal %w[1 10 2 3 4 5 6 7 8 9],
|
||||
`seq 10 | #{FZF} -f '1 | !1'`.lines(chomp: true)
|
||||
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_filter_exitstatus
|
||||
# filter / streaming filter
|
||||
['', '--no-sort'].each do |opts|
|
||||
assert_includes `echo foo | #{FZF} -f foo #{opts}`, 'foo'
|
||||
assert_equal 0, $CHILD_STATUS.exitstatus
|
||||
|
||||
assert_empty `echo foo | #{FZF} -f bar #{opts}`
|
||||
assert_equal 1, $CHILD_STATUS.exitstatus
|
||||
end
|
||||
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 .`.lines(chomp: true)
|
||||
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_nth_suffix_match
|
||||
assert_equal \
|
||||
'foo,bar,baz',
|
||||
`echo foo,bar,baz | #{FZF} -d, -f'bar$' -n2`.chomp
|
||||
end
|
||||
|
||||
def test_with_nth_basic
|
||||
writelines(['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_template
|
||||
writelines(['hello world ', 'byebye'])
|
||||
assert_equal \
|
||||
'hello world ',
|
||||
`#{FZF} -f"^he he.he." -x -n 2.. --with-nth '{2} {1}. {1}.' < #{tempname}`.chomp
|
||||
end
|
||||
|
||||
def test_with_nth_ansi
|
||||
writelines(["\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([src, 'byebye'])
|
||||
assert_equal \
|
||||
src,
|
||||
`#{FZF} -fhehe -x -n 2.. --with-nth 2,1,1 --no-ansi < #{tempname}`.chomp
|
||||
end
|
||||
|
||||
def test_escaped_meta_characters
|
||||
input = [
|
||||
'foo^bar',
|
||||
'foo$bar',
|
||||
'foo!bar',
|
||||
"foo'bar",
|
||||
'foo bar',
|
||||
'bar foo'
|
||||
]
|
||||
writelines(input)
|
||||
|
||||
assert_equal input.length, `#{FZF} -f'foo bar' < #{tempname}`.lines.length
|
||||
assert_equal input.length - 1, `#{FZF} -f'^foo bar$' < #{tempname}`.lines.length
|
||||
assert_equal ['foo bar'], `#{FZF} -f'foo\\ bar' < #{tempname}`.lines(chomp: true)
|
||||
assert_equal ['foo bar'], `#{FZF} -f'^foo\\ bar$' < #{tempname}`.lines(chomp: true)
|
||||
assert_equal input.length - 1, `#{FZF} -f'!^foo\\ bar$' < #{tempname}`.lines.length
|
||||
end
|
||||
|
||||
def test_normalized_match
|
||||
echoes = '(echo a; echo á; echo A; echo Á;)'
|
||||
assert_equal %w[a á A Á], `#{echoes} | #{FZF} -f a`.lines.map(&:chomp)
|
||||
assert_equal %w[á Á], `#{echoes} | #{FZF} -f á`.lines.map(&:chomp)
|
||||
assert_equal %w[A Á], `#{echoes} | #{FZF} -f A`.lines.map(&:chomp)
|
||||
assert_equal %w[Á], `#{echoes} | #{FZF} -f Á`.lines.map(&:chomp)
|
||||
end
|
||||
|
||||
def test_unicode_case
|
||||
writelines(%w[строКА1 СТРОКА2 строка3 Строка4])
|
||||
assert_equal %w[СТРОКА2 Строка4], `#{FZF} -fС < #{tempname}`.lines(chomp: true)
|
||||
assert_equal %w[строКА1 СТРОКА2 строка3 Строка4], `#{FZF} -fс < #{tempname}`.lines(chomp: true)
|
||||
end
|
||||
|
||||
def test_tiebreak
|
||||
input = %w[
|
||||
--foobar--------
|
||||
-----foobar---
|
||||
----foobar--
|
||||
-------foobar-
|
||||
]
|
||||
writelines(input)
|
||||
|
||||
assert_equal input, `#{FZF} -ffoobar --tiebreak=index < #{tempname}`.lines(chomp: true)
|
||||
|
||||
by_length = %w[
|
||||
----foobar--
|
||||
-----foobar---
|
||||
-------foobar-
|
||||
--foobar--------
|
||||
]
|
||||
assert_equal by_length, `#{FZF} -ffoobar < #{tempname}`.lines(chomp: true)
|
||||
assert_equal by_length, `#{FZF} -ffoobar --tiebreak=length < #{tempname}`.lines(chomp: true)
|
||||
|
||||
by_begin = %w[
|
||||
--foobar--------
|
||||
----foobar--
|
||||
-----foobar---
|
||||
-------foobar-
|
||||
]
|
||||
assert_equal by_begin, `#{FZF} -ffoobar --tiebreak=begin < #{tempname}`.lines(chomp: true)
|
||||
assert_equal by_begin, `#{FZF} -f"!z foobar" -x --tiebreak begin < #{tempname}`.lines(chomp: true)
|
||||
|
||||
assert_equal %w[
|
||||
-------foobar-
|
||||
----foobar--
|
||||
-----foobar---
|
||||
--foobar--------
|
||||
], `#{FZF} -ffoobar --tiebreak end < #{tempname}`.lines(chomp: true)
|
||||
|
||||
assert_equal input, `#{FZF} -f"!z" -x --tiebreak end < #{tempname}`.lines(chomp: true)
|
||||
end
|
||||
|
||||
def test_tiebreak_index_begin
|
||||
writelines([
|
||||
'xoxxxxxoxx',
|
||||
'xoxxxxxox',
|
||||
'xxoxxxoxx',
|
||||
'xxxoxoxxx',
|
||||
'xxxxoxox',
|
||||
' xxoxoxxx'
|
||||
])
|
||||
|
||||
assert_equal [
|
||||
'xxxxoxox',
|
||||
' xxoxoxxx',
|
||||
'xxxoxoxxx',
|
||||
'xxoxxxoxx',
|
||||
'xoxxxxxox',
|
||||
'xoxxxxxoxx'
|
||||
], `#{FZF} -foo < #{tempname}`.lines(chomp: true)
|
||||
|
||||
assert_equal [
|
||||
'xxxoxoxxx',
|
||||
'xxxxoxox',
|
||||
' xxoxoxxx',
|
||||
'xxoxxxoxx',
|
||||
'xoxxxxxoxx',
|
||||
'xoxxxxxox'
|
||||
], `#{FZF} -foo --tiebreak=index < #{tempname}`.lines(chomp: true)
|
||||
|
||||
# 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}`.lines(chomp: true)
|
||||
|
||||
assert_equal [
|
||||
' xxoxoxxx',
|
||||
'xxxoxoxxx',
|
||||
'xxxxoxox',
|
||||
'xxoxxxoxx',
|
||||
'xoxxxxxox',
|
||||
'xoxxxxxoxx'
|
||||
], `#{FZF} -foo --tiebreak=begin,length < #{tempname}`.lines(chomp: true)
|
||||
end
|
||||
|
||||
def test_tiebreak_begin_algo_v2
|
||||
writelines(['baz foo bar',
|
||||
'foo bar baz'])
|
||||
assert_equal [
|
||||
'foo bar baz',
|
||||
'baz foo bar'
|
||||
], `#{FZF} -fbar --tiebreak=begin --algo=v2 < #{tempname}`.lines(chomp: true)
|
||||
end
|
||||
|
||||
def test_tiebreak_end
|
||||
writelines(['xoxxxxxxxx',
|
||||
'xxoxxxxxxx',
|
||||
'xxxoxxxxxx',
|
||||
'xxxxoxxxx',
|
||||
'xxxxxoxxx',
|
||||
' xxxxoxxx'])
|
||||
|
||||
assert_equal [
|
||||
' xxxxoxxx',
|
||||
'xxxxoxxxx',
|
||||
'xxxxxoxxx',
|
||||
'xoxxxxxxxx',
|
||||
'xxoxxxxxxx',
|
||||
'xxxoxxxxxx'
|
||||
], `#{FZF} -fo < #{tempname}`.lines(chomp: true)
|
||||
|
||||
assert_equal [
|
||||
'xxxxxoxxx',
|
||||
' xxxxoxxx',
|
||||
'xxxxoxxxx',
|
||||
'xxxoxxxxxx',
|
||||
'xxoxxxxxxx',
|
||||
'xoxxxxxxxx'
|
||||
], `#{FZF} -fo --tiebreak=end < #{tempname}`.lines(chomp: true)
|
||||
|
||||
assert_equal [
|
||||
'xxxxxoxxx',
|
||||
' xxxxoxxx',
|
||||
'xxxxoxxxx',
|
||||
'xxxoxxxxxx',
|
||||
'xxoxxxxxxx',
|
||||
'xoxxxxxxxx'
|
||||
], `#{FZF} -fo --tiebreak=end,length,begin < #{tempname}`.lines(chomp: true)
|
||||
|
||||
writelines(['/bar/baz', '/foo/bar/baz'])
|
||||
assert_equal [
|
||||
'/foo/bar/baz',
|
||||
'/bar/baz'
|
||||
], `#{FZF} -fbaz --tiebreak=end < #{tempname}`.lines(chomp: true)
|
||||
end
|
||||
|
||||
def test_tiebreak_length_with_nth
|
||||
input = %w[
|
||||
1:hell
|
||||
123:hello
|
||||
12345:he
|
||||
1234567:h
|
||||
]
|
||||
writelines(input)
|
||||
|
||||
output = %w[
|
||||
1:hell
|
||||
12345:he
|
||||
123:hello
|
||||
1234567:h
|
||||
]
|
||||
assert_equal output, `#{FZF} -fh < #{tempname}`.lines(chomp: true)
|
||||
|
||||
# Since 0.16.8, --nth doesn't affect --tiebreak
|
||||
assert_equal output, `#{FZF} -fh -n2 -d: < #{tempname}`.lines(chomp: true)
|
||||
end
|
||||
|
||||
def test_tiebreak_chunk
|
||||
writelines(['1 foobarbaz ba',
|
||||
'2 foobar baz',
|
||||
'3 foo barbaz'])
|
||||
|
||||
assert_equal [
|
||||
'3 foo barbaz',
|
||||
'2 foobar baz',
|
||||
'1 foobarbaz ba'
|
||||
], `#{FZF} -fo --tiebreak=chunk < #{tempname}`.lines(chomp: true)
|
||||
|
||||
assert_equal [
|
||||
'1 foobarbaz ba',
|
||||
'2 foobar baz',
|
||||
'3 foo barbaz'
|
||||
], `#{FZF} -fba --tiebreak=chunk < #{tempname}`.lines(chomp: true)
|
||||
|
||||
assert_equal [
|
||||
'3 foo barbaz'
|
||||
], `#{FZF} -f'!foobar' --tiebreak=chunk < #{tempname}`.lines(chomp: true)
|
||||
end
|
||||
|
||||
def test_boundary_match
|
||||
# Underscore boundaries should be ranked lower
|
||||
{
|
||||
default: [' xyz '] + %w[/xyz/ [xyz] -xyz- -xyz_ _xyz- _xyz_],
|
||||
path: ['/xyz/', ' xyz '] + %w[[xyz] -xyz- -xyz_ _xyz- _xyz_],
|
||||
history: ['[xyz]', '-xyz-', ' xyz '] + %w[/xyz/ -xyz_ _xyz- _xyz_]
|
||||
}.each do |scheme, expected|
|
||||
result = `printf -- 'xxyzx\n-xxyz\nxyzx-\n_xyz_\n_xyz-\n-xyz_\n[xyz]\n-xyz-\n xyz \n/xyz/\n' | #{FZF} -f"'xyz'" --scheme=#{scheme}`.lines(chomp: true)
|
||||
assert_equal expected, result
|
||||
end
|
||||
end
|
||||
|
||||
def test_accept_nth
|
||||
# Single field selection
|
||||
assert_equal 'three', `echo 'one two three' | #{FZF} -d' ' --with-nth 1 --accept-nth -1 -f one`.chomp
|
||||
|
||||
# Multiple field selection
|
||||
writelines(['ID001:John:Developer', 'ID002:Jane:Manager', 'ID003:Bob:Designer'])
|
||||
assert_equal 'ID001', `#{FZF} -d: --with-nth 2 --accept-nth 1 -f John < #{tempname}`.chomp
|
||||
assert_equal 'ID002:Manager', `#{FZF} -d: --with-nth 2 --accept-nth 1,3 -f Jane < #{tempname}`.chomp
|
||||
|
||||
# Test with different delimiters
|
||||
writelines(['emp001 Alice Engineering', 'emp002 Bob Marketing'])
|
||||
assert_equal 'emp001', `#{FZF} -d' ' --with-nth 2 --accept-nth 1 -f Alice < #{tempname}`.chomp
|
||||
end
|
||||
|
||||
def test_header_lines_filter
|
||||
assert_equal %w[4 5 6 7 8 9 10],
|
||||
`seq 10 | #{FZF} --header-lines 3 -f ""`.lines(chomp: true)
|
||||
assert_equal %w[5],
|
||||
`seq 10 | #{FZF} --header-lines 3 -f 5`.lines(chomp: true)
|
||||
# Header items should not be matched
|
||||
assert_empty `seq 10 | #{FZF} --header-lines 3 -f "^1$"`.lines(chomp: true)
|
||||
end
|
||||
|
||||
def test_header_lines_filter_with_nth
|
||||
writelines(%w[a:1 b:2 c:3 d:4 e:5])
|
||||
assert_equal %w[c:3 d:4 e:5],
|
||||
`#{FZF} --header-lines 2 -d: --with-nth 2 -f "" < #{tempname}`.lines(chomp: true)
|
||||
assert_equal %w[d:4],
|
||||
`#{FZF} --header-lines 2 -d: --with-nth 2 -f 4 < #{tempname}`.lines(chomp: true)
|
||||
end
|
||||
|
||||
def test_header_lines_all_headers
|
||||
# When all lines are header lines, no results
|
||||
assert_empty `seq 3 | #{FZF} --header-lines 10 -f ""`.chomp
|
||||
assert_equal 1, $CHILD_STATUS.exitstatus
|
||||
end
|
||||
end
|
||||
+1749
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,685 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'lib/common'
|
||||
|
||||
# Test cases for preview
|
||||
class TestPreview < TestInteractive
|
||||
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| assert_includes lines[1], ' {1-1} ' }
|
||||
tmux.send_keys :Up
|
||||
tmux.until { |lines| assert_includes lines[1], ' {-} ' }
|
||||
tmux.send_keys '555'
|
||||
tmux.until { |lines| assert_includes lines[1], ' {555-555} ' }
|
||||
tmux.send_keys '?'
|
||||
tmux.until { |lines| refute_includes lines[1], ' {555-555} ' }
|
||||
tmux.send_keys '?'
|
||||
tmux.until { |lines| assert_includes lines[1], ' {555-555} ' }
|
||||
tmux.send_keys :BSpace
|
||||
tmux.until { |lines| assert lines[-2]&.start_with?(' 28/1000 ') }
|
||||
tmux.send_keys 'foobar'
|
||||
tmux.until { |lines| refute_includes lines[1], ' {55-55} ' }
|
||||
tmux.send_keys 'C-u'
|
||||
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||
tmux.until { |lines| assert_includes lines[1], ' {1-1} ' }
|
||||
tmux.send_keys :BTab
|
||||
tmux.until { |lines| assert_includes lines[1], ' {-1} ' }
|
||||
tmux.send_keys :BTab
|
||||
tmux.until { |lines| assert_includes lines[1], ' {3-1 } ' }
|
||||
tmux.send_keys :BTab
|
||||
tmux.until { |lines| assert_includes lines[1], ' {4-1 3} ' }
|
||||
tmux.send_keys :BTab
|
||||
tmux.until { |lines| assert_includes lines[1], ' {5-1 3 4} ' }
|
||||
end
|
||||
|
||||
def test_toggle_preview_without_default_preview_command
|
||||
tmux.send_keys %(seq 100 | #{FZF} --bind 'space:preview(echo [{}]),enter:toggle-preview' --preview-window up,border-double), :Enter
|
||||
tmux.until do |lines|
|
||||
assert_equal 100, lines.match_count
|
||||
refute_includes lines[1], '║ [1]'
|
||||
end
|
||||
|
||||
# toggle-preview should do nothing
|
||||
tmux.send_keys :Enter
|
||||
tmux.until { |lines| refute_includes lines[1], '║ [1]' }
|
||||
tmux.send_keys :Up
|
||||
tmux.until do |lines|
|
||||
refute_includes lines[1], '║ [1]'
|
||||
refute_includes lines[1], '║ [2]'
|
||||
end
|
||||
|
||||
tmux.send_keys :Up
|
||||
tmux.until do |lines|
|
||||
assert_includes lines, '> 3'
|
||||
refute_includes lines[1], '║ [3]'
|
||||
end
|
||||
|
||||
# One-off preview action
|
||||
tmux.send_keys :Space
|
||||
tmux.until { |lines| assert_includes lines[1], '║ [3]' }
|
||||
|
||||
# toggle-preview to hide it
|
||||
tmux.send_keys :Enter
|
||||
tmux.until { |lines| refute_includes lines[1], '║ [3]' }
|
||||
|
||||
# toggle-preview again does nothing
|
||||
tmux.send_keys :Enter, :Up
|
||||
tmux.until do |lines|
|
||||
assert_includes lines, '> 4'
|
||||
refute_includes lines[1], '║ [4]'
|
||||
end
|
||||
end
|
||||
|
||||
def test_show_and_hide_preview
|
||||
tmux.send_keys %(seq 100 | #{FZF} --preview-window hidden,border-bold --preview 'echo [{}]' --bind 'a:show-preview,b:hide-preview'), :Enter
|
||||
|
||||
# Hidden by default
|
||||
tmux.until do |lines|
|
||||
assert_equal 100, lines.match_count
|
||||
refute_includes lines[1], '┃ [1]'
|
||||
end
|
||||
|
||||
# Show
|
||||
tmux.send_keys :a
|
||||
tmux.until { |lines| assert_includes lines[1], '┃ [1]' }
|
||||
|
||||
# Already shown
|
||||
tmux.send_keys :a
|
||||
tmux.send_keys :Up
|
||||
tmux.until { |lines| assert_includes lines[1], '┃ [2]' }
|
||||
|
||||
# Hide
|
||||
tmux.send_keys :b
|
||||
tmux.send_keys :Up
|
||||
tmux.until do |lines|
|
||||
assert_includes lines, '> 3'
|
||||
refute_includes lines[1], '┃ [3]'
|
||||
end
|
||||
|
||||
# Already hidden
|
||||
tmux.send_keys :b
|
||||
tmux.send_keys :Up
|
||||
tmux.until do |lines|
|
||||
assert_includes lines, '> 4'
|
||||
refute_includes lines[1], '┃ [4]'
|
||||
end
|
||||
|
||||
# Show it again
|
||||
tmux.send_keys :a
|
||||
tmux.until { |lines| assert_includes lines[1], '┃ [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| assert_equal '>', lines[-1] }
|
||||
tmux.send_keys '?'
|
||||
tmux.until { |lines| assert_match(/ {1-1-1-[0-9]+}/, lines[-2]) }
|
||||
tmux.send_keys '555'
|
||||
tmux.until { |lines| assert_match(/ {555-555-1-[0-9]+}/, lines[-2]) }
|
||||
tmux.send_keys '?'
|
||||
tmux.until { |lines| assert_equal '> 555', lines[-1] }
|
||||
end
|
||||
|
||||
def test_preview_size_0
|
||||
tmux.send_keys %(seq 100 | #{FZF} --reverse --preview 'echo {} >> #{tempname}; echo ' --preview-window 0 --bind space:toggle-preview), :Enter
|
||||
tmux.until do |lines|
|
||||
assert_equal 100, lines.match_count
|
||||
assert_equal ' 100/100', lines[1]
|
||||
assert_equal '> 1', lines[2]
|
||||
end
|
||||
wait do
|
||||
assert_path_exists tempname
|
||||
assert_equal %w[1], File.readlines(tempname, chomp: true)
|
||||
end
|
||||
tmux.send_keys :Space, :Down, :Down
|
||||
tmux.until { |lines| assert_equal '> 3', lines[4] }
|
||||
wait do
|
||||
assert_path_exists tempname
|
||||
assert_equal %w[1], File.readlines(tempname, chomp: true)
|
||||
end
|
||||
tmux.send_keys :Space, :Down
|
||||
tmux.until { |lines| assert_equal '> 4', lines[5] }
|
||||
wait do
|
||||
assert_path_exists tempname
|
||||
assert_equal %w[1 3 4], File.readlines(tempname, chomp: true)
|
||||
end
|
||||
end
|
||||
|
||||
def test_preview_size_0_hidden
|
||||
tmux.send_keys %(seq 100 | #{FZF} --reverse --preview 'echo {} >> #{tempname}; echo ' --preview-window 0,hidden --bind space:toggle-preview), :Enter
|
||||
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||
tmux.send_keys :Down, :Down
|
||||
tmux.until { |lines| assert_includes lines, '> 3' }
|
||||
wait { refute_path_exists tempname }
|
||||
tmux.send_keys :Space
|
||||
wait do
|
||||
assert_path_exists tempname
|
||||
assert_equal %w[3], File.readlines(tempname, chomp: true)
|
||||
end
|
||||
tmux.send_keys :Down
|
||||
wait do
|
||||
assert_equal %w[3 4], File.readlines(tempname, chomp: true)
|
||||
end
|
||||
tmux.send_keys :Space, :Down
|
||||
tmux.until { |lines| assert_includes lines, '> 5' }
|
||||
tmux.send_keys :Down
|
||||
tmux.until { |lines| assert_includes lines, '> 6' }
|
||||
tmux.send_keys :Space
|
||||
wait do
|
||||
assert_equal %w[3 4 6], File.readlines(tempname, chomp: true)
|
||||
end
|
||||
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| assert_includes lines[1], ' {1/1 /1/1 //0/0} ' }
|
||||
tmux.send_keys '123'
|
||||
tmux.until { |lines| assert_includes lines[1], ' {////123//} ' }
|
||||
tmux.send_keys 'C-u', '1'
|
||||
tmux.until { |lines| assert_equal 2, lines.match_count }
|
||||
tmux.until { |lines| assert_includes lines[1], ' {1/1 /1/1 /1/0/0} ' }
|
||||
tmux.send_keys :BTab
|
||||
tmux.until { |lines| assert_includes lines[1], ' {10/10 /1/1 /1/9/0} ' }
|
||||
tmux.send_keys :BTab
|
||||
tmux.until { |lines| assert_includes lines[1], ' {10/10 /1 10/1 10 /1/9/0 9} ' }
|
||||
tmux.send_keys '2'
|
||||
tmux.until { |lines| assert_includes lines[1], ' {//1 10/1 10 /12//0 9} ' }
|
||||
tmux.send_keys '3'
|
||||
tmux.until { |lines| assert_includes lines[1], ' {//1 10/1 10 /123//0 9} ' }
|
||||
end
|
||||
|
||||
def test_preview_asterisk
|
||||
tmux.send_keys %(seq 5 | #{FZF} --multi --preview 'echo [{}/{+}/{*}/{*n}]' --preview-window '+{1}'), :Enter
|
||||
tmux.until { |lines| assert_equal 5, lines.match_count }
|
||||
tmux.until { |lines| assert_includes lines[1], ' [1/1/1 2 3 4 5/0 1 2 3 4] ' }
|
||||
tmux.send_keys :BTab
|
||||
tmux.until { |lines| assert_includes lines[1], ' [2/1/1 2 3 4 5/0 1 2 3 4] ' }
|
||||
tmux.send_keys :BTab
|
||||
tmux.until { |lines| assert_includes lines[1], ' [3/1 2/1 2 3 4 5/0 1 2 3 4] ' }
|
||||
tmux.send_keys '5'
|
||||
tmux.until { |lines| assert_includes lines[1], ' [5/1 2/5/4] ' }
|
||||
tmux.send_keys '5'
|
||||
tmux.until { |lines| assert_includes lines[1], ' [/1 2//] ' }
|
||||
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| assert_includes lines[1], ' foo barbar00 ' }
|
||||
tmux.send_keys :BTab
|
||||
tmux.until { |lines| assert_includes lines[1], ' foo barbar00 ' }
|
||||
tmux.send_keys :BTab
|
||||
tmux.until { |lines| assert_includes lines[1], ' foo barbar foobarfoo0101 ' }
|
||||
end
|
||||
|
||||
def test_preview_q_no_match
|
||||
tmux.send_keys %(: | #{FZF} --preview 'echo foo {q} foo'), :Enter
|
||||
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||
tmux.until { |lines| assert_includes lines[1], ' foo foo' }
|
||||
tmux.send_keys 'bar'
|
||||
tmux.until { |lines| assert_includes lines[1], ' foo bar foo' }
|
||||
tmux.send_keys 'C-u'
|
||||
tmux.until { |lines| assert_includes lines[1], ' foo foo' }
|
||||
end
|
||||
|
||||
def test_preview_q_no_match_with_initial_query
|
||||
tmux.send_keys %(: | #{FZF} --preview 'echo 1. /{q}/{q:1}/; echo 2. /{q:..}/{q:2}/{q:-1}/; echo 3. /{q:s-2}/{q:-2}/{q:x}/' --query 'foo bar'), :Enter
|
||||
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||
tmux.until { |lines| assert_includes lines[1], '1. /foo bar/foo/' }
|
||||
tmux.until { |lines| assert_includes lines[2], '2. /foo bar/bar/bar/' }
|
||||
tmux.until { |lines| assert_includes lines[3], '3. /foo /foo/{q:x}/' }
|
||||
end
|
||||
|
||||
def test_preview_update_on_select
|
||||
tmux.send_keys %(seq 10 | fzf -m --preview 'echo {+}' --bind a:toggle-all),
|
||||
:Enter
|
||||
tmux.until { |lines| assert_equal 10, lines.match_count }
|
||||
tmux.send_keys 'a'
|
||||
tmux.until { |lines| assert(lines.any? { |line| line.include?(' 1 2 3 4 5 ') }) }
|
||||
tmux.send_keys 'a'
|
||||
tmux.until { |lines| lines.each { |line| refute_includes line, ' 1 2 3 4 5 ' } }
|
||||
end
|
||||
|
||||
def test_preview_correct_tab_width_after_ansi_reset_code
|
||||
writelines(["\x1b[31m+\x1b[m\t\x1b[32mgreen"])
|
||||
tmux.send_keys "#{FZF} --preview 'cat #{tempname}'", :Enter
|
||||
tmux.until { |lines| assert_includes lines[1], ' + green ' }
|
||||
end
|
||||
|
||||
def test_preview_bindings_with_default_preview
|
||||
tmux.send_keys "seq 10 | #{FZF} --preview 'echo [{}]' --bind 'a:preview(echo [{}{}]),b:preview(echo [{}{}{}]),c:refresh-preview'", :Enter
|
||||
tmux.until { |lines| lines.match_count == 10 }
|
||||
tmux.until { |lines| assert_includes lines[1], '[1]' }
|
||||
tmux.send_keys 'a'
|
||||
tmux.until { |lines| assert_includes lines[1], '[11]' }
|
||||
tmux.send_keys 'c'
|
||||
tmux.until { |lines| assert_includes lines[1], '[1]' }
|
||||
tmux.send_keys 'b'
|
||||
tmux.until { |lines| assert_includes lines[1], '[111]' }
|
||||
tmux.send_keys :Up
|
||||
tmux.until { |lines| assert_includes lines[1], '[2]' }
|
||||
end
|
||||
|
||||
def test_preview_bindings_without_default_preview
|
||||
tmux.send_keys "seq 10 | #{FZF} --bind 'a:preview(echo [{}{}]),b:preview(echo [{}{}{}]),c:refresh-preview'", :Enter
|
||||
tmux.until { |lines| lines.match_count == 10 }
|
||||
tmux.until { |lines| refute_includes lines[1], '1' }
|
||||
tmux.send_keys 'a'
|
||||
tmux.until { |lines| assert_includes lines[1], '[11]' }
|
||||
tmux.send_keys 'c' # does nothing
|
||||
tmux.until { |lines| assert_includes lines[1], '[11]' }
|
||||
tmux.send_keys 'b'
|
||||
tmux.until { |lines| assert_includes lines[1], '[111]' }
|
||||
tmux.send_keys 9
|
||||
tmux.until { |lines| lines.match_count == 1 }
|
||||
tmux.until { |lines| refute_includes lines[1], '2' }
|
||||
tmux.until { |lines| assert_includes lines[1], '[111]' }
|
||||
end
|
||||
|
||||
def test_preview_scroll_begin_constant
|
||||
tmux.send_keys "echo foo 123 321 | #{FZF} --preview 'seq 1000' --preview-window left:+123", :Enter
|
||||
tmux.until { |lines| assert_match %r{1/1}, lines[-2] }
|
||||
tmux.until { |lines| assert_match %r{123.*123/1000}, lines[1] }
|
||||
end
|
||||
|
||||
def test_preview_scroll_begin_expr
|
||||
tmux.send_keys "echo foo 123 321 | #{FZF} --preview 'seq 1000' --preview-window left:+{3}", :Enter
|
||||
tmux.until { |lines| assert_match %r{1/1}, lines[-2] }
|
||||
tmux.until { |lines| assert_match %r{321.*321/1000}, lines[1] }
|
||||
end
|
||||
|
||||
def test_preview_scroll_begin_and_offset
|
||||
['echo foo 123 321', 'echo foo :123: 321'].each do |input|
|
||||
tmux.send_keys "#{input} | #{FZF} --preview 'seq 1000' --preview-window left:+{2}-2", :Enter
|
||||
tmux.until { |lines| assert_match %r{1/1}, lines[-2] }
|
||||
tmux.until { |lines| assert_match %r{121.*121/1000}, lines[1] }
|
||||
tmux.send_keys 'C-c'
|
||||
end
|
||||
end
|
||||
|
||||
def test_preview_clear_screen
|
||||
tmux.send_keys %{seq 100 | #{FZF} --preview 'for i in $(seq 300); do (( i % 200 == 0 )) && printf "\\033[2J"; echo "[$i]"; sleep 0.001; done'}, :Enter
|
||||
tmux.until { |lines| lines.match_count == 100 }
|
||||
tmux.until { |lines| lines[1]&.include?('[200]') }
|
||||
end
|
||||
|
||||
def test_preview_window_follow
|
||||
file = Tempfile.new('fzf-follow')
|
||||
file.sync = true
|
||||
|
||||
tmux.send_keys %(seq 100 | #{FZF} --preview 'echo start; tail -f "#{file.path}"' --preview-window follow --bind 'up:preview-up,down:preview-down,space:change-preview-window:follow|nofollow' --preview-window '~4'), :Enter
|
||||
tmux.until { |lines| lines.match_count == 100 }
|
||||
|
||||
# Write to the temporary file, and check if the preview window is showing
|
||||
# the last line of the file
|
||||
tmux.until { |lines| assert_includes lines[1], 'start' }
|
||||
3.times { file.puts _1 } # header lines
|
||||
1000.times { file.puts _1 }
|
||||
tmux.until { |lines| assert_includes lines[1], '/1004' }
|
||||
tmux.until { |lines| assert_includes lines[-2], '999' }
|
||||
|
||||
# Scroll the preview window and fzf should stop following the file content
|
||||
tmux.send_keys :Up
|
||||
tmux.until { |lines| assert_includes lines[-2], '998' }
|
||||
file.puts 'foo', 'bar'
|
||||
tmux.until do |lines|
|
||||
assert_includes lines[1], '/1006'
|
||||
assert_includes lines[-2], '998'
|
||||
end
|
||||
|
||||
# Scroll back to the bottom and fzf should start following the file again
|
||||
%w[999 foo bar].each do |item|
|
||||
wait do
|
||||
tmux.send_keys :Down
|
||||
tmux.until { |lines| assert_includes lines[-2], item }
|
||||
end
|
||||
end
|
||||
file.puts 'baz'
|
||||
tmux.until do |lines|
|
||||
assert_includes lines[1], '/1007'
|
||||
assert_includes lines[-2], 'baz'
|
||||
end
|
||||
|
||||
# Scroll upwards to stop following
|
||||
tmux.send_keys :Up
|
||||
wait { assert_includes lines[-2], 'bar' }
|
||||
file.puts 'aaa'
|
||||
tmux.until do |lines|
|
||||
assert_includes lines[1], '/1008'
|
||||
assert_includes lines[-2], 'bar'
|
||||
end
|
||||
|
||||
# Manually enable following
|
||||
tmux.send_keys :Space
|
||||
tmux.until { |lines| assert_includes lines[-2], 'aaa' }
|
||||
file.puts 'bbb'
|
||||
tmux.until do |lines|
|
||||
assert_includes lines[1], '/1009'
|
||||
assert_includes lines[-2], 'bbb'
|
||||
end
|
||||
|
||||
# Disable following
|
||||
tmux.send_keys :Space
|
||||
file.puts 'ccc', 'ddd'
|
||||
tmux.until do |lines|
|
||||
assert_includes lines[1], '/1011'
|
||||
assert_includes lines[-2], 'bbb'
|
||||
end
|
||||
rescue StandardError
|
||||
file.close
|
||||
file.unlink
|
||||
end
|
||||
|
||||
def test_toggle_preview_wrap
|
||||
tmux.send_keys "#{FZF} --preview 'for i in $(seq $FZF_PREVIEW_COLUMNS); do echo -n .; done; echo wrapped; echo 2nd line' --bind ctrl-w:toggle-preview-wrap", :Enter
|
||||
2.times do
|
||||
tmux.until { |lines| assert_includes lines[2], '2nd line' }
|
||||
tmux.send_keys 'C-w'
|
||||
tmux.until do |lines|
|
||||
assert_includes lines[2], 'wrapped'
|
||||
assert_includes lines[3], '2nd line'
|
||||
end
|
||||
tmux.send_keys 'C-w'
|
||||
end
|
||||
end
|
||||
|
||||
def test_change_preview_window_preserves_wrap_toggle
|
||||
# https://github.com/junegunn/fzf/issues/4791
|
||||
tmux.send_keys "#{FZF} --preview 'for i in $(seq $FZF_PREVIEW_COLUMNS); do echo -n .; done; echo -n .; echo wrapped; echo 2nd line' " \
|
||||
"--preview-window 'right,nowrap,border-rounded' " \
|
||||
'--bind ctrl-w:toggle-preview-wrap ' \
|
||||
'--bind ctrl-r:change-preview-window:border-bold', :Enter
|
||||
sleep(2)
|
||||
# Initial: nowrap, rounded border. The long line is truncated; "wrapped" is hidden.
|
||||
tmux.until do |lines|
|
||||
assert_includes lines[2], '2nd line'
|
||||
assert(lines.any? { it.include?('╭') })
|
||||
end
|
||||
# Toggle wrap on.
|
||||
tmux.send_keys 'C-w'
|
||||
tmux.until do |lines|
|
||||
assert_includes lines[2], 'wrapped'
|
||||
assert_includes lines[3], '2nd line'
|
||||
end
|
||||
# change-preview-window swaps the border to bold; wrap state must persist.
|
||||
tmux.send_keys 'C-r'
|
||||
tmux.until do |lines|
|
||||
assert(lines.any? { it.include?('┏') }) # border actually changed
|
||||
refute(lines.any? { it.include?('╭') })
|
||||
assert_includes lines[2], 'wrapped' # wrap was preserved
|
||||
assert_includes lines[3], '2nd line'
|
||||
end
|
||||
end
|
||||
|
||||
def test_change_preview_window_overrides_wrap_explicitly
|
||||
# When the new spec sets wrap/nowrap explicitly, it should still win.
|
||||
tmux.send_keys "#{FZF} --preview 'for i in $(seq $FZF_PREVIEW_COLUMNS); do echo -n .; done; echo -n .; echo wrapped; echo 2nd line' " \
|
||||
"--preview-window 'right,wrap' " \
|
||||
'--bind ctrl-r:change-preview-window:nowrap', :Enter
|
||||
# Initial: wrap is on.
|
||||
tmux.until do |lines|
|
||||
assert_includes lines[2], 'wrapped'
|
||||
assert_includes lines[3], '2nd line'
|
||||
end
|
||||
# Explicit nowrap in the spec must override the (initially wrapped) state.
|
||||
tmux.send_keys 'C-r'
|
||||
tmux.until { |lines| assert_includes lines[2], '2nd line' }
|
||||
end
|
||||
|
||||
def test_preview_follow_wrap
|
||||
tmux.send_keys "seq 1 | #{FZF} --preview 'seq 1000' --preview-window right,2,follow,wrap", :Enter
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
tmux.until do |lines|
|
||||
idx = lines.rindex { it.include?('│ 10 │') }
|
||||
assert_includes lines[idx + 1], '│ ↳ │'
|
||||
assert_includes lines[idx + 2], '│ ↳ │'
|
||||
end
|
||||
end
|
||||
|
||||
def test_preview_follow_wrap_long_line
|
||||
tmux.send_keys %(seq 1 | #{FZF} --preview "seq 2; yes yes | head -10000 | tr '\n' ' '" --preview-window follow,wrap --bind up:preview-up,down:preview-down), :Enter
|
||||
tmux.until do |lines|
|
||||
assert_equal 1, lines.match_count
|
||||
assert lines.any_include?('3/3 │')
|
||||
end
|
||||
tmux.send_keys :Up
|
||||
tmux.until { |lines| assert lines.any_include?('2/3 │') }
|
||||
tmux.send_keys :Up
|
||||
tmux.until { |lines| assert lines.any_include?('1/3 │') }
|
||||
tmux.send_keys :Down
|
||||
tmux.until { |lines| assert lines.any_include?('2/3 │') }
|
||||
end
|
||||
|
||||
def test_close
|
||||
tmux.send_keys "seq 100 | #{FZF} --preview 'echo foo' --bind ctrl-c:close", :Enter
|
||||
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||
tmux.until { |lines| assert_includes lines[1], 'foo' }
|
||||
tmux.send_keys 'C-c'
|
||||
tmux.until { |lines| refute_includes lines[1], 'foo' }
|
||||
tmux.send_keys '10'
|
||||
tmux.until { |lines| assert_equal 2, lines.match_count }
|
||||
tmux.send_keys 'C-c'
|
||||
tmux.send_keys 'C-l', 'closed'
|
||||
tmux.until { |lines| assert_includes lines[0], 'closed' }
|
||||
end
|
||||
|
||||
def test_preview_header
|
||||
tmux.send_keys "seq 100 | #{FZF} --bind ctrl-k:preview-up+preview-up,ctrl-j:preview-down+preview-down+preview-down --preview 'seq 1000' --preview-window 'top:+{1}:~3'", :Enter
|
||||
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||
top5 = ->(lines) { lines.drop(1).take(5).map { |s| s[/[0-9]+/] } }
|
||||
tmux.until do |lines|
|
||||
assert_includes lines[1], '4/1000'
|
||||
assert_equal(%w[1 2 3 4 5], top5[lines])
|
||||
end
|
||||
tmux.send_keys '55'
|
||||
tmux.until do |lines|
|
||||
assert_equal 1, lines.match_count
|
||||
assert_equal(%w[1 2 3 55 56], top5[lines])
|
||||
end
|
||||
tmux.send_keys 'C-J'
|
||||
tmux.until do |lines|
|
||||
assert_equal(%w[1 2 3 58 59], top5[lines])
|
||||
end
|
||||
tmux.send_keys :BSpace
|
||||
tmux.until do |lines|
|
||||
assert_equal 19, lines.match_count
|
||||
assert_equal(%w[1 2 3 5 6], top5[lines])
|
||||
end
|
||||
tmux.send_keys 'C-K'
|
||||
tmux.until { |lines| assert_equal(%w[1 2 3 4 5], top5[lines]) }
|
||||
end
|
||||
|
||||
def test_change_preview_window
|
||||
tmux.send_keys "seq 1000 | #{FZF} --preview 'echo [[{}]]' --no-preview-border --bind '" \
|
||||
'a:change-preview(echo __{}__),' \
|
||||
'b:change-preview-window(down)+change-preview(echo =={}==)+change-preview-window(up),' \
|
||||
'c:change-preview(),d:change-preview-window(hidden),' \
|
||||
"e:preview(printf ::%${FZF_PREVIEW_COLUMNS}s{})+change-preview-window(up),f:change-preview-window(up,wrap)'", :Enter
|
||||
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||
tmux.until { |lines| assert_includes lines[0], '[[1]]' }
|
||||
|
||||
# change-preview action permanently changes the preview command set by --preview
|
||||
tmux.send_keys 'a'
|
||||
tmux.until { |lines| assert_includes lines[0], '__1__' }
|
||||
tmux.send_keys :Up
|
||||
tmux.until { |lines| assert_includes lines[0], '__2__' }
|
||||
|
||||
# When multiple change-preview-window actions are bound to a single key,
|
||||
# the last one wins and the updated options are immediately applied to the new preview
|
||||
tmux.send_keys 'b'
|
||||
tmux.until { |lines| assert_equal '==2==', lines[0] }
|
||||
tmux.send_keys :Up
|
||||
tmux.until { |lines| assert_equal '==3==', lines[0] }
|
||||
|
||||
# change-preview with an empty preview command closes the preview window
|
||||
tmux.send_keys 'c'
|
||||
tmux.until { |lines| refute_includes lines[0], '==' }
|
||||
|
||||
# change-preview again to re-open the preview window
|
||||
tmux.send_keys 'a'
|
||||
tmux.until { |lines| assert_equal '__3__', lines[0] }
|
||||
|
||||
# Hide the preview window with hidden flag
|
||||
tmux.send_keys 'd'
|
||||
tmux.until { |lines| refute_includes lines[0], '__3__' }
|
||||
|
||||
# One-off preview
|
||||
tmux.send_keys 'e'
|
||||
tmux.until do |lines|
|
||||
assert_equal '::', lines[0]
|
||||
refute_includes lines[1], '3'
|
||||
end
|
||||
|
||||
# Wrapped
|
||||
tmux.send_keys 'f'
|
||||
tmux.until do |lines|
|
||||
assert_equal '::', lines[0]
|
||||
assert_equal '↳ 3', lines[1]
|
||||
end
|
||||
end
|
||||
|
||||
def test_change_preview_window_should_not_reset_change_preview
|
||||
tmux.send_keys "#{FZF} --preview-window up,border-none --bind 'start:change-preview(echo hello)' --bind 'enter:change-preview-window(border-left)'", :Enter
|
||||
tmux.until { |lines| assert_includes lines, 'hello' }
|
||||
tmux.send_keys :Enter
|
||||
tmux.until { |lines| assert_includes lines, '│ hello' }
|
||||
end
|
||||
|
||||
def test_change_preview_window_rotate
|
||||
tmux.send_keys "seq 100 | #{FZF} --preview-window left,border-none --preview 'echo hello' --bind '" \
|
||||
"a:change-preview-window(right|down|up|hidden|)'", :Enter
|
||||
tmux.until { |lines| assert(lines.any? { _1.include?('100/100') }) }
|
||||
3.times do
|
||||
tmux.until { |lines| lines[0].start_with?('hello') }
|
||||
tmux.send_keys 'a'
|
||||
tmux.until { |lines| lines[0].end_with?('hello') }
|
||||
tmux.send_keys 'a'
|
||||
tmux.until { |lines| lines[-1].start_with?('hello') }
|
||||
tmux.send_keys 'a'
|
||||
tmux.until { |lines| assert_equal 'hello', lines[0] }
|
||||
tmux.send_keys 'a'
|
||||
tmux.until { |lines| refute_includes lines[0], 'hello' }
|
||||
tmux.send_keys 'a'
|
||||
end
|
||||
end
|
||||
|
||||
def test_change_preview_window_rotate_hidden
|
||||
tmux.send_keys "seq 100 | #{FZF} --preview-window hidden --preview 'echo =={}==' --bind '" \
|
||||
"a:change-preview-window(nohidden||down,1|)'", :Enter
|
||||
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||
tmux.until { |lines| refute_includes lines[1], '==1==' }
|
||||
tmux.send_keys 'a'
|
||||
tmux.until { |lines| assert_includes lines[1], '==1==' }
|
||||
tmux.send_keys 'a'
|
||||
tmux.until { |lines| refute_includes lines[1], '==1==' }
|
||||
tmux.send_keys 'a'
|
||||
tmux.until { |lines| assert_includes lines[-2], '==1==' }
|
||||
tmux.send_keys 'a'
|
||||
tmux.until { |lines| refute_includes lines[-2], '==1==' }
|
||||
tmux.send_keys 'a'
|
||||
tmux.until { |lines| assert_includes lines[1], '==1==' }
|
||||
end
|
||||
|
||||
def test_change_preview_window_rotate_hidden_down
|
||||
tmux.send_keys "seq 100 | #{FZF} --bind '?:change-preview-window:up||down|' --preview 'echo =={}==' --preview-window hidden,down,1", :Enter
|
||||
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||
tmux.until { |lines| refute_includes lines[1], '==1==' }
|
||||
tmux.send_keys '?'
|
||||
tmux.until { |lines| assert_includes lines[1], '==1==' }
|
||||
tmux.send_keys '?'
|
||||
tmux.until { |lines| refute_includes lines[1], '==1==' }
|
||||
tmux.send_keys '?'
|
||||
tmux.until { |lines| assert_includes lines[-2], '==1==' }
|
||||
tmux.send_keys '?'
|
||||
tmux.until { |lines| refute_includes lines[-2], '==1==' }
|
||||
tmux.send_keys '?'
|
||||
tmux.until { |lines| assert_includes lines[1], '==1==' }
|
||||
end
|
||||
|
||||
def test_toggle_alternative_preview_window
|
||||
tmux.send_keys "seq 10 | #{FZF} --bind space:toggle-preview --preview-window '<100000(hidden,up,border-none)' --preview 'echo /{}/{}/'", :Enter
|
||||
tmux.until { |lines| assert_equal 10, lines.match_count }
|
||||
tmux.until { |lines| refute_includes lines, '/1/1/' }
|
||||
tmux.send_keys :Space
|
||||
tmux.until { |lines| assert_includes lines, '/1/1/' }
|
||||
end
|
||||
|
||||
def test_alternative_preview_window_opts
|
||||
tmux.send_keys "seq 10 | #{FZF} --preview-border rounded --preview-window '~5,2,+0,<100000(~0,+100,wrap,noinfo)' --preview 'seq 1000'", :Enter
|
||||
tmux.until { |lines| assert_equal 10, lines.match_count }
|
||||
tmux.until do |lines|
|
||||
assert_equal ['╭────╮', '│ 10 │', '│ ↳ │', '│ 10 │', '│ ↳ │'], lines.take(5).map(&:strip)
|
||||
end
|
||||
end
|
||||
|
||||
def test_preview_window_width_exception
|
||||
tmux.send_keys "seq 10 | #{FZF} --scrollbar --preview-window border-left --border --preview 'seq 1000'", :Enter
|
||||
tmux.until do |lines|
|
||||
assert lines[1]&.end_with?(' 1/1000││')
|
||||
end
|
||||
end
|
||||
|
||||
def test_preview_window_hidden_on_focus
|
||||
tmux.send_keys "seq 3 | #{FZF} --preview 'echo {}' --bind focus:hide-preview", :Enter
|
||||
tmux.until { |lines| assert_includes lines, '> 1' }
|
||||
tmux.send_keys :Up
|
||||
tmux.until { |lines| assert_includes lines, '> 2' }
|
||||
end
|
||||
|
||||
def test_preview_query_should_not_be_affected_by_search
|
||||
tmux.send_keys "seq 1 | #{FZF} --bind 'change:transform-search(echo {q:1})' --preview 'echo [{q}/{}]'", :Enter
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
tmux.send_keys '1'
|
||||
tmux.until { |lines| assert lines.any_include?('[1/1]') }
|
||||
tmux.send_keys :Space
|
||||
tmux.until { |lines| assert lines.any_include?('[1 /1]') }
|
||||
tmux.send_keys '2'
|
||||
tmux.until do |lines|
|
||||
assert lines.any_include?('[1 2/1]')
|
||||
assert_equal 1, lines.match_count
|
||||
end
|
||||
end
|
||||
|
||||
def test_preview_wrap_sign_between_ansi_fragments
|
||||
tmux.send_keys %(seq 1 | #{FZF} --preview 'echo -e "\\x1b[33m1234567890 \\x1b[mhello"; echo -e "\\x1b[33m1234567890 \\x1b[mhello"' --preview-window 10,wrap-word), :Enter
|
||||
tmux.until do |lines|
|
||||
assert_equal 1, lines.match_count
|
||||
assert_equal(2, lines.count { |line| line.include?('│ 1234567890 │') })
|
||||
assert_equal(2, lines.count { |line| line.include?('│ ↳ hello │') })
|
||||
end
|
||||
end
|
||||
|
||||
def test_preview_wrap_sign_between_ansi_fragments_overflow
|
||||
tmux.send_keys %(seq 1 | #{FZF} --preview 'echo -e "\\x1b[33m123 \\x1b[mhi"; echo -e "\\x1b[33m123 \\x1b[mhi"' --preview-window 2,wrap-word,noinfo), :Enter
|
||||
tmux.until do |lines|
|
||||
assert_equal 1, lines.match_count
|
||||
assert_equal(2, lines.count { |line| line.include?('│ 12 │') })
|
||||
assert_equal(0, lines.count { |line| line.include?('│ h') })
|
||||
end
|
||||
end
|
||||
|
||||
def test_preview_wrap_sign_between_ansi_fragments_overflow2
|
||||
tmux.send_keys %(seq 1 | #{FZF} --preview 'echo -e "\\x1b[33m123 \\x1b[mhi"; echo -e "\\x1b[33m123 \\x1b[mhi"' --preview-window 1,wrap-word,noinfo), :Enter
|
||||
tmux.until do |lines|
|
||||
assert_equal 1, lines.match_count
|
||||
assert_equal(2, lines.count { |line| line.include?('│ 1 │') })
|
||||
assert_equal(0, lines.count { |line| line.include?('│ h') })
|
||||
end
|
||||
end
|
||||
|
||||
def test_preview_toggle_should_redraw_scrollbar
|
||||
tmux.send_keys %(seq 1 | #{FZF} --no-border --scrollbar --preview 'seq $((FZF_PREVIEW_LINES + 1))' --preview-border line --bind tab:toggle-preview --header foo --header-border --footer bar --footer-border), :Enter
|
||||
tmux.until do |lines|
|
||||
assert_equal 1, lines.match_count
|
||||
assert_operator lines.count { |line| line.end_with?('│') }, :>, 2
|
||||
end
|
||||
tmux.send_keys :Tab
|
||||
tmux.until do |lines|
|
||||
assert_equal(2, lines.count { |line| line.end_with?('│') })
|
||||
end
|
||||
tmux.send_keys :Tab
|
||||
tmux.until do |lines|
|
||||
assert_operator lines.count { |line| line.end_with?('│') }, :>, 2
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,113 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'lib/common'
|
||||
|
||||
# Testing raw mode
|
||||
class TestRaw < TestInteractive
|
||||
def test_raw_mode
|
||||
tmux.send_keys %(seq 1000 | #{FZF} --raw --bind ctrl-x:toggle-raw,a:enable-raw,b:disable-raw --gutter '▌' --multi --bind 'space:transform-prompt:echo "[[$FZF_RAW]] "'), :Enter
|
||||
tmux.until { assert_equal 1000, it.match_count }
|
||||
|
||||
tmux.send_keys 1
|
||||
tmux.until { assert_equal 272, it.match_count }
|
||||
|
||||
tmux.send_keys :Up
|
||||
tmux.until { assert_includes it, '> 2' }
|
||||
|
||||
tmux.send_keys 'C-p'
|
||||
tmux.until do
|
||||
assert_includes it, '> 10'
|
||||
assert_includes it, '▖ 9'
|
||||
end
|
||||
|
||||
tmux.send_keys 'C-x'
|
||||
tmux.until do
|
||||
assert_includes it, '> 10'
|
||||
assert_includes it, '▌ 1'
|
||||
end
|
||||
|
||||
tmux.send_keys :Up, 'C-x'
|
||||
tmux.until do
|
||||
assert_includes it, '> 11'
|
||||
assert_includes it, '▖ 10'
|
||||
end
|
||||
|
||||
tmux.send_keys 1
|
||||
tmux.until { assert_equal 28, it.match_count }
|
||||
|
||||
tmux.send_keys 'C-p'
|
||||
tmux.until do
|
||||
assert_includes it, '> 101'
|
||||
assert_includes it, '▖ 100'
|
||||
end
|
||||
|
||||
tmux.send_keys 'C-n'
|
||||
tmux.until do
|
||||
assert_includes it, '> 11'
|
||||
assert_includes it, '▖ 10'
|
||||
end
|
||||
|
||||
tmux.send_keys :Tab, :Tab, :Tab
|
||||
tmux.until { assert_equal 3, it.select_count }
|
||||
|
||||
tmux.send_keys 'C-x'
|
||||
tmux.until do
|
||||
assert_equal 1, it.select_count
|
||||
assert_includes it, '▌ 110'
|
||||
assert_includes it, '>>11'
|
||||
end
|
||||
|
||||
tmux.send_keys 'a'
|
||||
tmux.until do
|
||||
assert_equal 1, it.select_count
|
||||
assert_includes it, '>>11'
|
||||
assert_includes it, '▖ 10'
|
||||
end
|
||||
|
||||
tmux.send_keys :Down, :Space
|
||||
tmux.until { assert_includes it, '[[0]] 11' }
|
||||
|
||||
tmux.send_keys :Up, :Space
|
||||
tmux.until { assert_includes it, '[[1]] 11' }
|
||||
|
||||
tmux.send_keys 'b'
|
||||
tmux.until do
|
||||
assert_equal 1, it.select_count
|
||||
assert_includes it, '▌ 110'
|
||||
assert_includes it, '>>11'
|
||||
end
|
||||
|
||||
tmux.send_keys :Space
|
||||
tmux.until { assert_includes it, '[[]] 11' }
|
||||
|
||||
tmux.send_keys 'C-u', '5'
|
||||
tmux.until { assert_includes it, '> 5' }
|
||||
|
||||
tmux.send_keys 'C-x', 'C-p', 'C-p'
|
||||
tmux.until do
|
||||
assert_includes it, '> 25'
|
||||
assert_includes it, '▖ 24'
|
||||
end
|
||||
|
||||
tmux.send_keys 'C-x'
|
||||
tmux.until do
|
||||
assert_includes it, '> 25'
|
||||
assert_includes it, '▌ 15'
|
||||
end
|
||||
|
||||
# 35 is the closest match in raw mode
|
||||
tmux.send_keys 'C-x', :Up, :Up, :Up, :Up, :Up, :Up, 'C-x'
|
||||
tmux.until do
|
||||
assert_includes it, '> 35'
|
||||
assert_includes it, '▌ 25'
|
||||
end
|
||||
end
|
||||
|
||||
def test_raw_best
|
||||
tmux.send_keys %(seq 1000 | #{FZF} --raw --bind space:best), :Enter
|
||||
tmux.send_keys 999
|
||||
tmux.until { assert_includes it, '> 1' }
|
||||
tmux.send_keys :Space
|
||||
tmux.until { assert_includes it, '> 999' }
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,87 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'lib/common'
|
||||
|
||||
# Test cases for API server
|
||||
class TestServer < TestInteractive
|
||||
def test_listen
|
||||
{ '--listen 6266' => -> { URI('http://localhost:6266') },
|
||||
"--listen --sync --bind 'start:execute-silent:echo $FZF_PORT > /tmp/fzf-port'" =>
|
||||
-> { URI("http://localhost:#{File.read('/tmp/fzf-port').chomp}") } }.each do |opts, fn|
|
||||
tmux.send_keys "seq 10 | fzf #{opts}", :Enter
|
||||
tmux.until { |lines| assert_equal 10, lines.match_count }
|
||||
state = JSON.parse(Net::HTTP.get(fn.call), symbolize_names: true)
|
||||
assert_equal 10, state[:totalCount]
|
||||
assert_equal 10, state[:matchCount]
|
||||
assert_empty state[:query]
|
||||
assert_equal({ index: 0, text: '1' }, state[:current])
|
||||
|
||||
# No positions when query is empty
|
||||
state[:matches].each do |m|
|
||||
assert_nil m[:positions]
|
||||
end
|
||||
assert_nil state[:current][:positions] if state[:current]
|
||||
|
||||
# Positions with a single-character query
|
||||
Net::HTTP.post(fn.call, 'change-query(1)')
|
||||
tmux.until { |lines| assert_equal 2, lines.match_count }
|
||||
state = JSON.parse(Net::HTTP.get(fn.call), symbolize_names: true)
|
||||
assert_equal [0], state[:current][:positions]
|
||||
state[:matches].each do |m|
|
||||
assert_includes m[:text], '1'
|
||||
assert_equal [m[:text].index('1')], m[:positions]
|
||||
end
|
||||
|
||||
# Positions with a multi-character query; verify sorted ascending
|
||||
Net::HTTP.post(fn.call, 'change-query(10)')
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
state = JSON.parse(Net::HTTP.get(fn.call), symbolize_names: true)
|
||||
assert_equal '10', state[:current][:text]
|
||||
assert_equal [0, 1], state[:current][:positions]
|
||||
assert_equal state[:current][:positions], state[:current][:positions].sort
|
||||
|
||||
# No match - no current item
|
||||
Net::HTTP.post(fn.call, 'change-query(yo)+reload(seq 100)+change-prompt:hundred> ')
|
||||
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||
tmux.until { |lines| assert_equal 'hundred> yo', lines[-1] }
|
||||
state = JSON.parse(Net::HTTP.get(fn.call), symbolize_names: true)
|
||||
assert_equal 100, state[:totalCount]
|
||||
assert_equal 0, state[:matchCount]
|
||||
assert_equal 'yo', state[:query]
|
||||
assert_nil state[:current]
|
||||
|
||||
teardown
|
||||
setup
|
||||
end
|
||||
end
|
||||
|
||||
def test_listen_with_api_key
|
||||
uri = URI('http://localhost:6266')
|
||||
tmux.send_keys 'seq 10 | FZF_API_KEY=123abc fzf --listen 6266', :Enter
|
||||
tmux.until { |lines| assert_equal 10, lines.match_count }
|
||||
# Incorrect API Key
|
||||
[nil, { 'x-api-key' => '' }, { 'x-api-key' => '124abc' }].each do |headers|
|
||||
res = Net::HTTP.post(uri, 'change-query(yo)+reload(seq 100)+change-prompt:hundred> ', headers)
|
||||
assert_equal '401', res.code
|
||||
assert_equal 'Unauthorized', res.message
|
||||
assert_equal "invalid api key\n", res.body
|
||||
|
||||
res = Net::HTTP.get_response(uri, headers)
|
||||
assert_equal '401', res.code
|
||||
assert_equal 'Unauthorized', res.message
|
||||
assert_equal "invalid api key\n", res.body
|
||||
end
|
||||
|
||||
# Valid API Key
|
||||
[{ 'x-api-key' => '123abc' }, { 'X-API-Key' => '123abc' }].each do |headers|
|
||||
res = Net::HTTP.post(uri, 'change-query(yo)+reload(seq 100)+change-prompt:hundred> ', headers)
|
||||
assert_equal '200', res.code
|
||||
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||
tmux.until { |lines| assert_equal 'hundred> yo', lines[-1] }
|
||||
|
||||
res = Net::HTTP.get_response(uri, headers)
|
||||
assert_equal '200', res.code
|
||||
assert_equal 'yo', JSON.parse(res.body, symbolize_names: true)[:query]
|
||||
end
|
||||
end
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,87 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'shellwords'
|
||||
require 'tmpdir'
|
||||
require_relative 'lib/common'
|
||||
|
||||
# Tests for running fzf in a tmux floating pane (--popup on tmux 3.7 or above)
|
||||
class TestTmux < TestInteractive
|
||||
def setup
|
||||
super
|
||||
# Cannot rely on the exit status; tmux versions before 3.7 exit
|
||||
# normally with empty output for an unknown command name
|
||||
supported = IO.popen(%w[tmux list-commands new-pane], err: File::NULL, &:read).include?('new-pane')
|
||||
skip('floating panes not supported') unless supported
|
||||
end
|
||||
|
||||
def test_floating_pane
|
||||
tmux.send_keys "seq 100 | #{fzf('--popup center,80% --margin 0')}", :Enter
|
||||
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||
# Border text is cleared when no label is given
|
||||
format = IO.popen(['tmux', 'show-options', '-p', '-t', floating_pane, 'pane-border-format'], &:read)
|
||||
assert_includes format, "''"
|
||||
tmux.send_keys '99'
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
tmux.send_keys :Enter
|
||||
assert_equal '99', fzf_output
|
||||
end
|
||||
|
||||
def test_floating_pane_killed
|
||||
tmux.send_keys "seq 100 | #{FZF} --popup bottom,50% --margin 0; echo code:$?", :Enter
|
||||
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||
pane = floating_pane
|
||||
refute_nil pane
|
||||
assert system('tmux', 'kill-pane', '-t', pane)
|
||||
tmux.until { |lines| assert lines.any_include?('code:130') }
|
||||
end
|
||||
|
||||
def test_floating_pane_border_label
|
||||
tmux.send_keys "seq 100 | #{fzf(%(--popup center,80% --margin 0 --border-label ' #fzf-label 100% '))}", :Enter
|
||||
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||
pane = floating_pane
|
||||
refute_nil pane
|
||||
title = IO.popen(['tmux', 'display-message', '-p', '-t', pane, "\#{pane_title}"], &:read)
|
||||
assert_equal ' #fzf-label 100% ', title.chomp
|
||||
format = IO.popen(['tmux', 'show-options', '-p', '-t', pane, 'pane-border-format'], &:read)
|
||||
assert_includes format, "\#{pane_title}"
|
||||
tmux.send_keys :Enter
|
||||
assert_equal '1', fzf_output
|
||||
end
|
||||
|
||||
def test_floating_pane_become
|
||||
tmux.send_keys "seq 100 | #{fzf(%(--popup center,80% --margin 0 --bind 'enter:become(echo became-{})'))}", :Enter
|
||||
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||
tmux.send_keys :Enter
|
||||
assert_equal 'became-1', fzf_output
|
||||
end
|
||||
|
||||
def test_explicit_border_falls_back_to_popup
|
||||
# display-popup requires an attached client, which the test environment
|
||||
# may not have; intercept it with a tmux shim on PATH
|
||||
dir = Dir.mktmpdir
|
||||
real = `command -v tmux`.chomp
|
||||
shim = File.join(dir, 'tmux')
|
||||
File.write(shim, <<~SH)
|
||||
#!/bin/sh
|
||||
if [ "$1" = display-popup ]; then
|
||||
echo popup-used >&2
|
||||
exit 0
|
||||
fi
|
||||
exec #{real.shellescape} "$@"
|
||||
SH
|
||||
FileUtils.chmod(0o755, shim)
|
||||
tmux.send_keys "seq 100 | PATH=#{dir.shellescape}:$PATH #{FZF} --popup center --border rounded", :Enter
|
||||
tmux.until { |lines| assert lines.any_include?('popup-used') }
|
||||
refute floating_pane
|
||||
ensure
|
||||
FileUtils.remove_entry(dir) if dir
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def floating_pane
|
||||
format = "\#{pane_id} \#{pane_floating_flag}"
|
||||
lines = IO.popen(['tmux', 'list-panes', '-t', tmux.win, '-F', format]) { |io| io.readlines(chomp: true) }
|
||||
lines.filter_map { |line| line.split.first if line.end_with?(' 1') }.first
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,175 @@
|
||||
Execute (Setup):
|
||||
let g:dir = fnamemodify(g:vader_file, ':p:h')
|
||||
unlet! g:fzf_layout g:fzf_action g:fzf_history_dir
|
||||
Log 'Test directory: ' . g:dir
|
||||
Save &acd
|
||||
|
||||
Execute (fzf#run with dir option):
|
||||
let cwd = getcwd()
|
||||
let result = fzf#run({ 'source': 'git ls-files', 'options': '--filter=vdr', 'dir': g:dir })
|
||||
AssertEqual ['fzf.vader'], result
|
||||
AssertEqual 0, haslocaldir()
|
||||
AssertEqual getcwd(), cwd
|
||||
|
||||
execute 'lcd' fnameescape(cwd)
|
||||
let result = sort(fzf#run({ 'source': 'git ls-files', 'options': '--filter e', 'dir': g:dir }))
|
||||
AssertEqual ['fzf.vader'], result
|
||||
AssertEqual 1, haslocaldir()
|
||||
AssertEqual getcwd(), cwd
|
||||
|
||||
Execute (fzf#run with Funcref command):
|
||||
let g:ret = []
|
||||
function! g:FzfTest(e)
|
||||
call add(g:ret, a:e)
|
||||
endfunction
|
||||
let result = sort(fzf#run({ 'source': 'git ls-files', 'sink': function('g:FzfTest'), 'options': '--filter e', 'dir': g:dir }))
|
||||
AssertEqual ['fzf.vader'], result
|
||||
AssertEqual ['fzf.vader'], sort(g:ret)
|
||||
|
||||
Execute (fzf#run with string source):
|
||||
let result = sort(fzf#run({ 'source': 'echo hi', 'options': '-f i' }))
|
||||
AssertEqual ['hi'], result
|
||||
|
||||
Execute (fzf#run with list source):
|
||||
let result = sort(fzf#run({ 'source': ['hello', 'world'], 'options': '-f e' }))
|
||||
AssertEqual ['hello'], result
|
||||
let result = sort(fzf#run({ 'source': ['hello', 'world'], 'options': '-f o' }))
|
||||
AssertEqual ['hello', 'world'], result
|
||||
|
||||
Execute (fzf#run with string source):
|
||||
let result = sort(fzf#run({ 'source': 'echo hi', 'options': '-f i' }))
|
||||
AssertEqual ['hi'], result
|
||||
|
||||
Execute (fzf#run with dir option and noautochdir):
|
||||
set noacd
|
||||
let cwd = getcwd()
|
||||
call fzf#run({'source': ['/foobar'], 'sink': 'e', 'dir': '/tmp', 'options': '-1'})
|
||||
" No change in working directory
|
||||
AssertEqual cwd, getcwd()
|
||||
|
||||
call fzf#run({'source': ['/foobar'], 'sink': 'tabe', 'dir': '/tmp', 'options': '-1'})
|
||||
AssertEqual cwd, getcwd()
|
||||
tabclose
|
||||
AssertEqual cwd, getcwd()
|
||||
|
||||
Execute (Incomplete fzf#run with dir option and autochdir):
|
||||
set acd
|
||||
let cwd = getcwd()
|
||||
call fzf#run({'source': [], 'sink': 'e', 'dir': '/tmp', 'options': '-0'})
|
||||
" No change in working directory even if &acd is set
|
||||
AssertEqual cwd, getcwd()
|
||||
|
||||
Execute (FIXME: fzf#run with dir option and autochdir):
|
||||
set acd
|
||||
call fzf#run({'source': ['/foobar'], 'sink': 'e', 'dir': '/tmp', 'options': '-1'})
|
||||
" Working directory changed due to &acd
|
||||
AssertEqual '/foobar', expand('%')
|
||||
AssertEqual '/', getcwd()
|
||||
|
||||
Execute (fzf#run with dir option and autochdir when final cwd is same as dir):
|
||||
set acd
|
||||
cd /tmp
|
||||
call fzf#run({'source': ['/foobar'], 'sink': 'e', 'dir': '/', 'options': '-1'})
|
||||
" Working directory changed due to &acd
|
||||
AssertEqual '/', getcwd()
|
||||
|
||||
Execute (fzf#wrap):
|
||||
AssertThrows fzf#wrap({'foo': 'bar'})
|
||||
|
||||
let opts = fzf#wrap('foobar')
|
||||
Log opts
|
||||
AssertEqual 0.9, opts.window.width
|
||||
Assert opts.options =~ '--expect='
|
||||
Assert !has_key(opts, 'sink')
|
||||
Assert has_key(opts, 'sink*')
|
||||
|
||||
let opts = fzf#wrap('foobar', {}, 0)
|
||||
Log opts
|
||||
AssertEqual 0.9, opts.window.width
|
||||
|
||||
let opts = fzf#wrap('foobar', {}, 1)
|
||||
Log opts
|
||||
Assert !has_key(opts, 'window')
|
||||
|
||||
let opts = fzf#wrap('foobar', {'down': '50%'})
|
||||
Log opts
|
||||
AssertEqual '50%', opts.down
|
||||
|
||||
let opts = fzf#wrap('foobar', {'down': '50%'}, 1)
|
||||
Log opts
|
||||
Assert !has_key(opts, 'down')
|
||||
|
||||
let opts = fzf#wrap('foobar', {'sink': 'e'})
|
||||
Log opts
|
||||
AssertEqual 'e', opts.sink
|
||||
Assert !has_key(opts, 'sink*')
|
||||
|
||||
let opts = fzf#wrap('foobar', {'options': '--reverse'})
|
||||
Log opts
|
||||
Assert opts.options =~ '--expect='
|
||||
Assert opts.options =~ '--reverse'
|
||||
|
||||
let g:fzf_layout = {'window': 'enew'}
|
||||
let opts = fzf#wrap('foobar')
|
||||
Log opts
|
||||
AssertEqual 'enew', opts.window
|
||||
|
||||
let opts = fzf#wrap('foobar', {}, 1)
|
||||
Log opts
|
||||
Assert !has_key(opts, 'window')
|
||||
|
||||
let opts = fzf#wrap('foobar', {'right': '50%'})
|
||||
Log opts
|
||||
Assert !has_key(opts, 'window')
|
||||
AssertEqual '50%', opts.right
|
||||
|
||||
let opts = fzf#wrap('foobar', {'right': '50%'}, 1)
|
||||
Log opts
|
||||
Assert !has_key(opts, 'window')
|
||||
Assert !has_key(opts, 'right')
|
||||
|
||||
let g:fzf_action = {'a': 'tabe'}
|
||||
let opts = fzf#wrap('foobar')
|
||||
Log opts
|
||||
Assert opts.options =~ '--expect=a'
|
||||
Assert !has_key(opts, 'sink')
|
||||
Assert has_key(opts, 'sink*')
|
||||
|
||||
let opts = fzf#wrap('foobar', {'sink': 'e'})
|
||||
Log opts
|
||||
AssertEqual 'e', opts.sink
|
||||
Assert !has_key(opts, 'sink*')
|
||||
|
||||
let g:fzf_history_dir = '/tmp'
|
||||
let opts = fzf#wrap('foobar', {'options': '--color light'})
|
||||
Log opts
|
||||
Assert opts.options =~ "--history '/tmp/foobar'"
|
||||
Assert opts.options =~ '--color light'
|
||||
|
||||
let g:fzf_colors = { 'fg': ['fg', 'Error'] }
|
||||
let opts = fzf#wrap({})
|
||||
Assert opts.options =~ '--color=fg:'
|
||||
|
||||
Execute (fzf#shellescape with sh):
|
||||
AssertEqual '''''', fzf#shellescape('', 'sh')
|
||||
AssertEqual '''\''', fzf#shellescape('\', 'sh')
|
||||
AssertEqual '''""''', fzf#shellescape('""', 'sh')
|
||||
AssertEqual '''foobar>''', fzf#shellescape('foobar>', 'sh')
|
||||
AssertEqual '''\\\"\\\''', fzf#shellescape('\\\"\\\', 'sh')
|
||||
AssertEqual '''echo ''\''''a''\'''' && echo ''\''''b''\''''''', fzf#shellescape('echo ''a'' && echo ''b''', 'sh')
|
||||
|
||||
Execute (fzf#shellescape with cmd.exe):
|
||||
AssertEqual '^"^"', fzf#shellescape('', 'cmd.exe')
|
||||
AssertEqual '^"\\^"', fzf#shellescape('\', 'cmd.exe')
|
||||
AssertEqual '^"\^"\^"^"', fzf#shellescape('""', 'cmd.exe')
|
||||
AssertEqual '^"foobar^>^"', fzf#shellescape('foobar>', 'cmd.exe')
|
||||
AssertEqual '^"\\\\\\\^"\\\\\\^"', fzf#shellescape('\\\"\\\', 'cmd.exe')
|
||||
AssertEqual '^"echo ''a'' ^&^& echo ''b''^"', fzf#shellescape('echo ''a'' && echo ''b''', 'cmd.exe')
|
||||
|
||||
AssertEqual '^"C:\Program Files ^(x86^)\\^"', fzf#shellescape('C:\Program Files (x86)\', 'cmd.exe')
|
||||
AssertEqual '^"C:/Program Files ^(x86^)/^"', fzf#shellescape('C:/Program Files (x86)/', 'cmd.exe')
|
||||
AssertEqual '^"%%USERPROFILE%%^"', fzf#shellescape('%USERPROFILE%', 'cmd.exe')
|
||||
|
||||
Execute (Cleanup):
|
||||
unlet g:dir
|
||||
Restore
|
||||
Reference in New Issue
Block a user