mirror of
https://github.com/junegunn/fzf.git
synced 2026-06-16 12:38:07 +08:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 647cac786f | |||
| 3c9965a61a | |||
| 3951df8537 | |||
| 5dd698b869 | |||
| f5fbfd848e | |||
| dea72834ed | |||
| abee152255 | |||
| bf114bcc21 | |||
| 838ac7554b | |||
| ae78a5c56d | |||
| 7d647c70c2 | |||
| 6bd17f8f9a | |||
| 249a6df4a4 | |||
| a50619388d | |||
| 5ef8dea36e | |||
| 845752f305 | |||
| 9a61a1457d | |||
| dfcacb443d | |||
| 5412f39b84 | |||
| 07c5cd4185 | |||
| ce4bef7595 | |||
| 25868a62f7 | |||
| 7963a2c658 | |||
| 4b23aa45a8 | |||
| 3953d1c649 | |||
| 5e137613d3 |
@@ -28,7 +28,7 @@ jobs:
|
|||||||
go-version: "1.23"
|
go-version: "1.23"
|
||||||
|
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
uses: ruby/setup-ruby@97ecb7b512899eb71ab1bf2310a624c6f1589ac6 # v1
|
uses: ruby/setup-ruby@89f90524b88a01fe6e0b732220432cc6142926af # v1
|
||||||
with:
|
with:
|
||||||
ruby-version: 3.4.6
|
ruby-version: 3.4.6
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ jobs:
|
|||||||
go-version: "1.23"
|
go-version: "1.23"
|
||||||
|
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
uses: ruby/setup-ruby@97ecb7b512899eb71ab1bf2310a624c6f1589ac6 # v1
|
uses: ruby/setup-ruby@89f90524b88a01fe6e0b732220432cc6142926af # v1
|
||||||
with:
|
with:
|
||||||
ruby-version: 3.0.0
|
ruby-version: 3.0.0
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: 'Version to validate (e.g. 0.73.0).'
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: macos-latest
|
||||||
|
environment: release
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version: stable
|
||||||
|
|
||||||
|
- name: Determine version
|
||||||
|
id: ver
|
||||||
|
run: |
|
||||||
|
if [ "${{ github.event_name }}" = "push" ]; then
|
||||||
|
v=${GITHUB_REF_NAME#v}
|
||||||
|
else
|
||||||
|
v='${{ inputs.version }}'
|
||||||
|
fi
|
||||||
|
echo "version=$v" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Resolved version: '$v'"
|
||||||
|
|
||||||
|
- name: Verify version consistency
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
V='${{ steps.ver.outputs.version }}'
|
||||||
|
R=$(echo "$V" | sed 's/\./\\./g')
|
||||||
|
grep -q "^${R}$" CHANGELOG.md
|
||||||
|
grep -qF "\"fzf ${V}\"" man/man1/fzf.1
|
||||||
|
grep -qF "\"fzf ${V}\"" man/man1/fzf-tmux.1
|
||||||
|
grep -qF "${V}" install
|
||||||
|
grep -qF "${V}" install.ps1
|
||||||
|
|
||||||
|
- name: Extract release notes
|
||||||
|
run: |
|
||||||
|
set -e
|
||||||
|
mkdir -p tmp
|
||||||
|
V='${{ steps.ver.outputs.version }}'
|
||||||
|
R=$(echo "$V" | sed 's/\./\\./g')
|
||||||
|
sed -n "/^${R}$/,/^[0-9]/p" CHANGELOG.md \
|
||||||
|
| tail -r | sed '1,/^ *$/d' | tail -r | sed '1,2d' \
|
||||||
|
| tee tmp/release-note
|
||||||
|
|
||||||
|
- name: Run goreleaser
|
||||||
|
uses: goreleaser/goreleaser-action@v7
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
args: >-
|
||||||
|
${{ github.event_name == 'push'
|
||||||
|
&& 'release --clean --release-notes tmp/release-note'
|
||||||
|
|| 'release --snapshot --clean --skip=publish' }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.RELEASE_PAT }}
|
||||||
|
MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }}
|
||||||
|
MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }}
|
||||||
|
MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}
|
||||||
|
MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
|
||||||
|
MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }}
|
||||||
@@ -2,6 +2,12 @@ name: Publish to Winget
|
|||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types: [released]
|
types: [released]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
release-tag:
|
||||||
|
description: 'Release tag to submit (e.g. v0.73.1)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
@@ -10,5 +16,6 @@ jobs:
|
|||||||
- uses: vedantmgoyal2009/winget-releaser@4ffc7888bffd451b357355dc214d43bb9f23917e # v2
|
- uses: vedantmgoyal2009/winget-releaser@4ffc7888bffd451b357355dc214d43bb9f23917e # v2
|
||||||
with:
|
with:
|
||||||
identifier: junegunn.fzf
|
identifier: junegunn.fzf
|
||||||
|
release-tag: ${{ inputs.release-tag || github.event.release.tag_name }}
|
||||||
installers-regex: '-windows_(armv7|arm64|amd64)\.zip$'
|
installers-regex: '-windows_(armv7|arm64|amd64)\.zip$'
|
||||||
token: ${{ secrets.WINGET_TOKEN }}
|
token: ${{ secrets.WINGET_TOKEN }}
|
||||||
|
|||||||
@@ -1,6 +1,24 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.74.0 (WIP)
|
||||||
|
------------
|
||||||
|
- Added `result-final` event, a variant of `result` that is not triggered while the input stream is still open (#4835)
|
||||||
|
- Use it for one-shot, per-query actions that would otherwise re-fire on every intermediate snapshot during loading
|
||||||
|
```sh
|
||||||
|
# 'result' fires per intermediate snapshot (header keeps updating during load);
|
||||||
|
# 'result-final' fires once after the stream closes (footer shows the final count)
|
||||||
|
(seq 100; sleep 1; seq 100) | fzf --query 1 \
|
||||||
|
--bind 'result:transform-header(echo result: $FZF_MATCH_COUNT),result-final:transform-footer(echo final: $FZF_MATCH_COUNT)'
|
||||||
|
```
|
||||||
|
- Bound `alt-left` to `backward-word` and `alt-right` to `forward-word` by default (#4833)
|
||||||
|
|
||||||
|
0.73.1
|
||||||
|
------
|
||||||
|
- Bug fixes
|
||||||
|
- Skip `$FZF_CURRENT_ITEM` export when the item contains a NUL byte; `exec(2)` rejects the env, breaking preview and other child commands (#4806)
|
||||||
|
- Fixed O(n^2) HTTP body accumulation in `--listen`; a single ~390 KB request could block the single-threaded server for ~8 s (Michal Majchrowicz, Marcin Wyczechowski, AFINE Team)
|
||||||
|
|
||||||
0.73.0
|
0.73.0
|
||||||
------
|
------
|
||||||
_Release highlights: https://junegunn.github.io/fzf/releases/0.73.0/_
|
_Release highlights: https://junegunn.github.io/fzf/releases/0.73.0/_
|
||||||
|
|||||||
+11
-1
@@ -1,5 +1,15 @@
|
|||||||
FROM rubylang/ruby:3.4.1-noble
|
FROM rubylang/ruby:3.4.1-noble
|
||||||
RUN apt-get update -y && apt install -y git make golang zsh fish tmux
|
RUN apt-get update && apt-get install -y git make golang zsh fish tmux
|
||||||
|
|
||||||
|
# https://www.nushell.sh/book/installation.html
|
||||||
|
RUN <<EOF
|
||||||
|
set -ex
|
||||||
|
apt-get install -y wget gnupg
|
||||||
|
wget -qO- https://apt.fury.io/nushell/gpg.key | gpg --dearmor -o /etc/apt/keyrings/fury-nushell.gpg
|
||||||
|
echo "deb [signed-by=/etc/apt/keyrings/fury-nushell.gpg] https://apt.fury.io/nushell/ /" | tee /etc/apt/sources.list.d/fury-nushell.list
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y nushell
|
||||||
|
EOF
|
||||||
RUN gem install --no-document -v 5.22.3 minitest
|
RUN gem install --no-document -v 5.22.3 minitest
|
||||||
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
||||||
RUN echo '. ~/.bashrc' >> ~/.bash_profile
|
RUN echo '. ~/.bashrc' >> ~/.bash_profile
|
||||||
|
|||||||
@@ -117,6 +117,19 @@ generate:
|
|||||||
build:
|
build:
|
||||||
goreleaser build --clean --snapshot --skip=post-hooks
|
goreleaser build --clean --snapshot --skip=post-hooks
|
||||||
|
|
||||||
|
prerelease:
|
||||||
|
# Check if version numbers are properly updated
|
||||||
|
grep -q ^$(VERSION_REGEX)$$ CHANGELOG.md
|
||||||
|
grep -qF '"fzf $(VERSION_TRIM)"' man/man1/fzf.1
|
||||||
|
grep -qF '"fzf $(VERSION_TRIM)"' man/man1/fzf-tmux.1
|
||||||
|
grep -qF $(VERSION) install
|
||||||
|
grep -qF $(VERSION) install.ps1
|
||||||
|
@echo "OK: all files consistent at $(VERSION)"
|
||||||
|
|
||||||
|
tag: prerelease
|
||||||
|
git tag -s v$(VERSION) -m v$(VERSION)
|
||||||
|
git push origin v$(VERSION)
|
||||||
|
|
||||||
release:
|
release:
|
||||||
# Make sure that the tests pass and the build works
|
# Make sure that the tests pass and the build works
|
||||||
TAGS=tcell make test
|
TAGS=tcell make test
|
||||||
@@ -206,4 +219,4 @@ update:
|
|||||||
$(GO) get -u
|
$(GO) get -u
|
||||||
$(GO) mod tidy
|
$(GO) mod tidy
|
||||||
|
|
||||||
.PHONY: all generate build release test itest bench lint install clean docker docker-test update fmt
|
.PHONY: all generate build prerelease tag release test itest bench lint install clean docker docker-test update fmt
|
||||||
|
|||||||
@@ -25,22 +25,22 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
fzf is a general-purpose command-line fuzzy finder.
|
fzf is a general-purpose command-line fuzzy finder and an interactive terminal toolkit.
|
||||||
|
|
||||||
<img src="https://raw.githubusercontent.com/junegunn/i/master/fzf-preview.png" width=640>
|
<img src="https://raw.githubusercontent.com/junegunn/i/master/fzf-preview.png" width=640>
|
||||||
|
|
||||||
It's an interactive filter program for any kind of list; files, command
|
Whether you're selecting files, browsing command history, previewing data,
|
||||||
history, processes, hostnames, bookmarks, git commits, etc. It implements
|
navigating complex datasets with fuzzy matching, or creating custom menus and
|
||||||
a "fuzzy" matching algorithm, so you can quickly type in patterns with omitted
|
workflows, fzf provides the building blocks to turn shell scripts into rich
|
||||||
characters and still get the results you want.
|
terminal applications.
|
||||||
|
|
||||||
Highlights
|
Highlights
|
||||||
----------
|
----------
|
||||||
|
|
||||||
- **Portable** -- Distributed as a single binary for easy installation
|
- **Portable** // Distributed as a single binary for easy installation
|
||||||
- **Fast** -- Optimized to process millions of items instantly
|
- **Fast** // Optimized to process millions of items in milliseconds
|
||||||
- **Versatile** -- Fully customizable through an event-action binding mechanism
|
- **Programmable** // Event-driven architecture for building custom terminal interfaces and workflows
|
||||||
- **All-inclusive** -- Comes with integrations for Bash, Zsh, Fish, Nushell, Vim, and Neovim
|
- **Batteries-included** // Comes with integrations for Bash, Zsh, Fish, Nushell, Vim, and Neovim
|
||||||
|
|
||||||
Table of Contents
|
Table of Contents
|
||||||
-----------------
|
-----------------
|
||||||
|
|||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
Release process
|
||||||
|
===============
|
||||||
|
|
||||||
|
Building, signing, notarizing, and publishing is handled by
|
||||||
|
[`.github/workflows/release.yml`](.github/workflows/release.yml),
|
||||||
|
triggered by a tag push.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Update version in the following files and commit on `master`:
|
||||||
|
- `CHANGELOG.md`
|
||||||
|
- `main.go`
|
||||||
|
- `install`
|
||||||
|
- `install.ps1`
|
||||||
|
- `man/man1/fzf.1`
|
||||||
|
- `man/man1/fzf-tmux.1`
|
||||||
|
|
||||||
|
2. Verify file consistency, sign the tag, and push the tag.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make tag VERSION=0.73.1
|
||||||
|
```
|
||||||
|
|
||||||
|
`make tag` runs `prerelease` first (checks that the version
|
||||||
|
appears in CHANGELOG.md, both man pages, install, and install.ps1)
|
||||||
|
and only signs + pushes the tag if the checks pass.
|
||||||
|
|
||||||
|
Only the tag is pushed; `master` on origin still points to the
|
||||||
|
old version, so `/master/install` keeps resolving against existing
|
||||||
|
binaries during the publish window.
|
||||||
|
|
||||||
|
3. The workflow fires on the tag push and pauses on the `release`
|
||||||
|
environment gate. Approve it in the Actions tab to release.
|
||||||
|
|
||||||
|
4. After the GitHub release is published, fast-forward `master`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git push origin master
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing the workflow
|
||||||
|
|
||||||
|
To exercise the workflow without firing a real release:
|
||||||
|
|
||||||
|
1. Actions tab -> **Release** -> **Run workflow**.
|
||||||
|
2. Pick a branch and enter the version currently on that branch
|
||||||
|
(the version-consistency check requires the input to match the
|
||||||
|
files in the checked-out tree).
|
||||||
|
3. Approve the `release` environment gate when prompted.
|
||||||
|
4. Goreleaser runs with `--snapshot --skip=publish`. Signing and
|
||||||
|
notarization run; only the GitHub release upload is skipped.
|
||||||
|
|
||||||
|
Use this to validate the workflow YAML, version-extraction logic,
|
||||||
|
the macOS runner setup, and the signing/notarization credentials.
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.73.0
|
version=0.73.1
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
@@ -227,13 +227,13 @@ fi
|
|||||||
|
|
||||||
for s in $shells; do
|
for s in $shells; do
|
||||||
bin=$s
|
bin=$s
|
||||||
[[ "$s" = nushell ]] && bin=nu
|
[[ $s == nushell ]] && bin=nu
|
||||||
if ! command -v "$bin" > /dev/null; then
|
if ! command -v "$bin" > /dev/null; then
|
||||||
shells=${shells/$s/}
|
shells=${shells/$s/}
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ ${#shells} -lt 3 ]]; then
|
if [[ -z ${shells// /} ]]; then
|
||||||
echo "No shell configuration to be updated."
|
echo "No shell configuration to be updated."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
@@ -252,10 +252,10 @@ fi
|
|||||||
|
|
||||||
echo
|
echo
|
||||||
for shell in $shells; do
|
for shell in $shells; do
|
||||||
|
[[ $shell == nushell ]] && continue
|
||||||
fzf_completion="source \"$fzf_base/shell/completion.${shell}\""
|
fzf_completion="source \"$fzf_base/shell/completion.${shell}\""
|
||||||
fzf_key_bindings="source \"$fzf_base/shell/key-bindings.${shell}\""
|
fzf_key_bindings="source \"$fzf_base/shell/key-bindings.${shell}\""
|
||||||
[[ $shell == fish ]] && continue
|
[[ $shell == fish ]] && continue
|
||||||
[[ $shell == nushell ]] && continue
|
|
||||||
src=${prefix_expand}.${shell}
|
src=${prefix_expand}.${shell}
|
||||||
echo -n "Generate $src ... "
|
echo -n "Generate $src ... "
|
||||||
|
|
||||||
@@ -442,7 +442,7 @@ if [[ $shells =~ fish ]]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$shells" =~ nushell ]]; then
|
if [[ $shells =~ nushell ]]; then
|
||||||
if [[ $key_bindings -eq 1 || $auto_completion -eq 1 ]]; then
|
if [[ $key_bindings -eq 1 || $auto_completion -eq 1 ]]; then
|
||||||
echo "Setting up Nushell integration ..."
|
echo "Setting up Nushell integration ..."
|
||||||
nushell_autoload_dir=$(nu -c '$nu.user-autoload-dirs | first')
|
nushell_autoload_dir=$(nu -c '$nu.user-autoload-dirs | first')
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
$version="0.73.0"
|
$version="0.73.1"
|
||||||
|
|
||||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
|
||||||
|
|||||||
+1
-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
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf\-tmux 1 "May 2026" "fzf 0.73.0" "fzf\-tmux - open fzf in tmux split pane"
|
.TH fzf\-tmux 1 "May 2026" "fzf 0.73.1" "fzf\-tmux - open fzf in tmux split pane"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf\-tmux - open fzf in tmux split pane
|
fzf\-tmux - open fzf in tmux split pane
|
||||||
|
|||||||
+22
-4
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf 1 "May 2026" "fzf 0.73.0" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "May 2026" "fzf 0.73.1" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -226,7 +226,7 @@ Enable processing of ANSI color codes
|
|||||||
Synchronous search for multi-staged filtering. If specified, fzf will launch
|
Synchronous search for multi-staged filtering. If specified, fzf will launch
|
||||||
the finder only after the input stream is complete and the initial filtering
|
the finder only after the input stream is complete and the initial filtering
|
||||||
and the associated actions (bound to any of \fBstart\fR, \fBload\fR,
|
and the associated actions (bound to any of \fBstart\fR, \fBload\fR,
|
||||||
\fBresult\fR, or \fBfocus\fR) are complete.
|
\fBresult\fR, \fBresult\-final\fR, or \fBfocus\fR) are complete.
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
e.g. \fB# Avoid rendering both fzf instances at the same time
|
e.g. \fB# Avoid rendering both fzf instances at the same time
|
||||||
@@ -1531,6 +1531,10 @@ fzf exports the following environment variables to its child processes.
|
|||||||
.br
|
.br
|
||||||
.BR FZF_RAW " Only in raw mode. 1 if the current item matches, 0 otherwise"
|
.BR FZF_RAW " Only in raw mode. 1 if the current item matches, 0 otherwise"
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.B FZF_CURRENT_ITEM
|
||||||
|
is omitted when the item contains a NUL byte, because exec(2) cannot pass it.
|
||||||
|
|
||||||
.SH EXTENDED SEARCH MODE
|
.SH EXTENDED SEARCH MODE
|
||||||
|
|
||||||
Unless specified otherwise, fzf will start in "extended\-search mode". In this
|
Unless specified otherwise, fzf will start in "extended\-search mode". In this
|
||||||
@@ -1851,6 +1855,20 @@ e.g.
|
|||||||
# * Note that you can't use 'change' event in this case because the second position may not be available
|
# * Note that you can't use 'change' event in this case because the second position may not be available
|
||||||
fzf \-\-sync \-\-bind 'result:transform:[[ \-z {q} ]] && echo "pos(2)"'\fR
|
fzf \-\-sync \-\-bind 'result:transform:[[ \-z {q} ]] && echo "pos(2)"'\fR
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
|
\fIresult\-final\fR
|
||||||
|
.RS
|
||||||
|
Same as \fIresult\fR, but suppressed while the input stream is still open. Use
|
||||||
|
this when you want a one-shot action per query instead of one per intermediate
|
||||||
|
snapshot during loading.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fB# 'result' fires per intermediate snapshot (header keeps updating during load);
|
||||||
|
# 'result-final' fires once after the stream closes (footer shows the final count)
|
||||||
|
(seq 100; sleep 1; seq 100) | fzf \-\-query 1 \\
|
||||||
|
\-\-bind 'result:transform\-header(echo result: $FZF_MATCH_COUNT),result\-final:transform\-footer(echo final: $FZF_MATCH_COUNT)'\fR
|
||||||
|
.RE
|
||||||
|
|
||||||
\fIchange\fR
|
\fIchange\fR
|
||||||
.RS
|
.RS
|
||||||
Triggered whenever the query string is changed
|
Triggered whenever the query string is changed
|
||||||
@@ -1994,7 +2012,7 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBbackward\-kill\-subword\fR
|
\fBbackward\-kill\-subword\fR
|
||||||
\fBbackward\-kill\-word\fR \fIalt\-bs\fR
|
\fBbackward\-kill\-word\fR \fIalt\-bs\fR
|
||||||
\fBbackward\-subword\fR
|
\fBbackward\-subword\fR
|
||||||
\fBbackward\-word\fR \fIalt\-b shift\-left\fR
|
\fBbackward\-word\fR \fIalt\-b shift\-left alt\-left\fR
|
||||||
\fBbecome(...)\fR (replace fzf process with the specified command; see below for the details)
|
\fBbecome(...)\fR (replace fzf process with the specified command; see below for the details)
|
||||||
\fBbeginning\-of\-line\fR \fIctrl\-a home\fR
|
\fBbeginning\-of\-line\fR \fIctrl\-a home\fR
|
||||||
\fBbell\fR (ring the terminal bell)
|
\fBbell\fR (ring the terminal bell)
|
||||||
@@ -2041,7 +2059,7 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBfirst\fR (move to the first match; same as \fBpos(1)\fR)
|
\fBfirst\fR (move to the first match; same as \fBpos(1)\fR)
|
||||||
\fBforward\-char\fR \fIctrl\-f right\fR
|
\fBforward\-char\fR \fIctrl\-f right\fR
|
||||||
\fBforward\-subword\fR
|
\fBforward\-subword\fR
|
||||||
\fBforward\-word\fR \fIalt\-f shift\-right\fR
|
\fBforward\-word\fR \fIalt\-f shift\-right alt\-right\fR
|
||||||
\fBignore\fR
|
\fBignore\fR
|
||||||
\fBjump\fR (EasyMotion-like 2-keystroke movement)
|
\fBjump\fR (EasyMotion-like 2-keystroke movement)
|
||||||
\fBkill\-line\fR
|
\fBkill\-line\fR
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ function fzf_key_bindings
|
|||||||
# Enable syntax highlighting colors on fish v4.3.3 and newer
|
# Enable syntax highlighting colors on fish v4.3.3 and newer
|
||||||
if string match -qr -- '^\\d\\d+|^4\\.[4-9]|^4\\.3\\.[3-9]' $version
|
if string match -qr -- '^\\d\\d+|^4\\.[4-9]|^4\\.3\\.[3-9]' $version
|
||||||
set -a -- FZF_DEFAULT_OPTS '--ansi'
|
set -a -- FZF_DEFAULT_OPTS '--ansi'
|
||||||
set -a -- FZF_DEFAULT_COMMAND '--color=always --show-time=(set_color $fish_color_comment)"%F %a %T%t%s%t"(set_color $fish_color_normal)'
|
set -a -- FZF_DEFAULT_COMMAND '--color=always --show-time=(set_color $fish_color_comment 2>/dev/null; or set_color normal)"%F %a %T%t%s%t"(set_color normal)'
|
||||||
else
|
else
|
||||||
set -a -- FZF_DEFAULT_COMMAND '--show-time="%F %a %T%t%s%t"'
|
set -a -- FZF_DEFAULT_COMMAND '--show-time="%F %a %T%t%s%t"'
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -110,8 +110,14 @@ fzf-cd-widget() {
|
|||||||
zle redisplay
|
zle redisplay
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
# Use subshell expansion to get the absolute PWD of the target dir.
|
||||||
|
# This allows the recorded shell history to be reused even from a different
|
||||||
|
# working directory.
|
||||||
|
# If failed, fallback to the unexpanded path to surface the error to the user.
|
||||||
|
# NOTE: Don't use the `:a` modifier as it resolves symlinks like `pwd -P`.
|
||||||
|
dir=$(builtin cd >/dev/null -- "${dir}" && echo "${PWD}" || echo "${dir}")
|
||||||
zle push-line # Clear buffer. Auto-restored on next prompt.
|
zle push-line # Clear buffer. Auto-restored on next prompt.
|
||||||
BUFFER="builtin cd -- ${(q)dir:a}"
|
BUFFER="builtin cd -- ${(q)dir}"
|
||||||
zle accept-line
|
zle accept-line
|
||||||
local ret=$?
|
local ret=$?
|
||||||
unset dir # ensure this doesn't end up appearing in prompt expansion
|
unset dir # ensure this doesn't end up appearing in prompt expansion
|
||||||
|
|||||||
+5
-1
@@ -4,6 +4,7 @@ package fzf
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
"maps"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -241,6 +242,9 @@ func Run(opts *Options) (int, error) {
|
|||||||
denylist = make(map[int32]struct{})
|
denylist = make(map[int32]struct{})
|
||||||
denyMutex.Unlock()
|
denyMutex.Unlock()
|
||||||
}
|
}
|
||||||
|
if opts.HeaderLines > math.MaxInt32 {
|
||||||
|
opts.HeaderLines = math.MaxInt32
|
||||||
|
}
|
||||||
headerLines := int32(opts.HeaderLines)
|
headerLines := int32(opts.HeaderLines)
|
||||||
headerUpdated := false
|
headerUpdated := false
|
||||||
patternBuilder := func(runes []rune) *Pattern {
|
patternBuilder := func(runes []rune) *Pattern {
|
||||||
@@ -467,7 +471,7 @@ func Run(opts *Options) (int, error) {
|
|||||||
terminal.UpdateCount(max(0, total-int(headerLines)), !reading, value.(*string))
|
terminal.UpdateCount(max(0, total-int(headerLines)), !reading, value.(*string))
|
||||||
if headerLines > 0 && !headerUpdated {
|
if headerLines > 0 && !headerUpdated {
|
||||||
terminal.UpdateHeader(GetItems(snapshot, int(headerLines)))
|
terminal.UpdateHeader(GetItems(snapshot, int(headerLines)))
|
||||||
headerUpdated = int32(total) >= headerLines
|
headerUpdated = total >= int(headerLines)
|
||||||
}
|
}
|
||||||
if heightUnknown && !deferred {
|
if heightUnknown && !deferred {
|
||||||
determine(!reading)
|
determine(!reading)
|
||||||
|
|||||||
+5
-5
@@ -10,7 +10,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unicode"
|
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/algo"
|
"github.com/junegunn/fzf/src/algo"
|
||||||
"github.com/junegunn/fzf/src/tui"
|
"github.com/junegunn/fzf/src/tui"
|
||||||
@@ -1064,6 +1063,8 @@ func parseKeyChords(str string, message string) (map[tui.Event]string, []tui.Eve
|
|||||||
add(tui.Focus)
|
add(tui.Focus)
|
||||||
case "result":
|
case "result":
|
||||||
add(tui.Result)
|
add(tui.Result)
|
||||||
|
case "result-final":
|
||||||
|
add(tui.ResultFinal)
|
||||||
case "resize":
|
case "resize":
|
||||||
add(tui.Resize)
|
add(tui.Resize)
|
||||||
case "one":
|
case "one":
|
||||||
@@ -1734,10 +1735,10 @@ Loop:
|
|||||||
return masked
|
return masked
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseSingleActionList(str string) ([]*action, error) {
|
func parseSingleActionList(str string, putAllowed bool) ([]*action, error) {
|
||||||
// We prepend a colon to satisfy argActionRegexp and remove it later
|
// We prepend a colon to satisfy argActionRegexp and remove it later
|
||||||
masked := maskActionContents(":" + str)[1:]
|
masked := maskActionContents(":" + str)[1:]
|
||||||
return parseActionList(masked, str, []*action{}, false)
|
return parseActionList(masked, str, []*action{}, putAllowed)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseActionList(masked string, original string, prevActions []*action, putAllowed bool) ([]*action, error) {
|
func parseActionList(masked string, original string, prevActions []*action, putAllowed bool) ([]*action, error) {
|
||||||
@@ -2043,8 +2044,7 @@ func parseKeymap(keymap map[tui.Event][]*action, str string) error {
|
|||||||
}
|
}
|
||||||
key = firstKey(keys)
|
key = firstKey(keys)
|
||||||
}
|
}
|
||||||
putAllowed := key.Type == tui.Rune && unicode.IsGraphic(key.Char)
|
keymap[key], err = parseActionList(pair[1], origPairStr[len(pair[0])+1:], keymap[key], key.Printable())
|
||||||
keymap[key], err = parseActionList(pair[1], origPairStr[len(pair[0])+1:], keymap[key], putAllowed)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -572,7 +572,7 @@ func TestValidateSign(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseSingleActionList(t *testing.T) {
|
func TestParseSingleActionList(t *testing.T) {
|
||||||
actions, _ := parseSingleActionList("Execute@foo+bar,baz@+up+up+reload:down+down")
|
actions, _ := parseSingleActionList("Execute@foo+bar,baz@+up+up+reload:down+down", false)
|
||||||
if len(actions) != 4 {
|
if len(actions) != 4 {
|
||||||
t.Errorf("Invalid number of actions parsed:%d", len(actions))
|
t.Errorf("Invalid number of actions parsed:%d", len(actions))
|
||||||
}
|
}
|
||||||
@@ -588,7 +588,7 @@ func TestParseSingleActionList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseSingleActionListError(t *testing.T) {
|
func TestParseSingleActionListError(t *testing.T) {
|
||||||
_, err := parseSingleActionList("change-query(foobar)baz")
|
_, err := parseSingleActionList("change-query(foobar)baz", false)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("Failed to detect error")
|
t.Errorf("Failed to detect error")
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -198,7 +198,10 @@ func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, t
|
|||||||
start := 0
|
start := 0
|
||||||
ansiToColorPair := func(ansi ansiOffset, base tui.ColorPair) tui.ColorPair {
|
ansiToColorPair := func(ansi ansiOffset, base tui.ColorPair) tui.ColorPair {
|
||||||
if !theme.Colored {
|
if !theme.Colored {
|
||||||
return tui.NewColorPair(-1, -1, ansi.color.attr).MergeAttr(base)
|
// Ignore ANSI colors but keep the attributes. Retain the base
|
||||||
|
// colors (e.g. an overridden input-bg or list-bg) instead of
|
||||||
|
// resetting to the terminal default.
|
||||||
|
return tui.NewColorPair(base.Fg(), base.Bg(), ansi.color.attr).MergeAttr(base)
|
||||||
}
|
}
|
||||||
// fd --color always | fzf --ansi --delimiter / --nth -1 --color fg:dim:strip,nth:regular
|
// fd --color always | fzf --ansi --delimiter / --nth -1 --color fg:dim:strip,nth:regular
|
||||||
if base.ShouldStripColors() {
|
if base.ShouldStripColors() {
|
||||||
|
|||||||
+5
-4
@@ -153,7 +153,7 @@ func startHttpServer(address listenAddress, actionChannel chan []*action, getHan
|
|||||||
func (server *httpServer) handleHttpRequest(conn net.Conn) string {
|
func (server *httpServer) handleHttpRequest(conn net.Conn) string {
|
||||||
contentLength := 0
|
contentLength := 0
|
||||||
apiKey := ""
|
apiKey := ""
|
||||||
body := ""
|
var bodyBuilder strings.Builder
|
||||||
answer := func(code string, message string) string {
|
answer := func(code string, message string) string {
|
||||||
message += "\n"
|
message += "\n"
|
||||||
return code + fmt.Sprintf("Content-Length: %d%s", len(message), crlf+crlf+message)
|
return code + fmt.Sprintf("Content-Length: %d%s", len(message), crlf+crlf+message)
|
||||||
@@ -175,7 +175,7 @@ func (server *httpServer) handleHttpRequest(conn net.Conn) string {
|
|||||||
token := data[:found+len(crlf)]
|
token := data[:found+len(crlf)]
|
||||||
return len(token), token, nil
|
return len(token), token, nil
|
||||||
}
|
}
|
||||||
if atEOF || len(body)+len(data) >= contentLength {
|
if atEOF || bodyBuilder.Len()+len(data) >= contentLength {
|
||||||
return 0, data, bufio.ErrFinalToken
|
return 0, data, bufio.ErrFinalToken
|
||||||
}
|
}
|
||||||
return 0, nil, nil
|
return 0, nil, nil
|
||||||
@@ -218,7 +218,7 @@ Loop:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 2: // Request body
|
case 2: // Request body
|
||||||
body += text
|
bodyBuilder.WriteString(text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,12 +234,13 @@ Loop:
|
|||||||
return answer(httpUnavailable+jsonContentType, `{"error":"timeout"}`)
|
return answer(httpUnavailable+jsonContentType, `{"error":"timeout"}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body := bodyBuilder.String()
|
||||||
if len(body) < contentLength {
|
if len(body) < contentLength {
|
||||||
return bad("incomplete request")
|
return bad("incomplete request")
|
||||||
}
|
}
|
||||||
body = body[:contentLength]
|
body = body[:contentLength]
|
||||||
|
|
||||||
actions, err := parseSingleActionList(strings.Trim(string(body), "\r\n"))
|
actions, err := parseSingleActionList(strings.Trim(string(body), "\r\n"), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return bad(err.Error())
|
return bad(err.Error())
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-6
@@ -871,8 +871,10 @@ func defaultKeymap() map[tui.Event][]*action {
|
|||||||
|
|
||||||
addEvent(tui.AltKey('b'), actBackwardWord)
|
addEvent(tui.AltKey('b'), actBackwardWord)
|
||||||
add(tui.ShiftLeft, actBackwardWord)
|
add(tui.ShiftLeft, actBackwardWord)
|
||||||
|
add(tui.AltLeft, actBackwardWord)
|
||||||
addEvent(tui.AltKey('f'), actForwardWord)
|
addEvent(tui.AltKey('f'), actForwardWord)
|
||||||
add(tui.ShiftRight, actForwardWord)
|
add(tui.ShiftRight, actForwardWord)
|
||||||
|
add(tui.AltRight, actForwardWord)
|
||||||
addEvent(tui.AltKey('d'), actKillWord)
|
addEvent(tui.AltKey('d'), actKillWord)
|
||||||
add(tui.AltBackspace, actBackwardKillWord)
|
add(tui.AltBackspace, actBackwardKillWord)
|
||||||
|
|
||||||
@@ -1152,7 +1154,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|||||||
bgSemaphore: make(chan struct{}, maxBgProcesses),
|
bgSemaphore: make(chan struct{}, maxBgProcesses),
|
||||||
bgSemaphores: make(map[action]chan struct{}),
|
bgSemaphores: make(map[action]chan struct{}),
|
||||||
keyChan: make(chan tui.Event),
|
keyChan: make(chan tui.Event),
|
||||||
eventChan: make(chan tui.Event, 6), // start | (load + result + zero|one) | (focus) | (resize)
|
eventChan: make(chan tui.Event, 7), // start | (load + result + result-final + zero|one) | (focus) | (resize)
|
||||||
timerChan: make(chan tui.Event), // unbuffered: every() ticks coalesce when main loop is busy
|
timerChan: make(chan tui.Event), // unbuffered: every() ticks coalesce when main loop is busy
|
||||||
tui: renderer,
|
tui: renderer,
|
||||||
ttyDefault: opts.TtyDefault,
|
ttyDefault: opts.TtyDefault,
|
||||||
@@ -1345,6 +1347,9 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|||||||
}
|
}
|
||||||
_, t.hasStartActions = t.keymap[tui.Start.AsEvent()]
|
_, t.hasStartActions = t.keymap[tui.Start.AsEvent()]
|
||||||
_, t.hasResultActions = t.keymap[tui.Result.AsEvent()]
|
_, t.hasResultActions = t.keymap[tui.Result.AsEvent()]
|
||||||
|
if _, prs := t.keymap[tui.ResultFinal.AsEvent()]; prs {
|
||||||
|
t.hasResultActions = true
|
||||||
|
}
|
||||||
_, t.hasFocusActions = t.keymap[tui.Focus.AsEvent()]
|
_, t.hasFocusActions = t.keymap[tui.Focus.AsEvent()]
|
||||||
_, t.hasLoadActions = t.keymap[tui.Load.AsEvent()]
|
_, t.hasLoadActions = t.keymap[tui.Load.AsEvent()]
|
||||||
|
|
||||||
@@ -1439,7 +1444,10 @@ func (t *Terminal) environImpl(forPreview bool) []string {
|
|||||||
env = append(env, fmt.Sprintf("FZF_COLUMNS=%d", t.areaColumns))
|
env = append(env, fmt.Sprintf("FZF_COLUMNS=%d", t.areaColumns))
|
||||||
env = append(env, fmt.Sprintf("FZF_POS=%d", min(t.merger.Length(), t.cy+1)))
|
env = append(env, fmt.Sprintf("FZF_POS=%d", min(t.merger.Length(), t.cy+1)))
|
||||||
if item := t.currentItem(); item != nil {
|
if item := t.currentItem(); item != nil {
|
||||||
env = append(env, "FZF_CURRENT_ITEM="+item.AsString(t.ansi))
|
// Skip if the value contains a NUL byte; exec(2) would reject the env.
|
||||||
|
if s := item.AsString(t.ansi); !strings.ContainsRune(s, 0) {
|
||||||
|
env = append(env, "FZF_CURRENT_ITEM="+s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
env = append(env, fmt.Sprintf("FZF_CLICK_HEADER_LINE=%d", t.clickHeaderLine))
|
env = append(env, fmt.Sprintf("FZF_CLICK_HEADER_LINE=%d", t.clickHeaderLine))
|
||||||
env = append(env, fmt.Sprintf("FZF_CLICK_HEADER_COLUMN=%d", t.clickHeaderColumn))
|
env = append(env, fmt.Sprintf("FZF_CLICK_HEADER_COLUMN=%d", t.clickHeaderColumn))
|
||||||
@@ -2019,8 +2027,18 @@ func (t *Terminal) UpdateList(result MatchResult) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if t.hasResultActions {
|
if t.hasResultActions {
|
||||||
t.pendingReqList = true
|
result := tui.Result.AsEvent()
|
||||||
t.eventChan <- tui.Result.AsEvent()
|
if _, prs := t.keymap[result]; prs {
|
||||||
|
t.pendingReqList = true
|
||||||
|
t.eventChan <- result
|
||||||
|
}
|
||||||
|
if !t.reading {
|
||||||
|
resultFinal := tui.ResultFinal.AsEvent()
|
||||||
|
if _, prs := t.keymap[resultFinal]; prs {
|
||||||
|
t.pendingReqList = true
|
||||||
|
t.eventChan <- resultFinal
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
updateList := !t.trackBlocked && !t.pendingReqList
|
updateList := !t.trackBlocked && !t.pendingReqList
|
||||||
updatePrompt := trackWasBlocked && !t.trackBlocked
|
updatePrompt := trackWasBlocked && !t.trackBlocked
|
||||||
@@ -6826,7 +6844,7 @@ func (t *Terminal) Loop() error {
|
|||||||
changed = true
|
changed = true
|
||||||
// Deselect items that are now part of the header
|
// Deselect items that are now part of the header
|
||||||
for idx := range t.selected {
|
for idx := range t.selected {
|
||||||
if idx < int32(n) {
|
if int(idx) < n {
|
||||||
delete(t.selected, idx)
|
delete(t.selected, idx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6970,7 +6988,8 @@ func (t *Terminal) Loop() error {
|
|||||||
})
|
})
|
||||||
case actTransform, actBgTransform:
|
case actTransform, actBgTransform:
|
||||||
capture(false, func(body string) {
|
capture(false, func(body string) {
|
||||||
if actions, err := parseSingleActionList(strings.Trim(body, "\r\n")); err == nil {
|
// Allow 'put' if the triggering key is a printable character
|
||||||
|
if actions, err := parseSingleActionList(strings.Trim(body, "\r\n"), event.Printable()); err == nil {
|
||||||
// NOTE: We're not properly passing the return value here
|
// NOTE: We're not properly passing the return value here
|
||||||
doActions(actions)
|
doActions(actions)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,11 +164,12 @@ func _() {
|
|||||||
_ = x[ClickFooter-153]
|
_ = x[ClickFooter-153]
|
||||||
_ = x[Multi-154]
|
_ = x[Multi-154]
|
||||||
_ = x[Every-155]
|
_ = x[Every-155]
|
||||||
|
_ = x[ResultFinal-156]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLEnterCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteShiftHomeShiftEndShiftPageUpShiftPageDownF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltDeleteAltHomeAltEndAltPageUpAltPageDownAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltShiftDeleteAltShiftHomeAltShiftEndAltShiftPageUpAltShiftPageDownCtrlUpCtrlDownCtrlLeftCtrlRightCtrlHomeCtrlEndCtrlBackspaceCtrlDeleteCtrlPageUpCtrlPageDownAltCtrlAltCtrlAltUpCtrlAltDownCtrlAltLeftCtrlAltRightCtrlAltHomeCtrlAltEndCtrlAltBackspaceCtrlAltDeleteCtrlAltPageUpCtrlAltPageDownCtrlShiftUpCtrlShiftDownCtrlShiftLeftCtrlShiftRightCtrlShiftHomeCtrlShiftEndCtrlShiftDeleteCtrlShiftPageUpCtrlShiftPageDownCtrlAltShiftUpCtrlAltShiftDownCtrlAltShiftLeftCtrlAltShiftRightCtrlAltShiftHomeCtrlAltShiftEndCtrlAltShiftDeleteCtrlAltShiftPageUpCtrlAltShiftPageDownMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownInvalidFatalBracketedPasteBeginBracketedPasteEndResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancelClickHeaderClickFooterMultiEvery"
|
const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLEnterCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteShiftHomeShiftEndShiftPageUpShiftPageDownF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltDeleteAltHomeAltEndAltPageUpAltPageDownAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltShiftDeleteAltShiftHomeAltShiftEndAltShiftPageUpAltShiftPageDownCtrlUpCtrlDownCtrlLeftCtrlRightCtrlHomeCtrlEndCtrlBackspaceCtrlDeleteCtrlPageUpCtrlPageDownAltCtrlAltCtrlAltUpCtrlAltDownCtrlAltLeftCtrlAltRightCtrlAltHomeCtrlAltEndCtrlAltBackspaceCtrlAltDeleteCtrlAltPageUpCtrlAltPageDownCtrlShiftUpCtrlShiftDownCtrlShiftLeftCtrlShiftRightCtrlShiftHomeCtrlShiftEndCtrlShiftDeleteCtrlShiftPageUpCtrlShiftPageDownCtrlAltShiftUpCtrlAltShiftDownCtrlAltShiftLeftCtrlAltShiftRightCtrlAltShiftHomeCtrlAltShiftEndCtrlAltShiftDeleteCtrlAltShiftPageUpCtrlAltShiftPageDownMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownInvalidFatalBracketedPasteBeginBracketedPasteEndResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancelClickHeaderClickFooterMultiEveryResultFinal"
|
||||||
|
|
||||||
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, 157, 173, 182, 191, 199, 208, 214, 220, 228, 230, 234, 238, 243, 247, 250, 256, 263, 272, 281, 291, 302, 311, 319, 330, 343, 345, 347, 349, 351, 353, 355, 357, 359, 361, 364, 367, 370, 382, 387, 394, 401, 409, 418, 425, 431, 440, 451, 461, 473, 485, 498, 512, 524, 535, 549, 565, 571, 579, 587, 596, 604, 611, 624, 634, 644, 656, 659, 666, 675, 686, 697, 709, 720, 730, 746, 759, 772, 787, 798, 811, 824, 838, 851, 863, 878, 893, 910, 924, 940, 956, 973, 989, 1004, 1022, 1040, 1060, 1065, 1076, 1085, 1095, 1105, 1116, 1124, 1134, 1143, 1154, 1169, 1186, 1193, 1198, 1217, 1234, 1240, 1246, 1257, 1262, 1266, 1271, 1274, 1278, 1284, 1288, 1298, 1309, 1320, 1325, 1330}
|
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, 157, 173, 182, 191, 199, 208, 214, 220, 228, 230, 234, 238, 243, 247, 250, 256, 263, 272, 281, 291, 302, 311, 319, 330, 343, 345, 347, 349, 351, 353, 355, 357, 359, 361, 364, 367, 370, 382, 387, 394, 401, 409, 418, 425, 431, 440, 451, 461, 473, 485, 498, 512, 524, 535, 549, 565, 571, 579, 587, 596, 604, 611, 624, 634, 644, 656, 659, 666, 675, 686, 697, 709, 720, 730, 746, 759, 772, 787, 798, 811, 824, 838, 851, 863, 878, 893, 910, 924, 940, 956, 973, 989, 1004, 1022, 1040, 1060, 1065, 1076, 1085, 1095, 1105, 1116, 1124, 1134, 1143, 1154, 1169, 1186, 1193, 1198, 1217, 1234, 1240, 1246, 1257, 1262, 1266, 1271, 1274, 1278, 1284, 1288, 1298, 1309, 1320, 1325, 1330, 1341}
|
||||||
|
|
||||||
func (i EventType) String() string {
|
func (i EventType) String() string {
|
||||||
if i < 0 || i >= EventType(len(_EventType_index)-1) {
|
if i < 0 || i >= EventType(len(_EventType_index)-1) {
|
||||||
|
|||||||
+51
-38
@@ -4,6 +4,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
"github.com/rivo/uniseg"
|
"github.com/rivo/uniseg"
|
||||||
@@ -233,6 +234,7 @@ const (
|
|||||||
ClickFooter
|
ClickFooter
|
||||||
Multi
|
Multi
|
||||||
Every
|
Every
|
||||||
|
ResultFinal
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t EventType) AsEvent() Event {
|
func (t EventType) AsEvent() Event {
|
||||||
@@ -252,6 +254,12 @@ func (e Event) Comparable() Event {
|
|||||||
return Event{e.Type, e.Char, nil}
|
return Event{e.Type, e.Char, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Printable returns true if the event is a printable character that can be
|
||||||
|
// inserted into the query (e.g. via the 'put' action).
|
||||||
|
func (e Event) Printable() bool {
|
||||||
|
return e.Type == Rune && unicode.IsGraphic(e.Char)
|
||||||
|
}
|
||||||
|
|
||||||
func (e Event) KeyName() string {
|
func (e Event) KeyName() string {
|
||||||
if me := e.MouseEvent; me != nil {
|
if me := e.MouseEvent; me != nil {
|
||||||
return me.Name()
|
return me.Name()
|
||||||
@@ -995,51 +1003,56 @@ func init() {
|
|||||||
undefined := ColorAttr{colUndefined, AttrUndefined}
|
undefined := ColorAttr{colUndefined, AttrUndefined}
|
||||||
|
|
||||||
NoColorTheme = &ColorTheme{
|
NoColorTheme = &ColorTheme{
|
||||||
Colored: false,
|
Colored: false,
|
||||||
Input: defaultColor,
|
// Root colors. Everything else is left undefined so that overriding a
|
||||||
Fg: defaultColor,
|
// root (e.g. --color bw,bg:blue) propagates to the derived colors,
|
||||||
Bg: defaultColor,
|
// just like in the colored base themes.
|
||||||
ListFg: defaultColor,
|
Input: defaultColor,
|
||||||
ListBg: defaultColor,
|
Fg: defaultColor,
|
||||||
|
Bg: defaultColor,
|
||||||
|
DarkBg: defaultColor,
|
||||||
|
Prompt: defaultColor,
|
||||||
|
Match: defaultColor,
|
||||||
|
Spinner: defaultColor,
|
||||||
|
Info: defaultColor,
|
||||||
|
Pointer: defaultColor,
|
||||||
|
Marker: defaultColor,
|
||||||
|
Header: defaultColor,
|
||||||
|
Footer: defaultColor,
|
||||||
|
BorderLabel: defaultColor,
|
||||||
|
// Derived colors. Left undefined so they inherit from a root.
|
||||||
|
ListFg: undefined,
|
||||||
|
ListBg: undefined,
|
||||||
AltBg: undefined,
|
AltBg: undefined,
|
||||||
SelectedFg: defaultColor,
|
SelectedFg: undefined,
|
||||||
SelectedBg: defaultColor,
|
SelectedBg: undefined,
|
||||||
SelectedMatch: defaultColor,
|
SelectedMatch: undefined,
|
||||||
DarkBg: defaultColor,
|
|
||||||
Prompt: defaultColor,
|
|
||||||
Match: defaultColor,
|
|
||||||
Current: undefined,
|
Current: undefined,
|
||||||
CurrentMatch: undefined,
|
CurrentMatch: undefined,
|
||||||
Spinner: defaultColor,
|
|
||||||
Info: defaultColor,
|
|
||||||
Pointer: defaultColor,
|
|
||||||
Marker: defaultColor,
|
|
||||||
Header: defaultColor,
|
|
||||||
Border: undefined,
|
Border: undefined,
|
||||||
BorderLabel: defaultColor,
|
|
||||||
Ghost: undefined,
|
Ghost: undefined,
|
||||||
Disabled: defaultColor,
|
Disabled: undefined,
|
||||||
PreviewFg: defaultColor,
|
PreviewFg: undefined,
|
||||||
PreviewBg: defaultColor,
|
PreviewBg: undefined,
|
||||||
Gutter: undefined,
|
Gutter: undefined,
|
||||||
AltGutter: undefined,
|
AltGutter: undefined,
|
||||||
PreviewBorder: defaultColor,
|
PreviewBorder: undefined,
|
||||||
PreviewScrollbar: defaultColor,
|
PreviewScrollbar: undefined,
|
||||||
PreviewLabel: defaultColor,
|
PreviewLabel: undefined,
|
||||||
ListLabel: defaultColor,
|
ListLabel: undefined,
|
||||||
ListBorder: defaultColor,
|
ListBorder: undefined,
|
||||||
Separator: defaultColor,
|
Separator: undefined,
|
||||||
Scrollbar: defaultColor,
|
Scrollbar: undefined,
|
||||||
InputBg: defaultColor,
|
InputBg: undefined,
|
||||||
InputBorder: defaultColor,
|
InputBorder: undefined,
|
||||||
InputLabel: defaultColor,
|
InputLabel: undefined,
|
||||||
HeaderBg: defaultColor,
|
HeaderBg: undefined,
|
||||||
HeaderBorder: defaultColor,
|
HeaderBorder: undefined,
|
||||||
HeaderLabel: defaultColor,
|
HeaderLabel: undefined,
|
||||||
FooterBg: defaultColor,
|
FooterBg: undefined,
|
||||||
FooterBorder: defaultColor,
|
FooterBorder: undefined,
|
||||||
FooterLabel: defaultColor,
|
FooterLabel: undefined,
|
||||||
GapLine: defaultColor,
|
GapLine: undefined,
|
||||||
Nth: undefined,
|
Nth: undefined,
|
||||||
Nomatch: undefined,
|
Nomatch: undefined,
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -136,6 +136,7 @@ class Tmux
|
|||||||
rescue Minitest::Assertion
|
rescue Minitest::Assertion
|
||||||
retries += 1
|
retries += 1
|
||||||
raise if retries > 5
|
raise if retries > 5
|
||||||
|
|
||||||
retry
|
retry
|
||||||
end
|
end
|
||||||
send_keys 'clear', :Enter
|
send_keys 'clear', :Enter
|
||||||
@@ -295,7 +296,7 @@ class Tmux
|
|||||||
if @shell == :nushell
|
if @shell == :nushell
|
||||||
message = "Prepare[#{tries}]"
|
message = "Prepare[#{tries}]"
|
||||||
send_keys 'C-u', 'C-l'
|
send_keys 'C-u', 'C-l'
|
||||||
sleep 0.2
|
sleep(0.2)
|
||||||
send_keys ' ', 'C-u', :Enter, message
|
send_keys ' ', 'C-u', :Enter, message
|
||||||
self.until { |lines| lines[-1] == message }
|
self.until { |lines| lines[-1] == message }
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -971,6 +971,24 @@ class TestCore < TestInteractive
|
|||||||
tmux.until { |lines| assert_includes lines[1], ' aabravo/aabravo' }
|
tmux.until { |lines| assert_includes lines[1], ' aabravo/aabravo' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_transform_put
|
||||||
|
tmux.send_keys %(seq 1000 | #{FZF} --bind 'a:transform:echo put'), :Enter
|
||||||
|
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||||
|
tmux.send_keys :a
|
||||||
|
tmux.until { |lines| assert_equal '> a', lines.last }
|
||||||
|
tmux.send_keys :b
|
||||||
|
tmux.until { |lines| assert_equal '> ab', lines.last }
|
||||||
|
end
|
||||||
|
|
||||||
|
# The async callback runs in a later iteration, but 'put' must still insert
|
||||||
|
# the key that triggered the bg-transform (snapshot of the scheduling event).
|
||||||
|
def test_bg_transform_put
|
||||||
|
tmux.send_keys %(seq 1000 | #{FZF} --bind 'a:bg-transform:sleep 0.5; echo put'), :Enter
|
||||||
|
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||||
|
tmux.send_keys 'ab'
|
||||||
|
tmux.until { |lines| assert_equal '> ba', lines.last }
|
||||||
|
end
|
||||||
|
|
||||||
def test_accept_non_empty
|
def test_accept_non_empty
|
||||||
tmux.send_keys %(seq 1000 | #{fzf('--print-query --bind enter:accept-non-empty')}), :Enter
|
tmux.send_keys %(seq 1000 | #{fzf('--print-query --bind enter:accept-non-empty')}), :Enter
|
||||||
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||||
@@ -1387,6 +1405,17 @@ class TestCore < TestInteractive
|
|||||||
tmux.until { |lines| assert_includes lines, '> 1' }
|
tmux.until { |lines| assert_includes lines, '> 1' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_result_final_event
|
||||||
|
tmux.send_keys %[(seq 100; sleep 1; seq 100) | #{FZF} \\
|
||||||
|
--query 1 \\
|
||||||
|
--bind 'result:transform-header(echo "R=$FZF_MATCH_COUNT")' \\
|
||||||
|
--bind 'result-final:transform-footer(echo "F=$FZF_MATCH_COUNT")'], :Enter
|
||||||
|
tmux.until { |lines| assert lines.any_include?('R=20') }
|
||||||
|
tmux.until { |lines| refute lines.any_include?('F=20') }
|
||||||
|
tmux.until { |lines| assert lines.any_include?('R=40') }
|
||||||
|
tmux.until { |lines| assert lines.any_include?('F=40') }
|
||||||
|
end
|
||||||
|
|
||||||
def test_every_event
|
def test_every_event
|
||||||
tmux.send_keys %(seq 100 | fzf --bind 'every(0.2):transform-prompt(cat #{tempname})'), :Enter
|
tmux.send_keys %(seq 100 | fzf --bind 'every(0.2):transform-prompt(cat #{tempname})'), :Enter
|
||||||
tmux.until { |lines| assert_equal 100, lines.match_count }
|
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||||
|
|||||||
@@ -100,6 +100,47 @@ module TestShell
|
|||||||
tmux.until { |lines| assert_equal '/tmp', lines[-1] }
|
tmux.until { |lines| assert_equal '/tmp', lines[-1] }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_alt_c_symlink
|
||||||
|
base = '/tmp/fzf-test-alt-c-symlink'
|
||||||
|
FileUtils.rm_rf(base)
|
||||||
|
FileUtils.mkdir_p("#{base}/real/subdir")
|
||||||
|
FileUtils.ln_s("#{base}/real", "#{base}/link")
|
||||||
|
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys "cd #{base}/link", :Enter
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys :Escape, :c
|
||||||
|
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||||
|
tmux.send_keys 'subdir'
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys :pwd, :Enter
|
||||||
|
tmux.until { |lines| assert_equal "#{base}/link/subdir", lines[-1] }
|
||||||
|
ensure
|
||||||
|
FileUtils.rm_rf(base)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_alt_c_absolute_cmd
|
||||||
|
base = '/tmp/fzf-test-alt-c-absolute'
|
||||||
|
FileUtils.rm_rf(base)
|
||||||
|
FileUtils.mkdir_p(base)
|
||||||
|
|
||||||
|
set_var('FZF_ALT_C_COMMAND', "echo #{base}")
|
||||||
|
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'cd /tmp', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys :Escape, :c
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys :pwd, :Enter
|
||||||
|
tmux.until { |lines| assert_equal base, lines[-1] }
|
||||||
|
ensure
|
||||||
|
FileUtils.rm_rf(base)
|
||||||
|
end
|
||||||
|
|
||||||
def test_ctrl_r
|
def test_ctrl_r
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'echo 1st', :Enter
|
tmux.send_keys 'echo 1st', :Enter
|
||||||
|
|||||||
Reference in New Issue
Block a user