Compare commits

...

22 Commits

Author SHA1 Message Date
Junegunn Choi
9a2d9ad947 0.10.4 2015-08-29 02:36:27 +09:00
Junegunn Choi
90b0cd44ac Should not strip ANSI codes when --ansi is not set 2015-08-28 21:23:10 +09:00
Junegunn Choi
698e8008df [vim] Dynamic height specification for 'up' and 'down' options
Values for 'up' and 'down' can be written with ~ prefix. Only applies
when the source is a Vim list.

    e.g. { 'source': range(10), 'down': '~40%' }
2015-08-28 18:38:47 +09:00
Junegunn Choi
1de4cc3ba8 [install] Fall back statically-linked binary on 64-bit linux
Close #322
2015-08-27 22:50:59 +09:00
Junegunn Choi
0d66ad23c6 Fix build script 2015-08-27 22:48:42 +09:00
Junegunn Choi
7f7741099b make linux-static (#322) 2015-08-27 03:28:05 +09:00
Junegunn Choi
5a72dc6922 Fix #329 - Trim ANSI codes from output when --ansi & --with-nth are set 2015-08-26 23:58:18 +09:00
Junegunn Choi
80ed02e72e Add failing test case for #329 2015-08-26 23:35:31 +09:00
Junegunn Choi
8fb31e1b4d [vim] Escape % and # when running source command with :! 2015-08-24 01:52:16 +09:00
Junegunn Choi
148f21415a Mention fzf.vim project 2015-08-22 19:33:04 +09:00
Junegunn Choi
1c31e07d34 [install] Improve error message 2015-08-19 19:42:06 +09:00
Junegunn Choi
55d566b72f Revert "[vim] Open silently"
This reverts commit c601fc6437.
2015-08-18 12:03:08 +09:00
Junegunn Choi
60336c7423 Remove Vim examples from README.md 2015-08-16 02:47:52 +09:00
Junegunn Choi
7ae877bd3a [vim] Handle single/double quote characters in 'dir' option 2015-08-16 00:04:45 +09:00
Junegunn Choi
c601fc6437 [vim] Open silently 2015-08-15 23:53:27 +09:00
Junegunn Choi
e5fec408c4 [vim] tab split instead of tabedit 2015-08-15 23:53:11 +09:00
Junegunn Choi
8156e9894e 0.10.3 2015-08-12 02:09:46 +09:00
Junegunn Choi
cacc212f12 [install] Prerelease of 0.10.3 2015-08-11 00:21:09 +09:00
Junegunn Choi
d0f2c00f9f Fix --with-nth performance; use simpler regular expression
Related #317
2015-08-11 00:15:41 +09:00
Junegunn Choi
766427de0c Fix --with-nth performance; avoid regex if possible
Close #317
2015-08-10 18:34:20 +09:00
Junegunn Choi
a7b75c99a5 [install] Stop installer when failed to download the binary
Close #312
2015-08-08 03:53:46 +09:00
Junegunn Choi
bae10a6582 [install] Add an extra new line character
so that it doesn't corrupt file that doesn't end with a new line
character. Close #311.
2015-08-05 23:50:38 +09:00
19 changed files with 282 additions and 165 deletions

View File

@@ -1,6 +1,21 @@
CHANGELOG
=========
0.10.4
------
- Fixed to remove ANSI code from output when `--with-nth` is set
0.10.3
------
- Fixed slow performance of `--with-nth` when used with `--delimiter`
- Regular expression engine of Golang as of now is very slow, so the fixed
version will treat the given delimiter pattern as a plain string instead
of a regular expression unless it contains special characters and is
a valid regular expression.
- Simpler regular expression for delimiter for better performance
0.10.2
------

View File

@@ -254,6 +254,9 @@ export FZF_COMPLETION_OPTS='+c -x'
Usage as Vim plugin
-------------------
This repository only enables basic integration with Vim. If you're looking for
more, check out [fzf.vim](https://github.com/junegunn/fzf.vim) project.
(Note: To use fzf in GVim, an external terminal emulator is required.)
#### `:FZF[!]`
@@ -286,10 +289,8 @@ customization.
#### `fzf#run([options])`
For more advanced uses, you can call `fzf#run()` function which returns the list
of the selected items.
`fzf#run()` may take an options-dictionary:
For more advanced uses, you can use `fzf#run()` function with the following
options.
| Option name | Type | Description |
| -------------------------- | ------------- | ---------------------------------------------------------------- |
@@ -305,65 +306,7 @@ of the selected items.
| `launcher` | string | External terminal emulator to start fzf with (GVim only) |
| `launcher` | funcref | Function for generating `launcher` string (GVim only) |
_However on Neovim `fzf#run` is asynchronous and does not return values so you
should use `sink` or `sink*` to process the output from fzf._
##### Examples
If `sink` option is not given, `fzf#run` will simply return the list.
```vim
let items = fzf#run({ 'options': '-m +c', 'dir': '~', 'source': 'ls' })
```
But if `sink` is given as a string, the command will be executed for each
selected item.
```vim
" Each selected item will be opened in a new tab
let items = fzf#run({ 'sink': 'tabe', 'options': '-m +c', 'dir': '~', 'source': 'ls' })
```
We can also use a Vim list as the source as follows:
```vim
" Choose a color scheme with fzf
nnoremap <silent> <Leader>C :call fzf#run({
\ 'source':
\ map(split(globpath(&rtp, "colors/*.vim"), "\n"),
\ "substitute(fnamemodify(v:val, ':t'), '\\..\\{-}$', '', '')"),
\ 'sink': 'colo',
\ 'options': '+m',
\ 'left': 20,
\ 'launcher': 'xterm -geometry 20x30 -e bash -ic %s'
\ })<CR>
```
`sink` option can be a function reference. The following example creates a
handy mapping that selects an open buffer.
```vim
" List of buffers
function! s:buflist()
redir => ls
silent ls
redir END
return split(ls, '\n')
endfunction
function! s:bufopen(e)
execute 'buffer' matchstr(a:e, '^[ 0-9]*')
endfunction
nnoremap <silent> <Leader><Enter> :call fzf#run({
\ 'source': reverse(<sid>buflist()),
\ 'sink': function('<sid>bufopen'),
\ 'options': '+m',
\ 'down': len(<sid>buflist()) + 2
\ })<CR>
```
More examples can be found on [the wiki
Examples can be found on [the wiki
page](https://github.com/junegunn/fzf/wiki/Examples-(vim)).
Tips
@@ -425,14 +368,6 @@ of fzf to a temporary file.
fzf > $TMPDIR/fzf.result; and vim (cat $TMPDIR/fzf.result)
```
#### Handling UTF-8 NFD paths on OSX
Use iconv to convert NFD paths to NFC:
```sh
find . | iconv -f utf-8-mac -t utf8//ignore | fzf
```
License
-------

32
install
View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash
[[ "$@" =~ --pre ]] && version=0.10.2 pre=1 ||
version=0.10.2 pre=0
[[ "$@" =~ --pre ]] && version=0.10.4 pre=1 ||
version=0.10.4 pre=0
cd $(dirname $BASH_SOURCE)
fzf_base=$(pwd)
@@ -21,16 +21,21 @@ ask() {
check_binary() {
echo -n " - Checking fzf executable ... "
local output=$("$fzf_base"/bin/fzf --version 2>&1)
if [ "$version" = "$output" ]; then
local output
output=$("$fzf_base"/bin/fzf --version 2>&1)
if [ $? -ne 0 ]; then
echo "Error: $output"
binary_error="Invalid binary"
elif [ "$version" != "$output" ]; then
echo "$output != $version"
binary_error="Invalid version"
else
echo "$output"
binary_error=""
else
echo "$output != $version"
rm -f "$fzf_base"/bin/fzf
binary_error="Invalid binary"
return 1
return 0
fi
rm -f "$fzf_base"/bin/fzf
return 1
}
symlink() {
@@ -75,7 +80,12 @@ download() {
return
fi
chmod +x $1 && symlink $1 && check_binary
chmod +x $1 && symlink $1 || return 1
if [[ $1 =~ linux_amd64$ ]]; then
check_binary || download $1-static
else
check_binary
fi
}
# Try to download binary executable
@@ -96,6 +106,7 @@ if [ -n "$binary_error" ]; then
echo "No prebuilt binary for $archi ... "
else
echo " - $binary_error !!!"
exit 1
fi
echo "Installing legacy Ruby version ..."
@@ -250,6 +261,7 @@ append_line() {
if [ -n "$line" ]; then
echo " - Already exists: line #$line"
else
echo >> "$2"
echo "$1" >> "$2"
echo " + Added"
fi

View File

@@ -21,7 +21,7 @@ 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.
..
.TH fzf 1 "Aug 2015" "fzf 0.10.2" "fzf - a command-line fuzzy finder"
.TH fzf 1 "Aug 2015" "fzf 0.10.4" "fzf - a command-line fuzzy finder"
.SH NAME
fzf - a command-line fuzzy finder

View File

@@ -80,7 +80,7 @@ function! s:shellesc(arg)
endfunction
function! s:escape(path)
return escape(a:path, ' %#\')
return escape(a:path, ' %#''"\')
endfunction
" Upgrade legacy options
@@ -164,7 +164,13 @@ function! s:fzf_tmux(dict)
let size = ''
for o in ['up', 'down', 'left', 'right']
if s:present(a:dict, o)
let size = '-'.o[0].(a:dict[o] == 1 ? '' : a:dict[o])
let spec = a:dict[o]
if (o == 'up' || o == 'down') && spec[0] == '~'
let size = '-'.o[0].s:calc_size(&lines, spec[1:], a:dict)
else
" Legacy boolean option
let size = '-'.o[0].(spec == 1 ? '' : spec)
endif
break
endif
endfor
@@ -218,7 +224,7 @@ function! s:execute(dict, command, temps)
else
let command = a:command
endif
execute 'silent !'.command
execute 'silent !'.escape(command, '%#')
redraw!
if v:shell_error
" Do not print error message on exit status 1
@@ -244,12 +250,21 @@ function! s:execute_tmux(dict, command, temps)
return s:callback(a:dict, a:temps)
endfunction
function! s:calc_size(max, val)
function! s:calc_size(max, val, dict)
if a:val =~ '%$'
return a:max * str2nr(a:val[:-2]) / 100
let size = a:max * str2nr(a:val[:-2]) / 100
else
return min([a:max, a:val])
let size = min([a:max, str2nr(a:val)])
endif
let srcsz = -1
if type(get(a:dict, 'source', 0)) == type([])
let srcsz = len(a:dict.source)
endif
let opts = get(a:dict, 'options', '').$FZF_DEFAULT_OPTS
let margin = stridx(opts, '--inline-info') > stridx(opts, '--no-inline-info') ? 1 : 2
return srcsz >= 0 ? min([srcsz + margin, size]) : size
endfunction
function! s:getpos()
@@ -268,7 +283,11 @@ function! s:split(dict)
let val = get(a:dict, dir, '')
if !empty(val)
let [cmd, resz, max] = triple
let sz = s:calc_size(max, val)
if (dir == 'up' || dir == 'down') && val[0] == '~'
let sz = s:calc_size(max, val[1:], a:dict)
else
let sz = s:calc_size(max, val, {})
endif
execute cmd sz.'new'
execute resz sz
return
@@ -358,7 +377,7 @@ endfunction
let s:default_action = {
\ 'ctrl-m': 'e',
\ 'ctrl-t': 'tabedit',
\ 'ctrl-t': 'tab split',
\ 'ctrl-x': 'split',
\ 'ctrl-v': 'vsplit' }
@@ -384,7 +403,7 @@ function! s:cmd(bang, ...) abort
let args = extend(['--expect='.join(keys(s:action), ',')], a:000)
let opts = {}
if len(args) > 0 && isdirectory(expand(args[-1]))
let opts.dir = remove(args, -1)
let opts.dir = substitute(remove(args, -1), '\\\(["'']\)', '\1', 'g')
endif
if !a:bang
let opts.down = get(g:, 'fzf_height', get(g:, 'fzf_tmux_height', s:default_height))

View File

@@ -2,6 +2,7 @@ FROM base/archlinux:2014.07.03
MAINTAINER Junegunn Choi <junegunn.c@gmail.com>
# apt-get
RUN pacman-key --populate archlinux && pacman-key --refresh-keys
RUN pacman-db-upgrade && pacman -Syu --noconfirm base-devel git
# Install Go 1.4

View File

@@ -3,7 +3,7 @@ MAINTAINER Junegunn Choi <junegunn.c@gmail.com>
# apt-get
RUN apt-get update && apt-get -y upgrade && \
apt-get install -y --force-yes git curl build-essential libncurses-dev
apt-get install -y --force-yes git curl build-essential libncurses-dev libgpm-dev
# Install Go 1.4
RUN cd / && curl \

View File

@@ -5,8 +5,19 @@ endif
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
GOOS := darwin
LDFLAGS :=
ifdef STATIC
$(error Static linking not possible on OS X)
endif
else ifeq ($(UNAME_S),Linux)
GOOS := linux
ifdef STATIC
SUFFIX := -static
LDFLAGS := --ldflags '-extldflags "-static -ltinfo -lgpm"'
else
SUFFIX :=
LDFLAGS :=
endif
endif
ifneq ($(shell uname -m),x86_64)
@@ -16,21 +27,24 @@ endif
SOURCES := $(wildcard *.go */*.go)
BINDIR := ../bin
BINARY32 := fzf-$(GOOS)_386
BINARY64 := fzf-$(GOOS)_amd64
BINARY32 := fzf-$(GOOS)_386$(SUFFIX)
BINARY64 := fzf-$(GOOS)_amd64$(SUFFIX)
VERSION = $(shell fzf/$(BINARY64) --version)
RELEASE32 = fzf-$(VERSION)-$(GOOS)_386
RELEASE64 = fzf-$(VERSION)-$(GOOS)_amd64
RELEASE32 = fzf-$(VERSION)-$(GOOS)_386$(SUFFIX)
RELEASE64 = fzf-$(VERSION)-$(GOOS)_amd64$(SUFFIX)
all: release
release: build
cd fzf && \
cp $(BINARY32) $(RELEASE32) && tar -czf $(RELEASE32).tgz $(RELEASE32) && \
cp $(BINARY64) $(RELEASE64) && tar -czf $(RELEASE64).tgz $(RELEASE64) && \
rm $(RELEASE32) $(RELEASE64)
-cd fzf && cp $(BINARY32) $(RELEASE32) && tar -czf $(RELEASE32).tgz $(RELEASE32)
cd fzf && cp $(BINARY64) $(RELEASE64) && tar -czf $(RELEASE64).tgz $(RELEASE64) && \
rm -f $(RELEASE32) $(RELEASE64)
ifndef STATIC
build: test fzf/$(BINARY32) fzf/$(BINARY64)
else
build: test fzf/$(BINARY64)
endif
test:
go get
@@ -42,13 +56,13 @@ uninstall:
rm -f $(BINDIR)/fzf $(BINDIR)/$(BINARY64)
clean:
cd fzf && rm -f $(BINARY32) $(BINARY64) $(RELEASE32).tgz $(RELEASE64).tgz
cd fzf && rm -f fzf-*
fzf/$(BINARY32): $(SOURCES)
cd fzf && GOARCH=386 CGO_ENABLED=1 go build -o $(BINARY32)
fzf/$(BINARY64): $(SOURCES)
cd fzf && go build -o $(BINARY64)
cd fzf && go build $(LDFLAGS) -o $(BINARY64)
$(BINDIR)/fzf: fzf/$(BINARY64) | $(BINDIR)
cp -f fzf/$(BINARY64) $(BINDIR)
@@ -57,18 +71,27 @@ $(BINDIR)/fzf: fzf/$(BINARY64) | $(BINDIR)
$(BINDIR):
mkdir -p $@
# Linux distribution to build fzf on
DISTRO := arch
docker-arch:
docker build -t junegunn/arch-sandbox - < Dockerfile.arch
docker:
docker build -t junegunn/$(DISTRO)-sandbox - < Dockerfile.$(DISTRO)
docker-ubuntu:
docker build -t junegunn/ubuntu-sandbox - < Dockerfile.ubuntu
linux: docker
docker run -i -t -v $(GOPATH):/go junegunn/$(DISTRO)-sandbox \
/bin/bash -ci 'cd /go/src/github.com/junegunn/fzf/src; make'
$(DISTRO): docker
docker run -i -t -v $(GOPATH):/go junegunn/$(DISTRO)-sandbox \
arch: docker-arch
docker run -i -t -v $(GOPATH):/go junegunn/$@-sandbox \
sh -c 'cd /go/src/github.com/junegunn/fzf/src; /bin/bash'
.PHONY: all build release test install uninstall clean docker linux $(DISTRO)
ubuntu: docker-ubuntu
docker run -i -t -v $(GOPATH):/go junegunn/$@-sandbox \
sh -c 'cd /go/src/github.com/junegunn/fzf/src; /bin/bash'
linux: docker-arch
docker run -i -t -v $(GOPATH):/go junegunn/arch-sandbox \
/bin/bash -ci 'cd /go/src/github.com/junegunn/fzf/src; make'
linux-static: docker-ubuntu
docker run -i -t -v $(GOPATH):/go junegunn/ubuntu-sandbox \
/bin/bash -ci 'cd /go/src/github.com/junegunn/fzf/src; make STATIC=1'
.PHONY: all build release test install uninstall clean docker \
linux linux-static arch ubuntu docker-arch docker-ubuntu

View File

@@ -8,7 +8,7 @@ import (
const (
// Current version
version = "0.10.2"
version = "0.10.4"
// Core
coordinatorDelayMax time.Duration = 100 * time.Millisecond

View File

@@ -174,7 +174,7 @@ func Run(opts *Options) {
chunks: snapshot,
pattern: pattern})
for i := 0; i < merger.Length(); i++ {
fmt.Println(merger.Get(i).AsString())
fmt.Println(merger.Get(i).AsString(opts.Ansi))
}
}
os.Exit(0)
@@ -250,7 +250,7 @@ func Run(opts *Options) {
fmt.Println()
}
for i := 0; i < count; i++ {
fmt.Println(val.Get(i).AsString())
fmt.Println(val.Get(i).AsString(opts.Ansi))
}
os.Exit(0)
}

View File

@@ -94,17 +94,21 @@ func (item *Item) Rank(cache bool) Rank {
}
// AsString returns the original string
func (item *Item) AsString() string {
return *item.StringPtr()
func (item *Item) AsString(stripAnsi bool) string {
return *item.StringPtr(stripAnsi)
}
// StringPtr returns the pointer to the original string
func (item *Item) StringPtr() *string {
runes := item.text
func (item *Item) StringPtr(stripAnsi bool) *string {
if item.origText != nil {
runes = *item.origText
if stripAnsi {
trimmed, _, _ := extractColor(string(*item.origText), nil)
return &trimmed
}
orig := string(*item.origText)
return &orig
}
str := string(runes)
str := string(item.text)
return &str
}

View File

@@ -1,7 +1,6 @@
package fzf
import (
"fmt"
"io/ioutil"
"os"
"regexp"
@@ -104,7 +103,7 @@ type Options struct {
Case Case
Nth []Range
WithNth []Range
Delimiter *regexp.Regexp
Delimiter Delimiter
Sort int
Tac bool
Tiebreak tiebreak
@@ -149,7 +148,7 @@ func defaultOptions() *Options {
Case: CaseSmart,
Nth: make([]Range, 0),
WithNth: make([]Range, 0),
Delimiter: nil,
Delimiter: Delimiter{},
Sort: 1000,
Tac: false,
Tiebreak: byLength,
@@ -268,17 +267,23 @@ func splitNth(str string) []Range {
return ranges
}
func delimiterRegexp(str string) *regexp.Regexp {
rx, e := regexp.Compile(str)
if e != nil {
str = regexp.QuoteMeta(str)
func delimiterRegexp(str string) Delimiter {
// Special handling of \t
str = strings.Replace(str, "\\t", "\t", -1)
// 1. Pattern does not contain any special character
if regexp.QuoteMeta(str) == str {
return Delimiter{str: &str}
}
rx, e = regexp.Compile(fmt.Sprintf("(?:.*?%s)|(?:.+?$)", str))
rx, e := regexp.Compile(str)
// 2. Pattern is not a valid regular expression
if e != nil {
errorExit("invalid regular expression: " + e.Error())
return Delimiter{str: &str}
}
return rx
// 3. Pattern as regular expression. Slow.
return Delimiter{regex: rx}
}
func isAlphabet(char uint8) bool {

View File

@@ -8,11 +8,59 @@ import (
)
func TestDelimiterRegex(t *testing.T) {
rx := delimiterRegexp("*")
tokens := rx.FindAllString("-*--*---**---", -1)
if tokens[0] != "-*" || tokens[1] != "--*" || tokens[2] != "---*" ||
tokens[3] != "*" || tokens[4] != "---" {
t.Errorf("%s %s %d", rx, tokens, len(tokens))
// Valid regex
delim := delimiterRegexp(".")
if delim.regex == nil || delim.str != nil {
t.Error(delim)
}
// Broken regex -> string
delim = delimiterRegexp("[0-9")
if delim.regex != nil || *delim.str != "[0-9" {
t.Error(delim)
}
// Valid regex
delim = delimiterRegexp("[0-9]")
if delim.regex.String() != "[0-9]" || delim.str != nil {
t.Error(delim)
}
// Tab character
delim = delimiterRegexp("\t")
if delim.regex != nil || *delim.str != "\t" {
t.Error(delim)
}
// Tab expression
delim = delimiterRegexp("\\t")
if delim.regex != nil || *delim.str != "\t" {
t.Error(delim)
}
// Tabs -> regex
delim = delimiterRegexp("\t+")
if delim.regex == nil || delim.str != nil {
t.Error(delim)
}
}
func TestDelimiterRegexString(t *testing.T) {
delim := delimiterRegexp("*")
tokens := Tokenize([]rune("-*--*---**---"), delim)
if delim.regex != nil ||
string(tokens[0].text) != "-*" ||
string(tokens[1].text) != "--*" ||
string(tokens[2].text) != "---*" ||
string(tokens[3].text) != "*" ||
string(tokens[4].text) != "---" {
t.Errorf("%s %s %d", delim, tokens, len(tokens))
}
}
func TestDelimiterRegexRegex(t *testing.T) {
delim := delimiterRegexp("--\\*")
tokens := Tokenize([]rune("-*--*---**---"), delim)
if delim.str != nil ||
string(tokens[0].text) != "-*--*" ||
string(tokens[1].text) != "---*" ||
string(tokens[2].text) != "*---" {
t.Errorf("%s %d", tokens, len(tokens))
}
}

View File

@@ -42,7 +42,7 @@ type Pattern struct {
text []rune
terms []term
hasInvTerm bool
delimiter *regexp.Regexp
delimiter Delimiter
nth []Range
procFun map[termType]func(bool, []rune, []rune) (int, int)
}
@@ -71,7 +71,7 @@ func clearChunkCache() {
// BuildPattern builds Pattern object from the given arguments
func BuildPattern(mode Mode, caseMode Case,
nth []Range, delimiter *regexp.Regexp, runes []rune) *Pattern {
nth []Range, delimiter Delimiter, runes []rune) *Pattern {
var asString string
switch mode {

View File

@@ -59,7 +59,7 @@ func TestExact(t *testing.T) {
defer clearPatternCache()
clearPatternCache()
pattern := BuildPattern(ModeExtended, CaseSmart,
[]Range{}, nil, []rune("'abc"))
[]Range{}, Delimiter{}, []rune("'abc"))
sidx, eidx := algo.ExactMatchNaive(
pattern.caseSensitive, []rune("aabbcc abc"), pattern.terms[0].text)
if sidx != 7 || eidx != 10 {
@@ -70,7 +70,7 @@ func TestExact(t *testing.T) {
func TestEqual(t *testing.T) {
defer clearPatternCache()
clearPatternCache()
pattern := BuildPattern(ModeExtended, CaseSmart, []Range{}, nil, []rune("^AbC$"))
pattern := BuildPattern(ModeExtended, CaseSmart, []Range{}, Delimiter{}, []rune("^AbC$"))
match := func(str string, sidxExpected int, eidxExpected int) {
sidx, eidx := algo.EqualMatch(
@@ -86,17 +86,17 @@ func TestEqual(t *testing.T) {
func TestCaseSensitivity(t *testing.T) {
defer clearPatternCache()
clearPatternCache()
pat1 := BuildPattern(ModeFuzzy, CaseSmart, []Range{}, nil, []rune("abc"))
pat1 := BuildPattern(ModeFuzzy, CaseSmart, []Range{}, Delimiter{}, []rune("abc"))
clearPatternCache()
pat2 := BuildPattern(ModeFuzzy, CaseSmart, []Range{}, nil, []rune("Abc"))
pat2 := BuildPattern(ModeFuzzy, CaseSmart, []Range{}, Delimiter{}, []rune("Abc"))
clearPatternCache()
pat3 := BuildPattern(ModeFuzzy, CaseIgnore, []Range{}, nil, []rune("abc"))
pat3 := BuildPattern(ModeFuzzy, CaseIgnore, []Range{}, Delimiter{}, []rune("abc"))
clearPatternCache()
pat4 := BuildPattern(ModeFuzzy, CaseIgnore, []Range{}, nil, []rune("Abc"))
pat4 := BuildPattern(ModeFuzzy, CaseIgnore, []Range{}, Delimiter{}, []rune("Abc"))
clearPatternCache()
pat5 := BuildPattern(ModeFuzzy, CaseRespect, []Range{}, nil, []rune("abc"))
pat5 := BuildPattern(ModeFuzzy, CaseRespect, []Range{}, Delimiter{}, []rune("abc"))
clearPatternCache()
pat6 := BuildPattern(ModeFuzzy, CaseRespect, []Range{}, nil, []rune("Abc"))
pat6 := BuildPattern(ModeFuzzy, CaseRespect, []Range{}, Delimiter{}, []rune("Abc"))
if string(pat1.text) != "abc" || pat1.caseSensitive != false ||
string(pat2.text) != "Abc" || pat2.caseSensitive != true ||
@@ -109,8 +109,8 @@ func TestCaseSensitivity(t *testing.T) {
}
func TestOrigTextAndTransformed(t *testing.T) {
pattern := BuildPattern(ModeExtended, CaseSmart, []Range{}, nil, []rune("jg"))
tokens := Tokenize([]rune("junegunn"), nil)
pattern := BuildPattern(ModeExtended, CaseSmart, []Range{}, Delimiter{}, []rune("jg"))
tokens := Tokenize([]rune("junegunn"), Delimiter{})
trans := Transform(tokens, []Range{Range{1, 1}})
origRunes := []rune("junegunn.choi")

View File

@@ -42,6 +42,7 @@ type Terminal struct {
history *History
cycle bool
header []string
ansi bool
margin [4]string
marginInt [4]int
count int
@@ -207,6 +208,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
marginInt: [4]int{0, 0, 0, 0},
cycle: opts.Cycle,
header: opts.Header,
ansi: opts.Ansi,
reading: true,
merger: EmptyMerger,
selected: make(map[uint32]selectedItem),
@@ -288,7 +290,7 @@ func (t *Terminal) output() {
if len(t.selected) == 0 {
cnt := t.merger.Length()
if cnt > 0 && cnt > t.cy {
fmt.Println(t.merger.Get(t.cy).AsString())
fmt.Println(t.merger.Get(t.cy).AsString(t.ansi))
}
} else {
sels := make([]selectedItem, 0, len(t.selected))
@@ -805,7 +807,7 @@ func (t *Terminal) Loop() {
}
selectItem := func(item *Item) bool {
if _, found := t.selected[item.index]; !found {
t.selected[item.index] = selectedItem{time.Now(), item.StringPtr()}
t.selected[item.index] = selectedItem{time.Now(), item.StringPtr(t.ansi)}
return true
}
return false
@@ -843,7 +845,7 @@ func (t *Terminal) Loop() {
case actExecute:
if t.cy >= 0 && t.cy < t.merger.Length() {
item := t.merger.Get(t.cy)
executeCommand(t.execmap[mapkey], item.AsString())
executeCommand(t.execmap[mapkey], item.AsString(t.ansi))
}
case actInvalid:
t.mutex.Unlock()

View File

@@ -22,6 +22,12 @@ type Token struct {
prefixLength int
}
// Delimiter for tokenizing the input
type Delimiter struct {
regex *regexp.Regexp
str *string
}
func newRange(begin int, end int) Range {
if begin == 1 {
begin = rangeEllipsis
@@ -68,15 +74,15 @@ func ParseRange(str *string) (Range, bool) {
return newRange(n, n), true
}
func withPrefixLengths(tokens []string, begin int) []Token {
func withPrefixLengths(tokens [][]rune, begin int) []Token {
ret := make([]Token, len(tokens))
prefixLength := begin
for idx, token := range tokens {
// Need to define a new local variable instead of the reused token to take
// the pointer to it
ret[idx] = Token{text: []rune(token), prefixLength: prefixLength}
prefixLength += len([]rune(token))
ret[idx] = Token{text: token, prefixLength: prefixLength}
prefixLength += len(token)
}
return ret
}
@@ -87,9 +93,9 @@ const (
awkWhite
)
func awkTokenizer(input []rune) ([]string, int) {
func awkTokenizer(input []rune) ([][]rune, int) {
// 9, 32
ret := []string{}
ret := [][]rune{}
str := []rune{}
prefixLength := 0
state := awkNil
@@ -112,27 +118,49 @@ func awkTokenizer(input []rune) ([]string, int) {
if white {
str = append(str, r)
} else {
ret = append(ret, string(str))
ret = append(ret, str)
state = awkBlack
str = []rune{r}
}
}
}
if len(str) > 0 {
ret = append(ret, string(str))
ret = append(ret, str)
}
return ret, prefixLength
}
// Tokenize tokenizes the given string with the delimiter
func Tokenize(runes []rune, delimiter *regexp.Regexp) []Token {
if delimiter == nil {
func Tokenize(runes []rune, delimiter Delimiter) []Token {
if delimiter.str == nil && delimiter.regex == nil {
// AWK-style (\S+\s*)
tokens, prefixLength := awkTokenizer(runes)
return withPrefixLengths(tokens, prefixLength)
}
tokens := delimiter.FindAllString(string(runes), -1)
return withPrefixLengths(tokens, 0)
var tokens []string
if delimiter.str != nil {
tokens = strings.Split(string(runes), *delimiter.str)
for i := 0; i < len(tokens)-1; i++ {
tokens[i] = tokens[i] + *delimiter.str
}
} else if delimiter.regex != nil {
str := string(runes)
for len(str) > 0 {
loc := delimiter.regex.FindStringIndex(str)
if loc == nil {
loc = []int{0, len(str)}
}
last := util.Max(loc[1], 1)
tokens = append(tokens, str[:last])
str = str[last:]
}
}
asRunes := make([][]rune, len(tokens))
for i, token := range tokens {
asRunes[i] = []rune(token)
}
return withPrefixLengths(asRunes, 0)
}
func joinTokens(tokens []Token) []rune {

View File

@@ -43,7 +43,7 @@ func TestParseRange(t *testing.T) {
func TestTokenize(t *testing.T) {
// AWK-style
input := " abc: def: ghi "
tokens := Tokenize([]rune(input), nil)
tokens := Tokenize([]rune(input), Delimiter{})
if string(tokens[0].text) != "abc: " || tokens[0].prefixLength != 2 {
t.Errorf("%s", tokens)
}
@@ -53,12 +53,21 @@ func TestTokenize(t *testing.T) {
if string(tokens[0].text) != " abc:" || tokens[0].prefixLength != 0 {
t.Errorf("%s", tokens)
}
// With delimiter regex
tokens = Tokenize([]rune(input), delimiterRegexp("\\s+"))
if string(tokens[0].text) != " " || tokens[0].prefixLength != 0 ||
string(tokens[1].text) != "abc: " || tokens[1].prefixLength != 2 ||
string(tokens[2].text) != "def: " || tokens[2].prefixLength != 8 ||
string(tokens[3].text) != "ghi " || tokens[3].prefixLength != 14 {
t.Errorf("%s", tokens)
}
}
func TestTransform(t *testing.T) {
input := " abc: def: ghi: jkl"
{
tokens := Tokenize([]rune(input), nil)
tokens := Tokenize([]rune(input), Delimiter{})
{
ranges := splitNth("1,2,3")
tx := Transform(tokens, ranges)

View File

@@ -774,6 +774,22 @@ class TestGoFZF < TestBase
tmux.until { |lines| lines.any? { |l| l.include? 'Invalid $TERM: xxx' } }
end
def test_with_nth
writelines tempname, ['hello world ', 'byebye']
assert_equal 'hello world ', `cat #{tempname} | #{FZF} -f"^he hehe" -x -n 2.. --with-nth 2,1,1`.chomp
end
def test_with_nth_ansi
writelines tempname, ["\x1b[33mhello \x1b[34;1mworld\x1b[m ", 'byebye']
assert_equal 'hello world ', `cat #{tempname} | #{FZF} -f"^he hehe" -x -n 2.. --with-nth 2,1,1 --ansi`.chomp
end
def test_with_nth_no_ansi
src = "\x1b[33mhello \x1b[34;1mworld\x1b[m "
writelines tempname, [src, 'byebye']
assert_equal src, `cat #{tempname} | #{FZF} -fhehe -x -n 2.. --with-nth 2,1,1 --no-ansi`.chomp
end
private
def writelines path, lines
File.unlink path while File.exists? path