mirror of
https://github.com/junegunn/fzf.git
synced 2025-12-07 13:24:27 +08:00
Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18a1aeaa91 | ||
|
|
c9f16b6430 | ||
|
|
bc9d2abdb6 | ||
|
|
28810c178f | ||
|
|
a9e64efe45 | ||
|
|
6b5886c034 | ||
|
|
7727ad43af | ||
|
|
bbe10f4f77 | ||
|
|
5e72709613 | ||
|
|
9e85cba0d0 | ||
|
|
4b59ced08f | ||
|
|
8dbdd55730 | ||
|
|
6725151a99 | ||
|
|
d4f3d5a164 | ||
|
|
7b5ccc45bc | ||
|
|
940214a1a2 | ||
|
|
68bd410159 | ||
|
|
b13fcfd831 | ||
|
|
07ef2b051c | ||
|
|
3fc795340d | ||
|
|
70cfa6af13 | ||
|
|
dbcaec59ae | ||
|
|
faedae708e | ||
|
|
0c66521b23 | ||
|
|
bf92862459 | ||
|
|
1a68698d76 | ||
|
|
842a73357c | ||
|
|
5efdeccdbb | ||
|
|
050777b8c4 | ||
|
|
a4d78e2200 | ||
|
|
b49f22cdf9 | ||
|
|
7e483b0c25 | ||
|
|
3cf9ae04c7 | ||
|
|
bf0cb4bfe2 | ||
|
|
773133c4ce | ||
|
|
ca0b3b6fd7 | ||
|
|
f4731c0514 | ||
|
|
34f16e5b7d | ||
|
|
83e9af6601 | ||
|
|
8bbf9335e1 | ||
|
|
159f30b37f | ||
|
|
2e3dc75425 | ||
|
|
7d3575b362 | ||
|
|
35d407021c | ||
|
|
076f49d447 | ||
|
|
0665fe0413 | ||
|
|
669a6fee40 | ||
|
|
8aab0fc189 | ||
|
|
5d6eb5bfd6 | ||
|
|
cf4711d878 | ||
|
|
21d664d670 | ||
|
|
ab182e276b | ||
|
|
96a3250152 | ||
|
|
f5746002fd | ||
|
|
e1e3339770 | ||
|
|
3a5086796d | ||
|
|
11300913a4 | ||
|
|
e65f14cbed | ||
|
|
6898849e3e |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,7 +1,8 @@
|
|||||||
bin
|
bin/fzf
|
||||||
src/fzf/fzf-*
|
target
|
||||||
gopath
|
|
||||||
pkg
|
pkg
|
||||||
Gemfile.lock
|
Gemfile.lock
|
||||||
.DS_Store
|
.DS_Store
|
||||||
doc/tags
|
doc/tags
|
||||||
|
vendor
|
||||||
|
gopath
|
||||||
|
|||||||
11
.travis.yml
11
.travis.yml
@@ -16,13 +16,6 @@ install:
|
|||||||
- sudo apt-get install -y zsh fish
|
- sudo apt-get install -y zsh fish
|
||||||
|
|
||||||
script: |
|
script: |
|
||||||
export GOPATH=~/go
|
make test install &&
|
||||||
export FZF_BASE=$GOPATH/src/github.com/junegunn/fzf
|
./install --all &&
|
||||||
|
|
||||||
mkdir -p $GOPATH/src/github.com/junegunn
|
|
||||||
ln -s $(pwd) $FZF_BASE
|
|
||||||
|
|
||||||
cd $FZF_BASE/src && make test fzf/fzf-linux_amd64 install &&
|
|
||||||
cd $FZF_BASE/bin && ln -sf fzf-linux_amd64 fzf-$(./fzf --version)-linux_amd64 &&
|
|
||||||
cd $FZF_BASE && yes | ./install && rm -f fzf &&
|
|
||||||
tmux new "ruby test/test_go.rb > out && touch ok" && cat out && [ -e ok ]
|
tmux new "ruby test/test_go.rb > out && touch ok" && cat out && [ -e ok ]
|
||||||
|
|||||||
11
BUILD.md
11
BUILD.md
@@ -13,19 +13,16 @@ Build instructions
|
|||||||
Makefile will set up and use its own `$GOPATH` under the project root.
|
Makefile will set up and use its own `$GOPATH` under the project root.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Source files are located in src directory
|
# Build fzf binary for your platform in target
|
||||||
cd src
|
|
||||||
|
|
||||||
# Build fzf binary for your platform in src/fzf
|
|
||||||
make
|
make
|
||||||
|
|
||||||
# Build fzf binary and copy it to bin directory
|
# Build fzf binary and copy it to bin directory
|
||||||
make install
|
make install
|
||||||
|
|
||||||
# Build 32-bit and 64-bit executables and tarballs
|
# Build 32-bit and 64-bit executables and tarballs in target
|
||||||
make release
|
make release
|
||||||
|
|
||||||
# Make release archives for all supported platforms
|
# Make release archives for all supported platforms in target
|
||||||
make release-all
|
make release-all
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -35,7 +32,7 @@ Alternatively, you can build fzf directly with `go get` command without
|
|||||||
manually cloning the repository.
|
manually cloning the repository.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go get -u github.com/junegunn/fzf/src/fzf
|
go get -u github.com/junegunn/fzf
|
||||||
```
|
```
|
||||||
|
|
||||||
Third-party libraries used
|
Third-party libraries used
|
||||||
|
|||||||
26
CHANGELOG.md
26
CHANGELOG.md
@@ -1,6 +1,32 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.16.9
|
||||||
|
------
|
||||||
|
- Memory and performance optimization
|
||||||
|
- Around 20% performance improvement for general use cases
|
||||||
|
- Up to 5x faster processing of `--ansi`
|
||||||
|
- Up to 50% reduction of memory usage
|
||||||
|
- Bug fixes and usability improvements
|
||||||
|
- Fixed handling of bracketed paste mode
|
||||||
|
- [ERROR] on info line when the default command failed
|
||||||
|
- More efficient rendering of preview window
|
||||||
|
- `--no-clear` updated for repetitive relaunching scenarios
|
||||||
|
|
||||||
|
0.16.8
|
||||||
|
------
|
||||||
|
- New `change` event and `top` action for `--bind`
|
||||||
|
- `fzf --bind change:top`
|
||||||
|
- Move cursor to the top result whenever the query string is changed
|
||||||
|
- `fzf --bind 'ctrl-w:unix-word-rubout+top,ctrl-u:unix-line-discard+top'`
|
||||||
|
- `top` combined with `unix-word-rubout` and `unix-line-discard`
|
||||||
|
- Fixed inconsistent tiebreak scores when `--nth` is used
|
||||||
|
- Proper display of tab characters in `--prompt`
|
||||||
|
- Fixed not to `--cycle` on page-up/page-down to prevent overshoot
|
||||||
|
- Git revision in `--version` output
|
||||||
|
- Basic support for Cygwin environment
|
||||||
|
- Many fixes in Vim plugin on Windows/Cygwin (thanks to @janlazo)
|
||||||
|
|
||||||
0.16.7
|
0.16.7
|
||||||
------
|
------
|
||||||
- Added support for `ctrl-alt-[a-z]` key chords
|
- Added support for `ctrl-alt-[a-z]` key chords
|
||||||
|
|||||||
138
Makefile
Normal file
138
Makefile
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
ifndef GOOS
|
||||||
|
UNAME_S := $(shell uname -s)
|
||||||
|
ifeq ($(UNAME_S),Darwin)
|
||||||
|
GOOS := darwin
|
||||||
|
else ifeq ($(UNAME_S),Linux)
|
||||||
|
GOOS := linux
|
||||||
|
else
|
||||||
|
$(error "$$GOOS is not defined.")
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
|
||||||
|
ROOT_DIR := $(shell dirname $(MAKEFILE))
|
||||||
|
GOPATH := $(ROOT_DIR)/gopath
|
||||||
|
SRC_LINK := $(GOPATH)/src/github.com/junegunn/fzf/src
|
||||||
|
VENDOR_LINK := $(GOPATH)/src/github.com/junegunn/fzf/vendor
|
||||||
|
export GOPATH
|
||||||
|
|
||||||
|
GLIDE_YAML := glide.yaml
|
||||||
|
GLIDE_LOCK := glide.lock
|
||||||
|
SOURCES := $(wildcard *.go src/*.go src/*/*.go) $(SRC_LINK) $(VENDOR_LINK) $(GLIDE_LOCK) $(MAKEFILE)
|
||||||
|
|
||||||
|
REVISION := $(shell git log -n 1 --pretty=format:%h -- $(SOURCES))
|
||||||
|
BUILD_FLAGS := -a -ldflags "-X main.revision=$(REVISION) -w -extldflags=$(LDFLAGS)" -tags "$(TAGS)"
|
||||||
|
|
||||||
|
BINARY32 := fzf-$(GOOS)_386
|
||||||
|
BINARY64 := fzf-$(GOOS)_amd64
|
||||||
|
BINARYARM5 := fzf-$(GOOS)_arm5
|
||||||
|
BINARYARM6 := fzf-$(GOOS)_arm6
|
||||||
|
BINARYARM7 := fzf-$(GOOS)_arm7
|
||||||
|
BINARYARM8 := fzf-$(GOOS)_arm8
|
||||||
|
VERSION := $(shell awk -F= '/version =/ {print $$2}' src/constants.go | tr -d "\" ")
|
||||||
|
RELEASE32 := fzf-$(VERSION)-$(GOOS)_386
|
||||||
|
RELEASE64 := fzf-$(VERSION)-$(GOOS)_amd64
|
||||||
|
RELEASEARM5 := fzf-$(VERSION)-$(GOOS)_arm5
|
||||||
|
RELEASEARM6 := fzf-$(VERSION)-$(GOOS)_arm6
|
||||||
|
RELEASEARM7 := fzf-$(VERSION)-$(GOOS)_arm7
|
||||||
|
RELEASEARM8 := fzf-$(VERSION)-$(GOOS)_arm8
|
||||||
|
|
||||||
|
# https://en.wikipedia.org/wiki/Uname
|
||||||
|
UNAME_M := $(shell uname -m)
|
||||||
|
ifeq ($(UNAME_M),x86_64)
|
||||||
|
BINARY := $(BINARY64)
|
||||||
|
else ifeq ($(UNAME_M),amd64)
|
||||||
|
BINARY := $(BINARY64)
|
||||||
|
else ifeq ($(UNAME_M),i686)
|
||||||
|
BINARY := $(BINARY32)
|
||||||
|
else ifeq ($(UNAME_M),i386)
|
||||||
|
BINARY := $(BINARY32)
|
||||||
|
else ifeq ($(UNAME_M),armv5l)
|
||||||
|
BINARY := $(BINARYARM5)
|
||||||
|
else ifeq ($(UNAME_M),armv6l)
|
||||||
|
BINARY := $(BINARYARM6)
|
||||||
|
else ifeq ($(UNAME_M),armv7l)
|
||||||
|
BINARY := $(BINARYARM7)
|
||||||
|
else
|
||||||
|
$(error "Build on $(UNAME_M) is not supported, yet.")
|
||||||
|
endif
|
||||||
|
|
||||||
|
all: target/$(BINARY)
|
||||||
|
|
||||||
|
target:
|
||||||
|
mkdir -p $@
|
||||||
|
|
||||||
|
ifeq ($(GOOS),windows)
|
||||||
|
release: target/$(BINARY32) target/$(BINARY64)
|
||||||
|
cd target && cp -f $(BINARY32) fzf.exe && zip $(RELEASE32).zip fzf.exe
|
||||||
|
cd target && cp -f $(BINARY64) fzf.exe && zip $(RELEASE64).zip fzf.exe
|
||||||
|
cd target && rm -f fzf.exe
|
||||||
|
else ifeq ($(GOOS),linux)
|
||||||
|
release: target/$(BINARY32) target/$(BINARY64) target/$(BINARYARM5) target/$(BINARYARM6) target/$(BINARYARM7) target/$(BINARYARM8)
|
||||||
|
cd target && cp -f $(BINARY32) fzf && tar -czf $(RELEASE32).tgz fzf
|
||||||
|
cd target && cp -f $(BINARY64) fzf && tar -czf $(RELEASE64).tgz fzf
|
||||||
|
cd target && cp -f $(BINARYARM5) fzf && tar -czf $(RELEASEARM5).tgz fzf
|
||||||
|
cd target && cp -f $(BINARYARM6) fzf && tar -czf $(RELEASEARM6).tgz fzf
|
||||||
|
cd target && cp -f $(BINARYARM7) fzf && tar -czf $(RELEASEARM7).tgz fzf
|
||||||
|
cd target && cp -f $(BINARYARM8) fzf && tar -czf $(RELEASEARM8).tgz fzf
|
||||||
|
cd target && rm -f fzf
|
||||||
|
else
|
||||||
|
release: target/$(BINARY32) target/$(BINARY64)
|
||||||
|
cd target && cp -f $(BINARY32) fzf && tar -czf $(RELEASE32).tgz fzf
|
||||||
|
cd target && cp -f $(BINARY64) fzf && tar -czf $(RELEASE64).tgz fzf
|
||||||
|
cd target && rm -f fzf
|
||||||
|
endif
|
||||||
|
|
||||||
|
release-all: clean test
|
||||||
|
GOOS=darwin make release
|
||||||
|
GOOS=linux make release
|
||||||
|
GOOS=freebsd make release
|
||||||
|
GOOS=openbsd make release
|
||||||
|
GOOS=windows make release
|
||||||
|
|
||||||
|
$(SRC_LINK):
|
||||||
|
mkdir -p $(shell dirname $(SRC_LINK))
|
||||||
|
ln -sf $(ROOT_DIR)/src $(SRC_LINK)
|
||||||
|
|
||||||
|
$(VENDOR_LINK):
|
||||||
|
mkdir -p $(shell dirname $(VENDOR_LINK))
|
||||||
|
ln -sf $(ROOT_DIR)/vendor $(VENDOR_LINK)
|
||||||
|
|
||||||
|
vendor: $(GLIDE_YAML)
|
||||||
|
go get -u github.com/Masterminds/glide && $(GOPATH)/bin/glide install && touch $@
|
||||||
|
|
||||||
|
test: $(SOURCES) vendor
|
||||||
|
SHELL=/bin/sh GOOS= go test -v -tags "$(TAGS)" \
|
||||||
|
github.com/junegunn/fzf/src \
|
||||||
|
github.com/junegunn/fzf/src/algo \
|
||||||
|
github.com/junegunn/fzf/src/tui \
|
||||||
|
github.com/junegunn/fzf/src/util
|
||||||
|
|
||||||
|
install: bin/fzf
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf target
|
||||||
|
|
||||||
|
target/$(BINARY32): $(SOURCES) vendor
|
||||||
|
GOARCH=386 go build $(BUILD_FLAGS) -o $@
|
||||||
|
|
||||||
|
target/$(BINARY64): $(SOURCES) vendor
|
||||||
|
GOARCH=amd64 go build $(BUILD_FLAGS) -o $@
|
||||||
|
|
||||||
|
# https://github.com/golang/go/wiki/GoArm
|
||||||
|
target/$(BINARYARM5): $(SOURCES) vendor
|
||||||
|
GOARCH=arm GOARM=5 go build $(BUILD_FLAGS) -o $@
|
||||||
|
|
||||||
|
target/$(BINARYARM6): $(SOURCES) vendor
|
||||||
|
GOARCH=arm GOARM=6 go build $(BUILD_FLAGS) -o $@
|
||||||
|
|
||||||
|
target/$(BINARYARM7): $(SOURCES) vendor
|
||||||
|
GOARCH=arm GOARM=7 go build $(BUILD_FLAGS) -o $@
|
||||||
|
|
||||||
|
target/$(BINARYARM8): $(SOURCES) vendor
|
||||||
|
GOARCH=arm64 go build $(BUILD_FLAGS) -o $@
|
||||||
|
|
||||||
|
bin/fzf: target/$(BINARY) | bin
|
||||||
|
cp -f target/$(BINARY) bin/fzf
|
||||||
|
|
||||||
|
.PHONY: all release release-all test install clean
|
||||||
45
README.md
45
README.md
@@ -15,6 +15,39 @@ Pros
|
|||||||
- Batteries included
|
- Batteries included
|
||||||
- Vim/Neovim plugin, key bindings and fuzzy auto-completion
|
- Vim/Neovim plugin, key bindings and fuzzy auto-completion
|
||||||
|
|
||||||
|
Table of Contents
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
* [Installation](#installation)
|
||||||
|
* [Using git](#using-git)
|
||||||
|
* [Using Homebrew](#using-homebrew)
|
||||||
|
* [As Vim plugin](#as-vim-plugin)
|
||||||
|
* [Windows](#windows)
|
||||||
|
* [Upgrading fzf](#upgrading-fzf)
|
||||||
|
* [Building fzf](#building-fzf)
|
||||||
|
* [Usage](#usage)
|
||||||
|
* [Using the finder](#using-the-finder)
|
||||||
|
* [Layout](#layout)
|
||||||
|
* [Search syntax](#search-syntax)
|
||||||
|
* [Environment variables](#environment-variables)
|
||||||
|
* [Options](#options)
|
||||||
|
* [Examples](#examples)
|
||||||
|
* [fzf-tmux script](#fzf-tmux-script)
|
||||||
|
* [Key bindings for command line](#key-bindings-for-command-line)
|
||||||
|
* [Fuzzy completion for bash and zsh](#fuzzy-completion-for-bash-and-zsh)
|
||||||
|
* [Files and directories](#files-and-directories)
|
||||||
|
* [Process IDs](#process-ids)
|
||||||
|
* [Host names](#host-names)
|
||||||
|
* [Environment variables / Aliases](#environment-variables--aliases)
|
||||||
|
* [Settings](#settings)
|
||||||
|
* [Supported commands](#supported-commands)
|
||||||
|
* [Vim plugin](#vim-plugin)
|
||||||
|
* [Tips](#tips)
|
||||||
|
* [Respecting .gitignore, <code>.hgignore</code>, and <code>svn:ignore</code>](#respecting-gitignore-hgignore-and-svnignore)
|
||||||
|
* [git ls-tree for fast traversal](#git-ls-tree-for-fast-traversal)
|
||||||
|
* [Fish shell](#fish-shell)
|
||||||
|
* [<a href="LICENSE">License</a>](#license)
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
|
||||||
@@ -341,8 +374,8 @@ On bash, fuzzy completion is enabled only for a predefined set of commands
|
|||||||
commands as well like follows.
|
commands as well like follows.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# There are also _fzf_path_completion and _fzf_dir_completion
|
complete -F _fzf_path_completion -o default -o bashdefault ag
|
||||||
complete -F _fzf_file_completion -o default -o bashdefault doge
|
complete -F _fzf_dir_completion -o default -o bashdefault tree
|
||||||
```
|
```
|
||||||
|
|
||||||
Vim plugin
|
Vim plugin
|
||||||
@@ -394,9 +427,9 @@ export FZF_DEFAULT_COMMAND='
|
|||||||
#### Fish shell
|
#### Fish shell
|
||||||
|
|
||||||
It's [a known bug of fish](https://github.com/fish-shell/fish-shell/issues/1362)
|
It's [a known bug of fish](https://github.com/fish-shell/fish-shell/issues/1362)
|
||||||
that it doesn't allow reading from STDIN in command substitution, which means
|
(will be fixed in 2.6.0) that it doesn't allow reading from STDIN in command
|
||||||
simple `vim (fzf)` won't work as expected. The workaround is to use the `read`
|
substitution, which means simple `vim (fzf)` won't work as expected. The
|
||||||
fish command:
|
workaround is to use the `read` fish command:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
fzf | read -l result; and vim $result
|
fzf | read -l result; and vim $result
|
||||||
@@ -424,7 +457,7 @@ make use of this feature. `$dir` defaults to `.` when the last token is not a
|
|||||||
valid directory. Example:
|
valid directory. Example:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
set -l FZF_CTRL_T_COMMAND "command find -L \$dir -type f 2> /dev/null | sed '1d; s#^\./##'"
|
set -g FZF_CTRL_T_COMMAND "command find -L \$dir -type f 2> /dev/null | sed '1d; s#^\./##'"
|
||||||
```
|
```
|
||||||
|
|
||||||
[License](LICENSE)
|
[License](LICENSE)
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ fifo1="${TMPDIR:-/tmp}/fzf-fifo1-$id"
|
|||||||
fifo2="${TMPDIR:-/tmp}/fzf-fifo2-$id"
|
fifo2="${TMPDIR:-/tmp}/fzf-fifo2-$id"
|
||||||
fifo3="${TMPDIR:-/tmp}/fzf-fifo3-$id"
|
fifo3="${TMPDIR:-/tmp}/fzf-fifo3-$id"
|
||||||
cleanup() {
|
cleanup() {
|
||||||
rm -f $argsf $fifo1 $fifo2 $fifo3
|
\rm -f $argsf $fifo1 $fifo2 $fifo3
|
||||||
|
|
||||||
# Remove temp window if we were zoomed
|
# Remove temp window if we were zoomed
|
||||||
if [[ -n "$zoomed" ]]; then
|
if [[ -n "$zoomed" ]]; then
|
||||||
@@ -150,6 +150,7 @@ cleanup() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
trap 'cleanup 1' SIGUSR1
|
trap 'cleanup 1' SIGUSR1
|
||||||
|
trap 'cleanup' EXIT
|
||||||
|
|
||||||
envs="env TERM=$TERM "
|
envs="env TERM=$TERM "
|
||||||
[[ -n "$FZF_DEFAULT_OPTS" ]] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
|
[[ -n "$FZF_DEFAULT_OPTS" ]] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
|
||||||
@@ -169,7 +170,7 @@ for arg in "${args[@]}"; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
pppid=$$
|
pppid=$$
|
||||||
trap_set="trap 'kill -SIGUSR1 $pppid' EXIT SIGINT SIGTERM"
|
trap_set="trap 'kill -SIGUSR1 -$pppid' EXIT SIGINT SIGTERM"
|
||||||
trap_unset="trap - EXIT SIGINT SIGTERM"
|
trap_unset="trap - EXIT SIGINT SIGTERM"
|
||||||
|
|
||||||
if [[ -n "$term" ]] || [[ -t 0 ]]; then
|
if [[ -n "$term" ]] || [[ -t 0 ]]; then
|
||||||
|
|||||||
38
glide.lock
generated
Normal file
38
glide.lock
generated
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
hash: d68dd0bd779ac4ffca1e0c49ca38d85f90d5d68fa8e2d5d7db70a8ce8c662ec1
|
||||||
|
updated: 2017-06-01T15:48:41.653745249-07:00
|
||||||
|
imports:
|
||||||
|
- name: github.com/gdamore/encoding
|
||||||
|
version: b23993cbb6353f0e6aa98d0ee318a34728f628b9
|
||||||
|
- name: github.com/gdamore/tcell
|
||||||
|
version: 44772c121bb7838819d3ba4a7e84c0c2d617328e
|
||||||
|
subpackages:
|
||||||
|
- encoding
|
||||||
|
- name: github.com/lucasb-eyer/go-colorful
|
||||||
|
version: c900de9dbbc73129068f5af6a823068fc5f2308c
|
||||||
|
- name: github.com/mattn/go-isatty
|
||||||
|
version: 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8
|
||||||
|
- name: github.com/mattn/go-runewidth
|
||||||
|
version: 14207d285c6c197daabb5c9793d63e7af9ab2d50
|
||||||
|
- name: github.com/mattn/go-shellwords
|
||||||
|
version: 02e3cf038dcea8290e44424da473dd12be796a8a
|
||||||
|
- name: golang.org/x/crypto
|
||||||
|
version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e
|
||||||
|
subpackages:
|
||||||
|
- ssh/terminal
|
||||||
|
- name: golang.org/x/sys
|
||||||
|
version: b90f89a1e7a9c1f6b918820b3daa7f08488c8594
|
||||||
|
subpackages:
|
||||||
|
- unix
|
||||||
|
- name: golang.org/x/text
|
||||||
|
version: 4ee4af566555f5fbe026368b75596286a312663a
|
||||||
|
subpackages:
|
||||||
|
- encoding
|
||||||
|
- encoding/charmap
|
||||||
|
- encoding/internal
|
||||||
|
- encoding/internal/identifier
|
||||||
|
- encoding/japanese
|
||||||
|
- encoding/korean
|
||||||
|
- encoding/simplifiedchinese
|
||||||
|
- encoding/traditionalchinese
|
||||||
|
- transform
|
||||||
|
testImports: []
|
||||||
16
glide.yaml
Normal file
16
glide.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package: github.com/junegunn/fzf
|
||||||
|
import:
|
||||||
|
- package: github.com/mattn/go-isatty
|
||||||
|
version: 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8
|
||||||
|
- package: github.com/mattn/go-runewidth
|
||||||
|
version: 14207d285c6c197daabb5c9793d63e7af9ab2d50
|
||||||
|
- package: github.com/mattn/go-shellwords
|
||||||
|
version: 02e3cf038dcea8290e44424da473dd12be796a8a
|
||||||
|
- package: github.com/gdamore/tcell
|
||||||
|
version: 44772c121bb7838819d3ba4a7e84c0c2d617328e
|
||||||
|
subpackages:
|
||||||
|
- encoding
|
||||||
|
- package: golang.org/x/crypto
|
||||||
|
version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e
|
||||||
|
subpackages:
|
||||||
|
- ssh/terminal
|
||||||
54
install
54
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.16.7
|
version=0.16.9
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
@@ -72,7 +72,7 @@ ask() {
|
|||||||
check_binary() {
|
check_binary() {
|
||||||
echo -n " - Checking fzf executable ... "
|
echo -n " - Checking fzf executable ... "
|
||||||
local output
|
local output
|
||||||
output=$("$fzf_base"/bin/fzf --version 2>&1)
|
output=$("$fzf_base"/bin/fzf --version 2>&1 | awk '{print $1}')
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "Error: $output"
|
echo "Error: $output"
|
||||||
binary_error="Invalid binary"
|
binary_error="Invalid binary"
|
||||||
@@ -99,11 +99,23 @@ link_fzf_in_path() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try_curl() {
|
try_curl() {
|
||||||
command -v curl > /dev/null && curl -fL $1 | tar -xzf -
|
command -v curl > /dev/null &&
|
||||||
|
if [[ $1 =~ tgz$ ]]; then
|
||||||
|
curl -fL $1 | tar -xzf -
|
||||||
|
else
|
||||||
|
local temp=${TMPDIR:-/tmp}/fzf.zip
|
||||||
|
curl -fLo "$temp" $1 && unzip -o "$temp" && rm -f "$temp"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
try_wget() {
|
try_wget() {
|
||||||
command -v wget > /dev/null && wget -O - $1 | tar -xzf -
|
command -v wget > /dev/null &&
|
||||||
|
if [[ $1 =~ tgz$ ]]; then
|
||||||
|
wget -O - $1 | tar -xzf -
|
||||||
|
else
|
||||||
|
local temp=${TMPDIR:-/tmp}/fzf.zip
|
||||||
|
wget -O "$temp" $1 && unzip -o "$temp" && rm -f "$temp"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
download() {
|
download() {
|
||||||
@@ -123,8 +135,8 @@ download() {
|
|||||||
|
|
||||||
local url
|
local url
|
||||||
[[ "$version" =~ alpha ]] &&
|
[[ "$version" =~ alpha ]] &&
|
||||||
url=https://github.com/junegunn/fzf-bin/releases/download/alpha/${1}.tgz ||
|
url=https://github.com/junegunn/fzf-bin/releases/download/alpha/${1} ||
|
||||||
url=https://github.com/junegunn/fzf-bin/releases/download/$version/${1}.tgz
|
url=https://github.com/junegunn/fzf-bin/releases/download/$version/${1}
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
if ! (try_curl $url || try_wget $url); then
|
if ! (try_curl $url || try_wget $url); then
|
||||||
set +o pipefail
|
set +o pipefail
|
||||||
@@ -146,18 +158,19 @@ archi=$(uname -sm)
|
|||||||
binary_available=1
|
binary_available=1
|
||||||
binary_error=""
|
binary_error=""
|
||||||
case "$archi" in
|
case "$archi" in
|
||||||
Darwin\ *64) download fzf-$version-darwin_${binary_arch:-amd64} ;;
|
Darwin\ *64) download fzf-$version-darwin_${binary_arch:-amd64}.tgz ;;
|
||||||
Darwin\ *86) download fzf-$version-darwin_${binary_arch:-386} ;;
|
Darwin\ *86) download fzf-$version-darwin_${binary_arch:-386}.tgz ;;
|
||||||
Linux\ *64) download fzf-$version-linux_${binary_arch:-amd64} ;;
|
Linux\ *64) download fzf-$version-linux_${binary_arch:-amd64}.tgz ;;
|
||||||
Linux\ *86) download fzf-$version-linux_${binary_arch:-386} ;;
|
Linux\ *86) download fzf-$version-linux_${binary_arch:-386}.tgz ;;
|
||||||
Linux\ armv5*) download fzf-$version-linux_${binary_arch:-arm5} ;;
|
Linux\ armv5*) download fzf-$version-linux_${binary_arch:-arm5}.tgz ;;
|
||||||
Linux\ armv6*) download fzf-$version-linux_${binary_arch:-arm6} ;;
|
Linux\ armv6*) download fzf-$version-linux_${binary_arch:-arm6}.tgz ;;
|
||||||
Linux\ armv7*) download fzf-$version-linux_${binary_arch:-arm7} ;;
|
Linux\ armv7*) download fzf-$version-linux_${binary_arch:-arm7}.tgz ;;
|
||||||
Linux\ armv8*) download fzf-$version-linux_${binary_arch:-arm8} ;;
|
Linux\ armv8*) download fzf-$version-linux_${binary_arch:-arm8}.tgz ;;
|
||||||
FreeBSD\ *64) download fzf-$version-freebsd_${binary_arch:-amd64} ;;
|
FreeBSD\ *64) download fzf-$version-freebsd_${binary_arch:-amd64}.tgz ;;
|
||||||
FreeBSD\ *86) download fzf-$version-freebsd_${binary_arch:-386} ;;
|
FreeBSD\ *86) download fzf-$version-freebsd_${binary_arch:-386}.tgz ;;
|
||||||
OpenBSD\ *64) download fzf-$version-openbsd_${binary_arch:-amd64} ;;
|
OpenBSD\ *64) download fzf-$version-openbsd_${binary_arch:-amd64}.tgz ;;
|
||||||
OpenBSD\ *86) download fzf-$version-openbsd_${binary_arch:-386} ;;
|
OpenBSD\ *86) download fzf-$version-openbsd_${binary_arch:-386}.tgz ;;
|
||||||
|
CYGWIN*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
|
||||||
*) binary_available=0 binary_error=1 ;;
|
*) binary_available=0 binary_error=1 ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
@@ -169,12 +182,12 @@ if [ -n "$binary_error" ]; then
|
|||||||
echo " - $binary_error !!!"
|
echo " - $binary_error !!!"
|
||||||
fi
|
fi
|
||||||
if command -v go > /dev/null; then
|
if command -v go > /dev/null; then
|
||||||
echo -n "Building binary (go get -u github.com/junegunn/fzf/src/fzf) ... "
|
echo -n "Building binary (go get -u github.com/junegunn/fzf) ... "
|
||||||
if [ -z "${GOPATH-}" ]; then
|
if [ -z "${GOPATH-}" ]; then
|
||||||
export GOPATH="${TMPDIR:-/tmp}/fzf-gopath"
|
export GOPATH="${TMPDIR:-/tmp}/fzf-gopath"
|
||||||
mkdir -p "$GOPATH"
|
mkdir -p "$GOPATH"
|
||||||
fi
|
fi
|
||||||
if go get -u github.com/junegunn/fzf/src/fzf; then
|
if go get -u github.com/junegunn/fzf; then
|
||||||
echo "OK"
|
echo "OK"
|
||||||
cp "$GOPATH/bin/fzf" "$fzf_base/bin/"
|
cp "$GOPATH/bin/fzf" "$fzf_base/bin/"
|
||||||
else
|
else
|
||||||
@@ -341,4 +354,3 @@ if [ $update_config -eq 1 ]; then
|
|||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
echo 'For more information, see: https://github.com/junegunn/fzf'
|
echo 'For more information, see: https://github.com/junegunn/fzf'
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package main
|
|||||||
|
|
||||||
import "github.com/junegunn/fzf/src"
|
import "github.com/junegunn/fzf/src"
|
||||||
|
|
||||||
|
var revision string
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fzf.Run(fzf.ParseOptions())
|
fzf.Run(fzf.ParseOptions(), revision)
|
||||||
}
|
}
|
||||||
@@ -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
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf-tmux 1 "Apr 2017" "fzf 0.16.7" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf-tmux 1 "Jul 2017" "fzf 0.16.9" "fzf-tmux - open fzf in tmux split pane"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf-tmux - open fzf in tmux split pane
|
fzf-tmux - open fzf in tmux split pane
|
||||||
|
|||||||
@@ -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
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf 1 "Apr 2017" "fzf 0.16.7" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Jul 2017" "fzf 0.16.9" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -236,6 +236,7 @@ e.g. \fBfzf --color=bg+:24\fR
|
|||||||
\fBbg+ \fRBackground (current line)
|
\fBbg+ \fRBackground (current line)
|
||||||
\fBhl+ \fRHighlighted substrings (current line)
|
\fBhl+ \fRHighlighted substrings (current line)
|
||||||
\fBinfo \fRInfo
|
\fBinfo \fRInfo
|
||||||
|
\fBborder \fRBorder of the preview window and horizontal separators (\fB--border\fR)
|
||||||
\fBprompt \fRPrompt
|
\fBprompt \fRPrompt
|
||||||
\fBpointer \fRPointer to the current line
|
\fBpointer \fRPointer to the current line
|
||||||
\fBmarker \fRMulti-select marker
|
\fBmarker \fRMulti-select marker
|
||||||
@@ -467,6 +468,11 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
|||||||
\fIdouble-click\fR
|
\fIdouble-click\fR
|
||||||
or any single character
|
or any single character
|
||||||
|
|
||||||
|
Additionally, a special event named \fIchange\fR is available which is
|
||||||
|
triggered whenever the query string is changed.
|
||||||
|
|
||||||
|
e.g. \fBfzf --bind change:top\fR
|
||||||
|
|
||||||
\fBACTION: DEFAULT BINDINGS (NOTES):
|
\fBACTION: DEFAULT BINDINGS (NOTES):
|
||||||
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
|
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
|
||||||
\fBaccept\fR \fIenter double-click\fR
|
\fBaccept\fR \fIenter double-click\fR
|
||||||
@@ -513,6 +519,7 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
|||||||
\fBtoggle-preview-wrap\fR
|
\fBtoggle-preview-wrap\fR
|
||||||
\fBtoggle-sort\fR
|
\fBtoggle-sort\fR
|
||||||
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
||||||
|
\fBtop\fR (move to the top result)
|
||||||
\fBunix-line-discard\fR \fIctrl-u\fR
|
\fBunix-line-discard\fR \fIctrl-u\fR
|
||||||
\fBunix-word-rubout\fR \fIctrl-w\fR
|
\fBunix-word-rubout\fR \fIctrl-w\fR
|
||||||
\fBup\fR \fIctrl-k ctrl-p up\fR
|
\fBup\fR \fIctrl-k ctrl-p up\fR
|
||||||
|
|||||||
@@ -45,19 +45,39 @@ if s:is_win
|
|||||||
endtry
|
endtry
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:fzf_shellescape(path)
|
" Use utf-8 for fzf.vim commands
|
||||||
return substitute(s:fzf_call('shellescape', a:path), '[^\\]\zs\\"$', '\\\\"', '')
|
" Return array of shell commands for cmd.exe
|
||||||
|
function! s:wrap_cmds(cmds)
|
||||||
|
return ['@echo off', 'for /f "tokens=4" %%a in (''chcp'') do set origchcp=%%a', 'chcp 65001 > nul'] +
|
||||||
|
\ (type(a:cmds) == type([]) ? a:cmds : [a:cmds]) +
|
||||||
|
\ ['chcp %origchcp% > nul']
|
||||||
endfunction
|
endfunction
|
||||||
else
|
else
|
||||||
function! s:fzf_call(fn, ...)
|
function! s:fzf_call(fn, ...)
|
||||||
return call(a:fn, a:000)
|
return call(a:fn, a:000)
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:fzf_shellescape(path)
|
function! s:wrap_cmds(cmds)
|
||||||
return shellescape(a:path)
|
return a:cmds
|
||||||
endfunction
|
endfunction
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
function! s:shellesc_cmd(arg)
|
||||||
|
let escaped = substitute(a:arg, '[&|<>()@^]', '^&', 'g')
|
||||||
|
let escaped = substitute(escaped, '%', '%%', 'g')
|
||||||
|
let escaped = substitute(escaped, '"', '\\^&', 'g')
|
||||||
|
let escaped = substitute(escaped, '\\\+\(\\^\)', '\\\\\1', 'g')
|
||||||
|
return '^"'.substitute(escaped, '[^\\]\zs\\$', '\\\\', '').'^"'
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! fzf#shellescape(arg, ...)
|
||||||
|
let shell = get(a:000, 0, &shell)
|
||||||
|
if shell =~# 'cmd.exe$'
|
||||||
|
return s:shellesc_cmd(a:arg)
|
||||||
|
endif
|
||||||
|
return s:fzf_call('shellescape', a:arg)
|
||||||
|
endfunction
|
||||||
|
|
||||||
function! s:fzf_getcwd()
|
function! s:fzf_getcwd()
|
||||||
return s:fzf_call('getcwd')
|
return s:fzf_call('getcwd')
|
||||||
endfunction
|
endfunction
|
||||||
@@ -67,7 +87,7 @@ function! s:fzf_fnamemodify(fname, mods)
|
|||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:fzf_expand(fmt)
|
function! s:fzf_expand(fmt)
|
||||||
return s:fzf_call('expand', a:fmt)
|
return s:fzf_call('expand', a:fmt, 1)
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:fzf_tempname()
|
function! s:fzf_tempname()
|
||||||
@@ -87,10 +107,10 @@ set cpo&vim
|
|||||||
function! s:fzf_exec()
|
function! s:fzf_exec()
|
||||||
if !exists('s:exec')
|
if !exists('s:exec')
|
||||||
if executable(s:fzf_go)
|
if executable(s:fzf_go)
|
||||||
let s:exec = s:fzf_expand(s:fzf_go)
|
let s:exec = s:fzf_go
|
||||||
elseif executable('fzf')
|
elseif executable('fzf')
|
||||||
let s:exec = 'fzf'
|
let s:exec = 'fzf'
|
||||||
elseif s:is_win
|
elseif s:is_win && !has('win32unix')
|
||||||
call s:warn('fzf executable not found.')
|
call s:warn('fzf executable not found.')
|
||||||
call s:warn('Download fzf binary for Windows from https://github.com/junegunn/fzf-bin/releases/')
|
call s:warn('Download fzf binary for Windows from https://github.com/junegunn/fzf-bin/releases/')
|
||||||
call s:warn('and place it as '.s:base_dir.'\bin\fzf.exe')
|
call s:warn('and place it as '.s:base_dir.'\bin\fzf.exe')
|
||||||
@@ -108,7 +128,7 @@ function! s:fzf_exec()
|
|||||||
throw 'fzf executable not found'
|
throw 'fzf executable not found'
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
return s:is_win ? s:exec : s:shellesc(s:exec)
|
return fzf#shellescape(s:exec)
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:tmux_enabled()
|
function! s:tmux_enabled()
|
||||||
@@ -128,18 +148,9 @@ function! s:tmux_enabled()
|
|||||||
return s:tmux
|
return s:tmux
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:shellesc(arg)
|
|
||||||
return '"'.substitute(a:arg, '"', '\\"', 'g').'"'
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
function! s:escape(path)
|
function! s:escape(path)
|
||||||
let escaped_chars = '$%#''"'
|
let path = fnameescape(a:path)
|
||||||
|
return s:is_win ? escape(path, '$') : path
|
||||||
if has('unix')
|
|
||||||
let escaped_chars .= ' \'
|
|
||||||
endif
|
|
||||||
|
|
||||||
return escape(a:path, escaped_chars)
|
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
" Upgrade legacy options
|
" Upgrade legacy options
|
||||||
@@ -250,7 +261,7 @@ endfunction
|
|||||||
|
|
||||||
function! s:evaluate_opts(options)
|
function! s:evaluate_opts(options)
|
||||||
return type(a:options) == type([]) ?
|
return type(a:options) == type([]) ?
|
||||||
\ join(map(copy(a:options), 's:fzf_shellescape(v:val)')) : a:options
|
\ join(map(copy(a:options), 'fzf#shellescape(v:val)')) : a:options
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
" [name string,] [opts dict,] [fullscreen boolean]
|
" [name string,] [opts dict,] [fullscreen boolean]
|
||||||
@@ -297,7 +308,7 @@ function! fzf#wrap(...)
|
|||||||
if !isdirectory(dir)
|
if !isdirectory(dir)
|
||||||
call mkdir(dir, 'p')
|
call mkdir(dir, 'p')
|
||||||
endif
|
endif
|
||||||
let history = s:is_win ? s:fzf_shellescape(dir.'\'.name) : s:escape(dir.'/'.name)
|
let history = fzf#shellescape(dir.'/'.name)
|
||||||
let opts.options = join(['--history', history, opts.options])
|
let opts.options = join(['--history', history, opts.options])
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@@ -345,11 +356,14 @@ try
|
|||||||
if has('nvim') && !has_key(dict, 'dir')
|
if has('nvim') && !has_key(dict, 'dir')
|
||||||
let dict.dir = s:fzf_getcwd()
|
let dict.dir = s:fzf_getcwd()
|
||||||
endif
|
endif
|
||||||
|
if has('win32unix') && has_key(dict, 'dir')
|
||||||
|
let dict.dir = fnamemodify(dict.dir, ':p')
|
||||||
|
endif
|
||||||
|
|
||||||
if !has_key(dict, 'source') && !empty($FZF_DEFAULT_COMMAND)
|
if !has_key(dict, 'source') && !empty($FZF_DEFAULT_COMMAND) && !s:is_win
|
||||||
let temps.source = s:fzf_tempname().(s:is_win ? '.bat' : '')
|
let temps.source = s:fzf_tempname()
|
||||||
call writefile((s:is_win ? ['@echo off'] : []) + split($FZF_DEFAULT_COMMAND, "\n"), temps.source)
|
call writefile(s:wrap_cmds(split($FZF_DEFAULT_COMMAND, "\n")), temps.source)
|
||||||
let dict.source = (empty($SHELL) ? &shell : $SHELL) . (s:is_win ? ' /c ' : ' ') . s:shellesc(temps.source)
|
let dict.source = (empty($SHELL) ? &shell : $SHELL).' '.fzf#shellescape(temps.source)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if has_key(dict, 'source')
|
if has_key(dict, 'source')
|
||||||
@@ -360,7 +374,7 @@ try
|
|||||||
elseif type == 3
|
elseif type == 3
|
||||||
let temps.input = s:fzf_tempname()
|
let temps.input = s:fzf_tempname()
|
||||||
call writefile(source, temps.input)
|
call writefile(source, temps.input)
|
||||||
let prefix = (s:is_win ? 'type ' : 'cat ').s:shellesc(temps.input).'|'
|
let prefix = (s:is_win ? 'type ' : 'cat ').fzf#shellescape(temps.input).'|'
|
||||||
else
|
else
|
||||||
throw 'Invalid source type'
|
throw 'Invalid source type'
|
||||||
endif
|
endif
|
||||||
@@ -370,10 +384,10 @@ try
|
|||||||
|
|
||||||
let prefer_tmux = get(g:, 'fzf_prefer_tmux', 0)
|
let prefer_tmux = get(g:, 'fzf_prefer_tmux', 0)
|
||||||
let use_height = has_key(dict, 'down') &&
|
let use_height = has_key(dict, 'down') &&
|
||||||
\ !(has('nvim') || s:is_win || s:present(dict, 'up', 'left', 'right')) &&
|
\ !(has('nvim') || s:is_win || has('win32unix') || s:present(dict, 'up', 'left', 'right')) &&
|
||||||
\ executable('tput') && filereadable('/dev/tty')
|
\ executable('tput') && filereadable('/dev/tty')
|
||||||
let use_term = has('nvim') && !s:is_win
|
let use_term = has('nvim') && !s:is_win
|
||||||
let use_tmux = (!use_height && !use_term || prefer_tmux) && s:tmux_enabled() && s:splittable(dict)
|
let use_tmux = (!use_height && !use_term || prefer_tmux) && !has('win32unix') && s:tmux_enabled() && s:splittable(dict)
|
||||||
if prefer_tmux && use_tmux
|
if prefer_tmux && use_tmux
|
||||||
let use_height = 0
|
let use_height = 0
|
||||||
let use_term = 0
|
let use_term = 0
|
||||||
@@ -424,7 +438,7 @@ function! s:fzf_tmux(dict)
|
|||||||
endif
|
endif
|
||||||
endfor
|
endfor
|
||||||
return printf('LINES=%d COLUMNS=%d %s %s %s --',
|
return printf('LINES=%d COLUMNS=%d %s %s %s --',
|
||||||
\ &lines, &columns, s:shellesc(s:fzf_tmux), size, (has_key(a:dict, 'source') ? '' : '-'))
|
\ &lines, &columns, fzf#shellescape(s:fzf_tmux), size, (has_key(a:dict, 'source') ? '' : '-'))
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:splittable(dict)
|
function! s:splittable(dict)
|
||||||
@@ -469,7 +483,7 @@ function! s:xterm_launcher()
|
|||||||
\ &columns, &lines/2, getwinposx(), getwinposy())
|
\ &columns, &lines/2, getwinposx(), getwinposy())
|
||||||
endfunction
|
endfunction
|
||||||
unlet! s:launcher
|
unlet! s:launcher
|
||||||
if s:is_win
|
if s:is_win || has('win32unix')
|
||||||
let s:launcher = '%s'
|
let s:launcher = '%s'
|
||||||
else
|
else
|
||||||
let s:launcher = function('s:xterm_launcher')
|
let s:launcher = function('s:xterm_launcher')
|
||||||
@@ -493,7 +507,7 @@ function! s:execute(dict, command, use_height, temps) abort
|
|||||||
if has('unix') && !a:use_height
|
if has('unix') && !a:use_height
|
||||||
silent! !clear 2> /dev/null
|
silent! !clear 2> /dev/null
|
||||||
endif
|
endif
|
||||||
let escaped = escape(substitute(a:command, '\n', '\\n', 'g'), '%#!')
|
let escaped = (a:use_height || s:is_win) ? a:command : escape(substitute(a:command, '\n', '\\n', 'g'), '%#!')
|
||||||
if has('gui_running')
|
if has('gui_running')
|
||||||
let Launcher = get(a:dict, 'launcher', get(g:, 'Fzf_launcher', get(g:, 'fzf_launcher', s:launcher)))
|
let Launcher = get(a:dict, 'launcher', get(g:, 'Fzf_launcher', get(g:, 'fzf_launcher', s:launcher)))
|
||||||
let fmt = type(Launcher) == 2 ? call(Launcher, []) : Launcher
|
let fmt = type(Launcher) == 2 ? call(Launcher, []) : Launcher
|
||||||
@@ -502,24 +516,33 @@ function! s:execute(dict, command, use_height, temps) abort
|
|||||||
endif
|
endif
|
||||||
let command = printf(fmt, escaped)
|
let command = printf(fmt, escaped)
|
||||||
else
|
else
|
||||||
let command = a:use_height ? a:command : escaped
|
let command = escaped
|
||||||
endif
|
endif
|
||||||
if s:is_win
|
if s:is_win
|
||||||
let batchfile = s:fzf_tempname().'.bat'
|
let batchfile = s:fzf_tempname().'.bat'
|
||||||
call writefile(['@echo off', command], batchfile)
|
call writefile(s:wrap_cmds(command), batchfile)
|
||||||
let command = batchfile
|
let command = batchfile
|
||||||
|
let a:temps.batchfile = batchfile
|
||||||
if has('nvim')
|
if has('nvim')
|
||||||
let s:dict = a:dict
|
|
||||||
let s:temps = a:temps
|
|
||||||
let fzf = {}
|
let fzf = {}
|
||||||
|
let fzf.dict = a:dict
|
||||||
|
let fzf.temps = a:temps
|
||||||
function! fzf.on_exit(job_id, exit_status, event) dict
|
function! fzf.on_exit(job_id, exit_status, event) dict
|
||||||
let lines = s:collect(s:temps)
|
if s:present(self.dict, 'dir')
|
||||||
call s:callback(s:dict, lines)
|
execute 'lcd' s:escape(self.dict.dir)
|
||||||
|
endif
|
||||||
|
let lines = s:collect(self.temps)
|
||||||
|
call s:callback(self.dict, lines)
|
||||||
endfunction
|
endfunction
|
||||||
let cmd = 'start /wait cmd /c '.command
|
let cmd = 'start /wait cmd /c '.command
|
||||||
call jobstart(cmd, fzf)
|
call jobstart(cmd, fzf)
|
||||||
return []
|
return []
|
||||||
endif
|
endif
|
||||||
|
elseif has('win32unix') && $TERM !=# 'cygwin'
|
||||||
|
let shellscript = s:fzf_tempname()
|
||||||
|
call writefile([command], shellscript)
|
||||||
|
let command = 'cmd.exe /C '.fzf#shellescape('set "TERM=" & start /WAIT sh -c '.shellscript)
|
||||||
|
let a:temps.shellscript = shellscript
|
||||||
endif
|
endif
|
||||||
if a:use_height
|
if a:use_height
|
||||||
let stdin = has_key(a:dict, 'source') ? '' : '< /dev/tty'
|
let stdin = has_key(a:dict, 'source') ? '' : '< /dev/tty'
|
||||||
@@ -536,7 +559,7 @@ function! s:execute_tmux(dict, command, temps) abort
|
|||||||
let command = a:command
|
let command = a:command
|
||||||
if s:pushd(a:dict)
|
if s:pushd(a:dict)
|
||||||
" -c '#{pane_current_path}' is only available on tmux 1.9 or above
|
" -c '#{pane_current_path}' is only available on tmux 1.9 or above
|
||||||
let command = 'cd '.s:escape(a:dict.dir).' && '.command
|
let command = join(['cd', fzf#shellescape(a:dict.dir), '&&', command])
|
||||||
endif
|
endif
|
||||||
|
|
||||||
call system(command)
|
call system(command)
|
||||||
|
|||||||
@@ -4,14 +4,9 @@ function fzf_key_bindings
|
|||||||
|
|
||||||
# Store current token in $dir as root for the 'find' command
|
# Store current token in $dir as root for the 'find' command
|
||||||
function fzf-file-widget -d "List files and folders"
|
function fzf-file-widget -d "List files and folders"
|
||||||
set -l dir (commandline -t)
|
set -l commandline (__fzf_parse_commandline)
|
||||||
# The commandline token might be escaped, we need to unescape it.
|
set -l dir $commandline[1]
|
||||||
set dir (eval "printf '%s' $dir")
|
set -l fzf_query $commandline[2]
|
||||||
if [ ! -d "$dir" ]
|
|
||||||
set dir .
|
|
||||||
end
|
|
||||||
# Some 'find' versions print undesired duplicated slashes if the path ends with slashes.
|
|
||||||
set dir (string replace --regex '(.)/+$' '$1' "$dir")
|
|
||||||
|
|
||||||
# "-path \$dir'*/\\.*'" matches hidden files/folders inside $dir but not
|
# "-path \$dir'*/\\.*'" matches hidden files/folders inside $dir but not
|
||||||
# $dir itself, even if hidden.
|
# $dir itself, even if hidden.
|
||||||
@@ -19,19 +14,17 @@ function fzf_key_bindings
|
|||||||
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
||||||
-o -type f -print \
|
-o -type f -print \
|
||||||
-o -type d -print \
|
-o -type d -print \
|
||||||
-o -type l -print 2> /dev/null | cut -b3-"
|
-o -type l -print 2> /dev/null | sed 's@^\./@@'"
|
||||||
|
|
||||||
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
||||||
begin
|
begin
|
||||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
|
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
|
||||||
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)" -m" | while read -l r; set result $result $r; end
|
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
|
||||||
end
|
end
|
||||||
if [ -z "$result" ]
|
if [ -z "$result" ]
|
||||||
commandline -f repaint
|
commandline -f repaint
|
||||||
return
|
return
|
||||||
end
|
else
|
||||||
|
|
||||||
if [ "$dir" != . ]
|
|
||||||
# Remove last token from commandline.
|
# Remove last token from commandline.
|
||||||
commandline -t ""
|
commandline -t ""
|
||||||
end
|
end
|
||||||
@@ -46,22 +39,45 @@ function fzf_key_bindings
|
|||||||
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
||||||
begin
|
begin
|
||||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m"
|
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m"
|
||||||
history | eval (__fzfcmd) -q '(commandline)' | read -l result
|
|
||||||
and commandline -- $result
|
set -l FISH_MAJOR (echo $FISH_VERSION | cut -f1 -d.)
|
||||||
|
set -l FISH_MINOR (echo $FISH_VERSION | cut -f2 -d.)
|
||||||
|
|
||||||
|
# history's -z flag is needed for multi-line support.
|
||||||
|
# history's -z flag was added in fish 2.4.0, so don't use it for versions
|
||||||
|
# before 2.4.0.
|
||||||
|
if [ "$FISH_MAJOR" -gt 2 -o \( "$FISH_MAJOR" -eq 2 -a "$FISH_MINOR" -ge 4 \) ];
|
||||||
|
history -z | eval (__fzfcmd) --read0 -q '(commandline)' | perl -pe 'chomp if eof' | read -lz result
|
||||||
|
and commandline -- $result
|
||||||
|
else
|
||||||
|
history | eval (__fzfcmd) -q '(commandline)' | read -l result
|
||||||
|
and commandline -- $result
|
||||||
|
end
|
||||||
end
|
end
|
||||||
commandline -f repaint
|
commandline -f repaint
|
||||||
end
|
end
|
||||||
|
|
||||||
function fzf-cd-widget -d "Change directory"
|
function fzf-cd-widget -d "Change directory"
|
||||||
|
set -l commandline (__fzf_parse_commandline)
|
||||||
|
set -l dir $commandline[1]
|
||||||
|
set -l fzf_query $commandline[2]
|
||||||
|
|
||||||
set -q FZF_ALT_C_COMMAND; or set -l FZF_ALT_C_COMMAND "
|
set -q FZF_ALT_C_COMMAND; or set -l FZF_ALT_C_COMMAND "
|
||||||
command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
||||||
-o -type d -print 2> /dev/null | cut -b3-"
|
-o -type d -print 2> /dev/null | sed 's@^\./@@'"
|
||||||
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
||||||
begin
|
begin
|
||||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
|
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
|
||||||
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)" +m" | read -l result
|
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
||||||
[ "$result" ]; and cd $result
|
|
||||||
|
if [ -n "$result" ]
|
||||||
|
cd $result
|
||||||
|
|
||||||
|
# Remove last token from commandline.
|
||||||
|
commandline -t ""
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
commandline -f repaint
|
commandline -f repaint
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -84,4 +100,47 @@ function fzf_key_bindings
|
|||||||
bind -M insert \cr fzf-history-widget
|
bind -M insert \cr fzf-history-widget
|
||||||
bind -M insert \ec fzf-cd-widget
|
bind -M insert \ec fzf-cd-widget
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath and rest of token'
|
||||||
|
# eval is used to do shell expansion on paths
|
||||||
|
set -l commandline (eval "printf '%s' "(commandline -t))
|
||||||
|
|
||||||
|
if [ -z $commandline ]
|
||||||
|
# Default to current directory with no --query
|
||||||
|
set dir '.'
|
||||||
|
set fzf_query ''
|
||||||
|
else
|
||||||
|
set dir (__fzf_get_dir $commandline)
|
||||||
|
|
||||||
|
if [ "$dir" = "." -a (string sub -l 1 $commandline) != '.' ]
|
||||||
|
# if $dir is "." but commandline is not a relative path, this means no file path found
|
||||||
|
set fzf_query $commandline
|
||||||
|
else
|
||||||
|
# Also remove trailing slash after dir, to "split" input properly
|
||||||
|
set fzf_query (string replace -r "^$dir/?" '' "$commandline")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
echo $dir
|
||||||
|
echo $fzf_query
|
||||||
|
end
|
||||||
|
|
||||||
|
function __fzf_get_dir -d 'Find the longest existing filepath from input string'
|
||||||
|
set dir $argv
|
||||||
|
|
||||||
|
# Strip all trailing slashes. Ignore if $dir is root dir (/)
|
||||||
|
if [ (string length $dir) -gt 1 ]
|
||||||
|
set dir (string replace -r '/*$' '' $dir)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Iteratively check if dir exists and strip tail end of path
|
||||||
|
while [ ! -d "$dir" ]
|
||||||
|
# If path is absolute, this can keep going until ends up at /
|
||||||
|
# If path is relative, this can keep going until entire input is consumed, dirname returns "."
|
||||||
|
set dir (dirname "$dir")
|
||||||
|
end
|
||||||
|
|
||||||
|
echo $dir
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
127
src/Makefile
127
src/Makefile
@@ -1,127 +0,0 @@
|
|||||||
ifndef GOOS
|
|
||||||
UNAME_S := $(shell uname -s)
|
|
||||||
ifeq ($(UNAME_S),Darwin)
|
|
||||||
GOOS := darwin
|
|
||||||
else ifeq ($(UNAME_S),Linux)
|
|
||||||
GOOS := linux
|
|
||||||
else
|
|
||||||
$(error "$$GOOS is not defined.")
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
|
|
||||||
SOURCES := $(wildcard *.go */*.go)
|
|
||||||
ROOTDIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
|
||||||
BINDIR := $(shell dirname $(ROOTDIR))/bin
|
|
||||||
GOPATH := $(shell dirname $(ROOTDIR))/gopath
|
|
||||||
SRCDIR := $(GOPATH)/src/github.com/junegunn/fzf/src
|
|
||||||
BINARY32 := fzf-$(GOOS)_386
|
|
||||||
BINARY64 := fzf-$(GOOS)_amd64
|
|
||||||
BINARYARM5 := fzf-$(GOOS)_arm5
|
|
||||||
BINARYARM6 := fzf-$(GOOS)_arm6
|
|
||||||
BINARYARM7 := fzf-$(GOOS)_arm7
|
|
||||||
BINARYARM8 := fzf-$(GOOS)_arm8
|
|
||||||
VERSION := $(shell awk -F= '/version =/ {print $$2}' constants.go | tr -d "\" ")
|
|
||||||
RELEASE32 := fzf-$(VERSION)-$(GOOS)_386
|
|
||||||
RELEASE64 := fzf-$(VERSION)-$(GOOS)_amd64
|
|
||||||
RELEASEARM5 := fzf-$(VERSION)-$(GOOS)_arm5
|
|
||||||
RELEASEARM6 := fzf-$(VERSION)-$(GOOS)_arm6
|
|
||||||
RELEASEARM7 := fzf-$(VERSION)-$(GOOS)_arm7
|
|
||||||
RELEASEARM8 := fzf-$(VERSION)-$(GOOS)_arm8
|
|
||||||
export GOPATH
|
|
||||||
|
|
||||||
# https://en.wikipedia.org/wiki/Uname
|
|
||||||
UNAME_M := $(shell uname -m)
|
|
||||||
ifeq ($(UNAME_M),x86_64)
|
|
||||||
BINARY := $(BINARY64)
|
|
||||||
else ifeq ($(UNAME_M),amd64)
|
|
||||||
BINARY := $(BINARY64)
|
|
||||||
else ifeq ($(UNAME_M),i686)
|
|
||||||
BINARY := $(BINARY32)
|
|
||||||
else ifeq ($(UNAME_M),i386)
|
|
||||||
BINARY := $(BINARY32)
|
|
||||||
else ifeq ($(UNAME_M),armv5l)
|
|
||||||
BINARY := $(BINARYARM5)
|
|
||||||
else ifeq ($(UNAME_M),armv6l)
|
|
||||||
BINARY := $(BINARYARM6)
|
|
||||||
else ifeq ($(UNAME_M),armv7l)
|
|
||||||
BINARY := $(BINARYARM7)
|
|
||||||
else
|
|
||||||
$(error "Build on $(UNAME_M) is not supported, yet.")
|
|
||||||
endif
|
|
||||||
|
|
||||||
all: fzf/$(BINARY)
|
|
||||||
|
|
||||||
ifeq ($(GOOS),windows)
|
|
||||||
release: fzf/$(BINARY32) fzf/$(BINARY64)
|
|
||||||
cd fzf && cp -f $(BINARY32) fzf.exe && zip $(RELEASE32).zip fzf.exe
|
|
||||||
cd fzf && cp -f $(BINARY64) fzf.exe && zip $(RELEASE64).zip fzf.exe
|
|
||||||
cd fzf && rm -f fzf.exe
|
|
||||||
else ifeq ($(GOOS),linux)
|
|
||||||
release: fzf/$(BINARY32) fzf/$(BINARY64) fzf/$(BINARYARM5) fzf/$(BINARYARM6) fzf/$(BINARYARM7) fzf/$(BINARYARM8)
|
|
||||||
cd fzf && cp -f $(BINARY32) fzf && tar -czf $(RELEASE32).tgz fzf
|
|
||||||
cd fzf && cp -f $(BINARY64) fzf && tar -czf $(RELEASE64).tgz fzf
|
|
||||||
cd fzf && cp -f $(BINARYARM5) fzf && tar -czf $(RELEASEARM5).tgz fzf
|
|
||||||
cd fzf && cp -f $(BINARYARM6) fzf && tar -czf $(RELEASEARM6).tgz fzf
|
|
||||||
cd fzf && cp -f $(BINARYARM7) fzf && tar -czf $(RELEASEARM7).tgz fzf
|
|
||||||
cd fzf && cp -f $(BINARYARM8) fzf && tar -czf $(RELEASEARM8).tgz fzf
|
|
||||||
cd fzf && rm -f fzf
|
|
||||||
else
|
|
||||||
release: fzf/$(BINARY32) fzf/$(BINARY64)
|
|
||||||
cd fzf && cp -f $(BINARY32) fzf && tar -czf $(RELEASE32).tgz fzf
|
|
||||||
cd fzf && cp -f $(BINARY64) fzf && tar -czf $(RELEASE64).tgz fzf
|
|
||||||
cd fzf && rm -f fzf
|
|
||||||
endif
|
|
||||||
|
|
||||||
release-all: clean test
|
|
||||||
GOOS=darwin make release
|
|
||||||
GOOS=linux make release
|
|
||||||
GOOS=freebsd make release
|
|
||||||
GOOS=openbsd make release
|
|
||||||
GOOS=windows make release
|
|
||||||
|
|
||||||
$(SRCDIR):
|
|
||||||
mkdir -p $(shell dirname $(SRCDIR))
|
|
||||||
ln -s $(ROOTDIR) $(SRCDIR)
|
|
||||||
|
|
||||||
deps: $(SRCDIR) $(SOURCES)
|
|
||||||
cd $(SRCDIR) && go get -tags "$(TAGS)"
|
|
||||||
./deps
|
|
||||||
|
|
||||||
test: deps
|
|
||||||
SHELL=/bin/sh GOOS= go test -v -tags "$(TAGS)" ./...
|
|
||||||
|
|
||||||
install: $(BINDIR)/fzf
|
|
||||||
|
|
||||||
uninstall:
|
|
||||||
rm -f $(BINDIR)/fzf $(BINDIR)/$(BINARY)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
cd fzf && rm -f fzf-*
|
|
||||||
|
|
||||||
fzf/$(BINARY32): deps
|
|
||||||
cd fzf && GOARCH=386 go build -a -ldflags "-w -extldflags=$(LDFLAGS)" -tags "$(TAGS)" -o $(BINARY32)
|
|
||||||
|
|
||||||
fzf/$(BINARY64): deps
|
|
||||||
cd fzf && GOARCH=amd64 go build -a -ldflags "-w -extldflags=$(LDFLAGS)" -tags "$(TAGS)" -o $(BINARY64)
|
|
||||||
|
|
||||||
# https://github.com/golang/go/wiki/GoArm
|
|
||||||
fzf/$(BINARYARM5): deps
|
|
||||||
cd fzf && GOARCH=arm GOARM=5 go build -a -ldflags "-w -extldflags=$(LDFLAGS)" -tags "$(TAGS)" -o $(BINARYARM5)
|
|
||||||
|
|
||||||
fzf/$(BINARYARM6): deps
|
|
||||||
cd fzf && GOARCH=arm GOARM=6 go build -a -ldflags "-w -extldflags=$(LDFLAGS)" -tags "$(TAGS)" -o $(BINARYARM6)
|
|
||||||
|
|
||||||
fzf/$(BINARYARM7): deps
|
|
||||||
cd fzf && GOARCH=arm GOARM=7 go build -a -ldflags "-w -extldflags=$(LDFLAGS)" -tags "$(TAGS)" -o $(BINARYARM7)
|
|
||||||
|
|
||||||
fzf/$(BINARYARM8): deps
|
|
||||||
cd fzf && GOARCH=arm64 go build -a -ldflags "-w -extldflags=$(LDFLAGS)" -tags "$(TAGS)" -o $(BINARYARM8)
|
|
||||||
|
|
||||||
$(BINDIR)/fzf: fzf/$(BINARY) | $(BINDIR)
|
|
||||||
cp -f fzf/$(BINARY) $(BINDIR)
|
|
||||||
cd $(BINDIR) && ln -sf $(BINARY) fzf
|
|
||||||
|
|
||||||
$(BINDIR):
|
|
||||||
mkdir -p $@
|
|
||||||
|
|
||||||
.PHONY: all deps release release-all test install uninstall clean
|
|
||||||
106
src/README.md
106
src/README.md
@@ -1,106 +0,0 @@
|
|||||||
fzf in Go
|
|
||||||
=========
|
|
||||||
|
|
||||||
<img src="https://cloud.githubusercontent.com/assets/700826/5725028/028ea834-9b93-11e4-9198-43088c3f295d.gif" height="463" alt="fzf in go">
|
|
||||||
|
|
||||||
This directory contains the source code for the new fzf implementation in
|
|
||||||
[Go][go].
|
|
||||||
|
|
||||||
Upgrade from Ruby version
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
The install script has been updated to download the right binary for your
|
|
||||||
system. If you already have installed fzf, simply git-pull the repository and
|
|
||||||
rerun the install script.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd ~/.fzf
|
|
||||||
git pull
|
|
||||||
./install
|
|
||||||
```
|
|
||||||
|
|
||||||
Otherwise, follow [the instruction][install] as before. You can also install
|
|
||||||
fzf using Homebrew if you prefer that way.
|
|
||||||
|
|
||||||
Motivations
|
|
||||||
-----------
|
|
||||||
|
|
||||||
### No Ruby dependency
|
|
||||||
|
|
||||||
There have always been complaints about fzf being a Ruby script. To make
|
|
||||||
matters worse, Ruby 2.1 removed ncurses binding from its standard libary.
|
|
||||||
Because of the change, users running Ruby 2.1 or above are forced to build C
|
|
||||||
extensions of curses gem to meet the requirement of fzf. The new Go version
|
|
||||||
will be distributed as an executable binary so it will be much more accessible
|
|
||||||
and should be easier to setup.
|
|
||||||
|
|
||||||
### Performance
|
|
||||||
|
|
||||||
Many people have been surprised to see how fast fzf is even when it was
|
|
||||||
written in Ruby. It stays quite responsive even for 100k+ lines, which is
|
|
||||||
well above the size of the usual input.
|
|
||||||
|
|
||||||
The new Go version, of course, is significantly faster than that. It has all
|
|
||||||
the performance optimization techniques used in Ruby implementation and more.
|
|
||||||
It also doesn't suffer from [GIL][gil], so the search performance scales
|
|
||||||
proportional to the number of CPU cores. On my MacBook Pro (Mid 2012), the new
|
|
||||||
version was shown to be an order of magnitude faster on certain cases. It also
|
|
||||||
starts much faster though the difference may not be noticeable.
|
|
||||||
|
|
||||||
Build
|
|
||||||
-----
|
|
||||||
|
|
||||||
See [BUILD.md](../BUILD.md)
|
|
||||||
|
|
||||||
Test
|
|
||||||
----
|
|
||||||
|
|
||||||
Unit tests can be run with `make test`. Integration tests are written in Ruby
|
|
||||||
script that should be run on tmux.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd src
|
|
||||||
|
|
||||||
# Unit tests
|
|
||||||
make test
|
|
||||||
|
|
||||||
# Integration tests
|
|
||||||
ruby ../test/test_go.rb
|
|
||||||
|
|
||||||
# Build binary for the platform
|
|
||||||
make
|
|
||||||
|
|
||||||
# Install the executable to ../bin directory
|
|
||||||
make install
|
|
||||||
|
|
||||||
# Make release archives
|
|
||||||
make release
|
|
||||||
|
|
||||||
# Make release archives for all supported platforms
|
|
||||||
make release-all
|
|
||||||
```
|
|
||||||
|
|
||||||
Third-party libraries used
|
|
||||||
--------------------------
|
|
||||||
|
|
||||||
- ~[ncurses][ncurses]~
|
|
||||||
- [mattn/go-runewidth](https://github.com/mattn/go-runewidth)
|
|
||||||
- Licensed under [MIT](http://mattn.mit-license.org)
|
|
||||||
- [mattn/go-shellwords](https://github.com/mattn/go-shellwords)
|
|
||||||
- Licensed under [MIT](http://mattn.mit-license.org)
|
|
||||||
- [mattn/go-isatty](https://github.com/mattn/go-isatty)
|
|
||||||
- Licensed under [MIT](http://mattn.mit-license.org)
|
|
||||||
- [tcell](https://github.com/gdamore/tcell)
|
|
||||||
- Licensed under [Apache License 2.0](https://github.com/gdamore/tcell/blob/master/LICENSE)
|
|
||||||
|
|
||||||
License
|
|
||||||
-------
|
|
||||||
|
|
||||||
[MIT](LICENSE)
|
|
||||||
|
|
||||||
[install]: https://github.com/junegunn/fzf#installation
|
|
||||||
[go]: https://golang.org/
|
|
||||||
[gil]: http://en.wikipedia.org/wiki/Global_Interpreter_Lock
|
|
||||||
[ncurses]: https://www.gnu.org/software/ncurses/
|
|
||||||
[req]: http://golang.org/doc/install
|
|
||||||
[tcell]: https://github.com/gdamore/tcell
|
|
||||||
@@ -283,8 +283,9 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input util.C
|
|||||||
|
|
||||||
// Phase 1. Check if there's a match and calculate bonus for each point
|
// Phase 1. Check if there's a match and calculate bonus for each point
|
||||||
pidx, lastIdx, prevClass := 0, 0, charNonWord
|
pidx, lastIdx, prevClass := 0, 0, charNonWord
|
||||||
|
input.CopyRunes(T)
|
||||||
for idx := 0; idx < N; idx++ {
|
for idx := 0; idx < N; idx++ {
|
||||||
char := input.Get(idx)
|
char := T[idx]
|
||||||
var class charClass
|
var class charClass
|
||||||
if char <= unicode.MaxASCII {
|
if char <= unicode.MaxASCII {
|
||||||
class = charClassOfAscii(char)
|
class = charClassOfAscii(char)
|
||||||
@@ -389,7 +390,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input util.C
|
|||||||
if i == 0 {
|
if i == 0 {
|
||||||
fmt.Print(" ")
|
fmt.Print(" ")
|
||||||
for j := int(F[i]); j <= lastIdx; j++ {
|
for j := int(F[i]); j <= lastIdx; j++ {
|
||||||
fmt.Printf(" " + string(input.Get(j)) + " ")
|
fmt.Printf(" " + string(T[j]) + " ")
|
||||||
}
|
}
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
|||||||
60
src/ansi.go
60
src/ansi.go
@@ -44,7 +44,21 @@ func init() {
|
|||||||
*/
|
*/
|
||||||
// The following regular expression will include not all but most of the
|
// The following regular expression will include not all but most of the
|
||||||
// frequently used ANSI sequences
|
// frequently used ANSI sequences
|
||||||
ansiRegex = regexp.MustCompile("\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b.|[\x0e\x0f]|.\x08")
|
ansiRegex = regexp.MustCompile("(?:\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b.|[\x0e\x0f]|.\x08)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func findAnsiStart(str string) int {
|
||||||
|
idx := 0
|
||||||
|
for ; idx < len(str); idx++ {
|
||||||
|
b := str[idx]
|
||||||
|
if b == 0x1b || b == 0x0e || b == 0x0f {
|
||||||
|
return idx
|
||||||
|
}
|
||||||
|
if b == 0x08 && idx > 0 {
|
||||||
|
return idx - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return idx
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractColor(str string, state *ansiState, proc func(string, *ansiState) bool) (string, *[]ansiOffset, *ansiState) {
|
func extractColor(str string, state *ansiState, proc func(string, *ansiState) bool) (string, *[]ansiOffset, *ansiState) {
|
||||||
@@ -55,41 +69,61 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
|
|||||||
offsets = append(offsets, ansiOffset{[2]int32{0, 0}, *state})
|
offsets = append(offsets, ansiOffset{[2]int32{0, 0}, *state})
|
||||||
}
|
}
|
||||||
|
|
||||||
idx := 0
|
prevIdx := 0
|
||||||
for _, offset := range ansiRegex.FindAllStringIndex(str, -1) {
|
runeCount := 0
|
||||||
prev := str[idx:offset[0]]
|
for idx := 0; idx < len(str); {
|
||||||
output.WriteString(prev)
|
idx += findAnsiStart(str[idx:])
|
||||||
|
|
||||||
|
// No sign of ANSI code
|
||||||
|
if idx == len(str) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure that we found an ANSI code
|
||||||
|
offset := ansiRegex.FindStringIndex(str[idx:])
|
||||||
|
if offset == nil {
|
||||||
|
idx++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
offset[0] += idx
|
||||||
|
offset[1] += idx
|
||||||
|
idx = offset[1]
|
||||||
|
|
||||||
|
// Check if we should continue
|
||||||
|
prev := str[prevIdx:offset[0]]
|
||||||
if proc != nil && !proc(prev, state) {
|
if proc != nil && !proc(prev, state) {
|
||||||
return "", nil, nil
|
return "", nil, nil
|
||||||
}
|
}
|
||||||
newState := interpretCode(str[offset[0]:offset[1]], state)
|
|
||||||
|
|
||||||
|
prevIdx = offset[1]
|
||||||
|
runeCount += utf8.RuneCountInString(prev)
|
||||||
|
output.WriteString(prev)
|
||||||
|
|
||||||
|
newState := interpretCode(str[offset[0]:offset[1]], state)
|
||||||
if !newState.equals(state) {
|
if !newState.equals(state) {
|
||||||
if state != nil {
|
if state != nil {
|
||||||
// Update last offset
|
// Update last offset
|
||||||
(&offsets[len(offsets)-1]).offset[1] = int32(utf8.RuneCount(output.Bytes()))
|
(&offsets[len(offsets)-1]).offset[1] = int32(runeCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
if newState.colored() {
|
if newState.colored() {
|
||||||
// Append new offset
|
// Append new offset
|
||||||
state = newState
|
state = newState
|
||||||
newLen := int32(utf8.RuneCount(output.Bytes()))
|
offsets = append(offsets, ansiOffset{[2]int32{int32(runeCount), int32(runeCount)}, *state})
|
||||||
offsets = append(offsets, ansiOffset{[2]int32{newLen, newLen}, *state})
|
|
||||||
} else {
|
} else {
|
||||||
// Discard state
|
// Discard state
|
||||||
state = nil
|
state = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
idx = offset[1]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rest := str[idx:]
|
rest := str[prevIdx:]
|
||||||
if len(rest) > 0 {
|
if len(rest) > 0 {
|
||||||
output.WriteString(rest)
|
output.WriteString(rest)
|
||||||
if state != nil {
|
if state != nil {
|
||||||
// Update last offset
|
// Update last offset
|
||||||
(&offsets[len(offsets)-1]).offset[1] = int32(utf8.RuneCount(output.Bytes()))
|
runeCount += utf8.RuneCountInString(rest)
|
||||||
|
(&offsets[len(offsets)-1]).offset[1] = int32(runeCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if proc != nil {
|
if proc != nil {
|
||||||
|
|||||||
42
src/cache.go
42
src/cache.go
@@ -3,7 +3,7 @@ package fzf
|
|||||||
import "sync"
|
import "sync"
|
||||||
|
|
||||||
// queryCache associates strings to lists of items
|
// queryCache associates strings to lists of items
|
||||||
type queryCache map[string][]*Result
|
type queryCache map[string][]Result
|
||||||
|
|
||||||
// ChunkCache associates Chunk and query string to lists of items
|
// ChunkCache associates Chunk and query string to lists of items
|
||||||
type ChunkCache struct {
|
type ChunkCache struct {
|
||||||
@@ -17,7 +17,7 @@ func NewChunkCache() ChunkCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add adds the list to the cache
|
// Add adds the list to the cache
|
||||||
func (cc *ChunkCache) Add(chunk *Chunk, key string, list []*Result) {
|
func (cc *ChunkCache) Add(chunk *Chunk, key string, list []Result) {
|
||||||
if len(key) == 0 || !chunk.IsFull() || len(list) > queryCacheMax {
|
if len(key) == 0 || !chunk.IsFull() || len(list) > queryCacheMax {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -33,10 +33,10 @@ func (cc *ChunkCache) Add(chunk *Chunk, key string, list []*Result) {
|
|||||||
(*qc)[key] = list
|
(*qc)[key] = list
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find is called to lookup ChunkCache
|
// Lookup is called to lookup ChunkCache
|
||||||
func (cc *ChunkCache) Find(chunk *Chunk, key string) ([]*Result, bool) {
|
func (cc *ChunkCache) Lookup(chunk *Chunk, key string) []Result {
|
||||||
if len(key) == 0 || !chunk.IsFull() {
|
if len(key) == 0 || !chunk.IsFull() {
|
||||||
return nil, false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cc.mutex.Lock()
|
cc.mutex.Lock()
|
||||||
@@ -46,8 +46,36 @@ func (cc *ChunkCache) Find(chunk *Chunk, key string) ([]*Result, bool) {
|
|||||||
if ok {
|
if ok {
|
||||||
list, ok := (*qc)[key]
|
list, ok := (*qc)[key]
|
||||||
if ok {
|
if ok {
|
||||||
return list, true
|
return list
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, false
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *ChunkCache) Search(chunk *Chunk, key string) []Result {
|
||||||
|
if len(key) == 0 || !chunk.IsFull() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cc.mutex.Lock()
|
||||||
|
defer cc.mutex.Unlock()
|
||||||
|
|
||||||
|
qc, ok := cc.cache[chunk]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx := 1; idx < len(key); idx++ {
|
||||||
|
// [---------| ] | [ |---------]
|
||||||
|
// [--------| ] | [ |--------]
|
||||||
|
// [-------| ] | [ |-------]
|
||||||
|
prefix := key[:len(key)-idx]
|
||||||
|
suffix := key[idx:]
|
||||||
|
for _, substr := range [2]string{prefix, suffix} {
|
||||||
|
if cached, found := (*qc)[substr]; found {
|
||||||
|
return cached
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,34 +7,34 @@ func TestChunkCache(t *testing.T) {
|
|||||||
chunk2 := make(Chunk, chunkSize)
|
chunk2 := make(Chunk, chunkSize)
|
||||||
chunk1p := &Chunk{}
|
chunk1p := &Chunk{}
|
||||||
chunk2p := &chunk2
|
chunk2p := &chunk2
|
||||||
items1 := []*Result{&Result{}}
|
items1 := []Result{Result{}}
|
||||||
items2 := []*Result{&Result{}, &Result{}}
|
items2 := []Result{Result{}, Result{}}
|
||||||
cache.Add(chunk1p, "foo", items1)
|
cache.Add(chunk1p, "foo", items1)
|
||||||
cache.Add(chunk2p, "foo", items1)
|
cache.Add(chunk2p, "foo", items1)
|
||||||
cache.Add(chunk2p, "bar", items2)
|
cache.Add(chunk2p, "bar", items2)
|
||||||
|
|
||||||
{ // chunk1 is not full
|
{ // chunk1 is not full
|
||||||
cached, found := cache.Find(chunk1p, "foo")
|
cached := cache.Lookup(chunk1p, "foo")
|
||||||
if found {
|
if cached != nil {
|
||||||
t.Error("Cached disabled for non-empty chunks", found, cached)
|
t.Error("Cached disabled for non-empty chunks", cached)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
cached, found := cache.Find(chunk2p, "foo")
|
cached := cache.Lookup(chunk2p, "foo")
|
||||||
if !found || len(cached) != 1 {
|
if cached == nil || len(cached) != 1 {
|
||||||
t.Error("Expected 1 item cached", found, cached)
|
t.Error("Expected 1 item cached", cached)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
cached, found := cache.Find(chunk2p, "bar")
|
cached := cache.Lookup(chunk2p, "bar")
|
||||||
if !found || len(cached) != 2 {
|
if cached == nil || len(cached) != 2 {
|
||||||
t.Error("Expected 2 items cached", found, cached)
|
t.Error("Expected 2 items cached", cached)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
cached, found := cache.Find(chunk1p, "foobar")
|
cached := cache.Lookup(chunk1p, "foobar")
|
||||||
if found {
|
if cached != nil {
|
||||||
t.Error("Expected 0 item cached", found, cached)
|
t.Error("Expected 0 item cached", cached)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ package fzf
|
|||||||
|
|
||||||
import "sync"
|
import "sync"
|
||||||
|
|
||||||
// Chunk is a list of Item pointers whose size has the upper limit of chunkSize
|
// Chunk is a list of Items whose size has the upper limit of chunkSize
|
||||||
type Chunk []*Item // >>> []Item
|
type Chunk []Item
|
||||||
|
|
||||||
// ItemBuilder is a closure type that builds Item object from a pointer to a
|
// ItemBuilder is a closure type that builds Item object from a pointer to a
|
||||||
// string and an integer
|
// string and an integer
|
||||||
type ItemBuilder func([]byte, int) *Item
|
type ItemBuilder func([]byte, int) Item
|
||||||
|
|
||||||
// ChunkList is a list of Chunks
|
// ChunkList is a list of Chunks
|
||||||
type ChunkList struct {
|
type ChunkList struct {
|
||||||
@@ -28,11 +28,11 @@ func NewChunkList(trans ItemBuilder) *ChunkList {
|
|||||||
|
|
||||||
func (c *Chunk) push(trans ItemBuilder, data []byte, index int) bool {
|
func (c *Chunk) push(trans ItemBuilder, data []byte, index int) bool {
|
||||||
item := trans(data, index)
|
item := trans(data, index)
|
||||||
if item != nil {
|
if item.Nil() {
|
||||||
*c = append(*c, item)
|
return false
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
return false
|
*c = append(*c, item)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsFull returns true if the Chunk is full
|
// IsFull returns true if the Chunk is full
|
||||||
@@ -58,7 +58,7 @@ func (cl *ChunkList) Push(data []byte) bool {
|
|||||||
defer cl.mutex.Unlock()
|
defer cl.mutex.Unlock()
|
||||||
|
|
||||||
if len(cl.chunks) == 0 || cl.lastChunk().IsFull() {
|
if len(cl.chunks) == 0 || cl.lastChunk().IsFull() {
|
||||||
newChunk := Chunk(make([]*Item, 0, chunkSize))
|
newChunk := Chunk(make([]Item, 0, chunkSize))
|
||||||
cl.chunks = append(cl.chunks, &newChunk)
|
cl.chunks = append(cl.chunks, &newChunk)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,15 +79,8 @@ func (cl *ChunkList) Snapshot() ([]*Chunk, int) {
|
|||||||
|
|
||||||
// Duplicate the last chunk
|
// Duplicate the last chunk
|
||||||
if cnt := len(ret); cnt > 0 {
|
if cnt := len(ret); cnt > 0 {
|
||||||
ret[cnt-1] = ret[cnt-1].dupe()
|
newChunk := *ret[cnt-1]
|
||||||
|
ret[cnt-1] = &newChunk
|
||||||
}
|
}
|
||||||
return ret, cl.count
|
return ret, cl.count
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Chunk) dupe() *Chunk {
|
|
||||||
newChunk := make(Chunk, len(*c))
|
|
||||||
for idx, ptr := range *c {
|
|
||||||
newChunk[idx] = ptr
|
|
||||||
}
|
|
||||||
return &newChunk
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ func TestChunkList(t *testing.T) {
|
|||||||
// FIXME global
|
// FIXME global
|
||||||
sortCriteria = []criterion{byScore, byLength}
|
sortCriteria = []criterion{byScore, byLength}
|
||||||
|
|
||||||
cl := NewChunkList(func(s []byte, i int) *Item {
|
cl := NewChunkList(func(s []byte, i int) Item {
|
||||||
return &Item{text: util.ToChars(s), index: int32(i * 2)}
|
chars := util.ToChars(s)
|
||||||
|
chars.Index = int32(i * 2)
|
||||||
|
return Item{text: chars}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Snapshot
|
// Snapshot
|
||||||
@@ -41,8 +43,8 @@ func TestChunkList(t *testing.T) {
|
|||||||
if len(*chunk1) != 2 {
|
if len(*chunk1) != 2 {
|
||||||
t.Error("Snapshot should contain only two items")
|
t.Error("Snapshot should contain only two items")
|
||||||
}
|
}
|
||||||
if (*chunk1)[0].text.ToString() != "hello" || (*chunk1)[0].index != 0 ||
|
if (*chunk1)[0].text.ToString() != "hello" || (*chunk1)[0].Index() != 0 ||
|
||||||
(*chunk1)[1].text.ToString() != "world" || (*chunk1)[1].index != 2 {
|
(*chunk1)[1].text.ToString() != "world" || (*chunk1)[1].Index() != 2 {
|
||||||
t.Error("Invalid data")
|
t.Error("Invalid data")
|
||||||
}
|
}
|
||||||
if chunk1.IsFull() {
|
if chunk1.IsFull() {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
@@ -8,7 +9,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// Current version
|
// Current version
|
||||||
version = "0.16.7"
|
version = "0.16.9"
|
||||||
|
|
||||||
// Core
|
// Core
|
||||||
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
||||||
@@ -47,6 +48,18 @@ const (
|
|||||||
defaultJumpLabels string = "asdfghjklqwertyuiopzxcvbnm1234567890ASDFGHJKLQWERTYUIOPZXCVBNM`~;:,<.>/?'\"!@#$%^&*()[{]}-_=+"
|
defaultJumpLabels string = "asdfghjklqwertyuiopzxcvbnm1234567890ASDFGHJKLQWERTYUIOPZXCVBNM`~;:,<.>/?'\"!@#$%^&*()[{]}-_=+"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var defaultCommand string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if !util.IsWindows() {
|
||||||
|
defaultCommand = `command find -L . -mindepth 1 \( -path '*/\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-`
|
||||||
|
} else if os.Getenv("TERM") == "cygwin" {
|
||||||
|
defaultCommand = `sh -c "command find -L . -mindepth 1 -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-"`
|
||||||
|
} else {
|
||||||
|
defaultCommand = `dir /s/b`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// fzf events
|
// fzf events
|
||||||
const (
|
const (
|
||||||
EvtReadNew util.EventType = iota
|
EvtReadNew util.EventType = iota
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
// +build !windows
|
|
||||||
|
|
||||||
package fzf
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Reader
|
|
||||||
defaultCommand = `command find -L . -mindepth 1 \( -path '*/\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-`
|
|
||||||
)
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
// +build windows
|
|
||||||
|
|
||||||
package fzf
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Reader
|
|
||||||
defaultCommand = `dir /s/b`
|
|
||||||
)
|
|
||||||
52
src/core.go
52
src/core.go
@@ -43,12 +43,16 @@ Matcher -> EvtHeader -> Terminal (update header)
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Run starts fzf
|
// Run starts fzf
|
||||||
func Run(opts *Options) {
|
func Run(opts *Options, revision string) {
|
||||||
sort := opts.Sort > 0
|
sort := opts.Sort > 0
|
||||||
sortCriteria = opts.Criteria
|
sortCriteria = opts.Criteria
|
||||||
|
|
||||||
if opts.Version {
|
if opts.Version {
|
||||||
fmt.Println(version)
|
if len(revision) > 0 {
|
||||||
|
fmt.Printf("%s (%s)\n", version, revision)
|
||||||
|
} else {
|
||||||
|
fmt.Println(version)
|
||||||
|
}
|
||||||
os.Exit(exitOk)
|
os.Exit(exitOk)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,9 +63,6 @@ func Run(opts *Options) {
|
|||||||
ansiProcessor := func(data []byte) (util.Chars, *[]ansiOffset) {
|
ansiProcessor := func(data []byte) (util.Chars, *[]ansiOffset) {
|
||||||
return util.ToChars(data), nil
|
return util.ToChars(data), nil
|
||||||
}
|
}
|
||||||
ansiProcessorRunes := func(data []rune) (util.Chars, *[]ansiOffset) {
|
|
||||||
return util.RunesToChars(data), nil
|
|
||||||
}
|
|
||||||
if opts.Ansi {
|
if opts.Ansi {
|
||||||
if opts.Theme != nil {
|
if opts.Theme != nil {
|
||||||
var state *ansiState
|
var state *ansiState
|
||||||
@@ -78,46 +79,35 @@ func Run(opts *Options) {
|
|||||||
return util.RunesToChars([]rune(trimmed)), nil
|
return util.RunesToChars([]rune(trimmed)), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ansiProcessorRunes = func(data []rune) (util.Chars, *[]ansiOffset) {
|
|
||||||
return ansiProcessor([]byte(string(data)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chunk list
|
// Chunk list
|
||||||
var chunkList *ChunkList
|
var chunkList *ChunkList
|
||||||
header := make([]string, 0, opts.HeaderLines)
|
header := make([]string, 0, opts.HeaderLines)
|
||||||
if len(opts.WithNth) == 0 {
|
if len(opts.WithNth) == 0 {
|
||||||
chunkList = NewChunkList(func(data []byte, index int) *Item {
|
chunkList = NewChunkList(func(data []byte, index int) Item {
|
||||||
if len(header) < opts.HeaderLines {
|
if len(header) < opts.HeaderLines {
|
||||||
header = append(header, string(data))
|
header = append(header, string(data))
|
||||||
eventBox.Set(EvtHeader, header)
|
eventBox.Set(EvtHeader, header)
|
||||||
return nil
|
return nilItem
|
||||||
}
|
}
|
||||||
chars, colors := ansiProcessor(data)
|
chars, colors := ansiProcessor(data)
|
||||||
return &Item{
|
chars.Index = int32(index)
|
||||||
index: int32(index),
|
return Item{text: chars, colors: colors}
|
||||||
text: chars,
|
|
||||||
colors: colors}
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
chunkList = NewChunkList(func(data []byte, index int) *Item {
|
chunkList = NewChunkList(func(data []byte, index int) Item {
|
||||||
tokens := Tokenize(util.ToChars(data), opts.Delimiter)
|
tokens := Tokenize(string(data), opts.Delimiter)
|
||||||
trans := Transform(tokens, opts.WithNth)
|
trans := Transform(tokens, opts.WithNth)
|
||||||
|
transformed := joinTokens(trans)
|
||||||
if len(header) < opts.HeaderLines {
|
if len(header) < opts.HeaderLines {
|
||||||
header = append(header, string(joinTokens(trans)))
|
header = append(header, transformed)
|
||||||
eventBox.Set(EvtHeader, header)
|
eventBox.Set(EvtHeader, header)
|
||||||
return nil
|
return nilItem
|
||||||
}
|
}
|
||||||
textRunes := joinTokens(trans)
|
trimmed, colors := ansiProcessor([]byte(transformed))
|
||||||
item := Item{
|
trimmed.Index = int32(index)
|
||||||
index: int32(index),
|
return Item{text: trimmed, colors: colors, origText: &data}
|
||||||
origText: &data,
|
|
||||||
colors: nil}
|
|
||||||
|
|
||||||
trimmed, colors := ansiProcessorRunes(textRunes)
|
|
||||||
item.text = trimmed
|
|
||||||
item.colors = colors
|
|
||||||
return &item
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,8 +152,8 @@ func Run(opts *Options) {
|
|||||||
reader := Reader{
|
reader := Reader{
|
||||||
func(runes []byte) bool {
|
func(runes []byte) bool {
|
||||||
item := chunkList.trans(runes, 0)
|
item := chunkList.trans(runes, 0)
|
||||||
if item != nil {
|
if !item.Nil() {
|
||||||
if result, _, _ := pattern.MatchItem(item, false, slab); result != nil {
|
if result, _, _ := pattern.MatchItem(&item, false, slab); result != nil {
|
||||||
opts.Printer(item.text.ToString())
|
opts.Printer(item.text.ToString())
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
@@ -222,7 +212,7 @@ func Run(opts *Options) {
|
|||||||
case EvtReadNew, EvtReadFin:
|
case EvtReadNew, EvtReadFin:
|
||||||
reading = reading && evt == EvtReadNew
|
reading = reading && evt == EvtReadNew
|
||||||
snapshot, count := chunkList.Snapshot()
|
snapshot, count := chunkList.Snapshot()
|
||||||
terminal.UpdateCount(count, !reading)
|
terminal.UpdateCount(count, !reading, value.(bool))
|
||||||
matcher.Reset(snapshot, terminal.Input(), false, !reading, sort)
|
matcher.Reset(snapshot, terminal.Input(), false, !reading, sort)
|
||||||
|
|
||||||
case EvtSearchNew:
|
case EvtSearchNew:
|
||||||
|
|||||||
18
src/deps
18
src/deps
@@ -1,18 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
if [ -z "$GOPATH" ]; then
|
|
||||||
echo '$GOPATH not defined'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
reset() (
|
|
||||||
cd "$GOPATH/src/$1"
|
|
||||||
export GIT_DIR="$(pwd)/.git"
|
|
||||||
[ "$(git rev-parse HEAD)" = "$2" ] ||
|
|
||||||
(git fetch && git reset --hard "$2")
|
|
||||||
)
|
|
||||||
|
|
||||||
reset github.com/junegunn/go-isatty 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8
|
|
||||||
reset github.com/junegunn/go-runewidth 14207d285c6c197daabb5c9793d63e7af9ab2d50
|
|
||||||
reset github.com/junegunn/go-shellwords 02e3cf038dcea8290e44424da473dd12be796a8a
|
|
||||||
reset golang.org/x/crypto abc5fa7ad02123a41f02bf1391c9760f7586e608
|
|
||||||
23
src/item.go
23
src/item.go
@@ -4,18 +4,27 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Item represents each input line
|
// Item represents each input line. 56 bytes.
|
||||||
type Item struct {
|
type Item struct {
|
||||||
index int32
|
text util.Chars // 32 = 24 + 1 + 1 + 2 + 4
|
||||||
text util.Chars
|
transformed *[]Token // 8
|
||||||
origText *[]byte
|
origText *[]byte // 8
|
||||||
colors *[]ansiOffset
|
colors *[]ansiOffset // 8
|
||||||
transformed []Token
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Index returns ordinal index of the Item
|
// Index returns ordinal index of the Item
|
||||||
func (item *Item) Index() int32 {
|
func (item *Item) Index() int32 {
|
||||||
return item.index
|
return item.text.Index
|
||||||
|
}
|
||||||
|
|
||||||
|
var nilItem = Item{text: util.Chars{Index: -1}}
|
||||||
|
|
||||||
|
func (item *Item) Nil() bool {
|
||||||
|
return item.Index() < 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (item *Item) TrimLength() uint16 {
|
||||||
|
return item.text.TrimLength()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Colors returns ansiOffsets of the Item
|
// Colors returns ansiOffsets of the Item
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ func (m *Matcher) sliceChunks(chunks []*Chunk) [][]*Chunk {
|
|||||||
|
|
||||||
type partialResult struct {
|
type partialResult struct {
|
||||||
index int
|
index int
|
||||||
matches []*Result
|
matches []Result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
||||||
@@ -162,7 +162,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
|||||||
go func(idx int, slab *util.Slab, chunks []*Chunk) {
|
go func(idx int, slab *util.Slab, chunks []*Chunk) {
|
||||||
defer func() { waitGroup.Done() }()
|
defer func() { waitGroup.Done() }()
|
||||||
count := 0
|
count := 0
|
||||||
allMatches := make([][]*Result, len(chunks))
|
allMatches := make([][]Result, len(chunks))
|
||||||
for idx, chunk := range chunks {
|
for idx, chunk := range chunks {
|
||||||
matches := request.pattern.Match(chunk, slab)
|
matches := request.pattern.Match(chunk, slab)
|
||||||
allMatches[idx] = matches
|
allMatches[idx] = matches
|
||||||
@@ -172,7 +172,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
|||||||
}
|
}
|
||||||
countChan <- len(matches)
|
countChan <- len(matches)
|
||||||
}
|
}
|
||||||
sliceMatches := make([]*Result, 0, count)
|
sliceMatches := make([]Result, 0, count)
|
||||||
for _, matches := range allMatches {
|
for _, matches := range allMatches {
|
||||||
sliceMatches = append(sliceMatches, matches...)
|
sliceMatches = append(sliceMatches, matches...)
|
||||||
}
|
}
|
||||||
@@ -212,7 +212,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
partialResults := make([][]*Result, numSlices)
|
partialResults := make([][]Result, numSlices)
|
||||||
for _ = range slices {
|
for _ = range slices {
|
||||||
partialResult := <-resultChan
|
partialResult := <-resultChan
|
||||||
partialResults[partialResult.index] = partialResult.matches
|
partialResults[partialResult.index] = partialResult.matches
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ package fzf
|
|||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
// EmptyMerger is a Merger with no data
|
// EmptyMerger is a Merger with no data
|
||||||
var EmptyMerger = NewMerger(nil, [][]*Result{}, false, false)
|
var EmptyMerger = NewMerger(nil, [][]Result{}, false, false)
|
||||||
|
|
||||||
// Merger holds a set of locally sorted lists of items and provides the view of
|
// Merger holds a set of locally sorted lists of items and provides the view of
|
||||||
// a single, globally-sorted list
|
// a single, globally-sorted list
|
||||||
type Merger struct {
|
type Merger struct {
|
||||||
pattern *Pattern
|
pattern *Pattern
|
||||||
lists [][]*Result
|
lists [][]Result
|
||||||
merged []*Result
|
merged []Result
|
||||||
chunks *[]*Chunk
|
chunks *[]*Chunk
|
||||||
cursors []int
|
cursors []int
|
||||||
sorted bool
|
sorted bool
|
||||||
@@ -35,11 +35,11 @@ func PassMerger(chunks *[]*Chunk, tac bool) *Merger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewMerger returns a new Merger
|
// NewMerger returns a new Merger
|
||||||
func NewMerger(pattern *Pattern, lists [][]*Result, sorted bool, tac bool) *Merger {
|
func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool) *Merger {
|
||||||
mg := Merger{
|
mg := Merger{
|
||||||
pattern: pattern,
|
pattern: pattern,
|
||||||
lists: lists,
|
lists: lists,
|
||||||
merged: []*Result{},
|
merged: []Result{},
|
||||||
chunks: nil,
|
chunks: nil,
|
||||||
cursors: make([]int, len(lists)),
|
cursors: make([]int, len(lists)),
|
||||||
sorted: sorted,
|
sorted: sorted,
|
||||||
@@ -59,13 +59,13 @@ func (mg *Merger) Length() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the pointer to the Result object indexed by the given integer
|
// Get returns the pointer to the Result object indexed by the given integer
|
||||||
func (mg *Merger) Get(idx int) *Result {
|
func (mg *Merger) Get(idx int) Result {
|
||||||
if mg.chunks != nil {
|
if mg.chunks != nil {
|
||||||
if mg.tac {
|
if mg.tac {
|
||||||
idx = mg.count - idx - 1
|
idx = mg.count - idx - 1
|
||||||
}
|
}
|
||||||
chunk := (*mg.chunks)[idx/chunkSize]
|
chunk := (*mg.chunks)[idx/chunkSize]
|
||||||
return &Result{item: (*chunk)[idx%chunkSize]}
|
return Result{item: &(*chunk)[idx%chunkSize]}
|
||||||
}
|
}
|
||||||
|
|
||||||
if mg.sorted {
|
if mg.sorted {
|
||||||
@@ -89,7 +89,7 @@ func (mg *Merger) cacheable() bool {
|
|||||||
return mg.count < mergerCacheMax
|
return mg.count < mergerCacheMax
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mg *Merger) mergedGet(idx int) *Result {
|
func (mg *Merger) mergedGet(idx int) Result {
|
||||||
for i := len(mg.merged); i <= idx; i++ {
|
for i := len(mg.merged); i <= idx; i++ {
|
||||||
minRank := minRank()
|
minRank := minRank()
|
||||||
minIdx := -1
|
minIdx := -1
|
||||||
@@ -100,7 +100,7 @@ func (mg *Merger) mergedGet(idx int) *Result {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if cursor >= 0 {
|
if cursor >= 0 {
|
||||||
rank := list[cursor].rank
|
rank := list[cursor]
|
||||||
if minIdx < 0 || compareRanks(rank, minRank, mg.tac) {
|
if minIdx < 0 || compareRanks(rank, minRank, mg.tac) {
|
||||||
minRank = rank
|
minRank = rank
|
||||||
minIdx = listIdx
|
minIdx = listIdx
|
||||||
|
|||||||
@@ -15,11 +15,11 @@ func assert(t *testing.T, cond bool, msg ...string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func randResult() *Result {
|
func randResult() Result {
|
||||||
str := fmt.Sprintf("%d", rand.Uint32())
|
str := fmt.Sprintf("%d", rand.Uint32())
|
||||||
return &Result{
|
chars := util.RunesToChars([]rune(str))
|
||||||
item: &Item{text: util.RunesToChars([]rune(str))},
|
chars.Index = rand.Int31()
|
||||||
rank: rank{index: rand.Int31()}}
|
return Result{item: &Item{text: chars}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEmptyMerger(t *testing.T) {
|
func TestEmptyMerger(t *testing.T) {
|
||||||
@@ -29,14 +29,14 @@ func TestEmptyMerger(t *testing.T) {
|
|||||||
assert(t, len(EmptyMerger.merged) == 0, "Invalid merged list")
|
assert(t, len(EmptyMerger.merged) == 0, "Invalid merged list")
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildLists(partiallySorted bool) ([][]*Result, []*Result) {
|
func buildLists(partiallySorted bool) ([][]Result, []Result) {
|
||||||
numLists := 4
|
numLists := 4
|
||||||
lists := make([][]*Result, numLists)
|
lists := make([][]Result, numLists)
|
||||||
cnt := 0
|
cnt := 0
|
||||||
for i := 0; i < numLists; i++ {
|
for i := 0; i < numLists; i++ {
|
||||||
numResults := rand.Int() % 20
|
numResults := rand.Int() % 20
|
||||||
cnt += numResults
|
cnt += numResults
|
||||||
lists[i] = make([]*Result, numResults)
|
lists[i] = make([]Result, numResults)
|
||||||
for j := 0; j < numResults; j++ {
|
for j := 0; j < numResults; j++ {
|
||||||
item := randResult()
|
item := randResult()
|
||||||
lists[i][j] = item
|
lists[i][j] = item
|
||||||
@@ -45,7 +45,7 @@ func buildLists(partiallySorted bool) ([][]*Result, []*Result) {
|
|||||||
sort.Sort(ByRelevance(lists[i]))
|
sort.Sort(ByRelevance(lists[i]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
items := []*Result{}
|
items := []Result{}
|
||||||
for _, list := range lists {
|
for _, list := range lists {
|
||||||
items = append(items, list...)
|
items = append(items, list...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/tui"
|
"github.com/junegunn/fzf/src/tui"
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
|
||||||
"github.com/junegunn/go-shellwords"
|
"github.com/mattn/go-shellwords"
|
||||||
)
|
)
|
||||||
|
|
||||||
const usage = `usage: fzf [options]
|
const usage = `usage: fzf [options]
|
||||||
@@ -400,6 +400,8 @@ func parseKeyChords(str string, message string) map[int]string {
|
|||||||
chord = tui.BSpace
|
chord = tui.BSpace
|
||||||
case "ctrl-space":
|
case "ctrl-space":
|
||||||
chord = tui.CtrlSpace
|
chord = tui.CtrlSpace
|
||||||
|
case "change":
|
||||||
|
chord = tui.Change
|
||||||
case "alt-enter", "alt-return":
|
case "alt-enter", "alt-return":
|
||||||
chord = tui.CtrlAltM
|
chord = tui.CtrlAltM
|
||||||
case "alt-space":
|
case "alt-space":
|
||||||
@@ -714,6 +716,8 @@ func parseKeymap(keymap map[int][]action, str string) {
|
|||||||
appendAction(actDown)
|
appendAction(actDown)
|
||||||
case "up":
|
case "up":
|
||||||
appendAction(actUp)
|
appendAction(actUp)
|
||||||
|
case "top":
|
||||||
|
appendAction(actTop)
|
||||||
case "page-up":
|
case "page-up":
|
||||||
appendAction(actPageUp)
|
appendAction(actPageUp)
|
||||||
case "page-down":
|
case "page-down":
|
||||||
@@ -959,6 +963,8 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.FuzzyAlgo = parseAlgo(nextString(allArgs, &i, "algorithm required (v1|v2)"))
|
opts.FuzzyAlgo = parseAlgo(nextString(allArgs, &i, "algorithm required (v1|v2)"))
|
||||||
case "--expect":
|
case "--expect":
|
||||||
opts.Expect = parseKeyChords(nextString(allArgs, &i, "key names required"), "key names required")
|
opts.Expect = parseKeyChords(nextString(allArgs, &i, "key names required"), "key names required")
|
||||||
|
case "--no-expect":
|
||||||
|
opts.Expect = make(map[int]string)
|
||||||
case "--tiebreak":
|
case "--tiebreak":
|
||||||
opts.Criteria = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
|
opts.Criteria = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
|
||||||
case "--bind":
|
case "--bind":
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/tui"
|
"github.com/junegunn/fzf/src/tui"
|
||||||
"github.com/junegunn/fzf/src/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDelimiterRegex(t *testing.T) {
|
func TestDelimiterRegex(t *testing.T) {
|
||||||
@@ -44,7 +43,7 @@ func TestDelimiterRegex(t *testing.T) {
|
|||||||
|
|
||||||
func TestDelimiterRegexString(t *testing.T) {
|
func TestDelimiterRegexString(t *testing.T) {
|
||||||
delim := delimiterRegexp("*")
|
delim := delimiterRegexp("*")
|
||||||
tokens := Tokenize(util.RunesToChars([]rune("-*--*---**---")), delim)
|
tokens := Tokenize("-*--*---**---", delim)
|
||||||
if delim.regex != nil ||
|
if delim.regex != nil ||
|
||||||
tokens[0].text.ToString() != "-*" ||
|
tokens[0].text.ToString() != "-*" ||
|
||||||
tokens[1].text.ToString() != "--*" ||
|
tokens[1].text.ToString() != "--*" ||
|
||||||
@@ -57,7 +56,7 @@ func TestDelimiterRegexString(t *testing.T) {
|
|||||||
|
|
||||||
func TestDelimiterRegexRegex(t *testing.T) {
|
func TestDelimiterRegexRegex(t *testing.T) {
|
||||||
delim := delimiterRegexp("--\\*")
|
delim := delimiterRegexp("--\\*")
|
||||||
tokens := Tokenize(util.RunesToChars([]rune("-*--*---**---")), delim)
|
tokens := Tokenize("-*--*---**---", delim)
|
||||||
if delim.str != nil ||
|
if delim.str != nil ||
|
||||||
tokens[0].text.ToString() != "-*--*" ||
|
tokens[0].text.ToString() != "-*--*" ||
|
||||||
tokens[1].text.ToString() != "---*" ||
|
tokens[1].text.ToString() != "---*" ||
|
||||||
|
|||||||
@@ -243,31 +243,17 @@ func (p *Pattern) CacheKey() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Match returns the list of matches Items in the given Chunk
|
// Match returns the list of matches Items in the given Chunk
|
||||||
func (p *Pattern) Match(chunk *Chunk, slab *util.Slab) []*Result {
|
func (p *Pattern) Match(chunk *Chunk, slab *util.Slab) []Result {
|
||||||
// ChunkCache: Exact match
|
// ChunkCache: Exact match
|
||||||
cacheKey := p.CacheKey()
|
cacheKey := p.CacheKey()
|
||||||
if p.cacheable {
|
if p.cacheable {
|
||||||
if cached, found := _cache.Find(chunk, cacheKey); found {
|
if cached := _cache.Lookup(chunk, cacheKey); cached != nil {
|
||||||
return cached
|
return cached
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prefix/suffix cache
|
// Prefix/suffix cache
|
||||||
var space []*Result
|
space := _cache.Search(chunk, cacheKey)
|
||||||
Loop:
|
|
||||||
for idx := 1; idx < len(cacheKey); idx++ {
|
|
||||||
// [---------| ] | [ |---------]
|
|
||||||
// [--------| ] | [ |--------]
|
|
||||||
// [-------| ] | [ |-------]
|
|
||||||
prefix := cacheKey[:len(cacheKey)-idx]
|
|
||||||
suffix := cacheKey[idx:]
|
|
||||||
for _, substr := range [2]*string{&prefix, &suffix} {
|
|
||||||
if cached, found := _cache.Find(chunk, *substr); found {
|
|
||||||
space = cached
|
|
||||||
break Loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
matches := p.matchChunk(chunk, space, slab)
|
matches := p.matchChunk(chunk, space, slab)
|
||||||
|
|
||||||
@@ -277,19 +263,19 @@ Loop:
|
|||||||
return matches
|
return matches
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pattern) matchChunk(chunk *Chunk, space []*Result, slab *util.Slab) []*Result {
|
func (p *Pattern) matchChunk(chunk *Chunk, space []Result, slab *util.Slab) []Result {
|
||||||
matches := []*Result{}
|
matches := []Result{}
|
||||||
|
|
||||||
if space == nil {
|
if space == nil {
|
||||||
for _, item := range *chunk {
|
for idx := range *chunk {
|
||||||
if match, _, _ := p.MatchItem(item, false, slab); match != nil {
|
if match, _, _ := p.MatchItem(&(*chunk)[idx], false, slab); match != nil {
|
||||||
matches = append(matches, match)
|
matches = append(matches, *match)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, result := range space {
|
for _, result := range space {
|
||||||
if match, _, _ := p.MatchItem(result.item, false, slab); match != nil {
|
if match, _, _ := p.MatchItem(result.item, false, slab); match != nil {
|
||||||
matches = append(matches, match)
|
matches = append(matches, *match)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -299,20 +285,22 @@ func (p *Pattern) matchChunk(chunk *Chunk, space []*Result, slab *util.Slab) []*
|
|||||||
// MatchItem returns true if the Item is a match
|
// MatchItem returns true if the Item is a match
|
||||||
func (p *Pattern) MatchItem(item *Item, withPos bool, slab *util.Slab) (*Result, []Offset, *[]int) {
|
func (p *Pattern) MatchItem(item *Item, withPos bool, slab *util.Slab) (*Result, []Offset, *[]int) {
|
||||||
if p.extended {
|
if p.extended {
|
||||||
if offsets, bonus, trimLen, pos := p.extendedMatch(item, withPos, slab); len(offsets) == len(p.termSets) {
|
if offsets, bonus, pos := p.extendedMatch(item, withPos, slab); len(offsets) == len(p.termSets) {
|
||||||
return buildResult(item, offsets, bonus, trimLen), offsets, pos
|
result := buildResult(item, offsets, bonus)
|
||||||
|
return &result, offsets, pos
|
||||||
}
|
}
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
offset, bonus, trimLen, pos := p.basicMatch(item, withPos, slab)
|
offset, bonus, pos := p.basicMatch(item, withPos, slab)
|
||||||
if sidx := offset[0]; sidx >= 0 {
|
if sidx := offset[0]; sidx >= 0 {
|
||||||
offsets := []Offset{offset}
|
offsets := []Offset{offset}
|
||||||
return buildResult(item, offsets, bonus, trimLen), offsets, pos
|
result := buildResult(item, offsets, bonus)
|
||||||
|
return &result, offsets, pos
|
||||||
}
|
}
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset, int, int, *[]int) {
|
func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset, int, *[]int) {
|
||||||
input := p.prepareInput(item)
|
input := p.prepareInput(item)
|
||||||
if p.fuzzy {
|
if p.fuzzy {
|
||||||
return p.iter(p.fuzzyAlgo, input, p.caseSensitive, p.normalize, p.forward, p.text, withPos, slab)
|
return p.iter(p.fuzzyAlgo, input, p.caseSensitive, p.normalize, p.forward, p.text, withPos, slab)
|
||||||
@@ -320,11 +308,10 @@ func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset,
|
|||||||
return p.iter(algo.ExactMatchNaive, input, p.caseSensitive, p.normalize, p.forward, p.text, withPos, slab)
|
return p.iter(algo.ExactMatchNaive, input, p.caseSensitive, p.normalize, p.forward, p.text, withPos, slab)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Offset, int, int, *[]int) {
|
func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Offset, int, *[]int) {
|
||||||
input := p.prepareInput(item)
|
input := p.prepareInput(item)
|
||||||
offsets := []Offset{}
|
offsets := []Offset{}
|
||||||
var totalScore int
|
var totalScore int
|
||||||
var totalTrimLen int
|
|
||||||
var allPos *[]int
|
var allPos *[]int
|
||||||
if withPos {
|
if withPos {
|
||||||
allPos = &[]int{}
|
allPos = &[]int{}
|
||||||
@@ -332,16 +319,15 @@ func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Of
|
|||||||
for _, termSet := range p.termSets {
|
for _, termSet := range p.termSets {
|
||||||
var offset Offset
|
var offset Offset
|
||||||
var currentScore int
|
var currentScore int
|
||||||
var trimLen int
|
|
||||||
matched := false
|
matched := false
|
||||||
for _, term := range termSet {
|
for _, term := range termSet {
|
||||||
pfun := p.procFun[term.typ]
|
pfun := p.procFun[term.typ]
|
||||||
off, score, tLen, pos := p.iter(pfun, input, term.caseSensitive, p.normalize, p.forward, term.text, withPos, slab)
|
off, score, pos := p.iter(pfun, input, term.caseSensitive, p.normalize, p.forward, term.text, withPos, slab)
|
||||||
if sidx := off[0]; sidx >= 0 {
|
if sidx := off[0]; sidx >= 0 {
|
||||||
if term.inv {
|
if term.inv {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
offset, currentScore, trimLen = off, score, tLen
|
offset, currentScore = off, score
|
||||||
matched = true
|
matched = true
|
||||||
if withPos {
|
if withPos {
|
||||||
if pos != nil {
|
if pos != nil {
|
||||||
@@ -354,7 +340,7 @@ func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Of
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
} else if term.inv {
|
} else if term.inv {
|
||||||
offset, currentScore, trimLen = Offset{0, 0}, 0, 0
|
offset, currentScore = Offset{0, 0}, 0
|
||||||
matched = true
|
matched = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -362,29 +348,27 @@ func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Of
|
|||||||
if matched {
|
if matched {
|
||||||
offsets = append(offsets, offset)
|
offsets = append(offsets, offset)
|
||||||
totalScore += currentScore
|
totalScore += currentScore
|
||||||
totalTrimLen += trimLen
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return offsets, totalScore, totalTrimLen, allPos
|
return offsets, totalScore, allPos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pattern) prepareInput(item *Item) []Token {
|
func (p *Pattern) prepareInput(item *Item) []Token {
|
||||||
if item.transformed != nil {
|
if len(p.nth) == 0 {
|
||||||
return item.transformed
|
return []Token{Token{text: &item.text, prefixLength: 0}}
|
||||||
}
|
}
|
||||||
|
|
||||||
var ret []Token
|
if item.transformed != nil {
|
||||||
if len(p.nth) == 0 {
|
return *item.transformed
|
||||||
ret = []Token{Token{text: &item.text, prefixLength: 0, trimLength: int32(item.text.TrimLength())}}
|
|
||||||
} else {
|
|
||||||
tokens := Tokenize(item.text, p.delimiter)
|
|
||||||
ret = Transform(tokens, p.nth)
|
|
||||||
}
|
}
|
||||||
item.transformed = ret
|
|
||||||
|
tokens := Tokenize(item.text.ToString(), p.delimiter)
|
||||||
|
ret := Transform(tokens, p.nth)
|
||||||
|
item.transformed = &ret
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pattern) iter(pfun algo.Algo, tokens []Token, caseSensitive bool, normalize bool, forward bool, pattern []rune, withPos bool, slab *util.Slab) (Offset, int, int, *[]int) {
|
func (p *Pattern) iter(pfun algo.Algo, tokens []Token, caseSensitive bool, normalize bool, forward bool, pattern []rune, withPos bool, slab *util.Slab) (Offset, int, *[]int) {
|
||||||
for _, part := range tokens {
|
for _, part := range tokens {
|
||||||
if res, pos := pfun(caseSensitive, normalize, forward, *part.text, pattern, withPos, slab); res.Start >= 0 {
|
if res, pos := pfun(caseSensitive, normalize, forward, *part.text, pattern, withPos, slab); res.Start >= 0 {
|
||||||
sidx := int32(res.Start) + part.prefixLength
|
sidx := int32(res.Start) + part.prefixLength
|
||||||
@@ -394,8 +378,8 @@ func (p *Pattern) iter(pfun algo.Algo, tokens []Token, caseSensitive bool, norma
|
|||||||
(*pos)[idx] += int(part.prefixLength)
|
(*pos)[idx] += int(part.prefixLength)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Offset{sidx, eidx}, res.Score, int(part.trimLength), pos
|
return Offset{sidx, eidx}, res.Score, pos
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Offset{-1, -1}, 0, -1, nil
|
return Offset{-1, -1}, 0, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,30 +133,30 @@ func TestCaseSensitivity(t *testing.T) {
|
|||||||
|
|
||||||
func TestOrigTextAndTransformed(t *testing.T) {
|
func TestOrigTextAndTransformed(t *testing.T) {
|
||||||
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("jg"))
|
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("jg"))
|
||||||
tokens := Tokenize(util.RunesToChars([]rune("junegunn")), Delimiter{})
|
tokens := Tokenize("junegunn", Delimiter{})
|
||||||
trans := Transform(tokens, []Range{Range{1, 1}})
|
trans := Transform(tokens, []Range{Range{1, 1}})
|
||||||
|
|
||||||
origBytes := []byte("junegunn.choi")
|
origBytes := []byte("junegunn.choi")
|
||||||
for _, extended := range []bool{false, true} {
|
for _, extended := range []bool{false, true} {
|
||||||
chunk := Chunk{
|
chunk := Chunk{
|
||||||
&Item{
|
Item{
|
||||||
text: util.RunesToChars([]rune("junegunn")),
|
text: util.RunesToChars([]rune("junegunn")),
|
||||||
origText: &origBytes,
|
origText: &origBytes,
|
||||||
transformed: trans},
|
transformed: &trans},
|
||||||
}
|
}
|
||||||
pattern.extended = extended
|
pattern.extended = extended
|
||||||
matches := pattern.matchChunk(&chunk, nil, slab) // No cache
|
matches := pattern.matchChunk(&chunk, nil, slab) // No cache
|
||||||
if !(matches[0].item.text.ToString() == "junegunn" &&
|
if !(matches[0].item.text.ToString() == "junegunn" &&
|
||||||
string(*matches[0].item.origText) == "junegunn.choi" &&
|
string(*matches[0].item.origText) == "junegunn.choi" &&
|
||||||
reflect.DeepEqual(matches[0].item.transformed, trans)) {
|
reflect.DeepEqual(*matches[0].item.transformed, trans)) {
|
||||||
t.Error("Invalid match result", matches)
|
t.Error("Invalid match result", matches)
|
||||||
}
|
}
|
||||||
|
|
||||||
match, offsets, pos := pattern.MatchItem(chunk[0], true, slab)
|
match, offsets, pos := pattern.MatchItem(&chunk[0], true, slab)
|
||||||
if !(match.item.text.ToString() == "junegunn" &&
|
if !(match.item.text.ToString() == "junegunn" &&
|
||||||
string(*match.item.origText) == "junegunn.choi" &&
|
string(*match.item.origText) == "junegunn.choi" &&
|
||||||
offsets[0][0] == 0 && offsets[0][1] == 5 &&
|
offsets[0][0] == 0 && offsets[0][1] == 5 &&
|
||||||
reflect.DeepEqual(match.item.transformed, trans)) {
|
reflect.DeepEqual(*match.item.transformed, trans)) {
|
||||||
t.Error("Invalid match result", match, offsets, extended)
|
t.Error("Invalid match result", match, offsets, extended)
|
||||||
}
|
}
|
||||||
if !((*pos)[0] == 4 && (*pos)[1] == 0) {
|
if !((*pos)[0] == 4 && (*pos)[1] == 0) {
|
||||||
|
|||||||
@@ -17,16 +17,17 @@ type Reader struct {
|
|||||||
|
|
||||||
// ReadSource reads data from the default command or from standard input
|
// ReadSource reads data from the default command or from standard input
|
||||||
func (r *Reader) ReadSource() {
|
func (r *Reader) ReadSource() {
|
||||||
|
var success bool
|
||||||
if util.IsTty() {
|
if util.IsTty() {
|
||||||
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
||||||
if len(cmd) == 0 {
|
if len(cmd) == 0 {
|
||||||
cmd = defaultCommand
|
cmd = defaultCommand
|
||||||
}
|
}
|
||||||
r.readFromCommand(cmd)
|
success = r.readFromCommand(cmd)
|
||||||
} else {
|
} else {
|
||||||
r.readFromStdin()
|
success = r.readFromStdin()
|
||||||
}
|
}
|
||||||
r.eventBox.Set(EvtReadFin, nil)
|
r.eventBox.Set(EvtReadFin, success)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) feed(src io.Reader) {
|
func (r *Reader) feed(src io.Reader) {
|
||||||
@@ -50,7 +51,7 @@ func (r *Reader) feed(src io.Reader) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if r.pusher(bytea) {
|
if r.pusher(bytea) {
|
||||||
r.eventBox.Set(EvtReadNew, nil)
|
r.eventBox.Set(EvtReadNew, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -59,20 +60,21 @@ func (r *Reader) feed(src io.Reader) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readFromStdin() {
|
func (r *Reader) readFromStdin() bool {
|
||||||
r.feed(os.Stdin)
|
r.feed(os.Stdin)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readFromCommand(cmd string) {
|
func (r *Reader) readFromCommand(cmd string) bool {
|
||||||
listCommand := util.ExecCommand(cmd)
|
listCommand := util.ExecCommand(cmd)
|
||||||
out, err := listCommand.StdoutPipe()
|
out, err := listCommand.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
err = listCommand.Start()
|
err = listCommand.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
defer listCommand.Wait()
|
|
||||||
r.feed(out)
|
r.feed(out)
|
||||||
|
return listCommand.Wait() == nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,22 +19,17 @@ type colorOffset struct {
|
|||||||
index int32
|
index int32
|
||||||
}
|
}
|
||||||
|
|
||||||
type rank struct {
|
|
||||||
points [4]uint16
|
|
||||||
index int32
|
|
||||||
}
|
|
||||||
|
|
||||||
type Result struct {
|
type Result struct {
|
||||||
item *Item
|
item *Item
|
||||||
rank rank
|
points [4]uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildResult(item *Item, offsets []Offset, score int, trimLen int) *Result {
|
func buildResult(item *Item, offsets []Offset, score int) Result {
|
||||||
if len(offsets) > 1 {
|
if len(offsets) > 1 {
|
||||||
sort.Sort(ByOrder(offsets))
|
sort.Sort(ByOrder(offsets))
|
||||||
}
|
}
|
||||||
|
|
||||||
result := Result{item: item, rank: rank{index: item.index}}
|
result := Result{item: item}
|
||||||
numChars := item.text.Length()
|
numChars := item.text.Length()
|
||||||
minBegin := math.MaxUint16
|
minBegin := math.MaxUint16
|
||||||
minEnd := math.MaxUint16
|
minEnd := math.MaxUint16
|
||||||
@@ -57,8 +52,7 @@ func buildResult(item *Item, offsets []Offset, score int, trimLen int) *Result {
|
|||||||
// Higher is better
|
// Higher is better
|
||||||
val = math.MaxUint16 - util.AsUint16(score)
|
val = math.MaxUint16 - util.AsUint16(score)
|
||||||
case byLength:
|
case byLength:
|
||||||
// If offsets is empty, trimLen will be 0, but we don't care
|
val = item.TrimLength()
|
||||||
val = util.AsUint16(trimLen)
|
|
||||||
case byBegin, byEnd:
|
case byBegin, byEnd:
|
||||||
if validOffsetFound {
|
if validOffsetFound {
|
||||||
whitePrefixLen := 0
|
whitePrefixLen := 0
|
||||||
@@ -72,14 +66,14 @@ func buildResult(item *Item, offsets []Offset, score int, trimLen int) *Result {
|
|||||||
if criterion == byBegin {
|
if criterion == byBegin {
|
||||||
val = util.AsUint16(minEnd - whitePrefixLen)
|
val = util.AsUint16(minEnd - whitePrefixLen)
|
||||||
} else {
|
} else {
|
||||||
val = util.AsUint16(math.MaxUint16 - math.MaxUint16*(maxEnd-whitePrefixLen)/trimLen)
|
val = util.AsUint16(math.MaxUint16 - math.MaxUint16*(maxEnd-whitePrefixLen)/int(item.TrimLength()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.rank.points[idx] = val
|
result.points[idx] = val
|
||||||
}
|
}
|
||||||
|
|
||||||
return &result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort criteria to use. Never changes once fzf is started.
|
// Sort criteria to use. Never changes once fzf is started.
|
||||||
@@ -87,11 +81,11 @@ var sortCriteria []criterion
|
|||||||
|
|
||||||
// Index returns ordinal index of the Item
|
// Index returns ordinal index of the Item
|
||||||
func (result *Result) Index() int32 {
|
func (result *Result) Index() int32 {
|
||||||
return result.item.index
|
return result.item.Index()
|
||||||
}
|
}
|
||||||
|
|
||||||
func minRank() rank {
|
func minRank() Result {
|
||||||
return rank{index: 0, points: [4]uint16{math.MaxUint16, 0, 0, 0}}
|
return Result{item: &nilItem, points: [4]uint16{math.MaxUint16, 0, 0, 0}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme, color tui.ColorPair, attr tui.Attr, current bool) []colorOffset {
|
func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme, color tui.ColorPair, attr tui.Attr, current bool) []colorOffset {
|
||||||
@@ -202,7 +196,7 @@ func (a ByOrder) Less(i, j int) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ByRelevance is for sorting Items
|
// ByRelevance is for sorting Items
|
||||||
type ByRelevance []*Result
|
type ByRelevance []Result
|
||||||
|
|
||||||
func (a ByRelevance) Len() int {
|
func (a ByRelevance) Len() int {
|
||||||
return len(a)
|
return len(a)
|
||||||
@@ -213,11 +207,11 @@ func (a ByRelevance) Swap(i, j int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a ByRelevance) Less(i, j int) bool {
|
func (a ByRelevance) Less(i, j int) bool {
|
||||||
return compareRanks((*a[i]).rank, (*a[j]).rank, false)
|
return compareRanks(a[i], a[j], false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ByRelevanceTac is for sorting Items
|
// ByRelevanceTac is for sorting Items
|
||||||
type ByRelevanceTac []*Result
|
type ByRelevanceTac []Result
|
||||||
|
|
||||||
func (a ByRelevanceTac) Len() int {
|
func (a ByRelevanceTac) Len() int {
|
||||||
return len(a)
|
return len(a)
|
||||||
@@ -228,10 +222,10 @@ func (a ByRelevanceTac) Swap(i, j int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a ByRelevanceTac) Less(i, j int) bool {
|
func (a ByRelevanceTac) Less(i, j int) bool {
|
||||||
return compareRanks((*a[i]).rank, (*a[j]).rank, true)
|
return compareRanks(a[i], a[j], true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func compareRanks(irank rank, jrank rank, tac bool) bool {
|
func compareRanks(irank Result, jrank Result, tac bool) bool {
|
||||||
for idx := 0; idx < 4; idx++ {
|
for idx := 0; idx < 4; idx++ {
|
||||||
left := irank.points[idx]
|
left := irank.points[idx]
|
||||||
right := jrank.points[idx]
|
right := jrank.points[idx]
|
||||||
@@ -241,5 +235,5 @@ func compareRanks(irank rank, jrank rank, tac bool) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (irank.index <= jrank.index) != tac
|
return (irank.item.Index() <= jrank.item.Index()) != tac
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func withIndex(i *Item, index int) *Item {
|
||||||
|
(*i).text.Index = int32(index)
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
func TestOffsetSort(t *testing.T) {
|
func TestOffsetSort(t *testing.T) {
|
||||||
offsets := []Offset{
|
offsets := []Offset{
|
||||||
Offset{3, 5}, Offset{2, 7},
|
Offset{3, 5}, Offset{2, 7},
|
||||||
@@ -26,10 +31,10 @@ func TestOffsetSort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRankComparison(t *testing.T) {
|
func TestRankComparison(t *testing.T) {
|
||||||
rank := func(vals ...uint16) rank {
|
rank := func(vals ...uint16) Result {
|
||||||
return rank{
|
return Result{
|
||||||
points: [4]uint16{vals[0], vals[1], vals[2], vals[3]},
|
points: [4]uint16{vals[0], vals[1], vals[2], vals[3]},
|
||||||
index: int32(vals[4])}
|
item: &Item{text: util.Chars{Index: int32(vals[4])}}}
|
||||||
}
|
}
|
||||||
if compareRanks(rank(3, 0, 0, 0, 5), rank(2, 0, 0, 0, 7), false) ||
|
if compareRanks(rank(3, 0, 0, 0, 5), rank(2, 0, 0, 0, 7), false) ||
|
||||||
!compareRanks(rank(3, 0, 0, 0, 5), rank(3, 0, 0, 0, 6), false) ||
|
!compareRanks(rank(3, 0, 0, 0, 5), rank(3, 0, 0, 0, 6), false) ||
|
||||||
@@ -52,36 +57,41 @@ func TestResultRank(t *testing.T) {
|
|||||||
sortCriteria = []criterion{byScore, byLength}
|
sortCriteria = []criterion{byScore, byLength}
|
||||||
|
|
||||||
strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")}
|
strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")}
|
||||||
item1 := buildResult(&Item{text: util.RunesToChars(strs[0]), index: 1}, []Offset{}, 2, 3)
|
item1 := buildResult(
|
||||||
if item1.rank.points[0] != math.MaxUint16-2 || // Bonus
|
withIndex(&Item{text: util.RunesToChars(strs[0])}, 1), []Offset{}, 2)
|
||||||
item1.rank.points[1] != 3 || // Length
|
if item1.points[0] != math.MaxUint16-2 || // Bonus
|
||||||
item1.rank.points[2] != 0 || // Unused
|
item1.points[1] != 3 || // Length
|
||||||
item1.rank.points[3] != 0 || // Unused
|
item1.points[2] != 0 || // Unused
|
||||||
item1.item.index != 1 {
|
item1.points[3] != 0 || // Unused
|
||||||
t.Error(item1.rank)
|
item1.item.Index() != 1 {
|
||||||
|
t.Error(item1)
|
||||||
}
|
}
|
||||||
// Only differ in index
|
// Only differ in index
|
||||||
item2 := buildResult(&Item{text: util.RunesToChars(strs[0])}, []Offset{}, 2, 3)
|
item2 := buildResult(&Item{text: util.RunesToChars(strs[0])}, []Offset{}, 2)
|
||||||
|
|
||||||
items := []*Result{item1, item2}
|
items := []Result{item1, item2}
|
||||||
sort.Sort(ByRelevance(items))
|
sort.Sort(ByRelevance(items))
|
||||||
if items[0] != item2 || items[1] != item1 {
|
if items[0] != item2 || items[1] != item1 {
|
||||||
t.Error(items)
|
t.Error(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
items = []*Result{item2, item1, item1, item2}
|
items = []Result{item2, item1, item1, item2}
|
||||||
sort.Sort(ByRelevance(items))
|
sort.Sort(ByRelevance(items))
|
||||||
if items[0] != item2 || items[1] != item2 ||
|
if items[0] != item2 || items[1] != item2 ||
|
||||||
items[2] != item1 || items[3] != item1 {
|
items[2] != item1 || items[3] != item1 {
|
||||||
t.Error(items, item1, item1.item.index, item2, item2.item.index)
|
t.Error(items, item1, item1.item.Index(), item2, item2.item.Index())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by relevance
|
// Sort by relevance
|
||||||
item3 := buildResult(&Item{index: 2}, []Offset{Offset{1, 3}, Offset{5, 7}}, 3, 0)
|
item3 := buildResult(
|
||||||
item4 := buildResult(&Item{index: 2}, []Offset{Offset{1, 2}, Offset{6, 7}}, 4, 0)
|
withIndex(&Item{}, 2), []Offset{Offset{1, 3}, Offset{5, 7}}, 3)
|
||||||
item5 := buildResult(&Item{index: 2}, []Offset{Offset{1, 3}, Offset{5, 7}}, 5, 0)
|
item4 := buildResult(
|
||||||
item6 := buildResult(&Item{index: 2}, []Offset{Offset{1, 2}, Offset{6, 7}}, 6, 0)
|
withIndex(&Item{}, 2), []Offset{Offset{1, 2}, Offset{6, 7}}, 4)
|
||||||
items = []*Result{item1, item2, item3, item4, item5, item6}
|
item5 := buildResult(
|
||||||
|
withIndex(&Item{}, 2), []Offset{Offset{1, 3}, Offset{5, 7}}, 5)
|
||||||
|
item6 := buildResult(
|
||||||
|
withIndex(&Item{}, 2), []Offset{Offset{1, 2}, Offset{6, 7}}, 6)
|
||||||
|
items = []Result{item1, item2, item3, item4, item5, item6}
|
||||||
sort.Sort(ByRelevance(items))
|
sort.Sort(ByRelevance(items))
|
||||||
if !(items[0] == item6 && items[1] == item5 &&
|
if !(items[0] == item6 && items[1] == item5 &&
|
||||||
items[2] == item4 && items[3] == item3 &&
|
items[2] == item4 && items[3] == item3 &&
|
||||||
|
|||||||
103
src/terminal.go
103
src/terminal.go
@@ -58,6 +58,7 @@ type Terminal struct {
|
|||||||
initDelay time.Duration
|
initDelay time.Duration
|
||||||
inlineInfo bool
|
inlineInfo bool
|
||||||
prompt string
|
prompt string
|
||||||
|
promptLen int
|
||||||
reverse bool
|
reverse bool
|
||||||
fullscreen bool
|
fullscreen bool
|
||||||
hscroll bool
|
hscroll bool
|
||||||
@@ -86,6 +87,7 @@ type Terminal struct {
|
|||||||
margin [4]sizeSpec
|
margin [4]sizeSpec
|
||||||
strong tui.Attr
|
strong tui.Attr
|
||||||
bordered bool
|
bordered bool
|
||||||
|
cleanExit bool
|
||||||
border tui.Window
|
border tui.Window
|
||||||
window tui.Window
|
window tui.Window
|
||||||
pborder tui.Window
|
pborder tui.Window
|
||||||
@@ -93,6 +95,7 @@ type Terminal struct {
|
|||||||
count int
|
count int
|
||||||
progress int
|
progress int
|
||||||
reading bool
|
reading bool
|
||||||
|
success bool
|
||||||
jumping jumpMode
|
jumping jumpMode
|
||||||
jumpLabels string
|
jumpLabels string
|
||||||
printer func(string)
|
printer func(string)
|
||||||
@@ -133,7 +136,6 @@ func (a byTimeOrder) Less(i, j int) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var _spinner = []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
|
var _spinner = []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
|
||||||
var _tabStop int
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
reqPrompt util.EventType = iota
|
reqPrompt util.EventType = iota
|
||||||
@@ -213,6 +215,7 @@ const (
|
|||||||
actExecuteSilent
|
actExecuteSilent
|
||||||
actExecuteMulti // Deprecated
|
actExecuteMulti // Deprecated
|
||||||
actSigStop
|
actSigStop
|
||||||
|
actTop
|
||||||
)
|
)
|
||||||
|
|
||||||
func toActions(types ...actionType) []action {
|
func toActions(types ...actionType) []action {
|
||||||
@@ -339,10 +342,9 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
wordRubout = fmt.Sprintf("%s[^%s]", sep, sep)
|
wordRubout = fmt.Sprintf("%s[^%s]", sep, sep)
|
||||||
wordNext = fmt.Sprintf("[^%s]%s|(.$)", sep, sep)
|
wordNext = fmt.Sprintf("[^%s]%s|(.$)", sep, sep)
|
||||||
}
|
}
|
||||||
return &Terminal{
|
t := Terminal{
|
||||||
initDelay: delay,
|
initDelay: delay,
|
||||||
inlineInfo: opts.InlineInfo,
|
inlineInfo: opts.InlineInfo,
|
||||||
prompt: opts.Prompt,
|
|
||||||
reverse: opts.Reverse,
|
reverse: opts.Reverse,
|
||||||
fullscreen: fullscreen,
|
fullscreen: fullscreen,
|
||||||
hscroll: opts.Hscroll,
|
hscroll: opts.Hscroll,
|
||||||
@@ -365,6 +367,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
history: opts.History,
|
history: opts.History,
|
||||||
margin: opts.Margin,
|
margin: opts.Margin,
|
||||||
bordered: opts.Bordered,
|
bordered: opts.Bordered,
|
||||||
|
cleanExit: opts.ClearOnExit,
|
||||||
strong: strongAttr,
|
strong: strongAttr,
|
||||||
cycle: opts.Cycle,
|
cycle: opts.Cycle,
|
||||||
header: header,
|
header: header,
|
||||||
@@ -372,6 +375,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
ansi: opts.Ansi,
|
ansi: opts.Ansi,
|
||||||
tabstop: opts.Tabstop,
|
tabstop: opts.Tabstop,
|
||||||
reading: true,
|
reading: true,
|
||||||
|
success: true,
|
||||||
jumping: jumpDisabled,
|
jumping: jumpDisabled,
|
||||||
jumpLabels: opts.JumpLabels,
|
jumpLabels: opts.JumpLabels,
|
||||||
printer: opts.Printer,
|
printer: opts.Printer,
|
||||||
@@ -389,6 +393,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
startChan: make(chan bool, 1),
|
startChan: make(chan bool, 1),
|
||||||
tui: renderer,
|
tui: renderer,
|
||||||
initFunc: func() { renderer.Init() }}
|
initFunc: func() { renderer.Init() }}
|
||||||
|
t.prompt, t.promptLen = t.processTabs([]rune(opts.Prompt), 0)
|
||||||
|
return &t
|
||||||
}
|
}
|
||||||
|
|
||||||
// Input returns current query string
|
// Input returns current query string
|
||||||
@@ -399,10 +405,11 @@ func (t *Terminal) Input() []rune {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateCount updates the count information
|
// UpdateCount updates the count information
|
||||||
func (t *Terminal) UpdateCount(cnt int, final bool) {
|
func (t *Terminal) UpdateCount(cnt int, final bool, success bool) {
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
t.count = cnt
|
t.count = cnt
|
||||||
t.reading = !final
|
t.reading = !final
|
||||||
|
t.success = success
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
t.reqBox.Set(reqInfo, nil)
|
t.reqBox.Set(reqInfo, nil)
|
||||||
if final {
|
if final {
|
||||||
@@ -632,7 +639,7 @@ func (t *Terminal) move(y int, x int, clear bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) placeCursor() {
|
func (t *Terminal) placeCursor() {
|
||||||
t.move(0, t.displayWidth([]rune(t.prompt))+t.displayWidth(t.input[:t.cx]), false)
|
t.move(0, t.promptLen+t.displayWidth(t.input[:t.cx]), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) printPrompt() {
|
func (t *Terminal) printPrompt() {
|
||||||
@@ -644,7 +651,7 @@ func (t *Terminal) printPrompt() {
|
|||||||
func (t *Terminal) printInfo() {
|
func (t *Terminal) printInfo() {
|
||||||
pos := 0
|
pos := 0
|
||||||
if t.inlineInfo {
|
if t.inlineInfo {
|
||||||
pos = t.displayWidth([]rune(t.prompt)) + t.displayWidth(t.input) + 1
|
pos = t.promptLen + t.displayWidth(t.input) + 1
|
||||||
if pos+len(" < ") > t.window.Width() {
|
if pos+len(" < ") > t.window.Width() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -680,6 +687,9 @@ func (t *Terminal) printInfo() {
|
|||||||
if t.progress > 0 && t.progress < 100 {
|
if t.progress > 0 && t.progress < 100 {
|
||||||
output += fmt.Sprintf(" (%d%%)", t.progress)
|
output += fmt.Sprintf(" (%d%%)", t.progress)
|
||||||
}
|
}
|
||||||
|
if !t.success && t.count == 0 {
|
||||||
|
output += " [ERROR]"
|
||||||
|
}
|
||||||
if pos+len(output) <= t.window.Width() {
|
if pos+len(output) <= t.window.Width() {
|
||||||
t.window.CPrint(tui.ColInfo, 0, output)
|
t.window.CPrint(tui.ColInfo, 0, output)
|
||||||
}
|
}
|
||||||
@@ -706,7 +716,7 @@ func (t *Terminal) printHeader() {
|
|||||||
colors: colors}
|
colors: colors}
|
||||||
|
|
||||||
t.move(line, 2, true)
|
t.move(line, 2, true)
|
||||||
t.printHighlighted(&Result{item: item},
|
t.printHighlighted(Result{item: item},
|
||||||
tui.AttrRegular, tui.ColHeader, tui.ColDefault, false, false)
|
tui.AttrRegular, tui.ColHeader, tui.ColDefault, false, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -734,7 +744,7 @@ func (t *Terminal) printList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) printItem(result *Result, line int, i int, current bool) {
|
func (t *Terminal) printItem(result Result, line int, i int, current bool) {
|
||||||
item := result.item
|
item := result.item
|
||||||
_, selected := t.selected[item.Index()]
|
_, selected := t.selected[item.Index()]
|
||||||
label := " "
|
label := " "
|
||||||
@@ -750,7 +760,7 @@ func (t *Terminal) printItem(result *Result, line int, i int, current bool) {
|
|||||||
|
|
||||||
// Avoid unnecessary redraw
|
// Avoid unnecessary redraw
|
||||||
newLine := itemLine{current: current, selected: selected, label: label,
|
newLine := itemLine{current: current, selected: selected, label: label,
|
||||||
result: *result, queryLen: len(t.input), width: 0}
|
result: result, queryLen: len(t.input), width: 0}
|
||||||
prevLine := t.prevLines[i]
|
prevLine := t.prevLines[i]
|
||||||
if prevLine.current == newLine.current &&
|
if prevLine.current == newLine.current &&
|
||||||
prevLine.selected == newLine.selected &&
|
prevLine.selected == newLine.selected &&
|
||||||
@@ -832,7 +842,7 @@ func (t *Terminal) overflow(runes []rune, max int) bool {
|
|||||||
return t.displayWidthWithLimit(runes, 0, max) > max
|
return t.displayWidthWithLimit(runes, 0, max) > max
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) printHighlighted(result *Result, attr tui.Attr, col1 tui.ColorPair, col2 tui.ColorPair, current bool, match bool) int {
|
func (t *Terminal) printHighlighted(result Result, attr tui.Attr, col1 tui.ColorPair, col2 tui.ColorPair, current bool, match bool) int {
|
||||||
item := result.item
|
item := result.item
|
||||||
|
|
||||||
// Overflow
|
// Overflow
|
||||||
@@ -952,6 +962,8 @@ func (t *Terminal) printPreview() {
|
|||||||
}
|
}
|
||||||
reader := bufio.NewReader(strings.NewReader(t.previewer.text))
|
reader := bufio.NewReader(strings.NewReader(t.previewer.text))
|
||||||
lineNo := -t.previewer.offset
|
lineNo := -t.previewer.offset
|
||||||
|
height := t.pwindow.Height()
|
||||||
|
var ansi *ansiState
|
||||||
for {
|
for {
|
||||||
line, err := reader.ReadString('\n')
|
line, err := reader.ReadString('\n')
|
||||||
eof := err == io.EOF
|
eof := err == io.EOF
|
||||||
@@ -959,11 +971,12 @@ func (t *Terminal) printPreview() {
|
|||||||
line = line[:len(line)-1]
|
line = line[:len(line)-1]
|
||||||
}
|
}
|
||||||
lineNo++
|
lineNo++
|
||||||
if lineNo > t.pwindow.Height() {
|
if lineNo > height ||
|
||||||
|
t.pwindow.Y() == height-1 && t.pwindow.X() > 0 {
|
||||||
break
|
break
|
||||||
} else if lineNo > 0 {
|
} else if lineNo > 0 {
|
||||||
var fillRet tui.FillReturn
|
var fillRet tui.FillReturn
|
||||||
extractColor(line, nil, func(str string, ansi *ansiState) bool {
|
_, _, ansi = extractColor(line, ansi, func(str string, ansi *ansiState) bool {
|
||||||
trimmed := []rune(str)
|
trimmed := []rune(str)
|
||||||
if !t.preview.wrap {
|
if !t.preview.wrap {
|
||||||
trimmed, _ = t.trimRight(trimmed, maxWidth-t.pwindow.X())
|
trimmed, _ = t.trimRight(trimmed, maxWidth-t.pwindow.X())
|
||||||
@@ -989,7 +1002,7 @@ func (t *Terminal) printPreview() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.pwindow.FinishFill()
|
t.pwindow.FinishFill()
|
||||||
if t.previewer.lines > t.pwindow.Height() {
|
if t.previewer.lines > height {
|
||||||
offset := fmt.Sprintf("%d/%d", t.previewer.offset+1, t.previewer.lines)
|
offset := fmt.Sprintf("%d/%d", t.previewer.offset+1, t.previewer.lines)
|
||||||
pos := t.pwindow.Width() - len(offset)
|
pos := t.pwindow.Width() - len(offset)
|
||||||
if t.tui.DoesAutoWrap() {
|
if t.tui.DoesAutoWrap() {
|
||||||
@@ -1161,7 +1174,7 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, fo
|
|||||||
|
|
||||||
for idx, item := range items {
|
for idx, item := range items {
|
||||||
chars := util.RunesToChars([]rune(item.AsString(stripAnsi)))
|
chars := util.RunesToChars([]rune(item.AsString(stripAnsi)))
|
||||||
tokens := Tokenize(chars, delimiter)
|
tokens := Tokenize(chars.ToString(), delimiter)
|
||||||
trans := Transform(tokens, ranges)
|
trans := Transform(tokens, ranges)
|
||||||
str := string(joinTokens(trans))
|
str := string(joinTokens(trans))
|
||||||
if delimiter.str != nil {
|
if delimiter.str != nil {
|
||||||
@@ -1240,7 +1253,7 @@ func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) truncateQuery() {
|
func (t *Terminal) truncateQuery() {
|
||||||
maxPatternLength := util.Max(1, t.window.Width()-t.displayWidth([]rune(t.prompt))-1)
|
maxPatternLength := util.Max(1, t.window.Width()-t.promptLen-1)
|
||||||
t.input, _ = t.trimRight(t.input, maxPatternLength)
|
t.input, _ = t.trimRight(t.input, maxPatternLength)
|
||||||
t.cx = util.Constrain(t.cx, 0, len(t.input))
|
t.cx = util.Constrain(t.cx, 0, len(t.input))
|
||||||
}
|
}
|
||||||
@@ -1332,7 +1345,12 @@ func (t *Terminal) Loop() {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
exit := func(code int) {
|
exit := func(getCode func() int) {
|
||||||
|
if !t.cleanExit && t.fullscreen && t.inlineInfo {
|
||||||
|
t.placeCursor()
|
||||||
|
}
|
||||||
|
t.tui.Close()
|
||||||
|
code := getCode()
|
||||||
if code <= exitNoMatch && t.history != nil {
|
if code <= exitNoMatch && t.history != nil {
|
||||||
t.history.append(string(t.input))
|
t.history.append(string(t.input))
|
||||||
}
|
}
|
||||||
@@ -1380,11 +1398,12 @@ func (t *Terminal) Loop() {
|
|||||||
case reqRedraw:
|
case reqRedraw:
|
||||||
t.redraw()
|
t.redraw()
|
||||||
case reqClose:
|
case reqClose:
|
||||||
t.tui.Close()
|
exit(func() int {
|
||||||
if t.output() {
|
if t.output() {
|
||||||
exit(exitOk)
|
return exitOk
|
||||||
}
|
}
|
||||||
exit(exitNoMatch)
|
return exitNoMatch
|
||||||
|
})
|
||||||
case reqPreviewDisplay:
|
case reqPreviewDisplay:
|
||||||
t.previewer.text = value.(string)
|
t.previewer.text = value.(string)
|
||||||
t.previewer.lines = strings.Count(t.previewer.text, "\n")
|
t.previewer.lines = strings.Count(t.previewer.text, "\n")
|
||||||
@@ -1393,12 +1412,12 @@ func (t *Terminal) Loop() {
|
|||||||
case reqPreviewRefresh:
|
case reqPreviewRefresh:
|
||||||
t.printPreview()
|
t.printPreview()
|
||||||
case reqPrintQuery:
|
case reqPrintQuery:
|
||||||
t.tui.Close()
|
exit(func() int {
|
||||||
t.printer(string(t.input))
|
t.printer(string(t.input))
|
||||||
exit(exitOk)
|
return exitOk
|
||||||
|
})
|
||||||
case reqQuit:
|
case reqQuit:
|
||||||
t.tui.Close()
|
exit(func() int { return exitInterrupt })
|
||||||
exit(exitInterrupt)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.placeCursor()
|
t.placeCursor()
|
||||||
@@ -1590,25 +1609,28 @@ func (t *Terminal) Loop() {
|
|||||||
case actToggleDown:
|
case actToggleDown:
|
||||||
if t.multi && t.merger.Length() > 0 {
|
if t.multi && t.merger.Length() > 0 {
|
||||||
toggle()
|
toggle()
|
||||||
t.vmove(-1)
|
t.vmove(-1, true)
|
||||||
req(reqList)
|
req(reqList)
|
||||||
}
|
}
|
||||||
case actToggleUp:
|
case actToggleUp:
|
||||||
if t.multi && t.merger.Length() > 0 {
|
if t.multi && t.merger.Length() > 0 {
|
||||||
toggle()
|
toggle()
|
||||||
t.vmove(1)
|
t.vmove(1, true)
|
||||||
req(reqList)
|
req(reqList)
|
||||||
}
|
}
|
||||||
case actDown:
|
case actDown:
|
||||||
t.vmove(-1)
|
t.vmove(-1, true)
|
||||||
req(reqList)
|
req(reqList)
|
||||||
case actUp:
|
case actUp:
|
||||||
t.vmove(1)
|
t.vmove(1, true)
|
||||||
req(reqList)
|
req(reqList)
|
||||||
case actAccept:
|
case actAccept:
|
||||||
req(reqClose)
|
req(reqClose)
|
||||||
case actClearScreen:
|
case actClearScreen:
|
||||||
req(reqRedraw)
|
req(reqRedraw)
|
||||||
|
case actTop:
|
||||||
|
t.vset(0)
|
||||||
|
req(reqList)
|
||||||
case actUnixLineDiscard:
|
case actUnixLineDiscard:
|
||||||
if t.cx > 0 {
|
if t.cx > 0 {
|
||||||
t.yanked = copySlice(t.input[:t.cx])
|
t.yanked = copySlice(t.input[:t.cx])
|
||||||
@@ -1628,16 +1650,16 @@ func (t *Terminal) Loop() {
|
|||||||
t.input = append(append(t.input[:t.cx], t.yanked...), suffix...)
|
t.input = append(append(t.input[:t.cx], t.yanked...), suffix...)
|
||||||
t.cx += len(t.yanked)
|
t.cx += len(t.yanked)
|
||||||
case actPageUp:
|
case actPageUp:
|
||||||
t.vmove(t.maxItems() - 1)
|
t.vmove(t.maxItems()-1, false)
|
||||||
req(reqList)
|
req(reqList)
|
||||||
case actPageDown:
|
case actPageDown:
|
||||||
t.vmove(-(t.maxItems() - 1))
|
t.vmove(-(t.maxItems() - 1), false)
|
||||||
req(reqList)
|
req(reqList)
|
||||||
case actHalfPageUp:
|
case actHalfPageUp:
|
||||||
t.vmove(t.maxItems() / 2)
|
t.vmove(t.maxItems()/2, false)
|
||||||
req(reqList)
|
req(reqList)
|
||||||
case actHalfPageDown:
|
case actHalfPageDown:
|
||||||
t.vmove(-(t.maxItems() / 2))
|
t.vmove(-(t.maxItems() / 2), false)
|
||||||
req(reqList)
|
req(reqList)
|
||||||
case actJump:
|
case actJump:
|
||||||
t.jumping = jumpEnabled
|
t.jumping = jumpEnabled
|
||||||
@@ -1695,7 +1717,7 @@ func (t *Terminal) Loop() {
|
|||||||
if t.multi && me.Mod {
|
if t.multi && me.Mod {
|
||||||
toggle()
|
toggle()
|
||||||
}
|
}
|
||||||
t.vmove(me.S)
|
t.vmove(me.S, true)
|
||||||
req(reqList)
|
req(reqList)
|
||||||
} else if t.hasPreviewWindow() && t.pwindow.Enclose(my, mx) {
|
} else if t.hasPreviewWindow() && t.pwindow.Enclose(my, mx) {
|
||||||
scrollPreview(-me.S)
|
scrollPreview(-me.S)
|
||||||
@@ -1703,7 +1725,7 @@ func (t *Terminal) Loop() {
|
|||||||
} else if t.window.Enclose(my, mx) {
|
} else if t.window.Enclose(my, mx) {
|
||||||
mx -= t.window.Left()
|
mx -= t.window.Left()
|
||||||
my -= t.window.Top()
|
my -= t.window.Top()
|
||||||
mx = util.Constrain(mx-t.displayWidth([]rune(t.prompt)), 0, len(t.input))
|
mx = util.Constrain(mx-t.promptLen, 0, len(t.input))
|
||||||
if !t.reverse {
|
if !t.reverse {
|
||||||
my = t.window.Height() - my - 1
|
my = t.window.Height() - my - 1
|
||||||
}
|
}
|
||||||
@@ -1749,6 +1771,11 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
t.truncateQuery()
|
t.truncateQuery()
|
||||||
changed = string(previousInput) != string(t.input)
|
changed = string(previousInput) != string(t.input)
|
||||||
|
if onChanges, prs := t.keymap[tui.Change]; changed && prs {
|
||||||
|
if !doActions(onChanges, tui.Change) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if mapkey == tui.Rune {
|
if mapkey == tui.Rune {
|
||||||
if idx := strings.IndexRune(t.jumpLabels, event.Char); idx >= 0 && idx < t.maxItems() && idx < t.merger.Length() {
|
if idx := strings.IndexRune(t.jumpLabels, event.Char); idx >= 0 && idx < t.maxItems() && idx < t.merger.Length() {
|
||||||
@@ -1787,12 +1814,12 @@ func (t *Terminal) constrain() {
|
|||||||
t.offset = util.Max(0, t.offset)
|
t.offset = util.Max(0, t.offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) vmove(o int) {
|
func (t *Terminal) vmove(o int, allowCycle bool) {
|
||||||
if t.reverse {
|
if t.reverse {
|
||||||
o *= -1
|
o *= -1
|
||||||
}
|
}
|
||||||
dest := t.cy + o
|
dest := t.cy + o
|
||||||
if t.cycle {
|
if t.cycle && allowCycle {
|
||||||
max := t.merger.Length() - 1
|
max := t.merger.Length() - 1
|
||||||
if dest > max {
|
if dest > max {
|
||||||
if t.cy == max {
|
if t.cy == max {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -20,7 +21,6 @@ type Range struct {
|
|||||||
type Token struct {
|
type Token struct {
|
||||||
text *util.Chars
|
text *util.Chars
|
||||||
prefixLength int32
|
prefixLength int32
|
||||||
trimLength int32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delimiter for tokenizing the input
|
// Delimiter for tokenizing the input
|
||||||
@@ -75,14 +75,14 @@ func ParseRange(str *string) (Range, bool) {
|
|||||||
return newRange(n, n), true
|
return newRange(n, n), true
|
||||||
}
|
}
|
||||||
|
|
||||||
func withPrefixLengths(tokens []util.Chars, begin int) []Token {
|
func withPrefixLengths(tokens []string, begin int) []Token {
|
||||||
ret := make([]Token, len(tokens))
|
ret := make([]Token, len(tokens))
|
||||||
|
|
||||||
prefixLength := begin
|
prefixLength := begin
|
||||||
for idx, token := range tokens {
|
for idx := range tokens {
|
||||||
// NOTE: &tokens[idx] instead of &tokens
|
chars := util.ToChars([]byte(tokens[idx]))
|
||||||
ret[idx] = Token{&tokens[idx], int32(prefixLength), int32(token.TrimLength())}
|
ret[idx] = Token{&chars, int32(prefixLength)}
|
||||||
prefixLength += token.Length()
|
prefixLength += chars.Length()
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
@@ -93,16 +93,15 @@ const (
|
|||||||
awkWhite
|
awkWhite
|
||||||
)
|
)
|
||||||
|
|
||||||
func awkTokenizer(input util.Chars) ([]util.Chars, int) {
|
func awkTokenizer(input string) ([]string, int) {
|
||||||
// 9, 32
|
// 9, 32
|
||||||
ret := []util.Chars{}
|
ret := []string{}
|
||||||
prefixLength := 0
|
prefixLength := 0
|
||||||
state := awkNil
|
state := awkNil
|
||||||
numChars := input.Length()
|
|
||||||
begin := 0
|
begin := 0
|
||||||
end := 0
|
end := 0
|
||||||
for idx := 0; idx < numChars; idx++ {
|
for idx := 0; idx < len(input); idx++ {
|
||||||
r := input.Get(idx)
|
r := input[idx]
|
||||||
white := r == 9 || r == 32
|
white := r == 9 || r == 32
|
||||||
switch state {
|
switch state {
|
||||||
case awkNil:
|
case awkNil:
|
||||||
@@ -120,19 +119,19 @@ func awkTokenizer(input util.Chars) ([]util.Chars, int) {
|
|||||||
if white {
|
if white {
|
||||||
end = idx + 1
|
end = idx + 1
|
||||||
} else {
|
} else {
|
||||||
ret = append(ret, input.Slice(begin, end))
|
ret = append(ret, input[begin:end])
|
||||||
state, begin, end = awkBlack, idx, idx+1
|
state, begin, end = awkBlack, idx, idx+1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if begin < end {
|
if begin < end {
|
||||||
ret = append(ret, input.Slice(begin, end))
|
ret = append(ret, input[begin:end])
|
||||||
}
|
}
|
||||||
return ret, prefixLength
|
return ret, prefixLength
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tokenize tokenizes the given string with the delimiter
|
// Tokenize tokenizes the given string with the delimiter
|
||||||
func Tokenize(text util.Chars, delimiter Delimiter) []Token {
|
func Tokenize(text string, delimiter Delimiter) []Token {
|
||||||
if delimiter.str == nil && delimiter.regex == nil {
|
if delimiter.str == nil && delimiter.regex == nil {
|
||||||
// AWK-style (\S+\s*)
|
// AWK-style (\S+\s*)
|
||||||
tokens, prefixLength := awkTokenizer(text)
|
tokens, prefixLength := awkTokenizer(text)
|
||||||
@@ -140,36 +139,31 @@ func Tokenize(text util.Chars, delimiter Delimiter) []Token {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if delimiter.str != nil {
|
if delimiter.str != nil {
|
||||||
return withPrefixLengths(text.Split(*delimiter.str), 0)
|
return withPrefixLengths(strings.SplitAfter(text, *delimiter.str), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME performance
|
// FIXME performance
|
||||||
var tokens []string
|
var tokens []string
|
||||||
if delimiter.regex != nil {
|
if delimiter.regex != nil {
|
||||||
str := text.ToString()
|
for len(text) > 0 {
|
||||||
for len(str) > 0 {
|
loc := delimiter.regex.FindStringIndex(text)
|
||||||
loc := delimiter.regex.FindStringIndex(str)
|
|
||||||
if loc == nil {
|
if loc == nil {
|
||||||
loc = []int{0, len(str)}
|
loc = []int{0, len(text)}
|
||||||
}
|
}
|
||||||
last := util.Max(loc[1], 1)
|
last := util.Max(loc[1], 1)
|
||||||
tokens = append(tokens, str[:last])
|
tokens = append(tokens, text[:last])
|
||||||
str = str[last:]
|
text = text[last:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
asRunes := make([]util.Chars, len(tokens))
|
return withPrefixLengths(tokens, 0)
|
||||||
for i, token := range tokens {
|
|
||||||
asRunes[i] = util.RunesToChars([]rune(token))
|
|
||||||
}
|
|
||||||
return withPrefixLengths(asRunes, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func joinTokens(tokens []Token) []rune {
|
func joinTokens(tokens []Token) string {
|
||||||
ret := []rune{}
|
var output bytes.Buffer
|
||||||
for _, token := range tokens {
|
for _, token := range tokens {
|
||||||
ret = append(ret, token.text.ToRunes()...)
|
output.WriteString(token.text.ToString())
|
||||||
}
|
}
|
||||||
return ret
|
return output.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform is used to transform the input when --with-nth option is given
|
// Transform is used to transform the input when --with-nth option is given
|
||||||
@@ -182,7 +176,7 @@ func Transform(tokens []Token, withNth []Range) []Token {
|
|||||||
if r.begin == r.end {
|
if r.begin == r.end {
|
||||||
idx := r.begin
|
idx := r.begin
|
||||||
if idx == rangeEllipsis {
|
if idx == rangeEllipsis {
|
||||||
chars := util.RunesToChars(joinTokens(tokens))
|
chars := util.ToChars([]byte(joinTokens(tokens)))
|
||||||
parts = append(parts, &chars)
|
parts = append(parts, &chars)
|
||||||
} else {
|
} else {
|
||||||
if idx < 0 {
|
if idx < 0 {
|
||||||
@@ -225,15 +219,15 @@ func Transform(tokens []Token, withNth []Range) []Token {
|
|||||||
var merged util.Chars
|
var merged util.Chars
|
||||||
switch len(parts) {
|
switch len(parts) {
|
||||||
case 0:
|
case 0:
|
||||||
merged = util.RunesToChars([]rune{})
|
merged = util.ToChars([]byte{})
|
||||||
case 1:
|
case 1:
|
||||||
merged = *parts[0]
|
merged = *parts[0]
|
||||||
default:
|
default:
|
||||||
runes := []rune{}
|
var output bytes.Buffer
|
||||||
for _, part := range parts {
|
for _, part := range parts {
|
||||||
runes = append(runes, part.ToRunes()...)
|
output.WriteString(part.ToString())
|
||||||
}
|
}
|
||||||
merged = util.RunesToChars(runes)
|
merged = util.ToChars([]byte(output.String()))
|
||||||
}
|
}
|
||||||
|
|
||||||
var prefixLength int32
|
var prefixLength int32
|
||||||
@@ -242,7 +236,7 @@ func Transform(tokens []Token, withNth []Range) []Token {
|
|||||||
} else {
|
} else {
|
||||||
prefixLength = 0
|
prefixLength = 0
|
||||||
}
|
}
|
||||||
transTokens[idx] = Token{&merged, prefixLength, int32(merged.TrimLength())}
|
transTokens[idx] = Token{&merged, prefixLength}
|
||||||
}
|
}
|
||||||
return transTokens
|
return transTokens
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package fzf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseRange(t *testing.T) {
|
func TestParseRange(t *testing.T) {
|
||||||
@@ -47,23 +45,23 @@ func TestParseRange(t *testing.T) {
|
|||||||
func TestTokenize(t *testing.T) {
|
func TestTokenize(t *testing.T) {
|
||||||
// AWK-style
|
// AWK-style
|
||||||
input := " abc: def: ghi "
|
input := " abc: def: ghi "
|
||||||
tokens := Tokenize(util.RunesToChars([]rune(input)), Delimiter{})
|
tokens := Tokenize(input, Delimiter{})
|
||||||
if tokens[0].text.ToString() != "abc: " || tokens[0].prefixLength != 2 || tokens[0].trimLength != 4 {
|
if tokens[0].text.ToString() != "abc: " || tokens[0].prefixLength != 2 {
|
||||||
t.Errorf("%s", tokens)
|
t.Errorf("%s", tokens)
|
||||||
}
|
}
|
||||||
|
|
||||||
// With delimiter
|
// With delimiter
|
||||||
tokens = Tokenize(util.RunesToChars([]rune(input)), delimiterRegexp(":"))
|
tokens = Tokenize(input, delimiterRegexp(":"))
|
||||||
if tokens[0].text.ToString() != " abc:" || tokens[0].prefixLength != 0 || tokens[0].trimLength != 4 {
|
if tokens[0].text.ToString() != " abc:" || tokens[0].prefixLength != 0 {
|
||||||
t.Errorf("%s", tokens)
|
t.Error(tokens[0].text.ToString(), tokens[0].prefixLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
// With delimiter regex
|
// With delimiter regex
|
||||||
tokens = Tokenize(util.RunesToChars([]rune(input)), delimiterRegexp("\\s+"))
|
tokens = Tokenize(input, delimiterRegexp("\\s+"))
|
||||||
if tokens[0].text.ToString() != " " || tokens[0].prefixLength != 0 || tokens[0].trimLength != 0 ||
|
if tokens[0].text.ToString() != " " || tokens[0].prefixLength != 0 ||
|
||||||
tokens[1].text.ToString() != "abc: " || tokens[1].prefixLength != 2 || tokens[1].trimLength != 4 ||
|
tokens[1].text.ToString() != "abc: " || tokens[1].prefixLength != 2 ||
|
||||||
tokens[2].text.ToString() != "def: " || tokens[2].prefixLength != 8 || tokens[2].trimLength != 4 ||
|
tokens[2].text.ToString() != "def: " || tokens[2].prefixLength != 8 ||
|
||||||
tokens[3].text.ToString() != "ghi " || tokens[3].prefixLength != 14 || tokens[3].trimLength != 3 {
|
tokens[3].text.ToString() != "ghi " || tokens[3].prefixLength != 14 {
|
||||||
t.Errorf("%s", tokens)
|
t.Errorf("%s", tokens)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,7 +69,7 @@ func TestTokenize(t *testing.T) {
|
|||||||
func TestTransform(t *testing.T) {
|
func TestTransform(t *testing.T) {
|
||||||
input := " abc: def: ghi: jkl"
|
input := " abc: def: ghi: jkl"
|
||||||
{
|
{
|
||||||
tokens := Tokenize(util.RunesToChars([]rune(input)), Delimiter{})
|
tokens := Tokenize(input, Delimiter{})
|
||||||
{
|
{
|
||||||
ranges := splitNth("1,2,3")
|
ranges := splitNth("1,2,3")
|
||||||
tx := Transform(tokens, ranges)
|
tx := Transform(tokens, ranges)
|
||||||
@@ -93,7 +91,7 @@ func TestTransform(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
tokens := Tokenize(util.RunesToChars([]rune(input)), delimiterRegexp(":"))
|
tokens := Tokenize(input, delimiterRegexp(":"))
|
||||||
{
|
{
|
||||||
ranges := splitNth("1..2,3,2..,1")
|
ranges := splitNth("1..2,3,2..,1")
|
||||||
tx := Transform(tokens, ranges)
|
tx := Transform(tokens, ranges)
|
||||||
|
|||||||
@@ -182,10 +182,18 @@ func (r *LightRenderer) Init() {
|
|||||||
if r.fullscreen {
|
if r.fullscreen {
|
||||||
r.smcup()
|
r.smcup()
|
||||||
} else {
|
} else {
|
||||||
r.csi("J")
|
// We assume that --no-clear is used for repetitive relaunching of fzf.
|
||||||
|
// So we do not clear the lower bottom of the screen.
|
||||||
|
if r.clearOnExit {
|
||||||
|
r.csi("J")
|
||||||
|
}
|
||||||
y, x := r.findOffset()
|
y, x := r.findOffset()
|
||||||
r.mouse = r.mouse && y >= 0
|
r.mouse = r.mouse && y >= 0
|
||||||
if x > 0 {
|
// When --no-clear is used for repetitive relaunching, there is a small
|
||||||
|
// time frame between fzf processes where the user keystrokes are not
|
||||||
|
// captured by either of fzf process which can cause x offset to be
|
||||||
|
// increased and we're left with unwanted extra new line.
|
||||||
|
if x > 0 && r.clearOnExit {
|
||||||
r.upOneLine = true
|
r.upOneLine = true
|
||||||
r.makeSpace()
|
r.makeSpace()
|
||||||
}
|
}
|
||||||
@@ -200,7 +208,7 @@ func (r *LightRenderer) Init() {
|
|||||||
r.csi(fmt.Sprintf("%dA", r.MaxY()-1))
|
r.csi(fmt.Sprintf("%dA", r.MaxY()-1))
|
||||||
r.csi("G")
|
r.csi("G")
|
||||||
r.csi("K")
|
r.csi("K")
|
||||||
// r.csi("s")
|
r.csi("s")
|
||||||
if !r.fullscreen && r.mouse {
|
if !r.fullscreen && r.mouse {
|
||||||
r.yoffset, _ = r.findOffset()
|
r.yoffset, _ = r.findOffset()
|
||||||
}
|
}
|
||||||
@@ -251,8 +259,9 @@ func (r *LightRenderer) updateTerminalSize() {
|
|||||||
|
|
||||||
func (r *LightRenderer) getch(nonblock bool) (int, bool) {
|
func (r *LightRenderer) getch(nonblock bool) (int, bool) {
|
||||||
b := make([]byte, 1)
|
b := make([]byte, 1)
|
||||||
|
fd := r.fd()
|
||||||
util.SetNonblock(r.ttyin, nonblock)
|
util.SetNonblock(r.ttyin, nonblock)
|
||||||
_, err := r.ttyin.Read(b)
|
_, err := util.Read(fd, b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
@@ -410,10 +419,12 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
return Event{F12, 0, nil}
|
return Event{F12, 0, nil}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Bracketed paste mode \e[200~ / \e[201
|
// Bracketed paste mode: \e[200~ ... \e[201~
|
||||||
if r.buffer[3] == 48 && (r.buffer[4] == 48 || r.buffer[4] == 49) && r.buffer[5] == 126 {
|
if r.buffer[3] == '0' && (r.buffer[4] == '0' || r.buffer[4] == '1') && r.buffer[5] == '~' {
|
||||||
*sz = 6
|
// Immediately discard the sequence from the buffer and reread input
|
||||||
return Event{Invalid, 0, nil}
|
r.buffer = r.buffer[6:]
|
||||||
|
*sz = 0
|
||||||
|
return r.GetChar()
|
||||||
}
|
}
|
||||||
return Event{Invalid, 0, nil} // INS
|
return Event{Invalid, 0, nil} // INS
|
||||||
case 51:
|
case 51:
|
||||||
@@ -583,10 +594,8 @@ func (r *LightRenderer) Close() {
|
|||||||
}
|
}
|
||||||
r.csi("J")
|
r.csi("J")
|
||||||
}
|
}
|
||||||
} else if r.fullscreen {
|
} else if !r.fullscreen {
|
||||||
r.csi("G")
|
r.csi("u")
|
||||||
} else {
|
|
||||||
r.move(r.height, 0)
|
|
||||||
}
|
}
|
||||||
if r.mouse {
|
if r.mouse {
|
||||||
r.csi("?1000l")
|
r.csi("?1000l")
|
||||||
@@ -696,6 +705,10 @@ func (w *LightWindow) X() int {
|
|||||||
return w.posx
|
return w.posx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) Y() int {
|
||||||
|
return w.posy
|
||||||
|
}
|
||||||
|
|
||||||
func (w *LightWindow) Enclose(y int, x int) bool {
|
func (w *LightWindow) Enclose(y int, x int) bool {
|
||||||
return x >= w.left && x < (w.left+w.width) &&
|
return x >= w.left && x < (w.left+w.width) &&
|
||||||
y >= w.top && y < (w.top+w.height)
|
y >= w.top && y < (w.top+w.height)
|
||||||
@@ -830,17 +843,20 @@ func (w *LightWindow) fill(str string, onMove func()) FillReturn {
|
|||||||
for j, wl := range lines {
|
for j, wl := range lines {
|
||||||
if w.posx >= w.Width()-1 && wl.displayWidth == 0 {
|
if w.posx >= w.Width()-1 && wl.displayWidth == 0 {
|
||||||
if w.posy < w.height-1 {
|
if w.posy < w.height-1 {
|
||||||
w.MoveAndClear(w.posy+1, 0)
|
w.Move(w.posy+1, 0)
|
||||||
}
|
}
|
||||||
return FillNextLine
|
return FillNextLine
|
||||||
}
|
}
|
||||||
w.stderrInternal(wl.text, false)
|
w.stderrInternal(wl.text, false)
|
||||||
w.posx += wl.displayWidth
|
w.posx += wl.displayWidth
|
||||||
|
|
||||||
|
// Wrap line
|
||||||
if j < len(lines)-1 || i < len(allLines)-1 {
|
if j < len(lines)-1 || i < len(allLines)-1 {
|
||||||
if w.posy+1 >= w.height {
|
if w.posy+1 >= w.height {
|
||||||
return FillSuspend
|
return FillSuspend
|
||||||
}
|
}
|
||||||
w.MoveAndClear(w.posy+1, 0)
|
w.MoveAndClear(w.posy, w.posx)
|
||||||
|
w.Move(w.posy+1, 0)
|
||||||
onMove()
|
onMove()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -855,13 +871,13 @@ func (w *LightWindow) setBg() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) Fill(text string) FillReturn {
|
func (w *LightWindow) Fill(text string) FillReturn {
|
||||||
w.MoveAndClear(w.posy, w.posx)
|
w.Move(w.posy, w.posx)
|
||||||
w.setBg()
|
w.setBg()
|
||||||
return w.fill(text, w.setBg)
|
return w.fill(text, w.setBg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillReturn {
|
func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillReturn {
|
||||||
w.MoveAndClear(w.posy, w.posx)
|
w.Move(w.posy, w.posx)
|
||||||
if bg == colDefault {
|
if bg == colDefault {
|
||||||
bg = w.bg
|
bg = w.bg
|
||||||
}
|
}
|
||||||
@@ -873,6 +889,7 @@ func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillRetu
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) FinishFill() {
|
func (w *LightWindow) FinishFill() {
|
||||||
|
w.MoveAndClear(w.posy, w.posx)
|
||||||
for y := w.posy + 1; y < w.height; y++ {
|
for y := w.posy + 1; y < w.height; y++ {
|
||||||
w.MoveAndClear(y, 0)
|
w.MoveAndClear(y, 0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,16 @@
|
|||||||
package tui
|
package tui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
// https://github.com/gdamore/tcell/pull/135
|
"github.com/gdamore/tcell"
|
||||||
"github.com/junegunn/tcell"
|
"github.com/gdamore/tcell/encoding"
|
||||||
"github.com/junegunn/tcell/encoding"
|
|
||||||
|
|
||||||
"github.com/junegunn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
)
|
)
|
||||||
|
|
||||||
func HasFullscreenRenderer() bool {
|
func HasFullscreenRenderer() bool {
|
||||||
@@ -141,6 +141,9 @@ func (r *FullscreenRenderer) initScreen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Init() {
|
func (r *FullscreenRenderer) Init() {
|
||||||
|
if os.Getenv("TERM") == "cygwin" {
|
||||||
|
os.Setenv("TERM", "")
|
||||||
|
}
|
||||||
encoding.Register()
|
encoding.Register()
|
||||||
|
|
||||||
r.initScreen()
|
r.initScreen()
|
||||||
@@ -161,6 +164,10 @@ func (w *TcellWindow) X() int {
|
|||||||
return w.lastX
|
return w.lastX
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *TcellWindow) Y() int {
|
||||||
|
return w.lastY
|
||||||
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) DoesAutoWrap() bool {
|
func (r *FullscreenRenderer) DoesAutoWrap() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ const (
|
|||||||
F11
|
F11
|
||||||
F12
|
F12
|
||||||
|
|
||||||
|
Change
|
||||||
|
|
||||||
AltSpace
|
AltSpace
|
||||||
AltSlash
|
AltSlash
|
||||||
AltBS
|
AltBS
|
||||||
@@ -234,6 +236,7 @@ type Window interface {
|
|||||||
Close()
|
Close()
|
||||||
|
|
||||||
X() int
|
X() int
|
||||||
|
Y() int
|
||||||
Enclose(y int, x int) bool
|
Enclose(y int, x int) bool
|
||||||
|
|
||||||
Move(y int, x int)
|
Move(y int, x int)
|
||||||
|
|||||||
@@ -3,63 +3,95 @@ package util
|
|||||||
import (
|
import (
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
overflow64 uint64 = 0x8080808080808080
|
||||||
|
overflow32 uint32 = 0x80808080
|
||||||
)
|
)
|
||||||
|
|
||||||
type Chars struct {
|
type Chars struct {
|
||||||
runes []rune
|
slice []byte // or []rune
|
||||||
bytes []byte
|
inBytes bool
|
||||||
|
trimLengthKnown bool
|
||||||
|
trimLength uint16
|
||||||
|
|
||||||
|
// XXX Piggybacking item index here is a horrible idea. But I'm trying to
|
||||||
|
// minimize the memory footprint by not wasting padded spaces.
|
||||||
|
Index int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkAscii(bytes []byte) (bool, int) {
|
||||||
|
i := 0
|
||||||
|
for ; i < len(bytes)-8; i += 8 {
|
||||||
|
if (overflow64 & *(*uint64)(unsafe.Pointer(&bytes[i]))) > 0 {
|
||||||
|
return false, i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ; i < len(bytes)-4; i += 4 {
|
||||||
|
if (overflow32 & *(*uint32)(unsafe.Pointer(&bytes[i]))) > 0 {
|
||||||
|
return false, i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ; i < len(bytes); i++ {
|
||||||
|
if bytes[i] >= utf8.RuneSelf {
|
||||||
|
return false, i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToChars converts byte array into rune array
|
// ToChars converts byte array into rune array
|
||||||
func ToChars(bytea []byte) Chars {
|
func ToChars(bytes []byte) Chars {
|
||||||
var runes []rune
|
inBytes, bytesUntil := checkAscii(bytes)
|
||||||
ascii := true
|
if inBytes {
|
||||||
numBytes := len(bytea)
|
return Chars{slice: bytes, inBytes: inBytes}
|
||||||
for i := 0; i < numBytes; {
|
|
||||||
if bytea[i] < utf8.RuneSelf {
|
|
||||||
if !ascii {
|
|
||||||
runes = append(runes, rune(bytea[i]))
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
} else {
|
|
||||||
if ascii {
|
|
||||||
ascii = false
|
|
||||||
runes = make([]rune, i, numBytes)
|
|
||||||
for j := 0; j < i; j++ {
|
|
||||||
runes[j] = rune(bytea[j])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r, sz := utf8.DecodeRune(bytea[i:])
|
|
||||||
i += sz
|
|
||||||
runes = append(runes, r)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if ascii {
|
|
||||||
return Chars{bytes: bytea}
|
runes := make([]rune, bytesUntil, len(bytes))
|
||||||
|
for i := 0; i < bytesUntil; i++ {
|
||||||
|
runes[i] = rune(bytes[i])
|
||||||
}
|
}
|
||||||
return Chars{runes: runes}
|
for i := bytesUntil; i < len(bytes); {
|
||||||
|
r, sz := utf8.DecodeRune(bytes[i:])
|
||||||
|
i += sz
|
||||||
|
runes = append(runes, r)
|
||||||
|
}
|
||||||
|
return RunesToChars(runes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunesToChars(runes []rune) Chars {
|
func RunesToChars(runes []rune) Chars {
|
||||||
return Chars{runes: runes}
|
return Chars{slice: *(*[]byte)(unsafe.Pointer(&runes)), inBytes: false}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) optionalRunes() []rune {
|
||||||
|
if chars.inBytes {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return *(*[]rune)(unsafe.Pointer(&chars.slice))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chars *Chars) Get(i int) rune {
|
func (chars *Chars) Get(i int) rune {
|
||||||
if chars.runes != nil {
|
if runes := chars.optionalRunes(); runes != nil {
|
||||||
return chars.runes[i]
|
return runes[i]
|
||||||
}
|
}
|
||||||
return rune(chars.bytes[i])
|
return rune(chars.slice[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chars *Chars) Length() int {
|
func (chars *Chars) Length() int {
|
||||||
if chars.runes != nil {
|
if runes := chars.optionalRunes(); runes != nil {
|
||||||
return len(chars.runes)
|
return len(runes)
|
||||||
}
|
}
|
||||||
return len(chars.bytes)
|
return len(chars.slice)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrimLength returns the length after trimming leading and trailing whitespaces
|
// TrimLength returns the length after trimming leading and trailing whitespaces
|
||||||
func (chars *Chars) TrimLength() int {
|
func (chars *Chars) TrimLength() uint16 {
|
||||||
|
if chars.trimLengthKnown {
|
||||||
|
return chars.trimLength
|
||||||
|
}
|
||||||
|
chars.trimLengthKnown = true
|
||||||
var i int
|
var i int
|
||||||
len := chars.Length()
|
len := chars.Length()
|
||||||
for i = len - 1; i >= 0; i-- {
|
for i = len - 1; i >= 0; i-- {
|
||||||
@@ -80,7 +112,8 @@ func (chars *Chars) TrimLength() int {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return i - j + 1
|
chars.trimLength = AsUint16(i - j + 1)
|
||||||
|
return chars.trimLength
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chars *Chars) TrailingWhitespaces() int {
|
func (chars *Chars) TrailingWhitespaces() int {
|
||||||
@@ -96,62 +129,31 @@ func (chars *Chars) TrailingWhitespaces() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (chars *Chars) ToString() string {
|
func (chars *Chars) ToString() string {
|
||||||
if chars.runes != nil {
|
if runes := chars.optionalRunes(); runes != nil {
|
||||||
return string(chars.runes)
|
return string(runes)
|
||||||
}
|
}
|
||||||
return string(chars.bytes)
|
return string(chars.slice)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chars *Chars) ToRunes() []rune {
|
func (chars *Chars) ToRunes() []rune {
|
||||||
if chars.runes != nil {
|
if runes := chars.optionalRunes(); runes != nil {
|
||||||
return chars.runes
|
return runes
|
||||||
}
|
}
|
||||||
runes := make([]rune, len(chars.bytes))
|
bytes := chars.slice
|
||||||
for idx, b := range chars.bytes {
|
runes := make([]rune, len(bytes))
|
||||||
|
for idx, b := range bytes {
|
||||||
runes[idx] = rune(b)
|
runes[idx] = rune(b)
|
||||||
}
|
}
|
||||||
return runes
|
return runes
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chars *Chars) Slice(b int, e int) Chars {
|
func (chars *Chars) CopyRunes(dest []rune) {
|
||||||
if chars.runes != nil {
|
if runes := chars.optionalRunes(); runes != nil {
|
||||||
return Chars{runes: chars.runes[b:e]}
|
copy(dest, runes)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return Chars{bytes: chars.bytes[b:e]}
|
for idx, b := range chars.slice {
|
||||||
}
|
dest[idx] = rune(b)
|
||||||
|
}
|
||||||
func (chars *Chars) Split(delimiter string) []Chars {
|
return
|
||||||
delim := []rune(delimiter)
|
|
||||||
numChars := chars.Length()
|
|
||||||
numDelim := len(delim)
|
|
||||||
begin := 0
|
|
||||||
ret := make([]Chars, 0, 1)
|
|
||||||
|
|
||||||
for index := 0; index < numChars; {
|
|
||||||
if index+numDelim <= numChars {
|
|
||||||
match := true
|
|
||||||
for off, d := range delim {
|
|
||||||
if chars.Get(index+off) != d {
|
|
||||||
match = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Found the delimiter
|
|
||||||
if match {
|
|
||||||
incr := Max(numDelim, 1)
|
|
||||||
ret = append(ret, chars.Slice(begin, index+incr))
|
|
||||||
index += incr
|
|
||||||
begin = index
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Impossible to find the delimiter in the remaining substring
|
|
||||||
break
|
|
||||||
}
|
|
||||||
index++
|
|
||||||
}
|
|
||||||
if begin < numChars || len(ret) == 0 {
|
|
||||||
ret = append(ret, chars.Slice(begin, numChars))
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,27 +2,16 @@ package util
|
|||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
func TestToCharsNil(t *testing.T) {
|
|
||||||
bs := Chars{bytes: []byte{}}
|
|
||||||
if bs.bytes == nil || bs.runes != nil {
|
|
||||||
t.Error()
|
|
||||||
}
|
|
||||||
rs := RunesToChars([]rune{})
|
|
||||||
if rs.bytes != nil || rs.runes == nil {
|
|
||||||
t.Error()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestToCharsAscii(t *testing.T) {
|
func TestToCharsAscii(t *testing.T) {
|
||||||
chars := ToChars([]byte("foobar"))
|
chars := ToChars([]byte("foobar"))
|
||||||
if chars.ToString() != "foobar" || chars.runes != nil {
|
if !chars.inBytes || chars.ToString() != "foobar" || !chars.inBytes {
|
||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCharsLength(t *testing.T) {
|
func TestCharsLength(t *testing.T) {
|
||||||
chars := ToChars([]byte("\tabc한글 "))
|
chars := ToChars([]byte("\tabc한글 "))
|
||||||
if chars.Length() != 8 || chars.TrimLength() != 5 {
|
if chars.inBytes || chars.Length() != 8 || chars.TrimLength() != 5 {
|
||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,7 +25,7 @@ func TestCharsToString(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTrimLength(t *testing.T) {
|
func TestTrimLength(t *testing.T) {
|
||||||
check := func(str string, exp int) {
|
check := func(str string, exp uint16) {
|
||||||
chars := ToChars([]byte(str))
|
chars := ToChars([]byte(str))
|
||||||
trimmed := chars.TrimLength()
|
trimmed := chars.TrimLength()
|
||||||
if trimmed != exp {
|
if trimmed != exp {
|
||||||
@@ -55,28 +44,3 @@ func TestTrimLength(t *testing.T) {
|
|||||||
check(" h o ", 5)
|
check(" h o ", 5)
|
||||||
check(" ", 0)
|
check(" ", 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSplit(t *testing.T) {
|
|
||||||
check := func(str string, delim string, tokens ...string) {
|
|
||||||
input := ToChars([]byte(str))
|
|
||||||
result := input.Split(delim)
|
|
||||||
if len(result) != len(tokens) {
|
|
||||||
t.Errorf("Invalid Split result for '%s': %d tokens found (expected %d): %s",
|
|
||||||
str, len(result), len(tokens), result)
|
|
||||||
}
|
|
||||||
for idx, token := range tokens {
|
|
||||||
if result[idx].ToString() != token {
|
|
||||||
t.Errorf("Invalid Split result for '%s': %s (expected %s)",
|
|
||||||
str, result[idx].ToString(), token)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
check("abc:def::", ":", "abc:", "def:", ":")
|
|
||||||
check("abc:def::", "-", "abc:def::")
|
|
||||||
check("abc", "", "a", "b", "c")
|
|
||||||
check("abc", "a", "a", "bc")
|
|
||||||
check("abc", "ab", "ab", "c")
|
|
||||||
check("abc", "abc", "abc")
|
|
||||||
check("abc", "abcd", "abc")
|
|
||||||
check("", "abcd", "")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/junegunn/go-isatty"
|
"github.com/mattn/go-isatty"
|
||||||
"github.com/junegunn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _runeWidths = make(map[rune]int)
|
var _runeWidths = make(map[rune]int)
|
||||||
|
|||||||
@@ -26,3 +26,8 @@ func IsWindows() bool {
|
|||||||
func SetNonblock(file *os.File, nonblock bool) {
|
func SetNonblock(file *os.File, nonblock bool) {
|
||||||
syscall.SetNonblock(int(file.Fd()), nonblock)
|
syscall.SetNonblock(int(file.Fd()), nonblock)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read executes syscall.Read on file descriptor
|
||||||
|
func Read(fd int, b []byte) (int, error) {
|
||||||
|
return syscall.Read(int(fd), b)
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,20 +7,16 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/junegunn/go-shellwords"
|
"github.com/mattn/go-shellwords"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExecCommand executes the given command with $SHELL
|
// ExecCommand executes the given command with $SHELL
|
||||||
func ExecCommand(command string) *exec.Cmd {
|
func ExecCommand(command string) *exec.Cmd {
|
||||||
shell := os.Getenv("SHELL")
|
|
||||||
if len(shell) == 0 {
|
|
||||||
shell = "cmd"
|
|
||||||
}
|
|
||||||
args, _ := shellwords.Parse(command)
|
args, _ := shellwords.Parse(command)
|
||||||
allArgs := make([]string, len(args)+1)
|
allArgs := make([]string, len(args)+1)
|
||||||
allArgs[0] = "/c"
|
allArgs[0] = "/c"
|
||||||
copy(allArgs[1:], args)
|
copy(allArgs[1:], args)
|
||||||
return exec.Command(shell, allArgs...)
|
return exec.Command("cmd", allArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsWindows returns true on Windows
|
// IsWindows returns true on Windows
|
||||||
@@ -32,3 +28,8 @@ func IsWindows() bool {
|
|||||||
func SetNonblock(file *os.File, nonblock bool) {
|
func SetNonblock(file *os.File, nonblock bool) {
|
||||||
syscall.SetNonblock(syscall.Handle(file.Fd()), nonblock)
|
syscall.SetNonblock(syscall.Handle(file.Fd()), nonblock)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read executes syscall.Read on file descriptor
|
||||||
|
func Read(fd int, b []byte) (int, error) {
|
||||||
|
return syscall.Read(syscall.Handle(fd), b)
|
||||||
|
}
|
||||||
|
|||||||
@@ -147,6 +147,24 @@ Execute (fzf#wrap):
|
|||||||
let opts = fzf#wrap({})
|
let opts = fzf#wrap({})
|
||||||
Assert opts.options =~ '^--color=fg:'
|
Assert opts.options =~ '^--color=fg:'
|
||||||
|
|
||||||
|
Execute (fzf#shellescape with sh):
|
||||||
|
AssertEqual '''''', fzf#shellescape('', 'sh')
|
||||||
|
AssertEqual '''""''', fzf#shellescape('""', 'sh')
|
||||||
|
AssertEqual '''foobar>''', fzf#shellescape('foobar>', 'sh')
|
||||||
|
AssertEqual '''\"''', fzf#shellescape('\"', 'sh')
|
||||||
|
AssertEqual '''echo ''\''''a''\'''' && echo ''\''''b''\''''''', fzf#shellescape('echo ''a'' && echo ''b''', 'sh')
|
||||||
|
|
||||||
|
Execute (fzf#shellescape with cmd.exe):
|
||||||
|
AssertEqual '^"^"', fzf#shellescape('', 'cmd.exe')
|
||||||
|
AssertEqual '^"\^"\^"^"', fzf#shellescape('""', 'cmd.exe')
|
||||||
|
AssertEqual '^"foobar^>^"', fzf#shellescape('foobar>', 'cmd.exe')
|
||||||
|
AssertEqual '^"\\\^"\\^"', fzf#shellescape('\\\\\\\\"\', 'cmd.exe')
|
||||||
|
AssertEqual '^"echo ''a'' ^&^& echo ''b''^"', fzf#shellescape('echo ''a'' && echo ''b''', 'cmd.exe')
|
||||||
|
|
||||||
|
AssertEqual '^"C:\Program Files ^(x86^)\\^"', fzf#shellescape('C:\Program Files (x86)\', 'cmd.exe')
|
||||||
|
AssertEqual '^"C:/Program Files ^(x86^)/^"', fzf#shellescape('C:/Program Files (x86)/', 'cmd.exe')
|
||||||
|
AssertEqual '^"%%USERPROFILE%%^"', fzf#shellescape('%USERPROFILE%', 'cmd.exe')
|
||||||
|
|
||||||
Execute (Cleanup):
|
Execute (Cleanup):
|
||||||
unlet g:dir
|
unlet g:dir
|
||||||
Restore
|
Restore
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ class Tmux
|
|||||||
else
|
else
|
||||||
raise "Unknown shell: #{shell}"
|
raise "Unknown shell: #{shell}"
|
||||||
end
|
end
|
||||||
|
go("set-window-option -t #{@win} pane-base-index 0")
|
||||||
@lines = `tput lines`.chomp.to_i
|
@lines = `tput lines`.chomp.to_i
|
||||||
|
|
||||||
if shell == :fish
|
if shell == :fish
|
||||||
@@ -259,6 +260,12 @@ class TestGoFZF < TestBase
|
|||||||
assert_equal 'hello', readonce.chomp
|
assert_equal 'hello', readonce.chomp
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_fzf_default_command_failure
|
||||||
|
tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', 'FZF_DEFAULT_COMMAND=false'), :Enter
|
||||||
|
tmux.until { |lines| lines[-2].include?('ERROR') }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
end
|
||||||
|
|
||||||
def test_key_bindings
|
def test_key_bindings
|
||||||
tmux.send_keys "#{FZF} -q 'foo bar foo-bar'", :Enter
|
tmux.send_keys "#{FZF} -q 'foo bar foo-bar'", :Enter
|
||||||
tmux.until { |lines| lines.last =~ /^>/ }
|
tmux.until { |lines| lines.last =~ /^>/ }
|
||||||
@@ -683,62 +690,10 @@ class TestGoFZF < TestBase
|
|||||||
]
|
]
|
||||||
assert_equal output, `#{FZF} -fh < #{tempname}`.split($/)
|
assert_equal output, `#{FZF} -fh < #{tempname}`.split($/)
|
||||||
|
|
||||||
output = %w[
|
# Since 0.16.8, --nth doesn't affect --tiebreak
|
||||||
1234567:h
|
|
||||||
12345:he
|
|
||||||
1:hell
|
|
||||||
123:hello
|
|
||||||
]
|
|
||||||
assert_equal output, `#{FZF} -fh -n2 -d: < #{tempname}`.split($/)
|
assert_equal output, `#{FZF} -fh -n2 -d: < #{tempname}`.split($/)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_tiebreak_length_with_nth_trim_length
|
|
||||||
input = [
|
|
||||||
"apple juice bottle 1",
|
|
||||||
"apple ui bottle 2",
|
|
||||||
"app ice bottle 3",
|
|
||||||
"app ic bottle 4",
|
|
||||||
]
|
|
||||||
writelines tempname, input
|
|
||||||
|
|
||||||
# len(1)
|
|
||||||
output = [
|
|
||||||
"app ice bottle 3",
|
|
||||||
"app ic bottle 4",
|
|
||||||
"apple juice bottle 1",
|
|
||||||
"apple ui bottle 2",
|
|
||||||
]
|
|
||||||
assert_equal output, `#{FZF} -fa -n1 < #{tempname}`.split($/)
|
|
||||||
|
|
||||||
# len(1 ~ 2)
|
|
||||||
output = [
|
|
||||||
"app ic bottle 4",
|
|
||||||
"app ice bottle 3",
|
|
||||||
"apple ui bottle 2",
|
|
||||||
"apple juice bottle 1",
|
|
||||||
]
|
|
||||||
assert_equal output, `#{FZF} -fai -n1..2 < #{tempname}`.split($/)
|
|
||||||
|
|
||||||
# len(1) + len(2)
|
|
||||||
output = [
|
|
||||||
"app ic bottle 4",
|
|
||||||
"app ice bottle 3",
|
|
||||||
"apple ui bottle 2",
|
|
||||||
"apple juice bottle 1",
|
|
||||||
]
|
|
||||||
assert_equal output, `#{FZF} -x -f"a i" -n1,2 < #{tempname}`.split($/)
|
|
||||||
|
|
||||||
# len(2)
|
|
||||||
output = [
|
|
||||||
"app ic bottle 4",
|
|
||||||
"app ice bottle 3",
|
|
||||||
"apple ui bottle 2",
|
|
||||||
"apple juice bottle 1",
|
|
||||||
]
|
|
||||||
assert_equal output, `#{FZF} -fi -n2 < #{tempname}`.split($/)
|
|
||||||
assert_equal output, `#{FZF} -fi -n2,1..2 < #{tempname}`.split($/)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_invalid_cache
|
def test_invalid_cache
|
||||||
tmux.send_keys "(echo d; echo D; echo x) | #{fzf '-q d'}", :Enter
|
tmux.send_keys "(echo d; echo D; echo x) | #{fzf '-q d'}", :Enter
|
||||||
tmux.until { |lines| lines[-2].include? '2/3' }
|
tmux.until { |lines| lines[-2].include? '2/3' }
|
||||||
@@ -975,15 +930,15 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| lines[-10].start_with? '>' }
|
tmux.until { |lines| lines[-10].start_with? '>' }
|
||||||
tmux.send_keys :Down
|
tmux.send_keys :Down
|
||||||
tmux.until { |lines| lines[-9].start_with? '>' }
|
tmux.until { |lines| lines[-9].start_with? '>' }
|
||||||
tmux.send_keys :PgUp
|
tmux.send_keys :Up
|
||||||
tmux.until { |lines| lines[-10].start_with? '>' }
|
tmux.until { |lines| lines[-10].start_with? '>' }
|
||||||
tmux.send_keys :PgUp
|
tmux.send_keys :PgUp
|
||||||
tmux.until { |lines| lines[-3].start_with? '>' }
|
tmux.until { |lines| lines[-10].start_with? '>' }
|
||||||
tmux.send_keys :Up
|
tmux.send_keys :Up
|
||||||
tmux.until { |lines| lines[-4].start_with? '>' }
|
|
||||||
tmux.send_keys :PgDn
|
|
||||||
tmux.until { |lines| lines[-3].start_with? '>' }
|
tmux.until { |lines| lines[-3].start_with? '>' }
|
||||||
tmux.send_keys :PgDn
|
tmux.send_keys :PgDn
|
||||||
|
tmux.until { |lines| lines[-3].start_with? '>' }
|
||||||
|
tmux.send_keys :Down
|
||||||
tmux.until { |lines| lines[-10].start_with? '>' }
|
tmux.until { |lines| lines[-10].start_with? '>' }
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1298,11 +1253,26 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_no_clear
|
def test_no_clear
|
||||||
tmux.send_keys 'seq 100 | fzf --no-clear --inline-info --height 5', :Enter
|
tmux.send_keys "seq 10 | fzf --no-clear --inline-info --height 5 > #{tempname}", :Enter
|
||||||
prompt = '> < 100/100'
|
prompt = '> < 10/10'
|
||||||
tmux.until { |lines| lines[-1] == prompt }
|
tmux.until { |lines| lines[-1] == prompt }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until { |lines| lines[-2] == prompt && lines[-1] == '1' }
|
tmux.until { |_| %w[1] == File.readlines(tempname).map(&:chomp) }
|
||||||
|
tmux.until { |lines| lines[-1] == prompt }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_change_top
|
||||||
|
tmux.send_keys %[seq 1000 | #{FZF} --bind change:top], :Enter
|
||||||
|
tmux.until { |lines| lines.match_count == 1000 }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| lines[-4] == '> 2' }
|
||||||
|
tmux.send_keys 1
|
||||||
|
tmux.until { |lines| lines[-3] == '> 1' }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| lines[-4] == '> 10' }
|
||||||
|
tmux.send_keys 1
|
||||||
|
tmux.until { |lines| lines[-3] == '> 11' }
|
||||||
|
tmux.send_keys :Enter
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user