Compare commits

...
24 Commits
Author SHA1 Message Date
Junegunn Choi ab9a552f5b Re-order CHANGELOG 2026-11-06 10:39:01 +09:00
Junegunn Choi a38beb544f Merge remote-tracking branch 'origin/master' into winget-msi 2026-11-06 10:37:26 +09:00
Junegunn Choi b5c054ee18 Document draft release flow in RELEASE.md 2026-11-05 22:55:33 +09:00
Junegunn Choi 3dd8e03ddc Fail msi job when release is already published
- Re-run would replace published MSIs with new builds, whose hashes
  differ from a winget manifest that may already be merged
- Also stops winget job from submitting again
2026-11-05 22:55:16 +09:00
Junegunn Choi 02d8843869 Remove make release
- Release workflow replaced it
- With draft releases, it would push master while the release was still
  a draft, so install scripts would point at assets not yet public
2026-11-05 22:55:01 +09:00
Junegunn Choi c076805825 Note how to switch existing winget installs to MSI 2026-11-04 16:02:58 +09:00
Junegunn Choi 398b66342d Drop armv7 from winget installer regex
- No Windows armv7 build since 0.73.1
2026-11-04 16:02:58 +09:00
Junegunn Choi fa30c5a278 Fix wix build path in usage comment
- Source="LICENSE" resolves from repository root, where CI runs it
2026-11-04 16:02:58 +09:00
Junegunn Choi e279faac50 Replace existing draft when retrying release job
- goreleaser looks up the release by tag, which does not return drafts
- A retry created a second draft for the same tag, and msi job could
  publish either one
2026-11-03 13:51:47 +09:00
Junegunn Choi 386c3e9ea9 Limit winget workflow token to read access
- winget-releaser passes github.token to cargo-bins/cargo-binstall@main
- Token had contents: write from release.yml, and repository default
  (write) when run by hand
2026-11-03 13:51:37 +09:00
Junegunn Choi 99bfd5aa0f Add change-info-command and transform variants
- change-info-command, transform-info-command, bg-transform-info-command
- change-info-command disallowed from remote --listen like change-preview

Close #4951
2026-11-02 22:14:08 +09:00
Junegunn Choi 14aacc115d Avoid second environment approval for msi job
- msi job used RELEASE_PAT only so that publishing would trigger
  winget.yml, which required approving the release environment again
- Publish with GITHUB_TOKEN instead and call winget.yml directly
- Skip winget for prerelease versions, as the released event did
2026-10-29 21:41:27 +09:00
Junegunn Choi 5853d57518 Add CHANGELOG entry for #4943 2026-10-26 20:29:14 +09:00
Junegunn Choi e19c2c4c6e Shorten MSI comments 2026-10-26 20:28:46 +09:00
Junegunn Choi 68ef9cf0d0 Skip MSI build for prerelease versions
- MSI versions cannot have a suffix such as -rc1, so wix build fails
- Release is still published
2026-10-26 20:27:47 +09:00
Junegunn Choi 243ccc325b Add MSIs to release checksum file 2026-10-26 20:27:22 +09:00
Junegunn Choi d7138593f2 Publish release only after MSI upload
- goreleaser creates a draft release
- msi job publishes it after uploading the MSIs
- Publishing triggers winget.yml, which previously ran before the MSIs
  existed and left them out of the manifest
- If the msi job fails, the release stays a draft
2026-10-26 20:26:50 +09:00
Marshall Porter 5b2936db82 Add WiX .msi installer alongside portable zip for Windows
winget currently only has a `portable` installer to offer for fzf, which
installs via a symlink into the WinGet Links folder. That link is created
by whoever runs winget, normally a non-elevated terminal, and Windows will
not let an elevated process follow a link that a less-privileged process
created ("The path cannot be traversed because it contains an untrusted
mount point"). An administrator's SSH session is elevated, so fzf, and the
shell integration that runs it at startup, fails there.

Build a real per-machine .msi with the WiX dotnet-tool CLI (this is a Go
project, so cargo-wix doesn't apply) from the goreleaser-built fzf.exe, for
amd64 and arm64 (WiX's -arch has no armv7 target). It installs to Program
Files with its own machine PATH entry, so there is no link. WiX only runs
on Windows, so the release job hands the Windows binaries to a new msi job
on windows-latest. The MSIs are built in both workflow modes, so a
workflow_dispatch validation run checks them, and uploaded alongside the
.zip on a tag push; the winget-releaser installers-regex also matches .msi.
WiX is pinned to 5.0.2, the last release that does not require accepting
its Open Source Maintenance Fee EULA.
2026-10-26 20:26:33 +09:00
Junegunn Choi 26ff936df0 Check walker ancestor symlinks against the current root only
- A symlink in one root was skipped when its target was an ancestor of
  another root, hiding files reachable only through it
- Fix and tests by @mabunemeh

Close #4944
2026-10-25 22:33:38 +09:00
Junegunn Choi b412a45053 Avoid O(n^2) string concatenation in maskActionContents
- `masked +=` per token copied the whole prefix on every append
- ~1 MB POST to --listen blocked the server for ~30 s
- switch to strings.Builder

Reported by Zachariah Mithani (@zachariah-mithani, AploSec).
2026-10-22 08:24:40 +09:00
Robby Russell ac28b8c3ba docs: add commit goods badge and utm tags to merch links 2026-10-21 20:00:06 +09:00
Junegunn ChoiandNavpreetSSidhu 2adb741c33 Respect --no-sort in non-streaming filter mode
- --sync, --tac, --bench disable streaming filter, and the result was sorted
- Matcher.scan already checks pattern.sortable

Problem analyzed and patch submitted by @NavpreetSSidhu

Fix #4940

Co-authored-by: NavpreetSSidhu <116211004+NavpreetSSidhu@users.noreply.github.com>
2026-10-21 15:05:38 +09:00
Junegunn Choi a38ab117c5 Add CHANGELOG entry for #4941 2026-10-21 15:05:17 +09:00
Junegunn Choi 903a4da7ab Avoid control characters in Neovim terminal buffer name
- With 'title', run command from temp script if it has control chars
- Raw ESC in buffer name leaked through title and drew garbage

Fix #4941
2026-10-21 14:47:16 +09:00
19 changed files with 525 additions and 242 deletions

No files matched your search

+94
View File
@@ -18,6 +18,8 @@ jobs:
release:
runs-on: macos-latest
environment: release
outputs:
version: ${{ steps.ver.outputs.version }}
steps:
- uses: actions/checkout@v7
with:
@@ -74,3 +76,95 @@ jobs:
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 }}
# For the msi job
- uses: actions/upload-artifact@v7
with:
name: windows-binaries
path: |
dist/*windows_amd64*/fzf.exe
dist/*windows_arm64*/fzf.exe
if-no-files-found: error
# winget's portable install runs fzf through a symlink that admin SSH
# sessions refuse to follow (#4943). The MSI installs a real file instead.
# WiX only runs on Windows.
msi:
needs: release
runs-on: windows-latest
env:
# MSI versions cannot have a suffix such as -rc1
BUILD_MSI: ${{ !contains(needs.release.outputs.version, '-') }}
steps:
- uses: actions/checkout@v7
- uses: actions/download-artifact@v8
if: env.BUILD_MSI == 'true'
with:
name: windows-binaries
path: dist
- uses: actions/setup-dotnet@v6
if: env.BUILD_MSI == 'true'
with:
dotnet-version: '8.0.x'
# WiX 6 and later fall under the Open Source Maintenance Fee
- name: Install WiX
if: env.BUILD_MSI == 'true'
run: dotnet tool install --global wix --version 5.0.2
- name: Build Windows MSI installers
if: env.BUILD_MSI == 'true'
shell: bash
run: |
set -e
V='${{ needs.release.outputs.version }}'
for pair in "amd64:x64" "arm64:arm64"; do
goarch="${pair%%:*}"
wixarch="${pair##*:}"
bindir=$(dirname "$(find dist -type f -iname fzf.exe -path "*windows_${goarch}*" | head -n1)")
wix build wix/main.wxs -arch "$wixarch" -d Version="$V" -d BinDir="$bindir" -o "dist/fzf-${V}-windows_${goarch}.msi"
done
ls -la dist/*.msi
- uses: actions/upload-artifact@v7
if: env.BUILD_MSI == 'true'
with:
name: windows-msi
path: dist/*.msi
- name: Upload MSI installers and publish release
if: github.event_name == 'push'
shell: bash
run: |
set -e
V='${{ needs.release.outputs.version }}'
# A re-run would replace MSIs that a winget manifest may already
# reference, and submit to winget again
if [[ $(gh release view "v$V" --json isDraft --jq .isDraft) != true ]]; then
echo "::error::v$V is already published"
exit 1
fi
if [[ $BUILD_MSI == true ]]; then
sums="fzf_${V}_checksums.txt"
# Drop MSI lines from a previous run of this job
gh release download "v$V" -p "$sums" -O - | grep -v '\.msi$' > "dist/$sums"
# sha256sum on Windows may print '<hash> *<name>'
(cd dist && sha256sum fzf-*.msi) | sed 's/ \*/ /' >> "dist/$sums"
gh release upload "v$V" dist/*.msi "dist/$sums" --clobber
fi
gh release edit "v$V" --draft=false
env:
GITHUB_TOKEN: ${{ github.token }}
# Called directly because a release published with GITHUB_TOKEN does not
# trigger other workflows
winget:
needs: [release, msi]
if: github.event_name == 'push' && !contains(needs.release.outputs.version, '-')
uses: ./.github/workflows/winget.yml
with:
release-tag: v${{ needs.release.outputs.version }}
secrets:
WINGET_TOKEN: ${{ secrets.WINGET_TOKEN }}
+15 -4
View File
@@ -1,7 +1,13 @@
name: Publish to Winget
on:
release:
types: [released]
workflow_call:
inputs:
release-tag:
type: string
required: true
secrets:
WINGET_TOKEN:
required: true
workflow_dispatch:
inputs:
release-tag:
@@ -9,6 +15,11 @@ on:
required: true
type: string
# winget-releaser passes this token to unpinned actions. Submission uses
# WINGET_TOKEN.
permissions:
contents: read
jobs:
publish:
runs-on: ubuntu-latest
@@ -16,6 +27,6 @@ jobs:
- uses: vedantmgoyal2009/winget-releaser@4ffc7888bffd451b357355dc214d43bb9f23917e # v2
with:
identifier: junegunn.fzf
release-tag: ${{ inputs.release-tag || github.event.release.tag_name }}
installers-regex: '-windows_(armv7|arm64|amd64)\.zip$'
release-tag: ${{ inputs.release-tag }}
installers-regex: '-windows_(arm64|amd64)\.(zip|msi)$'
token: ${{ secrets.WINGET_TOKEN }}
+4
View File
@@ -120,6 +120,10 @@ release:
owner: junegunn
name: fzf
prerelease: auto
# Published by the msi job in release.yml after it uploads the MSIs
draft: true
# A retried release job would otherwise add a second draft for the tag
replace_existing_draft: true
name_template: '{{ .Version }}'
snapshot:
-3
View File
@@ -19,9 +19,6 @@ make install
# Build fzf binaries and archives for all platforms using goreleaser
make build
# Publish GitHub release
make release
```
> [!WARNING]
+19 -3
View File
@@ -1,10 +1,14 @@
CHANGELOG
=========
0.74.6
0.75.0
------
- Fixed `--with-shell` on Windows not detecting cmd, PowerShell, and pwsh, which made fzf quote commands and entries for a POSIX shell (#4936) (@vimkim)
- e.g. With `--with-shell "cmd.exe /d /c"`, `--preview '"C:\Program Files\nu\bin\nu.exe" --version'` failed
- Added `{change,transform,bg-transform}-info-command` actions (#4951) (@casperdcl)
```sh
seq 100 | fzf --info-command 'echo "mode: A | $FZF_INFO"' \
--bind 'ctrl-a:change-info-command:echo "mode: A | $FZF_INFO"' \
--bind 'ctrl-b:change-info-command:echo "mode: B | $FZF_INFO"'
```
- `transform-nth` and `transform-with-nth` (and their `bg-` variants) no longer split the output at `|`
- Only `change-nth` and `change-with-nth` rotate through `|`-separated values
- Fixed the bound command being replaced with the output. e.g. With `transform-nth(echo '1|2')`, the next key press ran `2|1` as a shell command.
@@ -24,6 +28,18 @@ CHANGELOG
# Now you can write:
ls -al | fzf --with-nth .. --bind 'space:change-with-nth:..|1|2'
```
- Fixed `--no-sort` being ignored in filter mode with `--sync`, `--tac`, or `--bench` (#4940) (@NavpreetSSidhu)
- Fixed `--walker=follow` skipping a symlink in one `--walker-root` when its target is an ancestor of another root (#4944) (@mabunemeh)
- Fixed O(n^2) action parsing in `--listen`. A single ~1 MB request could block the server for ~30 s (@zachariah-mithani, AploSec)
- Windows
- Fixed `--with-shell` on Windows not detecting cmd, PowerShell, and pwsh, which made fzf quote commands and entries for a POSIX shell (#4936) (@vimkim)
- e.g. With `--with-shell "cmd.exe /d /c"`, `--preview '"C:\Program Files\nu\bin\nu.exe" --version'` failed
- Added `.msi` installers for amd64 and arm64, also offered through winget (#4943) (@meop)
- The portable package runs fzf through a symlink in the WinGet Links folder, which an administrator's SSH session refuses to follow
- The MSI installs `fzf.exe` under Program Files and adds it to the system PATH
- `winget upgrade` keeps an existing portable install. To switch to the MSI, run `winget uninstall junegunn.fzf` and `winget install junegunn.fzf`.
- Vim plugin
- Fixed garbage text on screen in Neovim with `set title` when the fzf command contains ANSI escape sequences (#4941)
- nushell
- The completion integration now requires nushell 0.116.1 or above, where an interactive completer can take the terminal through the `@interactive` attribute (#4934) (@sim590)
+1 -41
View File
@@ -144,46 +144,6 @@ tag: prerelease
git tag -s v$(VERSION) -m v$(VERSION)
git push origin v$(VERSION)
release:
# Make sure that the tests pass and the build works
TAGS=tcell make test
make test build clean
ifndef GITHUB_TOKEN
$(error GITHUB_TOKEN is not defined)
endif
# Check if we are on master branch
ifneq ($(shell git symbolic-ref --short HEAD),master)
$(error Not on master branch)
endif
# Check if version numbers are properly updated
grep -q ^$(VERSION_REGEX)$$ CHANGELOG.md
grep -qF '"fzf $(VERSION_TRIM)"' man/man1/fzf.1
grep -qF '"fzf $(VERSION_TRIM)"' man/man1/fzf-tmux.1
grep -qF $(VERSION) install
grep -qF $(VERSION) install.ps1
# Make release note out of CHANGELOG.md
mkdir -p tmp
sed -n '/^$(VERSION_REGEX)$$/,/^[0-9]/p' CHANGELOG.md | tail -r | \
sed '1,/^ *$$/d' | tail -r | sed 1,2d | tee tmp/release-note
# Push to temp branch first so that install scripts always works on master branch
git checkout -B temp master
git push origin temp --follow-tags --force
# Make a GitHub release
goreleaser --clean --release-notes tmp/release-note
# Push to master
git checkout master
git push origin master
# Delete temp branch
git push origin --delete temp
clean:
$(RM) -r dist target
@@ -233,4 +193,4 @@ update:
$(GO) get -u
$(GO) mod tidy
.PHONY: all generate build prerelease tag release test itest bench lint install clean docker docker-test update fmt
.PHONY: all generate build prerelease tag test itest bench lint install clean docker docker-test update fmt
+3 -3
View File
@@ -12,13 +12,13 @@
<kbd align="center">
<br/>
<a href="https://commitgoods.com/collections/fzf"><img src="https://junegunn.github.io/fzf/images/fzf-mugs.jpg" width="80%" alt="fzf merch"></a>
<a href="https://commitgoods.com/collections/fzf?utm_source=github&amp;utm_medium=referral"><img src="https://junegunn.github.io/fzf/images/fzf-mugs.jpg" width="80%" alt="fzf merch"></a>
<br/>
<br/>
Show your love for fzf -- T-shirts, mugs, and stickers now available!
<br/>
<br/>
<a href="https://commitgoods.com/collections/fzf">commitgoods.com/collections/fzf</a>
<a href="https://commitgoods.com/collections/fzf?utm_source=github&amp;utm_medium=referral"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cdn.shopify.com/s/files/1/0101/8752/files/commitgoods-badge-stacked-navy-dark.svg"><img src="https://cdn.shopify.com/s/files/1/0101/8752/files/commitgoods-badge-stacked-navy.svg" height="28" alt="Official merch on commitgoods.com"></picture></a>
<br/>
<br/>
</kbd>
@@ -1150,7 +1150,7 @@ Copyright (c) 2013-2026 Junegunn Choi
Goods
-----
Grab fzf T-shirts, mugs, and stickers here: https://commitgoods.com/collections/fzf
Grab fzf T-shirts, mugs, and stickers here: [commitgoods.com/collections/fzf](https://commitgoods.com/collections/fzf?utm_source=github&utm_medium=referral)
Sponsors :heart:
----------------
+20 -1
View File
@@ -35,13 +35,29 @@ triggered by a tag push.
4. The workflow fires on the tag push and pauses on the `release`
environment gate. Approve it in the Actions tab to release.
- goreleaser creates a draft release
- The `msi` job builds the Windows MSIs, uploads them, and publishes
the release
- The `winget` job submits the release to winget
5. After the GitHub release is published, fast-forward `master`:
5. After the `msi` job publishes the GitHub release, fast-forward `master`:
```sh
git push origin master
```
### If the `msi` job fails
The release stays a draft. Do not push `master` until it is published.
- Fix the problem and re-run the failed jobs, or
- Publish without MSIs and submit to winget by hand:
```sh
gh release edit v0.74.5 --draft=false
gh workflow run winget.yml -f release-tag=v0.74.5
```
## Testing the workflow
To exercise the workflow without firing a real release:
@@ -53,6 +69,9 @@ To exercise the workflow without firing a real release:
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.
5. The `msi` job builds the MSIs and uploads them as the `windows-msi`
artifact of the run. The release upload and the `winget` job are
skipped.
Use this to validate the workflow YAML, version-extraction logic,
the macOS runner setup, and the signing/notarization credentials.
+2
View File
@@ -2056,6 +2056,7 @@ A key or an event can be bound to one or more of the following actions.
\fBchange\-header(...)\fR (change header to the given string; doesn't affect \fB\-\-header\-lines\fR)
\fBchange\-header\-lines(N)\fR (change the number of \fB\-\-header\-lines\fR)
\fBchange\-header\-label(...)\fR (change \fB\-\-header\-label\fR to the given string)
\fBchange\-info\-command(...)\fR (change \fB\-\-info\-command\fR option)
\fBchange\-input\-label(...)\fR (change \fB\-\-input\-label\fR to the given string)
\fBchange\-list\-label(...)\fR (change \fB\-\-list\-label\fR to the given string)
\fBchange\-multi\fR (enable multi-select mode with no limit)
@@ -2164,6 +2165,7 @@ A key or an event can be bound to one or more of the following actions.
\fBtransform\-header(...)\fR (transform header using an external command)
\fBtransform\-header\-lines(...)\fR (transform the number of \fB\-\-header\-lines\fR using an external command)
\fBtransform\-header\-label(...)\fR (transform header label using an external command)
\fBtransform\-info\-command(...)\fR (transform \fB\-\-info\-command\fR using an external command)
\fBtransform\-input\-label(...)\fR (transform input label using an external command)
\fBtransform\-list\-label(...)\fR (transform list label using an external command)
\fBtransform\-nth(...)\fR (transform nth using an external command; see CHANGE NTH AND WITH\-NTH)
+6
View File
@@ -1104,6 +1104,12 @@ function! s:execute_term(dict, command, temps) abort
let fzf.temps.batchfile = s:fzf_tempname().'.bat'
call s:writefile(s:wrap_cmds(a:command), fzf.temps.batchfile)
let command = fzf.temps.batchfile
elseif has('nvim') && &title && a:command =~ '[[:cntrl:]]'
" Neovim puts the command in the buffer name, which can end up raw in
" the terminal title (#4941)
let fzf.temps.shellscript = s:fzf_tempname()
call s:writefile([a:command], fzf.temps.shellscript)
let command = 'sh '.fzf#shellescape(fzf.temps.shellscript)
else
let command = a:command
endif
+161 -157
View File
@@ -34,168 +34,172 @@ func _() {
_ = x[actChangeFooter-23]
_ = x[actChangeHeaderLabel-24]
_ = x[actChangeFooterLabel-25]
_ = x[actChangeInputLabel-26]
_ = x[actChangeListLabel-27]
_ = x[actChangeMulti-28]
_ = x[actChangeNth-29]
_ = x[actChangeWithNth-30]
_ = x[actChangePointer-31]
_ = x[actChangePreview-32]
_ = x[actChangePreviewLabel-33]
_ = x[actChangePreviewWindow-34]
_ = x[actChangePrompt-35]
_ = x[actChangeQuery-36]
_ = x[actClearScreen-37]
_ = x[actClearQuery-38]
_ = x[actClearSelection-39]
_ = x[actClose-40]
_ = x[actDeleteChar-41]
_ = x[actDeleteCharEof-42]
_ = x[actEndOfLine-43]
_ = x[actFatal-44]
_ = x[actForwardChar-45]
_ = x[actForwardWord-46]
_ = x[actForwardSubWord-47]
_ = x[actKillLine-48]
_ = x[actKillWord-49]
_ = x[actKillSubWord-50]
_ = x[actUnixLineDiscard-51]
_ = x[actUnixWordRubout-52]
_ = x[actYank-53]
_ = x[actBackwardKillWord-54]
_ = x[actBackwardKillSubWord-55]
_ = x[actSelectAll-56]
_ = x[actDeselectAll-57]
_ = x[actToggle-58]
_ = x[actToggleSearch-59]
_ = x[actToggleAll-60]
_ = x[actToggleDown-61]
_ = x[actToggleUp-62]
_ = x[actToggleIn-63]
_ = x[actToggleOut-64]
_ = x[actToggleTrack-65]
_ = x[actToggleTrackCurrent-66]
_ = x[actToggleHeader-67]
_ = x[actToggleWrap-68]
_ = x[actToggleWrapWord-69]
_ = x[actToggleMultiLine-70]
_ = x[actToggleHscroll-71]
_ = x[actToggleRaw-72]
_ = x[actEnableRaw-73]
_ = x[actDisableRaw-74]
_ = x[actTrackCurrent-75]
_ = x[actToggleInput-76]
_ = x[actHideInput-77]
_ = x[actShowInput-78]
_ = x[actUntrackCurrent-79]
_ = x[actDown-80]
_ = x[actDownMatch-81]
_ = x[actUp-82]
_ = x[actUpMatch-83]
_ = x[actPageUp-84]
_ = x[actPageDown-85]
_ = x[actPosition-86]
_ = x[actHalfPageUp-87]
_ = x[actHalfPageDown-88]
_ = x[actOffsetUp-89]
_ = x[actOffsetDown-90]
_ = x[actOffsetMiddle-91]
_ = x[actJump-92]
_ = x[actJumpAccept-93]
_ = x[actPrintQuery-94]
_ = x[actRefreshPreview-95]
_ = x[actReplaceQuery-96]
_ = x[actToggleSort-97]
_ = x[actShowPreview-98]
_ = x[actHidePreview-99]
_ = x[actTogglePreview-100]
_ = x[actTogglePreviewWrap-101]
_ = x[actTogglePreviewWrapWord-102]
_ = x[actTransform-103]
_ = x[actTransformBorderLabel-104]
_ = x[actTransformGhost-105]
_ = x[actTransformHeader-106]
_ = x[actTransformHeaderLines-107]
_ = x[actTransformFooter-108]
_ = x[actTransformHeaderLabel-109]
_ = x[actTransformFooterLabel-110]
_ = x[actTransformInputLabel-111]
_ = x[actTransformListLabel-112]
_ = x[actTransformNth-113]
_ = x[actTransformWithNth-114]
_ = x[actTransformPointer-115]
_ = x[actTransformPreviewLabel-116]
_ = x[actTransformPrompt-117]
_ = x[actTransformQuery-118]
_ = x[actTransformSearch-119]
_ = x[actTrigger-120]
_ = x[actBgTransform-121]
_ = x[actBgTransformBorderLabel-122]
_ = x[actBgTransformGhost-123]
_ = x[actBgTransformHeader-124]
_ = x[actBgTransformHeaderLines-125]
_ = x[actBgTransformFooter-126]
_ = x[actBgTransformHeaderLabel-127]
_ = x[actBgTransformFooterLabel-128]
_ = x[actBgTransformInputLabel-129]
_ = x[actBgTransformListLabel-130]
_ = x[actBgTransformNth-131]
_ = x[actBgTransformWithNth-132]
_ = x[actBgTransformPointer-133]
_ = x[actBgTransformPreviewLabel-134]
_ = x[actBgTransformPrompt-135]
_ = x[actBgTransformQuery-136]
_ = x[actBgTransformSearch-137]
_ = x[actBgCancel-138]
_ = x[actSearch-139]
_ = x[actPreview-140]
_ = x[actPreviewTop-141]
_ = x[actPreviewBottom-142]
_ = x[actPreviewUp-143]
_ = x[actPreviewDown-144]
_ = x[actPreviewPageUp-145]
_ = x[actPreviewPageDown-146]
_ = x[actPreviewHalfPageUp-147]
_ = x[actPreviewHalfPageDown-148]
_ = x[actPrevHistory-149]
_ = x[actPrevSelected-150]
_ = x[actPrint-151]
_ = x[actPut-152]
_ = x[actNextHistory-153]
_ = x[actNextSelected-154]
_ = x[actExecute-155]
_ = x[actExecuteSilent-156]
_ = x[actExecuteMulti-157]
_ = x[actSigStop-158]
_ = x[actBest-159]
_ = x[actFirst-160]
_ = x[actLast-161]
_ = x[actReload-162]
_ = x[actReloadSync-163]
_ = x[actDisableSearch-164]
_ = x[actEnableSearch-165]
_ = x[actSelect-166]
_ = x[actDeselect-167]
_ = x[actUnbind-168]
_ = x[actRebind-169]
_ = x[actToggleBind-170]
_ = x[actBecome-171]
_ = x[actShowHeader-172]
_ = x[actHideHeader-173]
_ = x[actBell-174]
_ = x[actExclude-175]
_ = x[actExcludeMulti-176]
_ = x[actAsync-177]
_ = x[actWait-178]
_ = x[actChangeInfoCommand-26]
_ = x[actChangeInputLabel-27]
_ = x[actChangeListLabel-28]
_ = x[actChangeMulti-29]
_ = x[actChangeNth-30]
_ = x[actChangeWithNth-31]
_ = x[actChangePointer-32]
_ = x[actChangePreview-33]
_ = x[actChangePreviewLabel-34]
_ = x[actChangePreviewWindow-35]
_ = x[actChangePrompt-36]
_ = x[actChangeQuery-37]
_ = x[actClearScreen-38]
_ = x[actClearQuery-39]
_ = x[actClearSelection-40]
_ = x[actClose-41]
_ = x[actDeleteChar-42]
_ = x[actDeleteCharEof-43]
_ = x[actEndOfLine-44]
_ = x[actFatal-45]
_ = x[actForwardChar-46]
_ = x[actForwardWord-47]
_ = x[actForwardSubWord-48]
_ = x[actKillLine-49]
_ = x[actKillWord-50]
_ = x[actKillSubWord-51]
_ = x[actUnixLineDiscard-52]
_ = x[actUnixWordRubout-53]
_ = x[actYank-54]
_ = x[actBackwardKillWord-55]
_ = x[actBackwardKillSubWord-56]
_ = x[actSelectAll-57]
_ = x[actDeselectAll-58]
_ = x[actToggle-59]
_ = x[actToggleSearch-60]
_ = x[actToggleAll-61]
_ = x[actToggleDown-62]
_ = x[actToggleUp-63]
_ = x[actToggleIn-64]
_ = x[actToggleOut-65]
_ = x[actToggleTrack-66]
_ = x[actToggleTrackCurrent-67]
_ = x[actToggleHeader-68]
_ = x[actToggleWrap-69]
_ = x[actToggleWrapWord-70]
_ = x[actToggleMultiLine-71]
_ = x[actToggleHscroll-72]
_ = x[actToggleRaw-73]
_ = x[actEnableRaw-74]
_ = x[actDisableRaw-75]
_ = x[actTrackCurrent-76]
_ = x[actToggleInput-77]
_ = x[actHideInput-78]
_ = x[actShowInput-79]
_ = x[actUntrackCurrent-80]
_ = x[actDown-81]
_ = x[actDownMatch-82]
_ = x[actUp-83]
_ = x[actUpMatch-84]
_ = x[actPageUp-85]
_ = x[actPageDown-86]
_ = x[actPosition-87]
_ = x[actHalfPageUp-88]
_ = x[actHalfPageDown-89]
_ = x[actOffsetUp-90]
_ = x[actOffsetDown-91]
_ = x[actOffsetMiddle-92]
_ = x[actJump-93]
_ = x[actJumpAccept-94]
_ = x[actPrintQuery-95]
_ = x[actRefreshPreview-96]
_ = x[actReplaceQuery-97]
_ = x[actToggleSort-98]
_ = x[actShowPreview-99]
_ = x[actHidePreview-100]
_ = x[actTogglePreview-101]
_ = x[actTogglePreviewWrap-102]
_ = x[actTogglePreviewWrapWord-103]
_ = x[actTransform-104]
_ = x[actTransformBorderLabel-105]
_ = x[actTransformGhost-106]
_ = x[actTransformHeader-107]
_ = x[actTransformHeaderLines-108]
_ = x[actTransformFooter-109]
_ = x[actTransformHeaderLabel-110]
_ = x[actTransformFooterLabel-111]
_ = x[actTransformInfoCommand-112]
_ = x[actTransformInputLabel-113]
_ = x[actTransformListLabel-114]
_ = x[actTransformNth-115]
_ = x[actTransformWithNth-116]
_ = x[actTransformPointer-117]
_ = x[actTransformPreviewLabel-118]
_ = x[actTransformPrompt-119]
_ = x[actTransformQuery-120]
_ = x[actTransformSearch-121]
_ = x[actTrigger-122]
_ = x[actBgTransform-123]
_ = x[actBgTransformBorderLabel-124]
_ = x[actBgTransformGhost-125]
_ = x[actBgTransformHeader-126]
_ = x[actBgTransformHeaderLines-127]
_ = x[actBgTransformFooter-128]
_ = x[actBgTransformHeaderLabel-129]
_ = x[actBgTransformFooterLabel-130]
_ = x[actBgTransformInfoCommand-131]
_ = x[actBgTransformInputLabel-132]
_ = x[actBgTransformListLabel-133]
_ = x[actBgTransformNth-134]
_ = x[actBgTransformWithNth-135]
_ = x[actBgTransformPointer-136]
_ = x[actBgTransformPreviewLabel-137]
_ = x[actBgTransformPrompt-138]
_ = x[actBgTransformQuery-139]
_ = x[actBgTransformSearch-140]
_ = x[actBgCancel-141]
_ = x[actSearch-142]
_ = x[actPreview-143]
_ = x[actPreviewTop-144]
_ = x[actPreviewBottom-145]
_ = x[actPreviewUp-146]
_ = x[actPreviewDown-147]
_ = x[actPreviewPageUp-148]
_ = x[actPreviewPageDown-149]
_ = x[actPreviewHalfPageUp-150]
_ = x[actPreviewHalfPageDown-151]
_ = x[actPrevHistory-152]
_ = x[actPrevSelected-153]
_ = x[actPrint-154]
_ = x[actPut-155]
_ = x[actNextHistory-156]
_ = x[actNextSelected-157]
_ = x[actExecute-158]
_ = x[actExecuteSilent-159]
_ = x[actExecuteMulti-160]
_ = x[actSigStop-161]
_ = x[actBest-162]
_ = x[actFirst-163]
_ = x[actLast-164]
_ = x[actReload-165]
_ = x[actReloadSync-166]
_ = x[actDisableSearch-167]
_ = x[actEnableSearch-168]
_ = x[actSelect-169]
_ = x[actDeselect-170]
_ = x[actUnbind-171]
_ = x[actRebind-172]
_ = x[actToggleBind-173]
_ = x[actBecome-174]
_ = x[actShowHeader-175]
_ = x[actHideHeader-176]
_ = x[actBell-177]
_ = x[actExclude-178]
_ = x[actExcludeMulti-179]
_ = x[actAsync-180]
_ = x[actWait-181]
}
const _actionType_name = "actIgnoreactStartactClickactInvalidactBracketedPasteBeginactBracketedPasteEndactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactBackwardSubWordactCancelactChangeBorderLabelactChangeGhostactChangeHeaderactChangeHeaderLinesactChangeFooteractChangeHeaderLabelactChangeFooterLabelactChangeInputLabelactChangeListLabelactChangeMultiactChangeNthactChangeWithNthactChangePointeractChangePreviewactChangePreviewLabelactChangePreviewWindowactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactForwardSubWordactKillLineactKillWordactKillSubWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactBackwardKillSubWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleWrapWordactToggleMultiLineactToggleHscrollactToggleRawactEnableRawactDisableRawactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactDownMatchactUpactUpMatchactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTogglePreviewWrapWordactTransformactTransformBorderLabelactTransformGhostactTransformHeaderactTransformHeaderLinesactTransformFooteractTransformHeaderLabelactTransformFooterLabelactTransformInputLabelactTransformListLabelactTransformNthactTransformWithNthactTransformPointeractTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactTriggeractBgTransformactBgTransformBorderLabelactBgTransformGhostactBgTransformHeaderactBgTransformHeaderLinesactBgTransformFooteractBgTransformHeaderLabelactBgTransformFooterLabelactBgTransformInputLabelactBgTransformListLabelactBgTransformNthactBgTransformWithNthactBgTransformPointeractBgTransformPreviewLabelactBgTransformPromptactBgTransformQueryactBgTransformSearchactBgCancelactSearchactPreviewactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactBestactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBellactExcludeactExcludeMultiactAsyncactWait"
const _actionType_name = "actIgnoreactStartactClickactInvalidactBracketedPasteBeginactBracketedPasteEndactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactBackwardSubWordactCancelactChangeBorderLabelactChangeGhostactChangeHeaderactChangeHeaderLinesactChangeFooteractChangeHeaderLabelactChangeFooterLabelactChangeInfoCommandactChangeInputLabelactChangeListLabelactChangeMultiactChangeNthactChangeWithNthactChangePointeractChangePreviewactChangePreviewLabelactChangePreviewWindowactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactForwardSubWordactKillLineactKillWordactKillSubWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactBackwardKillSubWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleWrapWordactToggleMultiLineactToggleHscrollactToggleRawactEnableRawactDisableRawactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactDownMatchactUpactUpMatchactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTogglePreviewWrapWordactTransformactTransformBorderLabelactTransformGhostactTransformHeaderactTransformHeaderLinesactTransformFooteractTransformHeaderLabelactTransformFooterLabelactTransformInfoCommandactTransformInputLabelactTransformListLabelactTransformNthactTransformWithNthactTransformPointeractTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactTriggeractBgTransformactBgTransformBorderLabelactBgTransformGhostactBgTransformHeaderactBgTransformHeaderLinesactBgTransformFooteractBgTransformHeaderLabelactBgTransformFooterLabelactBgTransformInfoCommandactBgTransformInputLabelactBgTransformListLabelactBgTransformNthactBgTransformWithNthactBgTransformPointeractBgTransformPreviewLabelactBgTransformPromptactBgTransformQueryactBgTransformSearchactBgCancelactSearchactPreviewactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactBestactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBellactExcludeactExcludeMultiactAsyncactWait"
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 57, 77, 84, 92, 110, 118, 127, 144, 165, 180, 201, 225, 240, 258, 267, 287, 301, 316, 336, 351, 371, 391, 410, 428, 442, 454, 470, 486, 502, 523, 545, 560, 574, 588, 601, 618, 626, 639, 655, 667, 675, 689, 703, 720, 731, 742, 756, 774, 791, 798, 817, 839, 851, 865, 874, 889, 901, 914, 925, 936, 948, 962, 983, 998, 1011, 1028, 1046, 1062, 1074, 1086, 1099, 1114, 1128, 1140, 1152, 1169, 1176, 1188, 1193, 1203, 1212, 1223, 1234, 1247, 1262, 1273, 1286, 1301, 1308, 1321, 1334, 1351, 1366, 1379, 1393, 1407, 1423, 1443, 1467, 1479, 1502, 1519, 1537, 1560, 1578, 1601, 1624, 1646, 1667, 1682, 1701, 1720, 1744, 1762, 1779, 1797, 1807, 1821, 1846, 1865, 1885, 1910, 1930, 1955, 1980, 2004, 2027, 2044, 2065, 2086, 2112, 2132, 2151, 2171, 2182, 2191, 2201, 2214, 2230, 2242, 2256, 2272, 2290, 2310, 2332, 2346, 2361, 2369, 2375, 2389, 2404, 2414, 2430, 2445, 2455, 2462, 2470, 2477, 2486, 2499, 2515, 2530, 2539, 2550, 2559, 2568, 2581, 2590, 2603, 2616, 2623, 2633, 2648, 2656, 2663}
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 57, 77, 84, 92, 110, 118, 127, 144, 165, 180, 201, 225, 240, 258, 267, 287, 301, 316, 336, 351, 371, 391, 411, 430, 448, 462, 474, 490, 506, 522, 543, 565, 580, 594, 608, 621, 638, 646, 659, 675, 687, 695, 709, 723, 740, 751, 762, 776, 794, 811, 818, 837, 859, 871, 885, 894, 909, 921, 934, 945, 956, 968, 982, 1003, 1018, 1031, 1048, 1066, 1082, 1094, 1106, 1119, 1134, 1148, 1160, 1172, 1189, 1196, 1208, 1213, 1223, 1232, 1243, 1254, 1267, 1282, 1293, 1306, 1321, 1328, 1341, 1354, 1371, 1386, 1399, 1413, 1427, 1443, 1463, 1487, 1499, 1522, 1539, 1557, 1580, 1598, 1621, 1644, 1667, 1689, 1710, 1725, 1744, 1763, 1787, 1805, 1822, 1840, 1850, 1864, 1889, 1908, 1928, 1953, 1973, 1998, 2023, 2048, 2072, 2095, 2112, 2133, 2154, 2180, 2200, 2219, 2239, 2250, 2259, 2269, 2282, 2298, 2310, 2324, 2340, 2358, 2378, 2400, 2414, 2429, 2437, 2443, 2457, 2472, 2482, 2498, 2513, 2523, 2530, 2538, 2545, 2554, 2567, 2583, 2598, 2607, 2618, 2627, 2636, 2649, 2658, 2671, 2684, 2691, 2701, 2716, 2724, 2731}
func (i actionType) String() string {
if i < 0 || i >= actionType(len(_actionType_index)-1) {
idx := int(i) - 0
if i < 0 || idx >= len(_actionType_index)-1 {
return "actionType(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _actionType_name[_actionType_index[i]:_actionType_index[i+1]]
return _actionType_name[_actionType_index[idx]:_actionType_index[idx+1]]
}
-1
View File
@@ -264,7 +264,6 @@ func Run(opts *Options) (int, error) {
}
pattern := patternBuilder([]rune(*opts.Filter))
matcher.sort = pattern.sortable
transformer := buildItemTransformer(opts)
+14 -7
View File
@@ -1672,21 +1672,21 @@ const (
func init() {
argActionRegexp = regexp.MustCompile(
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|bg-transform|transform)-(?:query|prompt|(?:border|list|preview|input|header|footer)-label|header-lines|header|footer|search|with-nth|nth|pointer|ghost)|bg-transform|transform|change-(?:preview-window|preview|multi)|(?:re|un|toggle-)bind|pos|put|print|search|trigger)`)
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|bg-transform|transform)-(?:query|prompt|(?:border|list|preview|input|header|footer)-label|header-lines|header|footer|search|with-nth|nth|pointer|ghost|info-command)|bg-transform|transform|change-(?:preview-window|preview|multi)|(?:re|un|toggle-)bind|pos|put|print|search|trigger)`)
splitRegexp = regexp.MustCompile("[,:]+")
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
}
func maskActionContents(action string) string {
masked := ""
var sb strings.Builder
Loop:
for len(action) > 0 {
loc := argActionRegexp.FindStringIndex(action)
if loc == nil {
masked += action
sb.WriteString(action)
break
}
masked += action[:loc[1]]
sb.WriteString(action[:loc[1]])
action = action[loc[1]:]
if len(action) == 0 {
break
@@ -1695,7 +1695,7 @@ Loop:
var ce string
switch action[0] {
case ':':
masked += strings.Repeat(" ", len(action))
sb.WriteString(strings.Repeat(" ", len(action)))
break Loop
case '(':
ce = ")"
@@ -1716,7 +1716,7 @@ Loop:
// @$ or @+
loc = regexp.MustCompile(fmt.Sprintf(`(?s)^%s.*?(%s[+,]|%s$)`, cs, ce, ce)).FindStringIndex(action)
if loc == nil {
masked += action
sb.WriteString(action)
break
}
// Keep + or , at the end
@@ -1724,9 +1724,10 @@ Loop:
if lastChar == '+' || lastChar == ',' {
loc[1]--
}
masked += strings.Repeat(" ", loc[1])
sb.WriteString(strings.Repeat(" ", loc[1]))
action = action[loc[1]:]
}
masked := sb.String()
masked = strings.ReplaceAll(masked, ",,,", string([]rune{',', escapedComma, ','}))
masked = strings.ReplaceAll(masked, ",:,", string([]rune{',', escapedColon, ','}))
masked = strings.ReplaceAll(masked, "::", string([]rune{escapedColon, ':'}))
@@ -2100,6 +2101,8 @@ func isExecuteAction(str string) actionType {
return actChangeHeaderLabel
case "change-footer-label":
return actChangeFooterLabel
case "change-info-command":
return actChangeInfoCommand
case "change-ghost":
return actChangeGhost
case "change-pointer":
@@ -2144,6 +2147,8 @@ func isExecuteAction(str string) actionType {
return actTransformHeaderLabel
case "transform-footer-label":
return actTransformFooterLabel
case "transform-info-command":
return actTransformInfoCommand
case "transform-footer":
return actTransformFooter
case "transform-header":
@@ -2178,6 +2183,8 @@ func isExecuteAction(str string) actionType {
return actBgTransformHeaderLabel
case "bg-transform-footer-label":
return actBgTransformFooterLabel
case "bg-transform-info-command":
return actBgTransformInfoCommand
case "bg-transform-footer":
return actBgTransformFooter
case "bg-transform-header":
+20 -22
View File
@@ -275,22 +275,12 @@ func (r *Reader) readFiles(roots []string, opts walkerOpts, ignores []string) bo
Sort: fastwalk.SortFilesFirst,
}
// When following symlinks, precompute the absolute real paths of walker
// roots so we can skip symlinks that point to an ancestor. fastwalk's
// built-in loop detection (shouldTraverse) catches loops on the second
// pass, but a single pass through a symlink like z: -> / already
// traverses the entire root filesystem, causing severe resource
// exhaustion. Skipping ancestor symlinks prevents this entirely.
var absRoots []string
if opts.follow {
for _, root := range roots {
if real, err := filepath.EvalSymlinks(root); err == nil {
if abs, err := filepath.Abs(real); err == nil {
absRoots = append(absRoots, filepath.Clean(abs))
}
}
}
}
// When following symlinks, skip targets that point to an ancestor of the
// current walker root. fastwalk's built-in loop detection (shouldTraverse)
// catches loops on the second pass, but a single pass through a symlink
// like z: -> / already traverses the entire root filesystem, causing
// severe resource exhaustion. Skipping ancestor symlinks prevents this.
var absRoot string
ignoresBase := []string{}
ignoresFull := []string{}
@@ -326,19 +316,17 @@ func (r *Reader) readFiles(roots []string, opts walkerOpts, ignores []string) bo
return filepath.SkipDir
}
// Skip symlinks whose target is an ancestor of (or equal to)
// any walker root. Following such symlinks would traverse a
// the current walker root. Following such symlinks would traverse a
// superset of the tree we're already walking.
if isDirSymlink && len(absRoots) > 0 {
if isDirSymlink && absRoot != "" {
if target, err := filepath.EvalSymlinks(path); err == nil {
if abs, err := filepath.Abs(target); err == nil {
abs = filepath.Clean(abs)
if abs == string(os.PathSeparator) {
return filepath.SkipDir
}
for _, absRoot := range absRoots {
if absRoot == abs || strings.HasPrefix(absRoot, abs+string(os.PathSeparator)) {
return filepath.SkipDir
}
if absRoot == abs || strings.HasPrefix(absRoot, abs+string(os.PathSeparator)) {
return filepath.SkipDir
}
}
}
@@ -377,6 +365,16 @@ func (r *Reader) readFiles(roots []string, opts walkerOpts, ignores []string) bo
}
noerr := true
for _, root := range roots {
// Compare against this root only. Safe to update here because
// fastwalk.Walk returns after all its workers finish.
absRoot = ""
if opts.follow {
if real, err := filepath.EvalSymlinks(root); err == nil {
if abs, err := filepath.Abs(real); err == nil {
absRoot = filepath.Clean(abs)
}
}
}
noerr = noerr && (fastwalk.Walk(&conf, root, fn) == nil)
}
return noerr
+71
View File
@@ -1,6 +1,10 @@
package fzf
import (
"os"
"path/filepath"
"slices"
"sync"
"testing"
"time"
@@ -66,3 +70,70 @@ func TestReadFromCommand(t *testing.T) {
t.Error("EvtReadFin should be set")
}
}
func walkFiles(t *testing.T, roots ...string) []string {
t.Helper()
var mu sync.Mutex
files := []string{}
reader := NewReader(func(data []byte) bool {
mu.Lock()
defer mu.Unlock()
files = append(files, filepath.FromSlash(string(data)))
return true
}, util.NewEventBox(), nil, false, false)
if !reader.readFiles(roots, walkerOpts{file: true, follow: true}, nil) {
t.Fatal("file walk failed")
}
slices.Sort(files)
return files
}
func TestReadFilesSymlinkAcrossRoots(t *testing.T) {
base := t.TempDir()
app := filepath.Join(base, "app")
shared := filepath.Join(base, "shared")
frontend := filepath.Join(shared, "frontend")
os.MkdirAll(app, 0755)
os.MkdirAll(frontend, 0755)
os.WriteFile(filepath.Join(shared, "docs.txt"), nil, 0644)
os.WriteFile(filepath.Join(frontend, "index.js"), nil, 0644)
if err := os.Symlink(filepath.Join("..", "shared"), filepath.Join(app, "shared")); err != nil {
t.Skipf("cannot create symlink: %v", err)
}
viaApp := []string{
filepath.Join(app, "shared", "docs.txt"),
filepath.Join(app, "shared", "frontend", "index.js"),
}
both := append(slices.Clone(viaApp), filepath.Join(frontend, "index.js"))
slices.Sort(both)
for _, tc := range []struct {
roots []string
want []string
}{
{[]string{app}, viaApp},
{[]string{app, frontend}, both},
{[]string{frontend, app}, both},
} {
if files := walkFiles(t, tc.roots...); !slices.Equal(files, tc.want) {
t.Errorf("roots = %v, files = %v, want %v", tc.roots, files, tc.want)
}
}
}
func TestReadFilesSkipsAncestorSymlinks(t *testing.T) {
base := t.TempDir()
project := filepath.Join(base, "project")
os.Mkdir(project, 0755)
file := filepath.Join(project, "file.txt")
os.WriteFile(file, nil, 0644)
os.WriteFile(filepath.Join(base, "outside.txt"), nil, 0644)
for name, target := range map[string]string{"parent": "..", "self": "."} {
if err := os.Symlink(target, filepath.Join(project, name)); err != nil {
t.Skipf("cannot create symlink: %v", err)
}
}
if files := walkFiles(t, project); !slices.Equal(files, []string{file}) {
t.Errorf("files = %v, want [%s]", files, file)
}
}
+13
View File
@@ -589,6 +589,7 @@ const (
actChangeFooter
actChangeHeaderLabel
actChangeFooterLabel
actChangeInfoCommand
actChangeInputLabel
actChangeListLabel
actChangeMulti
@@ -676,6 +677,7 @@ const (
actTransformFooter
actTransformHeaderLabel
actTransformFooterLabel
actTransformInfoCommand
actTransformInputLabel
actTransformListLabel
actTransformNth
@@ -696,6 +698,7 @@ const (
actBgTransformFooter
actBgTransformHeaderLabel
actBgTransformFooterLabel
actBgTransformInfoCommand
actBgTransformInputLabel
actBgTransformListLabel
actBgTransformNth
@@ -764,6 +767,7 @@ func processExecution(action actionType) bool {
actTransformFooter,
actTransformHeaderLabel,
actTransformFooterLabel,
actTransformInfoCommand,
actTransformInputLabel,
actTransformListLabel,
actTransformNth,
@@ -781,6 +785,7 @@ func processExecution(action actionType) bool {
actBgTransformFooter,
actBgTransformHeaderLabel,
actBgTransformFooterLabel,
actBgTransformInfoCommand,
actBgTransformInputLabel,
actBgTransformListLabel,
actBgTransformNth,
@@ -792,6 +797,7 @@ func processExecution(action actionType) bool {
actBgTransformSearch,
actPreview,
actChangePreview,
actChangeInfoCommand,
actRefreshPreview,
actExecute,
actExecuteSilent,
@@ -7447,6 +7453,13 @@ func (t *Terminal) Loop() error {
t.footerLabel, t.footerLabelLen = t.ansiLabelPrinter(label, &tui.ColFooterLabel, false)
req(reqRedrawFooterLabel)
})
case actChangeInfoCommand, actTransformInfoCommand, actBgTransformInfoCommand:
capture(true, func(command string) {
if t.infoCommand != command {
t.infoCommand = command
req(reqInfo)
}
})
case actChangeInputLabel, actTransformInputLabel, actBgTransformInputLabel:
capture(true, func(label string) {
t.inputLabelOpts.label = label
+30
View File
@@ -1826,6 +1826,36 @@ class TestCore < TestInteractive
tmux.until { assert_match(/^ 2 xx/, it[-2]) }
end
def test_change_info_command
tmux.send_keys(%(seq 100 | #{FZF} --separator x --bind 'a:change-info-command(echo {}: $FZF_INFO)' --bind 'b:change-info-command()'), :Enter)
tmux.until { assert_match(%r{^ 100/100 xx}, it[-2]) }
tmux.send_keys :a
tmux.until { assert_match(%r{^ 1: 100/100 xx}, it[-2]) }
# Placeholders are evaluated on each update, not when the action runs
tmux.send_keys :Up
tmux.until { assert_match(%r{^ 2: 100/100 xx}, it[-2]) }
tmux.send_keys :b
tmux.until { assert_match(%r{^ 100/100 xx}, it[-2]) }
end
def test_transform_info_command
tmux.send_keys(%(seq 100 | #{FZF} --separator x --bind 'a:transform-info-command(echo "echo {}: \\$FZF_POS")'), :Enter)
tmux.until { assert_match(%r{^ 100/100 xx}, it[-2]) }
tmux.send_keys :a
tmux.until { assert_match(%r{^ 1: 1 xx}, it[-2]) }
tmux.send_keys :Up
tmux.until { assert_match(%r{^ 1: 2 xx}, it[-2]) }
end
def test_bg_transform_info_command
tmux.send_keys(%(seq 100 | #{FZF} --separator x --bind 'a:bg-transform-info-command(sleep 0.5; echo "echo {}: \\$FZF_POS")'), :Enter)
tmux.until { assert_match(%r{^ 100/100 xx}, it[-2]) }
tmux.send_keys :a
tmux.until { assert_match(%r{^ 1: 1 xx}, it[-2]) }
tmux.send_keys :Up
tmux.until { assert_match(%r{^ 1: 2 xx}, it[-2]) }
end
def test_prev_next_selected
tmux.send_keys 'seq 10 | fzf --multi --bind ctrl-n:next-selected,ctrl-p:prev-selected', :Enter
tmux.until { |lines| assert_equal 10, lines.match_count }
+9
View File
@@ -25,6 +25,15 @@ class TestFilter < TestBase
assert_equal 1, `echo Foo bar | #{FZF} -x -f "foo Fbar" | wc -l`.to_i
end
def test_filter_no_sort
input = "printf 'abz\\nab\\nzab\\n'"
['', '--sync'].each do |opts|
assert_equal %w[ab abz zab], `#{input} | #{FZF} -f ab #{opts}`.lines(chomp: true)
assert_equal %w[abz ab zab], `#{input} | #{FZF} -f ab --no-sort #{opts}`.lines(chomp: true)
assert_equal %w[zab ab abz], `#{input} | #{FZF} -f ab --no-sort --tac #{opts}`.lines(chomp: true)
end
end
def test_filter_exitstatus
# filter / streaming filter
['', '--no-sort'].each do |opts|
+43
View File
@@ -0,0 +1,43 @@
<?xml version='1.0' encoding='utf-8'?>
<!--
Per-machine installer for fzf. See the msi job in .github/workflows/release.yml.
wix build wix/main.wxs -arch <x64|arm64> -d Version=<ver> -d BinDir=<dir of fzf.exe>
-->
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Package
Name="fzf"
Manufacturer="junegunn"
Version="$(var.Version)"
UpgradeCode="C34C8C24-3E05-4D14-BD9A-439C3F6E9942"
Language="1033"
Scope="perMachine">
<SummaryInformation Description="fzf - a command-line fuzzy finder" Manufacturer="junegunn" />
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed. Setup will now exit." />
<MediaTemplate EmbedCab="yes" />
<StandardDirectory Id="ProgramFiles6432Folder">
<Directory Id="APPLICATIONFOLDER" Name="fzf">
<Directory Id="BinFolder" Name="bin" />
</Directory>
</StandardDirectory>
<Feature Id="Main" Title="fzf" Level="1" AllowAbsent="no">
<ComponentGroupRef Id="MainComponents" />
</Feature>
<ComponentGroup Id="MainComponents" Directory="BinFolder">
<Component Id="FzfBinary">
<File Id="FzfExe" Name="fzf.exe" Source="$(var.BinDir)\fzf.exe" KeyPath="yes" />
</Component>
<Component Id="LicenseFile" Directory="APPLICATIONFOLDER">
<File Id="License" Name="LICENSE" Source="LICENSE" KeyPath="yes" />
</Component>
<Component Id="PathEnv" Guid="8B7C2C6A-F4A0-4E3E-9E37-2E8F3E4E5B6A">
<Environment Id="PATH" Name="PATH" Value="[BinFolder]" Permanent="no" Part="last" Action="set" System="yes" />
</Component>
</ComponentGroup>
</Package>
</Wix>