diff --git a/fzf b/fzf
index 8f29d5bd595aa651bbccf72e4d31a483fbb1290c..c66f4a4fd7cc6f3674164c0a22b2faa322606c18 100755
--- a/fzf
+++ b/fzf
@@ -10,7 +10,7 @@
 # URL:         https://github.com/junegunn/fzf
 # Author:      Junegunn Choi
 # License:     MIT
-# Last update: November 10, 2013
+# Last update: November 15, 2013
 #
 # Copyright (c) 2013 Junegunn Choi
 #
@@ -35,608 +35,668 @@
 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
-def usage x
-  puts %[usage: fzf [options]
-
-  -m, --multi      Enable multi-select
-  -s, --sort=MAX   Maximum number of matched items to sort. Default: 500.
-  +s, --no-sort    Do not sort the result. Keep the sequence unchanged.
-  +i               Case-sensitive match
-  +c, --no-color   Disable colors]
-  exit x
-end
-
-stdout = $stdout.clone
-$stdout.reopen($stderr)
-
-usage 0 unless (%w[--help -h] & ARGV).empty?
-@rxflag = ARGV.delete('+i') ? 0 : Regexp::IGNORECASE
-@sort   = %w[+s --no-sort].map  { |e| ARGV.delete e }.compact.empty? ?
-  ENV.fetch('FZF_DEFAULT_SORT', 500).to_i : nil
-@color  = %w[+c --no-color].map { |e| ARGV.delete e }.compact.empty?
-@multi  = !%w[-m --multi].map   { |e| ARGV.delete e }.compact.empty?
-rest    = ARGV.join ' '
-if sort = rest.match(/(-s|--sort=?) ?([0-9]+)/)
-  usage 1 unless @sort
-  @sort = sort[2].to_i
-  rest  = rest.delete sort[0]
-end
-usage 1 unless rest.empty?
-
 require 'thread'
 require 'curses'
 
-@mtx      = Mutex.new
-@smtx     = Mutex.new
-@cv       = ConditionVariable.new
-@lists    = []
-@new      = []
-@query    = ''
-@matches  = []
-@count    = 0
-@cursor_x = 0
-@vcursor  = 0
-@events   = {}
-@selects  = {} # ordered >= 1.9
-
-case RUBY_PLATFORM
-when /darwin/
-  module UConv
-    CHOSUNG   = 0x1100
-    JUNGSUNG  = 0x1161
-    JONGSUNG  = 0x11A7
-    CHOSUNGS  = 19
-    JUNGSUNGS = 21
-    JONGSUNGS = 28
-    JJCOUNT   = JUNGSUNGS * JONGSUNGS
-    NFC_BEGIN = 0xAC00
-    NFC_END   = NFC_BEGIN + CHOSUNGS * JUNGSUNGS * JONGSUNGS
-
-    def self.nfd str
-      ret = ''
-      str.split(//).each do |c|
-        cp = c.ord
-        if cp >= NFC_BEGIN && cp < NFC_END
-          idx  = cp - NFC_BEGIN
-          cho  = CHOSUNG  + idx / JJCOUNT
-          jung = JUNGSUNG + (idx % JJCOUNT) / JONGSUNGS
-          jong = JONGSUNG + idx % JONGSUNGS
-          ret << cho << jung
-          ret << jong if jong != JONGSUNG
-        else
-          ret << c
-        end
+class FZF
+  C = Curses
+  attr_reader :rxflag, :sort, :color, :multi
+
+  class AtomicVar
+    def initialize value
+      @value = value
+      @mutex = Mutex.new
+    end
+
+    def get
+      @mutex.synchronize { @value }
+    end
+
+    def set value = nil
+      if block_given?
+        @mutex.synchronize { @value = yield @value }
+      else
+        @mutex.synchronize { @value = value }
       end
-      ret
     end
 
-    def self.nfc str, offsets = []
-      ret  = ''
-      omap = []
-      pend = []
-      str.split(//).each_with_index do |c, idx|
-        cp = c.ord
-        omap << ret.length
-        unless pend.empty?
-          if cp >= JUNGSUNG && cp < JUNGSUNG + JUNGSUNGS
-            pend << cp - JUNGSUNG
-            next
-          elsif cp >= JONGSUNG && cp < JONGSUNG + JONGSUNGS
-            pend << cp - JONGSUNG
-            next
+    def method_missing sym, *args, &blk
+      @mutex.synchronize { @value.send(sym, *args, &blk) }
+    end
+  end
+
+  def initialize argv, source = $stdin
+    usage 0 unless (%w[--help -h] & argv).empty?
+    @rxflag = argv.delete('+i') ? 0 : Regexp::IGNORECASE
+    @sort   = %w[+s --no-sort].map  { |e| argv.delete e }.compact.empty? ?
+      ENV.fetch('FZF_DEFAULT_SORT', 500).to_i : nil
+    @color  = %w[+c --no-color].map { |e| argv.delete e }.compact.empty?
+    @multi  = !%w[-m --multi].map   { |e| argv.delete e }.compact.empty?
+    rest    = argv.join ' '
+    if sort = rest.match(/(-s|--sort=?) ?([0-9]+)/)
+      usage 1 unless @sort
+      @sort = sort[2].to_i
+      rest  = rest.delete sort[0]
+    end
+    usage 1 unless rest.empty?
+
+    @source   = source
+    @mtx      = Mutex.new
+    @cv       = ConditionVariable.new
+    @events   = {}
+    @new      = []
+    @smtx     = Mutex.new
+    @cursor_x = AtomicVar.new(0)
+    @query    = AtomicVar.new('')
+    @matches  = AtomicVar.new([])
+    @count    = AtomicVar.new(0)
+    @vcursor  = AtomicVar.new(0)
+    @fan      = AtomicVar.new('-\|/-\|/'.split(//))
+    @selects  = AtomicVar.new({}) # ordered >= 1.9
+  end
+
+  def start
+    @stdout = $stdout.clone
+    $stdout.reopen($stderr)
+
+    init_screen
+    start_reader
+    start_search
+    start_loop
+  end
+
+  def usage x
+    $stderr.puts %[usage: fzf [options]
+
+    -m, --multi      Enable multi-select
+    -s, --sort=MAX   Maximum number of matched items to sort. Default: 500.
+    +s, --no-sort    Do not sort the result. Keep the sequence unchanged.
+    +i               Case-sensitive match
+    +c, --no-color   Disable colors]
+    exit x
+  end
+
+  case RUBY_PLATFORM
+  when /darwin/
+    module UConv
+      CHOSUNG   = 0x1100
+      JUNGSUNG  = 0x1161
+      JONGSUNG  = 0x11A7
+      CHOSUNGS  = 19
+      JUNGSUNGS = 21
+      JONGSUNGS = 28
+      JJCOUNT   = JUNGSUNGS * JONGSUNGS
+      NFC_BEGIN = 0xAC00
+      NFC_END   = NFC_BEGIN + CHOSUNGS * JUNGSUNGS * JONGSUNGS
+
+      def self.nfd str
+        ret = ''
+        str.split(//).each do |c|
+          cp = c.ord
+          if cp >= NFC_BEGIN && cp < NFC_END
+            idx  = cp - NFC_BEGIN
+            cho  = CHOSUNG  + idx / JJCOUNT
+            jung = JUNGSUNG + (idx % JJCOUNT) / JONGSUNGS
+            jong = JONGSUNG + idx % JONGSUNGS
+            ret << cho << jung
+            ret << jong if jong != JONGSUNG
           else
-            omap[-1] = omap[-1] + 1
-            ret << [NFC_BEGIN + pend[0] * JJCOUNT +
-                                (pend[1] || 0) * JONGSUNGS +
-                                (pend[2] || 0)].pack('U*')
-            pend.clear
+            ret << c
           end
         end
-        if cp >= CHOSUNG && cp < CHOSUNG + CHOSUNGS
-          pend << cp - CHOSUNG
-        else
-          ret << c
+        ret
+      end
+
+      def self.nfc str, offsets = []
+        ret  = ''
+        omap = []
+        pend = []
+        str.split(//).each_with_index do |c, idx|
+          cp = c.ord
+          omap << ret.length
+          unless pend.empty?
+            if cp >= JUNGSUNG && cp < JUNGSUNG + JUNGSUNGS
+              pend << cp - JUNGSUNG
+              next
+            elsif cp >= JONGSUNG && cp < JONGSUNG + JONGSUNGS
+              pend << cp - JONGSUNG
+              next
+            else
+              omap[-1] = omap[-1] + 1
+              ret << [NFC_BEGIN + pend[0] * JJCOUNT +
+                                  (pend[1] || 0) * JONGSUNGS +
+                                  (pend[2] || 0)].pack('U*')
+              pend.clear
+            end
+          end
+          if cp >= CHOSUNG && cp < CHOSUNG + CHOSUNGS
+            pend << cp - CHOSUNG
+          else
+            ret << c
+          end
         end
+        return [ret,
+                offsets.map { |pair|
+                  b, e = pair
+                  [omap[b] || 0, omap[e] || ((omap.last || 0) + 1)] }]
+      end
+    end
+
+    def convert_item item
+      UConv.nfc(*item)
+    end
+
+    class Matcher
+      def convert_query q
+        UConv.nfd(q).split(//)
+      end
+    end
+  else
+    def convert_item item
+      item
+    end
+
+    class Matcher
+      def convert_query q
+        q.split(//)
       end
-      return [ret,
-              offsets.map { |pair|
-                b, e = pair
-                [omap[b] || 0, omap[e] || ((omap.last || 0) + 1)] }]
     end
   end
 
-  def convert_query q
-    UConv.nfd(q).split(//)
+  def emit event
+    @mtx.synchronize do
+      @events[event] = yield
+      @cv.broadcast
+    end
   end
 
-  def convert_item item
-    UConv.nfc(*item)
+  def max_items; C.lines - 2; end
+  def cursor_y;  C.lines - 1; end
+  def cprint str, col
+    C.attron(col) do
+      C.addstr str.gsub("\0", '')
+    end if str
   end
-else
-  def convert_query q
-    q.split(//)
+
+  def print_input
+    C.setpos cursor_y, 0
+    C.clrtoeol
+    cprint '> ', color(:blue, true)
+    C.attron(C::A_BOLD) do
+      C.addstr @query.get
+    end
   end
 
-  def convert_item item
-    item
+  def print_info msg = nil
+    C.setpos cursor_y - 1, 0
+    C.clrtoeol
+    prefix =
+      if fan = @fan.shift
+        @fan.push fan
+        cprint fan, color(:fan, true)
+        ' '
+      else
+        '  '
+      end
+    C.attron color(:info, false) do
+      C.addstr "#{prefix}#{@matches.length}/#{@count.get}"
+      if (selected = @selects.length) > 0
+        C.addstr " (#{selected})"
+      end
+      C.addstr msg if msg
+    end
   end
-end
 
-def emit event
-  @mtx.synchronize do
-    @events[event] = yield
-    @cv.broadcast
+  def refresh
+    C.setpos cursor_y, 2 + width(@query[0, @cursor_x.get])
+    C.refresh
   end
-end
-
-C = Curses
-def max_items; C.lines - 2; end
-def cursor_y;  C.lines - 1; end
-def cprint str, col
-  C.attron(col) do
-    C.addstr str.gsub("\0", '')
-  end if str
-end
-
-def print_input
-  C.setpos cursor_y, 0
-  C.clrtoeol
-  cprint '> ', color(:blue, true)
-  C.attron(C::A_BOLD) do
-    C.addstr @query
+
+  def ctrl char
+    char.to_s.ord - 'a'.ord + 1
   end
-end
-
-def print_info selected, msg = nil
-  @fan ||= '-\|/-\|/'.split(//)
-  C.setpos cursor_y - 1, 0
-  C.clrtoeol
-  prefix =
-    if fan = @fan.shift
-      @fan.push fan
-      cprint fan, color(:fan, true)
-      ' '
-    else
-      '  '
+
+  def format line, limit, offsets
+    offsets ||= []
+    maxe = offsets.map { |e| e.last }.max || 0
+
+    # Overflow
+    if width(line) > limit
+      ewidth = width(line[0...maxe])
+      # Stri..
+      if ewidth <= limit - 2
+        line, _ = trim line, limit - 2, false
+        line << '..'
+      # ..ring
+      else
+        # ..ri..
+        line = line[0...maxe] + '..' if ewidth < width(line) - 2
+        line, diff = trim line, limit - 2, true
+        offsets = offsets.map { |pair|
+          b, e = pair
+          b += 2 - diff
+          e += 2 - diff
+          b = [2, b].max
+          [b, e]
+        }
+        line = '..' + line
+      end
     end
-  C.attron color(:info, false) do
-    C.addstr "#{prefix}#{@matches.length}/#{@count}"
-    C.addstr " (#{selected})" if selected > 0
-    C.addstr msg if msg
-  end
-end
-
-def refresh
-  C.setpos cursor_y, 2 + width(@query[0, @cursor_x])
-  C.refresh
-end
-
-def ctrl char
-  char.to_s.ord - 'a'.ord + 1
-end
-
-def format line, limit, offsets
-  offsets ||= []
-  maxe = offsets.map { |e| e.last }.max || 0
-
-  # Overflow
-  if width(line) > limit
-    ewidth = width(line[0...maxe])
-    # Stri..
-    if ewidth <= limit - 2
-      line, _ = trim line, limit - 2, false
-      line << '..'
-    # ..ring
-    else
-      # ..ri..
-      line = line[0...maxe] + '..' if ewidth < width(line) - 2
-      line, diff = trim line, limit - 2, true
-      offsets = offsets.map { |pair|
-        b, e = pair
-        b += 2 - diff
-        e += 2 - diff
-        b = [2, b].max
-        [b, e]
-      }
-      line = '..' + line
+
+    tokens = []
+    index = 0
+    offsets.select  { |pair| pair.first < pair.last }.
+            sort_by { |pair| pair }.each do |pair|
+      b, e = pair.map { |x| [index, x].max }
+      tokens << [line[index...b], false]
+      tokens << [line[b...e], true]
+      index = e
     end
+    tokens << [line[index..-1], false]
+    tokens.reject { |pair| pair.first.empty? }
   end
 
-  tokens = []
-  index = 0
-  offsets.select  { |pair| pair.first < pair.last }.
-          sort_by { |pair| pair }.each do |pair|
-    b, e = pair.map { |x| [index, x].max }
-    tokens << [line[index...b], false]
-    tokens << [line[b...e], true]
-    index = e
-  end
-  tokens << [line[index..-1], false]
-  tokens.reject { |pair| pair.first.empty? }
-end
-
-def print_item row, tokens, chosen, selected
-  # Cursor
-  C.setpos row, 0
-  C.clrtoeol
-  cprint chosen ? '>' : ' ', color(:red, true)
-  cprint selected ? '>' : ' ',
-    chosen ? color(:chosen) : (selected ? color(:red, true) : 0)
-
-  # Highlighted item
-  C.attron color(:chosen, true) if chosen
-  tokens.each do |pair|
-    token, highlighted = pair
-
-    if highlighted
-      cprint   token, color(chosen ? :match! : :match, chosen)
-      C.attron color(:chosen, true) if chosen
-    else
-      C.addstr token
+  def print_item row, tokens, chosen, selected
+    # Cursor
+    C.setpos row, 0
+    C.clrtoeol
+    cprint chosen ? '>' : ' ', color(:red, true)
+    cprint selected ? '>' : ' ',
+      chosen ? color(:chosen) : (selected ? color(:red, true) : 0)
+
+    # Highlighted item
+    C.attron color(:chosen, true) if chosen
+    tokens.each do |pair|
+      token, highlighted = pair
+
+      if highlighted
+        cprint   token, color(chosen ? :match! : :match, chosen)
+        C.attron color(:chosen, true) if chosen
+      else
+        C.addstr token
+      end
     end
+    C.attroff color(:chosen, true) if chosen
   end
-  C.attroff color(:chosen, true) if chosen
-end
 
-if RUBY_VERSION.split('.').map { |e| e.rjust(3, '0') }.join > '001009'
-  @wrx = Regexp.new '\p{Han}|\p{Katakana}|\p{Hiragana}|\p{Hangul}'
-  def width str
-    str.gsub(@wrx, '  ').length
+  def sort_by_rank list
+    list.sort_by { |tuple|
+      line, offsets = tuple
+      matchlen = (offsets.map { |pair| pair.last  }.max || 0) -
+                 (offsets.map { |pair| pair.first }.min || 0)
+      [matchlen, line.length, line]
+    }
   end
 
-  def trim str, len, left
-    width = width str
-    diff = 0
-    while width > len
-      width -= (left ? str[0, 1] : str[-1, 1]) =~ @wrx ? 2 : 1
-      str = left ? str[1..-1] : str[0...-1]
-      diff += 1
+  if RUBY_VERSION.split('.').map { |e| e.rjust(3, '0') }.join > '001009'
+    @@wrx = Regexp.new '\p{Han}|\p{Katakana}|\p{Hiragana}|\p{Hangul}'
+    def width str
+      str.gsub(@@wrx, '  ').length
     end
-    [str, diff]
-  end
-else
-  def width str
-    str.length
-  end
 
-  def trim str, len, left
-    diff = str.length - len
-    if diff > 0
-      [left ? str[diff..-1] : str[0...-diff], diff]
-    else
-      [str, 0]
+    def trim str, len, left
+      width = width str
+      diff  = 0
+      while width > len
+        width -= (left ? str[0, 1] : str[-1, 1]) =~ @@wrx ? 2 : 1
+        str    = left ? str[1..-1] : str[0...-1]
+        diff  += 1
+      end
+      [str, diff]
+    end
+  else
+    def width str
+      str.length
     end
-  end
 
-  class String
-    def ord
-      self.unpack('c').first
+    def trim str, len, left
+      diff = str.length - len
+      if diff > 0
+        [left ? str[diff..-1] : str[0...-diff], diff]
+      else
+        [str, 0]
+      end
     end
-  end
 
-  class Fixnum
-    def ord
-      self
+    class ::String
+      def ord
+        self.unpack('c').first
+      end
     end
-  end
-end
-
-C.init_screen
-C.start_color
-dbg =
-  if C.respond_to?(:use_default_colors)
-    C.use_default_colors
-    -1
-  else
-    C::COLOR_BLACK
-  end
-C.raw
-C.noecho
-
-if @color
-  if C.can_change_color?
-    fg = ENV.fetch('FZF_FG', 252).to_i
-    bg = ENV.fetch('FZF_BG', 236).to_i
-    C.init_pair 1, 110,    dbg
-    C.init_pair 2, 108,    dbg
-    C.init_pair 3, fg + 2, bg
-    C.init_pair 4, 151,    bg
-    C.init_pair 5, 148,    dbg
-    C.init_pair 6, 144,    dbg
-    C.init_pair 7, 161,    bg
-  else
-    C.init_pair 1, C::COLOR_BLUE,   dbg
-    C.init_pair 2, C::COLOR_GREEN,  dbg
-    C.init_pair 3, C::COLOR_YELLOW, C::COLOR_BLACK
-    C.init_pair 4, C::COLOR_GREEN,  C::COLOR_BLACK
-    C.init_pair 5, C::COLOR_GREEN,  dbg
-    C.init_pair 6, C::COLOR_WHITE,  dbg
-    C.init_pair 7, C::COLOR_RED,    C::COLOR_BLACK
-  end
 
-  def color sym, bold = false
-    C.color_pair([:blue, :match, :chosen,
-                  :match!, :fan, :info, :red].index(sym) + 1) |
-      (bold ? C::A_BOLD : 0)
-  end
-else
-  def color sym, bold = false
-    case sym
-    when :chosen
-     bold ? C::A_REVERSE : 0
-    when :match
-      C::A_UNDERLINE
-    when :match!
-      C::A_REVERSE | C::A_UNDERLINE
-    else
-      0
-    end | (bold ? C::A_BOLD : 0)
+    class ::Fixnum
+      def ord
+        self
+      end
+    end
   end
-end
-
-@read =
-  if $stdin.tty?
-    if default_command = ENV['FZF_DEFAULT_COMMAND']
-      IO.popen(default_command)
-    elsif !`which find`.empty?
-      IO.popen("find * -path '*/\\.*' -prune -o -type f -print -o -type l -print 2> /dev/null")
+
+  def init_screen
+    C.init_screen
+    C.start_color
+    dbg =
+      if C.respond_to?(:use_default_colors)
+        C.use_default_colors
+        -1
+      else
+        C::COLOR_BLACK
+      end
+    C.raw
+    C.noecho
+
+    if @color
+      if C.can_change_color?
+        fg = ENV.fetch('FZF_FG', 252).to_i
+        bg = ENV.fetch('FZF_BG', 236).to_i
+        C.init_pair 1, 110,    dbg
+        C.init_pair 2, 108,    dbg
+        C.init_pair 3, fg + 2, bg
+        C.init_pair 4, 151,    bg
+        C.init_pair 5, 148,    dbg
+        C.init_pair 6, 144,    dbg
+        C.init_pair 7, 161,    bg
+      else
+        C.init_pair 1, C::COLOR_BLUE,   dbg
+        C.init_pair 2, C::COLOR_GREEN,  dbg
+        C.init_pair 3, C::COLOR_YELLOW, C::COLOR_BLACK
+        C.init_pair 4, C::COLOR_GREEN,  C::COLOR_BLACK
+        C.init_pair 5, C::COLOR_GREEN,  dbg
+        C.init_pair 6, C::COLOR_WHITE,  dbg
+        C.init_pair 7, C::COLOR_RED,    C::COLOR_BLACK
+      end
+
+      def self.color sym, bold = false
+        C.color_pair([:blue, :match, :chosen,
+                      :match!, :fan, :info, :red].index(sym) + 1) |
+          (bold ? C::A_BOLD : 0)
+      end
     else
-      exit 1
+      def self.color sym, bold = false
+        case sym
+        when :chosen
+          bold ? C::A_REVERSE : 0
+        when :match
+          C::A_UNDERLINE
+        when :match!
+          C::A_REVERSE | C::A_UNDERLINE
+        else
+          0
+        end | (bold ? C::A_BOLD : 0)
+      end
     end
-  else
-    $stdin
   end
 
-reader = Thread.new {
-  while line = @read.gets
-    emit(:new) { @new << line.chomp }
-  end
-  emit(:loaded) { true }
-  @smtx.synchronize { @fan = [] }
-}
-
-main     = Thread.current
-searcher = Thread.new {
-  events  = {}
-  fcache  = {}
-  matches = []
-  selects = {}
-  mcount  = 0      # match count
-  plcount = 0      # prev list count
-  q       = ''
-  vcursor = 0
-  delay   = -5
-
-  begin
-    while true
-      @mtx.synchronize do
-        while true
-          events.merge! @events
-
-          if @events.empty? # No new events
-            @cv.wait @mtx
-            next
-          end
-          @events.clear
-          break
+  def start_reader
+    stream =
+      if @source.tty?
+        if default_command = ENV['FZF_DEFAULT_COMMAND']
+          IO.popen(default_command)
+        elsif !`which find`.empty?
+          IO.popen("find * -path '*/\\.*' -prune -o -type f -print -o -type l -print 2> /dev/null")
+        else
+          exit 1
         end
+      else
+        @source
+      end
 
-        if events[:new]
-          @lists << [@new, {}]
-          @count += @new.length
-          @new    = []
-          fcache  = {}
-        end
+    Thread.new do
+      while line = stream.gets
+        emit(:new) { @new << line.chomp }
+      end
+      emit(:loaded) { true }
+      @fan.clear
+    end
+  end
 
-        if events[:select]
-          selects = @selects.dup
-        end
-      end#mtx
-
-      new_search = events[:key] || events.delete(:new)
-      user_input = events[:key] || events[:vcursor] || events.delete(:select)
-      progress   = 0
-      started_at = Time.now
-
-      if new_search && !@lists.empty?
-        q = events.delete(:key) || q
-
-        unless q.empty?
-          q = q.downcase if @rxflag != 0
-          regexp = Regexp.new(convert_query(q).inject('') { |sum, e|
-            e = Regexp.escape e
-            sum << "#{e}[^#{e}]*?"
-          }, @rxflag)
-        end
+  def start_search
+    main     = Thread.current
+    matcher  = FuzzyMatcher.new @rxflag
+    searcher = Thread.new {
+      lists   = []
+      events  = {}
+      fcache  = {}
+      mcount  = 0  # match count
+      plcount = 0  # prev list count
+      q       = ''
+      delay   = -5
+
+      begin
+        while true
+          @mtx.synchronize do
+            while true
+              events.merge! @events
+
+              if @events.empty? # No new events
+                @cv.wait @mtx
+                next
+              end
+              @events.clear
+              break
+            end
 
-        matches = fcache[q] ||=
-          begin
-            found = []
-            skip  = false
-            cnt   = 0
-            @lists.each do |pair|
-              list, cache = pair
-              cnt += list.length
-
-              @mtx.synchronize {
-                skip = @events[:key]
-                progress = (100 * cnt / @count)
-              }
-              break if skip
-
-              found.concat(cache[q] ||= q.empty? ? list : begin
-                if progress < 100 && Time.now - started_at > 0.5
-                  @smtx.synchronize do
-                    print_info selects.length, " (#{progress}%)"
-                    refresh
+            if events[:new]
+              lists << @new
+              @count.set { |c| c + @new.length }
+              @new    = []
+              fcache  = {}
+            end
+          end#mtx
+
+          new_search = events[:key] || events.delete(:new)
+          user_input = events[:key] || events[:vcursor] || events.delete(:select)
+          progress   = 0
+          started_at = Time.now
+
+          if new_search && !lists.empty?
+            q, cx = events.delete(:key) || [q, 0]
+
+            plcount = [@matches.length, max_items].min
+            @matches.set(fcache[q] ||=
+              begin
+                found = []
+                skip  = false
+                cnt   = 0
+                lists.each do |list|
+                  cnt += list.length
+                  skip = @mtx.synchronize { @events[:key] }
+                  break if skip
+
+                  progress = (100 * cnt / @count.get)
+                  if progress < 100 && Time.now - started_at > 0.5 && !q.empty?
+                    @smtx.synchronize do
+                      print_info " (#{progress}%)"
+                      refresh
+                    end
                   end
+
+                  found.concat(q.empty? ? list :
+                               matcher.match(list, q, q[0, cx], q[cx..-1]))
                 end
+                next if skip
+                @sort ? found : found.reverse
+              end)
 
-                prefix, suffix = @query[0, @cursor_x], @query[@cursor_x..-1] || ''
-                prefix_cache = suffix_cache = nil
+            mcount = @matches.length
+            if @sort && mcount <= @sort && !q.empty?
+              @matches.set { |m| sort_by_rank m }
+            end
+          end#new_search
 
-                (prefix.length - 1).downto(1) do |len|
-                  break if prefix_cache = cache[prefix[0, len]]
-                end
+          # This small delay reduces the number of partial lists
+          sleep((delay = [20, delay + 5].min) * 0.01) unless user_input
 
-                0.upto(suffix.length - 1) do |idx|
-                  break if suffix_cache = cache[suffix[idx..-1]]
-                end unless suffix.empty?
+          if events.delete(:vcursor) || new_search
+            @vcursor.set { |v| [0, [v, mcount - 1, max_items - 1].min].max }
+          end
 
-                partial_cache = [prefix_cache, suffix_cache].compact.sort_by { |e| e.length }.first
-                (partial_cache ? partial_cache.map { |e| e.first } : list).map { |line|
-                  # Ignore errors: e.g. invalid byte sequence in UTF-8
-                  md = line.match(regexp) rescue nil
-                  md && [line, [md.offset(0)]]
-                }.compact
-              end)
+          # Output
+          @smtx.synchronize do
+            item_length = [mcount, max_items].min
+            if item_length < plcount
+              plcount.downto(item_length) do |idx|
+                C.setpos cursor_y - idx - 2, 0
+                C.clrtoeol
+              end
             end
-            next if skip
-            @sort ? found : found.reverse
-          end
 
-        mcount = matches.length
-        if @sort && mcount <= @sort && !q.empty?
-          matches.replace matches.sort_by { |tuple|
-            line, offsets = tuple
-            matchlen = offsets.map { |pair| pair.last }.max || 0 -
-                       offsets.map { |pair| pair.first }.min || 0
-            [matchlen, line.length, line]
-          }
-        end
-      end#new_search
+            maxc    = C.cols - 3
+            vcursor = @vcursor.get
+            @matches[0, max_items].each_with_index do |item, idx|
+              next if !new_search && !((vcursor-1)..(vcursor+1)).include?(idx)
+              row           = cursor_y - idx - 2
+              chosen        = idx == vcursor
+              selected      = @selects.include?([*item][0])
+              line, offsets = convert_item item
+              tokens        = format line, maxc, offsets
+              print_item row, tokens, chosen, selected
+            end
 
-      # This small delay reduces the number of partial lists
-      sleep((delay = [20, delay + 5].min) * 0.01) unless user_input
+            print_info if !lists.empty? || events[:loaded]
+            refresh
+          end
+        end#while
+      rescue Exception => e
+        main.raise e
+      end
+    }
+  end
 
-      if events.delete(:vcursor) || new_search
-        @mtx.synchronize do
-          plcount  = [@matches.length, max_items].min
-          @matches = matches
-          vcursor  = @vcursor = [0, [@vcursor, mcount - 1, max_items - 1].min].max
+  def start_loop
+    got = nil
+    begin
+      tty     = IO.open(IO.sysopen('/dev/tty'), 'r')
+      input   = ''
+      cursor  = 0
+      actions = {
+        :nop     => proc {},
+        ctrl(:c) => proc { exit 1 },
+        ctrl(:d) => proc { exit 1 if input.empty? },
+        ctrl(:m) => proc {
+          got = [*@matches.fetch(@vcursor.get, [])][0]
+          exit 0
+        },
+        ctrl(:u) => proc { input = input[cursor..-1]; cursor = 0 },
+        ctrl(:a) => proc { cursor = 0 },
+        ctrl(:e) => proc { cursor = input.length },
+        ctrl(:j) => proc { emit(:vcursor) { @vcursor.set { |v| v - 1 } } },
+        ctrl(:k) => proc { emit(:vcursor) { @vcursor.set { |v| v + 1 } } },
+        ctrl(:w) => proc {
+          ridx   = (input[0...cursor - 1].rindex(/\S\s/) || -2) + 2
+          input  = input[0...ridx] + input[cursor..-1]
+          cursor = ridx
+        },
+        127 => proc { input[cursor -= 1] = '' if cursor > 0 },
+        9   => proc { |o|
+          emit(:select) {
+            if sel = [*@matches.fetch(@vcursor.get, [])][0]
+              if @selects.has_key? sel
+                @selects.delete sel
+              else
+                @selects[sel] = 1
+              end
+              @vcursor.set { |v| [0, v + (o == :stab ? 1 : -1)].max }
+            end
+          } if @multi
+        },
+        :left => proc { cursor = [0, cursor - 1].max },
+        :right => proc { cursor = [input.length, cursor + 1].min },
+      }
+      actions[ctrl(:b)] = actions[:left]
+      actions[ctrl(:f)] = actions[:right]
+      actions[ctrl(:h)] = actions[127]
+      actions[ctrl(:n)] = actions[ctrl(:j)]
+      actions[ctrl(:p)] = actions[ctrl(:k)]
+      actions[:stab]    = actions[9]
+
+      while true
+        @cursor_x.set cursor
+        # Update user input
+        @smtx.synchronize do
+          print_input
+          refresh
         end
-      end
 
-      # Output
-      @smtx.synchronize do
-        item_length = [mcount, max_items].min
-        if item_length < plcount
-          plcount.downto(item_length) do |idx|
-            C.setpos cursor_y - idx - 2, 0
-            C.clrtoeol
+        ord = tty.getc.ord
+        if ord == 27
+          ord = tty.getc.ord
+          if ord == 91
+            ord = case tty.getc.ord
+                  when 68 then :left
+                  when 67 then :right
+                  when 66 then ctrl(:j)
+                  when 65 then ctrl(:k)
+                  when 90 then :stab
+                  else :nop
+                  end
           end
         end
 
-        maxc = C.cols - 3
-        matches[0, max_items].each_with_index do |item, idx|
-          next if !new_search && !((vcursor-1)..(vcursor+1)).include?(idx)
-          row = cursor_y - idx - 2
-          chosen = idx == vcursor
-          selected = selects.include?([*item][0])
-          line, offsets = convert_item item
-          tokens = format line, maxc, offsets
-          print_item row, tokens, chosen, selected
-        end
+        actions.fetch(ord, proc { |ord|
+          char = [ord].pack('U*')
+          if char =~ /[[:print:]]/
+            input.insert cursor, char
+            cursor += 1
+          end
+        }).call(ord)
 
-        print_info selects.length if !@lists.empty? || events[:loaded]
-        refresh
+        # Dispatch key event
+        emit(:key) { [@query.set(input.dup), cursor] }
       end
-    end#while
-  rescue Exception => e
-    main.raise e
-  end
-}
-
-got = nil
-begin
-  tty     = IO.open(IO.sysopen('/dev/tty'), 'r')
-  input   = ''
-  cursor  = 0
-  actions = {
-    :nop     => proc {},
-    ctrl(:c) => proc { exit 1 },
-    ctrl(:d) => proc { exit 1 if input.empty? },
-    ctrl(:m) => proc {
-      @mtx.synchronize do
-        got = [*@matches.fetch(@vcursor, [])][0]
+    ensure
+      C.close_screen
+      if got
+        @selects.delete got
+        @selects.each do |sel, _|
+          @stdout.puts sel
+        end
+        @stdout.puts got
       end
       exit 0
-    },
-    ctrl(:u) => proc { input = input[cursor..-1]; cursor = 0 },
-    ctrl(:a) => proc { cursor = 0 },
-    ctrl(:e) => proc { cursor = input.length },
-    ctrl(:j) => proc { emit(:vcursor) { @vcursor -= 1 } },
-    ctrl(:k) => proc { emit(:vcursor) { @vcursor += 1 } },
-    ctrl(:w) => proc {
-      ridx   = (input[0...cursor - 1].rindex(/\S\s/) || -2) + 2
-      input  = input[0...ridx] + input[cursor..-1]
-      cursor = ridx
-    },
-    127 => proc { input[cursor -= 1] = '' if cursor > 0 },
-    9 => proc { |o|
-      emit(:select) {
-        if sel = [*@matches.fetch(@vcursor, [])][0]
-          if @selects.has_key? sel
-            @selects.delete sel
-          else
-            @selects[sel] = 1
-          end
-          @vcursor = [0, @vcursor + (o == :stab ? 1 : -1)].max
-        end
-      } if @multi
-    },
-    :left => proc { cursor = [0, cursor - 1].max },
-    :right => proc { cursor = [input.length, cursor + 1].min },
-  }
-  actions[ctrl(:b)] = actions[:left]
-  actions[ctrl(:f)] = actions[:right]
-  actions[ctrl(:h)] = actions[127]
-  actions[ctrl(:n)] = actions[ctrl(:j)]
-  actions[ctrl(:p)] = actions[ctrl(:k)]
-  actions[:stab]    = actions[9]
-
-  while true
-    # Update user input
-    @smtx.synchronize do
-      @cursor_x = cursor
-      print_input
-      refresh
+    end
+  end
+
+  class FuzzyMatcher < Matcher
+    attr_reader :cache
+
+    def initialize rxflag
+      @cache  = Hash.new { |h, k| h[k] = {} }
+      @regexp = {}
+      @rxflag = rxflag
     end
 
-    ord = tty.getc.ord
-    if ord == 27
-      ord = tty.getc.ord
-      if ord == 91
-        ord = case tty.getc.ord
-              when 68 then :left
-              when 67 then :right
-              when 66 then ctrl(:j)
-              when 65 then ctrl(:k)
-              when 90 then :stab
-              else :nop
-              end
+    def match list, q, prefix, suffix
+      regexp = @regexp[q] ||= begin
+        q = q.downcase if @rxflag != 0
+        Regexp.new(convert_query(q).inject('') { |sum, e|
+          e = Regexp.escape e
+          sum << "#{e}[^#{e}]*?"
+        }, @rxflag)
       end
-    end
 
-    actions.fetch(ord, proc { |ord|
-      char = [ord].pack('U*')
-      if char =~ /[[:print:]]/
-        input.insert cursor, char
-        cursor += 1
+      cache = @cache[list.object_id]
+
+      prefix_cache = nil
+      (prefix.length - 1).downto(1) do |len|
+        break if prefix_cache = cache[prefix[0, len]]
       end
-    }).call(ord)
 
-    # Dispatch key event
-    emit(:key) { @query = input.dup }
-  end
-ensure
-  C.close_screen
-  if got
-    @selects.delete got
-    @selects.each do |sel, _|
-      stdout.puts sel
+      suffix_cache = nil
+      0.upto(suffix.length - 1) do |idx|
+        break if suffix_cache = cache[suffix[idx..-1]]
+      end unless suffix.empty?
+
+      partial_cache = [prefix_cache,
+                       suffix_cache].compact.sort_by { |e| e.length }.first
+      cache[q] ||= (partial_cache ?
+                    partial_cache.map { |e| e.first } : list).map { |line|
+        # Ignore errors: e.g. invalid byte sequence in UTF-8
+        md = line.match(regexp) rescue nil
+        md && [line, [md.offset(0)]]
+      }.compact
     end
-    stdout.puts got
   end
-end
+end#FZF
+
+FZF.new(ARGV, $stdin).start if $0 == __FILE__
 
diff --git a/fzf.gemspec b/fzf.gemspec
index c92f181ddf55c6933beb9475f468fe7316e7f887..3f3d2bbb4f3a98614dbaa459c3638feaa4cefa3e 100644
--- a/fzf.gemspec
+++ b/fzf.gemspec
@@ -1,7 +1,7 @@
 # coding: utf-8
 Gem::Specification.new do |spec|
   spec.name          = 'fzf'
-  spec.version       = '0.3.1'
+  spec.version       = '0.4.0'
   spec.authors       = ['Junegunn Choi']
   spec.email         = ['junegunn.c@gmail.com']
   spec.description   = %q{Fuzzy finder for your shell}
diff --git a/test/test_fzf.rb b/test/test_fzf.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2af60b8aea4348503f241139ba9ed92e3dcb0eab
--- /dev/null
+++ b/test/test_fzf.rb
@@ -0,0 +1,152 @@
+#!/usr/bin/env ruby
+# encoding: utf-8
+
+require 'minitest/autorun'
+$LOAD_PATH.unshift File.expand_path('../..', __FILE__)
+load 'fzf'
+
+class TestFZF < MiniTest::Unit::TestCase
+  def test_default_options
+    fzf = FZF.new []
+    assert_equal 500, fzf.sort
+    assert_equal false, fzf.multi
+    assert_equal true, fzf.color
+    assert_equal Regexp::IGNORECASE, fzf.rxflag
+
+    begin
+      ENV['FZF_DEFAULT_SORT'] = '1000'
+      fzf = FZF.new []
+      assert_equal 1000, fzf.sort
+    ensure
+      ENV.delete 'FZF_DEFAULT_SORT'
+    end
+  end
+
+  def test_option_parser
+    # Long opts
+    fzf = FZF.new %w[--sort=2000 --no-color --multi +i]
+    assert_equal 2000, fzf.sort
+    assert_equal true, fzf.multi
+    assert_equal false, fzf.color
+    assert_equal 0, fzf.rxflag
+
+    # Short opts
+    fzf = FZF.new %w[-s 2000 +c -m +i]
+    assert_equal 2000, fzf.sort
+    assert_equal true, fzf.multi
+    assert_equal false, fzf.color
+    assert_equal 0, fzf.rxflag
+  end
+
+  def test_invalid_option
+    [%w[-s 2000 +s], %w[yo dawg]].each do |argv|
+      assert_raises(SystemExit) do
+        fzf = FZF.new argv
+      end
+    end
+  end
+
+  # FIXME Only on 1.9 or above
+  def test_width
+    fzf = FZF.new []
+    assert_equal 5, fzf.width('abcde')
+    assert_equal 4, fzf.width('한글')
+    assert_equal 5, fzf.width('한글.')
+  end
+
+  def test_trim
+    fzf = FZF.new []
+    assert_equal ['사.',   6], fzf.trim('가나다라마바사.', 4, true)
+    assert_equal ['바사.', 5], fzf.trim('가나다라마바사.', 5, true)
+    assert_equal ['가나',  6], fzf.trim('가나다라마바사.', 4, false)
+    assert_equal ['가나',  6], fzf.trim('가나다라마바사.', 5, false)
+    assert_equal ['가나a', 6], fzf.trim('가나ab라마바사.', 5, false)
+  end
+
+  def test_format
+    fzf = FZF.new []
+    assert_equal [['01234..', false]], fzf.format('0123456789', 7, [])
+    assert_equal [['012', false], ['34', true], ['..', false]],
+      fzf.format('0123456789', 7, [[3, 5]])
+    assert_equal [['..56', false], ['789', true]],
+      fzf.format('0123456789', 7, [[7, 10]])
+    assert_equal [['..56', false], ['78', true], ['9', false]],
+      fzf.format('0123456789', 7, [[7, 9]])
+
+    (3..5).each do |i|
+      assert_equal [['..', false], ['567', true], ['89', false]],
+        fzf.format('0123456789', 7, [[i, 8]])
+    end
+
+    assert_equal [['..', false], ['345', true], ['..', false]],
+      fzf.format('0123456789', 7, [[3, 6]])
+    assert_equal [['012', false], ['34', true], ['..', false]],
+      fzf.format('0123456789', 7, [[3, 5]])
+
+    # Multi-region
+    assert_equal [["0", true], ["1", false], ["2", true], ["34..", false]],
+      fzf.format('0123456789', 7, [[0, 1], [2, 3]])
+
+    assert_equal [["..", false], ["5", true], ["6", false], ["78", true], ["9", false]],
+      fzf.format('0123456789', 7, [[3, 6], [7, 9]])
+
+    assert_equal [["..", false], ["3", true], ["4", false], ["5", true], ["..", false]],
+      fzf.format('0123456789', 7, [[3, 4], [5, 6]])
+
+    # Multi-region Overlap
+    assert_equal [["..", false], ["345", true], ["..", false]],
+      fzf.format('0123456789', 7, [[4, 5], [3, 6]])
+  end
+
+  def test_fuzzy_matcher
+    matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE
+    list = %w[
+      juice
+      juiceful
+      juiceless
+      juicily
+      juiciness
+      juicy]
+    assert matcher.cache.empty?
+    assert_equal(
+      [["juice",     [[0, 1]]],
+       ["juiceful",  [[0, 1]]],
+       ["juiceless", [[0, 1]]],
+       ["juicily",   [[0, 1]]],
+       ["juiciness", [[0, 1]]],
+       ["juicy",     [[0, 1]]]], matcher.match(list, 'j', '', '').sort)
+    assert !matcher.cache.empty?
+    assert_equal [list.object_id], matcher.cache.keys
+    assert_equal 1, matcher.cache[list.object_id].length
+    assert_equal 6, matcher.cache[list.object_id]['j'].length
+
+    assert_equal(
+      [["juicily",   [[0, 5]]],
+       ["juiciness", [[0, 5]]]], matcher.match(list, 'jii', '', '').sort)
+
+    assert_equal(
+      [["juicily",   [[2, 5]]],
+       ["juiciness", [[2, 5]]]], matcher.match(list, 'ii', '', '').sort)
+
+    assert_equal 3, matcher.cache[list.object_id].length
+    assert_equal 2, matcher.cache[list.object_id]['ii'].length
+
+    # TODO : partial_cache
+  end
+
+  def test_sort_by_rank
+    matcher = FZF::FuzzyMatcher.new Regexp::IGNORECASE
+    list = %w[
+      0____1
+      0_____1
+      01
+      ____0_1
+      01_
+      _01_
+      0______1
+      ___01___
+    ]
+    assert_equal %w[01 01_ _01_ ___01___ ____0_1 0____1 0_____1 0______1],
+        FZF.new([]).sort_by_rank(matcher.match(list, '01', '', '')).map(&:first)
+  end
+end