mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-11 04:43:48 -05:00
Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64c61603e9 | ||
|
|
57c08d925f | ||
|
|
51623a5f6a | ||
|
|
ca3f6181d7 | ||
|
|
9c94f9c3d0 | ||
|
|
4a85843bcf | ||
|
|
d4d9b99879 | ||
|
|
6816b7d95b | ||
|
|
acdf265d7a | ||
|
|
19495eb9bb | ||
|
|
bacc8609ee | ||
|
|
99163f5afa | ||
|
|
0607227730 | ||
|
|
d938fdc496 | ||
|
|
dcb4c3d84a | ||
|
|
82ebcd9209 | ||
|
|
ff1687744d | ||
|
|
782c870fb2 | ||
|
|
71fad63829 | ||
|
|
d65c6101a8 | ||
|
|
3c40b1bd51 | ||
|
|
90a8800bb5 | ||
|
|
97f1dae2d1 | ||
|
|
e54ec05709 | ||
|
|
a24eb99679 | ||
|
|
ad113d00b7 | ||
|
|
7bd5884d12 | ||
|
|
c3505858a6 | ||
|
|
e76aa37fd4 | ||
|
|
1a32220ca9 | ||
|
|
4161403a1d | ||
|
|
53bcdc4294 | ||
|
|
30a8ef28cd | ||
|
|
855f90727a | ||
|
|
2191a44e36 | ||
|
|
952276dc2d | ||
|
|
2286edb329 | ||
|
|
a0f28583e7 | ||
|
|
8af0af3400 | ||
|
|
769e5cbb2d |
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.24.1
|
- uses: crate-ci/typos@v1.26.0
|
||||||
|
|||||||
28
CHANGELOG.md
28
CHANGELOG.md
@@ -1,6 +1,34 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
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/_
|
||||||
|
|||||||
@@ -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 ]"]
|
||||||
|
|||||||
6
go.mod
6
go.mod
@@ -1,13 +1,13 @@
|
|||||||
module github.com/junegunn/fzf
|
module github.com/junegunn/fzf
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/charlievieth/fastwalk v1.0.8
|
github.com/charlievieth/fastwalk v1.0.9
|
||||||
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.24.0
|
golang.org/x/sys v0.26.0
|
||||||
golang.org/x/term v0.23.0
|
golang.org/x/term v0.25.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
12
go.sum
12
go.sum
@@ -1,5 +1,5 @@
|
|||||||
github.com/charlievieth/fastwalk v1.0.8 h1:uaoH6cAKSk73aK7aKXqs0+bL+J3Txzd3NGH8tRXgHko=
|
github.com/charlievieth/fastwalk v1.0.9 h1:Odb92AfoReO3oFBfDGT5J+nwgzQPF/gWAw6E6/lkor0=
|
||||||
github.com/charlievieth/fastwalk v1.0.8/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI=
|
github.com/charlievieth/fastwalk v1.0.9/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.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.26.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.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
|
||||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
||||||
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=
|
||||||
|
|||||||
2
install
2
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.55.0
|
version=0.56.1
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
$version="0.55.0"
|
$version="0.56.1"
|
||||||
|
|
||||||
$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.55"
|
var version = "0.56"
|
||||||
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 "Aug 2024" "fzf 0.55.0" "fzf\-tmux - open fzf in tmux split pane"
|
.TH fzf\-tmux 1 "Nov 2024" "fzf 0.56.1" "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 "Aug 2024" "fzf 0.55.0" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Nov 2024" "fzf 0.56.1" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -208,6 +208,9 @@ 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.
|
||||||
@@ -756,7 +759,7 @@ default value 0 (or \fBcenter\fR) will put the label at the center of the
|
|||||||
border line.
|
border line.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-preview\-window=" "[POSITION][,SIZE[%]][,border\-BORDER_OPT][,[no]wrap][,[no]follow][,[no]cycle][,[no]hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]"
|
.BI "\-\-preview\-window=" "[POSITION][,SIZE[%]][,border\-BORDER_OPT][,[no]wrap][,[no]follow][,[no]cycle][,[no]info][,[no]hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]"
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
.B POSITION: (default: right)
|
.B POSITION: (default: right)
|
||||||
@@ -790,6 +793,9 @@ e.g.
|
|||||||
|
|
||||||
* Cyclic scrolling is enabled with \fBcycle\fR flag.
|
* Cyclic scrolling is enabled with \fBcycle\fR flag.
|
||||||
|
|
||||||
|
* To hide the scroll offset information on the top right corner, specify
|
||||||
|
\fBnoinfo\fR.
|
||||||
|
|
||||||
* To change the style of the border of the preview window, specify one of
|
* To change the style of the border of the preview window, specify one of
|
||||||
the options for \fB\-\-border\fR with \fBborder\-\fR prefix.
|
the options for \fB\-\-border\fR with \fBborder\-\fR prefix.
|
||||||
e.g. \fBborder\-rounded\fR (border with rounded edges, default),
|
e.g. \fBborder\-rounded\fR (border with rounded edges, default),
|
||||||
@@ -799,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).
|
||||||
|
|
||||||
|
|||||||
@@ -264,6 +264,7 @@ _fzf_handle_dynamic_completion() {
|
|||||||
# _completion_loader may not have updated completion for the command
|
# _completion_loader may not have updated completion for the command
|
||||||
if [[ "$(complete -p "$orig_cmd" 2> /dev/null)" != "$orig_complete" ]]; then
|
if [[ "$(complete -p "$orig_cmd" 2> /dev/null)" != "$orig_complete" ]]; then
|
||||||
__fzf_orig_completion < <(complete -p "$orig_cmd" 2> /dev/null)
|
__fzf_orig_completion < <(complete -p "$orig_cmd" 2> /dev/null)
|
||||||
|
__fzf_orig_completion_get_orig_func "$cmd" || ret=1
|
||||||
|
|
||||||
# Update orig_complete by _fzf_orig_completion entry
|
# Update orig_complete by _fzf_orig_completion entry
|
||||||
[[ $orig_complete =~ ' -F '(_fzf_[^ ]+)' ' ]] &&
|
[[ $orig_complete =~ ' -F '(_fzf_[^ ]+)' ' ]] &&
|
||||||
@@ -376,7 +377,7 @@ _fzf_complete() {
|
|||||||
selected=$(
|
selected=$(
|
||||||
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" | $post | command tr '\n' ' ')
|
__fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | eval "$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")
|
||||||
|
|||||||
@@ -120,25 +120,19 @@ __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
|
setopt localoptions noksh_arrays
|
||||||
tokens=(${(z)1})
|
# Control completion with the "compstate" parameter, insert and list noting
|
||||||
for token in $tokens; do
|
compstate[insert]=
|
||||||
token=${(Q)token}
|
compstate[list]=
|
||||||
if [[ "$token" =~ [[:alnum:]] && ! "$token" =~ "=" ]]; then
|
cmd_word="${words[1]}"
|
||||||
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 +155,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 +164,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 +221,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 +231,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,9 +303,16 @@ _fzf_complete_kill_post() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fzf-completion() {
|
fzf-completion() {
|
||||||
local tokens cmd prefix trigger tail matches lbuf d_cmds
|
trap 'unset cmd_word' EXIT
|
||||||
|
local tokens prefix trigger tail matches lbuf d_cmds
|
||||||
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})
|
||||||
@@ -321,8 +321,6 @@ 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" -a ${LBUFFER[-1]} = ' ' ] && tokens+=("")
|
||||||
@@ -340,16 +338,20 @@ 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})
|
||||||
|
|
||||||
|
# Make the 'cmd_word' global
|
||||||
|
zle __fzf_extract_command || :
|
||||||
|
[[ -z "$cmd_word" ]] && return
|
||||||
|
|
||||||
[ -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 "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 +368,9 @@ 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
|
||||||
|
|||||||
@@ -108,10 +108,17 @@ 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.
|
||||||
selected="$(printf '%s\t%s\000' "${(kv)history[@]}" |
|
if zmodload -F zsh/parameter p:{commands,history} 2>/dev/null && (( $+commands[perl] )); then
|
||||||
|
# Import commands from other shells if SHARE_HISTORY is enabled, as the
|
||||||
|
# 'history' array only updates after executing a non-empty command.
|
||||||
|
selected="$(
|
||||||
|
if [[ -o sharehistory ]]; then
|
||||||
|
fc -RI
|
||||||
|
fi
|
||||||
|
printf '%s\t%s\000' "${(kv)history[@]}" |
|
||||||
perl -0 -ne 'if (!$seen{(/^\s*[0-9]+\**\t(.*)/s, $1)}++) { s/\n/\n\t/g; print; }' |
|
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") \
|
||||||
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
|
||||||
|
|||||||
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
|
||||||
|
|||||||
@@ -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 {
|
if cached, found := m.mergerCache[patternString]; found && cached.final == request.final {
|
||||||
merger = cached
|
merger = cached
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ 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)
|
||||||
@@ -120,8 +121,8 @@ Usage: fzf [options]
|
|||||||
--preview=COMMAND Command to preview highlighted line ({})
|
--preview=COMMAND Command to preview highlighted line ({})
|
||||||
--preview-window=OPT Preview window layout (default: right:50%)
|
--preview-window=OPT Preview window layout (default: right:50%)
|
||||||
[up|down|left|right][,SIZE[%]]
|
[up|down|left|right][,SIZE[%]]
|
||||||
[,[no]wrap][,[no]cycle][,[no]follow][,[no]hidden]
|
[,[no]wrap][,[no]cycle][,[no]follow][,[no]info]
|
||||||
[,border-BORDER_OPT]
|
[,[no]hidden][,border-BORDER_OPT]
|
||||||
[,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
|
[,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
|
||||||
[,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]
|
[,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]
|
||||||
--preview-label=LABEL
|
--preview-label=LABEL
|
||||||
@@ -271,6 +272,7 @@ type previewOpts struct {
|
|||||||
wrap bool
|
wrap bool
|
||||||
cycle bool
|
cycle bool
|
||||||
follow bool
|
follow bool
|
||||||
|
info bool
|
||||||
border tui.BorderShape
|
border tui.BorderShape
|
||||||
headerLines int
|
headerLines int
|
||||||
threshold int
|
threshold int
|
||||||
@@ -386,7 +388,7 @@ func (a previewOpts) sameLayout(b previewOpts) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a previewOpts) sameContentLayout(b previewOpts) bool {
|
func (a previewOpts) sameContentLayout(b previewOpts) bool {
|
||||||
return a.wrap == b.wrap && a.headerLines == b.headerLines
|
return a.wrap == b.wrap && a.headerLines == b.headerLines && a.info == b.info
|
||||||
}
|
}
|
||||||
|
|
||||||
func firstLine(s string) string {
|
func firstLine(s string) string {
|
||||||
@@ -472,6 +474,7 @@ 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
|
||||||
@@ -508,7 +511,7 @@ func filterNonEmpty(input []string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func defaultPreviewOpts(command string) previewOpts {
|
func defaultPreviewOpts(command string) previewOpts {
|
||||||
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, tui.DefaultBorderShape, 0, 0, nil}
|
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, true, tui.DefaultBorderShape, 0, 0, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultOptions() *Options {
|
func defaultOptions() *Options {
|
||||||
@@ -578,6 +581,7 @@ 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(),
|
||||||
@@ -1722,7 +1726,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
|
||||||
@@ -1789,6 +1793,10 @@ func parsePreviewWindowImpl(opts *previewOpts, input string) error {
|
|||||||
opts.follow = true
|
opts.follow = true
|
||||||
case "nofollow":
|
case "nofollow":
|
||||||
opts.follow = false
|
opts.follow = false
|
||||||
|
case "info":
|
||||||
|
opts.info = true
|
||||||
|
case "noinfo":
|
||||||
|
opts.info = false
|
||||||
default:
|
default:
|
||||||
if headerRegex.MatchString(token) {
|
if headerRegex.MatchString(token) {
|
||||||
if opts.headerLines, err = atoi(token[1:]); err != nil {
|
if opts.headerLines, err = atoi(token[1:]); err != nil {
|
||||||
@@ -2338,6 +2346,12 @@ 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 {
|
||||||
@@ -2625,6 +2639,10 @@ 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
|
||||||
|
|||||||
22
src/proxy.go
22
src/proxy.go
@@ -9,6 +9,7 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@ func fifo(name string) (string, error) {
|
|||||||
return output, nil
|
return output, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runProxy(commandPrefix string, cmdBuilder func(temp string) *exec.Cmd, opts *Options, withExports bool) (int, error) {
|
func runProxy(commandPrefix string, cmdBuilder func(temp string, needBash bool) (*exec.Cmd, error), opts *Options, withExports bool) (int, error) {
|
||||||
output, err := fifo("proxy-output")
|
output, err := fifo("proxy-output")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ExitError, err
|
return ExitError, err
|
||||||
@@ -92,17 +93,28 @@ func runProxy(commandPrefix string, cmdBuilder func(temp string) *exec.Cmd, opts
|
|||||||
// To ensure that the options are processed by a POSIX-compliant shell,
|
// To ensure that the options are processed by a POSIX-compliant shell,
|
||||||
// we need to write the command to a temporary file and execute it with sh.
|
// we need to write the command to a temporary file and execute it with sh.
|
||||||
var exports []string
|
var exports []string
|
||||||
|
needBash := false
|
||||||
if withExports {
|
if withExports {
|
||||||
exports = os.Environ()
|
validIdentifier := regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
|
||||||
for idx, pairStr := range exports {
|
for _, pairStr := range os.Environ() {
|
||||||
pair := strings.SplitN(pairStr, "=", 2)
|
pair := strings.SplitN(pairStr, "=", 2)
|
||||||
exports[idx] = fmt.Sprintf("export %s=%s", pair[0], escapeSingleQuote(pair[1]))
|
if validIdentifier.MatchString(pair[0]) {
|
||||||
|
exports = append(exports, fmt.Sprintf("export %s=%s", pair[0], escapeSingleQuote(pair[1])))
|
||||||
|
} else if strings.HasPrefix(pair[0], "BASH_FUNC_") && strings.HasSuffix(pair[0], "%%") {
|
||||||
|
name := pair[0][10 : len(pair[0])-2]
|
||||||
|
exports = append(exports, name+pair[1])
|
||||||
|
exports = append(exports, "export -f "+name)
|
||||||
|
needBash = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
temp := WriteTemporaryFile(append(exports, command), "\n")
|
temp := WriteTemporaryFile(append(exports, command), "\n")
|
||||||
defer os.Remove(temp)
|
defer os.Remove(temp)
|
||||||
|
|
||||||
cmd := cmdBuilder(temp)
|
cmd, err := cmdBuilder(temp, needBash)
|
||||||
|
if err != nil {
|
||||||
|
return ExitError, err
|
||||||
|
}
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
intChan := make(chan os.Signal, 1)
|
intChan := make(chan os.Signal, 1)
|
||||||
defer close(intChan)
|
defer close(intChan)
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
func sh() (string, error) {
|
func sh(bash bool) (string, error) {
|
||||||
|
if bash {
|
||||||
|
return "bash", nil
|
||||||
|
}
|
||||||
return "sh", nil
|
return "sh", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,12 +13,16 @@ import (
|
|||||||
|
|
||||||
var shPath atomic.Value
|
var shPath atomic.Value
|
||||||
|
|
||||||
func sh() (string, error) {
|
func sh(bash bool) (string, error) {
|
||||||
if cached := shPath.Load(); cached != nil {
|
if cached := shPath.Load(); cached != nil {
|
||||||
return cached.(string), nil
|
return cached.(string), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command("cygpath", "-w", "/usr/bin/sh")
|
name := "sh"
|
||||||
|
if bash {
|
||||||
|
name = "bash"
|
||||||
|
}
|
||||||
|
cmd := exec.Command("cygpath", "-w", "/usr/bin/"+name)
|
||||||
bytes, err := cmd.Output()
|
bytes, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -31,7 +35,7 @@ func sh() (string, error) {
|
|||||||
|
|
||||||
func mkfifo(path string, mode uint32) (string, error) {
|
func mkfifo(path string, mode uint32) (string, error) {
|
||||||
m := strconv.FormatUint(uint64(mode), 8)
|
m := strconv.FormatUint(uint64(mode), 8)
|
||||||
sh, err := sh()
|
sh, err := sh(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return path, err
|
return path, err
|
||||||
}
|
}
|
||||||
@@ -43,7 +47,7 @@ func mkfifo(path string, mode uint32) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func withOutputPipe(output string, task func(io.ReadCloser)) error {
|
func withOutputPipe(output string, task func(io.ReadCloser)) error {
|
||||||
sh, err := sh()
|
sh, err := sh(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -62,7 +66,7 @@ func withOutputPipe(output string, task func(io.ReadCloser)) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func withInputPipe(input string, task func(io.WriteCloser)) error {
|
func withInputPipe(input string, task func(io.WriteCloser)) error {
|
||||||
sh, err := sh()
|
sh, err := sh(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -25,16 +24,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 +89,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 +120,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, root 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 {
|
||||||
|
signalReady()
|
||||||
success = r.readFiles(root, opts, ignores)
|
success = r.readFiles(root, 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)
|
||||||
@@ -249,7 +266,6 @@ 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
|
||||||
@@ -265,7 +281,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] == '.' {
|
if !opts.hidden && base[0] == '.' && base != ".." {
|
||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
}
|
}
|
||||||
for _, ignore := range ignores {
|
for _, ignore := range ignores {
|
||||||
@@ -288,31 +304,32 @@ 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) 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
151
src/terminal.go
151
src/terminal.go
@@ -245,6 +245,7 @@ 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
|
||||||
@@ -825,6 +826,7 @@ 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,
|
||||||
@@ -1136,15 +1138,23 @@ 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 {
|
||||||
return 1, false
|
numLines = 1 + t.gap
|
||||||
|
return numLines, numLines > atMost
|
||||||
}
|
}
|
||||||
|
var overflow bool
|
||||||
if !t.wrap && t.multiLine {
|
if !t.wrap && t.multiLine {
|
||||||
return item.text.NumLines(atMost)
|
numLines, overflow = 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)
|
||||||
}
|
}
|
||||||
lines, overflow := item.text.Lines(t.multiLine, atMost, t.wrapCols(), t.wrapSignWidth, t.tabstop)
|
numLines += t.gap
|
||||||
return len(lines), overflow
|
return numLines, overflow || numLines > atMost
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) itemLines(item *Item, atMost int) ([][]rune, bool) {
|
func (t *Terminal) itemLines(item *Item, atMost int) ([][]rune, bool) {
|
||||||
@@ -2050,6 +2060,21 @@ 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()
|
||||||
@@ -2070,14 +2095,7 @@ 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.move(line, 0, true)
|
t.renderEmptyLine(line, barRange)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2125,9 +2143,6 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2214,6 +2229,10 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2275,7 +2294,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.multiLine || t.wrap {
|
if t.canSpanMultiLines() {
|
||||||
maxLines = maxLineNum - lineNum + 1
|
maxLines = maxLineNum - lineNum + 1
|
||||||
}
|
}
|
||||||
lines, overflow := t.itemLines(item, maxLines)
|
lines, overflow := t.itemLines(item, maxLines)
|
||||||
@@ -2285,7 +2304,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.multiLine || t.wrap {
|
if t.canSpanMultiLines() {
|
||||||
// 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)
|
||||||
@@ -2507,7 +2526,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 {
|
if !t.previewer.scrollable || !t.previewOpts.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)
|
||||||
@@ -2702,11 +2721,15 @@ 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)
|
||||||
@@ -2728,7 +2751,7 @@ Loop:
|
|||||||
if unchanged && lineNo == 0 {
|
if unchanged && lineNo == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if lbg >= 0 {
|
if t.theme.Colored && 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 {
|
||||||
@@ -4387,17 +4410,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
|
||||||
|
maxItems := t.maxItems()
|
||||||
|
linesToMove := maxItems - 1
|
||||||
|
if a.t == actHalfPageUp || a.t == actHalfPageDown {
|
||||||
|
linesToMove = maxItems / 2
|
||||||
|
}
|
||||||
|
// Move at least one line even in a very short window
|
||||||
|
linesToMove = util.Max(1, linesToMove)
|
||||||
|
|
||||||
|
// Determine the direction of the movement
|
||||||
|
direction := -1
|
||||||
|
if a.t == actPageUp || a.t == actHalfPageUp {
|
||||||
|
direction = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// In non-default layout, items are listed from top to bottom
|
||||||
|
if t.layout != layoutDefault {
|
||||||
|
direction *= -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can simply add the number of lines to the current position in
|
||||||
|
// single-line mode
|
||||||
|
if !t.canSpanMultiLines() {
|
||||||
|
t.vset(t.cy + direction*linesToMove)
|
||||||
req(reqList)
|
req(reqList)
|
||||||
case actPageDown:
|
break
|
||||||
t.vmove(-(t.maxItems() - 1), false)
|
}
|
||||||
req(reqList)
|
|
||||||
case actHalfPageUp:
|
// But in multi-line mode, we need to carefully limit the amount of
|
||||||
t.vmove(t.maxItems()/2, false)
|
// vertical movement so that items are not skipped. In order to do
|
||||||
req(reqList)
|
// this, we calculate the minimum or maximum offset based on the
|
||||||
case actHalfPageDown:
|
// direction of the movement and the number of lines of the items
|
||||||
t.vmove(-(t.maxItems() / 2), false)
|
// 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
|
||||||
@@ -4875,7 +4958,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.multiLine || t.wrap) && t.merger.Length() > 0 {
|
if t.canSpanMultiLines() && t.merger.Length() > 0 {
|
||||||
numItemsFound := 0
|
numItemsFound := 0
|
||||||
linesSum := 0
|
linesSum := 0
|
||||||
|
|
||||||
@@ -4930,12 +5013,12 @@ func (t *Terminal) constrain() {
|
|||||||
for {
|
for {
|
||||||
prevOffset := newOffset
|
prevOffset := newOffset
|
||||||
numItems := t.merger.Length()
|
numItems := t.merger.Length()
|
||||||
itemLines := 1
|
itemLines := 1 + t.gap
|
||||||
if (t.multiLine || t.wrap) && t.cy < numItems {
|
if t.canSpanMultiLines() && 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.multiLine || t.wrap {
|
if t.canSpanMultiLines() {
|
||||||
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)
|
||||||
|
|||||||
@@ -49,9 +49,12 @@ func runTmux(args []string, opts *Options) (int, error) {
|
|||||||
tmuxArgs = append(tmuxArgs, "-w"+opts.Tmux.width.String())
|
tmuxArgs = append(tmuxArgs, "-w"+opts.Tmux.width.String())
|
||||||
tmuxArgs = append(tmuxArgs, "-h"+opts.Tmux.height.String())
|
tmuxArgs = append(tmuxArgs, "-h"+opts.Tmux.height.String())
|
||||||
|
|
||||||
return runProxy(argStr, func(temp string) *exec.Cmd {
|
return runProxy(argStr, func(temp string, needBash bool) (*exec.Cmd, error) {
|
||||||
sh, _ := sh()
|
sh, err := sh(needBash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
tmuxArgs = append(tmuxArgs, sh, temp)
|
tmuxArgs = append(tmuxArgs, sh, temp)
|
||||||
return exec.Command("tmux", tmuxArgs...)
|
return exec.Command("tmux", tmuxArgs...), nil
|
||||||
}, opts, true)
|
}, opts, true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1097,7 +1097,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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,11 +44,6 @@ func needWinpty(opts *Options) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runWinpty(args []string, opts *Options) (int, error) {
|
func runWinpty(args []string, opts *Options) (int, error) {
|
||||||
sh, err := sh()
|
|
||||||
if err != nil {
|
|
||||||
return ExitError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
argStr := escapeSingleQuote(args[0])
|
argStr := escapeSingleQuote(args[0])
|
||||||
for _, arg := range args[1:] {
|
for _, arg := range args[1:] {
|
||||||
argStr += " " + escapeSingleQuote(arg)
|
argStr += " " + escapeSingleQuote(arg)
|
||||||
@@ -56,20 +51,30 @@ func runWinpty(args []string, opts *Options) (int, error) {
|
|||||||
argStr += ` --no-winpty`
|
argStr += ` --no-winpty`
|
||||||
|
|
||||||
if isMintty345() {
|
if isMintty345() {
|
||||||
return runProxy(argStr, func(temp string) *exec.Cmd {
|
return runProxy(argStr, func(temp string, needBash bool) (*exec.Cmd, error) {
|
||||||
|
sh, err := sh(needBash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
cmd := exec.Command(sh, temp)
|
cmd := exec.Command(sh, temp)
|
||||||
cmd.Env = append(os.Environ(), "MSYS=enable_pcon")
|
cmd.Env = append(os.Environ(), "MSYS=enable_pcon")
|
||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
return cmd
|
return cmd, nil
|
||||||
}, opts, false)
|
}, opts, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return runProxy(argStr, func(temp string) *exec.Cmd {
|
return runProxy(argStr, func(temp string, needBash bool) (*exec.Cmd, error) {
|
||||||
|
sh, err := sh(needBash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
cmd := exec.Command(sh, "-c", fmt.Sprintf(`winpty < /dev/tty > /dev/tty -- sh %q`, temp))
|
cmd := exec.Command(sh, "-c", fmt.Sprintf(`winpty < /dev/tty > /dev/tty -- sh %q`, temp))
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
return cmd
|
return cmd, nil
|
||||||
}, opts, false)
|
}, opts, false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3378,6 +3378,54 @@ class TestGoFZF < TestBase
|
|||||||
assert_equal expected, result
|
assert_equal expected, result
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_preview_window_noinfo
|
||||||
|
# │ 1 ││
|
||||||
|
tmux.send_keys %(#{FZF} --preview 'seq 1000' --preview-window top,noinfo --scrollbar --bind space:change-preview-window:info), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines[1]&.start_with?('│ 1')
|
||||||
|
assert lines[1]&.end_with?(' ││')
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines[1]&.start_with?('│ 1')
|
||||||
|
assert lines[1]&.end_with?('1000││')
|
||||||
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user