Compare commits

...

33 Commits

Author SHA1 Message Date
Junegunn Choi
ff951341c9 0.18.0 2019-03-31 11:22:38 +09:00
Junegunn Choi
df570afd52 [docker] Fix gem install option in Dockerfile 2019-03-31 11:22:12 +09:00
Junegunn Choi
07d755df11 Fix regression of prompt display 2019-03-30 02:07:09 +09:00
Junegunn Choi
37585bd5a5 Disable preview scroll if the content fits on the screen
Close #1540
2019-03-29 18:28:45 +09:00
Junegunn Choi
89e24bf8f2 Fix ineffective break statement 2019-03-29 17:29:24 +09:00
Junegunn Choi
8d2fcd3518 Avoid unnecessary redraw of the preview window 2019-03-29 15:12:46 +09:00
Junegunn Choi
f39ab3875e Redraw prompt only when necessary 2019-03-29 15:02:31 +09:00
AnakinXL
82efe6c60d [doc] Add bat for preview syntax highlighting example (#1538)
Similar to this PR from fzf.vim:
https://github.com/junegunn/fzf.vim/pull/712
2019-03-29 10:31:17 +09:00
Junegunn Choi
75972d59a8 Add --no-unicode option to draw borders in ASCII characters
Close ##1533
2019-03-29 02:11:03 +09:00
Junegunn Choi
e7d60aac9c [vim] Do not restore cwd when autochdir is set and buffer changed
Close #1539
2019-03-28 13:57:04 +09:00
Junegunn Choi
a0bfbdd49c [vim] Increase window height by 2 when --border is set
Close #1535
2019-03-26 16:42:35 +09:00
Junegunn Choi
ba594982f0 Add MacPorts instruction
Close #1521
2019-03-18 18:45:57 +09:00
Junegunn Choi
2157f4f193 Add color option for gutter
fzf --color gutter:-1

Close #1529
Close #1468
2019-03-17 15:52:38 +09:00
Junegunn Choi
309bae423c [zsh-completion] Suppress "no matches found" message 2019-03-14 01:10:51 +09:00
Junegunn Choi
4f8bf2ae78 [install] Avoid generating empty component in $PATH
Fix #1517
2019-03-08 12:38:36 +09:00
Junegunn Choi
85c1f8a9e0 Always prepend ANSI reset code before re-assembling tokens 2019-03-07 15:29:57 +09:00
Junegunn Choi
e00e7e1e56 Remove unnecessary ANSI code injection 2019-03-07 02:02:02 +09:00
Junegunn Choi
1a6defdbcc Use simple string concatenation instead of using fmt.Sprintf 2019-03-07 02:00:31 +09:00
Junegunn Choi
ef577a6509 Preserve the original color of each token when using --with-nth with --ansi
Close #1500
2019-03-06 19:05:05 +09:00
Junegunn Choi
b7c6838e45 [install] Fix symlink log
Related #1466
2019-03-05 14:45:29 +09:00
Junegunn Choi
91d04cec5c [install] Print better error message when fzf --version failed
Related #1466
2019-03-05 14:44:29 +09:00
Rui Coelho
3bd8441079 [completion] Look up on ~/.ssh/config.d/* files when doing ssh host complete (#1420) 2019-02-28 16:40:41 +09:00
Junegunn Choi
8cf45a5197 [shell] Skip loading completion code on non-interactive shell
This change is not required if you use the install script to generate
~/.fzf.bash or ~/.fzf.zsh which already has the proper guard statement.

Close #1474
2019-02-28 16:13:59 +09:00
Junegunn Choi
8dc1377efb Export FZF_PREVIEW_LINES and FZF_PREVIEW_COLUMNS to preview process
fzf will still override LINES and COLUMNS as before but they may not
hold the correct values depending on the default shell.

Close #1314
2019-02-22 14:36:30 +09:00
Junegunn Choi
6c32148f90 Add placeholder expression for zero-based item index: {n} and {+n}
Close #1482
2019-02-19 01:12:57 +09:00
Junegunn Choi
315e568de0 Update build instruction
Close #1485
2019-01-31 18:43:10 +09:00
Junegunn Choi
5d16b28869 Fix tab width after ANSI reset code in preview window
Close #1423
2018-12-22 11:52:18 +09:00
Junegunn Choi
5624a89231 Inverse-only matches should not reorder the remaining results
Fix #1458
2018-12-19 23:05:29 +09:00
Junegunn Choi
63c42b14f2 Remove trailing spaces in Makefile 2018-12-13 15:17:30 +09:00
Stefan Tatschner
6f1eaa9b39 Use go modules and simplify build (#1444)
* Update .travis.yml and use stages

This updates the .travis.yml configuration to use separate stages for
unittests and CLI tests. The output is now clearer, since for unittests
and CLI tests separate web pages are available.

* Use go modules and simplify build
2018-12-13 14:36:15 +09:00
Junegunn Choi
ca42e5e00a Avoid unnecessary redraw of preview window
Close #1455
2018-12-13 10:58:57 +09:00
Junegunn Choi
61feee690c Render preview window when the initial query fails to match
Only if preview template contains {q}

Fix #1452
Related #1307
2018-12-05 18:45:55 +09:00
Christian Muehlhaeuser
d4ed955aee Typo & grammar fixes in README (#1413) 2018-11-09 16:50:16 +09:00
29 changed files with 516 additions and 264 deletions

View File

@@ -1,20 +1,27 @@
language: ruby language: go
dist: trusty dist: xenial
sudo: required addons:
matrix: apt:
sources:
- sourceline: "ppa:pi-rho/dev"
- sourceline: "ppa:fish-shell/release-2"
packages:
- tmux
- zsh
- fish
env:
- GO111MODULE=on
jobs:
include: include:
- env: TAGS= - stage: unittest
rvm: 2.3.3 go: "1.11.x"
# - env: TAGS=tcell script: make && make test
# rvm: 2.3.3
install: - stage: cli
- sudo add-apt-repository -y ppa:pi-rho/dev go: "1.11.x"
- sudo apt-add-repository -y ppa:fish-shell/release-2 rvm: "2.5"
- sudo apt-get update script: |
- sudo apt-get install -y tmux zsh fish make install && ./install --all && tmux new "ruby test/test_go.rb > out && touch ok" && cat out && [ -e ok ]
script: |
make test install &&
./install --all &&
tmux new "ruby test/test_go.rb > out && touch ok" && cat out && [ -e ok ]

View File

@@ -6,12 +6,10 @@ Build instructions
### Prerequisites ### Prerequisites
- `go` executable in $PATH - Go 1.11 or above
### Using Makefile ### Using Makefile
Makefile will set up and use its own `$GOPATH` under the project root.
```sh ```sh
# Build fzf binary for your platform in target # Build fzf binary for your platform in target
make make

View File

@@ -1,6 +1,21 @@
CHANGELOG CHANGELOG
========= =========
0.18.0
------
- Added placeholder expression for zero-based item index: `{n}` and `{+n}`
- `fzf --preview 'echo {n}: {}'`
- Added color option for the gutter: `--color gutter:-1`
- Added `--no-unicode` option for drawing borders in non-Unicode, ASCII
characters
- `FZF_PREVIEW_LINES` and `FZF_PREVIEW_COLUMNS` are exported to preview process
- fzf still overrides `LINES` and `COLUMNS` as before, but they may be
reset by the default shell.
- Bug fixes and improvements
- See https://github.com/junegunn/fzf/milestone/14?closed=1
- Built with Go 1.12.1
0.17.5 0.17.5
------ ------

View File

@@ -1,6 +1,6 @@
FROM archlinux/base:latest FROM archlinux/base:latest
RUN pacman -Sy && pacman --noconfirm -S awk git tmux zsh fish ruby procps go make 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 '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
RUN echo '. ~/.bashrc' >> ~/.bash_profile RUN echo '. ~/.bashrc' >> ~/.bash_profile

View File

@@ -1,17 +1,9 @@
ifndef GOOS GO ?= go
GOOS := $(word 1, $(subst /, " ", $(word 4, $(shell go version)))) GOOS ?= $(word 1, $(subst /, " ", $(word 4, $(shell go version))))
endif
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST))) MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
ROOT_DIR := $(shell dirname $(MAKEFILE)) ROOT_DIR := $(shell dirname $(MAKEFILE))
GOPATH := $(ROOT_DIR)/gopath SOURCES := $(wildcard *.go src/*.go src/*/*.go) $(MAKEFILE)
SRC_LINK := $(GOPATH)/src/github.com/junegunn/fzf/src
VENDOR_LINK := $(GOPATH)/src/github.com/junegunn/fzf/vendor
export GOPATH
GLIDE_YAML := glide.yaml
GLIDE_LOCK := glide.lock
SOURCES := $(wildcard *.go src/*.go src/*/*.go) $(SRC_LINK) $(VENDOR_LINK) $(GLIDE_LOCK) $(MAKEFILE)
REVISION := $(shell git log -n 1 --pretty=format:%h -- $(SOURCES)) REVISION := $(shell git log -n 1 --pretty=format:%h -- $(SOURCES))
BUILD_FLAGS := -a -ldflags "-X main.revision=$(REVISION) -w -extldflags=$(LDFLAGS)" -tags "$(TAGS)" 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=openbsd make release
GOOS=windows make release GOOS=windows make release
$(SRC_LINK): test: $(SOURCES)
mkdir -p $(shell dirname $(SRC_LINK)) SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \
ln -sf $(ROOT_DIR)/src $(SRC_LINK)
$(VENDOR_LINK):
mkdir -p $(shell dirname $(VENDOR_LINK))
ln -sf $(ROOT_DIR)/vendor $(VENDOR_LINK)
vendor: $(GLIDE_YAML)
go get -u github.com/Masterminds/glide && $(GOPATH)/bin/glide install && touch $@
test: $(SOURCES) vendor
SHELL=/bin/sh GOOS= go test -v -tags "$(TAGS)" \
github.com/junegunn/fzf/src \ github.com/junegunn/fzf/src \
github.com/junegunn/fzf/src/algo \ github.com/junegunn/fzf/src/algo \
github.com/junegunn/fzf/src/tui \ github.com/junegunn/fzf/src/tui \
@@ -111,29 +92,29 @@ test: $(SOURCES) vendor
install: bin/fzf install: bin/fzf
clean: clean:
rm -rf target $(RM) -r target
target/$(BINARY32): $(SOURCES) vendor target/$(BINARY32): $(SOURCES)
GOARCH=386 go build $(BUILD_FLAGS) -o $@ GOARCH=386 $(GO) build $(BUILD_FLAGS) -o $@
target/$(BINARY64): $(SOURCES) vendor target/$(BINARY64): $(SOURCES)
GOARCH=amd64 go build $(BUILD_FLAGS) -o $@ GOARCH=amd64 $(GO) build $(BUILD_FLAGS) -o $@
# https://github.com/golang/go/wiki/GoArm # https://github.com/golang/go/wiki/GoArm
target/$(BINARYARM5): $(SOURCES) vendor target/$(BINARYARM5): $(SOURCES)
GOARCH=arm GOARM=5 go build $(BUILD_FLAGS) -o $@ GOARCH=arm GOARM=5 $(GO) build $(BUILD_FLAGS) -o $@
target/$(BINARYARM6): $(SOURCES) vendor target/$(BINARYARM6): $(SOURCES)
GOARCH=arm GOARM=6 go build $(BUILD_FLAGS) -o $@ GOARCH=arm GOARM=6 $(GO) build $(BUILD_FLAGS) -o $@
target/$(BINARYARM7): $(SOURCES) vendor target/$(BINARYARM7): $(SOURCES)
GOARCH=arm GOARM=7 go build $(BUILD_FLAGS) -o $@ GOARCH=arm GOARM=7 $(GO) build $(BUILD_FLAGS) -o $@
target/$(BINARYARM8): $(SOURCES) vendor target/$(BINARYARM8): $(SOURCES)
GOARCH=arm64 go build $(BUILD_FLAGS) -o $@ GOARCH=arm64 $(GO) build $(BUILD_FLAGS) -o $@
target/$(BINARYPPC64LE): $(SOURCES) vendor target/$(BINARYPPC64LE): $(SOURCES)
GOARCH=ppc64le go build $(BUILD_FLAGS) -o $@ GOARCH=ppc64le $(GO) build $(BUILD_FLAGS) -o $@
bin/fzf: target/$(BINARY) | bin bin/fzf: target/$(BINARY) | bin
cp -f target/$(BINARY) bin/fzf cp -f target/$(BINARY) bin/fzf
@@ -146,4 +127,8 @@ docker-test:
docker build -t fzf-arch . docker build -t fzf-arch .
docker run -it 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

View File

@@ -88,6 +88,10 @@ brew install fzf
$(brew --prefix)/opt/fzf/install $(brew --prefix)/opt/fzf/install
``` ```
fzf is also available [via MacPorts][portfile]: `sudo port install fzf`
[portfile]: https://github.com/macports/macports-ports/blob/master/sysutils/fzf/Portfile
### Using git ### Using git
Alternatively, you can "git clone" this repository to any directory and run Alternatively, you can "git clone" this repository to any directory and run
@@ -127,10 +131,10 @@ But instead of separately installing fzf on your system (using Homebrew or
vim-plug to do both. vim-plug to do both.
```vim ```vim
" PlugInstall and PlugUpdate will clone fzf in ~/.fzf and run install script " PlugInstall and PlugUpdate will clone fzf in ~/.fzf and run the install script
Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' } Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }
" Both options are optional. You don't have to install fzf in ~/.fzf " Both options are optional. You don't have to install fzf in ~/.fzf
" and you don't have to run install script if you use fzf only in Vim. " and you don't have to run the install script if you use fzf only in Vim.
``` ```
### Arch Linux ### Arch Linux
@@ -312,16 +316,16 @@ fullscreen mode.
fzf --height 40% fzf --height 40%
``` ```
Key bindings for command line Key bindings for command-line
----------------------------- -----------------------------
The install script will setup the following key bindings for bash, zsh, and The install script will setup the following key bindings for bash, zsh, and
fish. fish.
- `CTRL-T` - Paste the selected files and directories onto the command line - `CTRL-T` - Paste the selected files and directories onto the command-line
- Set `FZF_CTRL_T_COMMAND` to override the default command - Set `FZF_CTRL_T_COMMAND` to override the default command
- Set `FZF_CTRL_T_OPTS` to pass additional options - Set `FZF_CTRL_T_OPTS` to pass additional options
- `CTRL-R` - Paste the selected command from history onto the command line - `CTRL-R` - Paste the selected command from history onto the command-line
- If you want to see the commands in chronological order, press `CTRL-R` - If you want to see the commands in chronological order, press `CTRL-R`
again which toggles sorting by relevance again which toggles sorting by relevance
- Set `FZF_CTRL_R_OPTS` to pass additional options - Set `FZF_CTRL_R_OPTS` to pass additional options
@@ -373,7 +377,7 @@ cd ~/github/fzf**<TAB>
#### Process IDs #### Process IDs
Fuzzy completion for PIDs is provided for kill command. In this case Fuzzy completion for PIDs is provided for kill command. In this case,
there is no trigger sequence, just press tab key after kill command. there is no trigger sequence, just press tab key after kill command.
```sh ```sh
@@ -426,7 +430,7 @@ _fzf_compgen_dir() {
On bash, fuzzy completion is enabled only for a predefined set of commands On bash, fuzzy completion is enabled only for a predefined set of commands
(`complete | grep _fzf` to see the list). But you can enable it for other (`complete | grep _fzf` to see the list). But you can enable it for other
commands as well like follows. commands as well as follows.
```sh ```sh
complete -F _fzf_path_completion -o default -o bashdefault ag complete -F _fzf_path_completion -o default -o bashdefault ag
@@ -443,7 +447,7 @@ Advanced topics
### Performance ### Performance
fzf is fast, and is [getting even faster][perf]. Performance should not be fzf is fast and is [getting even faster][perf]. Performance should not be
a problem in most use cases. However, you might want to be aware of the a problem in most use cases. However, you might want to be aware of the
options that affect the performance. options that affect the performance.
@@ -454,7 +458,7 @@ options that affect the performance.
- `--with-nth` makes fzf slower as fzf has to tokenize and reassemble each - `--with-nth` makes fzf slower as fzf has to tokenize and reassemble each
line. line.
- If you absolutely need better performance, you can consider using - If you absolutely need better performance, you can consider using
`--algo=v1` (the default being `v2`) to make fzf use faster greedy `--algo=v1` (the default being `v2`) to make fzf use a faster greedy
algorithm. However, this algorithm is not guaranteed to find the optimal algorithm. However, this algorithm is not guaranteed to find the optimal
ordering of the matches and is not recommended. ordering of the matches and is not recommended.
@@ -475,7 +479,7 @@ See *KEY BINDINGS* section of the man page for details.
### Preview window ### Preview window
When `--preview` option is set, fzf automatically starts external process with When `--preview` option is set, fzf automatically starts an external process with
the current line as the argument and shows the result in the split window. the current line as the argument and shows the result in the split window.
```bash ```bash
@@ -483,7 +487,7 @@ the current line as the argument and shows the result in the split window.
fzf --preview 'cat {}' fzf --preview 'cat {}'
``` ```
Since preview window is updated only after the process is complete, it's Since the preview window is updated only after the process is complete, it's
important that the command finishes quickly. important that the command finishes quickly.
```bash ```bash
@@ -494,15 +498,17 @@ fzf --preview 'head -100 {}'
Preview window supports ANSI colors, so you can use programs that Preview window supports ANSI colors, so you can use programs that
syntax-highlights the content of a file. syntax-highlights the content of a file.
- Bat: https://github.com/sharkdp/bat
- Highlight: http://www.andre-simon.de/doku/highlight/en/highlight.php - Highlight: http://www.andre-simon.de/doku/highlight/en/highlight.php
- CodeRay: http://coderay.rubychan.de/ - CodeRay: http://coderay.rubychan.de/
- Rouge: https://github.com/jneen/rouge - Rouge: https://github.com/jneen/rouge
```bash ```bash
# Try highlight, coderay, rougify in turn, then fall back to cat # Try bat, highlight, coderay, rougify in turn, then fall back to cat
fzf --preview '[[ $(file --mime {}) =~ binary ]] && fzf --preview '[[ $(file --mime {}) =~ binary ]] &&
echo {} is a binary file || echo {} is a binary file ||
(highlight -O ansi -l {} || (bat --style=numbers --color=always {} ||
highlight -O ansi -l {} ||
coderay {} || coderay {} ||
rougify {} || rougify {} ||
cat {}) 2> /dev/null | head -500' cat {}) 2> /dev/null | head -500'
@@ -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. The globbing system is different in fish and thus `**` completion will not work.
However, the `CTRL-T` command will use the last token on the commandline as the However, the `CTRL-T` command will use the last token on the command-line as the
root folder for the recursive search. For instance, hitting `CTRL-T` at the end root folder for the recursive search. For instance, hitting `CTRL-T` at the end
of the following commandline of the following command-line
```sh ```sh
ls /var/ ls /var/

48
glide.lock generated
View File

@@ -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: []

View File

@@ -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
View 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
View 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
View File

@@ -2,7 +2,7 @@
set -u set -u
version=0.17.5 version=0.18.0
auto_completion= auto_completion=
key_bindings= key_bindings=
update_config=2 update_config=2
@@ -91,17 +91,20 @@ ask() {
check_binary() { check_binary() {
echo -n " - Checking fzf executable ... " echo -n " - Checking fzf executable ... "
local output local output
output=$("$fzf_base"/bin/fzf --version 2>&1 | awk '{print $1}') output=$("$fzf_base"/bin/fzf --version 2>&1)
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo "Error: $output" echo "Error: $output"
binary_error="Invalid binary" binary_error="Invalid binary"
elif [ "$version" != "$output" ]; then
echo "$output != $version"
binary_error="Invalid version"
else else
echo "$output" output=${output/ */}
binary_error="" if [ "$version" != "$output" ]; then
return 0 echo "$output != $version"
binary_error="Invalid version"
else
echo "$output"
binary_error=""
return 0
fi
fi fi
rm -f "$fzf_base"/bin/fzf rm -f "$fzf_base"/bin/fzf
return 1 return 1
@@ -110,7 +113,7 @@ check_binary() {
link_fzf_in_path() { link_fzf_in_path() {
if which_fzf="$(command -v fzf)"; then if which_fzf="$(command -v fzf)"; then
echo " - Found in \$PATH" echo " - Found in \$PATH"
echo " - Creating symlink: $which_fzf -> bin/fzf" echo " - Creating symlink: bin/fzf -> $which_fzf"
(cd "$fzf_base"/bin && rm -f fzf && ln -sf "$which_fzf" fzf) (cd "$fzf_base"/bin && rm -f fzf && ln -sf "$which_fzf" fzf)
check_binary && return check_binary && return
fi fi
@@ -269,7 +272,7 @@ for shell in $shells; do
# Setup fzf # Setup fzf
# --------- # ---------
if [[ ! "\$PATH" == *$fzf_base_esc/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
@@ -279,7 +282,6 @@ $fzf_completion
# Key bindings # Key bindings
# ------------ # ------------
$fzf_key_bindings $fzf_key_bindings
EOF EOF
echo "OK" echo "OK"
done done

View File

@@ -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 "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 .SH NAME
fzf-tmux - open fzf in tmux split pane fzf-tmux - open fzf in tmux split pane

View File

@@ -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 "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 .SH NAME
fzf - a command-line fuzzy finder fzf - a command-line fuzzy finder
@@ -174,6 +174,11 @@ 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.
@@ -250,6 +255,7 @@ e.g. \fBfzf --color=bg+:24\fR
\fBhl \fRHighlighted substrings \fBhl \fRHighlighted substrings
\fBfg+ \fRText (current line) \fBfg+ \fRText (current line)
\fBbg+ \fRBackground (current line) \fBbg+ \fRBackground (current line)
\fBgutter \fRGutter on the left (defaults to \fBbg+\fR)
\fBhl+ \fRHighlighted substrings (current line) \fBhl+ \fRHighlighted substrings (current line)
\fBinfo \fRInfo \fBinfo \fRInfo
\fBborder \fRBorder of the preview window and horizontal separators (\fB--border\fR) \fBborder \fRBorder of the preview window and horizontal separators (\fB--border\fR)
@@ -288,8 +294,11 @@ EXPRESSION\fR for the details).
e.g. \fBfzf --preview='head -$LINES {}'\fR e.g. \fBfzf --preview='head -$LINES {}'\fR
\fBls -l | fzf --preview="echo user={3} when={-4..-2}; cat {-1}" --header-lines=1\fR \fBls -l | fzf --preview="echo user={3} when={-4..-2}; cat {-1}" --header-lines=1\fR
fzf overrides \fB$LINES\fR and \fB$COLUMNS\fR so that they represent the exact fzf exports \fB$FZF_PREVIEW_LINES\fR and \fB$FZF_PREVIEW_COLUMNS\fR so that
size of the preview window. they represent the exact size of the preview window. (It also overrides
\fB$LINES\fR and \fB$COLUMNS\fR with the same values but they can be reset
by the default shell, so prefer to refer to the ones with \fBFZF_PREVIEW_\fR
prefix.)
A placeholder expression starting with \fB+\fR flag will be replaced to the A placeholder expression starting with \fB+\fR flag will be replaced to the
space-separated list of the selected lines (or the current line if no selection space-separated list of the selected lines (or the current line if no selection
@@ -301,7 +310,9 @@ e.g. \fBfzf --multi --preview='head -10 {+}'\fR
When using a field index expression, leading and trailing whitespace is stripped 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. 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. Note that you can escape a placeholder pattern by prepending a backslash.

View File

@@ -457,7 +457,8 @@ function! s:pushd(dict)
let cwd = s:fzf_getcwd() let cwd = s:fzf_getcwd()
let w:fzf_pushd = { let w:fzf_pushd = {
\ 'command': haslocaldir() ? 'lcd' : (exists(':tcd') && haslocaldir(-1) ? 'tcd' : 'cd'), \ 'command': haslocaldir() ? 'lcd' : (exists(':tcd') && haslocaldir(-1) ? 'tcd' : 'cd'),
\ 'origin': cwd \ 'origin': cwd,
\ 'bufname': bufname('')
\ } \ }
execute 'lcd' s:escape(a:dict.dir) execute 'lcd' s:escape(a:dict.dir)
let cwd = s:fzf_getcwd() let cwd = s:fzf_getcwd()
@@ -493,7 +494,7 @@ function! s:dopopd()
" matches 'dir' entry. However, it is possible that the sink function did " 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 " change the directory to 'dir'. In that case, the user will have an
" unexpected result. " 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) execute w:fzf_pushd.command s:escape(w:fzf_pushd.origin)
endif endif
unlet w:fzf_pushd unlet w:fzf_pushd
@@ -606,8 +607,9 @@ function! s:calc_size(max, val, dict)
let srcsz = len(a:dict.source) let srcsz = len(a:dict.source)
endif endif
let opts = 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, '--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

View File

@@ -9,6 +9,8 @@
# - $FZF_COMPLETION_TRIGGER (default: '**') # - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty) # - $FZF_COMPLETION_OPTS (default: empty)
if [[ $- =~ i ]]; then
# To use custom commands instead of find, override _fzf_compgen_{path,dir} # To use custom commands instead of find, override _fzf_compgen_{path,dir}
if ! declare -f _fzf_compgen_path > /dev/null; then if ! declare -f _fzf_compgen_path > /dev/null; then
_fzf_compgen_path() { _fzf_compgen_path() {
@@ -241,7 +243,7 @@ _fzf_complete_telnet() {
_fzf_complete_ssh() { _fzf_complete_ssh() {
_fzf_complete '+m' "$@" < <( _fzf_complete '+m' "$@" < <(
cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host ' | command grep -v '[*?]' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}') \ cat <(cat ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host ' | command grep -v '[*?]' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}') \
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \ <(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') | <(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
awk '{if (length($2) > 0) {print $2}}' | sort -u awk '{if (length($2) > 0) {print $2}}' | sort -u
@@ -330,3 +332,5 @@ complete -F _fzf_complete_export -o default -o bashdefault export
complete -F _fzf_complete_unalias -o default -o bashdefault unalias complete -F _fzf_complete_unalias -o default -o bashdefault unalias
unset cmd d_cmds a_cmds x_cmds unset cmd d_cmds a_cmds x_cmds
fi

View File

@@ -9,6 +9,8 @@
# - $FZF_COMPLETION_TRIGGER (default: '**') # - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty) # - $FZF_COMPLETION_OPTS (default: empty)
if [[ $- =~ i ]]; then
# To use custom commands instead of find, override _fzf_compgen_{path,dir} # To use custom commands instead of find, override _fzf_compgen_{path,dir}
if ! declare -f _fzf_compgen_path > /dev/null; then if ! declare -f _fzf_compgen_path > /dev/null; then
_fzf_compgen_path() { _fzf_compgen_path() {
@@ -112,7 +114,8 @@ _fzf_complete_telnet() {
_fzf_complete_ssh() { _fzf_complete_ssh() {
_fzf_complete '+m' "$@" < <( _fzf_complete '+m' "$@" < <(
command cat <(cat ~/.ssh/config /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host ' | command grep -v '[*?]' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}') \ setopt localoptions nonomatch
command cat <(cat ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^host ' | command grep -v '[*?]' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}') \
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \ <(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') | <(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
awk '{if (length($2) > 0) {print $2}}' | sort -u awk '{if (length($2) > 0) {print $2}}' | sort -u
@@ -192,3 +195,5 @@ fzf-completion() {
zle -N fzf-completion zle -N fzf-completion
bindkey '^I' fzf-completion bindkey '^I' fzf-completion
fi

View File

@@ -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() {

View File

@@ -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"
@@ -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")
}

View File

@@ -9,7 +9,7 @@ import (
const ( const (
// Current version // Current version
version = "0.17.5" version = "0.18.0"
// Core // Core
coordinatorDelayMax time.Duration = 100 * time.Millisecond coordinatorDelayMax time.Duration = 100 * time.Millisecond

View File

@@ -63,12 +63,14 @@ func Run(opts *Options, revision string) {
ansiProcessor := func(data []byte) (util.Chars, *[]ansiOffset) { ansiProcessor := func(data []byte) (util.Chars, *[]ansiOffset) {
return util.ToChars(data), nil return util.ToChars(data), nil
} }
var lineAnsiState, prevLineAnsiState *ansiState
if opts.Ansi { if opts.Ansi {
if opts.Theme != nil { if opts.Theme != nil {
var state *ansiState
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) { ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
trimmed, offsets, newState := extractColor(string(data), state, nil) prevLineAnsiState = lineAnsiState
state = newState trimmed, offsets, newState := extractColor(string(data), lineAnsiState, nil)
lineAnsiState = newState
return util.ToChars([]byte(trimmed)), offsets return util.ToChars([]byte(trimmed)), offsets
} }
} else { } else {
@@ -100,6 +102,22 @@ func Run(opts *Options, revision string) {
} else { } else {
chunkList = NewChunkList(func(item *Item, data []byte) bool { chunkList = NewChunkList(func(item *Item, data []byte) bool {
tokens := Tokenize(string(data), opts.Delimiter) tokens := Tokenize(string(data), opts.Delimiter)
if opts.Ansi && opts.Theme != nil && len(tokens) > 1 {
var ansiState *ansiState
if prevLineAnsiState != nil {
ansiStateDup := *prevLineAnsiState
ansiState = &ansiStateDup
}
for _, token := range tokens {
prevAnsiState := ansiState
_, _, ansiState = extractColor(token.text.ToString(), ansiState, nil)
if prevAnsiState != nil {
token.text.Prepend("\x1b[m" + prevAnsiState.ToString())
} else {
token.text.Prepend("\x1b[m")
}
}
}
trans := Transform(tokens, opts.WithNth) trans := Transform(tokens, opts.WithNth)
transformed := joinTokens(trans) transformed := joinTokens(trans)
if len(header) < opts.HeaderLines { if len(header) < opts.HeaderLines {
@@ -149,6 +167,7 @@ func Run(opts *Options, revision string) {
} }
pattern := patternBuilder([]rune(*opts.Filter)) pattern := patternBuilder([]rune(*opts.Filter))
matcher.sort = pattern.sortable
found := false found := false
if streamingFilter { if streamingFilter {

View File

@@ -230,5 +230,5 @@ func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final
} else { } else {
event = reqRetry event = reqRetry
} }
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort}) m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable})
} }

View File

@@ -195,6 +195,7 @@ type Options struct {
HeaderLines int HeaderLines int
Margin [4]sizeSpec Margin [4]sizeSpec
Bordered bool Bordered bool
Unicode bool
Tabstop int Tabstop int
ClearOnExit bool ClearOnExit bool
Version bool Version bool
@@ -244,6 +245,7 @@ func defaultOptions() *Options {
Header: make([]string, 0), Header: make([]string, 0),
HeaderLines: 0, HeaderLines: 0,
Margin: defaultMargin(), Margin: defaultMargin(),
Unicode: true,
Tabstop: 8, Tabstop: 8,
ClearOnExit: true, ClearOnExit: true,
Version: false} Version: false}
@@ -576,6 +578,8 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
theme.Current = ansi theme.Current = ansi
case "bg+": case "bg+":
theme.DarkBg = ansi theme.DarkBg = ansi
case "gutter":
theme.Gutter = ansi
case "hl": case "hl":
theme.Match = ansi theme.Match = ansi
case "hl+": case "hl+":
@@ -1150,6 +1154,10 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Bordered = false opts.Bordered = false
case "--border": case "--border":
opts.Bordered = true opts.Bordered = true
case "--no-unicode":
opts.Unicode = false
case "--unicode":
opts.Unicode = true
case "--margin": case "--margin":
opts.Margin = parseMargin( opts.Margin = parseMargin(
nextString(allArgs, &i, "margin required (TRBL / TB,RL / T,RL,B / T,R,B,L)")) nextString(allArgs, &i, "margin required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))

View File

@@ -52,6 +52,7 @@ type Pattern struct {
forward bool forward bool
text []rune text []rune
termSets []termSet termSets []termSet
sortable bool
cacheable bool cacheable bool
cacheKey string cacheKey string
delimiter Delimiter delimiter Delimiter
@@ -101,18 +102,27 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
} }
caseSensitive := true caseSensitive := true
sortable := true
termSets := []termSet{} termSets := []termSet{}
if extended { if extended {
termSets = parseTerms(fuzzy, caseMode, normalize, asString) termSets = parseTerms(fuzzy, caseMode, normalize, asString)
// We should not sort the result if there are only inverse search terms
sortable = false
Loop: Loop:
for _, termSet := range termSets { for _, termSet := range termSets {
for idx, term := range termSet { for idx, term := range termSet {
if !term.inv {
sortable = true
}
// If the query contains inverse search terms or OR operators, // If the query contains inverse search terms or OR operators,
// we cannot cache the search scope // we cannot cache the search scope
if !cacheable || idx > 0 || term.inv || fuzzy && term.typ != termFuzzy || !fuzzy && term.typ != termExact { if !cacheable || idx > 0 || term.inv || fuzzy && term.typ != termFuzzy || !fuzzy && term.typ != termExact {
cacheable = false cacheable = false
break Loop if sortable {
// Can't break until we see at least one non-inverse term
break Loop
}
} }
} }
} }
@@ -134,6 +144,7 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
forward: forward, forward: forward,
text: []rune(asString), text: []rune(asString),
termSets: termSets, termSets: termSets,
sortable: sortable,
cacheable: cacheable, cacheable: cacheable,
nth: nth, nth: nth,
delimiter: delimiter, delimiter: delimiter,

View File

@@ -24,7 +24,7 @@ import (
var placeholder *regexp.Regexp var placeholder *regexp.Regexp
func init() { func init() {
placeholder = regexp.MustCompile("\\\\?(?:{[+s]*[0-9,-.]*}|{q})") placeholder = regexp.MustCompile("\\\\?(?:{[+s]*[0-9,-.]*}|{q}|{\\+?n})")
} }
type jumpMode int type jumpMode int
@@ -40,6 +40,7 @@ type previewer struct {
lines int lines int
offset int offset int
enabled bool enabled bool
more bool
} }
type itemLine struct { type itemLine struct {
@@ -88,6 +89,7 @@ type Terminal struct {
tabstop int tabstop int
margin [4]sizeSpec margin [4]sizeSpec
strong tui.Attr strong tui.Attr
unicode bool
bordered bool bordered bool
cleanExit bool cleanExit bool
border tui.Window border tui.Window
@@ -227,6 +229,7 @@ const (
type placeholderFlags struct { type placeholderFlags struct {
plus bool plus bool
preserveSpace bool preserveSpace bool
number bool
query bool query bool
} }
@@ -390,6 +393,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
printQuery: opts.PrintQuery, printQuery: opts.PrintQuery,
history: opts.History, history: opts.History,
margin: opts.Margin, margin: opts.Margin,
unicode: opts.Unicode,
bordered: opts.Bordered, bordered: opts.Bordered,
cleanExit: opts.ClearOnExit, cleanExit: opts.ClearOnExit,
strong: strongAttr, strong: strongAttr,
@@ -407,7 +411,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
selected: make(map[int32]selectedItem), selected: make(map[int32]selectedItem),
reqBox: util.NewEventBox(), reqBox: util.NewEventBox(),
preview: opts.Preview, preview: opts.Preview,
previewer: previewer{"", 0, 0, previewBox != nil && !opts.Preview.hidden}, previewer: previewer{"", 0, 0, previewBox != nil && !opts.Preview.hidden, false},
previewBox: previewBox, previewBox: previewBox,
eventBox: eventBox, eventBox: eventBox,
mutex: sync.Mutex{}, mutex: sync.Mutex{},
@@ -599,11 +603,12 @@ func (t *Terminal) resizeWindows() {
marginInt[0]-1, marginInt[0]-1,
marginInt[3], marginInt[3],
width, width,
height+2, tui.BorderHorizontal) height+2, tui.MakeBorderStyle(tui.BorderHorizontal, t.unicode))
} }
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
if previewVisible { if previewVisible {
createPreviewWindow := func(y int, x int, w int, h int) { createPreviewWindow := func(y int, x int, w int, h int) {
t.pborder = t.tui.NewWindow(y, x, w, h, tui.BorderAround) t.pborder = t.tui.NewWindow(y, x, w, h, tui.MakeBorderStyle(tui.BorderAround, t.unicode))
pwidth := w - 4 pwidth := w - 4
// ncurses auto-wraps the line when the cursor reaches the right-end of // ncurses auto-wraps the line when the cursor reaches the right-end of
// the window. To prevent unintended line-wraps, we use the width one // the window. To prevent unintended line-wraps, we use the width one
@@ -611,29 +616,28 @@ func (t *Terminal) resizeWindows() {
if !t.preview.wrap && t.tui.DoesAutoWrap() { if !t.preview.wrap && t.tui.DoesAutoWrap() {
pwidth += 1 pwidth += 1
} }
t.pwindow = t.tui.NewWindow(y+1, x+2, pwidth, h-2, tui.BorderNone) t.pwindow = t.tui.NewWindow(y+1, x+2, pwidth, h-2, noBorder)
os.Setenv("FZF_PREVIEW_HEIGHT", strconv.Itoa(h-2))
} }
switch t.preview.position { switch t.preview.position {
case posUp: case posUp:
pheight := calculateSize(height, t.preview.size, minHeight, 3) pheight := calculateSize(height, t.preview.size, minHeight, 3)
t.window = t.tui.NewWindow( t.window = t.tui.NewWindow(
marginInt[0]+pheight, marginInt[3], width, height-pheight, tui.BorderNone) marginInt[0]+pheight, marginInt[3], width, height-pheight, noBorder)
createPreviewWindow(marginInt[0], marginInt[3], width, pheight) createPreviewWindow(marginInt[0], marginInt[3], width, pheight)
case posDown: case posDown:
pheight := calculateSize(height, t.preview.size, minHeight, 3) pheight := calculateSize(height, t.preview.size, minHeight, 3)
t.window = t.tui.NewWindow( t.window = t.tui.NewWindow(
marginInt[0], marginInt[3], width, height-pheight, tui.BorderNone) marginInt[0], marginInt[3], width, height-pheight, noBorder)
createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight) createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
case posLeft: case posLeft:
pwidth := calculateSize(width, t.preview.size, minWidth, 5) pwidth := calculateSize(width, t.preview.size, minWidth, 5)
t.window = t.tui.NewWindow( t.window = t.tui.NewWindow(
marginInt[0], marginInt[3]+pwidth, width-pwidth, height, tui.BorderNone) marginInt[0], marginInt[3]+pwidth, width-pwidth, height, noBorder)
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height) createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
case posRight: case posRight:
pwidth := calculateSize(width, t.preview.size, minWidth, 5) pwidth := calculateSize(width, t.preview.size, minWidth, 5)
t.window = t.tui.NewWindow( t.window = t.tui.NewWindow(
marginInt[0], marginInt[3], width-pwidth, height, tui.BorderNone) marginInt[0], marginInt[3], width-pwidth, height, noBorder)
createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height) createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height)
} }
} else { } else {
@@ -641,7 +645,7 @@ func (t *Terminal) resizeWindows() {
marginInt[0], marginInt[0],
marginInt[3], marginInt[3],
width, width,
height, tui.BorderNone) height, noBorder)
} }
for i := 0; i < t.window.Height(); i++ { for i := 0; i < t.window.Height(); i++ {
t.window.MoveAndClear(i, 0) t.window.MoveAndClear(i, 0)
@@ -834,15 +838,16 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool) {
} }
t.move(line, 0, false) t.move(line, 0, false)
t.window.CPrint(tui.ColCursor, t.strong, label)
if current { if current {
t.window.CPrint(tui.ColCurrentCursor, t.strong, label)
if selected { if selected {
t.window.CPrint(tui.ColSelected, t.strong, ">") t.window.CPrint(tui.ColCurrentSelected, t.strong, ">")
} else { } else {
t.window.CPrint(tui.ColCurrent, t.strong, " ") t.window.CPrint(tui.ColCurrentSelected, t.strong, " ")
} }
newLine.width = t.printHighlighted(result, t.strong, tui.ColCurrent, tui.ColCurrentMatch, true, true) newLine.width = t.printHighlighted(result, t.strong, tui.ColCurrent, tui.ColCurrentMatch, true, true)
} else { } else {
t.window.CPrint(tui.ColCursor, t.strong, label)
if selected { if selected {
t.window.CPrint(tui.ColSelected, t.strong, ">") t.window.CPrint(tui.ColSelected, t.strong, ">")
} else { } else {
@@ -1023,25 +1028,26 @@ func (t *Terminal) printPreview() {
reader := bufio.NewReader(strings.NewReader(t.previewer.text)) reader := bufio.NewReader(strings.NewReader(t.previewer.text))
lineNo := -t.previewer.offset lineNo := -t.previewer.offset
height := t.pwindow.Height() height := t.pwindow.Height()
t.previewer.more = t.previewer.offset > 0
var ansi *ansiState var ansi *ansiState
for { for ; ; lineNo++ {
line, err := reader.ReadString('\n') line, err := reader.ReadString('\n')
eof := err == io.EOF eof := err == io.EOF
if !eof { if !eof {
line = line[:len(line)-1] line = line[:len(line)-1]
} }
lineNo++ if lineNo >= height || t.pwindow.Y() == height-1 && t.pwindow.X() > 0 {
if lineNo > height ||
t.pwindow.Y() == height-1 && t.pwindow.X() > 0 {
break break
} else if lineNo > 0 { } else if lineNo >= 0 {
var fillRet tui.FillReturn var fillRet tui.FillReturn
prefixWidth := 0
_, _, ansi = extractColor(line, ansi, func(str string, ansi *ansiState) bool { _, _, ansi = extractColor(line, ansi, func(str string, ansi *ansiState) bool {
trimmed := []rune(str) trimmed := []rune(str)
if !t.preview.wrap { if !t.preview.wrap {
trimmed, _ = t.trimRight(trimmed, maxWidth-t.pwindow.X()) trimmed, _ = t.trimRight(trimmed, maxWidth-t.pwindow.X())
} }
str, _ = t.processTabs(trimmed, 0) str, width := t.processTabs(trimmed, prefixWidth)
prefixWidth += width
if t.theme != nil && ansi != nil && ansi.colored() { if t.theme != nil && ansi != nil && ansi.colored() {
fillRet = t.pwindow.CFill(ansi.fg, ansi.bg, ansi.attr, str) fillRet = t.pwindow.CFill(ansi.fg, ansi.bg, ansi.attr, str)
} else { } else {
@@ -1049,10 +1055,10 @@ func (t *Terminal) printPreview() {
} }
return fillRet == tui.FillContinue return fillRet == tui.FillContinue
}) })
switch fillRet { t.previewer.more = t.previewer.more || t.pwindow.Y() == height-1 && t.pwindow.X() > 0
case tui.FillNextLine: if fillRet == tui.FillNextLine {
continue continue
case tui.FillSuspend: } else if fillRet == tui.FillSuspend {
break break
} }
t.pwindow.Fill("\n") t.pwindow.Fill("\n")
@@ -1063,6 +1069,7 @@ func (t *Terminal) printPreview() {
} }
t.pwindow.FinishFill() t.pwindow.FinishFill()
if t.previewer.lines > height { if t.previewer.lines > height {
t.previewer.more = true
offset := fmt.Sprintf("%d/%d", t.previewer.offset+1, t.previewer.lines) offset := fmt.Sprintf("%d/%d", t.previewer.offset+1, t.previewer.lines)
pos := t.pwindow.Width() - len(offset) pos := t.pwindow.Width() - len(offset)
if t.tui.DoesAutoWrap() { if t.tui.DoesAutoWrap() {
@@ -1098,6 +1105,7 @@ func (t *Terminal) printAll() {
} }
func (t *Terminal) refresh() { func (t *Terminal) refresh() {
t.placeCursor()
if !t.suppress { if !t.suppress {
windows := make([]tui.Window, 0, 4) windows := make([]tui.Window, 0, 4)
if t.bordered { if t.bordered {
@@ -1196,6 +1204,9 @@ func parsePlaceholder(match string) (bool, string, placeholderFlags) {
case 's': case 's':
flags.preserveSpace = true flags.preserveSpace = true
skipChars++ skipChars++
case 'n':
flags.number = true
skipChars++
case 'q': case 'q':
flags.query = true flags.query = true
default: default:
@@ -1251,7 +1262,16 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, fo
if match == "{}" { if match == "{}" {
for idx, item := range items { for idx, item := range items {
replacements[idx] = quoteEntry(item.AsString(stripAnsi)) if flags.number {
n := int(item.text.Index)
if n < 0 {
replacements[idx] = ""
} else {
replacements[idx] = strconv.Itoa(n)
}
} else {
replacements[idx] = quoteEntry(item.AsString(stripAnsi))
}
} }
return strings.Join(replacements, " ") return strings.Join(replacements, " ")
} }
@@ -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. // 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. // To do so, we pass an empty Item instead of nil to trigger an update.
if current == nil { if current == nil {
current = &Item{} current = &minItem
} }
var sels []*Item var sels []*Item
@@ -1433,7 +1453,6 @@ func (t *Terminal) Loop() {
t.printPrompt() t.printPrompt()
t.printInfo() t.printInfo()
t.printHeader() t.printHeader()
t.placeCursor()
t.refresh() t.refresh()
t.mutex.Unlock() t.mutex.Unlock()
go func() { go func() {
@@ -1477,8 +1496,12 @@ func (t *Terminal) Loop() {
cmd := util.ExecCommand(command, true) cmd := util.ExecCommand(command, true)
if t.pwindow != nil { if t.pwindow != nil {
env := os.Environ() env := os.Environ()
env = append(env, fmt.Sprintf("LINES=%d", t.pwindow.Height())) lines := fmt.Sprintf("LINES=%d", t.pwindow.Height())
env = append(env, fmt.Sprintf("COLUMNS=%d", t.pwindow.Width())) columns := fmt.Sprintf("COLUMNS=%d", t.pwindow.Width())
env = append(env, lines)
env = append(env, "FZF_PREVIEW_"+lines)
env = append(env, columns)
env = append(env, "FZF_PREVIEW_"+columns)
cmd.Env = env cmd.Env = env
} }
var out bytes.Buffer var out bytes.Buffer
@@ -1532,8 +1555,8 @@ func (t *Terminal) Loop() {
} }
go func() { go func() {
var focused *Item var focusedIndex int32 = minItem.Index()
var version int64 var version int64 = -1
for { for {
t.reqBox.Wait(func(events *util.Events) { t.reqBox.Wait(func(events *util.Events) {
defer events.Clear() defer events.Clear()
@@ -1549,10 +1572,14 @@ func (t *Terminal) Loop() {
t.printInfo() t.printInfo()
case reqList: case reqList:
t.printList() t.printList()
currentFocus := t.currentItem() var currentIndex int32 = minItem.Index()
if currentFocus != focused || version != t.version { currentItem := t.currentItem()
if currentItem != nil {
currentIndex = currentItem.Index()
}
if focusedIndex != currentIndex || version != t.version {
version = t.version version = t.version
focused = currentFocus focusedIndex = currentIndex
if t.isPreviewEnabled() { if t.isPreviewEnabled() {
_, list := t.buildPlusList(t.preview.command, false) _, list := t.buildPlusList(t.preview.command, false)
t.cancelPreview() t.cancelPreview()
@@ -1596,7 +1623,6 @@ func (t *Terminal) Loop() {
exit(func() int { return exitInterrupt }) exit(func() int { return exitInterrupt })
} }
} }
t.placeCursor()
t.refresh() t.refresh()
t.mutex.Unlock() t.mutex.Unlock()
}) })
@@ -1609,7 +1635,8 @@ func (t *Terminal) Loop() {
t.mutex.Lock() t.mutex.Lock()
previousInput := t.input previousInput := t.input
events := []util.EventType{reqPrompt} previousCx := t.cx
events := []util.EventType{}
req := func(evts ...util.EventType) { req := func(evts ...util.EventType) {
for _, event := range evts { for _, event := range evts {
events = append(events, event) events = append(events, event)
@@ -1625,9 +1652,15 @@ func (t *Terminal) Loop() {
} }
} }
scrollPreview := func(amount int) { scrollPreview := func(amount int) {
t.previewer.offset = util.Constrain( if !t.previewer.more {
return
}
newOffset := util.Constrain(
t.previewer.offset+amount, 0, t.previewer.lines-1) t.previewer.offset+amount, 0, t.previewer.lines-1)
req(reqPreviewRefresh) if t.previewer.offset != newOffset {
t.previewer.offset = newOffset
req(reqPreviewRefresh)
}
} }
for key, ret := range t.expect { for key, ret := range t.expect {
if keyMatch(key, event) { if keyMatch(key, event) {
@@ -1669,7 +1702,7 @@ func (t *Terminal) Loop() {
t.previewBox.Set(reqPreviewEnqueue, list) t.previewBox.Set(reqPreviewEnqueue, list)
} }
} }
req(reqList, reqInfo, reqHeader) req(reqPrompt, reqList, reqInfo, reqHeader)
} }
case actTogglePreviewWrap: case actTogglePreviewWrap:
if t.hasPreviewWindow() { if t.hasPreviewWindow() {
@@ -1970,7 +2003,6 @@ func (t *Terminal) Loop() {
t.jumping = jumpDisabled t.jumping = jumpDisabled
req(reqList) req(reqList)
} }
t.mutex.Unlock() // Must be unlocked before touching reqBox
if changed { if changed {
if t.isPreviewEnabled() { if t.isPreviewEnabled() {
@@ -1979,6 +2011,15 @@ func (t *Terminal) Loop() {
t.version++ 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) t.eventBox.Set(EvtSearchNew, t.sort)
} }
for _, event := range events { for _, event := range events {

View File

@@ -163,9 +163,9 @@ func (r *LightRenderer) findOffset() (row int, col int) {
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 ""
} }
@@ -676,7 +676,7 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, bord
} }
func (w *LightWindow) drawBorder() { func (w *LightWindow) drawBorder() {
switch w.border { switch w.border.shape {
case BorderAround: case BorderAround:
w.drawBorderAround() w.drawBorderAround()
case BorderHorizontal: case BorderHorizontal:
@@ -686,22 +686,24 @@ func (w *LightWindow) drawBorder() {
func (w *LightWindow) drawBorderHorizontal() { func (w *LightWindow) drawBorderHorizontal() {
w.Move(0, 0) w.Move(0, 0)
w.CPrint(ColBorder, AttrRegular, repeat("─", w.width)) w.CPrint(ColBorder, AttrRegular, repeat(w.border.horizontal, w.width))
w.Move(w.height-1, 0) w.Move(w.height-1, 0)
w.CPrint(ColBorder, AttrRegular, repeat("─", w.width)) w.CPrint(ColBorder, AttrRegular, repeat(w.border.horizontal, w.width))
} }
func (w *LightWindow) drawBorderAround() { func (w *LightWindow) drawBorderAround() {
w.Move(0, 0) w.Move(0, 0)
w.CPrint(ColBorder, AttrRegular, "┌"+repeat("─", w.width-2)+"┐") w.CPrint(ColBorder, AttrRegular,
string(w.border.topLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.topRight))
for y := 1; y < w.height-1; y++ { for y := 1; y < w.height-1; y++ {
w.Move(y, 0) w.Move(y, 0)
w.CPrint(ColBorder, AttrRegular, "│") w.CPrint(ColBorder, AttrRegular, string(w.border.vertical))
w.cprint2(colDefault, w.bg, AttrRegular, repeat(" ", w.width-2)) w.cprint2(colDefault, w.bg, AttrRegular, repeat(' ', w.width-2))
w.CPrint(ColBorder, AttrRegular, "│") w.CPrint(ColBorder, AttrRegular, string(w.border.vertical))
} }
w.Move(w.height-1, 0) w.Move(w.height-1, 0)
w.CPrint(ColBorder, AttrRegular, "└"+repeat("─", w.width-2)+"┘") w.CPrint(ColBorder, AttrRegular,
string(w.border.bottomLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.bottomRight))
} }
func (w *LightWindow) csi(code string) { func (w *LightWindow) csi(code string) {
@@ -762,7 +764,7 @@ func (w *LightWindow) MoveAndClear(y int, x int) {
w.Move(y, x) w.Move(y, x)
// We should not delete preview window on the right // We should not delete preview window on the right
// csi("K") // csi("K")
w.Print(repeat(" ", w.width-x)) w.Print(repeat(' ', w.width-x))
w.Move(y, x) w.Move(y, x)
} }
@@ -858,7 +860,7 @@ func wrapLine(input string, prefixLength int, max int, tabstop int) []wrappedLin
width += w width += w
str := string(r) str := string(r)
if r == '\t' { if r == '\t' {
str = repeat(" ", w) str = repeat(' ', w)
} }
if prefixLength+width <= max { if prefixLength+width <= max {
line += str line += str

View File

@@ -61,12 +61,8 @@ func (w *TcellWindow) Refresh() {
} }
w.lastX = 0 w.lastX = 0
w.lastY = 0 w.lastY = 0
switch w.borderStyle {
case BorderAround: w.drawBorder()
w.drawBorder(true)
case BorderHorizontal:
w.drawBorder(false)
}
} }
func (w *TcellWindow) FinishFill() { func (w *TcellWindow) FinishFill() {
@@ -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) 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
@@ -584,19 +584,19 @@ func (w *TcellWindow) drawBorder(around bool) {
} }
for x := left; x < right; x++ { for x := left; x < right; x++ {
_screen.SetContent(x, top, tcell.RuneHLine, nil, style) _screen.SetContent(x, top, w.borderStyle.horizontal, nil, style)
_screen.SetContent(x, bot-1, tcell.RuneHLine, nil, style) _screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style)
} }
if around { if w.borderStyle.shape == BorderAround {
for y := top; y < bot; y++ { for y := top; y < bot; y++ {
_screen.SetContent(left, y, tcell.RuneVLine, nil, style) _screen.SetContent(left, y, w.borderStyle.vertical, nil, style)
_screen.SetContent(right-1, y, tcell.RuneVLine, nil, style) _screen.SetContent(right-1, y, w.borderStyle.vertical, nil, style)
} }
_screen.SetContent(left, top, tcell.RuneULCorner, nil, style) _screen.SetContent(left, top, w.borderStyle.topLeft, nil, style)
_screen.SetContent(right-1, top, tcell.RuneURCorner, nil, style) _screen.SetContent(right-1, top, w.borderStyle.topRight, nil, style)
_screen.SetContent(left, bot-1, tcell.RuneLLCorner, nil, style) _screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style)
_screen.SetContent(right-1, bot-1, tcell.RuneLRCorner, nil, style) _screen.SetContent(right-1, bot-1, w.borderStyle.bottomRight, nil, style)
} }
} }

View File

@@ -172,6 +172,7 @@ type ColorTheme struct {
Fg Color Fg Color
Bg Color Bg Color
DarkBg Color DarkBg Color
Gutter Color
Prompt Color Prompt Color
Match Color Match Color
Current Color Current Color
@@ -200,14 +201,47 @@ type MouseEvent struct {
Mod bool Mod bool
} }
type BorderStyle int type BorderShape int
const ( const (
BorderNone BorderStyle = iota BorderNone BorderShape = iota
BorderAround BorderAround
BorderHorizontal BorderHorizontal
) )
type BorderStyle struct {
shape BorderShape
horizontal rune
vertical rune
topLeft rune
topRight rune
bottomLeft rune
bottomRight rune
}
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
if unicode {
return BorderStyle{
shape: shape,
horizontal: '─',
vertical: '│',
topLeft: '┌',
topRight: '┐',
bottomLeft: '└',
bottomRight: '┘',
}
}
return BorderStyle{
shape: shape,
horizontal: '-',
vertical: '|',
topLeft: '+',
topRight: '+',
bottomLeft: '+',
bottomRight: '+',
}
}
type Renderer interface { type Renderer interface {
Init() Init()
Pause(clear bool) Pause(clear bool)
@@ -272,17 +306,19 @@ var (
Dark256 *ColorTheme Dark256 *ColorTheme
Light256 *ColorTheme Light256 *ColorTheme
ColNormal ColorPair ColPrompt ColorPair
ColPrompt ColorPair ColNormal ColorPair
ColMatch ColorPair ColMatch ColorPair
ColCurrent ColorPair ColCursor ColorPair
ColCurrentMatch ColorPair ColSelected ColorPair
ColSpinner ColorPair ColCurrent ColorPair
ColInfo ColorPair ColCurrentMatch ColorPair
ColCursor ColorPair ColCurrentCursor ColorPair
ColSelected ColorPair ColCurrentSelected ColorPair
ColHeader ColorPair ColSpinner ColorPair
ColBorder ColorPair ColInfo ColorPair
ColHeader ColorPair
ColBorder ColorPair
) )
func EmptyTheme() *ColorTheme { func EmptyTheme() *ColorTheme {
@@ -290,6 +326,7 @@ func EmptyTheme() *ColorTheme {
Fg: colUndefined, Fg: colUndefined,
Bg: colUndefined, Bg: colUndefined,
DarkBg: colUndefined, DarkBg: colUndefined,
Gutter: colUndefined,
Prompt: colUndefined, Prompt: colUndefined,
Match: colUndefined, Match: colUndefined,
Current: colUndefined, Current: colUndefined,
@@ -312,6 +349,7 @@ func init() {
Fg: colDefault, Fg: colDefault,
Bg: colDefault, Bg: colDefault,
DarkBg: colBlack, DarkBg: colBlack,
Gutter: colBlack,
Prompt: colBlue, Prompt: colBlue,
Match: colGreen, Match: colGreen,
Current: colYellow, Current: colYellow,
@@ -326,6 +364,7 @@ func init() {
Fg: colDefault, Fg: colDefault,
Bg: colDefault, Bg: colDefault,
DarkBg: 236, DarkBg: 236,
Gutter: colUndefined,
Prompt: 110, Prompt: 110,
Match: 108, Match: 108,
Current: 254, Current: 254,
@@ -340,6 +379,7 @@ func init() {
Fg: colDefault, Fg: colDefault,
Bg: colDefault, Bg: colDefault,
DarkBg: 251, DarkBg: 251,
Gutter: colUndefined,
Prompt: 25, Prompt: 25,
Match: 66, Match: 66,
Current: 237, Current: 237,
@@ -371,6 +411,7 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
theme.Fg = o(baseTheme.Fg, theme.Fg) theme.Fg = o(baseTheme.Fg, theme.Fg)
theme.Bg = o(baseTheme.Bg, theme.Bg) theme.Bg = o(baseTheme.Bg, theme.Bg)
theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg) theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg)
theme.Gutter = o(theme.DarkBg, o(baseTheme.Gutter, theme.Gutter))
theme.Prompt = o(baseTheme.Prompt, theme.Prompt) theme.Prompt = o(baseTheme.Prompt, theme.Prompt)
theme.Match = o(baseTheme.Match, theme.Match) theme.Match = o(baseTheme.Match, theme.Match)
theme.Current = o(baseTheme.Current, theme.Current) theme.Current = o(baseTheme.Current, theme.Current)
@@ -392,27 +433,31 @@ func initPalette(theme *ColorTheme) {
return ColorPair{fg, bg, idx} return ColorPair{fg, bg, idx}
} }
if theme != nil { if theme != nil {
ColNormal = pair(theme.Fg, theme.Bg)
ColPrompt = pair(theme.Prompt, theme.Bg) ColPrompt = pair(theme.Prompt, theme.Bg)
ColNormal = pair(theme.Fg, theme.Bg)
ColMatch = pair(theme.Match, theme.Bg) ColMatch = pair(theme.Match, theme.Bg)
ColCursor = pair(theme.Cursor, theme.Gutter)
ColSelected = pair(theme.Selected, theme.Gutter)
ColCurrent = pair(theme.Current, theme.DarkBg) ColCurrent = pair(theme.Current, theme.DarkBg)
ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg) ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg)
ColCurrentCursor = pair(theme.Cursor, theme.DarkBg)
ColCurrentSelected = pair(theme.Selected, theme.DarkBg)
ColSpinner = pair(theme.Spinner, theme.Bg) ColSpinner = pair(theme.Spinner, theme.Bg)
ColInfo = pair(theme.Info, theme.Bg) ColInfo = pair(theme.Info, theme.Bg)
ColCursor = pair(theme.Cursor, theme.DarkBg)
ColSelected = pair(theme.Selected, theme.DarkBg)
ColHeader = pair(theme.Header, theme.Bg) ColHeader = pair(theme.Header, theme.Bg)
ColBorder = pair(theme.Border, theme.Bg) ColBorder = pair(theme.Border, theme.Bg)
} else { } else {
ColNormal = pair(colDefault, colDefault)
ColPrompt = pair(colDefault, colDefault) ColPrompt = pair(colDefault, colDefault)
ColNormal = pair(colDefault, colDefault)
ColMatch = pair(colDefault, colDefault) ColMatch = pair(colDefault, colDefault)
ColCurrent = pair(colDefault, colDefault)
ColCurrentMatch = pair(colDefault, colDefault)
ColSpinner = pair(colDefault, colDefault)
ColInfo = pair(colDefault, colDefault)
ColCursor = pair(colDefault, colDefault) ColCursor = pair(colDefault, colDefault)
ColSelected = pair(colDefault, colDefault) ColSelected = pair(colDefault, colDefault)
ColCurrent = pair(colDefault, colDefault)
ColCurrentMatch = pair(colDefault, colDefault)
ColCurrentCursor = pair(colDefault, colDefault)
ColCurrentSelected = pair(colDefault, colDefault)
ColSpinner = pair(colDefault, colDefault)
ColInfo = pair(colDefault, colDefault)
ColHeader = pair(colDefault, colDefault) ColHeader = pair(colDefault, colDefault)
ColBorder = pair(colDefault, colDefault) ColBorder = pair(colDefault, colDefault)
} }

View File

@@ -171,3 +171,12 @@ func (chars *Chars) CopyRunes(dest []rune) {
} }
return return
} }
func (chars *Chars) Prepend(prefix string) {
if runes := chars.optionalRunes(); runes != nil {
runes = append([]rune(prefix), runes...)
chars.slice = *(*[]byte)(unsafe.Pointer(&runes))
} else {
chars.slice = append([]byte(prefix), chars.slice...)
}
}

View File

@@ -1371,7 +1371,7 @@ class TestGoFZF < TestBase
end end
def test_preview_hidden def test_preview_hidden
tmux.send_keys %(seq 1000 | #{FZF} --preview 'echo {{}-{}-\\$LINES-\\$COLUMNS}' --preview-window down:1:hidden --bind ?:toggle-preview), :Enter tmux.send_keys %(seq 1000 | #{FZF} --preview 'echo {{}-{}-\\$FZF_PREVIEW_LINES-\\$FZF_PREVIEW_COLUMNS}' --preview-window down:1:hidden --bind ?:toggle-preview), :Enter
tmux.until { |lines| lines[-1] == '>' } tmux.until { |lines| lines[-1] == '>' }
tmux.send_keys '?' tmux.send_keys '?'
tmux.until { |lines| lines[-2] =~ / {1-1-1-[0-9]+}/ } tmux.until { |lines| lines[-2] =~ / {1-1-1-[0-9]+}/ }
@@ -1400,21 +1400,21 @@ class TestGoFZF < TestBase
def test_preview_flags def test_preview_flags
tmux.send_keys %(seq 10 | sed 's/^/:: /; s/$/ /' | tmux.send_keys %(seq 10 | sed 's/^/:: /; s/$/ /' |
#{FZF} --multi --preview 'echo {{2}/{s2}/{+2}/{+s2}/{q}}'), :Enter #{FZF} --multi --preview 'echo {{2}/{s2}/{+2}/{+s2}/{q}/{n}/{+n}}'), :Enter
tmux.until { |lines| lines[1].include?('{1/1 /1/1 /}') } tmux.until { |lines| lines[1].include?('{1/1 /1/1 //0/0}') }
tmux.send_keys '123' 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.send_keys 'C-u', '1'
tmux.until { |lines| lines.match_count == 2 } 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.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.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.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.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 end
def test_preview_q_no_match def test_preview_q_no_match
@@ -1427,6 +1427,12 @@ class TestGoFZF < TestBase
tmux.until { |lines| !lines[1].include?('foo') } tmux.until { |lines| !lines[1].include?('foo') }
end end
def test_preview_q_no_match_with_initial_query
tmux.send_keys %(: | #{FZF} --preview 'echo foo {q}{q}' --query foo), :Enter
tmux.until { |lines| lines.match_count == 0 }
tmux.until { |lines| lines[1].include?('foofoo') }
end
def test_no_clear def test_no_clear
tmux.send_keys "seq 10 | fzf --no-clear --inline-info --height 5 > #{tempname}", :Enter tmux.send_keys "seq 10 | fzf --no-clear --inline-info --height 5 > #{tempname}", :Enter
prompt = '> < 10/10' prompt = '> < 10/10'
@@ -1513,6 +1519,25 @@ class TestGoFZF < TestBase
assert_equal ['foo bar'], `#{FZF} -f'^foo\\ bar$' < #{tempname}`.lines.map(&:chomp) assert_equal ['foo bar'], `#{FZF} -f'^foo\\ bar$' < #{tempname}`.lines.map(&:chomp)
assert_equal input.lines.count - 1, `#{FZF} -f'!^foo\\ bar$' < #{tempname}`.lines.count assert_equal input.lines.count - 1, `#{FZF} -f'!^foo\\ bar$' < #{tempname}`.lines.count
end end
def test_inverse_only_search_should_not_sort_the_result
# Filter
assert_equal(%w[aaaaa b ccc],
`printf '%s\n' aaaaa b ccc BAD | #{FZF} -f '!bad'`.lines.map(&:chomp))
# Interactive
tmux.send_keys(%[printf '%s\n' aaaaa b ccc BAD | #{FZF} -q '!bad'], :Enter)
tmux.until { |lines| lines.item_count == 4 && lines.match_count == 3 }
tmux.until { |lines| lines[-3] == '> aaaaa' }
tmux.until { |lines| lines[-4] == ' b' }
tmux.until { |lines| lines[-5] == ' ccc' }
end
def test_preview_correct_tab_width_after_ansi_reset_code
writelines tempname, ["\x1b[31m+\x1b[m\t\x1b[32mgreen"]
tmux.send_keys "#{FZF} --preview 'cat #{tempname}'", :Enter
tmux.until { |lines| lines[1].include?('+ green') }
end
end end
module TestShell module TestShell