mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2024-12-23 11:29:01 +00:00
Merge pull request #32 from junegunn/nth
Add --nth and --delimiter option
This commit is contained in:
commit
52b74abb99
10
README.md
10
README.md
@ -53,6 +53,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
|
||||||
|
-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.
|
||||||
-i Case-insensitive match (default: smart-case match)
|
-i Case-insensitive match (default: smart-case match)
|
||||||
@ -177,6 +179,14 @@ fco() {
|
|||||||
git checkout $(echo "$commit" | sed "s/ .*//")
|
git checkout $(echo "$commit" | sed "s/ .*//")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ftags - search ctags
|
||||||
|
ftags() {
|
||||||
|
local line
|
||||||
|
[ -e tags ] &&
|
||||||
|
line=$(grep -v "^!" tags | cut -f1-3 | cut -c1-80 | fzf --nth=1) &&
|
||||||
|
$EDITOR $(cut -f2 <<< "$line")
|
||||||
|
}
|
||||||
|
|
||||||
# fq1 [QUERY]
|
# fq1 [QUERY]
|
||||||
# - Immediately select the file when there's only one match.
|
# - Immediately select the file when there's only one match.
|
||||||
# If not, start the fuzzy finder as usual.
|
# If not, start the fuzzy finder as usual.
|
||||||
|
84
fzf
84
fzf
@ -7,7 +7,7 @@
|
|||||||
# / __/ / /_/ __/
|
# / __/ / /_/ __/
|
||||||
# /_/ /___/_/ Fuzzy finder for your shell
|
# /_/ /___/_/ Fuzzy finder for your shell
|
||||||
#
|
#
|
||||||
# Version: 0.8.2 (March 15, 2014)
|
# Version: 0.8.2 (March 30, 2014)
|
||||||
#
|
#
|
||||||
# Author: Junegunn Choi
|
# Author: Junegunn Choi
|
||||||
# URL: https://github.com/junegunn/fzf
|
# URL: https://github.com/junegunn/fzf
|
||||||
@ -50,7 +50,8 @@ end
|
|||||||
|
|
||||||
class FZF
|
class FZF
|
||||||
C = Curses
|
C = Curses
|
||||||
attr_reader :rxflag, :sort, :color, :black, :ansi256, :mouse, :multi, :query, :filter, :extended
|
attr_reader :rxflag, :sort, :nth, :color, :black, :ansi256,
|
||||||
|
:mouse, :multi, :query, :filter, :extended
|
||||||
|
|
||||||
class AtomicVar
|
class AtomicVar
|
||||||
def initialize value
|
def initialize value
|
||||||
@ -83,6 +84,8 @@ class FZF
|
|||||||
@mouse = true
|
@mouse = true
|
||||||
@extended = nil
|
@extended = nil
|
||||||
@filter = nil
|
@filter = nil
|
||||||
|
@nth = nil
|
||||||
|
@delim = nil
|
||||||
|
|
||||||
argv =
|
argv =
|
||||||
if opts = ENV['FZF_DEFAULT_OPTS']
|
if opts = ENV['FZF_DEFAULT_OPTS']
|
||||||
@ -120,6 +123,17 @@ class FZF
|
|||||||
@filter = query
|
@filter = query
|
||||||
when /^-f(.*)$/, /^--filter=(.*)$/
|
when /^-f(.*)$/, /^--filter=(.*)$/
|
||||||
@filter = $1
|
@filter = $1
|
||||||
|
when '-n', '--nth'
|
||||||
|
usage 1, 'field number required' unless nth = argv.shift
|
||||||
|
usage 1, 'invalid field number' if nth.to_i == 0
|
||||||
|
@nth = nth.to_i
|
||||||
|
when /^-n(-?[1-9][0-9]*)$/, /^--nth=(-?[1-9][0-9]*)$/
|
||||||
|
@nth = $1.to_i
|
||||||
|
when '-d', '--delimiter'
|
||||||
|
usage 1, 'delimiter required' unless delim = argv.shift
|
||||||
|
@delim = FZF.build_delim_regex delim
|
||||||
|
when /^-d(.+)$/, /^--delimiter=(.+)$/
|
||||||
|
@delim = FZF.build_delim_regex $1
|
||||||
when '-s', '--sort'
|
when '-s', '--sort'
|
||||||
usage 1, 'sort size required' unless sort = argv.shift
|
usage 1, 'sort size required' unless sort = argv.shift
|
||||||
usage 1, 'invalid sort size' unless sort =~ /^[0-9]+$/
|
usage 1, 'invalid sort size' unless sort =~ /^[0-9]+$/
|
||||||
@ -155,6 +169,11 @@ class FZF
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def FZF.build_delim_regex delim
|
||||||
|
Regexp.compile(delim) rescue (delim = Regexp.escape(delim))
|
||||||
|
Regexp.compile "(?:.*?#{delim})|(?:.+?$)"
|
||||||
|
end
|
||||||
|
|
||||||
def start
|
def start
|
||||||
if @filter
|
if @filter
|
||||||
start_reader(false).join
|
start_reader(false).join
|
||||||
@ -181,9 +200,9 @@ class FZF
|
|||||||
|
|
||||||
def get_matcher
|
def get_matcher
|
||||||
if @extended
|
if @extended
|
||||||
ExtendedFuzzyMatcher.new @rxflag, @extended
|
ExtendedFuzzyMatcher.new @rxflag, @extended, @nth, @delim
|
||||||
else
|
else
|
||||||
FuzzyMatcher.new @rxflag
|
FuzzyMatcher.new @rxflag, @nth, @delim
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -208,6 +227,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
|
||||||
|
-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.
|
||||||
-i Case-insensitive match (default: smart-case match)
|
-i Case-insensitive match (default: smart-case match)
|
||||||
@ -1026,10 +1047,55 @@ class FZF
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class Matcher
|
||||||
|
class MatchData
|
||||||
|
def initialize n
|
||||||
|
@n = n
|
||||||
|
end
|
||||||
|
|
||||||
|
def offset _
|
||||||
|
@n
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize nth, delim
|
||||||
|
@nth = nth && (nth > 0 ? nth - 1 : nth)
|
||||||
|
@delim = delim
|
||||||
|
@tokens_cache = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def tokenize str
|
||||||
|
@tokens_cache[str] ||=
|
||||||
|
unless @delim
|
||||||
|
# AWK default
|
||||||
|
prefix_length = str[/^\s+/].length rescue 0
|
||||||
|
[prefix_length, (str.strip.scan(/\S+\s*/) rescue [])]
|
||||||
|
else
|
||||||
|
prefix_length = 0
|
||||||
|
[prefix_length, (str.scan(@delim) rescue [])]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def do_match str, pat
|
||||||
|
if @nth
|
||||||
|
prefix_length, tokens = tokenize str
|
||||||
|
|
||||||
|
if (token = tokens[@nth]) && (md = token.match(pat) rescue nil)
|
||||||
|
prefix_length += (tokens[0...@nth] || []).join.length
|
||||||
|
offset = md.offset(0).map { |o| o + prefix_length }
|
||||||
|
MatchData.new offset
|
||||||
|
end
|
||||||
|
else
|
||||||
|
str.match(pat) rescue nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class FuzzyMatcher < Matcher
|
class FuzzyMatcher < Matcher
|
||||||
attr_reader :caches, :rxflag
|
attr_reader :caches, :rxflag
|
||||||
|
|
||||||
def initialize rxflag
|
def initialize rxflag, nth = nil, delim = nil
|
||||||
|
super nth, delim
|
||||||
@caches = Hash.new { |h, k| h[k] = {} }
|
@caches = Hash.new { |h, k| h[k] = {} }
|
||||||
@regexp = {}
|
@regexp = {}
|
||||||
@rxflag = rxflag
|
@rxflag = rxflag
|
||||||
@ -1073,15 +1139,15 @@ class FZF
|
|||||||
cache[q] ||= (partial_cache ?
|
cache[q] ||= (partial_cache ?
|
||||||
partial_cache.map { |e| e.first } : list).map { |line|
|
partial_cache.map { |e| e.first } : list).map { |line|
|
||||||
# Ignore errors: e.g. invalid byte sequence in UTF-8
|
# Ignore errors: e.g. invalid byte sequence in UTF-8
|
||||||
md = line.match(regexp) rescue nil
|
md = do_match(line, regexp)
|
||||||
md && [line, [md.offset(0)]]
|
md && [line, [md.offset(0)]]
|
||||||
}.compact
|
}.compact
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class ExtendedFuzzyMatcher < FuzzyMatcher
|
class ExtendedFuzzyMatcher < FuzzyMatcher
|
||||||
def initialize rxflag, mode = :fuzzy
|
def initialize rxflag, mode = :fuzzy, nth = nil, delim = nil
|
||||||
super rxflag
|
super rxflag, nth, delim
|
||||||
@regexps = {}
|
@regexps = {}
|
||||||
@mode = mode
|
@mode = mode
|
||||||
end
|
end
|
||||||
@ -1143,7 +1209,7 @@ class FZF
|
|||||||
offsets = []
|
offsets = []
|
||||||
regexps.all? { |pair|
|
regexps.all? { |pair|
|
||||||
regexp, invert = pair
|
regexp, invert = pair
|
||||||
md = line.match(regexp) rescue nil
|
md = do_match(line, regexp)
|
||||||
if md && !invert
|
if md && !invert
|
||||||
offsets << md.offset(0)
|
offsets << md.offset(0)
|
||||||
elsif !md && invert
|
elsif !md && invert
|
||||||
|
@ -27,8 +27,10 @@ class TestFZF < MiniTest::Unit::TestCase
|
|||||||
ENV['FZF_DEFAULT_SORT'] = '20000'
|
ENV['FZF_DEFAULT_SORT'] = '20000'
|
||||||
fzf = FZF.new []
|
fzf = FZF.new []
|
||||||
assert_equal 20000, fzf.sort
|
assert_equal 20000, fzf.sort
|
||||||
|
assert_equal nil, fzf.nth
|
||||||
|
|
||||||
ENV['FZF_DEFAULT_OPTS'] = '-x -m -s 10000 -q " hello world " +c +2 --no-mouse -f "goodbye world" --black'
|
ENV['FZF_DEFAULT_OPTS'] =
|
||||||
|
'-x -m -s 10000 -q " hello world " +c +2 --no-mouse -f "goodbye world" --black --nth=3'
|
||||||
fzf = FZF.new []
|
fzf = FZF.new []
|
||||||
assert_equal 10000, fzf.sort
|
assert_equal 10000, fzf.sort
|
||||||
assert_equal ' hello world ',
|
assert_equal ' hello world ',
|
||||||
@ -41,12 +43,13 @@ 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
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_option_parser
|
def test_option_parser
|
||||||
# Long opts
|
# Long opts
|
||||||
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=howdy --extended-exact --no-mouse --no-256]
|
--filter=howdy --extended-exact --no-mouse --no-256 --nth=1]
|
||||||
assert_equal 2000, fzf.sort
|
assert_equal 2000, fzf.sort
|
||||||
assert_equal true, fzf.multi
|
assert_equal true, fzf.multi
|
||||||
assert_equal false, fzf.color
|
assert_equal false, fzf.color
|
||||||
@ -57,9 +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
|
||||||
|
|
||||||
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
|
--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
|
||||||
@ -71,9 +75,10 @@ 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
|
||||||
|
|
||||||
# Short opts
|
# Short opts
|
||||||
fzf = FZF.new %w[-s 2000 +c -m +i -qhello -x -fhowdy +2]
|
fzf = FZF.new %w[-s 2000 +c -m +i -qhello -x -fhowdy +2 -n3]
|
||||||
assert_equal 2000, fzf.sort
|
assert_equal 2000, fzf.sort
|
||||||
assert_equal true, fzf.multi
|
assert_equal true, fzf.multi
|
||||||
assert_equal false, fzf.color
|
assert_equal false, fzf.color
|
||||||
@ -82,9 +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
|
||||||
|
|
||||||
# Left-to-right
|
# Left-to-right
|
||||||
fzf = FZF.new %w[-s 2000 +c -m +i -qhello -x -fgoodbye +2
|
fzf = FZF.new %w[-s 2000 +c -m +i -qhello -x -fgoodbye +2 -n3 -n4
|
||||||
-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
|
||||||
@ -95,6 +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
|
||||||
|
|
||||||
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
|
||||||
@ -109,6 +116,12 @@ class TestFZF < MiniTest::Unit::TestCase
|
|||||||
fzf = FZF.new argv
|
fzf = FZF.new argv
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
assert_raises(SystemExit) do
|
||||||
|
fzf = FZF.new %w[--nth=0]
|
||||||
|
end
|
||||||
|
assert_raises(SystemExit) do
|
||||||
|
fzf = FZF.new %w[-n 0]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# FIXME Only on 1.9 or above
|
# FIXME Only on 1.9 or above
|
||||||
@ -476,5 +489,46 @@ class TestFZF < MiniTest::Unit::TestCase
|
|||||||
sleep interval
|
sleep interval
|
||||||
assert_equal false, me.double?(20)
|
assert_equal false, me.double?(20)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_nth_match
|
||||||
|
list = [
|
||||||
|
' first second third',
|
||||||
|
'fourth fifth sixth',
|
||||||
|
]
|
||||||
|
|
||||||
|
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE
|
||||||
|
assert_equal list, matcher.match(list, 'f', '', '').map(&:first)
|
||||||
|
assert_equal [
|
||||||
|
[list[0], [[2, 5]]],
|
||||||
|
[list[1], [[9, 17]]]], matcher.match(list, 'is', '', '')
|
||||||
|
|
||||||
|
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, 2
|
||||||
|
assert_equal [[list[1], [[8, 9]]]], matcher.match(list, 'f', '', '')
|
||||||
|
assert_equal [[list[0], [[8, 9]]]], matcher.match(list, 's', '', '')
|
||||||
|
|
||||||
|
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, 3
|
||||||
|
assert_equal [[list[0], [[19, 20]]]], matcher.match(list, 'r', '', '')
|
||||||
|
|
||||||
|
regex = FZF.build_delim_regex "\t"
|
||||||
|
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, 1, regex
|
||||||
|
assert_equal [[list[0], [[3, 10]]]], matcher.match(list, 're', '', '')
|
||||||
|
|
||||||
|
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, 2, regex
|
||||||
|
assert_equal [], matcher.match(list, 'r', '', '')
|
||||||
|
assert_equal [[list[1], [[9, 17]]]], matcher.match(list, 'is', '', '')
|
||||||
|
|
||||||
|
# Negative indexing
|
||||||
|
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, -1, regex
|
||||||
|
assert_equal [[list[0], [[3, 6]]]], matcher.match(list, 'rt', '', '')
|
||||||
|
assert_equal [[list[0], [[2, 5]]], [list[1], [[9, 17]]]], matcher.match(list, 'is', '', '')
|
||||||
|
|
||||||
|
# Regex delimiter
|
||||||
|
regex = FZF.build_delim_regex "[ \t]+"
|
||||||
|
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, 1, regex
|
||||||
|
assert_equal [list[1]], matcher.match(list, 'f', '', '').map(&:first)
|
||||||
|
|
||||||
|
matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE, 2, regex
|
||||||
|
assert_equal [[list[0], [[1, 2]]], [list[1], [[8, 9]]]], matcher.match(list, 'f', '', '')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user