Compare commits

...

72 Commits

Author SHA1 Message Date
Junegunn Choi
81366c548b Support zellij floating pane via --popup (new name for --tmux) 2024-12-26 22:06:52 +09:00
bitraid
b2c3e567da [fish] Partly revert change of 0167691 (#4137)
Don't use the `-f` switch of `string split`, because it was added in
fish version 3.2.0.
2024-12-20 10:05:09 +09:00
Junegunn Choi
ca5e633399 Add toggle-hscroll 2024-12-19 21:05:26 +09:00
Junegunn Choi
e60a9a628b Add toggle-multi-line action 2024-12-19 21:05:26 +09:00
bitraid
0167691941 [fish] Small syntax modification of some commands
No actual change, just for consistency with the rest of the code.
2024-12-19 20:50:04 +09:00
bitraid
3b0f976380 [fish] Enable home dir expansion of leading ~/
Enable expanding to user's home directory, when pressing <Ctrl-T> or
<Alt-C>, and the current command line token starts with `~/`.
2024-12-19 20:50:04 +09:00
bitraid
7bd298b536 [fish] Don't strip leading dot (.) character
Fix the removal of the leading dot character from the query, when
<Ctrl-T> was pressed and the current command line token started with a
dot. It was also removed when <Alt-C> was pressed and the directory
didn't exist under the current path.
2024-12-19 20:50:04 +09:00
Junegunn Choi
0476a65fca 0.57.0 2024-12-15 17:04:04 +09:00
junegunn
2cb2af115a Deploying to master from @ junegunn/fzf@789226ff6d 🚀 2024-12-15 00:02:31 +00:00
Junegunn Choi
789226ff6d Fix test failure
cdcab26 removed excessive clearing of the windows. But it caused the
problem where the right side of the preview window border was not
cleared when hiding the preview window with the scrollbar disabled.
2024-12-14 22:42:40 +09:00
Junegunn Choi
805efc5bf1 Remove unused interface 2024-12-14 22:31:39 +09:00
Junegunn Choi
cdcab26766 Fix redundant clearing of the windows with non-default bg color 2024-12-14 22:06:14 +09:00
Junegunn Choi
ec3acb1932 Update CHANGELOG 2024-12-12 13:53:58 +09:00
Junegunn Choi
d30e37434e Less flickering of the candidate list when resizing the preview window 2024-12-12 13:53:08 +09:00
Junegunn Choi
20d5b2e20e Avoid redrawing the windows on the first click on the border 2024-12-12 13:53:08 +09:00
Junegunn Choi
6c6be4ab1a Simplify resize code 2024-12-12 13:53:08 +09:00
Junegunn Choi
d004eb1f7c Redraw preview scrollbar when window width changes 2024-12-12 13:53:08 +09:00
Junegunn Choi
3148b0f3e8 Restore previous behavior 2024-12-12 13:53:08 +09:00
Junegunn Choi
3fc0bd26a5 Disallow dragging the wrong sides of the border 2024-12-12 13:53:08 +09:00
Junegunn Choi
6c9025ff17 Update comments 2024-12-12 13:53:08 +09:00
Junegunn Choi
289997e373 Refactor 2024-12-12 13:53:08 +09:00
Junegunn Choi
db44cbdff0 Change test case expectation (hard-coded minimum width removed) 2024-12-12 13:53:08 +09:00
Junegunn Choi
da9179335c Respect the properties of the currently active preview window options 2024-12-12 13:53:08 +09:00
Julian Prein
cdf641fa3e Use Has{Top,Right,Bottom,Left}() where possible
De-duplicate code and reduce the amount of code that has to be changed
when new BorderShapes are being added. This also adds and uses the
missing HasBottom().
2024-12-12 13:53:08 +09:00
Julian Prein
66dbee10f5 Fix minimum preview width without left/right borders
When the chosen preview border shape has no left and/or right border,
the minimum total preview window size decreases. But due to the
hardcoded value for the minimum size of the preview window the size
could not be decreased further than 5.
2024-12-12 13:53:08 +09:00
Julian Prein
19e9b620ba Fix maximum preview height without horizontal separator
The minimum window height decreases when no extra line for the
horizontal separator is used (e.g. with `--info=inline --no-separator`).
In this case the preview window should be able to occupy this extra
line.
2024-12-12 13:53:08 +09:00
Julian Prein
e4e4700aff Make the preview window resizable by mouse drag
Enable resizing the preview window by dragging its border with the
mouse. This works with all border styles except for `none`.
Counter-intuitively, having the border only on the opposite side of the
window works too - dragging from it will first decrease the preview size
to its minimum.
2024-12-12 13:53:08 +09:00
dependabot[bot]
bb55045596 Bump golang.org/x/term from 0.26.0 to 0.27.0 (#4124)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.26.0 to 0.27.0.
- [Commits](https://github.com/golang/term/compare/v0.26.0...v0.27.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-09 23:06:49 +09:00
dependabot[bot]
d7e51cdeb5 Bump crate-ci/typos from 1.28.1 to 1.28.2 (#4123)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.28.1 to 1.28.2.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.28.1...v1.28.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-09 23:06:32 +09:00
junegunn
7f4964b366 Deploying to master from @ junegunn/fzf@a6957aba11 🚀 2024-12-08 00:02:15 +00:00
LangLangBart
a6957aba11 chore: completion test command sequence (#4115)
cleanup zsh global scope
2024-12-03 20:34:26 +09:00
dependabot[bot]
b5f94f961d Bump crate-ci/typos from 1.27.3 to 1.28.1 (#4114)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.27.3 to 1.28.1.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.27.3...v1.28.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-03 00:25:33 +09:00
Junegunn Choi
e182d3db7a Fix line wrap toggle when switching between screens
Fix #4099
2024-12-02 22:25:23 +09:00
Junegunn Choi
3e6e0528a6 [install] grep -> \grep 2024-12-01 23:22:36 +09:00
buttering
ac508a1ce4 Enhance install script to handle commented and uncommented lines (#3632) (#4112)
* Enhance install script to handle commented and uncommented lines (#3632)

Resolves #3632

Enhance install script to handle commented and uncommented lines in shell file with user prompts for modification.
- Track commented and uncommented lines in the file.
- Prompt user to append or skip if the line is commented.
- Ensure new lines are added only when necessary, based on user input.
- To the `fish_user_key_bindings.fish`, the original logic would append the line to the end if no corresponding statement was found. I’ve adopted the same behavior for commented lines.

* Refactor append_line function to improve line existence check.

- Replaced `lno` variable with `lines` to store matching lines and simplified the logic.
- Improved line existence check, now prints all matching lines directly and handles commented lines separately.
- Removed unnecessary variables like `all_commented`, `commented_lines`, and `non_commented_lines`.

* Fix indentation

---------

Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2024-12-01 23:21:12 +09:00
junegunn
d7fc1e09b1 Deploying to master from @ junegunn/fzf@3b0c86e401 🚀 2024-12-01 00:02:24 +00:00
Junegunn Choi
3b0c86e401 Much faster image processing
Fix #3984
2024-11-29 00:26:12 +09:00
Junegunn Choi
61d10d8ffa Update README and CHANGELOG
Close #4022
2024-11-28 19:46:56 +09:00
Junegunn Choi
7d9548919e Extend --walker-skip to support multi-component patterns
fzf --walker-skip 'foo/bar'

Close #4107
2024-11-26 17:26:16 +09:00
msabathier
bee80a730f Allow walking multiple root directories (#4109)
Co-authored-by: Martin Sabathier <martin.sabathier.ext@corys.fr>
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2024-11-25 19:25:30 +09:00
Junegunn Choi
ac3e24c99c Export FZF_PREVIEW_* variables to other processes as well
Close #4098
2024-11-24 18:49:10 +09:00
junegunn
e7e852bdb3 Deploying to master from @ junegunn/fzf@2b7f168571 🚀 2024-11-24 00:03:09 +00:00
bitraid
2b7f168571 [fish] Enable keys for scripts that use read
Remove the check that exits when the shell is non-interactive, so that
the key bindings will work for the read command, when used by scripts.
2024-11-18 19:08:34 +09:00
bitraid
5b3da1d878 [fish] Use more native syntax
Mainly, replace [ with test. Also, change $FZF_TMUX check from numeric
to string, so that it won't show error if doesn't contain a number.
2024-11-18 19:08:34 +09:00
bitraid
99f1bc0177 [fish] Format history using builtins if perl is missing 2024-11-18 19:08:34 +09:00
bitraid
ed76f076dd [fish] Replace external commands with builtins
- Use `string collect` instead of cat to get the contents of
  $FZF_DEFAULT_OPTS_FILE. Also, check if the file is readable first.
- Use `string split` instead of cut to set $FISH_MAJOR, $FISH_MINOR.
- Use `string replace` instead of perl to strip leading tabs.
2024-11-18 19:08:34 +09:00
bitraid
4d357d1063 [fish] Improve commandline parsing
- Enable using unescaped quotes for exact-match, exact-boundary-match.
- Enable suffix-exact-match.
- Enable inverse-exact-match, inverse-prefix/suffix-exact-match.
- Allow searching for double quotes and backslashes.
- Combine multiple consecutive slashes into one.
- Workaround for test command bug, allowing $dir or $commandline be a
  single `!`.
2024-11-18 19:08:34 +09:00
junegunn
961ae1541c Deploying to master from @ junegunn/fzf@add1aec685 🚀 2024-11-17 00:02:20 +00:00
Junegunn Choi
add1aec685 0.56.3 2024-11-15 10:06:01 +09:00
LangLangBart
03d6ba7496 fix(zsh): handle backtick trigger edge case (#4090) 2024-11-14 16:07:52 +09:00
LangLangBart
71e4d5cc51 revert(zsh): remove 'fc -RI' call in the history widget (#4093) 2024-11-14 10:38:05 +09:00
Junegunn Choi
215ab48222 0.56.2 2024-11-12 00:57:55 +09:00
林千里
0c64c68781 Fix zsh $+name syntax does not work with setopt ksh_arrays (#4084) 2024-11-12 00:53:36 +09:00
Junegunn Choi
3ec035c68b Fix incorrect overflow detection when --wrap is set
Fix #4083
2024-11-12 00:33:07 +09:00
dependabot[bot]
20c7dcfbca Bump golang.org/x/term from 0.25.0 to 0.26.0 (#4085)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.25.0 to 0.26.0.
- [Commits](https://github.com/golang/term/compare/v0.25.0...v0.26.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-12 00:32:37 +09:00
dependabot[bot]
c1b8780b9c Bump crate-ci/typos from 1.26.0 to 1.27.3 (#4087)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.26.0 to 1.27.3.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.26.0...v1.27.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-12 00:32:12 +09:00
Junegunn Choi
64c61603e9 0.56.1 2024-11-10 23:15:35 +09:00
LangLangBart
57c08d925f Enhance command extraction in zsh completion (#4082)
Fix #1992
2024-11-10 17:40:59 +09:00
junegunn
51623a5f6a Deploying to master from @ junegunn/fzf@ca3f6181d7 🚀 2024-11-10 00:02:06 +00:00
Junegunn Choi
ca3f6181d7 page-up/down: undo last up/down if items are skipped
Fix #4069
2024-11-09 11:54:41 +09:00
Junegunn Choi
9c94f9c3d0 Another attempt to fix (half-)page-up/down for multi-line items
Fix #4069
2024-11-08 20:18:42 +09:00
Junegunn Choi
4a85843bcf Fix (half-)page-up/down in the presence of multi-line items
Fix #4069
2024-11-07 22:21:07 +09:00
jaydee-coder
d4d9b99879 Allow specifying '{n}' as the OFFSET in the preview-window flag (#4079)
* fzf: Allow '{n}' to be used as the OFFSET in the preview-window flag

* man: Document using '{n}' as the OFFSET in the preview-window flag
2024-11-04 22:27:59 +09:00
jaydee-coder
6816b7d95b docker: fix dockerfile warnings (#4080)
The following warnings were emitted when running `make docker-test`:
```
 2 warnings found (use docker --debug to expand):
 - LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format (line 11)
 - JSONArgsRecommended: JSON arguments recommended for CMD to prevent unintended behavior related to OS signals (line 12)
```

This change fixes both of these 2 trivial warnings.
2024-11-04 22:26:19 +09:00
Junegunn Choi
acdf265d7a Fix reader regression (#4070) 2024-11-03 20:32:26 +09:00
Junegunn Choi
19495eb9bb Remove possible races (#4070) 2024-11-03 20:12:47 +09:00
Junegunn Choi
bacc8609ee Fix characters from previous preview not being cleared
Fix #4075
2024-11-03 15:07:17 +09:00
junegunn
99163f5afa Deploying to master from @ junegunn/fzf@0607227730 🚀 2024-11-03 00:02:14 +00:00
LangLangBart
0607227730 fix(zsh): move 'fc -RI' inside command substitution (#4073)
* fix(zsh): move 'fc -RI' inside command substitution

* chore: follow existing option check format
2024-11-02 10:41:17 +09:00
LangLangBart
d938fdc496 fix(zsh): history loading with shared option (#4071)
Fix #4061
2024-11-01 00:19:47 +09:00
Junegunn Choi
dcb4c3d84a Fix race in reload action
Fix #4070
2024-10-31 19:40:40 +09:00
Junegunn Choi
82ebcd9209 Fix (half-)page-up/down in the presence of multi-line items
Fix #4069
2024-10-30 16:52:42 +09:00
28 changed files with 1018 additions and 447 deletions

View File

@@ -7,4 +7,4 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: crate-ci/typos@v1.26.0
- uses: crate-ci/typos@v1.28.2

View File

@@ -1,6 +1,39 @@
CHANGELOG
=========
0.57.0
------
- You can now resize the preview window by dragging the border
- Built-in walker improvements
- `--walker-root` can take multiple directory arguments. e.g. `--walker-root include src lib`
- `--walker-skip` can handle multi-component patterns. e.g. `--walker-skip target/build`
- Removed long processing delay when displaying images in the preview window
- `FZF_PREVIEW_*` environment variables are exported to all child processes (#4098)
- Bug fixes in fish scripts
0.56.3
------
- Bug fixes in zsh scripts
- fix(zsh): handle backtick trigger edge case (#4090)
- revert(zsh): remove 'fc -RI' call in the history widget (#4093)
- Thanks to @LangLangBart for the contributions
0.56.2
------
- Bug fixes
- Fixed abnormal scrolling behavior when `--wrap` is set (#4083)
- [zsh] Fixed warning message when `ksh_arrays` is set (#4084)
0.56.1
------
- Bug fixes and improvements
- Fixed a race condition which would cause fzf to present stale results after `reload` (#4070)
- `page-up` and `page-down` actions now work correctly with multi-line items (#4069)
- `{n}` is allowed in `SCROLL` expression in `--preview-window` (#4079)
- [zsh] Fixed regression in history loading with shared option (#4071)
- [zsh] Better command extraction in zsh completion (#4082)
- Thanks to @LangLangBart, @jaydee-coder, @alex-huff, and @vejkse for the contributions
0.56.0
------
- Added `--gap[=N]` option to display empty lines between items.

View File

@@ -8,5 +8,5 @@ RUN echo '. ~/.bashrc' >> ~/.bash_profile
RUN rm -f /etc/bash.bashrc
COPY . /fzf
RUN cd /fzf && make install && ./install --all
ENV LANG C.UTF-8
CMD tmux new 'set -o pipefail; ruby /fzf/test/test_go.rb | tee out && touch ok' && cat out && [ -e ok ]
ENV LANG=C.UTF-8
CMD ["bash", "-ic", "tmux new 'set -o pipefail; ruby /fzf/test/test_go.rb | tee out && touch ok' && cat out && [ -e ok ]"]

File diff suppressed because one or more lines are too long

4
go.mod
View File

@@ -6,8 +6,8 @@ require (
github.com/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97
github.com/mattn/go-isatty v0.0.20
github.com/rivo/uniseg v0.4.7
golang.org/x/sys v0.26.0
golang.org/x/term v0.25.0
golang.org/x/sys v0.28.0
golang.org/x/term v0.27.0
)
require (

8
go.sum
View File

@@ -36,14 +36,14 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=

33
install
View File

@@ -2,7 +2,7 @@
set -u
version=0.56.0
version=0.57.0
auto_completion=
key_bindings=
update_config=2
@@ -295,35 +295,44 @@ EOF
fi
append_line() {
set -e
local update line file pat lno
local update line file pat lines
update="$1"
line="$2"
file="$3"
pat="${4:-}"
lno=""
lines=""
echo "Update $file:"
echo " - $line"
if [ -f "$file" ]; then
if [ $# -lt 4 ]; then
lno=$(\grep -nF "$line" "$file" | sed 's/:.*//' | tr '\n' ' ')
lines=$(\grep -nF "$line" "$file")
else
lno=$(\grep -nF "$pat" "$file" | sed 's/:.*//' | tr '\n' ' ')
lines=$(\grep -nF "$pat" "$file")
fi
fi
if [ -n "$lno" ]; then
echo " - Already exists: line #$lno"
else
if [ $update -eq 1 ]; then
if [ -n "$lines" ]; then
echo " - Already exists:"
sed 's/^/ Line /' <<< "$lines"
update=0
if ! \grep -qv "^[0-9]*:[[:space:]]*#" <<< "$lines" ; then
echo " - But they all seem to be commented"
ask " - Continue modifying $file?"
update=$?
fi
fi
set -e
if [ "$update" -eq 1 ]; then
[ -f "$file" ] && echo >> "$file"
echo "$line" >> "$file"
echo " + Added"
else
echo " ~ Skipped"
fi
fi
echo
set +e
}

View File

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

View File

@@ -11,7 +11,7 @@ import (
"github.com/junegunn/fzf/src/protector"
)
var version = "0.56"
var version = "0.57"
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 "Oct 2024" "fzf 0.56.0" "fzf\-tmux - open fzf in tmux split pane"
.TH fzf\-tmux 1 "Dec 2024" "fzf 0.57.0" "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 "Oct 2024" "fzf 0.56.0" "fzf - a command-line fuzzy finder"
.TH fzf 1 "Dec 2024" "fzf 0.57.0" "fzf - a command-line fuzzy finder"
.SH NAME
fzf - a command-line fuzzy finder
@@ -271,22 +271,23 @@ Adaptive height has the following limitations:
Minimum height when \fB\-\-height\fR is given in percent (default: 10).
Ignored when \fB\-\-height\fR is not specified.
.TP
.BI "\-\-tmux" "[=[center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]]"
Start fzf in a tmux popup (default \fBcenter,50%\fR). Requires tmux 3.3 or
later. This option is ignored if you are not running fzf inside tmux.
.BI "\-\-popup" "[=[center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]]"
Start fzf in a tmux popup or in a zellij floating pane (default
\fBcenter,50%\fR). Requires tmux 3.3+ or zellij. This option is ignored if you
are not running fzf inside tmux or zellij.
e.g.
\fB# Popup in the center with 70% width and height
fzf \-\-tmux 70%
fzf \-\-popup 70%
# Popup on the left with 40% width and 100% height
fzf \-\-tmux right,40%
fzf \-\-popup right,40%
# Popup on the bottom with 100% width and 30% height
fzf \-\-tmux bottom,30%
fzf \-\-popup bottom,30%
# Popup on the top with 80% width and 40% height
fzf \-\-tmux top,80%,40%\fR
fzf \-\-popup top,80%,40%\fR
.TP
.BI "\-\-layout=" "LAYOUT"
@@ -805,7 +806,7 @@ e.g. \fBborder\-rounded\fR (border with rounded edges, default),
* \fB[:+SCROLL[OFFSETS][/DENOM]]\fR determines the initial scroll offset of the
preview window.
- \fBSCROLL\fR can be either a numeric integer or a single-field index expression that refers to a numeric integer.
- \fBSCROLL\fR can be either a numeric integer or a single-field index expression that refers to a numeric integer or {n} to refer to the zero-based ordinal index of the current item.
- The optional \fBOFFSETS\fR part is for adjusting the base offset. It should be given as a series of signed integers (\fB\-INTEGER\fR or \fB+INTEGER\fR).
@@ -1005,8 +1006,8 @@ Determines the behavior of the built-in directory walker that is used when
.br
.TP
.B "\-\-walker\-root=DIR"
The root directory from which to start the built-in directory walker.
.B "\-\-walker\-root=DIR [...]"
List of directory names to start the built-in directory walker.
The default value is the current working directory.
.TP
@@ -1122,9 +1123,6 @@ fzf exports the following environment variables to its child processes.
.br
.BR FZF_PORT " Port number when \-\-listen option is used"
.br
The following variables are additionally exported to the preview commands.
.BR FZF_PREVIEW_TOP " Top position of the preview window"
.br
.BR FZF_PREVIEW_LEFT " Left position of the preview window"
@@ -1509,10 +1507,11 @@ A key or an event can be bound to one or more of the following actions.
\fBshow\-preview\fR
\fBtoggle\fR (\fIright\-click\fR)
\fBtoggle\-all\fR (toggle all matches)
\fBtoggle+down\fR \fIctrl\-i (tab)\fR
\fBtoggle\-header\fR
\fBtoggle\-in\fR (\fB\-\-layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
\fBtoggle\-out\fR (\fB\-\-layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
\fBtoggle\-header\fR
\fBtoggle\-hscroll\fR
\fBtoggle\-multi\-line\fR
\fBtoggle\-preview\fR
\fBtoggle\-preview\-wrap\fR
\fBtoggle\-search\fR (toggle search functionality)
@@ -1520,6 +1519,7 @@ A key or an event can be bound to one or more of the following actions.
\fBtoggle\-track\fR (toggle global tracking option (\fB\-\-track\fR))
\fBtoggle\-track\-current\fR (toggle tracking of the current item)
\fBtoggle\-wrap\fR \fIctrl\-/\fR \fIalt\-/\fR
\fBtoggle+down\fR \fIctrl\-i (tab)\fR
\fBtoggle+up\fR \fIbtab (shift\-tab)\fR
\fBtrack\-current\fR (track the current item; automatically disabled if focus changes)
\fBtransform(...)\fR (transform states using the output of an external command)

View File

@@ -120,25 +120,18 @@ __fzf_comprun() {
fi
}
# Extract the name of the command. e.g. foo=1 bar baz**<tab>
# Extract the name of the command. e.g. ls; foo=1 ssh **<tab>
__fzf_extract_command() {
local token tokens
tokens=(${(z)1})
for token in $tokens; do
token=${(Q)token}
if [[ "$token" =~ [[:alnum:]] && ! "$token" =~ "=" ]]; then
echo "$token"
return
fi
done
echo "${tokens[1]}"
# Control completion with the "compstate" parameter, insert and list nothing
compstate[insert]=
compstate[list]=
cmd_word="${(Q)words[1]}"
}
__fzf_generic_path_completion() {
local base lbuf cmd compgen fzf_opts suffix tail dir leftover matches
local base lbuf compgen fzf_opts suffix tail dir leftover matches
base=$1
lbuf=$2
cmd=$(__fzf_extract_command "$lbuf")
compgen=$3
fzf_opts=$4
suffix=$5
@@ -161,7 +154,7 @@ __fzf_generic_path_completion() {
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --scheme=path" "${FZF_COMPLETION_OPTS-}")
unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE
if declare -f "$compgen" > /dev/null; then
eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover"
eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd_word" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover"
else
if [[ $compgen =~ dir ]]; then
walker=dir,follow
@@ -170,7 +163,7 @@ __fzf_generic_path_completion() {
walker=file,dir,follow,hidden
rest=${FZF_COMPLETION_PATH_OPTS-}
fi
__fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" --walker "$walker" --walker-root="$dir" ${(Q)${(Z+n+)rest}} < /dev/tty
__fzf_comprun "$cmd_word" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" --walker "$walker" --walker-root="$dir" ${(Q)${(Z+n+)rest}} < /dev/tty
fi | while read -r item; do
item="${item%$suffix}$suffix"
echo -n -E "${(q)item} "
@@ -227,10 +220,9 @@ _fzf_complete() {
rest=("$@")
fi
local fifo lbuf cmd matches post
local fifo lbuf matches post
fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$"
lbuf=${rest[0]}
cmd=$(__fzf_extract_command "$lbuf")
post="${funcstack[1]}_post"
type $post > /dev/null 2>&1 || post=cat
@@ -238,7 +230,7 @@ _fzf_complete() {
matches=$(
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse" "${FZF_COMPLETION_OPTS-} $str_arg") \
FZF_DEFAULT_OPTS_FILE='' \
__fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
__fzf_comprun "$cmd_word" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
if [ -n "$matches" ]; then
LBUFFER="$lbuf$matches"
fi
@@ -310,7 +302,7 @@ _fzf_complete_kill_post() {
}
fzf-completion() {
local tokens cmd prefix trigger tail matches lbuf d_cmds
local tokens prefix trigger tail matches lbuf d_cmds cursor_pos cmd_word
setopt localoptions noshwordsplit noksh_arrays noposixbuiltins
# http://zsh.sourceforge.net/FAQ/zshfaq03.html
@@ -321,11 +313,9 @@ fzf-completion() {
return
fi
cmd=$(__fzf_extract_command "$LBUFFER")
# Explicitly allow for empty trigger.
trigger=${FZF_COMPLETION_TRIGGER-'**'}
[ -z "$trigger" -a ${LBUFFER[-1]} = ' ' ] && tokens+=("")
[[ -z $trigger && ${LBUFFER[-1]} == ' ' ]] && tokens+=("")
# When the trigger starts with ';', it becomes a separate token
if [[ ${LBUFFER} = *"${tokens[-2]-}${tokens[-1]}" ]]; then
@@ -340,16 +330,37 @@ fzf-completion() {
if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS-cd pushd rmdir})
{
cursor_pos=$CURSOR
# Move the cursor before the trigger to preserve word array elements when
# trigger chars like ';' or '`' would otherwise reset the 'words' array.
CURSOR=$((cursor_pos - ${#trigger} - 1))
# Check if at least one completion system (old or new) is active.
# If at least one user-defined completion widget is detected, nothing will
# be completed if neither the old nor the new completion system is enabled.
# In such cases, the 'zsh/compctl' module is loaded as a fallback.
if ! zmodload -F zsh/parameter p:functions 2>/dev/null || ! (( ${+functions[compdef]} )); then
zmodload -F zsh/compctl 2>/dev/null
fi
# Create a completion widget to access the 'words' array (man zshcompwid)
zle -C __fzf_extract_command .complete-word __fzf_extract_command
zle __fzf_extract_command
} always {
CURSOR=$cursor_pos
# Delete the completion widget
zle -D __fzf_extract_command 2>/dev/null
}
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
if [[ $prefix = *'$('* ]] || [[ $prefix = *'<('* ]] || [[ $prefix = *'>('* ]] || [[ $prefix = *':='* ]] || [[ $prefix = *'`'* ]]; then
return
fi
[ -n "${tokens[-1]}" ] && lbuf=${lbuf:0:-${#tokens[-1]}}
if eval "type _fzf_complete_${cmd} > /dev/null"; then
prefix="$prefix" eval _fzf_complete_${cmd} ${(q)lbuf}
if eval "noglob type _fzf_complete_${cmd_word} >/dev/null"; then
prefix="$prefix" eval _fzf_complete_${cmd_word} ${(q)lbuf}
zle reset-prompt
elif [ ${d_cmds[(i)$cmd]} -le ${#d_cmds} ]; then
elif [ ${d_cmds[(i)$cmd_word]} -le ${#d_cmds} ]; then
_fzf_dir_completion "$prefix" "$lbuf"
else
_fzf_path_completion "$prefix" "$lbuf"
@@ -366,6 +377,7 @@ fzf-completion() {
unset binding
}
# Normal widget
zle -N fzf-completion
bindkey '^I' fzf-completion
fi

View File

@@ -11,8 +11,6 @@
# - $FZF_ALT_C_COMMAND
# - $FZF_ALT_C_OPTS
status is-interactive; or exit 0
# Key bindings
# ------------
@@ -23,7 +21,7 @@ function fzf_key_bindings
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
echo "--height $FZF_TMUX_HEIGHT --bind=ctrl-z:ignore" $argv[1]
command cat "$FZF_DEFAULT_OPTS_FILE" 2> /dev/null
test -r "$FZF_DEFAULT_OPTS_FILE"; and string collect -N -- <$FZF_DEFAULT_OPTS_FILE
echo $FZF_DEFAULT_OPTS $argv[2]
end
@@ -36,12 +34,12 @@ function fzf_key_bindings
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "--reverse --walker=file,dir,follow,hidden --scheme=path --walker-root='$dir'" "$FZF_CTRL_T_OPTS")
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "--reverse --walker=file,dir,follow,hidden --scheme=path --walker-root=$dir" "$FZF_CTRL_T_OPTS")
set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND"
set -lx FZF_DEFAULT_OPTS_FILE ''
eval (__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
eval (__fzfcmd) -m --query=$fzf_query | while read -l r; set -a result $r; end
end
if [ -z "$result" ]
if test -z "$result"
commandline -f repaint
return
else
@@ -50,7 +48,7 @@ function fzf_key_bindings
end
for i in $result
commandline -it -- $prefix
commandline -it -- (string escape $i)
commandline -it -- (string escape -- $i)
commandline -it -- ' '
end
commandline -f repaint
@@ -59,27 +57,27 @@ function fzf_key_bindings
function fzf-history-widget -d "Show command history"
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin
set -l FISH_MAJOR (echo $version | cut -f1 -d.)
set -l FISH_MINOR (echo $version | cut -f2 -d.)
set -l FISH_MAJOR (string split -- '.' $version)[1]
set -l FISH_MINOR (string split -- '.' $version)[2]
# merge history from other sessions before searching
if test -z "$fish_private_mode"
builtin history merge
end
test -z "$fish_private_mode"; and builtin history merge
# history's -z flag is needed for multi-line support.
# history's -z flag was added in fish 2.4.0, so don't use it for versions
# before 2.4.0.
if [ "$FISH_MAJOR" -gt 2 -o \( "$FISH_MAJOR" -eq 2 -a "$FISH_MINOR" -ge 4 \) ];
if type -P perl > /dev/null 2>&1
if test "$FISH_MAJOR" -gt 2 -o \( "$FISH_MAJOR" -eq 2 -a "$FISH_MINOR" -ge 4 \)
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '"\t"↳ ' --highlight-line $FZF_CTRL_R_OPTS +m")
set -lx FZF_DEFAULT_OPTS_FILE ''
builtin history -z --reverse | command perl -0 -pe 's/^/$.\t/g; s/\n/\n\t/gm' | eval (__fzfcmd) --tac --read0 --print0 -q '(commandline)' | command perl -pe 's/^\d*\t//' | read -lz result
if type -q perl
builtin history -z --reverse | command perl -0 -pe 's/^/$.\t/g; s/\n/\n\t/gm' | eval (__fzfcmd) --tac --read0 --print0 -q '(commandline)' | string replace -r '^\d*\t' '' | read -lz result
and commandline -- $result
else
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "" "--scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '"\t"↳ ' --highlight-line $FZF_CTRL_R_OPTS +m")
set -lx FZF_DEFAULT_OPTS_FILE ''
builtin history -z | eval (__fzfcmd) --read0 --print0 -q '(commandline)' | read -lz result
set -l line 0
for i in (builtin history -z --reverse | string split0)
set line (math $line + 1)
string escape -n -- $line\t$i
end | string join0 | string replace -a '\n' '\n\t' | string unescape -n | eval (__fzfcmd) --tac --read0 --print0 -q '(commandline)' | string replace -r '^\d*\t' '' | read -lz result
and commandline -- $result
end
else
@@ -98,12 +96,12 @@ function fzf_key_bindings
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "--reverse --walker=dir,follow,hidden --scheme=path --walker-root='$dir'" "$FZF_ALT_C_OPTS")
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "--reverse --walker=dir,follow,hidden --scheme=path --walker-root=$dir" "$FZF_ALT_C_OPTS")
set -lx FZF_DEFAULT_OPTS_FILE ''
set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND"
eval (__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
eval (__fzfcmd) +m --query=$fzf_query | read -l result
if [ -n "$result" ]
if test -n "$result"
cd -- $result
# Remove last token from commandline.
@@ -118,9 +116,9 @@ function fzf_key_bindings
function __fzfcmd
test -n "$FZF_TMUX"; or set FZF_TMUX 0
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
if [ -n "$FZF_TMUX_OPTS" ]
if test -n "$FZF_TMUX_OPTS"
echo "fzf-tmux $FZF_TMUX_OPTS -- "
else if [ $FZF_TMUX -eq 1 ]
else if test "$FZF_TMUX" = "1"
echo "fzf-tmux -d$FZF_TMUX_HEIGHT -- "
else
echo "fzf"
@@ -135,7 +133,7 @@ function fzf_key_bindings
bind \ec fzf-cd-widget
end
if bind -M insert > /dev/null 2>&1
if bind -M insert &> /dev/null
bind -M insert \cr fzf-history-widget
if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND"
bind -M insert \ct fzf-file-widget
@@ -152,40 +150,53 @@ function fzf_key_bindings
set -l prefix (string match -r -- '^-[^\s=]+=' $commandline)
set commandline (string replace -- "$prefix" '' $commandline)
# Enable home directory expansion of leading ~/
set commandline (string replace -r -- '^~/' '\$HOME/' $commandline)
# escape special characters, except for the $ sign of valid variable names,
# so that after eval, the original string is returned, but with the
# variable names replaced by their values.
set commandline (string escape -n -- $commandline)
set commandline (string replace -r -a -- '\x5c\$(?=[\w])' '\$' $commandline)
# eval is used to do shell expansion on paths
eval set commandline $commandline
if [ -z $commandline ]
# Combine multiple consecutive slashes into one
set commandline (string replace -r -a -- '/+' '/' $commandline)
if test -z "$commandline"
# Default to current directory with no --query
set dir '.'
set fzf_query ''
else
set dir (__fzf_get_dir $commandline)
if [ "$dir" = "." -a (string sub -l 1 -- $commandline) != '.' ]
# BUG: on combined expressions, if a left argument is a single `!`, the
# builtin test command of fish will treat it as the ! operator. To
# overcome this, have the variable parts on the right.
if test "." = "$dir" -a "./" != (string sub -l 2 -- $commandline)
# if $dir is "." but commandline is not a relative path, this means no file path found
set fzf_query $commandline
else
# Also remove trailing slash after dir, to "split" input properly
set fzf_query (string replace -r "^$dir/?" -- '' "$commandline")
set fzf_query (string replace -r -- "^$dir/?" '' $commandline)
end
end
echo $dir
echo $fzf_query
echo (string escape -- $dir)
echo (string escape -- $fzf_query)
echo $prefix
end
function __fzf_get_dir -d 'Find the longest existing filepath from input string'
set dir $argv
# Strip all trailing slashes. Ignore if $dir is root dir (/)
if [ (string length -- $dir) -gt 1 ]
set dir (string replace -r '/*$' -- '' $dir)
end
# Strip trailing slash, unless $dir is root dir (/)
set dir (string replace -r -- '(?<!^)/$' '' $dir)
# Iteratively check if dir exists and strip tail end of path
while [ ! -d "$dir" ]
while test ! -d "$dir"
# If path is absolute, this can keep going until ends up at /
# If path is relative, this can keep going until entire input is consumed, dirname returns "."
set dir (dirname -- "$dir")

View File

@@ -108,9 +108,10 @@ fi
fzf-history-widget() {
local selected
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases noglob nobash_rematch 2> /dev/null
# Ensure the associative history array, which maps event numbers to the full
# history lines, is loaded, and that Perl is installed for multi-line output.
if zmodload -F zsh/parameter p:history 2>/dev/null && (( ${#commands[perl]} )); then
# Ensure the module is loaded if not already, and the required features, such
# as the associative 'history' array, which maps event numbers to full history
# lines, are set. Also, make sure Perl is installed for multi-line output.
if zmodload -F zsh/parameter p:{commands,history} 2>/dev/null && (( ${+commands[perl]} )); then
selected="$(printf '%s\t%s\000' "${(kv)history[@]}" |
perl -0 -ne 'if (!$seen{(/^\s*[0-9]+\**\t(.*)/s, $1)}++) { s/\n/\n\t/g; print; }' |
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '\t↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m --read0") \

View File

@@ -59,73 +59,75 @@ func _() {
_ = x[actToggleTrackCurrent-48]
_ = x[actToggleHeader-49]
_ = x[actToggleWrap-50]
_ = x[actTrackCurrent-51]
_ = x[actUntrackCurrent-52]
_ = x[actDown-53]
_ = x[actUp-54]
_ = x[actPageUp-55]
_ = x[actPageDown-56]
_ = x[actPosition-57]
_ = x[actHalfPageUp-58]
_ = x[actHalfPageDown-59]
_ = x[actOffsetUp-60]
_ = x[actOffsetDown-61]
_ = x[actOffsetMiddle-62]
_ = x[actJump-63]
_ = x[actJumpAccept-64]
_ = x[actPrintQuery-65]
_ = x[actRefreshPreview-66]
_ = x[actReplaceQuery-67]
_ = x[actToggleSort-68]
_ = x[actShowPreview-69]
_ = x[actHidePreview-70]
_ = x[actTogglePreview-71]
_ = x[actTogglePreviewWrap-72]
_ = x[actTransform-73]
_ = x[actTransformBorderLabel-74]
_ = x[actTransformHeader-75]
_ = x[actTransformPreviewLabel-76]
_ = x[actTransformPrompt-77]
_ = x[actTransformQuery-78]
_ = x[actPreview-79]
_ = x[actChangePreview-80]
_ = x[actChangePreviewWindow-81]
_ = x[actPreviewTop-82]
_ = x[actPreviewBottom-83]
_ = x[actPreviewUp-84]
_ = x[actPreviewDown-85]
_ = x[actPreviewPageUp-86]
_ = x[actPreviewPageDown-87]
_ = x[actPreviewHalfPageUp-88]
_ = x[actPreviewHalfPageDown-89]
_ = x[actPrevHistory-90]
_ = x[actPrevSelected-91]
_ = x[actPrint-92]
_ = x[actPut-93]
_ = x[actNextHistory-94]
_ = x[actNextSelected-95]
_ = x[actExecute-96]
_ = x[actExecuteSilent-97]
_ = x[actExecuteMulti-98]
_ = x[actSigStop-99]
_ = x[actFirst-100]
_ = x[actLast-101]
_ = x[actReload-102]
_ = x[actReloadSync-103]
_ = x[actDisableSearch-104]
_ = x[actEnableSearch-105]
_ = x[actSelect-106]
_ = x[actDeselect-107]
_ = x[actUnbind-108]
_ = x[actRebind-109]
_ = x[actBecome-110]
_ = x[actShowHeader-111]
_ = x[actHideHeader-112]
_ = x[actToggleMultiLine-51]
_ = x[actToggleHscroll-52]
_ = x[actTrackCurrent-53]
_ = x[actUntrackCurrent-54]
_ = x[actDown-55]
_ = x[actUp-56]
_ = x[actPageUp-57]
_ = x[actPageDown-58]
_ = x[actPosition-59]
_ = x[actHalfPageUp-60]
_ = x[actHalfPageDown-61]
_ = x[actOffsetUp-62]
_ = x[actOffsetDown-63]
_ = x[actOffsetMiddle-64]
_ = x[actJump-65]
_ = x[actJumpAccept-66]
_ = x[actPrintQuery-67]
_ = x[actRefreshPreview-68]
_ = x[actReplaceQuery-69]
_ = x[actToggleSort-70]
_ = x[actShowPreview-71]
_ = x[actHidePreview-72]
_ = x[actTogglePreview-73]
_ = x[actTogglePreviewWrap-74]
_ = x[actTransform-75]
_ = x[actTransformBorderLabel-76]
_ = x[actTransformHeader-77]
_ = x[actTransformPreviewLabel-78]
_ = x[actTransformPrompt-79]
_ = x[actTransformQuery-80]
_ = x[actPreview-81]
_ = x[actChangePreview-82]
_ = x[actChangePreviewWindow-83]
_ = x[actPreviewTop-84]
_ = x[actPreviewBottom-85]
_ = x[actPreviewUp-86]
_ = x[actPreviewDown-87]
_ = x[actPreviewPageUp-88]
_ = x[actPreviewPageDown-89]
_ = x[actPreviewHalfPageUp-90]
_ = x[actPreviewHalfPageDown-91]
_ = x[actPrevHistory-92]
_ = x[actPrevSelected-93]
_ = x[actPrint-94]
_ = x[actPut-95]
_ = x[actNextHistory-96]
_ = x[actNextSelected-97]
_ = x[actExecute-98]
_ = x[actExecuteSilent-99]
_ = x[actExecuteMulti-100]
_ = x[actSigStop-101]
_ = x[actFirst-102]
_ = x[actLast-103]
_ = x[actReload-104]
_ = x[actReloadSync-105]
_ = x[actDisableSearch-106]
_ = x[actEnableSearch-107]
_ = x[actSelect-108]
_ = x[actDeselect-109]
_ = x[actUnbind-110]
_ = x[actRebind-111]
_ = x[actBecome-112]
_ = x[actShowHeader-113]
_ = x[actHideHeader-114]
}
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactShowHeaderactHideHeader"
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactShowHeaderactHideHeader"
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 256, 277, 292, 306, 320, 333, 350, 358, 371, 387, 399, 407, 421, 435, 446, 457, 475, 492, 499, 518, 530, 544, 553, 568, 580, 593, 604, 615, 627, 641, 662, 677, 690, 705, 722, 729, 734, 743, 754, 765, 778, 793, 804, 817, 832, 839, 852, 865, 882, 897, 910, 924, 938, 954, 974, 986, 1009, 1027, 1051, 1069, 1086, 1096, 1112, 1134, 1147, 1163, 1175, 1189, 1205, 1223, 1243, 1265, 1279, 1294, 1302, 1308, 1322, 1337, 1347, 1363, 1378, 1388, 1396, 1403, 1412, 1425, 1441, 1456, 1465, 1476, 1485, 1494, 1503, 1516, 1529}
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 256, 277, 292, 306, 320, 333, 350, 358, 371, 387, 399, 407, 421, 435, 446, 457, 475, 492, 499, 518, 530, 544, 553, 568, 580, 593, 604, 615, 627, 641, 662, 677, 690, 708, 724, 739, 756, 763, 768, 777, 788, 799, 812, 827, 838, 851, 866, 873, 886, 899, 916, 931, 944, 958, 972, 988, 1008, 1020, 1043, 1061, 1085, 1103, 1120, 1130, 1146, 1168, 1181, 1197, 1209, 1223, 1239, 1257, 1277, 1299, 1313, 1328, 1336, 1342, 1356, 1371, 1381, 1397, 1412, 1422, 1430, 1437, 1446, 1459, 1475, 1490, 1499, 1510, 1519, 1528, 1537, 1550, 1563}
func (i actionType) String() string {
if i < 0 || i >= actionType(len(_actionType_index)-1) {

View File

@@ -39,9 +39,14 @@ func (r revision) compatible(other revision) bool {
// Run starts fzf
func Run(opts *Options) (int, error) {
if opts.Filter == nil {
if opts.Tmux != nil && len(os.Getenv("TMUX")) > 0 && opts.Tmux.index >= opts.Height.index {
if opts.Tmux != nil && opts.Tmux.index >= opts.Height.index {
if len(os.Getenv("TMUX")) > 0 {
return runTmux(os.Args, opts)
}
if len(os.Getenv("ZELLIJ")) > 0 {
return runZellij(os.Args, opts)
}
}
if needWinpty(opts) {
return runWinpty(os.Args, opts)
@@ -172,7 +177,9 @@ func Run(opts *Options) (int, error) {
return chunkList.Push(data)
}, eventBox, executor, opts.ReadZero, opts.Filter == nil)
go reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv)
readyChan := make(chan bool)
go reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv, readyChan)
<-readyChan
}
// Matcher
@@ -224,7 +231,7 @@ func Run(opts *Options) (int, error) {
}
return false
}, eventBox, executor, opts.ReadZero, false)
reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv)
reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv, nil)
} else {
eventBox.Unwatch(EvtReadNew)
eventBox.WaitFor(EvtReadFin)
@@ -299,7 +306,9 @@ func Run(opts *Options) (int, error) {
itemIndex = 0
inputRevision.bumpMajor()
header = make([]string, 0, opts.HeaderLines)
go reader.restart(command, environ)
readyChan := make(chan bool)
go reader.restart(command, environ, readyChan)
<-readyChan
}
exitCode := ExitOk

View File

@@ -75,7 +75,7 @@ Usage: fzf [options]
according to the input size.
--min-height=HEIGHT Minimum height when --height is given in percent
(default: 10)
--tmux[=OPTS] Start fzf in a tmux popup (requires tmux 3.3+)
--popup[=OPTS] Start fzf in a popup (requires tmux 3.3+ or zellij)
[center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]
(default: center,50%)
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
@@ -145,7 +145,7 @@ Usage: fzf [options]
Directory traversal (Only used when $FZF_DEFAULT_COMMAND is not set)
--walker=OPTS [file][,dir][,follow][,hidden] (default: file,follow,hidden)
--walker-root=DIR Root directory from which to start walker (default: .)
--walker-root=DIR [...] List of directories to walk (default: .)
--walker-skip=DIRS Comma-separated list of directory names to skip
(default: .git,node_modules)
@@ -299,7 +299,7 @@ func parseTmuxOptions(arg string, index int) (*tmuxOptions, error) {
var err error
opts := defaultTmuxOptions(index)
tokens := splitRegexp.Split(arg, -1)
errorToReturn := errors.New("invalid tmux option: " + arg + " (expected: [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]])")
errorToReturn := errors.New("invalid popup option: " + arg + " (expected: [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]])")
if len(tokens) == 0 || len(tokens) > 3 {
return nil, errorToReturn
}
@@ -381,14 +381,46 @@ func (a previewOpts) aboveOrBelow() bool {
return a.size.size > 0 && (a.position == posUp || a.position == posDown)
}
func (a previewOpts) sameLayout(b previewOpts) bool {
return a.size == b.size && a.position == b.position && a.border == b.border && a.hidden == b.hidden && a.threshold == b.threshold &&
(a.alternative != nil && b.alternative != nil && a.alternative.sameLayout(*b.alternative) ||
a.alternative == nil && b.alternative == nil)
}
type previewOptsCompare int
func (a previewOpts) sameContentLayout(b previewOpts) bool {
return a.wrap == b.wrap && a.headerLines == b.headerLines && a.info == b.info
const (
previewOptsSame previewOptsCompare = iota
previewOptsDifferentContentLayout
previewOptsDifferentLayout
)
func (o *previewOpts) compare(active *previewOpts, b *previewOpts) previewOptsCompare {
a := o
sameThreshold := o.position == b.position && o.threshold == b.threshold
// Alternative layout is being used
if o.alternative == active {
a = active
// If the other also has an alternative layout,
if b.alternative != nil {
// and if the same condition is the same, compare alt vs. alt.
if sameThreshold {
b = b.alternative
} else {
// If not, we pessimistically decide that the layouts may not be the same
return previewOptsDifferentLayout
}
}
} else if b.alternative != nil && !sameThreshold {
// We may choose the other's alternative layout, so let's be conservative.
return previewOptsDifferentLayout
}
if !(a.size == b.size && a.position == b.position && a.border == b.border && a.hidden == b.hidden) {
return previewOptsDifferentLayout
}
if a.wrap == b.wrap && a.headerLines == b.headerLines && a.info == b.info && a.scroll == b.scroll {
return previewOptsSame
}
return previewOptsDifferentContentLayout
}
func firstLine(s string) string {
@@ -490,7 +522,7 @@ type Options struct {
Unsafe bool
ClearOnExit bool
WalkerOpts walkerOpts
WalkerRoot string
WalkerRoot []string
WalkerSkip []string
Version bool
Help bool
@@ -594,7 +626,7 @@ func defaultOptions() *Options {
Unsafe: false,
ClearOnExit: true,
WalkerOpts: walkerOpts{file: true, hidden: true, follow: true},
WalkerRoot: ".",
WalkerRoot: []string{"."},
WalkerSkip: []string{".git", "node_modules"},
Help: false,
Version: false}
@@ -626,6 +658,28 @@ func optionalNextString(args []string, i *int) (bool, string) {
return false, ""
}
func isDir(path string) bool {
stat, err := os.Stat(path)
return err == nil && stat.IsDir()
}
func nextDirs(args []string, i *int) ([]string, error) {
dirs := []string{}
for *i < len(args)-1 {
arg := args[*i+1]
if isDir(arg) {
dirs = append(dirs, arg)
*i++
} else {
break
}
}
if len(dirs) == 0 {
return nil, errors.New("no directory specified")
}
return dirs, nil
}
func atoi(str string) (int, error) {
num, err := strconv.Atoi(str)
if err != nil {
@@ -1377,6 +1431,10 @@ func parseActionList(masked string, original string, prevActions []*action, putA
appendAction(actToggleHeader)
case "toggle-wrap":
appendAction(actToggleWrap)
case "toggle-multi-line":
appendAction(actToggleMultiLine)
case "toggle-hscroll":
appendAction(actToggleHscroll)
case "show-header":
appendAction(actShowHeader)
case "hide-header":
@@ -1726,7 +1784,7 @@ func parsePreviewWindowImpl(opts *previewOpts, input string) error {
var err error
tokenRegex := regexp.MustCompile(`[:,]*(<([1-9][0-9]*)\(([^)<]+)\)|[^,:]+)`)
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
offsetRegex := regexp.MustCompile(`^(\+{-?[0-9]+})?([+-][0-9]+)*(-?/[1-9][0-9]*)?$`)
offsetRegex := regexp.MustCompile(`^(\+{(-?[0-9]+|n)})?([+-][0-9]+)*(-?/[1-9][0-9]*)?$`)
headerRegex := regexp.MustCompile("^~(0|[1-9][0-9]*)$")
tokens := tokenRegex.FindAllStringSubmatch(input, -1)
var alternative string
@@ -1973,7 +2031,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
opts.Version = true
case "--no-winpty":
opts.NoWinpty = true
case "--tmux":
case "--tmux", "--popup":
given, str := optionalNextString(allArgs, &i)
if given {
if opts.Tmux, err = parseTmuxOptions(str, index); err != nil {
@@ -1982,7 +2040,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
} else {
opts.Tmux = defaultTmuxOptions(index)
}
case "--no-tmux":
case "--no-tmux", "--no-popup":
opts.Tmux = nil
case "--force-tty-in":
// NOTE: We need this because `system('fzf --tmux < /dev/tty')` doesn't
@@ -2487,7 +2545,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
return err
}
case "--walker-root":
if opts.WalkerRoot, err = nextString(allArgs, &i, "directory required"); err != nil {
if opts.WalkerRoot, err = nextDirs(allArgs, &i); err != nil {
return err
}
case "--walker-skip":
@@ -2519,6 +2577,10 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if opts.FuzzyAlgo, err = parseAlgo(value); err != nil {
return err
}
} else if match, value := optString(arg, "--popup="); match {
if opts.Tmux, err = parseTmuxOptions(value, index); err != nil {
return err
}
} else if match, value := optString(arg, "--tmux="); match {
if opts.Tmux, err = parseTmuxOptions(value, index); err != nil {
return err
@@ -2685,7 +2747,11 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
return err
}
} else if match, value := optString(arg, "--walker-root="); match {
opts.WalkerRoot = value
if !isDir(value) {
return errors.New("not a directory: " + value)
}
dirs, _ := nextDirs(allArgs, &i)
opts.WalkerRoot = append([]string{value}, dirs...)
} else if match, value := optString(arg, "--walker-skip="); match {
opts.WalkerSkip = filterNonEmpty(strings.Split(value, ","))
} else if match, value := optString(arg, "--hscroll-off="); match {

View File

@@ -6,8 +6,8 @@ import (
"io"
"io/fs"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"sync/atomic"
"time"
@@ -25,16 +25,26 @@ type Reader struct {
event int32
finChan chan bool
mutex sync.Mutex
exec *exec.Cmd
execOut io.ReadCloser
command *string
killed bool
termFunc func()
command *string
wait bool
}
// NewReader returns new Reader object
func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, executor *util.Executor, delimNil bool, wait bool) *Reader {
return &Reader{pusher, executor, eventBox, delimNil, int32(EvtReady), make(chan bool, 1), sync.Mutex{}, nil, nil, nil, false, wait}
return &Reader{
pusher,
executor,
eventBox,
delimNil,
int32(EvtReady),
make(chan bool, 1),
sync.Mutex{},
false,
func() { os.Stdin.Close() },
nil,
wait}
}
func (r *Reader) startEventPoller() {
@@ -80,19 +90,19 @@ func (r *Reader) fin(success bool) {
func (r *Reader) terminate() {
r.mutex.Lock()
r.killed = true
if r.exec != nil && r.exec.Process != nil {
r.execOut.Close()
util.KillCommand(r.exec)
} else {
os.Stdin.Close()
if r.termFunc != nil {
r.termFunc()
r.termFunc = nil
}
r.mutex.Unlock()
}
func (r *Reader) restart(command commandSpec, environ []string) {
func (r *Reader) restart(command commandSpec, environ []string, readyChan chan bool) {
r.event = int32(EvtReady)
r.startEventPoller()
success := r.readFromCommand(command.command, environ)
success := r.readFromCommand(command.command, environ, func() {
readyChan <- true
})
r.fin(success)
removeFiles(command.tempFiles)
}
@@ -111,21 +121,29 @@ func (r *Reader) readChannel(inputChan chan string) bool {
}
// ReadSource reads data from the default command or from standard input
func (r *Reader) ReadSource(inputChan chan string, root string, opts walkerOpts, ignores []string, initCmd string, initEnv []string) {
func (r *Reader) ReadSource(inputChan chan string, roots []string, opts walkerOpts, ignores []string, initCmd string, initEnv []string, readyChan chan bool) {
r.startEventPoller()
var success bool
signalReady := func() {
if readyChan != nil {
readyChan <- true
}
}
if inputChan != nil {
signalReady()
success = r.readChannel(inputChan)
} else if len(initCmd) > 0 {
success = r.readFromCommand(initCmd, initEnv)
success = r.readFromCommand(initCmd, initEnv, signalReady)
} else if util.IsTty(os.Stdin) {
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
if len(cmd) == 0 {
success = r.readFiles(root, opts, ignores)
signalReady()
success = r.readFiles(roots, opts, ignores)
} else {
success = r.readFromCommand(cmd, initEnv)
success = r.readFromCommand(cmd, initEnv, signalReady)
}
} else {
signalReady()
success = r.readFromStdin()
}
r.fin(success)
@@ -248,14 +266,33 @@ func trimPath(path string) string {
return byteString(bytes)
}
func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool {
r.killed = false
func (r *Reader) readFiles(roots []string, opts walkerOpts, ignores []string) bool {
conf := fastwalk.Config{
Follow: opts.follow,
// Use forward slashes when running a Windows binary under WSL or MSYS
ToSlash: fastwalk.DefaultToSlash(),
Sort: fastwalk.SortFilesFirst,
}
ignoresBase := []string{}
ignoresFull := []string{}
ignoresSuffix := []string{}
sep := string(os.PathSeparator)
for _, ignore := range ignores {
if strings.ContainsRune(ignore, os.PathSeparator) {
if strings.HasPrefix(ignore, sep) {
ignoresSuffix = append(ignoresSuffix, ignore)
} else {
// 'foo/bar' should match match
// * 'foo/bar'
// * 'baz/foo/bar'
// * but NOT 'bazfoo/bar'
ignoresFull = append(ignoresFull, ignore)
ignoresSuffix = append(ignoresSuffix, sep+ignore)
}
} else {
ignoresBase = append(ignoresBase, ignore)
}
}
fn := func(path string, de os.DirEntry, err error) error {
if err != nil {
return nil
@@ -268,11 +305,21 @@ func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool
if !opts.hidden && base[0] == '.' && base != ".." {
return filepath.SkipDir
}
for _, ignore := range ignores {
for _, ignore := range ignoresBase {
if ignore == base {
return filepath.SkipDir
}
}
for _, ignore := range ignoresFull {
if ignore == path {
return filepath.SkipDir
}
}
for _, ignore := range ignoresSuffix {
if strings.HasSuffix(path, ignore) {
return filepath.SkipDir
}
}
}
if ((opts.file && !isDir) || (opts.dir && isDir)) && r.pusher(stringBytes(path)) {
atomic.StoreInt32(&r.event, int32(EvtReadNew))
@@ -285,34 +332,39 @@ func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool
}
return nil
}
return fastwalk.Walk(&conf, root, fn) == nil
noerr := true
for _, root := range roots {
noerr = noerr && (fastwalk.Walk(&conf, root, fn) == nil)
}
return noerr
}
func (r *Reader) readFromCommand(command string, environ []string) bool {
func (r *Reader) readFromCommand(command string, environ []string, signalReady func()) bool {
r.mutex.Lock()
r.killed = false
r.termFunc = nil
r.command = &command
r.exec = r.executor.ExecCommand(command, true)
exec := r.executor.ExecCommand(command, true)
if environ != nil {
r.exec.Env = environ
exec.Env = environ
}
var err error
r.execOut, err = r.exec.StdoutPipe()
if err != nil {
r.exec = nil
execOut, err := exec.StdoutPipe()
if err != nil || exec.Start() != nil {
signalReady()
r.mutex.Unlock()
return false
}
err = r.exec.Start()
if err != nil {
r.exec = nil
r.mutex.Unlock()
return false
// Function to call to terminate the running command
r.termFunc = func() {
execOut.Close()
util.KillCommand(exec)
}
signalReady()
r.mutex.Unlock()
r.feed(r.execOut)
return r.exec.Wait() == nil
r.feed(execOut)
return exec.Wait() == nil
}

View File

@@ -23,8 +23,12 @@ func TestReadFromCommand(t *testing.T) {
}
// Normal command
reader.fin(reader.readFromCommand(`echo abc&&echo def`, nil))
if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" {
counter := 0
ready := func() {
counter++
}
reader.fin(reader.readFromCommand(`echo abc&&echo def`, nil, ready))
if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" || counter != 1 {
t.Errorf("%s", strs)
}
@@ -48,9 +52,9 @@ func TestReadFromCommand(t *testing.T) {
reader.startEventPoller()
// Failing command
reader.fin(reader.readFromCommand(`no-such-command`, nil))
reader.fin(reader.readFromCommand(`no-such-command`, nil, ready))
strs = []string{}
if len(strs) > 0 {
if len(strs) > 0 || counter != 2 {
t.Errorf("%s", strs)
}

View File

@@ -51,7 +51,8 @@ var placeholder *regexp.Regexp
var whiteSuffix *regexp.Regexp
var offsetComponentRegex *regexp.Regexp
var offsetTrimCharsRegex *regexp.Regexp
var passThroughRegex *regexp.Regexp
var passThroughBeginRegex *regexp.Regexp
var passThroughEndTmuxRegex *regexp.Regexp
var ttyin *os.File
const clearCode string = "\x1b[2J"
@@ -74,7 +75,15 @@ func init() {
// * https://sw.kovidgoyal.net/kitty/graphics-protocol
// * https://en.wikipedia.org/wiki/Sixel
// * https://iterm2.com/documentation-images.html
passThroughRegex = regexp.MustCompile(`\x1bPtmux;\x1b\x1b.*?[^\x1b]\x1b\\|\x1b(_G|P[0-9;]*q).*?\x1b\\\r?|\x1b]1337;.*?(\a|\x1b\\)`)
/*
passThroughRegex = regexp.MustCompile(`
\x1bPtmux;\x1b\x1b .*? [^\x1b]\x1b\\
| \x1b(_G|P[0-9;]*q) .*? \x1b\\\r?
| \x1b]1337; .*? (\a|\x1b\\)
`)
*/
passThroughBeginRegex = regexp.MustCompile(`\x1bPtmux;\x1b\x1b|\x1b(_G|P[0-9;]*q)|\x1b]1337;`)
passThroughEndTmuxRegex = regexp.MustCompile(`[^\x1b]\x1b\\`)
}
type jumpMode int
@@ -136,6 +145,7 @@ type previewer struct {
following resumableState
spinner string
bar []bool
xw [2]int
}
type previewed struct {
@@ -453,6 +463,8 @@ const (
actToggleTrackCurrent
actToggleHeader
actToggleWrap
actToggleMultiLine
actToggleHscroll
actTrackCurrent
actUntrackCurrent
actDown
@@ -561,8 +573,6 @@ type searchRequest struct {
type previewRequest struct {
template string
pwindow tui.Window
pwindowSize tui.TermSize
scrollOffset int
list []*Item
env []string
@@ -849,7 +859,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
reqBox: util.NewEventBox(),
initialPreviewOpts: opts.Preview,
previewOpts: opts.Preview,
previewer: previewer{0, []string{}, 0, false, true, disabledState, "", []bool{}},
activePreviewOpts: &opts.Preview,
previewer: previewer{0, []string{}, 0, false, true, disabledState, "", []bool{}, [2]int{0, 0}},
previewed: previewed{0, 0, 0, false, false, false, false},
previewBox: previewBox,
eventBox: eventBox,
@@ -970,17 +981,43 @@ func (t *Terminal) environ() []string {
env = append(env, fmt.Sprintf("FZF_POS=%d", util.Min(t.merger.Length(), t.cy+1)))
env = append(env, fmt.Sprintf("FZF_CLICK_HEADER_LINE=%d", t.clickHeaderLine))
env = append(env, fmt.Sprintf("FZF_CLICK_HEADER_COLUMN=%d", t.clickHeaderColumn))
// Add preview environment variables if preview is enabled
pwindowSize := t.pwindowSize()
if pwindowSize.Lines > 0 {
lines := fmt.Sprintf("LINES=%d", pwindowSize.Lines)
columns := fmt.Sprintf("COLUMNS=%d", pwindowSize.Columns)
env = append(env, lines)
env = append(env, "FZF_PREVIEW_"+lines)
env = append(env, columns)
env = append(env, "FZF_PREVIEW_"+columns)
env = append(env, fmt.Sprintf("FZF_PREVIEW_TOP=%d", t.tui.Top()+t.pwindow.Top()))
env = append(env, fmt.Sprintf("FZF_PREVIEW_LEFT=%d", t.pwindow.Left()))
}
return env
}
func borderLines(shape tui.BorderShape) int {
switch shape {
case tui.BorderHorizontal, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble:
return 2
case tui.BorderTop, tui.BorderBottom:
return 1
lines := 0
if shape.HasTop() {
lines++
}
return 0
if shape.HasBottom() {
lines++
}
return lines
}
func borderColumns(shape tui.BorderShape, borderWidth int) int {
columns := 0
if shape.HasLeft() {
columns += 1 + borderWidth
}
if shape.HasRight() {
columns += 1 + borderWidth
}
return columns
}
func (t *Terminal) visibleHeaderLines() int {
@@ -1383,7 +1420,7 @@ const (
minHeight = 3
)
func calculateSize(base int, size sizeSpec, occupied int, minSize int, pad int) int {
func calculateSize(base int, size sizeSpec, occupied int, minSize int) int {
max := base - occupied
if max < minSize {
max = minSize
@@ -1391,7 +1428,22 @@ func calculateSize(base int, size sizeSpec, occupied int, minSize int, pad int)
if size.percent {
return util.Constrain(int(float64(base)*0.01*size.size), minSize, max)
}
return util.Constrain(int(size.size)+pad, minSize, max)
return util.Constrain(int(size.size)+minSize-1, minSize, max)
}
func (t *Terminal) minPreviewSize(opts *previewOpts) (int, int) {
minPreviewWidth := 1 + borderColumns(opts.border, t.borderWidth)
minPreviewHeight := 1 + borderLines(opts.border)
switch opts.position {
case posLeft, posRight:
if len(t.scrollbar) > 0 && !opts.border.HasRight() {
// Need a column to show scrollbar
minPreviewWidth++
}
}
return minPreviewWidth, minPreviewHeight
}
func (t *Terminal) adjustMarginAndPadding() (int, int, [4]int, [4]int) {
@@ -1465,9 +1517,8 @@ func (t *Terminal) adjustMarginAndPadding() (int, int, [4]int, [4]int) {
minAreaHeight -= 1
}
if t.needPreviewWindow() {
minPreviewHeight := 1 + borderLines(t.previewOpts.border)
minPreviewWidth := 5
switch t.previewOpts.position {
minPreviewWidth, minPreviewHeight := t.minPreviewSize(t.activePreviewOpts)
switch t.activePreviewOpts.position {
case posUp, posDown:
minAreaHeight += minPreviewHeight
minAreaWidth = util.Max(minPreviewWidth, minAreaWidth)
@@ -1482,61 +1533,48 @@ func (t *Terminal) adjustMarginAndPadding() (int, int, [4]int, [4]int) {
return screenWidth, screenHeight, marginInt, paddingInt
}
func (t *Terminal) resizeWindows(forcePreview bool) {
func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
t.forcePreview = forcePreview
screenWidth, screenHeight, marginInt, paddingInt := t.adjustMarginAndPadding()
width := screenWidth - marginInt[1] - marginInt[3]
height := screenHeight - marginInt[0] - marginInt[2]
t.prevLines = make([]itemLine, screenHeight)
if t.border != nil {
t.border.Close()
t.prevLines = make([]itemLine, util.Max(1, screenHeight))
if t.border != nil && redrawBorder {
t.border = nil
}
if t.window != nil {
t.window.Close()
t.window = nil
}
if t.pborder != nil {
t.pborder.Close()
t.pborder = nil
}
hadPreviewWindow := t.hasPreviewWindow()
if hadPreviewWindow {
t.pwindow.Close()
t.pwindow = nil
}
// Reset preview version so that full redraw occurs
t.previewed.version = 0
bw := t.borderWidth
switch t.borderShape {
case tui.BorderHorizontal:
offsets := [4]int{} // TRWH
if t.borderShape.HasTop() {
offsets[0] -= 1
offsets[3] += 1
}
if t.borderShape.HasRight() {
offsets[2] += 1 + bw
}
if t.borderShape.HasBottom() {
offsets[3] += 1
}
if t.borderShape.HasLeft() {
offsets[1] -= 1 + bw
offsets[2] += 1 + bw
}
if t.border == nil && t.borderShape != tui.BorderNone {
t.border = t.tui.NewWindow(
marginInt[0]-1, marginInt[3], width, height+2,
false, tui.MakeBorderStyle(tui.BorderHorizontal, t.unicode))
case tui.BorderVertical:
t.border = t.tui.NewWindow(
marginInt[0], marginInt[3]-(1+bw), width+(1+bw)*2, height,
false, tui.MakeBorderStyle(tui.BorderVertical, t.unicode))
case tui.BorderTop:
t.border = t.tui.NewWindow(
marginInt[0]-1, marginInt[3], width, height+1,
false, tui.MakeBorderStyle(tui.BorderTop, t.unicode))
case tui.BorderBottom:
t.border = t.tui.NewWindow(
marginInt[0], marginInt[3], width, height+1,
false, tui.MakeBorderStyle(tui.BorderBottom, t.unicode))
case tui.BorderLeft:
t.border = t.tui.NewWindow(
marginInt[0], marginInt[3]-(1+bw), width+(1+bw), height,
false, tui.MakeBorderStyle(tui.BorderLeft, t.unicode))
case tui.BorderRight:
t.border = t.tui.NewWindow(
marginInt[0], marginInt[3], width+(1+bw), height,
false, tui.MakeBorderStyle(tui.BorderRight, t.unicode))
case tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble:
t.border = t.tui.NewWindow(
marginInt[0]-1, marginInt[3]-(1+bw), width+(1+bw)*2, height+2,
marginInt[0]+offsets[0], marginInt[3]+offsets[1], width+offsets[2], height+offsets[3],
false, tui.MakeBorderStyle(t.borderShape, t.unicode))
}
@@ -1563,35 +1601,15 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
createPreviewWindow := func(y int, x int, w int, h int) {
pwidth := w
pheight := h
var previewBorder tui.BorderStyle
if previewOpts.border == tui.BorderNone {
previewBorder = tui.MakeTransparentBorder()
} else {
previewBorder = tui.MakeBorderStyle(previewOpts.border, t.unicode)
}
previewBorder := tui.MakeBorderStyle(previewOpts.border, t.unicode)
t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
switch previewOpts.border {
case tui.BorderSharp, tui.BorderRounded, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble:
pwidth -= (1 + bw) * 2
pheight -= 2
pwidth -= borderColumns(previewOpts.border, bw)
pheight -= borderLines(previewOpts.border)
if previewOpts.border.HasLeft() {
x += 1 + bw
}
if previewOpts.border.HasTop() {
y += 1
case tui.BorderLeft:
pwidth -= 1 + bw
x += 1 + bw
case tui.BorderRight:
pwidth -= 1 + bw
case tui.BorderTop:
pheight -= 1
y += 1
case tui.BorderBottom:
pheight -= 1
case tui.BorderHorizontal:
pheight -= 2
y += 1
case tui.BorderVertical:
pwidth -= (1 + bw) * 2
x += 1 + bw
}
if len(t.scrollbar) > 0 && !previewOpts.border.HasRight() {
// Need a column to show scrollbar
@@ -1604,19 +1622,14 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
t.pwindow.Erase()
}
}
verticalPad := 2
minPreviewHeight := 3
switch previewOpts.border {
case tui.BorderNone, tui.BorderVertical, tui.BorderLeft, tui.BorderRight:
verticalPad = 0
minPreviewHeight = 1
case tui.BorderTop, tui.BorderBottom:
verticalPad = 1
minPreviewHeight = 2
}
minPreviewWidth, minPreviewHeight := t.minPreviewSize(previewOpts)
switch previewOpts.position {
case posUp, posDown:
pheight := calculateSize(height, previewOpts.size, minHeight, minPreviewHeight, verticalPad)
minWindowHeight := minHeight
if t.noSeparatorLine() {
minWindowHeight--
}
pheight := calculateSize(height, previewOpts.size, minWindowHeight, minPreviewHeight)
if hasThreshold && pheight < previewOpts.threshold {
t.activePreviewOpts = previewOpts.alternative
if forcePreview {
@@ -1643,7 +1656,7 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
}
case posLeft, posRight:
pwidth := calculateSize(width, previewOpts.size, minWidth, 5, 4)
pwidth := calculateSize(width, previewOpts.size, minWidth, minPreviewWidth)
if hasThreshold && pwidth < previewOpts.threshold {
t.activePreviewOpts = previewOpts.alternative
if forcePreview {
@@ -1678,13 +1691,13 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
} else {
// NOTE: fzf --preview 'cat {}' --preview-window border-left --border
if !previewOpts.border.HasRight() && t.borderShape.HasRight() {
width++
}
t.window = t.tui.NewWindow(
marginInt[0], marginInt[3], width-pwidth, height, false, noBorder)
// NOTE: fzf --preview 'cat {}' --preview-window border-left --border
x := marginInt[3] + width - pwidth
if !previewOpts.border.HasRight() && t.borderShape.HasRight() {
pwidth++
}
createPreviewWindow(marginInt[0], x, pwidth, height)
}
}
@@ -1709,7 +1722,7 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
// Print border label
t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, false)
t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.previewOpts.border, false)
t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.activePreviewOpts.border, false)
}
func (t *Terminal) printLabel(window tui.Window, render labelPrinter, opts labelOpts, length int, borderShape tui.BorderShape, redrawBorder bool) {
@@ -2102,9 +2115,9 @@ func (t *Terminal) printList() {
func (t *Terminal) printBar(lineNum int, forceRedraw bool, barRange [2]int) bool {
hasBar := lineNum >= barRange[0] && lineNum < barRange[1]
if len(t.scrollbar) > 0 && (hasBar != t.prevLines[lineNum].hasBar || forceRedraw) {
if hasBar != t.prevLines[lineNum].hasBar || forceRedraw {
t.move(lineNum, t.window.Width()-1, true)
if hasBar {
if len(t.scrollbar) > 0 && hasBar {
t.window.CPrint(tui.ColScrollbar, t.scrollbar)
}
}
@@ -2147,7 +2160,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
}
maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1)
postTask := func(lineNum int, width int, wrapped bool) {
postTask := func(lineNum int, width int, wrapped bool, forceRedraw bool) {
if (current || selected) && t.highlightLine {
color := tui.ColSelected
if current {
@@ -2162,7 +2175,12 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
}
newLine.width = maxWidth
} else {
fillSpaces := t.prevLines[lineNum].width - width
var fillSpaces int
if forceRedraw {
fillSpaces = maxWidth - width
} else {
fillSpaces = t.prevLines[lineNum].width - width
}
if wrapped {
fillSpaces -= t.wrapSignWidth
}
@@ -2274,7 +2292,7 @@ func (t *Terminal) overflow(runes []rune, max int) bool {
return t.displayWidthWithLimit(runes, 0, max) > max
}
func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMatch tui.ColorPair, current bool, match bool, lineNum int, maxLineNum int, forceRedraw bool, preTask func(markerClass), postTask func(int, int, bool)) int {
func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMatch tui.ColorPair, current bool, match bool, lineNum int, maxLineNum int, forceRedraw bool, preTask func(markerClass), postTask func(int, int, bool, bool)) int {
var displayWidth int
item := result.item
matchOffsets := []Offset{}
@@ -2367,7 +2385,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
if t.layout == layoutDefault {
actualLineNum = (lineNum - actualLineOffset) + (numItemLines - actualLineOffset) - 1
}
t.move(actualLineNum, 0, forceRedraw)
t.move(actualLineNum, 0, forceRedraw && postTask == nil)
if preTask != nil {
var marker markerClass
@@ -2471,7 +2489,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
t.printColoredString(t.window, line, offsets, colBase)
if postTask != nil {
postTask(actualLineNum, displayWidth, wasWrapped)
postTask(actualLineNum, displayWidth, wasWrapped, forceRedraw)
} else {
t.markOtherLine(actualLineNum)
}
@@ -2526,7 +2544,7 @@ func (t *Terminal) renderPreviewSpinner() {
spin := t.previewer.spinner
if len(spin) > 0 || t.previewer.scrollable {
maxWidth := t.pwindow.Width()
if !t.previewer.scrollable || !t.previewOpts.info {
if !t.previewer.scrollable || !t.activePreviewOpts.info {
if maxWidth > 0 {
t.pwindow.Move(0, maxWidth-1)
t.pwindow.CPrint(tui.ColPreviewSpinner, spin)
@@ -2568,7 +2586,7 @@ func (t *Terminal) renderPreviewArea(unchanged bool) {
height := t.pwindow.Height()
body := t.previewer.lines
headerLines := t.previewOpts.headerLines
headerLines := t.activePreviewOpts.headerLines
// Do not enable preview header lines if it's value is too large
if headerLines > 0 && headerLines < util.Min(len(body), height) {
header := t.previewer.lines[0:headerLines]
@@ -2610,6 +2628,67 @@ func (t *Terminal) makeImageBorder(width int, top bool) string {
return v + strings.Repeat(" ", repeat) + v
}
func findPassThrough(line string) []int {
loc := passThroughBeginRegex.FindStringIndex(line)
if loc == nil {
return nil
}
rest := line[loc[0]:]
after := line[loc[1]:]
if strings.HasPrefix(rest, "\x1bPtmux") { // Tmux
eloc := passThroughEndTmuxRegex.FindStringIndex(after)
if eloc == nil {
return nil
}
return []int{loc[0], loc[1] + eloc[1]}
} else if strings.HasPrefix(rest, "\x1b]1337;") { // iTerm2
index := loc[1]
for {
after := line[index:]
pos := strings.IndexAny(after, "\x1b\a")
if pos < 0 {
return nil
}
if after[pos] == '\a' {
return []int{loc[0], index + pos + 1}
}
if pos < len(after)-1 && after[pos+1] == '\\' {
return []int{loc[0], index + pos + 2}
}
index += pos + 1
}
}
// Kitty
pos := strings.Index(after, "\x1b\\")
if pos < 0 {
return nil
}
if pos < len(after)-2 && after[pos+2] == '\r' {
return []int{loc[0], loc[1] + pos + 3}
}
return []int{loc[0], loc[1] + pos + 2}
}
func extractPassThroughs(line string) ([]string, string) {
passThroughs := []string{}
transformed := ""
index := 0
for {
rest := line[index:]
loc := findPassThrough(rest)
if loc == nil {
transformed += rest
break
}
passThroughs = append(passThroughs, rest[loc[0]:loc[1]])
transformed += line[index : index+loc[0]]
index += loc[1]
}
return passThroughs, transformed
}
func (t *Terminal) renderPreviewText(height int, lines []string, lineNo int, unchanged bool) {
maxWidth := t.pwindow.Width()
var ansi *ansiState
@@ -2624,10 +2703,7 @@ Loop:
ansi.lbg = -1
}
passThroughs := passThroughRegex.FindAllString(line, -1)
if passThroughs != nil {
line = passThroughRegex.ReplaceAllString(line, "")
}
passThroughs, line := extractPassThroughs(line)
line = strings.TrimLeft(strings.TrimRight(line, "\r\n"), "\r")
if lineNo >= height || t.pwindow.Y() == height-1 && t.pwindow.X() > 0 {
@@ -2710,7 +2786,7 @@ Loop:
_, _, ansi = extractColor(line, ansi, func(str string, ansi *ansiState) bool {
trimmed := []rune(str)
isTrimmed := false
if !t.previewOpts.wrap {
if !t.activePreviewOpts.wrap {
trimmed, isTrimmed = t.trimRight(trimmed, maxWidth-t.pwindow.X())
}
if url == nil && ansi != nil && ansi.url != nil {
@@ -2736,7 +2812,7 @@ Loop:
}
}
return !isTrimmed &&
(fillRet == tui.FillContinue || t.previewOpts.wrap && fillRet == tui.FillNextLine)
(fillRet == tui.FillContinue || t.activePreviewOpts.wrap && fillRet == tui.FillNextLine)
})
if url != nil {
t.pwindow.LinkEnd()
@@ -2771,17 +2847,19 @@ Loop:
func (t *Terminal) renderPreviewScrollbar(yoff int, barLength int, barStart int) {
height := t.pwindow.Height()
w := t.pborder.Width()
xw := [2]int{t.pwindow.Left(), t.pwindow.Width()}
redraw := false
if len(t.previewer.bar) != height {
if len(t.previewer.bar) != height || t.previewer.xw != xw {
redraw = true
t.previewer.bar = make([]bool, height)
t.previewer.xw = xw
}
xshift := -1 - t.borderWidth
if !t.previewOpts.border.HasRight() {
if !t.activePreviewOpts.border.HasRight() {
xshift = -1
}
yshift := 1
if !t.previewOpts.border.HasTop() {
if !t.activePreviewOpts.border.HasTop() {
yshift = 0
}
for i := yoff; i < height; i++ {
@@ -2813,7 +2891,7 @@ func (t *Terminal) printPreview() {
unchanged := (t.previewed.filled || numLines == t.previewed.numLines) &&
t.previewer.version == t.previewed.version &&
t.previewer.offset == t.previewed.offset
t.previewer.scrollable = t.previewer.offset > t.previewOpts.headerLines || numLines > height
t.previewer.scrollable = t.previewer.offset > t.activePreviewOpts.headerLines || numLines > height
t.renderPreviewArea(unchanged)
t.renderPreviewSpinner()
t.previewed.numLines = numLines
@@ -2856,7 +2934,7 @@ func (t *Terminal) processTabs(runes []rune, prefixWidth int) (string, int) {
}
func (t *Terminal) printAll() {
t.resizeWindows(t.forcePreview)
t.resizeWindows(t.forcePreview, true)
t.printList()
t.printPrompt()
t.printInfo()
@@ -3030,7 +3108,7 @@ func (t *Terminal) evaluateScrollOffset() int {
}
// We only need the current item to calculate the scroll offset
replaced, tempFiles := t.replacePlaceholder(t.previewOpts.scroll, false, "", []*Item{t.currentItem(), nil})
replaced, tempFiles := t.replacePlaceholder(t.activePreviewOpts.scroll, false, "", []*Item{t.currentItem(), nil})
removeFiles(tempFiles)
offsetExpr := offsetTrimCharsRegex.ReplaceAllString(replaced, "")
@@ -3043,7 +3121,7 @@ func (t *Terminal) evaluateScrollOffset() int {
}
base := -1
height := util.Max(0, t.pwindow.Height()-t.previewOpts.headerLines)
height := util.Max(0, t.pwindow.Height()-t.activePreviewOpts.headerLines)
for _, component := range offsetComponentRegex.FindAllString(offsetExpr, -1) {
if strings.HasPrefix(component, "-/") {
component = component[1:]
@@ -3289,12 +3367,12 @@ func (t *Terminal) hasPreviewer() bool {
}
func (t *Terminal) needPreviewWindow() bool {
return t.hasPreviewer() && len(t.previewOpts.command) > 0 && t.previewOpts.Visible()
return t.hasPreviewer() && len(t.previewOpts.command) > 0 && t.activePreviewOpts.Visible()
}
// Check if previewer is currently in action (invisible previewer with size 0 or visible previewer)
func (t *Terminal) canPreview() bool {
return t.hasPreviewer() && (!t.previewOpts.Visible() && !t.previewOpts.hidden || t.hasPreviewWindow())
return t.hasPreviewer() && (!t.activePreviewOpts.Visible() && !t.activePreviewOpts.hidden || t.hasPreviewWindow())
}
func (t *Terminal) hasPreviewWindow() bool {
@@ -3423,16 +3501,16 @@ func (t *Terminal) Loop() error {
t.tui.Resize(func(termHeight int) int {
contentHeight := fit + t.extraLines()
if t.needPreviewWindow() {
if t.previewOpts.aboveOrBelow() {
if t.previewOpts.size.percent {
newContentHeight := int(float64(contentHeight) * 100. / (100. - t.previewOpts.size.size))
contentHeight = util.Max(contentHeight+1+borderLines(t.previewOpts.border), newContentHeight)
if t.activePreviewOpts.aboveOrBelow() {
if t.activePreviewOpts.size.percent {
newContentHeight := int(float64(contentHeight) * 100. / (100. - t.activePreviewOpts.size.size))
contentHeight = util.Max(contentHeight+1+borderLines(t.activePreviewOpts.border), newContentHeight)
} else {
contentHeight += int(t.previewOpts.size.size) + borderLines(t.previewOpts.border)
contentHeight += int(t.activePreviewOpts.size.size) + borderLines(t.activePreviewOpts.border)
}
} else {
// Minimum height if preview window can appear
contentHeight = util.Max(contentHeight, 1+borderLines(t.previewOpts.border))
contentHeight = util.Max(contentHeight, 1+borderLines(t.activePreviewOpts.border))
}
}
return util.Min(termHeight, contentHeight+pad)
@@ -3482,7 +3560,7 @@ func (t *Terminal) Loop() error {
return err
}
t.termSize = t.tui.Size()
t.resizeWindows(false)
t.resizeWindows(false, false)
t.window.Erase()
t.mutex.Unlock()
@@ -3519,8 +3597,6 @@ func (t *Terminal) Loop() error {
for {
var items []*Item
var commandTemplate string
var pwindow tui.Window
var pwindowSize tui.TermSize
var env []string
initialOffset := 0
t.previewBox.Wait(func(events *util.Events) {
@@ -3534,8 +3610,6 @@ func (t *Terminal) Loop() error {
commandTemplate = request.template
initialOffset = request.scrollOffset
items = request.list
pwindow = request.pwindow
pwindowSize = request.pwindowSize
env = request.env
}
}
@@ -3553,16 +3627,6 @@ func (t *Terminal) Loop() error {
_, query := t.Input()
command, tempFiles := t.replacePlaceholder(commandTemplate, false, string(query), items)
cmd := t.executor.ExecCommand(command, true)
if pwindowSize.Lines > 0 {
lines := fmt.Sprintf("LINES=%d", pwindowSize.Lines)
columns := fmt.Sprintf("COLUMNS=%d", pwindowSize.Columns)
env = append(env, lines)
env = append(env, "FZF_PREVIEW_"+lines)
env = append(env, columns)
env = append(env, "FZF_PREVIEW_"+columns)
env = append(env, fmt.Sprintf("FZF_PREVIEW_TOP=%d", t.tui.Top()+pwindow.Top()))
env = append(env, fmt.Sprintf("FZF_PREVIEW_LEFT=%d", pwindow.Left()))
}
cmd.Env = env
out, _ := cmd.StdoutPipe()
@@ -3689,7 +3753,7 @@ func (t *Terminal) Loop() error {
if len(command) > 0 && t.canPreview() {
_, list := t.buildPlusList(command, false)
t.cancelPreview()
t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.pwindow, t.pwindowSize(), t.evaluateScrollOffset(), list, t.environ()})
t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.evaluateScrollOffset(), list, t.environ()})
}
}
@@ -3794,7 +3858,7 @@ func (t *Terminal) Loop() error {
case reqRedrawBorderLabel:
t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, true)
case reqRedrawPreviewLabel:
t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.previewOpts.border, true)
t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.activePreviewOpts.border, true)
case reqReinit:
t.tui.Resume(t.fullscreen, true)
t.fullRedraw()
@@ -3822,7 +3886,7 @@ func (t *Terminal) Loop() error {
result := value.(previewResult)
if t.previewer.version != result.version {
t.previewer.version = result.version
t.previewer.following.Force(t.previewOpts.follow)
t.previewer.following.Force(t.activePreviewOpts.follow)
if t.previewer.following.Enabled() {
t.previewer.offset = 0
}
@@ -3830,9 +3894,9 @@ func (t *Terminal) Loop() error {
t.previewer.lines = result.lines
t.previewer.spinner = result.spinner
if t.hasPreviewWindow() && t.previewer.following.Enabled() {
t.previewer.offset = util.Max(t.previewer.offset, len(t.previewer.lines)-(t.pwindow.Height()-t.previewOpts.headerLines))
t.previewer.offset = util.Max(t.previewer.offset, len(t.previewer.lines)-(t.pwindow.Height()-t.activePreviewOpts.headerLines))
} else if result.offset >= 0 {
t.previewer.offset = util.Constrain(result.offset, t.previewOpts.headerLines, len(t.previewer.lines)-1)
t.previewer.offset = util.Constrain(result.offset, t.activePreviewOpts.headerLines, len(t.previewer.lines)-1)
}
t.printPreview()
case reqPreviewRefresh:
@@ -3888,6 +3952,7 @@ func (t *Terminal) Loop() error {
previewDraggingPos := -1
barDragging := false
pbarDragging := false
pborderDragging := false
wasDown := false
needBarrier := true
@@ -3971,7 +4036,7 @@ func (t *Terminal) Loop() error {
}
}
updatePreviewWindow := func(forcePreview bool) {
t.resizeWindows(forcePreview)
t.resizeWindows(forcePreview, false)
req(reqPrompt, reqList, reqInfo, reqHeader)
}
toggle := func() bool {
@@ -3987,8 +4052,8 @@ func (t *Terminal) Loop() error {
return
}
numLines := len(t.previewer.lines)
headerLines := t.previewOpts.headerLines
if t.previewOpts.cycle {
headerLines := t.activePreviewOpts.headerLines
if t.activePreviewOpts.cycle {
offsetRange := numLines - headerLines
newOffset = ((newOffset-headerLines)+offsetRange)%offsetRange + headerLines
}
@@ -4074,7 +4139,7 @@ func (t *Terminal) Loop() error {
if valid {
t.cancelPreview()
t.previewBox.Set(reqPreviewEnqueue,
previewRequest{t.previewOpts.command, t.pwindow, t.pwindowSize(), t.evaluateScrollOffset(), list, t.environ()})
previewRequest{t.previewOpts.command, t.evaluateScrollOffset(), list, t.environ()})
}
} else {
// Discard the preview content so that it won't accidentally appear
@@ -4087,7 +4152,7 @@ func (t *Terminal) Loop() error {
}
case actTogglePreviewWrap:
if t.hasPreviewWindow() {
t.previewOpts.wrap = !t.previewOpts.wrap
t.activePreviewOpts.wrap = !t.activePreviewOpts.wrap
// Reset preview version so that full redraw occurs
t.previewed.version = 0
req(reqPreviewRefresh)
@@ -4410,17 +4475,77 @@ func (t *Terminal) Loop() error {
suffix := copySlice(t.input[t.cx:])
t.input = append(append(t.input[:t.cx], t.yanked...), suffix...)
t.cx += len(t.yanked)
case actPageUp:
t.vmove(t.maxItems()-1, false)
case actPageUp, actPageDown, actHalfPageUp, actHalfPageDown:
// Calculate the number of lines to move
maxItems := t.maxItems()
linesToMove := maxItems - 1
if a.t == actHalfPageUp || a.t == actHalfPageDown {
linesToMove = maxItems / 2
}
// Move at least one line even in a very short window
linesToMove = util.Max(1, linesToMove)
// Determine the direction of the movement
direction := -1
if a.t == actPageUp || a.t == actHalfPageUp {
direction = 1
}
// In non-default layout, items are listed from top to bottom
if t.layout != layoutDefault {
direction *= -1
}
// We can simply add the number of lines to the current position in
// single-line mode
if !t.canSpanMultiLines() {
t.vset(t.cy + direction*linesToMove)
req(reqList)
case actPageDown:
t.vmove(-(t.maxItems() - 1), false)
req(reqList)
case actHalfPageUp:
t.vmove(t.maxItems()/2, false)
req(reqList)
case actHalfPageDown:
t.vmove(-(t.maxItems() / 2), false)
break
}
// But in multi-line mode, we need to carefully limit the amount of
// vertical movement so that items are not skipped. In order to do
// this, we calculate the minimum or maximum offset based on the
// direction of the movement and the number of lines of the items
// around the current scroll offset.
var minOffset, maxOffset, lineSum int
if direction > 0 {
maxOffset = t.offset
for ; maxOffset < t.merger.Length(); maxOffset++ {
itemLines, _ := t.numItemLines(t.merger.Get(maxOffset).item, maxItems)
lineSum += itemLines
if lineSum >= maxItems {
break
}
}
} else {
minOffset = t.offset
for ; minOffset >= 0 && minOffset < t.merger.Length(); minOffset-- {
itemLines, _ := t.numItemLines(t.merger.Get(minOffset).item, maxItems)
lineSum += itemLines
if lineSum >= maxItems {
if lineSum > maxItems {
minOffset++
}
break
}
}
}
for i := 0; i < linesToMove; i++ {
cy, offset := t.cy, t.offset
t.vset(cy + direction)
t.constrain()
if cy == t.cy {
break
}
if i > 0 && (direction > 0 && t.offset > maxOffset ||
direction < 0 && t.offset < minOffset) {
t.cy, t.offset = cy, offset
break
}
}
req(reqList)
case actOffsetUp, actOffsetDown:
diff := 1
@@ -4516,6 +4641,14 @@ func (t *Terminal) Loop() error {
case actToggleWrap:
t.wrap = !t.wrap
req(reqList, reqHeader)
case actToggleMultiLine:
t.multiLine = !t.multiLine
req(reqList)
case actToggleHscroll:
// Force re-rendering of the list
t.prevLines = make([]itemLine, len(t.prevLines))
t.hscroll = !t.hscroll
req(reqList)
case actTrackCurrent:
if t.track == trackDisabled {
t.track = trackCurrent
@@ -4551,6 +4684,7 @@ func (t *Terminal) Loop() error {
if !me.Down {
barDragging = false
pbarDragging = false
pborderDragging = false
previewDraggingPos = -1
}
@@ -4588,7 +4722,7 @@ func (t *Terminal) Loop() error {
}
// Preview scrollbar dragging
headerLines := t.previewOpts.headerLines
headerLines := t.activePreviewOpts.headerLines
pbarDragging = me.Down && (pbarDragging || clicked && t.hasPreviewWindow() && my >= t.pwindow.Top()+headerLines && my < t.pwindow.Top()+t.pwindow.Height() && mx == t.pwindow.Left()+t.pwindow.Width())
if pbarDragging {
effectiveHeight := t.pwindow.Height() - headerLines
@@ -4605,6 +4739,55 @@ func (t *Terminal) Loop() error {
break
}
// Preview border dragging (resizing)
if !pborderDragging && clicked && t.hasPreviewWindow() && t.pborder.Enclose(my, mx) {
switch t.activePreviewOpts.position {
case posUp:
pborderDragging = my == t.pborder.Top()+t.pborder.Height()-1
case posDown:
pborderDragging = my == t.pborder.Top()
case posLeft:
pborderDragging = mx == t.pborder.Left()+t.pborder.Width()-1
case posRight:
pborderDragging = mx == t.pborder.Left()
}
}
if pborderDragging {
var newSize int
var prevSize int
switch t.activePreviewOpts.position {
case posLeft:
prevSize = t.pwindow.Width()
diff := t.pborder.Width() - prevSize
newSize = mx - t.pborder.Left() - diff + 1
case posUp:
prevSize = t.pwindow.Height()
diff := t.pborder.Height() - prevSize
newSize = my - t.pborder.Top() - diff + 1
case posDown:
prevSize = t.pwindow.Height()
offset := my - t.pborder.Top()
newSize = prevSize - offset
case posRight:
prevSize = t.pwindow.Width()
offset := mx - t.pborder.Left()
newSize = prevSize - offset
}
if newSize < 1 {
newSize = 1
}
if prevSize == newSize {
break
}
t.activePreviewOpts.size = sizeSpec{float64(newSize), false}
updatePreviewWindow(false)
req(reqPreviewRefresh)
break
}
// Ignored
if !t.window.Enclose(my, mx) && !barDragging {
break
@@ -4748,6 +4931,7 @@ func (t *Terminal) Loop() error {
refreshPreview(t.previewOpts.command)
}
case actChangePreviewWindow:
// NOTE: We intentionally use "previewOpts" instead of "activePreviewOpts" here
currentPreviewOpts := t.previewOpts
// Reset preview options and apply the additional options
@@ -4765,7 +4949,8 @@ func (t *Terminal) Loop() error {
}
// Full redraw
if !currentPreviewOpts.sameLayout(t.previewOpts) {
switch currentPreviewOpts.compare(t.activePreviewOpts, &t.previewOpts) {
case previewOptsDifferentLayout:
// Preview command can be running in the background if the size of
// the preview window is 0 but not 'hidden'
wasHidden := currentPreviewOpts.hidden
@@ -4773,20 +4958,20 @@ func (t *Terminal) Loop() error {
if wasHidden && t.hasPreviewWindow() {
// Restart
refreshPreview(t.previewOpts.command)
} else if t.previewOpts.hidden {
} else if t.activePreviewOpts.hidden {
// Cancel
t.cancelPreview()
} else {
// Refresh
req(reqPreviewRefresh)
}
} else if !currentPreviewOpts.sameContentLayout(t.previewOpts) {
case previewOptsDifferentContentLayout:
t.previewed.version = 0
req(reqPreviewRefresh)
}
// Adjust scroll offset
if t.hasPreviewWindow() && currentPreviewOpts.scroll != t.previewOpts.scroll {
if t.hasPreviewWindow() && currentPreviewOpts.scroll != t.activePreviewOpts.scroll {
scrollPreviewTo(t.evaluateScrollOffset())
}

View File

@@ -507,6 +507,34 @@ func TestParsePlaceholder(t *testing.T) {
}
}
func TestExtractPassthroughs(t *testing.T) {
for _, middle := range []string{
"\x1bPtmux;\x1b\x1bbar\x1b\\",
"\x1bPtmux;\x1b\x1bbar\x1bbar\x1b\\",
"\x1b]1337;bar\x1b\\",
"\x1b]1337;bar\x1bbar\x1b\\",
"\x1b]1337;bar\a",
"\x1b_Ga=T,f=32,s=1258,v=1295,c=74,r=35,m=1\x1b\\",
"\x1b_Ga=T,f=32,s=1258,v=1295,c=74,r=35,m=1\x1b\\\r",
"\x1b_Ga=T,f=32,s=1258,v=1295,c=74,r=35,m=1\x1bbar\x1b\\\r",
"\x1b_Gm=1;AAAAAAAAA=\x1b\\",
"\x1b_Gm=1;AAAAAAAAA=\x1b\\\r",
"\x1b_Gm=1;\x1bAAAAAAAAA=\x1b\\\r",
} {
line := "foo" + middle + "baz"
loc := findPassThrough(line)
if loc == nil || line[0:loc[0]] != "foo" || line[loc[1]:] != "baz" {
t.Error("failed to find passthrough")
}
garbage := "\x1bPtmux;\x1b]1337;\x1b_Ga=\x1b]1337;bar\x1b."
line = strings.Repeat("foo"+middle+middle+"baz", 3) + garbage
passthroughs, result := extractPassThroughs(line)
if result != "foobazfoobazfoobaz"+garbage || len(passthroughs) != 6 {
t.Error("failed to extract passthroughs")
}
}
}
/* utilities section */
// Item represents one line in fzf UI. Usually it is relative path to files and folders.

View File

@@ -18,7 +18,7 @@ func runTmux(args []string, opts *Options) (int, error) {
for _, arg := range args {
argStr += " " + escapeSingleQuote(arg)
}
argStr += ` --no-tmux --no-height`
argStr += ` --no-popup --no-height`
// Get current directory
dir, err := os.Getwd()

View File

@@ -73,11 +73,15 @@ func (r *LightRenderer) csi(code string) string {
func (r *LightRenderer) flush() {
if r.queued.Len() > 0 {
fmt.Fprint(r.ttyout, "\x1b[?7l\x1b[?25l"+r.queued.String()+"\x1b[?25h\x1b[?7h")
r.flushRaw("\x1b[?7l\x1b[?25l" + r.queued.String() + "\x1b[?25h\x1b[?7h")
r.queued.Reset()
}
}
func (r *LightRenderer) flushRaw(sequence string) {
fmt.Fprint(r.ttyout, sequence)
}
// Light renderer
type LightRenderer struct {
closed *util.AtomicBool
@@ -655,11 +659,13 @@ func (r *LightRenderer) mouseSequence(sz *int) Event {
}
func (r *LightRenderer) smcup() {
r.csi("?1049h")
r.flush()
r.flushRaw("\x1b[?1049h")
}
func (r *LightRenderer) rmcup() {
r.csi("?1049l")
r.flush()
r.flushRaw("\x1b[?1049l")
}
func (r *LightRenderer) Pause(clear bool) {
@@ -929,9 +935,6 @@ func (w *LightWindow) Height() int {
func (w *LightWindow) Refresh() {
}
func (w *LightWindow) Close() {
}
func (w *LightWindow) X() int {
return w.posx
}
@@ -1097,7 +1100,7 @@ func (w *LightWindow) fill(str string, resetCode string) FillReturn {
}
}
}
if w.posx+1 >= w.Width() {
if w.posx >= w.Width() {
if w.posy+1 >= w.height {
return FillSuspend
}

View File

@@ -555,10 +555,6 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int,
return w
}
func (w *TcellWindow) Close() {
// TODO
}
func fill(x, y, w, h int, n ColorPair, r rune) {
for ly := 0; ly <= h; ly++ {
for lx := 0; lx <= w; lx++ {

View File

@@ -387,6 +387,14 @@ func (s BorderShape) HasTop() bool {
return true
}
func (s BorderShape) HasBottom() bool {
switch s {
case BorderNone, BorderLeft, BorderRight, BorderTop, BorderVertical: // No bottom
return false
}
return true
}
type BorderStyle struct {
shape BorderShape
top rune
@@ -402,6 +410,18 @@ type BorderStyle struct {
type BorderCharacter int
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
if shape == BorderNone {
return BorderStyle{
shape: shape,
top: ' ',
bottom: ' ',
left: ' ',
right: ' ',
topLeft: ' ',
topRight: ' ',
bottomLeft: ' ',
bottomRight: ' '}
}
if !unicode {
return BorderStyle{
shape: shape,
@@ -498,19 +518,6 @@ func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
}
}
func MakeTransparentBorder() BorderStyle {
return BorderStyle{
shape: BorderRounded,
top: ' ',
bottom: ' ',
left: ' ',
right: ' ',
topLeft: ' ',
topRight: ' ',
bottomLeft: ' ',
bottomRight: ' '}
}
type TermSize struct {
Lines int
Columns int
@@ -552,7 +559,6 @@ type Window interface {
DrawHBorder()
Refresh()
FinishFill()
Close()
X() int
Y() int

View File

@@ -306,5 +306,5 @@ func (chars *Chars) Lines(multiLine bool, maxLines int, wrapCols int, wrapSignWi
}
}
return wrapped, false
return wrapped, overflow
}

103
src/zellij.go Normal file
View File

@@ -0,0 +1,103 @@
package fzf
import (
"fmt"
"os"
"os/exec"
"strconv"
"github.com/junegunn/fzf/src/tui"
)
func runZellij(args []string, opts *Options) (int, error) {
// Prepare arguments
fzf := args[0]
args = append([]string{"--bind=ctrl-z:ignore"}, args[1:]...)
if opts.BorderShape == tui.BorderUndefined {
args = append(args, "--no-border")
}
argStr := escapeSingleQuote(fzf)
for _, arg := range args {
argStr += " " + escapeSingleQuote(arg)
}
argStr += ` --no-popup --no-height`
// Get current directory
dir, err := os.Getwd()
if err != nil {
dir = "."
}
sh, err := sh(false)
if err != nil {
return ExitError, err
}
fifo, err := fifo("zellij-fifo")
if err != nil {
return ExitError, err
}
zopts := " --width " + opts.Tmux.width.String() + " --height " + opts.Tmux.height.String()
centerX := func() {
// TODO: Handle non-percent values
if opts.Tmux.width.percent {
x := (100.0 - opts.Tmux.width.size) / 2
if x <= 0 {
zopts += " -x0"
} else {
zopts += fmt.Sprintf(" -x%d%%", int(x))
}
} else if cols := os.Getenv("COLUMNS"); len(cols) > 0 {
if w, e := strconv.Atoi(cols); e == nil {
x := (float64(w) - opts.Tmux.width.size) / 2
zopts += fmt.Sprintf(" -x%d", int(x))
}
}
}
centerY := func() {
if opts.Tmux.height.percent {
y := (100.0 - opts.Tmux.height.size) / 2
if y <= 0 {
zopts += " -y0"
} else {
zopts += fmt.Sprintf(" -y%d%%", int(y))
}
} else if lines := os.Getenv("LINES"); len(lines) > 0 {
if h, e := strconv.Atoi(lines); e == nil {
y := (float64(h) - opts.Tmux.height.size) / 2
zopts += fmt.Sprintf(" -y%d", int(y))
}
}
}
switch opts.Tmux.position {
case posUp:
zopts += " -y0"
centerX()
case posDown:
zopts += " -y9999"
centerX()
case posLeft:
zopts += " -x0"
centerY()
case posRight:
zopts += " -x9999"
centerY()
case posCenter:
centerX()
centerY()
}
lines := []string{
"#!/bin/sh",
fmt.Sprintf(`zellij run --name '' --floating --close-on-exit --cwd %s %s -- %s -c "%s $1; echo \$? > %s" || exit $?`, dir, zopts, sh, sh, fifo),
fmt.Sprintf(`exit $(cat %s)`, fifo),
}
temptemp := WriteTemporaryFile(lines, "\n")
defer os.Remove(temptemp)
return runProxy(argStr, func(temp string, needBash bool) (*exec.Cmd, error) {
return exec.Command(sh, temptemp, temp), nil
}, opts, true)
}

View File

@@ -1442,10 +1442,14 @@ class TestGoFZF < TestBase
writelines(['=' * 10_000 + '0123456789'])
[0, 3, 6].each do |off|
tmux.prepare
tmux.send_keys "#{FZF} --hscroll-off=#{off} -q 0 < #{tempname}", :Enter
tmux.send_keys "#{FZF} --hscroll-off=#{off} -q 0 --bind space:toggle-hscroll < #{tempname}", :Enter
tmux.until { |lines| assert lines[-3]&.end_with?((0..off).to_a.join + '··') }
tmux.send_keys '9'
tmux.until { |lines| assert lines[-3]&.end_with?('789') }
tmux.send_keys :Space
tmux.until { |lines| assert lines[-3]&.end_with?('=··') }
tmux.send_keys :Space
tmux.until { |lines| assert lines[-3]&.end_with?('789') }
tmux.send_keys :Enter
end
end
@@ -2133,7 +2137,11 @@ class TestGoFZF < TestBase
end
def test_keep_right
tmux.send_keys "seq 10000 | #{FZF} --read0 --keep-right --no-multi-line", :Enter
tmux.send_keys "seq 10000 | #{FZF} --read0 --keep-right --no-multi-line --bind space:toggle-multi-line", :Enter
tmux.until { |lines| assert lines.any_include?('9999␊10000') }
tmux.send_keys :Space
tmux.until { |lines| assert lines.any_include?('> 1') }
tmux.send_keys :Space
tmux.until { |lines| assert lines.any_include?('9999␊10000') }
end
@@ -3073,6 +3081,21 @@ class TestGoFZF < TestBase
tmux.until { |lines| assert_includes lines, '/1/1/' }
end
def test_alternative_preview_window_opts
tmux.send_keys "seq 10 | #{FZF} --preview-window '~5,2,+0,<100000(~0,+100,wrap,noinfo)' --preview 'seq 1000'", :Enter
tmux.until { |lines| assert_equal 10, lines.item_count }
tmux.until do |lines|
assert_equal ['╭────╮', '│ 10 │', '│ 0 │', '│ 10 │', '│ 1 │'], lines.take(5).map(&:strip)
end
end
def test_preview_window_width_exception
tmux.send_keys "seq 10 | #{FZF} --scrollbar --preview-window border-left --border --preview 'seq 1000'", :Enter
tmux.until do |lines|
assert lines[1]&.end_with?(' 1/1000││')
end
end
def test_become
tmux.send_keys "seq 100 | #{FZF} --bind 'enter:become:seq {} | #{FZF}'", :Enter
tmux.until { |lines| assert_equal 100, lines.item_count }
@@ -3737,6 +3760,23 @@ module CompletionTest
tmux.until { |lines| assert_equal 'unset FZFFOOBAR', lines[-1] }
end
def test_completion_in_command_sequence
tmux.send_keys 'export FZFFOOBAR=BAZ', :Enter
tmux.prepare
triggers = ['**', '~~', '++', 'ff', '/']
triggers.concat(['&', '[', ';', '`']) if instance_of?(TestZsh)
triggers.each do |trigger|
set_var('FZF_COMPLETION_TRIGGER', trigger)
command = "echo foo; QUX=THUD unset FZFFOOBR#{trigger}"
tmux.send_keys command.sub(/(;|`)$/, '\\\\\1'), :Tab
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.send_keys :Enter
tmux.until { |lines| assert_equal 'echo foo; QUX=THUD unset FZFFOOBAR', lines[-1] }
end
end
def test_file_completion_unicode
FileUtils.mkdir_p('/tmp/fzf-test')
tmux.paste "cd /tmp/fzf-test; echo test3 > $'fzf-unicode \\355\\205\\214\\354\\212\\244\\355\\212\\2701'; echo test4 > $'fzf-unicode \\355\\205\\214\\354\\212\\244\\355\\212\\2702'"