From 159dd7f06926441f6476ab86bd673281b67f5fe4 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Fri, 20 Dec 2013 15:30:48 +0900 Subject: [PATCH] Implement smart-case match (#12) --- README.md | 1 + fzf | 20 ++++++++++++------- test/test_fzf.rb | 50 +++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 61 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 0b96bcd..b5f00b3 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ usage: fzf [options] -q, --query=STR Initial query -s, --sort=MAX Maximum number of matched items to sort. Default: 1000 +s, --no-sort Do not sort the result. Keep the sequence unchanged. + -i Case-insensitive match (default: smart-case match) +i Case-sensitive match +c, --no-color Disable colors ``` diff --git a/fzf b/fzf index ee395ff..a6a4334 100755 --- a/fzf +++ b/fzf @@ -67,7 +67,7 @@ class FZF end def initialize argv, source = $stdin - @rxflag = Regexp::IGNORECASE + @rxflag = nil @sort = ENV.fetch('FZF_DEFAULT_SORT', 1000).to_i @color = true @multi = false @@ -79,6 +79,7 @@ class FZF when '-h', '--help' then usage 0 when '-m', '--multi' then @multi = true when '-x', '--extended' then @xmode = true + when '-i' then @rxflag = Regexp::IGNORECASE when '+i' then @rxflag = 0 when '+s', '--no-sort' then @sort = nil when '+c', '--no-color' then @color = false @@ -136,6 +137,7 @@ class FZF -q, --query=STR Initial query -s, --sort=MAX Maximum number of matched items to sort. Default: 1000 +s, --no-sort Do not sort the result. Keep the sequence unchanged. + -i Case-insensitive match (default: smart-case match) +i Case-sensitive match +c, --no-color Disable colors] exit x @@ -770,14 +772,18 @@ class FZF q.empty? end + def rxflag_for q + @rxflag || (q =~ /[A-Z]/ ? 0 : Regexp::IGNORECASE) + end + def fuzzy_regex q @regexp[q] ||= begin - q = q.downcase if @rxflag != 0 + q = q.downcase if @rxflag == Regexp::IGNORECASE Regexp.new(query_chars(q).inject('') { |sum, e| e = Regexp.escape e sum << (e.length > 1 ? "(?:#{e}).*?" : # FIXME: not equivalent "#{e}[^#{e}]*?") - }, @rxflag) + }, rxflag_for(q)) end end @@ -830,16 +836,16 @@ class FZF when '' nil when /^\^(.*)\$$/ - Regexp.new('^' << sanitize(Regexp.escape($1)) << '$', rxflag) + Regexp.new('^' << sanitize(Regexp.escape($1)) << '$', rxflag_for(w)) when /^'/ w.length > 1 ? - Regexp.new(sanitize(Regexp.escape(w[1..-1])), rxflag) : nil + Regexp.new(sanitize(Regexp.escape(w[1..-1])), rxflag_for(w)) : nil when /^\^/ w.length > 1 ? - Regexp.new('^' << sanitize(Regexp.escape(w[1..-1])), rxflag) : nil + Regexp.new('^' << sanitize(Regexp.escape(w[1..-1])), rxflag_for(w)) : nil when /\$$/ w.length > 1 ? - Regexp.new(sanitize(Regexp.escape(w[0..-2])) << '$', rxflag) : nil + Regexp.new(sanitize(Regexp.escape(w[0..-2])) << '$', rxflag_for(w)) : nil else fuzzy_regex w end, invert ] diff --git a/test/test_fzf.rb b/test/test_fzf.rb index 98818d7..8133c22 100644 --- a/test/test_fzf.rb +++ b/test/test_fzf.rb @@ -9,10 +9,10 @@ load 'fzf' class TestFZF < MiniTest::Unit::TestCase def test_default_options fzf = FZF.new [] - assert_equal 1000, fzf.sort + assert_equal 1000, fzf.sort assert_equal false, fzf.multi - assert_equal true, fzf.color - assert_equal Regexp::IGNORECASE, fzf.rxflag + assert_equal true, fzf.color + assert_equal nil, fzf.rxflag begin ENV['FZF_DEFAULT_SORT'] = '1500' @@ -152,15 +152,59 @@ class TestFZF < MiniTest::Unit::TestCase # TODO : partial_cache end + def test_fuzzy_matcher_rxflag + assert_equal nil, FZF::FuzzyMatcher.new(nil).rxflag + assert_equal 0, FZF::FuzzyMatcher.new(0).rxflag + assert_equal 1, FZF::FuzzyMatcher.new(1).rxflag + + assert_equal 1, FZF::FuzzyMatcher.new(nil).rxflag_for('abc') + assert_equal 0, FZF::FuzzyMatcher.new(nil).rxflag_for('Abc') + assert_equal 0, FZF::FuzzyMatcher.new(0).rxflag_for('abc') + assert_equal 0, FZF::FuzzyMatcher.new(0).rxflag_for('Abc') + assert_equal 1, FZF::FuzzyMatcher.new(1).rxflag_for('abc') + assert_equal 1, FZF::FuzzyMatcher.new(1).rxflag_for('Abc') + end + def test_fuzzy_matcher_case_sensitive + # Smart-case match (Uppercase found) + assert_equal [['Fruit', [[0, 5]]]], + FZF::FuzzyMatcher.new(nil).match(%w[Fruit Grapefruit], 'Fruit', '', '').sort + + # Smart-case match (Uppercase not-found) + assert_equal [["Fruit", [[0, 5]]], ["Grapefruit", [[5, 10]]]], + FZF::FuzzyMatcher.new(nil).match(%w[Fruit Grapefruit], 'fruit', '', '').sort + + # Case-sensitive match (-i) assert_equal [['Fruit', [[0, 5]]]], FZF::FuzzyMatcher.new(0).match(%w[Fruit Grapefruit], 'Fruit', '', '').sort + # Case-insensitive match (+i) assert_equal [["Fruit", [[0, 5]]], ["Grapefruit", [[5, 10]]]], FZF::FuzzyMatcher.new(Regexp::IGNORECASE). match(%w[Fruit Grapefruit], 'Fruit', '', '').sort end + def test_extended_fuzzy_matcher_case_sensitive + %w['Fruit Fruit$].each do |q| + # Smart-case match (Uppercase found) + assert_equal [['Fruit', [[0, 5]]]], + FZF::ExtendedFuzzyMatcher.new(nil).match(%w[Fruit Grapefruit], q, '', '').sort + + # Smart-case match (Uppercase not-found) + assert_equal [["Fruit", [[0, 5]]], ["Grapefruit", [[5, 10]]]], + FZF::ExtendedFuzzyMatcher.new(nil).match(%w[Fruit Grapefruit], q.downcase, '', '').sort + + # Case-sensitive match (-i) + assert_equal [['Fruit', [[0, 5]]]], + FZF::ExtendedFuzzyMatcher.new(0).match(%w[Fruit Grapefruit], q, '', '').sort + + # Case-insensitive match (+i) + assert_equal [["Fruit", [[0, 5]]], ["Grapefruit", [[5, 10]]]], + FZF::ExtendedFuzzyMatcher.new(Regexp::IGNORECASE). + match(%w[Fruit Grapefruit], q, '', '').sort + end + end + def test_extended_fuzzy_matcher matcher = FZF::ExtendedFuzzyMatcher.new Regexp::IGNORECASE list = %w[