#!/usr/bin/env ruby
# encoding: utf-8
#
#     ____      ____
#    / __/___  / __/
#   / /_/_  / / /_
#  / __/ / /_/ __/
# /_/   /___/_/    Fuzzy finder for your shell
#
# Version: 0.8.9 (Dec 24, 2014)
# Deprecation alert:
#          This script is no longer maintained. Use the new Go version.
#
# Author:  Junegunn Choi
# URL:     https://github.com/junegunn/fzf
# License: MIT
#
# Copyright (c) 2014 Junegunn Choi
#
# MIT License
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

begin
  require 'curses'
rescue LoadError
  $stderr.puts 'curses gem is not installed. Try `gem install curses`.'
  sleep 1
  exit 1
end
require 'thread'
require 'set'

unless String.method_defined? :force_encoding
  class String
    def force_encoding *arg
      self
    end
  end
end

class String
  attr_accessor :orig

  def tokenize delim, nth
    unless delim
      # AWK default
      prefix_length = (index(/\S/) || 0) rescue 0
      tokens = scan(/\S+\s*/) rescue []
    else
      prefix_length = 0
      tokens = scan(delim) rescue []
    end
    nth.map { |n|
      if n.begin == 0 && n.end == -1
        [prefix_length, tokens.join]
      elsif part = tokens[n]
        [prefix_length + (tokens[0...(n.begin)] || []).join.length,
         part.join]
      end
    }.compact
  end
end

class FZF
  C = Curses
  attr_reader :rxflag, :sort, :nth, :color, :black, :ansi256, :reverse, :prompt,
              :mouse, :multi, :query, :select1, :exit0, :filter, :extended,
              :print_query, :with_nth

  def sync
    @shr_mtx.synchronize { yield }
  end

  def get name
    sync { instance_variable_get name }
  end

  def geta(*names)
    sync { names.map { |name| instance_variable_get name } }
  end

  def call(name, method, *args)
    sync { instance_variable_get(name).send(method, *args) }
  end

  def set name, value = nil
    sync do
      instance_variable_set name,
        (block_given? ? yield(instance_variable_get(name)) : value)
    end
  end

  def initialize argv, source = $stdin
    @rxflag   = nil
    @sort     = ENV.fetch('FZF_DEFAULT_SORT', 1000).to_i
    @color    = true
    @ansi256  = true
    @black    = false
    @multi    = false
    @mouse    = true
    @extended = nil
    @select1  = false
    @exit0    = false
    @filter   = nil
    @nth      = nil
    @with_nth = nil
    @delim    = nil
    @reverse  = false
    @prompt   = '> '
    @shr_mtx  = Mutex.new
    @expect   = false
    @print_query = false

    argv =
      if opts = ENV['FZF_DEFAULT_OPTS']
        require 'shellwords'
        Shellwords.shellwords(opts) + argv
      else
        argv.dup
      end
    while o = argv.shift
      case o
      when '--version'           then FZF.version
      when '-h', '--help'        then usage 0
      when '-m', '--multi'       then @multi    = true
      when '+m', '--no-multi'    then @multi    = 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
      when '+c', '--no-color'    then @color    = false
      when '-2', '--256'         then @ansi256  = true
      when '+2', '--no-256'      then @ansi256  = false
      when       '--black'       then @black    = true
      when       '--no-black'    then @black    = false
      when       '--mouse'       then @mouse    = true
      when       '--no-mouse'    then @mouse    = false
      when       '--reverse'     then @reverse  = true
      when       '--no-reverse'  then @reverse  = false
      when '+s', '--no-sort'     then @sort     = nil
      when '-1', '--select-1'    then @select1  = true
      when '+1', '--no-select-1' then @select1  = false
      when '-0', '--exit-0'      then @exit0    = true
      when '+0', '--no-exit-0'   then @exit0    = false
      when '-q', '--query'
        usage 1, 'query string required' unless query = argv.shift
        @query = query
      when /^-q(.*)$/, /^--query=(.*)$/
        @query = $1
      when '-f', '--filter'
        usage 1, 'query string required' unless query = argv.shift
        @filter = query
      when /^-f(.*)$/, /^--filter=(.*)$/
        @filter = $1
      when '-n', '--nth'
        usage 1, 'field expression required' unless nth = argv.shift
        @nth = parse_nth nth
      when /^-n([0-9,-\.]+)$/, /^--nth=([0-9,-\.]+)$/
        @nth = parse_nth $1
      when '--with-nth'
        usage 1, 'field expression required' unless nth = argv.shift
        @with_nth = parse_nth nth
      when /^--with-nth=([0-9,-\.]+)$/
        @with_nth = parse_nth $1
      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'
        usage 1, 'sort size required' unless sort = argv.shift
        usage 1, 'invalid sort size'  unless sort =~ /^[0-9]+$/
        @sort = sort.to_i
      when /^-s([0-9]+)$/, /^--sort=([0-9]+)$/
        @sort = $1.to_i
      when '--prompt'
        usage 1, 'prompt string required' unless prompt = argv.shift
        @prompt = prompt
      when /^--prompt=(.*)$/
        @prompt = $1
      when '--print-query'             then @print_query = true
      when '--no-print-query'          then @print_query = false
      when '-e', '--extended-exact'    then @extended = :exact
      when '+e', '--no-extended-exact' then @extended = nil
      when '--expect'
        argv.shift
        @expect = true
      when /^--expect=(.*)$/
        @expect = true
      when '--toggle-sort', '--tiebreak', '--color', '--bind', '--history', '--history-size'
        argv.shift
      when '--tac', '--no-tac', '--sync', '--no-sync', '--hscroll', '--no-hscroll',
           '--inline-info', '--no-inline-info', '--read0', '--cycle', /^--bind=(.*)$/,
           /^--color=(.*)$/, /^--toggle-sort=(.*)$/, /^--tiebreak=(.*)$/, /^--history(-max)?=(.*)$/
        # XXX
      else
        usage 1, "illegal option: #{o}"
      end
    end

    @source  = source.clone
    @evt_mtx = Mutex.new
    @cv      = ConditionVariable.new
    @events  = {}
    @new     = []
    @queue   = Queue.new
    @pending = nil
    @rev_dir = @reverse ? -1 : 1
    @stdout  = $stdout.clone

    unless @filter
      # Shared variables: needs protection
      @query   ||= ''
      @matches   = []
      @count     = 0
      @xcur      = @query.length
      @ycur      = 0
      @yoff      = 0
      @dirty     = Set.new
      @spinner   = '-\|/-\|/'.split(//)
      @selects   = {} # ordered >= 1.9

      @main      = Thread.current
      @plcount   = 0
    end
  end

  def parse_nth nth
    ranges = nth.split(',').map { |expr|
      x = proc { usage 1, "invalid field expression: #{expr}" }
      first, second = expr.split('..', 2)
      x.call if !first.empty? && first.to_i == 0 ||
        second && !second.empty? && (second.to_i == 0 || second.include?('.'))

      first  = first.empty? ? 1 : first.to_i
      second = case second
               when nil then first
               when ''  then -1
               else second.to_i
               end

      Range.new(*[first, second].map { |e| e > 0 ? e - 1 : e })
    }
    ranges == [0..-1] ? nil : ranges
  end

  def FZF.build_delim_regex delim
    Regexp.compile(delim) rescue (delim = Regexp.escape(delim))
    Regexp.compile "(?:.*?#{delim})|(?:.+?$)"
  end

  def burp string, orig = nil
    @stdout.puts(orig || string.orig || string)
  end

  def start
    if @filter
      start_reader.join
      filter_list @new
    else
      start_reader
      query = get(:@query)
      emit(:key) { [query, query.length] } unless empty = query.empty?
      if @select1 || @exit0
        start_search do |loaded, matches|
          len = empty ? get(:@count) : matches.length
          if loaded
            if @select1 && len == 1
              puts @query if @print_query
              puts if @expect
              burp(empty ? matches.first : matches.first.first)
              exit 0
            elsif @exit0 && len == 0
              puts @query if @print_query
              puts if @expect
              exit 0
            end
          end

          if loaded || len > 1
            start_renderer
            Thread.new { start_loop }
          end
        end

        sleep
      else
        start_search
        start_renderer
        start_loop
      end
    end
  end

  def filter_list list
    puts @filter if @print_query
    matches = matcher.match(list, @filter, '', '')
    if @sort && matches.length <= @sort
      matches = FZF.sort(matches)
    end
    matches.each { |m| puts m.first }
  end

  def matcher
    @matcher ||=
      if @extended
        ExtendedFuzzyMatcher.new @rxflag, @extended, @nth, @delim
      else
        FuzzyMatcher.new @rxflag, @nth, @delim
      end
  end

  class << self
    def version
      File.open(__FILE__, 'r') do |f|
        f.each_line do |line|
          if line =~ /Version: (.*)/
            $stdout.puts 'fzf ' << $1
            exit
          end
        end
      end
    end

    def sort list
      list.sort_by { |tuple| rank tuple }
    end

    def rank tuple
      line, offsets = tuple
      matchlen = 0
      pe = 0
      offsets.sort.each do |pair|
        b, e = pair
        b    = pe if pe > b
        pe   = e  if e > pe
        matchlen += e - b if e > b
      end
      [matchlen, line.length, line]
    end
  end

  def usage x, message = nil
    $stderr.puts message if message
    $stderr.puts %[usage: fzf [options]

  Search
    -x, --extended        Extended-search mode
    -e, --extended-exact  Extended-search mode (exact match)
    -i                    Case-insensitive match (default: smart-case match)
    +i                    Case-sensitive match
    -n, --nth=N[,..]      Comma-separated list of field index expressions
                          for limiting search scope. Each can be a non-zero
                          integer or a range expression ([BEGIN]..[END]).
        --with-nth=N[,..] Transform the item using index expressions for search
    -d, --delimiter=STR   Field delimiter regex for --nth (default: AWK-style)

  Search result
    -s, --sort=MAX        Maximum number of matched items to sort (default: 1000)
    +s, --no-sort         Do not sort the result. Keep the sequence unchanged.

  Interface
    -m, --multi           Enable multi-select with tab/shift-tab
        --no-mouse        Disable mouse
    +c, --no-color        Disable colors
    +2, --no-256          Disable 256-color
        --black           Use black background
        --reverse         Reverse orientation
        --prompt=STR      Input prompt (default: '> ')

  Scripting
    -q, --query=STR       Start the finder with the given query
    -1, --select-1        Automatically select the only match
    -0, --exit-0          Exit immediately when there's no match
    -f, --filter=STR      Filter mode. Do not start interactive finder.
        --print-query     Print query as the first line

  Environment variables
    FZF_DEFAULT_COMMAND   Default command to use when input is tty
    FZF_DEFAULT_OPTS      Default options (e.g. "-x -m --sort 10000")] + $/ + $/
   exit x
  end

  def emit event
    @evt_mtx.synchronize do
      @events[event] = yield
      @cv.broadcast
    end
  end

  def max_items; C.lines - 2; end

  def cursor_y offset = 0
    @reverse ? (offset) : (C.lines - 1 - offset)
  end

  def cprint str, col
    C.attron(col) do
      addstr_safe str
    end if str
  end
  def addstr_safe str
    str = str.gsub("\0", '') rescue str
    C.addstr str
  end

  def print_input
    C.setpos cursor_y, 0
    C.clrtoeol
    cprint @prompt, color(:prompt, true)
    C.attron(C::A_BOLD) do
      C.addstr get(:@query)
    end
  end

  def print_info msg = nil
    C.setpos cursor_y(1), 0
    C.clrtoeol

    prefix =
      if spin_char = call(:@spinner, :first)
        cprint spin_char, color(:spinner, true)
        ' '
      else
        '  '
      end
    C.attron color(:info, false) do
      sync do
        C.addstr "#{prefix}#{@matches.length}/#{@count}"
        if (selected = @selects.length) > 0
          C.addstr " (#{selected})"
        end
      end
      C.addstr msg if msg
    end
  end

  def refresh
    query, xcur = geta(:@query, :@xcur)
    C.setpos cursor_y, @prompt.length + width(query[0, xcur])
    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
      end
    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] if index < line.length
    tokens.reject { |pair| pair.first.empty? }
  end

  def print_item row, tokens, chosen, selected
    # Cursor
    C.setpos row, 0
    C.clrtoeol
    cprint chosen ? '>' : ' ', color(:cursor, true)
    cprint selected ? '>' : ' ',
      chosen ? color(:chosen) : (selected ? color(:selected, 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
        addstr_safe token
      end
    end
    C.attroff color(:chosen, true) if chosen
  end

  AFTER_1_9 = RUBY_VERSION.split('.').map { |e| e.rjust(3, '0') }.join >= '001009'

  if AFTER_1_9
    @@wrx = Regexp.new '\p{Han}|\p{Katakana}|\p{Hiragana}|\p{Hangul}'
    def width str
      str.gsub(@@wrx, '  ').length rescue str.length
    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) rescue 1
        str    = left ? str[1..-1] : str[0...-1]
        diff  += 1
      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]
      end
    end

    class ::String
      def ord
        self.unpack('c').first
      end
    end

    class ::Fixnum
      def ord
        self
      end
    end
  end

  def init_screen
    $stdout.reopen($stderr)

    C.init_screen
    C.mousemask C::ALL_MOUSE_EVENTS if @mouse
    C.start_color
    dbg =
      if !@black && C.respond_to?(:use_default_colors)
        C.use_default_colors
        -1
      else
        C::COLOR_BLACK
      end
    C.raw
    C.noecho

    if @color
      if @ansi256 && ENV['TERM'].to_s =~ /256/
        C.init_pair 1, 110, dbg
        C.init_pair 2, 108, dbg
        C.init_pair 3, 254, 236
        C.init_pair 4, 151, 236
        C.init_pair 5, 148, dbg
        C.init_pair 6, 144, dbg
        C.init_pair 7, 161, 236
        C.init_pair 8, 168, 236
      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
        C.init_pair 8, C::COLOR_MAGENTA, C::COLOR_BLACK
      end

      def self.color sym, bold = false
        C.color_pair([:prompt, :match, :chosen, :match!,
                      :spinner, :info, :cursor, :selected].index(sym) + 1) |
          (bold ? C::A_BOLD : 0)
      end
    else
      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

    C.refresh
  end

  def start_reader
    stream =
      if @source.tty?
        default_command = ENV['FZF_DEFAULT_COMMAND']
        if default_command && !default_command.empty?
          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

    Thread.new do
      if @with_nth
        while line = stream.gets
          emit(:new) { @new << transform(line) }
        end
      else
        while line = stream.gets
          emit(:new) { @new << line.chomp }
        end
      end
      emit(:loaded) { true }
      @spinner.clear if @spinner
    end
  end

  def transform line
    line = line.chomp
    mut = (line =~ / $/ ? line : line + ' ').
      tokenize(@delim, @with_nth).map { |e| e.last }.join('').sub(/ *$/, '')
    mut.orig = line
    mut
  end

  def start_search &callback
    Thread.new do
      lists   = []
      events  = {}
      fcache  = {}
      q       = ''
      delay   = -5

      begin
        while true
          @evt_mtx.synchronize do
            while true
              events.merge! @events

              if @events.empty? # No new events
                @cv.wait @evt_mtx
                next
              end
              @events.clear
              break
            end

            if events[:new]
              lists << @new
              set(:@count) { |c| c + @new.length }
              set(:@spinner) { |spinner|
                if e = spinner.shift
                  spinner.push e
                end; spinner
              }
              @new = []
              fcache.clear
            end
          end#mtx

          new_search = events[:key] || events.delete(:new)
          user_input = events[:key]
          progress   = 0
          started_at = Time.now

          if updated = new_search && !lists.empty?
            q, cx = events.delete(:key) || [q, 0]
            empty = matcher.empty?(q)
            unless matches = fcache[q]
              found = []
              skip  = false
              cnt   = 0
              lists.each do |list|
                cnt += list.length
                skip = @evt_mtx.synchronize { @events[:key] }
                break if skip

                if !empty && (progress = 100 * cnt / get(:@count)) < 100 && Time.now - started_at > 0.5
                  render { print_info " (#{progress}%)" }
                end

                found.concat(q.empty? ? list :
                             matcher.match(list, q, q[0, cx], q[cx..-1]))
              end
              if skip
                sleep 0.1
                next
              end
              matches = @sort ? found : found.reverse
              if !empty && @sort && matches.length <= @sort
                matches = FZF.sort(matches)
              end
              fcache[q] = matches
            end

            # Atomic update
            set(:@matches, matches)
          end#new_search

          callback = nil if callback &&
                            (updated || events[:loaded]) &&
                            callback.call(events[:loaded], matches)

          # This small delay reduces the number of partial lists
          sleep((delay = [20, delay + 5].min) * 0.01) unless user_input

          update_list new_search
        end#while
      rescue Exception => e
        @main.raise e
      end
    end
  end

  def pick
    sync do
      item = @matches[@ycur]
      item.is_a?(Array) ? item[0] : item
    end
  end

  def constrain offset, cursor, count, height
    original = [offset, cursor]
    diffpos = cursor - offset

    # Constrain cursor
    cursor = [0, [cursor, count - 1].min].max

    # Ceil
    if cursor > offset + (height - 1)
      offset = cursor - (height - 1)
    # Floor
    elsif offset > cursor
      offset = cursor
    end

    # Adjustment
    if count - offset < height
      offset = [0, count - height].max
      cursor = [0, [offset + diffpos, count - 1].min].max
    end

    [[offset, cursor] != original, offset, cursor]
  end

  def update_list wipe
    render do
      pos, items = sync {
        changed, @yoff, @ycur =
            constrain(@yoff, @ycur, @matches.length, max_items)
        wipe ||= changed

        [@ycur - @yoff, @matches[@yoff, max_items]]
      }

      # Wipe
      if items.length < @plcount
        @plcount.downto(items.length) do |idx|
          C.setpos cursor_y(idx + 2), 0
          C.clrtoeol
        end
      end
      @plcount = items.length

      dirty = Set[pos]
      set(:@dirty) do |vs|
        dirty.merge vs
        Set.new
      end
      items.each_with_index do |item, idx|
        next unless wipe || dirty.include?(idx)
        row           = cursor_y(idx + 2)
        chosen        = idx == pos
        selected      = @selects.include?([*item][0])
        line, offsets = item
        tokens        = format line, C.cols - 3, offsets
        print_item row, tokens, chosen, selected
      end
      print_info
      print_input
    end
  end

  def start_renderer
    init_screen

    Thread.new do
      begin
        while blk = @queue.shift
          blk.call
          refresh
        end
      rescue Exception => e
        @main.raise e
      end
    end
  end

  def render &blk
    @queue.push blk
    nil
  end

  def vselect &prc
    sync do
      @dirty << @ycur - @yoff
      @ycur = prc.call @ycur
    end
    update_list false
  end

  def num_unicode_bytes chr
    # http://en.wikipedia.org/wiki/UTF-8
    if chr & 0b10000000 > 0
      bytes = 0
      7.downto(2) do |shift|
        break if (chr >> shift) & 0x1 == 0
        bytes += 1
      end
      bytes
    else
      1
    end
  end

  def read_nb chars = 1, default = nil, tries = 10
    tries.times do |_|
      begin
        return @tty.read_nonblock(chars).ord
      rescue Exception
        sleep 0.01
      end
    end
    default
  end

  def read_nbs
    ords = []
    while ord = read_nb
      ords << ord
    end
    ords
  end

  def get_mouse
    case ord = read_nb
    when 32, 36, 40, 48, # mouse-down / shift / cmd / ctrl
         35, 39, 43, 51  # mouse-up / shift / cmd / ctrl
      x = read_nb - 33
      y = read_nb - 33
      { :event => (ord % 2 == 0 ? :click : :release),
        :x => x, :y => y, :shift => ord >= 36 }
    when 96, 100, 104, 112, # scroll-up / shift / cmd / ctrl
         97, 101, 105, 113  # scroll-down / shift / cmd / ctrl
      read_nb(2)
      { :event => :scroll, :diff => (ord % 2 == 0 ? -1 : 1), :shift => ord >= 100 }
    else
      # e.g. 40, 43, 104, 105
      read_nb(2)
      nil
    end
  end

  def get_input actions
    @tty ||=
      begin
        require 'io/console'
        IO.console
      rescue LoadError
        IO.open(IO.sysopen('/dev/tty'), 'r')
      end

    if pending = @pending
      @pending = nil
      return pending
    end

    str = ''
    while true
      ord =
        if str.empty?
          @tty.getc.ord
        else
          begin
            ord = @tty.read_nonblock(1).ord
            if (nb = num_unicode_bytes(ord)) > 1
              ords = [ord]
              (nb - 1).times do |_|
                ords << @tty.read_nonblock(1).ord
              end
              # UTF-8 TODO Ruby 1.8
              ords.pack('C*').force_encoding('UTF-8')
            else
              ord
            end
          rescue Exception
            return str
          end
        end

      ord =
        case read_nb(1, :esc)
        when 91, 79
          case read_nb(1, nil)
          when 68 then ctrl(:b)
          when 67 then ctrl(:f)
          when 66 then ctrl(:j)
          when 65 then ctrl(:k)
          when 90 then :stab
          when 50 then read_nb; :ins
          when 51 then read_nb; :del
          when 53 then read_nb; :pgup
          when 54 then read_nb; :pgdn
          when 49
            case read_nbs
            when [59, 50, 68] then ctrl(:a)
            when [59, 50, 67] then ctrl(:e)
            when [59, 53, 68] then :alt_b
            when [59, 53, 67] then :alt_f
            when [126]        then ctrl(:a)
            end
          when 52 then read_nb; ctrl(:e)
          when 72 then ctrl(:a)
          when 70 then ctrl(:e)
          when 77
            get_mouse
          end
        when 'b',  98 then :alt_b
        when 'd', 100 then :alt_d
        when 'f', 102 then :alt_f
        when :esc     then :esc
        when 127      then :alt_bs
        else          next
        end if ord == 27

      return ord if ord.nil? || ord.is_a?(Hash)

      if actions.has_key?(ord)
        if str.empty?
          return ord
        else
          @pending = ord
          return str
        end
      else
        unless ord.is_a? String
          ord = [ord].pack('U*')
        end
        str << ord if ord =~ /[[:print:]]/
      end
    end
  end

  class MouseEvent
    DOUBLE_CLICK_INTERVAL = 0.5

    attr_reader :v

    def initialize v = nil
      @c = 0
      @v = v
      @t = Time.at 0
    end

    def v= v
      @c = (@v == v && within?) ? @c + 1 : 0
      @v = v
      @t = Time.now
    end

    def double? v
      @c == 1 && @v == v && within?
    end

    def within?
      (Time.now - @t) < DOUBLE_CLICK_INTERVAL
    end
  end

  def start_loop
    got = nil
    begin
      input  = call(:@query, :dup)
      cursor = input.length
      yanked = ''
      mouse_event = MouseEvent.new
      backword = proc {
        cursor = (input[0, cursor].rindex(/[^[:alnum:]][[:alnum:]]/) || -1) + 1
        nil
      }
      forward = proc {
        cursor += (input[cursor..-1].index(/([[:alnum:]][^[:alnum:]])|(.$)/) || -1) + 1
        nil
      }
      rubout = proc { |regex|
        pcursor = cursor
        cursor = (input[0, cursor].rindex(regex) || -1) + 1
        if pcursor > cursor
          yanked = input[cursor...pcursor]
          input = input[0...cursor] + input[pcursor..-1]
        end
      }
      actions = {
        :esc     => proc { exit 1 },
        ctrl(:d) => proc {
          if input.empty?
            exit 1
          elsif cursor < input.length
            input = input[0...cursor] + input[(cursor + 1)..-1]
          end
        },
        ctrl(:m) => proc {
          got = pick
          exit 0
        },
        ctrl(:u) => proc {
          yanked = input[0...cursor] if cursor > 0
          input = input[cursor..-1]
          cursor = 0
        },
        ctrl(:a) => proc { cursor = 0; nil },
        ctrl(:e) => proc { cursor = input.length; nil },
        ctrl(:j) => proc { vselect { |v| v - @rev_dir } },
        ctrl(:k) => proc { vselect { |v| v + @rev_dir } },
        ctrl(:w) => proc { rubout.call /\s\S/ },
        ctrl(:y) => proc { actions[:default].call yanked },
        ctrl(:h) => proc { input[cursor -= 1] = '' if cursor > 0 },
        ctrl(:i) => proc { |o|
          if @multi && sel = pick
            sync do
              if @selects.has_key? sel
                @selects.delete sel
              else
                @selects[sel] = sel.orig
              end
            end
            vselect { |v| v + case o
                              when :stab then 1
                              when :sclick then 0
                              else -1
                              end * @rev_dir }
          end
        },
        ctrl(:b) => proc { cursor = [0, cursor - 1].max; nil },
        ctrl(:f) => proc { cursor = [input.length, cursor + 1].min; nil },
        ctrl(:l) => proc { render { C.clear; C.refresh }; update_list true },
        :del     => proc { input[cursor] = '' if input.length > cursor },
        :pgup    => proc { vselect { |v| v + @rev_dir * (max_items - 1) } },
        :pgdn    => proc { vselect { |v| v - @rev_dir * (max_items - 1) } },
        :alt_bs  => proc { rubout.call /[^[:alnum:]][[:alnum:]]/ },
        :alt_b   => proc { backword.call },
        :alt_d   => proc {
          pcursor = cursor
          forward.call
          if cursor > pcursor
            yanked = input[pcursor...cursor]
            input = input[0...pcursor] + input[cursor..-1]
            cursor = pcursor
          end
        },
        :alt_f   => proc {
          forward.call
        },
        :default => proc { |val|
          case val
          when String
            input.insert cursor, val
            cursor += val.length
          when Hash
            event = val[:event]
            case event
            when :click, :release
              x, y, shift = val.values_at :x, :y, :shift
              y = @reverse ? (C.lines - 1 - y) : y
              if y == C.lines - 1
                cursor = [0, [input.length, x - @prompt.length].min].max
              elsif x > 1 && y <= max_items
                tv = get(:@yoff) + max_items - y - 1

                case event
                when :click
                  vselect { |_| tv }
                  actions[ctrl(:i)].call(:sclick) if shift
                  mouse_event.v = tv
                when :release
                  if !shift && mouse_event.double?(tv)
                    actions[ctrl(:m)].call
                  end
                end
              end
            when :scroll
              diff, shift = val.values_at :diff, :shift
              actions[ctrl(:i)].call(:sclick) if shift
              actions[ctrl(diff > 0 ? :j : :k)].call
            end
            nil
          end
        }
      }
      actions[ctrl(:p)] = actions[ctrl(:k)]
      actions[ctrl(:n)] = actions[ctrl(:j)]
      actions[:stab]    = actions[ctrl(:i)]
      actions[127]      = actions[ctrl(:h)]
      actions[ctrl(:q)] = actions[ctrl(:g)] = actions[ctrl(:c)] = actions[:esc]

      while true
        set(:@xcur, cursor)
        render { print_input }

        if key = get_input(actions)
          upd = actions.fetch(key, actions[:default]).call(key)

          # Dispatch key event
          emit(:key) { [set(:@query, input.dup), cursor] } if upd
        end
      end
    ensure
      C.close_screen
      q, selects = geta(:@query, :@selects)
      @stdout.puts q if @print_query
      @stdout.puts if @expect
      if got
        if selects.empty?
          burp got
        else
          selects.each do |sel, orig|
            burp sel, orig
          end
        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
      @delim = delim
      @tokens_cache = {}
    end

    def tokenize str
      @tokens_cache[str] ||= str.tokenize(@delim, @nth)
    end

    def do_match str, pat
      if @nth
        tokenize(str).each do |pair|
          prefix_length, token = pair
          if md = token.match(pat) rescue nil
            return MatchData.new(md.offset(0).map { |o| o + prefix_length })
          end
        end
        nil
      else
        str.match(pat) rescue nil
      end
    end
  end

  class FuzzyMatcher < Matcher
    attr_reader :caches, :rxflag

    def initialize rxflag, nth = nil, delim = nil
      super nth, delim
      @caches = Hash.new { |h, k| h[k] = {} }
      @regexp = {}
      @rxflag = rxflag
    end

    def empty? q
      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 == Regexp::IGNORECASE
        Regexp.new(q.split(//).inject('') { |sum, e|
          e = Regexp.escape e
          sum << (e.length > 1 ? "(?:#{e}).*?" :  # FIXME: not equivalent
                                 "#{e}[^#{e}]*?")
        }, rxflag_for(q))
      end
    end

    def match list, q, prefix, suffix
      regexp = fuzzy_regex q

      cache = @caches[list.object_id]
      prefix_cache = nil
      (prefix.length - 1).downto(1) do |len|
        break if prefix_cache = cache[prefix[0, len]]
      end

      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 = do_match(line, regexp)
        md && [line, [md.offset(0)]]
      }.compact
    end
  end

  class ExtendedFuzzyMatcher < FuzzyMatcher
    def initialize rxflag, mode = :fuzzy, nth = nil, delim = nil
      super rxflag, nth, delim
      @regexps = {}
      @mode = mode
    end

    def empty? q
      parse(q).empty?
    end

    def parse q
      q = q.strip
      @regexps[q] ||= q.split(/\s+/).map { |w|
        invert =
          if w =~ /^!/
            w = w[1..-1]
            true
          end

        [ @regexp[w] ||=
            case w
            when ''
              nil
            when /^\^(.*)\$$/
              Regexp.new('^' << Regexp.escape($1) << '$', rxflag_for(w))
            when /^'/
              if @mode == :fuzzy && w.length > 1
                exact_regex w[1..-1]
              elsif @mode == :exact
                exact_regex w
              end
            when /^\^/
              w.length > 1 ?
                Regexp.new('^' << Regexp.escape(w[1..-1]), rxflag_for(w)) : nil
            when /\$$/
              w.length > 1 ?
                Regexp.new(Regexp.escape(w[0..-2]) << '$', rxflag_for(w)) : nil
            else
              @mode == :fuzzy ? fuzzy_regex(w) : exact_regex(w)
            end, invert ]
      }.select { |pair| pair.first }
    end

    def exact_regex w
      Regexp.new(Regexp.escape(w), rxflag_for(w))
    end

    def match list, q, prefix, suffix
      regexps = parse q
      # Look for prefix cache
      cache  = @caches[list.object_id]
      prefix = prefix.strip.sub(/\$\S*$/, '').sub(/(^|\s)!\S*$/, '')
      prefix_cache = nil
      (prefix.length - 1).downto(1) do |len|
        break if prefix_cache = cache[Set[@regexps[prefix[0, len]]]]
      end

      cache[Set[regexps]] ||= (prefix_cache ?
                               prefix_cache.map { |e| e.first } :
                               list).map { |line|
        offsets = []
        regexps.all? { |pair|
          regexp, invert = pair
          md = do_match(line, regexp)
          if md && !invert
            offsets << md.offset(0)
          elsif !md && invert
            true
          end
        } && [line, offsets]
      }.select { |e| e }
    end
  end
end#FZF

FZF.new(ARGV, $stdin).start if ENV.fetch('FZF_EXECUTABLE', '1') == '1'

