mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2024-11-12 08:26:27 +00:00
Merge branch 'devel'
This commit is contained in:
commit
b471042037
51
CHANGELOG.md
51
CHANGELOG.md
@ -1,6 +1,57 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.19.0
|
||||||
|
------
|
||||||
|
|
||||||
|
- Added "reload" action for dynamically updating the input list without
|
||||||
|
restarting fzf. See https://github.com/junegunn/fzf/issues/1750 to learn
|
||||||
|
more about it.
|
||||||
|
```sh
|
||||||
|
# Using fzf as the selector interface for ripgrep
|
||||||
|
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
||||||
|
INITIAL_QUERY="foo"
|
||||||
|
FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY'" \
|
||||||
|
fzf --bind "change:reload:$RG_PREFIX {q} || true" \
|
||||||
|
--ansi --phony --query "$INITIAL_QUERY"
|
||||||
|
```
|
||||||
|
- `--multi` now takes an optional integer argument which indicates the maximum
|
||||||
|
number of items that can be selected
|
||||||
|
```sh
|
||||||
|
seq 100 | fzf --multi 3 --reverse --height 50%
|
||||||
|
```
|
||||||
|
- If a placeholder expression for `--preview` and `execute` action (and the
|
||||||
|
new `reload` action) contains `f` flag, it is replaced to the
|
||||||
|
path of a temporary file that holds the evaluated list. This is useful
|
||||||
|
when you multi-select a large number of items and the length of the
|
||||||
|
evaluated string may exceed [`ARG_MAX`][argmax].
|
||||||
|
```sh
|
||||||
|
# Press CTRL-A to select 100K items and see the sum of all the numbers
|
||||||
|
seq 100000 | fzf --multi --bind ctrl-a:select-all \
|
||||||
|
--preview "awk '{sum+=\$1} END {print sum}' {+f}"
|
||||||
|
```
|
||||||
|
- `deselect-all` no longer deselects unmatched items. It is now consistent
|
||||||
|
with `select-all` and `toggle-all` in that it only affects matched items.
|
||||||
|
- Due to the limitation of bash, fuzzy completion is enabled by default for
|
||||||
|
a fixed set of commands. A helper function for easily setting up fuzzy
|
||||||
|
completion for any command is now provided.
|
||||||
|
```sh
|
||||||
|
# usage: _fzf_setup_completion path|dir COMMANDS...
|
||||||
|
_fzf_setup_completion path git kubectl
|
||||||
|
```
|
||||||
|
- Info line style can be changed by `--info=STYLE`
|
||||||
|
- `--info=default`
|
||||||
|
- `--info=inline` (same as old `--inline-info`)
|
||||||
|
- `--info=hidden`
|
||||||
|
- Preview window border can be disabled by adding `noborder` to
|
||||||
|
`--preview-window`.
|
||||||
|
- When you transform the input with `--with-nth`, the trailing white spaces
|
||||||
|
are removed.
|
||||||
|
- `ctrl-\`, `ctrl-]`, `ctrl-^`, and `ctrl-/` can now be used with `--bind`
|
||||||
|
- See https://github.com/junegunn/fzf/milestone/15?closed=1 for more details
|
||||||
|
|
||||||
|
[argmax]: https://unix.stackexchange.com/questions/120642/what-defines-the-maximum-size-for-a-command-single-argument
|
||||||
|
|
||||||
0.18.0
|
0.18.0
|
||||||
------
|
------
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
.ig
|
.ig
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2017 Junegunn Choi
|
Copyright (c) 2019 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -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 "Mar 2019" "fzf 0.18.0" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf-tmux 1 "Nov 2019" "fzf 0.19.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
|
||||||
|
269
man/man1/fzf.1
269
man/man1/fzf.1
@ -1,7 +1,7 @@
|
|||||||
.ig
|
.ig
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2017 Junegunn Choi
|
Copyright (c) 2019 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -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 "Mar 2019" "fzf 0.18.0" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Nov 2019" "fzf 0.19.0" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@ -70,6 +70,10 @@ Transform the presentation of each line using field index expressions
|
|||||||
.TP
|
.TP
|
||||||
.BI "-d, --delimiter=" "STR"
|
.BI "-d, --delimiter=" "STR"
|
||||||
Field delimiter regex for \fB--nth\fR and \fB--with-nth\fR (default: AWK-style)
|
Field delimiter regex for \fB--nth\fR and \fB--with-nth\fR (default: AWK-style)
|
||||||
|
.TP
|
||||||
|
.BI "--phony"
|
||||||
|
Do not perform search. With this option, fzf becomes a simple selector
|
||||||
|
interface rather than a "fuzzy finder".
|
||||||
.SS Search result
|
.SS Search result
|
||||||
.TP
|
.TP
|
||||||
.B "+s, --no-sort"
|
.B "+s, --no-sort"
|
||||||
@ -79,7 +83,8 @@ Do not sort the result
|
|||||||
Reverse the order of the input
|
Reverse the order of the input
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
e.g. \fBhistory | fzf --tac --no-sort\fR
|
e.g.
|
||||||
|
\fBhistory | fzf --tac --no-sort\fR
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.BI "--tiebreak=" "CRI[,..]"
|
.BI "--tiebreak=" "CRI[,..]"
|
||||||
@ -109,7 +114,8 @@ Comma-separated list of sort criteria to apply when the scores are tied.
|
|||||||
.SS Interface
|
.SS Interface
|
||||||
.TP
|
.TP
|
||||||
.B "-m, --multi"
|
.B "-m, --multi"
|
||||||
Enable multi-select with tab/shift-tab
|
Enable multi-select with tab/shift-tab. It optionally takes an integer argument
|
||||||
|
which denotes the maximum number of items that can be selected.
|
||||||
.TP
|
.TP
|
||||||
.B "+m, --no-multi"
|
.B "+m, --no-multi"
|
||||||
Disable multi-select
|
Disable multi-select
|
||||||
@ -118,8 +124,8 @@ Disable multi-select
|
|||||||
Disable mouse
|
Disable mouse
|
||||||
.TP
|
.TP
|
||||||
.BI "--bind=" "KEYBINDS"
|
.BI "--bind=" "KEYBINDS"
|
||||||
Comma-separated list of custom key bindings. See \fBKEY BINDINGS\fR for the
|
Comma-separated list of custom key bindings. See \fBKEY/EVENT BINDINGS\fR for
|
||||||
details.
|
the details.
|
||||||
.TP
|
.TP
|
||||||
.B "--cycle"
|
.B "--cycle"
|
||||||
Enable cyclic scroll
|
Enable cyclic scroll
|
||||||
@ -201,12 +207,26 @@ terminal size with \fB%\fR suffix.
|
|||||||
.br
|
.br
|
||||||
|
|
||||||
.br
|
.br
|
||||||
e.g. \fBfzf --margin 10%\fR
|
e.g.
|
||||||
\fBfzf --margin 1,5%\fR
|
\fBfzf --margin 10%
|
||||||
|
fzf --margin 1,5%\fR
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.B "--inline-info"
|
.BI "--info=" "STYLE"
|
||||||
Display finder info inline with the query
|
Determines the display style of finder info.
|
||||||
|
|
||||||
|
.br
|
||||||
|
.BR default " Display on the next line to the prompt"
|
||||||
|
.br
|
||||||
|
.BR inline " Display on the same line"
|
||||||
|
.br
|
||||||
|
.BR hidden " Do not display finder info"
|
||||||
|
.br
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B "--no-info"
|
||||||
|
A synonym for \fB--info=hidden\fB
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "--prompt=" "STR"
|
.BI "--prompt=" "STR"
|
||||||
Input prompt (default: '> ')
|
Input prompt (default: '> ')
|
||||||
@ -235,11 +255,6 @@ color mappings. Ansi color code of -1 denotes terminal default
|
|||||||
foreground/background color. You can also specify 24-bit color in \fB#rrggbb\fR
|
foreground/background color. You can also specify 24-bit color in \fB#rrggbb\fR
|
||||||
format.
|
format.
|
||||||
|
|
||||||
.RS
|
|
||||||
e.g. \fBfzf --color=bg+:24\fR
|
|
||||||
\fBfzf --color=light,fg:232,bg:255,bg+:116,info:27\fR
|
|
||||||
.RE
|
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
.B BASE SCHEME:
|
.B BASE SCHEME:
|
||||||
(default: dark on 256-color terminal, otherwise 16)
|
(default: dark on 256-color terminal, otherwise 16)
|
||||||
@ -264,6 +279,19 @@ e.g. \fBfzf --color=bg+:24\fR
|
|||||||
\fBmarker \fRMulti-select marker
|
\fBmarker \fRMulti-select marker
|
||||||
\fBspinner \fRStreaming input indicator
|
\fBspinner \fRStreaming input indicator
|
||||||
\fBheader \fRHeader
|
\fBheader \fRHeader
|
||||||
|
|
||||||
|
.B EXAMPLES:
|
||||||
|
|
||||||
|
\fB# Seoul256 theme with 8-bit colors
|
||||||
|
# (https://github.com/junegunn/seoul256.vim)
|
||||||
|
fzf --color='bg:237,bg+:236,info:143,border:240,spinner:108' \\
|
||||||
|
--color='hl:65,fg:252,header:65,fg+:252' \\
|
||||||
|
--color='pointer:161,marker:168,prompt:110,hl+:108'
|
||||||
|
|
||||||
|
# Seoul256 theme with 24-bit colors
|
||||||
|
fzf --color='bg:#4B4B4B,bg+:#3F3F3F,info:#BDBB72,border:#6B6B6B,spinner:#98BC99' \\
|
||||||
|
--color='hl:#719872,fg:#D9D9D9,header:#719872,fg+:#D9D9D9' \\
|
||||||
|
--color='pointer:#E12672,marker:#E17899,prompt:#98BEDE,hl+:#98BC99'\fR
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.B "--no-bold"
|
.B "--no-bold"
|
||||||
@ -291,8 +319,9 @@ string, specify field index expressions between the braces (See \fBFIELD INDEX
|
|||||||
EXPRESSION\fR for the details).
|
EXPRESSION\fR for the details).
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
e.g. \fBfzf --preview='head -$LINES {}'\fR
|
e.g.
|
||||||
\fBls -l | fzf --preview="echo user={3} when={-4..-2}; cat {-1}" --header-lines=1\fR
|
\fBfzf --preview='head -$LINES {}'
|
||||||
|
ls -l | fzf --preview="echo user={3} when={-4..-2}; cat {-1}" --header-lines=1\fR
|
||||||
|
|
||||||
fzf exports \fB$FZF_PREVIEW_LINES\fR and \fB$FZF_PREVIEW_COLUMNS\fR so that
|
fzf exports \fB$FZF_PREVIEW_LINES\fR and \fB$FZF_PREVIEW_COLUMNS\fR so that
|
||||||
they represent the exact size of the preview window. (It also overrides
|
they represent the exact size of the preview window. (It also overrides
|
||||||
@ -304,8 +333,9 @@ A placeholder expression starting with \fB+\fR flag will be replaced to the
|
|||||||
space-separated list of the selected lines (or the current line if no selection
|
space-separated list of the selected lines (or the current line if no selection
|
||||||
was made) individually quoted.
|
was made) individually quoted.
|
||||||
|
|
||||||
e.g. \fBfzf --multi --preview='head -10 {+}'\fR
|
e.g.
|
||||||
\fBgit log --oneline | fzf --multi --preview 'git show {+1}'\fR
|
\fBfzf --multi --preview='head -10 {+}'
|
||||||
|
git log --oneline | fzf --multi --preview 'git show {+1}'\fR
|
||||||
|
|
||||||
When using a field index expression, leading and trailing whitespace is stripped
|
When using a field index expression, leading and trailing whitespace is stripped
|
||||||
from the replacement string. To preserve the whitespace, use the \fBs\fR flag.
|
from the replacement string. To preserve the whitespace, use the \fBs\fR flag.
|
||||||
@ -314,14 +344,25 @@ Also, \fB{q}\fR is replaced to the current query string, and \fB{n}\fR is
|
|||||||
replaced to zero-based ordinal index of the line. Use \fB{+n}\fR if you want
|
replaced to zero-based ordinal index of the line. Use \fB{+n}\fR if you want
|
||||||
all index numbers when multiple lines are selected.
|
all index numbers when multiple lines are selected.
|
||||||
|
|
||||||
|
A placeholder expression with \fBf\fR flag is replaced to the path of
|
||||||
|
a temporary file that holds the evaluated list. This is useful when you
|
||||||
|
multi-select a large number of items and the length of the evaluated string may
|
||||||
|
exceed \fBARG_MAX\fR.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fB# Press CTRL-A to select 100K items and see the sum of all the numbers.
|
||||||
|
# This won't work properly without 'f' flag due to ARG_MAX limit.
|
||||||
|
seq 100000 | fzf --multi --bind ctrl-a:select-all \\
|
||||||
|
--preview "awk '{sum+=\$1} END {print sum}' {+f}"\fR
|
||||||
|
|
||||||
Note that you can escape a placeholder pattern by prepending a backslash.
|
Note that you can escape a placeholder pattern by prepending a backslash.
|
||||||
|
|
||||||
Preview window will be updated even when there is no match for the current
|
Preview window will be updated even when there is no match for the current
|
||||||
query if any of the placeholder expressions evaluates to a non-empty string.
|
query if any of the placeholder expressions evaluates to a non-empty string.
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.BI "--preview-window=" "[POSITION][:SIZE[%]][:wrap][:hidden]"
|
.BI "--preview-window=" "[POSITION][:SIZE[%]][:noborder][:wrap][:hidden]"
|
||||||
Determine the layout of the preview window. If the argument ends with
|
Determines the layout of the preview window. If the argument contains
|
||||||
\fB:hidden\fR, the preview window will be hidden by default until
|
\fB:hidden\fR, the preview window will be hidden by default until
|
||||||
\fBtoggle-preview\fR action is triggered. Long lines are truncated by default.
|
\fBtoggle-preview\fR action is triggered. Long lines are truncated by default.
|
||||||
Line wrap can be enabled with \fB:wrap\fR flag.
|
Line wrap can be enabled with \fB:wrap\fR flag.
|
||||||
@ -338,8 +379,9 @@ execute the command in the background.
|
|||||||
.RE
|
.RE
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
e.g. \fBfzf --preview="head {}" --preview-window=up:30%\fR
|
e.g.
|
||||||
\fBfzf --preview="file {}" --preview-window=down:1\fR
|
\fBfzf --preview="head {}" --preview-window=up:30%
|
||||||
|
fzf --preview="file {}" --preview-window=down:1\fR
|
||||||
.RE
|
.RE
|
||||||
.SS Scripting
|
.SS Scripting
|
||||||
.TP
|
.TP
|
||||||
@ -369,7 +411,8 @@ times, fzf will expect the union of the keys. \fB--no-expect\fR will clear the
|
|||||||
list.
|
list.
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
e.g. \fBfzf --expect=ctrl-v,ctrl-t,alt-s --expect=f1,f2,~,@\fR
|
e.g.
|
||||||
|
\fBfzf --expect=ctrl-v,ctrl-t,alt-s --expect=f1,f2,~,@\fR
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.B "--read0"
|
.B "--read0"
|
||||||
@ -475,56 +518,110 @@ query matches entries that start with \fBcore\fR and end with either \fBgo\fR,
|
|||||||
|
|
||||||
e.g. \fB^core go$ | rb$ | py$\fR
|
e.g. \fB^core go$ | rb$ | py$\fR
|
||||||
|
|
||||||
.SH KEY BINDINGS
|
.SH KEY/EVENT BINDINGS
|
||||||
You can customize key bindings of fzf with \fB--bind\fR option which takes
|
\fB--bind\fR option allows you to bind \fBa key\fR or \fBan event\fR to one or
|
||||||
a comma-separated list of key binding expressions. Each key binding expression
|
more \fBactions\fR. You can use it to customize key bindings or implement
|
||||||
follows the following format: \fBKEY:ACTION\fR
|
dynamic behaviors.
|
||||||
|
|
||||||
e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
\fB--bind\fR takes a comma-separated list of binding expressions. Each binding
|
||||||
|
expression is \fBKEY:ACTION\fR or \fBEVENT:ACTION\fR.
|
||||||
|
|
||||||
.B AVAILABLE KEYS: (SYNONYMS)
|
e.g.
|
||||||
\fIctrl-[a-z]\fR
|
\fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
||||||
\fIctrl-space\fR
|
|
||||||
\fIctrl-alt-[a-z]\fR
|
|
||||||
\fIalt-[a-z]\fR
|
|
||||||
\fIalt-[0-9]\fR
|
|
||||||
\fIf[1-12]\fR
|
|
||||||
\fIenter\fR (\fIreturn\fR \fIctrl-m\fR)
|
|
||||||
\fIspace\fR
|
|
||||||
\fIbspace\fR (\fIbs\fR)
|
|
||||||
\fIalt-up\fR
|
|
||||||
\fIalt-down\fR
|
|
||||||
\fIalt-left\fR
|
|
||||||
\fIalt-right\fR
|
|
||||||
\fIalt-enter\fR
|
|
||||||
\fIalt-space\fR
|
|
||||||
\fIalt-bspace\fR (\fIalt-bs\fR)
|
|
||||||
\fIalt-/\fR
|
|
||||||
\fItab\fR
|
|
||||||
\fIbtab\fR (\fIshift-tab\fR)
|
|
||||||
\fIesc\fR
|
|
||||||
\fIdel\fR
|
|
||||||
\fIup\fR
|
|
||||||
\fIdown\fR
|
|
||||||
\fIleft\fR
|
|
||||||
\fIright\fR
|
|
||||||
\fIhome\fR
|
|
||||||
\fIend\fR
|
|
||||||
\fIpgup\fR (\fIpage-up\fR)
|
|
||||||
\fIpgdn\fR (\fIpage-down\fR)
|
|
||||||
\fIshift-up\fR
|
|
||||||
\fIshift-down\fR
|
|
||||||
\fIshift-left\fR
|
|
||||||
\fIshift-right\fR
|
|
||||||
\fIleft-click\fR
|
|
||||||
\fIright-click\fR
|
|
||||||
\fIdouble-click\fR
|
|
||||||
or any single character
|
|
||||||
|
|
||||||
Additionally, a special event named \fIchange\fR is available which is
|
.SS AVAILABLE KEYS: (SYNONYMS)
|
||||||
triggered whenever the query string is changed.
|
\fIctrl-[a-z]\fR
|
||||||
|
.br
|
||||||
|
\fIctrl-space\fR
|
||||||
|
.br
|
||||||
|
\fIctrl-\\\fR
|
||||||
|
.br
|
||||||
|
\fIctrl-]\fR
|
||||||
|
.br
|
||||||
|
\fIctrl-^\fR (\fIctrl-6\fR)
|
||||||
|
.br
|
||||||
|
\fIctrl-/\fR (\fIctrl-_\fR)
|
||||||
|
.br
|
||||||
|
\fIctrl-alt-[a-z]\fR
|
||||||
|
.br
|
||||||
|
\fIalt-[a-z]\fR
|
||||||
|
.br
|
||||||
|
\fIalt-[0-9]\fR
|
||||||
|
.br
|
||||||
|
\fIf[1-12]\fR
|
||||||
|
.br
|
||||||
|
\fIenter\fR (\fIreturn\fR \fIctrl-m\fR)
|
||||||
|
.br
|
||||||
|
\fIspace\fR
|
||||||
|
.br
|
||||||
|
\fIbspace\fR (\fIbs\fR)
|
||||||
|
.br
|
||||||
|
\fIalt-up\fR
|
||||||
|
.br
|
||||||
|
\fIalt-down\fR
|
||||||
|
.br
|
||||||
|
\fIalt-left\fR
|
||||||
|
.br
|
||||||
|
\fIalt-right\fR
|
||||||
|
.br
|
||||||
|
\fIalt-enter\fR
|
||||||
|
.br
|
||||||
|
\fIalt-space\fR
|
||||||
|
.br
|
||||||
|
\fIalt-bspace\fR (\fIalt-bs\fR)
|
||||||
|
.br
|
||||||
|
\fIalt-/\fR
|
||||||
|
.br
|
||||||
|
\fItab\fR
|
||||||
|
.br
|
||||||
|
\fIbtab\fR (\fIshift-tab\fR)
|
||||||
|
.br
|
||||||
|
\fIesc\fR
|
||||||
|
.br
|
||||||
|
\fIdel\fR
|
||||||
|
.br
|
||||||
|
\fIup\fR
|
||||||
|
.br
|
||||||
|
\fIdown\fR
|
||||||
|
.br
|
||||||
|
\fIleft\fR
|
||||||
|
.br
|
||||||
|
\fIright\fR
|
||||||
|
.br
|
||||||
|
\fIhome\fR
|
||||||
|
.br
|
||||||
|
\fIend\fR
|
||||||
|
.br
|
||||||
|
\fIpgup\fR (\fIpage-up\fR)
|
||||||
|
.br
|
||||||
|
\fIpgdn\fR (\fIpage-down\fR)
|
||||||
|
.br
|
||||||
|
\fIshift-up\fR
|
||||||
|
.br
|
||||||
|
\fIshift-down\fR
|
||||||
|
.br
|
||||||
|
\fIshift-left\fR
|
||||||
|
.br
|
||||||
|
\fIshift-right\fR
|
||||||
|
.br
|
||||||
|
\fIleft-click\fR
|
||||||
|
.br
|
||||||
|
\fIright-click\fR
|
||||||
|
.br
|
||||||
|
\fIdouble-click\fR
|
||||||
|
.br
|
||||||
|
or any single character
|
||||||
|
|
||||||
e.g. \fBfzf --bind change:top\fR
|
.SS AVAILABLE EVENTS:
|
||||||
|
\fIchange\fR (triggered whenever the query string is changed)
|
||||||
|
.br
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fB# Moves cursor to the top (or bottom depending on --layout) whenever the query is changed
|
||||||
|
fzf --bind change:top\fR
|
||||||
|
|
||||||
|
.SS AVAILABLE ACTIONS:
|
||||||
|
A key or an event can be bound to one or more of the following actions.
|
||||||
|
|
||||||
\fBACTION: DEFAULT BINDINGS (NOTES):
|
\fBACTION: DEFAULT BINDINGS (NOTES):
|
||||||
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
|
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
|
||||||
@ -563,6 +660,7 @@ triggered whenever the query string is changed.
|
|||||||
\fBpreview-page-up\fR
|
\fBpreview-page-up\fR
|
||||||
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
||||||
\fBprint-query\fR (print query and exit)
|
\fBprint-query\fR (print query and exit)
|
||||||
|
\fBreload(...)\fR (see below for the details)
|
||||||
\fBreplace-query\fR (replace query string with the current selection)
|
\fBreplace-query\fR (replace query string with the current selection)
|
||||||
\fBselect-all\fR
|
\fBselect-all\fR
|
||||||
\fBtoggle\fR (\fIright-click\fR)
|
\fBtoggle\fR (\fIright-click\fR)
|
||||||
@ -580,10 +678,15 @@ triggered whenever the query string is changed.
|
|||||||
\fBup\fR \fIctrl-k ctrl-p up\fR
|
\fBup\fR \fIctrl-k ctrl-p up\fR
|
||||||
\fByank\fR \fIctrl-y\fR
|
\fByank\fR \fIctrl-y\fR
|
||||||
|
|
||||||
|
.SS ACTION COMPOSITION
|
||||||
|
|
||||||
Multiple actions can be chained using \fB+\fR separator.
|
Multiple actions can be chained using \fB+\fR separator.
|
||||||
|
|
||||||
|
e.g.
|
||||||
\fBfzf --bind 'ctrl-a:select-all+accept'\fR
|
\fBfzf --bind 'ctrl-a:select-all+accept'\fR
|
||||||
|
|
||||||
|
.SS COMMAND EXECUTION
|
||||||
|
|
||||||
With \fBexecute(...)\fR action, you can execute arbitrary commands without
|
With \fBexecute(...)\fR action, you can execute arbitrary commands without
|
||||||
leaving fzf. For example, you can turn fzf into a simple file browser by
|
leaving fzf. For example, you can turn fzf into a simple file browser by
|
||||||
binding \fBenter\fR key to \fBless\fR command like follows.
|
binding \fBenter\fR key to \fBless\fR command like follows.
|
||||||
@ -611,9 +714,9 @@ parse errors.
|
|||||||
\fBexecute|...|\fR
|
\fBexecute|...|\fR
|
||||||
\fBexecute:...\fR
|
\fBexecute:...\fR
|
||||||
.RS
|
.RS
|
||||||
This is the special form that frees you from parse errors as it does not expect
|
The last one is the special form that frees you from parse errors as it does
|
||||||
the closing character. The catch is that it should be the last one in the
|
not expect the closing character. The catch is that it should be the last one
|
||||||
comma-separated list of key-action pairs.
|
in the comma-separated list of key-action pairs.
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
fzf switches to the alternate screen when executing a command. However, if the
|
fzf switches to the alternate screen when executing a command. However, if the
|
||||||
@ -623,6 +726,26 @@ executes the command without the switching. Note that fzf will not be
|
|||||||
responsive until the command is complete. For asynchronous execution, start
|
responsive until the command is complete. For asynchronous execution, start
|
||||||
your command as a background process (i.e. appending \fB&\fR).
|
your command as a background process (i.e. appending \fB&\fR).
|
||||||
|
|
||||||
|
.SS RELOAD INPUT
|
||||||
|
|
||||||
|
\fBreload(...)\fR action is used to dynamically update the input list
|
||||||
|
without restarting fzf. It takes the same command template with placeholder
|
||||||
|
expressions as \fBexecute(...)\fR.
|
||||||
|
|
||||||
|
See \fIhttps://github.com/junegunn/fzf/issues/1750\fR for more info.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fB# Update the list of processes by pressing CTRL-R
|
||||||
|
ps -ef | fzf --bind 'ctrl-r:reload(ps -ef)' --header 'Press CTRL-R to reload' \\
|
||||||
|
--header-lines=1 --layout=reverse
|
||||||
|
|
||||||
|
# Integration with ripgrep
|
||||||
|
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
||||||
|
INITIAL_QUERY="foobar"
|
||||||
|
FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY'" \\
|
||||||
|
fzf --bind "change:reload:$RG_PREFIX {q} || true" \\
|
||||||
|
--ansi --phony --query "$INITIAL_QUERY"\fR
|
||||||
|
|
||||||
.SH AUTHOR
|
.SH AUTHOR
|
||||||
Junegunn Choi (\fIjunegunn.c@gmail.com\fR)
|
Junegunn Choi (\fIjunegunn.c@gmail.com\fR)
|
||||||
|
|
||||||
|
@ -64,6 +64,13 @@ func (cl *ChunkList) Push(data []byte) bool {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear clears the data
|
||||||
|
func (cl *ChunkList) Clear() {
|
||||||
|
cl.mutex.Lock()
|
||||||
|
cl.chunks = nil
|
||||||
|
cl.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
// Snapshot returns immutable snapshot of the ChunkList
|
// Snapshot returns immutable snapshot of the ChunkList
|
||||||
func (cl *ChunkList) Snapshot() ([]*Chunk, int) {
|
func (cl *ChunkList) Snapshot() ([]*Chunk, int) {
|
||||||
cl.mutex.Lock()
|
cl.mutex.Lock()
|
||||||
|
48
src/core.go
48
src/core.go
@ -126,6 +126,7 @@ func Run(opts *Options, revision string) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
item.text, item.colors = ansiProcessor([]byte(transformed))
|
item.text, item.colors = ansiProcessor([]byte(transformed))
|
||||||
|
item.text.TrimTrailingWhitespaces()
|
||||||
item.text.Index = itemIndex
|
item.text.Index = itemIndex
|
||||||
item.origText = &data
|
item.origText = &data
|
||||||
itemIndex++
|
itemIndex++
|
||||||
@ -135,10 +136,11 @@ func Run(opts *Options, revision string) {
|
|||||||
|
|
||||||
// Reader
|
// Reader
|
||||||
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
|
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
|
||||||
|
var reader *Reader
|
||||||
if !streamingFilter {
|
if !streamingFilter {
|
||||||
reader := NewReader(func(data []byte) bool {
|
reader = NewReader(func(data []byte) bool {
|
||||||
return chunkList.Push(data)
|
return chunkList.Push(data)
|
||||||
}, eventBox, opts.ReadZero)
|
}, eventBox, opts.ReadZero, opts.Filter == nil)
|
||||||
go reader.ReadSource()
|
go reader.ReadSource()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,7 +184,7 @@ func Run(opts *Options, revision string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}, eventBox, opts.ReadZero)
|
}, eventBox, opts.ReadZero, false)
|
||||||
reader.ReadSource()
|
reader.ReadSource()
|
||||||
} else {
|
} else {
|
||||||
eventBox.Unwatch(EvtReadNew)
|
eventBox.Unwatch(EvtReadNew)
|
||||||
@ -223,10 +225,23 @@ func Run(opts *Options, revision string) {
|
|||||||
// Event coordination
|
// Event coordination
|
||||||
reading := true
|
reading := true
|
||||||
ticks := 0
|
ticks := 0
|
||||||
|
var nextCommand *string
|
||||||
|
restart := func(command string) {
|
||||||
|
reading = true
|
||||||
|
chunkList.Clear()
|
||||||
|
header = make([]string, 0, opts.HeaderLines)
|
||||||
|
go reader.restart(command)
|
||||||
|
}
|
||||||
eventBox.Watch(EvtReadNew)
|
eventBox.Watch(EvtReadNew)
|
||||||
for {
|
for {
|
||||||
delay := true
|
delay := true
|
||||||
ticks++
|
ticks++
|
||||||
|
input := func() []rune {
|
||||||
|
if opts.Phony {
|
||||||
|
return []rune{}
|
||||||
|
}
|
||||||
|
return []rune(terminal.Input())
|
||||||
|
}
|
||||||
eventBox.Wait(func(events *util.Events) {
|
eventBox.Wait(func(events *util.Events) {
|
||||||
if _, fin := (*events)[EvtReadFin]; fin {
|
if _, fin := (*events)[EvtReadFin]; fin {
|
||||||
delete(*events, EvtReadNew)
|
delete(*events, EvtReadNew)
|
||||||
@ -235,21 +250,38 @@ func Run(opts *Options, revision string) {
|
|||||||
switch evt {
|
switch evt {
|
||||||
|
|
||||||
case EvtReadNew, EvtReadFin:
|
case EvtReadNew, EvtReadFin:
|
||||||
|
clearCache := false
|
||||||
|
if evt == EvtReadFin && nextCommand != nil {
|
||||||
|
clearCache = true
|
||||||
|
restart(*nextCommand)
|
||||||
|
nextCommand = nil
|
||||||
|
} else {
|
||||||
reading = reading && evt == EvtReadNew
|
reading = reading && evt == EvtReadNew
|
||||||
|
}
|
||||||
snapshot, count := chunkList.Snapshot()
|
snapshot, count := chunkList.Snapshot()
|
||||||
terminal.UpdateCount(count, !reading, value.(bool))
|
terminal.UpdateCount(count, !reading, value.(*string))
|
||||||
if opts.Sync {
|
if opts.Sync {
|
||||||
terminal.UpdateList(PassMerger(&snapshot, opts.Tac))
|
terminal.UpdateList(PassMerger(&snapshot, opts.Tac))
|
||||||
}
|
}
|
||||||
matcher.Reset(snapshot, terminal.Input(), false, !reading, sort)
|
matcher.Reset(snapshot, input(), false, !reading, sort, clearCache)
|
||||||
|
|
||||||
case EvtSearchNew:
|
case EvtSearchNew:
|
||||||
|
var command *string
|
||||||
switch val := value.(type) {
|
switch val := value.(type) {
|
||||||
case bool:
|
case searchRequest:
|
||||||
sort = val
|
sort = val.sort
|
||||||
|
command = val.command
|
||||||
|
}
|
||||||
|
if command != nil {
|
||||||
|
if reading {
|
||||||
|
reader.terminate()
|
||||||
|
nextCommand = command
|
||||||
|
} else {
|
||||||
|
restart(*command)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
snapshot, _ := chunkList.Snapshot()
|
snapshot, _ := chunkList.Snapshot()
|
||||||
matcher.Reset(snapshot, terminal.Input(), true, !reading, sort)
|
matcher.Reset(snapshot, input(), true, !reading, sort, command != nil)
|
||||||
delay = false
|
delay = false
|
||||||
|
|
||||||
case EvtSearchProgress:
|
case EvtSearchProgress:
|
||||||
|
@ -16,6 +16,7 @@ type MatchRequest struct {
|
|||||||
pattern *Pattern
|
pattern *Pattern
|
||||||
final bool
|
final bool
|
||||||
sort bool
|
sort bool
|
||||||
|
clearCache bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Matcher is responsible for performing search
|
// Matcher is responsible for performing search
|
||||||
@ -69,7 +70,7 @@ func (m *Matcher) Loop() {
|
|||||||
events.Clear()
|
events.Clear()
|
||||||
})
|
})
|
||||||
|
|
||||||
if request.sort != m.sort {
|
if request.sort != m.sort || request.clearCache {
|
||||||
m.sort = request.sort
|
m.sort = request.sort
|
||||||
m.mergerCache = make(map[string]*Merger)
|
m.mergerCache = make(map[string]*Merger)
|
||||||
clearChunkCache()
|
clearChunkCache()
|
||||||
@ -221,7 +222,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reset is called to interrupt/signal the ongoing search
|
// Reset is called to interrupt/signal the ongoing search
|
||||||
func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final bool, sort bool) {
|
func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final bool, sort bool, clearCache bool) {
|
||||||
pattern := m.patternBuilder(patternRunes)
|
pattern := m.patternBuilder(patternRunes)
|
||||||
|
|
||||||
var event util.EventType
|
var event util.EventType
|
||||||
@ -230,5 +231,5 @@ func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final
|
|||||||
} else {
|
} else {
|
||||||
event = reqRetry
|
event = reqRetry
|
||||||
}
|
}
|
||||||
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable})
|
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable, clearCache})
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ const usage = `usage: fzf [options]
|
|||||||
-d, --delimiter=STR Field delimiter regex (default: AWK-style)
|
-d, --delimiter=STR Field delimiter regex (default: AWK-style)
|
||||||
+s, --no-sort Do not sort the result
|
+s, --no-sort Do not sort the result
|
||||||
--tac Reverse the order of the input
|
--tac Reverse the order of the input
|
||||||
|
--phony Do not perform search
|
||||||
--tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
|
--tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
|
||||||
when the scores are tied [length|begin|end|index]
|
when the scores are tied [length|begin|end|index]
|
||||||
(default: length)
|
(default: length)
|
||||||
@ -56,7 +57,7 @@ const usage = `usage: fzf [options]
|
|||||||
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
|
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
|
||||||
--border Draw border above and below the finder
|
--border Draw border above and below the finder
|
||||||
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
|
--margin=MARGIN Screen margin (TRBL / TB,RL / T,RL,B / T,R,B,L)
|
||||||
--inline-info Display finder info inline with the query
|
--info=STYLE Finder info style [default|inline|hidden]
|
||||||
--prompt=STR Input prompt (default: '> ')
|
--prompt=STR Input prompt (default: '> ')
|
||||||
--header=STR String to print as header
|
--header=STR String to print as header
|
||||||
--header-lines=N The first N lines of the input are treated as header
|
--header-lines=N The first N lines of the input are treated as header
|
||||||
@ -141,12 +142,21 @@ const (
|
|||||||
layoutReverseList
|
layoutReverseList
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type infoStyle int
|
||||||
|
|
||||||
|
const (
|
||||||
|
infoDefault infoStyle = iota
|
||||||
|
infoInline
|
||||||
|
infoHidden
|
||||||
|
)
|
||||||
|
|
||||||
type previewOpts struct {
|
type previewOpts struct {
|
||||||
command string
|
command string
|
||||||
position windowPosition
|
position windowPosition
|
||||||
size sizeSpec
|
size sizeSpec
|
||||||
hidden bool
|
hidden bool
|
||||||
wrap bool
|
wrap bool
|
||||||
|
border bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Options stores the values of command-line options
|
// Options stores the values of command-line options
|
||||||
@ -154,6 +164,7 @@ type Options struct {
|
|||||||
Fuzzy bool
|
Fuzzy bool
|
||||||
FuzzyAlgo algo.Algo
|
FuzzyAlgo algo.Algo
|
||||||
Extended bool
|
Extended bool
|
||||||
|
Phony bool
|
||||||
Case Case
|
Case Case
|
||||||
Normalize bool
|
Normalize bool
|
||||||
Nth []Range
|
Nth []Range
|
||||||
@ -175,7 +186,7 @@ type Options struct {
|
|||||||
Hscroll bool
|
Hscroll bool
|
||||||
HscrollOff int
|
HscrollOff int
|
||||||
FileWord bool
|
FileWord bool
|
||||||
InlineInfo bool
|
InfoStyle infoStyle
|
||||||
JumpLabels string
|
JumpLabels string
|
||||||
Prompt string
|
Prompt string
|
||||||
Query string
|
Query string
|
||||||
@ -207,6 +218,7 @@ func defaultOptions() *Options {
|
|||||||
Fuzzy: true,
|
Fuzzy: true,
|
||||||
FuzzyAlgo: algo.FuzzyMatchV2,
|
FuzzyAlgo: algo.FuzzyMatchV2,
|
||||||
Extended: true,
|
Extended: true,
|
||||||
|
Phony: false,
|
||||||
Case: CaseSmart,
|
Case: CaseSmart,
|
||||||
Normalize: true,
|
Normalize: true,
|
||||||
Nth: make([]Range, 0),
|
Nth: make([]Range, 0),
|
||||||
@ -227,7 +239,7 @@ func defaultOptions() *Options {
|
|||||||
Hscroll: true,
|
Hscroll: true,
|
||||||
HscrollOff: 10,
|
HscrollOff: 10,
|
||||||
FileWord: false,
|
FileWord: false,
|
||||||
InlineInfo: false,
|
InfoStyle: infoDefault,
|
||||||
JumpLabels: defaultJumpLabels,
|
JumpLabels: defaultJumpLabels,
|
||||||
Prompt: "> ",
|
Prompt: "> ",
|
||||||
Query: "",
|
Query: "",
|
||||||
@ -237,7 +249,7 @@ func defaultOptions() *Options {
|
|||||||
ToggleSort: false,
|
ToggleSort: false,
|
||||||
Expect: make(map[int]string),
|
Expect: make(map[int]string),
|
||||||
Keymap: make(map[int][]action),
|
Keymap: make(map[int][]action),
|
||||||
Preview: previewOpts{"", posRight, sizeSpec{50, true}, false, false},
|
Preview: previewOpts{"", posRight, sizeSpec{50, true}, false, false, true},
|
||||||
PrintQuery: false,
|
PrintQuery: false,
|
||||||
ReadZero: false,
|
ReadZero: false,
|
||||||
Printer: func(str string) { fmt.Println(str) },
|
Printer: func(str string) { fmt.Println(str) },
|
||||||
@ -414,6 +426,14 @@ func parseKeyChords(str string, message string) map[int]string {
|
|||||||
chord = tui.BSpace
|
chord = tui.BSpace
|
||||||
case "ctrl-space":
|
case "ctrl-space":
|
||||||
chord = tui.CtrlSpace
|
chord = tui.CtrlSpace
|
||||||
|
case "ctrl-^", "ctrl-6":
|
||||||
|
chord = tui.CtrlCaret
|
||||||
|
case "ctrl-/", "ctrl-_":
|
||||||
|
chord = tui.CtrlSlash
|
||||||
|
case "ctrl-\\":
|
||||||
|
chord = tui.CtrlBackSlash
|
||||||
|
case "ctrl-]":
|
||||||
|
chord = tui.CtrlRightBracket
|
||||||
case "change":
|
case "change":
|
||||||
chord = tui.Change
|
chord = tui.Change
|
||||||
case "alt-enter", "alt-return":
|
case "alt-enter", "alt-return":
|
||||||
@ -628,13 +648,15 @@ func init() {
|
|||||||
// Backreferences are not supported.
|
// Backreferences are not supported.
|
||||||
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
||||||
executeRegexp = regexp.MustCompile(
|
executeRegexp = regexp.MustCompile(
|
||||||
`(?si):(execute(?:-multi|-silent)?):.+|:(execute(?:-multi|-silent)?)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
`(?si):(execute(?:-multi|-silent)?|reload):.+|:(execute(?:-multi|-silent)?|reload)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseKeymap(keymap map[int][]action, str string) {
|
func parseKeymap(keymap map[int][]action, str string) {
|
||||||
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
||||||
prefix := ":execute"
|
prefix := ":execute"
|
||||||
if src[len(prefix)] == '-' {
|
if strings.HasPrefix(src, ":reload") {
|
||||||
|
prefix = ":reload"
|
||||||
|
} else if src[len(prefix)] == '-' {
|
||||||
c := src[len(prefix)+1]
|
c := src[len(prefix)+1]
|
||||||
if c == 's' || c == 'S' {
|
if c == 's' || c == 'S' {
|
||||||
prefix += "-silent"
|
prefix += "-silent"
|
||||||
@ -787,6 +809,8 @@ func parseKeymap(keymap map[int][]action, str string) {
|
|||||||
} else {
|
} else {
|
||||||
var offset int
|
var offset int
|
||||||
switch t {
|
switch t {
|
||||||
|
case actReload:
|
||||||
|
offset = len("reload")
|
||||||
case actExecuteSilent:
|
case actExecuteSilent:
|
||||||
offset = len("execute-silent")
|
offset = len("execute-silent")
|
||||||
case actExecuteMulti:
|
case actExecuteMulti:
|
||||||
@ -822,6 +846,8 @@ func isExecuteAction(str string) actionType {
|
|||||||
prefix = matches[0][2]
|
prefix = matches[0][2]
|
||||||
}
|
}
|
||||||
switch prefix {
|
switch prefix {
|
||||||
|
case "reload":
|
||||||
|
return actReload
|
||||||
case "execute":
|
case "execute":
|
||||||
return actExecute
|
return actExecute
|
||||||
case "execute-silent":
|
case "execute-silent":
|
||||||
@ -887,6 +913,20 @@ func parseLayout(str string) layoutType {
|
|||||||
return layoutDefault
|
return layoutDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseInfoStyle(str string) infoStyle {
|
||||||
|
switch str {
|
||||||
|
case "default":
|
||||||
|
return infoDefault
|
||||||
|
case "inline":
|
||||||
|
return infoInline
|
||||||
|
case "hidden":
|
||||||
|
return infoHidden
|
||||||
|
default:
|
||||||
|
errorExit("invalid info style (expected: default / inline / hidden)")
|
||||||
|
}
|
||||||
|
return infoDefault
|
||||||
|
}
|
||||||
|
|
||||||
func parsePreviewWindow(opts *previewOpts, input string) {
|
func parsePreviewWindow(opts *previewOpts, input string) {
|
||||||
// Default
|
// Default
|
||||||
opts.position = posRight
|
opts.position = posRight
|
||||||
@ -898,6 +938,7 @@ func parsePreviewWindow(opts *previewOpts, input string) {
|
|||||||
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
|
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
|
||||||
for _, token := range tokens {
|
for _, token := range tokens {
|
||||||
switch token {
|
switch token {
|
||||||
|
case "":
|
||||||
case "hidden":
|
case "hidden":
|
||||||
opts.hidden = true
|
opts.hidden = true
|
||||||
case "wrap":
|
case "wrap":
|
||||||
@ -910,6 +951,10 @@ func parsePreviewWindow(opts *previewOpts, input string) {
|
|||||||
opts.position = posLeft
|
opts.position = posLeft
|
||||||
case "right":
|
case "right":
|
||||||
opts.position = posRight
|
opts.position = posRight
|
||||||
|
case "border":
|
||||||
|
opts.border = true
|
||||||
|
case "noborder":
|
||||||
|
opts.border = false
|
||||||
default:
|
default:
|
||||||
if sizeRegex.MatchString(token) {
|
if sizeRegex.MatchString(token) {
|
||||||
opts.size = parseSize(token, 99, "window size")
|
opts.size = parseSize(token, 99, "window size")
|
||||||
@ -1014,6 +1059,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
}
|
}
|
||||||
case "--no-expect":
|
case "--no-expect":
|
||||||
opts.Expect = make(map[int]string)
|
opts.Expect = make(map[int]string)
|
||||||
|
case "--no-phony":
|
||||||
|
opts.Phony = false
|
||||||
|
case "--phony":
|
||||||
|
opts.Phony = true
|
||||||
case "--tiebreak":
|
case "--tiebreak":
|
||||||
opts.Criteria = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
|
opts.Criteria = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
|
||||||
case "--bind":
|
case "--bind":
|
||||||
@ -1088,10 +1137,15 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.FileWord = true
|
opts.FileWord = true
|
||||||
case "--no-filepath-word":
|
case "--no-filepath-word":
|
||||||
opts.FileWord = false
|
opts.FileWord = false
|
||||||
|
case "--info":
|
||||||
|
opts.InfoStyle = parseInfoStyle(
|
||||||
|
nextString(allArgs, &i, "info style required"))
|
||||||
|
case "--no-info":
|
||||||
|
opts.InfoStyle = infoHidden
|
||||||
case "--inline-info":
|
case "--inline-info":
|
||||||
opts.InlineInfo = true
|
opts.InfoStyle = infoInline
|
||||||
case "--no-inline-info":
|
case "--no-inline-info":
|
||||||
opts.InlineInfo = false
|
opts.InfoStyle = infoDefault
|
||||||
case "--jump-labels":
|
case "--jump-labels":
|
||||||
opts.JumpLabels = nextString(allArgs, &i, "label characters required")
|
opts.JumpLabels = nextString(allArgs, &i, "label characters required")
|
||||||
validateJumpLabels = true
|
validateJumpLabels = true
|
||||||
@ -1146,7 +1200,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Preview.command = ""
|
opts.Preview.command = ""
|
||||||
case "--preview-window":
|
case "--preview-window":
|
||||||
parsePreviewWindow(&opts.Preview,
|
parsePreviewWindow(&opts.Preview,
|
||||||
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]][:wrap][:hidden]"))
|
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]][:noborder][:wrap][:hidden]"))
|
||||||
case "--height":
|
case "--height":
|
||||||
opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]"))
|
opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]"))
|
||||||
case "--min-height":
|
case "--min-height":
|
||||||
@ -1199,6 +1253,8 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.MinHeight = atoi(value)
|
opts.MinHeight = atoi(value)
|
||||||
} else if match, value := optString(arg, "--layout="); match {
|
} else if match, value := optString(arg, "--layout="); match {
|
||||||
opts.Layout = parseLayout(value)
|
opts.Layout = parseLayout(value)
|
||||||
|
} else if match, value := optString(arg, "--info="); match {
|
||||||
|
opts.InfoStyle = parseInfoStyle(value)
|
||||||
} else if match, value := optString(arg, "--toggle-sort="); match {
|
} else if match, value := optString(arg, "--toggle-sort="); match {
|
||||||
parseToggleSort(opts.Keymap, value)
|
parseToggleSort(opts.Keymap, value)
|
||||||
} else if match, value := optString(arg, "--expect="); match {
|
} else if match, value := optString(arg, "--expect="); match {
|
||||||
|
@ -4,6 +4,8 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -16,11 +18,17 @@ type Reader struct {
|
|||||||
eventBox *util.EventBox
|
eventBox *util.EventBox
|
||||||
delimNil bool
|
delimNil bool
|
||||||
event int32
|
event int32
|
||||||
|
finChan chan bool
|
||||||
|
mutex sync.Mutex
|
||||||
|
exec *exec.Cmd
|
||||||
|
command *string
|
||||||
|
killed bool
|
||||||
|
wait bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewReader returns new Reader object
|
// NewReader returns new Reader object
|
||||||
func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, delimNil bool) *Reader {
|
func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, delimNil bool, wait bool) *Reader {
|
||||||
return &Reader{pusher, eventBox, delimNil, int32(EvtReady)}
|
return &Reader{pusher, eventBox, delimNil, int32(EvtReady), make(chan bool, 1), sync.Mutex{}, nil, nil, false, wait}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) startEventPoller() {
|
func (r *Reader) startEventPoller() {
|
||||||
@ -29,9 +37,12 @@ func (r *Reader) startEventPoller() {
|
|||||||
pollInterval := readerPollIntervalMin
|
pollInterval := readerPollIntervalMin
|
||||||
for {
|
for {
|
||||||
if atomic.CompareAndSwapInt32(ptr, int32(EvtReadNew), int32(EvtReady)) {
|
if atomic.CompareAndSwapInt32(ptr, int32(EvtReadNew), int32(EvtReady)) {
|
||||||
r.eventBox.Set(EvtReadNew, true)
|
r.eventBox.Set(EvtReadNew, (*string)(nil))
|
||||||
pollInterval = readerPollIntervalMin
|
pollInterval = readerPollIntervalMin
|
||||||
} else if atomic.LoadInt32(ptr) == int32(EvtReadFin) {
|
} else if atomic.LoadInt32(ptr) == int32(EvtReadFin) {
|
||||||
|
if r.wait {
|
||||||
|
r.finChan <- true
|
||||||
|
}
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
pollInterval += readerPollIntervalStep
|
pollInterval += readerPollIntervalStep
|
||||||
@ -46,7 +57,37 @@ func (r *Reader) startEventPoller() {
|
|||||||
|
|
||||||
func (r *Reader) fin(success bool) {
|
func (r *Reader) fin(success bool) {
|
||||||
atomic.StoreInt32(&r.event, int32(EvtReadFin))
|
atomic.StoreInt32(&r.event, int32(EvtReadFin))
|
||||||
r.eventBox.Set(EvtReadFin, success)
|
if r.wait {
|
||||||
|
<-r.finChan
|
||||||
|
}
|
||||||
|
|
||||||
|
r.mutex.Lock()
|
||||||
|
ret := r.command
|
||||||
|
if success || r.killed {
|
||||||
|
ret = nil
|
||||||
|
}
|
||||||
|
r.mutex.Unlock()
|
||||||
|
|
||||||
|
r.eventBox.Set(EvtReadFin, ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) terminate() {
|
||||||
|
r.mutex.Lock()
|
||||||
|
defer func() { r.mutex.Unlock() }()
|
||||||
|
|
||||||
|
r.killed = true
|
||||||
|
if r.exec != nil && r.exec.Process != nil {
|
||||||
|
util.KillCommand(r.exec)
|
||||||
|
} else {
|
||||||
|
os.Stdin.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) restart(command string) {
|
||||||
|
r.event = int32(EvtReady)
|
||||||
|
r.startEventPoller()
|
||||||
|
success := r.readFromCommand(nil, command)
|
||||||
|
r.fin(success)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadSource reads data from the default command or from standard input
|
// ReadSource reads data from the default command or from standard input
|
||||||
@ -54,12 +95,13 @@ func (r *Reader) ReadSource() {
|
|||||||
r.startEventPoller()
|
r.startEventPoller()
|
||||||
var success bool
|
var success bool
|
||||||
if util.IsTty() {
|
if util.IsTty() {
|
||||||
|
// The default command for *nix requires bash
|
||||||
|
shell := "bash"
|
||||||
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
||||||
if len(cmd) == 0 {
|
if len(cmd) == 0 {
|
||||||
// The default command for *nix requires bash
|
success = r.readFromCommand(&shell, defaultCommand)
|
||||||
success = r.readFromCommand("bash", defaultCommand)
|
|
||||||
} else {
|
} else {
|
||||||
success = r.readFromCommand("sh", cmd)
|
success = r.readFromCommand(nil, cmd)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
success = r.readFromStdin()
|
success = r.readFromStdin()
|
||||||
@ -102,16 +144,25 @@ func (r *Reader) readFromStdin() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readFromCommand(shell string, cmd string) bool {
|
func (r *Reader) readFromCommand(shell *string, command string) bool {
|
||||||
listCommand := util.ExecCommandWith(shell, cmd, false)
|
r.mutex.Lock()
|
||||||
out, err := listCommand.StdoutPipe()
|
r.killed = false
|
||||||
|
r.command = &command
|
||||||
|
if shell != nil {
|
||||||
|
r.exec = util.ExecCommandWith(*shell, command, true)
|
||||||
|
} else {
|
||||||
|
r.exec = util.ExecCommand(command, true)
|
||||||
|
}
|
||||||
|
out, err := r.exec.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
r.mutex.Unlock()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
err = listCommand.Start()
|
err = r.exec.Start()
|
||||||
|
r.mutex.Unlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
r.feed(out)
|
r.feed(out)
|
||||||
return listCommand.Wait() == nil
|
return r.exec.Wait() == nil
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,9 @@ import (
|
|||||||
func TestReadFromCommand(t *testing.T) {
|
func TestReadFromCommand(t *testing.T) {
|
||||||
strs := []string{}
|
strs := []string{}
|
||||||
eb := util.NewEventBox()
|
eb := util.NewEventBox()
|
||||||
reader := Reader{
|
reader := NewReader(
|
||||||
pusher: func(s []byte) bool { strs = append(strs, string(s)); return true },
|
func(s []byte) bool { strs = append(strs, string(s)); return true },
|
||||||
eventBox: eb,
|
eb, false, true)
|
||||||
event: int32(EvtReady)}
|
|
||||||
|
|
||||||
reader.startEventPoller()
|
reader.startEventPoller()
|
||||||
|
|
||||||
@ -23,7 +22,7 @@ func TestReadFromCommand(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Normal command
|
// Normal command
|
||||||
reader.fin(reader.readFromCommand("sh", `echo abc && echo def`))
|
reader.fin(reader.readFromCommand(nil, `echo abc && echo def`))
|
||||||
if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" {
|
if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" {
|
||||||
t.Errorf("%s", strs)
|
t.Errorf("%s", strs)
|
||||||
}
|
}
|
||||||
@ -48,7 +47,7 @@ func TestReadFromCommand(t *testing.T) {
|
|||||||
reader.startEventPoller()
|
reader.startEventPoller()
|
||||||
|
|
||||||
// Failing command
|
// Failing command
|
||||||
reader.fin(reader.readFromCommand("sh", `no-such-command`))
|
reader.fin(reader.readFromCommand(nil, `no-such-command`))
|
||||||
strs = []string{}
|
strs = []string{}
|
||||||
if len(strs) > 0 {
|
if len(strs) > 0 {
|
||||||
t.Errorf("%s", strs)
|
t.Errorf("%s", strs)
|
||||||
|
134
src/terminal.go
134
src/terminal.go
@ -60,7 +60,7 @@ var emptyLine = itemLine{}
|
|||||||
// Terminal represents terminal input/output
|
// Terminal represents terminal input/output
|
||||||
type Terminal struct {
|
type Terminal struct {
|
||||||
initDelay time.Duration
|
initDelay time.Duration
|
||||||
inlineInfo bool
|
infoStyle infoStyle
|
||||||
prompt string
|
prompt string
|
||||||
promptLen int
|
promptLen int
|
||||||
queryLen [2]int
|
queryLen [2]int
|
||||||
@ -102,7 +102,7 @@ type Terminal struct {
|
|||||||
count int
|
count int
|
||||||
progress int
|
progress int
|
||||||
reading bool
|
reading bool
|
||||||
success bool
|
failed *string
|
||||||
jumping jumpMode
|
jumping jumpMode
|
||||||
jumpLabels string
|
jumpLabels string
|
||||||
printer func(string)
|
printer func(string)
|
||||||
@ -228,6 +228,7 @@ const (
|
|||||||
actExecuteMulti // Deprecated
|
actExecuteMulti // Deprecated
|
||||||
actSigStop
|
actSigStop
|
||||||
actTop
|
actTop
|
||||||
|
actReload
|
||||||
)
|
)
|
||||||
|
|
||||||
type placeholderFlags struct {
|
type placeholderFlags struct {
|
||||||
@ -238,6 +239,11 @@ type placeholderFlags struct {
|
|||||||
file bool
|
file bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type searchRequest struct {
|
||||||
|
sort bool
|
||||||
|
command *string
|
||||||
|
}
|
||||||
|
|
||||||
func toActions(types ...actionType) []action {
|
func toActions(types ...actionType) []action {
|
||||||
actions := make([]action, len(types))
|
actions := make([]action, len(types))
|
||||||
for idx, t := range types {
|
for idx, t := range types {
|
||||||
@ -355,7 +361,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
if previewBox != nil && (opts.Preview.position == posUp || opts.Preview.position == posDown) {
|
if previewBox != nil && (opts.Preview.position == posUp || opts.Preview.position == posDown) {
|
||||||
effectiveMinHeight *= 2
|
effectiveMinHeight *= 2
|
||||||
}
|
}
|
||||||
if opts.InlineInfo {
|
if opts.InfoStyle != infoDefault {
|
||||||
effectiveMinHeight -= 1
|
effectiveMinHeight -= 1
|
||||||
}
|
}
|
||||||
if opts.Bordered {
|
if opts.Bordered {
|
||||||
@ -374,7 +380,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
}
|
}
|
||||||
t := Terminal{
|
t := Terminal{
|
||||||
initDelay: delay,
|
initDelay: delay,
|
||||||
inlineInfo: opts.InlineInfo,
|
infoStyle: opts.InfoStyle,
|
||||||
queryLen: [2]int{0, 0},
|
queryLen: [2]int{0, 0},
|
||||||
layout: opts.Layout,
|
layout: opts.Layout,
|
||||||
fullscreen: fullscreen,
|
fullscreen: fullscreen,
|
||||||
@ -408,7 +414,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
ansi: opts.Ansi,
|
ansi: opts.Ansi,
|
||||||
tabstop: opts.Tabstop,
|
tabstop: opts.Tabstop,
|
||||||
reading: true,
|
reading: true,
|
||||||
success: true,
|
failed: nil,
|
||||||
jumping: jumpDisabled,
|
jumping: jumpDisabled,
|
||||||
jumpLabels: opts.JumpLabels,
|
jumpLabels: opts.JumpLabels,
|
||||||
printer: opts.Printer,
|
printer: opts.Printer,
|
||||||
@ -432,6 +438,10 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
return &t
|
return &t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) noInfoLine() bool {
|
||||||
|
return t.infoStyle != infoDefault
|
||||||
|
}
|
||||||
|
|
||||||
// Input returns current query string
|
// Input returns current query string
|
||||||
func (t *Terminal) Input() []rune {
|
func (t *Terminal) Input() []rune {
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
@ -440,11 +450,11 @@ func (t *Terminal) Input() []rune {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateCount updates the count information
|
// UpdateCount updates the count information
|
||||||
func (t *Terminal) UpdateCount(cnt int, final bool, success bool) {
|
func (t *Terminal) UpdateCount(cnt int, final bool, failedCommand *string) {
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
t.count = cnt
|
t.count = cnt
|
||||||
t.reading = !final
|
t.reading = !final
|
||||||
t.success = success
|
t.failed = failedCommand
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
t.reqBox.Set(reqInfo, nil)
|
t.reqBox.Set(reqInfo, nil)
|
||||||
if final {
|
if final {
|
||||||
@ -614,7 +624,11 @@ func (t *Terminal) resizeWindows() {
|
|||||||
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
|
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
|
||||||
if previewVisible {
|
if previewVisible {
|
||||||
createPreviewWindow := func(y int, x int, w int, h int) {
|
createPreviewWindow := func(y int, x int, w int, h int) {
|
||||||
t.pborder = t.tui.NewWindow(y, x, w, h, tui.MakeBorderStyle(tui.BorderAround, t.unicode))
|
previewBorder := tui.MakeBorderStyle(tui.BorderAround, t.unicode)
|
||||||
|
if !t.preview.border {
|
||||||
|
previewBorder = tui.MakeTransparentBorder()
|
||||||
|
}
|
||||||
|
t.pborder = t.tui.NewWindow(y, x, w, h, previewBorder)
|
||||||
pwidth := w - 4
|
pwidth := w - 4
|
||||||
// ncurses auto-wraps the line when the cursor reaches the right-end of
|
// ncurses auto-wraps the line when the cursor reaches the right-end of
|
||||||
// the window. To prevent unintended line-wraps, we use the width one
|
// the window. To prevent unintended line-wraps, we use the width one
|
||||||
@ -666,7 +680,7 @@ func (t *Terminal) move(y int, x int, clear bool) {
|
|||||||
y = h - y - 1
|
y = h - y - 1
|
||||||
case layoutReverseList:
|
case layoutReverseList:
|
||||||
n := 2 + len(t.header)
|
n := 2 + len(t.header)
|
||||||
if t.inlineInfo {
|
if t.noInfoLine() {
|
||||||
n--
|
n--
|
||||||
}
|
}
|
||||||
if y < n {
|
if y < n {
|
||||||
@ -719,7 +733,17 @@ func (t *Terminal) printPrompt() {
|
|||||||
|
|
||||||
func (t *Terminal) printInfo() {
|
func (t *Terminal) printInfo() {
|
||||||
pos := 0
|
pos := 0
|
||||||
if t.inlineInfo {
|
switch t.infoStyle {
|
||||||
|
case infoDefault:
|
||||||
|
t.move(1, 0, true)
|
||||||
|
if t.reading {
|
||||||
|
duration := int64(spinnerDuration)
|
||||||
|
idx := (time.Now().UnixNano() % (duration * int64(len(_spinner)))) / duration
|
||||||
|
t.window.CPrint(tui.ColSpinner, t.strong, _spinner[idx])
|
||||||
|
}
|
||||||
|
t.move(1, 2, false)
|
||||||
|
pos = 2
|
||||||
|
case infoInline:
|
||||||
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
||||||
if pos+len(" < ") > t.window.Width() {
|
if pos+len(" < ") > t.window.Width() {
|
||||||
return
|
return
|
||||||
@ -731,18 +755,13 @@ func (t *Terminal) printInfo() {
|
|||||||
t.window.CPrint(tui.ColPrompt, t.strong, " < ")
|
t.window.CPrint(tui.ColPrompt, t.strong, " < ")
|
||||||
}
|
}
|
||||||
pos += len(" < ")
|
pos += len(" < ")
|
||||||
} else {
|
case infoHidden:
|
||||||
t.move(1, 0, true)
|
return
|
||||||
if t.reading {
|
|
||||||
duration := int64(spinnerDuration)
|
|
||||||
idx := (time.Now().UnixNano() % (duration * int64(len(_spinner)))) / duration
|
|
||||||
t.window.CPrint(tui.ColSpinner, t.strong, _spinner[idx])
|
|
||||||
}
|
|
||||||
t.move(1, 2, false)
|
|
||||||
pos = 2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
output := fmt.Sprintf("%d/%d", t.merger.Length(), t.count)
|
found := t.merger.Length()
|
||||||
|
total := util.Max(found, t.count)
|
||||||
|
output := fmt.Sprintf("%d/%d", found, total)
|
||||||
if t.toggleSort {
|
if t.toggleSort {
|
||||||
if t.sort {
|
if t.sort {
|
||||||
output += " +S"
|
output += " +S"
|
||||||
@ -760,16 +779,15 @@ func (t *Terminal) printInfo() {
|
|||||||
if t.progress > 0 && t.progress < 100 {
|
if t.progress > 0 && t.progress < 100 {
|
||||||
output += fmt.Sprintf(" (%d%%)", t.progress)
|
output += fmt.Sprintf(" (%d%%)", t.progress)
|
||||||
}
|
}
|
||||||
if !t.success && t.count == 0 {
|
if t.failed != nil && t.count == 0 {
|
||||||
if len(os.Getenv("FZF_DEFAULT_COMMAND")) > 0 {
|
output = fmt.Sprintf("[Command failed: %s]", *t.failed)
|
||||||
output = "[$FZF_DEFAULT_COMMAND failed]"
|
|
||||||
} else {
|
|
||||||
output = "[default command failed - $FZF_DEFAULT_COMMAND required]"
|
|
||||||
}
|
}
|
||||||
|
maxWidth := t.window.Width() - pos
|
||||||
|
if len(output) > maxWidth {
|
||||||
|
outputRunes, _ := t.trimRight([]rune(output), maxWidth-2)
|
||||||
|
output = string(outputRunes) + ".."
|
||||||
}
|
}
|
||||||
if pos+len(output) <= t.window.Width() {
|
|
||||||
t.window.CPrint(tui.ColInfo, 0, output)
|
t.window.CPrint(tui.ColInfo, 0, output)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) printHeader() {
|
func (t *Terminal) printHeader() {
|
||||||
@ -780,7 +798,7 @@ func (t *Terminal) printHeader() {
|
|||||||
var state *ansiState
|
var state *ansiState
|
||||||
for idx, lineStr := range t.header {
|
for idx, lineStr := range t.header {
|
||||||
line := idx + 2
|
line := idx + 2
|
||||||
if t.inlineInfo {
|
if t.noInfoLine() {
|
||||||
line--
|
line--
|
||||||
}
|
}
|
||||||
if line >= max {
|
if line >= max {
|
||||||
@ -809,7 +827,7 @@ func (t *Terminal) printList() {
|
|||||||
i = maxy - 1 - j
|
i = maxy - 1 - j
|
||||||
}
|
}
|
||||||
line := i + 2 + len(t.header)
|
line := i + 2 + len(t.header)
|
||||||
if t.inlineInfo {
|
if t.noInfoLine() {
|
||||||
line--
|
line--
|
||||||
}
|
}
|
||||||
if i < count {
|
if i < count {
|
||||||
@ -1383,7 +1401,7 @@ func (t *Terminal) hasPreviewWindow() bool {
|
|||||||
|
|
||||||
func (t *Terminal) currentItem() *Item {
|
func (t *Terminal) currentItem() *Item {
|
||||||
cnt := t.merger.Length()
|
cnt := t.merger.Length()
|
||||||
if cnt > 0 && cnt > t.cy {
|
if t.cy >= 0 && cnt > 0 && cnt > t.cy {
|
||||||
return t.merger.Get(t.cy).item
|
return t.merger.Get(t.cy).item
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -1422,7 +1440,7 @@ func (t *Terminal) selectItem(item *Item) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if _, found := t.selected[item.Index()]; found {
|
if _, found := t.selected[item.Index()]; found {
|
||||||
return false
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
t.selected[item.Index()] = selectedItem{time.Now(), item}
|
t.selected[item.Index()] = selectedItem{time.Now(), item}
|
||||||
@ -1508,12 +1526,11 @@ func (t *Terminal) Loop() {
|
|||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
reading := t.reading
|
reading := t.reading
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
if !reading {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
time.Sleep(spinnerDuration)
|
time.Sleep(spinnerDuration)
|
||||||
|
if reading {
|
||||||
t.reqBox.Set(reqInfo, nil)
|
t.reqBox.Set(reqInfo, nil)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1533,7 +1550,7 @@ func (t *Terminal) Loop() {
|
|||||||
// We don't display preview window if no match
|
// We don't display preview window if no match
|
||||||
if request[0] != nil {
|
if request[0] != nil {
|
||||||
command := replacePlaceholder(t.preview.command,
|
command := replacePlaceholder(t.preview.command,
|
||||||
t.ansi, t.delimiter, t.printsep, false, string(t.input), request)
|
t.ansi, t.delimiter, t.printsep, false, string(t.Input()), request)
|
||||||
cmd := util.ExecCommand(command, true)
|
cmd := util.ExecCommand(command, true)
|
||||||
if t.pwindow != nil {
|
if t.pwindow != nil {
|
||||||
env := os.Environ()
|
env := os.Environ()
|
||||||
@ -1584,9 +1601,6 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
exit := func(getCode func() int) {
|
exit := func(getCode func() int) {
|
||||||
if !t.cleanExit && t.fullscreen && t.inlineInfo {
|
|
||||||
t.placeCursor()
|
|
||||||
}
|
|
||||||
t.tui.Close()
|
t.tui.Close()
|
||||||
code := getCode()
|
code := getCode()
|
||||||
if code <= exitNoMatch && t.history != nil {
|
if code <= exitNoMatch && t.history != nil {
|
||||||
@ -1607,7 +1621,7 @@ func (t *Terminal) Loop() {
|
|||||||
switch req {
|
switch req {
|
||||||
case reqPrompt:
|
case reqPrompt:
|
||||||
t.printPrompt()
|
t.printPrompt()
|
||||||
if t.inlineInfo {
|
if t.noInfoLine() {
|
||||||
t.printInfo()
|
t.printInfo()
|
||||||
}
|
}
|
||||||
case reqInfo:
|
case reqInfo:
|
||||||
@ -1673,6 +1687,10 @@ func (t *Terminal) Loop() {
|
|||||||
|
|
||||||
looping := true
|
looping := true
|
||||||
for looping {
|
for looping {
|
||||||
|
var newCommand *string
|
||||||
|
changed := false
|
||||||
|
queryChanged := false
|
||||||
|
|
||||||
event := t.tui.GetChar()
|
event := t.tui.GetChar()
|
||||||
|
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
@ -1754,9 +1772,7 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
case actToggleSort:
|
case actToggleSort:
|
||||||
t.sort = !t.sort
|
t.sort = !t.sort
|
||||||
t.eventBox.Set(EvtSearchNew, t.sort)
|
changed = true
|
||||||
t.mutex.Unlock()
|
|
||||||
return false
|
|
||||||
case actPreviewUp:
|
case actPreviewUp:
|
||||||
if t.hasPreviewWindow() {
|
if t.hasPreviewWindow() {
|
||||||
scrollPreview(-1)
|
scrollPreview(-1)
|
||||||
@ -1987,7 +2003,7 @@ func (t *Terminal) Loop() {
|
|||||||
my -= t.window.Top()
|
my -= t.window.Top()
|
||||||
mx = util.Constrain(mx-t.promptLen, 0, len(t.input))
|
mx = util.Constrain(mx-t.promptLen, 0, len(t.input))
|
||||||
min := 2 + len(t.header)
|
min := 2 + len(t.header)
|
||||||
if t.inlineInfo {
|
if t.noInfoLine() {
|
||||||
min--
|
min--
|
||||||
}
|
}
|
||||||
h := t.window.Height()
|
h := t.window.Height()
|
||||||
@ -2025,10 +2041,25 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case actReload:
|
||||||
|
t.failed = nil
|
||||||
|
|
||||||
|
valid, list := t.buildPlusList(a.a, false)
|
||||||
|
// If the command template has {q}, we run the command even when the
|
||||||
|
// query string is empty.
|
||||||
|
if !valid {
|
||||||
|
_, query := hasPreviewFlags(a.a)
|
||||||
|
valid = query
|
||||||
|
}
|
||||||
|
if valid {
|
||||||
|
command := replacePlaceholder(a.a,
|
||||||
|
t.ansi, t.delimiter, t.printsep, false, string(t.input), list)
|
||||||
|
newCommand = &command
|
||||||
|
t.selected = make(map[int32]selectedItem)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
changed := false
|
|
||||||
mapkey := event.Type
|
mapkey := event.Type
|
||||||
if t.jumping == jumpDisabled {
|
if t.jumping == jumpDisabled {
|
||||||
actions := t.keymap[mapkey]
|
actions := t.keymap[mapkey]
|
||||||
@ -2042,8 +2073,9 @@ func (t *Terminal) Loop() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
t.truncateQuery()
|
t.truncateQuery()
|
||||||
changed = string(previousInput) != string(t.input)
|
queryChanged = string(previousInput) != string(t.input)
|
||||||
if onChanges, prs := t.keymap[tui.Change]; changed && prs {
|
changed = changed || queryChanged
|
||||||
|
if onChanges, prs := t.keymap[tui.Change]; queryChanged && prs {
|
||||||
if !doActions(onChanges, tui.Change) {
|
if !doActions(onChanges, tui.Change) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -2061,7 +2093,7 @@ func (t *Terminal) Loop() {
|
|||||||
req(reqList)
|
req(reqList)
|
||||||
}
|
}
|
||||||
|
|
||||||
if changed {
|
if queryChanged {
|
||||||
if t.isPreviewEnabled() {
|
if t.isPreviewEnabled() {
|
||||||
_, q := hasPreviewFlags(t.preview.command)
|
_, q := hasPreviewFlags(t.preview.command)
|
||||||
if q {
|
if q {
|
||||||
@ -2070,14 +2102,14 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if changed || t.cx != previousCx {
|
if queryChanged || t.cx != previousCx {
|
||||||
req(reqPrompt)
|
req(reqPrompt)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.mutex.Unlock() // Must be unlocked before touching reqBox
|
t.mutex.Unlock() // Must be unlocked before touching reqBox
|
||||||
|
|
||||||
if changed {
|
if changed || newCommand != nil {
|
||||||
t.eventBox.Set(EvtSearchNew, t.sort)
|
t.eventBox.Set(EvtSearchNew, searchRequest{sort: t.sort, command: newCommand})
|
||||||
}
|
}
|
||||||
for _, event := range events {
|
for _, event := range events {
|
||||||
t.reqBox.Set(event, nil)
|
t.reqBox.Set(event, nil)
|
||||||
@ -2127,7 +2159,7 @@ func (t *Terminal) vset(o int) bool {
|
|||||||
|
|
||||||
func (t *Terminal) maxItems() int {
|
func (t *Terminal) maxItems() int {
|
||||||
max := t.window.Height() - 2 - len(t.header)
|
max := t.window.Height() - 2 - len(t.header)
|
||||||
if t.inlineInfo {
|
if t.noInfoLine() {
|
||||||
max++
|
max++
|
||||||
}
|
}
|
||||||
return util.Max(max, 0)
|
return util.Max(max, 0)
|
||||||
|
@ -345,6 +345,14 @@ func (r *LightRenderer) GetChar() Event {
|
|||||||
return Event{BSpace, 0, nil}
|
return Event{BSpace, 0, nil}
|
||||||
case 0:
|
case 0:
|
||||||
return Event{CtrlSpace, 0, nil}
|
return Event{CtrlSpace, 0, nil}
|
||||||
|
case 28:
|
||||||
|
return Event{CtrlBackSlash, 0, nil}
|
||||||
|
case 29:
|
||||||
|
return Event{CtrlRightBracket, 0, nil}
|
||||||
|
case 30:
|
||||||
|
return Event{CtrlCaret, 0, nil}
|
||||||
|
case 31:
|
||||||
|
return Event{CtrlSlash, 0, nil}
|
||||||
case ESC:
|
case ESC:
|
||||||
ev := r.escSequence(&sz)
|
ev := r.escSequence(&sz)
|
||||||
// Second chance
|
// Second chance
|
||||||
|
@ -284,6 +284,12 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
return Event{keyfn('z'), 0, nil}
|
return Event{keyfn('z'), 0, nil}
|
||||||
case tcell.KeyCtrlSpace:
|
case tcell.KeyCtrlSpace:
|
||||||
return Event{CtrlSpace, 0, nil}
|
return Event{CtrlSpace, 0, nil}
|
||||||
|
case tcell.KeyCtrlBackslash:
|
||||||
|
return Event{CtrlBackSlash, 0, nil}
|
||||||
|
case tcell.KeyCtrlRightSq:
|
||||||
|
return Event{CtrlRightBracket, 0, nil}
|
||||||
|
case tcell.KeyCtrlUnderscore:
|
||||||
|
return Event{CtrlSlash, 0, nil}
|
||||||
case tcell.KeyBackspace2:
|
case tcell.KeyBackspace2:
|
||||||
if alt {
|
if alt {
|
||||||
return Event{AltBS, 0, nil}
|
return Event{AltBS, 0, nil}
|
||||||
|
@ -40,6 +40,12 @@ const (
|
|||||||
ESC
|
ESC
|
||||||
CtrlSpace
|
CtrlSpace
|
||||||
|
|
||||||
|
// https://apple.stackexchange.com/questions/24261/how-do-i-send-c-that-is-control-slash-to-the-terminal
|
||||||
|
CtrlBackSlash
|
||||||
|
CtrlRightBracket
|
||||||
|
CtrlCaret
|
||||||
|
CtrlSlash
|
||||||
|
|
||||||
Invalid
|
Invalid
|
||||||
Resize
|
Resize
|
||||||
Mouse
|
Mouse
|
||||||
@ -215,6 +221,8 @@ type BorderStyle struct {
|
|||||||
bottomRight rune
|
bottomRight rune
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BorderCharacter int
|
||||||
|
|
||||||
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
|
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
|
||||||
if unicode {
|
if unicode {
|
||||||
return BorderStyle{
|
return BorderStyle{
|
||||||
@ -238,6 +246,17 @@ func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MakeTransparentBorder() BorderStyle {
|
||||||
|
return BorderStyle{
|
||||||
|
shape: BorderAround,
|
||||||
|
horizontal: ' ',
|
||||||
|
vertical: ' ',
|
||||||
|
topLeft: ' ',
|
||||||
|
topRight: ' ',
|
||||||
|
bottomLeft: ' ',
|
||||||
|
bottomRight: ' '}
|
||||||
|
}
|
||||||
|
|
||||||
type Renderer interface {
|
type Renderer interface {
|
||||||
Init()
|
Init()
|
||||||
Pause(clear bool)
|
Pause(clear bool)
|
||||||
|
@ -142,6 +142,11 @@ func (chars *Chars) TrailingWhitespaces() int {
|
|||||||
return whitespaces
|
return whitespaces
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) TrimTrailingWhitespaces() {
|
||||||
|
whitespaces := chars.TrailingWhitespaces()
|
||||||
|
chars.slice = chars.slice[0 : len(chars.slice)-whitespaces]
|
||||||
|
}
|
||||||
|
|
||||||
func (chars *Chars) ToString() string {
|
func (chars *Chars) ToString() string {
|
||||||
if runes := chars.optionalRunes(); runes != nil {
|
if runes := chars.optionalRunes(); runes != nil {
|
||||||
return string(runes)
|
return string(runes)
|
||||||
|
@ -277,7 +277,7 @@ class TestGoFZF < TestBase
|
|||||||
|
|
||||||
def test_fzf_default_command_failure
|
def test_fzf_default_command_failure
|
||||||
tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', 'FZF_DEFAULT_COMMAND=false'), :Enter
|
tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', 'FZF_DEFAULT_COMMAND=false'), :Enter
|
||||||
tmux.until { |lines| lines[-2].include?('FZF_DEFAULT_COMMAND failed') }
|
tmux.until { |lines| lines[-2].include?('Command failed: false') }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -1516,6 +1516,11 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| lines[-1] == prompt }
|
tmux.until { |lines| lines[-1] == prompt }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_info_hidden
|
||||||
|
tmux.send_keys 'seq 10 | fzf --info=hidden', :Enter
|
||||||
|
tmux.until { |lines| lines[-2] == '> 1' }
|
||||||
|
end
|
||||||
|
|
||||||
def test_change_top
|
def test_change_top
|
||||||
tmux.send_keys %(seq 1000 | #{FZF} --bind change:top), :Enter
|
tmux.send_keys %(seq 1000 | #{FZF} --bind change:top), :Enter
|
||||||
tmux.until { |lines| lines.match_count == 1000 }
|
tmux.until { |lines| lines.match_count == 1000 }
|
||||||
@ -1612,6 +1617,29 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys "#{FZF} --preview 'cat #{tempname}'", :Enter
|
tmux.send_keys "#{FZF} --preview 'cat #{tempname}'", :Enter
|
||||||
tmux.until { |lines| lines[1].include?('+ green') }
|
tmux.until { |lines| lines[1].include?('+ green') }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_phony
|
||||||
|
tmux.send_keys %(seq 1000 | #{FZF} --query 333 --phony --preview 'echo {} {q}'), :Enter
|
||||||
|
tmux.until { |lines| lines.match_count == 1000 }
|
||||||
|
tmux.until { |lines| lines[1].include?('1 333') }
|
||||||
|
tmux.send_keys 'foo'
|
||||||
|
tmux.until { |lines| lines.match_count == 1000 }
|
||||||
|
tmux.until { |lines| lines[1].include?('1 333foo') }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reload
|
||||||
|
tmux.send_keys %(seq 1000 | #{FZF} --bind 'change:reload(seq {q}),a:reload(seq 100),b:reload:seq 200' --header-lines 2 --multi 2), :Enter
|
||||||
|
tmux.until { |lines| lines.match_count == 998 }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| lines.item_count == 98 && lines.match_count == 98 }
|
||||||
|
tmux.send_keys 'b'
|
||||||
|
tmux.until { |lines| lines.item_count == 198 && lines.match_count == 198 }
|
||||||
|
tmux.send_keys :Tab
|
||||||
|
tmux.until { |lines| lines[-2].include?('(1/2)') }
|
||||||
|
tmux.send_keys '555'
|
||||||
|
tmux.until { |lines| lines.item_count == 553 && lines.match_count == 1 }
|
||||||
|
tmux.until { |lines| !lines[-2].include?('(1/2)') }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module TestShell
|
module TestShell
|
||||||
|
Loading…
Reference in New Issue
Block a user