mirror of
https://github.com/junegunn/fzf.git
synced 2025-07-31 20:22:01 -07:00
Compare commits
70 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
00a3610331 | ||
|
f502725120 | ||
|
b62a74b315 | ||
|
2ec382ae0e | ||
|
cbfee31593 | ||
|
6d647e13ff | ||
|
d2af3ff98d | ||
|
052d17e66a | ||
|
a9bc954e17 | ||
|
2983426771 | ||
|
c61eb94b3f | ||
|
3829eab1cf | ||
|
3fe8eeedc5 | ||
|
1efef88b6e | ||
|
7acdaf0b43 | ||
|
1ed25d76ba | ||
|
474c1f5e32 | ||
|
8b71fea5dc | ||
|
7bd99a22ee | ||
|
75b8cca3b3 | ||
|
ef2c29d5d4 | ||
|
218b3c8274 | ||
|
db9cb2ddda | ||
|
722d66e85a | ||
|
f6269f0193 | ||
|
520eae817a | ||
|
d099941360 | ||
|
e3e76fa8c5 | ||
|
2553806e79 | ||
|
1bcbc5a353 | ||
|
15d351b0f0 | ||
|
c144c95cda | ||
|
f08f4fd87d | ||
|
f8aaeef218 | ||
|
7915e365b3 | ||
|
1c68f81c37 | ||
|
d4c9db0a27 | ||
|
b5e0e29ec6 | ||
|
569be4c6c9 | ||
|
e7ca237b07 | ||
|
a7d3b72117 | ||
|
3ba7b5cf2d | ||
|
254e9765fe | ||
|
3304f284a5 | ||
|
0d5f862daf | ||
|
51dfacd542 | ||
|
c691d52fa7 | ||
|
de3d09fe79 | ||
|
eaa413c566 | ||
|
407205e52b | ||
|
552414978e | ||
|
607081bbaa | ||
|
e73383fbbb | ||
|
2e8e63fb0b | ||
|
874f7dd416 | ||
|
8b0e3b1624 | ||
|
9b946f2b7a | ||
|
11841f688b | ||
|
03c4f04246 | ||
|
a1f06ae27f | ||
|
69dffd78a6 | ||
|
2750e19657 | ||
|
b0987f727b | ||
|
a4d9b0b468 | ||
|
e2b87e0d74 | ||
|
2166b4ca17 | ||
|
d2d4d68585 | ||
|
faf68dbc5c | ||
|
305896fcb3 | ||
|
6c9adea0d3 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -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
11
.gon.hcl
Normal 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
69
.goreleaser.yml
Normal 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
|
@@ -20,5 +20,9 @@ Style/MethodCallWithArgsParentheses:
|
||||
- ^refute_
|
||||
Style/NumericPredicate:
|
||||
Enabled: false
|
||||
Style/StringConcatenation:
|
||||
Enabled: false
|
||||
Style/OptionalBooleanParameter:
|
||||
Enabled: false
|
||||
Style/WordArray:
|
||||
MinSize: 1
|
||||
|
@@ -6,6 +6,7 @@ os:
|
||||
- linux
|
||||
- osx
|
||||
dist: bionic
|
||||
osx_image: xcode12.2
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
@@ -17,8 +18,7 @@ addons:
|
||||
packages:
|
||||
- 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
|
||||
|
22
BUILD.md
22
BUILD.md
@@ -17,21 +17,19 @@ make
|
||||
# Build fzf binary and copy it to bin directory
|
||||
make install
|
||||
|
||||
# Build 32-bit and 64-bit executables and tarballs in target
|
||||
# Build fzf binaries and archives for all platforms using goreleaser
|
||||
make build
|
||||
|
||||
# Publish GitHub release
|
||||
make release
|
||||
|
||||
# Make release archives for all supported platforms in target
|
||||
make release-all
|
||||
```
|
||||
|
||||
### Using `go get`
|
||||
|
||||
Alternatively, you can build fzf directly with `go get` command without
|
||||
manually cloning the repository.
|
||||
|
||||
```sh
|
||||
go get -u github.com/junegunn/fzf
|
||||
```
|
||||
> :warning: Makefile uses git commands to determine the version and the
|
||||
> revision information for `fzf --version`. So if you're building fzf from an
|
||||
> environment where its git information is not available, you have to manually
|
||||
> set `$FZF_VERSION` and `$FZF_REVISION`.
|
||||
>
|
||||
> e.g. `FZF_VERSION=0.24.0 FZF_REVISION=tarball make`
|
||||
|
||||
Third-party libraries used
|
||||
--------------------------
|
||||
|
91
CHANGELOG.md
91
CHANGELOG.md
@@ -1,6 +1,97 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
0.24.4
|
||||
------
|
||||
- Added `--preview-window` option `follow`
|
||||
```sh
|
||||
# Preview window will automatically scroll to the bottom
|
||||
fzf --preview-window follow --preview 'for i in $(seq 100000); do
|
||||
echo "$i"
|
||||
sleep 0.01
|
||||
(( i % 300 == 0 )) && printf "\033[2J"
|
||||
done'
|
||||
```
|
||||
- Added `change-prompt` action
|
||||
```sh
|
||||
fzf --prompt 'foo> ' --bind $'a:change-prompt:\x1b[31mbar> '
|
||||
```
|
||||
- Bug fixes and improvements
|
||||
|
||||
0.24.3
|
||||
------
|
||||
- Added `--padding` option
|
||||
```sh
|
||||
fzf --margin 5% --padding 5% --border --preview 'cat {}' \
|
||||
--color bg:#222222,preview-bg:#333333
|
||||
```
|
||||
|
||||
0.24.2
|
||||
------
|
||||
- Bug fixes and improvements
|
||||
|
||||
0.24.1
|
||||
------
|
||||
- Fixed broken `--color=[bw|no]` option
|
||||
|
||||
0.24.0
|
||||
------
|
||||
- Real-time rendering of preview window
|
||||
```sh
|
||||
# fzf can render preview window before the command completes
|
||||
fzf --preview 'sleep 1; for i in $(seq 100); do echo $i; sleep 0.01; done'
|
||||
|
||||
# Preview window can process ANSI escape sequence (CSI 2 J) for clearing the display
|
||||
fzf --preview 'for i in $(seq 100000); do
|
||||
(( i % 200 == 0 )) && printf "\033[2J"
|
||||
echo "$i"
|
||||
sleep 0.01
|
||||
done'
|
||||
```
|
||||
- Updated `--color` option to support text styles
|
||||
- `regular` / `bold` / `dim` / `underline` / `italic` / `reverse` / `blink`
|
||||
```sh
|
||||
# * Set -1 to keep the original color
|
||||
# * Multiple style attributes can be combined
|
||||
# * Italic style may not be supported by some terminals
|
||||
rg --line-number --no-heading --color=always "" |
|
||||
fzf --ansi --prompt "Rg: " \
|
||||
--color fg+:italic,hl:underline:-1,hl+:italic:underline:reverse:-1 \
|
||||
--color pointer:reverse,prompt:reverse,input:159 \
|
||||
--pointer ' '
|
||||
```
|
||||
- More `--border` options
|
||||
- `vertical`, `top`, `bottom`, `left`, `right`
|
||||
- Updated Vim plugin to use these new `--border` options
|
||||
```vim
|
||||
" Floating popup window in the center of the screen
|
||||
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
||||
|
||||
" Popup with 100% width
|
||||
let g:fzf_layout = { 'window': { 'width': 1.0, 'height': 0.5, 'border': 'horizontal' } }
|
||||
|
||||
" Popup with 100% height
|
||||
let g:fzf_layout = { 'window': { 'width': 0.5, 'height': 1.0, 'border': 'vertical' } }
|
||||
|
||||
" Similar to 'down' layout, but it uses a popup window and doesn't affect the window layout
|
||||
let g:fzf_layout = { 'window': { 'width': 1.0, 'height': 0.5, 'yoffset': 1.0, 'border': 'top' } }
|
||||
|
||||
" Opens on the right;
|
||||
" 'highlight' option is still supported but it will only take the foreground color of the group
|
||||
let g:fzf_layout = { 'window': { 'width': 0.5, 'height': 1.0, 'xoffset': 1.0, 'border': 'left', 'highlight': 'Comment' } }
|
||||
```
|
||||
- To indicate if `--multi` mode is enabled, fzf will print the number of
|
||||
selected items even when no item is selected
|
||||
```sh
|
||||
seq 100 | fzf
|
||||
# 100/100
|
||||
seq 100 | fzf --multi
|
||||
# 100/100 (0)
|
||||
seq 100 | fzf --multi 5
|
||||
# 100/100 (0/5)
|
||||
```
|
||||
- Since 0.24.0, release binaries will be uploaded to https://github.com/junegunn/fzf/releases
|
||||
|
||||
0.23.1
|
||||
------
|
||||
- Added `--preview-window` options for disabling flags
|
||||
|
@@ -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
|
||||
|
||||
|
102
Makefile
102
Makefile
@@ -5,8 +5,26 @@ MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
|
||||
ROOT_DIR := $(shell dirname $(MAKEFILE))
|
||||
SOURCES := $(wildcard *.go src/*.go src/*/*.go) $(MAKEFILE)
|
||||
|
||||
REVISION := $(shell git log -n 1 --pretty=format:%h -- $(SOURCES))
|
||||
BUILD_FLAGS := -a -ldflags "-X main.revision=$(REVISION) -w '-extldflags=$(LDFLAGS)'" -tags "$(TAGS)"
|
||||
ifdef FZF_VERSION
|
||||
VERSION := $(FZF_VERSION)
|
||||
else
|
||||
VERSION := $(shell git describe --abbrev=0 2> /dev/null)
|
||||
endif
|
||||
ifeq ($(VERSION),)
|
||||
$(error Not on git repository; cannot determine $$FZF_VERSION)
|
||||
endif
|
||||
VERSION_TRIM := $(shell sed "s/-.*//" <<< $(VERSION))
|
||||
VERSION_REGEX := $(subst .,\.,$(VERSION_TRIM))
|
||||
|
||||
ifdef FZF_REVISION
|
||||
REVISION := $(FZF_REVISION)
|
||||
else
|
||||
REVISION := $(shell git log -n 1 --pretty=format:%h -- $(SOURCES) 2> /dev/null)
|
||||
endif
|
||||
ifeq ($(REVISION),)
|
||||
$(error Not on git repository; cannot determine $$FZF_REVISION)
|
||||
endif
|
||||
BUILD_FLAGS := -a -ldflags "-s -w -X main.version=$(VERSION) -X main.revision=$(REVISION)" -tags "$(TAGS)"
|
||||
|
||||
BINARY64 := fzf-$(GOOS)_amd64
|
||||
BINARYARM5 := fzf-$(GOOS)_arm5
|
||||
@@ -14,13 +32,6 @@ BINARYARM6 := fzf-$(GOOS)_arm6
|
||||
BINARYARM7 := fzf-$(GOOS)_arm7
|
||||
BINARYARM8 := fzf-$(GOOS)_arm8
|
||||
BINARYPPC64LE := fzf-$(GOOS)_ppc64le
|
||||
VERSION := $(shell awk -F= '/version =/ {print $$2}' src/constants.go | tr -d "\" ")
|
||||
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)
|
||||
@@ -41,40 +52,11 @@ else ifeq ($(UNAME_M),aarch64)
|
||||
else ifeq ($(UNAME_M),ppc64le)
|
||||
BINARY := $(BINARYPPC64LE)
|
||||
else
|
||||
$(error "Build on $(UNAME_M) is not supported, yet.")
|
||||
$(error Build on $(UNAME_M) is not supported, yet.)
|
||||
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 +66,46 @@ test: $(SOURCES)
|
||||
|
||||
install: bin/fzf
|
||||
|
||||
build:
|
||||
goreleaser --rm-dist --snapshot
|
||||
|
||||
release:
|
||||
ifndef GITHUB_TOKEN
|
||||
$(error GITHUB_TOKEN is not defined)
|
||||
endif
|
||||
|
||||
# Check if we are on master branch
|
||||
ifneq ($(shell git symbolic-ref --short HEAD),master)
|
||||
$(error Not on master branch)
|
||||
endif
|
||||
|
||||
# Check if version numbers are properly updated
|
||||
grep -q ^$(VERSION_REGEX)$$ CHANGELOG.md
|
||||
grep -qF '"fzf $(VERSION_TRIM)"' man/man1/fzf.1
|
||||
grep -qF '"fzf $(VERSION_TRIM)"' man/man1/fzf-tmux.1
|
||||
grep -qF $(VERSION) install
|
||||
grep -qF $(VERSION) install.ps1
|
||||
|
||||
# Make release note out of CHANGELOG.md
|
||||
sed -n '/^$(VERSION_REGEX)$$/,/^[0-9]/p' CHANGELOG.md | tail -r | \
|
||||
sed '1,/^ *$$/d' | tail -r | sed 1,2d | tee tmp/release-note
|
||||
|
||||
# Push to temp branch first so that install scripts always works on master branch
|
||||
git checkout -B temp master
|
||||
git push origin temp --follow-tags --force
|
||||
|
||||
# Make a GitHub release
|
||||
goreleaser --rm-dist --release-notes tmp/release-note
|
||||
|
||||
# Push to master
|
||||
git checkout master
|
||||
git push origin master
|
||||
|
||||
# Delete temp branch
|
||||
git push origin --delete temp
|
||||
|
||||
clean:
|
||||
$(RM) -r target
|
||||
$(RM) -r dist target
|
||||
|
||||
target/$(BINARY64): $(SOURCES)
|
||||
GOARCH=amd64 $(GO) build $(BUILD_FLAGS) -o $@
|
||||
@@ -121,4 +141,4 @@ update:
|
||||
$(GO) get -u
|
||||
$(GO) mod tidy
|
||||
|
||||
.PHONY: all release release-all test install clean docker docker-test update
|
||||
.PHONY: all build release test install clean docker docker-test update
|
||||
|
@@ -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' }
|
||||
@@ -296,9 +298,8 @@ following options are allowed:
|
||||
- Optional:
|
||||
- `yoffset` [float default 0.5 range [0 ~ 1]]
|
||||
- `xoffset` [float default 0.5 range [0 ~ 1]]
|
||||
- `highlight` [string default `'Comment'`]: Highlight group for border
|
||||
- `border` [string default `rounded`]: Border style
|
||||
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right`
|
||||
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right` / `no[ne]`
|
||||
|
||||
`fzf#wrap`
|
||||
----------
|
||||
@@ -332,8 +333,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 +372,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
|
||||
----
|
||||
|
||||
|
25
README.md
25
README.md
@@ -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):
|
||||
|
||||
|
41
doc/fzf.txt
41
doc/fzf.txt
@@ -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. `tabedit some-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 `FileType fzf` 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
14
go.mod
@@ -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
40
go.sum
@@ -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=
|
||||
|
51
install
51
install
@@ -2,11 +2,10 @@
|
||||
|
||||
set -u
|
||||
|
||||
version=0.23.1
|
||||
version=0.24.4
|
||||
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"
|
||||
@@ -202,7 +197,7 @@ if [ -n "$binary_error" ]; then
|
||||
export GOPATH="${TMPDIR:-/tmp}/fzf-gopath"
|
||||
mkdir -p "$GOPATH"
|
||||
fi
|
||||
if go get -u github.com/junegunn/fzf; then
|
||||
if go get -ldflags "-s -w -X main.version=$version -X main.revision=go-get" github.com/junegunn/fzf; then
|
||||
echo "OK"
|
||||
cp "$GOPATH/bin/fzf" "$fzf_base/bin/"
|
||||
else
|
||||
|
26
install.ps1
26
install.ps1
@@ -1,10 +1,4 @@
|
||||
$version="0.23.1"
|
||||
|
||||
if ([Environment]::Is64BitProcess) {
|
||||
$binary_arch="amd64"
|
||||
} else {
|
||||
$binary_arch="386"
|
||||
}
|
||||
$version="0.24.4"
|
||||
|
||||
$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'
|
||||
|
5
main.go
5
main.go
@@ -5,9 +5,10 @@ import (
|
||||
"github.com/junegunn/fzf/src/protector"
|
||||
)
|
||||
|
||||
var revision string
|
||||
var version string = "0.24"
|
||||
var revision string = "devel"
|
||||
|
||||
func main() {
|
||||
protector.Protect()
|
||||
fzf.Run(fzf.ParseOptions(), revision)
|
||||
fzf.Run(fzf.ParseOptions(), version, revision)
|
||||
}
|
||||
|
@@ -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.1" "fzf-tmux - open fzf in tmux split pane"
|
||||
.TH fzf-tmux 1 "Dec 2020" "fzf 0.24.4" "fzf-tmux - open fzf in tmux split pane"
|
||||
|
||||
.SH NAME
|
||||
fzf-tmux - open fzf in tmux split pane
|
||||
|
177
man/man1/fzf.1
177
man/man1/fzf.1
@@ -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.1" "fzf - a command-line fuzzy finder"
|
||||
.TH fzf 1 "Dec 2020" "fzf 0.24.4" "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"
|
||||
@@ -223,6 +233,29 @@ e.g.
|
||||
\fBfzf --margin 10%
|
||||
fzf --margin 1,5%\fR
|
||||
.RE
|
||||
.TP
|
||||
.BI "--padding=" PADDING
|
||||
Comma-separated expression for padding inside the border. Padding is
|
||||
distinguishable from margin only when \fB--border\fR option is used.
|
||||
.br
|
||||
|
||||
.br
|
||||
e.g.
|
||||
\fBfzf --margin 5% --padding 5% --border --preview 'cat {}' \\
|
||||
--color bg:#222222,preview-bg:#333333\fR
|
||||
|
||||
.br
|
||||
.RS
|
||||
.BR TRBL " Same padding for top, right, bottom, and left"
|
||||
.br
|
||||
.BR TB,RL " Vertical, horizontal padding"
|
||||
.br
|
||||
.BR T,RL,B " Top, horizontal, bottom padding"
|
||||
.br
|
||||
.BR T,R,B,L " Top, right, bottom, left padding"
|
||||
.br
|
||||
.RE
|
||||
|
||||
.TP
|
||||
.BI "--info=" "STYLE"
|
||||
Determines the display style of finder info.
|
||||
@@ -267,11 +300,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 +313,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 +331,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,9 +425,21 @@ 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][:[no]wrap][:[no]cycle][:[no]hidden][:+SCROLL[-OFFSET]][:default]"
|
||||
.BI "--preview-window=" "[POSITION][:SIZE[%]][:rounded|sharp|noborder][:[no]wrap][:[no]follow][:[no]cycle][:[no]hidden][:+SCROLL[-OFFSET]][:default]"
|
||||
|
||||
.RS
|
||||
.B POSITION: (default: right)
|
||||
@@ -389,29 +447,44 @@ query if any of the placeholder expressions evaluates to a non-empty string.
|
||||
\fBdown
|
||||
\fBleft
|
||||
\fBright
|
||||
.RE
|
||||
|
||||
\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. Cyclic scrolling is enabled
|
||||
with \fB:cycle\fR flag.
|
||||
\fRDetermines the layout of the preview window.
|
||||
|
||||
If size is given as 0, preview window will not be visible, but fzf will still
|
||||
* If the argument contains \fB:hidden\fR, the preview window will be hidden by
|
||||
default until \fBtoggle-preview\fR action is triggered.
|
||||
|
||||
* If size is given as 0, preview window will not be visible, but fzf will still
|
||||
execute the command in the background.
|
||||
|
||||
To change the style of the border of the preview window, specify one of
|
||||
* Long lines are truncated by default. Line wrap can be enabled with
|
||||
\fB:wrap\fR flag.
|
||||
|
||||
* Preview window will automatically scroll to the bottom when \fB:follow\fR
|
||||
flag is set, similarly to how \fBtail -f\fR works.
|
||||
|
||||
.RS
|
||||
e.g.
|
||||
\fBfzf --preview-window follow --preview 'for i in $(seq 100000); do
|
||||
echo "$i"
|
||||
sleep 0.01
|
||||
(( i % 300 == 0 )) && printf "\\033[2J"
|
||||
done'\fR
|
||||
.RE
|
||||
|
||||
* Cyclic scrolling is enabled with \fB:cycle\fR flag.
|
||||
|
||||
* To change the style of the border of the preview window, specify one of
|
||||
\fBrounded\fR (border with rounded edges, default), \fBsharp\fR (border with
|
||||
sharp edges), or \fBnoborder\fR (no border).
|
||||
|
||||
\fB+SCROLL[-OFFSET]\fR determines the initial scroll offset of the preview
|
||||
* \fB+SCROLL[-OFFSET]\fR determines the initial scroll offset of the preview
|
||||
window. \fBSCROLL\fR can be either a numeric integer or a single-field index
|
||||
expression that refers to a numeric integer. The optional \fB-OFFSET\fR part is
|
||||
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.
|
||||
|
||||
\fBdefault\fR resets all options previously set to the default.
|
||||
* \fBdefault\fR resets all options previously set to the default.
|
||||
|
||||
.RS
|
||||
e.g.
|
||||
@@ -655,6 +728,14 @@ e.g.
|
||||
.br
|
||||
\fIshift-right\fR
|
||||
.br
|
||||
\fIalt-shift-up\fR
|
||||
.br
|
||||
\fIalt-shift-down\fR
|
||||
.br
|
||||
\fIalt-shift-left\fR
|
||||
.br
|
||||
\fIalt-shift-right\fR
|
||||
.br
|
||||
\fIleft-click\fR
|
||||
.br
|
||||
\fIright-click\fR
|
||||
@@ -696,6 +777,7 @@ A key or an event can be bound to one or more of the following actions.
|
||||
\fBbackward-word\fR \fIalt-b shift-left\fR
|
||||
\fBbeginning-of-line\fR \fIctrl-a home\fR
|
||||
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
||||
\fBchange-prompt(...)\fR (change prompt to the given string)
|
||||
\fBclear-screen\fR \fIctrl-l\fR
|
||||
\fBclear-selection\fR (clear multi-selection)
|
||||
\fBclear-query\fR (clear query string)
|
||||
@@ -751,7 +833,40 @@ A key or an event can be bound to one or more of the following actions.
|
||||
Multiple actions can be chained using \fB+\fR separator.
|
||||
|
||||
e.g.
|
||||
\fBfzf --bind 'ctrl-a:select-all+accept'\fR
|
||||
\fBfzf --multi --bind 'ctrl-a:select-all+accept'\fR
|
||||
\fBfzf --multi --bind 'ctrl-a:select-all' --bind 'ctrl-a:+accept'\fR
|
||||
|
||||
.SS ACTION ARGUMENT
|
||||
|
||||
An action denoted with \fB(...)\fR suffix takes an argument.
|
||||
|
||||
e.g.
|
||||
\fBfzf --bind 'ctrl-a:change-prompt(NewPrompt> )'\fR
|
||||
\fBfzf --bind 'ctrl-v:preview(cat {})' --preview-window hidden\fR
|
||||
|
||||
If the argument contains parentheses, fzf may fail to parse the expression. In
|
||||
that case, you can use any of the following alternative notations to avoid
|
||||
parse errors.
|
||||
|
||||
\fBaction-name[...]\fR
|
||||
\fBaction-name~...~\fR
|
||||
\fBaction-name!...!\fR
|
||||
\fBaction-name@...@\fR
|
||||
\fBaction-name#...#\fR
|
||||
\fBaction-name$...$\fR
|
||||
\fBaction-name%...%\fR
|
||||
\fBaction-name^...^\fR
|
||||
\fBaction-name&...&\fR
|
||||
\fBaction-name*...*\fR
|
||||
\fBaction-name;...;\fR
|
||||
\fBaction-name/.../\fR
|
||||
\fBaction-name|...|\fR
|
||||
\fBaction-name:...\fR
|
||||
.RS
|
||||
The last one is the special form that frees you from parse errors as it does
|
||||
not expect the closing character. The catch is that it should be the last one
|
||||
in the comma-separated list of key-action pairs.
|
||||
.RE
|
||||
|
||||
.SS COMMAND EXECUTION
|
||||
|
||||
@@ -763,30 +878,6 @@ binding \fBenter\fR key to \fBless\fR command like follows.
|
||||
|
||||
You can use the same placeholder expressions as in \fB--preview\fR.
|
||||
|
||||
If the command contains parentheses, fzf may fail to parse the expression. In
|
||||
that case, you can use any of the following alternative notations to avoid
|
||||
parse errors.
|
||||
|
||||
\fBexecute[...]\fR
|
||||
\fBexecute~...~\fR
|
||||
\fBexecute!...!\fR
|
||||
\fBexecute@...@\fR
|
||||
\fBexecute#...#\fR
|
||||
\fBexecute$...$\fR
|
||||
\fBexecute%...%\fR
|
||||
\fBexecute^...^\fR
|
||||
\fBexecute&...&\fR
|
||||
\fBexecute*...*\fR
|
||||
\fBexecute;...;\fR
|
||||
\fBexecute/.../\fR
|
||||
\fBexecute|...|\fR
|
||||
\fBexecute:...\fR
|
||||
.RS
|
||||
The last one is the special form that frees you from parse errors as it does
|
||||
not expect the closing character. The catch is that it should be the last one
|
||||
in the comma-separated list of key-action pairs.
|
||||
.RE
|
||||
|
||||
fzf switches to the alternate screen when executing a command. However, if the
|
||||
command is expected to complete quickly, and you are not interested in its
|
||||
output, you might want to use \fBexecute-silent\fR instead, which silently
|
||||
|
138
plugin/fzf.vim
138
plugin/fzf.vim
@@ -128,7 +128,7 @@ endfunction
|
||||
|
||||
function! s:default_layout()
|
||||
return s:popup_support()
|
||||
\ ? { 'window' : { 'width': 0.9, 'height': 0.6, 'highlight': 'Normal' } }
|
||||
\ ? { 'window' : { 'width': 0.9, 'height': 0.6 } }
|
||||
\ : { 'down': '~40%' }
|
||||
endfunction
|
||||
|
||||
@@ -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[-1], '[0-9.]\+')
|
||||
if s:version_requirement(fzf_version, a:1)
|
||||
let s:checked[a:1] = 1
|
||||
return s:exec
|
||||
elseif a:0 < 2 && input(printf('You need fzf %s or above. Found: %s. Download binary? (y/n) ', a:1, fzf_version)) =~? '^y'
|
||||
redraw
|
||||
call fzf#install()
|
||||
return fzf#exec(a:1, 1)
|
||||
else
|
||||
throw printf('You need to upgrade fzf (required: %s or above)', a:1)
|
||||
endif
|
||||
endif
|
||||
|
||||
return s:exec
|
||||
endfunction
|
||||
|
||||
@@ -250,7 +283,8 @@ function! s:common_sink(action, lines) abort
|
||||
let cwd = exists('w:fzf_pushd') ? w:fzf_pushd.dir : expand('%:p:h')
|
||||
for item in a:lines
|
||||
if item[0] != '~' && item !~ (s:is_win ? '^[A-Z]:\' : '^/')
|
||||
let item = join([cwd, item], (s:is_win ? '\' : '/'))
|
||||
let sep = s:is_win ? '\' : '/'
|
||||
let item = join([cwd, item], cwd[len(cwd)-1] == sep ? '' : sep)
|
||||
endif
|
||||
if empty
|
||||
execute 'e' s:escape(item)
|
||||
@@ -404,7 +438,7 @@ try
|
||||
let prefix = '( '.source.' )|'
|
||||
elseif type == 3
|
||||
let temps.input = s:fzf_tempname()
|
||||
call writefile(map(source, '<SID>enc_to_cp(v:val)'), temps.input)
|
||||
call writefile(source, temps.input)
|
||||
let prefix = (s:is_win ? 'type ' : 'cat ').fzf#shellescape(temps.input).'|'
|
||||
else
|
||||
throw 'Invalid source type'
|
||||
@@ -432,6 +466,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
|
||||
@@ -610,7 +645,8 @@ function! s:execute(dict, command, use_height, temps) abort
|
||||
endif
|
||||
let exit_status = v:shell_error
|
||||
redraw!
|
||||
return s:exit_handler(exit_status, command) ? s:collect(a:temps) : []
|
||||
let lines = s:collect(a:temps)
|
||||
return s:exit_handler(exit_status, command) ? lines : []
|
||||
endfunction
|
||||
|
||||
function! s:execute_tmux(dict, command, temps) abort
|
||||
@@ -624,7 +660,8 @@ function! s:execute_tmux(dict, command, temps) abort
|
||||
call system(command)
|
||||
let exit_status = v:shell_error
|
||||
redraw!
|
||||
return s:exit_handler(exit_status, command) ? s:collect(a:temps) : []
|
||||
let lines = s:collect(a:temps)
|
||||
return s:exit_handler(exit_status, command) ? lines : []
|
||||
endfunction
|
||||
|
||||
function! s:calc_size(max, val, dict)
|
||||
@@ -656,6 +693,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' || style == 'no'
|
||||
return ''
|
||||
endif
|
||||
|
||||
" For --border styles, we need fzf 0.24.0 or above
|
||||
call fzf#exec('0.24.0')
|
||||
let opt = ' --border=' . style
|
||||
if has_key(a:window, 'highlight')
|
||||
let color = s:get_color('fg', a:window.highlight)
|
||||
if len(color)
|
||||
let opt .= ' --color=border:' . color
|
||||
endif
|
||||
endif
|
||||
return opt
|
||||
endfunction
|
||||
|
||||
function! s:split(dict)
|
||||
let directions = {
|
||||
\ 'up': ['topleft', 'resize', &lines],
|
||||
@@ -745,12 +808,12 @@ function! s:execute_term(dict, command, temps) abort
|
||||
execute self.winrest
|
||||
endif
|
||||
|
||||
let lines = s:collect(self.temps)
|
||||
if !s:exit_handler(a:code, self.command, 1)
|
||||
return
|
||||
endif
|
||||
|
||||
call s:pushd(self.dict)
|
||||
let lines = s:collect(self.temps)
|
||||
call s:callback(self.dict, lines)
|
||||
call self.switch_back(s:getpos() == self.ppos)
|
||||
endfunction
|
||||
@@ -775,6 +838,9 @@ function! s:execute_term(dict, command, temps) abort
|
||||
let term_opts.curwin = 1
|
||||
endif
|
||||
let fzf.buf = term_start([&shell, &shellcmdflag, command], term_opts)
|
||||
if exists('&termwinkey')
|
||||
call setbufvar(fzf.buf, '&termwinkey', '<c-z>')
|
||||
endif
|
||||
if is_popup && exists('#TerminalWinOpen')
|
||||
doautocmd <nomodeline> TerminalWinOpen
|
||||
endif
|
||||
@@ -782,6 +848,7 @@ function! s:execute_term(dict, command, temps) abort
|
||||
call term_wait(fzf.buf, 20)
|
||||
endif
|
||||
endif
|
||||
tnoremap <buffer> <c-z> <nop>
|
||||
finally
|
||||
call s:dopopd()
|
||||
endtry
|
||||
@@ -848,33 +915,22 @@ if has('nvim')
|
||||
endfunction
|
||||
else
|
||||
function! s:create_popup(hl, opts) abort
|
||||
let is_frame = has_key(a:opts, 'border')
|
||||
let s:popup_create = {buf -> popup_create(buf, #{
|
||||
\ line: a:opts.row,
|
||||
\ col: a:opts.col,
|
||||
\ minwidth: a:opts.width,
|
||||
\ maxwidth: a:opts.width,
|
||||
\ minheight: a:opts.height,
|
||||
\ zindex: 50 - is_frame,
|
||||
\ maxheight: a:opts.height,
|
||||
\ zindex: 1000,
|
||||
\ })}
|
||||
if is_frame
|
||||
let id = s:popup_create('')
|
||||
call setwinvar(id, '&wincolor', a:hl)
|
||||
call setbufline(winbufnr(id), 1, a:opts.border)
|
||||
execute 'autocmd BufWipeout * ++once call popup_close('..id..')'
|
||||
return winbufnr(id)
|
||||
else
|
||||
autocmd TerminalOpen * ++once call s:popup_create(str2nr(expand('<abuf>')))
|
||||
endif
|
||||
autocmd TerminalOpen * ++once call s:popup_create(str2nr(expand('<abuf>')))
|
||||
endfunction
|
||||
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 = {
|
||||
|
@@ -46,9 +46,20 @@ __fzf_comprun() {
|
||||
fi
|
||||
}
|
||||
|
||||
__fzf_orig_completion_filter() {
|
||||
sed 's/^\(.*-F\) *\([^ ]*\).* \([^ ]*\)$/export _fzf_orig_completion_\3="\1 %s \3 #\2"; [[ "\1" = *" -o nospace "* ]] \&\& [[ ! "$__fzf_nospace_commands" = *" \3 "* ]] \&\& __fzf_nospace_commands="$__fzf_nospace_commands \3 ";/' |
|
||||
awk -F= '{OFS = FS} {gsub(/[^A-Za-z0-9_= ;]/, "_", $1);}1'
|
||||
__fzf_orig_completion() {
|
||||
local l comp f cmd
|
||||
while read -r l; do
|
||||
if [[ "$l" =~ ^(.*\ -F)\ *([^ ]*).*\ ([^ ]*)$ ]]; then
|
||||
comp="${BASH_REMATCH[1]}"
|
||||
f="${BASH_REMATCH[2]}"
|
||||
cmd="${BASH_REMATCH[3]}"
|
||||
[[ "$f" = _fzf_* ]] && continue
|
||||
printf -v "_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}" "%s" "${comp} %s ${cmd} #${f}"
|
||||
if [[ "$l" = *" -o nospace "* ]] && [[ ! "$__fzf_nospace_commands" = *" $cmd "* ]]; then
|
||||
__fzf_nospace_commands="$__fzf_nospace_commands $cmd "
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
_fzf_opts_completion() {
|
||||
@@ -137,7 +148,7 @@ _fzf_handle_dynamic_completion() {
|
||||
ret=$?
|
||||
# _completion_loader may not have updated completion for the command
|
||||
if [ "$(complete -p "$orig_cmd" 2> /dev/null)" != "$orig_complete" ]; then
|
||||
eval "$(complete | command grep " -F.* $orig_cmd$" | __fzf_orig_completion_filter)"
|
||||
__fzf_orig_completion < <(complete -p "$orig_cmd" 2> /dev/null)
|
||||
if [[ "$__fzf_nospace_commands" = *" $orig_cmd "* ]]; then
|
||||
eval "${orig_complete/ -F / -o nospace -F }"
|
||||
else
|
||||
@@ -306,9 +317,7 @@ a_cmds="
|
||||
svn tar unzip zip"
|
||||
|
||||
# Preserve existing completion
|
||||
eval "$(complete |
|
||||
sed -E '/-F/!d; / _fzf/d; '"/ ($(echo $d_cmds $a_cmds | sed 's/ /|/g; s/+/\\+/g'))$/"'!d' |
|
||||
__fzf_orig_completion_filter)"
|
||||
__fzf_orig_completion < <(complete -p $d_cmds $a_cmds 2> /dev/null)
|
||||
|
||||
if type _completion_loader > /dev/null 2>&1; then
|
||||
_fzf_completion_loader=1
|
||||
@@ -353,7 +362,7 @@ _fzf_setup_completion() {
|
||||
return 1
|
||||
fi
|
||||
shift
|
||||
eval "$(complete -p "$@" 2> /dev/null | grep -v "$fn" | __fzf_orig_completion_filter)"
|
||||
__fzf_orig_completion < <(complete -p "$@" 2> /dev/null)
|
||||
for cmd in "$@"; do
|
||||
case "$kind" in
|
||||
dir) __fzf_defc "$cmd" "$fn" "-o nospace -o dirnames" ;;
|
||||
|
@@ -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
|
||||
|
@@ -45,6 +45,7 @@ __fsel() {
|
||||
-o -type d -print \
|
||||
-o -type l -print 2> /dev/null | cut -b3-"}"
|
||||
setopt localoptions pipefail no_aliases 2> /dev/null
|
||||
local item
|
||||
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read item; do
|
||||
echo -n "${(q)item} "
|
||||
done
|
||||
@@ -106,6 +107,7 @@ bindkey '\ec' fzf-cd-widget
|
||||
fzf-history-widget() {
|
||||
local selected num
|
||||
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
|
||||
[[ -o sharehistory ]] && fc -RI
|
||||
selected=( $(fc -rl 1 | perl -ne 'print if !$seen{(/^\s*[0-9]+\**\s+(.*)/, $1)}++' |
|
||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
|
||||
local ret=$?
|
||||
|
12
src/ansi.go
12
src/ansi.go
@@ -19,17 +19,18 @@ type ansiState struct {
|
||||
fg tui.Color
|
||||
bg tui.Color
|
||||
attr tui.Attr
|
||||
lbg tui.Color
|
||||
}
|
||||
|
||||
func (s *ansiState) colored() bool {
|
||||
return s.fg != -1 || s.bg != -1 || s.attr > 0
|
||||
return s.fg != -1 || s.bg != -1 || s.attr > 0 || s.lbg >= 0
|
||||
}
|
||||
|
||||
func (s *ansiState) equals(t *ansiState) bool {
|
||||
if t == nil {
|
||||
return !s.colored()
|
||||
}
|
||||
return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr
|
||||
return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr && s.lbg == t.lbg
|
||||
}
|
||||
|
||||
func (s *ansiState) ToString() string {
|
||||
@@ -195,11 +196,14 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
||||
// State
|
||||
var state *ansiState
|
||||
if prevState == nil {
|
||||
state = &ansiState{-1, -1, 0}
|
||||
state = &ansiState{-1, -1, 0, -1}
|
||||
} else {
|
||||
state = &ansiState{prevState.fg, prevState.bg, prevState.attr}
|
||||
state = &ansiState{prevState.fg, prevState.bg, prevState.attr, prevState.lbg}
|
||||
}
|
||||
if ansiCode[0] != '\x1b' || ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' {
|
||||
if strings.HasSuffix(ansiCode, "0K") {
|
||||
state.lbg = prevState.bg
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
|
@@ -168,7 +168,7 @@ func TestAnsiCodeStringConversion(t *testing.T) {
|
||||
}
|
||||
}
|
||||
assert("\x1b[m", nil, "")
|
||||
assert("\x1b[m", &ansiState{attr: tui.Blink}, "")
|
||||
assert("\x1b[m", &ansiState{attr: tui.Blink, lbg: -1}, "")
|
||||
|
||||
assert("\x1b[31m", nil, "\x1b[31;49m")
|
||||
assert("\x1b[41m", nil, "\x1b[39;41m")
|
||||
@@ -176,8 +176,8 @@ func TestAnsiCodeStringConversion(t *testing.T) {
|
||||
assert("\x1b[92m", nil, "\x1b[92;49m")
|
||||
assert("\x1b[102m", nil, "\x1b[39;102m")
|
||||
|
||||
assert("\x1b[31m", &ansiState{fg: 4, bg: 4}, "\x1b[31;44m")
|
||||
assert("\x1b[1;2;31m", &ansiState{fg: 2, bg: -1, attr: tui.Reverse}, "\x1b[1;2;7;31;49m")
|
||||
assert("\x1b[31m", &ansiState{fg: 4, bg: 4, lbg: -1}, "\x1b[31;44m")
|
||||
assert("\x1b[1;2;31m", &ansiState{fg: 2, bg: -1, attr: tui.Reverse, lbg: -1}, "\x1b[1;2;7;31;49m")
|
||||
assert("\x1b[38;5;100;48;5;200m", nil, "\x1b[38;5;100;48;5;200m")
|
||||
assert("\x1b[48;5;100;38;5;200m", nil, "\x1b[38;5;200;48;5;100m")
|
||||
assert("\x1b[48;5;100;38;2;10;20;30;1m", nil, "\x1b[1;38;2;10;20;30;48;5;100m")
|
||||
|
@@ -9,9 +9,6 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// Current version
|
||||
version = "0.23.1"
|
||||
|
||||
// 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
|
||||
|
||||
|
@@ -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
|
||||
|
168
src/options.go
168
src/options.go
@@ -58,8 +58,10 @@ 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)
|
||||
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
|
||||
[rounded|sharp|horizontal|vertical|
|
||||
top|bottom|left|right] (default: rounded)
|
||||
--margin=MARGIN Screen margin (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
||||
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
||||
--info=STYLE Finder info style [default|inline|hidden]
|
||||
--prompt=STR Input prompt (default: '> ')
|
||||
--pointer=STR Pointer to the current line (default: '>')
|
||||
@@ -81,7 +83,7 @@ const usage = `usage: fzf [options]
|
||||
--preview=COMMAND Command to preview highlighted line ({})
|
||||
--preview-window=OPT Preview window layout (default: right:50%)
|
||||
[up|down|left|right][:SIZE[%]]
|
||||
[:[no]wrap][:[no]cycle][:[no]hidden]
|
||||
[:[no]wrap][:[no]cycle][:[no]follow][:[no]hidden]
|
||||
[:rounded|sharp|noborder]
|
||||
[:+SCROLL[-OFFSET]]
|
||||
[:default]
|
||||
@@ -167,6 +169,7 @@ type previewOpts struct {
|
||||
hidden bool
|
||||
wrap bool
|
||||
cycle bool
|
||||
follow bool
|
||||
border tui.BorderShape
|
||||
}
|
||||
|
||||
@@ -220,6 +223,7 @@ type Options struct {
|
||||
Header []string
|
||||
HeaderLines int
|
||||
Margin [4]sizeSpec
|
||||
Padding [4]sizeSpec
|
||||
BorderShape tui.BorderShape
|
||||
Unicode bool
|
||||
Tabstop int
|
||||
@@ -228,7 +232,7 @@ type Options struct {
|
||||
}
|
||||
|
||||
func defaultPreviewOpts(command string) previewOpts {
|
||||
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, tui.BorderRounded}
|
||||
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, tui.BorderRounded}
|
||||
}
|
||||
|
||||
func defaultOptions() *Options {
|
||||
@@ -280,6 +284,7 @@ func defaultOptions() *Options {
|
||||
Header: make([]string, 0),
|
||||
HeaderLines: 0,
|
||||
Margin: defaultMargin(),
|
||||
Padding: defaultMargin(),
|
||||
Unicode: true,
|
||||
Tabstop: 8,
|
||||
ClearOnExit: true,
|
||||
@@ -421,11 +426,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
|
||||
}
|
||||
@@ -510,6 +525,14 @@ func parseKeyChords(str string, message string) map[int]string {
|
||||
chord = tui.PgUp
|
||||
case "pgdn", "page-down":
|
||||
chord = tui.PgDn
|
||||
case "alt-shift-up", "shift-alt-up":
|
||||
chord = tui.AltSUp
|
||||
case "alt-shift-down", "shift-alt-down":
|
||||
chord = tui.AltSDown
|
||||
case "alt-shift-left", "shift-alt-left":
|
||||
chord = tui.AltSLeft
|
||||
case "alt-shift-right", "shift-alt-right":
|
||||
chord = tui.AltSRight
|
||||
case "shift-up":
|
||||
chord = tui.SUp
|
||||
case "shift-down":
|
||||
@@ -590,11 +613,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 {
|
||||
@@ -609,7 +629,7 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
|
||||
case "16":
|
||||
theme = dupeTheme(tui.Default16)
|
||||
case "bw", "no":
|
||||
theme = nil
|
||||
theme = tui.NoColorTheme()
|
||||
default:
|
||||
fail := func() {
|
||||
errorExit("invalid color specification: " + str)
|
||||
@@ -619,54 +639,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()
|
||||
}
|
||||
@@ -694,7 +736,7 @@ func init() {
|
||||
// Backreferences are not supported.
|
||||
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
||||
executeRegexp = regexp.MustCompile(
|
||||
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview):.+|[:+](execute(?:-multi|-silent)?|reload|preview)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
||||
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
||||
}
|
||||
|
||||
func parseKeymap(keymap map[int][]action, str string) {
|
||||
@@ -708,6 +750,8 @@ func parseKeymap(keymap map[int][]action, str string) {
|
||||
prefix = symbol + "reload"
|
||||
} else if strings.HasPrefix(src[1:], "preview") {
|
||||
prefix = symbol + "preview"
|
||||
} else if strings.HasPrefix(src[1:], "change-prompt") {
|
||||
prefix = symbol + "change-prompt"
|
||||
} else if src[len(prefix)] == '-' {
|
||||
c := src[len(prefix)+1]
|
||||
if c == 's' || c == 'S' {
|
||||
@@ -881,6 +925,8 @@ func parseKeymap(keymap map[int][]action, str string) {
|
||||
offset = len("reload")
|
||||
case actPreview:
|
||||
offset = len("preview")
|
||||
case actChangePrompt:
|
||||
offset = len("change-prompt")
|
||||
case actExecuteSilent:
|
||||
offset = len("execute-silent")
|
||||
case actExecuteMulti:
|
||||
@@ -920,6 +966,8 @@ func isExecuteAction(str string) actionType {
|
||||
return actReload
|
||||
case "preview":
|
||||
return actPreview
|
||||
case "change-prompt":
|
||||
return actChangePrompt
|
||||
case "execute":
|
||||
return actExecute
|
||||
case "execute-silent":
|
||||
@@ -1034,6 +1082,10 @@ func parsePreviewWindow(opts *previewOpts, input string) {
|
||||
opts.border = tui.BorderSharp
|
||||
case "noborder":
|
||||
opts.border = tui.BorderNone
|
||||
case "follow":
|
||||
opts.follow = true
|
||||
case "nofollow":
|
||||
opts.follow = false
|
||||
default:
|
||||
if sizeRegex.MatchString(token) {
|
||||
opts.size = parseSize(token, 99, "window size")
|
||||
@@ -1046,10 +1098,10 @@ func parsePreviewWindow(opts *previewOpts, input string) {
|
||||
}
|
||||
}
|
||||
|
||||
func parseMargin(margin string) [4]sizeSpec {
|
||||
func parseMargin(opt string, margin string) [4]sizeSpec {
|
||||
margins := strings.Split(margin, ",")
|
||||
checked := func(str string) sizeSpec {
|
||||
return parseSize(str, 49, "margin")
|
||||
return parseSize(str, 49, opt)
|
||||
}
|
||||
switch len(margins) {
|
||||
case 1:
|
||||
@@ -1069,7 +1121,7 @@ func parseMargin(margin string) [4]sizeSpec {
|
||||
checked(margins[0]), checked(margins[1]),
|
||||
checked(margins[2]), checked(margins[3])}
|
||||
default:
|
||||
errorExit("invalid margin: " + margin)
|
||||
errorExit("invalid " + opt + ": " + margin)
|
||||
}
|
||||
return defaultMargin()
|
||||
}
|
||||
@@ -1180,7 +1232,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":
|
||||
@@ -1294,6 +1346,8 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
opts.Height = sizeSpec{}
|
||||
case "--no-margin":
|
||||
opts.Margin = defaultMargin()
|
||||
case "--no-padding":
|
||||
opts.Padding = defaultMargin()
|
||||
case "--no-border":
|
||||
opts.BorderShape = tui.BorderNone
|
||||
case "--border":
|
||||
@@ -1305,7 +1359,12 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
opts.Unicode = true
|
||||
case "--margin":
|
||||
opts.Margin = parseMargin(
|
||||
"margin",
|
||||
nextString(allArgs, &i, "margin required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
|
||||
case "--padding":
|
||||
opts.Padding = parseMargin(
|
||||
"padding",
|
||||
nextString(allArgs, &i, "padding required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
|
||||
case "--tabstop":
|
||||
opts.Tabstop = nextInt(allArgs, &i, "tab stop required")
|
||||
case "--clear":
|
||||
@@ -1374,7 +1433,9 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
} else if match, value := optString(arg, "--preview-window="); match {
|
||||
parsePreviewWindow(&opts.Preview, value)
|
||||
} else if match, value := optString(arg, "--margin="); match {
|
||||
opts.Margin = parseMargin(value)
|
||||
opts.Margin = parseMargin("margin", value)
|
||||
} else if match, value := optString(arg, "--padding="); match {
|
||||
opts.Padding = parseMargin("padding", value)
|
||||
} else if match, value := optString(arg, "--tabstop="); match {
|
||||
opts.Tabstop = atoi(value)
|
||||
} else if match, value := optString(arg, "--hscroll-off="); match {
|
||||
@@ -1478,6 +1539,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
|
||||
|
@@ -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()
|
||||
|
@@ -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,53 @@ 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)
|
||||
// hl or hl+ only sets the foreground color, so colMatch is the
|
||||
// combination of either [hl and bg] or [hl+ and bg+].
|
||||
//
|
||||
// If the original text already has background color, and the
|
||||
// forground color of colMatch is -1, we shouldn't only apply the
|
||||
// background color of colMatch.
|
||||
// e.g. echo -e "\x1b[32;7mfoo\x1b[mbar" | fzf --ansi --color bg+:1,hl+:-1:underline
|
||||
// echo -e "\x1b[42mfoo\x1b[mbar" | fzf --ansi --color bg+:1,hl+:-1:underline
|
||||
if color.Fg().IsDefault() && origColor.HasBg() {
|
||||
color = origColor
|
||||
} else {
|
||||
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)})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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, -1}},
|
||||
{[2]int32{22, 27}, ansiState{2, 6, tui.Bold, -1}},
|
||||
{[2]int32{30, 32}, ansiState{3, 7, 0, -1}},
|
||||
{[2]int32{33, 40}, ansiState{4, 8, tui.Bold, -1}}}}}
|
||||
|
||||
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))
|
||||
}
|
||||
|
593
src/terminal.go
593
src/terminal.go
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@
|
||||
|
||||
package tui
|
||||
|
||||
type Attr int
|
||||
type Attr int32
|
||||
|
||||
func HasFullscreenRenderer() bool {
|
||||
return false
|
||||
@@ -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) {}
|
||||
|
||||
|
128
src/tui/light.go
128
src/tui/light.go
@@ -463,20 +463,54 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
}
|
||||
return Event{Invalid, 0, nil}
|
||||
case ';':
|
||||
if len(r.buffer) != 6 {
|
||||
if len(r.buffer) < 6 {
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
*sz = 6
|
||||
switch r.buffer[4] {
|
||||
case '2', '5':
|
||||
switch r.buffer[5] {
|
||||
case '1', '2', '3', '5':
|
||||
alt := r.buffer[4] == '3'
|
||||
altShift := r.buffer[4] == '1' && r.buffer[5] == '0'
|
||||
char := r.buffer[5]
|
||||
if altShift {
|
||||
if len(r.buffer) < 7 {
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
*sz = 7
|
||||
char = r.buffer[6]
|
||||
}
|
||||
switch char {
|
||||
case 'A':
|
||||
if alt {
|
||||
return Event{AltUp, 0, nil}
|
||||
}
|
||||
if altShift {
|
||||
return Event{AltSUp, 0, nil}
|
||||
}
|
||||
return Event{SUp, 0, nil}
|
||||
case 'B':
|
||||
if alt {
|
||||
return Event{AltDown, 0, nil}
|
||||
}
|
||||
if altShift {
|
||||
return Event{AltSDown, 0, nil}
|
||||
}
|
||||
return Event{SDown, 0, nil}
|
||||
case 'C':
|
||||
if alt {
|
||||
return Event{AltRight, 0, nil}
|
||||
}
|
||||
if altShift {
|
||||
return Event{AltSRight, 0, nil}
|
||||
}
|
||||
return Event{SRight, 0, nil}
|
||||
case 'D':
|
||||
if alt {
|
||||
return Event{AltLeft, 0, nil}
|
||||
}
|
||||
if altShift {
|
||||
return Event{AltSLeft, 0, nil}
|
||||
}
|
||||
return Event{SLeft, 0, nil}
|
||||
}
|
||||
} // r.buffer[4]
|
||||
@@ -624,14 +658,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 +671,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 +687,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 +735,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 +806,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 +868,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 +891,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' {
|
||||
|
114
src/tui/tcell.go
114
src/tui/tcell.go
@@ -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()
|
||||
@@ -224,7 +222,10 @@ func (r *FullscreenRenderer) GetChar() Event {
|
||||
|
||||
// process keyboard:
|
||||
case *tcell.EventKey:
|
||||
alt := (ev.Modifiers() & tcell.ModAlt) > 0
|
||||
mods := ev.Modifiers()
|
||||
alt := (mods & tcell.ModAlt) > 0
|
||||
shift := (mods & tcell.ModShift) > 0
|
||||
altShift := alt && shift
|
||||
keyfn := func(r rune) int {
|
||||
if alt {
|
||||
return CtrlAltA - 'a' + int(r)
|
||||
@@ -299,21 +300,45 @@ func (r *FullscreenRenderer) GetChar() Event {
|
||||
return Event{BSpace, 0, nil}
|
||||
|
||||
case tcell.KeyUp:
|
||||
if altShift {
|
||||
return Event{AltSUp, 0, nil}
|
||||
}
|
||||
if shift {
|
||||
return Event{SUp, 0, nil}
|
||||
}
|
||||
if alt {
|
||||
return Event{AltUp, 0, nil}
|
||||
}
|
||||
return Event{Up, 0, nil}
|
||||
case tcell.KeyDown:
|
||||
if altShift {
|
||||
return Event{AltSDown, 0, nil}
|
||||
}
|
||||
if shift {
|
||||
return Event{SDown, 0, nil}
|
||||
}
|
||||
if alt {
|
||||
return Event{AltDown, 0, nil}
|
||||
}
|
||||
return Event{Down, 0, nil}
|
||||
case tcell.KeyLeft:
|
||||
if altShift {
|
||||
return Event{AltSLeft, 0, nil}
|
||||
}
|
||||
if shift {
|
||||
return Event{SLeft, 0, nil}
|
||||
}
|
||||
if alt {
|
||||
return Event{AltLeft, 0, nil}
|
||||
}
|
||||
return Event{Left, 0, nil}
|
||||
case tcell.KeyRight:
|
||||
if altShift {
|
||||
return Event{AltSRight, 0, nil}
|
||||
}
|
||||
if shift {
|
||||
return Event{SRight, 0, nil}
|
||||
}
|
||||
if alt {
|
||||
return Event{AltRight, 0, nil}
|
||||
}
|
||||
@@ -418,7 +443,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 +489,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 +538,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 +557,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 +586,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 +606,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 +631,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)
|
||||
|
395
src/tui/tui.go
395
src/tui/tui.go
@@ -98,6 +98,11 @@ const (
|
||||
AltLeft
|
||||
AltRight
|
||||
|
||||
AltSUp
|
||||
AltSDown
|
||||
AltSLeft
|
||||
AltSRight
|
||||
|
||||
Alt0
|
||||
)
|
||||
|
||||
@@ -119,10 +124,23 @@ const (
|
||||
|
||||
type Color int32
|
||||
|
||||
func (c Color) IsDefault() bool {
|
||||
return c == colDefault
|
||||
}
|
||||
|
||||
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 +166,9 @@ const (
|
||||
)
|
||||
|
||||
type ColorPair struct {
|
||||
fg Color
|
||||
bg Color
|
||||
id int
|
||||
fg Color
|
||||
bg Color
|
||||
attr Attr
|
||||
}
|
||||
|
||||
func HexToColor(rrggbb string) Color {
|
||||
@@ -160,8 +178,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 +190,64 @@ func (p ColorPair) Bg() Color {
|
||||
return p.bg
|
||||
}
|
||||
|
||||
func (p ColorPair) Attr() Attr {
|
||||
return p.attr
|
||||
}
|
||||
|
||||
func (p ColorPair) HasBg() bool {
|
||||
return p.attr&Reverse == 0 && p.bg != colDefault ||
|
||||
p.attr&Reverse > 0 && p.fg != colDefault
|
||||
}
|
||||
|
||||
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 +273,11 @@ const (
|
||||
BorderRounded
|
||||
BorderSharp
|
||||
BorderHorizontal
|
||||
BorderVertical
|
||||
BorderTop
|
||||
BorderBottom
|
||||
BorderLeft
|
||||
BorderRight
|
||||
)
|
||||
|
||||
type BorderStyle struct {
|
||||
@@ -286,7 +350,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 +371,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 +400,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 +472,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 +567,32 @@ 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 {
|
||||
if fg.Color == colDefault && (fg.Attr&Reverse) > 0 {
|
||||
bg.Color = colDefault
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
@@ -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)
|
100
test/test_go.rb
100
test/test_go.rb
@@ -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,26 @@ 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
|
||||
|
||||
def test_change_prompt
|
||||
tmux.send_keys "#{FZF} --bind 'a:change-prompt(a> ),b:change-prompt:b> ' --query foo", :Enter
|
||||
tmux.until { |lines| assert_equal '> foo', lines[-1] }
|
||||
tmux.send_keys 'a'
|
||||
tmux.until { |lines| assert_equal 'a> foo', lines[-1] }
|
||||
tmux.send_keys 'b'
|
||||
tmux.until { |lines| assert_equal 'b> foo', lines[-1] }
|
||||
end
|
||||
|
||||
def test_preview_window_follow
|
||||
tmux.send_keys "#{FZF} --preview 'seq 1000 | nl' --preview-window down:noborder:follow", :Enter
|
||||
tmux.until { |lines| assert_equal '1000 1000', lines[-1].strip }
|
||||
end
|
||||
end
|
||||
|
||||
module TestShell
|
||||
@@ -1970,7 +1991,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 +2007,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 +2072,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 +2263,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 +2282,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> \"" "$@" < <(
|
||||
|
Reference in New Issue
Block a user