mirror of
https://github.com/junegunn/fzf.git
synced 2025-08-15 20:23:50 -07:00
Compare commits
205 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
30577b0c17 | ||
|
212de25409 | ||
|
5da8bbf45a | ||
|
aa0e10ead7 | ||
|
a9906c7c29 | ||
|
9fefe08b3f | ||
|
684bfff713 | ||
|
3db6b88d82 | ||
|
8ae96774df | ||
|
f68017d21e | ||
|
2b725a4db5 | ||
|
af1a5f130b | ||
|
86e3994e87 | ||
|
1e6ac5590e | ||
|
5e42b1c9f8 | ||
|
9d842630c9 | ||
|
77cb906dfe | ||
|
a59e846f74 | ||
|
6e6340a0c9 | ||
|
357e82e51b | ||
|
394d8cfd18 | ||
|
ef80bd401f | ||
|
f51d61d57a | ||
|
1dd256a68a | ||
|
85644aa3fb | ||
|
effbc258bb | ||
|
e615600ff1 | ||
|
60465c4664 | ||
|
c03c058bd5 | ||
|
7238c8944d | ||
|
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 | ||
|
f0fe79dd3b | ||
|
daa1958f86 | ||
|
2c26f02f5c | ||
|
af87650bc4 | ||
|
2b19c0bc68 | ||
|
76a2dcb5a9 | ||
|
68ec3d1c10 | ||
|
2ff19084ca | ||
|
62f062ecfa | ||
|
cce17ad0a0 | ||
|
b8296a91b9 | ||
|
6e9452b06e | ||
|
888fd35689 | ||
|
1fb0fbca58 | ||
|
ddd2a109e4 | ||
|
87504a528e | ||
|
6eac4af7db | ||
|
89de1340af | ||
|
9e753a0d44 | ||
|
f57920ad90 | ||
|
7dbbbef51a | ||
|
7add75126d | ||
|
d207672bd5 | ||
|
851fa38251 | ||
|
43345fb642 | ||
|
9ff33814ea | ||
|
21b94d2de5 | ||
|
24236860c8 | ||
|
3f868fd792 | ||
|
417bca03df | ||
|
cce6aef557 | ||
|
eb3afc03b5 | ||
|
7f0caf0683 | ||
|
7f606665cb | ||
|
202872c2dc | ||
|
93aeae1985 | ||
|
5c34ab6692 | ||
|
390b49653b | ||
|
b877c385f0 | ||
|
9c47739c0e | ||
|
04aa2992e7 | ||
|
2f1edeff78 | ||
|
306d51cdcf | ||
|
54a026525a | ||
|
d6588fc835 | ||
|
5a7b41a2cf | ||
|
338a73d764 | ||
|
c20954f020 | ||
|
1e8e1d3c9d | ||
|
f6b1962056 | ||
|
b3b101a89c | ||
|
9615c4edf1 | ||
|
85a75ee035 | ||
|
1e5bd55672 | ||
|
37d4015d56 | ||
|
6b27554cdb | ||
|
fc1b119159 | ||
|
2cd0d4a9f7 | ||
|
fd03aabeb2 | ||
|
8068c975c2 | ||
|
a6d2ab3360 | ||
|
fe7b91dfd9 | ||
|
5784101bea | ||
|
eaf6eb8978 | ||
|
3af63bcf1f | ||
|
80a21f7a75 |
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 is set to screen or screen-256color
|
||||
- For more Vim stuff, check out https://github.com/junegunn/fzf.vim
|
||||
|
||||
Describe your problem or suggestion from here ...
|
||||
-->
|
||||
|
||||
## Problem / Steps to reproduce
|
||||
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
bin/fzf
|
||||
bin/fzf.exe
|
||||
target
|
||||
pkg
|
||||
Gemfile.lock
|
||||
@@ -6,3 +7,4 @@ Gemfile.lock
|
||||
doc/tags
|
||||
vendor
|
||||
gopath
|
||||
*.zwc
|
||||
|
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
|
||||
|
149
CHANGELOG.md
149
CHANGELOG.md
@@ -1,6 +1,155 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
0.20.0
|
||||
------
|
||||
- Customizable preview window color (`preview-fg` and `preview-bg` for `--color`)
|
||||
```sh
|
||||
fzf --preview 'cat {}' \
|
||||
--color 'fg:#bbccdd,fg+:#ddeeff,bg:#334455,preview-bg:#223344,border:#778899' \
|
||||
--border --height 20 --layout reverse --info inline
|
||||
```
|
||||
- Removed the immediate flicking of the screen on `reload` action.
|
||||
```sh
|
||||
: | fzf --bind 'change:reload:seq {q}' --phony
|
||||
```
|
||||
- Added `clear-query` and `clear-selection` actions for `--bind`
|
||||
- It is now possible to split a composite bind action over multiple `--bind`
|
||||
expressions by prefixing the later ones with `+`.
|
||||
```sh
|
||||
fzf --bind 'ctrl-a:up+up'
|
||||
|
||||
# Can be now written as
|
||||
fzf --bind 'ctrl-a:up' --bind 'ctrl-a:+up'
|
||||
|
||||
# This is useful when you need to write special execute/reload form (i.e. `execute:...`)
|
||||
# to avoid parse errors and add more actions to the same key
|
||||
fzf --multi --bind 'ctrl-l:select-all+execute:less {+f}' --bind 'ctrl-l:+deselect-all'
|
||||
```
|
||||
- Fixed parse error of `--bind` expression where concatenated execute/reload
|
||||
action contains `+` character.
|
||||
```sh
|
||||
fzf --multi --bind 'ctrl-l:select-all+execute(less {+f})+deselect-all'
|
||||
```
|
||||
- Fixed bugs of reload action
|
||||
- Not triggered when there's no match even when the command doesn't have
|
||||
any placeholder expressions
|
||||
- Screen not properly cleared when `--header-lines` not filled on reload
|
||||
|
||||
0.19.0
|
||||
------
|
||||
|
||||
- Added `--phony` option which completely disables search functionality.
|
||||
Useful when you want to use fzf only as a selector interface. See below.
|
||||
- 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' || true" \
|
||||
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
|
||||
------
|
||||
|
||||
- Added `--layout` option with a new layout called `reverse-list`.
|
||||
- `--layout=reverse` is a synonym for `--reverse`
|
||||
- `--layout=default` is a synonym for `--no-reverse`
|
||||
- Preview window will be updated even when there is no match for the query
|
||||
if any of the placeholder expressions (e.g. `{q}`, `{+}`) evaluates to
|
||||
a non-empty string.
|
||||
- More keys for binding: `shift-{up,down}`, `alt-{up,down,left,right}`
|
||||
- fzf can now start even when `/dev/tty` is not available by making an
|
||||
educated guess.
|
||||
- Updated the default command for Windows.
|
||||
- Fixes and improvements on bash/zsh completion
|
||||
- install and uninstall scripts now supports generating files under
|
||||
`XDG_CONFIG_HOME` on `--xdg` flag.
|
||||
|
||||
See https://github.com/junegunn/fzf/milestone/12?closed=1 for the full list of
|
||||
changes.
|
||||
|
||||
0.17.3
|
||||
------
|
||||
- `$LINES` and `$COLUMNS` are exported to preview command so that the command
|
||||
knows the exact size of the preview window.
|
||||
- Better error messages when the default command or `$FZF_DEFAULT_COMMAND`
|
||||
fails.
|
||||
- Reverted #1061 to avoid having duplicate entries in the list when find
|
||||
command detected a file system loop (#1120). The default command now
|
||||
requires that find supports `-fstype` option.
|
||||
- fzf now distinguishes mouse left click and right click (#1130)
|
||||
- Right click is now bound to `toggle` action by default
|
||||
- `--bind` understands `left-click` and `right-click`
|
||||
- Added `replace-query` action (#1137)
|
||||
- Replaces query string with the current selection
|
||||
- Added `accept-non-empty` action (#1162)
|
||||
- Same as accept, except that it prevents fzf from exiting without any
|
||||
selection
|
||||
|
||||
0.17.1
|
||||
------
|
||||
|
||||
|
11
Dockerfile
Normal file
11
Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
||||
FROM archlinux/base:latest
|
||||
RUN pacman -Sy && pacman --noconfirm -S awk git tmux zsh fish ruby procps go make
|
||||
RUN gem install --no-document minitest
|
||||
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
||||
RUN echo '. ~/.bashrc' >> ~/.bash_profile
|
||||
|
||||
# Do not set default PS1
|
||||
RUN rm -f /etc/bash.bashrc
|
||||
COPY . /fzf
|
||||
RUN cd /fzf && make install && ./install --all
|
||||
CMD tmux new 'set -o pipefail; ruby /fzf/test/test_go.rb | tee out && touch ok' && cat out && [ -e ok ]
|
124
Makefile
124
Makefile
@@ -1,41 +1,28 @@
|
||||
ifndef GOOS
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
GOOS := darwin
|
||||
else ifeq ($(UNAME_S),Linux)
|
||||
GOOS := linux
|
||||
else
|
||||
$(error "$$GOOS is not defined.")
|
||||
endif
|
||||
endif
|
||||
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)
|
||||
@@ -53,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
|
||||
@@ -68,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)
|
||||
@@ -90,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 \
|
||||
@@ -111,28 +94,43 @@ 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
|
||||
|
||||
.PHONY: all release release-all test install clean
|
||||
docker:
|
||||
docker build -t fzf-arch .
|
||||
docker run -it fzf-arch tmux
|
||||
|
||||
docker-test:
|
||||
docker build -t fzf-arch .
|
||||
docker run -it fzf-arch
|
||||
|
||||
update:
|
||||
$(GO) get -u
|
||||
$(GO) mod tidy
|
||||
|
||||
.PHONY: all release release-all test install clean docker docker-test update
|
||||
|
258
README-VIM.md
258
README-VIM.md
@@ -1,16 +1,33 @@
|
||||
FZF Vim integration
|
||||
===================
|
||||
|
||||
This repository only enables basic integration with Vim. If you're looking for
|
||||
more, check out [fzf.vim](https://github.com/junegunn/fzf.vim) project.
|
||||
Summary
|
||||
-------
|
||||
|
||||
(Note: To use fzf in GVim, an external terminal emulator is required.)
|
||||
The Vim plugin of fzf provides two core functions, and `:FZF` command which is
|
||||
the basic file selector command built on top of them.
|
||||
|
||||
1. **`fzf#run([spec dict])`**
|
||||
- Starts fzf inside Vim with the given spec
|
||||
- `:call fzf#run({'source': 'ls'})`
|
||||
2. **`fzf#wrap([spec dict]) -> (dict)`**
|
||||
- Takes a spec for `fzf#run` and returns an extended version of it with
|
||||
additional options for addressing global preferences (`g:fzf_xxx`)
|
||||
- `:echo fzf#wrap({'source': 'ls'})`
|
||||
- We usually *wrap* a spec with `fzf#wrap` before passing it to `fzf#run`
|
||||
- `:call fzf#run(fzf#wrap({'source': 'ls'}))`
|
||||
3. **`:FZF [fzf_options string] [path string]`**
|
||||
- Basic fuzzy file selector
|
||||
- A reference implementation for those who don't want to write VimScript
|
||||
to implement custom commands
|
||||
- If you're looking for more such commands, check out [fzf.vim](https://github.com/junegunn/fzf.vim) project.
|
||||
|
||||
The most important of all is `fzf#run`, but it would be easier to understand
|
||||
the whole if we start off with `:FZF` command.
|
||||
|
||||
`:FZF[!]`
|
||||
---------
|
||||
|
||||
If you have set up fzf for Vim, `:FZF` command will be added.
|
||||
|
||||
```vim
|
||||
" Look for files under current directory
|
||||
:FZF
|
||||
@@ -18,8 +35,8 @@ If you have set up fzf for Vim, `:FZF` command will be added.
|
||||
" Look for files under your home directory
|
||||
:FZF ~
|
||||
|
||||
" With options
|
||||
:FZF --no-sort --reverse --inline-info /tmp
|
||||
" With fzf command-line options
|
||||
:FZF --reverse --info=inline /tmp
|
||||
|
||||
" Bang version starts fzf in fullscreen mode
|
||||
:FZF!
|
||||
@@ -42,9 +59,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,9 +89,10 @@ 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
|
||||
" - fzf#wrap translates this to a set of `--color` options
|
||||
let g:fzf_colors =
|
||||
\ { 'fg': ['fg', 'Normal'],
|
||||
\ 'bg': ['bg', 'Normal'],
|
||||
@@ -93,36 +108,85 @@ let g:fzf_colors =
|
||||
\ 'spinner': ['fg', 'Label'],
|
||||
\ 'header': ['fg', 'Comment'] }
|
||||
|
||||
" Enable per-command history.
|
||||
" CTRL-N and CTRL-P will be automatically bound to next-history and
|
||||
" previous-history instead of down and up. If you don't like the change,
|
||||
" explicitly bind the keys to down and up in your $FZF_DEFAULT_OPTS.
|
||||
" Enable per-command history
|
||||
" - History files will be stored in the specified directory
|
||||
" - When set, CTRL-N and CTRL-P will be bound to 'next-history' and
|
||||
" 'previous-history' instead of 'down' and 'up'.
|
||||
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.
|
||||
`fzf#run()` function is the core of Vim integration. It takes a single
|
||||
dictionary argument, *a spec*, and starts fzf process accordingly. At the very
|
||||
least, specify `sink` option to tell what it should do with the selected
|
||||
entry.
|
||||
|
||||
| Option name | Type | Description |
|
||||
| -------------------------- | ------------- | ---------------------------------------------------------------- |
|
||||
| `source` | string | External command to generate input to fzf (e.g. `find .`) |
|
||||
| `source` | list | Vim list as input to fzf |
|
||||
| `sink` | string | Vim command to handle the selected item (e.g. `e`, `tabe`) |
|
||||
| `sink` | funcref | Reference to function to process each selected item |
|
||||
| `sink*` | funcref | Similar to `sink`, but takes the list of output lines at once |
|
||||
| `options` | string/list | Options to fzf |
|
||||
| `dir` | string | Working directory |
|
||||
| `up`/`down`/`left`/`right` | number/string | Use tmux pane with the given size (e.g. `20`, `50%`) |
|
||||
| `window` (Vim 8 / Neovim) | string | Command to open fzf window (e.g. `vertical aboveleft 30new`) |
|
||||
| `launcher` | string | External terminal emulator to start fzf with (GVim only) |
|
||||
| `launcher` | funcref | Function for generating `launcher` string (GVim only) |
|
||||
```vim
|
||||
call fzf#run({'sink': 'e'})
|
||||
```
|
||||
|
||||
We haven't specified the `source`, so this is equivalent to starting fzf on
|
||||
command line without standard input pipe; 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 the sink, `: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'})
|
||||
```
|
||||
|
||||
Instead of using the default find command, you can use any shell command as
|
||||
the source. The following example will list the files managed by git. It's
|
||||
equivalent to running `git ls-files | fzf` on shell.
|
||||
|
||||
```vim
|
||||
call fzf#run({'source': 'git ls-files', 'sink': 'e'})
|
||||
```
|
||||
|
||||
fzf options can be specified as `options` entry in spec dictionary.
|
||||
|
||||
```vim
|
||||
call fzf#run({'sink': 'tabedit', 'options': '--multi --reverse'})
|
||||
```
|
||||
|
||||
You can also 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', 'left': '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 next example, we pass the names of color
|
||||
schemes as the source to implement a color scheme selector.
|
||||
|
||||
```vim
|
||||
call fzf#run({'source': map(split(globpath(&rtp, 'colors/*.vim')),
|
||||
\ 'fnamemodify(v:val, ":t:r")'),
|
||||
\ 'sink': 'colo', 'left': '25%'})
|
||||
```
|
||||
|
||||
The following table summarizes the available options.
|
||||
|
||||
| Option name | Type | Description |
|
||||
| -------------------------- | ------------- | ---------------------------------------------------------------- |
|
||||
| `source` | string | External command to generate input to fzf (e.g. `find .`) |
|
||||
| `source` | list | Vim list as input to fzf |
|
||||
| `sink` | string | Vim command to handle the selected item (e.g. `e`, `tabe`) |
|
||||
| `sink` | funcref | Reference to function to process each selected item |
|
||||
| `sink*` | funcref | Similar to `sink`, but takes the list of output lines at once |
|
||||
| `options` | string/list | Options to fzf |
|
||||
| `dir` | string | Working directory |
|
||||
| `up`/`down`/`left`/`right` | number/string | (Layout) Window position and size (e.g. `20`, `50%`) |
|
||||
| `window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new`) |
|
||||
|
||||
`options` entry can be either a string or a list. For simple cases, string
|
||||
should suffice, but prefer to use list type if you're concerned about escaping
|
||||
issues on different platforms.
|
||||
should suffice, but prefer to use list type to avoid escaping issues.
|
||||
|
||||
```vim
|
||||
call fzf#run({'options': '--reverse --prompt "C:\\Program Files\\"'})
|
||||
@@ -132,43 +196,131 @@ 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`.
|
||||
We have seen that several aspects of `:FZF` command can be configured with
|
||||
a set of global option variables; different ways to open files
|
||||
(`g:fzf_action`), window position and size (`g:fzf_layout`), color palette
|
||||
(`g:fzf_colors`), etc.
|
||||
|
||||
So how can we make our custom `fzf#run` calls also respect those variables?
|
||||
Simply by *"wrapping"* the spec dictionary with `fzf#wrap` before passing it
|
||||
to `fzf#run`.
|
||||
|
||||
- **`fzf#wrap([name string], [spec dict], [fullscreen bool]) -> (dict)`**
|
||||
- All arguments are optional. Usually we only need to pass a spec dictionary.
|
||||
- `name` is for managing history files. It is ignored if
|
||||
`g:fzf_history_dir` is not defined.
|
||||
- `fullscreen` can be either `0` or `1` (default: 0).
|
||||
|
||||
`fzf#wrap` takes a spec and returns an extended version of it (also
|
||||
a dictionary) with additional options for addressing global preferences. You
|
||||
can examine the return value of it like so:
|
||||
|
||||
```vim
|
||||
command! -bang MyStuff
|
||||
\ call fzf#run(fzf#wrap('my-stuff', {'dir': '~/my-stuff'}, <bang>0))
|
||||
echo fzf#wrap({'source': 'ls'})
|
||||
```
|
||||
|
||||
GVim
|
||||
After we *"wrap"* our spec, we pass it to `fzf#run`.
|
||||
|
||||
```vim
|
||||
call fzf#run(fzf#wrap({'source': 'ls'}))
|
||||
```
|
||||
|
||||
Now it supports `CTRL-T`, `CTRL-V`, and `CTRL-X` key bindings and it opens fzf
|
||||
window according to `g:fzf_layout` setting.
|
||||
|
||||
To make it easier to use, let's define `LS` command.
|
||||
|
||||
```vim
|
||||
command! LS call fzf#run(fzf#wrap({'source': 'ls'}))
|
||||
```
|
||||
|
||||
Type `:LS` and see how it works.
|
||||
|
||||
We would like to make `:LS!` (bang version) open fzf in fullscreen, just like
|
||||
`:FZF!`. Add `-bang` to command definition, and use `<bang>` value to set
|
||||
the last `fullscreen` argument of `fzf#wrap` (see `:help <bang>`).
|
||||
|
||||
```vim
|
||||
" On :LS!, <bang> evaluates to '!', and '!0' becomes 1
|
||||
command! -bang LS call fzf#run(fzf#wrap({'source': 'ls'}, <bang>0))
|
||||
```
|
||||
|
||||
Our `:LS` command will be much more useful if we can pass a directory argument
|
||||
to it, so that something like `:LS /tmp` is possible.
|
||||
|
||||
```vim
|
||||
command! -bang -complete=dir -nargs=* LS
|
||||
\ call fzf#run(fzf#wrap({'source': 'ls', 'dir': <q-args>}, <bang>0))
|
||||
```
|
||||
|
||||
Lastly, if you have enabled `g:fzf_history_dir`, you might want to assign
|
||||
a unique name to our command and pass it as the first argument to `fzf#wrap`.
|
||||
|
||||
```vim
|
||||
" The query history for this command will be stored as 'ls' inside g:fzf_history_dir.
|
||||
" The name is ignored if g:fzf_history_dir is not defined.
|
||||
command! -bang -complete=dir -nargs=* LS
|
||||
\ call fzf#run(fzf#wrap('ls', {'source': 'ls', 'dir': <q-args>}, <bang>0))
|
||||
```
|
||||
|
||||
Tips
|
||||
----
|
||||
|
||||
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.
|
||||
### fzf inside terminal buffer
|
||||
|
||||
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`.
|
||||
The latest versions of Vim and Neovim include builtin terminal emulator
|
||||
(`:terminal`) and fzf will start in a terminal buffer in the following cases:
|
||||
|
||||
- On Neovim
|
||||
- On GVim
|
||||
- On Terminal Vim with a non-default layout
|
||||
- `call fzf#run({'left': '30%'})` or `let g:fzf_layout = {'left': '30%'}`
|
||||
|
||||
#### Starting fzf in Neovim floating window
|
||||
|
||||
```vim
|
||||
" This is the default. %s is replaced with fzf command
|
||||
let g:fzf_launcher = 'xterm -e bash -ic %s'
|
||||
" Using floating windows of Neovim to start fzf
|
||||
if has('nvim')
|
||||
let $FZF_DEFAULT_OPTS .= ' --border --margin=0,2'
|
||||
|
||||
function! FloatingFZF()
|
||||
let width = float2nr(&columns * 0.9)
|
||||
let height = float2nr(&lines * 0.6)
|
||||
let opts = { 'relative': 'editor',
|
||||
\ 'row': (&lines - height) / 2,
|
||||
\ 'col': (&columns - width) / 2,
|
||||
\ 'width': width,
|
||||
\ 'height': height }
|
||||
|
||||
let win = nvim_open_win(nvim_create_buf(v:false, v:true), v:true, opts)
|
||||
call setwinvar(win, '&winhighlight', 'NormalFloat:Normal')
|
||||
endfunction
|
||||
|
||||
let g:fzf_layout = { 'window': 'call FloatingFZF()' }
|
||||
endif
|
||||
|
||||
" 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.
|
||||
#### Hide statusline
|
||||
|
||||
[macvim-iterm2]: https://github.com/junegunn/fzf/wiki/On-MacVim-with-iTerm2
|
||||
When fzf starts in a terminal buffer, the file type of the buffer is set to
|
||||
`fzf`. So you can set up `FileType fzf` autocmd to customize the settings of
|
||||
the window.
|
||||
|
||||
For example, if you use the default layout (`{'down': '~40%'}`) on Neovim, you
|
||||
might want to temporarily disable the statusline for a cleaner look.
|
||||
|
||||
```vim
|
||||
if has('nvim') && !exists('g:fzf_layout')
|
||||
autocmd! FileType fzf
|
||||
autocmd FileType fzf set laststatus=0 noshowmode noruler
|
||||
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
|
||||
endif
|
||||
```
|
||||
|
||||
[License](LICENSE)
|
||||
------------------
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Junegunn Choi
|
||||
Copyright (c) 2019 Junegunn Choi
|
||||
|
303
README.md
303
README.md
@@ -1,4 +1,4 @@
|
||||
<img src="https://raw.githubusercontent.com/junegunn/i/master/fzf.png" height="170" alt="fzf - a command-line fuzzy finder"> [](https://travis-ci.org/junegunn/fzf) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=EKYAW9PGKPD2N)
|
||||
<img src="https://raw.githubusercontent.com/junegunn/i/master/fzf.png" height="170" alt="fzf - a command-line fuzzy finder"> [](https://travis-ci.org/junegunn/fzf)
|
||||
===
|
||||
|
||||
fzf is a general-purpose command-line fuzzy finder.
|
||||
@@ -23,10 +23,11 @@ Table of Contents
|
||||
-----------------
|
||||
|
||||
* [Installation](#installation)
|
||||
* [Using git](#using-git)
|
||||
* [Using Homebrew or Linuxbrew](#using-homebrew-or-linuxbrew)
|
||||
* [As Vim plugin](#as-vim-plugin)
|
||||
* [Using git](#using-git)
|
||||
* [Using Linux package managers](#using-linux-package-managers)
|
||||
* [Windows](#windows)
|
||||
* [As Vim plugin](#as-vim-plugin)
|
||||
* [Upgrading fzf](#upgrading-fzf)
|
||||
* [Building fzf](#building-fzf)
|
||||
* [Usage](#usage)
|
||||
@@ -35,6 +36,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)
|
||||
@@ -51,9 +53,9 @@ Table of Contents
|
||||
* [Executing external programs](#executing-external-programs)
|
||||
* [Preview window](#preview-window)
|
||||
* [Tips](#tips)
|
||||
* [Respecting .gitignore, <code>.hgignore</code>, and <code>svn:ignore</code>](#respecting-gitignore-hgignore-and-svnignore)
|
||||
* [git ls-tree for fast traversal](#git-ls-tree-for-fast-traversal)
|
||||
* [Respecting .gitignore](#respecting-gitignore)
|
||||
* [Fish shell](#fish-shell)
|
||||
* [Related projects](#related-projects)
|
||||
* [<a href="LICENSE">License</a>](#license)
|
||||
|
||||
Installation
|
||||
@@ -73,20 +75,10 @@ stuff.
|
||||
|
||||
[bin]: https://github.com/junegunn/fzf-bin/releases
|
||||
|
||||
### Using git
|
||||
|
||||
Clone this repository and run
|
||||
[install](https://github.com/junegunn/fzf/blob/master/install) script.
|
||||
|
||||
```sh
|
||||
git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
|
||||
~/.fzf/install
|
||||
```
|
||||
|
||||
### Using Homebrew or Linuxbrew
|
||||
|
||||
Alternatively, you can use [Homebrew](http://brew.sh/) or
|
||||
[Linuxbrew](http://linuxbrew.sh/) to install fzf.
|
||||
You can use [Homebrew](http://brew.sh/) or [Linuxbrew](http://linuxbrew.sh/)
|
||||
to install fzf.
|
||||
|
||||
```sh
|
||||
brew install fzf
|
||||
@@ -95,43 +87,89 @@ brew install fzf
|
||||
$(brew --prefix)/opt/fzf/install
|
||||
```
|
||||
|
||||
### As Vim plugin
|
||||
fzf is also available [via MacPorts][portfile]: `sudo port install fzf`
|
||||
|
||||
You can manually add the directory to `&runtimepath` as follows,
|
||||
[portfile]: https://github.com/macports/macports-ports/blob/master/sysutils/fzf/Portfile
|
||||
|
||||
```vim
|
||||
" If installed using git
|
||||
set rtp+=~/.fzf
|
||||
### Using git
|
||||
|
||||
" If installed using Homebrew
|
||||
set rtp+=/usr/local/opt/fzf
|
||||
Alternatively, you can "git clone" this repository to any directory and run
|
||||
[install](https://github.com/junegunn/fzf/blob/master/install) script.
|
||||
|
||||
```sh
|
||||
git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
|
||||
~/.fzf/install
|
||||
```
|
||||
|
||||
But it's recommended that you use a plugin manager like
|
||||
[vim-plug](https://github.com/junegunn/vim-plug).
|
||||
### Using Linux package managers
|
||||
|
||||
```vim
|
||||
Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
|
||||
```
|
||||
| Distro | Command |
|
||||
| --- | --- |
|
||||
| Alpine Linux | `sudo apk add fzf` |
|
||||
| Arch Linux | `sudo pacman -S fzf` |
|
||||
| Debian | `sudo apt-get install fzf` |
|
||||
| Fedora | `sudo dnf install fzf` |
|
||||
| FreeBSD | `pkg install fzf` |
|
||||
| NixOS | `nix-env -iA nixpkgs.fzf` |
|
||||
| openSUSE | `sudo zypper install fzf` |
|
||||
|
||||
Shell extensions (key bindings and fuzzy auto-completion) and Vim/Neovim
|
||||
plugin may or may not be enabled by default depending on the package manager.
|
||||
Refer to the package documentation for more information.
|
||||
|
||||
### Windows
|
||||
|
||||
Pre-built binaries for Windows can be downloaded [here][bin]. fzf is also
|
||||
available as a [Chocolatey package][choco].
|
||||
available via [Chocolatey][choco] and [Scoop][scoop]:
|
||||
|
||||
| Package manager | Command |
|
||||
| --- | --- |
|
||||
| Chocolatey | `choco install fzf` |
|
||||
| Scoop | `scoop install fzf` |
|
||||
|
||||
[choco]: https://chocolatey.org/packages/fzf
|
||||
[scoop]: https://github.com/ScoopInstaller/Main/blob/master/bucket/fzf.json
|
||||
|
||||
```sh
|
||||
choco 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
|
||||
everything runs flawlessly.
|
||||
Known issues and limitations on Windows can be found on [the wiki
|
||||
page][windows-wiki].
|
||||
|
||||
[windows-wiki]: https://github.com/junegunn/fzf/wiki/Windows
|
||||
[wsl]: https://blogs.msdn.microsoft.com/wsl/
|
||||
|
||||
### As Vim plugin
|
||||
|
||||
Once you have fzf installed, you can enable it inside Vim simply by adding the
|
||||
directory to `&runtimepath` in your Vim configuration file. The path may
|
||||
differ depending on the package manager.
|
||||
|
||||
```vim
|
||||
" If installed using Homebrew
|
||||
set rtp+=/usr/local/opt/fzf
|
||||
|
||||
" If installed using git
|
||||
set rtp+=~/.fzf
|
||||
```
|
||||
|
||||
If you use [vim-plug](https://github.com/junegunn/vim-plug), the same can be
|
||||
written as:
|
||||
|
||||
```vim
|
||||
" If installed using Homebrew
|
||||
Plug '/usr/local/opt/fzf'
|
||||
|
||||
" If installed using git
|
||||
Plug '~/.fzf'
|
||||
```
|
||||
|
||||
But instead of separately installing fzf on your system (using Homebrew or
|
||||
"git clone") and enabling it on Vim (adding it to `&runtimepath`), you can use
|
||||
vim-plug to do both.
|
||||
|
||||
```vim
|
||||
" 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 the install script if you use fzf only in Vim.
|
||||
```
|
||||
|
||||
Upgrading fzf
|
||||
-------------
|
||||
@@ -186,8 +224,8 @@ cursor with `--height` option.
|
||||
vim $(fzf --height 40%)
|
||||
```
|
||||
|
||||
Also check out `--reverse` option if you prefer "top-down" layout instead of
|
||||
the default "bottom-up" layout.
|
||||
Also check out `--reverse` and `--layout` options if you prefer
|
||||
"top-down" layout instead of the default "bottom-up" layout.
|
||||
|
||||
```sh
|
||||
vim $(fzf --height 40% --reverse)
|
||||
@@ -197,7 +235,7 @@ You can add these options to `$FZF_DEFAULT_OPTS` so that they're applied by
|
||||
default. For example,
|
||||
|
||||
```sh
|
||||
export FZF_DEFAULT_OPTS='--height 40% --reverse --border'
|
||||
export FZF_DEFAULT_OPTS='--height 40% --layout=reverse --border'
|
||||
```
|
||||
|
||||
#### Search syntax
|
||||
@@ -206,14 +244,15 @@ Unless otherwise specified, fzf starts in "extended-search mode" where you can
|
||||
type in multiple search terms delimited by spaces. e.g. `^music .mp3$ sbtrkt
|
||||
!fire`
|
||||
|
||||
| Token | Match type | Description |
|
||||
| -------- | -------------------------- | --------------------------------- |
|
||||
| `sbtrkt` | fuzzy-match | Items that match `sbtrkt` |
|
||||
| `^music` | prefix-exact-match | Items that start with `music` |
|
||||
| `.mp3$` | suffix-exact-match | Items that end with `.mp3` |
|
||||
| `'wild` | exact-match (quoted) | Items that include `wild` |
|
||||
| `!fire` | inverse-exact-match | Items that do not include `fire` |
|
||||
| `!.mp3$` | inverse-suffix-exact-match | Items that do not end with `.mp3` |
|
||||
| Token | Match type | Description |
|
||||
| --------- | -------------------------- | ------------------------------------ |
|
||||
| `sbtrkt` | fuzzy-match | Items that match `sbtrkt` |
|
||||
| `'wild` | exact-match (quoted) | Items that include `wild` |
|
||||
| `^music` | prefix-exact-match | Items that start with `music` |
|
||||
| `.mp3$` | suffix-exact-match | Items that end with `.mp3` |
|
||||
| `!fire` | inverse-exact-match | Items that do not include `fire` |
|
||||
| `!^music` | inverse-prefix-exact-match | Items that do not start with `music` |
|
||||
| `!.mp3$` | inverse-suffix-exact-match | Items that do not end with `.mp3` |
|
||||
|
||||
If you don't prefer fuzzy matching and do not wish to "quote" every word,
|
||||
start fzf with `-e` or `--exact` option. Note that when `--exact` is set,
|
||||
@@ -231,15 +270,22 @@ or `py`.
|
||||
|
||||
- `FZF_DEFAULT_COMMAND`
|
||||
- Default command to use when input is tty
|
||||
- e.g. `export FZF_DEFAULT_COMMAND='ag -g ""'`
|
||||
- e.g. `export FZF_DEFAULT_COMMAND='fd --type f'`
|
||||
- `FZF_DEFAULT_OPTS`
|
||||
- Default options
|
||||
- e.g. `export FZF_DEFAULT_OPTS="--reverse --inline-info"`
|
||||
- e.g. `export FZF_DEFAULT_OPTS="--layout=reverse --inline-info"`
|
||||
|
||||
#### Options
|
||||
|
||||
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
|
||||
--------
|
||||
|
||||
@@ -273,16 +319,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
|
||||
@@ -334,7 +380,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
|
||||
@@ -369,35 +415,30 @@ export FZF_COMPLETION_TRIGGER='~~'
|
||||
# Options to fzf command
|
||||
export FZF_COMPLETION_OPTS='+c -x'
|
||||
|
||||
# Use ag instead of the default find command for listing path candidates.
|
||||
# - The first argument to the function is the base path to start traversal
|
||||
# Use fd (https://github.com/sharkdp/fd) instead of the default find
|
||||
# command for listing path candidates.
|
||||
# - The first argument to the function ($1) is the base path to start traversal
|
||||
# - See the source code (completion.{bash,zsh}) for the details.
|
||||
# - ag only lists files, so we use with-dir script to augment the output
|
||||
_fzf_compgen_path() {
|
||||
ag -g "" "$1" | with-dir "$1"
|
||||
fd --hidden --follow --exclude ".git" . "$1"
|
||||
}
|
||||
|
||||
# Use ag to generate the list for directory completion
|
||||
# Use fd to generate the list for directory completion
|
||||
_fzf_compgen_dir() {
|
||||
ag -g "" "$1" | only-dir "$1"
|
||||
fd --type d --hidden --follow --exclude ".git" . "$1"
|
||||
}
|
||||
```
|
||||
|
||||
`only-dir` and `with-dir` scripts can be found [here][dir-scripts]. They are
|
||||
written in Ruby, but you should be able to rewrite them in any language you
|
||||
prefer.
|
||||
|
||||
[dir-scripts]: https://gist.github.com/junegunn/8c3796a965f22e6a803fe53096ad7a75
|
||||
|
||||
#### Supported commands
|
||||
|
||||
On bash, fuzzy completion is enabled only for a predefined set of commands
|
||||
(`complete | grep _fzf` to see the list). But you can enable it for other
|
||||
commands as well like follows.
|
||||
commands as well by using `_fzf_setup_completion` helper function.
|
||||
|
||||
```sh
|
||||
complete -F _fzf_path_completion -o default -o bashdefault ag
|
||||
complete -F _fzf_dir_completion -o default -o bashdefault tree
|
||||
# usage: _fzf_setup_completion path|dir COMMANDS...
|
||||
_fzf_setup_completion path ag git kubectl
|
||||
_fzf_setup_completion dir tree
|
||||
```
|
||||
|
||||
Vim plugin
|
||||
@@ -410,7 +451,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.
|
||||
|
||||
@@ -421,7 +462,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.
|
||||
|
||||
@@ -442,7 +483,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
|
||||
@@ -450,7 +491,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
|
||||
@@ -458,102 +499,93 @@ important that the command finishes quickly.
|
||||
fzf --preview 'head -100 {}'
|
||||
```
|
||||
|
||||
Preview window supports ANSI colors, so you can use programs that
|
||||
Preview window supports ANSI colors, so you can use any program 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
|
||||
fzf --preview '[[ $(file --mime {}) =~ binary ]] &&
|
||||
echo {} is a binary file ||
|
||||
(highlight -O ansi -l {} ||
|
||||
coderay {} ||
|
||||
rougify {} ||
|
||||
cat {}) 2> /dev/null | head -500'
|
||||
fzf --preview 'bat --style=numbers --color=always {} | head -500'
|
||||
```
|
||||
|
||||
You can customize the size and position of the preview window using
|
||||
`--preview-window` option. For example,
|
||||
You can customize the size, position, and border of the preview window using
|
||||
`--preview-window` option, and the foreground and background color of it with
|
||||
`--color` option. For example,
|
||||
|
||||
```bash
|
||||
fzf --height 40% --reverse --preview 'file {}' --preview-window down:1
|
||||
fzf --height 40% --layout reverse --info inline --border \
|
||||
--preview 'file {}' --preview-window down:1:noborder \
|
||||
--color 'fg:#bbccdd,fg+:#ddeeff,bg:#334455,preview-bg:#223344,border:#778899'
|
||||
```
|
||||
|
||||
For more advanced examples, see [Key bindings for git with fzf][fzf-git].
|
||||
See the man page (`man fzf`) for the full list of options.
|
||||
|
||||
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/
|
||||
|
||||
----
|
||||
|
||||
Since fzf is a general-purpose text filter rather than a file finder, **it is
|
||||
not a good idea to add `--preview` option to your `$FZF_DEFAULT_OPTS`**.
|
||||
|
||||
```sh
|
||||
# *********************
|
||||
# ** DO NOT DO THIS! **
|
||||
# *********************
|
||||
export FZF_DEFAULT_OPTS='--preview "bat --style=numbers --color=always {} | head -500"'
|
||||
|
||||
# bat doesn't work with any input other than the list of files
|
||||
ps -ef | fzf
|
||||
seq 100 | fzf
|
||||
history | fzf
|
||||
```
|
||||
|
||||
Tips
|
||||
----
|
||||
|
||||
#### Respecting `.gitignore`, `.hgignore`, and `svn:ignore`
|
||||
#### Respecting `.gitignore`
|
||||
|
||||
[ag](https://github.com/ggreer/the_silver_searcher) or
|
||||
[rg](https://github.com/BurntSushi/ripgrep) will do the
|
||||
filtering:
|
||||
You can use [fd](https://github.com/sharkdp/fd),
|
||||
[ripgrep](https://github.com/BurntSushi/ripgrep), or [the silver
|
||||
searcher](https://github.com/ggreer/the_silver_searcher) instead of the
|
||||
default find command to traverse the file system while respecting
|
||||
`.gitignore`.
|
||||
|
||||
```sh
|
||||
# Feed the output of ag into fzf
|
||||
ag -g "" | fzf
|
||||
# Feed the output of fd into fzf
|
||||
fd --type f | fzf
|
||||
|
||||
# Setting ag as the default source for fzf
|
||||
export FZF_DEFAULT_COMMAND='ag -g ""'
|
||||
# Setting fd as the default source for fzf
|
||||
export FZF_DEFAULT_COMMAND='fd --type f'
|
||||
|
||||
# Now fzf (w/o pipe) will use ag instead of find
|
||||
# Now fzf (w/o pipe) will use fd instead of find
|
||||
fzf
|
||||
|
||||
# To apply the command to CTRL-T as well
|
||||
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
|
||||
```
|
||||
|
||||
If you don't want to exclude hidden files, use the following command:
|
||||
If you want the command to follow symbolic links, and don't want it to exclude
|
||||
hidden files, use the following command:
|
||||
|
||||
```sh
|
||||
export FZF_DEFAULT_COMMAND='ag --hidden --ignore .git -g ""'
|
||||
```
|
||||
|
||||
#### `git ls-tree` for fast traversal
|
||||
|
||||
If you're running fzf in a large git repository, `git ls-tree` can boost up the
|
||||
speed of the traversal.
|
||||
|
||||
```sh
|
||||
export FZF_DEFAULT_COMMAND='
|
||||
(git ls-tree -r --name-only HEAD ||
|
||||
find . -path "*/\.*" -prune -o -type f -print -o -type l -print |
|
||||
sed s/^..//) 2> /dev/null'
|
||||
export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git'
|
||||
```
|
||||
|
||||
#### Fish shell
|
||||
|
||||
Fish shell before version 2.6.0 [doesn't allow](https://github.com/fish-shell/fish-shell/issues/1362)
|
||||
reading from STDIN in command substitution, which means simple `vim (fzf)`
|
||||
doesn't work as expected. The workaround for fish 2.5.0 and earlier is to use
|
||||
the `read` fish command:
|
||||
|
||||
```sh
|
||||
fzf | read -l result; and vim $result
|
||||
```
|
||||
|
||||
or, for multiple results:
|
||||
|
||||
```sh
|
||||
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
|
||||
root folder for the recursive search. For instance, hitting `CTRL-T` at the end
|
||||
of the following commandline
|
||||
`CTRL-T` key binding of fish, unlike those of bash and zsh, will use the last
|
||||
token on the command-line as the root directory for the recursive search. For
|
||||
instance, hitting `CTRL-T` at the end of the following command-line
|
||||
|
||||
```sh
|
||||
ls /var/
|
||||
```
|
||||
|
||||
will list all files and folders under `/var/`.
|
||||
will list all files and directories under `/var/`.
|
||||
|
||||
When using a custom `FZF_CTRL_T_COMMAND`, use the unexpanded `$dir` variable to
|
||||
make use of this feature. `$dir` defaults to `.` when the last token is not a
|
||||
@@ -563,9 +595,14 @@ 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)
|
||||
------------------
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Junegunn Choi
|
||||
Copyright (c) 2019 Junegunn Choi
|
||||
|
@@ -136,6 +136,11 @@ fifo3="${TMPDIR:-/tmp}/fzf-fifo3-$id"
|
||||
cleanup() {
|
||||
\rm -f $argsf $fifo1 $fifo2 $fifo3
|
||||
|
||||
# Restore tmux window options
|
||||
if [[ "${#tmux_win_opts[@]}" -gt 0 ]]; then
|
||||
eval "tmux ${tmux_win_opts[@]}"
|
||||
fi
|
||||
|
||||
# Remove temp window if we were zoomed
|
||||
if [[ -n "$zoomed" ]]; then
|
||||
tmux display-message -p "#{window_id}" > /dev/null
|
||||
@@ -174,6 +179,8 @@ pppid=$$
|
||||
echo -n "trap 'kill -SIGUSR1 -$pppid' EXIT SIGINT SIGTERM;" > $argsf
|
||||
close="; trap - EXIT SIGINT SIGTERM $close"
|
||||
|
||||
tmux_win_opts=( $(tmux show-window-options remain-on-exit \; show-window-options synchronize-panes | sed '/ off/d; s/^/set-window-option /; s/$/ \\;/') )
|
||||
|
||||
if [[ -n "$term" ]] || [[ -t 0 ]]; then
|
||||
cat <<< "\"$fzf\" $opts > $fifo2; echo \$? > $fifo3 $close" >> $argsf
|
||||
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
|
||||
|
251
doc/fzf.txt
251
doc/fzf.txt
@@ -1,23 +1,47 @@
|
||||
fzf.txt fzf Last change: September 29 2017
|
||||
fzf.txt fzf Last change: November 23 2019
|
||||
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
|
||||
==============================================================================
|
||||
|
||||
FZF Vim integration
|
||||
Summary
|
||||
:FZF[!]
|
||||
Configuration
|
||||
Examples
|
||||
fzf#run
|
||||
fzf#wrap
|
||||
GVim
|
||||
Tips
|
||||
fzf inside terminal buffer
|
||||
Starting fzf in Neovim floating window
|
||||
Hide statusline
|
||||
License
|
||||
|
||||
FZF VIM INTEGRATION *fzf-vim-integration*
|
||||
==============================================================================
|
||||
|
||||
This repository only enables basic integration with Vim. If you're looking for
|
||||
more, check out {fzf.vim}{1} project.
|
||||
|
||||
(Note: To use fzf in GVim, an external terminal emulator is required.)
|
||||
SUMMARY *fzf-summary*
|
||||
==============================================================================
|
||||
|
||||
The Vim plugin of fzf provides two core functions, and `:FZF` command which is
|
||||
the basic file selector command built on top of them.
|
||||
|
||||
1. `fzf#run([spec dict])`
|
||||
- Starts fzf inside Vim with the given spec
|
||||
- `:call fzf#run({'source': 'ls'})`
|
||||
2. `fzf#wrap([spec dict]) -> (dict)`
|
||||
- Takes a spec for `fzf#run` and returns an extended version of it with
|
||||
additional options for addressing global preferences (`g:fzf_xxx`)
|
||||
- `:echo fzf#wrap({'source': 'ls'})`
|
||||
- We usually wrap a spec with `fzf#wrap` before passing it to `fzf#run`
|
||||
- `:call fzf#run(fzf#wrap({'source': 'ls'}))`
|
||||
3. `:FZF [fzf_options string] [path string]`
|
||||
- Basic fuzzy file selector
|
||||
- A reference implementation for those who don't want to write VimScript to
|
||||
implement custom commands
|
||||
- If you're looking for more such commands, check out {fzf.vim}{1} project.
|
||||
|
||||
The most important of all is `fzf#run`, but it would be easier to understand
|
||||
the whole if we start off with `:FZF` command.
|
||||
|
||||
{1} https://github.com/junegunn/fzf.vim
|
||||
|
||||
@@ -26,8 +50,6 @@ more, check out {fzf.vim}{1} project.
|
||||
==============================================================================
|
||||
|
||||
*:FZF*
|
||||
|
||||
If you have set up fzf for Vim, `:FZF` command will be added.
|
||||
>
|
||||
" Look for files under current directory
|
||||
:FZF
|
||||
@@ -35,8 +57,8 @@ If you have set up fzf for Vim, `:FZF` command will be added.
|
||||
" Look for files under your home directory
|
||||
:FZF ~
|
||||
|
||||
" With options
|
||||
:FZF --no-sort --reverse --inline-info /tmp
|
||||
" With fzf command-line options
|
||||
:FZF --reverse --info=inline /tmp
|
||||
|
||||
" Bang version starts fzf in fullscreen mode
|
||||
:FZF!
|
||||
@@ -54,8 +76,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
|
||||
@@ -66,9 +87,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~
|
||||
@@ -100,9 +118,10 @@ 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
|
||||
" - fzf#wrap translates this to a set of `--color` options
|
||||
let g:fzf_colors =
|
||||
\ { 'fg': ['fg', 'Normal'],
|
||||
\ 'bg': ['bg', 'Normal'],
|
||||
@@ -118,22 +137,64 @@ Examples~
|
||||
\ 'spinner': ['fg', 'Label'],
|
||||
\ 'header': ['fg', 'Comment'] }
|
||||
|
||||
" Enable per-command history.
|
||||
" CTRL-N and CTRL-P will be automatically bound to next-history and
|
||||
" previous-history instead of down and up. If you don't like the change,
|
||||
" explicitly bind the keys to down and up in your $FZF_DEFAULT_OPTS.
|
||||
" Enable per-command history
|
||||
" - History files will be stored in the specified directory
|
||||
" - When set, CTRL-N and CTRL-P will be bound to 'next-history' and
|
||||
" 'previous-history' instead of 'down' and 'up'.
|
||||
let g:fzf_history_dir = '~/.local/share/fzf-history'
|
||||
<
|
||||
|
||||
FZF#RUN *fzf#run*
|
||||
FZF#RUN
|
||||
==============================================================================
|
||||
|
||||
For more advanced uses, you can use `fzf#run([options])` function with the
|
||||
following options.
|
||||
*fzf#run*
|
||||
|
||||
---------------------------+---------------+--------------------------------------------------------------
|
||||
Option name | Type | Description ~
|
||||
---------------------------+---------------+--------------------------------------------------------------
|
||||
`fzf#run()` function is the core of Vim integration. It takes a single
|
||||
dictionary argument, a spec, and starts fzf process accordingly. At the very
|
||||
least, specify `sink` option to tell what it should do with the selected
|
||||
entry.
|
||||
>
|
||||
call fzf#run({'sink': 'e'})
|
||||
<
|
||||
We haven't specified the `source`, so this is equivalent to starting fzf on
|
||||
command line without standard input pipe; 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 the sink, `: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'})
|
||||
<
|
||||
Instead of using the default find command, you can use any shell command as
|
||||
the source. The following example will list the files managed by git. It's
|
||||
equivalent to running `git ls-files | fzf` on shell.
|
||||
>
|
||||
call fzf#run({'source': 'git ls-files', 'sink': 'e'})
|
||||
<
|
||||
fzf options can be specified as `options` entry in spec dictionary.
|
||||
>
|
||||
call fzf#run({'sink': 'tabedit', 'options': '--multi --reverse'})
|
||||
<
|
||||
You can also 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', 'left': '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 next example, we pass the names of color schemes
|
||||
as the source to implement a color scheme selector.
|
||||
>
|
||||
call fzf#run({'source': map(split(globpath(&rtp, 'colors/*.vim')),
|
||||
\ 'fnamemodify(v:val, ":t:r")'),
|
||||
\ 'sink': 'colo', 'left': '25%'})
|
||||
<
|
||||
The following table summarizes the available options.
|
||||
|
||||
---------------------------+---------------+----------------------------------------------------------------------
|
||||
Option name | Type | Description ~
|
||||
---------------------------+---------------+----------------------------------------------------------------------
|
||||
`source` | string | External command to generate input to fzf (e.g. `find .` )
|
||||
`source` | list | Vim list as input to fzf
|
||||
`sink` | string | Vim command to handle the selected item (e.g. `e` , `tabe` )
|
||||
@@ -141,61 +202,143 @@ following options.
|
||||
`sink*` | funcref | Similar to `sink` , but takes the list of output lines at once
|
||||
`options` | string/list | Options to fzf
|
||||
`dir` | string | Working directory
|
||||
`up` / `down` / `left` / `right` | number/string | Use tmux pane with the given size (e.g. `20` , `50%` )
|
||||
`window` (Vim 8 / Neovim) | string | Command to open fzf window (e.g. `vertical aboveleft 30new` )
|
||||
`launcher` | string | External terminal emulator to start fzf with (GVim only)
|
||||
`launcher` | funcref | Function for generating `launcher` string (GVim only)
|
||||
---------------------------+---------------+--------------------------------------------------------------
|
||||
`up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` )
|
||||
`window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new` )
|
||||
---------------------------+---------------+----------------------------------------------------------------------
|
||||
|
||||
`options` entry can be either a string or a list. For simple cases, string
|
||||
should suffice, but prefer to use list type if you're concerned about escaping
|
||||
issues on different platforms.
|
||||
should suffice, but prefer to use list type to avoid escaping issues.
|
||||
>
|
||||
call fzf#run({'options': '--reverse --prompt "C:\\Program Files\\"'})
|
||||
call fzf#run({'options': ['--reverse', '--prompt', 'C:\Program Files\']})
|
||||
<
|
||||
|
||||
FZF#WRAP *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#wrap*
|
||||
|
||||
We have seen that several aspects of `:FZF` command can be configured with a
|
||||
set of global option variables; different ways to open files (`g:fzf_action`),
|
||||
window position and size (`g:fzf_layout`), color palette (`g:fzf_colors`),
|
||||
etc.
|
||||
|
||||
So how can we make our custom `fzf#run` calls also respect those variables?
|
||||
Simply by "wrapping" the spec dictionary with `fzf#wrap` before passing it to
|
||||
`fzf#run`.
|
||||
|
||||
- `fzf#wrap([name string], [spec dict], [fullscreen bool]) -> (dict)`
|
||||
- All arguments are optional. Usually we only need to pass a spec
|
||||
dictionary.
|
||||
- `name` is for managing history files. It is ignored if `g:fzf_history_dir`
|
||||
is not defined.
|
||||
- `fullscreen` can be either `0` or `1` (default: 0).
|
||||
|
||||
`fzf#wrap` takes a spec and returns an extended version of it (also a
|
||||
dictionary) with additional options for addressing global preferences. You can
|
||||
examine the return value of it like so:
|
||||
>
|
||||
command! -bang MyStuff
|
||||
\ call fzf#run(fzf#wrap('my-stuff', {'dir': '~/my-stuff'}, <bang>0))
|
||||
echo fzf#wrap({'source': 'ls'})
|
||||
<
|
||||
After we "wrap" our spec, we pass it to `fzf#run`.
|
||||
>
|
||||
call fzf#run(fzf#wrap({'source': 'ls'}))
|
||||
<
|
||||
Now it supports CTRL-T, CTRL-V, and CTRL-X key bindings and it opens fzf
|
||||
window according to `g:fzf_layout` setting.
|
||||
|
||||
To make it easier to use, let's define `LS` command.
|
||||
>
|
||||
command! LS call fzf#run(fzf#wrap({'source': 'ls'}))
|
||||
<
|
||||
Type `:LS` and see how it works.
|
||||
|
||||
We would like to make `:LS!` (bang version) open fzf in fullscreen, just like
|
||||
`:FZF!`. Add `-bang` to command definition, and use <bang> value to set the
|
||||
last `fullscreen` argument of `fzf#wrap` (see :help <bang>).
|
||||
>
|
||||
" On :LS!, <bang> evaluates to '!', and '!0' becomes 1
|
||||
command! -bang LS call fzf#run(fzf#wrap({'source': 'ls'}, <bang>0))
|
||||
<
|
||||
Our `:LS` command will be much more useful if we can pass a directory argument
|
||||
to it, so that something like `:LS /tmp` is possible.
|
||||
>
|
||||
command! -bang -complete=dir -nargs=* LS
|
||||
\ call fzf#run(fzf#wrap({'source': 'ls', 'dir': <q-args>}, <bang>0))
|
||||
<
|
||||
Lastly, if you have enabled `g:fzf_history_dir`, you might want to assign a
|
||||
unique name to our command and pass it as the first argument to `fzf#wrap`.
|
||||
>
|
||||
" The query history for this command will be stored as 'ls' inside g:fzf_history_dir.
|
||||
" The name is ignored if g:fzf_history_dir is not defined.
|
||||
command! -bang -complete=dir -nargs=* LS
|
||||
\ call fzf#run(fzf#wrap('ls', {'source': 'ls', 'dir': <q-args>}, <bang>0))
|
||||
<
|
||||
|
||||
GVIM *fzf-gvim*
|
||||
TIPS *fzf-tips*
|
||||
==============================================================================
|
||||
|
||||
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`.
|
||||
< fzf inside terminal buffer >________________________________________________~
|
||||
*fzf-inside-terminal-buffer*
|
||||
|
||||
The latest versions of Vim and Neovim include builtin terminal emulator
|
||||
(`:terminal`) and fzf will start in a terminal buffer in the following cases:
|
||||
|
||||
- On Neovim
|
||||
- On GVim
|
||||
- On Terminal Vim with a non-default layout
|
||||
- `call fzf#run({'left': '30%'})` or `let g:fzf_layout = {'left': '30%'}`
|
||||
|
||||
|
||||
Starting fzf in Neovim floating window~
|
||||
*fzf-starting-fzf-in-neovim-floating-window*
|
||||
>
|
||||
" This is the default. %s is replaced with fzf command
|
||||
let g:fzf_launcher = 'xterm -e bash -ic %s'
|
||||
" Using floating windows of Neovim to start fzf
|
||||
if has('nvim')
|
||||
let $FZF_DEFAULT_OPTS .= ' --border --margin=0,2'
|
||||
|
||||
function! FloatingFZF()
|
||||
let width = float2nr(&columns * 0.9)
|
||||
let height = float2nr(&lines * 0.6)
|
||||
let opts = { 'relative': 'editor',
|
||||
\ 'row': (&lines - height) / 2,
|
||||
\ 'col': (&columns - width) / 2,
|
||||
\ 'width': width,
|
||||
\ 'height': height }
|
||||
|
||||
let win = nvim_open_win(nvim_create_buf(v:false, v:true), v:true, opts)
|
||||
call setwinvar(win, '&winhighlight', 'NormalFloat:Normal')
|
||||
endfunction
|
||||
|
||||
let g:fzf_layout = { 'window': 'call FloatingFZF()' }
|
||||
endif
|
||||
|
||||
" 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
|
||||
Hide statusline~
|
||||
*fzf-hide-statusline*
|
||||
|
||||
When fzf starts in a terminal buffer, the file type of the buffer is set to
|
||||
`fzf`. So you can set up `FileType fzf` autocmd to customize the settings of
|
||||
the window.
|
||||
|
||||
For example, if you use the default layout (`{'down': '~40%'}`) on Neovim, you
|
||||
might want to temporarily disable the statusline for a cleaner look.
|
||||
>
|
||||
if has('nvim') && !exists('g:fzf_layout')
|
||||
autocmd! FileType fzf
|
||||
autocmd FileType fzf set laststatus=0 noshowmode noruler
|
||||
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
|
||||
endif
|
||||
<
|
||||
|
||||
LICENSE *fzf-license*
|
||||
==============================================================================
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Junegunn Choi
|
||||
Copyright (c) 2019 Junegunn Choi
|
||||
|
||||
==============================================================================
|
||||
vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap:
|
||||
|
38
glide.lock
generated
38
glide.lock
generated
@@ -1,38 +0,0 @@
|
||||
hash: d68dd0bd779ac4ffca1e0c49ca38d85f90d5d68fa8e2d5d7db70a8ce8c662ec1
|
||||
updated: 2017-06-01T15:48:41.653745249-07:00
|
||||
imports:
|
||||
- name: github.com/gdamore/encoding
|
||||
version: b23993cbb6353f0e6aa98d0ee318a34728f628b9
|
||||
- name: github.com/gdamore/tcell
|
||||
version: 44772c121bb7838819d3ba4a7e84c0c2d617328e
|
||||
subpackages:
|
||||
- encoding
|
||||
- name: github.com/lucasb-eyer/go-colorful
|
||||
version: c900de9dbbc73129068f5af6a823068fc5f2308c
|
||||
- name: github.com/mattn/go-isatty
|
||||
version: 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8
|
||||
- name: github.com/mattn/go-runewidth
|
||||
version: 14207d285c6c197daabb5c9793d63e7af9ab2d50
|
||||
- name: github.com/mattn/go-shellwords
|
||||
version: 02e3cf038dcea8290e44424da473dd12be796a8a
|
||||
- name: golang.org/x/crypto
|
||||
version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e
|
||||
subpackages:
|
||||
- ssh/terminal
|
||||
- name: golang.org/x/sys
|
||||
version: b90f89a1e7a9c1f6b918820b3daa7f08488c8594
|
||||
subpackages:
|
||||
- unix
|
||||
- name: golang.org/x/text
|
||||
version: 4ee4af566555f5fbe026368b75596286a312663a
|
||||
subpackages:
|
||||
- encoding
|
||||
- encoding/charmap
|
||||
- encoding/internal
|
||||
- encoding/internal/identifier
|
||||
- encoding/japanese
|
||||
- encoding/korean
|
||||
- encoding/simplifiedchinese
|
||||
- encoding/traditionalchinese
|
||||
- transform
|
||||
testImports: []
|
16
glide.yaml
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: 44772c121bb7838819d3ba4a7e84c0c2d617328e
|
||||
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=
|
73
install
73
install
@@ -2,13 +2,15 @@
|
||||
|
||||
set -u
|
||||
|
||||
version=0.17.1
|
||||
version=0.20.0
|
||||
auto_completion=
|
||||
key_bindings=
|
||||
update_config=2
|
||||
binary_arch=
|
||||
allow_legacy=
|
||||
shells="bash zsh fish"
|
||||
prefix='~/.fzf'
|
||||
prefix_expand=~/.fzf
|
||||
fish_dir=${XDG_CONFIG_HOME:-$HOME/.config}/fish
|
||||
|
||||
help() {
|
||||
cat << EOF
|
||||
@@ -18,6 +20,7 @@ usage: $0 [OPTIONS]
|
||||
--bin Download fzf binary only; Do not generate ~/.fzf.{bash,zsh}
|
||||
--all Download fzf binary and update configuration files
|
||||
to enable key bindings and fuzzy completion
|
||||
--xdg Generate files under \$XDG_CONFIG_HOME/fzf
|
||||
--[no-]key-bindings Enable/disable key bindings (CTRL-T, CTRL-R, ALT-C)
|
||||
--[no-]completion Enable/disable fuzzy completion (bash & zsh)
|
||||
--[no-]update-rc Whether or not to update shell configuration files
|
||||
@@ -41,7 +44,11 @@ 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'
|
||||
prefix_expand=${XDG_CONFIG_HOME:-$HOME/.config}/fzf/fzf
|
||||
mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/fzf"
|
||||
;;
|
||||
--key-bindings) key_bindings=1 ;;
|
||||
--no-key-bindings) key_bindings=0 ;;
|
||||
@@ -64,11 +71,13 @@ 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
|
||||
read -p "$1 ([y]/n) " -r
|
||||
REPLY=${REPLY:-"y"}
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
return 1
|
||||
elif [[ $REPLY =~ ^[Nn]$ ]]; then
|
||||
@@ -80,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
|
||||
@@ -99,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
|
||||
@@ -168,13 +180,13 @@ binary_error=""
|
||||
case "$archi" in
|
||||
Darwin\ *64) download fzf-$version-darwin_${binary_arch:-amd64}.tgz ;;
|
||||
Darwin\ *86) download fzf-$version-darwin_${binary_arch:-386}.tgz ;;
|
||||
Linux\ *64) download fzf-$version-linux_${binary_arch:-amd64}.tgz ;;
|
||||
Linux\ *86) download fzf-$version-linux_${binary_arch:-386}.tgz ;;
|
||||
Linux\ armv5*) download fzf-$version-linux_${binary_arch:-arm5}.tgz ;;
|
||||
Linux\ armv6*) download fzf-$version-linux_${binary_arch:-arm6}.tgz ;;
|
||||
Linux\ armv7*) download fzf-$version-linux_${binary_arch:-arm7}.tgz ;;
|
||||
Linux\ armv8*) download fzf-$version-linux_${binary_arch:-arm8}.tgz ;;
|
||||
Linux\ aarch64*) download fzf-$version-linux_${binary_arch:-arm8}.tgz ;;
|
||||
Linux\ *64) download fzf-$version-linux_${binary_arch:-amd64}.tgz ;;
|
||||
Linux\ *86) download fzf-$version-linux_${binary_arch:-386}.tgz ;;
|
||||
FreeBSD\ *64) download fzf-$version-freebsd_${binary_arch:-amd64}.tgz ;;
|
||||
FreeBSD\ *86) download fzf-$version-freebsd_${binary_arch:-386}.tgz ;;
|
||||
OpenBSD\ *64) download fzf-$version-openbsd_${binary_arch:-amd64}.tgz ;;
|
||||
@@ -182,6 +194,10 @@ 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 ;;
|
||||
Windows*\ *86) download fzf-$version-windows_${binary_arch:-386}.zip ;;
|
||||
Windows*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
|
||||
*) binary_available=0 binary_error=1 ;;
|
||||
esac
|
||||
|
||||
@@ -239,8 +255,8 @@ fi
|
||||
echo
|
||||
for shell in $shells; do
|
||||
[[ "$shell" = fish ]] && continue
|
||||
echo -n "Generate ~/.fzf.$shell ... "
|
||||
src=~/.fzf.${shell}
|
||||
src=${prefix_expand}.${shell}
|
||||
echo -n "Generate $src ... "
|
||||
|
||||
fzf_completion="[[ \$- == *i* ]] && source \"$fzf_base/shell/completion.${shell}\" 2> /dev/null"
|
||||
if [ $auto_completion -eq 0 ]; then
|
||||
@@ -252,11 +268,11 @@ for shell in $shells; do
|
||||
fzf_key_bindings="# $fzf_key_bindings"
|
||||
fi
|
||||
|
||||
cat > $src << EOF
|
||||
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
|
||||
@@ -266,7 +282,6 @@ $fzf_completion
|
||||
# Key bindings
|
||||
# ------------
|
||||
$fzf_key_bindings
|
||||
|
||||
EOF
|
||||
echo "OK"
|
||||
done
|
||||
@@ -275,18 +290,18 @@ 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"
|
||||
|
||||
mkdir -p ~/.config/fish/functions
|
||||
if [ -e ~/.config/fish/functions/fzf.fish ]; then
|
||||
echo -n "Remove unnecessary ~/.config/fish/functions/fzf.fish ... "
|
||||
rm -f ~/.config/fish/functions/fzf.fish && echo "OK" || echo "Failed"
|
||||
mkdir -p "${fish_dir}/functions"
|
||||
if [ -e "${fish_dir}/functions/fzf.fish" ]; then
|
||||
echo -n "Remove unnecessary ${fish_dir}/functions/fzf.fish ... "
|
||||
rm -f "${fish_dir}/functions/fzf.fish" && echo "OK" || echo "Failed"
|
||||
fi
|
||||
|
||||
fish_binding=~/.config/fish/functions/fzf_key_bindings.fish
|
||||
fish_binding="${fish_dir}/functions/fzf_key_bindings.fish"
|
||||
if [ $key_bindings -ne 0 ]; then
|
||||
echo -n "Symlink $fish_binding ... "
|
||||
ln -sf "$fzf_base/shell/key-bindings.fish" \
|
||||
@@ -352,11 +367,11 @@ echo
|
||||
for shell in $shells; do
|
||||
[[ "$shell" = fish ]] && continue
|
||||
[ $shell = zsh ] && dest=${ZDOTDIR:-~}/.zshrc || dest=~/.bashrc
|
||||
append_line $update_config "[ -f ~/.fzf.${shell} ] && source ~/.fzf.${shell}" "$dest" "~/.fzf.${shell}"
|
||||
append_line $update_config "[ -f ${prefix}.${shell} ] && source ${prefix}.${shell}" "$dest" "${prefix}.${shell}"
|
||||
done
|
||||
|
||||
if [ $key_bindings -eq 1 ] && [[ "$shells" =~ fish ]]; then
|
||||
bind_file=~/.config/fish/functions/fish_user_key_bindings.fish
|
||||
bind_file="${fish_dir}/functions/fish_user_key_bindings.fish"
|
||||
if [ ! -e "$bind_file" ]; then
|
||||
create_file "$bind_file" \
|
||||
'function fish_user_key_bindings' \
|
||||
|
@@ -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 "Oct 2017" "fzf 0.17.1" "fzf-tmux - open fzf in tmux split pane"
|
||||
.TH fzf-tmux 1 "Dec 2019" "fzf 0.20.0" "fzf-tmux - open fzf in tmux split pane"
|
||||
|
||||
.SH NAME
|
||||
fzf-tmux - open fzf in tmux split pane
|
||||
|
359
man/man1/fzf.1
359
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 "Oct 2017" "fzf 0.17.1" "fzf - a command-line fuzzy finder"
|
||||
.TH fzf 1 "Dec 2019" "fzf 0.20.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
|
||||
@@ -155,12 +161,30 @@ the full screen.
|
||||
.BI "--min-height=" "HEIGHT"
|
||||
Minimum height when \fB--height\fR is given in percent (default: 10).
|
||||
Ignored when \fB--height\fR is not specified.
|
||||
.TP
|
||||
.BI "--layout=" "LAYOUT"
|
||||
Choose the layout (default: default)
|
||||
|
||||
.br
|
||||
.BR default " Display from the bottom of the screen"
|
||||
.br
|
||||
.BR reverse " Display from the top of the screen"
|
||||
.br
|
||||
.BR reverse-list " Display from the top of the screen, prompt at the bottom"
|
||||
.br
|
||||
|
||||
.TP
|
||||
.B "--reverse"
|
||||
Reverse orientation
|
||||
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.
|
||||
@@ -183,19 +207,33 @@ 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: '> ')
|
||||
.TP
|
||||
.BI "--header=" "STR"
|
||||
The given string will be printed as the sticky header. The lines are displayed
|
||||
in the given order from top to bottom regardless of \fB--reverse\fR option, and
|
||||
in the given order from top to bottom regardless of \fB--layout\fR option, and
|
||||
are not affected by \fB--with-nth\fR. ANSI color codes are processed even when
|
||||
\fB--ansi\fR is not set.
|
||||
.TP
|
||||
@@ -217,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)
|
||||
@@ -229,22 +262,38 @@ e.g. \fBfzf --color=bg+:24\fR
|
||||
\fBdark \fRColor scheme for dark 256-color terminal
|
||||
\fBlight \fRColor scheme for light 256-color terminal
|
||||
\fB16 \fRColor scheme for 16-color terminal
|
||||
\fBbw \fRNo colors
|
||||
\fBbw \fRNo colors (equivalent to \fB--no-color\fR)
|
||||
|
||||
.B COLOR:
|
||||
\fBfg \fRText
|
||||
\fBbg \fRBackground
|
||||
\fBhl \fRHighlighted substrings
|
||||
\fBfg+ \fRText (current line)
|
||||
\fBbg+ \fRBackground (current line)
|
||||
\fBhl+ \fRHighlighted substrings (current line)
|
||||
\fBinfo \fRInfo
|
||||
\fBborder \fRBorder of the preview window and horizontal separators (\fB--border\fR)
|
||||
\fBprompt \fRPrompt
|
||||
\fBpointer \fRPointer to the current line
|
||||
\fBmarker \fRMulti-select marker
|
||||
\fBspinner \fRStreaming input indicator
|
||||
\fBheader \fRHeader
|
||||
\fBfg \fRText
|
||||
\fBbg \fRBackground
|
||||
\fBpreview-fg \fRPreview window text
|
||||
\fBpreview-bg \fRPreview window background
|
||||
\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)
|
||||
\fBprompt \fRPrompt
|
||||
\fBpointer \fRPointer to the current line
|
||||
\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"
|
||||
@@ -272,23 +321,50 @@ 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 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
|
||||
|
||||
Also, \fB{q}\fR is replaced to the current query string.
|
||||
When using a field index expression, leading and trailing whitespace is stripped
|
||||
from the replacement string. To preserve the whitespace, use the \fBs\fR flag.
|
||||
|
||||
Also, \fB{q}\fR is replaced to the current query string, and \fB{n}\fR is
|
||||
replaced to zero-based ordinal index of the line. Use \fB{+n}\fR if you want
|
||||
all index numbers when multiple lines are selected.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
@@ -305,8 +381,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
|
||||
@@ -336,7 +413,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"
|
||||
@@ -368,7 +446,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
|
||||
@@ -441,62 +520,127 @@ 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-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-left\fR
|
||||
\fIshift-right\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
|
||||
\fBaccept\fR \fIenter double-click\fR
|
||||
\fBaccept-non-empty\fR (same as \fBaccept\fR except that it prevents fzf from exiting without selection)
|
||||
\fBbackward-char\fR \fIctrl-b left\fR
|
||||
\fBbackward-delete-char\fR \fIctrl-h bspace\fR
|
||||
\fBbackward-kill-word\fR \fIalt-bs\fR
|
||||
\fBbackward-word\fR \fIalt-b shift-left\fR
|
||||
\fBbeginning-of-line\fR \fIctrl-a home\fR
|
||||
\fBcancel\fR
|
||||
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
||||
\fBclear-screen\fR \fIctrl-l\fR
|
||||
\fBclear-selection\fR (clear multi-selection)
|
||||
\fBclear-query\fR (clear query string)
|
||||
\fBdelete-char\fR \fIdel\fR
|
||||
\fBdelete-char/eof\fR \fIctrl-d\fR
|
||||
\fBdeselect-all\fR
|
||||
\fBdeselect-all\fR (deselect all matches)
|
||||
\fBdown\fR \fIctrl-j ctrl-n down\fR
|
||||
\fBend-of-line\fR \fIctrl-e end\fR
|
||||
\fBexecute(...)\fR (see below for the details)
|
||||
@@ -514,18 +658,20 @@ triggered whenever the query string is changed.
|
||||
\fBpage-up\fR \fIpgup\fR
|
||||
\fBhalf-page-down\fR
|
||||
\fBhalf-page-up\fR
|
||||
\fBpreview-down\fR
|
||||
\fBpreview-up\fR
|
||||
\fBpreview-down\fR \fIshift-down\fR
|
||||
\fBpreview-up\fR \fIshift-up\fR
|
||||
\fBpreview-page-down\fR
|
||||
\fBpreview-page-up\fR
|
||||
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
||||
\fBprint-query\fR (print query and exit)
|
||||
\fBselect-all\fR
|
||||
\fBtoggle\fR
|
||||
\fBtoggle-all\fR
|
||||
\fBreload(...)\fR (see below for the details)
|
||||
\fBreplace-query\fR (replace query string with the current selection)
|
||||
\fBselect-all\fR (select all matches)
|
||||
\fBtoggle\fR (\fIright-click\fR)
|
||||
\fBtoggle-all\fR (toggle all matches)
|
||||
\fBtoggle+down\fR \fIctrl-i (tab)\fR
|
||||
\fBtoggle-in\fR (\fB--reverse\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
||||
\fBtoggle-out\fR (\fB--reverse\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
||||
\fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
||||
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
||||
\fBtoggle-preview\fR
|
||||
\fBtoggle-preview-wrap\fR
|
||||
\fBtoggle-sort\fR
|
||||
@@ -536,9 +682,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
|
||||
@@ -567,18 +718,38 @@ 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
|
||||
command is expected to complete quickly, and you are not interested in its
|
||||
output, you might want to use \fBexecute-silent\fR instead, which silently
|
||||
executes the command without the switching. Note that fzf will not be
|
||||
responsible until the command is complete. For asynchronous execution, start
|
||||
responsive until the command is complete. For asynchronous execution, start
|
||||
your command as a background process (i.e. appending \fB&\fR).
|
||||
|
||||
.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)
|
||||
|
||||
|
135
plugin/fzf.vim
135
plugin/fzf.vim
@@ -49,10 +49,18 @@ if s:is_win
|
||||
|
||||
" Use utf-8 for fzf.vim commands
|
||||
" Return array of shell commands for cmd.exe
|
||||
let s:codepage = libcallnr('kernel32.dll', 'GetACP', 0)
|
||||
function! s:enc_to_cp(str)
|
||||
return iconv(a:str, &encoding, 'cp'.s:codepage)
|
||||
endfunction
|
||||
function! s:wrap_cmds(cmds)
|
||||
return ['@echo off', 'for /f "tokens=4" %%a in (''chcp'') do set origchcp=%%a', 'chcp 65001 > nul'] +
|
||||
\ (type(a:cmds) == type([]) ? a:cmds : [a:cmds]) +
|
||||
\ ['chcp %origchcp% > nul']
|
||||
return map([
|
||||
\ '@echo off',
|
||||
\ 'setlocal enabledelayedexpansion']
|
||||
\ + (has('gui_running') ? ['set TERM= > nul'] : [])
|
||||
\ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds])
|
||||
\ + ['endlocal'],
|
||||
\ '<SID>enc_to_cp(v:val."\r")')
|
||||
endfunction
|
||||
else
|
||||
let s:term_marker = ";#FZF"
|
||||
@@ -64,6 +72,10 @@ else
|
||||
function! s:wrap_cmds(cmds)
|
||||
return a:cmds
|
||||
endfunction
|
||||
|
||||
function! s:enc_to_cp(str)
|
||||
return a:str
|
||||
endfunction
|
||||
endif
|
||||
|
||||
function! s:shellesc_cmd(arg)
|
||||
@@ -75,7 +87,7 @@ function! s:shellesc_cmd(arg)
|
||||
endfunction
|
||||
|
||||
function! fzf#shellescape(arg, ...)
|
||||
let shell = get(a:000, 0, &shell)
|
||||
let shell = get(a:000, 0, s:is_win ? 'cmd.exe' : 'sh')
|
||||
if shell =~# 'cmd.exe$'
|
||||
return s:shellesc_cmd(a:arg)
|
||||
endif
|
||||
@@ -231,6 +243,7 @@ function! s:common_sink(action, lines) abort
|
||||
doautocmd BufEnter
|
||||
endif
|
||||
endfor
|
||||
catch /^Vim:Interrupt$/
|
||||
finally
|
||||
let &autochdir = autochdir
|
||||
silent! autocmd! fzf_swap
|
||||
@@ -238,7 +251,7 @@ function! s:common_sink(action, lines) abort
|
||||
endfunction
|
||||
|
||||
function! s:get_color(attr, ...)
|
||||
let gui = has('termguicolors') && &termguicolors
|
||||
let gui = !s:is_win && !has('win32unix') && has('termguicolors') && &termguicolors
|
||||
let fam = gui ? 'gui' : 'cterm'
|
||||
let pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$'
|
||||
for group in a:000
|
||||
@@ -253,7 +266,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)
|
||||
@@ -333,19 +346,21 @@ function! fzf#wrap(...)
|
||||
endfunction
|
||||
|
||||
function! s:use_sh()
|
||||
let [shell, shellslash] = [&shell, &shellslash]
|
||||
let [shell, shellslash, shellcmdflag, shellxquote] = [&shell, &shellslash, &shellcmdflag, &shellxquote]
|
||||
if s:is_win
|
||||
set shell=cmd.exe
|
||||
set noshellslash
|
||||
let &shellcmdflag = has('nvim') ? '/s /c' : '/c'
|
||||
let &shellxquote = has('nvim') ? '"' : '('
|
||||
else
|
||||
set shell=sh
|
||||
endif
|
||||
return [shell, shellslash]
|
||||
return [shell, shellslash, shellcmdflag, shellxquote]
|
||||
endfunction
|
||||
|
||||
function! fzf#run(...) abort
|
||||
try
|
||||
let [shell, shellslash] = s:use_sh()
|
||||
let [shell, shellslash, shellcmdflag, shellxquote] = s:use_sh()
|
||||
|
||||
let dict = exists('a:1') ? s:upgrade(a:1) : {}
|
||||
let temps = { 'result': s:fzf_tempname() }
|
||||
@@ -356,7 +371,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')
|
||||
@@ -376,7 +391,7 @@ try
|
||||
let prefix = '( '.source.' )|'
|
||||
elseif type == 3
|
||||
let temps.input = s:fzf_tempname()
|
||||
call writefile(source, temps.input)
|
||||
call writefile(map(source, '<SID>enc_to_cp(v:val)'), temps.input)
|
||||
let prefix = (s:is_win ? 'type ' : 'cat ').fzf#shellescape(temps.input).'|'
|
||||
else
|
||||
throw 'Invalid source type'
|
||||
@@ -392,7 +407,7 @@ try
|
||||
let has_vim8_term = has('terminal') && has('patch-8.0.995')
|
||||
let has_nvim_term = has('nvim-0.2.1') || has('nvim') && !s:is_win
|
||||
let use_term = has_nvim_term ||
|
||||
\ has_vim8_term && (has('gui_running') || s:is_win || !use_height && s:present(dict, 'down', 'up', 'left', 'right', 'window'))
|
||||
\ has_vim8_term && !has('win32unix') && (has('gui_running') || s:is_win || !use_height && s:present(dict, 'down', 'up', 'left', 'right', 'window'))
|
||||
let use_tmux = (!use_height && !use_term || prefer_tmux) && !has('win32unix') && s:tmux_enabled() && s:splittable(dict)
|
||||
if prefer_tmux && use_tmux
|
||||
let use_height = 0
|
||||
@@ -415,7 +430,7 @@ try
|
||||
call s:callback(dict, lines)
|
||||
return lines
|
||||
finally
|
||||
let [&shell, &shellslash] = [shell, shellslash]
|
||||
let [&shell, &shellslash, &shellcmdflag, &shellxquote] = [shell, shellslash, shellcmdflag, shellxquote]
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
@@ -454,15 +469,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
|
||||
@@ -471,20 +489,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
|
||||
@@ -497,6 +533,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)
|
||||
@@ -533,9 +573,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
|
||||
@@ -562,9 +600,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)
|
||||
@@ -586,8 +625,9 @@ function! s:calc_size(max, val, dict)
|
||||
let srcsz = len(a:dict.source)
|
||||
endif
|
||||
|
||||
let opts = get(a:dict, 'options', '').$FZF_DEFAULT_OPTS
|
||||
let opts = $FZF_DEFAULT_OPTS.' '.s:evaluate_opts(get(a:dict, 'options', ''))
|
||||
let margin = stridx(opts, '--inline-info') > stridx(opts, '--no-inline-info') ? 1 : 2
|
||||
let margin += stridx(opts, '--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
|
||||
@@ -685,9 +725,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)
|
||||
@@ -699,16 +737,13 @@ function! s:execute_term(dict, command, temps) abort
|
||||
if has('nvim')
|
||||
call termopen(command, fzf)
|
||||
else
|
||||
let t = term_start([&shell, &shellcmdflag, command], {'curwin': fzf.buf, 'exit_cb': function(fzf.on_exit)})
|
||||
" FIXME: https://github.com/vim/vim/issues/1998
|
||||
if !has('nvim') && !s:is_win
|
||||
call term_wait(t, 20)
|
||||
let fzf.buf = term_start([&shell, &shellcmdflag, command], {'curwin': 1, 'exit_cb': function(fzf.on_exit)})
|
||||
if !has('patch-8.0.1261') && !has('nvim') && !s:is_win
|
||||
call term_wait(fzf.buf, 20)
|
||||
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 +762,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 +788,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
|
||||
|
@@ -1,4 +1,3 @@
|
||||
#!/bin/bash
|
||||
# ____ ____
|
||||
# / __/___ / __/
|
||||
# / /_/_ / / /_
|
||||
@@ -10,12 +9,14 @@
|
||||
# - $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() {
|
||||
echo "$1"
|
||||
command find -L "$1" \
|
||||
-name .git -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
||||
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
||||
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||
}
|
||||
fi
|
||||
@@ -23,7 +24,7 @@ fi
|
||||
if ! declare -f _fzf_compgen_dir > /dev/null; then
|
||||
_fzf_compgen_dir() {
|
||||
command find -L "$1" \
|
||||
-name .git -prune -o -name .svn -prune -o -type d \
|
||||
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
|
||||
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||
}
|
||||
fi
|
||||
@@ -38,9 +39,9 @@ __fzfcmd_complete() {
|
||||
echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
|
||||
}
|
||||
|
||||
_fzf_orig_completion_filter() {
|
||||
sed 's/^\(.*-F\) *\([^ ]*\).* \([^ ]*\)$/export _fzf_orig_completion_\3="\1 %s \3 #\2";/' |
|
||||
awk -F= '{gsub(/[^A-Za-z0-9_= ;]/, "_", $1); print $1"="$2}'
|
||||
__fzf_orig_completion_filter() {
|
||||
sed 's/^\(.*-F\) *\([^ ]*\).* \([^ ]*\)$/export _fzf_orig_completion_\3="\1 %s \3 #\2"; [[ "\1" = *" -o nospace "* ]] \&\& [[ ! "$__fzf_nospace_commands" = *" \3 "* ]] \&\& __fzf_nospace_commands="$__fzf_nospace_commands \3 ";/' |
|
||||
awk -F= '{OFS = FS} {gsub(/[^A-Za-z0-9_= ;]/, "_", $1);}1'
|
||||
}
|
||||
|
||||
_fzf_opts_completion() {
|
||||
@@ -113,7 +114,7 @@ _fzf_opts_completion() {
|
||||
}
|
||||
|
||||
_fzf_handle_dynamic_completion() {
|
||||
local cmd orig_var orig ret orig_cmd
|
||||
local cmd orig_var orig ret orig_cmd orig_complete
|
||||
cmd="$1"
|
||||
shift
|
||||
orig_cmd="$1"
|
||||
@@ -122,10 +123,18 @@ _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 "$orig_cmd" 2> /dev/null)
|
||||
_completion_loader "$@"
|
||||
ret=$?
|
||||
eval "$(complete | command grep "\-F.* $orig_cmd$" | _fzf_orig_completion_filter)"
|
||||
source "${BASH_SOURCE[0]}"
|
||||
# _completion_loader may not have updated completion for the command
|
||||
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 }"
|
||||
else
|
||||
eval "$orig_complete"
|
||||
fi
|
||||
fi
|
||||
return $ret
|
||||
fi
|
||||
}
|
||||
@@ -141,7 +150,7 @@ __fzf_generic_path_completion() {
|
||||
base=${cur:0:${#cur}-${#trigger}}
|
||||
eval "base=$base"
|
||||
|
||||
dir="$base"
|
||||
[[ $base = *"/"* ]] && dir="$base"
|
||||
while true; do
|
||||
if [ -z "$dir" ] || [ -d "$dir" ]; then
|
||||
leftover=${base/#"$dir"}
|
||||
@@ -152,6 +161,7 @@ __fzf_generic_path_completion() {
|
||||
printf "%q$3 " "$item"
|
||||
done)
|
||||
matches=${matches% }
|
||||
[[ -z "$3" ]] && [[ "$__fzf_nospace_commands" = *" ${COMP_WORDS[0]} "* ]] && matches="$matches "
|
||||
if [ -n "$matches" ]; then
|
||||
COMPREPLY=( "$matches" )
|
||||
else
|
||||
@@ -185,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" "$@"
|
||||
@@ -215,7 +226,7 @@ _fzf_complete_kill() {
|
||||
|
||||
local selected fzf
|
||||
fzf="$(__fzfcmd_complete)"
|
||||
selected=$(ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS --preview 'echo {}' --preview-window down:3:wrap $FZF_COMPLETION_OPTS" $fzf -m | awk '{print $2}' | tr '\n' ' ')
|
||||
selected=$(command ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS --preview 'echo {}' --preview-window down:3:wrap $FZF_COMPLETION_OPTS" $fzf -m | awk '{print $2}' | tr '\n' ' ')
|
||||
printf '\e[5n'
|
||||
|
||||
if [ -n "$selected" ]; then
|
||||
@@ -233,8 +244,8 @@ _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}') \
|
||||
<(command grep -oE '^[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | awk '{ print $1 " " $1 }') \
|
||||
cat <(cat ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?]') \
|
||||
<(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
|
||||
)
|
||||
@@ -274,15 +285,15 @@ a_cmds="
|
||||
x_cmds="kill ssh telnet unset unalias export"
|
||||
|
||||
# Preserve existing completion
|
||||
eval $(complete |
|
||||
eval "$(complete |
|
||||
sed -E '/-F/!d; / _fzf/d; '"/ ($(echo $d_cmds $a_cmds $x_cmds | sed 's/ /|/g; s/+/\\+/g'))$/"'!d' |
|
||||
_fzf_orig_completion_filter)
|
||||
__fzf_orig_completion_filter)"
|
||||
|
||||
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"
|
||||
@@ -299,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
|
||||
|
||||
@@ -322,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
|
||||
|
@@ -1,4 +1,3 @@
|
||||
#!/bin/zsh
|
||||
# ____ ____
|
||||
# / __/___ / __/
|
||||
# / /_/_ / / /_
|
||||
@@ -10,12 +9,14 @@
|
||||
# - $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() {
|
||||
echo "$1"
|
||||
command find -L "$1" \
|
||||
-name .git -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
||||
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
||||
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||
}
|
||||
fi
|
||||
@@ -23,7 +24,7 @@ fi
|
||||
if ! declare -f _fzf_compgen_dir > /dev/null; then
|
||||
_fzf_compgen_dir() {
|
||||
command find -L "$1" \
|
||||
-name .git -prune -o -name .svn -prune -o -type d \
|
||||
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
|
||||
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||
}
|
||||
fi
|
||||
@@ -37,8 +38,7 @@ __fzfcmd_complete() {
|
||||
|
||||
__fzf_generic_path_completion() {
|
||||
local base lbuf compgen fzf_opts suffix tail fzf dir leftover matches
|
||||
# (Q) flag removes a quoting level: "foo\ bar" => "foo bar"
|
||||
base=${(Q)1}
|
||||
base=$1
|
||||
lbuf=$2
|
||||
compgen=$3
|
||||
fzf_opts=$4
|
||||
@@ -47,14 +47,14 @@ __fzf_generic_path_completion() {
|
||||
fzf="$(__fzfcmd_complete)"
|
||||
|
||||
setopt localoptions nonomatch
|
||||
dir="$base"
|
||||
eval "base=$base"
|
||||
[[ $base = *"/"* ]] && dir="$base"
|
||||
while [ 1 ]; do
|
||||
if [[ -z "$dir" || -d ${~dir} ]]; then
|
||||
if [[ -z "$dir" || -d ${dir} ]]; then
|
||||
leftover=${base/#"$dir"}
|
||||
leftover=${leftover/#\/}
|
||||
[ -z "$dir" ] && dir='.'
|
||||
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
||||
dir=${~dir}
|
||||
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" ${=fzf} ${=fzf_opts} -q "$leftover" | while read item; do
|
||||
echo -n "${(q)item}$suffix "
|
||||
done)
|
||||
@@ -62,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")
|
||||
@@ -102,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"
|
||||
}
|
||||
|
||||
@@ -116,8 +114,9 @@ _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}') \
|
||||
<(command grep -oE '^[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | awk '{ print $1 " " $1 }') \
|
||||
setopt localoptions nonomatch
|
||||
command cat <(cat ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?]') \
|
||||
<(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
|
||||
)
|
||||
@@ -159,16 +158,21 @@ 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
|
||||
fzf="$(__fzfcmd_complete)"
|
||||
matches=$(ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS --preview 'echo {}' --preview-window down:3:wrap $FZF_COMPLETION_OPTS" ${=fzf} -m | awk '{print $2}' | tr '\n' ' ')
|
||||
matches=$(command ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS --preview 'echo {}' --preview-window down:3:wrap $FZF_COMPLETION_OPTS" ${=fzf} -m | awk '{print $2}' | tr '\n' ' ')
|
||||
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})
|
||||
@@ -197,3 +201,5 @@ fzf-completion() {
|
||||
|
||||
zle -N fzf-completion
|
||||
bindkey '^I' fzf-completion
|
||||
|
||||
fi
|
||||
|
@@ -55,8 +55,8 @@ __fzf_history__() (
|
||||
local line
|
||||
shopt -u nocaseglob nocasematch
|
||||
line=$(
|
||||
HISTTIMEFORMAT= history |
|
||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS --tac -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m" $(__fzfcmd) |
|
||||
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
|
||||
sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line"
|
||||
@@ -80,7 +80,7 @@ if [[ ! -o vi ]]; then
|
||||
fi
|
||||
|
||||
# CTRL-R - Paste the selected command from history into the command line
|
||||
bind '"\C-r": " \C-e\C-u`__fzf_history__`\e\C-e\e^\er"'
|
||||
bind '"\C-r": " \C-e\C-u\C-y\ey\C-u`__fzf_history__`\e\C-e\er\e^"'
|
||||
|
||||
# ALT-C - cd into the selected directory
|
||||
bind '"\ec": " \C-e\C-u`__fzf_cd__`\e\C-e\er\C-m"'
|
||||
@@ -110,7 +110,7 @@ else
|
||||
bind -m vi-command '"\C-t": "i\C-t"'
|
||||
|
||||
# CTRL-R - Paste the selected command from history into the command line
|
||||
bind '"\C-r": "\C-x\C-addi`__fzf_history__`\C-x\C-e\C-x^\C-x\C-a$a\C-x\C-r"'
|
||||
bind '"\C-r": "\C-x\C-addi`__fzf_history__`\C-x\C-e\C-x\C-r\C-x^\C-x\C-a$a"'
|
||||
bind -m vi-command '"\C-r": "i\C-r"'
|
||||
|
||||
# ALT-C - cd into the selected directory
|
||||
|
@@ -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,27 +29,36 @@ __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
|
||||
bindkey '^T' fzf-file-widget
|
||||
|
||||
# Ensure precmds are run after cd
|
||||
fzf-redraw-prompt() {
|
||||
local precmd
|
||||
for precmd in $precmd_functions; do
|
||||
$precmd
|
||||
done
|
||||
zle reset-prompt
|
||||
}
|
||||
zle -N fzf-redraw-prompt
|
||||
|
||||
# ALT-C - cd into the selected directory
|
||||
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
|
||||
return 0
|
||||
fi
|
||||
cd "$dir"
|
||||
unset dir # ensure this doesn't end up appearing in prompt expansion
|
||||
local ret=$?
|
||||
zle reset-prompt
|
||||
typeset -f zle-line-init >/dev/null && zle zle-line-init
|
||||
zle fzf-redraw-prompt
|
||||
return $ret
|
||||
}
|
||||
zle -N fzf-cd-widget
|
||||
@@ -58,9 +67,9 @@ 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
|
||||
selected=( $(fc -l 1 |
|
||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS --tac -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
|
||||
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=$?
|
||||
if [ -n "$selected" ]; then
|
||||
num=$selected[1]
|
||||
@@ -68,8 +77,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"
|
||||
@@ -26,7 +27,7 @@ func TestExtractColor(t *testing.T) {
|
||||
output, ansiOffsets, newState := extractColor(src, state, nil)
|
||||
state = newState
|
||||
if output != "hello world" {
|
||||
t.Errorf("Invalid output: %s %s", output, []rune(output))
|
||||
t.Errorf("Invalid output: %s %v", output, []rune(output))
|
||||
}
|
||||
fmt.Println(src, ansiOffsets, clean)
|
||||
assertion(ansiOffsets, state)
|
||||
@@ -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.1"
|
||||
version = "0.20.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
|
||||
@@ -55,11 +59,11 @@ var defaultCommand string
|
||||
|
||||
func init() {
|
||||
if !util.IsWindows() {
|
||||
defaultCommand = `set -o pipefail; (command find -L . -mindepth 1 \( -path '*/\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print || command find -L . -mindepth 1 -path '*/\.*' -prune -o -type f -print -o -type l -print) 2> /dev/null | cut -b3-`
|
||||
defaultCommand = `set -o pipefail; command find -L . -mindepth 1 \( -path '*/\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-`
|
||||
} else if os.Getenv("TERM") == "cygwin" {
|
||||
defaultCommand = `sh -c "command find -L . -mindepth 1 -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-"`
|
||||
} else {
|
||||
defaultCommand = `dir /s/b`
|
||||
defaultCommand = `for /r %P in (*) do @(set "_curfile=%P" & set "_curfile=!_curfile:%__CD__%=!" & echo !_curfile!)`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +79,7 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
exitCancel = -1
|
||||
exitOk = 0
|
||||
exitNoMatch = 1
|
||||
exitError = 2
|
||||
|
92
src/core.go
92
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)
|
||||
@@ -203,11 +224,28 @@ func Run(opts *Options, revision string) {
|
||||
|
||||
// Event coordination
|
||||
reading := true
|
||||
clearCache := util.Once(false)
|
||||
clearSelection := util.Once(false)
|
||||
ticks := 0
|
||||
var nextCommand *string
|
||||
restart := func(command string) {
|
||||
reading = true
|
||||
clearCache = util.Once(true)
|
||||
clearSelection = util.Once(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 +254,39 @@ func Run(opts *Options, revision string) {
|
||||
switch evt {
|
||||
|
||||
case EvtReadNew, EvtReadFin:
|
||||
reading = reading && evt == EvtReadNew
|
||||
snapshot, count := chunkList.Snapshot()
|
||||
terminal.UpdateCount(count, !reading, value.(bool))
|
||||
if opts.Sync {
|
||||
terminal.UpdateList(PassMerger(&snapshot, opts.Tac))
|
||||
if evt == EvtReadFin && nextCommand != nil {
|
||||
restart(*nextCommand)
|
||||
nextCommand = nil
|
||||
break
|
||||
} else {
|
||||
reading = reading && evt == EvtReadNew
|
||||
}
|
||||
matcher.Reset(snapshot, terminal.Input(), false, !reading, sort)
|
||||
snapshot, count := chunkList.Snapshot()
|
||||
terminal.UpdateCount(count, !reading, value.(*string))
|
||||
if opts.Sync {
|
||||
opts.Sync = false
|
||||
terminal.UpdateList(PassMerger(&snapshot, opts.Tac), false)
|
||||
}
|
||||
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)
|
||||
}
|
||||
break
|
||||
}
|
||||
snapshot, _ := chunkList.Snapshot()
|
||||
matcher.Reset(snapshot, terminal.Input(), true, !reading, sort)
|
||||
matcher.Reset(snapshot, input(), true, !reading, sort, clearCache())
|
||||
delay = false
|
||||
|
||||
case EvtSearchProgress:
|
||||
@@ -240,7 +296,9 @@ func Run(opts *Options, revision string) {
|
||||
}
|
||||
|
||||
case EvtHeader:
|
||||
terminal.UpdateHeader(value.([]string))
|
||||
headerPadded := make([]string, opts.HeaderLines)
|
||||
copy(headerPadded, value.([]string))
|
||||
terminal.UpdateHeader(headerPadded)
|
||||
|
||||
case EvtSearchFin:
|
||||
switch val := value.(type) {
|
||||
@@ -270,7 +328,7 @@ func Run(opts *Options, revision string) {
|
||||
terminal.startChan <- true
|
||||
}
|
||||
}
|
||||
terminal.UpdateList(val)
|
||||
terminal.UpdateList(val, clearSelection())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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})
|
||||
}
|
||||
|
189
src/options.go
189
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
|
||||
@@ -53,10 +54,10 @@ const usage = `usage: fzf [options]
|
||||
height instead of using fullscreen
|
||||
--min-height=HEIGHT Minimum height when --height is given in percent
|
||||
(default: 10)
|
||||
--reverse Reverse orientation
|
||||
--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
|
||||
@@ -90,7 +91,8 @@ const usage = `usage: fzf [options]
|
||||
|
||||
Environment variables
|
||||
FZF_DEFAULT_COMMAND Default command to use when input is tty
|
||||
FZF_DEFAULT_OPTS Default options (e.g. '--reverse --inline-info')
|
||||
FZF_DEFAULT_OPTS Default options
|
||||
(e.g. '--layout=reverse --inline-info')
|
||||
|
||||
`
|
||||
|
||||
@@ -132,12 +134,29 @@ const (
|
||||
posRight
|
||||
)
|
||||
|
||||
type layoutType int
|
||||
|
||||
const (
|
||||
layoutDefault layoutType = iota
|
||||
layoutReverse
|
||||
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
|
||||
@@ -145,6 +164,7 @@ type Options struct {
|
||||
Fuzzy bool
|
||||
FuzzyAlgo algo.Algo
|
||||
Extended bool
|
||||
Phony bool
|
||||
Case Case
|
||||
Normalize bool
|
||||
Nth []Range
|
||||
@@ -153,7 +173,7 @@ type Options struct {
|
||||
Sort int
|
||||
Tac bool
|
||||
Criteria []criterion
|
||||
Multi bool
|
||||
Multi int
|
||||
Ansi bool
|
||||
Mouse bool
|
||||
Theme *tui.ColorTheme
|
||||
@@ -161,12 +181,12 @@ type Options struct {
|
||||
Bold bool
|
||||
Height sizeSpec
|
||||
MinHeight int
|
||||
Reverse bool
|
||||
Layout layoutType
|
||||
Cycle bool
|
||||
Hscroll bool
|
||||
HscrollOff int
|
||||
FileWord bool
|
||||
InlineInfo bool
|
||||
InfoStyle infoStyle
|
||||
JumpLabels string
|
||||
Prompt string
|
||||
Query string
|
||||
@@ -180,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
|
||||
@@ -196,6 +218,7 @@ func defaultOptions() *Options {
|
||||
Fuzzy: true,
|
||||
FuzzyAlgo: algo.FuzzyMatchV2,
|
||||
Extended: true,
|
||||
Phony: false,
|
||||
Case: CaseSmart,
|
||||
Normalize: true,
|
||||
Nth: make([]Range, 0),
|
||||
@@ -204,19 +227,19 @@ func defaultOptions() *Options {
|
||||
Sort: 1000,
|
||||
Tac: false,
|
||||
Criteria: []criterion{byScore, byLength},
|
||||
Multi: false,
|
||||
Multi: 0,
|
||||
Ansi: false,
|
||||
Mouse: true,
|
||||
Theme: tui.EmptyTheme(),
|
||||
Black: false,
|
||||
Bold: true,
|
||||
MinHeight: 10,
|
||||
Reverse: false,
|
||||
Layout: layoutDefault,
|
||||
Cycle: false,
|
||||
Hscroll: true,
|
||||
HscrollOff: 10,
|
||||
FileWord: false,
|
||||
InlineInfo: false,
|
||||
InfoStyle: infoDefault,
|
||||
JumpLabels: defaultJumpLabels,
|
||||
Prompt: "> ",
|
||||
Query: "",
|
||||
@@ -226,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)
|
||||
}
|
||||
|
||||
@@ -301,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 {
|
||||
@@ -372,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, ",")
|
||||
}
|
||||
|
||||
@@ -400,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":
|
||||
@@ -410,6 +444,14 @@ func parseKeyChords(str string, message string) map[int]string {
|
||||
chord = tui.AltSlash
|
||||
case "alt-bs", "alt-bspace":
|
||||
chord = tui.AltBS
|
||||
case "alt-up":
|
||||
chord = tui.AltUp
|
||||
case "alt-down":
|
||||
chord = tui.AltDown
|
||||
case "alt-left":
|
||||
chord = tui.AltLeft
|
||||
case "alt-right":
|
||||
chord = tui.AltRight
|
||||
case "tab":
|
||||
chord = tui.Tab
|
||||
case "btab", "shift-tab":
|
||||
@@ -426,10 +468,18 @@ func parseKeyChords(str string, message string) map[int]string {
|
||||
chord = tui.PgUp
|
||||
case "pgdn", "page-down":
|
||||
chord = tui.PgDn
|
||||
case "shift-up":
|
||||
chord = tui.SUp
|
||||
case "shift-down":
|
||||
chord = tui.SDown
|
||||
case "shift-left":
|
||||
chord = tui.SLeft
|
||||
case "shift-right":
|
||||
chord = tui.SRight
|
||||
case "left-click":
|
||||
chord = tui.LeftClick
|
||||
case "right-click":
|
||||
chord = tui.RightClick
|
||||
case "double-click":
|
||||
chord = tui.DoubleClick
|
||||
case "f10":
|
||||
@@ -547,10 +597,16 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
|
||||
theme.Fg = ansi
|
||||
case "bg":
|
||||
theme.Bg = ansi
|
||||
case "preview-fg":
|
||||
theme.PreviewFg = ansi
|
||||
case "preview-bg":
|
||||
theme.PreviewBg = ansi
|
||||
case "fg+":
|
||||
theme.Current = ansi
|
||||
case "bg+":
|
||||
theme.DarkBg = ansi
|
||||
case "gutter":
|
||||
theme.Gutter = ansi
|
||||
case "hl":
|
||||
theme.Match = ansi
|
||||
case "hl+":
|
||||
@@ -596,13 +652,19 @@ 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)] == '-' {
|
||||
symbol := ":"
|
||||
if strings.HasPrefix(src, "+") {
|
||||
symbol = "+"
|
||||
}
|
||||
prefix := symbol + "execute"
|
||||
if strings.HasPrefix(src[1:], "reload") {
|
||||
prefix = symbol + "reload"
|
||||
} else if src[len(prefix)] == '-' {
|
||||
c := src[len(prefix)+1]
|
||||
if c == 's' || c == 'S' {
|
||||
prefix += "-silent"
|
||||
@@ -658,8 +720,12 @@ func parseKeymap(keymap map[int][]action, str string) {
|
||||
appendAction(actAbort)
|
||||
case "accept":
|
||||
appendAction(actAccept)
|
||||
case "accept-non-empty":
|
||||
appendAction(actAcceptNonEmpty)
|
||||
case "print-query":
|
||||
appendAction(actPrintQuery)
|
||||
case "replace-query":
|
||||
appendAction(actReplaceQuery)
|
||||
case "backward-char":
|
||||
appendAction(actBackwardChar)
|
||||
case "backward-delete-char":
|
||||
@@ -676,6 +742,10 @@ func parseKeymap(keymap map[int][]action, str string) {
|
||||
appendAction(actEndOfLine)
|
||||
case "cancel":
|
||||
appendAction(actCancel)
|
||||
case "clear-query":
|
||||
appendAction(actClearQuery)
|
||||
case "clear-selection":
|
||||
appendAction(actClearSelection)
|
||||
case "forward-char":
|
||||
appendAction(actForwardChar)
|
||||
case "forward-word":
|
||||
@@ -747,10 +817,16 @@ func parseKeymap(keymap map[int][]action, str string) {
|
||||
default:
|
||||
t := isExecuteAction(specLower)
|
||||
if t == actIgnore {
|
||||
errorExit("unknown action: " + spec)
|
||||
if specIndex == 0 && specLower == "" {
|
||||
actions = append(keymap[key], actions...)
|
||||
} else {
|
||||
errorExit("unknown action: " + spec)
|
||||
}
|
||||
} else {
|
||||
var offset int
|
||||
switch t {
|
||||
case actReload:
|
||||
offset = len("reload")
|
||||
case actExecuteSilent:
|
||||
offset = len("execute-silent")
|
||||
case actExecuteMulti:
|
||||
@@ -786,6 +862,8 @@ func isExecuteAction(str string) actionType {
|
||||
prefix = matches[0][2]
|
||||
}
|
||||
switch prefix {
|
||||
case "reload":
|
||||
return actReload
|
||||
case "execute":
|
||||
return actExecute
|
||||
case "execute-silent":
|
||||
@@ -837,6 +915,34 @@ func parseHeight(str string) sizeSpec {
|
||||
return size
|
||||
}
|
||||
|
||||
func parseLayout(str string) layoutType {
|
||||
switch str {
|
||||
case "default":
|
||||
return layoutDefault
|
||||
case "reverse":
|
||||
return layoutReverse
|
||||
case "reverse-list":
|
||||
return layoutReverseList
|
||||
default:
|
||||
errorExit("invalid layout (expected: default / reverse / reverse-list)")
|
||||
}
|
||||
return layoutDefault
|
||||
}
|
||||
|
||||
func 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
|
||||
@@ -848,6 +954,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":
|
||||
@@ -860,6 +967,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")
|
||||
@@ -964,6 +1075,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":
|
||||
@@ -984,7 +1099,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":
|
||||
@@ -996,9 +1111,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":
|
||||
@@ -1017,10 +1132,13 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
opts.Bold = true
|
||||
case "--no-bold":
|
||||
opts.Bold = false
|
||||
case "--layout":
|
||||
opts.Layout = parseLayout(
|
||||
nextString(allArgs, &i, "layout required (default / reverse / reverse-list)"))
|
||||
case "--reverse":
|
||||
opts.Reverse = true
|
||||
opts.Layout = layoutReverse
|
||||
case "--no-reverse":
|
||||
opts.Reverse = false
|
||||
opts.Layout = layoutDefault
|
||||
case "--cycle":
|
||||
opts.Cycle = true
|
||||
case "--no-cycle":
|
||||
@@ -1035,10 +1153,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
|
||||
@@ -1056,8 +1179,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":
|
||||
@@ -1091,7 +1216,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":
|
||||
@@ -1104,6 +1229,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)"))
|
||||
@@ -1132,10 +1261,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 {
|
||||
|
@@ -50,7 +50,7 @@ func TestDelimiterRegexString(t *testing.T) {
|
||||
tokens[2].text.ToString() != "---*" ||
|
||||
tokens[3].text.ToString() != "*" ||
|
||||
tokens[4].text.ToString() != "---" {
|
||||
t.Errorf("%s %s %d", delim, tokens, len(tokens))
|
||||
t.Errorf("%s %v %d", delim, tokens, len(tokens))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ func TestSplitNth(t *testing.T) {
|
||||
if len(ranges) != 1 ||
|
||||
ranges[0].begin != rangeEllipsis ||
|
||||
ranges[0].end != rangeEllipsis {
|
||||
t.Errorf("%s", ranges)
|
||||
t.Errorf("%v", ranges)
|
||||
}
|
||||
}
|
||||
{
|
||||
@@ -87,7 +87,7 @@ func TestSplitNth(t *testing.T) {
|
||||
ranges[7].begin != -2 || ranges[7].end != -2 ||
|
||||
ranges[8].begin != 2 || ranges[8].end != -2 ||
|
||||
ranges[9].begin != rangeEllipsis || ranges[9].end != rangeEllipsis {
|
||||
t.Errorf("%s", ranges)
|
||||
t.Errorf("%v", ranges)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,7 +99,7 @@ func TestIrrelevantNth(t *testing.T) {
|
||||
parseOptions(opts, words)
|
||||
postProcessOptions(opts)
|
||||
if len(opts.Nth) != 0 {
|
||||
t.Errorf("nth should be empty: %s", opts.Nth)
|
||||
t.Errorf("nth should be empty: %v", opts.Nth)
|
||||
}
|
||||
}
|
||||
for _, words := range [][]string{[]string{"--nth", "..,3", "+x"}, []string{"--nth", "3,1..", "+x"}, []string{"--nth", "..-1,1", "+x"}} {
|
||||
@@ -108,7 +108,7 @@ func TestIrrelevantNth(t *testing.T) {
|
||||
parseOptions(opts, words)
|
||||
postProcessOptions(opts)
|
||||
if len(opts.Nth) != 0 {
|
||||
t.Errorf("nth should be empty: %s", opts.Nth)
|
||||
t.Errorf("nth should be empty: %v", opts.Nth)
|
||||
}
|
||||
}
|
||||
{
|
||||
@@ -117,7 +117,7 @@ func TestIrrelevantNth(t *testing.T) {
|
||||
parseOptions(opts, words)
|
||||
postProcessOptions(opts)
|
||||
if len(opts.Nth) != 2 {
|
||||
t.Errorf("nth should not be empty: %s", opts.Nth)
|
||||
t.Errorf("nth should not be empty: %v", opts.Nth)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -243,9 +243,10 @@ func TestBind(t *testing.T) {
|
||||
check(tui.CtrlA, "", actBeginningOfLine)
|
||||
parseKeymap(keymap,
|
||||
"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+
|
||||
"f1:execute(ls {})+abort,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
|
||||
"f1:execute(ls {+})+abort+execute(echo {+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
|
||||
"alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+
|
||||
"x:Execute(foo+bar),X:execute/bar+baz/"+
|
||||
",f1:+top,f1:+top"+
|
||||
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up")
|
||||
check(tui.CtrlA, "", actKillLine)
|
||||
check(tui.CtrlB, "", actToggleSort, actUp, actDown)
|
||||
@@ -253,7 +254,7 @@ func TestBind(t *testing.T) {
|
||||
check(tui.AltZ+',', "", actAbort)
|
||||
check(tui.AltZ+':', "", actAccept)
|
||||
check(tui.AltZ, "", actPageDown)
|
||||
check(tui.F1, "ls {}", actExecute, actAbort)
|
||||
check(tui.F1, "ls {+}", actExecute, actAbort, actExecute, actSelectAll, actTop, actTop)
|
||||
check(tui.F2, "echo {}, {}, {}", actExecute)
|
||||
check(tui.F3, "echo '({})'", actExecute)
|
||||
check(tui.F4, "less {}", actExecute)
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package fzf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -34,6 +35,11 @@ type term struct {
|
||||
caseSensitive bool
|
||||
}
|
||||
|
||||
// String returns the string representation of a term.
|
||||
func (t term) String() string {
|
||||
return fmt.Sprintf("term{typ: %d, inv: %v, text: []rune(%q), caseSensitive: %v}", t.typ, t.inv, string(t.text), t.caseSensitive)
|
||||
}
|
||||
|
||||
type termSet []term
|
||||
|
||||
// Pattern represents search pattern
|
||||
@@ -46,6 +52,7 @@ type Pattern struct {
|
||||
forward bool
|
||||
text []rune
|
||||
termSets []termSet
|
||||
sortable bool
|
||||
cacheable bool
|
||||
cacheKey string
|
||||
delimiter Delimiter
|
||||
@@ -95,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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,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,
|
||||
|
@@ -31,12 +31,12 @@ func TestParseTermsExtended(t *testing.T) {
|
||||
terms[8][1].typ != termExact || terms[8][1].inv ||
|
||||
terms[8][2].typ != termSuffix || terms[8][2].inv ||
|
||||
terms[8][3].typ != termExact || !terms[8][3].inv {
|
||||
t.Errorf("%s", terms)
|
||||
t.Errorf("%v", terms)
|
||||
}
|
||||
for _, termSet := range terms[:8] {
|
||||
term := termSet[0]
|
||||
if len(term.text) != 3 {
|
||||
t.Errorf("%s", term)
|
||||
t.Errorf("%v", term)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,14 +53,14 @@ func TestParseTermsExtendedExact(t *testing.T) {
|
||||
terms[5][0].typ != termFuzzy || !terms[5][0].inv || len(terms[5][0].text) != 3 ||
|
||||
terms[6][0].typ != termPrefix || !terms[6][0].inv || len(terms[6][0].text) != 3 ||
|
||||
terms[7][0].typ != termSuffix || !terms[7][0].inv || len(terms[7][0].text) != 3 {
|
||||
t.Errorf("%s", terms)
|
||||
t.Errorf("%v", terms)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTermsEmpty(t *testing.T) {
|
||||
terms := parseTerms(true, CaseSmart, false, "' ^ !' !^")
|
||||
if len(terms) != 0 {
|
||||
t.Errorf("%s", terms)
|
||||
t.Errorf("%v", terms)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ func TestExact(t *testing.T) {
|
||||
res, pos := algo.ExactMatchNaive(
|
||||
pattern.caseSensitive, pattern.normalize, pattern.forward, &chars, pattern.termSets[0][0].text, true, nil)
|
||||
if res.Start != 7 || res.End != 10 {
|
||||
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
|
||||
t.Errorf("%v / %d / %d", pattern.termSets, res.Start, res.End)
|
||||
}
|
||||
if pos != nil {
|
||||
t.Errorf("pos is expected to be nil")
|
||||
@@ -90,7 +90,7 @@ func TestEqual(t *testing.T) {
|
||||
res, pos := algo.EqualMatch(
|
||||
pattern.caseSensitive, pattern.normalize, pattern.forward, &chars, pattern.termSets[0][0].text, true, nil)
|
||||
if res.Start != sidxExpected || res.End != eidxExpected {
|
||||
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
|
||||
t.Errorf("%v / %d / %d", pattern.termSets, res.Start, res.End)
|
||||
}
|
||||
if pos != nil {
|
||||
t.Errorf("pos is expected to be nil")
|
||||
|
@@ -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 {
|
||||
|
680
src/terminal.go
680
src/terminal.go
File diff suppressed because it is too large
Load Diff
@@ -21,74 +21,101 @@ func TestReplacePlaceholder(t *testing.T) {
|
||||
newItem("foo'bar \x1b[31mbaz\x1b[m"),
|
||||
newItem("FOO'BAR \x1b[31mBAZ\x1b[m")}
|
||||
|
||||
delim := "'"
|
||||
var regex *regexp.Regexp
|
||||
|
||||
var result string
|
||||
check := func(expected string) {
|
||||
if result != expected {
|
||||
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}, printsep, false, "query", items1)
|
||||
check("echo ' foo'")
|
||||
|
||||
result = replacePlaceholder("echo {s2}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
||||
check("echo 'bar baz'")
|
||||
|
||||
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}, printsep, false, "query", items1)
|
||||
check("echo ' foo'\\''bar baz'")
|
||||
|
||||
// Whitespace preserving flag with regex delimiter
|
||||
regex = regexp.MustCompile(`\w+`)
|
||||
|
||||
result = replacePlaceholder("echo {s1}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
||||
check("echo ' '")
|
||||
|
||||
result = replacePlaceholder("echo {s2}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
||||
check("echo ''\\'''")
|
||||
|
||||
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
|
||||
delim := "'"
|
||||
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]+")
|
||||
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'")
|
||||
}
|
||||
|
||||
|
@@ -2,6 +2,7 @@ package fzf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -23,12 +24,22 @@ type Token struct {
|
||||
prefixLength int32
|
||||
}
|
||||
|
||||
// String returns the string representation of a Token.
|
||||
func (t Token) String() string {
|
||||
return fmt.Sprintf("Token{text: %s, prefixLength: %d}", t.text, t.prefixLength)
|
||||
}
|
||||
|
||||
// Delimiter for tokenizing the input
|
||||
type Delimiter struct {
|
||||
regex *regexp.Regexp
|
||||
str *string
|
||||
}
|
||||
|
||||
// String returns the string representation of a Delimeter.
|
||||
func (d Delimiter) String() string {
|
||||
return fmt.Sprintf("Delimiter{regex: %v, str: &%q}", d.regex, *d.str)
|
||||
}
|
||||
|
||||
func newRange(begin int, end int) Range {
|
||||
if begin == 1 {
|
||||
begin = rangeEllipsis
|
||||
@@ -227,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
|
||||
|
@@ -9,35 +9,35 @@ func TestParseRange(t *testing.T) {
|
||||
i := ".."
|
||||
r, _ := ParseRange(&i)
|
||||
if r.begin != rangeEllipsis || r.end != rangeEllipsis {
|
||||
t.Errorf("%s", r)
|
||||
t.Errorf("%v", r)
|
||||
}
|
||||
}
|
||||
{
|
||||
i := "3.."
|
||||
r, _ := ParseRange(&i)
|
||||
if r.begin != 3 || r.end != rangeEllipsis {
|
||||
t.Errorf("%s", r)
|
||||
t.Errorf("%v", r)
|
||||
}
|
||||
}
|
||||
{
|
||||
i := "3..5"
|
||||
r, _ := ParseRange(&i)
|
||||
if r.begin != 3 || r.end != 5 {
|
||||
t.Errorf("%s", r)
|
||||
t.Errorf("%v", r)
|
||||
}
|
||||
}
|
||||
{
|
||||
i := "-3..-5"
|
||||
r, _ := ParseRange(&i)
|
||||
if r.begin != -3 || r.end != -5 {
|
||||
t.Errorf("%s", r)
|
||||
t.Errorf("%v", r)
|
||||
}
|
||||
}
|
||||
{
|
||||
i := "3"
|
||||
r, _ := ParseRange(&i)
|
||||
if r.begin != 3 || r.end != 3 {
|
||||
t.Errorf("%s", r)
|
||||
t.Errorf("%v", r)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 ||
|
||||
|
@@ -39,6 +39,6 @@ func (r *FullscreenRenderer) MaxY() int { return 0 }
|
||||
|
||||
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {}
|
||||
|
||||
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window {
|
||||
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window {
|
||||
return nil
|
||||
}
|
||||
|
141
src/tui/light.go
141
src/tui/light.go
@@ -27,11 +27,17 @@ 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)
|
||||
if err != nil {
|
||||
tty := ttyname()
|
||||
if len(tty) > 0 {
|
||||
if in, err := os.OpenFile(tty, syscall.O_RDONLY, 0); err == nil {
|
||||
return in
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(os.Stderr, "Failed to open "+consoleDevice)
|
||||
os.Exit(2)
|
||||
}
|
||||
@@ -48,11 +54,13 @@ func (r *LightRenderer) stderrInternal(str string, allowNLCR bool) {
|
||||
runes := []rune{}
|
||||
for len(bytes) > 0 {
|
||||
r, sz := utf8.DecodeRune(bytes)
|
||||
if r == utf8.RuneError || r < 32 &&
|
||||
r != '\x1b' && (!allowNLCR || r != '\n' && r != '\r') {
|
||||
runes = append(runes, '?')
|
||||
} else {
|
||||
runes = append(runes, r)
|
||||
nlcr := r == '\n' || r == '\r'
|
||||
if r >= 32 || r == '\x1b' || nlcr {
|
||||
if r == utf8.RuneError || nlcr && !allowNLCR {
|
||||
runes = append(runes, ' ')
|
||||
} else {
|
||||
runes = append(runes, r)
|
||||
}
|
||||
}
|
||||
bytes = bytes[sz:]
|
||||
}
|
||||
@@ -146,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 ""
|
||||
}
|
||||
@@ -289,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 {
|
||||
@@ -298,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
|
||||
@@ -330,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
|
||||
@@ -360,7 +383,14 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
if r.buffer[1] >= 1 && r.buffer[1] <= 'z'-'a'+1 {
|
||||
return Event{int(CtrlAltA + r.buffer[1] - 1), 0, nil}
|
||||
}
|
||||
alt := false
|
||||
if len(r.buffer) > 2 && r.buffer[1] == ESC {
|
||||
r.buffer = r.buffer[1:]
|
||||
alt = true
|
||||
}
|
||||
switch r.buffer[1] {
|
||||
case ESC:
|
||||
return Event{ESC, 0, nil}
|
||||
case 32:
|
||||
return Event{AltSpace, 0, nil}
|
||||
case 47:
|
||||
@@ -380,12 +410,25 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
*sz = 3
|
||||
switch r.buffer[2] {
|
||||
case 68:
|
||||
if alt {
|
||||
return Event{AltLeft, 0, nil}
|
||||
}
|
||||
return Event{Left, 0, nil}
|
||||
case 67:
|
||||
if alt {
|
||||
// Ugh..
|
||||
return Event{AltRight, 0, nil}
|
||||
}
|
||||
return Event{Right, 0, nil}
|
||||
case 66:
|
||||
if alt {
|
||||
return Event{AltDown, 0, nil}
|
||||
}
|
||||
return Event{Down, 0, nil}
|
||||
case 65:
|
||||
if alt {
|
||||
return Event{AltUp, 0, nil}
|
||||
}
|
||||
return Event{Up, 0, nil}
|
||||
case 90:
|
||||
return Event{BTab, 0, nil}
|
||||
@@ -458,25 +501,22 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
}
|
||||
}
|
||||
return Event{Invalid, 0, nil}
|
||||
case 59:
|
||||
case ';':
|
||||
if len(r.buffer) != 6 {
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
*sz = 6
|
||||
switch r.buffer[4] {
|
||||
case 50:
|
||||
case '2', '5':
|
||||
switch r.buffer[5] {
|
||||
case 68:
|
||||
return Event{Home, 0, nil}
|
||||
case 67:
|
||||
return Event{End, 0, nil}
|
||||
}
|
||||
case 53:
|
||||
switch r.buffer[5] {
|
||||
case 68:
|
||||
return Event{SLeft, 0, nil}
|
||||
case 67:
|
||||
case 'A':
|
||||
return Event{SUp, 0, nil}
|
||||
case 'B':
|
||||
return Event{SDown, 0, nil}
|
||||
case 'C':
|
||||
return Event{SRight, 0, nil}
|
||||
case 'D':
|
||||
return Event{SLeft, 0, nil}
|
||||
}
|
||||
} // r.buffer[4]
|
||||
} // r.buffer[3]
|
||||
@@ -486,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}
|
||||
}
|
||||
|
||||
@@ -495,16 +538,19 @@ func (r *LightRenderer) mouseSequence(sz *int) Event {
|
||||
}
|
||||
*sz = 6
|
||||
switch r.buffer[3] {
|
||||
case 32, 36, 40, 48, // mouse-down / shift / cmd / ctrl
|
||||
case 32, 34, 36, 40, 48, // mouse-down / shift / cmd / ctrl
|
||||
35, 39, 43, 51: // mouse-up / shift / cmd / ctrl
|
||||
mod := r.buffer[3] >= 36
|
||||
left := r.buffer[3] == 32
|
||||
down := r.buffer[3]%2 == 0
|
||||
x := int(r.buffer[4] - 33)
|
||||
y := int(r.buffer[5]-33) - r.yoffset
|
||||
double := false
|
||||
if down {
|
||||
now := time.Now()
|
||||
if now.Sub(r.prevDownTime) < doubleClickDuration {
|
||||
if !left { // Right double click is not allowed
|
||||
r.clickY = []int{}
|
||||
} else if now.Sub(r.prevDownTime) < doubleClickDuration {
|
||||
r.clickY = append(r.clickY, y)
|
||||
} else {
|
||||
r.clickY = []int{y}
|
||||
@@ -512,19 +558,19 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, down, double, mod}}
|
||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, mod}}
|
||||
case 96, 100, 104, 112, // scroll-up / shift / cmd / ctrl
|
||||
97, 101, 105, 113: // scroll-down / shift / cmd / ctrl
|
||||
mod := r.buffer[3] >= 100
|
||||
s := 1 - int(r.buffer[3]%2)*2
|
||||
x := int(r.buffer[4] - 33)
|
||||
y := int(r.buffer[5]-33) - r.yoffset
|
||||
return Event{Mouse, 0, &MouseEvent{y, x, s, false, false, mod}}
|
||||
return Event{Mouse, 0, &MouseEvent{y, x, s, false, false, false, mod}}
|
||||
}
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
@@ -620,7 +666,7 @@ func (r *LightRenderer) DoesAutoWrap() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *LightRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window {
|
||||
func (r *LightRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window {
|
||||
w := &LightWindow{
|
||||
renderer: r,
|
||||
colored: r.theme != nil,
|
||||
@@ -633,15 +679,20 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, bord
|
||||
fg: colDefault,
|
||||
bg: colDefault}
|
||||
if r.theme != nil {
|
||||
w.fg = r.theme.Fg
|
||||
w.bg = r.theme.Bg
|
||||
if preview {
|
||||
w.fg = r.theme.PreviewFg
|
||||
w.bg = r.theme.PreviewBg
|
||||
} else {
|
||||
w.fg = r.theme.Fg
|
||||
w.bg = r.theme.Bg
|
||||
}
|
||||
}
|
||||
w.drawBorder()
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *LightWindow) drawBorder() {
|
||||
switch w.border {
|
||||
switch w.border.shape {
|
||||
case BorderAround:
|
||||
w.drawBorderAround()
|
||||
case BorderHorizontal:
|
||||
@@ -651,32 +702,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(ColPreviewBorder, 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(ColPreviewBorder, AttrRegular, string(w.border.vertical))
|
||||
w.CPrint(ColPreviewBorder, AttrRegular, repeat(' ', w.width-2))
|
||||
w.CPrint(ColPreviewBorder, AttrRegular, string(w.border.vertical))
|
||||
}
|
||||
w.Move(w.height-1, 0)
|
||||
w.CPrint(ColBorder, AttrRegular, "└"+repeat("─", w.width-2)+"┘")
|
||||
w.CPrint(ColPreviewBorder, 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)
|
||||
}
|
||||
@@ -727,7 +776,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)
|
||||
}
|
||||
|
||||
@@ -789,7 +838,7 @@ func (w *LightWindow) Print(text string) {
|
||||
}
|
||||
|
||||
func cleanse(str string) string {
|
||||
return strings.Replace(str, "\x1b", "?", -1)
|
||||
return strings.Replace(str, "\x1b", "", -1)
|
||||
}
|
||||
|
||||
func (w *LightWindow) CPrint(pair ColorPair, attr Attr, text string) {
|
||||
@@ -823,7 +872,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
|
||||
|
112
src/tui/tcell.go
112
src/tui/tcell.go
@@ -32,6 +32,7 @@ type TcellWindow struct {
|
||||
left int
|
||||
width int
|
||||
height int
|
||||
normal ColorPair
|
||||
lastX int
|
||||
lastY int
|
||||
moveCursor bool
|
||||
@@ -61,12 +62,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() {
|
||||
@@ -193,19 +190,22 @@ func (r *FullscreenRenderer) GetChar() Event {
|
||||
button := ev.Buttons()
|
||||
mod := ev.Modifiers() != 0
|
||||
if button&tcell.WheelDown != 0 {
|
||||
return Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, mod}}
|
||||
return Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, false, mod}}
|
||||
} else if button&tcell.WheelUp != 0 {
|
||||
return Event{Mouse, 0, &MouseEvent{y, x, +1, false, false, mod}}
|
||||
return Event{Mouse, 0, &MouseEvent{y, x, +1, false, false, false, mod}}
|
||||
} else if runtime.GOOS != "windows" {
|
||||
// double and single taps on Windows don't quite work due to
|
||||
// the console acting on the events and not allowing us
|
||||
// to consume them.
|
||||
|
||||
down := button&tcell.Button1 != 0 // left
|
||||
left := button&tcell.Button1 != 0
|
||||
down := left || button&tcell.Button3 != 0
|
||||
double := false
|
||||
if down {
|
||||
now := time.Now()
|
||||
if now.Sub(r.prevDownTime) < doubleClickDuration {
|
||||
if !left {
|
||||
r.clickY = []int{}
|
||||
} else if now.Sub(r.prevDownTime) < doubleClickDuration {
|
||||
r.clickY = append(r.clickY, x)
|
||||
} else {
|
||||
r.clickY = []int{x}
|
||||
@@ -218,7 +218,7 @@ func (r *FullscreenRenderer) GetChar() Event {
|
||||
}
|
||||
}
|
||||
|
||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, down, double, mod}}
|
||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, mod}}
|
||||
}
|
||||
|
||||
// process keyboard:
|
||||
@@ -285,6 +285,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}
|
||||
@@ -292,12 +298,24 @@ func (r *FullscreenRenderer) GetChar() Event {
|
||||
return Event{BSpace, 0, nil}
|
||||
|
||||
case tcell.KeyUp:
|
||||
if alt {
|
||||
return Event{AltUp, 0, nil}
|
||||
}
|
||||
return Event{Up, 0, nil}
|
||||
case tcell.KeyDown:
|
||||
if alt {
|
||||
return Event{AltDown, 0, nil}
|
||||
}
|
||||
return Event{Down, 0, nil}
|
||||
case tcell.KeyLeft:
|
||||
if alt {
|
||||
return Event{AltLeft, 0, nil}
|
||||
}
|
||||
return Event{Left, 0, nil}
|
||||
case tcell.KeyRight:
|
||||
if alt {
|
||||
return Event{AltRight, 0, nil}
|
||||
}
|
||||
return Event{Right, 0, nil}
|
||||
|
||||
case tcell.KeyHome:
|
||||
@@ -367,12 +385,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() {
|
||||
@@ -387,14 +409,18 @@ func (r *FullscreenRenderer) RefreshWindows(windows []Window) {
|
||||
_screen.Show()
|
||||
}
|
||||
|
||||
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window {
|
||||
// TODO
|
||||
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window {
|
||||
normal := ColNormal
|
||||
if preview {
|
||||
normal = ColPreview
|
||||
}
|
||||
return &TcellWindow{
|
||||
color: r.theme != nil,
|
||||
top: top,
|
||||
left: left,
|
||||
width: width,
|
||||
height: height,
|
||||
normal: normal,
|
||||
borderStyle: borderStyle}
|
||||
}
|
||||
|
||||
@@ -402,16 +428,16 @@ func (w *TcellWindow) Close() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
func fill(x, y, w, h int, r rune) {
|
||||
func fill(x, y, w, h int, n ColorPair, r rune) {
|
||||
for ly := 0; ly <= h; ly++ {
|
||||
for lx := 0; lx <= w; lx++ {
|
||||
_screen.SetContent(x+lx, y+ly, r, nil, ColNormal.style())
|
||||
_screen.SetContent(x+lx, y+ly, r, nil, n.style())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *TcellWindow) Erase() {
|
||||
fill(w.left-1, w.top, w.width+1, w.height, ' ')
|
||||
fill(w.left-1, w.top, w.width+1, w.height, w.normal, ' ')
|
||||
}
|
||||
|
||||
func (w *TcellWindow) Enclose(y int, x int) bool {
|
||||
@@ -428,13 +454,13 @@ func (w *TcellWindow) Move(y int, x int) {
|
||||
func (w *TcellWindow) MoveAndClear(y int, x int) {
|
||||
w.Move(y, x)
|
||||
for i := w.lastX; i < w.width; i++ {
|
||||
_screen.SetContent(i+w.left, w.lastY+w.top, rune(' '), nil, ColNormal.style())
|
||||
_screen.SetContent(i+w.left, w.lastY+w.top, rune(' '), nil, w.normal.style())
|
||||
}
|
||||
w.lastX = x
|
||||
}
|
||||
|
||||
func (w *TcellWindow) Print(text string) {
|
||||
w.printString(text, ColNormal, 0)
|
||||
w.printString(text, w.normal, 0)
|
||||
}
|
||||
|
||||
func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) {
|
||||
@@ -447,7 +473,7 @@ func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) {
|
||||
Reverse(a&Attr(tcell.AttrReverse) != 0).
|
||||
Underline(a&Attr(tcell.AttrUnderline) != 0)
|
||||
} else {
|
||||
style = ColNormal.style().
|
||||
style = w.normal.style().
|
||||
Reverse(a&Attr(tcell.AttrReverse) != 0 || pair == ColCurrent || pair == ColCurrentMatch).
|
||||
Underline(a&Attr(tcell.AttrUnderline) != 0 || pair == ColMatch || pair == ColCurrentMatch)
|
||||
}
|
||||
@@ -498,7 +524,7 @@ func (w *TcellWindow) fillString(text string, pair ColorPair, a Attr) FillReturn
|
||||
if w.color {
|
||||
style = pair.style()
|
||||
} else {
|
||||
style = ColNormal.style()
|
||||
style = w.normal.style()
|
||||
}
|
||||
style = style.
|
||||
Blink(a&Attr(tcell.AttrBlink) != 0).
|
||||
@@ -538,20 +564,24 @@ func (w *TcellWindow) fillString(text string, pair ColorPair, a Attr) FillReturn
|
||||
}
|
||||
|
||||
func (w *TcellWindow) Fill(str string) FillReturn {
|
||||
return w.fillString(str, ColNormal, 0)
|
||||
return w.fillString(str, w.normal, 0)
|
||||
}
|
||||
|
||||
func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
|
||||
if fg == colDefault {
|
||||
fg = ColNormal.Fg()
|
||||
fg = w.normal.Fg()
|
||||
}
|
||||
if bg == colDefault {
|
||||
bg = ColNormal.Bg()
|
||||
bg = w.normal.Bg()
|
||||
}
|
||||
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
|
||||
@@ -559,25 +589,29 @@ func (w *TcellWindow) drawBorder(around bool) {
|
||||
|
||||
var style tcell.Style
|
||||
if w.color {
|
||||
style = ColBorder.style()
|
||||
if w.borderStyle.shape == BorderAround {
|
||||
style = ColPreviewBorder.style()
|
||||
} else {
|
||||
style = ColBorder.style()
|
||||
}
|
||||
} else {
|
||||
style = ColNormal.style()
|
||||
style = w.normal.style()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
31
src/tui/ttyname_unix.go
Normal file
31
src/tui/ttyname_unix.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// +build !windows
|
||||
|
||||
package tui
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var devPrefixes = [...]string{"/dev/pts/", "/dev/"}
|
||||
|
||||
func ttyname() string {
|
||||
var stderr syscall.Stat_t
|
||||
if syscall.Fstat(2, &stderr) != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, prefix := range devPrefixes {
|
||||
files, err := ioutil.ReadDir(prefix)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if stat, ok := file.Sys().(*syscall.Stat_t); ok && stat.Rdev == stderr.Rdev {
|
||||
return prefix + file.Name()
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
7
src/tui/ttyname_windows.go
Normal file
7
src/tui/ttyname_windows.go
Normal file
@@ -0,0 +1,7 @@
|
||||
// +build windows
|
||||
|
||||
package tui
|
||||
|
||||
func ttyname() string {
|
||||
return ""
|
||||
}
|
142
src/tui/tui.go
142
src/tui/tui.go
@@ -40,10 +40,18 @@ 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
|
||||
DoubleClick
|
||||
LeftClick
|
||||
RightClick
|
||||
|
||||
BTab
|
||||
BSpace
|
||||
@@ -59,6 +67,8 @@ const (
|
||||
Home
|
||||
End
|
||||
|
||||
SUp
|
||||
SDown
|
||||
SLeft
|
||||
SRight
|
||||
|
||||
@@ -81,6 +91,11 @@ const (
|
||||
AltSlash
|
||||
AltBS
|
||||
|
||||
AltUp
|
||||
AltDown
|
||||
AltLeft
|
||||
AltRight
|
||||
|
||||
Alt0
|
||||
)
|
||||
|
||||
@@ -108,7 +123,7 @@ func (c Color) is24() bool {
|
||||
|
||||
const (
|
||||
colUndefined Color = -2
|
||||
colDefault = -1
|
||||
colDefault Color = -1
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -155,14 +170,13 @@ 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
|
||||
PreviewFg Color
|
||||
PreviewBg Color
|
||||
DarkBg Color
|
||||
Gutter Color
|
||||
Prompt Color
|
||||
Match Color
|
||||
Current Color
|
||||
@@ -185,19 +199,66 @@ type MouseEvent struct {
|
||||
Y int
|
||||
X int
|
||||
S int
|
||||
Left bool
|
||||
Down bool
|
||||
Double bool
|
||||
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)
|
||||
@@ -213,7 +274,7 @@ type Renderer interface {
|
||||
MaxY() int
|
||||
DoesAutoWrap() bool
|
||||
|
||||
NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window
|
||||
NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window
|
||||
}
|
||||
|
||||
type Window interface {
|
||||
@@ -262,24 +323,31 @@ 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
|
||||
ColPreview ColorPair
|
||||
ColPreviewBorder ColorPair
|
||||
)
|
||||
|
||||
func EmptyTheme() *ColorTheme {
|
||||
return &ColorTheme{
|
||||
Fg: colUndefined,
|
||||
Bg: colUndefined,
|
||||
PreviewFg: colUndefined,
|
||||
PreviewBg: colUndefined,
|
||||
DarkBg: colUndefined,
|
||||
Gutter: colUndefined,
|
||||
Prompt: colUndefined,
|
||||
Match: colUndefined,
|
||||
Current: colUndefined,
|
||||
@@ -301,7 +369,10 @@ func init() {
|
||||
Default16 = &ColorTheme{
|
||||
Fg: colDefault,
|
||||
Bg: colDefault,
|
||||
PreviewFg: colUndefined,
|
||||
PreviewBg: colUndefined,
|
||||
DarkBg: colBlack,
|
||||
Gutter: colUndefined,
|
||||
Prompt: colBlue,
|
||||
Match: colGreen,
|
||||
Current: colYellow,
|
||||
@@ -315,7 +386,10 @@ func init() {
|
||||
Dark256 = &ColorTheme{
|
||||
Fg: colDefault,
|
||||
Bg: colDefault,
|
||||
PreviewFg: colUndefined,
|
||||
PreviewBg: colUndefined,
|
||||
DarkBg: 236,
|
||||
Gutter: colUndefined,
|
||||
Prompt: 110,
|
||||
Match: 108,
|
||||
Current: 254,
|
||||
@@ -329,7 +403,10 @@ func init() {
|
||||
Light256 = &ColorTheme{
|
||||
Fg: colDefault,
|
||||
Bg: colDefault,
|
||||
PreviewFg: colUndefined,
|
||||
PreviewBg: colUndefined,
|
||||
DarkBg: 251,
|
||||
Gutter: colUndefined,
|
||||
Prompt: 25,
|
||||
Match: 66,
|
||||
Current: 237,
|
||||
@@ -360,7 +437,10 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
|
||||
}
|
||||
theme.Fg = o(baseTheme.Fg, theme.Fg)
|
||||
theme.Bg = o(baseTheme.Bg, theme.Bg)
|
||||
theme.PreviewFg = o(theme.Fg, o(baseTheme.PreviewFg, theme.PreviewFg))
|
||||
theme.PreviewBg = o(theme.Bg, o(baseTheme.PreviewBg, theme.PreviewBg))
|
||||
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)
|
||||
@@ -382,29 +462,37 @@ 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)
|
||||
ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
|
||||
ColPreviewBorder = pair(theme.Border, theme.PreviewBg)
|
||||
} 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)
|
||||
ColPreview = pair(colDefault, colDefault)
|
||||
ColPreviewBorder = pair(colDefault, colDefault)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
@@ -94,6 +95,11 @@ func (chars *Chars) Length() int {
|
||||
return len(chars.slice)
|
||||
}
|
||||
|
||||
// String returns the string representation of a Chars object.
|
||||
func (chars *Chars) String() string {
|
||||
return fmt.Sprintf("Chars{slice: []byte(%q), inBytes: %v, trimLengthKnown: %v, trimLength: %d, Index: %d}", chars.slice, chars.inBytes, chars.trimLengthKnown, chars.trimLength, chars.Index)
|
||||
}
|
||||
|
||||
// TrimLength returns the length after trimming leading and trailing whitespaces
|
||||
func (chars *Chars) TrimLength() uint16 {
|
||||
if chars.trimLengthKnown {
|
||||
@@ -136,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)
|
||||
@@ -163,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...)
|
||||
}
|
||||
}
|
||||
|
@@ -17,11 +17,12 @@ func RuneWidth(r rune, prefixWidth int, tabstop int) int {
|
||||
return tabstop - prefixWidth%tabstop
|
||||
} else if w, found := _runeWidths[r]; found {
|
||||
return w
|
||||
} else {
|
||||
w := Max(runewidth.RuneWidth(r), 1)
|
||||
_runeWidths[r] = w
|
||||
return w
|
||||
} else if r == '\n' || r == '\r' {
|
||||
return 1
|
||||
}
|
||||
w := runewidth.RuneWidth(r)
|
||||
_runeWidths[r] = w
|
||||
return w
|
||||
}
|
||||
|
||||
// Max returns the largest integer
|
||||
@@ -111,3 +112,13 @@ func DurWithin(
|
||||
func IsTty() bool {
|
||||
return isatty.IsTerminal(os.Stdin.Fd())
|
||||
}
|
||||
|
||||
// Once returns a function that returns the specified boolean value only once
|
||||
func Once(nextResponse bool) func() bool {
|
||||
state := nextResponse
|
||||
return func() bool {
|
||||
prevState := state
|
||||
state = false
|
||||
return prevState
|
||||
}
|
||||
}
|
||||
|
@@ -20,3 +20,21 @@ func TestContrain(t *testing.T) {
|
||||
t.Error("Expected", 3)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnce(t *testing.T) {
|
||||
o := Once(false)
|
||||
if o() {
|
||||
t.Error("Expected: false")
|
||||
}
|
||||
if o() {
|
||||
t.Error("Expected: false")
|
||||
}
|
||||
|
||||
o = Once(true)
|
||||
if !o() {
|
||||
t.Error("Expected: true")
|
||||
}
|
||||
if o() {
|
||||
t.Error("Expected: false")
|
||||
}
|
||||
}
|
||||
|
@@ -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,28 +10,35 @@ 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,
|
||||
CmdLine: fmt.Sprintf(` /s /c "%s"`, command),
|
||||
CmdLine: fmt.Sprintf(` /v:on/s/c "%s"`, command),
|
||||
CreationFlags: 0,
|
||||
}
|
||||
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):
|
||||
|
348
test/test_go.rb
Normal file → Executable file
348
test/test_go.rb
Normal file → Executable file
@@ -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?('ERROR') }
|
||||
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/';
|
||||
@@ -769,6 +828,15 @@ class TestGoFZF < TestBase
|
||||
assert_equal %w[print-my-query], readonce.split($INPUT_RECORD_SEPARATOR)
|
||||
end
|
||||
|
||||
def test_bind_replace_query
|
||||
tmux.send_keys "seq 1 1000 | #{fzf '--print-query --bind=ctrl-j:replace-query'}", :Enter
|
||||
tmux.send_keys '1'
|
||||
tmux.until { |lines| lines[-2].end_with? '272/1000' }
|
||||
tmux.send_keys 'C-k', 'C-j'
|
||||
tmux.until { |lines| lines[-2].end_with? '29/1000' }
|
||||
tmux.until { |lines| lines[-1].end_with? '> 10' }
|
||||
end
|
||||
|
||||
def test_long_line
|
||||
data = '.' * 256 * 1024
|
||||
File.open(tempname, 'w') do |f|
|
||||
@@ -797,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
|
||||
|
||||
@@ -864,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
|
||||
@@ -889,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)
|
||||
@@ -1051,6 +1125,21 @@ class TestGoFZF < TestBase
|
||||
assert_equal '50', readonce.chomp
|
||||
end
|
||||
|
||||
def test_header_lines_reverse_list
|
||||
tmux.send_keys "seq 100 | #{fzf '--header-lines=10 -q 5 --layout=reverse-list'}", :Enter
|
||||
2.times do
|
||||
tmux.until do |lines|
|
||||
lines[0] == '> 50' &&
|
||||
lines[-4] == ' 2' &&
|
||||
lines[-3] == ' 1' &&
|
||||
lines[-2].include?('/90')
|
||||
end
|
||||
tmux.send_keys :Up
|
||||
end
|
||||
tmux.send_keys :Enter
|
||||
assert_equal '50', readonce.chomp
|
||||
end
|
||||
|
||||
def test_header_lines_overflow
|
||||
tmux.send_keys "seq 100 | #{fzf '--header-lines=200'}", :Enter
|
||||
tmux.until do |lines|
|
||||
@@ -1078,7 +1167,8 @@ class TestGoFZF < TestBase
|
||||
header = File.readlines(FILE).take(5).map(&:strip)
|
||||
tmux.until do |lines|
|
||||
lines[-2].include?('100/100') &&
|
||||
lines[-7..-3].map(&:strip) == header
|
||||
lines[-7..-3].map(&:strip) == header &&
|
||||
lines[-8] == '> 1'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1087,7 +1177,18 @@ class TestGoFZF < TestBase
|
||||
header = File.readlines(FILE).take(5).map(&:strip)
|
||||
tmux.until do |lines|
|
||||
lines[1].include?('100/100') &&
|
||||
lines[2..6].map(&:strip) == header
|
||||
lines[2..6].map(&:strip) == header &&
|
||||
lines[7] == '> 1'
|
||||
end
|
||||
end
|
||||
|
||||
def test_header_reverse_list
|
||||
tmux.send_keys "seq 100 | #{fzf "--header=\\\"\\$(head -5 #{FILE})\\\" --layout=reverse-list"}", :Enter
|
||||
header = File.readlines(FILE).take(5).map(&:strip)
|
||||
tmux.until do |lines|
|
||||
lines[-2].include?('100/100') &&
|
||||
lines[-7..-3].map(&:strip) == header &&
|
||||
lines[0] == '> 1'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1111,6 +1212,16 @@ class TestGoFZF < TestBase
|
||||
end
|
||||
end
|
||||
|
||||
def test_header_and_header_lines_reverse_list
|
||||
tmux.send_keys "seq 100 | #{fzf "--layout=reverse-list --header-lines 10 --header \\\"\\$(head -5 #{FILE})\\\""}", :Enter
|
||||
header = File.readlines(FILE).take(5).map(&:strip)
|
||||
tmux.until do |lines|
|
||||
lines[-2].include?('90/90') &&
|
||||
lines[-7...-2].map(&:strip) == header &&
|
||||
lines[-17...-7].map(&:strip) == (1..10).map(&:to_s).reverse
|
||||
end
|
||||
end
|
||||
|
||||
def test_cancel
|
||||
tmux.send_keys "seq 10 | #{fzf '--bind 2:cancel'}", :Enter
|
||||
tmux.until { |lines| lines[-2].include?('10/10') }
|
||||
@@ -1136,6 +1247,12 @@ class TestGoFZF < TestBase
|
||||
tmux.send_keys :Enter
|
||||
end
|
||||
|
||||
def test_margin_reverse_list
|
||||
tmux.send_keys "yes | head -1000 | #{fzf '--margin 5,3 --layout=reverse-list'}", :Enter
|
||||
tmux.until { |lines| lines[4] == '' && lines[5] == ' > y' }
|
||||
tmux.send_keys :Enter
|
||||
end
|
||||
|
||||
def test_tabstop
|
||||
writelines tempname, ["f\too\tba\tr\tbaz\tbarfooq\tux"]
|
||||
{
|
||||
@@ -1319,12 +1436,12 @@ class TestGoFZF < TestBase
|
||||
end
|
||||
|
||||
def test_preview_hidden
|
||||
tmux.send_keys %(seq 1000 | #{FZF} --preview 'echo {{}-{}}' --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].include?(' {1-1}') }
|
||||
tmux.until { |lines| lines[-2] =~ / {1-1-1-[0-9]+}/ }
|
||||
tmux.send_keys '555'
|
||||
tmux.until { |lines| lines[-2].include?(' {555-555}') }
|
||||
tmux.until { |lines| lines[-2] =~ / {555-555-1-[0-9]+}/ }
|
||||
tmux.send_keys '?'
|
||||
tmux.until { |lines| lines[-1] == '> 555' }
|
||||
end
|
||||
@@ -1346,6 +1463,50 @@ class TestGoFZF < TestBase
|
||||
tmux.until { |_| %w[1 2 3] == File.readlines(tempname).map(&:chomp) }
|
||||
end
|
||||
|
||||
def test_preview_flags
|
||||
tmux.send_keys %(seq 10 | sed 's/^/:: /; s/$/ /' |
|
||||
#{FZF} --multi --preview 'echo {{2}/{s2}/{+2}/{+s2}/{q}/{n}/{+n}}'), :Enter
|
||||
tmux.until { |lines| lines[1].include?('{1/1 /1/1 //0/0}') }
|
||||
tmux.send_keys '123'
|
||||
tmux.until { |lines| lines[1].include?('{////123//}') }
|
||||
tmux.send_keys 'C-u', '1'
|
||||
tmux.until { |lines| lines.match_count == 2 }
|
||||
tmux.until { |lines| lines[1].include?('{1/1 /1/1 /1/0/0}') }
|
||||
tmux.send_keys :BTab
|
||||
tmux.until { |lines| lines[1].include?('{10/10 /1/1 /1/9/0}') }
|
||||
tmux.send_keys :BTab
|
||||
tmux.until { |lines| lines[1].include?('{10/10 /1 10/1 10 /1/9/0 9}') }
|
||||
tmux.send_keys '2'
|
||||
tmux.until { |lines| lines[1].include?('{//1 10/1 10 /12//0 9}') }
|
||||
tmux.send_keys '3'
|
||||
tmux.until { |lines| lines[1].include?('{//1 10/1 10 /123//0 9}') }
|
||||
end
|
||||
|
||||
def test_preview_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
|
||||
tmux.send_keys %(: | #{FZF} --preview 'echo foo {q}'), :Enter
|
||||
tmux.until { |lines| lines.match_count == 0 }
|
||||
tmux.until { |lines| !lines[1].include?('foo') }
|
||||
tmux.send_keys 'bar'
|
||||
tmux.until { |lines| lines[1].include?('foo bar') }
|
||||
tmux.send_keys 'C-u'
|
||||
tmux.until { |lines| !lines[1].include?('foo') }
|
||||
end
|
||||
|
||||
def test_preview_q_no_match_with_initial_query
|
||||
tmux.send_keys %(: | #{FZF} --preview 'echo foo {q}{q}' --query foo), :Enter
|
||||
tmux.until { |lines| lines.match_count == 0 }
|
||||
tmux.until { |lines| lines[1].include?('foofoo') }
|
||||
end
|
||||
|
||||
def test_no_clear
|
||||
tmux.send_keys "seq 10 | fzf --no-clear --inline-info --height 5 > #{tempname}", :Enter
|
||||
prompt = '> < 10/10'
|
||||
@@ -1355,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 }
|
||||
@@ -1369,6 +1535,42 @@ class TestGoFZF < TestBase
|
||||
tmux.send_keys :Enter
|
||||
end
|
||||
|
||||
def test_accept_non_empty
|
||||
tmux.send_keys %(seq 1000 | #{fzf '--print-query --bind enter:accept-non-empty'}), :Enter
|
||||
tmux.until { |lines| lines.match_count == 1000 }
|
||||
tmux.send_keys 'foo'
|
||||
tmux.until { |lines| lines[-2].include? '0/1000' }
|
||||
# fzf doesn't exit since there's no selection
|
||||
tmux.send_keys :Enter
|
||||
tmux.until { |lines| lines[-2].include? '0/1000' }
|
||||
tmux.send_keys 'C-u'
|
||||
tmux.until { |lines| lines[-2].include? '1000/1000' }
|
||||
tmux.send_keys '999'
|
||||
tmux.until { |lines| lines[-2].include? '1/1000' }
|
||||
tmux.send_keys :Enter
|
||||
assert_equal %w[999 999], readonce.split($INPUT_RECORD_SEPARATOR)
|
||||
end
|
||||
|
||||
def test_accept_non_empty_with_multi_selection
|
||||
tmux.send_keys %(seq 1000 | #{fzf '-m --print-query --bind enter:accept-non-empty'}), :Enter
|
||||
tmux.until { |lines| lines.match_count == 1000 }
|
||||
tmux.send_keys :Tab
|
||||
tmux.until { |lines| lines[-2].include? '1000/1000 (1)' }
|
||||
tmux.send_keys 'foo'
|
||||
tmux.until { |lines| lines[-2].include? '0/1000' }
|
||||
# fzf will exit in this case even though there's no match for the current query
|
||||
tmux.send_keys :Enter
|
||||
assert_equal %w[foo 1], readonce.split($INPUT_RECORD_SEPARATOR)
|
||||
end
|
||||
|
||||
def test_accept_non_empty_with_empty_list
|
||||
tmux.send_keys %(: | #{fzf '-q foo --print-query --bind enter:accept-non-empty'}), :Enter
|
||||
tmux.until { |lines| lines[-2].strip == '0/0' }
|
||||
tmux.send_keys :Enter
|
||||
# fzf will exit anyway since input list is empty
|
||||
assert_equal %w[foo], readonce.split($INPUT_RECORD_SEPARATOR)
|
||||
end
|
||||
|
||||
def test_preview_update_on_select
|
||||
tmux.send_keys(%(seq 10 | fzf -m --preview 'echo {+}' --bind a:toggle-all),
|
||||
:Enter)
|
||||
@@ -1396,6 +1598,85 @@ 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
|
||||
|
||||
def test_reload_even_when_theres_no_match
|
||||
tmux.send_keys %(: | #{FZF} --bind 'space:reload(seq 10)'), :Enter
|
||||
tmux.until { |lines| lines.item_count.zero? }
|
||||
tmux.send_keys :Space
|
||||
tmux.until { |lines| lines.item_count == 10 }
|
||||
end
|
||||
|
||||
def test_clear_list_when_header_lines_changed_due_to_reload
|
||||
tmux.send_keys %(seq 10 | #{FZF} --header 0 --header-lines 3 --bind 'space:reload(seq 1)'), :Enter
|
||||
tmux.until { |lines| lines.any? { |line| line.include?('9') } }
|
||||
tmux.send_keys :Space
|
||||
tmux.until { |lines| lines.none? { |line| line.include?('9') } }
|
||||
end
|
||||
|
||||
def test_clear_query
|
||||
tmux.send_keys %(: | #{FZF} --query foo --bind space:clear-query), :Enter
|
||||
tmux.until { |lines| lines.item_count.zero? }
|
||||
tmux.until { |lines| lines.last.include?('> foo') }
|
||||
tmux.send_keys 'C-a', 'bar'
|
||||
tmux.until { |lines| lines.last.include?('> barfoo') }
|
||||
tmux.send_keys :Space
|
||||
tmux.until { |lines| lines.last == '>' }
|
||||
end
|
||||
|
||||
def test_clear_selection
|
||||
tmux.send_keys %(seq 100 | #{FZF} --multi --bind space:clear-selection), :Enter
|
||||
tmux.until { |lines| lines.match_count == 100 }
|
||||
tmux.send_keys :Tab
|
||||
tmux.until { |lines| lines[-2].include?('(1)') }
|
||||
tmux.send_keys 'foo'
|
||||
tmux.until { |lines| lines.match_count.zero? }
|
||||
tmux.until { |lines| lines[-2].include?('(1)') }
|
||||
tmux.send_keys :Space
|
||||
tmux.until { |lines| lines.match_count.zero? }
|
||||
tmux.until { |lines| !lines[-2].include?('(1)') }
|
||||
end
|
||||
end
|
||||
|
||||
module TestShell
|
||||
@@ -1469,7 +1750,7 @@ module TestShell
|
||||
lines = retries do
|
||||
tmux.prepare
|
||||
tmux.send_keys :Escape, :c
|
||||
tmux.until { |lines| lines.item_count.positive? }
|
||||
tmux.until { |lines| lines.match_count.positive? }
|
||||
end
|
||||
expected = lines.reverse.select { |l| l.start_with? '>' }.first[2..-1]
|
||||
tmux.send_keys :Enter
|
||||
@@ -1506,7 +1787,7 @@ module TestShell
|
||||
retries do
|
||||
tmux.prepare
|
||||
tmux.send_keys 'C-r'
|
||||
tmux.until { |lines| lines.item_count.positive? }
|
||||
tmux.until { |lines| lines.match_count.positive? }
|
||||
end
|
||||
tmux.send_keys 'C-r'
|
||||
tmux.send_keys '3d'
|
||||
@@ -1538,7 +1819,7 @@ module CompletionTest
|
||||
end
|
||||
tmux.prepare
|
||||
tmux.send_keys 'cat /tmp/fzf-test/10**', :Tab
|
||||
tmux.until { |lines| lines.item_count.positive? }
|
||||
tmux.until { |lines| lines.match_count.positive? }
|
||||
tmux.send_keys ' !d'
|
||||
tmux.until { |lines| lines.match_count == 2 }
|
||||
tmux.send_keys :Tab, :Tab
|
||||
@@ -1552,7 +1833,7 @@ module CompletionTest
|
||||
# ~USERNAME**<TAB>
|
||||
tmux.send_keys 'C-u'
|
||||
tmux.send_keys "cat ~#{ENV['USER']}**", :Tab
|
||||
tmux.until { |lines| lines.item_count.positive? }
|
||||
tmux.until { |lines| lines.match_count.positive? }
|
||||
tmux.send_keys "'.fzf-home"
|
||||
tmux.until { |lines| lines.select { |l| l.include? '.fzf-home' }.count > 1 }
|
||||
tmux.send_keys :Enter
|
||||
@@ -1570,7 +1851,7 @@ module CompletionTest
|
||||
# /tmp/fzf\ test**<TAB>
|
||||
tmux.send_keys 'C-u'
|
||||
tmux.send_keys 'cat /tmp/fzf\ test/**', :Tab
|
||||
tmux.until { |lines| lines.item_count.positive? }
|
||||
tmux.until { |lines| lines.match_count.positive? }
|
||||
tmux.send_keys 'foobar$'
|
||||
tmux.until { |lines| lines.match_count == 1 }
|
||||
tmux.send_keys :Enter
|
||||
@@ -1590,7 +1871,7 @@ module CompletionTest
|
||||
|
||||
def test_file_completion_root
|
||||
tmux.send_keys 'ls /**', :Tab
|
||||
tmux.until { |lines| lines.item_count.positive? }
|
||||
tmux.until { |lines| lines.match_count.positive? }
|
||||
tmux.send_keys :Enter
|
||||
end
|
||||
|
||||
@@ -1601,7 +1882,7 @@ module CompletionTest
|
||||
FileUtils.touch '/tmp/fzf-test/d55/xxx'
|
||||
tmux.prepare
|
||||
tmux.send_keys 'cd /tmp/fzf-test/**', :Tab
|
||||
tmux.until { |lines| lines.item_count.positive? }
|
||||
tmux.until { |lines| lines.match_count.positive? }
|
||||
tmux.send_keys :Tab, :Tab # Tab does not work here
|
||||
tmux.send_keys 55
|
||||
tmux.until { |lines| lines.match_count == 1 }
|
||||
@@ -1630,7 +1911,7 @@ module CompletionTest
|
||||
tmux.prepare
|
||||
tmux.send_keys 'C-L'
|
||||
tmux.send_keys 'kill ', :Tab
|
||||
tmux.until { |lines| lines.item_count.positive? }
|
||||
tmux.until { |lines| lines.match_count.positive? }
|
||||
tmux.send_keys 'sleep12345'
|
||||
tmux.until { |lines| lines.any_include? 'sleep 12345' }
|
||||
tmux.send_keys :Enter
|
||||
@@ -1649,7 +1930,7 @@ module CompletionTest
|
||||
tmux.send_keys '_fzf_compgen_path() { echo "\$1"; seq 10; }', :Enter
|
||||
tmux.prepare
|
||||
tmux.send_keys 'ls /tmp/**', :Tab
|
||||
tmux.until { |lines| lines.item_count == 11 }
|
||||
tmux.until { |lines| lines.match_count == 11 }
|
||||
tmux.send_keys :Tab, :Tab, :Tab
|
||||
tmux.until { |lines| lines.select_count == 3 }
|
||||
tmux.send_keys :Enter
|
||||
@@ -1716,6 +1997,23 @@ class TestBash < TestBase
|
||||
super
|
||||
@tmux = Tmux.new :bash
|
||||
end
|
||||
|
||||
def test_dynamic_completion_loader
|
||||
tmux.paste 'touch /tmp/foo; _fzf_completion_loader=1'
|
||||
tmux.paste '_completion_loader() { complete -o default fake; }'
|
||||
tmux.paste 'complete -F _fzf_path_completion -o default -o bashdefault fake'
|
||||
tmux.send_keys 'fake /tmp/foo**', :Tab
|
||||
tmux.until { |lines| lines.match_count.positive? }
|
||||
tmux.send_keys 'C-c'
|
||||
|
||||
tmux.prepare
|
||||
tmux.send_keys 'fake /tmp/foo'
|
||||
tmux.send_keys :Tab , 'C-u'
|
||||
|
||||
tmux.prepare
|
||||
tmux.send_keys 'fake /tmp/foo**', :Tab
|
||||
tmux.until { |lines| lines.match_count.positive? }
|
||||
end
|
||||
end
|
||||
|
||||
class TestZsh < TestBase
|
||||
|
72
uninstall
72
uninstall
@@ -1,12 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
confirm() {
|
||||
while [ 1 ]; do
|
||||
read -p "$1" -n 1 -r
|
||||
echo
|
||||
if [[ "$REPLY" =~ ^[Yy] ]]; then
|
||||
xdg=0
|
||||
prefix='~/.fzf'
|
||||
prefix_expand=~/.fzf
|
||||
fish_dir=${XDG_CONFIG_HOME:-$HOME/.config}/fish
|
||||
|
||||
help() {
|
||||
cat << EOF
|
||||
usage: $0 [OPTIONS]
|
||||
|
||||
--help Show this message
|
||||
--xdg Remove files generated under \$XDG_CONFIG_HOME/fzf
|
||||
EOF
|
||||
}
|
||||
|
||||
for opt in "$@"; do
|
||||
case $opt in
|
||||
--help)
|
||||
help
|
||||
exit 0
|
||||
;;
|
||||
--xdg)
|
||||
xdg=1
|
||||
prefix='"${XDG_CONFIG_HOME:-$HOME/.config}"/fzf/fzf'
|
||||
prefix_expand=${XDG_CONFIG_HOME:-$HOME/.config}/fzf/fzf
|
||||
;;
|
||||
*)
|
||||
echo "unknown option: $opt"
|
||||
help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
ask() {
|
||||
while true; do
|
||||
read -p "$1 ([y]/n) " -r
|
||||
REPLY=${REPLY:-"y"}
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
return 0
|
||||
elif [[ "$REPLY" =~ ^[Nn] ]]; then
|
||||
elif [[ $REPLY =~ ^[Nn]$ ]]; then
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
@@ -40,7 +73,7 @@ remove_line() {
|
||||
content=$(sed 's/^[0-9]*://' <<< "$line")
|
||||
match=1
|
||||
echo " - Line #$line_no: $content"
|
||||
[ "$content" = "$1" ] || confirm " - Remove (y/n) ? "
|
||||
[ "$content" = "$1" ] || ask " - Remove?"
|
||||
if [ $? -eq 0 ]; then
|
||||
awk -v n=$line_no 'NR == n {next} {print}' "$src" > "$src.bak" &&
|
||||
mv "$src.bak" "$src" || break
|
||||
@@ -55,25 +88,30 @@ remove_line() {
|
||||
}
|
||||
|
||||
for shell in bash zsh; do
|
||||
remove ~/.fzf.${shell}
|
||||
shell_config=${prefix_expand}.${shell}
|
||||
remove "${shell_config}"
|
||||
remove_line ~/.${shell}rc \
|
||||
"[ -f ~/.fzf.${shell} ] && source ~/.fzf.${shell}" \
|
||||
"source ~/.fzf.${shell}"
|
||||
"[ -f ${prefix}.${shell} ] && source ${prefix}.${shell}" \
|
||||
"source ${prefix}.${shell}"
|
||||
done
|
||||
|
||||
bind_file=~/.config/fish/functions/fish_user_key_bindings.fish
|
||||
bind_file="${fish_dir}/functions/fish_user_key_bindings.fish"
|
||||
if [ -f "$bind_file" ]; then
|
||||
remove_line "$bind_file" "fzf_key_bindings"
|
||||
fi
|
||||
|
||||
if [ -d ~/.config/fish/functions ]; then
|
||||
remove ~/.config/fish/functions/fzf.fish
|
||||
remove ~/.config/fish/functions/fzf_key_bindings.fish
|
||||
if [ -d "${fish_dir}/functions" ]; then
|
||||
remove "${fish_dir}/functions/fzf.fish"
|
||||
remove "${fish_dir}/functions/fzf_key_bindings.fish"
|
||||
|
||||
if [ "$(ls -A ~/.config/fish/functions)" ]; then
|
||||
echo "Can't delete non-empty directory: \"~/.config/fish/functions\""
|
||||
if [ "$(ls -A "${fish_dir}/functions")" ]; then
|
||||
echo "Can't delete non-empty directory: \"${fish_dir}/functions\""
|
||||
else
|
||||
rmdir ~/.config/fish/functions
|
||||
rmdir "${fish_dir}/functions"
|
||||
fi
|
||||
fi
|
||||
|
||||
config_dir=$(dirname "$prefix_expand")
|
||||
if [[ "$xdg" = 1 ]] && [[ "$config_dir" = */fzf ]] && [[ -d "$config_dir" ]]; then
|
||||
rmdir "$config_dir"
|
||||
fi
|
||||
|
Reference in New Issue
Block a user