mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-18 08:13:40 -05:00
Compare commits
20 Commits
v0.67.0
...
04a2dfacf0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04a2dfacf0 | ||
|
|
f4937c1df9 | ||
|
|
7288daff07 | ||
|
|
edd08d5cc6 | ||
|
|
ee9c13eeeb | ||
|
|
5dea11ddbb | ||
|
|
e393fda2fb | ||
|
|
e7e7bc3315 | ||
|
|
93b446a848 | ||
|
|
d3485a06a1 | ||
|
|
e21f1c1b69 | ||
|
|
ba113ebe6a | ||
|
|
9ca2f09d5b | ||
|
|
abb1d3af7b | ||
|
|
436e43ad0e | ||
|
|
2f03c5d10f | ||
|
|
ba945f1bf9 | ||
|
|
44572af6bb | ||
|
|
c481058f90 | ||
|
|
f8521fa90e |
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/linux.yml
vendored
2
.github/workflows/linux.yml
vendored
@@ -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"
|
||||
|
||||
|
||||
2
.github/workflows/macos.yml
vendored
2
.github/workflows/macos.yml
vendored
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
150
CHANGELOG.md
150
CHANGELOG.md
@@ -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
|
||||
|
||||

|
||||
### 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:
|
||||
|
||||

|
||||
### 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
|
||||
|
||||
|
||||
3
install
3
install
@@ -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 ;;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
$version="0.67.0"
|
||||
$version="0.65.2"
|
||||
|
||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
|
||||
|
||||
2
main.go
2
main.go
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" "$@"
|
||||
|
||||
@@ -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" "$@"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
247
src/terminal.go
247
src/terminal.go
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) })
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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']
|
||||
|
||||
|
||||
Reference in New Issue
Block a user