mirror of
https://github.com/junegunn/fzf.git
synced 2025-08-23 16:33:50 -07:00
Compare commits
77 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
34fe5ab143 | ||
|
1b08f43f82 | ||
|
b24a2e2fdc | ||
|
4c4c6e626e | ||
|
7310370a31 | ||
|
8ae94f0059 | ||
|
8fccf20892 | ||
|
5a874ae241 | ||
|
f4e1ed25f2 | ||
|
cbfbb49ab4 | ||
|
489b16efce | ||
|
b82c1693c0 | ||
|
019bfc4e35 | ||
|
dfda5c054a | ||
|
f657169616 | ||
|
4c06da8b70 | ||
|
9fe2393a00 | ||
|
e2e8d94b14 | ||
|
4f9a7f8c87 | ||
|
bb0502ff44 | ||
|
c256442245 | ||
|
1137404190 | ||
|
d57c6d0284 | ||
|
76bbf57b3d | ||
|
806a47a7cc | ||
|
29851c18aa | ||
|
dea950c2c8 | ||
|
a367dfb22e | ||
|
9fe1a7b373 | ||
|
8e2d21c548 | ||
|
bedf1cd357 | ||
|
13f180a70c | ||
|
6654239c94 | ||
|
1b61e5e9e9 | ||
|
43b3b907f8 | ||
|
fcd896508b | ||
|
f55c990e86 | ||
|
d110372f99 | ||
|
c862af09f2 | ||
|
1cfeec0ca3 | ||
|
a0649edc1e | ||
|
0e0bcb3e10 | ||
|
686528d627 | ||
|
3afa920151 | ||
|
32c493e994 | ||
|
1a76bdf891 | ||
|
af48b3df29 | ||
|
58ac1fb2fa | ||
|
e922704f72 | ||
|
c6115735c7 | ||
|
9ddf5c72be | ||
|
cc5640326b | ||
|
bf447d7703 | ||
|
cbb008c938 | ||
|
eaa0c52b45 | ||
|
82791f7efc | ||
|
8c533e34ea | ||
|
37708ad9cd | ||
|
090dee857f | ||
|
d779ff7e6d | ||
|
fd8858f8c9 | ||
|
b234647a63 | ||
|
6e93eefc82 | ||
|
38fca30125 | ||
|
012ee9ca85 | ||
|
151252e33a | ||
|
7136cfc68b | ||
|
408c04f25f | ||
|
7f8e0dbc40 | ||
|
0de7ab18f6 | ||
|
e9bc7331bd | ||
|
797dd7c449 | ||
|
f37ccaa64f | ||
|
ab3937ee5a | ||
|
00f4551a7b | ||
|
257ddd028d | ||
|
e0a22e76f8 |
36
.github/workflows/codeql-analysis.yml
vendored
Normal file
36
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning
|
||||||
|
name: CodeQL
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master, devel ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: ['go']
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v1
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v1
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v1
|
44
.github/workflows/linux.yml
vendored
Normal file
44
.github/workflows/linux.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
name: Test fzf on Linux
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master, devel ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
go: [1.14, 1.15]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go }}
|
||||||
|
|
||||||
|
- name: Setup Ruby
|
||||||
|
uses: ruby/setup-ruby@v1.62.0
|
||||||
|
with:
|
||||||
|
ruby-version: 3.0.0
|
||||||
|
|
||||||
|
- name: Install packages
|
||||||
|
run: sudo apt-get install --yes zsh fish tmux
|
||||||
|
|
||||||
|
- name: Install Ruby gems
|
||||||
|
run: sudo gem install --no-document minitest:5.14.2 rubocop:1.0.0 rubocop-minitest:0.10.1 rubocop-performance:1.8.1
|
||||||
|
|
||||||
|
- name: Rubocop
|
||||||
|
run: rubocop --require rubocop-minitest --require rubocop-performance
|
||||||
|
|
||||||
|
- name: Unit test
|
||||||
|
run: make test
|
||||||
|
|
||||||
|
- name: Integration test
|
||||||
|
run: make install && ./install --all && LC_ALL=C tmux new-session -d && ruby test/test_go.rb --verbose
|
44
.github/workflows/macos.yml
vendored
Normal file
44
.github/workflows/macos.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
name: Test fzf on macOS
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master, devel ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: macos-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
go: [1.14, 1.15]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go }}
|
||||||
|
|
||||||
|
- name: Setup Ruby
|
||||||
|
uses: ruby/setup-ruby@v1.62.0
|
||||||
|
with:
|
||||||
|
ruby-version: 3.0.0
|
||||||
|
|
||||||
|
- name: Install packages
|
||||||
|
run: HOMEBREW_NO_INSTALL_CLEANUP=1 brew install fish zsh tmux
|
||||||
|
|
||||||
|
- name: Install Ruby gems
|
||||||
|
run: gem install --no-document minitest:5.14.2 rubocop:1.0.0 rubocop-minitest:0.10.1 rubocop-performance:1.8.1
|
||||||
|
|
||||||
|
- name: Rubocop
|
||||||
|
run: rubocop --require rubocop-minitest --require rubocop-performance
|
||||||
|
|
||||||
|
- name: Unit test
|
||||||
|
run: make test
|
||||||
|
|
||||||
|
- name: Integration test
|
||||||
|
run: make install && ./install --all && LC_ALL=C tmux new-session -d && ruby test/test_go.rb --verbose
|
11
.gon.hcl
11
.gon.hcl
@@ -1,11 +0,0 @@
|
|||||||
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"
|
|
||||||
}
|
|
@@ -12,10 +12,25 @@ builds:
|
|||||||
- darwin
|
- darwin
|
||||||
goarch:
|
goarch:
|
||||||
- amd64
|
- amd64
|
||||||
|
- arm64
|
||||||
ldflags:
|
ldflags:
|
||||||
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
||||||
hooks:
|
hooks:
|
||||||
post: gon .gon.hcl
|
post: |-
|
||||||
|
sh -c '
|
||||||
|
cat > /tmp/fzf-gon.hcl << EOF
|
||||||
|
source = ["./dist/fzf-macos_darwin_{{ .Arch }}/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"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
gon /tmp/fzf-gon.hcl
|
||||||
|
'
|
||||||
|
|
||||||
- goos:
|
- goos:
|
||||||
- linux
|
- linux
|
||||||
|
28
.travis.yml
28
.travis.yml
@@ -1,28 +0,0 @@
|
|||||||
language: go
|
|
||||||
go:
|
|
||||||
- "1.14"
|
|
||||||
env: GO111MODULE=on
|
|
||||||
os:
|
|
||||||
- linux
|
|
||||||
- osx
|
|
||||||
dist: bionic
|
|
||||||
osx_image: xcode12.2
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
packages:
|
|
||||||
- fish
|
|
||||||
- zsh
|
|
||||||
sources:
|
|
||||||
sourceline: ppa:fish-shell/release-3
|
|
||||||
homebrew:
|
|
||||||
packages:
|
|
||||||
- fish
|
|
||||||
- tmux
|
|
||||||
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
|
|
||||||
# printf %q $'\355\205\214\354\212\244\355\212\270' on macOS. Bash on
|
|
||||||
# macOS is built without HANDLE_MULTIBYTE?
|
|
||||||
- make install && ./install --all && LC_ALL=C tmux new-session -d && ruby test/test_go.rb --verbose
|
|
||||||
- rubocop --require rubocop-minitest --require rubocop-performance
|
|
2
BUILD.md
2
BUILD.md
@@ -6,7 +6,7 @@ Build instructions
|
|||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- Go 1.11 or above
|
- Go 1.13 or above
|
||||||
|
|
||||||
### Using Makefile
|
### Using Makefile
|
||||||
|
|
||||||
|
72
CHANGELOG.md
72
CHANGELOG.md
@@ -1,6 +1,78 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.26.0
|
||||||
|
------
|
||||||
|
- Added support for fixed header in preview window
|
||||||
|
```sh
|
||||||
|
# Display top 3 lines as the fixed header
|
||||||
|
fzf --preview 'bat --style=header,grid --color=always {}' --preview-window '~3'
|
||||||
|
```
|
||||||
|
- More advanced preview offset expression to better support the fixed header
|
||||||
|
```sh
|
||||||
|
# Preview with bat, matching line in the middle of the window below
|
||||||
|
# the fixed header of the top 3 lines
|
||||||
|
#
|
||||||
|
# ~3 Top 3 lines as the fixed header
|
||||||
|
# +{2} Base scroll offset extracted from the second field
|
||||||
|
# +3 Extra offset to compensate for the 3-line header
|
||||||
|
# /2 Put in the middle of the preview area
|
||||||
|
#
|
||||||
|
git grep --line-number '' |
|
||||||
|
fzf --delimiter : \
|
||||||
|
--preview 'bat --style=full --color=always --highlight-line {2} {1}' \
|
||||||
|
--preview-window '~3:+{2}+3/2'
|
||||||
|
```
|
||||||
|
- Added `select` and `deselect` action for unconditionally selecting or
|
||||||
|
deselecting a single item in `--multi` mode. Complements `toggle` action.
|
||||||
|
- Sigificant performance improvement in ANSI code processing
|
||||||
|
- Bug fixes and improvements
|
||||||
|
- Built with Go 1.16
|
||||||
|
|
||||||
|
0.25.1
|
||||||
|
------
|
||||||
|
- Added `close` action
|
||||||
|
- Close preview window if open, abort fzf otherwise
|
||||||
|
- Bug fixes and improvements
|
||||||
|
|
||||||
|
0.25.0
|
||||||
|
------
|
||||||
|
- Text attributes set in `--color` are not reset when fzf sees another
|
||||||
|
`--color` option for the same element. This allows you to put custom text
|
||||||
|
attributes in your `$FZF_DEFAULT_OPTS` and still have those attributes
|
||||||
|
even when you override the colors.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Default colors and attributes
|
||||||
|
fzf
|
||||||
|
|
||||||
|
# Apply custom text attributes
|
||||||
|
export FZF_DEFAULT_OPTS='--color fg+:italic,hl:-1:underline,hl+:-1:reverse:underline'
|
||||||
|
|
||||||
|
fzf
|
||||||
|
|
||||||
|
# Different colors but you still have the attributes
|
||||||
|
fzf --color hl:176,hl+:177
|
||||||
|
|
||||||
|
# Write "regular" if you want to clear the attributes
|
||||||
|
fzf --color hl:176:regular,hl+:177:regular
|
||||||
|
```
|
||||||
|
- Renamed `--phony` to `--disabled`
|
||||||
|
- You can dynamically enable and disable the search functionality using the
|
||||||
|
new `enable-search`, `disable-search`, and `toggle-search` actions
|
||||||
|
- You can assign a different color to the query string for when search is disabled
|
||||||
|
```sh
|
||||||
|
fzf --color query:#ffffff,disabled:#999999 --bind space:toggle-search
|
||||||
|
```
|
||||||
|
- Added `last` action to move the cursor to the last match
|
||||||
|
- The opposite action `top` is renamed to `first`, but `top` is still
|
||||||
|
recognized as a synonym for backward compatibility
|
||||||
|
- Added `preview-top` and `preview-bottom` actions
|
||||||
|
- Extended support for alt key chords: alt with any case-sensitive single character
|
||||||
|
```sh
|
||||||
|
fzf --bind alt-,:first,alt-.:last
|
||||||
|
```
|
||||||
|
|
||||||
0.24.4
|
0.24.4
|
||||||
------
|
------
|
||||||
- Added `--preview-window` option `follow`
|
- Added `--preview-window` option `follow`
|
||||||
|
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2020 Junegunn Choi
|
Copyright (c) 2013-2021 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
17
Makefile
17
Makefile
@@ -1,3 +1,4 @@
|
|||||||
|
SHELL := bash
|
||||||
GO ?= go
|
GO ?= go
|
||||||
GOOS ?= $(word 1, $(subst /, " ", $(word 4, $(shell go version))))
|
GOOS ?= $(word 1, $(subst /, " ", $(word 4, $(shell go version))))
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ $(error Not on git repository; cannot determine $$FZF_REVISION)
|
|||||||
endif
|
endif
|
||||||
BUILD_FLAGS := -a -ldflags "-s -w -X main.version=$(VERSION) -X main.revision=$(REVISION)" -tags "$(TAGS)"
|
BUILD_FLAGS := -a -ldflags "-s -w -X main.version=$(VERSION) -X main.revision=$(REVISION)" -tags "$(TAGS)"
|
||||||
|
|
||||||
|
BINARY32 := fzf-$(GOOS)_386
|
||||||
BINARY64 := fzf-$(GOOS)_amd64
|
BINARY64 := fzf-$(GOOS)_amd64
|
||||||
BINARYARM5 := fzf-$(GOOS)_arm5
|
BINARYARM5 := fzf-$(GOOS)_arm5
|
||||||
BINARYARM6 := fzf-$(GOOS)_arm6
|
BINARYARM6 := fzf-$(GOOS)_arm6
|
||||||
@@ -39,6 +41,10 @@ ifeq ($(UNAME_M),x86_64)
|
|||||||
BINARY := $(BINARY64)
|
BINARY := $(BINARY64)
|
||||||
else ifeq ($(UNAME_M),amd64)
|
else ifeq ($(UNAME_M),amd64)
|
||||||
BINARY := $(BINARY64)
|
BINARY := $(BINARY64)
|
||||||
|
else ifeq ($(UNAME_M),i686)
|
||||||
|
BINARY := $(BINARY32)
|
||||||
|
else ifeq ($(UNAME_M),i386)
|
||||||
|
BINARY := $(BINARY32)
|
||||||
else ifeq ($(UNAME_M),armv5l)
|
else ifeq ($(UNAME_M),armv5l)
|
||||||
BINARY := $(BINARYARM5)
|
BINARY := $(BINARYARM5)
|
||||||
else ifeq ($(UNAME_M),armv6l)
|
else ifeq ($(UNAME_M),armv6l)
|
||||||
@@ -47,6 +53,8 @@ else ifeq ($(UNAME_M),armv7l)
|
|||||||
BINARY := $(BINARYARM7)
|
BINARY := $(BINARYARM7)
|
||||||
else ifeq ($(UNAME_M),armv8l)
|
else ifeq ($(UNAME_M),armv8l)
|
||||||
BINARY := $(BINARYARM8)
|
BINARY := $(BINARYARM8)
|
||||||
|
else ifeq ($(UNAME_M),arm64)
|
||||||
|
BINARY := $(BINARYARM8)
|
||||||
else ifeq ($(UNAME_M),aarch64)
|
else ifeq ($(UNAME_M),aarch64)
|
||||||
BINARY := $(BINARYARM8)
|
BINARY := $(BINARYARM8)
|
||||||
else ifeq ($(UNAME_M),ppc64le)
|
else ifeq ($(UNAME_M),ppc64le)
|
||||||
@@ -58,12 +66,16 @@ endif
|
|||||||
all: target/$(BINARY)
|
all: target/$(BINARY)
|
||||||
|
|
||||||
test: $(SOURCES)
|
test: $(SOURCES)
|
||||||
|
[ -z "$$(gofmt -s -d src)" ] || (gofmt -s -d src; exit 1)
|
||||||
SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \
|
SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \
|
||||||
github.com/junegunn/fzf/src \
|
github.com/junegunn/fzf/src \
|
||||||
github.com/junegunn/fzf/src/algo \
|
github.com/junegunn/fzf/src/algo \
|
||||||
github.com/junegunn/fzf/src/tui \
|
github.com/junegunn/fzf/src/tui \
|
||||||
github.com/junegunn/fzf/src/util
|
github.com/junegunn/fzf/src/util
|
||||||
|
|
||||||
|
bench:
|
||||||
|
cd src && SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" -run=Bench -bench=. -benchmem
|
||||||
|
|
||||||
install: bin/fzf
|
install: bin/fzf
|
||||||
|
|
||||||
build:
|
build:
|
||||||
@@ -107,6 +119,9 @@ endif
|
|||||||
clean:
|
clean:
|
||||||
$(RM) -r dist target
|
$(RM) -r dist target
|
||||||
|
|
||||||
|
target/$(BINARY32): $(SOURCES)
|
||||||
|
GOARCH=386 $(GO) build $(BUILD_FLAGS) -o $@
|
||||||
|
|
||||||
target/$(BINARY64): $(SOURCES)
|
target/$(BINARY64): $(SOURCES)
|
||||||
GOARCH=amd64 $(GO) build $(BUILD_FLAGS) -o $@
|
GOARCH=amd64 $(GO) build $(BUILD_FLAGS) -o $@
|
||||||
|
|
||||||
@@ -141,4 +156,4 @@ update:
|
|||||||
$(GO) get -u
|
$(GO) get -u
|
||||||
$(GO) mod tidy
|
$(GO) mod tidy
|
||||||
|
|
||||||
.PHONY: all build release test install clean docker docker-test update
|
.PHONY: all build release test bench install clean docker docker-test update
|
||||||
|
@@ -171,19 +171,23 @@ list:
|
|||||||
|
|
||||||
- `element` is an fzf element to apply a color to:
|
- `element` is an fzf element to apply a color to:
|
||||||
|
|
||||||
| Element | Description |
|
| Element | Description |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `fg` / `bg` / `hl` | Item (foreground / background / highlight) |
|
| `fg` / `bg` / `hl` | Item (foreground / background / highlight) |
|
||||||
| `fg+` / `bg+` / `hl+` | Current item (foreground / background / highlight) |
|
| `fg+` / `bg+` / `hl+` | Current item (foreground / background / highlight) |
|
||||||
| `hl` / `hl+` | Highlighted substrings (normal / current) |
|
| `preview-fg` / `preview-bg` | Preview window text and background |
|
||||||
| `gutter` | Background of the gutter on the left |
|
| `hl` / `hl+` | Highlighted substrings (normal / current) |
|
||||||
| `pointer` | Pointer to the current line (`>`) |
|
| `gutter` | Background of the gutter on the left |
|
||||||
| `marker` | Multi-select marker (`>`) |
|
| `pointer` | Pointer to the current line (`>`) |
|
||||||
| `border` | Border around the window (`--border` and `--preview`) |
|
| `marker` | Multi-select marker (`>`) |
|
||||||
| `header` | Header (`--header` or `--header-lines`) |
|
| `border` | Border around the window (`--border` and `--preview`) |
|
||||||
| `info` | Info line (match counters) |
|
| `header` | Header (`--header` or `--header-lines`) |
|
||||||
| `spinner` | Streaming input indicator |
|
| `info` | Info line (match counters) |
|
||||||
| `prompt` | Prompt before query (`> `) |
|
| `spinner` | Streaming input indicator |
|
||||||
|
| `query` | Query string |
|
||||||
|
| `disabled` | Query string when search is disabled |
|
||||||
|
| `prompt` | Prompt before query (`> `) |
|
||||||
|
| `pointer` | Pointer to the current line (`>`) |
|
||||||
|
|
||||||
- `component` specifies the component (`fg` / `bg`) from which to extract the
|
- `component` specifies the component (`fg` / `bg`) from which to extract the
|
||||||
color when considering each of the following highlight groups
|
color when considering each of the following highlight groups
|
||||||
@@ -358,7 +362,7 @@ Our `:LS` command will be much more useful if we can pass a directory argument
|
|||||||
to it, so that something like `:LS /tmp` is possible.
|
to it, so that something like `:LS /tmp` is possible.
|
||||||
|
|
||||||
```vim
|
```vim
|
||||||
command! -bang -complete=dir -nargs=* LS
|
command! -bang -complete=dir -nargs=? LS
|
||||||
\ call fzf#run(fzf#wrap({'source': 'ls', 'dir': <q-args>}, <bang>0))
|
\ call fzf#run(fzf#wrap({'source': 'ls', 'dir': <q-args>}, <bang>0))
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -368,7 +372,7 @@ a unique name to our command and pass it as the first argument to `fzf#wrap`.
|
|||||||
```vim
|
```vim
|
||||||
" The query history for this command will be stored as 'ls' inside g:fzf_history_dir.
|
" The query history for this command will be stored as 'ls' inside g:fzf_history_dir.
|
||||||
" The name is ignored if g:fzf_history_dir is not defined.
|
" The name is ignored if g:fzf_history_dir is not defined.
|
||||||
command! -bang -complete=dir -nargs=* LS
|
command! -bang -complete=dir -nargs=? LS
|
||||||
\ call fzf#run(fzf#wrap('ls', {'source': 'ls', 'dir': <q-args>}, <bang>0))
|
\ call fzf#run(fzf#wrap('ls', {'source': 'ls', 'dir': <q-args>}, <bang>0))
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -406,7 +410,6 @@ The latest versions of Vim and Neovim include builtin terminal emulator
|
|||||||
" Optional:
|
" Optional:
|
||||||
" - xoffset [float default 0.5 range [0 ~ 1]]
|
" - xoffset [float default 0.5 range [0 ~ 1]]
|
||||||
" - yoffset [float default 0.5 range [0 ~ 1]]
|
" - yoffset [float default 0.5 range [0 ~ 1]]
|
||||||
" - highlight [string default 'Comment']: Highlight group for border
|
|
||||||
" - border [string default 'rounded']: Border style
|
" - border [string default 'rounded']: Border style
|
||||||
" - 'rounded' / 'sharp' / 'horizontal' / 'vertical' / 'top' / 'bottom' / 'left' / 'right'
|
" - 'rounded' / 'sharp' / 'horizontal' / 'vertical' / 'top' / 'bottom' / 'left' / 'right'
|
||||||
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
||||||
@@ -446,4 +449,4 @@ endif
|
|||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2020 Junegunn Choi
|
Copyright (c) 2013-2021 Junegunn Choi
|
||||||
|
55
README.md
55
README.md
@@ -1,4 +1,4 @@
|
|||||||
<img src="https://raw.githubusercontent.com/junegunn/i/master/fzf.png" height="170" alt="fzf - a command-line fuzzy finder"> [](https://travis-ci.org/junegunn/fzf)
|
<img src="https://raw.githubusercontent.com/junegunn/i/master/fzf.png" height="170" alt="fzf - a command-line fuzzy finder"> [](https://github.com/junegunn/fzf/actions)
|
||||||
===
|
===
|
||||||
|
|
||||||
fzf is a general-purpose command-line fuzzy finder.
|
fzf is a general-purpose command-line fuzzy finder.
|
||||||
@@ -17,7 +17,7 @@ Pros
|
|||||||
- The most comprehensive feature set
|
- The most comprehensive feature set
|
||||||
- Flexible layout
|
- Flexible layout
|
||||||
- Batteries included
|
- Batteries included
|
||||||
- Vim/Neovim plugin, key bindings and fuzzy auto-completion
|
- Vim/Neovim plugin, key bindings, and fuzzy auto-completion
|
||||||
|
|
||||||
Table of Contents
|
Table of Contents
|
||||||
-----------------
|
-----------------
|
||||||
@@ -25,7 +25,7 @@ Table of Contents
|
|||||||
<!-- vim-markdown-toc GFM -->
|
<!-- vim-markdown-toc GFM -->
|
||||||
|
|
||||||
* [Installation](#installation)
|
* [Installation](#installation)
|
||||||
* [Using Homebrew or Linuxbrew](#using-homebrew-or-linuxbrew)
|
* [Using Homebrew](#using-homebrew)
|
||||||
* [Using git](#using-git)
|
* [Using git](#using-git)
|
||||||
* [Using Linux package managers](#using-linux-package-managers)
|
* [Using Linux package managers](#using-linux-package-managers)
|
||||||
* [Windows](#windows)
|
* [Windows](#windows)
|
||||||
@@ -84,9 +84,9 @@ stuff.
|
|||||||
|
|
||||||
[bin]: https://github.com/junegunn/fzf/releases
|
[bin]: https://github.com/junegunn/fzf/releases
|
||||||
|
|
||||||
### Using Homebrew or Linuxbrew
|
### Using Homebrew
|
||||||
|
|
||||||
You can use [Homebrew](http://brew.sh/) or [Linuxbrew](http://linuxbrew.sh/)
|
You can use [Homebrew](http://brew.sh/) (on macOS or Linux)
|
||||||
to install fzf.
|
to install fzf.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -166,12 +166,12 @@ For more installation options, see [README-VIM.md](README-VIM.md).
|
|||||||
Upgrading fzf
|
Upgrading fzf
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
fzf is being actively developed and you might want to upgrade it once in a
|
fzf is being actively developed, and you might want to upgrade it once in a
|
||||||
while. Please follow the instruction below depending on the installation
|
while. Please follow the instruction below depending on the installation
|
||||||
method used.
|
method used.
|
||||||
|
|
||||||
- git: `cd ~/.fzf && git pull && ./install`
|
- git: `cd ~/.fzf && git pull && ./install`
|
||||||
- brew: `brew update; brew reinstall fzf`
|
- brew: `brew update; brew upgrade fzf`
|
||||||
- macports: `sudo port upgrade fzf`
|
- macports: `sudo port upgrade fzf`
|
||||||
- chocolatey: `choco upgrade fzf`
|
- chocolatey: `choco upgrade fzf`
|
||||||
- vim-plug: `:PlugUpdate fzf`
|
- vim-plug: `:PlugUpdate fzf`
|
||||||
@@ -217,7 +217,7 @@ cursor with `--height` option.
|
|||||||
vim $(fzf --height 40%)
|
vim $(fzf --height 40%)
|
||||||
```
|
```
|
||||||
|
|
||||||
Also check out `--reverse` and `--layout` options if you prefer
|
Also, check out `--reverse` and `--layout` options if you prefer
|
||||||
"top-down" layout instead of the default "bottom-up" layout.
|
"top-down" layout instead of the default "bottom-up" layout.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -264,6 +264,13 @@ or `py`.
|
|||||||
- `FZF_DEFAULT_COMMAND`
|
- `FZF_DEFAULT_COMMAND`
|
||||||
- Default command to use when input is tty
|
- Default command to use when input is tty
|
||||||
- e.g. `export FZF_DEFAULT_COMMAND='fd --type f'`
|
- e.g. `export FZF_DEFAULT_COMMAND='fd --type f'`
|
||||||
|
- > :warning: This variable is not used by shell extensions due to the
|
||||||
|
> slight difference in requirements.
|
||||||
|
>
|
||||||
|
> (e.g. `CTRL-T` runs `$FZF_CTRL_T_COMMAND` instead, `vim **<tab>` runs
|
||||||
|
> `_fzf_compgen_path()`, and `cd **<tab>` runs `_fzf_compgen_dir()`)
|
||||||
|
>
|
||||||
|
> The available options are described later in this document.
|
||||||
- `FZF_DEFAULT_OPTS`
|
- `FZF_DEFAULT_OPTS`
|
||||||
- Default options
|
- Default options
|
||||||
- e.g. `export FZF_DEFAULT_OPTS="--layout=reverse --inline-info"`
|
- e.g. `export FZF_DEFAULT_OPTS="--layout=reverse --inline-info"`
|
||||||
@@ -331,7 +338,7 @@ fish.
|
|||||||
- Set `FZF_ALT_C_COMMAND` to override the default command
|
- Set `FZF_ALT_C_COMMAND` to override the default command
|
||||||
- Set `FZF_ALT_C_OPTS` to pass additional options
|
- Set `FZF_ALT_C_OPTS` to pass additional options
|
||||||
|
|
||||||
If you're on a tmux session, you can start fzf in a tmux split pane or in
|
If you're on a tmux session, you can start fzf in a tmux split-pane or in
|
||||||
a tmux popup window by setting `FZF_TMUX_OPTS` (e.g. `-d 40%`).
|
a tmux popup window by setting `FZF_TMUX_OPTS` (e.g. `-d 40%`).
|
||||||
See `fzf-tmux --help` for available options.
|
See `fzf-tmux --help` for available options.
|
||||||
|
|
||||||
@@ -343,12 +350,12 @@ Fuzzy completion for bash and zsh
|
|||||||
#### Files and directories
|
#### Files and directories
|
||||||
|
|
||||||
Fuzzy completion for files and directories can be triggered if the word before
|
Fuzzy completion for files and directories can be triggered if the word before
|
||||||
the cursor ends with the trigger sequence which is by default `**`.
|
the cursor ends with the trigger sequence, which is by default `**`.
|
||||||
|
|
||||||
- `COMMAND [DIRECTORY/][FUZZY_PATTERN]**<TAB>`
|
- `COMMAND [DIRECTORY/][FUZZY_PATTERN]**<TAB>`
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Files under current directory
|
# Files under the current directory
|
||||||
# - You can select multiple items with TAB key
|
# - You can select multiple items with TAB key
|
||||||
vim **<TAB>
|
vim **<TAB>
|
||||||
|
|
||||||
@@ -372,7 +379,7 @@ cd ~/github/fzf**<TAB>
|
|||||||
#### Process IDs
|
#### Process IDs
|
||||||
|
|
||||||
Fuzzy completion for PIDs is provided for kill command. In this case,
|
Fuzzy completion for PIDs is provided for kill command. In this case,
|
||||||
there is no trigger sequence, just press tab key after kill command.
|
there is no trigger sequence; just press the tab key after the kill command.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Can select multiple processes with <TAB> or <Shift-TAB> keys
|
# Can select multiple processes with <TAB> or <Shift-TAB> keys
|
||||||
@@ -381,7 +388,7 @@ kill -9 <TAB>
|
|||||||
|
|
||||||
#### Host names
|
#### Host names
|
||||||
|
|
||||||
For ssh and telnet commands, fuzzy completion for host names is provided. The
|
For ssh and telnet commands, fuzzy completion for hostnames is provided. The
|
||||||
names are extracted from /etc/hosts and ~/.ssh/config.
|
names are extracted from /etc/hosts and ~/.ssh/config.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -404,7 +411,7 @@ unalias **<TAB>
|
|||||||
export FZF_COMPLETION_TRIGGER='~~'
|
export FZF_COMPLETION_TRIGGER='~~'
|
||||||
|
|
||||||
# Options to fzf command
|
# Options to fzf command
|
||||||
export FZF_COMPLETION_OPTS='+c -x'
|
export FZF_COMPLETION_OPTS='--border --info=inline'
|
||||||
|
|
||||||
# Use fd (https://github.com/sharkdp/fd) instead of the default find
|
# Use fd (https://github.com/sharkdp/fd) instead of the default find
|
||||||
# command for listing path candidates.
|
# command for listing path candidates.
|
||||||
@@ -469,11 +476,11 @@ _fzf_complete_doge() {
|
|||||||
|
|
||||||
- The arguments before `--` are the options to fzf.
|
- The arguments before `--` are the options to fzf.
|
||||||
- After `--`, simply pass the original completion arguments unchanged (`"$@"`).
|
- After `--`, simply pass the original completion arguments unchanged (`"$@"`).
|
||||||
- Then write a set of commands that generates the completion candidates and
|
- Then, write a set of commands that generates the completion candidates and
|
||||||
feed its output to the function using process substitution (`< <(...)`).
|
feed its output to the function using process substitution (`< <(...)`).
|
||||||
|
|
||||||
zsh will automatically pick up the function using the naming convention but in
|
zsh will automatically pick up the function using the naming convention but in
|
||||||
bash you have to manually associate the function with the command using
|
bash you have to manually associate the function with the command using the
|
||||||
`complete` command.
|
`complete` command.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -509,12 +516,12 @@ Advanced topics
|
|||||||
|
|
||||||
fzf is fast and is [getting even faster][perf]. Performance should not be
|
fzf is fast and is [getting even faster][perf]. Performance should not be
|
||||||
a problem in most use cases. However, you might want to be aware of the
|
a problem in most use cases. However, you might want to be aware of the
|
||||||
options that affect the performance.
|
options that affect performance.
|
||||||
|
|
||||||
- `--ansi` tells fzf to extract and parse ANSI color codes in the input and it
|
- `--ansi` tells fzf to extract and parse ANSI color codes in the input, and it
|
||||||
makes the initial scanning slower. So it's not recommended that you add it
|
makes the initial scanning slower. So it's not recommended that you add it
|
||||||
to your `$FZF_DEFAULT_OPTS`.
|
to your `$FZF_DEFAULT_OPTS`.
|
||||||
- `--nth` makes fzf slower as fzf has to tokenize each line.
|
- `--nth` makes fzf slower because it has to tokenize each line.
|
||||||
- `--with-nth` makes fzf slower as fzf has to tokenize and reassemble each
|
- `--with-nth` makes fzf slower as fzf has to tokenize and reassemble each
|
||||||
line.
|
line.
|
||||||
- If you absolutely need better performance, you can consider using
|
- If you absolutely need better performance, you can consider using
|
||||||
@@ -563,7 +570,7 @@ FZF_DEFAULT_COMMAND='find . -type f' \
|
|||||||
#### 3. Interactive ripgrep integration
|
#### 3. Interactive ripgrep integration
|
||||||
|
|
||||||
The following example uses fzf as the selector interface for ripgrep. We bound
|
The following example uses fzf as the selector interface for ripgrep. We bound
|
||||||
`reload` action to `change` event, so every time you type on fzf, ripgrep
|
`reload` action to `change` event, so every time you type on fzf, the ripgrep
|
||||||
process will restart with the updated query string denoted by the placeholder
|
process will restart with the updated query string denoted by the placeholder
|
||||||
expression `{q}`. Also, note that we used `--phony` option so that fzf doesn't
|
expression `{q}`. Also, note that we used `--phony` option so that fzf doesn't
|
||||||
perform any secondary filtering.
|
perform any secondary filtering.
|
||||||
@@ -573,7 +580,7 @@ INITIAL_QUERY=""
|
|||||||
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
||||||
FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY'" \
|
FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY'" \
|
||||||
fzf --bind "change:reload:$RG_PREFIX {q} || true" \
|
fzf --bind "change:reload:$RG_PREFIX {q} || true" \
|
||||||
--ansi --phony --query "$INITIAL_QUERY" \
|
--ansi --disabled --query "$INITIAL_QUERY" \
|
||||||
--height=50% --layout=reverse
|
--height=50% --layout=reverse
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -589,7 +596,7 @@ Your `$SHELL` is used to execute the command with `$SHELL -c COMMAND`.
|
|||||||
The window can be scrolled using the mouse or custom key bindings.
|
The window can be scrolled using the mouse or custom key bindings.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# {} is replaced to the single-quoted string of the focused line
|
# {} is replaced with the single-quoted string of the focused line
|
||||||
fzf --preview 'cat {}'
|
fzf --preview 'cat {}'
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -661,7 +668,7 @@ fzf
|
|||||||
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
|
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want the command to follow symbolic links, and don't want it to exclude
|
If you want the command to follow symbolic links and don't want it to exclude
|
||||||
hidden files, use the following command:
|
hidden files, use the following command:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -698,4 +705,4 @@ https://github.com/junegunn/fzf/wiki/Related-projects
|
|||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2020 Junegunn Choi
|
Copyright (c) 2013-2021 Junegunn Choi
|
||||||
|
15
bin/fzf-tmux
15
bin/fzf-tmux
@@ -135,8 +135,8 @@ if [[ -z "$TMUX" ]]; then
|
|||||||
exit $?
|
exit $?
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --height option is not allowed
|
# --height option is not allowed. CTRL-Z is also disabled.
|
||||||
args=("${args[@]}" "--no-height")
|
args=("${args[@]}" "--no-height" "--bind=ctrl-z:ignore")
|
||||||
|
|
||||||
# Handle zoomed tmux pane without popup options by moving it to a temp window
|
# Handle zoomed tmux pane without popup options by moving it to a temp window
|
||||||
if [[ ! "$opt" =~ "-K -E" ]] && tmux list-panes -F '#F' | grep -q Z; then
|
if [[ ! "$opt" =~ "-K -E" ]] && tmux list-panes -F '#F' | grep -q Z; then
|
||||||
@@ -204,7 +204,16 @@ if [[ "$opt" =~ "-K -E" ]]; then
|
|||||||
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; out=\$? $close; exit \$out" >> $argsf
|
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; out=\$? $close; exit \$out" >> $argsf
|
||||||
cat <&0 > $fifo1 &
|
cat <&0 > $fifo1 &
|
||||||
fi
|
fi
|
||||||
tmux popup -d "$PWD" "${tmux_args[@]}" $opt -R "bash $argsf" > /dev/null 2>&1
|
|
||||||
|
# tmux dropped the support for `-K`, `-R` to popup command
|
||||||
|
# TODO: We can remove this once tmux 3.2 is released
|
||||||
|
if [[ ! "$(tmux popup --help 2>&1)" =~ '-R shell-command' ]]; then
|
||||||
|
opt="${opt/-K/}"
|
||||||
|
else
|
||||||
|
opt="${opt} -R"
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmux popup -d "$PWD" "${tmux_args[@]}" $opt "bash $argsf" > /dev/null 2>&1
|
||||||
exit $?
|
exit $?
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
72
doc/fzf.txt
72
doc/fzf.txt
@@ -1,22 +1,22 @@
|
|||||||
fzf.txt fzf Last change: October 18 2020
|
fzf.txt fzf Last change: January 3 2021
|
||||||
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
|
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
|
||||||
==============================================================================
|
==============================================================================
|
||||||
|
|
||||||
FZF Vim integration
|
FZF Vim integration |fzf-vim-integration|
|
||||||
Installation
|
Installation |fzf-installation|
|
||||||
Summary
|
Summary |fzf-summary|
|
||||||
:FZF[!]
|
:FZF[!] |:FZF|
|
||||||
Configuration
|
Configuration |fzf-configuration|
|
||||||
Examples
|
Examples |fzf-examples|
|
||||||
Explanation of g:fzf_colors
|
Explanation of g:fzf_colors |fzf-explanation-of-gfzfcolors|
|
||||||
fzf#run
|
fzf#run |fzf#run|
|
||||||
fzf#wrap
|
fzf#wrap |fzf#wrap|
|
||||||
Global options supported by fzf#wrap
|
Global options supported by fzf#wrap |fzf-global-options-supported-by-fzf#wrap|
|
||||||
Tips
|
Tips |fzf-tips|
|
||||||
fzf inside terminal buffer
|
fzf inside terminal buffer |fzf-inside-terminal-buffer|
|
||||||
Starting fzf in a popup window
|
Starting fzf in a popup window |fzf-starting-fzf-in-a-popup-window|
|
||||||
Hide statusline
|
Hide statusline |fzf-hide-statusline|
|
||||||
License
|
License |fzf-license|
|
||||||
|
|
||||||
FZF VIM INTEGRATION *fzf-vim-integration*
|
FZF VIM INTEGRATION *fzf-vim-integration*
|
||||||
==============================================================================
|
==============================================================================
|
||||||
@@ -200,21 +200,25 @@ list:
|
|||||||
<
|
<
|
||||||
- `element` is an fzf element to apply a color to:
|
- `element` is an fzf element to apply a color to:
|
||||||
|
|
||||||
----------------------+------------------------------------------------------
|
----------------------------+------------------------------------------------------
|
||||||
Element | Description ~
|
Element | Description ~
|
||||||
----------------------+------------------------------------------------------
|
----------------------------+------------------------------------------------------
|
||||||
`fg` / `bg` / `hl` | Item (foreground / background / highlight)
|
`fg` / `bg` / `hl` | Item (foreground / background / highlight)
|
||||||
`fg+` / `bg+` / `hl+` | Current item (foreground / background / highlight)
|
`fg+` / `bg+` / `hl+` | Current item (foreground / background / highlight)
|
||||||
`hl` / `hl+` | Highlighted substrings (normal / current)
|
`preview-fg` / `preview-bg` | Preview window text and background
|
||||||
`gutter` | Background of the gutter on the left
|
`hl` / `hl+` | Highlighted substrings (normal / current)
|
||||||
`pointer` | Pointer to the current line ( `>` )
|
`gutter` | Background of the gutter on the left
|
||||||
`marker` | Multi-select marker ( `>` )
|
`pointer` | Pointer to the current line ( `>` )
|
||||||
`border` | Border around the window ( `--border` and `--preview` )
|
`marker` | Multi-select marker ( `>` )
|
||||||
`header` | Header ( `--header` or `--header-lines` )
|
`border` | Border around the window ( `--border` and `--preview` )
|
||||||
`info` | Info line (match counters)
|
`header` | Header ( `--header` or `--header-lines` )
|
||||||
`spinner` | Streaming input indicator
|
`info` | Info line (match counters)
|
||||||
`prompt` | Prompt before query ( `> ` )
|
`spinner` | Streaming input indicator
|
||||||
----------------------+------------------------------------------------------
|
`query` | Query string
|
||||||
|
`disabled` | Query string when search is disabled
|
||||||
|
`prompt` | Prompt before query ( `> ` )
|
||||||
|
`pointer` | Pointer to the current line ( `>` )
|
||||||
|
----------------------------+------------------------------------------------------
|
||||||
- `component` specifies the component (`fg` / `bg`) from which to extract the
|
- `component` specifies the component (`fg` / `bg`) from which to extract the
|
||||||
color when considering each of the following highlight groups
|
color when considering each of the following highlight groups
|
||||||
- `group1 [, group2, ...]` is a list of highlight groups that are searched (in
|
- `group1 [, group2, ...]` is a list of highlight groups that are searched (in
|
||||||
@@ -314,9 +318,8 @@ following options are allowed:
|
|||||||
- Optional:
|
- Optional:
|
||||||
- `yoffset` [float default 0.5 range [0 ~ 1]]
|
- `yoffset` [float default 0.5 range [0 ~ 1]]
|
||||||
- `xoffset` [float default 0.5 range [0 ~ 1]]
|
- `xoffset` [float default 0.5 range [0 ~ 1]]
|
||||||
- `highlight` [string default `'Comment'`]: Highlight group for border
|
|
||||||
- `border` [string default `rounded`]: Border style
|
- `border` [string default `rounded`]: Border style
|
||||||
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right`
|
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right` / `no[ne]`
|
||||||
|
|
||||||
|
|
||||||
FZF#WRAP
|
FZF#WRAP
|
||||||
@@ -420,7 +423,6 @@ Starting fzf in a popup window~
|
|||||||
" Optional:
|
" Optional:
|
||||||
" - xoffset [float default 0.5 range [0 ~ 1]]
|
" - xoffset [float default 0.5 range [0 ~ 1]]
|
||||||
" - yoffset [float default 0.5 range [0 ~ 1]]
|
" - yoffset [float default 0.5 range [0 ~ 1]]
|
||||||
" - highlight [string default 'Comment']: Highlight group for border
|
|
||||||
" - border [string default 'rounded']: Border style
|
" - border [string default 'rounded']: Border style
|
||||||
" - 'rounded' / 'sharp' / 'horizontal' / 'vertical' / 'top' / 'bottom' / 'left' / 'right'
|
" - 'rounded' / 'sharp' / 'horizontal' / 'vertical' / 'top' / 'bottom' / 'left' / 'right'
|
||||||
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
||||||
@@ -458,7 +460,7 @@ LICENSE *fzf-license*
|
|||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2020 Junegunn Choi
|
Copyright (c) 2013-2021 Junegunn Choi
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap:
|
vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap:
|
||||||
|
2
go.sum
2
go.sum
@@ -17,6 +17,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90Pveol
|
|||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
|
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/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/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 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
|
||||||
@@ -32,4 +33,5 @@ golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
|||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
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/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
5
install
5
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.24.4
|
version=0.26.0
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
@@ -168,7 +168,8 @@ archi=$(uname -sm)
|
|||||||
binary_available=1
|
binary_available=1
|
||||||
binary_error=""
|
binary_error=""
|
||||||
case "$archi" in
|
case "$archi" in
|
||||||
Darwin\ *64) download fzf-$version-darwin_amd64.tar.gz ;;
|
# Darwin\ arm64) download fzf-$version-darwin_arm64.tar.gz ;; # TODO
|
||||||
|
Darwin\ x86_64) download fzf-$version-darwin_amd64.tar.gz ;;
|
||||||
Linux\ armv5*) download fzf-$version-linux_armv5.tar.gz ;;
|
Linux\ armv5*) download fzf-$version-linux_armv5.tar.gz ;;
|
||||||
Linux\ armv6*) download fzf-$version-linux_armv6.tar.gz ;;
|
Linux\ armv6*) download fzf-$version-linux_armv6.tar.gz ;;
|
||||||
Linux\ armv7*) download fzf-$version-linux_armv7.tar.gz ;;
|
Linux\ armv7*) download fzf-$version-linux_armv7.tar.gz ;;
|
||||||
|
10
install.ps1
10
install.ps1
@@ -1,4 +1,4 @@
|
|||||||
$version="0.24.4"
|
$version="0.26.0"
|
||||||
|
|
||||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
|
||||||
@@ -43,7 +43,11 @@ function download {
|
|||||||
$url="https://github.com/junegunn/fzf/releases/download/$version/$file"
|
$url="https://github.com/junegunn/fzf/releases/download/$version/$file"
|
||||||
$temp=$env:TMP + "\fzf.zip"
|
$temp=$env:TMP + "\fzf.zip"
|
||||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
(New-Object Net.WebClient).DownloadFile($url, $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$temp"))
|
if ($PSVersionTable.PSVersion.Major -ge 3) {
|
||||||
|
Invoke-WebRequest -Uri $url -OutFile $temp
|
||||||
|
} else {
|
||||||
|
(New-Object Net.WebClient).DownloadFile($url, $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$temp"))
|
||||||
|
}
|
||||||
if ($?) {
|
if ($?) {
|
||||||
(Microsoft.PowerShell.Archive\Expand-Archive -Path $temp -DestinationPath .); (Remove-Item $temp)
|
(Microsoft.PowerShell.Archive\Expand-Archive -Path $temp -DestinationPath .); (Remove-Item $temp)
|
||||||
} else {
|
} else {
|
||||||
@@ -53,7 +57,7 @@ function download {
|
|||||||
$binary_error="Failed to download $file"
|
$binary_error="Failed to download $file"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
check_binary >$null
|
echo y | icacls $fzf_base\bin\fzf.exe /grant Administrator:F ; check_binary >$null
|
||||||
}
|
}
|
||||||
|
|
||||||
download "fzf-$version-windows_amd64.zip"
|
download "fzf-$version-windows_amd64.zip"
|
||||||
|
2
main.go
2
main.go
@@ -5,7 +5,7 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/protector"
|
"github.com/junegunn/fzf/src/protector"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "0.24"
|
var version string = "0.26"
|
||||||
var revision string = "devel"
|
var revision string = "devel"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
.ig
|
.ig
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2020 Junegunn Choi
|
Copyright (c) 2013-2021 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf-tmux 1 "Dec 2020" "fzf 0.24.4" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf-tmux 1 "Mar 2021" "fzf 0.26.0" "fzf-tmux - open fzf in tmux split pane"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf-tmux - open fzf in tmux split pane
|
fzf-tmux - open fzf in tmux split pane
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
.ig
|
.ig
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2020 Junegunn Choi
|
Copyright (c) 2013-2021 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf 1 "Dec 2020" "fzf 0.24.4" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Mar 2021" "fzf 0.26.0" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -71,9 +71,10 @@ Transform the presentation of each line using field index expressions
|
|||||||
.BI "-d, --delimiter=" "STR"
|
.BI "-d, --delimiter=" "STR"
|
||||||
Field delimiter regex for \fB--nth\fR and \fB--with-nth\fR (default: AWK-style)
|
Field delimiter regex for \fB--nth\fR and \fB--with-nth\fR (default: AWK-style)
|
||||||
.TP
|
.TP
|
||||||
.BI "--phony"
|
.BI "--disabled"
|
||||||
Do not perform search. With this option, fzf becomes a simple selector
|
Do not perform search. With this option, fzf becomes a simple selector
|
||||||
interface rather than a "fuzzy finder".
|
interface rather than a "fuzzy finder". You can later enable the search using
|
||||||
|
\fBenable-search\fR or \fBtoggle-search\fR action.
|
||||||
.SS Search result
|
.SS Search result
|
||||||
.TP
|
.TP
|
||||||
.B "+s, --no-sort"
|
.B "+s, --no-sort"
|
||||||
@@ -323,6 +324,8 @@ color mappings.
|
|||||||
\fBbg+ \fRBackground (current line)
|
\fBbg+ \fRBackground (current line)
|
||||||
\fBgutter \fRGutter on the left (defaults to \fBbg+\fR)
|
\fBgutter \fRGutter on the left (defaults to \fBbg+\fR)
|
||||||
\fBhl+ \fRHighlighted substrings (current line)
|
\fBhl+ \fRHighlighted substrings (current line)
|
||||||
|
\fBquery \fRQuery string
|
||||||
|
\fBdisabled \fRQuery string when search is disabled
|
||||||
\fBinfo \fRInfo line (match counters)
|
\fBinfo \fRInfo line (match counters)
|
||||||
\fBborder \fRBorder around the window (\fB--border\fR and \fB--preview\fR)
|
\fBborder \fRBorder around the window (\fB--border\fR and \fB--preview\fR)
|
||||||
\fBprompt \fRPrompt
|
\fBprompt \fRPrompt
|
||||||
@@ -439,7 +442,7 @@ e.g.
|
|||||||
done'\fR
|
done'\fR
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.BI "--preview-window=" "[POSITION][:SIZE[%]][:rounded|sharp|noborder][:[no]wrap][:[no]follow][:[no]cycle][:[no]hidden][:+SCROLL[-OFFSET]][:default]"
|
.BI "--preview-window=" "[POSITION][:SIZE[%]][:rounded|sharp|noborder][:[no]wrap][:[no]follow][:[no]cycle][:[no]hidden][:+SCROLL[OFFSETS][/DENOM]][:~HEADER_LINES][:default]"
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
.B POSITION: (default: right)
|
.B POSITION: (default: right)
|
||||||
@@ -477,12 +480,17 @@ e.g.
|
|||||||
\fBrounded\fR (border with rounded edges, default), \fBsharp\fR (border with
|
\fBrounded\fR (border with rounded edges, default), \fBsharp\fR (border with
|
||||||
sharp edges), or \fBnoborder\fR (no border).
|
sharp edges), or \fBnoborder\fR (no border).
|
||||||
|
|
||||||
* \fB+SCROLL[-OFFSET]\fR determines the initial scroll offset of the preview
|
* \fB[:+SCROLL[OFFSETS][/DENOM]]\fR determines the initial scroll offset of the
|
||||||
window. \fBSCROLL\fR can be either a numeric integer or a single-field index
|
preview window.
|
||||||
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
|
- \fBSCROLL\fR can be either a numeric integer or a single-field index expression that refers to a numeric integer.
|
||||||
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.
|
- The optional \fBOFFSETS\fR part is for adjusting the base offset. It should be given as a series of signed integers (\fB-INTEGER\fR or \fB+INTEGER\fR).
|
||||||
|
|
||||||
|
- The final \fB/DENOM\fR part is for specifying a fraction of the preview window height.
|
||||||
|
|
||||||
|
* \fB~HEADER_LINES\fR keeps the top N lines as the fixed header so that they
|
||||||
|
are always visible.
|
||||||
|
|
||||||
* \fBdefault\fR resets all options previously set to the default.
|
* \fBdefault\fR resets all options previously set to the default.
|
||||||
|
|
||||||
@@ -495,14 +503,23 @@ e.g.
|
|||||||
# Initial scroll offset is set to the line number of each line of
|
# Initial scroll offset is set to the line number of each line of
|
||||||
# git grep output *minus* 5 lines (-5)
|
# git grep output *minus* 5 lines (-5)
|
||||||
git grep --line-number '' |
|
git grep --line-number '' |
|
||||||
fzf --delimiter : --preview 'nl {1}' --preview-window +{2}-5
|
fzf --delimiter : --preview 'nl {1}' --preview-window '+{2}-5'
|
||||||
|
|
||||||
# Preview with bat, matching line in the middle of the window (-/2)
|
# Preview with bat, matching line in the middle of the window below
|
||||||
|
# the fixed header of the top 3 lines
|
||||||
|
#
|
||||||
|
# ~3 Top 3 lines as the fixed header
|
||||||
|
# +{2} Base scroll offset extracted from the second field
|
||||||
|
# +3 Extra offset to compensate for the 3-line header
|
||||||
|
# /2 Put in the middle of the preview area
|
||||||
|
#
|
||||||
git grep --line-number '' |
|
git grep --line-number '' |
|
||||||
fzf --delimiter : \\
|
fzf --delimiter : \\
|
||||||
--preview 'bat --style=numbers --color=always --highlight-line {2} {1}' \\
|
--preview 'bat --style=full --color=always --highlight-line {2} {1}' \\
|
||||||
--preview-window +{2}-/2\fR
|
--preview-window '~3:+{2}+3/2'
|
||||||
|
|
||||||
|
# Display top 3 lines as the fixed header
|
||||||
|
fzf --preview 'bat --style=full --color=always {}' --preview-window '~3'\fR
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
.SS Scripting
|
.SS Scripting
|
||||||
@@ -567,7 +584,8 @@ Note that most options have the opposite versions with \fB--no-\fR prefix.
|
|||||||
.TP
|
.TP
|
||||||
.B FZF_DEFAULT_COMMAND
|
.B FZF_DEFAULT_COMMAND
|
||||||
Default command to use when input is tty. On *nix systems, fzf runs the command
|
Default command to use when input is tty. On *nix systems, fzf runs the command
|
||||||
with \fBsh -c\fR, so make sure that it's POSIX-compliant.
|
with \fB$SHELL -c\fR if \fBSHELL\fR is set, otherwise with \fBsh -c\fR, so in
|
||||||
|
this case make sure that the command is POSIX-compliant.
|
||||||
.TP
|
.TP
|
||||||
.B FZF_DEFAULT_OPTS
|
.B FZF_DEFAULT_OPTS
|
||||||
Default options. e.g. \fBexport FZF_DEFAULT_OPTS="--extended --cycle"\fR
|
Default options. e.g. \fBexport FZF_DEFAULT_OPTS="--extended --cycle"\fR
|
||||||
@@ -666,9 +684,7 @@ e.g.
|
|||||||
.br
|
.br
|
||||||
\fIctrl-alt-[a-z]\fR
|
\fIctrl-alt-[a-z]\fR
|
||||||
.br
|
.br
|
||||||
\fIalt-[a-z]\fR
|
\fIalt-[*]\fR (Any case-sensitive single character is allowed)
|
||||||
.br
|
|
||||||
\fIalt-[0-9]\fR
|
|
||||||
.br
|
.br
|
||||||
\fIf[1-12]\fR
|
\fIf[1-12]\fR
|
||||||
.br
|
.br
|
||||||
@@ -692,8 +708,6 @@ e.g.
|
|||||||
.br
|
.br
|
||||||
\fIalt-bspace\fR (\fIalt-bs\fR)
|
\fIalt-bspace\fR (\fIalt-bs\fR)
|
||||||
.br
|
.br
|
||||||
\fIalt-/\fR
|
|
||||||
.br
|
|
||||||
\fItab\fR
|
\fItab\fR
|
||||||
.br
|
.br
|
||||||
\fIbtab\fR (\fIshift-tab\fR)
|
\fIbtab\fR (\fIshift-tab\fR)
|
||||||
@@ -750,8 +764,8 @@ or any single character
|
|||||||
Triggered whenever the query string is changed
|
Triggered whenever the query string is changed
|
||||||
|
|
||||||
e.g.
|
e.g.
|
||||||
\fB# Moves cursor to the top (or bottom depending on --layout) whenever the query is changed
|
\fB# Move cursor to the first entry whenever the query is changed
|
||||||
fzf --bind change:top\fR
|
fzf --bind change:first\fR
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
\fIbackward-eof\fR
|
\fIbackward-eof\fR
|
||||||
@@ -780,14 +794,19 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBchange-prompt(...)\fR (change prompt to the given string)
|
\fBchange-prompt(...)\fR (change prompt to the given string)
|
||||||
\fBclear-screen\fR \fIctrl-l\fR
|
\fBclear-screen\fR \fIctrl-l\fR
|
||||||
\fBclear-selection\fR (clear multi-selection)
|
\fBclear-selection\fR (clear multi-selection)
|
||||||
|
\fBclose\fR (close preview window if open, abort fzf otherwise)
|
||||||
\fBclear-query\fR (clear query string)
|
\fBclear-query\fR (clear query string)
|
||||||
\fBdelete-char\fR \fIdel\fR
|
\fBdelete-char\fR \fIdel\fR
|
||||||
\fBdelete-char/eof\fR \fIctrl-d\fR (same as \fBdelete-char\fR except aborts fzf if query is empty)
|
\fBdelete-char/eof\fR \fIctrl-d\fR (same as \fBdelete-char\fR except aborts fzf if query is empty)
|
||||||
|
\fBdeselect\fR
|
||||||
\fBdeselect-all\fR (deselect all matches)
|
\fBdeselect-all\fR (deselect all matches)
|
||||||
|
\fBdisable-search\fR (disable search functionality)
|
||||||
\fBdown\fR \fIctrl-j ctrl-n down\fR
|
\fBdown\fR \fIctrl-j ctrl-n down\fR
|
||||||
|
\fBenable-search\fR (enable search functionality)
|
||||||
\fBend-of-line\fR \fIctrl-e end\fR
|
\fBend-of-line\fR \fIctrl-e end\fR
|
||||||
\fBexecute(...)\fR (see below for the details)
|
\fBexecute(...)\fR (see below for the details)
|
||||||
\fBexecute-silent(...)\fR (see below for the details)
|
\fBexecute-silent(...)\fR (see below for the details)
|
||||||
|
\fBfirst\fR (move to the first match)
|
||||||
\fBforward-char\fR \fIctrl-f right\fR
|
\fBforward-char\fR \fIctrl-f right\fR
|
||||||
\fBforward-word\fR \fIalt-f shift-right\fR
|
\fBforward-word\fR \fIalt-f shift-right\fR
|
||||||
\fBignore\fR
|
\fBignore\fR
|
||||||
@@ -795,6 +814,7 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBjump-accept\fR (jump and accept)
|
\fBjump-accept\fR (jump and accept)
|
||||||
\fBkill-line\fR
|
\fBkill-line\fR
|
||||||
\fBkill-word\fR \fIalt-d\fR
|
\fBkill-word\fR \fIalt-d\fR
|
||||||
|
\fBlast\fR (move to the last match)
|
||||||
\fBnext-history\fR (\fIctrl-n\fR on \fB--history\fR)
|
\fBnext-history\fR (\fIctrl-n\fR on \fB--history\fR)
|
||||||
\fBpage-down\fR \fIpgdn\fR
|
\fBpage-down\fR \fIpgdn\fR
|
||||||
\fBpage-up\fR \fIpgup\fR
|
\fBpage-up\fR \fIpgup\fR
|
||||||
@@ -807,11 +827,14 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBpreview-page-up\fR
|
\fBpreview-page-up\fR
|
||||||
\fBpreview-half-page-down\fR
|
\fBpreview-half-page-down\fR
|
||||||
\fBpreview-half-page-up\fR
|
\fBpreview-half-page-up\fR
|
||||||
|
\fBpreview-bottom\fR
|
||||||
|
\fBpreview-top\fR
|
||||||
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
||||||
\fBprint-query\fR (print query and exit)
|
\fBprint-query\fR (print query and exit)
|
||||||
\fBrefresh-preview\fR
|
\fBrefresh-preview\fR
|
||||||
\fBreload(...)\fR (see below for the details)
|
\fBreload(...)\fR (see below for the details)
|
||||||
\fBreplace-query\fR (replace query string with the current selection)
|
\fBreplace-query\fR (replace query string with the current selection)
|
||||||
|
\fBselect\fR
|
||||||
\fBselect-all\fR (select all matches)
|
\fBselect-all\fR (select all matches)
|
||||||
\fBtoggle\fR (\fIright-click\fR)
|
\fBtoggle\fR (\fIright-click\fR)
|
||||||
\fBtoggle-all\fR (toggle all matches)
|
\fBtoggle-all\fR (toggle all matches)
|
||||||
@@ -820,9 +843,9 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
||||||
\fBtoggle-preview\fR
|
\fBtoggle-preview\fR
|
||||||
\fBtoggle-preview-wrap\fR
|
\fBtoggle-preview-wrap\fR
|
||||||
|
\fBtoggle-search\fR (toggle search functionality)
|
||||||
\fBtoggle-sort\fR
|
\fBtoggle-sort\fR
|
||||||
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
||||||
\fBtop\fR (move to the top result)
|
|
||||||
\fBunix-line-discard\fR \fIctrl-u\fR
|
\fBunix-line-discard\fR \fIctrl-u\fR
|
||||||
\fBunix-word-rubout\fR \fIctrl-w\fR
|
\fBunix-word-rubout\fR \fIctrl-w\fR
|
||||||
\fBup\fR \fIctrl-k ctrl-p up\fR
|
\fBup\fR \fIctrl-k ctrl-p up\fR
|
||||||
@@ -885,6 +908,10 @@ executes the command without the switching. Note that fzf will not be
|
|||||||
responsive until the command is complete. For asynchronous execution, start
|
responsive until the command is complete. For asynchronous execution, start
|
||||||
your command as a background process (i.e. appending \fB&\fR).
|
your command as a background process (i.e. appending \fB&\fR).
|
||||||
|
|
||||||
|
On *nix systems, fzf runs the command with \fB$SHELL -c\fR if \fBSHELL\fR is
|
||||||
|
set, otherwise with \fBsh -c\fR, so in this case make sure that the command is
|
||||||
|
POSIX-compliant.
|
||||||
|
|
||||||
.SS RELOAD INPUT
|
.SS RELOAD INPUT
|
||||||
|
|
||||||
\fBreload(...)\fR action is used to dynamically update the input list
|
\fBreload(...)\fR action is used to dynamically update the input list
|
||||||
@@ -903,7 +930,7 @@ e.g.
|
|||||||
INITIAL_QUERY="foobar"
|
INITIAL_QUERY="foobar"
|
||||||
FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY'" \\
|
FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY'" \\
|
||||||
fzf --bind "change:reload:$RG_PREFIX {q} || true" \\
|
fzf --bind "change:reload:$RG_PREFIX {q} || true" \\
|
||||||
--ansi --phony --query "$INITIAL_QUERY"\fR
|
--ansi --disabled --query "$INITIAL_QUERY"\fR
|
||||||
|
|
||||||
.SS PREVIEW BINDING
|
.SS PREVIEW BINDING
|
||||||
|
|
||||||
|
@@ -424,10 +424,10 @@ try
|
|||||||
throw v:exception
|
throw v:exception
|
||||||
endtry
|
endtry
|
||||||
|
|
||||||
if !has_key(dict, 'dir')
|
if !s:present(dict, 'dir')
|
||||||
let dict.dir = s:fzf_getcwd()
|
let dict.dir = s:fzf_getcwd()
|
||||||
endif
|
endif
|
||||||
if has('win32unix') && has_key(dict, 'dir')
|
if has('win32unix') && s:present(dict, 'dir')
|
||||||
let dict.dir = fnamemodify(dict.dir, ':p')
|
let dict.dir = fnamemodify(dict.dir, ':p')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@@ -764,6 +764,13 @@ function! s:split(dict)
|
|||||||
endtry
|
endtry
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
nnoremap <silent> <Plug>(fzf-insert) i
|
||||||
|
nnoremap <silent> <Plug>(fzf-normal) <Nop>
|
||||||
|
if exists(':tnoremap')
|
||||||
|
tnoremap <silent> <Plug>(fzf-insert) <C-\><C-n>i
|
||||||
|
tnoremap <silent> <Plug>(fzf-normal) <C-\><C-n>
|
||||||
|
endif
|
||||||
|
|
||||||
function! s:execute_term(dict, command, temps) abort
|
function! s:execute_term(dict, command, temps) abort
|
||||||
let winrest = winrestcmd()
|
let winrest = winrestcmd()
|
||||||
let pbuf = bufnr('')
|
let pbuf = bufnr('')
|
||||||
@@ -776,7 +783,7 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
function! fzf.switch_back(inplace)
|
function! fzf.switch_back(inplace)
|
||||||
if a:inplace && bufnr('') == self.buf
|
if a:inplace && bufnr('') == self.buf
|
||||||
if bufexists(self.pbuf)
|
if bufexists(self.pbuf)
|
||||||
execute 'keepalt b' self.pbuf
|
execute 'keepalt keepjumps b' self.pbuf
|
||||||
endif
|
endif
|
||||||
" No other listed buffer
|
" No other listed buffer
|
||||||
if bufnr('') == self.buf
|
if bufnr('') == self.buf
|
||||||
@@ -816,6 +823,10 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
call s:pushd(self.dict)
|
call s:pushd(self.dict)
|
||||||
call s:callback(self.dict, lines)
|
call s:callback(self.dict, lines)
|
||||||
call self.switch_back(s:getpos() == self.ppos)
|
call self.switch_back(s:getpos() == self.ppos)
|
||||||
|
|
||||||
|
if &buftype == 'terminal'
|
||||||
|
call feedkeys(&filetype == 'fzf' ? "\<Plug>(fzf-insert)" : "\<Plug>(fzf-normal)")
|
||||||
|
endif
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -832,6 +843,9 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
call termopen(command, fzf)
|
call termopen(command, fzf)
|
||||||
else
|
else
|
||||||
let term_opts = {'exit_cb': function(fzf.on_exit)}
|
let term_opts = {'exit_cb': function(fzf.on_exit)}
|
||||||
|
if v:version >= 802
|
||||||
|
let term_opts.term_kill = 'term'
|
||||||
|
endif
|
||||||
if is_popup
|
if is_popup
|
||||||
let term_opts.hidden = 1
|
let term_opts.hidden = 1
|
||||||
else
|
else
|
||||||
@@ -904,13 +918,9 @@ if has('nvim')
|
|||||||
function s:create_popup(hl, opts) abort
|
function s:create_popup(hl, opts) abort
|
||||||
let buf = nvim_create_buf(v:false, v:true)
|
let buf = nvim_create_buf(v:false, v:true)
|
||||||
let opts = extend({'relative': 'editor', 'style': 'minimal'}, a:opts)
|
let opts = extend({'relative': 'editor', 'style': 'minimal'}, a:opts)
|
||||||
let border = has_key(opts, 'border') ? remove(opts, 'border') : []
|
|
||||||
let win = nvim_open_win(buf, v:true, opts)
|
let win = nvim_open_win(buf, v:true, opts)
|
||||||
call setwinvar(win, '&winhighlight', 'NormalFloat:'..a:hl)
|
call setwinvar(win, '&winhighlight', 'NormalFloat:'..a:hl)
|
||||||
call setwinvar(win, '&colorcolumn', '')
|
call setwinvar(win, '&colorcolumn', '')
|
||||||
if !empty(border)
|
|
||||||
call nvim_buf_set_lines(buf, 0, -1, v:true, border)
|
|
||||||
endif
|
|
||||||
return buf
|
return buf
|
||||||
endfunction
|
endfunction
|
||||||
else
|
else
|
||||||
|
@@ -176,7 +176,7 @@ __fzf_generic_path_completion() {
|
|||||||
leftover=${leftover/#\/}
|
leftover=${leftover/#\/}
|
||||||
[ -z "$dir" ] && dir='.'
|
[ -z "$dir" ] && dir='.'
|
||||||
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
||||||
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
|
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
|
||||||
printf "%q$3 " "$item"
|
printf "%q$3 " "$item"
|
||||||
done)
|
done)
|
||||||
matches=${matches% }
|
matches=${matches% }
|
||||||
@@ -232,7 +232,7 @@ _fzf_complete() {
|
|||||||
if [[ "$cur" == *"$trigger" ]]; then
|
if [[ "$cur" == *"$trigger" ]]; then
|
||||||
cur=${cur:0:${#cur}-${#trigger}}
|
cur=${cur:0:${#cur}-${#trigger}}
|
||||||
|
|
||||||
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | tr '\n' ' ')
|
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | tr '\n' ' ')
|
||||||
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
|
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
COMPREPLY=("$selected")
|
COMPREPLY=("$selected")
|
||||||
|
@@ -145,7 +145,7 @@ __fzf_generic_path_completion() {
|
|||||||
leftover=${leftover/#\/}
|
leftover=${leftover/#\/}
|
||||||
[ -z "$dir" ] && dir='.'
|
[ -z "$dir" ] && dir='.'
|
||||||
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
||||||
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
|
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
|
||||||
echo -n "${(q)item}$suffix "
|
echo -n "${(q)item}$suffix "
|
||||||
done)
|
done)
|
||||||
matches=${matches% }
|
matches=${matches% }
|
||||||
@@ -207,11 +207,10 @@ _fzf_complete() {
|
|||||||
type $post > /dev/null 2>&1 || post=cat
|
type $post > /dev/null 2>&1 || post=cat
|
||||||
|
|
||||||
_fzf_feed_fifo "$fifo"
|
_fzf_feed_fifo "$fifo"
|
||||||
matches=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
|
matches=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
|
||||||
if [ -n "$matches" ]; then
|
if [ -n "$matches" ]; then
|
||||||
LBUFFER="$lbuf$matches"
|
LBUFFER="$lbuf$matches"
|
||||||
fi
|
fi
|
||||||
zle reset-prompt
|
|
||||||
command rm -f "$fifo"
|
command rm -f "$fifo"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,6 +301,7 @@ fzf-completion() {
|
|||||||
|
|
||||||
if eval "type _fzf_complete_${cmd} > /dev/null"; then
|
if eval "type _fzf_complete_${cmd} > /dev/null"; then
|
||||||
prefix="$prefix" eval _fzf_complete_${cmd} ${(q)lbuf}
|
prefix="$prefix" eval _fzf_complete_${cmd} ${(q)lbuf}
|
||||||
|
zle reset-prompt
|
||||||
elif [ ${d_cmds[(i)$cmd]} -le ${#d_cmds} ]; then
|
elif [ ${d_cmds[(i)$cmd]} -le ${#d_cmds} ]; then
|
||||||
_fzf_dir_completion "$prefix" "$lbuf"
|
_fzf_dir_completion "$prefix" "$lbuf"
|
||||||
else
|
else
|
||||||
|
@@ -18,7 +18,7 @@ __fzf_select__() {
|
|||||||
-o -type f -print \
|
-o -type f -print \
|
||||||
-o -type d -print \
|
-o -type d -print \
|
||||||
-o -type l -print 2> /dev/null | cut -b3-"}"
|
-o -type l -print 2> /dev/null | cut -b3-"}"
|
||||||
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read -r item; do
|
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read -r item; do
|
||||||
printf '%q ' "$item"
|
printf '%q ' "$item"
|
||||||
done
|
done
|
||||||
echo
|
echo
|
||||||
@@ -41,7 +41,7 @@ __fzf_cd__() {
|
|||||||
local cmd dir
|
local cmd dir
|
||||||
cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||||
-o -type d -print 2> /dev/null | cut -b3-"}"
|
-o -type d -print 2> /dev/null | cut -b3-"}"
|
||||||
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m) && printf 'cd %q' "$dir"
|
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m) && printf 'cd %q' "$dir"
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzf_history__() {
|
__fzf_history__() {
|
||||||
@@ -49,7 +49,7 @@ __fzf_history__() {
|
|||||||
output=$(
|
output=$(
|
||||||
builtin fc -lnr -2147483648 |
|
builtin fc -lnr -2147483648 |
|
||||||
last_hist=$(HISTTIMEFORMAT='' builtin history 1) perl -n -l0 -e 'BEGIN { getc; $/ = "\n\t"; $HISTCMD = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCMD - $. . "\t$_" if !$seen{$_}++' |
|
last_hist=$(HISTTIMEFORMAT='' builtin history 1) perl -n -l0 -e 'BEGIN { getc; $/ = "\n\t"; $HISTCMD = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCMD - $. . "\t$_" if !$seen{$_}++' |
|
||||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m --read0" $(__fzfcmd) --query "$READLINE_LINE"
|
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS +m --read0" $(__fzfcmd) --query "$READLINE_LINE"
|
||||||
) || return
|
) || return
|
||||||
READLINE_LINE=${output#*$'\t'}
|
READLINE_LINE=${output#*$'\t'}
|
||||||
if [ -z "$READLINE_POINT" ]; then
|
if [ -z "$READLINE_POINT" ]; then
|
||||||
|
@@ -20,6 +20,7 @@ function fzf_key_bindings
|
|||||||
set -l commandline (__fzf_parse_commandline)
|
set -l commandline (__fzf_parse_commandline)
|
||||||
set -l dir $commandline[1]
|
set -l dir $commandline[1]
|
||||||
set -l fzf_query $commandline[2]
|
set -l fzf_query $commandline[2]
|
||||||
|
set -l prefix $commandline[3]
|
||||||
|
|
||||||
# "-path \$dir'*/\\.*'" matches hidden files/folders inside $dir but not
|
# "-path \$dir'*/\\.*'" matches hidden files/folders inside $dir but not
|
||||||
# $dir itself, even if hidden.
|
# $dir itself, even if hidden.
|
||||||
@@ -31,7 +32,7 @@ function fzf_key_bindings
|
|||||||
|
|
||||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||||
begin
|
begin
|
||||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
|
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
|
||||||
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
|
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
|
||||||
end
|
end
|
||||||
if [ -z "$result" ]
|
if [ -z "$result" ]
|
||||||
@@ -42,6 +43,7 @@ function fzf_key_bindings
|
|||||||
commandline -t ""
|
commandline -t ""
|
||||||
end
|
end
|
||||||
for i in $result
|
for i in $result
|
||||||
|
commandline -it -- $prefix
|
||||||
commandline -it -- (string escape $i)
|
commandline -it -- (string escape $i)
|
||||||
commandline -it -- ' '
|
commandline -it -- ' '
|
||||||
end
|
end
|
||||||
@@ -51,7 +53,7 @@ function fzf_key_bindings
|
|||||||
function fzf-history-widget -d "Show command history"
|
function fzf-history-widget -d "Show command history"
|
||||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||||
begin
|
begin
|
||||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m"
|
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS +m"
|
||||||
|
|
||||||
set -l FISH_MAJOR (echo $version | cut -f1 -d.)
|
set -l FISH_MAJOR (echo $version | cut -f1 -d.)
|
||||||
set -l FISH_MINOR (echo $version | cut -f2 -d.)
|
set -l FISH_MINOR (echo $version | cut -f2 -d.)
|
||||||
@@ -74,13 +76,14 @@ function fzf_key_bindings
|
|||||||
set -l commandline (__fzf_parse_commandline)
|
set -l commandline (__fzf_parse_commandline)
|
||||||
set -l dir $commandline[1]
|
set -l dir $commandline[1]
|
||||||
set -l fzf_query $commandline[2]
|
set -l fzf_query $commandline[2]
|
||||||
|
set -l prefix $commandline[3]
|
||||||
|
|
||||||
test -n "$FZF_ALT_C_COMMAND"; or set -l FZF_ALT_C_COMMAND "
|
test -n "$FZF_ALT_C_COMMAND"; or set -l FZF_ALT_C_COMMAND "
|
||||||
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
||||||
-o -type d -print 2> /dev/null | sed 's@^\./@@'"
|
-o -type d -print 2> /dev/null | sed 's@^\./@@'"
|
||||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||||
begin
|
begin
|
||||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
|
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
|
||||||
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
||||||
|
|
||||||
if [ -n "$result" ]
|
if [ -n "$result" ]
|
||||||
@@ -88,6 +91,7 @@ function fzf_key_bindings
|
|||||||
|
|
||||||
# Remove last token from commandline.
|
# Remove last token from commandline.
|
||||||
commandline -t ""
|
commandline -t ""
|
||||||
|
commandline -it -- $prefix
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -116,9 +120,15 @@ function fzf_key_bindings
|
|||||||
bind -M insert \ec fzf-cd-widget
|
bind -M insert \ec fzf-cd-widget
|
||||||
end
|
end
|
||||||
|
|
||||||
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath and rest of token'
|
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix'
|
||||||
|
set -l commandline (commandline -t)
|
||||||
|
|
||||||
|
# strip -option= from token if present
|
||||||
|
set -l prefix (string match -r -- '^-[^\s=]+=' $commandline)
|
||||||
|
set commandline (string replace -- "$prefix" '' $commandline)
|
||||||
|
|
||||||
# eval is used to do shell expansion on paths
|
# eval is used to do shell expansion on paths
|
||||||
set -l commandline (eval "printf '%s' "(commandline -t))
|
eval set commandline $commandline
|
||||||
|
|
||||||
if [ -z $commandline ]
|
if [ -z $commandline ]
|
||||||
# Default to current directory with no --query
|
# Default to current directory with no --query
|
||||||
@@ -138,6 +148,7 @@ function fzf_key_bindings
|
|||||||
|
|
||||||
echo $dir
|
echo $dir
|
||||||
echo $fzf_query
|
echo $fzf_query
|
||||||
|
echo $prefix
|
||||||
end
|
end
|
||||||
|
|
||||||
function __fzf_get_dir -d 'Find the longest existing filepath from input string'
|
function __fzf_get_dir -d 'Find the longest existing filepath from input string'
|
||||||
|
@@ -46,7 +46,7 @@ __fsel() {
|
|||||||
-o -type l -print 2> /dev/null | cut -b3-"}"
|
-o -type l -print 2> /dev/null | cut -b3-"}"
|
||||||
setopt localoptions pipefail no_aliases 2> /dev/null
|
setopt localoptions pipefail no_aliases 2> /dev/null
|
||||||
local item
|
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
|
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read item; do
|
||||||
echo -n "${(q)item} "
|
echo -n "${(q)item} "
|
||||||
done
|
done
|
||||||
local ret=$?
|
local ret=$?
|
||||||
@@ -68,36 +68,22 @@ fzf-file-widget() {
|
|||||||
zle -N fzf-file-widget
|
zle -N fzf-file-widget
|
||||||
bindkey '^T' fzf-file-widget
|
bindkey '^T' fzf-file-widget
|
||||||
|
|
||||||
# Ensure precmds are run after cd
|
|
||||||
fzf-redraw-prompt() {
|
|
||||||
local precmd
|
|
||||||
for precmd in $precmd_functions; do
|
|
||||||
$precmd
|
|
||||||
done
|
|
||||||
zle reset-prompt
|
|
||||||
}
|
|
||||||
zle -N fzf-redraw-prompt
|
|
||||||
|
|
||||||
# ALT-C - cd into the selected directory
|
# ALT-C - cd into the selected directory
|
||||||
fzf-cd-widget() {
|
fzf-cd-widget() {
|
||||||
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||||
-o -type d -print 2> /dev/null | cut -b3-"}"
|
-o -type d -print 2> /dev/null | cut -b3-"}"
|
||||||
setopt localoptions pipefail no_aliases 2> /dev/null
|
setopt localoptions pipefail no_aliases 2> /dev/null
|
||||||
local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m)"
|
local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m)"
|
||||||
if [[ -z "$dir" ]]; then
|
if [[ -z "$dir" ]]; then
|
||||||
zle redisplay
|
zle redisplay
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
if [ -z "$BUFFER" ]; then
|
zle push-line # Clear buffer. Auto-restored on next prompt.
|
||||||
BUFFER="cd ${(q)dir}"
|
BUFFER="cd ${(q)dir}"
|
||||||
zle accept-line
|
zle accept-line
|
||||||
else
|
|
||||||
print -sr "cd ${(q)dir}"
|
|
||||||
cd "$dir"
|
|
||||||
fi
|
|
||||||
local ret=$?
|
local ret=$?
|
||||||
unset dir # ensure this doesn't end up appearing in prompt expansion
|
unset dir # ensure this doesn't end up appearing in prompt expansion
|
||||||
zle fzf-redraw-prompt
|
zle reset-prompt
|
||||||
return $ret
|
return $ret
|
||||||
}
|
}
|
||||||
zle -N fzf-cd-widget
|
zle -N fzf-cd-widget
|
||||||
@@ -107,9 +93,8 @@ bindkey '\ec' fzf-cd-widget
|
|||||||
fzf-history-widget() {
|
fzf-history-widget() {
|
||||||
local selected num
|
local selected num
|
||||||
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
|
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)}++' |
|
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)) )
|
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
|
||||||
local ret=$?
|
local ret=$?
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
num=$selected[1]
|
num=$selected[1]
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2020 Junegunn Choi
|
Copyright (c) 2013-2021 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
263
src/ansi.go
263
src/ansi.go
@@ -1,8 +1,6 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
@@ -82,73 +80,154 @@ func toAnsiString(color tui.Color, offset int) string {
|
|||||||
return ret + ";"
|
return ret + ";"
|
||||||
}
|
}
|
||||||
|
|
||||||
var ansiRegex *regexp.Regexp
|
func isPrint(c uint8) bool {
|
||||||
|
return '\x20' <= c && c <= '\x7e'
|
||||||
func init() {
|
|
||||||
/*
|
|
||||||
References:
|
|
||||||
- https://github.com/gnachman/iTerm2
|
|
||||||
- http://ascii-table.com/ansi-escape-sequences.php
|
|
||||||
- http://ascii-table.com/ansi-escape-sequences-vt-100.php
|
|
||||||
- http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
|
|
||||||
- https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
|
||||||
*/
|
|
||||||
// The following regular expression will include not all but most of the
|
|
||||||
// frequently used ANSI sequences
|
|
||||||
ansiRegex = regexp.MustCompile("(?:\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func findAnsiStart(str string) int {
|
func matchOperatingSystemCommand(s string) int {
|
||||||
idx := 0
|
// `\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)`
|
||||||
for ; idx < len(str); idx++ {
|
// ^ match starting here
|
||||||
b := str[idx]
|
//
|
||||||
if b == 0x1b || b == 0x0e || b == 0x0f {
|
i := 5 // prefix matched in nextAnsiEscapeSequence()
|
||||||
return idx
|
for ; i < len(s) && isPrint(s[i]); i++ {
|
||||||
|
}
|
||||||
|
if i < len(s) {
|
||||||
|
if s[i] == '\x07' {
|
||||||
|
return i + 1
|
||||||
}
|
}
|
||||||
if b == 0x08 && idx > 0 {
|
if s[i] == '\x1b' && i < len(s)-1 && s[i+1] == '\\' {
|
||||||
return idx - 1
|
return i + 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return idx
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchControlSequence(s string) int {
|
||||||
|
// `\x1b[\\[()][0-9;]*[a-zA-Z@]`
|
||||||
|
// ^ match starting here
|
||||||
|
//
|
||||||
|
i := 2 // prefix matched in nextAnsiEscapeSequence()
|
||||||
|
for ; i < len(s) && (isNumeric(s[i]) || s[i] == ';'); i++ {
|
||||||
|
}
|
||||||
|
if i < len(s) {
|
||||||
|
c := s[i]
|
||||||
|
if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '@' {
|
||||||
|
return i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCtrlSeqStart(c uint8) bool {
|
||||||
|
return c == '\\' || c == '[' || c == '(' || c == ')'
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextAnsiEscapeSequence returns the ANSI escape sequence and is equivalent to
|
||||||
|
// calling FindStringIndex() on the below regex (which was originally used):
|
||||||
|
//
|
||||||
|
// "(?:\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)"
|
||||||
|
//
|
||||||
|
func nextAnsiEscapeSequence(s string) (int, int) {
|
||||||
|
// fast check for ANSI escape sequences
|
||||||
|
i := 0
|
||||||
|
for ; i < len(s); i++ {
|
||||||
|
switch s[i] {
|
||||||
|
case '\x0e', '\x0f', '\x1b', '\x08':
|
||||||
|
// We ignore the fact that '\x08' cannot be the first char
|
||||||
|
// in the string and be an escape sequence for the sake of
|
||||||
|
// speed and simplicity.
|
||||||
|
goto Loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1, -1
|
||||||
|
|
||||||
|
Loop:
|
||||||
|
for ; i < len(s); i++ {
|
||||||
|
switch s[i] {
|
||||||
|
case '\x08':
|
||||||
|
// backtrack to match: `.\x08`
|
||||||
|
if i > 0 && s[i-1] != '\n' {
|
||||||
|
if s[i-1] < utf8.RuneSelf {
|
||||||
|
return i - 1, i + 1
|
||||||
|
}
|
||||||
|
_, n := utf8.DecodeLastRuneInString(s[:i])
|
||||||
|
return i - n, i + 1
|
||||||
|
}
|
||||||
|
case '\x1b':
|
||||||
|
// match: `\x1b[\\[()][0-9;]*[a-zA-Z@]`
|
||||||
|
if i+2 < len(s) && isCtrlSeqStart(s[i+1]) {
|
||||||
|
if j := matchControlSequence(s[i:]); j != -1 {
|
||||||
|
return i, i + j
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// match: `\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)`
|
||||||
|
if i+5 < len(s) && s[i+1] == ']' && isNumeric(s[i+2]) &&
|
||||||
|
s[i+3] == ';' && isPrint(s[i+4]) {
|
||||||
|
|
||||||
|
if j := matchOperatingSystemCommand(s[i:]); j != -1 {
|
||||||
|
return i, i + j
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// match: `\x1b.`
|
||||||
|
if i+1 < len(s) && s[i+1] != '\n' {
|
||||||
|
if s[i+1] < utf8.RuneSelf {
|
||||||
|
return i, i + 2
|
||||||
|
}
|
||||||
|
_, n := utf8.DecodeRuneInString(s[i+1:])
|
||||||
|
return i, i + n + 1
|
||||||
|
}
|
||||||
|
case '\x0e', '\x0f':
|
||||||
|
// match: `[\x0e\x0f]`
|
||||||
|
return i, i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1, -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractColor(str string, state *ansiState, proc func(string, *ansiState) bool) (string, *[]ansiOffset, *ansiState) {
|
func extractColor(str string, state *ansiState, proc func(string, *ansiState) bool) (string, *[]ansiOffset, *ansiState) {
|
||||||
var offsets []ansiOffset
|
// We append to a stack allocated variable that we'll
|
||||||
var output bytes.Buffer
|
// later copy and return, to save on allocations.
|
||||||
|
offsets := make([]ansiOffset, 0, 32)
|
||||||
|
|
||||||
if state != nil {
|
if state != nil {
|
||||||
offsets = append(offsets, ansiOffset{[2]int32{0, 0}, *state})
|
offsets = append(offsets, ansiOffset{[2]int32{0, 0}, *state})
|
||||||
}
|
}
|
||||||
|
|
||||||
prevIdx := 0
|
var (
|
||||||
runeCount := 0
|
pstate *ansiState // lazily allocated
|
||||||
|
output strings.Builder
|
||||||
|
prevIdx int
|
||||||
|
runeCount int
|
||||||
|
)
|
||||||
for idx := 0; idx < len(str); {
|
for idx := 0; idx < len(str); {
|
||||||
idx += findAnsiStart(str[idx:])
|
// Make sure that we found an ANSI code
|
||||||
if idx == len(str) {
|
start, end := nextAnsiEscapeSequence(str[idx:])
|
||||||
|
if start == -1 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
start += idx
|
||||||
// Make sure that we found an ANSI code
|
idx += end
|
||||||
offset := ansiRegex.FindStringIndex(str[idx:])
|
|
||||||
if len(offset) < 2 {
|
|
||||||
idx++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
offset[0] += idx
|
|
||||||
offset[1] += idx
|
|
||||||
idx = offset[1]
|
|
||||||
|
|
||||||
// Check if we should continue
|
// Check if we should continue
|
||||||
prev := str[prevIdx:offset[0]]
|
prev := str[prevIdx:start]
|
||||||
if proc != nil && !proc(prev, state) {
|
if proc != nil && !proc(prev, state) {
|
||||||
return "", nil, nil
|
return "", nil, nil
|
||||||
}
|
}
|
||||||
|
prevIdx = idx
|
||||||
|
|
||||||
prevIdx = offset[1]
|
if len(prev) != 0 {
|
||||||
runeCount += utf8.RuneCountInString(prev)
|
runeCount += utf8.RuneCountInString(prev)
|
||||||
output.WriteString(prev)
|
// Grow the buffer size to the maximum possible length (string length
|
||||||
|
// containing ansi codes) to avoid repetitive allocation
|
||||||
|
if output.Cap() == 0 {
|
||||||
|
output.Grow(len(str))
|
||||||
|
}
|
||||||
|
output.WriteString(prev)
|
||||||
|
}
|
||||||
|
|
||||||
newState := interpretCode(str[offset[0]:offset[1]], state)
|
newState := interpretCode(str[start:idx], state)
|
||||||
if !newState.equals(state) {
|
if !newState.equals(state) {
|
||||||
if state != nil {
|
if state != nil {
|
||||||
// Update last offset
|
// Update last offset
|
||||||
@@ -157,8 +236,15 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
|
|||||||
|
|
||||||
if newState.colored() {
|
if newState.colored() {
|
||||||
// Append new offset
|
// Append new offset
|
||||||
state = newState
|
if pstate == nil {
|
||||||
offsets = append(offsets, ansiOffset{[2]int32{int32(runeCount), int32(runeCount)}, *state})
|
pstate = &ansiState{}
|
||||||
|
}
|
||||||
|
*pstate = newState
|
||||||
|
state = pstate
|
||||||
|
offsets = append(offsets, ansiOffset{
|
||||||
|
[2]int32{int32(runeCount), int32(runeCount)},
|
||||||
|
newState,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
// Discard state
|
// Discard state
|
||||||
state = nil
|
state = nil
|
||||||
@@ -168,7 +254,6 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
|
|||||||
|
|
||||||
var rest string
|
var rest string
|
||||||
var trimmed string
|
var trimmed string
|
||||||
|
|
||||||
if prevIdx == 0 {
|
if prevIdx == 0 {
|
||||||
// No ANSI code found
|
// No ANSI code found
|
||||||
rest = str
|
rest = str
|
||||||
@@ -178,51 +263,75 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
|
|||||||
output.WriteString(rest)
|
output.WriteString(rest)
|
||||||
trimmed = output.String()
|
trimmed = output.String()
|
||||||
}
|
}
|
||||||
if len(rest) > 0 && state != nil {
|
|
||||||
// Update last offset
|
|
||||||
runeCount += utf8.RuneCountInString(rest)
|
|
||||||
(&offsets[len(offsets)-1]).offset[1] = int32(runeCount)
|
|
||||||
}
|
|
||||||
if proc != nil {
|
if proc != nil {
|
||||||
proc(rest, state)
|
proc(rest, state)
|
||||||
}
|
}
|
||||||
if len(offsets) == 0 {
|
if len(offsets) > 0 {
|
||||||
return trimmed, nil, state
|
if len(rest) > 0 && state != nil {
|
||||||
|
// Update last offset
|
||||||
|
runeCount += utf8.RuneCountInString(rest)
|
||||||
|
(&offsets[len(offsets)-1]).offset[1] = int32(runeCount)
|
||||||
|
}
|
||||||
|
// Return a copy of the offsets slice
|
||||||
|
a := make([]ansiOffset, len(offsets))
|
||||||
|
copy(a, offsets)
|
||||||
|
return trimmed, &a, state
|
||||||
}
|
}
|
||||||
return trimmed, &offsets, state
|
return trimmed, nil, state
|
||||||
}
|
}
|
||||||
|
|
||||||
func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
func parseAnsiCode(s string) (int, string) {
|
||||||
// State
|
var remaining string
|
||||||
var state *ansiState
|
if i := strings.IndexByte(s, ';'); i >= 0 {
|
||||||
|
remaining = s[i+1:]
|
||||||
|
s = s[:i]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(s) > 0 {
|
||||||
|
// Inlined version of strconv.Atoi() that only handles positive
|
||||||
|
// integers and does not allocate on error.
|
||||||
|
code := 0
|
||||||
|
for _, ch := range []byte(s) {
|
||||||
|
ch -= '0'
|
||||||
|
if ch > 9 {
|
||||||
|
return -1, remaining
|
||||||
|
}
|
||||||
|
code = code*10 + int(ch)
|
||||||
|
}
|
||||||
|
return code, remaining
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1, remaining
|
||||||
|
}
|
||||||
|
|
||||||
|
func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
||||||
|
var state ansiState
|
||||||
if prevState == nil {
|
if prevState == nil {
|
||||||
state = &ansiState{-1, -1, 0, -1}
|
state = ansiState{-1, -1, 0, -1}
|
||||||
} else {
|
} else {
|
||||||
state = &ansiState{prevState.fg, prevState.bg, prevState.attr, prevState.lbg}
|
state = ansiState{prevState.fg, prevState.bg, prevState.attr, prevState.lbg}
|
||||||
}
|
}
|
||||||
if ansiCode[0] != '\x1b' || ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' {
|
if ansiCode[0] != '\x1b' || ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' {
|
||||||
if strings.HasSuffix(ansiCode, "0K") {
|
if prevState != nil && strings.HasSuffix(ansiCode, "0K") {
|
||||||
state.lbg = prevState.bg
|
state.lbg = prevState.bg
|
||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
ptr := &state.fg
|
if len(ansiCode) <= 3 {
|
||||||
state256 := 0
|
|
||||||
|
|
||||||
init := func() {
|
|
||||||
state.fg = -1
|
state.fg = -1
|
||||||
state.bg = -1
|
state.bg = -1
|
||||||
state.attr = 0
|
state.attr = 0
|
||||||
state256 = 0
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
ansiCode = ansiCode[2 : len(ansiCode)-1]
|
ansiCode = ansiCode[2 : len(ansiCode)-1]
|
||||||
if len(ansiCode) == 0 {
|
|
||||||
init()
|
state256 := 0
|
||||||
}
|
ptr := &state.fg
|
||||||
for _, code := range strings.Split(ansiCode, ";") {
|
|
||||||
if num, err := strconv.Atoi(code); err == nil {
|
for len(ansiCode) != 0 {
|
||||||
|
var num int
|
||||||
|
if num, ansiCode = parseAnsiCode(ansiCode); num != -1 {
|
||||||
switch state256 {
|
switch state256 {
|
||||||
case 0:
|
case 0:
|
||||||
switch num {
|
switch num {
|
||||||
@@ -253,7 +362,10 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
|||||||
case 24: // tput rmul
|
case 24: // tput rmul
|
||||||
state.attr = state.attr &^ tui.Underline
|
state.attr = state.attr &^ tui.Underline
|
||||||
case 0:
|
case 0:
|
||||||
init()
|
state.fg = -1
|
||||||
|
state.bg = -1
|
||||||
|
state.attr = 0
|
||||||
|
state256 = 0
|
||||||
default:
|
default:
|
||||||
if num >= 30 && num <= 37 {
|
if num >= 30 && num <= 37 {
|
||||||
state.fg = tui.Color(num - 30)
|
state.fg = tui.Color(num - 30)
|
||||||
@@ -289,6 +401,7 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if state256 > 0 {
|
if state256 > 0 {
|
||||||
*ptr = -1
|
*ptr = -1
|
||||||
}
|
}
|
||||||
|
239
src/ansi_test.go
239
src/ansi_test.go
@@ -2,12 +2,190 @@ package fzf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/tui"
|
"github.com/junegunn/fzf/src/tui"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// The following regular expression will include not all but most of the
|
||||||
|
// frequently used ANSI sequences. This regex is used as a reference for
|
||||||
|
// testing nextAnsiEscapeSequence().
|
||||||
|
//
|
||||||
|
// References:
|
||||||
|
// - https://github.com/gnachman/iTerm2
|
||||||
|
// - http://ascii-table.com/ansi-escape-sequences.php
|
||||||
|
// - http://ascii-table.com/ansi-escape-sequences-vt-100.php
|
||||||
|
// - http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
|
||||||
|
// - https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||||
|
var ansiRegexRefence = regexp.MustCompile("(?:\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)")
|
||||||
|
|
||||||
|
func testParserReference(t testing.TB, str string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
toSlice := func(start, end int) []int {
|
||||||
|
if start == -1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return []int{start, end}
|
||||||
|
}
|
||||||
|
|
||||||
|
s := str
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
got := toSlice(nextAnsiEscapeSequence(s))
|
||||||
|
exp := ansiRegexRefence.FindStringIndex(s)
|
||||||
|
|
||||||
|
equal := len(got) == len(exp)
|
||||||
|
if equal {
|
||||||
|
for i := 0; i < len(got); i++ {
|
||||||
|
if got[i] != exp[i] {
|
||||||
|
equal = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !equal {
|
||||||
|
var exps, gots []rune
|
||||||
|
if len(got) == 2 {
|
||||||
|
gots = []rune(s[got[0]:got[1]])
|
||||||
|
}
|
||||||
|
if len(exp) == 2 {
|
||||||
|
exps = []rune(s[exp[0]:exp[1]])
|
||||||
|
}
|
||||||
|
t.Errorf("%d: %q: got: %v (%q) want: %v (%q)", i, s, got, gots, exp, exps)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(exp) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s = s[exp[1]:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNextAnsiEscapeSequence(t *testing.T) {
|
||||||
|
testStrs := []string{
|
||||||
|
"\x1b[0mhello world",
|
||||||
|
"\x1b[1mhello world",
|
||||||
|
"椙\x1b[1m椙",
|
||||||
|
"椙\x1b[1椙m椙",
|
||||||
|
"\x1b[1mhello \x1b[mw\x1b7o\x1b8r\x1b(Bl\x1b[2@d",
|
||||||
|
"\x1b[1mhello \x1b[Kworld",
|
||||||
|
"hello \x1b[34;45;1mworld",
|
||||||
|
"hello \x1b[34;45;1mwor\x1b[34;45;1mld",
|
||||||
|
"hello \x1b[34;45;1mwor\x1b[0mld",
|
||||||
|
"hello \x1b[34;48;5;233;1mwo\x1b[38;5;161mr\x1b[0ml\x1b[38;5;161md",
|
||||||
|
"hello \x1b[38;5;38;48;5;48;1mwor\x1b[38;5;48;48;5;38ml\x1b[0md",
|
||||||
|
"hello \x1b[32;1mworld",
|
||||||
|
"hello world",
|
||||||
|
"hello \x1b[0;38;5;200;48;5;100mworld",
|
||||||
|
"\x1b椙",
|
||||||
|
"椙\x08",
|
||||||
|
"\n\x08",
|
||||||
|
"X\x08",
|
||||||
|
"",
|
||||||
|
"\x1b]4;3;rgb:aa/bb/cc\x07 ",
|
||||||
|
"\x1b]4;3;rgb:aa/bb/cc\x1b\\ ",
|
||||||
|
ansiBenchmarkString,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range testStrs {
|
||||||
|
testParserReference(t, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNextAnsiEscapeSequence_Fuzz_Modified(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("short test")
|
||||||
|
}
|
||||||
|
|
||||||
|
testStrs := []string{
|
||||||
|
"\x1b[0mhello world",
|
||||||
|
"\x1b[1mhello world",
|
||||||
|
"椙\x1b[1m椙",
|
||||||
|
"椙\x1b[1椙m椙",
|
||||||
|
"\x1b[1mhello \x1b[mw\x1b7o\x1b8r\x1b(Bl\x1b[2@d",
|
||||||
|
"\x1b[1mhello \x1b[Kworld",
|
||||||
|
"hello \x1b[34;45;1mworld",
|
||||||
|
"hello \x1b[34;45;1mwor\x1b[34;45;1mld",
|
||||||
|
"hello \x1b[34;45;1mwor\x1b[0mld",
|
||||||
|
"hello \x1b[34;48;5;233;1mwo\x1b[38;5;161mr\x1b[0ml\x1b[38;5;161md",
|
||||||
|
"hello \x1b[38;5;38;48;5;48;1mwor\x1b[38;5;48;48;5;38ml\x1b[0md",
|
||||||
|
"hello \x1b[32;1mworld",
|
||||||
|
"hello world",
|
||||||
|
"hello \x1b[0;38;5;200;48;5;100mworld",
|
||||||
|
ansiBenchmarkString,
|
||||||
|
}
|
||||||
|
|
||||||
|
replacementBytes := [...]rune{'\x0e', '\x0f', '\x1b', '\x08'}
|
||||||
|
|
||||||
|
modifyString := func(s string, rr *rand.Rand) string {
|
||||||
|
n := rr.Intn(len(s))
|
||||||
|
b := []rune(s)
|
||||||
|
for ; n >= 0 && len(b) != 0; n-- {
|
||||||
|
i := rr.Intn(len(b))
|
||||||
|
switch x := rr.Intn(4); x {
|
||||||
|
case 0:
|
||||||
|
b = append(b[:i], b[i+1:]...)
|
||||||
|
case 1:
|
||||||
|
j := rr.Intn(len(replacementBytes) - 1)
|
||||||
|
b[i] = replacementBytes[j]
|
||||||
|
case 2:
|
||||||
|
x := rune(rr.Intn(utf8.MaxRune))
|
||||||
|
for !utf8.ValidRune(x) {
|
||||||
|
x = rune(rr.Intn(utf8.MaxRune))
|
||||||
|
}
|
||||||
|
b[i] = x
|
||||||
|
case 3:
|
||||||
|
b[i] = rune(rr.Intn(utf8.MaxRune)) // potentially invalid
|
||||||
|
default:
|
||||||
|
t.Fatalf("unsupported value: %d", x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
rr := rand.New(rand.NewSource(1))
|
||||||
|
for _, s := range testStrs {
|
||||||
|
for i := 1_000; i >= 0; i-- {
|
||||||
|
testParserReference(t, modifyString(s, rr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNextAnsiEscapeSequence_Fuzz_Random(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("short test")
|
||||||
|
}
|
||||||
|
|
||||||
|
randomString := func(rr *rand.Rand) string {
|
||||||
|
numChars := rand.Intn(50)
|
||||||
|
codePoints := make([]rune, numChars)
|
||||||
|
for i := 0; i < len(codePoints); i++ {
|
||||||
|
var r rune
|
||||||
|
for n := 0; n < 1000; n++ {
|
||||||
|
r = rune(rr.Intn(utf8.MaxRune))
|
||||||
|
// Allow 10% of runes to be invalid
|
||||||
|
if utf8.ValidRune(r) || rr.Float64() < 0.10 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
codePoints[i] = r
|
||||||
|
}
|
||||||
|
return string(codePoints)
|
||||||
|
}
|
||||||
|
|
||||||
|
rr := rand.New(rand.NewSource(1))
|
||||||
|
for i := 0; i < 100_000; i++ {
|
||||||
|
testParserReference(t, randomString(rr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestExtractColor(t *testing.T) {
|
func TestExtractColor(t *testing.T) {
|
||||||
assert := func(offset ansiOffset, b int32, e int32, fg tui.Color, bg tui.Color, bold bool) {
|
assert := func(offset ansiOffset, b int32, e int32, fg tui.Color, bg tui.Color, bold bool) {
|
||||||
var attr tui.Attr
|
var attr tui.Attr
|
||||||
@@ -185,3 +363,64 @@ func TestAnsiCodeStringConversion(t *testing.T) {
|
|||||||
&ansiState{attr: tui.Dim | tui.Italic, fg: 1, bg: 1},
|
&ansiState{attr: tui.Dim | tui.Italic, fg: 1, bg: 1},
|
||||||
"\x1b[2;3;7;38;2;10;20;30;48;5;100m")
|
"\x1b[2;3;7;38;2;10;20;30;48;5;100m")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseAnsiCode(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
In, Exp string
|
||||||
|
N int
|
||||||
|
}{
|
||||||
|
{"123", "", 123},
|
||||||
|
{"1a", "", -1},
|
||||||
|
{"1a;12", "12", -1},
|
||||||
|
{"12;a", "a", 12},
|
||||||
|
{"-2", "", -1},
|
||||||
|
}
|
||||||
|
for _, x := range tests {
|
||||||
|
n, s := parseAnsiCode(x.In)
|
||||||
|
if n != x.N || s != x.Exp {
|
||||||
|
t.Fatalf("%q: got: (%d %q) want: (%d %q)", x.In, n, s, x.N, x.Exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// kernel/bpf/preload/iterators/README
|
||||||
|
const ansiBenchmarkString = "\x1b[38;5;81m\x1b[01;31m\x1b[Kkernel/\x1b[0m\x1b[38;5;81mbpf/" +
|
||||||
|
"\x1b[0m\x1b[38;5;81mpreload/\x1b[0m\x1b[38;5;81miterators/" +
|
||||||
|
"\x1b[0m\x1b[38;5;149mMakefile\x1b[m\x1b[K\x1b[0m"
|
||||||
|
|
||||||
|
func BenchmarkNextAnsiEscapeSequence(b *testing.B) {
|
||||||
|
b.SetBytes(int64(len(ansiBenchmarkString)))
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
s := ansiBenchmarkString
|
||||||
|
for {
|
||||||
|
_, o := nextAnsiEscapeSequence(s)
|
||||||
|
if o == -1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
s = s[o:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Baseline test to compare the speed of nextAnsiEscapeSequence() to the
|
||||||
|
// previously used regex based implementation.
|
||||||
|
func BenchmarkNextAnsiEscapeSequence_Regex(b *testing.B) {
|
||||||
|
b.SetBytes(int64(len(ansiBenchmarkString)))
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
s := ansiBenchmarkString
|
||||||
|
for {
|
||||||
|
a := ansiRegexRefence.FindStringIndex(s)
|
||||||
|
if len(a) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
s = s[a[1]:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkExtractColor(b *testing.B) {
|
||||||
|
b.SetBytes(int64(len(ansiBenchmarkString)))
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
extractColor(ansiBenchmarkString, nil, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -6,8 +6,8 @@ func TestChunkCache(t *testing.T) {
|
|||||||
cache := NewChunkCache()
|
cache := NewChunkCache()
|
||||||
chunk1p := &Chunk{}
|
chunk1p := &Chunk{}
|
||||||
chunk2p := &Chunk{count: chunkSize}
|
chunk2p := &Chunk{count: chunkSize}
|
||||||
items1 := []Result{Result{}}
|
items1 := []Result{{}}
|
||||||
items2 := []Result{Result{}, Result{}}
|
items2 := []Result{{}, {}}
|
||||||
cache.Add(chunk1p, "foo", items1)
|
cache.Add(chunk1p, "foo", items1)
|
||||||
cache.Add(chunk2p, "foo", items1)
|
cache.Add(chunk2p, "foo", items1)
|
||||||
cache.Add(chunk2p, "bar", items2)
|
cache.Add(chunk2p, "bar", items2)
|
||||||
|
@@ -73,6 +73,7 @@ const (
|
|||||||
EvtSearchFin
|
EvtSearchFin
|
||||||
EvtHeader
|
EvtHeader
|
||||||
EvtReady
|
EvtReady
|
||||||
|
EvtQuit
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
16
src/core.go
16
src/core.go
@@ -3,7 +3,7 @@ Package fzf implements fzf, a command-line fuzzy finder.
|
|||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2017 Junegunn Choi
|
Copyright (c) 2013-2021 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -237,14 +237,16 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
go reader.restart(command)
|
go reader.restart(command)
|
||||||
}
|
}
|
||||||
eventBox.Watch(EvtReadNew)
|
eventBox.Watch(EvtReadNew)
|
||||||
|
query := []rune{}
|
||||||
for {
|
for {
|
||||||
delay := true
|
delay := true
|
||||||
ticks++
|
ticks++
|
||||||
input := func() []rune {
|
input := func() []rune {
|
||||||
if opts.Phony {
|
paused, input := terminal.Input()
|
||||||
return []rune{}
|
if !paused {
|
||||||
|
query = input
|
||||||
}
|
}
|
||||||
return []rune(terminal.Input())
|
return query
|
||||||
}
|
}
|
||||||
eventBox.Wait(func(events *util.Events) {
|
eventBox.Wait(func(events *util.Events) {
|
||||||
if _, fin := (*events)[EvtReadFin]; fin {
|
if _, fin := (*events)[EvtReadFin]; fin {
|
||||||
@@ -252,7 +254,11 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
}
|
}
|
||||||
for evt, value := range *events {
|
for evt, value := range *events {
|
||||||
switch evt {
|
switch evt {
|
||||||
|
case EvtQuit:
|
||||||
|
if reading {
|
||||||
|
reader.terminate()
|
||||||
|
}
|
||||||
|
os.Exit(value.(int))
|
||||||
case EvtReadNew, EvtReadFin:
|
case EvtReadNew, EvtReadFin:
|
||||||
if evt == EvtReadFin && nextCommand != nil {
|
if evt == EvtReadFin && nextCommand != nil {
|
||||||
restart(*nextCommand)
|
restart(*nextCommand)
|
||||||
|
@@ -3,7 +3,6 @@ package fzf
|
|||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -12,16 +11,12 @@ func TestHistory(t *testing.T) {
|
|||||||
maxHistory := 50
|
maxHistory := 50
|
||||||
|
|
||||||
// Invalid arguments
|
// Invalid arguments
|
||||||
user, _ := user.Current()
|
|
||||||
var paths []string
|
var paths []string
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
// GOPATH should exist, so we shouldn't be able to override it
|
// GOPATH should exist, so we shouldn't be able to override it
|
||||||
paths = []string{os.Getenv("GOPATH")}
|
paths = []string{os.Getenv("GOPATH")}
|
||||||
} else {
|
} else {
|
||||||
paths = []string{"/etc", "/proc"}
|
paths = []string{"/etc", "/proc"}
|
||||||
if user.Name != "root" {
|
|
||||||
paths = append(paths, "/etc/sudoers")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
|
@@ -106,7 +106,6 @@ func (mg *Merger) mergedGet(idx int) Result {
|
|||||||
minIdx = listIdx
|
minIdx = listIdx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mg.cursors[listIdx] = cursor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if minIdx >= 0 {
|
if minIdx >= 0 {
|
||||||
|
317
src/options.go
317
src/options.go
@@ -7,7 +7,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/algo"
|
"github.com/junegunn/fzf/src/algo"
|
||||||
"github.com/junegunn/fzf/src/tui"
|
"github.com/junegunn/fzf/src/tui"
|
||||||
@@ -34,7 +33,7 @@ const usage = `usage: fzf [options]
|
|||||||
-d, --delimiter=STR Field delimiter regex (default: AWK-style)
|
-d, --delimiter=STR Field delimiter regex (default: AWK-style)
|
||||||
+s, --no-sort Do not sort the result
|
+s, --no-sort Do not sort the result
|
||||||
--tac Reverse the order of the input
|
--tac Reverse the order of the input
|
||||||
--phony Do not perform search
|
--disabled Do not perform search
|
||||||
--tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
|
--tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
|
||||||
when the scores are tied [length|begin|end|index]
|
when the scores are tied [length|begin|end|index]
|
||||||
(default: length)
|
(default: length)
|
||||||
@@ -85,7 +84,7 @@ const usage = `usage: fzf [options]
|
|||||||
[up|down|left|right][:SIZE[%]]
|
[up|down|left|right][:SIZE[%]]
|
||||||
[:[no]wrap][:[no]cycle][:[no]follow][:[no]hidden]
|
[:[no]wrap][:[no]cycle][:[no]follow][:[no]hidden]
|
||||||
[:rounded|sharp|noborder]
|
[:rounded|sharp|noborder]
|
||||||
[:+SCROLL[-OFFSET]]
|
[:+SCROLL[OFFSETS][/DENOM]][:~HEADER_LINES]
|
||||||
[:default]
|
[:default]
|
||||||
|
|
||||||
Scripting
|
Scripting
|
||||||
@@ -162,15 +161,16 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type previewOpts struct {
|
type previewOpts struct {
|
||||||
command string
|
command string
|
||||||
position windowPosition
|
position windowPosition
|
||||||
size sizeSpec
|
size sizeSpec
|
||||||
scroll string
|
scroll string
|
||||||
hidden bool
|
hidden bool
|
||||||
wrap bool
|
wrap bool
|
||||||
cycle bool
|
cycle bool
|
||||||
follow bool
|
follow bool
|
||||||
border tui.BorderShape
|
border tui.BorderShape
|
||||||
|
headerLines int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Options stores the values of command-line options
|
// Options stores the values of command-line options
|
||||||
@@ -211,8 +211,8 @@ type Options struct {
|
|||||||
Exit0 bool
|
Exit0 bool
|
||||||
Filter *string
|
Filter *string
|
||||||
ToggleSort bool
|
ToggleSort bool
|
||||||
Expect map[int]string
|
Expect map[tui.Event]string
|
||||||
Keymap map[int][]action
|
Keymap map[tui.Event][]action
|
||||||
Preview previewOpts
|
Preview previewOpts
|
||||||
PrintQuery bool
|
PrintQuery bool
|
||||||
ReadZero bool
|
ReadZero bool
|
||||||
@@ -232,7 +232,7 @@ type Options struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func defaultPreviewOpts(command string) previewOpts {
|
func defaultPreviewOpts(command string) previewOpts {
|
||||||
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, tui.BorderRounded}
|
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, tui.BorderRounded, 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultOptions() *Options {
|
func defaultOptions() *Options {
|
||||||
@@ -272,8 +272,8 @@ func defaultOptions() *Options {
|
|||||||
Exit0: false,
|
Exit0: false,
|
||||||
Filter: nil,
|
Filter: nil,
|
||||||
ToggleSort: false,
|
ToggleSort: false,
|
||||||
Expect: make(map[int]string),
|
Expect: make(map[tui.Event]string),
|
||||||
Keymap: make(map[int][]action),
|
Keymap: make(map[tui.Event][]action),
|
||||||
Preview: defaultPreviewOpts(""),
|
Preview: defaultPreviewOpts(""),
|
||||||
PrintQuery: false,
|
PrintQuery: false,
|
||||||
ReadZero: false,
|
ReadZero: false,
|
||||||
@@ -445,134 +445,141 @@ func parseBorder(str string, optional bool) tui.BorderShape {
|
|||||||
return tui.BorderNone
|
return tui.BorderNone
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseKeyChords(str string, message string) map[int]string {
|
func parseKeyChords(str string, message string) map[tui.Event]string {
|
||||||
if len(str) == 0 {
|
if len(str) == 0 {
|
||||||
errorExit(message)
|
errorExit(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
str = regexp.MustCompile("(?i)(alt-),").ReplaceAllString(str, "$1"+string([]rune{escapedComma}))
|
||||||
tokens := strings.Split(str, ",")
|
tokens := strings.Split(str, ",")
|
||||||
if str == "," || strings.HasPrefix(str, ",,") || strings.HasSuffix(str, ",,") || strings.Contains(str, ",,,") {
|
if str == "," || strings.HasPrefix(str, ",,") || strings.HasSuffix(str, ",,") || strings.Contains(str, ",,,") {
|
||||||
tokens = append(tokens, ",")
|
tokens = append(tokens, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
chords := make(map[int]string)
|
chords := make(map[tui.Event]string)
|
||||||
for _, key := range tokens {
|
for _, key := range tokens {
|
||||||
if len(key) == 0 {
|
if len(key) == 0 {
|
||||||
continue // ignore
|
continue // ignore
|
||||||
}
|
}
|
||||||
|
key = strings.ReplaceAll(key, string([]rune{escapedComma}), ",")
|
||||||
lkey := strings.ToLower(key)
|
lkey := strings.ToLower(key)
|
||||||
chord := 0
|
add := func(e tui.EventType) {
|
||||||
|
chords[e.AsEvent()] = key
|
||||||
|
}
|
||||||
switch lkey {
|
switch lkey {
|
||||||
case "up":
|
case "up":
|
||||||
chord = tui.Up
|
add(tui.Up)
|
||||||
case "down":
|
case "down":
|
||||||
chord = tui.Down
|
add(tui.Down)
|
||||||
case "left":
|
case "left":
|
||||||
chord = tui.Left
|
add(tui.Left)
|
||||||
case "right":
|
case "right":
|
||||||
chord = tui.Right
|
add(tui.Right)
|
||||||
case "enter", "return":
|
case "enter", "return":
|
||||||
chord = tui.CtrlM
|
add(tui.CtrlM)
|
||||||
case "space":
|
case "space":
|
||||||
chord = tui.AltZ + int(' ')
|
chords[tui.Key(' ')] = key
|
||||||
case "bspace", "bs":
|
case "bspace", "bs":
|
||||||
chord = tui.BSpace
|
add(tui.BSpace)
|
||||||
case "ctrl-space":
|
case "ctrl-space":
|
||||||
chord = tui.CtrlSpace
|
add(tui.CtrlSpace)
|
||||||
case "ctrl-^", "ctrl-6":
|
case "ctrl-^", "ctrl-6":
|
||||||
chord = tui.CtrlCaret
|
add(tui.CtrlCaret)
|
||||||
case "ctrl-/", "ctrl-_":
|
case "ctrl-/", "ctrl-_":
|
||||||
chord = tui.CtrlSlash
|
add(tui.CtrlSlash)
|
||||||
case "ctrl-\\":
|
case "ctrl-\\":
|
||||||
chord = tui.CtrlBackSlash
|
add(tui.CtrlBackSlash)
|
||||||
case "ctrl-]":
|
case "ctrl-]":
|
||||||
chord = tui.CtrlRightBracket
|
add(tui.CtrlRightBracket)
|
||||||
case "change":
|
case "change":
|
||||||
chord = tui.Change
|
add(tui.Change)
|
||||||
case "backward-eof":
|
case "backward-eof":
|
||||||
chord = tui.BackwardEOF
|
add(tui.BackwardEOF)
|
||||||
case "alt-enter", "alt-return":
|
case "alt-enter", "alt-return":
|
||||||
chord = tui.CtrlAltM
|
chords[tui.CtrlAltKey('m')] = key
|
||||||
case "alt-space":
|
case "alt-space":
|
||||||
chord = tui.AltSpace
|
chords[tui.AltKey(' ')] = key
|
||||||
case "alt-/":
|
|
||||||
chord = tui.AltSlash
|
|
||||||
case "alt-bs", "alt-bspace":
|
case "alt-bs", "alt-bspace":
|
||||||
chord = tui.AltBS
|
add(tui.AltBS)
|
||||||
case "alt-up":
|
case "alt-up":
|
||||||
chord = tui.AltUp
|
add(tui.AltUp)
|
||||||
case "alt-down":
|
case "alt-down":
|
||||||
chord = tui.AltDown
|
add(tui.AltDown)
|
||||||
case "alt-left":
|
case "alt-left":
|
||||||
chord = tui.AltLeft
|
add(tui.AltLeft)
|
||||||
case "alt-right":
|
case "alt-right":
|
||||||
chord = tui.AltRight
|
add(tui.AltRight)
|
||||||
case "tab":
|
case "tab":
|
||||||
chord = tui.Tab
|
add(tui.Tab)
|
||||||
case "btab", "shift-tab":
|
case "btab", "shift-tab":
|
||||||
chord = tui.BTab
|
add(tui.BTab)
|
||||||
case "esc":
|
case "esc":
|
||||||
chord = tui.ESC
|
add(tui.ESC)
|
||||||
case "del":
|
case "del":
|
||||||
chord = tui.Del
|
add(tui.Del)
|
||||||
case "home":
|
case "home":
|
||||||
chord = tui.Home
|
add(tui.Home)
|
||||||
case "end":
|
case "end":
|
||||||
chord = tui.End
|
add(tui.End)
|
||||||
case "insert":
|
case "insert":
|
||||||
chord = tui.Insert
|
add(tui.Insert)
|
||||||
case "pgup", "page-up":
|
case "pgup", "page-up":
|
||||||
chord = tui.PgUp
|
add(tui.PgUp)
|
||||||
case "pgdn", "page-down":
|
case "pgdn", "page-down":
|
||||||
chord = tui.PgDn
|
add(tui.PgDn)
|
||||||
case "alt-shift-up", "shift-alt-up":
|
case "alt-shift-up", "shift-alt-up":
|
||||||
chord = tui.AltSUp
|
add(tui.AltSUp)
|
||||||
case "alt-shift-down", "shift-alt-down":
|
case "alt-shift-down", "shift-alt-down":
|
||||||
chord = tui.AltSDown
|
add(tui.AltSDown)
|
||||||
case "alt-shift-left", "shift-alt-left":
|
case "alt-shift-left", "shift-alt-left":
|
||||||
chord = tui.AltSLeft
|
add(tui.AltSLeft)
|
||||||
case "alt-shift-right", "shift-alt-right":
|
case "alt-shift-right", "shift-alt-right":
|
||||||
chord = tui.AltSRight
|
add(tui.AltSRight)
|
||||||
case "shift-up":
|
case "shift-up":
|
||||||
chord = tui.SUp
|
add(tui.SUp)
|
||||||
case "shift-down":
|
case "shift-down":
|
||||||
chord = tui.SDown
|
add(tui.SDown)
|
||||||
case "shift-left":
|
case "shift-left":
|
||||||
chord = tui.SLeft
|
add(tui.SLeft)
|
||||||
case "shift-right":
|
case "shift-right":
|
||||||
chord = tui.SRight
|
add(tui.SRight)
|
||||||
case "left-click":
|
case "left-click":
|
||||||
chord = tui.LeftClick
|
add(tui.LeftClick)
|
||||||
case "right-click":
|
case "right-click":
|
||||||
chord = tui.RightClick
|
add(tui.RightClick)
|
||||||
case "double-click":
|
case "double-click":
|
||||||
chord = tui.DoubleClick
|
add(tui.DoubleClick)
|
||||||
case "f10":
|
case "f10":
|
||||||
chord = tui.F10
|
add(tui.F10)
|
||||||
case "f11":
|
case "f11":
|
||||||
chord = tui.F11
|
add(tui.F11)
|
||||||
case "f12":
|
case "f12":
|
||||||
chord = tui.F12
|
add(tui.F12)
|
||||||
default:
|
default:
|
||||||
|
runes := []rune(key)
|
||||||
if len(key) == 10 && strings.HasPrefix(lkey, "ctrl-alt-") && isAlphabet(lkey[9]) {
|
if len(key) == 10 && strings.HasPrefix(lkey, "ctrl-alt-") && isAlphabet(lkey[9]) {
|
||||||
chord = tui.CtrlAltA + int(lkey[9]) - 'a'
|
chords[tui.CtrlAltKey(rune(key[9]))] = key
|
||||||
} else if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
|
} else if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
|
||||||
chord = tui.CtrlA + int(lkey[5]) - 'a'
|
add(tui.EventType(tui.CtrlA.Int() + int(lkey[5]) - 'a'))
|
||||||
} else if len(key) == 5 && strings.HasPrefix(lkey, "alt-") && isAlphabet(lkey[4]) {
|
} else if len(runes) == 5 && strings.HasPrefix(lkey, "alt-") {
|
||||||
chord = tui.AltA + int(lkey[4]) - 'a'
|
r := runes[4]
|
||||||
} else if len(key) == 5 && strings.HasPrefix(lkey, "alt-") && isNumeric(lkey[4]) {
|
switch r {
|
||||||
chord = tui.Alt0 + int(lkey[4]) - '0'
|
case escapedColon:
|
||||||
|
r = ':'
|
||||||
|
case escapedComma:
|
||||||
|
r = ','
|
||||||
|
case escapedPlus:
|
||||||
|
r = '+'
|
||||||
|
}
|
||||||
|
chords[tui.AltKey(r)] = key
|
||||||
} else if len(key) == 2 && strings.HasPrefix(lkey, "f") && key[1] >= '1' && key[1] <= '9' {
|
} else if len(key) == 2 && strings.HasPrefix(lkey, "f") && key[1] >= '1' && key[1] <= '9' {
|
||||||
chord = tui.F1 + int(key[1]) - '1'
|
add(tui.EventType(tui.F1.Int() + int(key[1]) - '1'))
|
||||||
} else if utf8.RuneCountInString(key) == 1 {
|
} else if len(runes) == 1 {
|
||||||
chord = tui.AltZ + int([]rune(key)[0])
|
chords[tui.Key(runes[0])] = key
|
||||||
} else {
|
} else {
|
||||||
errorExit("unsupported key: " + key)
|
errorExit("unsupported key: " + key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if chord > 0 {
|
|
||||||
chords[chord] = key
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return chords
|
return chords
|
||||||
}
|
}
|
||||||
@@ -644,71 +651,74 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
|
|||||||
fail()
|
fail()
|
||||||
}
|
}
|
||||||
|
|
||||||
cattr := tui.NewColorAttr()
|
mergeAttr := func(cattr *tui.ColorAttr) {
|
||||||
for _, component := range components[1:] {
|
for _, component := range components[1:] {
|
||||||
switch component {
|
switch component {
|
||||||
case "regular":
|
case "regular":
|
||||||
cattr.Attr = tui.AttrRegular
|
cattr.Attr = tui.AttrRegular
|
||||||
case "bold", "strong":
|
case "bold", "strong":
|
||||||
cattr.Attr |= tui.Bold
|
cattr.Attr |= tui.Bold
|
||||||
case "dim":
|
case "dim":
|
||||||
cattr.Attr |= tui.Dim
|
cattr.Attr |= tui.Dim
|
||||||
case "italic":
|
case "italic":
|
||||||
cattr.Attr |= tui.Italic
|
cattr.Attr |= tui.Italic
|
||||||
case "underline":
|
case "underline":
|
||||||
cattr.Attr |= tui.Underline
|
cattr.Attr |= tui.Underline
|
||||||
case "blink":
|
case "blink":
|
||||||
cattr.Attr |= tui.Blink
|
cattr.Attr |= tui.Blink
|
||||||
case "reverse":
|
case "reverse":
|
||||||
cattr.Attr |= tui.Reverse
|
cattr.Attr |= tui.Reverse
|
||||||
case "":
|
case "":
|
||||||
default:
|
default:
|
||||||
if rrggbb.MatchString(component) {
|
if rrggbb.MatchString(component) {
|
||||||
cattr.Color = tui.HexToColor(component)
|
cattr.Color = tui.HexToColor(component)
|
||||||
} else {
|
} else {
|
||||||
ansi32, err := strconv.Atoi(component)
|
ansi32, err := strconv.Atoi(component)
|
||||||
if err != nil || ansi32 < -1 || ansi32 > 255 {
|
if err != nil || ansi32 < -1 || ansi32 > 255 {
|
||||||
fail()
|
fail()
|
||||||
|
}
|
||||||
|
cattr.Color = tui.Color(ansi32)
|
||||||
}
|
}
|
||||||
cattr.Color = tui.Color(ansi32)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch components[0] {
|
switch components[0] {
|
||||||
case "input":
|
case "query", "input":
|
||||||
theme.Input = cattr
|
mergeAttr(&theme.Input)
|
||||||
|
case "disabled":
|
||||||
|
mergeAttr(&theme.Disabled)
|
||||||
case "fg":
|
case "fg":
|
||||||
theme.Fg = cattr
|
mergeAttr(&theme.Fg)
|
||||||
case "bg":
|
case "bg":
|
||||||
theme.Bg = cattr
|
mergeAttr(&theme.Bg)
|
||||||
case "preview-fg":
|
case "preview-fg":
|
||||||
theme.PreviewFg = cattr
|
mergeAttr(&theme.PreviewFg)
|
||||||
case "preview-bg":
|
case "preview-bg":
|
||||||
theme.PreviewBg = cattr
|
mergeAttr(&theme.PreviewBg)
|
||||||
case "fg+":
|
case "fg+":
|
||||||
theme.Current = cattr
|
mergeAttr(&theme.Current)
|
||||||
case "bg+":
|
case "bg+":
|
||||||
theme.DarkBg = cattr
|
mergeAttr(&theme.DarkBg)
|
||||||
case "gutter":
|
case "gutter":
|
||||||
theme.Gutter = cattr
|
mergeAttr(&theme.Gutter)
|
||||||
case "hl":
|
case "hl":
|
||||||
theme.Match = cattr
|
mergeAttr(&theme.Match)
|
||||||
case "hl+":
|
case "hl+":
|
||||||
theme.CurrentMatch = cattr
|
mergeAttr(&theme.CurrentMatch)
|
||||||
case "border":
|
case "border":
|
||||||
theme.Border = cattr
|
mergeAttr(&theme.Border)
|
||||||
case "prompt":
|
case "prompt":
|
||||||
theme.Prompt = cattr
|
mergeAttr(&theme.Prompt)
|
||||||
case "spinner":
|
case "spinner":
|
||||||
theme.Spinner = cattr
|
mergeAttr(&theme.Spinner)
|
||||||
case "info":
|
case "info":
|
||||||
theme.Info = cattr
|
mergeAttr(&theme.Info)
|
||||||
case "pointer":
|
case "pointer":
|
||||||
theme.Cursor = cattr
|
mergeAttr(&theme.Cursor)
|
||||||
case "marker":
|
case "marker":
|
||||||
theme.Selected = cattr
|
mergeAttr(&theme.Selected)
|
||||||
case "header":
|
case "header":
|
||||||
theme.Header = cattr
|
mergeAttr(&theme.Header)
|
||||||
default:
|
default:
|
||||||
fail()
|
fail()
|
||||||
}
|
}
|
||||||
@@ -719,11 +729,11 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
|
|||||||
|
|
||||||
var executeRegexp *regexp.Regexp
|
var executeRegexp *regexp.Regexp
|
||||||
|
|
||||||
func firstKey(keymap map[int]string) int {
|
func firstKey(keymap map[tui.Event]string) tui.Event {
|
||||||
for k := range keymap {
|
for k := range keymap {
|
||||||
return k
|
return k
|
||||||
}
|
}
|
||||||
return 0
|
return tui.EventType(0).AsEvent()
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -739,7 +749,7 @@ func init() {
|
|||||||
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseKeymap(keymap map[int][]action, str string) {
|
func parseKeymap(keymap map[tui.Event][]action, str string) {
|
||||||
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
||||||
symbol := ":"
|
symbol := ":"
|
||||||
if strings.HasPrefix(src, "+") {
|
if strings.HasPrefix(src, "+") {
|
||||||
@@ -775,13 +785,13 @@ func parseKeymap(keymap map[int][]action, str string) {
|
|||||||
if len(pair) < 2 {
|
if len(pair) < 2 {
|
||||||
errorExit("bind action not specified: " + origPairStr)
|
errorExit("bind action not specified: " + origPairStr)
|
||||||
}
|
}
|
||||||
var key int
|
var key tui.Event
|
||||||
if len(pair[0]) == 1 && pair[0][0] == escapedColon {
|
if len(pair[0]) == 1 && pair[0][0] == escapedColon {
|
||||||
key = ':' + tui.AltZ
|
key = tui.Key(':')
|
||||||
} else if len(pair[0]) == 1 && pair[0][0] == escapedComma {
|
} else if len(pair[0]) == 1 && pair[0][0] == escapedComma {
|
||||||
key = ',' + tui.AltZ
|
key = tui.Key(',')
|
||||||
} else if len(pair[0]) == 1 && pair[0][0] == escapedPlus {
|
} else if len(pair[0]) == 1 && pair[0][0] == escapedPlus {
|
||||||
key = '+' + tui.AltZ
|
key = tui.Key('+')
|
||||||
} else {
|
} else {
|
||||||
keys := parseKeyChords(pair[0], "key name required")
|
keys := parseKeyChords(pair[0], "key name required")
|
||||||
key = firstKey(keys)
|
key = firstKey(keys)
|
||||||
@@ -830,6 +840,8 @@ func parseKeymap(keymap map[int][]action, str string) {
|
|||||||
appendAction(actDeleteChar)
|
appendAction(actDeleteChar)
|
||||||
case "delete-char/eof":
|
case "delete-char/eof":
|
||||||
appendAction(actDeleteCharEOF)
|
appendAction(actDeleteCharEOF)
|
||||||
|
case "deselect":
|
||||||
|
appendAction(actDeselect)
|
||||||
case "end-of-line":
|
case "end-of-line":
|
||||||
appendAction(actEndOfLine)
|
appendAction(actEndOfLine)
|
||||||
case "cancel":
|
case "cancel":
|
||||||
@@ -868,18 +880,26 @@ func parseKeymap(keymap map[int][]action, str string) {
|
|||||||
appendAction(actToggleOut)
|
appendAction(actToggleOut)
|
||||||
case "toggle-all":
|
case "toggle-all":
|
||||||
appendAction(actToggleAll)
|
appendAction(actToggleAll)
|
||||||
|
case "toggle-search":
|
||||||
|
appendAction(actToggleSearch)
|
||||||
|
case "select":
|
||||||
|
appendAction(actSelect)
|
||||||
case "select-all":
|
case "select-all":
|
||||||
appendAction(actSelectAll)
|
appendAction(actSelectAll)
|
||||||
case "deselect-all":
|
case "deselect-all":
|
||||||
appendAction(actDeselectAll)
|
appendAction(actDeselectAll)
|
||||||
|
case "close":
|
||||||
|
appendAction(actClose)
|
||||||
case "toggle":
|
case "toggle":
|
||||||
appendAction(actToggle)
|
appendAction(actToggle)
|
||||||
case "down":
|
case "down":
|
||||||
appendAction(actDown)
|
appendAction(actDown)
|
||||||
case "up":
|
case "up":
|
||||||
appendAction(actUp)
|
appendAction(actUp)
|
||||||
case "top":
|
case "first", "top":
|
||||||
appendAction(actTop)
|
appendAction(actFirst)
|
||||||
|
case "last":
|
||||||
|
appendAction(actLast)
|
||||||
case "page-up":
|
case "page-up":
|
||||||
appendAction(actPageUp)
|
appendAction(actPageUp)
|
||||||
case "page-down":
|
case "page-down":
|
||||||
@@ -898,6 +918,10 @@ func parseKeymap(keymap map[int][]action, str string) {
|
|||||||
appendAction(actTogglePreviewWrap)
|
appendAction(actTogglePreviewWrap)
|
||||||
case "toggle-sort":
|
case "toggle-sort":
|
||||||
appendAction(actToggleSort)
|
appendAction(actToggleSort)
|
||||||
|
case "preview-top":
|
||||||
|
appendAction(actPreviewTop)
|
||||||
|
case "preview-bottom":
|
||||||
|
appendAction(actPreviewBottom)
|
||||||
case "preview-up":
|
case "preview-up":
|
||||||
appendAction(actPreviewUp)
|
appendAction(actPreviewUp)
|
||||||
case "preview-down":
|
case "preview-down":
|
||||||
@@ -910,6 +934,10 @@ func parseKeymap(keymap map[int][]action, str string) {
|
|||||||
appendAction(actPreviewHalfPageUp)
|
appendAction(actPreviewHalfPageUp)
|
||||||
case "preview-half-page-down":
|
case "preview-half-page-down":
|
||||||
appendAction(actPreviewHalfPageDown)
|
appendAction(actPreviewHalfPageDown)
|
||||||
|
case "enable-search":
|
||||||
|
appendAction(actEnableSearch)
|
||||||
|
case "disable-search":
|
||||||
|
appendAction(actDisableSearch)
|
||||||
default:
|
default:
|
||||||
t := isExecuteAction(specLower)
|
t := isExecuteAction(specLower)
|
||||||
if t == actIgnore {
|
if t == actIgnore {
|
||||||
@@ -978,7 +1006,7 @@ func isExecuteAction(str string) actionType {
|
|||||||
return actIgnore
|
return actIgnore
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseToggleSort(keymap map[int][]action, str string) {
|
func parseToggleSort(keymap map[tui.Event][]action, str string) {
|
||||||
keys := parseKeyChords(str, "key name required")
|
keys := parseKeyChords(str, "key name required")
|
||||||
if len(keys) != 1 {
|
if len(keys) != 1 {
|
||||||
errorExit("multiple keys specified")
|
errorExit("multiple keys specified")
|
||||||
@@ -1050,7 +1078,8 @@ func parseInfoStyle(str string) infoStyle {
|
|||||||
func parsePreviewWindow(opts *previewOpts, input string) {
|
func parsePreviewWindow(opts *previewOpts, input string) {
|
||||||
tokens := strings.Split(input, ":")
|
tokens := strings.Split(input, ":")
|
||||||
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
|
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
|
||||||
offsetRegex := regexp.MustCompile("^\\+([0-9]+|{-?[0-9]+})(-[0-9]+|-/[1-9][0-9]*)?$")
|
offsetRegex := regexp.MustCompile(`^(\+{-?[0-9]+})?([+-][0-9]+)*(-?/[1-9][0-9]*)?$`)
|
||||||
|
headerRegex := regexp.MustCompile("^~(0|[1-9][0-9]*)$")
|
||||||
for _, token := range tokens {
|
for _, token := range tokens {
|
||||||
switch token {
|
switch token {
|
||||||
case "":
|
case "":
|
||||||
@@ -1087,10 +1116,12 @@ func parsePreviewWindow(opts *previewOpts, input string) {
|
|||||||
case "nofollow":
|
case "nofollow":
|
||||||
opts.follow = false
|
opts.follow = false
|
||||||
default:
|
default:
|
||||||
if sizeRegex.MatchString(token) {
|
if headerRegex.MatchString(token) {
|
||||||
|
opts.headerLines = atoi(token[1:])
|
||||||
|
} else if sizeRegex.MatchString(token) {
|
||||||
opts.size = parseSize(token, 99, "window size")
|
opts.size = parseSize(token, 99, "window size")
|
||||||
} else if offsetRegex.MatchString(token) {
|
} else if offsetRegex.MatchString(token) {
|
||||||
opts.scroll = token[1:]
|
opts.scroll = token
|
||||||
} else {
|
} else {
|
||||||
errorExit("invalid preview window option: " + token)
|
errorExit("invalid preview window option: " + token)
|
||||||
}
|
}
|
||||||
@@ -1185,10 +1216,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Expect[k] = v
|
opts.Expect[k] = v
|
||||||
}
|
}
|
||||||
case "--no-expect":
|
case "--no-expect":
|
||||||
opts.Expect = make(map[int]string)
|
opts.Expect = make(map[tui.Event]string)
|
||||||
case "--no-phony":
|
case "--enabled", "--no-phony":
|
||||||
opts.Phony = false
|
opts.Phony = false
|
||||||
case "--phony":
|
case "--disabled", "--phony":
|
||||||
opts.Phony = true
|
opts.Phony = true
|
||||||
case "--tiebreak":
|
case "--tiebreak":
|
||||||
opts.Criteria = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
|
opts.Criteria = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
|
||||||
@@ -1337,7 +1368,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Preview.command = ""
|
opts.Preview.command = ""
|
||||||
case "--preview-window":
|
case "--preview-window":
|
||||||
parsePreviewWindow(&opts.Preview,
|
parsePreviewWindow(&opts.Preview,
|
||||||
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]][:rounded|sharp|noborder][:wrap][:cycle][:hidden][:+SCROLL[-OFFSET]][:default]"))
|
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]][:rounded|sharp|noborder][:wrap][:cycle][:hidden][:+SCROLL[OFFSETS][/DENOM]][:~HEADER_LINES][:default]"))
|
||||||
case "--height":
|
case "--height":
|
||||||
opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]"))
|
opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]"))
|
||||||
case "--min-height":
|
case "--min-height":
|
||||||
@@ -1509,11 +1540,11 @@ func postProcessOptions(opts *Options) {
|
|||||||
}
|
}
|
||||||
// Default actions for CTRL-N / CTRL-P when --history is set
|
// Default actions for CTRL-N / CTRL-P when --history is set
|
||||||
if opts.History != nil {
|
if opts.History != nil {
|
||||||
if _, prs := opts.Keymap[tui.CtrlP]; !prs {
|
if _, prs := opts.Keymap[tui.CtrlP.AsEvent()]; !prs {
|
||||||
opts.Keymap[tui.CtrlP] = toActions(actPreviousHistory)
|
opts.Keymap[tui.CtrlP.AsEvent()] = toActions(actPreviousHistory)
|
||||||
}
|
}
|
||||||
if _, prs := opts.Keymap[tui.CtrlN]; !prs {
|
if _, prs := opts.Keymap[tui.CtrlN.AsEvent()]; !prs {
|
||||||
opts.Keymap[tui.CtrlN] = toActions(actNextHistory)
|
opts.Keymap[tui.CtrlN.AsEvent()] = toActions(actNextHistory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -102,7 +102,7 @@ func TestIrrelevantNth(t *testing.T) {
|
|||||||
t.Errorf("nth should be empty: %v", opts.Nth)
|
t.Errorf("nth should be empty: %v", opts.Nth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, words := range [][]string{[]string{"--nth", "..,3", "+x"}, []string{"--nth", "3,1..", "+x"}, []string{"--nth", "..-1,1", "+x"}} {
|
for _, words := range [][]string{{"--nth", "..,3", "+x"}, {"--nth", "3,1..", "+x"}, {"--nth", "..-1,1", "+x"}} {
|
||||||
{
|
{
|
||||||
opts := defaultOptions()
|
opts := defaultOptions()
|
||||||
parseOptions(opts, words)
|
parseOptions(opts, words)
|
||||||
@@ -125,26 +125,29 @@ func TestIrrelevantNth(t *testing.T) {
|
|||||||
|
|
||||||
func TestParseKeys(t *testing.T) {
|
func TestParseKeys(t *testing.T) {
|
||||||
pairs := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ctrl-alt-a,ALT-enter,alt-SPACE", "")
|
pairs := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ctrl-alt-a,ALT-enter,alt-SPACE", "")
|
||||||
check := func(i int, s string) {
|
checkEvent := func(e tui.Event, s string) {
|
||||||
if pairs[i] != s {
|
if pairs[e] != s {
|
||||||
t.Errorf("%s != %s", pairs[i], s)
|
t.Errorf("%s != %s", pairs[e], s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
check := func(et tui.EventType, s string) {
|
||||||
|
checkEvent(et.AsEvent(), s)
|
||||||
|
}
|
||||||
if len(pairs) != 12 {
|
if len(pairs) != 12 {
|
||||||
t.Error(12)
|
t.Error(12)
|
||||||
}
|
}
|
||||||
check(tui.CtrlZ, "ctrl-z")
|
check(tui.CtrlZ, "ctrl-z")
|
||||||
check(tui.AltZ, "alt-z")
|
|
||||||
check(tui.F2, "f2")
|
check(tui.F2, "f2")
|
||||||
check(tui.AltZ+'@', "@")
|
check(tui.CtrlG, "ctrl-G")
|
||||||
check(tui.AltA, "Alt-a")
|
checkEvent(tui.AltKey('z'), "alt-z")
|
||||||
check(tui.AltZ+'!', "!")
|
checkEvent(tui.Key('@'), "@")
|
||||||
check(tui.CtrlA+'g'-'a', "ctrl-G")
|
checkEvent(tui.AltKey('a'), "Alt-a")
|
||||||
check(tui.AltZ+'J', "J")
|
checkEvent(tui.Key('!'), "!")
|
||||||
check(tui.AltZ+'g', "g")
|
checkEvent(tui.Key('J'), "J")
|
||||||
check(tui.CtrlAltA, "ctrl-alt-a")
|
checkEvent(tui.Key('g'), "g")
|
||||||
check(tui.CtrlAltM, "ALT-enter")
|
checkEvent(tui.CtrlAltKey('a'), "ctrl-alt-a")
|
||||||
check(tui.AltSpace, "alt-SPACE")
|
checkEvent(tui.CtrlAltKey('m'), "ALT-enter")
|
||||||
|
checkEvent(tui.AltKey(' '), "alt-SPACE")
|
||||||
|
|
||||||
// Synonyms
|
// Synonyms
|
||||||
pairs = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
|
pairs = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
|
||||||
@@ -152,7 +155,7 @@ func TestParseKeys(t *testing.T) {
|
|||||||
t.Error(9)
|
t.Error(9)
|
||||||
}
|
}
|
||||||
check(tui.CtrlM, "Return")
|
check(tui.CtrlM, "Return")
|
||||||
check(tui.AltZ+' ', "space")
|
checkEvent(tui.Key(' '), "space")
|
||||||
check(tui.Tab, "tab")
|
check(tui.Tab, "tab")
|
||||||
check(tui.BTab, "btab")
|
check(tui.BTab, "btab")
|
||||||
check(tui.ESC, "esc")
|
check(tui.ESC, "esc")
|
||||||
@@ -184,93 +187,98 @@ func TestParseKeysWithComma(t *testing.T) {
|
|||||||
t.Errorf("%d != %d", a, b)
|
t.Errorf("%d != %d", a, b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
check := func(pairs map[int]string, i int, s string) {
|
check := func(pairs map[tui.Event]string, e tui.Event, s string) {
|
||||||
if pairs[i] != s {
|
if pairs[e] != s {
|
||||||
t.Errorf("%s != %s", pairs[i], s)
|
t.Errorf("%s != %s", pairs[e], s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pairs := parseKeyChords(",", "")
|
pairs := parseKeyChords(",", "")
|
||||||
checkN(len(pairs), 1)
|
checkN(len(pairs), 1)
|
||||||
check(pairs, tui.AltZ+',', ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs = parseKeyChords(",,a,b", "")
|
pairs = parseKeyChords(",,a,b", "")
|
||||||
checkN(len(pairs), 3)
|
checkN(len(pairs), 3)
|
||||||
check(pairs, tui.AltZ+'a', "a")
|
check(pairs, tui.Key('a'), "a")
|
||||||
check(pairs, tui.AltZ+'b', "b")
|
check(pairs, tui.Key('b'), "b")
|
||||||
check(pairs, tui.AltZ+',', ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs = parseKeyChords("a,b,,", "")
|
pairs = parseKeyChords("a,b,,", "")
|
||||||
checkN(len(pairs), 3)
|
checkN(len(pairs), 3)
|
||||||
check(pairs, tui.AltZ+'a', "a")
|
check(pairs, tui.Key('a'), "a")
|
||||||
check(pairs, tui.AltZ+'b', "b")
|
check(pairs, tui.Key('b'), "b")
|
||||||
check(pairs, tui.AltZ+',', ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs = parseKeyChords("a,,,b", "")
|
pairs = parseKeyChords("a,,,b", "")
|
||||||
checkN(len(pairs), 3)
|
checkN(len(pairs), 3)
|
||||||
check(pairs, tui.AltZ+'a', "a")
|
check(pairs, tui.Key('a'), "a")
|
||||||
check(pairs, tui.AltZ+'b', "b")
|
check(pairs, tui.Key('b'), "b")
|
||||||
check(pairs, tui.AltZ+',', ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs = parseKeyChords("a,,,b,c", "")
|
pairs = parseKeyChords("a,,,b,c", "")
|
||||||
checkN(len(pairs), 4)
|
checkN(len(pairs), 4)
|
||||||
check(pairs, tui.AltZ+'a', "a")
|
check(pairs, tui.Key('a'), "a")
|
||||||
check(pairs, tui.AltZ+'b', "b")
|
check(pairs, tui.Key('b'), "b")
|
||||||
check(pairs, tui.AltZ+'c', "c")
|
check(pairs, tui.Key('c'), "c")
|
||||||
check(pairs, tui.AltZ+',', ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs = parseKeyChords(",,,", "")
|
pairs = parseKeyChords(",,,", "")
|
||||||
checkN(len(pairs), 1)
|
checkN(len(pairs), 1)
|
||||||
check(pairs, tui.AltZ+',', ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
|
pairs = parseKeyChords(",ALT-,,", "")
|
||||||
|
checkN(len(pairs), 1)
|
||||||
|
check(pairs, tui.AltKey(','), "ALT-,")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBind(t *testing.T) {
|
func TestBind(t *testing.T) {
|
||||||
keymap := defaultKeymap()
|
keymap := defaultKeymap()
|
||||||
check := func(keyName int, arg1 string, types ...actionType) {
|
check := func(event tui.Event, arg1 string, types ...actionType) {
|
||||||
if len(keymap[keyName]) != len(types) {
|
if len(keymap[event]) != len(types) {
|
||||||
t.Errorf("invalid number of actions (%d != %d)", len(types), len(keymap[keyName]))
|
t.Errorf("invalid number of actions for %v (%d != %d)",
|
||||||
|
event, len(types), len(keymap[event]))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for idx, action := range keymap[keyName] {
|
for idx, action := range keymap[event] {
|
||||||
if types[idx] != action.t {
|
if types[idx] != action.t {
|
||||||
t.Errorf("invalid action type (%d != %d)", types[idx], action.t)
|
t.Errorf("invalid action type (%d != %d)", types[idx], action.t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(arg1) > 0 && keymap[keyName][0].a != arg1 {
|
if len(arg1) > 0 && keymap[event][0].a != arg1 {
|
||||||
t.Errorf("invalid action argument: (%s != %s)", arg1, keymap[keyName][0].a)
|
t.Errorf("invalid action argument: (%s != %s)", arg1, keymap[event][0].a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
check(tui.CtrlA, "", actBeginningOfLine)
|
check(tui.CtrlA.AsEvent(), "", actBeginningOfLine)
|
||||||
parseKeymap(keymap,
|
parseKeymap(keymap,
|
||||||
"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+
|
"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+
|
||||||
"f1:execute(ls {+})+abort+execute(echo {+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
|
"f1:execute(ls {+})+abort+execute(echo {+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
|
||||||
"alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+
|
"alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+
|
||||||
"x:Execute(foo+bar),X:execute/bar+baz/"+
|
"x:Execute(foo+bar),X:execute/bar+baz/"+
|
||||||
",f1:+top,f1:+top"+
|
",f1:+first,f1:+top"+
|
||||||
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up")
|
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up")
|
||||||
check(tui.CtrlA, "", actKillLine)
|
check(tui.CtrlA.AsEvent(), "", actKillLine)
|
||||||
check(tui.CtrlB, "", actToggleSort, actUp, actDown)
|
check(tui.CtrlB.AsEvent(), "", actToggleSort, actUp, actDown)
|
||||||
check(tui.AltZ+'c', "", actPageUp)
|
check(tui.Key('c'), "", actPageUp)
|
||||||
check(tui.AltZ+',', "", actAbort)
|
check(tui.Key(','), "", actAbort)
|
||||||
check(tui.AltZ+':', "", actAccept)
|
check(tui.Key(':'), "", actAccept)
|
||||||
check(tui.AltZ, "", actPageDown)
|
check(tui.AltKey('z'), "", actPageDown)
|
||||||
check(tui.F1, "ls {+}", actExecute, actAbort, actExecute, actSelectAll, actTop, actTop)
|
check(tui.F1.AsEvent(), "ls {+}", actExecute, actAbort, actExecute, actSelectAll, actFirst, actFirst)
|
||||||
check(tui.F2, "echo {}, {}, {}", actExecute)
|
check(tui.F2.AsEvent(), "echo {}, {}, {}", actExecute)
|
||||||
check(tui.F3, "echo '({})'", actExecute)
|
check(tui.F3.AsEvent(), "echo '({})'", actExecute)
|
||||||
check(tui.F4, "less {}", actExecute)
|
check(tui.F4.AsEvent(), "less {}", actExecute)
|
||||||
check(tui.AltZ+'x', "foo+bar", actExecute)
|
check(tui.Key('x'), "foo+bar", actExecute)
|
||||||
check(tui.AltZ+'X', "bar+baz", actExecute)
|
check(tui.Key('X'), "bar+baz", actExecute)
|
||||||
check(tui.AltA, "echo (,),[,],/,:,;,%,{}", actExecuteMulti)
|
check(tui.AltKey('a'), "echo (,),[,],/,:,;,%,{}", actExecuteMulti)
|
||||||
check(tui.AltB, "echo (,),[,],/,:,@,%,{}", actExecute)
|
check(tui.AltKey('b'), "echo (,),[,],/,:,@,%,{}", actExecute)
|
||||||
check(tui.AltZ+'+', "++\nfoobar,Y:execute(baz)+up", actExecute)
|
check(tui.Key('+'), "++\nfoobar,Y:execute(baz)+up", actExecute)
|
||||||
|
|
||||||
for idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} {
|
for idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} {
|
||||||
parseKeymap(keymap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char))
|
parseKeymap(keymap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char))
|
||||||
check(tui.AltZ+int([]rune(fmt.Sprintf("%d", idx%10))[0]), "foobar", actExecute)
|
check(tui.Key([]rune(fmt.Sprintf("%d", idx%10))[0]), "foobar", actExecute)
|
||||||
}
|
}
|
||||||
|
|
||||||
parseKeymap(keymap, "f1:abort")
|
parseKeymap(keymap, "f1:abort")
|
||||||
check(tui.F1, "", actAbort)
|
check(tui.F1.AsEvent(), "", actAbort)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestColorSpec(t *testing.T) {
|
func TestColorSpec(t *testing.T) {
|
||||||
@@ -314,11 +322,12 @@ func TestColorSpec(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultCtrlNP(t *testing.T) {
|
func TestDefaultCtrlNP(t *testing.T) {
|
||||||
check := func(words []string, key int, expected actionType) {
|
check := func(words []string, et tui.EventType, expected actionType) {
|
||||||
|
e := et.AsEvent()
|
||||||
opts := defaultOptions()
|
opts := defaultOptions()
|
||||||
parseOptions(opts, words)
|
parseOptions(opts, words)
|
||||||
postProcessOptions(opts)
|
postProcessOptions(opts)
|
||||||
if opts.Keymap[key][0].t != expected {
|
if opts.Keymap[e][0].t != expected {
|
||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -380,18 +389,18 @@ func TestPreviewOpts(t *testing.T) {
|
|||||||
opts.Preview.hidden == true &&
|
opts.Preview.hidden == true &&
|
||||||
opts.Preview.wrap == true &&
|
opts.Preview.wrap == true &&
|
||||||
opts.Preview.position == posLeft &&
|
opts.Preview.position == posLeft &&
|
||||||
opts.Preview.scroll == "{1}-/2" &&
|
opts.Preview.scroll == "+{1}-/2" &&
|
||||||
opts.Preview.size.percent == false &&
|
opts.Preview.size.percent == false &&
|
||||||
opts.Preview.size.size == 15) {
|
opts.Preview.size.size == 15) {
|
||||||
t.Error(opts.Preview)
|
t.Error(opts.Preview)
|
||||||
}
|
}
|
||||||
opts = optsFor("--preview-window=up:15:wrap:hidden:+{1}-/2", "--preview-window=down", "--preview-window=cycle")
|
opts = optsFor("--preview-window=up:15:wrap:hidden:+{1}+3-1-2/2", "--preview-window=down", "--preview-window=cycle")
|
||||||
if !(opts.Preview.command == "" &&
|
if !(opts.Preview.command == "" &&
|
||||||
opts.Preview.hidden == true &&
|
opts.Preview.hidden == true &&
|
||||||
opts.Preview.wrap == true &&
|
opts.Preview.wrap == true &&
|
||||||
opts.Preview.cycle == true &&
|
opts.Preview.cycle == true &&
|
||||||
opts.Preview.position == posDown &&
|
opts.Preview.position == posDown &&
|
||||||
opts.Preview.scroll == "{1}-/2" &&
|
opts.Preview.scroll == "+{1}+3-1-2/2" &&
|
||||||
opts.Preview.size.percent == false &&
|
opts.Preview.size.percent == false &&
|
||||||
opts.Preview.size.size == 15) {
|
opts.Preview.size.size == 15) {
|
||||||
t.Error(opts.Preview.size.size)
|
t.Error(opts.Preview.size.size)
|
||||||
|
@@ -337,7 +337,7 @@ func (p *Pattern) MatchItem(item *Item, withPos bool, slab *util.Slab) (*Result,
|
|||||||
func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset, int, *[]int) {
|
func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset, int, *[]int) {
|
||||||
var input []Token
|
var input []Token
|
||||||
if len(p.nth) == 0 {
|
if len(p.nth) == 0 {
|
||||||
input = []Token{Token{text: &item.text, prefixLength: 0}}
|
input = []Token{{text: &item.text, prefixLength: 0}}
|
||||||
} else {
|
} else {
|
||||||
input = p.transformInput(item)
|
input = p.transformInput(item)
|
||||||
}
|
}
|
||||||
@@ -350,7 +350,7 @@ func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset,
|
|||||||
func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Offset, int, *[]int) {
|
func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Offset, int, *[]int) {
|
||||||
var input []Token
|
var input []Token
|
||||||
if len(p.nth) == 0 {
|
if len(p.nth) == 0 {
|
||||||
input = []Token{Token{text: &item.text, prefixLength: 0}}
|
input = []Token{{text: &item.text, prefixLength: 0}}
|
||||||
} else {
|
} else {
|
||||||
input = p.transformInput(item)
|
input = p.transformInput(item)
|
||||||
}
|
}
|
||||||
|
@@ -131,7 +131,7 @@ func TestCaseSensitivity(t *testing.T) {
|
|||||||
func TestOrigTextAndTransformed(t *testing.T) {
|
func TestOrigTextAndTransformed(t *testing.T) {
|
||||||
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("jg"))
|
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("jg"))
|
||||||
tokens := Tokenize("junegunn", Delimiter{})
|
tokens := Tokenize("junegunn", Delimiter{})
|
||||||
trans := Transform(tokens, []Range{Range{1, 1}})
|
trans := Transform(tokens, []Range{{1, 1}})
|
||||||
|
|
||||||
origBytes := []byte("junegunn.choi")
|
origBytes := []byte("junegunn.choi")
|
||||||
for _, extended := range []bool{false, true} {
|
for _, extended := range []bool{false, true} {
|
||||||
|
@@ -164,7 +164,7 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
|
|||||||
// combination of either [hl and bg] or [hl+ and bg+].
|
// combination of either [hl and bg] or [hl+ and bg+].
|
||||||
//
|
//
|
||||||
// If the original text already has background color, and the
|
// If the original text already has background color, and the
|
||||||
// forground color of colMatch is -1, we shouldn't only apply the
|
// foreground color of colMatch is -1, we shouldn't only apply the
|
||||||
// background color of colMatch.
|
// background color of colMatch.
|
||||||
// e.g. echo -e "\x1b[32;7mfoo\x1b[mbar" | fzf --ansi --color bg+:1,hl+:-1:underline
|
// 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
|
// echo -e "\x1b[42mfoo\x1b[mbar" | fzf --ansi --color bg+:1,hl+:-1:underline
|
||||||
|
@@ -18,8 +18,8 @@ func withIndex(i *Item, index int) *Item {
|
|||||||
|
|
||||||
func TestOffsetSort(t *testing.T) {
|
func TestOffsetSort(t *testing.T) {
|
||||||
offsets := []Offset{
|
offsets := []Offset{
|
||||||
Offset{3, 5}, Offset{2, 7},
|
{3, 5}, {2, 7},
|
||||||
Offset{1, 3}, Offset{2, 9}}
|
{1, 3}, {2, 9}}
|
||||||
sort.Sort(ByOrder(offsets))
|
sort.Sort(ByOrder(offsets))
|
||||||
|
|
||||||
if offsets[0][0] != 1 || offsets[0][1] != 3 ||
|
if offsets[0][0] != 1 || offsets[0][1] != 3 ||
|
||||||
@@ -84,13 +84,13 @@ func TestResultRank(t *testing.T) {
|
|||||||
|
|
||||||
// Sort by relevance
|
// Sort by relevance
|
||||||
item3 := buildResult(
|
item3 := buildResult(
|
||||||
withIndex(&Item{}, 2), []Offset{Offset{1, 3}, Offset{5, 7}}, 3)
|
withIndex(&Item{}, 2), []Offset{{1, 3}, {5, 7}}, 3)
|
||||||
item4 := buildResult(
|
item4 := buildResult(
|
||||||
withIndex(&Item{}, 2), []Offset{Offset{1, 2}, Offset{6, 7}}, 4)
|
withIndex(&Item{}, 2), []Offset{{1, 2}, {6, 7}}, 4)
|
||||||
item5 := buildResult(
|
item5 := buildResult(
|
||||||
withIndex(&Item{}, 2), []Offset{Offset{1, 3}, Offset{5, 7}}, 5)
|
withIndex(&Item{}, 2), []Offset{{1, 3}, {5, 7}}, 5)
|
||||||
item6 := buildResult(
|
item6 := buildResult(
|
||||||
withIndex(&Item{}, 2), []Offset{Offset{1, 2}, Offset{6, 7}}, 6)
|
withIndex(&Item{}, 2), []Offset{{1, 2}, {6, 7}}, 6)
|
||||||
items = []Result{item1, item2, item3, item4, item5, item6}
|
items = []Result{item1, item2, item3, item4, item5, item6}
|
||||||
sort.Sort(ByRelevance(items))
|
sort.Sort(ByRelevance(items))
|
||||||
if !(items[0] == item6 && items[1] == item5 &&
|
if !(items[0] == item6 && items[1] == item5 &&
|
||||||
|
456
src/terminal.go
456
src/terminal.go
@@ -22,8 +22,9 @@ import (
|
|||||||
// import "github.com/pkg/profile"
|
// import "github.com/pkg/profile"
|
||||||
|
|
||||||
var placeholder *regexp.Regexp
|
var placeholder *regexp.Regexp
|
||||||
var numericPrefix *regexp.Regexp
|
|
||||||
var whiteSuffix *regexp.Regexp
|
var whiteSuffix *regexp.Regexp
|
||||||
|
var offsetComponentRegex *regexp.Regexp
|
||||||
|
var offsetTrimCharsRegex *regexp.Regexp
|
||||||
var activeTempFiles []string
|
var activeTempFiles []string
|
||||||
|
|
||||||
const ellipsis string = ".."
|
const ellipsis string = ".."
|
||||||
@@ -31,8 +32,9 @@ const clearCode string = "\x1b[2J"
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{\+?f?nf?})`)
|
placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{\+?f?nf?})`)
|
||||||
numericPrefix = regexp.MustCompile(`^[[:punct:]]*([0-9]+)`)
|
|
||||||
whiteSuffix = regexp.MustCompile(`\s*$`)
|
whiteSuffix = regexp.MustCompile(`\s*$`)
|
||||||
|
offsetComponentRegex = regexp.MustCompile(`([+-][0-9]+)|(-?/[1-9][0-9]*)`)
|
||||||
|
offsetTrimCharsRegex = regexp.MustCompile(`[^0-9/+-]`)
|
||||||
activeTempFiles = []string{}
|
activeTempFiles = []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,8 +111,8 @@ type Terminal struct {
|
|||||||
sort bool
|
sort bool
|
||||||
toggleSort bool
|
toggleSort bool
|
||||||
delimiter Delimiter
|
delimiter Delimiter
|
||||||
expect map[int]string
|
expect map[tui.Event]string
|
||||||
keymap map[int][]action
|
keymap map[tui.Event][]action
|
||||||
pressed string
|
pressed string
|
||||||
printQuery bool
|
printQuery bool
|
||||||
history *History
|
history *History
|
||||||
@@ -125,6 +127,7 @@ type Terminal struct {
|
|||||||
unicode bool
|
unicode bool
|
||||||
borderShape tui.BorderShape
|
borderShape tui.BorderShape
|
||||||
cleanExit bool
|
cleanExit bool
|
||||||
|
paused bool
|
||||||
border tui.Window
|
border tui.Window
|
||||||
window tui.Window
|
window tui.Window
|
||||||
pborder tui.Window
|
pborder tui.Window
|
||||||
@@ -132,6 +135,7 @@ type Terminal struct {
|
|||||||
count int
|
count int
|
||||||
progress int
|
progress int
|
||||||
reading bool
|
reading bool
|
||||||
|
running bool
|
||||||
failed *string
|
failed *string
|
||||||
jumping jumpMode
|
jumping jumpMode
|
||||||
jumpLabels string
|
jumpLabels string
|
||||||
@@ -150,11 +154,13 @@ type Terminal struct {
|
|||||||
initFunc func()
|
initFunc func()
|
||||||
prevLines []itemLine
|
prevLines []itemLine
|
||||||
suppress bool
|
suppress bool
|
||||||
|
sigstop bool
|
||||||
startChan chan bool
|
startChan chan bool
|
||||||
killChan chan int
|
killChan chan int
|
||||||
slab *util.Slab
|
slab *util.Slab
|
||||||
theme *tui.ColorTheme
|
theme *tui.ColorTheme
|
||||||
tui tui.Renderer
|
tui tui.Renderer
|
||||||
|
executing *util.AtomicBool
|
||||||
}
|
}
|
||||||
|
|
||||||
type selectedItem struct {
|
type selectedItem struct {
|
||||||
@@ -219,6 +225,7 @@ const (
|
|||||||
actClearScreen
|
actClearScreen
|
||||||
actClearQuery
|
actClearQuery
|
||||||
actClearSelection
|
actClearSelection
|
||||||
|
actClose
|
||||||
actDeleteChar
|
actDeleteChar
|
||||||
actDeleteCharEOF
|
actDeleteCharEOF
|
||||||
actEndOfLine
|
actEndOfLine
|
||||||
@@ -233,6 +240,7 @@ const (
|
|||||||
actSelectAll
|
actSelectAll
|
||||||
actDeselectAll
|
actDeselectAll
|
||||||
actToggle
|
actToggle
|
||||||
|
actToggleSearch
|
||||||
actToggleAll
|
actToggleAll
|
||||||
actToggleDown
|
actToggleDown
|
||||||
actToggleUp
|
actToggleUp
|
||||||
@@ -253,6 +261,8 @@ const (
|
|||||||
actTogglePreview
|
actTogglePreview
|
||||||
actTogglePreviewWrap
|
actTogglePreviewWrap
|
||||||
actPreview
|
actPreview
|
||||||
|
actPreviewTop
|
||||||
|
actPreviewBottom
|
||||||
actPreviewUp
|
actPreviewUp
|
||||||
actPreviewDown
|
actPreviewDown
|
||||||
actPreviewPageUp
|
actPreviewPageUp
|
||||||
@@ -265,8 +275,13 @@ const (
|
|||||||
actExecuteSilent
|
actExecuteSilent
|
||||||
actExecuteMulti // Deprecated
|
actExecuteMulti // Deprecated
|
||||||
actSigStop
|
actSigStop
|
||||||
actTop
|
actFirst
|
||||||
|
actLast
|
||||||
actReload
|
actReload
|
||||||
|
actDisableSearch
|
||||||
|
actEnableSearch
|
||||||
|
actSelect
|
||||||
|
actDeselect
|
||||||
)
|
)
|
||||||
|
|
||||||
type placeholderFlags struct {
|
type placeholderFlags struct {
|
||||||
@@ -303,62 +318,68 @@ func toActions(types ...actionType) []action {
|
|||||||
return actions
|
return actions
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultKeymap() map[int][]action {
|
func defaultKeymap() map[tui.Event][]action {
|
||||||
keymap := make(map[int][]action)
|
keymap := make(map[tui.Event][]action)
|
||||||
keymap[tui.Invalid] = toActions(actInvalid)
|
add := func(e tui.EventType, a actionType) {
|
||||||
keymap[tui.Resize] = toActions(actClearScreen)
|
keymap[e.AsEvent()] = toActions(a)
|
||||||
keymap[tui.CtrlA] = toActions(actBeginningOfLine)
|
}
|
||||||
keymap[tui.CtrlB] = toActions(actBackwardChar)
|
addEvent := func(e tui.Event, a actionType) {
|
||||||
keymap[tui.CtrlC] = toActions(actAbort)
|
keymap[e] = toActions(a)
|
||||||
keymap[tui.CtrlG] = toActions(actAbort)
|
|
||||||
keymap[tui.CtrlQ] = toActions(actAbort)
|
|
||||||
keymap[tui.ESC] = toActions(actAbort)
|
|
||||||
keymap[tui.CtrlD] = toActions(actDeleteCharEOF)
|
|
||||||
keymap[tui.CtrlE] = toActions(actEndOfLine)
|
|
||||||
keymap[tui.CtrlF] = toActions(actForwardChar)
|
|
||||||
keymap[tui.CtrlH] = toActions(actBackwardDeleteChar)
|
|
||||||
keymap[tui.BSpace] = toActions(actBackwardDeleteChar)
|
|
||||||
keymap[tui.Tab] = toActions(actToggleDown)
|
|
||||||
keymap[tui.BTab] = toActions(actToggleUp)
|
|
||||||
keymap[tui.CtrlJ] = toActions(actDown)
|
|
||||||
keymap[tui.CtrlK] = toActions(actUp)
|
|
||||||
keymap[tui.CtrlL] = toActions(actClearScreen)
|
|
||||||
keymap[tui.CtrlM] = toActions(actAccept)
|
|
||||||
keymap[tui.CtrlN] = toActions(actDown)
|
|
||||||
keymap[tui.CtrlP] = toActions(actUp)
|
|
||||||
keymap[tui.CtrlU] = toActions(actUnixLineDiscard)
|
|
||||||
keymap[tui.CtrlW] = toActions(actUnixWordRubout)
|
|
||||||
keymap[tui.CtrlY] = toActions(actYank)
|
|
||||||
if !util.IsWindows() {
|
|
||||||
keymap[tui.CtrlZ] = toActions(actSigStop)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
keymap[tui.AltB] = toActions(actBackwardWord)
|
add(tui.Invalid, actInvalid)
|
||||||
keymap[tui.SLeft] = toActions(actBackwardWord)
|
add(tui.Resize, actClearScreen)
|
||||||
keymap[tui.AltF] = toActions(actForwardWord)
|
add(tui.CtrlA, actBeginningOfLine)
|
||||||
keymap[tui.SRight] = toActions(actForwardWord)
|
add(tui.CtrlB, actBackwardChar)
|
||||||
keymap[tui.AltD] = toActions(actKillWord)
|
add(tui.CtrlC, actAbort)
|
||||||
keymap[tui.AltBS] = toActions(actBackwardKillWord)
|
add(tui.CtrlG, actAbort)
|
||||||
|
add(tui.CtrlQ, actAbort)
|
||||||
|
add(tui.ESC, actAbort)
|
||||||
|
add(tui.CtrlD, actDeleteCharEOF)
|
||||||
|
add(tui.CtrlE, actEndOfLine)
|
||||||
|
add(tui.CtrlF, actForwardChar)
|
||||||
|
add(tui.CtrlH, actBackwardDeleteChar)
|
||||||
|
add(tui.BSpace, actBackwardDeleteChar)
|
||||||
|
add(tui.Tab, actToggleDown)
|
||||||
|
add(tui.BTab, actToggleUp)
|
||||||
|
add(tui.CtrlJ, actDown)
|
||||||
|
add(tui.CtrlK, actUp)
|
||||||
|
add(tui.CtrlL, actClearScreen)
|
||||||
|
add(tui.CtrlM, actAccept)
|
||||||
|
add(tui.CtrlN, actDown)
|
||||||
|
add(tui.CtrlP, actUp)
|
||||||
|
add(tui.CtrlU, actUnixLineDiscard)
|
||||||
|
add(tui.CtrlW, actUnixWordRubout)
|
||||||
|
add(tui.CtrlY, actYank)
|
||||||
|
if !util.IsWindows() {
|
||||||
|
add(tui.CtrlZ, actSigStop)
|
||||||
|
}
|
||||||
|
|
||||||
keymap[tui.Up] = toActions(actUp)
|
addEvent(tui.AltKey('b'), actBackwardWord)
|
||||||
keymap[tui.Down] = toActions(actDown)
|
add(tui.SLeft, actBackwardWord)
|
||||||
keymap[tui.Left] = toActions(actBackwardChar)
|
addEvent(tui.AltKey('f'), actForwardWord)
|
||||||
keymap[tui.Right] = toActions(actForwardChar)
|
add(tui.SRight, actForwardWord)
|
||||||
|
addEvent(tui.AltKey('d'), actKillWord)
|
||||||
|
add(tui.AltBS, actBackwardKillWord)
|
||||||
|
|
||||||
keymap[tui.Home] = toActions(actBeginningOfLine)
|
add(tui.Up, actUp)
|
||||||
keymap[tui.End] = toActions(actEndOfLine)
|
add(tui.Down, actDown)
|
||||||
keymap[tui.Del] = toActions(actDeleteChar)
|
add(tui.Left, actBackwardChar)
|
||||||
keymap[tui.PgUp] = toActions(actPageUp)
|
add(tui.Right, actForwardChar)
|
||||||
keymap[tui.PgDn] = toActions(actPageDown)
|
|
||||||
|
|
||||||
keymap[tui.SUp] = toActions(actPreviewUp)
|
add(tui.Home, actBeginningOfLine)
|
||||||
keymap[tui.SDown] = toActions(actPreviewDown)
|
add(tui.End, actEndOfLine)
|
||||||
|
add(tui.Del, actDeleteChar)
|
||||||
|
add(tui.PgUp, actPageUp)
|
||||||
|
add(tui.PgDn, actPageDown)
|
||||||
|
|
||||||
keymap[tui.Rune] = toActions(actRune)
|
add(tui.SUp, actPreviewUp)
|
||||||
keymap[tui.Mouse] = toActions(actMouse)
|
add(tui.SDown, actPreviewDown)
|
||||||
keymap[tui.DoubleClick] = toActions(actAccept)
|
|
||||||
keymap[tui.LeftClick] = toActions(actIgnore)
|
add(tui.Mouse, actMouse)
|
||||||
keymap[tui.RightClick] = toActions(actToggle)
|
add(tui.DoubleClick, actAccept)
|
||||||
|
add(tui.LeftClick, actIgnore)
|
||||||
|
add(tui.RightClick, actToggle)
|
||||||
return keymap
|
return keymap
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,6 +500,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
unicode: opts.Unicode,
|
unicode: opts.Unicode,
|
||||||
borderShape: opts.BorderShape,
|
borderShape: opts.BorderShape,
|
||||||
cleanExit: opts.ClearOnExit,
|
cleanExit: opts.ClearOnExit,
|
||||||
|
paused: opts.Phony,
|
||||||
strong: strongAttr,
|
strong: strongAttr,
|
||||||
cycle: opts.Cycle,
|
cycle: opts.Cycle,
|
||||||
header: header,
|
header: header,
|
||||||
@@ -486,6 +508,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
ansi: opts.Ansi,
|
ansi: opts.Ansi,
|
||||||
tabstop: opts.Tabstop,
|
tabstop: opts.Tabstop,
|
||||||
reading: true,
|
reading: true,
|
||||||
|
running: true,
|
||||||
failed: nil,
|
failed: nil,
|
||||||
jumping: jumpDisabled,
|
jumping: jumpDisabled,
|
||||||
jumpLabels: opts.JumpLabels,
|
jumpLabels: opts.JumpLabels,
|
||||||
@@ -501,12 +524,14 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
eventBox: eventBox,
|
eventBox: eventBox,
|
||||||
mutex: sync.Mutex{},
|
mutex: sync.Mutex{},
|
||||||
suppress: true,
|
suppress: true,
|
||||||
|
sigstop: false,
|
||||||
slab: util.MakeSlab(slab16Size, slab32Size),
|
slab: util.MakeSlab(slab16Size, slab32Size),
|
||||||
theme: opts.Theme,
|
theme: opts.Theme,
|
||||||
startChan: make(chan bool, 1),
|
startChan: make(chan bool, 1),
|
||||||
killChan: make(chan int),
|
killChan: make(chan int),
|
||||||
tui: renderer,
|
tui: renderer,
|
||||||
initFunc: func() { renderer.Init() }}
|
initFunc: func() { renderer.Init() },
|
||||||
|
executing: util.NewAtomicBool(false)}
|
||||||
t.prompt, t.promptLen = t.parsePrompt(opts.Prompt)
|
t.prompt, t.promptLen = t.parsePrompt(opts.Prompt)
|
||||||
t.pointer, t.pointerLen = t.processTabs([]rune(opts.Pointer), 0)
|
t.pointer, t.pointerLen = t.processTabs([]rune(opts.Pointer), 0)
|
||||||
t.marker, t.markerLen = t.processTabs([]rune(opts.Marker), 0)
|
t.marker, t.markerLen = t.processTabs([]rune(opts.Marker), 0)
|
||||||
@@ -554,10 +579,10 @@ func (t *Terminal) noInfoLine() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Input returns current query string
|
// Input returns current query string
|
||||||
func (t *Terminal) Input() []rune {
|
func (t *Terminal) Input() (bool, []rune) {
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
defer t.mutex.Unlock()
|
defer t.mutex.Unlock()
|
||||||
return copySlice(t.input)
|
return t.paused, copySlice(t.input)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateCount updates the count information
|
// UpdateCount updates the count information
|
||||||
@@ -916,8 +941,12 @@ func (t *Terminal) printPrompt() {
|
|||||||
t.prompt()
|
t.prompt()
|
||||||
|
|
||||||
before, after := t.updatePromptOffset()
|
before, after := t.updatePromptOffset()
|
||||||
t.window.CPrint(tui.ColInput, string(before))
|
color := tui.ColInput
|
||||||
t.window.CPrint(tui.ColInput, string(after))
|
if t.paused {
|
||||||
|
color = tui.ColDisabled
|
||||||
|
}
|
||||||
|
t.window.CPrint(color, string(before))
|
||||||
|
t.window.CPrint(color, string(after))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) trimMessage(message string, maxWidth int) string {
|
func (t *Terminal) trimMessage(message string, maxWidth int) string {
|
||||||
@@ -1119,8 +1148,8 @@ func (t *Terminal) trimLeft(runes []rune, width int) ([]rune, int32) {
|
|||||||
width = util.Max(0, width)
|
width = util.Max(0, width)
|
||||||
var trimmed int32
|
var trimmed int32
|
||||||
// Assume that each rune takes at least one column on screen
|
// Assume that each rune takes at least one column on screen
|
||||||
if len(runes) > width {
|
if len(runes) > width+2 {
|
||||||
diff := len(runes) - width
|
diff := len(runes) - width - 2
|
||||||
trimmed = int32(diff)
|
trimmed = int32(diff)
|
||||||
runes = runes[diff:]
|
runes = runes[diff:]
|
||||||
}
|
}
|
||||||
@@ -1268,18 +1297,37 @@ func (t *Terminal) renderPreviewSpinner() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) renderPreviewText(unchanged bool) {
|
func (t *Terminal) renderPreviewArea(unchanged bool) {
|
||||||
maxWidth := t.pwindow.Width()
|
|
||||||
lineNo := -t.previewer.offset
|
|
||||||
height := t.pwindow.Height()
|
|
||||||
if unchanged {
|
if unchanged {
|
||||||
t.pwindow.MoveAndClear(0, 0)
|
t.pwindow.MoveAndClear(0, 0) // Clear scroll offset display
|
||||||
} else {
|
} else {
|
||||||
t.previewed.filled = false
|
t.previewed.filled = false
|
||||||
t.pwindow.Erase()
|
t.pwindow.Erase()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
height := t.pwindow.Height()
|
||||||
|
header := []string{}
|
||||||
|
body := t.previewer.lines
|
||||||
|
headerLines := t.previewOpts.headerLines
|
||||||
|
// Do not enable preview header lines if it's value is too large
|
||||||
|
if headerLines > 0 && headerLines < util.Min(len(body), height) {
|
||||||
|
header = t.previewer.lines[0:headerLines]
|
||||||
|
body = t.previewer.lines[headerLines:]
|
||||||
|
// Always redraw header
|
||||||
|
t.renderPreviewText(height, header, 0, false)
|
||||||
|
t.pwindow.MoveAndClear(t.pwindow.Y(), 0)
|
||||||
|
}
|
||||||
|
t.renderPreviewText(height, body, -t.previewer.offset+headerLines, unchanged)
|
||||||
|
|
||||||
|
if !unchanged {
|
||||||
|
t.pwindow.FinishFill()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) renderPreviewText(height int, lines []string, lineNo int, unchanged bool) {
|
||||||
|
maxWidth := t.pwindow.Width()
|
||||||
var ansi *ansiState
|
var ansi *ansiState
|
||||||
for _, line := range t.previewer.lines {
|
for _, line := range lines {
|
||||||
var lbg tui.Color = -1
|
var lbg tui.Color = -1
|
||||||
if ansi != nil {
|
if ansi != nil {
|
||||||
ansi.lbg = -1
|
ansi.lbg = -1
|
||||||
@@ -1293,8 +1341,9 @@ func (t *Terminal) renderPreviewText(unchanged bool) {
|
|||||||
prefixWidth := 0
|
prefixWidth := 0
|
||||||
_, _, ansi = extractColor(line, ansi, func(str string, ansi *ansiState) bool {
|
_, _, ansi = extractColor(line, ansi, func(str string, ansi *ansiState) bool {
|
||||||
trimmed := []rune(str)
|
trimmed := []rune(str)
|
||||||
|
trimmedLen := 0
|
||||||
if !t.previewOpts.wrap {
|
if !t.previewOpts.wrap {
|
||||||
trimmed, _ = t.trimRight(trimmed, maxWidth-t.pwindow.X())
|
trimmed, trimmedLen = t.trimRight(trimmed, maxWidth-t.pwindow.X())
|
||||||
}
|
}
|
||||||
str, width := t.processTabs(trimmed, prefixWidth)
|
str, width := t.processTabs(trimmed, prefixWidth)
|
||||||
prefixWidth += width
|
prefixWidth += width
|
||||||
@@ -1304,7 +1353,8 @@ func (t *Terminal) renderPreviewText(unchanged bool) {
|
|||||||
} else {
|
} else {
|
||||||
fillRet = t.pwindow.CFill(tui.ColPreview.Fg(), tui.ColPreview.Bg(), tui.AttrRegular, str)
|
fillRet = t.pwindow.CFill(tui.ColPreview.Fg(), tui.ColPreview.Bg(), tui.AttrRegular, str)
|
||||||
}
|
}
|
||||||
return fillRet == tui.FillContinue
|
return trimmedLen == 0 &&
|
||||||
|
(fillRet == tui.FillContinue || t.previewOpts.wrap && fillRet == tui.FillNextLine)
|
||||||
})
|
})
|
||||||
t.previewer.scrollable = t.previewer.scrollable || t.pwindow.Y() == height-1 && t.pwindow.X() == t.pwindow.Width()
|
t.previewer.scrollable = t.previewer.scrollable || t.pwindow.Y() == height-1 && t.pwindow.X() == t.pwindow.Width()
|
||||||
if fillRet == tui.FillNextLine {
|
if fillRet == tui.FillNextLine {
|
||||||
@@ -1325,9 +1375,6 @@ func (t *Terminal) renderPreviewText(unchanged bool) {
|
|||||||
}
|
}
|
||||||
lineNo++
|
lineNo++
|
||||||
}
|
}
|
||||||
if !unchanged {
|
|
||||||
t.pwindow.FinishFill()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) printPreview() {
|
func (t *Terminal) printPreview() {
|
||||||
@@ -1340,7 +1387,7 @@ func (t *Terminal) printPreview() {
|
|||||||
t.previewer.version == t.previewed.version &&
|
t.previewer.version == t.previewed.version &&
|
||||||
t.previewer.offset == t.previewed.offset
|
t.previewer.offset == t.previewed.offset
|
||||||
t.previewer.scrollable = t.previewer.offset > 0 || numLines > height
|
t.previewer.scrollable = t.previewer.offset > 0 || numLines > height
|
||||||
t.renderPreviewText(unchanged)
|
t.renderPreviewArea(unchanged)
|
||||||
t.renderPreviewSpinner()
|
t.renderPreviewSpinner()
|
||||||
t.previewed.numLines = numLines
|
t.previewed.numLines = numLines
|
||||||
t.previewed.version = t.previewer.version
|
t.previewed.version = t.previewer.version
|
||||||
@@ -1353,7 +1400,7 @@ func (t *Terminal) printPreviewDelayed() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.previewer.scrollable = false
|
t.previewer.scrollable = false
|
||||||
t.renderPreviewText(true)
|
t.renderPreviewArea(true)
|
||||||
|
|
||||||
message := t.trimMessage("Loading ..", t.pwindow.Width())
|
message := t.trimMessage("Loading ..", t.pwindow.Width())
|
||||||
pos := t.pwindow.Width() - len(message)
|
pos := t.pwindow.Width() - len(message)
|
||||||
@@ -1451,10 +1498,9 @@ func (t *Terminal) rubout(pattern string) {
|
|||||||
t.input = append(t.input[:t.cx], after...)
|
t.input = append(t.input[:t.cx], after...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func keyMatch(key int, event tui.Event) bool {
|
func keyMatch(key tui.Event, event tui.Event) bool {
|
||||||
return event.Type == key ||
|
return event.Type == key.Type && event.Char == key.Char ||
|
||||||
event.Type == tui.Rune && int(event.Char) == key-tui.AltZ ||
|
key.Type == tui.DoubleClick && event.Type == tui.Mouse && event.MouseEvent.Double
|
||||||
event.Type == tui.Mouse && key == tui.DoubleClick && event.MouseEvent.Double
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func quoteEntryCmd(entry string) string {
|
func quoteEntryCmd(entry string) string {
|
||||||
@@ -1547,43 +1593,33 @@ func (t *Terminal) replacePlaceholder(template string, forcePlus bool, input str
|
|||||||
template, t.ansi, t.delimiter, t.printsep, forcePlus, input, list)
|
template, t.ansi, t.delimiter, t.printsep, forcePlus, input, list)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ascii to positive integer
|
|
||||||
func atopi(s string) int {
|
|
||||||
matches := numericPrefix.FindStringSubmatch(s)
|
|
||||||
if len(matches) < 2 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
n, e := strconv.Atoi(matches[1])
|
|
||||||
if e != nil || n < 1 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Terminal) evaluateScrollOffset(list []*Item, height int) int {
|
func (t *Terminal) evaluateScrollOffset(list []*Item, height int) int {
|
||||||
offsetExpr := t.replacePlaceholder(t.previewOpts.scroll, false, "", list)
|
offsetExpr := offsetTrimCharsRegex.ReplaceAllString(
|
||||||
nums := strings.Split(offsetExpr, "-")
|
t.replacePlaceholder(t.previewOpts.scroll, false, "", list), "")
|
||||||
switch len(nums) {
|
|
||||||
case 0:
|
atoi := func(s string) int {
|
||||||
return 0
|
n, e := strconv.Atoi(s)
|
||||||
case 1, 2:
|
if e != nil {
|
||||||
base := atopi(nums[0])
|
|
||||||
if base == 0 {
|
|
||||||
return 0
|
return 0
|
||||||
} else if len(nums) == 1 {
|
|
||||||
return base - 1
|
|
||||||
}
|
}
|
||||||
if nums[1][0] == '/' {
|
return n
|
||||||
denom := atopi(nums[1][1:])
|
}
|
||||||
|
|
||||||
|
base := -1
|
||||||
|
for _, component := range offsetComponentRegex.FindAllString(offsetExpr, -1) {
|
||||||
|
if strings.HasPrefix(component, "-/") {
|
||||||
|
component = component[1:]
|
||||||
|
}
|
||||||
|
if component[0] == '/' {
|
||||||
|
denom := atoi(component[1:])
|
||||||
if denom == 0 {
|
if denom == 0 {
|
||||||
return base
|
return base
|
||||||
}
|
}
|
||||||
return base - height/denom
|
return base - height/denom
|
||||||
}
|
}
|
||||||
return base - atopi(nums[1]) - 1
|
base += atoi(component)
|
||||||
default:
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
return base
|
||||||
}
|
}
|
||||||
|
|
||||||
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
|
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
|
||||||
@@ -1691,13 +1727,17 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
|||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
t.tui.Pause(true)
|
t.tui.Pause(true)
|
||||||
|
t.executing.Set(true)
|
||||||
cmd.Run()
|
cmd.Run()
|
||||||
|
t.executing.Set(false)
|
||||||
t.tui.Resume(true, false)
|
t.tui.Resume(true, false)
|
||||||
t.redraw()
|
t.redraw()
|
||||||
t.refresh()
|
t.refresh()
|
||||||
} else {
|
} else {
|
||||||
t.tui.Pause(false)
|
t.tui.Pause(false)
|
||||||
|
t.executing.Set(true)
|
||||||
cmd.Run()
|
cmd.Run()
|
||||||
|
t.executing.Set(false)
|
||||||
t.tui.Resume(false, false)
|
t.tui.Resume(false, false)
|
||||||
}
|
}
|
||||||
cleanTemporaryFiles()
|
cleanTemporaryFiles()
|
||||||
@@ -1765,11 +1805,26 @@ func (t *Terminal) selectItem(item *Item) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) selectItemChanged(item *Item) bool {
|
||||||
|
if _, found := t.selected[item.Index()]; found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return t.selectItem(item)
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Terminal) deselectItem(item *Item) {
|
func (t *Terminal) deselectItem(item *Item) {
|
||||||
delete(t.selected, item.Index())
|
delete(t.selected, item.Index())
|
||||||
t.version++
|
t.version++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) deselectItemChanged(item *Item) bool {
|
||||||
|
if _, found := t.selected[item.Index()]; found {
|
||||||
|
t.deselectItem(item)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Terminal) toggleItem(item *Item) bool {
|
func (t *Terminal) toggleItem(item *Item) bool {
|
||||||
if _, found := t.selected[item.Index()]; !found {
|
if _, found := t.selected[item.Index()]; !found {
|
||||||
return t.selectItem(item)
|
return t.selectItem(item)
|
||||||
@@ -1783,7 +1838,7 @@ func (t *Terminal) killPreview(code int) {
|
|||||||
case t.killChan <- code:
|
case t.killChan <- code:
|
||||||
default:
|
default:
|
||||||
if code != exitCancel {
|
if code != exitCancel {
|
||||||
os.Exit(code)
|
t.eventBox.Set(EvtQuit, code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1800,8 +1855,12 @@ func (t *Terminal) Loop() {
|
|||||||
intChan := make(chan os.Signal, 1)
|
intChan := make(chan os.Signal, 1)
|
||||||
signal.Notify(intChan, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(intChan, os.Interrupt, syscall.SIGTERM)
|
||||||
go func() {
|
go func() {
|
||||||
<-intChan
|
for s := range intChan {
|
||||||
t.reqBox.Set(reqQuit, nil)
|
// Don't quit by SIGINT while executing because it should be for the executing command and not for fzf itself
|
||||||
|
if !(s == os.Interrupt && t.executing.Get()) {
|
||||||
|
t.reqBox.Set(reqQuit, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
contChan := make(chan os.Signal, 1)
|
contChan := make(chan os.Signal, 1)
|
||||||
@@ -1872,12 +1931,13 @@ func (t *Terminal) Loop() {
|
|||||||
version++
|
version++
|
||||||
// We don't display preview window if no match
|
// We don't display preview window if no match
|
||||||
if items[0] != nil {
|
if items[0] != nil {
|
||||||
command := t.replacePlaceholder(commandTemplate, false, string(t.Input()), items)
|
_, query := t.Input()
|
||||||
|
command := t.replacePlaceholder(commandTemplate, false, string(query), items)
|
||||||
initialOffset := 0
|
initialOffset := 0
|
||||||
cmd := util.ExecCommand(command, true)
|
cmd := util.ExecCommand(command, true)
|
||||||
if pwindow != nil {
|
if pwindow != nil {
|
||||||
height := pwindow.Height()
|
height := pwindow.Height()
|
||||||
initialOffset = util.Max(0, t.evaluateScrollOffset(items, height))
|
initialOffset = util.Max(0, t.evaluateScrollOffset(items, util.Max(0, height-t.previewOpts.headerLines)))
|
||||||
env := os.Environ()
|
env := os.Environ()
|
||||||
lines := fmt.Sprintf("LINES=%d", height)
|
lines := fmt.Sprintf("LINES=%d", height)
|
||||||
columns := fmt.Sprintf("COLUMNS=%d", pwindow.Width())
|
columns := fmt.Sprintf("COLUMNS=%d", pwindow.Width())
|
||||||
@@ -1962,7 +2022,7 @@ func (t *Terminal) Loop() {
|
|||||||
case code := <-t.killChan:
|
case code := <-t.killChan:
|
||||||
if code != exitCancel {
|
if code != exitCancel {
|
||||||
util.KillCommand(cmd)
|
util.KillCommand(cmd)
|
||||||
os.Exit(code)
|
t.eventBox.Set(EvtQuit, code)
|
||||||
} else {
|
} else {
|
||||||
timer := time.NewTimer(previewCancelWait)
|
timer := time.NewTimer(previewCancelWait)
|
||||||
select {
|
select {
|
||||||
@@ -1999,16 +2059,6 @@ func (t *Terminal) Loop() {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
exit := func(getCode func() int) {
|
|
||||||
t.tui.Close()
|
|
||||||
code := getCode()
|
|
||||||
if code <= exitNoMatch && t.history != nil {
|
|
||||||
t.history.append(string(t.input))
|
|
||||||
}
|
|
||||||
// prof.Stop()
|
|
||||||
t.killPreview(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshPreview := func(command string) {
|
refreshPreview := func(command string) {
|
||||||
if len(command) > 0 && t.isPreviewEnabled() {
|
if len(command) > 0 && t.isPreviewEnabled() {
|
||||||
_, list := t.buildPlusList(command, false)
|
_, list := t.buildPlusList(command, false)
|
||||||
@@ -2020,7 +2070,19 @@ func (t *Terminal) Loop() {
|
|||||||
go func() {
|
go func() {
|
||||||
var focusedIndex int32 = minItem.Index()
|
var focusedIndex int32 = minItem.Index()
|
||||||
var version int64 = -1
|
var version int64 = -1
|
||||||
for {
|
running := true
|
||||||
|
code := exitError
|
||||||
|
exit := func(getCode func() int) {
|
||||||
|
t.tui.Close()
|
||||||
|
code = getCode()
|
||||||
|
if code <= exitNoMatch && t.history != nil {
|
||||||
|
t.history.append(string(t.input))
|
||||||
|
}
|
||||||
|
running = false
|
||||||
|
t.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
for running {
|
||||||
t.reqBox.Wait(func(events *util.Events) {
|
t.reqBox.Wait(func(events *util.Events) {
|
||||||
defer events.Clear()
|
defer events.Clear()
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
@@ -2055,7 +2117,7 @@ func (t *Terminal) Loop() {
|
|||||||
case reqRefresh:
|
case reqRefresh:
|
||||||
t.suppress = false
|
t.suppress = false
|
||||||
case reqReinit:
|
case reqReinit:
|
||||||
t.tui.Resume(t.fullscreen, true)
|
t.tui.Resume(t.fullscreen, t.sigstop)
|
||||||
t.redraw()
|
t.redraw()
|
||||||
case reqRedraw:
|
case reqRedraw:
|
||||||
t.redraw()
|
t.redraw()
|
||||||
@@ -2066,6 +2128,7 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
return exitNoMatch
|
return exitNoMatch
|
||||||
})
|
})
|
||||||
|
return
|
||||||
case reqPreviewDisplay:
|
case reqPreviewDisplay:
|
||||||
result := value.(previewResult)
|
result := value.(previewResult)
|
||||||
if t.previewer.version != result.version {
|
if t.previewer.version != result.version {
|
||||||
@@ -2077,7 +2140,7 @@ func (t *Terminal) Loop() {
|
|||||||
if t.previewer.following {
|
if t.previewer.following {
|
||||||
t.previewer.offset = len(t.previewer.lines) - t.pwindow.Height()
|
t.previewer.offset = len(t.previewer.lines) - t.pwindow.Height()
|
||||||
} else if result.offset >= 0 {
|
} else if result.offset >= 0 {
|
||||||
t.previewer.offset = util.Constrain(result.offset, 0, len(t.previewer.lines)-1)
|
t.previewer.offset = util.Constrain(result.offset, t.previewOpts.headerLines, len(t.previewer.lines)-1)
|
||||||
}
|
}
|
||||||
t.printPreview()
|
t.printPreview()
|
||||||
case reqPreviewRefresh:
|
case reqPreviewRefresh:
|
||||||
@@ -2090,14 +2153,18 @@ func (t *Terminal) Loop() {
|
|||||||
t.printer(string(t.input))
|
t.printer(string(t.input))
|
||||||
return exitOk
|
return exitOk
|
||||||
})
|
})
|
||||||
|
return
|
||||||
case reqQuit:
|
case reqQuit:
|
||||||
exit(func() int { return exitInterrupt })
|
exit(func() int { return exitInterrupt })
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.refresh()
|
t.refresh()
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// prof.Stop()
|
||||||
|
t.killPreview(code)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
looping := true
|
looping := true
|
||||||
@@ -2130,28 +2197,31 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
toggle := func() bool {
|
toggle := func() bool {
|
||||||
if t.cy < t.merger.Length() && t.toggleItem(t.merger.Get(t.cy).item) {
|
current := t.currentItem()
|
||||||
|
if current != nil && t.toggleItem(current) {
|
||||||
req(reqInfo)
|
req(reqInfo)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
scrollPreview := func(amount int) {
|
scrollPreviewTo := func(newOffset int) {
|
||||||
if !t.previewer.scrollable {
|
if !t.previewer.scrollable {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t.previewer.following = false
|
t.previewer.following = false
|
||||||
newOffset := t.previewer.offset + amount
|
|
||||||
numLines := len(t.previewer.lines)
|
numLines := len(t.previewer.lines)
|
||||||
if t.previewOpts.cycle {
|
if t.previewOpts.cycle {
|
||||||
newOffset = (newOffset + numLines) % numLines
|
newOffset = (newOffset + numLines) % numLines
|
||||||
}
|
}
|
||||||
newOffset = util.Constrain(newOffset, 0, numLines-1)
|
newOffset = util.Constrain(newOffset, t.previewOpts.headerLines, numLines-1)
|
||||||
if t.previewer.offset != newOffset {
|
if t.previewer.offset != newOffset {
|
||||||
t.previewer.offset = newOffset
|
t.previewer.offset = newOffset
|
||||||
req(reqPreviewRefresh)
|
req(reqPreviewRefresh)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
scrollPreviewBy := func(amount int) {
|
||||||
|
scrollPreviewTo(t.previewer.offset + amount)
|
||||||
|
}
|
||||||
for key, ret := range t.expect {
|
for key, ret := range t.expect {
|
||||||
if keyMatch(key, event) {
|
if keyMatch(key, event) {
|
||||||
t.pressed = ret
|
t.pressed = ret
|
||||||
@@ -2161,16 +2231,20 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var doAction func(action, int) bool
|
actionsFor := func(eventType tui.EventType) []action {
|
||||||
doActions := func(actions []action, mapkey int) bool {
|
return t.keymap[eventType.AsEvent()]
|
||||||
|
}
|
||||||
|
|
||||||
|
var doAction func(action) bool
|
||||||
|
doActions := func(actions []action) bool {
|
||||||
for _, action := range actions {
|
for _, action := range actions {
|
||||||
if !doAction(action, mapkey) {
|
if !doAction(action) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
doAction = func(a action, mapkey int) bool {
|
doAction = func(a action) bool {
|
||||||
switch a.t {
|
switch a.t {
|
||||||
case actIgnore:
|
case actIgnore:
|
||||||
case actExecute, actExecuteSilent:
|
case actExecute, actExecuteSilent:
|
||||||
@@ -2195,34 +2269,44 @@ func (t *Terminal) Loop() {
|
|||||||
case actTogglePreviewWrap:
|
case actTogglePreviewWrap:
|
||||||
if t.hasPreviewWindow() {
|
if t.hasPreviewWindow() {
|
||||||
t.previewOpts.wrap = !t.previewOpts.wrap
|
t.previewOpts.wrap = !t.previewOpts.wrap
|
||||||
|
// Reset preview version so that full redraw occurs
|
||||||
|
t.previewed.version = 0
|
||||||
req(reqPreviewRefresh)
|
req(reqPreviewRefresh)
|
||||||
}
|
}
|
||||||
case actToggleSort:
|
case actToggleSort:
|
||||||
t.sort = !t.sort
|
t.sort = !t.sort
|
||||||
changed = true
|
changed = true
|
||||||
|
case actPreviewTop:
|
||||||
|
if t.hasPreviewWindow() {
|
||||||
|
scrollPreviewTo(0)
|
||||||
|
}
|
||||||
|
case actPreviewBottom:
|
||||||
|
if t.hasPreviewWindow() {
|
||||||
|
scrollPreviewTo(len(t.previewer.lines) - t.pwindow.Height())
|
||||||
|
}
|
||||||
case actPreviewUp:
|
case actPreviewUp:
|
||||||
if t.hasPreviewWindow() {
|
if t.hasPreviewWindow() {
|
||||||
scrollPreview(-1)
|
scrollPreviewBy(-1)
|
||||||
}
|
}
|
||||||
case actPreviewDown:
|
case actPreviewDown:
|
||||||
if t.hasPreviewWindow() {
|
if t.hasPreviewWindow() {
|
||||||
scrollPreview(1)
|
scrollPreviewBy(1)
|
||||||
}
|
}
|
||||||
case actPreviewPageUp:
|
case actPreviewPageUp:
|
||||||
if t.hasPreviewWindow() {
|
if t.hasPreviewWindow() {
|
||||||
scrollPreview(-t.pwindow.Height())
|
scrollPreviewBy(-t.pwindow.Height())
|
||||||
}
|
}
|
||||||
case actPreviewPageDown:
|
case actPreviewPageDown:
|
||||||
if t.hasPreviewWindow() {
|
if t.hasPreviewWindow() {
|
||||||
scrollPreview(t.pwindow.Height())
|
scrollPreviewBy(t.pwindow.Height())
|
||||||
}
|
}
|
||||||
case actPreviewHalfPageUp:
|
case actPreviewHalfPageUp:
|
||||||
if t.hasPreviewWindow() {
|
if t.hasPreviewWindow() {
|
||||||
scrollPreview(-t.pwindow.Height() / 2)
|
scrollPreviewBy(-t.pwindow.Height() / 2)
|
||||||
}
|
}
|
||||||
case actPreviewHalfPageDown:
|
case actPreviewHalfPageDown:
|
||||||
if t.hasPreviewWindow() {
|
if t.hasPreviewWindow() {
|
||||||
scrollPreview(t.pwindow.Height() / 2)
|
scrollPreviewBy(t.pwindow.Height() / 2)
|
||||||
}
|
}
|
||||||
case actBeginningOfLine:
|
case actBeginningOfLine:
|
||||||
t.cx = 0
|
t.cx = 0
|
||||||
@@ -2241,8 +2325,9 @@ func (t *Terminal) Loop() {
|
|||||||
case actRefreshPreview:
|
case actRefreshPreview:
|
||||||
refreshPreview(t.previewOpts.command)
|
refreshPreview(t.previewOpts.command)
|
||||||
case actReplaceQuery:
|
case actReplaceQuery:
|
||||||
if t.cy >= 0 && t.cy < t.merger.Length() {
|
current := t.currentItem()
|
||||||
t.input = t.merger.Get(t.cy).item.text.ToRunes()
|
if current != nil {
|
||||||
|
t.input = current.text.ToRunes()
|
||||||
t.cx = len(t.input)
|
t.cx = len(t.input)
|
||||||
}
|
}
|
||||||
case actAbort:
|
case actAbort:
|
||||||
@@ -2296,6 +2381,22 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
req(reqList, reqInfo)
|
req(reqList, reqInfo)
|
||||||
}
|
}
|
||||||
|
case actClose:
|
||||||
|
if t.isPreviewEnabled() {
|
||||||
|
togglePreview(false)
|
||||||
|
} else {
|
||||||
|
req(reqQuit)
|
||||||
|
}
|
||||||
|
case actSelect:
|
||||||
|
current := t.currentItem()
|
||||||
|
if t.multi > 0 && current != nil && t.selectItemChanged(current) {
|
||||||
|
req(reqList, reqInfo)
|
||||||
|
}
|
||||||
|
case actDeselect:
|
||||||
|
current := t.currentItem()
|
||||||
|
if t.multi > 0 && current != nil && t.deselectItemChanged(current) {
|
||||||
|
req(reqList, reqInfo)
|
||||||
|
}
|
||||||
case actToggle:
|
case actToggle:
|
||||||
if t.multi > 0 && t.merger.Length() > 0 && toggle() {
|
if t.multi > 0 && t.merger.Length() > 0 && toggle() {
|
||||||
req(reqList)
|
req(reqList)
|
||||||
@@ -2323,14 +2424,14 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
case actToggleIn:
|
case actToggleIn:
|
||||||
if t.layout != layoutDefault {
|
if t.layout != layoutDefault {
|
||||||
return doAction(action{t: actToggleUp}, mapkey)
|
return doAction(action{t: actToggleUp})
|
||||||
}
|
}
|
||||||
return doAction(action{t: actToggleDown}, mapkey)
|
return doAction(action{t: actToggleDown})
|
||||||
case actToggleOut:
|
case actToggleOut:
|
||||||
if t.layout != layoutDefault {
|
if t.layout != layoutDefault {
|
||||||
return doAction(action{t: actToggleDown}, mapkey)
|
return doAction(action{t: actToggleDown})
|
||||||
}
|
}
|
||||||
return doAction(action{t: actToggleUp}, mapkey)
|
return doAction(action{t: actToggleUp})
|
||||||
case actToggleDown:
|
case actToggleDown:
|
||||||
if t.multi > 0 && t.merger.Length() > 0 && toggle() {
|
if t.multi > 0 && t.merger.Length() > 0 && toggle() {
|
||||||
t.vmove(-1, true)
|
t.vmove(-1, true)
|
||||||
@@ -2364,9 +2465,12 @@ func (t *Terminal) Loop() {
|
|||||||
t.version++
|
t.version++
|
||||||
req(reqList, reqInfo)
|
req(reqList, reqInfo)
|
||||||
}
|
}
|
||||||
case actTop:
|
case actFirst:
|
||||||
t.vset(0)
|
t.vset(0)
|
||||||
req(reqList)
|
req(reqList)
|
||||||
|
case actLast:
|
||||||
|
t.vset(t.merger.Length() - 1)
|
||||||
|
req(reqList)
|
||||||
case actUnixLineDiscard:
|
case actUnixLineDiscard:
|
||||||
beof = len(t.input) == 0
|
beof = len(t.input) == 0
|
||||||
if t.cx > 0 {
|
if t.cx > 0 {
|
||||||
@@ -2438,9 +2542,21 @@ func (t *Terminal) Loop() {
|
|||||||
t.input = trimQuery(t.history.next())
|
t.input = trimQuery(t.history.next())
|
||||||
t.cx = len(t.input)
|
t.cx = len(t.input)
|
||||||
}
|
}
|
||||||
|
case actToggleSearch:
|
||||||
|
t.paused = !t.paused
|
||||||
|
changed = !t.paused
|
||||||
|
req(reqPrompt)
|
||||||
|
case actEnableSearch:
|
||||||
|
t.paused = false
|
||||||
|
changed = true
|
||||||
|
req(reqPrompt)
|
||||||
|
case actDisableSearch:
|
||||||
|
t.paused = true
|
||||||
|
req(reqPrompt)
|
||||||
case actSigStop:
|
case actSigStop:
|
||||||
p, err := os.FindProcess(os.Getpid())
|
p, err := os.FindProcess(os.Getpid())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
t.sigstop = true
|
||||||
t.tui.Clear()
|
t.tui.Clear()
|
||||||
t.tui.Pause(t.fullscreen)
|
t.tui.Pause(t.fullscreen)
|
||||||
notifyStop(p)
|
notifyStop(p)
|
||||||
@@ -2459,7 +2575,7 @@ func (t *Terminal) Loop() {
|
|||||||
t.vmove(me.S, true)
|
t.vmove(me.S, true)
|
||||||
req(reqList)
|
req(reqList)
|
||||||
} else if t.hasPreviewWindow() && t.pwindow.Enclose(my, mx) {
|
} else if t.hasPreviewWindow() && t.pwindow.Enclose(my, mx) {
|
||||||
scrollPreview(-me.S)
|
scrollPreviewBy(-me.S)
|
||||||
}
|
}
|
||||||
} else if t.window.Enclose(my, mx) {
|
} else if t.window.Enclose(my, mx) {
|
||||||
mx -= t.window.Left()
|
mx -= t.window.Left()
|
||||||
@@ -2484,7 +2600,7 @@ func (t *Terminal) Loop() {
|
|||||||
// Double-click
|
// Double-click
|
||||||
if my >= min {
|
if my >= min {
|
||||||
if t.vset(t.offset+my-min) && t.cy < t.merger.Length() {
|
if t.vset(t.offset+my-min) && t.cy < t.merger.Length() {
|
||||||
return doActions(t.keymap[tui.DoubleClick], tui.DoubleClick)
|
return doActions(actionsFor(tui.DoubleClick))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if me.Down {
|
} else if me.Down {
|
||||||
@@ -2498,9 +2614,9 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
req(reqList)
|
req(reqList)
|
||||||
if me.Left {
|
if me.Left {
|
||||||
return doActions(t.keymap[tui.LeftClick], tui.LeftClick)
|
return doActions(actionsFor(tui.LeftClick))
|
||||||
}
|
}
|
||||||
return doActions(t.keymap[tui.RightClick], tui.RightClick)
|
return doActions(actionsFor(tui.RightClick))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2522,33 +2638,29 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
mapkey := event.Type
|
|
||||||
if t.jumping == jumpDisabled {
|
if t.jumping == jumpDisabled {
|
||||||
actions := t.keymap[mapkey]
|
actions := t.keymap[event.Comparable()]
|
||||||
if mapkey == tui.Rune {
|
if len(actions) == 0 && event.Type == tui.Rune {
|
||||||
mapkey = int(event.Char) + int(tui.AltZ)
|
doAction(action{t: actRune})
|
||||||
if act, prs := t.keymap[mapkey]; prs {
|
} else if !doActions(actions) {
|
||||||
actions = act
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !doActions(actions, mapkey) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
t.truncateQuery()
|
t.truncateQuery()
|
||||||
queryChanged = string(previousInput) != string(t.input)
|
queryChanged = string(previousInput) != string(t.input)
|
||||||
changed = changed || queryChanged
|
changed = changed || queryChanged
|
||||||
if onChanges, prs := t.keymap[tui.Change]; queryChanged && prs {
|
if onChanges, prs := t.keymap[tui.Change.AsEvent()]; queryChanged && prs {
|
||||||
if !doActions(onChanges, tui.Change) {
|
if !doActions(onChanges) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if onEOFs, prs := t.keymap[tui.BackwardEOF]; beof && prs {
|
if onEOFs, prs := t.keymap[tui.BackwardEOF.AsEvent()]; beof && prs {
|
||||||
if !doActions(onEOFs, tui.BackwardEOF) {
|
if !doActions(onEOFs) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if mapkey == tui.Rune {
|
if event.Type == tui.Rune {
|
||||||
if idx := strings.IndexRune(t.jumpLabels, event.Char); idx >= 0 && idx < t.maxItems() && idx < t.merger.Length() {
|
if idx := strings.IndexRune(t.jumpLabels, event.Char); idx >= 0 && idx < t.maxItems() && idx < t.merger.Length() {
|
||||||
t.cy = idx + t.offset
|
t.cy = idx + t.offset
|
||||||
if t.jumping == jumpAcceptEnabled {
|
if t.jumping == jumpAcceptEnabled {
|
||||||
@@ -2585,18 +2697,16 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) constrain() {
|
func (t *Terminal) constrain() {
|
||||||
|
// count of items to display allowed by filtering
|
||||||
count := t.merger.Length()
|
count := t.merger.Length()
|
||||||
|
// count of lines can be displayed
|
||||||
height := t.maxItems()
|
height := t.maxItems()
|
||||||
diffpos := t.cy - t.offset
|
|
||||||
|
|
||||||
t.cy = util.Constrain(t.cy, 0, count-1)
|
t.cy = util.Constrain(t.cy, 0, count-1)
|
||||||
t.offset = util.Constrain(t.offset, t.cy-height+1, t.cy)
|
|
||||||
// Adjustment
|
minOffset := t.cy - height + 1
|
||||||
if count-t.offset < height {
|
maxOffset := util.Max(util.Min(count-height, t.cy), 0)
|
||||||
t.offset = util.Max(0, count-height)
|
t.offset = util.Constrain(t.offset, minOffset, maxOffset)
|
||||||
t.cy = util.Constrain(t.offset+diffpos, 0, count-1)
|
|
||||||
}
|
|
||||||
t.offset = util.Max(0, t.offset)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) vmove(o int, allowCycle bool) {
|
func (t *Terminal) vmove(o int, allowCycle bool) {
|
||||||
|
@@ -35,7 +35,7 @@ type Delimiter struct {
|
|||||||
str *string
|
str *string
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns the string representation of a Delimeter.
|
// String returns the string representation of a Delimiter.
|
||||||
func (d Delimiter) String() string {
|
func (d Delimiter) String() string {
|
||||||
return fmt.Sprintf("Delimiter{regex: %v, str: &%q}", d.regex, *d.str)
|
return fmt.Sprintf("Delimiter{regex: %v, str: &%q}", d.regex, *d.str)
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package tui
|
package tui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -230,7 +231,7 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
retries := 0
|
retries := 0
|
||||||
if c == ESC || nonblock {
|
if c == ESC.Int() || nonblock {
|
||||||
retries = r.escDelay / escPollInterval
|
retries = r.escDelay / escPollInterval
|
||||||
}
|
}
|
||||||
buffer = append(buffer, byte(c))
|
buffer = append(buffer, byte(c))
|
||||||
@@ -245,7 +246,7 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
} else if c == ESC && pc != c {
|
} else if c == ESC.Int() && pc != c {
|
||||||
retries = r.escDelay / escPollInterval
|
retries = r.escDelay / escPollInterval
|
||||||
} else {
|
} else {
|
||||||
retries = 0
|
retries = 0
|
||||||
@@ -278,11 +279,11 @@ func (r *LightRenderer) GetChar() Event {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
switch r.buffer[0] {
|
switch r.buffer[0] {
|
||||||
case CtrlC:
|
case CtrlC.Byte():
|
||||||
return Event{CtrlC, 0, nil}
|
return Event{CtrlC, 0, nil}
|
||||||
case CtrlG:
|
case CtrlG.Byte():
|
||||||
return Event{CtrlG, 0, nil}
|
return Event{CtrlG, 0, nil}
|
||||||
case CtrlQ:
|
case CtrlQ.Byte():
|
||||||
return Event{CtrlQ, 0, nil}
|
return Event{CtrlQ, 0, nil}
|
||||||
case 127:
|
case 127:
|
||||||
return Event{BSpace, 0, nil}
|
return Event{BSpace, 0, nil}
|
||||||
@@ -296,7 +297,7 @@ func (r *LightRenderer) GetChar() Event {
|
|||||||
return Event{CtrlCaret, 0, nil}
|
return Event{CtrlCaret, 0, nil}
|
||||||
case 31:
|
case 31:
|
||||||
return Event{CtrlSlash, 0, nil}
|
return Event{CtrlSlash, 0, nil}
|
||||||
case ESC:
|
case ESC.Byte():
|
||||||
ev := r.escSequence(&sz)
|
ev := r.escSequence(&sz)
|
||||||
// Second chance
|
// Second chance
|
||||||
if ev.Type == Invalid {
|
if ev.Type == Invalid {
|
||||||
@@ -307,8 +308,8 @@ func (r *LightRenderer) GetChar() Event {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CTRL-A ~ CTRL-Z
|
// CTRL-A ~ CTRL-Z
|
||||||
if r.buffer[0] <= CtrlZ {
|
if r.buffer[0] <= CtrlZ.Byte() {
|
||||||
return Event{int(r.buffer[0]), 0, nil}
|
return Event{EventType(r.buffer[0]), 0, nil}
|
||||||
}
|
}
|
||||||
char, rsz := utf8.DecodeRune(r.buffer)
|
char, rsz := utf8.DecodeRune(r.buffer)
|
||||||
if char == utf8.RuneError {
|
if char == utf8.RuneError {
|
||||||
@@ -331,26 +332,16 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
|
|
||||||
*sz = 2
|
*sz = 2
|
||||||
if r.buffer[1] >= 1 && r.buffer[1] <= 'z'-'a'+1 {
|
if r.buffer[1] >= 1 && r.buffer[1] <= 'z'-'a'+1 {
|
||||||
return Event{int(CtrlAltA + r.buffer[1] - 1), 0, nil}
|
return CtrlAltKey(rune(r.buffer[1] + 'a' - 1))
|
||||||
}
|
}
|
||||||
alt := false
|
alt := false
|
||||||
if len(r.buffer) > 2 && r.buffer[1] == ESC {
|
if len(r.buffer) > 2 && r.buffer[1] == ESC.Byte() {
|
||||||
r.buffer = r.buffer[1:]
|
r.buffer = r.buffer[1:]
|
||||||
alt = true
|
alt = true
|
||||||
}
|
}
|
||||||
switch r.buffer[1] {
|
switch r.buffer[1] {
|
||||||
case ESC:
|
case ESC.Byte():
|
||||||
return Event{ESC, 0, nil}
|
return Event{ESC, 0, nil}
|
||||||
case ' ':
|
|
||||||
return Event{AltSpace, 0, nil}
|
|
||||||
case '/':
|
|
||||||
return Event{AltSlash, 0, nil}
|
|
||||||
case 'b':
|
|
||||||
return Event{AltB, 0, nil}
|
|
||||||
case 'd':
|
|
||||||
return Event{AltD, 0, nil}
|
|
||||||
case 'f':
|
|
||||||
return Event{AltF, 0, nil}
|
|
||||||
case 127:
|
case 127:
|
||||||
return Event{AltBS, 0, nil}
|
return Event{AltBS, 0, nil}
|
||||||
case '[', 'O':
|
case '[', 'O':
|
||||||
@@ -518,11 +509,11 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
} // r.buffer[2]
|
} // r.buffer[2]
|
||||||
} // r.buffer[2]
|
} // r.buffer[2]
|
||||||
} // r.buffer[1]
|
} // r.buffer[1]
|
||||||
if r.buffer[1] >= 'a' && r.buffer[1] <= 'z' {
|
rest := bytes.NewBuffer(r.buffer[1:])
|
||||||
return Event{AltA + int(r.buffer[1]) - 'a', 0, nil}
|
c, size, err := rest.ReadRune()
|
||||||
}
|
if err == nil {
|
||||||
if r.buffer[1] >= '0' && r.buffer[1] <= '9' {
|
*sz = 1 + size
|
||||||
return Event{Alt0 + int(r.buffer[1]) - '0', 0, nil}
|
return AltKey(c)
|
||||||
}
|
}
|
||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
}
|
}
|
||||||
@@ -915,12 +906,6 @@ func (w *LightWindow) fill(str string, onMove func()) FillReturn {
|
|||||||
for i, line := range allLines {
|
for i, line := range allLines {
|
||||||
lines := wrapLine(line, w.posx, w.width, w.tabstop)
|
lines := wrapLine(line, w.posx, w.width, w.tabstop)
|
||||||
for j, wl := range lines {
|
for j, wl := range lines {
|
||||||
if w.posx >= w.Width()-1 && wl.displayWidth == 0 {
|
|
||||||
if w.posy < w.height-1 {
|
|
||||||
w.Move(w.posy+1, 0)
|
|
||||||
}
|
|
||||||
return FillNextLine
|
|
||||||
}
|
|
||||||
w.stderrInternal(wl.text, false)
|
w.stderrInternal(wl.text, false)
|
||||||
w.posx += wl.displayWidth
|
w.posx += wl.displayWidth
|
||||||
|
|
||||||
@@ -935,6 +920,14 @@ func (w *LightWindow) fill(str string, onMove func()) FillReturn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if w.posx+1 >= w.Width() {
|
||||||
|
if w.posy+1 >= w.height {
|
||||||
|
return FillSuspend
|
||||||
|
}
|
||||||
|
w.Move(w.posy+1, 0)
|
||||||
|
onMove()
|
||||||
|
return FillNextLine
|
||||||
|
}
|
||||||
return FillContinue
|
return FillContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -226,65 +226,65 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
alt := (mods & tcell.ModAlt) > 0
|
alt := (mods & tcell.ModAlt) > 0
|
||||||
shift := (mods & tcell.ModShift) > 0
|
shift := (mods & tcell.ModShift) > 0
|
||||||
altShift := alt && shift
|
altShift := alt && shift
|
||||||
keyfn := func(r rune) int {
|
keyfn := func(r rune) Event {
|
||||||
if alt {
|
if alt {
|
||||||
return CtrlAltA - 'a' + int(r)
|
return CtrlAltKey(r)
|
||||||
}
|
}
|
||||||
return CtrlA - 'a' + int(r)
|
return EventType(CtrlA.Int() - 'a' + int(r)).AsEvent()
|
||||||
}
|
}
|
||||||
switch ev.Key() {
|
switch ev.Key() {
|
||||||
case tcell.KeyCtrlA:
|
case tcell.KeyCtrlA:
|
||||||
return Event{keyfn('a'), 0, nil}
|
return keyfn('a')
|
||||||
case tcell.KeyCtrlB:
|
case tcell.KeyCtrlB:
|
||||||
return Event{keyfn('b'), 0, nil}
|
return keyfn('b')
|
||||||
case tcell.KeyCtrlC:
|
case tcell.KeyCtrlC:
|
||||||
return Event{keyfn('c'), 0, nil}
|
return keyfn('c')
|
||||||
case tcell.KeyCtrlD:
|
case tcell.KeyCtrlD:
|
||||||
return Event{keyfn('d'), 0, nil}
|
return keyfn('d')
|
||||||
case tcell.KeyCtrlE:
|
case tcell.KeyCtrlE:
|
||||||
return Event{keyfn('e'), 0, nil}
|
return keyfn('e')
|
||||||
case tcell.KeyCtrlF:
|
case tcell.KeyCtrlF:
|
||||||
return Event{keyfn('f'), 0, nil}
|
return keyfn('f')
|
||||||
case tcell.KeyCtrlG:
|
case tcell.KeyCtrlG:
|
||||||
return Event{keyfn('g'), 0, nil}
|
return keyfn('g')
|
||||||
case tcell.KeyCtrlH:
|
case tcell.KeyCtrlH:
|
||||||
return Event{keyfn('h'), 0, nil}
|
return keyfn('h')
|
||||||
case tcell.KeyCtrlI:
|
case tcell.KeyCtrlI:
|
||||||
return Event{keyfn('i'), 0, nil}
|
return keyfn('i')
|
||||||
case tcell.KeyCtrlJ:
|
case tcell.KeyCtrlJ:
|
||||||
return Event{keyfn('j'), 0, nil}
|
return keyfn('j')
|
||||||
case tcell.KeyCtrlK:
|
case tcell.KeyCtrlK:
|
||||||
return Event{keyfn('k'), 0, nil}
|
return keyfn('k')
|
||||||
case tcell.KeyCtrlL:
|
case tcell.KeyCtrlL:
|
||||||
return Event{keyfn('l'), 0, nil}
|
return keyfn('l')
|
||||||
case tcell.KeyCtrlM:
|
case tcell.KeyCtrlM:
|
||||||
return Event{keyfn('m'), 0, nil}
|
return keyfn('m')
|
||||||
case tcell.KeyCtrlN:
|
case tcell.KeyCtrlN:
|
||||||
return Event{keyfn('n'), 0, nil}
|
return keyfn('n')
|
||||||
case tcell.KeyCtrlO:
|
case tcell.KeyCtrlO:
|
||||||
return Event{keyfn('o'), 0, nil}
|
return keyfn('o')
|
||||||
case tcell.KeyCtrlP:
|
case tcell.KeyCtrlP:
|
||||||
return Event{keyfn('p'), 0, nil}
|
return keyfn('p')
|
||||||
case tcell.KeyCtrlQ:
|
case tcell.KeyCtrlQ:
|
||||||
return Event{keyfn('q'), 0, nil}
|
return keyfn('q')
|
||||||
case tcell.KeyCtrlR:
|
case tcell.KeyCtrlR:
|
||||||
return Event{keyfn('r'), 0, nil}
|
return keyfn('r')
|
||||||
case tcell.KeyCtrlS:
|
case tcell.KeyCtrlS:
|
||||||
return Event{keyfn('s'), 0, nil}
|
return keyfn('s')
|
||||||
case tcell.KeyCtrlT:
|
case tcell.KeyCtrlT:
|
||||||
return Event{keyfn('t'), 0, nil}
|
return keyfn('t')
|
||||||
case tcell.KeyCtrlU:
|
case tcell.KeyCtrlU:
|
||||||
return Event{keyfn('u'), 0, nil}
|
return keyfn('u')
|
||||||
case tcell.KeyCtrlV:
|
case tcell.KeyCtrlV:
|
||||||
return Event{keyfn('v'), 0, nil}
|
return keyfn('v')
|
||||||
case tcell.KeyCtrlW:
|
case tcell.KeyCtrlW:
|
||||||
return Event{keyfn('w'), 0, nil}
|
return keyfn('w')
|
||||||
case tcell.KeyCtrlX:
|
case tcell.KeyCtrlX:
|
||||||
return Event{keyfn('x'), 0, nil}
|
return keyfn('x')
|
||||||
case tcell.KeyCtrlY:
|
case tcell.KeyCtrlY:
|
||||||
return Event{keyfn('y'), 0, nil}
|
return keyfn('y')
|
||||||
case tcell.KeyCtrlZ:
|
case tcell.KeyCtrlZ:
|
||||||
return Event{keyfn('z'), 0, nil}
|
return keyfn('z')
|
||||||
case tcell.KeyCtrlSpace:
|
case tcell.KeyCtrlSpace:
|
||||||
return Event{CtrlSpace, 0, nil}
|
return Event{CtrlSpace, 0, nil}
|
||||||
case tcell.KeyCtrlBackslash:
|
case tcell.KeyCtrlBackslash:
|
||||||
@@ -389,18 +389,7 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
case tcell.KeyRune:
|
case tcell.KeyRune:
|
||||||
r := ev.Rune()
|
r := ev.Rune()
|
||||||
if alt {
|
if alt {
|
||||||
switch r {
|
return AltKey(r)
|
||||||
case ' ':
|
|
||||||
return Event{AltSpace, 0, nil}
|
|
||||||
case '/':
|
|
||||||
return Event{AltSlash, 0, nil}
|
|
||||||
}
|
|
||||||
if r >= 'a' && r <= 'z' {
|
|
||||||
return Event{AltA + int(r) - 'a', 0, nil}
|
|
||||||
}
|
|
||||||
if r >= '0' && r <= '9' {
|
|
||||||
return Event{Alt0 + int(r) - '0', 0, nil}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return Event{Rune, r, nil}
|
return Event{Rune, r, nil}
|
||||||
|
|
||||||
|
@@ -8,8 +8,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Types of user action
|
// Types of user action
|
||||||
|
type EventType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Rune = iota
|
Rune EventType = iota
|
||||||
|
|
||||||
CtrlA
|
CtrlA
|
||||||
CtrlB
|
CtrlB
|
||||||
@@ -89,8 +91,6 @@ const (
|
|||||||
Change
|
Change
|
||||||
BackwardEOF
|
BackwardEOF
|
||||||
|
|
||||||
AltSpace
|
|
||||||
AltSlash
|
|
||||||
AltBS
|
AltBS
|
||||||
|
|
||||||
AltUp
|
AltUp
|
||||||
@@ -103,20 +103,38 @@ const (
|
|||||||
AltSLeft
|
AltSLeft
|
||||||
AltSRight
|
AltSRight
|
||||||
|
|
||||||
Alt0
|
Alt
|
||||||
|
CtrlAlt
|
||||||
)
|
)
|
||||||
|
|
||||||
const ( // Reset iota
|
func (t EventType) AsEvent() Event {
|
||||||
AltA = Alt0 + 'a' - '0' + iota
|
return Event{t, 0, nil}
|
||||||
AltB
|
}
|
||||||
AltC
|
|
||||||
AltD
|
func (t EventType) Int() int {
|
||||||
AltE
|
return int(t)
|
||||||
AltF
|
}
|
||||||
AltZ = AltA + 'z' - 'a'
|
|
||||||
CtrlAltA = AltZ + 1
|
func (t EventType) Byte() byte {
|
||||||
CtrlAltM = CtrlAltA + 'm' - 'a'
|
return byte(t)
|
||||||
)
|
}
|
||||||
|
|
||||||
|
func (e Event) Comparable() Event {
|
||||||
|
// Ignore MouseEvent pointer
|
||||||
|
return Event{e.Type, e.Char, nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Key(r rune) Event {
|
||||||
|
return Event{Rune, r, nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AltKey(r rune) Event {
|
||||||
|
return Event{Alt, r, nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CtrlAltKey(r rune) Event {
|
||||||
|
return Event{CtrlAlt, r, nil}
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
doubleClickDuration = 500 * time.Millisecond
|
doubleClickDuration = 500 * time.Millisecond
|
||||||
@@ -232,6 +250,7 @@ func (p ColorPair) MergeNonDefault(other ColorPair) ColorPair {
|
|||||||
type ColorTheme struct {
|
type ColorTheme struct {
|
||||||
Colored bool
|
Colored bool
|
||||||
Input ColorAttr
|
Input ColorAttr
|
||||||
|
Disabled ColorAttr
|
||||||
Fg ColorAttr
|
Fg ColorAttr
|
||||||
Bg ColorAttr
|
Bg ColorAttr
|
||||||
PreviewFg ColorAttr
|
PreviewFg ColorAttr
|
||||||
@@ -251,7 +270,7 @@ type ColorTheme struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
Type int
|
Type EventType
|
||||||
Char rune
|
Char rune
|
||||||
MouseEvent *MouseEvent
|
MouseEvent *MouseEvent
|
||||||
}
|
}
|
||||||
@@ -403,6 +422,7 @@ var (
|
|||||||
ColPrompt ColorPair
|
ColPrompt ColorPair
|
||||||
ColNormal ColorPair
|
ColNormal ColorPair
|
||||||
ColInput ColorPair
|
ColInput ColorPair
|
||||||
|
ColDisabled ColorPair
|
||||||
ColMatch ColorPair
|
ColMatch ColorPair
|
||||||
ColCursor ColorPair
|
ColCursor ColorPair
|
||||||
ColCursorEmpty ColorPair
|
ColCursorEmpty ColorPair
|
||||||
@@ -425,6 +445,7 @@ func EmptyTheme() *ColorTheme {
|
|||||||
return &ColorTheme{
|
return &ColorTheme{
|
||||||
Colored: true,
|
Colored: true,
|
||||||
Input: ColorAttr{colUndefined, AttrUndefined},
|
Input: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
Disabled: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Fg: ColorAttr{colUndefined, AttrUndefined},
|
Fg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Bg: ColorAttr{colUndefined, AttrUndefined},
|
Bg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
|
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
@@ -447,6 +468,7 @@ func NoColorTheme() *ColorTheme {
|
|||||||
return &ColorTheme{
|
return &ColorTheme{
|
||||||
Colored: false,
|
Colored: false,
|
||||||
Input: ColorAttr{colDefault, AttrRegular},
|
Input: ColorAttr{colDefault, AttrRegular},
|
||||||
|
Disabled: ColorAttr{colDefault, AttrRegular},
|
||||||
Fg: ColorAttr{colDefault, AttrRegular},
|
Fg: ColorAttr{colDefault, AttrRegular},
|
||||||
Bg: ColorAttr{colDefault, AttrRegular},
|
Bg: ColorAttr{colDefault, AttrRegular},
|
||||||
PreviewFg: ColorAttr{colDefault, AttrRegular},
|
PreviewFg: ColorAttr{colDefault, AttrRegular},
|
||||||
@@ -474,6 +496,7 @@ func init() {
|
|||||||
Default16 = &ColorTheme{
|
Default16 = &ColorTheme{
|
||||||
Colored: true,
|
Colored: true,
|
||||||
Input: ColorAttr{colDefault, AttrUndefined},
|
Input: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
Disabled: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Fg: ColorAttr{colDefault, AttrUndefined},
|
Fg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
|
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
@@ -493,6 +516,7 @@ func init() {
|
|||||||
Dark256 = &ColorTheme{
|
Dark256 = &ColorTheme{
|
||||||
Colored: true,
|
Colored: true,
|
||||||
Input: ColorAttr{colDefault, AttrUndefined},
|
Input: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
Disabled: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Fg: ColorAttr{colDefault, AttrUndefined},
|
Fg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
|
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
@@ -512,6 +536,7 @@ func init() {
|
|||||||
Light256 = &ColorTheme{
|
Light256 = &ColorTheme{
|
||||||
Colored: true,
|
Colored: true,
|
||||||
Input: ColorAttr{colDefault, AttrUndefined},
|
Input: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
Disabled: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Fg: ColorAttr{colDefault, AttrUndefined},
|
Fg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
|
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
@@ -546,6 +571,7 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
theme.Input = o(baseTheme.Input, theme.Input)
|
theme.Input = o(baseTheme.Input, theme.Input)
|
||||||
|
theme.Disabled = o(theme.Input, o(baseTheme.Disabled, theme.Disabled))
|
||||||
theme.Fg = o(baseTheme.Fg, theme.Fg)
|
theme.Fg = o(baseTheme.Fg, theme.Fg)
|
||||||
theme.Bg = o(baseTheme.Bg, theme.Bg)
|
theme.Bg = o(baseTheme.Bg, theme.Bg)
|
||||||
theme.PreviewFg = o(theme.Fg, o(baseTheme.PreviewFg, theme.PreviewFg))
|
theme.PreviewFg = o(theme.Fg, o(baseTheme.PreviewFg, theme.PreviewFg))
|
||||||
@@ -579,6 +605,7 @@ func initPalette(theme *ColorTheme) {
|
|||||||
ColPrompt = pair(theme.Prompt, theme.Bg)
|
ColPrompt = pair(theme.Prompt, theme.Bg)
|
||||||
ColNormal = pair(theme.Fg, theme.Bg)
|
ColNormal = pair(theme.Fg, theme.Bg)
|
||||||
ColInput = pair(theme.Input, theme.Bg)
|
ColInput = pair(theme.Input, theme.Bg)
|
||||||
|
ColDisabled = pair(theme.Disabled, theme.Bg)
|
||||||
ColMatch = pair(theme.Match, theme.Bg)
|
ColMatch = pair(theme.Match, theme.Bg)
|
||||||
ColCursor = pair(theme.Cursor, theme.Gutter)
|
ColCursor = pair(theme.Cursor, theme.Gutter)
|
||||||
ColCursorEmpty = pair(blank, theme.Gutter)
|
ColCursorEmpty = pair(blank, theme.Gutter)
|
||||||
|
223
test/test_go.rb
223
test/test_go.rb
@@ -148,14 +148,15 @@ class Tmux
|
|||||||
def prepare
|
def prepare
|
||||||
tries = 0
|
tries = 0
|
||||||
begin
|
begin
|
||||||
self.until do |lines|
|
self.until(true) do |lines|
|
||||||
send_keys ' ', 'C-u', :Enter, 'hello', :Left, :Right
|
message = "Prepare[#{tries}]"
|
||||||
lines[-1] == 'hello'
|
send_keys ' ', 'C-u', :Enter, message, :Left, :Right
|
||||||
|
lines[-1] == message
|
||||||
end
|
end
|
||||||
rescue Minitest::Assertion
|
rescue Minitest::Assertion
|
||||||
(tries += 1) < 5 ? retry : raise
|
(tries += 1) < 5 ? retry : raise
|
||||||
end
|
end
|
||||||
send_keys 'C-u'
|
send_keys 'C-u', 'C-l'
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -1554,8 +1555,8 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| assert_equal '> 1', lines[-2] }
|
tmux.until { |lines| assert_equal '> 1', lines[-2] }
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_change_top
|
def test_change_first_last
|
||||||
tmux.send_keys %(seq 1000 | #{FZF} --bind change:top), :Enter
|
tmux.send_keys %(seq 1000 | #{FZF} --bind change:first,alt-Z:last), :Enter
|
||||||
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||||
tmux.send_keys :Up
|
tmux.send_keys :Up
|
||||||
tmux.until { |lines| assert_equal '> 2', lines[-4] }
|
tmux.until { |lines| assert_equal '> 2', lines[-4] }
|
||||||
@@ -1565,6 +1566,10 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| assert_equal '> 10', lines[-4] }
|
tmux.until { |lines| assert_equal '> 10', lines[-4] }
|
||||||
tmux.send_keys 1
|
tmux.send_keys 1
|
||||||
tmux.until { |lines| assert_equal '> 11', lines[-3] }
|
tmux.until { |lines| assert_equal '> 11', lines[-3] }
|
||||||
|
tmux.send_keys 'C-u'
|
||||||
|
tmux.until { |lines| assert_equal '> 1', lines[-3] }
|
||||||
|
tmux.send_keys :Escape, 'Z'
|
||||||
|
tmux.until { |lines| assert_equal '> 1000', lines[0] }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1654,13 +1659,35 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| assert_includes lines[1], ' + green ' }
|
tmux.until { |lines| assert_includes lines[1], ' + green ' }
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_phony
|
def test_disabled
|
||||||
tmux.send_keys %(seq 1000 | #{FZF} --query 333 --phony --preview 'echo {} {q}'), :Enter
|
tmux.send_keys %(seq 1000 | #{FZF} --query 333 --disabled --bind a:enable-search,b:disable-search,c:toggle-search --preview 'echo {} {q}'), :Enter
|
||||||
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||||
tmux.until { |lines| assert_includes lines[1], ' 1 333 ' }
|
tmux.until { |lines| assert_includes lines[1], ' 1 333 ' }
|
||||||
tmux.send_keys 'foo'
|
tmux.send_keys 'foo'
|
||||||
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||||
tmux.until { |lines| assert_includes lines[1], ' 1 333foo ' }
|
tmux.until { |lines| assert_includes lines[1], ' 1 333foo ' }
|
||||||
|
|
||||||
|
# Already disabled, no change
|
||||||
|
tmux.send_keys 'b'
|
||||||
|
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||||
|
|
||||||
|
# Enable search
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||||
|
tmux.send_keys :BSpace, :BSpace, :BSpace
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' 333 333 ' }
|
||||||
|
|
||||||
|
# Toggle search -> disabled again, but retains the previous result
|
||||||
|
tmux.send_keys 'c'
|
||||||
|
tmux.send_keys 'foo'
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' 333 333foo ' }
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
|
||||||
|
# Enabled, no match
|
||||||
|
tmux.send_keys 'c'
|
||||||
|
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' 333foo ' }
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_reload
|
def test_reload
|
||||||
@@ -1837,6 +1864,184 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys "#{FZF} --preview 'seq 1000 | nl' --preview-window down:noborder:follow", :Enter
|
tmux.send_keys "#{FZF} --preview 'seq 1000 | nl' --preview-window down:noborder:follow", :Enter
|
||||||
tmux.until { |lines| assert_equal '1000 1000', lines[-1].strip }
|
tmux.until { |lines| assert_equal '1000 1000', lines[-1].strip }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_toggle_preview_wrap
|
||||||
|
tmux.send_keys "#{FZF} --preview 'for i in $(seq $FZF_PREVIEW_COLUMNS); do echo -n .; done; echo wrapped; echo 2nd line' --bind ctrl-w:toggle-preview-wrap", :Enter
|
||||||
|
2.times do
|
||||||
|
tmux.until { |lines| assert_includes lines[2], '2nd line' }
|
||||||
|
tmux.send_keys 'C-w'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines[2], 'wrapped'
|
||||||
|
assert_includes lines[3], '2nd line'
|
||||||
|
end
|
||||||
|
tmux.send_keys 'C-w'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_close
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --preview 'echo foo' --bind ctrl-c:close", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||||
|
tmux.until { |lines| assert_includes lines[1], 'foo' }
|
||||||
|
tmux.send_keys 'C-c'
|
||||||
|
tmux.until { |lines| refute_includes lines[1], 'foo' }
|
||||||
|
tmux.send_keys '10'
|
||||||
|
tmux.until { |lines| assert_equal 2, lines.match_count }
|
||||||
|
tmux.send_keys 'C-c'
|
||||||
|
tmux.send_keys 'C-l', 'closed'
|
||||||
|
tmux.until { |lines| assert_includes lines[0], 'closed' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_select_deselect
|
||||||
|
tmux.send_keys "seq 3 | #{FZF} --multi --bind up:deselect+up,down:select+down", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 3, lines.match_count }
|
||||||
|
tmux.send_keys :Tab
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.select_count }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_equal 0, lines.select_count }
|
||||||
|
tmux.send_keys :Down, :Down
|
||||||
|
tmux.until { |lines| assert_equal 2, lines.select_count }
|
||||||
|
tmux.send_keys :Tab
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.select_count }
|
||||||
|
tmux.send_keys :Down, :Down
|
||||||
|
tmux.until { |lines| assert_equal 2, lines.select_count }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.select_count }
|
||||||
|
tmux.send_keys :Down
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.select_count }
|
||||||
|
tmux.send_keys :Down
|
||||||
|
tmux.until { |lines| assert_equal 2, lines.select_count }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_interrupt_execute
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --bind 'ctrl-l:execute:echo executing {}; sleep 100'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||||
|
tmux.send_keys 'C-l'
|
||||||
|
tmux.until { |lines| assert lines.any_include?('executing 1') }
|
||||||
|
tmux.send_keys 'C-c'
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||||
|
tmux.send_keys 99
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_kill_default_command_on_abort
|
||||||
|
script = tempname + '.sh'
|
||||||
|
writelines(script,
|
||||||
|
['#!/usr/bin/env bash',
|
||||||
|
"echo 'Started'",
|
||||||
|
'while :; do sleep 1; done'])
|
||||||
|
system("chmod +x #{script}")
|
||||||
|
|
||||||
|
tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', "FZF_DEFAULT_COMMAND=#{script}"), :Enter
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.item_count }
|
||||||
|
tmux.send_keys 'C-c'
|
||||||
|
tmux.send_keys 'C-l', 'closed'
|
||||||
|
tmux.until { |lines| assert_includes lines[0], 'closed' }
|
||||||
|
wait { refute system("pgrep -f #{script}") }
|
||||||
|
ensure
|
||||||
|
system("pkill -9 -f #{script}")
|
||||||
|
begin
|
||||||
|
File.unlink(script)
|
||||||
|
rescue StandardError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_kill_default_command_on_accept
|
||||||
|
script = tempname + '.sh'
|
||||||
|
writelines(script,
|
||||||
|
['#!/usr/bin/env bash',
|
||||||
|
"echo 'Started'",
|
||||||
|
'while :; do sleep 1; done'])
|
||||||
|
system("chmod +x #{script}")
|
||||||
|
|
||||||
|
tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', "FZF_DEFAULT_COMMAND=#{script}"), :Enter
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.item_count }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
assert_equal 'Started', readonce.chomp
|
||||||
|
wait { refute system("pgrep -f #{script}") }
|
||||||
|
ensure
|
||||||
|
system("pkill -9 -f #{script}")
|
||||||
|
begin
|
||||||
|
File.unlink(script)
|
||||||
|
rescue StandardError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_kill_reload_command_on_abort
|
||||||
|
script = tempname + '.sh'
|
||||||
|
writelines(script,
|
||||||
|
['#!/usr/bin/env bash',
|
||||||
|
"echo 'Started'",
|
||||||
|
'while :; do sleep 1; done'])
|
||||||
|
system("chmod +x #{script}")
|
||||||
|
|
||||||
|
tmux.send_keys "seq 1 3 | #{fzf("--bind 'ctrl-r:reload(#{script})'")}", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 3, lines.item_count }
|
||||||
|
tmux.send_keys 'C-r'
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.item_count }
|
||||||
|
tmux.send_keys 'C-c'
|
||||||
|
tmux.send_keys 'C-l', 'closed'
|
||||||
|
tmux.until { |lines| assert_includes lines[0], 'closed' }
|
||||||
|
wait { refute system("pgrep -f #{script}") }
|
||||||
|
ensure
|
||||||
|
system("pkill -9 -f #{script}")
|
||||||
|
begin
|
||||||
|
File.unlink(script)
|
||||||
|
rescue StandardError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_kill_reload_command_on_accept
|
||||||
|
script = tempname + '.sh'
|
||||||
|
writelines(script,
|
||||||
|
['#!/usr/bin/env bash',
|
||||||
|
"echo 'Started'",
|
||||||
|
'while :; do sleep 1; done'])
|
||||||
|
system("chmod +x #{script}")
|
||||||
|
|
||||||
|
tmux.send_keys "seq 1 3 | #{fzf("--bind 'ctrl-r:reload(#{script})'")}", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 3, lines.item_count }
|
||||||
|
tmux.send_keys 'C-r'
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.item_count }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
assert_equal 'Started', readonce.chomp
|
||||||
|
wait { refute system("pgrep -f #{script}") }
|
||||||
|
ensure
|
||||||
|
system("pkill -9 -f #{script}")
|
||||||
|
begin
|
||||||
|
File.unlink(script)
|
||||||
|
rescue StandardError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_header
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --bind ctrl-k:preview-up+preview-up,ctrl-j:preview-down+preview-down+preview-down --preview 'seq 1000' --preview-window 'top:+{1}:~3'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||||
|
top5 = ->(lines) { lines.drop(1).take(5).map { |s| s[/[0-9]+/] } }
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines[1], '4/1000'
|
||||||
|
assert_equal(%w[1 2 3 4 5], top5[lines])
|
||||||
|
end
|
||||||
|
tmux.send_keys '55'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
assert_equal(%w[1 2 3 55 56], top5[lines])
|
||||||
|
end
|
||||||
|
tmux.send_keys 'C-J'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal(%w[1 2 3 58 59], top5[lines])
|
||||||
|
end
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 19, lines.match_count
|
||||||
|
assert_equal(%w[1 2 3 5 6], top5[lines])
|
||||||
|
end
|
||||||
|
tmux.send_keys 'C-K'
|
||||||
|
tmux.until { |lines| assert_equal(%w[1 2 3 4 5], top5[lines]) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module TestShell
|
module TestShell
|
||||||
@@ -2007,7 +2212,7 @@ module CompletionTest
|
|||||||
end
|
end
|
||||||
|
|
||||||
# ~USERNAME**<TAB>
|
# ~USERNAME**<TAB>
|
||||||
user = ENV['USER']
|
user = `whoami`.chomp
|
||||||
tmux.send_keys 'C-u'
|
tmux.send_keys 'C-u'
|
||||||
tmux.send_keys "cat ~#{user}**", :Tab
|
tmux.send_keys "cat ~#{user}**", :Tab
|
||||||
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||||
|
Reference in New Issue
Block a user