Compare commits

...

27 Commits

Author SHA1 Message Date
Junegunn Choi
beb2de2dd9 0.46.0 2024-01-23 23:47:24 +09:00
Junegunn Choi
2a8b65e105 Fix highlighting of regions that are matched multiple times
Fix #3596
2024-01-23 12:19:32 +09:00
dependabot[bot]
62a916bc24 Bump crate-ci/typos from 1.16.4 to 1.17.2 (#3595)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.16.4 to 1.17.2.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.16.4...v1.17.2)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-23 10:21:01 +09:00
dependabot[bot]
c47b833e7b Bump actions/dependency-review-action from 3 to 4 (#3594)
Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 3 to 4.
- [Release notes](https://github.com/actions/dependency-review-action/releases)
- [Commits](https://github.com/actions/dependency-review-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/dependency-review-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-23 10:20:39 +09:00
LangLangBart
09b0958b5f docs(ADVANCED.md): replace placeholder with env variable for toggle single key binding (#3593) 2024-01-22 13:17:03 +09:00
Junegunn Choi
3a4c3d3e58 Add link to fzf Theme Playground by @vitormv
https://vitormv.github.io/fzf-themes/
2024-01-21 23:12:57 +09:00
Junegunn Choi
7484292e63 Avoid deadlocks by adding a 2 second timeout to GET / endpoint
Because fzf processes HTTP GET requests in the main event loop,
accessing the endpoint from within execute/transform actions would
result in a deadlock and hang fzf indefinitely. This commit sets
a 2 second timeout to avoid the deadlock.
2024-01-21 23:04:37 +09:00
Junegunn Choi
687c2741b8 Add 'resize' event
Close #3570
2024-01-21 15:30:59 +09:00
junegunn
2fb285e530 Deploying to master from @ junegunn/fzf@16f6473938 🚀 2024-01-21 00:01:45 +00:00
Junegunn Choi
16f6473938 Change mattn/go-runewidth dependency to rivo/uniseg for accurate results
Related #3588 #3588 #3567
2024-01-21 02:54:41 +09:00
Junegunn Choi
66546208b2 Update goreleaser flags 2024-01-20 14:36:52 +09:00
Junegunn Choi
532274045e Update to the latest go 2024-01-20 14:35:00 +09:00
LangLangBart
9347c72fb6 docs(ADVANCED.md): Add fzf example switching ripgrep/fzf with single hotkey (#3590) 2024-01-20 13:47:54 +09:00
Junegunn Choi
e90bb7169c [zsh] Handle '*' suffix in history line numbers
Fix #3591
2024-01-20 13:43:15 +09:00
Junegunn Choi
8a2c41e183 Handle ambiguous emoji width
Fix #3588
2024-01-19 16:41:50 +09:00
Junegunn Choi
59fb65293a README.md: More information on image support 2024-01-17 13:21:00 +09:00
Junegunn Choi
e7718b92b7 Kitty image support improvements
* Use `--unicode-placeholder` for consistent result in and out of tmux
* Use updated version of junegunn/go-runewidth that handles diacritics
  used in Kitty Unicode placeholder

Close #3567
2024-01-17 00:17:22 +09:00
Junegunn Choi
cdfaf761df Expose state information via environment variables to child processes
Close #3582
2024-01-16 14:18:31 +09:00
Junegunn Choi
1a9ea6f738 Remove 'replace' directive for 'go install' compatibility
Close #3577
2024-01-14 17:12:24 +09:00
junegunn
945c1c8597 Deploying to master from @ junegunn/fzf@e4d0f7acd5 🚀 2024-01-14 00:01:41 +00:00
dependabot[bot]
e4d0f7acd5 Bump golang.org/x/term from 0.15.0 to 0.16.0 (#3564)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.15.0 to 0.16.0.
- [Commits](https://github.com/golang/term/compare/v0.15.0...v0.16.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-09 12:31:14 +09:00
Junegunn Choi
250496c953 Add 'result' event that is triggered when the result list is ready
Close #3560
2024-01-07 17:46:21 +09:00
Junegunn Choi
e47dc758c9 Fix focus event not triggered in certain cases 2024-01-07 15:43:17 +09:00
Junegunn Choi
b92a843c5f Use Ubuntu 22 to match GitHub Actions environment 2024-01-07 15:10:06 +09:00
Junegunn Choi
91bea9c5b3 Use forked version of go-runewidth
Fix #3558

  go get github.com/junegunn/go-runewidth@fzf
2024-01-07 15:09:46 +09:00
junegunn
d75bb5cbe1 Deploying to master from @ junegunn/fzf@2671259fdb 🚀 2024-01-07 00:01:39 +00:00
danztran
2671259fdb [zsh] Make CTRL-R compatible with accept-or-print-query (#3557)
Fix #3556

Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2024-01-05 10:47:54 +09:00
29 changed files with 336 additions and 121 deletions

View File

@@ -11,4 +11,4 @@ jobs:
- name: 'Checkout Repository' - name: 'Checkout Repository'
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: 'Dependency Review' - name: 'Dependency Review'
uses: actions/dependency-review-action@v3 uses: actions/dependency-review-action@v4

View File

@@ -7,4 +7,4 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: crate-ci/typos@v1.16.4 - uses: crate-ci/typos@v1.17.2

View File

@@ -1 +1 @@
golang 1.20.4 golang 1.21.6

View File

@@ -1,8 +1,8 @@
Advanced fzf examples Advanced fzf examples
====================== ======================
* *Last update: 2023/12/29* * *Last update: 2024/01/20*
* *Requires fzf 0.45.0 or above* * *Requires fzf 0.46.0 or above*
--- ---
@@ -22,12 +22,14 @@ Advanced fzf examples
* [Using fzf as interactive Ripgrep launcher](#using-fzf-as-interactive-ripgrep-launcher) * [Using fzf as interactive Ripgrep launcher](#using-fzf-as-interactive-ripgrep-launcher)
* [Switching to fzf-only search mode](#switching-to-fzf-only-search-mode) * [Switching to fzf-only search mode](#switching-to-fzf-only-search-mode)
* [Switching between Ripgrep mode and fzf mode](#switching-between-ripgrep-mode-and-fzf-mode) * [Switching between Ripgrep mode and fzf mode](#switching-between-ripgrep-mode-and-fzf-mode)
* [Switching between Ripgrep mode and fzf mode using a single key binding](#switching-between-ripgrep-mode-and-fzf-mode-using-a-single-key-binding)
* [Log tailing](#log-tailing) * [Log tailing](#log-tailing)
* [Key bindings for git objects](#key-bindings-for-git-objects) * [Key bindings for git objects](#key-bindings-for-git-objects)
* [Files listed in `git status`](#files-listed-in-git-status) * [Files listed in `git status`](#files-listed-in-git-status)
* [Branches](#branches) * [Branches](#branches)
* [Commit hashes](#commit-hashes) * [Commit hashes](#commit-hashes)
* [Color themes](#color-themes) * [Color themes](#color-themes)
* [fzf Theme Playground](#fzf-theme-playground)
* [Generating fzf color theme from Vim color schemes](#generating-fzf-color-theme-from-vim-color-schemes) * [Generating fzf color theme from Vim color schemes](#generating-fzf-color-theme-from-vim-color-schemes)
<!-- vim-markdown-toc --> <!-- vim-markdown-toc -->
@@ -220,17 +222,17 @@ To make a key binding behave differently each time it is pressed, we need:
2. and a way to dynamically perform different actions depending on the state. 2. and a way to dynamically perform different actions depending on the state.
The following example shows how to 1. store the current mode in the prompt The following example shows how to 1. store the current mode in the prompt
string, 2. and use this information (`{fzf:prompt}`) to determine which string, 2. and use this information (`$FZF_PROMPT`) to determine which
actions to perform using the `transform` action. actions to perform using the `transform` action.
```sh ```sh
fd --type file | fd --type file |
fzf --prompt 'Files> ' \ fzf --prompt 'Files> ' \
--header 'CTRL-T: Switch between Files/Directories' \ --header 'CTRL-T: Switch between Files/Directories' \
--bind 'ctrl-t:transform:[[ ! {fzf:prompt} =~ Files ]] && --bind 'ctrl-t:transform:[[ ! $FZF_PROMPT =~ Files ]] &&
echo "change-prompt(Files> )+reload(fd --type file)" || echo "change-prompt(Files> )+reload(fd --type file)" ||
echo "change-prompt(Directories> )+reload(fd --type directory)"' \ echo "change-prompt(Directories> )+reload(fd --type directory)"' \
--preview '[[ {fzf:prompt} =~ Files ]] && bat --color=always {} || tree -C {}' --preview '[[ $FZF_PROMPT =~ Files ]] && bat --color=always {} || tree -C {}'
``` ```
Ripgrep integration Ripgrep integration
@@ -467,6 +469,41 @@ INITIAL_QUERY="${*:-}"
[0.30.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0300 [0.30.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0300
[0.36.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0360 [0.36.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0360
### Switching between Ripgrep mode and fzf mode using a single key binding
In contrast to the previous version, we use just one hotkey to toggle between
ripgrep and fzf mode. This is achieved by using the `$FZF_PROMPT` as a state
within the `transform` action, a feature introduced in [fzf 0.45.0][0.45.0]. A
more detailed explanation of this feature can be found in a previous section -
[Toggling with a single keybinding](#toggling-with-a-single-key-binding).
[0.45.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0450
When using the `transform` action, the placeholder (`\{q}`) should be escaped to
prevent immediate evaluation.
```sh
#!/usr/bin/env bash
# Switch between Ripgrep mode and fzf filtering mode (CTRL-T)
rm -f /tmp/rg-fzf-{r,f}
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
INITIAL_QUERY="${*:-}"
: | fzf --ansi --disabled --query "$INITIAL_QUERY" \
--bind "start:reload:$RG_PREFIX {q}" \
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
--bind 'ctrl-t:transform:[[ ! $FZF_PROMPT =~ ripgrep ]] &&
echo "rebind(change)+change-prompt(1. ripgrep> )+disable-search+transform-query:echo \{q} > /tmp/rg-fzf-f; cat /tmp/rg-fzf-r" ||
echo "unbind(change)+change-prompt(2. fzf> )+enable-search+transform-query:echo \{q} > /tmp/rg-fzf-r; cat /tmp/rg-fzf-f"' \
--color "hl:-1:underline,hl+:-1:underline:reverse" \
--prompt '1. ripgrep> ' \
--delimiter : \
--header 'CTRL-T: Switch between ripgrep/fzf' \
--preview 'bat --color=always {1} --highlight-line {2}' \
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \
--bind 'enter:become(vim {1} +{2})'
```
Log tailing Log tailing
----------- -----------
@@ -596,6 +633,12 @@ export FZF_DEFAULT_OPTS='--color=bg+:#293739,bg:#1B1D1E,border:#808080,spinner:#
![molokai](https://user-images.githubusercontent.com/700826/113475085-8619f300-94ae-11eb-85e4-2766fc3246bf.png) ![molokai](https://user-images.githubusercontent.com/700826/113475085-8619f300-94ae-11eb-85e4-2766fc3246bf.png)
### fzf Theme Playground
[fzf Theme Playground](https://vitormv.github.io/fzf-themes/) created by
[Vitor Mello](https://github.com/vitormv) is a webpage where you can
interactively create fzf themes.
### Generating fzf color theme from Vim color schemes ### Generating fzf color theme from Vim color schemes
The Vim plugin of fzf can generate `--color` option from the current color The Vim plugin of fzf can generate `--color` option from the current color

View File

@@ -34,8 +34,8 @@ make release
Third-party libraries used Third-party libraries used
-------------------------- --------------------------
- [mattn/go-runewidth](https://github.com/mattn/go-runewidth) - [rivo/uniseg](https://github.com/rivo/uniseg)
- Licensed under [MIT](http://mattn.mit-license.org) - Licensed under [MIT](https://raw.githubusercontent.com/rivo/uniseg/master/LICENSE.txt)
- [mattn/go-shellwords](https://github.com/mattn/go-shellwords) - [mattn/go-shellwords](https://github.com/mattn/go-shellwords)
- Licensed under [MIT](http://mattn.mit-license.org) - Licensed under [MIT](http://mattn.mit-license.org)
- [mattn/go-isatty](https://github.com/mattn/go-isatty) - [mattn/go-isatty](https://github.com/mattn/go-isatty)

View File

@@ -1,6 +1,46 @@
CHANGELOG CHANGELOG
========= =========
0.46.0
------
- Added two new events
- `result` - triggered when the filtering for the current query is complete and the result list is ready
- `resize` - triggered when the terminal size is changed
- fzf now exports the following environment variables to the child processes
| Variable | Description |
| --- | --- |
| `FZF_LINES` | Number of lines fzf takes up excluding padding and margin |
| `FZF_COLUMNS` | Number of columns fzf takes up excluding padding and margin |
| `FZF_TOTAL_COUNT` | Total number of items |
| `FZF_MATCH_COUNT` | Number of matched items |
| `FZF_SELECT_COUNT` | Number of selected items |
| `FZF_QUERY` | Current query string |
| `FZF_PROMPT` | Prompt string |
| `FZF_ACTION` | The name of the last action performed |
- This allows you to write sophisticated transformations like so
```sh
# Script to dynamically resize the preview window
transformer='
# 1 line for info, another for prompt, and 2 more lines for preview window border
lines=$(( FZF_LINES - FZF_MATCH_COUNT - 4 ))
if [[ $FZF_MATCH_COUNT -eq 0 ]]; then
echo "change-preview-window:hidden"
elif [[ $lines -gt 3 ]]; then
echo "change-preview-window:$lines"
elif [[ $FZF_PREVIEW_LINES -ne 3 ]]; then
echo "change-preview-window:3"
fi
'
seq 10000 | fzf --preview 'seq {} 10000' --preview-window up \
--bind "result:transform:$transformer" \
--bind "resize:transform:$transformer"
```
- And we're phasing out `{fzf:prompt}` and `{fzf:action}`
- Changed [mattn/go-runewidth](https://github.com/mattn/go-runewidth) dependency to [rivo/uniseg](https://github.com/rivo/uniseg) for accurate results
- Set `--ambidouble` if your terminal displays ambiguous width characters (e.g. box-drawing characters for borders) as 2 columns
- `RUNEWIDTH_EASTASIAN=1` is still respected for backward compatibility, but it's recommended that you use this new option instead
- Bug fixes
0.45.0 0.45.0
------ ------
- Added `transform` action to conditionally perform a series of actions - Added `transform` action to conditionally perform a series of actions
@@ -92,7 +132,7 @@ CHANGELOG
# --transfer-mode=memory is the fastest option but if you want fzf to be able # --transfer-mode=memory is the fastest option but if you want fzf to be able
# to redraw the image on terminal resize or on 'change-preview-window', # to redraw the image on terminal resize or on 'change-preview-window',
# you need to use --transfer-mode=stream. # you need to use --transfer-mode=stream.
kitty icat --clear --transfer-mode=memory --stdin=no --place=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}@0x0 {} | sed \$d kitty icat --clear --transfer-mode=memory --unicode-placeholder --stdin=no --place=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}@0x0 {} | sed \$d
else else
bat --color=always {} bat --color=always {}
fi fi

View File

@@ -1,5 +1,5 @@
FROM --platform=linux/amd64 archlinux FROM --platform=linux/amd64 ubuntu:22.04
RUN pacman -Sy && pacman --noconfirm -S awk git tmux zsh fish ruby procps go make gcc RUN apt-get update -y && apt install -y git make golang zsh fish ruby tmux
RUN gem install --no-document -v 5.14.2 minitest RUN gem install --no-document -v 5.14.2 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
@@ -8,4 +8,5 @@ RUN echo '. ~/.bashrc' >> ~/.bash_profile
RUN rm -f /etc/bash.bashrc RUN rm -f /etc/bash.bashrc
COPY . /fzf COPY . /fzf
RUN cd /fzf && make install && ./install --all RUN cd /fzf && make install && ./install --all
ENV LANG C.UTF-8
CMD tmux new 'set -o pipefail; ruby /fzf/test/test_go.rb | tee out && touch ok' && cat out && [ -e ok ] CMD tmux new 'set -o pipefail; ruby /fzf/test/test_go.rb | tee out && touch ok' && cat out && [ -e ok ]

View File

@@ -93,7 +93,7 @@ generate:
PATH=$(PATH):$(GOPATH)/bin $(GO) generate ./... PATH=$(PATH):$(GOPATH)/bin $(GO) generate ./...
build: build:
goreleaser build --rm-dist --snapshot --skip-post-hooks goreleaser build --clean --snapshot --skip=post-hooks
release: release:
# Make sure that the tests pass and the build works # Make sure that the tests pass and the build works
@@ -126,7 +126,7 @@ endif
git push origin temp --follow-tags --force git push origin temp --follow-tags --force
# Make a GitHub release # Make a GitHub release
goreleaser --rm-dist --release-notes tmp/release-note goreleaser --clean --release-notes tmp/release-note
# Push to master # Push to master
git checkout master git checkout master

File diff suppressed because one or more lines are too long

View File

@@ -53,7 +53,7 @@ if [[ $KITTY_WINDOW_ID ]]; then
# 2. The last line of the output is the ANSI reset code without newline. # 2. The last line of the output is the ANSI reset code without newline.
# This confuses fzf and makes it render scroll offset indicator. # This confuses fzf and makes it render scroll offset indicator.
# So we remove the last line and append the reset code to its previous line. # So we remove the last line and append the reset code to its previous line.
kitty icat --clear --transfer-mode=memory --stdin=no --place="$dim@0x0" "$file" | sed '$d' | sed $'$s/$/\e[m/' kitty icat --clear --transfer-mode=memory --unicode-placeholder --stdin=no --place="$dim@0x0" "$file" | sed '$d' | sed $'$s/$/\e[m/'
# 2. Use chafa with Sixel output # 2. Use chafa with Sixel output
elif command -v chafa > /dev/null; then elif command -v chafa > /dev/null; then

11
go.mod
View File

@@ -2,22 +2,21 @@ module github.com/junegunn/fzf
require ( require (
github.com/gdamore/tcell/v2 v2.5.4 github.com/gdamore/tcell/v2 v2.5.4
github.com/junegunn/uniseg v0.0.0-20240120174029-b504da4f6ed2
github.com/mattn/go-isatty v0.0.17 github.com/mattn/go-isatty v0.0.17
github.com/mattn/go-runewidth v0.0.14
github.com/mattn/go-shellwords v1.0.12 github.com/mattn/go-shellwords v1.0.12
github.com/rivo/uniseg v0.4.4
github.com/saracen/walker v0.1.3 github.com/saracen/walker v0.1.3
golang.org/x/sys v0.15.0 golang.org/x/sys v0.16.0
golang.org/x/term v0.15.0 golang.org/x/term v0.16.0
) )
require ( require (
github.com/gdamore/encoding v1.0.0 // indirect github.com/gdamore/encoding v1.0.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
golang.org/x/mod v0.14.0 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
golang.org/x/sync v0.5.0 // indirect golang.org/x/sync v0.5.0 // indirect
golang.org/x/text v0.5.0 // indirect golang.org/x/text v0.5.0 // indirect
golang.org/x/tools v0.16.1 // indirect
) )
go 1.17 go 1.17

15
go.sum
View File

@@ -2,6 +2,8 @@ github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdk
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.5.4 h1:TGU4tSjD3sCL788vFNeJnTdzpNKIw1H5dgLnJRQVv/k= github.com/gdamore/tcell/v2 v2.5.4 h1:TGU4tSjD3sCL788vFNeJnTdzpNKIw1H5dgLnJRQVv/k=
github.com/gdamore/tcell/v2 v2.5.4/go.mod h1:dZgRy5v4iMobMEcWNYBtREnDZAT9DYmfqIkrgEMxLyw= github.com/gdamore/tcell/v2 v2.5.4/go.mod h1:dZgRy5v4iMobMEcWNYBtREnDZAT9DYmfqIkrgEMxLyw=
github.com/junegunn/uniseg v0.0.0-20240120174029-b504da4f6ed2 h1:oEwPBh29BPu1MaTsz2dM9bDrkOgKBoYFC0u6uY2izWo=
github.com/junegunn/uniseg v0.0.0-20240120174029-b504da4f6ed2/go.mod h1:ywqF55XaSE3/uS2tkJqVFKiE0oIYAXRvU2N7DU4y3XQ=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
@@ -19,14 +21,11 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
@@ -36,12 +35,12 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@@ -50,6 +49,4 @@ golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -2,7 +2,7 @@
set -u set -u
version=0.45.0 version=0.46.0
auto_completion= auto_completion=
key_bindings= key_bindings=
update_config=2 update_config=2

View File

@@ -1,4 +1,4 @@
$version="0.45.0" $version="0.46.0"
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition $fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition

View File

@@ -5,7 +5,7 @@ import (
"github.com/junegunn/fzf/src/protector" "github.com/junegunn/fzf/src/protector"
) )
var version string = "0.45" var version string = "0.46"
var revision string = "devel" var revision string = "devel"
func main() { func main() {

View File

@@ -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 "Jan 2024" "fzf 0.45.0" "fzf-tmux - open fzf in tmux split pane" .TH fzf-tmux 1 "Jan 2024" "fzf 0.46.0" "fzf-tmux - open fzf in tmux split pane"
.SH NAME .SH NAME
fzf-tmux - open fzf in tmux split pane fzf-tmux - open fzf in tmux split pane

View File

@@ -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 "Jan 2024" "fzf 0.45.0" "fzf - a command-line fuzzy finder" .TH fzf 1 "Jan 2024" "fzf 0.46.0" "fzf - a command-line fuzzy finder"
.SH NAME .SH NAME
fzf - a command-line fuzzy finder fzf - a command-line fuzzy finder
@@ -260,9 +260,8 @@ Draw border around the finder
.br .br
If you use a terminal emulator where each box-drawing character takes If you use a terminal emulator where each box-drawing character takes
2 columns, try setting \fBRUNEWIDTH_EASTASIAN\fR environment variable to 2 columns, try setting \fB--ambidouble\fR. If the border is still not properly
\fB0\fR or \fB1\fR. If the border is still not properly rendered, set rendered, set \fB--no-unicode\fR.
\fB--no-unicode\fR.
.TP .TP
.BI "--border-label" [=LABEL] .BI "--border-label" [=LABEL]
@@ -313,6 +312,11 @@ the label. Label is printed on the top border line by default, add
Use ASCII characters instead of Unicode drawing characters to draw borders, Use ASCII characters instead of Unicode drawing characters to draw borders,
the spinner and the horizontal separator. the spinner and the horizontal separator.
.TP
.B "--ambidouble"
Set this option if your terminal displays ambiguous width characters (e.g.
box-drawing characters for borders) as 2 columns.
.TP .TP
.BI "--margin=" MARGIN .BI "--margin=" MARGIN
Comma-separated expression for margins around the finder. Comma-separated expression for margins around the finder.
@@ -592,8 +596,6 @@ Also,
* \fB{n}\fR is replaced to the zero-based ordinal index of the current item. * \fB{n}\fR is replaced to the zero-based ordinal index of the current item.
Use \fB{+n}\fR if you want all index numbers when multiple lines are selected. Use \fB{+n}\fR if you want all index numbers when multiple lines are selected.
.br .br
* \fB{fzf:action}\fR is replaced to to the name of the last action performed
* \fB{fzf:prompt}\fR is replaced to to the prompt string
Note that you can escape a placeholder pattern by prepending a backslash. Note that you can escape a placeholder pattern by prepending a backslash.
@@ -829,12 +831,14 @@ e.g.
\fB# Start HTTP server on port 6266 \fB# Start HTTP server on port 6266
fzf --listen 6266 fzf --listen 6266
# Get program state in JSON format (experimental)
curl localhost:6266
# Send action to the server # Send action to the server
curl -XPOST localhost:6266 -d 'reload(seq 100)+change-prompt(hundred> )' curl -XPOST localhost:6266 -d 'reload(seq 100)+change-prompt(hundred> )'
# Get program state in JSON format (experimental)
# * Make sure NOT to access this endpoint from execute/transform actions
# as it will result in a timeout
curl localhost:6266
# Start HTTP server on port 6266 with remote connections allowed # Start HTTP server on port 6266 with remote connections allowed
# * Listening on non-localhost address requires using an API key # * Listening on non-localhost address requires using an API key
export FZF_API_KEY="$(head -c 32 /dev/urandom | base64)" export FZF_API_KEY="$(head -c 32 /dev/urandom | base64)"
@@ -901,6 +905,39 @@ of field index expressions.
.BR .. " All the fields" .BR .. " All the fields"
.br .br
.SH ENVIRONMENT VARIABLES EXPORTED TO CHILD PROCESSES
fzf exports the following environment variables to its child processes.
.BR FZF_LINES " Number of lines fzf takes up excluding padding and margin"
.br
.BR FZF_COLUMNS " Number of columns fzf takes up excluding padding and margin"
.br
.BR FZF_TOTAL_COUNT " Total number of items"
.br
.BR FZF_MATCH_COUNT " Number of matched items"
.br
.BR FZF_SELECT_COUNT " Number of selected items"
.br
.BR FZF_QUERY " Current query string"
.br
.BR FZF_PROMPT " Prompt string"
.br
.BR FZF_ACTION " The name of the last action performed"
.br
.BR FZF_PORT " Port number when --listen option is used"
.br
The following variables are additionally exported to the preview commands.
.BR FZF_PREVIEW_TOP " Top position of the preview window"
.br
.BR FZF_PREVIEW_LEFT " Left position of the preview window"
.br
.BR FZF_PREVIEW_LINES " Number of lines in the preview window"
.br
.BR FZF_PREVIEW_COLUMNS " Number of columns in the preview window"
.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
@@ -1075,6 +1112,22 @@ e.g.
\fB# Change the prompt to "loaded" when the input stream is complete \fB# Change the prompt to "loaded" when the input stream is complete
(seq 10; sleep 1; seq 11 20) | fzf --prompt 'Loading> ' --bind 'load:change-prompt:Loaded> '\fR (seq 10; sleep 1; seq 11 20) | fzf --prompt 'Loading> ' --bind 'load:change-prompt:Loaded> '\fR
.RE .RE
\fIresize\fR
.RS
Triggered when the terminal size is changed.
e.g.
\fBfzf --bind 'resize:transform-header:echo Resized: ${FZF_COLUMNS}x${FZF_LINES}'\fR
.RE
\fIresult\fR
.RS
Triggered when the filtering for the current query is complete and the result list is ready.
e.g.
\fB# Put the cursor on the second item when the query string is empty
# * 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 {fzf:query} ]] && echo "pos(2)"'\fR
.RE
\fIchange\fR \fIchange\fR
.RS .RS
Triggered whenever the query string is changed Triggered whenever the query string is changed

View File

@@ -98,13 +98,15 @@ bindkey -M viins '\ec' fzf-cd-widget
fzf-history-widget() { fzf-history-widget() {
local selected num local selected num
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
selected=( $(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' | selected="$(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} ${FZF_DEFAULT_OPTS-} -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,ctrl-z:ignore ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) ) FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} ${FZF_DEFAULT_OPTS-} -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,ctrl-z:ignore ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m" $(__fzfcmd))"
local ret=$? local ret=$?
if [ -n "$selected" ]; then if [ -n "$selected" ]; then
num=$selected[1] num=$(awk '{print $1}' <<< "$selected")
if [ -n "$num" ]; then if [[ "$num" =~ '^[1-9][0-9]*\*?$' ]]; then
zle vi-fetch-history -n $num zle vi-fetch-history -n ${num%\*}
else # selected is a custom query, not from history
LBUFFER="$selected"
fi fi
fi fi
zle reset-prompt zle reset-prompt

View File

@@ -11,8 +11,8 @@ import (
"github.com/junegunn/fzf/src/algo" "github.com/junegunn/fzf/src/algo"
"github.com/junegunn/fzf/src/tui" "github.com/junegunn/fzf/src/tui"
"github.com/junegunn/fzf/src/util" "github.com/junegunn/fzf/src/util"
"github.com/junegunn/uniseg"
"github.com/mattn/go-runewidth"
"github.com/mattn/go-shellwords" "github.com/mattn/go-shellwords"
) )
@@ -337,6 +337,7 @@ type Options struct {
BorderLabel labelOpts BorderLabel labelOpts
PreviewLabel labelOpts PreviewLabel labelOpts
Unicode bool Unicode bool
Ambidouble bool
Tabstop int Tabstop int
ListenAddr *listenAddress ListenAddr *listenAddress
Unsafe bool Unsafe bool
@@ -406,6 +407,7 @@ func defaultOptions() *Options {
Margin: defaultMargin(), Margin: defaultMargin(),
Padding: defaultMargin(), Padding: defaultMargin(),
Unicode: true, Unicode: true,
Ambidouble: os.Getenv("RUNEWIDTH_EASTASIAN") == "1",
Tabstop: 8, Tabstop: 8,
BorderLabel: labelOpts{}, BorderLabel: labelOpts{},
PreviewLabel: labelOpts{}, PreviewLabel: labelOpts{},
@@ -650,6 +652,10 @@ func parseKeyChordsImpl(str string, message string, exit func(string)) map[tui.E
add(tui.Load) add(tui.Load)
case "focus": case "focus":
add(tui.Focus) add(tui.Focus)
case "result":
add(tui.Result)
case "resize":
add(tui.Resize)
case "one": case "one":
add(tui.One) add(tui.One)
case "zero": case "zero":
@@ -1591,8 +1597,6 @@ func parseOptions(opts *Options, allArgs []string) {
} }
} }
validateJumpLabels := false validateJumpLabels := false
validatePointer := false
validateMarker := false
for i := 0; i < len(allArgs); i++ { for i := 0; i < len(allArgs); i++ {
arg := allArgs[i] arg := allArgs[i]
switch arg { switch arg {
@@ -1772,10 +1776,8 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Prompt = nextString(allArgs, &i, "prompt string required") opts.Prompt = nextString(allArgs, &i, "prompt string required")
case "--pointer": case "--pointer":
opts.Pointer = firstLine(nextString(allArgs, &i, "pointer sign string required")) opts.Pointer = firstLine(nextString(allArgs, &i, "pointer sign string required"))
validatePointer = true
case "--marker": case "--marker":
opts.Marker = firstLine(nextString(allArgs, &i, "selected sign string required")) opts.Marker = firstLine(nextString(allArgs, &i, "selected sign string required"))
validateMarker = true
case "--sync": case "--sync":
opts.Sync = true opts.Sync = true
case "--no-sync": case "--no-sync":
@@ -1843,6 +1845,10 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Unicode = false opts.Unicode = false
case "--unicode": case "--unicode":
opts.Unicode = true opts.Unicode = true
case "--ambidouble":
opts.Ambidouble = true
case "--no-ambidouble":
opts.Ambidouble = false
case "--margin": case "--margin":
opts.Margin = parseMargin( opts.Margin = parseMargin(
"margin", "margin",
@@ -1901,10 +1907,8 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Prompt = value opts.Prompt = value
} else if match, value := optString(arg, "--pointer="); match { } else if match, value := optString(arg, "--pointer="); match {
opts.Pointer = firstLine(value) opts.Pointer = firstLine(value)
validatePointer = true
} else if match, value := optString(arg, "--marker="); match { } else if match, value := optString(arg, "--marker="); match {
opts.Marker = firstLine(value) opts.Marker = firstLine(value)
validateMarker = true
} else if match, value := optString(arg, "-n", "--nth="); match { } else if match, value := optString(arg, "-n", "--nth="); match {
opts.Nth = splitNth(value) opts.Nth = splitNth(value)
} else if match, value := optString(arg, "--with-nth="); match { } else if match, value := optString(arg, "--with-nth="); match {
@@ -2011,31 +2015,31 @@ func parseOptions(opts *Options, allArgs []string) {
} }
} }
} }
if validatePointer {
if err := validateSign(opts.Pointer, "pointer"); err != nil {
errorExit(err.Error())
}
}
if validateMarker {
if err := validateSign(opts.Marker, "marker"); err != nil {
errorExit(err.Error())
}
}
} }
func validateSign(sign string, signOptName string) error { func validateSign(sign string, signOptName string) error {
if sign == "" { if sign == "" {
return fmt.Errorf("%v cannot be empty", signOptName) return fmt.Errorf("%v cannot be empty", signOptName)
} }
if runewidth.StringWidth(sign) > 2 { if uniseg.StringWidth(sign) > 2 {
return fmt.Errorf("%v display width should be up to 2", signOptName) return fmt.Errorf("%v display width should be up to 2", signOptName)
} }
return nil return nil
} }
func postProcessOptions(opts *Options) { func postProcessOptions(opts *Options) {
if opts.Ambidouble {
uniseg.EastAsianAmbiguousWidth = 2
}
if err := validateSign(opts.Pointer, "pointer"); err != nil {
errorExit(err.Error())
}
if err := validateSign(opts.Marker, "marker"); err != nil {
errorExit(err.Error())
}
if !opts.Version && !tui.IsLightRendererSupported() && opts.Height.size > 0 { if !opts.Version && !tui.IsLightRendererSupported() && opts.Height.size > 0 {
errorExit("--height option is currently not supported on this platform") errorExit("--height option is currently not supported on this platform")
} }
@@ -2046,7 +2050,7 @@ func postProcessOptions(opts *Options) {
errorExit("--scrollbar should be given one or two characters") errorExit("--scrollbar should be given one or two characters")
} }
for _, r := range runes { for _, r := range runes {
if runewidth.RuneWidth(r) != 1 { if uniseg.StringWidth(string(r)) != 1 {
errorExit("scrollbar display width should be 1") errorExit("scrollbar display width should be 1")
} }
} }

View File

@@ -138,9 +138,11 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
for i := off[0]; i < off[1]; i++ { for i := off[0]; i < off[1]; i++ {
// Negative of 1-based index of itemColors // Negative of 1-based index of itemColors
// - The extra -1 means highlighted // - The extra -1 means highlighted
if cols[i] >= 0 {
cols[i] = cols[i]*-1 - 1 cols[i] = cols[i]*-1 - 1
} }
} }
}
// sort.Sort(ByOrder(offsets)) // sort.Sort(ByOrder(offsets))

View File

@@ -120,7 +120,7 @@ func TestColorOffset(t *testing.T) {
// ++++++++ ++++++++++ // ++++++++ ++++++++++
// --++++++++-- --++++++++++--- // --++++++++-- --++++++++++---
offsets := []Offset{{5, 15}, {25, 35}} offsets := []Offset{{5, 15}, {10, 12}, {25, 35}}
item := Result{ item := Result{
item: &Item{ item: &Item{
colors: &[]ansiOffset{ colors: &[]ansiOffset{

View File

@@ -30,7 +30,9 @@ const (
httpOk = "HTTP/1.1 200 OK" + crlf httpOk = "HTTP/1.1 200 OK" + crlf
httpBadRequest = "HTTP/1.1 400 Bad Request" + crlf httpBadRequest = "HTTP/1.1 400 Bad Request" + crlf
httpUnauthorized = "HTTP/1.1 401 Unauthorized" + crlf httpUnauthorized = "HTTP/1.1 401 Unauthorized" + crlf
httpUnavailable = "HTTP/1.1 503 Service Unavailable" + crlf
httpReadTimeout = 10 * time.Second httpReadTimeout = 10 * time.Second
jsonContentType = "Content-Type: application/json" + crlf
maxContentLength = 1024 * 1024 maxContentLength = 1024 * 1024
) )
@@ -141,7 +143,7 @@ func (server *httpServer) handleHttpRequest(conn net.Conn) string {
return answer(httpBadRequest, message) return answer(httpBadRequest, message)
} }
good := func(message string) string { good := func(message string) string {
return answer(httpOk+"Content-Type: application/json"+crlf, message) return answer(httpOk+jsonContentType, message)
} }
conn.SetReadDeadline(time.Now().Add(httpReadTimeout)) conn.SetReadDeadline(time.Now().Add(httpReadTimeout))
scanner := bufio.NewScanner(conn) scanner := bufio.NewScanner(conn)
@@ -165,8 +167,16 @@ func (server *httpServer) handleHttpRequest(conn net.Conn) string {
getMatch := getRegex.FindStringSubmatch(text) getMatch := getRegex.FindStringSubmatch(text)
if len(getMatch) > 0 { if len(getMatch) > 0 {
server.actionChannel <- []*action{{t: actResponse, a: getMatch[1]}} server.actionChannel <- []*action{{t: actResponse, a: getMatch[1]}}
response := <-server.responseChannel select {
case response := <-server.responseChannel:
return good(response) return good(response)
case <-time.After(2 * time.Second):
go func() {
// Drain the channel
<-server.responseChannel
}()
return answer(httpUnavailable+jsonContentType, `{"error":"timeout"}`)
}
} else if !strings.HasPrefix(text, "POST / HTTP") { } else if !strings.HasPrefix(text, "POST / HTTP") {
return bad("invalid request method") return bad("invalid request method")
} }

View File

@@ -17,8 +17,7 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/mattn/go-runewidth" "github.com/junegunn/uniseg"
"github.com/rivo/uniseg"
"github.com/junegunn/fzf/src/tui" "github.com/junegunn/fzf/src/tui"
"github.com/junegunn/fzf/src/util" "github.com/junegunn/fzf/src/util"
@@ -251,7 +250,10 @@ type Terminal struct {
borderWidth int borderWidth int
count int count int
progress int progress int
hasResultActions bool
hasFocusActions bool
hasLoadActions bool hasLoadActions bool
hasResizeActions bool
triggerLoad bool triggerLoad bool
reading bool reading bool
running bool running bool
@@ -289,6 +291,8 @@ type Terminal struct {
termSize tui.TermSize termSize tui.TermSize
lastAction actionType lastAction actionType
lastFocus int32 lastFocus int32
areaLines int
areaColumns int
} }
type selectedItem struct { type selectedItem struct {
@@ -449,6 +453,17 @@ const (
actHideHeader actHideHeader
) )
func (a actionType) Name() string {
name := ""
for i, r := range a.String()[3:] {
if i > 0 && r >= 'A' && r <= 'Z' {
name += "-"
}
name += string(r)
}
return strings.ToLower(name)
}
func processExecution(action actionType) bool { func processExecution(action actionType) bool {
switch action { switch action {
case actTransform, case actTransform,
@@ -519,7 +534,6 @@ func defaultKeymap() map[tui.Event][]*action {
} }
add(tui.Invalid, actInvalid) add(tui.Invalid, actInvalid)
add(tui.Resize, actClearScreen)
add(tui.CtrlA, actBeginningOfLine) add(tui.CtrlA, actBeginningOfLine)
add(tui.CtrlB, actBackwardChar) add(tui.CtrlB, actBackwardChar)
add(tui.CtrlC, actAbort) add(tui.CtrlC, actAbort)
@@ -730,6 +744,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
ellipsis: opts.Ellipsis, ellipsis: opts.Ellipsis,
ansi: opts.Ansi, ansi: opts.Ansi,
tabstop: opts.Tabstop, tabstop: opts.Tabstop,
hasResultActions: false,
hasFocusActions: false,
hasLoadActions: false, hasLoadActions: false,
triggerLoad: false, triggerLoad: false,
reading: true, reading: true,
@@ -757,7 +773,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
killChan: make(chan int), killChan: make(chan int),
serverInputChan: make(chan []*action, 10), serverInputChan: make(chan []*action, 10),
serverOutputChan: make(chan string), serverOutputChan: make(chan string),
eventChan: make(chan tui.Event, 3), // load / zero|one | GetChar eventChan: make(chan tui.Event, 6), // (load + result + zero|one) | (focus) | (resize) | (GetChar)
tui: renderer, tui: renderer,
initFunc: func() { renderer.Init() }, initFunc: func() { renderer.Init() },
executing: util.NewAtomicBool(false), executing: util.NewAtomicBool(false),
@@ -781,7 +797,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
t.separator, t.separatorLen = t.ansiLabelPrinter(bar, &tui.ColSeparator, true) t.separator, t.separatorLen = t.ansiLabelPrinter(bar, &tui.ColSeparator, true)
} }
if t.unicode { if t.unicode {
t.borderWidth = runewidth.RuneWidth('│') t.borderWidth = uniseg.StringWidth("│")
} }
if opts.Scrollbar == nil { if opts.Scrollbar == nil {
if t.unicode && t.borderWidth == 1 { if t.unicode && t.borderWidth == 1 {
@@ -801,6 +817,9 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
} }
} }
_, t.hasResizeActions = t.keymap[tui.Resize.AsEvent()]
_, t.hasResultActions = t.keymap[tui.Result.AsEvent()]
_, t.hasFocusActions = t.keymap[tui.Focus.AsEvent()]
_, t.hasLoadActions = t.keymap[tui.Load.AsEvent()] _, t.hasLoadActions = t.keymap[tui.Load.AsEvent()]
if t.listenAddr != nil { if t.listenAddr != nil {
@@ -819,6 +838,14 @@ func (t *Terminal) environ() []string {
if t.listenPort != nil { if t.listenPort != nil {
env = append(env, fmt.Sprintf("FZF_PORT=%d", *t.listenPort)) env = append(env, fmt.Sprintf("FZF_PORT=%d", *t.listenPort))
} }
env = append(env, "FZF_QUERY="+string(t.input))
env = append(env, "FZF_ACTION="+t.lastAction.Name())
env = append(env, "FZF_PROMPT="+string(t.promptString))
env = append(env, fmt.Sprintf("FZF_TOTAL_COUNT=%d", t.count))
env = append(env, fmt.Sprintf("FZF_MATCH_COUNT=%d", t.merger.Length()))
env = append(env, fmt.Sprintf("FZF_SELECT_COUNT=%d", len(t.selected)))
env = append(env, fmt.Sprintf("FZF_LINES=%d", t.areaLines))
env = append(env, fmt.Sprintf("FZF_COLUMNS=%d", t.areaColumns))
return env return env
} }
@@ -1073,6 +1100,9 @@ func (t *Terminal) UpdateList(merger *Merger) {
t.eventChan <- one t.eventChan <- one
} }
} }
if t.hasResultActions {
t.eventChan <- tui.Result.AsEvent()
}
} }
t.mutex.Unlock() t.mutex.Unlock()
t.reqBox.Set(reqInfo, nil) t.reqBox.Set(reqInfo, nil)
@@ -1282,6 +1312,9 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
width -= paddingInt[1] + paddingInt[3] width -= paddingInt[1] + paddingInt[3]
height -= paddingInt[0] + paddingInt[2] height -= paddingInt[0] + paddingInt[2]
t.areaLines = height
t.areaColumns = width
// Set up preview window // Set up preview window
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode) noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
if forcePreview || t.needPreviewWindow() { if forcePreview || t.needPreviewWindow() {
@@ -2534,14 +2567,7 @@ func replacePlaceholder(params replacePlaceholderParams) string {
} }
} }
case match == "{fzf:action}": case match == "{fzf:action}":
name := "" return params.lastAction.Name()
for i, r := range params.lastAction.String()[3:] {
if i > 0 && r >= 'A' && r <= 'Z' {
name += "-"
}
name += string(r)
}
return strings.ToLower(name)
case match == "{fzf:prompt}": case match == "{fzf:prompt}":
return quoteEntry(params.prompt) return quoteEntry(params.prompt)
default: default:
@@ -3070,9 +3096,9 @@ func (t *Terminal) Loop() {
t.track = trackDisabled t.track = trackDisabled
t.printInfo() t.printInfo()
} }
if onFocus, prs := t.keymap[tui.Focus.AsEvent()]; prs && focusChanged && currentIndex != t.lastFocus { if t.hasFocusActions && focusChanged && currentIndex != t.lastFocus {
t.lastFocus = focusedIndex t.lastFocus = currentIndex
t.serverInputChan <- onFocus t.eventChan <- tui.Focus.AsEvent()
} }
if focusChanged || version != t.version { if focusChanged || version != t.version {
version = t.version version = t.version
@@ -3104,6 +3130,9 @@ func (t *Terminal) Loop() {
if wasHidden && t.hasPreviewWindow() { if wasHidden && t.hasPreviewWindow() {
refreshPreview(t.previewOpts.command) refreshPreview(t.previewOpts.command)
} }
if req == reqResize && t.hasResizeActions {
t.eventChan <- tui.Resize.AsEvent()
}
case reqClose: case reqClose:
exit(func() int { exit(func() int {
if t.output() { if t.output() {
@@ -3186,7 +3215,7 @@ func (t *Terminal) Loop() {
} }
select { select {
case event = <-t.eventChan: case event = <-t.eventChan:
needBarrier = !event.Is(tui.Load, tui.One, tui.Zero) needBarrier = !event.Is(tui.Load, tui.Result, tui.Focus, tui.One, tui.Zero, tui.Resize)
case serverActions := <-t.serverInputChan: case serverActions := <-t.serverInputChan:
event = tui.Invalid.AsEvent() event = tui.Invalid.AsEvent()
if t.listenAddr == nil || t.listenAddr.IsLocal() || t.listenUnsafe { if t.listenAddr == nil || t.listenAddr.IsLocal() || t.listenUnsafe {

View File

@@ -10,8 +10,7 @@ import (
"time" "time"
"unicode/utf8" "unicode/utf8"
"github.com/mattn/go-runewidth" "github.com/junegunn/uniseg"
"github.com/rivo/uniseg"
"golang.org/x/term" "golang.org/x/term"
) )
@@ -804,7 +803,7 @@ func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
if w.preview { if w.preview {
color = ColPreviewBorder color = ColPreviewBorder
} }
hw := runewidth.RuneWidth(w.border.top) hw := runeWidth(w.border.top)
if top { if top {
w.Move(0, 0) w.Move(0, 0)
w.CPrint(color, repeat(w.border.top, w.width/hw)) w.CPrint(color, repeat(w.border.top, w.width/hw))
@@ -842,13 +841,13 @@ func (w *LightWindow) drawBorderAround(onlyHorizontal bool) {
if w.preview { if w.preview {
color = ColPreviewBorder color = ColPreviewBorder
} }
hw := runewidth.RuneWidth(w.border.top) hw := runeWidth(w.border.top)
tcw := runewidth.RuneWidth(w.border.topLeft) + runewidth.RuneWidth(w.border.topRight) tcw := runeWidth(w.border.topLeft) + runeWidth(w.border.topRight)
bcw := runewidth.RuneWidth(w.border.bottomLeft) + runewidth.RuneWidth(w.border.bottomRight) bcw := runeWidth(w.border.bottomLeft) + runeWidth(w.border.bottomRight)
rem := (w.width - tcw) % hw rem := (w.width - tcw) % hw
w.CPrint(color, string(w.border.topLeft)+repeat(w.border.top, (w.width-tcw)/hw)+repeat(' ', rem)+string(w.border.topRight)) w.CPrint(color, string(w.border.topLeft)+repeat(w.border.top, (w.width-tcw)/hw)+repeat(' ', rem)+string(w.border.topRight))
if !onlyHorizontal { if !onlyHorizontal {
vw := runewidth.RuneWidth(w.border.left) vw := runeWidth(w.border.left)
for y := 1; y < w.height-1; y++ { for y := 1; y < w.height-1; y++ {
w.Move(y, 0) w.Move(y, 0)
w.CPrint(color, string(w.border.left)) w.CPrint(color, string(w.border.left))
@@ -1020,7 +1019,7 @@ func wrapLine(input string, prefixLength int, max int, tabstop int) []wrappedLin
} else if rs[0] == '\r' { } else if rs[0] == '\r' {
w++ w++
} else { } else {
w = runewidth.StringWidth(str) w = uniseg.StringWidth(str)
} }
width += w width += w

View File

@@ -10,8 +10,7 @@ import (
"github.com/gdamore/tcell/v2/encoding" "github.com/gdamore/tcell/v2/encoding"
"github.com/junegunn/fzf/src/util" "github.com/junegunn/fzf/src/util"
"github.com/mattn/go-runewidth" "github.com/junegunn/uniseg"
"github.com/rivo/uniseg"
) )
func HasFullscreenRenderer() bool { func HasFullscreenRenderer() bool {
@@ -738,7 +737,7 @@ func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
style = w.normal.style() style = w.normal.style()
} }
hw := runewidth.RuneWidth(w.borderStyle.top) hw := runeWidth(w.borderStyle.top)
switch shape { switch shape {
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderHorizontal, BorderTop: case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderHorizontal, BorderTop:
max := right - 2*hw max := right - 2*hw
@@ -773,7 +772,7 @@ func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
} }
switch shape { switch shape {
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderVertical, BorderRight: case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderVertical, BorderRight:
vw := runewidth.RuneWidth(w.borderStyle.right) vw := runeWidth(w.borderStyle.right)
for y := top; y < bot; y++ { for y := top; y < bot; y++ {
_screen.SetContent(right-vw, y, w.borderStyle.right, nil, style) _screen.SetContent(right-vw, y, w.borderStyle.right, nil, style)
} }
@@ -782,8 +781,8 @@ func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
switch shape { switch shape {
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble: case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble:
_screen.SetContent(left, top, w.borderStyle.topLeft, nil, style) _screen.SetContent(left, top, w.borderStyle.topLeft, nil, style)
_screen.SetContent(right-runewidth.RuneWidth(w.borderStyle.topRight), top, w.borderStyle.topRight, nil, style) _screen.SetContent(right-runeWidth(w.borderStyle.topRight), top, w.borderStyle.topRight, nil, style)
_screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style) _screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style)
_screen.SetContent(right-runewidth.RuneWidth(w.borderStyle.bottomRight), bot-1, w.borderStyle.bottomRight, nil, style) _screen.SetContent(right-runeWidth(w.borderStyle.bottomRight), bot-1, w.borderStyle.bottomRight, nil, style)
} }
} }

View File

@@ -5,6 +5,8 @@ import (
"os" "os"
"strconv" "strconv"
"time" "time"
"github.com/junegunn/uniseg"
) )
// Types of user action // Types of user action
@@ -105,6 +107,7 @@ const (
Focus Focus
One One
Zero Zero
Result
AltBS AltBS
@@ -811,3 +814,7 @@ func initPalette(theme *ColorTheme) {
ColPreviewScrollbar = pair(theme.PreviewScrollbar, theme.PreviewBg) ColPreviewScrollbar = pair(theme.PreviewScrollbar, theme.PreviewBg)
ColPreviewSpinner = pair(theme.Spinner, theme.PreviewBg) ColPreviewSpinner = pair(theme.Spinner, theme.PreviewBg)
} }
func runeWidth(r rune) int {
return uniseg.StringWidth(string(r))
}

View File

@@ -6,14 +6,13 @@ import (
"strings" "strings"
"time" "time"
"github.com/junegunn/uniseg"
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
"github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
) )
// StringWidth returns string width where each CR/LF character takes 1 column // StringWidth returns string width where each CR/LF character takes 1 column
func StringWidth(s string) int { func StringWidth(s string) int {
return runewidth.StringWidth(s) + strings.Count(s, "\n") + strings.Count(s, "\r") return uniseg.StringWidth(s) + strings.Count(s, "\n") + strings.Count(s, "\r")
} }
// RunesWidth returns runes width // RunesWidth returns runes width
@@ -165,7 +164,7 @@ func RepeatToFill(str string, length int, limit int) string {
output := strings.Repeat(str, times) output := strings.Repeat(str, times)
if rest > 0 { if rest > 0 {
for _, r := range str { for _, r := range str {
rest -= runewidth.RuneWidth(r) rest -= uniseg.StringWidth(string(r))
if rest < 0 { if rest < 0 {
break break
} }

View File

@@ -164,6 +164,18 @@ func TestRunesWidth(t *testing.T) {
t.Errorf("Expected overflow index: %d, actual: %d", args[2], overflowIdx) t.Errorf("Expected overflow index: %d, actual: %d", args[2], overflowIdx)
} }
} }
for _, input := range []struct {
s string
w int
}{
{"▶", 1},
{"▶️", 2},
} {
width, _ := RunesWidth([]rune(input.s), 0, 0, 100)
if width != input.w {
t.Errorf("Expected width of %s: %d, actual: %d", input.s, input.w, width)
}
}
} }
func TestTruncate(t *testing.T) { func TestTruncate(t *testing.T) {

View File

@@ -2711,12 +2711,26 @@ class TestGoFZF < TestBase
tmux.until { |lines| assert_includes(lines[-1], '[[2]]') } tmux.until { |lines| assert_includes(lines[-1], '[[2]]') }
tmux.send_keys :X tmux.send_keys :X
tmux.until { |lines| assert_includes(lines[-1], '[[]]') } tmux.until { |lines| assert_includes(lines[-1], '[[]]') }
tmux.send_keys :BSpace
tmux.until { |lines| assert_includes(lines[-1], '[[1]]') }
tmux.send_keys :X
tmux.until { |lines| assert_includes(lines[-1], '[[]]') }
tmux.send_keys '?' tmux.send_keys '?'
tmux.send_keys :BSpace tmux.send_keys :BSpace
tmux.until { |lines| assert_equal 100, lines.match_count } tmux.until { |lines| assert_equal 100, lines.match_count }
tmux.until { |lines| refute_includes(lines[-1], '[[1]]') } tmux.until { |lines| refute_includes(lines[-1], '[[1]]') }
end end
def test_result_event
tmux.send_keys '(echo 0; seq 10) | fzf --bind "result:pos(2)"', :Enter
tmux.until { |lines| assert_equal 11, lines.item_count }
tmux.until { |lines| assert_includes lines, '> 1' }
tmux.send_keys '9'
tmux.until { |lines| assert_includes lines, '> 9' }
tmux.send_keys :BSpace
tmux.until { |lines| assert_includes lines, '> 1' }
end
def test_labels_center def test_labels_center
tmux.send_keys 'echo x | fzf --border --border-label foobar --preview : --preview-label barfoo --bind "space:change-border-label(foobarfoo)+change-preview-label(barfoobar),enter:transform-border-label(echo foo{}foo)+transform-preview-label(echo bar{}bar)"', :Enter tmux.send_keys 'echo x | fzf --border --border-label foobar --preview : --preview-label barfoo --bind "space:change-border-label(foobarfoo)+change-preview-label(barfoobar),enter:transform-border-label(echo foo{}foo)+transform-preview-label(echo bar{}bar)"', :Enter
tmux.until do tmux.until do