mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-16 07:13:48 -05:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff951341c9 | ||
|
|
df570afd52 | ||
|
|
07d755df11 | ||
|
|
37585bd5a5 | ||
|
|
89e24bf8f2 | ||
|
|
8d2fcd3518 | ||
|
|
f39ab3875e | ||
|
|
82efe6c60d | ||
|
|
75972d59a8 | ||
|
|
e7d60aac9c | ||
|
|
a0bfbdd49c | ||
|
|
ba594982f0 | ||
|
|
2157f4f193 | ||
|
|
309bae423c | ||
|
|
4f8bf2ae78 | ||
|
|
85c1f8a9e0 | ||
|
|
e00e7e1e56 | ||
|
|
1a6defdbcc | ||
|
|
ef577a6509 | ||
|
|
b7c6838e45 | ||
|
|
91d04cec5c | ||
|
|
3bd8441079 | ||
|
|
8cf45a5197 | ||
|
|
8dc1377efb | ||
|
|
6c32148f90 | ||
|
|
315e568de0 | ||
|
|
5d16b28869 | ||
|
|
5624a89231 | ||
|
|
63c42b14f2 | ||
|
|
6f1eaa9b39 | ||
|
|
ca42e5e00a | ||
|
|
61feee690c | ||
|
|
d4ed955aee |
41
.travis.yml
41
.travis.yml
@@ -1,20 +1,27 @@
|
||||
language: ruby
|
||||
dist: trusty
|
||||
sudo: required
|
||||
matrix:
|
||||
language: go
|
||||
dist: xenial
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- sourceline: "ppa:pi-rho/dev"
|
||||
- sourceline: "ppa:fish-shell/release-2"
|
||||
packages:
|
||||
- tmux
|
||||
- zsh
|
||||
- fish
|
||||
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- env: TAGS=
|
||||
rvm: 2.3.3
|
||||
# - env: TAGS=tcell
|
||||
# rvm: 2.3.3
|
||||
- stage: unittest
|
||||
go: "1.11.x"
|
||||
script: make && make test
|
||||
|
||||
install:
|
||||
- sudo add-apt-repository -y ppa:pi-rho/dev
|
||||
- sudo apt-add-repository -y ppa:fish-shell/release-2
|
||||
- sudo apt-get update
|
||||
- sudo apt-get install -y tmux zsh fish
|
||||
- stage: cli
|
||||
go: "1.11.x"
|
||||
rvm: "2.5"
|
||||
script: |
|
||||
make install && ./install --all && tmux new "ruby test/test_go.rb > out && touch ok" && cat out && [ -e ok ]
|
||||
|
||||
script: |
|
||||
make test install &&
|
||||
./install --all &&
|
||||
tmux new "ruby test/test_go.rb > out && touch ok" && cat out && [ -e ok ]
|
||||
|
||||
4
BUILD.md
4
BUILD.md
@@ -6,12 +6,10 @@ Build instructions
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- `go` executable in $PATH
|
||||
- Go 1.11 or above
|
||||
|
||||
### Using Makefile
|
||||
|
||||
Makefile will set up and use its own `$GOPATH` under the project root.
|
||||
|
||||
```sh
|
||||
# Build fzf binary for your platform in target
|
||||
make
|
||||
|
||||
15
CHANGELOG.md
15
CHANGELOG.md
@@ -1,6 +1,21 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
0.18.0
|
||||
------
|
||||
|
||||
- Added placeholder expression for zero-based item index: `{n}` and `{+n}`
|
||||
- `fzf --preview 'echo {n}: {}'`
|
||||
- Added color option for the gutter: `--color gutter:-1`
|
||||
- Added `--no-unicode` option for drawing borders in non-Unicode, ASCII
|
||||
characters
|
||||
- `FZF_PREVIEW_LINES` and `FZF_PREVIEW_COLUMNS` are exported to preview process
|
||||
- fzf still overrides `LINES` and `COLUMNS` as before, but they may be
|
||||
reset by the default shell.
|
||||
- Bug fixes and improvements
|
||||
- See https://github.com/junegunn/fzf/milestone/14?closed=1
|
||||
- Built with Go 1.12.1
|
||||
|
||||
0.17.5
|
||||
------
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
FROM archlinux/base:latest
|
||||
RUN pacman -Sy && pacman --noconfirm -S awk git tmux zsh fish ruby procps go make
|
||||
RUN gem install --no-ri --no-rdoc minitest
|
||||
RUN gem install --no-document minitest
|
||||
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
||||
RUN echo '. ~/.bashrc' >> ~/.bash_profile
|
||||
|
||||
|
||||
65
Makefile
65
Makefile
@@ -1,17 +1,9 @@
|
||||
ifndef GOOS
|
||||
GOOS := $(word 1, $(subst /, " ", $(word 4, $(shell go version))))
|
||||
endif
|
||||
GO ?= go
|
||||
GOOS ?= $(word 1, $(subst /, " ", $(word 4, $(shell go version))))
|
||||
|
||||
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
|
||||
ROOT_DIR := $(shell dirname $(MAKEFILE))
|
||||
GOPATH := $(ROOT_DIR)/gopath
|
||||
SRC_LINK := $(GOPATH)/src/github.com/junegunn/fzf/src
|
||||
VENDOR_LINK := $(GOPATH)/src/github.com/junegunn/fzf/vendor
|
||||
export GOPATH
|
||||
|
||||
GLIDE_YAML := glide.yaml
|
||||
GLIDE_LOCK := glide.lock
|
||||
SOURCES := $(wildcard *.go src/*.go src/*/*.go) $(SRC_LINK) $(VENDOR_LINK) $(GLIDE_LOCK) $(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)"
|
||||
@@ -90,19 +82,8 @@ release-all: clean test
|
||||
GOOS=openbsd make release
|
||||
GOOS=windows make release
|
||||
|
||||
$(SRC_LINK):
|
||||
mkdir -p $(shell dirname $(SRC_LINK))
|
||||
ln -sf $(ROOT_DIR)/src $(SRC_LINK)
|
||||
|
||||
$(VENDOR_LINK):
|
||||
mkdir -p $(shell dirname $(VENDOR_LINK))
|
||||
ln -sf $(ROOT_DIR)/vendor $(VENDOR_LINK)
|
||||
|
||||
vendor: $(GLIDE_YAML)
|
||||
go get -u github.com/Masterminds/glide && $(GOPATH)/bin/glide install && touch $@
|
||||
|
||||
test: $(SOURCES) vendor
|
||||
SHELL=/bin/sh GOOS= go test -v -tags "$(TAGS)" \
|
||||
test: $(SOURCES)
|
||||
SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \
|
||||
github.com/junegunn/fzf/src \
|
||||
github.com/junegunn/fzf/src/algo \
|
||||
github.com/junegunn/fzf/src/tui \
|
||||
@@ -111,29 +92,29 @@ test: $(SOURCES) vendor
|
||||
install: bin/fzf
|
||||
|
||||
clean:
|
||||
rm -rf target
|
||||
$(RM) -r target
|
||||
|
||||
target/$(BINARY32): $(SOURCES) vendor
|
||||
GOARCH=386 go build $(BUILD_FLAGS) -o $@
|
||||
target/$(BINARY32): $(SOURCES)
|
||||
GOARCH=386 $(GO) build $(BUILD_FLAGS) -o $@
|
||||
|
||||
target/$(BINARY64): $(SOURCES) vendor
|
||||
GOARCH=amd64 go build $(BUILD_FLAGS) -o $@
|
||||
target/$(BINARY64): $(SOURCES)
|
||||
GOARCH=amd64 $(GO) build $(BUILD_FLAGS) -o $@
|
||||
|
||||
# https://github.com/golang/go/wiki/GoArm
|
||||
target/$(BINARYARM5): $(SOURCES) vendor
|
||||
GOARCH=arm GOARM=5 go build $(BUILD_FLAGS) -o $@
|
||||
target/$(BINARYARM5): $(SOURCES)
|
||||
GOARCH=arm GOARM=5 $(GO) build $(BUILD_FLAGS) -o $@
|
||||
|
||||
target/$(BINARYARM6): $(SOURCES) vendor
|
||||
GOARCH=arm GOARM=6 go build $(BUILD_FLAGS) -o $@
|
||||
target/$(BINARYARM6): $(SOURCES)
|
||||
GOARCH=arm GOARM=6 $(GO) build $(BUILD_FLAGS) -o $@
|
||||
|
||||
target/$(BINARYARM7): $(SOURCES) vendor
|
||||
GOARCH=arm GOARM=7 go build $(BUILD_FLAGS) -o $@
|
||||
target/$(BINARYARM7): $(SOURCES)
|
||||
GOARCH=arm GOARM=7 $(GO) build $(BUILD_FLAGS) -o $@
|
||||
|
||||
target/$(BINARYARM8): $(SOURCES) vendor
|
||||
GOARCH=arm64 go build $(BUILD_FLAGS) -o $@
|
||||
target/$(BINARYARM8): $(SOURCES)
|
||||
GOARCH=arm64 $(GO) build $(BUILD_FLAGS) -o $@
|
||||
|
||||
target/$(BINARYPPC64LE): $(SOURCES) vendor
|
||||
GOARCH=ppc64le 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
|
||||
@@ -146,4 +127,8 @@ docker-test:
|
||||
docker build -t fzf-arch .
|
||||
docker run -it fzf-arch
|
||||
|
||||
.PHONY: all release release-all test install clean docker docker-test
|
||||
update:
|
||||
$(GO) get -u
|
||||
$(GO) mod tidy
|
||||
|
||||
.PHONY: all release release-all test install clean docker docker-test update
|
||||
|
||||
36
README.md
36
README.md
@@ -88,6 +88,10 @@ brew install fzf
|
||||
$(brew --prefix)/opt/fzf/install
|
||||
```
|
||||
|
||||
fzf is also available [via MacPorts][portfile]: `sudo port install fzf`
|
||||
|
||||
[portfile]: https://github.com/macports/macports-ports/blob/master/sysutils/fzf/Portfile
|
||||
|
||||
### Using git
|
||||
|
||||
Alternatively, you can "git clone" this repository to any directory and run
|
||||
@@ -127,10 +131,10 @@ But instead of separately installing fzf on your system (using Homebrew or
|
||||
vim-plug to do both.
|
||||
|
||||
```vim
|
||||
" PlugInstall and PlugUpdate will clone fzf in ~/.fzf and run install script
|
||||
" PlugInstall and PlugUpdate will clone fzf in ~/.fzf and run the install script
|
||||
Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
|
||||
" Both options are optional. You don't have to install fzf in ~/.fzf
|
||||
" and you don't have to run install script if you use fzf only in Vim.
|
||||
" and you don't have to run the install script if you use fzf only in Vim.
|
||||
```
|
||||
|
||||
### Arch Linux
|
||||
@@ -312,16 +316,16 @@ fullscreen mode.
|
||||
fzf --height 40%
|
||||
```
|
||||
|
||||
Key bindings for command line
|
||||
Key bindings for command-line
|
||||
-----------------------------
|
||||
|
||||
The install script will setup the following key bindings for bash, zsh, and
|
||||
fish.
|
||||
|
||||
- `CTRL-T` - Paste the selected files and directories onto the command line
|
||||
- `CTRL-T` - Paste the selected files and directories onto the command-line
|
||||
- Set `FZF_CTRL_T_COMMAND` to override the default command
|
||||
- Set `FZF_CTRL_T_OPTS` to pass additional options
|
||||
- `CTRL-R` - Paste the selected command from history onto the command line
|
||||
- `CTRL-R` - Paste the selected command from history onto the command-line
|
||||
- If you want to see the commands in chronological order, press `CTRL-R`
|
||||
again which toggles sorting by relevance
|
||||
- Set `FZF_CTRL_R_OPTS` to pass additional options
|
||||
@@ -373,7 +377,7 @@ cd ~/github/fzf**<TAB>
|
||||
|
||||
#### Process IDs
|
||||
|
||||
Fuzzy completion for PIDs is provided for kill command. In this case
|
||||
Fuzzy completion for PIDs is provided for kill command. In this case,
|
||||
there is no trigger sequence, just press tab key after kill command.
|
||||
|
||||
```sh
|
||||
@@ -426,7 +430,7 @@ _fzf_compgen_dir() {
|
||||
|
||||
On bash, fuzzy completion is enabled only for a predefined set of commands
|
||||
(`complete | grep _fzf` to see the list). But you can enable it for other
|
||||
commands as well like follows.
|
||||
commands as well as follows.
|
||||
|
||||
```sh
|
||||
complete -F _fzf_path_completion -o default -o bashdefault ag
|
||||
@@ -443,7 +447,7 @@ Advanced topics
|
||||
|
||||
### Performance
|
||||
|
||||
fzf is fast, and is [getting even faster][perf]. Performance should not be
|
||||
fzf is fast and is [getting even faster][perf]. Performance should not be
|
||||
a problem in most use cases. However, you might want to be aware of the
|
||||
options that affect the performance.
|
||||
|
||||
@@ -454,7 +458,7 @@ options that affect the performance.
|
||||
- `--with-nth` makes fzf slower as fzf has to tokenize and reassemble each
|
||||
line.
|
||||
- If you absolutely need better performance, you can consider using
|
||||
`--algo=v1` (the default being `v2`) to make fzf use faster greedy
|
||||
`--algo=v1` (the default being `v2`) to make fzf use a faster greedy
|
||||
algorithm. However, this algorithm is not guaranteed to find the optimal
|
||||
ordering of the matches and is not recommended.
|
||||
|
||||
@@ -475,7 +479,7 @@ See *KEY BINDINGS* section of the man page for details.
|
||||
|
||||
### Preview window
|
||||
|
||||
When `--preview` option is set, fzf automatically starts external process with
|
||||
When `--preview` option is set, fzf automatically starts an external process with
|
||||
the current line as the argument and shows the result in the split window.
|
||||
|
||||
```bash
|
||||
@@ -483,7 +487,7 @@ the current line as the argument and shows the result in the split window.
|
||||
fzf --preview 'cat {}'
|
||||
```
|
||||
|
||||
Since preview window is updated only after the process is complete, it's
|
||||
Since the preview window is updated only after the process is complete, it's
|
||||
important that the command finishes quickly.
|
||||
|
||||
```bash
|
||||
@@ -494,15 +498,17 @@ fzf --preview 'head -100 {}'
|
||||
Preview window supports ANSI colors, so you can use programs that
|
||||
syntax-highlights the content of a file.
|
||||
|
||||
- Bat: https://github.com/sharkdp/bat
|
||||
- Highlight: http://www.andre-simon.de/doku/highlight/en/highlight.php
|
||||
- CodeRay: http://coderay.rubychan.de/
|
||||
- Rouge: https://github.com/jneen/rouge
|
||||
|
||||
```bash
|
||||
# Try highlight, coderay, rougify in turn, then fall back to cat
|
||||
# Try bat, highlight, coderay, rougify in turn, then fall back to cat
|
||||
fzf --preview '[[ $(file --mime {}) =~ binary ]] &&
|
||||
echo {} is a binary file ||
|
||||
(highlight -O ansi -l {} ||
|
||||
(bat --style=numbers --color=always {} ||
|
||||
highlight -O ansi -l {} ||
|
||||
coderay {} ||
|
||||
rougify {} ||
|
||||
cat {}) 2> /dev/null | head -500'
|
||||
@@ -582,9 +588,9 @@ fzf -m | while read -l r; set result $result $r; end; and vim $result
|
||||
```
|
||||
|
||||
The globbing system is different in fish and thus `**` completion will not work.
|
||||
However, the `CTRL-T` command will use the last token on the commandline as the
|
||||
However, the `CTRL-T` command will use the last token on the command-line as the
|
||||
root folder for the recursive search. For instance, hitting `CTRL-T` at the end
|
||||
of the following commandline
|
||||
of the following command-line
|
||||
|
||||
```sh
|
||||
ls /var/
|
||||
|
||||
48
glide.lock
generated
48
glide.lock
generated
@@ -1,48 +0,0 @@
|
||||
hash: b617c76661b399f586276767bb93ee67b65dd03cfd1348ecad409e372ea97b3e
|
||||
updated: 2018-06-27T18:37:20.189962-07:00
|
||||
imports:
|
||||
- name: github.com/codegangsta/cli
|
||||
version: c6af8847eb2b7b297d07c3ede98903e95e680ef9
|
||||
- name: github.com/gdamore/encoding
|
||||
version: b23993cbb6353f0e6aa98d0ee318a34728f628b9
|
||||
- name: github.com/gdamore/tcell
|
||||
version: 0a0db94084dfe181108c18508ebd312f12d331fb
|
||||
subpackages:
|
||||
- encoding
|
||||
- name: github.com/lucasb-eyer/go-colorful
|
||||
version: c900de9dbbc73129068f5af6a823068fc5f2308c
|
||||
- name: github.com/Masterminds/semver
|
||||
version: 15d8430ab86497c5c0da827b748823945e1cf1e1
|
||||
- name: github.com/Masterminds/vcs
|
||||
version: 6f1c6d150500e452704e9863f68c2559f58616bf
|
||||
- name: github.com/mattn/go-isatty
|
||||
version: 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8
|
||||
- name: github.com/mattn/go-runewidth
|
||||
version: 14207d285c6c197daabb5c9793d63e7af9ab2d50
|
||||
- name: github.com/mattn/go-shellwords
|
||||
version: 02e3cf038dcea8290e44424da473dd12be796a8a
|
||||
- name: github.com/mitchellh/go-homedir
|
||||
version: b8bc1bf767474819792c23f32d8286a45736f1c6
|
||||
- name: golang.org/x/crypto
|
||||
version: 558b6879de74bc843225cde5686419267ff707ca
|
||||
subpackages:
|
||||
- ssh/terminal
|
||||
- name: golang.org/x/sys
|
||||
version: b90f89a1e7a9c1f6b918820b3daa7f08488c8594
|
||||
subpackages:
|
||||
- unix
|
||||
- name: golang.org/x/text
|
||||
version: 4ee4af566555f5fbe026368b75596286a312663a
|
||||
subpackages:
|
||||
- encoding
|
||||
- encoding/charmap
|
||||
- encoding/internal
|
||||
- encoding/internal/identifier
|
||||
- encoding/japanese
|
||||
- encoding/korean
|
||||
- encoding/simplifiedchinese
|
||||
- encoding/traditionalchinese
|
||||
- transform
|
||||
- name: gopkg.in/yaml.v2
|
||||
version: 287cf08546ab5e7e37d55a84f7ed3fd1db036de5
|
||||
testImports: []
|
||||
16
glide.yaml
16
glide.yaml
@@ -1,16 +0,0 @@
|
||||
package: github.com/junegunn/fzf
|
||||
import:
|
||||
- package: github.com/mattn/go-isatty
|
||||
version: 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8
|
||||
- package: github.com/mattn/go-runewidth
|
||||
version: 14207d285c6c197daabb5c9793d63e7af9ab2d50
|
||||
- package: github.com/mattn/go-shellwords
|
||||
version: 02e3cf038dcea8290e44424da473dd12be796a8a
|
||||
- package: github.com/gdamore/tcell
|
||||
version: 0a0db94084dfe181108c18508ebd312f12d331fb
|
||||
subpackages:
|
||||
- encoding
|
||||
- package: golang.org/x/crypto
|
||||
version: 558b6879de74bc843225cde5686419267ff707ca
|
||||
subpackages:
|
||||
- ssh/terminal
|
||||
17
go.mod
Normal file
17
go.mod
Normal file
@@ -0,0 +1,17 @@
|
||||
module github.com/junegunn/fzf
|
||||
|
||||
require (
|
||||
github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635 // indirect
|
||||
github.com/gdamore/tcell v0.0.0-20170915061752-0a0db94084df
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
|
||||
github.com/jtolds/gls v4.2.1+incompatible // indirect
|
||||
github.com/lucasb-eyer/go-colorful v0.0.0-20170223221042-c900de9dbbc7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c
|
||||
github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c
|
||||
github.com/mattn/go-shellwords v1.0.3
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
|
||||
golang.org/x/crypto v0.0.0-20170728183002-558b6879de74
|
||||
golang.org/x/sys v0.0.0-20170529185110-b90f89a1e7a9 // indirect
|
||||
golang.org/x/text v0.0.0-20170530162606-4ee4af566555 // indirect
|
||||
)
|
||||
26
go.sum
Normal file
26
go.sum
Normal file
@@ -0,0 +1,26 @@
|
||||
github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635 h1:hheUEMzaOie/wKeIc1WPa7CDVuIO5hqQxjS+dwTQEnI=
|
||||
github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635/go.mod h1:yrQYJKKDTrHmbYxI7CYi+/hbdiDT2m4Hj+t0ikCjsrQ=
|
||||
github.com/gdamore/tcell v0.0.0-20170915061752-0a0db94084df h1:tLS1QD2puA1USLvkEnGfOt+Zp2ijtNIK3z2YFaIZZR4=
|
||||
github.com/gdamore/tcell v0.0.0-20170915061752-0a0db94084df/go.mod h1:tqyG50u7+Ctv1w5VX67kLzKcj9YXR/JSBZQq/+mLl1A=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
|
||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/lucasb-eyer/go-colorful v0.0.0-20170223221042-c900de9dbbc7 h1:G52I+Gk/wPD4HKvKT0Vxxp9OUPxqKs3OK6rffSPtNkA=
|
||||
github.com/lucasb-eyer/go-colorful v0.0.0-20170223221042-c900de9dbbc7/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
|
||||
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c h1:3nKFouDdpgGUV/uerJcYWH45ZbJzX0SiVWfTgmUeTzc=
|
||||
github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c h1:eFzthqtg3W6Pihj3DMTXLAF4f+ge5r5Ie5g6HLIZAF0=
|
||||
github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-shellwords v1.0.3 h1:K/VxK7SZ+cvuPgFSLKi5QPI9Vr/ipOf4C1gN+ntueUk=
|
||||
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||
golang.org/x/crypto v0.0.0-20170728183002-558b6879de74 h1:/0jf0Cx3u07Ma4EzUA6NIGuvk9hb3Br6i9V8atthkwk=
|
||||
golang.org/x/crypto v0.0.0-20170728183002-558b6879de74/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/sys v0.0.0-20170529185110-b90f89a1e7a9 h1:wFe/9vW2TmDagagfMeC56pEcmhyMWEqvuwE9CDAePNo=
|
||||
golang.org/x/sys v0.0.0-20170529185110-b90f89a1e7a9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.0.0-20170530162606-4ee4af566555 h1:pjwO9HxObpgZBurDvTLFbSinaf3+cKpTAjVfiGaHwrI=
|
||||
golang.org/x/text v0.0.0-20170530162606-4ee4af566555/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
24
install
24
install
@@ -2,7 +2,7 @@
|
||||
|
||||
set -u
|
||||
|
||||
version=0.17.5
|
||||
version=0.18.0
|
||||
auto_completion=
|
||||
key_bindings=
|
||||
update_config=2
|
||||
@@ -91,17 +91,20 @@ ask() {
|
||||
check_binary() {
|
||||
echo -n " - Checking fzf executable ... "
|
||||
local output
|
||||
output=$("$fzf_base"/bin/fzf --version 2>&1 | awk '{print $1}')
|
||||
output=$("$fzf_base"/bin/fzf --version 2>&1)
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error: $output"
|
||||
binary_error="Invalid binary"
|
||||
elif [ "$version" != "$output" ]; then
|
||||
echo "$output != $version"
|
||||
binary_error="Invalid version"
|
||||
else
|
||||
echo "$output"
|
||||
binary_error=""
|
||||
return 0
|
||||
output=${output/ */}
|
||||
if [ "$version" != "$output" ]; then
|
||||
echo "$output != $version"
|
||||
binary_error="Invalid version"
|
||||
else
|
||||
echo "$output"
|
||||
binary_error=""
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
rm -f "$fzf_base"/bin/fzf
|
||||
return 1
|
||||
@@ -110,7 +113,7 @@ check_binary() {
|
||||
link_fzf_in_path() {
|
||||
if which_fzf="$(command -v fzf)"; then
|
||||
echo " - Found in \$PATH"
|
||||
echo " - Creating symlink: $which_fzf -> bin/fzf"
|
||||
echo " - Creating symlink: bin/fzf -> $which_fzf"
|
||||
(cd "$fzf_base"/bin && rm -f fzf && ln -sf "$which_fzf" fzf)
|
||||
check_binary && return
|
||||
fi
|
||||
@@ -269,7 +272,7 @@ for shell in $shells; do
|
||||
# Setup fzf
|
||||
# ---------
|
||||
if [[ ! "\$PATH" == *$fzf_base_esc/bin* ]]; then
|
||||
export PATH="\$PATH:$fzf_base/bin"
|
||||
export PATH="\${PATH:+\${PATH}:}$fzf_base/bin"
|
||||
fi
|
||||
|
||||
# Auto-completion
|
||||
@@ -279,7 +282,6 @@ $fzf_completion
|
||||
# Key bindings
|
||||
# ------------
|
||||
$fzf_key_bindings
|
||||
|
||||
EOF
|
||||
echo "OK"
|
||||
done
|
||||
|
||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf-tmux 1 "Oct 2018" "fzf 0.17.5" "fzf-tmux - open fzf in tmux split pane"
|
||||
.TH fzf-tmux 1 "Mar 2019" "fzf 0.18.0" "fzf-tmux - open fzf in tmux split pane"
|
||||
|
||||
.SH NAME
|
||||
fzf-tmux - open fzf in tmux split pane
|
||||
|
||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf 1 "Oct 2018" "fzf 0.17.5" "fzf - a command-line fuzzy finder"
|
||||
.TH fzf 1 "Mar 2019" "fzf 0.18.0" "fzf - a command-line fuzzy finder"
|
||||
|
||||
.SH NAME
|
||||
fzf - a command-line fuzzy finder
|
||||
@@ -174,6 +174,11 @@ A synonym for \fB--layout=reverse\fB
|
||||
.TP
|
||||
.B "--border"
|
||||
Draw border above and below the finder
|
||||
|
||||
.TP
|
||||
.B "--no-unicode"
|
||||
Use ASCII characters instead of Unicode box drawing characters to draw border
|
||||
|
||||
.TP
|
||||
.BI "--margin=" MARGIN
|
||||
Comma-separated expression for margins around the finder.
|
||||
@@ -250,6 +255,7 @@ e.g. \fBfzf --color=bg+:24\fR
|
||||
\fBhl \fRHighlighted substrings
|
||||
\fBfg+ \fRText (current line)
|
||||
\fBbg+ \fRBackground (current line)
|
||||
\fBgutter \fRGutter on the left (defaults to \fBbg+\fR)
|
||||
\fBhl+ \fRHighlighted substrings (current line)
|
||||
\fBinfo \fRInfo
|
||||
\fBborder \fRBorder of the preview window and horizontal separators (\fB--border\fR)
|
||||
@@ -288,8 +294,11 @@ EXPRESSION\fR for the details).
|
||||
e.g. \fBfzf --preview='head -$LINES {}'\fR
|
||||
\fBls -l | fzf --preview="echo user={3} when={-4..-2}; cat {-1}" --header-lines=1\fR
|
||||
|
||||
fzf overrides \fB$LINES\fR and \fB$COLUMNS\fR so that they represent the exact
|
||||
size of the preview window.
|
||||
fzf exports \fB$FZF_PREVIEW_LINES\fR and \fB$FZF_PREVIEW_COLUMNS\fR so that
|
||||
they represent the exact size of the preview window. (It also overrides
|
||||
\fB$LINES\fR and \fB$COLUMNS\fR with the same values but they can be reset
|
||||
by the default shell, so prefer to refer to the ones with \fBFZF_PREVIEW_\fR
|
||||
prefix.)
|
||||
|
||||
A placeholder expression starting with \fB+\fR flag will be replaced to the
|
||||
space-separated list of the selected lines (or the current line if no selection
|
||||
@@ -301,7 +310,9 @@ e.g. \fBfzf --multi --preview='head -10 {+}'\fR
|
||||
When using a field index expression, leading and trailing whitespace is stripped
|
||||
from the replacement string. To preserve the whitespace, use the \fBs\fR flag.
|
||||
|
||||
Also, \fB{q}\fR is replaced to the current query string.
|
||||
Also, \fB{q}\fR is replaced to the current query string, and \fB{n}\fR is
|
||||
replaced to zero-based ordinal index of the line. Use \fB{+n}\fR if you want
|
||||
all index numbers when multiple lines are selected.
|
||||
|
||||
Note that you can escape a placeholder pattern by prepending a backslash.
|
||||
|
||||
|
||||
@@ -457,7 +457,8 @@ function! s:pushd(dict)
|
||||
let cwd = s:fzf_getcwd()
|
||||
let w:fzf_pushd = {
|
||||
\ 'command': haslocaldir() ? 'lcd' : (exists(':tcd') && haslocaldir(-1) ? 'tcd' : 'cd'),
|
||||
\ 'origin': cwd
|
||||
\ 'origin': cwd,
|
||||
\ 'bufname': bufname('')
|
||||
\ }
|
||||
execute 'lcd' s:escape(a:dict.dir)
|
||||
let cwd = s:fzf_getcwd()
|
||||
@@ -493,7 +494,7 @@ function! s:dopopd()
|
||||
" 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
|
||||
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
|
||||
@@ -606,8 +607,9 @@ function! s:calc_size(max, val, dict)
|
||||
let srcsz = len(a:dict.source)
|
||||
endif
|
||||
|
||||
let opts = s:evaluate_opts(get(a:dict, 'options', '')).$FZF_DEFAULT_OPTS
|
||||
let opts = $FZF_DEFAULT_OPTS.' '.s:evaluate_opts(get(a:dict, 'options', ''))
|
||||
let margin = stridx(opts, '--inline-info') > stridx(opts, '--no-inline-info') ? 1 : 2
|
||||
let margin += stridx(opts, '--border') > stridx(opts, '--no-border') ? 2 : 0
|
||||
let margin += stridx(opts, '--header') > stridx(opts, '--no-header')
|
||||
return srcsz >= 0 ? min([srcsz + margin, size]) : size
|
||||
endfunction
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||
|
||||
if [[ $- =~ i ]]; then
|
||||
|
||||
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
||||
if ! declare -f _fzf_compgen_path > /dev/null; then
|
||||
_fzf_compgen_path() {
|
||||
@@ -241,7 +243,7 @@ _fzf_complete_telnet() {
|
||||
|
||||
_fzf_complete_ssh() {
|
||||
_fzf_complete '+m' "$@" < <(
|
||||
cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host ' | command grep -v '[*?]' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}') \
|
||||
cat <(cat ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host ' | command grep -v '[*?]' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}') \
|
||||
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
||||
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||
@@ -330,3 +332,5 @@ complete -F _fzf_complete_export -o default -o bashdefault export
|
||||
complete -F _fzf_complete_unalias -o default -o bashdefault unalias
|
||||
|
||||
unset cmd d_cmds a_cmds x_cmds
|
||||
|
||||
fi
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||
|
||||
if [[ $- =~ i ]]; then
|
||||
|
||||
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
||||
if ! declare -f _fzf_compgen_path > /dev/null; then
|
||||
_fzf_compgen_path() {
|
||||
@@ -112,7 +114,8 @@ _fzf_complete_telnet() {
|
||||
|
||||
_fzf_complete_ssh() {
|
||||
_fzf_complete '+m' "$@" < <(
|
||||
command cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host ' | command grep -v '[*?]' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}') \
|
||||
setopt localoptions nonomatch
|
||||
command cat <(cat ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host ' | command grep -v '[*?]' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}') \
|
||||
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
||||
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||
@@ -192,3 +195,5 @@ fzf-completion() {
|
||||
|
||||
zle -N fzf-completion
|
||||
bindkey '^I' fzf-completion
|
||||
|
||||
fi
|
||||
|
||||
49
src/ansi.go
49
src/ansi.go
@@ -32,6 +32,55 @@ func (s *ansiState) equals(t *ansiState) bool {
|
||||
return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr
|
||||
}
|
||||
|
||||
func (s *ansiState) ToString() string {
|
||||
if !s.colored() {
|
||||
return ""
|
||||
}
|
||||
|
||||
ret := ""
|
||||
if s.attr&tui.Bold > 0 {
|
||||
ret += "1;"
|
||||
}
|
||||
if s.attr&tui.Dim > 0 {
|
||||
ret += "2;"
|
||||
}
|
||||
if s.attr&tui.Italic > 0 {
|
||||
ret += "3;"
|
||||
}
|
||||
if s.attr&tui.Underline > 0 {
|
||||
ret += "4;"
|
||||
}
|
||||
if s.attr&tui.Blink > 0 {
|
||||
ret += "5;"
|
||||
}
|
||||
if s.attr&tui.Reverse > 0 {
|
||||
ret += "7;"
|
||||
}
|
||||
ret += toAnsiString(s.fg, 30) + toAnsiString(s.bg, 40)
|
||||
|
||||
return "\x1b[" + strings.TrimSuffix(ret, ";") + "m"
|
||||
}
|
||||
|
||||
func toAnsiString(color tui.Color, offset int) string {
|
||||
col := int(color)
|
||||
ret := ""
|
||||
if col == -1 {
|
||||
ret += strconv.Itoa(offset + 9)
|
||||
} else if col < 8 {
|
||||
ret += strconv.Itoa(offset + col)
|
||||
} else if col < 16 {
|
||||
ret += strconv.Itoa(offset - 30 + 90 + col - 8)
|
||||
} else if col < 256 {
|
||||
ret += strconv.Itoa(offset+8) + ";5;" + strconv.Itoa(col)
|
||||
} else if col >= (1 << 24) {
|
||||
r := strconv.Itoa((col >> 16) & 0xff)
|
||||
g := strconv.Itoa((col >> 8) & 0xff)
|
||||
b := strconv.Itoa(col & 0xff)
|
||||
ret += strconv.Itoa(offset+8) + ";2;" + r + ";" + g + ";" + b
|
||||
}
|
||||
return ret + ";"
|
||||
}
|
||||
|
||||
var ansiRegex *regexp.Regexp
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -2,6 +2,7 @@ package fzf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/junegunn/fzf/src/tui"
|
||||
@@ -156,3 +157,31 @@ func TestExtractColor(t *testing.T) {
|
||||
assert((*offsets)[1], 6, 11, 200, 100, false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAnsiCodeStringConversion(t *testing.T) {
|
||||
assert := func(code string, prevState *ansiState, expected string) {
|
||||
state := interpretCode(code, prevState)
|
||||
if expected != state.ToString() {
|
||||
t.Errorf("expected: %s, actual: %s",
|
||||
strings.Replace(expected, "\x1b[", "\\x1b[", -1),
|
||||
strings.Replace(state.ToString(), "\x1b[", "\\x1b[", -1))
|
||||
}
|
||||
}
|
||||
assert("\x1b[m", nil, "")
|
||||
assert("\x1b[m", &ansiState{attr: tui.Blink}, "")
|
||||
|
||||
assert("\x1b[31m", nil, "\x1b[31;49m")
|
||||
assert("\x1b[41m", nil, "\x1b[39;41m")
|
||||
|
||||
assert("\x1b[92m", nil, "\x1b[92;49m")
|
||||
assert("\x1b[102m", nil, "\x1b[39;102m")
|
||||
|
||||
assert("\x1b[31m", &ansiState{fg: 4, bg: 4}, "\x1b[31;44m")
|
||||
assert("\x1b[1;2;31m", &ansiState{fg: 2, bg: -1, attr: tui.Reverse}, "\x1b[1;2;7;31;49m")
|
||||
assert("\x1b[38;5;100;48;5;200m", nil, "\x1b[38;5;100;48;5;200m")
|
||||
assert("\x1b[48;5;100;38;5;200m", nil, "\x1b[38;5;200;48;5;100m")
|
||||
assert("\x1b[48;5;100;38;2;10;20;30;1m", nil, "\x1b[1;38;2;10;20;30;48;5;100m")
|
||||
assert("\x1b[48;5;100;38;2;10;20;30;7m",
|
||||
&ansiState{attr: tui.Dim | tui.Italic, fg: 1, bg: 1},
|
||||
"\x1b[2;3;7;38;2;10;20;30;48;5;100m")
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
const (
|
||||
// Current version
|
||||
version = "0.17.5"
|
||||
version = "0.18.0"
|
||||
|
||||
// Core
|
||||
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
||||
|
||||
25
src/core.go
25
src/core.go
@@ -63,12 +63,14 @@ func Run(opts *Options, revision string) {
|
||||
ansiProcessor := func(data []byte) (util.Chars, *[]ansiOffset) {
|
||||
return util.ToChars(data), nil
|
||||
}
|
||||
|
||||
var lineAnsiState, prevLineAnsiState *ansiState
|
||||
if opts.Ansi {
|
||||
if opts.Theme != nil {
|
||||
var state *ansiState
|
||||
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
||||
trimmed, offsets, newState := extractColor(string(data), state, nil)
|
||||
state = newState
|
||||
prevLineAnsiState = lineAnsiState
|
||||
trimmed, offsets, newState := extractColor(string(data), lineAnsiState, nil)
|
||||
lineAnsiState = newState
|
||||
return util.ToChars([]byte(trimmed)), offsets
|
||||
}
|
||||
} else {
|
||||
@@ -100,6 +102,22 @@ func Run(opts *Options, revision string) {
|
||||
} else {
|
||||
chunkList = NewChunkList(func(item *Item, data []byte) bool {
|
||||
tokens := Tokenize(string(data), opts.Delimiter)
|
||||
if opts.Ansi && opts.Theme != nil && len(tokens) > 1 {
|
||||
var ansiState *ansiState
|
||||
if prevLineAnsiState != nil {
|
||||
ansiStateDup := *prevLineAnsiState
|
||||
ansiState = &ansiStateDup
|
||||
}
|
||||
for _, token := range tokens {
|
||||
prevAnsiState := ansiState
|
||||
_, _, ansiState = extractColor(token.text.ToString(), ansiState, nil)
|
||||
if prevAnsiState != nil {
|
||||
token.text.Prepend("\x1b[m" + prevAnsiState.ToString())
|
||||
} else {
|
||||
token.text.Prepend("\x1b[m")
|
||||
}
|
||||
}
|
||||
}
|
||||
trans := Transform(tokens, opts.WithNth)
|
||||
transformed := joinTokens(trans)
|
||||
if len(header) < opts.HeaderLines {
|
||||
@@ -149,6 +167,7 @@ func Run(opts *Options, revision string) {
|
||||
}
|
||||
|
||||
pattern := patternBuilder([]rune(*opts.Filter))
|
||||
matcher.sort = pattern.sortable
|
||||
|
||||
found := false
|
||||
if streamingFilter {
|
||||
|
||||
@@ -230,5 +230,5 @@ func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final
|
||||
} else {
|
||||
event = reqRetry
|
||||
}
|
||||
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort})
|
||||
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable})
|
||||
}
|
||||
|
||||
@@ -195,6 +195,7 @@ type Options struct {
|
||||
HeaderLines int
|
||||
Margin [4]sizeSpec
|
||||
Bordered bool
|
||||
Unicode bool
|
||||
Tabstop int
|
||||
ClearOnExit bool
|
||||
Version bool
|
||||
@@ -244,6 +245,7 @@ func defaultOptions() *Options {
|
||||
Header: make([]string, 0),
|
||||
HeaderLines: 0,
|
||||
Margin: defaultMargin(),
|
||||
Unicode: true,
|
||||
Tabstop: 8,
|
||||
ClearOnExit: true,
|
||||
Version: false}
|
||||
@@ -576,6 +578,8 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
|
||||
theme.Current = ansi
|
||||
case "bg+":
|
||||
theme.DarkBg = ansi
|
||||
case "gutter":
|
||||
theme.Gutter = ansi
|
||||
case "hl":
|
||||
theme.Match = ansi
|
||||
case "hl+":
|
||||
@@ -1150,6 +1154,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
opts.Bordered = false
|
||||
case "--border":
|
||||
opts.Bordered = true
|
||||
case "--no-unicode":
|
||||
opts.Unicode = false
|
||||
case "--unicode":
|
||||
opts.Unicode = true
|
||||
case "--margin":
|
||||
opts.Margin = parseMargin(
|
||||
nextString(allArgs, &i, "margin required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
|
||||
|
||||
@@ -52,6 +52,7 @@ type Pattern struct {
|
||||
forward bool
|
||||
text []rune
|
||||
termSets []termSet
|
||||
sortable bool
|
||||
cacheable bool
|
||||
cacheKey string
|
||||
delimiter Delimiter
|
||||
@@ -101,18 +102,27 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
||||
}
|
||||
|
||||
caseSensitive := true
|
||||
sortable := true
|
||||
termSets := []termSet{}
|
||||
|
||||
if extended {
|
||||
termSets = parseTerms(fuzzy, caseMode, normalize, asString)
|
||||
// We should not sort the result if there are only inverse search terms
|
||||
sortable = false
|
||||
Loop:
|
||||
for _, termSet := range termSets {
|
||||
for idx, term := range termSet {
|
||||
if !term.inv {
|
||||
sortable = true
|
||||
}
|
||||
// If the query contains inverse search terms or OR operators,
|
||||
// we cannot cache the search scope
|
||||
if !cacheable || idx > 0 || term.inv || fuzzy && term.typ != termFuzzy || !fuzzy && term.typ != termExact {
|
||||
cacheable = false
|
||||
break Loop
|
||||
if sortable {
|
||||
// Can't break until we see at least one non-inverse term
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,6 +144,7 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
||||
forward: forward,
|
||||
text: []rune(asString),
|
||||
termSets: termSets,
|
||||
sortable: sortable,
|
||||
cacheable: cacheable,
|
||||
nth: nth,
|
||||
delimiter: delimiter,
|
||||
|
||||
119
src/terminal.go
119
src/terminal.go
@@ -24,7 +24,7 @@ import (
|
||||
var placeholder *regexp.Regexp
|
||||
|
||||
func init() {
|
||||
placeholder = regexp.MustCompile("\\\\?(?:{[+s]*[0-9,-.]*}|{q})")
|
||||
placeholder = regexp.MustCompile("\\\\?(?:{[+s]*[0-9,-.]*}|{q}|{\\+?n})")
|
||||
}
|
||||
|
||||
type jumpMode int
|
||||
@@ -40,6 +40,7 @@ type previewer struct {
|
||||
lines int
|
||||
offset int
|
||||
enabled bool
|
||||
more bool
|
||||
}
|
||||
|
||||
type itemLine struct {
|
||||
@@ -88,6 +89,7 @@ type Terminal struct {
|
||||
tabstop int
|
||||
margin [4]sizeSpec
|
||||
strong tui.Attr
|
||||
unicode bool
|
||||
bordered bool
|
||||
cleanExit bool
|
||||
border tui.Window
|
||||
@@ -227,6 +229,7 @@ const (
|
||||
type placeholderFlags struct {
|
||||
plus bool
|
||||
preserveSpace bool
|
||||
number bool
|
||||
query bool
|
||||
}
|
||||
|
||||
@@ -390,6 +393,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
printQuery: opts.PrintQuery,
|
||||
history: opts.History,
|
||||
margin: opts.Margin,
|
||||
unicode: opts.Unicode,
|
||||
bordered: opts.Bordered,
|
||||
cleanExit: opts.ClearOnExit,
|
||||
strong: strongAttr,
|
||||
@@ -407,7 +411,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
selected: make(map[int32]selectedItem),
|
||||
reqBox: util.NewEventBox(),
|
||||
preview: opts.Preview,
|
||||
previewer: previewer{"", 0, 0, previewBox != nil && !opts.Preview.hidden},
|
||||
previewer: previewer{"", 0, 0, previewBox != nil && !opts.Preview.hidden, false},
|
||||
previewBox: previewBox,
|
||||
eventBox: eventBox,
|
||||
mutex: sync.Mutex{},
|
||||
@@ -599,11 +603,12 @@ func (t *Terminal) resizeWindows() {
|
||||
marginInt[0]-1,
|
||||
marginInt[3],
|
||||
width,
|
||||
height+2, tui.BorderHorizontal)
|
||||
height+2, tui.MakeBorderStyle(tui.BorderHorizontal, t.unicode))
|
||||
}
|
||||
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
|
||||
if previewVisible {
|
||||
createPreviewWindow := func(y int, x int, w int, h int) {
|
||||
t.pborder = t.tui.NewWindow(y, x, w, h, tui.BorderAround)
|
||||
t.pborder = t.tui.NewWindow(y, x, w, h, tui.MakeBorderStyle(tui.BorderAround, t.unicode))
|
||||
pwidth := w - 4
|
||||
// ncurses auto-wraps the line when the cursor reaches the right-end of
|
||||
// the window. To prevent unintended line-wraps, we use the width one
|
||||
@@ -611,29 +616,28 @@ func (t *Terminal) resizeWindows() {
|
||||
if !t.preview.wrap && t.tui.DoesAutoWrap() {
|
||||
pwidth += 1
|
||||
}
|
||||
t.pwindow = t.tui.NewWindow(y+1, x+2, pwidth, h-2, tui.BorderNone)
|
||||
os.Setenv("FZF_PREVIEW_HEIGHT", strconv.Itoa(h-2))
|
||||
t.pwindow = t.tui.NewWindow(y+1, x+2, pwidth, h-2, noBorder)
|
||||
}
|
||||
switch t.preview.position {
|
||||
case posUp:
|
||||
pheight := calculateSize(height, t.preview.size, minHeight, 3)
|
||||
t.window = t.tui.NewWindow(
|
||||
marginInt[0]+pheight, marginInt[3], width, height-pheight, tui.BorderNone)
|
||||
marginInt[0]+pheight, marginInt[3], width, height-pheight, noBorder)
|
||||
createPreviewWindow(marginInt[0], marginInt[3], width, pheight)
|
||||
case posDown:
|
||||
pheight := calculateSize(height, t.preview.size, minHeight, 3)
|
||||
t.window = t.tui.NewWindow(
|
||||
marginInt[0], marginInt[3], width, height-pheight, tui.BorderNone)
|
||||
marginInt[0], marginInt[3], width, height-pheight, noBorder)
|
||||
createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
|
||||
case posLeft:
|
||||
pwidth := calculateSize(width, t.preview.size, minWidth, 5)
|
||||
t.window = t.tui.NewWindow(
|
||||
marginInt[0], marginInt[3]+pwidth, width-pwidth, height, tui.BorderNone)
|
||||
marginInt[0], marginInt[3]+pwidth, width-pwidth, height, noBorder)
|
||||
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
|
||||
case posRight:
|
||||
pwidth := calculateSize(width, t.preview.size, minWidth, 5)
|
||||
t.window = t.tui.NewWindow(
|
||||
marginInt[0], marginInt[3], width-pwidth, height, tui.BorderNone)
|
||||
marginInt[0], marginInt[3], width-pwidth, height, noBorder)
|
||||
createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height)
|
||||
}
|
||||
} else {
|
||||
@@ -641,7 +645,7 @@ func (t *Terminal) resizeWindows() {
|
||||
marginInt[0],
|
||||
marginInt[3],
|
||||
width,
|
||||
height, tui.BorderNone)
|
||||
height, noBorder)
|
||||
}
|
||||
for i := 0; i < t.window.Height(); i++ {
|
||||
t.window.MoveAndClear(i, 0)
|
||||
@@ -834,15 +838,16 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool) {
|
||||
}
|
||||
|
||||
t.move(line, 0, false)
|
||||
t.window.CPrint(tui.ColCursor, t.strong, label)
|
||||
if current {
|
||||
t.window.CPrint(tui.ColCurrentCursor, t.strong, label)
|
||||
if selected {
|
||||
t.window.CPrint(tui.ColSelected, t.strong, ">")
|
||||
t.window.CPrint(tui.ColCurrentSelected, t.strong, ">")
|
||||
} else {
|
||||
t.window.CPrint(tui.ColCurrent, t.strong, " ")
|
||||
t.window.CPrint(tui.ColCurrentSelected, t.strong, " ")
|
||||
}
|
||||
newLine.width = t.printHighlighted(result, t.strong, tui.ColCurrent, tui.ColCurrentMatch, true, true)
|
||||
} else {
|
||||
t.window.CPrint(tui.ColCursor, t.strong, label)
|
||||
if selected {
|
||||
t.window.CPrint(tui.ColSelected, t.strong, ">")
|
||||
} else {
|
||||
@@ -1023,25 +1028,26 @@ func (t *Terminal) printPreview() {
|
||||
reader := bufio.NewReader(strings.NewReader(t.previewer.text))
|
||||
lineNo := -t.previewer.offset
|
||||
height := t.pwindow.Height()
|
||||
t.previewer.more = t.previewer.offset > 0
|
||||
var ansi *ansiState
|
||||
for {
|
||||
for ; ; lineNo++ {
|
||||
line, err := reader.ReadString('\n')
|
||||
eof := err == io.EOF
|
||||
if !eof {
|
||||
line = line[:len(line)-1]
|
||||
}
|
||||
lineNo++
|
||||
if lineNo > height ||
|
||||
t.pwindow.Y() == height-1 && t.pwindow.X() > 0 {
|
||||
if lineNo >= height || t.pwindow.Y() == height-1 && t.pwindow.X() > 0 {
|
||||
break
|
||||
} else if lineNo > 0 {
|
||||
} else if lineNo >= 0 {
|
||||
var fillRet tui.FillReturn
|
||||
prefixWidth := 0
|
||||
_, _, ansi = extractColor(line, ansi, func(str string, ansi *ansiState) bool {
|
||||
trimmed := []rune(str)
|
||||
if !t.preview.wrap {
|
||||
trimmed, _ = t.trimRight(trimmed, maxWidth-t.pwindow.X())
|
||||
}
|
||||
str, _ = t.processTabs(trimmed, 0)
|
||||
str, width := t.processTabs(trimmed, prefixWidth)
|
||||
prefixWidth += width
|
||||
if t.theme != nil && ansi != nil && ansi.colored() {
|
||||
fillRet = t.pwindow.CFill(ansi.fg, ansi.bg, ansi.attr, str)
|
||||
} else {
|
||||
@@ -1049,10 +1055,10 @@ func (t *Terminal) printPreview() {
|
||||
}
|
||||
return fillRet == tui.FillContinue
|
||||
})
|
||||
switch fillRet {
|
||||
case tui.FillNextLine:
|
||||
t.previewer.more = t.previewer.more || t.pwindow.Y() == height-1 && t.pwindow.X() > 0
|
||||
if fillRet == tui.FillNextLine {
|
||||
continue
|
||||
case tui.FillSuspend:
|
||||
} else if fillRet == tui.FillSuspend {
|
||||
break
|
||||
}
|
||||
t.pwindow.Fill("\n")
|
||||
@@ -1063,6 +1069,7 @@ func (t *Terminal) printPreview() {
|
||||
}
|
||||
t.pwindow.FinishFill()
|
||||
if t.previewer.lines > height {
|
||||
t.previewer.more = true
|
||||
offset := fmt.Sprintf("%d/%d", t.previewer.offset+1, t.previewer.lines)
|
||||
pos := t.pwindow.Width() - len(offset)
|
||||
if t.tui.DoesAutoWrap() {
|
||||
@@ -1098,6 +1105,7 @@ func (t *Terminal) printAll() {
|
||||
}
|
||||
|
||||
func (t *Terminal) refresh() {
|
||||
t.placeCursor()
|
||||
if !t.suppress {
|
||||
windows := make([]tui.Window, 0, 4)
|
||||
if t.bordered {
|
||||
@@ -1196,6 +1204,9 @@ func parsePlaceholder(match string) (bool, string, placeholderFlags) {
|
||||
case 's':
|
||||
flags.preserveSpace = true
|
||||
skipChars++
|
||||
case 'n':
|
||||
flags.number = true
|
||||
skipChars++
|
||||
case 'q':
|
||||
flags.query = true
|
||||
default:
|
||||
@@ -1251,7 +1262,16 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, fo
|
||||
|
||||
if match == "{}" {
|
||||
for idx, item := range items {
|
||||
replacements[idx] = quoteEntry(item.AsString(stripAnsi))
|
||||
if flags.number {
|
||||
n := int(item.text.Index)
|
||||
if n < 0 {
|
||||
replacements[idx] = ""
|
||||
} else {
|
||||
replacements[idx] = strconv.Itoa(n)
|
||||
}
|
||||
} else {
|
||||
replacements[idx] = quoteEntry(item.AsString(stripAnsi))
|
||||
}
|
||||
}
|
||||
return strings.Join(replacements, " ")
|
||||
}
|
||||
@@ -1349,7 +1369,7 @@ func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item
|
||||
// 2. or it contains {+} and we have more than one item already selected.
|
||||
// To do so, we pass an empty Item instead of nil to trigger an update.
|
||||
if current == nil {
|
||||
current = &Item{}
|
||||
current = &minItem
|
||||
}
|
||||
|
||||
var sels []*Item
|
||||
@@ -1433,7 +1453,6 @@ func (t *Terminal) Loop() {
|
||||
t.printPrompt()
|
||||
t.printInfo()
|
||||
t.printHeader()
|
||||
t.placeCursor()
|
||||
t.refresh()
|
||||
t.mutex.Unlock()
|
||||
go func() {
|
||||
@@ -1477,8 +1496,12 @@ func (t *Terminal) Loop() {
|
||||
cmd := util.ExecCommand(command, true)
|
||||
if t.pwindow != nil {
|
||||
env := os.Environ()
|
||||
env = append(env, fmt.Sprintf("LINES=%d", t.pwindow.Height()))
|
||||
env = append(env, fmt.Sprintf("COLUMNS=%d", t.pwindow.Width()))
|
||||
lines := fmt.Sprintf("LINES=%d", t.pwindow.Height())
|
||||
columns := fmt.Sprintf("COLUMNS=%d", t.pwindow.Width())
|
||||
env = append(env, lines)
|
||||
env = append(env, "FZF_PREVIEW_"+lines)
|
||||
env = append(env, columns)
|
||||
env = append(env, "FZF_PREVIEW_"+columns)
|
||||
cmd.Env = env
|
||||
}
|
||||
var out bytes.Buffer
|
||||
@@ -1532,8 +1555,8 @@ func (t *Terminal) Loop() {
|
||||
}
|
||||
|
||||
go func() {
|
||||
var focused *Item
|
||||
var version int64
|
||||
var focusedIndex int32 = minItem.Index()
|
||||
var version int64 = -1
|
||||
for {
|
||||
t.reqBox.Wait(func(events *util.Events) {
|
||||
defer events.Clear()
|
||||
@@ -1549,10 +1572,14 @@ func (t *Terminal) Loop() {
|
||||
t.printInfo()
|
||||
case reqList:
|
||||
t.printList()
|
||||
currentFocus := t.currentItem()
|
||||
if currentFocus != focused || version != t.version {
|
||||
var currentIndex int32 = minItem.Index()
|
||||
currentItem := t.currentItem()
|
||||
if currentItem != nil {
|
||||
currentIndex = currentItem.Index()
|
||||
}
|
||||
if focusedIndex != currentIndex || version != t.version {
|
||||
version = t.version
|
||||
focused = currentFocus
|
||||
focusedIndex = currentIndex
|
||||
if t.isPreviewEnabled() {
|
||||
_, list := t.buildPlusList(t.preview.command, false)
|
||||
t.cancelPreview()
|
||||
@@ -1596,7 +1623,6 @@ func (t *Terminal) Loop() {
|
||||
exit(func() int { return exitInterrupt })
|
||||
}
|
||||
}
|
||||
t.placeCursor()
|
||||
t.refresh()
|
||||
t.mutex.Unlock()
|
||||
})
|
||||
@@ -1609,7 +1635,8 @@ func (t *Terminal) Loop() {
|
||||
|
||||
t.mutex.Lock()
|
||||
previousInput := t.input
|
||||
events := []util.EventType{reqPrompt}
|
||||
previousCx := t.cx
|
||||
events := []util.EventType{}
|
||||
req := func(evts ...util.EventType) {
|
||||
for _, event := range evts {
|
||||
events = append(events, event)
|
||||
@@ -1625,9 +1652,15 @@ func (t *Terminal) Loop() {
|
||||
}
|
||||
}
|
||||
scrollPreview := func(amount int) {
|
||||
t.previewer.offset = util.Constrain(
|
||||
if !t.previewer.more {
|
||||
return
|
||||
}
|
||||
newOffset := util.Constrain(
|
||||
t.previewer.offset+amount, 0, t.previewer.lines-1)
|
||||
req(reqPreviewRefresh)
|
||||
if t.previewer.offset != newOffset {
|
||||
t.previewer.offset = newOffset
|
||||
req(reqPreviewRefresh)
|
||||
}
|
||||
}
|
||||
for key, ret := range t.expect {
|
||||
if keyMatch(key, event) {
|
||||
@@ -1669,7 +1702,7 @@ func (t *Terminal) Loop() {
|
||||
t.previewBox.Set(reqPreviewEnqueue, list)
|
||||
}
|
||||
}
|
||||
req(reqList, reqInfo, reqHeader)
|
||||
req(reqPrompt, reqList, reqInfo, reqHeader)
|
||||
}
|
||||
case actTogglePreviewWrap:
|
||||
if t.hasPreviewWindow() {
|
||||
@@ -1970,7 +2003,6 @@ func (t *Terminal) Loop() {
|
||||
t.jumping = jumpDisabled
|
||||
req(reqList)
|
||||
}
|
||||
t.mutex.Unlock() // Must be unlocked before touching reqBox
|
||||
|
||||
if changed {
|
||||
if t.isPreviewEnabled() {
|
||||
@@ -1979,6 +2011,15 @@ func (t *Terminal) Loop() {
|
||||
t.version++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if changed || t.cx != previousCx {
|
||||
req(reqPrompt)
|
||||
}
|
||||
|
||||
t.mutex.Unlock() // Must be unlocked before touching reqBox
|
||||
|
||||
if changed {
|
||||
t.eventBox.Set(EvtSearchNew, t.sort)
|
||||
}
|
||||
for _, event := range events {
|
||||
|
||||
@@ -163,9 +163,9 @@ func (r *LightRenderer) findOffset() (row int, col int) {
|
||||
return -1, -1
|
||||
}
|
||||
|
||||
func repeat(s string, times int) string {
|
||||
func repeat(r rune, times int) string {
|
||||
if times > 0 {
|
||||
return strings.Repeat(s, times)
|
||||
return strings.Repeat(string(r), times)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -676,7 +676,7 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, bord
|
||||
}
|
||||
|
||||
func (w *LightWindow) drawBorder() {
|
||||
switch w.border {
|
||||
switch w.border.shape {
|
||||
case BorderAround:
|
||||
w.drawBorderAround()
|
||||
case BorderHorizontal:
|
||||
@@ -686,22 +686,24 @@ func (w *LightWindow) drawBorder() {
|
||||
|
||||
func (w *LightWindow) drawBorderHorizontal() {
|
||||
w.Move(0, 0)
|
||||
w.CPrint(ColBorder, AttrRegular, repeat("─", w.width))
|
||||
w.CPrint(ColBorder, AttrRegular, repeat(w.border.horizontal, w.width))
|
||||
w.Move(w.height-1, 0)
|
||||
w.CPrint(ColBorder, AttrRegular, repeat("─", w.width))
|
||||
w.CPrint(ColBorder, AttrRegular, repeat(w.border.horizontal, w.width))
|
||||
}
|
||||
|
||||
func (w *LightWindow) drawBorderAround() {
|
||||
w.Move(0, 0)
|
||||
w.CPrint(ColBorder, AttrRegular, "┌"+repeat("─", w.width-2)+"┐")
|
||||
w.CPrint(ColBorder, AttrRegular,
|
||||
string(w.border.topLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.topRight))
|
||||
for y := 1; y < w.height-1; y++ {
|
||||
w.Move(y, 0)
|
||||
w.CPrint(ColBorder, AttrRegular, "│")
|
||||
w.cprint2(colDefault, w.bg, AttrRegular, repeat(" ", w.width-2))
|
||||
w.CPrint(ColBorder, AttrRegular, "│")
|
||||
w.CPrint(ColBorder, AttrRegular, string(w.border.vertical))
|
||||
w.cprint2(colDefault, w.bg, AttrRegular, repeat(' ', w.width-2))
|
||||
w.CPrint(ColBorder, AttrRegular, string(w.border.vertical))
|
||||
}
|
||||
w.Move(w.height-1, 0)
|
||||
w.CPrint(ColBorder, AttrRegular, "└"+repeat("─", w.width-2)+"┘")
|
||||
w.CPrint(ColBorder, AttrRegular,
|
||||
string(w.border.bottomLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.bottomRight))
|
||||
}
|
||||
|
||||
func (w *LightWindow) csi(code string) {
|
||||
@@ -762,7 +764,7 @@ func (w *LightWindow) MoveAndClear(y int, x int) {
|
||||
w.Move(y, x)
|
||||
// We should not delete preview window on the right
|
||||
// csi("K")
|
||||
w.Print(repeat(" ", w.width-x))
|
||||
w.Print(repeat(' ', w.width-x))
|
||||
w.Move(y, x)
|
||||
}
|
||||
|
||||
@@ -858,7 +860,7 @@ func wrapLine(input string, prefixLength int, max int, tabstop int) []wrappedLin
|
||||
width += w
|
||||
str := string(r)
|
||||
if r == '\t' {
|
||||
str = repeat(" ", w)
|
||||
str = repeat(' ', w)
|
||||
}
|
||||
if prefixLength+width <= max {
|
||||
line += str
|
||||
|
||||
@@ -61,12 +61,8 @@ func (w *TcellWindow) Refresh() {
|
||||
}
|
||||
w.lastX = 0
|
||||
w.lastY = 0
|
||||
switch w.borderStyle {
|
||||
case BorderAround:
|
||||
w.drawBorder(true)
|
||||
case BorderHorizontal:
|
||||
w.drawBorder(false)
|
||||
}
|
||||
|
||||
w.drawBorder()
|
||||
}
|
||||
|
||||
func (w *TcellWindow) FinishFill() {
|
||||
@@ -570,7 +566,11 @@ func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
|
||||
return w.fillString(str, NewColorPair(fg, bg), a)
|
||||
}
|
||||
|
||||
func (w *TcellWindow) drawBorder(around bool) {
|
||||
func (w *TcellWindow) drawBorder() {
|
||||
if w.borderStyle.shape == BorderNone {
|
||||
return
|
||||
}
|
||||
|
||||
left := w.left
|
||||
right := left + w.width
|
||||
top := w.top
|
||||
@@ -584,19 +584,19 @@ func (w *TcellWindow) drawBorder(around bool) {
|
||||
}
|
||||
|
||||
for x := left; x < right; x++ {
|
||||
_screen.SetContent(x, top, tcell.RuneHLine, nil, style)
|
||||
_screen.SetContent(x, bot-1, tcell.RuneHLine, nil, style)
|
||||
_screen.SetContent(x, top, w.borderStyle.horizontal, nil, style)
|
||||
_screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style)
|
||||
}
|
||||
|
||||
if around {
|
||||
if w.borderStyle.shape == BorderAround {
|
||||
for y := top; y < bot; y++ {
|
||||
_screen.SetContent(left, y, tcell.RuneVLine, nil, style)
|
||||
_screen.SetContent(right-1, y, tcell.RuneVLine, nil, style)
|
||||
_screen.SetContent(left, y, w.borderStyle.vertical, nil, style)
|
||||
_screen.SetContent(right-1, y, w.borderStyle.vertical, nil, style)
|
||||
}
|
||||
|
||||
_screen.SetContent(left, top, tcell.RuneULCorner, nil, style)
|
||||
_screen.SetContent(right-1, top, tcell.RuneURCorner, nil, style)
|
||||
_screen.SetContent(left, bot-1, tcell.RuneLLCorner, nil, style)
|
||||
_screen.SetContent(right-1, bot-1, tcell.RuneLRCorner, nil, style)
|
||||
_screen.SetContent(left, top, w.borderStyle.topLeft, nil, style)
|
||||
_screen.SetContent(right-1, top, w.borderStyle.topRight, nil, style)
|
||||
_screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style)
|
||||
_screen.SetContent(right-1, bot-1, w.borderStyle.bottomRight, nil, style)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,6 +172,7 @@ type ColorTheme struct {
|
||||
Fg Color
|
||||
Bg Color
|
||||
DarkBg Color
|
||||
Gutter Color
|
||||
Prompt Color
|
||||
Match Color
|
||||
Current Color
|
||||
@@ -200,14 +201,47 @@ type MouseEvent struct {
|
||||
Mod bool
|
||||
}
|
||||
|
||||
type BorderStyle int
|
||||
type BorderShape int
|
||||
|
||||
const (
|
||||
BorderNone BorderStyle = iota
|
||||
BorderNone BorderShape = iota
|
||||
BorderAround
|
||||
BorderHorizontal
|
||||
)
|
||||
|
||||
type BorderStyle struct {
|
||||
shape BorderShape
|
||||
horizontal rune
|
||||
vertical rune
|
||||
topLeft rune
|
||||
topRight rune
|
||||
bottomLeft rune
|
||||
bottomRight rune
|
||||
}
|
||||
|
||||
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
|
||||
if unicode {
|
||||
return BorderStyle{
|
||||
shape: shape,
|
||||
horizontal: '─',
|
||||
vertical: '│',
|
||||
topLeft: '┌',
|
||||
topRight: '┐',
|
||||
bottomLeft: '└',
|
||||
bottomRight: '┘',
|
||||
}
|
||||
}
|
||||
return BorderStyle{
|
||||
shape: shape,
|
||||
horizontal: '-',
|
||||
vertical: '|',
|
||||
topLeft: '+',
|
||||
topRight: '+',
|
||||
bottomLeft: '+',
|
||||
bottomRight: '+',
|
||||
}
|
||||
}
|
||||
|
||||
type Renderer interface {
|
||||
Init()
|
||||
Pause(clear bool)
|
||||
@@ -272,17 +306,19 @@ var (
|
||||
Dark256 *ColorTheme
|
||||
Light256 *ColorTheme
|
||||
|
||||
ColNormal ColorPair
|
||||
ColPrompt ColorPair
|
||||
ColMatch ColorPair
|
||||
ColCurrent ColorPair
|
||||
ColCurrentMatch ColorPair
|
||||
ColSpinner ColorPair
|
||||
ColInfo ColorPair
|
||||
ColCursor ColorPair
|
||||
ColSelected ColorPair
|
||||
ColHeader ColorPair
|
||||
ColBorder ColorPair
|
||||
ColPrompt ColorPair
|
||||
ColNormal ColorPair
|
||||
ColMatch ColorPair
|
||||
ColCursor ColorPair
|
||||
ColSelected ColorPair
|
||||
ColCurrent ColorPair
|
||||
ColCurrentMatch ColorPair
|
||||
ColCurrentCursor ColorPair
|
||||
ColCurrentSelected ColorPair
|
||||
ColSpinner ColorPair
|
||||
ColInfo ColorPair
|
||||
ColHeader ColorPair
|
||||
ColBorder ColorPair
|
||||
)
|
||||
|
||||
func EmptyTheme() *ColorTheme {
|
||||
@@ -290,6 +326,7 @@ func EmptyTheme() *ColorTheme {
|
||||
Fg: colUndefined,
|
||||
Bg: colUndefined,
|
||||
DarkBg: colUndefined,
|
||||
Gutter: colUndefined,
|
||||
Prompt: colUndefined,
|
||||
Match: colUndefined,
|
||||
Current: colUndefined,
|
||||
@@ -312,6 +349,7 @@ func init() {
|
||||
Fg: colDefault,
|
||||
Bg: colDefault,
|
||||
DarkBg: colBlack,
|
||||
Gutter: colBlack,
|
||||
Prompt: colBlue,
|
||||
Match: colGreen,
|
||||
Current: colYellow,
|
||||
@@ -326,6 +364,7 @@ func init() {
|
||||
Fg: colDefault,
|
||||
Bg: colDefault,
|
||||
DarkBg: 236,
|
||||
Gutter: colUndefined,
|
||||
Prompt: 110,
|
||||
Match: 108,
|
||||
Current: 254,
|
||||
@@ -340,6 +379,7 @@ func init() {
|
||||
Fg: colDefault,
|
||||
Bg: colDefault,
|
||||
DarkBg: 251,
|
||||
Gutter: colUndefined,
|
||||
Prompt: 25,
|
||||
Match: 66,
|
||||
Current: 237,
|
||||
@@ -371,6 +411,7 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
|
||||
theme.Fg = o(baseTheme.Fg, theme.Fg)
|
||||
theme.Bg = o(baseTheme.Bg, theme.Bg)
|
||||
theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg)
|
||||
theme.Gutter = o(theme.DarkBg, o(baseTheme.Gutter, theme.Gutter))
|
||||
theme.Prompt = o(baseTheme.Prompt, theme.Prompt)
|
||||
theme.Match = o(baseTheme.Match, theme.Match)
|
||||
theme.Current = o(baseTheme.Current, theme.Current)
|
||||
@@ -392,27 +433,31 @@ func initPalette(theme *ColorTheme) {
|
||||
return ColorPair{fg, bg, idx}
|
||||
}
|
||||
if theme != nil {
|
||||
ColNormal = pair(theme.Fg, theme.Bg)
|
||||
ColPrompt = pair(theme.Prompt, theme.Bg)
|
||||
ColNormal = pair(theme.Fg, theme.Bg)
|
||||
ColMatch = pair(theme.Match, theme.Bg)
|
||||
ColCursor = pair(theme.Cursor, theme.Gutter)
|
||||
ColSelected = pair(theme.Selected, theme.Gutter)
|
||||
ColCurrent = pair(theme.Current, theme.DarkBg)
|
||||
ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg)
|
||||
ColCurrentCursor = pair(theme.Cursor, theme.DarkBg)
|
||||
ColCurrentSelected = pair(theme.Selected, theme.DarkBg)
|
||||
ColSpinner = pair(theme.Spinner, theme.Bg)
|
||||
ColInfo = pair(theme.Info, theme.Bg)
|
||||
ColCursor = pair(theme.Cursor, theme.DarkBg)
|
||||
ColSelected = pair(theme.Selected, theme.DarkBg)
|
||||
ColHeader = pair(theme.Header, theme.Bg)
|
||||
ColBorder = pair(theme.Border, theme.Bg)
|
||||
} else {
|
||||
ColNormal = pair(colDefault, colDefault)
|
||||
ColPrompt = pair(colDefault, colDefault)
|
||||
ColNormal = pair(colDefault, colDefault)
|
||||
ColMatch = pair(colDefault, colDefault)
|
||||
ColCurrent = pair(colDefault, colDefault)
|
||||
ColCurrentMatch = pair(colDefault, colDefault)
|
||||
ColSpinner = pair(colDefault, colDefault)
|
||||
ColInfo = pair(colDefault, colDefault)
|
||||
ColCursor = pair(colDefault, colDefault)
|
||||
ColSelected = pair(colDefault, colDefault)
|
||||
ColCurrent = pair(colDefault, colDefault)
|
||||
ColCurrentMatch = pair(colDefault, colDefault)
|
||||
ColCurrentCursor = pair(colDefault, colDefault)
|
||||
ColCurrentSelected = pair(colDefault, colDefault)
|
||||
ColSpinner = pair(colDefault, colDefault)
|
||||
ColInfo = pair(colDefault, colDefault)
|
||||
ColHeader = pair(colDefault, colDefault)
|
||||
ColBorder = pair(colDefault, colDefault)
|
||||
}
|
||||
|
||||
@@ -171,3 +171,12 @@ func (chars *Chars) CopyRunes(dest []rune) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (chars *Chars) Prepend(prefix string) {
|
||||
if runes := chars.optionalRunes(); runes != nil {
|
||||
runes = append([]rune(prefix), runes...)
|
||||
chars.slice = *(*[]byte)(unsafe.Pointer(&runes))
|
||||
} else {
|
||||
chars.slice = append([]byte(prefix), chars.slice...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1371,7 +1371,7 @@ class TestGoFZF < TestBase
|
||||
end
|
||||
|
||||
def test_preview_hidden
|
||||
tmux.send_keys %(seq 1000 | #{FZF} --preview 'echo {{}-{}-\\$LINES-\\$COLUMNS}' --preview-window down:1:hidden --bind ?:toggle-preview), :Enter
|
||||
tmux.send_keys %(seq 1000 | #{FZF} --preview 'echo {{}-{}-\\$FZF_PREVIEW_LINES-\\$FZF_PREVIEW_COLUMNS}' --preview-window down:1:hidden --bind ?:toggle-preview), :Enter
|
||||
tmux.until { |lines| lines[-1] == '>' }
|
||||
tmux.send_keys '?'
|
||||
tmux.until { |lines| lines[-2] =~ / {1-1-1-[0-9]+}/ }
|
||||
@@ -1400,21 +1400,21 @@ class TestGoFZF < TestBase
|
||||
|
||||
def test_preview_flags
|
||||
tmux.send_keys %(seq 10 | sed 's/^/:: /; s/$/ /' |
|
||||
#{FZF} --multi --preview 'echo {{2}/{s2}/{+2}/{+s2}/{q}}'), :Enter
|
||||
tmux.until { |lines| lines[1].include?('{1/1 /1/1 /}') }
|
||||
#{FZF} --multi --preview 'echo {{2}/{s2}/{+2}/{+s2}/{q}/{n}/{+n}}'), :Enter
|
||||
tmux.until { |lines| lines[1].include?('{1/1 /1/1 //0/0}') }
|
||||
tmux.send_keys '123'
|
||||
tmux.until { |lines| lines[1].include?('{////123}') }
|
||||
tmux.until { |lines| lines[1].include?('{////123//}') }
|
||||
tmux.send_keys 'C-u', '1'
|
||||
tmux.until { |lines| lines.match_count == 2 }
|
||||
tmux.until { |lines| lines[1].include?('{1/1 /1/1 /1}') }
|
||||
tmux.until { |lines| lines[1].include?('{1/1 /1/1 /1/0/0}') }
|
||||
tmux.send_keys :BTab
|
||||
tmux.until { |lines| lines[1].include?('{10/10 /1/1 /1}') }
|
||||
tmux.until { |lines| lines[1].include?('{10/10 /1/1 /1/9/0}') }
|
||||
tmux.send_keys :BTab
|
||||
tmux.until { |lines| lines[1].include?('{10/10 /1 10/1 10 /1}') }
|
||||
tmux.until { |lines| lines[1].include?('{10/10 /1 10/1 10 /1/9/0 9}') }
|
||||
tmux.send_keys '2'
|
||||
tmux.until { |lines| lines[1].include?('{//1 10/1 10 /12}') }
|
||||
tmux.until { |lines| lines[1].include?('{//1 10/1 10 /12//0 9}') }
|
||||
tmux.send_keys '3'
|
||||
tmux.until { |lines| lines[1].include?('{//1 10/1 10 /123}') }
|
||||
tmux.until { |lines| lines[1].include?('{//1 10/1 10 /123//0 9}') }
|
||||
end
|
||||
|
||||
def test_preview_q_no_match
|
||||
@@ -1427,6 +1427,12 @@ class TestGoFZF < TestBase
|
||||
tmux.until { |lines| !lines[1].include?('foo') }
|
||||
end
|
||||
|
||||
def test_preview_q_no_match_with_initial_query
|
||||
tmux.send_keys %(: | #{FZF} --preview 'echo foo {q}{q}' --query foo), :Enter
|
||||
tmux.until { |lines| lines.match_count == 0 }
|
||||
tmux.until { |lines| lines[1].include?('foofoo') }
|
||||
end
|
||||
|
||||
def test_no_clear
|
||||
tmux.send_keys "seq 10 | fzf --no-clear --inline-info --height 5 > #{tempname}", :Enter
|
||||
prompt = '> < 10/10'
|
||||
@@ -1513,6 +1519,25 @@ class TestGoFZF < TestBase
|
||||
assert_equal ['foo bar'], `#{FZF} -f'^foo\\ bar$' < #{tempname}`.lines.map(&:chomp)
|
||||
assert_equal input.lines.count - 1, `#{FZF} -f'!^foo\\ bar$' < #{tempname}`.lines.count
|
||||
end
|
||||
|
||||
def test_inverse_only_search_should_not_sort_the_result
|
||||
# Filter
|
||||
assert_equal(%w[aaaaa b ccc],
|
||||
`printf '%s\n' aaaaa b ccc BAD | #{FZF} -f '!bad'`.lines.map(&:chomp))
|
||||
|
||||
# Interactive
|
||||
tmux.send_keys(%[printf '%s\n' aaaaa b ccc BAD | #{FZF} -q '!bad'], :Enter)
|
||||
tmux.until { |lines| lines.item_count == 4 && lines.match_count == 3 }
|
||||
tmux.until { |lines| lines[-3] == '> aaaaa' }
|
||||
tmux.until { |lines| lines[-4] == ' b' }
|
||||
tmux.until { |lines| lines[-5] == ' ccc' }
|
||||
end
|
||||
|
||||
def test_preview_correct_tab_width_after_ansi_reset_code
|
||||
writelines tempname, ["\x1b[31m+\x1b[m\t\x1b[32mgreen"]
|
||||
tmux.send_keys "#{FZF} --preview 'cat #{tempname}'", :Enter
|
||||
tmux.until { |lines| lines[1].include?('+ green') }
|
||||
end
|
||||
end
|
||||
|
||||
module TestShell
|
||||
|
||||
Reference in New Issue
Block a user