m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-15 14:53:47 -05:00

Compare commits

...

52 Commits

Author SHA1 Message Date
Junegunn Choi
ff951341c9 0.18.0 2019-03-31 11:22:38 +09:00
Junegunn Choi
df570afd52 [docker] Fix gem install option in Dockerfile 2019-03-31 11:22:12 +09:00
Junegunn Choi
07d755df11 Fix regression of prompt display 2019-03-30 02:07:09 +09:00
Junegunn Choi
37585bd5a5 Disable preview scroll if the content fits on the screen
Close #1540
2019-03-29 18:28:45 +09:00
Junegunn Choi
89e24bf8f2 Fix ineffective break statement 2019-03-29 17:29:24 +09:00
Junegunn Choi
8d2fcd3518 Avoid unnecessary redraw of the preview window 2019-03-29 15:12:46 +09:00
Junegunn Choi
f39ab3875e Redraw prompt only when necessary 2019-03-29 15:02:31 +09:00
AnakinXL
82efe6c60d [doc] Add bat for preview syntax highlighting example (#1538)
Similar to this PR from fzf.vim:
https://github.com/junegunn/fzf.vim/pull/712
2019-03-29 10:31:17 +09:00
Junegunn Choi
75972d59a8 Add --no-unicode option to draw borders in ASCII characters
Close ##1533
2019-03-29 02:11:03 +09:00
Junegunn Choi
e7d60aac9c [vim] Do not restore cwd when autochdir is set and buffer changed
Close #1539
2019-03-28 13:57:04 +09:00
Junegunn Choi
a0bfbdd49c [vim] Increase window height by 2 when --border is set
Close #1535
2019-03-26 16:42:35 +09:00
Junegunn Choi
ba594982f0 Add MacPorts instruction
Close #1521
2019-03-18 18:45:57 +09:00
Junegunn Choi
2157f4f193 Add color option for gutter
fzf --color gutter:-1

Close #1529
Close #1468
2019-03-17 15:52:38 +09:00
Junegunn Choi
309bae423c [zsh-completion] Suppress "no matches found" message 2019-03-14 01:10:51 +09:00
Junegunn Choi
4f8bf2ae78 [install] Avoid generating empty component in $PATH
Fix #1517
2019-03-08 12:38:36 +09:00
Junegunn Choi
85c1f8a9e0 Always prepend ANSI reset code before re-assembling tokens 2019-03-07 15:29:57 +09:00
Junegunn Choi
e00e7e1e56 Remove unnecessary ANSI code injection 2019-03-07 02:02:02 +09:00
Junegunn Choi
1a6defdbcc Use simple string concatenation instead of using fmt.Sprintf 2019-03-07 02:00:31 +09:00
Junegunn Choi
ef577a6509 Preserve the original color of each token when using --with-nth with --ansi
Close #1500
2019-03-06 19:05:05 +09:00
Junegunn Choi
b7c6838e45 [install] Fix symlink log
Related #1466
2019-03-05 14:45:29 +09:00
Junegunn Choi
91d04cec5c [install] Print better error message when fzf --version failed
Related #1466
2019-03-05 14:44:29 +09:00
Rui Coelho
3bd8441079 [completion] Look up on ~/.ssh/config.d/* files when doing ssh host complete (#1420) 2019-02-28 16:40:41 +09:00
Junegunn Choi
8cf45a5197 [shell] Skip loading completion code on non-interactive shell
This change is not required if you use the install script to generate
~/.fzf.bash or ~/.fzf.zsh which already has the proper guard statement.

Close #1474
2019-02-28 16:13:59 +09:00
Junegunn Choi
8dc1377efb Export FZF_PREVIEW_LINES and FZF_PREVIEW_COLUMNS to preview process
fzf will still override LINES and COLUMNS as before but they may not
hold the correct values depending on the default shell.

Close #1314
2019-02-22 14:36:30 +09:00
Junegunn Choi
6c32148f90 Add placeholder expression for zero-based item index: {n} and {+n}
Close #1482
2019-02-19 01:12:57 +09:00
Junegunn Choi
315e568de0 Update build instruction
Close #1485
2019-01-31 18:43:10 +09:00
Junegunn Choi
5d16b28869 Fix tab width after ANSI reset code in preview window
Close #1423
2018-12-22 11:52:18 +09:00
Junegunn Choi
5624a89231 Inverse-only matches should not reorder the remaining results
Fix #1458
2018-12-19 23:05:29 +09:00
Junegunn Choi
63c42b14f2 Remove trailing spaces in Makefile 2018-12-13 15:17:30 +09:00
Stefan Tatschner
6f1eaa9b39 Use go modules and simplify build (#1444)
* Update .travis.yml and use stages

This updates the .travis.yml configuration to use separate stages for
unittests and CLI tests. The output is now clearer, since for unittests
and CLI tests separate web pages are available.

* Use go modules and simplify build
2018-12-13 14:36:15 +09:00
Junegunn Choi
ca42e5e00a Avoid unnecessary redraw of preview window
Close #1455
2018-12-13 10:58:57 +09:00
Junegunn Choi
61feee690c Render preview window when the initial query fails to match
Only if preview template contains {q}

Fix #1452
Related #1307
2018-12-05 18:45:55 +09:00
Christian Muehlhaeuser
d4ed955aee Typo & grammar fixes in README (#1413) 2018-11-09 16:50:16 +09:00
Junegunn Choi
b46227dcb6 0.17.5 2018-10-07 01:46:29 +09:00
Paul Frybarger
fd8d371ac7 [zsh] Fix multiline prompt issue with 'zle reset-prompt' (#1397)
Close #867 
Close #1256
2018-10-05 10:56:26 +09:00
Junegunn Choi
0e06e298d4 [man] Document that FZF_DEFAULT_COMMAND should be POSIX-compliant
Close #1379
2018-09-30 22:20:46 +09:00
Junegunn Choi
72df905902 Do not wait for more keystrokes after double escape characters
Close #1393
2018-09-28 10:33:53 +09:00
Junegunn Choi
0d748a0699 Kill running preview process after 500ms when focus has changed
Close #1383
Close #1384
2018-09-28 10:33:52 +09:00
Junegunn Choi
27c40dc6b0 Restore STDIN during execute-silent
This allows users to terminate the process with CTRL-C when it hangs.
2018-09-27 15:54:13 +09:00
Junegunn Choi
8e34e6fbb4 [install] Escape spaces in installation directory
Close #1390
2018-09-27 10:15:22 +09:00
Junegunn Choi
3bc98ed623 Add link to related projects page 2018-09-27 02:52:52 +09:00
Tim Cuthbertson
70a92a858a Don't drop buffered input data in findOffset() (#1392) 2018-09-27 02:35:44 +09:00
Jan Edmund Lazo
49d04374a4 [install] Detect MSYS on Windows (#1391) 2018-09-25 23:03:36 +09:00
Junegunn Choi
8540902a35 Add link to git key bindings gist 2018-09-04 12:24:46 +09:00
Junegunn Choi
8c6fcee3ca [vim] Fix directory switching around sink function
Close #1356

Related:
- #612
- https://github.com/junegunn/fzf.vim/issues/308
2018-08-20 15:31:41 +09:00
Junegunn Choi
13803d0dbb [vim] Clear temporary window-local working directory
Close #1085
Close #1086
Close https://github.com/junegunn/fzf.vim/issues/678
2018-08-10 18:24:18 +09:00
Michael Kelley
423986996a Handle incomplete ESC sequence in typeahead buffer (#1350)
If an ESC char is found while processing characters,
continue to check for characters. This prevents fzf from
prematurely exiting.

Close #1349
2018-08-08 15:43:55 +09:00
Younes Manton
1c9e7b7ea6 Update Makefile to build ppc64le binary (#1326)
* Add ppc64le support to Makefile

* Update crypt libs to fix tty ioctls on ppc64le

The hardcoded tty ioctl commands in the terminal package were not
correct for ppc64le and caused the ioctls to return ENOTTY for
commands like TCGETS and so on. The bug is fixed in later versions.
2018-07-16 18:55:06 +09:00
Jay
6de1ad9d3d [completion] Filter out non-hostnames in SSH config file (#1329)
* Correctly exclude SSH config options with Host

SSH config files have 14 options containing 'Host'.
Previously The zsh and bash completion scripts would include lines
containing these options when doing command-line completion of SSH hosts
with `ssh **`.

This commit fixes that problem by only including lines with 'host '.

* Don't autocomplete SSH hostnames using ?

SSH config files support ? as well as * for wildcards in Host lines.
This commit excludes lines containing ? for zsh/bash command line
completeion using `ssh **`
2018-07-06 11:29:39 +09:00
Oliver Schrenk
5004ae3457 [fish] Use $version instead of $FISH_VERSION (#1100)
$FISH_VERSION is dropped in 2.7, but every version has $version

- https://github.com/fish-shell/fish-shell/issues/4414
- fb8ae04f80

Comment from @faho in #1316:

Unfortunately, $FISH_VERSION was only ever a thing from fish 2.0 to fish 2.7.1.

All fish versions from the very beginning though used a variable called simply "$version" to store their version, so that is the one that should be used.
2018-06-27 19:02:16 +09:00
做梦专业户
e67cc75063 Update Makefile to support armv8l (#1321) 2018-06-27 18:56:02 +09:00
Junegunn Choi
0edbcbdf19 Allow search query longer than the screen width
By implementing horizontal scrolling of the prompt line.
Maximum length is hard-coded to 300-chars.

Close #1312
Fix #1225
2018-06-25 19:07:47 +09:00
35 changed files with 757 additions and 461 deletions

View File

@@ -1,20 +1,27 @@
language: ruby language: go
dist: trusty dist: xenial
sudo: required addons:
matrix: apt:
sources:
- sourceline: "ppa:pi-rho/dev"
- sourceline: "ppa:fish-shell/release-2"
packages:
- tmux
- zsh
- fish
env:
- GO111MODULE=on
jobs:
include: include:
- env: TAGS= - stage: unittest
rvm: 2.3.3 go: "1.11.x"
# - env: TAGS=tcell script: make && make test
# rvm: 2.3.3
install:
- sudo add-apt-repository -y ppa:pi-rho/dev
- sudo apt-add-repository -y ppa:fish-shell/release-2
- sudo apt-get update
- sudo apt-get install -y tmux zsh fish
- stage: cli
go: "1.11.x"
rvm: "2.5"
script: | script: |
make test install && make install && ./install --all && tmux new "ruby test/test_go.rb > out && touch ok" && cat out && [ -e ok ]
./install --all &&
tmux new "ruby test/test_go.rb > out && touch ok" && cat out && [ -e ok ]

View File

@@ -6,12 +6,10 @@ Build instructions
### Prerequisites ### Prerequisites
- `go` executable in $PATH - Go 1.11 or above
### Using Makefile ### Using Makefile
Makefile will set up and use its own `$GOPATH` under the project root.
```sh ```sh
# Build fzf binary for your platform in target # Build fzf binary for your platform in target
make make

View File

@@ -1,6 +1,29 @@
CHANGELOG CHANGELOG
========= =========
0.18.0
------
- Added placeholder expression for zero-based item index: `{n}` and `{+n}`
- `fzf --preview 'echo {n}: {}'`
- Added color option for the gutter: `--color gutter:-1`
- Added `--no-unicode` option for drawing borders in non-Unicode, ASCII
characters
- `FZF_PREVIEW_LINES` and `FZF_PREVIEW_COLUMNS` are exported to preview process
- fzf still overrides `LINES` and `COLUMNS` as before, but they may be
reset by the default shell.
- Bug fixes and improvements
- See https://github.com/junegunn/fzf/milestone/14?closed=1
- Built with Go 1.12.1
0.17.5
------
- Bug fixes and improvements
- See https://github.com/junegunn/fzf/milestone/13?closed=1
- Search query longer than the screen width is allowed (up to 300 chars)
- Built with Go 1.11.1
0.17.4 0.17.4
------ ------

View File

@@ -1,6 +1,6 @@
FROM archlinux/base:latest FROM archlinux/base:latest
RUN pacman -Sy && pacman --noconfirm -S awk git tmux zsh fish ruby procps go make RUN pacman -Sy && pacman --noconfirm -S awk git tmux zsh fish ruby procps go make
RUN gem install --no-ri --no-rdoc minitest RUN gem install --no-document minitest
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
RUN echo '. ~/.bashrc' >> ~/.bash_profile RUN echo '. ~/.bashrc' >> ~/.bash_profile

View File

@@ -1,17 +1,9 @@
ifndef GOOS GO ?= go
GOOS := $(word 1, $(subst /, " ", $(word 4, $(shell go version)))) GOOS ?= $(word 1, $(subst /, " ", $(word 4, $(shell go version))))
endif
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST))) MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
ROOT_DIR := $(shell dirname $(MAKEFILE)) ROOT_DIR := $(shell dirname $(MAKEFILE))
GOPATH := $(ROOT_DIR)/gopath SOURCES := $(wildcard *.go src/*.go src/*/*.go) $(MAKEFILE)
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)) REVISION := $(shell git log -n 1 --pretty=format:%h -- $(SOURCES))
BUILD_FLAGS := -a -ldflags "-X main.revision=$(REVISION) -w -extldflags=$(LDFLAGS)" -tags "$(TAGS)" BUILD_FLAGS := -a -ldflags "-X main.revision=$(REVISION) -w -extldflags=$(LDFLAGS)" -tags "$(TAGS)"
@@ -22,6 +14,7 @@ BINARYARM5 := fzf-$(GOOS)_arm5
BINARYARM6 := fzf-$(GOOS)_arm6 BINARYARM6 := fzf-$(GOOS)_arm6
BINARYARM7 := fzf-$(GOOS)_arm7 BINARYARM7 := fzf-$(GOOS)_arm7
BINARYARM8 := fzf-$(GOOS)_arm8 BINARYARM8 := fzf-$(GOOS)_arm8
BINARYPPC64LE := fzf-$(GOOS)_ppc64le
VERSION := $(shell awk -F= '/version =/ {print $$2}' src/constants.go | tr -d "\" ") VERSION := $(shell awk -F= '/version =/ {print $$2}' src/constants.go | tr -d "\" ")
RELEASE32 := fzf-$(VERSION)-$(GOOS)_386 RELEASE32 := fzf-$(VERSION)-$(GOOS)_386
RELEASE64 := fzf-$(VERSION)-$(GOOS)_amd64 RELEASE64 := fzf-$(VERSION)-$(GOOS)_amd64
@@ -29,6 +22,7 @@ RELEASEARM5 := fzf-$(VERSION)-$(GOOS)_arm5
RELEASEARM6 := fzf-$(VERSION)-$(GOOS)_arm6 RELEASEARM6 := fzf-$(VERSION)-$(GOOS)_arm6
RELEASEARM7 := fzf-$(VERSION)-$(GOOS)_arm7 RELEASEARM7 := fzf-$(VERSION)-$(GOOS)_arm7
RELEASEARM8 := fzf-$(VERSION)-$(GOOS)_arm8 RELEASEARM8 := fzf-$(VERSION)-$(GOOS)_arm8
RELEASEPPC64LE := fzf-$(VERSION)-$(GOOS)_ppc64le
# https://en.wikipedia.org/wiki/Uname # https://en.wikipedia.org/wiki/Uname
UNAME_M := $(shell uname -m) UNAME_M := $(shell uname -m)
@@ -46,6 +40,10 @@ else ifeq ($(UNAME_M),armv6l)
BINARY := $(BINARYARM6) BINARY := $(BINARYARM6)
else ifeq ($(UNAME_M),armv7l) else ifeq ($(UNAME_M),armv7l)
BINARY := $(BINARYARM7) BINARY := $(BINARYARM7)
else ifeq ($(UNAME_M),armv8l)
BINARY := $(BINARYARM8)
else ifeq ($(UNAME_M),ppc64le)
BINARY := $(BINARYPPC64LE)
else else
$(error "Build on $(UNAME_M) is not supported, yet.") $(error "Build on $(UNAME_M) is not supported, yet.")
endif endif
@@ -61,13 +59,14 @@ release: target/$(BINARY32) target/$(BINARY64)
cd target && cp -f $(BINARY64) fzf.exe && zip $(RELEASE64).zip fzf.exe cd target && cp -f $(BINARY64) fzf.exe && zip $(RELEASE64).zip fzf.exe
cd target && rm -f fzf.exe cd target && rm -f fzf.exe
else ifeq ($(GOOS),linux) else ifeq ($(GOOS),linux)
release: target/$(BINARY32) target/$(BINARY64) target/$(BINARYARM5) target/$(BINARYARM6) target/$(BINARYARM7) target/$(BINARYARM8) release: target/$(BINARY32) target/$(BINARY64) target/$(BINARYARM5) target/$(BINARYARM6) target/$(BINARYARM7) target/$(BINARYARM8) target/$(BINARYPPC64LE)
cd target && cp -f $(BINARY32) fzf && tar -czf $(RELEASE32).tgz fzf 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 $(BINARY64) fzf && tar -czf $(RELEASE64).tgz fzf
cd target && cp -f $(BINARYARM5) fzf && tar -czf $(RELEASEARM5).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 $(BINARYARM6) fzf && tar -czf $(RELEASEARM6).tgz fzf
cd target && cp -f $(BINARYARM7) fzf && tar -czf $(RELEASEARM7).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 && cp -f $(BINARYARM8) fzf && tar -czf $(RELEASEARM8).tgz fzf
cd target && cp -f $(BINARYPPC64LE) fzf && tar -czf $(RELEASEPPC64LE).tgz fzf
cd target && rm -f fzf cd target && rm -f fzf
else else
release: target/$(BINARY32) target/$(BINARY64) release: target/$(BINARY32) target/$(BINARY64)
@@ -83,19 +82,8 @@ release-all: clean test
GOOS=openbsd make release GOOS=openbsd make release
GOOS=windows make release GOOS=windows make release
$(SRC_LINK): test: $(SOURCES)
mkdir -p $(shell dirname $(SRC_LINK)) SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \
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 \
github.com/junegunn/fzf/src/algo \ github.com/junegunn/fzf/src/algo \
github.com/junegunn/fzf/src/tui \ github.com/junegunn/fzf/src/tui \
@@ -104,26 +92,29 @@ test: $(SOURCES) vendor
install: bin/fzf install: bin/fzf
clean: clean:
rm -rf target $(RM) -r target
target/$(BINARY32): $(SOURCES) vendor target/$(BINARY32): $(SOURCES)
GOARCH=386 go build $(BUILD_FLAGS) -o $@ GOARCH=386 $(GO) build $(BUILD_FLAGS) -o $@
target/$(BINARY64): $(SOURCES) vendor target/$(BINARY64): $(SOURCES)
GOARCH=amd64 go build $(BUILD_FLAGS) -o $@ GOARCH=amd64 $(GO) build $(BUILD_FLAGS) -o $@
# https://github.com/golang/go/wiki/GoArm # https://github.com/golang/go/wiki/GoArm
target/$(BINARYARM5): $(SOURCES) vendor target/$(BINARYARM5): $(SOURCES)
GOARCH=arm GOARM=5 go build $(BUILD_FLAGS) -o $@ GOARCH=arm GOARM=5 $(GO) build $(BUILD_FLAGS) -o $@
target/$(BINARYARM6): $(SOURCES) vendor target/$(BINARYARM6): $(SOURCES)
GOARCH=arm GOARM=6 go build $(BUILD_FLAGS) -o $@ GOARCH=arm GOARM=6 $(GO) build $(BUILD_FLAGS) -o $@
target/$(BINARYARM7): $(SOURCES) vendor target/$(BINARYARM7): $(SOURCES)
GOARCH=arm GOARM=7 go build $(BUILD_FLAGS) -o $@ GOARCH=arm GOARM=7 $(GO) build $(BUILD_FLAGS) -o $@
target/$(BINARYARM8): $(SOURCES) vendor target/$(BINARYARM8): $(SOURCES)
GOARCH=arm64 go build $(BUILD_FLAGS) -o $@ GOARCH=arm64 $(GO) build $(BUILD_FLAGS) -o $@
target/$(BINARYPPC64LE): $(SOURCES)
GOARCH=ppc64le $(GO) build $(BUILD_FLAGS) -o $@
bin/fzf: target/$(BINARY) | bin bin/fzf: target/$(BINARY) | bin
cp -f target/$(BINARY) bin/fzf cp -f target/$(BINARY) bin/fzf
@@ -136,4 +127,8 @@ docker-test:
docker build -t fzf-arch . docker build -t fzf-arch .
docker run -it fzf-arch docker run -it fzf-arch
.PHONY: all release release-all test install clean docker docker-test update:
$(GO) get -u
$(GO) mod tidy
.PHONY: all release release-all test install clean docker docker-test update

View File

@@ -56,6 +56,7 @@ Table of Contents
* [Respecting .gitignore](#respecting-gitignore) * [Respecting .gitignore](#respecting-gitignore)
* [git ls-tree for fast traversal](#git-ls-tree-for-fast-traversal) * [git ls-tree for fast traversal](#git-ls-tree-for-fast-traversal)
* [Fish shell](#fish-shell) * [Fish shell](#fish-shell)
* [Related projects](#related-projects)
* [<a href="LICENSE">License</a>](#license) * [<a href="LICENSE">License</a>](#license)
Installation Installation
@@ -87,6 +88,10 @@ brew install fzf
$(brew --prefix)/opt/fzf/install $(brew --prefix)/opt/fzf/install
``` ```
fzf is also available [via MacPorts][portfile]: `sudo port install fzf`
[portfile]: https://github.com/macports/macports-ports/blob/master/sysutils/fzf/Portfile
### Using git ### Using git
Alternatively, you can "git clone" this repository to any directory and run Alternatively, you can "git clone" this repository to any directory and run
@@ -126,10 +131,10 @@ But instead of separately installing fzf on your system (using Homebrew or
vim-plug to do both. vim-plug to do both.
```vim ```vim
" PlugInstall and PlugUpdate will clone fzf in ~/.fzf and run install script " PlugInstall and PlugUpdate will clone fzf in ~/.fzf and run the install script
Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' } Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
" Both options are optional. You don't have to install fzf in ~/.fzf " Both options are optional. You don't have to install fzf in ~/.fzf
" and you don't have to run install script if you use fzf only in Vim. " and you don't have to run the install script if you use fzf only in Vim.
``` ```
### Arch Linux ### Arch Linux
@@ -311,16 +316,16 @@ fullscreen mode.
fzf --height 40% fzf --height 40%
``` ```
Key bindings for command line Key bindings for command-line
----------------------------- -----------------------------
The install script will setup the following key bindings for bash, zsh, and The install script will setup the following key bindings for bash, zsh, and
fish. fish.
- `CTRL-T` - Paste the selected files and directories onto the command line - `CTRL-T` - Paste the selected files and directories onto the command-line
- Set `FZF_CTRL_T_COMMAND` to override the default command - Set `FZF_CTRL_T_COMMAND` to override the default command
- Set `FZF_CTRL_T_OPTS` to pass additional options - Set `FZF_CTRL_T_OPTS` to pass additional options
- `CTRL-R` - Paste the selected command from history onto the command line - `CTRL-R` - Paste the selected command from history onto the command-line
- If you want to see the commands in chronological order, press `CTRL-R` - If you want to see the commands in chronological order, press `CTRL-R`
again which toggles sorting by relevance again which toggles sorting by relevance
- Set `FZF_CTRL_R_OPTS` to pass additional options - Set `FZF_CTRL_R_OPTS` to pass additional options
@@ -372,7 +377,7 @@ cd ~/github/fzf**<TAB>
#### Process IDs #### Process IDs
Fuzzy completion for PIDs is provided for kill command. In this case Fuzzy completion for PIDs is provided for kill command. In this case,
there is no trigger sequence, just press tab key after kill command. there is no trigger sequence, just press tab key after kill command.
```sh ```sh
@@ -425,7 +430,7 @@ _fzf_compgen_dir() {
On bash, fuzzy completion is enabled only for a predefined set of commands On bash, fuzzy completion is enabled only for a predefined set of commands
(`complete | grep _fzf` to see the list). But you can enable it for other (`complete | grep _fzf` to see the list). But you can enable it for other
commands as well like follows. commands as well as follows.
```sh ```sh
complete -F _fzf_path_completion -o default -o bashdefault ag complete -F _fzf_path_completion -o default -o bashdefault ag
@@ -442,7 +447,7 @@ Advanced topics
### Performance ### Performance
fzf is fast, and is [getting even faster][perf]. Performance should not be fzf is fast and is [getting even faster][perf]. Performance should not be
a problem in most use cases. However, you might want to be aware of the a problem in most use cases. However, you might want to be aware of the
options that affect the performance. options that affect the performance.
@@ -453,7 +458,7 @@ options that affect the performance.
- `--with-nth` makes fzf slower as fzf has to tokenize and reassemble each - `--with-nth` makes fzf slower as fzf has to tokenize and reassemble each
line. line.
- If you absolutely need better performance, you can consider using - If you absolutely need better performance, you can consider using
`--algo=v1` (the default being `v2`) to make fzf use faster greedy `--algo=v1` (the default being `v2`) to make fzf use a faster greedy
algorithm. However, this algorithm is not guaranteed to find the optimal algorithm. However, this algorithm is not guaranteed to find the optimal
ordering of the matches and is not recommended. ordering of the matches and is not recommended.
@@ -474,7 +479,7 @@ See *KEY BINDINGS* section of the man page for details.
### Preview window ### Preview window
When `--preview` option is set, fzf automatically starts external process with When `--preview` option is set, fzf automatically starts an external process with
the current line as the argument and shows the result in the split window. the current line as the argument and shows the result in the split window.
```bash ```bash
@@ -482,7 +487,7 @@ the current line as the argument and shows the result in the split window.
fzf --preview 'cat {}' fzf --preview 'cat {}'
``` ```
Since preview window is updated only after the process is complete, it's Since the preview window is updated only after the process is complete, it's
important that the command finishes quickly. important that the command finishes quickly.
```bash ```bash
@@ -493,15 +498,17 @@ fzf --preview 'head -100 {}'
Preview window supports ANSI colors, so you can use programs that Preview window supports ANSI colors, so you can use programs that
syntax-highlights the content of a file. syntax-highlights the content of a file.
- Bat: https://github.com/sharkdp/bat
- Highlight: http://www.andre-simon.de/doku/highlight/en/highlight.php - Highlight: http://www.andre-simon.de/doku/highlight/en/highlight.php
- CodeRay: http://coderay.rubychan.de/ - CodeRay: http://coderay.rubychan.de/
- Rouge: https://github.com/jneen/rouge - Rouge: https://github.com/jneen/rouge
```bash ```bash
# Try highlight, coderay, rougify in turn, then fall back to cat # Try bat, highlight, coderay, rougify in turn, then fall back to cat
fzf --preview '[[ $(file --mime {}) =~ binary ]] && fzf --preview '[[ $(file --mime {}) =~ binary ]] &&
echo {} is a binary file || echo {} is a binary file ||
(highlight -O ansi -l {} || (bat --style=numbers --color=always {} ||
highlight -O ansi -l {} ||
coderay {} || coderay {} ||
rougify {} || rougify {} ||
cat {}) 2> /dev/null | head -500' cat {}) 2> /dev/null | head -500'
@@ -514,7 +521,8 @@ You can customize the size and position of the preview window using
fzf --height 40% --reverse --preview 'file {}' --preview-window down:1 fzf --height 40% --reverse --preview 'file {}' --preview-window down:1
``` ```
For more advanced examples, see [Key bindings for git with fzf][fzf-git]. For more advanced examples, see [Key bindings for git with fzf][fzf-git]
([code](https://gist.github.com/junegunn/8b572b8d4b5eddd8b85e5f4d40f17236)).
[fzf-git]: https://junegunn.kr/2016/07/fzf-git/ [fzf-git]: https://junegunn.kr/2016/07/fzf-git/
@@ -580,9 +588,9 @@ fzf -m | while read -l r; set result $result $r; end; and vim $result
``` ```
The globbing system is different in fish and thus `**` completion will not work. The globbing system is different in fish and thus `**` completion will not work.
However, the `CTRL-T` command will use the last token on the commandline as the However, the `CTRL-T` command will use the last token on the command-line as the
root folder for the recursive search. For instance, hitting `CTRL-T` at the end root folder for the recursive search. For instance, hitting `CTRL-T` at the end
of the following commandline of the following command-line
```sh ```sh
ls /var/ ls /var/
@@ -598,6 +606,11 @@ valid directory. Example:
set -g 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#^\./##'"
``` ```
Related projects
----------------
https://github.com/junegunn/fzf/wiki/Related-projects
[License](LICENSE) [License](LICENSE)
------------------ ------------------

134
glide.lock generated
View File

@@ -1,134 +0,0 @@
hash: 92a208bfbaecdf8d1ccaf99a465884c49f9cd91f44f1756d7bbf3290795c781b
updated: 2017-12-03T13:37:23.420874333+09:00
imports:
- name: github.com/bjwbell/gensimd
version: 06eb18285485c0d572cc7f024050fc6cb652ed4c
subpackages:
- simd
- name: github.com/codegangsta/cli
version: c6af8847eb2b7b297d07c3ede98903e95e680ef9
- name: github.com/gdamore/encoding
version: b23993cbb6353f0e6aa98d0ee318a34728f628b9
- name: github.com/gdamore/tcell
version: 0a0db94084dfe181108c18508ebd312f12d331fb
subpackages:
- encoding
- name: github.com/lucasb-eyer/go-colorful
version: c900de9dbbc73129068f5af6a823068fc5f2308c
- name: github.com/Masterminds/semver
version: 15d8430ab86497c5c0da827b748823945e1cf1e1
- name: github.com/Masterminds/vcs
version: 6f1c6d150500e452704e9863f68c2559f58616bf
- 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: github.com/mengzhuo/intrinsic
version: 34b800838e0bcd9c5b6abd414e3ad03dc1a686b8
subpackages:
- sse2
- name: github.com/mitchellh/go-homedir
version: b8bc1bf767474819792c23f32d8286a45736f1c6
- name: golang.org/x/crypto
version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e
subpackages:
- acme
- blowfish
- cast5
- chacha20poly1305/internal/chacha20
- curve25519
- ed25519
- ed25519/internal/edwards25519
- hkdf
- nacl/secretbox
- openpgp
- openpgp/armor
- openpgp/elgamal
- openpgp/errors
- openpgp/packet
- openpgp/s2k
- pbkdf2
- pkcs12/internal/rc2
- poly1305
- ripemd160
- salsa20/salsa
- ssh
- ssh/agent
- ssh/terminal
- ssh/testdata
- name: golang.org/x/net
version: a8b9294777976932365dabb6640cf1468d95c70f
subpackages:
- context
- context/ctxhttp
- name: golang.org/x/sys
version: b90f89a1e7a9c1f6b918820b3daa7f08488c8594
subpackages:
- unix
- name: golang.org/x/text
version: 4ee4af566555f5fbe026368b75596286a312663a
subpackages:
- cases
- collate
- collate/build
- currency
- encoding
- encoding/charmap
- encoding/ianaindex
- encoding/internal
- encoding/internal/identifier
- encoding/japanese
- encoding/korean
- encoding/simplifiedchinese
- encoding/traditionalchinese
- encoding/unicode
- encoding/unicode/utf32
- internal
- internal/colltab
- internal/format
- internal/gen
- internal/stringset
- internal/tag
- internal/testtext
- internal/triegen
- internal/ucd
- internal/utf8internal
- language
- language/display
- message
- runes
- secure/bidirule
- transform
- unicode/bidi
- unicode/cldr
- unicode/norm
- unicode/rangetable
- width
- name: golang.org/x/tools
version: 04447353bc504b9a5c02eb227b9ecd252e64ea20
subpackages:
- go/ast/astutil
- go/buildutil
- go/loader
- name: gopkg.in/yaml.v2
version: 287cf08546ab5e7e37d55a84f7ed3fd1db036de5
testImports:
- name: github.com/gopherjs/gopherjs
version: 444abdf920945de5d4a977b572bcc6c674d1e4eb
subpackages:
- js
- name: github.com/jtolds/gls
version: 77f18212c9c7edc9bd6a33d383a7b545ce62f064
- name: github.com/smartystreets/assertions
version: 0b37b35ec7434b77e77a4bb29b79677cced992ea
subpackages:
- internal/go-render/render
- internal/oglematchers
- name: github.com/smartystreets/goconvey
version: e5b2b7c9111590d019a696c7800593f666e1a7f4
subpackages:
- convey
- convey/gotest
- convey/reporting

View File

@@ -1,16 +0,0 @@
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: 0a0db94084dfe181108c18508ebd312f12d331fb
subpackages:
- encoding
- package: golang.org/x/crypto
version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e
subpackages:
- ssh/terminal

17
go.mod Normal file
View File

@@ -0,0 +1,17 @@
module github.com/junegunn/fzf
require (
github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635 // indirect
github.com/gdamore/tcell v0.0.0-20170915061752-0a0db94084df
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
github.com/jtolds/gls v4.2.1+incompatible // indirect
github.com/lucasb-eyer/go-colorful v0.0.0-20170223221042-c900de9dbbc7 // indirect
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c
github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c
github.com/mattn/go-shellwords v1.0.3
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
golang.org/x/crypto v0.0.0-20170728183002-558b6879de74
golang.org/x/sys v0.0.0-20170529185110-b90f89a1e7a9 // indirect
golang.org/x/text v0.0.0-20170530162606-4ee4af566555 // indirect
)

26
go.sum Normal file
View File

@@ -0,0 +1,26 @@
github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635 h1:hheUEMzaOie/wKeIc1WPa7CDVuIO5hqQxjS+dwTQEnI=
github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635/go.mod h1:yrQYJKKDTrHmbYxI7CYi+/hbdiDT2m4Hj+t0ikCjsrQ=
github.com/gdamore/tcell v0.0.0-20170915061752-0a0db94084df h1:tLS1QD2puA1USLvkEnGfOt+Zp2ijtNIK3z2YFaIZZR4=
github.com/gdamore/tcell v0.0.0-20170915061752-0a0db94084df/go.mod h1:tqyG50u7+Ctv1w5VX67kLzKcj9YXR/JSBZQq/+mLl1A=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/lucasb-eyer/go-colorful v0.0.0-20170223221042-c900de9dbbc7 h1:G52I+Gk/wPD4HKvKT0Vxxp9OUPxqKs3OK6rffSPtNkA=
github.com/lucasb-eyer/go-colorful v0.0.0-20170223221042-c900de9dbbc7/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c h1:3nKFouDdpgGUV/uerJcYWH45ZbJzX0SiVWfTgmUeTzc=
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c h1:eFzthqtg3W6Pihj3DMTXLAF4f+ge5r5Ie5g6HLIZAF0=
github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-shellwords v1.0.3 h1:K/VxK7SZ+cvuPgFSLKi5QPI9Vr/ipOf4C1gN+ntueUk=
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
golang.org/x/crypto v0.0.0-20170728183002-558b6879de74 h1:/0jf0Cx3u07Ma4EzUA6NIGuvk9hb3Br6i9V8atthkwk=
golang.org/x/crypto v0.0.0-20170728183002-558b6879de74/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/sys v0.0.0-20170529185110-b90f89a1e7a9 h1:wFe/9vW2TmDagagfMeC56pEcmhyMWEqvuwE9CDAePNo=
golang.org/x/sys v0.0.0-20170529185110-b90f89a1e7a9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.0.0-20170530162606-4ee4af566555 h1:pjwO9HxObpgZBurDvTLFbSinaf3+cKpTAjVfiGaHwrI=
golang.org/x/text v0.0.0-20170530162606-4ee4af566555/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

25
install
View File

@@ -2,7 +2,7 @@
set -u set -u
version=0.17.4 version=0.18.0
auto_completion= auto_completion=
key_bindings= key_bindings=
update_config=2 update_config=2
@@ -73,7 +73,8 @@ for opt in "$@"; do
done done
cd "$(dirname "${BASH_SOURCE[0]}")" cd "$(dirname "${BASH_SOURCE[0]}")"
fzf_base="$(pwd)" fzf_base=$(pwd)
fzf_base_esc=$(printf %q "$fzf_base")
ask() { ask() {
while true; do while true; do
@@ -90,11 +91,13 @@ 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 | awk '{print $1}') output=$("$fzf_base"/bin/fzf --version 2>&1)
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo "Error: $output" echo "Error: $output"
binary_error="Invalid binary" binary_error="Invalid binary"
elif [ "$version" != "$output" ]; then else
output=${output/ */}
if [ "$version" != "$output" ]; then
echo "$output != $version" echo "$output != $version"
binary_error="Invalid version" binary_error="Invalid version"
else else
@@ -102,6 +105,7 @@ check_binary() {
binary_error="" binary_error=""
return 0 return 0
fi fi
fi
rm -f "$fzf_base"/bin/fzf rm -f "$fzf_base"/bin/fzf
return 1 return 1
} }
@@ -109,7 +113,7 @@ check_binary() {
link_fzf_in_path() { link_fzf_in_path() {
if which_fzf="$(command -v fzf)"; then if which_fzf="$(command -v fzf)"; then
echo " - Found in \$PATH" echo " - Found in \$PATH"
echo " - Creating symlink: $which_fzf -> bin/fzf" echo " - Creating symlink: bin/fzf -> $which_fzf"
(cd "$fzf_base"/bin && rm -f fzf && ln -sf "$which_fzf" fzf) (cd "$fzf_base"/bin && rm -f fzf && ln -sf "$which_fzf" fzf)
check_binary && return check_binary && return
fi fi
@@ -192,6 +196,8 @@ case "$archi" in
CYGWIN*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;; CYGWIN*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
MINGW*\ *86) download fzf-$version-windows_${binary_arch:-386}.zip ;; MINGW*\ *86) download fzf-$version-windows_${binary_arch:-386}.zip ;;
MINGW*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;; MINGW*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
MSYS*\ *86) download fzf-$version-windows_${binary_arch:-386}.zip ;;
MSYS*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
*) binary_available=0 binary_error=1 ;; *) binary_available=0 binary_error=1 ;;
esac esac
@@ -265,8 +271,8 @@ for shell in $shells; do
cat > "$src" << EOF cat > "$src" << EOF
# Setup fzf # Setup fzf
# --------- # ---------
if [[ ! "\$PATH" == *$fzf_base/bin* ]]; then if [[ ! "\$PATH" == *$fzf_base_esc/bin* ]]; then
export PATH="\$PATH:$fzf_base/bin" export PATH="\${PATH:+\${PATH}:}$fzf_base/bin"
fi fi
# Auto-completion # Auto-completion
@@ -276,7 +282,6 @@ $fzf_completion
# Key bindings # Key bindings
# ------------ # ------------
$fzf_key_bindings $fzf_key_bindings
EOF EOF
echo "OK" echo "OK"
done done
@@ -285,8 +290,8 @@ done
if [[ "$shells" =~ fish ]]; then if [[ "$shells" =~ fish ]]; then
echo -n "Update fish_user_paths ... " echo -n "Update fish_user_paths ... "
fish << EOF fish << EOF
echo \$fish_user_paths | \grep $fzf_base/bin > /dev/null echo \$fish_user_paths | \grep "$fzf_base"/bin > /dev/null
or set --universal fish_user_paths \$fish_user_paths $fzf_base/bin or set --universal fish_user_paths \$fish_user_paths "$fzf_base"/bin
EOF EOF
[ $? -eq 0 ] && echo "OK" || echo "Failed" [ $? -eq 0 ] && echo "OK" || echo "Failed"

View File

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf-tmux 1 "Jun 2018" "fzf 0.17.4" "fzf-tmux - open fzf in tmux split pane" .TH fzf-tmux 1 "Mar 2019" "fzf 0.18.0" "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

View File

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf 1 "Jun 2018" "fzf 0.17.4" "fzf - a command-line fuzzy finder" .TH fzf 1 "Mar 2019" "fzf 0.18.0" "fzf - a command-line fuzzy finder"
.SH NAME .SH NAME
fzf - a command-line fuzzy finder fzf - a command-line fuzzy finder
@@ -174,6 +174,11 @@ A synonym for \fB--layout=reverse\fB
.TP .TP
.B "--border" .B "--border"
Draw border above and below the finder Draw border above and below the finder
.TP
.B "--no-unicode"
Use ASCII characters instead of Unicode box drawing characters to draw border
.TP .TP
.BI "--margin=" MARGIN .BI "--margin=" MARGIN
Comma-separated expression for margins around the finder. Comma-separated expression for margins around the finder.
@@ -250,6 +255,7 @@ e.g. \fBfzf --color=bg+:24\fR
\fBhl \fRHighlighted substrings \fBhl \fRHighlighted substrings
\fBfg+ \fRText (current line) \fBfg+ \fRText (current line)
\fBbg+ \fRBackground (current line) \fBbg+ \fRBackground (current line)
\fBgutter \fRGutter on the left (defaults to \fBbg+\fR)
\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) \fBborder \fRBorder of the preview window and horizontal separators (\fB--border\fR)
@@ -288,8 +294,11 @@ EXPRESSION\fR for the details).
e.g. \fBfzf --preview='head -$LINES {}'\fR e.g. \fBfzf --preview='head -$LINES {}'\fR
\fBls -l | fzf --preview="echo user={3} when={-4..-2}; cat {-1}" --header-lines=1\fR \fBls -l | fzf --preview="echo user={3} when={-4..-2}; cat {-1}" --header-lines=1\fR
fzf overrides \fB$LINES\fR and \fB$COLUMNS\fR so that they represent the exact fzf exports \fB$FZF_PREVIEW_LINES\fR and \fB$FZF_PREVIEW_COLUMNS\fR so that
size of the preview window. they represent the exact size of the preview window. (It also overrides
\fB$LINES\fR and \fB$COLUMNS\fR with the same values but they can be reset
by the default shell, so prefer to refer to the ones with \fBFZF_PREVIEW_\fR
prefix.)
A placeholder expression starting with \fB+\fR flag will be replaced to the A placeholder expression starting with \fB+\fR flag will be replaced to the
space-separated list of the selected lines (or the current line if no selection space-separated list of the selected lines (or the current line if no selection
@@ -301,7 +310,9 @@ e.g. \fBfzf --multi --preview='head -10 {+}'\fR
When using a field index expression, leading and trailing whitespace is stripped When using a field index expression, leading and trailing whitespace is stripped
from the replacement string. To preserve the whitespace, use the \fBs\fR flag. from the replacement string. To preserve the whitespace, use the \fBs\fR flag.
Also, \fB{q}\fR is replaced to the current query string. Also, \fB{q}\fR is replaced to the current query string, and \fB{n}\fR is
replaced to zero-based ordinal index of the line. Use \fB{+n}\fR if you want
all index numbers when multiple lines are selected.
Note that you can escape a placeholder pattern by prepending a backslash. Note that you can escape a placeholder pattern by prepending a backslash.
@@ -390,7 +401,8 @@ Note that most options have the opposite versions with \fB--no-\fR prefix.
.SH ENVIRONMENT VARIABLES .SH ENVIRONMENT VARIABLES
.TP .TP
.B FZF_DEFAULT_COMMAND .B FZF_DEFAULT_COMMAND
Default command to use when input is tty Default command to use when input is tty. On *nix systems, fzf runs the command
with \fBsh -c\fR, so make sure that it's POSIX-compliant.
.TP .TP
.B FZF_DEFAULT_OPTS .B FZF_DEFAULT_OPTS
Default options. e.g. \fBexport FZF_DEFAULT_OPTS="--extended --cycle"\fR Default options. e.g. \fBexport FZF_DEFAULT_OPTS="--extended --cycle"\fR

View File

@@ -357,7 +357,7 @@ try
throw v:exception throw v:exception
endtry endtry
if has('nvim') && !has_key(dict, 'dir') if !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') if has('win32unix') && has_key(dict, 'dir')
@@ -455,15 +455,18 @@ endfunction
function! s:pushd(dict) function! s:pushd(dict)
if s:present(a:dict, 'dir') if s:present(a:dict, 'dir')
let cwd = s:fzf_getcwd() let cwd = s:fzf_getcwd()
if get(a:dict, 'prev_dir', '') ==# cwd let w:fzf_pushd = {
return 1 \ 'command': haslocaldir() ? 'lcd' : (exists(':tcd') && haslocaldir(-1) ? 'tcd' : 'cd'),
endif \ 'origin': cwd,
let a:dict.prev_dir = cwd \ 'bufname': bufname('')
\ }
execute 'lcd' s:escape(a:dict.dir) execute 'lcd' s:escape(a:dict.dir)
let a:dict.dir = s:fzf_getcwd() let cwd = s:fzf_getcwd()
return 1 let w:fzf_pushd.dir = cwd
let a:dict.pushd = w:fzf_pushd
return cwd
endif endif
return 0 return ''
endfunction endfunction
augroup fzf_popd augroup fzf_popd
@@ -472,11 +475,29 @@ augroup fzf_popd
augroup END augroup END
function! s:dopopd() function! s:dopopd()
if !exists('w:fzf_dir') || s:fzf_getcwd() != w:fzf_dir[1] if !exists('w:fzf_pushd')
return return
endif endif
execute 'lcd' s:escape(w:fzf_dir[0])
unlet w:fzf_dir " FIXME: We temporarily change the working directory to 'dir' entry
" of options dictionary (set to the current working directory if not given)
" before running fzf.
"
" e.g. call fzf#run({'dir': '/tmp', 'source': 'ls', 'sink': 'e'})
"
" After processing the sink function, we have to restore the current working
" directory. But doing so may not be desirable if the function changed the
" working directory on purpose.
"
" So how can we tell if we should do it or not? A simple heuristic we use
" here is that we change directory only if the current working directory
" matches 'dir' entry. However, it is possible that the sink function did
" change the directory to 'dir'. In that case, the user will have an
" unexpected result.
if s:fzf_getcwd() ==# w:fzf_pushd.dir && (!&autochdir || w:fzf_pushd.bufname ==# bufname(''))
execute w:fzf_pushd.command s:escape(w:fzf_pushd.origin)
endif
unlet w:fzf_pushd
endfunction endfunction
function! s:xterm_launcher() function! s:xterm_launcher()
@@ -534,9 +555,7 @@ function! s:execute(dict, command, use_height, temps) abort
let fzf.dict = a:dict let fzf.dict = a:dict
let fzf.temps = a:temps 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
if s:present(self.dict, 'dir') call s:pushd(self.dict)
execute 'lcd' s:escape(self.dict.dir)
endif
let lines = s:collect(self.temps) let lines = s:collect(self.temps)
call s:callback(self.dict, lines) call s:callback(self.dict, lines)
endfunction endfunction
@@ -563,9 +582,10 @@ endfunction
function! s:execute_tmux(dict, command, temps) abort function! s:execute_tmux(dict, command, temps) abort
let command = a:command let command = a:command
if s:pushd(a:dict) let cwd = s:pushd(a:dict)
if len(cwd)
" -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 = join(['cd', fzf#shellescape(a:dict.dir), '&&', command]) let command = join(['cd', fzf#shellescape(cwd), '&&', command])
endif endif
call system(command) call system(command)
@@ -587,8 +607,9 @@ function! s:calc_size(max, val, dict)
let srcsz = len(a:dict.source) let srcsz = len(a:dict.source)
endif endif
let opts = s:evaluate_opts(get(a:dict, 'options', '')).$FZF_DEFAULT_OPTS let opts = $FZF_DEFAULT_OPTS.' '.s:evaluate_opts(get(a:dict, 'options', ''))
let margin = stridx(opts, '--inline-info') > stridx(opts, '--no-inline-info') ? 1 : 2 let margin = stridx(opts, '--inline-info') > stridx(opts, '--no-inline-info') ? 1 : 2
let margin += stridx(opts, '--border') > stridx(opts, '--no-border') ? 2 : 0
let margin += stridx(opts, '--header') > stridx(opts, '--no-header') let margin += stridx(opts, '--header') > stridx(opts, '--no-header')
return srcsz >= 0 ? min([srcsz + margin, size]) : size return srcsz >= 0 ? min([srcsz + margin, size]) : size
endfunction endfunction
@@ -686,9 +707,7 @@ function! s:execute_term(dict, command, temps) abort
endfunction endfunction
try try
if s:present(a:dict, 'dir') call s:pushd(a:dict)
execute 'lcd' s:escape(a:dict.dir)
endif
if s:is_win if s:is_win
let fzf.temps.batchfile = s:fzf_tempname().'.bat' let fzf.temps.batchfile = s:fzf_tempname().'.bat'
call writefile(s:wrap_cmds(a:command), fzf.temps.batchfile) call writefile(s:wrap_cmds(a:command), fzf.temps.batchfile)
@@ -706,9 +725,7 @@ function! s:execute_term(dict, command, temps) abort
endif endif
endif endif
finally finally
if s:present(a:dict, 'dir') call s:dopopd()
lcd -
endif
endtry endtry
setlocal nospell bufhidden=wipe nobuflisted nonumber setlocal nospell bufhidden=wipe nobuflisted nonumber
setf fzf setf fzf
@@ -727,21 +744,9 @@ function! s:collect(temps) abort
endfunction endfunction
function! s:callback(dict, lines) abort function! s:callback(dict, lines) abort
" Since anything can be done in the sink function, there is no telling that let popd = has_key(a:dict, 'pushd')
" the change of the working directory was made by &autochdir setting.
"
" We use the following heuristic to determine whether to restore CWD:
" - Always restore the current directory when &autochdir is disabled.
" FIXME This makes it impossible to change directory from inside the sink
" function when &autochdir is not used.
" - In case of an error or an interrupt, a:lines will be empty.
" And it will be an array of a single empty string when fzf was finished
" without a match. In these cases, we presume that the change of the
" directory is not expected and should be undone.
let popd = has_key(a:dict, 'prev_dir') &&
\ (!&autochdir || (empty(a:lines) || len(a:lines) == 1 && empty(a:lines[0])))
if popd if popd
let w:fzf_dir = [a:dict.prev_dir, a:dict.dir] let w:fzf_pushd = a:dict.pushd
endif endif
try try
@@ -765,7 +770,7 @@ function! s:callback(dict, lines) abort
" We may have opened a new window or tab " We may have opened a new window or tab
if popd if popd
let w:fzf_dir = [a:dict.prev_dir, a:dict.dir] let w:fzf_pushd = a:dict.pushd
call s:dopopd() call s:dopopd()
endif endif
endfunction endfunction

View File

@@ -9,6 +9,8 @@
# - $FZF_COMPLETION_TRIGGER (default: '**') # - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty) # - $FZF_COMPLETION_OPTS (default: empty)
if [[ $- =~ i ]]; then
# To use custom commands instead of find, override _fzf_compgen_{path,dir} # To use custom commands instead of find, override _fzf_compgen_{path,dir}
if ! declare -f _fzf_compgen_path > /dev/null; then if ! declare -f _fzf_compgen_path > /dev/null; then
_fzf_compgen_path() { _fzf_compgen_path() {
@@ -241,7 +243,7 @@ _fzf_complete_telnet() {
_fzf_complete_ssh() { _fzf_complete_ssh() {
_fzf_complete '+m' "$@" < <( _fzf_complete '+m' "$@" < <(
cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host' | command grep -v '*' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}') \ cat <(cat ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host ' | command grep -v '[*?]' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}') \
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \ <(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') | <(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
awk '{if (length($2) > 0) {print $2}}' | sort -u awk '{if (length($2) > 0) {print $2}}' | sort -u
@@ -330,3 +332,5 @@ complete -F _fzf_complete_export -o default -o bashdefault export
complete -F _fzf_complete_unalias -o default -o bashdefault unalias complete -F _fzf_complete_unalias -o default -o bashdefault unalias
unset cmd d_cmds a_cmds x_cmds unset cmd d_cmds a_cmds x_cmds
fi

View File

@@ -9,6 +9,8 @@
# - $FZF_COMPLETION_TRIGGER (default: '**') # - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty) # - $FZF_COMPLETION_OPTS (default: empty)
if [[ $- =~ i ]]; then
# To use custom commands instead of find, override _fzf_compgen_{path,dir} # To use custom commands instead of find, override _fzf_compgen_{path,dir}
if ! declare -f _fzf_compgen_path > /dev/null; then if ! declare -f _fzf_compgen_path > /dev/null; then
_fzf_compgen_path() { _fzf_compgen_path() {
@@ -60,8 +62,7 @@ __fzf_generic_path_completion() {
if [ -n "$matches" ]; then if [ -n "$matches" ]; then
LBUFFER="$lbuf$matches$tail" LBUFFER="$lbuf$matches$tail"
fi fi
zle redisplay zle reset-prompt
typeset -f zle-line-init >/dev/null && zle zle-line-init
break break
fi fi
dir=$(dirname "$dir") dir=$(dirname "$dir")
@@ -100,8 +101,7 @@ _fzf_complete() {
if [ -n "$matches" ]; then if [ -n "$matches" ]; then
LBUFFER="$lbuf$matches" LBUFFER="$lbuf$matches"
fi fi
zle redisplay zle reset-prompt
typeset -f zle-line-init >/dev/null && zle zle-line-init
command rm -f "$fifo" command rm -f "$fifo"
} }
@@ -114,7 +114,8 @@ _fzf_complete_telnet() {
_fzf_complete_ssh() { _fzf_complete_ssh() {
_fzf_complete '+m' "$@" < <( _fzf_complete '+m' "$@" < <(
command cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host' | command grep -v '*' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}') \ setopt localoptions nonomatch
command cat <(cat ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host ' | command grep -v '[*?]' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}') \
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \ <(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') | <(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
awk '{if (length($2) > 0) {print $2}}' | sort -u awk '{if (length($2) > 0) {print $2}}' | sort -u
@@ -165,8 +166,7 @@ fzf-completion() {
if [ -n "$matches" ]; then if [ -n "$matches" ]; then
LBUFFER="$LBUFFER$matches" LBUFFER="$LBUFFER$matches"
fi fi
zle redisplay zle reset-prompt
typeset -f zle-line-init >/dev/null && zle zle-line-init
# Trigger sequence given # Trigger sequence given
elif [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then elif [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir}) d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})
@@ -195,3 +195,5 @@ fzf-completion() {
zle -N fzf-completion zle -N fzf-completion
bindkey '^I' fzf-completion bindkey '^I' fzf-completion
fi

View File

@@ -40,8 +40,8 @@ function fzf_key_bindings
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"
set -l FISH_MAJOR (echo $FISH_VERSION | cut -f1 -d.) set -l FISH_MAJOR (echo $version | cut -f1 -d.)
set -l FISH_MINOR (echo $FISH_VERSION | cut -f2 -d.) set -l FISH_MINOR (echo $version | cut -f2 -d.)
# history's -z flag is needed for multi-line support. # 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 # history's -z flag was added in fish 2.4.0, so don't use it for versions

View File

@@ -29,8 +29,7 @@ __fzfcmd() {
fzf-file-widget() { fzf-file-widget() {
LBUFFER="${LBUFFER}$(__fsel)" LBUFFER="${LBUFFER}$(__fsel)"
local ret=$? local ret=$?
zle redisplay zle reset-prompt
typeset -f zle-line-init >/dev/null && zle zle-line-init
return $ret return $ret
} }
zle -N fzf-file-widget zle -N fzf-file-widget
@@ -59,7 +58,6 @@ fzf-cd-widget() {
cd "$dir" cd "$dir"
local ret=$? local ret=$?
zle fzf-redraw-prompt zle fzf-redraw-prompt
typeset -f zle-line-init >/dev/null && zle zle-line-init
return $ret return $ret
} }
zle -N fzf-cd-widget zle -N fzf-cd-widget
@@ -78,8 +76,7 @@ fzf-history-widget() {
zle vi-fetch-history -n $num zle vi-fetch-history -n $num
fi fi
fi fi
zle redisplay zle reset-prompt
typeset -f zle-line-init >/dev/null && zle zle-line-init
return $ret return $ret
} }
zle -N fzf-history-widget zle -N fzf-history-widget

View File

@@ -32,6 +32,55 @@ func (s *ansiState) equals(t *ansiState) bool {
return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr
} }
func (s *ansiState) ToString() string {
if !s.colored() {
return ""
}
ret := ""
if s.attr&tui.Bold > 0 {
ret += "1;"
}
if s.attr&tui.Dim > 0 {
ret += "2;"
}
if s.attr&tui.Italic > 0 {
ret += "3;"
}
if s.attr&tui.Underline > 0 {
ret += "4;"
}
if s.attr&tui.Blink > 0 {
ret += "5;"
}
if s.attr&tui.Reverse > 0 {
ret += "7;"
}
ret += toAnsiString(s.fg, 30) + toAnsiString(s.bg, 40)
return "\x1b[" + strings.TrimSuffix(ret, ";") + "m"
}
func toAnsiString(color tui.Color, offset int) string {
col := int(color)
ret := ""
if col == -1 {
ret += strconv.Itoa(offset + 9)
} else if col < 8 {
ret += strconv.Itoa(offset + col)
} else if col < 16 {
ret += strconv.Itoa(offset - 30 + 90 + col - 8)
} else if col < 256 {
ret += strconv.Itoa(offset+8) + ";5;" + strconv.Itoa(col)
} else if col >= (1 << 24) {
r := strconv.Itoa((col >> 16) & 0xff)
g := strconv.Itoa((col >> 8) & 0xff)
b := strconv.Itoa(col & 0xff)
ret += strconv.Itoa(offset+8) + ";2;" + r + ";" + g + ";" + b
}
return ret + ";"
}
var ansiRegex *regexp.Regexp var ansiRegex *regexp.Regexp
func init() { func init() {

View File

@@ -2,6 +2,7 @@ package fzf
import ( import (
"fmt" "fmt"
"strings"
"testing" "testing"
"github.com/junegunn/fzf/src/tui" "github.com/junegunn/fzf/src/tui"
@@ -156,3 +157,31 @@ func TestExtractColor(t *testing.T) {
assert((*offsets)[1], 6, 11, 200, 100, false) assert((*offsets)[1], 6, 11, 200, 100, false)
}) })
} }
func TestAnsiCodeStringConversion(t *testing.T) {
assert := func(code string, prevState *ansiState, expected string) {
state := interpretCode(code, prevState)
if expected != state.ToString() {
t.Errorf("expected: %s, actual: %s",
strings.Replace(expected, "\x1b[", "\\x1b[", -1),
strings.Replace(state.ToString(), "\x1b[", "\\x1b[", -1))
}
}
assert("\x1b[m", nil, "")
assert("\x1b[m", &ansiState{attr: tui.Blink}, "")
assert("\x1b[31m", nil, "\x1b[31;49m")
assert("\x1b[41m", nil, "\x1b[39;41m")
assert("\x1b[92m", nil, "\x1b[92;49m")
assert("\x1b[102m", nil, "\x1b[39;102m")
assert("\x1b[31m", &ansiState{fg: 4, bg: 4}, "\x1b[31;44m")
assert("\x1b[1;2;31m", &ansiState{fg: 2, bg: -1, attr: tui.Reverse}, "\x1b[1;2;7;31;49m")
assert("\x1b[38;5;100;48;5;200m", nil, "\x1b[38;5;100;48;5;200m")
assert("\x1b[48;5;100;38;5;200m", nil, "\x1b[38;5;200;48;5;100m")
assert("\x1b[48;5;100;38;2;10;20;30;1m", nil, "\x1b[1;38;2;10;20;30;48;5;100m")
assert("\x1b[48;5;100;38;2;10;20;30;7m",
&ansiState{attr: tui.Dim | tui.Italic, fg: 1, bg: 1},
"\x1b[2;3;7;38;2;10;20;30;48;5;100m")
}

View File

@@ -9,7 +9,7 @@ import (
const ( const (
// Current version // Current version
version = "0.17.4" version = "0.18.0"
// Core // Core
coordinatorDelayMax time.Duration = 100 * time.Millisecond coordinatorDelayMax time.Duration = 100 * time.Millisecond
@@ -25,6 +25,8 @@ const (
initialDelay = 20 * time.Millisecond initialDelay = 20 * time.Millisecond
initialDelayTac = 100 * time.Millisecond initialDelayTac = 100 * time.Millisecond
spinnerDuration = 200 * time.Millisecond spinnerDuration = 200 * time.Millisecond
previewCancelWait = 500 * time.Millisecond
maxPatternLength = 300
// Matcher // Matcher
numPartitionsMultiplier = 8 numPartitionsMultiplier = 8
@@ -75,6 +77,7 @@ const (
) )
const ( const (
exitCancel = -1
exitOk = 0 exitOk = 0
exitNoMatch = 1 exitNoMatch = 1
exitError = 2 exitError = 2

View File

@@ -63,12 +63,14 @@ func Run(opts *Options, revision string) {
ansiProcessor := func(data []byte) (util.Chars, *[]ansiOffset) { ansiProcessor := func(data []byte) (util.Chars, *[]ansiOffset) {
return util.ToChars(data), nil return util.ToChars(data), nil
} }
var lineAnsiState, prevLineAnsiState *ansiState
if opts.Ansi { if opts.Ansi {
if opts.Theme != nil { if opts.Theme != nil {
var state *ansiState
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) { ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
trimmed, offsets, newState := extractColor(string(data), state, nil) prevLineAnsiState = lineAnsiState
state = newState trimmed, offsets, newState := extractColor(string(data), lineAnsiState, nil)
lineAnsiState = newState
return util.ToChars([]byte(trimmed)), offsets return util.ToChars([]byte(trimmed)), offsets
} }
} else { } else {
@@ -100,6 +102,22 @@ func Run(opts *Options, revision string) {
} else { } else {
chunkList = NewChunkList(func(item *Item, data []byte) bool { chunkList = NewChunkList(func(item *Item, data []byte) bool {
tokens := Tokenize(string(data), opts.Delimiter) tokens := Tokenize(string(data), opts.Delimiter)
if opts.Ansi && opts.Theme != nil && len(tokens) > 1 {
var ansiState *ansiState
if prevLineAnsiState != nil {
ansiStateDup := *prevLineAnsiState
ansiState = &ansiStateDup
}
for _, token := range tokens {
prevAnsiState := ansiState
_, _, ansiState = extractColor(token.text.ToString(), ansiState, nil)
if prevAnsiState != nil {
token.text.Prepend("\x1b[m" + prevAnsiState.ToString())
} else {
token.text.Prepend("\x1b[m")
}
}
}
trans := Transform(tokens, opts.WithNth) trans := Transform(tokens, opts.WithNth)
transformed := joinTokens(trans) transformed := joinTokens(trans)
if len(header) < opts.HeaderLines { if len(header) < opts.HeaderLines {
@@ -149,6 +167,7 @@ func Run(opts *Options, revision string) {
} }
pattern := patternBuilder([]rune(*opts.Filter)) pattern := patternBuilder([]rune(*opts.Filter))
matcher.sort = pattern.sortable
found := false found := false
if streamingFilter { if streamingFilter {

View File

@@ -230,5 +230,5 @@ func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final
} else { } else {
event = reqRetry event = reqRetry
} }
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort}) m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable})
} }

View File

@@ -195,6 +195,7 @@ type Options struct {
HeaderLines int HeaderLines int
Margin [4]sizeSpec Margin [4]sizeSpec
Bordered bool Bordered bool
Unicode bool
Tabstop int Tabstop int
ClearOnExit bool ClearOnExit bool
Version bool Version bool
@@ -244,6 +245,7 @@ func defaultOptions() *Options {
Header: make([]string, 0), Header: make([]string, 0),
HeaderLines: 0, HeaderLines: 0,
Margin: defaultMargin(), Margin: defaultMargin(),
Unicode: true,
Tabstop: 8, Tabstop: 8,
ClearOnExit: true, ClearOnExit: true,
Version: false} Version: false}
@@ -576,6 +578,8 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
theme.Current = ansi theme.Current = ansi
case "bg+": case "bg+":
theme.DarkBg = ansi theme.DarkBg = ansi
case "gutter":
theme.Gutter = ansi
case "hl": case "hl":
theme.Match = ansi theme.Match = ansi
case "hl+": case "hl+":
@@ -1150,6 +1154,10 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Bordered = false opts.Bordered = false
case "--border": case "--border":
opts.Bordered = true opts.Bordered = true
case "--no-unicode":
opts.Unicode = false
case "--unicode":
opts.Unicode = true
case "--margin": case "--margin":
opts.Margin = parseMargin( opts.Margin = parseMargin(
nextString(allArgs, &i, "margin required (TRBL / TB,RL / T,RL,B / T,R,B,L)")) nextString(allArgs, &i, "margin required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))

View File

@@ -52,6 +52,7 @@ type Pattern struct {
forward bool forward bool
text []rune text []rune
termSets []termSet termSets []termSet
sortable bool
cacheable bool cacheable bool
cacheKey string cacheKey string
delimiter Delimiter delimiter Delimiter
@@ -101,21 +102,30 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
} }
caseSensitive := true caseSensitive := true
sortable := true
termSets := []termSet{} termSets := []termSet{}
if extended { if extended {
termSets = parseTerms(fuzzy, caseMode, normalize, asString) termSets = parseTerms(fuzzy, caseMode, normalize, asString)
// We should not sort the result if there are only inverse search terms
sortable = false
Loop: Loop:
for _, termSet := range termSets { for _, termSet := range termSets {
for idx, term := range termSet { for idx, term := range termSet {
if !term.inv {
sortable = true
}
// If the query contains inverse search terms or OR operators, // If the query contains inverse search terms or OR operators,
// we cannot cache the search scope // we cannot cache the search scope
if !cacheable || idx > 0 || term.inv || fuzzy && term.typ != termFuzzy || !fuzzy && term.typ != termExact { if !cacheable || idx > 0 || term.inv || fuzzy && term.typ != termFuzzy || !fuzzy && term.typ != termExact {
cacheable = false cacheable = false
if sortable {
// Can't break until we see at least one non-inverse term
break Loop break Loop
} }
} }
} }
}
} else { } else {
lowerString := strings.ToLower(asString) lowerString := strings.ToLower(asString)
caseSensitive = caseMode == CaseRespect || caseSensitive = caseMode == CaseRespect ||
@@ -134,6 +144,7 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
forward: forward, forward: forward,
text: []rune(asString), text: []rune(asString),
termSets: termSets, termSets: termSets,
sortable: sortable,
cacheable: cacheable, cacheable: cacheable,
nth: nth, nth: nth,
delimiter: delimiter, delimiter: delimiter,

View File

@@ -103,7 +103,7 @@ func (r *Reader) readFromStdin() bool {
} }
func (r *Reader) readFromCommand(shell string, cmd string) bool { func (r *Reader) readFromCommand(shell string, cmd string) bool {
listCommand := util.ExecCommandWith(shell, cmd) listCommand := util.ExecCommandWith(shell, cmd, false)
out, err := listCommand.StdoutPipe() out, err := listCommand.StdoutPipe()
if err != nil { if err != nil {
return false return false

View File

@@ -24,7 +24,7 @@ import (
var placeholder *regexp.Regexp var placeholder *regexp.Regexp
func init() { func init() {
placeholder = regexp.MustCompile("\\\\?(?:{[+s]*[0-9,-.]*}|{q})") placeholder = regexp.MustCompile("\\\\?(?:{[+s]*[0-9,-.]*}|{q}|{\\+?n})")
} }
type jumpMode int type jumpMode int
@@ -40,6 +40,7 @@ type previewer struct {
lines int lines int
offset int offset int
enabled bool enabled bool
more bool
} }
type itemLine struct { type itemLine struct {
@@ -59,6 +60,7 @@ type Terminal struct {
inlineInfo bool inlineInfo bool
prompt string prompt string
promptLen int promptLen int
queryLen [2]int
layout layoutType layout layoutType
fullscreen bool fullscreen bool
hscroll bool hscroll bool
@@ -68,6 +70,7 @@ type Terminal struct {
cx int cx int
cy int cy int
offset int offset int
xoffset int
yanked []rune yanked []rune
input []rune input []rune
multi bool multi bool
@@ -86,6 +89,7 @@ type Terminal struct {
tabstop int tabstop int
margin [4]sizeSpec margin [4]sizeSpec
strong tui.Attr strong tui.Attr
unicode bool
bordered bool bordered bool
cleanExit bool cleanExit bool
border tui.Window border tui.Window
@@ -112,6 +116,7 @@ type Terminal struct {
prevLines []itemLine prevLines []itemLine
suppress bool suppress bool
startChan chan bool startChan chan bool
killChan chan int
slab *util.Slab slab *util.Slab
theme *tui.ColorTheme theme *tui.ColorTheme
tui tui.Renderer tui tui.Renderer
@@ -224,6 +229,7 @@ const (
type placeholderFlags struct { type placeholderFlags struct {
plus bool plus bool
preserveSpace bool preserveSpace bool
number bool
query bool query bool
} }
@@ -364,6 +370,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
t := Terminal{ t := Terminal{
initDelay: delay, initDelay: delay,
inlineInfo: opts.InlineInfo, inlineInfo: opts.InlineInfo,
queryLen: [2]int{0, 0},
layout: opts.Layout, layout: opts.Layout,
fullscreen: fullscreen, fullscreen: fullscreen,
hscroll: opts.Hscroll, hscroll: opts.Hscroll,
@@ -373,6 +380,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
cx: len(input), cx: len(input),
cy: 0, cy: 0,
offset: 0, offset: 0,
xoffset: 0,
yanked: []rune{}, yanked: []rune{},
input: input, input: input,
multi: opts.Multi, multi: opts.Multi,
@@ -385,6 +393,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
printQuery: opts.PrintQuery, printQuery: opts.PrintQuery,
history: opts.History, history: opts.History,
margin: opts.Margin, margin: opts.Margin,
unicode: opts.Unicode,
bordered: opts.Bordered, bordered: opts.Bordered,
cleanExit: opts.ClearOnExit, cleanExit: opts.ClearOnExit,
strong: strongAttr, strong: strongAttr,
@@ -402,7 +411,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
selected: make(map[int32]selectedItem), selected: make(map[int32]selectedItem),
reqBox: util.NewEventBox(), reqBox: util.NewEventBox(),
preview: opts.Preview, preview: opts.Preview,
previewer: previewer{"", 0, 0, previewBox != nil && !opts.Preview.hidden}, previewer: previewer{"", 0, 0, previewBox != nil && !opts.Preview.hidden, false},
previewBox: previewBox, previewBox: previewBox,
eventBox: eventBox, eventBox: eventBox,
mutex: sync.Mutex{}, mutex: sync.Mutex{},
@@ -410,6 +419,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
slab: util.MakeSlab(slab16Size, slab32Size), slab: util.MakeSlab(slab16Size, slab32Size),
theme: opts.Theme, theme: opts.Theme,
startChan: make(chan bool, 1), startChan: make(chan bool, 1),
killChan: make(chan int),
tui: renderer, tui: renderer,
initFunc: func() { renderer.Init() }} initFunc: func() { renderer.Init() }}
t.prompt, t.promptLen = t.processTabs([]rune(opts.Prompt), 0) t.prompt, t.promptLen = t.processTabs([]rune(opts.Prompt), 0)
@@ -593,11 +603,12 @@ func (t *Terminal) resizeWindows() {
marginInt[0]-1, marginInt[0]-1,
marginInt[3], marginInt[3],
width, width,
height+2, tui.BorderHorizontal) height+2, tui.MakeBorderStyle(tui.BorderHorizontal, t.unicode))
} }
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
if previewVisible { if previewVisible {
createPreviewWindow := func(y int, x int, w int, h int) { createPreviewWindow := func(y int, x int, w int, h int) {
t.pborder = t.tui.NewWindow(y, x, w, h, tui.BorderAround) t.pborder = t.tui.NewWindow(y, x, w, h, tui.MakeBorderStyle(tui.BorderAround, t.unicode))
pwidth := w - 4 pwidth := w - 4
// ncurses auto-wraps the line when the cursor reaches the right-end of // ncurses auto-wraps the line when the cursor reaches the right-end of
// the window. To prevent unintended line-wraps, we use the width one // the window. To prevent unintended line-wraps, we use the width one
@@ -605,29 +616,28 @@ func (t *Terminal) resizeWindows() {
if !t.preview.wrap && t.tui.DoesAutoWrap() { if !t.preview.wrap && t.tui.DoesAutoWrap() {
pwidth += 1 pwidth += 1
} }
t.pwindow = t.tui.NewWindow(y+1, x+2, pwidth, h-2, tui.BorderNone) t.pwindow = t.tui.NewWindow(y+1, x+2, pwidth, h-2, noBorder)
os.Setenv("FZF_PREVIEW_HEIGHT", strconv.Itoa(h-2))
} }
switch t.preview.position { switch t.preview.position {
case posUp: case posUp:
pheight := calculateSize(height, t.preview.size, minHeight, 3) pheight := calculateSize(height, t.preview.size, minHeight, 3)
t.window = t.tui.NewWindow( t.window = t.tui.NewWindow(
marginInt[0]+pheight, marginInt[3], width, height-pheight, tui.BorderNone) marginInt[0]+pheight, marginInt[3], width, height-pheight, noBorder)
createPreviewWindow(marginInt[0], marginInt[3], width, pheight) createPreviewWindow(marginInt[0], marginInt[3], width, pheight)
case posDown: case posDown:
pheight := calculateSize(height, t.preview.size, minHeight, 3) pheight := calculateSize(height, t.preview.size, minHeight, 3)
t.window = t.tui.NewWindow( t.window = t.tui.NewWindow(
marginInt[0], marginInt[3], width, height-pheight, tui.BorderNone) marginInt[0], marginInt[3], width, height-pheight, noBorder)
createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight) createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
case posLeft: case posLeft:
pwidth := calculateSize(width, t.preview.size, minWidth, 5) pwidth := calculateSize(width, t.preview.size, minWidth, 5)
t.window = t.tui.NewWindow( t.window = t.tui.NewWindow(
marginInt[0], marginInt[3]+pwidth, width-pwidth, height, tui.BorderNone) marginInt[0], marginInt[3]+pwidth, width-pwidth, height, noBorder)
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height) createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
case posRight: case posRight:
pwidth := calculateSize(width, t.preview.size, minWidth, 5) pwidth := calculateSize(width, t.preview.size, minWidth, 5)
t.window = t.tui.NewWindow( t.window = t.tui.NewWindow(
marginInt[0], marginInt[3], width-pwidth, height, tui.BorderNone) marginInt[0], marginInt[3], width-pwidth, height, noBorder)
createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height) createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height)
} }
} else { } else {
@@ -635,12 +645,11 @@ func (t *Terminal) resizeWindows() {
marginInt[0], marginInt[0],
marginInt[3], marginInt[3],
width, width,
height, tui.BorderNone) height, noBorder)
} }
for i := 0; i < t.window.Height(); i++ { for i := 0; i < t.window.Height(); i++ {
t.window.MoveAndClear(i, 0) t.window.MoveAndClear(i, 0)
} }
t.truncateQuery()
} }
func (t *Terminal) move(y int, x int, clear bool) { func (t *Terminal) move(y int, x int, clear bool) {
@@ -668,20 +677,44 @@ func (t *Terminal) move(y int, x int, clear bool) {
} }
} }
func (t *Terminal) truncateQuery() {
t.input, _ = t.trimRight(t.input, maxPatternLength)
t.cx = util.Constrain(t.cx, 0, len(t.input))
}
func (t *Terminal) updatePromptOffset() ([]rune, []rune) {
maxWidth := util.Max(1, t.window.Width()-t.promptLen-1)
_, overflow := t.trimLeft(t.input[:t.cx], maxWidth)
minOffset := int(overflow)
maxOffset := util.Min(util.Min(len(t.input), minOffset+maxWidth), t.cx)
t.xoffset = util.Constrain(t.xoffset, minOffset, maxOffset)
before, _ := t.trimLeft(t.input[t.xoffset:t.cx], maxWidth)
beforeLen := t.displayWidth(before)
after, _ := t.trimRight(t.input[t.cx:], maxWidth-beforeLen)
afterLen := t.displayWidth(after)
t.queryLen = [2]int{beforeLen, afterLen}
return before, after
}
func (t *Terminal) placeCursor() { func (t *Terminal) placeCursor() {
t.move(0, t.promptLen+t.displayWidth(t.input[:t.cx]), false) t.move(0, t.promptLen+t.queryLen[0], false)
} }
func (t *Terminal) printPrompt() { func (t *Terminal) printPrompt() {
t.move(0, 0, true) t.move(0, 0, true)
t.window.CPrint(tui.ColPrompt, t.strong, t.prompt) t.window.CPrint(tui.ColPrompt, t.strong, t.prompt)
t.window.CPrint(tui.ColNormal, t.strong, string(t.input))
before, after := t.updatePromptOffset()
t.window.CPrint(tui.ColNormal, t.strong, string(before))
t.window.CPrint(tui.ColNormal, t.strong, string(after))
} }
func (t *Terminal) printInfo() { func (t *Terminal) printInfo() {
pos := 0 pos := 0
if t.inlineInfo { if t.inlineInfo {
pos = t.promptLen + t.displayWidth(t.input) + 1 pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
if pos+len(" < ") > t.window.Width() { if pos+len(" < ") > t.window.Width() {
return return
} }
@@ -805,15 +838,16 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool) {
} }
t.move(line, 0, false) t.move(line, 0, false)
t.window.CPrint(tui.ColCursor, t.strong, label)
if current { if current {
t.window.CPrint(tui.ColCurrentCursor, t.strong, label)
if selected { if selected {
t.window.CPrint(tui.ColSelected, t.strong, ">") t.window.CPrint(tui.ColCurrentSelected, t.strong, ">")
} else { } else {
t.window.CPrint(tui.ColCurrent, t.strong, " ") t.window.CPrint(tui.ColCurrentSelected, t.strong, " ")
} }
newLine.width = t.printHighlighted(result, t.strong, tui.ColCurrent, tui.ColCurrentMatch, true, true) newLine.width = t.printHighlighted(result, t.strong, tui.ColCurrent, tui.ColCurrentMatch, true, true)
} else { } else {
t.window.CPrint(tui.ColCursor, t.strong, label)
if selected { if selected {
t.window.CPrint(tui.ColSelected, t.strong, ">") t.window.CPrint(tui.ColSelected, t.strong, ">")
} else { } else {
@@ -994,25 +1028,26 @@ 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() height := t.pwindow.Height()
t.previewer.more = t.previewer.offset > 0
var ansi *ansiState var ansi *ansiState
for { for ; ; lineNo++ {
line, err := reader.ReadString('\n') line, err := reader.ReadString('\n')
eof := err == io.EOF eof := err == io.EOF
if !eof { if !eof {
line = line[:len(line)-1] line = line[:len(line)-1]
} }
lineNo++ if lineNo >= height || t.pwindow.Y() == height-1 && t.pwindow.X() > 0 {
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
prefixWidth := 0
_, _, ansi = extractColor(line, ansi, 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())
} }
str, _ = t.processTabs(trimmed, 0) str, width := t.processTabs(trimmed, prefixWidth)
prefixWidth += width
if t.theme != nil && ansi != nil && ansi.colored() { if t.theme != nil && ansi != nil && ansi.colored() {
fillRet = t.pwindow.CFill(ansi.fg, ansi.bg, ansi.attr, str) fillRet = t.pwindow.CFill(ansi.fg, ansi.bg, ansi.attr, str)
} else { } else {
@@ -1020,10 +1055,10 @@ func (t *Terminal) printPreview() {
} }
return fillRet == tui.FillContinue return fillRet == tui.FillContinue
}) })
switch fillRet { t.previewer.more = t.previewer.more || t.pwindow.Y() == height-1 && t.pwindow.X() > 0
case tui.FillNextLine: if fillRet == tui.FillNextLine {
continue continue
case tui.FillSuspend: } else if fillRet == tui.FillSuspend {
break break
} }
t.pwindow.Fill("\n") t.pwindow.Fill("\n")
@@ -1034,6 +1069,7 @@ func (t *Terminal) printPreview() {
} }
t.pwindow.FinishFill() t.pwindow.FinishFill()
if t.previewer.lines > height { if t.previewer.lines > height {
t.previewer.more = true
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() {
@@ -1069,6 +1105,7 @@ func (t *Terminal) printAll() {
} }
func (t *Terminal) refresh() { func (t *Terminal) refresh() {
t.placeCursor()
if !t.suppress { if !t.suppress {
windows := make([]tui.Window, 0, 4) windows := make([]tui.Window, 0, 4)
if t.bordered { if t.bordered {
@@ -1167,6 +1204,9 @@ func parsePlaceholder(match string) (bool, string, placeholderFlags) {
case 's': case 's':
flags.preserveSpace = true flags.preserveSpace = true
skipChars++ skipChars++
case 'n':
flags.number = true
skipChars++
case 'q': case 'q':
flags.query = true flags.query = true
default: default:
@@ -1222,8 +1262,17 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, fo
if match == "{}" { if match == "{}" {
for idx, item := range items { for idx, item := range items {
if flags.number {
n := int(item.text.Index)
if n < 0 {
replacements[idx] = ""
} else {
replacements[idx] = strconv.Itoa(n)
}
} else {
replacements[idx] = quoteEntry(item.AsString(stripAnsi)) replacements[idx] = quoteEntry(item.AsString(stripAnsi))
} }
}
return strings.Join(replacements, " ") return strings.Join(replacements, " ")
} }
@@ -1271,7 +1320,7 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
return return
} }
command := replacePlaceholder(template, t.ansi, t.delimiter, forcePlus, string(t.input), list) command := replacePlaceholder(template, t.ansi, t.delimiter, forcePlus, string(t.input), list)
cmd := util.ExecCommand(command) cmd := util.ExecCommand(command, false)
if !background { if !background {
cmd.Stdin = os.Stdin cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
@@ -1282,7 +1331,9 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
t.redraw() t.redraw()
t.refresh() t.refresh()
} else { } else {
t.tui.Pause(false)
cmd.Run() cmd.Run()
t.tui.Resume(false)
} }
} }
@@ -1318,7 +1369,7 @@ func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item
// 2. or it contains {+} and we have more than one item already selected. // 2. or it contains {+} and we have more than one item already selected.
// To do so, we pass an empty Item instead of nil to trigger an update. // To do so, we pass an empty Item instead of nil to trigger an update.
if current == nil { if current == nil {
current = &Item{} current = &minItem
} }
var sels []*Item var sels []*Item
@@ -1334,12 +1385,6 @@ func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item
return true, sels return true, sels
} }
func (t *Terminal) truncateQuery() {
maxPatternLength := util.Max(1, t.window.Width()-t.promptLen-1)
t.input, _ = t.trimRight(t.input, maxPatternLength)
t.cx = util.Constrain(t.cx, 0, len(t.input))
}
func (t *Terminal) selectItem(item *Item) { func (t *Terminal) selectItem(item *Item) {
t.selected[item.Index()] = selectedItem{time.Now(), item} t.selected[item.Index()] = selectedItem{time.Now(), item}
t.version++ t.version++
@@ -1358,6 +1403,20 @@ func (t *Terminal) toggleItem(item *Item) {
} }
} }
func (t *Terminal) killPreview(code int) {
select {
case t.killChan <- code:
default:
if code != exitCancel {
os.Exit(code)
}
}
}
func (t *Terminal) cancelPreview() {
t.killPreview(exitCancel)
}
// Loop is called to start Terminal I/O // Loop is called to start Terminal I/O
func (t *Terminal) Loop() { func (t *Terminal) Loop() {
// prof := profile.Start(profile.ProfilePath("/tmp/")) // prof := profile.Start(profile.ProfilePath("/tmp/"))
@@ -1392,10 +1451,9 @@ func (t *Terminal) Loop() {
t.initFunc() t.initFunc()
t.resizeWindows() t.resizeWindows()
t.printPrompt() t.printPrompt()
t.placeCursor()
t.refresh()
t.printInfo() t.printInfo()
t.printHeader() t.printHeader()
t.refresh()
t.mutex.Unlock() t.mutex.Unlock()
go func() { go func() {
timer := time.NewTimer(t.initDelay) timer := time.NewTimer(t.initDelay)
@@ -1435,15 +1493,47 @@ func (t *Terminal) Loop() {
if request[0] != nil { if request[0] != nil {
command := replacePlaceholder(t.preview.command, command := replacePlaceholder(t.preview.command,
t.ansi, t.delimiter, false, string(t.input), request) t.ansi, t.delimiter, false, string(t.input), request)
cmd := util.ExecCommand(command) cmd := util.ExecCommand(command, true)
if t.pwindow != nil { if t.pwindow != nil {
env := os.Environ() env := os.Environ()
env = append(env, fmt.Sprintf("LINES=%d", t.pwindow.Height())) lines := fmt.Sprintf("LINES=%d", t.pwindow.Height())
env = append(env, fmt.Sprintf("COLUMNS=%d", t.pwindow.Width())) columns := fmt.Sprintf("COLUMNS=%d", t.pwindow.Width())
env = append(env, lines)
env = append(env, "FZF_PREVIEW_"+lines)
env = append(env, columns)
env = append(env, "FZF_PREVIEW_"+columns)
cmd.Env = env cmd.Env = env
} }
out, _ := cmd.CombinedOutput() var out bytes.Buffer
t.reqBox.Set(reqPreviewDisplay, string(out)) cmd.Stdout = &out
cmd.Stderr = &out
cmd.Start()
finishChan := make(chan bool, 1)
updateChan := make(chan bool)
go func() {
select {
case code := <-t.killChan:
if code != exitCancel {
util.KillCommand(cmd)
os.Exit(code)
} else {
select {
case <-time.After(previewCancelWait):
util.KillCommand(cmd)
updateChan <- true
case <-finishChan:
updateChan <- false
}
}
case <-finishChan:
updateChan <- false
}
}()
cmd.Wait()
finishChan <- true
if out.Len() > 0 || !<-updateChan {
t.reqBox.Set(reqPreviewDisplay, string(out.Bytes()))
}
} else { } else {
t.reqBox.Set(reqPreviewDisplay, "") t.reqBox.Set(reqPreviewDisplay, "")
} }
@@ -1461,12 +1551,12 @@ func (t *Terminal) Loop() {
t.history.append(string(t.input)) t.history.append(string(t.input))
} }
// prof.Stop() // prof.Stop()
os.Exit(code) t.killPreview(code)
} }
go func() { go func() {
var focused *Item var focusedIndex int32 = minItem.Index()
var version int64 var version int64 = -1
for { for {
t.reqBox.Wait(func(events *util.Events) { t.reqBox.Wait(func(events *util.Events) {
defer events.Clear() defer events.Clear()
@@ -1482,12 +1572,17 @@ func (t *Terminal) Loop() {
t.printInfo() t.printInfo()
case reqList: case reqList:
t.printList() t.printList()
currentFocus := t.currentItem() var currentIndex int32 = minItem.Index()
if currentFocus != focused || version != t.version { currentItem := t.currentItem()
if currentItem != nil {
currentIndex = currentItem.Index()
}
if focusedIndex != currentIndex || version != t.version {
version = t.version version = t.version
focused = currentFocus focusedIndex = currentIndex
if t.isPreviewEnabled() { if t.isPreviewEnabled() {
_, list := t.buildPlusList(t.preview.command, false) _, list := t.buildPlusList(t.preview.command, false)
t.cancelPreview()
t.previewBox.Set(reqPreviewEnqueue, list) t.previewBox.Set(reqPreviewEnqueue, list)
} }
} }
@@ -1528,10 +1623,9 @@ func (t *Terminal) Loop() {
exit(func() int { return exitInterrupt }) exit(func() int { return exitInterrupt })
} }
} }
t.placeCursor() t.refresh()
t.mutex.Unlock() t.mutex.Unlock()
}) })
t.refresh()
} }
}() }()
@@ -1541,7 +1635,8 @@ func (t *Terminal) Loop() {
t.mutex.Lock() t.mutex.Lock()
previousInput := t.input previousInput := t.input
events := []util.EventType{reqPrompt} previousCx := t.cx
events := []util.EventType{}
req := func(evts ...util.EventType) { req := func(evts ...util.EventType) {
for _, event := range evts { for _, event := range evts {
events = append(events, event) events = append(events, event)
@@ -1557,10 +1652,16 @@ func (t *Terminal) Loop() {
} }
} }
scrollPreview := func(amount int) { scrollPreview := func(amount int) {
t.previewer.offset = util.Constrain( if !t.previewer.more {
return
}
newOffset := util.Constrain(
t.previewer.offset+amount, 0, t.previewer.lines-1) t.previewer.offset+amount, 0, t.previewer.lines-1)
if t.previewer.offset != newOffset {
t.previewer.offset = newOffset
req(reqPreviewRefresh) req(reqPreviewRefresh)
} }
}
for key, ret := range t.expect { for key, ret := range t.expect {
if keyMatch(key, event) { if keyMatch(key, event) {
t.pressed = ret t.pressed = ret
@@ -1597,10 +1698,11 @@ func (t *Terminal) Loop() {
if t.previewer.enabled { if t.previewer.enabled {
valid, list := t.buildPlusList(t.preview.command, false) valid, list := t.buildPlusList(t.preview.command, false)
if valid { if valid {
t.cancelPreview()
t.previewBox.Set(reqPreviewEnqueue, list) t.previewBox.Set(reqPreviewEnqueue, list)
} }
} }
req(reqList, reqInfo, reqHeader) req(reqPrompt, reqList, reqInfo, reqHeader)
} }
case actTogglePreviewWrap: case actTogglePreviewWrap:
if t.hasPreviewWindow() { if t.hasPreviewWindow() {
@@ -1852,7 +1954,7 @@ func (t *Terminal) Loop() {
} else if me.Down { } else if me.Down {
if my == 0 && mx >= 0 { if my == 0 && mx >= 0 {
// Prompt // Prompt
t.cx = mx t.cx = mx + t.xoffset
} else if my >= min { } else if my >= min {
// List // List
if t.vset(t.offset+my-min) && t.multi && me.Mod { if t.vset(t.offset+my-min) && t.multi && me.Mod {
@@ -1901,7 +2003,6 @@ func (t *Terminal) Loop() {
t.jumping = jumpDisabled t.jumping = jumpDisabled
req(reqList) req(reqList)
} }
t.mutex.Unlock() // Must be unlocked before touching reqBox
if changed { if changed {
if t.isPreviewEnabled() { if t.isPreviewEnabled() {
@@ -1910,6 +2011,15 @@ func (t *Terminal) Loop() {
t.version++ t.version++
} }
} }
}
if changed || t.cx != previousCx {
req(reqPrompt)
}
t.mutex.Unlock() // Must be unlocked before touching reqBox
if changed {
t.eventBox.Set(EvtSearchNew, t.sort) t.eventBox.Set(EvtSearchNew, t.sort)
} }
for _, event := range events { for _, event := range events {

View File

@@ -27,7 +27,7 @@ const (
const consoleDevice string = "/dev/tty" const consoleDevice string = "/dev/tty"
var offsetRegexp *regexp.Regexp = regexp.MustCompile("\x1b\\[([0-9]+);([0-9]+)R") var offsetRegexp *regexp.Regexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R")
func openTtyIn() *os.File { func openTtyIn() *os.File {
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0) in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
@@ -154,16 +154,18 @@ func (r *LightRenderer) findOffset() (row int, col int) {
for tries := 0; tries < offsetPollTries; tries++ { for tries := 0; tries < offsetPollTries; tries++ {
bytes = r.getBytesInternal(bytes, tries > 0) bytes = r.getBytesInternal(bytes, tries > 0)
offsets := offsetRegexp.FindSubmatch(bytes) offsets := offsetRegexp.FindSubmatch(bytes)
if len(offsets) > 2 { if len(offsets) > 3 {
return atoi(string(offsets[1]), 0) - 1, atoi(string(offsets[2]), 0) - 1 // add anything we skipped over to the input buffer
r.buffer = append(r.buffer, offsets[1]...)
return atoi(string(offsets[2]), 0) - 1, atoi(string(offsets[3]), 0) - 1
} }
} }
return -1, -1 return -1, -1
} }
func repeat(s string, times int) string { func repeat(r rune, times int) string {
if times > 0 { if times > 0 {
return strings.Repeat(s, times) return strings.Repeat(string(r), times)
} }
return "" return ""
} }
@@ -297,6 +299,7 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
} }
buffer = append(buffer, byte(c)) buffer = append(buffer, byte(c))
pc := c
for { for {
c, ok = r.getch(true) c, ok = r.getch(true)
if !ok { if !ok {
@@ -306,9 +309,13 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
continue continue
} }
break break
} } else if c == ESC && pc != c {
retries = r.escDelay / escPollInterval
} else {
retries = 0 retries = 0
}
buffer = append(buffer, byte(c)) buffer = append(buffer, byte(c))
pc = c
} }
return buffer return buffer
@@ -374,6 +381,8 @@ func (r *LightRenderer) escSequence(sz *int) Event {
alt = true alt = true
} }
switch r.buffer[1] { switch r.buffer[1] {
case ESC:
return Event{ESC, 0, nil}
case 32: case 32:
return Event{AltSpace, 0, nil} return Event{AltSpace, 0, nil}
case 47: case 47:
@@ -667,7 +676,7 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, bord
} }
func (w *LightWindow) drawBorder() { func (w *LightWindow) drawBorder() {
switch w.border { switch w.border.shape {
case BorderAround: case BorderAround:
w.drawBorderAround() w.drawBorderAround()
case BorderHorizontal: case BorderHorizontal:
@@ -677,22 +686,24 @@ func (w *LightWindow) drawBorder() {
func (w *LightWindow) drawBorderHorizontal() { func (w *LightWindow) drawBorderHorizontal() {
w.Move(0, 0) w.Move(0, 0)
w.CPrint(ColBorder, AttrRegular, repeat("─", w.width)) w.CPrint(ColBorder, AttrRegular, repeat(w.border.horizontal, w.width))
w.Move(w.height-1, 0) w.Move(w.height-1, 0)
w.CPrint(ColBorder, AttrRegular, repeat("─", w.width)) w.CPrint(ColBorder, AttrRegular, repeat(w.border.horizontal, w.width))
} }
func (w *LightWindow) drawBorderAround() { func (w *LightWindow) drawBorderAround() {
w.Move(0, 0) w.Move(0, 0)
w.CPrint(ColBorder, AttrRegular, "┌"+repeat("─", w.width-2)+"┐") w.CPrint(ColBorder, AttrRegular,
string(w.border.topLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.topRight))
for y := 1; y < w.height-1; y++ { for y := 1; y < w.height-1; y++ {
w.Move(y, 0) w.Move(y, 0)
w.CPrint(ColBorder, AttrRegular, "│") w.CPrint(ColBorder, AttrRegular, string(w.border.vertical))
w.cprint2(colDefault, w.bg, AttrRegular, repeat(" ", w.width-2)) w.cprint2(colDefault, w.bg, AttrRegular, repeat(' ', w.width-2))
w.CPrint(ColBorder, AttrRegular, "│") w.CPrint(ColBorder, AttrRegular, string(w.border.vertical))
} }
w.Move(w.height-1, 0) w.Move(w.height-1, 0)
w.CPrint(ColBorder, AttrRegular, "└"+repeat("─", w.width-2)+"┘") w.CPrint(ColBorder, AttrRegular,
string(w.border.bottomLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.bottomRight))
} }
func (w *LightWindow) csi(code string) { func (w *LightWindow) csi(code string) {
@@ -753,7 +764,7 @@ func (w *LightWindow) MoveAndClear(y int, x int) {
w.Move(y, x) w.Move(y, x)
// We should not delete preview window on the right // We should not delete preview window on the right
// csi("K") // csi("K")
w.Print(repeat(" ", w.width-x)) w.Print(repeat(' ', w.width-x))
w.Move(y, x) w.Move(y, x)
} }
@@ -849,7 +860,7 @@ func wrapLine(input string, prefixLength int, max int, tabstop int) []wrappedLin
width += w width += w
str := string(r) str := string(r)
if r == '\t' { if r == '\t' {
str = repeat(" ", w) str = repeat(' ', w)
} }
if prefixLength+width <= max { if prefixLength+width <= max {
line += str line += str

View File

@@ -61,12 +61,8 @@ func (w *TcellWindow) Refresh() {
} }
w.lastX = 0 w.lastX = 0
w.lastY = 0 w.lastY = 0
switch w.borderStyle {
case BorderAround: w.drawBorder()
w.drawBorder(true)
case BorderHorizontal:
w.drawBorder(false)
}
} }
func (w *TcellWindow) FinishFill() { func (w *TcellWindow) FinishFill() {
@@ -382,13 +378,17 @@ func (r *FullscreenRenderer) GetChar() Event {
return Event{Invalid, 0, nil} return Event{Invalid, 0, nil}
} }
func (r *FullscreenRenderer) Pause(bool) { func (r *FullscreenRenderer) Pause(clear bool) {
if clear {
_screen.Fini() _screen.Fini()
} }
}
func (r *FullscreenRenderer) Resume(bool) { func (r *FullscreenRenderer) Resume(clear bool) {
if clear {
r.initScreen() r.initScreen()
} }
}
func (r *FullscreenRenderer) Close() { func (r *FullscreenRenderer) Close() {
_screen.Fini() _screen.Fini()
@@ -566,7 +566,11 @@ func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
return w.fillString(str, NewColorPair(fg, bg), a) return w.fillString(str, NewColorPair(fg, bg), a)
} }
func (w *TcellWindow) drawBorder(around bool) { func (w *TcellWindow) drawBorder() {
if w.borderStyle.shape == BorderNone {
return
}
left := w.left left := w.left
right := left + w.width right := left + w.width
top := w.top top := w.top
@@ -580,19 +584,19 @@ func (w *TcellWindow) drawBorder(around bool) {
} }
for x := left; x < right; x++ { for x := left; x < right; x++ {
_screen.SetContent(x, top, tcell.RuneHLine, nil, style) _screen.SetContent(x, top, w.borderStyle.horizontal, nil, style)
_screen.SetContent(x, bot-1, tcell.RuneHLine, nil, style) _screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style)
} }
if around { if w.borderStyle.shape == BorderAround {
for y := top; y < bot; y++ { for y := top; y < bot; y++ {
_screen.SetContent(left, y, tcell.RuneVLine, nil, style) _screen.SetContent(left, y, w.borderStyle.vertical, nil, style)
_screen.SetContent(right-1, y, tcell.RuneVLine, nil, style) _screen.SetContent(right-1, y, w.borderStyle.vertical, nil, style)
} }
_screen.SetContent(left, top, tcell.RuneULCorner, nil, style) _screen.SetContent(left, top, w.borderStyle.topLeft, nil, style)
_screen.SetContent(right-1, top, tcell.RuneURCorner, nil, style) _screen.SetContent(right-1, top, w.borderStyle.topRight, nil, style)
_screen.SetContent(left, bot-1, tcell.RuneLLCorner, nil, style) _screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style)
_screen.SetContent(right-1, bot-1, tcell.RuneLRCorner, nil, style) _screen.SetContent(right-1, bot-1, w.borderStyle.bottomRight, nil, style)
} }
} }

View File

@@ -172,6 +172,7 @@ type ColorTheme struct {
Fg Color Fg Color
Bg Color Bg Color
DarkBg Color DarkBg Color
Gutter Color
Prompt Color Prompt Color
Match Color Match Color
Current Color Current Color
@@ -200,14 +201,47 @@ type MouseEvent struct {
Mod bool Mod bool
} }
type BorderStyle int type BorderShape int
const ( const (
BorderNone BorderStyle = iota BorderNone BorderShape = iota
BorderAround BorderAround
BorderHorizontal BorderHorizontal
) )
type BorderStyle struct {
shape BorderShape
horizontal rune
vertical rune
topLeft rune
topRight rune
bottomLeft rune
bottomRight rune
}
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
if unicode {
return BorderStyle{
shape: shape,
horizontal: '─',
vertical: '│',
topLeft: '┌',
topRight: '┐',
bottomLeft: '└',
bottomRight: '┘',
}
}
return BorderStyle{
shape: shape,
horizontal: '-',
vertical: '|',
topLeft: '+',
topRight: '+',
bottomLeft: '+',
bottomRight: '+',
}
}
type Renderer interface { type Renderer interface {
Init() Init()
Pause(clear bool) Pause(clear bool)
@@ -272,15 +306,17 @@ var (
Dark256 *ColorTheme Dark256 *ColorTheme
Light256 *ColorTheme Light256 *ColorTheme
ColNormal ColorPair
ColPrompt ColorPair ColPrompt ColorPair
ColNormal ColorPair
ColMatch ColorPair ColMatch ColorPair
ColCurrent ColorPair
ColCurrentMatch ColorPair
ColSpinner ColorPair
ColInfo ColorPair
ColCursor ColorPair ColCursor ColorPair
ColSelected ColorPair ColSelected ColorPair
ColCurrent ColorPair
ColCurrentMatch ColorPair
ColCurrentCursor ColorPair
ColCurrentSelected ColorPair
ColSpinner ColorPair
ColInfo ColorPair
ColHeader ColorPair ColHeader ColorPair
ColBorder ColorPair ColBorder ColorPair
) )
@@ -290,6 +326,7 @@ func EmptyTheme() *ColorTheme {
Fg: colUndefined, Fg: colUndefined,
Bg: colUndefined, Bg: colUndefined,
DarkBg: colUndefined, DarkBg: colUndefined,
Gutter: colUndefined,
Prompt: colUndefined, Prompt: colUndefined,
Match: colUndefined, Match: colUndefined,
Current: colUndefined, Current: colUndefined,
@@ -312,6 +349,7 @@ func init() {
Fg: colDefault, Fg: colDefault,
Bg: colDefault, Bg: colDefault,
DarkBg: colBlack, DarkBg: colBlack,
Gutter: colBlack,
Prompt: colBlue, Prompt: colBlue,
Match: colGreen, Match: colGreen,
Current: colYellow, Current: colYellow,
@@ -326,6 +364,7 @@ func init() {
Fg: colDefault, Fg: colDefault,
Bg: colDefault, Bg: colDefault,
DarkBg: 236, DarkBg: 236,
Gutter: colUndefined,
Prompt: 110, Prompt: 110,
Match: 108, Match: 108,
Current: 254, Current: 254,
@@ -340,6 +379,7 @@ func init() {
Fg: colDefault, Fg: colDefault,
Bg: colDefault, Bg: colDefault,
DarkBg: 251, DarkBg: 251,
Gutter: colUndefined,
Prompt: 25, Prompt: 25,
Match: 66, Match: 66,
Current: 237, Current: 237,
@@ -371,6 +411,7 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
theme.Fg = o(baseTheme.Fg, theme.Fg) theme.Fg = o(baseTheme.Fg, theme.Fg)
theme.Bg = o(baseTheme.Bg, theme.Bg) theme.Bg = o(baseTheme.Bg, theme.Bg)
theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg) theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg)
theme.Gutter = o(theme.DarkBg, o(baseTheme.Gutter, theme.Gutter))
theme.Prompt = o(baseTheme.Prompt, theme.Prompt) theme.Prompt = o(baseTheme.Prompt, theme.Prompt)
theme.Match = o(baseTheme.Match, theme.Match) theme.Match = o(baseTheme.Match, theme.Match)
theme.Current = o(baseTheme.Current, theme.Current) theme.Current = o(baseTheme.Current, theme.Current)
@@ -392,27 +433,31 @@ func initPalette(theme *ColorTheme) {
return ColorPair{fg, bg, idx} return ColorPair{fg, bg, idx}
} }
if theme != nil { if theme != nil {
ColNormal = pair(theme.Fg, theme.Bg)
ColPrompt = pair(theme.Prompt, theme.Bg) ColPrompt = pair(theme.Prompt, theme.Bg)
ColNormal = pair(theme.Fg, theme.Bg)
ColMatch = pair(theme.Match, theme.Bg) ColMatch = pair(theme.Match, theme.Bg)
ColCursor = pair(theme.Cursor, theme.Gutter)
ColSelected = pair(theme.Selected, theme.Gutter)
ColCurrent = pair(theme.Current, theme.DarkBg) ColCurrent = pair(theme.Current, theme.DarkBg)
ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg) ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg)
ColCurrentCursor = pair(theme.Cursor, theme.DarkBg)
ColCurrentSelected = pair(theme.Selected, theme.DarkBg)
ColSpinner = pair(theme.Spinner, theme.Bg) ColSpinner = pair(theme.Spinner, theme.Bg)
ColInfo = pair(theme.Info, theme.Bg) ColInfo = pair(theme.Info, theme.Bg)
ColCursor = pair(theme.Cursor, theme.DarkBg)
ColSelected = pair(theme.Selected, theme.DarkBg)
ColHeader = pair(theme.Header, theme.Bg) ColHeader = pair(theme.Header, theme.Bg)
ColBorder = pair(theme.Border, theme.Bg) ColBorder = pair(theme.Border, theme.Bg)
} else { } else {
ColNormal = pair(colDefault, colDefault)
ColPrompt = pair(colDefault, colDefault) ColPrompt = pair(colDefault, colDefault)
ColNormal = pair(colDefault, colDefault)
ColMatch = pair(colDefault, colDefault) ColMatch = pair(colDefault, colDefault)
ColCurrent = pair(colDefault, colDefault)
ColCurrentMatch = pair(colDefault, colDefault)
ColSpinner = pair(colDefault, colDefault)
ColInfo = pair(colDefault, colDefault)
ColCursor = pair(colDefault, colDefault) ColCursor = pair(colDefault, colDefault)
ColSelected = pair(colDefault, colDefault) ColSelected = pair(colDefault, colDefault)
ColCurrent = pair(colDefault, colDefault)
ColCurrentMatch = pair(colDefault, colDefault)
ColCurrentCursor = pair(colDefault, colDefault)
ColCurrentSelected = pair(colDefault, colDefault)
ColSpinner = pair(colDefault, colDefault)
ColInfo = pair(colDefault, colDefault)
ColHeader = pair(colDefault, colDefault) ColHeader = pair(colDefault, colDefault)
ColBorder = pair(colDefault, colDefault) ColBorder = pair(colDefault, colDefault)
} }

View File

@@ -171,3 +171,12 @@ func (chars *Chars) CopyRunes(dest []rune) {
} }
return return
} }
func (chars *Chars) Prepend(prefix string) {
if runes := chars.optionalRunes(); runes != nil {
runes = append([]rune(prefix), runes...)
chars.slice = *(*[]byte)(unsafe.Pointer(&runes))
} else {
chars.slice = append([]byte(prefix), chars.slice...)
}
}

View File

@@ -9,17 +9,26 @@ import (
) )
// ExecCommand executes the given command with $SHELL // ExecCommand executes the given command with $SHELL
func ExecCommand(command string) *exec.Cmd { func ExecCommand(command string, setpgid bool) *exec.Cmd {
shell := os.Getenv("SHELL") shell := os.Getenv("SHELL")
if len(shell) == 0 { if len(shell) == 0 {
shell = "sh" shell = "sh"
} }
return ExecCommandWith(shell, command) return ExecCommandWith(shell, command, setpgid)
} }
// ExecCommandWith executes the given command with the specified shell // ExecCommandWith executes the given command with the specified shell
func ExecCommandWith(shell string, command string) *exec.Cmd { func ExecCommandWith(shell string, command string, setpgid bool) *exec.Cmd {
return exec.Command(shell, "-c", command) cmd := exec.Command(shell, "-c", command)
if setpgid {
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
}
return cmd
}
// KillCommand kills the process for the given command
func KillCommand(cmd *exec.Cmd) error {
return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
} }
// IsWindows returns true on Windows // IsWindows returns true on Windows
@@ -27,7 +36,7 @@ func IsWindows() bool {
return false return false
} }
// SetNonBlock executes syscall.SetNonblock on file descriptor // SetNonblock executes syscall.SetNonblock on file descriptor
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)
} }

View File

@@ -10,13 +10,15 @@ import (
) )
// ExecCommand executes the given command with cmd // ExecCommand executes the given command with cmd
func ExecCommand(command string) *exec.Cmd { func ExecCommand(command string, setpgid bool) *exec.Cmd {
return ExecCommandWith("cmd", command) return ExecCommandWith("cmd", command, setpgid)
} }
// ExecCommandWith executes the given command with cmd. _shell parameter is // ExecCommandWith executes the given command with cmd. _shell parameter is
// ignored on Windows. // ignored on Windows.
func ExecCommandWith(_shell string, command string) *exec.Cmd { // FIXME: setpgid is unused. We set it in the Unix implementation so that we
// can kill preview process with its child processes at once.
func ExecCommandWith(_shell string, command string, setpgid bool) *exec.Cmd {
cmd := exec.Command("cmd") cmd := exec.Command("cmd")
cmd.SysProcAttr = &syscall.SysProcAttr{ cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: false, HideWindow: false,
@@ -26,12 +28,17 @@ func ExecCommandWith(_shell string, command string) *exec.Cmd {
return cmd return cmd
} }
// KillCommand kills the process for the given command
func KillCommand(cmd *exec.Cmd) error {
return cmd.Process.Kill()
}
// IsWindows returns true on Windows // IsWindows returns true on Windows
func IsWindows() bool { func IsWindows() bool {
return true return true
} }
// SetNonBlock executes syscall.SetNonblock on file descriptor // SetNonblock executes syscall.SetNonblock on file descriptor
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)
} }

View File

@@ -8,10 +8,13 @@ Execute (fzf#run with dir option):
let cwd = getcwd() let cwd = getcwd()
let result = fzf#run({ 'source': 'git ls-files', 'options': '--filter=vdr', 'dir': g:dir }) let result = fzf#run({ 'source': 'git ls-files', 'options': '--filter=vdr', 'dir': g:dir })
AssertEqual ['fzf.vader'], result AssertEqual ['fzf.vader'], result
AssertEqual 0, haslocaldir()
AssertEqual getcwd(), cwd AssertEqual getcwd(), cwd
execute 'lcd' fnameescape(cwd)
let result = sort(fzf#run({ 'source': 'git ls-files', 'options': '--filter e', 'dir': g:dir })) let result = sort(fzf#run({ 'source': 'git ls-files', 'options': '--filter e', 'dir': g:dir }))
AssertEqual ['fzf.vader', 'test_go.rb'], result AssertEqual ['fzf.vader', 'test_go.rb'], result
AssertEqual 1, haslocaldir()
AssertEqual getcwd(), cwd AssertEqual getcwd(), cwd
Execute (fzf#run with Funcref command): Execute (fzf#run with Funcref command):
@@ -56,11 +59,11 @@ Execute (Incomplete fzf#run with dir option and autochdir):
" No change in working directory even if &acd is set " No change in working directory even if &acd is set
AssertEqual cwd, getcwd() AssertEqual cwd, getcwd()
Execute (fzf#run with dir option and autochdir): Execute (FIXME: fzf#run with dir option and autochdir):
set acd set acd
let cwd = getcwd()
call fzf#run({'source': ['/foobar'], 'sink': 'e', 'dir': '/tmp', 'options': '-1'}) call fzf#run({'source': ['/foobar'], 'sink': 'e', 'dir': '/tmp', 'options': '-1'})
" Working directory changed due to &acd " Working directory changed due to &acd
AssertEqual '/foobar', expand('%')
AssertEqual '/', getcwd() AssertEqual '/', getcwd()
Execute (fzf#run with dir option and autochdir when final cwd is same as dir): Execute (fzf#run with dir option and autochdir when final cwd is same as dir):

View File

@@ -1371,7 +1371,7 @@ class TestGoFZF < TestBase
end end
def test_preview_hidden def test_preview_hidden
tmux.send_keys %(seq 1000 | #{FZF} --preview 'echo {{}-{}-\\$LINES-\\$COLUMNS}' --preview-window down:1:hidden --bind ?:toggle-preview), :Enter tmux.send_keys %(seq 1000 | #{FZF} --preview 'echo {{}-{}-\\$FZF_PREVIEW_LINES-\\$FZF_PREVIEW_COLUMNS}' --preview-window down:1:hidden --bind ?:toggle-preview), :Enter
tmux.until { |lines| lines[-1] == '>' } tmux.until { |lines| lines[-1] == '>' }
tmux.send_keys '?' tmux.send_keys '?'
tmux.until { |lines| lines[-2] =~ / {1-1-1-[0-9]+}/ } tmux.until { |lines| lines[-2] =~ / {1-1-1-[0-9]+}/ }
@@ -1400,21 +1400,21 @@ class TestGoFZF < TestBase
def test_preview_flags def test_preview_flags
tmux.send_keys %(seq 10 | sed 's/^/:: /; s/$/ /' | tmux.send_keys %(seq 10 | sed 's/^/:: /; s/$/ /' |
#{FZF} --multi --preview 'echo {{2}/{s2}/{+2}/{+s2}/{q}}'), :Enter #{FZF} --multi --preview 'echo {{2}/{s2}/{+2}/{+s2}/{q}/{n}/{+n}}'), :Enter
tmux.until { |lines| lines[1].include?('{1/1 /1/1 /}') } tmux.until { |lines| lines[1].include?('{1/1 /1/1 //0/0}') }
tmux.send_keys '123' tmux.send_keys '123'
tmux.until { |lines| lines[1].include?('{////123}') } tmux.until { |lines| lines[1].include?('{////123//}') }
tmux.send_keys 'C-u', '1' tmux.send_keys 'C-u', '1'
tmux.until { |lines| lines.match_count == 2 } tmux.until { |lines| lines.match_count == 2 }
tmux.until { |lines| lines[1].include?('{1/1 /1/1 /1}') } tmux.until { |lines| lines[1].include?('{1/1 /1/1 /1/0/0}') }
tmux.send_keys :BTab tmux.send_keys :BTab
tmux.until { |lines| lines[1].include?('{10/10 /1/1 /1}') } tmux.until { |lines| lines[1].include?('{10/10 /1/1 /1/9/0}') }
tmux.send_keys :BTab tmux.send_keys :BTab
tmux.until { |lines| lines[1].include?('{10/10 /1 10/1 10 /1}') } tmux.until { |lines| lines[1].include?('{10/10 /1 10/1 10 /1/9/0 9}') }
tmux.send_keys '2' tmux.send_keys '2'
tmux.until { |lines| lines[1].include?('{//1 10/1 10 /12}') } tmux.until { |lines| lines[1].include?('{//1 10/1 10 /12//0 9}') }
tmux.send_keys '3' tmux.send_keys '3'
tmux.until { |lines| lines[1].include?('{//1 10/1 10 /123}') } tmux.until { |lines| lines[1].include?('{//1 10/1 10 /123//0 9}') }
end end
def test_preview_q_no_match def test_preview_q_no_match
@@ -1427,6 +1427,12 @@ class TestGoFZF < TestBase
tmux.until { |lines| !lines[1].include?('foo') } tmux.until { |lines| !lines[1].include?('foo') }
end end
def test_preview_q_no_match_with_initial_query
tmux.send_keys %(: | #{FZF} --preview 'echo foo {q}{q}' --query foo), :Enter
tmux.until { |lines| lines.match_count == 0 }
tmux.until { |lines| lines[1].include?('foofoo') }
end
def test_no_clear def test_no_clear
tmux.send_keys "seq 10 | fzf --no-clear --inline-info --height 5 > #{tempname}", :Enter tmux.send_keys "seq 10 | fzf --no-clear --inline-info --height 5 > #{tempname}", :Enter
prompt = '> < 10/10' prompt = '> < 10/10'
@@ -1513,6 +1519,25 @@ class TestGoFZF < TestBase
assert_equal ['foo bar'], `#{FZF} -f'^foo\\ bar$' < #{tempname}`.lines.map(&:chomp) assert_equal ['foo bar'], `#{FZF} -f'^foo\\ bar$' < #{tempname}`.lines.map(&:chomp)
assert_equal input.lines.count - 1, `#{FZF} -f'!^foo\\ bar$' < #{tempname}`.lines.count assert_equal input.lines.count - 1, `#{FZF} -f'!^foo\\ bar$' < #{tempname}`.lines.count
end end
def test_inverse_only_search_should_not_sort_the_result
# Filter
assert_equal(%w[aaaaa b ccc],
`printf '%s\n' aaaaa b ccc BAD | #{FZF} -f '!bad'`.lines.map(&:chomp))
# Interactive
tmux.send_keys(%[printf '%s\n' aaaaa b ccc BAD | #{FZF} -q '!bad'], :Enter)
tmux.until { |lines| lines.item_count == 4 && lines.match_count == 3 }
tmux.until { |lines| lines[-3] == '> aaaaa' }
tmux.until { |lines| lines[-4] == ' b' }
tmux.until { |lines| lines[-5] == ' ccc' }
end
def test_preview_correct_tab_width_after_ansi_reset_code
writelines tempname, ["\x1b[31m+\x1b[m\t\x1b[32mgreen"]
tmux.send_keys "#{FZF} --preview 'cat #{tempname}'", :Enter
tmux.until { |lines| lines[1].include?('+ green') }
end
end end
module TestShell module TestShell