Compare commits

...

56 Commits

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

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

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

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

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

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

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

Resolves #3632

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

* Refactor append_line function to improve line existence check.

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

* Fix indentation

---------

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-12 00:32:12 +09:00
26 changed files with 859 additions and 395 deletions

View File

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

View File

@@ -1,6 +1,29 @@
CHANGELOG
=========
0.57.0
------
- You can now resize the preview window by dragging the border
- Built-in walker improvements
- `--walker-root` can take multiple directory arguments. e.g. `--walker-root include src lib`
- `--walker-skip` can handle multi-component patterns. e.g. `--walker-skip target/build`
- Removed long processing delay when displaying images in the preview window
- `FZF_PREVIEW_*` environment variables are exported to all child processes (#4098)
- Bug fixes in fish scripts
0.56.3
------
- Bug fixes in zsh scripts
- fix(zsh): handle backtick trigger edge case (#4090)
- revert(zsh): remove 'fc -RI' call in the history widget (#4093)
- Thanks to @LangLangBart for the contributions
0.56.2
------
- Bug fixes
- Fixed abnormal scrolling behavior when `--wrap` is set (#4083)
- [zsh] Fixed warning message when `ksh_arrays` is set (#4084)
0.56.1
------
- Bug fixes and improvements

File diff suppressed because one or more lines are too long

4
go.mod
View File

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

8
go.sum
View File

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

41
install
View File

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

View File

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

View File

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

View File

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
..
.TH fzf\-tmux 1 "Nov 2024" "fzf 0.56.1" "fzf\-tmux - open fzf in tmux split pane"
.TH fzf\-tmux 1 "Dec 2024" "fzf 0.57.0" "fzf\-tmux - open fzf in tmux split pane"
.SH NAME
fzf\-tmux - open fzf in tmux split pane

View File

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

View File

@@ -122,11 +122,10 @@ __fzf_comprun() {
# Extract the name of the command. e.g. ls; foo=1 ssh **<tab>
__fzf_extract_command() {
setopt localoptions noksh_arrays
# Control completion with the "compstate" parameter, insert and list noting
# Control completion with the "compstate" parameter, insert and list nothing
compstate[insert]=
compstate[list]=
cmd_word="${words[1]}"
cmd_word="${(Q)words[1]}"
}
__fzf_generic_path_completion() {
@@ -303,16 +302,9 @@ _fzf_complete_kill_post() {
}
fzf-completion() {
trap 'unset cmd_word' EXIT
local tokens prefix trigger tail matches lbuf d_cmds
local tokens prefix trigger tail matches lbuf d_cmds cursor_pos cmd_word
setopt localoptions noshwordsplit noksh_arrays noposixbuiltins
# 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/Doc/Release/Expansion.html#Parameter-Expansion-Flags
tokens=(${(z)LBUFFER})
@@ -323,7 +315,7 @@ fzf-completion() {
# Explicitly allow for empty trigger.
trigger=${FZF_COMPLETION_TRIGGER-'**'}
[ -z "$trigger" -a ${LBUFFER[-1]} = ' ' ] && tokens+=("")
[[ -z $trigger && ${LBUFFER[-1]} == ' ' ]] && tokens+=("")
# When the trigger starts with ';', it becomes a separate token
if [[ ${LBUFFER} = *"${tokens[-2]-}${tokens[-1]}" ]]; then
@@ -338,9 +330,26 @@ fzf-completion() {
if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS-cd pushd rmdir})
# Make the 'cmd_word' global
zle __fzf_extract_command || :
[[ -z "$cmd_word" ]] && return
{
cursor_pos=$CURSOR
# Move the cursor before the trigger to preserve word array elements when
# trigger chars like ';' or '`' would otherwise reset the 'words' array.
CURSOR=$((cursor_pos - ${#trigger} - 1))
# Check if at least one completion system (old or new) is active.
# If at least one user-defined completion widget is detected, nothing will
# be completed if neither the old nor the new completion system is enabled.
# In such cases, the 'zsh/compctl' module is loaded as a fallback.
if ! zmodload -F zsh/parameter p:functions 2>/dev/null || ! (( ${+functions[compdef]} )); then
zmodload -F zsh/compctl 2>/dev/null
fi
# Create a completion widget to access the 'words' array (man zshcompwid)
zle -C __fzf_extract_command .complete-word __fzf_extract_command
zle __fzf_extract_command
} always {
CURSOR=$cursor_pos
# Delete the completion widget
zle -D __fzf_extract_command 2>/dev/null
}
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
if [[ $prefix = *'$('* ]] || [[ $prefix = *'<('* ]] || [[ $prefix = *'>('* ]] || [[ $prefix = *':='* ]] || [[ $prefix = *'`'* ]]; then
@@ -348,7 +357,7 @@ fzf-completion() {
fi
[ -n "${tokens[-1]}" ] && lbuf=${lbuf:0:-${#tokens[-1]}}
if eval "type _fzf_complete_${cmd_word} > /dev/null"; then
if eval "noglob type _fzf_complete_${cmd_word} >/dev/null"; then
prefix="$prefix" eval _fzf_complete_${cmd_word} ${(q)lbuf}
zle reset-prompt
elif [ ${d_cmds[(i)$cmd_word]} -le ${#d_cmds} ]; then
@@ -368,8 +377,6 @@ fzf-completion() {
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
zle -N fzf-completion
bindkey '^I' fzf-completion

View File

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

View File

@@ -111,17 +111,11 @@ fzf-history-widget() {
# Ensure the module is loaded if not already, and the required features, such
# as the associative 'history' array, which maps event numbers to full history
# lines, are set. Also, make sure Perl is installed for multi-line output.
if zmodload -F zsh/parameter p:{commands,history} 2>/dev/null && (( $+commands[perl] )); then
# Import commands from other shells if SHARE_HISTORY is enabled, as the
# 'history' array only updates after executing a non-empty command.
selected="$(
if [[ -o sharehistory ]]; then
fc -RI
fi
printf '%s\t%s\000' "${(kv)history[@]}" |
perl -0 -ne 'if (!$seen{(/^\s*[0-9]+\**\t(.*)/s, $1)}++) { s/\n/\n\t/g; print; }' |
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '\t↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m --read0") \
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
if zmodload -F zsh/parameter p:{commands,history} 2>/dev/null && (( ${+commands[perl]} )); then
selected="$(printf '%s\t%s\000' "${(kv)history[@]}" |
perl -0 -ne 'if (!$seen{(/^\s*[0-9]+\**\t(.*)/s, $1)}++) { s/\n/\n\t/g; print; }' |
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '\t↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m --read0") \
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
else
selected="$(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '\t↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m") \

View File

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

View File

@@ -39,8 +39,13 @@ func (r revision) compatible(other revision) bool {
// Run starts fzf
func Run(opts *Options) (int, error) {
if opts.Filter == nil {
if opts.Tmux != nil && len(os.Getenv("TMUX")) > 0 && opts.Tmux.index >= opts.Height.index {
return runTmux(os.Args, opts)
if opts.Tmux != nil && opts.Tmux.index >= opts.Height.index {
if len(os.Getenv("TMUX")) > 0 {
return runTmux(os.Args, opts)
}
if len(os.Getenv("ZELLIJ")) > 0 {
return runZellij(os.Args, opts)
}
}
if needWinpty(opts) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

103
src/zellij.go Normal file
View File

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

View File

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