mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-13 13:53:47 -05:00
Compare commits
425 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 | ||
|
|
0b33dc6ce1 | ||
|
|
64a6ced62e | ||
|
|
438f6c96cd | ||
|
|
6ae085f974 | ||
|
|
cb8e97274e | ||
|
|
c4185e81e8 | ||
|
|
0580fe9046 | ||
|
|
1b1bc9ea36 | ||
|
|
c2614467cf | ||
|
|
077ae51f05 | ||
|
|
ee40212e97 | ||
|
|
7f5f6efbac | ||
|
|
45d4c57d91 | ||
|
|
41e0208335 | ||
|
|
2f8238342b | ||
|
|
e1582b8323 | ||
|
|
7cfa6f0265 | ||
|
|
e3973c74e7 | ||
|
|
a8deca2dd9 | ||
|
|
a78ade1771 | ||
|
|
79d2ef4616 | ||
|
|
5edc3f755c | ||
|
|
288976310b | ||
|
|
58b5be8ab6 | ||
|
|
26d7896877 | ||
|
|
fd6bc7308f | ||
|
|
6c41c95f28 | ||
|
|
446e04469d | ||
|
|
5097e563df | ||
|
|
c7ad97c641 | ||
|
|
9516fe3324 | ||
|
|
20cdbac8c3 | ||
|
|
e3e7b3360c | ||
|
|
655dfb8328 | ||
|
|
9b9c67b768 | ||
|
|
5b7457ff08 | ||
|
|
48adad5454 | ||
|
|
b27dc3eb17 | ||
|
|
e89eebb7ba | ||
|
|
fee404399a | ||
|
|
6b4805ca1a | ||
|
|
159699b5d7 | ||
|
|
af809c9661 | ||
|
|
329de8f416 | ||
|
|
e825b07e85 | ||
|
|
71fdb99a07 | ||
|
|
55ee4186aa | ||
|
|
941b0a0ff7 | ||
|
|
6aae12288e | ||
|
|
302cc552ef | ||
|
|
a2a4df0886 | ||
|
|
3399e39968 | ||
|
|
87874bba88 | ||
|
|
c304fc4333 | ||
|
|
6977cf268f | ||
|
|
931c78a70c | ||
|
|
8d23646fe6 | ||
|
|
656963e018 | ||
|
|
644277faf1 | ||
|
|
0558dfee79 | ||
|
|
487c8fe88f | ||
|
|
0d171ba1d8 | ||
|
|
2069bbc8b5 | ||
|
|
053d628b53 | ||
|
|
6bc592e6c9 | ||
|
|
6c76d8cd1c | ||
|
|
a09e411936 | ||
|
|
02a7b96f33 | ||
|
|
e55e029ae8 | ||
|
|
6b18b144cf | ||
|
|
6d53089cc1 | ||
|
|
e85a8a68d0 | ||
|
|
dc55e68524 | ||
|
|
462c68b625 | ||
|
|
999d374f0c | ||
|
|
b208aa675e | ||
|
|
2b98fee136 | ||
|
|
e5e75efebc | ||
|
|
4a4fef2daf | ||
|
|
ecb6b234cc | ||
|
|
39dbc8acdb | ||
|
|
a56489bc7f | ||
|
|
99927c7071 | ||
|
|
3e28403978 | ||
|
|
37370f057f | ||
|
|
f4b46fad27 | ||
|
|
9d2c6a95f4 | ||
|
|
376a76d1d3 | ||
|
|
1fcc07e54e | ||
|
|
8db3345c2f | ||
|
|
69aa2fea68 | ||
|
|
298749bfcd | ||
|
|
f1f31baae1 | ||
|
|
e1c8f19e8f | ||
|
|
5e302c70e9 | ||
|
|
4c5a679066 | ||
|
|
41f0b2c354 | ||
|
|
a0a3c349c9 | ||
|
|
bc3983181d | ||
|
|
980b58ef5a | ||
|
|
a2604c0963 | ||
|
|
6dbc108da2 | ||
|
|
bd98f988f0 | ||
|
|
06301c7847 | ||
|
|
18a1aeaa91 | ||
|
|
c9f16b6430 | ||
|
|
bc9d2abdb6 | ||
|
|
28810c178f | ||
|
|
a9e64efe45 | ||
|
|
6b5886c034 | ||
|
|
7727ad43af | ||
|
|
bbe10f4f77 | ||
|
|
5e72709613 | ||
|
|
9e85cba0d0 | ||
|
|
4b59ced08f | ||
|
|
8dbdd55730 | ||
|
|
6725151a99 | ||
|
|
d4f3d5a164 | ||
|
|
7b5ccc45bc | ||
|
|
940214a1a2 | ||
|
|
68bd410159 | ||
|
|
b13fcfd831 | ||
|
|
07ef2b051c | ||
|
|
3fc795340d | ||
|
|
70cfa6af13 | ||
|
|
dbcaec59ae | ||
|
|
faedae708e | ||
|
|
0c66521b23 | ||
|
|
bf92862459 | ||
|
|
1a68698d76 | ||
|
|
842a73357c | ||
|
|
5efdeccdbb | ||
|
|
050777b8c4 | ||
|
|
a4d78e2200 | ||
|
|
b49f22cdf9 | ||
|
|
7e483b0c25 | ||
|
|
3cf9ae04c7 | ||
|
|
bf0cb4bfe2 | ||
|
|
773133c4ce | ||
|
|
ca0b3b6fd7 | ||
|
|
f4731c0514 | ||
|
|
34f16e5b7d | ||
|
|
83e9af6601 | ||
|
|
8bbf9335e1 | ||
|
|
159f30b37f | ||
|
|
2e3dc75425 | ||
|
|
7d3575b362 | ||
|
|
35d407021c | ||
|
|
076f49d447 | ||
|
|
0665fe0413 | ||
|
|
669a6fee40 | ||
|
|
8aab0fc189 | ||
|
|
5d6eb5bfd6 | ||
|
|
cf4711d878 | ||
|
|
21d664d670 | ||
|
|
ab182e276b | ||
|
|
96a3250152 | ||
|
|
f5746002fd | ||
|
|
e1e3339770 | ||
|
|
3a5086796d | ||
|
|
11300913a4 | ||
|
|
e65f14cbed | ||
|
|
6898849e3e | ||
|
|
2d61691bb2 | ||
|
|
eba9e04e2e | ||
|
|
33f32de690 | ||
|
|
93b8f61551 | ||
|
|
7f17a9d1b4 | ||
|
|
d34e4cf698 | ||
|
|
6b592137b9 | ||
|
|
d5e72bf55d | ||
|
|
5677e5e133 | ||
|
|
7a11a06cbd | ||
|
|
a5862d4b9c | ||
|
|
a50909e806 | ||
|
|
7c8f7d3f20 | ||
|
|
9078197446 | ||
|
|
0fe07cf9fe | ||
|
|
2216169ca1 | ||
|
|
50e989ca85 | ||
|
|
fa1fc3d855 | ||
|
|
bbe696e925 | ||
|
|
5d12f523a3 | ||
|
|
d295d20dc4 | ||
|
|
2ba10071c9 | ||
|
|
505dc0491b | ||
|
|
54a4ab0f26 | ||
|
|
e03e91477b | ||
|
|
88ac397158 | ||
|
|
6fd4be580b | ||
|
|
53348feb89 | ||
|
|
337cdbb37c | ||
|
|
05fdf91fc5 | ||
|
|
c387689d1c | ||
|
|
cb9238dc4e | ||
|
|
a484811f78 | ||
|
|
111d1934c4 | ||
|
|
972fb1a29d | ||
|
|
3a6af27586 | ||
|
|
c89ac341e4 | ||
|
|
cd59e5d07b | ||
|
|
0b940e4b2b | ||
|
|
b29375c844 | ||
|
|
76d3f6d248 | ||
|
|
e87a85a179 | ||
|
|
11407bf656 | ||
|
|
c82fb3c9b9 | ||
|
|
309e1d8619 | ||
|
|
c2db67c1c0 | ||
|
|
9526594905 | ||
|
|
3d74d277aa | ||
|
|
fc274c2ba4 | ||
|
|
ce43ea9f42 | ||
|
|
21da02fac2 | ||
|
|
19569bd5c5 | ||
|
|
afa25d8c57 | ||
|
|
1ba7acf4bd | ||
|
|
a847fe8754 | ||
|
|
5bb18b6441 | ||
|
|
876c233a26 |
28
.github/ISSUE_TEMPLATE.md
vendored
28
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,30 +1,22 @@
|
|||||||
|
<!-- ISSUES NOT FOLLOWING THIS TEMPLATE WILL BE CLOSED AND DELETED -->
|
||||||
|
|
||||||
<!-- Check all that apply [x] -->
|
<!-- Check all that apply [x] -->
|
||||||
- Category
|
|
||||||
- [ ] fzf binary
|
- [ ] I have read through the manual page (`man fzf`)
|
||||||
- [ ] fzf-tmux script
|
- [ ] I have the latest version of fzf
|
||||||
- [ ] Key bindings
|
- [ ] I have searched through the existing issues
|
||||||
- [ ] Completion
|
|
||||||
- [ ] Vim
|
## Info
|
||||||
- [ ] Neovim
|
|
||||||
- [ ] Etc.
|
|
||||||
- OS
|
- OS
|
||||||
- [ ] Linux
|
- [ ] Linux
|
||||||
- [ ] Mac OS X
|
- [ ] Mac OS X
|
||||||
- [ ] Windows
|
- [ ] Windows
|
||||||
- [ ] Windows Subsystem for Linux
|
|
||||||
- [ ] Etc.
|
- [ ] Etc.
|
||||||
- Shell
|
- Shell
|
||||||
- [ ] bash
|
- [ ] bash
|
||||||
- [ ] zsh
|
- [ ] zsh
|
||||||
- [ ] fish
|
- [ ] fish
|
||||||
|
|
||||||
<!--
|
## Problem / Steps to reproduce
|
||||||
### 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 ...
|
|
||||||
-->
|
|
||||||
|
|
||||||
|
|||||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -1,6 +1,10 @@
|
|||||||
bin
|
bin/fzf
|
||||||
src/fzf/fzf-*
|
bin/fzf.exe
|
||||||
gopath
|
target
|
||||||
pkg
|
pkg
|
||||||
Gemfile.lock
|
Gemfile.lock
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
doc/tags
|
||||||
|
vendor
|
||||||
|
gopath
|
||||||
|
*.zwc
|
||||||
|
|||||||
49
.travis.yml
49
.travis.yml
@@ -1,28 +1,27 @@
|
|||||||
language: ruby
|
language: go
|
||||||
matrix:
|
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:
|
include:
|
||||||
- env: TAGS=
|
- stage: unittest
|
||||||
rvm: 2.3.3
|
go: "1.13.x"
|
||||||
# - env: TAGS=tcell
|
script: make && make test
|
||||||
# rvm: 2.2.0
|
|
||||||
|
|
||||||
install:
|
- stage: cli
|
||||||
- sudo apt-get update
|
go: "1.13.x"
|
||||||
- sudo apt-get install -y libncurses-dev lib32ncurses5-dev libgpm-dev
|
rvm: "2.5"
|
||||||
- sudo add-apt-repository -y ppa:pi-rho/dev
|
script: |
|
||||||
- sudo apt-add-repository -y ppa:fish-shell/release-2
|
make install && ./install --all && tmux new "ruby test/test_go.rb > out && touch ok" && cat out && [ -e ok ]
|
||||||
- sudo apt-get update
|
|
||||||
- sudo apt-get install -y tmux=1.9a-1~ppa1~p
|
|
||||||
- sudo apt-get install -y zsh fish
|
|
||||||
|
|
||||||
script: |
|
|
||||||
export GOPATH=~/go
|
|
||||||
export FZF_BASE=$GOPATH/src/github.com/junegunn/fzf
|
|
||||||
|
|
||||||
mkdir -p $GOPATH/src/github.com/junegunn
|
|
||||||
ln -s $(pwd) $FZF_BASE
|
|
||||||
|
|
||||||
cd $FZF_BASE/src && make test fzf/fzf-linux_amd64 install &&
|
|
||||||
cd $FZF_BASE/bin && ln -sf fzf-linux_amd64 fzf-$(./fzf --version)-linux_amd64 &&
|
|
||||||
cd $FZF_BASE && yes | ./install && rm -f fzf &&
|
|
||||||
tmux new "ruby test/test_go.rb > out && touch ok" && cat out && [ -e ok ]
|
|
||||||
|
|||||||
15
BUILD.md
15
BUILD.md
@@ -6,26 +6,21 @@ Build instructions
|
|||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- `go` executable in $PATH
|
- Go 1.11 or above
|
||||||
|
|
||||||
### Using Makefile
|
### Using Makefile
|
||||||
|
|
||||||
Makefile will set up and use its own `$GOPATH` under the project root.
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Source files are located in src directory
|
# Build fzf binary for your platform in target
|
||||||
cd src
|
|
||||||
|
|
||||||
# Build fzf binary for your platform in src/fzf
|
|
||||||
make
|
make
|
||||||
|
|
||||||
# Build fzf binary and copy it to bin directory
|
# Build fzf binary and copy it to bin directory
|
||||||
make install
|
make install
|
||||||
|
|
||||||
# Build 32-bit and 64-bit executables and tarballs
|
# Build 32-bit and 64-bit executables and tarballs in target
|
||||||
make release
|
make release
|
||||||
|
|
||||||
# Make release archives for all supported platforms
|
# Make release archives for all supported platforms in target
|
||||||
make release-all
|
make release-all
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -35,7 +30,7 @@ Alternatively, you can build fzf directly with `go get` command without
|
|||||||
manually cloning the repository.
|
manually cloning the repository.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go get -u github.com/junegunn/fzf/src/fzf
|
go get -u github.com/junegunn/fzf
|
||||||
```
|
```
|
||||||
|
|
||||||
Third-party libraries used
|
Third-party libraries used
|
||||||
|
|||||||
230
CHANGELOG.md
230
CHANGELOG.md
@@ -1,6 +1,236 @@
|
|||||||
CHANGELOG
|
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
|
||||||
|
------
|
||||||
|
|
||||||
|
- Fixed custom background color of preview window (#1046)
|
||||||
|
- Fixed background color issues of Windows binary
|
||||||
|
- Fixed Windows binary to execute command using cmd.exe with no parsing and
|
||||||
|
escaping (#1072)
|
||||||
|
- Added support for `window` layout on Vim 8 using Vim 8 terminal (#1055)
|
||||||
|
|
||||||
|
0.17.0-2
|
||||||
|
--------
|
||||||
|
|
||||||
|
A maintenance release for auxiliary scripts. fzf binaries are not updated.
|
||||||
|
|
||||||
|
- Experimental support for the builtin terminal of Vim 8
|
||||||
|
- fzf can now run inside GVim
|
||||||
|
- Updated Vim plugin to better handle `&shell` issue on fish
|
||||||
|
- Fixed a bug of fzf-tmux where invalid output is generated
|
||||||
|
- Fixed fzf-tmux to work even when `tput` does not work
|
||||||
|
|
||||||
|
0.17.0
|
||||||
|
------
|
||||||
|
- Performance optimization
|
||||||
|
- One can match literal spaces in extended-search mode with a space prepended
|
||||||
|
by a backslash.
|
||||||
|
- `--expect` is now additive and can be specified multiple times.
|
||||||
|
|
||||||
|
0.16.11
|
||||||
|
-------
|
||||||
|
- Performance optimization
|
||||||
|
- Fixed missing preview update
|
||||||
|
|
||||||
|
0.16.10
|
||||||
|
-------
|
||||||
|
- Fixed invalid handling of ANSI colors in preview window
|
||||||
|
- Further improved `--ansi` performance
|
||||||
|
|
||||||
|
0.16.9
|
||||||
|
------
|
||||||
|
- Memory and performance optimization
|
||||||
|
- Around 20% performance improvement for general use cases
|
||||||
|
- Up to 5x faster processing of `--ansi`
|
||||||
|
- Up to 50% reduction of memory usage
|
||||||
|
- Bug fixes and usability improvements
|
||||||
|
- Fixed handling of bracketed paste mode
|
||||||
|
- [ERROR] on info line when the default command failed
|
||||||
|
- More efficient rendering of preview window
|
||||||
|
- `--no-clear` updated for repetitive relaunching scenarios
|
||||||
|
|
||||||
|
0.16.8
|
||||||
|
------
|
||||||
|
- New `change` event and `top` action for `--bind`
|
||||||
|
- `fzf --bind change:top`
|
||||||
|
- Move cursor to the top result whenever the query string is changed
|
||||||
|
- `fzf --bind 'ctrl-w:unix-word-rubout+top,ctrl-u:unix-line-discard+top'`
|
||||||
|
- `top` combined with `unix-word-rubout` and `unix-line-discard`
|
||||||
|
- Fixed inconsistent tiebreak scores when `--nth` is used
|
||||||
|
- Proper display of tab characters in `--prompt`
|
||||||
|
- Fixed not to `--cycle` on page-up/page-down to prevent overshoot
|
||||||
|
- Git revision in `--version` output
|
||||||
|
- Basic support for Cygwin environment
|
||||||
|
- Many fixes in Vim plugin on Windows/Cygwin (thanks to @janlazo)
|
||||||
|
|
||||||
|
0.16.7
|
||||||
|
------
|
||||||
|
- Added support for `ctrl-alt-[a-z]` key chords
|
||||||
|
- CTRL-Z (SIGSTOP) now works with fzf
|
||||||
|
- fzf will export `$FZF_PREVIEW_WINDOW` so that the scripts can use it
|
||||||
|
- Bug fixes and improvements in Vim plugin and shell extensions
|
||||||
|
|
||||||
|
0.16.6
|
||||||
|
------
|
||||||
|
- Minor bug fixes and improvements
|
||||||
|
- Added `--no-clear` option for scripting purposes
|
||||||
|
|
||||||
|
0.16.5
|
||||||
|
------
|
||||||
|
- Minor bug fixes
|
||||||
|
- Added `toggle-preview-wrap` action
|
||||||
|
- Built with Go 1.8
|
||||||
|
|
||||||
0.16.4
|
0.16.4
|
||||||
------
|
------
|
||||||
- Added `--border` option to draw border above and below the finder
|
- Added `--border` option to draw border above and below the finder
|
||||||
|
|||||||
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 ]
|
||||||
136
Makefile
Normal file
136
Makefile
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
GO ?= go
|
||||||
|
GOOS ?= $(word 1, $(subst /, " ", $(word 4, $(shell go version))))
|
||||||
|
|
||||||
|
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
|
||||||
|
ROOT_DIR := $(shell dirname $(MAKEFILE))
|
||||||
|
SOURCES := $(wildcard *.go src/*.go src/*/*.go) $(MAKEFILE)
|
||||||
|
|
||||||
|
REVISION := $(shell git log -n 1 --pretty=format:%h -- $(SOURCES))
|
||||||
|
BUILD_FLAGS := -a -ldflags "-X main.revision=$(REVISION) -w -extldflags=$(LDFLAGS)" -tags "$(TAGS)"
|
||||||
|
|
||||||
|
BINARY32 := fzf-$(GOOS)_386
|
||||||
|
BINARY64 := fzf-$(GOOS)_amd64
|
||||||
|
BINARYARM5 := fzf-$(GOOS)_arm5
|
||||||
|
BINARYARM6 := fzf-$(GOOS)_arm6
|
||||||
|
BINARYARM7 := fzf-$(GOOS)_arm7
|
||||||
|
BINARYARM8 := fzf-$(GOOS)_arm8
|
||||||
|
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)
|
||||||
|
ifeq ($(UNAME_M),x86_64)
|
||||||
|
BINARY := $(BINARY64)
|
||||||
|
else ifeq ($(UNAME_M),amd64)
|
||||||
|
BINARY := $(BINARY64)
|
||||||
|
else ifeq ($(UNAME_M),i686)
|
||||||
|
BINARY := $(BINARY32)
|
||||||
|
else ifeq ($(UNAME_M),i386)
|
||||||
|
BINARY := $(BINARY32)
|
||||||
|
else ifeq ($(UNAME_M),armv5l)
|
||||||
|
BINARY := $(BINARYARM5)
|
||||||
|
else ifeq ($(UNAME_M),armv6l)
|
||||||
|
BINARY := $(BINARYARM6)
|
||||||
|
else ifeq ($(UNAME_M),armv7l)
|
||||||
|
BINARY := $(BINARYARM7)
|
||||||
|
else 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
|
||||||
|
|
||||||
|
all: target/$(BINARY)
|
||||||
|
|
||||||
|
target:
|
||||||
|
mkdir -p $@
|
||||||
|
|
||||||
|
ifeq ($(GOOS),windows)
|
||||||
|
release: target/$(BINARY32) target/$(BINARY64)
|
||||||
|
cd target && cp -f $(BINARY32) fzf.exe && zip $(RELEASE32).zip fzf.exe
|
||||||
|
cd target && cp -f $(BINARY64) fzf.exe && zip $(RELEASE64).zip fzf.exe
|
||||||
|
cd target && rm -f fzf.exe
|
||||||
|
else ifeq ($(GOOS),linux)
|
||||||
|
release: target/$(BINARY32) target/$(BINARY64) target/$(BINARYARM5) target/$(BINARYARM6) target/$(BINARYARM7) target/$(BINARYARM8) 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)
|
||||||
|
cd target && cp -f $(BINARY32) fzf && tar -czf $(RELEASE32).tgz fzf
|
||||||
|
cd target && cp -f $(BINARY64) fzf && tar -czf $(RELEASE64).tgz fzf
|
||||||
|
cd target && rm -f fzf
|
||||||
|
endif
|
||||||
|
|
||||||
|
release-all: clean test
|
||||||
|
GOOS=darwin make release
|
||||||
|
GOOS=linux make release
|
||||||
|
GOOS=freebsd make release
|
||||||
|
GOOS=openbsd make release
|
||||||
|
GOOS=windows make release
|
||||||
|
|
||||||
|
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 \
|
||||||
|
github.com/junegunn/fzf/src/util
|
||||||
|
|
||||||
|
install: bin/fzf
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(RM) -r target
|
||||||
|
|
||||||
|
target/$(BINARY32): $(SOURCES)
|
||||||
|
GOARCH=386 $(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)
|
||||||
|
GOARCH=arm GOARM=5 $(GO) build $(BUILD_FLAGS) -o $@
|
||||||
|
|
||||||
|
target/$(BINARYARM6): $(SOURCES)
|
||||||
|
GOARCH=arm GOARM=6 $(GO) build $(BUILD_FLAGS) -o $@
|
||||||
|
|
||||||
|
target/$(BINARYARM7): $(SOURCES)
|
||||||
|
GOARCH=arm GOARM=7 $(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
|
||||||
|
|
||||||
|
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
|
||||||
326
README-VIM.md
Normal file
326
README-VIM.md
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
FZF Vim integration
|
||||||
|
===================
|
||||||
|
|
||||||
|
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](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[!]`
|
||||||
|
---------
|
||||||
|
|
||||||
|
```vim
|
||||||
|
" Look for files under current directory
|
||||||
|
:FZF
|
||||||
|
|
||||||
|
" Look for files under your home directory
|
||||||
|
:FZF ~
|
||||||
|
|
||||||
|
" With fzf command-line options
|
||||||
|
:FZF --reverse --info=inline /tmp
|
||||||
|
|
||||||
|
" Bang version starts fzf in fullscreen mode
|
||||||
|
:FZF!
|
||||||
|
```
|
||||||
|
|
||||||
|
Similarly to [ctrlp.vim](https://github.com/kien/ctrlp.vim), use enter key,
|
||||||
|
`CTRL-T`, `CTRL-X` or `CTRL-V` to open selected files in the current window,
|
||||||
|
in new tabs, in horizontal splits, or in vertical splits respectively.
|
||||||
|
|
||||||
|
Note that the environment variables `FZF_DEFAULT_COMMAND` and
|
||||||
|
`FZF_DEFAULT_OPTS` also apply here.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
- `g:fzf_action`
|
||||||
|
- Customizable extra key bindings for opening selected files in different ways
|
||||||
|
- `g:fzf_layout`
|
||||||
|
- Determines the size and position of fzf window
|
||||||
|
- `g:fzf_colors`
|
||||||
|
- Customizes fzf colors to match the current color scheme
|
||||||
|
- `g:fzf_history_dir`
|
||||||
|
- Enables history feature
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
```vim
|
||||||
|
" This is the default extra key bindings
|
||||||
|
let g:fzf_action = {
|
||||||
|
\ 'ctrl-t': 'tab split',
|
||||||
|
\ 'ctrl-x': 'split',
|
||||||
|
\ 'ctrl-v': 'vsplit' }
|
||||||
|
|
||||||
|
" An action can be a reference to a function that processes selected lines
|
||||||
|
function! s:build_quickfix_list(lines)
|
||||||
|
call setqflist(map(copy(a:lines), '{ "filename": v:val }'))
|
||||||
|
copen
|
||||||
|
cc
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
let g:fzf_action = {
|
||||||
|
\ 'ctrl-q': function('s:build_quickfix_list'),
|
||||||
|
\ 'ctrl-t': 'tab split',
|
||||||
|
\ 'ctrl-x': 'split',
|
||||||
|
\ 'ctrl-v': 'vsplit' }
|
||||||
|
|
||||||
|
" Default fzf layout
|
||||||
|
" - down / up / left / right
|
||||||
|
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': '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'],
|
||||||
|
\ 'hl': ['fg', 'Comment'],
|
||||||
|
\ 'fg+': ['fg', 'CursorLine', 'CursorColumn', 'Normal'],
|
||||||
|
\ 'bg+': ['bg', 'CursorLine', 'CursorColumn'],
|
||||||
|
\ 'hl+': ['fg', 'Statement'],
|
||||||
|
\ 'info': ['fg', 'PreProc'],
|
||||||
|
\ 'border': ['fg', 'Ignore'],
|
||||||
|
\ 'prompt': ['fg', 'Conditional'],
|
||||||
|
\ 'pointer': ['fg', 'Exception'],
|
||||||
|
\ 'marker': ['fg', 'Keyword'],
|
||||||
|
\ 'spinner': ['fg', 'Label'],
|
||||||
|
\ 'header': ['fg', 'Comment'] }
|
||||||
|
|
||||||
|
" 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()` 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.
|
||||||
|
|
||||||
|
```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 to avoid escaping issues.
|
||||||
|
|
||||||
|
```vim
|
||||||
|
call fzf#run({'options': '--reverse --prompt "C:\\Program Files\\"'})
|
||||||
|
call fzf#run({'options': ['--reverse', '--prompt', 'C:\Program Files\']})
|
||||||
|
```
|
||||||
|
|
||||||
|
`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:
|
||||||
|
|
||||||
|
```vim
|
||||||
|
echo fzf#wrap({'source': 'ls'})
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
----
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
```vim
|
||||||
|
" 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
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 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.
|
||||||
|
|
||||||
|
```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) 2019 Junegunn Choi
|
||||||
419
README.md
419
README.md
@@ -3,18 +3,61 @@
|
|||||||
|
|
||||||
fzf is a general-purpose command-line fuzzy finder.
|
fzf is a general-purpose command-line fuzzy finder.
|
||||||
|
|
||||||

|
<img src="https://raw.githubusercontent.com/junegunn/i/master/fzf-preview.png" width=640>
|
||||||
|
|
||||||
|
It's an interactive Unix filter for command-line that can be used with any
|
||||||
|
list; files, command history, processes, hostnames, bookmarks, git commits,
|
||||||
|
etc.
|
||||||
|
|
||||||
Pros
|
Pros
|
||||||
----
|
----
|
||||||
|
|
||||||
- No dependencies
|
- Portable, no dependencies
|
||||||
- Blazingly fast
|
- Blazingly fast
|
||||||
- The most comprehensive feature set
|
- The most comprehensive feature set
|
||||||
- Flexible layout using tmux panes
|
- Flexible layout
|
||||||
- Batteries included
|
- Batteries included
|
||||||
- Vim/Neovim plugin, key bindings and fuzzy auto-completion
|
- Vim/Neovim plugin, key bindings and fuzzy auto-completion
|
||||||
|
|
||||||
|
Table of Contents
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
* [Installation](#installation)
|
||||||
|
* [Using Homebrew or Linuxbrew](#using-homebrew-or-linuxbrew)
|
||||||
|
* [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)
|
||||||
|
* [Using the finder](#using-the-finder)
|
||||||
|
* [Layout](#layout)
|
||||||
|
* [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)
|
||||||
|
* [Fuzzy completion for bash and zsh](#fuzzy-completion-for-bash-and-zsh)
|
||||||
|
* [Files and directories](#files-and-directories)
|
||||||
|
* [Process IDs](#process-ids)
|
||||||
|
* [Host names](#host-names)
|
||||||
|
* [Environment variables / Aliases](#environment-variables--aliases)
|
||||||
|
* [Settings](#settings)
|
||||||
|
* [Supported commands](#supported-commands)
|
||||||
|
* [Vim plugin](#vim-plugin)
|
||||||
|
* [Advanced topics](#advanced-topics)
|
||||||
|
* [Performance](#performance)
|
||||||
|
* [Executing external programs](#executing-external-programs)
|
||||||
|
* [Preview window](#preview-window)
|
||||||
|
* [Tips](#tips)
|
||||||
|
* [Respecting .gitignore](#respecting-gitignore)
|
||||||
|
* [Fish shell](#fish-shell)
|
||||||
|
* [Related projects](#related-projects)
|
||||||
|
* [<a href="LICENSE">License</a>](#license)
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
|
||||||
@@ -32,9 +75,25 @@ stuff.
|
|||||||
|
|
||||||
[bin]: https://github.com/junegunn/fzf-bin/releases
|
[bin]: https://github.com/junegunn/fzf-bin/releases
|
||||||
|
|
||||||
|
### Using Homebrew or Linuxbrew
|
||||||
|
|
||||||
|
You can use [Homebrew](http://brew.sh/) or [Linuxbrew](http://linuxbrew.sh/)
|
||||||
|
to install fzf.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
brew install fzf
|
||||||
|
|
||||||
|
# To install useful key bindings and fuzzy completion:
|
||||||
|
$(brew --prefix)/opt/fzf/install
|
||||||
|
```
|
||||||
|
|
||||||
|
fzf is also available [via MacPorts][portfile]: `sudo port install fzf`
|
||||||
|
|
||||||
|
[portfile]: https://github.com/macports/macports-ports/blob/master/sysutils/fzf/Portfile
|
||||||
|
|
||||||
### Using git
|
### Using git
|
||||||
|
|
||||||
Clone this repository and run
|
Alternatively, you can "git clone" this repository to any directory and run
|
||||||
[install](https://github.com/junegunn/fzf/blob/master/install) script.
|
[install](https://github.com/junegunn/fzf/blob/master/install) script.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -42,44 +101,75 @@ git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
|
|||||||
~/.fzf/install
|
~/.fzf/install
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using Homebrew
|
### Using Linux package managers
|
||||||
|
|
||||||
On OS X, you can use [Homebrew](http://brew.sh/) to install fzf.
|
| 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` |
|
||||||
|
|
||||||
```sh
|
Shell extensions (key bindings and fuzzy auto-completion) and Vim/Neovim
|
||||||
brew install fzf
|
plugin may or may not be enabled by default depending on the package manager.
|
||||||
|
Refer to the package documentation for more information.
|
||||||
# Install shell extensions
|
|
||||||
/usr/local/opt/fzf/install
|
|
||||||
```
|
|
||||||
|
|
||||||
### Vim plugin
|
|
||||||
|
|
||||||
You can manually add the directory to `&runtimepath` as follows,
|
|
||||||
|
|
||||||
```vim
|
|
||||||
" If installed using git
|
|
||||||
set rtp+=~/.fzf
|
|
||||||
|
|
||||||
" If installed using Homebrew
|
|
||||||
set rtp+=/usr/local/opt/fzf
|
|
||||||
```
|
|
||||||
|
|
||||||
But it's recommended that you use a plugin manager like
|
|
||||||
[vim-plug](https://github.com/junegunn/vim-plug).
|
|
||||||
|
|
||||||
```vim
|
|
||||||
Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
|
|
||||||
```
|
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
Pre-built binaries for Windows can be downloaded [here][bin]. However, other
|
Pre-built binaries for Windows can be downloaded [here][bin]. fzf is also
|
||||||
components of the project may not work on Windows. You might want to consider
|
available via [Chocolatey][choco] and [Scoop][scoop]:
|
||||||
installing fzf on [Windows Subsystem for Linux][wsl] where everything runs
|
|
||||||
flawlessly.
|
|
||||||
|
|
||||||
[wsl]: https://blogs.msdn.microsoft.com/wsl/
|
| 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
|
||||||
|
|
||||||
|
Known issues and limitations on Windows can be found on [the wiki
|
||||||
|
page][windows-wiki].
|
||||||
|
|
||||||
|
[windows-wiki]: https://github.com/junegunn/fzf/wiki/Windows
|
||||||
|
|
||||||
|
### 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
|
Upgrading fzf
|
||||||
-------------
|
-------------
|
||||||
@@ -90,6 +180,7 @@ method used.
|
|||||||
|
|
||||||
- git: `cd ~/.fzf && git pull && ./install`
|
- git: `cd ~/.fzf && git pull && ./install`
|
||||||
- brew: `brew update; brew reinstall fzf`
|
- brew: `brew update; brew reinstall fzf`
|
||||||
|
- chocolatey: `choco upgrade fzf`
|
||||||
- vim-plug: `:PlugUpdate fzf`
|
- vim-plug: `:PlugUpdate fzf`
|
||||||
|
|
||||||
Building fzf
|
Building fzf
|
||||||
@@ -100,7 +191,7 @@ See [BUILD.md](BUILD.md).
|
|||||||
Usage
|
Usage
|
||||||
-----
|
-----
|
||||||
|
|
||||||
fzf will launch curses-based finder, read the list from STDIN, and write the
|
fzf will launch interactive finder, read the list from STDIN, and write the
|
||||||
selected item to STDOUT.
|
selected item to STDOUT.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -133,8 +224,8 @@ cursor with `--height` option.
|
|||||||
vim $(fzf --height 40%)
|
vim $(fzf --height 40%)
|
||||||
```
|
```
|
||||||
|
|
||||||
Also check out `--reverse` option if you prefer "top-down" layout instead of
|
Also check out `--reverse` and `--layout` options if you prefer
|
||||||
the default "bottom-up" layout.
|
"top-down" layout instead of the default "bottom-up" layout.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
vim $(fzf --height 40% --reverse)
|
vim $(fzf --height 40% --reverse)
|
||||||
@@ -144,7 +235,7 @@ You can add these options to `$FZF_DEFAULT_OPTS` so that they're applied by
|
|||||||
default. For example,
|
default. For example,
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
export FZF_DEFAULT_OPTS='--height 40% --reverse --border'
|
export FZF_DEFAULT_OPTS='--height 40% --layout=reverse --border'
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Search syntax
|
#### Search syntax
|
||||||
@@ -154,12 +245,13 @@ type in multiple search terms delimited by spaces. e.g. `^music .mp3$ sbtrkt
|
|||||||
!fire`
|
!fire`
|
||||||
|
|
||||||
| Token | Match type | Description |
|
| Token | Match type | Description |
|
||||||
| -------- | -------------------------- | --------------------------------- |
|
| --------- | -------------------------- | ------------------------------------ |
|
||||||
| `sbtrkt` | fuzzy-match | Items that match `sbtrkt` |
|
| `sbtrkt` | fuzzy-match | Items that match `sbtrkt` |
|
||||||
|
| `'wild` | exact-match (quoted) | Items that include `wild` |
|
||||||
| `^music` | prefix-exact-match | Items that start with `music` |
|
| `^music` | prefix-exact-match | Items that start with `music` |
|
||||||
| `.mp3$` | suffix-exact-match | Items that end with `.mp3` |
|
| `.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` |
|
| `!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` |
|
| `!.mp3$` | inverse-suffix-exact-match | Items that do not end with `.mp3` |
|
||||||
|
|
||||||
If you don't prefer fuzzy matching and do not wish to "quote" every word,
|
If you don't prefer fuzzy matching and do not wish to "quote" every word,
|
||||||
@@ -178,15 +270,22 @@ or `py`.
|
|||||||
|
|
||||||
- `FZF_DEFAULT_COMMAND`
|
- `FZF_DEFAULT_COMMAND`
|
||||||
- Default command to use when input is tty
|
- 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`
|
- `FZF_DEFAULT_OPTS`
|
||||||
- Default options
|
- Default options
|
||||||
- e.g. `export FZF_DEFAULT_OPTS="--reverse --inline-info"`
|
- e.g. `export FZF_DEFAULT_OPTS="--layout=reverse --inline-info"`
|
||||||
|
|
||||||
#### Options
|
#### Options
|
||||||
|
|
||||||
See the man page (`man fzf`) for the full list of 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
|
Examples
|
||||||
--------
|
--------
|
||||||
|
|
||||||
@@ -220,18 +319,18 @@ fullscreen mode.
|
|||||||
fzf --height 40%
|
fzf --height 40%
|
||||||
```
|
```
|
||||||
|
|
||||||
Key bindings for command line
|
Key bindings for command-line
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
The install script will setup the following key bindings for bash, zsh, and
|
The install script will setup the following key bindings for bash, zsh, and
|
||||||
fish.
|
fish.
|
||||||
|
|
||||||
- `CTRL-T` - Paste the selected files and directories onto the command line
|
- `CTRL-T` - Paste the selected files and directories onto the command-line
|
||||||
- Set `FZF_CTRL_T_COMMAND` to override the default command
|
- Set `FZF_CTRL_T_COMMAND` to override the default command
|
||||||
- Set `FZF_CTRL_T_OPTS` to pass additional options
|
- Set `FZF_CTRL_T_OPTS` to pass additional options
|
||||||
- `CTRL-R` - Paste the selected command from history onto the command line
|
- `CTRL-R` - Paste the selected command from history onto the command-line
|
||||||
- Sort is disabled by default to respect chronological ordering
|
- If you want to see the commands in chronological order, press `CTRL-R`
|
||||||
- Press `CTRL-R` again to toggle sort
|
again which toggles sorting by relevance
|
||||||
- Set `FZF_CTRL_R_OPTS` to pass additional options
|
- Set `FZF_CTRL_R_OPTS` to pass additional options
|
||||||
- `ALT-C` - cd into the selected directory
|
- `ALT-C` - cd into the selected directory
|
||||||
- Set `FZF_ALT_C_COMMAND` to override the default command
|
- Set `FZF_ALT_C_COMMAND` to override the default command
|
||||||
@@ -281,7 +380,7 @@ cd ~/github/fzf**<TAB>
|
|||||||
|
|
||||||
#### Process IDs
|
#### Process IDs
|
||||||
|
|
||||||
Fuzzy completion for PIDs is provided for kill command. In this case
|
Fuzzy completion for PIDs is provided for kill command. In this case,
|
||||||
there is no trigger sequence, just press tab key after kill command.
|
there is no trigger sequence, just press tab key after kill command.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -316,12 +415,17 @@ export FZF_COMPLETION_TRIGGER='~~'
|
|||||||
# Options to fzf command
|
# Options to fzf command
|
||||||
export FZF_COMPLETION_OPTS='+c -x'
|
export FZF_COMPLETION_OPTS='+c -x'
|
||||||
|
|
||||||
# Use ag instead of the default find command for listing candidates.
|
# Use fd (https://github.com/sharkdp/fd) instead of the default find
|
||||||
# - The first argument to the function is the base path to start traversal
|
# command for listing path candidates.
|
||||||
# - Note that ag only lists files not directories
|
# - The first argument to the function ($1) is the base path to start traversal
|
||||||
# - See the source code (completion.{bash,zsh}) for the details.
|
# - See the source code (completion.{bash,zsh}) for the details.
|
||||||
_fzf_compgen_path() {
|
_fzf_compgen_path() {
|
||||||
ag -g "" "$1"
|
fd --hidden --follow --exclude ".git" . "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Use fd to generate the list for directory completion
|
||||||
|
_fzf_compgen_dir() {
|
||||||
|
fd --type d --hidden --follow --exclude ".git" . "$1"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -329,163 +433,176 @@ _fzf_compgen_path() {
|
|||||||
|
|
||||||
On bash, fuzzy completion is enabled only for a predefined set of commands
|
On bash, fuzzy completion is enabled only for a predefined set of commands
|
||||||
(`complete | grep _fzf` to see the list). But you can enable it for other
|
(`complete | grep _fzf` to see the list). But you can enable it for other
|
||||||
commands as well like follows.
|
commands as well by using `_fzf_setup_completion` helper function.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# There are also _fzf_path_completion and _fzf_dir_completion
|
# usage: _fzf_setup_completion path|dir COMMANDS...
|
||||||
complete -F _fzf_file_completion -o default -o bashdefault doge
|
_fzf_setup_completion path ag git kubectl
|
||||||
|
_fzf_setup_completion dir tree
|
||||||
```
|
```
|
||||||
|
|
||||||
Usage as Vim plugin
|
Vim plugin
|
||||||
-------------------
|
----------
|
||||||
|
|
||||||
This repository only enables basic integration with Vim. If you're looking for
|
See [README-VIM.md](README-VIM.md).
|
||||||
more, check out [fzf.vim](https://github.com/junegunn/fzf.vim) project.
|
|
||||||
|
|
||||||
(Note: To use fzf in GVim, an external terminal emulator is required.)
|
Advanced topics
|
||||||
|
---------------
|
||||||
|
|
||||||
#### `:FZF[!]`
|
### Performance
|
||||||
|
|
||||||
If you have set up fzf for Vim, `:FZF` command will be added.
|
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.
|
||||||
|
|
||||||
```vim
|
- `--ansi` tells fzf to extract and parse ANSI color codes in the input and it
|
||||||
" Look for files under current directory
|
makes the initial scanning slower. So it's not recommended that you add it
|
||||||
:FZF
|
to your `$FZF_DEFAULT_OPTS`.
|
||||||
|
- `--nth` makes fzf slower as fzf has to tokenize each line.
|
||||||
|
- `--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 a faster greedy
|
||||||
|
algorithm. However, this algorithm is not guaranteed to find the optimal
|
||||||
|
ordering of the matches and is not recommended.
|
||||||
|
|
||||||
" Look for files under your home directory
|
[perf]: https://junegunn.kr/images/fzf-0.17.0.png
|
||||||
:FZF ~
|
|
||||||
|
|
||||||
" With options
|
### Executing external programs
|
||||||
:FZF --no-sort --reverse --inline-info /tmp
|
|
||||||
|
|
||||||
" Bang version starts fzf in fullscreen mode
|
You can set up key bindings for starting external processes without leaving
|
||||||
:FZF!
|
fzf (`execute`, `execute-silent`).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Press F1 to open the file with less without leaving fzf
|
||||||
|
# Press CTRL-Y to copy the line to clipboard and aborts fzf (requires pbcopy)
|
||||||
|
fzf --bind 'f1:execute(less -f {}),ctrl-y:execute-silent(echo {} | pbcopy)+abort'
|
||||||
```
|
```
|
||||||
|
|
||||||
Similarly to [ctrlp.vim](https://github.com/kien/ctrlp.vim), use enter key,
|
See *KEY BINDINGS* section of the man page for details.
|
||||||
`CTRL-T`, `CTRL-X` or `CTRL-V` to open selected files in the current window,
|
|
||||||
in new tabs, in horizontal splits, or in vertical splits respectively.
|
|
||||||
|
|
||||||
Note that the environment variables `FZF_DEFAULT_COMMAND` and
|
### Preview window
|
||||||
`FZF_DEFAULT_OPTS` also apply here. Refer to [the wiki page][fzf-config] for
|
|
||||||
customization.
|
|
||||||
|
|
||||||
[fzf-config]: https://github.com/junegunn/fzf/wiki/Configuring-Vim-plugin
|
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.
|
||||||
|
|
||||||
#### `fzf#run`
|
```bash
|
||||||
|
# {} is replaced to the single-quoted string of the focused line
|
||||||
|
fzf --preview 'cat {}'
|
||||||
|
```
|
||||||
|
|
||||||
For more advanced uses, you can use `fzf#run([options])` function with the
|
Since the preview window is updated only after the process is complete, it's
|
||||||
following options.
|
important that the command finishes quickly.
|
||||||
|
|
||||||
| Option name | Type | Description |
|
```bash
|
||||||
| -------------------------- | ------------- | ---------------------------------------------------------------- |
|
# Use head instead of cat so that the command doesn't take too long to finish
|
||||||
| `source` | string | External command to generate input to fzf (e.g. `find .`) |
|
fzf --preview 'head -100 {}'
|
||||||
| `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 | 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` (*Neovim only*) | 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) |
|
|
||||||
|
|
||||||
Examples can be found on [the wiki
|
Preview window supports ANSI colors, so you can use any program that
|
||||||
page](https://github.com/junegunn/fzf/wiki/Examples-(vim)).
|
syntax-highlights the content of a file.
|
||||||
|
|
||||||
#### `fzf#wrap`
|
- Bat: https://github.com/sharkdp/bat
|
||||||
|
- Highlight: http://www.andre-simon.de/doku/highlight/en/highlight.php
|
||||||
|
|
||||||
`fzf#wrap([name string,] [opts dict,] [fullscreen boolean])` is a helper
|
```bash
|
||||||
function that decorates the options dictionary so that it understands
|
fzf --preview 'bat --style=numbers --color=always {} | head -500'
|
||||||
`g:fzf_layout`, `g:fzf_action`, `g:fzf_colors`, and `g:fzf_history_dir` like
|
```
|
||||||
`:FZF`.
|
|
||||||
|
|
||||||
```vim
|
You can customize the size, position, and border of the preview window using
|
||||||
command! -bang MyStuff
|
`--preview-window` option, and the foreground and background color of it with
|
||||||
\ call fzf#run(fzf#wrap('my-stuff', {'dir': '~/my-stuff'}, <bang>0))
|
`--color` option. For example,
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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'
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
Tips
|
||||||
----
|
----
|
||||||
|
|
||||||
#### Respecting `.gitignore`, `.hgignore`, and `svn:ignore`
|
#### Respecting `.gitignore`
|
||||||
|
|
||||||
[ag](https://github.com/ggreer/the_silver_searcher) or
|
You can use [fd](https://github.com/sharkdp/fd),
|
||||||
[pt](https://github.com/monochromegane/the_platinum_searcher) will do the
|
[ripgrep](https://github.com/BurntSushi/ripgrep), or [the silver
|
||||||
filtering:
|
searcher](https://github.com/ggreer/the_silver_searcher) instead of the
|
||||||
|
default find command to traverse the file system while respecting
|
||||||
|
`.gitignore`.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Feed the output of ag into fzf
|
# Feed the output of fd into fzf
|
||||||
ag -g "" | fzf
|
fd --type f | fzf
|
||||||
|
|
||||||
# Setting ag as the default source for fzf
|
# Setting fd as the default source for fzf
|
||||||
export FZF_DEFAULT_COMMAND='ag -g ""'
|
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
|
fzf
|
||||||
|
|
||||||
# To apply the command to CTRL-T as well
|
# To apply the command to CTRL-T as well
|
||||||
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
|
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
|
```sh
|
||||||
export FZF_DEFAULT_COMMAND='ag --hidden --ignore .git -g ""'
|
export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git'
|
||||||
```
|
|
||||||
|
|
||||||
#### `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'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Fish shell
|
#### Fish shell
|
||||||
|
|
||||||
It's [a known bug of fish](https://github.com/fish-shell/fish-shell/issues/1362)
|
`CTRL-T` key binding of fish, unlike those of bash and zsh, will use the last
|
||||||
that it doesn't allow reading from STDIN in command substitution, which means
|
token on the command-line as the root directory for the recursive search. For
|
||||||
simple `vim (fzf)` won't work as expected. The workaround is to use the `read`
|
instance, hitting `CTRL-T` at the end of the following command-line
|
||||||
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
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
ls /var/
|
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
|
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
|
make use of this feature. `$dir` defaults to `.` when the last token is not a
|
||||||
valid directory. Example:
|
valid directory. Example:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
set -l FZF_CTRL_T_COMMAND "command find -L \$dir -type f 2> /dev/null | sed '1d; s#^\./##'"
|
set -g FZF_CTRL_T_COMMAND "command find -L \$dir -type f 2> /dev/null | sed '1d; s#^\./##'"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Related projects
|
||||||
|
----------------
|
||||||
|
|
||||||
|
https://github.com/junegunn/fzf/wiki/Related-projects
|
||||||
|
|
||||||
[License](LICENSE)
|
[License](LICENSE)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2017 Junegunn Choi
|
Copyright (c) 2019 Junegunn Choi
|
||||||
|
|||||||
36
bin/fzf-tmux
36
bin/fzf-tmux
@@ -16,8 +16,8 @@ skip=""
|
|||||||
swap=""
|
swap=""
|
||||||
close=""
|
close=""
|
||||||
term=""
|
term=""
|
||||||
[[ -n "$LINES" ]] && lines=$LINES || lines=$(tput lines)
|
[[ -n "$LINES" ]] && lines=$LINES || lines=$(tput lines) || lines=$(tmux display-message -p "#{pane_height}")
|
||||||
[[ -n "$COLUMNS" ]] && columns=$COLUMNS || columns=$(tput cols)
|
[[ -n "$COLUMNS" ]] && columns=$COLUMNS || columns=$(tput cols) || columns=$(tmux display-message -p "#{pane_width}")
|
||||||
|
|
||||||
help() {
|
help() {
|
||||||
>&2 echo 'usage: fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS]
|
>&2 echo 'usage: fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS]
|
||||||
@@ -121,7 +121,7 @@ args+=("--no-height")
|
|||||||
if tmux list-panes -F '#F' | grep -q Z; then
|
if tmux list-panes -F '#F' | grep -q Z; then
|
||||||
zoomed=1
|
zoomed=1
|
||||||
original_window=$(tmux display-message -p "#{window_id}")
|
original_window=$(tmux display-message -p "#{window_id}")
|
||||||
tmp_window=$(tmux new-window -d -P -F "#{window_id}" "bash -c 'while :; do for c in \\| / - \\\\; do sleep 0.2; printf \"\\r\$c fzf-tmux is running\\r\"; done; done'")
|
tmp_window=$(tmux new-window -d -P -F "#{window_id}" "bash -c 'while :; do for c in \\| / - '\\;' do sleep 0.2; printf \"\\r\$c fzf-tmux is running\\r\"; done; done'")
|
||||||
tmux swap-pane -t $tmp_window \; select-window -t $tmp_window
|
tmux swap-pane -t $tmp_window \; select-window -t $tmp_window
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -134,17 +134,29 @@ fifo1="${TMPDIR:-/tmp}/fzf-fifo1-$id"
|
|||||||
fifo2="${TMPDIR:-/tmp}/fzf-fifo2-$id"
|
fifo2="${TMPDIR:-/tmp}/fzf-fifo2-$id"
|
||||||
fifo3="${TMPDIR:-/tmp}/fzf-fifo3-$id"
|
fifo3="${TMPDIR:-/tmp}/fzf-fifo3-$id"
|
||||||
cleanup() {
|
cleanup() {
|
||||||
rm -f $argsf $fifo1 $fifo2 $fifo3
|
\rm -f $argsf $fifo1 $fifo2 $fifo3
|
||||||
|
|
||||||
|
# Restore tmux window options
|
||||||
|
if [[ "${#tmux_win_opts[@]}" -gt 0 ]]; then
|
||||||
|
eval "tmux ${tmux_win_opts[@]}"
|
||||||
|
fi
|
||||||
|
|
||||||
# Remove temp window if we were zoomed
|
# Remove temp window if we were zoomed
|
||||||
if [[ -n "$zoomed" ]]; then
|
if [[ -n "$zoomed" ]]; then
|
||||||
|
tmux display-message -p "#{window_id}" > /dev/null
|
||||||
tmux swap-pane -t $original_window \; \
|
tmux swap-pane -t $original_window \; \
|
||||||
select-window -t $original_window \; \
|
select-window -t $original_window \; \
|
||||||
kill-window -t $tmp_window \; \
|
kill-window -t $tmp_window \; \
|
||||||
resize-pane -Z
|
resize-pane -Z
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ $# -gt 0 ]; then
|
||||||
|
trap - EXIT
|
||||||
|
exit 130
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
trap cleanup EXIT SIGINT SIGTERM
|
trap 'cleanup 1' SIGUSR1
|
||||||
|
trap 'cleanup' EXIT
|
||||||
|
|
||||||
envs="env TERM=$TERM "
|
envs="env TERM=$TERM "
|
||||||
[[ -n "$FZF_DEFAULT_OPTS" ]] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
|
[[ -n "$FZF_DEFAULT_OPTS" ]] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
|
||||||
@@ -163,18 +175,24 @@ for arg in "${args[@]}"; do
|
|||||||
opts="$opts \"$arg\""
|
opts="$opts \"$arg\""
|
||||||
done
|
done
|
||||||
|
|
||||||
|
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
|
if [[ -n "$term" ]] || [[ -t 0 ]]; then
|
||||||
cat <<< "\"$fzf\" $opts > $fifo2; echo \$? > $fifo3 $close" > $argsf
|
cat <<< "\"$fzf\" $opts > $fifo2; echo \$? > $fifo3 $close" >> $argsf
|
||||||
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
|
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
|
||||||
set-window-option remain-on-exit off \;\
|
set-window-option remain-on-exit off \;\
|
||||||
split-window $opt "cd $(printf %q "$PWD");$envs bash $argsf" $swap \
|
split-window $opt "$envs bash -c 'cd $(printf %q "$PWD"); exec -a fzf bash $argsf'" $swap \
|
||||||
> /dev/null 2>&1
|
> /dev/null 2>&1
|
||||||
else
|
else
|
||||||
mkfifo $fifo1
|
mkfifo $fifo1
|
||||||
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; echo \$? > $fifo3 $close" > $argsf
|
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; echo \$? > $fifo3 $close" >> $argsf
|
||||||
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
|
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
|
||||||
set-window-option remain-on-exit off \;\
|
set-window-option remain-on-exit off \;\
|
||||||
split-window $opt "$envs bash $argsf" $swap \
|
split-window $opt "$envs bash -c 'exec -a fzf bash $argsf'" $swap \
|
||||||
> /dev/null 2>&1
|
> /dev/null 2>&1
|
||||||
cat <&0 > $fifo1 &
|
cat <&0 > $fifo1 &
|
||||||
fi
|
fi
|
||||||
|
|||||||
344
doc/fzf.txt
Normal file
344
doc/fzf.txt
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
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
|
||||||
|
Tips
|
||||||
|
fzf inside terminal buffer
|
||||||
|
Starting fzf in Neovim floating window
|
||||||
|
Hide statusline
|
||||||
|
License
|
||||||
|
|
||||||
|
FZF VIM INTEGRATION *fzf-vim-integration*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
:FZF[!]
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
*:FZF*
|
||||||
|
>
|
||||||
|
" Look for files under current directory
|
||||||
|
:FZF
|
||||||
|
|
||||||
|
" Look for files under your home directory
|
||||||
|
:FZF ~
|
||||||
|
|
||||||
|
" With fzf command-line options
|
||||||
|
:FZF --reverse --info=inline /tmp
|
||||||
|
|
||||||
|
" Bang version starts fzf in fullscreen mode
|
||||||
|
:FZF!
|
||||||
|
<
|
||||||
|
Similarly to {ctrlp.vim}{2}, use enter key, CTRL-T, CTRL-X or CTRL-V to open
|
||||||
|
selected files in the current window, in new tabs, in horizontal splits, or in
|
||||||
|
vertical splits respectively.
|
||||||
|
|
||||||
|
Note that the environment variables `FZF_DEFAULT_COMMAND` and
|
||||||
|
`FZF_DEFAULT_OPTS` also apply here.
|
||||||
|
|
||||||
|
{2} https://github.com/kien/ctrlp.vim
|
||||||
|
|
||||||
|
|
||||||
|
< Configuration >_____________________________________________________________~
|
||||||
|
*fzf-configuration*
|
||||||
|
|
||||||
|
*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
|
||||||
|
ways
|
||||||
|
- `g:fzf_layout`
|
||||||
|
- Determines the size and position of fzf window
|
||||||
|
- `g:fzf_colors`
|
||||||
|
- Customizes fzf colors to match the current color scheme
|
||||||
|
- `g:fzf_history_dir`
|
||||||
|
- Enables history feature
|
||||||
|
|
||||||
|
|
||||||
|
Examples~
|
||||||
|
*fzf-examples*
|
||||||
|
>
|
||||||
|
" This is the default extra key bindings
|
||||||
|
let g:fzf_action = {
|
||||||
|
\ 'ctrl-t': 'tab split',
|
||||||
|
\ 'ctrl-x': 'split',
|
||||||
|
\ 'ctrl-v': 'vsplit' }
|
||||||
|
|
||||||
|
" An action can be a reference to a function that processes selected lines
|
||||||
|
function! s:build_quickfix_list(lines)
|
||||||
|
call setqflist(map(copy(a:lines), '{ "filename": v:val }'))
|
||||||
|
copen
|
||||||
|
cc
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
let g:fzf_action = {
|
||||||
|
\ 'ctrl-q': function('s:build_quickfix_list'),
|
||||||
|
\ 'ctrl-t': 'tab split',
|
||||||
|
\ 'ctrl-x': 'split',
|
||||||
|
\ 'ctrl-v': 'vsplit' }
|
||||||
|
|
||||||
|
" Default fzf layout
|
||||||
|
" - down / up / left / right
|
||||||
|
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': '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'],
|
||||||
|
\ 'hl': ['fg', 'Comment'],
|
||||||
|
\ 'fg+': ['fg', 'CursorLine', 'CursorColumn', 'Normal'],
|
||||||
|
\ 'bg+': ['bg', 'CursorLine', 'CursorColumn'],
|
||||||
|
\ 'hl+': ['fg', 'Statement'],
|
||||||
|
\ 'info': ['fg', 'PreProc'],
|
||||||
|
\ 'border': ['fg', 'Ignore'],
|
||||||
|
\ 'prompt': ['fg', 'Conditional'],
|
||||||
|
\ 'pointer': ['fg', 'Exception'],
|
||||||
|
\ 'marker': ['fg', 'Keyword'],
|
||||||
|
\ 'spinner': ['fg', 'Label'],
|
||||||
|
\ 'header': ['fg', 'Comment'] }
|
||||||
|
|
||||||
|
" 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()` 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` )
|
||||||
|
`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 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*
|
||||||
|
|
||||||
|
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:
|
||||||
|
>
|
||||||
|
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))
|
||||||
|
<
|
||||||
|
|
||||||
|
TIPS *fzf-tips*
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
< 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*
|
||||||
|
>
|
||||||
|
" 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
|
||||||
|
|
||||||
|
<
|
||||||
|
|
||||||
|
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) 2019 Junegunn Choi
|
||||||
|
|
||||||
|
==============================================================================
|
||||||
|
vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap:
|
||||||
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=
|
||||||
263
install
263
install
@@ -2,12 +2,15 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.16.4
|
version=0.20.0
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
binary_arch=
|
binary_arch=
|
||||||
allow_legacy=
|
shells="bash zsh fish"
|
||||||
|
prefix='~/.fzf'
|
||||||
|
prefix_expand=~/.fzf
|
||||||
|
fish_dir=${XDG_CONFIG_HOME:-$HOME/.config}/fish
|
||||||
|
|
||||||
help() {
|
help() {
|
||||||
cat << EOF
|
cat << EOF
|
||||||
@@ -17,10 +20,15 @@ usage: $0 [OPTIONS]
|
|||||||
--bin Download fzf binary only; Do not generate ~/.fzf.{bash,zsh}
|
--bin Download fzf binary only; Do not generate ~/.fzf.{bash,zsh}
|
||||||
--all Download fzf binary and update configuration files
|
--all Download fzf binary and update configuration files
|
||||||
to enable key bindings and fuzzy completion
|
to enable key bindings and fuzzy completion
|
||||||
|
--xdg Generate files under \$XDG_CONFIG_HOME/fzf
|
||||||
--[no-]key-bindings Enable/disable key bindings (CTRL-T, CTRL-R, ALT-C)
|
--[no-]key-bindings Enable/disable key bindings (CTRL-T, CTRL-R, ALT-C)
|
||||||
--[no-]completion Enable/disable fuzzy completion (bash & zsh)
|
--[no-]completion Enable/disable fuzzy completion (bash & zsh)
|
||||||
--[no-]update-rc Whether or not to update shell configuration files
|
--[no-]update-rc Whether or not to update shell configuration files
|
||||||
|
|
||||||
|
--no-bash Do not set up bash configuration
|
||||||
|
--no-zsh Do not set up zsh configuration
|
||||||
|
--no-fish Do not set up fish configuration
|
||||||
|
|
||||||
--32 Download 32-bit binary
|
--32 Download 32-bit binary
|
||||||
--64 Download 64-bit binary
|
--64 Download 64-bit binary
|
||||||
EOF
|
EOF
|
||||||
@@ -36,7 +44,11 @@ for opt in "$@"; do
|
|||||||
auto_completion=1
|
auto_completion=1
|
||||||
key_bindings=1
|
key_bindings=1
|
||||||
update_config=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 ;;
|
--key-bindings) key_bindings=1 ;;
|
||||||
--no-key-bindings) key_bindings=0 ;;
|
--no-key-bindings) key_bindings=0 ;;
|
||||||
@@ -47,6 +59,9 @@ for opt in "$@"; do
|
|||||||
--32) binary_arch=386 ;;
|
--32) binary_arch=386 ;;
|
||||||
--64) binary_arch=amd64 ;;
|
--64) binary_arch=amd64 ;;
|
||||||
--bin) ;;
|
--bin) ;;
|
||||||
|
--no-bash) shells=${shells/bash/} ;;
|
||||||
|
--no-zsh) shells=${shells/zsh/} ;;
|
||||||
|
--no-fish) shells=${shells/fish/} ;;
|
||||||
*)
|
*)
|
||||||
echo "unknown option: $opt"
|
echo "unknown option: $opt"
|
||||||
help
|
help
|
||||||
@@ -56,17 +71,19 @@ for opt in "$@"; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
fzf_base="$(pwd)"
|
fzf_base=$(pwd)
|
||||||
|
fzf_base_esc=$(printf %q "$fzf_base")
|
||||||
|
|
||||||
ask() {
|
ask() {
|
||||||
# If stdin is a tty, we are "interactive".
|
while true; do
|
||||||
# non-interactive shell: wait for a linefeed
|
read -p "$1 ([y]/n) " -r
|
||||||
# interactive shell: continue after a single keypress
|
REPLY=${REPLY:-"y"}
|
||||||
read_n=$([ -t 0 ] && echo "-n 1")
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
return 1
|
||||||
read -p "$1 ([y]/n) " $read_n -r
|
elif [[ $REPLY =~ ^[Nn]$ ]]; then
|
||||||
echo
|
return 0
|
||||||
[[ $REPLY =~ ^[Nn]$ ]]
|
fi
|
||||||
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
check_binary() {
|
check_binary() {
|
||||||
@@ -76,7 +93,9 @@ check_binary() {
|
|||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "Error: $output"
|
echo "Error: $output"
|
||||||
binary_error="Invalid binary"
|
binary_error="Invalid binary"
|
||||||
elif [ "$version" != "$output" ]; then
|
else
|
||||||
|
output=${output/ */}
|
||||||
|
if [ "$version" != "$output" ]; then
|
||||||
echo "$output != $version"
|
echo "$output != $version"
|
||||||
binary_error="Invalid version"
|
binary_error="Invalid version"
|
||||||
else
|
else
|
||||||
@@ -84,6 +103,7 @@ check_binary() {
|
|||||||
binary_error=""
|
binary_error=""
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
rm -f "$fzf_base"/bin/fzf
|
rm -f "$fzf_base"/bin/fzf
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
@@ -91,7 +111,7 @@ check_binary() {
|
|||||||
link_fzf_in_path() {
|
link_fzf_in_path() {
|
||||||
if which_fzf="$(command -v fzf)"; then
|
if which_fzf="$(command -v fzf)"; then
|
||||||
echo " - Found in \$PATH"
|
echo " - Found in \$PATH"
|
||||||
echo " - Creating symlink: $which_fzf -> bin/fzf"
|
echo " - Creating symlink: bin/fzf -> $which_fzf"
|
||||||
(cd "$fzf_base"/bin && rm -f fzf && ln -sf "$which_fzf" fzf)
|
(cd "$fzf_base"/bin && rm -f fzf && ln -sf "$which_fzf" fzf)
|
||||||
check_binary && return
|
check_binary && return
|
||||||
fi
|
fi
|
||||||
@@ -99,11 +119,23 @@ link_fzf_in_path() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try_curl() {
|
try_curl() {
|
||||||
command -v curl > /dev/null && curl -fL $1 | tar -xz
|
command -v curl > /dev/null &&
|
||||||
|
if [[ $1 =~ tgz$ ]]; then
|
||||||
|
curl -fL $1 | tar -xzf -
|
||||||
|
else
|
||||||
|
local temp=${TMPDIR:-/tmp}/fzf.zip
|
||||||
|
curl -fLo "$temp" $1 && unzip -o "$temp" && rm -f "$temp"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
try_wget() {
|
try_wget() {
|
||||||
command -v wget > /dev/null && wget -O - $1 | tar -xz
|
command -v wget > /dev/null &&
|
||||||
|
if [[ $1 =~ tgz$ ]]; then
|
||||||
|
wget -O - $1 | tar -xzf -
|
||||||
|
else
|
||||||
|
local temp=${TMPDIR:-/tmp}/fzf.zip
|
||||||
|
wget -O "$temp" $1 && unzip -o "$temp" && rm -f "$temp"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
download() {
|
download() {
|
||||||
@@ -123,8 +155,8 @@ download() {
|
|||||||
|
|
||||||
local url
|
local url
|
||||||
[[ "$version" =~ alpha ]] &&
|
[[ "$version" =~ alpha ]] &&
|
||||||
url=https://github.com/junegunn/fzf-bin/releases/download/alpha/${1}.tgz ||
|
url=https://github.com/junegunn/fzf-bin/releases/download/alpha/${1} ||
|
||||||
url=https://github.com/junegunn/fzf-bin/releases/download/$version/${1}.tgz
|
url=https://github.com/junegunn/fzf-bin/releases/download/$version/${1}
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
if ! (try_curl $url || try_wget $url); then
|
if ! (try_curl $url || try_wget $url); then
|
||||||
set +o pipefail
|
set +o pipefail
|
||||||
@@ -146,92 +178,29 @@ archi=$(uname -sm)
|
|||||||
binary_available=1
|
binary_available=1
|
||||||
binary_error=""
|
binary_error=""
|
||||||
case "$archi" in
|
case "$archi" in
|
||||||
Darwin\ *64) download fzf-$version-darwin_${binary_arch:-amd64} ;;
|
Darwin\ *64) download fzf-$version-darwin_${binary_arch:-amd64}.tgz ;;
|
||||||
Darwin\ *86) download fzf-$version-darwin_${binary_arch:-386} ;;
|
Darwin\ *86) download fzf-$version-darwin_${binary_arch:-386}.tgz ;;
|
||||||
Linux\ *64) download fzf-$version-linux_${binary_arch:-amd64} ;;
|
Linux\ armv5*) download fzf-$version-linux_${binary_arch:-arm5}.tgz ;;
|
||||||
Linux\ *86) download fzf-$version-linux_${binary_arch:-386} ;;
|
Linux\ armv6*) download fzf-$version-linux_${binary_arch:-arm6}.tgz ;;
|
||||||
Linux\ armv5*) download fzf-$version-linux_${binary_arch:-arm5} ;;
|
Linux\ armv7*) download fzf-$version-linux_${binary_arch:-arm7}.tgz ;;
|
||||||
Linux\ armv6*) download fzf-$version-linux_${binary_arch:-arm6} ;;
|
Linux\ armv8*) download fzf-$version-linux_${binary_arch:-arm8}.tgz ;;
|
||||||
Linux\ armv7*) download fzf-$version-linux_${binary_arch:-arm7} ;;
|
Linux\ aarch64*) download fzf-$version-linux_${binary_arch:-arm8}.tgz ;;
|
||||||
Linux\ armv8*) download fzf-$version-linux_${binary_arch:-arm8} ;;
|
Linux\ *64) download fzf-$version-linux_${binary_arch:-amd64}.tgz ;;
|
||||||
FreeBSD\ *64) download fzf-$version-freebsd_${binary_arch:-amd64} ;;
|
Linux\ *86) download fzf-$version-linux_${binary_arch:-386}.tgz ;;
|
||||||
FreeBSD\ *86) download fzf-$version-freebsd_${binary_arch:-386} ;;
|
FreeBSD\ *64) download fzf-$version-freebsd_${binary_arch:-amd64}.tgz ;;
|
||||||
OpenBSD\ *64) download fzf-$version-openbsd_${binary_arch:-amd64} ;;
|
FreeBSD\ *86) download fzf-$version-freebsd_${binary_arch:-386}.tgz ;;
|
||||||
OpenBSD\ *86) download fzf-$version-openbsd_${binary_arch:-386} ;;
|
OpenBSD\ *64) download fzf-$version-openbsd_${binary_arch:-amd64}.tgz ;;
|
||||||
|
OpenBSD\ *86) download fzf-$version-openbsd_${binary_arch:-386}.tgz ;;
|
||||||
|
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 ;;
|
*) binary_available=0 binary_error=1 ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
install_ruby_fzf() {
|
|
||||||
if [ -z "$allow_legacy" ]; then
|
|
||||||
ask "Do you want to install legacy Ruby version instead?" && exit 1
|
|
||||||
fi
|
|
||||||
echo "Installing legacy Ruby version ..."
|
|
||||||
|
|
||||||
# ruby executable
|
|
||||||
echo -n "Checking Ruby executable ... "
|
|
||||||
ruby=$(command -v ruby)
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "ruby executable not found !!!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# System ruby is preferred
|
|
||||||
system_ruby=/usr/bin/ruby
|
|
||||||
if [ -x $system_ruby ] && [ $system_ruby != "$ruby" ]; then
|
|
||||||
$system_ruby --disable-gems -rcurses -e0 2> /dev/null
|
|
||||||
[ $? -eq 0 ] && ruby=$system_ruby
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "OK ($ruby)"
|
|
||||||
|
|
||||||
# Curses-support
|
|
||||||
echo -n "Checking Curses support ... "
|
|
||||||
"$ruby" -rcurses -e0 2> /dev/null
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
echo "OK"
|
|
||||||
else
|
|
||||||
echo "Not found"
|
|
||||||
echo "Installing 'curses' gem ... "
|
|
||||||
if (( EUID )); then
|
|
||||||
/usr/bin/env gem install curses --user-install
|
|
||||||
else
|
|
||||||
/usr/bin/env gem install curses
|
|
||||||
fi
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo
|
|
||||||
echo "Failed to install 'curses' gem."
|
|
||||||
if [[ $(uname -r) =~ 'ARCH' ]]; then
|
|
||||||
echo "Make sure that base-devel package group is installed."
|
|
||||||
fi
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Ruby version
|
|
||||||
echo -n "Checking Ruby version ... "
|
|
||||||
"$ruby" -e 'exit RUBY_VERSION >= "1.9"'
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
echo ">= 1.9"
|
|
||||||
"$ruby" --disable-gems -rcurses -e0 2> /dev/null
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
fzf_cmd="$ruby --disable-gems $fzf_base/fzf"
|
|
||||||
else
|
|
||||||
fzf_cmd="$ruby $fzf_base/fzf"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "< 1.9"
|
|
||||||
fzf_cmd="$ruby $fzf_base/fzf"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create fzf script
|
|
||||||
echo -n "Creating wrapper script for fzf ... "
|
|
||||||
rm -f "$fzf_base"/bin/fzf
|
|
||||||
echo "#!/bin/sh" > "$fzf_base"/bin/fzf
|
|
||||||
echo "$fzf_cmd \"\$@\"" >> "$fzf_base"/bin/fzf
|
|
||||||
chmod +x "$fzf_base"/bin/fzf
|
|
||||||
echo "OK"
|
|
||||||
}
|
|
||||||
|
|
||||||
cd "$fzf_base"
|
cd "$fzf_base"
|
||||||
if [ -n "$binary_error" ]; then
|
if [ -n "$binary_error" ]; then
|
||||||
if [ $binary_available -eq 0 ]; then
|
if [ $binary_available -eq 0 ]; then
|
||||||
@@ -240,26 +209,37 @@ if [ -n "$binary_error" ]; then
|
|||||||
echo " - $binary_error !!!"
|
echo " - $binary_error !!!"
|
||||||
fi
|
fi
|
||||||
if command -v go > /dev/null; then
|
if command -v go > /dev/null; then
|
||||||
echo -n "Building binary (go get -u github.com/junegunn/fzf/src/fzf) ... "
|
echo -n "Building binary (go get -u github.com/junegunn/fzf) ... "
|
||||||
if [ -z "${GOPATH-}" ]; then
|
if [ -z "${GOPATH-}" ]; then
|
||||||
export GOPATH="${TMPDIR:-/tmp}/fzf-gopath"
|
export GOPATH="${TMPDIR:-/tmp}/fzf-gopath"
|
||||||
mkdir -p "$GOPATH"
|
mkdir -p "$GOPATH"
|
||||||
fi
|
fi
|
||||||
if go get -u github.com/junegunn/fzf/src/fzf; then
|
if go get -u github.com/junegunn/fzf; then
|
||||||
echo "OK"
|
echo "OK"
|
||||||
cp "$GOPATH/bin/fzf" "$fzf_base/bin/"
|
cp "$GOPATH/bin/fzf" "$fzf_base/bin/"
|
||||||
else
|
else
|
||||||
echo "Failed to build binary ..."
|
echo "Failed to build binary. Installation failed."
|
||||||
install_ruby_fzf
|
exit 1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "go executable not found. Cannot build binary ..."
|
echo "go executable not found. Installation failed."
|
||||||
install_ruby_fzf
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
[[ "$*" =~ "--bin" ]] && exit 0
|
[[ "$*" =~ "--bin" ]] && exit 0
|
||||||
|
|
||||||
|
for s in $shells; do
|
||||||
|
if ! command -v "$s" > /dev/null; then
|
||||||
|
shells=${shells/$s/}
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ${#shells} -lt 3 ]]; then
|
||||||
|
echo "No shell configuration to be updated."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
# Auto-completion
|
# Auto-completion
|
||||||
if [ -z "$auto_completion" ]; then
|
if [ -z "$auto_completion" ]; then
|
||||||
ask "Do you want to enable fuzzy auto-completion?"
|
ask "Do you want to enable fuzzy auto-completion?"
|
||||||
@@ -273,11 +253,10 @@ if [ -z "$key_bindings" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
has_zsh=$(command -v zsh > /dev/null && echo 1 || echo 0)
|
|
||||||
shells=$([ $has_zsh -eq 1 ] && echo "bash zsh" || echo "bash")
|
|
||||||
for shell in $shells; do
|
for shell in $shells; do
|
||||||
echo -n "Generate ~/.fzf.$shell ... "
|
[[ "$shell" = fish ]] && continue
|
||||||
src=~/.fzf.${shell}
|
src=${prefix_expand}.${shell}
|
||||||
|
echo -n "Generate $src ... "
|
||||||
|
|
||||||
fzf_completion="[[ \$- == *i* ]] && source \"$fzf_base/shell/completion.${shell}\" 2> /dev/null"
|
fzf_completion="[[ \$- == *i* ]] && source \"$fzf_base/shell/completion.${shell}\" 2> /dev/null"
|
||||||
if [ $auto_completion -eq 0 ]; then
|
if [ $auto_completion -eq 0 ]; then
|
||||||
@@ -289,11 +268,11 @@ for shell in $shells; do
|
|||||||
fzf_key_bindings="# $fzf_key_bindings"
|
fzf_key_bindings="# $fzf_key_bindings"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cat > $src << EOF
|
cat > "$src" << EOF
|
||||||
# Setup fzf
|
# Setup fzf
|
||||||
# ---------
|
# ---------
|
||||||
if [[ ! "\$PATH" == *$fzf_base/bin* ]]; then
|
if [[ ! "\$PATH" == *$fzf_base_esc/bin* ]]; then
|
||||||
export PATH="\$PATH:$fzf_base/bin"
|
export PATH="\${PATH:+\${PATH}:}$fzf_base/bin"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Auto-completion
|
# Auto-completion
|
||||||
@@ -303,28 +282,26 @@ $fzf_completion
|
|||||||
# Key bindings
|
# Key bindings
|
||||||
# ------------
|
# ------------
|
||||||
$fzf_key_bindings
|
$fzf_key_bindings
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
echo "OK"
|
echo "OK"
|
||||||
done
|
done
|
||||||
|
|
||||||
# fish
|
# fish
|
||||||
has_fish=$(command -v fish > /dev/null && echo 1 || echo 0)
|
if [[ "$shells" =~ fish ]]; then
|
||||||
if [ $has_fish -eq 1 ]; then
|
|
||||||
echo -n "Update fish_user_paths ... "
|
echo -n "Update fish_user_paths ... "
|
||||||
fish << EOF
|
fish << EOF
|
||||||
echo \$fish_user_paths | grep $fzf_base/bin > /dev/null
|
echo \$fish_user_paths | \grep "$fzf_base"/bin > /dev/null
|
||||||
or set --universal fish_user_paths \$fish_user_paths $fzf_base/bin
|
or set --universal fish_user_paths \$fish_user_paths "$fzf_base"/bin
|
||||||
EOF
|
EOF
|
||||||
[ $? -eq 0 ] && echo "OK" || echo "Failed"
|
[ $? -eq 0 ] && echo "OK" || echo "Failed"
|
||||||
|
|
||||||
mkdir -p ~/.config/fish/functions
|
mkdir -p "${fish_dir}/functions"
|
||||||
if [ -e ~/.config/fish/functions/fzf.fish ]; then
|
if [ -e "${fish_dir}/functions/fzf.fish" ]; then
|
||||||
echo -n "Remove unnecessary ~/.config/fish/functions/fzf.fish ... "
|
echo -n "Remove unnecessary ${fish_dir}/functions/fzf.fish ... "
|
||||||
rm -f ~/.config/fish/functions/fzf.fish && echo "OK" || echo "Failed"
|
rm -f "${fish_dir}/functions/fzf.fish" && echo "OK" || echo "Failed"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
fish_binding=~/.config/fish/functions/fzf_key_bindings.fish
|
fish_binding="${fish_dir}/functions/fzf_key_bindings.fish"
|
||||||
if [ $key_bindings -ne 0 ]; then
|
if [ $key_bindings -ne 0 ]; then
|
||||||
echo -n "Symlink $fish_binding ... "
|
echo -n "Symlink $fish_binding ... "
|
||||||
ln -sf "$fzf_base/shell/key-bindings.fish" \
|
ln -sf "$fzf_base/shell/key-bindings.fish" \
|
||||||
@@ -344,20 +321,22 @@ append_line() {
|
|||||||
line="$2"
|
line="$2"
|
||||||
file="$3"
|
file="$3"
|
||||||
pat="${4:-}"
|
pat="${4:-}"
|
||||||
|
lno=""
|
||||||
|
|
||||||
echo "Update $file:"
|
echo "Update $file:"
|
||||||
echo " - $line"
|
echo " - $line"
|
||||||
[ -f "$file" ] || touch "$file"
|
if [ -f "$file" ]; then
|
||||||
if [ $# -lt 4 ]; then
|
if [ $# -lt 4 ]; then
|
||||||
lno=$(\grep -nF "$line" "$file" | sed 's/:.*//' | tr '\n' ' ')
|
lno=$(\grep -nF "$line" "$file" | sed 's/:.*//' | tr '\n' ' ')
|
||||||
else
|
else
|
||||||
lno=$(\grep -nF "$pat" "$file" | sed 's/:.*//' | tr '\n' ' ')
|
lno=$(\grep -nF "$pat" "$file" | sed 's/:.*//' | tr '\n' ' ')
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
if [ -n "$lno" ]; then
|
if [ -n "$lno" ]; then
|
||||||
echo " - Already exists: line #$lno"
|
echo " - Already exists: line #$lno"
|
||||||
else
|
else
|
||||||
if [ $update -eq 1 ]; then
|
if [ $update -eq 1 ]; then
|
||||||
echo >> "$file"
|
[ -f "$file" ] && echo >> "$file"
|
||||||
echo "$line" >> "$file"
|
echo "$line" >> "$file"
|
||||||
echo " + Added"
|
echo " + Added"
|
||||||
else
|
else
|
||||||
@@ -368,6 +347,17 @@ append_line() {
|
|||||||
set +e
|
set +e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
create_file() {
|
||||||
|
local file="$1"
|
||||||
|
shift
|
||||||
|
echo "Create $file:"
|
||||||
|
for line in "$@"; do
|
||||||
|
echo " $line"
|
||||||
|
echo "$line" >> "$file"
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
if [ $update_config -eq 2 ]; then
|
if [ $update_config -eq 2 ]; then
|
||||||
echo
|
echo
|
||||||
ask "Do you want to update your shell configuration files?"
|
ask "Do you want to update your shell configuration files?"
|
||||||
@@ -375,23 +365,30 @@ if [ $update_config -eq 2 ]; then
|
|||||||
fi
|
fi
|
||||||
echo
|
echo
|
||||||
for shell in $shells; do
|
for shell in $shells; do
|
||||||
|
[[ "$shell" = fish ]] && continue
|
||||||
[ $shell = zsh ] && dest=${ZDOTDIR:-~}/.zshrc || dest=~/.bashrc
|
[ $shell = zsh ] && dest=${ZDOTDIR:-~}/.zshrc || dest=~/.bashrc
|
||||||
append_line $update_config "[ -f ~/.fzf.${shell} ] && source ~/.fzf.${shell}" "$dest" "~/.fzf.${shell}"
|
append_line $update_config "[ -f ${prefix}.${shell} ] && source ${prefix}.${shell}" "$dest" "${prefix}.${shell}"
|
||||||
done
|
done
|
||||||
|
|
||||||
if [ $key_bindings -eq 1 ] && [ $has_fish -eq 1 ]; then
|
if [ $key_bindings -eq 1 ] && [[ "$shells" =~ fish ]]; then
|
||||||
bind_file=~/.config/fish/functions/fish_user_key_bindings.fish
|
bind_file="${fish_dir}/functions/fish_user_key_bindings.fish"
|
||||||
|
if [ ! -e "$bind_file" ]; then
|
||||||
|
create_file "$bind_file" \
|
||||||
|
'function fish_user_key_bindings' \
|
||||||
|
' fzf_key_bindings' \
|
||||||
|
'end'
|
||||||
|
else
|
||||||
append_line $update_config "fzf_key_bindings" "$bind_file"
|
append_line $update_config "fzf_key_bindings" "$bind_file"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ $update_config -eq 1 ]; then
|
if [ $update_config -eq 1 ]; then
|
||||||
echo 'Finished. Restart your shell or reload config file.'
|
echo 'Finished. Restart your shell or reload config file.'
|
||||||
echo ' source ~/.bashrc # bash'
|
[[ "$shells" =~ bash ]] && echo ' source ~/.bashrc # bash'
|
||||||
[ $has_zsh -eq 1 ] && echo " source ${ZDOTDIR:-~}/.zshrc # zsh"
|
[[ "$shells" =~ zsh ]] && echo " source ${ZDOTDIR:-~}/.zshrc # zsh"
|
||||||
[ $has_fish -eq 1 ] && [ $key_bindings -eq 1 ] && echo ' fzf_key_bindings # fish'
|
[[ "$shells" =~ fish ]] && [ $key_bindings -eq 1 ] && echo ' fzf_key_bindings # fish'
|
||||||
echo
|
echo
|
||||||
echo 'Use uninstall script to remove fzf.'
|
echo 'Use uninstall script to remove fzf.'
|
||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
echo 'For more information, see: https://github.com/junegunn/fzf'
|
echo 'For more information, see: https://github.com/junegunn/fzf'
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package main
|
|||||||
|
|
||||||
import "github.com/junegunn/fzf/src"
|
import "github.com/junegunn/fzf/src"
|
||||||
|
|
||||||
|
var revision string
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fzf.Run(fzf.ParseOptions())
|
fzf.Run(fzf.ParseOptions(), revision)
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
.ig
|
.ig
|
||||||
The MIT License (MIT)
|
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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
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
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf-tmux 1 "Feb 2017" "fzf 0.16.4" "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
|
.SH NAME
|
||||||
fzf-tmux - open fzf in tmux split pane
|
fzf-tmux - open fzf in tmux split pane
|
||||||
|
|||||||
354
man/man1/fzf.1
354
man/man1/fzf.1
@@ -1,7 +1,7 @@
|
|||||||
.ig
|
.ig
|
||||||
The MIT License (MIT)
|
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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
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
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf 1 "Feb 2017" "fzf 0.16.4" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Dec 2019" "fzf 0.20.0" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -70,6 +70,10 @@ Transform the presentation of each line using field index expressions
|
|||||||
.TP
|
.TP
|
||||||
.BI "-d, --delimiter=" "STR"
|
.BI "-d, --delimiter=" "STR"
|
||||||
Field delimiter regex for \fB--nth\fR and \fB--with-nth\fR (default: AWK-style)
|
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
|
.SS Search result
|
||||||
.TP
|
.TP
|
||||||
.B "+s, --no-sort"
|
.B "+s, --no-sort"
|
||||||
@@ -79,7 +83,8 @@ Do not sort the result
|
|||||||
Reverse the order of the input
|
Reverse the order of the input
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
e.g. \fBhistory | fzf --tac --no-sort\fR
|
e.g.
|
||||||
|
\fBhistory | fzf --tac --no-sort\fR
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.BI "--tiebreak=" "CRI[,..]"
|
.BI "--tiebreak=" "CRI[,..]"
|
||||||
@@ -109,14 +114,18 @@ Comma-separated list of sort criteria to apply when the scores are tied.
|
|||||||
.SS Interface
|
.SS Interface
|
||||||
.TP
|
.TP
|
||||||
.B "-m, --multi"
|
.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
|
||||||
.TP
|
.TP
|
||||||
.B "--no-mouse"
|
.B "--no-mouse"
|
||||||
Disable mouse
|
Disable mouse
|
||||||
.TP
|
.TP
|
||||||
.BI "--bind=" "KEYBINDS"
|
.BI "--bind=" "KEYBINDS"
|
||||||
Comma-separated list of custom key bindings. See \fBKEY BINDINGS\fR for the
|
Comma-separated list of custom key bindings. See \fBKEY/EVENT BINDINGS\fR for
|
||||||
details.
|
the details.
|
||||||
.TP
|
.TP
|
||||||
.B "--cycle"
|
.B "--cycle"
|
||||||
Enable cyclic scroll
|
Enable cyclic scroll
|
||||||
@@ -152,12 +161,30 @@ the full screen.
|
|||||||
.BI "--min-height=" "HEIGHT"
|
.BI "--min-height=" "HEIGHT"
|
||||||
Minimum height when \fB--height\fR is given in percent (default: 10).
|
Minimum height when \fB--height\fR is given in percent (default: 10).
|
||||||
Ignored when \fB--height\fR is not specified.
|
Ignored when \fB--height\fR is not specified.
|
||||||
|
.TP
|
||||||
|
.BI "--layout=" "LAYOUT"
|
||||||
|
Choose the layout (default: default)
|
||||||
|
|
||||||
|
.br
|
||||||
|
.BR default " Display from the bottom of the screen"
|
||||||
|
.br
|
||||||
|
.BR reverse " Display from the top of the screen"
|
||||||
|
.br
|
||||||
|
.BR reverse-list " Display from the top of the screen, prompt at the bottom"
|
||||||
|
.br
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B "--reverse"
|
.B "--reverse"
|
||||||
Reverse orientation
|
A synonym for \fB--layout=reverse\fB
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B "--border"
|
.B "--border"
|
||||||
Draw border above and below the finder
|
Draw border above and below the finder
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B "--no-unicode"
|
||||||
|
Use ASCII characters instead of Unicode box drawing characters to draw border
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "--margin=" MARGIN
|
.BI "--margin=" MARGIN
|
||||||
Comma-separated expression for margins around the finder.
|
Comma-separated expression for margins around the finder.
|
||||||
@@ -180,19 +207,33 @@ terminal size with \fB%\fR suffix.
|
|||||||
.br
|
.br
|
||||||
|
|
||||||
.br
|
.br
|
||||||
e.g. \fBfzf --margin 10%\fR
|
e.g.
|
||||||
\fBfzf --margin 1,5%\fR
|
\fBfzf --margin 10%
|
||||||
|
fzf --margin 1,5%\fR
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.B "--inline-info"
|
.BI "--info=" "STYLE"
|
||||||
Display finder info inline with the query
|
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
|
.TP
|
||||||
.BI "--prompt=" "STR"
|
.BI "--prompt=" "STR"
|
||||||
Input prompt (default: '> ')
|
Input prompt (default: '> ')
|
||||||
.TP
|
.TP
|
||||||
.BI "--header=" "STR"
|
.BI "--header=" "STR"
|
||||||
The given string will be printed as the sticky header. The lines are displayed
|
The given string will be printed as the sticky header. The lines are displayed
|
||||||
in the given order from top to bottom regardless of \fB--reverse\fR option, and
|
in the given order from top to bottom regardless of \fB--layout\fR option, and
|
||||||
are not affected by \fB--with-nth\fR. ANSI color codes are processed even when
|
are not affected by \fB--with-nth\fR. ANSI color codes are processed even when
|
||||||
\fB--ansi\fR is not set.
|
\fB--ansi\fR is not set.
|
||||||
.TP
|
.TP
|
||||||
@@ -214,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
|
foreground/background color. You can also specify 24-bit color in \fB#rrggbb\fR
|
||||||
format.
|
format.
|
||||||
|
|
||||||
.RS
|
|
||||||
e.g. \fBfzf --color=bg+:24\fR
|
|
||||||
\fBfzf --color=light,fg:232,bg:255,bg+:116,info:27\fR
|
|
||||||
.RE
|
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
.B BASE SCHEME:
|
.B BASE SCHEME:
|
||||||
(default: dark on 256-color terminal, otherwise 16)
|
(default: dark on 256-color terminal, otherwise 16)
|
||||||
@@ -226,21 +262,38 @@ e.g. \fBfzf --color=bg+:24\fR
|
|||||||
\fBdark \fRColor scheme for dark 256-color terminal
|
\fBdark \fRColor scheme for dark 256-color terminal
|
||||||
\fBlight \fRColor scheme for light 256-color terminal
|
\fBlight \fRColor scheme for light 256-color terminal
|
||||||
\fB16 \fRColor scheme for 16-color terminal
|
\fB16 \fRColor scheme for 16-color terminal
|
||||||
\fBbw \fRNo colors
|
\fBbw \fRNo colors (equivalent to \fB--no-color\fR)
|
||||||
|
|
||||||
.B COLOR:
|
.B COLOR:
|
||||||
\fBfg \fRText
|
\fBfg \fRText
|
||||||
\fBbg \fRBackground
|
\fBbg \fRBackground
|
||||||
|
\fBpreview-fg \fRPreview window text
|
||||||
|
\fBpreview-bg \fRPreview window background
|
||||||
\fBhl \fRHighlighted substrings
|
\fBhl \fRHighlighted substrings
|
||||||
\fBfg+ \fRText (current line)
|
\fBfg+ \fRText (current line)
|
||||||
\fBbg+ \fRBackground (current line)
|
\fBbg+ \fRBackground (current line)
|
||||||
|
\fBgutter \fRGutter on the left (defaults to \fBbg+\fR)
|
||||||
\fBhl+ \fRHighlighted substrings (current line)
|
\fBhl+ \fRHighlighted substrings (current line)
|
||||||
\fBinfo \fRInfo
|
\fBinfo \fRInfo
|
||||||
|
\fBborder \fRBorder of the preview window and horizontal separators (\fB--border\fR)
|
||||||
\fBprompt \fRPrompt
|
\fBprompt \fRPrompt
|
||||||
\fBpointer \fRPointer to the current line
|
\fBpointer \fRPointer to the current line
|
||||||
\fBmarker \fRMulti-select marker
|
\fBmarker \fRMulti-select marker
|
||||||
\fBspinner \fRStreaming input indicator
|
\fBspinner \fRStreaming input indicator
|
||||||
\fBheader \fRHeader
|
\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
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.B "--no-bold"
|
.B "--no-bold"
|
||||||
@@ -268,23 +321,50 @@ string, specify field index expressions between the braces (See \fBFIELD INDEX
|
|||||||
EXPRESSION\fR for the details).
|
EXPRESSION\fR for the details).
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
e.g. \fBfzf --preview="head -$LINES {}"\fR
|
e.g.
|
||||||
\fBls -l | fzf --preview="echo user={3} when={-4..-2}; cat {-1}" --header-lines=1\fR
|
\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
|
A placeholder expression starting with \fB+\fR flag will be replaced to the
|
||||||
space-separated list of the selected lines (or the current line if no selection
|
space-separated list of the selected lines (or the current line if no selection
|
||||||
was made) individually quoted.
|
was made) individually quoted.
|
||||||
|
|
||||||
e.g. \fBfzf --multi --preview="head -10 {+}"\fR
|
e.g.
|
||||||
\fBgit log --oneline | fzf --multi --preview 'git show {+1}'\fR
|
\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.
|
Note that you can escape a placeholder pattern by prepending a backslash.
|
||||||
|
|
||||||
|
Preview window will be updated even when there is no match for the current
|
||||||
|
query if any of the placeholder expressions evaluates to a non-empty string.
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.BI "--preview-window=" "[POSITION][:SIZE[%]][:wrap][:hidden]"
|
.BI "--preview-window=" "[POSITION][:SIZE[%]][:noborder][:wrap][:hidden]"
|
||||||
Determine the layout of the preview window. If the argument ends with
|
Determines the layout of the preview window. If the argument contains
|
||||||
\fB:hidden\fR, the preview window will be hidden by default until
|
\fB:hidden\fR, the preview window will be hidden by default until
|
||||||
\fBtoggle-preview\fR action is triggered. Long lines are truncated by default.
|
\fBtoggle-preview\fR action is triggered. Long lines are truncated by default.
|
||||||
Line wrap can be enabled with \fB:wrap\fR flag.
|
Line wrap can be enabled with \fB:wrap\fR flag.
|
||||||
@@ -301,8 +381,9 @@ execute the command in the background.
|
|||||||
.RE
|
.RE
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
e.g. \fBfzf --preview="head {}" --preview-window=up:30%\fR
|
e.g.
|
||||||
\fBfzf --preview="file {}" --preview-window=down:1\fR
|
\fBfzf --preview="head {}" --preview-window=up:30%
|
||||||
|
fzf --preview="file {}" --preview-window=down:1\fR
|
||||||
.RE
|
.RE
|
||||||
.SS Scripting
|
.SS Scripting
|
||||||
.TP
|
.TP
|
||||||
@@ -327,10 +408,13 @@ Comma-separated list of keys that can be used to complete fzf in addition to
|
|||||||
the default enter key. When this option is set, fzf will print the name of the
|
the default enter key. When this option is set, fzf will print the name of the
|
||||||
key pressed as the first line of its output (or as the second line if
|
key pressed as the first line of its output (or as the second line if
|
||||||
\fB--print-query\fR is also used). The line will be empty if fzf is completed
|
\fB--print-query\fR is also used). The line will be empty if fzf is completed
|
||||||
with the default enter key.
|
with the default enter key. If \fB--expect\fR option is specified multiple
|
||||||
|
times, fzf will expect the union of the keys. \fB--no-expect\fR will clear the
|
||||||
|
list.
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
e.g. \fBfzf --expect=ctrl-v,ctrl-t,alt-s,f1,f2,~,@\fR
|
e.g.
|
||||||
|
\fBfzf --expect=ctrl-v,ctrl-t,alt-s --expect=f1,f2,~,@\fR
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.B "--read0"
|
.B "--read0"
|
||||||
@@ -339,6 +423,12 @@ Read input delimited by ASCII NUL characters instead of newline characters
|
|||||||
.B "--print0"
|
.B "--print0"
|
||||||
Print output delimited by ASCII NUL characters instead of newline characters
|
Print output delimited by ASCII NUL characters instead of newline characters
|
||||||
.TP
|
.TP
|
||||||
|
.B "--no-clear"
|
||||||
|
Do not clear finder interface on exit. If fzf was started in full screen mode,
|
||||||
|
it will not switch back to the original screen, so you'll have to manually run
|
||||||
|
\fBtput rmcup\fR to return. This option can be used to avoid flickering of the
|
||||||
|
screen when your application needs to start fzf multiple times in order.
|
||||||
|
.TP
|
||||||
.B "--sync"
|
.B "--sync"
|
||||||
Synchronous search for multi-staged filtering. If specified, fzf will launch
|
Synchronous search for multi-staged filtering. If specified, fzf will launch
|
||||||
ncurses finder only after the input stream is complete.
|
ncurses finder only after the input stream is complete.
|
||||||
@@ -346,11 +436,18 @@ ncurses finder only after the input stream is complete.
|
|||||||
.RS
|
.RS
|
||||||
e.g. \fBfzf --multi | fzf --sync\fR
|
e.g. \fBfzf --multi | fzf --sync\fR
|
||||||
.RE
|
.RE
|
||||||
|
.TP
|
||||||
|
.B "--version"
|
||||||
|
Display version information and exit
|
||||||
|
|
||||||
|
.TP
|
||||||
|
Note that most options have the opposite versions with \fB--no-\fR prefix.
|
||||||
|
|
||||||
.SH ENVIRONMENT VARIABLES
|
.SH ENVIRONMENT VARIABLES
|
||||||
.TP
|
.TP
|
||||||
.B FZF_DEFAULT_COMMAND
|
.B FZF_DEFAULT_COMMAND
|
||||||
Default command to use when input is tty
|
Default command to use when input is tty. On *nix systems, fzf runs the command
|
||||||
|
with \fBsh -c\fR, so make sure that it's POSIX-compliant.
|
||||||
.TP
|
.TP
|
||||||
.B FZF_DEFAULT_OPTS
|
.B FZF_DEFAULT_OPTS
|
||||||
Default options. e.g. \fBexport FZF_DEFAULT_OPTS="--extended --cycle"\fR
|
Default options. e.g. \fBexport FZF_DEFAULT_OPTS="--extended --cycle"\fR
|
||||||
@@ -394,6 +491,9 @@ Unless specified otherwise, fzf will start in "extended-search mode". In this
|
|||||||
mode, you can specify multiple patterns delimited by spaces, such as: \fB'wild
|
mode, you can specify multiple patterns delimited by spaces, such as: \fB'wild
|
||||||
^music .mp3$ sbtrkt !rmx\fR
|
^music .mp3$ sbtrkt !rmx\fR
|
||||||
|
|
||||||
|
You can prepend a backslash to a space (\fB\\ \fR) to match a literal space
|
||||||
|
character.
|
||||||
|
|
||||||
.SS Exact-match (quoted)
|
.SS Exact-match (quoted)
|
||||||
A term that is prefixed by a single-quote character (\fB'\fR) is interpreted as
|
A term that is prefixed by a single-quote character (\fB'\fR) is interpreted as
|
||||||
an "exact-match" (or "non-fuzzy") term. fzf will search for the exact
|
an "exact-match" (or "non-fuzzy") term. fzf will search for the exact
|
||||||
@@ -420,56 +520,127 @@ query matches entries that start with \fBcore\fR and end with either \fBgo\fR,
|
|||||||
|
|
||||||
e.g. \fB^core go$ | rb$ | py$\fR
|
e.g. \fB^core go$ | rb$ | py$\fR
|
||||||
|
|
||||||
.SH KEY BINDINGS
|
.SH KEY/EVENT BINDINGS
|
||||||
You can customize key bindings of fzf with \fB--bind\fR option which takes
|
\fB--bind\fR option allows you to bind \fBa key\fR or \fBan event\fR to one or
|
||||||
a comma-separated list of key binding expressions. Each key binding expression
|
more \fBactions\fR. You can use it to customize key bindings or implement
|
||||||
follows the following format: \fBKEY:ACTION\fR
|
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)
|
e.g.
|
||||||
\fIctrl-[a-z]\fR
|
\fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
||||||
\fIctrl-space\fR
|
|
||||||
\fIalt-[a-z]\fR
|
.SS AVAILABLE KEYS: (SYNONYMS)
|
||||||
\fIalt-[0-9]\fR
|
\fIctrl-[a-z]\fR
|
||||||
\fIf[1-12]\fR
|
.br
|
||||||
\fIenter\fR (\fIreturn\fR \fIctrl-m\fR)
|
\fIctrl-space\fR
|
||||||
\fIspace\fR
|
.br
|
||||||
\fIbspace\fR (\fIbs\fR)
|
\fIctrl-\\\fR
|
||||||
\fIalt-enter\fR
|
.br
|
||||||
\fIalt-space\fR
|
\fIctrl-]\fR
|
||||||
\fIalt-bspace\fR (\fIalt-bs\fR)
|
.br
|
||||||
\fIalt-/\fR
|
\fIctrl-^\fR (\fIctrl-6\fR)
|
||||||
\fItab\fR
|
.br
|
||||||
\fIbtab\fR (\fIshift-tab\fR)
|
\fIctrl-/\fR (\fIctrl-_\fR)
|
||||||
\fIesc\fR
|
.br
|
||||||
\fIdel\fR
|
\fIctrl-alt-[a-z]\fR
|
||||||
\fIup\fR
|
.br
|
||||||
\fIdown\fR
|
\fIalt-[a-z]\fR
|
||||||
\fIleft\fR
|
.br
|
||||||
\fIright\fR
|
\fIalt-[0-9]\fR
|
||||||
\fIhome\fR
|
.br
|
||||||
\fIend\fR
|
\fIf[1-12]\fR
|
||||||
\fIpgup\fR (\fIpage-up\fR)
|
.br
|
||||||
\fIpgdn\fR (\fIpage-down\fR)
|
\fIenter\fR (\fIreturn\fR \fIctrl-m\fR)
|
||||||
\fIshift-left\fR
|
.br
|
||||||
\fIshift-right\fR
|
\fIspace\fR
|
||||||
\fIdouble-click\fR
|
.br
|
||||||
or any single character
|
\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
|
||||||
|
|
||||||
|
.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):
|
\fBACTION: DEFAULT BINDINGS (NOTES):
|
||||||
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
|
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
|
||||||
\fBaccept\fR \fIenter double-click\fR
|
\fBaccept\fR \fIenter double-click\fR
|
||||||
|
\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-char\fR \fIctrl-b left\fR
|
||||||
\fBbackward-delete-char\fR \fIctrl-h bspace\fR
|
\fBbackward-delete-char\fR \fIctrl-h bspace\fR
|
||||||
\fBbackward-kill-word\fR \fIalt-bs\fR
|
\fBbackward-kill-word\fR \fIalt-bs\fR
|
||||||
\fBbackward-word\fR \fIalt-b shift-left\fR
|
\fBbackward-word\fR \fIalt-b shift-left\fR
|
||||||
\fBbeginning-of-line\fR \fIctrl-a home\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-screen\fR \fIctrl-l\fR
|
||||||
|
\fBclear-selection\fR (clear multi-selection)
|
||||||
|
\fBclear-query\fR (clear query string)
|
||||||
\fBdelete-char\fR \fIdel\fR
|
\fBdelete-char\fR \fIdel\fR
|
||||||
\fBdelete-char/eof\fR \fIctrl-d\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
|
\fBdown\fR \fIctrl-j ctrl-n down\fR
|
||||||
\fBend-of-line\fR \fIctrl-e end\fR
|
\fBend-of-line\fR \fIctrl-e end\fR
|
||||||
\fBexecute(...)\fR (see below for the details)
|
\fBexecute(...)\fR (see below for the details)
|
||||||
@@ -487,30 +658,39 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
|||||||
\fBpage-up\fR \fIpgup\fR
|
\fBpage-up\fR \fIpgup\fR
|
||||||
\fBhalf-page-down\fR
|
\fBhalf-page-down\fR
|
||||||
\fBhalf-page-up\fR
|
\fBhalf-page-up\fR
|
||||||
\fBpreview-down\fR
|
\fBpreview-down\fR \fIshift-down\fR
|
||||||
\fBpreview-up\fR
|
\fBpreview-up\fR \fIshift-up\fR
|
||||||
\fBpreview-page-down\fR
|
\fBpreview-page-down\fR
|
||||||
\fBpreview-page-up\fR
|
\fBpreview-page-up\fR
|
||||||
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
||||||
\fBprint-query\fR (print query and exit)
|
\fBprint-query\fR (print query and exit)
|
||||||
\fBselect-all\fR
|
\fBreload(...)\fR (see below for the details)
|
||||||
\fBtoggle\fR
|
\fBreplace-query\fR (replace query string with the current selection)
|
||||||
\fBtoggle-all\fR
|
\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+down\fR \fIctrl-i (tab)\fR
|
||||||
\fBtoggle-in\fR (\fB--reverse\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
\fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
||||||
\fBtoggle-out\fR (\fB--reverse\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
||||||
\fBtoggle-preview\fR
|
\fBtoggle-preview\fR
|
||||||
|
\fBtoggle-preview-wrap\fR
|
||||||
\fBtoggle-sort\fR
|
\fBtoggle-sort\fR
|
||||||
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
||||||
|
\fBtop\fR (move to the top result)
|
||||||
\fBunix-line-discard\fR \fIctrl-u\fR
|
\fBunix-line-discard\fR \fIctrl-u\fR
|
||||||
\fBunix-word-rubout\fR \fIctrl-w\fR
|
\fBunix-word-rubout\fR \fIctrl-w\fR
|
||||||
\fBup\fR \fIctrl-k ctrl-p up\fR
|
\fBup\fR \fIctrl-k ctrl-p up\fR
|
||||||
\fByank\fR \fIctrl-y\fR
|
\fByank\fR \fIctrl-y\fR
|
||||||
|
|
||||||
|
.SS ACTION COMPOSITION
|
||||||
|
|
||||||
Multiple actions can be chained using \fB+\fR separator.
|
Multiple actions can be chained using \fB+\fR separator.
|
||||||
|
|
||||||
|
e.g.
|
||||||
\fBfzf --bind 'ctrl-a:select-all+accept'\fR
|
\fBfzf --bind 'ctrl-a:select-all+accept'\fR
|
||||||
|
|
||||||
|
.SS COMMAND EXECUTION
|
||||||
|
|
||||||
With \fBexecute(...)\fR action, you can execute arbitrary commands without
|
With \fBexecute(...)\fR action, you can execute arbitrary commands without
|
||||||
leaving fzf. For example, you can turn fzf into a simple file browser by
|
leaving fzf. For example, you can turn fzf into a simple file browser by
|
||||||
binding \fBenter\fR key to \fBless\fR command like follows.
|
binding \fBenter\fR key to \fBless\fR command like follows.
|
||||||
@@ -538,18 +718,38 @@ parse errors.
|
|||||||
\fBexecute|...|\fR
|
\fBexecute|...|\fR
|
||||||
\fBexecute:...\fR
|
\fBexecute:...\fR
|
||||||
.RS
|
.RS
|
||||||
This is the special form that frees you from parse errors as it does not expect
|
The last one is the special form that frees you from parse errors as it does
|
||||||
the closing character. The catch is that it should be the last one in the
|
not expect the closing character. The catch is that it should be the last one
|
||||||
comma-separated list of key-action pairs.
|
in the comma-separated list of key-action pairs.
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
fzf switches to the alternate screen when executing a command. However, if the
|
fzf switches to the alternate screen when executing a command. However, if the
|
||||||
command is expected to complete quickly, and you are not interested in its
|
command is expected to complete quickly, and you are not interested in its
|
||||||
output, you might want to use \fBexecute-silent\fR instead, which silently
|
output, you might want to use \fBexecute-silent\fR instead, which silently
|
||||||
executes the command without the switching. Note that fzf will not be
|
executes the command without the switching. Note that fzf will not be
|
||||||
responsible until the command is complete. For asynchronous execution, start
|
responsive until the command is complete. For asynchronous execution, start
|
||||||
your command as a background process (i.e. appending \fB&\fR).
|
your command as a background process (i.e. appending \fB&\fR).
|
||||||
|
|
||||||
|
.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
|
.SH AUTHOR
|
||||||
Junegunn Choi (\fIjunegunn.c@gmail.com\fR)
|
Junegunn Choi (\fIjunegunn.c@gmail.com\fR)
|
||||||
|
|
||||||
|
|||||||
381
plugin/fzf.vim
381
plugin/fzf.vim
@@ -26,10 +26,92 @@ if exists('g:loaded_fzf')
|
|||||||
endif
|
endif
|
||||||
let g:loaded_fzf = 1
|
let g:loaded_fzf = 1
|
||||||
|
|
||||||
|
let s:is_win = has('win32') || has('win64')
|
||||||
|
if s:is_win && &shellslash
|
||||||
|
set noshellslash
|
||||||
|
let s:base_dir = expand('<sfile>:h:h')
|
||||||
|
set shellslash
|
||||||
|
else
|
||||||
|
let s:base_dir = expand('<sfile>:h:h')
|
||||||
|
endif
|
||||||
|
if s:is_win
|
||||||
|
let s:term_marker = '&::FZF'
|
||||||
|
|
||||||
|
function! s:fzf_call(fn, ...)
|
||||||
|
let shellslash = &shellslash
|
||||||
|
try
|
||||||
|
set noshellslash
|
||||||
|
return call(a:fn, a:000)
|
||||||
|
finally
|
||||||
|
let &shellslash = shellslash
|
||||||
|
endtry
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
" 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 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"
|
||||||
|
|
||||||
|
function! s:fzf_call(fn, ...)
|
||||||
|
return call(a:fn, a:000)
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
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)
|
||||||
|
let escaped = substitute(a:arg, '[&|<>()@^]', '^&', 'g')
|
||||||
|
let escaped = substitute(escaped, '%', '%%', 'g')
|
||||||
|
let escaped = substitute(escaped, '"', '\\^&', 'g')
|
||||||
|
let escaped = substitute(escaped, '\(\\\+\)\(\\^\)', '\1\1\2', 'g')
|
||||||
|
return '^"'.substitute(escaped, '\(\\\+\)$', '\1\1', '').'^"'
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! fzf#shellescape(arg, ...)
|
||||||
|
let shell = get(a:000, 0, s:is_win ? 'cmd.exe' : 'sh')
|
||||||
|
if shell =~# 'cmd.exe$'
|
||||||
|
return s:shellesc_cmd(a:arg)
|
||||||
|
endif
|
||||||
|
return s:fzf_call('shellescape', a:arg)
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:fzf_getcwd()
|
||||||
|
return s:fzf_call('getcwd')
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:fzf_fnamemodify(fname, mods)
|
||||||
|
return s:fzf_call('fnamemodify', a:fname, a:mods)
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:fzf_expand(fmt)
|
||||||
|
return s:fzf_call('expand', a:fmt, 1)
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:fzf_tempname()
|
||||||
|
return s:fzf_call('tempname')
|
||||||
|
endfunction
|
||||||
|
|
||||||
let s:default_layout = { 'down': '~40%' }
|
let s:default_layout = { 'down': '~40%' }
|
||||||
let s:layout_keys = ['window', 'up', 'down', 'left', 'right']
|
let s:layout_keys = ['window', 'up', 'down', 'left', 'right']
|
||||||
let s:is_win = has('win32') || has('win64')
|
|
||||||
let s:base_dir = expand('<sfile>:h:h')
|
|
||||||
let s:fzf_go = s:base_dir.'/bin/fzf'
|
let s:fzf_go = s:base_dir.'/bin/fzf'
|
||||||
let s:fzf_tmux = s:base_dir.'/bin/fzf-tmux'
|
let s:fzf_tmux = s:base_dir.'/bin/fzf-tmux'
|
||||||
let s:install = s:base_dir.'/install'
|
let s:install = s:base_dir.'/install'
|
||||||
@@ -44,7 +126,7 @@ function! s:fzf_exec()
|
|||||||
let s:exec = s:fzf_go
|
let s:exec = s:fzf_go
|
||||||
elseif executable('fzf')
|
elseif executable('fzf')
|
||||||
let s:exec = 'fzf'
|
let s:exec = 'fzf'
|
||||||
elseif s:is_win
|
elseif s:is_win && !has('win32unix')
|
||||||
call s:warn('fzf executable not found.')
|
call s:warn('fzf executable not found.')
|
||||||
call s:warn('Download fzf binary for Windows from https://github.com/junegunn/fzf-bin/releases/')
|
call s:warn('Download fzf binary for Windows from https://github.com/junegunn/fzf-bin/releases/')
|
||||||
call s:warn('and place it as '.s:base_dir.'\bin\fzf.exe')
|
call s:warn('and place it as '.s:base_dir.'\bin\fzf.exe')
|
||||||
@@ -62,7 +144,7 @@ function! s:fzf_exec()
|
|||||||
throw 'fzf executable not found'
|
throw 'fzf executable not found'
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
return s:shellesc(s:exec)
|
return fzf#shellescape(s:exec)
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:tmux_enabled()
|
function! s:tmux_enabled()
|
||||||
@@ -82,18 +164,9 @@ function! s:tmux_enabled()
|
|||||||
return s:tmux
|
return s:tmux
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:shellesc(arg)
|
|
||||||
return '"'.substitute(a:arg, '"', '\\"', 'g').'"'
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
function! s:escape(path)
|
function! s:escape(path)
|
||||||
let escaped_chars = '$%#''"'
|
let path = fnameescape(a:path)
|
||||||
|
return s:is_win ? escape(path, '$') : path
|
||||||
if has('unix')
|
|
||||||
let escaped_chars .= ' \'
|
|
||||||
endif
|
|
||||||
|
|
||||||
return escape(a:path, escaped_chars)
|
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
" Upgrade legacy options
|
" Upgrade legacy options
|
||||||
@@ -133,7 +206,7 @@ function! s:has_any(dict, keys)
|
|||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:open(cmd, target)
|
function! s:open(cmd, target)
|
||||||
if stridx('edit', a:cmd) == 0 && fnamemodify(a:target, ':p') ==# expand('%:p')
|
if stridx('edit', a:cmd) == 0 && s:fzf_fnamemodify(a:target, ':p') ==# s:fzf_expand('%:p')
|
||||||
return
|
return
|
||||||
endif
|
endif
|
||||||
execute a:cmd s:escape(a:target)
|
execute a:cmd s:escape(a:target)
|
||||||
@@ -144,15 +217,18 @@ function! s:common_sink(action, lines) abort
|
|||||||
return
|
return
|
||||||
endif
|
endif
|
||||||
let key = remove(a:lines, 0)
|
let key = remove(a:lines, 0)
|
||||||
let cmd = get(a:action, key, 'e')
|
let Cmd = get(a:action, key, 'e')
|
||||||
|
if type(Cmd) == type(function('call'))
|
||||||
|
return Cmd(a:lines)
|
||||||
|
endif
|
||||||
if len(a:lines) > 1
|
if len(a:lines) > 1
|
||||||
augroup fzf_swap
|
augroup fzf_swap
|
||||||
autocmd SwapExists * let v:swapchoice='o'
|
autocmd SwapExists * let v:swapchoice='o'
|
||||||
\| call s:warn('fzf: E325: swap file exists: '.expand('<afile>'))
|
\| call s:warn('fzf: E325: swap file exists: '.s:fzf_expand('<afile>'))
|
||||||
augroup END
|
augroup END
|
||||||
endif
|
endif
|
||||||
try
|
try
|
||||||
let empty = empty(expand('%')) && line('$') == 1 && empty(getline(1)) && !&modified
|
let empty = empty(s:fzf_expand('%')) && line('$') == 1 && empty(getline(1)) && !&modified
|
||||||
let autochdir = &autochdir
|
let autochdir = &autochdir
|
||||||
set noautochdir
|
set noautochdir
|
||||||
for item in a:lines
|
for item in a:lines
|
||||||
@@ -160,13 +236,14 @@ function! s:common_sink(action, lines) abort
|
|||||||
execute 'e' s:escape(item)
|
execute 'e' s:escape(item)
|
||||||
let empty = 0
|
let empty = 0
|
||||||
else
|
else
|
||||||
call s:open(cmd, item)
|
call s:open(Cmd, item)
|
||||||
endif
|
endif
|
||||||
if !has('patch-8.0.0177') && !has('nvim-0.2') && exists('#BufEnter')
|
if !has('patch-8.0.0177') && !has('nvim-0.2') && exists('#BufEnter')
|
||||||
\ && isdirectory(item)
|
\ && isdirectory(item)
|
||||||
doautocmd BufEnter
|
doautocmd BufEnter
|
||||||
endif
|
endif
|
||||||
endfor
|
endfor
|
||||||
|
catch /^Vim:Interrupt$/
|
||||||
finally
|
finally
|
||||||
let &autochdir = autochdir
|
let &autochdir = autochdir
|
||||||
silent! autocmd! fzf_swap
|
silent! autocmd! fzf_swap
|
||||||
@@ -174,7 +251,7 @@ function! s:common_sink(action, lines) abort
|
|||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:get_color(attr, ...)
|
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 fam = gui ? 'gui' : 'cterm'
|
||||||
let pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$'
|
let pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$'
|
||||||
for group in a:000
|
for group in a:000
|
||||||
@@ -189,7 +266,22 @@ endfunction
|
|||||||
function! s:defaults()
|
function! s:defaults()
|
||||||
let rules = copy(get(g:, 'fzf_colors', {}))
|
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, ":")'), ',')
|
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)
|
||||||
|
for key in keys(a:layout)
|
||||||
|
if index(s:layout_keys, key) < 0
|
||||||
|
throw printf('Invalid entry in g:fzf_layout: %s (allowed: %s)%s',
|
||||||
|
\ key, join(s:layout_keys, ', '), key == 'options' ? '. Use $FZF_DEFAULT_OPTS.' : '')
|
||||||
|
endif
|
||||||
|
endfor
|
||||||
|
return a:layout
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:evaluate_opts(options)
|
||||||
|
return type(a:options) == type([]) ?
|
||||||
|
\ join(map(copy(a:options), 'fzf#shellescape(v:val)')) : a:options
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
" [name string,] [opts dict,] [fullscreen boolean]
|
" [name string,] [opts dict,] [fullscreen boolean]
|
||||||
@@ -200,7 +292,7 @@ function! fzf#wrap(...)
|
|||||||
for arg in copy(a:000)
|
for arg in copy(a:000)
|
||||||
let tidx = index(expects, type(arg), tidx)
|
let tidx = index(expects, type(arg), tidx)
|
||||||
if tidx < 0
|
if tidx < 0
|
||||||
throw 'invalid arguments (expected: [name string] [opts dict] [fullscreen boolean])'
|
throw 'Invalid arguments (expected: [name string] [opts dict] [fullscreen boolean])'
|
||||||
endif
|
endif
|
||||||
let args[tidx] = arg
|
let args[tidx] = arg
|
||||||
let tidx += 1
|
let tidx += 1
|
||||||
@@ -223,20 +315,21 @@ function! fzf#wrap(...)
|
|||||||
if !exists('g:fzf_layout') && exists('g:fzf_height')
|
if !exists('g:fzf_layout') && exists('g:fzf_height')
|
||||||
let opts.down = g:fzf_height
|
let opts.down = g:fzf_height
|
||||||
else
|
else
|
||||||
let opts = extend(opts, get(g:, 'fzf_layout', s:default_layout))
|
let opts = extend(opts, s:validate_layout(get(g:, 'fzf_layout', s:default_layout)))
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
" Colors: g:fzf_colors
|
" Colors: g:fzf_colors
|
||||||
let opts.options = s:defaults() .' '. get(opts, 'options', '')
|
let opts.options = s:defaults() .' '. s:evaluate_opts(get(opts, 'options', ''))
|
||||||
|
|
||||||
" History: g:fzf_history_dir
|
" History: g:fzf_history_dir
|
||||||
if len(name) && len(get(g:, 'fzf_history_dir', ''))
|
if len(name) && len(get(g:, 'fzf_history_dir', ''))
|
||||||
let dir = expand(g:fzf_history_dir)
|
let dir = s:fzf_expand(g:fzf_history_dir)
|
||||||
if !isdirectory(dir)
|
if !isdirectory(dir)
|
||||||
call mkdir(dir, 'p')
|
call mkdir(dir, 'p')
|
||||||
endif
|
endif
|
||||||
let opts.options = join(['--history', s:escape(dir.'/'.name), opts.options])
|
let history = fzf#shellescape(dir.'/'.name)
|
||||||
|
let opts.options = join(['--history', history, opts.options])
|
||||||
endif
|
endif
|
||||||
|
|
||||||
" Action: g:fzf_action
|
" Action: g:fzf_action
|
||||||
@@ -252,78 +345,77 @@ function! fzf#wrap(...)
|
|||||||
return opts
|
return opts
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! fzf#shellescape(path)
|
function! s:use_sh()
|
||||||
|
let [shell, shellslash, shellcmdflag, shellxquote] = [&shell, &shellslash, &shellcmdflag, &shellxquote]
|
||||||
if s:is_win
|
if s:is_win
|
||||||
let shellslash = &shellslash
|
set shell=cmd.exe
|
||||||
try
|
|
||||||
set noshellslash
|
set noshellslash
|
||||||
return shellescape(a:path)
|
let &shellcmdflag = has('nvim') ? '/s /c' : '/c'
|
||||||
finally
|
let &shellxquote = has('nvim') ? '"' : '('
|
||||||
let &shellslash = shellslash
|
else
|
||||||
endtry
|
set shell=sh
|
||||||
endif
|
endif
|
||||||
return shellescape(a:path)
|
return [shell, shellslash, shellcmdflag, shellxquote]
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! fzf#run(...) abort
|
function! fzf#run(...) abort
|
||||||
try
|
try
|
||||||
let oshell = &shell
|
let [shell, shellslash, shellcmdflag, shellxquote] = s:use_sh()
|
||||||
let useshellslash = &shellslash
|
|
||||||
|
|
||||||
if s:is_win
|
|
||||||
set shell=cmd.exe
|
|
||||||
set noshellslash
|
|
||||||
else
|
|
||||||
set shell=sh
|
|
||||||
endif
|
|
||||||
|
|
||||||
if has('nvim') && len(filter(range(1, bufnr('$')), 'bufname(v:val) =~# ";#FZF"'))
|
|
||||||
call s:warn('FZF is already running!')
|
|
||||||
return []
|
|
||||||
endif
|
|
||||||
let dict = exists('a:1') ? s:upgrade(a:1) : {}
|
let dict = exists('a:1') ? s:upgrade(a:1) : {}
|
||||||
let temps = { 'result': tempname() }
|
let temps = { 'result': s:fzf_tempname() }
|
||||||
let optstr = get(dict, 'options', '')
|
let optstr = s:evaluate_opts(get(dict, 'options', ''))
|
||||||
try
|
try
|
||||||
let fzf_exec = s:fzf_exec()
|
let fzf_exec = s:fzf_exec()
|
||||||
catch
|
catch
|
||||||
throw v:exception
|
throw v:exception
|
||||||
endtry
|
endtry
|
||||||
|
|
||||||
if !has_key(dict, 'source') && !empty($FZF_DEFAULT_COMMAND)
|
if !has_key(dict, 'dir')
|
||||||
let temps.source = tempname()
|
let dict.dir = s:fzf_getcwd()
|
||||||
call writefile(split($FZF_DEFAULT_COMMAND, "\n"), temps.source)
|
endif
|
||||||
let dict.source = (empty($SHELL) ? &shell : $SHELL) . ' ' . s:shellesc(temps.source)
|
if has('win32unix') && has_key(dict, 'dir')
|
||||||
|
let dict.dir = fnamemodify(dict.dir, ':p')
|
||||||
|
endif
|
||||||
|
|
||||||
|
if !has_key(dict, 'source') && !empty($FZF_DEFAULT_COMMAND) && !s:is_win
|
||||||
|
let temps.source = s:fzf_tempname()
|
||||||
|
call writefile(s:wrap_cmds(split($FZF_DEFAULT_COMMAND, "\n")), temps.source)
|
||||||
|
let dict.source = (empty($SHELL) ? &shell : $SHELL).' '.fzf#shellescape(temps.source)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if has_key(dict, 'source')
|
if has_key(dict, 'source')
|
||||||
let source = dict.source
|
let source = dict.source
|
||||||
let type = type(source)
|
let type = type(source)
|
||||||
if type == 1
|
if type == 1
|
||||||
let prefix = source.'|'
|
let prefix = '( '.source.' )|'
|
||||||
elseif type == 3
|
elseif type == 3
|
||||||
let temps.input = tempname()
|
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 = 'cat '.s:shellesc(temps.input).'|'
|
let prefix = (s:is_win ? 'type ' : 'cat ').fzf#shellescape(temps.input).'|'
|
||||||
else
|
else
|
||||||
throw 'invalid source type'
|
throw 'Invalid source type'
|
||||||
endif
|
endif
|
||||||
else
|
else
|
||||||
let prefix = ''
|
let prefix = ''
|
||||||
endif
|
endif
|
||||||
|
|
||||||
let prefer_tmux = get(g:, 'fzf_prefer_tmux', 0)
|
let prefer_tmux = get(g:, 'fzf_prefer_tmux', 0)
|
||||||
let use_height = has_key(dict, 'down') &&
|
let use_height = has_key(dict, 'down') && !has('gui_running') &&
|
||||||
\ !(has('nvim') || s:is_win || s:present(dict, 'up', 'left', 'right')) &&
|
\ !(has('nvim') || s:is_win || has('win32unix') || s:present(dict, 'up', 'left', 'right', 'window')) &&
|
||||||
\ executable('tput') && filereadable('/dev/tty')
|
\ executable('tput') && filereadable('/dev/tty')
|
||||||
let use_term = has('nvim')
|
let has_vim8_term = has('terminal') && has('patch-8.0.995')
|
||||||
let use_tmux = (!use_height && !use_term || prefer_tmux) && s:tmux_enabled() && s:splittable(dict)
|
let has_nvim_term = has('nvim-0.2.1') || has('nvim') && !s:is_win
|
||||||
|
let use_term = has_nvim_term ||
|
||||||
|
\ 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
|
if prefer_tmux && use_tmux
|
||||||
let use_height = 0
|
let use_height = 0
|
||||||
let use_term = 0
|
let use_term = 0
|
||||||
endif
|
endif
|
||||||
if use_height
|
if use_height
|
||||||
let optstr .= ' --height='.s:calc_size(&lines, dict.down, dict)
|
let height = s:calc_size(&lines, dict.down, dict)
|
||||||
|
let optstr .= ' --height='.height
|
||||||
elseif use_term
|
elseif use_term
|
||||||
let optstr .= ' --no-height'
|
let optstr .= ' --no-height'
|
||||||
endif
|
endif
|
||||||
@@ -338,8 +430,7 @@ try
|
|||||||
call s:callback(dict, lines)
|
call s:callback(dict, lines)
|
||||||
return lines
|
return lines
|
||||||
finally
|
finally
|
||||||
let &shell = oshell
|
let [&shell, &shellslash, &shellcmdflag, &shellxquote] = [shell, shellslash, shellcmdflag, shellxquote]
|
||||||
let &shellslash = useshellslash
|
|
||||||
endtry
|
endtry
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
@@ -367,7 +458,7 @@ function! s:fzf_tmux(dict)
|
|||||||
endif
|
endif
|
||||||
endfor
|
endfor
|
||||||
return printf('LINES=%d COLUMNS=%d %s %s %s --',
|
return printf('LINES=%d COLUMNS=%d %s %s %s --',
|
||||||
\ &lines, &columns, s:shellesc(s:fzf_tmux), size, (has_key(a:dict, 'source') ? '' : '-'))
|
\ &lines, &columns, fzf#shellescape(s:fzf_tmux), size, (has_key(a:dict, 'source') ? '' : '-'))
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:splittable(dict)
|
function! s:splittable(dict)
|
||||||
@@ -377,16 +468,19 @@ endfunction
|
|||||||
|
|
||||||
function! s:pushd(dict)
|
function! s:pushd(dict)
|
||||||
if s:present(a:dict, 'dir')
|
if s:present(a:dict, 'dir')
|
||||||
let cwd = getcwd()
|
let cwd = s:fzf_getcwd()
|
||||||
if get(a:dict, 'prev_dir', '') ==# cwd
|
let w:fzf_pushd = {
|
||||||
return 1
|
\ 'command': haslocaldir() ? 'lcd' : (exists(':tcd') && haslocaldir(-1) ? 'tcd' : 'cd'),
|
||||||
endif
|
\ 'origin': cwd,
|
||||||
let a:dict.prev_dir = cwd
|
\ 'bufname': bufname('')
|
||||||
|
\ }
|
||||||
execute 'lcd' s:escape(a:dict.dir)
|
execute 'lcd' s:escape(a:dict.dir)
|
||||||
let a:dict.dir = getcwd()
|
let cwd = s:fzf_getcwd()
|
||||||
return 1
|
let w:fzf_pushd.dir = cwd
|
||||||
|
let a:dict.pushd = w:fzf_pushd
|
||||||
|
return cwd
|
||||||
endif
|
endif
|
||||||
return 0
|
return ''
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
augroup fzf_popd
|
augroup fzf_popd
|
||||||
@@ -395,24 +489,42 @@ augroup fzf_popd
|
|||||||
augroup END
|
augroup END
|
||||||
|
|
||||||
function! s:dopopd()
|
function! s:dopopd()
|
||||||
if !exists('w:fzf_prev_dir') || exists('*haslocaldir') && !haslocaldir()
|
if !exists('w:fzf_pushd')
|
||||||
return
|
return
|
||||||
endif
|
endif
|
||||||
execute 'lcd' s:escape(w:fzf_prev_dir)
|
|
||||||
unlet w:fzf_prev_dir
|
" FIXME: We temporarily change the working directory to 'dir' entry
|
||||||
|
" of options dictionary (set to the current working directory if not given)
|
||||||
|
" before running fzf.
|
||||||
|
"
|
||||||
|
" e.g. call fzf#run({'dir': '/tmp', 'source': 'ls', 'sink': 'e'})
|
||||||
|
"
|
||||||
|
" After processing the sink function, we have to restore the current working
|
||||||
|
" directory. But doing so may not be desirable if the function changed the
|
||||||
|
" working directory on purpose.
|
||||||
|
"
|
||||||
|
" So how can we tell if we should do it or not? A simple heuristic we use
|
||||||
|
" here is that we change directory only if the current working directory
|
||||||
|
" matches 'dir' entry. However, it is possible that the sink function did
|
||||||
|
" change the directory to 'dir'. In that case, the user will have an
|
||||||
|
" unexpected result.
|
||||||
|
if s:fzf_getcwd() ==# w:fzf_pushd.dir && (!&autochdir || w:fzf_pushd.bufname ==# bufname(''))
|
||||||
|
execute w:fzf_pushd.command s:escape(w:fzf_pushd.origin)
|
||||||
|
endif
|
||||||
|
unlet w:fzf_pushd
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:xterm_launcher()
|
function! s:xterm_launcher()
|
||||||
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')
|
if has('gui_macvim')
|
||||||
let fmt .= '&& osascript -e "tell application \"MacVim\" to activate"'
|
let fmt .= '&& osascript -e "tell application \"MacVim\" to activate"'
|
||||||
endif
|
endif
|
||||||
return printf(fmt,
|
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())
|
\ &columns, &lines/2, getwinposx(), getwinposy())
|
||||||
endfunction
|
endfunction
|
||||||
unlet! s:launcher
|
unlet! s:launcher
|
||||||
if s:is_win
|
if s:is_win || has('win32unix')
|
||||||
let s:launcher = '%s'
|
let s:launcher = '%s'
|
||||||
else
|
else
|
||||||
let s:launcher = function('s:xterm_launcher')
|
let s:launcher = function('s:xterm_launcher')
|
||||||
@@ -421,6 +533,10 @@ endif
|
|||||||
function! s:exit_handler(code, command, ...)
|
function! s:exit_handler(code, command, ...)
|
||||||
if a:code == 130
|
if a:code == 130
|
||||||
return 0
|
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
|
elseif a:code > 1
|
||||||
call s:error('Error running ' . a:command)
|
call s:error('Error running ' . a:command)
|
||||||
if !empty(a:000)
|
if !empty(a:000)
|
||||||
@@ -436,7 +552,7 @@ function! s:execute(dict, command, use_height, temps) abort
|
|||||||
if has('unix') && !a:use_height
|
if has('unix') && !a:use_height
|
||||||
silent! !clear 2> /dev/null
|
silent! !clear 2> /dev/null
|
||||||
endif
|
endif
|
||||||
let escaped = escape(substitute(a:command, '\n', '\\n', 'g'), '%#')
|
let escaped = (a:use_height || s:is_win) ? a:command : escape(substitute(a:command, '\n', '\\n', 'g'), '%#!')
|
||||||
if has('gui_running')
|
if has('gui_running')
|
||||||
let Launcher = get(a:dict, 'launcher', get(g:, 'Fzf_launcher', get(g:, 'fzf_launcher', s:launcher)))
|
let Launcher = get(a:dict, 'launcher', get(g:, 'Fzf_launcher', get(g:, 'fzf_launcher', s:launcher)))
|
||||||
let fmt = type(Launcher) == 2 ? call(Launcher, []) : Launcher
|
let fmt = type(Launcher) == 2 ? call(Launcher, []) : Launcher
|
||||||
@@ -447,6 +563,30 @@ function! s:execute(dict, command, use_height, temps) abort
|
|||||||
else
|
else
|
||||||
let command = escaped
|
let command = escaped
|
||||||
endif
|
endif
|
||||||
|
if s:is_win
|
||||||
|
let batchfile = s:fzf_tempname().'.bat'
|
||||||
|
call writefile(s:wrap_cmds(command), batchfile)
|
||||||
|
let command = batchfile
|
||||||
|
let a:temps.batchfile = batchfile
|
||||||
|
if has('nvim')
|
||||||
|
let fzf = {}
|
||||||
|
let fzf.dict = a:dict
|
||||||
|
let fzf.temps = a:temps
|
||||||
|
function! fzf.on_exit(job_id, exit_status, event) dict
|
||||||
|
call s:pushd(self.dict)
|
||||||
|
let lines = s:collect(self.temps)
|
||||||
|
call s:callback(self.dict, lines)
|
||||||
|
endfunction
|
||||||
|
let cmd = 'start /wait cmd /c '.command
|
||||||
|
call jobstart(cmd, fzf)
|
||||||
|
return []
|
||||||
|
endif
|
||||||
|
elseif has('win32unix') && $TERM !=# 'cygwin'
|
||||||
|
let shellscript = s:fzf_tempname()
|
||||||
|
call writefile([command], shellscript)
|
||||||
|
let command = 'cmd.exe /C '.fzf#shellescape('set "TERM=" & start /WAIT sh -c '.shellscript)
|
||||||
|
let a:temps.shellscript = shellscript
|
||||||
|
endif
|
||||||
if a:use_height
|
if a:use_height
|
||||||
let stdin = has_key(a:dict, 'source') ? '' : '< /dev/tty'
|
let stdin = has_key(a:dict, 'source') ? '' : '< /dev/tty'
|
||||||
call system(printf('tput cup %d > /dev/tty; tput cnorm > /dev/tty; %s %s 2> /dev/tty', &lines, command, stdin))
|
call system(printf('tput cup %d > /dev/tty; tput cnorm > /dev/tty; %s %s 2> /dev/tty', &lines, command, stdin))
|
||||||
@@ -460,9 +600,10 @@ endfunction
|
|||||||
|
|
||||||
function! s:execute_tmux(dict, command, temps) abort
|
function! s:execute_tmux(dict, command, temps) abort
|
||||||
let command = a:command
|
let command = a:command
|
||||||
if s:pushd(a:dict)
|
let cwd = s:pushd(a:dict)
|
||||||
|
if len(cwd)
|
||||||
" -c '#{pane_current_path}' is only available on tmux 1.9 or above
|
" -c '#{pane_current_path}' is only available on tmux 1.9 or above
|
||||||
let command = 'cd '.s:escape(a:dict.dir).' && '.command
|
let command = join(['cd', fzf#shellescape(cwd), '&&', command])
|
||||||
endif
|
endif
|
||||||
|
|
||||||
call system(command)
|
call system(command)
|
||||||
@@ -484,8 +625,9 @@ function! s:calc_size(max, val, dict)
|
|||||||
let srcsz = len(a:dict.source)
|
let srcsz = len(a:dict.source)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
let opts = get(a:dict, 'options', '').$FZF_DEFAULT_OPTS
|
let opts = $FZF_DEFAULT_OPTS.' '.s:evaluate_opts(get(a:dict, 'options', ''))
|
||||||
let margin = stridx(opts, '--inline-info') > stridx(opts, '--no-inline-info') ? 1 : 2
|
let margin = stridx(opts, '--inline-info') > stridx(opts, '--no-inline-info') ? 1 : 2
|
||||||
|
let margin += stridx(opts, '--border') > stridx(opts, '--no-border') ? 2 : 0
|
||||||
let margin += stridx(opts, '--header') > stridx(opts, '--no-header')
|
let margin += stridx(opts, '--header') > stridx(opts, '--no-header')
|
||||||
return srcsz >= 0 ? min([srcsz + margin, size]) : size
|
return srcsz >= 0 ? min([srcsz + margin, size]) : size
|
||||||
endfunction
|
endfunction
|
||||||
@@ -532,6 +674,7 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
let winrest = winrestcmd()
|
let winrest = winrestcmd()
|
||||||
let pbuf = bufnr('')
|
let pbuf = bufnr('')
|
||||||
let [ppos, winopts] = s:split(a:dict)
|
let [ppos, winopts] = s:split(a:dict)
|
||||||
|
call s:use_sh()
|
||||||
let b:fzf = a:dict
|
let b:fzf = a:dict
|
||||||
let fzf = { 'buf': bufnr(''), 'pbuf': pbuf, 'ppos': ppos, 'dict': a:dict, 'temps': a:temps,
|
let fzf = { 'buf': bufnr(''), 'pbuf': pbuf, 'ppos': ppos, 'dict': a:dict, 'temps': a:temps,
|
||||||
\ 'winopts': winopts, 'winrest': winrest, 'lines': &lines,
|
\ 'winopts': winopts, 'winrest': winrest, 'lines': &lines,
|
||||||
@@ -547,7 +690,7 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
endfunction
|
endfunction
|
||||||
function! fzf.on_exit(id, code, _event)
|
function! fzf.on_exit(id, code, ...)
|
||||||
if s:getpos() == self.ppos " {'window': 'enew'}
|
if s:getpos() == self.ppos " {'window': 'enew'}
|
||||||
for [opt, val] in items(self.winopts)
|
for [opt, val] in items(self.winopts)
|
||||||
execute 'let' opt '=' val
|
execute 'let' opt '=' val
|
||||||
@@ -582,16 +725,27 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
try
|
try
|
||||||
if s:present(a:dict, 'dir')
|
call s:pushd(a:dict)
|
||||||
execute 'lcd' s:escape(a:dict.dir)
|
if s:is_win
|
||||||
|
let fzf.temps.batchfile = s:fzf_tempname().'.bat'
|
||||||
|
call writefile(s:wrap_cmds(a:command), fzf.temps.batchfile)
|
||||||
|
let command = fzf.temps.batchfile
|
||||||
|
else
|
||||||
|
let command = a:command
|
||||||
|
endif
|
||||||
|
let command .= s:term_marker
|
||||||
|
if has('nvim')
|
||||||
|
call termopen(command, fzf)
|
||||||
|
else
|
||||||
|
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
|
endif
|
||||||
call termopen(a:command . ';#FZF', fzf)
|
|
||||||
finally
|
finally
|
||||||
if s:present(a:dict, 'dir')
|
call s:dopopd()
|
||||||
lcd -
|
|
||||||
endif
|
|
||||||
endtry
|
endtry
|
||||||
setlocal nospell bufhidden=wipe nobuflisted
|
setlocal nospell bufhidden=wipe nobuflisted nonumber
|
||||||
setf fzf
|
setf fzf
|
||||||
startinsert
|
startinsert
|
||||||
return []
|
return []
|
||||||
@@ -608,21 +762,9 @@ function! s:collect(temps) abort
|
|||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:callback(dict, lines) abort
|
function! s:callback(dict, lines) abort
|
||||||
" Since anything can be done in the sink function, there is no telling that
|
let popd = has_key(a:dict, 'pushd')
|
||||||
" the change of the working directory was made by &autochdir setting.
|
|
||||||
"
|
|
||||||
" We use the following heuristic to determine whether to restore CWD:
|
|
||||||
" - Always restore the current directory when &autochdir is disabled.
|
|
||||||
" FIXME This makes it impossible to change directory from inside the sink
|
|
||||||
" function when &autochdir is not used.
|
|
||||||
" - In case of an error or an interrupt, a:lines will be empty.
|
|
||||||
" And it will be an array of a single empty string when fzf was finished
|
|
||||||
" without a match. In these cases, we presume that the change of the
|
|
||||||
" directory is not expected and should be undone.
|
|
||||||
let popd = has_key(a:dict, 'prev_dir') &&
|
|
||||||
\ (!&autochdir || (empty(a:lines) || len(a:lines) == 1 && empty(a:lines[0])))
|
|
||||||
if popd
|
if popd
|
||||||
let w:fzf_prev_dir = a:dict.prev_dir
|
let w:fzf_pushd = a:dict.pushd
|
||||||
endif
|
endif
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -646,7 +788,7 @@ function! s:callback(dict, lines) abort
|
|||||||
|
|
||||||
" We may have opened a new window or tab
|
" We may have opened a new window or tab
|
||||||
if popd
|
if popd
|
||||||
let w:fzf_prev_dir = a:dict.prev_dir
|
let w:fzf_pushd = a:dict.pushd
|
||||||
call s:dopopd()
|
call s:dopopd()
|
||||||
endif
|
endif
|
||||||
endfunction
|
endfunction
|
||||||
@@ -657,20 +799,29 @@ let s:default_action = {
|
|||||||
\ 'ctrl-v': 'vsplit' }
|
\ 'ctrl-v': 'vsplit' }
|
||||||
|
|
||||||
function! s:shortpath()
|
function! s:shortpath()
|
||||||
let short = pathshorten(fnamemodify(getcwd(), ':~:.'))
|
let short = fnamemodify(getcwd(), ':~:.')
|
||||||
return empty(short) ? '~/' : short . (short =~ '/$' ? '' : '/')
|
if !has('win32unix')
|
||||||
|
let short = pathshorten(short)
|
||||||
|
endif
|
||||||
|
let slash = (s:is_win && !&shellslash) ? '\' : '/'
|
||||||
|
return empty(short) ? '~'.slash : short . (short =~ escape(slash, '\').'$' ? '' : slash)
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:cmd(bang, ...) abort
|
function! s:cmd(bang, ...) abort
|
||||||
let args = copy(a:000)
|
let args = copy(a:000)
|
||||||
let opts = { 'options': '--multi ' }
|
let opts = { 'options': ['--multi'] }
|
||||||
if len(args) && isdirectory(expand(args[-1]))
|
if len(args) && isdirectory(expand(args[-1]))
|
||||||
let opts.dir = substitute(substitute(remove(args, -1), '\\\(["'']\)', '\1', 'g'), '[/\\]*$', '/', '')
|
let opts.dir = substitute(substitute(remove(args, -1), '\\\(["'']\)', '\1', 'g'), '[/\\]*$', '/', '')
|
||||||
let opts.options .= ' --prompt '.fzf#shellescape(opts.dir)
|
if s:is_win && !&shellslash
|
||||||
else
|
let opts.dir = substitute(opts.dir, '/', '\\', 'g')
|
||||||
let opts.options .= ' --prompt '.fzf#shellescape(s:shortpath())
|
|
||||||
endif
|
endif
|
||||||
let opts.options .= ' '.join(args)
|
let prompt = opts.dir
|
||||||
|
else
|
||||||
|
let prompt = s:shortpath()
|
||||||
|
endif
|
||||||
|
let prompt = strwidth(prompt) < &columns - 20 ? prompt : '> '
|
||||||
|
call extend(opts.options, ['--prompt', prompt])
|
||||||
|
call extend(opts.options, args)
|
||||||
call fzf#run(fzf#wrap('FZF', opts, a:bang))
|
call fzf#run(fzf#wrap('FZF', opts, a:bang))
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#!/bin/bash
|
|
||||||
# ____ ____
|
# ____ ____
|
||||||
# / __/___ / __/
|
# / __/___ / __/
|
||||||
# / /_/_ / / /_
|
# / /_/_ / / /_
|
||||||
@@ -10,12 +9,14 @@
|
|||||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||||
|
|
||||||
|
if [[ $- =~ i ]]; then
|
||||||
|
|
||||||
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
||||||
if ! declare -f _fzf_compgen_path > /dev/null; then
|
if ! declare -f _fzf_compgen_path > /dev/null; then
|
||||||
_fzf_compgen_path() {
|
_fzf_compgen_path() {
|
||||||
echo "$1"
|
echo "$1"
|
||||||
command find -L "$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@^\./@@'
|
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||||
}
|
}
|
||||||
fi
|
fi
|
||||||
@@ -23,7 +24,7 @@ fi
|
|||||||
if ! declare -f _fzf_compgen_dir > /dev/null; then
|
if ! declare -f _fzf_compgen_dir > /dev/null; then
|
||||||
_fzf_compgen_dir() {
|
_fzf_compgen_dir() {
|
||||||
command find -L "$1" \
|
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@^\./@@'
|
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||||
}
|
}
|
||||||
fi
|
fi
|
||||||
@@ -38,9 +39,9 @@ __fzfcmd_complete() {
|
|||||||
echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
|
echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_orig_completion_filter() {
|
__fzf_orig_completion_filter() {
|
||||||
sed 's/^\(.*-F\) *\([^ ]*\).* \([^ ]*\)$/export _fzf_orig_completion_\3="\1 %s \3 #\2";/' |
|
sed 's/^\(.*-F\) *\([^ ]*\).* \([^ ]*\)$/export _fzf_orig_completion_\3="\1 %s \3 #\2"; [[ "\1" = *" -o nospace "* ]] \&\& [[ ! "$__fzf_nospace_commands" = *" \3 "* ]] \&\& __fzf_nospace_commands="$__fzf_nospace_commands \3 ";/' |
|
||||||
awk -F= '{gsub(/[^A-Za-z0-9_= ;]/, "_", $1); print $1"="$2}'
|
awk -F= '{OFS = FS} {gsub(/[^A-Za-z0-9_= ;]/, "_", $1);}1'
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_opts_completion() {
|
_fzf_opts_completion() {
|
||||||
@@ -113,7 +114,7 @@ _fzf_opts_completion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_fzf_handle_dynamic_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"
|
cmd="$1"
|
||||||
shift
|
shift
|
||||||
orig_cmd="$1"
|
orig_cmd="$1"
|
||||||
@@ -122,10 +123,18 @@ _fzf_handle_dynamic_completion() {
|
|||||||
if [ -n "$orig" ] && type "$orig" > /dev/null 2>&1; then
|
if [ -n "$orig" ] && type "$orig" > /dev/null 2>&1; then
|
||||||
$orig "$@"
|
$orig "$@"
|
||||||
elif [ -n "$_fzf_completion_loader" ]; then
|
elif [ -n "$_fzf_completion_loader" ]; then
|
||||||
|
orig_complete=$(complete -p "$orig_cmd" 2> /dev/null)
|
||||||
_completion_loader "$@"
|
_completion_loader "$@"
|
||||||
ret=$?
|
ret=$?
|
||||||
eval "$(complete | command grep "\-F.* $orig_cmd$" | _fzf_orig_completion_filter)"
|
# _completion_loader may not have updated completion for the command
|
||||||
source "${BASH_SOURCE[0]}"
|
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
|
return $ret
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -141,7 +150,7 @@ __fzf_generic_path_completion() {
|
|||||||
base=${cur:0:${#cur}-${#trigger}}
|
base=${cur:0:${#cur}-${#trigger}}
|
||||||
eval "base=$base"
|
eval "base=$base"
|
||||||
|
|
||||||
dir="$base"
|
[[ $base = *"/"* ]] && dir="$base"
|
||||||
while true; do
|
while true; do
|
||||||
if [ -z "$dir" ] || [ -d "$dir" ]; then
|
if [ -z "$dir" ] || [ -d "$dir" ]; then
|
||||||
leftover=${base/#"$dir"}
|
leftover=${base/#"$dir"}
|
||||||
@@ -152,6 +161,7 @@ __fzf_generic_path_completion() {
|
|||||||
printf "%q$3 " "$item"
|
printf "%q$3 " "$item"
|
||||||
done)
|
done)
|
||||||
matches=${matches% }
|
matches=${matches% }
|
||||||
|
[[ -z "$3" ]] && [[ "$__fzf_nospace_commands" = *" ${COMP_WORDS[0]} "* ]] && matches="$matches "
|
||||||
if [ -n "$matches" ]; then
|
if [ -n "$matches" ]; then
|
||||||
COMPREPLY=( "$matches" )
|
COMPREPLY=( "$matches" )
|
||||||
else
|
else
|
||||||
@@ -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=$(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"
|
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
|
||||||
printf '\e[5n'
|
|
||||||
|
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
COMPREPLY=("$selected")
|
COMPREPLY=("$selected")
|
||||||
return 0
|
else
|
||||||
|
COMPREPLY=("$cur")
|
||||||
fi
|
fi
|
||||||
|
printf '\e[5n'
|
||||||
|
return 0
|
||||||
else
|
else
|
||||||
shift
|
shift
|
||||||
_fzf_handle_dynamic_completion "$cmd" "$@"
|
_fzf_handle_dynamic_completion "$cmd" "$@"
|
||||||
@@ -215,7 +226,7 @@ _fzf_complete_kill() {
|
|||||||
|
|
||||||
local selected fzf
|
local selected fzf
|
||||||
fzf="$(__fzfcmd_complete)"
|
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'
|
printf '\e[5n'
|
||||||
|
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
@@ -233,8 +244,8 @@ _fzf_complete_telnet() {
|
|||||||
|
|
||||||
_fzf_complete_ssh() {
|
_fzf_complete_ssh() {
|
||||||
_fzf_complete '+m' "$@" < <(
|
_fzf_complete '+m' "$@" < <(
|
||||||
cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host' | command grep -v '*') \
|
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' | awk '{ print $1 " " $1 }') \
|
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
||||||
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
||||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||||
)
|
)
|
||||||
@@ -274,15 +285,15 @@ a_cmds="
|
|||||||
x_cmds="kill ssh telnet unset unalias export"
|
x_cmds="kill ssh telnet unset unalias export"
|
||||||
|
|
||||||
# Preserve existing completion
|
# Preserve existing completion
|
||||||
eval $(complete |
|
eval "$(complete |
|
||||||
sed -E '/-F/!d; / _fzf/d; '"/ ($(echo $d_cmds $a_cmds $x_cmds | sed 's/ /|/g; s/+/\\+/g'))$/"'!d' |
|
sed -E '/-F/!d; / _fzf/d; '"/ ($(echo $d_cmds $a_cmds $x_cmds | sed 's/ /|/g; s/+/\\+/g'))$/"'!d' |
|
||||||
_fzf_orig_completion_filter)
|
__fzf_orig_completion_filter)"
|
||||||
|
|
||||||
if type _completion_loader > /dev/null 2>&1; then
|
if type _completion_loader > /dev/null 2>&1; then
|
||||||
_fzf_completion_loader=1
|
_fzf_completion_loader=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
_fzf_defc() {
|
__fzf_defc() {
|
||||||
local cmd func opts orig_var orig def
|
local cmd func opts orig_var orig def
|
||||||
cmd="$1"
|
cmd="$1"
|
||||||
func="$2"
|
func="$2"
|
||||||
@@ -299,16 +310,14 @@ _fzf_defc() {
|
|||||||
|
|
||||||
# Anything
|
# Anything
|
||||||
for cmd in $a_cmds; do
|
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
|
done
|
||||||
|
|
||||||
# Directory
|
# Directory
|
||||||
for cmd in $d_cmds; do
|
for cmd in $d_cmds; do
|
||||||
_fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o plusdirs"
|
__fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames"
|
||||||
done
|
done
|
||||||
|
|
||||||
unset _fzf_defc
|
|
||||||
|
|
||||||
# Kill completion
|
# Kill completion
|
||||||
complete -F _fzf_complete_kill -o nospace -o default -o bashdefault kill
|
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
|
complete -F _fzf_complete_unalias -o default -o bashdefault unalias
|
||||||
|
|
||||||
unset cmd d_cmds a_cmds x_cmds
|
unset cmd d_cmds a_cmds x_cmds
|
||||||
|
|
||||||
|
_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_TRIGGER (default: '**')
|
||||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||||
|
|
||||||
|
if [[ $- =~ i ]]; then
|
||||||
|
|
||||||
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
||||||
if ! declare -f _fzf_compgen_path > /dev/null; then
|
if ! declare -f _fzf_compgen_path > /dev/null; then
|
||||||
_fzf_compgen_path() {
|
_fzf_compgen_path() {
|
||||||
echo "$1"
|
echo "$1"
|
||||||
command find -L "$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@^\./@@'
|
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||||
}
|
}
|
||||||
fi
|
fi
|
||||||
@@ -23,7 +24,7 @@ fi
|
|||||||
if ! declare -f _fzf_compgen_dir > /dev/null; then
|
if ! declare -f _fzf_compgen_dir > /dev/null; then
|
||||||
_fzf_compgen_dir() {
|
_fzf_compgen_dir() {
|
||||||
command find -L "$1" \
|
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@^\./@@'
|
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||||
}
|
}
|
||||||
fi
|
fi
|
||||||
@@ -37,8 +38,7 @@ __fzfcmd_complete() {
|
|||||||
|
|
||||||
__fzf_generic_path_completion() {
|
__fzf_generic_path_completion() {
|
||||||
local base lbuf compgen fzf_opts suffix tail fzf dir leftover matches
|
local base lbuf compgen fzf_opts suffix tail fzf dir leftover matches
|
||||||
# (Q) flag removes a quoting level: "foo\ bar" => "foo bar"
|
base=$1
|
||||||
base=${(Q)1}
|
|
||||||
lbuf=$2
|
lbuf=$2
|
||||||
compgen=$3
|
compgen=$3
|
||||||
fzf_opts=$4
|
fzf_opts=$4
|
||||||
@@ -47,14 +47,14 @@ __fzf_generic_path_completion() {
|
|||||||
fzf="$(__fzfcmd_complete)"
|
fzf="$(__fzfcmd_complete)"
|
||||||
|
|
||||||
setopt localoptions nonomatch
|
setopt localoptions nonomatch
|
||||||
dir="$base"
|
eval "base=$base"
|
||||||
|
[[ $base = *"/"* ]] && dir="$base"
|
||||||
while [ 1 ]; do
|
while [ 1 ]; do
|
||||||
if [[ -z "$dir" || -d ${~dir} ]]; then
|
if [[ -z "$dir" || -d ${dir} ]]; then
|
||||||
leftover=${base/#"$dir"}
|
leftover=${base/#"$dir"}
|
||||||
leftover=${leftover/#\/}
|
leftover=${leftover/#\/}
|
||||||
[ -z "$dir" ] && dir='.'
|
[ -z "$dir" ] && dir='.'
|
||||||
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
||||||
dir=${~dir}
|
|
||||||
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" ${=fzf} ${=fzf_opts} -q "$leftover" | while read item; do
|
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" ${=fzf} ${=fzf_opts} -q "$leftover" | while read item; do
|
||||||
echo -n "${(q)item}$suffix "
|
echo -n "${(q)item}$suffix "
|
||||||
done)
|
done)
|
||||||
@@ -62,8 +62,7 @@ __fzf_generic_path_completion() {
|
|||||||
if [ -n "$matches" ]; then
|
if [ -n "$matches" ]; then
|
||||||
LBUFFER="$lbuf$matches$tail"
|
LBUFFER="$lbuf$matches$tail"
|
||||||
fi
|
fi
|
||||||
zle redisplay
|
zle reset-prompt
|
||||||
typeset -f zle-line-init >/dev/null && zle zle-line-init
|
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
dir=$(dirname "$dir")
|
dir=$(dirname "$dir")
|
||||||
@@ -102,8 +101,7 @@ _fzf_complete() {
|
|||||||
if [ -n "$matches" ]; then
|
if [ -n "$matches" ]; then
|
||||||
LBUFFER="$lbuf$matches"
|
LBUFFER="$lbuf$matches"
|
||||||
fi
|
fi
|
||||||
zle redisplay
|
zle reset-prompt
|
||||||
typeset -f zle-line-init >/dev/null && zle zle-line-init
|
|
||||||
command rm -f "$fifo"
|
command rm -f "$fifo"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,8 +114,9 @@ _fzf_complete_telnet() {
|
|||||||
|
|
||||||
_fzf_complete_ssh() {
|
_fzf_complete_ssh() {
|
||||||
_fzf_complete '+m' "$@" < <(
|
_fzf_complete '+m' "$@" < <(
|
||||||
command cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host' | command grep -v '*') \
|
setopt localoptions nonomatch
|
||||||
<(command grep -oE '^[a-z0-9.,-]+' ~/.ssh/known_hosts | tr ',' '\n' | awk '{ print $1 " " $1 }') \
|
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') |
|
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
||||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||||
)
|
)
|
||||||
@@ -143,7 +142,7 @@ _fzf_complete_unalias() {
|
|||||||
|
|
||||||
fzf-completion() {
|
fzf-completion() {
|
||||||
local tokens cmd prefix trigger tail fzf matches lbuf d_cmds
|
local tokens cmd prefix trigger tail fzf matches lbuf d_cmds
|
||||||
setopt localoptions noshwordsplit noksh_arrays
|
setopt localoptions noshwordsplit noksh_arrays noposixbuiltins
|
||||||
|
|
||||||
# http://zsh.sourceforge.net/FAQ/zshfaq03.html
|
# http://zsh.sourceforge.net/FAQ/zshfaq03.html
|
||||||
# http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion-Flags
|
# http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion-Flags
|
||||||
@@ -159,16 +158,21 @@ fzf-completion() {
|
|||||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||||
[ -z "$trigger" -a ${LBUFFER[-1]} = ' ' ] && tokens+=("")
|
[ -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} ))}
|
tail=${LBUFFER:$(( ${#LBUFFER} - ${#trigger} ))}
|
||||||
# Kill completion (do not require trigger sequence)
|
# Kill completion (do not require trigger sequence)
|
||||||
if [ $cmd = kill -a ${LBUFFER[-1]} = ' ' ]; then
|
if [ $cmd = kill -a ${LBUFFER[-1]} = ' ' ]; then
|
||||||
fzf="$(__fzfcmd_complete)"
|
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
|
if [ -n "$matches" ]; then
|
||||||
LBUFFER="$LBUFFER$matches"
|
LBUFFER="$LBUFFER$matches"
|
||||||
fi
|
fi
|
||||||
zle redisplay
|
zle reset-prompt
|
||||||
typeset -f zle-line-init >/dev/null && zle zle-line-init
|
|
||||||
# Trigger sequence given
|
# Trigger sequence given
|
||||||
elif [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
|
elif [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
|
||||||
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})
|
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})
|
||||||
@@ -197,3 +201,5 @@ fzf-completion() {
|
|||||||
|
|
||||||
zle -N fzf-completion
|
zle -N fzf-completion
|
||||||
bindkey '^I' fzf-completion
|
bindkey '^I' fzf-completion
|
||||||
|
|
||||||
|
fi
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Key bindings
|
# Key bindings
|
||||||
# ------------
|
# ------------
|
||||||
__fzf_select__() {
|
__fzf_select__() {
|
||||||
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
local cmd="${FZF_CTRL_T_COMMAND:-"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 f -print \
|
||||||
-o -type d -print \
|
-o -type d -print \
|
||||||
-o -type l -print 2> /dev/null | cut -b3-"}"
|
-o -type l -print 2> /dev/null | cut -b3-"}"
|
||||||
@@ -46,8 +46,8 @@ fzf-file-widget() {
|
|||||||
|
|
||||||
__fzf_cd__() {
|
__fzf_cd__() {
|
||||||
local cmd dir
|
local cmd dir
|
||||||
cmd="${FZF_ALT_C_COMMAND:-"command find -L . \\( -path '*/\\.*' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
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 | sed 1d | cut -b3-"}"
|
-o -type d -print 2> /dev/null | cut -b3-"}"
|
||||||
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m) && printf 'cd %q' "$dir"
|
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m) && printf 'cd %q' "$dir"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,8 +55,8 @@ __fzf_history__() (
|
|||||||
local line
|
local line
|
||||||
shopt -u nocaseglob nocasematch
|
shopt -u nocaseglob nocasematch
|
||||||
line=$(
|
line=$(
|
||||||
HISTTIMEFORMAT= history |
|
HISTTIMEFORMAT= builtin history |
|
||||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS +s --tac -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m" $(__fzfcmd) |
|
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS --tac --sync -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m" $(__fzfcmd) |
|
||||||
command grep '^ *[0-9]') &&
|
command grep '^ *[0-9]') &&
|
||||||
if [[ $- =~ H ]]; then
|
if [[ $- =~ H ]]; then
|
||||||
sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line"
|
sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line"
|
||||||
@@ -80,7 +80,7 @@ if [[ ! -o vi ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# CTRL-R - Paste the selected command from history into the command line
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
bind '"\C-r": " \C-e\C-u`__fzf_history__`\e\C-e\e^\er"'
|
bind '"\C-r": " \C-e\C-u\C-y\ey\C-u`__fzf_history__`\e\C-e\er\e^"'
|
||||||
|
|
||||||
# ALT-C - cd into the selected directory
|
# ALT-C - cd into the selected directory
|
||||||
bind '"\ec": " \C-e\C-u`__fzf_cd__`\e\C-e\er\C-m"'
|
bind '"\ec": " \C-e\C-u`__fzf_cd__`\e\C-e\er\C-m"'
|
||||||
@@ -110,7 +110,7 @@ else
|
|||||||
bind -m vi-command '"\C-t": "i\C-t"'
|
bind -m vi-command '"\C-t": "i\C-t"'
|
||||||
|
|
||||||
# CTRL-R - Paste the selected command from history into the command line
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
bind '"\C-r": "\C-x\C-addi`__fzf_history__`\C-x\C-e\C-x^\C-x\C-a$a\C-x\C-r"'
|
bind '"\C-r": "\C-x\C-addi`__fzf_history__`\C-x\C-e\C-x\C-r\C-x^\C-x\C-a$a"'
|
||||||
bind -m vi-command '"\C-r": "i\C-r"'
|
bind -m vi-command '"\C-r": "i\C-r"'
|
||||||
|
|
||||||
# ALT-C - cd into the selected directory
|
# ALT-C - cd into the selected directory
|
||||||
|
|||||||
@@ -2,36 +2,29 @@
|
|||||||
# ------------
|
# ------------
|
||||||
function fzf_key_bindings
|
function fzf_key_bindings
|
||||||
|
|
||||||
# Store last token in $dir as root for the 'find' command
|
# Store current token in $dir as root for the 'find' command
|
||||||
function fzf-file-widget -d "List files and folders"
|
function fzf-file-widget -d "List files and folders"
|
||||||
set -l dir (commandline -t)
|
set -l commandline (__fzf_parse_commandline)
|
||||||
# The commandline token might be escaped, we need to unescape it.
|
set -l dir $commandline[1]
|
||||||
set dir (eval "printf '%s' $dir")
|
set -l fzf_query $commandline[2]
|
||||||
if [ ! -d "$dir" ]
|
|
||||||
set dir .
|
|
||||||
end
|
|
||||||
# Some 'find' versions print undesired duplicated slashes if the path ends with slashes.
|
|
||||||
set dir (string replace --regex '(.)/+$' '$1' "$dir")
|
|
||||||
|
|
||||||
# "-path \$dir'*/\\.*'" matches hidden files/folders inside $dir but not
|
# "-path \$dir'*/\\.*'" matches hidden files/folders inside $dir but not
|
||||||
# $dir itself, even if hidden.
|
# $dir itself, even if hidden.
|
||||||
set -q FZF_CTRL_T_COMMAND; or set -l FZF_CTRL_T_COMMAND "
|
set -q FZF_CTRL_T_COMMAND; or set -l FZF_CTRL_T_COMMAND "
|
||||||
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
||||||
-o -type f -print \
|
-o -type f -print \
|
||||||
-o -type d -print \
|
-o -type d -print \
|
||||||
-o -type l -print 2> /dev/null | sed 's#^\./##'"
|
-o -type l -print 2> /dev/null | sed 's@^\./@@'"
|
||||||
|
|
||||||
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
||||||
begin
|
begin
|
||||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
|
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
|
||||||
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)" -m" | while read -l r; set result $result $r; end
|
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
|
||||||
end
|
end
|
||||||
if [ -z "$result" ]
|
if [ -z "$result" ]
|
||||||
commandline -f repaint
|
commandline -f repaint
|
||||||
return
|
return
|
||||||
end
|
else
|
||||||
|
|
||||||
if [ "$dir" != . ]
|
|
||||||
# Remove last token from commandline.
|
# Remove last token from commandline.
|
||||||
commandline -t ""
|
commandline -t ""
|
||||||
end
|
end
|
||||||
@@ -45,23 +38,46 @@ function fzf_key_bindings
|
|||||||
function fzf-history-widget -d "Show command history"
|
function fzf-history-widget -d "Show command history"
|
||||||
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
||||||
begin
|
begin
|
||||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS +s --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m"
|
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m"
|
||||||
|
|
||||||
|
set -l FISH_MAJOR (echo $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 --print0 -q '(commandline)' | read -lz result
|
||||||
|
and commandline -- $result
|
||||||
|
else
|
||||||
history | eval (__fzfcmd) -q '(commandline)' | read -l result
|
history | eval (__fzfcmd) -q '(commandline)' | read -l result
|
||||||
and commandline -- $result
|
and commandline -- $result
|
||||||
end
|
end
|
||||||
|
end
|
||||||
commandline -f repaint
|
commandline -f repaint
|
||||||
end
|
end
|
||||||
|
|
||||||
function fzf-cd-widget -d "Change directory"
|
function fzf-cd-widget -d "Change directory"
|
||||||
|
set -l commandline (__fzf_parse_commandline)
|
||||||
|
set -l dir $commandline[1]
|
||||||
|
set -l fzf_query $commandline[2]
|
||||||
|
|
||||||
set -q FZF_ALT_C_COMMAND; or set -l FZF_ALT_C_COMMAND "
|
set -q FZF_ALT_C_COMMAND; or set -l FZF_ALT_C_COMMAND "
|
||||||
command find -L . \\( -path '*/\\.*' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
||||||
-o -type d -print 2> /dev/null | sed 1d | cut -b3-"
|
-o -type d -print 2> /dev/null | sed 's@^\./@@'"
|
||||||
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
||||||
begin
|
begin
|
||||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
|
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
|
||||||
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)" +m" | read -l result
|
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
||||||
[ "$result" ]; and cd $result
|
|
||||||
|
if [ -n "$result" ]
|
||||||
|
cd $result
|
||||||
|
|
||||||
|
# Remove last token from commandline.
|
||||||
|
commandline -t ""
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
commandline -f repaint
|
commandline -f repaint
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -84,4 +100,47 @@ function fzf_key_bindings
|
|||||||
bind -M insert \cr fzf-history-widget
|
bind -M insert \cr fzf-history-widget
|
||||||
bind -M insert \ec fzf-cd-widget
|
bind -M insert \ec fzf-cd-widget
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath and rest of token'
|
||||||
|
# eval is used to do shell expansion on paths
|
||||||
|
set -l commandline (eval "printf '%s' "(commandline -t))
|
||||||
|
|
||||||
|
if [ -z $commandline ]
|
||||||
|
# Default to current directory with no --query
|
||||||
|
set dir '.'
|
||||||
|
set fzf_query ''
|
||||||
|
else
|
||||||
|
set dir (__fzf_get_dir $commandline)
|
||||||
|
|
||||||
|
if [ "$dir" = "." -a (string sub -l 1 $commandline) != '.' ]
|
||||||
|
# if $dir is "." but commandline is not a relative path, this means no file path found
|
||||||
|
set fzf_query $commandline
|
||||||
|
else
|
||||||
|
# Also remove trailing slash after dir, to "split" input properly
|
||||||
|
set fzf_query (string replace -r "^$dir/?" '' "$commandline")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
echo $dir
|
||||||
|
echo $fzf_query
|
||||||
|
end
|
||||||
|
|
||||||
|
function __fzf_get_dir -d 'Find the longest existing filepath from input string'
|
||||||
|
set dir $argv
|
||||||
|
|
||||||
|
# Strip all trailing slashes. Ignore if $dir is root dir (/)
|
||||||
|
if [ (string length $dir) -gt 1 ]
|
||||||
|
set dir (string replace -r '/*$' '' $dir)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Iteratively check if dir exists and strip tail end of path
|
||||||
|
while [ ! -d "$dir" ]
|
||||||
|
# If path is absolute, this can keep going until ends up at /
|
||||||
|
# If path is relative, this can keep going until entire input is consumed, dirname returns "."
|
||||||
|
set dir (dirname "$dir")
|
||||||
|
end
|
||||||
|
|
||||||
|
echo $dir
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ if [[ $- == *i* ]]; then
|
|||||||
|
|
||||||
# CTRL-T - Paste the selected file path(s) into the command line
|
# CTRL-T - Paste the selected file path(s) into the command line
|
||||||
__fsel() {
|
__fsel() {
|
||||||
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
local cmd="${FZF_CTRL_T_COMMAND:-"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 f -print \
|
||||||
-o -type d -print \
|
-o -type d -print \
|
||||||
-o -type l -print 2> /dev/null | cut -b3-"}"
|
-o -type l -print 2> /dev/null | 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
|
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} "
|
echo -n "${(q)item} "
|
||||||
done
|
done
|
||||||
@@ -29,22 +29,36 @@ __fzfcmd() {
|
|||||||
fzf-file-widget() {
|
fzf-file-widget() {
|
||||||
LBUFFER="${LBUFFER}$(__fsel)"
|
LBUFFER="${LBUFFER}$(__fsel)"
|
||||||
local ret=$?
|
local ret=$?
|
||||||
zle redisplay
|
zle reset-prompt
|
||||||
typeset -f zle-line-init >/dev/null && zle zle-line-init
|
|
||||||
return $ret
|
return $ret
|
||||||
}
|
}
|
||||||
zle -N fzf-file-widget
|
zle -N fzf-file-widget
|
||||||
bindkey '^T' 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
|
# ALT-C - cd into the selected directory
|
||||||
fzf-cd-widget() {
|
fzf-cd-widget() {
|
||||||
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . \\( -path '*/\\.*' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
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 | sed 1d | cut -b3-"}"
|
-o -type d -print 2> /dev/null | cut -b3-"}"
|
||||||
setopt localoptions pipefail 2> /dev/null
|
setopt localoptions pipefail no_aliases 2> /dev/null
|
||||||
cd "${$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m):-.}"
|
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=$?
|
local ret=$?
|
||||||
zle reset-prompt
|
zle fzf-redraw-prompt
|
||||||
typeset -f zle-line-init >/dev/null && zle zle-line-init
|
|
||||||
return $ret
|
return $ret
|
||||||
}
|
}
|
||||||
zle -N fzf-cd-widget
|
zle -N fzf-cd-widget
|
||||||
@@ -53,9 +67,9 @@ bindkey '\ec' fzf-cd-widget
|
|||||||
# CTRL-R - Paste the selected command from history into the command line
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
fzf-history-widget() {
|
fzf-history-widget() {
|
||||||
local selected num
|
local selected num
|
||||||
setopt localoptions noglobsubst pipefail 2> /dev/null
|
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
|
||||||
selected=( $(fc -l 1 |
|
selected=( $(fc -rl 1 |
|
||||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS +s --tac -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS --query=${(q)LBUFFER} +m" $(__fzfcmd)) )
|
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=$?
|
local ret=$?
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
num=$selected[1]
|
num=$selected[1]
|
||||||
@@ -63,12 +77,10 @@ fzf-history-widget() {
|
|||||||
zle vi-fetch-history -n $num
|
zle vi-fetch-history -n $num
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
zle redisplay
|
zle reset-prompt
|
||||||
typeset -f zle-line-init >/dev/null && zle zle-line-init
|
|
||||||
return $ret
|
return $ret
|
||||||
}
|
}
|
||||||
zle -N fzf-history-widget
|
zle -N fzf-history-widget
|
||||||
bindkey '^R' fzf-history-widget
|
bindkey '^R' fzf-history-widget
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
FROM ubuntu:14.04
|
|
||||||
MAINTAINER Junegunn Choi <junegunn.c@gmail.com>
|
|
||||||
|
|
||||||
# apt-get
|
|
||||||
RUN apt-get update && apt-get -y upgrade && \
|
|
||||||
apt-get install -y --force-yes git curl build-essential
|
|
||||||
|
|
||||||
# Install Go 1.4
|
|
||||||
RUN cd / && curl \
|
|
||||||
https://storage.googleapis.com/golang/go1.4.2.linux-amd64.tar.gz | \
|
|
||||||
tar -xz && mv go go1.4 && \
|
|
||||||
sed -i 's@#define PTHREAD_KEYS_MAX 128@@' /go1.4/src/runtime/cgo/gcc_android_arm.c
|
|
||||||
|
|
||||||
ENV GOROOT /go1.4
|
|
||||||
ENV PATH /go1.4/bin:$PATH
|
|
||||||
|
|
||||||
RUN cd / && \
|
|
||||||
curl -O http://dl.google.com/android/ndk/android-ndk-r10e-linux-x86_64.bin && \
|
|
||||||
chmod 755 /android-ndk* && /android-ndk-r10e-linux-x86_64.bin && \
|
|
||||||
mv android-ndk-r10e /android-ndk
|
|
||||||
|
|
||||||
RUN cd /android-ndk && bash ./build/tools/make-standalone-toolchain.sh --platform=android-21 --install-dir=/ndk --arch=arm
|
|
||||||
|
|
||||||
ENV NDK_CC /ndk/bin/arm-linux-androideabi-gcc
|
|
||||||
|
|
||||||
RUN cd $GOROOT/src && \
|
|
||||||
CC_FOR_TARGET=$NDK_CC GOOS=android GOARCH=arm GOARM=7 ./make.bash
|
|
||||||
|
|
||||||
RUN cd / && curl \
|
|
||||||
http://ftp.gnu.org/gnu/ncurses/ncurses-5.9.tar.gz | \
|
|
||||||
tar -xz && cd /ncurses-5.9 && \
|
|
||||||
./configure CC=$NDK_CC CFLAGS="-fPIE -march=armv7-a -mfpu=neon -mhard-float -Wl,--no-warn-mismatch" LDFLAGS="-march=armv7-a -Wl,--no-warn-mismatch" --host=arm-linux --enable-overwrite --enable-const --without-cxx-binding --without-shared --without-debug --enable-widec --enable-ext-colors --enable-ext-mouse --enable-pc-files --with-pkg-config-libdir=$PKG_CONFIG_LIBDIR --without-manpages --without-ada --disable-shared --without-tests --prefix=/ndk/sysroot/usr --with-default-terminfo-dirs=/usr/share/terminfo --with-terminfo-dirs=/usr/share/terminfo ac_cv_header_locale_h=n ac_cv_func_getpwent=no ac_cv_func_getpwnam=no ac_cv_func_getpwuid=no && \
|
|
||||||
sed -i 's@#define HAVE_LOCALE_H 1@/* #undef HAVE_LOCALE_H */@' include/ncurses_cfg.h && \
|
|
||||||
make && \
|
|
||||||
sed -i '0,/echo.*/{s/echo.*/exit 0/}' misc/run_tic.sh && \
|
|
||||||
make install && \
|
|
||||||
mv /ndk/sysroot/usr/lib/libncursesw.a /ndk/sysroot/usr/lib/libncurses.a
|
|
||||||
|
|
||||||
# Default CMD
|
|
||||||
CMD cd /fzf/src && /bin/bash
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
FROM base/archlinux:2014.07.03
|
|
||||||
MAINTAINER Junegunn Choi <junegunn.c@gmail.com>
|
|
||||||
|
|
||||||
# apt-get
|
|
||||||
RUN pacman-key --populate archlinux && pacman-key --refresh-keys
|
|
||||||
RUN pacman-db-upgrade && pacman -Syu --noconfirm base-devel git
|
|
||||||
|
|
||||||
# Install Go 1.4
|
|
||||||
RUN cd / && curl \
|
|
||||||
https://storage.googleapis.com/golang/go1.4.2.linux-amd64.tar.gz | \
|
|
||||||
tar -xz && mv go go1.4
|
|
||||||
|
|
||||||
ENV GOROOT /go1.4
|
|
||||||
ENV PATH /go1.4/bin:$PATH
|
|
||||||
|
|
||||||
# For i386 build
|
|
||||||
RUN echo '[multilib]' >> /etc/pacman.conf && \
|
|
||||||
echo 'Include = /etc/pacman.d/mirrorlist' >> /etc/pacman.conf && \
|
|
||||||
pacman-db-upgrade && yes | pacman -Sy gcc-multilib lib32-ncurses && \
|
|
||||||
cd $GOROOT/src && GOARCH=386 ./make.bash
|
|
||||||
|
|
||||||
# Default CMD
|
|
||||||
CMD cd /fzf/src && /bin/bash
|
|
||||||
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
FROM centos:centos6
|
|
||||||
MAINTAINER Junegunn Choi <junegunn.c@gmail.com>
|
|
||||||
|
|
||||||
# yum
|
|
||||||
RUN yum install -y git gcc make tar glibc-devel glibc-devel.i686 \
|
|
||||||
ncurses-devel ncurses-static ncurses-devel.i686 \
|
|
||||||
gpm-devel gpm-static libgcc.i686
|
|
||||||
|
|
||||||
# Install Go 1.4
|
|
||||||
RUN cd / && curl \
|
|
||||||
https://storage.googleapis.com/golang/go1.4.2.linux-amd64.tar.gz | \
|
|
||||||
tar -xz && mv go go1.4
|
|
||||||
|
|
||||||
# Install Go 1.7
|
|
||||||
RUN cd / && curl \
|
|
||||||
https://storage.googleapis.com/golang/go1.7.linux-amd64.tar.gz | \
|
|
||||||
tar -xz && mv go go1.7
|
|
||||||
|
|
||||||
# Install RPMs for building static 32-bit binary
|
|
||||||
RUN curl ftp://ftp.pbone.net/mirror/ftp.centos.org/6.8/os/i386/Packages/ncurses-static-5.7-4.20090207.el6.i686.rpm -o rpm && rpm -i rpm && \
|
|
||||||
curl ftp://ftp.pbone.net/mirror/ftp.centos.org/6.8/os/i386/Packages/gpm-static-1.20.6-12.el6.i686.rpm -o rpm && rpm -i rpm
|
|
||||||
|
|
||||||
ENV GOROOT_BOOTSTRAP /go1.4
|
|
||||||
ENV GOROOT /go1.7
|
|
||||||
ENV PATH /go1.7/bin:$PATH
|
|
||||||
|
|
||||||
# For i386 build
|
|
||||||
RUN cd $GOROOT/src && GOARCH=386 ./make.bash
|
|
||||||
|
|
||||||
# Default CMD
|
|
||||||
CMD cd /fzf/src && /bin/bash
|
|
||||||
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
FROM ubuntu:14.04
|
|
||||||
MAINTAINER Junegunn Choi <junegunn.c@gmail.com>
|
|
||||||
|
|
||||||
# apt-get
|
|
||||||
RUN apt-get update && apt-get -y upgrade && \
|
|
||||||
apt-get install -y --force-yes git curl build-essential libncurses-dev libgpm-dev
|
|
||||||
|
|
||||||
# Install Go 1.4
|
|
||||||
RUN cd / && curl \
|
|
||||||
https://storage.googleapis.com/golang/go1.4.2.linux-amd64.tar.gz | \
|
|
||||||
tar -xz && mv go go1.4
|
|
||||||
|
|
||||||
ENV GOROOT /go1.4
|
|
||||||
ENV PATH /go1.4/bin:$PATH
|
|
||||||
|
|
||||||
# For i386 build
|
|
||||||
RUN apt-get install -y lib32ncurses5-dev && \
|
|
||||||
cd $GOROOT/src && GOARCH=386 ./make.bash
|
|
||||||
|
|
||||||
# Default CMD
|
|
||||||
CMD cd /fzf/src && /bin/bash
|
|
||||||
|
|
||||||
170
src/Makefile
170
src/Makefile
@@ -1,170 +0,0 @@
|
|||||||
ifndef GOOS
|
|
||||||
UNAME_S := $(shell uname -s)
|
|
||||||
ifeq ($(UNAME_S),Darwin)
|
|
||||||
GOOS := darwin
|
|
||||||
else ifeq ($(UNAME_S),Linux)
|
|
||||||
GOOS := linux
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
|
|
||||||
SOURCES := $(wildcard *.go */*.go)
|
|
||||||
ROOTDIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
|
||||||
BINDIR := $(shell dirname $(ROOTDIR))/bin
|
|
||||||
GOPATH := $(shell dirname $(ROOTDIR))/gopath
|
|
||||||
SRCDIR := $(GOPATH)/src/github.com/junegunn/fzf/src
|
|
||||||
DOCKEROPTS := -i -t -v $(ROOTDIR):/fzf/src
|
|
||||||
BINARY32 := fzf-$(GOOS)_386
|
|
||||||
BINARY64 := fzf-$(GOOS)_amd64
|
|
||||||
BINARYARM5 := fzf-$(GOOS)_arm5
|
|
||||||
BINARYARM6 := fzf-$(GOOS)_arm6
|
|
||||||
BINARYARM7 := fzf-$(GOOS)_arm7
|
|
||||||
BINARYARM8 := fzf-$(GOOS)_arm8
|
|
||||||
VERSION := $(shell awk -F= '/version =/ {print $$2}' constants.go | tr -d "\" ")
|
|
||||||
RELEASE32 := fzf-$(VERSION)-$(GOOS)_386
|
|
||||||
RELEASE64 := fzf-$(VERSION)-$(GOOS)_amd64
|
|
||||||
RELEASEARM5 := fzf-$(VERSION)-$(GOOS)_arm5
|
|
||||||
RELEASEARM6 := fzf-$(VERSION)-$(GOOS)_arm6
|
|
||||||
RELEASEARM7 := fzf-$(VERSION)-$(GOOS)_arm7
|
|
||||||
RELEASEARM8 := fzf-$(VERSION)-$(GOOS)_arm8
|
|
||||||
export GOPATH
|
|
||||||
|
|
||||||
# https://en.wikipedia.org/wiki/Uname
|
|
||||||
UNAME_M := $(shell uname -m)
|
|
||||||
ifeq ($(UNAME_M),x86_64)
|
|
||||||
BINARY := $(BINARY64)
|
|
||||||
else ifeq ($(UNAME_M),amd64)
|
|
||||||
BINARY := $(BINARY64)
|
|
||||||
else ifeq ($(UNAME_M),i686)
|
|
||||||
BINARY := $(BINARY32)
|
|
||||||
else ifeq ($(UNAME_M),i386)
|
|
||||||
BINARY := $(BINARY32)
|
|
||||||
else ifeq ($(UNAME_M),armv5l)
|
|
||||||
BINARY := $(BINARYARM5)
|
|
||||||
else ifeq ($(UNAME_M),armv6l)
|
|
||||||
BINARY := $(BINARYARM6)
|
|
||||||
else ifeq ($(UNAME_M),armv7l)
|
|
||||||
BINARY := $(BINARYARM7)
|
|
||||||
else
|
|
||||||
$(error "Build on $(UNAME_M) is not supported, yet.")
|
|
||||||
endif
|
|
||||||
|
|
||||||
all: fzf/$(BINARY)
|
|
||||||
|
|
||||||
ifeq ($(GOOS),windows)
|
|
||||||
release: fzf/$(BINARY32) fzf/$(BINARY64)
|
|
||||||
cd fzf && cp -f $(BINARY32) fzf.exe && zip $(RELEASE32).zip fzf.exe
|
|
||||||
cd fzf && cp -f $(BINARY64) fzf.exe && zip $(RELEASE64).zip fzf.exe
|
|
||||||
cd fzf && rm -f fzf.exe
|
|
||||||
else ifeq ($(GOOS),linux)
|
|
||||||
release: fzf/$(BINARY32) fzf/$(BINARY64) fzf/$(BINARYARM5) fzf/$(BINARYARM6) fzf/$(BINARYARM7) fzf/$(BINARYARM8)
|
|
||||||
cd fzf && cp -f $(BINARY32) fzf && tar -czf $(RELEASE32).tgz fzf
|
|
||||||
cd fzf && cp -f $(BINARY64) fzf && tar -czf $(RELEASE64).tgz fzf
|
|
||||||
cd fzf && cp -f $(BINARYARM5) fzf && tar -czf $(RELEASEARM5).tgz fzf
|
|
||||||
cd fzf && cp -f $(BINARYARM6) fzf && tar -czf $(RELEASEARM6).tgz fzf
|
|
||||||
cd fzf && cp -f $(BINARYARM7) fzf && tar -czf $(RELEASEARM7).tgz fzf
|
|
||||||
cd fzf && cp -f $(BINARYARM8) fzf && tar -czf $(RELEASEARM8).tgz fzf
|
|
||||||
cd fzf && rm -f fzf
|
|
||||||
else
|
|
||||||
release: fzf/$(BINARY32) fzf/$(BINARY64)
|
|
||||||
cd fzf && cp -f $(BINARY32) fzf && tar -czf $(RELEASE32).tgz fzf
|
|
||||||
cd fzf && cp -f $(BINARY64) fzf && tar -czf $(RELEASE64).tgz fzf
|
|
||||||
cd fzf && rm -f fzf
|
|
||||||
endif
|
|
||||||
|
|
||||||
release-all: clean test
|
|
||||||
GOOS=darwin make release
|
|
||||||
GOOS=linux make release
|
|
||||||
GOOS=freebsd make release
|
|
||||||
GOOS=openbsd make release
|
|
||||||
GOOS=windows make release
|
|
||||||
|
|
||||||
$(SRCDIR):
|
|
||||||
mkdir -p $(shell dirname $(SRCDIR))
|
|
||||||
ln -s $(ROOTDIR) $(SRCDIR)
|
|
||||||
|
|
||||||
deps: $(SRCDIR) $(SOURCES)
|
|
||||||
cd $(SRCDIR) && go get -tags "$(TAGS)"
|
|
||||||
./deps
|
|
||||||
|
|
||||||
android-build: $(SRCDIR)
|
|
||||||
cd $(SRCDIR) && GOARCH=arm GOARM=7 CGO_ENABLED=1 go get
|
|
||||||
cd $(SRCDIR)/fzf && GOARCH=arm GOARM=7 CGO_ENABLED=1 go build -a -ldflags="-w -extldflags=-pie" -o $(BINARYARM7)
|
|
||||||
cd $(SRCDIR)/fzf && cp $(BINARYARM7) $(RELEASEARM7) && tar -czf $(RELEASEARM7).tgz $(RELEASEARM7) && \
|
|
||||||
rm -f $(RELEASEARM7)
|
|
||||||
|
|
||||||
test: deps
|
|
||||||
SHELL=/bin/sh GOOS= go test -v -tags "$(TAGS)" ./...
|
|
||||||
|
|
||||||
install: $(BINDIR)/fzf
|
|
||||||
|
|
||||||
uninstall:
|
|
||||||
rm -f $(BINDIR)/fzf $(BINDIR)/$(BINARY)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
cd fzf && rm -f fzf-*
|
|
||||||
|
|
||||||
fzf/$(BINARY32): deps
|
|
||||||
cd fzf && GOARCH=386 go build -a -ldflags "-w -extldflags=$(LDFLAGS)" -tags "$(TAGS)" -o $(BINARY32)
|
|
||||||
|
|
||||||
fzf/$(BINARY64): deps
|
|
||||||
cd fzf && GOARCH=amd64 go build -a -ldflags "-w -extldflags=$(LDFLAGS)" -tags "$(TAGS)" -o $(BINARY64)
|
|
||||||
|
|
||||||
# https://github.com/golang/go/wiki/GoArm
|
|
||||||
fzf/$(BINARYARM5): deps
|
|
||||||
cd fzf && GOARCH=arm GOARM=5 go build -a -ldflags "-w -extldflags=$(LDFLAGS)" -tags "$(TAGS)" -o $(BINARYARM5)
|
|
||||||
|
|
||||||
fzf/$(BINARYARM6): deps
|
|
||||||
cd fzf && GOARCH=arm GOARM=6 go build -a -ldflags "-w -extldflags=$(LDFLAGS)" -tags "$(TAGS)" -o $(BINARYARM6)
|
|
||||||
|
|
||||||
fzf/$(BINARYARM7): deps
|
|
||||||
cd fzf && GOARCH=arm GOARM=7 go build -a -ldflags "-w -extldflags=$(LDFLAGS)" -tags "$(TAGS)" -o $(BINARYARM7)
|
|
||||||
|
|
||||||
fzf/$(BINARYARM8): deps
|
|
||||||
cd fzf && GOARCH=arm64 go build -a -ldflags "-w -extldflags=$(LDFLAGS)" -tags "$(TAGS)" -o $(BINARYARM8)
|
|
||||||
|
|
||||||
$(BINDIR)/fzf: fzf/$(BINARY) | $(BINDIR)
|
|
||||||
cp -f fzf/$(BINARY) $(BINDIR)
|
|
||||||
cd $(BINDIR) && ln -sf $(BINARY) fzf
|
|
||||||
|
|
||||||
$(BINDIR):
|
|
||||||
mkdir -p $@
|
|
||||||
|
|
||||||
docker-arch:
|
|
||||||
docker build -t junegunn/arch-sandbox - < Dockerfile.arch
|
|
||||||
|
|
||||||
docker-ubuntu:
|
|
||||||
docker build -t junegunn/ubuntu-sandbox - < Dockerfile.ubuntu
|
|
||||||
|
|
||||||
docker-centos:
|
|
||||||
docker build -t junegunn/centos-sandbox - < Dockerfile.centos
|
|
||||||
|
|
||||||
docker-android:
|
|
||||||
docker build -t junegunn/android-sandbox - < Dockerfile.android
|
|
||||||
|
|
||||||
arch: docker-arch
|
|
||||||
docker run $(DOCKEROPTS) junegunn/$@-sandbox \
|
|
||||||
sh -c 'cd /fzf/src; /bin/bash'
|
|
||||||
|
|
||||||
ubuntu: docker-ubuntu
|
|
||||||
docker run $(DOCKEROPTS) junegunn/$@-sandbox \
|
|
||||||
sh -c 'cd /fzf/src; /bin/bash'
|
|
||||||
|
|
||||||
centos: docker-centos
|
|
||||||
docker run $(DOCKEROPTS) junegunn/$@-sandbox \
|
|
||||||
sh -c 'cd /fzf/src; /bin/bash'
|
|
||||||
|
|
||||||
linux: docker-centos
|
|
||||||
docker run $(DOCKEROPTS) junegunn/centos-sandbox \
|
|
||||||
/bin/bash -ci 'cd /fzf/src; make TAGS=static release'
|
|
||||||
|
|
||||||
ubuntu-android: docker-android
|
|
||||||
docker run $(DOCKEROPTS) junegunn/android-sandbox \
|
|
||||||
sh -c 'cd /fzf/src; /bin/bash'
|
|
||||||
|
|
||||||
android: docker-android
|
|
||||||
docker run $(DOCKEROPTS) junegunn/android-sandbox \
|
|
||||||
/bin/bash -ci 'cd /fzf/src; GOOS=android make android-build'
|
|
||||||
|
|
||||||
.PHONY: all deps release test install uninstall clean \
|
|
||||||
linux arch ubuntu centos docker-arch docker-ubuntu docker-centos \
|
|
||||||
android-build docker-android ubuntu-android android
|
|
||||||
106
src/README.md
106
src/README.md
@@ -1,106 +0,0 @@
|
|||||||
fzf in Go
|
|
||||||
=========
|
|
||||||
|
|
||||||
<img src="https://cloud.githubusercontent.com/assets/700826/5725028/028ea834-9b93-11e4-9198-43088c3f295d.gif" height="463" alt="fzf in go">
|
|
||||||
|
|
||||||
This directory contains the source code for the new fzf implementation in
|
|
||||||
[Go][go].
|
|
||||||
|
|
||||||
Upgrade from Ruby version
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
The install script has been updated to download the right binary for your
|
|
||||||
system. If you already have installed fzf, simply git-pull the repository and
|
|
||||||
rerun the install script.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd ~/.fzf
|
|
||||||
git pull
|
|
||||||
./install
|
|
||||||
```
|
|
||||||
|
|
||||||
Otherwise, follow [the instruction][install] as before. You can also install
|
|
||||||
fzf using Homebrew if you prefer that way.
|
|
||||||
|
|
||||||
Motivations
|
|
||||||
-----------
|
|
||||||
|
|
||||||
### No Ruby dependency
|
|
||||||
|
|
||||||
There have always been complaints about fzf being a Ruby script. To make
|
|
||||||
matters worse, Ruby 2.1 removed ncurses binding from its standard libary.
|
|
||||||
Because of the change, users running Ruby 2.1 or above are forced to build C
|
|
||||||
extensions of curses gem to meet the requirement of fzf. The new Go version
|
|
||||||
will be distributed as an executable binary so it will be much more accessible
|
|
||||||
and should be easier to setup.
|
|
||||||
|
|
||||||
### Performance
|
|
||||||
|
|
||||||
Many people have been surprised to see how fast fzf is even when it was
|
|
||||||
written in Ruby. It stays quite responsive even for 100k+ lines, which is
|
|
||||||
well above the size of the usual input.
|
|
||||||
|
|
||||||
The new Go version, of course, is significantly faster than that. It has all
|
|
||||||
the performance optimization techniques used in Ruby implementation and more.
|
|
||||||
It also doesn't suffer from [GIL][gil], so the search performance scales
|
|
||||||
proportional to the number of CPU cores. On my MacBook Pro (Mid 2012), the new
|
|
||||||
version was shown to be an order of magnitude faster on certain cases. It also
|
|
||||||
starts much faster though the difference may not be noticeable.
|
|
||||||
|
|
||||||
Build
|
|
||||||
-----
|
|
||||||
|
|
||||||
See [BUILD.md](../BUILD.md)
|
|
||||||
|
|
||||||
Test
|
|
||||||
----
|
|
||||||
|
|
||||||
Unit tests can be run with `make test`. Integration tests are written in Ruby
|
|
||||||
script that should be run on tmux.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd src
|
|
||||||
|
|
||||||
# Unit tests
|
|
||||||
make test
|
|
||||||
|
|
||||||
# Integration tests
|
|
||||||
ruby ../test/test_go.rb
|
|
||||||
|
|
||||||
# Build binary for the platform
|
|
||||||
make
|
|
||||||
|
|
||||||
# Install the executable to ../bin directory
|
|
||||||
make install
|
|
||||||
|
|
||||||
# Make release archives
|
|
||||||
make release
|
|
||||||
|
|
||||||
# Make release archives for all supported platforms
|
|
||||||
make release-all
|
|
||||||
```
|
|
||||||
|
|
||||||
Third-party libraries used
|
|
||||||
--------------------------
|
|
||||||
|
|
||||||
- ~[ncurses][ncurses]~
|
|
||||||
- [mattn/go-runewidth](https://github.com/mattn/go-runewidth)
|
|
||||||
- Licensed under [MIT](http://mattn.mit-license.org)
|
|
||||||
- [mattn/go-shellwords](https://github.com/mattn/go-shellwords)
|
|
||||||
- Licensed under [MIT](http://mattn.mit-license.org)
|
|
||||||
- [mattn/go-isatty](https://github.com/mattn/go-isatty)
|
|
||||||
- Licensed under [MIT](http://mattn.mit-license.org)
|
|
||||||
- [tcell](https://github.com/gdamore/tcell)
|
|
||||||
- Licensed under [Apache License 2.0](https://github.com/gdamore/tcell/blob/master/LICENSE)
|
|
||||||
|
|
||||||
License
|
|
||||||
-------
|
|
||||||
|
|
||||||
[MIT](LICENSE)
|
|
||||||
|
|
||||||
[install]: https://github.com/junegunn/fzf#installation
|
|
||||||
[go]: https://golang.org/
|
|
||||||
[gil]: http://en.wikipedia.org/wiki/Global_Interpreter_Lock
|
|
||||||
[ncurses]: https://www.gnu.org/software/ncurses/
|
|
||||||
[req]: http://golang.org/doc/install
|
|
||||||
[tcell]: https://github.com/gdamore/tcell
|
|
||||||
416
src/algo/algo.go
416
src/algo/algo.go
@@ -78,9 +78,11 @@ Scoring criteria
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
@@ -156,27 +158,17 @@ func posArray(withPos bool, len int) *[]int {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func alloc16(offset int, slab *util.Slab, size int, clear bool) (int, []int16) {
|
func alloc16(offset int, slab *util.Slab, size int) (int, []int16) {
|
||||||
if slab != nil && cap(slab.I16) > offset+size {
|
if slab != nil && cap(slab.I16) > offset+size {
|
||||||
slice := slab.I16[offset : offset+size]
|
slice := slab.I16[offset : offset+size]
|
||||||
if clear {
|
|
||||||
for idx := range slice {
|
|
||||||
slice[idx] = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return offset + size, slice
|
return offset + size, slice
|
||||||
}
|
}
|
||||||
return offset, make([]int16, size)
|
return offset, make([]int16, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func alloc32(offset int, slab *util.Slab, size int, clear bool) (int, []int32) {
|
func alloc32(offset int, slab *util.Slab, size int) (int, []int32) {
|
||||||
if slab != nil && cap(slab.I32) > offset+size {
|
if slab != nil && cap(slab.I32) > offset+size {
|
||||||
slice := slab.I32[offset : offset+size]
|
slice := slab.I32[offset : offset+size]
|
||||||
if clear {
|
|
||||||
for idx := range slice {
|
|
||||||
slice[idx] = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return offset + size, slice
|
return offset + size, slice
|
||||||
}
|
}
|
||||||
return offset, make([]int32, size)
|
return offset, make([]int32, size)
|
||||||
@@ -227,7 +219,7 @@ func bonusFor(prevClass charClass, class charClass) int16 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func bonusAt(input util.Chars, idx int) int16 {
|
func bonusAt(input *util.Chars, idx int) int16 {
|
||||||
if idx == 0 {
|
if idx == 0 {
|
||||||
return bonusBoundary
|
return bonusBoundary
|
||||||
}
|
}
|
||||||
@@ -249,155 +241,84 @@ func normalizeRune(r rune) rune {
|
|||||||
// Algo functions make two assumptions
|
// Algo functions make two assumptions
|
||||||
// 1. "pattern" is given in lowercase if "caseSensitive" is false
|
// 1. "pattern" is given in lowercase if "caseSensitive" is false
|
||||||
// 2. "pattern" is already normalized if "normalize" is true
|
// 2. "pattern" is already normalized if "normalize" is true
|
||||||
type Algo func(caseSensitive bool, normalize bool, forward bool, input util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int)
|
type Algo func(caseSensitive bool, normalize bool, forward bool, input *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int)
|
||||||
|
|
||||||
func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
func trySkip(input *util.Chars, caseSensitive bool, b byte, from int) int {
|
||||||
// Assume that pattern is given in lowercase if case-insensitive.
|
byteArray := input.Bytes()[from:]
|
||||||
// First check if there's a match and calculate bonus for each position.
|
idx := bytes.IndexByte(byteArray, b)
|
||||||
// If the input string is too long, consider finding the matching chars in
|
if idx == 0 {
|
||||||
// this phase as well (non-optimal alignment).
|
// Can't skip any further
|
||||||
N := input.Length()
|
return from
|
||||||
M := len(pattern)
|
}
|
||||||
switch M {
|
// We may need to search for the uppercase letter again. We don't have to
|
||||||
case 0:
|
// consider normalization as we can be sure that this is an ASCII string.
|
||||||
return Result{0, 0, 0}, posArray(withPos, M)
|
if !caseSensitive && b >= 'a' && b <= 'z' {
|
||||||
case 1:
|
if idx > 0 {
|
||||||
return ExactMatchNaive(caseSensitive, normalize, forward, input, pattern[0:1], withPos, slab)
|
byteArray = byteArray[:idx]
|
||||||
|
}
|
||||||
|
uidx := bytes.IndexByte(byteArray, b-32)
|
||||||
|
if uidx >= 0 {
|
||||||
|
idx = uidx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if idx < 0 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return from + idx
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAscii(runes []rune) bool {
|
||||||
|
for _, r := range runes {
|
||||||
|
if r >= utf8.RuneSelf {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func asciiFuzzyIndex(input *util.Chars, pattern []rune, caseSensitive bool) int {
|
||||||
|
// Can't determine
|
||||||
|
if !input.IsBytes() {
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since O(nm) algorithm can be prohibitively expensive for large input,
|
// Not possible
|
||||||
// we fall back to the greedy algorithm.
|
if !isAscii(pattern) {
|
||||||
if slab != nil && N*M > cap(slab.I16) {
|
return -1
|
||||||
return FuzzyMatchV1(caseSensitive, normalize, forward, input, pattern, withPos, slab)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reuse pre-allocated integer slice to avoid unnecessary sweeping of garbages
|
firstIdx, idx := 0, 0
|
||||||
offset16 := 0
|
for pidx := 0; pidx < len(pattern); pidx++ {
|
||||||
offset32 := 0
|
idx = trySkip(input, caseSensitive, byte(pattern[pidx]), idx)
|
||||||
// Bonus point for each position
|
if idx < 0 {
|
||||||
offset16, B := alloc16(offset16, slab, N, false)
|
return -1
|
||||||
// The first occurrence of each character in the pattern
|
}
|
||||||
offset32, F := alloc32(offset32, slab, M, false)
|
if pidx == 0 && idx > 0 {
|
||||||
// Rune array
|
// Step back to find the right bonus point
|
||||||
offset32, T := alloc32(offset32, slab, N, false)
|
firstIdx = idx - 1
|
||||||
|
}
|
||||||
|
idx++
|
||||||
|
}
|
||||||
|
return firstIdx
|
||||||
|
}
|
||||||
|
|
||||||
// Phase 1. Check if there's a match and calculate bonus for each point
|
func debugV2(T []rune, pattern []rune, F []int32, lastIdx int, H []int16, C []int16) {
|
||||||
pidx, lastIdx, prevClass := 0, 0, charNonWord
|
|
||||||
for idx := 0; idx < N; idx++ {
|
|
||||||
char := input.Get(idx)
|
|
||||||
var class charClass
|
|
||||||
if char <= unicode.MaxASCII {
|
|
||||||
class = charClassOfAscii(char)
|
|
||||||
} else {
|
|
||||||
class = charClassOfNonAscii(char)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !caseSensitive && class == charUpper {
|
|
||||||
if char <= unicode.MaxASCII {
|
|
||||||
char += 32
|
|
||||||
} else {
|
|
||||||
char = unicode.To(unicode.LowerCase, char)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if normalize {
|
|
||||||
char = normalizeRune(char)
|
|
||||||
}
|
|
||||||
|
|
||||||
T[idx] = char
|
|
||||||
B[idx] = bonusFor(prevClass, class)
|
|
||||||
prevClass = class
|
|
||||||
|
|
||||||
if pidx < M {
|
|
||||||
if char == pattern[pidx] {
|
|
||||||
lastIdx = idx
|
|
||||||
F[pidx] = int32(idx)
|
|
||||||
pidx++
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if char == pattern[M-1] {
|
|
||||||
lastIdx = idx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if pidx != M {
|
|
||||||
return Result{-1, -1, 0}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 2. Fill in score matrix (H)
|
|
||||||
// Unlike the original algorithm, we do not allow omission.
|
|
||||||
width := lastIdx - int(F[0]) + 1
|
width := lastIdx - int(F[0]) + 1
|
||||||
offset16, H := alloc16(offset16, slab, width*M, false)
|
|
||||||
|
|
||||||
// Possible length of consecutive chunk at each position.
|
for i, f := range F {
|
||||||
offset16, C := alloc16(offset16, slab, width*M, false)
|
|
||||||
|
|
||||||
maxScore, maxScorePos := int16(0), 0
|
|
||||||
for i := 0; i < M; i++ {
|
|
||||||
I := i * width
|
I := i * width
|
||||||
inGap := false
|
|
||||||
for j := int(F[i]); j <= lastIdx; j++ {
|
|
||||||
j0 := j - int(F[0])
|
|
||||||
var s1, s2, consecutive int16
|
|
||||||
|
|
||||||
if j > int(F[i]) {
|
|
||||||
if inGap {
|
|
||||||
s2 = H[I+j0-1] + scoreGapExtention
|
|
||||||
} else {
|
|
||||||
s2 = H[I+j0-1] + scoreGapStart
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if pattern[i] == T[j] {
|
|
||||||
var diag int16
|
|
||||||
if i > 0 && j0 > 0 {
|
|
||||||
diag = H[I-width+j0-1]
|
|
||||||
}
|
|
||||||
s1 = diag + scoreMatch
|
|
||||||
b := B[j]
|
|
||||||
if i > 0 {
|
|
||||||
// j > 0 if i > 0
|
|
||||||
consecutive = C[I-width+j0-1] + 1
|
|
||||||
// Break consecutive chunk
|
|
||||||
if b == bonusBoundary {
|
|
||||||
consecutive = 1
|
|
||||||
} else if consecutive > 1 {
|
|
||||||
b = util.Max16(b, util.Max16(bonusConsecutive, B[j-int(consecutive)+1]))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
consecutive = 1
|
|
||||||
b *= bonusFirstCharMultiplier
|
|
||||||
}
|
|
||||||
if s1+b < s2 {
|
|
||||||
s1 += B[j]
|
|
||||||
consecutive = 0
|
|
||||||
} else {
|
|
||||||
s1 += b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
C[I+j0] = consecutive
|
|
||||||
|
|
||||||
inGap = s1 < s2
|
|
||||||
score := util.Max16(util.Max16(s1, s2), 0)
|
|
||||||
if i == M-1 && (forward && score > maxScore || !forward && score >= maxScore) {
|
|
||||||
maxScore, maxScorePos = score, j
|
|
||||||
}
|
|
||||||
H[I+j0] = score
|
|
||||||
}
|
|
||||||
|
|
||||||
if DEBUG {
|
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
fmt.Print(" ")
|
fmt.Print(" ")
|
||||||
for j := int(F[i]); j <= lastIdx; j++ {
|
for j := int(f); j <= lastIdx; j++ {
|
||||||
fmt.Printf(" " + string(input.Get(j)) + " ")
|
fmt.Printf(" " + string(T[j]) + " ")
|
||||||
}
|
}
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
fmt.Print(string(pattern[i]) + " ")
|
fmt.Print(string(pattern[i]) + " ")
|
||||||
for idx := int(F[0]); idx < int(F[i]); idx++ {
|
for idx := int(F[0]); idx < int(f); idx++ {
|
||||||
fmt.Print(" 0 ")
|
fmt.Print(" 0 ")
|
||||||
}
|
}
|
||||||
for idx := int(F[i]); idx <= lastIdx; idx++ {
|
for idx := int(f); idx <= lastIdx; idx++ {
|
||||||
fmt.Printf("%2d ", H[i*width+idx-int(F[0])])
|
fmt.Printf("%2d ", H[i*width+idx-int(F[0])])
|
||||||
}
|
}
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
@@ -407,22 +328,202 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input util.C
|
|||||||
if idx+int(F[0]) < int(F[i]) {
|
if idx+int(F[0]) < int(F[i]) {
|
||||||
p = 0
|
p = 0
|
||||||
}
|
}
|
||||||
|
if p > 0 {
|
||||||
fmt.Printf("%2d ", p)
|
fmt.Printf("%2d ", p)
|
||||||
|
} else {
|
||||||
|
fmt.Print(" ")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
|
// Assume that pattern is given in lowercase if case-insensitive.
|
||||||
|
// First check if there's a match and calculate bonus for each position.
|
||||||
|
// If the input string is too long, consider finding the matching chars in
|
||||||
|
// this phase as well (non-optimal alignment).
|
||||||
|
M := len(pattern)
|
||||||
|
if M == 0 {
|
||||||
|
return Result{0, 0, 0}, posArray(withPos, M)
|
||||||
|
}
|
||||||
|
N := input.Length()
|
||||||
|
|
||||||
|
// Since O(nm) algorithm can be prohibitively expensive for large input,
|
||||||
|
// we fall back to the greedy algorithm.
|
||||||
|
if slab != nil && N*M > cap(slab.I16) {
|
||||||
|
return FuzzyMatchV1(caseSensitive, normalize, forward, input, pattern, withPos, slab)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase 3. (Optional) Backtrace to find character positions
|
// Phase 1. Optimized search for ASCII string
|
||||||
|
idx := asciiFuzzyIndex(input, pattern, caseSensitive)
|
||||||
|
if idx < 0 {
|
||||||
|
return Result{-1, -1, 0}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reuse pre-allocated integer slice to avoid unnecessary sweeping of garbages
|
||||||
|
offset16 := 0
|
||||||
|
offset32 := 0
|
||||||
|
offset16, H0 := alloc16(offset16, slab, N)
|
||||||
|
offset16, C0 := alloc16(offset16, slab, N)
|
||||||
|
// Bonus point for each position
|
||||||
|
offset16, B := alloc16(offset16, slab, N)
|
||||||
|
// The first occurrence of each character in the pattern
|
||||||
|
offset32, F := alloc32(offset32, slab, M)
|
||||||
|
// Rune array
|
||||||
|
_, T := alloc32(offset32, slab, N)
|
||||||
|
input.CopyRunes(T)
|
||||||
|
|
||||||
|
// Phase 2. Calculate bonus for each point
|
||||||
|
maxScore, maxScorePos := int16(0), 0
|
||||||
|
pidx, lastIdx := 0, 0
|
||||||
|
pchar0, pchar, prevH0, prevClass, inGap := pattern[0], pattern[0], int16(0), charNonWord, false
|
||||||
|
Tsub := T[idx:]
|
||||||
|
H0sub, C0sub, Bsub := H0[idx:][:len(Tsub)], C0[idx:][:len(Tsub)], B[idx:][:len(Tsub)]
|
||||||
|
for off, char := range Tsub {
|
||||||
|
var class charClass
|
||||||
|
if char <= unicode.MaxASCII {
|
||||||
|
class = charClassOfAscii(char)
|
||||||
|
if !caseSensitive && class == charUpper {
|
||||||
|
char += 32
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
class = charClassOfNonAscii(char)
|
||||||
|
if !caseSensitive && class == charUpper {
|
||||||
|
char = unicode.To(unicode.LowerCase, char)
|
||||||
|
}
|
||||||
|
if normalize {
|
||||||
|
char = normalizeRune(char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Tsub[off] = char
|
||||||
|
bonus := bonusFor(prevClass, class)
|
||||||
|
Bsub[off] = bonus
|
||||||
|
prevClass = class
|
||||||
|
|
||||||
|
if char == pchar {
|
||||||
|
if pidx < M {
|
||||||
|
F[pidx] = int32(idx + off)
|
||||||
|
pidx++
|
||||||
|
pchar = pattern[util.Min(pidx, M-1)]
|
||||||
|
}
|
||||||
|
lastIdx = idx + off
|
||||||
|
}
|
||||||
|
|
||||||
|
if char == pchar0 {
|
||||||
|
score := scoreMatch + bonus*bonusFirstCharMultiplier
|
||||||
|
H0sub[off] = score
|
||||||
|
C0sub[off] = 1
|
||||||
|
if M == 1 && (forward && score > maxScore || !forward && score >= maxScore) {
|
||||||
|
maxScore, maxScorePos = score, idx+off
|
||||||
|
if forward && bonus == bonusBoundary {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inGap = false
|
||||||
|
} else {
|
||||||
|
if inGap {
|
||||||
|
H0sub[off] = util.Max16(prevH0+scoreGapExtention, 0)
|
||||||
|
} else {
|
||||||
|
H0sub[off] = util.Max16(prevH0+scoreGapStart, 0)
|
||||||
|
}
|
||||||
|
C0sub[off] = 0
|
||||||
|
inGap = true
|
||||||
|
}
|
||||||
|
prevH0 = H0sub[off]
|
||||||
|
}
|
||||||
|
if pidx != M {
|
||||||
|
return Result{-1, -1, 0}, nil
|
||||||
|
}
|
||||||
|
if M == 1 {
|
||||||
|
result := Result{maxScorePos, maxScorePos + 1, int(maxScore)}
|
||||||
|
if !withPos {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
pos := []int{maxScorePos}
|
||||||
|
return result, &pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 3. Fill in score matrix (H)
|
||||||
|
// Unlike the original algorithm, we do not allow omission.
|
||||||
|
f0 := int(F[0])
|
||||||
|
width := lastIdx - f0 + 1
|
||||||
|
offset16, H := alloc16(offset16, slab, width*M)
|
||||||
|
copy(H, H0[f0:lastIdx+1])
|
||||||
|
|
||||||
|
// Possible length of consecutive chunk at each position.
|
||||||
|
_, C := alloc16(offset16, slab, width*M)
|
||||||
|
copy(C, C0[f0:lastIdx+1])
|
||||||
|
|
||||||
|
Fsub := F[1:]
|
||||||
|
Psub := pattern[1:][:len(Fsub)]
|
||||||
|
for off, f := range Fsub {
|
||||||
|
f := int(f)
|
||||||
|
pchar := Psub[off]
|
||||||
|
pidx := off + 1
|
||||||
|
row := pidx * width
|
||||||
|
inGap := false
|
||||||
|
Tsub := T[f : lastIdx+1]
|
||||||
|
Bsub := B[f:][:len(Tsub)]
|
||||||
|
Csub := C[row+f-f0:][:len(Tsub)]
|
||||||
|
Cdiag := C[row+f-f0-1-width:][:len(Tsub)]
|
||||||
|
Hsub := H[row+f-f0:][:len(Tsub)]
|
||||||
|
Hdiag := H[row+f-f0-1-width:][:len(Tsub)]
|
||||||
|
Hleft := H[row+f-f0-1:][:len(Tsub)]
|
||||||
|
Hleft[0] = 0
|
||||||
|
for off, char := range Tsub {
|
||||||
|
col := off + f
|
||||||
|
var s1, s2, consecutive int16
|
||||||
|
|
||||||
|
if inGap {
|
||||||
|
s2 = Hleft[off] + scoreGapExtention
|
||||||
|
} else {
|
||||||
|
s2 = Hleft[off] + scoreGapStart
|
||||||
|
}
|
||||||
|
|
||||||
|
if pchar == char {
|
||||||
|
s1 = Hdiag[off] + scoreMatch
|
||||||
|
b := Bsub[off]
|
||||||
|
consecutive = Cdiag[off] + 1
|
||||||
|
// Break consecutive chunk
|
||||||
|
if b == bonusBoundary {
|
||||||
|
consecutive = 1
|
||||||
|
} else if consecutive > 1 {
|
||||||
|
b = util.Max16(b, util.Max16(bonusConsecutive, B[col-int(consecutive)+1]))
|
||||||
|
}
|
||||||
|
if s1+b < s2 {
|
||||||
|
s1 += Bsub[off]
|
||||||
|
consecutive = 0
|
||||||
|
} else {
|
||||||
|
s1 += b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Csub[off] = consecutive
|
||||||
|
|
||||||
|
inGap = s1 < s2
|
||||||
|
score := util.Max16(util.Max16(s1, s2), 0)
|
||||||
|
if pidx == M-1 && (forward && score > maxScore || !forward && score >= maxScore) {
|
||||||
|
maxScore, maxScorePos = score, col
|
||||||
|
}
|
||||||
|
Hsub[off] = score
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if DEBUG {
|
||||||
|
debugV2(T, pattern, F, lastIdx, H, C)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 4. (Optional) Backtrace to find character positions
|
||||||
pos := posArray(withPos, M)
|
pos := posArray(withPos, M)
|
||||||
j := int(F[0])
|
j := f0
|
||||||
if withPos {
|
if withPos {
|
||||||
i := M - 1
|
i := M - 1
|
||||||
j = maxScorePos
|
j = maxScorePos
|
||||||
preferMatch := true
|
preferMatch := true
|
||||||
for {
|
for {
|
||||||
I := i * width
|
I := i * width
|
||||||
j0 := j - int(F[0])
|
j0 := j - f0
|
||||||
s := H[I+j0]
|
s := H[I+j0]
|
||||||
|
|
||||||
var s1, s2 int16
|
var s1, s2 int16
|
||||||
@@ -451,7 +552,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input util.C
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Implement the same sorting criteria as V2
|
// Implement the same sorting criteria as V2
|
||||||
func calculateScore(caseSensitive bool, normalize bool, text util.Chars, pattern []rune, sidx int, eidx int, withPos bool) (int, *[]int) {
|
func calculateScore(caseSensitive bool, normalize bool, text *util.Chars, pattern []rune, sidx int, eidx int, withPos bool) (int, *[]int) {
|
||||||
pidx, score, inGap, consecutive, firstBonus := 0, 0, false, 0, int16(0)
|
pidx, score, inGap, consecutive, firstBonus := 0, 0, false, 0, int16(0)
|
||||||
pos := posArray(withPos, len(pattern))
|
pos := posArray(withPos, len(pattern))
|
||||||
prevClass := charNonWord
|
prevClass := charNonWord
|
||||||
@@ -511,10 +612,13 @@ func calculateScore(caseSensitive bool, normalize bool, text util.Chars, pattern
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FuzzyMatchV1 performs fuzzy-match
|
// FuzzyMatchV1 performs fuzzy-match
|
||||||
func FuzzyMatchV1(caseSensitive bool, normalize bool, forward bool, text util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
func FuzzyMatchV1(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
if len(pattern) == 0 {
|
if len(pattern) == 0 {
|
||||||
return Result{0, 0, 0}, nil
|
return Result{0, 0, 0}, nil
|
||||||
}
|
}
|
||||||
|
if asciiFuzzyIndex(text, pattern, caseSensitive) < 0 {
|
||||||
|
return Result{-1, -1, 0}, nil
|
||||||
|
}
|
||||||
|
|
||||||
pidx := 0
|
pidx := 0
|
||||||
sidx := -1
|
sidx := -1
|
||||||
@@ -594,7 +698,7 @@ func FuzzyMatchV1(caseSensitive bool, normalize bool, forward bool, text util.Ch
|
|||||||
// bonus point, instead of stopping immediately after finding the first match.
|
// bonus point, instead of stopping immediately after finding the first match.
|
||||||
// The solution is much cheaper since there is only one possible alignment of
|
// The solution is much cheaper since there is only one possible alignment of
|
||||||
// the pattern.
|
// the pattern.
|
||||||
func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
if len(pattern) == 0 {
|
if len(pattern) == 0 {
|
||||||
return Result{0, 0, 0}, nil
|
return Result{0, 0, 0}, nil
|
||||||
}
|
}
|
||||||
@@ -606,6 +710,10 @@ func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text util
|
|||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if asciiFuzzyIndex(text, pattern, caseSensitive) < 0 {
|
||||||
|
return Result{-1, -1, 0}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// For simplicity, only look at the bonus at the first character position
|
// For simplicity, only look at the bonus at the first character position
|
||||||
pidx := 0
|
pidx := 0
|
||||||
bestPos, bonus, bestBonus := -1, int16(0), int16(-1)
|
bestPos, bonus, bestBonus := -1, int16(0), int16(-1)
|
||||||
@@ -660,7 +768,7 @@ func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text util
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PrefixMatch performs prefix-match
|
// PrefixMatch performs prefix-match
|
||||||
func PrefixMatch(caseSensitive bool, normalize bool, forward bool, text util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
func PrefixMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
if len(pattern) == 0 {
|
if len(pattern) == 0 {
|
||||||
return Result{0, 0, 0}, nil
|
return Result{0, 0, 0}, nil
|
||||||
}
|
}
|
||||||
@@ -687,7 +795,7 @@ func PrefixMatch(caseSensitive bool, normalize bool, forward bool, text util.Cha
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SuffixMatch performs suffix-match
|
// SuffixMatch performs suffix-match
|
||||||
func SuffixMatch(caseSensitive bool, normalize bool, forward bool, text util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
func SuffixMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
lenRunes := text.Length()
|
lenRunes := text.Length()
|
||||||
trimmedLen := lenRunes - text.TrailingWhitespaces()
|
trimmedLen := lenRunes - text.TrailingWhitespaces()
|
||||||
if len(pattern) == 0 {
|
if len(pattern) == 0 {
|
||||||
@@ -718,7 +826,7 @@ func SuffixMatch(caseSensitive bool, normalize bool, forward bool, text util.Cha
|
|||||||
}
|
}
|
||||||
|
|
||||||
// EqualMatch performs equal-match
|
// EqualMatch performs equal-match
|
||||||
func EqualMatch(caseSensitive bool, normalize bool, forward bool, text util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
func EqualMatch(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
lenPattern := len(pattern)
|
lenPattern := len(pattern)
|
||||||
if text.Length() != lenPattern {
|
if text.Length() != lenPattern {
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ func assertMatch2(t *testing.T, fun Algo, caseSensitive, normalize, forward bool
|
|||||||
if !caseSensitive {
|
if !caseSensitive {
|
||||||
pattern = strings.ToLower(pattern)
|
pattern = strings.ToLower(pattern)
|
||||||
}
|
}
|
||||||
res, pos := fun(caseSensitive, normalize, forward, util.RunesToChars([]rune(input)), []rune(pattern), true, nil)
|
chars := util.ToChars([]byte(input))
|
||||||
|
res, pos := fun(caseSensitive, normalize, forward, &chars, []rune(pattern), true, nil)
|
||||||
var start, end int
|
var start, end int
|
||||||
if pos == nil || len(*pos) == 0 {
|
if pos == nil || len(*pos) == 0 {
|
||||||
start = res.Start
|
start = res.Start
|
||||||
|
|||||||
125
src/ansi.go
125
src/ansi.go
@@ -32,6 +32,55 @@ func (s *ansiState) equals(t *ansiState) bool {
|
|||||||
return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr
|
return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ansiState) ToString() string {
|
||||||
|
if !s.colored() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := ""
|
||||||
|
if s.attr&tui.Bold > 0 {
|
||||||
|
ret += "1;"
|
||||||
|
}
|
||||||
|
if s.attr&tui.Dim > 0 {
|
||||||
|
ret += "2;"
|
||||||
|
}
|
||||||
|
if s.attr&tui.Italic > 0 {
|
||||||
|
ret += "3;"
|
||||||
|
}
|
||||||
|
if s.attr&tui.Underline > 0 {
|
||||||
|
ret += "4;"
|
||||||
|
}
|
||||||
|
if s.attr&tui.Blink > 0 {
|
||||||
|
ret += "5;"
|
||||||
|
}
|
||||||
|
if s.attr&tui.Reverse > 0 {
|
||||||
|
ret += "7;"
|
||||||
|
}
|
||||||
|
ret += toAnsiString(s.fg, 30) + toAnsiString(s.bg, 40)
|
||||||
|
|
||||||
|
return "\x1b[" + strings.TrimSuffix(ret, ";") + "m"
|
||||||
|
}
|
||||||
|
|
||||||
|
func toAnsiString(color tui.Color, offset int) string {
|
||||||
|
col := int(color)
|
||||||
|
ret := ""
|
||||||
|
if col == -1 {
|
||||||
|
ret += strconv.Itoa(offset + 9)
|
||||||
|
} else if col < 8 {
|
||||||
|
ret += strconv.Itoa(offset + col)
|
||||||
|
} else if col < 16 {
|
||||||
|
ret += strconv.Itoa(offset - 30 + 90 + col - 8)
|
||||||
|
} else if col < 256 {
|
||||||
|
ret += strconv.Itoa(offset+8) + ";5;" + strconv.Itoa(col)
|
||||||
|
} else if col >= (1 << 24) {
|
||||||
|
r := strconv.Itoa((col >> 16) & 0xff)
|
||||||
|
g := strconv.Itoa((col >> 8) & 0xff)
|
||||||
|
b := strconv.Itoa(col & 0xff)
|
||||||
|
ret += strconv.Itoa(offset+8) + ";2;" + r + ";" + g + ";" + b
|
||||||
|
}
|
||||||
|
return ret + ";"
|
||||||
|
}
|
||||||
|
|
||||||
var ansiRegex *regexp.Regexp
|
var ansiRegex *regexp.Regexp
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -44,7 +93,21 @@ func init() {
|
|||||||
*/
|
*/
|
||||||
// The following regular expression will include not all but most of the
|
// The following regular expression will include not all but most of the
|
||||||
// frequently used ANSI sequences
|
// frequently used ANSI sequences
|
||||||
ansiRegex = regexp.MustCompile("\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b.|[\x0e\x0f]|.\x08")
|
ansiRegex = regexp.MustCompile("(?:\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b.|[\x0e\x0f]|.\x08)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func findAnsiStart(str string) int {
|
||||||
|
idx := 0
|
||||||
|
for ; idx < len(str); idx++ {
|
||||||
|
b := str[idx]
|
||||||
|
if b == 0x1b || b == 0x0e || b == 0x0f {
|
||||||
|
return idx
|
||||||
|
}
|
||||||
|
if b == 0x08 && idx > 0 {
|
||||||
|
return idx - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return idx
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractColor(str string, state *ansiState, proc func(string, *ansiState) bool) (string, *[]ansiOffset, *ansiState) {
|
func extractColor(str string, state *ansiState, proc func(string, *ansiState) bool) (string, *[]ansiOffset, *ansiState) {
|
||||||
@@ -55,50 +118,76 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
|
|||||||
offsets = append(offsets, ansiOffset{[2]int32{0, 0}, *state})
|
offsets = append(offsets, ansiOffset{[2]int32{0, 0}, *state})
|
||||||
}
|
}
|
||||||
|
|
||||||
idx := 0
|
prevIdx := 0
|
||||||
for _, offset := range ansiRegex.FindAllStringIndex(str, -1) {
|
runeCount := 0
|
||||||
prev := str[idx:offset[0]]
|
for idx := 0; idx < len(str); {
|
||||||
output.WriteString(prev)
|
idx += findAnsiStart(str[idx:])
|
||||||
|
if idx == len(str) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure that we found an ANSI code
|
||||||
|
offset := ansiRegex.FindStringIndex(str[idx:])
|
||||||
|
if len(offset) < 2 {
|
||||||
|
idx++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
offset[0] += idx
|
||||||
|
offset[1] += idx
|
||||||
|
idx = offset[1]
|
||||||
|
|
||||||
|
// Check if we should continue
|
||||||
|
prev := str[prevIdx:offset[0]]
|
||||||
if proc != nil && !proc(prev, state) {
|
if proc != nil && !proc(prev, state) {
|
||||||
return "", nil, nil
|
return "", nil, nil
|
||||||
}
|
}
|
||||||
newState := interpretCode(str[offset[0]:offset[1]], state)
|
|
||||||
|
|
||||||
|
prevIdx = offset[1]
|
||||||
|
runeCount += utf8.RuneCountInString(prev)
|
||||||
|
output.WriteString(prev)
|
||||||
|
|
||||||
|
newState := interpretCode(str[offset[0]:offset[1]], state)
|
||||||
if !newState.equals(state) {
|
if !newState.equals(state) {
|
||||||
if state != nil {
|
if state != nil {
|
||||||
// Update last offset
|
// Update last offset
|
||||||
(&offsets[len(offsets)-1]).offset[1] = int32(utf8.RuneCount(output.Bytes()))
|
(&offsets[len(offsets)-1]).offset[1] = int32(runeCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
if newState.colored() {
|
if newState.colored() {
|
||||||
// Append new offset
|
// Append new offset
|
||||||
state = newState
|
state = newState
|
||||||
newLen := int32(utf8.RuneCount(output.Bytes()))
|
offsets = append(offsets, ansiOffset{[2]int32{int32(runeCount), int32(runeCount)}, *state})
|
||||||
offsets = append(offsets, ansiOffset{[2]int32{newLen, newLen}, *state})
|
|
||||||
} else {
|
} else {
|
||||||
// Discard state
|
// Discard state
|
||||||
state = nil
|
state = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
idx = offset[1]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rest := str[idx:]
|
var rest string
|
||||||
if len(rest) > 0 {
|
var trimmed string
|
||||||
|
|
||||||
|
if prevIdx == 0 {
|
||||||
|
// No ANSI code found
|
||||||
|
rest = str
|
||||||
|
trimmed = str
|
||||||
|
} else {
|
||||||
|
rest = str[prevIdx:]
|
||||||
output.WriteString(rest)
|
output.WriteString(rest)
|
||||||
if state != nil {
|
trimmed = output.String()
|
||||||
// Update last offset
|
|
||||||
(&offsets[len(offsets)-1]).offset[1] = int32(utf8.RuneCount(output.Bytes()))
|
|
||||||
}
|
}
|
||||||
|
if len(rest) > 0 && state != nil {
|
||||||
|
// Update last offset
|
||||||
|
runeCount += utf8.RuneCountInString(rest)
|
||||||
|
(&offsets[len(offsets)-1]).offset[1] = int32(runeCount)
|
||||||
}
|
}
|
||||||
if proc != nil {
|
if proc != nil {
|
||||||
proc(rest, state)
|
proc(rest, state)
|
||||||
}
|
}
|
||||||
if len(offsets) == 0 {
|
if len(offsets) == 0 {
|
||||||
return output.String(), nil, state
|
return trimmed, nil, state
|
||||||
}
|
}
|
||||||
return output.String(), &offsets, state
|
return trimmed, &offsets, state
|
||||||
}
|
}
|
||||||
|
|
||||||
func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package fzf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/tui"
|
"github.com/junegunn/fzf/src/tui"
|
||||||
@@ -26,7 +27,7 @@ func TestExtractColor(t *testing.T) {
|
|||||||
output, ansiOffsets, newState := extractColor(src, state, nil)
|
output, ansiOffsets, newState := extractColor(src, state, nil)
|
||||||
state = newState
|
state = newState
|
||||||
if output != "hello world" {
|
if output != "hello world" {
|
||||||
t.Errorf("Invalid output: %s %s", output, []rune(output))
|
t.Errorf("Invalid output: %s %v", output, []rune(output))
|
||||||
}
|
}
|
||||||
fmt.Println(src, ansiOffsets, clean)
|
fmt.Println(src, ansiOffsets, clean)
|
||||||
assertion(ansiOffsets, state)
|
assertion(ansiOffsets, state)
|
||||||
@@ -156,3 +157,31 @@ func TestExtractColor(t *testing.T) {
|
|||||||
assert((*offsets)[1], 6, 11, 200, 100, false)
|
assert((*offsets)[1], 6, 11, 200, 100, false)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAnsiCodeStringConversion(t *testing.T) {
|
||||||
|
assert := func(code string, prevState *ansiState, expected string) {
|
||||||
|
state := interpretCode(code, prevState)
|
||||||
|
if expected != state.ToString() {
|
||||||
|
t.Errorf("expected: %s, actual: %s",
|
||||||
|
strings.Replace(expected, "\x1b[", "\\x1b[", -1),
|
||||||
|
strings.Replace(state.ToString(), "\x1b[", "\\x1b[", -1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert("\x1b[m", nil, "")
|
||||||
|
assert("\x1b[m", &ansiState{attr: tui.Blink}, "")
|
||||||
|
|
||||||
|
assert("\x1b[31m", nil, "\x1b[31;49m")
|
||||||
|
assert("\x1b[41m", nil, "\x1b[39;41m")
|
||||||
|
|
||||||
|
assert("\x1b[92m", nil, "\x1b[92;49m")
|
||||||
|
assert("\x1b[102m", nil, "\x1b[39;102m")
|
||||||
|
|
||||||
|
assert("\x1b[31m", &ansiState{fg: 4, bg: 4}, "\x1b[31;44m")
|
||||||
|
assert("\x1b[1;2;31m", &ansiState{fg: 2, bg: -1, attr: tui.Reverse}, "\x1b[1;2;7;31;49m")
|
||||||
|
assert("\x1b[38;5;100;48;5;200m", nil, "\x1b[38;5;100;48;5;200m")
|
||||||
|
assert("\x1b[48;5;100;38;5;200m", nil, "\x1b[38;5;200;48;5;100m")
|
||||||
|
assert("\x1b[48;5;100;38;2;10;20;30;1m", nil, "\x1b[1;38;2;10;20;30;48;5;100m")
|
||||||
|
assert("\x1b[48;5;100;38;2;10;20;30;7m",
|
||||||
|
&ansiState{attr: tui.Dim | tui.Italic, fg: 1, bg: 1},
|
||||||
|
"\x1b[2;3;7;38;2;10;20;30;48;5;100m")
|
||||||
|
}
|
||||||
|
|||||||
42
src/cache.go
42
src/cache.go
@@ -3,7 +3,7 @@ package fzf
|
|||||||
import "sync"
|
import "sync"
|
||||||
|
|
||||||
// queryCache associates strings to lists of items
|
// queryCache associates strings to lists of items
|
||||||
type queryCache map[string][]*Result
|
type queryCache map[string][]Result
|
||||||
|
|
||||||
// ChunkCache associates Chunk and query string to lists of items
|
// ChunkCache associates Chunk and query string to lists of items
|
||||||
type ChunkCache struct {
|
type ChunkCache struct {
|
||||||
@@ -17,7 +17,7 @@ func NewChunkCache() ChunkCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add adds the list to the cache
|
// Add adds the list to the cache
|
||||||
func (cc *ChunkCache) Add(chunk *Chunk, key string, list []*Result) {
|
func (cc *ChunkCache) Add(chunk *Chunk, key string, list []Result) {
|
||||||
if len(key) == 0 || !chunk.IsFull() || len(list) > queryCacheMax {
|
if len(key) == 0 || !chunk.IsFull() || len(list) > queryCacheMax {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -33,10 +33,10 @@ func (cc *ChunkCache) Add(chunk *Chunk, key string, list []*Result) {
|
|||||||
(*qc)[key] = list
|
(*qc)[key] = list
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find is called to lookup ChunkCache
|
// Lookup is called to lookup ChunkCache
|
||||||
func (cc *ChunkCache) Find(chunk *Chunk, key string) ([]*Result, bool) {
|
func (cc *ChunkCache) Lookup(chunk *Chunk, key string) []Result {
|
||||||
if len(key) == 0 || !chunk.IsFull() {
|
if len(key) == 0 || !chunk.IsFull() {
|
||||||
return nil, false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cc.mutex.Lock()
|
cc.mutex.Lock()
|
||||||
@@ -46,8 +46,36 @@ func (cc *ChunkCache) Find(chunk *Chunk, key string) ([]*Result, bool) {
|
|||||||
if ok {
|
if ok {
|
||||||
list, ok := (*qc)[key]
|
list, ok := (*qc)[key]
|
||||||
if ok {
|
if ok {
|
||||||
return list, true
|
return list
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, false
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *ChunkCache) Search(chunk *Chunk, key string) []Result {
|
||||||
|
if len(key) == 0 || !chunk.IsFull() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cc.mutex.Lock()
|
||||||
|
defer cc.mutex.Unlock()
|
||||||
|
|
||||||
|
qc, ok := cc.cache[chunk]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx := 1; idx < len(key); idx++ {
|
||||||
|
// [---------| ] | [ |---------]
|
||||||
|
// [--------| ] | [ |--------]
|
||||||
|
// [-------| ] | [ |-------]
|
||||||
|
prefix := key[:len(key)-idx]
|
||||||
|
suffix := key[idx:]
|
||||||
|
for _, substr := range [2]string{prefix, suffix} {
|
||||||
|
if cached, found := (*qc)[substr]; found {
|
||||||
|
return cached
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,37 +4,36 @@ import "testing"
|
|||||||
|
|
||||||
func TestChunkCache(t *testing.T) {
|
func TestChunkCache(t *testing.T) {
|
||||||
cache := NewChunkCache()
|
cache := NewChunkCache()
|
||||||
chunk2 := make(Chunk, chunkSize)
|
|
||||||
chunk1p := &Chunk{}
|
chunk1p := &Chunk{}
|
||||||
chunk2p := &chunk2
|
chunk2p := &Chunk{count: chunkSize}
|
||||||
items1 := []*Result{&Result{}}
|
items1 := []Result{Result{}}
|
||||||
items2 := []*Result{&Result{}, &Result{}}
|
items2 := []Result{Result{}, Result{}}
|
||||||
cache.Add(chunk1p, "foo", items1)
|
cache.Add(chunk1p, "foo", items1)
|
||||||
cache.Add(chunk2p, "foo", items1)
|
cache.Add(chunk2p, "foo", items1)
|
||||||
cache.Add(chunk2p, "bar", items2)
|
cache.Add(chunk2p, "bar", items2)
|
||||||
|
|
||||||
{ // chunk1 is not full
|
{ // chunk1 is not full
|
||||||
cached, found := cache.Find(chunk1p, "foo")
|
cached := cache.Lookup(chunk1p, "foo")
|
||||||
if found {
|
if cached != nil {
|
||||||
t.Error("Cached disabled for non-empty chunks", found, cached)
|
t.Error("Cached disabled for non-empty chunks", cached)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
cached, found := cache.Find(chunk2p, "foo")
|
cached := cache.Lookup(chunk2p, "foo")
|
||||||
if !found || len(cached) != 1 {
|
if cached == nil || len(cached) != 1 {
|
||||||
t.Error("Expected 1 item cached", found, cached)
|
t.Error("Expected 1 item cached", cached)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
cached, found := cache.Find(chunk2p, "bar")
|
cached := cache.Lookup(chunk2p, "bar")
|
||||||
if !found || len(cached) != 2 {
|
if cached == nil || len(cached) != 2 {
|
||||||
t.Error("Expected 2 items cached", found, cached)
|
t.Error("Expected 2 items cached", cached)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
cached, found := cache.Find(chunk1p, "foobar")
|
cached := cache.Lookup(chunk1p, "foobar")
|
||||||
if found {
|
if cached != nil {
|
||||||
t.Error("Expected 0 item cached", found, cached)
|
t.Error("Expected 0 item cached", cached)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,18 @@ package fzf
|
|||||||
|
|
||||||
import "sync"
|
import "sync"
|
||||||
|
|
||||||
// Chunk is a list of Item pointers whose size has the upper limit of chunkSize
|
// Chunk is a list of Items whose size has the upper limit of chunkSize
|
||||||
type Chunk []*Item // >>> []Item
|
type Chunk struct {
|
||||||
|
items [chunkSize]Item
|
||||||
|
count int
|
||||||
|
}
|
||||||
|
|
||||||
// ItemBuilder is a closure type that builds Item object from a pointer to a
|
// ItemBuilder is a closure type that builds Item object from byte array
|
||||||
// string and an integer
|
type ItemBuilder func(*Item, []byte) bool
|
||||||
type ItemBuilder func([]byte, int) *Item
|
|
||||||
|
|
||||||
// ChunkList is a list of Chunks
|
// ChunkList is a list of Chunks
|
||||||
type ChunkList struct {
|
type ChunkList struct {
|
||||||
chunks []*Chunk
|
chunks []*Chunk
|
||||||
count int
|
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
trans ItemBuilder
|
trans ItemBuilder
|
||||||
}
|
}
|
||||||
@@ -21,15 +22,13 @@ type ChunkList struct {
|
|||||||
func NewChunkList(trans ItemBuilder) *ChunkList {
|
func NewChunkList(trans ItemBuilder) *ChunkList {
|
||||||
return &ChunkList{
|
return &ChunkList{
|
||||||
chunks: []*Chunk{},
|
chunks: []*Chunk{},
|
||||||
count: 0,
|
|
||||||
mutex: sync.Mutex{},
|
mutex: sync.Mutex{},
|
||||||
trans: trans}
|
trans: trans}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Chunk) push(trans ItemBuilder, data []byte, index int) bool {
|
func (c *Chunk) push(trans ItemBuilder, data []byte) bool {
|
||||||
item := trans(data, index)
|
if trans(&c.items[c.count], data) {
|
||||||
if item != nil {
|
c.count++
|
||||||
*c = append(*c, item)
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -37,7 +36,7 @@ func (c *Chunk) push(trans ItemBuilder, data []byte, index int) bool {
|
|||||||
|
|
||||||
// IsFull returns true if the Chunk is full
|
// IsFull returns true if the Chunk is full
|
||||||
func (c *Chunk) IsFull() bool {
|
func (c *Chunk) IsFull() bool {
|
||||||
return len(*c) == chunkSize
|
return c.count == chunkSize
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cl *ChunkList) lastChunk() *Chunk {
|
func (cl *ChunkList) lastChunk() *Chunk {
|
||||||
@@ -49,45 +48,42 @@ func CountItems(cs []*Chunk) int {
|
|||||||
if len(cs) == 0 {
|
if len(cs) == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return chunkSize*(len(cs)-1) + len(*(cs[len(cs)-1]))
|
return chunkSize*(len(cs)-1) + cs[len(cs)-1].count
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push adds the item to the list
|
// Push adds the item to the list
|
||||||
func (cl *ChunkList) Push(data []byte) bool {
|
func (cl *ChunkList) Push(data []byte) bool {
|
||||||
cl.mutex.Lock()
|
cl.mutex.Lock()
|
||||||
defer cl.mutex.Unlock()
|
|
||||||
|
|
||||||
if len(cl.chunks) == 0 || cl.lastChunk().IsFull() {
|
if len(cl.chunks) == 0 || cl.lastChunk().IsFull() {
|
||||||
newChunk := Chunk(make([]*Item, 0, chunkSize))
|
cl.chunks = append(cl.chunks, &Chunk{})
|
||||||
cl.chunks = append(cl.chunks, &newChunk)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cl.lastChunk().push(cl.trans, data, cl.count) {
|
ret := cl.lastChunk().push(cl.trans, data)
|
||||||
cl.count++
|
cl.mutex.Unlock()
|
||||||
return true
|
return ret
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
|
// Clear clears the data
|
||||||
|
func (cl *ChunkList) Clear() {
|
||||||
|
cl.mutex.Lock()
|
||||||
|
cl.chunks = nil
|
||||||
|
cl.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Snapshot returns immutable snapshot of the ChunkList
|
// Snapshot returns immutable snapshot of the ChunkList
|
||||||
func (cl *ChunkList) Snapshot() ([]*Chunk, int) {
|
func (cl *ChunkList) Snapshot() ([]*Chunk, int) {
|
||||||
cl.mutex.Lock()
|
cl.mutex.Lock()
|
||||||
defer cl.mutex.Unlock()
|
|
||||||
|
|
||||||
ret := make([]*Chunk, len(cl.chunks))
|
ret := make([]*Chunk, len(cl.chunks))
|
||||||
copy(ret, cl.chunks)
|
copy(ret, cl.chunks)
|
||||||
|
|
||||||
// Duplicate the last chunk
|
// Duplicate the last chunk
|
||||||
if cnt := len(ret); cnt > 0 {
|
if cnt := len(ret); cnt > 0 {
|
||||||
ret[cnt-1] = ret[cnt-1].dupe()
|
newChunk := *ret[cnt-1]
|
||||||
|
ret[cnt-1] = &newChunk
|
||||||
}
|
}
|
||||||
return ret, cl.count
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Chunk) dupe() *Chunk {
|
cl.mutex.Unlock()
|
||||||
newChunk := make(Chunk, len(*c))
|
return ret, CountItems(ret)
|
||||||
for idx, ptr := range *c {
|
|
||||||
newChunk[idx] = ptr
|
|
||||||
}
|
|
||||||
return &newChunk
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ func TestChunkList(t *testing.T) {
|
|||||||
// FIXME global
|
// FIXME global
|
||||||
sortCriteria = []criterion{byScore, byLength}
|
sortCriteria = []criterion{byScore, byLength}
|
||||||
|
|
||||||
cl := NewChunkList(func(s []byte, i int) *Item {
|
cl := NewChunkList(func(item *Item, s []byte) bool {
|
||||||
return &Item{text: util.ToChars(s), index: int32(i * 2)}
|
item.text = util.ToChars(s)
|
||||||
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
// Snapshot
|
// Snapshot
|
||||||
@@ -38,11 +39,11 @@ func TestChunkList(t *testing.T) {
|
|||||||
|
|
||||||
// Check the content of the ChunkList
|
// Check the content of the ChunkList
|
||||||
chunk1 := snapshot[0]
|
chunk1 := snapshot[0]
|
||||||
if len(*chunk1) != 2 {
|
if chunk1.count != 2 {
|
||||||
t.Error("Snapshot should contain only two items")
|
t.Error("Snapshot should contain only two items")
|
||||||
}
|
}
|
||||||
if (*chunk1)[0].text.ToString() != "hello" || (*chunk1)[0].index != 0 ||
|
if chunk1.items[0].text.ToString() != "hello" ||
|
||||||
(*chunk1)[1].text.ToString() != "world" || (*chunk1)[1].index != 2 {
|
chunk1.items[1].text.ToString() != "world" {
|
||||||
t.Error("Invalid data")
|
t.Error("Invalid data")
|
||||||
}
|
}
|
||||||
if chunk1.IsFull() {
|
if chunk1.IsFull() {
|
||||||
@@ -65,14 +66,14 @@ func TestChunkList(t *testing.T) {
|
|||||||
!snapshot[1].IsFull() || snapshot[2].IsFull() || count != chunkSize*2+2 {
|
!snapshot[1].IsFull() || snapshot[2].IsFull() || count != chunkSize*2+2 {
|
||||||
t.Error("Expected two full chunks and one more chunk")
|
t.Error("Expected two full chunks and one more chunk")
|
||||||
}
|
}
|
||||||
if len(*snapshot[2]) != 2 {
|
if snapshot[2].count != 2 {
|
||||||
t.Error("Unexpected number of items")
|
t.Error("Unexpected number of items")
|
||||||
}
|
}
|
||||||
|
|
||||||
cl.Push([]byte("hello"))
|
cl.Push([]byte("hello"))
|
||||||
cl.Push([]byte("world"))
|
cl.Push([]byte("world"))
|
||||||
|
|
||||||
lastChunkCount := len(*snapshot[len(snapshot)-1])
|
lastChunkCount := snapshot[len(snapshot)-1].count
|
||||||
if lastChunkCount != 2 {
|
if lastChunkCount != 2 {
|
||||||
t.Error("Unexpected number of items:", lastChunkCount)
|
t.Error("Unexpected number of items:", lastChunkCount)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
@@ -8,7 +10,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// Current version
|
// Current version
|
||||||
version = "0.16.4"
|
version = "0.20.0"
|
||||||
|
|
||||||
// Core
|
// Core
|
||||||
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
||||||
@@ -16,12 +18,17 @@ const (
|
|||||||
|
|
||||||
// Reader
|
// Reader
|
||||||
readerBufferSize = 64 * 1024
|
readerBufferSize = 64 * 1024
|
||||||
|
readerPollIntervalMin = 10 * time.Millisecond
|
||||||
|
readerPollIntervalStep = 5 * time.Millisecond
|
||||||
|
readerPollIntervalMax = 50 * time.Millisecond
|
||||||
|
|
||||||
// Terminal
|
// Terminal
|
||||||
initialDelay = 20 * time.Millisecond
|
initialDelay = 20 * time.Millisecond
|
||||||
initialDelayTac = 100 * time.Millisecond
|
initialDelayTac = 100 * time.Millisecond
|
||||||
spinnerDuration = 200 * time.Millisecond
|
spinnerDuration = 200 * time.Millisecond
|
||||||
maxPatternLength = 100
|
previewCancelWait = 500 * time.Millisecond
|
||||||
|
maxPatternLength = 300
|
||||||
|
maxMulti = math.MaxInt32
|
||||||
|
|
||||||
// Matcher
|
// Matcher
|
||||||
numPartitionsMultiplier = 8
|
numPartitionsMultiplier = 8
|
||||||
@@ -48,6 +55,18 @@ const (
|
|||||||
defaultJumpLabels string = "asdfghjklqwertyuiopzxcvbnm1234567890ASDFGHJKLQWERTYUIOPZXCVBNM`~;:,<.>/?'\"!@#$%^&*()[{]}-_=+"
|
defaultJumpLabels string = "asdfghjklqwertyuiopzxcvbnm1234567890ASDFGHJKLQWERTYUIOPZXCVBNM`~;:,<.>/?'\"!@#$%^&*()[{]}-_=+"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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 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 = `for /r %P in (*) do @(set "_curfile=%P" & set "_curfile=!_curfile:%__CD__%=!" & echo !_curfile!)`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// fzf events
|
// fzf events
|
||||||
const (
|
const (
|
||||||
EvtReadNew util.EventType = iota
|
EvtReadNew util.EventType = iota
|
||||||
@@ -56,10 +75,11 @@ const (
|
|||||||
EvtSearchProgress
|
EvtSearchProgress
|
||||||
EvtSearchFin
|
EvtSearchFin
|
||||||
EvtHeader
|
EvtHeader
|
||||||
EvtClose
|
EvtReady
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
exitCancel = -1
|
||||||
exitOk = 0
|
exitOk = 0
|
||||||
exitNoMatch = 1
|
exitNoMatch = 1
|
||||||
exitError = 2
|
exitError = 2
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
// +build !windows
|
|
||||||
|
|
||||||
package fzf
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Reader
|
|
||||||
defaultCommand = `find -L . -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | sed s/^..//`
|
|
||||||
)
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
// +build windows
|
|
||||||
|
|
||||||
package fzf
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Reader
|
|
||||||
defaultCommand = `dir /s/b`
|
|
||||||
)
|
|
||||||
160
src/core.go
160
src/core.go
@@ -43,12 +43,16 @@ Matcher -> EvtHeader -> Terminal (update header)
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Run starts fzf
|
// Run starts fzf
|
||||||
func Run(opts *Options) {
|
func Run(opts *Options, revision string) {
|
||||||
sort := opts.Sort > 0
|
sort := opts.Sort > 0
|
||||||
sortCriteria = opts.Criteria
|
sortCriteria = opts.Criteria
|
||||||
|
|
||||||
if opts.Version {
|
if opts.Version {
|
||||||
|
if len(revision) > 0 {
|
||||||
|
fmt.Printf("%s (%s)\n", version, revision)
|
||||||
|
} else {
|
||||||
fmt.Println(version)
|
fmt.Println(version)
|
||||||
|
}
|
||||||
os.Exit(exitOk)
|
os.Exit(exitOk)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,74 +63,84 @@ func Run(opts *Options) {
|
|||||||
ansiProcessor := func(data []byte) (util.Chars, *[]ansiOffset) {
|
ansiProcessor := func(data []byte) (util.Chars, *[]ansiOffset) {
|
||||||
return util.ToChars(data), nil
|
return util.ToChars(data), nil
|
||||||
}
|
}
|
||||||
ansiProcessorRunes := func(data []rune) (util.Chars, *[]ansiOffset) {
|
|
||||||
return util.RunesToChars(data), nil
|
var lineAnsiState, prevLineAnsiState *ansiState
|
||||||
}
|
|
||||||
if opts.Ansi {
|
if opts.Ansi {
|
||||||
if opts.Theme != nil {
|
if opts.Theme != nil {
|
||||||
var state *ansiState
|
|
||||||
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
||||||
trimmed, offsets, newState := extractColor(string(data), state, nil)
|
prevLineAnsiState = lineAnsiState
|
||||||
state = newState
|
trimmed, offsets, newState := extractColor(string(data), lineAnsiState, nil)
|
||||||
return util.RunesToChars([]rune(trimmed)), offsets
|
lineAnsiState = newState
|
||||||
|
return util.ToChars([]byte(trimmed)), offsets
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// When color is disabled but ansi option is given,
|
// When color is disabled but ansi option is given,
|
||||||
// we simply strip out ANSI codes from the input
|
// we simply strip out ANSI codes from the input
|
||||||
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
||||||
trimmed, _, _ := extractColor(string(data), nil, nil)
|
trimmed, _, _ := extractColor(string(data), nil, nil)
|
||||||
return util.RunesToChars([]rune(trimmed)), nil
|
return util.ToChars([]byte(trimmed)), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ansiProcessorRunes = func(data []rune) (util.Chars, *[]ansiOffset) {
|
|
||||||
return ansiProcessor([]byte(string(data)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chunk list
|
// Chunk list
|
||||||
var chunkList *ChunkList
|
var chunkList *ChunkList
|
||||||
|
var itemIndex int32
|
||||||
header := make([]string, 0, opts.HeaderLines)
|
header := make([]string, 0, opts.HeaderLines)
|
||||||
if len(opts.WithNth) == 0 {
|
if len(opts.WithNth) == 0 {
|
||||||
chunkList = NewChunkList(func(data []byte, index int) *Item {
|
chunkList = NewChunkList(func(item *Item, data []byte) bool {
|
||||||
if len(header) < opts.HeaderLines {
|
if len(header) < opts.HeaderLines {
|
||||||
header = append(header, string(data))
|
header = append(header, string(data))
|
||||||
eventBox.Set(EvtHeader, header)
|
eventBox.Set(EvtHeader, header)
|
||||||
return nil
|
return false
|
||||||
}
|
}
|
||||||
chars, colors := ansiProcessor(data)
|
item.text, item.colors = ansiProcessor(data)
|
||||||
return &Item{
|
item.text.Index = itemIndex
|
||||||
index: int32(index),
|
itemIndex++
|
||||||
text: chars,
|
return true
|
||||||
colors: colors}
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
chunkList = NewChunkList(func(data []byte, index int) *Item {
|
chunkList = NewChunkList(func(item *Item, data []byte) bool {
|
||||||
tokens := Tokenize(util.ToChars(data), opts.Delimiter)
|
tokens := Tokenize(string(data), opts.Delimiter)
|
||||||
trans := Transform(tokens, opts.WithNth)
|
if opts.Ansi && opts.Theme != nil && len(tokens) > 1 {
|
||||||
if len(header) < opts.HeaderLines {
|
var ansiState *ansiState
|
||||||
header = append(header, string(joinTokens(trans)))
|
if prevLineAnsiState != nil {
|
||||||
eventBox.Set(EvtHeader, header)
|
ansiStateDup := *prevLineAnsiState
|
||||||
return nil
|
ansiState = &ansiStateDup
|
||||||
}
|
}
|
||||||
textRunes := joinTokens(trans)
|
for _, token := range tokens {
|
||||||
item := Item{
|
prevAnsiState := ansiState
|
||||||
index: int32(index),
|
_, _, ansiState = extractColor(token.text.ToString(), ansiState, nil)
|
||||||
origText: &data,
|
if prevAnsiState != nil {
|
||||||
colors: nil}
|
token.text.Prepend("\x1b[m" + prevAnsiState.ToString())
|
||||||
|
} else {
|
||||||
trimmed, colors := ansiProcessorRunes(textRunes)
|
token.text.Prepend("\x1b[m")
|
||||||
item.text = trimmed
|
}
|
||||||
item.colors = colors
|
}
|
||||||
return &item
|
}
|
||||||
|
trans := Transform(tokens, opts.WithNth)
|
||||||
|
transformed := joinTokens(trans)
|
||||||
|
if len(header) < opts.HeaderLines {
|
||||||
|
header = append(header, transformed)
|
||||||
|
eventBox.Set(EvtHeader, header)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
item.text, item.colors = ansiProcessor([]byte(transformed))
|
||||||
|
item.text.TrimTrailingWhitespaces()
|
||||||
|
item.text.Index = itemIndex
|
||||||
|
item.origText = &data
|
||||||
|
itemIndex++
|
||||||
|
return true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reader
|
// Reader
|
||||||
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
|
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
|
||||||
|
var reader *Reader
|
||||||
if !streamingFilter {
|
if !streamingFilter {
|
||||||
reader := Reader{func(data []byte) bool {
|
reader = NewReader(func(data []byte) bool {
|
||||||
return chunkList.Push(data)
|
return chunkList.Push(data)
|
||||||
}, eventBox, opts.ReadZero}
|
}, eventBox, opts.ReadZero, opts.Filter == nil)
|
||||||
go reader.ReadSource()
|
go reader.ReadSource()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,21 +169,22 @@ func Run(opts *Options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pattern := patternBuilder([]rune(*opts.Filter))
|
pattern := patternBuilder([]rune(*opts.Filter))
|
||||||
|
matcher.sort = pattern.sortable
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
if streamingFilter {
|
if streamingFilter {
|
||||||
slab := util.MakeSlab(slab16Size, slab32Size)
|
slab := util.MakeSlab(slab16Size, slab32Size)
|
||||||
reader := Reader{
|
reader := NewReader(
|
||||||
func(runes []byte) bool {
|
func(runes []byte) bool {
|
||||||
item := chunkList.trans(runes, 0)
|
item := Item{}
|
||||||
if item != nil {
|
if chunkList.trans(&item, runes) {
|
||||||
if result, _, _ := pattern.MatchItem(item, false, slab); result != nil {
|
if result, _, _ := pattern.MatchItem(&item, false, slab); result != nil {
|
||||||
opts.Printer(item.text.ToString())
|
opts.Printer(item.text.ToString())
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}, eventBox, opts.ReadZero}
|
}, eventBox, opts.ReadZero, false)
|
||||||
reader.ReadSource()
|
reader.ReadSource()
|
||||||
} else {
|
} else {
|
||||||
eventBox.Unwatch(EvtReadNew)
|
eventBox.Unwatch(EvtReadNew)
|
||||||
@@ -209,29 +224,69 @@ func Run(opts *Options) {
|
|||||||
|
|
||||||
// Event coordination
|
// Event coordination
|
||||||
reading := true
|
reading := true
|
||||||
|
clearCache := util.Once(false)
|
||||||
|
clearSelection := util.Once(false)
|
||||||
ticks := 0
|
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)
|
eventBox.Watch(EvtReadNew)
|
||||||
for {
|
for {
|
||||||
delay := true
|
delay := true
|
||||||
ticks++
|
ticks++
|
||||||
|
input := func() []rune {
|
||||||
|
if opts.Phony {
|
||||||
|
return []rune{}
|
||||||
|
}
|
||||||
|
return []rune(terminal.Input())
|
||||||
|
}
|
||||||
eventBox.Wait(func(events *util.Events) {
|
eventBox.Wait(func(events *util.Events) {
|
||||||
defer events.Clear()
|
if _, fin := (*events)[EvtReadFin]; fin {
|
||||||
|
delete(*events, EvtReadNew)
|
||||||
|
}
|
||||||
for evt, value := range *events {
|
for evt, value := range *events {
|
||||||
switch evt {
|
switch evt {
|
||||||
|
|
||||||
case EvtReadNew, EvtReadFin:
|
case EvtReadNew, EvtReadFin:
|
||||||
|
if evt == EvtReadFin && nextCommand != nil {
|
||||||
|
restart(*nextCommand)
|
||||||
|
nextCommand = nil
|
||||||
|
break
|
||||||
|
} else {
|
||||||
reading = reading && evt == EvtReadNew
|
reading = reading && evt == EvtReadNew
|
||||||
|
}
|
||||||
snapshot, count := chunkList.Snapshot()
|
snapshot, count := chunkList.Snapshot()
|
||||||
terminal.UpdateCount(count, !reading)
|
terminal.UpdateCount(count, !reading, value.(*string))
|
||||||
matcher.Reset(snapshot, terminal.Input(), false, !reading, sort)
|
if opts.Sync {
|
||||||
|
opts.Sync = false
|
||||||
|
terminal.UpdateList(PassMerger(&snapshot, opts.Tac), false)
|
||||||
|
}
|
||||||
|
matcher.Reset(snapshot, input(), false, !reading, sort, clearCache())
|
||||||
|
|
||||||
case EvtSearchNew:
|
case EvtSearchNew:
|
||||||
|
var command *string
|
||||||
switch val := value.(type) {
|
switch val := value.(type) {
|
||||||
case bool:
|
case searchRequest:
|
||||||
sort = val
|
sort = val.sort
|
||||||
|
command = val.command
|
||||||
|
}
|
||||||
|
if command != nil {
|
||||||
|
if reading {
|
||||||
|
reader.terminate()
|
||||||
|
nextCommand = command
|
||||||
|
} else {
|
||||||
|
restart(*command)
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
snapshot, _ := chunkList.Snapshot()
|
snapshot, _ := chunkList.Snapshot()
|
||||||
matcher.Reset(snapshot, terminal.Input(), true, !reading, sort)
|
matcher.Reset(snapshot, input(), true, !reading, sort, clearCache())
|
||||||
delay = false
|
delay = false
|
||||||
|
|
||||||
case EvtSearchProgress:
|
case EvtSearchProgress:
|
||||||
@@ -241,7 +296,9 @@ func Run(opts *Options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case EvtHeader:
|
case EvtHeader:
|
||||||
terminal.UpdateHeader(value.([]string))
|
headerPadded := make([]string, opts.HeaderLines)
|
||||||
|
copy(headerPadded, value.([]string))
|
||||||
|
terminal.UpdateHeader(headerPadded)
|
||||||
|
|
||||||
case EvtSearchFin:
|
case EvtSearchFin:
|
||||||
switch val := value.(type) {
|
switch val := value.(type) {
|
||||||
@@ -271,10 +328,11 @@ func Run(opts *Options) {
|
|||||||
terminal.startChan <- true
|
terminal.startChan <- true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
terminal.UpdateList(val)
|
terminal.UpdateList(val, clearSelection())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
events.Clear()
|
||||||
})
|
})
|
||||||
if delay && reading {
|
if delay && reading {
|
||||||
dur := util.DurWithin(
|
dur := util.DurWithin(
|
||||||
|
|||||||
18
src/deps
18
src/deps
@@ -1,18 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
if [ -z "$GOPATH" ]; then
|
|
||||||
echo '$GOPATH not defined'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
reset() (
|
|
||||||
cd "$GOPATH/src/$1"
|
|
||||||
export GIT_DIR="$(pwd)/.git"
|
|
||||||
[ "$(git rev-parse HEAD)" = "$2" ] ||
|
|
||||||
(git fetch && git reset --hard "$2")
|
|
||||||
)
|
|
||||||
|
|
||||||
reset github.com/junegunn/go-isatty 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8
|
|
||||||
reset github.com/junegunn/go-runewidth 14207d285c6c197daabb5c9793d63e7af9ab2d50
|
|
||||||
reset github.com/junegunn/go-shellwords 33bd8f1ebe16d6e5eb688cc885749a63059e9167
|
|
||||||
reset golang.org/x/crypto abc5fa7ad02123a41f02bf1391c9760f7586e608
|
|
||||||
@@ -59,7 +59,7 @@ func (h *History) append(line string) error {
|
|||||||
|
|
||||||
lines := append(h.lines[:len(h.lines)-1], line)
|
lines := append(h.lines[:len(h.lines)-1], line)
|
||||||
if len(lines) > h.maxSize {
|
if len(lines) > h.maxSize {
|
||||||
lines = lines[len(lines)-h.maxSize : len(lines)]
|
lines = lines[len(lines)-h.maxSize:]
|
||||||
}
|
}
|
||||||
h.lines = append(lines, "")
|
h.lines = append(lines, "")
|
||||||
return ioutil.WriteFile(h.path, []byte(strings.Join(h.lines, "\n")), 0600)
|
return ioutil.WriteFile(h.path, []byte(strings.Join(h.lines, "\n")), 0600)
|
||||||
|
|||||||
19
src/item.go
19
src/item.go
@@ -4,18 +4,23 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Item represents each input line
|
// Item represents each input line. 56 bytes.
|
||||||
type Item struct {
|
type Item struct {
|
||||||
index int32
|
text util.Chars // 32 = 24 + 1 + 1 + 2 + 4
|
||||||
text util.Chars
|
transformed *[]Token // 8
|
||||||
origText *[]byte
|
origText *[]byte // 8
|
||||||
colors *[]ansiOffset
|
colors *[]ansiOffset // 8
|
||||||
transformed []Token
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Index returns ordinal index of the Item
|
// Index returns ordinal index of the Item
|
||||||
func (item *Item) Index() int32 {
|
func (item *Item) Index() int32 {
|
||||||
return item.index
|
return item.text.Index
|
||||||
|
}
|
||||||
|
|
||||||
|
var minItem = Item{text: util.Chars{Index: -1}}
|
||||||
|
|
||||||
|
func (item *Item) TrimLength() uint16 {
|
||||||
|
return item.text.TrimLength()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Colors returns ansiOffsets of the Item
|
// Colors returns ansiOffsets of the Item
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ type MatchRequest struct {
|
|||||||
pattern *Pattern
|
pattern *Pattern
|
||||||
final bool
|
final bool
|
||||||
sort bool
|
sort bool
|
||||||
|
clearCache bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Matcher is responsible for performing search
|
// Matcher is responsible for performing search
|
||||||
@@ -69,7 +70,7 @@ func (m *Matcher) Loop() {
|
|||||||
events.Clear()
|
events.Clear()
|
||||||
})
|
})
|
||||||
|
|
||||||
if request.sort != m.sort {
|
if request.sort != m.sort || request.clearCache {
|
||||||
m.sort = request.sort
|
m.sort = request.sort
|
||||||
m.mergerCache = make(map[string]*Merger)
|
m.mergerCache = make(map[string]*Merger)
|
||||||
clearChunkCache()
|
clearChunkCache()
|
||||||
@@ -131,7 +132,7 @@ func (m *Matcher) sliceChunks(chunks []*Chunk) [][]*Chunk {
|
|||||||
|
|
||||||
type partialResult struct {
|
type partialResult struct {
|
||||||
index int
|
index int
|
||||||
matches []*Result
|
matches []Result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
||||||
@@ -162,7 +163,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
|||||||
go func(idx int, slab *util.Slab, chunks []*Chunk) {
|
go func(idx int, slab *util.Slab, chunks []*Chunk) {
|
||||||
defer func() { waitGroup.Done() }()
|
defer func() { waitGroup.Done() }()
|
||||||
count := 0
|
count := 0
|
||||||
allMatches := make([][]*Result, len(chunks))
|
allMatches := make([][]Result, len(chunks))
|
||||||
for idx, chunk := range chunks {
|
for idx, chunk := range chunks {
|
||||||
matches := request.pattern.Match(chunk, slab)
|
matches := request.pattern.Match(chunk, slab)
|
||||||
allMatches[idx] = matches
|
allMatches[idx] = matches
|
||||||
@@ -172,7 +173,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
|||||||
}
|
}
|
||||||
countChan <- len(matches)
|
countChan <- len(matches)
|
||||||
}
|
}
|
||||||
sliceMatches := make([]*Result, 0, count)
|
sliceMatches := make([]Result, 0, count)
|
||||||
for _, matches := range allMatches {
|
for _, matches := range allMatches {
|
||||||
sliceMatches = append(sliceMatches, matches...)
|
sliceMatches = append(sliceMatches, matches...)
|
||||||
}
|
}
|
||||||
@@ -207,13 +208,13 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
|||||||
return nil, wait()
|
return nil, wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
if time.Now().Sub(startedAt) > progressMinDuration {
|
if time.Since(startedAt) > progressMinDuration {
|
||||||
m.eventBox.Set(EvtSearchProgress, float32(count)/float32(numChunks))
|
m.eventBox.Set(EvtSearchProgress, float32(count)/float32(numChunks))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
partialResults := make([][]*Result, numSlices)
|
partialResults := make([][]Result, numSlices)
|
||||||
for _ = range slices {
|
for range slices {
|
||||||
partialResult := <-resultChan
|
partialResult := <-resultChan
|
||||||
partialResults[partialResult.index] = partialResult.matches
|
partialResults[partialResult.index] = partialResult.matches
|
||||||
}
|
}
|
||||||
@@ -221,7 +222,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reset is called to interrupt/signal the ongoing search
|
// 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)
|
pattern := m.patternBuilder(patternRunes)
|
||||||
|
|
||||||
var event util.EventType
|
var event util.EventType
|
||||||
@@ -230,5 +231,5 @@ func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final
|
|||||||
} else {
|
} else {
|
||||||
event = reqRetry
|
event = reqRetry
|
||||||
}
|
}
|
||||||
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort})
|
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable, clearCache})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ package fzf
|
|||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
// EmptyMerger is a Merger with no data
|
// EmptyMerger is a Merger with no data
|
||||||
var EmptyMerger = NewMerger(nil, [][]*Result{}, false, false)
|
var EmptyMerger = NewMerger(nil, [][]Result{}, false, false)
|
||||||
|
|
||||||
// Merger holds a set of locally sorted lists of items and provides the view of
|
// Merger holds a set of locally sorted lists of items and provides the view of
|
||||||
// a single, globally-sorted list
|
// a single, globally-sorted list
|
||||||
type Merger struct {
|
type Merger struct {
|
||||||
pattern *Pattern
|
pattern *Pattern
|
||||||
lists [][]*Result
|
lists [][]Result
|
||||||
merged []*Result
|
merged []Result
|
||||||
chunks *[]*Chunk
|
chunks *[]*Chunk
|
||||||
cursors []int
|
cursors []int
|
||||||
sorted bool
|
sorted bool
|
||||||
@@ -29,17 +29,17 @@ func PassMerger(chunks *[]*Chunk, tac bool) *Merger {
|
|||||||
count: 0}
|
count: 0}
|
||||||
|
|
||||||
for _, chunk := range *mg.chunks {
|
for _, chunk := range *mg.chunks {
|
||||||
mg.count += len(*chunk)
|
mg.count += chunk.count
|
||||||
}
|
}
|
||||||
return &mg
|
return &mg
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMerger returns a new Merger
|
// NewMerger returns a new Merger
|
||||||
func NewMerger(pattern *Pattern, lists [][]*Result, sorted bool, tac bool) *Merger {
|
func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool) *Merger {
|
||||||
mg := Merger{
|
mg := Merger{
|
||||||
pattern: pattern,
|
pattern: pattern,
|
||||||
lists: lists,
|
lists: lists,
|
||||||
merged: []*Result{},
|
merged: []Result{},
|
||||||
chunks: nil,
|
chunks: nil,
|
||||||
cursors: make([]int, len(lists)),
|
cursors: make([]int, len(lists)),
|
||||||
sorted: sorted,
|
sorted: sorted,
|
||||||
@@ -59,13 +59,13 @@ func (mg *Merger) Length() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the pointer to the Result object indexed by the given integer
|
// Get returns the pointer to the Result object indexed by the given integer
|
||||||
func (mg *Merger) Get(idx int) *Result {
|
func (mg *Merger) Get(idx int) Result {
|
||||||
if mg.chunks != nil {
|
if mg.chunks != nil {
|
||||||
if mg.tac {
|
if mg.tac {
|
||||||
idx = mg.count - idx - 1
|
idx = mg.count - idx - 1
|
||||||
}
|
}
|
||||||
chunk := (*mg.chunks)[idx/chunkSize]
|
chunk := (*mg.chunks)[idx/chunkSize]
|
||||||
return &Result{item: (*chunk)[idx%chunkSize]}
|
return Result{item: &chunk.items[idx%chunkSize]}
|
||||||
}
|
}
|
||||||
|
|
||||||
if mg.sorted {
|
if mg.sorted {
|
||||||
@@ -89,7 +89,7 @@ func (mg *Merger) cacheable() bool {
|
|||||||
return mg.count < mergerCacheMax
|
return mg.count < mergerCacheMax
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mg *Merger) mergedGet(idx int) *Result {
|
func (mg *Merger) mergedGet(idx int) Result {
|
||||||
for i := len(mg.merged); i <= idx; i++ {
|
for i := len(mg.merged); i <= idx; i++ {
|
||||||
minRank := minRank()
|
minRank := minRank()
|
||||||
minIdx := -1
|
minIdx := -1
|
||||||
@@ -100,7 +100,7 @@ func (mg *Merger) mergedGet(idx int) *Result {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if cursor >= 0 {
|
if cursor >= 0 {
|
||||||
rank := list[cursor].rank
|
rank := list[cursor]
|
||||||
if minIdx < 0 || compareRanks(rank, minRank, mg.tac) {
|
if minIdx < 0 || compareRanks(rank, minRank, mg.tac) {
|
||||||
minRank = rank
|
minRank = rank
|
||||||
minIdx = listIdx
|
minIdx = listIdx
|
||||||
|
|||||||
@@ -15,11 +15,11 @@ func assert(t *testing.T, cond bool, msg ...string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func randResult() *Result {
|
func randResult() Result {
|
||||||
str := fmt.Sprintf("%d", rand.Uint32())
|
str := fmt.Sprintf("%d", rand.Uint32())
|
||||||
return &Result{
|
chars := util.ToChars([]byte(str))
|
||||||
item: &Item{text: util.RunesToChars([]rune(str))},
|
chars.Index = rand.Int31()
|
||||||
rank: rank{index: rand.Int31()}}
|
return Result{item: &Item{text: chars}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEmptyMerger(t *testing.T) {
|
func TestEmptyMerger(t *testing.T) {
|
||||||
@@ -29,14 +29,14 @@ func TestEmptyMerger(t *testing.T) {
|
|||||||
assert(t, len(EmptyMerger.merged) == 0, "Invalid merged list")
|
assert(t, len(EmptyMerger.merged) == 0, "Invalid merged list")
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildLists(partiallySorted bool) ([][]*Result, []*Result) {
|
func buildLists(partiallySorted bool) ([][]Result, []Result) {
|
||||||
numLists := 4
|
numLists := 4
|
||||||
lists := make([][]*Result, numLists)
|
lists := make([][]Result, numLists)
|
||||||
cnt := 0
|
cnt := 0
|
||||||
for i := 0; i < numLists; i++ {
|
for i := 0; i < numLists; i++ {
|
||||||
numResults := rand.Int() % 20
|
numResults := rand.Int() % 20
|
||||||
cnt += numResults
|
cnt += numResults
|
||||||
lists[i] = make([]*Result, numResults)
|
lists[i] = make([]Result, numResults)
|
||||||
for j := 0; j < numResults; j++ {
|
for j := 0; j < numResults; j++ {
|
||||||
item := randResult()
|
item := randResult()
|
||||||
lists[i][j] = item
|
lists[i][j] = item
|
||||||
@@ -45,7 +45,7 @@ func buildLists(partiallySorted bool) ([][]*Result, []*Result) {
|
|||||||
sort.Sort(ByRelevance(lists[i]))
|
sort.Sort(ByRelevance(lists[i]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
items := []*Result{}
|
items := []Result{}
|
||||||
for _, list := range lists {
|
for _, list := range lists {
|
||||||
items = append(items, list...)
|
items = append(items, list...)
|
||||||
}
|
}
|
||||||
|
|||||||
224
src/options.go
224
src/options.go
@@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/tui"
|
"github.com/junegunn/fzf/src/tui"
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
|
||||||
"github.com/junegunn/go-shellwords"
|
"github.com/mattn/go-shellwords"
|
||||||
)
|
)
|
||||||
|
|
||||||
const usage = `usage: fzf [options]
|
const usage = `usage: fzf [options]
|
||||||
@@ -33,12 +33,13 @@ const usage = `usage: fzf [options]
|
|||||||
-d, --delimiter=STR Field delimiter regex (default: AWK-style)
|
-d, --delimiter=STR Field delimiter regex (default: AWK-style)
|
||||||
+s, --no-sort Do not sort the result
|
+s, --no-sort Do not sort the result
|
||||||
--tac Reverse the order of the input
|
--tac Reverse the order of the input
|
||||||
|
--phony Do not perform search
|
||||||
--tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
|
--tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
|
||||||
when the scores are tied [length|begin|end|index]
|
when the scores are tied [length|begin|end|index]
|
||||||
(default: length)
|
(default: length)
|
||||||
|
|
||||||
Interface
|
Interface
|
||||||
-m, --multi Enable multi-select with tab/shift-tab
|
-m, --multi[=MAX] Enable multi-select with tab/shift-tab
|
||||||
--no-mouse Disable mouse
|
--no-mouse Disable mouse
|
||||||
--bind=KEYBINDS Custom key bindings. Refer to the man page.
|
--bind=KEYBINDS Custom key bindings. Refer to the man page.
|
||||||
--cycle Enable cyclic scroll
|
--cycle Enable cyclic scroll
|
||||||
@@ -53,10 +54,10 @@ const usage = `usage: fzf [options]
|
|||||||
height instead of using fullscreen
|
height instead of using fullscreen
|
||||||
--min-height=HEIGHT Minimum height when --height is given in percent
|
--min-height=HEIGHT Minimum height when --height is given in percent
|
||||||
(default: 10)
|
(default: 10)
|
||||||
--reverse Reverse orientation
|
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
|
||||||
--border Draw border above and below the finder
|
--border Draw border above and below the finder
|
||||||
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
|
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
|
||||||
--inline-info Display finder info inline with the query
|
--info=STYLE Finder info style [default|inline|hidden]
|
||||||
--prompt=STR Input prompt (default: '> ')
|
--prompt=STR Input prompt (default: '> ')
|
||||||
--header=STR String to print as header
|
--header=STR String to print as header
|
||||||
--header-lines=N The first N lines of the input are treated as header
|
--header-lines=N The first N lines of the input are treated as header
|
||||||
@@ -86,10 +87,12 @@ const usage = `usage: fzf [options]
|
|||||||
--read0 Read input delimited by ASCII NUL characters
|
--read0 Read input delimited by ASCII NUL characters
|
||||||
--print0 Print output delimited by ASCII NUL characters
|
--print0 Print output delimited by ASCII NUL characters
|
||||||
--sync Synchronous search for multi-staged filtering
|
--sync Synchronous search for multi-staged filtering
|
||||||
|
--version Display version information and exit
|
||||||
|
|
||||||
Environment variables
|
Environment variables
|
||||||
FZF_DEFAULT_COMMAND Default command to use when input is tty
|
FZF_DEFAULT_COMMAND Default command to use when input is tty
|
||||||
FZF_DEFAULT_OPTS Default options (e.g. '--reverse --inline-info')
|
FZF_DEFAULT_OPTS Default options
|
||||||
|
(e.g. '--layout=reverse --inline-info')
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -131,12 +134,29 @@ const (
|
|||||||
posRight
|
posRight
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type layoutType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
layoutDefault layoutType = iota
|
||||||
|
layoutReverse
|
||||||
|
layoutReverseList
|
||||||
|
)
|
||||||
|
|
||||||
|
type infoStyle int
|
||||||
|
|
||||||
|
const (
|
||||||
|
infoDefault infoStyle = iota
|
||||||
|
infoInline
|
||||||
|
infoHidden
|
||||||
|
)
|
||||||
|
|
||||||
type previewOpts struct {
|
type previewOpts struct {
|
||||||
command string
|
command string
|
||||||
position windowPosition
|
position windowPosition
|
||||||
size sizeSpec
|
size sizeSpec
|
||||||
hidden bool
|
hidden bool
|
||||||
wrap bool
|
wrap bool
|
||||||
|
border bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Options stores the values of command-line options
|
// Options stores the values of command-line options
|
||||||
@@ -144,6 +164,7 @@ type Options struct {
|
|||||||
Fuzzy bool
|
Fuzzy bool
|
||||||
FuzzyAlgo algo.Algo
|
FuzzyAlgo algo.Algo
|
||||||
Extended bool
|
Extended bool
|
||||||
|
Phony bool
|
||||||
Case Case
|
Case Case
|
||||||
Normalize bool
|
Normalize bool
|
||||||
Nth []Range
|
Nth []Range
|
||||||
@@ -152,7 +173,7 @@ type Options struct {
|
|||||||
Sort int
|
Sort int
|
||||||
Tac bool
|
Tac bool
|
||||||
Criteria []criterion
|
Criteria []criterion
|
||||||
Multi bool
|
Multi int
|
||||||
Ansi bool
|
Ansi bool
|
||||||
Mouse bool
|
Mouse bool
|
||||||
Theme *tui.ColorTheme
|
Theme *tui.ColorTheme
|
||||||
@@ -160,12 +181,12 @@ type Options struct {
|
|||||||
Bold bool
|
Bold bool
|
||||||
Height sizeSpec
|
Height sizeSpec
|
||||||
MinHeight int
|
MinHeight int
|
||||||
Reverse bool
|
Layout layoutType
|
||||||
Cycle bool
|
Cycle bool
|
||||||
Hscroll bool
|
Hscroll bool
|
||||||
HscrollOff int
|
HscrollOff int
|
||||||
FileWord bool
|
FileWord bool
|
||||||
InlineInfo bool
|
InfoStyle infoStyle
|
||||||
JumpLabels string
|
JumpLabels string
|
||||||
Prompt string
|
Prompt string
|
||||||
Query string
|
Query string
|
||||||
@@ -179,13 +200,16 @@ type Options struct {
|
|||||||
PrintQuery bool
|
PrintQuery bool
|
||||||
ReadZero bool
|
ReadZero bool
|
||||||
Printer func(string)
|
Printer func(string)
|
||||||
|
PrintSep string
|
||||||
Sync bool
|
Sync bool
|
||||||
History *History
|
History *History
|
||||||
Header []string
|
Header []string
|
||||||
HeaderLines int
|
HeaderLines int
|
||||||
Margin [4]sizeSpec
|
Margin [4]sizeSpec
|
||||||
Bordered bool
|
Bordered bool
|
||||||
|
Unicode bool
|
||||||
Tabstop int
|
Tabstop int
|
||||||
|
ClearOnExit bool
|
||||||
Version bool
|
Version bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,6 +218,7 @@ func defaultOptions() *Options {
|
|||||||
Fuzzy: true,
|
Fuzzy: true,
|
||||||
FuzzyAlgo: algo.FuzzyMatchV2,
|
FuzzyAlgo: algo.FuzzyMatchV2,
|
||||||
Extended: true,
|
Extended: true,
|
||||||
|
Phony: false,
|
||||||
Case: CaseSmart,
|
Case: CaseSmart,
|
||||||
Normalize: true,
|
Normalize: true,
|
||||||
Nth: make([]Range, 0),
|
Nth: make([]Range, 0),
|
||||||
@@ -202,19 +227,19 @@ func defaultOptions() *Options {
|
|||||||
Sort: 1000,
|
Sort: 1000,
|
||||||
Tac: false,
|
Tac: false,
|
||||||
Criteria: []criterion{byScore, byLength},
|
Criteria: []criterion{byScore, byLength},
|
||||||
Multi: false,
|
Multi: 0,
|
||||||
Ansi: false,
|
Ansi: false,
|
||||||
Mouse: true,
|
Mouse: true,
|
||||||
Theme: tui.EmptyTheme(),
|
Theme: tui.EmptyTheme(),
|
||||||
Black: false,
|
Black: false,
|
||||||
Bold: true,
|
Bold: true,
|
||||||
MinHeight: 10,
|
MinHeight: 10,
|
||||||
Reverse: false,
|
Layout: layoutDefault,
|
||||||
Cycle: false,
|
Cycle: false,
|
||||||
Hscroll: true,
|
Hscroll: true,
|
||||||
HscrollOff: 10,
|
HscrollOff: 10,
|
||||||
FileWord: false,
|
FileWord: false,
|
||||||
InlineInfo: false,
|
InfoStyle: infoDefault,
|
||||||
JumpLabels: defaultJumpLabels,
|
JumpLabels: defaultJumpLabels,
|
||||||
Prompt: "> ",
|
Prompt: "> ",
|
||||||
Query: "",
|
Query: "",
|
||||||
@@ -224,21 +249,24 @@ func defaultOptions() *Options {
|
|||||||
ToggleSort: false,
|
ToggleSort: false,
|
||||||
Expect: make(map[int]string),
|
Expect: make(map[int]string),
|
||||||
Keymap: make(map[int][]action),
|
Keymap: make(map[int][]action),
|
||||||
Preview: previewOpts{"", posRight, sizeSpec{50, true}, false, false},
|
Preview: previewOpts{"", posRight, sizeSpec{50, true}, false, false, true},
|
||||||
PrintQuery: false,
|
PrintQuery: false,
|
||||||
ReadZero: false,
|
ReadZero: false,
|
||||||
Printer: func(str string) { fmt.Println(str) },
|
Printer: func(str string) { fmt.Println(str) },
|
||||||
|
PrintSep: "\n",
|
||||||
Sync: false,
|
Sync: false,
|
||||||
History: nil,
|
History: nil,
|
||||||
Header: make([]string, 0),
|
Header: make([]string, 0),
|
||||||
HeaderLines: 0,
|
HeaderLines: 0,
|
||||||
Margin: defaultMargin(),
|
Margin: defaultMargin(),
|
||||||
|
Unicode: true,
|
||||||
Tabstop: 8,
|
Tabstop: 8,
|
||||||
|
ClearOnExit: true,
|
||||||
Version: false}
|
Version: false}
|
||||||
}
|
}
|
||||||
|
|
||||||
func help(code int) {
|
func help(code int) {
|
||||||
os.Stderr.WriteString(usage)
|
os.Stdout.WriteString(usage)
|
||||||
os.Exit(code)
|
os.Exit(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,13 +326,14 @@ func nextInt(args []string, i *int, message string) int {
|
|||||||
return atoi(args[*i])
|
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 len(args) > *i+1 {
|
||||||
if strings.IndexAny(args[*i+1], "0123456789") == 0 {
|
if strings.IndexAny(args[*i+1], "0123456789") == 0 {
|
||||||
*i++
|
*i++
|
||||||
|
return atoi(args[*i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 1 // Don't care
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
func splitNth(str string) []Range {
|
func splitNth(str string) []Range {
|
||||||
@@ -369,7 +398,7 @@ func parseKeyChords(str string, message string) map[int]string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tokens := strings.Split(str, ",")
|
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, ",")
|
tokens = append(tokens, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,14 +426,32 @@ func parseKeyChords(str string, message string) map[int]string {
|
|||||||
chord = tui.BSpace
|
chord = tui.BSpace
|
||||||
case "ctrl-space":
|
case "ctrl-space":
|
||||||
chord = tui.CtrlSpace
|
chord = tui.CtrlSpace
|
||||||
|
case "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":
|
case "alt-enter", "alt-return":
|
||||||
chord = tui.AltEnter
|
chord = tui.CtrlAltM
|
||||||
case "alt-space":
|
case "alt-space":
|
||||||
chord = tui.AltSpace
|
chord = tui.AltSpace
|
||||||
case "alt-/":
|
case "alt-/":
|
||||||
chord = tui.AltSlash
|
chord = tui.AltSlash
|
||||||
case "alt-bs", "alt-bspace":
|
case "alt-bs", "alt-bspace":
|
||||||
chord = tui.AltBS
|
chord = tui.AltBS
|
||||||
|
case "alt-up":
|
||||||
|
chord = tui.AltUp
|
||||||
|
case "alt-down":
|
||||||
|
chord = tui.AltDown
|
||||||
|
case "alt-left":
|
||||||
|
chord = tui.AltLeft
|
||||||
|
case "alt-right":
|
||||||
|
chord = tui.AltRight
|
||||||
case "tab":
|
case "tab":
|
||||||
chord = tui.Tab
|
chord = tui.Tab
|
||||||
case "btab", "shift-tab":
|
case "btab", "shift-tab":
|
||||||
@@ -421,10 +468,18 @@ func parseKeyChords(str string, message string) map[int]string {
|
|||||||
chord = tui.PgUp
|
chord = tui.PgUp
|
||||||
case "pgdn", "page-down":
|
case "pgdn", "page-down":
|
||||||
chord = tui.PgDn
|
chord = tui.PgDn
|
||||||
|
case "shift-up":
|
||||||
|
chord = tui.SUp
|
||||||
|
case "shift-down":
|
||||||
|
chord = tui.SDown
|
||||||
case "shift-left":
|
case "shift-left":
|
||||||
chord = tui.SLeft
|
chord = tui.SLeft
|
||||||
case "shift-right":
|
case "shift-right":
|
||||||
chord = tui.SRight
|
chord = tui.SRight
|
||||||
|
case "left-click":
|
||||||
|
chord = tui.LeftClick
|
||||||
|
case "right-click":
|
||||||
|
chord = tui.RightClick
|
||||||
case "double-click":
|
case "double-click":
|
||||||
chord = tui.DoubleClick
|
chord = tui.DoubleClick
|
||||||
case "f10":
|
case "f10":
|
||||||
@@ -434,7 +489,9 @@ func parseKeyChords(str string, message string) map[int]string {
|
|||||||
case "f12":
|
case "f12":
|
||||||
chord = tui.F12
|
chord = tui.F12
|
||||||
default:
|
default:
|
||||||
if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
|
if len(key) == 10 && strings.HasPrefix(lkey, "ctrl-alt-") && isAlphabet(lkey[9]) {
|
||||||
|
chord = tui.CtrlAltA + int(lkey[9]) - 'a'
|
||||||
|
} else if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
|
||||||
chord = tui.CtrlA + int(lkey[5]) - 'a'
|
chord = tui.CtrlA + int(lkey[5]) - 'a'
|
||||||
} else if len(key) == 5 && strings.HasPrefix(lkey, "alt-") && isAlphabet(lkey[4]) {
|
} else if len(key) == 5 && strings.HasPrefix(lkey, "alt-") && isAlphabet(lkey[4]) {
|
||||||
chord = tui.AltA + int(lkey[4]) - 'a'
|
chord = tui.AltA + int(lkey[4]) - 'a'
|
||||||
@@ -540,10 +597,16 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
|
|||||||
theme.Fg = ansi
|
theme.Fg = ansi
|
||||||
case "bg":
|
case "bg":
|
||||||
theme.Bg = ansi
|
theme.Bg = ansi
|
||||||
|
case "preview-fg":
|
||||||
|
theme.PreviewFg = ansi
|
||||||
|
case "preview-bg":
|
||||||
|
theme.PreviewBg = ansi
|
||||||
case "fg+":
|
case "fg+":
|
||||||
theme.Current = ansi
|
theme.Current = ansi
|
||||||
case "bg+":
|
case "bg+":
|
||||||
theme.DarkBg = ansi
|
theme.DarkBg = ansi
|
||||||
|
case "gutter":
|
||||||
|
theme.Gutter = ansi
|
||||||
case "hl":
|
case "hl":
|
||||||
theme.Match = ansi
|
theme.Match = ansi
|
||||||
case "hl+":
|
case "hl+":
|
||||||
@@ -589,13 +652,19 @@ func init() {
|
|||||||
// Backreferences are not supported.
|
// Backreferences are not supported.
|
||||||
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
||||||
executeRegexp = regexp.MustCompile(
|
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) {
|
func parseKeymap(keymap map[int][]action, str string) {
|
||||||
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
||||||
prefix := ":execute"
|
symbol := ":"
|
||||||
if src[len(prefix)] == '-' {
|
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]
|
c := src[len(prefix)+1]
|
||||||
if c == 's' || c == 'S' {
|
if c == 's' || c == 'S' {
|
||||||
prefix += "-silent"
|
prefix += "-silent"
|
||||||
@@ -651,8 +720,12 @@ func parseKeymap(keymap map[int][]action, str string) {
|
|||||||
appendAction(actAbort)
|
appendAction(actAbort)
|
||||||
case "accept":
|
case "accept":
|
||||||
appendAction(actAccept)
|
appendAction(actAccept)
|
||||||
|
case "accept-non-empty":
|
||||||
|
appendAction(actAcceptNonEmpty)
|
||||||
case "print-query":
|
case "print-query":
|
||||||
appendAction(actPrintQuery)
|
appendAction(actPrintQuery)
|
||||||
|
case "replace-query":
|
||||||
|
appendAction(actReplaceQuery)
|
||||||
case "backward-char":
|
case "backward-char":
|
||||||
appendAction(actBackwardChar)
|
appendAction(actBackwardChar)
|
||||||
case "backward-delete-char":
|
case "backward-delete-char":
|
||||||
@@ -669,6 +742,10 @@ func parseKeymap(keymap map[int][]action, str string) {
|
|||||||
appendAction(actEndOfLine)
|
appendAction(actEndOfLine)
|
||||||
case "cancel":
|
case "cancel":
|
||||||
appendAction(actCancel)
|
appendAction(actCancel)
|
||||||
|
case "clear-query":
|
||||||
|
appendAction(actClearQuery)
|
||||||
|
case "clear-selection":
|
||||||
|
appendAction(actClearSelection)
|
||||||
case "forward-char":
|
case "forward-char":
|
||||||
appendAction(actForwardChar)
|
appendAction(actForwardChar)
|
||||||
case "forward-word":
|
case "forward-word":
|
||||||
@@ -709,6 +786,8 @@ func parseKeymap(keymap map[int][]action, str string) {
|
|||||||
appendAction(actDown)
|
appendAction(actDown)
|
||||||
case "up":
|
case "up":
|
||||||
appendAction(actUp)
|
appendAction(actUp)
|
||||||
|
case "top":
|
||||||
|
appendAction(actTop)
|
||||||
case "page-up":
|
case "page-up":
|
||||||
appendAction(actPageUp)
|
appendAction(actPageUp)
|
||||||
case "page-down":
|
case "page-down":
|
||||||
@@ -723,6 +802,8 @@ func parseKeymap(keymap map[int][]action, str string) {
|
|||||||
appendAction(actNextHistory)
|
appendAction(actNextHistory)
|
||||||
case "toggle-preview":
|
case "toggle-preview":
|
||||||
appendAction(actTogglePreview)
|
appendAction(actTogglePreview)
|
||||||
|
case "toggle-preview-wrap":
|
||||||
|
appendAction(actTogglePreviewWrap)
|
||||||
case "toggle-sort":
|
case "toggle-sort":
|
||||||
appendAction(actToggleSort)
|
appendAction(actToggleSort)
|
||||||
case "preview-up":
|
case "preview-up":
|
||||||
@@ -736,10 +817,16 @@ func parseKeymap(keymap map[int][]action, str string) {
|
|||||||
default:
|
default:
|
||||||
t := isExecuteAction(specLower)
|
t := isExecuteAction(specLower)
|
||||||
if t == actIgnore {
|
if t == actIgnore {
|
||||||
|
if specIndex == 0 && specLower == "" {
|
||||||
|
actions = append(keymap[key], actions...)
|
||||||
|
} else {
|
||||||
errorExit("unknown action: " + spec)
|
errorExit("unknown action: " + spec)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
var offset int
|
var offset int
|
||||||
switch t {
|
switch t {
|
||||||
|
case actReload:
|
||||||
|
offset = len("reload")
|
||||||
case actExecuteSilent:
|
case actExecuteSilent:
|
||||||
offset = len("execute-silent")
|
offset = len("execute-silent")
|
||||||
case actExecuteMulti:
|
case actExecuteMulti:
|
||||||
@@ -775,6 +862,8 @@ func isExecuteAction(str string) actionType {
|
|||||||
prefix = matches[0][2]
|
prefix = matches[0][2]
|
||||||
}
|
}
|
||||||
switch prefix {
|
switch prefix {
|
||||||
|
case "reload":
|
||||||
|
return actReload
|
||||||
case "execute":
|
case "execute":
|
||||||
return actExecute
|
return actExecute
|
||||||
case "execute-silent":
|
case "execute-silent":
|
||||||
@@ -822,13 +911,38 @@ func parseSize(str string, maxPercent float64, label string) sizeSpec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseHeight(str string) sizeSpec {
|
func parseHeight(str string) sizeSpec {
|
||||||
if util.IsWindows() {
|
|
||||||
errorExit("--height options is currently not supported on Windows")
|
|
||||||
}
|
|
||||||
size := parseSize(str, 100, "height")
|
size := parseSize(str, 100, "height")
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseLayout(str string) layoutType {
|
||||||
|
switch str {
|
||||||
|
case "default":
|
||||||
|
return layoutDefault
|
||||||
|
case "reverse":
|
||||||
|
return layoutReverse
|
||||||
|
case "reverse-list":
|
||||||
|
return layoutReverseList
|
||||||
|
default:
|
||||||
|
errorExit("invalid layout (expected: default / reverse / reverse-list)")
|
||||||
|
}
|
||||||
|
return layoutDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
func 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) {
|
func parsePreviewWindow(opts *previewOpts, input string) {
|
||||||
// Default
|
// Default
|
||||||
opts.position = posRight
|
opts.position = posRight
|
||||||
@@ -840,6 +954,7 @@ func parsePreviewWindow(opts *previewOpts, input string) {
|
|||||||
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
|
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
|
||||||
for _, token := range tokens {
|
for _, token := range tokens {
|
||||||
switch token {
|
switch token {
|
||||||
|
case "":
|
||||||
case "hidden":
|
case "hidden":
|
||||||
opts.hidden = true
|
opts.hidden = true
|
||||||
case "wrap":
|
case "wrap":
|
||||||
@@ -852,6 +967,10 @@ func parsePreviewWindow(opts *previewOpts, input string) {
|
|||||||
opts.position = posLeft
|
opts.position = posLeft
|
||||||
case "right":
|
case "right":
|
||||||
opts.position = posRight
|
opts.position = posRight
|
||||||
|
case "border":
|
||||||
|
opts.border = true
|
||||||
|
case "noborder":
|
||||||
|
opts.border = false
|
||||||
default:
|
default:
|
||||||
if sizeRegex.MatchString(token) {
|
if sizeRegex.MatchString(token) {
|
||||||
opts.size = parseSize(token, 99, "window size")
|
opts.size = parseSize(token, 99, "window size")
|
||||||
@@ -951,7 +1070,15 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
case "--algo":
|
case "--algo":
|
||||||
opts.FuzzyAlgo = parseAlgo(nextString(allArgs, &i, "algorithm required (v1|v2)"))
|
opts.FuzzyAlgo = parseAlgo(nextString(allArgs, &i, "algorithm required (v1|v2)"))
|
||||||
case "--expect":
|
case "--expect":
|
||||||
opts.Expect = parseKeyChords(nextString(allArgs, &i, "key names required"), "key names required")
|
for k, v := range parseKeyChords(nextString(allArgs, &i, "key names required"), "key names required") {
|
||||||
|
opts.Expect[k] = v
|
||||||
|
}
|
||||||
|
case "--no-expect":
|
||||||
|
opts.Expect = make(map[int]string)
|
||||||
|
case "--no-phony":
|
||||||
|
opts.Phony = false
|
||||||
|
case "--phony":
|
||||||
|
opts.Phony = true
|
||||||
case "--tiebreak":
|
case "--tiebreak":
|
||||||
opts.Criteria = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
|
opts.Criteria = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
|
||||||
case "--bind":
|
case "--bind":
|
||||||
@@ -972,7 +1099,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
case "--with-nth":
|
case "--with-nth":
|
||||||
opts.WithNth = splitNth(nextString(allArgs, &i, "nth expression required"))
|
opts.WithNth = splitNth(nextString(allArgs, &i, "nth expression required"))
|
||||||
case "-s", "--sort":
|
case "-s", "--sort":
|
||||||
opts.Sort = optionalNumeric(allArgs, &i)
|
opts.Sort = optionalNumeric(allArgs, &i, 1)
|
||||||
case "+s", "--no-sort":
|
case "+s", "--no-sort":
|
||||||
opts.Sort = 0
|
opts.Sort = 0
|
||||||
case "--tac":
|
case "--tac":
|
||||||
@@ -984,9 +1111,9 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
case "+i":
|
case "+i":
|
||||||
opts.Case = CaseRespect
|
opts.Case = CaseRespect
|
||||||
case "-m", "--multi":
|
case "-m", "--multi":
|
||||||
opts.Multi = true
|
opts.Multi = optionalNumeric(allArgs, &i, maxMulti)
|
||||||
case "+m", "--no-multi":
|
case "+m", "--no-multi":
|
||||||
opts.Multi = false
|
opts.Multi = 0
|
||||||
case "--ansi":
|
case "--ansi":
|
||||||
opts.Ansi = true
|
opts.Ansi = true
|
||||||
case "--no-ansi":
|
case "--no-ansi":
|
||||||
@@ -1005,10 +1132,13 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Bold = true
|
opts.Bold = true
|
||||||
case "--no-bold":
|
case "--no-bold":
|
||||||
opts.Bold = false
|
opts.Bold = false
|
||||||
|
case "--layout":
|
||||||
|
opts.Layout = parseLayout(
|
||||||
|
nextString(allArgs, &i, "layout required (default / reverse / reverse-list)"))
|
||||||
case "--reverse":
|
case "--reverse":
|
||||||
opts.Reverse = true
|
opts.Layout = layoutReverse
|
||||||
case "--no-reverse":
|
case "--no-reverse":
|
||||||
opts.Reverse = false
|
opts.Layout = layoutDefault
|
||||||
case "--cycle":
|
case "--cycle":
|
||||||
opts.Cycle = true
|
opts.Cycle = true
|
||||||
case "--no-cycle":
|
case "--no-cycle":
|
||||||
@@ -1023,10 +1153,15 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.FileWord = true
|
opts.FileWord = true
|
||||||
case "--no-filepath-word":
|
case "--no-filepath-word":
|
||||||
opts.FileWord = false
|
opts.FileWord = false
|
||||||
|
case "--info":
|
||||||
|
opts.InfoStyle = parseInfoStyle(
|
||||||
|
nextString(allArgs, &i, "info style required"))
|
||||||
|
case "--no-info":
|
||||||
|
opts.InfoStyle = infoHidden
|
||||||
case "--inline-info":
|
case "--inline-info":
|
||||||
opts.InlineInfo = true
|
opts.InfoStyle = infoInline
|
||||||
case "--no-inline-info":
|
case "--no-inline-info":
|
||||||
opts.InlineInfo = false
|
opts.InfoStyle = infoDefault
|
||||||
case "--jump-labels":
|
case "--jump-labels":
|
||||||
opts.JumpLabels = nextString(allArgs, &i, "label characters required")
|
opts.JumpLabels = nextString(allArgs, &i, "label characters required")
|
||||||
validateJumpLabels = true
|
validateJumpLabels = true
|
||||||
@@ -1044,8 +1179,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.ReadZero = false
|
opts.ReadZero = false
|
||||||
case "--print0":
|
case "--print0":
|
||||||
opts.Printer = func(str string) { fmt.Print(str, "\x00") }
|
opts.Printer = func(str string) { fmt.Print(str, "\x00") }
|
||||||
|
opts.PrintSep = "\x00"
|
||||||
case "--no-print0":
|
case "--no-print0":
|
||||||
opts.Printer = func(str string) { fmt.Println(str) }
|
opts.Printer = func(str string) { fmt.Println(str) }
|
||||||
|
opts.PrintSep = "\n"
|
||||||
case "--print-query":
|
case "--print-query":
|
||||||
opts.PrintQuery = true
|
opts.PrintQuery = true
|
||||||
case "--no-print-query":
|
case "--no-print-query":
|
||||||
@@ -1079,7 +1216,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Preview.command = ""
|
opts.Preview.command = ""
|
||||||
case "--preview-window":
|
case "--preview-window":
|
||||||
parsePreviewWindow(&opts.Preview,
|
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":
|
case "--height":
|
||||||
opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]"))
|
opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]"))
|
||||||
case "--min-height":
|
case "--min-height":
|
||||||
@@ -1092,11 +1229,19 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Bordered = false
|
opts.Bordered = false
|
||||||
case "--border":
|
case "--border":
|
||||||
opts.Bordered = true
|
opts.Bordered = true
|
||||||
|
case "--no-unicode":
|
||||||
|
opts.Unicode = false
|
||||||
|
case "--unicode":
|
||||||
|
opts.Unicode = true
|
||||||
case "--margin":
|
case "--margin":
|
||||||
opts.Margin = parseMargin(
|
opts.Margin = parseMargin(
|
||||||
nextString(allArgs, &i, "margin required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
|
nextString(allArgs, &i, "margin required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
|
||||||
case "--tabstop":
|
case "--tabstop":
|
||||||
opts.Tabstop = nextInt(allArgs, &i, "tab stop required")
|
opts.Tabstop = nextInt(allArgs, &i, "tab stop required")
|
||||||
|
case "--clear":
|
||||||
|
opts.ClearOnExit = true
|
||||||
|
case "--no-clear":
|
||||||
|
opts.ClearOnExit = false
|
||||||
case "--version":
|
case "--version":
|
||||||
opts.Version = true
|
opts.Version = true
|
||||||
default:
|
default:
|
||||||
@@ -1116,14 +1261,22 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.WithNth = splitNth(value)
|
opts.WithNth = splitNth(value)
|
||||||
} else if match, _ := optString(arg, "-s", "--sort="); match {
|
} else if match, _ := optString(arg, "-s", "--sort="); match {
|
||||||
opts.Sort = 1 // Don't care
|
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 {
|
} else if match, value := optString(arg, "--height="); match {
|
||||||
opts.Height = parseHeight(value)
|
opts.Height = parseHeight(value)
|
||||||
} else if match, value := optString(arg, "--min-height="); match {
|
} else if match, value := optString(arg, "--min-height="); match {
|
||||||
opts.MinHeight = atoi(value)
|
opts.MinHeight = atoi(value)
|
||||||
|
} else if match, value := optString(arg, "--layout="); match {
|
||||||
|
opts.Layout = parseLayout(value)
|
||||||
|
} else if match, value := optString(arg, "--info="); match {
|
||||||
|
opts.InfoStyle = parseInfoStyle(value)
|
||||||
} else if match, value := optString(arg, "--toggle-sort="); match {
|
} else if match, value := optString(arg, "--toggle-sort="); match {
|
||||||
parseToggleSort(opts.Keymap, value)
|
parseToggleSort(opts.Keymap, value)
|
||||||
} else if match, value := optString(arg, "--expect="); match {
|
} else if match, value := optString(arg, "--expect="); match {
|
||||||
opts.Expect = parseKeyChords(value, "key names required")
|
for k, v := range parseKeyChords(value, "key names required") {
|
||||||
|
opts.Expect[k] = v
|
||||||
|
}
|
||||||
} else if match, value := optString(arg, "--tiebreak="); match {
|
} else if match, value := optString(arg, "--tiebreak="); match {
|
||||||
opts.Criteria = parseTiebreak(value)
|
opts.Criteria = parseTiebreak(value)
|
||||||
} else if match, value := optString(arg, "--color="); match {
|
} else if match, value := optString(arg, "--color="); match {
|
||||||
@@ -1182,6 +1335,9 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func postProcessOptions(opts *Options) {
|
func postProcessOptions(opts *Options) {
|
||||||
|
if util.IsWindows() && opts.Height.size > 0 {
|
||||||
|
errorExit("--height option is currently not supported on Windows")
|
||||||
|
}
|
||||||
// Default actions for CTRL-N / CTRL-P when --history is set
|
// Default actions for CTRL-N / CTRL-P when --history is set
|
||||||
if opts.History != nil {
|
if opts.History != nil {
|
||||||
if _, prs := opts.Keymap[tui.CtrlP]; !prs {
|
if _, prs := opts.Keymap[tui.CtrlP]; !prs {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/tui"
|
"github.com/junegunn/fzf/src/tui"
|
||||||
"github.com/junegunn/fzf/src/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDelimiterRegex(t *testing.T) {
|
func TestDelimiterRegex(t *testing.T) {
|
||||||
@@ -44,20 +43,20 @@ func TestDelimiterRegex(t *testing.T) {
|
|||||||
|
|
||||||
func TestDelimiterRegexString(t *testing.T) {
|
func TestDelimiterRegexString(t *testing.T) {
|
||||||
delim := delimiterRegexp("*")
|
delim := delimiterRegexp("*")
|
||||||
tokens := Tokenize(util.RunesToChars([]rune("-*--*---**---")), delim)
|
tokens := Tokenize("-*--*---**---", delim)
|
||||||
if delim.regex != nil ||
|
if delim.regex != nil ||
|
||||||
tokens[0].text.ToString() != "-*" ||
|
tokens[0].text.ToString() != "-*" ||
|
||||||
tokens[1].text.ToString() != "--*" ||
|
tokens[1].text.ToString() != "--*" ||
|
||||||
tokens[2].text.ToString() != "---*" ||
|
tokens[2].text.ToString() != "---*" ||
|
||||||
tokens[3].text.ToString() != "*" ||
|
tokens[3].text.ToString() != "*" ||
|
||||||
tokens[4].text.ToString() != "---" {
|
tokens[4].text.ToString() != "---" {
|
||||||
t.Errorf("%s %s %d", delim, tokens, len(tokens))
|
t.Errorf("%s %v %d", delim, tokens, len(tokens))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDelimiterRegexRegex(t *testing.T) {
|
func TestDelimiterRegexRegex(t *testing.T) {
|
||||||
delim := delimiterRegexp("--\\*")
|
delim := delimiterRegexp("--\\*")
|
||||||
tokens := Tokenize(util.RunesToChars([]rune("-*--*---**---")), delim)
|
tokens := Tokenize("-*--*---**---", delim)
|
||||||
if delim.str != nil ||
|
if delim.str != nil ||
|
||||||
tokens[0].text.ToString() != "-*--*" ||
|
tokens[0].text.ToString() != "-*--*" ||
|
||||||
tokens[1].text.ToString() != "---*" ||
|
tokens[1].text.ToString() != "---*" ||
|
||||||
@@ -72,7 +71,7 @@ func TestSplitNth(t *testing.T) {
|
|||||||
if len(ranges) != 1 ||
|
if len(ranges) != 1 ||
|
||||||
ranges[0].begin != rangeEllipsis ||
|
ranges[0].begin != rangeEllipsis ||
|
||||||
ranges[0].end != rangeEllipsis {
|
ranges[0].end != rangeEllipsis {
|
||||||
t.Errorf("%s", ranges)
|
t.Errorf("%v", ranges)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@@ -88,7 +87,7 @@ func TestSplitNth(t *testing.T) {
|
|||||||
ranges[7].begin != -2 || ranges[7].end != -2 ||
|
ranges[7].begin != -2 || ranges[7].end != -2 ||
|
||||||
ranges[8].begin != 2 || ranges[8].end != -2 ||
|
ranges[8].begin != 2 || ranges[8].end != -2 ||
|
||||||
ranges[9].begin != rangeEllipsis || ranges[9].end != rangeEllipsis {
|
ranges[9].begin != rangeEllipsis || ranges[9].end != rangeEllipsis {
|
||||||
t.Errorf("%s", ranges)
|
t.Errorf("%v", ranges)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,7 +99,7 @@ func TestIrrelevantNth(t *testing.T) {
|
|||||||
parseOptions(opts, words)
|
parseOptions(opts, words)
|
||||||
postProcessOptions(opts)
|
postProcessOptions(opts)
|
||||||
if len(opts.Nth) != 0 {
|
if len(opts.Nth) != 0 {
|
||||||
t.Errorf("nth should be empty: %s", opts.Nth)
|
t.Errorf("nth should be empty: %v", opts.Nth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, words := range [][]string{[]string{"--nth", "..,3", "+x"}, []string{"--nth", "3,1..", "+x"}, []string{"--nth", "..-1,1", "+x"}} {
|
for _, words := range [][]string{[]string{"--nth", "..,3", "+x"}, []string{"--nth", "3,1..", "+x"}, []string{"--nth", "..-1,1", "+x"}} {
|
||||||
@@ -109,7 +108,7 @@ func TestIrrelevantNth(t *testing.T) {
|
|||||||
parseOptions(opts, words)
|
parseOptions(opts, words)
|
||||||
postProcessOptions(opts)
|
postProcessOptions(opts)
|
||||||
if len(opts.Nth) != 0 {
|
if len(opts.Nth) != 0 {
|
||||||
t.Errorf("nth should be empty: %s", opts.Nth)
|
t.Errorf("nth should be empty: %v", opts.Nth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@@ -118,21 +117,21 @@ func TestIrrelevantNth(t *testing.T) {
|
|||||||
parseOptions(opts, words)
|
parseOptions(opts, words)
|
||||||
postProcessOptions(opts)
|
postProcessOptions(opts)
|
||||||
if len(opts.Nth) != 2 {
|
if len(opts.Nth) != 2 {
|
||||||
t.Errorf("nth should not be empty: %s", opts.Nth)
|
t.Errorf("nth should not be empty: %v", opts.Nth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseKeys(t *testing.T) {
|
func TestParseKeys(t *testing.T) {
|
||||||
pairs := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ALT-enter,alt-SPACE", "")
|
pairs := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ctrl-alt-a,ALT-enter,alt-SPACE", "")
|
||||||
check := func(i int, s string) {
|
check := func(i int, s string) {
|
||||||
if pairs[i] != s {
|
if pairs[i] != s {
|
||||||
t.Errorf("%s != %s", pairs[i], s)
|
t.Errorf("%s != %s", pairs[i], s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(pairs) != 11 {
|
if len(pairs) != 12 {
|
||||||
t.Error(11)
|
t.Error(12)
|
||||||
}
|
}
|
||||||
check(tui.CtrlZ, "ctrl-z")
|
check(tui.CtrlZ, "ctrl-z")
|
||||||
check(tui.AltZ, "alt-z")
|
check(tui.AltZ, "alt-z")
|
||||||
@@ -143,7 +142,8 @@ func TestParseKeys(t *testing.T) {
|
|||||||
check(tui.CtrlA+'g'-'a', "ctrl-G")
|
check(tui.CtrlA+'g'-'a', "ctrl-G")
|
||||||
check(tui.AltZ+'J', "J")
|
check(tui.AltZ+'J', "J")
|
||||||
check(tui.AltZ+'g', "g")
|
check(tui.AltZ+'g', "g")
|
||||||
check(tui.AltEnter, "ALT-enter")
|
check(tui.CtrlAltA, "ctrl-alt-a")
|
||||||
|
check(tui.CtrlAltM, "ALT-enter")
|
||||||
check(tui.AltSpace, "alt-SPACE")
|
check(tui.AltSpace, "alt-SPACE")
|
||||||
|
|
||||||
// Synonyms
|
// Synonyms
|
||||||
@@ -243,9 +243,10 @@ func TestBind(t *testing.T) {
|
|||||||
check(tui.CtrlA, "", actBeginningOfLine)
|
check(tui.CtrlA, "", actBeginningOfLine)
|
||||||
parseKeymap(keymap,
|
parseKeymap(keymap,
|
||||||
"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+
|
"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 (,),[,],/,:,@,%,{};,"+
|
"alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+
|
||||||
"x:Execute(foo+bar),X:execute/bar+baz/"+
|
"x:Execute(foo+bar),X:execute/bar+baz/"+
|
||||||
|
",f1:+top,f1:+top"+
|
||||||
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up")
|
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up")
|
||||||
check(tui.CtrlA, "", actKillLine)
|
check(tui.CtrlA, "", actKillLine)
|
||||||
check(tui.CtrlB, "", actToggleSort, actUp, actDown)
|
check(tui.CtrlB, "", actToggleSort, actUp, actDown)
|
||||||
@@ -253,7 +254,7 @@ func TestBind(t *testing.T) {
|
|||||||
check(tui.AltZ+',', "", actAbort)
|
check(tui.AltZ+',', "", actAbort)
|
||||||
check(tui.AltZ+':', "", actAccept)
|
check(tui.AltZ+':', "", actAccept)
|
||||||
check(tui.AltZ, "", actPageDown)
|
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.F2, "echo {}, {}, {}", actExecute)
|
||||||
check(tui.F3, "echo '({})'", actExecute)
|
check(tui.F3, "echo '({})'", actExecute)
|
||||||
check(tui.F4, "less {}", actExecute)
|
check(tui.F4, "less {}", actExecute)
|
||||||
@@ -414,3 +415,10 @@ func TestPreviewOpts(t *testing.T) {
|
|||||||
t.Error(opts.Preview)
|
t.Error(opts.Preview)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAdditiveExpect(t *testing.T) {
|
||||||
|
opts := optsFor("--expect=a", "--expect", "b", "--expect=c")
|
||||||
|
if len(opts.Expect) != 3 {
|
||||||
|
t.Error(opts.Expect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
172
src/pattern.go
172
src/pattern.go
@@ -1,6 +1,7 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -10,12 +11,12 @@ import (
|
|||||||
|
|
||||||
// fuzzy
|
// fuzzy
|
||||||
// 'exact
|
// 'exact
|
||||||
// ^exact-prefix
|
// ^prefix-exact
|
||||||
// exact-suffix$
|
// suffix-exact$
|
||||||
// !not-fuzzy
|
// !inverse-exact
|
||||||
// !'not-exact
|
// !'inverse-fuzzy
|
||||||
// !^not-exact-prefix
|
// !^inverse-prefix-exact
|
||||||
// !not-exact-suffix$
|
// !inverse-suffix-exact$
|
||||||
|
|
||||||
type termType int
|
type termType int
|
||||||
|
|
||||||
@@ -32,7 +33,11 @@ type term struct {
|
|||||||
inv bool
|
inv bool
|
||||||
text []rune
|
text []rune
|
||||||
caseSensitive bool
|
caseSensitive bool
|
||||||
origText []rune
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of a term.
|
||||||
|
func (t term) String() string {
|
||||||
|
return fmt.Sprintf("term{typ: %d, inv: %v, text: []rune(%q), caseSensitive: %v}", t.typ, t.inv, string(t.text), t.caseSensitive)
|
||||||
}
|
}
|
||||||
|
|
||||||
type termSet []term
|
type termSet []term
|
||||||
@@ -47,7 +52,9 @@ type Pattern struct {
|
|||||||
forward bool
|
forward bool
|
||||||
text []rune
|
text []rune
|
||||||
termSets []termSet
|
termSets []termSet
|
||||||
|
sortable bool
|
||||||
cacheable bool
|
cacheable bool
|
||||||
|
cacheKey string
|
||||||
delimiter Delimiter
|
delimiter Delimiter
|
||||||
nth []Range
|
nth []Range
|
||||||
procFun map[termType]algo.Algo
|
procFun map[termType]algo.Algo
|
||||||
@@ -60,7 +67,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
_splitRegex = regexp.MustCompile("\\s+")
|
_splitRegex = regexp.MustCompile(" +")
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
clearChunkCache()
|
clearChunkCache()
|
||||||
}
|
}
|
||||||
@@ -81,7 +88,10 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
|||||||
|
|
||||||
var asString string
|
var asString string
|
||||||
if extended {
|
if extended {
|
||||||
asString = strings.Trim(string(runes), " ")
|
asString = strings.TrimLeft(string(runes), " ")
|
||||||
|
for strings.HasSuffix(asString, " ") && !strings.HasSuffix(asString, "\\ ") {
|
||||||
|
asString = asString[:len(asString)-1]
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
asString = string(runes)
|
asString = string(runes)
|
||||||
}
|
}
|
||||||
@@ -92,21 +102,30 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
|||||||
}
|
}
|
||||||
|
|
||||||
caseSensitive := true
|
caseSensitive := true
|
||||||
|
sortable := true
|
||||||
termSets := []termSet{}
|
termSets := []termSet{}
|
||||||
|
|
||||||
if extended {
|
if extended {
|
||||||
termSets = parseTerms(fuzzy, caseMode, normalize, asString)
|
termSets = parseTerms(fuzzy, caseMode, normalize, asString)
|
||||||
|
// We should not sort the result if there are only inverse search terms
|
||||||
|
sortable = false
|
||||||
Loop:
|
Loop:
|
||||||
for _, termSet := range termSets {
|
for _, termSet := range termSets {
|
||||||
for idx, term := range termSet {
|
for idx, term := range termSet {
|
||||||
|
if !term.inv {
|
||||||
|
sortable = true
|
||||||
|
}
|
||||||
// If the query contains inverse search terms or OR operators,
|
// If the query contains inverse search terms or OR operators,
|
||||||
// we cannot cache the search scope
|
// we cannot cache the search scope
|
||||||
if !cacheable || idx > 0 || term.inv || !fuzzy && term.typ != termExact {
|
if !cacheable || idx > 0 || term.inv || fuzzy && term.typ != termFuzzy || !fuzzy && term.typ != termExact {
|
||||||
cacheable = false
|
cacheable = false
|
||||||
|
if sortable {
|
||||||
|
// Can't break until we see at least one non-inverse term
|
||||||
break Loop
|
break Loop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
lowerString := strings.ToLower(asString)
|
lowerString := strings.ToLower(asString)
|
||||||
caseSensitive = caseMode == CaseRespect ||
|
caseSensitive = caseMode == CaseRespect ||
|
||||||
@@ -125,11 +144,13 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
|||||||
forward: forward,
|
forward: forward,
|
||||||
text: []rune(asString),
|
text: []rune(asString),
|
||||||
termSets: termSets,
|
termSets: termSets,
|
||||||
|
sortable: sortable,
|
||||||
cacheable: cacheable,
|
cacheable: cacheable,
|
||||||
nth: nth,
|
nth: nth,
|
||||||
delimiter: delimiter,
|
delimiter: delimiter,
|
||||||
procFun: make(map[termType]algo.Algo)}
|
procFun: make(map[termType]algo.Algo)}
|
||||||
|
|
||||||
|
ptr.cacheKey = ptr.buildCacheKey()
|
||||||
ptr.procFun[termFuzzy] = fuzzyAlgo
|
ptr.procFun[termFuzzy] = fuzzyAlgo
|
||||||
ptr.procFun[termEqual] = algo.EqualMatch
|
ptr.procFun[termEqual] = algo.EqualMatch
|
||||||
ptr.procFun[termExact] = algo.ExactMatchNaive
|
ptr.procFun[termExact] = algo.ExactMatchNaive
|
||||||
@@ -141,27 +162,30 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet {
|
func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet {
|
||||||
|
str = strings.Replace(str, "\\ ", "\t", -1)
|
||||||
tokens := _splitRegex.Split(str, -1)
|
tokens := _splitRegex.Split(str, -1)
|
||||||
sets := []termSet{}
|
sets := []termSet{}
|
||||||
set := termSet{}
|
set := termSet{}
|
||||||
switchSet := false
|
switchSet := false
|
||||||
|
afterBar := false
|
||||||
for _, token := range tokens {
|
for _, token := range tokens {
|
||||||
typ, inv, text := termFuzzy, false, token
|
typ, inv, text := termFuzzy, false, strings.Replace(token, "\t", " ", -1)
|
||||||
lowerText := strings.ToLower(text)
|
lowerText := strings.ToLower(text)
|
||||||
caseSensitive := caseMode == CaseRespect ||
|
caseSensitive := caseMode == CaseRespect ||
|
||||||
caseMode == CaseSmart && text != lowerText
|
caseMode == CaseSmart && text != lowerText
|
||||||
if !caseSensitive {
|
if !caseSensitive {
|
||||||
text = lowerText
|
text = lowerText
|
||||||
}
|
}
|
||||||
origText := []rune(text)
|
|
||||||
if !fuzzy {
|
if !fuzzy {
|
||||||
typ = termExact
|
typ = termExact
|
||||||
}
|
}
|
||||||
|
|
||||||
if text == "|" {
|
if len(set) > 0 && !afterBar && text == "|" {
|
||||||
switchSet = false
|
switchSet = false
|
||||||
|
afterBar = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
afterBar = false
|
||||||
|
|
||||||
if strings.HasPrefix(text, "!") {
|
if strings.HasPrefix(text, "!") {
|
||||||
inv = true
|
inv = true
|
||||||
@@ -169,6 +193,11 @@ func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet
|
|||||||
text = text[1:]
|
text = text[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if text != "$" && strings.HasSuffix(text, "$") {
|
||||||
|
typ = termSuffix
|
||||||
|
text = text[:len(text)-1]
|
||||||
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(text, "'") {
|
if strings.HasPrefix(text, "'") {
|
||||||
// Flip exactness
|
// Flip exactness
|
||||||
if fuzzy && !inv {
|
if fuzzy && !inv {
|
||||||
@@ -179,16 +208,12 @@ func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet
|
|||||||
text = text[1:]
|
text = text[1:]
|
||||||
}
|
}
|
||||||
} else if strings.HasPrefix(text, "^") {
|
} else if strings.HasPrefix(text, "^") {
|
||||||
if strings.HasSuffix(text, "$") {
|
if typ == termSuffix {
|
||||||
typ = termEqual
|
typ = termEqual
|
||||||
text = text[1 : len(text)-1]
|
|
||||||
} else {
|
} else {
|
||||||
typ = termPrefix
|
typ = termPrefix
|
||||||
text = text[1:]
|
|
||||||
}
|
}
|
||||||
} else if strings.HasSuffix(text, "$") {
|
text = text[1:]
|
||||||
typ = termSuffix
|
|
||||||
text = text[:len(text)-1]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(text) > 0 {
|
if len(text) > 0 {
|
||||||
@@ -204,8 +229,7 @@ func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet
|
|||||||
typ: typ,
|
typ: typ,
|
||||||
inv: inv,
|
inv: inv,
|
||||||
text: textRunes,
|
text: textRunes,
|
||||||
caseSensitive: caseSensitive,
|
caseSensitive: caseSensitive})
|
||||||
origText: origText})
|
|
||||||
switchSet = true
|
switchSet = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -228,46 +252,36 @@ func (p *Pattern) AsString() string {
|
|||||||
return string(p.text)
|
return string(p.text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CacheKey is used to build string to be used as the key of result cache
|
func (p *Pattern) buildCacheKey() string {
|
||||||
func (p *Pattern) CacheKey() string {
|
|
||||||
if !p.extended {
|
if !p.extended {
|
||||||
return p.AsString()
|
return p.AsString()
|
||||||
}
|
}
|
||||||
cacheableTerms := []string{}
|
cacheableTerms := []string{}
|
||||||
for _, termSet := range p.termSets {
|
for _, termSet := range p.termSets {
|
||||||
if len(termSet) == 1 && !termSet[0].inv && (p.fuzzy || termSet[0].typ == termExact) {
|
if len(termSet) == 1 && !termSet[0].inv && (p.fuzzy || termSet[0].typ == termExact) {
|
||||||
cacheableTerms = append(cacheableTerms, string(termSet[0].origText))
|
cacheableTerms = append(cacheableTerms, string(termSet[0].text))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return strings.Join(cacheableTerms, " ")
|
return strings.Join(cacheableTerms, "\t")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CacheKey is used to build string to be used as the key of result cache
|
||||||
|
func (p *Pattern) CacheKey() string {
|
||||||
|
return p.cacheKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match returns the list of matches Items in the given Chunk
|
// Match returns the list of matches Items in the given Chunk
|
||||||
func (p *Pattern) Match(chunk *Chunk, slab *util.Slab) []*Result {
|
func (p *Pattern) Match(chunk *Chunk, slab *util.Slab) []Result {
|
||||||
// ChunkCache: Exact match
|
// ChunkCache: Exact match
|
||||||
cacheKey := p.CacheKey()
|
cacheKey := p.CacheKey()
|
||||||
if p.cacheable {
|
if p.cacheable {
|
||||||
if cached, found := _cache.Find(chunk, cacheKey); found {
|
if cached := _cache.Lookup(chunk, cacheKey); cached != nil {
|
||||||
return cached
|
return cached
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prefix/suffix cache
|
// Prefix/suffix cache
|
||||||
var space []*Result
|
space := _cache.Search(chunk, cacheKey)
|
||||||
Loop:
|
|
||||||
for idx := 1; idx < len(cacheKey); idx++ {
|
|
||||||
// [---------| ] | [ |---------]
|
|
||||||
// [--------| ] | [ |--------]
|
|
||||||
// [-------| ] | [ |-------]
|
|
||||||
prefix := cacheKey[:len(cacheKey)-idx]
|
|
||||||
suffix := cacheKey[idx:]
|
|
||||||
for _, substr := range [2]*string{&prefix, &suffix} {
|
|
||||||
if cached, found := _cache.Find(chunk, *substr); found {
|
|
||||||
space = cached
|
|
||||||
break Loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
matches := p.matchChunk(chunk, space, slab)
|
matches := p.matchChunk(chunk, space, slab)
|
||||||
|
|
||||||
@@ -277,19 +291,19 @@ Loop:
|
|||||||
return matches
|
return matches
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pattern) matchChunk(chunk *Chunk, space []*Result, slab *util.Slab) []*Result {
|
func (p *Pattern) matchChunk(chunk *Chunk, space []Result, slab *util.Slab) []Result {
|
||||||
matches := []*Result{}
|
matches := []Result{}
|
||||||
|
|
||||||
if space == nil {
|
if space == nil {
|
||||||
for _, item := range *chunk {
|
for idx := 0; idx < chunk.count; idx++ {
|
||||||
if match, _, _ := p.MatchItem(item, false, slab); match != nil {
|
if match, _, _ := p.MatchItem(&chunk.items[idx], false, slab); match != nil {
|
||||||
matches = append(matches, match)
|
matches = append(matches, *match)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, result := range space {
|
for _, result := range space {
|
||||||
if match, _, _ := p.MatchItem(result.item, false, slab); match != nil {
|
if match, _, _ := p.MatchItem(result.item, false, slab); match != nil {
|
||||||
matches = append(matches, match)
|
matches = append(matches, *match)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -299,32 +313,43 @@ func (p *Pattern) matchChunk(chunk *Chunk, space []*Result, slab *util.Slab) []*
|
|||||||
// MatchItem returns true if the Item is a match
|
// MatchItem returns true if the Item is a match
|
||||||
func (p *Pattern) MatchItem(item *Item, withPos bool, slab *util.Slab) (*Result, []Offset, *[]int) {
|
func (p *Pattern) MatchItem(item *Item, withPos bool, slab *util.Slab) (*Result, []Offset, *[]int) {
|
||||||
if p.extended {
|
if p.extended {
|
||||||
if offsets, bonus, trimLen, pos := p.extendedMatch(item, withPos, slab); len(offsets) == len(p.termSets) {
|
if offsets, bonus, pos := p.extendedMatch(item, withPos, slab); len(offsets) == len(p.termSets) {
|
||||||
return buildResult(item, offsets, bonus, trimLen), offsets, pos
|
result := buildResult(item, offsets, bonus)
|
||||||
|
return &result, offsets, pos
|
||||||
}
|
}
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
offset, bonus, trimLen, pos := p.basicMatch(item, withPos, slab)
|
offset, bonus, pos := p.basicMatch(item, withPos, slab)
|
||||||
if sidx := offset[0]; sidx >= 0 {
|
if sidx := offset[0]; sidx >= 0 {
|
||||||
offsets := []Offset{offset}
|
offsets := []Offset{offset}
|
||||||
return buildResult(item, offsets, bonus, trimLen), offsets, pos
|
result := buildResult(item, offsets, bonus)
|
||||||
|
return &result, offsets, pos
|
||||||
}
|
}
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset, int, int, *[]int) {
|
func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset, int, *[]int) {
|
||||||
input := p.prepareInput(item)
|
var input []Token
|
||||||
|
if len(p.nth) == 0 {
|
||||||
|
input = []Token{Token{text: &item.text, prefixLength: 0}}
|
||||||
|
} else {
|
||||||
|
input = p.transformInput(item)
|
||||||
|
}
|
||||||
if p.fuzzy {
|
if p.fuzzy {
|
||||||
return p.iter(p.fuzzyAlgo, input, p.caseSensitive, p.normalize, p.forward, p.text, withPos, slab)
|
return p.iter(p.fuzzyAlgo, input, p.caseSensitive, p.normalize, p.forward, p.text, withPos, slab)
|
||||||
}
|
}
|
||||||
return p.iter(algo.ExactMatchNaive, input, p.caseSensitive, p.normalize, p.forward, p.text, withPos, slab)
|
return p.iter(algo.ExactMatchNaive, input, p.caseSensitive, p.normalize, p.forward, p.text, withPos, slab)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Offset, int, int, *[]int) {
|
func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Offset, int, *[]int) {
|
||||||
input := p.prepareInput(item)
|
var input []Token
|
||||||
|
if len(p.nth) == 0 {
|
||||||
|
input = []Token{Token{text: &item.text, prefixLength: 0}}
|
||||||
|
} else {
|
||||||
|
input = p.transformInput(item)
|
||||||
|
}
|
||||||
offsets := []Offset{}
|
offsets := []Offset{}
|
||||||
var totalScore int
|
var totalScore int
|
||||||
var totalTrimLen int
|
|
||||||
var allPos *[]int
|
var allPos *[]int
|
||||||
if withPos {
|
if withPos {
|
||||||
allPos = &[]int{}
|
allPos = &[]int{}
|
||||||
@@ -332,16 +357,15 @@ func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Of
|
|||||||
for _, termSet := range p.termSets {
|
for _, termSet := range p.termSets {
|
||||||
var offset Offset
|
var offset Offset
|
||||||
var currentScore int
|
var currentScore int
|
||||||
var trimLen int
|
|
||||||
matched := false
|
matched := false
|
||||||
for _, term := range termSet {
|
for _, term := range termSet {
|
||||||
pfun := p.procFun[term.typ]
|
pfun := p.procFun[term.typ]
|
||||||
off, score, tLen, pos := p.iter(pfun, input, term.caseSensitive, p.normalize, p.forward, term.text, withPos, slab)
|
off, score, pos := p.iter(pfun, input, term.caseSensitive, p.normalize, p.forward, term.text, withPos, slab)
|
||||||
if sidx := off[0]; sidx >= 0 {
|
if sidx := off[0]; sidx >= 0 {
|
||||||
if term.inv {
|
if term.inv {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
offset, currentScore, trimLen = off, score, tLen
|
offset, currentScore = off, score
|
||||||
matched = true
|
matched = true
|
||||||
if withPos {
|
if withPos {
|
||||||
if pos != nil {
|
if pos != nil {
|
||||||
@@ -354,7 +378,7 @@ func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Of
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
} else if term.inv {
|
} else if term.inv {
|
||||||
offset, currentScore, trimLen = Offset{0, 0}, 0, 0
|
offset, currentScore = Offset{0, 0}, 0
|
||||||
matched = true
|
matched = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -362,31 +386,25 @@ func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Of
|
|||||||
if matched {
|
if matched {
|
||||||
offsets = append(offsets, offset)
|
offsets = append(offsets, offset)
|
||||||
totalScore += currentScore
|
totalScore += currentScore
|
||||||
totalTrimLen += trimLen
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return offsets, totalScore, totalTrimLen, allPos
|
return offsets, totalScore, allPos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pattern) prepareInput(item *Item) []Token {
|
func (p *Pattern) transformInput(item *Item) []Token {
|
||||||
if item.transformed != nil {
|
if item.transformed != nil {
|
||||||
return item.transformed
|
return *item.transformed
|
||||||
}
|
}
|
||||||
|
|
||||||
var ret []Token
|
tokens := Tokenize(item.text.ToString(), p.delimiter)
|
||||||
if len(p.nth) == 0 {
|
ret := Transform(tokens, p.nth)
|
||||||
ret = []Token{Token{text: &item.text, prefixLength: 0, trimLength: int32(item.text.TrimLength())}}
|
item.transformed = &ret
|
||||||
} else {
|
|
||||||
tokens := Tokenize(item.text, p.delimiter)
|
|
||||||
ret = Transform(tokens, p.nth)
|
|
||||||
}
|
|
||||||
item.transformed = ret
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pattern) iter(pfun algo.Algo, tokens []Token, caseSensitive bool, normalize bool, forward bool, pattern []rune, withPos bool, slab *util.Slab) (Offset, int, int, *[]int) {
|
func (p *Pattern) iter(pfun algo.Algo, tokens []Token, caseSensitive bool, normalize bool, forward bool, pattern []rune, withPos bool, slab *util.Slab) (Offset, int, *[]int) {
|
||||||
for _, part := range tokens {
|
for _, part := range tokens {
|
||||||
if res, pos := pfun(caseSensitive, normalize, forward, *part.text, pattern, withPos, slab); res.Start >= 0 {
|
if res, pos := pfun(caseSensitive, normalize, forward, part.text, pattern, withPos, slab); res.Start >= 0 {
|
||||||
sidx := int32(res.Start) + part.prefixLength
|
sidx := int32(res.Start) + part.prefixLength
|
||||||
eidx := int32(res.End) + part.prefixLength
|
eidx := int32(res.End) + part.prefixLength
|
||||||
if pos != nil {
|
if pos != nil {
|
||||||
@@ -394,8 +412,8 @@ func (p *Pattern) iter(pfun algo.Algo, tokens []Token, caseSensitive bool, norma
|
|||||||
(*pos)[idx] += int(part.prefixLength)
|
(*pos)[idx] += int(part.prefixLength)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Offset{sidx, eidx}, res.Score, int(part.trimLength), pos
|
return Offset{sidx, eidx}, res.Score, pos
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Offset{-1, -1}, 0, -1, nil
|
return Offset{-1, -1}, 0, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ func init() {
|
|||||||
|
|
||||||
func TestParseTermsExtended(t *testing.T) {
|
func TestParseTermsExtended(t *testing.T) {
|
||||||
terms := parseTerms(true, CaseSmart, false,
|
terms := parseTerms(true, CaseSmart, false,
|
||||||
"| aaa 'bbb ^ccc ddd$ !eee !'fff !^ggg !hhh$ | ^iii$ ^xxx | 'yyy | | zzz$ | !ZZZ |")
|
"aaa 'bbb ^ccc ddd$ !eee !'fff !^ggg !hhh$ | ^iii$ ^xxx | 'yyy | zzz$ | !ZZZ |")
|
||||||
if len(terms) != 9 ||
|
if len(terms) != 9 ||
|
||||||
terms[0][0].typ != termFuzzy || terms[0][0].inv ||
|
terms[0][0].typ != termFuzzy || terms[0][0].inv ||
|
||||||
terms[1][0].typ != termExact || terms[1][0].inv ||
|
terms[1][0].typ != termExact || terms[1][0].inv ||
|
||||||
@@ -31,20 +31,12 @@ func TestParseTermsExtended(t *testing.T) {
|
|||||||
terms[8][1].typ != termExact || terms[8][1].inv ||
|
terms[8][1].typ != termExact || terms[8][1].inv ||
|
||||||
terms[8][2].typ != termSuffix || terms[8][2].inv ||
|
terms[8][2].typ != termSuffix || terms[8][2].inv ||
|
||||||
terms[8][3].typ != termExact || !terms[8][3].inv {
|
terms[8][3].typ != termExact || !terms[8][3].inv {
|
||||||
t.Errorf("%s", terms)
|
t.Errorf("%v", terms)
|
||||||
}
|
}
|
||||||
for idx, termSet := range terms[:8] {
|
for _, termSet := range terms[:8] {
|
||||||
term := termSet[0]
|
term := termSet[0]
|
||||||
if len(term.text) != 3 {
|
if len(term.text) != 3 {
|
||||||
t.Errorf("%s", term)
|
t.Errorf("%v", term)
|
||||||
}
|
|
||||||
if idx > 0 && len(term.origText) != 4+idx/5 {
|
|
||||||
t.Errorf("%s", term)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, term := range terms[8] {
|
|
||||||
if len(term.origText) != 4 {
|
|
||||||
t.Errorf("%s", term)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,14 +53,14 @@ func TestParseTermsExtendedExact(t *testing.T) {
|
|||||||
terms[5][0].typ != termFuzzy || !terms[5][0].inv || len(terms[5][0].text) != 3 ||
|
terms[5][0].typ != termFuzzy || !terms[5][0].inv || len(terms[5][0].text) != 3 ||
|
||||||
terms[6][0].typ != termPrefix || !terms[6][0].inv || len(terms[6][0].text) != 3 ||
|
terms[6][0].typ != termPrefix || !terms[6][0].inv || len(terms[6][0].text) != 3 ||
|
||||||
terms[7][0].typ != termSuffix || !terms[7][0].inv || len(terms[7][0].text) != 3 {
|
terms[7][0].typ != termSuffix || !terms[7][0].inv || len(terms[7][0].text) != 3 {
|
||||||
t.Errorf("%s", terms)
|
t.Errorf("%v", terms)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseTermsEmpty(t *testing.T) {
|
func TestParseTermsEmpty(t *testing.T) {
|
||||||
terms := parseTerms(true, CaseSmart, false, "' $ ^ !' !^ !$")
|
terms := parseTerms(true, CaseSmart, false, "' ^ !' !^")
|
||||||
if len(terms) != 0 {
|
if len(terms) != 0 {
|
||||||
t.Errorf("%s", terms)
|
t.Errorf("%v", terms)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,10 +69,11 @@ func TestExact(t *testing.T) {
|
|||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true,
|
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true,
|
||||||
[]Range{}, Delimiter{}, []rune("'abc"))
|
[]Range{}, Delimiter{}, []rune("'abc"))
|
||||||
|
chars := util.ToChars([]byte("aabbcc abc"))
|
||||||
res, pos := algo.ExactMatchNaive(
|
res, pos := algo.ExactMatchNaive(
|
||||||
pattern.caseSensitive, pattern.normalize, pattern.forward, util.RunesToChars([]rune("aabbcc abc")), pattern.termSets[0][0].text, true, nil)
|
pattern.caseSensitive, pattern.normalize, pattern.forward, &chars, pattern.termSets[0][0].text, true, nil)
|
||||||
if res.Start != 7 || res.End != 10 {
|
if res.Start != 7 || res.End != 10 {
|
||||||
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
|
t.Errorf("%v / %d / %d", pattern.termSets, res.Start, res.End)
|
||||||
}
|
}
|
||||||
if pos != nil {
|
if pos != nil {
|
||||||
t.Errorf("pos is expected to be nil")
|
t.Errorf("pos is expected to be nil")
|
||||||
@@ -93,10 +86,11 @@ func TestEqual(t *testing.T) {
|
|||||||
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("^AbC$"))
|
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("^AbC$"))
|
||||||
|
|
||||||
match := func(str string, sidxExpected int, eidxExpected int) {
|
match := func(str string, sidxExpected int, eidxExpected int) {
|
||||||
|
chars := util.ToChars([]byte(str))
|
||||||
res, pos := algo.EqualMatch(
|
res, pos := algo.EqualMatch(
|
||||||
pattern.caseSensitive, pattern.normalize, pattern.forward, util.RunesToChars([]rune(str)), pattern.termSets[0][0].text, true, nil)
|
pattern.caseSensitive, pattern.normalize, pattern.forward, &chars, pattern.termSets[0][0].text, true, nil)
|
||||||
if res.Start != sidxExpected || res.End != eidxExpected {
|
if res.Start != sidxExpected || res.End != eidxExpected {
|
||||||
t.Errorf("%s / %d / %d", pattern.termSets, res.Start, res.End)
|
t.Errorf("%v / %d / %d", pattern.termSets, res.Start, res.End)
|
||||||
}
|
}
|
||||||
if pos != nil {
|
if pos != nil {
|
||||||
t.Errorf("pos is expected to be nil")
|
t.Errorf("pos is expected to be nil")
|
||||||
@@ -133,30 +127,29 @@ func TestCaseSensitivity(t *testing.T) {
|
|||||||
|
|
||||||
func TestOrigTextAndTransformed(t *testing.T) {
|
func TestOrigTextAndTransformed(t *testing.T) {
|
||||||
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("jg"))
|
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("jg"))
|
||||||
tokens := Tokenize(util.RunesToChars([]rune("junegunn")), Delimiter{})
|
tokens := Tokenize("junegunn", Delimiter{})
|
||||||
trans := Transform(tokens, []Range{Range{1, 1}})
|
trans := Transform(tokens, []Range{Range{1, 1}})
|
||||||
|
|
||||||
origBytes := []byte("junegunn.choi")
|
origBytes := []byte("junegunn.choi")
|
||||||
for _, extended := range []bool{false, true} {
|
for _, extended := range []bool{false, true} {
|
||||||
chunk := Chunk{
|
chunk := Chunk{count: 1}
|
||||||
&Item{
|
chunk.items[0] = Item{
|
||||||
text: util.RunesToChars([]rune("junegunn")),
|
text: util.ToChars([]byte("junegunn")),
|
||||||
origText: &origBytes,
|
origText: &origBytes,
|
||||||
transformed: trans},
|
transformed: &trans}
|
||||||
}
|
|
||||||
pattern.extended = extended
|
pattern.extended = extended
|
||||||
matches := pattern.matchChunk(&chunk, nil, slab) // No cache
|
matches := pattern.matchChunk(&chunk, nil, slab) // No cache
|
||||||
if !(matches[0].item.text.ToString() == "junegunn" &&
|
if !(matches[0].item.text.ToString() == "junegunn" &&
|
||||||
string(*matches[0].item.origText) == "junegunn.choi" &&
|
string(*matches[0].item.origText) == "junegunn.choi" &&
|
||||||
reflect.DeepEqual(matches[0].item.transformed, trans)) {
|
reflect.DeepEqual(*matches[0].item.transformed, trans)) {
|
||||||
t.Error("Invalid match result", matches)
|
t.Error("Invalid match result", matches)
|
||||||
}
|
}
|
||||||
|
|
||||||
match, offsets, pos := pattern.MatchItem(chunk[0], true, slab)
|
match, offsets, pos := pattern.MatchItem(&chunk.items[0], true, slab)
|
||||||
if !(match.item.text.ToString() == "junegunn" &&
|
if !(match.item.text.ToString() == "junegunn" &&
|
||||||
string(*match.item.origText) == "junegunn.choi" &&
|
string(*match.item.origText) == "junegunn.choi" &&
|
||||||
offsets[0][0] == 0 && offsets[0][1] == 5 &&
|
offsets[0][0] == 0 && offsets[0][1] == 5 &&
|
||||||
reflect.DeepEqual(match.item.transformed, trans)) {
|
reflect.DeepEqual(*match.item.transformed, trans)) {
|
||||||
t.Error("Invalid match result", match, offsets, extended)
|
t.Error("Invalid match result", match, offsets, extended)
|
||||||
}
|
}
|
||||||
if !((*pos)[0] == 4 && (*pos)[1] == 0) {
|
if !((*pos)[0] == 4 && (*pos)[1] == 0) {
|
||||||
@@ -167,40 +160,47 @@ func TestOrigTextAndTransformed(t *testing.T) {
|
|||||||
|
|
||||||
func TestCacheKey(t *testing.T) {
|
func TestCacheKey(t *testing.T) {
|
||||||
test := func(extended bool, patStr string, expected string, cacheable bool) {
|
test := func(extended bool, patStr string, expected string, cacheable bool) {
|
||||||
|
clearPatternCache()
|
||||||
pat := BuildPattern(true, algo.FuzzyMatchV2, extended, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune(patStr))
|
pat := BuildPattern(true, algo.FuzzyMatchV2, extended, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune(patStr))
|
||||||
if pat.CacheKey() != expected {
|
if pat.CacheKey() != expected {
|
||||||
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
|
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
|
||||||
}
|
}
|
||||||
if pat.cacheable != cacheable {
|
if pat.cacheable != cacheable {
|
||||||
t.Errorf("Expected: %s, actual: %s (%s)", cacheable, pat.cacheable, patStr)
|
t.Errorf("Expected: %t, actual: %t (%s)", cacheable, pat.cacheable, patStr)
|
||||||
}
|
}
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
}
|
}
|
||||||
test(false, "foo !bar", "foo !bar", true)
|
test(false, "foo !bar", "foo !bar", true)
|
||||||
test(false, "foo | bar !baz", "foo | bar !baz", true)
|
test(false, "foo | bar !baz", "foo | bar !baz", true)
|
||||||
test(true, "foo bar baz", "foo bar baz", true)
|
test(true, "foo bar baz", "foo\tbar\tbaz", true)
|
||||||
test(true, "foo !bar", "foo", false)
|
test(true, "foo !bar", "foo", false)
|
||||||
test(true, "foo !bar baz", "foo baz", false)
|
test(true, "foo !bar baz", "foo\tbaz", false)
|
||||||
test(true, "foo | bar baz", "baz", false)
|
test(true, "foo | bar baz", "baz", false)
|
||||||
test(true, "foo | bar | baz", "", false)
|
test(true, "foo | bar | baz", "", false)
|
||||||
test(true, "foo | bar !baz", "", false)
|
test(true, "foo | bar !baz", "", false)
|
||||||
test(true, "| | | foo", "foo", true)
|
test(true, "| | foo", "", false)
|
||||||
|
test(true, "| | | foo", "foo", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheable(t *testing.T) {
|
func TestCacheable(t *testing.T) {
|
||||||
test := func(fuzzy bool, str string, cacheable bool) {
|
test := func(fuzzy bool, str string, expected string, cacheable bool) {
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pat := BuildPattern(fuzzy, algo.FuzzyMatchV2, true, CaseSmart, true, true, true, []Range{}, Delimiter{}, []rune(str))
|
pat := BuildPattern(fuzzy, algo.FuzzyMatchV2, true, CaseSmart, true, true, true, []Range{}, Delimiter{}, []rune(str))
|
||||||
|
if pat.CacheKey() != expected {
|
||||||
|
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
|
||||||
|
}
|
||||||
if cacheable != pat.cacheable {
|
if cacheable != pat.cacheable {
|
||||||
t.Errorf("Invalid Pattern.cacheable for \"%s\": %v (expected: %v)", str, pat.cacheable, cacheable)
|
t.Errorf("Invalid Pattern.cacheable for \"%s\": %v (expected: %v)", str, pat.cacheable, cacheable)
|
||||||
}
|
}
|
||||||
|
clearPatternCache()
|
||||||
}
|
}
|
||||||
test(true, "foo bar", true)
|
test(true, "foo bar", "foo\tbar", true)
|
||||||
test(true, "foo 'bar", true)
|
test(true, "foo 'bar", "foo\tbar", false)
|
||||||
test(true, "foo !bar", false)
|
test(true, "foo !bar", "foo", false)
|
||||||
|
|
||||||
test(false, "foo bar", true)
|
test(false, "foo bar", "foo\tbar", true)
|
||||||
test(false, "foo '", true)
|
test(false, "foo 'bar", "foo", false)
|
||||||
test(false, "foo 'bar", false)
|
test(false, "foo '", "foo", true)
|
||||||
test(false, "foo !bar", false)
|
test(false, "foo 'bar", "foo", false)
|
||||||
|
test(false, "foo !bar", "foo", false)
|
||||||
}
|
}
|
||||||
|
|||||||
122
src/reader.go
122
src/reader.go
@@ -4,6 +4,10 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
@@ -13,20 +17,96 @@ type Reader struct {
|
|||||||
pusher func([]byte) bool
|
pusher func([]byte) bool
|
||||||
eventBox *util.EventBox
|
eventBox *util.EventBox
|
||||||
delimNil bool
|
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, wait bool) *Reader {
|
||||||
|
return &Reader{pusher, eventBox, delimNil, int32(EvtReady), make(chan bool, 1), sync.Mutex{}, nil, nil, false, wait}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) startEventPoller() {
|
||||||
|
go func() {
|
||||||
|
ptr := &r.event
|
||||||
|
pollInterval := readerPollIntervalMin
|
||||||
|
for {
|
||||||
|
if atomic.CompareAndSwapInt32(ptr, int32(EvtReadNew), int32(EvtReady)) {
|
||||||
|
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
|
||||||
|
if pollInterval > readerPollIntervalMax {
|
||||||
|
pollInterval = readerPollIntervalMax
|
||||||
|
}
|
||||||
|
}
|
||||||
|
time.Sleep(pollInterval)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) fin(success bool) {
|
||||||
|
atomic.StoreInt32(&r.event, int32(EvtReadFin))
|
||||||
|
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
|
// ReadSource reads data from the default command or from standard input
|
||||||
func (r *Reader) ReadSource() {
|
func (r *Reader) ReadSource() {
|
||||||
|
r.startEventPoller()
|
||||||
|
var success bool
|
||||||
if util.IsTty() {
|
if util.IsTty() {
|
||||||
|
// The default command for *nix requires bash
|
||||||
|
shell := "bash"
|
||||||
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
||||||
if len(cmd) == 0 {
|
if len(cmd) == 0 {
|
||||||
cmd = defaultCommand
|
success = r.readFromCommand(&shell, defaultCommand)
|
||||||
}
|
|
||||||
r.readFromCommand(cmd)
|
|
||||||
} else {
|
} else {
|
||||||
r.readFromStdin()
|
success = r.readFromCommand(nil, cmd)
|
||||||
}
|
}
|
||||||
r.eventBox.Set(EvtReadFin, nil)
|
} else {
|
||||||
|
success = r.readFromStdin()
|
||||||
|
}
|
||||||
|
r.fin(success)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) feed(src io.Reader) {
|
func (r *Reader) feed(src io.Reader) {
|
||||||
@@ -40,7 +120,7 @@ func (r *Reader) feed(src io.Reader) {
|
|||||||
// end in delim.
|
// end in delim.
|
||||||
bytea, err := reader.ReadBytes(delim)
|
bytea, err := reader.ReadBytes(delim)
|
||||||
byteaLen := len(bytea)
|
byteaLen := len(bytea)
|
||||||
if len(bytea) > 0 {
|
if byteaLen > 0 {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// get rid of carriage return if under Windows:
|
// get rid of carriage return if under Windows:
|
||||||
if util.IsWindows() && byteaLen >= 2 && bytea[byteaLen-2] == byte('\r') {
|
if util.IsWindows() && byteaLen >= 2 && bytea[byteaLen-2] == byte('\r') {
|
||||||
@@ -50,7 +130,7 @@ func (r *Reader) feed(src io.Reader) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if r.pusher(bytea) {
|
if r.pusher(bytea) {
|
||||||
r.eventBox.Set(EvtReadNew, nil)
|
atomic.StoreInt32(&r.event, int32(EvtReadNew))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -59,20 +139,30 @@ func (r *Reader) feed(src io.Reader) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readFromStdin() {
|
func (r *Reader) readFromStdin() bool {
|
||||||
r.feed(os.Stdin)
|
r.feed(os.Stdin)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readFromCommand(cmd string) {
|
func (r *Reader) readFromCommand(shell *string, command string) bool {
|
||||||
listCommand := util.ExecCommand(cmd)
|
r.mutex.Lock()
|
||||||
out, err := listCommand.StdoutPipe()
|
r.killed = false
|
||||||
if err != nil {
|
r.command = &command
|
||||||
return
|
if shell != nil {
|
||||||
|
r.exec = util.ExecCommandWith(*shell, command, true)
|
||||||
|
} else {
|
||||||
|
r.exec = util.ExecCommand(command, true)
|
||||||
}
|
}
|
||||||
err = listCommand.Start()
|
out, err := r.exec.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
r.mutex.Unlock()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
err = r.exec.Start()
|
||||||
|
r.mutex.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
defer listCommand.Wait()
|
|
||||||
r.feed(out)
|
r.feed(out)
|
||||||
|
return r.exec.Wait() == nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package fzf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
@@ -9,9 +10,11 @@ import (
|
|||||||
func TestReadFromCommand(t *testing.T) {
|
func TestReadFromCommand(t *testing.T) {
|
||||||
strs := []string{}
|
strs := []string{}
|
||||||
eb := util.NewEventBox()
|
eb := util.NewEventBox()
|
||||||
reader := Reader{
|
reader := NewReader(
|
||||||
pusher: func(s []byte) bool { strs = append(strs, string(s)); return true },
|
func(s []byte) bool { strs = append(strs, string(s)); return true },
|
||||||
eventBox: eb}
|
eb, false, true)
|
||||||
|
|
||||||
|
reader.startEventPoller()
|
||||||
|
|
||||||
// Check EventBox
|
// Check EventBox
|
||||||
if eb.Peek(EvtReadNew) {
|
if eb.Peek(EvtReadNew) {
|
||||||
@@ -19,21 +22,16 @@ func TestReadFromCommand(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Normal command
|
// Normal command
|
||||||
reader.readFromCommand(`echo abc && echo def`)
|
reader.fin(reader.readFromCommand(nil, `echo abc && echo def`))
|
||||||
if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" {
|
if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" {
|
||||||
t.Errorf("%s", strs)
|
t.Errorf("%s", strs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check EventBox again
|
// Check EventBox again
|
||||||
if !eb.Peek(EvtReadNew) {
|
eb.WaitFor(EvtReadFin)
|
||||||
t.Error("EvtReadNew should be set yet")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait should return immediately
|
// Wait should return immediately
|
||||||
eb.Wait(func(events *util.Events) {
|
eb.Wait(func(events *util.Events) {
|
||||||
if _, found := (*events)[EvtReadNew]; !found {
|
|
||||||
t.Errorf("%s", events)
|
|
||||||
}
|
|
||||||
events.Clear()
|
events.Clear()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -42,8 +40,14 @@ func TestReadFromCommand(t *testing.T) {
|
|||||||
t.Error("EvtReadNew should not be set yet")
|
t.Error("EvtReadNew should not be set yet")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure that event poller is finished
|
||||||
|
time.Sleep(readerPollIntervalMax)
|
||||||
|
|
||||||
|
// Restart event poller
|
||||||
|
reader.startEventPoller()
|
||||||
|
|
||||||
// Failing command
|
// Failing command
|
||||||
reader.readFromCommand(`no-such-command`)
|
reader.fin(reader.readFromCommand(nil, `no-such-command`))
|
||||||
strs = []string{}
|
strs = []string{}
|
||||||
if len(strs) > 0 {
|
if len(strs) > 0 {
|
||||||
t.Errorf("%s", strs)
|
t.Errorf("%s", strs)
|
||||||
@@ -51,6 +55,9 @@ func TestReadFromCommand(t *testing.T) {
|
|||||||
|
|
||||||
// Check EventBox again
|
// Check EventBox again
|
||||||
if eb.Peek(EvtReadNew) {
|
if eb.Peek(EvtReadNew) {
|
||||||
t.Error("Command failed. EvtReadNew should be set")
|
t.Error("Command failed. EvtReadNew should not be set")
|
||||||
|
}
|
||||||
|
if !eb.Peek(EvtReadFin) {
|
||||||
|
t.Error("EvtReadFin should be set")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,25 +16,19 @@ type colorOffset struct {
|
|||||||
offset [2]int32
|
offset [2]int32
|
||||||
color tui.ColorPair
|
color tui.ColorPair
|
||||||
attr tui.Attr
|
attr tui.Attr
|
||||||
index int32
|
|
||||||
}
|
|
||||||
|
|
||||||
type rank struct {
|
|
||||||
points [4]uint16
|
|
||||||
index int32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result struct {
|
type Result struct {
|
||||||
item *Item
|
item *Item
|
||||||
rank rank
|
points [4]uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildResult(item *Item, offsets []Offset, score int, trimLen int) *Result {
|
func buildResult(item *Item, offsets []Offset, score int) Result {
|
||||||
if len(offsets) > 1 {
|
if len(offsets) > 1 {
|
||||||
sort.Sort(ByOrder(offsets))
|
sort.Sort(ByOrder(offsets))
|
||||||
}
|
}
|
||||||
|
|
||||||
result := Result{item: item, rank: rank{index: item.index}}
|
result := Result{item: item}
|
||||||
numChars := item.text.Length()
|
numChars := item.text.Length()
|
||||||
minBegin := math.MaxUint16
|
minBegin := math.MaxUint16
|
||||||
minEnd := math.MaxUint16
|
minEnd := math.MaxUint16
|
||||||
@@ -57,8 +51,7 @@ func buildResult(item *Item, offsets []Offset, score int, trimLen int) *Result {
|
|||||||
// Higher is better
|
// Higher is better
|
||||||
val = math.MaxUint16 - util.AsUint16(score)
|
val = math.MaxUint16 - util.AsUint16(score)
|
||||||
case byLength:
|
case byLength:
|
||||||
// If offsets is empty, trimLen will be 0, but we don't care
|
val = item.TrimLength()
|
||||||
val = util.AsUint16(trimLen)
|
|
||||||
case byBegin, byEnd:
|
case byBegin, byEnd:
|
||||||
if validOffsetFound {
|
if validOffsetFound {
|
||||||
whitePrefixLen := 0
|
whitePrefixLen := 0
|
||||||
@@ -72,14 +65,14 @@ func buildResult(item *Item, offsets []Offset, score int, trimLen int) *Result {
|
|||||||
if criterion == byBegin {
|
if criterion == byBegin {
|
||||||
val = util.AsUint16(minEnd - whitePrefixLen)
|
val = util.AsUint16(minEnd - whitePrefixLen)
|
||||||
} else {
|
} else {
|
||||||
val = util.AsUint16(math.MaxUint16 - math.MaxUint16*(maxEnd-whitePrefixLen)/trimLen)
|
val = util.AsUint16(math.MaxUint16 - math.MaxUint16*(maxEnd-whitePrefixLen)/int(item.TrimLength()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.rank.points[idx] = val
|
result.points[3-idx] = val
|
||||||
}
|
}
|
||||||
|
|
||||||
return &result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort criteria to use. Never changes once fzf is started.
|
// Sort criteria to use. Never changes once fzf is started.
|
||||||
@@ -87,11 +80,11 @@ var sortCriteria []criterion
|
|||||||
|
|
||||||
// Index returns ordinal index of the Item
|
// Index returns ordinal index of the Item
|
||||||
func (result *Result) Index() int32 {
|
func (result *Result) Index() int32 {
|
||||||
return result.item.index
|
return result.item.Index()
|
||||||
}
|
}
|
||||||
|
|
||||||
func minRank() rank {
|
func minRank() Result {
|
||||||
return rank{index: 0, points: [4]uint16{math.MaxUint16, 0, 0, 0}}
|
return Result{item: &minItem, points: [4]uint16{math.MaxUint16, 0, 0, 0}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme, color tui.ColorPair, attr tui.Attr, current bool) []colorOffset {
|
func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme, color tui.ColorPair, attr tui.Attr, current bool) []colorOffset {
|
||||||
@@ -202,7 +195,7 @@ func (a ByOrder) Less(i, j int) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ByRelevance is for sorting Items
|
// ByRelevance is for sorting Items
|
||||||
type ByRelevance []*Result
|
type ByRelevance []Result
|
||||||
|
|
||||||
func (a ByRelevance) Len() int {
|
func (a ByRelevance) Len() int {
|
||||||
return len(a)
|
return len(a)
|
||||||
@@ -213,11 +206,11 @@ func (a ByRelevance) Swap(i, j int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a ByRelevance) Less(i, j int) bool {
|
func (a ByRelevance) Less(i, j int) bool {
|
||||||
return compareRanks((*a[i]).rank, (*a[j]).rank, false)
|
return compareRanks(a[i], a[j], false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ByRelevanceTac is for sorting Items
|
// ByRelevanceTac is for sorting Items
|
||||||
type ByRelevanceTac []*Result
|
type ByRelevanceTac []Result
|
||||||
|
|
||||||
func (a ByRelevanceTac) Len() int {
|
func (a ByRelevanceTac) Len() int {
|
||||||
return len(a)
|
return len(a)
|
||||||
@@ -228,18 +221,5 @@ func (a ByRelevanceTac) Swap(i, j int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a ByRelevanceTac) Less(i, j int) bool {
|
func (a ByRelevanceTac) Less(i, j int) bool {
|
||||||
return compareRanks((*a[i]).rank, (*a[j]).rank, true)
|
return compareRanks(a[i], a[j], true)
|
||||||
}
|
|
||||||
|
|
||||||
func compareRanks(irank rank, jrank rank, tac bool) bool {
|
|
||||||
for idx := 0; idx < 4; idx++ {
|
|
||||||
left := irank.points[idx]
|
|
||||||
right := jrank.points[idx]
|
|
||||||
if left < right {
|
|
||||||
return true
|
|
||||||
} else if left > right {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (irank.index <= jrank.index) != tac
|
|
||||||
}
|
}
|
||||||
|
|||||||
16
src/result_others.go
Normal file
16
src/result_others.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// +build !386,!amd64
|
||||||
|
|
||||||
|
package fzf
|
||||||
|
|
||||||
|
func compareRanks(irank Result, jrank Result, tac bool) bool {
|
||||||
|
for idx := 3; idx >= 0; idx-- {
|
||||||
|
left := irank.points[idx]
|
||||||
|
right := jrank.points[idx]
|
||||||
|
if left < right {
|
||||||
|
return true
|
||||||
|
} else if left > right {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (irank.item.Index() <= jrank.item.Index()) != tac
|
||||||
|
}
|
||||||
@@ -11,6 +11,11 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func withIndex(i *Item, index int) *Item {
|
||||||
|
(*i).text.Index = int32(index)
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
func TestOffsetSort(t *testing.T) {
|
func TestOffsetSort(t *testing.T) {
|
||||||
offsets := []Offset{
|
offsets := []Offset{
|
||||||
Offset{3, 5}, Offset{2, 7},
|
Offset{3, 5}, Offset{2, 7},
|
||||||
@@ -26,10 +31,10 @@ func TestOffsetSort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRankComparison(t *testing.T) {
|
func TestRankComparison(t *testing.T) {
|
||||||
rank := func(vals ...uint16) rank {
|
rank := func(vals ...uint16) Result {
|
||||||
return rank{
|
return Result{
|
||||||
points: [4]uint16{vals[0], vals[1], vals[2], vals[3]},
|
points: [4]uint16{vals[0], vals[1], vals[2], vals[3]},
|
||||||
index: int32(vals[4])}
|
item: &Item{text: util.Chars{Index: int32(vals[4])}}}
|
||||||
}
|
}
|
||||||
if compareRanks(rank(3, 0, 0, 0, 5), rank(2, 0, 0, 0, 7), false) ||
|
if compareRanks(rank(3, 0, 0, 0, 5), rank(2, 0, 0, 0, 7), false) ||
|
||||||
!compareRanks(rank(3, 0, 0, 0, 5), rank(3, 0, 0, 0, 6), false) ||
|
!compareRanks(rank(3, 0, 0, 0, 5), rank(3, 0, 0, 0, 6), false) ||
|
||||||
@@ -52,36 +57,41 @@ func TestResultRank(t *testing.T) {
|
|||||||
sortCriteria = []criterion{byScore, byLength}
|
sortCriteria = []criterion{byScore, byLength}
|
||||||
|
|
||||||
strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")}
|
strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")}
|
||||||
item1 := buildResult(&Item{text: util.RunesToChars(strs[0]), index: 1}, []Offset{}, 2, 3)
|
item1 := buildResult(
|
||||||
if item1.rank.points[0] != math.MaxUint16-2 || // Bonus
|
withIndex(&Item{text: util.RunesToChars(strs[0])}, 1), []Offset{}, 2)
|
||||||
item1.rank.points[1] != 3 || // Length
|
if item1.points[3] != math.MaxUint16-2 || // Bonus
|
||||||
item1.rank.points[2] != 0 || // Unused
|
item1.points[2] != 3 || // Length
|
||||||
item1.rank.points[3] != 0 || // Unused
|
item1.points[1] != 0 || // Unused
|
||||||
item1.item.index != 1 {
|
item1.points[0] != 0 || // Unused
|
||||||
t.Error(item1.rank)
|
item1.item.Index() != 1 {
|
||||||
|
t.Error(item1)
|
||||||
}
|
}
|
||||||
// Only differ in index
|
// Only differ in index
|
||||||
item2 := buildResult(&Item{text: util.RunesToChars(strs[0])}, []Offset{}, 2, 3)
|
item2 := buildResult(&Item{text: util.RunesToChars(strs[0])}, []Offset{}, 2)
|
||||||
|
|
||||||
items := []*Result{item1, item2}
|
items := []Result{item1, item2}
|
||||||
sort.Sort(ByRelevance(items))
|
sort.Sort(ByRelevance(items))
|
||||||
if items[0] != item2 || items[1] != item1 {
|
if items[0] != item2 || items[1] != item1 {
|
||||||
t.Error(items)
|
t.Error(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
items = []*Result{item2, item1, item1, item2}
|
items = []Result{item2, item1, item1, item2}
|
||||||
sort.Sort(ByRelevance(items))
|
sort.Sort(ByRelevance(items))
|
||||||
if items[0] != item2 || items[1] != item2 ||
|
if items[0] != item2 || items[1] != item2 ||
|
||||||
items[2] != item1 || items[3] != item1 {
|
items[2] != item1 || items[3] != item1 {
|
||||||
t.Error(items, item1, item1.item.index, item2, item2.item.index)
|
t.Error(items, item1, item1.item.Index(), item2, item2.item.Index())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by relevance
|
// Sort by relevance
|
||||||
item3 := buildResult(&Item{index: 2}, []Offset{Offset{1, 3}, Offset{5, 7}}, 3, 0)
|
item3 := buildResult(
|
||||||
item4 := buildResult(&Item{index: 2}, []Offset{Offset{1, 2}, Offset{6, 7}}, 4, 0)
|
withIndex(&Item{}, 2), []Offset{Offset{1, 3}, Offset{5, 7}}, 3)
|
||||||
item5 := buildResult(&Item{index: 2}, []Offset{Offset{1, 3}, Offset{5, 7}}, 5, 0)
|
item4 := buildResult(
|
||||||
item6 := buildResult(&Item{index: 2}, []Offset{Offset{1, 2}, Offset{6, 7}}, 6, 0)
|
withIndex(&Item{}, 2), []Offset{Offset{1, 2}, Offset{6, 7}}, 4)
|
||||||
items = []*Result{item1, item2, item3, item4, item5, item6}
|
item5 := buildResult(
|
||||||
|
withIndex(&Item{}, 2), []Offset{Offset{1, 3}, Offset{5, 7}}, 5)
|
||||||
|
item6 := buildResult(
|
||||||
|
withIndex(&Item{}, 2), []Offset{Offset{1, 2}, Offset{6, 7}}, 6)
|
||||||
|
items = []Result{item1, item2, item3, item4, item5, item6}
|
||||||
sort.Sort(ByRelevance(items))
|
sort.Sort(ByRelevance(items))
|
||||||
if !(items[0] == item6 && items[1] == item5 &&
|
if !(items[0] == item6 && items[1] == item5 &&
|
||||||
items[2] == item4 && items[3] == item3 &&
|
items[2] == item4 && items[3] == item3 &&
|
||||||
|
|||||||
16
src/result_x86.go
Normal file
16
src/result_x86.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// +build 386 amd64
|
||||||
|
|
||||||
|
package fzf
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
func compareRanks(irank Result, jrank Result, tac bool) bool {
|
||||||
|
left := *(*uint64)(unsafe.Pointer(&irank.points[0]))
|
||||||
|
right := *(*uint64)(unsafe.Pointer(&jrank.points[0]))
|
||||||
|
if left < right {
|
||||||
|
return true
|
||||||
|
} else if left > right {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return (irank.item.Index() <= jrank.item.Index()) != tac
|
||||||
|
}
|
||||||
856
src/terminal.go
856
src/terminal.go
File diff suppressed because it is too large
Load Diff
@@ -10,7 +10,7 @@ import (
|
|||||||
func newItem(str string) *Item {
|
func newItem(str string) *Item {
|
||||||
bytes := []byte(str)
|
bytes := []byte(str)
|
||||||
trimmed, _, _ := extractColor(str, nil, nil)
|
trimmed, _, _ := extractColor(str, nil, nil)
|
||||||
return &Item{origText: &bytes, text: util.RunesToChars([]rune(trimmed))}
|
return &Item{origText: &bytes, text: util.ToChars([]byte(trimmed))}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReplacePlaceholder(t *testing.T) {
|
func TestReplacePlaceholder(t *testing.T) {
|
||||||
@@ -21,73 +21,119 @@ func TestReplacePlaceholder(t *testing.T) {
|
|||||||
newItem("foo'bar \x1b[31mbaz\x1b[m"),
|
newItem("foo'bar \x1b[31mbaz\x1b[m"),
|
||||||
newItem("FOO'BAR \x1b[31mBAZ\x1b[m")}
|
newItem("FOO'BAR \x1b[31mBAZ\x1b[m")}
|
||||||
|
|
||||||
|
delim := "'"
|
||||||
|
var regex *regexp.Regexp
|
||||||
|
|
||||||
var result string
|
var result string
|
||||||
check := func(expected string) {
|
check := func(expected string) {
|
||||||
if result != expected {
|
if result != expected {
|
||||||
t.Errorf("expected: %s, actual: %s", expected, result)
|
t.Errorf("expected: %s, actual: %s", expected, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
printsep := "\n"
|
||||||
// {}, preserve ansi
|
// {}, 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'")
|
check("echo ' foo'\\''bar \x1b[31mbaz\x1b[m'")
|
||||||
|
|
||||||
// {}, strip ansi
|
// {}, strip ansi
|
||||||
result = replacePlaceholder("echo {}", true, Delimiter{}, false, "query", items1)
|
result = replacePlaceholder("echo {}", true, Delimiter{}, printsep, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz'")
|
check("echo ' foo'\\''bar baz'")
|
||||||
|
|
||||||
// {}, with multiple items
|
// {}, 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'")
|
check("echo 'foo'\\''bar baz'")
|
||||||
|
|
||||||
// {..}, strip leading whitespaces, preserve ansi
|
// {..}, 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'")
|
check("echo 'foo'\\''bar \x1b[31mbaz\x1b[m'")
|
||||||
|
|
||||||
// {..}, strip leading whitespaces, strip ansi
|
// {..}, 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'")
|
check("echo 'foo'\\''bar baz'")
|
||||||
|
|
||||||
// {q}
|
// {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'")
|
check("echo ' foo'\\''bar baz' 'query'")
|
||||||
|
|
||||||
// {q}, multiple items
|
// {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'")
|
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'")
|
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}/''")
|
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}/''")
|
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}/'' ''")
|
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
|
// 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}/'' ''")
|
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
|
// 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 /")
|
check("echo /")
|
||||||
|
|
||||||
// No match, but with selections
|
// 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'")
|
check("echo /' foo'\\''bar baz'")
|
||||||
|
|
||||||
// String delimiter
|
// String delimiter
|
||||||
delim := "'"
|
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
||||||
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, false, "query", items1)
|
|
||||||
check("echo ' foo'\\''bar baz'/'foo'/'bar baz'")
|
check("echo ' foo'\\''bar baz'/'foo'/'bar baz'")
|
||||||
|
|
||||||
// Regex delimiter
|
// Regex delimiter
|
||||||
regex := regexp.MustCompile("[oa]+")
|
regex = regexp.MustCompile("[oa]+")
|
||||||
// foo'bar baz
|
// foo'bar baz
|
||||||
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, false, "query", items1)
|
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz'/'f'/'r b'/''\\''bar b'")
|
check("echo ' foo'\\''bar baz'/'f'/'r b'/''\\''bar b'")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestQuoteEntryCmd(t *testing.T) {
|
||||||
|
tests := map[string]string{
|
||||||
|
`"`: `^"\^"^"`,
|
||||||
|
`\`: `^"\\^"`,
|
||||||
|
`\"`: `^"\\\^"^"`,
|
||||||
|
`"\\\"`: `^"\^"\\\\\\\^"^"`,
|
||||||
|
`&|<>()@^%!`: `^"^&^|^<^>^(^)^@^^^%^!^"`,
|
||||||
|
`%USERPROFILE%`: `^"^%USERPROFILE^%^"`,
|
||||||
|
`C:\Program Files (x86)\`: `^"C:\\Program Files ^(x86^)\\^"`,
|
||||||
|
}
|
||||||
|
|
||||||
|
for input, expected := range tests {
|
||||||
|
escaped := quoteEntryCmd(input)
|
||||||
|
if escaped != expected {
|
||||||
|
t.Errorf("Input: %s, expected: %s, actual %s", input, expected, escaped)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,3 +11,11 @@ import (
|
|||||||
func notifyOnResize(resizeChan chan<- os.Signal) {
|
func notifyOnResize(resizeChan chan<- os.Signal) {
|
||||||
signal.Notify(resizeChan, syscall.SIGWINCH)
|
signal.Notify(resizeChan, syscall.SIGWINCH)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func notifyStop(p *os.Process) {
|
||||||
|
p.Signal(syscall.SIGSTOP)
|
||||||
|
}
|
||||||
|
|
||||||
|
func notifyOnCont(resizeChan chan<- os.Signal) {
|
||||||
|
signal.Notify(resizeChan, syscall.SIGCONT)
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,3 +9,11 @@ import (
|
|||||||
func notifyOnResize(resizeChan chan<- os.Signal) {
|
func notifyOnResize(resizeChan chan<- os.Signal) {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func notifyStop(p *os.Process) {
|
||||||
|
// NOOP
|
||||||
|
}
|
||||||
|
|
||||||
|
func notifyOnCont(resizeChan chan<- os.Signal) {
|
||||||
|
// NOOP
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -20,7 +22,11 @@ type Range struct {
|
|||||||
type Token struct {
|
type Token struct {
|
||||||
text *util.Chars
|
text *util.Chars
|
||||||
prefixLength int32
|
prefixLength int32
|
||||||
trimLength int32
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of a Token.
|
||||||
|
func (t Token) String() string {
|
||||||
|
return fmt.Sprintf("Token{text: %s, prefixLength: %d}", t.text, t.prefixLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delimiter for tokenizing the input
|
// Delimiter for tokenizing the input
|
||||||
@@ -29,6 +35,11 @@ type Delimiter struct {
|
|||||||
str *string
|
str *string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of a Delimeter.
|
||||||
|
func (d Delimiter) String() string {
|
||||||
|
return fmt.Sprintf("Delimiter{regex: %v, str: &%q}", d.regex, *d.str)
|
||||||
|
}
|
||||||
|
|
||||||
func newRange(begin int, end int) Range {
|
func newRange(begin int, end int) Range {
|
||||||
if begin == 1 {
|
if begin == 1 {
|
||||||
begin = rangeEllipsis
|
begin = rangeEllipsis
|
||||||
@@ -75,14 +86,14 @@ func ParseRange(str *string) (Range, bool) {
|
|||||||
return newRange(n, n), true
|
return newRange(n, n), true
|
||||||
}
|
}
|
||||||
|
|
||||||
func withPrefixLengths(tokens []util.Chars, begin int) []Token {
|
func withPrefixLengths(tokens []string, begin int) []Token {
|
||||||
ret := make([]Token, len(tokens))
|
ret := make([]Token, len(tokens))
|
||||||
|
|
||||||
prefixLength := begin
|
prefixLength := begin
|
||||||
for idx, token := range tokens {
|
for idx := range tokens {
|
||||||
// NOTE: &tokens[idx] instead of &tokens
|
chars := util.ToChars([]byte(tokens[idx]))
|
||||||
ret[idx] = Token{&tokens[idx], int32(prefixLength), int32(token.TrimLength())}
|
ret[idx] = Token{&chars, int32(prefixLength)}
|
||||||
prefixLength += token.Length()
|
prefixLength += chars.Length()
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
@@ -93,16 +104,15 @@ const (
|
|||||||
awkWhite
|
awkWhite
|
||||||
)
|
)
|
||||||
|
|
||||||
func awkTokenizer(input util.Chars) ([]util.Chars, int) {
|
func awkTokenizer(input string) ([]string, int) {
|
||||||
// 9, 32
|
// 9, 32
|
||||||
ret := []util.Chars{}
|
ret := []string{}
|
||||||
prefixLength := 0
|
prefixLength := 0
|
||||||
state := awkNil
|
state := awkNil
|
||||||
numChars := input.Length()
|
|
||||||
begin := 0
|
begin := 0
|
||||||
end := 0
|
end := 0
|
||||||
for idx := 0; idx < numChars; idx++ {
|
for idx := 0; idx < len(input); idx++ {
|
||||||
r := input.Get(idx)
|
r := input[idx]
|
||||||
white := r == 9 || r == 32
|
white := r == 9 || r == 32
|
||||||
switch state {
|
switch state {
|
||||||
case awkNil:
|
case awkNil:
|
||||||
@@ -120,19 +130,19 @@ func awkTokenizer(input util.Chars) ([]util.Chars, int) {
|
|||||||
if white {
|
if white {
|
||||||
end = idx + 1
|
end = idx + 1
|
||||||
} else {
|
} else {
|
||||||
ret = append(ret, input.Slice(begin, end))
|
ret = append(ret, input[begin:end])
|
||||||
state, begin, end = awkBlack, idx, idx+1
|
state, begin, end = awkBlack, idx, idx+1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if begin < end {
|
if begin < end {
|
||||||
ret = append(ret, input.Slice(begin, end))
|
ret = append(ret, input[begin:end])
|
||||||
}
|
}
|
||||||
return ret, prefixLength
|
return ret, prefixLength
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tokenize tokenizes the given string with the delimiter
|
// Tokenize tokenizes the given string with the delimiter
|
||||||
func Tokenize(text util.Chars, delimiter Delimiter) []Token {
|
func Tokenize(text string, delimiter Delimiter) []Token {
|
||||||
if delimiter.str == nil && delimiter.regex == nil {
|
if delimiter.str == nil && delimiter.regex == nil {
|
||||||
// AWK-style (\S+\s*)
|
// AWK-style (\S+\s*)
|
||||||
tokens, prefixLength := awkTokenizer(text)
|
tokens, prefixLength := awkTokenizer(text)
|
||||||
@@ -140,36 +150,31 @@ func Tokenize(text util.Chars, delimiter Delimiter) []Token {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if delimiter.str != nil {
|
if delimiter.str != nil {
|
||||||
return withPrefixLengths(text.Split(*delimiter.str), 0)
|
return withPrefixLengths(strings.SplitAfter(text, *delimiter.str), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME performance
|
// FIXME performance
|
||||||
var tokens []string
|
var tokens []string
|
||||||
if delimiter.regex != nil {
|
if delimiter.regex != nil {
|
||||||
str := text.ToString()
|
for len(text) > 0 {
|
||||||
for len(str) > 0 {
|
loc := delimiter.regex.FindStringIndex(text)
|
||||||
loc := delimiter.regex.FindStringIndex(str)
|
if len(loc) < 2 {
|
||||||
if loc == nil {
|
loc = []int{0, len(text)}
|
||||||
loc = []int{0, len(str)}
|
|
||||||
}
|
}
|
||||||
last := util.Max(loc[1], 1)
|
last := util.Max(loc[1], 1)
|
||||||
tokens = append(tokens, str[:last])
|
tokens = append(tokens, text[:last])
|
||||||
str = str[last:]
|
text = text[last:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
asRunes := make([]util.Chars, len(tokens))
|
return withPrefixLengths(tokens, 0)
|
||||||
for i, token := range tokens {
|
|
||||||
asRunes[i] = util.RunesToChars([]rune(token))
|
|
||||||
}
|
|
||||||
return withPrefixLengths(asRunes, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func joinTokens(tokens []Token) []rune {
|
func joinTokens(tokens []Token) string {
|
||||||
ret := []rune{}
|
var output bytes.Buffer
|
||||||
for _, token := range tokens {
|
for _, token := range tokens {
|
||||||
ret = append(ret, token.text.ToRunes()...)
|
output.WriteString(token.text.ToString())
|
||||||
}
|
}
|
||||||
return ret
|
return output.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform is used to transform the input when --with-nth option is given
|
// Transform is used to transform the input when --with-nth option is given
|
||||||
@@ -182,7 +187,7 @@ func Transform(tokens []Token, withNth []Range) []Token {
|
|||||||
if r.begin == r.end {
|
if r.begin == r.end {
|
||||||
idx := r.begin
|
idx := r.begin
|
||||||
if idx == rangeEllipsis {
|
if idx == rangeEllipsis {
|
||||||
chars := util.RunesToChars(joinTokens(tokens))
|
chars := util.ToChars([]byte(joinTokens(tokens)))
|
||||||
parts = append(parts, &chars)
|
parts = append(parts, &chars)
|
||||||
} else {
|
} else {
|
||||||
if idx < 0 {
|
if idx < 0 {
|
||||||
@@ -225,15 +230,15 @@ func Transform(tokens []Token, withNth []Range) []Token {
|
|||||||
var merged util.Chars
|
var merged util.Chars
|
||||||
switch len(parts) {
|
switch len(parts) {
|
||||||
case 0:
|
case 0:
|
||||||
merged = util.RunesToChars([]rune{})
|
merged = util.ToChars([]byte{})
|
||||||
case 1:
|
case 1:
|
||||||
merged = *parts[0]
|
merged = *parts[0]
|
||||||
default:
|
default:
|
||||||
runes := []rune{}
|
var output bytes.Buffer
|
||||||
for _, part := range parts {
|
for _, part := range parts {
|
||||||
runes = append(runes, part.ToRunes()...)
|
output.WriteString(part.ToString())
|
||||||
}
|
}
|
||||||
merged = util.RunesToChars(runes)
|
merged = util.ToChars(output.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
var prefixLength int32
|
var prefixLength int32
|
||||||
@@ -242,7 +247,7 @@ func Transform(tokens []Token, withNth []Range) []Token {
|
|||||||
} else {
|
} else {
|
||||||
prefixLength = 0
|
prefixLength = 0
|
||||||
}
|
}
|
||||||
transTokens[idx] = Token{&merged, prefixLength, int32(merged.TrimLength())}
|
transTokens[idx] = Token{&merged, prefixLength}
|
||||||
}
|
}
|
||||||
return transTokens
|
return transTokens
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package fzf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseRange(t *testing.T) {
|
func TestParseRange(t *testing.T) {
|
||||||
@@ -11,35 +9,35 @@ func TestParseRange(t *testing.T) {
|
|||||||
i := ".."
|
i := ".."
|
||||||
r, _ := ParseRange(&i)
|
r, _ := ParseRange(&i)
|
||||||
if r.begin != rangeEllipsis || r.end != rangeEllipsis {
|
if r.begin != rangeEllipsis || r.end != rangeEllipsis {
|
||||||
t.Errorf("%s", r)
|
t.Errorf("%v", r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
i := "3.."
|
i := "3.."
|
||||||
r, _ := ParseRange(&i)
|
r, _ := ParseRange(&i)
|
||||||
if r.begin != 3 || r.end != rangeEllipsis {
|
if r.begin != 3 || r.end != rangeEllipsis {
|
||||||
t.Errorf("%s", r)
|
t.Errorf("%v", r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
i := "3..5"
|
i := "3..5"
|
||||||
r, _ := ParseRange(&i)
|
r, _ := ParseRange(&i)
|
||||||
if r.begin != 3 || r.end != 5 {
|
if r.begin != 3 || r.end != 5 {
|
||||||
t.Errorf("%s", r)
|
t.Errorf("%v", r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
i := "-3..-5"
|
i := "-3..-5"
|
||||||
r, _ := ParseRange(&i)
|
r, _ := ParseRange(&i)
|
||||||
if r.begin != -3 || r.end != -5 {
|
if r.begin != -3 || r.end != -5 {
|
||||||
t.Errorf("%s", r)
|
t.Errorf("%v", r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
i := "3"
|
i := "3"
|
||||||
r, _ := ParseRange(&i)
|
r, _ := ParseRange(&i)
|
||||||
if r.begin != 3 || r.end != 3 {
|
if r.begin != 3 || r.end != 3 {
|
||||||
t.Errorf("%s", r)
|
t.Errorf("%v", r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,23 +45,23 @@ func TestParseRange(t *testing.T) {
|
|||||||
func TestTokenize(t *testing.T) {
|
func TestTokenize(t *testing.T) {
|
||||||
// AWK-style
|
// AWK-style
|
||||||
input := " abc: def: ghi "
|
input := " abc: def: ghi "
|
||||||
tokens := Tokenize(util.RunesToChars([]rune(input)), Delimiter{})
|
tokens := Tokenize(input, Delimiter{})
|
||||||
if tokens[0].text.ToString() != "abc: " || tokens[0].prefixLength != 2 || tokens[0].trimLength != 4 {
|
if tokens[0].text.ToString() != "abc: " || tokens[0].prefixLength != 2 {
|
||||||
t.Errorf("%s", tokens)
|
t.Errorf("%s", tokens)
|
||||||
}
|
}
|
||||||
|
|
||||||
// With delimiter
|
// With delimiter
|
||||||
tokens = Tokenize(util.RunesToChars([]rune(input)), delimiterRegexp(":"))
|
tokens = Tokenize(input, delimiterRegexp(":"))
|
||||||
if tokens[0].text.ToString() != " abc:" || tokens[0].prefixLength != 0 || tokens[0].trimLength != 4 {
|
if tokens[0].text.ToString() != " abc:" || tokens[0].prefixLength != 0 {
|
||||||
t.Errorf("%s", tokens)
|
t.Error(tokens[0].text.ToString(), tokens[0].prefixLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
// With delimiter regex
|
// With delimiter regex
|
||||||
tokens = Tokenize(util.RunesToChars([]rune(input)), delimiterRegexp("\\s+"))
|
tokens = Tokenize(input, delimiterRegexp("\\s+"))
|
||||||
if tokens[0].text.ToString() != " " || tokens[0].prefixLength != 0 || tokens[0].trimLength != 0 ||
|
if tokens[0].text.ToString() != " " || tokens[0].prefixLength != 0 ||
|
||||||
tokens[1].text.ToString() != "abc: " || tokens[1].prefixLength != 2 || tokens[1].trimLength != 4 ||
|
tokens[1].text.ToString() != "abc: " || tokens[1].prefixLength != 2 ||
|
||||||
tokens[2].text.ToString() != "def: " || tokens[2].prefixLength != 8 || tokens[2].trimLength != 4 ||
|
tokens[2].text.ToString() != "def: " || tokens[2].prefixLength != 8 ||
|
||||||
tokens[3].text.ToString() != "ghi " || tokens[3].prefixLength != 14 || tokens[3].trimLength != 3 {
|
tokens[3].text.ToString() != "ghi " || tokens[3].prefixLength != 14 {
|
||||||
t.Errorf("%s", tokens)
|
t.Errorf("%s", tokens)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,11 +69,11 @@ func TestTokenize(t *testing.T) {
|
|||||||
func TestTransform(t *testing.T) {
|
func TestTransform(t *testing.T) {
|
||||||
input := " abc: def: ghi: jkl"
|
input := " abc: def: ghi: jkl"
|
||||||
{
|
{
|
||||||
tokens := Tokenize(util.RunesToChars([]rune(input)), Delimiter{})
|
tokens := Tokenize(input, Delimiter{})
|
||||||
{
|
{
|
||||||
ranges := splitNth("1,2,3")
|
ranges := splitNth("1,2,3")
|
||||||
tx := Transform(tokens, ranges)
|
tx := Transform(tokens, ranges)
|
||||||
if string(joinTokens(tx)) != "abc: def: ghi: " {
|
if joinTokens(tx) != "abc: def: ghi: " {
|
||||||
t.Errorf("%s", tx)
|
t.Errorf("%s", tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,11 +91,11 @@ func TestTransform(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
tokens := Tokenize(util.RunesToChars([]rune(input)), delimiterRegexp(":"))
|
tokens := Tokenize(input, delimiterRegexp(":"))
|
||||||
{
|
{
|
||||||
ranges := splitNth("1..2,3,2..,1")
|
ranges := splitNth("1..2,3,2..,1")
|
||||||
tx := Transform(tokens, ranges)
|
tx := Transform(tokens, ranges)
|
||||||
if string(joinTokens(tx)) != " abc: def: ghi: def: ghi: jkl abc:" ||
|
if joinTokens(tx) != " abc: def: ghi: def: ghi: jkl abc:" ||
|
||||||
len(tx) != 4 ||
|
len(tx) != 4 ||
|
||||||
tx[0].text.ToString() != " abc: def:" || tx[0].prefixLength != 0 ||
|
tx[0].text.ToString() != " abc: def:" || tx[0].prefixLength != 0 ||
|
||||||
tx[1].text.ToString() != " ghi:" || tx[1].prefixLength != 12 ||
|
tx[1].text.ToString() != " ghi:" || tx[1].prefixLength != 12 ||
|
||||||
|
|||||||
@@ -26,20 +26,19 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Init() {}
|
func (r *FullscreenRenderer) Init() {}
|
||||||
func (r *FullscreenRenderer) Pause() {}
|
func (r *FullscreenRenderer) Pause(bool) {}
|
||||||
|
func (r *FullscreenRenderer) Resume(bool) {}
|
||||||
func (r *FullscreenRenderer) Clear() {}
|
func (r *FullscreenRenderer) Clear() {}
|
||||||
func (r *FullscreenRenderer) Refresh() {}
|
func (r *FullscreenRenderer) Refresh() {}
|
||||||
func (r *FullscreenRenderer) Close() {}
|
func (r *FullscreenRenderer) Close() {}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Resume() bool { return false }
|
|
||||||
func (r *FullscreenRenderer) DoesAutoWrap() bool { return false }
|
func (r *FullscreenRenderer) DoesAutoWrap() bool { return false }
|
||||||
func (r *FullscreenRenderer) IsOptimized() bool { return false }
|
|
||||||
func (r *FullscreenRenderer) GetChar() Event { return Event{} }
|
func (r *FullscreenRenderer) GetChar() Event { return Event{} }
|
||||||
func (r *FullscreenRenderer) MaxX() int { return 0 }
|
func (r *FullscreenRenderer) MaxX() int { return 0 }
|
||||||
func (r *FullscreenRenderer) MaxY() int { return 0 }
|
func (r *FullscreenRenderer) MaxY() int { return 0 }
|
||||||
|
|
||||||
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {}
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
238
src/tui/light.go
238
src/tui/light.go
@@ -27,12 +27,19 @@ const (
|
|||||||
|
|
||||||
const consoleDevice string = "/dev/tty"
|
const consoleDevice string = "/dev/tty"
|
||||||
|
|
||||||
var offsetRegexp *regexp.Regexp = regexp.MustCompile("\x1b\\[([0-9]+);([0-9]+)R")
|
var offsetRegexp *regexp.Regexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R")
|
||||||
|
|
||||||
func openTtyIn() *os.File {
|
func openTtyIn() *os.File {
|
||||||
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
|
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("Failed to open " + consoleDevice)
|
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)
|
||||||
}
|
}
|
||||||
return in
|
return in
|
||||||
}
|
}
|
||||||
@@ -47,12 +54,14 @@ func (r *LightRenderer) stderrInternal(str string, allowNLCR bool) {
|
|||||||
runes := []rune{}
|
runes := []rune{}
|
||||||
for len(bytes) > 0 {
|
for len(bytes) > 0 {
|
||||||
r, sz := utf8.DecodeRune(bytes)
|
r, sz := utf8.DecodeRune(bytes)
|
||||||
if r == utf8.RuneError || r < 32 &&
|
nlcr := r == '\n' || r == '\r'
|
||||||
r != '\x1b' && (!allowNLCR || r != '\n' && r != '\r') {
|
if r >= 32 || r == '\x1b' || nlcr {
|
||||||
runes = append(runes, '?')
|
if r == utf8.RuneError || nlcr && !allowNLCR {
|
||||||
|
runes = append(runes, ' ')
|
||||||
} else {
|
} else {
|
||||||
runes = append(runes, r)
|
runes = append(runes, r)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
bytes = bytes[sz:]
|
bytes = bytes[sz:]
|
||||||
}
|
}
|
||||||
r.queued += string(runes)
|
r.queued += string(runes)
|
||||||
@@ -74,6 +83,7 @@ type LightRenderer struct {
|
|||||||
theme *ColorTheme
|
theme *ColorTheme
|
||||||
mouse bool
|
mouse bool
|
||||||
forceBlack bool
|
forceBlack bool
|
||||||
|
clearOnExit bool
|
||||||
prevDownTime time.Time
|
prevDownTime time.Time
|
||||||
clickY []int
|
clickY []int
|
||||||
ttyin *os.File
|
ttyin *os.File
|
||||||
@@ -103,18 +113,20 @@ type LightWindow struct {
|
|||||||
posx int
|
posx int
|
||||||
posy int
|
posy int
|
||||||
tabstop int
|
tabstop int
|
||||||
|
fg Color
|
||||||
bg Color
|
bg Color
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLightRenderer(theme *ColorTheme, forceBlack bool, mouse bool, tabstop int, maxHeightFunc func(int) int) Renderer {
|
func NewLightRenderer(theme *ColorTheme, forceBlack bool, mouse bool, tabstop int, clearOnExit bool, fullscreen bool, maxHeightFunc func(int) int) Renderer {
|
||||||
r := LightRenderer{
|
r := LightRenderer{
|
||||||
theme: theme,
|
theme: theme,
|
||||||
forceBlack: forceBlack,
|
forceBlack: forceBlack,
|
||||||
mouse: mouse,
|
mouse: mouse,
|
||||||
|
clearOnExit: clearOnExit,
|
||||||
ttyin: openTtyIn(),
|
ttyin: openTtyIn(),
|
||||||
yoffset: 0,
|
yoffset: 0,
|
||||||
tabstop: tabstop,
|
tabstop: tabstop,
|
||||||
fullscreen: false,
|
fullscreen: fullscreen,
|
||||||
upOneLine: false,
|
upOneLine: false,
|
||||||
maxHeightFunc: maxHeightFunc}
|
maxHeightFunc: maxHeightFunc}
|
||||||
return &r
|
return &r
|
||||||
@@ -142,16 +154,18 @@ func (r *LightRenderer) findOffset() (row int, col int) {
|
|||||||
for tries := 0; tries < offsetPollTries; tries++ {
|
for tries := 0; tries < offsetPollTries; tries++ {
|
||||||
bytes = r.getBytesInternal(bytes, tries > 0)
|
bytes = r.getBytesInternal(bytes, tries > 0)
|
||||||
offsets := offsetRegexp.FindSubmatch(bytes)
|
offsets := offsetRegexp.FindSubmatch(bytes)
|
||||||
if len(offsets) > 2 {
|
if len(offsets) > 3 {
|
||||||
return atoi(string(offsets[1]), 0) - 1, atoi(string(offsets[2]), 0) - 1
|
// add anything we skipped over to the input buffer
|
||||||
|
r.buffer = append(r.buffer, offsets[1]...)
|
||||||
|
return atoi(string(offsets[2]), 0) - 1, atoi(string(offsets[3]), 0) - 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1, -1
|
return -1, -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func repeat(s string, times int) string {
|
func repeat(r rune, times int) string {
|
||||||
if times > 0 {
|
if times > 0 {
|
||||||
return strings.Repeat(s, times)
|
return strings.Repeat(string(r), times)
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -174,20 +188,24 @@ func (r *LightRenderer) Init() {
|
|||||||
}
|
}
|
||||||
r.origState = origState
|
r.origState = origState
|
||||||
terminal.MakeRaw(fd)
|
terminal.MakeRaw(fd)
|
||||||
terminalHeight, capHeight := r.updateTerminalSize()
|
r.updateTerminalSize()
|
||||||
if capHeight == terminalHeight {
|
|
||||||
r.fullscreen = true
|
|
||||||
r.height = terminalHeight
|
|
||||||
}
|
|
||||||
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
||||||
|
|
||||||
if r.fullscreen {
|
if r.fullscreen {
|
||||||
r.smcup()
|
r.smcup()
|
||||||
} else {
|
} else {
|
||||||
|
// We assume that --no-clear is used for repetitive relaunching of fzf.
|
||||||
|
// So we do not clear the lower bottom of the screen.
|
||||||
|
if r.clearOnExit {
|
||||||
r.csi("J")
|
r.csi("J")
|
||||||
|
}
|
||||||
y, x := r.findOffset()
|
y, x := r.findOffset()
|
||||||
r.mouse = r.mouse && y >= 0
|
r.mouse = r.mouse && y >= 0
|
||||||
if x > 0 {
|
// When --no-clear is used for repetitive relaunching, there is a small
|
||||||
|
// time frame between fzf processes where the user keystrokes are not
|
||||||
|
// captured by either of fzf process which can cause x offset to be
|
||||||
|
// increased and we're left with unwanted extra new line.
|
||||||
|
if x > 0 && r.clearOnExit {
|
||||||
r.upOneLine = true
|
r.upOneLine = true
|
||||||
r.makeSpace()
|
r.makeSpace()
|
||||||
}
|
}
|
||||||
@@ -202,7 +220,9 @@ func (r *LightRenderer) Init() {
|
|||||||
r.csi(fmt.Sprintf("%dA", r.MaxY()-1))
|
r.csi(fmt.Sprintf("%dA", r.MaxY()-1))
|
||||||
r.csi("G")
|
r.csi("G")
|
||||||
r.csi("K")
|
r.csi("K")
|
||||||
// r.csi("s")
|
if !r.clearOnExit && !r.fullscreen {
|
||||||
|
r.csi("s")
|
||||||
|
}
|
||||||
if !r.fullscreen && r.mouse {
|
if !r.fullscreen && r.mouse {
|
||||||
r.yoffset, _ = r.findOffset()
|
r.yoffset, _ = r.findOffset()
|
||||||
}
|
}
|
||||||
@@ -240,26 +260,22 @@ func getEnv(name string, defaultValue int) int {
|
|||||||
return atoi(env, defaultValue)
|
return atoi(env, defaultValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) updateTerminalSize() (int, int) {
|
func (r *LightRenderer) updateTerminalSize() {
|
||||||
width, height, err := terminal.GetSize(r.fd())
|
width, height, err := terminal.GetSize(r.fd())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
r.width = width
|
r.width = width
|
||||||
if r.fullscreen {
|
|
||||||
r.height = height
|
|
||||||
} else {
|
|
||||||
r.height = r.maxHeightFunc(height)
|
r.height = r.maxHeightFunc(height)
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
r.width = getEnv("COLUMNS", defaultWidth)
|
r.width = getEnv("COLUMNS", defaultWidth)
|
||||||
r.height = r.maxHeightFunc(getEnv("LINES", defaultHeight))
|
r.height = r.maxHeightFunc(getEnv("LINES", defaultHeight))
|
||||||
}
|
}
|
||||||
return height, r.height
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) getch(nonblock bool) (int, bool) {
|
func (r *LightRenderer) getch(nonblock bool) (int, bool) {
|
||||||
b := make([]byte, 1)
|
b := make([]byte, 1)
|
||||||
|
fd := r.fd()
|
||||||
util.SetNonblock(r.ttyin, nonblock)
|
util.SetNonblock(r.ttyin, nonblock)
|
||||||
_, err := r.ttyin.Read(b)
|
_, err := util.Read(fd, b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
@@ -283,6 +299,7 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
|
|||||||
}
|
}
|
||||||
buffer = append(buffer, byte(c))
|
buffer = append(buffer, byte(c))
|
||||||
|
|
||||||
|
pc := c
|
||||||
for {
|
for {
|
||||||
c, ok = r.getch(true)
|
c, ok = r.getch(true)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -292,9 +309,13 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
} else if c == ESC && pc != c {
|
||||||
|
retries = r.escDelay / escPollInterval
|
||||||
|
} else {
|
||||||
retries = 0
|
retries = 0
|
||||||
|
}
|
||||||
buffer = append(buffer, byte(c))
|
buffer = append(buffer, byte(c))
|
||||||
|
pc = c
|
||||||
}
|
}
|
||||||
|
|
||||||
return buffer
|
return buffer
|
||||||
@@ -324,6 +345,14 @@ func (r *LightRenderer) GetChar() Event {
|
|||||||
return Event{BSpace, 0, nil}
|
return Event{BSpace, 0, nil}
|
||||||
case 0:
|
case 0:
|
||||||
return Event{CtrlSpace, 0, nil}
|
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:
|
case ESC:
|
||||||
ev := r.escSequence(&sz)
|
ev := r.escSequence(&sz)
|
||||||
// Second chance
|
// Second chance
|
||||||
@@ -351,9 +380,17 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
return Event{ESC, 0, nil}
|
return Event{ESC, 0, nil}
|
||||||
}
|
}
|
||||||
*sz = 2
|
*sz = 2
|
||||||
|
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] {
|
switch r.buffer[1] {
|
||||||
case 13:
|
case ESC:
|
||||||
return Event{AltEnter, 0, nil}
|
return Event{ESC, 0, nil}
|
||||||
case 32:
|
case 32:
|
||||||
return Event{AltSpace, 0, nil}
|
return Event{AltSpace, 0, nil}
|
||||||
case 47:
|
case 47:
|
||||||
@@ -373,12 +410,25 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
*sz = 3
|
*sz = 3
|
||||||
switch r.buffer[2] {
|
switch r.buffer[2] {
|
||||||
case 68:
|
case 68:
|
||||||
|
if alt {
|
||||||
|
return Event{AltLeft, 0, nil}
|
||||||
|
}
|
||||||
return Event{Left, 0, nil}
|
return Event{Left, 0, nil}
|
||||||
case 67:
|
case 67:
|
||||||
|
if alt {
|
||||||
|
// Ugh..
|
||||||
|
return Event{AltRight, 0, nil}
|
||||||
|
}
|
||||||
return Event{Right, 0, nil}
|
return Event{Right, 0, nil}
|
||||||
case 66:
|
case 66:
|
||||||
|
if alt {
|
||||||
|
return Event{AltDown, 0, nil}
|
||||||
|
}
|
||||||
return Event{Down, 0, nil}
|
return Event{Down, 0, nil}
|
||||||
case 65:
|
case 65:
|
||||||
|
if alt {
|
||||||
|
return Event{AltUp, 0, nil}
|
||||||
|
}
|
||||||
return Event{Up, 0, nil}
|
return Event{Up, 0, nil}
|
||||||
case 90:
|
case 90:
|
||||||
return Event{BTab, 0, nil}
|
return Event{BTab, 0, nil}
|
||||||
@@ -416,10 +466,12 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
return Event{F12, 0, nil}
|
return Event{F12, 0, nil}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Bracketed paste mode \e[200~ / \e[201
|
// Bracketed paste mode: \e[200~ ... \e[201~
|
||||||
if r.buffer[3] == 48 && (r.buffer[4] == 48 || r.buffer[4] == 49) && r.buffer[5] == 126 {
|
if r.buffer[3] == '0' && (r.buffer[4] == '0' || r.buffer[4] == '1') && r.buffer[5] == '~' {
|
||||||
*sz = 6
|
// Immediately discard the sequence from the buffer and reread input
|
||||||
return Event{Invalid, 0, nil}
|
r.buffer = r.buffer[6:]
|
||||||
|
*sz = 0
|
||||||
|
return r.GetChar()
|
||||||
}
|
}
|
||||||
return Event{Invalid, 0, nil} // INS
|
return Event{Invalid, 0, nil} // INS
|
||||||
case 51:
|
case 51:
|
||||||
@@ -449,25 +501,22 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
case 59:
|
case ';':
|
||||||
if len(r.buffer) != 6 {
|
if len(r.buffer) != 6 {
|
||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
}
|
}
|
||||||
*sz = 6
|
*sz = 6
|
||||||
switch r.buffer[4] {
|
switch r.buffer[4] {
|
||||||
case 50:
|
case '2', '5':
|
||||||
switch r.buffer[5] {
|
switch r.buffer[5] {
|
||||||
case 68:
|
case 'A':
|
||||||
return Event{Home, 0, nil}
|
return Event{SUp, 0, nil}
|
||||||
case 67:
|
case 'B':
|
||||||
return Event{End, 0, nil}
|
return Event{SDown, 0, nil}
|
||||||
}
|
case 'C':
|
||||||
case 53:
|
|
||||||
switch r.buffer[5] {
|
|
||||||
case 68:
|
|
||||||
return Event{SLeft, 0, nil}
|
|
||||||
case 67:
|
|
||||||
return Event{SRight, 0, nil}
|
return Event{SRight, 0, nil}
|
||||||
|
case 'D':
|
||||||
|
return Event{SLeft, 0, nil}
|
||||||
}
|
}
|
||||||
} // r.buffer[4]
|
} // r.buffer[4]
|
||||||
} // r.buffer[3]
|
} // r.buffer[3]
|
||||||
@@ -477,6 +526,9 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
if r.buffer[1] >= 'a' && r.buffer[1] <= 'z' {
|
if r.buffer[1] >= 'a' && r.buffer[1] <= 'z' {
|
||||||
return Event{AltA + int(r.buffer[1]) - 'a', 0, nil}
|
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}
|
return Event{Invalid, 0, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,16 +538,19 @@ func (r *LightRenderer) mouseSequence(sz *int) Event {
|
|||||||
}
|
}
|
||||||
*sz = 6
|
*sz = 6
|
||||||
switch r.buffer[3] {
|
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
|
35, 39, 43, 51: // mouse-up / shift / cmd / ctrl
|
||||||
mod := r.buffer[3] >= 36
|
mod := r.buffer[3] >= 36
|
||||||
|
left := r.buffer[3] == 32
|
||||||
down := r.buffer[3]%2 == 0
|
down := r.buffer[3]%2 == 0
|
||||||
x := int(r.buffer[4] - 33)
|
x := int(r.buffer[4] - 33)
|
||||||
y := int(r.buffer[5]-33) - r.yoffset
|
y := int(r.buffer[5]-33) - r.yoffset
|
||||||
double := false
|
double := false
|
||||||
if down {
|
if down {
|
||||||
now := time.Now()
|
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)
|
r.clickY = append(r.clickY, y)
|
||||||
} else {
|
} else {
|
||||||
r.clickY = []int{y}
|
r.clickY = []int{y}
|
||||||
@@ -503,19 +558,19 @@ func (r *LightRenderer) mouseSequence(sz *int) Event {
|
|||||||
r.prevDownTime = now
|
r.prevDownTime = now
|
||||||
} else {
|
} else {
|
||||||
if len(r.clickY) > 1 && r.clickY[0] == r.clickY[1] &&
|
if len(r.clickY) > 1 && r.clickY[0] == r.clickY[1] &&
|
||||||
time.Now().Sub(r.prevDownTime) < doubleClickDuration {
|
time.Since(r.prevDownTime) < doubleClickDuration {
|
||||||
double = true
|
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
|
case 96, 100, 104, 112, // scroll-up / shift / cmd / ctrl
|
||||||
97, 101, 105, 113: // scroll-down / shift / cmd / ctrl
|
97, 101, 105, 113: // scroll-down / shift / cmd / ctrl
|
||||||
mod := r.buffer[3] >= 100
|
mod := r.buffer[3] >= 100
|
||||||
s := 1 - int(r.buffer[3]%2)*2
|
s := 1 - int(r.buffer[3]%2)*2
|
||||||
x := int(r.buffer[4] - 33)
|
x := int(r.buffer[4] - 33)
|
||||||
y := int(r.buffer[5]-33) - r.yoffset
|
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}
|
return Event{Invalid, 0, nil}
|
||||||
}
|
}
|
||||||
@@ -528,8 +583,9 @@ func (r *LightRenderer) rmcup() {
|
|||||||
r.csi("?1049l")
|
r.csi("?1049l")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Pause() {
|
func (r *LightRenderer) Pause(clear bool) {
|
||||||
terminal.Restore(r.fd(), r.origState)
|
terminal.Restore(r.fd(), r.origState)
|
||||||
|
if clear {
|
||||||
if r.fullscreen {
|
if r.fullscreen {
|
||||||
r.rmcup()
|
r.rmcup()
|
||||||
} else {
|
} else {
|
||||||
@@ -537,21 +593,31 @@ func (r *LightRenderer) Pause() {
|
|||||||
r.csi("H")
|
r.csi("H")
|
||||||
}
|
}
|
||||||
r.flush()
|
r.flush()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Resume() bool {
|
func (r *LightRenderer) Resume(clear bool) {
|
||||||
terminal.MakeRaw(r.fd())
|
terminal.MakeRaw(r.fd())
|
||||||
|
if clear {
|
||||||
if r.fullscreen {
|
if r.fullscreen {
|
||||||
r.smcup()
|
r.smcup()
|
||||||
} else {
|
} else {
|
||||||
r.rmcup()
|
r.rmcup()
|
||||||
}
|
}
|
||||||
r.flush()
|
r.flush()
|
||||||
// Should redraw
|
} else if !r.fullscreen && r.mouse {
|
||||||
return true
|
// NOTE: Resume(false) is only called on SIGCONT after SIGSTOP.
|
||||||
|
// And It's highly likely that the offset we obtained at the beginning will
|
||||||
|
// no longer be correct, so we simply disable mouse input.
|
||||||
|
r.csi("?1000l")
|
||||||
|
r.mouse = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Clear() {
|
func (r *LightRenderer) Clear() {
|
||||||
|
if r.fullscreen {
|
||||||
|
r.csi("H")
|
||||||
|
}
|
||||||
// r.csi("u")
|
// r.csi("u")
|
||||||
r.origin()
|
r.origin()
|
||||||
r.csi("J")
|
r.csi("J")
|
||||||
@@ -568,6 +634,7 @@ func (r *LightRenderer) Refresh() {
|
|||||||
|
|
||||||
func (r *LightRenderer) Close() {
|
func (r *LightRenderer) Close() {
|
||||||
// r.csi("u")
|
// r.csi("u")
|
||||||
|
if r.clearOnExit {
|
||||||
if r.fullscreen {
|
if r.fullscreen {
|
||||||
r.rmcup()
|
r.rmcup()
|
||||||
} else {
|
} else {
|
||||||
@@ -577,6 +644,9 @@ func (r *LightRenderer) Close() {
|
|||||||
}
|
}
|
||||||
r.csi("J")
|
r.csi("J")
|
||||||
}
|
}
|
||||||
|
} else if !r.fullscreen {
|
||||||
|
r.csi("u")
|
||||||
|
}
|
||||||
if r.mouse {
|
if r.mouse {
|
||||||
r.csi("?1000l")
|
r.csi("?1000l")
|
||||||
}
|
}
|
||||||
@@ -593,14 +663,10 @@ func (r *LightRenderer) MaxY() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) DoesAutoWrap() bool {
|
func (r *LightRenderer) DoesAutoWrap() bool {
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LightRenderer) IsOptimized() bool {
|
|
||||||
return false
|
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{
|
w := &LightWindow{
|
||||||
renderer: r,
|
renderer: r,
|
||||||
colored: r.theme != nil,
|
colored: r.theme != nil,
|
||||||
@@ -610,16 +676,23 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, bord
|
|||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
tabstop: r.tabstop,
|
tabstop: r.tabstop,
|
||||||
|
fg: colDefault,
|
||||||
bg: colDefault}
|
bg: colDefault}
|
||||||
if r.theme != nil {
|
if r.theme != nil {
|
||||||
|
if preview {
|
||||||
|
w.fg = r.theme.PreviewFg
|
||||||
|
w.bg = r.theme.PreviewBg
|
||||||
|
} else {
|
||||||
|
w.fg = r.theme.Fg
|
||||||
w.bg = r.theme.Bg
|
w.bg = r.theme.Bg
|
||||||
}
|
}
|
||||||
|
}
|
||||||
w.drawBorder()
|
w.drawBorder()
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) drawBorder() {
|
func (w *LightWindow) drawBorder() {
|
||||||
switch w.border {
|
switch w.border.shape {
|
||||||
case BorderAround:
|
case BorderAround:
|
||||||
w.drawBorderAround()
|
w.drawBorderAround()
|
||||||
case BorderHorizontal:
|
case BorderHorizontal:
|
||||||
@@ -629,32 +702,30 @@ func (w *LightWindow) drawBorder() {
|
|||||||
|
|
||||||
func (w *LightWindow) drawBorderHorizontal() {
|
func (w *LightWindow) drawBorderHorizontal() {
|
||||||
w.Move(0, 0)
|
w.Move(0, 0)
|
||||||
w.CPrint(ColBorder, AttrRegular, repeat("─", w.width))
|
w.CPrint(ColBorder, AttrRegular, repeat(w.border.horizontal, w.width))
|
||||||
w.Move(w.height-1, 0)
|
w.Move(w.height-1, 0)
|
||||||
w.CPrint(ColBorder, AttrRegular, repeat("─", w.width))
|
w.CPrint(ColBorder, AttrRegular, repeat(w.border.horizontal, w.width))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) drawBorderAround() {
|
func (w *LightWindow) drawBorderAround() {
|
||||||
w.Move(0, 0)
|
w.Move(0, 0)
|
||||||
w.CPrint(ColBorder, AttrRegular, "┌"+repeat("─", w.width-2)+"┐")
|
w.CPrint(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++ {
|
for y := 1; y < w.height-1; y++ {
|
||||||
w.Move(y, 0)
|
w.Move(y, 0)
|
||||||
w.CPrint(ColBorder, AttrRegular, "│")
|
w.CPrint(ColPreviewBorder, AttrRegular, string(w.border.vertical))
|
||||||
w.cprint2(colDefault, w.bg, AttrRegular, repeat(" ", w.width-2))
|
w.CPrint(ColPreviewBorder, AttrRegular, repeat(' ', w.width-2))
|
||||||
w.CPrint(ColBorder, AttrRegular, "│")
|
w.CPrint(ColPreviewBorder, AttrRegular, string(w.border.vertical))
|
||||||
}
|
}
|
||||||
w.Move(w.height-1, 0)
|
w.Move(w.height-1, 0)
|
||||||
w.CPrint(ColBorder, AttrRegular, "└"+repeat("─", w.width-2)+"┘")
|
w.CPrint(ColPreviewBorder, AttrRegular,
|
||||||
|
string(w.border.bottomLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.bottomRight))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) csi(code string) {
|
func (w *LightWindow) csi(code string) {
|
||||||
w.renderer.csi(code)
|
w.renderer.csi(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) stderr(str string) {
|
|
||||||
w.renderer.stderr(str)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *LightWindow) stderrInternal(str string, allowNLCR bool) {
|
func (w *LightWindow) stderrInternal(str string, allowNLCR bool) {
|
||||||
w.renderer.stderrInternal(str, allowNLCR)
|
w.renderer.stderrInternal(str, allowNLCR)
|
||||||
}
|
}
|
||||||
@@ -685,6 +756,10 @@ func (w *LightWindow) X() int {
|
|||||||
return w.posx
|
return w.posx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) Y() int {
|
||||||
|
return w.posy
|
||||||
|
}
|
||||||
|
|
||||||
func (w *LightWindow) Enclose(y int, x int) bool {
|
func (w *LightWindow) Enclose(y int, x int) bool {
|
||||||
return x >= w.left && x < (w.left+w.width) &&
|
return x >= w.left && x < (w.left+w.width) &&
|
||||||
y >= w.top && y < (w.top+w.height)
|
y >= w.top && y < (w.top+w.height)
|
||||||
@@ -701,7 +776,7 @@ func (w *LightWindow) MoveAndClear(y int, x int) {
|
|||||||
w.Move(y, x)
|
w.Move(y, x)
|
||||||
// We should not delete preview window on the right
|
// We should not delete preview window on the right
|
||||||
// csi("K")
|
// csi("K")
|
||||||
w.Print(repeat(" ", w.width-x))
|
w.Print(repeat(' ', w.width-x))
|
||||||
w.Move(y, x)
|
w.Move(y, x)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -763,7 +838,7 @@ func (w *LightWindow) Print(text string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func cleanse(str string) string {
|
func cleanse(str string) string {
|
||||||
return strings.Replace(str, "\x1b", "?", -1)
|
return strings.Replace(str, "\x1b", "", -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) CPrint(pair ColorPair, attr Attr, text string) {
|
func (w *LightWindow) CPrint(pair ColorPair, attr Attr, text string) {
|
||||||
@@ -797,7 +872,7 @@ func wrapLine(input string, prefixLength int, max int, tabstop int) []wrappedLin
|
|||||||
width += w
|
width += w
|
||||||
str := string(r)
|
str := string(r)
|
||||||
if r == '\t' {
|
if r == '\t' {
|
||||||
str = repeat(" ", w)
|
str = repeat(' ', w)
|
||||||
}
|
}
|
||||||
if prefixLength+width <= max {
|
if prefixLength+width <= max {
|
||||||
line += str
|
line += str
|
||||||
@@ -819,17 +894,20 @@ func (w *LightWindow) fill(str string, onMove func()) FillReturn {
|
|||||||
for j, wl := range lines {
|
for j, wl := range lines {
|
||||||
if w.posx >= w.Width()-1 && wl.displayWidth == 0 {
|
if w.posx >= w.Width()-1 && wl.displayWidth == 0 {
|
||||||
if w.posy < w.height-1 {
|
if w.posy < w.height-1 {
|
||||||
w.MoveAndClear(w.posy+1, 0)
|
w.Move(w.posy+1, 0)
|
||||||
}
|
}
|
||||||
return FillNextLine
|
return FillNextLine
|
||||||
}
|
}
|
||||||
w.stderrInternal(wl.text, false)
|
w.stderrInternal(wl.text, false)
|
||||||
w.posx += wl.displayWidth
|
w.posx += wl.displayWidth
|
||||||
|
|
||||||
|
// Wrap line
|
||||||
if j < len(lines)-1 || i < len(allLines)-1 {
|
if j < len(lines)-1 || i < len(allLines)-1 {
|
||||||
if w.posy+1 >= w.height {
|
if w.posy+1 >= w.height {
|
||||||
return FillSuspend
|
return FillSuspend
|
||||||
}
|
}
|
||||||
w.MoveAndClear(w.posy+1, 0)
|
w.MoveAndClear(w.posy, w.posx)
|
||||||
|
w.Move(w.posy+1, 0)
|
||||||
onMove()
|
onMove()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -844,24 +922,28 @@ func (w *LightWindow) setBg() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) Fill(text string) FillReturn {
|
func (w *LightWindow) Fill(text string) FillReturn {
|
||||||
w.MoveAndClear(w.posy, w.posx)
|
w.Move(w.posy, w.posx)
|
||||||
w.setBg()
|
w.setBg()
|
||||||
return w.fill(text, w.setBg)
|
return w.fill(text, w.setBg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillReturn {
|
func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillReturn {
|
||||||
w.MoveAndClear(w.posy, w.posx)
|
w.Move(w.posy, w.posx)
|
||||||
|
if fg == colDefault {
|
||||||
|
fg = w.fg
|
||||||
|
}
|
||||||
if bg == colDefault {
|
if bg == colDefault {
|
||||||
bg = w.bg
|
bg = w.bg
|
||||||
}
|
}
|
||||||
if w.csiColor(fg, bg, attr) {
|
if w.csiColor(fg, bg, attr) {
|
||||||
return w.fill(text, func() { w.csiColor(fg, bg, attr) })
|
|
||||||
defer w.csi("m")
|
defer w.csi("m")
|
||||||
|
return w.fill(text, func() { w.csiColor(fg, bg, attr) })
|
||||||
}
|
}
|
||||||
return w.fill(text, w.setBg)
|
return w.fill(text, w.setBg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) FinishFill() {
|
func (w *LightWindow) FinishFill() {
|
||||||
|
w.MoveAndClear(w.posy, w.posx)
|
||||||
for y := w.posy + 1; y < w.height; y++ {
|
for y := w.posy + 1; y < w.height; y++ {
|
||||||
w.MoveAndClear(y, 0)
|
w.MoveAndClear(y, 0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,506 +0,0 @@
|
|||||||
// +build ncurses
|
|
||||||
// +build !windows
|
|
||||||
// +build !tcell
|
|
||||||
|
|
||||||
package tui
|
|
||||||
|
|
||||||
/*
|
|
||||||
#include <ncurses.h>
|
|
||||||
#include <locale.h>
|
|
||||||
#cgo !static LDFLAGS: -lncurses
|
|
||||||
#cgo static LDFLAGS: -l:libncursesw.a -l:libtinfo.a -l:libgpm.a -ldl
|
|
||||||
#cgo android static LDFLAGS: -l:libncurses.a -fPIE -march=armv7-a -mfpu=neon -mhard-float -Wl,--no-warn-mismatch
|
|
||||||
|
|
||||||
FILE* c_tty() {
|
|
||||||
return fopen("/dev/tty", "r");
|
|
||||||
}
|
|
||||||
|
|
||||||
SCREEN* c_newterm(FILE* tty) {
|
|
||||||
return newterm(NULL, stderr, tty);
|
|
||||||
}
|
|
||||||
|
|
||||||
int c_getcurx(WINDOW* win) {
|
|
||||||
return getcurx(win);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
func HasFullscreenRenderer() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type Attr C.uint
|
|
||||||
|
|
||||||
type CursesWindow struct {
|
|
||||||
impl *C.WINDOW
|
|
||||||
top int
|
|
||||||
left int
|
|
||||||
width int
|
|
||||||
height int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Top() int {
|
|
||||||
return w.top
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Left() int {
|
|
||||||
return w.left
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Width() int {
|
|
||||||
return w.width
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Height() int {
|
|
||||||
return w.height
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Refresh() {
|
|
||||||
C.wnoutrefresh(w.impl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) FinishFill() {
|
|
||||||
// NO-OP
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
Bold Attr = C.A_BOLD
|
|
||||||
Dim = C.A_DIM
|
|
||||||
Blink = C.A_BLINK
|
|
||||||
Reverse = C.A_REVERSE
|
|
||||||
Underline = C.A_UNDERLINE
|
|
||||||
)
|
|
||||||
|
|
||||||
var Italic Attr = C.A_VERTICAL << 1 // FIXME
|
|
||||||
|
|
||||||
const (
|
|
||||||
AttrRegular Attr = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
_screen *C.SCREEN
|
|
||||||
_colorMap map[int]int16
|
|
||||||
_colorFn func(ColorPair, Attr) (C.short, C.int)
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
_colorMap = make(map[int]int16)
|
|
||||||
if strings.HasPrefix(C.GoString(C.curses_version()), "ncurses 5") {
|
|
||||||
Italic = C.A_NORMAL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a Attr) Merge(b Attr) Attr {
|
|
||||||
return a | b
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
|
|
||||||
if C.tigetnum(C.CString("colors")) >= 256 {
|
|
||||||
return Dark256
|
|
||||||
}
|
|
||||||
return Default16
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Init() {
|
|
||||||
C.setlocale(C.LC_ALL, C.CString(""))
|
|
||||||
tty := C.c_tty()
|
|
||||||
if tty == nil {
|
|
||||||
errorExit("Failed to open /dev/tty")
|
|
||||||
}
|
|
||||||
_screen = C.c_newterm(tty)
|
|
||||||
if _screen == nil {
|
|
||||||
errorExit("Invalid $TERM: " + os.Getenv("TERM"))
|
|
||||||
}
|
|
||||||
C.set_term(_screen)
|
|
||||||
if r.mouse {
|
|
||||||
C.mousemask(C.ALL_MOUSE_EVENTS, nil)
|
|
||||||
C.mouseinterval(0)
|
|
||||||
}
|
|
||||||
C.noecho()
|
|
||||||
C.raw() // stty dsusp undef
|
|
||||||
C.nonl()
|
|
||||||
C.keypad(C.stdscr, true)
|
|
||||||
|
|
||||||
delay := 50
|
|
||||||
delayEnv := os.Getenv("ESCDELAY")
|
|
||||||
if len(delayEnv) > 0 {
|
|
||||||
num, err := strconv.Atoi(delayEnv)
|
|
||||||
if err == nil && num >= 0 {
|
|
||||||
delay = num
|
|
||||||
}
|
|
||||||
}
|
|
||||||
C.set_escdelay(C.int(delay))
|
|
||||||
|
|
||||||
if r.theme != nil {
|
|
||||||
C.start_color()
|
|
||||||
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
|
||||||
initPairs(r.theme)
|
|
||||||
C.bkgd(C.chtype(C.COLOR_PAIR(C.int(ColNormal.index()))))
|
|
||||||
_colorFn = attrColored
|
|
||||||
} else {
|
|
||||||
initTheme(r.theme, nil, r.forceBlack)
|
|
||||||
_colorFn = attrMono
|
|
||||||
}
|
|
||||||
|
|
||||||
C.nodelay(C.stdscr, true)
|
|
||||||
ch := C.getch()
|
|
||||||
if ch != C.ERR {
|
|
||||||
C.ungetch(ch)
|
|
||||||
}
|
|
||||||
C.nodelay(C.stdscr, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func initPairs(theme *ColorTheme) {
|
|
||||||
C.assume_default_colors(C.int(theme.Fg), C.int(theme.Bg))
|
|
||||||
for _, pair := range []ColorPair{
|
|
||||||
ColNormal,
|
|
||||||
ColPrompt,
|
|
||||||
ColMatch,
|
|
||||||
ColCurrent,
|
|
||||||
ColCurrentMatch,
|
|
||||||
ColSpinner,
|
|
||||||
ColInfo,
|
|
||||||
ColCursor,
|
|
||||||
ColSelected,
|
|
||||||
ColHeader,
|
|
||||||
ColBorder} {
|
|
||||||
C.init_pair(C.short(pair.index()), C.short(pair.Fg()), C.short(pair.Bg()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Pause() {
|
|
||||||
C.endwin()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Resume() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Close() {
|
|
||||||
C.endwin()
|
|
||||||
C.delscreen(_screen)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, borderStyle BorderStyle) Window {
|
|
||||||
win := C.newwin(C.int(height), C.int(width), C.int(top), C.int(left))
|
|
||||||
if r.theme != nil {
|
|
||||||
C.wbkgd(win, C.chtype(C.COLOR_PAIR(C.int(ColNormal.index()))))
|
|
||||||
}
|
|
||||||
// FIXME Does not implement BorderHorizontal
|
|
||||||
if borderStyle != BorderNone {
|
|
||||||
pair, attr := _colorFn(ColBorder, 0)
|
|
||||||
C.wcolor_set(win, pair, nil)
|
|
||||||
C.wattron(win, attr)
|
|
||||||
C.box(win, 0, 0)
|
|
||||||
C.wattroff(win, attr)
|
|
||||||
C.wcolor_set(win, 0, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &CursesWindow{
|
|
||||||
impl: win,
|
|
||||||
top: top,
|
|
||||||
left: left,
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func attrColored(color ColorPair, a Attr) (C.short, C.int) {
|
|
||||||
return C.short(color.index()), C.int(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
func attrMono(color ColorPair, a Attr) (C.short, C.int) {
|
|
||||||
return 0, C.int(attrFor(color, a))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) MaxX() int {
|
|
||||||
return int(C.COLS)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) MaxY() int {
|
|
||||||
return int(C.LINES)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Close() {
|
|
||||||
C.delwin(w.impl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Enclose(y int, x int) bool {
|
|
||||||
return bool(C.wenclose(w.impl, C.int(y), C.int(x)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Move(y int, x int) {
|
|
||||||
C.wmove(w.impl, C.int(y), C.int(x))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) MoveAndClear(y int, x int) {
|
|
||||||
w.Move(y, x)
|
|
||||||
C.wclrtoeol(w.impl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Print(text string) {
|
|
||||||
C.waddstr(w.impl, C.CString(strings.Map(func(r rune) rune {
|
|
||||||
if r < 32 {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}, text)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) CPrint(color ColorPair, attr Attr, text string) {
|
|
||||||
p, a := _colorFn(color, attr)
|
|
||||||
C.wcolor_set(w.impl, p, nil)
|
|
||||||
C.wattron(w.impl, a)
|
|
||||||
w.Print(text)
|
|
||||||
C.wattroff(w.impl, a)
|
|
||||||
C.wcolor_set(w.impl, 0, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Clear() {
|
|
||||||
C.clear()
|
|
||||||
C.endwin()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Refresh() {
|
|
||||||
C.refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Erase() {
|
|
||||||
C.werase(w.impl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) X() int {
|
|
||||||
return int(C.c_getcurx(w.impl))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) DoesAutoWrap() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) IsOptimized() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) Fill(str string) FillReturn {
|
|
||||||
if C.waddstr(w.impl, C.CString(str)) == C.OK {
|
|
||||||
return FillContinue
|
|
||||||
}
|
|
||||||
return FillSuspend
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *CursesWindow) CFill(fg Color, bg Color, attr Attr, str string) FillReturn {
|
|
||||||
index := ColorPair{fg, bg, -1}.index()
|
|
||||||
C.wcolor_set(w.impl, C.short(index), nil)
|
|
||||||
C.wattron(w.impl, C.int(attr))
|
|
||||||
ret := w.Fill(str)
|
|
||||||
C.wattroff(w.impl, C.int(attr))
|
|
||||||
C.wcolor_set(w.impl, 0, nil)
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {
|
|
||||||
for _, w := range windows {
|
|
||||||
w.Refresh()
|
|
||||||
}
|
|
||||||
C.doupdate()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p ColorPair) index() int16 {
|
|
||||||
if p.id >= 0 {
|
|
||||||
return p.id
|
|
||||||
}
|
|
||||||
|
|
||||||
// ncurses does not support 24-bit colors
|
|
||||||
if p.is24() {
|
|
||||||
return ColDefault.index()
|
|
||||||
}
|
|
||||||
|
|
||||||
key := p.key()
|
|
||||||
if found, prs := _colorMap[key]; prs {
|
|
||||||
return found
|
|
||||||
}
|
|
||||||
|
|
||||||
id := int16(len(_colorMap)) + ColUser.id
|
|
||||||
C.init_pair(C.short(id), C.short(p.Fg()), C.short(p.Bg()))
|
|
||||||
_colorMap[key] = id
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
func consume(expects ...rune) bool {
|
|
||||||
for _, r := range expects {
|
|
||||||
if int(C.getch()) != int(r) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func escSequence() Event {
|
|
||||||
C.nodelay(C.stdscr, true)
|
|
||||||
defer func() {
|
|
||||||
C.nodelay(C.stdscr, false)
|
|
||||||
}()
|
|
||||||
c := C.getch()
|
|
||||||
switch c {
|
|
||||||
case C.ERR:
|
|
||||||
return Event{ESC, 0, nil}
|
|
||||||
case CtrlM:
|
|
||||||
return Event{AltEnter, 0, nil}
|
|
||||||
case '/':
|
|
||||||
return Event{AltSlash, 0, nil}
|
|
||||||
case ' ':
|
|
||||||
return Event{AltSpace, 0, nil}
|
|
||||||
case 127, C.KEY_BACKSPACE:
|
|
||||||
return Event{AltBS, 0, nil}
|
|
||||||
case '[':
|
|
||||||
// Bracketed paste mode (printf "\e[?2004h")
|
|
||||||
// \e[200~ TEXT \e[201~
|
|
||||||
if consume('2', '0', '0', '~') {
|
|
||||||
return Event{Invalid, 0, nil}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c >= 'a' && c <= 'z' {
|
|
||||||
return Event{AltA + int(c) - 'a', 0, nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c >= '0' && c <= '9' {
|
|
||||||
return Event{Alt0 + int(c) - '0', 0, nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't care. Ignore the rest.
|
|
||||||
for ; c != C.ERR; c = C.getch() {
|
|
||||||
}
|
|
||||||
return Event{Invalid, 0, nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *FullscreenRenderer) GetChar() Event {
|
|
||||||
c := C.getch()
|
|
||||||
switch c {
|
|
||||||
case C.ERR:
|
|
||||||
// Unexpected error from blocking read
|
|
||||||
r.Close()
|
|
||||||
errorExit("Failed to read /dev/tty")
|
|
||||||
case C.KEY_UP:
|
|
||||||
return Event{Up, 0, nil}
|
|
||||||
case C.KEY_DOWN:
|
|
||||||
return Event{Down, 0, nil}
|
|
||||||
case C.KEY_LEFT:
|
|
||||||
return Event{Left, 0, nil}
|
|
||||||
case C.KEY_RIGHT:
|
|
||||||
return Event{Right, 0, nil}
|
|
||||||
case C.KEY_HOME:
|
|
||||||
return Event{Home, 0, nil}
|
|
||||||
case C.KEY_END:
|
|
||||||
return Event{End, 0, nil}
|
|
||||||
case C.KEY_BACKSPACE:
|
|
||||||
return Event{BSpace, 0, nil}
|
|
||||||
case C.KEY_F0 + 1:
|
|
||||||
return Event{F1, 0, nil}
|
|
||||||
case C.KEY_F0 + 2:
|
|
||||||
return Event{F2, 0, nil}
|
|
||||||
case C.KEY_F0 + 3:
|
|
||||||
return Event{F3, 0, nil}
|
|
||||||
case C.KEY_F0 + 4:
|
|
||||||
return Event{F4, 0, nil}
|
|
||||||
case C.KEY_F0 + 5:
|
|
||||||
return Event{F5, 0, nil}
|
|
||||||
case C.KEY_F0 + 6:
|
|
||||||
return Event{F6, 0, nil}
|
|
||||||
case C.KEY_F0 + 7:
|
|
||||||
return Event{F7, 0, nil}
|
|
||||||
case C.KEY_F0 + 8:
|
|
||||||
return Event{F8, 0, nil}
|
|
||||||
case C.KEY_F0 + 9:
|
|
||||||
return Event{F9, 0, nil}
|
|
||||||
case C.KEY_F0 + 10:
|
|
||||||
return Event{F10, 0, nil}
|
|
||||||
case C.KEY_F0 + 11:
|
|
||||||
return Event{F11, 0, nil}
|
|
||||||
case C.KEY_F0 + 12:
|
|
||||||
return Event{F12, 0, nil}
|
|
||||||
case C.KEY_DC:
|
|
||||||
return Event{Del, 0, nil}
|
|
||||||
case C.KEY_PPAGE:
|
|
||||||
return Event{PgUp, 0, nil}
|
|
||||||
case C.KEY_NPAGE:
|
|
||||||
return Event{PgDn, 0, nil}
|
|
||||||
case C.KEY_BTAB:
|
|
||||||
return Event{BTab, 0, nil}
|
|
||||||
case C.KEY_ENTER:
|
|
||||||
return Event{CtrlM, 0, nil}
|
|
||||||
case C.KEY_SLEFT:
|
|
||||||
return Event{SLeft, 0, nil}
|
|
||||||
case C.KEY_SRIGHT:
|
|
||||||
return Event{SRight, 0, nil}
|
|
||||||
case C.KEY_MOUSE:
|
|
||||||
var me C.MEVENT
|
|
||||||
if C.getmouse(&me) != C.ERR {
|
|
||||||
mod := ((me.bstate & C.BUTTON_SHIFT) | (me.bstate & C.BUTTON_CTRL) | (me.bstate & C.BUTTON_ALT)) > 0
|
|
||||||
x := int(me.x)
|
|
||||||
y := int(me.y)
|
|
||||||
/* Cannot use BUTTON1_DOUBLE_CLICKED due to mouseinterval(0) */
|
|
||||||
if (me.bstate & C.BUTTON1_PRESSED) > 0 {
|
|
||||||
now := time.Now()
|
|
||||||
if now.Sub(r.prevDownTime) < doubleClickDuration {
|
|
||||||
r.clickY = append(r.clickY, y)
|
|
||||||
} else {
|
|
||||||
r.clickY = []int{y}
|
|
||||||
r.prevDownTime = now
|
|
||||||
}
|
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, true, false, mod}}
|
|
||||||
} else if (me.bstate & C.BUTTON1_RELEASED) > 0 {
|
|
||||||
double := false
|
|
||||||
if len(r.clickY) > 1 && r.clickY[0] == r.clickY[1] &&
|
|
||||||
time.Now().Sub(r.prevDownTime) < doubleClickDuration {
|
|
||||||
double = true
|
|
||||||
}
|
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, false, double, mod}}
|
|
||||||
} else if (me.bstate&0x8000000) > 0 || (me.bstate&0x80) > 0 {
|
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, mod}}
|
|
||||||
} else if (me.bstate & C.BUTTON4_PRESSED) > 0 {
|
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, 1, false, false, mod}}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Event{Invalid, 0, nil}
|
|
||||||
case C.KEY_RESIZE:
|
|
||||||
return Event{Resize, 0, nil}
|
|
||||||
case ESC:
|
|
||||||
return escSequence()
|
|
||||||
case 127:
|
|
||||||
return Event{BSpace, 0, nil}
|
|
||||||
case 0:
|
|
||||||
return Event{CtrlSpace, 0, nil}
|
|
||||||
}
|
|
||||||
// CTRL-A ~ CTRL-Z
|
|
||||||
if c >= CtrlA && c <= CtrlZ {
|
|
||||||
return Event{int(c), 0, nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multi-byte character
|
|
||||||
buffer := []byte{byte(c)}
|
|
||||||
for {
|
|
||||||
r, _ := utf8.DecodeRune(buffer)
|
|
||||||
if r != utf8.RuneError {
|
|
||||||
return Event{Rune, r, nil}
|
|
||||||
}
|
|
||||||
|
|
||||||
c := C.getch()
|
|
||||||
if c == C.ERR {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if c >= C.KEY_CODE_YES {
|
|
||||||
C.ungetch(c)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
buffer = append(buffer, byte(c))
|
|
||||||
}
|
|
||||||
return Event{Invalid, 0, nil}
|
|
||||||
}
|
|
||||||
196
src/tui/tcell.go
196
src/tui/tcell.go
@@ -3,16 +3,16 @@
|
|||||||
package tui
|
package tui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
// https://github.com/gdamore/tcell/pull/135
|
"github.com/gdamore/tcell"
|
||||||
"github.com/junegunn/tcell"
|
"github.com/gdamore/tcell/encoding"
|
||||||
"github.com/junegunn/tcell/encoding"
|
|
||||||
|
|
||||||
"github.com/junegunn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
)
|
)
|
||||||
|
|
||||||
func HasFullscreenRenderer() bool {
|
func HasFullscreenRenderer() bool {
|
||||||
@@ -32,6 +32,7 @@ type TcellWindow struct {
|
|||||||
left int
|
left int
|
||||||
width int
|
width int
|
||||||
height int
|
height int
|
||||||
|
normal ColorPair
|
||||||
lastX int
|
lastX int
|
||||||
lastY int
|
lastY int
|
||||||
moveCursor bool
|
moveCursor bool
|
||||||
@@ -61,12 +62,8 @@ func (w *TcellWindow) Refresh() {
|
|||||||
}
|
}
|
||||||
w.lastX = 0
|
w.lastX = 0
|
||||||
w.lastY = 0
|
w.lastY = 0
|
||||||
switch w.borderStyle {
|
|
||||||
case BorderAround:
|
w.drawBorder()
|
||||||
w.drawBorder(true)
|
|
||||||
case BorderHorizontal:
|
|
||||||
w.drawBorder(false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) FinishFill() {
|
func (w *TcellWindow) FinishFill() {
|
||||||
@@ -141,6 +138,9 @@ func (r *FullscreenRenderer) initScreen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Init() {
|
func (r *FullscreenRenderer) Init() {
|
||||||
|
if os.Getenv("TERM") == "cygwin" {
|
||||||
|
os.Setenv("TERM", "")
|
||||||
|
}
|
||||||
encoding.Register()
|
encoding.Register()
|
||||||
|
|
||||||
r.initScreen()
|
r.initScreen()
|
||||||
@@ -161,11 +161,11 @@ func (w *TcellWindow) X() int {
|
|||||||
return w.lastX
|
return w.lastX
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) DoesAutoWrap() bool {
|
func (w *TcellWindow) Y() int {
|
||||||
return false
|
return w.lastY
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) IsOptimized() bool {
|
func (r *FullscreenRenderer) DoesAutoWrap() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,19 +190,22 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
button := ev.Buttons()
|
button := ev.Buttons()
|
||||||
mod := ev.Modifiers() != 0
|
mod := ev.Modifiers() != 0
|
||||||
if button&tcell.WheelDown != 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 {
|
} 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" {
|
} else if runtime.GOOS != "windows" {
|
||||||
// double and single taps on Windows don't quite work due to
|
// double and single taps on Windows don't quite work due to
|
||||||
// the console acting on the events and not allowing us
|
// the console acting on the events and not allowing us
|
||||||
// to consume them.
|
// to consume them.
|
||||||
|
|
||||||
down := button&tcell.Button1 != 0 // left
|
left := button&tcell.Button1 != 0
|
||||||
|
down := left || button&tcell.Button3 != 0
|
||||||
double := false
|
double := false
|
||||||
if down {
|
if down {
|
||||||
now := time.Now()
|
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)
|
r.clickY = append(r.clickY, x)
|
||||||
} else {
|
} else {
|
||||||
r.clickY = []int{x}
|
r.clickY = []int{x}
|
||||||
@@ -215,79 +218,104 @@ 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:
|
// process keyboard:
|
||||||
case *tcell.EventKey:
|
case *tcell.EventKey:
|
||||||
alt := (ev.Modifiers() & tcell.ModAlt) > 0
|
alt := (ev.Modifiers() & tcell.ModAlt) > 0
|
||||||
|
keyfn := func(r rune) int {
|
||||||
|
if alt {
|
||||||
|
return CtrlAltA - 'a' + int(r)
|
||||||
|
}
|
||||||
|
return CtrlA - 'a' + int(r)
|
||||||
|
}
|
||||||
switch ev.Key() {
|
switch ev.Key() {
|
||||||
case tcell.KeyCtrlA:
|
case tcell.KeyCtrlA:
|
||||||
return Event{CtrlA, 0, nil}
|
return Event{keyfn('a'), 0, nil}
|
||||||
case tcell.KeyCtrlB:
|
case tcell.KeyCtrlB:
|
||||||
return Event{CtrlB, 0, nil}
|
return Event{keyfn('b'), 0, nil}
|
||||||
case tcell.KeyCtrlC:
|
case tcell.KeyCtrlC:
|
||||||
return Event{CtrlC, 0, nil}
|
return Event{keyfn('c'), 0, nil}
|
||||||
case tcell.KeyCtrlD:
|
case tcell.KeyCtrlD:
|
||||||
return Event{CtrlD, 0, nil}
|
return Event{keyfn('d'), 0, nil}
|
||||||
case tcell.KeyCtrlE:
|
case tcell.KeyCtrlE:
|
||||||
return Event{CtrlE, 0, nil}
|
return Event{keyfn('e'), 0, nil}
|
||||||
case tcell.KeyCtrlF:
|
case tcell.KeyCtrlF:
|
||||||
return Event{CtrlF, 0, nil}
|
return Event{keyfn('f'), 0, nil}
|
||||||
case tcell.KeyCtrlG:
|
case tcell.KeyCtrlG:
|
||||||
return Event{CtrlG, 0, nil}
|
return Event{keyfn('g'), 0, nil}
|
||||||
|
case tcell.KeyCtrlH:
|
||||||
|
return Event{keyfn('h'), 0, nil}
|
||||||
|
case tcell.KeyCtrlI:
|
||||||
|
return Event{keyfn('i'), 0, nil}
|
||||||
case tcell.KeyCtrlJ:
|
case tcell.KeyCtrlJ:
|
||||||
return Event{CtrlJ, 0, nil}
|
return Event{keyfn('j'), 0, nil}
|
||||||
case tcell.KeyCtrlK:
|
case tcell.KeyCtrlK:
|
||||||
return Event{CtrlK, 0, nil}
|
return Event{keyfn('k'), 0, nil}
|
||||||
case tcell.KeyCtrlL:
|
case tcell.KeyCtrlL:
|
||||||
return Event{CtrlL, 0, nil}
|
return Event{keyfn('l'), 0, nil}
|
||||||
case tcell.KeyCtrlM:
|
case tcell.KeyCtrlM:
|
||||||
if alt {
|
return Event{keyfn('m'), 0, nil}
|
||||||
return Event{AltEnter, 0, nil}
|
|
||||||
}
|
|
||||||
return Event{CtrlM, 0, nil}
|
|
||||||
case tcell.KeyCtrlN:
|
case tcell.KeyCtrlN:
|
||||||
return Event{CtrlN, 0, nil}
|
return Event{keyfn('n'), 0, nil}
|
||||||
case tcell.KeyCtrlO:
|
case tcell.KeyCtrlO:
|
||||||
return Event{CtrlO, 0, nil}
|
return Event{keyfn('o'), 0, nil}
|
||||||
case tcell.KeyCtrlP:
|
case tcell.KeyCtrlP:
|
||||||
return Event{CtrlP, 0, nil}
|
return Event{keyfn('p'), 0, nil}
|
||||||
case tcell.KeyCtrlQ:
|
case tcell.KeyCtrlQ:
|
||||||
return Event{CtrlQ, 0, nil}
|
return Event{keyfn('q'), 0, nil}
|
||||||
case tcell.KeyCtrlR:
|
case tcell.KeyCtrlR:
|
||||||
return Event{CtrlR, 0, nil}
|
return Event{keyfn('r'), 0, nil}
|
||||||
case tcell.KeyCtrlS:
|
case tcell.KeyCtrlS:
|
||||||
return Event{CtrlS, 0, nil}
|
return Event{keyfn('s'), 0, nil}
|
||||||
case tcell.KeyCtrlT:
|
case tcell.KeyCtrlT:
|
||||||
return Event{CtrlT, 0, nil}
|
return Event{keyfn('t'), 0, nil}
|
||||||
case tcell.KeyCtrlU:
|
case tcell.KeyCtrlU:
|
||||||
return Event{CtrlU, 0, nil}
|
return Event{keyfn('u'), 0, nil}
|
||||||
case tcell.KeyCtrlV:
|
case tcell.KeyCtrlV:
|
||||||
return Event{CtrlV, 0, nil}
|
return Event{keyfn('v'), 0, nil}
|
||||||
case tcell.KeyCtrlW:
|
case tcell.KeyCtrlW:
|
||||||
return Event{CtrlW, 0, nil}
|
return Event{keyfn('w'), 0, nil}
|
||||||
case tcell.KeyCtrlX:
|
case tcell.KeyCtrlX:
|
||||||
return Event{CtrlX, 0, nil}
|
return Event{keyfn('x'), 0, nil}
|
||||||
case tcell.KeyCtrlY:
|
case tcell.KeyCtrlY:
|
||||||
return Event{CtrlY, 0, nil}
|
return Event{keyfn('y'), 0, nil}
|
||||||
case tcell.KeyCtrlZ:
|
case tcell.KeyCtrlZ:
|
||||||
return Event{CtrlZ, 0, nil}
|
return Event{keyfn('z'), 0, nil}
|
||||||
case tcell.KeyCtrlSpace:
|
case tcell.KeyCtrlSpace:
|
||||||
return Event{CtrlSpace, 0, nil}
|
return Event{CtrlSpace, 0, nil}
|
||||||
case tcell.KeyBackspace, tcell.KeyBackspace2:
|
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 {
|
if alt {
|
||||||
return Event{AltBS, 0, nil}
|
return Event{AltBS, 0, nil}
|
||||||
}
|
}
|
||||||
return Event{BSpace, 0, nil}
|
return Event{BSpace, 0, nil}
|
||||||
|
|
||||||
case tcell.KeyUp:
|
case tcell.KeyUp:
|
||||||
|
if alt {
|
||||||
|
return Event{AltUp, 0, nil}
|
||||||
|
}
|
||||||
return Event{Up, 0, nil}
|
return Event{Up, 0, nil}
|
||||||
case tcell.KeyDown:
|
case tcell.KeyDown:
|
||||||
|
if alt {
|
||||||
|
return Event{AltDown, 0, nil}
|
||||||
|
}
|
||||||
return Event{Down, 0, nil}
|
return Event{Down, 0, nil}
|
||||||
case tcell.KeyLeft:
|
case tcell.KeyLeft:
|
||||||
|
if alt {
|
||||||
|
return Event{AltLeft, 0, nil}
|
||||||
|
}
|
||||||
return Event{Left, 0, nil}
|
return Event{Left, 0, nil}
|
||||||
case tcell.KeyRight:
|
case tcell.KeyRight:
|
||||||
|
if alt {
|
||||||
|
return Event{AltRight, 0, nil}
|
||||||
|
}
|
||||||
return Event{Right, 0, nil}
|
return Event{Right, 0, nil}
|
||||||
|
|
||||||
case tcell.KeyHome:
|
case tcell.KeyHome:
|
||||||
@@ -301,8 +329,6 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
case tcell.KeyPgDn:
|
case tcell.KeyPgDn:
|
||||||
return Event{PgDn, 0, nil}
|
return Event{PgDn, 0, nil}
|
||||||
|
|
||||||
case tcell.KeyTab:
|
|
||||||
return Event{Tab, 0, nil}
|
|
||||||
case tcell.KeyBacktab:
|
case tcell.KeyBacktab:
|
||||||
return Event{BTab, 0, nil}
|
return Event{BTab, 0, nil}
|
||||||
|
|
||||||
@@ -359,13 +385,16 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Pause() {
|
func (r *FullscreenRenderer) Pause(clear bool) {
|
||||||
|
if clear {
|
||||||
_screen.Fini()
|
_screen.Fini()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Resume() bool {
|
func (r *FullscreenRenderer) Resume(clear bool) {
|
||||||
|
if clear {
|
||||||
r.initScreen()
|
r.initScreen()
|
||||||
return true
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Close() {
|
func (r *FullscreenRenderer) Close() {
|
||||||
@@ -380,14 +409,18 @@ func (r *FullscreenRenderer) RefreshWindows(windows []Window) {
|
|||||||
_screen.Show()
|
_screen.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
// TODO
|
normal := ColNormal
|
||||||
|
if preview {
|
||||||
|
normal = ColPreview
|
||||||
|
}
|
||||||
return &TcellWindow{
|
return &TcellWindow{
|
||||||
color: r.theme != nil,
|
color: r.theme != nil,
|
||||||
top: top,
|
top: top,
|
||||||
left: left,
|
left: left,
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
|
normal: normal,
|
||||||
borderStyle: borderStyle}
|
borderStyle: borderStyle}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,17 +428,16 @@ func (w *TcellWindow) Close() {
|
|||||||
// TODO
|
// 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 ly := 0; ly <= h; ly++ {
|
||||||
for lx := 0; lx <= w; lx++ {
|
for lx := 0; lx <= w; lx++ {
|
||||||
_screen.SetContent(x+lx, y+ly, r, nil, ColDefault.style())
|
_screen.SetContent(x+lx, y+ly, r, nil, n.style())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Erase() {
|
func (w *TcellWindow) Erase() {
|
||||||
// TODO
|
fill(w.left-1, w.top, w.width+1, w.height, w.normal, ' ')
|
||||||
fill(w.left, w.top, w.width, w.height, ' ')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Enclose(y int, x int) bool {
|
func (w *TcellWindow) Enclose(y int, x int) bool {
|
||||||
@@ -422,13 +454,13 @@ func (w *TcellWindow) Move(y int, x int) {
|
|||||||
func (w *TcellWindow) MoveAndClear(y int, x int) {
|
func (w *TcellWindow) MoveAndClear(y int, x int) {
|
||||||
w.Move(y, x)
|
w.Move(y, x)
|
||||||
for i := w.lastX; i < w.width; i++ {
|
for i := w.lastX; i < w.width; i++ {
|
||||||
_screen.SetContent(i+w.left, w.lastY+w.top, rune(' '), nil, ColDefault.style())
|
_screen.SetContent(i+w.left, w.lastY+w.top, rune(' '), nil, w.normal.style())
|
||||||
}
|
}
|
||||||
w.lastX = x
|
w.lastX = x
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Print(text string) {
|
func (w *TcellWindow) Print(text string) {
|
||||||
w.printString(text, ColDefault, 0)
|
w.printString(text, w.normal, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) {
|
func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) {
|
||||||
@@ -441,7 +473,7 @@ func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) {
|
|||||||
Reverse(a&Attr(tcell.AttrReverse) != 0).
|
Reverse(a&Attr(tcell.AttrReverse) != 0).
|
||||||
Underline(a&Attr(tcell.AttrUnderline) != 0)
|
Underline(a&Attr(tcell.AttrUnderline) != 0)
|
||||||
} else {
|
} else {
|
||||||
style = ColDefault.style().
|
style = w.normal.style().
|
||||||
Reverse(a&Attr(tcell.AttrReverse) != 0 || pair == ColCurrent || pair == ColCurrentMatch).
|
Reverse(a&Attr(tcell.AttrReverse) != 0 || pair == ColCurrent || pair == ColCurrentMatch).
|
||||||
Underline(a&Attr(tcell.AttrUnderline) != 0 || pair == ColMatch || pair == ColCurrentMatch)
|
Underline(a&Attr(tcell.AttrUnderline) != 0 || pair == ColMatch || pair == ColCurrentMatch)
|
||||||
}
|
}
|
||||||
@@ -492,7 +524,7 @@ func (w *TcellWindow) fillString(text string, pair ColorPair, a Attr) FillReturn
|
|||||||
if w.color {
|
if w.color {
|
||||||
style = pair.style()
|
style = pair.style()
|
||||||
} else {
|
} else {
|
||||||
style = ColDefault.style()
|
style = w.normal.style()
|
||||||
}
|
}
|
||||||
style = style.
|
style = style.
|
||||||
Blink(a&Attr(tcell.AttrBlink) != 0).
|
Blink(a&Attr(tcell.AttrBlink) != 0).
|
||||||
@@ -532,14 +564,24 @@ func (w *TcellWindow) fillString(text string, pair ColorPair, a Attr) FillReturn
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Fill(str string) FillReturn {
|
func (w *TcellWindow) Fill(str string) FillReturn {
|
||||||
return w.fillString(str, ColDefault, 0)
|
return w.fillString(str, w.normal, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
|
func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
|
||||||
return w.fillString(str, ColorPair{fg, bg, -1}, a)
|
if fg == colDefault {
|
||||||
|
fg = w.normal.Fg()
|
||||||
|
}
|
||||||
|
if bg == colDefault {
|
||||||
|
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
|
left := w.left
|
||||||
right := left + w.width
|
right := left + w.width
|
||||||
top := w.top
|
top := w.top
|
||||||
@@ -547,25 +589,29 @@ func (w *TcellWindow) drawBorder(around bool) {
|
|||||||
|
|
||||||
var style tcell.Style
|
var style tcell.Style
|
||||||
if w.color {
|
if w.color {
|
||||||
style = ColBorder.style()
|
if w.borderStyle.shape == BorderAround {
|
||||||
|
style = ColPreviewBorder.style()
|
||||||
} else {
|
} else {
|
||||||
style = ColDefault.style()
|
style = ColBorder.style()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
style = w.normal.style()
|
||||||
}
|
}
|
||||||
|
|
||||||
for x := left; x < right; x++ {
|
for x := left; x < right; x++ {
|
||||||
_screen.SetContent(x, top, tcell.RuneHLine, nil, style)
|
_screen.SetContent(x, top, w.borderStyle.horizontal, nil, style)
|
||||||
_screen.SetContent(x, bot-1, tcell.RuneHLine, nil, style)
|
_screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style)
|
||||||
}
|
}
|
||||||
|
|
||||||
if around {
|
if w.borderStyle.shape == BorderAround {
|
||||||
for y := top; y < bot; y++ {
|
for y := top; y < bot; y++ {
|
||||||
_screen.SetContent(left, y, tcell.RuneVLine, nil, style)
|
_screen.SetContent(left, y, w.borderStyle.vertical, nil, style)
|
||||||
_screen.SetContent(right-1, y, tcell.RuneVLine, nil, style)
|
_screen.SetContent(right-1, y, w.borderStyle.vertical, nil, style)
|
||||||
}
|
}
|
||||||
|
|
||||||
_screen.SetContent(left, top, tcell.RuneULCorner, nil, style)
|
_screen.SetContent(left, top, w.borderStyle.topLeft, nil, style)
|
||||||
_screen.SetContent(right-1, top, tcell.RuneURCorner, nil, style)
|
_screen.SetContent(right-1, top, w.borderStyle.topRight, nil, style)
|
||||||
_screen.SetContent(left, bot-1, tcell.RuneLLCorner, nil, style)
|
_screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style)
|
||||||
_screen.SetContent(right-1, bot-1, tcell.RuneLRCorner, nil, style)
|
_screen.SetContent(right-1, bot-1, w.borderStyle.bottomRight, nil, style)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
31
src/tui/ttyname_unix.go
Normal file
31
src/tui/ttyname_unix.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package tui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
var devPrefixes = [...]string{"/dev/pts/", "/dev/"}
|
||||||
|
|
||||||
|
func ttyname() string {
|
||||||
|
var stderr syscall.Stat_t
|
||||||
|
if syscall.Fstat(2, &stderr) != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, prefix := range devPrefixes {
|
||||||
|
files, err := ioutil.ReadDir(prefix)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
if stat, ok := file.Sys().(*syscall.Stat_t); ok && stat.Rdev == stderr.Rdev {
|
||||||
|
return prefix + file.Name()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
7
src/tui/ttyname_windows.go
Normal file
7
src/tui/ttyname_windows.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package tui
|
||||||
|
|
||||||
|
func ttyname() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
192
src/tui/tui.go
192
src/tui/tui.go
@@ -40,10 +40,18 @@ const (
|
|||||||
ESC
|
ESC
|
||||||
CtrlSpace
|
CtrlSpace
|
||||||
|
|
||||||
|
// https://apple.stackexchange.com/questions/24261/how-do-i-send-c-that-is-control-slash-to-the-terminal
|
||||||
|
CtrlBackSlash
|
||||||
|
CtrlRightBracket
|
||||||
|
CtrlCaret
|
||||||
|
CtrlSlash
|
||||||
|
|
||||||
Invalid
|
Invalid
|
||||||
Resize
|
Resize
|
||||||
Mouse
|
Mouse
|
||||||
DoubleClick
|
DoubleClick
|
||||||
|
LeftClick
|
||||||
|
RightClick
|
||||||
|
|
||||||
BTab
|
BTab
|
||||||
BSpace
|
BSpace
|
||||||
@@ -59,6 +67,8 @@ const (
|
|||||||
Home
|
Home
|
||||||
End
|
End
|
||||||
|
|
||||||
|
SUp
|
||||||
|
SDown
|
||||||
SLeft
|
SLeft
|
||||||
SRight
|
SRight
|
||||||
|
|
||||||
@@ -75,11 +85,17 @@ const (
|
|||||||
F11
|
F11
|
||||||
F12
|
F12
|
||||||
|
|
||||||
AltEnter
|
Change
|
||||||
|
|
||||||
AltSpace
|
AltSpace
|
||||||
AltSlash
|
AltSlash
|
||||||
AltBS
|
AltBS
|
||||||
|
|
||||||
|
AltUp
|
||||||
|
AltDown
|
||||||
|
AltLeft
|
||||||
|
AltRight
|
||||||
|
|
||||||
Alt0
|
Alt0
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -91,6 +107,8 @@ const ( // Reset iota
|
|||||||
AltE
|
AltE
|
||||||
AltF
|
AltF
|
||||||
AltZ = AltA + 'z' - 'a'
|
AltZ = AltA + 'z' - 'a'
|
||||||
|
CtrlAltA = AltZ + 1
|
||||||
|
CtrlAltM = CtrlAltA + 'm' - 'a'
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -105,7 +123,7 @@ func (c Color) is24() bool {
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
colUndefined Color = -2
|
colUndefined Color = -2
|
||||||
colDefault = -1
|
colDefault Color = -1
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -130,7 +148,7 @@ const (
|
|||||||
type ColorPair struct {
|
type ColorPair struct {
|
||||||
fg Color
|
fg Color
|
||||||
bg Color
|
bg Color
|
||||||
id int16
|
id int
|
||||||
}
|
}
|
||||||
|
|
||||||
func HexToColor(rrggbb string) Color {
|
func HexToColor(rrggbb string) Color {
|
||||||
@@ -152,18 +170,13 @@ func (p ColorPair) Bg() Color {
|
|||||||
return p.bg
|
return p.bg
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p ColorPair) key() int {
|
|
||||||
return (int(p.Fg()) << 8) + int(p.Bg())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p ColorPair) is24() bool {
|
|
||||||
return p.Fg().is24() || p.Bg().is24()
|
|
||||||
}
|
|
||||||
|
|
||||||
type ColorTheme struct {
|
type ColorTheme struct {
|
||||||
Fg Color
|
Fg Color
|
||||||
Bg Color
|
Bg Color
|
||||||
|
PreviewFg Color
|
||||||
|
PreviewBg Color
|
||||||
DarkBg Color
|
DarkBg Color
|
||||||
|
Gutter Color
|
||||||
Prompt Color
|
Prompt Color
|
||||||
Match Color
|
Match Color
|
||||||
Current Color
|
Current Color
|
||||||
@@ -176,10 +189,6 @@ type ColorTheme struct {
|
|||||||
Border Color
|
Border Color
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ColorTheme) HasBg() bool {
|
|
||||||
return t.Bg != colDefault
|
|
||||||
}
|
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
Type int
|
Type int
|
||||||
Char rune
|
Char rune
|
||||||
@@ -190,23 +199,70 @@ type MouseEvent struct {
|
|||||||
Y int
|
Y int
|
||||||
X int
|
X int
|
||||||
S int
|
S int
|
||||||
|
Left bool
|
||||||
Down bool
|
Down bool
|
||||||
Double bool
|
Double bool
|
||||||
Mod bool
|
Mod bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type BorderStyle int
|
type BorderShape int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
BorderNone BorderStyle = iota
|
BorderNone BorderShape = iota
|
||||||
BorderAround
|
BorderAround
|
||||||
BorderHorizontal
|
BorderHorizontal
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type BorderStyle struct {
|
||||||
|
shape BorderShape
|
||||||
|
horizontal rune
|
||||||
|
vertical rune
|
||||||
|
topLeft rune
|
||||||
|
topRight rune
|
||||||
|
bottomLeft rune
|
||||||
|
bottomRight rune
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
type Renderer interface {
|
||||||
Init()
|
Init()
|
||||||
Pause()
|
Pause(clear bool)
|
||||||
Resume() bool
|
Resume(clear bool)
|
||||||
Clear()
|
Clear()
|
||||||
RefreshWindows(windows []Window)
|
RefreshWindows(windows []Window)
|
||||||
Refresh()
|
Refresh()
|
||||||
@@ -217,9 +273,8 @@ type Renderer interface {
|
|||||||
MaxX() int
|
MaxX() int
|
||||||
MaxY() int
|
MaxY() int
|
||||||
DoesAutoWrap() bool
|
DoesAutoWrap() bool
|
||||||
IsOptimized() 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 {
|
type Window interface {
|
||||||
@@ -233,6 +288,7 @@ type Window interface {
|
|||||||
Close()
|
Close()
|
||||||
|
|
||||||
X() int
|
X() int
|
||||||
|
Y() int
|
||||||
Enclose(y int, x int) bool
|
Enclose(y int, x int) bool
|
||||||
|
|
||||||
Move(y int, x int)
|
Move(y int, x int)
|
||||||
@@ -267,26 +323,31 @@ var (
|
|||||||
Dark256 *ColorTheme
|
Dark256 *ColorTheme
|
||||||
Light256 *ColorTheme
|
Light256 *ColorTheme
|
||||||
|
|
||||||
ColDefault ColorPair
|
|
||||||
ColNormal ColorPair
|
|
||||||
ColPrompt ColorPair
|
ColPrompt ColorPair
|
||||||
|
ColNormal ColorPair
|
||||||
ColMatch ColorPair
|
ColMatch ColorPair
|
||||||
ColCurrent ColorPair
|
|
||||||
ColCurrentMatch ColorPair
|
|
||||||
ColSpinner ColorPair
|
|
||||||
ColInfo ColorPair
|
|
||||||
ColCursor ColorPair
|
ColCursor ColorPair
|
||||||
ColSelected ColorPair
|
ColSelected ColorPair
|
||||||
|
ColCurrent ColorPair
|
||||||
|
ColCurrentMatch ColorPair
|
||||||
|
ColCurrentCursor ColorPair
|
||||||
|
ColCurrentSelected ColorPair
|
||||||
|
ColSpinner ColorPair
|
||||||
|
ColInfo ColorPair
|
||||||
ColHeader ColorPair
|
ColHeader ColorPair
|
||||||
ColBorder ColorPair
|
ColBorder ColorPair
|
||||||
ColUser ColorPair
|
ColPreview ColorPair
|
||||||
|
ColPreviewBorder ColorPair
|
||||||
)
|
)
|
||||||
|
|
||||||
func EmptyTheme() *ColorTheme {
|
func EmptyTheme() *ColorTheme {
|
||||||
return &ColorTheme{
|
return &ColorTheme{
|
||||||
Fg: colUndefined,
|
Fg: colUndefined,
|
||||||
Bg: colUndefined,
|
Bg: colUndefined,
|
||||||
|
PreviewFg: colUndefined,
|
||||||
|
PreviewBg: colUndefined,
|
||||||
DarkBg: colUndefined,
|
DarkBg: colUndefined,
|
||||||
|
Gutter: colUndefined,
|
||||||
Prompt: colUndefined,
|
Prompt: colUndefined,
|
||||||
Match: colUndefined,
|
Match: colUndefined,
|
||||||
Current: colUndefined,
|
Current: colUndefined,
|
||||||
@@ -308,7 +369,10 @@ func init() {
|
|||||||
Default16 = &ColorTheme{
|
Default16 = &ColorTheme{
|
||||||
Fg: colDefault,
|
Fg: colDefault,
|
||||||
Bg: colDefault,
|
Bg: colDefault,
|
||||||
|
PreviewFg: colUndefined,
|
||||||
|
PreviewBg: colUndefined,
|
||||||
DarkBg: colBlack,
|
DarkBg: colBlack,
|
||||||
|
Gutter: colUndefined,
|
||||||
Prompt: colBlue,
|
Prompt: colBlue,
|
||||||
Match: colGreen,
|
Match: colGreen,
|
||||||
Current: colYellow,
|
Current: colYellow,
|
||||||
@@ -322,7 +386,10 @@ func init() {
|
|||||||
Dark256 = &ColorTheme{
|
Dark256 = &ColorTheme{
|
||||||
Fg: colDefault,
|
Fg: colDefault,
|
||||||
Bg: colDefault,
|
Bg: colDefault,
|
||||||
|
PreviewFg: colUndefined,
|
||||||
|
PreviewBg: colUndefined,
|
||||||
DarkBg: 236,
|
DarkBg: 236,
|
||||||
|
Gutter: colUndefined,
|
||||||
Prompt: 110,
|
Prompt: 110,
|
||||||
Match: 108,
|
Match: 108,
|
||||||
Current: 254,
|
Current: 254,
|
||||||
@@ -336,7 +403,10 @@ func init() {
|
|||||||
Light256 = &ColorTheme{
|
Light256 = &ColorTheme{
|
||||||
Fg: colDefault,
|
Fg: colDefault,
|
||||||
Bg: colDefault,
|
Bg: colDefault,
|
||||||
|
PreviewFg: colUndefined,
|
||||||
|
PreviewBg: colUndefined,
|
||||||
DarkBg: 251,
|
DarkBg: 251,
|
||||||
|
Gutter: colUndefined,
|
||||||
Prompt: 25,
|
Prompt: 25,
|
||||||
Match: 66,
|
Match: 66,
|
||||||
Current: 237,
|
Current: 237,
|
||||||
@@ -367,7 +437,10 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
|
|||||||
}
|
}
|
||||||
theme.Fg = o(baseTheme.Fg, theme.Fg)
|
theme.Fg = o(baseTheme.Fg, theme.Fg)
|
||||||
theme.Bg = o(baseTheme.Bg, theme.Bg)
|
theme.Bg = o(baseTheme.Bg, theme.Bg)
|
||||||
|
theme.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.DarkBg = o(baseTheme.DarkBg, theme.DarkBg)
|
||||||
|
theme.Gutter = o(theme.DarkBg, o(baseTheme.Gutter, theme.Gutter))
|
||||||
theme.Prompt = o(baseTheme.Prompt, theme.Prompt)
|
theme.Prompt = o(baseTheme.Prompt, theme.Prompt)
|
||||||
theme.Match = o(baseTheme.Match, theme.Match)
|
theme.Match = o(baseTheme.Match, theme.Match)
|
||||||
theme.Current = o(baseTheme.Current, theme.Current)
|
theme.Current = o(baseTheme.Current, theme.Current)
|
||||||
@@ -383,33 +456,44 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func initPalette(theme *ColorTheme) {
|
func initPalette(theme *ColorTheme) {
|
||||||
ColDefault = ColorPair{colDefault, colDefault, 0}
|
idx := 0
|
||||||
if theme != nil {
|
pair := func(fg, bg Color) ColorPair {
|
||||||
ColNormal = ColorPair{theme.Fg, theme.Bg, 1}
|
idx++
|
||||||
ColPrompt = ColorPair{theme.Prompt, theme.Bg, 2}
|
return ColorPair{fg, bg, idx}
|
||||||
ColMatch = ColorPair{theme.Match, theme.Bg, 3}
|
}
|
||||||
ColCurrent = ColorPair{theme.Current, theme.DarkBg, 4}
|
if theme != nil {
|
||||||
ColCurrentMatch = ColorPair{theme.CurrentMatch, theme.DarkBg, 5}
|
ColPrompt = pair(theme.Prompt, theme.Bg)
|
||||||
ColSpinner = ColorPair{theme.Spinner, theme.Bg, 6}
|
ColNormal = pair(theme.Fg, theme.Bg)
|
||||||
ColInfo = ColorPair{theme.Info, theme.Bg, 7}
|
ColMatch = pair(theme.Match, theme.Bg)
|
||||||
ColCursor = ColorPair{theme.Cursor, theme.DarkBg, 8}
|
ColCursor = pair(theme.Cursor, theme.Gutter)
|
||||||
ColSelected = ColorPair{theme.Selected, theme.DarkBg, 9}
|
ColSelected = pair(theme.Selected, theme.Gutter)
|
||||||
ColHeader = ColorPair{theme.Header, theme.Bg, 10}
|
ColCurrent = pair(theme.Current, theme.DarkBg)
|
||||||
ColBorder = ColorPair{theme.Border, theme.Bg, 11}
|
ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg)
|
||||||
} else {
|
ColCurrentCursor = pair(theme.Cursor, theme.DarkBg)
|
||||||
ColNormal = ColorPair{colDefault, colDefault, 1}
|
ColCurrentSelected = pair(theme.Selected, theme.DarkBg)
|
||||||
ColPrompt = ColorPair{colDefault, colDefault, 2}
|
ColSpinner = pair(theme.Spinner, theme.Bg)
|
||||||
ColMatch = ColorPair{colDefault, colDefault, 3}
|
ColInfo = pair(theme.Info, theme.Bg)
|
||||||
ColCurrent = ColorPair{colDefault, colDefault, 4}
|
ColHeader = pair(theme.Header, theme.Bg)
|
||||||
ColCurrentMatch = ColorPair{colDefault, colDefault, 5}
|
ColBorder = pair(theme.Border, theme.Bg)
|
||||||
ColSpinner = ColorPair{colDefault, colDefault, 6}
|
ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
|
||||||
ColInfo = ColorPair{colDefault, colDefault, 7}
|
ColPreviewBorder = pair(theme.Border, theme.PreviewBg)
|
||||||
ColCursor = ColorPair{colDefault, colDefault, 8}
|
} else {
|
||||||
ColSelected = ColorPair{colDefault, colDefault, 9}
|
ColPrompt = pair(colDefault, colDefault)
|
||||||
ColHeader = ColorPair{colDefault, colDefault, 10}
|
ColNormal = pair(colDefault, colDefault)
|
||||||
ColBorder = ColorPair{colDefault, colDefault, 11}
|
ColMatch = 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)
|
||||||
}
|
}
|
||||||
ColUser = ColorPair{colDefault, colDefault, 12}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func attrFor(color ColorPair, attr Attr) Attr {
|
func attrFor(color ColorPair, attr Attr) Attr {
|
||||||
|
|||||||
@@ -1,65 +1,111 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
overflow64 uint64 = 0x8080808080808080
|
||||||
|
overflow32 uint32 = 0x80808080
|
||||||
)
|
)
|
||||||
|
|
||||||
type Chars struct {
|
type Chars struct {
|
||||||
runes []rune
|
slice []byte // or []rune
|
||||||
bytes []byte
|
inBytes bool
|
||||||
|
trimLengthKnown bool
|
||||||
|
trimLength uint16
|
||||||
|
|
||||||
|
// XXX Piggybacking item index here is a horrible idea. But I'm trying to
|
||||||
|
// minimize the memory footprint by not wasting padded spaces.
|
||||||
|
Index int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkAscii(bytes []byte) (bool, int) {
|
||||||
|
i := 0
|
||||||
|
for ; i <= len(bytes)-8; i += 8 {
|
||||||
|
if (overflow64 & *(*uint64)(unsafe.Pointer(&bytes[i]))) > 0 {
|
||||||
|
return false, i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ; i <= len(bytes)-4; i += 4 {
|
||||||
|
if (overflow32 & *(*uint32)(unsafe.Pointer(&bytes[i]))) > 0 {
|
||||||
|
return false, i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ; i < len(bytes); i++ {
|
||||||
|
if bytes[i] >= utf8.RuneSelf {
|
||||||
|
return false, i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToChars converts byte array into rune array
|
// ToChars converts byte array into rune array
|
||||||
func ToChars(bytea []byte) Chars {
|
func ToChars(bytes []byte) Chars {
|
||||||
var runes []rune
|
inBytes, bytesUntil := checkAscii(bytes)
|
||||||
ascii := true
|
if inBytes {
|
||||||
numBytes := len(bytea)
|
return Chars{slice: bytes, inBytes: inBytes}
|
||||||
for i := 0; i < numBytes; {
|
|
||||||
if bytea[i] < utf8.RuneSelf {
|
|
||||||
if !ascii {
|
|
||||||
runes = append(runes, rune(bytea[i]))
|
|
||||||
}
|
}
|
||||||
i++
|
|
||||||
} else {
|
runes := make([]rune, bytesUntil, len(bytes))
|
||||||
if ascii {
|
for i := 0; i < bytesUntil; i++ {
|
||||||
ascii = false
|
runes[i] = rune(bytes[i])
|
||||||
runes = make([]rune, i, numBytes)
|
|
||||||
for j := 0; j < i; j++ {
|
|
||||||
runes[j] = rune(bytea[j])
|
|
||||||
}
|
}
|
||||||
}
|
for i := bytesUntil; i < len(bytes); {
|
||||||
r, sz := utf8.DecodeRune(bytea[i:])
|
r, sz := utf8.DecodeRune(bytes[i:])
|
||||||
i += sz
|
i += sz
|
||||||
runes = append(runes, r)
|
runes = append(runes, r)
|
||||||
}
|
}
|
||||||
}
|
return RunesToChars(runes)
|
||||||
if ascii {
|
|
||||||
return Chars{bytes: bytea}
|
|
||||||
}
|
|
||||||
return Chars{runes: runes}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunesToChars(runes []rune) Chars {
|
func RunesToChars(runes []rune) Chars {
|
||||||
return Chars{runes: runes}
|
return Chars{slice: *(*[]byte)(unsafe.Pointer(&runes)), inBytes: false}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) IsBytes() bool {
|
||||||
|
return chars.inBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) Bytes() []byte {
|
||||||
|
return chars.slice
|
||||||
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) optionalRunes() []rune {
|
||||||
|
if chars.inBytes {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return *(*[]rune)(unsafe.Pointer(&chars.slice))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chars *Chars) Get(i int) rune {
|
func (chars *Chars) Get(i int) rune {
|
||||||
if chars.runes != nil {
|
if runes := chars.optionalRunes(); runes != nil {
|
||||||
return chars.runes[i]
|
return runes[i]
|
||||||
}
|
}
|
||||||
return rune(chars.bytes[i])
|
return rune(chars.slice[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chars *Chars) Length() int {
|
func (chars *Chars) Length() int {
|
||||||
if chars.runes != nil {
|
if runes := chars.optionalRunes(); runes != nil {
|
||||||
return len(chars.runes)
|
return len(runes)
|
||||||
}
|
}
|
||||||
return len(chars.bytes)
|
return len(chars.slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of a Chars object.
|
||||||
|
func (chars *Chars) String() string {
|
||||||
|
return fmt.Sprintf("Chars{slice: []byte(%q), inBytes: %v, trimLengthKnown: %v, trimLength: %d, Index: %d}", chars.slice, chars.inBytes, chars.trimLengthKnown, chars.trimLength, chars.Index)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrimLength returns the length after trimming leading and trailing whitespaces
|
// TrimLength returns the length after trimming leading and trailing whitespaces
|
||||||
func (chars *Chars) TrimLength() int {
|
func (chars *Chars) TrimLength() uint16 {
|
||||||
|
if chars.trimLengthKnown {
|
||||||
|
return chars.trimLength
|
||||||
|
}
|
||||||
|
chars.trimLengthKnown = true
|
||||||
var i int
|
var i int
|
||||||
len := chars.Length()
|
len := chars.Length()
|
||||||
for i = len - 1; i >= 0; i-- {
|
for i = len - 1; i >= 0; i-- {
|
||||||
@@ -80,7 +126,8 @@ func (chars *Chars) TrimLength() int {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return i - j + 1
|
chars.trimLength = AsUint16(i - j + 1)
|
||||||
|
return chars.trimLength
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chars *Chars) TrailingWhitespaces() int {
|
func (chars *Chars) TrailingWhitespaces() int {
|
||||||
@@ -95,63 +142,45 @@ func (chars *Chars) TrailingWhitespaces() int {
|
|||||||
return whitespaces
|
return whitespaces
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) TrimTrailingWhitespaces() {
|
||||||
|
whitespaces := chars.TrailingWhitespaces()
|
||||||
|
chars.slice = chars.slice[0 : len(chars.slice)-whitespaces]
|
||||||
|
}
|
||||||
|
|
||||||
func (chars *Chars) ToString() string {
|
func (chars *Chars) ToString() string {
|
||||||
if chars.runes != nil {
|
if runes := chars.optionalRunes(); runes != nil {
|
||||||
return string(chars.runes)
|
return string(runes)
|
||||||
}
|
}
|
||||||
return string(chars.bytes)
|
return string(chars.slice)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chars *Chars) ToRunes() []rune {
|
func (chars *Chars) ToRunes() []rune {
|
||||||
if chars.runes != nil {
|
if runes := chars.optionalRunes(); runes != nil {
|
||||||
return chars.runes
|
return runes
|
||||||
}
|
}
|
||||||
runes := make([]rune, len(chars.bytes))
|
bytes := chars.slice
|
||||||
for idx, b := range chars.bytes {
|
runes := make([]rune, len(bytes))
|
||||||
|
for idx, b := range bytes {
|
||||||
runes[idx] = rune(b)
|
runes[idx] = rune(b)
|
||||||
}
|
}
|
||||||
return runes
|
return runes
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chars *Chars) Slice(b int, e int) Chars {
|
func (chars *Chars) CopyRunes(dest []rune) {
|
||||||
if chars.runes != nil {
|
if runes := chars.optionalRunes(); runes != nil {
|
||||||
return Chars{runes: chars.runes[b:e]}
|
copy(dest, runes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for idx, b := range chars.slice[:len(dest)] {
|
||||||
|
dest[idx] = rune(b)
|
||||||
}
|
}
|
||||||
return Chars{bytes: chars.bytes[b:e]}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chars *Chars) Split(delimiter string) []Chars {
|
func (chars *Chars) Prepend(prefix string) {
|
||||||
delim := []rune(delimiter)
|
if runes := chars.optionalRunes(); runes != nil {
|
||||||
numChars := chars.Length()
|
runes = append([]rune(prefix), runes...)
|
||||||
numDelim := len(delim)
|
chars.slice = *(*[]byte)(unsafe.Pointer(&runes))
|
||||||
begin := 0
|
|
||||||
ret := make([]Chars, 0, 1)
|
|
||||||
|
|
||||||
for index := 0; index < numChars; {
|
|
||||||
if index+numDelim <= numChars {
|
|
||||||
match := true
|
|
||||||
for off, d := range delim {
|
|
||||||
if chars.Get(index+off) != d {
|
|
||||||
match = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Found the delimiter
|
|
||||||
if match {
|
|
||||||
incr := Max(numDelim, 1)
|
|
||||||
ret = append(ret, chars.Slice(begin, index+incr))
|
|
||||||
index += incr
|
|
||||||
begin = index
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Impossible to find the delimiter in the remaining substring
|
chars.slice = append([]byte(prefix), chars.slice...)
|
||||||
break
|
|
||||||
}
|
}
|
||||||
index++
|
|
||||||
}
|
|
||||||
if begin < numChars || len(ret) == 0 {
|
|
||||||
ret = append(ret, chars.Slice(begin, numChars))
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,27 +2,16 @@ package util
|
|||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
func TestToCharsNil(t *testing.T) {
|
|
||||||
bs := Chars{bytes: []byte{}}
|
|
||||||
if bs.bytes == nil || bs.runes != nil {
|
|
||||||
t.Error()
|
|
||||||
}
|
|
||||||
rs := RunesToChars([]rune{})
|
|
||||||
if rs.bytes != nil || rs.runes == nil {
|
|
||||||
t.Error()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestToCharsAscii(t *testing.T) {
|
func TestToCharsAscii(t *testing.T) {
|
||||||
chars := ToChars([]byte("foobar"))
|
chars := ToChars([]byte("foobar"))
|
||||||
if chars.ToString() != "foobar" || chars.runes != nil {
|
if !chars.inBytes || chars.ToString() != "foobar" || !chars.inBytes {
|
||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCharsLength(t *testing.T) {
|
func TestCharsLength(t *testing.T) {
|
||||||
chars := ToChars([]byte("\tabc한글 "))
|
chars := ToChars([]byte("\tabc한글 "))
|
||||||
if chars.Length() != 8 || chars.TrimLength() != 5 {
|
if chars.inBytes || chars.Length() != 8 || chars.TrimLength() != 5 {
|
||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,7 +25,7 @@ func TestCharsToString(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTrimLength(t *testing.T) {
|
func TestTrimLength(t *testing.T) {
|
||||||
check := func(str string, exp int) {
|
check := func(str string, exp uint16) {
|
||||||
chars := ToChars([]byte(str))
|
chars := ToChars([]byte(str))
|
||||||
trimmed := chars.TrimLength()
|
trimmed := chars.TrimLength()
|
||||||
if trimmed != exp {
|
if trimmed != exp {
|
||||||
@@ -55,28 +44,3 @@ func TestTrimLength(t *testing.T) {
|
|||||||
check(" h o ", 5)
|
check(" h o ", 5)
|
||||||
check(" ", 0)
|
check(" ", 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSplit(t *testing.T) {
|
|
||||||
check := func(str string, delim string, tokens ...string) {
|
|
||||||
input := ToChars([]byte(str))
|
|
||||||
result := input.Split(delim)
|
|
||||||
if len(result) != len(tokens) {
|
|
||||||
t.Errorf("Invalid Split result for '%s': %d tokens found (expected %d): %s",
|
|
||||||
str, len(result), len(tokens), result)
|
|
||||||
}
|
|
||||||
for idx, token := range tokens {
|
|
||||||
if result[idx].ToString() != token {
|
|
||||||
t.Errorf("Invalid Split result for '%s': %s (expected %s)",
|
|
||||||
str, result[idx].ToString(), token)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
check("abc:def::", ":", "abc:", "def:", ":")
|
|
||||||
check("abc:def::", "-", "abc:def::")
|
|
||||||
check("abc", "", "a", "b", "c")
|
|
||||||
check("abc", "a", "a", "bc")
|
|
||||||
check("abc", "ab", "ab", "c")
|
|
||||||
check("abc", "abc", "abc")
|
|
||||||
check("abc", "abcd", "abc")
|
|
||||||
check("", "abcd", "")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -26,23 +26,23 @@ func NewEventBox() *EventBox {
|
|||||||
// Wait blocks the goroutine until signaled
|
// Wait blocks the goroutine until signaled
|
||||||
func (b *EventBox) Wait(callback func(*Events)) {
|
func (b *EventBox) Wait(callback func(*Events)) {
|
||||||
b.cond.L.Lock()
|
b.cond.L.Lock()
|
||||||
defer b.cond.L.Unlock()
|
|
||||||
|
|
||||||
if len(b.events) == 0 {
|
if len(b.events) == 0 {
|
||||||
b.cond.Wait()
|
b.cond.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
callback(&b.events)
|
callback(&b.events)
|
||||||
|
b.cond.L.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set turns on the event type on the box
|
// Set turns on the event type on the box
|
||||||
func (b *EventBox) Set(event EventType, value interface{}) {
|
func (b *EventBox) Set(event EventType, value interface{}) {
|
||||||
b.cond.L.Lock()
|
b.cond.L.Lock()
|
||||||
defer b.cond.L.Unlock()
|
|
||||||
b.events[event] = value
|
b.events[event] = value
|
||||||
if _, found := b.ignore[event]; !found {
|
if _, found := b.ignore[event]; !found {
|
||||||
b.cond.Broadcast()
|
b.cond.Broadcast()
|
||||||
}
|
}
|
||||||
|
b.cond.L.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear clears the events
|
// Clear clears the events
|
||||||
@@ -56,27 +56,27 @@ func (events *Events) Clear() {
|
|||||||
// Peek peeks at the event box if the given event is set
|
// Peek peeks at the event box if the given event is set
|
||||||
func (b *EventBox) Peek(event EventType) bool {
|
func (b *EventBox) Peek(event EventType) bool {
|
||||||
b.cond.L.Lock()
|
b.cond.L.Lock()
|
||||||
defer b.cond.L.Unlock()
|
|
||||||
_, ok := b.events[event]
|
_, ok := b.events[event]
|
||||||
|
b.cond.L.Unlock()
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// Watch deletes the events from the ignore list
|
// Watch deletes the events from the ignore list
|
||||||
func (b *EventBox) Watch(events ...EventType) {
|
func (b *EventBox) Watch(events ...EventType) {
|
||||||
b.cond.L.Lock()
|
b.cond.L.Lock()
|
||||||
defer b.cond.L.Unlock()
|
|
||||||
for _, event := range events {
|
for _, event := range events {
|
||||||
delete(b.ignore, event)
|
delete(b.ignore, event)
|
||||||
}
|
}
|
||||||
|
b.cond.L.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unwatch adds the events to the ignore list
|
// Unwatch adds the events to the ignore list
|
||||||
func (b *EventBox) Unwatch(events ...EventType) {
|
func (b *EventBox) Unwatch(events ...EventType) {
|
||||||
b.cond.L.Lock()
|
b.cond.L.Lock()
|
||||||
defer b.cond.L.Unlock()
|
|
||||||
for _, event := range events {
|
for _, event := range events {
|
||||||
b.ignore[event] = true
|
b.ignore[event] = true
|
||||||
}
|
}
|
||||||
|
b.cond.L.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// WaitFor blocks the execution until the event is received
|
// WaitFor blocks the execution until the event is received
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/junegunn/go-isatty"
|
"github.com/mattn/go-isatty"
|
||||||
"github.com/junegunn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _runeWidths = make(map[rune]int)
|
var _runeWidths = make(map[rune]int)
|
||||||
@@ -17,11 +17,12 @@ func RuneWidth(r rune, prefixWidth int, tabstop int) int {
|
|||||||
return tabstop - prefixWidth%tabstop
|
return tabstop - prefixWidth%tabstop
|
||||||
} else if w, found := _runeWidths[r]; found {
|
} else if w, found := _runeWidths[r]; found {
|
||||||
return w
|
return w
|
||||||
} else {
|
} else if r == '\n' || r == '\r' {
|
||||||
w := Max(runewidth.RuneWidth(r), 1)
|
return 1
|
||||||
|
}
|
||||||
|
w := runewidth.RuneWidth(r)
|
||||||
_runeWidths[r] = w
|
_runeWidths[r] = w
|
||||||
return w
|
return w
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Max returns the largest integer
|
// Max returns the largest integer
|
||||||
@@ -111,3 +112,13 @@ func DurWithin(
|
|||||||
func IsTty() bool {
|
func IsTty() bool {
|
||||||
return isatty.IsTerminal(os.Stdin.Fd())
|
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)
|
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,12 +9,26 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ExecCommand executes the given command with $SHELL
|
// ExecCommand executes the given command with $SHELL
|
||||||
func ExecCommand(command string) *exec.Cmd {
|
func ExecCommand(command string, setpgid bool) *exec.Cmd {
|
||||||
shell := os.Getenv("SHELL")
|
shell := os.Getenv("SHELL")
|
||||||
if len(shell) == 0 {
|
if len(shell) == 0 {
|
||||||
shell = "sh"
|
shell = "sh"
|
||||||
}
|
}
|
||||||
return exec.Command(shell, "-c", command)
|
return ExecCommandWith(shell, command, setpgid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecCommandWith executes the given command with the specified shell
|
||||||
|
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
|
// IsWindows returns true on Windows
|
||||||
@@ -22,7 +36,12 @@ func IsWindows() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetNonBlock executes syscall.SetNonblock on file descriptor
|
// SetNonblock executes syscall.SetNonblock on file descriptor
|
||||||
func SetNonblock(file *os.File, nonblock bool) {
|
func SetNonblock(file *os.File, nonblock bool) {
|
||||||
syscall.SetNonblock(int(file.Fd()), nonblock)
|
syscall.SetNonblock(int(file.Fd()), nonblock)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read executes syscall.Read on file descriptor
|
||||||
|
func Read(fd int, b []byte) (int, error) {
|
||||||
|
return syscall.Read(int(fd), b)
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,24 +3,34 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/junegunn/go-shellwords"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExecCommand executes the given command with $SHELL
|
// ExecCommand executes the given command with cmd
|
||||||
func ExecCommand(command string) *exec.Cmd {
|
func ExecCommand(command string, setpgid bool) *exec.Cmd {
|
||||||
shell := os.Getenv("SHELL")
|
return ExecCommandWith("cmd", command, setpgid)
|
||||||
if len(shell) == 0 {
|
}
|
||||||
shell = "cmd"
|
|
||||||
|
// ExecCommandWith executes the given command with cmd. _shell parameter is
|
||||||
|
// ignored on Windows.
|
||||||
|
// 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(` /v:on/s/c "%s"`, command),
|
||||||
|
CreationFlags: 0,
|
||||||
}
|
}
|
||||||
args, _ := shellwords.Parse(command)
|
return cmd
|
||||||
allArgs := make([]string, len(args)+1)
|
}
|
||||||
allArgs[0] = "/c"
|
|
||||||
copy(allArgs[1:], args)
|
// KillCommand kills the process for the given command
|
||||||
return exec.Command(shell, allArgs...)
|
func KillCommand(cmd *exec.Cmd) error {
|
||||||
|
return cmd.Process.Kill()
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsWindows returns true on Windows
|
// IsWindows returns true on Windows
|
||||||
@@ -28,7 +38,12 @@ func IsWindows() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetNonBlock executes syscall.SetNonblock on file descriptor
|
// SetNonblock executes syscall.SetNonblock on file descriptor
|
||||||
func SetNonblock(file *os.File, nonblock bool) {
|
func SetNonblock(file *os.File, nonblock bool) {
|
||||||
syscall.SetNonblock(syscall.Handle(file.Fd()), nonblock)
|
syscall.SetNonblock(syscall.Handle(file.Fd()), nonblock)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read executes syscall.Read on file descriptor
|
||||||
|
func Read(fd int, b []byte) (int, error) {
|
||||||
|
return syscall.Read(syscall.Handle(fd), b)
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,12 +6,15 @@ Execute (Setup):
|
|||||||
|
|
||||||
Execute (fzf#run with dir option):
|
Execute (fzf#run with dir option):
|
||||||
let cwd = getcwd()
|
let cwd = getcwd()
|
||||||
let result = fzf#run({ 'options': '--filter=vdr', 'dir': g:dir })
|
let result = fzf#run({ 'source': 'git ls-files', 'options': '--filter=vdr', 'dir': g:dir })
|
||||||
AssertEqual ['fzf.vader'], result
|
AssertEqual ['fzf.vader'], result
|
||||||
|
AssertEqual 0, haslocaldir()
|
||||||
AssertEqual getcwd(), cwd
|
AssertEqual getcwd(), cwd
|
||||||
|
|
||||||
let result = sort(fzf#run({ 'options': '--filter e', 'dir': g:dir }))
|
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 ['fzf.vader', 'test_go.rb'], result
|
||||||
|
AssertEqual 1, haslocaldir()
|
||||||
AssertEqual getcwd(), cwd
|
AssertEqual getcwd(), cwd
|
||||||
|
|
||||||
Execute (fzf#run with Funcref command):
|
Execute (fzf#run with Funcref command):
|
||||||
@@ -19,7 +22,7 @@ Execute (fzf#run with Funcref command):
|
|||||||
function! g:FzfTest(e)
|
function! g:FzfTest(e)
|
||||||
call add(g:ret, a:e)
|
call add(g:ret, a:e)
|
||||||
endfunction
|
endfunction
|
||||||
let result = sort(fzf#run({ 'sink': function('g:FzfTest'), 'options': '--filter e', 'dir': g:dir }))
|
let result = sort(fzf#run({ 'source': 'git ls-files', 'sink': function('g:FzfTest'), 'options': '--filter e', 'dir': g:dir }))
|
||||||
AssertEqual ['fzf.vader', 'test_go.rb'], result
|
AssertEqual ['fzf.vader', 'test_go.rb'], result
|
||||||
AssertEqual ['fzf.vader', 'test_go.rb'], sort(g:ret)
|
AssertEqual ['fzf.vader', 'test_go.rb'], sort(g:ret)
|
||||||
|
|
||||||
@@ -56,11 +59,11 @@ Execute (Incomplete fzf#run with dir option and autochdir):
|
|||||||
" No change in working directory even if &acd is set
|
" No change in working directory even if &acd is set
|
||||||
AssertEqual cwd, getcwd()
|
AssertEqual cwd, getcwd()
|
||||||
|
|
||||||
Execute (fzf#run with dir option and autochdir):
|
Execute (FIXME: fzf#run with dir option and autochdir):
|
||||||
set acd
|
set acd
|
||||||
let cwd = getcwd()
|
|
||||||
call fzf#run({'source': ['/foobar'], 'sink': 'e', 'dir': '/tmp', 'options': '-1'})
|
call fzf#run({'source': ['/foobar'], 'sink': 'e', 'dir': '/tmp', 'options': '-1'})
|
||||||
" Working directory changed due to &acd
|
" Working directory changed due to &acd
|
||||||
|
AssertEqual '/foobar', expand('%')
|
||||||
AssertEqual '/', getcwd()
|
AssertEqual '/', getcwd()
|
||||||
|
|
||||||
Execute (fzf#run with dir option and autochdir when final cwd is same as dir):
|
Execute (fzf#run with dir option and autochdir when final cwd is same as dir):
|
||||||
@@ -140,13 +143,33 @@ Execute (fzf#wrap):
|
|||||||
let g:fzf_history_dir = '/tmp'
|
let g:fzf_history_dir = '/tmp'
|
||||||
let opts = fzf#wrap('foobar', {'options': '--color light'})
|
let opts = fzf#wrap('foobar', {'options': '--color light'})
|
||||||
Log opts
|
Log opts
|
||||||
Assert opts.options =~ '--history /tmp/foobar'
|
Assert opts.options =~ "--history '/tmp/foobar'"
|
||||||
Assert opts.options =~ '--color light'
|
Assert opts.options =~ '--color light'
|
||||||
|
|
||||||
let g:fzf_colors = { 'fg': ['fg', 'Error'] }
|
let g:fzf_colors = { 'fg': ['fg', 'Error'] }
|
||||||
let opts = fzf#wrap({})
|
let opts = fzf#wrap({})
|
||||||
Assert opts.options =~ '^--color=fg:'
|
Assert opts.options =~ '^--color=fg:'
|
||||||
|
|
||||||
|
Execute (fzf#shellescape with sh):
|
||||||
|
AssertEqual '''''', fzf#shellescape('', 'sh')
|
||||||
|
AssertEqual '''\''', fzf#shellescape('\', 'sh')
|
||||||
|
AssertEqual '''""''', fzf#shellescape('""', 'sh')
|
||||||
|
AssertEqual '''foobar>''', fzf#shellescape('foobar>', 'sh')
|
||||||
|
AssertEqual '''\\\"\\\''', fzf#shellescape('\\\"\\\', 'sh')
|
||||||
|
AssertEqual '''echo ''\''''a''\'''' && echo ''\''''b''\''''''', fzf#shellescape('echo ''a'' && echo ''b''', 'sh')
|
||||||
|
|
||||||
|
Execute (fzf#shellescape with cmd.exe):
|
||||||
|
AssertEqual '^"^"', fzf#shellescape('', 'cmd.exe')
|
||||||
|
AssertEqual '^"\\^"', fzf#shellescape('\', 'cmd.exe')
|
||||||
|
AssertEqual '^"\^"\^"^"', fzf#shellescape('""', 'cmd.exe')
|
||||||
|
AssertEqual '^"foobar^>^"', fzf#shellescape('foobar>', 'cmd.exe')
|
||||||
|
AssertEqual '^"\\\\\\\^"\\\\\\^"', fzf#shellescape('\\\"\\\', 'cmd.exe')
|
||||||
|
AssertEqual '^"echo ''a'' ^&^& echo ''b''^"', fzf#shellescape('echo ''a'' && echo ''b''', 'cmd.exe')
|
||||||
|
|
||||||
|
AssertEqual '^"C:\Program Files ^(x86^)\\^"', fzf#shellescape('C:\Program Files (x86)\', 'cmd.exe')
|
||||||
|
AssertEqual '^"C:/Program Files ^(x86^)/^"', fzf#shellescape('C:/Program Files (x86)/', 'cmd.exe')
|
||||||
|
AssertEqual '^"%%USERPROFILE%%^"', fzf#shellescape('%USERPROFILE%', 'cmd.exe')
|
||||||
|
|
||||||
Execute (Cleanup):
|
Execute (Cleanup):
|
||||||
unlet g:dir
|
unlet g:dir
|
||||||
Restore
|
Restore
|
||||||
|
|||||||
906
test/test_go.rb
Normal file → Executable file
906
test/test_go.rb
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
72
uninstall
72
uninstall
@@ -1,12 +1,45 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
confirm() {
|
xdg=0
|
||||||
while [ 1 ]; do
|
prefix='~/.fzf'
|
||||||
read -p "$1" -n 1 -r
|
prefix_expand=~/.fzf
|
||||||
echo
|
fish_dir=${XDG_CONFIG_HOME:-$HOME/.config}/fish
|
||||||
if [[ "$REPLY" =~ ^[Yy] ]]; then
|
|
||||||
|
help() {
|
||||||
|
cat << EOF
|
||||||
|
usage: $0 [OPTIONS]
|
||||||
|
|
||||||
|
--help Show this message
|
||||||
|
--xdg Remove files generated under \$XDG_CONFIG_HOME/fzf
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
for opt in "$@"; do
|
||||||
|
case $opt in
|
||||||
|
--help)
|
||||||
|
help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--xdg)
|
||||||
|
xdg=1
|
||||||
|
prefix='"${XDG_CONFIG_HOME:-$HOME/.config}"/fzf/fzf'
|
||||||
|
prefix_expand=${XDG_CONFIG_HOME:-$HOME/.config}/fzf/fzf
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "unknown option: $opt"
|
||||||
|
help
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
ask() {
|
||||||
|
while true; do
|
||||||
|
read -p "$1 ([y]/n) " -r
|
||||||
|
REPLY=${REPLY:-"y"}
|
||||||
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
return 0
|
return 0
|
||||||
elif [[ "$REPLY" =~ ^[Nn] ]]; then
|
elif [[ $REPLY =~ ^[Nn]$ ]]; then
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
@@ -40,7 +73,7 @@ remove_line() {
|
|||||||
content=$(sed 's/^[0-9]*://' <<< "$line")
|
content=$(sed 's/^[0-9]*://' <<< "$line")
|
||||||
match=1
|
match=1
|
||||||
echo " - Line #$line_no: $content"
|
echo " - Line #$line_no: $content"
|
||||||
[ "$content" = "$1" ] || confirm " - Remove (y/n) ? "
|
[ "$content" = "$1" ] || ask " - Remove?"
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
awk -v n=$line_no 'NR == n {next} {print}' "$src" > "$src.bak" &&
|
awk -v n=$line_no 'NR == n {next} {print}' "$src" > "$src.bak" &&
|
||||||
mv "$src.bak" "$src" || break
|
mv "$src.bak" "$src" || break
|
||||||
@@ -55,25 +88,30 @@ remove_line() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for shell in bash zsh; do
|
for shell in bash zsh; do
|
||||||
remove ~/.fzf.${shell}
|
shell_config=${prefix_expand}.${shell}
|
||||||
|
remove "${shell_config}"
|
||||||
remove_line ~/.${shell}rc \
|
remove_line ~/.${shell}rc \
|
||||||
"[ -f ~/.fzf.${shell} ] && source ~/.fzf.${shell}" \
|
"[ -f ${prefix}.${shell} ] && source ${prefix}.${shell}" \
|
||||||
"source ~/.fzf.${shell}"
|
"source ${prefix}.${shell}"
|
||||||
done
|
done
|
||||||
|
|
||||||
bind_file=~/.config/fish/functions/fish_user_key_bindings.fish
|
bind_file="${fish_dir}/functions/fish_user_key_bindings.fish"
|
||||||
if [ -f "$bind_file" ]; then
|
if [ -f "$bind_file" ]; then
|
||||||
remove_line "$bind_file" "fzf_key_bindings"
|
remove_line "$bind_file" "fzf_key_bindings"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -d ~/.config/fish/functions ]; then
|
if [ -d "${fish_dir}/functions" ]; then
|
||||||
remove ~/.config/fish/functions/fzf.fish
|
remove "${fish_dir}/functions/fzf.fish"
|
||||||
remove ~/.config/fish/functions/fzf_key_bindings.fish
|
remove "${fish_dir}/functions/fzf_key_bindings.fish"
|
||||||
|
|
||||||
if [ "$(ls -A ~/.config/fish/functions)" ]; then
|
if [ "$(ls -A "${fish_dir}/functions")" ]; then
|
||||||
echo "Can't delete non-empty directory: \"~/.config/fish/functions\""
|
echo "Can't delete non-empty directory: \"${fish_dir}/functions\""
|
||||||
else
|
else
|
||||||
rmdir ~/.config/fish/functions
|
rmdir "${fish_dir}/functions"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
config_dir=$(dirname "$prefix_expand")
|
||||||
|
if [[ "$xdg" = 1 ]] && [[ "$config_dir" = */fzf ]] && [[ -d "$config_dir" ]]; then
|
||||||
|
rmdir "$config_dir"
|
||||||
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user