From 2dbca00bfb8e9f0d63514bd389f09a28bbf6e149 Mon Sep 17 00:00:00 2001
From: Junegunn Choi <junegunn.c@gmail.com>
Date: Tue, 4 Mar 2014 21:29:45 +0900
Subject: [PATCH] Implement --extended-exact option (#24)

---
 README.md        |  4 ++++
 fzf              | 42 ++++++++++++++++++++++++++++++------------
 test/test_fzf.rb | 31 +++++++++++++++++++++++++------
 3 files changed, 59 insertions(+), 18 deletions(-)

diff --git a/README.md b/README.md
index b0c8203c..baa73f2c 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 84c1f117..180a53df 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 4be0846f..d1006659 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
-- 
GitLab