mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-15 06:43:47 -05:00
Compare commits
89 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff951341c9 | ||
|
|
df570afd52 | ||
|
|
07d755df11 | ||
|
|
37585bd5a5 | ||
|
|
89e24bf8f2 | ||
|
|
8d2fcd3518 | ||
|
|
f39ab3875e | ||
|
|
82efe6c60d | ||
|
|
75972d59a8 | ||
|
|
e7d60aac9c | ||
|
|
a0bfbdd49c | ||
|
|
ba594982f0 | ||
|
|
2157f4f193 | ||
|
|
309bae423c | ||
|
|
4f8bf2ae78 | ||
|
|
85c1f8a9e0 | ||
|
|
e00e7e1e56 | ||
|
|
1a6defdbcc | ||
|
|
ef577a6509 | ||
|
|
b7c6838e45 | ||
|
|
91d04cec5c | ||
|
|
3bd8441079 | ||
|
|
8cf45a5197 | ||
|
|
8dc1377efb | ||
|
|
6c32148f90 | ||
|
|
315e568de0 | ||
|
|
5d16b28869 | ||
|
|
5624a89231 | ||
|
|
63c42b14f2 | ||
|
|
6f1eaa9b39 | ||
|
|
ca42e5e00a | ||
|
|
61feee690c | ||
|
|
d4ed955aee | ||
|
|
b46227dcb6 | ||
|
|
fd8d371ac7 | ||
|
|
0e06e298d4 | ||
|
|
72df905902 | ||
|
|
0d748a0699 | ||
|
|
27c40dc6b0 | ||
|
|
8e34e6fbb4 | ||
|
|
3bc98ed623 | ||
|
|
70a92a858a | ||
|
|
49d04374a4 | ||
|
|
8540902a35 | ||
|
|
8c6fcee3ca | ||
|
|
13803d0dbb | ||
|
|
423986996a | ||
|
|
1c9e7b7ea6 | ||
|
|
6de1ad9d3d | ||
|
|
5004ae3457 | ||
|
|
e67cc75063 | ||
|
|
0edbcbdf19 | ||
|
|
f0fe79dd3b | ||
|
|
daa1958f86 | ||
|
|
2c26f02f5c | ||
|
|
af87650bc4 | ||
|
|
2b19c0bc68 | ||
|
|
76a2dcb5a9 | ||
|
|
68ec3d1c10 | ||
|
|
2ff19084ca | ||
|
|
62f062ecfa | ||
|
|
cce17ad0a0 | ||
|
|
b8296a91b9 | ||
|
|
6e9452b06e | ||
|
|
888fd35689 | ||
|
|
1fb0fbca58 | ||
|
|
ddd2a109e4 | ||
|
|
87504a528e | ||
|
|
6eac4af7db | ||
|
|
89de1340af | ||
|
|
9e753a0d44 | ||
|
|
f57920ad90 | ||
|
|
7dbbbef51a | ||
|
|
7add75126d | ||
|
|
d207672bd5 | ||
|
|
851fa38251 | ||
|
|
43345fb642 | ||
|
|
9ff33814ea | ||
|
|
21b94d2de5 | ||
|
|
24236860c8 | ||
|
|
3f868fd792 | ||
|
|
417bca03df | ||
|
|
cce6aef557 | ||
|
|
eb3afc03b5 | ||
|
|
7f0caf0683 | ||
|
|
7f606665cb | ||
|
|
202872c2dc | ||
|
|
93aeae1985 | ||
|
|
5c34ab6692 |
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -22,7 +22,7 @@
|
|||||||
### Before submitting
|
### Before submitting
|
||||||
|
|
||||||
- Make sure that you have the latest version of fzf
|
- Make sure that you have the latest version of fzf
|
||||||
- If you use tmux, make sure $TERM is set to screen or screen-256color
|
- If you use tmux, make sure $TERM starts with "screen"
|
||||||
- For more Vim stuff, check out https://github.com/junegunn/fzf.vim
|
- For more Vim stuff, check out https://github.com/junegunn/fzf.vim
|
||||||
|
|
||||||
Describe your problem or suggestion from here ...
|
Describe your problem or suggestion from here ...
|
||||||
|
|||||||
41
.travis.yml
41
.travis.yml
@@ -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:
|
- stage: cli
|
||||||
- sudo add-apt-repository -y ppa:pi-rho/dev
|
go: "1.11.x"
|
||||||
- sudo apt-add-repository -y ppa:fish-shell/release-2
|
rvm: "2.5"
|
||||||
- sudo apt-get update
|
script: |
|
||||||
- sudo apt-get install -y tmux zsh fish
|
make install && ./install --all && tmux new "ruby test/test_go.rb > out && touch ok" && cat out && [ -e ok ]
|
||||||
|
|
||||||
script: |
|
|
||||||
make test install &&
|
|
||||||
./install --all &&
|
|
||||||
tmux new "ruby test/test_go.rb > out && touch ok" && cat out && [ -e ok ]
|
|
||||||
|
|||||||
4
BUILD.md
4
BUILD.md
@@ -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
|
||||||
|
|||||||
43
CHANGELOG.md
43
CHANGELOG.md
@@ -1,6 +1,49 @@
|
|||||||
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
|
||||||
|
------
|
||||||
|
|
||||||
|
- Added `--layout` option with a new layout called `reverse-list`.
|
||||||
|
- `--layout=reverse` is a synonym for `--reverse`
|
||||||
|
- `--layout=default` is a synonym for `--no-reverse`
|
||||||
|
- Preview window will be updated even when there is no match for the query
|
||||||
|
if any of the placeholder expressions (e.g. `{q}`, `{+}`) evaluates to
|
||||||
|
a non-empty string.
|
||||||
|
- More keys for binding: `shift-{up,down}`, `alt-{up,down,left,right}`
|
||||||
|
- fzf can now start even when `/dev/tty` is not available by making an
|
||||||
|
educated guess.
|
||||||
|
- Updated the default command for Windows.
|
||||||
|
- Fixes and improvements on bash/zsh completion
|
||||||
|
- install and uninstall scripts now supports generating files under
|
||||||
|
`XDG_CONFIG_HOME` on `--xdg` flag.
|
||||||
|
|
||||||
|
See https://github.com/junegunn/fzf/milestone/12?closed=1 for the full list of
|
||||||
|
changes.
|
||||||
|
|
||||||
0.17.3
|
0.17.3
|
||||||
------
|
------
|
||||||
- `$LINES` and `$COLUMNS` are exported to preview command so that the command
|
- `$LINES` and `$COLUMNS` are exported to preview command so that the command
|
||||||
|
|||||||
11
Dockerfile
Normal file
11
Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
FROM archlinux/base:latest
|
||||||
|
RUN pacman -Sy && pacman --noconfirm -S awk git tmux zsh fish ruby procps go make
|
||||||
|
RUN gem install --no-document minitest
|
||||||
|
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
||||||
|
RUN echo '. ~/.bashrc' >> ~/.bash_profile
|
||||||
|
|
||||||
|
# Do not set default PS1
|
||||||
|
RUN rm -f /etc/bash.bashrc
|
||||||
|
COPY . /fzf
|
||||||
|
RUN cd /fzf && make install && ./install --all
|
||||||
|
CMD tmux new 'set -o pipefail; ruby /fzf/test/test_go.rb | tee out && touch ok' && cat out && [ -e ok ]
|
||||||
122
Makefile
122
Makefile
@@ -1,41 +1,28 @@
|
|||||||
ifndef GOOS
|
GO ?= go
|
||||||
UNAME_S := $(shell uname -s)
|
GOOS ?= $(word 1, $(subst /, " ", $(word 4, $(shell go version))))
|
||||||
ifeq ($(UNAME_S),Darwin)
|
|
||||||
GOOS := darwin
|
|
||||||
else ifeq ($(UNAME_S),Linux)
|
|
||||||
GOOS := linux
|
|
||||||
else
|
|
||||||
$(error "$$GOOS is not defined.")
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
|
|
||||||
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
|
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
|
REVISION := $(shell git log -n 1 --pretty=format:%h -- $(SOURCES))
|
||||||
GLIDE_LOCK := glide.lock
|
BUILD_FLAGS := -a -ldflags "-X main.revision=$(REVISION) -w -extldflags=$(LDFLAGS)" -tags "$(TAGS)"
|
||||||
SOURCES := $(wildcard *.go src/*.go src/*/*.go) $(SRC_LINK) $(VENDOR_LINK) $(GLIDE_LOCK) $(MAKEFILE)
|
|
||||||
|
|
||||||
REVISION := $(shell git log -n 1 --pretty=format:%h -- $(SOURCES))
|
BINARY32 := fzf-$(GOOS)_386
|
||||||
BUILD_FLAGS := -a -ldflags "-X main.revision=$(REVISION) -w -extldflags=$(LDFLAGS)" -tags "$(TAGS)"
|
BINARY64 := fzf-$(GOOS)_amd64
|
||||||
|
BINARYARM5 := fzf-$(GOOS)_arm5
|
||||||
BINARY32 := fzf-$(GOOS)_386
|
BINARYARM6 := fzf-$(GOOS)_arm6
|
||||||
BINARY64 := fzf-$(GOOS)_amd64
|
BINARYARM7 := fzf-$(GOOS)_arm7
|
||||||
BINARYARM5 := fzf-$(GOOS)_arm5
|
BINARYARM8 := fzf-$(GOOS)_arm8
|
||||||
BINARYARM6 := fzf-$(GOOS)_arm6
|
BINARYPPC64LE := fzf-$(GOOS)_ppc64le
|
||||||
BINARYARM7 := fzf-$(GOOS)_arm7
|
VERSION := $(shell awk -F= '/version =/ {print $$2}' src/constants.go | tr -d "\" ")
|
||||||
BINARYARM8 := fzf-$(GOOS)_arm8
|
RELEASE32 := fzf-$(VERSION)-$(GOOS)_386
|
||||||
VERSION := $(shell awk -F= '/version =/ {print $$2}' src/constants.go | tr -d "\" ")
|
RELEASE64 := fzf-$(VERSION)-$(GOOS)_amd64
|
||||||
RELEASE32 := fzf-$(VERSION)-$(GOOS)_386
|
RELEASEARM5 := fzf-$(VERSION)-$(GOOS)_arm5
|
||||||
RELEASE64 := fzf-$(VERSION)-$(GOOS)_amd64
|
RELEASEARM6 := fzf-$(VERSION)-$(GOOS)_arm6
|
||||||
RELEASEARM5 := fzf-$(VERSION)-$(GOOS)_arm5
|
RELEASEARM7 := fzf-$(VERSION)-$(GOOS)_arm7
|
||||||
RELEASEARM6 := fzf-$(VERSION)-$(GOOS)_arm6
|
RELEASEARM8 := fzf-$(VERSION)-$(GOOS)_arm8
|
||||||
RELEASEARM7 := fzf-$(VERSION)-$(GOOS)_arm7
|
RELEASEPPC64LE := fzf-$(VERSION)-$(GOOS)_ppc64le
|
||||||
RELEASEARM8 := fzf-$(VERSION)-$(GOOS)_arm8
|
|
||||||
|
|
||||||
# https://en.wikipedia.org/wiki/Uname
|
# https://en.wikipedia.org/wiki/Uname
|
||||||
UNAME_M := $(shell uname -m)
|
UNAME_M := $(shell uname -m)
|
||||||
@@ -53,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
|
||||||
@@ -68,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)
|
||||||
@@ -90,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 \
|
||||||
@@ -111,28 +92,43 @@ 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
|
||||||
|
|
||||||
.PHONY: all release release-all test install clean
|
docker:
|
||||||
|
docker build -t fzf-arch .
|
||||||
|
docker run -it fzf-arch tmux
|
||||||
|
|
||||||
|
docker-test:
|
||||||
|
docker build -t fzf-arch .
|
||||||
|
docker run -it fzf-arch
|
||||||
|
|
||||||
|
update:
|
||||||
|
$(GO) get -u
|
||||||
|
$(GO) mod tidy
|
||||||
|
|
||||||
|
.PHONY: all release release-all test install clean docker docker-test update
|
||||||
|
|||||||
84
README.md
84
README.md
@@ -1,4 +1,4 @@
|
|||||||
<img src="https://raw.githubusercontent.com/junegunn/i/master/fzf.png" height="170" alt="fzf - a command-line fuzzy finder"> [](https://travis-ci.org/junegunn/fzf) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=EKYAW9PGKPD2N)
|
<img src="https://raw.githubusercontent.com/junegunn/i/master/fzf.png" height="170" alt="fzf - a command-line fuzzy finder"> [](https://travis-ci.org/junegunn/fzf)
|
||||||
===
|
===
|
||||||
|
|
||||||
fzf is a general-purpose command-line fuzzy finder.
|
fzf is a general-purpose command-line fuzzy finder.
|
||||||
@@ -26,6 +26,7 @@ Table of Contents
|
|||||||
* [Using Homebrew or Linuxbrew](#using-homebrew-or-linuxbrew)
|
* [Using Homebrew or Linuxbrew](#using-homebrew-or-linuxbrew)
|
||||||
* [Using git](#using-git)
|
* [Using git](#using-git)
|
||||||
* [As Vim plugin](#as-vim-plugin)
|
* [As Vim plugin](#as-vim-plugin)
|
||||||
|
* [Arch Linux](#arch-linux)
|
||||||
* [Fedora](#fedora)
|
* [Fedora](#fedora)
|
||||||
* [Windows](#windows)
|
* [Windows](#windows)
|
||||||
* [Upgrading fzf](#upgrading-fzf)
|
* [Upgrading fzf](#upgrading-fzf)
|
||||||
@@ -55,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
|
||||||
@@ -86,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
|
||||||
@@ -99,7 +105,7 @@ git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
|
|||||||
### As Vim plugin
|
### As Vim plugin
|
||||||
|
|
||||||
Once you have fzf installed, you can enable it inside Vim simply by adding the
|
Once you have fzf installed, you can enable it inside Vim simply by adding the
|
||||||
directory to `&runtimepath` as follows:
|
directory to `&runtimepath` in your Vim configuration file as follows:
|
||||||
|
|
||||||
```vim
|
```vim
|
||||||
" If installed using Homebrew
|
" If installed using Homebrew
|
||||||
@@ -125,10 +131,16 @@ 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
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo pacman -S fzf
|
||||||
```
|
```
|
||||||
|
|
||||||
### Fedora
|
### Fedora
|
||||||
@@ -142,8 +154,7 @@ sudo dnf install fzf
|
|||||||
|
|
||||||
Shell completion and plugins for vim or neovim are enabled by default. Shell
|
Shell completion and plugins for vim or neovim are enabled by default. Shell
|
||||||
key bindings are installed but not enabled by default. See Fedora's package
|
key bindings are installed but not enabled by default. See Fedora's package
|
||||||
documentation for more information.
|
documentation (/usr/share/doc/fzf/README.Fedora) for more information.
|
||||||
|
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
@@ -217,8 +228,8 @@ cursor with `--height` option.
|
|||||||
vim $(fzf --height 40%)
|
vim $(fzf --height 40%)
|
||||||
```
|
```
|
||||||
|
|
||||||
Also check out `--reverse` option if you prefer "top-down" layout instead of
|
Also check out `--reverse` and `--layout` options if you prefer
|
||||||
the default "bottom-up" layout.
|
"top-down" layout instead of the default "bottom-up" layout.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
vim $(fzf --height 40% --reverse)
|
vim $(fzf --height 40% --reverse)
|
||||||
@@ -228,7 +239,7 @@ You can add these options to `$FZF_DEFAULT_OPTS` so that they're applied by
|
|||||||
default. For example,
|
default. For example,
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
export FZF_DEFAULT_OPTS='--height 40% --reverse --border'
|
export FZF_DEFAULT_OPTS='--height 40% --layout=reverse --border'
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Search syntax
|
#### Search syntax
|
||||||
@@ -237,14 +248,15 @@ Unless otherwise specified, fzf starts in "extended-search mode" where you can
|
|||||||
type in multiple search terms delimited by spaces. e.g. `^music .mp3$ sbtrkt
|
type in multiple search terms delimited by spaces. e.g. `^music .mp3$ sbtrkt
|
||||||
!fire`
|
!fire`
|
||||||
|
|
||||||
| Token | Match type | Description |
|
| Token | Match type | Description |
|
||||||
| -------- | -------------------------- | --------------------------------- |
|
| --------- | -------------------------- | ------------------------------------ |
|
||||||
| `sbtrkt` | fuzzy-match | Items that match `sbtrkt` |
|
| `sbtrkt` | fuzzy-match | Items that match `sbtrkt` |
|
||||||
| `^music` | prefix-exact-match | Items that start with `music` |
|
| `'wild` | exact-match (quoted) | Items that include `wild` |
|
||||||
| `.mp3$` | suffix-exact-match | Items that end with `.mp3` |
|
| `^music` | prefix-exact-match | Items that start with `music` |
|
||||||
| `'wild` | exact-match (quoted) | Items that include `wild` |
|
| `.mp3$` | suffix-exact-match | Items that end with `.mp3` |
|
||||||
| `!fire` | inverse-exact-match | Items that do not include `fire` |
|
| `!fire` | inverse-exact-match | Items that do not include `fire` |
|
||||||
| `!.mp3$` | inverse-suffix-exact-match | Items that do not end with `.mp3` |
|
| `!^music` | inverse-prefix-exact-match | Items that do not start with `music` |
|
||||||
|
| `!.mp3$` | inverse-suffix-exact-match | Items that do not end with `.mp3` |
|
||||||
|
|
||||||
If you don't prefer fuzzy matching and do not wish to "quote" every word,
|
If you don't prefer fuzzy matching and do not wish to "quote" every word,
|
||||||
start fzf with `-e` or `--exact` option. Note that when `--exact` is set,
|
start fzf with `-e` or `--exact` option. Note that when `--exact` is set,
|
||||||
@@ -265,7 +277,7 @@ or `py`.
|
|||||||
- e.g. `export FZF_DEFAULT_COMMAND='fd --type f'`
|
- e.g. `export FZF_DEFAULT_COMMAND='fd --type f'`
|
||||||
- `FZF_DEFAULT_OPTS`
|
- `FZF_DEFAULT_OPTS`
|
||||||
- Default options
|
- Default options
|
||||||
- e.g. `export FZF_DEFAULT_OPTS="--reverse --inline-info"`
|
- e.g. `export FZF_DEFAULT_OPTS="--layout=reverse --inline-info"`
|
||||||
|
|
||||||
#### Options
|
#### Options
|
||||||
|
|
||||||
@@ -304,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
|
||||||
@@ -365,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
|
||||||
@@ -418,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
|
||||||
@@ -435,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.
|
||||||
|
|
||||||
@@ -446,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.
|
||||||
|
|
||||||
@@ -467,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
|
||||||
@@ -475,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
|
||||||
@@ -486,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'
|
||||||
@@ -507,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/
|
||||||
|
|
||||||
@@ -573,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/
|
||||||
@@ -591,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)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
|||||||
@@ -136,6 +136,11 @@ fifo3="${TMPDIR:-/tmp}/fzf-fifo3-$id"
|
|||||||
cleanup() {
|
cleanup() {
|
||||||
\rm -f $argsf $fifo1 $fifo2 $fifo3
|
\rm -f $argsf $fifo1 $fifo2 $fifo3
|
||||||
|
|
||||||
|
# Restore tmux window options
|
||||||
|
if [[ "${#tmux_win_opts[@]}" -gt 0 ]]; then
|
||||||
|
eval "tmux ${tmux_win_opts[@]}"
|
||||||
|
fi
|
||||||
|
|
||||||
# Remove temp window if we were zoomed
|
# Remove temp window if we were zoomed
|
||||||
if [[ -n "$zoomed" ]]; then
|
if [[ -n "$zoomed" ]]; then
|
||||||
tmux display-message -p "#{window_id}" > /dev/null
|
tmux display-message -p "#{window_id}" > /dev/null
|
||||||
@@ -174,6 +179,8 @@ pppid=$$
|
|||||||
echo -n "trap 'kill -SIGUSR1 -$pppid' EXIT SIGINT SIGTERM;" > $argsf
|
echo -n "trap 'kill -SIGUSR1 -$pppid' EXIT SIGINT SIGTERM;" > $argsf
|
||||||
close="; trap - EXIT SIGINT SIGTERM $close"
|
close="; trap - EXIT SIGINT SIGTERM $close"
|
||||||
|
|
||||||
|
tmux_win_opts=( $(tmux show-window-options remain-on-exit \; show-window-options synchronize-panes | sed '/ off/d; s/^/set-window-option /; s/$/ \\;/') )
|
||||||
|
|
||||||
if [[ -n "$term" ]] || [[ -t 0 ]]; then
|
if [[ -n "$term" ]] || [[ -t 0 ]]; then
|
||||||
cat <<< "\"$fzf\" $opts > $fifo2; echo \$? > $fifo3 $close" >> $argsf
|
cat <<< "\"$fzf\" $opts > $fifo2; echo \$? > $fifo3 $close" >> $argsf
|
||||||
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
|
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
|
||||||
|
|||||||
134
glide.lock
generated
134
glide.lock
generated
@@ -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
|
|
||||||
16
glide.yaml
16
glide.yaml
@@ -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
17
go.mod
Normal 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
26
go.sum
Normal 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=
|
||||||
65
install
65
install
@@ -2,13 +2,16 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.17.3
|
version=0.18.0
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
binary_arch=
|
binary_arch=
|
||||||
allow_legacy=
|
allow_legacy=
|
||||||
shells="bash zsh fish"
|
shells="bash zsh fish"
|
||||||
|
prefix='~/.fzf'
|
||||||
|
prefix_expand=~/.fzf
|
||||||
|
fish_dir=${XDG_CONFIG_HOME:-$HOME/.config}/fish
|
||||||
|
|
||||||
help() {
|
help() {
|
||||||
cat << EOF
|
cat << EOF
|
||||||
@@ -18,6 +21,7 @@ usage: $0 [OPTIONS]
|
|||||||
--bin Download fzf binary only; Do not generate ~/.fzf.{bash,zsh}
|
--bin Download fzf binary only; Do not generate ~/.fzf.{bash,zsh}
|
||||||
--all Download fzf binary and update configuration files
|
--all Download fzf binary and update configuration files
|
||||||
to enable key bindings and fuzzy completion
|
to enable key bindings and fuzzy completion
|
||||||
|
--xdg Generate files under \$XDG_CONFIG_HOME/fzf
|
||||||
--[no-]key-bindings Enable/disable key bindings (CTRL-T, CTRL-R, ALT-C)
|
--[no-]key-bindings Enable/disable key bindings (CTRL-T, CTRL-R, ALT-C)
|
||||||
--[no-]completion Enable/disable fuzzy completion (bash & zsh)
|
--[no-]completion Enable/disable fuzzy completion (bash & zsh)
|
||||||
--[no-]update-rc Whether or not to update shell configuration files
|
--[no-]update-rc Whether or not to update shell configuration files
|
||||||
@@ -43,6 +47,11 @@ for opt in "$@"; do
|
|||||||
update_config=1
|
update_config=1
|
||||||
allow_legacy=1
|
allow_legacy=1
|
||||||
;;
|
;;
|
||||||
|
--xdg)
|
||||||
|
prefix='"${XDG_CONFIG_HOME:-$HOME/.config}"/fzf/fzf'
|
||||||
|
prefix_expand=${XDG_CONFIG_HOME:-$HOME/.config}/fzf/fzf
|
||||||
|
mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/fzf"
|
||||||
|
;;
|
||||||
--key-bindings) key_bindings=1 ;;
|
--key-bindings) key_bindings=1 ;;
|
||||||
--no-key-bindings) key_bindings=0 ;;
|
--no-key-bindings) key_bindings=0 ;;
|
||||||
--completion) auto_completion=1 ;;
|
--completion) auto_completion=1 ;;
|
||||||
@@ -64,11 +73,13 @@ 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
|
||||||
read -p "$1 ([y]/n) " -r
|
read -p "$1 ([y]/n) " -r
|
||||||
|
REPLY=${REPLY:-"y"}
|
||||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
return 1
|
return 1
|
||||||
elif [[ $REPLY =~ ^[Nn]$ ]]; then
|
elif [[ $REPLY =~ ^[Nn]$ ]]; then
|
||||||
@@ -80,17 +91,20 @@ 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
|
|
||||||
echo "$output != $version"
|
|
||||||
binary_error="Invalid version"
|
|
||||||
else
|
else
|
||||||
echo "$output"
|
output=${output/ */}
|
||||||
binary_error=""
|
if [ "$version" != "$output" ]; then
|
||||||
return 0
|
echo "$output != $version"
|
||||||
|
binary_error="Invalid version"
|
||||||
|
else
|
||||||
|
echo "$output"
|
||||||
|
binary_error=""
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
rm -f "$fzf_base"/bin/fzf
|
rm -f "$fzf_base"/bin/fzf
|
||||||
return 1
|
return 1
|
||||||
@@ -99,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
|
||||||
@@ -182,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
|
||||||
|
|
||||||
@@ -239,8 +255,8 @@ fi
|
|||||||
echo
|
echo
|
||||||
for shell in $shells; do
|
for shell in $shells; do
|
||||||
[[ "$shell" = fish ]] && continue
|
[[ "$shell" = fish ]] && continue
|
||||||
echo -n "Generate ~/.fzf.$shell ... "
|
src=${prefix_expand}.${shell}
|
||||||
src=~/.fzf.${shell}
|
echo -n "Generate $src ... "
|
||||||
|
|
||||||
fzf_completion="[[ \$- == *i* ]] && source \"$fzf_base/shell/completion.${shell}\" 2> /dev/null"
|
fzf_completion="[[ \$- == *i* ]] && source \"$fzf_base/shell/completion.${shell}\" 2> /dev/null"
|
||||||
if [ $auto_completion -eq 0 ]; then
|
if [ $auto_completion -eq 0 ]; then
|
||||||
@@ -252,11 +268,11 @@ for shell in $shells; do
|
|||||||
fzf_key_bindings="# $fzf_key_bindings"
|
fzf_key_bindings="# $fzf_key_bindings"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
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
|
||||||
@@ -266,7 +282,6 @@ $fzf_completion
|
|||||||
# Key bindings
|
# Key bindings
|
||||||
# ------------
|
# ------------
|
||||||
$fzf_key_bindings
|
$fzf_key_bindings
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
echo "OK"
|
echo "OK"
|
||||||
done
|
done
|
||||||
@@ -275,18 +290,18 @@ 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"
|
||||||
|
|
||||||
mkdir -p ~/.config/fish/functions
|
mkdir -p "${fish_dir}/functions"
|
||||||
if [ -e ~/.config/fish/functions/fzf.fish ]; then
|
if [ -e "${fish_dir}/functions/fzf.fish" ]; then
|
||||||
echo -n "Remove unnecessary ~/.config/fish/functions/fzf.fish ... "
|
echo -n "Remove unnecessary ${fish_dir}/functions/fzf.fish ... "
|
||||||
rm -f ~/.config/fish/functions/fzf.fish && echo "OK" || echo "Failed"
|
rm -f "${fish_dir}/functions/fzf.fish" && echo "OK" || echo "Failed"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
fish_binding=~/.config/fish/functions/fzf_key_bindings.fish
|
fish_binding="${fish_dir}/functions/fzf_key_bindings.fish"
|
||||||
if [ $key_bindings -ne 0 ]; then
|
if [ $key_bindings -ne 0 ]; then
|
||||||
echo -n "Symlink $fish_binding ... "
|
echo -n "Symlink $fish_binding ... "
|
||||||
ln -sf "$fzf_base/shell/key-bindings.fish" \
|
ln -sf "$fzf_base/shell/key-bindings.fish" \
|
||||||
@@ -352,11 +367,11 @@ echo
|
|||||||
for shell in $shells; do
|
for shell in $shells; do
|
||||||
[[ "$shell" = fish ]] && continue
|
[[ "$shell" = fish ]] && continue
|
||||||
[ $shell = zsh ] && dest=${ZDOTDIR:-~}/.zshrc || dest=~/.bashrc
|
[ $shell = zsh ] && dest=${ZDOTDIR:-~}/.zshrc || dest=~/.bashrc
|
||||||
append_line $update_config "[ -f ~/.fzf.${shell} ] && source ~/.fzf.${shell}" "$dest" "~/.fzf.${shell}"
|
append_line $update_config "[ -f ${prefix}.${shell} ] && source ${prefix}.${shell}" "$dest" "${prefix}.${shell}"
|
||||||
done
|
done
|
||||||
|
|
||||||
if [ $key_bindings -eq 1 ] && [[ "$shells" =~ fish ]]; then
|
if [ $key_bindings -eq 1 ] && [[ "$shells" =~ fish ]]; then
|
||||||
bind_file=~/.config/fish/functions/fish_user_key_bindings.fish
|
bind_file="${fish_dir}/functions/fish_user_key_bindings.fish"
|
||||||
if [ ! -e "$bind_file" ]; then
|
if [ ! -e "$bind_file" ]; then
|
||||||
create_file "$bind_file" \
|
create_file "$bind_file" \
|
||||||
'function fish_user_key_bindings' \
|
'function fish_user_key_bindings' \
|
||||||
|
|||||||
@@ -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 "Dec 2017" "fzf 0.17.3" "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
|
||||||
|
|||||||
@@ -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 "Dec 2017" "fzf 0.17.3" "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
|
||||||
@@ -155,12 +155,30 @@ the full screen.
|
|||||||
.BI "--min-height=" "HEIGHT"
|
.BI "--min-height=" "HEIGHT"
|
||||||
Minimum height when \fB--height\fR is given in percent (default: 10).
|
Minimum height when \fB--height\fR is given in percent (default: 10).
|
||||||
Ignored when \fB--height\fR is not specified.
|
Ignored when \fB--height\fR is not specified.
|
||||||
|
.TP
|
||||||
|
.BI "--layout=" "LAYOUT"
|
||||||
|
Choose the layout (default: default)
|
||||||
|
|
||||||
|
.br
|
||||||
|
.BR default " Display from the bottom of the screen"
|
||||||
|
.br
|
||||||
|
.BR reverse " Display from the top of the screen"
|
||||||
|
.br
|
||||||
|
.BR reverse-list " Display from the top of the screen, prompt at the bottom"
|
||||||
|
.br
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B "--reverse"
|
.B "--reverse"
|
||||||
Reverse orientation
|
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.
|
||||||
@@ -195,7 +213,7 @@ Input prompt (default: '> ')
|
|||||||
.TP
|
.TP
|
||||||
.BI "--header=" "STR"
|
.BI "--header=" "STR"
|
||||||
The given string will be printed as the sticky header. The lines are displayed
|
The given string will be printed as the sticky header. The lines are displayed
|
||||||
in the given order from top to bottom regardless of \fB--reverse\fR option, and
|
in the given order from top to bottom regardless of \fB--layout\fR option, and
|
||||||
are not affected by \fB--with-nth\fR. ANSI color codes are processed even when
|
are not affected by \fB--with-nth\fR. ANSI color codes are processed even when
|
||||||
\fB--ansi\fR is not set.
|
\fB--ansi\fR is not set.
|
||||||
.TP
|
.TP
|
||||||
@@ -237,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)
|
||||||
@@ -275,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
|
||||||
@@ -285,9 +307,17 @@ was made) individually quoted.
|
|||||||
e.g. \fBfzf --multi --preview='head -10 {+}'\fR
|
e.g. \fBfzf --multi --preview='head -10 {+}'\fR
|
||||||
\fBgit log --oneline | fzf --multi --preview 'git show {+1}'\fR
|
\fBgit log --oneline | fzf --multi --preview 'git show {+1}'\fR
|
||||||
|
|
||||||
Also, \fB{q}\fR is replaced to the current query string.
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Preview window will be updated even when there is no match for the current
|
||||||
|
query if any of the placeholder expressions evaluates to a non-empty string.
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.BI "--preview-window=" "[POSITION][:SIZE[%]][:wrap][:hidden]"
|
.BI "--preview-window=" "[POSITION][:SIZE[%]][:wrap][:hidden]"
|
||||||
@@ -371,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
|
||||||
@@ -461,6 +492,10 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
|||||||
\fIenter\fR (\fIreturn\fR \fIctrl-m\fR)
|
\fIenter\fR (\fIreturn\fR \fIctrl-m\fR)
|
||||||
\fIspace\fR
|
\fIspace\fR
|
||||||
\fIbspace\fR (\fIbs\fR)
|
\fIbspace\fR (\fIbs\fR)
|
||||||
|
\fIalt-up\fR
|
||||||
|
\fIalt-down\fR
|
||||||
|
\fIalt-left\fR
|
||||||
|
\fIalt-right\fR
|
||||||
\fIalt-enter\fR
|
\fIalt-enter\fR
|
||||||
\fIalt-space\fR
|
\fIalt-space\fR
|
||||||
\fIalt-bspace\fR (\fIalt-bs\fR)
|
\fIalt-bspace\fR (\fIalt-bs\fR)
|
||||||
@@ -477,6 +512,8 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
|||||||
\fIend\fR
|
\fIend\fR
|
||||||
\fIpgup\fR (\fIpage-up\fR)
|
\fIpgup\fR (\fIpage-up\fR)
|
||||||
\fIpgdn\fR (\fIpage-down\fR)
|
\fIpgdn\fR (\fIpage-down\fR)
|
||||||
|
\fIshift-up\fR
|
||||||
|
\fIshift-down\fR
|
||||||
\fIshift-left\fR
|
\fIshift-left\fR
|
||||||
\fIshift-right\fR
|
\fIshift-right\fR
|
||||||
\fIleft-click\fR
|
\fIleft-click\fR
|
||||||
@@ -520,8 +557,8 @@ triggered whenever the query string is changed.
|
|||||||
\fBpage-up\fR \fIpgup\fR
|
\fBpage-up\fR \fIpgup\fR
|
||||||
\fBhalf-page-down\fR
|
\fBhalf-page-down\fR
|
||||||
\fBhalf-page-up\fR
|
\fBhalf-page-up\fR
|
||||||
\fBpreview-down\fR
|
\fBpreview-down\fR \fIshift-down\fR
|
||||||
\fBpreview-up\fR
|
\fBpreview-up\fR \fIshift-up\fR
|
||||||
\fBpreview-page-down\fR
|
\fBpreview-page-down\fR
|
||||||
\fBpreview-page-up\fR
|
\fBpreview-page-up\fR
|
||||||
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
||||||
@@ -531,8 +568,8 @@ triggered whenever the query string is changed.
|
|||||||
\fBtoggle\fR (\fIright-click\fR)
|
\fBtoggle\fR (\fIright-click\fR)
|
||||||
\fBtoggle-all\fR
|
\fBtoggle-all\fR
|
||||||
\fBtoggle+down\fR \fIctrl-i (tab)\fR
|
\fBtoggle+down\fR \fIctrl-i (tab)\fR
|
||||||
\fBtoggle-in\fR (\fB--reverse\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
\fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
||||||
\fBtoggle-out\fR (\fB--reverse\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
||||||
\fBtoggle-preview\fR
|
\fBtoggle-preview\fR
|
||||||
\fBtoggle-preview-wrap\fR
|
\fBtoggle-preview-wrap\fR
|
||||||
\fBtoggle-sort\fR
|
\fBtoggle-sort\fR
|
||||||
@@ -583,7 +620,7 @@ fzf switches to the alternate screen when executing a command. However, if the
|
|||||||
command is expected to complete quickly, and you are not interested in its
|
command is expected to complete quickly, and you are not interested in its
|
||||||
output, you might want to use \fBexecute-silent\fR instead, which silently
|
output, you might want to use \fBexecute-silent\fR instead, which silently
|
||||||
executes the command without the switching. Note that fzf will not be
|
executes the command without the switching. Note that fzf will not be
|
||||||
responsible until the command is complete. For asynchronous execution, start
|
responsive until the command is complete. For asynchronous execution, start
|
||||||
your command as a background process (i.e. appending \fB&\fR).
|
your command as a background process (i.e. appending \fB&\fR).
|
||||||
|
|
||||||
.SH AUTHOR
|
.SH AUTHOR
|
||||||
|
|||||||
@@ -50,9 +50,9 @@ if s:is_win
|
|||||||
" Use utf-8 for fzf.vim commands
|
" Use utf-8 for fzf.vim commands
|
||||||
" Return array of shell commands for cmd.exe
|
" Return array of shell commands for cmd.exe
|
||||||
function! s:wrap_cmds(cmds)
|
function! s:wrap_cmds(cmds)
|
||||||
return ['@echo off', 'for /f "tokens=4" %%a in (''chcp'') do set origchcp=%%a', 'chcp 65001 > nul'] +
|
return map(['@echo off', 'for /f "tokens=4" %%a in (''chcp'') do set origchcp=%%a', 'chcp 65001 > nul'] +
|
||||||
\ (type(a:cmds) == type([]) ? a:cmds : [a:cmds]) +
|
\ (type(a:cmds) == type([]) ? a:cmds : [a:cmds]) +
|
||||||
\ ['chcp %origchcp% > nul']
|
\ ['chcp %origchcp% > nul'], 'v:val."\r"')
|
||||||
endfunction
|
endfunction
|
||||||
else
|
else
|
||||||
let s:term_marker = ";#FZF"
|
let s:term_marker = ";#FZF"
|
||||||
@@ -231,6 +231,7 @@ function! s:common_sink(action, lines) abort
|
|||||||
doautocmd BufEnter
|
doautocmd BufEnter
|
||||||
endif
|
endif
|
||||||
endfor
|
endfor
|
||||||
|
catch /^Vim:Interrupt$/
|
||||||
finally
|
finally
|
||||||
let &autochdir = autochdir
|
let &autochdir = autochdir
|
||||||
silent! autocmd! fzf_swap
|
silent! autocmd! fzf_swap
|
||||||
@@ -356,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')
|
||||||
@@ -454,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
|
||||||
@@ -471,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()
|
||||||
@@ -533,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
|
||||||
@@ -562,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)
|
||||||
@@ -586,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 = 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
|
||||||
@@ -685,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)
|
||||||
@@ -699,15 +719,13 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
if has('nvim')
|
if has('nvim')
|
||||||
call termopen(command, fzf)
|
call termopen(command, fzf)
|
||||||
else
|
else
|
||||||
let t = term_start([&shell, &shellcmdflag, command], {'curwin': fzf.buf, 'exit_cb': function(fzf.on_exit)})
|
let fzf.buf = term_start([&shell, &shellcmdflag, command], {'curwin': 1, 'exit_cb': function(fzf.on_exit)})
|
||||||
if !has('patch-8.0.1261') && !has('nvim') && !s:is_win
|
if !has('patch-8.0.1261') && !has('nvim') && !s:is_win
|
||||||
call term_wait(t, 20)
|
call term_wait(fzf.buf, 20)
|
||||||
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
|
||||||
@@ -726,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
|
||||||
@@ -764,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
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#!/bin/bash
|
|
||||||
# ____ ____
|
# ____ ____
|
||||||
# / __/___ / __/
|
# / __/___ / __/
|
||||||
# / /_/_ / / /_
|
# / /_/_ / / /_
|
||||||
@@ -10,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() {
|
||||||
@@ -38,9 +39,9 @@ __fzfcmd_complete() {
|
|||||||
echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
|
echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_orig_completion_filter() {
|
__fzf_orig_completion_filter() {
|
||||||
sed 's/^\(.*-F\) *\([^ ]*\).* \([^ ]*\)$/export _fzf_orig_completion_\3="\1 %s \3 #\2";/' |
|
sed 's/^\(.*-F\) *\([^ ]*\).* \([^ ]*\)$/export _fzf_orig_completion_\3="\1 %s \3 #\2"; [[ "\1" = *" -o nospace "* ]] \&\& [[ ! "$__fzf_nospace_commands" = *" \3 "* ]] \&\& __fzf_nospace_commands="$__fzf_nospace_commands \3 ";/' |
|
||||||
awk -F= '{gsub(/[^A-Za-z0-9_= ;]/, "_", $1); print $1"="$2}'
|
awk -F= '{OFS = FS} {gsub(/[^A-Za-z0-9_= ;]/, "_", $1);}1'
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_opts_completion() {
|
_fzf_opts_completion() {
|
||||||
@@ -122,13 +123,17 @@ _fzf_handle_dynamic_completion() {
|
|||||||
if [ -n "$orig" ] && type "$orig" > /dev/null 2>&1; then
|
if [ -n "$orig" ] && type "$orig" > /dev/null 2>&1; then
|
||||||
$orig "$@"
|
$orig "$@"
|
||||||
elif [ -n "$_fzf_completion_loader" ]; then
|
elif [ -n "$_fzf_completion_loader" ]; then
|
||||||
orig_complete=$(complete -p "$cmd")
|
orig_complete=$(complete -p "$cmd" 2> /dev/null)
|
||||||
_completion_loader "$@"
|
_completion_loader "$@"
|
||||||
ret=$?
|
ret=$?
|
||||||
# _completion_loader may not have updated completion for the command
|
# _completion_loader may not have updated completion for the command
|
||||||
if [ "$(complete -p "$cmd")" != "$orig_complete" ]; then
|
if [ "$(complete -p "$cmd" 2> /dev/null)" != "$orig_complete" ]; then
|
||||||
eval "$(complete | command grep "\-F.* $orig_cmd$" | _fzf_orig_completion_filter)"
|
eval "$(complete | command grep " -F.* $orig_cmd$" | __fzf_orig_completion_filter)"
|
||||||
eval "$orig_complete"
|
if [[ "$__fzf_nospace_commands" = *" $orig_cmd "* ]]; then
|
||||||
|
eval "${orig_complete/ -F / -o nospace -F }"
|
||||||
|
else
|
||||||
|
eval "$orig_complete"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
return $ret
|
return $ret
|
||||||
fi
|
fi
|
||||||
@@ -145,7 +150,7 @@ __fzf_generic_path_completion() {
|
|||||||
base=${cur:0:${#cur}-${#trigger}}
|
base=${cur:0:${#cur}-${#trigger}}
|
||||||
eval "base=$base"
|
eval "base=$base"
|
||||||
|
|
||||||
dir="$base"
|
[[ $base = *"/"* ]] && dir="$base"
|
||||||
while true; do
|
while true; do
|
||||||
if [ -z "$dir" ] || [ -d "$dir" ]; then
|
if [ -z "$dir" ] || [ -d "$dir" ]; then
|
||||||
leftover=${base/#"$dir"}
|
leftover=${base/#"$dir"}
|
||||||
@@ -156,6 +161,7 @@ __fzf_generic_path_completion() {
|
|||||||
printf "%q$3 " "$item"
|
printf "%q$3 " "$item"
|
||||||
done)
|
done)
|
||||||
matches=${matches% }
|
matches=${matches% }
|
||||||
|
[[ -z "$3" ]] && [[ "$__fzf_nospace_commands" = *" ${COMP_WORDS[0]} "* ]] && matches="$matches "
|
||||||
if [ -n "$matches" ]; then
|
if [ -n "$matches" ]; then
|
||||||
COMPREPLY=( "$matches" )
|
COMPREPLY=( "$matches" )
|
||||||
else
|
else
|
||||||
@@ -237,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
|
||||||
@@ -278,9 +284,9 @@ a_cmds="
|
|||||||
x_cmds="kill ssh telnet unset unalias export"
|
x_cmds="kill ssh telnet unset unalias export"
|
||||||
|
|
||||||
# Preserve existing completion
|
# Preserve existing completion
|
||||||
eval $(complete |
|
eval "$(complete |
|
||||||
sed -E '/-F/!d; / _fzf/d; '"/ ($(echo $d_cmds $a_cmds $x_cmds | sed 's/ /|/g; s/+/\\+/g'))$/"'!d' |
|
sed -E '/-F/!d; / _fzf/d; '"/ ($(echo $d_cmds $a_cmds $x_cmds | sed 's/ /|/g; s/+/\\+/g'))$/"'!d' |
|
||||||
_fzf_orig_completion_filter)
|
__fzf_orig_completion_filter)"
|
||||||
|
|
||||||
if type _completion_loader > /dev/null 2>&1; then
|
if type _completion_loader > /dev/null 2>&1; then
|
||||||
_fzf_completion_loader=1
|
_fzf_completion_loader=1
|
||||||
@@ -326,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
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#!/bin/zsh
|
|
||||||
# ____ ____
|
# ____ ____
|
||||||
# / __/___ / __/
|
# / __/___ / __/
|
||||||
# / /_/_ / / /_
|
# / /_/_ / / /_
|
||||||
@@ -10,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() {
|
||||||
@@ -37,8 +38,7 @@ __fzfcmd_complete() {
|
|||||||
|
|
||||||
__fzf_generic_path_completion() {
|
__fzf_generic_path_completion() {
|
||||||
local base lbuf compgen fzf_opts suffix tail fzf dir leftover matches
|
local base lbuf compgen fzf_opts suffix tail fzf dir leftover matches
|
||||||
# (Q) flag removes a quoting level: "foo\ bar" => "foo bar"
|
base=$1
|
||||||
base=${(Q)1}
|
|
||||||
lbuf=$2
|
lbuf=$2
|
||||||
compgen=$3
|
compgen=$3
|
||||||
fzf_opts=$4
|
fzf_opts=$4
|
||||||
@@ -47,14 +47,14 @@ __fzf_generic_path_completion() {
|
|||||||
fzf="$(__fzfcmd_complete)"
|
fzf="$(__fzfcmd_complete)"
|
||||||
|
|
||||||
setopt localoptions nonomatch
|
setopt localoptions nonomatch
|
||||||
dir="$base"
|
eval "base=$base"
|
||||||
|
[[ $base = *"/"* ]] && dir="$base"
|
||||||
while [ 1 ]; do
|
while [ 1 ]; do
|
||||||
if [[ -z "$dir" || -d ${~dir} ]]; then
|
if [[ -z "$dir" || -d ${dir} ]]; then
|
||||||
leftover=${base/#"$dir"}
|
leftover=${base/#"$dir"}
|
||||||
leftover=${leftover/#\/}
|
leftover=${leftover/#\/}
|
||||||
[ -z "$dir" ] && dir='.'
|
[ -z "$dir" ] && dir='.'
|
||||||
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
||||||
dir=${~dir}
|
|
||||||
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" ${=fzf} ${=fzf_opts} -q "$leftover" | while read item; do
|
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" ${=fzf} ${=fzf_opts} -q "$leftover" | while read item; do
|
||||||
echo -n "${(q)item}$suffix "
|
echo -n "${(q)item}$suffix "
|
||||||
done)
|
done)
|
||||||
@@ -62,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")
|
||||||
@@ -102,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"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,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
|
||||||
@@ -167,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})
|
||||||
@@ -197,3 +195,5 @@ fzf-completion() {
|
|||||||
|
|
||||||
zle -N fzf-completion
|
zle -N fzf-completion
|
||||||
bindkey '^I' fzf-completion
|
bindkey '^I' fzf-completion
|
||||||
|
|
||||||
|
fi
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ __fzf_history__() (
|
|||||||
shopt -u nocaseglob nocasematch
|
shopt -u nocaseglob nocasematch
|
||||||
line=$(
|
line=$(
|
||||||
HISTTIMEFORMAT= history |
|
HISTTIMEFORMAT= history |
|
||||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS --tac -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m" $(__fzfcmd) |
|
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS --tac --sync -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m" $(__fzfcmd) |
|
||||||
command grep '^ *[0-9]') &&
|
command grep '^ *[0-9]') &&
|
||||||
if [[ $- =~ H ]]; then
|
if [[ $- =~ H ]]; then
|
||||||
sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line"
|
sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line"
|
||||||
@@ -80,7 +80,7 @@ if [[ ! -o vi ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# CTRL-R - Paste the selected command from history into the command line
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
bind '"\C-r": " \C-e\C-u`__fzf_history__`\e\C-e\e^\er"'
|
bind '"\C-r": " \C-e\C-u\C-y\ey\C-u`__fzf_history__`\e\C-e\er\e^"'
|
||||||
|
|
||||||
# ALT-C - cd into the selected directory
|
# ALT-C - cd into the selected directory
|
||||||
bind '"\ec": " \C-e\C-u`__fzf_cd__`\e\C-e\er\C-m"'
|
bind '"\ec": " \C-e\C-u`__fzf_cd__`\e\C-e\er\C-m"'
|
||||||
@@ -110,7 +110,7 @@ else
|
|||||||
bind -m vi-command '"\C-t": "i\C-t"'
|
bind -m vi-command '"\C-t": "i\C-t"'
|
||||||
|
|
||||||
# CTRL-R - Paste the selected command from history into the command line
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
bind '"\C-r": "\C-x\C-addi`__fzf_history__`\C-x\C-e\C-x^\C-x\C-a$a\C-x\C-r"'
|
bind '"\C-r": "\C-x\C-addi`__fzf_history__`\C-x\C-e\C-x\C-r\C-x^\C-x\C-a$a"'
|
||||||
bind -m vi-command '"\C-r": "i\C-r"'
|
bind -m vi-command '"\C-r": "i\C-r"'
|
||||||
|
|
||||||
# ALT-C - cd into the selected directory
|
# ALT-C - cd into the selected directory
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
49
src/ansi.go
49
src/ansi.go
@@ -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() {
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -26,7 +27,7 @@ func TestExtractColor(t *testing.T) {
|
|||||||
output, ansiOffsets, newState := extractColor(src, state, nil)
|
output, ansiOffsets, newState := extractColor(src, state, nil)
|
||||||
state = newState
|
state = newState
|
||||||
if output != "hello world" {
|
if output != "hello world" {
|
||||||
t.Errorf("Invalid output: %s %s", output, []rune(output))
|
t.Errorf("Invalid output: %s %v", output, []rune(output))
|
||||||
}
|
}
|
||||||
fmt.Println(src, ansiOffsets, clean)
|
fmt.Println(src, ansiOffsets, clean)
|
||||||
assertion(ansiOffsets, state)
|
assertion(ansiOffsets, state)
|
||||||
@@ -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")
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// Current version
|
// Current version
|
||||||
version = "0.17.3"
|
version = "0.18.0"
|
||||||
|
|
||||||
// Core
|
// Core
|
||||||
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
||||||
@@ -22,9 +22,11 @@ const (
|
|||||||
readerPollIntervalMax = 50 * time.Millisecond
|
readerPollIntervalMax = 50 * time.Millisecond
|
||||||
|
|
||||||
// Terminal
|
// Terminal
|
||||||
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
|
||||||
@@ -59,7 +61,7 @@ func init() {
|
|||||||
} else if os.Getenv("TERM") == "cygwin" {
|
} else if os.Getenv("TERM") == "cygwin" {
|
||||||
defaultCommand = `sh -c "command find -L . -mindepth 1 -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-"`
|
defaultCommand = `sh -c "command find -L . -mindepth 1 -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-"`
|
||||||
} else {
|
} else {
|
||||||
defaultCommand = `dir /s/b`
|
defaultCommand = `for /r %P in (*) do @(set "_curfile=%P" & set "_curfile=!_curfile:%__CD__%=!" & echo !_curfile!)`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +77,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
exitCancel = -1
|
||||||
exitOk = 0
|
exitOk = 0
|
||||||
exitNoMatch = 1
|
exitNoMatch = 1
|
||||||
exitError = 2
|
exitError = 2
|
||||||
|
|||||||
25
src/core.go
25
src/core.go
@@ -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 {
|
||||||
|
|||||||
@@ -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})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ const usage = `usage: fzf [options]
|
|||||||
height instead of using fullscreen
|
height instead of using fullscreen
|
||||||
--min-height=HEIGHT Minimum height when --height is given in percent
|
--min-height=HEIGHT Minimum height when --height is given in percent
|
||||||
(default: 10)
|
(default: 10)
|
||||||
--reverse Reverse orientation
|
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
|
||||||
--border Draw border above and below the finder
|
--border Draw border above and below the finder
|
||||||
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
|
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
|
||||||
--inline-info Display finder info inline with the query
|
--inline-info Display finder info inline with the query
|
||||||
@@ -90,7 +90,8 @@ const usage = `usage: fzf [options]
|
|||||||
|
|
||||||
Environment variables
|
Environment variables
|
||||||
FZF_DEFAULT_COMMAND Default command to use when input is tty
|
FZF_DEFAULT_COMMAND Default command to use when input is tty
|
||||||
FZF_DEFAULT_OPTS Default options (e.g. '--reverse --inline-info')
|
FZF_DEFAULT_OPTS Default options
|
||||||
|
(e.g. '--layout=reverse --inline-info')
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -132,6 +133,14 @@ const (
|
|||||||
posRight
|
posRight
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type layoutType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
layoutDefault layoutType = iota
|
||||||
|
layoutReverse
|
||||||
|
layoutReverseList
|
||||||
|
)
|
||||||
|
|
||||||
type previewOpts struct {
|
type previewOpts struct {
|
||||||
command string
|
command string
|
||||||
position windowPosition
|
position windowPosition
|
||||||
@@ -161,7 +170,7 @@ type Options struct {
|
|||||||
Bold bool
|
Bold bool
|
||||||
Height sizeSpec
|
Height sizeSpec
|
||||||
MinHeight int
|
MinHeight int
|
||||||
Reverse bool
|
Layout layoutType
|
||||||
Cycle bool
|
Cycle bool
|
||||||
Hscroll bool
|
Hscroll bool
|
||||||
HscrollOff int
|
HscrollOff int
|
||||||
@@ -186,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
|
||||||
@@ -211,7 +221,7 @@ func defaultOptions() *Options {
|
|||||||
Black: false,
|
Black: false,
|
||||||
Bold: true,
|
Bold: true,
|
||||||
MinHeight: 10,
|
MinHeight: 10,
|
||||||
Reverse: false,
|
Layout: layoutDefault,
|
||||||
Cycle: false,
|
Cycle: false,
|
||||||
Hscroll: true,
|
Hscroll: true,
|
||||||
HscrollOff: 10,
|
HscrollOff: 10,
|
||||||
@@ -235,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}
|
||||||
@@ -410,6 +421,14 @@ func parseKeyChords(str string, message string) map[int]string {
|
|||||||
chord = tui.AltSlash
|
chord = tui.AltSlash
|
||||||
case "alt-bs", "alt-bspace":
|
case "alt-bs", "alt-bspace":
|
||||||
chord = tui.AltBS
|
chord = tui.AltBS
|
||||||
|
case "alt-up":
|
||||||
|
chord = tui.AltUp
|
||||||
|
case "alt-down":
|
||||||
|
chord = tui.AltDown
|
||||||
|
case "alt-left":
|
||||||
|
chord = tui.AltLeft
|
||||||
|
case "alt-right":
|
||||||
|
chord = tui.AltRight
|
||||||
case "tab":
|
case "tab":
|
||||||
chord = tui.Tab
|
chord = tui.Tab
|
||||||
case "btab", "shift-tab":
|
case "btab", "shift-tab":
|
||||||
@@ -426,6 +445,10 @@ func parseKeyChords(str string, message string) map[int]string {
|
|||||||
chord = tui.PgUp
|
chord = tui.PgUp
|
||||||
case "pgdn", "page-down":
|
case "pgdn", "page-down":
|
||||||
chord = tui.PgDn
|
chord = tui.PgDn
|
||||||
|
case "shift-up":
|
||||||
|
chord = tui.SUp
|
||||||
|
case "shift-down":
|
||||||
|
chord = tui.SDown
|
||||||
case "shift-left":
|
case "shift-left":
|
||||||
chord = tui.SLeft
|
chord = tui.SLeft
|
||||||
case "shift-right":
|
case "shift-right":
|
||||||
@@ -555,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+":
|
||||||
@@ -845,6 +870,20 @@ func parseHeight(str string) sizeSpec {
|
|||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseLayout(str string) layoutType {
|
||||||
|
switch str {
|
||||||
|
case "default":
|
||||||
|
return layoutDefault
|
||||||
|
case "reverse":
|
||||||
|
return layoutReverse
|
||||||
|
case "reverse-list":
|
||||||
|
return layoutReverseList
|
||||||
|
default:
|
||||||
|
errorExit("invalid layout (expected: default / reverse / reverse-list)")
|
||||||
|
}
|
||||||
|
return layoutDefault
|
||||||
|
}
|
||||||
|
|
||||||
func parsePreviewWindow(opts *previewOpts, input string) {
|
func parsePreviewWindow(opts *previewOpts, input string) {
|
||||||
// Default
|
// Default
|
||||||
opts.position = posRight
|
opts.position = posRight
|
||||||
@@ -1025,10 +1064,13 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Bold = true
|
opts.Bold = true
|
||||||
case "--no-bold":
|
case "--no-bold":
|
||||||
opts.Bold = false
|
opts.Bold = false
|
||||||
|
case "--layout":
|
||||||
|
opts.Layout = parseLayout(
|
||||||
|
nextString(allArgs, &i, "layout required (default / reverse / reverse-list)"))
|
||||||
case "--reverse":
|
case "--reverse":
|
||||||
opts.Reverse = true
|
opts.Layout = layoutReverse
|
||||||
case "--no-reverse":
|
case "--no-reverse":
|
||||||
opts.Reverse = false
|
opts.Layout = layoutDefault
|
||||||
case "--cycle":
|
case "--cycle":
|
||||||
opts.Cycle = true
|
opts.Cycle = true
|
||||||
case "--no-cycle":
|
case "--no-cycle":
|
||||||
@@ -1112,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)"))
|
||||||
@@ -1144,6 +1190,8 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Height = parseHeight(value)
|
opts.Height = parseHeight(value)
|
||||||
} else if match, value := optString(arg, "--min-height="); match {
|
} else if match, value := optString(arg, "--min-height="); match {
|
||||||
opts.MinHeight = atoi(value)
|
opts.MinHeight = atoi(value)
|
||||||
|
} else if match, value := optString(arg, "--layout="); match {
|
||||||
|
opts.Layout = parseLayout(value)
|
||||||
} else if match, value := optString(arg, "--toggle-sort="); match {
|
} else if match, value := optString(arg, "--toggle-sort="); match {
|
||||||
parseToggleSort(opts.Keymap, value)
|
parseToggleSort(opts.Keymap, value)
|
||||||
} else if match, value := optString(arg, "--expect="); match {
|
} else if match, value := optString(arg, "--expect="); match {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ func TestDelimiterRegexString(t *testing.T) {
|
|||||||
tokens[2].text.ToString() != "---*" ||
|
tokens[2].text.ToString() != "---*" ||
|
||||||
tokens[3].text.ToString() != "*" ||
|
tokens[3].text.ToString() != "*" ||
|
||||||
tokens[4].text.ToString() != "---" {
|
tokens[4].text.ToString() != "---" {
|
||||||
t.Errorf("%s %s %d", delim, tokens, len(tokens))
|
t.Errorf("%s %v %d", delim, tokens, len(tokens))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ func TestSplitNth(t *testing.T) {
|
|||||||
if len(ranges) != 1 ||
|
if len(ranges) != 1 ||
|
||||||
ranges[0].begin != rangeEllipsis ||
|
ranges[0].begin != rangeEllipsis ||
|
||||||
ranges[0].end != rangeEllipsis {
|
ranges[0].end != rangeEllipsis {
|
||||||
t.Errorf("%s", ranges)
|
t.Errorf("%v", ranges)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@@ -87,7 +87,7 @@ func TestSplitNth(t *testing.T) {
|
|||||||
ranges[7].begin != -2 || ranges[7].end != -2 ||
|
ranges[7].begin != -2 || ranges[7].end != -2 ||
|
||||||
ranges[8].begin != 2 || ranges[8].end != -2 ||
|
ranges[8].begin != 2 || ranges[8].end != -2 ||
|
||||||
ranges[9].begin != rangeEllipsis || ranges[9].end != rangeEllipsis {
|
ranges[9].begin != rangeEllipsis || ranges[9].end != rangeEllipsis {
|
||||||
t.Errorf("%s", ranges)
|
t.Errorf("%v", ranges)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,7 +99,7 @@ func TestIrrelevantNth(t *testing.T) {
|
|||||||
parseOptions(opts, words)
|
parseOptions(opts, words)
|
||||||
postProcessOptions(opts)
|
postProcessOptions(opts)
|
||||||
if len(opts.Nth) != 0 {
|
if len(opts.Nth) != 0 {
|
||||||
t.Errorf("nth should be empty: %s", opts.Nth)
|
t.Errorf("nth should be empty: %v", opts.Nth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, words := range [][]string{[]string{"--nth", "..,3", "+x"}, []string{"--nth", "3,1..", "+x"}, []string{"--nth", "..-1,1", "+x"}} {
|
for _, words := range [][]string{[]string{"--nth", "..,3", "+x"}, []string{"--nth", "3,1..", "+x"}, []string{"--nth", "..-1,1", "+x"}} {
|
||||||
@@ -108,7 +108,7 @@ func TestIrrelevantNth(t *testing.T) {
|
|||||||
parseOptions(opts, words)
|
parseOptions(opts, words)
|
||||||
postProcessOptions(opts)
|
postProcessOptions(opts)
|
||||||
if len(opts.Nth) != 0 {
|
if len(opts.Nth) != 0 {
|
||||||
t.Errorf("nth should be empty: %s", opts.Nth)
|
t.Errorf("nth should be empty: %v", opts.Nth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@@ -117,7 +117,7 @@ func TestIrrelevantNth(t *testing.T) {
|
|||||||
parseOptions(opts, words)
|
parseOptions(opts, words)
|
||||||
postProcessOptions(opts)
|
postProcessOptions(opts)
|
||||||
if len(opts.Nth) != 2 {
|
if len(opts.Nth) != 2 {
|
||||||
t.Errorf("nth should not be empty: %s", opts.Nth)
|
t.Errorf("nth should not be empty: %v", opts.Nth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -34,6 +35,11 @@ type term struct {
|
|||||||
caseSensitive bool
|
caseSensitive bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of a term.
|
||||||
|
func (t term) String() string {
|
||||||
|
return fmt.Sprintf("term{typ: %d, inv: %v, text: []rune(%q), caseSensitive: %v}", t.typ, t.inv, string(t.text), t.caseSensitive)
|
||||||
|
}
|
||||||
|
|
||||||
type termSet []term
|
type termSet []term
|
||||||
|
|
||||||
// Pattern represents search pattern
|
// Pattern represents search pattern
|
||||||
@@ -46,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
|
||||||
@@ -95,18 +102,27 @@ 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
|
||||||
break Loop
|
if sortable {
|
||||||
|
// Can't break until we see at least one non-inverse term
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,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,
|
||||||
|
|||||||
@@ -31,12 +31,12 @@ func TestParseTermsExtended(t *testing.T) {
|
|||||||
terms[8][1].typ != termExact || terms[8][1].inv ||
|
terms[8][1].typ != termExact || terms[8][1].inv ||
|
||||||
terms[8][2].typ != termSuffix || terms[8][2].inv ||
|
terms[8][2].typ != termSuffix || terms[8][2].inv ||
|
||||||
terms[8][3].typ != termExact || !terms[8][3].inv {
|
terms[8][3].typ != termExact || !terms[8][3].inv {
|
||||||
t.Errorf("%s", terms)
|
t.Errorf("%v", terms)
|
||||||
}
|
}
|
||||||
for _, termSet := range terms[:8] {
|
for _, termSet := range terms[:8] {
|
||||||
term := termSet[0]
|
term := termSet[0]
|
||||||
if len(term.text) != 3 {
|
if len(term.text) != 3 {
|
||||||
t.Errorf("%s", term)
|
t.Errorf("%v", term)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,14 +53,14 @@ func TestParseTermsExtendedExact(t *testing.T) {
|
|||||||
terms[5][0].typ != termFuzzy || !terms[5][0].inv || len(terms[5][0].text) != 3 ||
|
terms[5][0].typ != termFuzzy || !terms[5][0].inv || len(terms[5][0].text) != 3 ||
|
||||||
terms[6][0].typ != termPrefix || !terms[6][0].inv || len(terms[6][0].text) != 3 ||
|
terms[6][0].typ != termPrefix || !terms[6][0].inv || len(terms[6][0].text) != 3 ||
|
||||||
terms[7][0].typ != termSuffix || !terms[7][0].inv || len(terms[7][0].text) != 3 {
|
terms[7][0].typ != termSuffix || !terms[7][0].inv || len(terms[7][0].text) != 3 {
|
||||||
t.Errorf("%s", terms)
|
t.Errorf("%v", terms)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseTermsEmpty(t *testing.T) {
|
func TestParseTermsEmpty(t *testing.T) {
|
||||||
terms := parseTerms(true, CaseSmart, false, "' ^ !' !^")
|
terms := parseTerms(true, CaseSmart, false, "' ^ !' !^")
|
||||||
if len(terms) != 0 {
|
if len(terms) != 0 {
|
||||||
t.Errorf("%s", terms)
|
t.Errorf("%v", terms)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ func TestExact(t *testing.T) {
|
|||||||
res, pos := algo.ExactMatchNaive(
|
res, pos := algo.ExactMatchNaive(
|
||||||
pattern.caseSensitive, pattern.normalize, pattern.forward, &chars, pattern.termSets[0][0].text, true, nil)
|
pattern.caseSensitive, pattern.normalize, pattern.forward, &chars, pattern.termSets[0][0].text, true, nil)
|
||||||
if res.Start != 7 || res.End != 10 {
|
if res.Start != 7 || res.End != 10 {
|
||||||
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
|
t.Errorf("%v / %d / %d", pattern.termSets, res.Start, res.End)
|
||||||
}
|
}
|
||||||
if pos != nil {
|
if pos != nil {
|
||||||
t.Errorf("pos is expected to be nil")
|
t.Errorf("pos is expected to be nil")
|
||||||
@@ -90,7 +90,7 @@ func TestEqual(t *testing.T) {
|
|||||||
res, pos := algo.EqualMatch(
|
res, pos := algo.EqualMatch(
|
||||||
pattern.caseSensitive, pattern.normalize, pattern.forward, &chars, pattern.termSets[0][0].text, true, nil)
|
pattern.caseSensitive, pattern.normalize, pattern.forward, &chars, pattern.termSets[0][0].text, true, nil)
|
||||||
if res.Start != sidxExpected || res.End != eidxExpected {
|
if res.Start != sidxExpected || res.End != eidxExpected {
|
||||||
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
|
t.Errorf("%v / %d / %d", pattern.termSets, res.Start, res.End)
|
||||||
}
|
}
|
||||||
if pos != nil {
|
if pos != nil {
|
||||||
t.Errorf("pos is expected to be nil")
|
t.Errorf("pos is expected to be nil")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
374
src/terminal.go
374
src/terminal.go
@@ -24,7 +24,7 @@ import (
|
|||||||
var placeholder *regexp.Regexp
|
var placeholder *regexp.Regexp
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
placeholder = regexp.MustCompile("\\\\?(?:{\\+?[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,7 +60,8 @@ type Terminal struct {
|
|||||||
inlineInfo bool
|
inlineInfo bool
|
||||||
prompt string
|
prompt string
|
||||||
promptLen int
|
promptLen int
|
||||||
reverse bool
|
queryLen [2]int
|
||||||
|
layout layoutType
|
||||||
fullscreen bool
|
fullscreen bool
|
||||||
hscroll bool
|
hscroll bool
|
||||||
hscrollOff int
|
hscrollOff int
|
||||||
@@ -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
|
||||||
@@ -221,6 +226,13 @@ const (
|
|||||||
actTop
|
actTop
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type placeholderFlags struct {
|
||||||
|
plus bool
|
||||||
|
preserveSpace bool
|
||||||
|
number bool
|
||||||
|
query bool
|
||||||
|
}
|
||||||
|
|
||||||
func toActions(types ...actionType) []action {
|
func toActions(types ...actionType) []action {
|
||||||
actions := make([]action, len(types))
|
actions := make([]action, len(types))
|
||||||
for idx, t := range types {
|
for idx, t := range types {
|
||||||
@@ -277,6 +289,9 @@ func defaultKeymap() map[int][]action {
|
|||||||
keymap[tui.PgUp] = toActions(actPageUp)
|
keymap[tui.PgUp] = toActions(actPageUp)
|
||||||
keymap[tui.PgDn] = toActions(actPageDown)
|
keymap[tui.PgDn] = toActions(actPageDown)
|
||||||
|
|
||||||
|
keymap[tui.SUp] = toActions(actPreviewUp)
|
||||||
|
keymap[tui.SDown] = toActions(actPreviewDown)
|
||||||
|
|
||||||
keymap[tui.Rune] = toActions(actRune)
|
keymap[tui.Rune] = toActions(actRune)
|
||||||
keymap[tui.Mouse] = toActions(actMouse)
|
keymap[tui.Mouse] = toActions(actMouse)
|
||||||
keymap[tui.DoubleClick] = toActions(actAccept)
|
keymap[tui.DoubleClick] = toActions(actAccept)
|
||||||
@@ -293,10 +308,11 @@ func trimQuery(query string) []rune {
|
|||||||
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||||
input := trimQuery(opts.Query)
|
input := trimQuery(opts.Query)
|
||||||
var header []string
|
var header []string
|
||||||
if opts.Reverse {
|
switch opts.Layout {
|
||||||
header = opts.Header
|
case layoutDefault, layoutReverseList:
|
||||||
} else {
|
|
||||||
header = reverseStringArray(opts.Header)
|
header = reverseStringArray(opts.Header)
|
||||||
|
default:
|
||||||
|
header = opts.Header
|
||||||
}
|
}
|
||||||
var delay time.Duration
|
var delay time.Duration
|
||||||
if opts.Tac {
|
if opts.Tac {
|
||||||
@@ -354,7 +370,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
t := Terminal{
|
t := Terminal{
|
||||||
initDelay: delay,
|
initDelay: delay,
|
||||||
inlineInfo: opts.InlineInfo,
|
inlineInfo: opts.InlineInfo,
|
||||||
reverse: opts.Reverse,
|
queryLen: [2]int{0, 0},
|
||||||
|
layout: opts.Layout,
|
||||||
fullscreen: fullscreen,
|
fullscreen: fullscreen,
|
||||||
hscroll: opts.Hscroll,
|
hscroll: opts.Hscroll,
|
||||||
hscrollOff: opts.HscrollOff,
|
hscrollOff: opts.HscrollOff,
|
||||||
@@ -363,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,
|
||||||
@@ -375,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,
|
||||||
@@ -392,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{},
|
||||||
@@ -400,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)
|
||||||
@@ -583,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
|
||||||
@@ -595,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 {
|
||||||
@@ -625,17 +645,29 @@ 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) {
|
||||||
if !t.reverse {
|
h := t.window.Height()
|
||||||
y = t.window.Height() - y - 1
|
|
||||||
|
switch t.layout {
|
||||||
|
case layoutDefault:
|
||||||
|
y = h - y - 1
|
||||||
|
case layoutReverseList:
|
||||||
|
n := 2 + len(t.header)
|
||||||
|
if t.inlineInfo {
|
||||||
|
n--
|
||||||
|
}
|
||||||
|
if y < n {
|
||||||
|
y = h - y - 1
|
||||||
|
} else {
|
||||||
|
y -= n
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if clear {
|
if clear {
|
||||||
@@ -645,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
|
||||||
}
|
}
|
||||||
@@ -739,7 +795,7 @@ func (t *Terminal) printList() {
|
|||||||
count := t.merger.Length() - t.offset
|
count := t.merger.Length() - t.offset
|
||||||
for j := 0; j < maxy; j++ {
|
for j := 0; j < maxy; j++ {
|
||||||
i := j
|
i := j
|
||||||
if !t.reverse {
|
if t.layout == layoutDefault {
|
||||||
i = maxy - 1 - j
|
i = maxy - 1 - j
|
||||||
}
|
}
|
||||||
line := i + 2 + len(t.header)
|
line := i + 2 + len(t.header)
|
||||||
@@ -782,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 {
|
||||||
@@ -971,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 {
|
||||||
@@ -997,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")
|
||||||
@@ -1011,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() {
|
||||||
@@ -1046,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 {
|
||||||
@@ -1127,16 +1187,49 @@ func quoteEntry(entry string) string {
|
|||||||
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
|
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasPlusFlag(template string) bool {
|
func parsePlaceholder(match string) (bool, string, placeholderFlags) {
|
||||||
for _, match := range placeholder.FindAllString(template, -1) {
|
flags := placeholderFlags{}
|
||||||
if match[0] == '\\' {
|
|
||||||
continue
|
if match[0] == '\\' {
|
||||||
}
|
// Escaped placeholder pattern
|
||||||
if match[1] == '+' {
|
return true, match[1:], flags
|
||||||
return true
|
}
|
||||||
|
|
||||||
|
skipChars := 1
|
||||||
|
for _, char := range match[1:] {
|
||||||
|
switch char {
|
||||||
|
case '+':
|
||||||
|
flags.plus = true
|
||||||
|
skipChars++
|
||||||
|
case 's':
|
||||||
|
flags.preserveSpace = true
|
||||||
|
skipChars++
|
||||||
|
case 'n':
|
||||||
|
flags.number = true
|
||||||
|
skipChars++
|
||||||
|
case 'q':
|
||||||
|
flags.query = true
|
||||||
|
default:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
|
matchWithoutFlags := "{" + match[skipChars:]
|
||||||
|
|
||||||
|
return false, matchWithoutFlags, flags
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasPreviewFlags(template string) (plus bool, query bool) {
|
||||||
|
for _, match := range placeholder.FindAllString(template, -1) {
|
||||||
|
_, _, flags := parsePlaceholder(match)
|
||||||
|
if flags.plus {
|
||||||
|
plus = true
|
||||||
|
}
|
||||||
|
if flags.query {
|
||||||
|
query = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, forcePlus bool, query string, allItems []*Item) string {
|
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, forcePlus bool, query string, allItems []*Item) string {
|
||||||
@@ -1149,9 +1242,10 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, fo
|
|||||||
selected = []*Item{}
|
selected = []*Item{}
|
||||||
}
|
}
|
||||||
return placeholder.ReplaceAllStringFunc(template, func(match string) string {
|
return placeholder.ReplaceAllStringFunc(template, func(match string) string {
|
||||||
// Escaped pattern
|
escaped, match, flags := parsePlaceholder(match)
|
||||||
if match[0] == '\\' {
|
|
||||||
return match[1:]
|
if escaped {
|
||||||
|
return match
|
||||||
}
|
}
|
||||||
|
|
||||||
// Current query
|
// Current query
|
||||||
@@ -1159,13 +1253,8 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, fo
|
|||||||
return quoteEntry(query)
|
return quoteEntry(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
plusFlag := forcePlus
|
|
||||||
if match[1] == '+' {
|
|
||||||
match = "{" + match[2:]
|
|
||||||
plusFlag = true
|
|
||||||
}
|
|
||||||
items := current
|
items := current
|
||||||
if plusFlag {
|
if flags.plus || forcePlus {
|
||||||
items = selected
|
items = selected
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1173,7 +1262,16 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, fo
|
|||||||
|
|
||||||
if match == "{}" {
|
if match == "{}" {
|
||||||
for idx, item := range items {
|
for idx, item := range items {
|
||||||
replacements[idx] = quoteEntry(item.AsString(stripAnsi))
|
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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return strings.Join(replacements, " ")
|
return strings.Join(replacements, " ")
|
||||||
}
|
}
|
||||||
@@ -1201,7 +1299,9 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, fo
|
|||||||
str = str[:delims[len(delims)-1][0]]
|
str = str[:delims[len(delims)-1][0]]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
str = strings.TrimSpace(str)
|
if !flags.preserveSpace {
|
||||||
|
str = strings.TrimSpace(str)
|
||||||
|
}
|
||||||
replacements[idx] = quoteEntry(str)
|
replacements[idx] = quoteEntry(str)
|
||||||
}
|
}
|
||||||
return strings.Join(replacements, " ")
|
return strings.Join(replacements, " ")
|
||||||
@@ -1220,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
|
||||||
@@ -1231,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1257,23 +1359,32 @@ func (t *Terminal) currentItem() *Item {
|
|||||||
|
|
||||||
func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item) {
|
func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item) {
|
||||||
current := t.currentItem()
|
current := t.currentItem()
|
||||||
if !forcePlus && !hasPlusFlag(template) || len(t.selected) == 0 {
|
plus, query := hasPreviewFlags(template)
|
||||||
|
if !(query && len(t.input) > 0 || (forcePlus || plus) && len(t.selected) > 0) {
|
||||||
return current != nil, []*Item{current, current}
|
return current != nil, []*Item{current, current}
|
||||||
}
|
}
|
||||||
sels := make([]*Item, len(t.selected)+1)
|
|
||||||
sels[0] = current
|
// We would still want to update preview window even if there is no match if
|
||||||
for i, sel := range t.sortSelected() {
|
// 1. command template contains {q} and the query string is not empty
|
||||||
sels[i+1] = sel.item
|
// 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.
|
||||||
|
if current == nil {
|
||||||
|
current = &minItem
|
||||||
|
}
|
||||||
|
|
||||||
|
var sels []*Item
|
||||||
|
if len(t.selected) == 0 {
|
||||||
|
sels = []*Item{current, current}
|
||||||
|
} else {
|
||||||
|
sels = make([]*Item, len(t.selected)+1)
|
||||||
|
sels[0] = current
|
||||||
|
for i, sel := range t.sortSelected() {
|
||||||
|
sels[i+1] = sel.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++
|
||||||
@@ -1292,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/"))
|
||||||
@@ -1326,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)
|
||||||
@@ -1369,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, "")
|
||||||
}
|
}
|
||||||
@@ -1395,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()
|
||||||
@@ -1416,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1462,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()
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -1475,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)
|
||||||
@@ -1491,9 +1652,15 @@ 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)
|
||||||
req(reqPreviewRefresh)
|
if t.previewer.offset != newOffset {
|
||||||
|
t.previewer.offset = newOffset
|
||||||
|
req(reqPreviewRefresh)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for key, ret := range t.expect {
|
for key, ret := range t.expect {
|
||||||
if keyMatch(key, event) {
|
if keyMatch(key, event) {
|
||||||
@@ -1531,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() {
|
||||||
@@ -1628,12 +1796,12 @@ func (t *Terminal) Loop() {
|
|||||||
req(reqList, reqInfo)
|
req(reqList, reqInfo)
|
||||||
}
|
}
|
||||||
case actToggleIn:
|
case actToggleIn:
|
||||||
if t.reverse {
|
if t.layout != layoutDefault {
|
||||||
return doAction(action{t: actToggleUp}, mapkey)
|
return doAction(action{t: actToggleUp}, mapkey)
|
||||||
}
|
}
|
||||||
return doAction(action{t: actToggleDown}, mapkey)
|
return doAction(action{t: actToggleDown}, mapkey)
|
||||||
case actToggleOut:
|
case actToggleOut:
|
||||||
if t.reverse {
|
if t.layout != layoutDefault {
|
||||||
return doAction(action{t: actToggleDown}, mapkey)
|
return doAction(action{t: actToggleDown}, mapkey)
|
||||||
}
|
}
|
||||||
return doAction(action{t: actToggleUp}, mapkey)
|
return doAction(action{t: actToggleUp}, mapkey)
|
||||||
@@ -1761,13 +1929,21 @@ func (t *Terminal) Loop() {
|
|||||||
mx -= t.window.Left()
|
mx -= t.window.Left()
|
||||||
my -= t.window.Top()
|
my -= t.window.Top()
|
||||||
mx = util.Constrain(mx-t.promptLen, 0, len(t.input))
|
mx = util.Constrain(mx-t.promptLen, 0, len(t.input))
|
||||||
if !t.reverse {
|
|
||||||
my = t.window.Height() - my - 1
|
|
||||||
}
|
|
||||||
min := 2 + len(t.header)
|
min := 2 + len(t.header)
|
||||||
if t.inlineInfo {
|
if t.inlineInfo {
|
||||||
min--
|
min--
|
||||||
}
|
}
|
||||||
|
h := t.window.Height()
|
||||||
|
switch t.layout {
|
||||||
|
case layoutDefault:
|
||||||
|
my = h - my - 1
|
||||||
|
case layoutReverseList:
|
||||||
|
if my < h-min {
|
||||||
|
my += min
|
||||||
|
} else {
|
||||||
|
my = h - my - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
if me.Double {
|
if me.Double {
|
||||||
// Double-click
|
// Double-click
|
||||||
if my >= min {
|
if my >= min {
|
||||||
@@ -1778,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 {
|
||||||
@@ -1827,6 +2003,20 @@ func (t *Terminal) Loop() {
|
|||||||
t.jumping = jumpDisabled
|
t.jumping = jumpDisabled
|
||||||
req(reqList)
|
req(reqList)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if changed {
|
||||||
|
if t.isPreviewEnabled() {
|
||||||
|
_, q := hasPreviewFlags(t.preview.command)
|
||||||
|
if q {
|
||||||
|
t.version++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if changed || t.cx != previousCx {
|
||||||
|
req(reqPrompt)
|
||||||
|
}
|
||||||
|
|
||||||
t.mutex.Unlock() // Must be unlocked before touching reqBox
|
t.mutex.Unlock() // Must be unlocked before touching reqBox
|
||||||
|
|
||||||
if changed {
|
if changed {
|
||||||
@@ -1854,7 +2044,7 @@ func (t *Terminal) constrain() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) vmove(o int, allowCycle bool) {
|
func (t *Terminal) vmove(o int, allowCycle bool) {
|
||||||
if t.reverse {
|
if t.layout != layoutDefault {
|
||||||
o *= -1
|
o *= -1
|
||||||
}
|
}
|
||||||
dest := t.cy + o
|
dest := t.cy + o
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ func TestReplacePlaceholder(t *testing.T) {
|
|||||||
newItem("foo'bar \x1b[31mbaz\x1b[m"),
|
newItem("foo'bar \x1b[31mbaz\x1b[m"),
|
||||||
newItem("FOO'BAR \x1b[31mBAZ\x1b[m")}
|
newItem("FOO'BAR \x1b[31mBAZ\x1b[m")}
|
||||||
|
|
||||||
|
delim := "'"
|
||||||
|
var regex *regexp.Regexp
|
||||||
|
|
||||||
var result string
|
var result string
|
||||||
check := func(expected string) {
|
check := func(expected string) {
|
||||||
if result != expected {
|
if result != expected {
|
||||||
@@ -72,6 +75,31 @@ func TestReplacePlaceholder(t *testing.T) {
|
|||||||
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, true, "query", items2)
|
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, true, "query", items2)
|
||||||
check("echo 'foo'\\''bar' 'FOO'\\''BAR'/'baz' 'BAZ'/'baz' 'BAZ'/'foo'\\''bar' 'FOO'\\''BAR'/'foo'\\''bar baz' 'FOO'\\''BAR BAZ'/{n.t}/{}/{1}/{q}/'' ''")
|
check("echo 'foo'\\''bar' 'FOO'\\''BAR'/'baz' 'BAZ'/'baz' 'BAZ'/'foo'\\''bar' 'FOO'\\''BAR'/'foo'\\''bar baz' 'FOO'\\''BAR BAZ'/{n.t}/{}/{1}/{q}/'' ''")
|
||||||
|
|
||||||
|
// Whitespace preserving flag with "'" delimiter
|
||||||
|
result = replacePlaceholder("echo {s1}", true, Delimiter{str: &delim}, false, "query", items1)
|
||||||
|
check("echo ' foo'")
|
||||||
|
|
||||||
|
result = replacePlaceholder("echo {s2}", true, Delimiter{str: &delim}, false, "query", items1)
|
||||||
|
check("echo 'bar baz'")
|
||||||
|
|
||||||
|
result = replacePlaceholder("echo {s}", true, Delimiter{str: &delim}, false, "query", items1)
|
||||||
|
check("echo ' foo'\\''bar baz'")
|
||||||
|
|
||||||
|
result = replacePlaceholder("echo {s..}", true, Delimiter{str: &delim}, false, "query", items1)
|
||||||
|
check("echo ' foo'\\''bar baz'")
|
||||||
|
|
||||||
|
// Whitespace preserving flag with regex delimiter
|
||||||
|
regex = regexp.MustCompile("\\w+")
|
||||||
|
|
||||||
|
result = replacePlaceholder("echo {s1}", true, Delimiter{regex: regex}, false, "query", items1)
|
||||||
|
check("echo ' '")
|
||||||
|
|
||||||
|
result = replacePlaceholder("echo {s2}", true, Delimiter{regex: regex}, false, "query", items1)
|
||||||
|
check("echo ''\\'''")
|
||||||
|
|
||||||
|
result = replacePlaceholder("echo {s3}", true, Delimiter{regex: regex}, false, "query", items1)
|
||||||
|
check("echo ' '")
|
||||||
|
|
||||||
// No match
|
// No match
|
||||||
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, false, "query", []*Item{nil, nil})
|
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, false, "query", []*Item{nil, nil})
|
||||||
check("echo /")
|
check("echo /")
|
||||||
@@ -81,12 +109,11 @@ func TestReplacePlaceholder(t *testing.T) {
|
|||||||
check("echo /' foo'\\''bar baz'")
|
check("echo /' foo'\\''bar baz'")
|
||||||
|
|
||||||
// String delimiter
|
// String delimiter
|
||||||
delim := "'"
|
|
||||||
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, false, "query", items1)
|
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz'/'foo'/'bar baz'")
|
check("echo ' foo'\\''bar baz'/'foo'/'bar baz'")
|
||||||
|
|
||||||
// Regex delimiter
|
// Regex delimiter
|
||||||
regex := regexp.MustCompile("[oa]+")
|
regex = regexp.MustCompile("[oa]+")
|
||||||
// foo'bar baz
|
// foo'bar baz
|
||||||
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, false, "query", items1)
|
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz'/'f'/'r b'/''\\''bar b'")
|
check("echo ' foo'\\''bar baz'/'f'/'r b'/''\\''bar b'")
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package fzf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -23,12 +24,22 @@ type Token struct {
|
|||||||
prefixLength int32
|
prefixLength int32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of a Token.
|
||||||
|
func (t Token) String() string {
|
||||||
|
return fmt.Sprintf("Token{text: %s, prefixLength: %d}", t.text, t.prefixLength)
|
||||||
|
}
|
||||||
|
|
||||||
// Delimiter for tokenizing the input
|
// Delimiter for tokenizing the input
|
||||||
type Delimiter struct {
|
type Delimiter struct {
|
||||||
regex *regexp.Regexp
|
regex *regexp.Regexp
|
||||||
str *string
|
str *string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of a Delimeter.
|
||||||
|
func (d Delimiter) String() string {
|
||||||
|
return fmt.Sprintf("Delimiter{regex: %v, str: &%q}", d.regex, *d.str)
|
||||||
|
}
|
||||||
|
|
||||||
func newRange(begin int, end int) Range {
|
func newRange(begin int, end int) Range {
|
||||||
if begin == 1 {
|
if begin == 1 {
|
||||||
begin = rangeEllipsis
|
begin = rangeEllipsis
|
||||||
|
|||||||
@@ -9,35 +9,35 @@ func TestParseRange(t *testing.T) {
|
|||||||
i := ".."
|
i := ".."
|
||||||
r, _ := ParseRange(&i)
|
r, _ := ParseRange(&i)
|
||||||
if r.begin != rangeEllipsis || r.end != rangeEllipsis {
|
if r.begin != rangeEllipsis || r.end != rangeEllipsis {
|
||||||
t.Errorf("%s", r)
|
t.Errorf("%v", r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
i := "3.."
|
i := "3.."
|
||||||
r, _ := ParseRange(&i)
|
r, _ := ParseRange(&i)
|
||||||
if r.begin != 3 || r.end != rangeEllipsis {
|
if r.begin != 3 || r.end != rangeEllipsis {
|
||||||
t.Errorf("%s", r)
|
t.Errorf("%v", r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
i := "3..5"
|
i := "3..5"
|
||||||
r, _ := ParseRange(&i)
|
r, _ := ParseRange(&i)
|
||||||
if r.begin != 3 || r.end != 5 {
|
if r.begin != 3 || r.end != 5 {
|
||||||
t.Errorf("%s", r)
|
t.Errorf("%v", r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
i := "-3..-5"
|
i := "-3..-5"
|
||||||
r, _ := ParseRange(&i)
|
r, _ := ParseRange(&i)
|
||||||
if r.begin != -3 || r.end != -5 {
|
if r.begin != -3 || r.end != -5 {
|
||||||
t.Errorf("%s", r)
|
t.Errorf("%v", r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
i := "3"
|
i := "3"
|
||||||
r, _ := ParseRange(&i)
|
r, _ := ParseRange(&i)
|
||||||
if r.begin != 3 || r.end != 3 {
|
if r.begin != 3 || r.end != 3 {
|
||||||
t.Errorf("%s", r)
|
t.Errorf("%v", r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
102
src/tui/light.go
102
src/tui/light.go
@@ -27,11 +27,17 @@ 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)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
tty := ttyname()
|
||||||
|
if len(tty) > 0 {
|
||||||
|
if in, err := os.OpenFile(tty, syscall.O_RDONLY, 0); err == nil {
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
}
|
||||||
fmt.Fprintln(os.Stderr, "Failed to open "+consoleDevice)
|
fmt.Fprintln(os.Stderr, "Failed to open "+consoleDevice)
|
||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
@@ -48,11 +54,13 @@ func (r *LightRenderer) stderrInternal(str string, allowNLCR bool) {
|
|||||||
runes := []rune{}
|
runes := []rune{}
|
||||||
for len(bytes) > 0 {
|
for len(bytes) > 0 {
|
||||||
r, sz := utf8.DecodeRune(bytes)
|
r, sz := utf8.DecodeRune(bytes)
|
||||||
if r == utf8.RuneError || r < 32 &&
|
nlcr := r == '\n' || r == '\r'
|
||||||
r != '\x1b' && (!allowNLCR || r != '\n' && r != '\r') {
|
if r >= 32 || r == '\x1b' || nlcr {
|
||||||
runes = append(runes, '?')
|
if r == utf8.RuneError || nlcr && !allowNLCR {
|
||||||
} else {
|
runes = append(runes, ' ')
|
||||||
runes = append(runes, r)
|
} else {
|
||||||
|
runes = append(runes, r)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
bytes = bytes[sz:]
|
bytes = bytes[sz:]
|
||||||
}
|
}
|
||||||
@@ -146,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 ""
|
||||||
}
|
}
|
||||||
@@ -289,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 {
|
||||||
@@ -298,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
|
||||||
@@ -360,7 +375,14 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
if r.buffer[1] >= 1 && r.buffer[1] <= 'z'-'a'+1 {
|
if r.buffer[1] >= 1 && r.buffer[1] <= 'z'-'a'+1 {
|
||||||
return Event{int(CtrlAltA + r.buffer[1] - 1), 0, nil}
|
return Event{int(CtrlAltA + r.buffer[1] - 1), 0, nil}
|
||||||
}
|
}
|
||||||
|
alt := false
|
||||||
|
if len(r.buffer) > 2 && r.buffer[1] == ESC {
|
||||||
|
r.buffer = r.buffer[1:]
|
||||||
|
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:
|
||||||
@@ -380,12 +402,25 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
*sz = 3
|
*sz = 3
|
||||||
switch r.buffer[2] {
|
switch r.buffer[2] {
|
||||||
case 68:
|
case 68:
|
||||||
|
if alt {
|
||||||
|
return Event{AltLeft, 0, nil}
|
||||||
|
}
|
||||||
return Event{Left, 0, nil}
|
return Event{Left, 0, nil}
|
||||||
case 67:
|
case 67:
|
||||||
|
if alt {
|
||||||
|
// Ugh..
|
||||||
|
return Event{AltRight, 0, nil}
|
||||||
|
}
|
||||||
return Event{Right, 0, nil}
|
return Event{Right, 0, nil}
|
||||||
case 66:
|
case 66:
|
||||||
|
if alt {
|
||||||
|
return Event{AltDown, 0, nil}
|
||||||
|
}
|
||||||
return Event{Down, 0, nil}
|
return Event{Down, 0, nil}
|
||||||
case 65:
|
case 65:
|
||||||
|
if alt {
|
||||||
|
return Event{AltUp, 0, nil}
|
||||||
|
}
|
||||||
return Event{Up, 0, nil}
|
return Event{Up, 0, nil}
|
||||||
case 90:
|
case 90:
|
||||||
return Event{BTab, 0, nil}
|
return Event{BTab, 0, nil}
|
||||||
@@ -458,25 +493,22 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
case 59:
|
case ';':
|
||||||
if len(r.buffer) != 6 {
|
if len(r.buffer) != 6 {
|
||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
}
|
}
|
||||||
*sz = 6
|
*sz = 6
|
||||||
switch r.buffer[4] {
|
switch r.buffer[4] {
|
||||||
case 50:
|
case '2', '5':
|
||||||
switch r.buffer[5] {
|
switch r.buffer[5] {
|
||||||
case 68:
|
case 'A':
|
||||||
return Event{Home, 0, nil}
|
return Event{SUp, 0, nil}
|
||||||
case 67:
|
case 'B':
|
||||||
return Event{End, 0, nil}
|
return Event{SDown, 0, nil}
|
||||||
}
|
case 'C':
|
||||||
case 53:
|
|
||||||
switch r.buffer[5] {
|
|
||||||
case 68:
|
|
||||||
return Event{SLeft, 0, nil}
|
|
||||||
case 67:
|
|
||||||
return Event{SRight, 0, nil}
|
return Event{SRight, 0, nil}
|
||||||
|
case 'D':
|
||||||
|
return Event{SLeft, 0, nil}
|
||||||
}
|
}
|
||||||
} // r.buffer[4]
|
} // r.buffer[4]
|
||||||
} // r.buffer[3]
|
} // r.buffer[3]
|
||||||
@@ -644,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:
|
||||||
@@ -654,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) {
|
||||||
@@ -730,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -792,7 +826,7 @@ func (w *LightWindow) Print(text string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func cleanse(str string) string {
|
func cleanse(str string) string {
|
||||||
return strings.Replace(str, "\x1b", "?", -1)
|
return strings.Replace(str, "\x1b", "", -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) CPrint(pair ColorPair, attr Attr, text string) {
|
func (w *LightWindow) CPrint(pair ColorPair, attr Attr, text string) {
|
||||||
@@ -826,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
|
||||||
|
|||||||
@@ -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() {
|
||||||
@@ -295,12 +291,24 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
return Event{BSpace, 0, nil}
|
return Event{BSpace, 0, nil}
|
||||||
|
|
||||||
case tcell.KeyUp:
|
case tcell.KeyUp:
|
||||||
|
if alt {
|
||||||
|
return Event{AltUp, 0, nil}
|
||||||
|
}
|
||||||
return Event{Up, 0, nil}
|
return Event{Up, 0, nil}
|
||||||
case tcell.KeyDown:
|
case tcell.KeyDown:
|
||||||
|
if alt {
|
||||||
|
return Event{AltDown, 0, nil}
|
||||||
|
}
|
||||||
return Event{Down, 0, nil}
|
return Event{Down, 0, nil}
|
||||||
case tcell.KeyLeft:
|
case tcell.KeyLeft:
|
||||||
|
if alt {
|
||||||
|
return Event{AltLeft, 0, nil}
|
||||||
|
}
|
||||||
return Event{Left, 0, nil}
|
return Event{Left, 0, nil}
|
||||||
case tcell.KeyRight:
|
case tcell.KeyRight:
|
||||||
|
if alt {
|
||||||
|
return Event{AltRight, 0, nil}
|
||||||
|
}
|
||||||
return Event{Right, 0, nil}
|
return Event{Right, 0, nil}
|
||||||
|
|
||||||
case tcell.KeyHome:
|
case tcell.KeyHome:
|
||||||
@@ -370,12 +378,16 @@ 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) {
|
||||||
_screen.Fini()
|
if clear {
|
||||||
|
_screen.Fini()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Resume(bool) {
|
func (r *FullscreenRenderer) Resume(clear bool) {
|
||||||
r.initScreen()
|
if clear {
|
||||||
|
r.initScreen()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Close() {
|
func (r *FullscreenRenderer) Close() {
|
||||||
@@ -554,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
|
||||||
@@ -568,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
31
src/tui/ttyname_unix.go
Normal file
31
src/tui/ttyname_unix.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package tui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
var devPrefixes = [...]string{"/dev/pts/", "/dev/"}
|
||||||
|
|
||||||
|
func ttyname() string {
|
||||||
|
var stderr syscall.Stat_t
|
||||||
|
if syscall.Fstat(2, &stderr) != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, prefix := range devPrefixes {
|
||||||
|
files, err := ioutil.ReadDir(prefix)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
if stat, ok := file.Sys().(*syscall.Stat_t); ok && stat.Rdev == stderr.Rdev {
|
||||||
|
return prefix + file.Name()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
7
src/tui/ttyname_windows.go
Normal file
7
src/tui/ttyname_windows.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package tui
|
||||||
|
|
||||||
|
func ttyname() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
@@ -61,6 +61,8 @@ const (
|
|||||||
Home
|
Home
|
||||||
End
|
End
|
||||||
|
|
||||||
|
SUp
|
||||||
|
SDown
|
||||||
SLeft
|
SLeft
|
||||||
SRight
|
SRight
|
||||||
|
|
||||||
@@ -83,6 +85,11 @@ const (
|
|||||||
AltSlash
|
AltSlash
|
||||||
AltBS
|
AltBS
|
||||||
|
|
||||||
|
AltUp
|
||||||
|
AltDown
|
||||||
|
AltLeft
|
||||||
|
AltRight
|
||||||
|
|
||||||
Alt0
|
Alt0
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -165,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
|
||||||
@@ -193,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)
|
||||||
@@ -265,17 +306,19 @@ var (
|
|||||||
Dark256 *ColorTheme
|
Dark256 *ColorTheme
|
||||||
Light256 *ColorTheme
|
Light256 *ColorTheme
|
||||||
|
|
||||||
ColNormal ColorPair
|
ColPrompt ColorPair
|
||||||
ColPrompt ColorPair
|
ColNormal ColorPair
|
||||||
ColMatch ColorPair
|
ColMatch ColorPair
|
||||||
ColCurrent ColorPair
|
ColCursor ColorPair
|
||||||
ColCurrentMatch ColorPair
|
ColSelected ColorPair
|
||||||
ColSpinner ColorPair
|
ColCurrent ColorPair
|
||||||
ColInfo ColorPair
|
ColCurrentMatch ColorPair
|
||||||
ColCursor ColorPair
|
ColCurrentCursor ColorPair
|
||||||
ColSelected ColorPair
|
ColCurrentSelected ColorPair
|
||||||
ColHeader ColorPair
|
ColSpinner ColorPair
|
||||||
ColBorder ColorPair
|
ColInfo ColorPair
|
||||||
|
ColHeader ColorPair
|
||||||
|
ColBorder ColorPair
|
||||||
)
|
)
|
||||||
|
|
||||||
func EmptyTheme() *ColorTheme {
|
func EmptyTheme() *ColorTheme {
|
||||||
@@ -283,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,
|
||||||
@@ -305,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,
|
||||||
@@ -319,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,
|
||||||
@@ -333,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,
|
||||||
@@ -364,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)
|
||||||
@@ -385,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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
@@ -94,6 +95,11 @@ func (chars *Chars) Length() int {
|
|||||||
return len(chars.slice)
|
return len(chars.slice)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of a Chars object.
|
||||||
|
func (chars *Chars) String() string {
|
||||||
|
return fmt.Sprintf("Chars{slice: []byte(%q), inBytes: %v, trimLengthKnown: %v, trimLength: %d, Index: %d}", chars.slice, chars.inBytes, chars.trimLengthKnown, chars.trimLength, chars.Index)
|
||||||
|
}
|
||||||
|
|
||||||
// TrimLength returns the length after trimming leading and trailing whitespaces
|
// TrimLength returns the length after trimming leading and trailing whitespaces
|
||||||
func (chars *Chars) TrimLength() uint16 {
|
func (chars *Chars) TrimLength() uint16 {
|
||||||
if chars.trimLengthKnown {
|
if chars.trimLengthKnown {
|
||||||
@@ -165,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...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,11 +17,12 @@ func RuneWidth(r rune, prefixWidth int, tabstop int) int {
|
|||||||
return tabstop - prefixWidth%tabstop
|
return tabstop - prefixWidth%tabstop
|
||||||
} else if w, found := _runeWidths[r]; found {
|
} else if w, found := _runeWidths[r]; found {
|
||||||
return w
|
return w
|
||||||
} else {
|
} else if r == '\n' || r == '\r' {
|
||||||
w := Max(runewidth.RuneWidth(r), 1)
|
return 1
|
||||||
_runeWidths[r] = w
|
|
||||||
return w
|
|
||||||
}
|
}
|
||||||
|
w := runewidth.RuneWidth(r)
|
||||||
|
_runeWidths[r] = w
|
||||||
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
// Max returns the largest integer
|
// Max returns the largest integer
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,28 +10,35 @@ 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,
|
||||||
CmdLine: fmt.Sprintf(` /s /c "%s"`, command),
|
CmdLine: fmt.Sprintf(` /v:on/s/c "%s"`, command),
|
||||||
CreationFlags: 0,
|
CreationFlags: 0,
|
||||||
}
|
}
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
125
test/test_go.rb
Normal file → Executable file
125
test/test_go.rb
Normal file → Executable file
@@ -1060,6 +1060,21 @@ class TestGoFZF < TestBase
|
|||||||
assert_equal '50', readonce.chomp
|
assert_equal '50', readonce.chomp
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_header_lines_reverse_list
|
||||||
|
tmux.send_keys "seq 100 | #{fzf '--header-lines=10 -q 5 --layout=reverse-list'}", :Enter
|
||||||
|
2.times do
|
||||||
|
tmux.until do |lines|
|
||||||
|
lines[0] == '> 50' &&
|
||||||
|
lines[-4] == ' 2' &&
|
||||||
|
lines[-3] == ' 1' &&
|
||||||
|
lines[-2].include?('/90')
|
||||||
|
end
|
||||||
|
tmux.send_keys :Up
|
||||||
|
end
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
assert_equal '50', readonce.chomp
|
||||||
|
end
|
||||||
|
|
||||||
def test_header_lines_overflow
|
def test_header_lines_overflow
|
||||||
tmux.send_keys "seq 100 | #{fzf '--header-lines=200'}", :Enter
|
tmux.send_keys "seq 100 | #{fzf '--header-lines=200'}", :Enter
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
@@ -1087,7 +1102,8 @@ class TestGoFZF < TestBase
|
|||||||
header = File.readlines(FILE).take(5).map(&:strip)
|
header = File.readlines(FILE).take(5).map(&:strip)
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
lines[-2].include?('100/100') &&
|
lines[-2].include?('100/100') &&
|
||||||
lines[-7..-3].map(&:strip) == header
|
lines[-7..-3].map(&:strip) == header &&
|
||||||
|
lines[-8] == '> 1'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1096,7 +1112,18 @@ class TestGoFZF < TestBase
|
|||||||
header = File.readlines(FILE).take(5).map(&:strip)
|
header = File.readlines(FILE).take(5).map(&:strip)
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
lines[1].include?('100/100') &&
|
lines[1].include?('100/100') &&
|
||||||
lines[2..6].map(&:strip) == header
|
lines[2..6].map(&:strip) == header &&
|
||||||
|
lines[7] == '> 1'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_header_reverse_list
|
||||||
|
tmux.send_keys "seq 100 | #{fzf "--header=\\\"\\$(head -5 #{FILE})\\\" --layout=reverse-list"}", :Enter
|
||||||
|
header = File.readlines(FILE).take(5).map(&:strip)
|
||||||
|
tmux.until do |lines|
|
||||||
|
lines[-2].include?('100/100') &&
|
||||||
|
lines[-7..-3].map(&:strip) == header &&
|
||||||
|
lines[0] == '> 1'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1120,6 +1147,16 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_header_and_header_lines_reverse_list
|
||||||
|
tmux.send_keys "seq 100 | #{fzf "--layout=reverse-list --header-lines 10 --header \\\"\\$(head -5 #{FILE})\\\""}", :Enter
|
||||||
|
header = File.readlines(FILE).take(5).map(&:strip)
|
||||||
|
tmux.until do |lines|
|
||||||
|
lines[-2].include?('90/90') &&
|
||||||
|
lines[-7...-2].map(&:strip) == header &&
|
||||||
|
lines[-17...-7].map(&:strip) == (1..10).map(&:to_s).reverse
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_cancel
|
def test_cancel
|
||||||
tmux.send_keys "seq 10 | #{fzf '--bind 2:cancel'}", :Enter
|
tmux.send_keys "seq 10 | #{fzf '--bind 2:cancel'}", :Enter
|
||||||
tmux.until { |lines| lines[-2].include?('10/10') }
|
tmux.until { |lines| lines[-2].include?('10/10') }
|
||||||
@@ -1145,6 +1182,12 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_margin_reverse_list
|
||||||
|
tmux.send_keys "yes | head -1000 | #{fzf '--margin 5,3 --layout=reverse-list'}", :Enter
|
||||||
|
tmux.until { |lines| lines[4] == '' && lines[5] == ' > y' }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
end
|
||||||
|
|
||||||
def test_tabstop
|
def test_tabstop
|
||||||
writelines tempname, ["f\too\tba\tr\tbaz\tbarfooq\tux"]
|
writelines tempname, ["f\too\tba\tr\tbaz\tbarfooq\tux"]
|
||||||
{
|
{
|
||||||
@@ -1328,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]+}/ }
|
||||||
@@ -1355,6 +1398,41 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |_| %w[1 2 3] == File.readlines(tempname).map(&:chomp) }
|
tmux.until { |_| %w[1 2 3] == File.readlines(tempname).map(&:chomp) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_preview_flags
|
||||||
|
tmux.send_keys %(seq 10 | sed 's/^/:: /; s/$/ /' |
|
||||||
|
#{FZF} --multi --preview 'echo {{2}/{s2}/{+2}/{+s2}/{q}/{n}/{+n}}'), :Enter
|
||||||
|
tmux.until { |lines| lines[1].include?('{1/1 /1/1 //0/0}') }
|
||||||
|
tmux.send_keys '123'
|
||||||
|
tmux.until { |lines| lines[1].include?('{////123//}') }
|
||||||
|
tmux.send_keys 'C-u', '1'
|
||||||
|
tmux.until { |lines| lines.match_count == 2 }
|
||||||
|
tmux.until { |lines| lines[1].include?('{1/1 /1/1 /1/0/0}') }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| lines[1].include?('{10/10 /1/1 /1/9/0}') }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| lines[1].include?('{10/10 /1 10/1 10 /1/9/0 9}') }
|
||||||
|
tmux.send_keys '2'
|
||||||
|
tmux.until { |lines| lines[1].include?('{//1 10/1 10 /12//0 9}') }
|
||||||
|
tmux.send_keys '3'
|
||||||
|
tmux.until { |lines| lines[1].include?('{//1 10/1 10 /123//0 9}') }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_q_no_match
|
||||||
|
tmux.send_keys %(: | #{FZF} --preview 'echo foo {q}'), :Enter
|
||||||
|
tmux.until { |lines| lines.match_count == 0 }
|
||||||
|
tmux.until { |lines| !lines[1].include?('foo') }
|
||||||
|
tmux.send_keys 'bar'
|
||||||
|
tmux.until { |lines| lines[1].include?('foo bar') }
|
||||||
|
tmux.send_keys 'C-u'
|
||||||
|
tmux.until { |lines| !lines[1].include?('foo') }
|
||||||
|
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'
|
||||||
@@ -1441,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
|
||||||
@@ -1514,7 +1611,7 @@ module TestShell
|
|||||||
lines = retries do
|
lines = retries do
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys :Escape, :c
|
tmux.send_keys :Escape, :c
|
||||||
tmux.until { |lines| lines.item_count.positive? }
|
tmux.until { |lines| lines.match_count.positive? }
|
||||||
end
|
end
|
||||||
expected = lines.reverse.select { |l| l.start_with? '>' }.first[2..-1]
|
expected = lines.reverse.select { |l| l.start_with? '>' }.first[2..-1]
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
@@ -1551,7 +1648,7 @@ module TestShell
|
|||||||
retries do
|
retries do
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'C-r'
|
tmux.send_keys 'C-r'
|
||||||
tmux.until { |lines| lines.item_count.positive? }
|
tmux.until { |lines| lines.match_count.positive? }
|
||||||
end
|
end
|
||||||
tmux.send_keys 'C-r'
|
tmux.send_keys 'C-r'
|
||||||
tmux.send_keys '3d'
|
tmux.send_keys '3d'
|
||||||
@@ -1583,7 +1680,7 @@ module CompletionTest
|
|||||||
end
|
end
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'cat /tmp/fzf-test/10**', :Tab
|
tmux.send_keys 'cat /tmp/fzf-test/10**', :Tab
|
||||||
tmux.until { |lines| lines.item_count.positive? }
|
tmux.until { |lines| lines.match_count.positive? }
|
||||||
tmux.send_keys ' !d'
|
tmux.send_keys ' !d'
|
||||||
tmux.until { |lines| lines.match_count == 2 }
|
tmux.until { |lines| lines.match_count == 2 }
|
||||||
tmux.send_keys :Tab, :Tab
|
tmux.send_keys :Tab, :Tab
|
||||||
@@ -1597,7 +1694,7 @@ module CompletionTest
|
|||||||
# ~USERNAME**<TAB>
|
# ~USERNAME**<TAB>
|
||||||
tmux.send_keys 'C-u'
|
tmux.send_keys 'C-u'
|
||||||
tmux.send_keys "cat ~#{ENV['USER']}**", :Tab
|
tmux.send_keys "cat ~#{ENV['USER']}**", :Tab
|
||||||
tmux.until { |lines| lines.item_count.positive? }
|
tmux.until { |lines| lines.match_count.positive? }
|
||||||
tmux.send_keys "'.fzf-home"
|
tmux.send_keys "'.fzf-home"
|
||||||
tmux.until { |lines| lines.select { |l| l.include? '.fzf-home' }.count > 1 }
|
tmux.until { |lines| lines.select { |l| l.include? '.fzf-home' }.count > 1 }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
@@ -1615,7 +1712,7 @@ module CompletionTest
|
|||||||
# /tmp/fzf\ test**<TAB>
|
# /tmp/fzf\ test**<TAB>
|
||||||
tmux.send_keys 'C-u'
|
tmux.send_keys 'C-u'
|
||||||
tmux.send_keys 'cat /tmp/fzf\ test/**', :Tab
|
tmux.send_keys 'cat /tmp/fzf\ test/**', :Tab
|
||||||
tmux.until { |lines| lines.item_count.positive? }
|
tmux.until { |lines| lines.match_count.positive? }
|
||||||
tmux.send_keys 'foobar$'
|
tmux.send_keys 'foobar$'
|
||||||
tmux.until { |lines| lines.match_count == 1 }
|
tmux.until { |lines| lines.match_count == 1 }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
@@ -1635,7 +1732,7 @@ module CompletionTest
|
|||||||
|
|
||||||
def test_file_completion_root
|
def test_file_completion_root
|
||||||
tmux.send_keys 'ls /**', :Tab
|
tmux.send_keys 'ls /**', :Tab
|
||||||
tmux.until { |lines| lines.item_count.positive? }
|
tmux.until { |lines| lines.match_count.positive? }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1646,7 +1743,7 @@ module CompletionTest
|
|||||||
FileUtils.touch '/tmp/fzf-test/d55/xxx'
|
FileUtils.touch '/tmp/fzf-test/d55/xxx'
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'cd /tmp/fzf-test/**', :Tab
|
tmux.send_keys 'cd /tmp/fzf-test/**', :Tab
|
||||||
tmux.until { |lines| lines.item_count.positive? }
|
tmux.until { |lines| lines.match_count.positive? }
|
||||||
tmux.send_keys :Tab, :Tab # Tab does not work here
|
tmux.send_keys :Tab, :Tab # Tab does not work here
|
||||||
tmux.send_keys 55
|
tmux.send_keys 55
|
||||||
tmux.until { |lines| lines.match_count == 1 }
|
tmux.until { |lines| lines.match_count == 1 }
|
||||||
@@ -1675,7 +1772,7 @@ module CompletionTest
|
|||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'C-L'
|
tmux.send_keys 'C-L'
|
||||||
tmux.send_keys 'kill ', :Tab
|
tmux.send_keys 'kill ', :Tab
|
||||||
tmux.until { |lines| lines.item_count.positive? }
|
tmux.until { |lines| lines.match_count.positive? }
|
||||||
tmux.send_keys 'sleep12345'
|
tmux.send_keys 'sleep12345'
|
||||||
tmux.until { |lines| lines.any_include? 'sleep 12345' }
|
tmux.until { |lines| lines.any_include? 'sleep 12345' }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
@@ -1694,7 +1791,7 @@ module CompletionTest
|
|||||||
tmux.send_keys '_fzf_compgen_path() { echo "\$1"; seq 10; }', :Enter
|
tmux.send_keys '_fzf_compgen_path() { echo "\$1"; seq 10; }', :Enter
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'ls /tmp/**', :Tab
|
tmux.send_keys 'ls /tmp/**', :Tab
|
||||||
tmux.until { |lines| lines.item_count == 11 }
|
tmux.until { |lines| lines.match_count == 11 }
|
||||||
tmux.send_keys :Tab, :Tab, :Tab
|
tmux.send_keys :Tab, :Tab, :Tab
|
||||||
tmux.until { |lines| lines.select_count == 3 }
|
tmux.until { |lines| lines.select_count == 3 }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
@@ -1767,7 +1864,7 @@ class TestBash < TestBase
|
|||||||
tmux.paste '_completion_loader() { complete -o default fake; }'
|
tmux.paste '_completion_loader() { complete -o default fake; }'
|
||||||
tmux.paste 'complete -F _fzf_path_completion -o default -o bashdefault fake'
|
tmux.paste 'complete -F _fzf_path_completion -o default -o bashdefault fake'
|
||||||
tmux.send_keys 'fake /tmp/foo**', :Tab
|
tmux.send_keys 'fake /tmp/foo**', :Tab
|
||||||
tmux.until { |lines| lines.item_count.positive? }
|
tmux.until { |lines| lines.match_count.positive? }
|
||||||
tmux.send_keys 'C-c'
|
tmux.send_keys 'C-c'
|
||||||
|
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
@@ -1776,7 +1873,7 @@ class TestBash < TestBase
|
|||||||
|
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'fake /tmp/foo**', :Tab
|
tmux.send_keys 'fake /tmp/foo**', :Tab
|
||||||
tmux.until { |lines| lines.item_count.positive? }
|
tmux.until { |lines| lines.match_count.positive? }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
72
uninstall
72
uninstall
@@ -1,12 +1,45 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
confirm() {
|
xdg=0
|
||||||
while [ 1 ]; do
|
prefix='~/.fzf'
|
||||||
read -p "$1" -n 1 -r
|
prefix_expand=~/.fzf
|
||||||
echo
|
fish_dir=${XDG_CONFIG_HOME:-$HOME/.config}/fish
|
||||||
if [[ "$REPLY" =~ ^[Yy] ]]; then
|
|
||||||
|
help() {
|
||||||
|
cat << EOF
|
||||||
|
usage: $0 [OPTIONS]
|
||||||
|
|
||||||
|
--help Show this message
|
||||||
|
--xdg Remove files generated under \$XDG_CONFIG_HOME/fzf
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
for opt in "$@"; do
|
||||||
|
case $opt in
|
||||||
|
--help)
|
||||||
|
help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--xdg)
|
||||||
|
xdg=1
|
||||||
|
prefix='"${XDG_CONFIG_HOME:-$HOME/.config}"/fzf/fzf'
|
||||||
|
prefix_expand=${XDG_CONFIG_HOME:-$HOME/.config}/fzf/fzf
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "unknown option: $opt"
|
||||||
|
help
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
ask() {
|
||||||
|
while true; do
|
||||||
|
read -p "$1 ([y]/n) " -r
|
||||||
|
REPLY=${REPLY:-"y"}
|
||||||
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
return 0
|
return 0
|
||||||
elif [[ "$REPLY" =~ ^[Nn] ]]; then
|
elif [[ $REPLY =~ ^[Nn]$ ]]; then
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
@@ -40,7 +73,7 @@ remove_line() {
|
|||||||
content=$(sed 's/^[0-9]*://' <<< "$line")
|
content=$(sed 's/^[0-9]*://' <<< "$line")
|
||||||
match=1
|
match=1
|
||||||
echo " - Line #$line_no: $content"
|
echo " - Line #$line_no: $content"
|
||||||
[ "$content" = "$1" ] || confirm " - Remove (y/n) ? "
|
[ "$content" = "$1" ] || ask " - Remove?"
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
awk -v n=$line_no 'NR == n {next} {print}' "$src" > "$src.bak" &&
|
awk -v n=$line_no 'NR == n {next} {print}' "$src" > "$src.bak" &&
|
||||||
mv "$src.bak" "$src" || break
|
mv "$src.bak" "$src" || break
|
||||||
@@ -55,25 +88,30 @@ remove_line() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for shell in bash zsh; do
|
for shell in bash zsh; do
|
||||||
remove ~/.fzf.${shell}
|
shell_config=${prefix_expand}.${shell}
|
||||||
|
remove "${shell_config}"
|
||||||
remove_line ~/.${shell}rc \
|
remove_line ~/.${shell}rc \
|
||||||
"[ -f ~/.fzf.${shell} ] && source ~/.fzf.${shell}" \
|
"[ -f ${prefix}.${shell} ] && source ${prefix}.${shell}" \
|
||||||
"source ~/.fzf.${shell}"
|
"source ${prefix}.${shell}"
|
||||||
done
|
done
|
||||||
|
|
||||||
bind_file=~/.config/fish/functions/fish_user_key_bindings.fish
|
bind_file="${fish_dir}/functions/fish_user_key_bindings.fish"
|
||||||
if [ -f "$bind_file" ]; then
|
if [ -f "$bind_file" ]; then
|
||||||
remove_line "$bind_file" "fzf_key_bindings"
|
remove_line "$bind_file" "fzf_key_bindings"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -d ~/.config/fish/functions ]; then
|
if [ -d "${fish_dir}/functions" ]; then
|
||||||
remove ~/.config/fish/functions/fzf.fish
|
remove "${fish_dir}/functions/fzf.fish"
|
||||||
remove ~/.config/fish/functions/fzf_key_bindings.fish
|
remove "${fish_dir}/functions/fzf_key_bindings.fish"
|
||||||
|
|
||||||
if [ "$(ls -A ~/.config/fish/functions)" ]; then
|
if [ "$(ls -A "${fish_dir}/functions")" ]; then
|
||||||
echo "Can't delete non-empty directory: \"~/.config/fish/functions\""
|
echo "Can't delete non-empty directory: \"${fish_dir}/functions\""
|
||||||
else
|
else
|
||||||
rmdir ~/.config/fish/functions
|
rmdir "${fish_dir}/functions"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
config_dir=$(dirname "$prefix_expand")
|
||||||
|
if [[ "$xdg" = 1 ]] && [[ "$config_dir" = */fzf ]] && [[ -d "$config_dir" ]]; then
|
||||||
|
rmdir "$config_dir"
|
||||||
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user