Compare commits

...

43 Commits

Author SHA1 Message Date
Junegunn Choi
e3e76fa8c5 0.24.2 2020-11-03 23:32:24 +09:00
Junegunn Choi
2553806e79 Allow preview window height shorter than 3
Fix #2231
2020-11-03 22:04:01 +09:00
Junegunn Choi
1bcbc5a353 Fix regression where lines are skipped in the preview window
Fix #2239
2020-11-03 21:31:19 +09:00
Junegunn Choi
15d351b0f0 Use default bg color when fg is set to -1 with reverse attribute 2020-11-03 20:51:44 +09:00
Junegunn Choi
c144c95cda [vim] Set maxwidth and maxheight when creating a popup
For me, this fixes invalid popup size problem on Windows GVim
2020-10-31 22:27:58 +09:00
Junegunn Choi
f08f4fd87d [vim] Remove dead code 2020-10-31 22:21:06 +09:00
Junegunn Choi
f8aaeef218 Revert "Prefer LightRenderer on Windows if it's available"
This reverts commit 7915e365b3
due to https://github.com/junegunn/fzf.vim/issues/1152#issuecomment-719696495.
2020-10-31 02:53:10 +09:00
Junegunn Choi
7915e365b3 Prefer LightRenderer on Windows if it's available
Fix #1766
2020-10-31 01:41:57 +09:00
Junegunn Choi
1c68f81c37 [vim] See the last line of "fzf --version" output
The output may contain some unexpected warning messages from the shell
if it's not properly configured. While such extra messages should be
properly addressed by the user, we can ignore them by checking the
last line of the output instead of the first line.

Related: bd3a021ec1
2020-10-30 21:57:09 +09:00
Junegunn Choi
d4c9db0a27 0.24.1 2020-10-29 01:45:55 +09:00
Junegunn Choi
b5e0e29ec6 Assign default number version (without patch version)
So that you can still build and use fzf even when the precise version
number is not injected via -ldflags.
2020-10-29 01:38:34 +09:00
Junegunn Choi
569be4c6c9 [vim] Allow 'border': 'no' to be consistent with --color=no 2020-10-29 01:32:41 +09:00
Junegunn Choi
e7ca237b07 Fix nil error on --color=bw
Fix #2229
2020-10-29 01:27:08 +09:00
Junegunn Choi
a7d3b72117 Make build flags consistent 2020-10-28 18:39:39 +09:00
Junegunn Choi
3ba7b5cf2d Make Makefile fail when git information is not available 2020-10-28 18:33:22 +09:00
Junegunn Choi
254e9765fe [install] Pass version number to go get command
Related: https://github.com/junegunn/fzf.vim/issues/1150#issuecomment-717735149
2020-10-28 15:56:05 +09:00
Junegunn Choi
3304f284a5 Panic when fzf was built without version information
So that the package maintainers would immediately know that the build is
incorrect. But is there a way to make build simply fail?

Related: https://github.com/junegunn/fzf.vim/issues/1150
2020-10-28 10:51:32 +09:00
Junegunn Choi
0d5f862daf 0.24.0 2020-10-27 23:57:18 +09:00
Junegunn Choi
51dfacd542 Merge branch 'devel' into master 2020-10-27 23:57:03 +09:00
Junegunn Choi
c691d52fa7 Fix: barbled multibyte text(exe. Japanese). (#2224)
* Fix: barbled multibyte text(exe. Japanese).

* fixup

Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2020-10-27 23:55:10 +09:00
Junegunn Choi
de3d09fe79 fixup 2020-10-27 23:53:25 +09:00
Junegunn Choi
eaa413c566 Fix error when preview command failed to start 2020-10-27 21:36:38 +09:00
nekowasabi
407205e52b Fix: barbled multibyte text(exe. Japanese). 2020-10-27 17:16:47 +09:00
Junegunn Choi
552414978e 0.24.0-rc1 2020-10-27 11:07:27 +09:00
Junegunn Choi
607081bbaa [vim] Download latest binary to meet version requirement 2020-10-27 01:01:58 +09:00
Junegunn Choi
e73383fbbb [vim] Add 'none' option for popup border 2020-10-26 23:41:20 +09:00
Junegunn Choi
2e8e63fb0b Add more --border options
Instead of drawing the window border in Vim using an extra window,
extend the --border option so that we do can it natively.

Close #2223
Fix #2184
2020-10-26 22:51:22 +09:00
Junegunn Choi
874f7dd416 Update --color example in CHANGELOG
New color name: input
2020-10-26 00:22:06 +09:00
Junegunn Choi
8b0e3b1624 Update --color docs 2020-10-26 00:15:30 +09:00
Junegunn Choi
9b946f2b7a Fix preview window of tcell renderer 2020-10-25 21:43:53 +09:00
Junegunn Choi
11841f688b Add support for text styling using --color
Close #1663
2020-10-25 19:30:41 +09:00
Junegunn Choi
03c4f04246 Use 64-bit integer for preview version 2020-10-24 16:55:55 +09:00
Junegunn Choi
a1f06ae27f Fix regression where empty preview content is not displayed 2020-10-23 23:52:05 +09:00
Junegunn Choi
69dffd78a6 Do not assume that each character takes at least 1 column
Fixes #2163, though this is not a proper fix to the problem.
2020-10-23 23:32:10 +09:00
Junegunn Choi
2750e19657 Update go-runewidth 2020-10-23 23:31:46 +09:00
Junegunn Choi
b0987f727b Clarify that additional installation steps may be required
Close #2211
2020-10-23 22:52:46 +09:00
Junegunn Choi
a4d9b0b468 Support ANSI escape sequence for clearing display in preview window
fzf --preview 'for i in $(seq 100000); do
    (( i % 200 == 0 )) && printf "\033[2J"
    echo "$i"
    sleep 0.01
  done'
2020-10-23 21:37:20 +09:00
Junegunn Choi
e2b87e0d74 Fix Travis CI build 2020-10-23 20:44:04 +09:00
Junegunn Choi
2166b4ca17 Fix test cases
We were not properly waiting for truthy-ness in until blocks.

Need Minitest with 21d9e804b6
2020-10-23 19:54:45 +09:00
Junegunn Choi
d2d4d68585 Always show the number of selected entries to indicate if --multi is enabled
Close #2217

  seq 100 | fzf
    # 100/100
  seq 100 | fzf --multi
    # 100/100 (0)
  seq 100 | fzf --multi 5
    # 100/100 (0/5)
2020-10-20 20:04:06 +09:00
Junegunn Choi
faf68dbc5c Implement streaming preview window (#2215)
Fix #2212

    # Will start rendering after 200ms, update every 100ms
    fzf --preview 'for i in $(seq 100); do echo $i; sleep 0.01; done'

    # Should print "Loading .." message after 500ms
    fzf --preview 'sleep 1; for i in $(seq 100); do echo $i; sleep 0.01; done'

    # The first line should appear after 200ms
    fzf --preview 'date; sleep 2; date'

    # Should not render before enough lines for the scroll offset are ready
    rg --line-number --no-heading --color=always ^ |
      fzf --delimiter : --ansi --preview-window '+{2}-/2' \
          --preview 'sleep 1; bat --style=numbers --color=always --pager=never --highlight-line={2} {1}'
2020-10-18 17:03:33 +09:00
Junegunn Choi
305896fcb3 README-VIM: g:fzf_action doesn't work with custom sink
Fix https://github.com/junegunn/fzf.vim/issues/1131
2020-10-18 13:23:40 +09:00
Andrew Zhou
6c9adea0d3 [fish] Fix parser handling of option-like args (#2208)
Fixes error when option-like args are parsed (e.g. "-1").
2020-10-12 12:58:37 +09:00
34 changed files with 1362 additions and 805 deletions

3
.gitignore vendored
View File

@@ -1,5 +1,6 @@
bin/fzf bin/fzf
bin/fzf.exe bin/fzf.exe
dist
target target
pkg pkg
Gemfile.lock Gemfile.lock
@@ -9,3 +10,5 @@ vendor
gopath gopath
*.zwc *.zwc
fzf fzf
tmp
*.patch

11
.gon.hcl Normal file
View File

@@ -0,0 +1,11 @@
source = ["./dist/fzf-macos_darwin_amd64/fzf"]
bundle_id = "kr.junegunn.fzf"
apple_id {
username = "junegunn.c@gmail.com"
password = "@env:AC_PASSWORD"
}
sign {
application_identity = "Apple Development: junegunn.c@gmail.com"
}

69
.goreleaser.yml Normal file
View File

@@ -0,0 +1,69 @@
---
project_name: fzf
before:
hooks:
- go mod download
builds:
- id: fzf-macos
binary: fzf
goos:
- darwin
goarch:
- amd64
ldflags:
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
hooks:
post: gon .gon.hcl
- goos:
- linux
- windows
- freebsd
- openbsd
goarch:
- amd64
- arm
- arm64
goarm:
- 5
- 6
- 7
ldflags:
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
ignore:
- goos: freebsd
goarch: arm
- goos: openbsd
goarch: arm
- goos: freebsd
goarch: arm64
- goos: openbsd
goarch: arm64
archives:
- name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
format: tar.gz
format_overrides:
- goos: windows
format: zip
files:
- non-existent*
release:
github:
owner: junegunn
name: fzf
prerelease: auto
name_template: '{{ .Tag }}'
snapshot:
name_template: "{{ .Tag }}-devel"
changelog:
sort: asc
filters:
exclude:
- README
- test

View File

@@ -20,5 +20,9 @@ Style/MethodCallWithArgsParentheses:
- ^refute_ - ^refute_
Style/NumericPredicate: Style/NumericPredicate:
Enabled: false Enabled: false
Style/StringConcatenation:
Enabled: false
Style/OptionalBooleanParameter:
Enabled: false
Style/WordArray: Style/WordArray:
MinSize: 1 MinSize: 1

View File

@@ -18,7 +18,7 @@ addons:
- fish - fish
- tmux - tmux
update: true update: true
install: gem install minitest rubocop rubocop-minitest rubocop-performance install: gem install --no-document minitest:5.14.2 rubocop:1.0.0 rubocop-minitest:0.10.1 rubocop-performance:1.8.1
script: script:
- make test - make test
# LC_ALL=C to avoid escape codes in # LC_ALL=C to avoid escape codes in

View File

@@ -17,21 +17,19 @@ make
# Build fzf binary and copy it to bin directory # Build fzf binary and copy it to bin directory
make install make install
# Build 32-bit and 64-bit executables and tarballs in target # Build fzf binaries and archives for all platforms using goreleaser
make build
# Publish GitHub release
make release make release
# Make release archives for all supported platforms in target
make release-all
``` ```
### Using `go get` > :warning: Makefile uses git commands to determine the version and the
> revision information for `fzf --version`. So if you're building fzf from an
Alternatively, you can build fzf directly with `go get` command without > environment where its git information is not available, you have to manually
manually cloning the repository. > set `$FZF_VERSION` and `$FZF_REVISION`.
>
```sh > e.g. `FZF_VERSION=0.24.0 FZF_REVISION=tarball make`
go get -u github.com/junegunn/fzf
```
Third-party libraries used Third-party libraries used
-------------------------- --------------------------

View File

@@ -1,6 +1,72 @@
CHANGELOG CHANGELOG
========= =========
0.24.2
------
- Bug fixes and improvements
0.24.1
------
- Fixed broken `--color=[bw|no]` option
0.24.0
------
- Real-time rendering of preview window
```sh
# fzf can render preview window before the command completes
fzf --preview 'sleep 1; for i in $(seq 100); do echo $i; sleep 0.01; done'
# Preview window can process ANSI escape sequence (CSI 2 J) for clearing the display
fzf --preview 'for i in $(seq 100000); do
(( i % 200 == 0 )) && printf "\033[2J"
echo "$i"
sleep 0.01
done'
```
- Updated `--color` option to support text styles
- `regular` / `bold` / `dim` / `underline` / `italic` / `reverse` / `blink`
```sh
# * Set -1 to keep the original color
# * Multiple style attributes can be combined
# * Italic style may not be supported by some terminals
rg --line-number --no-heading --color=always "" |
fzf --ansi --prompt "Rg: " \
--color fg+:italic,hl:underline:-1,hl+:italic:underline:reverse:-1 \
--color pointer:reverse,prompt:reverse,input:159 \
--pointer ' '
```
- More `--border` options
- `vertical`, `top`, `bottom`, `left`, `right`
- Updated Vim plugin to use these new `--border` options
```vim
" Floating popup window in the center of the screen
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
" Popup with 100% width
let g:fzf_layout = { 'window': { 'width': 1.0, 'height': 0.5, 'border': 'horizontal' } }
" Popup with 100% height
let g:fzf_layout = { 'window': { 'width': 0.5, 'height': 1.0, 'border': 'vertical' } }
" Similar to 'down' layout, but it uses a popup window and doesn't affect the window layout
let g:fzf_layout = { 'window': { 'width': 1.0, 'height': 0.5, 'yoffset': 1.0, 'border': 'top' } }
" Opens on the right;
" 'highlight' option is still supported but it will only take the foreground color of the group
let g:fzf_layout = { 'window': { 'width': 0.5, 'height': 1.0, 'xoffset': 1.0, 'border': 'left', 'highlight': 'Comment' } }
```
- To indicate if `--multi` mode is enabled, fzf will print the number of
selected items even when no item is selected
```sh
seq 100 | fzf
# 100/100
seq 100 | fzf --multi
# 100/100 (0)
seq 100 | fzf --multi 5
# 100/100 (0/5)
```
- Since 0.24.0, release binaries will be uploaded to https://github.com/junegunn/fzf/releases
0.23.1 0.23.1
------ ------
- Added `--preview-window` options for disabling flags - Added `--preview-window` options for disabling flags

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 gcc RUN pacman -Sy && pacman --noconfirm -S awk git tmux zsh fish ruby procps go make gcc
RUN gem install --no-document minitest RUN gem install --no-document -v 5.14.2 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

102
Makefile
View File

@@ -5,8 +5,26 @@ MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
ROOT_DIR := $(shell dirname $(MAKEFILE)) ROOT_DIR := $(shell dirname $(MAKEFILE))
SOURCES := $(wildcard *.go src/*.go src/*/*.go) $(MAKEFILE) SOURCES := $(wildcard *.go src/*.go src/*/*.go) $(MAKEFILE)
REVISION := $(shell git log -n 1 --pretty=format:%h -- $(SOURCES)) ifdef FZF_VERSION
BUILD_FLAGS := -a -ldflags "-X main.revision=$(REVISION) -w '-extldflags=$(LDFLAGS)'" -tags "$(TAGS)" VERSION := $(FZF_VERSION)
else
VERSION := $(shell git describe --abbrev=0 2> /dev/null)
endif
ifeq ($(VERSION),)
$(error Not on git repository; cannot determine $$FZF_VERSION)
endif
VERSION_TRIM := $(shell sed "s/-.*//" <<< $(VERSION))
VERSION_REGEX := $(subst .,\.,$(VERSION_TRIM))
ifdef FZF_REVISION
REVISION := $(FZF_REVISION)
else
REVISION := $(shell git log -n 1 --pretty=format:%h -- $(SOURCES) 2> /dev/null)
endif
ifeq ($(REVISION),)
$(error Not on git repository; cannot determine $$FZF_REVISION)
endif
BUILD_FLAGS := -a -ldflags "-s -w -X main.version=$(VERSION) -X main.revision=$(REVISION)" -tags "$(TAGS)"
BINARY64 := fzf-$(GOOS)_amd64 BINARY64 := fzf-$(GOOS)_amd64
BINARYARM5 := fzf-$(GOOS)_arm5 BINARYARM5 := fzf-$(GOOS)_arm5
@@ -14,13 +32,6 @@ BINARYARM6 := fzf-$(GOOS)_arm6
BINARYARM7 := fzf-$(GOOS)_arm7 BINARYARM7 := fzf-$(GOOS)_arm7
BINARYARM8 := fzf-$(GOOS)_arm8 BINARYARM8 := fzf-$(GOOS)_arm8
BINARYPPC64LE := fzf-$(GOOS)_ppc64le BINARYPPC64LE := fzf-$(GOOS)_ppc64le
VERSION := $(shell awk -F= '/version =/ {print $$2}' src/constants.go | tr -d "\" ")
RELEASE64 := fzf-$(VERSION)-$(GOOS)_amd64
RELEASEARM5 := fzf-$(VERSION)-$(GOOS)_arm5
RELEASEARM6 := fzf-$(VERSION)-$(GOOS)_arm6
RELEASEARM7 := fzf-$(VERSION)-$(GOOS)_arm7
RELEASEARM8 := fzf-$(VERSION)-$(GOOS)_arm8
RELEASEPPC64LE := fzf-$(VERSION)-$(GOOS)_ppc64le
# https://en.wikipedia.org/wiki/Uname # https://en.wikipedia.org/wiki/Uname
UNAME_M := $(shell uname -m) UNAME_M := $(shell uname -m)
@@ -41,40 +52,11 @@ else ifeq ($(UNAME_M),aarch64)
else ifeq ($(UNAME_M),ppc64le) else ifeq ($(UNAME_M),ppc64le)
BINARY := $(BINARYPPC64LE) BINARY := $(BINARYPPC64LE)
else else
$(error "Build on $(UNAME_M) is not supported, yet.") $(error Build on $(UNAME_M) is not supported, yet.)
endif endif
all: target/$(BINARY) all: target/$(BINARY)
target:
mkdir -p $@
ifeq ($(GOOS),windows)
release: target/$(BINARY64)
cd target && cp -f $(BINARY64) fzf.exe && zip $(RELEASE64).zip fzf.exe
cd target && rm -f fzf.exe
else ifeq ($(GOOS),linux)
release: target/$(BINARY64) target/$(BINARYARM5) target/$(BINARYARM6) target/$(BINARYARM7) target/$(BINARYARM8) target/$(BINARYPPC64LE)
cd target && cp -f $(BINARY64) fzf && tar -czf $(RELEASE64).tgz fzf
cd target && cp -f $(BINARYARM5) fzf && tar -czf $(RELEASEARM5).tgz fzf
cd target && cp -f $(BINARYARM6) fzf && tar -czf $(RELEASEARM6).tgz fzf
cd target && cp -f $(BINARYARM7) fzf && tar -czf $(RELEASEARM7).tgz fzf
cd target && cp -f $(BINARYARM8) fzf && tar -czf $(RELEASEARM8).tgz fzf
cd target && cp -f $(BINARYPPC64LE) fzf && tar -czf $(RELEASEPPC64LE).tgz fzf
cd target && rm -f fzf
else
release: target/$(BINARY64)
cd target && cp -f $(BINARY64) fzf && tar -czf $(RELEASE64).tgz fzf
cd target && rm -f fzf
endif
release-all: clean test
GOOS=darwin make release
GOOS=linux make release
GOOS=freebsd make release
GOOS=openbsd make release
GOOS=windows make release
test: $(SOURCES) test: $(SOURCES)
SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \ SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \
github.com/junegunn/fzf/src \ github.com/junegunn/fzf/src \
@@ -84,8 +66,46 @@ test: $(SOURCES)
install: bin/fzf install: bin/fzf
build:
goreleaser --rm-dist --snapshot
release:
ifndef GITHUB_TOKEN
$(error GITHUB_TOKEN is not defined)
endif
# Check if we are on master branch
ifneq ($(shell git symbolic-ref --short HEAD),master)
$(error Not on master branch)
endif
# Check if version numbers are properly updated
grep -q ^$(VERSION_REGEX)$$ CHANGELOG.md
grep -qF '"fzf $(VERSION_TRIM)"' man/man1/fzf.1
grep -qF '"fzf $(VERSION_TRIM)"' man/man1/fzf-tmux.1
grep -qF $(VERSION) install
grep -qF $(VERSION) install.ps1
# Make release note out of CHANGELOG.md
sed -n '/^$(VERSION_REGEX)$$/,/^[0-9]/p' CHANGELOG.md | tail -r | \
sed '1,/^ *$$/d' | tail -r | sed 1,2d | tee tmp/release-note
# Push to temp branch first so that install scripts always works on master branch
git checkout -B temp master
git push origin temp --follow-tags --force
# Make a GitHub release
goreleaser --rm-dist --release-notes tmp/release-note
# Push to master
git checkout master
git push origin master
# Delete temp branch
git push origin --delete temp
clean: clean:
$(RM) -r target $(RM) -r dist target
target/$(BINARY64): $(SOURCES) target/$(BINARY64): $(SOURCES)
GOARCH=amd64 $(GO) build $(BUILD_FLAGS) -o $@ GOARCH=amd64 $(GO) build $(BUILD_FLAGS) -o $@
@@ -121,4 +141,4 @@ update:
$(GO) get -u $(GO) get -u
$(GO) mod tidy $(GO) mod tidy
.PHONY: all release release-all test install clean docker docker-test update .PHONY: all build release test install clean docker docker-test update

View File

@@ -127,11 +127,13 @@ let g:fzf_action = {
\ 'ctrl-v': 'vsplit' } \ 'ctrl-v': 'vsplit' }
" Default fzf layout " Default fzf layout
" - down / up / left / right / window " - Popup window
let g:fzf_layout = { 'down': '40%' }
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } } let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
" You can set up fzf window using a Vim command (Neovim or latest Vim 8 required) " - down / up / left / right
let g:fzf_layout = { 'down': '40%' }
" - Window using a Vim command
let g:fzf_layout = { 'window': 'enew' } let g:fzf_layout = { 'window': 'enew' }
let g:fzf_layout = { 'window': '-tabnew' } let g:fzf_layout = { 'window': '-tabnew' }
let g:fzf_layout = { 'window': '10new' } let g:fzf_layout = { 'window': '10new' }
@@ -296,9 +298,8 @@ following options are allowed:
- Optional: - Optional:
- `yoffset` [float default 0.5 range [0 ~ 1]] - `yoffset` [float default 0.5 range [0 ~ 1]]
- `xoffset` [float default 0.5 range [0 ~ 1]] - `xoffset` [float default 0.5 range [0 ~ 1]]
- `highlight` [string default `'Comment'`]: Highlight group for border
- `border` [string default `rounded`]: Border style - `border` [string default `rounded`]: Border style
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right` - `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right` / `no[ne]`
`fzf#wrap` `fzf#wrap`
---------- ----------
@@ -332,8 +333,9 @@ After we *"wrap"* our spec, we pass it to `fzf#run`.
call fzf#run(fzf#wrap({'source': 'ls'})) call fzf#run(fzf#wrap({'source': 'ls'}))
``` ```
Now it supports `CTRL-T`, `CTRL-V`, and `CTRL-X` key bindings and it opens fzf Now it supports `CTRL-T`, `CTRL-V`, and `CTRL-X` key bindings (configurable
window according to `g:fzf_layout` setting. via `g:fzf_action`) and it opens fzf window according to `g:fzf_layout`
setting.
To make it easier to use, let's define `LS` command. To make it easier to use, let's define `LS` command.
@@ -370,6 +372,17 @@ command! -bang -complete=dir -nargs=* LS
\ call fzf#run(fzf#wrap('ls', {'source': 'ls', 'dir': <q-args>}, <bang>0)) \ call fzf#run(fzf#wrap('ls', {'source': 'ls', 'dir': <q-args>}, <bang>0))
``` ```
### Global options supported by `fzf#wrap`
- `g:fzf_layout`
- `g:fzf_action`
- **Works only when no custom `sink` (or `sink*`) is provided**
- Having custom sink usually means that each entry is not an ordinary
file path (e.g. name of color scheme), so we can't blindly apply the
same strategy (i.e. `tabedit some-color-scheme` doesn't make sense)
- `g:fzf_colors`
- `g:fzf_history_dir`
Tips Tips
---- ----

View File

@@ -82,7 +82,7 @@ fzf project consists of the following components:
You can [download fzf executable][bin] alone if you don't need the extra You can [download fzf executable][bin] alone if you don't need the extra
stuff. stuff.
[bin]: https://github.com/junegunn/fzf-bin/releases [bin]: https://github.com/junegunn/fzf/releases
### Using Homebrew or Linuxbrew ### Using Homebrew or Linuxbrew
@@ -125,9 +125,10 @@ git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
| XBPS | Void Linux | `sudo xbps-install -S fzf` | | XBPS | Void Linux | `sudo xbps-install -S fzf` |
| Zypper | openSUSE | `sudo zypper install fzf` | | Zypper | openSUSE | `sudo zypper install fzf` |
Shell extensions (key bindings and fuzzy auto-completion) and Vim/Neovim > :warning: **Key bindings (CTRL-T / CTRL-R / ALT-C) and fuzzy auto-completion
plugin may or may not be enabled by default depending on the package manager. > may not be enabled by default.**
Refer to the package documentation for more information. >
> Refer to the package documentation for more information. (e.g. `apt-cache show fzf`)
### Windows ### Windows
@@ -582,9 +583,9 @@ and fzf will warn you about it. To suppress the warning message, we added
### Preview window ### Preview window
When the `--preview` option is set, fzf automatically starts an external process When the `--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. with the current line as the argument and shows the result in the split window.
Your `$SHELL` is used to execute the command with `$SHELL -c COMMAND`. Your `$SHELL` is used to execute the command with `$SHELL -c COMMAND`.
The window can be scrolled using the mouse or custom key bindings. The window can be scrolled using the mouse or custom key bindings.
```bash ```bash
@@ -592,16 +593,8 @@ The window can be scrolled using the mouse or custom key bindings.
fzf --preview 'cat {}' fzf --preview 'cat {}'
``` ```
Since the preview window is updated only after the process is complete, it's
important that the command finishes quickly.
```bash
# Use head instead of cat so that the command doesn't take too long to finish
fzf --preview 'head -100 {}'
```
Preview window supports ANSI colors, so you can use any program that Preview window supports ANSI colors, so you can use any program that
syntax-highlights the content of a file, such as syntax-highlights the content of a file, such as
[Bat](https://github.com/sharkdp/bat) or [Bat](https://github.com/sharkdp/bat) or
[Highlight](http://www.andre-simon.de/doku/highlight/en/highlight.php): [Highlight](http://www.andre-simon.de/doku/highlight/en/highlight.php):

View File

@@ -1,4 +1,4 @@
fzf.txt fzf Last change: April 4 2020 fzf.txt fzf Last change: October 18 2020
FZF - TABLE OF CONTENTS *fzf* *fzf-toc* FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
============================================================================== ==============================================================================
@@ -11,6 +11,7 @@ FZF - TABLE OF CONTENTS *fzf* *fzf-to
Explanation of g:fzf_colors Explanation of g:fzf_colors
fzf#run fzf#run
fzf#wrap fzf#wrap
Global options supported by fzf#wrap
Tips Tips
fzf inside terminal buffer fzf inside terminal buffer
Starting fzf in a popup window Starting fzf in a popup window
@@ -105,14 +106,14 @@ the whole if we start off with `:FZF` command.
" Bang version starts fzf in fullscreen mode " Bang version starts fzf in fullscreen mode
:FZF! :FZF!
< <
Similarly to {ctrlp.vim}{2}, use enter key, CTRL-T, CTRL-X or CTRL-V to open Similarly to {ctrlp.vim}{3}, use enter key, CTRL-T, CTRL-X or CTRL-V to open
selected files in the current window, in new tabs, in horizontal splits, or in selected files in the current window, in new tabs, in horizontal splits, or in
vertical splits respectively. vertical splits respectively.
Note that the environment variables `FZF_DEFAULT_COMMAND` and Note that the environment variables `FZF_DEFAULT_COMMAND` and
`FZF_DEFAULT_OPTS` also apply here. `FZF_DEFAULT_OPTS` also apply here.
{2} https://github.com/kien/ctrlp.vim {3} https://github.com/kien/ctrlp.vim
< Configuration >_____________________________________________________________~ < Configuration >_____________________________________________________________~
@@ -154,11 +155,13 @@ Examples~
\ 'ctrl-v': 'vsplit' } \ 'ctrl-v': 'vsplit' }
" Default fzf layout " Default fzf layout
" - down / up / left / right / window " - Popup window
let g:fzf_layout = { 'down': '~40%' }
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } } let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
" You can set up fzf window using a Vim command (Neovim or latest Vim 8 required) " - down / up / left / right
let g:fzf_layout = { 'down': '40%' }
" - Window using a Vim command
let g:fzf_layout = { 'window': 'enew' } let g:fzf_layout = { 'window': 'enew' }
let g:fzf_layout = { 'window': '-tabnew' } let g:fzf_layout = { 'window': '-tabnew' }
let g:fzf_layout = { 'window': '10new' } let g:fzf_layout = { 'window': '10new' }
@@ -347,8 +350,8 @@ After we "wrap" our spec, we pass it to `fzf#run`.
> >
call fzf#run(fzf#wrap({'source': 'ls'})) call fzf#run(fzf#wrap({'source': 'ls'}))
< <
Now it supports CTRL-T, CTRL-V, and CTRL-X key bindings and it opens fzf Now it supports CTRL-T, CTRL-V, and CTRL-X key bindings (configurable via
window according to `g:fzf_layout` setting. `g:fzf_action`) and it opens fzf window according to `g:fzf_layout` setting.
To make it easier to use, let's define `LS` command. To make it easier to use, let's define `LS` command.
> >
@@ -378,6 +381,19 @@ unique name to our command and pass it as the first argument to `fzf#wrap`.
\ call fzf#run(fzf#wrap('ls', {'source': 'ls', 'dir': <q-args>}, <bang>0)) \ call fzf#run(fzf#wrap('ls', {'source': 'ls', 'dir': <q-args>}, <bang>0))
< <
< Global options supported by fzf#wrap >______________________________________~
*fzf-global-options-supported-by-fzf#wrap*
- `g:fzf_layout`
- `g:fzf_action`
- Works only when no custom `sink` (or `sink*`) is provided
- Having custom sink usually means that each entry is not an ordinary
file path (e.g. name of color scheme), so we can't blindly apply the
same strategy (i.e. `tabeditsome-color-scheme` doesn't make sense)
- `g:fzf_colors`
- `g:fzf_history_dir`
TIPS *fzf-tips* TIPS *fzf-tips*
============================================================================== ==============================================================================
@@ -398,8 +414,8 @@ Starting fzf in a popup window~
*fzf-starting-fzf-in-a-popup-window* *fzf-starting-fzf-in-a-popup-window*
> >
" Required: " Required:
" - width [float range [0 ~ 1]] " - width [float range [0 ~ 1]] or [integer range [8 ~ ]]
" - height [float range [0 ~ 1]] " - height [float range [0 ~ 1]] or [integer range [4 ~ ]]
" "
" Optional: " Optional:
" - xoffset [float default 0.5 range [0 ~ 1]] " - xoffset [float default 0.5 range [0 ~ 1]]
@@ -427,9 +443,8 @@ When fzf starts in a terminal buffer, the file type of the buffer is set to
`fzf`. So you can set up `FileTypefzf` autocmd to customize the settings of `fzf`. So you can set up `FileTypefzf` autocmd to customize the settings of
the window. the window.
For example, if you use a non-popup layout (e.g. `{'down':'40%'}`) on For example, if you use a non-popup layout (e.g. `{'down':'40%'}`) on Neovim,
Neovim, you might want to temporarily disable the statusline for a cleaner you might want to temporarily disable the statusline for a cleaner look.
look.
> >
if has('nvim') && !exists('g:fzf_layout') if has('nvim') && !exists('g:fzf_layout')
autocmd! FileType fzf autocmd! FileType fzf

14
go.mod
View File

@@ -2,14 +2,14 @@ module github.com/junegunn/fzf
require ( require (
github.com/gdamore/tcell v1.4.0 github.com/gdamore/tcell v1.4.0
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
github.com/mattn/go-isatty v0.0.12 github.com/mattn/go-isatty v0.0.12
github.com/mattn/go-runewidth v0.0.8 github.com/mattn/go-runewidth v0.0.9
github.com/mattn/go-shellwords v1.0.9 github.com/mattn/go-shellwords v1.0.10
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e github.com/saracen/walker v0.1.1
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect
golang.org/x/text v0.3.2 // indirect golang.org/x/sys v0.0.0-20201026173827-119d4633e4d1
golang.org/x/text v0.3.3 // indirect
) )
go 1.13 go 1.13

40
go.sum
View File

@@ -1,45 +1,35 @@
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU= github.com/gdamore/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU=
github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0= github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0=
github.com/lucasb-eyer/go-colorful v1.0.2 h1:mCMFu6PgSozg9tDNMMK3g18oJBX7oYGrC09mS6CXfO4=
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-shellwords v1.0.9 h1:eaB5JspOwiKKcHdqcjbfe5lA9cNn/4NRRtddXJCimqk= github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw=
github.com/mattn/go-shellwords v1.0.9/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e h1:NO86zOn5ScSKW8wRbMaSIcjDZUFpWdCQQnexRqZ9h9A= github.com/saracen/walker v0.1.1 h1:Ou2QIKTWqo0QxhtuHVmtObbmhjMCEUyJ82xp0uV+MGI=
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e/go.mod h1:G0Z6yVPru183i2MuRJx1DcR4dgIZtLcTdaaE/pC1BJU= github.com/saracen/walker v0.1.1/go.mod h1:0oKYMsKVhSJ+ful4p/XbjvXbMgLEkLITZaxozsl4CGE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10= golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0= golang.org/x/sys v0.0.0-20201026173827-119d4633e4d1 h1:/DtoiOYKoQCcIFXQjz07RnWNPRCbqmSXSpgEzhC9ZHM=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201026173827-119d4633e4d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191011211836-4c025a95b26e h1:1o2bDs9pCd2xFhdwqJTrCIswAeEsn4h/PCNelWpfcsI=
golang.org/x/tools v0.0.0-20191011211836-4c025a95b26e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

51
install
View File

@@ -2,11 +2,10 @@
set -u set -u
version=0.23.1 version=0.24.2
auto_completion= auto_completion=
key_bindings= key_bindings=
update_config=2 update_config=2
binary_arch=
shells="bash zsh fish" shells="bash zsh fish"
prefix='~/.fzf' prefix='~/.fzf'
prefix_expand=~/.fzf prefix_expand=~/.fzf
@@ -115,7 +114,7 @@ link_fzf_in_path() {
try_curl() { try_curl() {
command -v curl > /dev/null && command -v curl > /dev/null &&
if [[ $1 =~ tgz$ ]]; then if [[ $1 =~ tar.gz$ ]]; then
curl -fL $1 | tar -xzf - curl -fL $1 | tar -xzf -
else else
local temp=${TMPDIR:-/tmp}/fzf.zip local temp=${TMPDIR:-/tmp}/fzf.zip
@@ -125,7 +124,7 @@ try_curl() {
try_wget() { try_wget() {
command -v wget > /dev/null && command -v wget > /dev/null &&
if [[ $1 =~ tgz$ ]]; then if [[ $1 =~ tar.gz$ ]]; then
wget -O - $1 | tar -xzf - wget -O - $1 | tar -xzf -
else else
local temp=${TMPDIR:-/tmp}/fzf.zip local temp=${TMPDIR:-/tmp}/fzf.zip
@@ -135,13 +134,11 @@ try_wget() {
download() { download() {
echo "Downloading bin/fzf ..." echo "Downloading bin/fzf ..."
if [[ ! "$version" =~ alpha ]]; then if [ -x "$fzf_base"/bin/fzf ]; then
if [ -x "$fzf_base"/bin/fzf ]; then echo " - Already exists"
echo " - Already exists" check_binary && return
check_binary && return
fi
link_fzf_in_path && return
fi fi
link_fzf_in_path && return
mkdir -p "$fzf_base"/bin && cd "$fzf_base"/bin mkdir -p "$fzf_base"/bin && cd "$fzf_base"/bin
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
binary_error="Failed to create bin directory" binary_error="Failed to create bin directory"
@@ -149,9 +146,7 @@ download() {
fi fi
local url local url
[[ "$version" =~ alpha ]] && url=https://github.com/junegunn/fzf/releases/download/$version/${1}
url=https://github.com/junegunn/fzf-bin/releases/download/alpha/${1} ||
url=https://github.com/junegunn/fzf-bin/releases/download/$version/${1}
set -o pipefail set -o pipefail
if ! (try_curl $url || try_wget $url); then if ! (try_curl $url || try_wget $url); then
set +o pipefail set +o pipefail
@@ -173,20 +168,20 @@ archi=$(uname -sm)
binary_available=1 binary_available=1
binary_error="" binary_error=""
case "$archi" in case "$archi" in
Darwin\ *64) download fzf-$version-darwin_${binary_arch:-amd64}.tgz ;; Darwin\ *64) download fzf-$version-darwin_amd64.tar.gz ;;
Linux\ armv5*) download fzf-$version-linux_${binary_arch:-arm5}.tgz ;; Linux\ armv5*) download fzf-$version-linux_armv5.tar.gz ;;
Linux\ armv6*) download fzf-$version-linux_${binary_arch:-arm6}.tgz ;; Linux\ armv6*) download fzf-$version-linux_armv6.tar.gz ;;
Linux\ armv7*) download fzf-$version-linux_${binary_arch:-arm7}.tgz ;; Linux\ armv7*) download fzf-$version-linux_armv7.tar.gz ;;
Linux\ armv8*) download fzf-$version-linux_${binary_arch:-arm8}.tgz ;; Linux\ armv8*) download fzf-$version-linux_arm64.tar.gz ;;
Linux\ aarch64*) download fzf-$version-linux_${binary_arch:-arm8}.tgz ;; Linux\ aarch64*) download fzf-$version-linux_arm64.tar.gz ;;
Linux\ *64) download fzf-$version-linux_${binary_arch:-amd64}.tgz ;; Linux\ *64) download fzf-$version-linux_amd64.tar.gz ;;
FreeBSD\ *64) download fzf-$version-freebsd_${binary_arch:-amd64}.tgz ;; FreeBSD\ *64) download fzf-$version-freebsd_amd64.tar.gz ;;
OpenBSD\ *64) download fzf-$version-openbsd_${binary_arch:-amd64}.tgz ;; OpenBSD\ *64) download fzf-$version-openbsd_amd64.tar.gz ;;
CYGWIN*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;; CYGWIN*\ *64) download fzf-$version-windows_amd64.zip ;;
MINGW*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;; MINGW*\ *64) download fzf-$version-windows_amd64.zip ;;
MSYS*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;; MSYS*\ *64) download fzf-$version-windows_amd64.zip ;;
Windows*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;; Windows*\ *64) download fzf-$version-windows_amd64.zip ;;
*) binary_available=0 binary_error=1 ;; *) binary_available=0 binary_error=1 ;;
esac esac
cd "$fzf_base" cd "$fzf_base"
@@ -202,7 +197,7 @@ if [ -n "$binary_error" ]; then
export GOPATH="${TMPDIR:-/tmp}/fzf-gopath" export GOPATH="${TMPDIR:-/tmp}/fzf-gopath"
mkdir -p "$GOPATH" mkdir -p "$GOPATH"
fi fi
if go get -u github.com/junegunn/fzf; then if go get -ldflags "-s -w -X main.version=$version -X main.revision=go-get" github.com/junegunn/fzf; then
echo "OK" echo "OK"
cp "$GOPATH/bin/fzf" "$fzf_base/bin/" cp "$GOPATH/bin/fzf" "$fzf_base/bin/"
else else

View File

@@ -1,10 +1,4 @@
$version="0.23.1" $version="0.24.2"
if ([Environment]::Is64BitProcess) {
$binary_arch="amd64"
} else {
$binary_arch="386"
}
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition $fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
@@ -32,12 +26,10 @@ function check_binary () {
function download { function download {
param($file) param($file)
Write-Host "Downloading bin/fzf ..." Write-Host "Downloading bin/fzf ..."
if ("$version" -ne "alpha") { if (Test-Path "$fzf_base\bin\fzf.exe") {
if (Test-Path "$fzf_base\bin\fzf.exe") { Write-Host " - Already exists"
Write-Host " - Already exists" if (check_binary) {
if (check_binary) { return
return
}
} }
} }
if (-not (Test-Path "$fzf_base\bin")) { if (-not (Test-Path "$fzf_base\bin")) {
@@ -48,11 +40,7 @@ function download {
return return
} }
cd "$fzf_base\bin" cd "$fzf_base\bin"
if ("$version" -eq "alpha") { $url="https://github.com/junegunn/fzf/releases/download/$version/$file"
$url="https://github.com/junegunn/fzf-bin/releases/download/alpha/$file"
} else {
$url="https://github.com/junegunn/fzf-bin/releases/download/$version/$file"
}
$temp=$env:TMP + "\fzf.zip" $temp=$env:TMP + "\fzf.zip"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
(New-Object Net.WebClient).DownloadFile($url, $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$temp")) (New-Object Net.WebClient).DownloadFile($url, $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$temp"))
@@ -68,6 +56,6 @@ function download {
check_binary >$null check_binary >$null
} }
download "fzf-$version-windows_$binary_arch.zip" download "fzf-$version-windows_amd64.zip"
Write-Host 'For more information, see: https://github.com/junegunn/fzf' Write-Host 'For more information, see: https://github.com/junegunn/fzf'

View File

@@ -5,9 +5,10 @@ import (
"github.com/junegunn/fzf/src/protector" "github.com/junegunn/fzf/src/protector"
) )
var revision string var version string = "0.24"
var revision string = "devel"
func main() { func main() {
protector.Protect() protector.Protect()
fzf.Run(fzf.ParseOptions(), revision) fzf.Run(fzf.ParseOptions(), version, revision)
} }

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 2020" "fzf 0.23.1" "fzf-tmux - open fzf in tmux split pane" .TH fzf-tmux 1 "Nov 2020" "fzf 0.24.2" "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 2020" "fzf 0.23.1" "fzf - a command-line fuzzy finder" .TH fzf 1 "Nov 2020" "fzf 0.24.2" "fzf - a command-line fuzzy finder"
.SH NAME .SH NAME
fzf - a command-line fuzzy finder fzf - a command-line fuzzy finder
@@ -192,6 +192,16 @@ Draw border around the finder
.br .br
.BR horizontal " Horizontal lines above and below the finder" .BR horizontal " Horizontal lines above and below the finder"
.br .br
.BR vertical " Vertical lines on each side of the finder"
.br
.BR top
.br
.BR bottom
.br
.BR left
.br
.BR right
.br
.TP .TP
.B "--no-unicode" .B "--no-unicode"
@@ -267,11 +277,9 @@ Enable processing of ANSI color codes
.BI "--tabstop=" SPACES .BI "--tabstop=" SPACES
Number of spaces for a tab character (default: 8) Number of spaces for a tab character (default: 8)
.TP .TP
.BI "--color=" "[BASE_SCHEME][,COLOR:ANSI]" .BI "--color=" "[BASE_SCHEME][,COLOR_NAME[:ANSI_COLOR][:ANSI_ATTRIBUTES]]..."
Color configuration. The name of the base color scheme is followed by custom Color configuration. The name of the base color scheme is followed by custom
color mappings. Ansi color code of -1 denotes terminal default color mappings.
foreground/background color. You can also specify 24-bit color in \fB#rrggbb\fR
format.
.RS .RS
.B BASE SCHEME: .B BASE SCHEME:
@@ -282,7 +290,7 @@ format.
\fB16 \fRColor scheme for 16-color terminal \fB16 \fRColor scheme for 16-color terminal
\fBbw \fRNo colors (equivalent to \fB--no-color\fR) \fBbw \fRNo colors (equivalent to \fB--no-color\fR)
.B COLOR: .B COLOR NAMES:
\fBfg \fRText \fBfg \fRText
\fBbg \fRBackground \fBbg \fRBackground
\fBpreview-fg \fRPreview window text \fBpreview-fg \fRPreview window text
@@ -300,10 +308,25 @@ format.
\fBspinner \fRStreaming input indicator \fBspinner \fRStreaming input indicator
\fBheader \fRHeader \fBheader \fRHeader
.B ANSI COLORS:
\fB-1 \fRDefault terminal foreground/background color
\fB \fR(or the original color of the text)
\fB0 ~ 15 \fR16 base colors
\fB16 ~ 255 \fRANSI 256 colors
\fB#rrggbb \fR24-bit colors
.B ANSI ATTRIBUTES: (Only applies to foreground colors)
\fBregular \fRClears previously set attributes; should precede the other ones
\fBbold\fR
\fBunderline\fR
\fBreverse\fR
\fBdim\fR
\fBitalic\fR
.B EXAMPLES: .B EXAMPLES:
\fB# Seoul256 theme with 8-bit colors \fB# Seoul256 theme with 8-bit colors
# (https://github.com/junegunn/seoul256.vim) # (https://github.com/junegunn/seoul256.vim)
fzf --color='bg:237,bg+:236,info:143,border:240,spinner:108' \\ fzf --color='bg:237,bg+:236,info:143,border:240,spinner:108' \\
--color='hl:65,fg:252,header:65,fg+:252' \\ --color='hl:65,fg:252,header:65,fg+:252' \\
--color='pointer:161,marker:168,prompt:110,hl+:108' --color='pointer:161,marker:168,prompt:110,hl+:108'
@@ -379,6 +402,18 @@ Note that you can escape a placeholder pattern by prepending a backslash.
Preview window will be updated even when there is no match for the current Preview window will be updated even when there is no match for the current
query if any of the placeholder expressions evaluates to a non-empty string. query if any of the placeholder expressions evaluates to a non-empty string.
Since 0.24.0, fzf can render partial preview content before the preview command
completes. ANSI escape sequence for clearing the display (\fBCSI 2 J\fR) is
supported, so you can use it to implement preview window that is constantly
updating.
e.g.
\fBfzf --preview 'for i in $(seq 100000); do
(( i % 200 == 0 )) && printf "\\033[2J"
echo "$i"
sleep 0.01
done'\fR
.RE .RE
.TP .TP
.BI "--preview-window=" "[POSITION][:SIZE[%]][:rounded|sharp|noborder][:[no]wrap][:[no]cycle][:[no]hidden][:+SCROLL[-OFFSET]][:default]" .BI "--preview-window=" "[POSITION][:SIZE[%]][:rounded|sharp|noborder][:[no]wrap][:[no]cycle][:[no]hidden][:+SCROLL[-OFFSET]][:default]"
@@ -389,7 +424,6 @@ query if any of the placeholder expressions evaluates to a non-empty string.
\fBdown \fBdown
\fBleft \fBleft
\fBright \fBright
.RE
\fRDetermines the layout of the preview window. If the argument contains \fRDetermines the layout of the preview window. If the argument contains
\fB:hidden\fR, the preview window will be hidden by default until \fB:hidden\fR, the preview window will be hidden by default until

View File

@@ -128,7 +128,7 @@ endfunction
function! s:default_layout() function! s:default_layout()
return s:popup_support() return s:popup_support()
\ ? { 'window' : { 'width': 0.9, 'height': 0.6, 'highlight': 'Normal' } } \ ? { 'window' : { 'width': 0.9, 'height': 0.6 } }
\ : { 'down': '~40%' } \ : { 'down': '~40%' }
endfunction endfunction
@@ -154,7 +154,20 @@ function! fzf#install()
endif endif
endfunction endfunction
function! fzf#exec() function! s:version_requirement(val, min)
let val = split(a:val, '\.')
let min = split(a:min, '\.')
for idx in range(0, len(min) - 1)
let v = get(val, idx, 0)
if v < min[idx] | return 0
elseif v > min[idx] | return 1
endif
endfor
return 1
endfunction
let s:checked = {}
function! fzf#exec(...)
if !exists('s:exec') if !exists('s:exec')
if executable(s:fzf_go) if executable(s:fzf_go)
let s:exec = s:fzf_go let s:exec = s:fzf_go
@@ -169,6 +182,26 @@ function! fzf#exec()
throw 'fzf executable not found' throw 'fzf executable not found'
endif endif
endif endif
if a:0 && !has_key(s:checked, a:1)
let command = s:exec . ' --version'
let output = systemlist(command)
if v:shell_error || empty(output)
throw printf('Failed to run "%s": %s', command, output)
endif
let fzf_version = matchstr(output[-1], '[0-9.]\+')
if s:version_requirement(fzf_version, a:1)
let s:checked[a:1] = 1
return s:exec
elseif a:0 < 2 && input(printf('You need fzf %s or above. Found: %s. Download binary? (y/n) ', a:1, fzf_version)) =~? '^y'
redraw
call fzf#install()
return fzf#exec(a:1, 1)
else
throw printf('You need to upgrade fzf (required: %s or above)', a:1)
endif
endif
return s:exec return s:exec
endfunction endfunction
@@ -404,7 +437,7 @@ try
let prefix = '( '.source.' )|' let prefix = '( '.source.' )|'
elseif type == 3 elseif type == 3
let temps.input = s:fzf_tempname() let temps.input = s:fzf_tempname()
call writefile(map(source, '<SID>enc_to_cp(v:val)'), temps.input) call writefile(source, temps.input)
let prefix = (s:is_win ? 'type ' : 'cat ').fzf#shellescape(temps.input).'|' let prefix = (s:is_win ? 'type ' : 'cat ').fzf#shellescape(temps.input).'|'
else else
throw 'Invalid source type' throw 'Invalid source type'
@@ -432,6 +465,7 @@ try
elseif use_term elseif use_term
let optstr .= ' --no-height' let optstr .= ' --no-height'
endif endif
let optstr .= s:border_opt(get(dict, 'window', 0))
let command = prefix.(use_tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result let command = prefix.(use_tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
if use_term if use_term
@@ -656,6 +690,32 @@ function! s:getpos()
return {'tab': tabpagenr(), 'win': winnr(), 'winid': win_getid(), 'cnt': winnr('$'), 'tcnt': tabpagenr('$')} return {'tab': tabpagenr(), 'win': winnr(), 'winid': win_getid(), 'cnt': winnr('$'), 'tcnt': tabpagenr('$')}
endfunction endfunction
function! s:border_opt(window)
if type(a:window) != type({})
return ''
endif
" Border style
let style = tolower(get(a:window, 'border', 'rounded'))
if !has_key(a:window, 'border') && !get(a:window, 'rounded', 1)
let style = 'sharp'
endif
if style == 'none' || style == 'no'
return ''
endif
" For --border styles, we need fzf 0.24.0 or above
call fzf#exec('0.24.0')
let opt = ' --border=' . style
if has_key(a:window, 'highlight')
let color = s:get_color('fg', a:window.highlight)
if len(color)
let opt .= ' --color=border:' . color
endif
endif
return opt
endfunction
function! s:split(dict) function! s:split(dict)
let directions = { let directions = {
\ 'up': ['topleft', 'resize', &lines], \ 'up': ['topleft', 'resize', &lines],
@@ -848,33 +908,22 @@ if has('nvim')
endfunction endfunction
else else
function! s:create_popup(hl, opts) abort function! s:create_popup(hl, opts) abort
let is_frame = has_key(a:opts, 'border')
let s:popup_create = {buf -> popup_create(buf, #{ let s:popup_create = {buf -> popup_create(buf, #{
\ line: a:opts.row, \ line: a:opts.row,
\ col: a:opts.col, \ col: a:opts.col,
\ minwidth: a:opts.width, \ minwidth: a:opts.width,
\ maxwidth: a:opts.width,
\ minheight: a:opts.height, \ minheight: a:opts.height,
\ zindex: 50 - is_frame, \ maxheight: a:opts.height,
\ zindex: 1000,
\ })} \ })}
if is_frame autocmd TerminalOpen * ++once call s:popup_create(str2nr(expand('<abuf>')))
let id = s:popup_create('')
call setwinvar(id, '&wincolor', a:hl)
call setbufline(winbufnr(id), 1, a:opts.border)
execute 'autocmd BufWipeout * ++once call popup_close('..id..')'
return winbufnr(id)
else
autocmd TerminalOpen * ++once call s:popup_create(str2nr(expand('<abuf>')))
endif
endfunction endfunction
endif endif
function! s:popup(opts) abort function! s:popup(opts) abort
" Support ambiwidth == 'double'
let ambidouble = &ambiwidth == 'double' ? 2 : 1
" Size and position " Size and position
let width = min([max([8, a:opts.width > 1 ? a:opts.width : float2nr(&columns * a:opts.width)]), &columns]) let width = min([max([8, a:opts.width > 1 ? a:opts.width : float2nr(&columns * a:opts.width)]), &columns])
let width += width % ambidouble
let height = min([max([4, a:opts.height > 1 ? a:opts.height : float2nr(&lines * a:opts.height)]), &lines - has('nvim')]) let height = min([max([4, a:opts.height > 1 ? a:opts.height : float2nr(&lines * a:opts.height)]), &lines - has('nvim')])
let row = float2nr(get(a:opts, 'yoffset', 0.5) * (&lines - height)) let row = float2nr(get(a:opts, 'yoffset', 0.5) * (&lines - height))
let col = float2nr(get(a:opts, 'xoffset', 0.5) * (&columns - width)) let col = float2nr(get(a:opts, 'xoffset', 0.5) * (&columns - width))
@@ -885,45 +934,9 @@ function! s:popup(opts) abort
let row += !has('nvim') let row += !has('nvim')
let col += !has('nvim') let col += !has('nvim')
" Border style
let style = tolower(get(a:opts, 'border', 'rounded'))
if !has_key(a:opts, 'border') && !get(a:opts, 'rounded', 1)
let style = 'sharp'
endif
if style =~ 'vertical\|left\|right'
let mid = style == 'vertical' ? '│' .. repeat(' ', width - 2 * ambidouble) .. '│' :
\ style == 'left' ? '│' .. repeat(' ', width - 1 * ambidouble)
\ : repeat(' ', width - 1 * ambidouble) .. '│'
let border = repeat([mid], height)
let shift = { 'row': 0, 'col': style == 'right' ? 0 : 2, 'width': style == 'vertical' ? -4 : -2, 'height': 0 }
elseif style =~ 'horizontal\|top\|bottom'
let hor = repeat('─', width / ambidouble)
let mid = repeat(' ', width)
let border = style == 'horizontal' ? [hor] + repeat([mid], height - 2) + [hor] :
\ style == 'top' ? [hor] + repeat([mid], height - 1)
\ : repeat([mid], height - 1) + [hor]
let shift = { 'row': style == 'bottom' ? 0 : 1, 'col': 0, 'width': 0, 'height': style == 'horizontal' ? -2 : -1 }
else
let edges = style == 'sharp' ? ['┌', '┐', '└', '┘'] : ['╭', '╮', '╰', '╯']
let bar = repeat('─', width / ambidouble - 2)
let top = edges[0] .. bar .. edges[1]
let mid = '│' .. repeat(' ', width - 2 * ambidouble) .. '│'
let bot = edges[2] .. bar .. edges[3]
let border = [top] + repeat([mid], height - 2) + [bot]
let shift = { 'row': 1, 'col': 2, 'width': -4, 'height': -2 }
endif
let highlight = get(a:opts, 'highlight', 'Comment')
let frame = s:create_popup(highlight, {
\ 'row': row, 'col': col, 'width': width, 'height': height, 'border': border
\ })
call s:create_popup('Normal', { call s:create_popup('Normal', {
\ 'row': row + shift.row, 'col': col + shift.col, 'width': width + shift.width, 'height': height + shift.height \ 'row': row, 'col': col, 'width': width, 'height': height
\ }) \ })
if has('nvim')
execute 'autocmd BufWipeout <buffer> bwipeout '..frame
endif
endfunction endfunction
let s:default_action = { let s:default_action = {

View File

@@ -127,12 +127,12 @@ function fzf_key_bindings
else else
set dir (__fzf_get_dir $commandline) set dir (__fzf_get_dir $commandline)
if [ "$dir" = "." -a (string sub -l 1 $commandline) != '.' ] if [ "$dir" = "." -a (string sub -l 1 -- $commandline) != '.' ]
# if $dir is "." but commandline is not a relative path, this means no file path found # if $dir is "." but commandline is not a relative path, this means no file path found
set fzf_query $commandline set fzf_query $commandline
else else
# Also remove trailing slash after dir, to "split" input properly # Also remove trailing slash after dir, to "split" input properly
set fzf_query (string replace -r "^$dir/?" '' "$commandline") set fzf_query (string replace -r "^$dir/?" -- '' "$commandline")
end end
end end
@@ -144,15 +144,15 @@ function fzf_key_bindings
set dir $argv set dir $argv
# Strip all trailing slashes. Ignore if $dir is root dir (/) # Strip all trailing slashes. Ignore if $dir is root dir (/)
if [ (string length $dir) -gt 1 ] if [ (string length -- $dir) -gt 1 ]
set dir (string replace -r '/*$' '' $dir) set dir (string replace -r '/*$' -- '' $dir)
end end
# Iteratively check if dir exists and strip tail end of path # Iteratively check if dir exists and strip tail end of path
while [ ! -d "$dir" ] while [ ! -d "$dir" ]
# If path is absolute, this can keep going until ends up at / # If path is absolute, this can keep going until ends up at /
# If path is relative, this can keep going until entire input is consumed, dirname returns "." # If path is relative, this can keep going until entire input is consumed, dirname returns "."
set dir (dirname "$dir") set dir (dirname -- "$dir")
end end
echo $dir echo $dir

View File

@@ -9,9 +9,6 @@ import (
) )
const ( const (
// Current version
version = "0.23.1"
// Core // Core
coordinatorDelayMax time.Duration = 100 * time.Millisecond coordinatorDelayMax time.Duration = 100 * time.Millisecond
coordinatorDelayStep time.Duration = 10 * time.Millisecond coordinatorDelayStep time.Duration = 10 * time.Millisecond
@@ -27,6 +24,8 @@ const (
initialDelayTac = 100 * time.Millisecond initialDelayTac = 100 * time.Millisecond
spinnerDuration = 100 * time.Millisecond spinnerDuration = 100 * time.Millisecond
previewCancelWait = 500 * time.Millisecond previewCancelWait = 500 * time.Millisecond
previewChunkDelay = 100 * time.Millisecond
previewDelayed = 500 * time.Millisecond
maxPatternLength = 300 maxPatternLength = 300
maxMulti = math.MaxInt32 maxMulti = math.MaxInt32

View File

@@ -43,7 +43,7 @@ Matcher -> EvtHeader -> Terminal (update header)
*/ */
// Run starts fzf // Run starts fzf
func Run(opts *Options, revision string) { func Run(opts *Options, version string, revision string) {
sort := opts.Sort > 0 sort := opts.Sort > 0
sortCriteria = opts.Criteria sortCriteria = opts.Criteria
@@ -66,7 +66,7 @@ func Run(opts *Options, revision string) {
var lineAnsiState, prevLineAnsiState *ansiState var lineAnsiState, prevLineAnsiState *ansiState
if opts.Ansi { if opts.Ansi {
if opts.Theme != nil { if opts.Theme.Colored {
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) { ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
prevLineAnsiState = lineAnsiState prevLineAnsiState = lineAnsiState
trimmed, offsets, newState := extractColor(string(data), lineAnsiState, nil) trimmed, offsets, newState := extractColor(string(data), lineAnsiState, nil)
@@ -102,7 +102,7 @@ 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 { if opts.Ansi && opts.Theme.Colored && len(tokens) > 1 {
var ansiState *ansiState var ansiState *ansiState
if prevLineAnsiState != nil { if prevLineAnsiState != nil {
ansiStateDup := *prevLineAnsiState ansiStateDup := *prevLineAnsiState

View File

@@ -58,7 +58,8 @@ const usage = `usage: fzf [options]
(default: 10) (default: 10)
--layout=LAYOUT Choose layout: [default|reverse|reverse-list] --layout=LAYOUT Choose layout: [default|reverse|reverse-list]
--border[=STYLE] Draw border around the finder --border[=STYLE] Draw border around the finder
[rounded|sharp|horizontal] (default: rounded) [rounded|sharp|horizontal|vertical|
top|bottom|left|right] (default: rounded)
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L) --margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
--info=STYLE Finder info style [default|inline|hidden] --info=STYLE Finder info style [default|inline|hidden]
--prompt=STR Input prompt (default: '> ') --prompt=STR Input prompt (default: '> ')
@@ -421,11 +422,21 @@ func parseBorder(str string, optional bool) tui.BorderShape {
return tui.BorderSharp return tui.BorderSharp
case "horizontal": case "horizontal":
return tui.BorderHorizontal return tui.BorderHorizontal
case "vertical":
return tui.BorderVertical
case "top":
return tui.BorderTop
case "bottom":
return tui.BorderBottom
case "left":
return tui.BorderLeft
case "right":
return tui.BorderRight
default: default:
if optional && str == "" { if optional && str == "" {
return tui.BorderRounded return tui.BorderRounded
} }
errorExit("invalid border style (expected: rounded|sharp|horizontal)") errorExit("invalid border style (expected: rounded|sharp|horizontal|vertical|top|bottom|left|right)")
} }
return tui.BorderNone return tui.BorderNone
} }
@@ -590,11 +601,8 @@ func parseTiebreak(str string) []criterion {
} }
func dupeTheme(theme *tui.ColorTheme) *tui.ColorTheme { func dupeTheme(theme *tui.ColorTheme) *tui.ColorTheme {
if theme != nil { dupe := *theme
dupe := *theme return &dupe
return &dupe
}
return nil
} }
func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme { func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
@@ -609,7 +617,7 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
case "16": case "16":
theme = dupeTheme(tui.Default16) theme = dupeTheme(tui.Default16)
case "bw", "no": case "bw", "no":
theme = nil theme = tui.NoColorTheme()
default: default:
fail := func() { fail := func() {
errorExit("invalid color specification: " + str) errorExit("invalid color specification: " + str)
@@ -619,54 +627,76 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
continue continue
} }
pair := strings.Split(str, ":") components := strings.Split(str, ":")
if len(pair) != 2 { if len(components) < 2 {
fail() fail()
} }
var ansi tui.Color cattr := tui.NewColorAttr()
if rrggbb.MatchString(pair[1]) { for _, component := range components[1:] {
ansi = tui.HexToColor(pair[1]) switch component {
} else { case "regular":
ansi32, err := strconv.Atoi(pair[1]) cattr.Attr = tui.AttrRegular
if err != nil || ansi32 < -1 || ansi32 > 255 { case "bold", "strong":
fail() cattr.Attr |= tui.Bold
case "dim":
cattr.Attr |= tui.Dim
case "italic":
cattr.Attr |= tui.Italic
case "underline":
cattr.Attr |= tui.Underline
case "blink":
cattr.Attr |= tui.Blink
case "reverse":
cattr.Attr |= tui.Reverse
case "":
default:
if rrggbb.MatchString(component) {
cattr.Color = tui.HexToColor(component)
} else {
ansi32, err := strconv.Atoi(component)
if err != nil || ansi32 < -1 || ansi32 > 255 {
fail()
}
cattr.Color = tui.Color(ansi32)
}
} }
ansi = tui.Color(ansi32)
} }
switch pair[0] { switch components[0] {
case "input":
theme.Input = cattr
case "fg": case "fg":
theme.Fg = ansi theme.Fg = cattr
case "bg": case "bg":
theme.Bg = ansi theme.Bg = cattr
case "preview-fg": case "preview-fg":
theme.PreviewFg = ansi theme.PreviewFg = cattr
case "preview-bg": case "preview-bg":
theme.PreviewBg = ansi theme.PreviewBg = cattr
case "fg+": case "fg+":
theme.Current = ansi theme.Current = cattr
case "bg+": case "bg+":
theme.DarkBg = ansi theme.DarkBg = cattr
case "gutter": case "gutter":
theme.Gutter = ansi theme.Gutter = cattr
case "hl": case "hl":
theme.Match = ansi theme.Match = cattr
case "hl+": case "hl+":
theme.CurrentMatch = ansi theme.CurrentMatch = cattr
case "border": case "border":
theme.Border = ansi theme.Border = cattr
case "prompt": case "prompt":
theme.Prompt = ansi theme.Prompt = cattr
case "spinner": case "spinner":
theme.Spinner = ansi theme.Spinner = cattr
case "info": case "info":
theme.Info = ansi theme.Info = cattr
case "pointer": case "pointer":
theme.Cursor = ansi theme.Cursor = cattr
case "marker": case "marker":
theme.Selected = ansi theme.Selected = cattr
case "header": case "header":
theme.Header = ansi theme.Header = cattr
default: default:
fail() fail()
} }
@@ -1180,7 +1210,7 @@ func parseOptions(opts *Options, allArgs []string) {
case "--no-mouse": case "--no-mouse":
opts.Mouse = false opts.Mouse = false
case "+c", "--no-color": case "+c", "--no-color":
opts.Theme = nil opts.Theme = tui.NoColorTheme()
case "+2", "--no-256": case "+2", "--no-256":
opts.Theme = tui.Default16 opts.Theme = tui.Default16
case "--black": case "--black":
@@ -1478,6 +1508,25 @@ func postProcessOptions(opts *Options) {
} }
} }
} }
if opts.Bold {
theme := opts.Theme
boldify := func(c tui.ColorAttr) tui.ColorAttr {
dup := c
if !theme.Colored {
dup.Attr |= tui.Bold
} else if (c.Attr & tui.AttrRegular) == 0 {
dup.Attr |= tui.Bold
}
return dup
}
theme.Current = boldify(theme.Current)
theme.CurrentMatch = boldify(theme.CurrentMatch)
theme.Prompt = boldify(theme.Prompt)
theme.Input = boldify(theme.Input)
theme.Cursor = boldify(theme.Cursor)
theme.Spinner = boldify(theme.Spinner)
}
} }
// ParseOptions parses command-line options // ParseOptions parses command-line options

View File

@@ -295,7 +295,7 @@ func TestColorSpec(t *testing.T) {
} }
customized := parseTheme(theme, "fg:231,bg:232") customized := parseTheme(theme, "fg:231,bg:232")
if customized.Fg != 231 || customized.Bg != 232 { if customized.Fg.Color != 231 || customized.Bg.Color != 232 {
t.Errorf("color not customized") t.Errorf("color not customized")
} }
if *tui.Dark256 == *customized { if *tui.Dark256 == *customized {
@@ -313,18 +313,6 @@ func TestColorSpec(t *testing.T) {
} }
} }
func TestParseNilTheme(t *testing.T) {
var theme *tui.ColorTheme
newTheme := parseTheme(theme, "prompt:12")
if newTheme != nil {
t.Errorf("color is disabled. keep it that way.")
}
newTheme = parseTheme(theme, "prompt:12,dark,prompt:13")
if newTheme.Prompt != 13 {
t.Errorf("color should now be enabled and customized")
}
}
func TestDefaultCtrlNP(t *testing.T) { func TestDefaultCtrlNP(t *testing.T) {
check := func(words []string, key int, expected actionType) { check := func(words []string, key int, expected actionType) {
opts := defaultOptions() opts := defaultOptions()

View File

@@ -15,7 +15,6 @@ type Offset [2]int32
type colorOffset struct { type colorOffset struct {
offset [2]int32 offset [2]int32
color tui.ColorPair color tui.ColorPair
attr tui.Attr
} }
type Result struct { type Result struct {
@@ -87,14 +86,14 @@ func minRank() Result {
return Result{item: &minItem, points: [4]uint16{math.MaxUint16, 0, 0, 0}} return Result{item: &minItem, points: [4]uint16{math.MaxUint16, 0, 0, 0}}
} }
func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme, color tui.ColorPair, attr tui.Attr, current bool) []colorOffset { func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme, colBase tui.ColorPair, colMatch tui.ColorPair, current bool) []colorOffset {
itemColors := result.item.Colors() itemColors := result.item.Colors()
// No ANSI code, or --color=no // No ANSI codes
if len(itemColors) == 0 { if len(itemColors) == 0 {
var offsets []colorOffset var offsets []colorOffset
for _, off := range matchOffsets { for _, off := range matchOffsets {
offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: color, attr: attr}) offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: colMatch})
} }
return offsets return offsets
} }
@@ -111,17 +110,19 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
maxCol = ansi.offset[1] maxCol = ansi.offset[1]
} }
} }
cols := make([]int, maxCol)
cols := make([]int, maxCol)
for colorIndex, ansi := range itemColors { for colorIndex, ansi := range itemColors {
for i := ansi.offset[0]; i < ansi.offset[1]; i++ { for i := ansi.offset[0]; i < ansi.offset[1]; i++ {
cols[i] = colorIndex + 1 // XXX cols[i] = colorIndex + 1 // 1-based index of itemColors
} }
} }
for _, off := range matchOffsets { for _, off := range matchOffsets {
for i := off[0]; i < off[1]; i++ { for i := off[0]; i < off[1]; i++ {
cols[i] = -1 // Negative of 1-based index of itemColors
// - The extra -1 means highlighted
cols[i] = cols[i]*-1 - 1
} }
} }
@@ -133,36 +134,41 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
// --++++++++-- --++++++++++--- // --++++++++-- --++++++++++---
curr := 0 curr := 0
start := 0 start := 0
ansiToColorPair := func(ansi ansiOffset, base tui.ColorPair) tui.ColorPair {
fg := ansi.color.fg
bg := ansi.color.bg
if fg == -1 {
if current {
fg = theme.Current.Color
} else {
fg = theme.Fg.Color
}
}
if bg == -1 {
if current {
bg = theme.DarkBg.Color
} else {
bg = theme.Bg.Color
}
}
return tui.NewColorPair(fg, bg, ansi.color.attr).MergeAttr(base)
}
var colors []colorOffset var colors []colorOffset
add := func(idx int) { add := func(idx int) {
if curr != 0 && idx > start { if curr != 0 && idx > start {
if curr == -1 { if curr < 0 {
colors = append(colors, colorOffset{ color := colMatch
offset: [2]int32{int32(start), int32(idx)}, color: color, attr: attr}) if curr < -1 && theme.Colored {
} else { origColor := ansiToColorPair(itemColors[-curr-2], colMatch)
ansi := itemColors[curr-1] color = origColor.MergeNonDefault(color)
fg := ansi.color.fg
bg := ansi.color.bg
if theme != nil {
if fg == -1 {
if current {
fg = theme.Current
} else {
fg = theme.Fg
}
}
if bg == -1 {
if current {
bg = theme.DarkBg
} else {
bg = theme.Bg
}
}
} }
colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)}, color: color})
} else {
ansi := itemColors[curr-1]
colors = append(colors, colorOffset{ colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)}, offset: [2]int32{int32(start), int32(idx)},
color: tui.NewColorPair(fg, bg), color: ansiToColorPair(ansi, colBase)})
attr: ansi.color.attr.Merge(attr)})
} }
} }
} }

View File

@@ -105,32 +105,55 @@ func TestColorOffset(t *testing.T) {
// ++++++++ ++++++++++ // ++++++++ ++++++++++
// --++++++++-- --++++++++++--- // --++++++++-- --++++++++++---
offsets := []Offset{Offset{5, 15}, Offset{25, 35}} offsets := []Offset{{5, 15}, {25, 35}}
item := Result{ item := Result{
item: &Item{ item: &Item{
colors: &[]ansiOffset{ colors: &[]ansiOffset{
ansiOffset{[2]int32{0, 20}, ansiState{1, 5, 0}}, {[2]int32{0, 20}, ansiState{1, 5, 0}},
ansiOffset{[2]int32{22, 27}, ansiState{2, 6, tui.Bold}}, {[2]int32{22, 27}, ansiState{2, 6, tui.Bold}},
ansiOffset{[2]int32{30, 32}, ansiState{3, 7, 0}}, {[2]int32{30, 32}, ansiState{3, 7, 0}},
ansiOffset{[2]int32{33, 40}, ansiState{4, 8, tui.Bold}}}}} {[2]int32{33, 40}, ansiState{4, 8, tui.Bold}}}}}
// [{[0 5] 9 false} {[5 15] 99 false} {[15 20] 9 false} {[22 25] 10 true} {[25 35] 99 false} {[35 40] 11 true}]
pair := tui.NewColorPair(99, 199) colBase := tui.NewColorPair(89, 189, tui.AttrUndefined)
colors := item.colorOffsets(offsets, tui.Dark256, pair, tui.AttrRegular, true) colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined)
assert := func(idx int, b int32, e int32, c tui.ColorPair, bold bool) { colors := item.colorOffsets(offsets, tui.Dark256, colBase, colMatch, true)
var attr tui.Attr assert := func(idx int, b int32, e int32, c tui.ColorPair) {
if bold {
attr = tui.Bold
}
o := colors[idx] o := colors[idx]
if o.offset[0] != b || o.offset[1] != e || o.color != c || o.attr != attr { if o.offset[0] != b || o.offset[1] != e || o.color != c {
t.Error(o) t.Error(o, b, e, c)
} }
} }
assert(0, 0, 5, tui.NewColorPair(1, 5), false) // [{[0 5] {1 5 0}} {[5 15] {99 199 0}} {[15 20] {1 5 0}}
assert(1, 5, 15, pair, false) // {[22 25] {2 6 1}} {[25 27] {99 199 1}} {[27 30] {99 199 0}}
assert(2, 15, 20, tui.NewColorPair(1, 5), false) // {[30 32] {99 199 0}} {[32 33] {99 199 0}} {[33 35] {99 199 1}}
assert(3, 22, 25, tui.NewColorPair(2, 6), true) // {[35 40] {4 8 1}}]
assert(4, 25, 35, pair, false) assert(0, 0, 5, tui.NewColorPair(1, 5, tui.AttrUndefined))
assert(5, 35, 40, tui.NewColorPair(4, 8), true) assert(1, 5, 15, colMatch)
assert(2, 15, 20, tui.NewColorPair(1, 5, tui.AttrUndefined))
assert(3, 22, 25, tui.NewColorPair(2, 6, tui.Bold))
assert(4, 25, 27, colMatch.WithAttr(tui.Bold))
assert(5, 27, 30, colMatch)
assert(6, 30, 32, colMatch)
assert(7, 32, 33, colMatch) // TODO: Should we merge consecutive blocks?
assert(8, 33, 35, colMatch.WithAttr(tui.Bold))
assert(9, 35, 40, tui.NewColorPair(4, 8, tui.Bold))
colRegular := tui.NewColorPair(-1, -1, tui.AttrUndefined)
colUnderline := tui.NewColorPair(-1, -1, tui.Underline)
colors = item.colorOffsets(offsets, tui.Dark256, colRegular, colUnderline, true)
// [{[0 5] {1 5 0}} {[5 15] {1 5 8}} {[15 20] {1 5 0}}
// {[22 25] {2 6 1}} {[25 27] {2 6 9}} {[27 30] {-1 -1 8}}
// {[30 32] {3 7 8}} {[32 33] {-1 -1 8}} {[33 35] {4 8 9}}
// {[35 40] {4 8 1}}]
assert(0, 0, 5, tui.NewColorPair(1, 5, tui.AttrUndefined))
assert(1, 5, 15, tui.NewColorPair(1, 5, tui.Underline))
assert(2, 15, 20, tui.NewColorPair(1, 5, tui.AttrUndefined))
assert(3, 22, 25, tui.NewColorPair(2, 6, tui.Bold))
assert(4, 25, 27, tui.NewColorPair(2, 6, tui.Bold|tui.Underline))
assert(5, 27, 30, colUnderline)
assert(6, 30, 32, tui.NewColorPair(3, 7, tui.Underline))
assert(7, 32, 33, colUnderline)
assert(8, 33, 35, tui.NewColorPair(4, 8, tui.Bold|tui.Underline))
assert(9, 35, 40, tui.NewColorPair(4, 8, tui.Bold))
} }

View File

@@ -4,7 +4,6 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"os/signal" "os/signal"
@@ -24,13 +23,16 @@ import (
var placeholder *regexp.Regexp var placeholder *regexp.Regexp
var numericPrefix *regexp.Regexp var numericPrefix *regexp.Regexp
var whiteSuffix *regexp.Regexp
var activeTempFiles []string var activeTempFiles []string
const ellipsis string = ".." const ellipsis string = ".."
const clearCode string = "\x1b[2J"
func init() { func init() {
placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{\+?f?nf?})`) placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{\+?f?nf?})`)
numericPrefix = regexp.MustCompile(`^[[:punct:]]*([0-9]+)`) numericPrefix = regexp.MustCompile(`^[[:punct:]]*([0-9]+)`)
whiteSuffix = regexp.MustCompile(`\s*$`)
activeTempFiles = []string{} activeTempFiles = []string{}
} }
@@ -43,11 +45,25 @@ const (
) )
type previewer struct { type previewer struct {
text string version int64
lines int lines []string
offset int offset int
enabled bool enabled bool
more bool scrollable bool
final bool
spinner string
}
type previewed struct {
version int64
numLines int
offset int
filled bool
}
type eachLine struct {
line string
err error
} }
type itemLine struct { type itemLine struct {
@@ -125,6 +141,7 @@ type Terminal struct {
reqBox *util.EventBox reqBox *util.EventBox
preview previewOpts preview previewOpts
previewer previewer previewer previewer
previewed previewed
previewBox *util.EventBox previewBox *util.EventBox
eventBox *util.EventBox eventBox *util.EventBox
mutex sync.Mutex mutex sync.Mutex
@@ -171,6 +188,7 @@ const (
reqPreviewEnqueue reqPreviewEnqueue
reqPreviewDisplay reqPreviewDisplay
reqPreviewRefresh reqPreviewRefresh
reqPreviewDelayed
reqQuit reqQuit
) )
@@ -263,12 +281,15 @@ type searchRequest struct {
type previewRequest struct { type previewRequest struct {
template string template string
pwindow tui.Window
list []*Item list []*Item
} }
type previewResult struct { type previewResult struct {
content string version int64
lines []string
offset int offset int
spinner string
} }
func toActions(types ...actionType) []action { func toActions(types ...actionType) []action {
@@ -353,6 +374,13 @@ func hasPreviewAction(opts *Options) bool {
return false return false
} }
func makeSpinner(unicode bool) []string {
if unicode {
return []string{``, ``, ``, ``, ``, ``, ``, ``, ``, ``}
}
return []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
}
// NewTerminal returns new Terminal object // NewTerminal returns new Terminal object
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal { func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
input := trimQuery(opts.Query) input := trimQuery(opts.Query)
@@ -416,14 +444,10 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
wordRubout = fmt.Sprintf("%s[^%s]", sep, sep) wordRubout = fmt.Sprintf("%s[^%s]", sep, sep)
wordNext = fmt.Sprintf("[^%s]%s|(.$)", sep, sep) wordNext = fmt.Sprintf("[^%s]%s|(.$)", sep, sep)
} }
spinner := []string{``, ``, ``, ``, ``, ``, ``, ``, ``, ``}
if !opts.Unicode {
spinner = []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
}
t := Terminal{ t := Terminal{
initDelay: delay, initDelay: delay,
infoStyle: opts.InfoStyle, infoStyle: opts.InfoStyle,
spinner: spinner, spinner: makeSpinner(opts.Unicode),
queryLen: [2]int{0, 0}, queryLen: [2]int{0, 0},
layout: opts.Layout, layout: opts.Layout,
fullscreen: fullscreen, fullscreen: fullscreen,
@@ -467,7 +491,8 @@ 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, false}, previewer: previewer{0, []string{}, 0, previewBox != nil && !opts.Preview.hidden, false, true, ""},
previewed: previewed{0, 0, 0, false},
previewBox: previewBox, previewBox: previewBox,
eventBox: eventBox, eventBox: eventBox,
mutex: sync.Mutex{}, mutex: sync.Mutex{},
@@ -492,9 +517,29 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) {
var state *ansiState var state *ansiState
trimmed, colors, _ := extractColor(prompt, state, nil) trimmed, colors, _ := extractColor(prompt, state, nil)
item := &Item{text: util.ToChars([]byte(trimmed)), colors: colors} item := &Item{text: util.ToChars([]byte(trimmed)), colors: colors}
// "Prompt> "
// ------- // Do not apply ANSI attributes to the trailing whitespaces
// // unless the part has a non-default ANSI state
loc := whiteSuffix.FindStringIndex(trimmed)
if loc != nil {
blankState := ansiOffset{[2]int32{int32(loc[0]), int32(loc[1])}, ansiState{-1, -1, tui.AttrClear}}
if item.colors != nil {
lastColor := (*item.colors)[len(*item.colors)-1]
fmt.Println(lastColor.offset[1], int32(loc[1]))
if lastColor.offset[1] < int32(loc[1]) {
blankState.offset[0] = lastColor.offset[1]
colors := append(*item.colors, blankState)
item.colors = &colors
}
} else {
colors := []ansiOffset{blankState}
item.colors = &colors
}
}
output := func() { output := func() {
t.printHighlighted( t.printHighlighted(
Result{item: item}, t.strong, tui.ColPrompt, tui.ColPrompt, false, false) Result{item: item}, tui.ColPrompt, tui.ColPrompt, false, false)
} }
_, promptLen := t.processTabs([]rune(trimmed), 0) _, promptLen := t.processTabs([]rune(trimmed), 0)
@@ -608,7 +653,7 @@ func (t *Terminal) displayWidth(runes []rune) int {
} }
const ( const (
minWidth = 16 minWidth = 4
minHeight = 4 minHeight = 4
maxDisplayWidthCalc = 1024 maxDisplayWidthCalc = 1024
@@ -625,7 +670,7 @@ func calculateSize(base int, size sizeSpec, occupied int, minSize int, pad int)
func (t *Terminal) resizeWindows() { func (t *Terminal) resizeWindows() {
screenWidth := t.tui.MaxX() screenWidth := t.tui.MaxX()
screenHeight := t.tui.MaxY() screenHeight := t.tui.MaxY()
marginInt := [4]int{} marginInt := [4]int{} // TRBL
t.prevLines = make([]itemLine, screenHeight) t.prevLines = make([]itemLine, screenHeight)
for idx, sizeSpec := range t.margin { for idx, sizeSpec := range t.margin {
if sizeSpec.percent { if sizeSpec.percent {
@@ -642,6 +687,24 @@ func (t *Terminal) resizeWindows() {
switch t.borderShape { switch t.borderShape {
case tui.BorderHorizontal: case tui.BorderHorizontal:
marginInt[idx] += 1 - idx%2 marginInt[idx] += 1 - idx%2
case tui.BorderVertical:
marginInt[idx] += 2 * (idx % 2)
case tui.BorderTop:
if idx == 0 {
marginInt[idx]++
}
case tui.BorderRight:
if idx == 1 {
marginInt[idx] += 2
}
case tui.BorderBottom:
if idx == 2 {
marginInt[idx]++
}
case tui.BorderLeft:
if idx == 3 {
marginInt[idx] += 2
}
case tui.BorderRounded, tui.BorderSharp: case tui.BorderRounded, tui.BorderSharp:
marginInt[idx] += 1 + idx%2 marginInt[idx] += 1 + idx%2
} }
@@ -682,23 +745,39 @@ func (t *Terminal) resizeWindows() {
if t.pwindow != nil { if t.pwindow != nil {
t.pwindow.Close() t.pwindow.Close()
} }
// Reset preview version so that full redraw occurs
t.previewed.version = 0
width := screenWidth - marginInt[1] - marginInt[3] width := screenWidth - marginInt[1] - marginInt[3]
height := screenHeight - marginInt[0] - marginInt[2] height := screenHeight - marginInt[0] - marginInt[2]
switch t.borderShape { switch t.borderShape {
case tui.BorderHorizontal: case tui.BorderHorizontal:
t.border = t.tui.NewWindow( t.border = t.tui.NewWindow(
marginInt[0]-1, marginInt[0]-1, marginInt[3], width, height+2,
marginInt[3],
width,
height+2,
false, tui.MakeBorderStyle(tui.BorderHorizontal, t.unicode)) false, tui.MakeBorderStyle(tui.BorderHorizontal, t.unicode))
case tui.BorderVertical:
t.border = t.tui.NewWindow(
marginInt[0], marginInt[3]-2, width+4, height,
false, tui.MakeBorderStyle(tui.BorderVertical, t.unicode))
case tui.BorderTop:
t.border = t.tui.NewWindow(
marginInt[0]-1, marginInt[3], width, height+1,
false, tui.MakeBorderStyle(tui.BorderTop, t.unicode))
case tui.BorderBottom:
t.border = t.tui.NewWindow(
marginInt[0], marginInt[3], width, height+1,
false, tui.MakeBorderStyle(tui.BorderBottom, t.unicode))
case tui.BorderLeft:
t.border = t.tui.NewWindow(
marginInt[0], marginInt[3]-2, width+2, height,
false, tui.MakeBorderStyle(tui.BorderLeft, t.unicode))
case tui.BorderRight:
t.border = t.tui.NewWindow(
marginInt[0], marginInt[3], width+2, height,
false, tui.MakeBorderStyle(tui.BorderRight, t.unicode))
case tui.BorderRounded, tui.BorderSharp: case tui.BorderRounded, tui.BorderSharp:
t.border = t.tui.NewWindow( t.border = t.tui.NewWindow(
marginInt[0]-1, marginInt[0]-1, marginInt[3]-2, width+4, height+2,
marginInt[3]-2,
width+4,
height+2,
false, tui.MakeBorderStyle(t.borderShape, t.unicode)) false, tui.MakeBorderStyle(t.borderShape, t.unicode))
} }
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode) noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
@@ -719,26 +798,22 @@ func (t *Terminal) resizeWindows() {
pwidth -= 4 pwidth -= 4
x += 2 x += 2
} }
// 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
// column larger than the desired value.
if !t.preview.wrap && t.tui.DoesAutoWrap() {
pwidth += 1
}
t.pwindow = t.tui.NewWindow(y, x, pwidth, pheight, true, noBorder) t.pwindow = t.tui.NewWindow(y, x, pwidth, pheight, true, noBorder)
} }
verticalPad := 2 verticalPad := 2
minPreviewHeight := 3
if t.preview.border == tui.BorderNone { if t.preview.border == tui.BorderNone {
verticalPad = 0 verticalPad = 0
minPreviewHeight = 1
} }
switch t.preview.position { switch t.preview.position {
case posUp: case posUp:
pheight := calculateSize(height, t.preview.size, minHeight, 3, verticalPad) pheight := calculateSize(height, t.preview.size, minHeight, minPreviewHeight, verticalPad)
t.window = t.tui.NewWindow( t.window = t.tui.NewWindow(
marginInt[0]+pheight, marginInt[3], width, height-pheight, false, noBorder) marginInt[0]+pheight, marginInt[3], width, height-pheight, false, 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, verticalPad) pheight := calculateSize(height, t.preview.size, minHeight, minPreviewHeight, verticalPad)
t.window = t.tui.NewWindow( t.window = t.tui.NewWindow(
marginInt[0], marginInt[3], width, height-pheight, false, noBorder) marginInt[0], marginInt[3], width, height-pheight, false, noBorder)
createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight) createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
@@ -820,8 +895,16 @@ func (t *Terminal) printPrompt() {
t.prompt() t.prompt()
before, after := t.updatePromptOffset() before, after := t.updatePromptOffset()
t.window.CPrint(tui.ColNormal, t.strong, string(before)) t.window.CPrint(tui.ColInput, string(before))
t.window.CPrint(tui.ColNormal, t.strong, string(after)) t.window.CPrint(tui.ColInput, string(after))
}
func (t *Terminal) trimMessage(message string, maxWidth int) string {
if len(message) <= maxWidth {
return message
}
runes, _ := t.trimRight([]rune(message), maxWidth-2)
return string(runes) + strings.Repeat(".", util.Constrain(maxWidth, 0, 2))
} }
func (t *Terminal) printInfo() { func (t *Terminal) printInfo() {
@@ -832,7 +915,7 @@ func (t *Terminal) printInfo() {
if t.reading { if t.reading {
duration := int64(spinnerDuration) duration := int64(spinnerDuration)
idx := (time.Now().UnixNano() % (duration * int64(len(t.spinner)))) / duration idx := (time.Now().UnixNano() % (duration * int64(len(t.spinner)))) / duration
t.window.CPrint(tui.ColSpinner, t.strong, t.spinner[idx]) t.window.CPrint(tui.ColSpinner, t.spinner[idx])
} }
t.move(1, 2, false) t.move(1, 2, false)
pos = 2 pos = 2
@@ -843,9 +926,9 @@ func (t *Terminal) printInfo() {
} }
t.move(0, pos, true) t.move(0, pos, true)
if t.reading { if t.reading {
t.window.CPrint(tui.ColSpinner, t.strong, " < ") t.window.CPrint(tui.ColSpinner, " < ")
} else { } else {
t.window.CPrint(tui.ColPrompt, t.strong, " < ") t.window.CPrint(tui.ColPrompt, " < ")
} }
pos += len(" < ") pos += len(" < ")
case infoHidden: case infoHidden:
@@ -862,7 +945,7 @@ func (t *Terminal) printInfo() {
output += " -S" output += " -S"
} }
} }
if len(t.selected) > 0 { if t.multi > 0 {
if t.multi == maxMulti { if t.multi == maxMulti {
output += fmt.Sprintf(" (%d)", len(t.selected)) output += fmt.Sprintf(" (%d)", len(t.selected))
} else { } else {
@@ -875,12 +958,8 @@ func (t *Terminal) printInfo() {
if t.failed != nil && t.count == 0 { if t.failed != nil && t.count == 0 {
output = fmt.Sprintf("[Command failed: %s]", *t.failed) output = fmt.Sprintf("[Command failed: %s]", *t.failed)
} }
maxWidth := t.window.Width() - pos output = t.trimMessage(output, t.window.Width()-pos)
if len(output) > maxWidth { t.window.CPrint(tui.ColInfo, output)
outputRunes, _ := t.trimRight([]rune(output), maxWidth-2)
output = string(outputRunes) + strings.Repeat(".", util.Constrain(maxWidth, 0, 2))
}
t.window.CPrint(tui.ColInfo, 0, output)
} }
func (t *Terminal) printHeader() { func (t *Terminal) printHeader() {
@@ -905,7 +984,7 @@ func (t *Terminal) printHeader() {
t.move(line, 2, true) t.move(line, 2, true)
t.printHighlighted(Result{item: item}, t.printHighlighted(Result{item: item},
tui.AttrRegular, tui.ColHeader, tui.ColHeader, false, false) tui.ColHeader, tui.ColHeader, false, false)
} }
} }
@@ -935,7 +1014,7 @@ func (t *Terminal) printList() {
func (t *Terminal) printItem(result Result, line int, i int, current bool) { func (t *Terminal) printItem(result Result, line int, i int, current bool) {
item := result.item item := result.item
_, selected := t.selected[item.Index()] _, selected := t.selected[item.Index()]
label := t.pointerEmpty label := ""
if t.jumping != jumpDisabled { if t.jumping != jumpDisabled {
if i < len(t.jumpLabels) { if i < len(t.jumpLabels) {
// Striped // Striped
@@ -960,21 +1039,29 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool) {
t.move(line, 0, false) t.move(line, 0, false)
if current { if current {
t.window.CPrint(tui.ColCurrentCursor, t.strong, label) if len(label) == 0 {
if selected { t.window.CPrint(tui.ColCurrentCursorEmpty, t.pointerEmpty)
t.window.CPrint(tui.ColCurrentSelected, t.strong, t.marker)
} else { } else {
t.window.CPrint(tui.ColCurrentSelected, t.strong, t.markerEmpty) t.window.CPrint(tui.ColCurrentCursor, label)
} }
newLine.width = t.printHighlighted(result, t.strong, tui.ColCurrent, tui.ColCurrentMatch, true, true)
} else {
t.window.CPrint(tui.ColCursor, t.strong, label)
if selected { if selected {
t.window.CPrint(tui.ColSelected, t.strong, t.marker) t.window.CPrint(tui.ColCurrentSelected, t.marker)
} else {
t.window.CPrint(tui.ColCurrentSelectedEmpty, t.markerEmpty)
}
newLine.width = t.printHighlighted(result, tui.ColCurrent, tui.ColCurrentMatch, true, true)
} else {
if len(label) == 0 {
t.window.CPrint(tui.ColCursorEmpty, t.pointerEmpty)
} else {
t.window.CPrint(tui.ColCursor, label)
}
if selected {
t.window.CPrint(tui.ColSelected, t.marker)
} else { } else {
t.window.Print(t.markerEmpty) t.window.Print(t.markerEmpty)
} }
newLine.width = t.printHighlighted(result, 0, tui.ColNormal, tui.ColMatch, false, true) newLine.width = t.printHighlighted(result, tui.ColNormal, tui.ColMatch, false, true)
} }
fillSpaces := prevLine.width - newLine.width fillSpaces := prevLine.width - newLine.width
if fillSpaces > 0 { if fillSpaces > 0 {
@@ -1028,7 +1115,7 @@ func (t *Terminal) overflow(runes []rune, max int) bool {
return t.displayWidthWithLimit(runes, 0, max) > max return t.displayWidthWithLimit(runes, 0, max) > max
} }
func (t *Terminal) printHighlighted(result Result, attr tui.Attr, col1 tui.ColorPair, col2 tui.ColorPair, current bool, match bool) int { func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMatch tui.ColorPair, current bool, match bool) int {
item := result.item item := result.item
// Overflow // Overflow
@@ -1053,7 +1140,7 @@ func (t *Terminal) printHighlighted(result Result, attr tui.Attr, col1 tui.Color
maxe = util.Max(maxe, int(offset[1])) maxe = util.Max(maxe, int(offset[1]))
} }
offsets := result.colorOffsets(charOffsets, t.theme, col2, attr, current) offsets := result.colorOffsets(charOffsets, t.theme, colBase, colMatch, current)
maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1) maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1)
maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text)) maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text))
displayWidth := t.displayWidthWithLimit(text, 0, maxWidth) displayWidth := t.displayWidthWithLimit(text, 0, maxWidth)
@@ -1111,11 +1198,11 @@ func (t *Terminal) printHighlighted(result Result, attr tui.Attr, col1 tui.Color
e := util.Constrain32(offset.offset[1], index, maxOffset) e := util.Constrain32(offset.offset[1], index, maxOffset)
substr, prefixWidth = t.processTabs(text[index:b], prefixWidth) substr, prefixWidth = t.processTabs(text[index:b], prefixWidth)
t.window.CPrint(col1, attr, substr) t.window.CPrint(colBase, substr)
if b < e { if b < e {
substr, prefixWidth = t.processTabs(text[b:e], prefixWidth) substr, prefixWidth = t.processTabs(text[b:e], prefixWidth)
t.window.CPrint(offset.color, offset.attr, substr) t.window.CPrint(offset.color, substr)
} }
index = e index = e
@@ -1125,33 +1212,53 @@ func (t *Terminal) printHighlighted(result Result, attr tui.Attr, col1 tui.Color
} }
if index < maxOffset { if index < maxOffset {
substr, _ = t.processTabs(text[index:], prefixWidth) substr, _ = t.processTabs(text[index:], prefixWidth)
t.window.CPrint(col1, attr, substr) t.window.CPrint(colBase, substr)
} }
return displayWidth return displayWidth
} }
func (t *Terminal) printPreview() { func (t *Terminal) renderPreviewSpinner() {
if !t.hasPreviewWindow() { numLines := len(t.previewer.lines)
return spin := t.previewer.spinner
if len(spin) > 0 || t.previewer.scrollable {
maxWidth := t.pwindow.Width()
if !t.previewer.scrollable {
if maxWidth > 0 {
t.pwindow.Move(0, maxWidth-1)
t.pwindow.CPrint(tui.ColSpinner, spin)
}
} else {
offsetString := fmt.Sprintf("%d/%d", t.previewer.offset+1, numLines)
if len(spin) > 0 {
spin += " "
maxWidth -= 2
}
offsetRunes, _ := t.trimRight([]rune(offsetString), maxWidth)
pos := maxWidth - t.displayWidth(offsetRunes)
t.pwindow.Move(0, pos)
if maxWidth > 0 {
t.pwindow.CPrint(tui.ColSpinner, spin)
t.pwindow.CPrint(tui.ColInfo.WithAttr(tui.Reverse), string(offsetRunes))
}
}
} }
t.pwindow.Erase() }
func (t *Terminal) renderPreviewText(unchanged bool) {
maxWidth := t.pwindow.Width() maxWidth := t.pwindow.Width()
if t.tui.DoesAutoWrap() {
maxWidth -= 1
}
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 if unchanged {
t.pwindow.MoveAndClear(0, 0)
} else {
t.previewed.filled = false
t.pwindow.Erase()
}
var ansi *ansiState var ansi *ansiState
for ; ; lineNo++ { for _, line := range t.previewer.lines {
line, err := reader.ReadString('\n') line = strings.TrimSuffix(line, "\n")
eof := err == io.EOF
if !eof {
line = line[:len(line)-1]
}
if lineNo >= height || t.pwindow.Y() == height-1 && t.pwindow.X() > 0 { if lineNo >= height || t.pwindow.Y() == height-1 && t.pwindow.X() > 0 {
t.previewed.filled = true
break break
} else if lineNo >= 0 { } else if lineNo >= 0 {
var fillRet tui.FillReturn var fillRet tui.FillReturn
@@ -1163,38 +1270,63 @@ func (t *Terminal) printPreview() {
} }
str, width := t.processTabs(trimmed, prefixWidth) str, width := t.processTabs(trimmed, prefixWidth)
prefixWidth += width prefixWidth += width
if t.theme != nil && ansi != nil && ansi.colored() { if t.theme.Colored && 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 {
fillRet = t.pwindow.CFill(tui.ColPreview.Fg(), tui.ColPreview.Bg(), tui.AttrRegular, str) fillRet = t.pwindow.CFill(tui.ColPreview.Fg(), tui.ColPreview.Bg(), tui.AttrRegular, str)
} }
return fillRet == tui.FillContinue return fillRet == tui.FillContinue
}) })
t.previewer.more = t.previewer.more || t.pwindow.Y() == height-1 && t.pwindow.X() == t.pwindow.Width() t.previewer.scrollable = t.previewer.scrollable || t.pwindow.Y() == height-1 && t.pwindow.X() == t.pwindow.Width()
if fillRet == tui.FillNextLine { if fillRet == tui.FillNextLine {
continue continue
} else if fillRet == tui.FillSuspend { } else if fillRet == tui.FillSuspend {
t.previewed.filled = true
break
}
if unchanged && lineNo == 0 {
break break
} }
t.pwindow.Fill("\n") t.pwindow.Fill("\n")
} }
if eof { lineNo++
break
}
} }
t.pwindow.FinishFill() if !unchanged {
if t.previewer.lines > height { t.pwindow.FinishFill()
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() {
pos -= 1
}
t.pwindow.Move(0, pos)
t.pwindow.CPrint(tui.ColInfo, tui.Reverse, offset)
} }
} }
func (t *Terminal) printPreview() {
if !t.hasPreviewWindow() {
return
}
numLines := len(t.previewer.lines)
height := t.pwindow.Height()
unchanged := (t.previewed.filled || numLines == t.previewed.numLines) &&
t.previewer.version == t.previewed.version &&
t.previewer.offset == t.previewed.offset
t.previewer.scrollable = t.previewer.offset > 0 || numLines > height
t.renderPreviewText(unchanged)
t.renderPreviewSpinner()
t.previewed.numLines = numLines
t.previewed.version = t.previewer.version
t.previewed.offset = t.previewer.offset
}
func (t *Terminal) printPreviewDelayed() {
if !t.hasPreviewWindow() || len(t.previewer.lines) > 0 && t.previewed.version == t.previewer.version {
return
}
t.previewer.scrollable = false
t.renderPreviewText(true)
message := t.trimMessage("Loading ..", t.pwindow.Width())
pos := t.pwindow.Width() - len(message)
t.pwindow.Move(0, pos)
t.pwindow.CPrint(tui.ColInfo.WithAttr(tui.Reverse), message)
}
func (t *Terminal) processTabs(runes []rune, prefixWidth int) (string, int) { func (t *Terminal) processTabs(runes []rune, prefixWidth int) (string, int) {
var strbuf bytes.Buffer var strbuf bytes.Buffer
l := prefixWidth l := prefixWidth
@@ -1686,9 +1818,11 @@ func (t *Terminal) Loop() {
if t.hasPreviewer() { if t.hasPreviewer() {
go func() { go func() {
var version int64
for { for {
var items []*Item var items []*Item
var commandTemplate string var commandTemplate string
var pwindow tui.Window
t.previewBox.Wait(func(events *util.Events) { t.previewBox.Wait(func(events *util.Events) {
for req, value := range *events { for req, value := range *events {
switch req { switch req {
@@ -1696,63 +1830,136 @@ func (t *Terminal) Loop() {
request := value.(previewRequest) request := value.(previewRequest)
commandTemplate = request.template commandTemplate = request.template
items = request.list items = request.list
pwindow = request.pwindow
} }
} }
events.Clear() events.Clear()
}) })
version++
// We don't display preview window if no match // We don't display preview window if no match
if items[0] != nil { if items[0] != nil {
command := t.replacePlaceholder(commandTemplate, false, string(t.Input()), items) command := t.replacePlaceholder(commandTemplate, false, string(t.Input()), items)
offset := 0 initialOffset := 0
cmd := util.ExecCommand(command, true) cmd := util.ExecCommand(command, true)
if t.pwindow != nil { if pwindow != nil {
height := t.pwindow.Height() height := pwindow.Height()
offset = t.evaluateScrollOffset(items, height) initialOffset = util.Max(0, t.evaluateScrollOffset(items, height))
env := os.Environ() env := os.Environ()
lines := fmt.Sprintf("LINES=%d", height) lines := fmt.Sprintf("LINES=%d", height)
columns := fmt.Sprintf("COLUMNS=%d", t.pwindow.Width()) columns := fmt.Sprintf("COLUMNS=%d", pwindow.Width())
env = append(env, lines) env = append(env, lines)
env = append(env, "FZF_PREVIEW_"+lines) env = append(env, "FZF_PREVIEW_"+lines)
env = append(env, columns) env = append(env, columns)
env = append(env, "FZF_PREVIEW_"+columns) env = append(env, "FZF_PREVIEW_"+columns)
cmd.Env = env cmd.Env = env
} }
var out bytes.Buffer
cmd.Stdout = &out out, _ := cmd.StdoutPipe()
cmd.Stderr = &out cmd.Stderr = cmd.Stdout
err := cmd.Start() reader := bufio.NewReader(out)
if err != nil { eofChan := make(chan bool)
out.Write([]byte(err.Error()))
}
finishChan := make(chan bool, 1) finishChan := make(chan bool, 1)
updateChan := make(chan bool) err := cmd.Start()
go func() { if err == nil {
select { reapChan := make(chan bool)
case code := <-t.killChan: lineChan := make(chan eachLine)
if code != exitCancel { // Goroutine 1 reads process output
util.KillCommand(cmd) go func() {
os.Exit(code) for {
} else { line, err := reader.ReadString('\n')
select { lineChan <- eachLine{line, err}
case <-time.After(previewCancelWait): if err != nil {
util.KillCommand(cmd) break
updateChan <- true
case <-finishChan:
updateChan <- false
} }
} }
case <-finishChan: eofChan <- true
updateChan <- false }()
}
}() // Goroutine 2 periodically requests rendering
cmd.Wait() go func(version int64) {
finishChan <- true lines := []string{}
if out.Len() > 0 || !<-updateChan { spinner := makeSpinner(t.unicode)
t.reqBox.Set(reqPreviewDisplay, previewResult{out.String(), offset}) spinnerIndex := -1 // Delay initial rendering by an extra tick
ticker := time.NewTicker(previewChunkDelay)
offset := initialOffset
Loop:
for {
select {
case <-ticker.C:
if len(lines) > 0 && len(lines) >= initialOffset {
if spinnerIndex >= 0 {
spin := spinner[spinnerIndex%len(spinner)]
t.reqBox.Set(reqPreviewDisplay, previewResult{version, lines, offset, spin})
offset = -1
}
spinnerIndex++
}
case eachLine := <-lineChan:
line := eachLine.line
err := eachLine.err
if len(line) > 0 {
clearIndex := strings.Index(line, clearCode)
if clearIndex >= 0 {
lines = []string{}
line = line[clearIndex+len(clearCode):]
version--
offset = 0
}
lines = append(lines, line)
}
if err != nil {
t.reqBox.Set(reqPreviewDisplay, previewResult{version, lines, offset, ""})
break Loop
}
}
}
ticker.Stop()
reapChan <- true
}(version)
// Goroutine 3 is responsible for cancelling running preview command
go func(version int64) {
timer := time.NewTimer(previewDelayed)
Loop:
for {
select {
case <-timer.C:
t.reqBox.Set(reqPreviewDelayed, version)
case code := <-t.killChan:
if code != exitCancel {
util.KillCommand(cmd)
os.Exit(code)
} else {
timer := time.NewTimer(previewCancelWait)
select {
case <-timer.C:
util.KillCommand(cmd)
case <-finishChan:
}
timer.Stop()
}
break Loop
case <-finishChan:
break Loop
}
}
timer.Stop()
reapChan <- true
}(version)
<-eofChan // Goroutine 1 finished
cmd.Wait() // NOTE: We should not call Wait before EOF
finishChan <- true // Tell Goroutine 3 to stop
<-reapChan // Goroutine 2 and 3 finished
<-reapChan
} else {
// Failed to start the command. Report the error immediately.
t.reqBox.Set(reqPreviewDisplay, previewResult{version, []string{err.Error()}, 0, ""})
} }
cleanTemporaryFiles() cleanTemporaryFiles()
} else { } else {
t.reqBox.Set(reqPreviewDisplay, previewResult{"", 0}) t.reqBox.Set(reqPreviewDisplay, previewResult{version, nil, 0, ""})
} }
} }
}() }()
@@ -1772,7 +1979,7 @@ func (t *Terminal) Loop() {
if len(command) > 0 && t.isPreviewEnabled() { if len(command) > 0 && t.isPreviewEnabled() {
_, list := t.buildPlusList(command, false) _, list := t.buildPlusList(command, false)
t.cancelPreview() t.cancelPreview()
t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, list}) t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.pwindow, list})
} }
} }
@@ -1827,12 +2034,18 @@ func (t *Terminal) Loop() {
}) })
case reqPreviewDisplay: case reqPreviewDisplay:
result := value.(previewResult) result := value.(previewResult)
t.previewer.text = result.content t.previewer.version = result.version
t.previewer.lines = strings.Count(t.previewer.text, "\n") t.previewer.lines = result.lines
t.previewer.offset = util.Constrain(result.offset, 0, t.previewer.lines-1) t.previewer.spinner = result.spinner
if result.offset >= 0 {
t.previewer.offset = util.Constrain(result.offset, 0, len(t.previewer.lines)-1)
}
t.printPreview() t.printPreview()
case reqPreviewRefresh: case reqPreviewRefresh:
t.printPreview() t.printPreview()
case reqPreviewDelayed:
t.previewer.version = value.(int64)
t.printPreviewDelayed()
case reqPrintQuery: case reqPrintQuery:
exit(func() int { exit(func() int {
t.printer(string(t.input)) t.printer(string(t.input))
@@ -1885,14 +2098,15 @@ func (t *Terminal) Loop() {
return false return false
} }
scrollPreview := func(amount int) { scrollPreview := func(amount int) {
if !t.previewer.more { if !t.previewer.scrollable {
return return
} }
newOffset := t.previewer.offset + amount newOffset := t.previewer.offset + amount
numLines := len(t.previewer.lines)
if t.preview.cycle { if t.preview.cycle {
newOffset = (newOffset + t.previewer.lines) % t.previewer.lines newOffset = (newOffset + numLines) % numLines
} }
newOffset = util.Constrain(newOffset, 0, t.previewer.lines-1) newOffset = util.Constrain(newOffset, 0, numLines-1)
if t.previewer.offset != newOffset { if t.previewer.offset != newOffset {
t.previewer.offset = newOffset t.previewer.offset = newOffset
req(reqPreviewRefresh) req(reqPreviewRefresh)
@@ -1934,7 +2148,7 @@ func (t *Terminal) Loop() {
if valid { if valid {
t.cancelPreview() t.cancelPreview()
t.previewBox.Set(reqPreviewEnqueue, t.previewBox.Set(reqPreviewEnqueue,
previewRequest{t.preview.command, list}) previewRequest{t.preview.command, t.pwindow, list})
} }
} }
} }

View File

@@ -15,14 +15,17 @@ func (a Attr) Merge(b Attr) Attr {
} }
const ( const (
AttrRegular Attr = Attr(0) AttrUndefined = Attr(0)
Bold = Attr(1) AttrRegular = Attr(1 << 7)
Dim = Attr(1 << 1) AttrClear = Attr(1 << 8)
Italic = Attr(1 << 2)
Underline = Attr(1 << 3) Bold = Attr(1)
Blink = Attr(1 << 4) Dim = Attr(1 << 1)
Blink2 = Attr(1 << 5) Italic = Attr(1 << 2)
Reverse = Attr(1 << 6) Underline = Attr(1 << 3)
Blink = Attr(1 << 4)
Blink2 = Attr(1 << 5)
Reverse = Attr(1 << 6)
) )
func (r *FullscreenRenderer) Init() {} func (r *FullscreenRenderer) Init() {}
@@ -32,10 +35,9 @@ func (r *FullscreenRenderer) Clear() {}
func (r *FullscreenRenderer) Refresh() {} func (r *FullscreenRenderer) Refresh() {}
func (r *FullscreenRenderer) Close() {} func (r *FullscreenRenderer) Close() {}
func (r *FullscreenRenderer) DoesAutoWrap() bool { return false } func (r *FullscreenRenderer) GetChar() Event { return Event{} }
func (r *FullscreenRenderer) GetChar() Event { return Event{} } func (r *FullscreenRenderer) MaxX() int { return 0 }
func (r *FullscreenRenderer) MaxX() int { return 0 } func (r *FullscreenRenderer) MaxY() int { return 0 }
func (r *FullscreenRenderer) MaxY() int { return 0 }
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {} func (r *FullscreenRenderer) RefreshWindows(windows []Window) {}

View File

@@ -624,14 +624,10 @@ func (r *LightRenderer) MaxY() int {
return r.height return r.height
} }
func (r *LightRenderer) DoesAutoWrap() bool {
return false
}
func (r *LightRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window { func (r *LightRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window {
w := &LightWindow{ w := &LightWindow{
renderer: r, renderer: r,
colored: r.theme != nil, colored: r.theme.Colored,
preview: preview, preview: preview,
border: borderStyle, border: borderStyle,
top: top, top: top,
@@ -641,14 +637,12 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, prev
tabstop: r.tabstop, tabstop: r.tabstop,
fg: colDefault, fg: colDefault,
bg: colDefault} bg: colDefault}
if r.theme != nil { if preview {
if preview { w.fg = r.theme.PreviewFg.Color
w.fg = r.theme.PreviewFg w.bg = r.theme.PreviewBg.Color
w.bg = r.theme.PreviewBg } else {
} else { w.fg = r.theme.Fg.Color
w.fg = r.theme.Fg w.bg = r.theme.Bg.Color
w.bg = r.theme.Bg
}
} }
w.drawBorder() w.drawBorder()
return w return w
@@ -659,15 +653,46 @@ func (w *LightWindow) drawBorder() {
case BorderRounded, BorderSharp: case BorderRounded, BorderSharp:
w.drawBorderAround() w.drawBorderAround()
case BorderHorizontal: case BorderHorizontal:
w.drawBorderHorizontal() w.drawBorderHorizontal(true, true)
case BorderVertical:
w.drawBorderVertical(true, true)
case BorderTop:
w.drawBorderHorizontal(true, false)
case BorderBottom:
w.drawBorderHorizontal(false, true)
case BorderLeft:
w.drawBorderVertical(true, false)
case BorderRight:
w.drawBorderVertical(false, true)
} }
} }
func (w *LightWindow) drawBorderHorizontal() { func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
w.Move(0, 0) if top {
w.CPrint(ColBorder, AttrRegular, repeat(w.border.horizontal, w.width)) w.Move(0, 0)
w.Move(w.height-1, 0) w.CPrint(ColBorder, repeat(w.border.horizontal, w.width))
w.CPrint(ColBorder, AttrRegular, repeat(w.border.horizontal, w.width)) }
if bottom {
w.Move(w.height-1, 0)
w.CPrint(ColBorder, repeat(w.border.horizontal, w.width))
}
}
func (w *LightWindow) drawBorderVertical(left, right bool) {
width := w.width - 2
if !left || !right {
width++
}
for y := 0; y < w.height; y++ {
w.Move(y, 0)
if left {
w.CPrint(ColBorder, string(w.border.vertical))
}
w.CPrint(ColBorder, repeat(' ', width))
if right {
w.CPrint(ColBorder, string(w.border.vertical))
}
}
} }
func (w *LightWindow) drawBorderAround() { func (w *LightWindow) drawBorderAround() {
@@ -676,17 +701,15 @@ func (w *LightWindow) drawBorderAround() {
if w.preview { if w.preview {
color = ColPreviewBorder color = ColPreviewBorder
} }
w.CPrint(color, AttrRegular, w.CPrint(color, string(w.border.topLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.topRight))
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(color, AttrRegular, string(w.border.vertical)) w.CPrint(color, string(w.border.vertical))
w.CPrint(color, AttrRegular, repeat(' ', w.width-2)) w.CPrint(color, repeat(' ', w.width-2))
w.CPrint(color, AttrRegular, string(w.border.vertical)) w.CPrint(color, string(w.border.vertical))
} }
w.Move(w.height-1, 0) w.Move(w.height-1, 0)
w.CPrint(color, AttrRegular, w.CPrint(color, string(w.border.bottomLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.bottomRight))
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) {
@@ -749,6 +772,9 @@ func (w *LightWindow) MoveAndClear(y int, x int) {
func attrCodes(attr Attr) []string { func attrCodes(attr Attr) []string {
codes := []string{} codes := []string{}
if (attr & AttrClear) > 0 {
return codes
}
if (attr & Bold) > 0 { if (attr & Bold) > 0 {
codes = append(codes, "1") codes = append(codes, "1")
} }
@@ -808,12 +834,8 @@ func cleanse(str string) string {
return strings.Replace(str, "\x1b", "", -1) return strings.Replace(str, "\x1b", "", -1)
} }
func (w *LightWindow) CPrint(pair ColorPair, attr Attr, text string) { func (w *LightWindow) CPrint(pair ColorPair, text string) {
if !w.colored { w.csiColor(pair.Fg(), pair.Bg(), pair.Attr())
w.csiColor(colDefault, colDefault, attrFor(pair, attr))
} else {
w.csiColor(pair.Fg(), pair.Bg(), attr)
}
w.stderrInternal(cleanse(text), false) w.stderrInternal(cleanse(text), false)
w.csi("m") w.csi("m")
} }
@@ -835,7 +857,7 @@ func wrapLine(input string, prefixLength int, max int, tabstop int) []wrappedLin
width := 0 width := 0
line := "" line := ""
for _, r := range input { for _, r := range input {
w := util.Max(util.RuneWidth(r, prefixLength+width, 8), 1) w := util.RuneWidth(r, prefixLength+width, 8)
width += w width += w
str := string(r) str := string(r)
if r == '\t' { if r == '\t' {

View File

@@ -77,11 +77,13 @@ const (
Blink = Attr(tcell.AttrBlink) Blink = Attr(tcell.AttrBlink)
Reverse = Attr(tcell.AttrReverse) Reverse = Attr(tcell.AttrReverse)
Underline = Attr(tcell.AttrUnderline) Underline = Attr(tcell.AttrUnderline)
Italic = Attr(tcell.AttrNone) // Not supported Italic = Attr(tcell.AttrItalic)
) )
const ( const (
AttrRegular Attr = 0 AttrUndefined = Attr(0)
AttrRegular = Attr(1 << 7)
AttrClear = Attr(1 << 8)
) )
func (r *FullscreenRenderer) defaultTheme() *ColorTheme { func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
@@ -166,10 +168,6 @@ func (w *TcellWindow) Y() int {
return w.lastY return w.lastY
} }
func (r *FullscreenRenderer) DoesAutoWrap() bool {
return false
}
func (r *FullscreenRenderer) Clear() { func (r *FullscreenRenderer) Clear() {
_screen.Sync() _screen.Sync()
_screen.Clear() _screen.Clear()
@@ -418,7 +416,7 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int,
normal = ColPreview normal = ColPreview
} }
return &TcellWindow{ return &TcellWindow{
color: r.theme != nil, color: r.theme.Colored,
preview: preview, preview: preview,
top: top, top: top,
left: left, left: left,
@@ -464,27 +462,23 @@ func (w *TcellWindow) MoveAndClear(y int, x int) {
} }
func (w *TcellWindow) Print(text string) { func (w *TcellWindow) Print(text string) {
w.printString(text, w.normal, 0) w.printString(text, w.normal)
} }
func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) { func (w *TcellWindow) printString(text string, pair ColorPair) {
t := text t := text
lx := 0 lx := 0
a := pair.Attr()
var style tcell.Style style := pair.style()
if w.color { if a&AttrClear == 0 {
style = pair.style(). style = style.
Reverse(a&Attr(tcell.AttrReverse) != 0). Reverse(a&Attr(tcell.AttrReverse) != 0).
Underline(a&Attr(tcell.AttrUnderline) != 0) Underline(a&Attr(tcell.AttrUnderline) != 0).
} else { Italic(a&Attr(tcell.AttrItalic) != 0).
style = w.normal.style(). Blink(a&Attr(tcell.AttrBlink) != 0).
Reverse(a&Attr(tcell.AttrReverse) != 0 || pair == ColCurrent || pair == ColCurrentMatch). Dim(a&Attr(tcell.AttrDim) != 0)
Underline(a&Attr(tcell.AttrUnderline) != 0 || pair == ColMatch || pair == ColCurrentMatch)
} }
style = style.
Blink(a&Attr(tcell.AttrBlink) != 0).
Bold(a&Attr(tcell.AttrBold) != 0).
Dim(a&Attr(tcell.AttrDim) != 0)
for { for {
if len(t) == 0 { if len(t) == 0 {
@@ -517,12 +511,13 @@ func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) {
w.lastX += lx w.lastX += lx
} }
func (w *TcellWindow) CPrint(pair ColorPair, attr Attr, text string) { func (w *TcellWindow) CPrint(pair ColorPair, text string) {
w.printString(text, pair, attr) w.printString(text, pair)
} }
func (w *TcellWindow) fillString(text string, pair ColorPair, a Attr) FillReturn { func (w *TcellWindow) fillString(text string, pair ColorPair) FillReturn {
lx := 0 lx := 0
a := pair.Attr()
var style tcell.Style var style tcell.Style
if w.color { if w.color {
@@ -535,7 +530,8 @@ func (w *TcellWindow) fillString(text string, pair ColorPair, a Attr) FillReturn
Bold(a&Attr(tcell.AttrBold) != 0). Bold(a&Attr(tcell.AttrBold) != 0).
Dim(a&Attr(tcell.AttrDim) != 0). Dim(a&Attr(tcell.AttrDim) != 0).
Reverse(a&Attr(tcell.AttrReverse) != 0). Reverse(a&Attr(tcell.AttrReverse) != 0).
Underline(a&Attr(tcell.AttrUnderline) != 0) Underline(a&Attr(tcell.AttrUnderline) != 0).
Italic(a&Attr(tcell.AttrItalic) != 0)
for _, r := range text { for _, r := range text {
if r == '\n' { if r == '\n' {
@@ -563,12 +559,17 @@ func (w *TcellWindow) fillString(text string, pair ColorPair, a Attr) FillReturn
} }
} }
w.lastX += lx w.lastX += lx
if w.lastX == w.width {
w.lastY++
w.lastX = 0
return FillNextLine
}
return FillContinue return FillContinue
} }
func (w *TcellWindow) Fill(str string) FillReturn { func (w *TcellWindow) Fill(str string) FillReturn {
return w.fillString(str, w.normal, 0) return w.fillString(str, w.normal)
} }
func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn { func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
@@ -578,11 +579,12 @@ func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
if bg == colDefault { if bg == colDefault {
bg = w.normal.Bg() bg = w.normal.Bg()
} }
return w.fillString(str, NewColorPair(fg, bg), a) return w.fillString(str, NewColorPair(fg, bg, a))
} }
func (w *TcellWindow) drawBorder() { func (w *TcellWindow) drawBorder() {
if w.borderStyle.shape == BorderNone { shape := w.borderStyle.shape
if shape == BorderNone {
return return
} }
@@ -602,17 +604,32 @@ func (w *TcellWindow) drawBorder() {
style = w.normal.style() style = w.normal.style()
} }
for x := left; x < right; x++ { switch shape {
_screen.SetContent(x, top, w.borderStyle.horizontal, nil, style) case BorderRounded, BorderSharp, BorderHorizontal, BorderTop:
_screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style) for x := left; x < right; x++ {
_screen.SetContent(x, top, w.borderStyle.horizontal, nil, style)
}
} }
switch shape {
if w.borderStyle.shape != BorderHorizontal { case BorderRounded, BorderSharp, BorderHorizontal, BorderBottom:
for x := left; x < right; x++ {
_screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style)
}
}
switch shape {
case BorderRounded, BorderSharp, BorderVertical, BorderLeft:
for y := top; y < bot; y++ { for y := top; y < bot; y++ {
_screen.SetContent(left, y, w.borderStyle.vertical, nil, style) _screen.SetContent(left, y, w.borderStyle.vertical, nil, style)
}
}
switch shape {
case BorderRounded, BorderSharp, BorderVertical, BorderRight:
for y := top; y < bot; y++ {
_screen.SetContent(right-1, y, w.borderStyle.vertical, nil, style) _screen.SetContent(right-1, y, w.borderStyle.vertical, nil, style)
} }
}
switch shape {
case BorderRounded, BorderSharp:
_screen.SetContent(left, top, w.borderStyle.topLeft, nil, style) _screen.SetContent(left, top, w.borderStyle.topLeft, nil, style)
_screen.SetContent(right-1, top, w.borderStyle.topRight, nil, style) _screen.SetContent(right-1, top, w.borderStyle.topRight, nil, style)
_screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style) _screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style)

View File

@@ -123,6 +123,15 @@ func (c Color) is24() bool {
return c > 0 && (c&(1<<24)) > 0 return c > 0 && (c&(1<<24)) > 0
} }
type ColorAttr struct {
Color Color
Attr Attr
}
func NewColorAttr() ColorAttr {
return ColorAttr{Color: colUndefined, Attr: AttrUndefined}
}
const ( const (
colUndefined Color = -2 colUndefined Color = -2
colDefault Color = -1 colDefault Color = -1
@@ -148,9 +157,9 @@ const (
) )
type ColorPair struct { type ColorPair struct {
fg Color fg Color
bg Color bg Color
id int attr Attr
} }
func HexToColor(rrggbb string) Color { func HexToColor(rrggbb string) Color {
@@ -160,8 +169,8 @@ func HexToColor(rrggbb string) Color {
return Color((1 << 24) + (r << 16) + (g << 8) + b) return Color((1 << 24) + (r << 16) + (g << 8) + b)
} }
func NewColorPair(fg Color, bg Color) ColorPair { func NewColorPair(fg Color, bg Color, attr Attr) ColorPair {
return ColorPair{fg, bg, -1} return ColorPair{fg, bg, attr}
} }
func (p ColorPair) Fg() Color { func (p ColorPair) Fg() Color {
@@ -172,23 +181,59 @@ func (p ColorPair) Bg() Color {
return p.bg return p.bg
} }
func (p ColorPair) Attr() Attr {
return p.attr
}
func (p ColorPair) merge(other ColorPair, except Color) ColorPair {
dup := p
dup.attr = dup.attr.Merge(other.attr)
if other.fg != except {
dup.fg = other.fg
}
if other.bg != except {
dup.bg = other.bg
}
return dup
}
func (p ColorPair) WithAttr(attr Attr) ColorPair {
dup := p
dup.attr = dup.attr.Merge(attr)
return dup
}
func (p ColorPair) MergeAttr(other ColorPair) ColorPair {
return p.WithAttr(other.attr)
}
func (p ColorPair) Merge(other ColorPair) ColorPair {
return p.merge(other, colUndefined)
}
func (p ColorPair) MergeNonDefault(other ColorPair) ColorPair {
return p.merge(other, colDefault)
}
type ColorTheme struct { type ColorTheme struct {
Fg Color Colored bool
Bg Color Input ColorAttr
PreviewFg Color Fg ColorAttr
PreviewBg Color Bg ColorAttr
DarkBg Color PreviewFg ColorAttr
Gutter Color PreviewBg ColorAttr
Prompt Color DarkBg ColorAttr
Match Color Gutter ColorAttr
Current Color Prompt ColorAttr
CurrentMatch Color Match ColorAttr
Spinner Color Current ColorAttr
Info Color CurrentMatch ColorAttr
Cursor Color Spinner ColorAttr
Selected Color Info ColorAttr
Header Color Cursor ColorAttr
Border Color Selected ColorAttr
Header ColorAttr
Border ColorAttr
} }
type Event struct { type Event struct {
@@ -214,6 +259,11 @@ const (
BorderRounded BorderRounded
BorderSharp BorderSharp
BorderHorizontal BorderHorizontal
BorderVertical
BorderTop
BorderBottom
BorderLeft
BorderRight
) )
type BorderStyle struct { type BorderStyle struct {
@@ -286,7 +336,6 @@ type Renderer interface {
MaxX() int MaxX() int
MaxY() int MaxY() int
DoesAutoWrap() bool
NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window
} }
@@ -308,7 +357,7 @@ type Window interface {
Move(y int, x int) Move(y int, x int)
MoveAndClear(y int, x int) MoveAndClear(y int, x int)
Print(text string) Print(text string)
CPrint(color ColorPair, attr Attr, text string) CPrint(color ColorPair, text string)
Fill(text string) FillReturn Fill(text string) FillReturn
CFill(fg Color, bg Color, attr Attr, text string) FillReturn CFill(fg Color, bg Color, attr Attr, text string) FillReturn
Erase() Erase()
@@ -337,41 +386,69 @@ var (
Dark256 *ColorTheme Dark256 *ColorTheme
Light256 *ColorTheme Light256 *ColorTheme
ColPrompt ColorPair ColPrompt ColorPair
ColNormal ColorPair ColNormal ColorPair
ColMatch ColorPair ColInput ColorPair
ColCursor ColorPair ColMatch ColorPair
ColSelected ColorPair ColCursor ColorPair
ColCurrent ColorPair ColCursorEmpty ColorPair
ColCurrentMatch ColorPair ColSelected ColorPair
ColCurrentCursor ColorPair ColCurrent ColorPair
ColCurrentSelected ColorPair ColCurrentMatch ColorPair
ColSpinner ColorPair ColCurrentCursor ColorPair
ColInfo ColorPair ColCurrentCursorEmpty ColorPair
ColHeader ColorPair ColCurrentSelected ColorPair
ColBorder ColorPair ColCurrentSelectedEmpty ColorPair
ColPreview ColorPair ColSpinner ColorPair
ColPreviewBorder ColorPair ColInfo ColorPair
ColHeader ColorPair
ColBorder ColorPair
ColPreview ColorPair
ColPreviewBorder ColorPair
) )
func EmptyTheme() *ColorTheme { func EmptyTheme() *ColorTheme {
return &ColorTheme{ return &ColorTheme{
Fg: colUndefined, Colored: true,
Bg: colUndefined, Input: ColorAttr{colUndefined, AttrUndefined},
PreviewFg: colUndefined, Fg: ColorAttr{colUndefined, AttrUndefined},
PreviewBg: colUndefined, Bg: ColorAttr{colUndefined, AttrUndefined},
DarkBg: colUndefined, PreviewFg: ColorAttr{colUndefined, AttrUndefined},
Gutter: colUndefined, PreviewBg: ColorAttr{colUndefined, AttrUndefined},
Prompt: colUndefined, DarkBg: ColorAttr{colUndefined, AttrUndefined},
Match: colUndefined, Gutter: ColorAttr{colUndefined, AttrUndefined},
Current: colUndefined, Prompt: ColorAttr{colUndefined, AttrUndefined},
CurrentMatch: colUndefined, Match: ColorAttr{colUndefined, AttrUndefined},
Spinner: colUndefined, Current: ColorAttr{colUndefined, AttrUndefined},
Info: colUndefined, CurrentMatch: ColorAttr{colUndefined, AttrUndefined},
Cursor: colUndefined, Spinner: ColorAttr{colUndefined, AttrUndefined},
Selected: colUndefined, Info: ColorAttr{colUndefined, AttrUndefined},
Header: colUndefined, Cursor: ColorAttr{colUndefined, AttrUndefined},
Border: colUndefined} Selected: ColorAttr{colUndefined, AttrUndefined},
Header: ColorAttr{colUndefined, AttrUndefined},
Border: ColorAttr{colUndefined, AttrUndefined}}
}
func NoColorTheme() *ColorTheme {
return &ColorTheme{
Colored: false,
Input: ColorAttr{colDefault, AttrRegular},
Fg: ColorAttr{colDefault, AttrRegular},
Bg: ColorAttr{colDefault, AttrRegular},
PreviewFg: ColorAttr{colDefault, AttrRegular},
PreviewBg: ColorAttr{colDefault, AttrRegular},
DarkBg: ColorAttr{colDefault, AttrRegular},
Gutter: ColorAttr{colDefault, AttrRegular},
Prompt: ColorAttr{colDefault, AttrRegular},
Match: ColorAttr{colDefault, Underline},
Current: ColorAttr{colDefault, Reverse},
CurrentMatch: ColorAttr{colDefault, Reverse | Underline},
Spinner: ColorAttr{colDefault, AttrRegular},
Info: ColorAttr{colDefault, AttrRegular},
Cursor: ColorAttr{colDefault, AttrRegular},
Selected: ColorAttr{colDefault, AttrRegular},
Header: ColorAttr{colDefault, AttrRegular},
Border: ColorAttr{colDefault, AttrRegular}}
} }
func errorExit(message string) { func errorExit(message string) {
@@ -381,74 +458,80 @@ func errorExit(message string) {
func init() { func init() {
Default16 = &ColorTheme{ Default16 = &ColorTheme{
Fg: colDefault, Colored: true,
Bg: colDefault, Input: ColorAttr{colDefault, AttrUndefined},
PreviewFg: colUndefined, Fg: ColorAttr{colDefault, AttrUndefined},
PreviewBg: colUndefined, Bg: ColorAttr{colDefault, AttrUndefined},
DarkBg: colBlack, PreviewFg: ColorAttr{colUndefined, AttrUndefined},
Gutter: colUndefined, PreviewBg: ColorAttr{colUndefined, AttrUndefined},
Prompt: colBlue, DarkBg: ColorAttr{colBlack, AttrUndefined},
Match: colGreen, Gutter: ColorAttr{colUndefined, AttrUndefined},
Current: colYellow, Prompt: ColorAttr{colBlue, AttrUndefined},
CurrentMatch: colGreen, Match: ColorAttr{colGreen, AttrUndefined},
Spinner: colGreen, Current: ColorAttr{colYellow, AttrUndefined},
Info: colWhite, CurrentMatch: ColorAttr{colGreen, AttrUndefined},
Cursor: colRed, Spinner: ColorAttr{colGreen, AttrUndefined},
Selected: colMagenta, Info: ColorAttr{colWhite, AttrUndefined},
Header: colCyan, Cursor: ColorAttr{colRed, AttrUndefined},
Border: colBlack} Selected: ColorAttr{colMagenta, AttrUndefined},
Header: ColorAttr{colCyan, AttrUndefined},
Border: ColorAttr{colBlack, AttrUndefined}}
Dark256 = &ColorTheme{ Dark256 = &ColorTheme{
Fg: colDefault, Colored: true,
Bg: colDefault, Input: ColorAttr{colDefault, AttrUndefined},
PreviewFg: colUndefined, Fg: ColorAttr{colDefault, AttrUndefined},
PreviewBg: colUndefined, Bg: ColorAttr{colDefault, AttrUndefined},
DarkBg: 236, PreviewFg: ColorAttr{colUndefined, AttrUndefined},
Gutter: colUndefined, PreviewBg: ColorAttr{colUndefined, AttrUndefined},
Prompt: 110, DarkBg: ColorAttr{236, AttrUndefined},
Match: 108, Gutter: ColorAttr{colUndefined, AttrUndefined},
Current: 254, Prompt: ColorAttr{110, AttrUndefined},
CurrentMatch: 151, Match: ColorAttr{108, AttrUndefined},
Spinner: 148, Current: ColorAttr{254, AttrUndefined},
Info: 144, CurrentMatch: ColorAttr{151, AttrUndefined},
Cursor: 161, Spinner: ColorAttr{148, AttrUndefined},
Selected: 168, Info: ColorAttr{144, AttrUndefined},
Header: 109, Cursor: ColorAttr{161, AttrUndefined},
Border: 59} Selected: ColorAttr{168, AttrUndefined},
Header: ColorAttr{109, AttrUndefined},
Border: ColorAttr{59, AttrUndefined}}
Light256 = &ColorTheme{ Light256 = &ColorTheme{
Fg: colDefault, Colored: true,
Bg: colDefault, Input: ColorAttr{colDefault, AttrUndefined},
PreviewFg: colUndefined, Fg: ColorAttr{colDefault, AttrUndefined},
PreviewBg: colUndefined, Bg: ColorAttr{colDefault, AttrUndefined},
DarkBg: 251, PreviewFg: ColorAttr{colUndefined, AttrUndefined},
Gutter: colUndefined, PreviewBg: ColorAttr{colUndefined, AttrUndefined},
Prompt: 25, DarkBg: ColorAttr{251, AttrUndefined},
Match: 66, Gutter: ColorAttr{colUndefined, AttrUndefined},
Current: 237, Prompt: ColorAttr{25, AttrUndefined},
CurrentMatch: 23, Match: ColorAttr{66, AttrUndefined},
Spinner: 65, Current: ColorAttr{237, AttrUndefined},
Info: 101, CurrentMatch: ColorAttr{23, AttrUndefined},
Cursor: 161, Spinner: ColorAttr{65, AttrUndefined},
Selected: 168, Info: ColorAttr{101, AttrUndefined},
Header: 31, Cursor: ColorAttr{161, AttrUndefined},
Border: 145} Selected: ColorAttr{168, AttrUndefined},
Header: ColorAttr{31, AttrUndefined},
Border: ColorAttr{145, AttrUndefined}}
} }
func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) { func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
if theme == nil {
initPalette(theme)
return
}
if forceBlack { if forceBlack {
theme.Bg = colBlack theme.Bg = ColorAttr{colBlack, AttrUndefined}
} }
o := func(a Color, b Color) Color { o := func(a ColorAttr, b ColorAttr) ColorAttr {
if b == colUndefined { c := a
return a if b.Color != colUndefined {
c.Color = b.Color
} }
return b if b.Attr != AttrUndefined {
c.Attr = b.Attr
}
return c
} }
theme.Input = o(baseTheme.Input, theme.Input)
theme.Fg = o(baseTheme.Fg, theme.Fg) theme.Fg = o(baseTheme.Fg, theme.Fg)
theme.Bg = o(baseTheme.Bg, theme.Bg) theme.Bg = o(baseTheme.Bg, theme.Bg)
theme.PreviewFg = o(theme.Fg, o(baseTheme.PreviewFg, theme.PreviewFg)) theme.PreviewFg = o(theme.Fg, o(baseTheme.PreviewFg, theme.PreviewFg))
@@ -470,54 +553,32 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
} }
func initPalette(theme *ColorTheme) { func initPalette(theme *ColorTheme) {
idx := 0 pair := func(fg, bg ColorAttr) ColorPair {
pair := func(fg, bg Color) ColorPair { if fg.Color == colDefault && (fg.Attr&Reverse) > 0 {
idx++ bg.Color = colDefault
return ColorPair{fg, bg, idx} }
return ColorPair{fg.Color, bg.Color, fg.Attr}
} }
if theme != nil { blank := theme.Fg
ColPrompt = pair(theme.Prompt, theme.Bg) blank.Attr = AttrRegular
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)
ColHeader = pair(theme.Header, theme.Bg)
ColBorder = pair(theme.Border, theme.Bg)
ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
ColPreviewBorder = pair(theme.Border, theme.PreviewBg)
} else {
ColPrompt = pair(colDefault, colDefault)
ColNormal = pair(colDefault, colDefault)
ColMatch = pair(colDefault, colDefault)
ColCursor = pair(colDefault, colDefault)
ColSelected = pair(colDefault, colDefault)
ColCurrent = pair(colDefault, colDefault)
ColCurrentMatch = pair(colDefault, colDefault)
ColCurrentCursor = pair(colDefault, colDefault)
ColCurrentSelected = pair(colDefault, colDefault)
ColSpinner = pair(colDefault, colDefault)
ColInfo = pair(colDefault, colDefault)
ColHeader = pair(colDefault, colDefault)
ColBorder = pair(colDefault, colDefault)
ColPreview = pair(colDefault, colDefault)
ColPreviewBorder = pair(colDefault, colDefault)
}
}
func attrFor(color ColorPair, attr Attr) Attr { ColPrompt = pair(theme.Prompt, theme.Bg)
switch color { ColNormal = pair(theme.Fg, theme.Bg)
case ColCurrent: ColInput = pair(theme.Input, theme.Bg)
return attr | Reverse ColMatch = pair(theme.Match, theme.Bg)
case ColMatch: ColCursor = pair(theme.Cursor, theme.Gutter)
return attr | Underline ColCursorEmpty = pair(blank, theme.Gutter)
case ColCurrentMatch: ColSelected = pair(theme.Selected, theme.Gutter)
return attr | Underline | Reverse ColCurrent = pair(theme.Current, theme.DarkBg)
} ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg)
return attr ColCurrentCursor = pair(theme.Cursor, theme.DarkBg)
ColCurrentCursorEmpty = pair(blank, theme.DarkBg)
ColCurrentSelected = pair(theme.Selected, theme.DarkBg)
ColCurrentSelectedEmpty = pair(blank, theme.DarkBg)
ColSpinner = pair(theme.Spinner, theme.Bg)
ColInfo = pair(theme.Info, theme.Bg)
ColHeader = pair(theme.Header, theme.Bg)
ColBorder = pair(theme.Border, theme.Bg)
ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
ColPreviewBorder = pair(theme.Border, theme.PreviewBg)
} }

View File

@@ -1,47 +0,0 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
# http://www.rubydoc.info/github/rest-client/rest-client/RestClient
require 'rest_client'
require 'json'
if ARGV.length < 3
puts "usage: #{$PROGRAM_NAME} <token> <version> <files...>"
exit 1
end
token, version, *files = ARGV
base = 'https://api.github.com/repos/junegunn/fzf-bin/releases'
# List releases
rels = JSON.parse(RestClient.get(base, authorization: "token #{token}"))
rel = rels.find { |r| r['tag_name'] == version }
unless rel
puts "#{version} not found"
exit 1
end
# List assets
assets = Hash[rel['assets'].map { |a| a.values_at('name', 'id') }]
files.select { |f| File.exist?(f) }.map do |file|
Thread.new do
name = File.basename(file)
if asset_id = assets[name] # rubocop:todo Lint/AssignmentInCondition
puts "#{name} found. Deleting asset id #{asset_id}."
RestClient.delete("#{base}/assets/#{asset_id}",
authorization: "token #{token}")
else
puts "#{name} not found"
end
puts "Uploading #{name}"
RestClient.post(
"#{base.sub('api', 'uploads')}/#{rel['id']}/assets?name=#{name}",
File.read(file),
authorization: "token #{token}",
content_type: 'application/octet-stream'
)
end
end.each(&:join)

View File

@@ -11,12 +11,13 @@ require 'tempfile'
TEMPLATE = DATA.read TEMPLATE = DATA.read
UNSETS = %w[ UNSETS = %w[
FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS
FZF_TMUX FZF_TMUX_OPTS
FZF_CTRL_T_COMMAND FZF_CTRL_T_OPTS FZF_CTRL_T_COMMAND FZF_CTRL_T_OPTS
FZF_ALT_C_COMMAND FZF_ALT_C_COMMAND
FZF_ALT_C_OPTS FZF_CTRL_R_OPTS FZF_ALT_C_OPTS FZF_CTRL_R_OPTS
fish_history fish_history
].freeze ].freeze
DEFAULT_TIMEOUT = 20 DEFAULT_TIMEOUT = 10
FILE = File.expand_path(__FILE__) FILE = File.expand_path(__FILE__)
BASE = File.expand_path('..', __dir__) BASE = File.expand_path('..', __dir__)
@@ -26,7 +27,7 @@ FZF = "FZF_DEFAULT_OPTS= FZF_DEFAULT_COMMAND= #{BASE}/bin/fzf"
def wait def wait
since = Time.now since = Time.now
begin begin
yield yield or raise Minitest::Assertion, 'Assertion failure'
rescue Minitest::Assertion rescue Minitest::Assertion
raise if Time.now - since > DEFAULT_TIMEOUT raise if Time.now - since > DEFAULT_TIMEOUT
@@ -77,7 +78,7 @@ class Tmux
return unless shell == :fish return unless shell == :fish
send_keys 'function fish_prompt; end; clear', :Enter send_keys 'function fish_prompt; end; clear', :Enter
self.until { |lines| raise Minitest::Assertion unless lines.empty? } self.until(&:empty?)
end end
def kill def kill
@@ -108,7 +109,7 @@ class Tmux
class << lines class << lines
def counts def counts
lazy lazy
.map { |l| l.scan(%r{^. ([0-9]+)\/([0-9]+)( \(([0-9]+)\))?}) } .map { |l| l.scan(%r{^. ([0-9]+)/([0-9]+)( \(([0-9]+)\))?}) }
.reject(&:empty?) .reject(&:empty?)
.first&.first&.map(&:to_i)&.values_at(0, 1, 3) || [0, 0, 0] .first&.first&.map(&:to_i)&.values_at(0, 1, 3) || [0, 0, 0]
end end
@@ -149,7 +150,7 @@ class Tmux
begin begin
self.until do |lines| self.until do |lines|
send_keys ' ', 'C-u', :Enter, 'hello', :Left, :Right send_keys ' ', 'C-u', :Enter, 'hello', :Left, :Right
raise Minitest::Assertion unless lines[-1] == 'hello' lines[-1] == 'hello'
end end
rescue Minitest::Assertion rescue Minitest::Assertion
(tries += 1) < 5 ? retry : raise (tries += 1) < 5 ? retry : raise
@@ -420,7 +421,7 @@ class TestGoFZF < TestBase
echo ' first second third/') | echo ' first second third/') |
#{fzf(multi && :multi, :x, :nth, 2, :with_nth, '2,-1,1')}", #{fzf(multi && :multi, :x, :nth, 2, :with_nth, '2,-1,1')}",
:Enter :Enter
tmux.until { |lines| assert_equal ' 2/2', lines[-2] } tmux.until { |lines| assert_equal multi ? ' 2/2 (0)' : ' 2/2', lines[-2] }
# Transformed list # Transformed list
lines = tmux.capture lines = tmux.capture
@@ -485,7 +486,7 @@ class TestGoFZF < TestBase
tmux.send_keys "seq 1 100 | #{fzf!(:multi)} | awk '{print $1 $1}' | #{fzf(:sync)}", :Enter tmux.send_keys "seq 1 100 | #{fzf!(:multi)} | awk '{print $1 $1}' | #{fzf(:sync)}", :Enter
tmux.until { |lines| assert_equal '>', lines[-1] } tmux.until { |lines| assert_equal '>', lines[-1] }
tmux.send_keys 9 tmux.send_keys 9
tmux.until { |lines| assert_equal ' 19/100', lines[-2] } tmux.until { |lines| assert_equal ' 19/100 (0)', lines[-2] }
tmux.send_keys :BTab, :BTab, :BTab tmux.send_keys :BTab, :BTab, :BTab
tmux.until { |lines| assert_equal ' 19/100 (3)', lines[-2] } tmux.until { |lines| assert_equal ' 19/100 (3)', lines[-2] }
tmux.send_keys :Enter tmux.send_keys :Enter
@@ -496,7 +497,7 @@ class TestGoFZF < TestBase
def test_tac def test_tac
tmux.send_keys "seq 1 1000 | #{fzf(:tac, :multi)}", :Enter tmux.send_keys "seq 1 1000 | #{fzf(:tac, :multi)}", :Enter
tmux.until { |lines| assert_equal ' 1000/1000', lines[-2] } tmux.until { |lines| assert_equal ' 1000/1000 (0)', lines[-2] }
tmux.send_keys :BTab, :BTab, :BTab tmux.send_keys :BTab, :BTab, :BTab
tmux.until { |lines| assert_equal ' 1000/1000 (3)', lines[-2] } tmux.until { |lines| assert_equal ' 1000/1000 (3)', lines[-2] }
tmux.send_keys :Enter tmux.send_keys :Enter
@@ -505,9 +506,9 @@ class TestGoFZF < TestBase
def test_tac_sort def test_tac_sort
tmux.send_keys "seq 1 1000 | #{fzf(:tac, :multi)}", :Enter tmux.send_keys "seq 1 1000 | #{fzf(:tac, :multi)}", :Enter
tmux.until { |lines| assert_equal ' 1000/1000', lines[-2] } tmux.until { |lines| assert_equal ' 1000/1000 (0)', lines[-2] }
tmux.send_keys '99' tmux.send_keys '99'
tmux.until { |lines| assert_equal ' 28/1000', lines[-2] } tmux.until { |lines| assert_equal ' 28/1000 (0)', lines[-2] }
tmux.send_keys :BTab, :BTab, :BTab tmux.send_keys :BTab, :BTab, :BTab
tmux.until { |lines| assert_equal ' 28/1000 (3)', lines[-2] } tmux.until { |lines| assert_equal ' 28/1000 (3)', lines[-2] }
tmux.send_keys :Enter tmux.send_keys :Enter
@@ -516,9 +517,9 @@ class TestGoFZF < TestBase
def test_tac_nosort def test_tac_nosort
tmux.send_keys "seq 1 1000 | #{fzf(:tac, :no_sort, :multi)}", :Enter tmux.send_keys "seq 1 1000 | #{fzf(:tac, :no_sort, :multi)}", :Enter
tmux.until { |lines| assert_equal ' 1000/1000', lines[-2] } tmux.until { |lines| assert_equal ' 1000/1000 (0)', lines[-2] }
tmux.send_keys '00' tmux.send_keys '00'
tmux.until { |lines| assert_equal ' 10/1000', lines[-2] } tmux.until { |lines| assert_equal ' 10/1000 (0)', lines[-2] }
tmux.send_keys :BTab, :BTab, :BTab tmux.send_keys :BTab, :BTab, :BTab
tmux.until { |lines| assert_equal ' 10/1000 (3)', lines[-2] } tmux.until { |lines| assert_equal ' 10/1000 (3)', lines[-2] }
tmux.send_keys :Enter tmux.send_keys :Enter
@@ -800,14 +801,14 @@ class TestGoFZF < TestBase
def test_bind def test_bind
tmux.send_keys "seq 1 1000 | #{fzf('-m --bind=ctrl-j:accept,u:up,T:toggle-up,t:toggle')}", :Enter tmux.send_keys "seq 1 1000 | #{fzf('-m --bind=ctrl-j:accept,u:up,T:toggle-up,t:toggle')}", :Enter
tmux.until { |lines| assert_equal ' 1000/1000', lines[-2] } tmux.until { |lines| assert_equal ' 1000/1000 (0)', lines[-2] }
tmux.send_keys 'uuu', 'TTT', 'tt', 'uu', 'ttt', 'C-j' tmux.send_keys 'uuu', 'TTT', 'tt', 'uu', 'ttt', 'C-j'
assert_equal %w[4 5 6 9], readonce.lines(chomp: true) assert_equal %w[4 5 6 9], readonce.lines(chomp: true)
end end
def test_bind_print_query def test_bind_print_query
tmux.send_keys "seq 1 1000 | #{fzf('-m --bind=ctrl-j:print-query')}", :Enter tmux.send_keys "seq 1 1000 | #{fzf('-m --bind=ctrl-j:print-query')}", :Enter
tmux.until { |lines| assert_equal ' 1000/1000', lines[-2] } tmux.until { |lines| assert_equal ' 1000/1000 (0)', lines[-2] }
tmux.send_keys 'print-my-query', 'C-j' tmux.send_keys 'print-my-query', 'C-j'
assert_equal %w[print-my-query], readonce.lines(chomp: true) assert_equal %w[print-my-query], readonce.lines(chomp: true)
end end
@@ -839,7 +840,7 @@ class TestGoFZF < TestBase
def test_select_all_deselect_all_toggle_all def test_select_all_deselect_all_toggle_all
tmux.send_keys "seq 100 | #{fzf('--bind ctrl-a:select-all,ctrl-d:deselect-all,ctrl-t:toggle-all --multi')}", :Enter tmux.send_keys "seq 100 | #{fzf('--bind ctrl-a:select-all,ctrl-d:deselect-all,ctrl-t:toggle-all --multi')}", :Enter
tmux.until { |lines| assert_equal ' 100/100', lines[-2] } tmux.until { |lines| assert_equal ' 100/100 (0)', lines[-2] }
tmux.send_keys :BTab, :BTab, :BTab tmux.send_keys :BTab, :BTab, :BTab
tmux.until { |lines| assert_equal ' 100/100 (3)', lines[-2] } tmux.until { |lines| assert_equal ' 100/100 (3)', lines[-2] }
tmux.send_keys 'C-t' tmux.send_keys 'C-t'
@@ -855,7 +856,7 @@ class TestGoFZF < TestBase
tmux.send_keys 'C-u' tmux.send_keys 'C-u'
tmux.until { |lines| assert_equal 100, lines.match_count } tmux.until { |lines| assert_equal 100, lines.match_count }
tmux.send_keys 'C-d' tmux.send_keys 'C-d'
tmux.until { |lines| assert_equal ' 100/100', lines[-2] } tmux.until { |lines| assert_equal ' 100/100 (0)', lines[-2] }
tmux.send_keys :BTab, :BTab tmux.send_keys :BTab, :BTab
tmux.until { |lines| assert_equal ' 100/100 (2)', lines[-2] } tmux.until { |lines| assert_equal ' 100/100 (2)', lines[-2] }
tmux.send_keys 0 tmux.send_keys 0
@@ -962,7 +963,7 @@ class TestGoFZF < TestBase
opts = %[--multi --bind "alt-a:execute-multi(echo {}/{+} >> #{output})"] opts = %[--multi --bind "alt-a:execute-multi(echo {}/{+} >> #{output})"]
writelines(tempname, %w[foo'bar foo"bar foo$bar foobar]) writelines(tempname, %w[foo'bar foo"bar foo$bar foobar])
tmux.send_keys "cat #{tempname} | #{fzf(opts)}", :Enter tmux.send_keys "cat #{tempname} | #{fzf(opts)}", :Enter
tmux.until { |lines| assert_equal ' 4/4', lines[-2] } tmux.until { |lines| assert_equal ' 4/4 (0)', lines[-2] }
tmux.send_keys :Escape, :a tmux.send_keys :Escape, :a
tmux.send_keys :BTab, :BTab, :BTab tmux.send_keys :BTab, :BTab, :BTab
tmux.until { |lines| assert_equal ' 4/4 (3)', lines[-2] } tmux.until { |lines| assert_equal ' 4/4 (3)', lines[-2] }
@@ -997,11 +998,11 @@ class TestGoFZF < TestBase
tmux.send_keys "cat #{tempname} | #{FZF} --multi --bind 'x:execute-silent(echo {+}/{}/{+2}/{2} >> #{output})'", :Enter tmux.send_keys "cat #{tempname} | #{FZF} --multi --bind 'x:execute-silent(echo {+}/{}/{+2}/{2} >> #{output})'", :Enter
tmux.until { |lines| assert_equal ' 2/2', lines[-2] } tmux.until { |lines| assert_equal ' 2/2 (0)', lines[-2] }
tmux.send_keys 'xy' tmux.send_keys 'xy'
tmux.until { |lines| assert_equal ' 0/2', lines[-2] } tmux.until { |lines| assert_equal ' 0/2 (0)', lines[-2] }
tmux.send_keys :BSpace tmux.send_keys :BSpace
tmux.until { |lines| assert_equal ' 2/2', lines[-2] } tmux.until { |lines| assert_equal ' 2/2 (0)', lines[-2] }
tmux.send_keys :Up tmux.send_keys :Up
tmux.send_keys :Tab tmux.send_keys :Tab
@@ -1362,7 +1363,7 @@ class TestGoFZF < TestBase
def test_jump def test_jump
tmux.send_keys "seq 1000 | #{fzf("--multi --jump-labels 12345 --bind 'ctrl-j:jump'")}", :Enter tmux.send_keys "seq 1000 | #{fzf("--multi --jump-labels 12345 --bind 'ctrl-j:jump'")}", :Enter
tmux.until { |lines| assert_equal ' 1000/1000', lines[-2] } tmux.until { |lines| assert_equal ' 1000/1000 (0)', lines[-2] }
tmux.send_keys 'C-j' tmux.send_keys 'C-j'
tmux.until { |lines| assert_equal '5 5', lines[-7] } tmux.until { |lines| assert_equal '5 5', lines[-7] }
tmux.until { |lines| assert_equal ' 6', lines[-8] } tmux.until { |lines| assert_equal ' 6', lines[-8] }
@@ -1390,7 +1391,7 @@ class TestGoFZF < TestBase
def test_jump_accept def test_jump_accept
tmux.send_keys "seq 1000 | #{fzf("--multi --jump-labels 12345 --bind 'ctrl-j:jump-accept'")}", :Enter tmux.send_keys "seq 1000 | #{fzf("--multi --jump-labels 12345 --bind 'ctrl-j:jump-accept'")}", :Enter
tmux.until { |lines| assert_equal ' 1000/1000', lines[-2] } tmux.until { |lines| assert_equal ' 1000/1000 (0)', lines[-2] }
tmux.send_keys 'C-j' tmux.send_keys 'C-j'
tmux.until { |lines| assert_equal '5 5', lines[-7] } tmux.until { |lines| assert_equal '5 5', lines[-7] }
tmux.send_keys '3' tmux.send_keys '3'
@@ -1405,7 +1406,7 @@ class TestGoFZF < TestBase
def test_pointer_with_jump def test_pointer_with_jump
tmux.send_keys "seq 10 | #{fzf("--multi --jump-labels 12345 --bind 'ctrl-j:jump' --pointer '>>'")}", :Enter tmux.send_keys "seq 10 | #{fzf("--multi --jump-labels 12345 --bind 'ctrl-j:jump' --pointer '>>'")}", :Enter
tmux.until { |lines| assert_equal ' 10/10', lines[-2] } tmux.until { |lines| assert_equal ' 10/10 (0)', lines[-2] }
tmux.send_keys 'C-j' tmux.send_keys 'C-j'
# Correctly padded jump label should appear # Correctly padded jump label should appear
tmux.until { |lines| assert_equal '5 5', lines[-7] } tmux.until { |lines| assert_equal '5 5', lines[-7] }
@@ -1417,7 +1418,7 @@ class TestGoFZF < TestBase
def test_marker def test_marker
tmux.send_keys "seq 10 | #{fzf("--multi --marker '>>'")}", :Enter tmux.send_keys "seq 10 | #{fzf("--multi --marker '>>'")}", :Enter
tmux.until { |lines| assert_equal ' 10/10', lines[-2] } tmux.until { |lines| assert_equal ' 10/10 (0)', lines[-2] }
tmux.send_keys :BTab tmux.send_keys :BTab
# Assert that specified marker is displayed # Assert that specified marker is displayed
tmux.until { |lines| assert_equal ' >>1', lines[-3] } tmux.until { |lines| assert_equal ' >>1', lines[-3] }
@@ -1678,7 +1679,7 @@ class TestGoFZF < TestBase
tmux.send_keys :Tab tmux.send_keys :Tab
tmux.until { |lines| assert_equal ' 198/198 (1/2)', lines[-2] } tmux.until { |lines| assert_equal ' 198/198 (1/2)', lines[-2] }
tmux.send_keys '555' tmux.send_keys '555'
tmux.until { |lines| assert_equal ' 1/553', lines[-2] } tmux.until { |lines| assert_equal ' 1/553 (0/2)', lines[-2] }
end end
def test_reload_even_when_theres_no_match def test_reload_even_when_theres_no_match
@@ -1713,7 +1714,7 @@ class TestGoFZF < TestBase
tmux.send_keys 'foo' tmux.send_keys 'foo'
tmux.until { |lines| assert_equal ' 0/100 (1)', lines[-2] } tmux.until { |lines| assert_equal ' 0/100 (1)', lines[-2] }
tmux.send_keys :Space tmux.send_keys :Space
tmux.until { |lines| assert_equal ' 0/100', lines[-2] } tmux.until { |lines| assert_equal ' 0/100 (0)', lines[-2] }
end end
def test_backward_delete_char_eof def test_backward_delete_char_eof
@@ -1790,20 +1791,20 @@ class TestGoFZF < TestBase
def test_preview_scroll_begin_constant def test_preview_scroll_begin_constant
tmux.send_keys "echo foo 123 321 | #{FZF} --preview 'seq 1000' --preview-window left:+123", :Enter tmux.send_keys "echo foo 123 321 | #{FZF} --preview 'seq 1000' --preview-window left:+123", :Enter
tmux.until { |lines| lines.item_count == 1 } tmux.until { |lines| assert_match %r{1/1}, lines[-2] }
tmux.until { |lines| assert_match %r{123.*123/1000}, lines[1] } tmux.until { |lines| assert_match %r{123.*123/1000}, lines[1] }
end end
def test_preview_scroll_begin_expr def test_preview_scroll_begin_expr
tmux.send_keys "echo foo 123 321 | #{FZF} --preview 'seq 1000' --preview-window left:+{3}", :Enter tmux.send_keys "echo foo 123 321 | #{FZF} --preview 'seq 1000' --preview-window left:+{3}", :Enter
tmux.until { |lines| lines.item_count == 1 } tmux.until { |lines| assert_match %r{1/1}, lines[-2] }
tmux.until { |lines| assert_match %r{321.*321/1000}, lines[1] } tmux.until { |lines| assert_match %r{321.*321/1000}, lines[1] }
end end
def test_preview_scroll_begin_and_offset def test_preview_scroll_begin_and_offset
['echo foo 123 321', 'echo foo :123: 321'].each do |input| ['echo foo 123 321', 'echo foo :123: 321'].each do |input|
tmux.send_keys "#{input} | #{FZF} --preview 'seq 1000' --preview-window left:+{2}-2", :Enter tmux.send_keys "#{input} | #{FZF} --preview 'seq 1000' --preview-window left:+{2}-2", :Enter
tmux.until { |lines| lines.item_count == 1 } tmux.until { |lines| assert_match %r{1/1}, lines[-2] }
tmux.until { |lines| assert_match %r{121.*121/1000}, lines[1] } tmux.until { |lines| assert_match %r{121.*121/1000}, lines[1] }
tmux.send_keys 'C-c' tmux.send_keys 'C-c'
end end
@@ -1816,6 +1817,12 @@ class TestGoFZF < TestBase
assert_equal %w[A Á], `#{echoes} | #{FZF} -f A`.lines.map(&:chomp) assert_equal %w[A Á], `#{echoes} | #{FZF} -f A`.lines.map(&:chomp)
assert_equal %w[Á], `#{echoes} | #{FZF} -f Á`.lines.map(&:chomp) assert_equal %w[Á], `#{echoes} | #{FZF} -f Á`.lines.map(&:chomp)
end end
def test_preview_clear_screen
tmux.send_keys %{seq 100 | #{FZF} --preview 'for i in $(seq 300); do (( i % 200 == 0 )) && printf "\\033[2J"; echo "[$i]"; sleep 0.001; done'}, :Enter
tmux.until { |lines| lines.item_count == 100 }
tmux.until { |lines| lines[1]&.include?('[200]') }
end
end end
module TestShell module TestShell
@@ -1970,7 +1977,7 @@ module CompletionTest
FileUtils.mkdir_p('/tmp/fzf-test') FileUtils.mkdir_p('/tmp/fzf-test')
FileUtils.mkdir_p('/tmp/fzf test') FileUtils.mkdir_p('/tmp/fzf test')
(1..100).each { |i| FileUtils.touch("/tmp/fzf-test/#{i}") } (1..100).each { |i| FileUtils.touch("/tmp/fzf-test/#{i}") }
['no~such~user', '/tmp/fzf test/foobar', '~/.fzf-home'].each do |f| ['no~such~user', '/tmp/fzf test/foobar'].each do |f|
FileUtils.touch(File.expand_path(f)) FileUtils.touch(File.expand_path(f))
end end
tmux.prepare tmux.prepare
@@ -1986,14 +1993,15 @@ module CompletionTest
end end
# ~USERNAME**<TAB> # ~USERNAME**<TAB>
user = ENV['USER']
tmux.send_keys 'C-u' tmux.send_keys 'C-u'
tmux.send_keys "cat ~#{ENV['USER']}**", :Tab tmux.send_keys "cat ~#{user}**", :Tab
tmux.until { |lines| assert_operator lines.match_count, :>, 0 } tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
tmux.send_keys "'.fzf-home" tmux.send_keys "/#{user}"
tmux.until { |lines| assert(lines.any? { |l| l.end_with?('/.fzf-home') }) } tmux.until { |lines| assert(lines.any? { |l| l.end_with?("/#{user}") }) }
tmux.send_keys :Enter tmux.send_keys :Enter
tmux.until(true) do |lines| tmux.until(true) do |lines|
assert_match %r{cat .*/\.fzf-home}, lines[-1] assert_match %r{cat .*/#{user}}, lines[-1]
end end
# ~INVALID_USERNAME**<TAB> # ~INVALID_USERNAME**<TAB>
@@ -2050,7 +2058,7 @@ module CompletionTest
tmux.until { |lines| assert_equal 'cd /tmp/fzf-test/d55/xx', lines[-1] } tmux.until { |lines| assert_equal 'cd /tmp/fzf-test/d55/xx', lines[-1] }
# Should not match regular files (bash-only) # Should not match regular files (bash-only)
if self.class == TestBash if instance_of?(TestBash)
tmux.send_keys :Tab tmux.send_keys :Tab
tmux.until { |lines| assert_equal 'cd /tmp/fzf-test/d55/xx', lines[-1] } tmux.until { |lines| assert_equal 'cd /tmp/fzf-test/d55/xx', lines[-1] }
end end
@@ -2241,6 +2249,11 @@ class TestFish < TestBase
end end
__END__ __END__
PS1= PROMPT_COMMAND= HISTFILE= HISTSIZE=100
unset <%= UNSETS.join(' ') %>
unset $(env | sed -n /^_fzf_orig/s/=.*//p)
unset $(declare -F | sed -n "/_fzf/s/.*-f //p")
# Setup fzf # Setup fzf
# --------- # ---------
if [[ ! "$PATH" == *<%= BASE %>/bin* ]]; then if [[ ! "$PATH" == *<%= BASE %>/bin* ]]; then
@@ -2255,9 +2268,6 @@ fi
# ------------ # ------------
source "<%= BASE %>/shell/key-bindings.<%= __method__ %>" source "<%= BASE %>/shell/key-bindings.<%= __method__ %>"
PS1= PROMPT_COMMAND= HISTFILE= HISTSIZE=100
unset <%= UNSETS.join(' ') %>
# Old API # Old API
_fzf_complete_f() { _fzf_complete_f() {
_fzf_complete "+m --multi --prompt \"prompt-f> \"" "$@" < <( _fzf_complete "+m --multi --prompt \"prompt-f> \"" "$@" < <(