Compare commits

...

25 Commits

Author SHA1 Message Date
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
Junegunn Choi
fc7630a66d 0.23.1 2020-10-11 02:04:07 +09:00
Junegunn Choi
3248153d9f Add --preview-window=default for resetting the options 2020-10-11 01:54:39 +09:00
Junegunn Choi
246b9f3130 Simplify fzf-tmux script
# Should properly escape arguments
    FZF_DEFAULT_OPTS='--prompt "\$a`b\"c"' fzf-tmux --header $'$a\nb"c`d'
2020-10-09 23:02:03 +09:00
Junegunn Choi
865144850d Add nowrap, nocycle, nohidden for --preview-window
Close #2203
2020-10-09 21:56:16 +09:00
Junegunn Choi
d9752a4c21 Reset preview window flags that are not style-related
Fix #2203
2020-10-09 19:53:51 +09:00
34 changed files with 1317 additions and 811 deletions

3
.gitignore vendored
View File

@@ -1,5 +1,6 @@
bin/fzf
bin/fzf.exe
dist
target
pkg
Gemfile.lock
@@ -9,3 +10,5 @@ vendor
gopath
*.zwc
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_
Style/NumericPredicate:
Enabled: false
Style/StringConcatenation:
Enabled: false
Style/OptionalBooleanParameter:
Enabled: false
Style/WordArray:
MinSize: 1

View File

@@ -18,7 +18,7 @@ addons:
- fish
- tmux
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:
- make test
# LC_ALL=C to avoid escape codes in

View File

@@ -1,6 +1,55 @@
CHANGELOG
=========
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`
- 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)
```
0.23.1
------
- Added `--preview-window` options for disabling flags
- `nocycle`
- `nohidden`
- `nowrap`
- `default`
- Built with Go 1.14.9 due to performance regression
- https://github.com/golang/go/issues/40727
0.23.0
------
- Support preview scroll offset relative to window height

View File

@@ -1,6 +1,6 @@
FROM archlinux/base:latest
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 '. ~/.bashrc' >> ~/.bash_profile

View File

@@ -5,8 +5,9 @@ MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
ROOT_DIR := $(shell dirname $(MAKEFILE))
SOURCES := $(wildcard *.go src/*.go src/*/*.go) $(MAKEFILE)
VERSION := $(shell git describe --abbrev=0)
REVISION := $(shell git log -n 1 --pretty=format:%h -- $(SOURCES))
BUILD_FLAGS := -a -ldflags "-X main.revision=$(REVISION) -w '-extldflags=$(LDFLAGS)'" -tags "$(TAGS)"
BUILD_FLAGS := -a -ldflags "-X main.version=$(VERSION) -X main.revision=$(REVISION) -w '-extldflags=$(LDFLAGS)'" -tags "$(TAGS)"
BINARY64 := fzf-$(GOOS)_amd64
BINARYARM5 := fzf-$(GOOS)_arm5
@@ -15,12 +16,6 @@ BINARYARM7 := fzf-$(GOOS)_arm7
BINARYARM8 := fzf-$(GOOS)_arm8
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
UNAME_M := $(shell uname -m)
@@ -46,35 +41,6 @@ endif
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)
SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \
github.com/junegunn/fzf/src \
@@ -84,8 +50,11 @@ test: $(SOURCES)
install: bin/fzf
release:
goreleaser --rm-dist --snapshot
clean:
$(RM) -r target
$(RM) -r dist target
target/$(BINARY64): $(SOURCES)
GOARCH=amd64 $(GO) build $(BUILD_FLAGS) -o $@
@@ -121,4 +90,4 @@ update:
$(GO) get -u
$(GO) mod tidy
.PHONY: all release release-all test install clean docker docker-test update
.PHONY: all release test install clean docker docker-test update

View File

@@ -127,11 +127,13 @@ let g:fzf_action = {
\ 'ctrl-v': 'vsplit' }
" Default fzf layout
" - down / up / left / right / window
let g:fzf_layout = { 'down': '40%' }
" - Popup window
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': '-tabnew' }
let g:fzf_layout = { 'window': '10new' }
@@ -298,7 +300,7 @@ following options are allowed:
- `xoffset` [float default 0.5 range [0 ~ 1]]
- `highlight` [string default `'Comment'`]: Highlight group for border
- `border` [string default `rounded`]: Border style
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right`
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right` / `none`
`fzf#wrap`
----------
@@ -332,8 +334,9 @@ After we *"wrap"* our spec, we pass it to `fzf#run`.
call fzf#run(fzf#wrap({'source': 'ls'}))
```
Now it supports `CTRL-T`, `CTRL-V`, and `CTRL-X` key bindings and it opens fzf
window according to `g:fzf_layout` setting.
Now it supports `CTRL-T`, `CTRL-V`, and `CTRL-X` key bindings (configurable
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.
@@ -370,6 +373,17 @@ command! -bang -complete=dir -nargs=* LS
\ 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
----

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
stuff.
[bin]: https://github.com/junegunn/fzf-bin/releases
[bin]: https://github.com/junegunn/fzf/releases
### 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` |
| Zypper | openSUSE | `sudo zypper install fzf` |
Shell extensions (key bindings and fuzzy auto-completion) and Vim/Neovim
plugin may or may not be enabled by default depending on the package manager.
Refer to the package documentation for more information.
> :warning: **Key bindings (CTRL-T / CTRL-R / ALT-C) and fuzzy auto-completion
> may not be enabled by default.**
>
> Refer to the package documentation for more information. (e.g. `apt-cache show fzf`)
### Windows
@@ -582,9 +583,9 @@ and fzf will warn you about it. To suppress the warning message, we added
### Preview window
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.
Your `$SHELL` is used to execute the command with `$SHELL -c COMMAND`.
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.
Your `$SHELL` is used to execute the command with `$SHELL -c COMMAND`.
The window can be scrolled using the mouse or custom key bindings.
```bash
@@ -592,16 +593,8 @@ The window can be scrolled using the mouse or custom key bindings.
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
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
[Highlight](http://www.andre-simon.de/doku/highlight/en/highlight.php):

View File

@@ -136,7 +136,7 @@ if [[ -z "$TMUX" ]]; then
fi
# --height option is not allowed
args=("--no-height" "${args[@]}")
args=("${args[@]}" "--no-height")
# Handle zoomed tmux pane without popup options by moving it to a temp window
if [[ ! "$opt" =~ "-K -E" ]] && tmux list-panes -F '#F' | grep -q Z; then
@@ -154,12 +154,13 @@ argsf="${TMPDIR:-/tmp}/fzf-args-$id"
fifo1="${TMPDIR:-/tmp}/fzf-fifo1-$id"
fifo2="${TMPDIR:-/tmp}/fzf-fifo2-$id"
fifo3="${TMPDIR:-/tmp}/fzf-fifo3-$id"
tmux_win_opts=( $(tmux show-window-options remain-on-exit \; show-window-options synchronize-panes | sed '/ off/d; s/^/set-window-option /; s/$/ \\;/') )
cleanup() {
\rm -f $argsf $fifo1 $fifo2 $fifo3
# Restore tmux window options
if [[ "${#tmux_win_opts[@]}" -gt 0 ]]; then
eval "tmux ${tmux_win_opts[@]}"
eval "tmux ${tmux_win_opts[*]}"
fi
# Remove temp window if we were zoomed without popup options
@@ -179,58 +180,45 @@ cleanup() {
trap 'cleanup 1' SIGUSR1
trap 'cleanup' EXIT
envs="env TERM=$TERM "
envs="export TERM=$TERM "
[[ "$opt" =~ "-K -E" ]] && FZF_DEFAULT_OPTS="--margin 0,1 $FZF_DEFAULT_OPTS"
[[ -n "$FZF_DEFAULT_OPTS" ]] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
[[ -n "$FZF_DEFAULT_COMMAND" ]] && envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
mkfifo -m o+w $fifo2
mkfifo -m o+w $fifo3
echo "$envs;" > "$argsf"
# Build arguments to fzf
opts=""
for arg in "${args[@]}"; do
arg="${arg//\\/\\\\}"
arg="${arg//\"/\\\"}"
arg="${arg//\`/\\\`}"
arg="${arg//$/\\$}"
opts="$opts \"$arg\""
done
opts=$(printf "%q " "${args[@]}")
pppid=$$
echo -n "trap 'kill -SIGUSR1 -$pppid' EXIT SIGINT SIGTERM;" > $argsf
echo -n "trap 'kill -SIGUSR1 -$pppid' EXIT SIGINT SIGTERM;" >> $argsf
close="; trap - EXIT SIGINT SIGTERM $close"
tmux_win_opts=( $(tmux show-window-options remain-on-exit \; show-window-options synchronize-panes | sed '/ off/d; s/^/set-window-option /; s/$/ \\;/') )
export TMUX=$(cut -d , -f 1,2 <<< "$TMUX")
mkfifo -m o+w $fifo2
if [[ "$opt" =~ "-K -E" ]]; then
cat $fifo2 &
if [[ -n "$term" ]] || [[ -t 0 ]]; then
cat <<< "\"$fzf\" $opts > $fifo2; out=\$? $close; exit \$out" >> $argsf
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux popup -d "$PWD" "${tmux_args[@]}" $opt -R "$envs bash $argsf" > /dev/null 2>&1
else
mkfifo $fifo1
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; out=\$? $close; exit \$out" >> $argsf
cat <&0 > $fifo1 &
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux popup -d "$PWD" "${tmux_args[@]}" $opt -R "$envs bash $argsf" > /dev/null 2>&1
fi
tmux popup -d "$PWD" "${tmux_args[@]}" $opt -R "bash $argsf" > /dev/null 2>&1
exit $?
fi
mkfifo -m o+w $fifo3
if [[ -n "$term" ]] || [[ -t 0 ]]; then
cat <<< "\"$fzf\" $opts > $fifo2; echo \$? > $fifo3 $close" >> $argsf
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
set-window-option remain-on-exit off \;\
split-window $opt "${tmux_args[@]}" "$envs bash -c 'cd $(printf %q "$PWD"); exec -a fzf bash $argsf'" $swap \
> /dev/null 2>&1 || { "$fzf" "${args[@]}"; exit $?; }
else
mkfifo $fifo1
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; echo \$? > $fifo3 $close" >> $argsf
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
set-window-option remain-on-exit off \;\
split-window $opt "${tmux_args[@]}" "$envs bash -c 'exec -a fzf bash $argsf'" $swap \
> /dev/null 2>&1 || { "$fzf" "${args[@]}"; exit $?; }
cat <&0 > $fifo1 &
fi
tmux set-window-option synchronize-panes off \;\
set-window-option remain-on-exit off \;\
split-window -c "$PWD" $opt "${tmux_args[@]}" "bash -c 'exec -a fzf bash $argsf'" $swap \
> /dev/null 2>&1 || { "$fzf" "${args[@]}"; exit $?; }
cat $fifo2
exit "$(cat $fifo3)"

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*
==============================================================================
@@ -11,6 +11,7 @@ FZF - TABLE OF CONTENTS *fzf* *fzf-to
Explanation of g:fzf_colors
fzf#run
fzf#wrap
Global options supported by fzf#wrap
Tips
fzf inside terminal buffer
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
: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
vertical splits respectively.
Note that the environment variables `FZF_DEFAULT_COMMAND` and
`FZF_DEFAULT_OPTS` also apply here.
{2} https://github.com/kien/ctrlp.vim
{3} https://github.com/kien/ctrlp.vim
< Configuration >_____________________________________________________________~
@@ -154,11 +155,13 @@ Examples~
\ 'ctrl-v': 'vsplit' }
" Default fzf layout
" - down / up / left / right / window
let g:fzf_layout = { 'down': '~40%' }
" - Popup window
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': '-tabnew' }
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'}))
<
Now it supports CTRL-T, CTRL-V, and CTRL-X key bindings and it opens fzf
window according to `g:fzf_layout` setting.
Now it supports CTRL-T, CTRL-V, and CTRL-X key bindings (configurable 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.
>
@@ -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))
<
< 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*
==============================================================================
@@ -398,8 +414,8 @@ Starting fzf in a popup window~
*fzf-starting-fzf-in-a-popup-window*
>
" Required:
" - width [float range [0 ~ 1]]
" - height [float range [0 ~ 1]]
" - width [float range [0 ~ 1]] or [integer range [8 ~ ]]
" - height [float range [0 ~ 1]] or [integer range [4 ~ ]]
"
" Optional:
" - 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
the window.
For example, if you use a non-popup layout (e.g. `{'down':'40%'}`) on
Neovim, you might want to temporarily disable the statusline for a cleaner
look.
For example, if you use a non-popup layout (e.g. `{'down':'40%'}`) on Neovim,
you might want to temporarily disable the statusline for a cleaner look.
>
if has('nvim') && !exists('g:fzf_layout')
autocmd! FileType fzf

14
go.mod
View File

@@ -2,14 +2,14 @@ module github.com/junegunn/fzf
require (
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-runewidth v0.0.8
github.com/mattn/go-shellwords v1.0.9
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5
golang.org/x/text v0.3.2 // indirect
github.com/mattn/go-runewidth v0.0.9
github.com/mattn/go-shellwords v1.0.10
github.com/saracen/walker v0.1.1
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect
golang.org/x/sys v0.0.0-20201026173827-119d4633e4d1
golang.org/x/text v0.3.3 // indirect
)
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/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU=
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/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
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-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.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0=
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-shellwords v1.0.9 h1:eaB5JspOwiKKcHdqcjbfe5lA9cNn/4NRRtddXJCimqk=
github.com/mattn/go-shellwords v1.0.9/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e h1:NO86zOn5ScSKW8wRbMaSIcjDZUFpWdCQQnexRqZ9h9A=
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e/go.mod h1:G0Z6yVPru183i2MuRJx1DcR4dgIZtLcTdaaE/pC1BJU=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw=
github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/saracen/walker v0.1.1 h1:Ou2QIKTWqo0QxhtuHVmtObbmhjMCEUyJ82xp0uV+MGI=
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/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-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
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-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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-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/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-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201026173827-119d4633e4d1 h1:/DtoiOYKoQCcIFXQjz07RnWNPRCbqmSXSpgEzhC9ZHM=
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/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
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-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=

49
install
View File

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

View File

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

View File

@@ -5,9 +5,10 @@ import (
"github.com/junegunn/fzf/src/protector"
)
var version string
var revision string
func main() {
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
THE SOFTWARE.
..
.TH fzf-tmux 1 "Oct 2020" "fzf 0.23.0" "fzf-tmux - open fzf in tmux split pane"
.TH fzf-tmux 1 "Oct 2020" "fzf 0.24.0" "fzf-tmux - open fzf in tmux split pane"
.SH NAME
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
THE SOFTWARE.
..
.TH fzf 1 "Oct 2020" "fzf 0.23.0" "fzf - a command-line fuzzy finder"
.TH fzf 1 "Oct 2020" "fzf 0.24.0" "fzf - a command-line fuzzy finder"
.SH NAME
fzf - a command-line fuzzy finder
@@ -192,6 +192,16 @@ Draw border around the finder
.br
.BR horizontal " Horizontal lines above and below the finder"
.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
.B "--no-unicode"
@@ -267,11 +277,9 @@ Enable processing of ANSI color codes
.BI "--tabstop=" SPACES
Number of spaces for a tab character (default: 8)
.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 mappings. Ansi color code of -1 denotes terminal default
foreground/background color. You can also specify 24-bit color in \fB#rrggbb\fR
format.
color mappings.
.RS
.B BASE SCHEME:
@@ -282,7 +290,7 @@ format.
\fB16 \fRColor scheme for 16-color terminal
\fBbw \fRNo colors (equivalent to \fB--no-color\fR)
.B COLOR:
.B COLOR NAMES:
\fBfg \fRText
\fBbg \fRBackground
\fBpreview-fg \fRPreview window text
@@ -300,10 +308,25 @@ format.
\fBspinner \fRStreaming input indicator
\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:
\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' \\
--color='hl:65,fg:252,header:65,fg+:252' \\
--color='pointer:161,marker:168,prompt:110,hl+:108'
@@ -379,13 +402,34 @@ Note that you can escape a placeholder pattern by prepending a backslash.
Preview window will be updated even when there is no match for the current
query if any of the placeholder expressions evaluates to a non-empty string.
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
.TP
.BI "--preview-window=" "[POSITION][:SIZE[%]][:rounded|sharp|noborder][:wrap][:hidden][:+SCROLL[-OFFSET]]"
Determines the layout of the preview window. If the argument contains
.BI "--preview-window=" "[POSITION][:SIZE[%]][:rounded|sharp|noborder][:[no]wrap][:[no]cycle][:[no]hidden][:+SCROLL[-OFFSET]][:default]"
.RS
.B POSITION: (default: right)
\fBup
\fBdown
\fBleft
\fBright
\fRDetermines the layout of the preview window. If the argument contains
\fB:hidden\fR, the preview window will be hidden by default until
\fBtoggle-preview\fR action is triggered. Long lines are truncated by default.
Line wrap can be enabled with \fB:wrap\fR flag.
Line wrap can be enabled with \fB:wrap\fR flag. Cyclic scrolling is enabled
with \fB:cycle\fR flag.
If size is given as 0, preview window will not be visible, but fzf will still
execute the command in the background.
@@ -401,13 +445,7 @@ for adjusting the base offset so that you can see the text above it. It should
be given as a numeric integer (\fB-INTEGER\fR), or as a denominator form
(\fB-/INTEGER\fR) for specifying a fraction of the preview window height.
.RS
.B POSITION: (default: right)
\fBup
\fBdown
\fBleft
\fBright
.RE
\fBdefault\fR resets all options previously set to the default.
.RS
e.g.
@@ -427,6 +465,7 @@ e.g.
--preview-window +{2}-/2\fR
.RE
.SS Scripting
.TP
.BI "-q, --query=" "STR"

View File

@@ -154,7 +154,20 @@ function! fzf#install()
endif
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 executable(s:fzf_go)
let s:exec = s:fzf_go
@@ -169,6 +182,26 @@ function! fzf#exec()
throw 'fzf executable not found'
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[0], '[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
endfunction
@@ -432,6 +465,7 @@ try
elseif use_term
let optstr .= ' --no-height'
endif
let optstr .= s:border_opt(get(dict, 'window', 0))
let command = prefix.(use_tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
if use_term
@@ -656,6 +690,32 @@ function! s:getpos()
return {'tab': tabpagenr(), 'win': winnr(), 'winid': win_getid(), 'cnt': winnr('$'), 'tcnt': tabpagenr('$')}
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'
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)
let directions = {
\ 'up': ['topleft', 'resize', &lines],
@@ -869,12 +929,8 @@ else
endif
function! s:popup(opts) abort
" Support ambiwidth == 'double'
let ambidouble = &ambiwidth == 'double' ? 2 : 1
" Size and position
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 row = float2nr(get(a:opts, 'yoffset', 0.5) * (&lines - height))
let col = float2nr(get(a:opts, 'xoffset', 0.5) * (&columns - width))
@@ -885,45 +941,9 @@ function! s:popup(opts) abort
let row += !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', {
\ '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
let s:default_action = {

View File

@@ -127,12 +127,12 @@ function fzf_key_bindings
else
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
set fzf_query $commandline
else
# 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
@@ -144,15 +144,15 @@ function fzf_key_bindings
set dir $argv
# Strip all trailing slashes. Ignore if $dir is root dir (/)
if [ (string length $dir) -gt 1 ]
set dir (string replace -r '/*$' '' $dir)
if [ (string length -- $dir) -gt 1 ]
set dir (string replace -r '/*$' -- '' $dir)
end
# Iteratively check if dir exists and strip tail end of path
while [ ! -d "$dir" ]
# If path is absolute, this can keep going until ends up at /
# If path is relative, this can keep going until entire input is consumed, dirname returns "."
set dir (dirname "$dir")
set dir (dirname -- "$dir")
end
echo $dir

View File

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

View File

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

View File

@@ -58,7 +58,8 @@ const usage = `usage: fzf [options]
(default: 10)
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
--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)
--info=STYLE Finder info style [default|inline|hidden]
--prompt=STR Input prompt (default: '> ')
@@ -80,9 +81,11 @@ const usage = `usage: fzf [options]
Preview
--preview=COMMAND Command to preview highlighted line ({})
--preview-window=OPT Preview window layout (default: right:50%)
[up|down|left|right][:SIZE[%]][:wrap][:cycle][:hidden]
[:+SCROLL[-OFFSET]]
[up|down|left|right][:SIZE[%]]
[:[no]wrap][:[no]cycle][:[no]hidden]
[:rounded|sharp|noborder]
[:+SCROLL[-OFFSET]]
[:default]
Scripting
-q, --query=STR Start the finder with the given query
@@ -225,6 +228,10 @@ type Options struct {
Version bool
}
func defaultPreviewOpts(command string) previewOpts {
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, tui.BorderRounded}
}
func defaultOptions() *Options {
return &Options{
Fuzzy: true,
@@ -264,7 +271,7 @@ func defaultOptions() *Options {
ToggleSort: false,
Expect: make(map[int]string),
Keymap: make(map[int][]action),
Preview: previewOpts{"", posRight, sizeSpec{50, true}, "", false, false, false, tui.BorderRounded},
Preview: defaultPreviewOpts(""),
PrintQuery: false,
ReadZero: false,
Printer: func(str string) { fmt.Println(str) },
@@ -415,11 +422,21 @@ func parseBorder(str string, optional bool) tui.BorderShape {
return tui.BorderSharp
case "horizontal":
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:
if optional && str == "" {
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
}
@@ -584,11 +601,8 @@ func parseTiebreak(str string) []criterion {
}
func dupeTheme(theme *tui.ColorTheme) *tui.ColorTheme {
if theme != nil {
dupe := *theme
return &dupe
}
return nil
dupe := *theme
return &dupe
}
func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
@@ -613,54 +627,76 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
continue
}
pair := strings.Split(str, ":")
if len(pair) != 2 {
components := strings.Split(str, ":")
if len(components) < 2 {
fail()
}
var ansi tui.Color
if rrggbb.MatchString(pair[1]) {
ansi = tui.HexToColor(pair[1])
} else {
ansi32, err := strconv.Atoi(pair[1])
if err != nil || ansi32 < -1 || ansi32 > 255 {
fail()
cattr := tui.NewColorAttr()
for _, component := range components[1:] {
switch component {
case "regular":
cattr.Attr = tui.AttrRegular
case "bold", "strong":
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":
theme.Fg = ansi
theme.Fg = cattr
case "bg":
theme.Bg = ansi
theme.Bg = cattr
case "preview-fg":
theme.PreviewFg = ansi
theme.PreviewFg = cattr
case "preview-bg":
theme.PreviewBg = ansi
theme.PreviewBg = cattr
case "fg+":
theme.Current = ansi
theme.Current = cattr
case "bg+":
theme.DarkBg = ansi
theme.DarkBg = cattr
case "gutter":
theme.Gutter = ansi
theme.Gutter = cattr
case "hl":
theme.Match = ansi
theme.Match = cattr
case "hl+":
theme.CurrentMatch = ansi
theme.CurrentMatch = cattr
case "border":
theme.Border = ansi
theme.Border = cattr
case "prompt":
theme.Prompt = ansi
theme.Prompt = cattr
case "spinner":
theme.Spinner = ansi
theme.Spinner = cattr
case "info":
theme.Info = ansi
theme.Info = cattr
case "pointer":
theme.Cursor = ansi
theme.Cursor = cattr
case "marker":
theme.Selected = ansi
theme.Selected = cattr
case "header":
theme.Header = ansi
theme.Header = cattr
default:
fail()
}
@@ -1000,12 +1036,20 @@ func parsePreviewWindow(opts *previewOpts, input string) {
for _, token := range tokens {
switch token {
case "":
case "default":
*opts = defaultPreviewOpts(opts.command)
case "hidden":
opts.hidden = true
case "nohidden":
opts.hidden = false
case "wrap":
opts.wrap = true
case "nowrap":
opts.wrap = false
case "cycle":
opts.cycle = true
case "nocycle":
opts.cycle = false
case "up", "top":
opts.position = posUp
case "down", "bottom":
@@ -1166,7 +1210,7 @@ func parseOptions(opts *Options, allArgs []string) {
case "--no-mouse":
opts.Mouse = false
case "+c", "--no-color":
opts.Theme = nil
opts.Theme = tui.NoColorTheme()
case "+2", "--no-256":
opts.Theme = tui.Default16
case "--black":
@@ -1271,7 +1315,7 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Preview.command = ""
case "--preview-window":
parsePreviewWindow(&opts.Preview,
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]][:rounded|sharp|noborder][:wrap][:cycle][:hidden][:+SCROLL[-OFFSET]]"))
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]][:rounded|sharp|noborder][:wrap][:cycle][:hidden][:+SCROLL[-OFFSET]][:default]"))
case "--height":
opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]"))
case "--min-height":
@@ -1464,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

View File

@@ -295,7 +295,7 @@ func TestColorSpec(t *testing.T) {
}
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")
}
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) {
check := func(words []string, key int, expected actionType) {
opts := defaultOptions()
@@ -387,21 +375,23 @@ func TestPreviewOpts(t *testing.T) {
opts.Preview.size.size == 50) {
t.Error()
}
opts = optsFor("--preview", "cat {}", "--preview-window=left:15:hidden:wrap")
opts = optsFor("--preview", "cat {}", "--preview-window=left:15:hidden:wrap:+{1}-/2")
if !(opts.Preview.command == "cat {}" &&
opts.Preview.hidden == true &&
opts.Preview.wrap == true &&
opts.Preview.position == posLeft &&
opts.Preview.scroll == "{1}-/2" &&
opts.Preview.size.percent == false &&
opts.Preview.size.size == 15) {
t.Error(opts.Preview)
}
opts = optsFor("--preview-window=up:15:wrap:hidden", "--preview-window=down", "--preview-window=cycle")
opts = optsFor("--preview-window=up:15:wrap:hidden:+{1}-/2", "--preview-window=down", "--preview-window=cycle")
if !(opts.Preview.command == "" &&
opts.Preview.hidden == true &&
opts.Preview.wrap == true &&
opts.Preview.cycle == true &&
opts.Preview.position == posDown &&
opts.Preview.scroll == "{1}-/2" &&
opts.Preview.size.percent == false &&
opts.Preview.size.size == 15) {
t.Error(opts.Preview.size.size)
@@ -415,6 +405,13 @@ func TestPreviewOpts(t *testing.T) {
opts.Preview.size.size == 15) {
t.Error(opts.Preview)
}
opts = optsFor("--preview=foo", "--preview-window=up", "--preview-window=default:70%")
if !(opts.Preview.command == "foo" &&
opts.Preview.position == posRight &&
opts.Preview.size.percent == true &&
opts.Preview.size.size == 70) {
t.Error(opts.Preview)
}
}
func TestAdditiveExpect(t *testing.T) {

View File

@@ -15,7 +15,6 @@ type Offset [2]int32
type colorOffset struct {
offset [2]int32
color tui.ColorPair
attr tui.Attr
}
type Result struct {
@@ -87,14 +86,14 @@ func minRank() Result {
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()
// No ANSI code, or --color=no
// No ANSI codes
if len(itemColors) == 0 {
var offsets []colorOffset
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
}
@@ -111,17 +110,19 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
maxCol = ansi.offset[1]
}
}
cols := make([]int, maxCol)
cols := make([]int, maxCol)
for colorIndex, ansi := range itemColors {
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 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
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
add := func(idx int) {
if curr != 0 && idx > start {
if curr == -1 {
colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)}, color: color, attr: attr})
} else {
ansi := itemColors[curr-1]
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
}
}
if curr < 0 {
color := colMatch
if curr < -1 && theme.Colored {
origColor := ansiToColorPair(itemColors[-curr-2], colMatch)
color = origColor.MergeNonDefault(color)
}
colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)}, color: color})
} else {
ansi := itemColors[curr-1]
colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)},
color: tui.NewColorPair(fg, bg),
attr: ansi.color.attr.Merge(attr)})
color: ansiToColorPair(ansi, colBase)})
}
}
}

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: &Item{
colors: &[]ansiOffset{
ansiOffset{[2]int32{0, 20}, ansiState{1, 5, 0}},
ansiOffset{[2]int32{22, 27}, ansiState{2, 6, tui.Bold}},
ansiOffset{[2]int32{30, 32}, ansiState{3, 7, 0}},
ansiOffset{[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}]
{[2]int32{0, 20}, ansiState{1, 5, 0}},
{[2]int32{22, 27}, ansiState{2, 6, tui.Bold}},
{[2]int32{30, 32}, ansiState{3, 7, 0}},
{[2]int32{33, 40}, ansiState{4, 8, tui.Bold}}}}}
pair := tui.NewColorPair(99, 199)
colors := item.colorOffsets(offsets, tui.Dark256, pair, tui.AttrRegular, true)
assert := func(idx int, b int32, e int32, c tui.ColorPair, bold bool) {
var attr tui.Attr
if bold {
attr = tui.Bold
}
colBase := tui.NewColorPair(89, 189, tui.AttrUndefined)
colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined)
colors := item.colorOffsets(offsets, tui.Dark256, colBase, colMatch, true)
assert := func(idx int, b int32, e int32, c tui.ColorPair) {
o := colors[idx]
if o.offset[0] != b || o.offset[1] != e || o.color != c || o.attr != attr {
t.Error(o)
if o.offset[0] != b || o.offset[1] != e || o.color != c {
t.Error(o, b, e, c)
}
}
assert(0, 0, 5, tui.NewColorPair(1, 5), false)
assert(1, 5, 15, pair, false)
assert(2, 15, 20, tui.NewColorPair(1, 5), false)
assert(3, 22, 25, tui.NewColorPair(2, 6), true)
assert(4, 25, 35, pair, false)
assert(5, 35, 40, tui.NewColorPair(4, 8), true)
// [{[0 5] {1 5 0}} {[5 15] {99 199 0}} {[15 20] {1 5 0}}
// {[22 25] {2 6 1}} {[25 27] {99 199 1}} {[27 30] {99 199 0}}
// {[30 32] {99 199 0}} {[32 33] {99 199 0}} {[33 35] {99 199 1}}
// {[35 40] {4 8 1}}]
assert(0, 0, 5, tui.NewColorPair(1, 5, tui.AttrUndefined))
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"
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"os/signal"
@@ -24,13 +23,16 @@ import (
var placeholder *regexp.Regexp
var numericPrefix *regexp.Regexp
var whiteSuffix *regexp.Regexp
var activeTempFiles []string
const ellipsis string = ".."
const clearCode string = "\x1b[2J"
func init() {
placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{\+?f?nf?})`)
numericPrefix = regexp.MustCompile(`^[[:punct:]]*([0-9]+)`)
whiteSuffix = regexp.MustCompile(`\s*$`)
activeTempFiles = []string{}
}
@@ -43,11 +45,25 @@ const (
)
type previewer struct {
text string
lines int
offset int
enabled bool
more bool
version int64
lines []string
offset int
enabled 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 {
@@ -125,6 +141,7 @@ type Terminal struct {
reqBox *util.EventBox
preview previewOpts
previewer previewer
previewed previewed
previewBox *util.EventBox
eventBox *util.EventBox
mutex sync.Mutex
@@ -171,6 +188,7 @@ const (
reqPreviewEnqueue
reqPreviewDisplay
reqPreviewRefresh
reqPreviewDelayed
reqQuit
)
@@ -263,12 +281,15 @@ type searchRequest struct {
type previewRequest struct {
template string
pwindow tui.Window
list []*Item
}
type previewResult struct {
content string
version int64
lines []string
offset int
spinner string
}
func toActions(types ...actionType) []action {
@@ -353,6 +374,13 @@ func hasPreviewAction(opts *Options) bool {
return false
}
func makeSpinner(unicode bool) []string {
if unicode {
return []string{``, ``, ``, ``, ``, ``, ``, ``, ``, ``}
}
return []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
}
// NewTerminal returns new Terminal object
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
input := trimQuery(opts.Query)
@@ -416,14 +444,10 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
wordRubout = fmt.Sprintf("%s[^%s]", sep, sep)
wordNext = fmt.Sprintf("[^%s]%s|(.$)", sep, sep)
}
spinner := []string{``, ``, ``, ``, ``, ``, ``, ``, ``, ``}
if !opts.Unicode {
spinner = []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
}
t := Terminal{
initDelay: delay,
infoStyle: opts.InfoStyle,
spinner: spinner,
spinner: makeSpinner(opts.Unicode),
queryLen: [2]int{0, 0},
layout: opts.Layout,
fullscreen: fullscreen,
@@ -467,7 +491,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
selected: make(map[int32]selectedItem),
reqBox: util.NewEventBox(),
preview: opts.Preview,
previewer: previewer{"", 0, 0, previewBox != nil && !opts.Preview.hidden, false},
previewer: previewer{0, []string{}, 0, previewBox != nil && !opts.Preview.hidden, false, true, ""},
previewed: previewed{0, 0, 0, false},
previewBox: previewBox,
eventBox: eventBox,
mutex: sync.Mutex{},
@@ -492,9 +517,29 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) {
var state *ansiState
trimmed, colors, _ := extractColor(prompt, state, nil)
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() {
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)
@@ -625,7 +670,7 @@ func calculateSize(base int, size sizeSpec, occupied int, minSize int, pad int)
func (t *Terminal) resizeWindows() {
screenWidth := t.tui.MaxX()
screenHeight := t.tui.MaxY()
marginInt := [4]int{}
marginInt := [4]int{} // TRBL
t.prevLines = make([]itemLine, screenHeight)
for idx, sizeSpec := range t.margin {
if sizeSpec.percent {
@@ -642,6 +687,24 @@ func (t *Terminal) resizeWindows() {
switch t.borderShape {
case tui.BorderHorizontal:
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:
marginInt[idx] += 1 + idx%2
}
@@ -682,23 +745,39 @@ func (t *Terminal) resizeWindows() {
if t.pwindow != nil {
t.pwindow.Close()
}
// Reset preview version so that full redraw occurs
t.previewed.version = 0
width := screenWidth - marginInt[1] - marginInt[3]
height := screenHeight - marginInt[0] - marginInt[2]
switch t.borderShape {
case tui.BorderHorizontal:
t.border = t.tui.NewWindow(
marginInt[0]-1,
marginInt[3],
width,
height+2,
marginInt[0]-1, marginInt[3], width, height+2,
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:
t.border = t.tui.NewWindow(
marginInt[0]-1,
marginInt[3]-2,
width+4,
height+2,
marginInt[0]-1, marginInt[3]-2, width+4, height+2,
false, tui.MakeBorderStyle(t.borderShape, t.unicode))
}
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
@@ -719,12 +798,6 @@ func (t *Terminal) resizeWindows() {
pwidth -= 4
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)
}
verticalPad := 2
@@ -820,8 +893,16 @@ func (t *Terminal) printPrompt() {
t.prompt()
before, after := t.updatePromptOffset()
t.window.CPrint(tui.ColNormal, t.strong, string(before))
t.window.CPrint(tui.ColNormal, t.strong, string(after))
t.window.CPrint(tui.ColInput, string(before))
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() {
@@ -832,7 +913,7 @@ func (t *Terminal) printInfo() {
if t.reading {
duration := int64(spinnerDuration)
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)
pos = 2
@@ -843,9 +924,9 @@ func (t *Terminal) printInfo() {
}
t.move(0, pos, true)
if t.reading {
t.window.CPrint(tui.ColSpinner, t.strong, " < ")
t.window.CPrint(tui.ColSpinner, " < ")
} else {
t.window.CPrint(tui.ColPrompt, t.strong, " < ")
t.window.CPrint(tui.ColPrompt, " < ")
}
pos += len(" < ")
case infoHidden:
@@ -862,7 +943,7 @@ func (t *Terminal) printInfo() {
output += " -S"
}
}
if len(t.selected) > 0 {
if t.multi > 0 {
if t.multi == maxMulti {
output += fmt.Sprintf(" (%d)", len(t.selected))
} else {
@@ -875,12 +956,8 @@ func (t *Terminal) printInfo() {
if t.failed != nil && t.count == 0 {
output = fmt.Sprintf("[Command failed: %s]", *t.failed)
}
maxWidth := t.window.Width() - pos
if len(output) > maxWidth {
outputRunes, _ := t.trimRight([]rune(output), maxWidth-2)
output = string(outputRunes) + strings.Repeat(".", util.Constrain(maxWidth, 0, 2))
}
t.window.CPrint(tui.ColInfo, 0, output)
output = t.trimMessage(output, t.window.Width()-pos)
t.window.CPrint(tui.ColInfo, output)
}
func (t *Terminal) printHeader() {
@@ -905,7 +982,7 @@ func (t *Terminal) printHeader() {
t.move(line, 2, true)
t.printHighlighted(Result{item: item},
tui.AttrRegular, tui.ColHeader, tui.ColHeader, false, false)
tui.ColHeader, tui.ColHeader, false, false)
}
}
@@ -935,7 +1012,7 @@ func (t *Terminal) printList() {
func (t *Terminal) printItem(result Result, line int, i int, current bool) {
item := result.item
_, selected := t.selected[item.Index()]
label := t.pointerEmpty
label := ""
if t.jumping != jumpDisabled {
if i < len(t.jumpLabels) {
// Striped
@@ -960,21 +1037,29 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool) {
t.move(line, 0, false)
if current {
t.window.CPrint(tui.ColCurrentCursor, t.strong, label)
if selected {
t.window.CPrint(tui.ColCurrentSelected, t.strong, t.marker)
if len(label) == 0 {
t.window.CPrint(tui.ColCurrentCursorEmpty, t.pointerEmpty)
} 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 {
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 {
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
if fillSpaces > 0 {
@@ -1028,7 +1113,7 @@ func (t *Terminal) overflow(runes []rune, max int) bool {
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
// Overflow
@@ -1053,7 +1138,7 @@ func (t *Terminal) printHighlighted(result Result, attr tui.Attr, col1 tui.Color
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)
maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text))
displayWidth := t.displayWidthWithLimit(text, 0, maxWidth)
@@ -1111,11 +1196,11 @@ func (t *Terminal) printHighlighted(result Result, attr tui.Attr, col1 tui.Color
e := util.Constrain32(offset.offset[1], index, maxOffset)
substr, prefixWidth = t.processTabs(text[index:b], prefixWidth)
t.window.CPrint(col1, attr, substr)
t.window.CPrint(colBase, substr)
if b < e {
substr, prefixWidth = t.processTabs(text[b:e], prefixWidth)
t.window.CPrint(offset.color, offset.attr, substr)
t.window.CPrint(offset.color, substr)
}
index = e
@@ -1125,33 +1210,52 @@ func (t *Terminal) printHighlighted(result Result, attr tui.Attr, col1 tui.Color
}
if index < maxOffset {
substr, _ = t.processTabs(text[index:], prefixWidth)
t.window.CPrint(col1, attr, substr)
t.window.CPrint(colBase, substr)
}
return displayWidth
}
func (t *Terminal) printPreview() {
if !t.hasPreviewWindow() {
return
func (t *Terminal) renderPreviewSpinner() {
numLines := len(t.previewer.lines)
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()
if t.tui.DoesAutoWrap() {
maxWidth -= 1
}
reader := bufio.NewReader(strings.NewReader(t.previewer.text))
lineNo := -t.previewer.offset
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
for ; ; lineNo++ {
line, err := reader.ReadString('\n')
eof := err == io.EOF
if !eof {
line = line[:len(line)-1]
}
for _, line := range t.previewer.lines {
if lineNo >= height || t.pwindow.Y() == height-1 && t.pwindow.X() > 0 {
t.previewed.filled = true
break
} else if lineNo >= 0 {
var fillRet tui.FillReturn
@@ -1163,38 +1267,62 @@ func (t *Terminal) printPreview() {
}
str, width := t.processTabs(trimmed, prefixWidth)
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)
} else {
fillRet = t.pwindow.CFill(tui.ColPreview.Fg(), tui.ColPreview.Bg(), tui.AttrRegular, str)
}
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 {
continue
} else if fillRet == tui.FillSuspend {
t.previewed.filled = true
break
}
if unchanged && lineNo == 0 {
break
}
t.pwindow.Fill("\n")
}
if eof {
break
}
lineNo++
}
t.pwindow.FinishFill()
if t.previewer.lines > height {
t.previewer.more = true
offset := fmt.Sprintf("%d/%d", t.previewer.offset+1, t.previewer.lines)
pos := t.pwindow.Width() - len(offset)
if t.tui.DoesAutoWrap() {
pos -= 1
}
t.pwindow.Move(0, pos)
t.pwindow.CPrint(tui.ColInfo, tui.Reverse, offset)
if !unchanged {
t.pwindow.FinishFill()
}
}
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) {
var strbuf bytes.Buffer
l := prefixWidth
@@ -1686,9 +1814,11 @@ func (t *Terminal) Loop() {
if t.hasPreviewer() {
go func() {
var version int64
for {
var items []*Item
var commandTemplate string
var pwindow tui.Window
t.previewBox.Wait(func(events *util.Events) {
for req, value := range *events {
switch req {
@@ -1696,63 +1826,134 @@ func (t *Terminal) Loop() {
request := value.(previewRequest)
commandTemplate = request.template
items = request.list
pwindow = request.pwindow
}
}
events.Clear()
})
version++
// We don't display preview window if no match
if items[0] != nil {
command := t.replacePlaceholder(commandTemplate, false, string(t.Input()), items)
offset := 0
initialOffset := 0
cmd := util.ExecCommand(command, true)
if t.pwindow != nil {
height := t.pwindow.Height()
offset = t.evaluateScrollOffset(items, height)
if pwindow != nil {
height := pwindow.Height()
initialOffset = util.Max(0, t.evaluateScrollOffset(items, height))
env := os.Environ()
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, "FZF_PREVIEW_"+lines)
env = append(env, columns)
env = append(env, "FZF_PREVIEW_"+columns)
cmd.Env = env
}
var out bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &out
err := cmd.Start()
if err != nil {
out.Write([]byte(err.Error()))
}
out, _ := cmd.StdoutPipe()
cmd.Stderr = cmd.Stdout
reader := bufio.NewReader(out)
eofChan := make(chan bool)
finishChan := make(chan bool, 1)
updateChan := make(chan bool)
go func() {
select {
case code := <-t.killChan:
if code != exitCancel {
util.KillCommand(cmd)
os.Exit(code)
} else {
select {
case <-time.After(previewCancelWait):
util.KillCommand(cmd)
updateChan <- true
case <-finishChan:
updateChan <- false
reapChan := make(chan bool)
err := cmd.Start()
reaps := 0
if err != nil {
t.reqBox.Set(reqPreviewDisplay, previewResult{version, []string{err.Error()}, 0, ""})
} else {
reaps = 2
lineChan := make(chan eachLine)
// Goroutine 1 reads process output
go func() {
for {
line, err := reader.ReadString('\n')
lineChan <- eachLine{line, err}
if err != nil {
break
}
}
case <-finishChan:
updateChan <- false
eofChan <- true
}()
// Goroutine 2 periodically requests rendering
go func(version int64) {
lines := []string{}
spinner := makeSpinner(t.unicode)
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
}
}
}()
cmd.Wait()
timer.Stop()
reapChan <- true
}(version)
<-eofChan
cmd.Wait() // NOTE: We should not call Wait before EOF
finishChan <- true
if out.Len() > 0 || !<-updateChan {
t.reqBox.Set(reqPreviewDisplay, previewResult{out.String(), offset})
for i := 0; i < reaps; i++ {
<-reapChan
}
cleanTemporaryFiles()
} else {
t.reqBox.Set(reqPreviewDisplay, previewResult{"", 0})
t.reqBox.Set(reqPreviewDisplay, previewResult{version, nil, 0, ""})
}
}
}()
@@ -1772,7 +1973,7 @@ func (t *Terminal) Loop() {
if len(command) > 0 && t.isPreviewEnabled() {
_, list := t.buildPlusList(command, false)
t.cancelPreview()
t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, list})
t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.pwindow, list})
}
}
@@ -1827,12 +2028,18 @@ func (t *Terminal) Loop() {
})
case reqPreviewDisplay:
result := value.(previewResult)
t.previewer.text = result.content
t.previewer.lines = strings.Count(t.previewer.text, "\n")
t.previewer.offset = util.Constrain(result.offset, 0, t.previewer.lines-1)
t.previewer.version = result.version
t.previewer.lines = result.lines
t.previewer.spinner = result.spinner
if result.offset >= 0 {
t.previewer.offset = util.Constrain(result.offset, 0, len(t.previewer.lines)-1)
}
t.printPreview()
case reqPreviewRefresh:
t.printPreview()
case reqPreviewDelayed:
t.previewer.version = value.(int64)
t.printPreviewDelayed()
case reqPrintQuery:
exit(func() int {
t.printer(string(t.input))
@@ -1885,14 +2092,15 @@ func (t *Terminal) Loop() {
return false
}
scrollPreview := func(amount int) {
if !t.previewer.more {
if !t.previewer.scrollable {
return
}
newOffset := t.previewer.offset + amount
numLines := len(t.previewer.lines)
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 {
t.previewer.offset = newOffset
req(reqPreviewRefresh)
@@ -1934,7 +2142,7 @@ func (t *Terminal) Loop() {
if valid {
t.cancelPreview()
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 (
AttrRegular Attr = Attr(0)
Bold = Attr(1)
Dim = Attr(1 << 1)
Italic = Attr(1 << 2)
Underline = Attr(1 << 3)
Blink = Attr(1 << 4)
Blink2 = Attr(1 << 5)
Reverse = Attr(1 << 6)
AttrUndefined = Attr(0)
AttrRegular = Attr(1 << 7)
AttrClear = Attr(1 << 8)
Bold = Attr(1)
Dim = Attr(1 << 1)
Italic = Attr(1 << 2)
Underline = Attr(1 << 3)
Blink = Attr(1 << 4)
Blink2 = Attr(1 << 5)
Reverse = Attr(1 << 6)
)
func (r *FullscreenRenderer) Init() {}
@@ -32,10 +35,9 @@ func (r *FullscreenRenderer) Clear() {}
func (r *FullscreenRenderer) Refresh() {}
func (r *FullscreenRenderer) Close() {}
func (r *FullscreenRenderer) DoesAutoWrap() bool { return false }
func (r *FullscreenRenderer) GetChar() Event { return Event{} }
func (r *FullscreenRenderer) MaxX() int { return 0 }
func (r *FullscreenRenderer) MaxY() int { return 0 }
func (r *FullscreenRenderer) GetChar() Event { return Event{} }
func (r *FullscreenRenderer) MaxX() int { return 0 }
func (r *FullscreenRenderer) MaxY() int { return 0 }
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {}

View File

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

View File

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

View File

@@ -123,6 +123,15 @@ func (c Color) is24() bool {
return c > 0 && (c&(1<<24)) > 0
}
type ColorAttr struct {
Color Color
Attr Attr
}
func NewColorAttr() ColorAttr {
return ColorAttr{Color: colUndefined, Attr: AttrUndefined}
}
const (
colUndefined Color = -2
colDefault Color = -1
@@ -148,9 +157,9 @@ const (
)
type ColorPair struct {
fg Color
bg Color
id int
fg Color
bg Color
attr Attr
}
func HexToColor(rrggbb string) Color {
@@ -160,8 +169,8 @@ func HexToColor(rrggbb string) Color {
return Color((1 << 24) + (r << 16) + (g << 8) + b)
}
func NewColorPair(fg Color, bg Color) ColorPair {
return ColorPair{fg, bg, -1}
func NewColorPair(fg Color, bg Color, attr Attr) ColorPair {
return ColorPair{fg, bg, attr}
}
func (p ColorPair) Fg() Color {
@@ -172,23 +181,59 @@ func (p ColorPair) Bg() Color {
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 {
Fg Color
Bg Color
PreviewFg Color
PreviewBg Color
DarkBg Color
Gutter Color
Prompt Color
Match Color
Current Color
CurrentMatch Color
Spinner Color
Info Color
Cursor Color
Selected Color
Header Color
Border Color
Colored bool
Input ColorAttr
Fg ColorAttr
Bg ColorAttr
PreviewFg ColorAttr
PreviewBg ColorAttr
DarkBg ColorAttr
Gutter ColorAttr
Prompt ColorAttr
Match ColorAttr
Current ColorAttr
CurrentMatch ColorAttr
Spinner ColorAttr
Info ColorAttr
Cursor ColorAttr
Selected ColorAttr
Header ColorAttr
Border ColorAttr
}
type Event struct {
@@ -214,6 +259,11 @@ const (
BorderRounded
BorderSharp
BorderHorizontal
BorderVertical
BorderTop
BorderBottom
BorderLeft
BorderRight
)
type BorderStyle struct {
@@ -286,7 +336,6 @@ type Renderer interface {
MaxX() int
MaxY() int
DoesAutoWrap() bool
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)
MoveAndClear(y int, x int)
Print(text string)
CPrint(color ColorPair, attr Attr, text string)
CPrint(color ColorPair, text string)
Fill(text string) FillReturn
CFill(fg Color, bg Color, attr Attr, text string) FillReturn
Erase()
@@ -337,41 +386,69 @@ var (
Dark256 *ColorTheme
Light256 *ColorTheme
ColPrompt ColorPair
ColNormal ColorPair
ColMatch ColorPair
ColCursor ColorPair
ColSelected ColorPair
ColCurrent ColorPair
ColCurrentMatch ColorPair
ColCurrentCursor ColorPair
ColCurrentSelected ColorPair
ColSpinner ColorPair
ColInfo ColorPair
ColHeader ColorPair
ColBorder ColorPair
ColPreview ColorPair
ColPreviewBorder ColorPair
ColPrompt ColorPair
ColNormal ColorPair
ColInput ColorPair
ColMatch ColorPair
ColCursor ColorPair
ColCursorEmpty ColorPair
ColSelected ColorPair
ColCurrent ColorPair
ColCurrentMatch ColorPair
ColCurrentCursor ColorPair
ColCurrentCursorEmpty ColorPair
ColCurrentSelected ColorPair
ColCurrentSelectedEmpty ColorPair
ColSpinner ColorPair
ColInfo ColorPair
ColHeader ColorPair
ColBorder ColorPair
ColPreview ColorPair
ColPreviewBorder ColorPair
)
func EmptyTheme() *ColorTheme {
return &ColorTheme{
Fg: colUndefined,
Bg: colUndefined,
PreviewFg: colUndefined,
PreviewBg: colUndefined,
DarkBg: colUndefined,
Gutter: colUndefined,
Prompt: colUndefined,
Match: colUndefined,
Current: colUndefined,
CurrentMatch: colUndefined,
Spinner: colUndefined,
Info: colUndefined,
Cursor: colUndefined,
Selected: colUndefined,
Header: colUndefined,
Border: colUndefined}
Colored: true,
Input: ColorAttr{colUndefined, AttrUndefined},
Fg: ColorAttr{colUndefined, AttrUndefined},
Bg: ColorAttr{colUndefined, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
DarkBg: ColorAttr{colUndefined, AttrUndefined},
Gutter: ColorAttr{colUndefined, AttrUndefined},
Prompt: ColorAttr{colUndefined, AttrUndefined},
Match: ColorAttr{colUndefined, AttrUndefined},
Current: ColorAttr{colUndefined, AttrUndefined},
CurrentMatch: ColorAttr{colUndefined, AttrUndefined},
Spinner: ColorAttr{colUndefined, AttrUndefined},
Info: ColorAttr{colUndefined, AttrUndefined},
Cursor: ColorAttr{colUndefined, AttrUndefined},
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) {
@@ -381,74 +458,80 @@ func errorExit(message string) {
func init() {
Default16 = &ColorTheme{
Fg: colDefault,
Bg: colDefault,
PreviewFg: colUndefined,
PreviewBg: colUndefined,
DarkBg: colBlack,
Gutter: colUndefined,
Prompt: colBlue,
Match: colGreen,
Current: colYellow,
CurrentMatch: colGreen,
Spinner: colGreen,
Info: colWhite,
Cursor: colRed,
Selected: colMagenta,
Header: colCyan,
Border: colBlack}
Colored: true,
Input: ColorAttr{colDefault, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
DarkBg: ColorAttr{colBlack, AttrUndefined},
Gutter: ColorAttr{colUndefined, AttrUndefined},
Prompt: ColorAttr{colBlue, AttrUndefined},
Match: ColorAttr{colGreen, AttrUndefined},
Current: ColorAttr{colYellow, AttrUndefined},
CurrentMatch: ColorAttr{colGreen, AttrUndefined},
Spinner: ColorAttr{colGreen, AttrUndefined},
Info: ColorAttr{colWhite, AttrUndefined},
Cursor: ColorAttr{colRed, AttrUndefined},
Selected: ColorAttr{colMagenta, AttrUndefined},
Header: ColorAttr{colCyan, AttrUndefined},
Border: ColorAttr{colBlack, AttrUndefined}}
Dark256 = &ColorTheme{
Fg: colDefault,
Bg: colDefault,
PreviewFg: colUndefined,
PreviewBg: colUndefined,
DarkBg: 236,
Gutter: colUndefined,
Prompt: 110,
Match: 108,
Current: 254,
CurrentMatch: 151,
Spinner: 148,
Info: 144,
Cursor: 161,
Selected: 168,
Header: 109,
Border: 59}
Colored: true,
Input: ColorAttr{colDefault, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
DarkBg: ColorAttr{236, AttrUndefined},
Gutter: ColorAttr{colUndefined, AttrUndefined},
Prompt: ColorAttr{110, AttrUndefined},
Match: ColorAttr{108, AttrUndefined},
Current: ColorAttr{254, AttrUndefined},
CurrentMatch: ColorAttr{151, AttrUndefined},
Spinner: ColorAttr{148, AttrUndefined},
Info: ColorAttr{144, AttrUndefined},
Cursor: ColorAttr{161, AttrUndefined},
Selected: ColorAttr{168, AttrUndefined},
Header: ColorAttr{109, AttrUndefined},
Border: ColorAttr{59, AttrUndefined}}
Light256 = &ColorTheme{
Fg: colDefault,
Bg: colDefault,
PreviewFg: colUndefined,
PreviewBg: colUndefined,
DarkBg: 251,
Gutter: colUndefined,
Prompt: 25,
Match: 66,
Current: 237,
CurrentMatch: 23,
Spinner: 65,
Info: 101,
Cursor: 161,
Selected: 168,
Header: 31,
Border: 145}
Colored: true,
Input: ColorAttr{colDefault, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
DarkBg: ColorAttr{251, AttrUndefined},
Gutter: ColorAttr{colUndefined, AttrUndefined},
Prompt: ColorAttr{25, AttrUndefined},
Match: ColorAttr{66, AttrUndefined},
Current: ColorAttr{237, AttrUndefined},
CurrentMatch: ColorAttr{23, AttrUndefined},
Spinner: ColorAttr{65, AttrUndefined},
Info: ColorAttr{101, AttrUndefined},
Cursor: ColorAttr{161, AttrUndefined},
Selected: ColorAttr{168, AttrUndefined},
Header: ColorAttr{31, AttrUndefined},
Border: ColorAttr{145, AttrUndefined}}
}
func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
if theme == nil {
initPalette(theme)
return
}
if forceBlack {
theme.Bg = colBlack
theme.Bg = ColorAttr{colBlack, AttrUndefined}
}
o := func(a Color, b Color) Color {
if b == colUndefined {
return a
o := func(a ColorAttr, b ColorAttr) ColorAttr {
c := 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.Bg = o(baseTheme.Bg, theme.Bg)
theme.PreviewFg = o(theme.Fg, o(baseTheme.PreviewFg, theme.PreviewFg))
@@ -470,54 +553,29 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
}
func initPalette(theme *ColorTheme) {
idx := 0
pair := func(fg, bg Color) ColorPair {
idx++
return ColorPair{fg, bg, idx}
pair := func(fg, bg ColorAttr) ColorPair {
return ColorPair{fg.Color, bg.Color, fg.Attr}
}
if theme != nil {
ColPrompt = pair(theme.Prompt, theme.Bg)
ColNormal = pair(theme.Fg, theme.Bg)
ColMatch = pair(theme.Match, theme.Bg)
ColCursor = pair(theme.Cursor, theme.Gutter)
ColSelected = pair(theme.Selected, theme.Gutter)
ColCurrent = pair(theme.Current, theme.DarkBg)
ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg)
ColCurrentCursor = pair(theme.Cursor, theme.DarkBg)
ColCurrentSelected = pair(theme.Selected, theme.DarkBg)
ColSpinner = pair(theme.Spinner, theme.Bg)
ColInfo = pair(theme.Info, theme.Bg)
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)
}
}
blank := theme.Fg
blank.Attr = AttrRegular
func attrFor(color ColorPair, attr Attr) Attr {
switch color {
case ColCurrent:
return attr | Reverse
case ColMatch:
return attr | Underline
case ColCurrentMatch:
return attr | Underline | Reverse
}
return attr
ColPrompt = pair(theme.Prompt, theme.Bg)
ColNormal = pair(theme.Fg, theme.Bg)
ColInput = pair(theme.Input, theme.Bg)
ColMatch = pair(theme.Match, theme.Bg)
ColCursor = pair(theme.Cursor, theme.Gutter)
ColCursorEmpty = pair(blank, 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)
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
UNSETS = %w[
FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS
FZF_TMUX FZF_TMUX_OPTS
FZF_CTRL_T_COMMAND FZF_CTRL_T_OPTS
FZF_ALT_C_COMMAND
FZF_ALT_C_OPTS FZF_CTRL_R_OPTS
fish_history
].freeze
DEFAULT_TIMEOUT = 20
DEFAULT_TIMEOUT = 10
FILE = File.expand_path(__FILE__)
BASE = File.expand_path('..', __dir__)
@@ -26,7 +27,7 @@ FZF = "FZF_DEFAULT_OPTS= FZF_DEFAULT_COMMAND= #{BASE}/bin/fzf"
def wait
since = Time.now
begin
yield
yield or raise Minitest::Assertion, 'Assertion failure'
rescue Minitest::Assertion
raise if Time.now - since > DEFAULT_TIMEOUT
@@ -77,7 +78,7 @@ class Tmux
return unless shell == :fish
send_keys 'function fish_prompt; end; clear', :Enter
self.until { |lines| raise Minitest::Assertion unless lines.empty? }
self.until(&:empty?)
end
def kill
@@ -108,7 +109,7 @@ class Tmux
class << lines
def counts
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?)
.first&.first&.map(&:to_i)&.values_at(0, 1, 3) || [0, 0, 0]
end
@@ -149,7 +150,7 @@ class Tmux
begin
self.until do |lines|
send_keys ' ', 'C-u', :Enter, 'hello', :Left, :Right
raise Minitest::Assertion unless lines[-1] == 'hello'
lines[-1] == 'hello'
end
rescue Minitest::Assertion
(tries += 1) < 5 ? retry : raise
@@ -420,7 +421,7 @@ class TestGoFZF < TestBase
echo ' first second third/') |
#{fzf(multi && :multi, :x, :nth, 2, :with_nth, '2,-1,1')}",
: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
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.until { |lines| assert_equal '>', lines[-1] }
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.until { |lines| assert_equal ' 19/100 (3)', lines[-2] }
tmux.send_keys :Enter
@@ -496,7 +497,7 @@ class TestGoFZF < TestBase
def test_tac
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.until { |lines| assert_equal ' 1000/1000 (3)', lines[-2] }
tmux.send_keys :Enter
@@ -505,9 +506,9 @@ class TestGoFZF < TestBase
def test_tac_sort
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.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.until { |lines| assert_equal ' 28/1000 (3)', lines[-2] }
tmux.send_keys :Enter
@@ -516,9 +517,9 @@ class TestGoFZF < TestBase
def test_tac_nosort
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.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.until { |lines| assert_equal ' 10/1000 (3)', lines[-2] }
tmux.send_keys :Enter
@@ -800,14 +801,14 @@ class TestGoFZF < TestBase
def test_bind
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'
assert_equal %w[4 5 6 9], readonce.lines(chomp: true)
end
def test_bind_print_query
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'
assert_equal %w[print-my-query], readonce.lines(chomp: true)
end
@@ -839,7 +840,7 @@ class TestGoFZF < TestBase
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.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.until { |lines| assert_equal ' 100/100 (3)', lines[-2] }
tmux.send_keys 'C-t'
@@ -855,7 +856,7 @@ class TestGoFZF < TestBase
tmux.send_keys 'C-u'
tmux.until { |lines| assert_equal 100, lines.match_count }
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.until { |lines| assert_equal ' 100/100 (2)', lines[-2] }
tmux.send_keys 0
@@ -962,7 +963,7 @@ class TestGoFZF < TestBase
opts = %[--multi --bind "alt-a:execute-multi(echo {}/{+} >> #{output})"]
writelines(tempname, %w[foo'bar foo"bar foo$bar foobar])
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 :BTab, :BTab, :BTab
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.until { |lines| assert_equal ' 2/2', lines[-2] }
tmux.until { |lines| assert_equal ' 2/2 (0)', lines[-2] }
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.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 :Tab
@@ -1362,7 +1363,7 @@ class TestGoFZF < TestBase
def test_jump
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.until { |lines| assert_equal '5 5', lines[-7] }
tmux.until { |lines| assert_equal ' 6', lines[-8] }
@@ -1390,7 +1391,7 @@ class TestGoFZF < TestBase
def test_jump_accept
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.until { |lines| assert_equal '5 5', lines[-7] }
tmux.send_keys '3'
@@ -1405,7 +1406,7 @@ class TestGoFZF < TestBase
def test_pointer_with_jump
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'
# Correctly padded jump label should appear
tmux.until { |lines| assert_equal '5 5', lines[-7] }
@@ -1417,7 +1418,7 @@ class TestGoFZF < TestBase
def test_marker
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
# Assert that specified marker is displayed
tmux.until { |lines| assert_equal ' >>1', lines[-3] }
@@ -1678,7 +1679,7 @@ class TestGoFZF < TestBase
tmux.send_keys :Tab
tmux.until { |lines| assert_equal ' 198/198 (1/2)', lines[-2] }
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
def test_reload_even_when_theres_no_match
@@ -1713,7 +1714,7 @@ class TestGoFZF < TestBase
tmux.send_keys 'foo'
tmux.until { |lines| assert_equal ' 0/100 (1)', lines[-2] }
tmux.send_keys :Space
tmux.until { |lines| assert_equal ' 0/100', lines[-2] }
tmux.until { |lines| assert_equal ' 0/100 (0)', lines[-2] }
end
def test_backward_delete_char_eof
@@ -1790,20 +1791,20 @@ class TestGoFZF < TestBase
def test_preview_scroll_begin_constant
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] }
end
def test_preview_scroll_begin_expr
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] }
end
def test_preview_scroll_begin_and_offset
['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.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.send_keys 'C-c'
end
@@ -1816,6 +1817,12 @@ class TestGoFZF < TestBase
assert_equal %w[A Á], `#{echoes} | #{FZF} -f A`.lines.map(&:chomp)
assert_equal %w[Á], `#{echoes} | #{FZF} -f Á`.lines.map(&:chomp)
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
module TestShell
@@ -1970,7 +1977,7 @@ module CompletionTest
FileUtils.mkdir_p('/tmp/fzf-test')
FileUtils.mkdir_p('/tmp/fzf test')
(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))
end
tmux.prepare
@@ -1986,14 +1993,15 @@ module CompletionTest
end
# ~USERNAME**<TAB>
user = ENV['USER']
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.send_keys "'.fzf-home"
tmux.until { |lines| assert(lines.any? { |l| l.end_with?('/.fzf-home') }) }
tmux.send_keys "/#{user}"
tmux.until { |lines| assert(lines.any? { |l| l.end_with?("/#{user}") }) }
tmux.send_keys :Enter
tmux.until(true) do |lines|
assert_match %r{cat .*/\.fzf-home}, lines[-1]
assert_match %r{cat .*/#{user}}, lines[-1]
end
# ~INVALID_USERNAME**<TAB>
@@ -2050,7 +2058,7 @@ module CompletionTest
tmux.until { |lines| assert_equal 'cd /tmp/fzf-test/d55/xx', lines[-1] }
# Should not match regular files (bash-only)
if self.class == TestBash
if instance_of?(TestBash)
tmux.send_keys :Tab
tmux.until { |lines| assert_equal 'cd /tmp/fzf-test/d55/xx', lines[-1] }
end
@@ -2241,6 +2249,11 @@ class TestFish < TestBase
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
# ---------
if [[ ! "$PATH" == *<%= BASE %>/bin* ]]; then
@@ -2255,9 +2268,6 @@ fi
# ------------
source "<%= BASE %>/shell/key-bindings.<%= __method__ %>"
PS1= PROMPT_COMMAND= HISTFILE= HISTSIZE=100
unset <%= UNSETS.join(' ') %>
# Old API
_fzf_complete_f() {
_fzf_complete "+m --multi --prompt \"prompt-f> \"" "$@" < <(