mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-14 22:33:47 -05:00
Compare commits
97 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
d579e335b5 | ||
|
|
7e344ceb85 | ||
|
|
0145b82ea0 | ||
|
|
b4efe7aab7 | ||
|
|
9ffe951f6d | ||
|
|
a5ea4f57bd | ||
|
|
88f4c16755 | ||
|
|
c7ee071efa | ||
|
|
0740ef7ceb | ||
|
|
b29bd809ac | ||
|
|
8977c9257a | ||
|
|
091b7eacba | ||
|
|
e74b1251c0 | ||
|
|
d282a1649d | ||
|
|
6ce8d49d1b | ||
|
|
c5b197078a | ||
|
|
0494f20d62 | ||
|
|
73aff476dd | ||
|
|
98ee5e651a | ||
|
|
01871ea383 | ||
|
|
1dbdb9438f | ||
|
|
c70f0eadb8 | ||
|
|
26244ad8c2 | ||
|
|
fa0aa5510d | ||
|
|
eec557b6aa | ||
|
|
0cc27c3cc1 | ||
|
|
507089d7b2 | ||
|
|
a6b3517b75 | ||
|
|
2d6beb7813 | ||
|
|
61bc129e1d | ||
|
|
52210a57f0 | ||
|
|
8061a2f108 | ||
|
|
7444eff6d4 | ||
|
|
f35a9da99a | ||
|
|
c3098e9ab2 | ||
|
|
686f9288fc | ||
|
|
1833670fb9 | ||
|
|
3dd42f5aa2 | ||
|
|
99a7beba57 | ||
|
|
edee2b753c | ||
|
|
545d5770be | ||
|
|
ca747a2b54 | ||
|
|
17da165cfe | ||
|
|
5e6788c679 | ||
|
|
425deadca9 | ||
|
|
2c8e9dd3a5 | ||
|
|
7a72f1a253 | ||
|
|
208e556332 | ||
|
|
c65d11bfb5 | ||
|
|
3b5b52d89a | ||
|
|
a4f6c8f990 | ||
|
|
670c329852 | ||
|
|
f3551c8422 | ||
|
|
90b8187882 | ||
|
|
1a43259989 |
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/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
2
.github/workflows/depsreview.yaml
vendored
2
.github/workflows/depsreview.yaml
vendored
@@ -9,6 +9,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v4
|
||||
|
||||
4
.github/workflows/linux.yml
vendored
4
.github/workflows/linux.yml
vendored
@@ -18,14 +18,14 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.19
|
||||
go-version: "1.20"
|
||||
|
||||
- name: Setup Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
|
||||
4
.github/workflows/macos.yml
vendored
4
.github/workflows/macos.yml
vendored
@@ -15,14 +15,14 @@ jobs:
|
||||
build:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version: "1.20"
|
||||
|
||||
- name: Setup Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
|
||||
2
.github/workflows/sponsors.yml
vendored
2
.github/workflows/sponsors.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout 🛎️
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Generate Sponsors 💖
|
||||
uses: JamesIves/github-sponsors-readme-action@v1
|
||||
|
||||
4
.github/workflows/typos.yml
vendored
4
.github/workflows/typos.yml
vendored
@@ -6,5 +6,5 @@ jobs:
|
||||
name: Spell Check with Typos
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: crate-ci/typos@v1.17.2
|
||||
- uses: actions/checkout@v4
|
||||
- uses: crate-ci/typos@v1.19.0
|
||||
|
||||
@@ -20,10 +20,6 @@ builds:
|
||||
cat > /tmp/fzf-gon-amd64.hcl << EOF
|
||||
source = ["./dist/fzf-macos_darwin_amd64_v1/fzf"]
|
||||
bundle_id = "kr.junegunn.fzf"
|
||||
apple_id {
|
||||
username = "junegunn.c@gmail.com"
|
||||
password = "@env:AC_PASSWORD"
|
||||
}
|
||||
sign {
|
||||
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
|
||||
}
|
||||
@@ -48,10 +44,6 @@ builds:
|
||||
cat > /tmp/fzf-gon-arm64.hcl << EOF
|
||||
source = ["./dist/fzf-macos-arm_darwin_arm64/fzf"]
|
||||
bundle_id = "kr.junegunn.fzf"
|
||||
apple_id {
|
||||
username = "junegunn.c@gmail.com"
|
||||
password = "@env:AC_PASSWORD"
|
||||
}
|
||||
sign {
|
||||
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
|
||||
}
|
||||
|
||||
@@ -552,7 +552,7 @@ pods() {
|
||||
- Press enter key on a pod to `kubectl exec` into it
|
||||
- Press CTRL-O to open the log in your editor
|
||||
- Press CTRL-R to reload the pod list
|
||||
- Press CTRL-/ repeatedly to to rotate through a different sets of preview
|
||||
- Press CTRL-/ repeatedly to rotate through a different sets of preview
|
||||
window options
|
||||
1. `80%,border-bottom`
|
||||
1. `hidden`
|
||||
|
||||
18
BUILD.md
18
BUILD.md
@@ -6,7 +6,7 @@ Build instructions
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Go 1.18 or above
|
||||
- Go 1.20 or above
|
||||
|
||||
### Using Makefile
|
||||
|
||||
@@ -24,13 +24,23 @@ make build
|
||||
make release
|
||||
```
|
||||
|
||||
> :warning: Makefile uses git commands to determine the version and the
|
||||
> revision information for `fzf --version`. So if you're building fzf from an
|
||||
> [!WARNING]
|
||||
> Makefile uses git commands to determine the version and the revision
|
||||
> information for `fzf --version`. So if you're building fzf from an
|
||||
> environment where its git information is not available, you have to manually
|
||||
> set `$FZF_VERSION` and `$FZF_REVISION`.
|
||||
>
|
||||
> e.g. `FZF_VERSION=0.24.0 FZF_REVISION=tarball make`
|
||||
|
||||
> [!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
|
||||
--------------------------
|
||||
|
||||
@@ -42,6 +52,8 @@ Third-party libraries used
|
||||
- Licensed under [MIT](http://mattn.mit-license.org)
|
||||
- [tcell](https://github.com/gdamore/tcell)
|
||||
- Licensed under [Apache License 2.0](https://github.com/gdamore/tcell/blob/master/LICENSE)
|
||||
- [fastwalk](https://github.com/charlievieth/fastwalk)
|
||||
- Licensed under [MIT](https://raw.githubusercontent.com/charlievieth/fastwalk/master/LICENSE)
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
151
CHANGELOG.md
151
CHANGELOG.md
@@ -1,6 +1,157 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
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
|
||||
------
|
||||
- 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
|
||||
```sh
|
||||
# bash
|
||||
FZF_CTRL_T_COMMAND= FZF_ALT_C_COMMAND= eval "$(fzf --bash)"
|
||||
|
||||
# zsh
|
||||
FZF_CTRL_T_COMMAND= FZF_ALT_C_COMMAND= eval "$(fzf --zsh)"
|
||||
|
||||
# fish
|
||||
fzf --fish | FZF_CTRL_T_COMMAND= FZF_ALT_C_COMMAND= source
|
||||
```
|
||||
- Setting the variables after sourcing the script will have no effect
|
||||
- Bug fixes
|
||||
|
||||
0.48.0
|
||||
------
|
||||
- Shell integration scripts are now embedded in the fzf binary. This simplifies the distribution, and the users are less likely to have problems caused by using incompatible scripts and binaries.
|
||||
- bash
|
||||
```sh
|
||||
# Set up fzf key bindings and fuzzy completion
|
||||
eval "$(fzf --bash)"
|
||||
```
|
||||
- zsh
|
||||
```sh
|
||||
# Set up fzf key bindings and fuzzy completion
|
||||
eval "$(fzf --zsh)"
|
||||
```
|
||||
- fish
|
||||
```fish
|
||||
# Set up fzf key bindings
|
||||
fzf --fish | source
|
||||
```
|
||||
- Added options for customizing the behavior of the built-in walker
|
||||
| Option | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| `--walker=OPTS` | Walker options (`[file][,dir][,follow][,hidden]`) | `file,follow,hidden` |
|
||||
| `--walker-root=DIR` | Root directory from which to start walker | `.` |
|
||||
| `--walker-skip=DIRS` | Comma-separated list of directory names to skip | `.git,node_modules` |
|
||||
- Examples
|
||||
```sh
|
||||
# Built-in walker is only used by standalone fzf when $FZF_DEFAULT_COMMAND is not set
|
||||
unset FZF_DEFAULT_COMMAND
|
||||
|
||||
fzf # default: --walker=file,follow,hidden --walker-root=. --walker-skip=.git,node_modules
|
||||
fzf --walker=file,dir,hidden,follow --walker-skip=.git,node_modules,target
|
||||
|
||||
# Walker options in $FZF_DEFAULT_OPTS
|
||||
export FZF_DEFAULT_OPTS="--walker=file,dir,hidden,follow --walker-skip=.git,node_modules,target"
|
||||
fzf
|
||||
|
||||
# Reading from STDIN; --walker is ignored
|
||||
seq 100 | fzf --walker=dir
|
||||
|
||||
# Reading from $FZF_DEFAULT_COMMAND; --walker is ignored
|
||||
export FZF_DEFAULT_COMMAND='seq 100'
|
||||
fzf --walker=dir
|
||||
```
|
||||
- Shell integration scripts have been updated to use the built-in walker with these new options and they are now much faster out of the box.
|
||||
|
||||
0.47.0
|
||||
------
|
||||
- Replaced ["the default find command"][find] with a built-in directory walker to simplify the code and to achieve better performance and consistent behavior across platforms.
|
||||
This doesn't affect you if you have `$FZF_DEFAULT_COMMAND` set.
|
||||
- Breaking changes:
|
||||
- Unlike [the previous "find" command][find], the new traversal code will list hidden files, but hidden directories will still be ignored
|
||||
- No filtering of `devtmpfs` or `proc` types
|
||||
- Traversal is parallelized, so the order of the entries will be different each time
|
||||
- You may wonder why fzf implements directory walker anyway when it's a filter program following the [Unix philosophy][unix].
|
||||
But fzf has had [the walker code for years][walker] to tackle the performance problem on Windows. And I decided to use the same approach on different platforms as well for the benefits listed above.
|
||||
- Built-in walker is using the excellent [charlievieth/fastwalk][fastwalk] library, which easily outperforms its competitors and supports safely following symlinks.
|
||||
- Added `$FZF_DEFAULT_OPTS_FILE` to allow managing default options in a file
|
||||
- See [#3618](https://github.com/junegunn/fzf/pull/3618)
|
||||
- Option precedence from lower to higher
|
||||
1. Options read from `$FZF_DEFAULT_OPTS_FILE`
|
||||
1. Options from `$FZF_DEFAULT_OPTS`
|
||||
1. Options from command-line arguments
|
||||
- Bug fixes and improvements
|
||||
|
||||
[find]: https://github.com/junegunn/fzf/blob/0.46.1/src/constants.go#L60-L64
|
||||
[walker]: https://github.com/junegunn/fzf/pull/1847
|
||||
[fastwalk]: https://github.com/charlievieth/fastwalk
|
||||
[unix]: https://en.wikipedia.org/wiki/Unix_philosophy
|
||||
|
||||
0.46.1
|
||||
------
|
||||
- Bug fixes and improvements
|
||||
|
||||
3
Makefile
3
Makefile
@@ -4,7 +4,7 @@ GOOS ?= $(word 1, $(subst /, " ", $(word 4, $(shell go version))))
|
||||
|
||||
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
|
||||
ROOT_DIR := $(shell dirname $(MAKEFILE))
|
||||
SOURCES := $(wildcard *.go src/*.go src/*/*.go) $(MAKEFILE)
|
||||
SOURCES := $(wildcard *.go src/*.go src/*/*.go shell/*sh) $(MAKEFILE)
|
||||
|
||||
ifdef FZF_VERSION
|
||||
VERSION := $(FZF_VERSION)
|
||||
@@ -79,6 +79,7 @@ all: target/$(BINARY)
|
||||
test: $(SOURCES)
|
||||
[ -z "$$(gofmt -s -d src)" ] || (gofmt -s -d src; exit 1)
|
||||
SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \
|
||||
github.com/junegunn/fzf \
|
||||
github.com/junegunn/fzf/src \
|
||||
github.com/junegunn/fzf/src/algo \
|
||||
github.com/junegunn/fzf/src/tui \
|
||||
|
||||
@@ -238,19 +238,20 @@ call fzf#run({'sink': 'e'})
|
||||
```
|
||||
|
||||
We haven't specified the `source`, so this is equivalent to starting fzf on
|
||||
command line without standard input pipe; fzf will use find command (or
|
||||
`$FZF_DEFAULT_COMMAND` if defined) to list the files under the current
|
||||
directory. When you select one, it will open it with the sink, `:e` command.
|
||||
If you want to open it in a new tab, you can pass `:tabedit` command instead
|
||||
as the sink.
|
||||
command line without standard input pipe; fzf will traverse the file system
|
||||
under the current directory to get the list of files. (If
|
||||
`$FZF_DEFAULT_COMMAND` is set, fzf will use the output of the command
|
||||
instead.) When you select one, it will open it with the sink, `:e` command. If
|
||||
you want to open it in a new tab, you can pass `:tabedit` command instead as
|
||||
the sink.
|
||||
|
||||
```vim
|
||||
call fzf#run({'sink': 'tabedit'})
|
||||
```
|
||||
|
||||
Instead of using the default find command, you can use any shell command as
|
||||
the source. The following example will list the files managed by git. It's
|
||||
equivalent to running `git ls-files | fzf` on shell.
|
||||
You can use any shell command as the source to generate the list. The
|
||||
following example will list the files managed by git. It's equivalent to
|
||||
running `git ls-files | fzf` on shell.
|
||||
|
||||
```vim
|
||||
call fzf#run({'source': 'git ls-files', 'sink': 'e'})
|
||||
|
||||
@@ -95,9 +95,9 @@ while [[ $# -gt 0 ]]; do
|
||||
elif [[ "$size" =~ %$ ]]; then
|
||||
size=${size:0:((${#size}-1))}
|
||||
if [[ -n "$swap" ]]; then
|
||||
opt="$opt -p $(( 100 - size ))"
|
||||
opt="$opt -l $(( 100 - size ))%"
|
||||
else
|
||||
opt="$opt -p $size"
|
||||
opt="$opt -l $size%"
|
||||
fi
|
||||
else
|
||||
if [[ -n "$swap" ]]; then
|
||||
@@ -196,8 +196,9 @@ if [[ "$opt" =~ "-E" ]]; then
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
[[ -n "$FZF_DEFAULT_OPTS" ]] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
|
||||
[[ -n "$FZF_DEFAULT_COMMAND" ]] && envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
|
||||
envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
|
||||
envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
|
||||
envs="$envs FZF_DEFAULT_OPTS_FILE=$(printf %q "$FZF_DEFAULT_OPTS_FILE")"
|
||||
[[ -n "$RUNEWIDTH_EASTASIAN" ]] && envs="$envs RUNEWIDTH_EASTASIAN=$(printf %q "$RUNEWIDTH_EASTASIAN")"
|
||||
[[ -n "$BAT_THEME" ]] && envs="$envs BAT_THEME=$(printf %q "$BAT_THEME")"
|
||||
echo "$envs;" > "$argsf"
|
||||
|
||||
31
doc/fzf.txt
31
doc/fzf.txt
@@ -1,4 +1,4 @@
|
||||
fzf.txt fzf Last change: January 1 2024
|
||||
fzf.txt fzf Last change: February 15 2024
|
||||
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
|
||||
==============================================================================
|
||||
|
||||
@@ -264,17 +264,18 @@ entry.
|
||||
call fzf#run({'sink': 'e'})
|
||||
<
|
||||
We haven't specified the `source`, so this is equivalent to starting fzf on
|
||||
command line without standard input pipe; fzf will use find command (or
|
||||
`$FZF_DEFAULT_COMMAND` if defined) to list the files under the current
|
||||
directory. When you select one, it will open it with the sink, `:e` command.
|
||||
If you want to open it in a new tab, you can pass `:tabedit` command instead
|
||||
as the sink.
|
||||
command line without standard input pipe; fzf will traverse the file system
|
||||
under the current directory to get the list of files. (If
|
||||
`$FZF_DEFAULT_COMMAND` is set, fzf will use the output of the command
|
||||
instead.) When you select one, it will open it with the sink, `:e` command. If
|
||||
you want to open it in a new tab, you can pass `:tabedit` command instead as
|
||||
the sink.
|
||||
>
|
||||
call fzf#run({'sink': 'tabedit'})
|
||||
<
|
||||
Instead of using the default find command, you can use any shell command as
|
||||
the source. The following example will list the files managed by git. It's
|
||||
equivalent to running `git ls-files | fzf` on shell.
|
||||
You can use any shell command as the source to generate the list. The
|
||||
following example will list the files managed by git. It's equivalent to
|
||||
running `git ls-files | fzf` on shell.
|
||||
>
|
||||
call fzf#run({'source': 'git ls-files', 'sink': 'e'})
|
||||
<
|
||||
@@ -417,24 +418,12 @@ TIPS *fzf-tips*
|
||||
< fzf inside terminal buffer >________________________________________________~
|
||||
*fzf-inside-terminal-buffer*
|
||||
|
||||
The latest versions of Vim and Neovim include builtin terminal emulator
|
||||
(`:terminal`) and fzf will start in a terminal buffer in the following cases:
|
||||
|
||||
- On Neovim
|
||||
- On GVim
|
||||
- On Terminal Vim with a non-default layout
|
||||
- `call fzf#run({'left': '30%'})` or `let g:fzf_layout = {'left': '30%'}`
|
||||
|
||||
On the latest versions of Vim and Neovim, fzf will start in a terminal buffer.
|
||||
If you find the default ANSI colors to be different, consider configuring the
|
||||
colors using `g:terminal_ansi_colors` in regular Vim or `g:terminal_color_x`
|
||||
in Neovim.
|
||||
|
||||
*g:terminal_color_15* *g:terminal_color_14* *g:terminal_color_13*
|
||||
*g:terminal_color_12* *g:terminal_color_11* *g:terminal_color_10* *g:terminal_color_9*
|
||||
*g:terminal_color_8* *g:terminal_color_7* *g:terminal_color_6* *g:terminal_color_5*
|
||||
*g:terminal_color_4* *g:terminal_color_3* *g:terminal_color_2* *g:terminal_color_1*
|
||||
*g:terminal_color_0*
|
||||
>
|
||||
" Terminal colors for seoul256 color scheme
|
||||
if has('nvim')
|
||||
|
||||
15
go.mod
15
go.mod
@@ -1,21 +1,20 @@
|
||||
module github.com/junegunn/fzf
|
||||
|
||||
require (
|
||||
github.com/gdamore/tcell/v2 v2.7.0
|
||||
github.com/mattn/go-isatty v0.0.17
|
||||
github.com/charlievieth/fastwalk v1.0.3
|
||||
github.com/gdamore/tcell/v2 v2.7.4
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/mattn/go-shellwords v1.0.12
|
||||
github.com/rivo/uniseg v0.4.6
|
||||
github.com/saracen/walker v0.1.3
|
||||
golang.org/x/sys v0.16.0
|
||||
golang.org/x/term v0.16.0
|
||||
github.com/rivo/uniseg v0.4.7
|
||||
golang.org/x/sys v0.19.0
|
||||
golang.org/x/term v0.19.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/gdamore/encoding v1.0.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
golang.org/x/sync v0.5.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
)
|
||||
|
||||
go 1.17
|
||||
go 1.20
|
||||
|
||||
33
go.sum
33
go.sum
@@ -1,21 +1,21 @@
|
||||
github.com/charlievieth/fastwalk v1.0.3 h1:eNWFaNPe5srPqQ5yyDbhAf11paeZaHWcihRhpuYFfSg=
|
||||
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/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell/v2 v2.7.0 h1:I5LiGTQuwrysAt1KS9wg1yFfOI3arI3ucFrxtd/xqaA=
|
||||
github.com/gdamore/tcell/v2 v2.7.0/go.mod h1:hl/KtAANGBecfIPxk+FzKvThTqI84oplgbPEmVX60b8=
|
||||
github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU=
|
||||
github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg=
|
||||
github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/saracen/walker v0.1.3 h1:YtcKKmpRPy6XJTHJ75J2QYXXZYWnZNQxPCVqZSHVV/g=
|
||||
github.com/saracen/walker v0.1.3/go.mod h1:FU+7qU8DeQQgSZDmmThMJi93kPkLFgy0oVAcLxurjIk=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
@@ -26,27 +26,24 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
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.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
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-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.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
|
||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||
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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
|
||||
36
install
36
install
@@ -2,7 +2,7 @@
|
||||
|
||||
set -u
|
||||
|
||||
version=0.46.1
|
||||
version=0.50.0
|
||||
auto_completion=
|
||||
key_bindings=
|
||||
update_config=2
|
||||
@@ -262,6 +262,12 @@ if [[ ! "\$PATH" == *$fzf_base_esc/bin* ]]; then
|
||||
PATH="\${PATH:+\${PATH}:}$fzf_base/bin"
|
||||
fi
|
||||
|
||||
EOF
|
||||
|
||||
if [[ $auto_completion -eq 1 ]] && [[ $key_bindings -eq 1 ]]; then
|
||||
echo "eval \"\$(fzf --$shell)\"" >> "$src"
|
||||
else
|
||||
cat >> "$src" << EOF
|
||||
# Auto-completion
|
||||
# ---------------
|
||||
$fzf_completion
|
||||
@@ -270,6 +276,7 @@ $fzf_completion
|
||||
# ------------
|
||||
$fzf_key_bindings
|
||||
EOF
|
||||
fi
|
||||
echo "OK"
|
||||
done
|
||||
|
||||
@@ -281,18 +288,6 @@ if [[ "$shells" =~ fish ]]; then
|
||||
or set --universal fish_user_paths \$fish_user_paths "$fzf_base"/bin
|
||||
EOF
|
||||
[ $? -eq 0 ] && echo "OK" || echo "Failed"
|
||||
|
||||
mkdir -p "${fish_dir}/functions"
|
||||
fish_binding="${fish_dir}/functions/fzf_key_bindings.fish"
|
||||
if [ $key_bindings -ne 0 ]; then
|
||||
echo -n "Symlink $fish_binding ... "
|
||||
ln -sf "$fzf_base/shell/key-bindings.fish" \
|
||||
"$fish_binding" && echo "OK" || echo "Failed"
|
||||
else
|
||||
echo -n "Removing $fish_binding ... "
|
||||
rm -f "$fish_binding"
|
||||
echo "OK"
|
||||
fi
|
||||
fi
|
||||
|
||||
append_line() {
|
||||
@@ -355,12 +350,23 @@ done
|
||||
if [ $key_bindings -eq 1 ] && [[ "$shells" =~ fish ]]; then
|
||||
bind_file="${fish_dir}/functions/fish_user_key_bindings.fish"
|
||||
if [ ! -e "$bind_file" ]; then
|
||||
mkdir -p "${fish_dir}/functions"
|
||||
create_file "$bind_file" \
|
||||
'function fish_user_key_bindings' \
|
||||
' fzf_key_bindings' \
|
||||
' fzf --fish | source' \
|
||||
'end'
|
||||
else
|
||||
append_line $update_config "fzf_key_bindings" "$bind_file"
|
||||
echo "Check $bind_file:"
|
||||
lno=$(\grep -nF "fzf_key_bindings" "$bind_file" | sed 's/:.*//' | tr '\n' ' ')
|
||||
if [[ -n $lno ]]; then
|
||||
echo " ** Found 'fzf_key_bindings' in line #$lno"
|
||||
echo " ** You have to replace the line to 'fzf --fish | source'"
|
||||
echo
|
||||
else
|
||||
echo " - Clear"
|
||||
echo
|
||||
append_line $update_config "fzf --fish | source" "$bind_file"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
$version="0.46.1"
|
||||
$version="0.50.0"
|
||||
|
||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
|
||||
|
||||
45
main.go
45
main.go
@@ -1,14 +1,55 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
fzf "github.com/junegunn/fzf/src"
|
||||
"github.com/junegunn/fzf/src/protector"
|
||||
)
|
||||
|
||||
var version string = "0.46"
|
||||
var version string = "0.50"
|
||||
var revision string = "devel"
|
||||
|
||||
//go:embed shell/key-bindings.bash
|
||||
var bashKeyBindings []byte
|
||||
|
||||
//go:embed shell/completion.bash
|
||||
var bashCompletion []byte
|
||||
|
||||
//go:embed shell/key-bindings.zsh
|
||||
var zshKeyBindings []byte
|
||||
|
||||
//go:embed shell/completion.zsh
|
||||
var zshCompletion []byte
|
||||
|
||||
//go:embed shell/key-bindings.fish
|
||||
var fishKeyBindings []byte
|
||||
|
||||
func printScript(label string, content []byte) {
|
||||
fmt.Println("### " + label + " ###")
|
||||
fmt.Println(strings.TrimSpace(string(content)))
|
||||
fmt.Println("### end: " + label + " ###")
|
||||
}
|
||||
|
||||
func main() {
|
||||
protector.Protect()
|
||||
fzf.Run(fzf.ParseOptions(), version, revision)
|
||||
options := fzf.ParseOptions()
|
||||
if options.Bash {
|
||||
printScript("key-bindings.bash", bashKeyBindings)
|
||||
printScript("completion.bash", bashCompletion)
|
||||
return
|
||||
}
|
||||
if options.Zsh {
|
||||
printScript("key-bindings.zsh", zshKeyBindings)
|
||||
printScript("completion.zsh", zshCompletion)
|
||||
return
|
||||
}
|
||||
if options.Fish {
|
||||
printScript("key-bindings.fish", fishKeyBindings)
|
||||
fmt.Println("fzf_key_bindings")
|
||||
return
|
||||
}
|
||||
fzf.Run(options, version, revision)
|
||||
}
|
||||
|
||||
158
main_test.go
Normal file
158
main_test.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/importer"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func loadPackages(t *testing.T) []*build.Package {
|
||||
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 := build.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
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf-tmux 1 "Feb 2024" "fzf 0.46.1" "fzf-tmux - open fzf in tmux split pane"
|
||||
.TH fzf-tmux 1 "Apr 2024" "fzf 0.50.0" "fzf-tmux - open fzf in tmux split pane"
|
||||
|
||||
.SH NAME
|
||||
fzf-tmux - open fzf in tmux split pane
|
||||
|
||||
129
man/man1/fzf.1
129
man/man1/fzf.1
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf 1 "Feb 2024" "fzf 0.46.1" "fzf - a command-line fuzzy finder"
|
||||
.TH fzf 1 "Apr 2024" "fzf 0.50.0" "fzf - a command-line fuzzy finder"
|
||||
|
||||
.SH NAME
|
||||
fzf - a command-line fuzzy finder
|
||||
@@ -33,6 +33,10 @@ fzf [options]
|
||||
fzf is a general-purpose command-line fuzzy finder.
|
||||
|
||||
.SH OPTIONS
|
||||
.SS Note
|
||||
.TP
|
||||
Most long options have the opposite version with \fB--no-\fR prefix.
|
||||
|
||||
.SS Search mode
|
||||
.TP
|
||||
.B "-x, --extended"
|
||||
@@ -187,7 +191,7 @@ actions are affected:
|
||||
\fBkill-word\fR
|
||||
.TP
|
||||
.BI "--jump-labels=" "CHARS"
|
||||
Label characters for \fBjump\fR and \fBjump-accept\fR
|
||||
Label characters for \fBjump\fR mode.
|
||||
.SS Layout
|
||||
.TP
|
||||
.BI "--height=" "[~]HEIGHT[%]"
|
||||
@@ -368,21 +372,22 @@ e.g.
|
||||
|
||||
.TP
|
||||
.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 default " Display on the next line to the prompt"
|
||||
.br
|
||||
.BR right " Display on the right end of the next line to the prompt"
|
||||
.br
|
||||
.BR inline " Display on the same line with the default separator ' < '"
|
||||
.br
|
||||
.BR inline:SEPARATOR " Display on the same line with a non-default separator"
|
||||
.br
|
||||
.BR inline-right " Display on the right end of the same line
|
||||
.BR right " On the right end of the horizontal separator"
|
||||
.br
|
||||
.BR hidden " Do not display finder info"
|
||||
.br
|
||||
.BR inline " After the prompt with the default prefix ' < '"
|
||||
.br
|
||||
.BR inline:PREFIX " After the prompt with a non-default prefix"
|
||||
.br
|
||||
.BR inline-right " On the right end of the prompt line"
|
||||
.br
|
||||
.BR inline-right:PREFIX " On the right end of the prompt line with a custom prefix"
|
||||
.br
|
||||
|
||||
.TP
|
||||
.B "--no-info"
|
||||
@@ -620,7 +625,7 @@ The following example uses https://github.com/junegunn/fzf/blob/master/bin/fzf-p
|
||||
script to render an image using either of the protocols inside the preview window.
|
||||
|
||||
e.g.
|
||||
\fBfzf --preview='fzf-preview.sh {}'
|
||||
\fBfzf --preview='fzf-preview.sh {}'\fR
|
||||
|
||||
.RE
|
||||
|
||||
@@ -854,8 +859,49 @@ e.g.
|
||||
.B "--version"
|
||||
Display version information and exit
|
||||
|
||||
.SS Directory traversal
|
||||
.TP
|
||||
Note that most options have the opposite versions with \fB--no-\fR prefix.
|
||||
.B "--walker=[file][,dir][,follow][,hidden]"
|
||||
Determines the behavior of the built-in directory walker that is used when
|
||||
\fB$FZF_DEFAULT_COMMAND\fR is not set. The default value is \fBfile,follow,hidden\fR.
|
||||
|
||||
* \fBfile\fR: Include files in the search result
|
||||
.br
|
||||
* \fBdir\fR: Include directories in the search result
|
||||
.br
|
||||
* \fBhidden\fR: Include and follow hidden directories
|
||||
.br
|
||||
* \fBfollow\fR: Follow symbolic links
|
||||
.br
|
||||
|
||||
.TP
|
||||
.B "--walker-root=DIR"
|
||||
The root directory from which to start the built-in directory walker.
|
||||
The default value is the current working directory.
|
||||
|
||||
.TP
|
||||
.B "--walker-skip=DIRS"
|
||||
Comma-separated list of directory names to skip during the directory walk.
|
||||
The default value is \fB.git,node_modules\fR.
|
||||
|
||||
.SS Shell integration
|
||||
.TP
|
||||
.B "--bash"
|
||||
Print script to set up Bash shell integration
|
||||
|
||||
e.g. \fBeval "$(fzf --bash)"\fR
|
||||
|
||||
.TP
|
||||
.B "--zsh"
|
||||
Print script to set up Zsh shell integration
|
||||
|
||||
e.g. \fBeval "$(fzf --zsh)"\fR
|
||||
|
||||
.TP
|
||||
.B "--fish"
|
||||
Print script to set up Fish shell integration
|
||||
|
||||
e.g. \fBfzf --fish | source\fR
|
||||
|
||||
.SH ENVIRONMENT VARIABLES
|
||||
.TP
|
||||
@@ -865,7 +911,14 @@ with \fB$SHELL -c\fR if \fBSHELL\fR is set, otherwise with \fBsh -c\fR, so in
|
||||
this case make sure that the command is POSIX-compliant.
|
||||
.TP
|
||||
.B FZF_DEFAULT_OPTS
|
||||
Default options. e.g. \fBexport FZF_DEFAULT_OPTS="--extended --cycle"\fR
|
||||
Default options.
|
||||
.br
|
||||
e.g. \fBexport FZF_DEFAULT_OPTS="--layout=reverse --border --cycle"\fR
|
||||
.TP
|
||||
.B FZF_DEFAULT_OPTS_FILE
|
||||
The location of the file that contains the default options.
|
||||
.br
|
||||
e.g. \fBexport FZF_DEFAULT_OPTS_FILE=~/.fzfrc\fR
|
||||
.TP
|
||||
.B FZF_API_KEY
|
||||
Can be used to require an API key when using \fB--listen\fR option. If not set,
|
||||
@@ -923,8 +976,14 @@ fzf exports the following environment variables to its child processes.
|
||||
.br
|
||||
.BR FZF_PROMPT " Prompt string"
|
||||
.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
|
||||
.BR FZF_KEY " The name of the last key pressed"
|
||||
.br
|
||||
.BR FZF_PORT " Port number when --listen option is used"
|
||||
.br
|
||||
|
||||
@@ -1009,7 +1068,7 @@ e.g.
|
||||
.br
|
||||
\fIspace\fR
|
||||
.br
|
||||
\fIbspace\fR (\fIbs\fR)
|
||||
\fIbackspace\fR (\fIbspace\fR \fIbs\fR)
|
||||
.br
|
||||
\fIalt-up\fR
|
||||
.br
|
||||
@@ -1023,15 +1082,15 @@ e.g.
|
||||
.br
|
||||
\fIalt-space\fR
|
||||
.br
|
||||
\fIalt-bspace\fR (\fIalt-bs\fR)
|
||||
\fIalt-backspace\fR (\fIalt-bspace\fR \fIalt-bs\fR)
|
||||
.br
|
||||
\fItab\fR
|
||||
.br
|
||||
\fIbtab\fR (\fIshift-tab\fR)
|
||||
\fIshift-tab\fR (\fIbtab\fR)
|
||||
.br
|
||||
\fIesc\fR
|
||||
.br
|
||||
\fIdel\fR
|
||||
\fIdelete\fR (\fIdel\fR)
|
||||
.br
|
||||
\fIup\fR
|
||||
.br
|
||||
@@ -1047,9 +1106,9 @@ e.g.
|
||||
.br
|
||||
\fIinsert\fR
|
||||
.br
|
||||
\fIpgup\fR (\fIpage-up\fR)
|
||||
\fIpage-up\fR (\fIpgup\fR)
|
||||
.br
|
||||
\fIpgdn\fR (\fIpage-down\fR)
|
||||
\fIpage-down\fR (\fIpgdn\fR)
|
||||
.br
|
||||
\fIshift-up\fR
|
||||
.br
|
||||
@@ -1103,6 +1162,7 @@ e.g.
|
||||
\fB# Move cursor to the last item and select all items
|
||||
seq 1000 | fzf --multi --sync --bind start:last+select-all\fR
|
||||
.RE
|
||||
|
||||
\fIload\fR
|
||||
.RS
|
||||
Triggered when the input stream is complete and the initial processing of the
|
||||
@@ -1112,6 +1172,7 @@ e.g.
|
||||
\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
|
||||
.RE
|
||||
|
||||
\fIresize\fR
|
||||
.RS
|
||||
Triggered when the terminal size is changed.
|
||||
@@ -1119,6 +1180,7 @@ Triggered when the terminal size is changed.
|
||||
e.g.
|
||||
\fBfzf --bind 'resize:transform-header:echo Resized: ${FZF_COLUMNS}x${FZF_LINES}'\fR
|
||||
.RE
|
||||
|
||||
\fIresult\fR
|
||||
.RS
|
||||
Triggered when the filtering for the current query is complete and the result list is ready.
|
||||
@@ -1152,6 +1214,7 @@ e.g.
|
||||
# Beware not to introduce an infinite loop
|
||||
seq 10 | fzf --bind 'focus:up' --cycle\fR
|
||||
.RE
|
||||
|
||||
\fIone\fR
|
||||
.RS
|
||||
Triggered when there's only one match. \fBone:accept\fR binding is comparable
|
||||
@@ -1163,6 +1226,7 @@ e.g.
|
||||
\fB# Automatically select the only match
|
||||
seq 10 | fzf --bind one:accept\fR
|
||||
.RE
|
||||
|
||||
\fIzero\fR
|
||||
.RS
|
||||
Triggered when there's no match. \fBzero:abort\fR binding is comparable to
|
||||
@@ -1184,6 +1248,22 @@ e.g.
|
||||
\fBfzf --bind backward-eof:abort\fR
|
||||
.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:
|
||||
A key or an event can be bound to one or more of the following actions.
|
||||
|
||||
@@ -1226,7 +1306,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
|
||||
\fBignore\fR
|
||||
\fBjump\fR (EasyMotion-like 2-keystroke movement)
|
||||
\fBjump-accept\fR (jump and accept)
|
||||
\fBkill-line\fR
|
||||
\fBkill-word\fR \fIalt-d\fR
|
||||
\fBlast\fR (move to the last match; same as \fBpos(-1)\fR)
|
||||
@@ -1274,9 +1353,10 @@ A key or an event can be bound to one or more of the following actions.
|
||||
\fBtoggle-preview-wrap\fR
|
||||
\fBtoggle-search\fR (toggle search functionality)
|
||||
\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
|
||||
\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-border-label(...)\fR (transform border label using an external command)
|
||||
\fBtransform-header(...)\fR (transform header using an external command)
|
||||
@@ -1286,6 +1366,7 @@ A key or an event can be bound to one or more of the following actions.
|
||||
\fBunbind(...)\fR (unbind bindings)
|
||||
\fBunix-line-discard\fR \fIctrl-u\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
|
||||
\fByank\fR \fIctrl-y\fR
|
||||
|
||||
|
||||
@@ -9,26 +9,23 @@
|
||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||
|
||||
[[ $- =~ i ]] || return 0
|
||||
if [[ $- =~ i ]]; then
|
||||
|
||||
|
||||
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
||||
if ! declare -F _fzf_compgen_path > /dev/null; then
|
||||
_fzf_compgen_path() {
|
||||
echo "$1"
|
||||
command find -L "$1" \
|
||||
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
||||
-a -not -path "$1" -print 2> /dev/null | command sed 's@^\./@@'
|
||||
}
|
||||
fi
|
||||
|
||||
if ! declare -F _fzf_compgen_dir > /dev/null; then
|
||||
_fzf_compgen_dir() {
|
||||
command find -L "$1" \
|
||||
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
|
||||
-a -not -path "$1" -print 2> /dev/null | command sed 's@^\./@@'
|
||||
}
|
||||
fi
|
||||
#
|
||||
# _fzf_compgen_path() {
|
||||
# echo "$1"
|
||||
# command find -L "$1" \
|
||||
# -name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
||||
# -a -not -path "$1" -print 2> /dev/null | command sed 's@^\./@@'
|
||||
# }
|
||||
#
|
||||
# _fzf_compgen_dir() {
|
||||
# command find -L "$1" \
|
||||
# -name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
|
||||
# -a -not -path "$1" -print 2> /dev/null | command sed 's@^\./@@'
|
||||
# }
|
||||
|
||||
###########################################################
|
||||
|
||||
@@ -63,6 +60,31 @@ __fzf_orig_completion() {
|
||||
done
|
||||
}
|
||||
|
||||
# @param $1 cmd - Command name for which the original completion is searched
|
||||
# @var[out] REPLY - Original function name is returned
|
||||
__fzf_orig_completion_get_orig_func() {
|
||||
local cmd orig_var orig
|
||||
cmd=$1
|
||||
orig_var="_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}"
|
||||
orig="${!orig_var-}"
|
||||
REPLY="${orig##*#}"
|
||||
[[ $REPLY ]] && type "$REPLY" &> /dev/null
|
||||
}
|
||||
|
||||
# @param $1 cmd - Command name for which the original completion is searched
|
||||
# @param $2 func - Fzf's completion function to replace the original function
|
||||
# @var[out] REPLY - Completion setting is returned as a string to "eval"
|
||||
__fzf_orig_completion_instantiate() {
|
||||
local cmd func orig_var orig
|
||||
cmd=$1
|
||||
func=$2
|
||||
orig_var="_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}"
|
||||
orig="${!orig_var-}"
|
||||
orig="${orig%#*}"
|
||||
[[ $orig == *' %s '* ]] || return 1
|
||||
printf -v REPLY "$orig" "$func"
|
||||
}
|
||||
|
||||
_fzf_opts_completion() {
|
||||
local cur prev opts
|
||||
COMPREPLY=()
|
||||
@@ -261,28 +283,32 @@ _fzf_opts_completion() {
|
||||
}
|
||||
|
||||
_fzf_handle_dynamic_completion() {
|
||||
local cmd orig_var orig ret orig_cmd orig_complete
|
||||
local cmd ret REPLY orig_cmd orig_complete
|
||||
cmd="$1"
|
||||
shift
|
||||
orig_cmd="$1"
|
||||
orig_var="_fzf_orig_completion_$cmd"
|
||||
orig="${!orig_var-}"
|
||||
orig="${orig##*#}"
|
||||
if [[ -n "$orig" ]] && type "$orig" > /dev/null 2>&1; then
|
||||
$orig "$@"
|
||||
if __fzf_orig_completion_get_orig_func "$cmd"; then
|
||||
"$REPLY" "$@"
|
||||
elif [[ -n "${_fzf_completion_loader-}" ]]; then
|
||||
orig_complete=$(complete -p "$orig_cmd" 2> /dev/null)
|
||||
_completion_loader "$@"
|
||||
$_fzf_completion_loader "$@"
|
||||
ret=$?
|
||||
# _completion_loader may not have updated completion for the command
|
||||
if [[ "$(complete -p "$orig_cmd" 2> /dev/null)" != "$orig_complete" ]]; then
|
||||
__fzf_orig_completion < <(complete -p "$orig_cmd" 2> /dev/null)
|
||||
|
||||
# Update orig_complete by _fzf_orig_completion entry
|
||||
[[ $orig_complete =~ ' -F '(_fzf_[^ ]+)' ' ]] &&
|
||||
__fzf_orig_completion_instantiate "$cmd" "${BASH_REMATCH[1]}" &&
|
||||
orig_complete=$REPLY
|
||||
|
||||
if [[ "${__fzf_nospace_commands-}" = *" $orig_cmd "* ]]; then
|
||||
eval "${orig_complete/ -F / -o nospace -F }"
|
||||
else
|
||||
eval "$orig_complete"
|
||||
fi
|
||||
fi
|
||||
[[ $ret -eq 0 ]] && return 124
|
||||
return $ret
|
||||
fi
|
||||
}
|
||||
@@ -293,7 +319,6 @@ __fzf_generic_path_completion() {
|
||||
if [[ $cmd == \\* ]]; then
|
||||
cmd="${cmd:1}"
|
||||
fi
|
||||
cmd="${cmd//[^A-Za-z0-9_=]/_}"
|
||||
COMPREPLY=()
|
||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
@@ -309,9 +334,18 @@ __fzf_generic_path_completion() {
|
||||
leftover=${leftover/#\/}
|
||||
[[ -z "$dir" ]] && dir='.'
|
||||
[[ "$dir" != "/" ]] && dir="${dir/%\//}"
|
||||
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
|
||||
matches=$(
|
||||
unset FZF_DEFAULT_COMMAND
|
||||
export FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $2"
|
||||
if declare -F "$1" > /dev/null; then
|
||||
eval "$1 $(printf %q "$dir")" | __fzf_comprun "$4" -q "$leftover"
|
||||
else
|
||||
[[ $1 =~ dir ]] && walker=dir,follow || walker=file,dir,follow,hidden
|
||||
__fzf_comprun "$4" -q "$leftover" --walker "$walker" --walker-root="$dir"
|
||||
fi | while read -r item; do
|
||||
printf "%q " "${item%$3}$3"
|
||||
done)
|
||||
done
|
||||
)
|
||||
matches=${matches% }
|
||||
[[ -z "$3" ]] && [[ "${__fzf_nospace_commands-}" = *" ${COMP_WORDS[0]} "* ]] && matches="$matches "
|
||||
if [[ -n "$matches" ]]; then
|
||||
@@ -359,8 +393,8 @@ _fzf_complete() {
|
||||
post="$(caller 0 | command awk '{print $2}')_post"
|
||||
type -t "$post" > /dev/null 2>&1 || post='command cat'
|
||||
|
||||
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
|
||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||
cmd="${COMP_WORDS[0]}"
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
|
||||
cur=${cur:0:${#cur}-${#trigger}}
|
||||
@@ -483,20 +517,25 @@ a_cmds="
|
||||
# Preserve existing completion
|
||||
__fzf_orig_completion < <(complete -p $d_cmds $a_cmds ssh 2> /dev/null)
|
||||
|
||||
if type _completion_loader > /dev/null 2>&1; then
|
||||
_fzf_completion_loader=1
|
||||
if type _comp_load > /dev/null 2>&1; then
|
||||
# _comp_load was added in bash-completion 2.12 to replace _completion_loader.
|
||||
# We use it without -D option so that it does not use _comp_complete_minimal as the fallback.
|
||||
_fzf_completion_loader=_comp_load
|
||||
elif type __load_completion > /dev/null 2>&1; then
|
||||
# In bash-completion 2.11, _completion_loader internally calls __load_completion
|
||||
# and if it returns a non-zero status, it sets the default 'minimal' completion.
|
||||
_fzf_completion_loader=__load_completion
|
||||
elif type _completion_loader > /dev/null 2>&1; then
|
||||
_fzf_completion_loader=_completion_loader
|
||||
fi
|
||||
|
||||
__fzf_defc() {
|
||||
local cmd func opts orig_var orig def
|
||||
local cmd func opts REPLY
|
||||
cmd="$1"
|
||||
func="$2"
|
||||
opts="$3"
|
||||
orig_var="_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}"
|
||||
orig="${!orig_var-}"
|
||||
if [[ -n "$orig" ]]; then
|
||||
printf -v def "$orig" "$func"
|
||||
eval "$def"
|
||||
if __fzf_orig_completion_instantiate "$cmd" "$func"; then
|
||||
eval "$REPLY"
|
||||
else
|
||||
complete -F "$func" $opts "$cmd"
|
||||
fi
|
||||
@@ -509,7 +548,7 @@ done
|
||||
|
||||
# Directory
|
||||
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
|
||||
|
||||
# ssh
|
||||
@@ -542,3 +581,5 @@ _fzf_setup_completion 'var' export unset printenv
|
||||
_fzf_setup_completion 'alias' unalias
|
||||
_fzf_setup_completion 'host' telnet
|
||||
_fzf_setup_completion 'proc' kill
|
||||
|
||||
fi
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||
|
||||
[[ -o interactive ]] || return 0
|
||||
if [[ -o interactive ]]; then
|
||||
|
||||
|
||||
# Both branches of the following `if` do the same thing -- define
|
||||
@@ -77,22 +77,19 @@ fi
|
||||
{
|
||||
|
||||
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
||||
if ! declare -f _fzf_compgen_path > /dev/null; then
|
||||
_fzf_compgen_path() {
|
||||
echo "$1"
|
||||
command find -L "$1" \
|
||||
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
||||
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||
}
|
||||
fi
|
||||
|
||||
if ! declare -f _fzf_compgen_dir > /dev/null; then
|
||||
_fzf_compgen_dir() {
|
||||
command find -L "$1" \
|
||||
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
|
||||
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||
}
|
||||
fi
|
||||
#
|
||||
# _fzf_compgen_path() {
|
||||
# echo "$1"
|
||||
# command find -L "$1" \
|
||||
# -name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
||||
# -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||
# }
|
||||
#
|
||||
# _fzf_compgen_dir() {
|
||||
# command find -L "$1" \
|
||||
# -name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
|
||||
# -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||
# }
|
||||
|
||||
###########################################################
|
||||
|
||||
@@ -148,10 +145,19 @@ __fzf_generic_path_completion() {
|
||||
leftover=${leftover/#\/}
|
||||
[ -z "$dir" ] && dir='.'
|
||||
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
||||
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-}" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
|
||||
matches=$(
|
||||
unset FZF_DEFAULT_COMMAND
|
||||
export FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-}"
|
||||
if declare -f "$compgen" > /dev/null; then
|
||||
eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover"
|
||||
else
|
||||
[[ $compgen =~ dir ]] && walker=dir,follow || walker=file,dir,follow,hidden
|
||||
__fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" --walker "$walker" --walker-root="$dir" < /dev/tty
|
||||
fi | while read item; do
|
||||
item="${item%$suffix}$suffix"
|
||||
echo -n "${(q)item} "
|
||||
done)
|
||||
done
|
||||
)
|
||||
matches=${matches% }
|
||||
if [ -n "$matches" ]; then
|
||||
LBUFFER="$lbuf$matches$tail"
|
||||
@@ -345,3 +351,5 @@ bindkey '^I' fzf-completion
|
||||
eval $__fzf_completion_options
|
||||
'unset' '__fzf_completion_options'
|
||||
}
|
||||
|
||||
fi
|
||||
|
||||
@@ -11,20 +11,15 @@
|
||||
# - $FZF_ALT_C_COMMAND
|
||||
# - $FZF_ALT_C_OPTS
|
||||
|
||||
[[ $- =~ i ]] || return 0
|
||||
if [[ $- =~ i ]]; then
|
||||
|
||||
|
||||
# Key bindings
|
||||
# ------------
|
||||
__fzf_select__() {
|
||||
local cmd opts
|
||||
cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||
-o -type f -print \
|
||||
-o -type d -print \
|
||||
-o -type l -print 2> /dev/null | command cut -b3-"}"
|
||||
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse --scheme=path ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-} -m"
|
||||
eval "$cmd" |
|
||||
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) "$@" |
|
||||
local opts
|
||||
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_COMMAND=${FZF_CTRL_T_COMMAND:-} FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) "$@" |
|
||||
while read -r item; do
|
||||
printf '%q ' "$item" # escape special chars
|
||||
done
|
||||
@@ -42,11 +37,11 @@ fzf-file-widget() {
|
||||
}
|
||||
|
||||
__fzf_cd__() {
|
||||
local cmd opts dir
|
||||
cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||
-o -type d -print 2> /dev/null | command cut -b3-"}"
|
||||
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse --scheme=path ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-} +m"
|
||||
dir=$(set +o pipefail; eval "$cmd" | FZF_DEFAULT_OPTS="$opts" $(__fzfcmd)) && printf 'builtin cd -- %q' "$dir"
|
||||
local opts 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=$(
|
||||
FZF_DEFAULT_COMMAND=${FZF_ALT_C_COMMAND:-} FZF_DEFAULT_OPTS="$opts" $(__fzfcmd)
|
||||
) && printf 'builtin cd -- %q' "$(builtin unset CDPATH && builtin cd -- "$dir" && builtin pwd)"
|
||||
}
|
||||
|
||||
if command -v perl > /dev/null; then
|
||||
@@ -107,9 +102,11 @@ bind -m emacs-standard '"\C-z": vi-editing-mode'
|
||||
|
||||
if (( BASH_VERSINFO[0] < 4 )); then
|
||||
# CTRL-T - Paste the selected file path into the command line
|
||||
if [[ "${FZF_CTRL_T_COMMAND-x}" != "" ]]; then
|
||||
bind -m emacs-standard '"\C-t": " \C-b\C-k \C-u`__fzf_select__`\e\C-e\er\C-a\C-y\C-h\C-e\e \C-y\ey\C-x\C-x\C-f"'
|
||||
bind -m vi-command '"\C-t": "\C-z\C-t\C-z"'
|
||||
bind -m vi-insert '"\C-t": "\C-z\C-t\C-z"'
|
||||
fi
|
||||
|
||||
# CTRL-R - Paste the selected command from history into the command line
|
||||
bind -m emacs-standard '"\C-r": "\C-e \C-u\C-y\ey\C-u`__fzf_history__`\e\C-e\er"'
|
||||
@@ -117,9 +114,11 @@ if (( BASH_VERSINFO[0] < 4 )); then
|
||||
bind -m vi-insert '"\C-r": "\C-z\C-r\C-z"'
|
||||
else
|
||||
# CTRL-T - Paste the selected file path into the command line
|
||||
if [[ "${FZF_CTRL_T_COMMAND-x}" != "" ]]; then
|
||||
bind -m emacs-standard -x '"\C-t": fzf-file-widget'
|
||||
bind -m vi-command -x '"\C-t": fzf-file-widget'
|
||||
bind -m vi-insert -x '"\C-t": fzf-file-widget'
|
||||
fi
|
||||
|
||||
# CTRL-R - Paste the selected command from history into the command line
|
||||
bind -m emacs-standard -x '"\C-r": __fzf_history__'
|
||||
@@ -128,6 +127,10 @@ else
|
||||
fi
|
||||
|
||||
# ALT-C - cd into the selected directory
|
||||
if [[ "${FZF_ALT_C_COMMAND-x}" != "" ]]; then
|
||||
bind -m emacs-standard '"\ec": " \C-b\C-k \C-u`__fzf_cd__`\e\C-e\er\C-m\C-y\C-h\e \C-y\ey\C-x\C-x\C-d"'
|
||||
bind -m vi-command '"\ec": "\C-z\ec\C-z"'
|
||||
bind -m vi-insert '"\ec": "\C-z\ec\C-z"'
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
@@ -21,22 +21,15 @@ function fzf_key_bindings
|
||||
# Store current token in $dir as root for the 'find' command
|
||||
function fzf-file-widget -d "List files and folders"
|
||||
set -l commandline (__fzf_parse_commandline)
|
||||
set -l dir $commandline[1]
|
||||
set -lx dir $commandline[1]
|
||||
set -l fzf_query $commandline[2]
|
||||
set -l prefix $commandline[3]
|
||||
|
||||
# "-path \$dir'*/.*'" matches hidden files/folders inside $dir but not
|
||||
# $dir itself, even if hidden.
|
||||
test -n "$FZF_CTRL_T_COMMAND"; or set -l FZF_CTRL_T_COMMAND "
|
||||
command find -L \$dir -mindepth 1 \\( -path \$dir'*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
||||
-o -type f -print \
|
||||
-o -type d -print \
|
||||
-o -type l -print 2> /dev/null | sed 's@^\./@@'"
|
||||
|
||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||
begin
|
||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
|
||||
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
|
||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --walker=file,dir,follow,hidden --walker-root='$dir' --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
|
||||
set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND"
|
||||
eval (__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
|
||||
end
|
||||
if [ -z "$result" ]
|
||||
commandline -f repaint
|
||||
@@ -77,17 +70,15 @@ function fzf_key_bindings
|
||||
|
||||
function fzf-cd-widget -d "Change directory"
|
||||
set -l commandline (__fzf_parse_commandline)
|
||||
set -l dir $commandline[1]
|
||||
set -lx dir $commandline[1]
|
||||
set -l fzf_query $commandline[2]
|
||||
set -l prefix $commandline[3]
|
||||
|
||||
test -n "$FZF_ALT_C_COMMAND"; or set -l FZF_ALT_C_COMMAND "
|
||||
command find -L \$dir -mindepth 1 \\( -path \$dir'*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
||||
-o -type d -print 2> /dev/null | sed 's@^\./@@'"
|
||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||
begin
|
||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
|
||||
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --walker=dir,follow,hidden --walker-root='$dir' --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
|
||||
set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND"
|
||||
eval (__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
||||
|
||||
if [ -n "$result" ]
|
||||
cd -- $result
|
||||
@@ -113,15 +104,23 @@ function fzf_key_bindings
|
||||
end
|
||||
end
|
||||
|
||||
bind \ct fzf-file-widget
|
||||
bind \cr fzf-history-widget
|
||||
if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND"
|
||||
bind \ct fzf-file-widget
|
||||
end
|
||||
if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND"
|
||||
bind \ec fzf-cd-widget
|
||||
end
|
||||
|
||||
if bind -M insert > /dev/null 2>&1
|
||||
bind -M insert \ct fzf-file-widget
|
||||
bind -M insert \cr fzf-history-widget
|
||||
if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND"
|
||||
bind -M insert \ct fzf-file-widget
|
||||
end
|
||||
if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND"
|
||||
bind -M insert \ec fzf-cd-widget
|
||||
end
|
||||
end
|
||||
|
||||
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix'
|
||||
set -l commandline (commandline -t)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
# - $FZF_ALT_C_COMMAND
|
||||
# - $FZF_ALT_C_OPTS
|
||||
|
||||
[[ -o interactive ]] || return 0
|
||||
if [[ -o interactive ]]; then
|
||||
|
||||
|
||||
# Key bindings
|
||||
@@ -41,13 +41,9 @@ fi
|
||||
|
||||
# CTRL-T - Paste the selected file path(s) into the command line
|
||||
__fsel() {
|
||||
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||
-o -type f -print \
|
||||
-o -type d -print \
|
||||
-o -type l -print 2> /dev/null | cut -b3-"}"
|
||||
setopt localoptions pipefail no_aliases 2> /dev/null
|
||||
local item
|
||||
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-}" $(__fzfcmd) -m "$@" | while read item; do
|
||||
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
|
||||
echo -n "${(q)item} "
|
||||
done
|
||||
local ret=$?
|
||||
@@ -66,33 +62,35 @@ fzf-file-widget() {
|
||||
zle reset-prompt
|
||||
return $ret
|
||||
}
|
||||
if [[ "${FZF_CTRL_T_COMMAND-x}" != "" ]]; then
|
||||
zle -N fzf-file-widget
|
||||
bindkey -M emacs '^T' fzf-file-widget
|
||||
bindkey -M vicmd '^T' fzf-file-widget
|
||||
bindkey -M viins '^T' fzf-file-widget
|
||||
fi
|
||||
|
||||
# ALT-C - cd into the selected directory
|
||||
fzf-cd-widget() {
|
||||
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||
-o -type d -print 2> /dev/null | cut -b3-"}"
|
||||
setopt localoptions pipefail no_aliases 2> /dev/null
|
||||
local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-}" $(__fzfcmd) +m)"
|
||||
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)"
|
||||
if [[ -z "$dir" ]]; then
|
||||
zle redisplay
|
||||
return 0
|
||||
fi
|
||||
zle push-line # Clear buffer. Auto-restored on next prompt.
|
||||
BUFFER="builtin cd -- ${(q)dir}"
|
||||
BUFFER="builtin cd -- ${(q)dir:a}"
|
||||
zle accept-line
|
||||
local ret=$?
|
||||
unset dir # ensure this doesn't end up appearing in prompt expansion
|
||||
zle reset-prompt
|
||||
return $ret
|
||||
}
|
||||
if [[ "${FZF_ALT_C_COMMAND-x}" != "" ]]; then
|
||||
zle -N fzf-cd-widget
|
||||
bindkey -M emacs '\ec' fzf-cd-widget
|
||||
bindkey -M vicmd '\ec' fzf-cd-widget
|
||||
bindkey -M viins '\ec' fzf-cd-widget
|
||||
fi
|
||||
|
||||
# CTRL-R - Paste the selected command from history into the command line
|
||||
fzf-history-widget() {
|
||||
@@ -121,3 +119,5 @@ bindkey -M viins '^R' fzf-history-widget
|
||||
eval $__fzf_key_bindings_options
|
||||
'unset' '__fzf_key_bindings_options'
|
||||
}
|
||||
|
||||
fi
|
||||
|
||||
@@ -54,72 +54,74 @@ func _() {
|
||||
_ = x[actToggleIn-43]
|
||||
_ = x[actToggleOut-44]
|
||||
_ = x[actToggleTrack-45]
|
||||
_ = x[actToggleHeader-46]
|
||||
_ = x[actTrack-47]
|
||||
_ = x[actDown-48]
|
||||
_ = x[actUp-49]
|
||||
_ = x[actPageUp-50]
|
||||
_ = x[actPageDown-51]
|
||||
_ = x[actPosition-52]
|
||||
_ = x[actHalfPageUp-53]
|
||||
_ = x[actHalfPageDown-54]
|
||||
_ = x[actOffsetUp-55]
|
||||
_ = x[actOffsetDown-56]
|
||||
_ = x[actJump-57]
|
||||
_ = x[actJumpAccept-58]
|
||||
_ = x[actPrintQuery-59]
|
||||
_ = x[actRefreshPreview-60]
|
||||
_ = x[actReplaceQuery-61]
|
||||
_ = x[actToggleSort-62]
|
||||
_ = x[actShowPreview-63]
|
||||
_ = x[actHidePreview-64]
|
||||
_ = x[actTogglePreview-65]
|
||||
_ = x[actTogglePreviewWrap-66]
|
||||
_ = x[actTransform-67]
|
||||
_ = x[actTransformBorderLabel-68]
|
||||
_ = x[actTransformHeader-69]
|
||||
_ = x[actTransformPreviewLabel-70]
|
||||
_ = x[actTransformPrompt-71]
|
||||
_ = x[actTransformQuery-72]
|
||||
_ = x[actPreview-73]
|
||||
_ = x[actChangePreview-74]
|
||||
_ = x[actChangePreviewWindow-75]
|
||||
_ = x[actPreviewTop-76]
|
||||
_ = x[actPreviewBottom-77]
|
||||
_ = x[actPreviewUp-78]
|
||||
_ = x[actPreviewDown-79]
|
||||
_ = x[actPreviewPageUp-80]
|
||||
_ = x[actPreviewPageDown-81]
|
||||
_ = x[actPreviewHalfPageUp-82]
|
||||
_ = x[actPreviewHalfPageDown-83]
|
||||
_ = x[actPrevHistory-84]
|
||||
_ = x[actPrevSelected-85]
|
||||
_ = x[actPut-86]
|
||||
_ = x[actNextHistory-87]
|
||||
_ = x[actNextSelected-88]
|
||||
_ = x[actExecute-89]
|
||||
_ = x[actExecuteSilent-90]
|
||||
_ = x[actExecuteMulti-91]
|
||||
_ = x[actSigStop-92]
|
||||
_ = x[actFirst-93]
|
||||
_ = x[actLast-94]
|
||||
_ = x[actReload-95]
|
||||
_ = x[actReloadSync-96]
|
||||
_ = x[actDisableSearch-97]
|
||||
_ = x[actEnableSearch-98]
|
||||
_ = x[actSelect-99]
|
||||
_ = x[actDeselect-100]
|
||||
_ = x[actUnbind-101]
|
||||
_ = x[actRebind-102]
|
||||
_ = x[actBecome-103]
|
||||
_ = x[actResponse-104]
|
||||
_ = x[actShowHeader-105]
|
||||
_ = x[actHideHeader-106]
|
||||
_ = x[actToggleTrackCurrent-46]
|
||||
_ = x[actToggleHeader-47]
|
||||
_ = x[actTrackCurrent-48]
|
||||
_ = x[actUntrackCurrent-49]
|
||||
_ = x[actDown-50]
|
||||
_ = x[actUp-51]
|
||||
_ = x[actPageUp-52]
|
||||
_ = x[actPageDown-53]
|
||||
_ = x[actPosition-54]
|
||||
_ = x[actHalfPageUp-55]
|
||||
_ = x[actHalfPageDown-56]
|
||||
_ = x[actOffsetUp-57]
|
||||
_ = x[actOffsetDown-58]
|
||||
_ = x[actJump-59]
|
||||
_ = x[actJumpAccept-60]
|
||||
_ = x[actPrintQuery-61]
|
||||
_ = x[actRefreshPreview-62]
|
||||
_ = x[actReplaceQuery-63]
|
||||
_ = x[actToggleSort-64]
|
||||
_ = x[actShowPreview-65]
|
||||
_ = x[actHidePreview-66]
|
||||
_ = x[actTogglePreview-67]
|
||||
_ = x[actTogglePreviewWrap-68]
|
||||
_ = x[actTransform-69]
|
||||
_ = x[actTransformBorderLabel-70]
|
||||
_ = x[actTransformHeader-71]
|
||||
_ = x[actTransformPreviewLabel-72]
|
||||
_ = x[actTransformPrompt-73]
|
||||
_ = x[actTransformQuery-74]
|
||||
_ = x[actPreview-75]
|
||||
_ = x[actChangePreview-76]
|
||||
_ = x[actChangePreviewWindow-77]
|
||||
_ = x[actPreviewTop-78]
|
||||
_ = x[actPreviewBottom-79]
|
||||
_ = x[actPreviewUp-80]
|
||||
_ = x[actPreviewDown-81]
|
||||
_ = x[actPreviewPageUp-82]
|
||||
_ = x[actPreviewPageDown-83]
|
||||
_ = x[actPreviewHalfPageUp-84]
|
||||
_ = x[actPreviewHalfPageDown-85]
|
||||
_ = x[actPrevHistory-86]
|
||||
_ = x[actPrevSelected-87]
|
||||
_ = x[actPut-88]
|
||||
_ = x[actNextHistory-89]
|
||||
_ = x[actNextSelected-90]
|
||||
_ = x[actExecute-91]
|
||||
_ = x[actExecuteSilent-92]
|
||||
_ = x[actExecuteMulti-93]
|
||||
_ = x[actSigStop-94]
|
||||
_ = x[actFirst-95]
|
||||
_ = x[actLast-96]
|
||||
_ = x[actReload-97]
|
||||
_ = x[actReloadSync-98]
|
||||
_ = x[actDisableSearch-99]
|
||||
_ = x[actEnableSearch-100]
|
||||
_ = x[actSelect-101]
|
||||
_ = x[actDeselect-102]
|
||||
_ = x[actUnbind-103]
|
||||
_ = x[actRebind-104]
|
||||
_ = x[actBecome-105]
|
||||
_ = x[actResponse-106]
|
||||
_ = x[actShowHeader-107]
|
||||
_ = x[actHideHeader-108]
|
||||
}
|
||||
|
||||
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleHeaderactTrackactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactResponseactShowHeaderactHideHeader"
|
||||
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactResponseactShowHeaderactHideHeader"
|
||||
|
||||
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, 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, 640, 655, 670, 687, 694, 699, 708, 719, 730, 743, 758, 769, 782, 789, 802, 815, 832, 847, 860, 874, 888, 904, 924, 936, 959, 977, 1001, 1019, 1036, 1046, 1062, 1084, 1097, 1113, 1125, 1139, 1155, 1173, 1193, 1215, 1229, 1244, 1250, 1264, 1279, 1289, 1305, 1320, 1330, 1338, 1345, 1354, 1367, 1383, 1398, 1407, 1418, 1427, 1436, 1445, 1456, 1469, 1482}
|
||||
|
||||
func (i actionType) String() string {
|
||||
if i < 0 || i >= actionType(len(_actionType_index)-1) {
|
||||
|
||||
148
src/algo/algo.go
148
src/algo/algo.go
@@ -153,6 +153,12 @@ var (
|
||||
bonusBoundaryDelimiter int16 = bonusBoundary + 1
|
||||
|
||||
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
|
||||
@@ -187,6 +193,27 @@ func Init(scheme string) bool {
|
||||
default:
|
||||
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
|
||||
}
|
||||
|
||||
@@ -214,21 +241,6 @@ func alloc32(offset int, slab *util.Slab, size int) (int, []int32) {
|
||||
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 {
|
||||
if unicode.IsLower(char) {
|
||||
return charLower
|
||||
@@ -248,31 +260,36 @@ func charClassOfNonAscii(char rune) charClass {
|
||||
|
||||
func charClassOf(char rune) charClass {
|
||||
if char <= unicode.MaxASCII {
|
||||
return charClassOfAscii(char)
|
||||
return asciiCharClasses[char]
|
||||
}
|
||||
return charClassOfNonAscii(char)
|
||||
}
|
||||
|
||||
func bonusFor(prevClass charClass, class charClass) int16 {
|
||||
if class > charNonWord {
|
||||
if prevClass == charWhite {
|
||||
switch prevClass {
|
||||
case charWhite:
|
||||
// Word boundary after whitespace
|
||||
return bonusBoundaryWhite
|
||||
} else if prevClass == charDelimiter {
|
||||
case charDelimiter:
|
||||
// Word boundary after a delimiter character
|
||||
return bonusBoundaryDelimiter
|
||||
} else if prevClass == charNonWord {
|
||||
case charNonWord:
|
||||
// Word boundary
|
||||
return bonusBoundary
|
||||
}
|
||||
}
|
||||
|
||||
if prevClass == charLower && class == charUpper ||
|
||||
prevClass != charNumber && class == charNumber {
|
||||
// camelCase letter123
|
||||
return bonusCamel123
|
||||
} else if class == charNonWord {
|
||||
}
|
||||
|
||||
switch class {
|
||||
case charNonWord, charDelimiter:
|
||||
return bonusNonWord
|
||||
} else if class == charWhite {
|
||||
case charWhite:
|
||||
return bonusBoundaryWhite
|
||||
}
|
||||
return 0
|
||||
@@ -282,7 +299,7 @@ func bonusAt(input *util.Chars, idx int) int16 {
|
||||
if idx == 0 {
|
||||
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 {
|
||||
@@ -335,30 +352,45 @@ func isAscii(runes []rune) bool {
|
||||
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
|
||||
if !input.IsBytes() {
|
||||
return 0
|
||||
return 0, input.Length()
|
||||
}
|
||||
|
||||
// Not possible
|
||||
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++ {
|
||||
idx = trySkip(input, caseSensitive, byte(pattern[pidx]), idx)
|
||||
b = byte(pattern[pidx])
|
||||
idx = trySkip(input, caseSensitive, b, idx)
|
||||
if idx < 0 {
|
||||
return -1
|
||||
return -1, -1
|
||||
}
|
||||
if pidx == 0 && idx > 0 {
|
||||
// Step back to find the right bonus point
|
||||
firstIdx = idx - 1
|
||||
}
|
||||
lastIdx = 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) {
|
||||
@@ -407,6 +439,9 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
||||
return Result{0, 0, 0}, posArray(withPos, M)
|
||||
}
|
||||
N := input.Length()
|
||||
if M > N {
|
||||
return Result{-1, -1, 0}, nil
|
||||
}
|
||||
|
||||
// Since O(nm) algorithm can be prohibitively expensive for large input,
|
||||
// we fall back to the greedy algorithm.
|
||||
@@ -415,10 +450,12 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
||||
}
|
||||
|
||||
// Phase 1. Optimized search for ASCII string
|
||||
idx := asciiFuzzyIndex(input, pattern, caseSensitive)
|
||||
if idx < 0 {
|
||||
minIdx, maxIdx := asciiFuzzyIndex(input, pattern, caseSensitive)
|
||||
if minIdx < 0 {
|
||||
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
|
||||
offset16 := 0
|
||||
@@ -431,20 +468,19 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
||||
offset32, F := alloc32(offset32, slab, M)
|
||||
// Rune array
|
||||
_, T := alloc32(offset32, slab, N)
|
||||
input.CopyRunes(T)
|
||||
input.CopyRunes(T, minIdx)
|
||||
|
||||
// Phase 2. Calculate bonus for each point
|
||||
maxScore, maxScorePos := int16(0), 0
|
||||
pidx, lastIdx := 0, 0
|
||||
pchar0, pchar, prevH0, prevClass, inGap := pattern[0], pattern[0], int16(0), initialCharClass, false
|
||||
Tsub := T[idx:]
|
||||
H0sub, C0sub, Bsub := H0[idx:][:len(Tsub)], C0[idx:][:len(Tsub)], B[idx:][:len(Tsub)]
|
||||
for off, char := range Tsub {
|
||||
for off, char := range T {
|
||||
var class charClass
|
||||
if char <= unicode.MaxASCII {
|
||||
class = charClassOfAscii(char)
|
||||
class = asciiCharClasses[char]
|
||||
if !caseSensitive && class == charUpper {
|
||||
char += 32
|
||||
T[off] = char
|
||||
}
|
||||
} else {
|
||||
class = charClassOfNonAscii(char)
|
||||
@@ -454,28 +490,28 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
||||
if normalize {
|
||||
char = normalizeRune(char)
|
||||
}
|
||||
T[off] = char
|
||||
}
|
||||
|
||||
Tsub[off] = char
|
||||
bonus := bonusFor(prevClass, class)
|
||||
Bsub[off] = bonus
|
||||
bonus := bonusMatrix[prevClass][class]
|
||||
B[off] = bonus
|
||||
prevClass = class
|
||||
|
||||
if char == pchar {
|
||||
if pidx < M {
|
||||
F[pidx] = int32(idx + off)
|
||||
F[pidx] = int32(off)
|
||||
pidx++
|
||||
pchar = pattern[util.Min(pidx, M-1)]
|
||||
}
|
||||
lastIdx = idx + off
|
||||
lastIdx = off
|
||||
}
|
||||
|
||||
if char == pchar0 {
|
||||
score := scoreMatch + bonus*bonusFirstCharMultiplier
|
||||
H0sub[off] = score
|
||||
C0sub[off] = 1
|
||||
H0[off] = score
|
||||
C0[off] = 1
|
||||
if M == 1 && (forward && score > maxScore || !forward && score >= maxScore) {
|
||||
maxScore, maxScorePos = score, idx+off
|
||||
maxScore, maxScorePos = score, off
|
||||
if forward && bonus >= bonusBoundary {
|
||||
break
|
||||
}
|
||||
@@ -483,24 +519,24 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
||||
inGap = false
|
||||
} else {
|
||||
if inGap {
|
||||
H0sub[off] = util.Max16(prevH0+scoreGapExtension, 0)
|
||||
H0[off] = util.Max16(prevH0+scoreGapExtension, 0)
|
||||
} else {
|
||||
H0sub[off] = util.Max16(prevH0+scoreGapStart, 0)
|
||||
H0[off] = util.Max16(prevH0+scoreGapStart, 0)
|
||||
}
|
||||
C0sub[off] = 0
|
||||
C0[off] = 0
|
||||
inGap = true
|
||||
}
|
||||
prevH0 = H0sub[off]
|
||||
prevH0 = H0[off]
|
||||
}
|
||||
if pidx != M {
|
||||
return Result{-1, -1, 0}, nil
|
||||
}
|
||||
if M == 1 {
|
||||
result := Result{maxScorePos, maxScorePos + 1, int(maxScore)}
|
||||
result := Result{minIdx + maxScorePos, minIdx + maxScorePos + 1, int(maxScore)}
|
||||
if !withPos {
|
||||
return result, nil
|
||||
}
|
||||
pos := []int{maxScorePos}
|
||||
pos := []int{minIdx + maxScorePos}
|
||||
return result, &pos
|
||||
}
|
||||
|
||||
@@ -597,7 +633,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
||||
}
|
||||
|
||||
if s > s1 && (s > s2 || s == s2 && preferMatch) {
|
||||
*pos = append(*pos, j)
|
||||
*pos = append(*pos, j+minIdx)
|
||||
if i == 0 {
|
||||
break
|
||||
}
|
||||
@@ -610,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.
|
||||
// However finding the accurate offset requires backtracking, and we don't
|
||||
// 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
|
||||
@@ -640,7 +676,7 @@ func calculateScore(caseSensitive bool, normalize bool, text *util.Chars, patter
|
||||
*pos = append(*pos, idx)
|
||||
}
|
||||
score += scoreMatch
|
||||
bonus := bonusFor(prevClass, class)
|
||||
bonus := bonusMatrix[prevClass][class]
|
||||
if consecutive == 0 {
|
||||
firstBonus = bonus
|
||||
} else {
|
||||
@@ -678,7 +714,8 @@ func FuzzyMatchV1(caseSensitive bool, normalize bool, forward bool, text *util.C
|
||||
if len(pattern) == 0 {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -772,7 +809,8 @@ func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *uti
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,10 @@ import (
|
||||
"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) {
|
||||
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
|
||||
// integers and does not allocate on error.
|
||||
code := 0
|
||||
for _, ch := range []byte(s) {
|
||||
for _, ch := range sbytes(s) {
|
||||
ch -= '0'
|
||||
if ch > 9 {
|
||||
return -1, delimiter, remaining
|
||||
|
||||
@@ -2,7 +2,6 @@ package fzf
|
||||
|
||||
import (
|
||||
"math"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/junegunn/fzf/src/util"
|
||||
@@ -15,6 +14,7 @@ const (
|
||||
|
||||
// Reader
|
||||
readerBufferSize = 64 * 1024
|
||||
readerSlabSize = 128 * 1024
|
||||
readerPollIntervalMin = 10 * time.Millisecond
|
||||
readerPollIntervalStep = 5 * time.Millisecond
|
||||
readerPollIntervalMax = 50 * time.Millisecond
|
||||
@@ -54,16 +54,6 @@ const (
|
||||
defaultJumpLabels string = "asdfghjklqwertyuiopzxcvbnm1234567890ASDFGHJKLQWERTYUIOPZXCVBNM`~;:,<.>/?'\"!@#$%^&*()[{]}-_=+"
|
||||
)
|
||||
|
||||
var defaultCommand string
|
||||
|
||||
func init() {
|
||||
if !util.IsWindows() {
|
||||
defaultCommand = `set -o pipefail; command find -L . -mindepth 1 \( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-`
|
||||
} else if os.Getenv("TERM") == "cygwin" {
|
||||
defaultCommand = `sh -c "command find -L . -mindepth 1 -path '*/.*' -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-"`
|
||||
}
|
||||
}
|
||||
|
||||
// fzf events
|
||||
const (
|
||||
EvtReadNew util.EventType = iota
|
||||
|
||||
74
src/core.go
74
src/core.go
@@ -3,8 +3,9 @@ package fzf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/junegunn/fzf/src/util"
|
||||
)
|
||||
@@ -18,8 +19,18 @@ Matcher -> EvtSearchFin -> Terminal (update list)
|
||||
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
|
||||
func Run(opts *Options, version string, revision string) {
|
||||
defer util.RunAtExitFuncs()
|
||||
|
||||
sort := opts.Sort > 0
|
||||
sortCriteria = opts.Criteria
|
||||
|
||||
@@ -29,7 +40,7 @@ func Run(opts *Options, version string, revision string) {
|
||||
} else {
|
||||
fmt.Println(version)
|
||||
}
|
||||
os.Exit(exitOk)
|
||||
util.Exit(exitOk)
|
||||
}
|
||||
|
||||
// Event channel
|
||||
@@ -45,16 +56,16 @@ func Run(opts *Options, version string, revision string) {
|
||||
if opts.Theme.Colored {
|
||||
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
||||
prevLineAnsiState = lineAnsiState
|
||||
trimmed, offsets, newState := extractColor(string(data), lineAnsiState, nil)
|
||||
trimmed, offsets, newState := extractColor(ustring(data), lineAnsiState, nil)
|
||||
lineAnsiState = newState
|
||||
return util.ToChars([]byte(trimmed)), offsets
|
||||
return util.ToChars(sbytes(trimmed)), offsets
|
||||
}
|
||||
} else {
|
||||
// When color is disabled but ansi option is given,
|
||||
// we simply strip out ANSI codes from the input
|
||||
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
||||
trimmed, _, _ := extractColor(string(data), nil, nil)
|
||||
return util.ToChars([]byte(trimmed)), nil
|
||||
trimmed, _, _ := extractColor(ustring(data), nil, nil)
|
||||
return util.ToChars(sbytes(trimmed)), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,7 +77,7 @@ func Run(opts *Options, version string, revision string) {
|
||||
if len(opts.WithNth) == 0 {
|
||||
chunkList = NewChunkList(func(item *Item, data []byte) bool {
|
||||
if len(header) < opts.HeaderLines {
|
||||
header = append(header, string(data))
|
||||
header = append(header, ustring(data))
|
||||
eventBox.Set(EvtHeader, header)
|
||||
return false
|
||||
}
|
||||
@@ -77,7 +88,7 @@ func Run(opts *Options, version string, revision string) {
|
||||
})
|
||||
} else {
|
||||
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 {
|
||||
var ansiState *ansiState
|
||||
if prevLineAnsiState != nil {
|
||||
@@ -101,7 +112,7 @@ func Run(opts *Options, version string, revision string) {
|
||||
eventBox.Set(EvtHeader, header)
|
||||
return false
|
||||
}
|
||||
item.text, item.colors = ansiProcessor([]byte(transformed))
|
||||
item.text, item.colors = ansiProcessor(sbytes(transformed))
|
||||
item.text.TrimTrailingWhitespaces()
|
||||
item.text.Index = itemIndex
|
||||
item.origText = &data
|
||||
@@ -117,7 +128,7 @@ func Run(opts *Options, version string, revision string) {
|
||||
reader = NewReader(func(data []byte) bool {
|
||||
return chunkList.Push(data)
|
||||
}, eventBox, opts.ReadZero, opts.Filter == nil)
|
||||
go reader.ReadSource()
|
||||
go reader.ReadSource(opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
|
||||
}
|
||||
|
||||
// Matcher
|
||||
@@ -154,18 +165,21 @@ func Run(opts *Options, version string, revision string) {
|
||||
found := false
|
||||
if streamingFilter {
|
||||
slab := util.MakeSlab(slab16Size, slab32Size)
|
||||
mutex := sync.Mutex{}
|
||||
reader := NewReader(
|
||||
func(runes []byte) bool {
|
||||
item := Item{}
|
||||
if chunkList.trans(&item, runes) {
|
||||
mutex.Lock()
|
||||
if result, _, _ := pattern.MatchItem(&item, false, slab); result != nil {
|
||||
opts.Printer(item.text.ToString())
|
||||
found = true
|
||||
}
|
||||
mutex.Unlock()
|
||||
}
|
||||
return false
|
||||
}, eventBox, opts.ReadZero, false)
|
||||
reader.ReadSource()
|
||||
reader.ReadSource(opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
|
||||
} else {
|
||||
eventBox.Unwatch(EvtReadNew)
|
||||
eventBox.WaitFor(EvtReadFin)
|
||||
@@ -180,9 +194,9 @@ func Run(opts *Options, version string, revision string) {
|
||||
}
|
||||
}
|
||||
if found {
|
||||
os.Exit(exitOk)
|
||||
util.Exit(exitOk)
|
||||
}
|
||||
os.Exit(exitNoMatch)
|
||||
util.Exit(exitNoMatch)
|
||||
}
|
||||
|
||||
// Synchronous search
|
||||
@@ -213,6 +227,7 @@ func Run(opts *Options, version string, revision string) {
|
||||
reading := true
|
||||
ticks := 0
|
||||
var nextCommand *string
|
||||
var nextEnviron []string
|
||||
eventBox.Watch(EvtReadNew)
|
||||
total := 0
|
||||
query := []rune{}
|
||||
@@ -232,23 +247,20 @@ func Run(opts *Options, version string, revision string) {
|
||||
useSnapshot := false
|
||||
var snapshot []*Chunk
|
||||
var count int
|
||||
restart := func(command string) {
|
||||
restart := func(command string, environ []string) {
|
||||
reading = true
|
||||
chunkList.Clear()
|
||||
itemIndex = 0
|
||||
inputRevision++
|
||||
header = make([]string, 0, opts.HeaderLines)
|
||||
go reader.restart(command)
|
||||
go reader.restart(command, environ)
|
||||
}
|
||||
for {
|
||||
delay := true
|
||||
ticks++
|
||||
input := func() []rune {
|
||||
reloaded := snapshotRevision != inputRevision
|
||||
paused, input := terminal.Input()
|
||||
if reloaded && paused {
|
||||
query = []rune{}
|
||||
} else if !paused {
|
||||
if !paused {
|
||||
query = input
|
||||
}
|
||||
return query
|
||||
@@ -263,11 +275,12 @@ func Run(opts *Options, version string, revision string) {
|
||||
if reading {
|
||||
reader.terminate()
|
||||
}
|
||||
os.Exit(value.(int))
|
||||
util.Exit(value.(int))
|
||||
case EvtReadNew, EvtReadFin:
|
||||
if evt == EvtReadFin && nextCommand != nil {
|
||||
restart(*nextCommand)
|
||||
restart(*nextCommand, nextEnviron)
|
||||
nextCommand = nil
|
||||
nextEnviron = nil
|
||||
break
|
||||
} else {
|
||||
reading = reading && evt == EvtReadNew
|
||||
@@ -276,6 +289,9 @@ func Run(opts *Options, version string, revision string) {
|
||||
useSnapshot = false
|
||||
}
|
||||
if !useSnapshot {
|
||||
if snapshotRevision != inputRevision {
|
||||
query = []rune{}
|
||||
}
|
||||
snapshot, count = chunkList.Snapshot()
|
||||
snapshotRevision = inputRevision
|
||||
}
|
||||
@@ -292,11 +308,13 @@ func Run(opts *Options, version string, revision string) {
|
||||
|
||||
case EvtSearchNew:
|
||||
var command *string
|
||||
var environ []string
|
||||
var changed bool
|
||||
switch val := value.(type) {
|
||||
case searchRequest:
|
||||
sort = val.sort
|
||||
command = val.command
|
||||
environ = val.environ
|
||||
changed = val.changed
|
||||
if command != nil {
|
||||
useSnapshot = val.sync
|
||||
@@ -306,18 +324,22 @@ func Run(opts *Options, version string, revision string) {
|
||||
if reading {
|
||||
reader.terminate()
|
||||
nextCommand = command
|
||||
nextEnviron = environ
|
||||
} else {
|
||||
restart(*command)
|
||||
restart(*command, environ)
|
||||
}
|
||||
}
|
||||
if !changed {
|
||||
break
|
||||
}
|
||||
if !useSnapshot {
|
||||
newSnapshot, _ := chunkList.Snapshot()
|
||||
newSnapshot, newCount := chunkList.Snapshot()
|
||||
// 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
|
||||
if command == nil || len(newSnapshot) > 0 {
|
||||
if command == nil || newCount > 0 {
|
||||
if snapshotRevision != inputRevision {
|
||||
query = []rune{}
|
||||
}
|
||||
snapshot = newSnapshot
|
||||
snapshotRevision = inputRevision
|
||||
}
|
||||
@@ -355,9 +377,9 @@ func Run(opts *Options, version string, revision string) {
|
||||
opts.Printer(val.Get(i).item.AsString(opts.Ansi))
|
||||
}
|
||||
if count > 0 {
|
||||
os.Exit(exitOk)
|
||||
util.Exit(exitOk)
|
||||
}
|
||||
os.Exit(exitNoMatch)
|
||||
util.Exit(exitNoMatch)
|
||||
}
|
||||
determine(val.final)
|
||||
}
|
||||
|
||||
242
src/options.go
242
src/options.go
@@ -52,7 +52,7 @@ const usage = `usage: fzf [options]
|
||||
--hscroll-off=COLS Number of screen columns to keep to the right of the
|
||||
highlighted substring (default: 10)
|
||||
--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
|
||||
--height=[~]HEIGHT[%] Display fzf window below the cursor with the given
|
||||
@@ -75,7 +75,7 @@ const usage = `usage: fzf [options]
|
||||
--margin=MARGIN Screen margin (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
||||
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
||||
--info=STYLE Finder info style
|
||||
[default|right|hidden|inline[:SEPARATOR]|inline-right]
|
||||
[default|right|hidden|inline[-right][:PREFIX]]
|
||||
--separator=STR String to form horizontal separator on info line
|
||||
--no-separator Hide info line separator
|
||||
--scrollbar[=C1[C2]] Scrollbar character(s) (each for main and preview window)
|
||||
@@ -124,15 +124,26 @@ const usage = `usage: fzf [options]
|
||||
(To allow remote process execution, use --listen-unsafe)
|
||||
--version Display version information and exit
|
||||
|
||||
Directory traversal (Only used when $FZF_DEFAULT_COMMAND is not set)
|
||||
--walker=OPTS [file][,dir][,follow][,hidden] (default: file,follow,hidden)
|
||||
--walker-root=DIR Root directory from which to start walker (default: .)
|
||||
--walker-skip=DIRS Comma-separated list of directory names to skip
|
||||
(default: .git,node_modules)
|
||||
|
||||
Shell integration
|
||||
--bash Print script to set up Bash shell integration
|
||||
--zsh Print script to set up Zsh shell integration
|
||||
--fish Print script to set up Fish shell integration
|
||||
|
||||
Environment variables
|
||||
FZF_DEFAULT_COMMAND Default command to use when input is tty
|
||||
FZF_DEFAULT_OPTS Default options
|
||||
(e.g. '--layout=reverse --inline-info')
|
||||
FZF_DEFAULT_OPTS Default options (e.g. '--layout=reverse --info=inline')
|
||||
FZF_DEFAULT_OPTS_FILE Location of the file to read default options from
|
||||
FZF_API_KEY X-API-Key header for HTTP server (--listen)
|
||||
|
||||
`
|
||||
|
||||
const defaultInfoSep = " < "
|
||||
const defaultInfoPrefix = " < "
|
||||
|
||||
// Case denotes case-sensitivity of search
|
||||
type Case int
|
||||
@@ -206,10 +217,6 @@ const (
|
||||
infoHidden
|
||||
)
|
||||
|
||||
func (s infoStyle) noExtraLine() bool {
|
||||
return s == infoInline || s == infoInlineRight || s == infoHidden
|
||||
}
|
||||
|
||||
type labelOpts struct {
|
||||
label string
|
||||
column int
|
||||
@@ -274,8 +281,18 @@ func firstLine(s string) string {
|
||||
return strings.SplitN(s, "\n", 2)[0]
|
||||
}
|
||||
|
||||
type walkerOpts struct {
|
||||
file bool
|
||||
dir bool
|
||||
hidden bool
|
||||
follow bool
|
||||
}
|
||||
|
||||
// Options stores the values of command-line options
|
||||
type Options struct {
|
||||
Bash bool
|
||||
Zsh bool
|
||||
Fish bool
|
||||
Fuzzy bool
|
||||
FuzzyAlgo algo.Algo
|
||||
Scheme string
|
||||
@@ -306,7 +323,7 @@ type Options struct {
|
||||
ScrollOff int
|
||||
FileWord bool
|
||||
InfoStyle infoStyle
|
||||
InfoSep string
|
||||
InfoPrefix string
|
||||
Separator *string
|
||||
JumpLabels string
|
||||
Prompt string
|
||||
@@ -342,7 +359,24 @@ type Options struct {
|
||||
ListenAddr *listenAddress
|
||||
Unsafe bool
|
||||
ClearOnExit bool
|
||||
WalkerOpts walkerOpts
|
||||
WalkerRoot string
|
||||
WalkerSkip []string
|
||||
Version bool
|
||||
CPUProfile string
|
||||
MEMProfile string
|
||||
BlockProfile string
|
||||
MutexProfile string
|
||||
}
|
||||
|
||||
func filterNonEmpty(input []string) []string {
|
||||
output := make([]string, 0, len(input))
|
||||
for _, str := range input {
|
||||
if len(str) > 0 {
|
||||
output = append(output, str)
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
func defaultPreviewOpts(command string) previewOpts {
|
||||
@@ -351,6 +385,9 @@ func defaultPreviewOpts(command string) previewOpts {
|
||||
|
||||
func defaultOptions() *Options {
|
||||
return &Options{
|
||||
Bash: false,
|
||||
Zsh: false,
|
||||
Fish: false,
|
||||
Fuzzy: true,
|
||||
FuzzyAlgo: algo.FuzzyMatchV2,
|
||||
Scheme: "default",
|
||||
@@ -413,17 +450,22 @@ func defaultOptions() *Options {
|
||||
PreviewLabel: labelOpts{},
|
||||
Unsafe: false,
|
||||
ClearOnExit: true,
|
||||
WalkerOpts: walkerOpts{file: true, hidden: true, follow: true},
|
||||
WalkerRoot: ".",
|
||||
WalkerSkip: []string{".git", "node_modules"},
|
||||
Version: false}
|
||||
}
|
||||
|
||||
func help(code int) {
|
||||
os.Stdout.WriteString(usage)
|
||||
os.Exit(code)
|
||||
util.Exit(code)
|
||||
}
|
||||
|
||||
var errorContext = ""
|
||||
|
||||
func errorExit(msg string) {
|
||||
os.Stderr.WriteString(msg + "\n")
|
||||
os.Exit(exitError)
|
||||
os.Stderr.WriteString(errorContext + msg + "\n")
|
||||
util.Exit(exitError)
|
||||
}
|
||||
|
||||
func optString(arg string, prefixes ...string) (bool, string) {
|
||||
@@ -628,8 +670,8 @@ func parseKeyChordsImpl(str string, message string, exit func(string)) map[tui.E
|
||||
add(tui.CtrlM)
|
||||
case "space":
|
||||
chords[tui.Key(' ')] = key
|
||||
case "bspace", "bs":
|
||||
add(tui.BSpace)
|
||||
case "backspace", "bspace", "bs":
|
||||
add(tui.Backspace)
|
||||
case "ctrl-space":
|
||||
add(tui.CtrlSpace)
|
||||
case "ctrl-delete":
|
||||
@@ -660,12 +702,16 @@ func parseKeyChordsImpl(str string, message string, exit func(string)) map[tui.E
|
||||
add(tui.One)
|
||||
case "zero":
|
||||
add(tui.Zero)
|
||||
case "jump":
|
||||
add(tui.Jump)
|
||||
case "jump-cancel":
|
||||
add(tui.JumpCancel)
|
||||
case "alt-enter", "alt-return":
|
||||
chords[tui.CtrlAltKey('m')] = key
|
||||
case "alt-space":
|
||||
chords[tui.AltKey(' ')] = key
|
||||
case "alt-bs", "alt-bspace":
|
||||
add(tui.AltBS)
|
||||
case "alt-bs", "alt-bspace", "alt-backspace":
|
||||
add(tui.AltBackspace)
|
||||
case "alt-up":
|
||||
add(tui.AltUp)
|
||||
case "alt-down":
|
||||
@@ -677,11 +723,11 @@ func parseKeyChordsImpl(str string, message string, exit func(string)) map[tui.E
|
||||
case "tab":
|
||||
add(tui.Tab)
|
||||
case "btab", "shift-tab":
|
||||
add(tui.BTab)
|
||||
add(tui.ShiftTab)
|
||||
case "esc":
|
||||
add(tui.ESC)
|
||||
case "del":
|
||||
add(tui.Del)
|
||||
add(tui.Esc)
|
||||
case "delete", "del":
|
||||
add(tui.Delete)
|
||||
case "home":
|
||||
add(tui.Home)
|
||||
case "end":
|
||||
@@ -689,27 +735,27 @@ func parseKeyChordsImpl(str string, message string, exit func(string)) map[tui.E
|
||||
case "insert":
|
||||
add(tui.Insert)
|
||||
case "pgup", "page-up":
|
||||
add(tui.PgUp)
|
||||
add(tui.PageUp)
|
||||
case "pgdn", "page-down":
|
||||
add(tui.PgDn)
|
||||
add(tui.PageDown)
|
||||
case "alt-shift-up", "shift-alt-up":
|
||||
add(tui.AltSUp)
|
||||
add(tui.AltShiftUp)
|
||||
case "alt-shift-down", "shift-alt-down":
|
||||
add(tui.AltSDown)
|
||||
add(tui.AltShiftDown)
|
||||
case "alt-shift-left", "shift-alt-left":
|
||||
add(tui.AltSLeft)
|
||||
add(tui.AltShiftLeft)
|
||||
case "alt-shift-right", "shift-alt-right":
|
||||
add(tui.AltSRight)
|
||||
add(tui.AltShiftRight)
|
||||
case "shift-up":
|
||||
add(tui.SUp)
|
||||
add(tui.ShiftUp)
|
||||
case "shift-down":
|
||||
add(tui.SDown)
|
||||
add(tui.ShiftDown)
|
||||
case "shift-left":
|
||||
add(tui.SLeft)
|
||||
add(tui.ShiftLeft)
|
||||
case "shift-right":
|
||||
add(tui.SRight)
|
||||
add(tui.ShiftRight)
|
||||
case "shift-delete":
|
||||
add(tui.SDelete)
|
||||
add(tui.ShiftDelete)
|
||||
case "left-click":
|
||||
add(tui.LeftClick)
|
||||
case "right-click":
|
||||
@@ -964,6 +1010,30 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
|
||||
return theme
|
||||
}
|
||||
|
||||
func parseWalkerOpts(str string) walkerOpts {
|
||||
opts := walkerOpts{}
|
||||
for _, str := range strings.Split(strings.ToLower(str), ",") {
|
||||
switch str {
|
||||
case "file":
|
||||
opts.file = true
|
||||
case "dir":
|
||||
opts.dir = true
|
||||
case "hidden":
|
||||
opts.hidden = true
|
||||
case "follow":
|
||||
opts.follow = true
|
||||
case "":
|
||||
// Ignored
|
||||
default:
|
||||
errorExit("invalid walker option: " + str)
|
||||
}
|
||||
}
|
||||
if !opts.file && !opts.dir {
|
||||
errorExit("at least one of 'file' or 'dir' should be specified")
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
var (
|
||||
executeRegexp *regexp.Regexp
|
||||
splitRegexp *regexp.Regexp
|
||||
@@ -1145,14 +1215,18 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
||||
appendAction(actToggleSearch)
|
||||
case "toggle-track":
|
||||
appendAction(actToggleTrack)
|
||||
case "toggle-track-current":
|
||||
appendAction(actToggleTrackCurrent)
|
||||
case "toggle-header":
|
||||
appendAction(actToggleHeader)
|
||||
case "show-header":
|
||||
appendAction(actShowHeader)
|
||||
case "hide-header":
|
||||
appendAction(actHideHeader)
|
||||
case "track":
|
||||
appendAction(actTrack)
|
||||
case "track", "track-current":
|
||||
appendAction(actTrackCurrent)
|
||||
case "untrack-current":
|
||||
appendAction(actUntrackCurrent)
|
||||
case "select":
|
||||
appendAction(actSelect)
|
||||
case "select-all":
|
||||
@@ -1436,17 +1510,24 @@ func parseInfoStyle(str string) (infoStyle, string) {
|
||||
case "right":
|
||||
return infoRight, ""
|
||||
case "inline":
|
||||
return infoInline, defaultInfoSep
|
||||
return infoInline, defaultInfoPrefix
|
||||
case "inline-right":
|
||||
return infoInlineRight, ""
|
||||
case "hidden":
|
||||
return infoHidden, ""
|
||||
default:
|
||||
prefix := "inline:"
|
||||
if strings.HasPrefix(str, prefix) {
|
||||
return infoInline, strings.ReplaceAll(str[len(prefix):], "\n", " ")
|
||||
type infoSpec struct {
|
||||
name string
|
||||
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, ""
|
||||
}
|
||||
@@ -1600,6 +1681,21 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
for i := 0; i < len(allArgs); i++ {
|
||||
arg := allArgs[i]
|
||||
switch arg {
|
||||
case "--bash":
|
||||
opts.Bash = true
|
||||
if opts.Zsh || opts.Fish {
|
||||
errorExit("cannot specify --bash with --zsh or --fish")
|
||||
}
|
||||
case "--zsh":
|
||||
opts.Zsh = true
|
||||
if opts.Bash || opts.Fish {
|
||||
errorExit("cannot specify --zsh with --bash or --fish")
|
||||
}
|
||||
case "--fish":
|
||||
opts.Fish = true
|
||||
if opts.Bash || opts.Zsh {
|
||||
errorExit("cannot specify --fish with --bash or --zsh")
|
||||
}
|
||||
case "-h", "--help":
|
||||
help(exitOk)
|
||||
case "-x", "--extended":
|
||||
@@ -1722,13 +1818,13 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
case "--no-filepath-word":
|
||||
opts.FileWord = false
|
||||
case "--info":
|
||||
opts.InfoStyle, opts.InfoSep = parseInfoStyle(
|
||||
opts.InfoStyle, opts.InfoPrefix = parseInfoStyle(
|
||||
nextString(allArgs, &i, "info style required"))
|
||||
case "--no-info":
|
||||
opts.InfoStyle = infoHidden
|
||||
case "--inline-info":
|
||||
opts.InfoStyle = infoInline
|
||||
opts.InfoSep = defaultInfoSep
|
||||
opts.InfoPrefix = defaultInfoPrefix
|
||||
case "--no-inline-info":
|
||||
opts.InfoStyle = infoDefault
|
||||
case "--separator":
|
||||
@@ -1864,7 +1960,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
addr := defaultListenAddr
|
||||
if given {
|
||||
var err error
|
||||
err, addr = parseListenAddress(str)
|
||||
addr, err = parseListenAddress(str)
|
||||
if err != nil {
|
||||
errorExit(err.Error())
|
||||
}
|
||||
@@ -1878,8 +1974,22 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
opts.ClearOnExit = true
|
||||
case "--no-clear":
|
||||
opts.ClearOnExit = false
|
||||
case "--walker":
|
||||
opts.WalkerOpts = parseWalkerOpts(nextString(allArgs, &i, "walker options required [file][,dir][,follow][,hidden]"))
|
||||
case "--walker-root":
|
||||
opts.WalkerRoot = nextString(allArgs, &i, "directory required")
|
||||
case "--walker-skip":
|
||||
opts.WalkerSkip = filterNonEmpty(strings.Split(nextString(allArgs, &i, "directory names to ignore required"), ","))
|
||||
case "--version":
|
||||
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 "--":
|
||||
// Ignored
|
||||
default:
|
||||
@@ -1924,7 +2034,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
} else if match, value := optString(arg, "--layout="); match {
|
||||
opts.Layout = parseLayout(value)
|
||||
} 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 {
|
||||
opts.Separator = &value
|
||||
} else if match, value := optString(arg, "--scrollbar="); match {
|
||||
@@ -1962,19 +2072,25 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
} else if match, value := optString(arg, "--tabstop="); match {
|
||||
opts.Tabstop = atoi(value)
|
||||
} else if match, value := optString(arg, "--listen="); match {
|
||||
err, addr := parseListenAddress(value)
|
||||
addr, err := parseListenAddress(value)
|
||||
if err != nil {
|
||||
errorExit(err.Error())
|
||||
}
|
||||
opts.ListenAddr = &addr
|
||||
opts.Unsafe = false
|
||||
} else if match, value := optString(arg, "--listen-unsafe="); match {
|
||||
err, addr := parseListenAddress(value)
|
||||
addr, err := parseListenAddress(value)
|
||||
if err != nil {
|
||||
errorExit(err.Error())
|
||||
}
|
||||
opts.ListenAddr = &addr
|
||||
opts.Unsafe = true
|
||||
} else if match, value := optString(arg, "--walker="); match {
|
||||
opts.WalkerOpts = parseWalkerOpts(value)
|
||||
} else if match, value := optString(arg, "--walker-root="); match {
|
||||
opts.WalkerRoot = value
|
||||
} else if match, value := optString(arg, "--walker-skip="); match {
|
||||
opts.WalkerSkip = filterNonEmpty(strings.Split(value, ","))
|
||||
} else if match, value := optString(arg, "--hscroll-off="); match {
|
||||
opts.HscrollOff = atoi(value)
|
||||
} else if match, value := optString(arg, "--scroll-off="); match {
|
||||
@@ -2143,10 +2259,8 @@ func postProcessOptions(opts *Options) {
|
||||
theme.Spinner = boldify(theme.Spinner)
|
||||
}
|
||||
|
||||
if opts.Scheme != "default" {
|
||||
processScheme(opts)
|
||||
}
|
||||
}
|
||||
|
||||
func expectsArbitraryString(opt string) bool {
|
||||
switch opt {
|
||||
@@ -2167,15 +2281,43 @@ func ParseOptions() *Options {
|
||||
}
|
||||
}
|
||||
|
||||
// Options from Env var
|
||||
words, _ := shellwords.Parse(os.Getenv("FZF_DEFAULT_OPTS"))
|
||||
// 1. Options from $FZF_DEFAULT_OPTS_FILE
|
||||
if path := os.Getenv("FZF_DEFAULT_OPTS_FILE"); path != "" {
|
||||
bytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
errorContext = "$FZF_DEFAULT_OPTS_FILE: "
|
||||
errorExit(err.Error())
|
||||
}
|
||||
|
||||
words, parseErr := shellwords.Parse(string(bytes))
|
||||
if parseErr != nil {
|
||||
errorContext = path + ": "
|
||||
errorExit(parseErr.Error())
|
||||
}
|
||||
if len(words) > 0 {
|
||||
parseOptions(opts, words)
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Options from $FZF_DEFAULT_OPTS string
|
||||
words, parseErr := shellwords.Parse(os.Getenv("FZF_DEFAULT_OPTS"))
|
||||
errorContext = "$FZF_DEFAULT_OPTS: "
|
||||
if parseErr != nil {
|
||||
errorExit(parseErr.Error())
|
||||
}
|
||||
if len(words) > 0 {
|
||||
parseOptions(opts, words)
|
||||
}
|
||||
|
||||
// Options from command-line arguments
|
||||
// 3. Options from command-line arguments
|
||||
errorContext = ""
|
||||
parseOptions(opts, os.Args[1:])
|
||||
|
||||
if err := opts.initProfiling(); err != nil {
|
||||
errorExit("failed to start pprof profiles: " + err.Error())
|
||||
}
|
||||
|
||||
postProcessOptions(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")
|
||||
checkEvent(tui.Key(' '), "space")
|
||||
check(tui.Tab, "tab")
|
||||
check(tui.BTab, "btab")
|
||||
check(tui.ESC, "esc")
|
||||
check(tui.ShiftTab, "btab")
|
||||
check(tui.Esc, "esc")
|
||||
check(tui.Up, "up")
|
||||
check(tui.Down, "down")
|
||||
check(tui.Left, "left")
|
||||
@@ -182,16 +182,16 @@ func TestParseKeys(t *testing.T) {
|
||||
t.Error(11)
|
||||
}
|
||||
check(tui.Tab, "Ctrl-I")
|
||||
check(tui.PgUp, "page-up")
|
||||
check(tui.PgDn, "Page-Down")
|
||||
check(tui.PageUp, "page-up")
|
||||
check(tui.PageDown, "Page-Down")
|
||||
check(tui.Home, "Home")
|
||||
check(tui.End, "End")
|
||||
check(tui.AltBS, "Alt-BSpace")
|
||||
check(tui.SLeft, "shift-left")
|
||||
check(tui.SRight, "shift-right")
|
||||
check(tui.BTab, "shift-tab")
|
||||
check(tui.AltBackspace, "Alt-BSpace")
|
||||
check(tui.ShiftLeft, "shift-left")
|
||||
check(tui.ShiftRight, "shift-right")
|
||||
check(tui.ShiftTab, "shift-tab")
|
||||
check(tui.CtrlM, "Enter")
|
||||
check(tui.BSpace, "bspace")
|
||||
check(tui.Backspace, "bspace")
|
||||
}
|
||||
|
||||
func TestParseKeysWithComma(t *testing.T) {
|
||||
|
||||
@@ -209,11 +209,10 @@ func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet
|
||||
// Flip exactness
|
||||
if fuzzy && !inv {
|
||||
typ = termExact
|
||||
text = text[1:]
|
||||
} else {
|
||||
typ = termFuzzy
|
||||
text = text[1:]
|
||||
}
|
||||
text = text[1:]
|
||||
} else if strings.HasPrefix(text, "^") {
|
||||
if typ == termSuffix {
|
||||
typ = termEqual
|
||||
|
||||
166
src/reader.go
166
src/reader.go
@@ -1,19 +1,18 @@
|
||||
package fzf
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/charlievieth/fastwalk"
|
||||
"github.com/junegunn/fzf/src/util"
|
||||
"github.com/saracen/walker"
|
||||
)
|
||||
|
||||
// Reader reads from command or standard input
|
||||
@@ -77,48 +76,33 @@ func (r *Reader) fin(success bool) {
|
||||
|
||||
func (r *Reader) terminate() {
|
||||
r.mutex.Lock()
|
||||
defer func() { r.mutex.Unlock() }()
|
||||
|
||||
r.killed = true
|
||||
if r.exec != nil && r.exec.Process != nil {
|
||||
util.KillCommand(r.exec)
|
||||
} else if defaultCommand != "" {
|
||||
} else {
|
||||
os.Stdin.Close()
|
||||
}
|
||||
r.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (r *Reader) restart(command string) {
|
||||
func (r *Reader) restart(command string, environ []string) {
|
||||
r.event = int32(EvtReady)
|
||||
r.startEventPoller()
|
||||
success := r.readFromCommand(nil, command)
|
||||
success := r.readFromCommand(command, environ)
|
||||
r.fin(success)
|
||||
}
|
||||
|
||||
// ReadSource reads data from the default command or from standard input
|
||||
func (r *Reader) ReadSource() {
|
||||
func (r *Reader) ReadSource(root string, opts walkerOpts, ignores []string) {
|
||||
r.startEventPoller()
|
||||
var success bool
|
||||
if util.IsTty() {
|
||||
// The default command for *nix requires a shell that supports "pipefail"
|
||||
// https://unix.stackexchange.com/a/654932/62171
|
||||
shell := "bash"
|
||||
currentShell := os.Getenv("SHELL")
|
||||
currentShellName := path.Base(currentShell)
|
||||
for _, shellName := range []string{"bash", "zsh", "ksh", "ash", "hush", "mksh", "yash"} {
|
||||
if currentShellName == shellName {
|
||||
shell = currentShell
|
||||
break
|
||||
}
|
||||
}
|
||||
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
||||
if len(cmd) == 0 {
|
||||
if defaultCommand != "" {
|
||||
success = r.readFromCommand(&shell, defaultCommand)
|
||||
success = r.readFiles(root, opts, ignores)
|
||||
} else {
|
||||
success = r.readFiles()
|
||||
}
|
||||
} else {
|
||||
success = r.readFromCommand(nil, cmd)
|
||||
// We can't export FZF_* environment variables to the default command
|
||||
success = r.readFromCommand(cmd, nil)
|
||||
}
|
||||
} else {
|
||||
success = r.readFromStdin()
|
||||
@@ -127,33 +111,91 @@ func (r *Reader) ReadSource() {
|
||||
}
|
||||
|
||||
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')
|
||||
trimCR := util.IsWindows()
|
||||
if r.delimNil {
|
||||
delim = '\000'
|
||||
trimCR = false
|
||||
}
|
||||
reader := bufio.NewReaderSize(src, readerBufferSize)
|
||||
|
||||
slab := make([]byte, readerSlabSize)
|
||||
leftover := []byte{}
|
||||
var err error
|
||||
for {
|
||||
// ReadBytes returns err != nil if and only if the returned data does not
|
||||
// end in delim.
|
||||
bytea, err := reader.ReadBytes(delim)
|
||||
byteaLen := len(bytea)
|
||||
if byteaLen > 0 {
|
||||
if err == nil {
|
||||
// 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 {
|
||||
n := 0
|
||||
scope := slab[:util.Min(len(slab), readerBufferSize)]
|
||||
for i := 0; i < 100; i++ {
|
||||
n, err = src.Read(scope)
|
||||
if n > 0 || err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// We're not making any progress after 100 tries. Stop.
|
||||
if n == 0 && err == nil {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Reader) readFromStdin() bool {
|
||||
@@ -161,16 +203,28 @@ func (r *Reader) readFromStdin() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *Reader) readFiles() bool {
|
||||
func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool {
|
||||
r.killed = false
|
||||
fn := func(path string, mode os.FileInfo) error {
|
||||
conf := fastwalk.Config{Follow: opts.follow}
|
||||
fn := func(path string, de os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
path = filepath.Clean(path)
|
||||
if path != "." {
|
||||
isDir := mode.Mode().IsDir()
|
||||
if isDir && filepath.Base(path)[0] == '.' {
|
||||
isDir := de.IsDir()
|
||||
if isDir {
|
||||
base := filepath.Base(path)
|
||||
if !opts.hidden && base[0] == '.' {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
if !isDir && r.pusher([]byte(path)) {
|
||||
for _, ignore := range ignores {
|
||||
if ignore == base {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((opts.file && !isDir) || (opts.dir && isDir)) && r.pusher([]byte(path)) {
|
||||
atomic.StoreInt32(&r.event, int32(EvtReadNew))
|
||||
}
|
||||
}
|
||||
@@ -181,20 +235,16 @@ func (r *Reader) readFiles() bool {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
cb := walker.WithErrorCallback(func(pathname string, err error) error {
|
||||
return nil
|
||||
})
|
||||
return walker.Walk(".", fn, cb) == nil
|
||||
return fastwalk.Walk(&conf, root, fn) == nil
|
||||
}
|
||||
|
||||
func (r *Reader) readFromCommand(shell *string, command string) bool {
|
||||
func (r *Reader) readFromCommand(command string, environ []string) bool {
|
||||
r.mutex.Lock()
|
||||
r.killed = false
|
||||
r.command = &command
|
||||
if shell != nil {
|
||||
r.exec = util.ExecCommandWith(*shell, command, true)
|
||||
} else {
|
||||
r.exec = util.ExecCommand(command, true)
|
||||
if environ != nil {
|
||||
r.exec.Env = environ
|
||||
}
|
||||
out, err := r.exec.StdoutPipe()
|
||||
if err != nil {
|
||||
|
||||
@@ -22,7 +22,7 @@ func TestReadFromCommand(t *testing.T) {
|
||||
}
|
||||
|
||||
// Normal command
|
||||
reader.fin(reader.readFromCommand(nil, `echo abc&&echo def`))
|
||||
reader.fin(reader.readFromCommand(`echo abc&&echo def`, nil))
|
||||
if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" {
|
||||
t.Errorf("%s", strs)
|
||||
}
|
||||
@@ -47,7 +47,7 @@ func TestReadFromCommand(t *testing.T) {
|
||||
reader.startEventPoller()
|
||||
|
||||
// Failing command
|
||||
reader.fin(reader.readFromCommand(nil, `no-such-command`))
|
||||
reader.fin(reader.readFromCommand(`no-such-command`, nil))
|
||||
strs = []string{}
|
||||
if len(strs) > 0 {
|
||||
t.Errorf("%s", strs)
|
||||
|
||||
@@ -32,6 +32,7 @@ const (
|
||||
httpUnauthorized = "HTTP/1.1 401 Unauthorized" + crlf
|
||||
httpUnavailable = "HTTP/1.1 503 Service Unavailable" + crlf
|
||||
httpReadTimeout = 10 * time.Second
|
||||
channelTimeout = 2 * time.Second
|
||||
jsonContentType = "Content-Type: application/json" + crlf
|
||||
maxContentLength = 1024 * 1024
|
||||
)
|
||||
@@ -53,47 +54,47 @@ func (addr listenAddress) IsLocal() bool {
|
||||
|
||||
var defaultListenAddr = listenAddress{"localhost", 0}
|
||||
|
||||
func parseListenAddress(address string) (error, listenAddress) {
|
||||
func parseListenAddress(address string) (listenAddress, error) {
|
||||
parts := strings.SplitN(address, ":", 3)
|
||||
if len(parts) == 1 {
|
||||
parts = []string{"localhost", parts[0]}
|
||||
}
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("invalid listen address: %s", address), defaultListenAddr
|
||||
return defaultListenAddr, fmt.Errorf("invalid listen address: %s", address)
|
||||
}
|
||||
portStr := parts[len(parts)-1]
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil || port < 0 || port > 65535 {
|
||||
return fmt.Errorf("invalid listen port: %s", portStr), defaultListenAddr
|
||||
return defaultListenAddr, fmt.Errorf("invalid listen port: %s", portStr)
|
||||
}
|
||||
if len(parts[0]) == 0 {
|
||||
parts[0] = "localhost"
|
||||
}
|
||||
return nil, listenAddress{parts[0], port}
|
||||
return listenAddress{parts[0], port}, nil
|
||||
}
|
||||
|
||||
func startHttpServer(address listenAddress, actionChannel chan []*action, responseChannel chan string) (error, int) {
|
||||
func startHttpServer(address listenAddress, actionChannel chan []*action, responseChannel chan string) (int, error) {
|
||||
host := address.host
|
||||
port := address.port
|
||||
apiKey := os.Getenv("FZF_API_KEY")
|
||||
if !address.IsLocal() && len(apiKey) == 0 {
|
||||
return fmt.Errorf("FZF_API_KEY is required to allow remote access"), port
|
||||
return port, fmt.Errorf("FZF_API_KEY is required to allow remote access")
|
||||
}
|
||||
addrStr := fmt.Sprintf("%s:%d", host, port)
|
||||
listener, err := net.Listen("tcp", addrStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to listen on %s", addrStr), port
|
||||
return port, fmt.Errorf("failed to listen on %s", addrStr)
|
||||
}
|
||||
if port == 0 {
|
||||
addr := listener.Addr().String()
|
||||
parts := strings.Split(addr, ":")
|
||||
if len(parts) < 2 {
|
||||
return fmt.Errorf("cannot extract port: %s", addr), port
|
||||
return port, fmt.Errorf("cannot extract port: %s", addr)
|
||||
}
|
||||
var err error
|
||||
port, err = strconv.Atoi(parts[len(parts)-1])
|
||||
if err != nil {
|
||||
return err, port
|
||||
return port, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +120,7 @@ func startHttpServer(address listenAddress, actionChannel chan []*action, respon
|
||||
listener.Close()
|
||||
}()
|
||||
|
||||
return nil, port
|
||||
return port, nil
|
||||
}
|
||||
|
||||
// Here we are writing a simplistic HTTP server without using net/http
|
||||
@@ -170,7 +171,7 @@ func (server *httpServer) handleHttpRequest(conn net.Conn) string {
|
||||
select {
|
||||
case response := <-server.responseChannel:
|
||||
return good(response)
|
||||
case <-time.After(2 * time.Second):
|
||||
case <-time.After(channelTimeout):
|
||||
go func() {
|
||||
// Drain the channel
|
||||
<-server.responseChannel
|
||||
@@ -227,7 +228,11 @@ func (server *httpServer) handleHttpRequest(conn net.Conn) string {
|
||||
return bad("no action specified")
|
||||
}
|
||||
|
||||
server.actionChannel <- actions
|
||||
select {
|
||||
case server.actionChannel <- actions:
|
||||
case <-time.After(channelTimeout):
|
||||
return httpUnavailable + crlf
|
||||
}
|
||||
return httpOk + crlf
|
||||
}
|
||||
|
||||
@@ -237,18 +242,16 @@ func parseGetParams(query string) getParams {
|
||||
parts := strings.SplitN(pair, "=", 2)
|
||||
if len(parts) == 2 {
|
||||
switch parts[0] {
|
||||
case "limit":
|
||||
val, err := strconv.Atoi(parts[1])
|
||||
if err == nil {
|
||||
case "limit", "offset":
|
||||
if val, err := strconv.Atoi(parts[1]); err == nil {
|
||||
if parts[0] == "limit" {
|
||||
params.limit = val
|
||||
}
|
||||
case "offset":
|
||||
val, err := strconv.Atoi(parts[1])
|
||||
if err == nil {
|
||||
} else {
|
||||
params.offset = val
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
249
src/terminal.go
249
src/terminal.go
@@ -55,6 +55,9 @@ var actionTypeRegex *regexp.Regexp
|
||||
|
||||
const clearCode string = "\x1b[2J"
|
||||
|
||||
// Number of maximum focus events to process synchronously
|
||||
const maxFocusEvents = 10000
|
||||
|
||||
func init() {
|
||||
placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{fzf:(?:query|action|prompt)}|{\+?f?nf?})`)
|
||||
whiteSuffix = regexp.MustCompile(`\s*$`)
|
||||
@@ -67,7 +70,7 @@ func init() {
|
||||
// * https://sw.kovidgoyal.net/kitty/graphics-protocol
|
||||
// * https://en.wikipedia.org/wiki/Sixel
|
||||
// * https://iterm2.com/documentation-images.html
|
||||
passThroughRegex = regexp.MustCompile(`\x1bPtmux;\x1b\x1b.*?[^\x1b]\x1b\\|\x1b(_G|P[0-9;]*q).*?\x1b\\\r?|\x1b]1337;.*?\a`)
|
||||
passThroughRegex = regexp.MustCompile(`\x1bPtmux;\x1b\x1b.*?[^\x1b]\x1b\\|\x1b(_G|P[0-9;]*q).*?\x1b\\\r?|\x1b]1337;.*?(\a|\x1b\\)`)
|
||||
}
|
||||
|
||||
type jumpMode int
|
||||
@@ -178,7 +181,7 @@ type Status struct {
|
||||
type Terminal struct {
|
||||
initDelay time.Duration
|
||||
infoStyle infoStyle
|
||||
infoSep string
|
||||
infoPrefix string
|
||||
separator labelPrinter
|
||||
separatorLen int
|
||||
spinner []string
|
||||
@@ -290,6 +293,7 @@ type Terminal struct {
|
||||
executing *util.AtomicBool
|
||||
termSize tui.TermSize
|
||||
lastAction actionType
|
||||
lastKey string
|
||||
lastFocus int32
|
||||
areaLines int
|
||||
areaColumns int
|
||||
@@ -391,8 +395,10 @@ const (
|
||||
actToggleIn
|
||||
actToggleOut
|
||||
actToggleTrack
|
||||
actToggleTrackCurrent
|
||||
actToggleHeader
|
||||
actTrack
|
||||
actTrackCurrent
|
||||
actUntrackCurrent
|
||||
actDown
|
||||
actUp
|
||||
actPageUp
|
||||
@@ -403,7 +409,7 @@ const (
|
||||
actOffsetUp
|
||||
actOffsetDown
|
||||
actJump
|
||||
actJumpAccept
|
||||
actJumpAccept // XXX Deprecated in favor of jump:accept binding
|
||||
actPrintQuery
|
||||
actRefreshPreview
|
||||
actReplaceQuery
|
||||
@@ -455,14 +461,7 @@ const (
|
||||
)
|
||||
|
||||
func (a actionType) Name() string {
|
||||
name := ""
|
||||
for i, r := range a.String()[3:] {
|
||||
if i > 0 && r >= 'A' && r <= 'Z' {
|
||||
name += "-"
|
||||
}
|
||||
name += string(r)
|
||||
}
|
||||
return strings.ToLower(name)
|
||||
return util.ToKebabCase(a.String()[3:])
|
||||
}
|
||||
|
||||
func processExecution(action actionType) bool {
|
||||
@@ -499,6 +498,7 @@ type searchRequest struct {
|
||||
sort bool
|
||||
sync bool
|
||||
command *string
|
||||
environ []string
|
||||
changed bool
|
||||
}
|
||||
|
||||
@@ -540,14 +540,14 @@ func defaultKeymap() map[tui.Event][]*action {
|
||||
add(tui.CtrlC, actAbort)
|
||||
add(tui.CtrlG, actAbort)
|
||||
add(tui.CtrlQ, actAbort)
|
||||
add(tui.ESC, actAbort)
|
||||
add(tui.Esc, actAbort)
|
||||
add(tui.CtrlD, actDeleteCharEof)
|
||||
add(tui.CtrlE, actEndOfLine)
|
||||
add(tui.CtrlF, actForwardChar)
|
||||
add(tui.CtrlH, actBackwardDeleteChar)
|
||||
add(tui.BSpace, actBackwardDeleteChar)
|
||||
add(tui.Backspace, actBackwardDeleteChar)
|
||||
add(tui.Tab, actToggleDown)
|
||||
add(tui.BTab, actToggleUp)
|
||||
add(tui.ShiftTab, actToggleUp)
|
||||
add(tui.CtrlJ, actDown)
|
||||
add(tui.CtrlK, actUp)
|
||||
add(tui.CtrlL, actClearScreen)
|
||||
@@ -562,11 +562,11 @@ func defaultKeymap() map[tui.Event][]*action {
|
||||
}
|
||||
|
||||
addEvent(tui.AltKey('b'), actBackwardWord)
|
||||
add(tui.SLeft, actBackwardWord)
|
||||
add(tui.ShiftLeft, actBackwardWord)
|
||||
addEvent(tui.AltKey('f'), actForwardWord)
|
||||
add(tui.SRight, actForwardWord)
|
||||
add(tui.ShiftRight, actForwardWord)
|
||||
addEvent(tui.AltKey('d'), actKillWord)
|
||||
add(tui.AltBS, actBackwardKillWord)
|
||||
add(tui.AltBackspace, actBackwardKillWord)
|
||||
|
||||
add(tui.Up, actUp)
|
||||
add(tui.Down, actDown)
|
||||
@@ -575,12 +575,12 @@ func defaultKeymap() map[tui.Event][]*action {
|
||||
|
||||
add(tui.Home, actBeginningOfLine)
|
||||
add(tui.End, actEndOfLine)
|
||||
add(tui.Del, actDeleteChar)
|
||||
add(tui.PgUp, actPageUp)
|
||||
add(tui.PgDn, actPageDown)
|
||||
add(tui.Delete, actDeleteChar)
|
||||
add(tui.PageUp, actPageUp)
|
||||
add(tui.PageDown, actPageDown)
|
||||
|
||||
add(tui.SUp, actPreviewUp)
|
||||
add(tui.SDown, actPreviewDown)
|
||||
add(tui.ShiftUp, actPreviewUp)
|
||||
add(tui.ShiftDown, actPreviewDown)
|
||||
|
||||
add(tui.Mouse, actMouse)
|
||||
add(tui.LeftClick, actClick)
|
||||
@@ -671,7 +671,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
if previewBox != nil && opts.Preview.aboveOrBelow() {
|
||||
effectiveMinHeight += 1 + borderLines(opts.Preview.border)
|
||||
}
|
||||
if opts.InfoStyle.noExtraLine() {
|
||||
if noSeparatorLine(opts.InfoStyle, opts.Separator == nil || uniseg.StringWidth(*opts.Separator) > 0) {
|
||||
effectiveMinHeight--
|
||||
}
|
||||
effectiveMinHeight += borderLines(opts.BorderShape)
|
||||
@@ -693,7 +693,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
t := Terminal{
|
||||
initDelay: delay,
|
||||
infoStyle: opts.InfoStyle,
|
||||
infoSep: opts.InfoSep,
|
||||
infoPrefix: opts.InfoPrefix,
|
||||
separator: nil,
|
||||
spinner: makeSpinner(opts.Unicode),
|
||||
promptString: opts.Prompt,
|
||||
@@ -772,7 +772,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
theme: opts.Theme,
|
||||
startChan: make(chan fitpad, 1),
|
||||
killChan: make(chan int),
|
||||
serverInputChan: make(chan []*action, 10),
|
||||
serverInputChan: make(chan []*action, 100),
|
||||
serverOutputChan: make(chan string),
|
||||
eventChan: make(chan tui.Event, 6), // (load + result + zero|one) | (focus) | (resize) | (GetChar)
|
||||
tui: renderer,
|
||||
@@ -828,7 +828,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
_, t.hasLoadActions = t.keymap[tui.Load.AsEvent()]
|
||||
|
||||
if t.listenAddr != nil {
|
||||
err, port := startHttpServer(*t.listenAddr, t.serverInputChan, t.serverOutputChan)
|
||||
port, err := startHttpServer(*t.listenAddr, t.serverInputChan, t.serverOutputChan)
|
||||
if err != nil {
|
||||
errorExit(err.Error())
|
||||
}
|
||||
@@ -845,7 +845,10 @@ func (t *Terminal) environ() []string {
|
||||
}
|
||||
env = append(env, "FZF_QUERY="+string(t.input))
|
||||
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_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_MATCH_COUNT=%d", t.merger.Length()))
|
||||
env = append(env, fmt.Sprintf("FZF_SELECT_COUNT=%d", len(t.selected)))
|
||||
@@ -874,7 +877,7 @@ func (t *Terminal) visibleHeaderLines() int {
|
||||
// Extra number of lines needed to display fzf
|
||||
func (t *Terminal) extraLines() int {
|
||||
extra := t.visibleHeaderLines() + 1
|
||||
if !t.noInfoLine() {
|
||||
if !t.noSeparatorLine() {
|
||||
extra++
|
||||
}
|
||||
return extra
|
||||
@@ -980,8 +983,18 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) {
|
||||
return output, promptLen
|
||||
}
|
||||
|
||||
func (t *Terminal) noInfoLine() bool {
|
||||
return t.infoStyle.noExtraLine()
|
||||
func noSeparatorLine(style infoStyle, separator bool) bool {
|
||||
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) {
|
||||
@@ -1056,11 +1069,11 @@ func (t *Terminal) UpdateProgress(progress float32) {
|
||||
// UpdateList updates Merger to display the list
|
||||
func (t *Terminal) UpdateList(merger *Merger) {
|
||||
t.mutex.Lock()
|
||||
var prevIndex int32 = -1
|
||||
prevIndex := minItem.Index()
|
||||
reset := t.revision != merger.Revision()
|
||||
if !reset && t.track != trackDisabled {
|
||||
if t.merger.Length() > 0 {
|
||||
prevIndex = t.merger.Get(t.cy).item.Index()
|
||||
prevIndex = t.currentIndex()
|
||||
} else if merger.Length() > 0 {
|
||||
prevIndex = merger.First().item.Index()
|
||||
}
|
||||
@@ -1233,7 +1246,7 @@ func (t *Terminal) adjustMarginAndPadding() (int, int, [4]int, [4]int) {
|
||||
|
||||
minAreaWidth := minWidth
|
||||
minAreaHeight := minHeight
|
||||
if t.noInfoLine() {
|
||||
if t.noSeparatorLine() {
|
||||
minAreaHeight -= 1
|
||||
}
|
||||
if t.needPreviewWindow() {
|
||||
@@ -1514,7 +1527,7 @@ func (t *Terminal) move(y int, x int, clear bool) {
|
||||
y = h - y - 1
|
||||
case layoutReverseList:
|
||||
n := 2 + t.visibleHeaderLines()
|
||||
if t.noInfoLine() {
|
||||
if t.noSeparatorLine() {
|
||||
n--
|
||||
}
|
||||
if y < n {
|
||||
@@ -1554,7 +1567,7 @@ func (t *Terminal) updatePromptOffset() ([]rune, []rune) {
|
||||
func (t *Terminal) promptLine() int {
|
||||
if t.headerFirst {
|
||||
max := t.window.Height() - 1
|
||||
if !t.noInfoLine() {
|
||||
if !t.noSeparatorLine() {
|
||||
max--
|
||||
}
|
||||
return util.Min(t.visibleHeaderLines(), max)
|
||||
@@ -1599,20 +1612,8 @@ func (t *Terminal) printInfo() {
|
||||
t.window.Print(" ") // Clear spinner
|
||||
}
|
||||
}
|
||||
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
|
||||
t.move(line, pos, true)
|
||||
case infoInline:
|
||||
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
||||
str := t.infoSep
|
||||
printInfoPrefix := func() {
|
||||
str := t.infoPrefix
|
||||
maxWidth := t.window.Width() - pos
|
||||
width := util.StringWidth(str)
|
||||
if width > maxWidth {
|
||||
@@ -1627,7 +1628,34 @@ func (t *Terminal) printInfo() {
|
||||
t.window.CPrint(tui.ColPrompt, str)
|
||||
}
|
||||
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:
|
||||
if t.separatorLen > 0 {
|
||||
t.move(line+1, 0, false)
|
||||
printSeparator(t.window.Width()-1, false)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1641,8 +1669,11 @@ func (t *Terminal) printInfo() {
|
||||
output += " -S"
|
||||
}
|
||||
}
|
||||
if t.track != trackDisabled {
|
||||
switch t.track {
|
||||
case trackEnabled:
|
||||
output += " +T"
|
||||
case trackCurrent:
|
||||
output += " +t"
|
||||
}
|
||||
if t.multi > 0 {
|
||||
if t.multi == maxMulti {
|
||||
@@ -1658,15 +1689,6 @@ func (t *Terminal) printInfo() {
|
||||
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 {
|
||||
maxWidth := t.window.Width()
|
||||
if t.reading {
|
||||
@@ -1689,19 +1711,35 @@ func (t *Terminal) printInfo() {
|
||||
}
|
||||
|
||||
if t.infoStyle == infoInlineRight {
|
||||
if len(t.infoPrefix) == 0 {
|
||||
pos = util.Max(pos, t.window.Width()-util.StringWidth(output)-3)
|
||||
if pos >= t.window.Width() {
|
||||
return
|
||||
}
|
||||
if pos < t.window.Width() {
|
||||
t.move(line, pos, false)
|
||||
printSpinner()
|
||||
pos++
|
||||
}
|
||||
if pos < t.window.Width()-1 {
|
||||
t.window.Print(" ")
|
||||
pos += 2
|
||||
pos++
|
||||
}
|
||||
} else {
|
||||
pos = util.Max(pos, t.window.Width()-util.StringWidth(output)-util.StringWidth(t.infoPrefix)-1)
|
||||
printInfoPrefix()
|
||||
}
|
||||
}
|
||||
|
||||
maxWidth := t.window.Width() - pos
|
||||
output = t.trimMessage(output, maxWidth)
|
||||
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
|
||||
if fillLength > 0 {
|
||||
t.window.CPrint(tui.ColSeparator, " ")
|
||||
@@ -1716,7 +1754,7 @@ func (t *Terminal) printHeader() {
|
||||
max := t.window.Height()
|
||||
if t.headerFirst {
|
||||
max--
|
||||
if !t.noInfoLine() {
|
||||
if !t.noSeparatorLine() {
|
||||
max--
|
||||
}
|
||||
}
|
||||
@@ -1733,7 +1771,7 @@ func (t *Terminal) printHeader() {
|
||||
}
|
||||
if !t.headerFirst {
|
||||
line++
|
||||
if !t.noInfoLine() {
|
||||
if !t.noSeparatorLine() {
|
||||
line++
|
||||
}
|
||||
}
|
||||
@@ -1765,7 +1803,7 @@ func (t *Terminal) printList() {
|
||||
i = maxy - 1 - j
|
||||
}
|
||||
line := i + 2 + t.visibleHeaderLines()
|
||||
if t.noInfoLine() {
|
||||
if t.noSeparatorLine() {
|
||||
line--
|
||||
}
|
||||
if i < count {
|
||||
@@ -3091,7 +3129,7 @@ func (t *Terminal) Loop() {
|
||||
switch req {
|
||||
case reqPrompt:
|
||||
t.printPrompt()
|
||||
if t.noInfoLine() {
|
||||
if t.infoStyle == infoInline || t.infoStyle == infoInlineRight {
|
||||
t.printInfo()
|
||||
}
|
||||
case reqInfo:
|
||||
@@ -3160,7 +3198,7 @@ func (t *Terminal) Loop() {
|
||||
}
|
||||
t.previewer.lines = result.lines
|
||||
t.previewer.spinner = result.spinner
|
||||
if t.previewer.following.Enabled() {
|
||||
if t.hasPreviewWindow() && t.previewer.following.Enabled() {
|
||||
t.previewer.offset = util.Max(t.previewer.offset, len(t.previewer.lines)-(t.pwindow.Height()-t.previewOpts.headerLines))
|
||||
} else if result.offset >= 0 {
|
||||
t.previewer.offset = util.Constrain(result.offset, t.previewOpts.headerLines, len(t.previewer.lines)-1)
|
||||
@@ -3247,6 +3285,7 @@ func (t *Terminal) Loop() {
|
||||
t.mutex.Lock()
|
||||
previousInput := t.input
|
||||
previousCx := t.cx
|
||||
t.lastKey = event.KeyName()
|
||||
events := []util.EventType{}
|
||||
req := func(evts ...util.EventType) {
|
||||
for _, event := range evts {
|
||||
@@ -3304,6 +3343,7 @@ func (t *Terminal) Loop() {
|
||||
var doAction func(*action) bool
|
||||
var doActions func(actions []*action) bool
|
||||
doActions = func(actions []*action) bool {
|
||||
for iter := 0; iter <= maxFocusEvents; iter++ {
|
||||
currentIndex := t.currentIndex()
|
||||
for _, action := range actions {
|
||||
if !doAction(action) {
|
||||
@@ -3311,12 +3351,15 @@ func (t *Terminal) Loop() {
|
||||
}
|
||||
}
|
||||
|
||||
if onFocus, prs := t.keymap[tui.Focus.AsEvent()]; prs {
|
||||
if onFocus, prs := t.keymap[tui.Focus.AsEvent()]; prs && iter < maxFocusEvents {
|
||||
if newIndex := t.currentIndex(); newIndex != currentIndex {
|
||||
t.lastFocus = newIndex
|
||||
return doActions(onFocus)
|
||||
actions = onFocus
|
||||
continue
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
return true
|
||||
}
|
||||
doAction = func(a *action) bool {
|
||||
@@ -3381,6 +3424,9 @@ func (t *Terminal) Loop() {
|
||||
// Discard the preview content so that it won't accidentally appear
|
||||
// when preview window is re-enabled and previewDelay is triggered
|
||||
t.previewer.lines = nil
|
||||
|
||||
// Also kill the preview process if it's still running
|
||||
t.cancelPreview()
|
||||
}
|
||||
}
|
||||
case actTogglePreviewWrap:
|
||||
@@ -3460,11 +3506,13 @@ func (t *Terminal) Loop() {
|
||||
req(reqHeader)
|
||||
}
|
||||
case actChangeBorderLabel:
|
||||
t.borderLabelOpts.label = a.a
|
||||
if t.border != nil {
|
||||
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(a.a, &tui.ColBorderLabel, false)
|
||||
req(reqRedrawBorderLabel)
|
||||
}
|
||||
case actChangePreviewLabel:
|
||||
t.previewLabelOpts.label = a.a
|
||||
if t.pborder != nil {
|
||||
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(a.a, &tui.ColPreviewLabel, false)
|
||||
req(reqRedrawPreviewLabel)
|
||||
@@ -3474,14 +3522,16 @@ func (t *Terminal) Loop() {
|
||||
actions := parseSingleActionList(strings.Trim(body, "\r\n"), func(message string) {})
|
||||
return doActions(actions)
|
||||
case actTransformBorderLabel:
|
||||
if t.border != nil {
|
||||
label := t.executeCommand(a.a, false, true, true, true)
|
||||
t.borderLabelOpts.label = label
|
||||
if t.border != nil {
|
||||
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(label, &tui.ColBorderLabel, false)
|
||||
req(reqRedrawBorderLabel)
|
||||
}
|
||||
case actTransformPreviewLabel:
|
||||
if t.pborder != nil {
|
||||
label := t.executeCommand(a.a, false, true, true, true)
|
||||
t.previewLabelOpts.label = label
|
||||
if t.pborder != nil {
|
||||
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(label, &tui.ColPreviewLabel, false)
|
||||
req(reqRedrawPreviewLabel)
|
||||
}
|
||||
@@ -3767,6 +3817,14 @@ func (t *Terminal) Loop() {
|
||||
t.track = trackEnabled
|
||||
}
|
||||
req(reqInfo)
|
||||
case actToggleTrackCurrent:
|
||||
switch t.track {
|
||||
case trackCurrent:
|
||||
t.track = trackDisabled
|
||||
case trackDisabled:
|
||||
t.track = trackCurrent
|
||||
}
|
||||
req(reqInfo)
|
||||
case actShowHeader:
|
||||
t.headerVisible = true
|
||||
req(reqList, reqInfo, reqPrompt, reqHeader)
|
||||
@@ -3776,11 +3834,16 @@ func (t *Terminal) Loop() {
|
||||
case actToggleHeader:
|
||||
t.headerVisible = !t.headerVisible
|
||||
req(reqList, reqInfo, reqPrompt, reqHeader)
|
||||
case actTrack:
|
||||
case actTrackCurrent:
|
||||
if t.track == trackDisabled {
|
||||
t.track = trackCurrent
|
||||
}
|
||||
req(reqInfo)
|
||||
case actUntrackCurrent:
|
||||
if t.track == trackCurrent {
|
||||
t.track = trackDisabled
|
||||
}
|
||||
req(reqInfo)
|
||||
case actEnableSearch:
|
||||
t.paused = false
|
||||
changed = true
|
||||
@@ -3869,7 +3932,7 @@ func (t *Terminal) Loop() {
|
||||
mx -= t.window.Left()
|
||||
my -= t.window.Top()
|
||||
min := 2 + t.visibleHeaderLines()
|
||||
if t.noInfoLine() {
|
||||
if t.noSeparatorLine() {
|
||||
min--
|
||||
}
|
||||
h := t.window.Height()
|
||||
@@ -3987,11 +4050,18 @@ func (t *Terminal) Loop() {
|
||||
|
||||
// Full redraw
|
||||
if !currentPreviewOpts.sameLayout(t.previewOpts) {
|
||||
wasHidden := t.pwindow == nil
|
||||
// Preview command can be running in the background if the size of
|
||||
// the preview window is 0 but not 'hidden'
|
||||
wasHidden := currentPreviewOpts.hidden
|
||||
updatePreviewWindow(false)
|
||||
if wasHidden && t.hasPreviewWindow() {
|
||||
// Restart
|
||||
refreshPreview(t.previewOpts.command)
|
||||
} else if t.previewOpts.hidden {
|
||||
// Cancel
|
||||
t.cancelPreview()
|
||||
} else {
|
||||
// Refresh
|
||||
req(reqPreviewRefresh)
|
||||
}
|
||||
} else if !currentPreviewOpts.sameContentLayout(t.previewOpts) {
|
||||
@@ -4034,6 +4104,9 @@ func (t *Terminal) Loop() {
|
||||
// Break out of jump mode if any action is submitted to the server
|
||||
if t.jumping != jumpDisabled {
|
||||
t.jumping = jumpDisabled
|
||||
if acts, prs := t.keymap[tui.JumpCancel.AsEvent()]; prs && !doActions(acts) {
|
||||
continue
|
||||
}
|
||||
req(reqList)
|
||||
}
|
||||
if len(actions) == 0 {
|
||||
@@ -4047,19 +4120,17 @@ func (t *Terminal) Loop() {
|
||||
t.truncateQuery()
|
||||
queryChanged = string(previousInput) != string(t.input)
|
||||
changed = changed || queryChanged
|
||||
if onChanges, prs := t.keymap[tui.Change.AsEvent()]; queryChanged && prs {
|
||||
if !doActions(onChanges) {
|
||||
if onChanges, prs := t.keymap[tui.Change.AsEvent()]; queryChanged && prs && !doActions(onChanges) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if onEOFs, prs := t.keymap[tui.BackwardEOF.AsEvent()]; beof && prs {
|
||||
if !doActions(onEOFs) {
|
||||
if onEOFs, prs := t.keymap[tui.BackwardEOF.AsEvent()]; beof && prs && !doActions(onEOFs) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
} else {
|
||||
jumpEvent := tui.JumpCancel
|
||||
if event.Type == tui.Rune {
|
||||
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
|
||||
if t.jumping == jumpAcceptEnabled {
|
||||
req(reqClose)
|
||||
@@ -4067,6 +4138,9 @@ func (t *Terminal) Loop() {
|
||||
}
|
||||
}
|
||||
t.jumping = jumpDisabled
|
||||
if acts, prs := t.keymap[jumpEvent.AsEvent()]; prs && !doActions(acts) {
|
||||
continue
|
||||
}
|
||||
req(reqList)
|
||||
}
|
||||
|
||||
@@ -4081,10 +4155,15 @@ func (t *Terminal) Loop() {
|
||||
req(reqPrompt)
|
||||
}
|
||||
|
||||
reload := changed || newCommand != nil
|
||||
var reloadRequest *searchRequest
|
||||
if reload {
|
||||
reloadRequest = &searchRequest{sort: t.sort, sync: reloadSync, command: newCommand, environ: t.environ(), changed: changed}
|
||||
}
|
||||
t.mutex.Unlock() // Must be unlocked before touching reqBox
|
||||
|
||||
if changed || newCommand != nil {
|
||||
t.eventBox.Set(EvtSearchNew, searchRequest{sort: t.sort, sync: reloadSync, command: newCommand, changed: changed})
|
||||
if reload {
|
||||
t.eventBox.Set(EvtSearchNew, *reloadRequest)
|
||||
}
|
||||
for _, event := range events {
|
||||
t.reqBox.Set(event, nil)
|
||||
@@ -4098,7 +4177,7 @@ func (t *Terminal) constrain() {
|
||||
// count of lines can be displayed
|
||||
height := t.maxItems()
|
||||
|
||||
t.cy = util.Constrain(t.cy, 0, count-1)
|
||||
t.cy = util.Constrain(t.cy, 0, util.Max(0, count-1))
|
||||
|
||||
minOffset := util.Max(t.cy-height+1, 0)
|
||||
maxOffset := util.Max(util.Min(count-height, t.cy), 0)
|
||||
@@ -4149,7 +4228,7 @@ func (t *Terminal) vset(o int) bool {
|
||||
|
||||
func (t *Terminal) maxItems() int {
|
||||
max := t.window.Height() - 2 - t.visibleHeaderLines()
|
||||
if t.noInfoLine() {
|
||||
if t.noSeparatorLine() {
|
||||
max++
|
||||
}
|
||||
return util.Max(max, 0)
|
||||
|
||||
@@ -91,7 +91,7 @@ func withPrefixLengths(tokens []string, begin int) []Token {
|
||||
|
||||
prefixLength := begin
|
||||
for idx := range tokens {
|
||||
chars := util.ToChars([]byte(tokens[idx]))
|
||||
chars := util.ToChars(sbytes(tokens[idx]))
|
||||
ret[idx] = Token{&chars, int32(prefixLength)}
|
||||
prefixLength += chars.Length()
|
||||
}
|
||||
@@ -187,7 +187,7 @@ func Transform(tokens []Token, withNth []Range) []Token {
|
||||
if r.begin == r.end {
|
||||
idx := r.begin
|
||||
if idx == rangeEllipsis {
|
||||
chars := util.ToChars([]byte(joinTokens(tokens)))
|
||||
chars := util.ToChars(sbytes(joinTokens(tokens)))
|
||||
parts = append(parts, &chars)
|
||||
} else {
|
||||
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() {
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -245,7 +245,7 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
|
||||
}
|
||||
|
||||
retries := 0
|
||||
if c == ESC.Int() || nonblock {
|
||||
if c == Esc.Int() || nonblock {
|
||||
retries = r.escDelay / escPollInterval
|
||||
}
|
||||
buffer = append(buffer, byte(c))
|
||||
@@ -260,7 +260,7 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
|
||||
continue
|
||||
}
|
||||
break
|
||||
} else if c == ESC.Int() && pc != c {
|
||||
} else if c == Esc.Int() && pc != c {
|
||||
retries = r.escDelay / escPollInterval
|
||||
} else {
|
||||
retries = 0
|
||||
@@ -300,7 +300,7 @@ func (r *LightRenderer) GetChar() Event {
|
||||
case CtrlQ.Byte():
|
||||
return Event{CtrlQ, 0, nil}
|
||||
case 127:
|
||||
return Event{BSpace, 0, nil}
|
||||
return Event{Backspace, 0, nil}
|
||||
case 0:
|
||||
return Event{CtrlSpace, 0, nil}
|
||||
case 28:
|
||||
@@ -311,7 +311,7 @@ func (r *LightRenderer) GetChar() Event {
|
||||
return Event{CtrlCaret, 0, nil}
|
||||
case 31:
|
||||
return Event{CtrlSlash, 0, nil}
|
||||
case ESC.Byte():
|
||||
case Esc.Byte():
|
||||
ev := r.escSequence(&sz)
|
||||
// Second chance
|
||||
if ev.Type == Invalid {
|
||||
@@ -327,7 +327,7 @@ func (r *LightRenderer) GetChar() Event {
|
||||
}
|
||||
char, rsz := utf8.DecodeRune(r.buffer)
|
||||
if char == utf8.RuneError {
|
||||
return Event{ESC, 0, nil}
|
||||
return Event{Esc, 0, nil}
|
||||
}
|
||||
sz = rsz
|
||||
return Event{Rune, char, nil}
|
||||
@@ -335,7 +335,7 @@ func (r *LightRenderer) GetChar() Event {
|
||||
|
||||
func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
if len(r.buffer) < 2 {
|
||||
return Event{ESC, 0, nil}
|
||||
return Event{Esc, 0, nil}
|
||||
}
|
||||
|
||||
loc := offsetRegexpBegin.FindIndex(r.buffer)
|
||||
@@ -349,15 +349,15 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
return CtrlAltKey(rune(r.buffer[1] + 'a' - 1))
|
||||
}
|
||||
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:]
|
||||
alt = true
|
||||
}
|
||||
switch r.buffer[1] {
|
||||
case ESC.Byte():
|
||||
return Event{ESC, 0, nil}
|
||||
case Esc.Byte():
|
||||
return Event{Esc, 0, nil}
|
||||
case 127:
|
||||
return Event{AltBS, 0, nil}
|
||||
return Event{AltBackspace, 0, nil}
|
||||
case '[', 'O':
|
||||
if len(r.buffer) < 3 {
|
||||
return Event{Invalid, 0, nil}
|
||||
@@ -386,7 +386,7 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
}
|
||||
return Event{Up, 0, nil}
|
||||
case 'Z':
|
||||
return Event{BTab, 0, nil}
|
||||
return Event{ShiftTab, 0, nil}
|
||||
case 'H':
|
||||
return Event{Home, 0, nil}
|
||||
case 'F':
|
||||
@@ -434,7 +434,7 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
return Event{Invalid, 0, nil} // INS
|
||||
case '3':
|
||||
if r.buffer[3] == '~' {
|
||||
return Event{Del, 0, nil}
|
||||
return Event{Delete, 0, nil}
|
||||
}
|
||||
if len(r.buffer) == 6 && r.buffer[5] == '~' {
|
||||
*sz = 6
|
||||
@@ -442,16 +442,16 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
case '5':
|
||||
return Event{CtrlDelete, 0, nil}
|
||||
case '2':
|
||||
return Event{SDelete, 0, nil}
|
||||
return Event{ShiftDelete, 0, nil}
|
||||
}
|
||||
}
|
||||
return Event{Invalid, 0, nil}
|
||||
case '4':
|
||||
return Event{End, 0, nil}
|
||||
case '5':
|
||||
return Event{PgUp, 0, nil}
|
||||
return Event{PageUp, 0, nil}
|
||||
case '6':
|
||||
return Event{PgDn, 0, nil}
|
||||
return Event{PageDown, 0, nil}
|
||||
case '7':
|
||||
return Event{Home, 0, nil}
|
||||
case '8':
|
||||
@@ -489,16 +489,29 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
}
|
||||
*sz = 6
|
||||
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'
|
||||
altShift := r.buffer[4] == '1' && r.buffer[5] == '0'
|
||||
char := r.buffer[5]
|
||||
if altShift {
|
||||
altShift := false
|
||||
if r.buffer[4] == '1' && r.buffer[5] == '0' {
|
||||
altShift = true
|
||||
if len(r.buffer) < 7 {
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
*sz = 7
|
||||
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 {
|
||||
case 'A':
|
||||
@@ -506,33 +519,33 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
return Event{AltUp, 0, nil}
|
||||
}
|
||||
if altShift {
|
||||
return Event{AltSUp, 0, nil}
|
||||
return Event{AltShiftUp, 0, nil}
|
||||
}
|
||||
return Event{SUp, 0, nil}
|
||||
return Event{ShiftUp, 0, nil}
|
||||
case 'B':
|
||||
if alt {
|
||||
return Event{AltDown, 0, nil}
|
||||
}
|
||||
if altShift {
|
||||
return Event{AltSDown, 0, nil}
|
||||
return Event{AltShiftDown, 0, nil}
|
||||
}
|
||||
return Event{SDown, 0, nil}
|
||||
return Event{ShiftDown, 0, nil}
|
||||
case 'C':
|
||||
if alt {
|
||||
return Event{AltRight, 0, nil}
|
||||
}
|
||||
if altShift {
|
||||
return Event{AltSRight, 0, nil}
|
||||
return Event{AltShiftRight, 0, nil}
|
||||
}
|
||||
return Event{SRight, 0, nil}
|
||||
return Event{ShiftRight, 0, nil}
|
||||
case 'D':
|
||||
if alt {
|
||||
return Event{AltLeft, 0, nil}
|
||||
}
|
||||
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[3]
|
||||
@@ -808,13 +821,25 @@ func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
|
||||
color = ColPreviewBorder
|
||||
}
|
||||
hw := runeWidth(w.border.top)
|
||||
if top {
|
||||
pad := repeat(' ', w.width/hw)
|
||||
|
||||
w.Move(0, 0)
|
||||
if top {
|
||||
w.CPrint(color, repeat(w.border.top, w.width/hw))
|
||||
} else {
|
||||
w.CPrint(color, pad)
|
||||
}
|
||||
if bottom {
|
||||
|
||||
for y := 1; y < w.height-1; y++ {
|
||||
w.Move(y, 0)
|
||||
w.CPrint(color, pad)
|
||||
}
|
||||
|
||||
w.Move(w.height-1, 0)
|
||||
if bottom {
|
||||
w.CPrint(color, repeat(w.border.bottom, w.width/hw))
|
||||
} else {
|
||||
w.CPrint(color, pad)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ func openTtyIn() *os.File {
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(os.Stderr, "Failed to open "+consoleDevice)
|
||||
os.Exit(2)
|
||||
util.Exit(2)
|
||||
}
|
||||
return in
|
||||
}
|
||||
|
||||
@@ -320,16 +320,16 @@ func (r *FullscreenRenderer) GetChar() Event {
|
||||
switch ev.Rune() {
|
||||
case 0:
|
||||
if ctrl {
|
||||
return Event{BSpace, 0, nil}
|
||||
return Event{Backspace, 0, nil}
|
||||
}
|
||||
case rune(tcell.KeyCtrlH):
|
||||
switch {
|
||||
case ctrl:
|
||||
return keyfn('h')
|
||||
case alt:
|
||||
return Event{AltBS, 0, nil}
|
||||
return Event{AltBackspace, 0, nil}
|
||||
case none, shift:
|
||||
return Event{BSpace, 0, nil}
|
||||
return Event{Backspace, 0, nil}
|
||||
}
|
||||
}
|
||||
case tcell.KeyCtrlI:
|
||||
@@ -382,17 +382,17 @@ func (r *FullscreenRenderer) GetChar() Event {
|
||||
// section 3: (Alt)+Backspace2
|
||||
case tcell.KeyBackspace2:
|
||||
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)
|
||||
case tcell.KeyUp:
|
||||
if altShift {
|
||||
return Event{AltSUp, 0, nil}
|
||||
return Event{AltShiftUp, 0, nil}
|
||||
}
|
||||
if shift {
|
||||
return Event{SUp, 0, nil}
|
||||
return Event{ShiftUp, 0, nil}
|
||||
}
|
||||
if alt {
|
||||
return Event{AltUp, 0, nil}
|
||||
@@ -400,10 +400,10 @@ func (r *FullscreenRenderer) GetChar() Event {
|
||||
return Event{Up, 0, nil}
|
||||
case tcell.KeyDown:
|
||||
if altShift {
|
||||
return Event{AltSDown, 0, nil}
|
||||
return Event{AltShiftDown, 0, nil}
|
||||
}
|
||||
if shift {
|
||||
return Event{SDown, 0, nil}
|
||||
return Event{ShiftDown, 0, nil}
|
||||
}
|
||||
if alt {
|
||||
return Event{AltDown, 0, nil}
|
||||
@@ -411,10 +411,10 @@ func (r *FullscreenRenderer) GetChar() Event {
|
||||
return Event{Down, 0, nil}
|
||||
case tcell.KeyLeft:
|
||||
if altShift {
|
||||
return Event{AltSLeft, 0, nil}
|
||||
return Event{AltShiftLeft, 0, nil}
|
||||
}
|
||||
if shift {
|
||||
return Event{SLeft, 0, nil}
|
||||
return Event{ShiftLeft, 0, nil}
|
||||
}
|
||||
if alt {
|
||||
return Event{AltLeft, 0, nil}
|
||||
@@ -422,10 +422,10 @@ func (r *FullscreenRenderer) GetChar() Event {
|
||||
return Event{Left, 0, nil}
|
||||
case tcell.KeyRight:
|
||||
if altShift {
|
||||
return Event{AltSRight, 0, nil}
|
||||
return Event{AltShiftRight, 0, nil}
|
||||
}
|
||||
if shift {
|
||||
return Event{SRight, 0, nil}
|
||||
return Event{ShiftRight, 0, nil}
|
||||
}
|
||||
if alt {
|
||||
return Event{AltRight, 0, nil}
|
||||
@@ -442,17 +442,17 @@ func (r *FullscreenRenderer) GetChar() Event {
|
||||
return Event{CtrlDelete, 0, nil}
|
||||
}
|
||||
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:
|
||||
return Event{End, 0, nil}
|
||||
case tcell.KeyPgUp:
|
||||
return Event{PgUp, 0, nil}
|
||||
return Event{PageUp, 0, nil}
|
||||
case tcell.KeyPgDn:
|
||||
return Event{PgDn, 0, nil}
|
||||
return Event{PageDown, 0, nil}
|
||||
case tcell.KeyBacktab:
|
||||
return Event{BTab, 0, nil}
|
||||
return Event{ShiftTab, 0, nil}
|
||||
case tcell.KeyF1:
|
||||
return Event{F1, 0, nil}
|
||||
case tcell.KeyF2:
|
||||
@@ -498,7 +498,7 @@ func (r *FullscreenRenderer) GetChar() Event {
|
||||
|
||||
// section 7: Esc
|
||||
case tcell.KeyEsc:
|
||||
return Event{ESC, 0, nil}
|
||||
return Event{Esc, 0, nil}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -544,7 +544,7 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int,
|
||||
height: height,
|
||||
normal: normal,
|
||||
borderStyle: borderStyle}
|
||||
w.drawBorder(false)
|
||||
w.Erase()
|
||||
return w
|
||||
}
|
||||
|
||||
@@ -561,8 +561,8 @@ func fill(x, y, w, h int, n ColorPair, r rune) {
|
||||
}
|
||||
|
||||
func (w *TcellWindow) Erase() {
|
||||
w.drawBorder(false)
|
||||
fill(w.left-1, w.top, w.width+1, w.height-1, w.normal, ' ')
|
||||
w.drawBorder(false)
|
||||
}
|
||||
|
||||
func (w *TcellWindow) EraseMaybe() bool {
|
||||
|
||||
@@ -102,22 +102,22 @@ func TestGetCharEventKey(t *testing.T) {
|
||||
// KeyBackspace2 is alias for KeyDEL = 0x7F (ASCII) (allegedly unused by Windows)
|
||||
// KeyDelete = 0x2E (VK_DELETE constant in Windows)
|
||||
// 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.ModAlt}, wantKey{AltBS, 0, nil}}, // fabricated
|
||||
{giveKey{tcell.KeyDEL, 0, tcell.ModNone}, wantKey{BSpace, 0, nil}}, // fabricated, unhandled
|
||||
{giveKey{tcell.KeyDelete, 0, tcell.ModNone}, wantKey{Del, 0, nil}},
|
||||
{giveKey{tcell.KeyDelete, 0, tcell.ModAlt}, wantKey{Del, 0, nil}},
|
||||
{giveKey{tcell.KeyBackspace2, 0, tcell.ModNone}, wantKey{Backspace, 0, nil}}, // fabricated
|
||||
{giveKey{tcell.KeyBackspace2, 0, tcell.ModAlt}, wantKey{AltBackspace, 0, nil}}, // fabricated
|
||||
{giveKey{tcell.KeyDEL, 0, tcell.ModNone}, wantKey{Backspace, 0, nil}}, // fabricated, unhandled
|
||||
{giveKey{tcell.KeyDelete, 0, tcell.ModNone}, wantKey{Delete, 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.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, rune(tcell.KeyCtrlH), tcell.ModNone}, wantKey{BSpace, 0, nil}}, // actual "Backspace" keystroke
|
||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModAlt}, wantKey{AltBS, 0, nil}}, // actual "Alt+Backspace" keystroke
|
||||
{giveKey{tcell.KeyDEL, rune(tcell.KeyDEL), tcell.ModCtrl}, wantKey{BSpace, 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, 0, tcell.ModCtrl | tcell.ModAlt}, wantKey{BSpace, 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, rune(tcell.KeyCtrlH), tcell.ModShift | tcell.ModAlt}, wantKey{AltBS, 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, rune(tcell.KeyCtrlH), tcell.ModNone}, wantKey{Backspace, 0, nil}}, // actual "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{Backspace, 0, nil}}, // actual "Ctrl+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{Backspace, 0, nil}}, // actual "Ctrl+Alt+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{AltBackspace, 0, nil}}, // actual "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 | 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
|
||||
@@ -126,8 +126,8 @@ func TestGetCharEventKey(t *testing.T) {
|
||||
// section 4: (Alt+Shift)+Key(Up|Down|Left|Right)
|
||||
{giveKey{tcell.KeyUp, 0, tcell.ModNone}, wantKey{Up, 0, nil}},
|
||||
{giveKey{tcell.KeyDown, 0, tcell.ModAlt}, wantKey{AltDown, 0, nil}},
|
||||
{giveKey{tcell.KeyLeft, 0, tcell.ModShift}, wantKey{SLeft, 0, nil}},
|
||||
{giveKey{tcell.KeyRight, 0, tcell.ModShift | tcell.ModAlt}, wantKey{AltSRight, 0, nil}},
|
||||
{giveKey{tcell.KeyLeft, 0, tcell.ModShift}, wantKey{ShiftLeft, 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.KeyUpRight, 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
|
||||
// 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}}, // 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.KeyCtrlLeftSq, rune(tcell.KeyCtrlLeftSq), tcell.ModCtrl}, wantKey{ESC, 0, nil}}, // fabricated, unhandled
|
||||
{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.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.KeyCtrlLeftSq, rune(tcell.KeyCtrlLeftSq), tcell.ModCtrl}, wantKey{Esc, 0, nil}}, // fabricated, unhandled
|
||||
|
||||
// section 8: Invalid
|
||||
{giveKey{tcell.KeyRune, 'a', tcell.ModMeta}, wantKey{Rune, 'a', nil}}, // fabricated
|
||||
@@ -259,7 +259,7 @@ Quick reference
|
||||
37 LeftClick
|
||||
38 RightClick
|
||||
39 BTab
|
||||
40 BSpace
|
||||
40 Backspace
|
||||
41 Del
|
||||
42 PgUp
|
||||
43 PgDn
|
||||
@@ -272,7 +272,7 @@ Quick reference
|
||||
50 Insert
|
||||
51 SUp
|
||||
52 SDown
|
||||
53 SLeft
|
||||
53 ShiftLeft
|
||||
54 SRight
|
||||
55 F1
|
||||
56 F2
|
||||
@@ -288,15 +288,15 @@ Quick reference
|
||||
66 F12
|
||||
67 Change
|
||||
68 BackwardEOF
|
||||
69 AltBS
|
||||
69 AltBackspace
|
||||
70 AltUp
|
||||
71 AltDown
|
||||
72 AltLeft
|
||||
73 AltRight
|
||||
74 AltSUp
|
||||
75 AltSDown
|
||||
76 AltSLeft
|
||||
77 AltSRight
|
||||
76 AltShiftLeft
|
||||
77 AltShiftRight
|
||||
78 Alt
|
||||
79 CtrlAlt
|
||||
..
|
||||
|
||||
116
src/tui/tui.go
116
src/tui/tui.go
@@ -6,10 +6,13 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/junegunn/fzf/src/util"
|
||||
"github.com/rivo/uniseg"
|
||||
)
|
||||
|
||||
// Types of user action
|
||||
//
|
||||
//go:generate stringer -type=EventType
|
||||
type EventType int
|
||||
|
||||
const (
|
||||
@@ -41,7 +44,7 @@ const (
|
||||
CtrlX
|
||||
CtrlY
|
||||
CtrlZ
|
||||
ESC
|
||||
Esc
|
||||
CtrlSpace
|
||||
CtrlDelete
|
||||
|
||||
@@ -51,27 +54,12 @@ const (
|
||||
CtrlCaret
|
||||
CtrlSlash
|
||||
|
||||
Invalid
|
||||
Resize
|
||||
Mouse
|
||||
DoubleClick
|
||||
LeftClick
|
||||
RightClick
|
||||
SLeftClick
|
||||
SRightClick
|
||||
ScrollUp
|
||||
ScrollDown
|
||||
SScrollUp
|
||||
SScrollDown
|
||||
PreviewScrollUp
|
||||
PreviewScrollDown
|
||||
ShiftTab
|
||||
Backspace
|
||||
|
||||
BTab
|
||||
BSpace
|
||||
|
||||
Del
|
||||
PgUp
|
||||
PgDn
|
||||
Delete
|
||||
PageUp
|
||||
PageDown
|
||||
|
||||
Up
|
||||
Down
|
||||
@@ -81,11 +69,11 @@ const (
|
||||
End
|
||||
Insert
|
||||
|
||||
SUp
|
||||
SDown
|
||||
SLeft
|
||||
SRight
|
||||
SDelete
|
||||
ShiftUp
|
||||
ShiftDown
|
||||
ShiftLeft
|
||||
ShiftRight
|
||||
ShiftDelete
|
||||
|
||||
F1
|
||||
F2
|
||||
@@ -100,6 +88,38 @@ const (
|
||||
F11
|
||||
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
|
||||
BackwardEOF
|
||||
Start
|
||||
@@ -108,21 +128,8 @@ const (
|
||||
One
|
||||
Zero
|
||||
Result
|
||||
|
||||
AltBS
|
||||
|
||||
AltUp
|
||||
AltDown
|
||||
AltLeft
|
||||
AltRight
|
||||
|
||||
AltSUp
|
||||
AltSDown
|
||||
AltSLeft
|
||||
AltSRight
|
||||
|
||||
Alt
|
||||
CtrlAlt
|
||||
Jump
|
||||
JumpCancel
|
||||
)
|
||||
|
||||
func (t EventType) AsEvent() Event {
|
||||
@@ -142,6 +149,31 @@ func (e Event) Comparable() Event {
|
||||
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 {
|
||||
return Event{Rune, r, nil}
|
||||
}
|
||||
@@ -646,7 +678,7 @@ func NoColorTheme() *ColorTheme {
|
||||
|
||||
func errorExit(message string) {
|
||||
fmt.Fprintln(os.Stderr, message)
|
||||
os.Exit(2)
|
||||
util.Exit(2)
|
||||
}
|
||||
|
||||
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 {
|
||||
return string(runes)
|
||||
}
|
||||
return string(chars.slice)
|
||||
return unsafe.String(unsafe.SliceData(chars.slice), len(chars.slice))
|
||||
}
|
||||
|
||||
func (chars *Chars) ToRunes() []rune {
|
||||
@@ -178,12 +178,12 @@ func (chars *Chars) ToRunes() []rune {
|
||||
return runes
|
||||
}
|
||||
|
||||
func (chars *Chars) CopyRunes(dest []rune) {
|
||||
func (chars *Chars) CopyRunes(dest []rune, from int) {
|
||||
if runes := chars.optionalRunes(); runes != nil {
|
||||
copy(dest, runes)
|
||||
copy(dest, runes[from:])
|
||||
return
|
||||
}
|
||||
for idx, b := range chars.slice[:len(dest)] {
|
||||
for idx, b := range chars.slice[from:][:len(dest)] {
|
||||
dest[idx] = rune(b)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,3 +176,15 @@ func RepeatToFill(str string, length int, limit int) string {
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
133
test/test_go.rb
133
test/test_go.rb
@@ -18,7 +18,6 @@ UNSETS = %w[
|
||||
FZF_ALT_C_COMMAND
|
||||
FZF_ALT_C_OPTS FZF_CTRL_R_OPTS
|
||||
FZF_API_KEY
|
||||
fish_history
|
||||
].freeze
|
||||
DEFAULT_TIMEOUT = 10
|
||||
|
||||
@@ -67,7 +66,7 @@ class Shell
|
||||
end
|
||||
|
||||
def fish
|
||||
UNSETS.map { |v| v + '= ' }.join + ' FZF_DEFAULT_OPTS=--no-scrollbar fish'
|
||||
"unset #{UNSETS.join(' ')}; FZF_DEFAULT_OPTS=--no-scrollbar fish_history= fish"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1469,6 +1468,19 @@ class TestGoFZF < TestBase
|
||||
assert_equal '3', readonce.chomp
|
||||
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
|
||||
tmux.send_keys "seq 10 | #{fzf("--pointer '>>'")}", :Enter
|
||||
# Assert that specified pointer is displayed
|
||||
@@ -1720,7 +1732,7 @@ class TestGoFZF < TestBase
|
||||
end
|
||||
|
||||
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] }
|
||||
end
|
||||
|
||||
@@ -1918,7 +1930,7 @@ class TestGoFZF < TestBase
|
||||
end
|
||||
|
||||
def test_reload
|
||||
tmux.send_keys %(seq 1000 | #{FZF} --bind 'change:reload(seq {q}),a:reload(seq 100),b:reload:seq 200' --header-lines 2 --multi 2), :Enter
|
||||
tmux.send_keys %(seq 1000 | #{FZF} --bind 'change:reload(seq $FZF_QUERY),a:reload(seq 100),b:reload:seq 200' --header-lines 2 --multi 2), :Enter
|
||||
tmux.until { |lines| assert_equal 998, lines.match_count }
|
||||
tmux.send_keys 'a'
|
||||
tmux.until do |lines|
|
||||
@@ -2464,6 +2476,84 @@ class TestGoFZF < TestBase
|
||||
tmux.until { |lines| assert_equal 10, lines.match_count }
|
||||
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
|
||||
tmux.send_keys "seq 1000 | #{FZF} --scroll-off=3 --bind l:last", :Enter
|
||||
tmux.until { |lines| assert_equal 1000, lines.item_count }
|
||||
@@ -2582,6 +2672,7 @@ class TestGoFZF < TestBase
|
||||
def test_change_preview_window_rotate
|
||||
tmux.send_keys "seq 100 | #{FZF} --preview-window left,border-none --preview 'echo hello' --bind '" \
|
||||
"a:change-preview-window(right|down|up|hidden|)'", :Enter
|
||||
tmux.until { |lines| assert(lines.any? { _1.include?('100/100') }) }
|
||||
3.times do
|
||||
tmux.until { |lines| lines[0].start_with?('hello') }
|
||||
tmux.send_keys 'a'
|
||||
@@ -2799,6 +2890,27 @@ class TestGoFZF < TestBase
|
||||
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
|
||||
tmux.send_keys 'seq 100 | fzf -q55', :Enter
|
||||
tmux.until { assert_includes(_1[-2], ' 1/100 ─') }
|
||||
@@ -3002,7 +3114,7 @@ class TestGoFZF < TestBase
|
||||
end
|
||||
tmux.send_keys :t
|
||||
tmux.until do |lines|
|
||||
assert_includes lines[-2], '+T'
|
||||
assert_includes lines[-2], '+t'
|
||||
end
|
||||
tmux.send_keys :BSpace
|
||||
tmux.until do |lines|
|
||||
@@ -3014,7 +3126,7 @@ class TestGoFZF < TestBase
|
||||
tmux.send_keys '4'
|
||||
tmux.until do |lines|
|
||||
assert_equal 28, lines.match_count
|
||||
refute_includes lines[-2], '+T'
|
||||
refute_includes lines[-2], '+t'
|
||||
end
|
||||
tmux.send_keys :BSpace
|
||||
tmux.until do |lines|
|
||||
@@ -3023,11 +3135,11 @@ class TestGoFZF < TestBase
|
||||
end
|
||||
tmux.send_keys :t
|
||||
tmux.until do |lines|
|
||||
assert_includes lines[-2], '+T'
|
||||
assert_includes lines[-2], '+t'
|
||||
end
|
||||
tmux.send_keys :Up
|
||||
tmux.until do |lines|
|
||||
refute_includes lines[-2], '+T'
|
||||
refute_includes lines[-2], '+t'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3307,7 +3419,10 @@ module CompletionTest
|
||||
tmux.send_keys 'cat /tmp/fzf\ test/**', :Tab
|
||||
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||
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.until(true) { |lines| assert_equal 'cat /tmp/fzf\ test/foobar', lines[-1] }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user