m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-18 08:13:40 -05:00

Compare commits

..

20 Commits

Author SHA1 Message Date
Junegunn Choi
04a2dfacf0 Add 'best' to man page 2025-10-08 14:29:20 +09:00
Junegunn Choi
f4937c1df9 Fix offset-up and offset-down with --layout=reverse-list
Related: 3df06a1c68
2025-10-08 11:14:40 +09:00
Junegunn Choi
7288daff07 Add 'best' action 2025-10-08 11:09:42 +09:00
Junegunn Choi
edd08d5cc6 Go to the closest match when disabling raw mode 2025-10-08 01:45:00 +09:00
Junegunn Choi
ee9c13eeeb ADD $FZF_DIRECTION 2025-10-04 22:50:33 +09:00
Junegunn Choi
5dea11ddbb CTRL-R: Bind ALT-R to toggle-raw 2025-10-03 18:08:46 +09:00
Junegunn Choi
e393fda2fb Rename: '--color hidden' to '--color nomatch' 2025-10-03 18:08:45 +09:00
Junegunn Choi
e7e7bc3315 Fix non-matching items not refreshing after clearing query 2025-10-02 23:58:32 +09:00
Junegunn Choi
93b446a848 Fix: 'hidden' style not applied to text without colors 2025-10-02 19:20:50 +09:00
Junegunn Choi
d3485a06a1 Add special 'strip' style attribute for stripping colors
Test cases:
  fd --color always | fzf --ansi --delimiter /
  fd --color always | fzf --ansi --delimiter / --nth -1 --color fg:dim,nth:regular
  fd --color always | fzf --ansi --delimiter / --nth -1 --color fg:dim:strip,nth:regular
  fd --color always | fzf --ansi --delimiter / --nth -1 --color fg:dim:strip,nth:regular --raw
  fd --color always | fzf --ansi --delimiter / --nth -1 --color fg:dim:strip,nth:regular,hidden:strikethrough --raw
  fd --color always | fzf --ansi --delimiter / --nth -1 --color fg:dim:strip,nth:regular,hidden:strip:strikethrough --raw
  fd --color always | fzf --ansi --delimiter / --nth -1 --color fg:dim:strip,nth:regular,hidden:strip:dim:strikethrough --raw
2025-10-01 23:52:24 +09:00
Junegunn Choi
e21f1c1b69 Revise color configuration 2025-10-01 08:52:06 +09:00
Junegunn Choi
ba113ebe6a Do not allow gutter characters with width other than 1 2025-09-30 17:44:29 +09:00
Junegunn Choi
9ca2f09d5b --gutter ' ' --color gutter:reverse 2025-09-29 22:55:43 +09:00
Junegunn Choi
abb1d3af7b Add $FZF_RAW for conditional actions 2025-09-29 22:36:44 +09:00
Junegunn Choi
436e43ad0e Update CHANGELOG 2025-09-29 21:53:52 +09:00
Junegunn Choi
2f03c5d10f Add enable-raw and disable-raw actions 2025-09-29 18:42:50 +09:00
Junegunn Choi
ba945f1bf9 Refactor action implementation 2025-09-29 18:30:01 +09:00
Junegunn Choi
44572af6bb Remove TODO comments 2025-09-29 18:30:01 +09:00
Junegunn Choi
c481058f90 Add '--bind ctrl-x:toggle-raw' to CTRL-R bindings 2025-09-29 18:30:01 +09:00
Junegunn Choi
f8521fa90e Introduce 'raw' mode 2025-09-29 18:30:00 +09:00
35 changed files with 195 additions and 649 deletions

View File

@@ -33,12 +33,12 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v4
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
uses: github/codeql-action/analyze@v3

View File

@@ -23,7 +23,7 @@ jobs:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v6
uses: actions/setup-go@v5
with:
go-version: "1.23"

View File

@@ -20,7 +20,7 @@ jobs:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v6
uses: actions/setup-go@v5
with:
go-version: "1.23"

View File

@@ -22,7 +22,6 @@ builds:
- loong64
- ppc64le
- s390x
- riscv64
goarm:
- "5"
- "6"
@@ -40,8 +39,6 @@ builds:
goarch: arm64
- goos: openbsd
goarch: arm64
- goos: openbsd
goarch: riscv64
- goos: android
goarch: amd64
- goos: android

View File

@@ -1,76 +1,17 @@
CHANGELOG
=========
0.67.0
------
- Added `--freeze-left=N` option to keep the leftmost N columns always visible.
```sh
# Keep the file name column fixed and always visible
git grep --line-number --color=always -- '' |
fzf --ansi --delimiter : --freeze-left 1
# Can be used with --keep-right
git grep --line-number --color=always -- '' |
fzf --ansi --delimiter : --freeze-left 1 --keep-right
```
- Also added `--freeze-right=N` option to keep the rightmost N columns always visible.
```sh
# Stronger version of --keep-right that always keeps the right-end visible
fd | fzf --freeze-right 1
# Keep the base name always visible
fd | fzf --freeze-right 1 --delimiter /
# Keep both leftmost and rightmost components visible
fd | fzf --freeze-left 1 --freeze-right 1 --delimiter /
```
- Updated `--info=inline` to print the spinner (load indicator).
- Bug fixes
0.66.1
------
- Bug fixes
- Fixed a bug preventing 'ctrl-h' from being bound to an action (#4556)
- Fixed `--no-color` / `NO_COLOR` theme (#4561)
0.66.0
------
### Quick summary
This version introduces many new features centered around the new "raw" mode.
| Type | Class | Name | Description |
| :-- | :-- | :-- | :-- |
| New | Option | `--raw` | Enable raw mode by default |
| New | Option | `--gutter CHAR` | Set the gutter column character |
| New | Option | `--gutter-raw CHAR` | Set the gutter column character in raw mode |
| Enhancement | Option | `--listen SOCKET` | Added support for Unix domain sockets |
| New | Action | `toggle-raw` | Toggle raw mode |
| New | Action | `enable-raw` | Enable raw mode |
| New | Action | `disable-raw` | Disable raw mode |
| New | Action | `up-match` | Move up to the matching item |
| New | Action | `down-match` | Move down to the matching item |
| New | Action | `best` | Move to the matching item with the best score |
| New | Color | `nomatch` | Color for non-matching items in raw mode |
| New | Env Var | `FZF_RAW` | Matching status in raw mode (0, 1, or undefined) |
| New | Env Var | `FZF_DIRECTION` | `up` or `down` depending on the layout |
| New | Env Var | `FZF_SOCK` | Path to the Unix domain socket fzf is listening on |
| Enhancement | Key | `CTRL-N` | `down` -> `down-match` |
| Enhancement | Key | `CTRL-P` | `up` -> `up-match` |
| Enhancement | Shell | `CTRL-R` binding | Toggle raw mode with `ALT-R` |
| Enhancement | Shell | `CTRL-R` binding | Opt-out with an empty `FZF_CTRL_R_COMMAND` |
### 1. Introducing "raw" mode
![](https://github.com/user-attachments/assets/9640ae11-b5f7-43fb-95f1-c29307fc17c2)
### Introducing "raw" mode
This version introduces a new "raw" mode (named so because it shows the list
"unfiltered"). In raw mode, non-matching items stay in their original positions,
but appear dimmed. This allows you to see the surrounding items of a match and
better understand the context of it. You can enable raw mode by default with
`--raw`, but it's often more useful when toggled dynamically with the
`toggle-raw` action.
but appear dimmed. This allows you see surrounding items of a match and better
understand the context of it. You can enable raw mode by default with `--raw`,
but it's often more useful when toggled dynamically with the `toggle-raw`
action.
```sh
tree | fzf --reverse --bind alt-r:toggle-raw
@@ -175,11 +116,7 @@ fzf --raw --bind 'enter:transform:[[ ${FZF_RAW-1} = 1 ]] && echo accept || echo
The `CTRL-R` binding (command history) now lets you toggle raw mode with `ALT-R`.
### 2. Style changes
The screenshot on the right shows the updated gutter style:
![](https://github.com/user-attachments/assets/8ea7b5ef-c99e-4686-905b-22eb078b700a)
### Style changes
This version includes a few minor updates to fzf's classic visual style:
@@ -187,25 +124,7 @@ This version includes a few minor updates to fzf's classic visual style:
- Markers no longer use background colors.
- The `--color base16` theme (alias: `16`) has been updated for better compatibility with both dark and light themes.
### 3. `--listen` now supports Unix domain sockets
If an argument to `--listen` ends with `.sock`, fzf will listen on a Unix
domain socket at the specified path.
```sh
fzf --listen /tmp/fzf.sock --no-tmux
# GET
curl --unix-socket /tmp/fzf.sock http
# POST
curl --unix-socket /tmp/fzf.sock http -d up
```
Note that any existing file at the given path will be removed before creating
the socket, so avoid using an important file path.
### 4. Added options
### Added options
#### `--gutter CHAR`
@@ -230,24 +149,20 @@ fzf --gutter ' ' --color gutter:reverse
As noted above, the `--gutter-raw CHAR` option was also added for customizing the gutter column in raw mode.
### 5. Added actions
### Added actions
The following actions were introduced to support working with raw mode:
| Action | Description |
| --- | --- |
| `up-match` | Move up to the matching item; identical to `up` if raw mode is disabled |
| `down-match` | Move down to the matching item; identical to `down` if raw mode is disabled |
| `toggle-raw` | Toggle raw mode |
| `enable-raw` | Enable raw mode |
| `disable-raw` | Disable raw mode |
| `best` | Move to the first matching item with the best score; identical to `first` if raw mode is disabled |
| Action | Description |
| :-- | :-- |
| `toggle-raw` | Toggle raw mode |
| `enable-raw` | Enable raw mode |
| `disable-raw` | Disable raw mode |
| `up-match` | Move up to the matching item; identical to `up` if raw mode is disabled |
| `down-match` | Move down to the matching item; identical to `down` if raw mode is disabled |
| `best` | Move to the matching item with the best score; identical to `first` if raw mode is disabled |
### Added environment variable
### 6. Added environment variables
#### `$FZF_DIRECTION`
`$FZF_DIRECTION` is now exported to child processes, indicating the list direction of the current layout:
`$FZF_DIRECTION` is now exported to child processes, indicating the list direction of the current layout.
- `up` for the default layout
- `down` for `reverse` or `reverse-list`
@@ -259,34 +174,7 @@ like `{up,down}-match`, `{up,down}-selected`, and `toggle+{up,down}`.
fzf --raw --bind 'result:first+transform:[[ $FZF_RAW = 0 ]] && echo $FZF_DIRECTION-match'
```
#### `$FZF_SOCK`
When fzf is listening on a Unix domain socket using `--listen`, the path to the
socket is exported as `$FZF_SOCK`, analogous to `$FZF_PORT` for TCP sockets.
#### `$FZF_RAW`
As described above, `$FZF_RAW` is now exported to child processes in raw mode,
indicating whether the current item is a match (`1`) or not (`0`). It is not
defined when not in raw mode.
#### `$FZF_CTRL_R_COMMAND`
You can opt-out `CTRL-R` binding from the shell integration by setting
`FZF_CTRL_R_COMMAND` to an empty string. Setting it to any other value is not
supported and will result in a warning.
```sh
# Disable the CTRL-R binding from the shell integration
FZF_CTRL_R_COMMAND= eval "$(fzf --bash)"
```
### 7. Added key support for `--bind`
Pull request [#3996](https://github.com/junegunn/fzf/pull/3996) added support
for many additional keys for `--bind` option, such as `ctrl-backspace`.
### 8. Breaking changes
### Breaking changes
#### Hiding the gutter column

File diff suppressed because one or more lines are too long

View File

@@ -2,7 +2,7 @@
set -u
version=0.67.0
version=0.65.2
auto_completion=
key_bindings=
update_config=2
@@ -177,7 +177,6 @@ case "$archi" in
Linux\ aarch64\ Android) download fzf-$version-android_arm64.tar.gz ;;
Linux\ aarch64*) download fzf-$version-linux_arm64.tar.gz ;;
Linux\ loongarch64*) download fzf-$version-linux_loong64.tar.gz ;;
Linux\ riscv64*) download fzf-$version-linux_riscv64.tar.gz ;;
Linux\ ppc64le*) download fzf-$version-linux_ppc64le.tar.gz ;;
Linux\ *64*) download fzf-$version-linux_amd64.tar.gz ;;
Linux\ s390x*) download fzf-$version-linux_s390x.tar.gz ;;

View File

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

View File

@@ -11,7 +11,7 @@ import (
"github.com/junegunn/fzf/src/protector"
)
var version = "0.67"
var version = "0.65"
var revision = "devel"
//go:embed shell/key-bindings.bash

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
THE SOFTWARE.
..
.TH fzf\-tmux 1 "Nov 2025" "fzf 0.67.0" "fzf\-tmux - open fzf in tmux split pane"
.TH fzf\-tmux 1 "Aug 2025" "fzf 0.65.2" "fzf\-tmux - open fzf in tmux split pane"
.SH NAME
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
THE SOFTWARE.
..
.TH fzf 1 "Nov 2025" "fzf 0.67.0" "fzf - a command-line fuzzy finder"
.TH fzf 1 "Sep 2025" "fzf 0.66.0" "fzf - a command-line fuzzy finder"
.SH NAME
fzf - a command-line fuzzy finder
@@ -629,16 +629,9 @@ Render empty lines between each item
The given string will be repeated to draw a horizontal line on each gap
(default: '┈' or '\-' depending on \fB\-\-no\-unicode\fR).
.TP
.BI "\-\-freeze\-left=" "N"
Number of fields to freeze on the left.
.TP
.BI "\-\-freeze\-right=" "N"
Number of fields to freeze on the right.
.TP
.B "\-\-keep\-right"
Keep the right end of the line visible when it's too long. Effective only when
the query string is empty. Use \fB\-\-freeze\-right=1\fR instead if you want
the last field to be always visible even with a non-empty query.
the query string is empty.
.TP
.BI "\-\-scroll\-off=" "LINES"
Number of screen lines to keep above or below when scrolling to the top or to
@@ -658,9 +651,6 @@ Label characters for \fBjump\fR mode.
.BI "\-\-gutter=" "CHAR"
Character used for the gutter column (default: '▌' unless \fB\-\-no\-unicode\fR is given)
.TP
.BI "\-\-gutter\-raw=" "CHAR"
Character used for the gutter column in raw mode (default: '▖' unless \fB\-\-no\-unicode\fR is given)
.TP
.BI "\-\-pointer=" "STR"
Pointer to the current line (default: '▌' or '>' depending on \fB\-\-no\-unicode\fR)
.TP
@@ -1143,25 +1133,19 @@ On Windows, the default value is \fBcmd /s/c\fR when \fB$SHELL\fR is not
set.
.TP
.B "\-\-listen[=SOCKET_PATH|[ADDR:]PORT]" "\-\-listen\-unsafe[=[ADDR:]PORT]"
Start HTTP server and listen on the given address or Unix socket. It allows
external processes to send actions to perform via POST method and query the
program state via GET method. For the argument to be recognized as a socket
path, it must have \fB.sock\fR extension.
.B "\-\-listen[=[ADDR:]PORT]" "\-\-listen\-unsafe[=[ADDR:]PORT]"
Start HTTP server and listen on the given address. It allows external processes
to send actions to perform via POST method.
- If the port number is omitted or given as 0, fzf will automatically choose
a port and export it as \fBFZF_PORT\fR environment variable to the child processes.
- If a Unix socket path is given, fzf will create a Unix domain socket at the
given path. The existing file will be removed. The path to the socket file
is exported as \fBFZF_SOCK\fR environment variable.
a port and export it as \fBFZF_PORT\fR environment variable to the child processes
- If \fBFZF_API_KEY\fR environment variable is set, the server would require
sending an API key with the same value in the \fBx\-api\-key\fR HTTP header.
sending an API key with the same value in the \fBx\-api\-key\fR HTTP header
- \fBFZF_API_KEY\fR is required for a non-localhost listen address.
- \fBFZF_API_KEY\fR is required for a non-localhost listen address
- To allow remote process execution, use \fB\-\-listen\-unsafe\fR.
- To allow remote process execution, use \fB\-\-listen\-unsafe\fR
e.g.
\fB# Start HTTP server on port 6266
@@ -1200,18 +1184,6 @@ e.g.
'
\fR
Here is an example script that uses a Unix socket instead of a TCP port.
\fB
fzf --listen=/tmp/fzf.sock
# GET
curl --unix-socket /tmp/fzf.sock http
# POST
curl --unix-socket /tmp/fzf.sock http -d up
\fR
.SS DIRECTORY TRAVERSAL
.TP
.B "\-\-walker=[file][,dir][,follow][,hidden]"
@@ -1401,8 +1373,6 @@ fzf exports the following environment variables to its child processes.
.br
.BR FZF_PORT " Port number when \-\-listen option is used"
.br
.BR FZF_SOCK " Unix socket path when \-\-listen option is used"
.br
.BR FZF_PREVIEW_TOP " Top position of the preview window"
.br
.BR FZF_PREVIEW_LEFT " Left position of the preview window"
@@ -1495,7 +1465,7 @@ e.g.
.br
\fIctrl\-/\fR (\fIctrl\-_\fR)
.br
\fIctrl\-alt\-[a\-z]\fR (\fIctrl\-alt\-h\fR is \fIctrl\-alt\-backspace\fR on non-Windows)
\fIctrl\-alt\-[a\-z]\fR
.br
\fIalt\-[*]\fR (Any case-sensitive single character is allowed)
.br
@@ -1625,7 +1595,7 @@ e.g.
.br
\fIctrl\-alt\-end\fR
.br
\fIctrl\-alt\-backspace\fR (\fIctrl\-alt\-bspace\fR \fIctrl\-alt\-bs\fR) (\fIctrl\-alt\-h\fR (non-Windows))
\fIctrl\-alt\-backspace\fR (\fIctrl\-alt\-bspace\fR \fIctrl\-alt\-bs\fR)
.br
\fIctrl\-alt\-delete\fR
.br

View File

@@ -26,10 +26,7 @@ __fzf_exec_awk() {
# version >= 1.3.4
local n x y z d
IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)
[[ $n == mawk ]] &&
(((x * 1000 + y) * 1000 + z >= 1003004)) 2> /dev/null &&
((d >= 20230302)) 2> /dev/null &&
__fzf_awk=mawk
[[ $n == mawk ]] && ((d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004)) && __fzf_awk=mawk
fi
fi
# Note: macOS awk has a quirk that it stops processing at all when it sees

View File

@@ -51,10 +51,7 @@ __fzf_exec_awk() {
elif command -v mawk > /dev/null 2>&1; then
local n x y z d
IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)
[[ $n == mawk ]] &&
(((x * 1000 + y) * 1000 + z >= 1003004)) 2> /dev/null &&
((d >= 20230302)) 2> /dev/null &&
__fzf_awk=mawk
[[ $n == mawk ]] && ((d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004)) && __fzf_awk=mawk
fi
fi
LC_ALL=C exec "$__fzf_awk" "$@"

View File

@@ -115,10 +115,7 @@ __fzf_exec_awk() {
elif command -v mawk > /dev/null 2>&1; then
local n x y z d
IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)
[[ $n == mawk ]] &&
(((x * 1000 + y) * 1000 + z >= 1003004)) 2> /dev/null &&
((d >= 20230302)) 2> /dev/null &&
__fzf_awk=mawk
[[ $n == mawk ]] && ((d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004)) && __fzf_awk=mawk
fi
fi
LC_ALL=C exec "$__fzf_awk" "$@"

View File

@@ -7,7 +7,6 @@
# - $FZF_TMUX_OPTS
# - $FZF_CTRL_T_COMMAND
# - $FZF_CTRL_T_OPTS
# - $FZF_CTRL_R_COMMAND
# - $FZF_CTRL_R_OPTS
# - $FZF_ALT_C_COMMAND
# - $FZF_ALT_C_OPTS
@@ -38,10 +37,7 @@ __fzf_exec_awk() {
elif command -v mawk > /dev/null 2>&1; then
local n x y z d
IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)
[[ $n == mawk ]] &&
(((x * 1000 + y) * 1000 + z >= 1003004)) 2> /dev/null &&
((d >= 20230302)) 2> /dev/null &&
__fzf_awk=mawk
[[ $n == mawk ]] && ((d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004)) && __fzf_awk=mawk
fi
fi
LC_ALL=C exec "$__fzf_awk" "$@"
@@ -136,14 +132,9 @@ if ((BASH_VERSINFO[0] < 4)); then
fi
# CTRL-R - Paste the selected command from history into the command line
if [[ ${FZF_CTRL_R_COMMAND-x} != "" ]]; then
if [[ -n ${FZF_CTRL_R_COMMAND-} ]]; then
echo "warning: FZF_CTRL_R_COMMAND is set to a custom command, but custom commands are not yet supported for CTRL-R" >&2
fi
bind -m emacs-standard '"\C-r": "\C-e \C-u\C-y\ey\C-u`__fzf_history__`\e\C-e\er"'
bind -m vi-command '"\C-r": "\C-z\C-r\C-z"'
bind -m vi-insert '"\C-r": "\C-z\C-r\C-z"'
fi
bind -m emacs-standard '"\C-r": "\C-e \C-u\C-y\ey\C-u`__fzf_history__`\e\C-e\er"'
bind -m vi-command '"\C-r": "\C-z\C-r\C-z"'
bind -m vi-insert '"\C-r": "\C-z\C-r\C-z"'
else
# CTRL-T - Paste the selected file path into the command line
if [[ ${FZF_CTRL_T_COMMAND-x} != "" ]]; then
@@ -153,14 +144,9 @@ else
fi
# CTRL-R - Paste the selected command from history into the command line
if [[ ${FZF_CTRL_R_COMMAND-x} != "" ]]; then
if [[ -n ${FZF_CTRL_R_COMMAND-} ]]; then
echo "warning: FZF_CTRL_R_COMMAND is set to a custom command, but custom commands are not yet supported for CTRL-R" >&2
fi
bind -m emacs-standard -x '"\C-r": __fzf_history__'
bind -m vi-command -x '"\C-r": __fzf_history__'
bind -m vi-insert -x '"\C-r": __fzf_history__'
fi
bind -m emacs-standard -x '"\C-r": __fzf_history__'
bind -m vi-command -x '"\C-r": __fzf_history__'
bind -m vi-insert -x '"\C-r": __fzf_history__'
fi
# ALT-C - cd into the selected directory

View File

@@ -7,7 +7,6 @@
# - $FZF_TMUX_OPTS
# - $FZF_CTRL_T_COMMAND
# - $FZF_CTRL_T_OPTS
# - $FZF_CTRL_R_COMMAND
# - $FZF_CTRL_R_OPTS
# - $FZF_ALT_C_COMMAND
# - $FZF_ALT_C_OPTS
@@ -215,13 +214,8 @@ function fzf_key_bindings
commandline -f repaint
end
if not set -q FZF_CTRL_R_COMMAND; or test -n "$FZF_CTRL_R_COMMAND"
if test -n "$FZF_CTRL_R_COMMAND"
echo "warning: FZF_CTRL_R_COMMAND is set to a custom command, but custom commands are not yet supported for CTRL-R" >&2
end
bind \cr fzf-history-widget
bind -M insert \cr fzf-history-widget
end
bind \cr fzf-history-widget
bind -M insert \cr fzf-history-widget
if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND"
bind \ct fzf-file-widget

View File

@@ -7,7 +7,6 @@
# - $FZF_TMUX_OPTS
# - $FZF_CTRL_T_COMMAND
# - $FZF_CTRL_T_OPTS
# - $FZF_CTRL_R_COMMAND
# - $FZF_CTRL_R_OPTS
# - $FZF_ALT_C_COMMAND
# - $FZF_ALT_C_OPTS
@@ -58,10 +57,7 @@ __fzf_exec_awk() {
elif command -v mawk > /dev/null 2>&1; then
local n x y z d
IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)
[[ $n == mawk ]] &&
(((x * 1000 + y) * 1000 + z >= 1003004)) 2> /dev/null &&
((d >= 20230302)) 2> /dev/null &&
__fzf_awk=mawk
[[ $n == mawk ]] && ((d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004)) && __fzf_awk=mawk
fi
fi
LC_ALL=C exec "$__fzf_awk" "$@"
@@ -154,15 +150,10 @@ fzf-history-widget() {
zle reset-prompt
return $ret
}
if [[ ${FZF_CTRL_R_COMMAND-x} != "" ]]; then
if [[ -n ${FZF_CTRL_R_COMMAND-} ]]; then
echo "warning: FZF_CTRL_R_COMMAND is set to a custom command, but custom commands are not yet supported for CTRL-R" >&2
fi
zle -N fzf-history-widget
bindkey -M emacs '^R' fzf-history-widget
bindkey -M vicmd '^R' fzf-history-widget
bindkey -M viins '^R' fzf-history-widget
fi
zle -N fzf-history-widget
bindkey -M emacs '^R' fzf-history-widget
bindkey -M vicmd '^R' fzf-history-widget
bindkey -M viins '^R' fzf-history-widget
fi
} always {

View File

@@ -365,7 +365,7 @@ func asciiFuzzyIndex(input *util.Chars, pattern []rune, caseSensitive bool) (int
firstIdx, idx, lastIdx := 0, 0, 0
var b byte
for pidx := range pattern {
for pidx := 0; pidx < len(pattern); pidx++ {
b = byte(pattern[pidx])
idx = trySkip(input, caseSensitive, b, idx)
if idx < 0 {
@@ -726,7 +726,7 @@ func FuzzyMatchV1(caseSensitive bool, normalize bool, forward bool, text *util.C
lenRunes := text.Length()
lenPattern := len(pattern)
for index := range lenRunes {
for index := 0; index < lenRunes; index++ {
char := text.Get(indexAt(index, lenRunes, forward))
// This is considerably faster than blindly applying strings.ToLower to the
// whole string

View File

@@ -41,7 +41,7 @@ func testParserReference(t testing.TB, str string) {
equal := len(got) == len(exp)
if equal {
for i := range got {
for i := 0; i < len(got); i++ {
if got[i] != exp[i] {
equal = false
break
@@ -167,9 +167,9 @@ func TestNextAnsiEscapeSequence_Fuzz_Random(t *testing.T) {
randomString := func(rr *rand.Rand) string {
numChars := rand.Intn(50)
codePoints := make([]rune, numChars)
for i := range codePoints {
for i := 0; i < len(codePoints); i++ {
var r rune
for range 1000 {
for n := 0; n < 1000; n++ {
r = rune(rr.Intn(utf8.MaxRune))
// Allow 10% of runes to be invalid
if utf8.ValidRune(r) || rr.Float64() < 0.10 {
@@ -182,7 +182,7 @@ func TestNextAnsiEscapeSequence_Fuzz_Random(t *testing.T) {
}
rr := rand.New(rand.NewSource(1))
for range 100_000 {
for i := 0; i < 100_000; i++ {
testParserReference(t, randomString(rr))
}
}

View File

@@ -51,7 +51,7 @@ func TestChunkList(t *testing.T) {
}
// Add more data
for i := range chunkSize * 2 {
for i := 0; i < chunkSize*2; i++ {
cl.Push(fmt.Appendf(nil, "item %d", i))
}
@@ -85,7 +85,7 @@ func TestChunkListTail(t *testing.T) {
return true
})
total := chunkSize*2 + chunkSize/2
for i := range total {
for i := 0; i < total; i++ {
cl.Push(fmt.Appendf(nil, "item %d", i))
}

View File

@@ -502,7 +502,7 @@ func Run(opts *Options) (int, error) {
return item.acceptNth(opts.Ansi, opts.Delimiter, fn)
}
}
for i := range count {
for i := 0; i < count; i++ {
opts.Printer(transformer(merger.Get(i).item))
}
if count == 0 {

View File

@@ -38,7 +38,7 @@ func TestHistory(t *testing.T) {
if len(h.lines) != maxHistory+1 {
t.Errorf("Expected: %d, actual: %d\n", maxHistory+1, len(h.lines))
}
for i := range maxHistory {
for i := 0; i < maxHistory; i++ {
if h.lines[i] != "foobar" {
t.Error("Expected: foobar, actual: " + h.lines[i])
}

View File

@@ -34,11 +34,11 @@ func buildLists(partiallySorted bool) ([][]Result, []Result) {
numLists := 4
lists := make([][]Result, numLists)
cnt := 0
for i := range numLists {
for i := 0; i < numLists; i++ {
numResults := rand.Int() % 20
cnt += numResults
lists[i] = make([]Result, numResults)
for j := range numResults {
for j := 0; j < numResults; j++ {
item := randResult()
lists[i][j] = item
}
@@ -60,7 +60,7 @@ func TestMergerUnsorted(t *testing.T) {
// Not sorted: same order
mg := NewMerger(nil, lists, false, false, revision{}, 0, 0)
assert(t, cnt == mg.Length(), "Invalid Length")
for i := range cnt {
for i := 0; i < cnt; i++ {
assert(t, items[i] == mg.Get(i), "Invalid Get")
}
}
@@ -73,7 +73,7 @@ func TestMergerSorted(t *testing.T) {
mg := NewMerger(nil, lists, true, false, revision{}, 0, 0)
assert(t, cnt == mg.Length(), "Invalid Length")
sort.Sort(ByRelevance(items))
for i := range cnt {
for i := 0; i < cnt; i++ {
if items[i] != mg.Get(i) {
t.Error("Not sorted", items[i], mg.Get(i))
}

View File

@@ -104,8 +104,6 @@ Usage: fzf [options]
--gap[=N] Render empty lines between each item
--gap-line[=STR] Draw horizontal line on each gap using the string
(default: '┈' or '-')
--freeze-left=N Number of fields to freeze on the left
--freeze-right=N Number of fields to freeze on the right
--keep-right Keep the right end of the line visible on overflow
--scroll-off=LINES Number of screen lines to keep above or below when
scrolling to the top or to the bottom (default: 0)
@@ -208,10 +206,8 @@ Usage: fzf [options]
ADVANCED
--with-shell=STR Shell command and flags to start child processes with
--listen[=[ADDR:]PORT] Start HTTP server to receive actions via TCP
--listen[=[ADDR:]PORT] Start HTTP server to receive actions (POST /)
(To allow remote process execution, use --listen-unsafe)
--listen=SOCKET_PATH Start HTTP server to receive actions via Unix domain socket
(Path should end with .sock)
DIRECTORY TRAVERSAL (Only used when $FZF_DEFAULT_COMMAND is not set)
--walker=OPTS [file][,dir][,follow][,hidden] (default: file,follow,hidden)
@@ -564,8 +560,6 @@ type Options struct {
Case Case
Normalize bool
Nth []Range
FreezeLeft int
FreezeRight int
WithNth func(Delimiter) func([]Token, int32) string
AcceptNth func(Delimiter) func([]Token, int32) string
Delimiter Delimiter
@@ -677,10 +671,9 @@ func defaultPreviewOpts(command string) previewOpts {
}
func defaultOptions() *Options {
var theme, baseTheme *tui.ColorTheme
var theme *tui.ColorTheme
if os.Getenv("NO_COLOR") != "" {
theme = tui.NoColorTheme
baseTheme = tui.NoColorTheme
} else {
theme = tui.EmptyTheme
}
@@ -708,7 +701,6 @@ func defaultOptions() *Options {
Ansi: false,
Mouse: true,
Theme: theme,
BaseTheme: baseTheme,
Black: false,
Bold: true,
MinHeight: -10,
@@ -1217,20 +1209,11 @@ func parseKeyChords(str string, message string) (map[tui.Event]string, []tui.Eve
default:
runes := []rune(key)
if len(key) == 10 && strings.HasPrefix(lkey, "ctrl-alt-") && isAlphabet(lkey[9]) {
r := rune(lkey[9])
evt := tui.CtrlAltKey(r)
if r == 'h' && !util.IsWindows() {
evt = tui.CtrlAltBackspace.AsEvent()
}
evt := tui.CtrlAltKey(rune(key[9]))
chords[evt] = key
list = append(list, evt)
} else if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
evt := tui.EventType(tui.CtrlA.Int() + int(lkey[5]) - 'a')
r := rune(lkey[5])
if r == 'h' && !util.IsWindows() {
evt = tui.CtrlBackspace
}
add(evt)
add(tui.EventType(tui.CtrlA.Int() + int(lkey[5]) - 'a'))
} else if len(runes) == 5 && strings.HasPrefix(lkey, "alt-") {
r := runes[4]
switch r {
@@ -2708,14 +2691,6 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if opts.Nth, err = splitNth(str); err != nil {
return err
}
case "--freeze-left":
if opts.FreezeLeft, err = nextInt("number of fields required"); err != nil {
return err
}
case "--freeze-right":
if opts.FreezeRight, err = nextInt("number of fields required"); err != nil {
return err
}
case "--with-nth":
str, err := nextString("nth expression required")
if err != nil {
@@ -3359,10 +3334,6 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
return errors.New("empty jump labels")
}
if opts.FreezeLeft < 0 || opts.FreezeRight < 0 {
return errors.New("number of fields to freeze must be a non-negative integer")
}
if validateJumpLabels {
for _, r := range opts.JumpLabels {
if r < 32 || r > 126 {

View File

@@ -7,7 +7,6 @@ import (
"io/fs"
"os"
"path/filepath"
"slices"
"strings"
"sync"
"sync/atomic"
@@ -179,7 +178,7 @@ func (r *Reader) feed(src io.Reader) {
for {
n := 0
scope := slab[:util.Min(len(slab), readerBufferSize)]
for range 100 {
for i := 0; i < 100; i++ {
n, err = src.Read(scope)
if n > 0 || err != nil {
break
@@ -309,11 +308,15 @@ func (r *Reader) readFiles(roots []string, opts walkerOpts, ignores []string) bo
if !opts.hidden && base[0] == '.' && base != ".." {
return filepath.SkipDir
}
if slices.Contains(ignoresBase, base) {
return filepath.SkipDir
for _, ignore := range ignoresBase {
if ignore == base {
return filepath.SkipDir
}
}
if slices.Contains(ignoresFull, path) {
return filepath.SkipDir
for _, ignore := range ignoresFull {
if ignore == path {
return filepath.SkipDir
}
}
for _, ignore := range ignoresSuffix {
if strings.HasSuffix(path, ignore) {

View File

@@ -91,7 +91,7 @@ func buildResult(item *Item, offsets []Offset, score int) Result {
case byBegin, byEnd:
if validOffsetFound {
whitePrefixLen := 0
for idx := range numChars {
for idx := 0; idx < numChars; idx++ {
r := item.text.Get(idx)
whitePrefixLen = idx
if idx == minBegin || !unicode.IsSpace(r) {

View File

@@ -46,20 +46,15 @@ type httpServer struct {
type listenAddress struct {
host string
port int
sock string
}
func (addr listenAddress) IsLocal() bool {
return addr.host == "localhost" || addr.host == "127.0.0.1" || len(addr.sock) > 0
return addr.host == "localhost" || addr.host == "127.0.0.1"
}
var defaultListenAddr = listenAddress{"localhost", 0, ""}
var defaultListenAddr = listenAddress{"localhost", 0}
func parseListenAddress(address string) (listenAddress, error) {
if strings.HasSuffix(address, ".sock") {
return listenAddress{"", 0, address}, nil
}
parts := strings.SplitN(address, ":", 3)
if len(parts) == 1 {
parts = []string{"localhost", parts[0]}
@@ -75,7 +70,7 @@ func parseListenAddress(address string) (listenAddress, error) {
if len(parts[0]) == 0 {
parts[0] = "localhost"
}
return listenAddress{parts[0], port, ""}, nil
return listenAddress{parts[0], port}, nil
}
func startHttpServer(address listenAddress, actionChannel chan []*action, getHandler func(getParams) string) (net.Listener, int, error) {
@@ -85,40 +80,21 @@ func startHttpServer(address listenAddress, actionChannel chan []*action, getHan
if !address.IsLocal() && len(apiKey) == 0 {
return nil, port, errors.New("FZF_API_KEY is required to allow remote access")
}
var listener net.Listener
var err error
if len(address.sock) > 0 {
if _, err := os.Stat(address.sock); err == nil {
// Check if the socket is already in use
if conn, err := net.Dial("unix", address.sock); err == nil {
conn.Close()
return nil, 0, fmt.Errorf("socket already in use: %s", address.sock)
}
os.Remove(address.sock)
addrStr := fmt.Sprintf("%s:%d", host, port)
listener, err := net.Listen("tcp", addrStr)
if err != nil {
return nil, port, fmt.Errorf("failed to listen on %s", addrStr)
}
if port == 0 {
addr := listener.Addr().String()
parts := strings.Split(addr, ":")
if len(parts) < 2 {
return nil, port, fmt.Errorf("cannot extract port: %s", addr)
}
listener, err = net.Listen("unix", address.sock)
var err error
port, err = strconv.Atoi(parts[len(parts)-1])
if err != nil {
return nil, 0, fmt.Errorf("failed to listen on %s", address.sock)
}
os.Chmod(address.sock, 0600)
} else {
addrStr := fmt.Sprintf("%s:%d", host, port)
listener, err = net.Listen("tcp", addrStr)
if err != nil {
return nil, port, fmt.Errorf("failed to listen on %s", addrStr)
}
if port == 0 {
addr := listener.Addr().String()
parts := strings.Split(addr, ":")
if len(parts) < 2 {
return nil, port, fmt.Errorf("cannot extract port: %s", addr)
}
var err error
port, err = strconv.Atoi(parts[len(parts)-1])
if err != nil {
return nil, port, err
}
return nil, port, err
}
}

View File

@@ -331,8 +331,6 @@ type Terminal struct {
scrollbar string
previewScrollbar string
ansi bool
freezeLeft int
freezeRight int
nthAttr tui.Attr
nth []Range
nthCurrent []Range
@@ -498,14 +496,6 @@ const (
reqFatal
)
func isTerminalEvent(et util.EventType) bool {
switch et {
case reqClose, reqPrintQuery, reqBecome, reqQuit, reqFatal:
return true
}
return false
}
type action struct {
t actionType
a string
@@ -810,6 +800,7 @@ func defaultKeymap() map[tui.Event][]*action {
add(tui.CtrlD, actDeleteCharEof)
add(tui.CtrlE, actEndOfLine)
add(tui.CtrlF, actForwardChar)
add(tui.CtrlH, actBackwardDeleteChar)
add(tui.Backspace, actBackwardDeleteChar)
add(tui.CtrlBackspace, actBackwardDeleteChar)
add(tui.Tab, actToggleDown)
@@ -1060,8 +1051,6 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
footer: opts.Footer,
header0: opts.Header,
ansi: opts.Ansi,
freezeLeft: opts.FreezeLeft,
freezeRight: opts.FreezeRight,
nthAttr: opts.Theme.Nth.Attr,
nth: opts.Nth,
nthCurrent: opts.Nth,
@@ -1279,9 +1268,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
return nil, err
}
t.listener = listener
if port > 0 {
t.listenPort = &port
}
t.listenPort = &port
}
if t.hasStartActions {
@@ -1305,9 +1292,6 @@ func (t *Terminal) environForPreview() []string {
func (t *Terminal) environImpl(forPreview bool) []string {
env := os.Environ()
if t.listenAddr != nil && len(t.listenAddr.sock) > 0 {
env = append(env, "FZF_SOCK="+t.listenAddr.sock)
}
if t.listenPort != nil {
env = append(env, fmt.Sprintf("FZF_PORT=%d", *t.listenPort))
}
@@ -2470,13 +2454,6 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
innerHeight-shrink, tui.WindowList, noBorder, true)
}
if len(t.scrollbar) == 0 {
for y := 0; y < t.window.Height(); y++ {
t.window.Move(y, t.window.Width()-1)
t.window.Print(" ")
}
}
createInnerWindow := func(b tui.Window, shape tui.BorderShape, windowType tui.WindowType, shift int) tui.Window {
top := b.Top()
left := b.Left() + shift
@@ -2490,8 +2467,6 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
if shape.HasRight() {
width++
}
// Make sure that the width does not exceed the list width
width = util.Min(t.window.Width()+t.headerIndentImpl(0, shape), width)
height := b.Height() - borderLines(shape)
return t.tui.NewWindow(top, left, width, height, windowType, noBorder, true)
}
@@ -2995,11 +2970,6 @@ func (t *Terminal) printInfoImpl() {
} else {
outputPrinter(t.window, maxWidth)
}
if t.infoStyle == infoInline && outputLen < maxWidth-1 && t.reading {
t.window.Print(" ")
printSpinner()
outputLen += 2
}
if t.infoStyle == infoInlineRight {
if t.separatorLen > 0 {
@@ -3109,11 +3079,7 @@ func (t *Terminal) printFooter() {
}
func (t *Terminal) headerIndent(borderShape tui.BorderShape) int {
return t.headerIndentImpl(t.pointerLen+t.markerLen, borderShape)
}
func (t *Terminal) headerIndentImpl(base int, borderShape tui.BorderShape) int {
indentSize := base
indentSize := t.pointerLen + t.markerLen
if t.listBorderShape.HasLeft() {
indentSize += 1 + t.borderWidth
}
@@ -3532,48 +3498,17 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
} else {
tokens = Transform(Tokenize(item.text.ToString(), t.delimiter), t.nthCurrent)
}
nthOffsets = make([]Offset, len(tokens))
for i, token := range tokens {
for _, token := range tokens {
start := token.prefixLength
length := token.text.Length() - token.text.TrailingWhitespaces()
end := start + int32(length)
nthOffsets[i] = Offset{int32(start), int32(end)}
nthOffsets = append(nthOffsets, Offset{int32(start), int32(end)})
}
sort.Sort(ByOrder(nthOffsets))
}
}
allOffsets := result.colorOffsets(charOffsets, nthOffsets, t.theme, colBase, colMatch, t.nthAttr, hidden)
// Determine split offset for horizontal scrolling with freeze
splitOffset1 := -1
splitOffset2 := -1
if t.hscroll && !t.wrap {
var tokens []Token
if t.freezeLeft > 0 || t.freezeRight > 0 {
tokens = Tokenize(item.text.ToString(), t.delimiter)
}
// 0 | 1 | 2 | 3 | 4 | 5
// ------> <------
if t.freezeLeft > 0 {
if len(tokens) > 0 {
token := tokens[util.Min(t.freezeLeft, len(tokens))-1]
splitOffset1 = int(token.prefixLength) + token.text.Length() - token.text.TrailingWhitespaces()
}
}
if t.freezeRight > 0 {
index := util.Max(t.freezeLeft-1, len(tokens)-t.freezeRight-1)
if index < 0 {
splitOffset2 = 0
} else if index >= t.freezeLeft {
token := tokens[index]
delimiter := strings.TrimLeftFunc(GetLastDelimiter(token.text.ToString(), t.delimiter), unicode.IsSpace)
splitOffset2 = int(token.prefixLength) + token.text.Length() - len([]rune(delimiter))
}
splitOffset2 = util.Max(splitOffset2, splitOffset1)
}
}
maxLines := 1
if t.canSpanMultiLines() {
maxLines = maxLineNum - lineNum + 1
@@ -3643,24 +3578,16 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
break
}
}
splitOffsetLeft := 0
if splitOffset1 >= 0 && splitOffset1 > from && splitOffset1 < from+len(line) {
splitOffsetLeft = splitOffset1 - from
}
splitOffsetRight := -1
if splitOffset2 >= 0 && splitOffset2 >= from && splitOffset2 < from+len(line) {
splitOffsetRight = splitOffset2 - from
}
from += len(line)
if lineOffset < skipLines {
continue
}
actualLineOffset := lineOffset - skipLines
var maxEnd int
var maxe int
for _, offset := range offsets {
if offset.match {
maxEnd = util.Max(maxEnd, int(offset.offset[1]))
maxe = util.Max(maxe, int(offset.offset[1]))
}
}
@@ -3724,117 +3651,69 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
wrapped = true
}
frozenLeft := line[:splitOffsetLeft]
middle := line[splitOffsetLeft:]
frozenRight := []rune{}
if splitOffsetRight >= splitOffsetLeft {
middle = line[splitOffsetLeft:splitOffsetRight]
frozenRight = line[splitOffsetRight:]
}
displayWidthSum := 0
todo := [3]func(){}
for fidx, runes := range [][]rune{frozenLeft, frozenRight, middle} {
if len(runes) == 0 {
continue
}
shift := 0
maxe := maxEnd
offs := make([]colorOffset, len(offsets))
for idx := range offsets {
offs[idx] = offsets[idx]
if fidx == 1 && splitOffsetRight > 0 {
shift = splitOffsetRight
} else if fidx == 2 && splitOffsetLeft > 0 {
shift = splitOffsetLeft
}
offs[idx].offset[0] -= int32(shift)
offs[idx].offset[1] -= int32(shift)
}
maxe -= shift
ellipsis, ellipsisWidth := util.Truncate(t.ellipsis, maxWidth)
adjustedMaxWidth := maxWidth
if fidx < 2 {
// For frozen parts, reserve space for the ellipsis in the middle part
adjustedMaxWidth -= ellipsisWidth
}
displayWidth = t.displayWidthWithLimit(runes, 0, adjustedMaxWidth)
if !t.wrap && displayWidth > adjustedMaxWidth {
maxe = util.Constrain(maxe+util.Min(maxWidth/2-ellipsisWidth, t.hscrollOff), 0, len(runes))
transformOffsets := func(diff int32, rightTrim bool) {
for idx, offset := range offs {
b, e := offset.offset[0], offset.offset[1]
el := int32(len(ellipsis))
b += el - diff
e += el - diff
b = util.Max32(b, el)
if rightTrim {
e = util.Min32(e, int32(maxWidth-ellipsisWidth))
}
offs[idx].offset[0] = b
offs[idx].offset[1] = util.Max32(b, e)
displayWidth = t.displayWidthWithLimit(line, 0, maxWidth)
if !t.wrap && displayWidth > maxWidth {
ellipsis, ellipsisWidth := util.Truncate(t.ellipsis, maxWidth/2)
maxe = util.Constrain(maxe+util.Min(maxWidth/2-ellipsisWidth, t.hscrollOff), 0, len(line))
transformOffsets := func(diff int32, rightTrim bool) {
for idx, offset := range offsets {
b, e := offset.offset[0], offset.offset[1]
el := int32(len(ellipsis))
b += el - diff
e += el - diff
b = util.Max32(b, el)
if rightTrim {
e = util.Min32(e, int32(maxWidth-ellipsisWidth))
}
offsets[idx].offset[0] = b
offsets[idx].offset[1] = util.Max32(b, e)
}
if t.hscroll {
if fidx == 1 || fidx == 2 && t.keepRight && pos == nil {
trimmed, diff := t.trimLeft(runes, maxWidth, ellipsisWidth)
transformOffsets(diff, false)
runes = append(ellipsis, trimmed...)
} else if fidx == 0 || !t.overflow(runes[:maxe], maxWidth-ellipsisWidth) {
// Stri..
runes, _ = t.trimRight(runes, maxWidth-ellipsisWidth)
runes = append(runes, ellipsis...)
} else {
// Stri..
rightTrim := false
if t.overflow(runes[maxe:], ellipsisWidth) {
runes = append(runes[:maxe], ellipsis...)
rightTrim = true
}
// ..ri..
var diff int32
runes, diff = t.trimLeft(runes, maxWidth, ellipsisWidth)
// Transform offsets
transformOffsets(diff, rightTrim)
runes = append(ellipsis, runes...)
}
}
if t.hscroll {
if t.keepRight && pos == nil {
trimmed, diff := t.trimLeft(line, maxWidth, ellipsisWidth)
transformOffsets(diff, false)
line = append(ellipsis, trimmed...)
} else if !t.overflow(line[:maxe], maxWidth-ellipsisWidth) {
// Stri..
line, _ = t.trimRight(line, maxWidth-ellipsisWidth)
line = append(line, ellipsis...)
} else {
runes, _ = t.trimRight(runes, maxWidth-ellipsisWidth)
runes = append(runes, ellipsis...)
for idx, offset := range offs {
offs[idx].offset[0] = util.Min32(offset.offset[0], int32(maxWidth-len(ellipsis)))
offs[idx].offset[1] = util.Min32(offset.offset[1], int32(maxWidth))
// Stri..
rightTrim := false
if t.overflow(line[maxe:], ellipsisWidth) {
line = append(line[:maxe], ellipsis...)
rightTrim = true
}
}
displayWidth = t.displayWidthWithLimit(runes, 0, displayWidth)
}
displayWidthSum += displayWidth
// ..ri..
var diff int32
line, diff = t.trimLeft(line, maxWidth, ellipsisWidth)
if maxWidth > 0 {
color := colBase
if hidden {
color = color.WithFg(t.theme.Nomatch)
}
todo[fidx] = func() {
t.printColoredString(t.window, runes, offs, color)
// Transform offsets
transformOffsets(diff, rightTrim)
line = append(ellipsis, line...)
}
} else {
break
line, _ = t.trimRight(line, maxWidth-ellipsisWidth)
line = append(line, ellipsis...)
for idx, offset := range offsets {
offsets[idx].offset[0] = util.Min32(offset.offset[0], int32(maxWidth-len(ellipsis)))
offsets[idx].offset[1] = util.Min32(offset.offset[1], int32(maxWidth))
}
}
maxWidth -= displayWidth
displayWidth = t.displayWidthWithLimit(line, 0, displayWidth)
}
if todo[0] != nil {
todo[0]()
}
if todo[2] != nil {
todo[2]()
}
if todo[1] != nil {
todo[1]()
if maxWidth > 0 {
color := colBase
if hidden {
color = color.WithFg(t.theme.Nomatch)
}
t.printColoredString(t.window, line, offsets, color)
}
if postTask != nil {
postTask(actualLineNum, displayWidthSum, wasWrapped, forceRedraw, lbg)
postTask(actualLineNum, displayWidth, wasWrapped, forceRedraw, lbg)
} else {
t.markOtherLine(actualLineNum)
}
@@ -4916,7 +4795,7 @@ func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, [3][]*I
if asterisk {
cnt := t.merger.Length()
all = make([]*Item, cnt)
for i := range cnt {
for i := 0; i < cnt; i++ {
all[i] = t.merger.Get(i).item
}
}
@@ -5633,7 +5512,7 @@ func (t *Terminal) Loop() error {
req := func(evts ...util.EventType) {
for _, event := range evts {
events = append(events, event)
if isTerminalEvent(event) {
if event == reqClose || event == reqQuit {
looping = false
}
}
@@ -7168,7 +7047,7 @@ func (t *Terminal) constrain() {
// May need to try again after adjusting the offset
t.offset = util.Constrain(t.offset, 0, count)
for range maxLines {
for tries := 0; tries < maxLines; tries++ {
numItems := maxLines
// How many items can be fit on screen including the current item?
if t.canSpanMultiLines() && t.merger.Length() > 0 {
@@ -7222,7 +7101,7 @@ func (t *Terminal) constrain() {
scrollOff := util.Min(maxLines/2, t.scrollOff)
newOffset := t.offset
// 2-phase adjustment to avoid infinite loop of alternating between moving up and down
for phase := range 2 {
for phase := 0; phase < 2; phase++ {
for {
prevOffset := newOffset
numItems := t.merger.Length()

View File

@@ -206,9 +206,8 @@ func Tokenize(text string, delimiter Delimiter) []Token {
if delimiter.regex != nil {
locs := delimiter.regex.FindAllStringIndex(text, -1)
begin := 0
tokens = make([]string, len(locs))
for i, loc := range locs {
tokens[i] = text[begin:loc[1]]
for _, loc := range locs {
tokens = append(tokens, text[begin:loc[1]])
begin = loc[1]
}
if begin < len(text) {
@@ -234,23 +233,6 @@ func StripLastDelimiter(str string, delimiter Delimiter) string {
return strings.TrimRightFunc(str, unicode.IsSpace)
}
func GetLastDelimiter(str string, delimiter Delimiter) string {
if delimiter.str != nil {
if strings.HasSuffix(str, *delimiter.str) {
return *delimiter.str
}
} else if delimiter.regex != nil {
locs := delimiter.regex.FindAllStringIndex(str, -1)
if len(locs) > 0 {
lastLoc := locs[len(locs)-1]
if lastLoc[1] == len(str) {
return str[lastLoc[0]:]
}
}
}
return ""
}
// JoinTokens concatenates the tokens into a single string
func JoinTokens(tokens []Token) string {
var output bytes.Buffer

View File

@@ -98,7 +98,7 @@ func (r *LightRenderer) findOffset() (row int, col int) {
r.flush()
var err error
bytes := []byte{}
for tries := range offsetPollTries {
for tries := 0; tries < offsetPollTries; tries++ {
bytes, err = r.getBytesInternal(bytes, tries > 0)
if err != nil {
return -1, -1

View File

@@ -296,9 +296,8 @@ func (a ColorAttr) IsColorDefined() bool {
}
func (a ColorAttr) IsAttrDefined() bool {
return a.Attr&^BoldForce != AttrUndefined
return a.Attr != AttrUndefined
}
func (a ColorAttr) IsUndefined() bool {
return !a.IsColorDefined() && !a.IsAttrDefined()
}
@@ -1118,22 +1117,6 @@ func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, boldify bool, forceBlac
theme.Bg = ColorAttr{colBlack, AttrUndefined}
}
if boldify {
boldify := func(c ColorAttr) ColorAttr {
dup := c
if (c.Attr & AttrRegular) == 0 {
dup.Attr |= BoldForce
}
return dup
}
theme.Current = boldify(theme.Current)
theme.CurrentMatch = boldify(theme.CurrentMatch)
theme.Prompt = boldify(theme.Prompt)
theme.Input = boldify(theme.Input)
theme.Cursor = boldify(theme.Cursor)
theme.Spinner = boldify(theme.Spinner)
}
o := func(a ColorAttr, b ColorAttr) ColorAttr {
c := a
if b.Color != colUndefined {
@@ -1158,12 +1141,12 @@ func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, boldify bool, forceBlac
// e.g. fzf --delimiter / --nth -1 --color fg:dim,nth:regular
current := theme.Current
if !baseTheme.Colored && current.IsUndefined() {
current.Attr |= Reverse
current.Attr = Reverse
}
theme.Current = theme.Fg.Merge(o(baseTheme.Current, current))
currentMatch := theme.CurrentMatch
if !baseTheme.Colored && currentMatch.IsUndefined() {
currentMatch.Attr |= Reverse | Underline
currentMatch.Attr = Reverse | Underline
}
theme.CurrentMatch = o(baseTheme.CurrentMatch, currentMatch)
theme.Spinner = o(baseTheme.Spinner, theme.Spinner)
@@ -1249,6 +1232,22 @@ func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, boldify bool, forceBlac
theme.FooterBorder = o(theme.Border, theme.FooterBorder)
theme.FooterLabel = o(theme.BorderLabel, theme.FooterLabel)
if boldify {
boldify := func(c ColorAttr) ColorAttr {
dup := c
if (c.Attr & AttrRegular) == 0 {
dup.Attr |= BoldForce
}
return dup
}
theme.Current = boldify(theme.Current)
theme.CurrentMatch = boldify(theme.CurrentMatch)
theme.Prompt = boldify(theme.Prompt)
theme.Input = boldify(theme.Input)
theme.Cursor = boldify(theme.Cursor)
theme.Spinner = boldify(theme.Spinner)
}
if theme.Nomatch.IsUndefined() {
theme.Nomatch.Attr = Dim
}

View File

@@ -8,7 +8,7 @@ import (
func TestAtExit(t *testing.T) {
want := []int{3, 2, 1, 0}
var called []int
for i := range 4 {
for i := 0; i < 4; i++ {
n := i
AtExit(func() { called = append(called, n) })
}

View File

@@ -52,7 +52,7 @@ func ToChars(bytes []byte) Chars {
}
runes := make([]rune, bytesUntil, len(bytes))
for i := range bytesUntil {
for i := 0; i < bytesUntil; i++ {
runes[i] = rune(bytes[i])
}
for i := bytesUntil; i < len(bytes); {
@@ -259,7 +259,7 @@ func (chars *Chars) Lines(multiLine bool, maxLines int, wrapCols int, wrapSignWi
lines = append(lines, text)
} else {
from := 0
for off := range text {
for off := 0; off < len(text); off++ {
if text[off] == '\n' {
lines = append(lines, text[from:off+1]) // Include '\n'
from = off + 1

View File

@@ -1190,51 +1190,6 @@ class TestCore < TestInteractive
tmux.until { |lines| assert lines.any_include?('9999␊10000') }
end
def test_freeze_left_keep_right
tmux.send_keys %[seq 10000 | #{FZF} --read0 --delimiter "\n" --freeze-left 3 --keep-right --ellipsis XX --no-multi-line --bind space:toggle-multi-line], :Enter
tmux.until { |lines| assert_match(/^> 1␊2␊3XX.*10000␊$/, lines[-3]) }
tmux.send_keys '5'
tmux.until { |lines| assert_match(/^> 1␊2␊3␊4␊5␊.*XX$/, lines[-3]) }
tmux.send_keys :Space
tmux.until { |lines| assert lines.any_include?('> 1') }
tmux.send_keys :Space
tmux.until { |lines| assert lines.any_include?('1␊2␊3␊4␊5␊') }
end
def test_freeze_left_and_right
tmux.send_keys %[seq 10000 | tr "\n" ' ' | #{FZF} --freeze-left 3 --freeze-right 3 --ellipsis XX], :Enter
tmux.until { |lines| assert_match(/XX9998 9999 10000$/, lines[-3]) }
tmux.send_keys "'1000"
tmux.until { |lines| assert_match(/^> 1 2 3XX.*XX9998 9999 10000$/,lines[-3]) }
end
def test_freeze_left_and_right_delimiter
tmux.send_keys %[seq 10000 | tr "\n" ' ' | sed 's/ / , /g' | #{FZF} --freeze-left 3 --freeze-right 3 --ellipsis XX --delimiter ' , '], :Enter
tmux.until { |lines| assert_match(/XX, 9999 , 10000 ,$/, lines[-3]) }
tmux.send_keys "'1000"
tmux.until { |lines| assert_match(/^> 1 , 2 , 3 ,XX.*XX, 9999 , 10000 ,$/,lines[-3]) }
end
def test_freeze_right_exceed_range
tmux.send_keys %[seq 10000 | tr "\n" ' ' | #{FZF} --freeze-right 100000 --ellipsis XX], :Enter
['', "'1000"].each do |query|
tmux.send_keys query
tmux.until { |lines| assert lines.any_include?("> #{query}".strip) }
tmux.until do |lines|
assert_match(/ 9998 9999 10000$/, lines[-3])
assert_equal(1, lines[-3].scan('XX').size)
end
end
end
def test_freeze_right_exceed_range_with_freeze_left
tmux.send_keys %[seq 10000 | tr "\n" ' ' | #{FZF} --freeze-left 3 --freeze-right 100000 --ellipsis XX], :Enter
tmux.until do |lines|
assert_match(/^> 1 2 3XX.*9998 9999 10000$/, lines[-3])
assert_equal(1, lines[-3].scan('XX').size)
end
end
def test_backward_eof
tmux.send_keys "echo foo | #{FZF} --bind 'backward-eof:reload(seq 100)'", :Enter
tmux.until { |lines| lines.item_count == 1 && lines.match_count == 1 }

View File

@@ -1192,38 +1192,6 @@ class TestLayout < TestInteractive
tmux.until { assert_block(block, it) }
end
# https://github.com/junegunn/fzf/issues/4537
def test_no_scrollbar_preview_toggle
x = 'x' * 300
y = 'y' * 300
tmux.send_keys %(yes #{x} | head -1000 | fzf --bind 'tab:toggle-preview' --border --no-scrollbar --preview 'echo #{y}' --preview-window 'border-left'), :Enter
# │ ▌ xxxxxxxx·· │ yyyyyyyy│
tmux.until do |lines|
lines.any? { it.match?(/x·· │ y+│$/) }
end
tmux.send_keys :Tab
# │ ▌ xxxxxxxx·· │
tmux.until do |lines|
lines.none? { it.match?(/x··y│$/) }
end
tmux.send_keys :Tab
tmux.until do |lines|
lines.any? { it.match?(/x·· │ y+│$/) }
end
end
def test_header_and_footer_should_not_be_wider_than_list
tmux.send_keys %(WIDE=$(printf 'x%.0s' {1..1000}); (echo $WIDE; echo $WIDE) | fzf --header-lines 1 --style full --header-border bottom --header-lines-border top --ellipsis XX --header "$WIDE" --footer "$WIDE" --no-footer-border), :Enter
tmux.until do |lines|
matches = lines.filter_map { |line| line[/x+XX/] }
assert_equal 4, matches.length
assert_equal 1, matches.uniq.length
end
end
def test_combinations
skip unless ENV['LONGTEST']