diff --git a/README.md b/README.md index b0c8203..baa73f2 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ usage: fzf [options] Options -m, --multi Enable multi-select -x, --extended Extended-search mode + -e, --extended-exact Extended-search mode (exact match) -q, --query=STR Initial query -f, --filter=STR Filter mode. Do not start interactive finder. -s, --sort=MAX Maximum number of matched items to sort (default: 1000) @@ -120,6 +121,9 @@ such as: `^music .mp3$ sbtrkt !rmx` | `'wild` | Items that include `wild` | exact-match (quoted) | | `!'fire` | Items that do not include `fire` | inverse-exact-match | +If you don't need fuzzy matching and do not wish to "quote" every word, start +fzf with `-e` or `--extended-exact` option. + Useful examples --------------- diff --git a/fzf b/fzf index 84c1f11..180a53d 100755 --- a/fzf +++ b/fzf @@ -7,7 +7,7 @@ # / __/ / /_/ __/ # /_/ /___/_/ Fuzzy finder for your shell # -# Version: 0.7.3 (February 20, 2014) +# Version: 0.7.3 (March 4, 2014) # # Author: Junegunn Choi # URL: https://github.com/junegunn/fzf @@ -78,7 +78,7 @@ class FZF @sort = ENV.fetch('FZF_DEFAULT_SORT', 1000).to_i @color = true @multi = false - @extended = false + @extended = nil @mouse = true @filter = nil @@ -95,8 +95,8 @@ class FZF when '-h', '--help' then usage 0 when '-m', '--multi' then @multi = true when '+m', '--no-multi' then @multi = false - when '-x', '--extended' then @extended = true - when '+x', '--no-extended' then @extended = false + when '-x', '--extended' then @extended = :fuzzy + when '+x', '--no-extended' then @extended = nil when '-i' then @rxflag = Regexp::IGNORECASE when '+i' then @rxflag = 0 when '-c', '--color' then @color = true @@ -119,6 +119,8 @@ class FZF @sort = sort.to_i when /^-s([0-9]+)$/, /^--sort=([0-9]+)$/ @sort = $1.to_i + when '-e', '--extended-exact' then @extended = :exact + when '+e', '--no-extended-exact' then @extended = nil else usage 1, "illegal option: #{o}" end @@ -162,14 +164,21 @@ class FZF end def filter_list list - matcher = (@extended ? ExtendedFuzzyMatcher : FuzzyMatcher).new @rxflag - matches = matcher.match(list, @filter, '', '') + matches = get_matcher.match(list, @filter, '', '') if @sort && matches.length <= @sort matches = sort_by_rank(matches) end matches.each { |m| puts m.first } end + def get_matcher + if @extended + ExtendedFuzzyMatcher.new @rxflag, @extended + else + FuzzyMatcher.new @rxflag + end + end + def version File.open(__FILE__, 'r') do |f| f.each_line do |line| @@ -188,6 +197,7 @@ class FZF Options -m, --multi Enable multi-select -x, --extended Extended-search mode + -e, --extended-exact Extended-search mode (exact match) -q, --query=STR Initial query -f, --filter=STR Filter mode. Do not start interactive finder. -s, --sort=MAX Maximum number of matched items to sort (default: 1000) @@ -582,7 +592,7 @@ class FZF end def start_search - matcher = (@extended ? ExtendedFuzzyMatcher : FuzzyMatcher).new @rxflag + matcher = get_matcher searcher = Thread.new { lists = [] events = {} @@ -952,9 +962,10 @@ class FZF end class ExtendedFuzzyMatcher < FuzzyMatcher - def initialize rxflag - super + def initialize rxflag, mode = :fuzzy + super rxflag @regexps = {} + @mode = mode end def empty? q @@ -977,8 +988,11 @@ class FZF when /^\^(.*)\$$/ Regexp.new('^' << sanitize(Regexp.escape($1)) << '$', rxflag_for(w)) when /^'/ - w.length > 1 ? - Regexp.new(sanitize(Regexp.escape(w[1..-1])), rxflag_for(w)) : nil + if @mode == :fuzzy && w.length > 1 + exact_regex w[1..-1] + elsif @mode == :exact + exact_regex w + end when /^\^/ w.length > 1 ? Regexp.new('^' << sanitize(Regexp.escape(w[1..-1])), rxflag_for(w)) : nil @@ -986,11 +1000,15 @@ class FZF w.length > 1 ? Regexp.new(sanitize(Regexp.escape(w[0..-2])) << '$', rxflag_for(w)) : nil else - fuzzy_regex w + @mode == :fuzzy ? fuzzy_regex(w) : exact_regex(w) end, invert ] }.select { |pair| pair.first } end + def exact_regex w + Regexp.new(sanitize(Regexp.escape(w)), rxflag_for(w)) + end + def match list, q, prefix, suffix regexps = parse q # Look for prefix cache diff --git a/test/test_fzf.rb b/test/test_fzf.rb index 4be0846..d100665 100644 --- a/test/test_fzf.rb +++ b/test/test_fzf.rb @@ -36,7 +36,7 @@ class TestFZF < MiniTest::Unit::TestCase fzf.query.get assert_equal 'goodbye world', fzf.filter - assert_equal true, fzf.extended + assert_equal :fuzzy, fzf.extended assert_equal true, fzf.multi assert_equal false, fzf.color assert_equal false, fzf.mouse @@ -45,7 +45,7 @@ class TestFZF < MiniTest::Unit::TestCase def test_option_parser # Long opts fzf = FZF.new %w[--sort=2000 --no-color --multi +i --query hello - --filter=howdy --extended --no-mouse] + --filter=howdy --extended-exact --no-mouse] assert_equal 2000, fzf.sort assert_equal true, fzf.multi assert_equal false, fzf.color @@ -53,7 +53,7 @@ class TestFZF < MiniTest::Unit::TestCase assert_equal 0, fzf.rxflag assert_equal 'hello', fzf.query.get assert_equal 'howdy', fzf.filter - assert_equal true, fzf.extended + assert_equal :exact, fzf.extended fzf = FZF.new %w[--sort=2000 --no-color --multi +i --query hello --filter a --filter b @@ -65,7 +65,7 @@ class TestFZF < MiniTest::Unit::TestCase assert_equal 1, fzf.rxflag assert_equal 'b', fzf.filter assert_equal 'hello', fzf.query.get - assert_equal false, fzf.extended + assert_equal nil, fzf.extended # Short opts fzf = FZF.new %w[-s 2000 +c -m +i -qhello -x -fhowdy] @@ -75,7 +75,7 @@ class TestFZF < MiniTest::Unit::TestCase assert_equal 0, fzf.rxflag assert_equal 'hello', fzf.query.get assert_equal 'howdy', fzf.filter - assert_equal true, fzf.extended + assert_equal :fuzzy, fzf.extended # Left-to-right fzf = FZF.new %w[-s 2000 +c -m +i -qhello -x -fgoodbye @@ -86,7 +86,7 @@ class TestFZF < MiniTest::Unit::TestCase assert_equal 1, fzf.rxflag assert_equal 'world', fzf.query.get assert_equal 'world', fzf.filter - assert_equal false, fzf.extended + assert_equal nil, fzf.extended fzf = FZF.new %w[--query hello +s -s 2000 --query=world] assert_equal 2000, fzf.sort @@ -378,6 +378,25 @@ class TestFZF < MiniTest::Unit::TestCase FZF.new([]).sort_by_rank(xmatcher.match(list, '01 __', '', ''))) end + def test_extended_exact_mode + exact = FZF::ExtendedFuzzyMatcher.new Regexp::IGNORECASE, :exact + fuzzy = FZF::ExtendedFuzzyMatcher.new Regexp::IGNORECASE, :fuzzy + list = %w[ + extended-exact-mode-not-fuzzy + extended'-fuzzy-mode + ] + assert_equal 2, fuzzy.match(list, 'extended', '', '').length + assert_equal 2, fuzzy.match(list, 'mode extended', '', '').length + assert_equal 2, fuzzy.match(list, 'xtndd', '', '').length + assert_equal 2, fuzzy.match(list, "'-fuzzy", '', '').length + + assert_equal 2, exact.match(list, 'extended', '', '').length + assert_equal 2, exact.match(list, 'mode extended', '', '').length + assert_equal 0, exact.match(list, 'xtndd', '', '').length + assert_equal 1, exact.match(list, "'-fuzzy", '', '').length + assert_equal 2, exact.match(list, "-fuzzy", '', '').length + end + if RUBY_PLATFORM =~ /darwin/ NFD = '한글' def test_nfc