Compare commits

...

48 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
24 changed files with 828 additions and 379 deletions

View File

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

View File

@@ -1,6 +1,16 @@
CHANGELOG 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 0.56.3
------ ------
- Bug fixes in zsh scripts - Bug fixes in zsh scripts

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/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97
github.com/mattn/go-isatty v0.0.20 github.com/mattn/go-isatty v0.0.20
github.com/rivo/uniseg v0.4.7 github.com/rivo/uniseg v0.4.7
golang.org/x/sys v0.27.0 golang.org/x/sys v0.28.0
golang.org/x/term v0.26.0 golang.org/x/term v0.27.0
) )
require ( 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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.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.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 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.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= 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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=

33
install
View File

@@ -2,7 +2,7 @@
set -u set -u
version=0.56.3 version=0.57.0
auto_completion= auto_completion=
key_bindings= key_bindings=
update_config=2 update_config=2
@@ -295,35 +295,44 @@ EOF
fi fi
append_line() { append_line() {
set -e local update line file pat lines
local update line file pat lno
update="$1" update="$1"
line="$2" line="$2"
file="$3" file="$3"
pat="${4:-}" pat="${4:-}"
lno="" lines=""
echo "Update $file:" echo "Update $file:"
echo " - $line" echo " - $line"
if [ -f "$file" ]; then if [ -f "$file" ]; then
if [ $# -lt 4 ]; then if [ $# -lt 4 ]; then
lno=$(\grep -nF "$line" "$file" | sed 's/:.*//' | tr '\n' ' ') lines=$(\grep -nF "$line" "$file")
else else
lno=$(\grep -nF "$pat" "$file" | sed 's/:.*//' | tr '\n' ' ') lines=$(\grep -nF "$pat" "$file")
fi fi
fi fi
if [ -n "$lno" ]; then
echo " - Already exists: line #$lno" if [ -n "$lines" ]; then
else echo " - Already exists:"
if [ $update -eq 1 ]; then 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" [ -f "$file" ] && echo >> "$file"
echo "$line" >> "$file" echo "$line" >> "$file"
echo " + Added" echo " + Added"
else else
echo " ~ Skipped" echo " ~ Skipped"
fi fi
fi
echo echo
set +e set +e
} }

View File

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

View File

@@ -11,7 +11,7 @@ import (
"github.com/junegunn/fzf/src/protector" "github.com/junegunn/fzf/src/protector"
) )
var version = "0.56" var version = "0.57"
var revision = "devel" var revision = "devel"
//go:embed shell/key-bindings.bash //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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf\-tmux 1 "Nov 2024" "fzf 0.56.3" "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 .SH NAME
fzf\-tmux - open fzf in tmux split pane fzf\-tmux - open fzf in tmux split pane

View File

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf 1 "Nov 2024" "fzf 0.56.3" "fzf - a command-line fuzzy finder" .TH fzf 1 "Dec 2024" "fzf 0.57.0" "fzf - a command-line fuzzy finder"
.SH NAME .SH NAME
fzf - a command-line fuzzy finder 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). Minimum height when \fB\-\-height\fR is given in percent (default: 10).
Ignored when \fB\-\-height\fR is not specified. Ignored when \fB\-\-height\fR is not specified.
.TP .TP
.BI "\-\-tmux" "[=[center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]]" .BI "\-\-popup" "[=[center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]]"
Start fzf in a tmux popup (default \fBcenter,50%\fR). Requires tmux 3.3 or Start fzf in a tmux popup or in a zellij floating pane (default
later. This option is ignored if you are not running fzf inside tmux. \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. e.g.
\fB# Popup in the center with 70% width and height \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 # 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 # 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 # Popup on the top with 80% width and 40% height
fzf \-\-tmux top,80%,40%\fR fzf \-\-popup top,80%,40%\fR
.TP .TP
.BI "\-\-layout=" "LAYOUT" .BI "\-\-layout=" "LAYOUT"
@@ -1005,8 +1006,8 @@ Determines the behavior of the built-in directory walker that is used when
.br .br
.TP .TP
.B "\-\-walker\-root=DIR" .B "\-\-walker\-root=DIR [...]"
The root directory from which to start the built-in directory walker. List of directory names to start the built-in directory walker.
The default value is the current working directory. The default value is the current working directory.
.TP .TP
@@ -1122,9 +1123,6 @@ fzf exports the following environment variables to its child processes.
.br .br
.BR FZF_PORT " Port number when \-\-listen option is used" .BR FZF_PORT " Port number when \-\-listen option is used"
.br .br
The following variables are additionally exported to the preview commands.
.BR FZF_PREVIEW_TOP " Top position of the preview window" .BR FZF_PREVIEW_TOP " Top position of the preview window"
.br .br
.BR FZF_PREVIEW_LEFT " Left position of the preview window" .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 \fBshow\-preview\fR
\fBtoggle\fR (\fIright\-click\fR) \fBtoggle\fR (\fIright\-click\fR)
\fBtoggle\-all\fR (toggle all matches) \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\-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\-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\fR
\fBtoggle\-preview\-wrap\fR \fBtoggle\-preview\-wrap\fR
\fBtoggle\-search\fR (toggle search functionality) \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\fR (toggle global tracking option (\fB\-\-track\fR))
\fBtoggle\-track\-current\fR (toggle tracking of the current item) \fBtoggle\-track\-current\fR (toggle tracking of the current item)
\fBtoggle\-wrap\fR \fIctrl\-/\fR \fIalt\-/\fR \fBtoggle\-wrap\fR \fIctrl\-/\fR \fIalt\-/\fR
\fBtoggle+down\fR \fIctrl\-i (tab)\fR
\fBtoggle+up\fR \fIbtab (shift\-tab)\fR \fBtoggle+up\fR \fIbtab (shift\-tab)\fR
\fBtrack\-current\fR (track the current item; automatically disabled if focus changes) \fBtrack\-current\fR (track the current item; automatically disabled if focus changes)
\fBtransform(...)\fR (transform states using the output of an external command) \fBtransform(...)\fR (transform states using the output of an external command)

View File

@@ -122,7 +122,6 @@ __fzf_comprun() {
# Extract the name of the command. e.g. ls; foo=1 ssh **<tab> # Extract the name of the command. e.g. ls; foo=1 ssh **<tab>
__fzf_extract_command() { __fzf_extract_command() {
setopt localoptions noksh_arrays
# Control completion with the "compstate" parameter, insert and list nothing # Control completion with the "compstate" parameter, insert and list nothing
compstate[insert]= compstate[insert]=
compstate[list]= compstate[list]=
@@ -303,17 +302,9 @@ _fzf_complete_kill_post() {
} }
fzf-completion() { fzf-completion() {
typeset -g cmd_word local tokens prefix trigger tail matches lbuf d_cmds cursor_pos cmd_word
trap 'unset cmd_word' EXIT
local tokens prefix trigger tail matches lbuf d_cmds cursor_pos
setopt localoptions noshwordsplit noksh_arrays noposixbuiltins setopt localoptions noshwordsplit noksh_arrays noposixbuiltins
# Check if at least one completion system (old or new) is active
if ! zmodload -F zsh/parameter p:functions 2>/dev/null || ! (( ${+functions[compdef]} )); then
if ! zmodload -e zsh/compctl; then
zmodload -i zsh/compctl
fi
fi
# http://zsh.sourceforge.net/FAQ/zshfaq03.html # http://zsh.sourceforge.net/FAQ/zshfaq03.html
# http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion-Flags # http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion-Flags
tokens=(${(z)LBUFFER}) tokens=(${(z)LBUFFER})
@@ -339,15 +330,25 @@ fzf-completion() {
if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS-cd pushd rmdir}) d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS-cd pushd rmdir})
cursor_pos=$CURSOR
{ {
cursor_pos=$CURSOR
# Move the cursor before the trigger to preserve word array elements when # Move the cursor before the trigger to preserve word array elements when
# trigger chars like ';' or '`' would otherwise reset the 'words' array. # trigger chars like ';' or '`' would otherwise reset the 'words' array.
CURSOR=$((cursor_pos - ${#trigger} - 1)) CURSOR=$((cursor_pos - ${#trigger} - 1))
# Assign the extracted command to the global variable 'cmd_word' # 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 zle __fzf_extract_command
} always { } always {
CURSOR=$cursor_pos 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}} [ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
@@ -376,8 +377,6 @@ fzf-completion() {
unset binding unset binding
} }
# Completion widget to gain access to the 'words' array (man zshcompwid)
zle -C __fzf_extract_command .complete-word __fzf_extract_command
# Normal widget # Normal widget
zle -N fzf-completion zle -N fzf-completion
bindkey '^I' fzf-completion bindkey '^I' fzf-completion

View File

@@ -11,8 +11,6 @@
# - $FZF_ALT_C_COMMAND # - $FZF_ALT_C_COMMAND
# - $FZF_ALT_C_OPTS # - $FZF_ALT_C_OPTS
status is-interactive; or exit 0
# Key bindings # Key bindings
# ------------ # ------------
@@ -23,7 +21,7 @@ function fzf_key_bindings
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS # $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40% test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
echo "--height $FZF_TMUX_HEIGHT --bind=ctrl-z:ignore" $argv[1] 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] echo $FZF_DEFAULT_OPTS $argv[2]
end end
@@ -36,12 +34,12 @@ function fzf_key_bindings
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40% test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin 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_COMMAND "$FZF_CTRL_T_COMMAND"
set -lx FZF_DEFAULT_OPTS_FILE '' 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 end
if [ -z "$result" ] if test -z "$result"
commandline -f repaint commandline -f repaint
return return
else else
@@ -50,7 +48,7 @@ function fzf_key_bindings
end end
for i in $result for i in $result
commandline -it -- $prefix commandline -it -- $prefix
commandline -it -- (string escape $i) commandline -it -- (string escape -- $i)
commandline -it -- ' ' commandline -it -- ' '
end end
commandline -f repaint commandline -f repaint
@@ -59,27 +57,27 @@ function fzf_key_bindings
function fzf-history-widget -d "Show command history" function fzf-history-widget -d "Show command history"
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40% test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin begin
set -l FISH_MAJOR (echo $version | cut -f1 -d.) set -l FISH_MAJOR (string split -- '.' $version)[1]
set -l FISH_MINOR (echo $version | cut -f2 -d.) set -l FISH_MINOR (string split -- '.' $version)[2]
# merge history from other sessions before searching # merge history from other sessions before searching
if test -z "$fish_private_mode" test -z "$fish_private_mode"; and builtin history merge
builtin history merge
end
# history's -z flag is needed for multi-line support. # 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 # history's -z flag was added in fish 2.4.0, so don't use it for versions
# before 2.4.0. # before 2.4.0.
if [ "$FISH_MAJOR" -gt 2 -o \( "$FISH_MAJOR" -eq 2 -a "$FISH_MINOR" -ge 4 \) ]; if test "$FISH_MAJOR" -gt 2 -o \( "$FISH_MAJOR" -eq 2 -a "$FISH_MINOR" -ge 4 \)
if type -P perl > /dev/null 2>&1
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 (__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 '' 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 and commandline -- $result
else 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 -l line 0
set -lx FZF_DEFAULT_OPTS_FILE '' for i in (builtin history -z --reverse | string split0)
builtin history -z | eval (__fzfcmd) --read0 --print0 -q '(commandline)' | read -lz result 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 and commandline -- $result
end end
else else
@@ -98,12 +96,12 @@ function fzf_key_bindings
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40% test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin 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_OPTS_FILE ''
set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND" 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 cd -- $result
# Remove last token from commandline. # Remove last token from commandline.
@@ -118,9 +116,9 @@ function fzf_key_bindings
function __fzfcmd function __fzfcmd
test -n "$FZF_TMUX"; or set FZF_TMUX 0 test -n "$FZF_TMUX"; or set FZF_TMUX 0
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40% 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 -- " 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 -- " echo "fzf-tmux -d$FZF_TMUX_HEIGHT -- "
else else
echo "fzf" echo "fzf"
@@ -135,7 +133,7 @@ function fzf_key_bindings
bind \ec fzf-cd-widget bind \ec fzf-cd-widget
end end
if bind -M insert > /dev/null 2>&1 if bind -M insert &> /dev/null
bind -M insert \cr fzf-history-widget bind -M insert \cr fzf-history-widget
if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND" if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND"
bind -M insert \ct fzf-file-widget bind -M insert \ct fzf-file-widget
@@ -152,40 +150,53 @@ function fzf_key_bindings
set -l prefix (string match -r -- '^-[^\s=]+=' $commandline) set -l prefix (string match -r -- '^-[^\s=]+=' $commandline)
set commandline (string replace -- "$prefix" '' $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 is used to do shell expansion on paths
eval set commandline $commandline 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 # Default to current directory with no --query
set dir '.' set dir '.'
set fzf_query '' set fzf_query ''
else else
set dir (__fzf_get_dir $commandline) 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 # if $dir is "." but commandline is not a relative path, this means no file path found
set fzf_query $commandline set fzf_query $commandline
else else
# Also remove trailing slash after dir, to "split" input properly # 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
end end
echo $dir echo (string escape -- $dir)
echo $fzf_query echo (string escape -- $fzf_query)
echo $prefix echo $prefix
end end
function __fzf_get_dir -d 'Find the longest existing filepath from input string' function __fzf_get_dir -d 'Find the longest existing filepath from input string'
set dir $argv set dir $argv
# Strip all trailing slashes. Ignore if $dir is root dir (/) # Strip trailing slash, unless $dir is root dir (/)
if [ (string length -- $dir) -gt 1 ] set dir (string replace -r -- '(?<!^)/$' '' $dir)
set dir (string replace -r '/*$' -- '' $dir)
end
# Iteratively check if dir exists and strip tail end of path # 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 absolute, this can keep going until ends up at /
# If path is relative, this can keep going until entire input is consumed, dirname returns "." # If path is relative, this can keep going until entire input is consumed, dirname returns "."
set dir (dirname -- "$dir") set dir (dirname -- "$dir")

View File

@@ -59,73 +59,75 @@ func _() {
_ = x[actToggleTrackCurrent-48] _ = x[actToggleTrackCurrent-48]
_ = x[actToggleHeader-49] _ = x[actToggleHeader-49]
_ = x[actToggleWrap-50] _ = x[actToggleWrap-50]
_ = x[actTrackCurrent-51] _ = x[actToggleMultiLine-51]
_ = x[actUntrackCurrent-52] _ = x[actToggleHscroll-52]
_ = x[actDown-53] _ = x[actTrackCurrent-53]
_ = x[actUp-54] _ = x[actUntrackCurrent-54]
_ = x[actPageUp-55] _ = x[actDown-55]
_ = x[actPageDown-56] _ = x[actUp-56]
_ = x[actPosition-57] _ = x[actPageUp-57]
_ = x[actHalfPageUp-58] _ = x[actPageDown-58]
_ = x[actHalfPageDown-59] _ = x[actPosition-59]
_ = x[actOffsetUp-60] _ = x[actHalfPageUp-60]
_ = x[actOffsetDown-61] _ = x[actHalfPageDown-61]
_ = x[actOffsetMiddle-62] _ = x[actOffsetUp-62]
_ = x[actJump-63] _ = x[actOffsetDown-63]
_ = x[actJumpAccept-64] _ = x[actOffsetMiddle-64]
_ = x[actPrintQuery-65] _ = x[actJump-65]
_ = x[actRefreshPreview-66] _ = x[actJumpAccept-66]
_ = x[actReplaceQuery-67] _ = x[actPrintQuery-67]
_ = x[actToggleSort-68] _ = x[actRefreshPreview-68]
_ = x[actShowPreview-69] _ = x[actReplaceQuery-69]
_ = x[actHidePreview-70] _ = x[actToggleSort-70]
_ = x[actTogglePreview-71] _ = x[actShowPreview-71]
_ = x[actTogglePreviewWrap-72] _ = x[actHidePreview-72]
_ = x[actTransform-73] _ = x[actTogglePreview-73]
_ = x[actTransformBorderLabel-74] _ = x[actTogglePreviewWrap-74]
_ = x[actTransformHeader-75] _ = x[actTransform-75]
_ = x[actTransformPreviewLabel-76] _ = x[actTransformBorderLabel-76]
_ = x[actTransformPrompt-77] _ = x[actTransformHeader-77]
_ = x[actTransformQuery-78] _ = x[actTransformPreviewLabel-78]
_ = x[actPreview-79] _ = x[actTransformPrompt-79]
_ = x[actChangePreview-80] _ = x[actTransformQuery-80]
_ = x[actChangePreviewWindow-81] _ = x[actPreview-81]
_ = x[actPreviewTop-82] _ = x[actChangePreview-82]
_ = x[actPreviewBottom-83] _ = x[actChangePreviewWindow-83]
_ = x[actPreviewUp-84] _ = x[actPreviewTop-84]
_ = x[actPreviewDown-85] _ = x[actPreviewBottom-85]
_ = x[actPreviewPageUp-86] _ = x[actPreviewUp-86]
_ = x[actPreviewPageDown-87] _ = x[actPreviewDown-87]
_ = x[actPreviewHalfPageUp-88] _ = x[actPreviewPageUp-88]
_ = x[actPreviewHalfPageDown-89] _ = x[actPreviewPageDown-89]
_ = x[actPrevHistory-90] _ = x[actPreviewHalfPageUp-90]
_ = x[actPrevSelected-91] _ = x[actPreviewHalfPageDown-91]
_ = x[actPrint-92] _ = x[actPrevHistory-92]
_ = x[actPut-93] _ = x[actPrevSelected-93]
_ = x[actNextHistory-94] _ = x[actPrint-94]
_ = x[actNextSelected-95] _ = x[actPut-95]
_ = x[actExecute-96] _ = x[actNextHistory-96]
_ = x[actExecuteSilent-97] _ = x[actNextSelected-97]
_ = x[actExecuteMulti-98] _ = x[actExecute-98]
_ = x[actSigStop-99] _ = x[actExecuteSilent-99]
_ = x[actFirst-100] _ = x[actExecuteMulti-100]
_ = x[actLast-101] _ = x[actSigStop-101]
_ = x[actReload-102] _ = x[actFirst-102]
_ = x[actReloadSync-103] _ = x[actLast-103]
_ = x[actDisableSearch-104] _ = x[actReload-104]
_ = x[actEnableSearch-105] _ = x[actReloadSync-105]
_ = x[actSelect-106] _ = x[actDisableSearch-106]
_ = x[actDeselect-107] _ = x[actEnableSearch-107]
_ = x[actUnbind-108] _ = x[actSelect-108]
_ = x[actRebind-109] _ = x[actDeselect-109]
_ = x[actBecome-110] _ = x[actUnbind-110]
_ = x[actShowHeader-111] _ = x[actRebind-111]
_ = x[actHideHeader-112] _ = 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 { func (i actionType) String() string {
if i < 0 || i >= actionType(len(_actionType_index)-1) { 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 // Run starts fzf
func Run(opts *Options) (int, error) { func Run(opts *Options) (int, error) {
if opts.Filter == nil { 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) return runTmux(os.Args, opts)
} }
if len(os.Getenv("ZELLIJ")) > 0 {
return runZellij(os.Args, opts)
}
}
if needWinpty(opts) { if needWinpty(opts) {
return runWinpty(os.Args, opts) return runWinpty(os.Args, opts)

View File

@@ -75,7 +75,7 @@ Usage: fzf [options]
according to the input size. according to the input size.
--min-height=HEIGHT Minimum height when --height is given in percent --min-height=HEIGHT Minimum height when --height is given in percent
(default: 10) (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[%]] [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]
(default: center,50%) (default: center,50%)
--layout=LAYOUT Choose layout: [default|reverse|reverse-list] --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) Directory traversal (Only used when $FZF_DEFAULT_COMMAND is not set)
--walker=OPTS [file][,dir][,follow][,hidden] (default: file,follow,hidden) --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 --walker-skip=DIRS Comma-separated list of directory names to skip
(default: .git,node_modules) (default: .git,node_modules)
@@ -299,7 +299,7 @@ func parseTmuxOptions(arg string, index int) (*tmuxOptions, error) {
var err error var err error
opts := defaultTmuxOptions(index) opts := defaultTmuxOptions(index)
tokens := splitRegexp.Split(arg, -1) 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 { if len(tokens) == 0 || len(tokens) > 3 {
return nil, errorToReturn return nil, errorToReturn
} }
@@ -381,14 +381,46 @@ func (a previewOpts) aboveOrBelow() bool {
return a.size.size > 0 && (a.position == posUp || a.position == posDown) return a.size.size > 0 && (a.position == posUp || a.position == posDown)
} }
func (a previewOpts) sameLayout(b previewOpts) bool { type previewOptsCompare int
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)
}
func (a previewOpts) sameContentLayout(b previewOpts) bool { const (
return a.wrap == b.wrap && a.headerLines == b.headerLines && a.info == b.info 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 { func firstLine(s string) string {
@@ -490,7 +522,7 @@ type Options struct {
Unsafe bool Unsafe bool
ClearOnExit bool ClearOnExit bool
WalkerOpts walkerOpts WalkerOpts walkerOpts
WalkerRoot string WalkerRoot []string
WalkerSkip []string WalkerSkip []string
Version bool Version bool
Help bool Help bool
@@ -594,7 +626,7 @@ func defaultOptions() *Options {
Unsafe: false, Unsafe: false,
ClearOnExit: true, ClearOnExit: true,
WalkerOpts: walkerOpts{file: true, hidden: true, follow: true}, WalkerOpts: walkerOpts{file: true, hidden: true, follow: true},
WalkerRoot: ".", WalkerRoot: []string{"."},
WalkerSkip: []string{".git", "node_modules"}, WalkerSkip: []string{".git", "node_modules"},
Help: false, Help: false,
Version: false} Version: false}
@@ -626,6 +658,28 @@ func optionalNextString(args []string, i *int) (bool, string) {
return false, "" 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) { func atoi(str string) (int, error) {
num, err := strconv.Atoi(str) num, err := strconv.Atoi(str)
if err != nil { if err != nil {
@@ -1377,6 +1431,10 @@ func parseActionList(masked string, original string, prevActions []*action, putA
appendAction(actToggleHeader) appendAction(actToggleHeader)
case "toggle-wrap": case "toggle-wrap":
appendAction(actToggleWrap) appendAction(actToggleWrap)
case "toggle-multi-line":
appendAction(actToggleMultiLine)
case "toggle-hscroll":
appendAction(actToggleHscroll)
case "show-header": case "show-header":
appendAction(actShowHeader) appendAction(actShowHeader)
case "hide-header": case "hide-header":
@@ -1973,7 +2031,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
opts.Version = true opts.Version = true
case "--no-winpty": case "--no-winpty":
opts.NoWinpty = true opts.NoWinpty = true
case "--tmux": case "--tmux", "--popup":
given, str := optionalNextString(allArgs, &i) given, str := optionalNextString(allArgs, &i)
if given { if given {
if opts.Tmux, err = parseTmuxOptions(str, index); err != nil { if opts.Tmux, err = parseTmuxOptions(str, index); err != nil {
@@ -1982,7 +2040,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
} else { } else {
opts.Tmux = defaultTmuxOptions(index) opts.Tmux = defaultTmuxOptions(index)
} }
case "--no-tmux": case "--no-tmux", "--no-popup":
opts.Tmux = nil opts.Tmux = nil
case "--force-tty-in": case "--force-tty-in":
// NOTE: We need this because `system('fzf --tmux < /dev/tty')` doesn't // 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 return err
} }
case "--walker-root": 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 return err
} }
case "--walker-skip": case "--walker-skip":
@@ -2519,6 +2577,10 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if opts.FuzzyAlgo, err = parseAlgo(value); err != nil { if opts.FuzzyAlgo, err = parseAlgo(value); err != nil {
return err 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 { } else if match, value := optString(arg, "--tmux="); match {
if opts.Tmux, err = parseTmuxOptions(value, index); err != nil { if opts.Tmux, err = parseTmuxOptions(value, index); err != nil {
return err return err
@@ -2685,7 +2747,11 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
return err return err
} }
} else if match, value := optString(arg, "--walker-root="); match { } 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 { } else if match, value := optString(arg, "--walker-skip="); match {
opts.WalkerSkip = filterNonEmpty(strings.Split(value, ",")) opts.WalkerSkip = filterNonEmpty(strings.Split(value, ","))
} else if match, value := optString(arg, "--hscroll-off="); match { } else if match, value := optString(arg, "--hscroll-off="); match {

View File

@@ -7,6 +7,7 @@ import (
"io/fs" "io/fs"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@@ -120,7 +121,7 @@ func (r *Reader) readChannel(inputChan chan string) bool {
} }
// ReadSource reads data from the default command or from standard input // 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, readyChan chan bool) { func (r *Reader) ReadSource(inputChan chan string, roots []string, opts walkerOpts, ignores []string, initCmd string, initEnv []string, readyChan chan bool) {
r.startEventPoller() r.startEventPoller()
var success bool var success bool
signalReady := func() { signalReady := func() {
@@ -137,7 +138,7 @@ func (r *Reader) ReadSource(inputChan chan string, root string, opts walkerOpts,
cmd := os.Getenv("FZF_DEFAULT_COMMAND") cmd := os.Getenv("FZF_DEFAULT_COMMAND")
if len(cmd) == 0 { if len(cmd) == 0 {
signalReady() signalReady()
success = r.readFiles(root, opts, ignores) success = r.readFiles(roots, opts, ignores)
} else { } else {
success = r.readFromCommand(cmd, initEnv, signalReady) success = r.readFromCommand(cmd, initEnv, signalReady)
} }
@@ -265,13 +266,33 @@ func trimPath(path string) string {
return byteString(bytes) return byteString(bytes)
} }
func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool { func (r *Reader) readFiles(roots []string, opts walkerOpts, ignores []string) bool {
conf := fastwalk.Config{ conf := fastwalk.Config{
Follow: opts.follow, Follow: opts.follow,
// Use forward slashes when running a Windows binary under WSL or MSYS // Use forward slashes when running a Windows binary under WSL or MSYS
ToSlash: fastwalk.DefaultToSlash(), ToSlash: fastwalk.DefaultToSlash(),
Sort: fastwalk.SortFilesFirst, 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 { fn := func(path string, de os.DirEntry, err error) error {
if err != nil { if err != nil {
return nil return nil
@@ -284,11 +305,21 @@ func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool
if !opts.hidden && base[0] == '.' && base != ".." { if !opts.hidden && base[0] == '.' && base != ".." {
return filepath.SkipDir return filepath.SkipDir
} }
for _, ignore := range ignores { for _, ignore := range ignoresBase {
if ignore == base { if ignore == base {
return filepath.SkipDir 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)) { if ((opts.file && !isDir) || (opts.dir && isDir)) && r.pusher(stringBytes(path)) {
atomic.StoreInt32(&r.event, int32(EvtReadNew)) atomic.StoreInt32(&r.event, int32(EvtReadNew))
@@ -301,7 +332,11 @@ func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool
} }
return nil 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, signalReady func()) bool { func (r *Reader) readFromCommand(command string, environ []string, signalReady func()) bool {

View File

@@ -51,7 +51,8 @@ var placeholder *regexp.Regexp
var whiteSuffix *regexp.Regexp var whiteSuffix *regexp.Regexp
var offsetComponentRegex *regexp.Regexp var offsetComponentRegex *regexp.Regexp
var offsetTrimCharsRegex *regexp.Regexp var offsetTrimCharsRegex *regexp.Regexp
var passThroughRegex *regexp.Regexp var passThroughBeginRegex *regexp.Regexp
var passThroughEndTmuxRegex *regexp.Regexp
var ttyin *os.File var ttyin *os.File
const clearCode string = "\x1b[2J" const clearCode string = "\x1b[2J"
@@ -74,7 +75,15 @@ func init() {
// * https://sw.kovidgoyal.net/kitty/graphics-protocol // * https://sw.kovidgoyal.net/kitty/graphics-protocol
// * https://en.wikipedia.org/wiki/Sixel // * https://en.wikipedia.org/wiki/Sixel
// * https://iterm2.com/documentation-images.html // * 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 type jumpMode int
@@ -136,6 +145,7 @@ type previewer struct {
following resumableState following resumableState
spinner string spinner string
bar []bool bar []bool
xw [2]int
} }
type previewed struct { type previewed struct {
@@ -453,6 +463,8 @@ const (
actToggleTrackCurrent actToggleTrackCurrent
actToggleHeader actToggleHeader
actToggleWrap actToggleWrap
actToggleMultiLine
actToggleHscroll
actTrackCurrent actTrackCurrent
actUntrackCurrent actUntrackCurrent
actDown actDown
@@ -561,8 +573,6 @@ type searchRequest struct {
type previewRequest struct { type previewRequest struct {
template string template string
pwindow tui.Window
pwindowSize tui.TermSize
scrollOffset int scrollOffset int
list []*Item list []*Item
env []string env []string
@@ -849,7 +859,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
reqBox: util.NewEventBox(), reqBox: util.NewEventBox(),
initialPreviewOpts: opts.Preview, initialPreviewOpts: opts.Preview,
previewOpts: 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}, previewed: previewed{0, 0, 0, false, false, false, false},
previewBox: previewBox, previewBox: previewBox,
eventBox: eventBox, 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_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_LINE=%d", t.clickHeaderLine))
env = append(env, fmt.Sprintf("FZF_CLICK_HEADER_COLUMN=%d", t.clickHeaderColumn)) 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 return env
} }
func borderLines(shape tui.BorderShape) int { func borderLines(shape tui.BorderShape) int {
switch shape { lines := 0
case tui.BorderHorizontal, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble: if shape.HasTop() {
return 2 lines++
case tui.BorderTop, tui.BorderBottom:
return 1
} }
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 { func (t *Terminal) visibleHeaderLines() int {
@@ -1383,7 +1420,7 @@ const (
minHeight = 3 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 max := base - occupied
if max < minSize { if max < minSize {
max = minSize max = minSize
@@ -1391,7 +1428,22 @@ func calculateSize(base int, size sizeSpec, occupied int, minSize int, pad int)
if size.percent { if size.percent {
return util.Constrain(int(float64(base)*0.01*size.size), minSize, max) 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) { 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 minAreaHeight -= 1
} }
if t.needPreviewWindow() { if t.needPreviewWindow() {
minPreviewHeight := 1 + borderLines(t.previewOpts.border) minPreviewWidth, minPreviewHeight := t.minPreviewSize(t.activePreviewOpts)
minPreviewWidth := 5 switch t.activePreviewOpts.position {
switch t.previewOpts.position {
case posUp, posDown: case posUp, posDown:
minAreaHeight += minPreviewHeight minAreaHeight += minPreviewHeight
minAreaWidth = util.Max(minPreviewWidth, minAreaWidth) minAreaWidth = util.Max(minPreviewWidth, minAreaWidth)
@@ -1482,61 +1533,48 @@ func (t *Terminal) adjustMarginAndPadding() (int, int, [4]int, [4]int) {
return screenWidth, screenHeight, marginInt, paddingInt return screenWidth, screenHeight, marginInt, paddingInt
} }
func (t *Terminal) resizeWindows(forcePreview bool) { func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
t.forcePreview = forcePreview t.forcePreview = forcePreview
screenWidth, screenHeight, marginInt, paddingInt := t.adjustMarginAndPadding() screenWidth, screenHeight, marginInt, paddingInt := t.adjustMarginAndPadding()
width := screenWidth - marginInt[1] - marginInt[3] width := screenWidth - marginInt[1] - marginInt[3]
height := screenHeight - marginInt[0] - marginInt[2] height := screenHeight - marginInt[0] - marginInt[2]
t.prevLines = make([]itemLine, screenHeight) t.prevLines = make([]itemLine, util.Max(1, screenHeight))
if t.border != nil { if t.border != nil && redrawBorder {
t.border.Close() t.border = nil
} }
if t.window != nil { if t.window != nil {
t.window.Close()
t.window = nil t.window = nil
} }
if t.pborder != nil { if t.pborder != nil {
t.pborder.Close()
t.pborder = nil t.pborder = nil
} }
hadPreviewWindow := t.hasPreviewWindow() hadPreviewWindow := t.hasPreviewWindow()
if hadPreviewWindow { if hadPreviewWindow {
t.pwindow.Close()
t.pwindow = nil t.pwindow = nil
} }
// Reset preview version so that full redraw occurs // Reset preview version so that full redraw occurs
t.previewed.version = 0 t.previewed.version = 0
bw := t.borderWidth bw := t.borderWidth
switch t.borderShape { offsets := [4]int{} // TRWH
case tui.BorderHorizontal: 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( t.border = t.tui.NewWindow(
marginInt[0]-1, marginInt[3], width, height+2, marginInt[0]+offsets[0], marginInt[3]+offsets[1], width+offsets[2], height+offsets[3],
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,
false, tui.MakeBorderStyle(t.borderShape, t.unicode)) 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) { createPreviewWindow := func(y int, x int, w int, h int) {
pwidth := w pwidth := w
pheight := h pheight := h
var previewBorder tui.BorderStyle previewBorder := tui.MakeBorderStyle(previewOpts.border, t.unicode)
if previewOpts.border == tui.BorderNone {
previewBorder = tui.MakeTransparentBorder()
} else {
previewBorder = tui.MakeBorderStyle(previewOpts.border, t.unicode)
}
t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder) t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
switch previewOpts.border { pwidth -= borderColumns(previewOpts.border, bw)
case tui.BorderSharp, tui.BorderRounded, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble: pheight -= borderLines(previewOpts.border)
pwidth -= (1 + bw) * 2 if previewOpts.border.HasLeft() {
pheight -= 2
x += 1 + bw x += 1 + bw
}
if previewOpts.border.HasTop() {
y += 1 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() { if len(t.scrollbar) > 0 && !previewOpts.border.HasRight() {
// Need a column to show scrollbar // Need a column to show scrollbar
@@ -1604,19 +1622,14 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
t.pwindow.Erase() t.pwindow.Erase()
} }
} }
verticalPad := 2 minPreviewWidth, minPreviewHeight := t.minPreviewSize(previewOpts)
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
}
switch previewOpts.position { switch previewOpts.position {
case posUp, posDown: 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 { if hasThreshold && pheight < previewOpts.threshold {
t.activePreviewOpts = previewOpts.alternative t.activePreviewOpts = previewOpts.alternative
if forcePreview { if forcePreview {
@@ -1643,7 +1656,7 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight) createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
} }
case posLeft, posRight: case posLeft, posRight:
pwidth := calculateSize(width, previewOpts.size, minWidth, 5, 4) pwidth := calculateSize(width, previewOpts.size, minWidth, minPreviewWidth)
if hasThreshold && pwidth < previewOpts.threshold { if hasThreshold && pwidth < previewOpts.threshold {
t.activePreviewOpts = previewOpts.alternative t.activePreviewOpts = previewOpts.alternative
if forcePreview { if forcePreview {
@@ -1678,13 +1691,13 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height) createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
} else { } else {
// NOTE: fzf --preview 'cat {}' --preview-window border-left --border
if !previewOpts.border.HasRight() && t.borderShape.HasRight() {
width++
}
t.window = t.tui.NewWindow( t.window = t.tui.NewWindow(
marginInt[0], marginInt[3], width-pwidth, height, false, noBorder) marginInt[0], marginInt[3], width-pwidth, height, false, noBorder)
// NOTE: fzf --preview 'cat {}' --preview-window border-left --border
x := marginInt[3] + width - pwidth x := marginInt[3] + width - pwidth
if !previewOpts.border.HasRight() && t.borderShape.HasRight() {
pwidth++
}
createPreviewWindow(marginInt[0], x, pwidth, height) createPreviewWindow(marginInt[0], x, pwidth, height)
} }
} }
@@ -1709,7 +1722,7 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
// Print border label // Print border label
t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, false) 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) { 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 { func (t *Terminal) printBar(lineNum int, forceRedraw bool, barRange [2]int) bool {
hasBar := lineNum >= barRange[0] && lineNum < barRange[1] 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) t.move(lineNum, t.window.Width()-1, true)
if hasBar { if len(t.scrollbar) > 0 && hasBar {
t.window.CPrint(tui.ColScrollbar, t.scrollbar) 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) 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 { if (current || selected) && t.highlightLine {
color := tui.ColSelected color := tui.ColSelected
if current { if current {
@@ -2162,7 +2175,12 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
} }
newLine.width = maxWidth newLine.width = maxWidth
} else { } else {
fillSpaces := t.prevLines[lineNum].width - width var fillSpaces int
if forceRedraw {
fillSpaces = maxWidth - width
} else {
fillSpaces = t.prevLines[lineNum].width - width
}
if wrapped { if wrapped {
fillSpaces -= t.wrapSignWidth fillSpaces -= t.wrapSignWidth
} }
@@ -2274,7 +2292,7 @@ func (t *Terminal) overflow(runes []rune, max int) bool {
return t.displayWidthWithLimit(runes, 0, max) > max 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 var displayWidth int
item := result.item item := result.item
matchOffsets := []Offset{} matchOffsets := []Offset{}
@@ -2367,7 +2385,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
if t.layout == layoutDefault { if t.layout == layoutDefault {
actualLineNum = (lineNum - actualLineOffset) + (numItemLines - actualLineOffset) - 1 actualLineNum = (lineNum - actualLineOffset) + (numItemLines - actualLineOffset) - 1
} }
t.move(actualLineNum, 0, forceRedraw) t.move(actualLineNum, 0, forceRedraw && postTask == nil)
if preTask != nil { if preTask != nil {
var marker markerClass var marker markerClass
@@ -2471,7 +2489,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
t.printColoredString(t.window, line, offsets, colBase) t.printColoredString(t.window, line, offsets, colBase)
if postTask != nil { if postTask != nil {
postTask(actualLineNum, displayWidth, wasWrapped) postTask(actualLineNum, displayWidth, wasWrapped, forceRedraw)
} else { } else {
t.markOtherLine(actualLineNum) t.markOtherLine(actualLineNum)
} }
@@ -2526,7 +2544,7 @@ func (t *Terminal) renderPreviewSpinner() {
spin := t.previewer.spinner spin := t.previewer.spinner
if len(spin) > 0 || t.previewer.scrollable { if len(spin) > 0 || t.previewer.scrollable {
maxWidth := t.pwindow.Width() maxWidth := t.pwindow.Width()
if !t.previewer.scrollable || !t.previewOpts.info { if !t.previewer.scrollable || !t.activePreviewOpts.info {
if maxWidth > 0 { if maxWidth > 0 {
t.pwindow.Move(0, maxWidth-1) t.pwindow.Move(0, maxWidth-1)
t.pwindow.CPrint(tui.ColPreviewSpinner, spin) t.pwindow.CPrint(tui.ColPreviewSpinner, spin)
@@ -2568,7 +2586,7 @@ func (t *Terminal) renderPreviewArea(unchanged bool) {
height := t.pwindow.Height() height := t.pwindow.Height()
body := t.previewer.lines 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 // Do not enable preview header lines if it's value is too large
if headerLines > 0 && headerLines < util.Min(len(body), height) { if headerLines > 0 && headerLines < util.Min(len(body), height) {
header := t.previewer.lines[0:headerLines] 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 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) { func (t *Terminal) renderPreviewText(height int, lines []string, lineNo int, unchanged bool) {
maxWidth := t.pwindow.Width() maxWidth := t.pwindow.Width()
var ansi *ansiState var ansi *ansiState
@@ -2624,10 +2703,7 @@ Loop:
ansi.lbg = -1 ansi.lbg = -1
} }
passThroughs := passThroughRegex.FindAllString(line, -1) passThroughs, line := extractPassThroughs(line)
if passThroughs != nil {
line = passThroughRegex.ReplaceAllString(line, "")
}
line = strings.TrimLeft(strings.TrimRight(line, "\r\n"), "\r") line = strings.TrimLeft(strings.TrimRight(line, "\r\n"), "\r")
if lineNo >= height || t.pwindow.Y() == height-1 && t.pwindow.X() > 0 { 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 { _, _, ansi = extractColor(line, ansi, func(str string, ansi *ansiState) bool {
trimmed := []rune(str) trimmed := []rune(str)
isTrimmed := false isTrimmed := false
if !t.previewOpts.wrap { if !t.activePreviewOpts.wrap {
trimmed, isTrimmed = t.trimRight(trimmed, maxWidth-t.pwindow.X()) trimmed, isTrimmed = t.trimRight(trimmed, maxWidth-t.pwindow.X())
} }
if url == nil && ansi != nil && ansi.url != nil { if url == nil && ansi != nil && ansi.url != nil {
@@ -2736,7 +2812,7 @@ Loop:
} }
} }
return !isTrimmed && return !isTrimmed &&
(fillRet == tui.FillContinue || t.previewOpts.wrap && fillRet == tui.FillNextLine) (fillRet == tui.FillContinue || t.activePreviewOpts.wrap && fillRet == tui.FillNextLine)
}) })
if url != nil { if url != nil {
t.pwindow.LinkEnd() t.pwindow.LinkEnd()
@@ -2771,17 +2847,19 @@ Loop:
func (t *Terminal) renderPreviewScrollbar(yoff int, barLength int, barStart int) { func (t *Terminal) renderPreviewScrollbar(yoff int, barLength int, barStart int) {
height := t.pwindow.Height() height := t.pwindow.Height()
w := t.pborder.Width() w := t.pborder.Width()
xw := [2]int{t.pwindow.Left(), t.pwindow.Width()}
redraw := false redraw := false
if len(t.previewer.bar) != height { if len(t.previewer.bar) != height || t.previewer.xw != xw {
redraw = true redraw = true
t.previewer.bar = make([]bool, height) t.previewer.bar = make([]bool, height)
t.previewer.xw = xw
} }
xshift := -1 - t.borderWidth xshift := -1 - t.borderWidth
if !t.previewOpts.border.HasRight() { if !t.activePreviewOpts.border.HasRight() {
xshift = -1 xshift = -1
} }
yshift := 1 yshift := 1
if !t.previewOpts.border.HasTop() { if !t.activePreviewOpts.border.HasTop() {
yshift = 0 yshift = 0
} }
for i := yoff; i < height; i++ { for i := yoff; i < height; i++ {
@@ -2813,7 +2891,7 @@ func (t *Terminal) printPreview() {
unchanged := (t.previewed.filled || numLines == t.previewed.numLines) && unchanged := (t.previewed.filled || numLines == t.previewed.numLines) &&
t.previewer.version == t.previewed.version && t.previewer.version == t.previewed.version &&
t.previewer.offset == t.previewed.offset 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.renderPreviewArea(unchanged)
t.renderPreviewSpinner() t.renderPreviewSpinner()
t.previewed.numLines = numLines t.previewed.numLines = numLines
@@ -2856,7 +2934,7 @@ func (t *Terminal) processTabs(runes []rune, prefixWidth int) (string, int) {
} }
func (t *Terminal) printAll() { func (t *Terminal) printAll() {
t.resizeWindows(t.forcePreview) t.resizeWindows(t.forcePreview, true)
t.printList() t.printList()
t.printPrompt() t.printPrompt()
t.printInfo() t.printInfo()
@@ -3030,7 +3108,7 @@ func (t *Terminal) evaluateScrollOffset() int {
} }
// We only need the current item to calculate the scroll offset // 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) removeFiles(tempFiles)
offsetExpr := offsetTrimCharsRegex.ReplaceAllString(replaced, "") offsetExpr := offsetTrimCharsRegex.ReplaceAllString(replaced, "")
@@ -3043,7 +3121,7 @@ func (t *Terminal) evaluateScrollOffset() int {
} }
base := -1 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) { for _, component := range offsetComponentRegex.FindAllString(offsetExpr, -1) {
if strings.HasPrefix(component, "-/") { if strings.HasPrefix(component, "-/") {
component = component[1:] component = component[1:]
@@ -3289,12 +3367,12 @@ func (t *Terminal) hasPreviewer() bool {
} }
func (t *Terminal) needPreviewWindow() 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) // Check if previewer is currently in action (invisible previewer with size 0 or visible previewer)
func (t *Terminal) canPreview() bool { 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 { func (t *Terminal) hasPreviewWindow() bool {
@@ -3423,16 +3501,16 @@ func (t *Terminal) Loop() error {
t.tui.Resize(func(termHeight int) int { t.tui.Resize(func(termHeight int) int {
contentHeight := fit + t.extraLines() contentHeight := fit + t.extraLines()
if t.needPreviewWindow() { if t.needPreviewWindow() {
if t.previewOpts.aboveOrBelow() { if t.activePreviewOpts.aboveOrBelow() {
if t.previewOpts.size.percent { if t.activePreviewOpts.size.percent {
newContentHeight := int(float64(contentHeight) * 100. / (100. - t.previewOpts.size.size)) newContentHeight := int(float64(contentHeight) * 100. / (100. - t.activePreviewOpts.size.size))
contentHeight = util.Max(contentHeight+1+borderLines(t.previewOpts.border), newContentHeight) contentHeight = util.Max(contentHeight+1+borderLines(t.activePreviewOpts.border), newContentHeight)
} else { } else {
contentHeight += int(t.previewOpts.size.size) + borderLines(t.previewOpts.border) contentHeight += int(t.activePreviewOpts.size.size) + borderLines(t.activePreviewOpts.border)
} }
} else { } else {
// Minimum height if preview window can appear // 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) return util.Min(termHeight, contentHeight+pad)
@@ -3482,7 +3560,7 @@ func (t *Terminal) Loop() error {
return err return err
} }
t.termSize = t.tui.Size() t.termSize = t.tui.Size()
t.resizeWindows(false) t.resizeWindows(false, false)
t.window.Erase() t.window.Erase()
t.mutex.Unlock() t.mutex.Unlock()
@@ -3519,8 +3597,6 @@ func (t *Terminal) Loop() error {
for { for {
var items []*Item var items []*Item
var commandTemplate string var commandTemplate string
var pwindow tui.Window
var pwindowSize tui.TermSize
var env []string var env []string
initialOffset := 0 initialOffset := 0
t.previewBox.Wait(func(events *util.Events) { t.previewBox.Wait(func(events *util.Events) {
@@ -3534,8 +3610,6 @@ func (t *Terminal) Loop() error {
commandTemplate = request.template commandTemplate = request.template
initialOffset = request.scrollOffset initialOffset = request.scrollOffset
items = request.list items = request.list
pwindow = request.pwindow
pwindowSize = request.pwindowSize
env = request.env env = request.env
} }
} }
@@ -3553,16 +3627,6 @@ func (t *Terminal) Loop() error {
_, query := t.Input() _, query := t.Input()
command, tempFiles := t.replacePlaceholder(commandTemplate, false, string(query), items) command, tempFiles := t.replacePlaceholder(commandTemplate, false, string(query), items)
cmd := t.executor.ExecCommand(command, true) 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 cmd.Env = env
out, _ := cmd.StdoutPipe() out, _ := cmd.StdoutPipe()
@@ -3689,7 +3753,7 @@ func (t *Terminal) Loop() error {
if len(command) > 0 && t.canPreview() { if len(command) > 0 && t.canPreview() {
_, list := t.buildPlusList(command, false) _, list := t.buildPlusList(command, false)
t.cancelPreview() 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: case reqRedrawBorderLabel:
t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, true) t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, true)
case reqRedrawPreviewLabel: 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: case reqReinit:
t.tui.Resume(t.fullscreen, true) t.tui.Resume(t.fullscreen, true)
t.fullRedraw() t.fullRedraw()
@@ -3822,7 +3886,7 @@ func (t *Terminal) Loop() error {
result := value.(previewResult) result := value.(previewResult)
if t.previewer.version != result.version { if t.previewer.version != result.version {
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() { if t.previewer.following.Enabled() {
t.previewer.offset = 0 t.previewer.offset = 0
} }
@@ -3830,9 +3894,9 @@ func (t *Terminal) Loop() error {
t.previewer.lines = result.lines t.previewer.lines = result.lines
t.previewer.spinner = result.spinner t.previewer.spinner = result.spinner
if t.hasPreviewWindow() && t.previewer.following.Enabled() { 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 { } 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() t.printPreview()
case reqPreviewRefresh: case reqPreviewRefresh:
@@ -3888,6 +3952,7 @@ func (t *Terminal) Loop() error {
previewDraggingPos := -1 previewDraggingPos := -1
barDragging := false barDragging := false
pbarDragging := false pbarDragging := false
pborderDragging := false
wasDown := false wasDown := false
needBarrier := true needBarrier := true
@@ -3971,7 +4036,7 @@ func (t *Terminal) Loop() error {
} }
} }
updatePreviewWindow := func(forcePreview bool) { updatePreviewWindow := func(forcePreview bool) {
t.resizeWindows(forcePreview) t.resizeWindows(forcePreview, false)
req(reqPrompt, reqList, reqInfo, reqHeader) req(reqPrompt, reqList, reqInfo, reqHeader)
} }
toggle := func() bool { toggle := func() bool {
@@ -3987,8 +4052,8 @@ func (t *Terminal) Loop() error {
return return
} }
numLines := len(t.previewer.lines) numLines := len(t.previewer.lines)
headerLines := t.previewOpts.headerLines headerLines := t.activePreviewOpts.headerLines
if t.previewOpts.cycle { if t.activePreviewOpts.cycle {
offsetRange := numLines - headerLines offsetRange := numLines - headerLines
newOffset = ((newOffset-headerLines)+offsetRange)%offsetRange + headerLines newOffset = ((newOffset-headerLines)+offsetRange)%offsetRange + headerLines
} }
@@ -4074,7 +4139,7 @@ func (t *Terminal) Loop() error {
if valid { if valid {
t.cancelPreview() t.cancelPreview()
t.previewBox.Set(reqPreviewEnqueue, 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 { } else {
// Discard the preview content so that it won't accidentally appear // Discard the preview content so that it won't accidentally appear
@@ -4087,7 +4152,7 @@ func (t *Terminal) Loop() error {
} }
case actTogglePreviewWrap: case actTogglePreviewWrap:
if t.hasPreviewWindow() { if t.hasPreviewWindow() {
t.previewOpts.wrap = !t.previewOpts.wrap t.activePreviewOpts.wrap = !t.activePreviewOpts.wrap
// Reset preview version so that full redraw occurs // Reset preview version so that full redraw occurs
t.previewed.version = 0 t.previewed.version = 0
req(reqPreviewRefresh) req(reqPreviewRefresh)
@@ -4576,6 +4641,14 @@ func (t *Terminal) Loop() error {
case actToggleWrap: case actToggleWrap:
t.wrap = !t.wrap t.wrap = !t.wrap
req(reqList, reqHeader) 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: case actTrackCurrent:
if t.track == trackDisabled { if t.track == trackDisabled {
t.track = trackCurrent t.track = trackCurrent
@@ -4611,6 +4684,7 @@ func (t *Terminal) Loop() error {
if !me.Down { if !me.Down {
barDragging = false barDragging = false
pbarDragging = false pbarDragging = false
pborderDragging = false
previewDraggingPos = -1 previewDraggingPos = -1
} }
@@ -4648,7 +4722,7 @@ func (t *Terminal) Loop() error {
} }
// Preview scrollbar dragging // 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()) 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 { if pbarDragging {
effectiveHeight := t.pwindow.Height() - headerLines effectiveHeight := t.pwindow.Height() - headerLines
@@ -4665,6 +4739,55 @@ func (t *Terminal) Loop() error {
break 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 // Ignored
if !t.window.Enclose(my, mx) && !barDragging { if !t.window.Enclose(my, mx) && !barDragging {
break break
@@ -4808,6 +4931,7 @@ func (t *Terminal) Loop() error {
refreshPreview(t.previewOpts.command) refreshPreview(t.previewOpts.command)
} }
case actChangePreviewWindow: case actChangePreviewWindow:
// NOTE: We intentionally use "previewOpts" instead of "activePreviewOpts" here
currentPreviewOpts := t.previewOpts currentPreviewOpts := t.previewOpts
// Reset preview options and apply the additional options // Reset preview options and apply the additional options
@@ -4825,7 +4949,8 @@ func (t *Terminal) Loop() error {
} }
// Full redraw // 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 // Preview command can be running in the background if the size of
// the preview window is 0 but not 'hidden' // the preview window is 0 but not 'hidden'
wasHidden := currentPreviewOpts.hidden wasHidden := currentPreviewOpts.hidden
@@ -4833,20 +4958,20 @@ func (t *Terminal) Loop() error {
if wasHidden && t.hasPreviewWindow() { if wasHidden && t.hasPreviewWindow() {
// Restart // Restart
refreshPreview(t.previewOpts.command) refreshPreview(t.previewOpts.command)
} else if t.previewOpts.hidden { } else if t.activePreviewOpts.hidden {
// Cancel // Cancel
t.cancelPreview() t.cancelPreview()
} else { } else {
// Refresh // Refresh
req(reqPreviewRefresh) req(reqPreviewRefresh)
} }
} else if !currentPreviewOpts.sameContentLayout(t.previewOpts) { case previewOptsDifferentContentLayout:
t.previewed.version = 0 t.previewed.version = 0
req(reqPreviewRefresh) req(reqPreviewRefresh)
} }
// Adjust scroll offset // Adjust scroll offset
if t.hasPreviewWindow() && currentPreviewOpts.scroll != t.previewOpts.scroll { if t.hasPreviewWindow() && currentPreviewOpts.scroll != t.activePreviewOpts.scroll {
scrollPreviewTo(t.evaluateScrollOffset()) 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 */ /* utilities section */
// Item represents one line in fzf UI. Usually it is relative path to files and folders. // 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 { for _, arg := range args {
argStr += " " + escapeSingleQuote(arg) argStr += " " + escapeSingleQuote(arg)
} }
argStr += ` --no-tmux --no-height` argStr += ` --no-popup --no-height`
// Get current directory // Get current directory
dir, err := os.Getwd() dir, err := os.Getwd()

View File

@@ -73,11 +73,15 @@ func (r *LightRenderer) csi(code string) string {
func (r *LightRenderer) flush() { func (r *LightRenderer) flush() {
if r.queued.Len() > 0 { 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() r.queued.Reset()
} }
} }
func (r *LightRenderer) flushRaw(sequence string) {
fmt.Fprint(r.ttyout, sequence)
}
// Light renderer // Light renderer
type LightRenderer struct { type LightRenderer struct {
closed *util.AtomicBool closed *util.AtomicBool
@@ -655,11 +659,13 @@ func (r *LightRenderer) mouseSequence(sz *int) Event {
} }
func (r *LightRenderer) smcup() { func (r *LightRenderer) smcup() {
r.csi("?1049h") r.flush()
r.flushRaw("\x1b[?1049h")
} }
func (r *LightRenderer) rmcup() { func (r *LightRenderer) rmcup() {
r.csi("?1049l") r.flush()
r.flushRaw("\x1b[?1049l")
} }
func (r *LightRenderer) Pause(clear bool) { func (r *LightRenderer) Pause(clear bool) {
@@ -929,9 +935,6 @@ func (w *LightWindow) Height() int {
func (w *LightWindow) Refresh() { func (w *LightWindow) Refresh() {
} }
func (w *LightWindow) Close() {
}
func (w *LightWindow) X() int { func (w *LightWindow) X() int {
return w.posx return w.posx
} }

View File

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

View File

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

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']) writelines(['=' * 10_000 + '0123456789'])
[0, 3, 6].each do |off| [0, 3, 6].each do |off|
tmux.prepare 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.until { |lines| assert lines[-3]&.end_with?((0..off).to_a.join + '··') }
tmux.send_keys '9' tmux.send_keys '9'
tmux.until { |lines| assert lines[-3]&.end_with?('789') } 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 tmux.send_keys :Enter
end end
end end
@@ -2133,7 +2137,11 @@ class TestGoFZF < TestBase
end end
def test_keep_right 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') } tmux.until { |lines| assert lines.any_include?('9999␊10000') }
end end
@@ -3073,6 +3081,21 @@ class TestGoFZF < TestBase
tmux.until { |lines| assert_includes lines, '/1/1/' } tmux.until { |lines| assert_includes lines, '/1/1/' }
end 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 def test_become
tmux.send_keys "seq 100 | #{FZF} --bind 'enter:become:seq {} | #{FZF}'", :Enter tmux.send_keys "seq 100 | #{FZF} --bind 'enter:become:seq {} | #{FZF}'", :Enter
tmux.until { |lines| assert_equal 100, lines.item_count } tmux.until { |lines| assert_equal 100, lines.item_count }
@@ -3737,6 +3760,23 @@ module CompletionTest
tmux.until { |lines| assert_equal 'unset FZFFOOBAR', lines[-1] } tmux.until { |lines| assert_equal 'unset FZFFOOBAR', lines[-1] }
end 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 def test_file_completion_unicode
FileUtils.mkdir_p('/tmp/fzf-test') 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'" 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'"