Allow --nth option to take multiple indexes (comma-separated)

This commit is contained in:
Junegunn Choi 2014-04-02 01:49:07 +09:00
parent 608ec2b806
commit ab9fbf1967
3 changed files with 52 additions and 28 deletions

View File

@ -56,7 +56,8 @@ usage: fzf [options]
-e, --extended-exact Extended-search mode (exact match) -e, --extended-exact Extended-search mode (exact match)
-q, --query=STR Initial query -q, --query=STR Initial query
-f, --filter=STR Filter mode. Do not start interactive finder. -f, --filter=STR Filter mode. Do not start interactive finder.
-n, --nth=[-]N Match only in the N-th token of the item -n, --nth=[-]N[,..] Comma-separated list of field indexes for limiting
search scope (positive or negative integers)
-d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style) -d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style)
-s, --sort=MAX Maximum number of matched items to sort (default: 1000) -s, --sort=MAX Maximum number of matched items to sort (default: 1000)
+s, --no-sort Do not sort the result. Keep the sequence unchanged. +s, --no-sort Do not sort the result. Keep the sequence unchanged.
@ -186,8 +187,11 @@ fco() {
ftags() { ftags() {
local line local line
[ -e tags ] && [ -e tags ] &&
line=$(grep -v "^!" tags | cut -f1-3 | cut -c1-80 | fzf --nth=1) && line=$(
$EDITOR $(cut -f2 <<< "$line") awk 'BEGIN { FS="\t" } !/^!/ {print toupper($4)"\t"$1"\t"$2"\t"$3}' tags |
cut -c1-80 | fzf --nth=1,2
) && $EDITOR $(cut -f3 <<< "$line") -c "set nocst" \
-c "silent tag $(cut -f2 <<< "$line")"
} }
# fq1 [QUERY] # fq1 [QUERY]

30
fzf
View File

@ -7,7 +7,7 @@
# / __/ / /_/ __/ # / __/ / /_/ __/
# /_/ /___/_/ Fuzzy finder for your shell # /_/ /___/_/ Fuzzy finder for your shell
# #
# Version: 0.8.2 (March 30, 2014) # Version: 0.8.3 (April 2, 2014)
# #
# Author: Junegunn Choi # Author: Junegunn Choi
# URL: https://github.com/junegunn/fzf # URL: https://github.com/junegunn/fzf
@ -126,9 +126,9 @@ class FZF
when '-n', '--nth' when '-n', '--nth'
usage 1, 'field number required' unless nth = argv.shift usage 1, 'field number required' unless nth = argv.shift
usage 1, 'invalid field number' if nth.to_i == 0 usage 1, 'invalid field number' if nth.to_i == 0
@nth = nth.to_i @nth = parse_nth nth
when /^-n(-?[1-9][0-9]*)$/, /^--nth=(-?[1-9][0-9]*)$/ when /^-n([0-9,-]+)$/, /^--nth=([0-9,-]+)$/
@nth = $1.to_i @nth = parse_nth $1
when '-d', '--delimiter' when '-d', '--delimiter'
usage 1, 'delimiter required' unless delim = argv.shift usage 1, 'delimiter required' unless delim = argv.shift
@delim = FZF.build_delim_regex delim @delim = FZF.build_delim_regex delim
@ -169,6 +169,14 @@ class FZF
end end
end end
def parse_nth nth
nth.split(',').map { |n|
ni = n.to_i
usage 1, "invalid field number: #{n}" if ni == 0
ni
}
end
def FZF.build_delim_regex delim def FZF.build_delim_regex delim
Regexp.compile(delim) rescue (delim = Regexp.escape(delim)) Regexp.compile(delim) rescue (delim = Regexp.escape(delim))
Regexp.compile "(?:.*?#{delim})|(?:.+?$)" Regexp.compile "(?:.*?#{delim})|(?:.+?$)"
@ -227,7 +235,8 @@ class FZF
-e, --extended-exact Extended-search mode (exact match) -e, --extended-exact Extended-search mode (exact match)
-q, --query=STR Initial query -q, --query=STR Initial query
-f, --filter=STR Filter mode. Do not start interactive finder. -f, --filter=STR Filter mode. Do not start interactive finder.
-n, --nth=[-]N Match only in the N-th token of the item -n, --nth=[-]N[,..] Comma-separated list of field indexes for limiting
search scope (positive or negative integers)
-d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style) -d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style)
-s, --sort=MAX Maximum number of matched items to sort (default: 1000) -s, --sort=MAX Maximum number of matched items to sort (default: 1000)
+s, --no-sort Do not sort the result. Keep the sequence unchanged. +s, --no-sort Do not sort the result. Keep the sequence unchanged.
@ -1059,7 +1068,7 @@ class FZF
end end
def initialize nth, delim def initialize nth, delim
@nth = nth && (nth > 0 ? nth - 1 : nth) @nth = nth && nth.map { |n| n > 0 ? n - 1 : n }
@delim = delim @delim = delim
@tokens_cache = {} @tokens_cache = {}
end end
@ -1080,11 +1089,14 @@ class FZF
if @nth if @nth
prefix_length, tokens = tokenize str prefix_length, tokens = tokenize str
if (token = tokens[@nth]) && (md = token.match(pat) rescue nil) @nth.each do |n|
prefix_length += (tokens[0...@nth] || []).join.length if (token = tokens[n]) && (md = token.match(pat) rescue nil)
prefix_length += (tokens[0...n] || []).join.length
offset = md.offset(0).map { |o| o + prefix_length } offset = md.offset(0).map { |o| o + prefix_length }
MatchData.new offset return MatchData.new offset
end end
end
nil
else else
str.match(pat) rescue nil str.match(pat) rescue nil
end end

View File

@ -30,7 +30,7 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal nil, fzf.nth assert_equal nil, fzf.nth
ENV['FZF_DEFAULT_OPTS'] = ENV['FZF_DEFAULT_OPTS'] =
'-x -m -s 10000 -q " hello world " +c +2 --no-mouse -f "goodbye world" --black --nth=3' '-x -m -s 10000 -q " hello world " +c +2 --no-mouse -f "goodbye world" --black --nth=3,-1,2'
fzf = FZF.new [] fzf = FZF.new []
assert_equal 10000, fzf.sort assert_equal 10000, fzf.sort
assert_equal ' hello world ', assert_equal ' hello world ',
@ -43,7 +43,7 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal false, fzf.ansi256 assert_equal false, fzf.ansi256
assert_equal true, fzf.black assert_equal true, fzf.black
assert_equal false, fzf.mouse assert_equal false, fzf.mouse
assert_equal 3, fzf.nth assert_equal [3, -1, 2], fzf.nth
end end
def test_option_parser def test_option_parser
@ -60,10 +60,10 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal 'hello', fzf.query.get assert_equal 'hello', fzf.query.get
assert_equal 'howdy', fzf.filter assert_equal 'howdy', fzf.filter
assert_equal :exact, fzf.extended assert_equal :exact, fzf.extended
assert_equal 1, fzf.nth assert_equal [1], fzf.nth
fzf = FZF.new %w[--sort=2000 --no-color --multi +i --query hello fzf = FZF.new %w[--sort=2000 --no-color --multi +i --query hello
--filter a --filter b --no-256 --black --nth 2 --filter a --filter b --no-256 --black --nth -2
--no-sort -i --color --no-multi --256] --no-sort -i --color --no-multi --256]
assert_equal nil, fzf.sort assert_equal nil, fzf.sort
assert_equal false, fzf.multi assert_equal false, fzf.multi
@ -75,7 +75,7 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal 'b', fzf.filter assert_equal 'b', fzf.filter
assert_equal 'hello', fzf.query.get assert_equal 'hello', fzf.query.get
assert_equal nil, fzf.extended assert_equal nil, fzf.extended
assert_equal 2, fzf.nth assert_equal [-2], fzf.nth
# Short opts # Short opts
fzf = FZF.new %w[-s 2000 +c -m +i -qhello -x -fhowdy +2 -n3] fzf = FZF.new %w[-s 2000 +c -m +i -qhello -x -fhowdy +2 -n3]
@ -87,10 +87,10 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal 'hello', fzf.query.get assert_equal 'hello', fzf.query.get
assert_equal 'howdy', fzf.filter assert_equal 'howdy', fzf.filter
assert_equal :fuzzy, fzf.extended assert_equal :fuzzy, fzf.extended
assert_equal 3, fzf.nth assert_equal [3], fzf.nth
# Left-to-right # Left-to-right
fzf = FZF.new %w[-s 2000 +c -m +i -qhello -x -fgoodbye +2 -n3 -n4 fzf = FZF.new %w[-s 2000 +c -m +i -qhello -x -fgoodbye +2 -n3 -n4,5
-s 3000 -c +m -i -q world +x -fworld -2 --black --no-black] -s 3000 -c +m -i -q world +x -fworld -2 --black --no-black]
assert_equal 3000, fzf.sort assert_equal 3000, fzf.sort
assert_equal false, fzf.multi assert_equal false, fzf.multi
@ -101,7 +101,7 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal 'world', fzf.query.get assert_equal 'world', fzf.query.get
assert_equal 'world', fzf.filter assert_equal 'world', fzf.filter
assert_equal nil, fzf.extended assert_equal nil, fzf.extended
assert_equal 4, fzf.nth assert_equal [4, 5], fzf.nth
fzf = FZF.new %w[--query hello +s -s 2000 --query=world] fzf = FZF.new %w[--query hello +s -s 2000 --query=world]
assert_equal 2000, fzf.sort assert_equal 2000, fzf.sort
@ -502,32 +502,40 @@ class TestFZF < MiniTest::Unit::TestCase
[list[0], [[2, 5]]], [list[0], [[2, 5]]],
[list[1], [[9, 17]]]], matcher.match(list, 'is', '', '') [list[1], [[9, 17]]]], matcher.match(list, 'is', '', '')
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, 2 matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [2]
assert_equal [[list[1], [[8, 9]]]], matcher.match(list, 'f', '', '') assert_equal [[list[1], [[8, 9]]]], matcher.match(list, 'f', '', '')
assert_equal [[list[0], [[8, 9]]]], matcher.match(list, 's', '', '') assert_equal [[list[0], [[8, 9]]]], matcher.match(list, 's', '', '')
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, 3 matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [3]
assert_equal [[list[0], [[19, 20]]]], matcher.match(list, 'r', '', '') assert_equal [[list[0], [[19, 20]]]], matcher.match(list, 'r', '', '')
# Comma-separated
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [3, 1]
assert_equal [[list[0], [[19, 20]]], [list[1], [[3, 4]]]], matcher.match(list, 'r', '', '')
# Ordered
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [1, 3]
assert_equal [[list[0], [[3, 4]]], [list[1], [[3, 4]]]], matcher.match(list, 'r', '', '')
regex = FZF.build_delim_regex "\t" regex = FZF.build_delim_regex "\t"
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, 1, regex matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [1], regex
assert_equal [[list[0], [[3, 10]]]], matcher.match(list, 're', '', '') assert_equal [[list[0], [[3, 10]]]], matcher.match(list, 're', '', '')
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, 2, regex matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [2], regex
assert_equal [], matcher.match(list, 'r', '', '') assert_equal [], matcher.match(list, 'r', '', '')
assert_equal [[list[1], [[9, 17]]]], matcher.match(list, 'is', '', '') assert_equal [[list[1], [[9, 17]]]], matcher.match(list, 'is', '', '')
# Negative indexing # Negative indexing
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, -1, regex matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [-1], regex
assert_equal [[list[0], [[3, 6]]]], matcher.match(list, 'rt', '', '') assert_equal [[list[0], [[3, 6]]]], matcher.match(list, 'rt', '', '')
assert_equal [[list[0], [[2, 5]]], [list[1], [[9, 17]]]], matcher.match(list, 'is', '', '') assert_equal [[list[0], [[2, 5]]], [list[1], [[9, 17]]]], matcher.match(list, 'is', '', '')
# Regex delimiter # Regex delimiter
regex = FZF.build_delim_regex "[ \t]+" regex = FZF.build_delim_regex "[ \t]+"
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, 1, regex matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [1], regex
assert_equal [list[1]], matcher.match(list, 'f', '', '').map(&:first) assert_equal [list[1]], matcher.match(list, 'f', '', '').map(&:first)
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, 2, regex matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, [2], regex
assert_equal [[list[0], [[1, 2]]], [list[1], [[8, 9]]]], matcher.match(list, 'f', '', '') assert_equal [[list[0], [[1, 2]]], [list[1], [[8, 9]]]], matcher.match(list, 'f', '', '')
end end
end end