mirror of
https://github.com/junegunn/fzf
synced 2024-11-16 12:12:48 +00:00
commit
7280e8ebc2
@ -60,6 +60,7 @@ usage: fzf [options]
|
|||||||
-i Case-insensitive match (default: smart-case match)
|
-i Case-insensitive match (default: smart-case match)
|
||||||
+i Case-sensitive match
|
+i Case-sensitive match
|
||||||
+c, --no-color Disable colors
|
+c, --no-color Disable colors
|
||||||
|
--no-mouse Disable mouse
|
||||||
|
|
||||||
Environment variables
|
Environment variables
|
||||||
FZF_DEFAULT_COMMAND Default command to use when input is tty
|
FZF_DEFAULT_COMMAND Default command to use when input is tty
|
||||||
@ -103,6 +104,9 @@ The following readline key bindings should also work as expected.
|
|||||||
If you enable multi-select mode with `-m` option, you can select multiple items
|
If you enable multi-select mode with `-m` option, you can select multiple items
|
||||||
with TAB or Shift-TAB key.
|
with TAB or Shift-TAB key.
|
||||||
|
|
||||||
|
You can also use mouse. Click on an item to select it or shift-click to select
|
||||||
|
multiple items. Use mouse wheel to move the cursor up and down.
|
||||||
|
|
||||||
### Extended-search mode
|
### Extended-search mode
|
||||||
|
|
||||||
With `-x` or `--extended` option, fzf will start in "extended-search mode".
|
With `-x` or `--extended` option, fzf will start in "extended-search mode".
|
||||||
|
166
fzf
166
fzf
@ -7,7 +7,7 @@
|
|||||||
# / __/ / /_/ __/
|
# / __/ / /_/ __/
|
||||||
# /_/ /___/_/ Fuzzy finder for your shell
|
# /_/ /___/_/ Fuzzy finder for your shell
|
||||||
#
|
#
|
||||||
# Version: 0.6.2-devel (January 22, 2014)
|
# Version: 0.7.0 (January 30, 2014)
|
||||||
#
|
#
|
||||||
# Author: Junegunn Choi
|
# Author: Junegunn Choi
|
||||||
# URL: https://github.com/junegunn/fzf
|
# URL: https://github.com/junegunn/fzf
|
||||||
@ -40,9 +40,17 @@ require 'thread'
|
|||||||
require 'curses'
|
require 'curses'
|
||||||
require 'set'
|
require 'set'
|
||||||
|
|
||||||
|
unless String.method_defined? :force_encoding
|
||||||
|
class String
|
||||||
|
def force_encoding *arg
|
||||||
|
self
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class FZF
|
class FZF
|
||||||
C = Curses
|
C = Curses
|
||||||
attr_reader :rxflag, :sort, :color, :multi, :query, :extended
|
attr_reader :rxflag, :sort, :color, :mouse, :multi, :query, :extended
|
||||||
|
|
||||||
class AtomicVar
|
class AtomicVar
|
||||||
def initialize value
|
def initialize value
|
||||||
@ -73,6 +81,7 @@ class FZF
|
|||||||
@color = true
|
@color = true
|
||||||
@multi = false
|
@multi = false
|
||||||
@extended = false
|
@extended = false
|
||||||
|
@mouse = true
|
||||||
|
|
||||||
argv =
|
argv =
|
||||||
if opts = ENV['FZF_DEFAULT_OPTS']
|
if opts = ENV['FZF_DEFAULT_OPTS']
|
||||||
@ -93,6 +102,7 @@ class FZF
|
|||||||
when '+i' then @rxflag = 0
|
when '+i' then @rxflag = 0
|
||||||
when '-c', '--color' then @color = true
|
when '-c', '--color' then @color = true
|
||||||
when '+c', '--no-color' then @color = false
|
when '+c', '--no-color' then @color = false
|
||||||
|
when '--no-mouse' then @mouse = false
|
||||||
when '+s', '--no-sort' then @sort = nil
|
when '+s', '--no-sort' then @sort = nil
|
||||||
when '-q', '--query'
|
when '-q', '--query'
|
||||||
usage 1, 'query string required' unless query = argv.shift
|
usage 1, 'query string required' unless query = argv.shift
|
||||||
@ -110,7 +120,7 @@ class FZF
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@source = source
|
@source = source.clone
|
||||||
@mtx = Mutex.new
|
@mtx = Mutex.new
|
||||||
@cv = ConditionVariable.new
|
@cv = ConditionVariable.new
|
||||||
@events = {}
|
@events = {}
|
||||||
@ -163,6 +173,7 @@ class FZF
|
|||||||
-i Case-insensitive match (default: smart-case match)
|
-i Case-insensitive match (default: smart-case match)
|
||||||
+i Case-sensitive match
|
+i Case-sensitive match
|
||||||
+c, --no-color Disable colors
|
+c, --no-color Disable colors
|
||||||
|
--no-mouse Disable mouse
|
||||||
|
|
||||||
Environment variables
|
Environment variables
|
||||||
FZF_DEFAULT_COMMAND Default command to use when input is tty
|
FZF_DEFAULT_COMMAND Default command to use when input is tty
|
||||||
@ -463,6 +474,11 @@ class FZF
|
|||||||
|
|
||||||
def init_screen
|
def init_screen
|
||||||
C.init_screen
|
C.init_screen
|
||||||
|
if @mouse
|
||||||
|
C.mouseinterval 0
|
||||||
|
C.mousemask C::ALL_MOUSE_EVENTS
|
||||||
|
end
|
||||||
|
C.stdscr.keypad(true)
|
||||||
C.start_color
|
C.start_color
|
||||||
dbg =
|
dbg =
|
||||||
if C.respond_to?(:use_default_colors)
|
if C.respond_to?(:use_default_colors)
|
||||||
@ -472,6 +488,7 @@ class FZF
|
|||||||
C::COLOR_BLACK
|
C::COLOR_BLACK
|
||||||
end
|
end
|
||||||
C.raw
|
C.raw
|
||||||
|
C.nonl
|
||||||
C.noecho
|
C.noecho
|
||||||
|
|
||||||
if @color
|
if @color
|
||||||
@ -527,6 +544,7 @@ class FZF
|
|||||||
exit 1
|
exit 1
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
$stdin.reopen IO.open(IO.sysopen('/dev/tty'), 'r')
|
||||||
@source
|
@source
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -665,6 +683,7 @@ class FZF
|
|||||||
def start_renderer
|
def start_renderer
|
||||||
Thread.new do
|
Thread.new do
|
||||||
begin
|
begin
|
||||||
|
refresh
|
||||||
while blk = @queue.shift
|
while blk = @queue.shift
|
||||||
blk.call
|
blk.call
|
||||||
refresh
|
refresh
|
||||||
@ -680,10 +699,32 @@ class FZF
|
|||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def vselect &prc
|
||||||
|
@vcursor.set { |v| @vcursors << v; prc.call v }
|
||||||
|
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 test_mouse st, *states
|
||||||
|
states.any? { |s| s & st > 0 }
|
||||||
|
end
|
||||||
|
|
||||||
def start_loop
|
def start_loop
|
||||||
got = nil
|
got = nil
|
||||||
begin
|
begin
|
||||||
tty = IO.open(IO.sysopen('/dev/tty'), 'r')
|
|
||||||
input = @query.get.dup
|
input = @query.get.dup
|
||||||
cursor = input.length
|
cursor = input.length
|
||||||
backword = proc {
|
backword = proc {
|
||||||
@ -699,74 +740,115 @@ class FZF
|
|||||||
ctrl(:u) => proc { input = input[cursor..-1]; cursor = 0 },
|
ctrl(:u) => proc { input = input[cursor..-1]; cursor = 0 },
|
||||||
ctrl(:a) => proc { cursor = 0; nil },
|
ctrl(:a) => proc { cursor = 0; nil },
|
||||||
ctrl(:e) => proc { cursor = input.length; nil },
|
ctrl(:e) => proc { cursor = input.length; nil },
|
||||||
ctrl(:j) => proc { @vcursor.set { |v| @vcursors << v; v - 1 }; update_list false },
|
ctrl(:j) => proc { vselect { |v| v - 1 } },
|
||||||
ctrl(:k) => proc { @vcursor.set { |v| @vcursors << v; v + 1 }; update_list false },
|
ctrl(:k) => proc { vselect { |v| v + 1 } },
|
||||||
ctrl(:w) => proc {
|
ctrl(:w) => proc {
|
||||||
pcursor = cursor
|
pcursor = cursor
|
||||||
backword.call
|
backword.call
|
||||||
input = input[0...cursor] + input[pcursor..-1]
|
input = input[0...cursor] + input[pcursor..-1]
|
||||||
},
|
},
|
||||||
127 => proc { input[cursor -= 1] = '' if cursor > 0 },
|
ctrl(:h) => proc { input[cursor -= 1] = '' if cursor > 0 },
|
||||||
9 => proc { |o|
|
ctrl(:i) => proc { |o|
|
||||||
if @multi && sel = pick
|
if @multi && sel = pick
|
||||||
if @selects.has_key? sel
|
if @selects.has_key? sel
|
||||||
@selects.delete sel
|
@selects.delete sel
|
||||||
else
|
else
|
||||||
@selects[sel] = 1
|
@selects[sel] = 1
|
||||||
end
|
end
|
||||||
@vcursor.set { |v|
|
vselect { |v|
|
||||||
@vcursors << v
|
v + case o
|
||||||
v + (o == :stab ? 1 : -1)
|
when :select then 0
|
||||||
|
when C::KEY_BTAB then 1
|
||||||
|
else -1
|
||||||
|
end
|
||||||
}
|
}
|
||||||
update_list false
|
|
||||||
end
|
end
|
||||||
},
|
},
|
||||||
:left => proc { cursor = [0, cursor - 1].max; nil },
|
ctrl(:b) => proc { cursor = [0, cursor - 1].max; nil },
|
||||||
:right => proc { cursor = [input.length, cursor + 1].min; nil },
|
ctrl(:f) => proc { cursor = [input.length, cursor + 1].min; nil },
|
||||||
:alt_b => proc { backword.call; nil },
|
:alt_b => proc { backword.call; nil },
|
||||||
:alt_f => proc {
|
:alt_f => proc {
|
||||||
cursor += (input[cursor..-1].index(/(\S\s)|(.$)/) || -1) + 1
|
cursor += (input[cursor..-1].index(/(\S\s)|(.$)/) || -1) + 1
|
||||||
nil
|
nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
actions[ctrl(:b)] = actions[:left]
|
actions[C::KEY_UP] = actions[ctrl(:p)] = actions[ctrl(:k)]
|
||||||
actions[ctrl(:f)] = actions[:right]
|
actions[C::KEY_DOWN] = actions[ctrl(:n)] = actions[ctrl(:j)]
|
||||||
actions[ctrl(:h)] = actions[127]
|
actions[C::KEY_LEFT] = actions[ctrl(:b)]
|
||||||
actions[ctrl(:n)] = actions[ctrl(:j)]
|
actions[C::KEY_RIGHT] = actions[ctrl(:f)]
|
||||||
actions[ctrl(:p)] = actions[ctrl(:k)]
|
actions[C::KEY_BTAB] = actions[:select] = actions[ctrl(:i)]
|
||||||
|
actions[C::KEY_BACKSPACE] = actions[127] = actions[ctrl(:h)]
|
||||||
actions[ctrl(:q)] = actions[ctrl(:g)] = actions[ctrl(:c)] = actions[:esc]
|
actions[ctrl(:q)] = actions[ctrl(:g)] = actions[ctrl(:c)] = actions[:esc]
|
||||||
actions[:stab] = actions[9]
|
|
||||||
|
|
||||||
emit(:key) { [@query.get, cursor] } unless @query.empty?
|
emit(:key) { [@query.get, cursor] } unless @query.empty?
|
||||||
|
pmv = nil
|
||||||
while true
|
while true
|
||||||
@cursor_x.set cursor
|
@cursor_x.set cursor
|
||||||
render { print_input }
|
render { print_input }
|
||||||
|
|
||||||
ord = tty.getc.ord
|
C.stdscr.timeout = -1
|
||||||
ord =
|
ch = C.getch
|
||||||
case ord = (tty.read_nonblock(1).ord rescue :esc)
|
|
||||||
when 91
|
|
||||||
case (tty.read_nonblock(1).ord rescue nil)
|
|
||||||
when 68 then :left
|
|
||||||
when 67 then :right
|
|
||||||
when 66 then ctrl(:j)
|
|
||||||
when 65 then ctrl(:k)
|
|
||||||
when 90 then :stab
|
|
||||||
else next
|
|
||||||
end
|
|
||||||
when 'b'.ord then :alt_b
|
|
||||||
when 'f'.ord then :alt_f
|
|
||||||
when :esc then :esc
|
|
||||||
else next
|
|
||||||
end if ord == 27
|
|
||||||
|
|
||||||
upd = actions.fetch(ord, proc { |ord|
|
case ch
|
||||||
char = [ord].pack('U*')
|
when C::KEY_MOUSE
|
||||||
if char =~ /[[:print:]]/
|
if m = C.getmouse
|
||||||
input.insert cursor, char
|
st = m.bstate
|
||||||
|
if test_mouse(st, C::BUTTON1_PRESSED, C::BUTTON1_RELEASED)
|
||||||
|
if m.y == cursor_y
|
||||||
|
# TODO Wide-characters
|
||||||
|
cursor = [0, [input.length, m.x - 2].min].max
|
||||||
|
elsif m.x > 1 && m.y <= max_items
|
||||||
|
vselect { |v|
|
||||||
|
tv = max_items - m.y - 1
|
||||||
|
if test_mouse(st, C::BUTTON1_RELEASED)
|
||||||
|
if test_mouse(st, C::BUTTON_SHIFT)
|
||||||
|
ch = :select
|
||||||
|
elsif pmv == tv
|
||||||
|
ch = ctrl(:m)
|
||||||
|
end
|
||||||
|
pmv = tv
|
||||||
|
end
|
||||||
|
tv
|
||||||
|
}
|
||||||
|
end
|
||||||
|
elsif test_mouse(st, 0x8000000, C::BUTTON2_PRESSED)
|
||||||
|
ch = C::KEY_DOWN
|
||||||
|
elsif test_mouse(st, C::BUTTON4_PRESSED)
|
||||||
|
ch = C::KEY_UP
|
||||||
|
end
|
||||||
|
end
|
||||||
|
when 27
|
||||||
|
C.stdscr.timeout = 0
|
||||||
|
ch =
|
||||||
|
case ch2 = C.getch
|
||||||
|
when 'b' then :alt_b
|
||||||
|
when 'f' then :alt_f
|
||||||
|
when nil then :esc
|
||||||
|
else
|
||||||
|
ch2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
upd = actions.fetch(ch, proc { |ch|
|
||||||
|
if ch.is_a?(Fixnum)
|
||||||
|
# Ruby 1.8
|
||||||
|
if (ch.chr rescue '') =~ /[[:print:]]/
|
||||||
|
ch = ch.chr
|
||||||
|
elsif (nch = num_unicode_bytes(ch)) > 1
|
||||||
|
chs = [ch]
|
||||||
|
(nch - 1).times do |i|
|
||||||
|
chs << C.getch
|
||||||
|
end
|
||||||
|
# UTF-8 TODO Ruby 1.8
|
||||||
|
ch = chs.pack('C*').force_encoding('UTF-8')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if ch.is_a?(String) && ch =~ /[[:print:]]/
|
||||||
|
input.insert cursor, ch
|
||||||
cursor += 1
|
cursor += 1
|
||||||
end
|
end
|
||||||
}).call(ord)
|
}).call(ch)
|
||||||
|
|
||||||
# Dispatch key event
|
# Dispatch key event
|
||||||
emit(:key) { [@query.set(input.dup), cursor] } if upd
|
emit(:key) { [@query.set(input.dup), cursor] } if upd
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
Gem::Specification.new do |spec|
|
Gem::Specification.new do |spec|
|
||||||
spec.name = 'fzf'
|
spec.name = 'fzf'
|
||||||
spec.version = '0.6.2'
|
spec.version = '0.7.0'
|
||||||
spec.authors = ['Junegunn Choi']
|
spec.authors = ['Junegunn Choi']
|
||||||
spec.email = ['junegunn.c@gmail.com']
|
spec.email = ['junegunn.c@gmail.com']
|
||||||
spec.description = %q{Fuzzy finder for your shell}
|
spec.description = %q{Fuzzy finder for your shell}
|
||||||
|
@ -20,6 +20,7 @@ class TestFZF < MiniTest::Unit::TestCase
|
|||||||
assert_equal false, fzf.multi
|
assert_equal false, fzf.multi
|
||||||
assert_equal true, fzf.color
|
assert_equal true, fzf.color
|
||||||
assert_equal nil, fzf.rxflag
|
assert_equal nil, fzf.rxflag
|
||||||
|
assert_equal true, fzf.mouse
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_environment_variables
|
def test_environment_variables
|
||||||
@ -28,7 +29,7 @@ class TestFZF < MiniTest::Unit::TestCase
|
|||||||
fzf = FZF.new []
|
fzf = FZF.new []
|
||||||
assert_equal 20000, fzf.sort
|
assert_equal 20000, fzf.sort
|
||||||
|
|
||||||
ENV['FZF_DEFAULT_OPTS'] = '-x -m -s 10000 -q " hello world " +c'
|
ENV['FZF_DEFAULT_OPTS'] = '-x -m -s 10000 -q " hello world " +c --no-mouse'
|
||||||
fzf = FZF.new []
|
fzf = FZF.new []
|
||||||
assert_equal 10000, fzf.sort
|
assert_equal 10000, fzf.sort
|
||||||
assert_equal ' hello world ',
|
assert_equal ' hello world ',
|
||||||
@ -36,14 +37,16 @@ class TestFZF < MiniTest::Unit::TestCase
|
|||||||
assert_equal true, fzf.extended
|
assert_equal true, fzf.extended
|
||||||
assert_equal true, fzf.multi
|
assert_equal true, fzf.multi
|
||||||
assert_equal false, fzf.color
|
assert_equal false, fzf.color
|
||||||
|
assert_equal false, fzf.mouse
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_option_parser
|
def test_option_parser
|
||||||
# Long opts
|
# Long opts
|
||||||
fzf = FZF.new %w[--sort=2000 --no-color --multi +i --query hello --extended]
|
fzf = FZF.new %w[--sort=2000 --no-color --multi +i --query hello --extended --no-mouse]
|
||||||
assert_equal 2000, fzf.sort
|
assert_equal 2000, fzf.sort
|
||||||
assert_equal true, fzf.multi
|
assert_equal true, fzf.multi
|
||||||
assert_equal false, fzf.color
|
assert_equal false, fzf.color
|
||||||
|
assert_equal false, fzf.mouse
|
||||||
assert_equal 0, fzf.rxflag
|
assert_equal 0, fzf.rxflag
|
||||||
assert_equal 'hello', fzf.query.get
|
assert_equal 'hello', fzf.query.get
|
||||||
assert_equal true, fzf.extended
|
assert_equal true, fzf.extended
|
||||||
@ -53,6 +56,7 @@ class TestFZF < MiniTest::Unit::TestCase
|
|||||||
assert_equal nil, fzf.sort
|
assert_equal nil, fzf.sort
|
||||||
assert_equal false, fzf.multi
|
assert_equal false, fzf.multi
|
||||||
assert_equal true, fzf.color
|
assert_equal true, fzf.color
|
||||||
|
assert_equal true, fzf.mouse
|
||||||
assert_equal 1, fzf.rxflag
|
assert_equal 1, fzf.rxflag
|
||||||
assert_equal 'hello', fzf.query.get
|
assert_equal 'hello', fzf.query.get
|
||||||
assert_equal false, fzf.extended
|
assert_equal false, fzf.extended
|
||||||
|
Loading…
Reference in New Issue
Block a user