mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-13 13:53:47 -05:00
Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
260a65b0fb | ||
|
|
835d2fb98c | ||
|
|
a9811addaa | ||
|
|
ee9d88b637 | ||
|
|
194a763c46 | ||
|
|
8d74446bef | ||
|
|
7ed6c7905c | ||
|
|
159a37fa37 | ||
|
|
f39ae0e7c1 | ||
|
|
4a68eac99b | ||
|
|
2665580120 | ||
|
|
a4391aeedd | ||
|
|
b86a967ee2 | ||
|
|
608232568b | ||
|
|
7f85beccb5 | ||
|
|
767f1255ab | ||
|
|
fddbfe7b0e | ||
|
|
4ab7fdc28e | ||
|
|
e352b68878 | ||
|
|
207deeadba | ||
|
|
d18d92f925 | ||
|
|
af3ce47c44 | ||
|
|
d8bfb6712d | ||
|
|
f864f8b5f7 | ||
|
|
31d72efba7 | ||
|
|
d169c951f3 | ||
|
|
90d7e38909 | ||
|
|
938f23e429 | ||
|
|
f97d275413 | ||
|
|
3acb4ca90e | ||
|
|
e86b81bbf5 | ||
|
|
a5447b8b75 | ||
|
|
7ce6452d83 | ||
|
|
5643a306bd | ||
|
|
3c877c504b | ||
|
|
892d1acccb | ||
|
|
1a9c282f76 | ||
|
|
fd1ba46f77 | ||
|
|
a4745626dd | ||
|
|
17bb7ad278 | ||
|
|
152988c17b | ||
|
|
4cd37fc02b | ||
|
|
69b9d674a3 | ||
|
|
bad8061547 | ||
|
|
62963dcefd | ||
|
|
68a35e4735 | ||
|
|
9b9ad77e1c | ||
|
|
118b4d4a01 | ||
|
|
da14ab6f16 | ||
|
|
09a4ca6ab5 | ||
|
|
8a2df79711 | ||
|
|
c30e486b64 | ||
|
|
a575c0c54b | ||
|
|
77fe96ac0d | ||
|
|
5234c3759a | ||
|
|
41b3511ad9 | ||
|
|
128e4a2e8d | ||
|
|
07ac90d798 | ||
|
|
7de87a9b2c | ||
|
|
dff865239a | ||
|
|
07f8f70c5b | ||
|
|
f625c5aabe | ||
|
|
8a74976c1f | ||
|
|
b6bfd4a5cb | ||
|
|
008fb9d258 | ||
|
|
db6db49ed6 | ||
|
|
05453881c3 | ||
|
|
5e47ab9431 | ||
|
|
ec70acd0b9 | ||
|
|
25e61056b6 |
22
.github/ISSUE_TEMPLATE.md
vendored
22
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,22 +0,0 @@
|
|||||||
<!-- ISSUES NOT FOLLOWING THIS TEMPLATE WILL BE CLOSED AND DELETED -->
|
|
||||||
|
|
||||||
<!-- Check all that apply [x] -->
|
|
||||||
|
|
||||||
- [ ] I have read through the manual page (`man fzf`)
|
|
||||||
- [ ] I have the latest version of fzf
|
|
||||||
- [ ] I have searched through the existing issues
|
|
||||||
|
|
||||||
## Info
|
|
||||||
|
|
||||||
- OS
|
|
||||||
- [ ] Linux
|
|
||||||
- [ ] Mac OS X
|
|
||||||
- [ ] Windows
|
|
||||||
- [ ] Etc.
|
|
||||||
- Shell
|
|
||||||
- [ ] bash
|
|
||||||
- [ ] zsh
|
|
||||||
- [ ] fish
|
|
||||||
|
|
||||||
## Problem / Steps to reproduce
|
|
||||||
|
|
||||||
49
.github/ISSUE_TEMPLATE/issue_template.yml
vendored
Normal file
49
.github/ISSUE_TEMPLATE/issue_template.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
---
|
||||||
|
name: Issue Template
|
||||||
|
description: Report a problem or bug related to fzf to help us improve
|
||||||
|
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: ISSUES NOT FOLLOWING THIS TEMPLATE WILL BE CLOSED AND DELETED
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Checklist
|
||||||
|
options:
|
||||||
|
- label: I have read through the manual page (`man fzf`)
|
||||||
|
required: true
|
||||||
|
- label: I have searched through the existing issues
|
||||||
|
required: true
|
||||||
|
- label: For bug reports, I have checked if the bug is reproducible in the latest version of fzf
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Output of `fzf --version`
|
||||||
|
placeholder: e.g. 0.48.1 (d579e33)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: OS
|
||||||
|
options:
|
||||||
|
- label: Linux
|
||||||
|
- label: macOS
|
||||||
|
- label: Windows
|
||||||
|
- label: Etc.
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Shell
|
||||||
|
options:
|
||||||
|
- label: bash
|
||||||
|
- label: zsh
|
||||||
|
- label: fish
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Problem / Steps to reproduce
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
2
.github/workflows/linux.yml
vendored
2
.github/workflows/linux.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.19
|
go-version: "1.20"
|
||||||
|
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
uses: ruby/setup-ruby@v1
|
uses: ruby/setup-ruby@v1
|
||||||
|
|||||||
2
.github/workflows/macos.yml
vendored
2
.github/workflows/macos.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.18
|
go-version: "1.20"
|
||||||
|
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
uses: ruby/setup-ruby@v1
|
uses: ruby/setup-ruby@v1
|
||||||
|
|||||||
2
.github/workflows/typos.yml
vendored
2
.github/workflows/typos.yml
vendored
@@ -7,4 +7,4 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: crate-ci/typos@v1.19.0
|
- uses: crate-ci/typos@v1.20.10
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ builds:
|
|||||||
- darwin
|
- darwin
|
||||||
goarch:
|
goarch:
|
||||||
- amd64
|
- amd64
|
||||||
|
flags:
|
||||||
|
- -trimpath
|
||||||
ldflags:
|
ldflags:
|
||||||
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
||||||
hooks:
|
hooks:
|
||||||
@@ -19,11 +21,7 @@ builds:
|
|||||||
sh -c '
|
sh -c '
|
||||||
cat > /tmp/fzf-gon-amd64.hcl << EOF
|
cat > /tmp/fzf-gon-amd64.hcl << EOF
|
||||||
source = ["./dist/fzf-macos_darwin_amd64_v1/fzf"]
|
source = ["./dist/fzf-macos_darwin_amd64_v1/fzf"]
|
||||||
bundle_id = "kr.junegunn.fzf"
|
bundle_id = "junegunn.fzf"
|
||||||
apple_id {
|
|
||||||
username = "junegunn.c@gmail.com"
|
|
||||||
password = "@env:AC_PASSWORD"
|
|
||||||
}
|
|
||||||
sign {
|
sign {
|
||||||
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
|
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
|
||||||
}
|
}
|
||||||
@@ -40,6 +38,8 @@ builds:
|
|||||||
- darwin
|
- darwin
|
||||||
goarch:
|
goarch:
|
||||||
- arm64
|
- arm64
|
||||||
|
flags:
|
||||||
|
- -trimpath
|
||||||
ldflags:
|
ldflags:
|
||||||
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
||||||
hooks:
|
hooks:
|
||||||
@@ -47,11 +47,7 @@ builds:
|
|||||||
sh -c '
|
sh -c '
|
||||||
cat > /tmp/fzf-gon-arm64.hcl << EOF
|
cat > /tmp/fzf-gon-arm64.hcl << EOF
|
||||||
source = ["./dist/fzf-macos-arm_darwin_arm64/fzf"]
|
source = ["./dist/fzf-macos-arm_darwin_arm64/fzf"]
|
||||||
bundle_id = "kr.junegunn.fzf"
|
bundle_id = "junegunn.fzf"
|
||||||
apple_id {
|
|
||||||
username = "junegunn.c@gmail.com"
|
|
||||||
password = "@env:AC_PASSWORD"
|
|
||||||
}
|
|
||||||
sign {
|
sign {
|
||||||
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
|
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
|
||||||
}
|
}
|
||||||
@@ -79,6 +75,8 @@ builds:
|
|||||||
- 5
|
- 5
|
||||||
- 6
|
- 6
|
||||||
- 7
|
- 7
|
||||||
|
flags:
|
||||||
|
- -trimpath
|
||||||
ldflags:
|
ldflags:
|
||||||
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
||||||
ignore:
|
ignore:
|
||||||
|
|||||||
16
BUILD.md
16
BUILD.md
@@ -6,7 +6,7 @@ Build instructions
|
|||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- Go 1.18 or above
|
- Go 1.20 or above
|
||||||
|
|
||||||
### Using Makefile
|
### Using Makefile
|
||||||
|
|
||||||
@@ -24,13 +24,23 @@ make build
|
|||||||
make release
|
make release
|
||||||
```
|
```
|
||||||
|
|
||||||
> :warning: Makefile uses git commands to determine the version and the
|
> [!WARNING]
|
||||||
> revision information for `fzf --version`. So if you're building fzf from an
|
> Makefile uses git commands to determine the version and the revision
|
||||||
|
> information for `fzf --version`. So if you're building fzf from an
|
||||||
> environment where its git information is not available, you have to manually
|
> environment where its git information is not available, you have to manually
|
||||||
> set `$FZF_VERSION` and `$FZF_REVISION`.
|
> set `$FZF_VERSION` and `$FZF_REVISION`.
|
||||||
>
|
>
|
||||||
> e.g. `FZF_VERSION=0.24.0 FZF_REVISION=tarball make`
|
> e.g. `FZF_VERSION=0.24.0 FZF_REVISION=tarball make`
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> To build fzf with profiling options enabled, set `TAGS=pprof`
|
||||||
|
>
|
||||||
|
> ```sh
|
||||||
|
> TAGS=pprof make clean install
|
||||||
|
> fzf --profile-cpu /tmp/cpu.pprof --profile-mem /tmp/mem.pprof \
|
||||||
|
> --profile-block /tmp/block.pprof --profile-mutex /tmp/mutex.pprof
|
||||||
|
> ```
|
||||||
|
|
||||||
Third-party libraries used
|
Third-party libraries used
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
|
|||||||
96
CHANGELOG.md
96
CHANGELOG.md
@@ -1,6 +1,102 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.51.0
|
||||||
|
------
|
||||||
|
- Added a new environment variable `$FZF_POS` exported to the child processes. It's the vertical position of the cursor in the list starting from 1.
|
||||||
|
```sh
|
||||||
|
# Toggle selection to the top or to the bottom
|
||||||
|
seq 30 | fzf --multi --bind 'load:pos(10)' \
|
||||||
|
--bind 'shift-up:transform:for _ in $(seq $FZF_POS $FZF_MATCH_COUNT); do echo -n +toggle+up; done' \
|
||||||
|
--bind 'shift-down:transform:for _ in $(seq 1 $FZF_POS); do echo -n +toggle+down; done'
|
||||||
|
```
|
||||||
|
- Added `--with-shell` option to start child processes with a custom shell command and flags
|
||||||
|
```sh
|
||||||
|
gem list | fzf --with-shell 'ruby -e' \
|
||||||
|
--preview 'pp Gem::Specification.find_by_name({1})' \
|
||||||
|
--bind 'ctrl-o:execute-silent:
|
||||||
|
spec = Gem::Specification.find_by_name({1})
|
||||||
|
[spec.homepage, *spec.metadata.filter { _1.end_with?("uri") }.values].uniq.each do
|
||||||
|
system "open", _1
|
||||||
|
end
|
||||||
|
'
|
||||||
|
```
|
||||||
|
- Added `change-multi` action for dynamically changing `--multi` option
|
||||||
|
- `change-multi` - enable multi-select mode with no limit
|
||||||
|
- `change-multi(NUM)` - enable multi-select mode with a limit
|
||||||
|
- `change-multi(0)` - disable multi-select mode
|
||||||
|
- Windows improvements
|
||||||
|
- `become` action is now supported on Windows
|
||||||
|
- Unlike in *nix, this does not use `execve(2)`. Instead it spawns a new process and waits for it to finish, so the exact behavior may differ.
|
||||||
|
- Fixed argument escaping for Windows cmd.exe. No redundant escaping of backslashes.
|
||||||
|
- Bug fixes and improvements
|
||||||
|
|
||||||
|
0.50.0
|
||||||
|
------
|
||||||
|
- Search performance optimization. You can observe 50%+ improvement in some scenarios.
|
||||||
|
```
|
||||||
|
$ rg --line-number --no-heading --smart-case . > $DATA
|
||||||
|
|
||||||
|
$ wc < $DATA
|
||||||
|
5520118 26862362 897487793
|
||||||
|
|
||||||
|
$ hyperfine -w 1 -L bin fzf-0.49.0,fzf-7ce6452,fzf-a5447b8,fzf '{bin} --filter "///" < $DATA | head -30'
|
||||||
|
Summary
|
||||||
|
fzf --filter "///" < $DATA | head -30 ran
|
||||||
|
1.16 ± 0.03 times faster than fzf-a5447b8 --filter "///" < $DATA | head -30
|
||||||
|
1.23 ± 0.03 times faster than fzf-7ce6452 --filter "///" < $DATA | head -30
|
||||||
|
1.52 ± 0.03 times faster than fzf-0.49.0 --filter "///" < $DATA | head -30
|
||||||
|
```
|
||||||
|
- Added `jump` and `jump-cancel` events that are triggered when leaving `jump` mode
|
||||||
|
```sh
|
||||||
|
# Default behavior
|
||||||
|
fzf --bind space:jump
|
||||||
|
|
||||||
|
# Same as jump-accept action
|
||||||
|
fzf --bind space:jump,jump:accept
|
||||||
|
|
||||||
|
# Accept on jump, abort on cancel
|
||||||
|
fzf --bind space:jump,jump:accept,jump-cancel:abort
|
||||||
|
|
||||||
|
# Change header on jump-cancel
|
||||||
|
fzf --bind 'space:change-header(Type jump label)+jump,jump-cancel:change-header:Jump cancelled'
|
||||||
|
```
|
||||||
|
- Added a new environment variable `$FZF_KEY` exported to the child processes. It's the name of the last key pressed.
|
||||||
|
```sh
|
||||||
|
fzf --bind 'space:jump,jump:accept,jump-cancel:transform:[[ $FZF_KEY =~ ctrl-c ]] && echo abort'
|
||||||
|
```
|
||||||
|
- fzf can be built with profiling options. See [BUILD.md](BUILD.md) for more information.
|
||||||
|
- Bug fixes
|
||||||
|
|
||||||
|
0.49.0
|
||||||
|
------
|
||||||
|
- Ingestion performance improved by around 40% (more or less depending on options)
|
||||||
|
- `--info=hidden` and `--info=inline-right` will no longer hide the horizontal separator by default. This gives you more flexibility in customizing the layout.
|
||||||
|
```sh
|
||||||
|
fzf --border --info=inline-right
|
||||||
|
fzf --border --info=inline-right --separator ═
|
||||||
|
fzf --border --info=inline-right --no-separator
|
||||||
|
fzf --border --info=hidden
|
||||||
|
fzf --border --info=hidden --separator ━
|
||||||
|
fzf --border --info=hidden --no-separator
|
||||||
|
```
|
||||||
|
- Added two environment variables exported to the child processes
|
||||||
|
- `FZF_PREVIEW_LABEL`
|
||||||
|
- `FZF_BORDER_LABEL`
|
||||||
|
```sh
|
||||||
|
# Use the current value of $FZF_PREVIEW_LABEL to determine which actions to perform
|
||||||
|
git ls-files |
|
||||||
|
fzf --header 'Press CTRL-P to change preview mode' \
|
||||||
|
--bind='ctrl-p:transform:[[ $FZF_PREVIEW_LABEL =~ cat ]] \
|
||||||
|
&& echo "change-preview(git log --color=always \{})+change-preview-label([[ log ]])" \
|
||||||
|
|| echo "change-preview(bat --color=always \{})+change-preview-label([[ cat ]])"'
|
||||||
|
```
|
||||||
|
- Renamed `track` action to `track-current` to highlight the difference between the global tracking state set by `--track` and a one-off tracking action
|
||||||
|
- `track` is still available as an alias
|
||||||
|
- Added `untrack-current` and `toggle-track-current` actions
|
||||||
|
- `*-current` actions are no-op when the global tracking state is set
|
||||||
|
- Bug fixes and minor improvements
|
||||||
|
|
||||||
0.48.1
|
0.48.1
|
||||||
------
|
------
|
||||||
- CTRL-T and ALT-C bindings can be disabled by setting `FZF_CTRL_T_COMMAND` and `FZF_ALT_C_COMMAND` to empty strings respectively when sourcing the script
|
- CTRL-T and ALT-C bindings can be disabled by setting `FZF_CTRL_T_COMMAND` and `FZF_ALT_C_COMMAND` to empty strings respectively when sourcing the script
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
FROM --platform=linux/amd64 ubuntu:22.04
|
FROM ubuntu:24.04
|
||||||
RUN apt-get update -y && apt install -y git make golang zsh fish ruby tmux
|
RUN apt-get update -y && apt install -y git make golang zsh fish ruby tmux
|
||||||
RUN gem install --no-document -v 5.14.2 minitest
|
RUN gem install --no-document -v 5.22.3 minitest
|
||||||
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
||||||
RUN echo '. ~/.bashrc' >> ~/.bash_profile
|
RUN echo '. ~/.bashrc' >> ~/.bash_profile
|
||||||
|
|
||||||
|
|||||||
11
Makefile
11
Makefile
@@ -25,7 +25,7 @@ endif
|
|||||||
ifeq ($(REVISION),)
|
ifeq ($(REVISION),)
|
||||||
$(error Not on git repository; cannot determine $$FZF_REVISION)
|
$(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)" -trimpath
|
||||||
|
|
||||||
BINARY32 := fzf-$(GOOS)_386
|
BINARY32 := fzf-$(GOOS)_386
|
||||||
BINARY64 := fzf-$(GOOS)_amd64
|
BINARY64 := fzf-$(GOOS)_amd64
|
||||||
@@ -79,6 +79,7 @@ all: target/$(BINARY)
|
|||||||
test: $(SOURCES)
|
test: $(SOURCES)
|
||||||
[ -z "$$(gofmt -s -d src)" ] || (gofmt -s -d src; exit 1)
|
[ -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 \
|
||||||
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 \
|
||||||
@@ -173,12 +174,12 @@ bin/fzf: target/$(BINARY) | bin
|
|||||||
cp -f target/$(BINARY) bin/fzf
|
cp -f target/$(BINARY) bin/fzf
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
docker build -t fzf-arch .
|
docker build -t fzf-ubuntu .
|
||||||
docker run -it fzf-arch tmux
|
docker run -it fzf-ubuntu tmux
|
||||||
|
|
||||||
docker-test:
|
docker-test:
|
||||||
docker build -t fzf-arch .
|
docker build -t fzf-ubuntu .
|
||||||
docker run -it fzf-arch
|
docker run -it fzf-ubuntu
|
||||||
|
|
||||||
update:
|
update:
|
||||||
$(GO) get -u
|
$(GO) get -u
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ fail() {
|
|||||||
exit 2
|
exit 2
|
||||||
}
|
}
|
||||||
|
|
||||||
fzf="$(command -v fzf 2> /dev/null)" || fzf="$(dirname "$0")/fzf"
|
fzf="$(command which fzf)" || fzf="$(dirname "$0")/fzf"
|
||||||
[[ -x "$fzf" ]] || fail 'fzf executable not found'
|
[[ -x "$fzf" ]] || fail 'fzf executable not found'
|
||||||
|
|
||||||
args=()
|
args=()
|
||||||
|
|||||||
8
go.mod
8
go.mod
@@ -1,13 +1,13 @@
|
|||||||
module github.com/junegunn/fzf
|
module github.com/junegunn/fzf
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/charlievieth/fastwalk v1.0.2
|
github.com/charlievieth/fastwalk v1.0.3
|
||||||
github.com/gdamore/tcell/v2 v2.7.4
|
github.com/gdamore/tcell/v2 v2.7.4
|
||||||
github.com/mattn/go-isatty v0.0.20
|
github.com/mattn/go-isatty v0.0.20
|
||||||
github.com/mattn/go-shellwords v1.0.12
|
github.com/mattn/go-shellwords v1.0.12
|
||||||
github.com/rivo/uniseg v0.4.7
|
github.com/rivo/uniseg v0.4.7
|
||||||
golang.org/x/sys v0.18.0
|
golang.org/x/sys v0.19.0
|
||||||
golang.org/x/term v0.18.0
|
golang.org/x/term v0.19.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -17,4 +17,4 @@ require (
|
|||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.17
|
go 1.20
|
||||||
|
|||||||
12
go.sum
12
go.sum
@@ -1,5 +1,5 @@
|
|||||||
github.com/charlievieth/fastwalk v1.0.2 h1:KYWo7xszmoldOGrwdNIeznSzhj9mhgk+6DwHunG99bc=
|
github.com/charlievieth/fastwalk v1.0.3 h1:eNWFaNPe5srPqQ5yyDbhAf11paeZaHWcihRhpuYFfSg=
|
||||||
github.com/charlievieth/fastwalk v1.0.2/go.mod h1:JSfglY/gmL/rqsUS1NCsJTocB5n6sSl9ApAqif4CUbs=
|
github.com/charlievieth/fastwalk v1.0.3/go.mod h1:JSfglY/gmL/rqsUS1NCsJTocB5n6sSl9ApAqif4CUbs=
|
||||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||||
github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU=
|
github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU=
|
||||||
@@ -36,14 +36,14 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||||
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/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
|||||||
2
install
2
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.48.1
|
version=0.51.0
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
$version="0.48.1"
|
$version="0.51.0"
|
||||||
|
|
||||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/protector"
|
"github.com/junegunn/fzf/src/protector"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "0.48"
|
var version string = "0.51"
|
||||||
var revision string = "devel"
|
var revision string = "devel"
|
||||||
|
|
||||||
//go:embed shell/key-bindings.bash
|
//go:embed shell/key-bindings.bash
|
||||||
|
|||||||
174
main_test.go
Normal file
174
main_test.go
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/build"
|
||||||
|
"go/importer"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"go/types"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func loadPackages(t *testing.T) []*build.Package {
|
||||||
|
// If GOROOT is not set, use `go env GOROOT` to determine it since it
|
||||||
|
// performs more work than just runtime.GOROOT(). For context, running
|
||||||
|
// the tests with the "-trimpath" flag causes GOROOT to not be set.
|
||||||
|
ctxt := &build.Default
|
||||||
|
if ctxt.GOROOT == "" {
|
||||||
|
cmd := exec.Command("go", "env", "GOROOT")
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
out = bytes.TrimSpace(out)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error running command: %q: %v\n%s", cmd.Args, err, out)
|
||||||
|
}
|
||||||
|
ctxt.GOROOT = string(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pkgs []*build.Package
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
err = filepath.WalkDir(wd, func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
name := d.Name()
|
||||||
|
if d.IsDir() {
|
||||||
|
if name == "" || name[0] == '.' || name[0] == '_' || name == "vendor" || name == "tmp" {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if d.Type().IsRegular() && filepath.Ext(name) == ".go" && !strings.HasSuffix(name, "_test.go") {
|
||||||
|
dir := filepath.Dir(path)
|
||||||
|
if !seen[dir] {
|
||||||
|
pkg, err := ctxt.ImportDir(dir, build.ImportComment)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: %s", dir, err)
|
||||||
|
}
|
||||||
|
if pkg.ImportPath == "" || pkg.ImportPath == "." {
|
||||||
|
importPath, err := filepath.Rel(wd, dir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
pkg.ImportPath = filepath.ToSlash(filepath.Join("github.com/junegunn/fzf", importPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgs = append(pkgs, pkg)
|
||||||
|
seen[dir] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(pkgs, func(i, j int) bool {
|
||||||
|
return pkgs[i].ImportPath < pkgs[j].ImportPath
|
||||||
|
})
|
||||||
|
return pkgs
|
||||||
|
}
|
||||||
|
|
||||||
|
var sourceImporter = importer.ForCompiler(token.NewFileSet(), "source", nil)
|
||||||
|
|
||||||
|
func checkPackageForOsExit(t *testing.T, bpkg *build.Package, allowed map[string]int) (errOsExit bool) {
|
||||||
|
var files []*ast.File
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
for _, name := range bpkg.GoFiles {
|
||||||
|
filename := filepath.Join(bpkg.Dir, name)
|
||||||
|
af, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
files = append(files, af)
|
||||||
|
}
|
||||||
|
|
||||||
|
info := types.Info{
|
||||||
|
Uses: make(map[*ast.Ident]types.Object),
|
||||||
|
}
|
||||||
|
conf := types.Config{
|
||||||
|
Importer: sourceImporter,
|
||||||
|
}
|
||||||
|
_, err := conf.Check(bpkg.Name, fset, files, &info)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for id, obj := range info.Uses {
|
||||||
|
if obj.Pkg() != nil && obj.Pkg().Name() == "os" && obj.Name() == "Exit" {
|
||||||
|
pos := fset.Position(id.Pos())
|
||||||
|
|
||||||
|
name, err := filepath.Rel(wd, pos.Filename)
|
||||||
|
if err != nil {
|
||||||
|
t.Log(err)
|
||||||
|
name = pos.Filename
|
||||||
|
}
|
||||||
|
name = filepath.ToSlash(name)
|
||||||
|
|
||||||
|
// Check if the usage is allowed
|
||||||
|
if allowed[name] > 0 {
|
||||||
|
allowed[name]--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Errorf("os.Exit referenced at: %s:%d:%d", name, pos.Line, pos.Column)
|
||||||
|
errOsExit = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errOsExit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enforce that src/util.Exit() is used instead of os.Exit by prohibiting
|
||||||
|
// references to it anywhere else in the fzf code base.
|
||||||
|
func TestOSExitNotAllowed(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("skipping: short test")
|
||||||
|
}
|
||||||
|
allowed := map[string]int{
|
||||||
|
"src/util/atexit.go": 1, // os.Exit allowed 1 time in "atexit.go"
|
||||||
|
}
|
||||||
|
var errOsExit bool
|
||||||
|
for _, pkg := range loadPackages(t) {
|
||||||
|
t.Run(pkg.ImportPath, func(t *testing.T) {
|
||||||
|
if checkPackageForOsExit(t, pkg, allowed) {
|
||||||
|
errOsExit = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if t.Failed() && errOsExit {
|
||||||
|
var names []string
|
||||||
|
for name := range allowed {
|
||||||
|
names = append(names, fmt.Sprintf("%q", name))
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
|
||||||
|
const errMsg = `
|
||||||
|
Test failed because os.Exit was referenced outside of the following files:
|
||||||
|
|
||||||
|
%s
|
||||||
|
|
||||||
|
Use github.com/junegunn/fzf/src/util.Exit() instead to exit the program.
|
||||||
|
This is enforced because calling os.Exit() prevents the functions
|
||||||
|
registered with util.AtExit() from running.`
|
||||||
|
|
||||||
|
t.Errorf(errMsg, strings.Join(names, "\n "))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 "Mar 2024" "fzf 0.48.1" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf-tmux 1 "May 2024" "fzf 0.51.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
|
||||||
|
|||||||
@@ -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 "Mar 2024" "fzf 0.48.1" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "May 2024" "fzf 0.51.0" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -191,7 +191,7 @@ actions are affected:
|
|||||||
\fBkill-word\fR
|
\fBkill-word\fR
|
||||||
.TP
|
.TP
|
||||||
.BI "--jump-labels=" "CHARS"
|
.BI "--jump-labels=" "CHARS"
|
||||||
Label characters for \fBjump\fR and \fBjump-accept\fR
|
Label characters for \fBjump\fR mode.
|
||||||
.SS Layout
|
.SS Layout
|
||||||
.TP
|
.TP
|
||||||
.BI "--height=" "[~]HEIGHT[%]"
|
.BI "--height=" "[~]HEIGHT[%]"
|
||||||
@@ -372,20 +372,21 @@ e.g.
|
|||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "--info=" "STYLE"
|
.BI "--info=" "STYLE"
|
||||||
Determines the display style of finder info (match counters).
|
Determines the display style of the finder info. (e.g. match counter, loading indicator, etc.)
|
||||||
|
|
||||||
|
.BR default " On the left end of the horizontal separator"
|
||||||
.br
|
.br
|
||||||
.BR default " Display on the next line to the prompt"
|
.BR right " On the right end of the horizontal separator"
|
||||||
.br
|
.br
|
||||||
.BR right " Display on the right end of the next line to the prompt"
|
.BR hidden " Do not display finder info"
|
||||||
.br
|
.br
|
||||||
.BR inline " Display on the same line with the default separator ' < '"
|
.BR inline " After the prompt with the default prefix ' < '"
|
||||||
.br
|
.br
|
||||||
.BR inline:SEPARATOR " Display on the same line with a non-default separator"
|
.BR inline:PREFIX " After the prompt with a non-default prefix"
|
||||||
.br
|
.br
|
||||||
.BR inline-right " Display on the right end of the same line
|
.BR inline-right " On the right end of the prompt line"
|
||||||
.br
|
.br
|
||||||
.BR hidden " Do not display finder info"
|
.BR inline-right:PREFIX " On the right end of the prompt line with a custom prefix"
|
||||||
.br
|
.br
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
@@ -811,12 +812,22 @@ e.g.
|
|||||||
.TP
|
.TP
|
||||||
.B "--sync"
|
.B "--sync"
|
||||||
Synchronous search for multi-staged filtering. If specified, fzf will launch
|
Synchronous search for multi-staged filtering. If specified, fzf will launch
|
||||||
ncurses finder only after the input stream is complete.
|
the finder only after the input stream is complete.
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
e.g. \fBfzf --multi | fzf --sync\fR
|
e.g. \fBfzf --multi | fzf --sync\fR
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
|
.B "--with-shell=STR"
|
||||||
|
Shell command and flags to start child processes with. On *nix Systems, the
|
||||||
|
default value is \fB$SHELL -c\fR if \fB$SHELL\fR is set, otherwise \fBsh -c\fR.
|
||||||
|
On Windows, the default value is \fBcmd /v:on/s/c\fR when \fB$SHELL\fR is not
|
||||||
|
set.
|
||||||
|
|
||||||
|
.RS
|
||||||
|
e.g. \fBgem list | fzf --with-shell 'ruby -e' --preview 'pp Gem::Specification.find_by_name({1})'\fR
|
||||||
|
.RE
|
||||||
|
.TP
|
||||||
.B "--listen[=[ADDR:]PORT]" "--listen-unsafe[=[ADDR:]PORT]"
|
.B "--listen[=[ADDR:]PORT]" "--listen-unsafe[=[ADDR:]PORT]"
|
||||||
Start HTTP server and listen on the given address. It allows external processes
|
Start HTTP server and listen on the given address. It allows external processes
|
||||||
to send actions to perform via POST method.
|
to send actions to perform via POST method.
|
||||||
@@ -931,6 +942,8 @@ you need to protect against DNS rebinding and privilege escalation attacks.
|
|||||||
.br
|
.br
|
||||||
.BR 2 " Error"
|
.BR 2 " Error"
|
||||||
.br
|
.br
|
||||||
|
.BR 127 " Invalid shell command for \fBbecome\fR action"
|
||||||
|
.br
|
||||||
.BR 130 " Interrupted with \fBCTRL-C\fR or \fBESC\fR"
|
.BR 130 " Interrupted with \fBCTRL-C\fR or \fBESC\fR"
|
||||||
|
|
||||||
.SH FIELD INDEX EXPRESSION
|
.SH FIELD INDEX EXPRESSION
|
||||||
@@ -971,12 +984,20 @@ fzf exports the following environment variables to its child processes.
|
|||||||
.br
|
.br
|
||||||
.BR FZF_SELECT_COUNT " Number of selected items"
|
.BR FZF_SELECT_COUNT " Number of selected items"
|
||||||
.br
|
.br
|
||||||
|
.BR FZF_POS " Vertical position of the cursor in the list starting from 1"
|
||||||
|
.br
|
||||||
.BR FZF_QUERY " Current query string"
|
.BR FZF_QUERY " Current query string"
|
||||||
.br
|
.br
|
||||||
.BR FZF_PROMPT " Prompt string"
|
.BR FZF_PROMPT " Prompt string"
|
||||||
.br
|
.br
|
||||||
|
.BR FZF_PREVIEW_LABEL " Preview label string"
|
||||||
|
.br
|
||||||
|
.BR FZF_BORDER_LABEL " Border label string"
|
||||||
|
.br
|
||||||
.BR FZF_ACTION " The name of the last action performed"
|
.BR FZF_ACTION " The name of the last action performed"
|
||||||
.br
|
.br
|
||||||
|
.BR FZF_KEY " The name of the last key pressed"
|
||||||
|
.br
|
||||||
.BR FZF_PORT " Port number when --listen option is used"
|
.BR FZF_PORT " Port number when --listen option is used"
|
||||||
.br
|
.br
|
||||||
|
|
||||||
@@ -1047,21 +1068,21 @@ e.g.
|
|||||||
.br
|
.br
|
||||||
\fIctrl-]\fR
|
\fIctrl-]\fR
|
||||||
.br
|
.br
|
||||||
\fIctrl-^\fR (\fIctrl-6\fR)
|
\fIctrl-^\fR (\fIctrl-6\fR)
|
||||||
.br
|
.br
|
||||||
\fIctrl-/\fR (\fIctrl-_\fR)
|
\fIctrl-/\fR (\fIctrl-_\fR)
|
||||||
.br
|
.br
|
||||||
\fIctrl-alt-[a-z]\fR
|
\fIctrl-alt-[a-z]\fR
|
||||||
.br
|
.br
|
||||||
\fIalt-[*]\fR (Any case-sensitive single character is allowed)
|
\fIalt-[*]\fR (Any case-sensitive single character is allowed)
|
||||||
.br
|
.br
|
||||||
\fIf[1-12]\fR
|
\fIf[1-12]\fR
|
||||||
.br
|
.br
|
||||||
\fIenter\fR (\fIreturn\fR \fIctrl-m\fR)
|
\fIenter\fR (\fIreturn\fR \fIctrl-m\fR)
|
||||||
.br
|
.br
|
||||||
\fIspace\fR
|
\fIspace\fR
|
||||||
.br
|
.br
|
||||||
\fIbspace\fR (\fIbs\fR)
|
\fIbackspace\fR (\fIbspace\fR \fIbs\fR)
|
||||||
.br
|
.br
|
||||||
\fIalt-up\fR
|
\fIalt-up\fR
|
||||||
.br
|
.br
|
||||||
@@ -1075,15 +1096,15 @@ e.g.
|
|||||||
.br
|
.br
|
||||||
\fIalt-space\fR
|
\fIalt-space\fR
|
||||||
.br
|
.br
|
||||||
\fIalt-bspace\fR (\fIalt-bs\fR)
|
\fIalt-backspace\fR (\fIalt-bspace\fR \fIalt-bs\fR)
|
||||||
.br
|
.br
|
||||||
\fItab\fR
|
\fItab\fR
|
||||||
.br
|
.br
|
||||||
\fIbtab\fR (\fIshift-tab\fR)
|
\fIshift-tab\fR (\fIbtab\fR)
|
||||||
.br
|
.br
|
||||||
\fIesc\fR
|
\fIesc\fR
|
||||||
.br
|
.br
|
||||||
\fIdel\fR
|
\fIdelete\fR (\fIdel\fR)
|
||||||
.br
|
.br
|
||||||
\fIup\fR
|
\fIup\fR
|
||||||
.br
|
.br
|
||||||
@@ -1099,9 +1120,9 @@ e.g.
|
|||||||
.br
|
.br
|
||||||
\fIinsert\fR
|
\fIinsert\fR
|
||||||
.br
|
.br
|
||||||
\fIpgup\fR (\fIpage-up\fR)
|
\fIpage-up\fR (\fIpgup\fR)
|
||||||
.br
|
.br
|
||||||
\fIpgdn\fR (\fIpage-down\fR)
|
\fIpage-down\fR (\fIpgdn\fR)
|
||||||
.br
|
.br
|
||||||
\fIshift-up\fR
|
\fIshift-up\fR
|
||||||
.br
|
.br
|
||||||
@@ -1155,6 +1176,7 @@ e.g.
|
|||||||
\fB# Move cursor to the last item and select all items
|
\fB# Move cursor to the last item and select all items
|
||||||
seq 1000 | fzf --multi --sync --bind start:last+select-all\fR
|
seq 1000 | fzf --multi --sync --bind start:last+select-all\fR
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
\fIload\fR
|
\fIload\fR
|
||||||
.RS
|
.RS
|
||||||
Triggered when the input stream is complete and the initial processing of the
|
Triggered when the input stream is complete and the initial processing of the
|
||||||
@@ -1164,6 +1186,7 @@ e.g.
|
|||||||
\fB# Change the prompt to "loaded" when the input stream is complete
|
\fB# Change the prompt to "loaded" when the input stream is complete
|
||||||
(seq 10; sleep 1; seq 11 20) | fzf --prompt 'Loading> ' --bind 'load:change-prompt:Loaded> '\fR
|
(seq 10; sleep 1; seq 11 20) | fzf --prompt 'Loading> ' --bind 'load:change-prompt:Loaded> '\fR
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
\fIresize\fR
|
\fIresize\fR
|
||||||
.RS
|
.RS
|
||||||
Triggered when the terminal size is changed.
|
Triggered when the terminal size is changed.
|
||||||
@@ -1171,6 +1194,7 @@ Triggered when the terminal size is changed.
|
|||||||
e.g.
|
e.g.
|
||||||
\fBfzf --bind 'resize:transform-header:echo Resized: ${FZF_COLUMNS}x${FZF_LINES}'\fR
|
\fBfzf --bind 'resize:transform-header:echo Resized: ${FZF_COLUMNS}x${FZF_LINES}'\fR
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
\fIresult\fR
|
\fIresult\fR
|
||||||
.RS
|
.RS
|
||||||
Triggered when the filtering for the current query is complete and the result list is ready.
|
Triggered when the filtering for the current query is complete and the result list is ready.
|
||||||
@@ -1204,6 +1228,7 @@ e.g.
|
|||||||
# Beware not to introduce an infinite loop
|
# Beware not to introduce an infinite loop
|
||||||
seq 10 | fzf --bind 'focus:up' --cycle\fR
|
seq 10 | fzf --bind 'focus:up' --cycle\fR
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
\fIone\fR
|
\fIone\fR
|
||||||
.RS
|
.RS
|
||||||
Triggered when there's only one match. \fBone:accept\fR binding is comparable
|
Triggered when there's only one match. \fBone:accept\fR binding is comparable
|
||||||
@@ -1215,6 +1240,7 @@ e.g.
|
|||||||
\fB# Automatically select the only match
|
\fB# Automatically select the only match
|
||||||
seq 10 | fzf --bind one:accept\fR
|
seq 10 | fzf --bind one:accept\fR
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
\fIzero\fR
|
\fIzero\fR
|
||||||
.RS
|
.RS
|
||||||
Triggered when there's no match. \fBzero:abort\fR binding is comparable to
|
Triggered when there's no match. \fBzero:abort\fR binding is comparable to
|
||||||
@@ -1236,6 +1262,22 @@ e.g.
|
|||||||
\fBfzf --bind backward-eof:abort\fR
|
\fBfzf --bind backward-eof:abort\fR
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
|
\fIjump\fR
|
||||||
|
.RS
|
||||||
|
Triggered when successfully jumped to the target item in \fBjump\fR mode.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fBfzf --bind space:jump,jump:accept\fR
|
||||||
|
.RE
|
||||||
|
|
||||||
|
\fIjump-cancel\fR
|
||||||
|
.RS
|
||||||
|
Triggered when \fBjump\fR mode is cancelled.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fBfzf --bind space:jump,jump:accept,jump-cancel:abort\fR
|
||||||
|
.RE
|
||||||
|
|
||||||
.SS AVAILABLE ACTIONS:
|
.SS AVAILABLE ACTIONS:
|
||||||
A key or an event can be bound to one or more of the following actions.
|
A key or an event can be bound to one or more of the following actions.
|
||||||
|
|
||||||
@@ -1254,6 +1296,8 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
||||||
\fBchange-border-label(...)\fR (change \fB--border-label\fR to the given string)
|
\fBchange-border-label(...)\fR (change \fB--border-label\fR to the given string)
|
||||||
\fBchange-header(...)\fR (change header to the given string; doesn't affect \fB--header-lines\fR)
|
\fBchange-header(...)\fR (change header to the given string; doesn't affect \fB--header-lines\fR)
|
||||||
|
\fBchange-multi\fR (enable multi-select mode with no limit)
|
||||||
|
\fBchange-multi(...)\fR (enable multi-select mode with a limit or disable it with 0)
|
||||||
\fBchange-preview(...)\fR (change \fB--preview\fR option)
|
\fBchange-preview(...)\fR (change \fB--preview\fR option)
|
||||||
\fBchange-preview-label(...)\fR (change \fB--preview-label\fR to the given string)
|
\fBchange-preview-label(...)\fR (change \fB--preview-label\fR to the given string)
|
||||||
\fBchange-preview-window(...)\fR (change \fB--preview-window\fR option; rotate through the multiple option sets separated by '|')
|
\fBchange-preview-window(...)\fR (change \fB--preview-window\fR option; rotate through the multiple option sets separated by '|')
|
||||||
@@ -1278,7 +1322,6 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBforward-word\fR \fIalt-f shift-right\fR
|
\fBforward-word\fR \fIalt-f shift-right\fR
|
||||||
\fBignore\fR
|
\fBignore\fR
|
||||||
\fBjump\fR (EasyMotion-like 2-keystroke movement)
|
\fBjump\fR (EasyMotion-like 2-keystroke movement)
|
||||||
\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; same as \fBpos(-1)\fR)
|
\fBlast\fR (move to the last match; same as \fBpos(-1)\fR)
|
||||||
@@ -1326,9 +1369,10 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBtoggle-preview-wrap\fR
|
\fBtoggle-preview-wrap\fR
|
||||||
\fBtoggle-search\fR (toggle search functionality)
|
\fBtoggle-search\fR (toggle search functionality)
|
||||||
\fBtoggle-sort\fR
|
\fBtoggle-sort\fR
|
||||||
\fBtoggle-track\fR
|
\fBtoggle-track\fR (toggle global tracking option (\fB--track\fR))
|
||||||
|
\fBtoggle-track-current\fR (toggle tracking of the current item)
|
||||||
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
||||||
\fBtrack\fR (track the current item; automatically disabled if focus changes)
|
\fBtrack-current\fR (track the current item; automatically disabled if focus changes)
|
||||||
\fBtransform(...)\fR (transform states using the output of an external command)
|
\fBtransform(...)\fR (transform states using the output of an external command)
|
||||||
\fBtransform-border-label(...)\fR (transform border label using an external command)
|
\fBtransform-border-label(...)\fR (transform border label using an external command)
|
||||||
\fBtransform-header(...)\fR (transform header using an external command)
|
\fBtransform-header(...)\fR (transform header using an external command)
|
||||||
@@ -1338,6 +1382,7 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBunbind(...)\fR (unbind bindings)
|
\fBunbind(...)\fR (unbind bindings)
|
||||||
\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
|
||||||
|
\fBuntrack-current\fR (stop tracking the current item; no-op if global tracking is enabled)
|
||||||
\fBup\fR \fIctrl-k ctrl-p up\fR
|
\fBup\fR \fIctrl-k ctrl-p up\fR
|
||||||
\fByank\fR \fIctrl-y\fR
|
\fByank\fR \fIctrl-y\fR
|
||||||
|
|
||||||
@@ -1410,8 +1455,6 @@ call.
|
|||||||
|
|
||||||
\fBfzf --bind "enter:become(vim {})"\fR
|
\fBfzf --bind "enter:become(vim {})"\fR
|
||||||
|
|
||||||
\fBbecome(...)\fR is not supported on Windows.
|
|
||||||
|
|
||||||
.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
|
||||||
|
|||||||
@@ -83,12 +83,28 @@ else
|
|||||||
endfunction
|
endfunction
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
let s:cmd_control_chars = ['&', '|', '<', '>', '(', ')', '@', '^', '!']
|
||||||
|
|
||||||
function! s:shellesc_cmd(arg)
|
function! s:shellesc_cmd(arg)
|
||||||
let escaped = substitute(a:arg, '[&|<>()@^]', '^&', 'g')
|
let e = '"'
|
||||||
let escaped = substitute(escaped, '%', '%%', 'g')
|
let slashes = 0
|
||||||
let escaped = substitute(escaped, '"', '\\^&', 'g')
|
for c in split(a:arg, '\zs')
|
||||||
let escaped = substitute(escaped, '\(\\\+\)\(\\^\)', '\1\1\2', 'g')
|
if c ==# '\'
|
||||||
return '^"'.substitute(escaped, '\(\\\+\)$', '\1\1', '').'^"'
|
let slashes += 1
|
||||||
|
elseif c ==# '"'
|
||||||
|
let e .= repeat('\', slashes + 1)
|
||||||
|
let slashes = 0
|
||||||
|
elseif c ==# '%'
|
||||||
|
let e .= '%'
|
||||||
|
elseif index(s:cmd_control_chars, c) >= 0
|
||||||
|
let e .= '^'
|
||||||
|
else
|
||||||
|
let slashes = 0
|
||||||
|
endif
|
||||||
|
let e .= c
|
||||||
|
endfor
|
||||||
|
let e .= repeat('\', slashes) .'"'
|
||||||
|
return e
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! fzf#shellescape(arg, ...)
|
function! fzf#shellescape(arg, ...)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||||
|
|
||||||
[[ $- =~ i ]] || return 0
|
if [[ $- =~ i ]]; then
|
||||||
|
|
||||||
|
|
||||||
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
||||||
@@ -32,6 +32,14 @@
|
|||||||
# To redraw line after fzf closes (printf '\e[5n')
|
# To redraw line after fzf closes (printf '\e[5n')
|
||||||
bind '"\e[0n": redraw-current-line' 2> /dev/null
|
bind '"\e[0n": redraw-current-line' 2> /dev/null
|
||||||
|
|
||||||
|
__fzf_defaults() {
|
||||||
|
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
echo "--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore $1"
|
||||||
|
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
||||||
|
echo "${FZF_DEFAULT_OPTS-} $2"
|
||||||
|
}
|
||||||
|
|
||||||
__fzf_comprun() {
|
__fzf_comprun() {
|
||||||
if [[ "$(type -t _fzf_comprun 2>&1)" = function ]]; then
|
if [[ "$(type -t _fzf_comprun 2>&1)" = function ]]; then
|
||||||
_fzf_comprun "$@"
|
_fzf_comprun "$@"
|
||||||
@@ -92,128 +100,77 @@ _fzf_opts_completion() {
|
|||||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||||
opts="
|
opts="
|
||||||
-h --help
|
-h --help
|
||||||
-x --extended
|
|
||||||
-e --exact
|
-e --exact
|
||||||
--extended-exact
|
|
||||||
+x --no-extended
|
+x --no-extended
|
||||||
+e --no-exact
|
|
||||||
-q --query
|
-q --query
|
||||||
-f --filter
|
-f --filter
|
||||||
--literal
|
--literal
|
||||||
--no-literal
|
|
||||||
--algo
|
|
||||||
--scheme
|
--scheme
|
||||||
--expect
|
--expect
|
||||||
--no-expect
|
--disabled
|
||||||
--enabled --no-phony
|
|
||||||
--disabled --phony
|
|
||||||
--tiebreak
|
--tiebreak
|
||||||
--bind
|
--bind
|
||||||
--color
|
--color
|
||||||
--toggle-sort
|
|
||||||
-d --delimiter
|
-d --delimiter
|
||||||
-n --nth
|
-n --nth
|
||||||
--with-nth
|
--with-nth
|
||||||
-s --sort
|
|
||||||
+s --no-sort
|
+s --no-sort
|
||||||
--track
|
--track
|
||||||
--no-track
|
|
||||||
--tac
|
--tac
|
||||||
--no-tac
|
|
||||||
-i
|
-i
|
||||||
+i
|
+i
|
||||||
-m --multi
|
-m --multi
|
||||||
+m --no-multi
|
|
||||||
--ansi
|
--ansi
|
||||||
--no-ansi
|
|
||||||
--no-mouse
|
--no-mouse
|
||||||
+c --no-color
|
+c --no-color
|
||||||
+2 --no-256
|
|
||||||
--black
|
|
||||||
--no-black
|
|
||||||
--bold
|
|
||||||
--no-bold
|
--no-bold
|
||||||
--layout
|
--layout
|
||||||
--reverse
|
--reverse
|
||||||
--no-reverse
|
|
||||||
--cycle
|
--cycle
|
||||||
--no-cycle
|
|
||||||
--keep-right
|
--keep-right
|
||||||
--no-keep-right
|
|
||||||
--hscroll
|
|
||||||
--no-hscroll
|
--no-hscroll
|
||||||
--hscroll-off
|
--hscroll-off
|
||||||
--scroll-off
|
--scroll-off
|
||||||
--filepath-word
|
--filepath-word
|
||||||
--no-filepath-word
|
|
||||||
--info
|
--info
|
||||||
--no-info
|
|
||||||
--inline-info
|
|
||||||
--no-inline-info
|
|
||||||
--separator
|
--separator
|
||||||
--no-separator
|
--no-separator
|
||||||
--scrollbar
|
|
||||||
--no-scrollbar
|
--no-scrollbar
|
||||||
--jump-labels
|
--jump-labels
|
||||||
-1 --select-1
|
-1 --select-1
|
||||||
+1 --no-select-1
|
|
||||||
-0 --exit-0
|
-0 --exit-0
|
||||||
+0 --no-exit-0
|
|
||||||
--read0
|
--read0
|
||||||
--no-read0
|
|
||||||
--print0
|
--print0
|
||||||
--no-print0
|
|
||||||
--print-query
|
--print-query
|
||||||
--no-print-query
|
|
||||||
--prompt
|
--prompt
|
||||||
--pointer
|
--pointer
|
||||||
--marker
|
--marker
|
||||||
--sync
|
--sync
|
||||||
--no-sync
|
|
||||||
--async
|
|
||||||
--no-history
|
|
||||||
--history
|
--history
|
||||||
--history-size
|
--history-size
|
||||||
--no-header
|
|
||||||
--no-header-lines
|
|
||||||
--header
|
--header
|
||||||
--header-lines
|
--header-lines
|
||||||
--header-first
|
--header-first
|
||||||
--no-header-first
|
|
||||||
--ellipsis
|
--ellipsis
|
||||||
--preview
|
--preview
|
||||||
--no-preview
|
|
||||||
--preview-window
|
--preview-window
|
||||||
--height
|
--height
|
||||||
--min-height
|
--min-height
|
||||||
--no-height
|
|
||||||
--no-margin
|
|
||||||
--no-padding
|
|
||||||
--no-border
|
|
||||||
--border
|
--border
|
||||||
--no-border-label
|
|
||||||
--border-label
|
--border-label
|
||||||
--border-label-pos
|
--border-label-pos
|
||||||
--no-preview-label
|
|
||||||
--preview-label
|
--preview-label
|
||||||
--preview-label-pos
|
--preview-label-pos
|
||||||
--no-unicode
|
--no-unicode
|
||||||
--unicode
|
|
||||||
--margin
|
--margin
|
||||||
--padding
|
--padding
|
||||||
--tabstop
|
--tabstop
|
||||||
--listen
|
--listen
|
||||||
--no-listen
|
|
||||||
--clear
|
|
||||||
--no-clear
|
--no-clear
|
||||||
--version
|
--version
|
||||||
--"
|
--"
|
||||||
|
|
||||||
case "${prev}" in
|
case "${prev}" in
|
||||||
--algo)
|
|
||||||
COMPREPLY=( $(compgen -W "v1 v2" -- "$cur") )
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
--scheme)
|
--scheme)
|
||||||
COMPREPLY=( $(compgen -W "default path history" -- "$cur") )
|
COMPREPLY=( $(compgen -W "default path history" -- "$cur") )
|
||||||
return 0
|
return 0
|
||||||
@@ -308,6 +265,7 @@ _fzf_handle_dynamic_completion() {
|
|||||||
eval "$orig_complete"
|
eval "$orig_complete"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
[[ $ret -eq 0 ]] && return 124
|
||||||
return $ret
|
return $ret
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -334,8 +292,8 @@ __fzf_generic_path_completion() {
|
|||||||
[[ -z "$dir" ]] && dir='.'
|
[[ -z "$dir" ]] && dir='.'
|
||||||
[[ "$dir" != "/" ]] && dir="${dir/%\//}"
|
[[ "$dir" != "/" ]] && dir="${dir/%\//}"
|
||||||
matches=$(
|
matches=$(
|
||||||
unset FZF_DEFAULT_COMMAND
|
export FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --scheme=path" "${FZF_COMPLETION_OPTS-} $2")
|
||||||
export FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $2"
|
unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE
|
||||||
if declare -F "$1" > /dev/null; then
|
if declare -F "$1" > /dev/null; then
|
||||||
eval "$1 $(printf %q "$dir")" | __fzf_comprun "$4" -q "$leftover"
|
eval "$1 $(printf %q "$dir")" | __fzf_comprun "$4" -q "$leftover"
|
||||||
else
|
else
|
||||||
@@ -398,7 +356,10 @@ _fzf_complete() {
|
|||||||
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
|
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
|
||||||
cur=${cur:0:${#cur}-${#trigger}}
|
cur=${cur:0:${#cur}-${#trigger}}
|
||||||
|
|
||||||
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 | command tr '\n' ' ')
|
selected=$(
|
||||||
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse" "${FZF_COMPLETION_OPTS-} $str_arg") \
|
||||||
|
FZF_DEFAULT_OPTS_FILE='' \
|
||||||
|
__fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | command 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")
|
||||||
@@ -502,8 +463,11 @@ complete -o default -F _fzf_opts_completion fzf
|
|||||||
# fzf-tmux specific options (like `-w WIDTH`) are left as a future patch.
|
# fzf-tmux specific options (like `-w WIDTH`) are left as a future patch.
|
||||||
complete -o default -F _fzf_opts_completion fzf-tmux
|
complete -o default -F _fzf_opts_completion fzf-tmux
|
||||||
|
|
||||||
d_cmds="${FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir}"
|
d_cmds="${FZF_COMPLETION_DIR_COMMANDS-cd pushd rmdir}"
|
||||||
a_cmds="
|
|
||||||
|
# NOTE: $FZF_COMPLETION_PATH_COMMANDS and $FZF_COMPLETION_VAR_COMMANDS are
|
||||||
|
# undocumented and subject to change in the future.
|
||||||
|
a_cmds="${FZF_COMPLETION_PATH_COMMANDS-"
|
||||||
awk bat cat diff diff3
|
awk bat cat diff diff3
|
||||||
emacs emacsclient ex file ftp g++ gcc gvim head hg hx java
|
emacs emacsclient ex file ftp g++ gcc gvim head hg hx java
|
||||||
javac ld less more mvim nvim patch perl python ruby
|
javac ld less more mvim nvim patch perl python ruby
|
||||||
@@ -511,10 +475,11 @@ a_cmds="
|
|||||||
basename bunzip2 bzip2 chmod chown curl cp dirname du
|
basename bunzip2 bzip2 chmod chown curl cp dirname du
|
||||||
find git grep gunzip gzip hg jar
|
find git grep gunzip gzip hg jar
|
||||||
ln ls mv open rm rsync scp
|
ln ls mv open rm rsync scp
|
||||||
svn tar unzip zip"
|
svn tar unzip zip"}"
|
||||||
|
v_cmds="${FZF_COMPLETION_VAR_COMMANDS-export unset printenv}"
|
||||||
|
|
||||||
# Preserve existing completion
|
# Preserve existing completion
|
||||||
__fzf_orig_completion < <(complete -p $d_cmds $a_cmds ssh 2> /dev/null)
|
__fzf_orig_completion < <(complete -p $d_cmds $a_cmds $v_cmds unalias kill ssh 2> /dev/null)
|
||||||
|
|
||||||
if type _comp_load > /dev/null 2>&1; then
|
if type _comp_load > /dev/null 2>&1; then
|
||||||
# _comp_load was added in bash-completion 2.12 to replace _completion_loader.
|
# _comp_load was added in bash-completion 2.12 to replace _completion_loader.
|
||||||
@@ -547,13 +512,24 @@ done
|
|||||||
|
|
||||||
# Directory
|
# Directory
|
||||||
for cmd in $d_cmds; do
|
for cmd in $d_cmds; do
|
||||||
__fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames"
|
__fzf_defc "$cmd" _fzf_dir_completion "-o bashdefault -o nospace -o dirnames"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Variables
|
||||||
|
for cmd in $v_cmds; do
|
||||||
|
__fzf_defc "$cmd" _fzf_var_completion "-o default -o nospace -v"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Aliases
|
||||||
|
__fzf_defc unalias _fzf_alias_completion "-a"
|
||||||
|
|
||||||
|
# Processes
|
||||||
|
__fzf_defc kill _fzf_proc_completion "-o default -o bashdefault"
|
||||||
|
|
||||||
# ssh
|
# ssh
|
||||||
__fzf_defc ssh _fzf_complete_ssh "-o default -o bashdefault"
|
__fzf_defc ssh _fzf_complete_ssh "-o default -o bashdefault"
|
||||||
|
|
||||||
unset cmd d_cmds a_cmds
|
unset cmd d_cmds a_cmds v_cmds
|
||||||
|
|
||||||
_fzf_setup_completion() {
|
_fzf_setup_completion() {
|
||||||
local kind fn cmd
|
local kind fn cmd
|
||||||
@@ -575,8 +551,4 @@ _fzf_setup_completion() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# Environment variables / Aliases / Hosts / Process
|
fi
|
||||||
_fzf_setup_completion 'var' export unset printenv
|
|
||||||
_fzf_setup_completion 'alias' unalias
|
|
||||||
_fzf_setup_completion 'host' telnet
|
|
||||||
_fzf_setup_completion 'proc' kill
|
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||||
|
|
||||||
[[ -o interactive ]] || return 0
|
|
||||||
|
|
||||||
|
|
||||||
# Both branches of the following `if` do the same thing -- define
|
# Both branches of the following `if` do the same thing -- define
|
||||||
# __fzf_completion_options such that `eval $__fzf_completion_options` sets
|
# __fzf_completion_options such that `eval $__fzf_completion_options` sets
|
||||||
@@ -75,6 +73,9 @@ fi
|
|||||||
# This brace is the start of try-always block. The `always` part is like
|
# This brace is the start of try-always block. The `always` part is like
|
||||||
# `finally` in lesser languages. We use it to *always* restore user options.
|
# `finally` in lesser languages. We use it to *always* restore user options.
|
||||||
{
|
{
|
||||||
|
# The 'emulate' command should not be placed inside the interactive if check;
|
||||||
|
# placing it there fails to disable alias expansion. See #3731.
|
||||||
|
if [[ -o interactive ]]; then
|
||||||
|
|
||||||
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
||||||
#
|
#
|
||||||
@@ -93,6 +94,14 @@ fi
|
|||||||
|
|
||||||
###########################################################
|
###########################################################
|
||||||
|
|
||||||
|
__fzf_defaults() {
|
||||||
|
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
echo "--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore $1"
|
||||||
|
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
||||||
|
echo "${FZF_DEFAULT_OPTS-} $2"
|
||||||
|
}
|
||||||
|
|
||||||
__fzf_comprun() {
|
__fzf_comprun() {
|
||||||
if [[ "$(type _fzf_comprun 2>&1)" =~ function ]]; then
|
if [[ "$(type _fzf_comprun 2>&1)" =~ function ]]; then
|
||||||
_fzf_comprun "$@"
|
_fzf_comprun "$@"
|
||||||
@@ -146,8 +155,8 @@ __fzf_generic_path_completion() {
|
|||||||
[ -z "$dir" ] && dir='.'
|
[ -z "$dir" ] && dir='.'
|
||||||
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
||||||
matches=$(
|
matches=$(
|
||||||
unset FZF_DEFAULT_COMMAND
|
export FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --scheme=path" "${FZF_COMPLETION_OPTS-}")
|
||||||
export FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-}"
|
unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE
|
||||||
if declare -f "$compgen" > /dev/null; then
|
if declare -f "$compgen" > /dev/null; then
|
||||||
eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover"
|
eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover"
|
||||||
else
|
else
|
||||||
@@ -217,7 +226,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 --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $str_arg" __fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
|
matches=$(
|
||||||
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse" "${FZF_COMPLETION_OPTS-} $str_arg") \
|
||||||
|
FZF_DEFAULT_OPTS_FILE='' \
|
||||||
|
__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
|
||||||
@@ -315,7 +327,7 @@ fzf-completion() {
|
|||||||
|
|
||||||
# Trigger sequence given
|
# Trigger sequence given
|
||||||
if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
|
if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
|
||||||
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})
|
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS-cd pushd rmdir})
|
||||||
|
|
||||||
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
|
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
|
||||||
if [[ $prefix = *'$('* ]] || [[ $prefix = *'<('* ]] || [[ $prefix = *'>('* ]] || [[ $prefix = *':='* ]] || [[ $prefix = *'`'* ]]; then
|
if [[ $prefix = *'$('* ]] || [[ $prefix = *'<('* ]] || [[ $prefix = *'>('* ]] || [[ $prefix = *':='* ]] || [[ $prefix = *'`'* ]]; then
|
||||||
@@ -345,6 +357,7 @@ fzf-completion() {
|
|||||||
|
|
||||||
zle -N fzf-completion
|
zle -N fzf-completion
|
||||||
bindkey '^I' fzf-completion
|
bindkey '^I' fzf-completion
|
||||||
|
fi
|
||||||
|
|
||||||
} always {
|
} always {
|
||||||
# Restore the original options.
|
# Restore the original options.
|
||||||
|
|||||||
@@ -11,15 +11,24 @@
|
|||||||
# - $FZF_ALT_C_COMMAND
|
# - $FZF_ALT_C_COMMAND
|
||||||
# - $FZF_ALT_C_OPTS
|
# - $FZF_ALT_C_OPTS
|
||||||
|
|
||||||
[[ $- =~ i ]] || return 0
|
if [[ $- =~ i ]]; then
|
||||||
|
|
||||||
|
|
||||||
# Key bindings
|
# Key bindings
|
||||||
# ------------
|
# ------------
|
||||||
|
|
||||||
|
__fzf_defaults() {
|
||||||
|
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
echo "--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore $1"
|
||||||
|
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
||||||
|
echo "${FZF_DEFAULT_OPTS-} $2"
|
||||||
|
}
|
||||||
|
|
||||||
__fzf_select__() {
|
__fzf_select__() {
|
||||||
local opts
|
FZF_DEFAULT_COMMAND=${FZF_CTRL_T_COMMAND:-} \
|
||||||
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse --walker=file,dir,follow,hidden --scheme=path ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-} -m"
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --walker=file,dir,follow,hidden --scheme=path" "${FZF_CTRL_T_OPTS-} -m") \
|
||||||
FZF_DEFAULT_COMMAND=${FZF_CTRL_T_COMMAND:-} FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) "$@" |
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) "$@" |
|
||||||
while read -r item; do
|
while read -r item; do
|
||||||
printf '%q ' "$item" # escape special chars
|
printf '%q ' "$item" # escape special chars
|
||||||
done
|
done
|
||||||
@@ -37,23 +46,24 @@ fzf-file-widget() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
__fzf_cd__() {
|
__fzf_cd__() {
|
||||||
local opts dir
|
local dir
|
||||||
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse --walker=dir,follow,hidden --scheme=path ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-} +m"
|
|
||||||
dir=$(
|
dir=$(
|
||||||
FZF_DEFAULT_COMMAND=${FZF_ALT_C_COMMAND:-} FZF_DEFAULT_OPTS="$opts" $(__fzfcmd)
|
FZF_DEFAULT_COMMAND=${FZF_ALT_C_COMMAND:-} \
|
||||||
) && printf 'builtin cd -- %q' "$dir"
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --walker=dir,follow,hidden --scheme=path" "${FZF_ALT_C_OPTS-} +m") \
|
||||||
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd)
|
||||||
|
) && printf 'builtin cd -- %q' "$(builtin unset CDPATH && builtin cd -- "$dir" && builtin pwd)"
|
||||||
}
|
}
|
||||||
|
|
||||||
if command -v perl > /dev/null; then
|
if command -v perl > /dev/null; then
|
||||||
__fzf_history__() {
|
__fzf_history__() {
|
||||||
local output opts script
|
local output script
|
||||||
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort ${FZF_CTRL_R_OPTS-} +m --read0"
|
|
||||||
script='BEGIN { getc; $/ = "\n\t"; $HISTCOUNT = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCOUNT - $. . "\t$_" if !$seen{$_}++'
|
script='BEGIN { getc; $/ = "\n\t"; $HISTCOUNT = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCOUNT - $. . "\t$_" if !$seen{$_}++'
|
||||||
output=$(
|
output=$(
|
||||||
set +o pipefail
|
set +o pipefail
|
||||||
builtin fc -lnr -2147483648 |
|
builtin fc -lnr -2147483648 |
|
||||||
last_hist=$(HISTTIMEFORMAT='' builtin history 1) command perl -n -l0 -e "$script" |
|
last_hist=$(HISTTIMEFORMAT='' builtin history 1) command perl -n -l0 -e "$script" |
|
||||||
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) --query "$READLINE_LINE"
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort ${FZF_CTRL_R_OPTS-} +m --read0") \
|
||||||
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) --query "$READLINE_LINE"
|
||||||
) || return
|
) || return
|
||||||
READLINE_LINE=${output#*$'\t'}
|
READLINE_LINE=${output#*$'\t'}
|
||||||
if [[ -z "$READLINE_POINT" ]]; then
|
if [[ -z "$READLINE_POINT" ]]; then
|
||||||
@@ -64,14 +74,13 @@ if command -v perl > /dev/null; then
|
|||||||
}
|
}
|
||||||
else # awk - fallback for POSIX systems
|
else # awk - fallback for POSIX systems
|
||||||
__fzf_history__() {
|
__fzf_history__() {
|
||||||
local output opts script n x y z d
|
local output script n x y z d
|
||||||
if [[ -z $__fzf_awk ]]; then
|
if [[ -z $__fzf_awk ]]; then
|
||||||
__fzf_awk=awk
|
__fzf_awk=awk
|
||||||
# choose the faster mawk if: it's installed && build date >= 20230322 && version >= 1.3.4
|
# choose the faster mawk if: it's installed && build date >= 20230322 && version >= 1.3.4
|
||||||
IFS=' .' read n x y z d <<< $(command mawk -W version 2> /dev/null)
|
IFS=' .' read n x y z d <<< $(command mawk -W version 2> /dev/null)
|
||||||
[[ $n == mawk ]] && (( d >= 20230302 && (x *1000 +y) *1000 +z >= 1003004 )) && __fzf_awk=mawk
|
[[ $n == mawk ]] && (( d >= 20230302 && (x *1000 +y) *1000 +z >= 1003004 )) && __fzf_awk=mawk
|
||||||
fi
|
fi
|
||||||
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort ${FZF_CTRL_R_OPTS-} +m --read0"
|
|
||||||
[[ $(HISTTIMEFORMAT='' builtin history 1) =~ [[:digit:]]+ ]] # how many history entries
|
[[ $(HISTTIMEFORMAT='' builtin history 1) =~ [[:digit:]]+ ]] # how many history entries
|
||||||
script='function P(b) { ++n; sub(/^[ *]/, "", b); if (!seen[b]++) { printf "%d\t%s%c", '$((BASH_REMATCH + 1))' - n, b, 0 } }
|
script='function P(b) { ++n; sub(/^[ *]/, "", b); if (!seen[b]++) { printf "%d\t%s%c", '$((BASH_REMATCH + 1))' - n, b, 0 } }
|
||||||
NR==1 { b = substr($0, 2); next }
|
NR==1 { b = substr($0, 2); next }
|
||||||
@@ -82,7 +91,8 @@ else # awk - fallback for POSIX systems
|
|||||||
set +o pipefail
|
set +o pipefail
|
||||||
builtin fc -lnr -2147483648 2> /dev/null | # ( $'\t '<lines>$'\n' )* ; <lines> ::= [^\n]* ( $'\n'<lines> )*
|
builtin fc -lnr -2147483648 2> /dev/null | # ( $'\t '<lines>$'\n' )* ; <lines> ::= [^\n]* ( $'\n'<lines> )*
|
||||||
command $__fzf_awk "$script" | # ( <counter>$'\t'<lines>$'\000' )*
|
command $__fzf_awk "$script" | # ( <counter>$'\t'<lines>$'\000' )*
|
||||||
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) --query "$READLINE_LINE"
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort ${FZF_CTRL_R_OPTS-} +m --read0") \
|
||||||
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) --query "$READLINE_LINE"
|
||||||
) || return
|
) || return
|
||||||
READLINE_LINE=${output#*$'\t'}
|
READLINE_LINE=${output#*$'\t'}
|
||||||
if [[ -z "$READLINE_POINT" ]]; then
|
if [[ -z "$READLINE_POINT" ]]; then
|
||||||
@@ -132,3 +142,5 @@ if [[ "${FZF_ALT_C_COMMAND-x}" != "" ]]; then
|
|||||||
bind -m vi-command '"\ec": "\C-z\ec\C-z"'
|
bind -m vi-command '"\ec": "\C-z\ec\C-z"'
|
||||||
bind -m vi-insert '"\ec": "\C-z\ec\C-z"'
|
bind -m vi-insert '"\ec": "\C-z\ec\C-z"'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|||||||
@@ -18,17 +18,27 @@ status is-interactive; or exit 0
|
|||||||
# ------------
|
# ------------
|
||||||
function fzf_key_bindings
|
function fzf_key_bindings
|
||||||
|
|
||||||
|
function __fzf_defaults
|
||||||
|
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||||
|
echo "--height $FZF_TMUX_HEIGHT --bind=ctrl-z:ignore" $argv[1]
|
||||||
|
command cat "$FZF_DEFAULT_OPTS_FILE" 2> /dev/null
|
||||||
|
echo $FZF_DEFAULT_OPTS $argv[2]
|
||||||
|
end
|
||||||
|
|
||||||
# Store current token in $dir as root for the 'find' command
|
# Store current token in $dir as root for the 'find' command
|
||||||
function fzf-file-widget -d "List files and folders"
|
function fzf-file-widget -d "List files and folders"
|
||||||
set -l commandline (__fzf_parse_commandline)
|
set -l commandline (__fzf_parse_commandline)
|
||||||
set -l dir $commandline[1]
|
set -lx dir $commandline[1]
|
||||||
set -l fzf_query $commandline[2]
|
set -l fzf_query $commandline[2]
|
||||||
set -l prefix $commandline[3]
|
set -l prefix $commandline[3]
|
||||||
|
|
||||||
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 --walker=file,dir,follow,hidden --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
|
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "--reverse --walker=file,dir,follow,hidden --scheme=path --walker-root='$dir'" "$FZF_CTRL_T_OPTS")
|
||||||
set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND"
|
set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND"
|
||||||
|
set -lx FZF_DEFAULT_OPTS_FILE ''
|
||||||
eval (__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
|
eval (__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
|
||||||
end
|
end
|
||||||
if [ -z "$result" ]
|
if [ -z "$result" ]
|
||||||
@@ -49,7 +59,8 @@ 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 --scheme=history --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS +m"
|
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "" "--scheme=history --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m")
|
||||||
|
set -lx FZF_DEFAULT_OPTS_FILE ''
|
||||||
|
|
||||||
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.)
|
||||||
@@ -70,13 +81,14 @@ function fzf_key_bindings
|
|||||||
|
|
||||||
function fzf-cd-widget -d "Change directory"
|
function fzf-cd-widget -d "Change directory"
|
||||||
set -l commandline (__fzf_parse_commandline)
|
set -l commandline (__fzf_parse_commandline)
|
||||||
set -l dir $commandline[1]
|
set -lx dir $commandline[1]
|
||||||
set -l fzf_query $commandline[2]
|
set -l fzf_query $commandline[2]
|
||||||
set -l prefix $commandline[3]
|
set -l prefix $commandline[3]
|
||||||
|
|
||||||
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 --walker=dir,follow,hidden --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
|
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "--reverse --walker=dir,follow,hidden --scheme=path --walker-root='$dir'" "$FZF_ALT_C_OPTS")
|
||||||
|
set -lx FZF_DEFAULT_OPTS_FILE ''
|
||||||
set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND"
|
set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND"
|
||||||
eval (__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
eval (__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,6 @@
|
|||||||
# - $FZF_ALT_C_COMMAND
|
# - $FZF_ALT_C_COMMAND
|
||||||
# - $FZF_ALT_C_OPTS
|
# - $FZF_ALT_C_OPTS
|
||||||
|
|
||||||
[[ -o interactive ]] || return 0
|
|
||||||
|
|
||||||
|
|
||||||
# Key bindings
|
# Key bindings
|
||||||
# ------------
|
# ------------
|
||||||
@@ -38,12 +36,23 @@ fi
|
|||||||
'builtin' 'emulate' 'zsh' && 'builtin' 'setopt' 'no_aliases'
|
'builtin' 'emulate' 'zsh' && 'builtin' 'setopt' 'no_aliases'
|
||||||
|
|
||||||
{
|
{
|
||||||
|
if [[ -o interactive ]]; then
|
||||||
|
|
||||||
|
__fzf_defaults() {
|
||||||
|
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
echo "--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore $1"
|
||||||
|
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
||||||
|
echo "${FZF_DEFAULT_OPTS-} $2"
|
||||||
|
}
|
||||||
|
|
||||||
# CTRL-T - Paste the selected file path(s) into the command line
|
# CTRL-T - Paste the selected file path(s) into the command line
|
||||||
__fsel() {
|
__fzf_select() {
|
||||||
setopt localoptions pipefail no_aliases 2> /dev/null
|
setopt localoptions pipefail no_aliases 2> /dev/null
|
||||||
local item
|
local item
|
||||||
FZF_DEFAULT_COMMAND=${FZF_CTRL_T_COMMAND:-} FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --walker=file,dir,follow,hidden --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-}" $(__fzfcmd) -m "$@" < /dev/tty | while read item; do
|
FZF_DEFAULT_COMMAND=${FZF_CTRL_T_COMMAND:-} \
|
||||||
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --walker=file,dir,follow,hidden --scheme=path" "${FZF_CTRL_T_OPTS-} -m") \
|
||||||
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) "$@" < /dev/tty | while read item; do
|
||||||
echo -n "${(q)item} "
|
echo -n "${(q)item} "
|
||||||
done
|
done
|
||||||
local ret=$?
|
local ret=$?
|
||||||
@@ -57,7 +66,7 @@ __fzfcmd() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fzf-file-widget() {
|
fzf-file-widget() {
|
||||||
LBUFFER="${LBUFFER}$(__fsel)"
|
LBUFFER="${LBUFFER}$(__fzf_select)"
|
||||||
local ret=$?
|
local ret=$?
|
||||||
zle reset-prompt
|
zle reset-prompt
|
||||||
return $ret
|
return $ret
|
||||||
@@ -72,13 +81,16 @@ fi
|
|||||||
# ALT-C - cd into the selected directory
|
# ALT-C - cd into the selected directory
|
||||||
fzf-cd-widget() {
|
fzf-cd-widget() {
|
||||||
setopt localoptions pipefail no_aliases 2> /dev/null
|
setopt localoptions pipefail no_aliases 2> /dev/null
|
||||||
local dir="$(FZF_DEFAULT_COMMAND=${FZF_ALT_C_COMMAND:-} FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --walker=dir,follow,hidden --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-}" $(__fzfcmd) +m < /dev/tty)"
|
local dir="$(
|
||||||
|
FZF_DEFAULT_COMMAND=${FZF_ALT_C_COMMAND:-} \
|
||||||
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --walker=dir,follow,hidden --scheme=path" "${FZF_ALT_C_OPTS-} +m") \
|
||||||
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) < /dev/tty)"
|
||||||
if [[ -z "$dir" ]]; then
|
if [[ -z "$dir" ]]; then
|
||||||
zle redisplay
|
zle redisplay
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
zle push-line # Clear buffer. Auto-restored on next prompt.
|
zle push-line # Clear buffer. Auto-restored on next prompt.
|
||||||
BUFFER="builtin cd -- ${(q)dir}"
|
BUFFER="builtin cd -- ${(q)dir:a}"
|
||||||
zle accept-line
|
zle accept-line
|
||||||
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
|
||||||
@@ -97,7 +109,8 @@ 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
|
||||||
selected="$(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
|
selected="$(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
|
||||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} ${FZF_DEFAULT_OPTS-} -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,ctrl-z:ignore ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m" $(__fzfcmd))"
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m") \
|
||||||
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
|
||||||
local ret=$?
|
local ret=$?
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
num=$(awk '{print $1}' <<< "$selected")
|
num=$(awk '{print $1}' <<< "$selected")
|
||||||
@@ -114,6 +127,7 @@ zle -N fzf-history-widget
|
|||||||
bindkey -M emacs '^R' fzf-history-widget
|
bindkey -M emacs '^R' fzf-history-widget
|
||||||
bindkey -M vicmd '^R' fzf-history-widget
|
bindkey -M vicmd '^R' fzf-history-widget
|
||||||
bindkey -M viins '^R' fzf-history-widget
|
bindkey -M viins '^R' fzf-history-widget
|
||||||
|
fi
|
||||||
|
|
||||||
} always {
|
} always {
|
||||||
eval $__fzf_key_bindings_options
|
eval $__fzf_key_bindings_options
|
||||||
|
|||||||
@@ -26,100 +26,103 @@ func _() {
|
|||||||
_ = x[actCancel-15]
|
_ = x[actCancel-15]
|
||||||
_ = x[actChangeBorderLabel-16]
|
_ = x[actChangeBorderLabel-16]
|
||||||
_ = x[actChangeHeader-17]
|
_ = x[actChangeHeader-17]
|
||||||
_ = x[actChangePreviewLabel-18]
|
_ = x[actChangeMulti-18]
|
||||||
_ = x[actChangePrompt-19]
|
_ = x[actChangePreviewLabel-19]
|
||||||
_ = x[actChangeQuery-20]
|
_ = x[actChangePrompt-20]
|
||||||
_ = x[actClearScreen-21]
|
_ = x[actChangeQuery-21]
|
||||||
_ = x[actClearQuery-22]
|
_ = x[actClearScreen-22]
|
||||||
_ = x[actClearSelection-23]
|
_ = x[actClearQuery-23]
|
||||||
_ = x[actClose-24]
|
_ = x[actClearSelection-24]
|
||||||
_ = x[actDeleteChar-25]
|
_ = x[actClose-25]
|
||||||
_ = x[actDeleteCharEof-26]
|
_ = x[actDeleteChar-26]
|
||||||
_ = x[actEndOfLine-27]
|
_ = x[actDeleteCharEof-27]
|
||||||
_ = x[actForwardChar-28]
|
_ = x[actEndOfLine-28]
|
||||||
_ = x[actForwardWord-29]
|
_ = x[actForwardChar-29]
|
||||||
_ = x[actKillLine-30]
|
_ = x[actForwardWord-30]
|
||||||
_ = x[actKillWord-31]
|
_ = x[actKillLine-31]
|
||||||
_ = x[actUnixLineDiscard-32]
|
_ = x[actKillWord-32]
|
||||||
_ = x[actUnixWordRubout-33]
|
_ = x[actUnixLineDiscard-33]
|
||||||
_ = x[actYank-34]
|
_ = x[actUnixWordRubout-34]
|
||||||
_ = x[actBackwardKillWord-35]
|
_ = x[actYank-35]
|
||||||
_ = x[actSelectAll-36]
|
_ = x[actBackwardKillWord-36]
|
||||||
_ = x[actDeselectAll-37]
|
_ = x[actSelectAll-37]
|
||||||
_ = x[actToggle-38]
|
_ = x[actDeselectAll-38]
|
||||||
_ = x[actToggleSearch-39]
|
_ = x[actToggle-39]
|
||||||
_ = x[actToggleAll-40]
|
_ = x[actToggleSearch-40]
|
||||||
_ = x[actToggleDown-41]
|
_ = x[actToggleAll-41]
|
||||||
_ = x[actToggleUp-42]
|
_ = x[actToggleDown-42]
|
||||||
_ = x[actToggleIn-43]
|
_ = x[actToggleUp-43]
|
||||||
_ = x[actToggleOut-44]
|
_ = x[actToggleIn-44]
|
||||||
_ = x[actToggleTrack-45]
|
_ = x[actToggleOut-45]
|
||||||
_ = x[actToggleHeader-46]
|
_ = x[actToggleTrack-46]
|
||||||
_ = x[actTrack-47]
|
_ = x[actToggleTrackCurrent-47]
|
||||||
_ = x[actDown-48]
|
_ = x[actToggleHeader-48]
|
||||||
_ = x[actUp-49]
|
_ = x[actTrackCurrent-49]
|
||||||
_ = x[actPageUp-50]
|
_ = x[actUntrackCurrent-50]
|
||||||
_ = x[actPageDown-51]
|
_ = x[actDown-51]
|
||||||
_ = x[actPosition-52]
|
_ = x[actUp-52]
|
||||||
_ = x[actHalfPageUp-53]
|
_ = x[actPageUp-53]
|
||||||
_ = x[actHalfPageDown-54]
|
_ = x[actPageDown-54]
|
||||||
_ = x[actOffsetUp-55]
|
_ = x[actPosition-55]
|
||||||
_ = x[actOffsetDown-56]
|
_ = x[actHalfPageUp-56]
|
||||||
_ = x[actJump-57]
|
_ = x[actHalfPageDown-57]
|
||||||
_ = x[actJumpAccept-58]
|
_ = x[actOffsetUp-58]
|
||||||
_ = x[actPrintQuery-59]
|
_ = x[actOffsetDown-59]
|
||||||
_ = x[actRefreshPreview-60]
|
_ = x[actJump-60]
|
||||||
_ = x[actReplaceQuery-61]
|
_ = x[actJumpAccept-61]
|
||||||
_ = x[actToggleSort-62]
|
_ = x[actPrintQuery-62]
|
||||||
_ = x[actShowPreview-63]
|
_ = x[actRefreshPreview-63]
|
||||||
_ = x[actHidePreview-64]
|
_ = x[actReplaceQuery-64]
|
||||||
_ = x[actTogglePreview-65]
|
_ = x[actToggleSort-65]
|
||||||
_ = x[actTogglePreviewWrap-66]
|
_ = x[actShowPreview-66]
|
||||||
_ = x[actTransform-67]
|
_ = x[actHidePreview-67]
|
||||||
_ = x[actTransformBorderLabel-68]
|
_ = x[actTogglePreview-68]
|
||||||
_ = x[actTransformHeader-69]
|
_ = x[actTogglePreviewWrap-69]
|
||||||
_ = x[actTransformPreviewLabel-70]
|
_ = x[actTransform-70]
|
||||||
_ = x[actTransformPrompt-71]
|
_ = x[actTransformBorderLabel-71]
|
||||||
_ = x[actTransformQuery-72]
|
_ = x[actTransformHeader-72]
|
||||||
_ = x[actPreview-73]
|
_ = x[actTransformPreviewLabel-73]
|
||||||
_ = x[actChangePreview-74]
|
_ = x[actTransformPrompt-74]
|
||||||
_ = x[actChangePreviewWindow-75]
|
_ = x[actTransformQuery-75]
|
||||||
_ = x[actPreviewTop-76]
|
_ = x[actPreview-76]
|
||||||
_ = x[actPreviewBottom-77]
|
_ = x[actChangePreview-77]
|
||||||
_ = x[actPreviewUp-78]
|
_ = x[actChangePreviewWindow-78]
|
||||||
_ = x[actPreviewDown-79]
|
_ = x[actPreviewTop-79]
|
||||||
_ = x[actPreviewPageUp-80]
|
_ = x[actPreviewBottom-80]
|
||||||
_ = x[actPreviewPageDown-81]
|
_ = x[actPreviewUp-81]
|
||||||
_ = x[actPreviewHalfPageUp-82]
|
_ = x[actPreviewDown-82]
|
||||||
_ = x[actPreviewHalfPageDown-83]
|
_ = x[actPreviewPageUp-83]
|
||||||
_ = x[actPrevHistory-84]
|
_ = x[actPreviewPageDown-84]
|
||||||
_ = x[actPrevSelected-85]
|
_ = x[actPreviewHalfPageUp-85]
|
||||||
_ = x[actPut-86]
|
_ = x[actPreviewHalfPageDown-86]
|
||||||
_ = x[actNextHistory-87]
|
_ = x[actPrevHistory-87]
|
||||||
_ = x[actNextSelected-88]
|
_ = x[actPrevSelected-88]
|
||||||
_ = x[actExecute-89]
|
_ = x[actPut-89]
|
||||||
_ = x[actExecuteSilent-90]
|
_ = x[actNextHistory-90]
|
||||||
_ = x[actExecuteMulti-91]
|
_ = x[actNextSelected-91]
|
||||||
_ = x[actSigStop-92]
|
_ = x[actExecute-92]
|
||||||
_ = x[actFirst-93]
|
_ = x[actExecuteSilent-93]
|
||||||
_ = x[actLast-94]
|
_ = x[actExecuteMulti-94]
|
||||||
_ = x[actReload-95]
|
_ = x[actSigStop-95]
|
||||||
_ = x[actReloadSync-96]
|
_ = x[actFirst-96]
|
||||||
_ = x[actDisableSearch-97]
|
_ = x[actLast-97]
|
||||||
_ = x[actEnableSearch-98]
|
_ = x[actReload-98]
|
||||||
_ = x[actSelect-99]
|
_ = x[actReloadSync-99]
|
||||||
_ = x[actDeselect-100]
|
_ = x[actDisableSearch-100]
|
||||||
_ = x[actUnbind-101]
|
_ = x[actEnableSearch-101]
|
||||||
_ = x[actRebind-102]
|
_ = x[actSelect-102]
|
||||||
_ = x[actBecome-103]
|
_ = x[actDeselect-103]
|
||||||
_ = x[actResponse-104]
|
_ = x[actUnbind-104]
|
||||||
_ = x[actShowHeader-105]
|
_ = x[actRebind-105]
|
||||||
_ = x[actHideHeader-106]
|
_ = x[actBecome-106]
|
||||||
|
_ = x[actResponse-107]
|
||||||
|
_ = x[actShowHeader-108]
|
||||||
|
_ = x[actHideHeader-109]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleHeaderactTrackactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactResponseactShowHeaderactHideHeader"
|
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactResponseactShowHeaderactHideHeader"
|
||||||
|
|
||||||
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 263, 278, 292, 306, 319, 336, 344, 357, 373, 385, 399, 413, 424, 435, 453, 470, 477, 496, 508, 522, 531, 546, 558, 571, 582, 593, 605, 619, 634, 642, 649, 654, 663, 674, 685, 698, 713, 724, 737, 744, 757, 770, 787, 802, 815, 829, 843, 859, 879, 891, 914, 932, 956, 974, 991, 1001, 1017, 1039, 1052, 1068, 1080, 1094, 1110, 1128, 1148, 1170, 1184, 1199, 1205, 1219, 1234, 1244, 1260, 1275, 1285, 1293, 1300, 1309, 1322, 1338, 1353, 1362, 1373, 1382, 1391, 1400, 1411, 1424, 1437}
|
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 256, 277, 292, 306, 320, 333, 350, 358, 371, 387, 399, 413, 427, 438, 449, 467, 484, 491, 510, 522, 536, 545, 560, 572, 585, 596, 607, 619, 633, 654, 669, 684, 701, 708, 713, 722, 733, 744, 757, 772, 783, 796, 803, 816, 829, 846, 861, 874, 888, 902, 918, 938, 950, 973, 991, 1015, 1033, 1050, 1060, 1076, 1098, 1111, 1127, 1139, 1153, 1169, 1187, 1207, 1229, 1243, 1258, 1264, 1278, 1293, 1303, 1319, 1334, 1344, 1352, 1359, 1368, 1381, 1397, 1412, 1421, 1432, 1441, 1450, 1459, 1470, 1483, 1496}
|
||||||
|
|
||||||
func (i actionType) String() string {
|
func (i actionType) String() string {
|
||||||
if i < 0 || i >= actionType(len(_actionType_index)-1) {
|
if i < 0 || i >= actionType(len(_actionType_index)-1) {
|
||||||
|
|||||||
133
src/algo/algo.go
133
src/algo/algo.go
@@ -153,6 +153,12 @@ var (
|
|||||||
bonusBoundaryDelimiter int16 = bonusBoundary + 1
|
bonusBoundaryDelimiter int16 = bonusBoundary + 1
|
||||||
|
|
||||||
initialCharClass charClass = charWhite
|
initialCharClass charClass = charWhite
|
||||||
|
|
||||||
|
// A minor optimization that can give 15%+ performance boost
|
||||||
|
asciiCharClasses [unicode.MaxASCII + 1]charClass
|
||||||
|
|
||||||
|
// A minor optimization that can give yet another 5% performance boost
|
||||||
|
bonusMatrix [charNumber + 1][charNumber + 1]int16
|
||||||
)
|
)
|
||||||
|
|
||||||
type charClass int
|
type charClass int
|
||||||
@@ -187,6 +193,27 @@ func Init(scheme string) bool {
|
|||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
for i := 0; i <= unicode.MaxASCII; i++ {
|
||||||
|
char := rune(i)
|
||||||
|
c := charNonWord
|
||||||
|
if char >= 'a' && char <= 'z' {
|
||||||
|
c = charLower
|
||||||
|
} else if char >= 'A' && char <= 'Z' {
|
||||||
|
c = charUpper
|
||||||
|
} else if char >= '0' && char <= '9' {
|
||||||
|
c = charNumber
|
||||||
|
} else if strings.ContainsRune(whiteChars, char) {
|
||||||
|
c = charWhite
|
||||||
|
} else if strings.ContainsRune(delimiterChars, char) {
|
||||||
|
c = charDelimiter
|
||||||
|
}
|
||||||
|
asciiCharClasses[i] = c
|
||||||
|
}
|
||||||
|
for i := 0; i <= int(charNumber); i++ {
|
||||||
|
for j := 0; j <= int(charNumber); j++ {
|
||||||
|
bonusMatrix[i][j] = bonusFor(charClass(i), charClass(j))
|
||||||
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,21 +241,6 @@ func alloc32(offset int, slab *util.Slab, size int) (int, []int32) {
|
|||||||
return offset, make([]int32, size)
|
return offset, make([]int32, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func charClassOfAscii(char rune) charClass {
|
|
||||||
if char >= 'a' && char <= 'z' {
|
|
||||||
return charLower
|
|
||||||
} else if char >= 'A' && char <= 'Z' {
|
|
||||||
return charUpper
|
|
||||||
} else if char >= '0' && char <= '9' {
|
|
||||||
return charNumber
|
|
||||||
} else if strings.ContainsRune(whiteChars, char) {
|
|
||||||
return charWhite
|
|
||||||
} else if strings.ContainsRune(delimiterChars, char) {
|
|
||||||
return charDelimiter
|
|
||||||
}
|
|
||||||
return charNonWord
|
|
||||||
}
|
|
||||||
|
|
||||||
func charClassOfNonAscii(char rune) charClass {
|
func charClassOfNonAscii(char rune) charClass {
|
||||||
if unicode.IsLower(char) {
|
if unicode.IsLower(char) {
|
||||||
return charLower
|
return charLower
|
||||||
@@ -248,7 +260,7 @@ func charClassOfNonAscii(char rune) charClass {
|
|||||||
|
|
||||||
func charClassOf(char rune) charClass {
|
func charClassOf(char rune) charClass {
|
||||||
if char <= unicode.MaxASCII {
|
if char <= unicode.MaxASCII {
|
||||||
return charClassOfAscii(char)
|
return asciiCharClasses[char]
|
||||||
}
|
}
|
||||||
return charClassOfNonAscii(char)
|
return charClassOfNonAscii(char)
|
||||||
}
|
}
|
||||||
@@ -287,7 +299,7 @@ func bonusAt(input *util.Chars, idx int) int16 {
|
|||||||
if idx == 0 {
|
if idx == 0 {
|
||||||
return bonusBoundaryWhite
|
return bonusBoundaryWhite
|
||||||
}
|
}
|
||||||
return bonusFor(charClassOf(input.Get(idx-1)), charClassOf(input.Get(idx)))
|
return bonusMatrix[charClassOf(input.Get(idx-1))][charClassOf(input.Get(idx))]
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizeRune(r rune) rune {
|
func normalizeRune(r rune) rune {
|
||||||
@@ -340,30 +352,45 @@ func isAscii(runes []rune) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func asciiFuzzyIndex(input *util.Chars, pattern []rune, caseSensitive bool) int {
|
func asciiFuzzyIndex(input *util.Chars, pattern []rune, caseSensitive bool) (int, int) {
|
||||||
// Can't determine
|
// Can't determine
|
||||||
if !input.IsBytes() {
|
if !input.IsBytes() {
|
||||||
return 0
|
return 0, input.Length()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not possible
|
// Not possible
|
||||||
if !isAscii(pattern) {
|
if !isAscii(pattern) {
|
||||||
return -1
|
return -1, -1
|
||||||
}
|
}
|
||||||
|
|
||||||
firstIdx, idx := 0, 0
|
firstIdx, idx, lastIdx := 0, 0, 0
|
||||||
|
var b byte
|
||||||
for pidx := 0; pidx < len(pattern); pidx++ {
|
for pidx := 0; pidx < len(pattern); pidx++ {
|
||||||
idx = trySkip(input, caseSensitive, byte(pattern[pidx]), idx)
|
b = byte(pattern[pidx])
|
||||||
|
idx = trySkip(input, caseSensitive, b, idx)
|
||||||
if idx < 0 {
|
if idx < 0 {
|
||||||
return -1
|
return -1, -1
|
||||||
}
|
}
|
||||||
if pidx == 0 && idx > 0 {
|
if pidx == 0 && idx > 0 {
|
||||||
// Step back to find the right bonus point
|
// Step back to find the right bonus point
|
||||||
firstIdx = idx - 1
|
firstIdx = idx - 1
|
||||||
}
|
}
|
||||||
|
lastIdx = idx
|
||||||
idx++
|
idx++
|
||||||
}
|
}
|
||||||
return firstIdx
|
|
||||||
|
// Find the last appearance of the last character of the pattern to limit the search scope
|
||||||
|
bu := b
|
||||||
|
if !caseSensitive && b >= 'a' && b <= 'z' {
|
||||||
|
bu = b - 32
|
||||||
|
}
|
||||||
|
scope := input.Bytes()[lastIdx:]
|
||||||
|
for offset := len(scope) - 1; offset > 0; offset-- {
|
||||||
|
if scope[offset] == b || scope[offset] == bu {
|
||||||
|
return firstIdx, lastIdx + offset + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return firstIdx, lastIdx + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func debugV2(T []rune, pattern []rune, F []int32, lastIdx int, H []int16, C []int16) {
|
func debugV2(T []rune, pattern []rune, F []int32, lastIdx int, H []int16, C []int16) {
|
||||||
@@ -412,6 +439,9 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
return Result{0, 0, 0}, posArray(withPos, M)
|
return Result{0, 0, 0}, posArray(withPos, M)
|
||||||
}
|
}
|
||||||
N := input.Length()
|
N := input.Length()
|
||||||
|
if M > N {
|
||||||
|
return Result{-1, -1, 0}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Since O(nm) algorithm can be prohibitively expensive for large input,
|
// Since O(nm) algorithm can be prohibitively expensive for large input,
|
||||||
// we fall back to the greedy algorithm.
|
// we fall back to the greedy algorithm.
|
||||||
@@ -420,10 +450,12 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Phase 1. Optimized search for ASCII string
|
// Phase 1. Optimized search for ASCII string
|
||||||
idx := asciiFuzzyIndex(input, pattern, caseSensitive)
|
minIdx, maxIdx := asciiFuzzyIndex(input, pattern, caseSensitive)
|
||||||
if idx < 0 {
|
if minIdx < 0 {
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
}
|
}
|
||||||
|
// fmt.Println(N, maxIdx, idx, maxIdx-idx, input.ToString())
|
||||||
|
N = maxIdx - minIdx
|
||||||
|
|
||||||
// Reuse pre-allocated integer slice to avoid unnecessary sweeping of garbages
|
// Reuse pre-allocated integer slice to avoid unnecessary sweeping of garbages
|
||||||
offset16 := 0
|
offset16 := 0
|
||||||
@@ -436,20 +468,19 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
offset32, F := alloc32(offset32, slab, M)
|
offset32, F := alloc32(offset32, slab, M)
|
||||||
// Rune array
|
// Rune array
|
||||||
_, T := alloc32(offset32, slab, N)
|
_, T := alloc32(offset32, slab, N)
|
||||||
input.CopyRunes(T)
|
input.CopyRunes(T, minIdx)
|
||||||
|
|
||||||
// Phase 2. Calculate bonus for each point
|
// Phase 2. Calculate bonus for each point
|
||||||
maxScore, maxScorePos := int16(0), 0
|
maxScore, maxScorePos := int16(0), 0
|
||||||
pidx, lastIdx := 0, 0
|
pidx, lastIdx := 0, 0
|
||||||
pchar0, pchar, prevH0, prevClass, inGap := pattern[0], pattern[0], int16(0), initialCharClass, false
|
pchar0, pchar, prevH0, prevClass, inGap := pattern[0], pattern[0], int16(0), initialCharClass, false
|
||||||
Tsub := T[idx:]
|
for off, char := range T {
|
||||||
H0sub, C0sub, Bsub := H0[idx:][:len(Tsub)], C0[idx:][:len(Tsub)], B[idx:][:len(Tsub)]
|
|
||||||
for off, char := range Tsub {
|
|
||||||
var class charClass
|
var class charClass
|
||||||
if char <= unicode.MaxASCII {
|
if char <= unicode.MaxASCII {
|
||||||
class = charClassOfAscii(char)
|
class = asciiCharClasses[char]
|
||||||
if !caseSensitive && class == charUpper {
|
if !caseSensitive && class == charUpper {
|
||||||
char += 32
|
char += 32
|
||||||
|
T[off] = char
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
class = charClassOfNonAscii(char)
|
class = charClassOfNonAscii(char)
|
||||||
@@ -459,28 +490,28 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
if normalize {
|
if normalize {
|
||||||
char = normalizeRune(char)
|
char = normalizeRune(char)
|
||||||
}
|
}
|
||||||
|
T[off] = char
|
||||||
}
|
}
|
||||||
|
|
||||||
Tsub[off] = char
|
bonus := bonusMatrix[prevClass][class]
|
||||||
bonus := bonusFor(prevClass, class)
|
B[off] = bonus
|
||||||
Bsub[off] = bonus
|
|
||||||
prevClass = class
|
prevClass = class
|
||||||
|
|
||||||
if char == pchar {
|
if char == pchar {
|
||||||
if pidx < M {
|
if pidx < M {
|
||||||
F[pidx] = int32(idx + off)
|
F[pidx] = int32(off)
|
||||||
pidx++
|
pidx++
|
||||||
pchar = pattern[util.Min(pidx, M-1)]
|
pchar = pattern[util.Min(pidx, M-1)]
|
||||||
}
|
}
|
||||||
lastIdx = idx + off
|
lastIdx = off
|
||||||
}
|
}
|
||||||
|
|
||||||
if char == pchar0 {
|
if char == pchar0 {
|
||||||
score := scoreMatch + bonus*bonusFirstCharMultiplier
|
score := scoreMatch + bonus*bonusFirstCharMultiplier
|
||||||
H0sub[off] = score
|
H0[off] = score
|
||||||
C0sub[off] = 1
|
C0[off] = 1
|
||||||
if M == 1 && (forward && score > maxScore || !forward && score >= maxScore) {
|
if M == 1 && (forward && score > maxScore || !forward && score >= maxScore) {
|
||||||
maxScore, maxScorePos = score, idx+off
|
maxScore, maxScorePos = score, off
|
||||||
if forward && bonus >= bonusBoundary {
|
if forward && bonus >= bonusBoundary {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -488,24 +519,24 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
inGap = false
|
inGap = false
|
||||||
} else {
|
} else {
|
||||||
if inGap {
|
if inGap {
|
||||||
H0sub[off] = util.Max16(prevH0+scoreGapExtension, 0)
|
H0[off] = util.Max16(prevH0+scoreGapExtension, 0)
|
||||||
} else {
|
} else {
|
||||||
H0sub[off] = util.Max16(prevH0+scoreGapStart, 0)
|
H0[off] = util.Max16(prevH0+scoreGapStart, 0)
|
||||||
}
|
}
|
||||||
C0sub[off] = 0
|
C0[off] = 0
|
||||||
inGap = true
|
inGap = true
|
||||||
}
|
}
|
||||||
prevH0 = H0sub[off]
|
prevH0 = H0[off]
|
||||||
}
|
}
|
||||||
if pidx != M {
|
if pidx != M {
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
}
|
}
|
||||||
if M == 1 {
|
if M == 1 {
|
||||||
result := Result{maxScorePos, maxScorePos + 1, int(maxScore)}
|
result := Result{minIdx + maxScorePos, minIdx + maxScorePos + 1, int(maxScore)}
|
||||||
if !withPos {
|
if !withPos {
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
pos := []int{maxScorePos}
|
pos := []int{minIdx + maxScorePos}
|
||||||
return result, &pos
|
return result, &pos
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -602,7 +633,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
}
|
}
|
||||||
|
|
||||||
if s > s1 && (s > s2 || s == s2 && preferMatch) {
|
if s > s1 && (s > s2 || s == s2 && preferMatch) {
|
||||||
*pos = append(*pos, j)
|
*pos = append(*pos, j+minIdx)
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -615,7 +646,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
// Start offset we return here is only relevant when begin tiebreak is used.
|
// Start offset we return here is only relevant when begin tiebreak is used.
|
||||||
// However finding the accurate offset requires backtracking, and we don't
|
// However finding the accurate offset requires backtracking, and we don't
|
||||||
// want to pay extra cost for the option that has lost its importance.
|
// want to pay extra cost for the option that has lost its importance.
|
||||||
return Result{j, maxScorePos + 1, int(maxScore)}, pos
|
return Result{minIdx + j, minIdx + maxScorePos + 1, int(maxScore)}, pos
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement the same sorting criteria as V2
|
// Implement the same sorting criteria as V2
|
||||||
@@ -645,7 +676,7 @@ func calculateScore(caseSensitive bool, normalize bool, text *util.Chars, patter
|
|||||||
*pos = append(*pos, idx)
|
*pos = append(*pos, idx)
|
||||||
}
|
}
|
||||||
score += scoreMatch
|
score += scoreMatch
|
||||||
bonus := bonusFor(prevClass, class)
|
bonus := bonusMatrix[prevClass][class]
|
||||||
if consecutive == 0 {
|
if consecutive == 0 {
|
||||||
firstBonus = bonus
|
firstBonus = bonus
|
||||||
} else {
|
} else {
|
||||||
@@ -683,7 +714,8 @@ func FuzzyMatchV1(caseSensitive bool, normalize bool, forward bool, text *util.C
|
|||||||
if len(pattern) == 0 {
|
if len(pattern) == 0 {
|
||||||
return Result{0, 0, 0}, nil
|
return Result{0, 0, 0}, nil
|
||||||
}
|
}
|
||||||
if asciiFuzzyIndex(text, pattern, caseSensitive) < 0 {
|
idx, _ := asciiFuzzyIndex(text, pattern, caseSensitive)
|
||||||
|
if idx < 0 {
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -777,7 +809,8 @@ func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *uti
|
|||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if asciiFuzzyIndex(text, pattern, caseSensitive) < 0 {
|
idx, _ := asciiFuzzyIndex(text, pattern, caseSensitive)
|
||||||
|
if idx < 0 {
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Init("default")
|
||||||
|
}
|
||||||
|
|
||||||
func assertMatch(t *testing.T, fun Algo, caseSensitive, forward bool, input, pattern string, sidx int, eidx int, score int) {
|
func assertMatch(t *testing.T, fun Algo, caseSensitive, forward bool, input, pattern string, sidx int, eidx int, score int) {
|
||||||
assertMatch2(t, fun, caseSensitive, false, forward, input, pattern, sidx, eidx, score)
|
assertMatch2(t, fun, caseSensitive, false, forward, input, pattern, sidx, eidx, score)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ func parseAnsiCode(s string, delimiter byte) (int, byte, string) {
|
|||||||
// Inlined version of strconv.Atoi() that only handles positive
|
// Inlined version of strconv.Atoi() that only handles positive
|
||||||
// integers and does not allocate on error.
|
// integers and does not allocate on error.
|
||||||
code := 0
|
code := 0
|
||||||
for _, ch := range []byte(s) {
|
for _, ch := range sbytes(s) {
|
||||||
ch -= '0'
|
ch -= '0'
|
||||||
if ch > 9 {
|
if ch > 9 {
|
||||||
return -1, delimiter, remaining
|
return -1, delimiter, remaining
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const (
|
|||||||
|
|
||||||
// Reader
|
// Reader
|
||||||
readerBufferSize = 64 * 1024
|
readerBufferSize = 64 * 1024
|
||||||
|
readerSlabSize = 128 * 1024
|
||||||
readerPollIntervalMin = 10 * time.Millisecond
|
readerPollIntervalMin = 10 * time.Millisecond
|
||||||
readerPollIntervalStep = 5 * time.Millisecond
|
readerPollIntervalStep = 5 * time.Millisecond
|
||||||
readerPollIntervalMax = 50 * time.Millisecond
|
readerPollIntervalMax = 50 * time.Millisecond
|
||||||
|
|||||||
71
src/core.go
71
src/core.go
@@ -3,8 +3,9 @@ package fzf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
@@ -18,8 +19,18 @@ Matcher -> EvtSearchFin -> Terminal (update list)
|
|||||||
Matcher -> EvtHeader -> Terminal (update header)
|
Matcher -> EvtHeader -> Terminal (update header)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
func ustring(data []byte) string {
|
||||||
|
return unsafe.String(unsafe.SliceData(data), len(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func sbytes(data string) []byte {
|
||||||
|
return unsafe.Slice(unsafe.StringData(data), len(data))
|
||||||
|
}
|
||||||
|
|
||||||
// Run starts fzf
|
// Run starts fzf
|
||||||
func Run(opts *Options, version string, revision string) {
|
func Run(opts *Options, version string, revision string) {
|
||||||
|
defer util.RunAtExitFuncs()
|
||||||
|
|
||||||
sort := opts.Sort > 0
|
sort := opts.Sort > 0
|
||||||
sortCriteria = opts.Criteria
|
sortCriteria = opts.Criteria
|
||||||
|
|
||||||
@@ -29,7 +40,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
} else {
|
} else {
|
||||||
fmt.Println(version)
|
fmt.Println(version)
|
||||||
}
|
}
|
||||||
os.Exit(exitOk)
|
util.Exit(exitOk)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event channel
|
// Event channel
|
||||||
@@ -45,16 +56,16 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
if opts.Theme.Colored {
|
if opts.Theme.Colored {
|
||||||
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
||||||
prevLineAnsiState = lineAnsiState
|
prevLineAnsiState = lineAnsiState
|
||||||
trimmed, offsets, newState := extractColor(string(data), lineAnsiState, nil)
|
trimmed, offsets, newState := extractColor(ustring(data), lineAnsiState, nil)
|
||||||
lineAnsiState = newState
|
lineAnsiState = newState
|
||||||
return util.ToChars([]byte(trimmed)), offsets
|
return util.ToChars(sbytes(trimmed)), offsets
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// When color is disabled but ansi option is given,
|
// When color is disabled but ansi option is given,
|
||||||
// we simply strip out ANSI codes from the input
|
// we simply strip out ANSI codes from the input
|
||||||
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
||||||
trimmed, _, _ := extractColor(string(data), nil, nil)
|
trimmed, _, _ := extractColor(ustring(data), nil, nil)
|
||||||
return util.ToChars([]byte(trimmed)), nil
|
return util.ToChars(sbytes(trimmed)), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,7 +77,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
if len(opts.WithNth) == 0 {
|
if len(opts.WithNth) == 0 {
|
||||||
chunkList = NewChunkList(func(item *Item, data []byte) bool {
|
chunkList = NewChunkList(func(item *Item, data []byte) bool {
|
||||||
if len(header) < opts.HeaderLines {
|
if len(header) < opts.HeaderLines {
|
||||||
header = append(header, string(data))
|
header = append(header, ustring(data))
|
||||||
eventBox.Set(EvtHeader, header)
|
eventBox.Set(EvtHeader, header)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -77,7 +88,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
chunkList = NewChunkList(func(item *Item, data []byte) bool {
|
chunkList = NewChunkList(func(item *Item, data []byte) bool {
|
||||||
tokens := Tokenize(string(data), opts.Delimiter)
|
tokens := Tokenize(ustring(data), opts.Delimiter)
|
||||||
if opts.Ansi && opts.Theme.Colored && len(tokens) > 1 {
|
if opts.Ansi && opts.Theme.Colored && len(tokens) > 1 {
|
||||||
var ansiState *ansiState
|
var ansiState *ansiState
|
||||||
if prevLineAnsiState != nil {
|
if prevLineAnsiState != nil {
|
||||||
@@ -101,7 +112,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
eventBox.Set(EvtHeader, header)
|
eventBox.Set(EvtHeader, header)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
item.text, item.colors = ansiProcessor([]byte(transformed))
|
item.text, item.colors = ansiProcessor(sbytes(transformed))
|
||||||
item.text.TrimTrailingWhitespaces()
|
item.text.TrimTrailingWhitespaces()
|
||||||
item.text.Index = itemIndex
|
item.text.Index = itemIndex
|
||||||
item.origText = &data
|
item.origText = &data
|
||||||
@@ -110,13 +121,16 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process executor
|
||||||
|
executor := util.NewExecutor(opts.WithShell)
|
||||||
|
|
||||||
// Reader
|
// Reader
|
||||||
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
|
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
|
||||||
var reader *Reader
|
var reader *Reader
|
||||||
if !streamingFilter {
|
if !streamingFilter {
|
||||||
reader = NewReader(func(data []byte) bool {
|
reader = NewReader(func(data []byte) bool {
|
||||||
return chunkList.Push(data)
|
return chunkList.Push(data)
|
||||||
}, eventBox, opts.ReadZero, opts.Filter == nil)
|
}, eventBox, executor, opts.ReadZero, opts.Filter == nil)
|
||||||
go reader.ReadSource(opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
|
go reader.ReadSource(opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,17 +168,20 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
found := false
|
found := false
|
||||||
if streamingFilter {
|
if streamingFilter {
|
||||||
slab := util.MakeSlab(slab16Size, slab32Size)
|
slab := util.MakeSlab(slab16Size, slab32Size)
|
||||||
|
mutex := sync.Mutex{}
|
||||||
reader := NewReader(
|
reader := NewReader(
|
||||||
func(runes []byte) bool {
|
func(runes []byte) bool {
|
||||||
item := Item{}
|
item := Item{}
|
||||||
if chunkList.trans(&item, runes) {
|
if chunkList.trans(&item, runes) {
|
||||||
|
mutex.Lock()
|
||||||
if result, _, _ := pattern.MatchItem(&item, false, slab); result != nil {
|
if result, _, _ := pattern.MatchItem(&item, false, slab); result != nil {
|
||||||
opts.Printer(item.text.ToString())
|
opts.Printer(item.text.ToString())
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
|
mutex.Unlock()
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}, eventBox, opts.ReadZero, false)
|
}, eventBox, executor, opts.ReadZero, false)
|
||||||
reader.ReadSource(opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
|
reader.ReadSource(opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
|
||||||
} else {
|
} else {
|
||||||
eventBox.Unwatch(EvtReadNew)
|
eventBox.Unwatch(EvtReadNew)
|
||||||
@@ -180,9 +197,9 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if found {
|
if found {
|
||||||
os.Exit(exitOk)
|
util.Exit(exitOk)
|
||||||
}
|
}
|
||||||
os.Exit(exitNoMatch)
|
util.Exit(exitNoMatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Synchronous search
|
// Synchronous search
|
||||||
@@ -195,7 +212,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
go matcher.Loop()
|
go matcher.Loop()
|
||||||
|
|
||||||
// Terminal I/O
|
// Terminal I/O
|
||||||
terminal := NewTerminal(opts, eventBox)
|
terminal := NewTerminal(opts, eventBox, executor)
|
||||||
maxFit := 0 // Maximum number of items that can fit on screen
|
maxFit := 0 // Maximum number of items that can fit on screen
|
||||||
padHeight := 0
|
padHeight := 0
|
||||||
heightUnknown := opts.Height.auto
|
heightUnknown := opts.Height.auto
|
||||||
@@ -245,11 +262,8 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
delay := true
|
delay := true
|
||||||
ticks++
|
ticks++
|
||||||
input := func() []rune {
|
input := func() []rune {
|
||||||
reloaded := snapshotRevision != inputRevision
|
|
||||||
paused, input := terminal.Input()
|
paused, input := terminal.Input()
|
||||||
if reloaded && paused {
|
if !paused {
|
||||||
query = []rune{}
|
|
||||||
} else if !paused {
|
|
||||||
query = input
|
query = input
|
||||||
}
|
}
|
||||||
return query
|
return query
|
||||||
@@ -264,7 +278,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
if reading {
|
if reading {
|
||||||
reader.terminate()
|
reader.terminate()
|
||||||
}
|
}
|
||||||
os.Exit(value.(int))
|
util.Exit(value.(int))
|
||||||
case EvtReadNew, EvtReadFin:
|
case EvtReadNew, EvtReadFin:
|
||||||
if evt == EvtReadFin && nextCommand != nil {
|
if evt == EvtReadFin && nextCommand != nil {
|
||||||
restart(*nextCommand, nextEnviron)
|
restart(*nextCommand, nextEnviron)
|
||||||
@@ -278,14 +292,16 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
useSnapshot = false
|
useSnapshot = false
|
||||||
}
|
}
|
||||||
if !useSnapshot {
|
if !useSnapshot {
|
||||||
|
if snapshotRevision != inputRevision {
|
||||||
|
query = []rune{}
|
||||||
|
}
|
||||||
snapshot, count = chunkList.Snapshot()
|
snapshot, count = chunkList.Snapshot()
|
||||||
snapshotRevision = inputRevision
|
snapshotRevision = inputRevision
|
||||||
}
|
}
|
||||||
total = count
|
total = count
|
||||||
terminal.UpdateCount(total, !reading, value.(*string))
|
terminal.UpdateCount(total, !reading, value.(*string))
|
||||||
if opts.Sync {
|
if opts.Sync {
|
||||||
opts.Sync = false
|
terminal.UpdateList(PassMerger(&snapshot, opts.Tac, snapshotRevision), false)
|
||||||
terminal.UpdateList(PassMerger(&snapshot, opts.Tac, snapshotRevision))
|
|
||||||
}
|
}
|
||||||
if heightUnknown && !deferred {
|
if heightUnknown && !deferred {
|
||||||
determine(!reading)
|
determine(!reading)
|
||||||
@@ -319,10 +335,13 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if !useSnapshot {
|
if !useSnapshot {
|
||||||
newSnapshot, _ := chunkList.Snapshot()
|
newSnapshot, newCount := chunkList.Snapshot()
|
||||||
// We want to avoid showing empty list when reload is triggered
|
// We want to avoid showing empty list when reload is triggered
|
||||||
// and the query string is changed at the same time i.e. command != nil && changed
|
// and the query string is changed at the same time i.e. command != nil && changed
|
||||||
if command == nil || len(newSnapshot) > 0 {
|
if command == nil || newCount > 0 {
|
||||||
|
if snapshotRevision != inputRevision {
|
||||||
|
query = []rune{}
|
||||||
|
}
|
||||||
snapshot = newSnapshot
|
snapshot = newSnapshot
|
||||||
snapshotRevision = inputRevision
|
snapshotRevision = inputRevision
|
||||||
}
|
}
|
||||||
@@ -360,14 +379,14 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
opts.Printer(val.Get(i).item.AsString(opts.Ansi))
|
opts.Printer(val.Get(i).item.AsString(opts.Ansi))
|
||||||
}
|
}
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
os.Exit(exitOk)
|
util.Exit(exitOk)
|
||||||
}
|
}
|
||||||
os.Exit(exitNoMatch)
|
util.Exit(exitNoMatch)
|
||||||
}
|
}
|
||||||
determine(val.final)
|
determine(val.final)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
terminal.UpdateList(val)
|
terminal.UpdateList(val, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
132
src/options.go
132
src/options.go
@@ -52,12 +52,12 @@ const usage = `usage: fzf [options]
|
|||||||
--hscroll-off=COLS Number of screen columns to keep to the right of the
|
--hscroll-off=COLS Number of screen columns to keep to the right of the
|
||||||
highlighted substring (default: 10)
|
highlighted substring (default: 10)
|
||||||
--filepath-word Make word-wise movements respect path separators
|
--filepath-word Make word-wise movements respect path separators
|
||||||
--jump-labels=CHARS Label characters for jump and jump-accept
|
--jump-labels=CHARS Label characters for jump mode
|
||||||
|
|
||||||
Layout
|
Layout
|
||||||
--height=[~]HEIGHT[%] Display fzf window below the cursor with the given
|
--height=[~]HEIGHT[%] Display fzf window below the cursor with the given
|
||||||
height instead of using fullscreen.
|
height instead of using fullscreen.
|
||||||
A negative value is calcalated as the terminal height
|
A negative value is calculated as the terminal height
|
||||||
minus the given value.
|
minus the given value.
|
||||||
If prefixed with '~', fzf will determine the height
|
If prefixed with '~', fzf will determine the height
|
||||||
according to the input size.
|
according to the input size.
|
||||||
@@ -75,7 +75,7 @@ const usage = `usage: fzf [options]
|
|||||||
--margin=MARGIN Screen margin (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
--margin=MARGIN Screen margin (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
||||||
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
||||||
--info=STYLE Finder info style
|
--info=STYLE Finder info style
|
||||||
[default|right|hidden|inline[:SEPARATOR]|inline-right]
|
[default|right|hidden|inline[-right][:PREFIX]]
|
||||||
--separator=STR String to form horizontal separator on info line
|
--separator=STR String to form horizontal separator on info line
|
||||||
--no-separator Hide info line separator
|
--no-separator Hide info line separator
|
||||||
--scrollbar[=C1[C2]] Scrollbar character(s) (each for main and preview window)
|
--scrollbar[=C1[C2]] Scrollbar character(s) (each for main and preview window)
|
||||||
@@ -120,6 +120,7 @@ const usage = `usage: fzf [options]
|
|||||||
--read0 Read input delimited by ASCII NUL characters
|
--read0 Read input delimited by ASCII NUL characters
|
||||||
--print0 Print output delimited by ASCII NUL characters
|
--print0 Print output delimited by ASCII NUL characters
|
||||||
--sync Synchronous search for multi-staged filtering
|
--sync Synchronous search for multi-staged filtering
|
||||||
|
--with-shell=STR Shell command and flags to start child processes with
|
||||||
--listen[=[ADDR:]PORT] Start HTTP server to receive actions (POST /)
|
--listen[=[ADDR:]PORT] Start HTTP server to receive actions (POST /)
|
||||||
(To allow remote process execution, use --listen-unsafe)
|
(To allow remote process execution, use --listen-unsafe)
|
||||||
--version Display version information and exit
|
--version Display version information and exit
|
||||||
@@ -143,7 +144,7 @@ const usage = `usage: fzf [options]
|
|||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
const defaultInfoSep = " < "
|
const defaultInfoPrefix = " < "
|
||||||
|
|
||||||
// Case denotes case-sensitivity of search
|
// Case denotes case-sensitivity of search
|
||||||
type Case int
|
type Case int
|
||||||
@@ -217,10 +218,6 @@ const (
|
|||||||
infoHidden
|
infoHidden
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s infoStyle) noExtraLine() bool {
|
|
||||||
return s == infoInline || s == infoInlineRight || s == infoHidden
|
|
||||||
}
|
|
||||||
|
|
||||||
type labelOpts struct {
|
type labelOpts struct {
|
||||||
label string
|
label string
|
||||||
column int
|
column int
|
||||||
@@ -327,7 +324,7 @@ type Options struct {
|
|||||||
ScrollOff int
|
ScrollOff int
|
||||||
FileWord bool
|
FileWord bool
|
||||||
InfoStyle infoStyle
|
InfoStyle infoStyle
|
||||||
InfoSep string
|
InfoPrefix string
|
||||||
Separator *string
|
Separator *string
|
||||||
JumpLabels string
|
JumpLabels string
|
||||||
Prompt string
|
Prompt string
|
||||||
@@ -360,6 +357,7 @@ type Options struct {
|
|||||||
Unicode bool
|
Unicode bool
|
||||||
Ambidouble bool
|
Ambidouble bool
|
||||||
Tabstop int
|
Tabstop int
|
||||||
|
WithShell string
|
||||||
ListenAddr *listenAddress
|
ListenAddr *listenAddress
|
||||||
Unsafe bool
|
Unsafe bool
|
||||||
ClearOnExit bool
|
ClearOnExit bool
|
||||||
@@ -367,6 +365,10 @@ type Options struct {
|
|||||||
WalkerRoot string
|
WalkerRoot string
|
||||||
WalkerSkip []string
|
WalkerSkip []string
|
||||||
Version bool
|
Version bool
|
||||||
|
CPUProfile string
|
||||||
|
MEMProfile string
|
||||||
|
BlockProfile string
|
||||||
|
MutexProfile string
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterNonEmpty(input []string) []string {
|
func filterNonEmpty(input []string) []string {
|
||||||
@@ -458,14 +460,14 @@ func defaultOptions() *Options {
|
|||||||
|
|
||||||
func help(code int) {
|
func help(code int) {
|
||||||
os.Stdout.WriteString(usage)
|
os.Stdout.WriteString(usage)
|
||||||
os.Exit(code)
|
util.Exit(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
var errorContext = ""
|
var errorContext = ""
|
||||||
|
|
||||||
func errorExit(msg string) {
|
func errorExit(msg string) {
|
||||||
os.Stderr.WriteString(errorContext + msg + "\n")
|
os.Stderr.WriteString(errorContext + msg + "\n")
|
||||||
os.Exit(exitError)
|
util.Exit(exitError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func optString(arg string, prefixes ...string) (bool, string) {
|
func optString(arg string, prefixes ...string) (bool, string) {
|
||||||
@@ -670,8 +672,8 @@ func parseKeyChordsImpl(str string, message string, exit func(string)) map[tui.E
|
|||||||
add(tui.CtrlM)
|
add(tui.CtrlM)
|
||||||
case "space":
|
case "space":
|
||||||
chords[tui.Key(' ')] = key
|
chords[tui.Key(' ')] = key
|
||||||
case "bspace", "bs":
|
case "backspace", "bspace", "bs":
|
||||||
add(tui.BSpace)
|
add(tui.Backspace)
|
||||||
case "ctrl-space":
|
case "ctrl-space":
|
||||||
add(tui.CtrlSpace)
|
add(tui.CtrlSpace)
|
||||||
case "ctrl-delete":
|
case "ctrl-delete":
|
||||||
@@ -702,12 +704,16 @@ func parseKeyChordsImpl(str string, message string, exit func(string)) map[tui.E
|
|||||||
add(tui.One)
|
add(tui.One)
|
||||||
case "zero":
|
case "zero":
|
||||||
add(tui.Zero)
|
add(tui.Zero)
|
||||||
|
case "jump":
|
||||||
|
add(tui.Jump)
|
||||||
|
case "jump-cancel":
|
||||||
|
add(tui.JumpCancel)
|
||||||
case "alt-enter", "alt-return":
|
case "alt-enter", "alt-return":
|
||||||
chords[tui.CtrlAltKey('m')] = key
|
chords[tui.CtrlAltKey('m')] = key
|
||||||
case "alt-space":
|
case "alt-space":
|
||||||
chords[tui.AltKey(' ')] = key
|
chords[tui.AltKey(' ')] = key
|
||||||
case "alt-bs", "alt-bspace":
|
case "alt-bs", "alt-bspace", "alt-backspace":
|
||||||
add(tui.AltBS)
|
add(tui.AltBackspace)
|
||||||
case "alt-up":
|
case "alt-up":
|
||||||
add(tui.AltUp)
|
add(tui.AltUp)
|
||||||
case "alt-down":
|
case "alt-down":
|
||||||
@@ -719,11 +725,11 @@ func parseKeyChordsImpl(str string, message string, exit func(string)) map[tui.E
|
|||||||
case "tab":
|
case "tab":
|
||||||
add(tui.Tab)
|
add(tui.Tab)
|
||||||
case "btab", "shift-tab":
|
case "btab", "shift-tab":
|
||||||
add(tui.BTab)
|
add(tui.ShiftTab)
|
||||||
case "esc":
|
case "esc":
|
||||||
add(tui.ESC)
|
add(tui.Esc)
|
||||||
case "del":
|
case "delete", "del":
|
||||||
add(tui.Del)
|
add(tui.Delete)
|
||||||
case "home":
|
case "home":
|
||||||
add(tui.Home)
|
add(tui.Home)
|
||||||
case "end":
|
case "end":
|
||||||
@@ -731,27 +737,27 @@ func parseKeyChordsImpl(str string, message string, exit func(string)) map[tui.E
|
|||||||
case "insert":
|
case "insert":
|
||||||
add(tui.Insert)
|
add(tui.Insert)
|
||||||
case "pgup", "page-up":
|
case "pgup", "page-up":
|
||||||
add(tui.PgUp)
|
add(tui.PageUp)
|
||||||
case "pgdn", "page-down":
|
case "pgdn", "page-down":
|
||||||
add(tui.PgDn)
|
add(tui.PageDown)
|
||||||
case "alt-shift-up", "shift-alt-up":
|
case "alt-shift-up", "shift-alt-up":
|
||||||
add(tui.AltSUp)
|
add(tui.AltShiftUp)
|
||||||
case "alt-shift-down", "shift-alt-down":
|
case "alt-shift-down", "shift-alt-down":
|
||||||
add(tui.AltSDown)
|
add(tui.AltShiftDown)
|
||||||
case "alt-shift-left", "shift-alt-left":
|
case "alt-shift-left", "shift-alt-left":
|
||||||
add(tui.AltSLeft)
|
add(tui.AltShiftLeft)
|
||||||
case "alt-shift-right", "shift-alt-right":
|
case "alt-shift-right", "shift-alt-right":
|
||||||
add(tui.AltSRight)
|
add(tui.AltShiftRight)
|
||||||
case "shift-up":
|
case "shift-up":
|
||||||
add(tui.SUp)
|
add(tui.ShiftUp)
|
||||||
case "shift-down":
|
case "shift-down":
|
||||||
add(tui.SDown)
|
add(tui.ShiftDown)
|
||||||
case "shift-left":
|
case "shift-left":
|
||||||
add(tui.SLeft)
|
add(tui.ShiftLeft)
|
||||||
case "shift-right":
|
case "shift-right":
|
||||||
add(tui.SRight)
|
add(tui.ShiftRight)
|
||||||
case "shift-delete":
|
case "shift-delete":
|
||||||
add(tui.SDelete)
|
add(tui.ShiftDelete)
|
||||||
case "left-click":
|
case "left-click":
|
||||||
add(tui.LeftClick)
|
add(tui.LeftClick)
|
||||||
case "right-click":
|
case "right-click":
|
||||||
@@ -1051,7 +1057,7 @@ const (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
executeRegexp = regexp.MustCompile(
|
executeRegexp = regexp.MustCompile(
|
||||||
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:header|query|prompt|border-label|preview-label)|transform|change-preview-window|change-preview|(?:re|un)bind|pos|put)`)
|
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:header|query|prompt|border-label|preview-label)|transform|change-(?:preview-window|preview|multi)|(?:re|un)bind|pos|put)`)
|
||||||
splitRegexp = regexp.MustCompile("[,:]+")
|
splitRegexp = regexp.MustCompile("[,:]+")
|
||||||
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
|
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
|
||||||
}
|
}
|
||||||
@@ -1211,14 +1217,18 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
|||||||
appendAction(actToggleSearch)
|
appendAction(actToggleSearch)
|
||||||
case "toggle-track":
|
case "toggle-track":
|
||||||
appendAction(actToggleTrack)
|
appendAction(actToggleTrack)
|
||||||
|
case "toggle-track-current":
|
||||||
|
appendAction(actToggleTrackCurrent)
|
||||||
case "toggle-header":
|
case "toggle-header":
|
||||||
appendAction(actToggleHeader)
|
appendAction(actToggleHeader)
|
||||||
case "show-header":
|
case "show-header":
|
||||||
appendAction(actShowHeader)
|
appendAction(actShowHeader)
|
||||||
case "hide-header":
|
case "hide-header":
|
||||||
appendAction(actHideHeader)
|
appendAction(actHideHeader)
|
||||||
case "track":
|
case "track", "track-current":
|
||||||
appendAction(actTrack)
|
appendAction(actTrackCurrent)
|
||||||
|
case "untrack-current":
|
||||||
|
appendAction(actUntrackCurrent)
|
||||||
case "select":
|
case "select":
|
||||||
appendAction(actSelect)
|
appendAction(actSelect)
|
||||||
case "select-all":
|
case "select-all":
|
||||||
@@ -1298,6 +1308,8 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
|||||||
if t == actIgnore {
|
if t == actIgnore {
|
||||||
if specIndex == 0 && specLower == "" {
|
if specIndex == 0 && specLower == "" {
|
||||||
actions = append(prevActions, actions...)
|
actions = append(prevActions, actions...)
|
||||||
|
} else if specLower == "change-multi" {
|
||||||
|
appendAction(actChangeMulti)
|
||||||
} else {
|
} else {
|
||||||
exit("unknown action: " + spec)
|
exit("unknown action: " + spec)
|
||||||
}
|
}
|
||||||
@@ -1317,10 +1329,6 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
|||||||
actions = append(actions, &action{t: t, a: actionArg})
|
actions = append(actions, &action{t: t, a: actionArg})
|
||||||
}
|
}
|
||||||
switch t {
|
switch t {
|
||||||
case actBecome:
|
|
||||||
if util.IsWindows() {
|
|
||||||
exit("become action is not supported on Windows")
|
|
||||||
}
|
|
||||||
case actUnbind, actRebind:
|
case actUnbind, actRebind:
|
||||||
parseKeyChordsImpl(actionArg, spec[0:offset]+" target required", exit)
|
parseKeyChordsImpl(actionArg, spec[0:offset]+" target required", exit)
|
||||||
case actChangePreviewWindow:
|
case actChangePreviewWindow:
|
||||||
@@ -1399,6 +1407,8 @@ func isExecuteAction(str string) actionType {
|
|||||||
return actChangePrompt
|
return actChangePrompt
|
||||||
case "change-query":
|
case "change-query":
|
||||||
return actChangeQuery
|
return actChangeQuery
|
||||||
|
case "change-multi":
|
||||||
|
return actChangeMulti
|
||||||
case "pos":
|
case "pos":
|
||||||
return actPosition
|
return actPosition
|
||||||
case "execute":
|
case "execute":
|
||||||
@@ -1502,17 +1512,24 @@ func parseInfoStyle(str string) (infoStyle, string) {
|
|||||||
case "right":
|
case "right":
|
||||||
return infoRight, ""
|
return infoRight, ""
|
||||||
case "inline":
|
case "inline":
|
||||||
return infoInline, defaultInfoSep
|
return infoInline, defaultInfoPrefix
|
||||||
case "inline-right":
|
case "inline-right":
|
||||||
return infoInlineRight, ""
|
return infoInlineRight, ""
|
||||||
case "hidden":
|
case "hidden":
|
||||||
return infoHidden, ""
|
return infoHidden, ""
|
||||||
default:
|
default:
|
||||||
prefix := "inline:"
|
type infoSpec struct {
|
||||||
if strings.HasPrefix(str, prefix) {
|
name string
|
||||||
return infoInline, strings.ReplaceAll(str[len(prefix):], "\n", " ")
|
style infoStyle
|
||||||
}
|
}
|
||||||
errorExit("invalid info style (expected: default|right|hidden|inline[:SEPARATOR]|inline-right)")
|
for _, spec := range []infoSpec{
|
||||||
|
{"inline", infoInline},
|
||||||
|
{"inline-right", infoInlineRight}} {
|
||||||
|
if strings.HasPrefix(str, spec.name+":") {
|
||||||
|
return spec.style, strings.ReplaceAll(str[len(spec.name)+1:], "\n", " ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
errorExit("invalid info style (expected: default|right|hidden|inline[-right][:PREFIX])")
|
||||||
}
|
}
|
||||||
return infoDefault, ""
|
return infoDefault, ""
|
||||||
}
|
}
|
||||||
@@ -1803,13 +1820,13 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
case "--no-filepath-word":
|
case "--no-filepath-word":
|
||||||
opts.FileWord = false
|
opts.FileWord = false
|
||||||
case "--info":
|
case "--info":
|
||||||
opts.InfoStyle, opts.InfoSep = parseInfoStyle(
|
opts.InfoStyle, opts.InfoPrefix = parseInfoStyle(
|
||||||
nextString(allArgs, &i, "info style required"))
|
nextString(allArgs, &i, "info style required"))
|
||||||
case "--no-info":
|
case "--no-info":
|
||||||
opts.InfoStyle = infoHidden
|
opts.InfoStyle = infoHidden
|
||||||
case "--inline-info":
|
case "--inline-info":
|
||||||
opts.InfoStyle = infoInline
|
opts.InfoStyle = infoInline
|
||||||
opts.InfoSep = defaultInfoSep
|
opts.InfoPrefix = defaultInfoPrefix
|
||||||
case "--no-inline-info":
|
case "--no-inline-info":
|
||||||
opts.InfoStyle = infoDefault
|
opts.InfoStyle = infoDefault
|
||||||
case "--separator":
|
case "--separator":
|
||||||
@@ -1861,9 +1878,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Marker = firstLine(nextString(allArgs, &i, "selected sign string required"))
|
opts.Marker = firstLine(nextString(allArgs, &i, "selected sign string required"))
|
||||||
case "--sync":
|
case "--sync":
|
||||||
opts.Sync = true
|
opts.Sync = true
|
||||||
case "--no-sync":
|
case "--no-sync", "--async":
|
||||||
opts.Sync = false
|
|
||||||
case "--async":
|
|
||||||
opts.Sync = false
|
opts.Sync = false
|
||||||
case "--no-history":
|
case "--no-history":
|
||||||
opts.History = nil
|
opts.History = nil
|
||||||
@@ -1940,6 +1955,8 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
nextString(allArgs, &i, "padding required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
|
nextString(allArgs, &i, "padding required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
|
||||||
case "--tabstop":
|
case "--tabstop":
|
||||||
opts.Tabstop = nextInt(allArgs, &i, "tab stop required")
|
opts.Tabstop = nextInt(allArgs, &i, "tab stop required")
|
||||||
|
case "--with-shell":
|
||||||
|
opts.WithShell = nextString(allArgs, &i, "shell command and flags required")
|
||||||
case "--listen", "--listen-unsafe":
|
case "--listen", "--listen-unsafe":
|
||||||
given, str := optionalNextString(allArgs, &i)
|
given, str := optionalNextString(allArgs, &i)
|
||||||
addr := defaultListenAddr
|
addr := defaultListenAddr
|
||||||
@@ -1967,6 +1984,14 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.WalkerSkip = filterNonEmpty(strings.Split(nextString(allArgs, &i, "directory names to ignore required"), ","))
|
opts.WalkerSkip = filterNonEmpty(strings.Split(nextString(allArgs, &i, "directory names to ignore required"), ","))
|
||||||
case "--version":
|
case "--version":
|
||||||
opts.Version = true
|
opts.Version = true
|
||||||
|
case "--profile-cpu":
|
||||||
|
opts.CPUProfile = nextString(allArgs, &i, "file path required: cpu")
|
||||||
|
case "--profile-mem":
|
||||||
|
opts.MEMProfile = nextString(allArgs, &i, "file path required: mem")
|
||||||
|
case "--profile-block":
|
||||||
|
opts.BlockProfile = nextString(allArgs, &i, "file path required: block")
|
||||||
|
case "--profile-mutex":
|
||||||
|
opts.MutexProfile = nextString(allArgs, &i, "file path required: mutex")
|
||||||
case "--":
|
case "--":
|
||||||
// Ignored
|
// Ignored
|
||||||
default:
|
default:
|
||||||
@@ -2011,7 +2036,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
} else if match, value := optString(arg, "--layout="); match {
|
} else if match, value := optString(arg, "--layout="); match {
|
||||||
opts.Layout = parseLayout(value)
|
opts.Layout = parseLayout(value)
|
||||||
} else if match, value := optString(arg, "--info="); match {
|
} else if match, value := optString(arg, "--info="); match {
|
||||||
opts.InfoStyle, opts.InfoSep = parseInfoStyle(value)
|
opts.InfoStyle, opts.InfoPrefix = parseInfoStyle(value)
|
||||||
} else if match, value := optString(arg, "--separator="); match {
|
} else if match, value := optString(arg, "--separator="); match {
|
||||||
opts.Separator = &value
|
opts.Separator = &value
|
||||||
} else if match, value := optString(arg, "--scrollbar="); match {
|
} else if match, value := optString(arg, "--scrollbar="); match {
|
||||||
@@ -2048,6 +2073,8 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Padding = parseMargin("padding", value)
|
opts.Padding = parseMargin("padding", value)
|
||||||
} else if match, value := optString(arg, "--tabstop="); match {
|
} else if match, value := optString(arg, "--tabstop="); match {
|
||||||
opts.Tabstop = atoi(value)
|
opts.Tabstop = atoi(value)
|
||||||
|
} else if match, value := optString(arg, "--with-shell="); match {
|
||||||
|
opts.WithShell = value
|
||||||
} else if match, value := optString(arg, "--listen="); match {
|
} else if match, value := optString(arg, "--listen="); match {
|
||||||
addr, err := parseListenAddress(value)
|
addr, err := parseListenAddress(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2236,9 +2263,7 @@ func postProcessOptions(opts *Options) {
|
|||||||
theme.Spinner = boldify(theme.Spinner)
|
theme.Spinner = boldify(theme.Spinner)
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Scheme != "default" {
|
processScheme(opts)
|
||||||
processScheme(opts)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func expectsArbitraryString(opt string) bool {
|
func expectsArbitraryString(opt string) bool {
|
||||||
@@ -2292,6 +2317,11 @@ func ParseOptions() *Options {
|
|||||||
errorContext = ""
|
errorContext = ""
|
||||||
parseOptions(opts, os.Args[1:])
|
parseOptions(opts, os.Args[1:])
|
||||||
|
|
||||||
|
if err := opts.initProfiling(); err != nil {
|
||||||
|
errorExit("failed to start pprof profiles: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
postProcessOptions(opts)
|
postProcessOptions(opts)
|
||||||
|
|
||||||
return opts
|
return opts
|
||||||
}
|
}
|
||||||
|
|||||||
11
src/options_no_pprof.go
Normal file
11
src/options_no_pprof.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
//go:build !pprof
|
||||||
|
// +build !pprof
|
||||||
|
|
||||||
|
package fzf
|
||||||
|
|
||||||
|
func (o *Options) initProfiling() error {
|
||||||
|
if o.CPUProfile != "" || o.MEMProfile != "" || o.BlockProfile != "" || o.MutexProfile != "" {
|
||||||
|
errorExit("error: profiling not supported: FZF must be built with '-tags=pprof' to enable profiling")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
73
src/options_pprof.go
Normal file
73
src/options_pprof.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
//go:build pprof
|
||||||
|
// +build pprof
|
||||||
|
|
||||||
|
package fzf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"runtime/pprof"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (o *Options) initProfiling() error {
|
||||||
|
if o.CPUProfile != "" {
|
||||||
|
f, err := os.Create(o.CPUProfile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create CPU profile: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := pprof.StartCPUProfile(f); err != nil {
|
||||||
|
return fmt.Errorf("could not start CPU profile: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
util.AtExit(func() {
|
||||||
|
pprof.StopCPUProfile()
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "Error: closing cpu profile:", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
stopProfile := func(name string, f *os.File) {
|
||||||
|
if err := pprof.Lookup(name).WriteTo(f, 0); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error: could not write %s profile: %v\n", name, err)
|
||||||
|
}
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error: closing %s profile: %v\n", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.MEMProfile != "" {
|
||||||
|
f, err := os.Create(o.MEMProfile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create MEM profile: %w", err)
|
||||||
|
}
|
||||||
|
util.AtExit(func() {
|
||||||
|
runtime.GC()
|
||||||
|
stopProfile("allocs", f)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.BlockProfile != "" {
|
||||||
|
runtime.SetBlockProfileRate(1)
|
||||||
|
f, err := os.Create(o.BlockProfile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create BLOCK profile: %w", err)
|
||||||
|
}
|
||||||
|
util.AtExit(func() { stopProfile("block", f) })
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.MutexProfile != "" {
|
||||||
|
runtime.SetMutexProfileFraction(1)
|
||||||
|
f, err := os.Create(o.MutexProfile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create MUTEX profile: %w", err)
|
||||||
|
}
|
||||||
|
util.AtExit(func() { stopProfile("mutex", f) })
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
89
src/options_pprof_test.go
Normal file
89
src/options_pprof_test.go
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
//go:build pprof
|
||||||
|
// +build pprof
|
||||||
|
|
||||||
|
package fzf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// runInitProfileTests is an internal flag used TestInitProfiling
|
||||||
|
var runInitProfileTests = flag.Bool("test-init-profile", false, "run init profile tests")
|
||||||
|
|
||||||
|
func TestInitProfiling(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("short test")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run this test in a separate process since it interferes with
|
||||||
|
// profiling and modifies the global atexit state. Without this
|
||||||
|
// running `go test -bench . -cpuprofile cpu.out` will fail.
|
||||||
|
if !*runInitProfileTests {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Make sure we are not the child process.
|
||||||
|
if os.Getenv("_FZF_CHILD_PROC") != "" {
|
||||||
|
t.Fatal("already running as child process!")
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(os.Args[0],
|
||||||
|
"-test.timeout", "30s",
|
||||||
|
"-test.run", "^"+t.Name()+"$",
|
||||||
|
"-test-init-profile",
|
||||||
|
)
|
||||||
|
cmd.Env = append(os.Environ(), "_FZF_CHILD_PROC=1")
|
||||||
|
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
out = bytes.TrimSpace(out)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Child test process failed: %v:\n%s", err, out)
|
||||||
|
}
|
||||||
|
// Make sure the test actually ran
|
||||||
|
if bytes.Contains(out, []byte("no tests to run")) {
|
||||||
|
t.Fatalf("Failed to run test %q:\n%s", t.Name(), out)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Child process
|
||||||
|
|
||||||
|
tempdir := t.TempDir()
|
||||||
|
t.Cleanup(util.RunAtExitFuncs)
|
||||||
|
|
||||||
|
o := Options{
|
||||||
|
CPUProfile: filepath.Join(tempdir, "cpu.prof"),
|
||||||
|
MEMProfile: filepath.Join(tempdir, "mem.prof"),
|
||||||
|
BlockProfile: filepath.Join(tempdir, "block.prof"),
|
||||||
|
MutexProfile: filepath.Join(tempdir, "mutex.prof"),
|
||||||
|
}
|
||||||
|
if err := o.initProfiling(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
profiles := []string{
|
||||||
|
o.CPUProfile,
|
||||||
|
o.MEMProfile,
|
||||||
|
o.BlockProfile,
|
||||||
|
o.MutexProfile,
|
||||||
|
}
|
||||||
|
for _, name := range profiles {
|
||||||
|
if _, err := os.Stat(name); err != nil {
|
||||||
|
t.Errorf("Failed to create profile %s: %v", filepath.Base(name), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
util.RunAtExitFuncs()
|
||||||
|
|
||||||
|
for _, name := range profiles {
|
||||||
|
if _, err := os.Stat(name); err != nil {
|
||||||
|
t.Errorf("Failed to write profile %s: %v", filepath.Base(name), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -170,8 +170,8 @@ func TestParseKeys(t *testing.T) {
|
|||||||
check(tui.CtrlM, "Return")
|
check(tui.CtrlM, "Return")
|
||||||
checkEvent(tui.Key(' '), "space")
|
checkEvent(tui.Key(' '), "space")
|
||||||
check(tui.Tab, "tab")
|
check(tui.Tab, "tab")
|
||||||
check(tui.BTab, "btab")
|
check(tui.ShiftTab, "btab")
|
||||||
check(tui.ESC, "esc")
|
check(tui.Esc, "esc")
|
||||||
check(tui.Up, "up")
|
check(tui.Up, "up")
|
||||||
check(tui.Down, "down")
|
check(tui.Down, "down")
|
||||||
check(tui.Left, "left")
|
check(tui.Left, "left")
|
||||||
@@ -182,16 +182,16 @@ func TestParseKeys(t *testing.T) {
|
|||||||
t.Error(11)
|
t.Error(11)
|
||||||
}
|
}
|
||||||
check(tui.Tab, "Ctrl-I")
|
check(tui.Tab, "Ctrl-I")
|
||||||
check(tui.PgUp, "page-up")
|
check(tui.PageUp, "page-up")
|
||||||
check(tui.PgDn, "Page-Down")
|
check(tui.PageDown, "Page-Down")
|
||||||
check(tui.Home, "Home")
|
check(tui.Home, "Home")
|
||||||
check(tui.End, "End")
|
check(tui.End, "End")
|
||||||
check(tui.AltBS, "Alt-BSpace")
|
check(tui.AltBackspace, "Alt-BSpace")
|
||||||
check(tui.SLeft, "shift-left")
|
check(tui.ShiftLeft, "shift-left")
|
||||||
check(tui.SRight, "shift-right")
|
check(tui.ShiftRight, "shift-right")
|
||||||
check(tui.BTab, "shift-tab")
|
check(tui.ShiftTab, "shift-tab")
|
||||||
check(tui.CtrlM, "Enter")
|
check(tui.CtrlM, "Enter")
|
||||||
check(tui.BSpace, "bspace")
|
check(tui.Backspace, "bspace")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseKeysWithComma(t *testing.T) {
|
func TestParseKeysWithComma(t *testing.T) {
|
||||||
|
|||||||
101
src/reader.go
101
src/reader.go
@@ -1,7 +1,7 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
// Reader reads from command or standard input
|
// Reader reads from command or standard input
|
||||||
type Reader struct {
|
type Reader struct {
|
||||||
pusher func([]byte) bool
|
pusher func([]byte) bool
|
||||||
|
executor *util.Executor
|
||||||
eventBox *util.EventBox
|
eventBox *util.EventBox
|
||||||
delimNil bool
|
delimNil bool
|
||||||
event int32
|
event int32
|
||||||
@@ -30,8 +31,8 @@ type Reader struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewReader returns new Reader object
|
// NewReader returns new Reader object
|
||||||
func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, delimNil bool, wait bool) *Reader {
|
func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, executor *util.Executor, delimNil bool, wait bool) *Reader {
|
||||||
return &Reader{pusher, eventBox, delimNil, int32(EvtReady), make(chan bool, 1), sync.Mutex{}, nil, nil, false, wait}
|
return &Reader{pusher, executor, eventBox, delimNil, int32(EvtReady), make(chan bool, 1), sync.Mutex{}, nil, nil, false, wait}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) startEventPoller() {
|
func (r *Reader) startEventPoller() {
|
||||||
@@ -111,32 +112,90 @@ func (r *Reader) ReadSource(root string, opts walkerOpts, ignores []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) feed(src io.Reader) {
|
func (r *Reader) feed(src io.Reader) {
|
||||||
|
/*
|
||||||
|
readerSlabSize, ae := strconv.Atoi(os.Getenv("SLAB_KB"))
|
||||||
|
if ae != nil {
|
||||||
|
readerSlabSize = 128 * 1024
|
||||||
|
} else {
|
||||||
|
readerSlabSize *= 1024
|
||||||
|
}
|
||||||
|
readerBufferSize, be := strconv.Atoi(os.Getenv("BUF_KB"))
|
||||||
|
if be != nil {
|
||||||
|
readerBufferSize = 64 * 1024
|
||||||
|
} else {
|
||||||
|
readerBufferSize *= 1024
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
delim := byte('\n')
|
delim := byte('\n')
|
||||||
|
trimCR := util.IsWindows()
|
||||||
if r.delimNil {
|
if r.delimNil {
|
||||||
delim = '\000'
|
delim = '\000'
|
||||||
|
trimCR = false
|
||||||
}
|
}
|
||||||
reader := bufio.NewReaderSize(src, readerBufferSize)
|
|
||||||
|
slab := make([]byte, readerSlabSize)
|
||||||
|
leftover := []byte{}
|
||||||
|
var err error
|
||||||
for {
|
for {
|
||||||
// ReadBytes returns err != nil if and only if the returned data does not
|
n := 0
|
||||||
// end in delim.
|
scope := slab[:util.Min(len(slab), readerBufferSize)]
|
||||||
bytea, err := reader.ReadBytes(delim)
|
for i := 0; i < 100; i++ {
|
||||||
byteaLen := len(bytea)
|
n, err = src.Read(scope)
|
||||||
if byteaLen > 0 {
|
if n > 0 || err != nil {
|
||||||
if err == nil {
|
break
|
||||||
// get rid of carriage return if under Windows:
|
|
||||||
if util.IsWindows() && byteaLen >= 2 && bytea[byteaLen-2] == byte('\r') {
|
|
||||||
bytea = bytea[:byteaLen-2]
|
|
||||||
} else {
|
|
||||||
bytea = bytea[:byteaLen-1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if r.pusher(bytea) {
|
|
||||||
atomic.StoreInt32(&r.event, int32(EvtReadNew))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
|
// We're not making any progress after 100 tries. Stop.
|
||||||
|
if n == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buf := slab[:n]
|
||||||
|
slab = slab[n:]
|
||||||
|
|
||||||
|
for len(buf) > 0 {
|
||||||
|
if i := bytes.IndexByte(buf, delim); i >= 0 {
|
||||||
|
// Found the delimiter
|
||||||
|
slice := buf[:i+1]
|
||||||
|
buf = buf[i+1:]
|
||||||
|
if trimCR && len(slice) >= 2 && slice[len(slice)-2] == byte('\r') {
|
||||||
|
slice = slice[:len(slice)-2]
|
||||||
|
} else {
|
||||||
|
slice = slice[:len(slice)-1]
|
||||||
|
}
|
||||||
|
if len(leftover) > 0 {
|
||||||
|
slice = append(leftover, slice...)
|
||||||
|
leftover = []byte{}
|
||||||
|
}
|
||||||
|
if (err == nil || len(slice) > 0) && r.pusher(slice) {
|
||||||
|
atomic.StoreInt32(&r.event, int32(EvtReadNew))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Could not find the delimiter in the buffer
|
||||||
|
// NOTE: We can further optimize this by keeping track of the cursor
|
||||||
|
// position in the slab so that a straddling item that doesn't go
|
||||||
|
// beyond the boundary of a slab doesn't need to be copied to
|
||||||
|
// another buffer. However, the performance gain is negligible in
|
||||||
|
// practice (< 0.1%) and is not
|
||||||
|
// worth the added complexity.
|
||||||
|
leftover = append(leftover, buf...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == io.EOF {
|
||||||
|
leftover = append(leftover, buf...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(slab) == 0 {
|
||||||
|
slab = make([]byte, readerSlabSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(leftover) > 0 && r.pusher(leftover) {
|
||||||
|
atomic.StoreInt32(&r.event, int32(EvtReadNew))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +243,7 @@ func (r *Reader) readFromCommand(command string, environ []string) bool {
|
|||||||
r.mutex.Lock()
|
r.mutex.Lock()
|
||||||
r.killed = false
|
r.killed = false
|
||||||
r.command = &command
|
r.command = &command
|
||||||
r.exec = util.ExecCommand(command, true)
|
r.exec = r.executor.ExecCommand(command, true)
|
||||||
if environ != nil {
|
if environ != nil {
|
||||||
r.exec.Env = environ
|
r.exec.Env = environ
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,10 @@ import (
|
|||||||
func TestReadFromCommand(t *testing.T) {
|
func TestReadFromCommand(t *testing.T) {
|
||||||
strs := []string{}
|
strs := []string{}
|
||||||
eb := util.NewEventBox()
|
eb := util.NewEventBox()
|
||||||
|
exec := util.NewExecutor("")
|
||||||
reader := NewReader(
|
reader := NewReader(
|
||||||
func(s []byte) bool { strs = append(strs, string(s)); return true },
|
func(s []byte) bool { strs = append(strs, string(s)); return true },
|
||||||
eb, false, true)
|
eb, exec, false, true)
|
||||||
|
|
||||||
reader.startEventPoller()
|
reader.startEventPoller()
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ const (
|
|||||||
httpUnauthorized = "HTTP/1.1 401 Unauthorized" + crlf
|
httpUnauthorized = "HTTP/1.1 401 Unauthorized" + crlf
|
||||||
httpUnavailable = "HTTP/1.1 503 Service Unavailable" + crlf
|
httpUnavailable = "HTTP/1.1 503 Service Unavailable" + crlf
|
||||||
httpReadTimeout = 10 * time.Second
|
httpReadTimeout = 10 * time.Second
|
||||||
|
channelTimeout = 2 * time.Second
|
||||||
jsonContentType = "Content-Type: application/json" + crlf
|
jsonContentType = "Content-Type: application/json" + crlf
|
||||||
maxContentLength = 1024 * 1024
|
maxContentLength = 1024 * 1024
|
||||||
)
|
)
|
||||||
@@ -77,7 +78,7 @@ func startHttpServer(address listenAddress, actionChannel chan []*action, respon
|
|||||||
port := address.port
|
port := address.port
|
||||||
apiKey := os.Getenv("FZF_API_KEY")
|
apiKey := os.Getenv("FZF_API_KEY")
|
||||||
if !address.IsLocal() && len(apiKey) == 0 {
|
if !address.IsLocal() && len(apiKey) == 0 {
|
||||||
return port, fmt.Errorf("FZF_API_KEY is required to allow remote access")
|
return port, errors.New("FZF_API_KEY is required to allow remote access")
|
||||||
}
|
}
|
||||||
addrStr := fmt.Sprintf("%s:%d", host, port)
|
addrStr := fmt.Sprintf("%s:%d", host, port)
|
||||||
listener, err := net.Listen("tcp", addrStr)
|
listener, err := net.Listen("tcp", addrStr)
|
||||||
@@ -170,7 +171,7 @@ func (server *httpServer) handleHttpRequest(conn net.Conn) string {
|
|||||||
select {
|
select {
|
||||||
case response := <-server.responseChannel:
|
case response := <-server.responseChannel:
|
||||||
return good(response)
|
return good(response)
|
||||||
case <-time.After(2 * time.Second):
|
case <-time.After(channelTimeout):
|
||||||
go func() {
|
go func() {
|
||||||
// Drain the channel
|
// Drain the channel
|
||||||
<-server.responseChannel
|
<-server.responseChannel
|
||||||
@@ -227,7 +228,11 @@ func (server *httpServer) handleHttpRequest(conn net.Conn) string {
|
|||||||
return bad("no action specified")
|
return bad("no action specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
server.actionChannel <- actions
|
select {
|
||||||
|
case server.actionChannel <- actions:
|
||||||
|
case <-time.After(channelTimeout):
|
||||||
|
return httpUnavailable + crlf
|
||||||
|
}
|
||||||
return httpOk + crlf
|
return httpOk + crlf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
286
src/terminal.go
286
src/terminal.go
@@ -7,7 +7,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -181,7 +180,7 @@ type Status struct {
|
|||||||
type Terminal struct {
|
type Terminal struct {
|
||||||
initDelay time.Duration
|
initDelay time.Duration
|
||||||
infoStyle infoStyle
|
infoStyle infoStyle
|
||||||
infoSep string
|
infoPrefix string
|
||||||
separator labelPrinter
|
separator labelPrinter
|
||||||
separatorLen int
|
separatorLen int
|
||||||
spinner []string
|
spinner []string
|
||||||
@@ -245,6 +244,7 @@ type Terminal struct {
|
|||||||
listenUnsafe bool
|
listenUnsafe bool
|
||||||
borderShape tui.BorderShape
|
borderShape tui.BorderShape
|
||||||
cleanExit bool
|
cleanExit bool
|
||||||
|
executor *util.Executor
|
||||||
paused bool
|
paused bool
|
||||||
border tui.Window
|
border tui.Window
|
||||||
window tui.Window
|
window tui.Window
|
||||||
@@ -293,6 +293,7 @@ type Terminal struct {
|
|||||||
executing *util.AtomicBool
|
executing *util.AtomicBool
|
||||||
termSize tui.TermSize
|
termSize tui.TermSize
|
||||||
lastAction actionType
|
lastAction actionType
|
||||||
|
lastKey string
|
||||||
lastFocus int32
|
lastFocus int32
|
||||||
areaLines int
|
areaLines int
|
||||||
areaColumns int
|
areaColumns int
|
||||||
@@ -366,6 +367,7 @@ const (
|
|||||||
actCancel
|
actCancel
|
||||||
actChangeBorderLabel
|
actChangeBorderLabel
|
||||||
actChangeHeader
|
actChangeHeader
|
||||||
|
actChangeMulti
|
||||||
actChangePreviewLabel
|
actChangePreviewLabel
|
||||||
actChangePrompt
|
actChangePrompt
|
||||||
actChangeQuery
|
actChangeQuery
|
||||||
@@ -394,8 +396,10 @@ const (
|
|||||||
actToggleIn
|
actToggleIn
|
||||||
actToggleOut
|
actToggleOut
|
||||||
actToggleTrack
|
actToggleTrack
|
||||||
|
actToggleTrackCurrent
|
||||||
actToggleHeader
|
actToggleHeader
|
||||||
actTrack
|
actTrackCurrent
|
||||||
|
actUntrackCurrent
|
||||||
actDown
|
actDown
|
||||||
actUp
|
actUp
|
||||||
actPageUp
|
actPageUp
|
||||||
@@ -406,7 +410,7 @@ const (
|
|||||||
actOffsetUp
|
actOffsetUp
|
||||||
actOffsetDown
|
actOffsetDown
|
||||||
actJump
|
actJump
|
||||||
actJumpAccept
|
actJumpAccept // XXX Deprecated in favor of jump:accept binding
|
||||||
actPrintQuery
|
actPrintQuery
|
||||||
actRefreshPreview
|
actRefreshPreview
|
||||||
actReplaceQuery
|
actReplaceQuery
|
||||||
@@ -458,14 +462,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (a actionType) Name() string {
|
func (a actionType) Name() string {
|
||||||
name := ""
|
return util.ToKebabCase(a.String()[3:])
|
||||||
for i, r := range a.String()[3:] {
|
|
||||||
if i > 0 && r >= 'A' && r <= 'Z' {
|
|
||||||
name += "-"
|
|
||||||
}
|
|
||||||
name += string(r)
|
|
||||||
}
|
|
||||||
return strings.ToLower(name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func processExecution(action actionType) bool {
|
func processExecution(action actionType) bool {
|
||||||
@@ -544,14 +541,14 @@ func defaultKeymap() map[tui.Event][]*action {
|
|||||||
add(tui.CtrlC, actAbort)
|
add(tui.CtrlC, actAbort)
|
||||||
add(tui.CtrlG, actAbort)
|
add(tui.CtrlG, actAbort)
|
||||||
add(tui.CtrlQ, actAbort)
|
add(tui.CtrlQ, actAbort)
|
||||||
add(tui.ESC, actAbort)
|
add(tui.Esc, actAbort)
|
||||||
add(tui.CtrlD, actDeleteCharEof)
|
add(tui.CtrlD, actDeleteCharEof)
|
||||||
add(tui.CtrlE, actEndOfLine)
|
add(tui.CtrlE, actEndOfLine)
|
||||||
add(tui.CtrlF, actForwardChar)
|
add(tui.CtrlF, actForwardChar)
|
||||||
add(tui.CtrlH, actBackwardDeleteChar)
|
add(tui.CtrlH, actBackwardDeleteChar)
|
||||||
add(tui.BSpace, actBackwardDeleteChar)
|
add(tui.Backspace, actBackwardDeleteChar)
|
||||||
add(tui.Tab, actToggleDown)
|
add(tui.Tab, actToggleDown)
|
||||||
add(tui.BTab, actToggleUp)
|
add(tui.ShiftTab, actToggleUp)
|
||||||
add(tui.CtrlJ, actDown)
|
add(tui.CtrlJ, actDown)
|
||||||
add(tui.CtrlK, actUp)
|
add(tui.CtrlK, actUp)
|
||||||
add(tui.CtrlL, actClearScreen)
|
add(tui.CtrlL, actClearScreen)
|
||||||
@@ -566,11 +563,11 @@ func defaultKeymap() map[tui.Event][]*action {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addEvent(tui.AltKey('b'), actBackwardWord)
|
addEvent(tui.AltKey('b'), actBackwardWord)
|
||||||
add(tui.SLeft, actBackwardWord)
|
add(tui.ShiftLeft, actBackwardWord)
|
||||||
addEvent(tui.AltKey('f'), actForwardWord)
|
addEvent(tui.AltKey('f'), actForwardWord)
|
||||||
add(tui.SRight, actForwardWord)
|
add(tui.ShiftRight, actForwardWord)
|
||||||
addEvent(tui.AltKey('d'), actKillWord)
|
addEvent(tui.AltKey('d'), actKillWord)
|
||||||
add(tui.AltBS, actBackwardKillWord)
|
add(tui.AltBackspace, actBackwardKillWord)
|
||||||
|
|
||||||
add(tui.Up, actUp)
|
add(tui.Up, actUp)
|
||||||
add(tui.Down, actDown)
|
add(tui.Down, actDown)
|
||||||
@@ -579,12 +576,12 @@ func defaultKeymap() map[tui.Event][]*action {
|
|||||||
|
|
||||||
add(tui.Home, actBeginningOfLine)
|
add(tui.Home, actBeginningOfLine)
|
||||||
add(tui.End, actEndOfLine)
|
add(tui.End, actEndOfLine)
|
||||||
add(tui.Del, actDeleteChar)
|
add(tui.Delete, actDeleteChar)
|
||||||
add(tui.PgUp, actPageUp)
|
add(tui.PageUp, actPageUp)
|
||||||
add(tui.PgDn, actPageDown)
|
add(tui.PageDown, actPageDown)
|
||||||
|
|
||||||
add(tui.SUp, actPreviewUp)
|
add(tui.ShiftUp, actPreviewUp)
|
||||||
add(tui.SDown, actPreviewDown)
|
add(tui.ShiftDown, actPreviewDown)
|
||||||
|
|
||||||
add(tui.Mouse, actMouse)
|
add(tui.Mouse, actMouse)
|
||||||
add(tui.LeftClick, actClick)
|
add(tui.LeftClick, actClick)
|
||||||
@@ -643,7 +640,7 @@ func evaluateHeight(opts *Options, termHeight int) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewTerminal returns new Terminal object
|
// NewTerminal returns new Terminal object
|
||||||
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor) *Terminal {
|
||||||
input := trimQuery(opts.Query)
|
input := trimQuery(opts.Query)
|
||||||
var delay time.Duration
|
var delay time.Duration
|
||||||
if opts.Tac {
|
if opts.Tac {
|
||||||
@@ -675,7 +672,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
if previewBox != nil && opts.Preview.aboveOrBelow() {
|
if previewBox != nil && opts.Preview.aboveOrBelow() {
|
||||||
effectiveMinHeight += 1 + borderLines(opts.Preview.border)
|
effectiveMinHeight += 1 + borderLines(opts.Preview.border)
|
||||||
}
|
}
|
||||||
if opts.InfoStyle.noExtraLine() {
|
if noSeparatorLine(opts.InfoStyle, opts.Separator == nil || uniseg.StringWidth(*opts.Separator) > 0) {
|
||||||
effectiveMinHeight--
|
effectiveMinHeight--
|
||||||
}
|
}
|
||||||
effectiveMinHeight += borderLines(opts.BorderShape)
|
effectiveMinHeight += borderLines(opts.BorderShape)
|
||||||
@@ -697,7 +694,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
t := Terminal{
|
t := Terminal{
|
||||||
initDelay: delay,
|
initDelay: delay,
|
||||||
infoStyle: opts.InfoStyle,
|
infoStyle: opts.InfoStyle,
|
||||||
infoSep: opts.InfoSep,
|
infoPrefix: opts.InfoPrefix,
|
||||||
separator: nil,
|
separator: nil,
|
||||||
spinner: makeSpinner(opts.Unicode),
|
spinner: makeSpinner(opts.Unicode),
|
||||||
promptString: opts.Prompt,
|
promptString: opts.Prompt,
|
||||||
@@ -739,6 +736,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
previewLabel: nil,
|
previewLabel: nil,
|
||||||
previewLabelOpts: opts.PreviewLabel,
|
previewLabelOpts: opts.PreviewLabel,
|
||||||
cleanExit: opts.ClearOnExit,
|
cleanExit: opts.ClearOnExit,
|
||||||
|
executor: executor,
|
||||||
paused: opts.Phony,
|
paused: opts.Phony,
|
||||||
cycle: opts.Cycle,
|
cycle: opts.Cycle,
|
||||||
headerVisible: true,
|
headerVisible: true,
|
||||||
@@ -776,7 +774,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
theme: opts.Theme,
|
theme: opts.Theme,
|
||||||
startChan: make(chan fitpad, 1),
|
startChan: make(chan fitpad, 1),
|
||||||
killChan: make(chan int),
|
killChan: make(chan int),
|
||||||
serverInputChan: make(chan []*action, 10),
|
serverInputChan: make(chan []*action, 100),
|
||||||
serverOutputChan: make(chan string),
|
serverOutputChan: make(chan string),
|
||||||
eventChan: make(chan tui.Event, 6), // (load + result + zero|one) | (focus) | (resize) | (GetChar)
|
eventChan: make(chan tui.Event, 6), // (load + result + zero|one) | (focus) | (resize) | (GetChar)
|
||||||
tui: renderer,
|
tui: renderer,
|
||||||
@@ -849,12 +847,16 @@ func (t *Terminal) environ() []string {
|
|||||||
}
|
}
|
||||||
env = append(env, "FZF_QUERY="+string(t.input))
|
env = append(env, "FZF_QUERY="+string(t.input))
|
||||||
env = append(env, "FZF_ACTION="+t.lastAction.Name())
|
env = append(env, "FZF_ACTION="+t.lastAction.Name())
|
||||||
|
env = append(env, "FZF_KEY="+t.lastKey)
|
||||||
env = append(env, "FZF_PROMPT="+string(t.promptString))
|
env = append(env, "FZF_PROMPT="+string(t.promptString))
|
||||||
|
env = append(env, "FZF_PREVIEW_LABEL="+t.previewLabelOpts.label)
|
||||||
|
env = append(env, "FZF_BORDER_LABEL="+t.borderLabelOpts.label)
|
||||||
env = append(env, fmt.Sprintf("FZF_TOTAL_COUNT=%d", t.count))
|
env = append(env, fmt.Sprintf("FZF_TOTAL_COUNT=%d", t.count))
|
||||||
env = append(env, fmt.Sprintf("FZF_MATCH_COUNT=%d", t.merger.Length()))
|
env = append(env, fmt.Sprintf("FZF_MATCH_COUNT=%d", t.merger.Length()))
|
||||||
env = append(env, fmt.Sprintf("FZF_SELECT_COUNT=%d", len(t.selected)))
|
env = append(env, fmt.Sprintf("FZF_SELECT_COUNT=%d", len(t.selected)))
|
||||||
env = append(env, fmt.Sprintf("FZF_LINES=%d", t.areaLines))
|
env = append(env, fmt.Sprintf("FZF_LINES=%d", t.areaLines))
|
||||||
env = append(env, fmt.Sprintf("FZF_COLUMNS=%d", t.areaColumns))
|
env = append(env, fmt.Sprintf("FZF_COLUMNS=%d", t.areaColumns))
|
||||||
|
env = append(env, fmt.Sprintf("FZF_POS=%d", util.Min(t.merger.Length(), t.cy+1)))
|
||||||
return env
|
return env
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -878,7 +880,7 @@ func (t *Terminal) visibleHeaderLines() int {
|
|||||||
// Extra number of lines needed to display fzf
|
// Extra number of lines needed to display fzf
|
||||||
func (t *Terminal) extraLines() int {
|
func (t *Terminal) extraLines() int {
|
||||||
extra := t.visibleHeaderLines() + 1
|
extra := t.visibleHeaderLines() + 1
|
||||||
if !t.noInfoLine() {
|
if !t.noSeparatorLine() {
|
||||||
extra++
|
extra++
|
||||||
}
|
}
|
||||||
return extra
|
return extra
|
||||||
@@ -984,8 +986,18 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) {
|
|||||||
return output, promptLen
|
return output, promptLen
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) noInfoLine() bool {
|
func noSeparatorLine(style infoStyle, separator bool) bool {
|
||||||
return t.infoStyle.noExtraLine()
|
switch style {
|
||||||
|
case infoInline:
|
||||||
|
return true
|
||||||
|
case infoHidden, infoInlineRight:
|
||||||
|
return !separator
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) noSeparatorLine() bool {
|
||||||
|
return noSeparatorLine(t.infoStyle, t.separatorLen > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getScrollbar(total int, height int, offset int) (int, int) {
|
func getScrollbar(total int, height int, offset int) (int, int) {
|
||||||
@@ -1058,7 +1070,7 @@ func (t *Terminal) UpdateProgress(progress float32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateList updates Merger to display the list
|
// UpdateList updates Merger to display the list
|
||||||
func (t *Terminal) UpdateList(merger *Merger) {
|
func (t *Terminal) UpdateList(merger *Merger, triggerResultEvent bool) {
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
prevIndex := minItem.Index()
|
prevIndex := minItem.Index()
|
||||||
reset := t.revision != merger.Revision()
|
reset := t.revision != merger.Revision()
|
||||||
@@ -1109,7 +1121,7 @@ func (t *Terminal) UpdateList(merger *Merger) {
|
|||||||
t.eventChan <- one
|
t.eventChan <- one
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if t.hasResultActions {
|
if triggerResultEvent && t.hasResultActions {
|
||||||
t.eventChan <- tui.Result.AsEvent()
|
t.eventChan <- tui.Result.AsEvent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1237,7 +1249,7 @@ func (t *Terminal) adjustMarginAndPadding() (int, int, [4]int, [4]int) {
|
|||||||
|
|
||||||
minAreaWidth := minWidth
|
minAreaWidth := minWidth
|
||||||
minAreaHeight := minHeight
|
minAreaHeight := minHeight
|
||||||
if t.noInfoLine() {
|
if t.noSeparatorLine() {
|
||||||
minAreaHeight -= 1
|
minAreaHeight -= 1
|
||||||
}
|
}
|
||||||
if t.needPreviewWindow() {
|
if t.needPreviewWindow() {
|
||||||
@@ -1518,7 +1530,7 @@ func (t *Terminal) move(y int, x int, clear bool) {
|
|||||||
y = h - y - 1
|
y = h - y - 1
|
||||||
case layoutReverseList:
|
case layoutReverseList:
|
||||||
n := 2 + t.visibleHeaderLines()
|
n := 2 + t.visibleHeaderLines()
|
||||||
if t.noInfoLine() {
|
if t.noSeparatorLine() {
|
||||||
n--
|
n--
|
||||||
}
|
}
|
||||||
if y < n {
|
if y < n {
|
||||||
@@ -1558,7 +1570,7 @@ func (t *Terminal) updatePromptOffset() ([]rune, []rune) {
|
|||||||
func (t *Terminal) promptLine() int {
|
func (t *Terminal) promptLine() int {
|
||||||
if t.headerFirst {
|
if t.headerFirst {
|
||||||
max := t.window.Height() - 1
|
max := t.window.Height() - 1
|
||||||
if !t.noInfoLine() {
|
if !t.noSeparatorLine() {
|
||||||
max--
|
max--
|
||||||
}
|
}
|
||||||
return util.Min(t.visibleHeaderLines(), max)
|
return util.Min(t.visibleHeaderLines(), max)
|
||||||
@@ -1603,20 +1615,8 @@ func (t *Terminal) printInfo() {
|
|||||||
t.window.Print(" ") // Clear spinner
|
t.window.Print(" ") // Clear spinner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch t.infoStyle {
|
printInfoPrefix := func() {
|
||||||
case infoDefault:
|
str := t.infoPrefix
|
||||||
t.move(line+1, 0, t.separatorLen == 0)
|
|
||||||
printSpinner()
|
|
||||||
t.move(line+1, 2, false)
|
|
||||||
pos = 2
|
|
||||||
case infoRight:
|
|
||||||
t.move(line+1, 0, false)
|
|
||||||
case infoInlineRight:
|
|
||||||
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
|
||||||
t.move(line, pos, true)
|
|
||||||
case infoInline:
|
|
||||||
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
|
||||||
str := t.infoSep
|
|
||||||
maxWidth := t.window.Width() - pos
|
maxWidth := t.window.Width() - pos
|
||||||
width := util.StringWidth(str)
|
width := util.StringWidth(str)
|
||||||
if width > maxWidth {
|
if width > maxWidth {
|
||||||
@@ -1631,7 +1631,34 @@ func (t *Terminal) printInfo() {
|
|||||||
t.window.CPrint(tui.ColPrompt, str)
|
t.window.CPrint(tui.ColPrompt, str)
|
||||||
}
|
}
|
||||||
pos += width
|
pos += width
|
||||||
|
}
|
||||||
|
printSeparator := func(fillLength int, pad bool) {
|
||||||
|
// --------_
|
||||||
|
if t.separatorLen > 0 {
|
||||||
|
t.separator(t.window, fillLength)
|
||||||
|
t.window.Print(" ")
|
||||||
|
} else if pad {
|
||||||
|
t.window.Print(strings.Repeat(" ", fillLength+1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch t.infoStyle {
|
||||||
|
case infoDefault:
|
||||||
|
t.move(line+1, 0, t.separatorLen == 0)
|
||||||
|
printSpinner()
|
||||||
|
t.move(line+1, 2, false)
|
||||||
|
pos = 2
|
||||||
|
case infoRight:
|
||||||
|
t.move(line+1, 0, false)
|
||||||
|
case infoInlineRight:
|
||||||
|
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
||||||
|
case infoInline:
|
||||||
|
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
||||||
|
printInfoPrefix()
|
||||||
case infoHidden:
|
case infoHidden:
|
||||||
|
if t.separatorLen > 0 {
|
||||||
|
t.move(line+1, 0, false)
|
||||||
|
printSeparator(t.window.Width()-1, false)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1645,8 +1672,11 @@ func (t *Terminal) printInfo() {
|
|||||||
output += " -S"
|
output += " -S"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if t.track != trackDisabled {
|
switch t.track {
|
||||||
|
case trackEnabled:
|
||||||
output += " +T"
|
output += " +T"
|
||||||
|
case trackCurrent:
|
||||||
|
output += " +t"
|
||||||
}
|
}
|
||||||
if t.multi > 0 {
|
if t.multi > 0 {
|
||||||
if t.multi == maxMulti {
|
if t.multi == maxMulti {
|
||||||
@@ -1662,15 +1692,6 @@ func (t *Terminal) printInfo() {
|
|||||||
output = fmt.Sprintf("[Command failed: %s]", *t.failed)
|
output = fmt.Sprintf("[Command failed: %s]", *t.failed)
|
||||||
}
|
}
|
||||||
|
|
||||||
printSeparator := func(fillLength int, pad bool) {
|
|
||||||
// --------_
|
|
||||||
if t.separatorLen > 0 {
|
|
||||||
t.separator(t.window, fillLength)
|
|
||||||
t.window.Print(" ")
|
|
||||||
} else if pad {
|
|
||||||
t.window.Print(strings.Repeat(" ", fillLength+1))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if t.infoStyle == infoRight {
|
if t.infoStyle == infoRight {
|
||||||
maxWidth := t.window.Width()
|
maxWidth := t.window.Width()
|
||||||
if t.reading {
|
if t.reading {
|
||||||
@@ -1693,19 +1714,35 @@ func (t *Terminal) printInfo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if t.infoStyle == infoInlineRight {
|
if t.infoStyle == infoInlineRight {
|
||||||
pos = util.Max(pos, t.window.Width()-util.StringWidth(output)-3)
|
if len(t.infoPrefix) == 0 {
|
||||||
if pos >= t.window.Width() {
|
pos = util.Max(pos, t.window.Width()-util.StringWidth(output)-3)
|
||||||
return
|
if pos < t.window.Width() {
|
||||||
|
t.move(line, pos, false)
|
||||||
|
printSpinner()
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
if pos < t.window.Width()-1 {
|
||||||
|
t.window.Print(" ")
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pos = util.Max(pos, t.window.Width()-util.StringWidth(output)-util.StringWidth(t.infoPrefix)-1)
|
||||||
|
printInfoPrefix()
|
||||||
}
|
}
|
||||||
t.move(line, pos, false)
|
|
||||||
printSpinner()
|
|
||||||
t.window.Print(" ")
|
|
||||||
pos += 2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
maxWidth := t.window.Width() - pos
|
maxWidth := t.window.Width() - pos
|
||||||
output = t.trimMessage(output, maxWidth)
|
output = t.trimMessage(output, maxWidth)
|
||||||
t.window.CPrint(tui.ColInfo, output)
|
t.window.CPrint(tui.ColInfo, output)
|
||||||
|
|
||||||
|
if t.infoStyle == infoInlineRight {
|
||||||
|
if t.separatorLen > 0 {
|
||||||
|
t.move(line+1, 0, false)
|
||||||
|
printSeparator(t.window.Width()-1, false)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
fillLength := maxWidth - len(output) - 2
|
fillLength := maxWidth - len(output) - 2
|
||||||
if fillLength > 0 {
|
if fillLength > 0 {
|
||||||
t.window.CPrint(tui.ColSeparator, " ")
|
t.window.CPrint(tui.ColSeparator, " ")
|
||||||
@@ -1720,7 +1757,7 @@ func (t *Terminal) printHeader() {
|
|||||||
max := t.window.Height()
|
max := t.window.Height()
|
||||||
if t.headerFirst {
|
if t.headerFirst {
|
||||||
max--
|
max--
|
||||||
if !t.noInfoLine() {
|
if !t.noSeparatorLine() {
|
||||||
max--
|
max--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1737,7 +1774,7 @@ func (t *Terminal) printHeader() {
|
|||||||
}
|
}
|
||||||
if !t.headerFirst {
|
if !t.headerFirst {
|
||||||
line++
|
line++
|
||||||
if !t.noInfoLine() {
|
if !t.noSeparatorLine() {
|
||||||
line++
|
line++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1769,7 +1806,7 @@ func (t *Terminal) printList() {
|
|||||||
i = maxy - 1 - j
|
i = maxy - 1 - j
|
||||||
}
|
}
|
||||||
line := i + 2 + t.visibleHeaderLines()
|
line := i + 2 + t.visibleHeaderLines()
|
||||||
if t.noInfoLine() {
|
if t.noSeparatorLine() {
|
||||||
line--
|
line--
|
||||||
}
|
}
|
||||||
if i < count {
|
if i < count {
|
||||||
@@ -2487,6 +2524,7 @@ type replacePlaceholderParams struct {
|
|||||||
allItems []*Item
|
allItems []*Item
|
||||||
lastAction actionType
|
lastAction actionType
|
||||||
prompt string
|
prompt string
|
||||||
|
executor *util.Executor
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) replacePlaceholder(template string, forcePlus bool, input string, list []*Item) string {
|
func (t *Terminal) replacePlaceholder(template string, forcePlus bool, input string, list []*Item) string {
|
||||||
@@ -2500,6 +2538,7 @@ func (t *Terminal) replacePlaceholder(template string, forcePlus bool, input str
|
|||||||
allItems: list,
|
allItems: list,
|
||||||
lastAction: t.lastAction,
|
lastAction: t.lastAction,
|
||||||
prompt: t.promptString,
|
prompt: t.promptString,
|
||||||
|
executor: t.executor,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2560,7 +2599,7 @@ func replacePlaceholder(params replacePlaceholderParams) string {
|
|||||||
case escaped:
|
case escaped:
|
||||||
return match
|
return match
|
||||||
case match == "{q}" || match == "{fzf:query}":
|
case match == "{q}" || match == "{fzf:query}":
|
||||||
return quoteEntry(params.query)
|
return params.executor.QuoteEntry(params.query)
|
||||||
case match == "{}":
|
case match == "{}":
|
||||||
replace = func(item *Item) string {
|
replace = func(item *Item) string {
|
||||||
switch {
|
switch {
|
||||||
@@ -2573,13 +2612,13 @@ func replacePlaceholder(params replacePlaceholderParams) string {
|
|||||||
case flags.file:
|
case flags.file:
|
||||||
return item.AsString(params.stripAnsi)
|
return item.AsString(params.stripAnsi)
|
||||||
default:
|
default:
|
||||||
return quoteEntry(item.AsString(params.stripAnsi))
|
return params.executor.QuoteEntry(item.AsString(params.stripAnsi))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case match == "{fzf:action}":
|
case match == "{fzf:action}":
|
||||||
return params.lastAction.Name()
|
return params.lastAction.Name()
|
||||||
case match == "{fzf:prompt}":
|
case match == "{fzf:prompt}":
|
||||||
return quoteEntry(params.prompt)
|
return params.executor.QuoteEntry(params.prompt)
|
||||||
default:
|
default:
|
||||||
// token type and also failover (below)
|
// token type and also failover (below)
|
||||||
rangeExpressions := strings.Split(match[1:len(match)-1], ",")
|
rangeExpressions := strings.Split(match[1:len(match)-1], ",")
|
||||||
@@ -2613,7 +2652,7 @@ func replacePlaceholder(params replacePlaceholderParams) string {
|
|||||||
str = strings.TrimSpace(str)
|
str = strings.TrimSpace(str)
|
||||||
}
|
}
|
||||||
if !flags.file {
|
if !flags.file {
|
||||||
str = quoteEntry(str)
|
str = params.executor.QuoteEntry(str)
|
||||||
}
|
}
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
@@ -2653,7 +2692,7 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
|||||||
return line
|
return line
|
||||||
}
|
}
|
||||||
command := t.replacePlaceholder(template, forcePlus, string(t.input), list)
|
command := t.replacePlaceholder(template, forcePlus, string(t.input), list)
|
||||||
cmd := util.ExecCommand(command, false)
|
cmd := t.executor.ExecCommand(command, false)
|
||||||
cmd.Env = t.environ()
|
cmd.Env = t.environ()
|
||||||
t.executing.Set(true)
|
t.executing.Set(true)
|
||||||
if !background {
|
if !background {
|
||||||
@@ -2930,7 +2969,7 @@ func (t *Terminal) Loop() {
|
|||||||
if items[0] != nil {
|
if items[0] != nil {
|
||||||
_, query := t.Input()
|
_, query := t.Input()
|
||||||
command := t.replacePlaceholder(commandTemplate, false, string(query), items)
|
command := t.replacePlaceholder(commandTemplate, false, string(query), items)
|
||||||
cmd := util.ExecCommand(command, true)
|
cmd := t.executor.ExecCommand(command, true)
|
||||||
env := t.environ()
|
env := t.environ()
|
||||||
if pwindowSize.Lines > 0 {
|
if pwindowSize.Lines > 0 {
|
||||||
lines := fmt.Sprintf("LINES=%d", pwindowSize.Lines)
|
lines := fmt.Sprintf("LINES=%d", pwindowSize.Lines)
|
||||||
@@ -3095,7 +3134,7 @@ func (t *Terminal) Loop() {
|
|||||||
switch req {
|
switch req {
|
||||||
case reqPrompt:
|
case reqPrompt:
|
||||||
t.printPrompt()
|
t.printPrompt()
|
||||||
if t.noInfoLine() {
|
if t.infoStyle == infoInline || t.infoStyle == infoInlineRight {
|
||||||
t.printInfo()
|
t.printInfo()
|
||||||
}
|
}
|
||||||
case reqInfo:
|
case reqInfo:
|
||||||
@@ -3251,6 +3290,7 @@ func (t *Terminal) Loop() {
|
|||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
previousInput := t.input
|
previousInput := t.input
|
||||||
previousCx := t.cx
|
previousCx := t.cx
|
||||||
|
t.lastKey = event.KeyName()
|
||||||
events := []util.EventType{}
|
events := []util.EventType{}
|
||||||
req := func(evts ...util.EventType) {
|
req := func(evts ...util.EventType) {
|
||||||
for _, event := range evts {
|
for _, event := range evts {
|
||||||
@@ -3336,27 +3376,21 @@ func (t *Terminal) Loop() {
|
|||||||
valid, list := t.buildPlusList(a.a, false)
|
valid, list := t.buildPlusList(a.a, false)
|
||||||
if valid {
|
if valid {
|
||||||
command := t.replacePlaceholder(a.a, false, string(t.input), list)
|
command := t.replacePlaceholder(a.a, false, string(t.input), list)
|
||||||
shell := os.Getenv("SHELL")
|
t.tui.Close()
|
||||||
if len(shell) == 0 {
|
if t.history != nil {
|
||||||
shell = "sh"
|
t.history.append(string(t.input))
|
||||||
}
|
|
||||||
shellPath, err := exec.LookPath(shell)
|
|
||||||
if err == nil {
|
|
||||||
t.tui.Close()
|
|
||||||
if t.history != nil {
|
|
||||||
t.history.append(string(t.input))
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
FIXME: It is not at all clear why this is required.
|
|
||||||
The following command will report 'not a tty', unless we open
|
|
||||||
/dev/tty *twice* after closing the standard input for 'reload'
|
|
||||||
in Reader.terminate().
|
|
||||||
: | fzf --bind 'start:reload:ls' --bind 'enter:become:tty'
|
|
||||||
*/
|
|
||||||
tui.TtyIn()
|
|
||||||
util.SetStdin(tui.TtyIn())
|
|
||||||
syscall.Exec(shellPath, []string{shell, "-c", command}, os.Environ())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
FIXME: It is not at all clear why this is required.
|
||||||
|
The following command will report 'not a tty', unless we open
|
||||||
|
/dev/tty *twice* after closing the standard input for 'reload'
|
||||||
|
in Reader.terminate().
|
||||||
|
|
||||||
|
while : | fzf --bind 'start:reload:ls' --bind 'load:become:tty'; do echo; done
|
||||||
|
*/
|
||||||
|
tui.TtyIn()
|
||||||
|
t.executor.Become(tui.TtyIn(), t.environ(), command)
|
||||||
}
|
}
|
||||||
case actExecute, actExecuteSilent:
|
case actExecute, actExecuteSilent:
|
||||||
t.executeCommand(a.a, false, a.t == actExecuteSilent, false, false)
|
t.executeCommand(a.a, false, a.t == actExecuteSilent, false, false)
|
||||||
@@ -3454,6 +3488,19 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
case actPrintQuery:
|
case actPrintQuery:
|
||||||
req(reqPrintQuery)
|
req(reqPrintQuery)
|
||||||
|
case actChangeMulti:
|
||||||
|
multi := t.multi
|
||||||
|
if a.a == "" {
|
||||||
|
multi = maxMulti
|
||||||
|
} else if n, e := strconv.Atoi(a.a); e == nil && n >= 0 {
|
||||||
|
multi = n
|
||||||
|
}
|
||||||
|
if t.multi > 0 && multi != t.multi {
|
||||||
|
t.selected = make(map[int32]selectedItem)
|
||||||
|
t.version++
|
||||||
|
}
|
||||||
|
t.multi = multi
|
||||||
|
req(reqList, reqInfo)
|
||||||
case actChangeQuery:
|
case actChangeQuery:
|
||||||
t.input = []rune(a.a)
|
t.input = []rune(a.a)
|
||||||
t.cx = len(t.input)
|
t.cx = len(t.input)
|
||||||
@@ -3471,11 +3518,13 @@ func (t *Terminal) Loop() {
|
|||||||
req(reqHeader)
|
req(reqHeader)
|
||||||
}
|
}
|
||||||
case actChangeBorderLabel:
|
case actChangeBorderLabel:
|
||||||
|
t.borderLabelOpts.label = a.a
|
||||||
if t.border != nil {
|
if t.border != nil {
|
||||||
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(a.a, &tui.ColBorderLabel, false)
|
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(a.a, &tui.ColBorderLabel, false)
|
||||||
req(reqRedrawBorderLabel)
|
req(reqRedrawBorderLabel)
|
||||||
}
|
}
|
||||||
case actChangePreviewLabel:
|
case actChangePreviewLabel:
|
||||||
|
t.previewLabelOpts.label = a.a
|
||||||
if t.pborder != nil {
|
if t.pborder != nil {
|
||||||
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(a.a, &tui.ColPreviewLabel, false)
|
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(a.a, &tui.ColPreviewLabel, false)
|
||||||
req(reqRedrawPreviewLabel)
|
req(reqRedrawPreviewLabel)
|
||||||
@@ -3485,14 +3534,16 @@ func (t *Terminal) Loop() {
|
|||||||
actions := parseSingleActionList(strings.Trim(body, "\r\n"), func(message string) {})
|
actions := parseSingleActionList(strings.Trim(body, "\r\n"), func(message string) {})
|
||||||
return doActions(actions)
|
return doActions(actions)
|
||||||
case actTransformBorderLabel:
|
case actTransformBorderLabel:
|
||||||
|
label := t.executeCommand(a.a, false, true, true, true)
|
||||||
|
t.borderLabelOpts.label = label
|
||||||
if t.border != nil {
|
if t.border != nil {
|
||||||
label := t.executeCommand(a.a, false, true, true, true)
|
|
||||||
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(label, &tui.ColBorderLabel, false)
|
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(label, &tui.ColBorderLabel, false)
|
||||||
req(reqRedrawBorderLabel)
|
req(reqRedrawBorderLabel)
|
||||||
}
|
}
|
||||||
case actTransformPreviewLabel:
|
case actTransformPreviewLabel:
|
||||||
|
label := t.executeCommand(a.a, false, true, true, true)
|
||||||
|
t.previewLabelOpts.label = label
|
||||||
if t.pborder != nil {
|
if t.pborder != nil {
|
||||||
label := t.executeCommand(a.a, false, true, true, true)
|
|
||||||
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(label, &tui.ColPreviewLabel, false)
|
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(label, &tui.ColPreviewLabel, false)
|
||||||
req(reqRedrawPreviewLabel)
|
req(reqRedrawPreviewLabel)
|
||||||
}
|
}
|
||||||
@@ -3778,6 +3829,14 @@ func (t *Terminal) Loop() {
|
|||||||
t.track = trackEnabled
|
t.track = trackEnabled
|
||||||
}
|
}
|
||||||
req(reqInfo)
|
req(reqInfo)
|
||||||
|
case actToggleTrackCurrent:
|
||||||
|
switch t.track {
|
||||||
|
case trackCurrent:
|
||||||
|
t.track = trackDisabled
|
||||||
|
case trackDisabled:
|
||||||
|
t.track = trackCurrent
|
||||||
|
}
|
||||||
|
req(reqInfo)
|
||||||
case actShowHeader:
|
case actShowHeader:
|
||||||
t.headerVisible = true
|
t.headerVisible = true
|
||||||
req(reqList, reqInfo, reqPrompt, reqHeader)
|
req(reqList, reqInfo, reqPrompt, reqHeader)
|
||||||
@@ -3787,11 +3846,16 @@ func (t *Terminal) Loop() {
|
|||||||
case actToggleHeader:
|
case actToggleHeader:
|
||||||
t.headerVisible = !t.headerVisible
|
t.headerVisible = !t.headerVisible
|
||||||
req(reqList, reqInfo, reqPrompt, reqHeader)
|
req(reqList, reqInfo, reqPrompt, reqHeader)
|
||||||
case actTrack:
|
case actTrackCurrent:
|
||||||
if t.track == trackDisabled {
|
if t.track == trackDisabled {
|
||||||
t.track = trackCurrent
|
t.track = trackCurrent
|
||||||
}
|
}
|
||||||
req(reqInfo)
|
req(reqInfo)
|
||||||
|
case actUntrackCurrent:
|
||||||
|
if t.track == trackCurrent {
|
||||||
|
t.track = trackDisabled
|
||||||
|
}
|
||||||
|
req(reqInfo)
|
||||||
case actEnableSearch:
|
case actEnableSearch:
|
||||||
t.paused = false
|
t.paused = false
|
||||||
changed = true
|
changed = true
|
||||||
@@ -3880,7 +3944,7 @@ func (t *Terminal) Loop() {
|
|||||||
mx -= t.window.Left()
|
mx -= t.window.Left()
|
||||||
my -= t.window.Top()
|
my -= t.window.Top()
|
||||||
min := 2 + t.visibleHeaderLines()
|
min := 2 + t.visibleHeaderLines()
|
||||||
if t.noInfoLine() {
|
if t.noSeparatorLine() {
|
||||||
min--
|
min--
|
||||||
}
|
}
|
||||||
h := t.window.Height()
|
h := t.window.Height()
|
||||||
@@ -4052,6 +4116,9 @@ func (t *Terminal) Loop() {
|
|||||||
// Break out of jump mode if any action is submitted to the server
|
// Break out of jump mode if any action is submitted to the server
|
||||||
if t.jumping != jumpDisabled {
|
if t.jumping != jumpDisabled {
|
||||||
t.jumping = jumpDisabled
|
t.jumping = jumpDisabled
|
||||||
|
if acts, prs := t.keymap[tui.JumpCancel.AsEvent()]; prs && !doActions(acts) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
req(reqList)
|
req(reqList)
|
||||||
}
|
}
|
||||||
if len(actions) == 0 {
|
if len(actions) == 0 {
|
||||||
@@ -4065,19 +4132,17 @@ func (t *Terminal) Loop() {
|
|||||||
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.AsEvent()]; queryChanged && prs {
|
if onChanges, prs := t.keymap[tui.Change.AsEvent()]; queryChanged && prs && !doActions(onChanges) {
|
||||||
if !doActions(onChanges) {
|
continue
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if onEOFs, prs := t.keymap[tui.BackwardEOF.AsEvent()]; beof && prs {
|
if onEOFs, prs := t.keymap[tui.BackwardEOF.AsEvent()]; beof && prs && !doActions(onEOFs) {
|
||||||
if !doActions(onEOFs) {
|
continue
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
jumpEvent := tui.JumpCancel
|
||||||
if event.Type == 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() {
|
||||||
|
jumpEvent = tui.Jump
|
||||||
t.cy = idx + t.offset
|
t.cy = idx + t.offset
|
||||||
if t.jumping == jumpAcceptEnabled {
|
if t.jumping == jumpAcceptEnabled {
|
||||||
req(reqClose)
|
req(reqClose)
|
||||||
@@ -4085,6 +4150,9 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.jumping = jumpDisabled
|
t.jumping = jumpDisabled
|
||||||
|
if acts, prs := t.keymap[jumpEvent.AsEvent()]; prs && !doActions(acts) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
req(reqList)
|
req(reqList)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4172,7 +4240,7 @@ func (t *Terminal) vset(o int) bool {
|
|||||||
|
|
||||||
func (t *Terminal) maxItems() int {
|
func (t *Terminal) maxItems() int {
|
||||||
max := t.window.Height() - 2 - t.visibleHeaderLines()
|
max := t.window.Height() - 2 - t.visibleHeaderLines()
|
||||||
if t.noInfoLine() {
|
if t.noSeparatorLine() {
|
||||||
max++
|
max++
|
||||||
}
|
}
|
||||||
return util.Max(max, 0)
|
return util.Max(max, 0)
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ func replacePlaceholderTest(template string, stripAnsi bool, delimiter Delimiter
|
|||||||
allItems: allItems,
|
allItems: allItems,
|
||||||
lastAction: actBackwardDeleteCharEof,
|
lastAction: actBackwardDeleteCharEof,
|
||||||
prompt: "prompt",
|
prompt: "prompt",
|
||||||
|
executor: util.NewExecutor(""),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,6 +245,7 @@ func TestQuoteEntry(t *testing.T) {
|
|||||||
unixStyle := quotes{``, `'`, `'\''`, `"`, `\`}
|
unixStyle := quotes{``, `'`, `'\''`, `"`, `\`}
|
||||||
windowsStyle := quotes{`^`, `^"`, `'`, `\^"`, `\\`}
|
windowsStyle := quotes{`^`, `^"`, `'`, `\^"`, `\\`}
|
||||||
var effectiveStyle quotes
|
var effectiveStyle quotes
|
||||||
|
exec := util.NewExecutor("")
|
||||||
|
|
||||||
if util.IsWindows() {
|
if util.IsWindows() {
|
||||||
effectiveStyle = windowsStyle
|
effectiveStyle = windowsStyle
|
||||||
@@ -278,7 +280,7 @@ func TestQuoteEntry(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for input, expected := range tests {
|
for input, expected := range tests {
|
||||||
escaped := quoteEntry(input)
|
escaped := exec.QuoteEntry(input)
|
||||||
expected = templateToString(expected, effectiveStyle)
|
expected = templateToString(expected, effectiveStyle)
|
||||||
if escaped != expected {
|
if escaped != expected {
|
||||||
t.Errorf("Input: %s, expected: %s, actual %s", input, expected, escaped)
|
t.Errorf("Input: %s, expected: %s, actual %s", input, expected, escaped)
|
||||||
@@ -317,9 +319,9 @@ func TestUnixCommands(t *testing.T) {
|
|||||||
|
|
||||||
// purpose of this test is to demonstrate some shortcomings of fzf's templating system on Windows
|
// purpose of this test is to demonstrate some shortcomings of fzf's templating system on Windows
|
||||||
func TestWindowsCommands(t *testing.T) {
|
func TestWindowsCommands(t *testing.T) {
|
||||||
if !util.IsWindows() {
|
// XXX Deprecated
|
||||||
t.SkipNow()
|
t.SkipNow()
|
||||||
}
|
|
||||||
tests := []testCase{
|
tests := []testCase{
|
||||||
// reference: give{template, query, items}, want{output OR match}
|
// reference: give{template, query, items}, want{output OR match}
|
||||||
|
|
||||||
|
|||||||
@@ -5,26 +5,11 @@ package fzf
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
var escaper *strings.Replacer
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
tokens := strings.Split(os.Getenv("SHELL"), "/")
|
|
||||||
if tokens[len(tokens)-1] == "fish" {
|
|
||||||
// https://fishshell.com/docs/current/language.html#quotes
|
|
||||||
// > The only meaningful escape sequences in single quotes are \', which
|
|
||||||
// > escapes a single quote and \\, which escapes the backslash symbol.
|
|
||||||
escaper = strings.NewReplacer("\\", "\\\\", "'", "\\'")
|
|
||||||
} else {
|
|
||||||
escaper = strings.NewReplacer("'", "'\\''")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func notifyOnResize(resizeChan chan<- os.Signal) {
|
func notifyOnResize(resizeChan chan<- os.Signal) {
|
||||||
signal.Notify(resizeChan, syscall.SIGWINCH)
|
signal.Notify(resizeChan, syscall.SIGWINCH)
|
||||||
}
|
}
|
||||||
@@ -41,7 +26,3 @@ func notifyStop(p *os.Process) {
|
|||||||
func notifyOnCont(resizeChan chan<- os.Signal) {
|
func notifyOnCont(resizeChan chan<- os.Signal) {
|
||||||
signal.Notify(resizeChan, syscall.SIGCONT)
|
signal.Notify(resizeChan, syscall.SIGCONT)
|
||||||
}
|
}
|
||||||
|
|
||||||
func quoteEntry(entry string) string {
|
|
||||||
return "'" + escaper.Replace(entry) + "'"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ package fzf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func notifyOnResize(resizeChan chan<- os.Signal) {
|
func notifyOnResize(resizeChan chan<- os.Signal) {
|
||||||
@@ -19,27 +17,3 @@ func notifyStop(p *os.Process) {
|
|||||||
func notifyOnCont(resizeChan chan<- os.Signal) {
|
func notifyOnCont(resizeChan chan<- os.Signal) {
|
||||||
// NOOP
|
// NOOP
|
||||||
}
|
}
|
||||||
|
|
||||||
func quoteEntry(entry string) string {
|
|
||||||
shell := os.Getenv("SHELL")
|
|
||||||
if len(shell) == 0 {
|
|
||||||
shell = "cmd"
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(shell, "cmd") {
|
|
||||||
// backslash escaping is done here for applications
|
|
||||||
// (see ripgrep test case in terminal_test.go#TestWindowsCommands)
|
|
||||||
escaped := strings.Replace(entry, `\`, `\\`, -1)
|
|
||||||
escaped = `"` + strings.Replace(escaped, `"`, `\"`, -1) + `"`
|
|
||||||
// caret is the escape character for cmd shell
|
|
||||||
r, _ := regexp.Compile(`[&|<>()@^%!"]`)
|
|
||||||
return r.ReplaceAllStringFunc(escaped, func(match string) string {
|
|
||||||
return "^" + match
|
|
||||||
})
|
|
||||||
} else if strings.Contains(shell, "pwsh") || strings.Contains(shell, "powershell") {
|
|
||||||
escaped := strings.Replace(entry, `"`, `\"`, -1)
|
|
||||||
return "'" + strings.Replace(escaped, "'", "''", -1) + "'"
|
|
||||||
} else {
|
|
||||||
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ func withPrefixLengths(tokens []string, begin int) []Token {
|
|||||||
|
|
||||||
prefixLength := begin
|
prefixLength := begin
|
||||||
for idx := range tokens {
|
for idx := range tokens {
|
||||||
chars := util.ToChars([]byte(tokens[idx]))
|
chars := util.ToChars(sbytes(tokens[idx]))
|
||||||
ret[idx] = Token{&chars, int32(prefixLength)}
|
ret[idx] = Token{&chars, int32(prefixLength)}
|
||||||
prefixLength += chars.Length()
|
prefixLength += chars.Length()
|
||||||
}
|
}
|
||||||
@@ -187,7 +187,7 @@ func Transform(tokens []Token, withNth []Range) []Token {
|
|||||||
if r.begin == r.end {
|
if r.begin == r.end {
|
||||||
idx := r.begin
|
idx := r.begin
|
||||||
if idx == rangeEllipsis {
|
if idx == rangeEllipsis {
|
||||||
chars := util.ToChars([]byte(joinTokens(tokens)))
|
chars := util.ToChars(sbytes(joinTokens(tokens)))
|
||||||
parts = append(parts, &chars)
|
parts = append(parts, &chars)
|
||||||
} else {
|
} else {
|
||||||
if idx < 0 {
|
if idx < 0 {
|
||||||
|
|||||||
120
src/tui/eventtype_string.go
Normal file
120
src/tui/eventtype_string.go
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
// Code generated by "stringer -type=EventType"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package tui
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[Rune-0]
|
||||||
|
_ = x[CtrlA-1]
|
||||||
|
_ = x[CtrlB-2]
|
||||||
|
_ = x[CtrlC-3]
|
||||||
|
_ = x[CtrlD-4]
|
||||||
|
_ = x[CtrlE-5]
|
||||||
|
_ = x[CtrlF-6]
|
||||||
|
_ = x[CtrlG-7]
|
||||||
|
_ = x[CtrlH-8]
|
||||||
|
_ = x[Tab-9]
|
||||||
|
_ = x[CtrlJ-10]
|
||||||
|
_ = x[CtrlK-11]
|
||||||
|
_ = x[CtrlL-12]
|
||||||
|
_ = x[CtrlM-13]
|
||||||
|
_ = x[CtrlN-14]
|
||||||
|
_ = x[CtrlO-15]
|
||||||
|
_ = x[CtrlP-16]
|
||||||
|
_ = x[CtrlQ-17]
|
||||||
|
_ = x[CtrlR-18]
|
||||||
|
_ = x[CtrlS-19]
|
||||||
|
_ = x[CtrlT-20]
|
||||||
|
_ = x[CtrlU-21]
|
||||||
|
_ = x[CtrlV-22]
|
||||||
|
_ = x[CtrlW-23]
|
||||||
|
_ = x[CtrlX-24]
|
||||||
|
_ = x[CtrlY-25]
|
||||||
|
_ = x[CtrlZ-26]
|
||||||
|
_ = x[Esc-27]
|
||||||
|
_ = x[CtrlSpace-28]
|
||||||
|
_ = x[CtrlDelete-29]
|
||||||
|
_ = x[CtrlBackSlash-30]
|
||||||
|
_ = x[CtrlRightBracket-31]
|
||||||
|
_ = x[CtrlCaret-32]
|
||||||
|
_ = x[CtrlSlash-33]
|
||||||
|
_ = x[ShiftTab-34]
|
||||||
|
_ = x[Backspace-35]
|
||||||
|
_ = x[Delete-36]
|
||||||
|
_ = x[PageUp-37]
|
||||||
|
_ = x[PageDown-38]
|
||||||
|
_ = x[Up-39]
|
||||||
|
_ = x[Down-40]
|
||||||
|
_ = x[Left-41]
|
||||||
|
_ = x[Right-42]
|
||||||
|
_ = x[Home-43]
|
||||||
|
_ = x[End-44]
|
||||||
|
_ = x[Insert-45]
|
||||||
|
_ = x[ShiftUp-46]
|
||||||
|
_ = x[ShiftDown-47]
|
||||||
|
_ = x[ShiftLeft-48]
|
||||||
|
_ = x[ShiftRight-49]
|
||||||
|
_ = x[ShiftDelete-50]
|
||||||
|
_ = x[F1-51]
|
||||||
|
_ = x[F2-52]
|
||||||
|
_ = x[F3-53]
|
||||||
|
_ = x[F4-54]
|
||||||
|
_ = x[F5-55]
|
||||||
|
_ = x[F6-56]
|
||||||
|
_ = x[F7-57]
|
||||||
|
_ = x[F8-58]
|
||||||
|
_ = x[F9-59]
|
||||||
|
_ = x[F10-60]
|
||||||
|
_ = x[F11-61]
|
||||||
|
_ = x[F12-62]
|
||||||
|
_ = x[AltBackspace-63]
|
||||||
|
_ = x[AltUp-64]
|
||||||
|
_ = x[AltDown-65]
|
||||||
|
_ = x[AltLeft-66]
|
||||||
|
_ = x[AltRight-67]
|
||||||
|
_ = x[AltShiftUp-68]
|
||||||
|
_ = x[AltShiftDown-69]
|
||||||
|
_ = x[AltShiftLeft-70]
|
||||||
|
_ = x[AltShiftRight-71]
|
||||||
|
_ = x[Alt-72]
|
||||||
|
_ = x[CtrlAlt-73]
|
||||||
|
_ = x[Invalid-74]
|
||||||
|
_ = x[Mouse-75]
|
||||||
|
_ = x[DoubleClick-76]
|
||||||
|
_ = x[LeftClick-77]
|
||||||
|
_ = x[RightClick-78]
|
||||||
|
_ = x[SLeftClick-79]
|
||||||
|
_ = x[SRightClick-80]
|
||||||
|
_ = x[ScrollUp-81]
|
||||||
|
_ = x[ScrollDown-82]
|
||||||
|
_ = x[SScrollUp-83]
|
||||||
|
_ = x[SScrollDown-84]
|
||||||
|
_ = x[PreviewScrollUp-85]
|
||||||
|
_ = x[PreviewScrollDown-86]
|
||||||
|
_ = x[Resize-87]
|
||||||
|
_ = x[Change-88]
|
||||||
|
_ = x[BackwardEOF-89]
|
||||||
|
_ = x[Start-90]
|
||||||
|
_ = x[Load-91]
|
||||||
|
_ = x[Focus-92]
|
||||||
|
_ = x[One-93]
|
||||||
|
_ = x[Zero-94]
|
||||||
|
_ = x[Result-95]
|
||||||
|
_ = x[Jump-96]
|
||||||
|
_ = x[JumpCancel-97]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLCtrlMCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlDeleteCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltCtrlAltInvalidMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancel"
|
||||||
|
|
||||||
|
var _EventType_index = [...]uint16{0, 4, 9, 14, 19, 24, 29, 34, 39, 44, 47, 52, 57, 62, 67, 72, 77, 82, 87, 92, 97, 102, 107, 112, 117, 122, 127, 132, 135, 144, 154, 167, 183, 192, 201, 209, 218, 224, 230, 238, 240, 244, 248, 253, 257, 260, 266, 273, 282, 291, 301, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 333, 336, 339, 351, 356, 363, 370, 378, 388, 400, 412, 425, 428, 435, 442, 447, 458, 467, 477, 487, 498, 506, 516, 525, 536, 551, 568, 574, 580, 591, 596, 600, 605, 608, 612, 618, 622, 632}
|
||||||
|
|
||||||
|
func (i EventType) String() string {
|
||||||
|
if i < 0 || i >= EventType(len(_EventType_index)-1) {
|
||||||
|
return "EventType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _EventType_name[_EventType_index[i]:_EventType_index[i+1]]
|
||||||
|
}
|
||||||
@@ -71,7 +71,7 @@ func (r *LightRenderer) csi(code string) string {
|
|||||||
|
|
||||||
func (r *LightRenderer) flush() {
|
func (r *LightRenderer) flush() {
|
||||||
if r.queued.Len() > 0 {
|
if r.queued.Len() > 0 {
|
||||||
fmt.Fprint(os.Stderr, "\x1b[?25l"+r.queued.String()+"\x1b[?25h")
|
fmt.Fprint(os.Stderr, "\x1b[?7l\x1b[?25l"+r.queued.String()+"\x1b[?25h\x1b[?7h")
|
||||||
r.queued.Reset()
|
r.queued.Reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -245,7 +245,7 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
retries := 0
|
retries := 0
|
||||||
if c == ESC.Int() || nonblock {
|
if c == Esc.Int() || nonblock {
|
||||||
retries = r.escDelay / escPollInterval
|
retries = r.escDelay / escPollInterval
|
||||||
}
|
}
|
||||||
buffer = append(buffer, byte(c))
|
buffer = append(buffer, byte(c))
|
||||||
@@ -260,7 +260,7 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
} else if c == ESC.Int() && pc != c {
|
} else if c == Esc.Int() && pc != c {
|
||||||
retries = r.escDelay / escPollInterval
|
retries = r.escDelay / escPollInterval
|
||||||
} else {
|
} else {
|
||||||
retries = 0
|
retries = 0
|
||||||
@@ -300,7 +300,7 @@ func (r *LightRenderer) GetChar() Event {
|
|||||||
case CtrlQ.Byte():
|
case CtrlQ.Byte():
|
||||||
return Event{CtrlQ, 0, nil}
|
return Event{CtrlQ, 0, nil}
|
||||||
case 127:
|
case 127:
|
||||||
return Event{BSpace, 0, nil}
|
return Event{Backspace, 0, nil}
|
||||||
case 0:
|
case 0:
|
||||||
return Event{CtrlSpace, 0, nil}
|
return Event{CtrlSpace, 0, nil}
|
||||||
case 28:
|
case 28:
|
||||||
@@ -311,7 +311,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.Byte():
|
case Esc.Byte():
|
||||||
ev := r.escSequence(&sz)
|
ev := r.escSequence(&sz)
|
||||||
// Second chance
|
// Second chance
|
||||||
if ev.Type == Invalid {
|
if ev.Type == Invalid {
|
||||||
@@ -327,7 +327,7 @@ func (r *LightRenderer) GetChar() Event {
|
|||||||
}
|
}
|
||||||
char, rsz := utf8.DecodeRune(r.buffer)
|
char, rsz := utf8.DecodeRune(r.buffer)
|
||||||
if char == utf8.RuneError {
|
if char == utf8.RuneError {
|
||||||
return Event{ESC, 0, nil}
|
return Event{Esc, 0, nil}
|
||||||
}
|
}
|
||||||
sz = rsz
|
sz = rsz
|
||||||
return Event{Rune, char, nil}
|
return Event{Rune, char, nil}
|
||||||
@@ -335,7 +335,7 @@ func (r *LightRenderer) GetChar() Event {
|
|||||||
|
|
||||||
func (r *LightRenderer) escSequence(sz *int) Event {
|
func (r *LightRenderer) escSequence(sz *int) Event {
|
||||||
if len(r.buffer) < 2 {
|
if len(r.buffer) < 2 {
|
||||||
return Event{ESC, 0, nil}
|
return Event{Esc, 0, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
loc := offsetRegexpBegin.FindIndex(r.buffer)
|
loc := offsetRegexpBegin.FindIndex(r.buffer)
|
||||||
@@ -349,15 +349,15 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
return CtrlAltKey(rune(r.buffer[1] + 'a' - 1))
|
return CtrlAltKey(rune(r.buffer[1] + 'a' - 1))
|
||||||
}
|
}
|
||||||
alt := false
|
alt := false
|
||||||
if len(r.buffer) > 2 && r.buffer[1] == ESC.Byte() {
|
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.Byte():
|
case Esc.Byte():
|
||||||
return Event{ESC, 0, nil}
|
return Event{Esc, 0, nil}
|
||||||
case 127:
|
case 127:
|
||||||
return Event{AltBS, 0, nil}
|
return Event{AltBackspace, 0, nil}
|
||||||
case '[', 'O':
|
case '[', 'O':
|
||||||
if len(r.buffer) < 3 {
|
if len(r.buffer) < 3 {
|
||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
@@ -386,7 +386,7 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
}
|
}
|
||||||
return Event{Up, 0, nil}
|
return Event{Up, 0, nil}
|
||||||
case 'Z':
|
case 'Z':
|
||||||
return Event{BTab, 0, nil}
|
return Event{ShiftTab, 0, nil}
|
||||||
case 'H':
|
case 'H':
|
||||||
return Event{Home, 0, nil}
|
return Event{Home, 0, nil}
|
||||||
case 'F':
|
case 'F':
|
||||||
@@ -434,7 +434,7 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
return Event{Invalid, 0, nil} // INS
|
return Event{Invalid, 0, nil} // INS
|
||||||
case '3':
|
case '3':
|
||||||
if r.buffer[3] == '~' {
|
if r.buffer[3] == '~' {
|
||||||
return Event{Del, 0, nil}
|
return Event{Delete, 0, nil}
|
||||||
}
|
}
|
||||||
if len(r.buffer) == 6 && r.buffer[5] == '~' {
|
if len(r.buffer) == 6 && r.buffer[5] == '~' {
|
||||||
*sz = 6
|
*sz = 6
|
||||||
@@ -442,16 +442,16 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
case '5':
|
case '5':
|
||||||
return Event{CtrlDelete, 0, nil}
|
return Event{CtrlDelete, 0, nil}
|
||||||
case '2':
|
case '2':
|
||||||
return Event{SDelete, 0, nil}
|
return Event{ShiftDelete, 0, nil}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
case '4':
|
case '4':
|
||||||
return Event{End, 0, nil}
|
return Event{End, 0, nil}
|
||||||
case '5':
|
case '5':
|
||||||
return Event{PgUp, 0, nil}
|
return Event{PageUp, 0, nil}
|
||||||
case '6':
|
case '6':
|
||||||
return Event{PgDn, 0, nil}
|
return Event{PageDown, 0, nil}
|
||||||
case '7':
|
case '7':
|
||||||
return Event{Home, 0, nil}
|
return Event{Home, 0, nil}
|
||||||
case '8':
|
case '8':
|
||||||
@@ -489,16 +489,29 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
}
|
}
|
||||||
*sz = 6
|
*sz = 6
|
||||||
switch r.buffer[4] {
|
switch r.buffer[4] {
|
||||||
case '1', '2', '3', '5':
|
case '1', '2', '3', '4', '5':
|
||||||
|
// Kitty iTerm2 WezTerm
|
||||||
|
// SHIFT-ARROW "\e[1;2D"
|
||||||
|
// ALT-SHIFT-ARROW "\e[1;4D" "\e[1;10D" "\e[1;4D"
|
||||||
|
// CTRL-SHIFT-ARROW "\e[1;6D" N/A
|
||||||
|
// CMD-SHIFT-ARROW "\e[1;10D" N/A N/A ("\e[1;2D")
|
||||||
alt := r.buffer[4] == '3'
|
alt := r.buffer[4] == '3'
|
||||||
altShift := r.buffer[4] == '1' && r.buffer[5] == '0'
|
|
||||||
char := r.buffer[5]
|
char := r.buffer[5]
|
||||||
if altShift {
|
altShift := false
|
||||||
|
if r.buffer[4] == '1' && r.buffer[5] == '0' {
|
||||||
|
altShift = true
|
||||||
if len(r.buffer) < 7 {
|
if len(r.buffer) < 7 {
|
||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
}
|
}
|
||||||
*sz = 7
|
*sz = 7
|
||||||
char = r.buffer[6]
|
char = r.buffer[6]
|
||||||
|
} else if r.buffer[4] == '4' {
|
||||||
|
altShift = true
|
||||||
|
if len(r.buffer) < 6 {
|
||||||
|
return Event{Invalid, 0, nil}
|
||||||
|
}
|
||||||
|
*sz = 6
|
||||||
|
char = r.buffer[5]
|
||||||
}
|
}
|
||||||
switch char {
|
switch char {
|
||||||
case 'A':
|
case 'A':
|
||||||
@@ -506,33 +519,33 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
return Event{AltUp, 0, nil}
|
return Event{AltUp, 0, nil}
|
||||||
}
|
}
|
||||||
if altShift {
|
if altShift {
|
||||||
return Event{AltSUp, 0, nil}
|
return Event{AltShiftUp, 0, nil}
|
||||||
}
|
}
|
||||||
return Event{SUp, 0, nil}
|
return Event{ShiftUp, 0, nil}
|
||||||
case 'B':
|
case 'B':
|
||||||
if alt {
|
if alt {
|
||||||
return Event{AltDown, 0, nil}
|
return Event{AltDown, 0, nil}
|
||||||
}
|
}
|
||||||
if altShift {
|
if altShift {
|
||||||
return Event{AltSDown, 0, nil}
|
return Event{AltShiftDown, 0, nil}
|
||||||
}
|
}
|
||||||
return Event{SDown, 0, nil}
|
return Event{ShiftDown, 0, nil}
|
||||||
case 'C':
|
case 'C':
|
||||||
if alt {
|
if alt {
|
||||||
return Event{AltRight, 0, nil}
|
return Event{AltRight, 0, nil}
|
||||||
}
|
}
|
||||||
if altShift {
|
if altShift {
|
||||||
return Event{AltSRight, 0, nil}
|
return Event{AltShiftRight, 0, nil}
|
||||||
}
|
}
|
||||||
return Event{SRight, 0, nil}
|
return Event{ShiftRight, 0, nil}
|
||||||
case 'D':
|
case 'D':
|
||||||
if alt {
|
if alt {
|
||||||
return Event{AltLeft, 0, nil}
|
return Event{AltLeft, 0, nil}
|
||||||
}
|
}
|
||||||
if altShift {
|
if altShift {
|
||||||
return Event{AltSLeft, 0, nil}
|
return Event{AltShiftLeft, 0, nil}
|
||||||
}
|
}
|
||||||
return Event{SLeft, 0, nil}
|
return Event{ShiftLeft, 0, nil}
|
||||||
}
|
}
|
||||||
} // r.buffer[4]
|
} // r.buffer[4]
|
||||||
} // r.buffer[3]
|
} // r.buffer[3]
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func openTtyIn() *os.File {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Fprintln(os.Stderr, "Failed to open "+consoleDevice)
|
fmt.Fprintln(os.Stderr, "Failed to open "+consoleDevice)
|
||||||
os.Exit(2)
|
util.Exit(2)
|
||||||
}
|
}
|
||||||
return in
|
return in
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -320,16 +320,16 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
switch ev.Rune() {
|
switch ev.Rune() {
|
||||||
case 0:
|
case 0:
|
||||||
if ctrl {
|
if ctrl {
|
||||||
return Event{BSpace, 0, nil}
|
return Event{Backspace, 0, nil}
|
||||||
}
|
}
|
||||||
case rune(tcell.KeyCtrlH):
|
case rune(tcell.KeyCtrlH):
|
||||||
switch {
|
switch {
|
||||||
case ctrl:
|
case ctrl:
|
||||||
return keyfn('h')
|
return keyfn('h')
|
||||||
case alt:
|
case alt:
|
||||||
return Event{AltBS, 0, nil}
|
return Event{AltBackspace, 0, nil}
|
||||||
case none, shift:
|
case none, shift:
|
||||||
return Event{BSpace, 0, nil}
|
return Event{Backspace, 0, nil}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case tcell.KeyCtrlI:
|
case tcell.KeyCtrlI:
|
||||||
@@ -382,17 +382,17 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
// section 3: (Alt)+Backspace2
|
// section 3: (Alt)+Backspace2
|
||||||
case tcell.KeyBackspace2:
|
case tcell.KeyBackspace2:
|
||||||
if alt {
|
if alt {
|
||||||
return Event{AltBS, 0, nil}
|
return Event{AltBackspace, 0, nil}
|
||||||
}
|
}
|
||||||
return Event{BSpace, 0, nil}
|
return Event{Backspace, 0, nil}
|
||||||
|
|
||||||
// section 4: (Alt+Shift)+Key(Up|Down|Left|Right)
|
// section 4: (Alt+Shift)+Key(Up|Down|Left|Right)
|
||||||
case tcell.KeyUp:
|
case tcell.KeyUp:
|
||||||
if altShift {
|
if altShift {
|
||||||
return Event{AltSUp, 0, nil}
|
return Event{AltShiftUp, 0, nil}
|
||||||
}
|
}
|
||||||
if shift {
|
if shift {
|
||||||
return Event{SUp, 0, nil}
|
return Event{ShiftUp, 0, nil}
|
||||||
}
|
}
|
||||||
if alt {
|
if alt {
|
||||||
return Event{AltUp, 0, nil}
|
return Event{AltUp, 0, nil}
|
||||||
@@ -400,10 +400,10 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
return Event{Up, 0, nil}
|
return Event{Up, 0, nil}
|
||||||
case tcell.KeyDown:
|
case tcell.KeyDown:
|
||||||
if altShift {
|
if altShift {
|
||||||
return Event{AltSDown, 0, nil}
|
return Event{AltShiftDown, 0, nil}
|
||||||
}
|
}
|
||||||
if shift {
|
if shift {
|
||||||
return Event{SDown, 0, nil}
|
return Event{ShiftDown, 0, nil}
|
||||||
}
|
}
|
||||||
if alt {
|
if alt {
|
||||||
return Event{AltDown, 0, nil}
|
return Event{AltDown, 0, nil}
|
||||||
@@ -411,10 +411,10 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
return Event{Down, 0, nil}
|
return Event{Down, 0, nil}
|
||||||
case tcell.KeyLeft:
|
case tcell.KeyLeft:
|
||||||
if altShift {
|
if altShift {
|
||||||
return Event{AltSLeft, 0, nil}
|
return Event{AltShiftLeft, 0, nil}
|
||||||
}
|
}
|
||||||
if shift {
|
if shift {
|
||||||
return Event{SLeft, 0, nil}
|
return Event{ShiftLeft, 0, nil}
|
||||||
}
|
}
|
||||||
if alt {
|
if alt {
|
||||||
return Event{AltLeft, 0, nil}
|
return Event{AltLeft, 0, nil}
|
||||||
@@ -422,10 +422,10 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
return Event{Left, 0, nil}
|
return Event{Left, 0, nil}
|
||||||
case tcell.KeyRight:
|
case tcell.KeyRight:
|
||||||
if altShift {
|
if altShift {
|
||||||
return Event{AltSRight, 0, nil}
|
return Event{AltShiftRight, 0, nil}
|
||||||
}
|
}
|
||||||
if shift {
|
if shift {
|
||||||
return Event{SRight, 0, nil}
|
return Event{ShiftRight, 0, nil}
|
||||||
}
|
}
|
||||||
if alt {
|
if alt {
|
||||||
return Event{AltRight, 0, nil}
|
return Event{AltRight, 0, nil}
|
||||||
@@ -442,17 +442,17 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
return Event{CtrlDelete, 0, nil}
|
return Event{CtrlDelete, 0, nil}
|
||||||
}
|
}
|
||||||
if shift {
|
if shift {
|
||||||
return Event{SDelete, 0, nil}
|
return Event{ShiftDelete, 0, nil}
|
||||||
}
|
}
|
||||||
return Event{Del, 0, nil}
|
return Event{Delete, 0, nil}
|
||||||
case tcell.KeyEnd:
|
case tcell.KeyEnd:
|
||||||
return Event{End, 0, nil}
|
return Event{End, 0, nil}
|
||||||
case tcell.KeyPgUp:
|
case tcell.KeyPgUp:
|
||||||
return Event{PgUp, 0, nil}
|
return Event{PageUp, 0, nil}
|
||||||
case tcell.KeyPgDn:
|
case tcell.KeyPgDn:
|
||||||
return Event{PgDn, 0, nil}
|
return Event{PageDown, 0, nil}
|
||||||
case tcell.KeyBacktab:
|
case tcell.KeyBacktab:
|
||||||
return Event{BTab, 0, nil}
|
return Event{ShiftTab, 0, nil}
|
||||||
case tcell.KeyF1:
|
case tcell.KeyF1:
|
||||||
return Event{F1, 0, nil}
|
return Event{F1, 0, nil}
|
||||||
case tcell.KeyF2:
|
case tcell.KeyF2:
|
||||||
@@ -498,7 +498,7 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
|
|
||||||
// section 7: Esc
|
// section 7: Esc
|
||||||
case tcell.KeyEsc:
|
case tcell.KeyEsc:
|
||||||
return Event{ESC, 0, nil}
|
return Event{Esc, 0, nil}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -102,22 +102,22 @@ func TestGetCharEventKey(t *testing.T) {
|
|||||||
// KeyBackspace2 is alias for KeyDEL = 0x7F (ASCII) (allegedly unused by Windows)
|
// KeyBackspace2 is alias for KeyDEL = 0x7F (ASCII) (allegedly unused by Windows)
|
||||||
// KeyDelete = 0x2E (VK_DELETE constant in Windows)
|
// KeyDelete = 0x2E (VK_DELETE constant in Windows)
|
||||||
// KeyBackspace is alias for KeyBS = 0x08 (ASCII) (implicit alias with KeyCtrlH)
|
// KeyBackspace is alias for KeyBS = 0x08 (ASCII) (implicit alias with KeyCtrlH)
|
||||||
{giveKey{tcell.KeyBackspace2, 0, tcell.ModNone}, wantKey{BSpace, 0, nil}}, // fabricated
|
{giveKey{tcell.KeyBackspace2, 0, tcell.ModNone}, wantKey{Backspace, 0, nil}}, // fabricated
|
||||||
{giveKey{tcell.KeyBackspace2, 0, tcell.ModAlt}, wantKey{AltBS, 0, nil}}, // fabricated
|
{giveKey{tcell.KeyBackspace2, 0, tcell.ModAlt}, wantKey{AltBackspace, 0, nil}}, // fabricated
|
||||||
{giveKey{tcell.KeyDEL, 0, tcell.ModNone}, wantKey{BSpace, 0, nil}}, // fabricated, unhandled
|
{giveKey{tcell.KeyDEL, 0, tcell.ModNone}, wantKey{Backspace, 0, nil}}, // fabricated, unhandled
|
||||||
{giveKey{tcell.KeyDelete, 0, tcell.ModNone}, wantKey{Del, 0, nil}},
|
{giveKey{tcell.KeyDelete, 0, tcell.ModNone}, wantKey{Delete, 0, nil}},
|
||||||
{giveKey{tcell.KeyDelete, 0, tcell.ModAlt}, wantKey{Del, 0, nil}},
|
{giveKey{tcell.KeyDelete, 0, tcell.ModAlt}, wantKey{Delete, 0, nil}},
|
||||||
{giveKey{tcell.KeyBackspace, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
{giveKey{tcell.KeyBackspace, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||||
{giveKey{tcell.KeyBS, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
{giveKey{tcell.KeyBS, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||||
{giveKey{tcell.KeyCtrlH, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
{giveKey{tcell.KeyCtrlH, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModNone}, wantKey{BSpace, 0, nil}}, // actual "Backspace" keystroke
|
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModNone}, wantKey{Backspace, 0, nil}}, // actual "Backspace" keystroke
|
||||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModAlt}, wantKey{AltBS, 0, nil}}, // actual "Alt+Backspace" keystroke
|
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModAlt}, wantKey{AltBackspace, 0, nil}}, // actual "Alt+Backspace" keystroke
|
||||||
{giveKey{tcell.KeyDEL, rune(tcell.KeyDEL), tcell.ModCtrl}, wantKey{BSpace, 0, nil}}, // actual "Ctrl+Backspace" keystroke
|
{giveKey{tcell.KeyDEL, rune(tcell.KeyDEL), tcell.ModCtrl}, wantKey{Backspace, 0, nil}}, // actual "Ctrl+Backspace" keystroke
|
||||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModShift}, wantKey{BSpace, 0, nil}}, // actual "Shift+Backspace" keystroke
|
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModShift}, wantKey{Backspace, 0, nil}}, // actual "Shift+Backspace" keystroke
|
||||||
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModAlt}, wantKey{BSpace, 0, nil}}, // actual "Ctrl+Alt+Backspace" keystroke
|
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModAlt}, wantKey{Backspace, 0, nil}}, // actual "Ctrl+Alt+Backspace" keystroke
|
||||||
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModShift}, wantKey{BSpace, 0, nil}}, // actual "Ctrl+Shift+Backspace" keystroke
|
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModShift}, wantKey{Backspace, 0, nil}}, // actual "Ctrl+Shift+Backspace" keystroke
|
||||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModShift | tcell.ModAlt}, wantKey{AltBS, 0, nil}}, // actual "Shift+Alt+Backspace" keystroke
|
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModShift | tcell.ModAlt}, wantKey{AltBackspace, 0, nil}}, // actual "Shift+Alt+Backspace" keystroke
|
||||||
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModAlt | tcell.ModShift}, wantKey{BSpace, 0, nil}}, // actual "Ctrl+Shift+Alt+Backspace" keystroke
|
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModAlt | tcell.ModShift}, wantKey{Backspace, 0, nil}}, // actual "Ctrl+Shift+Alt+Backspace" keystroke
|
||||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl}, wantKey{CtrlH, 0, nil}}, // actual "Ctrl+H" keystroke
|
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl}, wantKey{CtrlH, 0, nil}}, // actual "Ctrl+H" keystroke
|
||||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAlt, 'h', nil}}, // fabricated "Ctrl+Alt+H" keystroke
|
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAlt, 'h', nil}}, // fabricated "Ctrl+Alt+H" keystroke
|
||||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl | tcell.ModShift}, wantKey{CtrlH, 0, nil}}, // actual "Ctrl+Shift+H" keystroke
|
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl | tcell.ModShift}, wantKey{CtrlH, 0, nil}}, // actual "Ctrl+Shift+H" keystroke
|
||||||
@@ -126,8 +126,8 @@ func TestGetCharEventKey(t *testing.T) {
|
|||||||
// section 4: (Alt+Shift)+Key(Up|Down|Left|Right)
|
// section 4: (Alt+Shift)+Key(Up|Down|Left|Right)
|
||||||
{giveKey{tcell.KeyUp, 0, tcell.ModNone}, wantKey{Up, 0, nil}},
|
{giveKey{tcell.KeyUp, 0, tcell.ModNone}, wantKey{Up, 0, nil}},
|
||||||
{giveKey{tcell.KeyDown, 0, tcell.ModAlt}, wantKey{AltDown, 0, nil}},
|
{giveKey{tcell.KeyDown, 0, tcell.ModAlt}, wantKey{AltDown, 0, nil}},
|
||||||
{giveKey{tcell.KeyLeft, 0, tcell.ModShift}, wantKey{SLeft, 0, nil}},
|
{giveKey{tcell.KeyLeft, 0, tcell.ModShift}, wantKey{ShiftLeft, 0, nil}},
|
||||||
{giveKey{tcell.KeyRight, 0, tcell.ModShift | tcell.ModAlt}, wantKey{AltSRight, 0, nil}},
|
{giveKey{tcell.KeyRight, 0, tcell.ModShift | tcell.ModAlt}, wantKey{AltShiftRight, 0, nil}},
|
||||||
{giveKey{tcell.KeyUpLeft, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
{giveKey{tcell.KeyUpLeft, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||||
{giveKey{tcell.KeyUpRight, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
{giveKey{tcell.KeyUpRight, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||||
{giveKey{tcell.KeyDownLeft, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
{giveKey{tcell.KeyDownLeft, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||||
@@ -161,11 +161,11 @@ func TestGetCharEventKey(t *testing.T) {
|
|||||||
|
|
||||||
// section 7: Esc
|
// section 7: Esc
|
||||||
// KeyEsc and KeyEscape are aliases for KeyESC
|
// KeyEsc and KeyEscape are aliases for KeyESC
|
||||||
{giveKey{tcell.KeyEsc, rune(tcell.KeyEsc), tcell.ModNone}, wantKey{ESC, 0, nil}}, // fabricated
|
{giveKey{tcell.KeyEsc, rune(tcell.KeyEsc), tcell.ModNone}, wantKey{Esc, 0, nil}}, // fabricated
|
||||||
{giveKey{tcell.KeyESC, rune(tcell.KeyESC), tcell.ModNone}, wantKey{ESC, 0, nil}}, // unhandled
|
{giveKey{tcell.KeyESC, rune(tcell.KeyESC), tcell.ModNone}, wantKey{Esc, 0, nil}}, // unhandled
|
||||||
{giveKey{tcell.KeyEscape, rune(tcell.KeyEscape), tcell.ModNone}, wantKey{ESC, 0, nil}}, // fabricated, unhandled
|
{giveKey{tcell.KeyEscape, rune(tcell.KeyEscape), tcell.ModNone}, wantKey{Esc, 0, nil}}, // fabricated, unhandled
|
||||||
{giveKey{tcell.KeyESC, rune(tcell.KeyESC), tcell.ModCtrl}, wantKey{ESC, 0, nil}}, // actual Ctrl+[ keystroke
|
{giveKey{tcell.KeyESC, rune(tcell.KeyESC), tcell.ModCtrl}, wantKey{Esc, 0, nil}}, // actual Ctrl+[ keystroke
|
||||||
{giveKey{tcell.KeyCtrlLeftSq, rune(tcell.KeyCtrlLeftSq), tcell.ModCtrl}, wantKey{ESC, 0, nil}}, // fabricated, unhandled
|
{giveKey{tcell.KeyCtrlLeftSq, rune(tcell.KeyCtrlLeftSq), tcell.ModCtrl}, wantKey{Esc, 0, nil}}, // fabricated, unhandled
|
||||||
|
|
||||||
// section 8: Invalid
|
// section 8: Invalid
|
||||||
{giveKey{tcell.KeyRune, 'a', tcell.ModMeta}, wantKey{Rune, 'a', nil}}, // fabricated
|
{giveKey{tcell.KeyRune, 'a', tcell.ModMeta}, wantKey{Rune, 'a', nil}}, // fabricated
|
||||||
@@ -259,7 +259,7 @@ Quick reference
|
|||||||
37 LeftClick
|
37 LeftClick
|
||||||
38 RightClick
|
38 RightClick
|
||||||
39 BTab
|
39 BTab
|
||||||
40 BSpace
|
40 Backspace
|
||||||
41 Del
|
41 Del
|
||||||
42 PgUp
|
42 PgUp
|
||||||
43 PgDn
|
43 PgDn
|
||||||
@@ -272,7 +272,7 @@ Quick reference
|
|||||||
50 Insert
|
50 Insert
|
||||||
51 SUp
|
51 SUp
|
||||||
52 SDown
|
52 SDown
|
||||||
53 SLeft
|
53 ShiftLeft
|
||||||
54 SRight
|
54 SRight
|
||||||
55 F1
|
55 F1
|
||||||
56 F2
|
56 F2
|
||||||
@@ -288,15 +288,15 @@ Quick reference
|
|||||||
66 F12
|
66 F12
|
||||||
67 Change
|
67 Change
|
||||||
68 BackwardEOF
|
68 BackwardEOF
|
||||||
69 AltBS
|
69 AltBackspace
|
||||||
70 AltUp
|
70 AltUp
|
||||||
71 AltDown
|
71 AltDown
|
||||||
72 AltLeft
|
72 AltLeft
|
||||||
73 AltRight
|
73 AltRight
|
||||||
74 AltSUp
|
74 AltSUp
|
||||||
75 AltSDown
|
75 AltSDown
|
||||||
76 AltSLeft
|
76 AltShiftLeft
|
||||||
77 AltSRight
|
77 AltShiftRight
|
||||||
78 Alt
|
78 Alt
|
||||||
79 CtrlAlt
|
79 CtrlAlt
|
||||||
..
|
..
|
||||||
|
|||||||
116
src/tui/tui.go
116
src/tui/tui.go
@@ -6,10 +6,13 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
"github.com/rivo/uniseg"
|
"github.com/rivo/uniseg"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Types of user action
|
// Types of user action
|
||||||
|
//
|
||||||
|
//go:generate stringer -type=EventType
|
||||||
type EventType int
|
type EventType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -41,7 +44,7 @@ const (
|
|||||||
CtrlX
|
CtrlX
|
||||||
CtrlY
|
CtrlY
|
||||||
CtrlZ
|
CtrlZ
|
||||||
ESC
|
Esc
|
||||||
CtrlSpace
|
CtrlSpace
|
||||||
CtrlDelete
|
CtrlDelete
|
||||||
|
|
||||||
@@ -51,27 +54,12 @@ const (
|
|||||||
CtrlCaret
|
CtrlCaret
|
||||||
CtrlSlash
|
CtrlSlash
|
||||||
|
|
||||||
Invalid
|
ShiftTab
|
||||||
Resize
|
Backspace
|
||||||
Mouse
|
|
||||||
DoubleClick
|
|
||||||
LeftClick
|
|
||||||
RightClick
|
|
||||||
SLeftClick
|
|
||||||
SRightClick
|
|
||||||
ScrollUp
|
|
||||||
ScrollDown
|
|
||||||
SScrollUp
|
|
||||||
SScrollDown
|
|
||||||
PreviewScrollUp
|
|
||||||
PreviewScrollDown
|
|
||||||
|
|
||||||
BTab
|
Delete
|
||||||
BSpace
|
PageUp
|
||||||
|
PageDown
|
||||||
Del
|
|
||||||
PgUp
|
|
||||||
PgDn
|
|
||||||
|
|
||||||
Up
|
Up
|
||||||
Down
|
Down
|
||||||
@@ -81,11 +69,11 @@ const (
|
|||||||
End
|
End
|
||||||
Insert
|
Insert
|
||||||
|
|
||||||
SUp
|
ShiftUp
|
||||||
SDown
|
ShiftDown
|
||||||
SLeft
|
ShiftLeft
|
||||||
SRight
|
ShiftRight
|
||||||
SDelete
|
ShiftDelete
|
||||||
|
|
||||||
F1
|
F1
|
||||||
F2
|
F2
|
||||||
@@ -100,6 +88,38 @@ const (
|
|||||||
F11
|
F11
|
||||||
F12
|
F12
|
||||||
|
|
||||||
|
AltBackspace
|
||||||
|
|
||||||
|
AltUp
|
||||||
|
AltDown
|
||||||
|
AltLeft
|
||||||
|
AltRight
|
||||||
|
|
||||||
|
AltShiftUp
|
||||||
|
AltShiftDown
|
||||||
|
AltShiftLeft
|
||||||
|
AltShiftRight
|
||||||
|
|
||||||
|
Alt
|
||||||
|
CtrlAlt
|
||||||
|
|
||||||
|
Invalid
|
||||||
|
|
||||||
|
Mouse
|
||||||
|
DoubleClick
|
||||||
|
LeftClick
|
||||||
|
RightClick
|
||||||
|
SLeftClick
|
||||||
|
SRightClick
|
||||||
|
ScrollUp
|
||||||
|
ScrollDown
|
||||||
|
SScrollUp
|
||||||
|
SScrollDown
|
||||||
|
PreviewScrollUp
|
||||||
|
PreviewScrollDown
|
||||||
|
|
||||||
|
// Events
|
||||||
|
Resize
|
||||||
Change
|
Change
|
||||||
BackwardEOF
|
BackwardEOF
|
||||||
Start
|
Start
|
||||||
@@ -108,21 +128,8 @@ const (
|
|||||||
One
|
One
|
||||||
Zero
|
Zero
|
||||||
Result
|
Result
|
||||||
|
Jump
|
||||||
AltBS
|
JumpCancel
|
||||||
|
|
||||||
AltUp
|
|
||||||
AltDown
|
|
||||||
AltLeft
|
|
||||||
AltRight
|
|
||||||
|
|
||||||
AltSUp
|
|
||||||
AltSDown
|
|
||||||
AltSLeft
|
|
||||||
AltSRight
|
|
||||||
|
|
||||||
Alt
|
|
||||||
CtrlAlt
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t EventType) AsEvent() Event {
|
func (t EventType) AsEvent() Event {
|
||||||
@@ -142,6 +149,31 @@ func (e Event) Comparable() Event {
|
|||||||
return Event{e.Type, e.Char, nil}
|
return Event{e.Type, e.Char, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e Event) KeyName() string {
|
||||||
|
if e.Type >= Invalid {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
switch e.Type {
|
||||||
|
case Rune:
|
||||||
|
return string(e.Char)
|
||||||
|
case Alt:
|
||||||
|
return "alt-" + string(e.Char)
|
||||||
|
case CtrlAlt:
|
||||||
|
return "ctrl-alt-" + string(e.Char)
|
||||||
|
case CtrlBackSlash:
|
||||||
|
return "ctrl-\\"
|
||||||
|
case CtrlRightBracket:
|
||||||
|
return "ctrl-]"
|
||||||
|
case CtrlCaret:
|
||||||
|
return "ctrl-^"
|
||||||
|
case CtrlSlash:
|
||||||
|
return "ctrl-/"
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.ToKebabCase(e.Type.String())
|
||||||
|
}
|
||||||
|
|
||||||
func Key(r rune) Event {
|
func Key(r rune) Event {
|
||||||
return Event{Rune, r, nil}
|
return Event{Rune, r, nil}
|
||||||
}
|
}
|
||||||
@@ -646,7 +678,7 @@ func NoColorTheme() *ColorTheme {
|
|||||||
|
|
||||||
func errorExit(message string) {
|
func errorExit(message string) {
|
||||||
fmt.Fprintln(os.Stderr, message)
|
fmt.Fprintln(os.Stderr, message)
|
||||||
os.Exit(2)
|
util.Exit(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
38
src/util/atexit.go
Normal file
38
src/util/atexit.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var atExitFuncs []func()
|
||||||
|
|
||||||
|
// AtExit registers the function fn to be called on program termination.
|
||||||
|
// The functions will be called in reverse order they were registered.
|
||||||
|
func AtExit(fn func()) {
|
||||||
|
if fn == nil {
|
||||||
|
panic("AtExit called with nil func")
|
||||||
|
}
|
||||||
|
once := &sync.Once{}
|
||||||
|
atExitFuncs = append(atExitFuncs, func() {
|
||||||
|
once.Do(fn)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunAtExitFuncs runs any functions registered with AtExit().
|
||||||
|
func RunAtExitFuncs() {
|
||||||
|
fns := atExitFuncs
|
||||||
|
for i := len(fns) - 1; i >= 0; i-- {
|
||||||
|
fns[i]()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit executes any functions registered with AtExit() then exits the program
|
||||||
|
// with os.Exit(code).
|
||||||
|
//
|
||||||
|
// NOTE: It must be used instead of os.Exit() since calling os.Exit() terminates
|
||||||
|
// the program before any of the AtExit functions can run.
|
||||||
|
func Exit(code int) {
|
||||||
|
defer os.Exit(code)
|
||||||
|
RunAtExitFuncs()
|
||||||
|
}
|
||||||
24
src/util/atexit_test.go
Normal file
24
src/util/atexit_test.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAtExit(t *testing.T) {
|
||||||
|
want := []int{3, 2, 1, 0}
|
||||||
|
var called []int
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
n := i
|
||||||
|
AtExit(func() { called = append(called, n) })
|
||||||
|
}
|
||||||
|
RunAtExitFuncs()
|
||||||
|
if !reflect.DeepEqual(called, want) {
|
||||||
|
t.Errorf("AtExit: want call order: %v got: %v", want, called)
|
||||||
|
}
|
||||||
|
|
||||||
|
RunAtExitFuncs()
|
||||||
|
if !reflect.DeepEqual(called, want) {
|
||||||
|
t.Error("AtExit: should only call exit funcs once")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -163,7 +163,7 @@ func (chars *Chars) ToString() string {
|
|||||||
if runes := chars.optionalRunes(); runes != nil {
|
if runes := chars.optionalRunes(); runes != nil {
|
||||||
return string(runes)
|
return string(runes)
|
||||||
}
|
}
|
||||||
return string(chars.slice)
|
return unsafe.String(unsafe.SliceData(chars.slice), len(chars.slice))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chars *Chars) ToRunes() []rune {
|
func (chars *Chars) ToRunes() []rune {
|
||||||
@@ -178,12 +178,12 @@ func (chars *Chars) ToRunes() []rune {
|
|||||||
return runes
|
return runes
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chars *Chars) CopyRunes(dest []rune) {
|
func (chars *Chars) CopyRunes(dest []rune, from int) {
|
||||||
if runes := chars.optionalRunes(); runes != nil {
|
if runes := chars.optionalRunes(); runes != nil {
|
||||||
copy(dest, runes)
|
copy(dest, runes[from:])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for idx, b := range chars.slice[:len(dest)] {
|
for idx, b := range chars.slice[from:][:len(dest)] {
|
||||||
dest[idx] = rune(b)
|
dest[idx] = rune(b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -176,3 +176,15 @@ func RepeatToFill(str string, length int, limit int) string {
|
|||||||
}
|
}
|
||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToKebabCase converts the given CamelCase string to kebab-case
|
||||||
|
func ToKebabCase(s string) string {
|
||||||
|
name := ""
|
||||||
|
for i, r := range s {
|
||||||
|
if i > 0 && r >= 'A' && r <= 'Z' {
|
||||||
|
name += "-"
|
||||||
|
}
|
||||||
|
name += string(r)
|
||||||
|
}
|
||||||
|
return strings.ToLower(name)
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,31 +3,71 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExecCommand executes the given command with $SHELL
|
type Executor struct {
|
||||||
func ExecCommand(command string, setpgid bool) *exec.Cmd {
|
shell string
|
||||||
shell := os.Getenv("SHELL")
|
args []string
|
||||||
if len(shell) == 0 {
|
escaper *strings.Replacer
|
||||||
shell = "sh"
|
|
||||||
}
|
|
||||||
return ExecCommandWith(shell, command, setpgid)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecCommandWith executes the given command with the specified shell
|
func NewExecutor(withShell string) *Executor {
|
||||||
func ExecCommandWith(shell string, command string, setpgid bool) *exec.Cmd {
|
shell := os.Getenv("SHELL")
|
||||||
cmd := exec.Command(shell, "-c", command)
|
args := strings.Fields(withShell)
|
||||||
|
if len(args) > 0 {
|
||||||
|
shell = args[0]
|
||||||
|
args = args[1:]
|
||||||
|
} else {
|
||||||
|
if len(shell) == 0 {
|
||||||
|
shell = "sh"
|
||||||
|
}
|
||||||
|
args = []string{"-c"}
|
||||||
|
}
|
||||||
|
|
||||||
|
var escaper *strings.Replacer
|
||||||
|
tokens := strings.Split(shell, "/")
|
||||||
|
if tokens[len(tokens)-1] == "fish" {
|
||||||
|
// https://fishshell.com/docs/current/language.html#quotes
|
||||||
|
// > The only meaningful escape sequences in single quotes are \', which
|
||||||
|
// > escapes a single quote and \\, which escapes the backslash symbol.
|
||||||
|
escaper = strings.NewReplacer("\\", "\\\\", "'", "\\'")
|
||||||
|
} else {
|
||||||
|
escaper = strings.NewReplacer("'", "'\\''")
|
||||||
|
}
|
||||||
|
return &Executor{shell, args, escaper}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecCommand executes the given command with $SHELL
|
||||||
|
func (x *Executor) ExecCommand(command string, setpgid bool) *exec.Cmd {
|
||||||
|
cmd := exec.Command(x.shell, append(x.args, command)...)
|
||||||
if setpgid {
|
if setpgid {
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||||
}
|
}
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Executor) QuoteEntry(entry string) string {
|
||||||
|
return "'" + x.escaper.Replace(entry) + "'"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Executor) Become(stdin *os.File, environ []string, command string) {
|
||||||
|
shellPath, err := exec.LookPath(x.shell)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "fzf (become): %s\n", err.Error())
|
||||||
|
Exit(127)
|
||||||
|
}
|
||||||
|
args := append([]string{shellPath}, append(x.args, command)...)
|
||||||
|
SetStdin(stdin)
|
||||||
|
syscall.Exec(shellPath, args, environ)
|
||||||
|
}
|
||||||
|
|
||||||
// KillCommand kills the process for the given command
|
// KillCommand kills the process for the given command
|
||||||
func KillCommand(cmd *exec.Cmd) error {
|
func KillCommand(cmd *exec.Cmd) error {
|
||||||
return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
|
return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
|
||||||
|
|||||||
@@ -6,62 +6,161 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
var shellPath atomic.Value
|
type shellType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
shellTypeUnknown shellType = iota
|
||||||
|
shellTypeCmd
|
||||||
|
shellTypePowerShell
|
||||||
|
)
|
||||||
|
|
||||||
|
type Executor struct {
|
||||||
|
shell string
|
||||||
|
shellType shellType
|
||||||
|
args []string
|
||||||
|
shellPath atomic.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExecutor(withShell string) *Executor {
|
||||||
|
shell := os.Getenv("SHELL")
|
||||||
|
args := strings.Fields(withShell)
|
||||||
|
if len(args) > 0 {
|
||||||
|
shell = args[0]
|
||||||
|
} else if len(shell) == 0 {
|
||||||
|
shell = "cmd"
|
||||||
|
}
|
||||||
|
|
||||||
|
shellType := shellTypeUnknown
|
||||||
|
basename := filepath.Base(shell)
|
||||||
|
if len(args) > 0 {
|
||||||
|
args = args[1:]
|
||||||
|
} else if strings.HasPrefix(basename, "cmd") {
|
||||||
|
shellType = shellTypeCmd
|
||||||
|
args = []string{"/v:on/s/c"}
|
||||||
|
} else if strings.HasPrefix(basename, "pwsh") || strings.HasPrefix(basename, "powershell") {
|
||||||
|
shellType = shellTypePowerShell
|
||||||
|
args = []string{"-NoProfile", "-Command"}
|
||||||
|
} else {
|
||||||
|
args = []string{"-c"}
|
||||||
|
}
|
||||||
|
return &Executor{shell: shell, shellType: shellType, args: args}
|
||||||
|
}
|
||||||
|
|
||||||
// ExecCommand executes the given command with $SHELL
|
// ExecCommand executes the given command with $SHELL
|
||||||
func ExecCommand(command string, setpgid bool) *exec.Cmd {
|
// FIXME: setpgid is unused. We set it in the Unix implementation so that we
|
||||||
var shell string
|
// can kill preview process with its child processes at once.
|
||||||
if cached := shellPath.Load(); cached != nil {
|
// NOTE: For "powershell", we should ideally set output encoding to UTF8,
|
||||||
|
// but it is left as is now because no adverse effect has been observed.
|
||||||
|
func (x *Executor) ExecCommand(command string, setpgid bool) *exec.Cmd {
|
||||||
|
shell := x.shell
|
||||||
|
if cached := x.shellPath.Load(); cached != nil {
|
||||||
shell = cached.(string)
|
shell = cached.(string)
|
||||||
} else {
|
} else {
|
||||||
shell = os.Getenv("SHELL")
|
if strings.Contains(shell, "/") {
|
||||||
if len(shell) == 0 {
|
|
||||||
shell = "cmd"
|
|
||||||
} else if strings.Contains(shell, "/") {
|
|
||||||
out, err := exec.Command("cygpath", "-w", shell).Output()
|
out, err := exec.Command("cygpath", "-w", shell).Output()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
shell = strings.Trim(string(out), "\n")
|
shell = strings.Trim(string(out), "\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shellPath.Store(shell)
|
x.shellPath.Store(shell)
|
||||||
}
|
}
|
||||||
return ExecCommandWith(shell, command, setpgid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExecCommandWith executes the given command with the specified shell
|
|
||||||
// FIXME: setpgid is unused. We set it in the Unix implementation so that we
|
|
||||||
// can kill preview process with its child processes at once.
|
|
||||||
// NOTE: For "powershell", we should ideally set output encoding to UTF8,
|
|
||||||
// but it is left as is now because no adverse effect has been observed.
|
|
||||||
func ExecCommandWith(shell string, command string, setpgid bool) *exec.Cmd {
|
|
||||||
var cmd *exec.Cmd
|
var cmd *exec.Cmd
|
||||||
if strings.Contains(shell, "cmd") {
|
if x.shellType == shellTypeCmd {
|
||||||
cmd = exec.Command(shell)
|
cmd = exec.Command(shell)
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
HideWindow: false,
|
HideWindow: false,
|
||||||
CmdLine: fmt.Sprintf(` /v:on/s/c "%s"`, command),
|
CmdLine: fmt.Sprintf(`%s "%s"`, strings.Join(x.args, " "), command),
|
||||||
CreationFlags: 0,
|
CreationFlags: 0,
|
||||||
}
|
}
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(shell, "pwsh") || strings.Contains(shell, "powershell") {
|
|
||||||
cmd = exec.Command(shell, "-NoProfile", "-Command", command)
|
|
||||||
} else {
|
} else {
|
||||||
cmd = exec.Command(shell, "-c", command)
|
cmd = exec.Command(shell, append(x.args, command)...)
|
||||||
}
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
HideWindow: false,
|
||||||
HideWindow: false,
|
CreationFlags: 0,
|
||||||
CreationFlags: 0,
|
}
|
||||||
}
|
}
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Executor) Become(stdin *os.File, environ []string, command string) {
|
||||||
|
cmd := x.ExecCommand(command, false)
|
||||||
|
cmd.Stdin = stdin
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Env = environ
|
||||||
|
err := cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "fzf (become): %s\n", err.Error())
|
||||||
|
Exit(127)
|
||||||
|
}
|
||||||
|
err = cmd.Wait()
|
||||||
|
if err != nil {
|
||||||
|
if exitError, ok := err.(*exec.ExitError); ok {
|
||||||
|
Exit(exitError.ExitCode())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func escapeArg(s string) string {
|
||||||
|
b := make([]byte, 0, len(s)+2)
|
||||||
|
b = append(b, '"')
|
||||||
|
slashes := 0
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
c := s[i]
|
||||||
|
switch c {
|
||||||
|
default:
|
||||||
|
slashes = 0
|
||||||
|
case '\\':
|
||||||
|
slashes++
|
||||||
|
case '&', '|', '<', '>', '(', ')', '@', '^', '%', '!':
|
||||||
|
b = append(b, '^')
|
||||||
|
case '"':
|
||||||
|
for ; slashes > 0; slashes-- {
|
||||||
|
b = append(b, '\\')
|
||||||
|
}
|
||||||
|
b = append(b, '\\')
|
||||||
|
}
|
||||||
|
b = append(b, c)
|
||||||
|
}
|
||||||
|
for ; slashes > 0; slashes-- {
|
||||||
|
b = append(b, '\\')
|
||||||
|
}
|
||||||
|
b = append(b, '"')
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Executor) QuoteEntry(entry string) string {
|
||||||
|
switch x.shellType {
|
||||||
|
case shellTypeCmd:
|
||||||
|
/* Manually tested with the following commands:
|
||||||
|
fzf --preview "echo {}"
|
||||||
|
fzf --preview "type {}"
|
||||||
|
echo .git\refs\| fzf --preview "dir {}"
|
||||||
|
echo .git\refs\\| fzf --preview "dir {}"
|
||||||
|
echo .git\refs\\\| fzf --preview "dir {}"
|
||||||
|
reg query HKCU | fzf --reverse --bind "enter:reload(reg query {})"
|
||||||
|
fzf --disabled --preview "echo {q} {n} {}" --query "&|<>()@^%!"
|
||||||
|
fd -H --no-ignore -td -d 4 | fzf --preview "dir {}"
|
||||||
|
fd -H --no-ignore -td -d 4 | fzf --preview "eza {}" --preview-window up
|
||||||
|
fd -H --no-ignore -td -d 4 | fzf --preview "eza --color=always --tree --level=3 --icons=always {}"
|
||||||
|
fd -H --no-ignore -td -d 4 | fzf --preview ".\eza.exe --color=always --tree --level=3 --icons=always {}" --with-shell "powershell -NoProfile -Command"
|
||||||
|
*/
|
||||||
|
return escapeArg(entry)
|
||||||
|
case shellTypePowerShell:
|
||||||
|
escaped := strings.Replace(entry, `"`, `\"`, -1)
|
||||||
|
return "'" + strings.Replace(escaped, "'", "''", -1) + "'"
|
||||||
|
default:
|
||||||
|
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// KillCommand kills the process for the given command
|
// KillCommand kills the process for the given command
|
||||||
func KillCommand(cmd *exec.Cmd) error {
|
func KillCommand(cmd *exec.Cmd) error {
|
||||||
return cmd.Process.Kill()
|
return cmd.Process.Kill()
|
||||||
|
|||||||
168
test/test_go.rb
168
test/test_go.rb
@@ -425,6 +425,25 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_multi_action
|
||||||
|
tmux.send_keys "seq 10 | #{FZF} --bind 'a:change-multi,b:change-multi(3),c:change-multi(xxx),d:change-multi(0)'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 10, lines.item_count }
|
||||||
|
tmux.until { |lines| assert lines[-2]&.start_with?(' 10/10 ') }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| assert lines[-2]&.start_with?(' 10/10 (0)') }
|
||||||
|
tmux.send_keys 'b'
|
||||||
|
tmux.until { |lines| assert lines[-2]&.start_with?(' 10/10 (0/3)') }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| assert lines[-2]&.start_with?(' 10/10 (1/3)') }
|
||||||
|
tmux.send_keys 'c'
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| assert lines[-2]&.start_with?(' 10/10 (2/3)') }
|
||||||
|
tmux.send_keys 'd'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines[-2]&.start_with?(' 10/10 ') && !lines[-2]&.include?('(')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_with_nth
|
def test_with_nth
|
||||||
[true, false].each do |multi|
|
[true, false].each do |multi|
|
||||||
tmux.send_keys "(echo ' 1st 2nd 3rd/';
|
tmux.send_keys "(echo ' 1st 2nd 3rd/';
|
||||||
@@ -1468,6 +1487,19 @@ class TestGoFZF < TestBase
|
|||||||
assert_equal '3', readonce.chomp
|
assert_equal '3', readonce.chomp
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_jump_events
|
||||||
|
tmux.send_keys "seq 1000 | #{fzf("--multi --jump-labels 12345 --bind 'ctrl-j:jump,jump:preview(echo jumped to {}),jump-cancel:preview(echo jump cancelled at {})'")}", :Enter
|
||||||
|
tmux.until { |lines| assert_equal ' 1000/1000 (0)', lines[-2] }
|
||||||
|
tmux.send_keys 'C-j'
|
||||||
|
tmux.until { |lines| assert_includes lines[-7], '5 5' }
|
||||||
|
tmux.send_keys '3'
|
||||||
|
tmux.until { |lines| assert(lines.any? { _1.include?('jumped to 3') }) }
|
||||||
|
tmux.send_keys 'C-j'
|
||||||
|
tmux.until { |lines| assert_includes lines[-7], '5 5' }
|
||||||
|
tmux.send_keys 'C-c'
|
||||||
|
tmux.until { |lines| assert(lines.any? { _1.include?('jump cancelled at 3') }) }
|
||||||
|
end
|
||||||
|
|
||||||
def test_pointer
|
def test_pointer
|
||||||
tmux.send_keys "seq 10 | #{fzf("--pointer '>>'")}", :Enter
|
tmux.send_keys "seq 10 | #{fzf("--pointer '>>'")}", :Enter
|
||||||
# Assert that specified pointer is displayed
|
# Assert that specified pointer is displayed
|
||||||
@@ -1719,7 +1751,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_info_hidden
|
def test_info_hidden
|
||||||
tmux.send_keys 'seq 10 | fzf --info=hidden', :Enter
|
tmux.send_keys 'seq 10 | fzf --info=hidden --no-separator', :Enter
|
||||||
tmux.until { |lines| assert_equal '> 1', lines[-2] }
|
tmux.until { |lines| assert_equal '> 1', lines[-2] }
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1942,6 +1974,11 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| assert_equal 10, lines.item_count }
|
tmux.until { |lines| assert_equal 10, lines.item_count }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_reload_should_terminate_standard_input_stream
|
||||||
|
tmux.send_keys %(ruby -e "STDOUT.sync = true; loop { puts 1; sleep 0.1 }" | fzf --bind 'start:reload(seq 100)'), :Enter
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||||
|
end
|
||||||
|
|
||||||
def test_clear_list_when_header_lines_changed_due_to_reload
|
def test_clear_list_when_header_lines_changed_due_to_reload
|
||||||
tmux.send_keys %(seq 10 | #{FZF} --header 0 --header-lines 3 --bind 'space:reload(seq 1)'), :Enter
|
tmux.send_keys %(seq 10 | #{FZF} --header 0 --header-lines 3 --bind 'space:reload(seq 1)'), :Enter
|
||||||
tmux.until { |lines| assert_includes lines, ' 9' }
|
tmux.until { |lines| assert_includes lines, ' 9' }
|
||||||
@@ -2463,6 +2500,84 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| assert_equal 10, lines.match_count }
|
tmux.until { |lines| assert_equal 10, lines.match_count }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_reload_disabled_case1
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload(sleep 2; seq 1000)'", :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 100, lines.item_count
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||||
|
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reload_disabled_case2
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload-sync(sleep 2; seq 1000)'", :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 100, lines.item_count
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reload_disabled_case3
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload(sleep 2; seq 1000)+backward-delete-char'", :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 100, lines.item_count
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||||
|
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reload_disabled_case4
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload-sync(sleep 2; seq 1000)+backward-delete-char'", :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 100, lines.item_count
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reload_disabled_case5
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload(echo xx; sleep 2; seq 1000)'", :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 100, lines.item_count
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 1, lines.item_count
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| assert_equal 1001, lines.match_count }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reload_disabled_case6
|
||||||
|
tmux.send_keys "seq 1000 | #{FZF} --disabled --bind 'change:reload:sleep 0.5; seq {q}'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||||
|
tmux.send_keys '9'
|
||||||
|
tmux.until { |lines| assert_equal 9, lines.match_count }
|
||||||
|
tmux.send_keys '9'
|
||||||
|
tmux.until { |lines| assert_equal 99, lines.match_count }
|
||||||
|
|
||||||
|
# TODO: How do we verify if an intermediate empty list is not shown?
|
||||||
|
end
|
||||||
|
|
||||||
def test_scroll_off
|
def test_scroll_off
|
||||||
tmux.send_keys "seq 1000 | #{FZF} --scroll-off=3 --bind l:last", :Enter
|
tmux.send_keys "seq 1000 | #{FZF} --scroll-off=3 --bind l:last", :Enter
|
||||||
tmux.until { |lines| assert_equal 1000, lines.item_count }
|
tmux.until { |lines| assert_equal 1000, lines.item_count }
|
||||||
@@ -2799,6 +2914,27 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_labels_variables
|
||||||
|
tmux.send_keys ': | fzf --border --border-label foobar --preview "echo \$FZF_BORDER_LABEL // \$FZF_PREVIEW_LABEL" --preview-label barfoo --bind "space:change-border-label(barbaz)+change-preview-label(bazbar)+refresh-preview,enter:transform-border-label(echo 123)+transform-preview-label(echo 456)+refresh-preview"', :Enter
|
||||||
|
tmux.until do
|
||||||
|
assert_includes(_1[0], '─foobar─')
|
||||||
|
assert_includes(_1[1], '─barfoo─')
|
||||||
|
assert_includes(_1[2], ' foobar // barfoo ')
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do
|
||||||
|
assert_includes(_1[0], '─barbaz─')
|
||||||
|
assert_includes(_1[1], '─bazbar─')
|
||||||
|
assert_includes(_1[2], ' barbaz // bazbar ')
|
||||||
|
end
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until do
|
||||||
|
assert_includes(_1[0], '─123─')
|
||||||
|
assert_includes(_1[1], '─456─')
|
||||||
|
assert_includes(_1[2], ' 123 // 456 ')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_info_separator_unicode
|
def test_info_separator_unicode
|
||||||
tmux.send_keys 'seq 100 | fzf -q55', :Enter
|
tmux.send_keys 'seq 100 | fzf -q55', :Enter
|
||||||
tmux.until { assert_includes(_1[-2], ' 1/100 ─') }
|
tmux.until { assert_includes(_1[-2], ' 1/100 ─') }
|
||||||
@@ -3002,7 +3138,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
tmux.send_keys :t
|
tmux.send_keys :t
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
assert_includes lines[-2], '+T'
|
assert_includes lines[-2], '+t'
|
||||||
end
|
end
|
||||||
tmux.send_keys :BSpace
|
tmux.send_keys :BSpace
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
@@ -3014,7 +3150,7 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys '4'
|
tmux.send_keys '4'
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
assert_equal 28, lines.match_count
|
assert_equal 28, lines.match_count
|
||||||
refute_includes lines[-2], '+T'
|
refute_includes lines[-2], '+t'
|
||||||
end
|
end
|
||||||
tmux.send_keys :BSpace
|
tmux.send_keys :BSpace
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
@@ -3023,11 +3159,11 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
tmux.send_keys :t
|
tmux.send_keys :t
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
assert_includes lines[-2], '+T'
|
assert_includes lines[-2], '+t'
|
||||||
end
|
end
|
||||||
tmux.send_keys :Up
|
tmux.send_keys :Up
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
refute_includes lines[-2], '+T'
|
refute_includes lines[-2], '+t'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -3114,6 +3250,17 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys :Up
|
tmux.send_keys :Up
|
||||||
tmux.until { |lines| assert_includes lines, '> 2' }
|
tmux.until { |lines| assert_includes lines, '> 2' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_fzf_pos
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --preview 'echo $FZF_POS / $FZF_MATCH_COUNT'", :Enter
|
||||||
|
tmux.until { |lines| assert(lines.any? { |line| line.include?('1 / 100') }) }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert(lines.any? { |line| line.include?('2 / 100') }) }
|
||||||
|
tmux.send_keys '99'
|
||||||
|
tmux.until { |lines| assert(lines.any? { |line| line.include?('1 / 1') }) }
|
||||||
|
tmux.send_keys '99'
|
||||||
|
tmux.until { |lines| assert(lines.any? { |line| line.include?('0 / 0') }) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module TestShell
|
module TestShell
|
||||||
@@ -3307,7 +3454,10 @@ module CompletionTest
|
|||||||
tmux.send_keys 'cat /tmp/fzf\ test/**', :Tab
|
tmux.send_keys 'cat /tmp/fzf\ test/**', :Tab
|
||||||
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||||
tmux.send_keys 'foobar$'
|
tmux.send_keys 'foobar$'
|
||||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
tmux.until do |lines|
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
assert lines.any_include?('> /tmp/fzf test/foobar')
|
||||||
|
end
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until(true) { |lines| assert_equal 'cat /tmp/fzf\ test/foobar', lines[-1] }
|
tmux.until(true) { |lines| assert_equal 'cat /tmp/fzf\ test/foobar', lines[-1] }
|
||||||
|
|
||||||
@@ -3342,7 +3492,11 @@ module CompletionTest
|
|||||||
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||||
tmux.send_keys :Tab, :Tab # Tab does not work here
|
tmux.send_keys :Tab, :Tab # Tab does not work here
|
||||||
tmux.send_keys 55
|
tmux.send_keys 55
|
||||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
tmux.until do |lines|
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
assert_includes lines, '> 55'
|
||||||
|
assert_includes lines, '> /tmp/fzf-test/d55'
|
||||||
|
end
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until(true) { |lines| assert_equal 'cd /tmp/fzf-test/d55/', lines[-1] }
|
tmux.until(true) { |lines| assert_equal 'cd /tmp/fzf-test/d55/', lines[-1] }
|
||||||
tmux.send_keys :xx
|
tmux.send_keys :xx
|
||||||
|
|||||||
Reference in New Issue
Block a user