summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2017-02-06 21:06:12 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2017-02-06 21:06:12 +0900
commit876c233a2641507d67b470cabbdedfb5dcd4696d (patch)
tree2fd2d860afca6d3d5743b8eda697a7d7c8a1ac14
parentee5aeb80a49fbf7bb958e09aa641ef17fb885884 (diff)
Remove Ruby version
Related #832
-rwxr-xr-xfzf1348
-rwxr-xr-xinstall81
2 files changed, 5 insertions, 1424 deletions
diff --git a/fzf b/fzf
deleted file mode 100755
index 62893af4..00000000
--- a/fzf
+++ /dev/null
@@ -1,1348 +0,0 @@
-#!/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