m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-09 20:03:48 -05:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Junegunn Choi
38040d43e4 Leverage existing bash completion
This is a PoC implementation for leveraging existing bash completion

git **<tab>
kubectl **<tab>
2024-09-30 19:08:28 +09:00
23 changed files with 154 additions and 340 deletions

View File

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

View File

@@ -1,47 +1,6 @@
CHANGELOG CHANGELOG
========= =========
0.56.3
------
- Bug fixes in zsh scripts
- fix(zsh): handle backtick trigger edge case (#4090)
- revert(zsh): remove 'fc -RI' call in the history widget (#4093)
- Thanks to @LangLangBart for the contributions
0.56.2
------
- Bug fixes
- Fixed abnormal scrolling behavior when `--wrap` is set (#4083)
- [zsh] Fixed warning message when `ksh_arrays` is set (#4084)
0.56.1
------
- Bug fixes and improvements
- Fixed a race condition which would cause fzf to present stale results after `reload` (#4070)
- `page-up` and `page-down` actions now work correctly with multi-line items (#4069)
- `{n}` is allowed in `SCROLL` expression in `--preview-window` (#4079)
- [zsh] Fixed regression in history loading with shared option (#4071)
- [zsh] Better command extraction in zsh completion (#4082)
- Thanks to @LangLangBart, @jaydee-coder, @alex-huff, and @vejkse for the contributions
0.56.0
------
- Added `--gap[=N]` option to display empty lines between items.
- This can be useful to visually separate adjacent multi-line items.
```sh
# All bash functions, highlighted
declare -f | perl -0777 -pe 's/^}\n/}\0/gm' |
bat --plain --language bash --color always |
fzf --read0 --ansi --reverse --multi --highlight-line --gap
```
- Or just to make the list easier to read. For single-line items, you probably want to set `--color gutter:-1` as well to hide the gutter.
```sh
fzf --info inline-right --gap --color gutter:-1
```
- Added `noinfo` option to `--preview-window` to hide the scroll indicator in the preview window
- Bug fixes
- Thanks to @LangLangBart, @akinomyoga, and @charlievieth for fixing the bugs
0.55.0 0.55.0
------ ------
_Release highlights: https://junegunn.github.io/fzf/releases/0.55.0/_ _Release highlights: https://junegunn.github.io/fzf/releases/0.55.0/_

View File

@@ -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 ["bash", "-ic", "tmux new 'set -o pipefail; ruby /fzf/test/test_go.rb | tee out && touch ok' && cat out && [ -e ok ]"] CMD tmux new 'set -o pipefail; ruby /fzf/test/test_go.rb | tee out && touch ok' && cat out && [ -e ok ]

File diff suppressed because one or more lines are too long

6
go.mod
View File

@@ -1,13 +1,13 @@
module github.com/junegunn/fzf module github.com/junegunn/fzf
require ( require (
github.com/charlievieth/fastwalk v1.0.9 github.com/charlievieth/fastwalk v1.0.8
github.com/gdamore/tcell/v2 v2.7.4 github.com/gdamore/tcell/v2 v2.7.4
github.com/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97 github.com/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97
github.com/mattn/go-isatty v0.0.20 github.com/mattn/go-isatty v0.0.20
github.com/rivo/uniseg v0.4.7 github.com/rivo/uniseg v0.4.7
golang.org/x/sys v0.27.0 golang.org/x/sys v0.25.0
golang.org/x/term v0.26.0 golang.org/x/term v0.24.0
) )
require ( require (

12
go.sum
View File

@@ -1,5 +1,5 @@
github.com/charlievieth/fastwalk v1.0.9 h1:Odb92AfoReO3oFBfDGT5J+nwgzQPF/gWAw6E6/lkor0= github.com/charlievieth/fastwalk v1.0.8 h1:uaoH6cAKSk73aK7aKXqs0+bL+J3Txzd3NGH8tRXgHko=
github.com/charlievieth/fastwalk v1.0.9/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI= github.com/charlievieth/fastwalk v1.0.8/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU= github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU=
@@ -36,14 +36,14 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
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=

View File

@@ -2,7 +2,7 @@
set -u set -u
version=0.56.3 version=0.55.0
auto_completion= auto_completion=
key_bindings= key_bindings=
update_config=2 update_config=2

View File

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

View File

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

View File

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

View File

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf 1 "Nov 2024" "fzf 0.56.3" "fzf - a command-line fuzzy finder" .TH fzf 1 "Sep 2024" "fzf 0.56.0" "fzf - a command-line fuzzy finder"
.SH NAME .SH NAME
fzf - a command-line fuzzy finder fzf - a command-line fuzzy finder
@@ -208,9 +208,6 @@ Indicator for wrapped lines. The default is '↳ ' or '> ' depending on
.B "\-\-no\-multi\-line" .B "\-\-no\-multi\-line"
Disable multi-line display of items when using \fB\-\-read0\fR Disable multi-line display of items when using \fB\-\-read0\fR
.TP .TP
.BI "\-\-gap" "[=N]"
Render empty lines between each item
.TP
.B "\-\-keep\-right" .B "\-\-keep\-right"
Keep the right end of the line visible when it's too long. Effective only when Keep the right end of the line visible when it's too long. Effective only when
the query string is empty. the query string is empty.
@@ -805,7 +802,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 or {n} to refer to the zero-based ordinal index of the current item. - \fBSCROLL\fR can be either a numeric integer or a single-field index expression that refers to a numeric integer.
- 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).

View File

@@ -283,7 +283,7 @@ _fzf_handle_dynamic_completion() {
} }
__fzf_generic_path_completion() { __fzf_generic_path_completion() {
local cur base dir leftover matches trigger cmd local cur base dir leftover matches trigger cmd rest
cmd="${COMP_WORDS[0]}" cmd="${COMP_WORDS[0]}"
if [[ $cmd == \\* ]]; then if [[ $cmd == \\* ]]; then
cmd="${cmd:1}" cmd="${cmd:1}"
@@ -295,6 +295,16 @@ __fzf_generic_path_completion() {
base=${cur:0:${#cur}-${#trigger}} base=${cur:0:${#cur}-${#trigger}}
eval "base=$base" 2> /dev/null || return eval "base=$base" 2> /dev/null || return
# Try to leverage existing completion
rest=("${@:4}")
unset 'rest[${#rest[@]}-2]'
COMP_LINE=${COMP_LINE:0:${#COMP_LINE}-${#trigger}}
COMP_POINT=$((COMP_POINT-${#trigger}))
COMP_WORDS[$COMP_CWORD]=$base
_fzf_handle_dynamic_completion "$cmd" "${rest[@]}"
[[ $? -ne 0 ]] &&
_fzf_handle_dynamic_completion "$cmd" "${rest[@]}"
dir= dir=
[[ $base = *"/"* ]] && dir="$base" [[ $base = *"/"* ]] && dir="$base"
while true; do while true; do
@@ -306,7 +316,11 @@ __fzf_generic_path_completion() {
matches=$( matches=$(
export FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --scheme=path" "${FZF_COMPLETION_OPTS-} $2") export FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --scheme=path" "${FZF_COMPLETION_OPTS-} $2")
unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE
if declare -F "$1" > /dev/null; then if [[ ${#COMPREPLY[@]} -gt 0 ]]; then
for h in "${COMPREPLY[@]}"; do
echo "$h"
done | command sort -u | __fzf_comprun "$4" -q "$leftover"
elif declare -F "$1" > /dev/null; then
eval "$1 $(printf %q "$dir")" | __fzf_comprun "$4" -q "$leftover" eval "$1 $(printf %q "$dir")" | __fzf_comprun "$4" -q "$leftover"
else else
if [[ $1 =~ dir ]]; then if [[ $1 =~ dir ]]; then
@@ -374,10 +388,23 @@ _fzf_complete() {
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
cur=${cur:0:${#cur}-${#trigger}} cur=${cur:0:${#cur}-${#trigger}}
# Try to leverage existing completion
COMP_LINE=${COMP_LINE:0:${#COMP_LINE}-${#trigger}}
COMP_POINT=$((COMP_POINT-${#trigger}))
unset 'rest[${#rest[@]}-2]'
_fzf_handle_dynamic_completion "$cmd" "${rest[@]}"
[[ $? -ne 0 ]] &&
_fzf_handle_dynamic_completion "$cmd" "${rest[@]}"
selected=$( selected=$(
(if [[ ${#COMPREPLY[@]} -gt 0 ]]; then
for h in "${COMPREPLY[@]}"; do
echo "$h"
done
fi; cat) | command sort -u |
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 "${rest[0]}" "${args[@]}" -q "$cur" | eval "$post" | command tr '\n' ' ') __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | command tr '\n' ' ')
selected=${selected% } # Strip trailing space not to repeat "-o nospace" selected=${selected% } # Strip trailing space not to repeat "-o nospace"
if [[ -n "$selected" ]]; then if [[ -n "$selected" ]]; then
COMPREPLY=("$selected") COMPREPLY=("$selected")

View File

@@ -120,19 +120,25 @@ __fzf_comprun() {
fi fi
} }
# Extract the name of the command. e.g. ls; foo=1 ssh **<tab> # Extract the name of the command. e.g. foo=1 bar baz**<tab>
__fzf_extract_command() { __fzf_extract_command() {
setopt localoptions noksh_arrays 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 compgen fzf_opts suffix tail dir leftover matches local base lbuf cmd 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
@@ -155,7 +161,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_word" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover"
else else
if [[ $compgen =~ dir ]]; then if [[ $compgen =~ dir ]]; then
walker=dir,follow walker=dir,follow
@@ -164,7 +170,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_word" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" --walker "$walker" --walker-root="$dir" ${(Q)${(Z+n+)rest}} < /dev/tty __fzf_comprun "$cmd" ${(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} "
@@ -221,9 +227,10 @@ _fzf_complete() {
rest=("$@") rest=("$@")
fi fi
local fifo lbuf matches post local fifo lbuf cmd 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
@@ -231,7 +238,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_word" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ') __fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
if [ -n "$matches" ]; then if [ -n "$matches" ]; then
LBUFFER="$lbuf$matches" LBUFFER="$lbuf$matches"
fi fi
@@ -303,17 +310,9 @@ _fzf_complete_kill_post() {
} }
fzf-completion() { fzf-completion() {
typeset -g cmd_word local tokens cmd prefix trigger tail matches lbuf d_cmds
trap 'unset cmd_word' EXIT
local tokens prefix trigger tail matches lbuf d_cmds cursor_pos
setopt localoptions noshwordsplit noksh_arrays noposixbuiltins setopt localoptions noshwordsplit noksh_arrays noposixbuiltins
# Check if at least one completion system (old or new) is active
if ! zmodload -F zsh/parameter p:functions 2>/dev/null || ! (( ${+functions[compdef]} )); then
if ! zmodload -e zsh/compctl; then
zmodload -i zsh/compctl
fi
fi
# http://zsh.sourceforge.net/FAQ/zshfaq03.html # http://zsh.sourceforge.net/FAQ/zshfaq03.html
# http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion-Flags # http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion-Flags
tokens=(${(z)LBUFFER}) tokens=(${(z)LBUFFER})
@@ -322,9 +321,11 @@ 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 && ${LBUFFER[-1]} == ' ' ]] && tokens+=("") [ -z "$trigger" -a ${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
@@ -339,27 +340,16 @@ 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))
# Assign the extracted command to the global variable 'cmd_word'
zle __fzf_extract_command
} always {
CURSOR=$cursor_pos
}
[ -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 "noglob type _fzf_complete_${cmd_word} >/dev/null"; then if eval "type _fzf_complete_${cmd} > /dev/null"; then
prefix="$prefix" eval _fzf_complete_${cmd_word} ${(q)lbuf} prefix="$prefix" eval _fzf_complete_${cmd} ${(q)lbuf}
zle reset-prompt zle reset-prompt
elif [ ${d_cmds[(i)$cmd_word]} -le ${#d_cmds} ]; then elif [ ${d_cmds[(i)$cmd]} -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"
@@ -376,9 +366,6 @@ fzf-completion() {
unset binding unset binding
} }
# Completion widget to gain access to the 'words' array (man zshcompwid)
zle -C __fzf_extract_command .complete-word __fzf_extract_command
# Normal widget
zle -N fzf-completion zle -N fzf-completion
bindkey '^I' fzf-completion bindkey '^I' fzf-completion
fi fi

View File

@@ -108,10 +108,9 @@ 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 module is loaded if not already, and the required features, such # Ensure the associative history array, which maps event numbers to the full
# as the associative 'history' array, which maps event numbers to full history # history lines, is loaded, and that Perl is installed for multi-line output.
# lines, are set. Also, make sure Perl is installed for multi-line output. if zmodload -F zsh/parameter p:history 2>/dev/null && (( ${#commands[perl]} )); then
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") \

View File

@@ -172,9 +172,7 @@ 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)
readyChan := make(chan bool) go reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv)
go reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv, readyChan)
<-readyChan
} }
// Matcher // Matcher
@@ -226,7 +224,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, nil) reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv)
} else { } else {
eventBox.Unwatch(EvtReadNew) eventBox.Unwatch(EvtReadNew)
eventBox.WaitFor(EvtReadFin) eventBox.WaitFor(EvtReadFin)
@@ -301,9 +299,7 @@ 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)
readyChan := make(chan bool) go reader.restart(command, environ)
go reader.restart(command, environ, readyChan)
<-readyChan
} }
exitCode := ExitOk exitCode := ExitOk

View File

@@ -102,7 +102,7 @@ func (m *Matcher) Loop() {
if !cacheCleared { if !cacheCleared {
if count == prevCount { if count == prevCount {
// Look up mergerCache // Look up mergerCache
if cached, found := m.mergerCache[patternString]; found && cached.final == request.final { if cached, found := m.mergerCache[patternString]; found {
merger = cached merger = cached
} }
} else { } else {

View File

@@ -56,7 +56,6 @@ Usage: fzf [options]
--wrap Enable line wrap --wrap Enable line wrap
--wrap-sign=STR Indicator for wrapped lines --wrap-sign=STR Indicator for wrapped lines
--no-multi-line Disable multi-line display of items when using --read0 --no-multi-line Disable multi-line display of items when using --read0
--gap[=N] Render empty lines between each item
--keep-right Keep the right end of the line visible on overflow --keep-right Keep the right end of the line visible on overflow
--scroll-off=LINES Number of screen lines to keep above or below when --scroll-off=LINES Number of screen lines to keep above or below when
scrolling to the top or to the bottom (default: 0) scrolling to the top or to the bottom (default: 0)
@@ -474,7 +473,6 @@ type Options struct {
Header []string Header []string
HeaderLines int HeaderLines int
HeaderFirst bool HeaderFirst bool
Gap int
Ellipsis *string Ellipsis *string
Scrollbar *string Scrollbar *string
Margin [4]sizeSpec Margin [4]sizeSpec
@@ -581,7 +579,6 @@ func defaultOptions() *Options {
Header: make([]string, 0), Header: make([]string, 0),
HeaderLines: 0, HeaderLines: 0,
HeaderFirst: false, HeaderFirst: false,
Gap: 0,
Ellipsis: nil, Ellipsis: nil,
Scrollbar: nil, Scrollbar: nil,
Margin: defaultMargin(), Margin: defaultMargin(),
@@ -1726,7 +1723,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]+|n)})?([+-][0-9]+)*(-?/[1-9][0-9]*)?$`) offsetRegex := regexp.MustCompile(`^(\+{-?[0-9]+})?([+-][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
@@ -2346,12 +2343,6 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
opts.HeaderFirst = true opts.HeaderFirst = true
case "--no-header-first": case "--no-header-first":
opts.HeaderFirst = false opts.HeaderFirst = false
case "--gap":
if opts.Gap, err = optionalNumeric(allArgs, &i, 1); err != nil {
return err
}
case "--no-gap":
opts.Gap = 0
case "--ellipsis": case "--ellipsis":
str, err := nextString(allArgs, &i, "ellipsis string required") str, err := nextString(allArgs, &i, "ellipsis string required")
if err != nil { if err != nil {
@@ -2639,10 +2630,6 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if opts.HeaderLines, err = atoi(value); err != nil { if opts.HeaderLines, err = atoi(value); err != nil {
return err return err
} }
} else if match, value := optString(arg, "--gap="); match {
if opts.Gap, err = atoi(value); err != nil {
return err
}
} else if match, value := optString(arg, "--ellipsis="); match { } else if match, value := optString(arg, "--ellipsis="); match {
str := firstLine(value) str := firstLine(value)
opts.Ellipsis = &str opts.Ellipsis = &str

View File

@@ -6,6 +6,7 @@ import (
"io" "io"
"io/fs" "io/fs"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"sync" "sync"
"sync/atomic" "sync/atomic"
@@ -24,26 +25,16 @@ type Reader struct {
event int32 event int32
finChan chan bool finChan chan bool
mutex sync.Mutex mutex sync.Mutex
killed bool exec *exec.Cmd
termFunc func() execOut io.ReadCloser
command *string command *string
killed bool
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{ return &Reader{pusher, executor, eventBox, delimNil, int32(EvtReady), make(chan bool, 1), sync.Mutex{}, nil, nil, nil, false, wait}
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() {
@@ -89,19 +80,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.termFunc != nil { if r.exec != nil && r.exec.Process != nil {
r.termFunc() r.execOut.Close()
r.termFunc = nil util.KillCommand(r.exec)
} else {
os.Stdin.Close()
} }
r.mutex.Unlock() r.mutex.Unlock()
} }
func (r *Reader) restart(command commandSpec, environ []string, readyChan chan bool) { func (r *Reader) restart(command commandSpec, environ []string) {
r.event = int32(EvtReady) r.event = int32(EvtReady)
r.startEventPoller() r.startEventPoller()
success := r.readFromCommand(command.command, environ, func() { success := r.readFromCommand(command.command, environ)
readyChan <- true
})
r.fin(success) r.fin(success)
removeFiles(command.tempFiles) removeFiles(command.tempFiles)
} }
@@ -120,29 +111,21 @@ func (r *Reader) readChannel(inputChan chan string) bool {
} }
// ReadSource reads data from the default command or from standard input // ReadSource reads data from the default command or from standard input
func (r *Reader) ReadSource(inputChan chan string, root string, opts walkerOpts, ignores []string, initCmd string, initEnv []string, readyChan chan bool) { func (r *Reader) ReadSource(inputChan chan string, root string, opts walkerOpts, ignores []string, initCmd string, initEnv []string) {
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, signalReady) success = r.readFromCommand(initCmd, initEnv)
} 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 {
signalReady()
success = r.readFiles(root, opts, ignores) success = r.readFiles(root, opts, ignores)
} else { } else {
success = r.readFromCommand(cmd, initEnv, signalReady) success = r.readFromCommand(cmd, initEnv)
} }
} else { } else {
signalReady()
success = r.readFromStdin() success = r.readFromStdin()
} }
r.fin(success) r.fin(success)
@@ -266,6 +249,7 @@ func trimPath(path string) string {
} }
func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool { func (r *Reader) readFiles(root 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
@@ -281,7 +265,7 @@ func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool
isDir := de.IsDir() isDir := de.IsDir()
if isDir || opts.follow && isSymlinkToDir(path, de) { if isDir || opts.follow && isSymlinkToDir(path, de) {
base := filepath.Base(path) base := filepath.Base(path)
if !opts.hidden && base[0] == '.' && base != ".." { if !opts.hidden && base[0] == '.' {
return filepath.SkipDir return filepath.SkipDir
} }
for _, ignore := range ignores { for _, ignore := range ignores {
@@ -304,32 +288,31 @@ func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool
return fastwalk.Walk(&conf, root, fn) == nil return fastwalk.Walk(&conf, root, fn) == nil
} }
func (r *Reader) readFromCommand(command string, environ []string, signalReady func()) bool { func (r *Reader) readFromCommand(command string, environ []string) bool {
r.mutex.Lock() r.mutex.Lock()
r.killed = false r.killed = false
r.termFunc = nil
r.command = &command r.command = &command
exec := r.executor.ExecCommand(command, true) r.exec = r.executor.ExecCommand(command, true)
if environ != nil { if environ != nil {
exec.Env = environ r.exec.Env = environ
} }
execOut, err := exec.StdoutPipe()
if err != nil || exec.Start() != nil { var err error
signalReady() r.execOut, err = r.exec.StdoutPipe()
if err != nil {
r.exec = nil
r.mutex.Unlock() r.mutex.Unlock()
return false return false
} }
// Function to call to terminate the running command err = r.exec.Start()
r.termFunc = func() { if err != nil {
execOut.Close() r.exec = nil
util.KillCommand(exec) r.mutex.Unlock()
return false
} }
signalReady()
r.mutex.Unlock() r.mutex.Unlock()
r.feed(r.execOut)
r.feed(execOut) return r.exec.Wait() == nil
return exec.Wait() == nil
} }

View File

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

View File

@@ -245,7 +245,6 @@ type Terminal struct {
hscroll bool hscroll bool
hscrollOff int hscrollOff int
scrollOff int scrollOff int
gap int
wordRubout string wordRubout string
wordNext string wordNext string
cx int cx int
@@ -826,7 +825,6 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
headerVisible: true, headerVisible: true,
headerFirst: opts.HeaderFirst, headerFirst: opts.HeaderFirst,
headerLines: opts.HeaderLines, headerLines: opts.HeaderLines,
gap: opts.Gap,
header: []string{}, header: []string{},
header0: opts.Header, header0: opts.Header,
ansi: opts.Ansi, ansi: opts.Ansi,
@@ -1138,23 +1136,15 @@ func (t *Terminal) wrapCols() int {
return util.Max(t.window.Width()-(t.pointerLen+t.markerLen+1), 1) return util.Max(t.window.Width()-(t.pointerLen+t.markerLen+1), 1)
} }
// Number of lines the item takes including the gap
func (t *Terminal) numItemLines(item *Item, atMost int) (int, bool) { func (t *Terminal) numItemLines(item *Item, atMost int) (int, bool) {
var numLines int
if !t.wrap && !t.multiLine { if !t.wrap && !t.multiLine {
numLines = 1 + t.gap return 1, false
return numLines, numLines > atMost
} }
var overflow bool
if !t.wrap && t.multiLine { if !t.wrap && t.multiLine {
numLines, overflow = item.text.NumLines(atMost) return item.text.NumLines(atMost)
} else {
var lines [][]rune
lines, overflow = item.text.Lines(t.multiLine, atMost, t.wrapCols(), t.wrapSignWidth, t.tabstop)
numLines = len(lines)
} }
numLines += t.gap lines, overflow := item.text.Lines(t.multiLine, atMost, t.wrapCols(), t.wrapSignWidth, t.tabstop)
return numLines, overflow || numLines > atMost return len(lines), overflow
} }
func (t *Terminal) itemLines(item *Item, atMost int) ([][]rune, bool) { func (t *Terminal) itemLines(item *Item, atMost int) ([][]rune, bool) {
@@ -2060,21 +2050,6 @@ func (t *Terminal) printHeader() {
t.wrap = wrap t.wrap = wrap
} }
func (t *Terminal) canSpanMultiLines() bool {
return t.multiLine || t.wrap || t.gap > 0
}
func (t *Terminal) renderEmptyLine(line int, barRange [2]int) {
t.move(line, 0, true)
t.markEmptyLine(line)
// If the screen is not filled with the list in non-multi-line mode,
// scrollbar is not visible at all. But in multi-line mode, we may need
// to redraw the scrollbar character at the end.
if t.canSpanMultiLines() {
t.prevLines[line].hasBar = t.printBar(line, true, barRange)
}
}
func (t *Terminal) printList() { func (t *Terminal) printList() {
t.constrain() t.constrain()
barLength, barStart := t.getScrollbar() barLength, barStart := t.getScrollbar()
@@ -2095,7 +2070,14 @@ func (t *Terminal) printList() {
item := t.merger.Get(itemCount + t.offset) item := t.merger.Get(itemCount + t.offset)
line = t.printItem(item, line, maxy, itemCount, itemCount == t.cy-t.offset, barRange) line = t.printItem(item, line, maxy, itemCount, itemCount == t.cy-t.offset, barRange)
} else if !t.prevLines[line].empty { } else if !t.prevLines[line].empty {
t.renderEmptyLine(line, barRange) t.move(line, 0, true)
t.markEmptyLine(line)
// If the screen is not filled with the list in non-multi-line mode,
// scrollbar is not visible at all. But in multi-line mode, we may need
// to redraw the scrollbar character at the end.
if t.multiLine || t.wrap {
t.prevLines[line].hasBar = t.printBar(line, true, barRange)
}
} }
} }
} }
@@ -2143,6 +2125,9 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
prevLine.queryLen == newLine.queryLen && prevLine.queryLen == newLine.queryLen &&
prevLine.result == newLine.result { prevLine.result == newLine.result {
t.prevLines[line].hasBar = printBar(line, false) t.prevLines[line].hasBar = printBar(line, false)
if !t.multiLine && !t.wrap {
return line
}
return line + numLines - 1 return line + numLines - 1
} }
@@ -2229,10 +2214,6 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
} }
finalLineNum = t.printHighlighted(result, base, match, false, true, line, maxLine, forceRedraw, preTask, postTask) finalLineNum = t.printHighlighted(result, base, match, false, true, line, maxLine, forceRedraw, preTask, postTask)
} }
for i := 0; i < t.gap && finalLineNum < maxLine; i++ {
finalLineNum++
t.renderEmptyLine(finalLineNum, barRange)
}
return finalLineNum return finalLineNum
} }
@@ -2294,7 +2275,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
allOffsets := result.colorOffsets(charOffsets, t.theme, colBase, colMatch, current) allOffsets := result.colorOffsets(charOffsets, t.theme, colBase, colMatch, current)
maxLines := 1 maxLines := 1
if t.canSpanMultiLines() { if t.multiLine || t.wrap {
maxLines = maxLineNum - lineNum + 1 maxLines = maxLineNum - lineNum + 1
} }
lines, overflow := t.itemLines(item, maxLines) lines, overflow := t.itemLines(item, maxLines)
@@ -2304,7 +2285,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
topCutoff := false topCutoff := false
skipLines := 0 skipLines := 0
wrapped := false wrapped := false
if t.canSpanMultiLines() { if t.multiLine || t.wrap {
// Cut off the upper lines in the 'default' layout // Cut off the upper lines in the 'default' layout
if t.layout == layoutDefault && !current && maxLines == numItemLines && overflow { if t.layout == layoutDefault && !current && maxLines == numItemLines && overflow {
lines, _ = t.itemLines(item, math.MaxInt) lines, _ = t.itemLines(item, math.MaxInt)
@@ -2721,15 +2702,11 @@ Loop:
url = nil url = nil
t.pwindow.LinkEnd() t.pwindow.LinkEnd()
} }
if ansi != nil {
lbg = ansi.lbg
} else {
lbg = -1
}
str, width := t.processTabs(trimmed, prefixWidth) str, width := t.processTabs(trimmed, prefixWidth)
if width > prefixWidth { if width > prefixWidth {
prefixWidth = width prefixWidth = width
if t.theme.Colored && ansi != nil && ansi.colored() { if t.theme.Colored && ansi != nil && ansi.colored() {
lbg = ansi.lbg
fillRet = t.pwindow.CFill(ansi.fg, ansi.bg, ansi.attr, str) fillRet = t.pwindow.CFill(ansi.fg, ansi.bg, ansi.attr, str)
} else { } else {
fillRet = t.pwindow.CFill(tui.ColPreview.Fg(), tui.ColPreview.Bg(), tui.AttrRegular, str) fillRet = t.pwindow.CFill(tui.ColPreview.Fg(), tui.ColPreview.Bg(), tui.AttrRegular, str)
@@ -2751,7 +2728,7 @@ Loop:
if unchanged && lineNo == 0 { if unchanged && lineNo == 0 {
break break
} }
if t.theme.Colored && lbg >= 0 { if lbg >= 0 {
fillRet = t.pwindow.CFill(-1, lbg, tui.AttrRegular, fillRet = t.pwindow.CFill(-1, lbg, tui.AttrRegular,
strings.Repeat(" ", t.pwindow.Width()-t.pwindow.X())+"\n") strings.Repeat(" ", t.pwindow.Width()-t.pwindow.X())+"\n")
} else { } else {
@@ -4410,77 +4387,17 @@ 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, actPageDown, actHalfPageUp, actHalfPageDown: case actPageUp:
// Calculate the number of lines to move t.vmove(t.maxItems()-1, false)
maxItems := t.maxItems() req(reqList)
linesToMove := maxItems - 1 case actPageDown:
if a.t == actHalfPageUp || a.t == actHalfPageDown { t.vmove(-(t.maxItems() - 1), false)
linesToMove = maxItems / 2 req(reqList)
} case actHalfPageUp:
// Move at least one line even in a very short window t.vmove(t.maxItems()/2, false)
linesToMove = util.Max(1, linesToMove) req(reqList)
case actHalfPageDown:
// Determine the direction of the movement t.vmove(-(t.maxItems() / 2), false)
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
@@ -4958,7 +4875,7 @@ func (t *Terminal) constrain() {
for tries := 0; tries < maxLines; tries++ { for tries := 0; tries < maxLines; tries++ {
numItems := maxLines numItems := maxLines
// How many items can be fit on screen including the current item? // How many items can be fit on screen including the current item?
if t.canSpanMultiLines() && t.merger.Length() > 0 { if (t.multiLine || t.wrap) && t.merger.Length() > 0 {
numItemsFound := 0 numItemsFound := 0
linesSum := 0 linesSum := 0
@@ -5013,12 +4930,12 @@ func (t *Terminal) constrain() {
for { for {
prevOffset := newOffset prevOffset := newOffset
numItems := t.merger.Length() numItems := t.merger.Length()
itemLines := 1 + t.gap itemLines := 1
if t.canSpanMultiLines() && t.cy < numItems { if (t.multiLine || t.wrap) && t.cy < numItems {
itemLines, _ = t.numItemLines(t.merger.Get(t.cy).item, maxLines) itemLines, _ = t.numItemLines(t.merger.Get(t.cy).item, maxLines)
} }
linesBefore := t.cy - newOffset linesBefore := t.cy - newOffset
if t.canSpanMultiLines() { if t.multiLine || t.wrap {
linesBefore = 0 linesBefore = 0
for i := newOffset; i < t.cy && i < numItems; i++ { for i := newOffset; i < t.cy && i < numItems; i++ {
lines, _ := t.numItemLines(t.merger.Get(i).item, maxLines-linesBefore-itemLines) lines, _ := t.numItemLines(t.merger.Get(i).item, maxLines-linesBefore-itemLines)

View File

@@ -1097,7 +1097,7 @@ func (w *LightWindow) fill(str string, resetCode string) FillReturn {
} }
} }
} }
if w.posx >= w.Width() { if w.posx+1 >= w.Width() {
if w.posy+1 >= w.height { if w.posy+1 >= w.height {
return FillSuspend return FillSuspend
} }

View File

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

View File

@@ -3392,40 +3392,6 @@ class TestGoFZF < TestBase
assert lines[1]&.end_with?('1000││') assert lines[1]&.end_with?('1000││')
end end
end end
def test_gap
tmux.send_keys %(seq 100 | #{FZF} --gap --border --reverse), :Enter
block = <<~BLOCK
>
100/100
> 1
2
3
4
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_gap_2
tmux.send_keys %(seq 100 | #{FZF} --gap=2 --border --reverse), :Enter
block = <<~BLOCK
>
100/100
> 1
2
3
BLOCK
tmux.until { assert_block(block, _1) }
end
end end
module TestShell module TestShell