mirror of
https://github.com/junegunn/fzf.git
synced 2025-08-29 11:23:50 -07:00
Compare commits
65 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
0476a65fca | ||
|
2cb2af115a | ||
|
789226ff6d | ||
|
805efc5bf1 | ||
|
cdcab26766 | ||
|
ec3acb1932 | ||
|
d30e37434e | ||
|
20d5b2e20e | ||
|
6c6be4ab1a | ||
|
d004eb1f7c | ||
|
3148b0f3e8 | ||
|
3fc0bd26a5 | ||
|
6c9025ff17 | ||
|
289997e373 | ||
|
db44cbdff0 | ||
|
da9179335c | ||
|
cdf641fa3e | ||
|
66dbee10f5 | ||
|
19e9b620ba | ||
|
e4e4700aff | ||
|
bb55045596 | ||
|
d7e51cdeb5 | ||
|
7f4964b366 | ||
|
a6957aba11 | ||
|
b5f94f961d | ||
|
e182d3db7a | ||
|
3e6e0528a6 | ||
|
ac508a1ce4 | ||
|
d7fc1e09b1 | ||
|
3b0c86e401 | ||
|
61d10d8ffa | ||
|
7d9548919e | ||
|
bee80a730f | ||
|
ac3e24c99c | ||
|
e7e852bdb3 | ||
|
2b7f168571 | ||
|
5b3da1d878 | ||
|
99f1bc0177 | ||
|
ed76f076dd | ||
|
4d357d1063 | ||
|
961ae1541c | ||
|
add1aec685 | ||
|
03d6ba7496 | ||
|
71e4d5cc51 | ||
|
215ab48222 | ||
|
0c64c68781 | ||
|
3ec035c68b | ||
|
20c7dcfbca | ||
|
c1b8780b9c | ||
|
64c61603e9 | ||
|
57c08d925f | ||
|
51623a5f6a | ||
|
ca3f6181d7 | ||
|
9c94f9c3d0 | ||
|
4a85843bcf | ||
|
d4d9b99879 | ||
|
6816b7d95b | ||
|
acdf265d7a | ||
|
19495eb9bb | ||
|
bacc8609ee | ||
|
99163f5afa | ||
|
0607227730 | ||
|
d938fdc496 | ||
|
dcb4c3d84a | ||
|
82ebcd9209 |
2
.github/workflows/typos.yml
vendored
2
.github/workflows/typos.yml
vendored
@@ -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.26.0
|
- uses: crate-ci/typos@v1.28.2
|
||||||
|
33
CHANGELOG.md
33
CHANGELOG.md
@@ -1,6 +1,39 @@
|
|||||||
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
|
||||||
|
------
|
||||||
|
- Bug fixes in zsh scripts
|
||||||
|
- fix(zsh): handle backtick trigger edge case (#4090)
|
||||||
|
- revert(zsh): remove 'fc -RI' call in the history widget (#4093)
|
||||||
|
- Thanks to @LangLangBart for the contributions
|
||||||
|
|
||||||
|
0.56.2
|
||||||
|
------
|
||||||
|
- Bug fixes
|
||||||
|
- Fixed abnormal scrolling behavior when `--wrap` is set (#4083)
|
||||||
|
- [zsh] Fixed warning message when `ksh_arrays` is set (#4084)
|
||||||
|
|
||||||
|
0.56.1
|
||||||
|
------
|
||||||
|
- Bug fixes and improvements
|
||||||
|
- Fixed a race condition which would cause fzf to present stale results after `reload` (#4070)
|
||||||
|
- `page-up` and `page-down` actions now work correctly with multi-line items (#4069)
|
||||||
|
- `{n}` is allowed in `SCROLL` expression in `--preview-window` (#4079)
|
||||||
|
- [zsh] Fixed regression in history loading with shared option (#4071)
|
||||||
|
- [zsh] Better command extraction in zsh completion (#4082)
|
||||||
|
- Thanks to @LangLangBart, @jaydee-coder, @alex-huff, and @vejkse for the contributions
|
||||||
|
|
||||||
0.56.0
|
0.56.0
|
||||||
------
|
------
|
||||||
- Added `--gap[=N]` option to display empty lines between items.
|
- Added `--gap[=N]` option to display empty lines between items.
|
||||||
|
@@ -8,5 +8,5 @@ RUN echo '. ~/.bashrc' >> ~/.bash_profile
|
|||||||
RUN rm -f /etc/bash.bashrc
|
RUN rm -f /etc/bash.bashrc
|
||||||
COPY . /fzf
|
COPY . /fzf
|
||||||
RUN cd /fzf && make install && ./install --all
|
RUN cd /fzf && make install && ./install --all
|
||||||
ENV LANG C.UTF-8
|
ENV LANG=C.UTF-8
|
||||||
CMD tmux new 'set -o pipefail; ruby /fzf/test/test_go.rb | tee out && touch ok' && cat out && [ -e ok ]
|
CMD ["bash", "-ic", "tmux new 'set -o pipefail; ruby /fzf/test/test_go.rb | tee out && touch ok' && cat out && [ -e ok ]"]
|
||||||
|
4
go.mod
4
go.mod
@@ -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.26.0
|
golang.org/x/sys v0.28.0
|
||||||
golang.org/x/term v0.25.0
|
golang.org/x/term v0.27.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
8
go.sum
8
go.sum
@@ -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.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
golang.org/x/sys v0.26.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.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
|
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||||
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
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=
|
||||||
|
41
install
41
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.56.0
|
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
|
||||||
|
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
|
else
|
||||||
if [ $update -eq 1 ]; then
|
echo " ~ Skipped"
|
||||||
[ -f "$file" ] && echo >> "$file"
|
|
||||||
echo "$line" >> "$file"
|
|
||||||
echo " + Added"
|
|
||||||
else
|
|
||||||
echo " ~ Skipped"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
set +e
|
set +e
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
$version="0.56.0"
|
$version="0.57.0"
|
||||||
|
|
||||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
|
||||||
|
2
main.go
2
main.go
@@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/protector"
|
"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
|
||||||
|
@@ -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 "Oct 2024" "fzf 0.56.0" "fzf\-tmux - open fzf in tmux split pane"
|
.TH fzf\-tmux 1 "Dec 2024" "fzf 0.57.0" "fzf\-tmux - open fzf in tmux split pane"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf\-tmux - open fzf in tmux split pane
|
fzf\-tmux - open fzf in tmux split pane
|
||||||
|
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf 1 "Oct 2024" "fzf 0.56.0" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Dec 2024" "fzf 0.57.0" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -805,7 +805,7 @@ e.g. \fBborder\-rounded\fR (border with rounded edges, default),
|
|||||||
* \fB[:+SCROLL[OFFSETS][/DENOM]]\fR determines the initial scroll offset of the
|
* \fB[:+SCROLL[OFFSETS][/DENOM]]\fR determines the initial scroll offset of the
|
||||||
preview window.
|
preview window.
|
||||||
|
|
||||||
- \fBSCROLL\fR can be either a numeric integer or a single-field index expression that refers to a numeric integer.
|
- \fBSCROLL\fR can be either a numeric integer or a single-field index expression that refers to a numeric integer or {n} to refer to the zero-based ordinal index of the current item.
|
||||||
|
|
||||||
- The optional \fBOFFSETS\fR part is for adjusting the base offset. It should be given as a series of signed integers (\fB\-INTEGER\fR or \fB+INTEGER\fR).
|
- The optional \fBOFFSETS\fR part is for adjusting the base offset. It should be given as a series of signed integers (\fB\-INTEGER\fR or \fB+INTEGER\fR).
|
||||||
|
|
||||||
@@ -1005,8 +1005,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 +1122,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"
|
||||||
|
@@ -120,25 +120,18 @@ __fzf_comprun() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Extract the name of the command. e.g. foo=1 bar baz**<tab>
|
# Extract the name of the command. e.g. ls; foo=1 ssh **<tab>
|
||||||
__fzf_extract_command() {
|
__fzf_extract_command() {
|
||||||
local token tokens
|
# Control completion with the "compstate" parameter, insert and list nothing
|
||||||
tokens=(${(z)1})
|
compstate[insert]=
|
||||||
for token in $tokens; do
|
compstate[list]=
|
||||||
token=${(Q)token}
|
cmd_word="${(Q)words[1]}"
|
||||||
if [[ "$token" =~ [[:alnum:]] && ! "$token" =~ "=" ]]; then
|
|
||||||
echo "$token"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo "${tokens[1]}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzf_generic_path_completion() {
|
__fzf_generic_path_completion() {
|
||||||
local base lbuf cmd compgen fzf_opts suffix tail dir leftover matches
|
local base lbuf compgen fzf_opts suffix tail dir leftover matches
|
||||||
base=$1
|
base=$1
|
||||||
lbuf=$2
|
lbuf=$2
|
||||||
cmd=$(__fzf_extract_command "$lbuf")
|
|
||||||
compgen=$3
|
compgen=$3
|
||||||
fzf_opts=$4
|
fzf_opts=$4
|
||||||
suffix=$5
|
suffix=$5
|
||||||
@@ -161,7 +154,7 @@ __fzf_generic_path_completion() {
|
|||||||
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --scheme=path" "${FZF_COMPLETION_OPTS-}")
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --scheme=path" "${FZF_COMPLETION_OPTS-}")
|
||||||
unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE
|
unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE
|
||||||
if declare -f "$compgen" > /dev/null; then
|
if declare -f "$compgen" > /dev/null; then
|
||||||
eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover"
|
eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd_word" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover"
|
||||||
else
|
else
|
||||||
if [[ $compgen =~ dir ]]; then
|
if [[ $compgen =~ dir ]]; then
|
||||||
walker=dir,follow
|
walker=dir,follow
|
||||||
@@ -170,7 +163,7 @@ __fzf_generic_path_completion() {
|
|||||||
walker=file,dir,follow,hidden
|
walker=file,dir,follow,hidden
|
||||||
rest=${FZF_COMPLETION_PATH_OPTS-}
|
rest=${FZF_COMPLETION_PATH_OPTS-}
|
||||||
fi
|
fi
|
||||||
__fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" --walker "$walker" --walker-root="$dir" ${(Q)${(Z+n+)rest}} < /dev/tty
|
__fzf_comprun "$cmd_word" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" --walker "$walker" --walker-root="$dir" ${(Q)${(Z+n+)rest}} < /dev/tty
|
||||||
fi | while read -r item; do
|
fi | while read -r item; do
|
||||||
item="${item%$suffix}$suffix"
|
item="${item%$suffix}$suffix"
|
||||||
echo -n -E "${(q)item} "
|
echo -n -E "${(q)item} "
|
||||||
@@ -227,10 +220,9 @@ _fzf_complete() {
|
|||||||
rest=("$@")
|
rest=("$@")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local fifo lbuf cmd matches post
|
local fifo lbuf matches post
|
||||||
fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$"
|
fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$"
|
||||||
lbuf=${rest[0]}
|
lbuf=${rest[0]}
|
||||||
cmd=$(__fzf_extract_command "$lbuf")
|
|
||||||
post="${funcstack[1]}_post"
|
post="${funcstack[1]}_post"
|
||||||
type $post > /dev/null 2>&1 || post=cat
|
type $post > /dev/null 2>&1 || post=cat
|
||||||
|
|
||||||
@@ -238,7 +230,7 @@ _fzf_complete() {
|
|||||||
matches=$(
|
matches=$(
|
||||||
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse" "${FZF_COMPLETION_OPTS-} $str_arg") \
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse" "${FZF_COMPLETION_OPTS-} $str_arg") \
|
||||||
FZF_DEFAULT_OPTS_FILE='' \
|
FZF_DEFAULT_OPTS_FILE='' \
|
||||||
__fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
|
__fzf_comprun "$cmd_word" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
|
||||||
if [ -n "$matches" ]; then
|
if [ -n "$matches" ]; then
|
||||||
LBUFFER="$lbuf$matches"
|
LBUFFER="$lbuf$matches"
|
||||||
fi
|
fi
|
||||||
@@ -310,7 +302,7 @@ _fzf_complete_kill_post() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fzf-completion() {
|
fzf-completion() {
|
||||||
local tokens cmd prefix trigger tail matches lbuf d_cmds
|
local tokens prefix trigger tail matches lbuf d_cmds cursor_pos cmd_word
|
||||||
setopt localoptions noshwordsplit noksh_arrays noposixbuiltins
|
setopt localoptions noshwordsplit noksh_arrays noposixbuiltins
|
||||||
|
|
||||||
# http://zsh.sourceforge.net/FAQ/zshfaq03.html
|
# http://zsh.sourceforge.net/FAQ/zshfaq03.html
|
||||||
@@ -321,11 +313,9 @@ fzf-completion() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cmd=$(__fzf_extract_command "$LBUFFER")
|
|
||||||
|
|
||||||
# Explicitly allow for empty trigger.
|
# Explicitly allow for empty trigger.
|
||||||
trigger=${FZF_COMPLETION_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
|
# When the trigger starts with ';', it becomes a separate token
|
||||||
if [[ ${LBUFFER} = *"${tokens[-2]-}${tokens[-1]}" ]]; then
|
if [[ ${LBUFFER} = *"${tokens[-2]-}${tokens[-1]}" ]]; then
|
||||||
@@ -340,16 +330,37 @@ 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
|
||||||
|
# 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}}
|
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
|
||||||
if [[ $prefix = *'$('* ]] || [[ $prefix = *'<('* ]] || [[ $prefix = *'>('* ]] || [[ $prefix = *':='* ]] || [[ $prefix = *'`'* ]]; then
|
if [[ $prefix = *'$('* ]] || [[ $prefix = *'<('* ]] || [[ $prefix = *'>('* ]] || [[ $prefix = *':='* ]] || [[ $prefix = *'`'* ]]; then
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
[ -n "${tokens[-1]}" ] && lbuf=${lbuf:0:-${#tokens[-1]}}
|
[ -n "${tokens[-1]}" ] && lbuf=${lbuf:0:-${#tokens[-1]}}
|
||||||
|
|
||||||
if eval "type _fzf_complete_${cmd} > /dev/null"; then
|
if eval "noglob type _fzf_complete_${cmd_word} >/dev/null"; then
|
||||||
prefix="$prefix" eval _fzf_complete_${cmd} ${(q)lbuf}
|
prefix="$prefix" eval _fzf_complete_${cmd_word} ${(q)lbuf}
|
||||||
zle reset-prompt
|
zle reset-prompt
|
||||||
elif [ ${d_cmds[(i)$cmd]} -le ${#d_cmds} ]; then
|
elif [ ${d_cmds[(i)$cmd_word]} -le ${#d_cmds} ]; then
|
||||||
_fzf_dir_completion "$prefix" "$lbuf"
|
_fzf_dir_completion "$prefix" "$lbuf"
|
||||||
else
|
else
|
||||||
_fzf_path_completion "$prefix" "$lbuf"
|
_fzf_path_completion "$prefix" "$lbuf"
|
||||||
@@ -366,6 +377,7 @@ fzf-completion() {
|
|||||||
unset binding
|
unset binding
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Normal widget
|
||||||
zle -N fzf-completion
|
zle -N fzf-completion
|
||||||
bindkey '^I' fzf-completion
|
bindkey '^I' fzf-completion
|
||||||
fi
|
fi
|
||||||
|
@@ -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 ''
|
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)' | command perl -pe 's/^\d*\t//' | read -lz result
|
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,50 @@ 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)
|
||||||
|
|
||||||
|
# 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 1 -- $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")
|
||||||
|
@@ -108,9 +108,10 @@ fi
|
|||||||
fzf-history-widget() {
|
fzf-history-widget() {
|
||||||
local selected
|
local selected
|
||||||
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases noglob nobash_rematch 2> /dev/null
|
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases noglob nobash_rematch 2> /dev/null
|
||||||
# Ensure the associative history array, which maps event numbers to the full
|
# Ensure the module is loaded if not already, and the required features, such
|
||||||
# history lines, is loaded, and that Perl is installed for multi-line output.
|
# as the associative 'history' array, which maps event numbers to full history
|
||||||
if zmodload -F zsh/parameter p:history 2>/dev/null && (( ${#commands[perl]} )); then
|
# lines, are set. Also, make sure Perl is installed for multi-line output.
|
||||||
|
if zmodload -F zsh/parameter p:{commands,history} 2>/dev/null && (( ${+commands[perl]} )); then
|
||||||
selected="$(printf '%s\t%s\000' "${(kv)history[@]}" |
|
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; }' |
|
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=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '\t↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m --read0") \
|
||||||
|
10
src/core.go
10
src/core.go
@@ -172,7 +172,9 @@ func Run(opts *Options) (int, error) {
|
|||||||
return chunkList.Push(data)
|
return chunkList.Push(data)
|
||||||
}, eventBox, executor, opts.ReadZero, opts.Filter == nil)
|
}, eventBox, executor, opts.ReadZero, opts.Filter == nil)
|
||||||
|
|
||||||
go reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv)
|
readyChan := make(chan bool)
|
||||||
|
go reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv, readyChan)
|
||||||
|
<-readyChan
|
||||||
}
|
}
|
||||||
|
|
||||||
// Matcher
|
// Matcher
|
||||||
@@ -224,7 +226,7 @@ func Run(opts *Options) (int, error) {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}, eventBox, executor, opts.ReadZero, false)
|
}, eventBox, executor, opts.ReadZero, false)
|
||||||
reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv)
|
reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv, nil)
|
||||||
} else {
|
} else {
|
||||||
eventBox.Unwatch(EvtReadNew)
|
eventBox.Unwatch(EvtReadNew)
|
||||||
eventBox.WaitFor(EvtReadFin)
|
eventBox.WaitFor(EvtReadFin)
|
||||||
@@ -299,7 +301,9 @@ func Run(opts *Options) (int, error) {
|
|||||||
itemIndex = 0
|
itemIndex = 0
|
||||||
inputRevision.bumpMajor()
|
inputRevision.bumpMajor()
|
||||||
header = make([]string, 0, opts.HeaderLines)
|
header = make([]string, 0, opts.HeaderLines)
|
||||||
go reader.restart(command, environ)
|
readyChan := make(chan bool)
|
||||||
|
go reader.restart(command, environ, readyChan)
|
||||||
|
<-readyChan
|
||||||
}
|
}
|
||||||
|
|
||||||
exitCode := ExitOk
|
exitCode := ExitOk
|
||||||
|
@@ -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)
|
||||||
|
|
||||||
@@ -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 {
|
||||||
@@ -1726,7 +1780,7 @@ func parsePreviewWindowImpl(opts *previewOpts, input string) error {
|
|||||||
var err error
|
var err error
|
||||||
tokenRegex := regexp.MustCompile(`[:,]*(<([1-9][0-9]*)\(([^)<]+)\)|[^,:]+)`)
|
tokenRegex := regexp.MustCompile(`[:,]*(<([1-9][0-9]*)\(([^)<]+)\)|[^,:]+)`)
|
||||||
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
|
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
|
||||||
offsetRegex := regexp.MustCompile(`^(\+{-?[0-9]+})?([+-][0-9]+)*(-?/[1-9][0-9]*)?$`)
|
offsetRegex := regexp.MustCompile(`^(\+{(-?[0-9]+|n)})?([+-][0-9]+)*(-?/[1-9][0-9]*)?$`)
|
||||||
headerRegex := regexp.MustCompile("^~(0|[1-9][0-9]*)$")
|
headerRegex := regexp.MustCompile("^~(0|[1-9][0-9]*)$")
|
||||||
tokens := tokenRegex.FindAllStringSubmatch(input, -1)
|
tokens := tokenRegex.FindAllStringSubmatch(input, -1)
|
||||||
var alternative string
|
var alternative string
|
||||||
@@ -2487,7 +2541,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":
|
||||||
@@ -2685,7 +2739,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 {
|
||||||
|
122
src/reader.go
122
src/reader.go
@@ -6,8 +6,8 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@@ -25,16 +25,26 @@ type Reader struct {
|
|||||||
event int32
|
event int32
|
||||||
finChan chan bool
|
finChan chan bool
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
exec *exec.Cmd
|
|
||||||
execOut io.ReadCloser
|
|
||||||
command *string
|
|
||||||
killed bool
|
killed bool
|
||||||
|
termFunc func()
|
||||||
|
command *string
|
||||||
wait bool
|
wait bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewReader returns new Reader object
|
// NewReader returns new Reader object
|
||||||
func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, executor *util.Executor, delimNil bool, wait bool) *Reader {
|
func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, executor *util.Executor, delimNil bool, wait bool) *Reader {
|
||||||
return &Reader{pusher, executor, eventBox, delimNil, int32(EvtReady), make(chan bool, 1), sync.Mutex{}, nil, nil, nil, false, wait}
|
return &Reader{
|
||||||
|
pusher,
|
||||||
|
executor,
|
||||||
|
eventBox,
|
||||||
|
delimNil,
|
||||||
|
int32(EvtReady),
|
||||||
|
make(chan bool, 1),
|
||||||
|
sync.Mutex{},
|
||||||
|
false,
|
||||||
|
func() { os.Stdin.Close() },
|
||||||
|
nil,
|
||||||
|
wait}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) startEventPoller() {
|
func (r *Reader) startEventPoller() {
|
||||||
@@ -80,19 +90,19 @@ func (r *Reader) fin(success bool) {
|
|||||||
func (r *Reader) terminate() {
|
func (r *Reader) terminate() {
|
||||||
r.mutex.Lock()
|
r.mutex.Lock()
|
||||||
r.killed = true
|
r.killed = true
|
||||||
if r.exec != nil && r.exec.Process != nil {
|
if r.termFunc != nil {
|
||||||
r.execOut.Close()
|
r.termFunc()
|
||||||
util.KillCommand(r.exec)
|
r.termFunc = nil
|
||||||
} else {
|
|
||||||
os.Stdin.Close()
|
|
||||||
}
|
}
|
||||||
r.mutex.Unlock()
|
r.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) restart(command commandSpec, environ []string) {
|
func (r *Reader) restart(command commandSpec, environ []string, readyChan chan bool) {
|
||||||
r.event = int32(EvtReady)
|
r.event = int32(EvtReady)
|
||||||
r.startEventPoller()
|
r.startEventPoller()
|
||||||
success := r.readFromCommand(command.command, environ)
|
success := r.readFromCommand(command.command, environ, func() {
|
||||||
|
readyChan <- true
|
||||||
|
})
|
||||||
r.fin(success)
|
r.fin(success)
|
||||||
removeFiles(command.tempFiles)
|
removeFiles(command.tempFiles)
|
||||||
}
|
}
|
||||||
@@ -111,21 +121,29 @@ func (r *Reader) readChannel(inputChan chan string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReadSource reads data from the default command or from standard input
|
// ReadSource reads data from the default command or from standard input
|
||||||
func (r *Reader) ReadSource(inputChan chan string, root string, opts walkerOpts, ignores []string, initCmd string, initEnv []string) {
|
func (r *Reader) ReadSource(inputChan chan string, roots []string, opts walkerOpts, ignores []string, initCmd string, initEnv []string, readyChan chan bool) {
|
||||||
r.startEventPoller()
|
r.startEventPoller()
|
||||||
var success bool
|
var success bool
|
||||||
|
signalReady := func() {
|
||||||
|
if readyChan != nil {
|
||||||
|
readyChan <- true
|
||||||
|
}
|
||||||
|
}
|
||||||
if inputChan != nil {
|
if inputChan != nil {
|
||||||
|
signalReady()
|
||||||
success = r.readChannel(inputChan)
|
success = r.readChannel(inputChan)
|
||||||
} else if len(initCmd) > 0 {
|
} else if len(initCmd) > 0 {
|
||||||
success = r.readFromCommand(initCmd, initEnv)
|
success = r.readFromCommand(initCmd, initEnv, signalReady)
|
||||||
} else if util.IsTty(os.Stdin) {
|
} else if util.IsTty(os.Stdin) {
|
||||||
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
||||||
if len(cmd) == 0 {
|
if len(cmd) == 0 {
|
||||||
success = r.readFiles(root, opts, ignores)
|
signalReady()
|
||||||
|
success = r.readFiles(roots, opts, ignores)
|
||||||
} else {
|
} else {
|
||||||
success = r.readFromCommand(cmd, initEnv)
|
success = r.readFromCommand(cmd, initEnv, signalReady)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
signalReady()
|
||||||
success = r.readFromStdin()
|
success = r.readFromStdin()
|
||||||
}
|
}
|
||||||
r.fin(success)
|
r.fin(success)
|
||||||
@@ -248,14 +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 {
|
||||||
r.killed = false
|
|
||||||
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
|
||||||
@@ -268,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))
|
||||||
@@ -285,34 +332,39 @@ 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) bool {
|
func (r *Reader) readFromCommand(command string, environ []string, signalReady func()) bool {
|
||||||
r.mutex.Lock()
|
r.mutex.Lock()
|
||||||
|
|
||||||
r.killed = false
|
r.killed = false
|
||||||
|
r.termFunc = nil
|
||||||
r.command = &command
|
r.command = &command
|
||||||
r.exec = r.executor.ExecCommand(command, true)
|
exec := r.executor.ExecCommand(command, true)
|
||||||
if environ != nil {
|
if environ != nil {
|
||||||
r.exec.Env = environ
|
exec.Env = environ
|
||||||
}
|
}
|
||||||
|
execOut, err := exec.StdoutPipe()
|
||||||
var err error
|
if err != nil || exec.Start() != nil {
|
||||||
r.execOut, err = r.exec.StdoutPipe()
|
signalReady()
|
||||||
if err != nil {
|
|
||||||
r.exec = nil
|
|
||||||
r.mutex.Unlock()
|
r.mutex.Unlock()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
err = r.exec.Start()
|
// Function to call to terminate the running command
|
||||||
if err != nil {
|
r.termFunc = func() {
|
||||||
r.exec = nil
|
execOut.Close()
|
||||||
r.mutex.Unlock()
|
util.KillCommand(exec)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signalReady()
|
||||||
r.mutex.Unlock()
|
r.mutex.Unlock()
|
||||||
r.feed(r.execOut)
|
|
||||||
return r.exec.Wait() == nil
|
r.feed(execOut)
|
||||||
|
return exec.Wait() == nil
|
||||||
}
|
}
|
||||||
|
@@ -23,8 +23,12 @@ func TestReadFromCommand(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Normal command
|
// Normal command
|
||||||
reader.fin(reader.readFromCommand(`echo abc&&echo def`, nil))
|
counter := 0
|
||||||
if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" {
|
ready := func() {
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
reader.fin(reader.readFromCommand(`echo abc&&echo def`, nil, ready))
|
||||||
|
if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" || counter != 1 {
|
||||||
t.Errorf("%s", strs)
|
t.Errorf("%s", strs)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,9 +52,9 @@ func TestReadFromCommand(t *testing.T) {
|
|||||||
reader.startEventPoller()
|
reader.startEventPoller()
|
||||||
|
|
||||||
// Failing command
|
// Failing command
|
||||||
reader.fin(reader.readFromCommand(`no-such-command`, nil))
|
reader.fin(reader.readFromCommand(`no-such-command`, nil, ready))
|
||||||
strs = []string{}
|
strs = []string{}
|
||||||
if len(strs) > 0 {
|
if len(strs) > 0 || counter != 2 {
|
||||||
t.Errorf("%s", strs)
|
t.Errorf("%s", strs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
501
src/terminal.go
501
src/terminal.go
@@ -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 {
|
||||||
@@ -561,8 +571,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 +857,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 +979,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 +1418,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 +1426,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 +1515,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 +1531,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, 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 +1599,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 +1620,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 +1654,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 +1689,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 +1720,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 +2113,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 +2158,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 +2173,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 +2290,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 +2383,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 +2487,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 +2542,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 +2584,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 +2626,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 +2701,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 +2784,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 +2810,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 +2845,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 +2889,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 +2932,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 +3106,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 +3119,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 +3365,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 +3499,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 +3558,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 +3595,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 +3608,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 +3625,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 +3751,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 +3856,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 +3884,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 +3892,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 +3950,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 +4034,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 +4050,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 +4137,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 +4150,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)
|
||||||
@@ -4410,17 +4473,77 @@ func (t *Terminal) Loop() error {
|
|||||||
suffix := copySlice(t.input[t.cx:])
|
suffix := copySlice(t.input[t.cx:])
|
||||||
t.input = append(append(t.input[:t.cx], t.yanked...), suffix...)
|
t.input = append(append(t.input[:t.cx], t.yanked...), suffix...)
|
||||||
t.cx += len(t.yanked)
|
t.cx += len(t.yanked)
|
||||||
case actPageUp:
|
case actPageUp, actPageDown, actHalfPageUp, actHalfPageDown:
|
||||||
t.vmove(t.maxItems()-1, false)
|
// Calculate the number of lines to move
|
||||||
req(reqList)
|
maxItems := t.maxItems()
|
||||||
case actPageDown:
|
linesToMove := maxItems - 1
|
||||||
t.vmove(-(t.maxItems() - 1), false)
|
if a.t == actHalfPageUp || a.t == actHalfPageDown {
|
||||||
req(reqList)
|
linesToMove = maxItems / 2
|
||||||
case actHalfPageUp:
|
}
|
||||||
t.vmove(t.maxItems()/2, false)
|
// Move at least one line even in a very short window
|
||||||
req(reqList)
|
linesToMove = util.Max(1, linesToMove)
|
||||||
case actHalfPageDown:
|
|
||||||
t.vmove(-(t.maxItems() / 2), false)
|
// Determine the direction of the movement
|
||||||
|
direction := -1
|
||||||
|
if a.t == actPageUp || a.t == actHalfPageUp {
|
||||||
|
direction = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// In non-default layout, items are listed from top to bottom
|
||||||
|
if t.layout != layoutDefault {
|
||||||
|
direction *= -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can simply add the number of lines to the current position in
|
||||||
|
// single-line mode
|
||||||
|
if !t.canSpanMultiLines() {
|
||||||
|
t.vset(t.cy + direction*linesToMove)
|
||||||
|
req(reqList)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// But in multi-line mode, we need to carefully limit the amount of
|
||||||
|
// vertical movement so that items are not skipped. In order to do
|
||||||
|
// this, we calculate the minimum or maximum offset based on the
|
||||||
|
// direction of the movement and the number of lines of the items
|
||||||
|
// around the current scroll offset.
|
||||||
|
var minOffset, maxOffset, lineSum int
|
||||||
|
if direction > 0 {
|
||||||
|
maxOffset = t.offset
|
||||||
|
for ; maxOffset < t.merger.Length(); maxOffset++ {
|
||||||
|
itemLines, _ := t.numItemLines(t.merger.Get(maxOffset).item, maxItems)
|
||||||
|
lineSum += itemLines
|
||||||
|
if lineSum >= maxItems {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
minOffset = t.offset
|
||||||
|
for ; minOffset >= 0 && minOffset < t.merger.Length(); minOffset-- {
|
||||||
|
itemLines, _ := t.numItemLines(t.merger.Get(minOffset).item, maxItems)
|
||||||
|
lineSum += itemLines
|
||||||
|
if lineSum >= maxItems {
|
||||||
|
if lineSum > maxItems {
|
||||||
|
minOffset++
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < linesToMove; i++ {
|
||||||
|
cy, offset := t.cy, t.offset
|
||||||
|
t.vset(cy + direction)
|
||||||
|
t.constrain()
|
||||||
|
if cy == t.cy {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if i > 0 && (direction > 0 && t.offset > maxOffset ||
|
||||||
|
direction < 0 && t.offset < minOffset) {
|
||||||
|
t.cy, t.offset = cy, offset
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
req(reqList)
|
req(reqList)
|
||||||
case actOffsetUp, actOffsetDown:
|
case actOffsetUp, actOffsetDown:
|
||||||
diff := 1
|
diff := 1
|
||||||
@@ -4551,6 +4674,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4588,7 +4712,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
|
||||||
@@ -4605,6 +4729,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
|
||||||
@@ -4748,6 +4921,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
|
||||||
@@ -4765,7 +4939,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
|
||||||
@@ -4773,20 +4948,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())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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.
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
@@ -1097,7 +1100,7 @@ func (w *LightWindow) fill(str string, resetCode string) FillReturn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if w.posx+1 >= w.Width() {
|
if w.posx >= w.Width() {
|
||||||
if w.posy+1 >= w.height {
|
if w.posy+1 >= w.height {
|
||||||
return FillSuspend
|
return FillSuspend
|
||||||
}
|
}
|
||||||
|
@@ -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++ {
|
||||||
|
@@ -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
|
||||||
|
@@ -306,5 +306,5 @@ func (chars *Chars) Lines(multiLine bool, maxLines int, wrapCols int, wrapSignWi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return wrapped, false
|
return wrapped, overflow
|
||||||
}
|
}
|
||||||
|
@@ -2814,13 +2814,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
|
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
|
expected = <<~OUTPUT
|
||||||
│
|
│
|
||||||
│ 1 │ > 3
|
│ 1 │ > 3
|
||||||
│ 2 │ 2
|
│ 2 │ 2
|
||||||
│ 3 │ 1
|
│ 3 │ 1
|
||||||
│ │ hello
|
│ │ hello
|
||||||
│ │ world
|
│ │ world
|
||||||
│ │ 1/1 ─
|
│ │ 1/1 ─
|
||||||
│ │ >
|
│ │ >
|
||||||
│
|
│
|
||||||
OUTPUT
|
OUTPUT
|
||||||
tmux.until { assert_block(expected, _1) }
|
tmux.until { assert_block(expected, _1) }
|
||||||
@@ -3073,6 +3073,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 +3752,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'"
|
||||||
|
Reference in New Issue
Block a user