mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-14 14:23:47 -05:00
Compare commits
109 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a41fd5327 | ||
|
|
b471042037 | ||
|
|
2886f06977 | ||
|
|
d630484eeb | ||
|
|
e24299239e | ||
|
|
d2fa470165 | ||
|
|
168453da71 | ||
|
|
23a06d63ac | ||
|
|
751aa1944a | ||
|
|
05b5f3f845 | ||
|
|
16fc6862a8 | ||
|
|
7e1c0f39e7 | ||
|
|
deccf20a35 | ||
|
|
73c0a645e0 | ||
|
|
e975bd0c8d | ||
|
|
78da928727 | ||
|
|
11962dabba | ||
|
|
dceb5d09cd | ||
|
|
b4cccf23d4 | ||
|
|
b911af200c | ||
|
|
68683c444f | ||
|
|
a185593d65 | ||
|
|
525040238e | ||
|
|
33f89a08f3 | ||
|
|
11645e1fac | ||
|
|
6390140539 | ||
|
|
072066c49c | ||
|
|
a2e9366c84 | ||
|
|
391669a451 | ||
|
|
0c6c76e081 | ||
|
|
f1520bdde6 | ||
|
|
3089880f18 | ||
|
|
ab11b74be4 | ||
|
|
a5a97be017 | ||
|
|
80b5bc1b68 | ||
|
|
5c7dcaffe8 | ||
|
|
5095899245 | ||
|
|
4800e5d2ae | ||
|
|
3b1e37f718 | ||
|
|
6577388250 | ||
|
|
3b9dbd4146 | ||
|
|
a1260feeed | ||
|
|
7322504ad0 | ||
|
|
de569f0052 | ||
|
|
e7097a9d25 | ||
|
|
c1dbc800e5 | ||
|
|
951746297e | ||
|
|
984304568d | ||
|
|
723217bdea | ||
|
|
0fdb71f7e4 | ||
|
|
12ce76b56a | ||
|
|
0030d18448 | ||
|
|
0e3e6ac442 | ||
|
|
430e8193e0 | ||
|
|
03e8ed4d88 | ||
|
|
ef492f6178 | ||
|
|
8eea45ef50 | ||
|
|
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 |
30
.github/ISSUE_TEMPLATE.md
vendored
30
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,30 +1,22 @@
|
||||
<!-- ISSUES NOT FOLLOWING THIS TEMPLATE WILL BE CLOSED AND DELETED -->
|
||||
|
||||
<!-- Check all that apply [x] -->
|
||||
- Category
|
||||
- [ ] fzf binary
|
||||
- [ ] fzf-tmux script
|
||||
- [ ] Key bindings
|
||||
- [ ] Completion
|
||||
- [ ] Vim
|
||||
- [ ] Neovim
|
||||
- [ ] Etc.
|
||||
|
||||
- [ ] I have read through the manual page (`man fzf`)
|
||||
- [ ] I have the latest version of fzf
|
||||
- [ ] I have searched through the existing issues
|
||||
|
||||
## Info
|
||||
|
||||
- OS
|
||||
- [ ] Linux
|
||||
- [ ] Mac OS X
|
||||
- [ ] Windows
|
||||
- [ ] Windows Subsystem for Linux
|
||||
- [ ] Etc.
|
||||
- Shell
|
||||
- [ ] bash
|
||||
- [ ] zsh
|
||||
- [ ] fish
|
||||
|
||||
<!--
|
||||
### Before submitting
|
||||
|
||||
- Make sure that you have the latest version of fzf
|
||||
- If you use tmux, make sure $TERM starts with "screen"
|
||||
- For more Vim stuff, check out https://github.com/junegunn/fzf.vim
|
||||
|
||||
Describe your problem or suggestion from here ...
|
||||
-->
|
||||
|
||||
## Problem / Steps to reproduce
|
||||
|
||||
|
||||
41
.travis.yml
41
.travis.yml
@@ -1,20 +1,27 @@
|
||||
language: ruby
|
||||
dist: trusty
|
||||
sudo: required
|
||||
matrix:
|
||||
language: go
|
||||
dist: xenial
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- sourceline: "ppa:pi-rho/dev"
|
||||
- sourceline: "ppa:fish-shell/release-2"
|
||||
packages:
|
||||
- tmux
|
||||
- zsh
|
||||
- fish
|
||||
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- env: TAGS=
|
||||
rvm: 2.3.3
|
||||
# - env: TAGS=tcell
|
||||
# rvm: 2.3.3
|
||||
- stage: unittest
|
||||
go: "1.13.x"
|
||||
script: make && make test
|
||||
|
||||
install:
|
||||
- sudo add-apt-repository -y ppa:pi-rho/dev
|
||||
- sudo apt-add-repository -y ppa:fish-shell/release-2
|
||||
- sudo apt-get update
|
||||
- sudo apt-get install -y tmux zsh fish
|
||||
- stage: cli
|
||||
go: "1.13.x"
|
||||
rvm: "2.5"
|
||||
script: |
|
||||
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
|
||||
|
||||
- `go` executable in $PATH
|
||||
- Go 1.11 or above
|
||||
|
||||
### Using Makefile
|
||||
|
||||
Makefile will set up and use its own `$GOPATH` under the project root.
|
||||
|
||||
```sh
|
||||
# Build fzf binary for your platform in target
|
||||
make
|
||||
|
||||
74
CHANGELOG.md
74
CHANGELOG.md
@@ -1,6 +1,80 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
0.19.0
|
||||
------
|
||||
|
||||
- Added "reload" action for dynamically updating the input list without
|
||||
restarting fzf. See https://github.com/junegunn/fzf/issues/1750 to learn
|
||||
more about it.
|
||||
```sh
|
||||
# Using fzf as the selector interface for ripgrep
|
||||
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
||||
INITIAL_QUERY="foo"
|
||||
FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY'" \
|
||||
fzf --bind "change:reload:$RG_PREFIX {q} || true" \
|
||||
--ansi --phony --query "$INITIAL_QUERY"
|
||||
```
|
||||
- `--multi` now takes an optional integer argument which indicates the maximum
|
||||
number of items that can be selected
|
||||
```sh
|
||||
seq 100 | fzf --multi 3 --reverse --height 50%
|
||||
```
|
||||
- If a placeholder expression for `--preview` and `execute` action (and the
|
||||
new `reload` action) contains `f` flag, it is replaced to the
|
||||
path of a temporary file that holds the evaluated list. This is useful
|
||||
when you multi-select a large number of items and the length of the
|
||||
evaluated string may exceed [`ARG_MAX`][argmax].
|
||||
```sh
|
||||
# Press CTRL-A to select 100K items and see the sum of all the numbers
|
||||
seq 100000 | fzf --multi --bind ctrl-a:select-all \
|
||||
--preview "awk '{sum+=\$1} END {print sum}' {+f}"
|
||||
```
|
||||
- `deselect-all` no longer deselects unmatched items. It is now consistent
|
||||
with `select-all` and `toggle-all` in that it only affects matched items.
|
||||
- Due to the limitation of bash, fuzzy completion is enabled by default for
|
||||
a fixed set of commands. A helper function for easily setting up fuzzy
|
||||
completion for any command is now provided.
|
||||
```sh
|
||||
# usage: _fzf_setup_completion path|dir COMMANDS...
|
||||
_fzf_setup_completion path git kubectl
|
||||
```
|
||||
- Info line style can be changed by `--info=STYLE`
|
||||
- `--info=default`
|
||||
- `--info=inline` (same as old `--inline-info`)
|
||||
- `--info=hidden`
|
||||
- Preview window border can be disabled by adding `noborder` to
|
||||
`--preview-window`.
|
||||
- When you transform the input with `--with-nth`, the trailing white spaces
|
||||
are removed.
|
||||
- `ctrl-\`, `ctrl-]`, `ctrl-^`, and `ctrl-/` can now be used with `--bind`
|
||||
- See https://github.com/junegunn/fzf/milestone/15?closed=1 for more details
|
||||
|
||||
[argmax]: https://unix.stackexchange.com/questions/120642/what-defines-the-maximum-size-for-a-command-single-argument
|
||||
|
||||
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
|
||||
------
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
FROM archlinux/base:latest
|
||||
RUN pacman -Sy && pacman --noconfirm -S awk git tmux zsh fish ruby procps go make
|
||||
RUN gem install --no-ri --no-rdoc minitest
|
||||
RUN gem install --no-document minitest
|
||||
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
||||
RUN echo '. ~/.bashrc' >> ~/.bash_profile
|
||||
|
||||
|
||||
109
Makefile
109
Makefile
@@ -1,34 +1,28 @@
|
||||
ifndef GOOS
|
||||
GOOS := $(word 1, $(subst /, " ", $(word 4, $(shell go version))))
|
||||
endif
|
||||
GO ?= go
|
||||
GOOS ?= $(word 1, $(subst /, " ", $(word 4, $(shell go version))))
|
||||
|
||||
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
|
||||
ROOT_DIR := $(shell dirname $(MAKEFILE))
|
||||
GOPATH := $(ROOT_DIR)/gopath
|
||||
SRC_LINK := $(GOPATH)/src/github.com/junegunn/fzf/src
|
||||
VENDOR_LINK := $(GOPATH)/src/github.com/junegunn/fzf/vendor
|
||||
export GOPATH
|
||||
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
|
||||
ROOT_DIR := $(shell dirname $(MAKEFILE))
|
||||
SOURCES := $(wildcard *.go src/*.go src/*/*.go) $(MAKEFILE)
|
||||
|
||||
GLIDE_YAML := glide.yaml
|
||||
GLIDE_LOCK := glide.lock
|
||||
SOURCES := $(wildcard *.go src/*.go src/*/*.go) $(SRC_LINK) $(VENDOR_LINK) $(GLIDE_LOCK) $(MAKEFILE)
|
||||
REVISION := $(shell git log -n 1 --pretty=format:%h -- $(SOURCES))
|
||||
BUILD_FLAGS := -a -ldflags "-X main.revision=$(REVISION) -w -extldflags=$(LDFLAGS)" -tags "$(TAGS)"
|
||||
|
||||
REVISION := $(shell git log -n 1 --pretty=format:%h -- $(SOURCES))
|
||||
BUILD_FLAGS := -a -ldflags "-X main.revision=$(REVISION) -w -extldflags=$(LDFLAGS)" -tags "$(TAGS)"
|
||||
|
||||
BINARY32 := fzf-$(GOOS)_386
|
||||
BINARY64 := fzf-$(GOOS)_amd64
|
||||
BINARYARM5 := fzf-$(GOOS)_arm5
|
||||
BINARYARM6 := fzf-$(GOOS)_arm6
|
||||
BINARYARM7 := fzf-$(GOOS)_arm7
|
||||
BINARYARM8 := fzf-$(GOOS)_arm8
|
||||
VERSION := $(shell awk -F= '/version =/ {print $$2}' src/constants.go | tr -d "\" ")
|
||||
RELEASE32 := fzf-$(VERSION)-$(GOOS)_386
|
||||
RELEASE64 := fzf-$(VERSION)-$(GOOS)_amd64
|
||||
RELEASEARM5 := fzf-$(VERSION)-$(GOOS)_arm5
|
||||
RELEASEARM6 := fzf-$(VERSION)-$(GOOS)_arm6
|
||||
RELEASEARM7 := fzf-$(VERSION)-$(GOOS)_arm7
|
||||
RELEASEARM8 := fzf-$(VERSION)-$(GOOS)_arm8
|
||||
BINARY32 := fzf-$(GOOS)_386
|
||||
BINARY64 := fzf-$(GOOS)_amd64
|
||||
BINARYARM5 := fzf-$(GOOS)_arm5
|
||||
BINARYARM6 := fzf-$(GOOS)_arm6
|
||||
BINARYARM7 := fzf-$(GOOS)_arm7
|
||||
BINARYARM8 := fzf-$(GOOS)_arm8
|
||||
BINARYPPC64LE := fzf-$(GOOS)_ppc64le
|
||||
VERSION := $(shell awk -F= '/version =/ {print $$2}' src/constants.go | tr -d "\" ")
|
||||
RELEASE32 := fzf-$(VERSION)-$(GOOS)_386
|
||||
RELEASE64 := fzf-$(VERSION)-$(GOOS)_amd64
|
||||
RELEASEARM5 := fzf-$(VERSION)-$(GOOS)_arm5
|
||||
RELEASEARM6 := fzf-$(VERSION)-$(GOOS)_arm6
|
||||
RELEASEARM7 := fzf-$(VERSION)-$(GOOS)_arm7
|
||||
RELEASEARM8 := fzf-$(VERSION)-$(GOOS)_arm8
|
||||
RELEASEPPC64LE := fzf-$(VERSION)-$(GOOS)_ppc64le
|
||||
|
||||
# https://en.wikipedia.org/wiki/Uname
|
||||
UNAME_M := $(shell uname -m)
|
||||
@@ -46,6 +40,12 @@ else ifeq ($(UNAME_M),armv6l)
|
||||
BINARY := $(BINARYARM6)
|
||||
else ifeq ($(UNAME_M),armv7l)
|
||||
BINARY := $(BINARYARM7)
|
||||
else ifeq ($(UNAME_M),armv8l)
|
||||
BINARY := $(BINARYARM8)
|
||||
else ifeq ($(UNAME_M),aarch64)
|
||||
BINARY := $(BINARYARM8)
|
||||
else ifeq ($(UNAME_M),ppc64le)
|
||||
BINARY := $(BINARYPPC64LE)
|
||||
else
|
||||
$(error "Build on $(UNAME_M) is not supported, yet.")
|
||||
endif
|
||||
@@ -61,13 +61,14 @@ release: target/$(BINARY32) target/$(BINARY64)
|
||||
cd target && cp -f $(BINARY64) fzf.exe && zip $(RELEASE64).zip fzf.exe
|
||||
cd target && rm -f fzf.exe
|
||||
else ifeq ($(GOOS),linux)
|
||||
release: target/$(BINARY32) target/$(BINARY64) target/$(BINARYARM5) target/$(BINARYARM6) target/$(BINARYARM7) target/$(BINARYARM8)
|
||||
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 $(BINARY64) fzf && tar -czf $(RELEASE64).tgz fzf
|
||||
cd target && cp -f $(BINARYARM5) fzf && tar -czf $(RELEASEARM5).tgz fzf
|
||||
cd target && cp -f $(BINARYARM6) fzf && tar -czf $(RELEASEARM6).tgz fzf
|
||||
cd target && cp -f $(BINARYARM7) fzf && tar -czf $(RELEASEARM7).tgz fzf
|
||||
cd target && cp -f $(BINARYARM8) fzf && tar -czf $(RELEASEARM8).tgz fzf
|
||||
cd target && cp -f $(BINARYPPC64LE) fzf && tar -czf $(RELEASEPPC64LE).tgz fzf
|
||||
cd target && rm -f fzf
|
||||
else
|
||||
release: target/$(BINARY32) target/$(BINARY64)
|
||||
@@ -83,19 +84,8 @@ release-all: clean test
|
||||
GOOS=openbsd make release
|
||||
GOOS=windows make release
|
||||
|
||||
$(SRC_LINK):
|
||||
mkdir -p $(shell dirname $(SRC_LINK))
|
||||
ln -sf $(ROOT_DIR)/src $(SRC_LINK)
|
||||
|
||||
$(VENDOR_LINK):
|
||||
mkdir -p $(shell dirname $(VENDOR_LINK))
|
||||
ln -sf $(ROOT_DIR)/vendor $(VENDOR_LINK)
|
||||
|
||||
vendor: $(GLIDE_YAML)
|
||||
go get -u github.com/Masterminds/glide && $(GOPATH)/bin/glide install && touch $@
|
||||
|
||||
test: $(SOURCES) vendor
|
||||
SHELL=/bin/sh GOOS= go test -v -tags "$(TAGS)" \
|
||||
test: $(SOURCES)
|
||||
SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \
|
||||
github.com/junegunn/fzf/src \
|
||||
github.com/junegunn/fzf/src/algo \
|
||||
github.com/junegunn/fzf/src/tui \
|
||||
@@ -104,26 +94,29 @@ test: $(SOURCES) vendor
|
||||
install: bin/fzf
|
||||
|
||||
clean:
|
||||
rm -rf target
|
||||
$(RM) -r target
|
||||
|
||||
target/$(BINARY32): $(SOURCES) vendor
|
||||
GOARCH=386 go build $(BUILD_FLAGS) -o $@
|
||||
target/$(BINARY32): $(SOURCES)
|
||||
GOARCH=386 $(GO) build $(BUILD_FLAGS) -o $@
|
||||
|
||||
target/$(BINARY64): $(SOURCES) vendor
|
||||
GOARCH=amd64 go build $(BUILD_FLAGS) -o $@
|
||||
target/$(BINARY64): $(SOURCES)
|
||||
GOARCH=amd64 $(GO) build $(BUILD_FLAGS) -o $@
|
||||
|
||||
# https://github.com/golang/go/wiki/GoArm
|
||||
target/$(BINARYARM5): $(SOURCES) vendor
|
||||
GOARCH=arm GOARM=5 go build $(BUILD_FLAGS) -o $@
|
||||
target/$(BINARYARM5): $(SOURCES)
|
||||
GOARCH=arm GOARM=5 $(GO) build $(BUILD_FLAGS) -o $@
|
||||
|
||||
target/$(BINARYARM6): $(SOURCES) vendor
|
||||
GOARCH=arm GOARM=6 go build $(BUILD_FLAGS) -o $@
|
||||
target/$(BINARYARM6): $(SOURCES)
|
||||
GOARCH=arm GOARM=6 $(GO) build $(BUILD_FLAGS) -o $@
|
||||
|
||||
target/$(BINARYARM7): $(SOURCES) vendor
|
||||
GOARCH=arm GOARM=7 go build $(BUILD_FLAGS) -o $@
|
||||
target/$(BINARYARM7): $(SOURCES)
|
||||
GOARCH=arm GOARM=7 $(GO) build $(BUILD_FLAGS) -o $@
|
||||
|
||||
target/$(BINARYARM8): $(SOURCES) vendor
|
||||
GOARCH=arm64 go build $(BUILD_FLAGS) -o $@
|
||||
target/$(BINARYARM8): $(SOURCES)
|
||||
GOARCH=arm64 $(GO) build $(BUILD_FLAGS) -o $@
|
||||
|
||||
target/$(BINARYPPC64LE): $(SOURCES)
|
||||
GOARCH=ppc64le $(GO) build $(BUILD_FLAGS) -o $@
|
||||
|
||||
bin/fzf: target/$(BINARY) | bin
|
||||
cp -f target/$(BINARY) bin/fzf
|
||||
@@ -136,4 +129,8 @@ docker-test:
|
||||
docker build -t fzf-arch .
|
||||
docker run -it fzf-arch
|
||||
|
||||
.PHONY: all release release-all test install clean docker docker-test
|
||||
update:
|
||||
$(GO) get -u
|
||||
$(GO) mod tidy
|
||||
|
||||
.PHONY: all release release-all test install clean docker docker-test update
|
||||
|
||||
125
README-VIM.md
125
README-VIM.md
@@ -42,9 +42,6 @@ Note that the environment variables `FZF_DEFAULT_COMMAND` and
|
||||
- Customizes fzf colors to match the current color scheme
|
||||
- `g:fzf_history_dir`
|
||||
- Enables history feature
|
||||
- `g:fzf_launcher`
|
||||
- (Only in GVim) Terminal emulator to open fzf with
|
||||
- `g:Fzf_launcher` for function reference
|
||||
|
||||
#### Examples
|
||||
|
||||
@@ -75,7 +72,7 @@ let g:fzf_layout = { 'down': '~40%' }
|
||||
" You can set up fzf window using a Vim command (Neovim or latest Vim 8 required)
|
||||
let g:fzf_layout = { 'window': 'enew' }
|
||||
let g:fzf_layout = { 'window': '-tabnew' }
|
||||
let g:fzf_layout = { 'window': '10split enew' }
|
||||
let g:fzf_layout = { 'window': '10new' }
|
||||
|
||||
" Customize fzf colors to match your color scheme
|
||||
let g:fzf_colors =
|
||||
@@ -103,8 +100,67 @@ let g:fzf_history_dir = '~/.local/share/fzf-history'
|
||||
`fzf#run`
|
||||
---------
|
||||
|
||||
For more advanced uses, you can use `fzf#run([options])` function with the
|
||||
following options.
|
||||
For more advanced uses, you can use `fzf#run([options])` function.
|
||||
|
||||
`fzf#run()` function is the core of Vim integration. It takes a single
|
||||
dictionary argument. At the very least, specify `sink` option to tell what it
|
||||
should do with the selected entry.
|
||||
|
||||
```vim
|
||||
call fzf#run({'sink': 'e'})
|
||||
```
|
||||
|
||||
Without `source`, fzf will use find command (or `$FZF_DEFAULT_COMMAND` if
|
||||
defined) to list the files under the current directory. When you select one,
|
||||
it will open it with `:e` command. If you want to open it in a new tab, you
|
||||
can pass `:tabedit` command instead as the sink.
|
||||
|
||||
```vim
|
||||
call fzf#run({'sink': 'tabedit'})
|
||||
```
|
||||
|
||||
fzf allows you to select multiple entries with `--multi` (or `-m`) option, and
|
||||
you can change its bottom-up layout with `--reverse` option. Such options can
|
||||
be specified as `options`.
|
||||
|
||||
```vim
|
||||
call fzf#run({'sink': 'tabedit', 'options': '--multi --reverse'})
|
||||
```
|
||||
|
||||
Instead of using the default find command, you can use any shell command as
|
||||
the source. This will list the files managed by git.
|
||||
|
||||
```vim
|
||||
call fzf#run({'source': 'git ls-files', 'sink': 'e'})
|
||||
```
|
||||
|
||||
Pass a layout option if you don't want fzf window to take up the entire screen.
|
||||
|
||||
```vim
|
||||
" up / down / left / right / window are allowed
|
||||
call fzf#run({'source': 'git ls-files', 'sink': 'e', 'right': '40%'})
|
||||
call fzf#run({'source': 'git ls-files', 'sink': 'e', 'window': '30vnew'})
|
||||
```
|
||||
|
||||
`source` doesn't have to be an external shell command, you can pass a Vim
|
||||
array as the source. In the following example, we use the names of the open
|
||||
buffers as the source.
|
||||
|
||||
```vim
|
||||
call fzf#run({'source': map(filter(range(1, bufnr('$')), 'buflisted(v:val)'),
|
||||
\ 'bufname(v:val)'),
|
||||
\ 'sink': 'e', 'down': '30%'})
|
||||
```
|
||||
|
||||
Or the names of color schemes.
|
||||
|
||||
```vim
|
||||
call fzf#run({'source': map(split(globpath(&rtp, 'colors/*.vim')),
|
||||
\ 'fnamemodify(v:val, ":t:r")'),
|
||||
\ 'sink': 'colo', 'left': '25%'})
|
||||
```
|
||||
|
||||
The following table shows the available options.
|
||||
|
||||
| Option name | Type | Description |
|
||||
| -------------------------- | ------------- | ---------------------------------------------------------------- |
|
||||
@@ -132,14 +188,35 @@ call fzf#run({'options': ['--reverse', '--prompt', 'C:\Program Files\']})
|
||||
`fzf#wrap`
|
||||
----------
|
||||
|
||||
`fzf#wrap([name string,] [opts dict,] [fullscreen boolean])` is a helper
|
||||
function that decorates the options dictionary so that it understands
|
||||
`g:fzf_layout`, `g:fzf_action`, `g:fzf_colors`, and `g:fzf_history_dir` like
|
||||
`:FZF`.
|
||||
`:FZF` command provided by default knows how to handle `CTRL-T`, `CTRL-X`, and
|
||||
`CTRL-V` and opens the selected file in a new tab, in a horizontal split, or
|
||||
in a vertical split respectively. And these key bindings can be configured via
|
||||
`g:fzf_action`. This is implemented using `--expect` option of fzf and the
|
||||
smart sink function. It also understands `g:fzf_colors`, `g:fzf_layout` and
|
||||
`g:fzf_history_dir`. However, `fzf#run` doesn't know about any of these
|
||||
options.
|
||||
|
||||
By *"wrapping"* your options dictionary with `fzf#wrap` before passing it to
|
||||
`fzf#run`, you can make your command also support the options.
|
||||
|
||||
```vim
|
||||
command! -bang MyStuff
|
||||
\ call fzf#run(fzf#wrap('my-stuff', {'dir': '~/my-stuff'}, <bang>0))
|
||||
" Usage:
|
||||
" fzf#wrap([name string,] [opts dict,] [fullscreen boolean])
|
||||
|
||||
" This command now supports CTRL-T, CTRL-V, and CTRL-X key bindings
|
||||
" and opens fzf according to g:fzf_layout setting.
|
||||
command! Buffers call fzf#run(fzf#wrap(
|
||||
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}))
|
||||
|
||||
" This extends the above example to open fzf in fullscreen
|
||||
" when the command is run with ! suffix (Buffers!)
|
||||
command! -bang Buffers call fzf#run(fzf#wrap(
|
||||
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}, <bang>0))
|
||||
|
||||
" You can optionally pass the name of the command as the first argument to
|
||||
" fzf#wrap to make it work with g:fzf_history_dir
|
||||
command! -bang Buffers call fzf#run(fzf#wrap('buffers',
|
||||
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}, <bang>0))
|
||||
```
|
||||
|
||||
fzf inside terminal buffer
|
||||
@@ -164,30 +241,6 @@ autocmd FileType fzf set laststatus=0 noshowmode noruler
|
||||
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
|
||||
```
|
||||
|
||||
GVim
|
||||
----
|
||||
|
||||
With the latest version of GVim, fzf will start inside the builtin terminal
|
||||
emulator of Vim. Please note that this terminal feature of Vim is still young
|
||||
and unstable and you may run into some issues.
|
||||
|
||||
If you have an older version of GVim, you need an external terminal emulator
|
||||
to start fzf with. `xterm` command is used by default, but you can customize
|
||||
it with `g:fzf_launcher`.
|
||||
|
||||
```vim
|
||||
" This is the default. %s is replaced with fzf command
|
||||
let g:fzf_launcher = 'xterm -e bash -ic %s'
|
||||
|
||||
" Use urxvt instead
|
||||
let g:fzf_launcher = 'urxvt -geometry 120x30 -e sh -c %s'
|
||||
```
|
||||
|
||||
If you're running MacVim on OSX, I recommend you to use iTerm2 as the
|
||||
launcher. Refer to the [this wiki page][macvim-iterm2] to see how to set up.
|
||||
|
||||
[macvim-iterm2]: https://github.com/junegunn/fzf/wiki/On-MacVim-with-iTerm2
|
||||
|
||||
[License](LICENSE)
|
||||
------------------
|
||||
|
||||
|
||||
91
README.md
91
README.md
@@ -27,7 +27,10 @@ Table of Contents
|
||||
* [Using git](#using-git)
|
||||
* [As Vim plugin](#as-vim-plugin)
|
||||
* [Arch Linux](#arch-linux)
|
||||
* [Debian](#debian)
|
||||
* [Fedora](#fedora)
|
||||
* [openSUSE](#opensuse)
|
||||
* [FreeBSD](#freebsd)
|
||||
* [Windows](#windows)
|
||||
* [Upgrading fzf](#upgrading-fzf)
|
||||
* [Building fzf](#building-fzf)
|
||||
@@ -37,6 +40,7 @@ Table of Contents
|
||||
* [Search syntax](#search-syntax)
|
||||
* [Environment variables](#environment-variables)
|
||||
* [Options](#options)
|
||||
* [Demo](#demo)
|
||||
* [Examples](#examples)
|
||||
* [fzf-tmux script](#fzf-tmux-script)
|
||||
* [Key bindings for command line](#key-bindings-for-command-line)
|
||||
@@ -56,6 +60,7 @@ Table of Contents
|
||||
* [Respecting .gitignore](#respecting-gitignore)
|
||||
* [git ls-tree for fast traversal](#git-ls-tree-for-fast-traversal)
|
||||
* [Fish shell](#fish-shell)
|
||||
* [Related projects](#related-projects)
|
||||
* [<a href="LICENSE">License</a>](#license)
|
||||
|
||||
Installation
|
||||
@@ -87,6 +92,10 @@ brew install fzf
|
||||
$(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
|
||||
|
||||
Alternatively, you can "git clone" this repository to any directory and run
|
||||
@@ -126,10 +135,10 @@ But instead of separately installing fzf on your system (using Homebrew or
|
||||
vim-plug to do both.
|
||||
|
||||
```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' }
|
||||
" 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
|
||||
@@ -138,6 +147,17 @@ Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
|
||||
sudo pacman -S fzf
|
||||
```
|
||||
|
||||
### Debian
|
||||
|
||||
fzf is available in Debian Buster and above, and can be installed using the usual
|
||||
method:
|
||||
|
||||
```sh
|
||||
sudo apt-get install fzf
|
||||
```
|
||||
|
||||
Read the documentation (/usr/share/doc/fzf/README.Debian) on how to enable it.
|
||||
|
||||
### Fedora
|
||||
|
||||
fzf is available in Fedora 26 and above, and can be installed using the usual
|
||||
@@ -151,10 +171,24 @@ 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
|
||||
documentation (/usr/share/doc/fzf/README.Fedora) for more information.
|
||||
|
||||
### openSUSE
|
||||
|
||||
fzf is available in openSUSE Tumbleweed and can be installed via zypper:
|
||||
|
||||
```sh
|
||||
sudo zypper install fzf
|
||||
```
|
||||
|
||||
### FreeBSD
|
||||
|
||||
```sh
|
||||
pkg install fzf
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
Pre-built binaries for Windows can be downloaded [here][bin]. fzf is also
|
||||
available as a [Chocolatey package][choco].
|
||||
available as a [Chocolatey package][choco]:
|
||||
|
||||
[choco]: https://chocolatey.org/packages/fzf
|
||||
|
||||
@@ -162,6 +196,14 @@ available as a [Chocolatey package][choco].
|
||||
choco install fzf
|
||||
```
|
||||
|
||||
or a [Scoop package][scoop]:
|
||||
|
||||
[scoop]: https://github.com/ScoopInstaller/Main/blob/master/bucket/fzf.json
|
||||
|
||||
```sh
|
||||
scoop install fzf
|
||||
```
|
||||
|
||||
However, other components of the project may not work on Windows. Known issues
|
||||
and limitations can be found on [the wiki page][windows-wiki]. You might want
|
||||
to consider installing fzf on [Windows Subsystem for Linux][wsl] where
|
||||
@@ -278,6 +320,13 @@ or `py`.
|
||||
|
||||
See the man page (`man fzf`) for the full list of options.
|
||||
|
||||
#### Demo
|
||||
If you learn by watching videos, check out this screencast by [@samoshkin](https://github.com/samoshkin) to explore `fzf` features.
|
||||
|
||||
<a title="fzf - command-line fuzzy finder" href="https://www.youtube.com/watch?v=qgG5Jhi_Els">
|
||||
<img src="https://i.imgur.com/vtG8olE.png" width="640">
|
||||
</a>
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
@@ -311,16 +360,16 @@ fullscreen mode.
|
||||
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
|
||||
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_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`
|
||||
again which toggles sorting by relevance
|
||||
- Set `FZF_CTRL_R_OPTS` to pass additional options
|
||||
@@ -372,7 +421,7 @@ cd ~/github/fzf**<TAB>
|
||||
|
||||
#### 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.
|
||||
|
||||
```sh
|
||||
@@ -425,7 +474,7 @@ _fzf_compgen_dir() {
|
||||
|
||||
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
|
||||
commands as well like follows.
|
||||
commands as well as follows.
|
||||
|
||||
```sh
|
||||
complete -F _fzf_path_completion -o default -o bashdefault ag
|
||||
@@ -442,7 +491,7 @@ Advanced topics
|
||||
|
||||
### 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
|
||||
options that affect the performance.
|
||||
|
||||
@@ -453,7 +502,7 @@ options that affect the performance.
|
||||
- `--with-nth` makes fzf slower as fzf has to tokenize and reassemble each
|
||||
line.
|
||||
- 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
|
||||
ordering of the matches and is not recommended.
|
||||
|
||||
@@ -474,7 +523,7 @@ See *KEY BINDINGS* section of the man page for details.
|
||||
|
||||
### 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.
|
||||
|
||||
```bash
|
||||
@@ -482,7 +531,7 @@ the current line as the argument and shows the result in the split window.
|
||||
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.
|
||||
|
||||
```bash
|
||||
@@ -493,15 +542,17 @@ fzf --preview 'head -100 {}'
|
||||
Preview window supports ANSI colors, so you can use programs that
|
||||
syntax-highlights the content of a file.
|
||||
|
||||
- Bat: https://github.com/sharkdp/bat
|
||||
- Highlight: http://www.andre-simon.de/doku/highlight/en/highlight.php
|
||||
- CodeRay: http://coderay.rubychan.de/
|
||||
- Rouge: https://github.com/jneen/rouge
|
||||
|
||||
```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 ]] &&
|
||||
echo {} is a binary file ||
|
||||
(highlight -O ansi -l {} ||
|
||||
(bat --style=numbers --color=always {} ||
|
||||
highlight -O ansi -l {} ||
|
||||
coderay {} ||
|
||||
rougify {} ||
|
||||
cat {}) 2> /dev/null | head -500'
|
||||
@@ -514,7 +565,8 @@ You can customize the size and position of the preview window using
|
||||
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/
|
||||
|
||||
@@ -580,9 +632,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.
|
||||
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
|
||||
of the following commandline
|
||||
of the following command-line
|
||||
|
||||
```sh
|
||||
ls /var/
|
||||
@@ -598,6 +650,11 @@ valid directory. Example:
|
||||
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)
|
||||
------------------
|
||||
|
||||
|
||||
117
doc/fzf.txt
117
doc/fzf.txt
@@ -1,4 +1,4 @@
|
||||
fzf.txt fzf Last change: November 19 2017
|
||||
fzf.txt fzf Last change: June 8 2019
|
||||
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
|
||||
==============================================================================
|
||||
|
||||
@@ -10,7 +10,6 @@ FZF - TABLE OF CONTENTS *fzf* *fzf-to
|
||||
fzf#wrap
|
||||
fzf inside terminal buffer
|
||||
Hide statusline
|
||||
GVim
|
||||
License
|
||||
|
||||
FZF VIM INTEGRATION *fzf-vim-integration*
|
||||
@@ -56,8 +55,7 @@ Note that the environment variables `FZF_DEFAULT_COMMAND` and
|
||||
< Configuration >_____________________________________________________________~
|
||||
*fzf-configuration*
|
||||
|
||||
*g:fzf_action* *g:fzf_layout* *g:fzf_colors* *g:fzf_history_dir* *g:fzf_launcher*
|
||||
*g:Fzf_launcher*
|
||||
*g:fzf_action* *g:fzf_layout* *g:fzf_colors* *g:fzf_history_dir*
|
||||
|
||||
- `g:fzf_action`
|
||||
- Customizable extra key bindings for opening selected files in different
|
||||
@@ -68,9 +66,6 @@ Note that the environment variables `FZF_DEFAULT_COMMAND` and
|
||||
- Customizes fzf colors to match the current color scheme
|
||||
- `g:fzf_history_dir`
|
||||
- Enables history feature
|
||||
- `g:fzf_launcher`
|
||||
- (Only in GVim) Terminal emulator to open fzf with
|
||||
- `g:Fzf_launcher` for function reference
|
||||
|
||||
|
||||
Examples~
|
||||
@@ -102,7 +97,7 @@ Examples~
|
||||
" You can set up fzf window using a Vim command (Neovim or latest Vim 8 required)
|
||||
let g:fzf_layout = { 'window': 'enew' }
|
||||
let g:fzf_layout = { 'window': '-tabnew' }
|
||||
let g:fzf_layout = { 'window': '10split enew' }
|
||||
let g:fzf_layout = { 'window': '10new' }
|
||||
|
||||
" Customize fzf colors to match your color scheme
|
||||
let g:fzf_colors =
|
||||
@@ -130,8 +125,54 @@ Examples~
|
||||
FZF#RUN *fzf#run*
|
||||
==============================================================================
|
||||
|
||||
For more advanced uses, you can use `fzf#run([options])` function with the
|
||||
following options.
|
||||
For more advanced uses, you can use `fzf#run([options])` function.
|
||||
|
||||
`fzf#run()` function is the core of Vim integration. It takes a single
|
||||
dictionary argument. At the very least, specify `sink` option to tell what it
|
||||
should do with the selected entry.
|
||||
>
|
||||
call fzf#run({'sink': 'e'})
|
||||
<
|
||||
Without `source`, fzf will use find command (or `$FZF_DEFAULT_COMMAND` if
|
||||
defined) to list the files under the current directory. When you select one,
|
||||
it will open it with `:e` command. If you want to open it in a new tab, you
|
||||
can pass `:tabedit` command instead as the sink.
|
||||
>
|
||||
call fzf#run({'sink': 'tabedit'})
|
||||
<
|
||||
fzf allows you to select multiple entries with `--multi` (or `-m`) option, and
|
||||
you can change its bottom-up layout with `--reverse` option. Such options can
|
||||
be specified as `options`.
|
||||
>
|
||||
call fzf#run({'sink': 'tabedit', 'options': '--multi --reverse'})
|
||||
<
|
||||
Instead of using the default find command, you can use any shell command as
|
||||
the source. This will list the files managed by git.
|
||||
>
|
||||
call fzf#run({'source': 'git ls-files', 'sink': 'e'})
|
||||
<
|
||||
Pass a layout option if you don't want fzf window to take up the entire
|
||||
screen.
|
||||
>
|
||||
" up / down / left / right / window are allowed
|
||||
call fzf#run({'source': 'git ls-files', 'sink': 'e', 'right': '40%'})
|
||||
call fzf#run({'source': 'git ls-files', 'sink': 'e', 'window': '30vnew'})
|
||||
<
|
||||
`source` doesn't have to be an external shell command, you can pass a Vim
|
||||
array as the source. In the following example, we use the names of the open
|
||||
buffers as the source.
|
||||
>
|
||||
call fzf#run({'source': map(filter(range(1, bufnr('$')), 'buflisted(v:val)'),
|
||||
\ 'bufname(v:val)'),
|
||||
\ 'sink': 'e', 'down': '30%'})
|
||||
<
|
||||
Or the names of color schemes.
|
||||
>
|
||||
call fzf#run({'source': map(split(globpath(&rtp, 'colors/*.vim')),
|
||||
\ 'fnamemodify(v:val, ":t:r")'),
|
||||
\ 'sink': 'colo', 'left': '25%'})
|
||||
<
|
||||
The following table shows the available options.
|
||||
|
||||
---------------------------+---------------+--------------------------------------------------------------
|
||||
Option name | Type | Description ~
|
||||
@@ -160,13 +201,34 @@ issues on different platforms.
|
||||
FZF#WRAP *fzf#wrap*
|
||||
==============================================================================
|
||||
|
||||
`fzf#wrap([name string,] [opts dict,] [fullscreen boolean])` is a helper
|
||||
function that decorates the options dictionary so that it understands
|
||||
`g:fzf_layout`, `g:fzf_action`, `g:fzf_colors`, and `g:fzf_history_dir` like
|
||||
`:FZF`.
|
||||
`:FZF` command provided by default knows how to handle CTRL-T, CTRL-X, and
|
||||
CTRL-V and opens the selected file in a new tab, in a horizontal split, or in
|
||||
a vertical split respectively. And these key bindings can be configured via
|
||||
`g:fzf_action`. This is implemented using `--expect` option of fzf and the
|
||||
smart sink function. It also understands `g:fzf_colors`, `g:fzf_layout` and
|
||||
`g:fzf_history_dir`. However, `fzf#run` doesn't know about any of these
|
||||
options.
|
||||
|
||||
By "wrapping" your options dictionary with `fzf#wrap` before passing it to
|
||||
`fzf#run`, you can make your command also support the options.
|
||||
>
|
||||
command! -bang MyStuff
|
||||
\ call fzf#run(fzf#wrap('my-stuff', {'dir': '~/my-stuff'}, <bang>0))
|
||||
" Usage:
|
||||
" fzf#wrap([name string,] [opts dict,] [fullscreen boolean])
|
||||
|
||||
" This command now supports CTRL-T, CTRL-V, and CTRL-X key bindings
|
||||
" and opens fzf according to g:fzf_layout setting.
|
||||
command! Buffers call fzf#run(fzf#wrap(
|
||||
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}))
|
||||
|
||||
" This extends the above example to open fzf in fullscreen
|
||||
" when the command is run with ! suffix (Buffers!)
|
||||
command! -bang Buffers call fzf#run(fzf#wrap(
|
||||
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}, <bang>0))
|
||||
|
||||
" You can optionally pass the name of the command as the first argument to
|
||||
" fzf#wrap to make it work with g:fzf_history_dir
|
||||
command! -bang Buffers call fzf#run(fzf#wrap('buffers',
|
||||
\ {'source': map(range(1, bufnr('$')), 'bufname(v:val)')}, <bang>0))
|
||||
<
|
||||
|
||||
FZF INSIDE TERMINAL BUFFER *fzf-inside-terminal-buffer*
|
||||
@@ -192,29 +254,6 @@ the containing buffer.
|
||||
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
|
||||
<
|
||||
|
||||
GVIM *fzf-gvim*
|
||||
==============================================================================
|
||||
|
||||
With the latest version of GVim, fzf will start inside the builtin terminal
|
||||
emulator of Vim. Please note that this terminal feature of Vim is still young
|
||||
and unstable and you may run into some issues.
|
||||
|
||||
If you have an older version of GVim, you need an external terminal emulator
|
||||
to start fzf with. `xterm` command is used by default, but you can customize
|
||||
it with `g:fzf_launcher`.
|
||||
>
|
||||
" This is the default. %s is replaced with fzf command
|
||||
let g:fzf_launcher = 'xterm -e bash -ic %s'
|
||||
|
||||
" Use urxvt instead
|
||||
let g:fzf_launcher = 'urxvt -geometry 120x30 -e sh -c %s'
|
||||
<
|
||||
If you're running MacVim on OSX, I recommend you to use iTerm2 as the
|
||||
launcher. Refer to the {this wiki page}{3} to see how to set up.
|
||||
|
||||
{3} https://github.com/junegunn/fzf/wiki/On-MacVim-with-iTerm2
|
||||
|
||||
|
||||
LICENSE *fzf-license*
|
||||
==============================================================================
|
||||
|
||||
|
||||
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
|
||||
19
go.mod
Normal file
19
go.mod
Normal file
@@ -0,0 +1,19 @@
|
||||
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-20190509141414-a5b02f93d862 // indirect
|
||||
golang.org/x/text v0.0.0-20170530162606-4ee4af566555 // indirect
|
||||
)
|
||||
|
||||
go 1.13
|
||||
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-20190509141414-a5b02f93d862 h1:rM0ROo5vb9AdYJi1110yjWGMej9ITfKddS89P3Fkhug=
|
||||
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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=
|
||||
37
install
37
install
@@ -2,12 +2,11 @@
|
||||
|
||||
set -u
|
||||
|
||||
version=0.17.4
|
||||
version=0.19.0
|
||||
auto_completion=
|
||||
key_bindings=
|
||||
update_config=2
|
||||
binary_arch=
|
||||
allow_legacy=
|
||||
shells="bash zsh fish"
|
||||
prefix='~/.fzf'
|
||||
prefix_expand=~/.fzf
|
||||
@@ -45,7 +44,6 @@ for opt in "$@"; do
|
||||
auto_completion=1
|
||||
key_bindings=1
|
||||
update_config=1
|
||||
allow_legacy=1
|
||||
;;
|
||||
--xdg)
|
||||
prefix='"${XDG_CONFIG_HOME:-$HOME/.config}"/fzf/fzf'
|
||||
@@ -73,7 +71,8 @@ for opt in "$@"; do
|
||||
done
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
fzf_base="$(pwd)"
|
||||
fzf_base=$(pwd)
|
||||
fzf_base_esc=$(printf %q "$fzf_base")
|
||||
|
||||
ask() {
|
||||
while true; do
|
||||
@@ -90,17 +89,20 @@ ask() {
|
||||
check_binary() {
|
||||
echo -n " - Checking fzf executable ... "
|
||||
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
|
||||
echo "Error: $output"
|
||||
binary_error="Invalid binary"
|
||||
elif [ "$version" != "$output" ]; then
|
||||
echo "$output != $version"
|
||||
binary_error="Invalid version"
|
||||
else
|
||||
echo "$output"
|
||||
binary_error=""
|
||||
return 0
|
||||
output=${output/ */}
|
||||
if [ "$version" != "$output" ]; then
|
||||
echo "$output != $version"
|
||||
binary_error="Invalid version"
|
||||
else
|
||||
echo "$output"
|
||||
binary_error=""
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
rm -f "$fzf_base"/bin/fzf
|
||||
return 1
|
||||
@@ -109,7 +111,7 @@ check_binary() {
|
||||
link_fzf_in_path() {
|
||||
if which_fzf="$(command -v fzf)"; then
|
||||
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)
|
||||
check_binary && return
|
||||
fi
|
||||
@@ -192,6 +194,8 @@ case "$archi" in
|
||||
CYGWIN*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
|
||||
MINGW*\ *86) download fzf-$version-windows_${binary_arch:-386}.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 ;;
|
||||
esac
|
||||
|
||||
@@ -265,8 +269,8 @@ for shell in $shells; do
|
||||
cat > "$src" << EOF
|
||||
# Setup fzf
|
||||
# ---------
|
||||
if [[ ! "\$PATH" == *$fzf_base/bin* ]]; then
|
||||
export PATH="\$PATH:$fzf_base/bin"
|
||||
if [[ ! "\$PATH" == *$fzf_base_esc/bin* ]]; then
|
||||
export PATH="\${PATH:+\${PATH}:}$fzf_base/bin"
|
||||
fi
|
||||
|
||||
# Auto-completion
|
||||
@@ -276,7 +280,6 @@ $fzf_completion
|
||||
# Key bindings
|
||||
# ------------
|
||||
$fzf_key_bindings
|
||||
|
||||
EOF
|
||||
echo "OK"
|
||||
done
|
||||
@@ -285,8 +288,8 @@ done
|
||||
if [[ "$shells" =~ fish ]]; then
|
||||
echo -n "Update fish_user_paths ... "
|
||||
fish << EOF
|
||||
echo \$fish_user_paths | \grep $fzf_base/bin > /dev/null
|
||||
or set --universal fish_user_paths \$fish_user_paths $fzf_base/bin
|
||||
echo \$fish_user_paths | \grep "$fzf_base"/bin > /dev/null
|
||||
or set --universal fish_user_paths \$fish_user_paths "$fzf_base"/bin
|
||||
EOF
|
||||
[ $? -eq 0 ] && echo "OK" || echo "Failed"
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.ig
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Junegunn Choi
|
||||
Copyright (c) 2019 Junegunn Choi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf-tmux 1 "Jun 2018" "fzf 0.17.4" "fzf-tmux - open fzf in tmux split pane"
|
||||
.TH fzf-tmux 1 "Nov 2019" "fzf 0.19.0" "fzf-tmux - open fzf in tmux split pane"
|
||||
|
||||
.SH NAME
|
||||
fzf-tmux - open fzf in tmux split pane
|
||||
|
||||
291
man/man1/fzf.1
291
man/man1/fzf.1
@@ -1,7 +1,7 @@
|
||||
.ig
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Junegunn Choi
|
||||
Copyright (c) 2019 Junegunn Choi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf 1 "Jun 2018" "fzf 0.17.4" "fzf - a command-line fuzzy finder"
|
||||
.TH fzf 1 "Nov 2019" "fzf 0.19.0" "fzf - a command-line fuzzy finder"
|
||||
|
||||
.SH NAME
|
||||
fzf - a command-line fuzzy finder
|
||||
@@ -70,6 +70,10 @@ Transform the presentation of each line using field index expressions
|
||||
.TP
|
||||
.BI "-d, --delimiter=" "STR"
|
||||
Field delimiter regex for \fB--nth\fR and \fB--with-nth\fR (default: AWK-style)
|
||||
.TP
|
||||
.BI "--phony"
|
||||
Do not perform search. With this option, fzf becomes a simple selector
|
||||
interface rather than a "fuzzy finder".
|
||||
.SS Search result
|
||||
.TP
|
||||
.B "+s, --no-sort"
|
||||
@@ -79,7 +83,8 @@ Do not sort the result
|
||||
Reverse the order of the input
|
||||
|
||||
.RS
|
||||
e.g. \fBhistory | fzf --tac --no-sort\fR
|
||||
e.g.
|
||||
\fBhistory | fzf --tac --no-sort\fR
|
||||
.RE
|
||||
.TP
|
||||
.BI "--tiebreak=" "CRI[,..]"
|
||||
@@ -109,7 +114,8 @@ Comma-separated list of sort criteria to apply when the scores are tied.
|
||||
.SS Interface
|
||||
.TP
|
||||
.B "-m, --multi"
|
||||
Enable multi-select with tab/shift-tab
|
||||
Enable multi-select with tab/shift-tab. It optionally takes an integer argument
|
||||
which denotes the maximum number of items that can be selected.
|
||||
.TP
|
||||
.B "+m, --no-multi"
|
||||
Disable multi-select
|
||||
@@ -118,8 +124,8 @@ Disable multi-select
|
||||
Disable mouse
|
||||
.TP
|
||||
.BI "--bind=" "KEYBINDS"
|
||||
Comma-separated list of custom key bindings. See \fBKEY BINDINGS\fR for the
|
||||
details.
|
||||
Comma-separated list of custom key bindings. See \fBKEY/EVENT BINDINGS\fR for
|
||||
the details.
|
||||
.TP
|
||||
.B "--cycle"
|
||||
Enable cyclic scroll
|
||||
@@ -174,6 +180,11 @@ A synonym for \fB--layout=reverse\fB
|
||||
.TP
|
||||
.B "--border"
|
||||
Draw border above and below the finder
|
||||
|
||||
.TP
|
||||
.B "--no-unicode"
|
||||
Use ASCII characters instead of Unicode box drawing characters to draw border
|
||||
|
||||
.TP
|
||||
.BI "--margin=" MARGIN
|
||||
Comma-separated expression for margins around the finder.
|
||||
@@ -196,12 +207,26 @@ terminal size with \fB%\fR suffix.
|
||||
.br
|
||||
|
||||
.br
|
||||
e.g. \fBfzf --margin 10%\fR
|
||||
\fBfzf --margin 1,5%\fR
|
||||
e.g.
|
||||
\fBfzf --margin 10%
|
||||
fzf --margin 1,5%\fR
|
||||
.RE
|
||||
.TP
|
||||
.B "--inline-info"
|
||||
Display finder info inline with the query
|
||||
.BI "--info=" "STYLE"
|
||||
Determines the display style of finder info.
|
||||
|
||||
.br
|
||||
.BR default " Display on the next line to the prompt"
|
||||
.br
|
||||
.BR inline " Display on the same line"
|
||||
.br
|
||||
.BR hidden " Do not display finder info"
|
||||
.br
|
||||
|
||||
.TP
|
||||
.B "--no-info"
|
||||
A synonym for \fB--info=hidden\fB
|
||||
|
||||
.TP
|
||||
.BI "--prompt=" "STR"
|
||||
Input prompt (default: '> ')
|
||||
@@ -230,11 +255,6 @@ color mappings. Ansi color code of -1 denotes terminal default
|
||||
foreground/background color. You can also specify 24-bit color in \fB#rrggbb\fR
|
||||
format.
|
||||
|
||||
.RS
|
||||
e.g. \fBfzf --color=bg+:24\fR
|
||||
\fBfzf --color=light,fg:232,bg:255,bg+:116,info:27\fR
|
||||
.RE
|
||||
|
||||
.RS
|
||||
.B BASE SCHEME:
|
||||
(default: dark on 256-color terminal, otherwise 16)
|
||||
@@ -250,6 +270,7 @@ e.g. \fBfzf --color=bg+:24\fR
|
||||
\fBhl \fRHighlighted substrings
|
||||
\fBfg+ \fRText (current line)
|
||||
\fBbg+ \fRBackground (current line)
|
||||
\fBgutter \fRGutter on the left (defaults to \fBbg+\fR)
|
||||
\fBhl+ \fRHighlighted substrings (current line)
|
||||
\fBinfo \fRInfo
|
||||
\fBborder \fRBorder of the preview window and horizontal separators (\fB--border\fR)
|
||||
@@ -258,6 +279,19 @@ e.g. \fBfzf --color=bg+:24\fR
|
||||
\fBmarker \fRMulti-select marker
|
||||
\fBspinner \fRStreaming input indicator
|
||||
\fBheader \fRHeader
|
||||
|
||||
.B EXAMPLES:
|
||||
|
||||
\fB# Seoul256 theme with 8-bit colors
|
||||
# (https://github.com/junegunn/seoul256.vim)
|
||||
fzf --color='bg:237,bg+:236,info:143,border:240,spinner:108' \\
|
||||
--color='hl:65,fg:252,header:65,fg+:252' \\
|
||||
--color='pointer:161,marker:168,prompt:110,hl+:108'
|
||||
|
||||
# Seoul256 theme with 24-bit colors
|
||||
fzf --color='bg:#4B4B4B,bg+:#3F3F3F,info:#BDBB72,border:#6B6B6B,spinner:#98BC99' \\
|
||||
--color='hl:#719872,fg:#D9D9D9,header:#719872,fg+:#D9D9D9' \\
|
||||
--color='pointer:#E12672,marker:#E17899,prompt:#98BEDE,hl+:#98BC99'\fR
|
||||
.RE
|
||||
.TP
|
||||
.B "--no-bold"
|
||||
@@ -285,23 +319,41 @@ string, specify field index expressions between the braces (See \fBFIELD INDEX
|
||||
EXPRESSION\fR for the details).
|
||||
|
||||
.RS
|
||||
e.g. \fBfzf --preview='head -$LINES {}'\fR
|
||||
\fBls -l | fzf --preview="echo user={3} when={-4..-2}; cat {-1}" --header-lines=1\fR
|
||||
e.g.
|
||||
\fBfzf --preview='head -$LINES {}'
|
||||
ls -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
|
||||
size of the preview window.
|
||||
fzf exports \fB$FZF_PREVIEW_LINES\fR and \fB$FZF_PREVIEW_COLUMNS\fR so that
|
||||
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
|
||||
space-separated list of the selected lines (or the current line if no selection
|
||||
was made) individually quoted.
|
||||
|
||||
e.g. \fBfzf --multi --preview='head -10 {+}'\fR
|
||||
\fBgit log --oneline | fzf --multi --preview 'git show {+1}'\fR
|
||||
e.g.
|
||||
\fBfzf --multi --preview='head -10 {+}'
|
||||
git log --oneline | fzf --multi --preview 'git show {+1}'\fR
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
A placeholder expression with \fBf\fR flag is replaced to the path of
|
||||
a temporary file that holds the evaluated list. This is useful when you
|
||||
multi-select a large number of items and the length of the evaluated string may
|
||||
exceed \fBARG_MAX\fR.
|
||||
|
||||
e.g.
|
||||
\fB# Press CTRL-A to select 100K items and see the sum of all the numbers.
|
||||
# This won't work properly without 'f' flag due to ARG_MAX limit.
|
||||
seq 100000 | fzf --multi --bind ctrl-a:select-all \\
|
||||
--preview "awk '{sum+=\$1} END {print sum}' {+f}"\fR
|
||||
|
||||
Note that you can escape a placeholder pattern by prepending a backslash.
|
||||
|
||||
@@ -309,8 +361,8 @@ 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
|
||||
.TP
|
||||
.BI "--preview-window=" "[POSITION][:SIZE[%]][:wrap][:hidden]"
|
||||
Determine the layout of the preview window. If the argument ends with
|
||||
.BI "--preview-window=" "[POSITION][:SIZE[%]][:noborder][:wrap][:hidden]"
|
||||
Determines the layout of the preview window. If the argument contains
|
||||
\fB:hidden\fR, the preview window will be hidden by default until
|
||||
\fBtoggle-preview\fR action is triggered. Long lines are truncated by default.
|
||||
Line wrap can be enabled with \fB:wrap\fR flag.
|
||||
@@ -327,8 +379,9 @@ execute the command in the background.
|
||||
.RE
|
||||
|
||||
.RS
|
||||
e.g. \fBfzf --preview="head {}" --preview-window=up:30%\fR
|
||||
\fBfzf --preview="file {}" --preview-window=down:1\fR
|
||||
e.g.
|
||||
\fBfzf --preview="head {}" --preview-window=up:30%
|
||||
fzf --preview="file {}" --preview-window=down:1\fR
|
||||
.RE
|
||||
.SS Scripting
|
||||
.TP
|
||||
@@ -358,7 +411,8 @@ times, fzf will expect the union of the keys. \fB--no-expect\fR will clear the
|
||||
list.
|
||||
|
||||
.RS
|
||||
e.g. \fBfzf --expect=ctrl-v,ctrl-t,alt-s --expect=f1,f2,~,@\fR
|
||||
e.g.
|
||||
\fBfzf --expect=ctrl-v,ctrl-t,alt-s --expect=f1,f2,~,@\fR
|
||||
.RE
|
||||
.TP
|
||||
.B "--read0"
|
||||
@@ -390,7 +444,8 @@ Note that most options have the opposite versions with \fB--no-\fR prefix.
|
||||
.SH ENVIRONMENT VARIABLES
|
||||
.TP
|
||||
.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
|
||||
.B FZF_DEFAULT_OPTS
|
||||
Default options. e.g. \fBexport FZF_DEFAULT_OPTS="--extended --cycle"\fR
|
||||
@@ -463,56 +518,110 @@ query matches entries that start with \fBcore\fR and end with either \fBgo\fR,
|
||||
|
||||
e.g. \fB^core go$ | rb$ | py$\fR
|
||||
|
||||
.SH KEY BINDINGS
|
||||
You can customize key bindings of fzf with \fB--bind\fR option which takes
|
||||
a comma-separated list of key binding expressions. Each key binding expression
|
||||
follows the following format: \fBKEY:ACTION\fR
|
||||
.SH KEY/EVENT BINDINGS
|
||||
\fB--bind\fR option allows you to bind \fBa key\fR or \fBan event\fR to one or
|
||||
more \fBactions\fR. You can use it to customize key bindings or implement
|
||||
dynamic behaviors.
|
||||
|
||||
e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
||||
\fB--bind\fR takes a comma-separated list of binding expressions. Each binding
|
||||
expression is \fBKEY:ACTION\fR or \fBEVENT:ACTION\fR.
|
||||
|
||||
.B AVAILABLE KEYS: (SYNONYMS)
|
||||
\fIctrl-[a-z]\fR
|
||||
\fIctrl-space\fR
|
||||
\fIctrl-alt-[a-z]\fR
|
||||
\fIalt-[a-z]\fR
|
||||
\fIalt-[0-9]\fR
|
||||
\fIf[1-12]\fR
|
||||
\fIenter\fR (\fIreturn\fR \fIctrl-m\fR)
|
||||
\fIspace\fR
|
||||
\fIbspace\fR (\fIbs\fR)
|
||||
\fIalt-up\fR
|
||||
\fIalt-down\fR
|
||||
\fIalt-left\fR
|
||||
\fIalt-right\fR
|
||||
\fIalt-enter\fR
|
||||
\fIalt-space\fR
|
||||
\fIalt-bspace\fR (\fIalt-bs\fR)
|
||||
\fIalt-/\fR
|
||||
\fItab\fR
|
||||
\fIbtab\fR (\fIshift-tab\fR)
|
||||
\fIesc\fR
|
||||
\fIdel\fR
|
||||
\fIup\fR
|
||||
\fIdown\fR
|
||||
\fIleft\fR
|
||||
\fIright\fR
|
||||
\fIhome\fR
|
||||
\fIend\fR
|
||||
\fIpgup\fR (\fIpage-up\fR)
|
||||
\fIpgdn\fR (\fIpage-down\fR)
|
||||
\fIshift-up\fR
|
||||
\fIshift-down\fR
|
||||
\fIshift-left\fR
|
||||
\fIshift-right\fR
|
||||
\fIleft-click\fR
|
||||
\fIright-click\fR
|
||||
\fIdouble-click\fR
|
||||
or any single character
|
||||
e.g.
|
||||
\fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
||||
|
||||
Additionally, a special event named \fIchange\fR is available which is
|
||||
triggered whenever the query string is changed.
|
||||
.SS AVAILABLE KEYS: (SYNONYMS)
|
||||
\fIctrl-[a-z]\fR
|
||||
.br
|
||||
\fIctrl-space\fR
|
||||
.br
|
||||
\fIctrl-\\\fR
|
||||
.br
|
||||
\fIctrl-]\fR
|
||||
.br
|
||||
\fIctrl-^\fR (\fIctrl-6\fR)
|
||||
.br
|
||||
\fIctrl-/\fR (\fIctrl-_\fR)
|
||||
.br
|
||||
\fIctrl-alt-[a-z]\fR
|
||||
.br
|
||||
\fIalt-[a-z]\fR
|
||||
.br
|
||||
\fIalt-[0-9]\fR
|
||||
.br
|
||||
\fIf[1-12]\fR
|
||||
.br
|
||||
\fIenter\fR (\fIreturn\fR \fIctrl-m\fR)
|
||||
.br
|
||||
\fIspace\fR
|
||||
.br
|
||||
\fIbspace\fR (\fIbs\fR)
|
||||
.br
|
||||
\fIalt-up\fR
|
||||
.br
|
||||
\fIalt-down\fR
|
||||
.br
|
||||
\fIalt-left\fR
|
||||
.br
|
||||
\fIalt-right\fR
|
||||
.br
|
||||
\fIalt-enter\fR
|
||||
.br
|
||||
\fIalt-space\fR
|
||||
.br
|
||||
\fIalt-bspace\fR (\fIalt-bs\fR)
|
||||
.br
|
||||
\fIalt-/\fR
|
||||
.br
|
||||
\fItab\fR
|
||||
.br
|
||||
\fIbtab\fR (\fIshift-tab\fR)
|
||||
.br
|
||||
\fIesc\fR
|
||||
.br
|
||||
\fIdel\fR
|
||||
.br
|
||||
\fIup\fR
|
||||
.br
|
||||
\fIdown\fR
|
||||
.br
|
||||
\fIleft\fR
|
||||
.br
|
||||
\fIright\fR
|
||||
.br
|
||||
\fIhome\fR
|
||||
.br
|
||||
\fIend\fR
|
||||
.br
|
||||
\fIpgup\fR (\fIpage-up\fR)
|
||||
.br
|
||||
\fIpgdn\fR (\fIpage-down\fR)
|
||||
.br
|
||||
\fIshift-up\fR
|
||||
.br
|
||||
\fIshift-down\fR
|
||||
.br
|
||||
\fIshift-left\fR
|
||||
.br
|
||||
\fIshift-right\fR
|
||||
.br
|
||||
\fIleft-click\fR
|
||||
.br
|
||||
\fIright-click\fR
|
||||
.br
|
||||
\fIdouble-click\fR
|
||||
.br
|
||||
or any single character
|
||||
|
||||
e.g. \fBfzf --bind change:top\fR
|
||||
.SS AVAILABLE EVENTS:
|
||||
\fIchange\fR (triggered whenever the query string is changed)
|
||||
.br
|
||||
|
||||
e.g.
|
||||
\fB# Moves cursor to the top (or bottom depending on --layout) whenever the query is changed
|
||||
fzf --bind change:top\fR
|
||||
|
||||
.SS AVAILABLE ACTIONS:
|
||||
A key or an event can be bound to one or more of the following actions.
|
||||
|
||||
\fBACTION: DEFAULT BINDINGS (NOTES):
|
||||
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
|
||||
@@ -551,6 +660,7 @@ triggered whenever the query string is changed.
|
||||
\fBpreview-page-up\fR
|
||||
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
||||
\fBprint-query\fR (print query and exit)
|
||||
\fBreload(...)\fR (see below for the details)
|
||||
\fBreplace-query\fR (replace query string with the current selection)
|
||||
\fBselect-all\fR
|
||||
\fBtoggle\fR (\fIright-click\fR)
|
||||
@@ -568,9 +678,14 @@ triggered whenever the query string is changed.
|
||||
\fBup\fR \fIctrl-k ctrl-p up\fR
|
||||
\fByank\fR \fIctrl-y\fR
|
||||
|
||||
.SS ACTION COMPOSITION
|
||||
|
||||
Multiple actions can be chained using \fB+\fR separator.
|
||||
|
||||
\fBfzf --bind 'ctrl-a:select-all+accept'\fR
|
||||
e.g.
|
||||
\fBfzf --bind 'ctrl-a:select-all+accept'\fR
|
||||
|
||||
.SS COMMAND EXECUTION
|
||||
|
||||
With \fBexecute(...)\fR action, you can execute arbitrary commands without
|
||||
leaving fzf. For example, you can turn fzf into a simple file browser by
|
||||
@@ -599,9 +714,9 @@ parse errors.
|
||||
\fBexecute|...|\fR
|
||||
\fBexecute:...\fR
|
||||
.RS
|
||||
This is the special form that frees you from parse errors as it does not expect
|
||||
the closing character. The catch is that it should be the last one in the
|
||||
comma-separated list of key-action pairs.
|
||||
The last one is the special form that frees you from parse errors as it does
|
||||
not expect the closing character. The catch is that it should be the last one
|
||||
in the comma-separated list of key-action pairs.
|
||||
.RE
|
||||
|
||||
fzf switches to the alternate screen when executing a command. However, if the
|
||||
@@ -611,6 +726,26 @@ executes the command without the switching. Note that fzf will not be
|
||||
responsive until the command is complete. For asynchronous execution, start
|
||||
your command as a background process (i.e. appending \fB&\fR).
|
||||
|
||||
.SS RELOAD INPUT
|
||||
|
||||
\fBreload(...)\fR action is used to dynamically update the input list
|
||||
without restarting fzf. It takes the same command template with placeholder
|
||||
expressions as \fBexecute(...)\fR.
|
||||
|
||||
See \fIhttps://github.com/junegunn/fzf/issues/1750\fR for more info.
|
||||
|
||||
e.g.
|
||||
\fB# Update the list of processes by pressing CTRL-R
|
||||
ps -ef | fzf --bind 'ctrl-r:reload(ps -ef)' --header 'Press CTRL-R to reload' \\
|
||||
--header-lines=1 --layout=reverse
|
||||
|
||||
# Integration with ripgrep
|
||||
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
||||
INITIAL_QUERY="foobar"
|
||||
FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY'" \\
|
||||
fzf --bind "change:reload:$RG_PREFIX {q} || true" \\
|
||||
--ansi --phony --query "$INITIAL_QUERY"\fR
|
||||
|
||||
.SH AUTHOR
|
||||
Junegunn Choi (\fIjunegunn.c@gmail.com\fR)
|
||||
|
||||
|
||||
105
plugin/fzf.vim
105
plugin/fzf.vim
@@ -50,9 +50,17 @@ if s:is_win
|
||||
" Use utf-8 for fzf.vim commands
|
||||
" Return array of shell commands for cmd.exe
|
||||
function! s:wrap_cmds(cmds)
|
||||
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]) +
|
||||
\ ['chcp %origchcp% > nul'], 'v:val."\r"')
|
||||
let use_chcp = executable('sed')
|
||||
return map([
|
||||
\ '@echo off',
|
||||
\ 'setlocal enabledelayedexpansion']
|
||||
\ + (use_chcp ? [
|
||||
\ 'for /f "usebackq" %%a in (`chcp ^| sed "s/[^0-9]//gp"`) do set origchcp=%%a',
|
||||
\ 'chcp 65001 > nul'] : [])
|
||||
\ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds])
|
||||
\ + (use_chcp ? ['chcp !origchcp! > nul'] : [])
|
||||
\ + ['endlocal'],
|
||||
\ 'v:val."\r"')
|
||||
endfunction
|
||||
else
|
||||
let s:term_marker = ";#FZF"
|
||||
@@ -254,7 +262,7 @@ endfunction
|
||||
function! s:defaults()
|
||||
let rules = copy(get(g:, 'fzf_colors', {}))
|
||||
let colors = join(map(items(filter(map(rules, 'call("s:get_color", v:val)'), '!empty(v:val)')), 'join(v:val, ":")'), ',')
|
||||
return empty(colors) ? '' : ('--color='.colors)
|
||||
return empty(colors) ? '' : fzf#shellescape('--color='.colors)
|
||||
endfunction
|
||||
|
||||
function! s:validate_layout(layout)
|
||||
@@ -357,7 +365,7 @@ try
|
||||
throw v:exception
|
||||
endtry
|
||||
|
||||
if has('nvim') && !has_key(dict, 'dir')
|
||||
if !has_key(dict, 'dir')
|
||||
let dict.dir = s:fzf_getcwd()
|
||||
endif
|
||||
if has('win32unix') && has_key(dict, 'dir')
|
||||
@@ -455,15 +463,18 @@ endfunction
|
||||
function! s:pushd(dict)
|
||||
if s:present(a:dict, 'dir')
|
||||
let cwd = s:fzf_getcwd()
|
||||
if get(a:dict, 'prev_dir', '') ==# cwd
|
||||
return 1
|
||||
endif
|
||||
let a:dict.prev_dir = cwd
|
||||
let w:fzf_pushd = {
|
||||
\ 'command': haslocaldir() ? 'lcd' : (exists(':tcd') && haslocaldir(-1) ? 'tcd' : 'cd'),
|
||||
\ 'origin': cwd,
|
||||
\ 'bufname': bufname('')
|
||||
\ }
|
||||
execute 'lcd' s:escape(a:dict.dir)
|
||||
let a:dict.dir = s:fzf_getcwd()
|
||||
return 1
|
||||
let cwd = s:fzf_getcwd()
|
||||
let w:fzf_pushd.dir = cwd
|
||||
let a:dict.pushd = w:fzf_pushd
|
||||
return cwd
|
||||
endif
|
||||
return 0
|
||||
return ''
|
||||
endfunction
|
||||
|
||||
augroup fzf_popd
|
||||
@@ -472,20 +483,38 @@ augroup fzf_popd
|
||||
augroup END
|
||||
|
||||
function! s:dopopd()
|
||||
if !exists('w:fzf_dir') || s:fzf_getcwd() != w:fzf_dir[1]
|
||||
if !exists('w:fzf_pushd')
|
||||
return
|
||||
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
|
||||
|
||||
function! s:xterm_launcher()
|
||||
let fmt = 'xterm -T "[fzf]" -bg "\%s" -fg "\%s" -geometry %dx%d+%d+%d -e bash -ic %%s'
|
||||
let fmt = 'xterm -T "[fzf]" -bg "%s" -fg "%s" -geometry %dx%d+%d+%d -e bash -ic %%s'
|
||||
if has('gui_macvim')
|
||||
let fmt .= '&& osascript -e "tell application \"MacVim\" to activate"'
|
||||
endif
|
||||
return printf(fmt,
|
||||
\ synIDattr(hlID("Normal"), "bg"), synIDattr(hlID("Normal"), "fg"),
|
||||
\ escape(synIDattr(hlID("Normal"), "bg"), '#'), escape(synIDattr(hlID("Normal"), "fg"), '#'),
|
||||
\ &columns, &lines/2, getwinposx(), getwinposy())
|
||||
endfunction
|
||||
unlet! s:launcher
|
||||
@@ -498,6 +527,10 @@ endif
|
||||
function! s:exit_handler(code, command, ...)
|
||||
if a:code == 130
|
||||
return 0
|
||||
elseif has('nvim') && a:code == 129
|
||||
" When deleting the terminal buffer while fzf is still running,
|
||||
" Nvim sends SIGHUP.
|
||||
return 0
|
||||
elseif a:code > 1
|
||||
call s:error('Error running ' . a:command)
|
||||
if !empty(a:000)
|
||||
@@ -534,9 +567,7 @@ function! s:execute(dict, command, use_height, temps) abort
|
||||
let fzf.dict = a:dict
|
||||
let fzf.temps = a:temps
|
||||
function! fzf.on_exit(job_id, exit_status, event) dict
|
||||
if s:present(self.dict, 'dir')
|
||||
execute 'lcd' s:escape(self.dict.dir)
|
||||
endif
|
||||
call s:pushd(self.dict)
|
||||
let lines = s:collect(self.temps)
|
||||
call s:callback(self.dict, lines)
|
||||
endfunction
|
||||
@@ -563,9 +594,10 @@ endfunction
|
||||
|
||||
function! s:execute_tmux(dict, command, temps) abort
|
||||
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
|
||||
let command = join(['cd', fzf#shellescape(a:dict.dir), '&&', command])
|
||||
let command = join(['cd', fzf#shellescape(cwd), '&&', command])
|
||||
endif
|
||||
|
||||
call system(command)
|
||||
@@ -587,8 +619,9 @@ function! s:calc_size(max, val, dict)
|
||||
let srcsz = len(a:dict.source)
|
||||
endif
|
||||
|
||||
let opts = s:evaluate_opts(get(a:dict, 'options', '')).$FZF_DEFAULT_OPTS
|
||||
let opts = $FZF_DEFAULT_OPTS.' '.s:evaluate_opts(get(a:dict, 'options', ''))
|
||||
let margin = stridx(opts, '--inline-info') > stridx(opts, '--no-inline-info') ? 1 : 2
|
||||
let margin += stridx(opts, '--border') > stridx(opts, '--no-border') ? 2 : 0
|
||||
let margin += stridx(opts, '--header') > stridx(opts, '--no-header')
|
||||
return srcsz >= 0 ? min([srcsz + margin, size]) : size
|
||||
endfunction
|
||||
@@ -686,9 +719,7 @@ function! s:execute_term(dict, command, temps) abort
|
||||
endfunction
|
||||
|
||||
try
|
||||
if s:present(a:dict, 'dir')
|
||||
execute 'lcd' s:escape(a:dict.dir)
|
||||
endif
|
||||
call s:pushd(a:dict)
|
||||
if s:is_win
|
||||
let fzf.temps.batchfile = s:fzf_tempname().'.bat'
|
||||
call writefile(s:wrap_cmds(a:command), fzf.temps.batchfile)
|
||||
@@ -706,9 +737,7 @@ function! s:execute_term(dict, command, temps) abort
|
||||
endif
|
||||
endif
|
||||
finally
|
||||
if s:present(a:dict, 'dir')
|
||||
lcd -
|
||||
endif
|
||||
call s:dopopd()
|
||||
endtry
|
||||
setlocal nospell bufhidden=wipe nobuflisted nonumber
|
||||
setf fzf
|
||||
@@ -727,21 +756,9 @@ function! s:collect(temps) abort
|
||||
endfunction
|
||||
|
||||
function! s:callback(dict, lines) abort
|
||||
" Since anything can be done in the sink function, there is no telling that
|
||||
" 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])))
|
||||
let popd = has_key(a:dict, 'pushd')
|
||||
if popd
|
||||
let w:fzf_dir = [a:dict.prev_dir, a:dict.dir]
|
||||
let w:fzf_pushd = a:dict.pushd
|
||||
endif
|
||||
|
||||
try
|
||||
@@ -765,7 +782,7 @@ function! s:callback(dict, lines) abort
|
||||
|
||||
" We may have opened a new window or tab
|
||||
if popd
|
||||
let w:fzf_dir = [a:dict.prev_dir, a:dict.dir]
|
||||
let w:fzf_pushd = a:dict.pushd
|
||||
call s:dopopd()
|
||||
endif
|
||||
endfunction
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||
|
||||
if [[ $- =~ i ]]; then
|
||||
|
||||
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
||||
if ! declare -f _fzf_compgen_path > /dev/null; then
|
||||
_fzf_compgen_path() {
|
||||
@@ -121,11 +123,11 @@ _fzf_handle_dynamic_completion() {
|
||||
if [ -n "$orig" ] && type "$orig" > /dev/null 2>&1; then
|
||||
$orig "$@"
|
||||
elif [ -n "$_fzf_completion_loader" ]; then
|
||||
orig_complete=$(complete -p "$cmd" 2> /dev/null)
|
||||
orig_complete=$(complete -p "$orig_cmd" 2> /dev/null)
|
||||
_completion_loader "$@"
|
||||
ret=$?
|
||||
# _completion_loader may not have updated completion for the command
|
||||
if [ "$(complete -p "$cmd" 2> /dev/null)" != "$orig_complete" ]; then
|
||||
if [ "$(complete -p "$orig_cmd" 2> /dev/null)" != "$orig_complete" ]; then
|
||||
eval "$(complete | command grep " -F.* $orig_cmd$" | __fzf_orig_completion_filter)"
|
||||
if [[ "$__fzf_nospace_commands" = *" $orig_cmd "* ]]; then
|
||||
eval "${orig_complete/ -F / -o nospace -F }"
|
||||
@@ -193,12 +195,13 @@ _fzf_complete() {
|
||||
|
||||
selected=$(cat | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" $fzf $1 -q "$cur" | $post | tr '\n' ' ')
|
||||
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
|
||||
printf '\e[5n'
|
||||
|
||||
if [ -n "$selected" ]; then
|
||||
COMPREPLY=("$selected")
|
||||
return 0
|
||||
else
|
||||
COMPREPLY=("$cur")
|
||||
fi
|
||||
printf '\e[5n'
|
||||
return 0
|
||||
else
|
||||
shift
|
||||
_fzf_handle_dynamic_completion "$cmd" "$@"
|
||||
@@ -241,7 +244,7 @@ _fzf_complete_telnet() {
|
||||
|
||||
_fzf_complete_ssh() {
|
||||
_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 -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||
@@ -290,7 +293,7 @@ if type _completion_loader > /dev/null 2>&1; then
|
||||
_fzf_completion_loader=1
|
||||
fi
|
||||
|
||||
_fzf_defc() {
|
||||
__fzf_defc() {
|
||||
local cmd func opts orig_var orig def
|
||||
cmd="$1"
|
||||
func="$2"
|
||||
@@ -307,16 +310,14 @@ _fzf_defc() {
|
||||
|
||||
# Anything
|
||||
for cmd in $a_cmds; do
|
||||
_fzf_defc "$cmd" _fzf_path_completion "-o default -o bashdefault"
|
||||
__fzf_defc "$cmd" _fzf_path_completion "-o default -o bashdefault"
|
||||
done
|
||||
|
||||
# Directory
|
||||
for cmd in $d_cmds; do
|
||||
_fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames"
|
||||
__fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames"
|
||||
done
|
||||
|
||||
unset _fzf_defc
|
||||
|
||||
# Kill completion
|
||||
complete -F _fzf_complete_kill -o nospace -o default -o bashdefault kill
|
||||
|
||||
@@ -330,3 +331,23 @@ complete -F _fzf_complete_export -o default -o bashdefault export
|
||||
complete -F _fzf_complete_unalias -o default -o bashdefault unalias
|
||||
|
||||
unset cmd d_cmds a_cmds x_cmds
|
||||
|
||||
_fzf_setup_completion() {
|
||||
local kind fn cmd
|
||||
kind=$1
|
||||
fn=_fzf_${1}_completion
|
||||
if [[ $# -lt 2 ]] || ! type -t "$fn" > /dev/null; then
|
||||
echo "usage: ${FUNCNAME[0]} path|dir COMMANDS..."
|
||||
return 1
|
||||
fi
|
||||
shift
|
||||
for cmd in "$@"; do
|
||||
eval "$(complete -p "$cmd" 2> /dev/null | grep -v "$fn" | __fzf_orig_completion_filter)"
|
||||
case "$kind" in
|
||||
dir) __fzf_defc "$cmd" "$fn" "-o nospace -o dirnames" ;;
|
||||
*) __fzf_defc "$cmd" "$fn" "-o default -o bashdefault" ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
fi
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||
|
||||
if [[ $- =~ i ]]; then
|
||||
|
||||
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
||||
if ! declare -f _fzf_compgen_path > /dev/null; then
|
||||
_fzf_compgen_path() {
|
||||
@@ -60,8 +62,7 @@ __fzf_generic_path_completion() {
|
||||
if [ -n "$matches" ]; then
|
||||
LBUFFER="$lbuf$matches$tail"
|
||||
fi
|
||||
zle redisplay
|
||||
typeset -f zle-line-init >/dev/null && zle zle-line-init
|
||||
zle reset-prompt
|
||||
break
|
||||
fi
|
||||
dir=$(dirname "$dir")
|
||||
@@ -100,8 +101,7 @@ _fzf_complete() {
|
||||
if [ -n "$matches" ]; then
|
||||
LBUFFER="$lbuf$matches"
|
||||
fi
|
||||
zle redisplay
|
||||
typeset -f zle-line-init >/dev/null && zle zle-line-init
|
||||
zle reset-prompt
|
||||
command rm -f "$fifo"
|
||||
}
|
||||
|
||||
@@ -114,7 +114,8 @@ _fzf_complete_telnet() {
|
||||
|
||||
_fzf_complete_ssh() {
|
||||
_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 -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||
@@ -157,6 +158,12 @@ fzf-completion() {
|
||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||
[ -z "$trigger" -a ${LBUFFER[-1]} = ' ' ] && tokens+=("")
|
||||
|
||||
# When the trigger starts with ';', it becomes a separate token
|
||||
if [[ ${LBUFFER} = *"${tokens[-2]}${tokens[-1]}" ]]; then
|
||||
tokens[-2]="${tokens[-2]}${tokens[-1]}"
|
||||
tokens=(${tokens[0,-2]})
|
||||
fi
|
||||
|
||||
tail=${LBUFFER:$(( ${#LBUFFER} - ${#trigger} ))}
|
||||
# Kill completion (do not require trigger sequence)
|
||||
if [ $cmd = kill -a ${LBUFFER[-1]} = ' ' ]; then
|
||||
@@ -165,8 +172,7 @@ fzf-completion() {
|
||||
if [ -n "$matches" ]; then
|
||||
LBUFFER="$LBUFFER$matches"
|
||||
fi
|
||||
zle redisplay
|
||||
typeset -f zle-line-init >/dev/null && zle zle-line-init
|
||||
zle reset-prompt
|
||||
# Trigger sequence given
|
||||
elif [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
|
||||
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})
|
||||
@@ -195,3 +201,5 @@ fzf-completion() {
|
||||
|
||||
zle -N fzf-completion
|
||||
bindkey '^I' fzf-completion
|
||||
|
||||
fi
|
||||
|
||||
@@ -55,7 +55,7 @@ __fzf_history__() (
|
||||
local line
|
||||
shopt -u nocaseglob nocasematch
|
||||
line=$(
|
||||
HISTTIMEFORMAT= history |
|
||||
HISTTIMEFORMAT= builtin history |
|
||||
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]') &&
|
||||
if [[ $- =~ H ]]; then
|
||||
|
||||
@@ -40,14 +40,14 @@ function fzf_key_bindings
|
||||
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 -l FISH_MAJOR (echo $FISH_VERSION | cut -f1 -d.)
|
||||
set -l FISH_MINOR (echo $FISH_VERSION | cut -f2 -d.)
|
||||
set -l FISH_MAJOR (echo $version | cut -f1 -d.)
|
||||
set -l FISH_MINOR (echo $version | cut -f2 -d.)
|
||||
|
||||
# history's -z flag is needed for multi-line support.
|
||||
# history's -z flag was added in fish 2.4.0, so don't use it for versions
|
||||
# before 2.4.0.
|
||||
if [ "$FISH_MAJOR" -gt 2 -o \( "$FISH_MAJOR" -eq 2 -a "$FISH_MINOR" -ge 4 \) ];
|
||||
history -z | eval (__fzfcmd) --read0 -q '(commandline)' | perl -pe 'chomp if eof' | read -lz result
|
||||
history -z | eval (__fzfcmd) --read0 --print0 -q '(commandline)' | read -lz result
|
||||
and commandline -- $result
|
||||
else
|
||||
history | eval (__fzfcmd) -q '(commandline)' | read -l result
|
||||
|
||||
@@ -8,7 +8,7 @@ __fsel() {
|
||||
-o -type f -print \
|
||||
-o -type d -print \
|
||||
-o -type l -print 2> /dev/null | cut -b3-"}"
|
||||
setopt localoptions pipefail 2> /dev/null
|
||||
setopt localoptions pipefail no_aliases 2> /dev/null
|
||||
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read item; do
|
||||
echo -n "${(q)item} "
|
||||
done
|
||||
@@ -29,8 +29,7 @@ __fzfcmd() {
|
||||
fzf-file-widget() {
|
||||
LBUFFER="${LBUFFER}$(__fsel)"
|
||||
local ret=$?
|
||||
zle redisplay
|
||||
typeset -f zle-line-init >/dev/null && zle zle-line-init
|
||||
zle reset-prompt
|
||||
return $ret
|
||||
}
|
||||
zle -N fzf-file-widget
|
||||
@@ -50,7 +49,7 @@ zle -N fzf-redraw-prompt
|
||||
fzf-cd-widget() {
|
||||
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||
-o -type d -print 2> /dev/null | cut -b3-"}"
|
||||
setopt localoptions pipefail 2> /dev/null
|
||||
setopt localoptions pipefail no_aliases 2> /dev/null
|
||||
local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m)"
|
||||
if [[ -z "$dir" ]]; then
|
||||
zle redisplay
|
||||
@@ -59,7 +58,6 @@ fzf-cd-widget() {
|
||||
cd "$dir"
|
||||
local ret=$?
|
||||
zle fzf-redraw-prompt
|
||||
typeset -f zle-line-init >/dev/null && zle zle-line-init
|
||||
return $ret
|
||||
}
|
||||
zle -N fzf-cd-widget
|
||||
@@ -68,7 +66,7 @@ bindkey '\ec' fzf-cd-widget
|
||||
# CTRL-R - Paste the selected command from history into the command line
|
||||
fzf-history-widget() {
|
||||
local selected num
|
||||
setopt localoptions noglobsubst noposixbuiltins pipefail 2> /dev/null
|
||||
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
|
||||
selected=( $(fc -rl 1 |
|
||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
|
||||
local ret=$?
|
||||
@@ -78,8 +76,7 @@ fzf-history-widget() {
|
||||
zle vi-fetch-history -n $num
|
||||
fi
|
||||
fi
|
||||
zle redisplay
|
||||
typeset -f zle-line-init >/dev/null && zle zle-line-init
|
||||
zle reset-prompt
|
||||
return $ret
|
||||
}
|
||||
zle -N fzf-history-widget
|
||||
|
||||
@@ -371,7 +371,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
||||
// The first occurrence of each character in the pattern
|
||||
offset32, F := alloc32(offset32, slab, M)
|
||||
// Rune array
|
||||
offset32, T := alloc32(offset32, slab, N)
|
||||
_, T := alloc32(offset32, slab, N)
|
||||
input.CopyRunes(T)
|
||||
|
||||
// Phase 2. Calculate bonus for each point
|
||||
@@ -453,7 +453,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
||||
copy(H, H0[f0:lastIdx+1])
|
||||
|
||||
// Possible length of consecutive chunk at each position.
|
||||
offset16, C := alloc16(offset16, slab, width*M)
|
||||
_, C := alloc16(offset16, slab, width*M)
|
||||
copy(C, C0[f0:lastIdx+1])
|
||||
|
||||
Fsub := F[1:]
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -2,6 +2,7 @@ package fzf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/junegunn/fzf/src/tui"
|
||||
@@ -156,3 +157,31 @@ func TestExtractColor(t *testing.T) {
|
||||
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")
|
||||
}
|
||||
|
||||
@@ -64,6 +64,13 @@ func (cl *ChunkList) Push(data []byte) bool {
|
||||
return ret
|
||||
}
|
||||
|
||||
// Clear clears the data
|
||||
func (cl *ChunkList) Clear() {
|
||||
cl.mutex.Lock()
|
||||
cl.chunks = nil
|
||||
cl.mutex.Unlock()
|
||||
}
|
||||
|
||||
// Snapshot returns immutable snapshot of the ChunkList
|
||||
func (cl *ChunkList) Snapshot() ([]*Chunk, int) {
|
||||
cl.mutex.Lock()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package fzf
|
||||
|
||||
import (
|
||||
"math"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
@@ -9,7 +10,7 @@ import (
|
||||
|
||||
const (
|
||||
// Current version
|
||||
version = "0.17.4"
|
||||
version = "0.19.0"
|
||||
|
||||
// Core
|
||||
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
||||
@@ -22,9 +23,12 @@ const (
|
||||
readerPollIntervalMax = 50 * time.Millisecond
|
||||
|
||||
// Terminal
|
||||
initialDelay = 20 * time.Millisecond
|
||||
initialDelayTac = 100 * time.Millisecond
|
||||
spinnerDuration = 200 * time.Millisecond
|
||||
initialDelay = 20 * time.Millisecond
|
||||
initialDelayTac = 100 * time.Millisecond
|
||||
spinnerDuration = 200 * time.Millisecond
|
||||
previewCancelWait = 500 * time.Millisecond
|
||||
maxPatternLength = 300
|
||||
maxMulti = math.MaxInt32
|
||||
|
||||
// Matcher
|
||||
numPartitionsMultiplier = 8
|
||||
@@ -75,6 +79,7 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
exitCancel = -1
|
||||
exitOk = 0
|
||||
exitNoMatch = 1
|
||||
exitError = 2
|
||||
|
||||
75
src/core.go
75
src/core.go
@@ -63,12 +63,14 @@ func Run(opts *Options, revision string) {
|
||||
ansiProcessor := func(data []byte) (util.Chars, *[]ansiOffset) {
|
||||
return util.ToChars(data), nil
|
||||
}
|
||||
|
||||
var lineAnsiState, prevLineAnsiState *ansiState
|
||||
if opts.Ansi {
|
||||
if opts.Theme != nil {
|
||||
var state *ansiState
|
||||
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
||||
trimmed, offsets, newState := extractColor(string(data), state, nil)
|
||||
state = newState
|
||||
prevLineAnsiState = lineAnsiState
|
||||
trimmed, offsets, newState := extractColor(string(data), lineAnsiState, nil)
|
||||
lineAnsiState = newState
|
||||
return util.ToChars([]byte(trimmed)), offsets
|
||||
}
|
||||
} else {
|
||||
@@ -100,6 +102,22 @@ func Run(opts *Options, revision string) {
|
||||
} else {
|
||||
chunkList = NewChunkList(func(item *Item, data []byte) bool {
|
||||
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)
|
||||
transformed := joinTokens(trans)
|
||||
if len(header) < opts.HeaderLines {
|
||||
@@ -108,6 +126,7 @@ func Run(opts *Options, revision string) {
|
||||
return false
|
||||
}
|
||||
item.text, item.colors = ansiProcessor([]byte(transformed))
|
||||
item.text.TrimTrailingWhitespaces()
|
||||
item.text.Index = itemIndex
|
||||
item.origText = &data
|
||||
itemIndex++
|
||||
@@ -117,10 +136,11 @@ func Run(opts *Options, revision string) {
|
||||
|
||||
// Reader
|
||||
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
|
||||
var reader *Reader
|
||||
if !streamingFilter {
|
||||
reader := NewReader(func(data []byte) bool {
|
||||
reader = NewReader(func(data []byte) bool {
|
||||
return chunkList.Push(data)
|
||||
}, eventBox, opts.ReadZero)
|
||||
}, eventBox, opts.ReadZero, opts.Filter == nil)
|
||||
go reader.ReadSource()
|
||||
}
|
||||
|
||||
@@ -149,6 +169,7 @@ func Run(opts *Options, revision string) {
|
||||
}
|
||||
|
||||
pattern := patternBuilder([]rune(*opts.Filter))
|
||||
matcher.sort = pattern.sortable
|
||||
|
||||
found := false
|
||||
if streamingFilter {
|
||||
@@ -163,7 +184,7 @@ func Run(opts *Options, revision string) {
|
||||
}
|
||||
}
|
||||
return false
|
||||
}, eventBox, opts.ReadZero)
|
||||
}, eventBox, opts.ReadZero, false)
|
||||
reader.ReadSource()
|
||||
} else {
|
||||
eventBox.Unwatch(EvtReadNew)
|
||||
@@ -204,10 +225,23 @@ func Run(opts *Options, revision string) {
|
||||
// Event coordination
|
||||
reading := true
|
||||
ticks := 0
|
||||
var nextCommand *string
|
||||
restart := func(command string) {
|
||||
reading = true
|
||||
chunkList.Clear()
|
||||
header = make([]string, 0, opts.HeaderLines)
|
||||
go reader.restart(command)
|
||||
}
|
||||
eventBox.Watch(EvtReadNew)
|
||||
for {
|
||||
delay := true
|
||||
ticks++
|
||||
input := func() []rune {
|
||||
if opts.Phony {
|
||||
return []rune{}
|
||||
}
|
||||
return []rune(terminal.Input())
|
||||
}
|
||||
eventBox.Wait(func(events *util.Events) {
|
||||
if _, fin := (*events)[EvtReadFin]; fin {
|
||||
delete(*events, EvtReadNew)
|
||||
@@ -216,21 +250,38 @@ func Run(opts *Options, revision string) {
|
||||
switch evt {
|
||||
|
||||
case EvtReadNew, EvtReadFin:
|
||||
reading = reading && evt == EvtReadNew
|
||||
clearCache := false
|
||||
if evt == EvtReadFin && nextCommand != nil {
|
||||
clearCache = true
|
||||
restart(*nextCommand)
|
||||
nextCommand = nil
|
||||
} else {
|
||||
reading = reading && evt == EvtReadNew
|
||||
}
|
||||
snapshot, count := chunkList.Snapshot()
|
||||
terminal.UpdateCount(count, !reading, value.(bool))
|
||||
terminal.UpdateCount(count, !reading, value.(*string))
|
||||
if opts.Sync {
|
||||
terminal.UpdateList(PassMerger(&snapshot, opts.Tac))
|
||||
}
|
||||
matcher.Reset(snapshot, terminal.Input(), false, !reading, sort)
|
||||
matcher.Reset(snapshot, input(), false, !reading, sort, clearCache)
|
||||
|
||||
case EvtSearchNew:
|
||||
var command *string
|
||||
switch val := value.(type) {
|
||||
case bool:
|
||||
sort = val
|
||||
case searchRequest:
|
||||
sort = val.sort
|
||||
command = val.command
|
||||
}
|
||||
if command != nil {
|
||||
if reading {
|
||||
reader.terminate()
|
||||
nextCommand = command
|
||||
} else {
|
||||
restart(*command)
|
||||
}
|
||||
}
|
||||
snapshot, _ := chunkList.Snapshot()
|
||||
matcher.Reset(snapshot, terminal.Input(), true, !reading, sort)
|
||||
matcher.Reset(snapshot, input(), true, !reading, sort, command != nil)
|
||||
delay = false
|
||||
|
||||
case EvtSearchProgress:
|
||||
|
||||
@@ -59,7 +59,7 @@ func (h *History) append(line string) error {
|
||||
|
||||
lines := append(h.lines[:len(h.lines)-1], line)
|
||||
if len(lines) > h.maxSize {
|
||||
lines = lines[len(lines)-h.maxSize : len(lines)]
|
||||
lines = lines[len(lines)-h.maxSize:]
|
||||
}
|
||||
h.lines = append(lines, "")
|
||||
return ioutil.WriteFile(h.path, []byte(strings.Join(h.lines, "\n")), 0600)
|
||||
|
||||
@@ -12,10 +12,11 @@ import (
|
||||
|
||||
// MatchRequest represents a search request
|
||||
type MatchRequest struct {
|
||||
chunks []*Chunk
|
||||
pattern *Pattern
|
||||
final bool
|
||||
sort bool
|
||||
chunks []*Chunk
|
||||
pattern *Pattern
|
||||
final bool
|
||||
sort bool
|
||||
clearCache bool
|
||||
}
|
||||
|
||||
// Matcher is responsible for performing search
|
||||
@@ -69,7 +70,7 @@ func (m *Matcher) Loop() {
|
||||
events.Clear()
|
||||
})
|
||||
|
||||
if request.sort != m.sort {
|
||||
if request.sort != m.sort || request.clearCache {
|
||||
m.sort = request.sort
|
||||
m.mergerCache = make(map[string]*Merger)
|
||||
clearChunkCache()
|
||||
@@ -207,13 +208,13 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
||||
return nil, wait()
|
||||
}
|
||||
|
||||
if time.Now().Sub(startedAt) > progressMinDuration {
|
||||
if time.Since(startedAt) > progressMinDuration {
|
||||
m.eventBox.Set(EvtSearchProgress, float32(count)/float32(numChunks))
|
||||
}
|
||||
}
|
||||
|
||||
partialResults := make([][]Result, numSlices)
|
||||
for _ = range slices {
|
||||
for range slices {
|
||||
partialResult := <-resultChan
|
||||
partialResults[partialResult.index] = partialResult.matches
|
||||
}
|
||||
@@ -221,7 +222,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
||||
}
|
||||
|
||||
// Reset is called to interrupt/signal the ongoing search
|
||||
func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final bool, sort bool) {
|
||||
func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final bool, sort bool, clearCache bool) {
|
||||
pattern := m.patternBuilder(patternRunes)
|
||||
|
||||
var event util.EventType
|
||||
@@ -230,5 +231,5 @@ func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final
|
||||
} else {
|
||||
event = reqRetry
|
||||
}
|
||||
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort})
|
||||
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable, clearCache})
|
||||
}
|
||||
|
||||
109
src/options.go
109
src/options.go
@@ -33,12 +33,13 @@ const usage = `usage: fzf [options]
|
||||
-d, --delimiter=STR Field delimiter regex (default: AWK-style)
|
||||
+s, --no-sort Do not sort the result
|
||||
--tac Reverse the order of the input
|
||||
--phony Do not perform search
|
||||
--tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
|
||||
when the scores are tied [length|begin|end|index]
|
||||
(default: length)
|
||||
|
||||
Interface
|
||||
-m, --multi Enable multi-select with tab/shift-tab
|
||||
-m, --multi[=MAX] Enable multi-select with tab/shift-tab
|
||||
--no-mouse Disable mouse
|
||||
--bind=KEYBINDS Custom key bindings. Refer to the man page.
|
||||
--cycle Enable cyclic scroll
|
||||
@@ -56,7 +57,7 @@ const usage = `usage: fzf [options]
|
||||
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
|
||||
--border Draw border above and below the finder
|
||||
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
|
||||
--inline-info Display finder info inline with the query
|
||||
--info=STYLE Finder info style [default|inline|hidden]
|
||||
--prompt=STR Input prompt (default: '> ')
|
||||
--header=STR String to print as header
|
||||
--header-lines=N The first N lines of the input are treated as header
|
||||
@@ -141,12 +142,21 @@ const (
|
||||
layoutReverseList
|
||||
)
|
||||
|
||||
type infoStyle int
|
||||
|
||||
const (
|
||||
infoDefault infoStyle = iota
|
||||
infoInline
|
||||
infoHidden
|
||||
)
|
||||
|
||||
type previewOpts struct {
|
||||
command string
|
||||
position windowPosition
|
||||
size sizeSpec
|
||||
hidden bool
|
||||
wrap bool
|
||||
border bool
|
||||
}
|
||||
|
||||
// Options stores the values of command-line options
|
||||
@@ -154,6 +164,7 @@ type Options struct {
|
||||
Fuzzy bool
|
||||
FuzzyAlgo algo.Algo
|
||||
Extended bool
|
||||
Phony bool
|
||||
Case Case
|
||||
Normalize bool
|
||||
Nth []Range
|
||||
@@ -162,7 +173,7 @@ type Options struct {
|
||||
Sort int
|
||||
Tac bool
|
||||
Criteria []criterion
|
||||
Multi bool
|
||||
Multi int
|
||||
Ansi bool
|
||||
Mouse bool
|
||||
Theme *tui.ColorTheme
|
||||
@@ -175,7 +186,7 @@ type Options struct {
|
||||
Hscroll bool
|
||||
HscrollOff int
|
||||
FileWord bool
|
||||
InlineInfo bool
|
||||
InfoStyle infoStyle
|
||||
JumpLabels string
|
||||
Prompt string
|
||||
Query string
|
||||
@@ -189,12 +200,14 @@ type Options struct {
|
||||
PrintQuery bool
|
||||
ReadZero bool
|
||||
Printer func(string)
|
||||
PrintSep string
|
||||
Sync bool
|
||||
History *History
|
||||
Header []string
|
||||
HeaderLines int
|
||||
Margin [4]sizeSpec
|
||||
Bordered bool
|
||||
Unicode bool
|
||||
Tabstop int
|
||||
ClearOnExit bool
|
||||
Version bool
|
||||
@@ -205,6 +218,7 @@ func defaultOptions() *Options {
|
||||
Fuzzy: true,
|
||||
FuzzyAlgo: algo.FuzzyMatchV2,
|
||||
Extended: true,
|
||||
Phony: false,
|
||||
Case: CaseSmart,
|
||||
Normalize: true,
|
||||
Nth: make([]Range, 0),
|
||||
@@ -213,7 +227,7 @@ func defaultOptions() *Options {
|
||||
Sort: 1000,
|
||||
Tac: false,
|
||||
Criteria: []criterion{byScore, byLength},
|
||||
Multi: false,
|
||||
Multi: 0,
|
||||
Ansi: false,
|
||||
Mouse: true,
|
||||
Theme: tui.EmptyTheme(),
|
||||
@@ -225,7 +239,7 @@ func defaultOptions() *Options {
|
||||
Hscroll: true,
|
||||
HscrollOff: 10,
|
||||
FileWord: false,
|
||||
InlineInfo: false,
|
||||
InfoStyle: infoDefault,
|
||||
JumpLabels: defaultJumpLabels,
|
||||
Prompt: "> ",
|
||||
Query: "",
|
||||
@@ -235,22 +249,24 @@ func defaultOptions() *Options {
|
||||
ToggleSort: false,
|
||||
Expect: make(map[int]string),
|
||||
Keymap: make(map[int][]action),
|
||||
Preview: previewOpts{"", posRight, sizeSpec{50, true}, false, false},
|
||||
Preview: previewOpts{"", posRight, sizeSpec{50, true}, false, false, true},
|
||||
PrintQuery: false,
|
||||
ReadZero: false,
|
||||
Printer: func(str string) { fmt.Println(str) },
|
||||
PrintSep: "\n",
|
||||
Sync: false,
|
||||
History: nil,
|
||||
Header: make([]string, 0),
|
||||
HeaderLines: 0,
|
||||
Margin: defaultMargin(),
|
||||
Unicode: true,
|
||||
Tabstop: 8,
|
||||
ClearOnExit: true,
|
||||
Version: false}
|
||||
}
|
||||
|
||||
func help(code int) {
|
||||
os.Stderr.WriteString(usage)
|
||||
os.Stdout.WriteString(usage)
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
@@ -310,13 +326,14 @@ func nextInt(args []string, i *int, message string) int {
|
||||
return atoi(args[*i])
|
||||
}
|
||||
|
||||
func optionalNumeric(args []string, i *int) int {
|
||||
func optionalNumeric(args []string, i *int, defaultValue int) int {
|
||||
if len(args) > *i+1 {
|
||||
if strings.IndexAny(args[*i+1], "0123456789") == 0 {
|
||||
*i++
|
||||
return atoi(args[*i])
|
||||
}
|
||||
}
|
||||
return 1 // Don't care
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func splitNth(str string) []Range {
|
||||
@@ -381,7 +398,7 @@ func parseKeyChords(str string, message string) map[int]string {
|
||||
}
|
||||
|
||||
tokens := strings.Split(str, ",")
|
||||
if str == "," || strings.HasPrefix(str, ",,") || strings.HasSuffix(str, ",,") || strings.Index(str, ",,,") >= 0 {
|
||||
if str == "," || strings.HasPrefix(str, ",,") || strings.HasSuffix(str, ",,") || strings.Contains(str, ",,,") {
|
||||
tokens = append(tokens, ",")
|
||||
}
|
||||
|
||||
@@ -409,6 +426,14 @@ func parseKeyChords(str string, message string) map[int]string {
|
||||
chord = tui.BSpace
|
||||
case "ctrl-space":
|
||||
chord = tui.CtrlSpace
|
||||
case "ctrl-^", "ctrl-6":
|
||||
chord = tui.CtrlCaret
|
||||
case "ctrl-/", "ctrl-_":
|
||||
chord = tui.CtrlSlash
|
||||
case "ctrl-\\":
|
||||
chord = tui.CtrlBackSlash
|
||||
case "ctrl-]":
|
||||
chord = tui.CtrlRightBracket
|
||||
case "change":
|
||||
chord = tui.Change
|
||||
case "alt-enter", "alt-return":
|
||||
@@ -576,6 +601,8 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
|
||||
theme.Current = ansi
|
||||
case "bg+":
|
||||
theme.DarkBg = ansi
|
||||
case "gutter":
|
||||
theme.Gutter = ansi
|
||||
case "hl":
|
||||
theme.Match = ansi
|
||||
case "hl+":
|
||||
@@ -621,13 +648,15 @@ func init() {
|
||||
// Backreferences are not supported.
|
||||
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
||||
executeRegexp = regexp.MustCompile(
|
||||
"(?si):(execute(?:-multi|-silent)?):.+|:(execute(?:-multi|-silent)?)(\\([^)]*\\)|\\[[^\\]]*\\]|~[^~]*~|![^!]*!|@[^@]*@|\\#[^\\#]*\\#|\\$[^\\$]*\\$|%[^%]*%|\\^[^\\^]*\\^|&[^&]*&|\\*[^\\*]*\\*|;[^;]*;|/[^/]*/|\\|[^\\|]*\\|)")
|
||||
`(?si):(execute(?:-multi|-silent)?|reload):.+|:(execute(?:-multi|-silent)?|reload)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
||||
}
|
||||
|
||||
func parseKeymap(keymap map[int][]action, str string) {
|
||||
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
||||
prefix := ":execute"
|
||||
if src[len(prefix)] == '-' {
|
||||
if strings.HasPrefix(src, ":reload") {
|
||||
prefix = ":reload"
|
||||
} else if src[len(prefix)] == '-' {
|
||||
c := src[len(prefix)+1]
|
||||
if c == 's' || c == 'S' {
|
||||
prefix += "-silent"
|
||||
@@ -780,6 +809,8 @@ func parseKeymap(keymap map[int][]action, str string) {
|
||||
} else {
|
||||
var offset int
|
||||
switch t {
|
||||
case actReload:
|
||||
offset = len("reload")
|
||||
case actExecuteSilent:
|
||||
offset = len("execute-silent")
|
||||
case actExecuteMulti:
|
||||
@@ -815,6 +846,8 @@ func isExecuteAction(str string) actionType {
|
||||
prefix = matches[0][2]
|
||||
}
|
||||
switch prefix {
|
||||
case "reload":
|
||||
return actReload
|
||||
case "execute":
|
||||
return actExecute
|
||||
case "execute-silent":
|
||||
@@ -880,6 +913,20 @@ func parseLayout(str string) layoutType {
|
||||
return layoutDefault
|
||||
}
|
||||
|
||||
func parseInfoStyle(str string) infoStyle {
|
||||
switch str {
|
||||
case "default":
|
||||
return infoDefault
|
||||
case "inline":
|
||||
return infoInline
|
||||
case "hidden":
|
||||
return infoHidden
|
||||
default:
|
||||
errorExit("invalid info style (expected: default / inline / hidden)")
|
||||
}
|
||||
return infoDefault
|
||||
}
|
||||
|
||||
func parsePreviewWindow(opts *previewOpts, input string) {
|
||||
// Default
|
||||
opts.position = posRight
|
||||
@@ -891,6 +938,7 @@ func parsePreviewWindow(opts *previewOpts, input string) {
|
||||
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
|
||||
for _, token := range tokens {
|
||||
switch token {
|
||||
case "":
|
||||
case "hidden":
|
||||
opts.hidden = true
|
||||
case "wrap":
|
||||
@@ -903,6 +951,10 @@ func parsePreviewWindow(opts *previewOpts, input string) {
|
||||
opts.position = posLeft
|
||||
case "right":
|
||||
opts.position = posRight
|
||||
case "border":
|
||||
opts.border = true
|
||||
case "noborder":
|
||||
opts.border = false
|
||||
default:
|
||||
if sizeRegex.MatchString(token) {
|
||||
opts.size = parseSize(token, 99, "window size")
|
||||
@@ -1007,6 +1059,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
}
|
||||
case "--no-expect":
|
||||
opts.Expect = make(map[int]string)
|
||||
case "--no-phony":
|
||||
opts.Phony = false
|
||||
case "--phony":
|
||||
opts.Phony = true
|
||||
case "--tiebreak":
|
||||
opts.Criteria = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
|
||||
case "--bind":
|
||||
@@ -1027,7 +1083,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
case "--with-nth":
|
||||
opts.WithNth = splitNth(nextString(allArgs, &i, "nth expression required"))
|
||||
case "-s", "--sort":
|
||||
opts.Sort = optionalNumeric(allArgs, &i)
|
||||
opts.Sort = optionalNumeric(allArgs, &i, 1)
|
||||
case "+s", "--no-sort":
|
||||
opts.Sort = 0
|
||||
case "--tac":
|
||||
@@ -1039,9 +1095,9 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
case "+i":
|
||||
opts.Case = CaseRespect
|
||||
case "-m", "--multi":
|
||||
opts.Multi = true
|
||||
opts.Multi = optionalNumeric(allArgs, &i, maxMulti)
|
||||
case "+m", "--no-multi":
|
||||
opts.Multi = false
|
||||
opts.Multi = 0
|
||||
case "--ansi":
|
||||
opts.Ansi = true
|
||||
case "--no-ansi":
|
||||
@@ -1081,10 +1137,15 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
opts.FileWord = true
|
||||
case "--no-filepath-word":
|
||||
opts.FileWord = false
|
||||
case "--info":
|
||||
opts.InfoStyle = parseInfoStyle(
|
||||
nextString(allArgs, &i, "info style required"))
|
||||
case "--no-info":
|
||||
opts.InfoStyle = infoHidden
|
||||
case "--inline-info":
|
||||
opts.InlineInfo = true
|
||||
opts.InfoStyle = infoInline
|
||||
case "--no-inline-info":
|
||||
opts.InlineInfo = false
|
||||
opts.InfoStyle = infoDefault
|
||||
case "--jump-labels":
|
||||
opts.JumpLabels = nextString(allArgs, &i, "label characters required")
|
||||
validateJumpLabels = true
|
||||
@@ -1102,8 +1163,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
opts.ReadZero = false
|
||||
case "--print0":
|
||||
opts.Printer = func(str string) { fmt.Print(str, "\x00") }
|
||||
opts.PrintSep = "\x00"
|
||||
case "--no-print0":
|
||||
opts.Printer = func(str string) { fmt.Println(str) }
|
||||
opts.PrintSep = "\n"
|
||||
case "--print-query":
|
||||
opts.PrintQuery = true
|
||||
case "--no-print-query":
|
||||
@@ -1137,7 +1200,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
opts.Preview.command = ""
|
||||
case "--preview-window":
|
||||
parsePreviewWindow(&opts.Preview,
|
||||
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]][:wrap][:hidden]"))
|
||||
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]][:noborder][:wrap][:hidden]"))
|
||||
case "--height":
|
||||
opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]"))
|
||||
case "--min-height":
|
||||
@@ -1150,6 +1213,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
opts.Bordered = false
|
||||
case "--border":
|
||||
opts.Bordered = true
|
||||
case "--no-unicode":
|
||||
opts.Unicode = false
|
||||
case "--unicode":
|
||||
opts.Unicode = true
|
||||
case "--margin":
|
||||
opts.Margin = parseMargin(
|
||||
nextString(allArgs, &i, "margin required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
|
||||
@@ -1178,12 +1245,16 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
opts.WithNth = splitNth(value)
|
||||
} else if match, _ := optString(arg, "-s", "--sort="); match {
|
||||
opts.Sort = 1 // Don't care
|
||||
} else if match, value := optString(arg, "-m", "--multi="); match {
|
||||
opts.Multi = atoi(value)
|
||||
} else if match, value := optString(arg, "--height="); match {
|
||||
opts.Height = parseHeight(value)
|
||||
} else if match, value := optString(arg, "--min-height="); match {
|
||||
opts.MinHeight = atoi(value)
|
||||
} else if match, value := optString(arg, "--layout="); match {
|
||||
opts.Layout = parseLayout(value)
|
||||
} else if match, value := optString(arg, "--info="); match {
|
||||
opts.InfoStyle = parseInfoStyle(value)
|
||||
} else if match, value := optString(arg, "--toggle-sort="); match {
|
||||
parseToggleSort(opts.Keymap, value)
|
||||
} else if match, value := optString(arg, "--expect="); match {
|
||||
|
||||
@@ -52,6 +52,7 @@ type Pattern struct {
|
||||
forward bool
|
||||
text []rune
|
||||
termSets []termSet
|
||||
sortable bool
|
||||
cacheable bool
|
||||
cacheKey string
|
||||
delimiter Delimiter
|
||||
@@ -101,18 +102,27 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
||||
}
|
||||
|
||||
caseSensitive := true
|
||||
sortable := true
|
||||
termSets := []termSet{}
|
||||
|
||||
if extended {
|
||||
termSets = parseTerms(fuzzy, caseMode, normalize, asString)
|
||||
// We should not sort the result if there are only inverse search terms
|
||||
sortable = false
|
||||
Loop:
|
||||
for _, termSet := range termSets {
|
||||
for idx, term := range termSet {
|
||||
if !term.inv {
|
||||
sortable = true
|
||||
}
|
||||
// If the query contains inverse search terms or OR operators,
|
||||
// we cannot cache the search scope
|
||||
if !cacheable || idx > 0 || term.inv || fuzzy && term.typ != termFuzzy || !fuzzy && term.typ != termExact {
|
||||
cacheable = false
|
||||
break Loop
|
||||
if sortable {
|
||||
// Can't break until we see at least one non-inverse term
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,6 +144,7 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
||||
forward: forward,
|
||||
text: []rune(asString),
|
||||
termSets: termSets,
|
||||
sortable: sortable,
|
||||
cacheable: cacheable,
|
||||
nth: nth,
|
||||
delimiter: delimiter,
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"bufio"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
@@ -16,11 +18,17 @@ type Reader struct {
|
||||
eventBox *util.EventBox
|
||||
delimNil bool
|
||||
event int32
|
||||
finChan chan bool
|
||||
mutex sync.Mutex
|
||||
exec *exec.Cmd
|
||||
command *string
|
||||
killed bool
|
||||
wait bool
|
||||
}
|
||||
|
||||
// NewReader returns new Reader object
|
||||
func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, delimNil bool) *Reader {
|
||||
return &Reader{pusher, eventBox, delimNil, int32(EvtReady)}
|
||||
func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, delimNil bool, wait bool) *Reader {
|
||||
return &Reader{pusher, eventBox, delimNil, int32(EvtReady), make(chan bool, 1), sync.Mutex{}, nil, nil, false, wait}
|
||||
}
|
||||
|
||||
func (r *Reader) startEventPoller() {
|
||||
@@ -29,9 +37,12 @@ func (r *Reader) startEventPoller() {
|
||||
pollInterval := readerPollIntervalMin
|
||||
for {
|
||||
if atomic.CompareAndSwapInt32(ptr, int32(EvtReadNew), int32(EvtReady)) {
|
||||
r.eventBox.Set(EvtReadNew, true)
|
||||
r.eventBox.Set(EvtReadNew, (*string)(nil))
|
||||
pollInterval = readerPollIntervalMin
|
||||
} else if atomic.LoadInt32(ptr) == int32(EvtReadFin) {
|
||||
if r.wait {
|
||||
r.finChan <- true
|
||||
}
|
||||
return
|
||||
} else {
|
||||
pollInterval += readerPollIntervalStep
|
||||
@@ -46,7 +57,37 @@ func (r *Reader) startEventPoller() {
|
||||
|
||||
func (r *Reader) fin(success bool) {
|
||||
atomic.StoreInt32(&r.event, int32(EvtReadFin))
|
||||
r.eventBox.Set(EvtReadFin, success)
|
||||
if r.wait {
|
||||
<-r.finChan
|
||||
}
|
||||
|
||||
r.mutex.Lock()
|
||||
ret := r.command
|
||||
if success || r.killed {
|
||||
ret = nil
|
||||
}
|
||||
r.mutex.Unlock()
|
||||
|
||||
r.eventBox.Set(EvtReadFin, ret)
|
||||
}
|
||||
|
||||
func (r *Reader) terminate() {
|
||||
r.mutex.Lock()
|
||||
defer func() { r.mutex.Unlock() }()
|
||||
|
||||
r.killed = true
|
||||
if r.exec != nil && r.exec.Process != nil {
|
||||
util.KillCommand(r.exec)
|
||||
} else {
|
||||
os.Stdin.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Reader) restart(command string) {
|
||||
r.event = int32(EvtReady)
|
||||
r.startEventPoller()
|
||||
success := r.readFromCommand(nil, command)
|
||||
r.fin(success)
|
||||
}
|
||||
|
||||
// ReadSource reads data from the default command or from standard input
|
||||
@@ -54,12 +95,13 @@ func (r *Reader) ReadSource() {
|
||||
r.startEventPoller()
|
||||
var success bool
|
||||
if util.IsTty() {
|
||||
// The default command for *nix requires bash
|
||||
shell := "bash"
|
||||
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
||||
if len(cmd) == 0 {
|
||||
// The default command for *nix requires bash
|
||||
success = r.readFromCommand("bash", defaultCommand)
|
||||
success = r.readFromCommand(&shell, defaultCommand)
|
||||
} else {
|
||||
success = r.readFromCommand("sh", cmd)
|
||||
success = r.readFromCommand(nil, cmd)
|
||||
}
|
||||
} else {
|
||||
success = r.readFromStdin()
|
||||
@@ -102,16 +144,25 @@ func (r *Reader) readFromStdin() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *Reader) readFromCommand(shell string, cmd string) bool {
|
||||
listCommand := util.ExecCommandWith(shell, cmd)
|
||||
out, err := listCommand.StdoutPipe()
|
||||
func (r *Reader) readFromCommand(shell *string, command string) bool {
|
||||
r.mutex.Lock()
|
||||
r.killed = false
|
||||
r.command = &command
|
||||
if shell != nil {
|
||||
r.exec = util.ExecCommandWith(*shell, command, true)
|
||||
} else {
|
||||
r.exec = util.ExecCommand(command, true)
|
||||
}
|
||||
out, err := r.exec.StdoutPipe()
|
||||
if err != nil {
|
||||
r.mutex.Unlock()
|
||||
return false
|
||||
}
|
||||
err = listCommand.Start()
|
||||
err = r.exec.Start()
|
||||
r.mutex.Unlock()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
r.feed(out)
|
||||
return listCommand.Wait() == nil
|
||||
return r.exec.Wait() == nil
|
||||
}
|
||||
|
||||
@@ -10,10 +10,9 @@ import (
|
||||
func TestReadFromCommand(t *testing.T) {
|
||||
strs := []string{}
|
||||
eb := util.NewEventBox()
|
||||
reader := Reader{
|
||||
pusher: func(s []byte) bool { strs = append(strs, string(s)); return true },
|
||||
eventBox: eb,
|
||||
event: int32(EvtReady)}
|
||||
reader := NewReader(
|
||||
func(s []byte) bool { strs = append(strs, string(s)); return true },
|
||||
eb, false, true)
|
||||
|
||||
reader.startEventPoller()
|
||||
|
||||
@@ -23,7 +22,7 @@ func TestReadFromCommand(t *testing.T) {
|
||||
}
|
||||
|
||||
// Normal command
|
||||
reader.fin(reader.readFromCommand("sh", `echo abc && echo def`))
|
||||
reader.fin(reader.readFromCommand(nil, `echo abc && echo def`))
|
||||
if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" {
|
||||
t.Errorf("%s", strs)
|
||||
}
|
||||
@@ -48,7 +47,7 @@ func TestReadFromCommand(t *testing.T) {
|
||||
reader.startEventPoller()
|
||||
|
||||
// Failing command
|
||||
reader.fin(reader.readFromCommand("sh", `no-such-command`))
|
||||
reader.fin(reader.readFromCommand(nil, `no-such-command`))
|
||||
strs = []string{}
|
||||
if len(strs) > 0 {
|
||||
t.Errorf("%s", strs)
|
||||
|
||||
@@ -16,7 +16,6 @@ type colorOffset struct {
|
||||
offset [2]int32
|
||||
color tui.ColorPair
|
||||
attr tui.Attr
|
||||
index int32
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
|
||||
497
src/terminal.go
497
src/terminal.go
File diff suppressed because it is too large
Load Diff
@@ -30,92 +30,92 @@ func TestReplacePlaceholder(t *testing.T) {
|
||||
t.Errorf("expected: %s, actual: %s", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
printsep := "\n"
|
||||
// {}, preserve ansi
|
||||
result = replacePlaceholder("echo {}", false, Delimiter{}, false, "query", items1)
|
||||
result = replacePlaceholder("echo {}", false, Delimiter{}, printsep, false, "query", items1)
|
||||
check("echo ' foo'\\''bar \x1b[31mbaz\x1b[m'")
|
||||
|
||||
// {}, strip ansi
|
||||
result = replacePlaceholder("echo {}", true, Delimiter{}, false, "query", items1)
|
||||
result = replacePlaceholder("echo {}", true, Delimiter{}, printsep, false, "query", items1)
|
||||
check("echo ' foo'\\''bar baz'")
|
||||
|
||||
// {}, with multiple items
|
||||
result = replacePlaceholder("echo {}", true, Delimiter{}, false, "query", items2)
|
||||
result = replacePlaceholder("echo {}", true, Delimiter{}, printsep, false, "query", items2)
|
||||
check("echo 'foo'\\''bar baz'")
|
||||
|
||||
// {..}, strip leading whitespaces, preserve ansi
|
||||
result = replacePlaceholder("echo {..}", false, Delimiter{}, false, "query", items1)
|
||||
result = replacePlaceholder("echo {..}", false, Delimiter{}, printsep, false, "query", items1)
|
||||
check("echo 'foo'\\''bar \x1b[31mbaz\x1b[m'")
|
||||
|
||||
// {..}, strip leading whitespaces, strip ansi
|
||||
result = replacePlaceholder("echo {..}", true, Delimiter{}, false, "query", items1)
|
||||
result = replacePlaceholder("echo {..}", true, Delimiter{}, printsep, false, "query", items1)
|
||||
check("echo 'foo'\\''bar baz'")
|
||||
|
||||
// {q}
|
||||
result = replacePlaceholder("echo {} {q}", true, Delimiter{}, false, "query", items1)
|
||||
result = replacePlaceholder("echo {} {q}", true, Delimiter{}, printsep, false, "query", items1)
|
||||
check("echo ' foo'\\''bar baz' 'query'")
|
||||
|
||||
// {q}, multiple items
|
||||
result = replacePlaceholder("echo {+}{q}{+}", true, Delimiter{}, false, "query 'string'", items2)
|
||||
result = replacePlaceholder("echo {+}{q}{+}", true, Delimiter{}, printsep, false, "query 'string'", items2)
|
||||
check("echo 'foo'\\''bar baz' 'FOO'\\''BAR BAZ''query '\\''string'\\''''foo'\\''bar baz' 'FOO'\\''BAR BAZ'")
|
||||
|
||||
result = replacePlaceholder("echo {}{q}{}", true, Delimiter{}, false, "query 'string'", items2)
|
||||
result = replacePlaceholder("echo {}{q}{}", true, Delimiter{}, printsep, false, "query 'string'", items2)
|
||||
check("echo 'foo'\\''bar baz''query '\\''string'\\''''foo'\\''bar baz'")
|
||||
|
||||
result = replacePlaceholder("echo {1}/{2}/{2,1}/{-1}/{-2}/{}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, false, "query", items1)
|
||||
result = replacePlaceholder("echo {1}/{2}/{2,1}/{-1}/{-2}/{}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, false, "query", items1)
|
||||
check("echo 'foo'\\''bar'/'baz'/'bazfoo'\\''bar'/'baz'/'foo'\\''bar'/' foo'\\''bar baz'/'foo'\\''bar baz'/{n.t}/{}/{1}/{q}/''")
|
||||
|
||||
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, false, "query", items2)
|
||||
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, false, "query", items2)
|
||||
check("echo 'foo'\\''bar'/'baz'/'baz'/'foo'\\''bar'/'foo'\\''bar baz'/{n.t}/{}/{1}/{q}/''")
|
||||
|
||||
result = replacePlaceholder("echo {+1}/{+2}/{+-1}/{+-2}/{+..}/{n.t}/\\{}/\\{1}/\\{q}/{+3}", true, Delimiter{}, false, "query", items2)
|
||||
result = replacePlaceholder("echo {+1}/{+2}/{+-1}/{+-2}/{+..}/{n.t}/\\{}/\\{1}/\\{q}/{+3}", true, Delimiter{}, printsep, false, "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}/'' ''")
|
||||
|
||||
// forcePlus
|
||||
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{}, printsep, 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}/'' ''")
|
||||
|
||||
// Whitespace preserving flag with "'" delimiter
|
||||
result = replacePlaceholder("echo {s1}", true, Delimiter{str: &delim}, false, "query", items1)
|
||||
result = replacePlaceholder("echo {s1}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
||||
check("echo ' foo'")
|
||||
|
||||
result = replacePlaceholder("echo {s2}", true, Delimiter{str: &delim}, false, "query", items1)
|
||||
result = replacePlaceholder("echo {s2}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
||||
check("echo 'bar baz'")
|
||||
|
||||
result = replacePlaceholder("echo {s}", true, Delimiter{str: &delim}, false, "query", items1)
|
||||
result = replacePlaceholder("echo {s}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
||||
check("echo ' foo'\\''bar baz'")
|
||||
|
||||
result = replacePlaceholder("echo {s..}", true, Delimiter{str: &delim}, false, "query", items1)
|
||||
result = replacePlaceholder("echo {s..}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
||||
check("echo ' foo'\\''bar baz'")
|
||||
|
||||
// Whitespace preserving flag with regex delimiter
|
||||
regex = regexp.MustCompile("\\w+")
|
||||
regex = regexp.MustCompile(`\w+`)
|
||||
|
||||
result = replacePlaceholder("echo {s1}", true, Delimiter{regex: regex}, false, "query", items1)
|
||||
result = replacePlaceholder("echo {s1}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
||||
check("echo ' '")
|
||||
|
||||
result = replacePlaceholder("echo {s2}", true, Delimiter{regex: regex}, false, "query", items1)
|
||||
result = replacePlaceholder("echo {s2}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
||||
check("echo ''\\'''")
|
||||
|
||||
result = replacePlaceholder("echo {s3}", true, Delimiter{regex: regex}, false, "query", items1)
|
||||
result = replacePlaceholder("echo {s3}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
||||
check("echo ' '")
|
||||
|
||||
// No match
|
||||
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, false, "query", []*Item{nil, nil})
|
||||
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, nil})
|
||||
check("echo /")
|
||||
|
||||
// No match, but with selections
|
||||
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, false, "query", []*Item{nil, item1})
|
||||
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, item1})
|
||||
check("echo /' foo'\\''bar baz'")
|
||||
|
||||
// String delimiter
|
||||
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, false, "query", items1)
|
||||
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
||||
check("echo ' foo'\\''bar baz'/'foo'/'bar baz'")
|
||||
|
||||
// Regex delimiter
|
||||
regex = regexp.MustCompile("[oa]+")
|
||||
// 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}, printsep, false, "query", items1)
|
||||
check("echo ' foo'\\''bar baz'/'f'/'r b'/''\\''bar b'")
|
||||
}
|
||||
|
||||
|
||||
@@ -238,7 +238,7 @@ func Transform(tokens []Token, withNth []Range) []Token {
|
||||
for _, part := range parts {
|
||||
output.WriteString(part.ToString())
|
||||
}
|
||||
merged = util.ToChars([]byte(output.String()))
|
||||
merged = util.ToChars(output.Bytes())
|
||||
}
|
||||
|
||||
var prefixLength int32
|
||||
|
||||
@@ -73,7 +73,7 @@ func TestTransform(t *testing.T) {
|
||||
{
|
||||
ranges := splitNth("1,2,3")
|
||||
tx := Transform(tokens, ranges)
|
||||
if string(joinTokens(tx)) != "abc: def: ghi: " {
|
||||
if joinTokens(tx) != "abc: def: ghi: " {
|
||||
t.Errorf("%s", tx)
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,7 @@ func TestTransform(t *testing.T) {
|
||||
{
|
||||
ranges := splitNth("1..2,3,2..,1")
|
||||
tx := Transform(tokens, ranges)
|
||||
if string(joinTokens(tx)) != " abc: def: ghi: def: ghi: jkl abc:" ||
|
||||
if joinTokens(tx) != " abc: def: ghi: def: ghi: jkl abc:" ||
|
||||
len(tx) != 4 ||
|
||||
tx[0].text.ToString() != " abc: def:" || tx[0].prefixLength != 0 ||
|
||||
tx[1].text.ToString() != " ghi:" || tx[1].prefixLength != 12 ||
|
||||
|
||||
@@ -27,7 +27,7 @@ const (
|
||||
|
||||
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 {
|
||||
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
|
||||
@@ -154,16 +154,18 @@ func (r *LightRenderer) findOffset() (row int, col int) {
|
||||
for tries := 0; tries < offsetPollTries; tries++ {
|
||||
bytes = r.getBytesInternal(bytes, tries > 0)
|
||||
offsets := offsetRegexp.FindSubmatch(bytes)
|
||||
if len(offsets) > 2 {
|
||||
return atoi(string(offsets[1]), 0) - 1, atoi(string(offsets[2]), 0) - 1
|
||||
if len(offsets) > 3 {
|
||||
// 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
|
||||
}
|
||||
|
||||
func repeat(s string, times int) string {
|
||||
func repeat(r rune, times int) string {
|
||||
if times > 0 {
|
||||
return strings.Repeat(s, times)
|
||||
return strings.Repeat(string(r), times)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -297,6 +299,7 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
|
||||
}
|
||||
buffer = append(buffer, byte(c))
|
||||
|
||||
pc := c
|
||||
for {
|
||||
c, ok = r.getch(true)
|
||||
if !ok {
|
||||
@@ -306,9 +309,13 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
|
||||
continue
|
||||
}
|
||||
break
|
||||
} else if c == ESC && pc != c {
|
||||
retries = r.escDelay / escPollInterval
|
||||
} else {
|
||||
retries = 0
|
||||
}
|
||||
retries = 0
|
||||
buffer = append(buffer, byte(c))
|
||||
pc = c
|
||||
}
|
||||
|
||||
return buffer
|
||||
@@ -338,6 +345,14 @@ func (r *LightRenderer) GetChar() Event {
|
||||
return Event{BSpace, 0, nil}
|
||||
case 0:
|
||||
return Event{CtrlSpace, 0, nil}
|
||||
case 28:
|
||||
return Event{CtrlBackSlash, 0, nil}
|
||||
case 29:
|
||||
return Event{CtrlRightBracket, 0, nil}
|
||||
case 30:
|
||||
return Event{CtrlCaret, 0, nil}
|
||||
case 31:
|
||||
return Event{CtrlSlash, 0, nil}
|
||||
case ESC:
|
||||
ev := r.escSequence(&sz)
|
||||
// Second chance
|
||||
@@ -374,6 +389,8 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
alt = true
|
||||
}
|
||||
switch r.buffer[1] {
|
||||
case ESC:
|
||||
return Event{ESC, 0, nil}
|
||||
case 32:
|
||||
return Event{AltSpace, 0, nil}
|
||||
case 47:
|
||||
@@ -509,6 +526,9 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
if r.buffer[1] >= 'a' && r.buffer[1] <= 'z' {
|
||||
return Event{AltA + int(r.buffer[1]) - 'a', 0, nil}
|
||||
}
|
||||
if r.buffer[1] >= '0' && r.buffer[1] <= '9' {
|
||||
return Event{Alt0 + int(r.buffer[1]) - '0', 0, nil}
|
||||
}
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
|
||||
@@ -538,7 +558,7 @@ func (r *LightRenderer) mouseSequence(sz *int) Event {
|
||||
r.prevDownTime = now
|
||||
} else {
|
||||
if len(r.clickY) > 1 && r.clickY[0] == r.clickY[1] &&
|
||||
time.Now().Sub(r.prevDownTime) < doubleClickDuration {
|
||||
time.Since(r.prevDownTime) < doubleClickDuration {
|
||||
double = true
|
||||
}
|
||||
}
|
||||
@@ -667,7 +687,7 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, bord
|
||||
}
|
||||
|
||||
func (w *LightWindow) drawBorder() {
|
||||
switch w.border {
|
||||
switch w.border.shape {
|
||||
case BorderAround:
|
||||
w.drawBorderAround()
|
||||
case BorderHorizontal:
|
||||
@@ -677,32 +697,30 @@ func (w *LightWindow) drawBorder() {
|
||||
|
||||
func (w *LightWindow) drawBorderHorizontal() {
|
||||
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.CPrint(ColBorder, AttrRegular, repeat("─", w.width))
|
||||
w.CPrint(ColBorder, AttrRegular, repeat(w.border.horizontal, w.width))
|
||||
}
|
||||
|
||||
func (w *LightWindow) drawBorderAround() {
|
||||
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++ {
|
||||
w.Move(y, 0)
|
||||
w.CPrint(ColBorder, AttrRegular, "│")
|
||||
w.cprint2(colDefault, w.bg, AttrRegular, repeat(" ", w.width-2))
|
||||
w.CPrint(ColBorder, AttrRegular, "│")
|
||||
w.CPrint(ColBorder, AttrRegular, string(w.border.vertical))
|
||||
w.cprint2(colDefault, w.bg, AttrRegular, repeat(' ', w.width-2))
|
||||
w.CPrint(ColBorder, AttrRegular, string(w.border.vertical))
|
||||
}
|
||||
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) {
|
||||
w.renderer.csi(code)
|
||||
}
|
||||
|
||||
func (w *LightWindow) stderr(str string) {
|
||||
w.renderer.stderr(str)
|
||||
}
|
||||
|
||||
func (w *LightWindow) stderrInternal(str string, allowNLCR bool) {
|
||||
w.renderer.stderrInternal(str, allowNLCR)
|
||||
}
|
||||
@@ -753,7 +771,7 @@ func (w *LightWindow) MoveAndClear(y int, x int) {
|
||||
w.Move(y, x)
|
||||
// We should not delete preview window on the right
|
||||
// csi("K")
|
||||
w.Print(repeat(" ", w.width-x))
|
||||
w.Print(repeat(' ', w.width-x))
|
||||
w.Move(y, x)
|
||||
}
|
||||
|
||||
@@ -849,7 +867,7 @@ func wrapLine(input string, prefixLength int, max int, tabstop int) []wrappedLin
|
||||
width += w
|
||||
str := string(r)
|
||||
if r == '\t' {
|
||||
str = repeat(" ", w)
|
||||
str = repeat(' ', w)
|
||||
}
|
||||
if prefixLength+width <= max {
|
||||
line += str
|
||||
|
||||
@@ -61,12 +61,8 @@ func (w *TcellWindow) Refresh() {
|
||||
}
|
||||
w.lastX = 0
|
||||
w.lastY = 0
|
||||
switch w.borderStyle {
|
||||
case BorderAround:
|
||||
w.drawBorder(true)
|
||||
case BorderHorizontal:
|
||||
w.drawBorder(false)
|
||||
}
|
||||
|
||||
w.drawBorder()
|
||||
}
|
||||
|
||||
func (w *TcellWindow) FinishFill() {
|
||||
@@ -288,6 +284,12 @@ func (r *FullscreenRenderer) GetChar() Event {
|
||||
return Event{keyfn('z'), 0, nil}
|
||||
case tcell.KeyCtrlSpace:
|
||||
return Event{CtrlSpace, 0, nil}
|
||||
case tcell.KeyCtrlBackslash:
|
||||
return Event{CtrlBackSlash, 0, nil}
|
||||
case tcell.KeyCtrlRightSq:
|
||||
return Event{CtrlRightBracket, 0, nil}
|
||||
case tcell.KeyCtrlUnderscore:
|
||||
return Event{CtrlSlash, 0, nil}
|
||||
case tcell.KeyBackspace2:
|
||||
if alt {
|
||||
return Event{AltBS, 0, nil}
|
||||
@@ -382,12 +384,16 @@ func (r *FullscreenRenderer) GetChar() Event {
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
|
||||
func (r *FullscreenRenderer) Pause(bool) {
|
||||
_screen.Fini()
|
||||
func (r *FullscreenRenderer) Pause(clear bool) {
|
||||
if clear {
|
||||
_screen.Fini()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *FullscreenRenderer) Resume(bool) {
|
||||
r.initScreen()
|
||||
func (r *FullscreenRenderer) Resume(clear bool) {
|
||||
if clear {
|
||||
r.initScreen()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *FullscreenRenderer) Close() {
|
||||
@@ -566,7 +572,11 @@ func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
|
||||
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
|
||||
right := left + w.width
|
||||
top := w.top
|
||||
@@ -580,19 +590,19 @@ func (w *TcellWindow) drawBorder(around bool) {
|
||||
}
|
||||
|
||||
for x := left; x < right; x++ {
|
||||
_screen.SetContent(x, top, tcell.RuneHLine, nil, style)
|
||||
_screen.SetContent(x, bot-1, tcell.RuneHLine, nil, style)
|
||||
_screen.SetContent(x, top, w.borderStyle.horizontal, 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++ {
|
||||
_screen.SetContent(left, y, tcell.RuneVLine, nil, style)
|
||||
_screen.SetContent(right-1, y, tcell.RuneVLine, nil, style)
|
||||
_screen.SetContent(left, y, w.borderStyle.vertical, nil, style)
|
||||
_screen.SetContent(right-1, y, w.borderStyle.vertical, nil, style)
|
||||
}
|
||||
|
||||
_screen.SetContent(left, top, tcell.RuneULCorner, nil, style)
|
||||
_screen.SetContent(right-1, top, tcell.RuneURCorner, nil, style)
|
||||
_screen.SetContent(left, bot-1, tcell.RuneLLCorner, nil, style)
|
||||
_screen.SetContent(right-1, bot-1, tcell.RuneLRCorner, nil, style)
|
||||
_screen.SetContent(left, top, w.borderStyle.topLeft, nil, style)
|
||||
_screen.SetContent(right-1, top, w.borderStyle.topRight, nil, style)
|
||||
_screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style)
|
||||
_screen.SetContent(right-1, bot-1, w.borderStyle.bottomRight, nil, style)
|
||||
}
|
||||
}
|
||||
|
||||
112
src/tui/tui.go
112
src/tui/tui.go
@@ -40,6 +40,12 @@ const (
|
||||
ESC
|
||||
CtrlSpace
|
||||
|
||||
// https://apple.stackexchange.com/questions/24261/how-do-i-send-c-that-is-control-slash-to-the-terminal
|
||||
CtrlBackSlash
|
||||
CtrlRightBracket
|
||||
CtrlCaret
|
||||
CtrlSlash
|
||||
|
||||
Invalid
|
||||
Resize
|
||||
Mouse
|
||||
@@ -117,7 +123,7 @@ func (c Color) is24() bool {
|
||||
|
||||
const (
|
||||
colUndefined Color = -2
|
||||
colDefault = -1
|
||||
colDefault Color = -1
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -164,14 +170,11 @@ func (p ColorPair) Bg() Color {
|
||||
return p.bg
|
||||
}
|
||||
|
||||
func (p ColorPair) is24() bool {
|
||||
return p.fg.is24() || p.bg.is24()
|
||||
}
|
||||
|
||||
type ColorTheme struct {
|
||||
Fg Color
|
||||
Bg Color
|
||||
DarkBg Color
|
||||
Gutter Color
|
||||
Prompt Color
|
||||
Match Color
|
||||
Current Color
|
||||
@@ -200,14 +203,60 @@ type MouseEvent struct {
|
||||
Mod bool
|
||||
}
|
||||
|
||||
type BorderStyle int
|
||||
type BorderShape int
|
||||
|
||||
const (
|
||||
BorderNone BorderStyle = iota
|
||||
BorderNone BorderShape = iota
|
||||
BorderAround
|
||||
BorderHorizontal
|
||||
)
|
||||
|
||||
type BorderStyle struct {
|
||||
shape BorderShape
|
||||
horizontal rune
|
||||
vertical rune
|
||||
topLeft rune
|
||||
topRight rune
|
||||
bottomLeft rune
|
||||
bottomRight rune
|
||||
}
|
||||
|
||||
type BorderCharacter int
|
||||
|
||||
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: '+',
|
||||
}
|
||||
}
|
||||
|
||||
func MakeTransparentBorder() BorderStyle {
|
||||
return BorderStyle{
|
||||
shape: BorderAround,
|
||||
horizontal: ' ',
|
||||
vertical: ' ',
|
||||
topLeft: ' ',
|
||||
topRight: ' ',
|
||||
bottomLeft: ' ',
|
||||
bottomRight: ' '}
|
||||
}
|
||||
|
||||
type Renderer interface {
|
||||
Init()
|
||||
Pause(clear bool)
|
||||
@@ -272,17 +321,19 @@ var (
|
||||
Dark256 *ColorTheme
|
||||
Light256 *ColorTheme
|
||||
|
||||
ColNormal ColorPair
|
||||
ColPrompt ColorPair
|
||||
ColMatch ColorPair
|
||||
ColCurrent ColorPair
|
||||
ColCurrentMatch ColorPair
|
||||
ColSpinner ColorPair
|
||||
ColInfo ColorPair
|
||||
ColCursor ColorPair
|
||||
ColSelected ColorPair
|
||||
ColHeader ColorPair
|
||||
ColBorder ColorPair
|
||||
ColPrompt ColorPair
|
||||
ColNormal ColorPair
|
||||
ColMatch ColorPair
|
||||
ColCursor ColorPair
|
||||
ColSelected ColorPair
|
||||
ColCurrent ColorPair
|
||||
ColCurrentMatch ColorPair
|
||||
ColCurrentCursor ColorPair
|
||||
ColCurrentSelected ColorPair
|
||||
ColSpinner ColorPair
|
||||
ColInfo ColorPair
|
||||
ColHeader ColorPair
|
||||
ColBorder ColorPair
|
||||
)
|
||||
|
||||
func EmptyTheme() *ColorTheme {
|
||||
@@ -290,6 +341,7 @@ func EmptyTheme() *ColorTheme {
|
||||
Fg: colUndefined,
|
||||
Bg: colUndefined,
|
||||
DarkBg: colUndefined,
|
||||
Gutter: colUndefined,
|
||||
Prompt: colUndefined,
|
||||
Match: colUndefined,
|
||||
Current: colUndefined,
|
||||
@@ -312,6 +364,7 @@ func init() {
|
||||
Fg: colDefault,
|
||||
Bg: colDefault,
|
||||
DarkBg: colBlack,
|
||||
Gutter: colBlack,
|
||||
Prompt: colBlue,
|
||||
Match: colGreen,
|
||||
Current: colYellow,
|
||||
@@ -326,6 +379,7 @@ func init() {
|
||||
Fg: colDefault,
|
||||
Bg: colDefault,
|
||||
DarkBg: 236,
|
||||
Gutter: colUndefined,
|
||||
Prompt: 110,
|
||||
Match: 108,
|
||||
Current: 254,
|
||||
@@ -340,6 +394,7 @@ func init() {
|
||||
Fg: colDefault,
|
||||
Bg: colDefault,
|
||||
DarkBg: 251,
|
||||
Gutter: colUndefined,
|
||||
Prompt: 25,
|
||||
Match: 66,
|
||||
Current: 237,
|
||||
@@ -371,6 +426,7 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
|
||||
theme.Fg = o(baseTheme.Fg, theme.Fg)
|
||||
theme.Bg = o(baseTheme.Bg, theme.Bg)
|
||||
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.Match = o(baseTheme.Match, theme.Match)
|
||||
theme.Current = o(baseTheme.Current, theme.Current)
|
||||
@@ -392,27 +448,31 @@ func initPalette(theme *ColorTheme) {
|
||||
return ColorPair{fg, bg, idx}
|
||||
}
|
||||
if theme != nil {
|
||||
ColNormal = pair(theme.Fg, theme.Bg)
|
||||
ColPrompt = pair(theme.Prompt, theme.Bg)
|
||||
ColNormal = pair(theme.Fg, 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)
|
||||
ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg)
|
||||
ColCurrentCursor = pair(theme.Cursor, theme.DarkBg)
|
||||
ColCurrentSelected = pair(theme.Selected, theme.DarkBg)
|
||||
ColSpinner = pair(theme.Spinner, 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)
|
||||
ColBorder = pair(theme.Border, theme.Bg)
|
||||
} else {
|
||||
ColNormal = pair(colDefault, colDefault)
|
||||
ColPrompt = pair(colDefault, colDefault)
|
||||
ColNormal = 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)
|
||||
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)
|
||||
ColBorder = pair(colDefault, colDefault)
|
||||
}
|
||||
|
||||
@@ -142,6 +142,11 @@ func (chars *Chars) TrailingWhitespaces() int {
|
||||
return whitespaces
|
||||
}
|
||||
|
||||
func (chars *Chars) TrimTrailingWhitespaces() {
|
||||
whitespaces := chars.TrailingWhitespaces()
|
||||
chars.slice = chars.slice[0 : len(chars.slice)-whitespaces]
|
||||
}
|
||||
|
||||
func (chars *Chars) ToString() string {
|
||||
if runes := chars.optionalRunes(); runes != nil {
|
||||
return string(runes)
|
||||
@@ -169,5 +174,13 @@ func (chars *Chars) CopyRunes(dest []rune) {
|
||||
for idx, b := range chars.slice[:len(dest)] {
|
||||
dest[idx] = rune(b)
|
||||
}
|
||||
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...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,17 +9,26 @@ import (
|
||||
)
|
||||
|
||||
// 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")
|
||||
if len(shell) == 0 {
|
||||
shell = "sh"
|
||||
}
|
||||
return ExecCommandWith(shell, command)
|
||||
return ExecCommandWith(shell, command, setpgid)
|
||||
}
|
||||
|
||||
// ExecCommandWith executes the given command with the specified shell
|
||||
func ExecCommandWith(shell string, command string) *exec.Cmd {
|
||||
return exec.Command(shell, "-c", command)
|
||||
func ExecCommandWith(shell string, command string, setpgid bool) *exec.Cmd {
|
||||
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
|
||||
@@ -27,7 +36,7 @@ func IsWindows() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// SetNonBlock executes syscall.SetNonblock on file descriptor
|
||||
// SetNonblock executes syscall.SetNonblock on file descriptor
|
||||
func SetNonblock(file *os.File, nonblock bool) {
|
||||
syscall.SetNonblock(int(file.Fd()), nonblock)
|
||||
}
|
||||
|
||||
@@ -10,13 +10,15 @@ import (
|
||||
)
|
||||
|
||||
// ExecCommand executes the given command with cmd
|
||||
func ExecCommand(command string) *exec.Cmd {
|
||||
return ExecCommandWith("cmd", command)
|
||||
func ExecCommand(command string, setpgid bool) *exec.Cmd {
|
||||
return ExecCommandWith("cmd", command, setpgid)
|
||||
}
|
||||
|
||||
// ExecCommandWith executes the given command with cmd. _shell parameter is
|
||||
// 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.SysProcAttr = &syscall.SysProcAttr{
|
||||
HideWindow: false,
|
||||
@@ -26,12 +28,17 @@ func ExecCommandWith(_shell string, command string) *exec.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
|
||||
func IsWindows() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// SetNonBlock executes syscall.SetNonblock on file descriptor
|
||||
// SetNonblock executes syscall.SetNonblock on file descriptor
|
||||
func SetNonblock(file *os.File, nonblock bool) {
|
||||
syscall.SetNonblock(syscall.Handle(file.Fd()), nonblock)
|
||||
}
|
||||
|
||||
@@ -8,10 +8,13 @@ Execute (fzf#run with dir option):
|
||||
let cwd = getcwd()
|
||||
let result = fzf#run({ 'source': 'git ls-files', 'options': '--filter=vdr', 'dir': g:dir })
|
||||
AssertEqual ['fzf.vader'], result
|
||||
AssertEqual 0, haslocaldir()
|
||||
AssertEqual getcwd(), cwd
|
||||
|
||||
execute 'lcd' fnameescape(cwd)
|
||||
let result = sort(fzf#run({ 'source': 'git ls-files', 'options': '--filter e', 'dir': g:dir }))
|
||||
AssertEqual ['fzf.vader', 'test_go.rb'], result
|
||||
AssertEqual 1, haslocaldir()
|
||||
AssertEqual getcwd(), cwd
|
||||
|
||||
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
|
||||
AssertEqual cwd, getcwd()
|
||||
|
||||
Execute (fzf#run with dir option and autochdir):
|
||||
Execute (FIXME: fzf#run with dir option and autochdir):
|
||||
set acd
|
||||
let cwd = getcwd()
|
||||
call fzf#run({'source': ['/foobar'], 'sink': 'e', 'dir': '/tmp', 'options': '-1'})
|
||||
" Working directory changed due to &acd
|
||||
AssertEqual '/foobar', expand('%')
|
||||
AssertEqual '/', getcwd()
|
||||
|
||||
Execute (fzf#run with dir option and autochdir when final cwd is same as dir):
|
||||
|
||||
167
test/test_go.rb
167
test/test_go.rb
@@ -256,12 +256,12 @@ class TestGoFZF < TestBase
|
||||
|
||||
# Testing basic key bindings
|
||||
tmux.send_keys '99', 'C-a', '1', 'C-f', '3', 'C-b', 'C-h', 'C-u', 'C-e', 'C-y', 'C-k', 'Tab', 'BTab'
|
||||
tmux.until { |lines| lines[-2] == ' 856/100000' }
|
||||
lines = tmux.capture
|
||||
assert_equal '> 3910', lines[-4]
|
||||
assert_equal ' 391', lines[-3]
|
||||
assert_equal ' 856/100000', lines[-2]
|
||||
assert_equal '> 391', lines[-1]
|
||||
tmux.until do |lines|
|
||||
'> 3910' == lines[-4] &&
|
||||
' 391' == lines[-3] &&
|
||||
' 856/100000' == lines[-2] &&
|
||||
'> 391' == lines[-1]
|
||||
end
|
||||
|
||||
tmux.send_keys :Enter
|
||||
assert_equal '3910', readonce.chomp
|
||||
@@ -277,7 +277,7 @@ class TestGoFZF < TestBase
|
||||
|
||||
def test_fzf_default_command_failure
|
||||
tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', 'FZF_DEFAULT_COMMAND=false'), :Enter
|
||||
tmux.until { |lines| lines[-2].include?('FZF_DEFAULT_COMMAND failed') }
|
||||
tmux.until { |lines| lines[-2].include?('Command failed: false') }
|
||||
tmux.send_keys :Enter
|
||||
end
|
||||
|
||||
@@ -371,6 +371,65 @@ class TestGoFZF < TestBase
|
||||
assert_equal %w[3 2 5 6 8 7], readonce.split($INPUT_RECORD_SEPARATOR)
|
||||
end
|
||||
|
||||
def test_multi_max
|
||||
tmux.send_keys "seq 1 10 | #{FZF} -m 3 --bind A:select-all,T:toggle-all --preview 'echo [{+}]/{}'", :Enter
|
||||
|
||||
tmux.until { |lines| lines.item_count == 10 }
|
||||
|
||||
tmux.send_keys '1'
|
||||
tmux.until do |lines|
|
||||
lines[1].include?('[1]/1') && lines[-2].include?('2/10')
|
||||
end
|
||||
|
||||
tmux.send_keys 'A'
|
||||
tmux.until do |lines|
|
||||
lines[1].include?('[1 10]/1') && lines[-2].include?('2/10 (2/3)')
|
||||
end
|
||||
|
||||
tmux.send_keys :BSpace
|
||||
tmux.until { |lines| lines[-2].include?('10/10 (2/3)') }
|
||||
|
||||
tmux.send_keys 'T'
|
||||
tmux.until do |lines|
|
||||
lines[1].include?('[2 3 4]/1') && lines[-2].include?('10/10 (3/3)')
|
||||
end
|
||||
|
||||
%w[T A].each do |key|
|
||||
tmux.send_keys key
|
||||
tmux.until do |lines|
|
||||
lines[1].include?('[1 5 6]/1') && lines[-2].include?('10/10 (3/3)')
|
||||
end
|
||||
end
|
||||
|
||||
tmux.send_keys :BTab
|
||||
tmux.until do |lines|
|
||||
lines[1].include?('[5 6]/2') && lines[-2].include?('10/10 (2/3)')
|
||||
end
|
||||
|
||||
[:BTab, :BTab, 'A'].each do |key|
|
||||
tmux.send_keys key
|
||||
tmux.until do |lines|
|
||||
lines[1].include?('[5 6 2]/3') && lines[-2].include?('10/10 (3/3)')
|
||||
end
|
||||
end
|
||||
|
||||
tmux.send_keys '2'
|
||||
tmux.until { |lines| lines[-2].include?('1/10 (3/3)') }
|
||||
|
||||
tmux.send_keys 'T'
|
||||
tmux.until do |lines|
|
||||
lines[1].include?('[5 6]/2') && lines[-2].include?('1/10 (2/3)')
|
||||
end
|
||||
|
||||
tmux.send_keys :BSpace
|
||||
tmux.until { |lines| lines[-2].include?('10/10 (2/3)') }
|
||||
|
||||
tmux.send_keys 'A'
|
||||
tmux.until do |lines|
|
||||
lines[1].include?('[5 6 1]/1') && lines[-2].include?('10/10 (3/3)')
|
||||
end
|
||||
end
|
||||
|
||||
def test_with_nth
|
||||
[true, false].each do |multi|
|
||||
tmux.send_keys "(echo ' 1st 2nd 3rd/';
|
||||
@@ -806,16 +865,22 @@ class TestGoFZF < TestBase
|
||||
tmux.until { |lines| lines[-2].include? '(100)' }
|
||||
tmux.send_keys :Tab, :Tab
|
||||
tmux.until { |lines| lines[-2].include? '(98)' }
|
||||
tmux.send_keys '100'
|
||||
tmux.until { |lines| lines.match_count == 1 }
|
||||
tmux.send_keys 'C-d'
|
||||
tmux.until { |lines| lines[-2].include? '(97)' }
|
||||
tmux.send_keys 'C-u'
|
||||
tmux.until { |lines| lines.match_count == 100 }
|
||||
tmux.send_keys 'C-d'
|
||||
tmux.until { |lines| !lines[-2].include? '(' }
|
||||
tmux.send_keys :Tab, :Tab
|
||||
tmux.send_keys :BTab, :BTab
|
||||
tmux.until { |lines| lines[-2].include? '(2)' }
|
||||
tmux.send_keys 0
|
||||
tmux.until { |lines| lines[-2].include? '10/100' }
|
||||
tmux.send_keys 'C-a'
|
||||
tmux.until { |lines| lines[-2].include? '(12)' }
|
||||
tmux.send_keys :Enter
|
||||
assert_equal %w[2 1 10 20 30 40 50 60 70 80 90 100],
|
||||
assert_equal %w[1 2 10 20 30 40 50 60 70 80 90 100],
|
||||
readonce.split($INPUT_RECORD_SEPARATOR)
|
||||
end
|
||||
|
||||
@@ -873,7 +938,7 @@ class TestGoFZF < TestBase
|
||||
|
||||
def test_execute
|
||||
output = '/tmp/fzf-test-execute'
|
||||
opts = %[--bind \\"alt-a:execute(echo [{}] >> #{output}),alt-b:execute[echo /{}{}/ >> #{output}],C:execute:echo /{}{}{}/ >> #{output}\\"]
|
||||
opts = %[--bind \\"alt-a:execute(echo /{}/ >> #{output}),alt-b:execute[echo /{}{}/ >> #{output}],C:execute:echo /{}{}{}/ >> #{output}\\"]
|
||||
wait = ->(exp) { tmux.until { |lines| lines[-2].include? exp } }
|
||||
writelines tempname, %w[foo'bar foo"bar foo$bar]
|
||||
tmux.send_keys "cat #{tempname} | #{fzf opts}; sync", :Enter
|
||||
@@ -898,7 +963,7 @@ class TestGoFZF < TestBase
|
||||
wait['/3']
|
||||
tmux.send_keys :Enter
|
||||
readonce
|
||||
assert_equal %w[[foo'bar] [foo'bar]
|
||||
assert_equal %w[/foo'bar/ /foo'bar/
|
||||
/foo"barfoo"bar/ /foo"barfoo"bar/
|
||||
/foo$barfoo$barfoo$bar/],
|
||||
File.readlines(output).map(&:chomp)
|
||||
@@ -1371,7 +1436,7 @@ class TestGoFZF < TestBase
|
||||
end
|
||||
|
||||
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.send_keys '?'
|
||||
tmux.until { |lines| lines[-2] =~ / {1-1-1-[0-9]+}/ }
|
||||
@@ -1400,21 +1465,30 @@ class TestGoFZF < TestBase
|
||||
|
||||
def test_preview_flags
|
||||
tmux.send_keys %(seq 10 | sed 's/^/:: /; s/$/ /' |
|
||||
#{FZF} --multi --preview 'echo {{2}/{s2}/{+2}/{+s2}/{q}}'), :Enter
|
||||
tmux.until { |lines| lines[1].include?('{1/1 /1/1 /}') }
|
||||
#{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.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}') }
|
||||
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}') }
|
||||
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}') }
|
||||
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}') }
|
||||
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}') }
|
||||
tmux.until { |lines| lines[1].include?('{//1 10/1 10 /123//0 9}') }
|
||||
end
|
||||
|
||||
def test_preview_file
|
||||
tmux.send_keys %[(echo foo bar; echo bar foo) | #{FZF} --multi --preview 'cat {+f} {+f2} {+nf} {+fn}' --print0], :Enter
|
||||
tmux.until { |lines| lines[1].include?('foo barbar00') }
|
||||
tmux.send_keys :BTab
|
||||
tmux.until { |lines| lines[1].include?('foo barbar00') }
|
||||
tmux.send_keys :BTab
|
||||
tmux.until { |lines| lines[1].include?('foo barbar foobarfoo0101') }
|
||||
end
|
||||
|
||||
def test_preview_q_no_match
|
||||
@@ -1427,6 +1501,12 @@ class TestGoFZF < TestBase
|
||||
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
|
||||
tmux.send_keys "seq 10 | fzf --no-clear --inline-info --height 5 > #{tempname}", :Enter
|
||||
prompt = '> < 10/10'
|
||||
@@ -1436,6 +1516,11 @@ class TestGoFZF < TestBase
|
||||
tmux.until { |lines| lines[-1] == prompt }
|
||||
end
|
||||
|
||||
def test_info_hidden
|
||||
tmux.send_keys 'seq 10 | fzf --info=hidden', :Enter
|
||||
tmux.until { |lines| lines[-2] == '> 1' }
|
||||
end
|
||||
|
||||
def test_change_top
|
||||
tmux.send_keys %(seq 1000 | #{FZF} --bind change:top), :Enter
|
||||
tmux.until { |lines| lines.match_count == 1000 }
|
||||
@@ -1513,6 +1598,48 @@ class TestGoFZF < TestBase
|
||||
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
|
||||
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
|
||||
|
||||
def test_phony
|
||||
tmux.send_keys %(seq 1000 | #{FZF} --query 333 --phony --preview 'echo {} {q}'), :Enter
|
||||
tmux.until { |lines| lines.match_count == 1000 }
|
||||
tmux.until { |lines| lines[1].include?('1 333') }
|
||||
tmux.send_keys 'foo'
|
||||
tmux.until { |lines| lines.match_count == 1000 }
|
||||
tmux.until { |lines| lines[1].include?('1 333foo') }
|
||||
end
|
||||
|
||||
def test_reload
|
||||
tmux.send_keys %(seq 1000 | #{FZF} --bind 'change:reload(seq {q}),a:reload(seq 100),b:reload:seq 200' --header-lines 2 --multi 2), :Enter
|
||||
tmux.until { |lines| lines.match_count == 998 }
|
||||
tmux.send_keys 'a'
|
||||
tmux.until { |lines| lines.item_count == 98 && lines.match_count == 98 }
|
||||
tmux.send_keys 'b'
|
||||
tmux.until { |lines| lines.item_count == 198 && lines.match_count == 198 }
|
||||
tmux.send_keys :Tab
|
||||
tmux.until { |lines| lines[-2].include?('(1/2)') }
|
||||
tmux.send_keys '555'
|
||||
tmux.until { |lines| lines.item_count == 553 && lines.match_count == 1 }
|
||||
tmux.until { |lines| !lines[-2].include?('(1/2)') }
|
||||
end
|
||||
end
|
||||
|
||||
module TestShell
|
||||
|
||||
Reference in New Issue
Block a user