m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-12 13:23:48 -05:00

Compare commits

..

11 Commits
0.5.1 ... 0.6.0

Author SHA1 Message Date
Junegunn Choi
622c54f4a3 Update gem version (0.6.0)
- Smart-case pattern matching
- CTRL-Q
2013-12-22 16:00:06 +09:00
Junegunn Choi
e09993f919 Update README 2013-12-22 00:36:39 +09:00
Junegunn Choi
7ee6fd1f6d Make install script to add key bindings as well 2013-12-22 00:18:41 +09:00
Junegunn Choi
2dca6f0cb2 Update Last update 2013-12-20 16:13:38 +09:00
Junegunn Choi
159dd7f069 Implement smart-case match (#12) 2013-12-20 15:30:48 +09:00
Junegunn Choi
b30f21e074 CTRL-Q to terminate the finder (#11) 2013-12-20 14:01:28 +09:00
Junegunn Choi
636c86cf6f Update bash host completion for ssh and telnet commands 2013-12-20 11:18:28 +09:00
Junegunn Choi
5483e41b2a Update README 2013-12-14 22:30:09 +09:00
Junegunn Choi
1c89994c94 Suppress warnings on old version of Ruby 2013-12-11 01:03:47 +09:00
Junegunn Choi
e1bc4b983e Update gem version 2013-12-11 01:00:11 +09:00
Junegunn Choi
cb3645ea95 Fix ^.*$ pattern matching in extended-search mode (#9) 2013-12-09 14:46:06 +09:00
6 changed files with 195 additions and 45 deletions

View File

@@ -16,8 +16,6 @@ fzf requires Ruby (>= 1.8.5).
Installation Installation
------------ ------------
### Using install script
Clone this repository and run Clone this repository and run
[install](https://github.com/junegunn/fzf/blob/master/install) script. [install](https://github.com/junegunn/fzf/blob/master/install) script.
@@ -29,29 +27,10 @@ git clone https://github.com/junegunn/fzf.git ~/.fzf
The script will generate `~/.fzf.bash` and `~/.fzf.zsh` and update your The script will generate `~/.fzf.bash` and `~/.fzf.zsh` and update your
`.bashrc` and `.zshrc` to load them. `.bashrc` and `.zshrc` to load them.
### Manual installation
Or you can just download Or you can just download
[fzf executable](https://raw.github.com/junegunn/fzf/master/fzf) and put it [fzf executable](https://raw.github.com/junegunn/fzf/master/fzf) and put it
somewhere in your search $PATH. somewhere in your search $PATH.
```sh
mkdir -p ~/bin
wget https://raw.github.com/junegunn/fzf/master/fzf -O ~/bin/fzf
chmod +x ~/bin/fzf
```
### Install as Ruby gem
fzf can be installed as a Ruby gem
```
gem install fzf
```
It's a bit easier to install and update the script but the Ruby gem version
takes slightly longer to start.
### Install as Vim plugin ### Install as Vim plugin
You can use any Vim plugin manager to install fzf for Vim. If you don't use one, You can use any Vim plugin manager to install fzf for Vim. If you don't use one,
@@ -78,6 +57,7 @@ usage: fzf [options]
-q, --query=STR Initial query -q, --query=STR Initial query
-s, --sort=MAX Maximum number of matched items to sort. Default: 1000 -s, --sort=MAX Maximum number of matched items to sort. Default: 1000
+s, --no-sort Do not sort the result. Keep the sequence unchanged. +s, --no-sort Do not sort the result. Keep the sequence unchanged.
-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
``` ```
@@ -173,8 +153,8 @@ Most of the time, you will prefer native Vim plugins with better integration
with Vim. The only reason one might consider using fzf in Vim is its speed. For with Vim. The only reason one might consider using fzf in Vim is its speed. For
a very large list of files, fzf is significantly faster and it does not block. a very large list of files, fzf is significantly faster and it does not block.
Useful bash examples Useful examples
-------------------- ---------------
```sh ```sh
# vimf - Open selected file in Vim # vimf - Open selected file in Vim
@@ -189,7 +169,7 @@ fd() {
# fda - including hidden directories # fda - including hidden directories
fda() { fda() {
DIR=$(find ${1:-*} -type d 2> /dev/null | fzf) && cd "$DIR" DIR=$(find ${1:-.} -type d 2> /dev/null | fzf) && cd "$DIR"
} }
# fh - repeat history # fh - repeat history
@@ -203,8 +183,16 @@ fkill() {
} }
``` ```
bash key bindings Key bindings for command line
----------------- -----------------------------
The install script will add the following key bindings to your configuration
files.
### bash
- `CTRL-T` - Paste the selected file path(s) into the command line
- `CTRL-R` - Paste the selected command from history into the command line
```sh ```sh
# Required to refresh the prompt after fzf # Required to refresh the prompt after fzf
@@ -212,7 +200,9 @@ bind '"\er": redraw-current-line'
# CTRL-T - Paste the selected file path into the command line # CTRL-T - Paste the selected file path into the command line
fsel() { fsel() {
find ${1:-*} | fzf -m | while read item; do find * -path '*/\.*' -prune \
-o -type f -print \
-o -type l -print 2> /dev/null | fzf -m | while read item; do
printf '%q ' "$item" printf '%q ' "$item"
done done
echo echo
@@ -223,8 +213,11 @@ bind '"\C-t": " \C-u \C-a\C-k$(fsel)\e\C-e\C-y\C-a\C-y\ey\C-h\C-e\er"'
bind '"\C-r": " \C-e\C-u$(history | fzf +s | sed \"s/ *[0-9]* *//\")\e\C-e\er"' bind '"\C-r": " \C-e\C-u$(history | fzf +s | sed \"s/ *[0-9]* *//\")\e\C-e\er"'
``` ```
zsh widgets ### zsh
-----------
- `CTRL-T` - Paste the selected file path(s) into the command line
- `CTRL-R` - Paste the selected command from history into the command line
- `ALT-C` - cd into the selected directory
```sh ```sh
# CTRL-T - Paste the selected file path(s) into the command line # CTRL-T - Paste the selected file path(s) into the command line
@@ -262,8 +255,8 @@ zle -N fzf-history-widget
bindkey '^R' fzf-history-widget bindkey '^R' fzf-history-widget
``` ```
Auto-completion (experimental) Auto-completion
------------------------------ ---------------
Disclaimer: *Auto-completion feature is currently experimental, it can change Disclaimer: *Auto-completion feature is currently experimental, it can change
over time* over time*
@@ -367,6 +360,20 @@ when it's running on Ruby 1.8. If you experience the problem, upgrade your Ruby
to 1.9 or above. Ruby 1.9 or above is also required for displaying Unicode to 1.9 or above. Ruby 1.9 or above is also required for displaying Unicode
characters. characters.
### Ranking algorithm
fzf sorts the result first by the length of the matched substring, then by the
length of the whole string. However it only does so when the number of matches
is less than the limit which is by default 1000, in order to avoid the cost of
sorting a large list and limit the response time of the query.
This limit can be adjusted with `-s` option, or with the environment variable
`FZF_DEFAULT_SORT`.
```sh
export FZF_DEFAULT_SORT=10000
```
License License
------- -------

26
fzf
View File

@@ -10,7 +10,7 @@
# URL: https://github.com/junegunn/fzf # URL: https://github.com/junegunn/fzf
# Author: Junegunn Choi # Author: Junegunn Choi
# License: MIT # License: MIT
# Last update: December 5, 2013 # Last update: December 20, 2013
# #
# Copyright (c) 2013 Junegunn Choi # Copyright (c) 2013 Junegunn Choi
# #
@@ -67,7 +67,7 @@ class FZF
end end
def initialize argv, source = $stdin def initialize argv, source = $stdin
@rxflag = Regexp::IGNORECASE @rxflag = nil
@sort = ENV.fetch('FZF_DEFAULT_SORT', 1000).to_i @sort = ENV.fetch('FZF_DEFAULT_SORT', 1000).to_i
@color = true @color = true
@multi = false @multi = false
@@ -79,6 +79,7 @@ class FZF
when '-h', '--help' then usage 0 when '-h', '--help' then usage 0
when '-m', '--multi' then @multi = true when '-m', '--multi' then @multi = true
when '-x', '--extended' then @xmode = true when '-x', '--extended' then @xmode = true
when '-i' then @rxflag = Regexp::IGNORECASE
when '+i' then @rxflag = 0 when '+i' then @rxflag = 0
when '+s', '--no-sort' then @sort = nil when '+s', '--no-sort' then @sort = nil
when '+c', '--no-color' then @color = false when '+c', '--no-color' then @color = false
@@ -136,6 +137,7 @@ class FZF
-q, --query=STR Initial query -q, --query=STR Initial query
-s, --sort=MAX Maximum number of matched items to sort. Default: 1000 -s, --sort=MAX Maximum number of matched items to sort. Default: 1000
+s, --no-sort Do not sort the result. Keep the sequence unchanged. +s, --no-sort Do not sort the result. Keep the sequence unchanged.
-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]
exit x exit x
@@ -347,7 +349,7 @@ class FZF
tokens << [line[b...e], true] tokens << [line[b...e], true]
index = e index = e
end end
tokens << [line[index..-1], false] tokens << [line[index..-1], false] if index < line.length
tokens.reject { |pair| pair.first.empty? } tokens.reject { |pair| pair.first.empty? }
end end
@@ -706,7 +708,7 @@ class FZF
actions[ctrl(:h)] = actions[127] actions[ctrl(:h)] = actions[127]
actions[ctrl(:n)] = actions[ctrl(:j)] actions[ctrl(:n)] = actions[ctrl(:j)]
actions[ctrl(:p)] = actions[ctrl(:k)] actions[ctrl(:p)] = actions[ctrl(:k)]
actions[ctrl(:g)] = actions[ctrl(:c)] = actions[:esc] actions[ctrl(:q)] = actions[ctrl(:g)] = actions[ctrl(:c)] = actions[:esc]
actions[:stab] = actions[9] actions[:stab] = actions[9]
emit(:key) { [@query.get, cursor] } unless @query.empty? emit(:key) { [@query.get, cursor] } unless @query.empty?
@@ -770,14 +772,18 @@ class FZF
q.empty? q.empty?
end end
def rxflag_for q
@rxflag || (q =~ /[A-Z]/ ? 0 : Regexp::IGNORECASE)
end
def fuzzy_regex q def fuzzy_regex q
@regexp[q] ||= begin @regexp[q] ||= begin
q = q.downcase if @rxflag != 0 q = q.downcase if @rxflag == Regexp::IGNORECASE
Regexp.new(query_chars(q).inject('') { |sum, e| Regexp.new(query_chars(q).inject('') { |sum, e|
e = Regexp.escape e e = Regexp.escape e
sum << (e.length > 1 ? "(?:#{e}).*?" : # FIXME: not equivalent sum << (e.length > 1 ? "(?:#{e}).*?" : # FIXME: not equivalent
"#{e}[^#{e}]*?") "#{e}[^#{e}]*?")
}, @rxflag) }, rxflag_for(q))
end end
end end
@@ -829,15 +835,17 @@ class FZF
case w case w
when '' when ''
nil nil
when /^\^(.*)\$$/
Regexp.new('^' << sanitize(Regexp.escape($1)) << '$', rxflag_for(w))
when /^'/ when /^'/
w.length > 1 ? w.length > 1 ?
Regexp.new(sanitize(Regexp.escape(w[1..-1])), rxflag) : nil Regexp.new(sanitize(Regexp.escape(w[1..-1])), rxflag_for(w)) : nil
when /^\^/ when /^\^/
w.length > 1 ? w.length > 1 ?
Regexp.new('^' << sanitize(Regexp.escape(w[1..-1])), rxflag) : nil Regexp.new('^' << sanitize(Regexp.escape(w[1..-1])), rxflag_for(w)) : nil
when /\$$/ when /\$$/
w.length > 1 ? w.length > 1 ?
Regexp.new(sanitize(Regexp.escape(w[0..-2])) << '$', rxflag) : nil Regexp.new(sanitize(Regexp.escape(w[0..-2])) << '$', rxflag_for(w)) : nil
else else
fuzzy_regex w fuzzy_regex w
end, invert ] end, invert ]

View File

@@ -100,7 +100,7 @@ _fzf_host_completion() {
local cur prev selected local cur prev selected
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}" prev="${COMP_WORDS[COMP_CWORD-1]}"
[ "$cur" = '-l' -o "$prev" = '-l' ] && return 1 [[ "$cur" =~ ^- || "$prev" =~ ^- ]] && return 1
tput sc tput sc
selected=$(grep -v '^\s*\(#\|$\)' /etc/hosts | awk '{print $2}' | sort -u | fzf $FZF_COMPLETION_OPTS -q "$cur") selected=$(grep -v '^\s*\(#\|$\)' /etc/hosts | awk '{print $2}' | sort -u | fzf $FZF_COMPLETION_OPTS -q "$cur")

View File

@@ -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.5.1' spec.version = '0.6.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}

77
install
View File

@@ -38,6 +38,12 @@ echo
[[ ! $REPLY =~ ^[Nn]$ ]] [[ ! $REPLY =~ ^[Nn]$ ]]
auto_completion=$? auto_completion=$?
# Key-bindings
read -p "Do you want to add key bindings? ([y]/n) " -n 1 -r
echo
[[ ! $REPLY =~ ^[Nn]$ ]]
key_bindings=$?
echo echo
for shell in bash zsh; do for shell in bash zsh; do
echo -n "Generate ~/.fzf.$shell ... " echo -n "Generate ~/.fzf.$shell ... "
@@ -49,14 +55,85 @@ for shell in bash zsh; do
fi fi
cat > $src << EOF cat > $src << EOF
# Setup fzf function
# ------------------
unalias fzf 2> /dev/null unalias fzf 2> /dev/null
fzf() { fzf() {
$fzf_cmd "\$@" $fzf_cmd "\$@"
} }
export -f fzf > /dev/null export -f fzf > /dev/null
# Auto-completion
# ---------------
$fzf_completion $fzf_completion
EOF EOF
if [ $key_bindings -eq 0 ]; then
if [ $shell = bash ]; then
cat >> $src << "EOF"
# Key bindings
# ------------
# Required to refresh the prompt after fzf
bind '"\er": redraw-current-line'
# CTRL-T - Paste the selected file path into the command line
fsel() {
find * -path '*/\.*' -prune \
-o -type f -print \
-o -type l -print 2> /dev/null | fzf -m | while read item; do
printf '%q ' "$item"
done
echo
}
bind '"\C-t": " \C-u \C-a\C-k$(fsel)\e\C-e\C-y\C-a\C-y\ey\C-h\C-e\er"'
# CTRL-R - Paste the selected command from history into the command line
bind '"\C-r": " \C-e\C-u$(history | fzf +s | sed \"s/ *[0-9]* *//\")\e\C-e\er"'
EOF
else
cat >> $src << "EOF"
# Key bindings
# ------------
# CTRL-T - Paste the selected file path(s) into the command line
fzf-file-widget() {
local FILES
local IFS="
"
FILES=($(
find * -path '*/\.*' -prune \
-o -type f -print \
-o -type l -print 2> /dev/null | fzf -m))
unset IFS
FILES=$FILES:q
LBUFFER="${LBUFFER%% #} $FILES"
zle redisplay
}
zle -N fzf-file-widget
bindkey '^T' fzf-file-widget
# ALT-C - cd into the selected directory
fzf-cd-widget() {
cd "${$(find * -path '*/\.*' -prune \
-o -type d -print 2> /dev/null | fzf):-.}"
zle reset-prompt
}
zle -N fzf-cd-widget
bindkey '\ec' fzf-cd-widget
# CTRL-R - Paste the selected command from history into the command line
fzf-history-widget() {
LBUFFER=$(history | fzf +s | sed "s/ *[0-9]* *//")
zle redisplay
}
zle -N fzf-history-widget
bindkey '^R' fzf-history-widget
EOF
fi
fi
echo "OK" echo "OK"
done done

View File

@@ -12,7 +12,7 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal 1000, fzf.sort assert_equal 1000, fzf.sort
assert_equal false, fzf.multi assert_equal false, fzf.multi
assert_equal true, fzf.color assert_equal true, fzf.color
assert_equal Regexp::IGNORECASE, fzf.rxflag assert_equal nil, fzf.rxflag
begin begin
ENV['FZF_DEFAULT_SORT'] = '1500' ENV['FZF_DEFAULT_SORT'] = '1500'
@@ -152,15 +152,59 @@ class TestFZF < MiniTest::Unit::TestCase
# TODO : partial_cache # TODO : partial_cache
end end
def test_fuzzy_matcher_rxflag
assert_equal nil, FZF::FuzzyMatcher.new(nil).rxflag
assert_equal 0, FZF::FuzzyMatcher.new(0).rxflag
assert_equal 1, FZF::FuzzyMatcher.new(1).rxflag
assert_equal 1, FZF::FuzzyMatcher.new(nil).rxflag_for('abc')
assert_equal 0, FZF::FuzzyMatcher.new(nil).rxflag_for('Abc')
assert_equal 0, FZF::FuzzyMatcher.new(0).rxflag_for('abc')
assert_equal 0, FZF::FuzzyMatcher.new(0).rxflag_for('Abc')
assert_equal 1, FZF::FuzzyMatcher.new(1).rxflag_for('abc')
assert_equal 1, FZF::FuzzyMatcher.new(1).rxflag_for('Abc')
end
def test_fuzzy_matcher_case_sensitive def test_fuzzy_matcher_case_sensitive
# Smart-case match (Uppercase found)
assert_equal [['Fruit', [[0, 5]]]],
FZF::FuzzyMatcher.new(nil).match(%w[Fruit Grapefruit], 'Fruit', '', '').sort
# Smart-case match (Uppercase not-found)
assert_equal [["Fruit", [[0, 5]]], ["Grapefruit", [[5, 10]]]],
FZF::FuzzyMatcher.new(nil).match(%w[Fruit Grapefruit], 'fruit', '', '').sort
# Case-sensitive match (-i)
assert_equal [['Fruit', [[0, 5]]]], assert_equal [['Fruit', [[0, 5]]]],
FZF::FuzzyMatcher.new(0).match(%w[Fruit Grapefruit], 'Fruit', '', '').sort FZF::FuzzyMatcher.new(0).match(%w[Fruit Grapefruit], 'Fruit', '', '').sort
# Case-insensitive match (+i)
assert_equal [["Fruit", [[0, 5]]], ["Grapefruit", [[5, 10]]]], assert_equal [["Fruit", [[0, 5]]], ["Grapefruit", [[5, 10]]]],
FZF::FuzzyMatcher.new(Regexp::IGNORECASE). FZF::FuzzyMatcher.new(Regexp::IGNORECASE).
match(%w[Fruit Grapefruit], 'Fruit', '', '').sort match(%w[Fruit Grapefruit], 'Fruit', '', '').sort
end end
def test_extended_fuzzy_matcher_case_sensitive
%w['Fruit Fruit$].each do |q|
# Smart-case match (Uppercase found)
assert_equal [['Fruit', [[0, 5]]]],
FZF::ExtendedFuzzyMatcher.new(nil).match(%w[Fruit Grapefruit], q, '', '').sort
# Smart-case match (Uppercase not-found)
assert_equal [["Fruit", [[0, 5]]], ["Grapefruit", [[5, 10]]]],
FZF::ExtendedFuzzyMatcher.new(nil).match(%w[Fruit Grapefruit], q.downcase, '', '').sort
# Case-sensitive match (-i)
assert_equal [['Fruit', [[0, 5]]]],
FZF::ExtendedFuzzyMatcher.new(0).match(%w[Fruit Grapefruit], q, '', '').sort
# Case-insensitive match (+i)
assert_equal [["Fruit", [[0, 5]]], ["Grapefruit", [[5, 10]]]],
FZF::ExtendedFuzzyMatcher.new(Regexp::IGNORECASE).
match(%w[Fruit Grapefruit], q, '', '').sort
end
end
def test_extended_fuzzy_matcher def test_extended_fuzzy_matcher
matcher = FZF::ExtendedFuzzyMatcher.new Regexp::IGNORECASE matcher = FZF::ExtendedFuzzyMatcher.new Regexp::IGNORECASE
list = %w[ list = %w[
@@ -197,6 +241,11 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal list.length, match.call('j', '').length assert_equal list.length, match.call('j', '').length
assert_equal list.length - 1, match.call('^j', '').length assert_equal list.length - 1, match.call('^j', '').length
# ^ + $
assert_equal 0, match.call('^juici$', '').length
assert_equal 1, match.call('^juice$', '').length
assert_equal 0, match.call('^.*$', '').length
# ! # !
assert_equal 0, match.call('!j', '').length assert_equal 0, match.call('!j', '').length
@@ -332,5 +381,14 @@ class TestFZF < MiniTest::Unit::TestCase
assert_equal ["a", "b", "c", "\xFF", "d", "e", "f"], assert_equal ["a", "b", "c", "\xFF", "d", "e", "f"],
FZF::UConv.split("abc\xFFdef") FZF::UConv.split("abc\xFFdef")
end end
# ^$ -> matches empty item
def test_format_empty_item
fzf = FZF.new []
item = ['', [[0, 0]]]
line, offsets = fzf.convert_item item
tokens = fzf.format line, 80, offsets
assert_equal [], tokens
end
end end