m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-14 22:33:47 -05:00

Compare commits

..

48 Commits

Author SHA1 Message Date
Junegunn Choi
e15cba0c8c 0.61.0 2025-03-30 19:51:28 +09:00
Junegunn Choi
31fd207ba2 Add 'r' flag (raw) for unquoted output
By default, placeholder expressions are automatically quoted to ensure
they are safely passed as arguments to external programs.

The r flag ({r}, {r1}, etc.) disables this behavior, outputting the
evaluated value without quotes.

For example,

  echo 'foo   bar' | fzf --preview 'echo {} {r}'

The preview command becomes:

  echo 'foo   bar' foo   bar

Since `{r}` expands to unquoted "foo   bar", 'foo' and 'bar' are passed
as separate arguments.

**Use with caution** Unquoted output can lead to broken commands.

  echo "let's go" | fzf --preview 'echo {r}'

Close #4330
2025-03-30 19:49:05 +09:00
Junegunn Choi
ba6d1b8772 Add change-ghost and transform-ghost 2025-03-28 23:35:20 +09:00
Junegunn Choi
0dce561ec9 Fix header window not updated on change-header 2025-03-28 23:23:43 +09:00
dependabot[bot]
376142eb0d Bump github.com/charlievieth/fastwalk from 1.0.9 to 1.0.10 (#4307)
Bumps [github.com/charlievieth/fastwalk](https://github.com/charlievieth/fastwalk) from 1.0.9 to 1.0.10.
- [Release notes](https://github.com/charlievieth/fastwalk/releases)
- [Commits](https://github.com/charlievieth/fastwalk/compare/v1.0.9...v1.0.10)

---
updated-dependencies:
- dependency-name: github.com/charlievieth/fastwalk
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-28 23:05:34 +09:00
Junegunn Choi
664ee1f483 Add change-pointer and transform-pointer
Close #4178
2025-03-28 21:28:25 +09:00
Junegunn Choi
dac5b6fde1 Fix info not updated after track-current is disabled due to race condition 2025-03-26 16:00:05 +09:00
Junegunn Choi
998c57442b Fix query precedence in an action chain (#4326)
When 'search' and any action that modifies the query are in an action
chain, anything that comes later takes precedence.
2025-03-26 15:47:43 +09:00
Junegunn Choi
4a0ab6c926 Improve query modification prevention in input-less mode
fzf would restore the original query in input-less mode after executing
a chain of actions.

This commit changes the behavior so that the restoration
happens after each action to allow something like
'show-input+change-query(...)+hide-input'.

Fix #4326
2025-03-26 10:34:52 +09:00
Junegunn Choi
f43e82f17f Do not ignore current query when input is hidden
* The initial query given by --query should be respected
* The current query should still be respected after `hide-input`
  (or `toggle-input)

Fix #4327
2025-03-25 21:08:06 +09:00
Junegunn Choi
62238620a5 Fix first entry not clickable when input section is hidden
Fix #4325
2025-03-24 22:08:57 +09:00
Junegunn Choi
200745011a Fix cursor position when prompt is truncated
e.g.
    fzf --preview 'cat {}' --prompt "$(seq 100 | xargs)"
    fzf --preview 'cat {}' --prompt "$(seq 100 | xargs)" --input-border
2025-03-24 17:09:44 +09:00
Junegunn Choi
82fd88339b Fix offset-middle not updating the list 2025-03-23 11:13:21 +09:00
junegunn
de0f2efbfb Deploying to master from @ junegunn/fzf@29cf28d845 🚀 2025-03-23 00:02:20 +00:00
Junegunn Choi
29cf28d845 Suppress 'change' event during bracketed paste mode
Close #4316
2025-03-22 09:17:18 +09:00
Junegunn Choi
7e4dbb5f3b Prevent start:track-current from being disabled
# track-current state can be immediately disabled
  fzf --sync --bind 'start:track-current'
2025-03-20 11:51:20 +09:00
Junegunn Choi
923c3a814d [bash] Fix $FZF_COMPLETION_{DIR,PATH}_OPTS to support non-trivial arguments
This used to fail with 'unknown option: World>'

  export FZF_COMPLETION_PATH_OPTS="--prompt 'Hello World> '"
2025-03-17 18:12:26 +09:00
Junegunn Choi
779e3cc5b5 [vim] Use 24-bit colors on gvim even when &termguicolors is off
Close #2563
2025-03-17 17:46:56 +09:00
junegunn
3f3d1ef8f5 Deploying to master from @ junegunn/fzf@f92f9f137a 🚀 2025-03-16 00:02:19 +00:00
Junegunn Choi
f92f9f137a Fix wrapping of the list section
# The first line of the second chunk would prematurely wrap
  printf '%0500s\n\n%0500s' 0 0 | fzf --wrap --read0
2025-03-16 01:57:20 +09:00
Junegunn Choi
87f7f436e8 Fix ghost text with inline info
Fix #4312
2025-03-15 18:42:08 +09:00
Junegunn Choi
4298c0b1eb Add --ghost=TEXT to display a ghost text when the input is empty 2025-03-14 16:46:23 +09:00
Gabriel Marin
6c104d771e Change 'interface{}' to 'any' (#4308) 2025-03-11 14:24:54 +09:00
Junegunn Choi
aefb9a5bc4 Nullify unwanted FZF_DEFAULT_* variables in tmux popup
Fix #4298
2025-03-10 18:18:50 +09:00
Junegunn Choi
8868d7cbb8 Add .idea to .gitignore 2025-03-10 18:15:53 +09:00
junegunn
10cbac20f9 Deploying to master from @ junegunn/fzf@26bcd0c90d 🚀 2025-03-09 00:01:51 +00:00
Junegunn Choi
26bcd0c90d README: Sponsors ❤️ 2025-03-04 18:30:50 +09:00
Junegunn Choi
fbece2bb67 Update README 2025-03-04 17:43:02 +09:00
Junegunn Choi
0012183ede 0.60.3 2025-03-03 17:10:49 +09:00
Junegunn Choi
8916cbc6ab [windows] Prevent fzf from consuming user input while paused
This partly fixes #4260.

fzf still can consume the first key stroke.
2025-03-03 14:04:16 +09:00
junegunn
21ce70054f Deploying to master from @ junegunn/fzf@3ba82b6d87 🚀 2025-03-02 00:02:11 +00:00
Junegunn Choi
3ba82b6d87 Make truncateQuery faster
https://github.com/junegunn/fzf/issues/4292#issuecomment-2687051731
2025-02-27 15:49:15 +09:00
Junegunn Choi
e771c5d057 Update README 2025-02-27 14:01:13 +09:00
Junegunn Choi
4e5e925e39 Increase the query length limit from 300 to 1000
Close #4292
2025-02-27 11:43:58 +09:00
Junegunn Choi
b7248d4115 Remove temp files before 'become' when using --tmux option
Close #4283

But the temp files for the `f` flags in the 'become' template will not
be removed, because we will need them after "becoming" another program.

  e.g. fzf --bind 'enter:become:cat {f}'
2025-02-26 20:47:09 +09:00
Junegunn Choi
639253840f Trim trailing whitespaces after processing ANSI sequences
Close #4282
2025-02-26 16:17:12 +09:00
Junegunn Choi
710ebdf9c1 Make --accept-nth compatible with --select-1
Fix #4287
2025-02-26 00:25:23 +09:00
bitraid
bb64d84ce4 [fish] Enable multiple history commands insertion (#4280)
Enable inserting multiple history commands. To disable, set `--no-multi`
through `$FZF_CTRL_R_OPTS`.

Also, remove the usage of `become` action, to avoid leaving behind
temporary files.

Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2025-02-26 00:18:56 +09:00
alex-huff
cd1da27ff2 Fix condition for using item numlines cache (#4285) 2025-02-25 20:25:26 +09:00
Junegunn Choi
c1accc2e5b Use '/' as path separator on MSYS2
Fix #4281
2025-02-25 10:12:19 +09:00
Junegunn Choi
e4489dcbc1 Fix regression: Trim trailing whitespaces when using --with-nth
https://github.com/junegunn/fzf/issues/4272#issuecomment-2677279620
2025-02-24 18:40:13 +09:00
Junegunn Choi
c0d407f7ce 0.60.2 2025-02-23 19:52:57 +09:00
Junegunn Choi
461115afde Add support for {n} in --with-nth and --accept-nth templates
Close #4275
2025-02-23 19:47:56 +09:00
junegunn
bae1965231 Deploying to master from @ junegunn/fzf@b89c77ec9a 🚀 2025-02-23 00:02:08 +00:00
Junegunn Choi
b89c77ec9a Mention that actions after accept or abort are ignored (#4271) 2025-02-22 22:19:16 +09:00
Junegunn Choi
1ca5f09d7b Explain the difference of template from a single field index expression
Close #4272
2025-02-22 22:14:49 +09:00
Junegunn Choi
d79902ae59 Fix 'jump' when pointer is empty
Fix #4270
2025-02-22 19:05:30 +09:00
phanium
77568e114f Don't trim last field when delimiter is regex (#4266) 2025-02-21 22:21:55 +09:00
34 changed files with 713 additions and 303 deletions

1
.gitignore vendored
View File

@@ -11,3 +11,4 @@ gopath
fzf fzf
tmp tmp
*.patch *.patch
.idea

View File

@@ -1,6 +1,37 @@
CHANGELOG CHANGELOG
========= =========
0.61.0
------
- Added `--ghost=TEXT` to display a ghost text when the input is empty
```sh
# Display "Type to search" when the input is empty
fzf --ghost "Type to search"
```
- Added `change-ghost` and `transform-ghost` actions for dynamically changing the ghost text
- Added `change-pointer` and `transform-pointer` actions for dynamically changing the pointer sign
- Added `r` flag for placeholder expression (raw mode) for unquoted output
- Bug fixes and improvements
0.60.3
------
- Bug fixes and improvements
- [fish] Enable multiple history commands insertion (#4280) (@bitraid)
- [walker] Append '/' to directory entries on MSYS2 (#4281)
- Trim trailing whitespaces after processing ANSI sequences (#4282)
- Remove temp files before `become` when using `--tmux` option (#4283)
- Fix condition for using item numlines cache (#4285) (@alex-huff)
- Make `--accept-nth` compatible with `--select-1` (#4287)
- Increase the query length limit from 300 to 1000 (#4292)
- [windows] Prevent fzf from consuming user input while paused (#4260)
0.60.2
------
- Template for `--with-nth` and `--accept-nth` now supports `{n}` which evaluates to the zero-based ordinal index of the item
- Fixed a regression that caused the last field in the "nth" expression to be trimmed when a regular expression delimiter is used
- Thanks to @phanen for the fix
- Fixed 'jump' action when the pointer is an empty string
0.60.1 0.60.1
------ ------
- Bug fixes and minor improvements - Bug fixes and minor improvements

File diff suppressed because one or more lines are too long

2
go.mod
View File

@@ -1,7 +1,7 @@
module github.com/junegunn/fzf module github.com/junegunn/fzf
require ( require (
github.com/charlievieth/fastwalk v1.0.9 github.com/charlievieth/fastwalk v1.0.10
github.com/gdamore/tcell/v2 v2.8.1 github.com/gdamore/tcell/v2 v2.8.1
github.com/junegunn/go-shellwords v0.0.0-20250127100254-2aa3b3277741 github.com/junegunn/go-shellwords v0.0.0-20250127100254-2aa3b3277741
github.com/mattn/go-isatty v0.0.20 github.com/mattn/go-isatty v0.0.20

4
go.sum
View File

@@ -1,5 +1,5 @@
github.com/charlievieth/fastwalk v1.0.9 h1:Odb92AfoReO3oFBfDGT5J+nwgzQPF/gWAw6E6/lkor0= github.com/charlievieth/fastwalk v1.0.10 h1:0qUbvA2O+K+X+IrTfZTC0UH2DK5MOA+KjVfStAHUnGg=
github.com/charlievieth/fastwalk v1.0.9/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI= github.com/charlievieth/fastwalk v1.0.10/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI=
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw= github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
github.com/gdamore/tcell/v2 v2.8.1 h1:KPNxyqclpWpWQlPLx6Xui1pMk8S+7+R37h3g07997NU= github.com/gdamore/tcell/v2 v2.8.1 h1:KPNxyqclpWpWQlPLx6Xui1pMk8S+7+R37h3g07997NU=

View File

@@ -2,7 +2,7 @@
set -u set -u
version=0.60.1 version=0.61.0
auto_completion= auto_completion=
key_bindings= key_bindings=
update_config=2 update_config=2

View File

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

View File

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

View File

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf\-tmux 1 "Feb 2025" "fzf 0.60.1" "fzf\-tmux - open fzf in tmux split pane" .TH fzf\-tmux 1 "Mar 2025" "fzf 0.61.0" "fzf\-tmux - open fzf in tmux split pane"
.SH NAME .SH NAME
fzf\-tmux - open fzf in tmux split pane fzf\-tmux - open fzf in tmux split pane

View File

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf 1 "Feb 2025" "fzf 0.60.1" "fzf - a command-line fuzzy finder" .TH fzf 1 "Mar 2025" "fzf 0.61.0" "fzf - a command-line fuzzy finder"
.SH NAME .SH NAME
fzf - a command-line fuzzy finder fzf - a command-line fuzzy finder
@@ -122,7 +122,9 @@ fields.
.BI "\-\-with\-nth=" "N[,..] or TEMPLATE" .BI "\-\-with\-nth=" "N[,..] or TEMPLATE"
Transform the presentation of each line using the field index expressions. Transform the presentation of each line using the field index expressions.
For advanced transformation, you can provide a template containing field index For advanced transformation, you can provide a template containing field index
expressions in curly braces. expressions in curly braces. When you use a template, the trailing delimiter is
stripped from each expression, giving you more control over the output.
\fB{n}\fR in template evaluates to the zero-based ordinal index of the line.
.RS .RS
e.g. e.g.
@@ -130,13 +132,16 @@ e.g.
echo foo bar baz | fzf --with-nth 2.. echo foo bar baz | fzf --with-nth 2..
# Use template to rearrange fields # Use template to rearrange fields
echo foo,bar,baz | fzf --delimiter , --with-nth '{1},{3},{2},{1..2}' echo foo,bar,baz | fzf --delimiter , --with-nth '{n},{1},{3},{2},{1..2}'
.RE .RE
.TP .TP
.BI "\-\-accept\-nth=" "N[,..] or TEMPLATE" .BI "\-\-accept\-nth=" "N[,..] or TEMPLATE"
Define which fields to print on accept. The last delimiter is stripped from the Define which fields to print on accept. The last delimiter is stripped from the
output. For advanced transformation, you can provide a template containing output. For advanced transformation, you can provide a template containing
field index expressions in curly braces. field index expressions in curly braces. When you use a template, the trailing
delimiter is stripped from each expression, giving you more control over the
output. \fB{n}\fR in template evaluates to the zero-based ordinal index of the
line.
.RS .RS
e.g. e.g.
@@ -144,7 +149,7 @@ e.g.
echo foo bar baz | fzf --accept-nth 2 echo foo bar baz | fzf --accept-nth 2
# Template # Template
echo foo bar baz | fzf --accept-nth '1st: {1}, 2nd: {2}, 3rd: {3}' echo foo bar baz | fzf --accept-nth 'Index: {n}, 1st: {1}, 2nd: {2}, 3rd: {3}'
.RE .RE
.TP .TP
.B "+s, \-\-no\-sort" .B "+s, \-\-no\-sort"
@@ -704,6 +709,10 @@ ANSI color codes are supported.
Do not display horizontal separator on the info line. A synonym for Do not display horizontal separator on the info line. A synonym for
\fB\-\-separator=''\fB \fB\-\-separator=''\fB
.TP
.BI "\-\-ghost=" "TEXT"
Ghost text to display when the input is empty
.TP .TP
.B "\-\-filepath\-word" .B "\-\-filepath\-word"
Make word-wise movements and actions respect path separators. The following Make word-wise movements and actions respect path separators. The following
@@ -759,6 +768,12 @@ e.g.
\fBfzf \-\-multi \-\-preview='head \-10 {+}' \fBfzf \-\-multi \-\-preview='head \-10 {+}'
git log \-\-oneline | fzf \-\-multi \-\-preview 'git show {+1}'\fR git log \-\-oneline | fzf \-\-multi \-\-preview 'git show {+1}'\fR
Each expression expands to a quoted string, so that it's safe to pass it as an
argument to an external command. So you should not manually add quotes around
the curly braces. But if you don't want this behavior, you can put
\fBr\fR flag (raw) in the expression (e.g. \fB{r}\fR, \fB{r1}\fR, etc).
Use it with caution as unquoted output can lead to broken commands.
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.
@@ -1600,6 +1615,7 @@ A key or an event can be bound to one or more of the following actions.
\fBbell\fR (ring the terminal bell) \fBbell\fR (ring the terminal bell)
\fBcancel\fR (clear query string if not empty, abort fzf otherwise) \fBcancel\fR (clear query string if not empty, abort fzf otherwise)
\fBchange\-border\-label(...)\fR (change \fB\-\-border\-label\fR to the given string) \fBchange\-border\-label(...)\fR (change \fB\-\-border\-label\fR to the given string)
\fBchange\-ghost(...)\fR (change ghost text to the given string)
\fBchange\-header(...)\fR (change header to the given string; doesn't affect \fB\-\-header\-lines\fR) \fBchange\-header(...)\fR (change header to the given string; doesn't affect \fB\-\-header\-lines\fR)
\fBchange\-header\-label(...)\fR (change \fB\-\-header\-label\fR to the given string) \fBchange\-header\-label(...)\fR (change \fB\-\-header\-label\fR to the given string)
\fBchange\-input\-label(...)\fR (change \fB\-\-input\-label\fR to the given string) \fBchange\-input\-label(...)\fR (change \fB\-\-input\-label\fR to the given string)
@@ -1607,6 +1623,7 @@ A key or an event can be bound to one or more of the following actions.
\fBchange\-multi\fR (enable multi-select mode with no limit) \fBchange\-multi\fR (enable multi-select mode with no limit)
\fBchange\-multi(...)\fR (enable multi-select mode with a limit or disable it with 0) \fBchange\-multi(...)\fR (enable multi-select mode with a limit or disable it with 0)
\fBchange\-nth(...)\fR (change \fB\-\-nth\fR option; rotate through the multiple options separated by '|') \fBchange\-nth(...)\fR (change \fB\-\-nth\fR option; rotate through the multiple options separated by '|')
\fBchange\-pointer(...)\fR (change \fB\-\-pointer\fR option)
\fBchange\-preview(...)\fR (change \fB\-\-preview\fR option) \fBchange\-preview(...)\fR (change \fB\-\-preview\fR option)
\fBchange\-preview\-label(...)\fR (change \fB\-\-preview\-label\fR to the given string) \fBchange\-preview\-label(...)\fR (change \fB\-\-preview\-label\fR to the given string)
\fBchange\-preview\-window(...)\fR (change \fB\-\-preview\-window\fR option; rotate through the multiple option sets separated by '|') \fBchange\-preview\-window(...)\fR (change \fB\-\-preview\-window\fR option; rotate through the multiple option sets separated by '|')
@@ -1695,11 +1712,13 @@ A key or an event can be bound to one or more of the following actions.
\fBtrack\-current\fR (track the current item; automatically disabled if focus changes) \fBtrack\-current\fR (track the current item; automatically disabled if focus changes)
\fBtransform(...)\fR (transform states using the output of an external command) \fBtransform(...)\fR (transform states using the output of an external command)
\fBtransform\-border\-label(...)\fR (transform border label using an external command) \fBtransform\-border\-label(...)\fR (transform border label using an external command)
\fBtransform\-ghost(...)\fR (transform ghost text using an external command)
\fBtransform\-header(...)\fR (transform header using an external command) \fBtransform\-header(...)\fR (transform header using an external command)
\fBtransform\-header\-label(...)\fR (transform header label using an external command) \fBtransform\-header\-label(...)\fR (transform header label using an external command)
\fBtransform\-input\-label(...)\fR (transform input label using an external command) \fBtransform\-input\-label(...)\fR (transform input label using an external command)
\fBtransform\-list\-label(...)\fR (transform list label using an external command) \fBtransform\-list\-label(...)\fR (transform list label using an external command)
\fBtransform\-nth(...)\fR (transform nth using an external command) \fBtransform\-nth(...)\fR (transform nth using an external command)
\fBtransform\-pointer(...)\fR (transform pointer using an external command)
\fBtransform\-preview\-label(...)\fR (transform preview label using an external command) \fBtransform\-preview\-label(...)\fR (transform preview label using an external command)
\fBtransform\-prompt(...)\fR (transform prompt string using an external command) \fBtransform\-prompt(...)\fR (transform prompt string using an external command)
\fBtransform\-query(...)\fR (transform query string using an external command) \fBtransform\-query(...)\fR (transform query string using an external command)
@@ -1719,6 +1738,9 @@ e.g.
\fBfzf \-\-multi \-\-bind 'ctrl\-a:select\-all+accept'\fR \fBfzf \-\-multi \-\-bind 'ctrl\-a:select\-all+accept'\fR
\fBfzf \-\-multi \-\-bind 'ctrl\-a:select\-all' \-\-bind 'ctrl\-a:+accept'\fR \fBfzf \-\-multi \-\-bind 'ctrl\-a:select\-all' \-\-bind 'ctrl\-a:+accept'\fR
Any action after a terminal action that exits fzf, such as \fBaccept\fR or
\fBabort\fR, is ignored.
.SS ACTION ARGUMENT .SS ACTION ARGUMENT
An action denoted with \fB(...)\fR suffix takes an argument. An action denoted with \fB(...)\fR suffix takes an argument.

View File

@@ -358,7 +358,7 @@ endfunction
function! s:get_color(attr, ...) function! s:get_color(attr, ...)
" Force 24 bit colors: g:fzf_force_termguicolors (temporary workaround for https://github.com/junegunn/fzf.vim/issues/1152) " Force 24 bit colors: g:fzf_force_termguicolors (temporary workaround for https://github.com/junegunn/fzf.vim/issues/1152)
let gui = get(g:, 'fzf_force_termguicolors', 0) || (!s:is_win && !has('win32unix') && has('termguicolors') && &termguicolors) let gui = get(g:, 'fzf_force_termguicolors', 0) || (!s:is_win && !has('win32unix') && (has('gui_running') || has('termguicolors') && &termguicolors))
let fam = gui ? 'gui' : 'cterm' let fam = gui ? 'gui' : 'cterm'
let pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$' let pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$'
for group in a:000 for group in a:000

View File

@@ -311,12 +311,12 @@ __fzf_generic_path_completion() {
else else
if [[ $1 =~ dir ]]; then if [[ $1 =~ dir ]]; then
walker=dir,follow walker=dir,follow
rest=${FZF_COMPLETION_DIR_OPTS-} eval "rest=(${FZF_COMPLETION_DIR_OPTS-})"
else else
walker=file,dir,follow,hidden walker=file,dir,follow,hidden
rest=${FZF_COMPLETION_PATH_OPTS-} eval "rest=(${FZF_COMPLETION_PATH_OPTS-})"
fi fi
__fzf_comprun "$4" -q "$leftover" --walker "$walker" --walker-root="$dir" $rest __fzf_comprun "$4" -q "$leftover" --walker "$walker" --walker-root="$dir" "${rest[@]}"
fi | while read -r item; do fi | while read -r item; do
printf "%q " "${item%$3}$3" printf "%q " "${item%$3}$3"
done done

View File

@@ -116,10 +116,9 @@ function fzf_key_bindings
set -l fzf_query (commandline | string escape) set -l fzf_query (commandline | string escape)
set -lx FZF_DEFAULT_OPTS (__fzf_defaults '' \ set -lx FZF_DEFAULT_OPTS (__fzf_defaults '' \
'--nth=2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign="\t↳ "' \ '--nth=2..,.. --scheme=history --multi --wrap-sign="\t↳ "' \
"--highlight-line --no-multi $FZF_CTRL_R_OPTS --read0 --print0" \ "--bind=ctrl-r:toggle-sort --highlight-line $FZF_CTRL_R_OPTS" \
"--bind='enter:become:string replace -a -- \n\t \n {2..} | string collect'" \ '--accept-nth=2.. --read0 --print0 --with-shell='(status fish-path)\\ -c)
'--with-shell='(status fish-path)\\ -c)
set -lx FZF_DEFAULT_OPTS_FILE set -lx FZF_DEFAULT_OPTS_FILE
set -lx FZF_DEFAULT_COMMAND set -lx FZF_DEFAULT_COMMAND
@@ -138,8 +137,12 @@ function fzf_key_bindings
# Merge history from other sessions before searching # Merge history from other sessions before searching
test -z "$fish_private_mode"; and builtin history merge test -z "$fish_private_mode"; and builtin history merge
set -l result (eval $FZF_DEFAULT_COMMAND \| (__fzfcmd) --query=$fzf_query) if set -l result (eval $FZF_DEFAULT_COMMAND \| (__fzfcmd) --query=$fzf_query | string split0)
and commandline -- $result commandline -- (string replace -a -- \n\t \n $result[1])
test (count $result) -gt 1; and for i in $result[2..-1]
commandline -i -- (string replace -a -- \n\t \n \n$i)
end
end
commandline -f repaint commandline -f repaint
end end

View File

@@ -12,139 +12,145 @@ func _() {
_ = x[actStart-1] _ = x[actStart-1]
_ = x[actClick-2] _ = x[actClick-2]
_ = x[actInvalid-3] _ = x[actInvalid-3]
_ = x[actChar-4] _ = x[actBracketedPasteBegin-4]
_ = x[actMouse-5] _ = x[actBracketedPasteEnd-5]
_ = x[actBeginningOfLine-6] _ = x[actChar-6]
_ = x[actAbort-7] _ = x[actMouse-7]
_ = x[actAccept-8] _ = x[actBeginningOfLine-8]
_ = x[actAcceptNonEmpty-9] _ = x[actAbort-9]
_ = x[actAcceptOrPrintQuery-10] _ = x[actAccept-10]
_ = x[actBackwardChar-11] _ = x[actAcceptNonEmpty-11]
_ = x[actBackwardDeleteChar-12] _ = x[actAcceptOrPrintQuery-12]
_ = x[actBackwardDeleteCharEof-13] _ = x[actBackwardChar-13]
_ = x[actBackwardWord-14] _ = x[actBackwardDeleteChar-14]
_ = x[actCancel-15] _ = x[actBackwardDeleteCharEof-15]
_ = x[actChangeBorderLabel-16] _ = x[actBackwardWord-16]
_ = x[actChangeListLabel-17] _ = x[actCancel-17]
_ = x[actChangeInputLabel-18] _ = x[actChangeBorderLabel-18]
_ = x[actChangeHeader-19] _ = x[actChangeGhost-19]
_ = x[actChangeHeaderLabel-20] _ = x[actChangeHeader-20]
_ = x[actChangeMulti-21] _ = x[actChangeHeaderLabel-21]
_ = x[actChangePreviewLabel-22] _ = x[actChangeInputLabel-22]
_ = x[actChangePrompt-23] _ = x[actChangeListLabel-23]
_ = x[actChangeQuery-24] _ = x[actChangeMulti-24]
_ = x[actChangeNth-25] _ = x[actChangeNth-25]
_ = x[actClearScreen-26] _ = x[actChangePointer-26]
_ = x[actClearQuery-27] _ = x[actChangePreview-27]
_ = x[actClearSelection-28] _ = x[actChangePreviewLabel-28]
_ = x[actClose-29] _ = x[actChangePreviewWindow-29]
_ = x[actDeleteChar-30] _ = x[actChangePrompt-30]
_ = x[actDeleteCharEof-31] _ = x[actChangeQuery-31]
_ = x[actEndOfLine-32] _ = x[actClearScreen-32]
_ = x[actFatal-33] _ = x[actClearQuery-33]
_ = x[actForwardChar-34] _ = x[actClearSelection-34]
_ = x[actForwardWord-35] _ = x[actClose-35]
_ = x[actKillLine-36] _ = x[actDeleteChar-36]
_ = x[actKillWord-37] _ = x[actDeleteCharEof-37]
_ = x[actUnixLineDiscard-38] _ = x[actEndOfLine-38]
_ = x[actUnixWordRubout-39] _ = x[actFatal-39]
_ = x[actYank-40] _ = x[actForwardChar-40]
_ = x[actBackwardKillWord-41] _ = x[actForwardWord-41]
_ = x[actSelectAll-42] _ = x[actKillLine-42]
_ = x[actDeselectAll-43] _ = x[actKillWord-43]
_ = x[actToggle-44] _ = x[actUnixLineDiscard-44]
_ = x[actToggleSearch-45] _ = x[actUnixWordRubout-45]
_ = x[actToggleAll-46] _ = x[actYank-46]
_ = x[actToggleDown-47] _ = x[actBackwardKillWord-47]
_ = x[actToggleUp-48] _ = x[actSelectAll-48]
_ = x[actToggleIn-49] _ = x[actDeselectAll-49]
_ = x[actToggleOut-50] _ = x[actToggle-50]
_ = x[actToggleTrack-51] _ = x[actToggleSearch-51]
_ = x[actToggleTrackCurrent-52] _ = x[actToggleAll-52]
_ = x[actToggleHeader-53] _ = x[actToggleDown-53]
_ = x[actToggleWrap-54] _ = x[actToggleUp-54]
_ = x[actToggleMultiLine-55] _ = x[actToggleIn-55]
_ = x[actToggleHscroll-56] _ = x[actToggleOut-56]
_ = x[actTrackCurrent-57] _ = x[actToggleTrack-57]
_ = x[actToggleInput-58] _ = x[actToggleTrackCurrent-58]
_ = x[actHideInput-59] _ = x[actToggleHeader-59]
_ = x[actShowInput-60] _ = x[actToggleWrap-60]
_ = x[actUntrackCurrent-61] _ = x[actToggleMultiLine-61]
_ = x[actDown-62] _ = x[actToggleHscroll-62]
_ = x[actUp-63] _ = x[actTrackCurrent-63]
_ = x[actPageUp-64] _ = x[actToggleInput-64]
_ = x[actPageDown-65] _ = x[actHideInput-65]
_ = x[actPosition-66] _ = x[actShowInput-66]
_ = x[actHalfPageUp-67] _ = x[actUntrackCurrent-67]
_ = x[actHalfPageDown-68] _ = x[actDown-68]
_ = x[actOffsetUp-69] _ = x[actUp-69]
_ = x[actOffsetDown-70] _ = x[actPageUp-70]
_ = x[actOffsetMiddle-71] _ = x[actPageDown-71]
_ = x[actJump-72] _ = x[actPosition-72]
_ = x[actJumpAccept-73] _ = x[actHalfPageUp-73]
_ = x[actPrintQuery-74] _ = x[actHalfPageDown-74]
_ = x[actRefreshPreview-75] _ = x[actOffsetUp-75]
_ = x[actReplaceQuery-76] _ = x[actOffsetDown-76]
_ = x[actToggleSort-77] _ = x[actOffsetMiddle-77]
_ = x[actShowPreview-78] _ = x[actJump-78]
_ = x[actHidePreview-79] _ = x[actJumpAccept-79]
_ = x[actTogglePreview-80] _ = x[actPrintQuery-80]
_ = x[actTogglePreviewWrap-81] _ = x[actRefreshPreview-81]
_ = x[actTransform-82] _ = x[actReplaceQuery-82]
_ = x[actTransformBorderLabel-83] _ = x[actToggleSort-83]
_ = x[actTransformListLabel-84] _ = x[actShowPreview-84]
_ = x[actTransformInputLabel-85] _ = x[actHidePreview-85]
_ = x[actTransformHeader-86] _ = x[actTogglePreview-86]
_ = x[actTransformHeaderLabel-87] _ = x[actTogglePreviewWrap-87]
_ = x[actTransformNth-88] _ = x[actTransform-88]
_ = x[actTransformPreviewLabel-89] _ = x[actTransformBorderLabel-89]
_ = x[actTransformPrompt-90] _ = x[actTransformGhost-90]
_ = x[actTransformQuery-91] _ = x[actTransformHeader-91]
_ = x[actTransformSearch-92] _ = x[actTransformHeaderLabel-92]
_ = x[actSearch-93] _ = x[actTransformInputLabel-93]
_ = x[actPreview-94] _ = x[actTransformListLabel-94]
_ = x[actChangePreview-95] _ = x[actTransformNth-95]
_ = x[actChangePreviewWindow-96] _ = x[actTransformPointer-96]
_ = x[actPreviewTop-97] _ = x[actTransformPreviewLabel-97]
_ = x[actPreviewBottom-98] _ = x[actTransformPrompt-98]
_ = x[actPreviewUp-99] _ = x[actTransformQuery-99]
_ = x[actPreviewDown-100] _ = x[actTransformSearch-100]
_ = x[actPreviewPageUp-101] _ = x[actSearch-101]
_ = x[actPreviewPageDown-102] _ = x[actPreview-102]
_ = x[actPreviewHalfPageUp-103] _ = x[actPreviewTop-103]
_ = x[actPreviewHalfPageDown-104] _ = x[actPreviewBottom-104]
_ = x[actPrevHistory-105] _ = x[actPreviewUp-105]
_ = x[actPrevSelected-106] _ = x[actPreviewDown-106]
_ = x[actPrint-107] _ = x[actPreviewPageUp-107]
_ = x[actPut-108] _ = x[actPreviewPageDown-108]
_ = x[actNextHistory-109] _ = x[actPreviewHalfPageUp-109]
_ = x[actNextSelected-110] _ = x[actPreviewHalfPageDown-110]
_ = x[actExecute-111] _ = x[actPrevHistory-111]
_ = x[actExecuteSilent-112] _ = x[actPrevSelected-112]
_ = x[actExecuteMulti-113] _ = x[actPrint-113]
_ = x[actSigStop-114] _ = x[actPut-114]
_ = x[actFirst-115] _ = x[actNextHistory-115]
_ = x[actLast-116] _ = x[actNextSelected-116]
_ = x[actReload-117] _ = x[actExecute-117]
_ = x[actReloadSync-118] _ = x[actExecuteSilent-118]
_ = x[actDisableSearch-119] _ = x[actExecuteMulti-119]
_ = x[actEnableSearch-120] _ = x[actSigStop-120]
_ = x[actSelect-121] _ = x[actFirst-121]
_ = x[actDeselect-122] _ = x[actLast-122]
_ = x[actUnbind-123] _ = x[actReload-123]
_ = x[actRebind-124] _ = x[actReloadSync-124]
_ = x[actToggleBind-125] _ = x[actDisableSearch-125]
_ = x[actBecome-126] _ = x[actEnableSearch-126]
_ = x[actShowHeader-127] _ = x[actSelect-127]
_ = x[actHideHeader-128] _ = x[actDeselect-128]
_ = x[actBell-129] _ = x[actUnbind-129]
_ = x[actExclude-130] _ = x[actRebind-130]
_ = x[actExcludeMulti-131] _ = x[actToggleBind-131]
_ = x[actBecome-132]
_ = x[actShowHeader-133]
_ = x[actHideHeader-134]
_ = x[actBell-135]
_ = x[actExclude-136]
_ = x[actExcludeMulti-137]
} }
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeListLabelactChangeInputLabelactChangeHeaderactChangeHeaderLabelactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactChangeNthactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformListLabelactTransformInputLabelactTransformHeaderactTransformHeaderLabelactTransformNthactTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactSearchactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBellactExcludeactExcludeMulti" const _actionType_name = "actIgnoreactStartactClickactInvalidactBracketedPasteBeginactBracketedPasteEndactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeGhostactChangeHeaderactChangeHeaderLabelactChangeInputLabelactChangeListLabelactChangeMultiactChangeNthactChangePointeractChangePreviewactChangePreviewLabelactChangePreviewWindowactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformGhostactTransformHeaderactTransformHeaderLabelactTransformInputLabelactTransformListLabelactTransformNthactTransformPointeractTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactSearchactPreviewactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBellactExcludeactExcludeMulti"
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 245, 264, 279, 299, 313, 334, 349, 363, 375, 389, 402, 419, 427, 440, 456, 468, 476, 490, 504, 515, 526, 544, 561, 568, 587, 599, 613, 622, 637, 649, 662, 673, 684, 696, 710, 731, 746, 759, 777, 793, 808, 822, 834, 846, 863, 870, 875, 884, 895, 906, 919, 934, 945, 958, 973, 980, 993, 1006, 1023, 1038, 1051, 1065, 1079, 1095, 1115, 1127, 1150, 1171, 1193, 1211, 1234, 1249, 1273, 1291, 1308, 1326, 1335, 1345, 1361, 1383, 1396, 1412, 1424, 1438, 1454, 1472, 1492, 1514, 1528, 1543, 1551, 1557, 1571, 1586, 1596, 1612, 1627, 1637, 1645, 1652, 1661, 1674, 1690, 1705, 1714, 1725, 1734, 1743, 1756, 1765, 1778, 1791, 1798, 1808, 1823} var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 57, 77, 84, 92, 110, 118, 127, 144, 165, 180, 201, 225, 240, 249, 269, 283, 298, 318, 337, 355, 369, 381, 397, 413, 434, 456, 471, 485, 499, 512, 529, 537, 550, 566, 578, 586, 600, 614, 625, 636, 654, 671, 678, 697, 709, 723, 732, 747, 759, 772, 783, 794, 806, 820, 841, 856, 869, 887, 903, 918, 932, 944, 956, 973, 980, 985, 994, 1005, 1016, 1029, 1044, 1055, 1068, 1083, 1090, 1103, 1116, 1133, 1148, 1161, 1175, 1189, 1205, 1225, 1237, 1260, 1277, 1295, 1318, 1340, 1361, 1376, 1395, 1419, 1437, 1454, 1472, 1481, 1491, 1504, 1520, 1532, 1546, 1562, 1580, 1600, 1622, 1636, 1651, 1659, 1665, 1679, 1694, 1704, 1720, 1735, 1745, 1753, 1760, 1769, 1782, 1798, 1813, 1822, 1833, 1842, 1851, 1864, 1873, 1886, 1899, 1906, 1916, 1931}
func (i actionType) String() string { func (i actionType) String() string {
if i < 0 || i >= actionType(len(_actionType_index)-1) { if i < 0 || i >= actionType(len(_actionType_index)-1) {

View File

@@ -26,7 +26,7 @@ const (
previewCancelWait = 500 * time.Millisecond previewCancelWait = 500 * time.Millisecond
previewChunkDelay = 100 * time.Millisecond previewChunkDelay = 100 * time.Millisecond
previewDelayed = 500 * time.Millisecond previewDelayed = 500 * time.Millisecond
maxPatternLength = 300 maxPatternLength = 1000
maxMulti = math.MaxInt32 maxMulti = math.MaxInt32
// Matcher // Matcher

View File

@@ -128,13 +128,14 @@ func Run(opts *Options) (int, error) {
} }
} }
} }
transformed := nthTransformer(tokens) transformed := nthTransformer(tokens, itemIndex)
if len(header) < opts.HeaderLines { if len(header) < opts.HeaderLines {
header = append(header, transformed) header = append(header, transformed)
eventBox.Set(EvtHeader, header) eventBox.Set(EvtHeader, header)
return false return false
} }
item.text, item.colors = ansiProcessor(stringBytes(transformed)) item.text, item.colors = ansiProcessor(stringBytes(transformed))
item.text.TrimTrailingWhitespaces()
item.text.Index = itemIndex item.text.Index = itemIndex
item.origText = &data item.origText = &data
itemIndex++ itemIndex++
@@ -476,8 +477,17 @@ func Run(opts *Options) (int, error) {
if len(opts.Expect) > 0 { if len(opts.Expect) > 0 {
opts.Printer("") opts.Printer("")
} }
transformer := func(item *Item) string {
return item.AsString(opts.Ansi)
}
if opts.AcceptNth != nil {
fn := opts.AcceptNth(opts.Delimiter)
transformer = func(item *Item) string {
return item.acceptNth(opts.Ansi, opts.Delimiter, fn)
}
}
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
opts.Printer(val.Get(i).item.AsString(opts.Ansi)) opts.Printer(transformer(val.Get(i).item))
} }
if count == 0 { if count == 0 {
exitCode = ExitNoMatch exitCode = ExitNoMatch

View File

@@ -51,3 +51,9 @@ func (item *Item) AsString(stripAnsi bool) string {
} }
return item.text.ToString() return item.text.ToString()
} }
func (item *Item) acceptNth(stripAnsi bool, delimiter Delimiter, transformer func([]Token, int32) string) string {
tokens := Tokenize(item.AsString(stripAnsi), delimiter)
transformed := transformer(tokens, item.Index())
return StripLastDelimiter(transformed, delimiter)
}

View File

@@ -136,6 +136,7 @@ Usage: fzf [options]
--separator=STR Draw horizontal separator on info line using the string --separator=STR Draw horizontal separator on info line using the string
(default: '─' or '-') (default: '─' or '-')
--no-separator Hide info line separator --no-separator Hide info line separator
--ghost=TEXT Ghost text to display when the input is empty
--filepath-word Make word-wise movements respect path separators --filepath-word Make word-wise movements respect path separators
--input-border[=STYLE] Draw border around the input section --input-border[=STYLE] Draw border around the input section
[rounded|sharp|bold|block|thinblock|double|horizontal|vertical| [rounded|sharp|bold|block|thinblock|double|horizontal|vertical|
@@ -544,8 +545,8 @@ type Options struct {
Case Case Case Case
Normalize bool Normalize bool
Nth []Range Nth []Range
WithNth func(Delimiter) func([]Token) string WithNth func(Delimiter) func([]Token, int32) string
AcceptNth func(Delimiter) func([]Token) string AcceptNth func(Delimiter) func([]Token, int32) string
Delimiter Delimiter Delimiter Delimiter
Sort int Sort int
Track trackOption Track trackOption
@@ -574,6 +575,7 @@ type Options struct {
InfoStyle infoStyle InfoStyle infoStyle
InfoPrefix string InfoPrefix string
InfoCommand string InfoCommand string
Ghost string
Separator *string Separator *string
JumpLabels string JumpLabels string
Prompt string Prompt string
@@ -689,6 +691,7 @@ func defaultOptions() *Options {
ScrollOff: 3, ScrollOff: 3,
FileWord: false, FileWord: false,
InfoStyle: infoDefault, InfoStyle: infoDefault,
Ghost: "",
Separator: nil, Separator: nil,
JumpLabels: defaultJumpLabels, JumpLabels: defaultJumpLabels,
Prompt: "> ", Prompt: "> ",
@@ -769,30 +772,31 @@ func splitNth(str string) ([]Range, error) {
return ranges, nil return ranges, nil
} }
func nthTransformer(str string) (func(Delimiter) func([]Token) string, error) { func nthTransformer(str string) (func(Delimiter) func([]Token, int32) string, error) {
// ^[0-9,-.]+$" // ^[0-9,-.]+$"
if match, _ := regexp.MatchString("^[0-9,-.]+$", str); match { if match, _ := regexp.MatchString("^[0-9,-.]+$", str); match {
nth, err := splitNth(str) nth, err := splitNth(str)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return func(Delimiter) func([]Token) string { return func(Delimiter) func([]Token, int32) string {
return func(tokens []Token) string { return func(tokens []Token, index int32) string {
return JoinTokens(Transform(tokens, nth)) return JoinTokens(Transform(tokens, nth))
} }
}, nil }, nil
} }
// {...} {...} ... // {...} {...} ...
placeholder := regexp.MustCompile("{[0-9,-.]+}") placeholder := regexp.MustCompile("{[0-9,-.]+}|{n}")
indexes := placeholder.FindAllStringIndex(str, -1) indexes := placeholder.FindAllStringIndex(str, -1)
if indexes == nil { if indexes == nil {
return nil, errors.New("template should include at least 1 placeholder: " + str) return nil, errors.New("template should include at least 1 placeholder: " + str)
} }
type NthParts struct { type NthParts struct {
str string str string
nth []Range index bool
nth []Range
} }
parts := make([]NthParts, len(indexes)) parts := make([]NthParts, len(indexes))
@@ -801,7 +805,10 @@ func nthTransformer(str string) (func(Delimiter) func([]Token) string, error) {
if idx < index[0] { if idx < index[0] {
parts = append(parts, NthParts{str: str[idx:index[0]]}) parts = append(parts, NthParts{str: str[idx:index[0]]})
} }
if nth, err := splitNth(str[index[0]+1 : index[1]-1]); err == nil { expr := str[index[0]+1 : index[1]-1]
if expr == "n" {
parts = append(parts, NthParts{index: true})
} else if nth, err := splitNth(expr); err == nil {
parts = append(parts, NthParts{nth: nth}) parts = append(parts, NthParts{nth: nth})
} }
idx = index[1] idx = index[1]
@@ -810,12 +817,16 @@ func nthTransformer(str string) (func(Delimiter) func([]Token) string, error) {
parts = append(parts, NthParts{str: str[idx:]}) parts = append(parts, NthParts{str: str[idx:]})
} }
return func(delimiter Delimiter) func([]Token) string { return func(delimiter Delimiter) func([]Token, int32) string {
return func(tokens []Token) string { return func(tokens []Token, index int32) string {
str := "" str := ""
for _, holder := range parts { for _, holder := range parts {
if holder.nth != nil { if holder.nth != nil {
str += StripLastDelimiter(JoinTokens(Transform(tokens, holder.nth)), delimiter) str += StripLastDelimiter(JoinTokens(Transform(tokens, holder.nth)), delimiter)
} else if holder.index {
if index >= 0 {
str += strconv.Itoa(int(index))
}
} else { } else {
str += holder.str str += holder.str
} }
@@ -1393,7 +1404,7 @@ const (
func init() { func init() {
executeRegexp = regexp.MustCompile( executeRegexp = regexp.MustCompile(
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:query|prompt|(?:border|list|preview|input|header)-label|header|search|nth)|transform|change-(?:preview-window|preview|multi)|(?:re|un|toggle-)bind|pos|put|print|search)`) `(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:query|prompt|(?:border|list|preview|input|header)-label|header|search|nth|pointer|ghost)|transform|change-(?:preview-window|preview|multi)|(?:re|un|toggle-)bind|pos|put|print|search)`)
splitRegexp = regexp.MustCompile("[,:]+") splitRegexp = regexp.MustCompile("[,:]+")
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+") actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
} }
@@ -1788,6 +1799,10 @@ func isExecuteAction(str string) actionType {
return actChangeInputLabel return actChangeInputLabel
case "change-header-label": case "change-header-label":
return actChangeHeaderLabel return actChangeHeaderLabel
case "change-ghost":
return actChangeGhost
case "change-pointer":
return actChangePointer
case "change-preview-window": case "change-preview-window":
return actChangePreviewWindow return actChangePreviewWindow
case "change-preview": case "change-preview":
@@ -1826,8 +1841,12 @@ func isExecuteAction(str string) actionType {
return actTransformHeaderLabel return actTransformHeaderLabel
case "transform-header": case "transform-header":
return actTransformHeader return actTransformHeader
case "transform-ghost":
return actTransformGhost
case "transform-nth": case "transform-nth":
return actTransformNth return actTransformNth
case "transform-pointer":
return actTransformPointer
case "transform-prompt": case "transform-prompt":
return actTransformPrompt return actTransformPrompt
case "transform-query": case "transform-query":
@@ -2589,6 +2608,10 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
case "--no-separator": case "--no-separator":
nosep := "" nosep := ""
opts.Separator = &nosep opts.Separator = &nosep
case "--ghost":
if opts.Ghost, err = nextString("ghost text required"); err != nil {
return err
}
case "--scrollbar": case "--scrollbar":
given, bar := optionalNextString() given, bar := optionalNextString()
if given { if given {

View File

@@ -59,12 +59,12 @@ func runProxy(commandPrefix string, cmdBuilder func(temp string, needBash bool)
}) })
}() }()
var command string var command, input string
commandPrefix += ` --no-force-tty-in --proxy-script "$0"` commandPrefix += ` --no-force-tty-in --proxy-script "$0"`
if opts.Input == nil && (opts.ForceTtyIn || util.IsTty(os.Stdin)) { if opts.Input == nil && (opts.ForceTtyIn || util.IsTty(os.Stdin)) {
command = fmt.Sprintf(`%s > %q`, commandPrefix, output) command = fmt.Sprintf(`%s > %q`, commandPrefix, output)
} else { } else {
input, err := fifo("proxy-input") input, err = fifo("proxy-input")
if err != nil { if err != nil {
return ExitError, err return ExitError, err
} }
@@ -90,9 +90,9 @@ func runProxy(commandPrefix string, cmdBuilder func(temp string, needBash bool)
} }
} }
// To ensure that the options are processed by a POSIX-compliant shell, // * Write the command to a temporary file and run it with sh to ensure POSIX compliance.
// we need to write the command to a temporary file and execute it with sh. // * Nullify FZF_DEFAULT_* variables as tmux popup may inject them even when undefined.
var exports []string exports := []string{"FZF_DEFAULT_COMMAND=", "FZF_DEFAULT_OPTS=", "FZF_DEFAULT_OPTS_FILE="}
needBash := false needBash := false
if withExports { if withExports {
validIdentifier := regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`) validIdentifier := regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
@@ -148,6 +148,9 @@ func runProxy(commandPrefix string, cmdBuilder func(temp string, needBash bool)
if err != nil { if err != nil {
return ExitError, err return ExitError, err
} }
os.Remove(temp)
os.Remove(input)
os.Remove(output)
executor.Become(ttyin, env, command) executor.Become(ttyin, env, command)
} }
return code, err return code, err

View File

@@ -277,6 +277,9 @@ func (r *Reader) readFiles(roots []string, opts walkerOpts, ignores []string) bo
ignoresFull := []string{} ignoresFull := []string{}
ignoresSuffix := []string{} ignoresSuffix := []string{}
sep := string(os.PathSeparator) sep := string(os.PathSeparator)
if _, ok := os.LookupEnv("MSYSTEM"); ok {
sep = "/"
}
for _, ignore := range ignores { for _, ignore := range ignores {
if strings.ContainsRune(ignore, os.PathSeparator) { if strings.ContainsRune(ignore, os.PathSeparator) {
if strings.HasPrefix(ignore, sep) { if strings.HasPrefix(ignore, sep) {

View File

@@ -38,7 +38,7 @@ As such it is not useful for validation, but rather to generate test
cases for example. cases for example.
\\?(?: # escaped type \\?(?: # escaped type
{\+?s?f?RANGE(?:,RANGE)*} # token type {\+?s?f?r?RANGE(?:,RANGE)*} # token type
{q[:s?RANGE]} # query type {q[:s?RANGE]} # query type
|{\+?n?f?} # item type (notice no mandatory element inside brackets) |{\+?n?f?} # item type (notice no mandatory element inside brackets)
) )
@@ -65,7 +65,7 @@ const maxFocusEvents = 10000
const blockDuration = 1 * time.Second const blockDuration = 1 * time.Second
func init() { func init() {
placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q(?::s?[0-9,-.]+)?}|{fzf:(?:query|action|prompt)}|{\+?f?nf?})`) placeholder = regexp.MustCompile(`\\?(?:{[+sfr]*[0-9,-.]*}|{q(?::s?[0-9,-.]+)?}|{fzf:(?:query|action|prompt)}|{\+?f?nf?})`)
whiteSuffix = regexp.MustCompile(`\s*$`) whiteSuffix = regexp.MustCompile(`\s*$`)
offsetComponentRegex = regexp.MustCompile(`([+-][0-9]+)|(-?/[1-9][0-9]*)`) offsetComponentRegex = regexp.MustCompile(`([+-][0-9]+)|(-?/[1-9][0-9]*)`)
offsetTrimCharsRegex = regexp.MustCompile(`[^0-9/+-]`) offsetTrimCharsRegex = regexp.MustCompile(`[^0-9/+-]`)
@@ -234,6 +234,7 @@ type Terminal struct {
wrap bool wrap bool
wrapSign string wrapSign string
wrapSignWidth int wrapSignWidth int
ghost string
separator labelPrinter separator labelPrinter
separatorLen int separatorLen int
spinner []string spinner []string
@@ -278,6 +279,7 @@ type Terminal struct {
yanked []rune yanked []rune
input []rune input []rune
inputOverride *[]rune inputOverride *[]rune
pasting *[]rune
multi int multi int
multiLine bool multiLine bool
sort bool sort bool
@@ -305,7 +307,7 @@ type Terminal struct {
nthAttr tui.Attr nthAttr tui.Attr
nth []Range nth []Range
nthCurrent []Range nthCurrent []Range
acceptNth func([]Token) string acceptNth func([]Token, int32) string
tabstop int tabstop int
margin [4]sizeSpec margin [4]sizeSpec
padding [4]sizeSpec padding [4]sizeSpec
@@ -458,6 +460,8 @@ const (
actStart actStart
actClick actClick
actInvalid actInvalid
actBracketedPasteBegin
actBracketedPasteEnd
actChar actChar
actMouse actMouse
actBeginningOfLine actBeginningOfLine
@@ -471,15 +475,19 @@ const (
actBackwardWord actBackwardWord
actCancel actCancel
actChangeBorderLabel actChangeBorderLabel
actChangeListLabel actChangeGhost
actChangeInputLabel
actChangeHeader actChangeHeader
actChangeHeaderLabel actChangeHeaderLabel
actChangeInputLabel
actChangeListLabel
actChangeMulti actChangeMulti
actChangeNth
actChangePointer
actChangePreview
actChangePreviewLabel actChangePreviewLabel
actChangePreviewWindow
actChangePrompt actChangePrompt
actChangeQuery actChangeQuery
actChangeNth
actClearScreen actClearScreen
actClearQuery actClearQuery
actClearSelection actClearSelection
@@ -538,19 +546,19 @@ const (
actTogglePreviewWrap actTogglePreviewWrap
actTransform actTransform
actTransformBorderLabel actTransformBorderLabel
actTransformListLabel actTransformGhost
actTransformInputLabel
actTransformHeader actTransformHeader
actTransformHeaderLabel actTransformHeaderLabel
actTransformInputLabel
actTransformListLabel
actTransformNth actTransformNth
actTransformPointer
actTransformPreviewLabel actTransformPreviewLabel
actTransformPrompt actTransformPrompt
actTransformQuery actTransformQuery
actTransformSearch actTransformSearch
actSearch actSearch
actPreview actPreview
actChangePreview
actChangePreviewWindow
actPreviewTop actPreviewTop
actPreviewBottom actPreviewBottom
actPreviewUp actPreviewUp
@@ -620,6 +628,7 @@ type placeholderFlags struct {
number bool number bool
forceUpdate bool forceUpdate bool
file bool file bool
raw bool
} }
type searchRequest struct { type searchRequest struct {
@@ -667,6 +676,8 @@ func defaultKeymap() map[tui.Event][]*action {
add(tui.Fatal, actFatal) add(tui.Fatal, actFatal)
add(tui.Invalid, actInvalid) add(tui.Invalid, actInvalid)
add(tui.BracketedPasteBegin, actBracketedPasteBegin)
add(tui.BracketedPasteEnd, actBracketedPasteEnd)
add(tui.CtrlA, actBeginningOfLine) add(tui.CtrlA, actBeginningOfLine)
add(tui.CtrlB, actBackwardChar) add(tui.CtrlB, actBackwardChar)
add(tui.CtrlC, actAbort) add(tui.CtrlC, actAbort)
@@ -847,6 +858,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
infoCommand: opts.InfoCommand, infoCommand: opts.InfoCommand,
infoStyle: opts.InfoStyle, infoStyle: opts.InfoStyle,
infoPrefix: opts.InfoPrefix, infoPrefix: opts.InfoPrefix,
ghost: opts.Ghost,
separator: nil, separator: nil,
spinner: makeSpinner(opts.Unicode), spinner: makeSpinner(opts.Unicode),
promptString: opts.Prompt, promptString: opts.Prompt,
@@ -1299,8 +1311,11 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) {
t.wrap = false t.wrap = false
t.withWindow(t.inputWindow, func() { t.withWindow(t.inputWindow, func() {
line := t.promptLine() line := t.promptLine()
preTask := func(markerClass) int {
return 1
}
t.printHighlighted( t.printHighlighted(
Result{item: item}, tui.ColPrompt, tui.ColPrompt, false, false, line, line, true, nil, nil) Result{item: item}, tui.ColPrompt, tui.ColPrompt, false, false, line, line, true, preTask, nil)
}) })
t.wrap = wrap t.wrap = wrap
} }
@@ -1347,7 +1362,7 @@ func (t *Terminal) numItemLines(item *Item, atMost int) (int, bool) {
} }
if cached, prs := t.numLinesCache[item.Index()]; prs { if cached, prs := t.numLinesCache[item.Index()]; prs {
// Can we use this cache? Let's be conservative. // Can we use this cache? Let's be conservative.
if cached.atMost >= atMost { if cached.atMost <= atMost {
return cached.numLines, false return cached.numLines, false
} }
} }
@@ -1408,10 +1423,7 @@ func (t *Terminal) Input() (bool, []rune) {
t.mutex.Lock() t.mutex.Lock()
defer t.mutex.Unlock() defer t.mutex.Unlock()
paused := t.paused paused := t.paused
var src []rune src := t.input
if !t.inputless {
src = t.input
}
if t.inputOverride != nil { if t.inputOverride != nil {
paused = false paused = false
src = *t.inputOverride src = *t.inputOverride
@@ -1575,9 +1587,7 @@ func (t *Terminal) output() bool {
} }
if t.acceptNth != nil { if t.acceptNth != nil {
transform = func(item *Item) string { transform = func(item *Item) string {
tokens := Tokenize(item.AsString(t.ansi), t.delimiter) return item.acceptNth(t.ansi, t.delimiter, t.acceptNth)
transformed := t.acceptNth(tokens)
return StripLastDelimiter(transformed, t.delimiter)
} }
} }
found := len(t.selected) > 0 found := len(t.selected) > 0
@@ -2288,7 +2298,11 @@ func (t *Terminal) move(y int, x int, clear bool) {
} }
func (t *Terminal) truncateQuery() { func (t *Terminal) truncateQuery() {
t.input, _ = t.trimRight(t.input, maxPatternLength) // We're limiting the length of the query not to make fzf unresponsive when
// the user accidentally pastes a huge chunk of text. Therefore, we're not
// interested in the exact display width of the query. We just limit the
// number of runes.
t.input = t.input[:util.Min(len(t.input), maxPatternLength)]
t.cx = util.Constrain(t.cx, 0, len(t.input)) t.cx = util.Constrain(t.cx, 0, len(t.input))
} }
@@ -2332,15 +2346,18 @@ func (t *Terminal) placeCursor() {
if t.inputless { if t.inputless {
return return
} }
x := t.promptLen + t.queryLen[0]
if t.inputWindow != nil { if t.inputWindow != nil {
y := t.inputWindow.Height() - 1 y := t.inputWindow.Height() - 1
if t.layout == layoutReverse { if t.layout == layoutReverse {
y = 0 y = 0
} }
t.inputWindow.Move(y, t.promptLen+t.queryLen[0]) x = util.Min(x, t.inputWindow.Width()-1)
t.inputWindow.Move(y, x)
return return
} }
t.move(t.promptLine(), t.promptLen+t.queryLen[0], false) x = util.Min(x, t.window.Width()-1)
t.move(t.promptLine(), x, false)
} }
func (t *Terminal) printPrompt() { func (t *Terminal) printPrompt() {
@@ -2357,6 +2374,11 @@ func (t *Terminal) printPrompt() {
t.prompt() t.prompt()
before, after := t.updatePromptOffset() before, after := t.updatePromptOffset()
if len(before) == 0 && len(after) == 0 && len(t.ghost) > 0 {
w.CPrint(tui.ColInput.WithAttr(tui.Dim), t.ghost)
return
}
color := tui.ColInput color := tui.ColInput
if t.paused { if t.paused {
color = tui.ColDisabled color = tui.ColDisabled
@@ -2476,6 +2498,10 @@ func (t *Terminal) printInfoImpl() {
outputPrinter, outputLen = t.ansiLabelPrinter(output, &tui.ColInfo, false) outputPrinter, outputLen = t.ansiLabelPrinter(output, &tui.ColInfo, false)
} }
shiftLen := t.queryLen[0] + t.queryLen[1] + 1
if shiftLen == 1 && len(t.ghost) > 0 {
shiftLen = util.StringWidth(t.ghost)
}
switch t.infoStyle { switch t.infoStyle {
case infoDefault: case infoDefault:
if !move(line+1, 0, t.separatorLen == 0) { if !move(line+1, 0, t.separatorLen == 0) {
@@ -2489,9 +2515,9 @@ func (t *Terminal) printInfoImpl() {
return return
} }
case infoInlineRight: case infoInlineRight:
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1 pos = t.promptLen + shiftLen
case infoInline: case infoInline:
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1 pos = t.promptLen + shiftLen
printInfoPrefix() printInfoPrefix()
} }
@@ -2755,11 +2781,15 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
item := result.item item := result.item
_, selected := t.selected[item.Index()] _, selected := t.selected[item.Index()]
label := "" label := ""
extraWidth := 0
if t.jumping != jumpDisabled { if t.jumping != jumpDisabled {
if index < len(t.jumpLabels) { if index < len(t.jumpLabels) {
// Striped // Striped
current = index%2 == 0 current = index%2 == 0
label = t.jumpLabels[index:index+1] + strings.Repeat(" ", t.pointerLen-1) label = t.jumpLabels[index:index+1] + strings.Repeat(" ", util.Max(0, t.pointerLen-1))
if t.pointerLen == 0 {
extraWidth = 1
}
} }
} else if current { } else if current {
label = t.pointer label = t.pointer
@@ -2788,6 +2818,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1) maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1)
postTask := func(lineNum int, width int, wrapped bool, forceRedraw bool) { postTask := func(lineNum int, width int, wrapped bool, forceRedraw bool) {
width += extraWidth
if (current || selected) && t.highlightLine { if (current || selected) && t.highlightLine {
color := tui.ColSelected color := tui.ColSelected
if current { if current {
@@ -3752,6 +3783,8 @@ func parsePlaceholder(match string) (bool, string, placeholderFlags) {
flags.number = true flags.number = true
case 'f': case 'f':
flags.file = true flags.file = true
case 'r':
flags.raw = true
case 'q': case 'q':
flags.forceUpdate = true flags.forceUpdate = true
trimmed += string(char) trimmed += string(char)
@@ -3903,7 +3936,7 @@ func replacePlaceholder(params replacePlaceholderParams) (string, []string) {
return "''" return "''"
} }
return strconv.Itoa(int(n)) return strconv.Itoa(int(n))
case flags.file: case flags.file || flags.raw:
return item.AsString(params.stripAnsi) return item.AsString(params.stripAnsi)
default: default:
return params.executor.QuoteEntry(item.AsString(params.stripAnsi)) return params.executor.QuoteEntry(item.AsString(params.stripAnsi))
@@ -3945,7 +3978,7 @@ func replacePlaceholder(params replacePlaceholderParams) (string, []string) {
if !flags.preserveSpace { if !flags.preserveSpace {
str = strings.TrimSpace(str) str = strings.TrimSpace(str)
} }
if !flags.file { if !flags.file && !flags.raw {
str = params.executor.QuoteEntry(str) str = params.executor.QuoteEntry(str)
} }
return str return str
@@ -4599,11 +4632,7 @@ func (t *Terminal) Loop() error {
// U t.uiMutex | // U t.uiMutex |
t.uiMutex.Lock() t.uiMutex.Lock()
t.mutex.Lock() t.mutex.Lock()
printInfo := util.RunOnce(func() { info := false
if !t.resizeIfNeeded() {
t.printInfo()
}
})
for _, key := range keys { for _, key := range keys {
req := util.EventType(key) req := util.EventType(key)
value := (*events)[req] value := (*events)[req]
@@ -4611,16 +4640,15 @@ func (t *Terminal) Loop() error {
case reqPrompt: case reqPrompt:
t.printPrompt() t.printPrompt()
if t.infoStyle == infoInline || t.infoStyle == infoInlineRight { if t.infoStyle == infoInline || t.infoStyle == infoInlineRight {
printInfo() info = true
} }
case reqInfo: case reqInfo:
printInfo() info = true
case reqList: case reqList:
t.printList() t.printList()
currentIndex := t.currentIndex() currentIndex := t.currentIndex()
focusChanged := focusedIndex != currentIndex focusChanged := focusedIndex != currentIndex
info := false if focusChanged && focusedIndex >= 0 && t.track == trackCurrent {
if focusChanged && t.track == trackCurrent {
t.track = trackDisabled t.track = trackDisabled
info = true info = true
} }
@@ -4631,9 +4659,6 @@ func (t *Terminal) Loop() error {
info = true info = true
} }
} }
if info {
printInfo()
}
if focusChanged || version != t.version { if focusChanged || version != t.version {
version = t.version version = t.version
focusedIndex = currentIndex focusedIndex = currentIndex
@@ -4725,6 +4750,9 @@ func (t *Terminal) Loop() error {
return return
} }
} }
if info && !t.resizeIfNeeded() {
t.printInfo()
}
t.flush() t.flush()
t.mutex.Unlock() t.mutex.Unlock()
t.uiMutex.Unlock() t.uiMutex.Unlock()
@@ -4908,6 +4936,14 @@ func (t *Terminal) Loop() error {
return true return true
} }
doAction = func(a *action) bool { doAction = func(a *action) bool {
// Keep track of the current query before the action is executed,
// so we can restore it when the input section is hidden (--no-input).
// * By doing this, we don't have to add a conditional branch to each
// query modifying action.
// * We restore the query after each action instead of after a set of
// actions to allow changing the query even when the input is hidden
// e.g. fzf --no-input --bind 'space:show-input+change-query(foo)+hide-input'
currentInput := t.input
Action: Action:
switch a.t { switch a.t {
case actIgnore, actStart, actClick: case actIgnore, actStart, actClick:
@@ -4959,6 +4995,14 @@ func (t *Terminal) Loop() error {
case actInvalid: case actInvalid:
t.mutex.Unlock() t.mutex.Unlock()
return false return false
case actBracketedPasteBegin:
current := []rune(t.input)
t.pasting = &current
case actBracketedPasteEnd:
if t.pasting != nil {
queryChanged = string(t.input) != string(*t.pasting)
t.pasting = nil
}
case actTogglePreview, actShowPreview, actHidePreview: case actTogglePreview, actShowPreview, actHidePreview:
var act bool var act bool
switch a.t { switch a.t {
@@ -5094,7 +5138,12 @@ func (t *Terminal) Loop() error {
header = t.captureLines(a.a) header = t.captureLines(a.a)
} }
if t.changeHeader(header) { if t.changeHeader(header) {
req(reqHeader, reqList, reqPrompt, reqInfo) if t.headerWindow != nil {
// Need to resize header window
req(reqFullRedraw)
} else {
req(reqHeader, reqList, reqPrompt, reqInfo)
}
} else { } else {
req(reqHeader) req(reqHeader)
} }
@@ -5455,6 +5504,7 @@ func (t *Terminal) Loop() error {
t.scrollOff = t.window.Height() t.scrollOff = t.window.Height()
t.constrain() t.constrain()
t.scrollOff = soff t.scrollOff = soff
req(reqList)
case actJump: case actJump:
t.jumping = jumpEnabled t.jumping = jumpEnabled
req(reqJump) req(reqJump)
@@ -5829,7 +5879,7 @@ func (t *Terminal) Loop() error {
if me.Down { if me.Down {
mxCons := util.Constrain(mx-t.promptLen, 0, len(t.input)) mxCons := util.Constrain(mx-t.promptLen, 0, len(t.input))
if t.inputWindow == nil && my == t.promptLine() && mxCons >= 0 { if !t.inputless && t.inputWindow == nil && my == t.promptLine() && mxCons >= 0 {
// Prompt // Prompt
t.cx = mxCons + t.xoffset t.cx = mxCons + t.xoffset
} else if my >= min { } else if my >= min {
@@ -5913,6 +5963,30 @@ func (t *Terminal) Loop() error {
} }
} }
} }
case actChangeGhost, actTransformGhost:
ghost := a.a
if a.t == actTransformGhost {
ghost = t.captureLine(a.a)
}
t.ghost = ghost
if len(t.input) == 0 {
req(reqPrompt)
}
case actChangePointer, actTransformPointer:
pointer := a.a
if a.t == actTransformPointer {
pointer = t.captureLine(a.a)
}
length := uniseg.StringWidth(pointer)
if length <= 2 {
if length != t.pointerLen {
t.forceRerenderList()
}
t.pointer = pointer
t.pointerLen = length
t.pointerEmpty = strings.Repeat(" ", t.pointerLen)
req(reqList)
}
case actChangePreview: case actChangePreview:
if t.previewOpts.command != a.a { if t.previewOpts.command != a.a {
t.previewOpts.command = a.a t.previewOpts.command = a.a
@@ -5990,6 +6064,15 @@ func (t *Terminal) Loop() error {
if !processExecution(a.t) { if !processExecution(a.t) {
t.lastAction = a.t t.lastAction = a.t
} }
if t.inputless {
// Always just discard the change
t.input = currentInput
t.cx = len(t.input)
beof = false
} else if string(t.input) != string(currentInput) {
t.inputOverride = nil
}
return true return true
} }
@@ -6010,18 +6093,10 @@ func (t *Terminal) Loop() error {
} else if !doActions(actions) { } else if !doActions(actions) {
continue continue
} }
if t.inputless { if !t.inputless {
// Always just discard the change
t.input = previousInput
t.cx = len(t.input)
beof = false
} else {
t.truncateQuery() t.truncateQuery()
} }
queryChanged = string(previousInput) != string(t.input) queryChanged = queryChanged || t.pasting == nil && string(previousInput) != string(t.input)
if queryChanged {
t.inputOverride = nil
}
changed = changed || queryChanged changed = changed || queryChanged
if onChanges, prs := t.keymap[tui.Change.AsEvent()]; queryChanged && prs && !doActions(onChanges) { if onChanges, prs := t.keymap[tui.Change.AsEvent()]; queryChanged && prs && !doActions(onChanges) {
continue continue

View File

@@ -75,6 +75,14 @@ func TestReplacePlaceholder(t *testing.T) {
result = replacePlaceholderTest("echo {}", true, Delimiter{}, printsep, false, "query", items1) result = replacePlaceholderTest("echo {}", true, Delimiter{}, printsep, false, "query", items1)
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}") checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}")
// {r}, strip ansi
result = replacePlaceholderTest("echo {r}", true, Delimiter{}, printsep, false, "query", items1)
checkFormat("echo foo'bar baz")
// {r..}, strip ansi
result = replacePlaceholderTest("echo {r..}", true, Delimiter{}, printsep, false, "query", items1)
checkFormat("echo foo'bar baz")
// {}, with multiple items // {}, with multiple items
result = replacePlaceholderTest("echo {}", true, Delimiter{}, printsep, false, "query", items2) result = replacePlaceholderTest("echo {}", true, Delimiter{}, printsep, false, "query", items2)
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}}") checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}}")
@@ -565,7 +573,7 @@ func (item *Item) String() string {
} }
// Helper function to parse, execute and convert "text/template" to string. Panics on error. // Helper function to parse, execute and convert "text/template" to string. Panics on error.
func templateToString(format string, data interface{}) string { func templateToString(format string, data any) string {
bb := &bytes.Buffer{} bb := &bytes.Buffer{}
err := template.Must(template.New("").Parse(format)).Execute(bb, data) err := template.Must(template.New("").Parse(format)).Execute(bb, data)

View File

@@ -225,7 +225,9 @@ func StripLastDelimiter(str string, delimiter Delimiter) string {
locs := delimiter.regex.FindAllStringIndex(str, -1) locs := delimiter.regex.FindAllStringIndex(str, -1)
if len(locs) > 0 { if len(locs) > 0 {
lastLoc := locs[len(locs)-1] lastLoc := locs[len(locs)-1]
str = str[:lastLoc[0]] if lastLoc[1] == len(str) {
str = str[:lastLoc[0]]
}
} }
} }
return strings.TrimRightFunc(str, unicode.IsSpace) return strings.TrimRightFunc(str, unicode.IsSpace)

View File

@@ -84,35 +84,37 @@ func _() {
_ = x[CtrlAlt-73] _ = x[CtrlAlt-73]
_ = x[Invalid-74] _ = x[Invalid-74]
_ = x[Fatal-75] _ = x[Fatal-75]
_ = x[Mouse-76] _ = x[BracketedPasteBegin-76]
_ = x[DoubleClick-77] _ = x[BracketedPasteEnd-77]
_ = x[LeftClick-78] _ = x[Mouse-78]
_ = x[RightClick-79] _ = x[DoubleClick-79]
_ = x[SLeftClick-80] _ = x[LeftClick-80]
_ = x[SRightClick-81] _ = x[RightClick-81]
_ = x[ScrollUp-82] _ = x[SLeftClick-82]
_ = x[ScrollDown-83] _ = x[SRightClick-83]
_ = x[SScrollUp-84] _ = x[ScrollUp-84]
_ = x[SScrollDown-85] _ = x[ScrollDown-85]
_ = x[PreviewScrollUp-86] _ = x[SScrollUp-86]
_ = x[PreviewScrollDown-87] _ = x[SScrollDown-87]
_ = x[Resize-88] _ = x[PreviewScrollUp-88]
_ = x[Change-89] _ = x[PreviewScrollDown-89]
_ = x[BackwardEOF-90] _ = x[Resize-90]
_ = x[Start-91] _ = x[Change-91]
_ = x[Load-92] _ = x[BackwardEOF-92]
_ = x[Focus-93] _ = x[Start-93]
_ = x[One-94] _ = x[Load-94]
_ = x[Zero-95] _ = x[Focus-95]
_ = x[Result-96] _ = x[One-96]
_ = x[Jump-97] _ = x[Zero-97]
_ = x[JumpCancel-98] _ = x[Result-98]
_ = x[ClickHeader-99] _ = x[Jump-99]
_ = x[JumpCancel-100]
_ = x[ClickHeader-101]
} }
const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLEnterCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlDeleteCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltCtrlAltInvalidFatalMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancelClickHeader" const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLEnterCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlDeleteCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltCtrlAltInvalidFatalBracketedPasteBeginBracketedPasteEndMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancelClickHeader"
var _EventType_index = [...]uint16{0, 4, 9, 14, 19, 24, 29, 34, 39, 44, 47, 52, 57, 62, 67, 72, 77, 82, 87, 92, 97, 102, 107, 112, 117, 122, 127, 132, 135, 144, 154, 167, 183, 192, 201, 209, 218, 224, 230, 238, 240, 244, 248, 253, 257, 260, 266, 273, 282, 291, 301, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 333, 336, 339, 351, 356, 363, 370, 378, 388, 400, 412, 425, 428, 435, 442, 447, 452, 463, 472, 482, 492, 503, 511, 521, 530, 541, 556, 573, 579, 585, 596, 601, 605, 610, 613, 617, 623, 627, 637, 648} var _EventType_index = [...]uint16{0, 4, 9, 14, 19, 24, 29, 34, 39, 44, 47, 52, 57, 62, 67, 72, 77, 82, 87, 92, 97, 102, 107, 112, 117, 122, 127, 132, 135, 144, 154, 167, 183, 192, 201, 209, 218, 224, 230, 238, 240, 244, 248, 253, 257, 260, 266, 273, 282, 291, 301, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 333, 336, 339, 351, 356, 363, 370, 378, 388, 400, 412, 425, 428, 435, 442, 447, 466, 483, 488, 499, 508, 518, 528, 539, 547, 557, 566, 577, 592, 609, 615, 621, 632, 637, 641, 646, 649, 653, 659, 663, 673, 684}
func (i EventType) String() string { func (i EventType) String() string {
if i < 0 || i >= EventType(len(_EventType_index)-1) { if i < 0 || i >= EventType(len(_EventType_index)-1) {

View File

@@ -8,6 +8,7 @@ import (
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"sync"
"time" "time"
"unicode/utf8" "unicode/utf8"
@@ -95,7 +96,6 @@ func (r *LightRenderer) flushRaw(sequence string) {
// Light renderer // Light renderer
type LightRenderer struct { type LightRenderer struct {
closed *util.AtomicBool
theme *ColorTheme theme *ColorTheme
mouse bool mouse bool
forceBlack bool forceBlack bool
@@ -120,6 +120,7 @@ type LightRenderer struct {
showCursor bool showCursor bool
// Windows only // Windows only
mutex sync.Mutex
ttyinChannel chan byte ttyinChannel chan byte
inHandle uintptr inHandle uintptr
outHandle uintptr outHandle uintptr
@@ -151,7 +152,6 @@ func NewLightRenderer(ttyin *os.File, theme *ColorTheme, forceBlack bool, mouse
out = os.Stderr out = os.Stderr
} }
r := LightRenderer{ r := LightRenderer{
closed: util.NewAtomicBool(false),
theme: theme, theme: theme,
forceBlack: forceBlack, forceBlack: forceBlack,
mouse: mouse, mouse: mouse,
@@ -214,6 +214,7 @@ func (r *LightRenderer) Init() error {
} }
r.enableMouse() r.enableMouse()
r.csi("?2004h") // Enable bracketed paste mode
r.csi(fmt.Sprintf("%dA", r.MaxY()-1)) r.csi(fmt.Sprintf("%dA", r.MaxY()-1))
r.csi("G") r.csi("G")
r.csi("K") r.csi("K")
@@ -462,10 +463,11 @@ func (r *LightRenderer) escSequence(sz *int) Event {
} }
// Bracketed paste mode: \e[200~ ... \e[201~ // Bracketed paste mode: \e[200~ ... \e[201~
if len(r.buffer) > 5 && r.buffer[3] == '0' && (r.buffer[4] == '0' || r.buffer[4] == '1') && r.buffer[5] == '~' { if len(r.buffer) > 5 && r.buffer[3] == '0' && (r.buffer[4] == '0' || r.buffer[4] == '1') && r.buffer[5] == '~' {
// Immediately discard the sequence from the buffer and reread input *sz = 6
r.buffer = r.buffer[6:] if r.buffer[4] == '0' {
*sz = 0 return Event{BracketedPasteBegin, 0, nil}
return r.GetChar() }
return Event{BracketedPasteEnd, 0, nil}
} }
return Event{Invalid, 0, nil} // INS return Event{Invalid, 0, nil} // INS
case '3': case '3':
@@ -775,9 +777,8 @@ func (r *LightRenderer) Close() {
} }
r.disableMouse() r.disableMouse()
r.flush() r.flush()
r.closePlatform()
r.restoreTerminal() r.restoreTerminal()
r.closed.Set(true) r.closePlatform()
} }
func (r *LightRenderer) Top() int { func (r *LightRenderer) Top() int {

View File

@@ -18,6 +18,7 @@ const (
var ( var (
consoleFlagsInput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_EXTENDED_FLAGS) consoleFlagsInput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_EXTENDED_FLAGS)
consoleFlagsOutput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING | windows.ENABLE_PROCESSED_OUTPUT | windows.DISABLE_NEWLINE_AUTO_RETURN) consoleFlagsOutput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING | windows.ENABLE_PROCESSED_OUTPUT | windows.DISABLE_NEWLINE_AUTO_RETURN)
counter = uint64(0)
) )
// IsLightRendererSupported checks to see if the Light renderer is supported // IsLightRendererSupported checks to see if the Light renderer is supported
@@ -61,27 +62,11 @@ func (r *LightRenderer) initPlatform() error {
} }
r.inHandle = uintptr(inHandle) r.inHandle = uintptr(inHandle)
r.setupTerminal()
// channel for non-blocking reads. Buffer to make sure // channel for non-blocking reads. Buffer to make sure
// we get the ESC sets: // we get the ESC sets:
r.ttyinChannel = make(chan byte, 1024) r.ttyinChannel = make(chan byte, 1024)
// the following allows for non-blocking IO. r.setupTerminal()
// syscall.SetNonblock() is a NOOP under Windows.
go func() {
fd := int(r.inHandle)
b := make([]byte, 1)
for !r.closed.Get() {
// HACK: if run from PSReadline, something resets ConsoleMode to remove ENABLE_VIRTUAL_TERMINAL_INPUT.
_ = windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
_, err := util.Read(fd, b)
if err == nil {
r.ttyinChannel <- b[0]
}
}
}()
return nil return nil
} }
@@ -100,18 +85,42 @@ func openTtyOut() (*os.File, error) {
return os.Stderr, nil return os.Stderr, nil
} }
func (r *LightRenderer) setupTerminal() error { func (r *LightRenderer) setupTerminal() {
if err := windows.SetConsoleMode(windows.Handle(r.outHandle), consoleFlagsOutput); err != nil { windows.SetConsoleMode(windows.Handle(r.outHandle), consoleFlagsOutput)
return err windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
}
return windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput) // The following allows for non-blocking IO.
// syscall.SetNonblock() is a NOOP under Windows.
current := counter
go func() {
fd := int(r.inHandle)
b := make([]byte, 1)
for {
if _, err := util.Read(fd, b); err == nil {
r.mutex.Lock()
// This condition prevents the goroutine from running after the renderer
// has been closed or paused.
if current != counter {
r.mutex.Unlock()
break
}
r.ttyinChannel <- b[0]
// HACK: if run from PSReadline, something resets ConsoleMode to remove ENABLE_VIRTUAL_TERMINAL_INPUT.
windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
r.mutex.Unlock()
}
}
}()
} }
func (r *LightRenderer) restoreTerminal() error { func (r *LightRenderer) restoreTerminal() {
if err := windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput); err != nil { r.mutex.Lock()
return err counter++
} // We're setting ENABLE_VIRTUAL_TERMINAL_INPUT to allow escape sequences to be read during 'execute'.
return windows.SetConsoleMode(windows.Handle(r.outHandle), r.origStateOutput) // e.g. fzf --bind 'enter:execute:less {}'
windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput|windows.ENABLE_VIRTUAL_TERMINAL_INPUT)
windows.SetConsoleMode(windows.Handle(r.outHandle), r.origStateOutput)
r.mutex.Unlock()
} }
func (r *LightRenderer) Size() TermSize { func (r *LightRenderer) Size() TermSize {

View File

@@ -197,6 +197,7 @@ func (r *FullscreenRenderer) initScreen() error {
if e = s.Init(); e != nil { if e = s.Init(); e != nil {
return e return e
} }
s.EnablePaste()
if r.mouse { if r.mouse {
s.EnableMouse() s.EnableMouse()
} else { } else {
@@ -266,6 +267,11 @@ func (r *FullscreenRenderer) Size() TermSize {
func (r *FullscreenRenderer) GetChar() Event { func (r *FullscreenRenderer) GetChar() Event {
ev := _screen.PollEvent() ev := _screen.PollEvent()
switch ev := ev.(type) { switch ev := ev.(type) {
case *tcell.EventPaste:
if ev.Start() {
return Event{BracketedPasteBegin, 0, nil}
}
return Event{BracketedPasteEnd, 0, nil}
case *tcell.EventResize: case *tcell.EventResize:
// Ignore the first resize event // Ignore the first resize event
// https://github.com/gdamore/tcell/blob/v2.7.0/TUTORIAL.md?plain=1#L18 // https://github.com/gdamore/tcell/blob/v2.7.0/TUTORIAL.md?plain=1#L18

View File

@@ -10,7 +10,7 @@ import (
"github.com/junegunn/fzf/src/util" "github.com/junegunn/fzf/src/util"
) )
func assert(t *testing.T, context string, got interface{}, want interface{}) bool { func assert(t *testing.T, context string, got any, want any) bool {
if got == want { if got == want {
return true return true
} else { } else {

View File

@@ -103,6 +103,8 @@ const (
Invalid Invalid
Fatal Fatal
BracketedPasteBegin
BracketedPasteEnd
Mouse Mouse
DoubleClick DoubleClick

View File

@@ -184,6 +184,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) TrimSuffix(runes []rune) { func (chars *Chars) TrimSuffix(runes []rune) {
lastIdx := len(chars.slice) lastIdx := len(chars.slice)
firstIdx := lastIdx - len(runes) firstIdx := lastIdx - len(runes)
@@ -289,9 +294,10 @@ func (chars *Chars) Lines(multiLine bool, maxLines int, wrapCols int, wrapSignWi
line = line[:len(line)-1] line = line[:len(line)-1]
} }
hasWrapSign := false
for { for {
cols := wrapCols cols := wrapCols
if len(wrapped) > 0 { if hasWrapSign {
cols -= wrapSignWidth cols -= wrapSignWidth
} }
_, overflowIdx := RunesWidth(line, 0, tabstop, cols) _, overflowIdx := RunesWidth(line, 0, tabstop, cols)
@@ -304,9 +310,11 @@ func (chars *Chars) Lines(multiLine bool, maxLines int, wrapCols int, wrapSignWi
return wrapped, true return wrapped, true
} }
wrapped = append(wrapped, line[:overflowIdx]) wrapped = append(wrapped, line[:overflowIdx])
hasWrapSign = true
line = line[overflowIdx:] line = line[overflowIdx:]
continue continue
} }
hasWrapSign = false
// Restore trailing '\n' // Restore trailing '\n'
if newline { if newline {

View File

@@ -76,7 +76,7 @@ func TestCharsLines(t *testing.T) {
check(true, 100, 3, 1, 1, 8, false) check(true, 100, 3, 1, 1, 8, false)
// With wrap sign (3 + 2) // With wrap sign (3 + 2)
check(true, 100, 3, 2, 1, 12, false) check(true, 100, 3, 2, 1, 10, false)
// With wrap sign (3 + 2) and no multi-line // With wrap sign (3 + 2) and no multi-line
check(false, 100, 3, 2, 1, 13, false) check(false, 100, 3, 2, 1, 13, false)

View File

@@ -6,7 +6,7 @@ import "sync"
type EventType int type EventType int
// Events is a type that associates EventType to any data // Events is a type that associates EventType to any data
type Events map[EventType]interface{} type Events map[EventType]any
// EventBox is used for coordinating events // EventBox is used for coordinating events
type EventBox struct { type EventBox struct {
@@ -36,7 +36,7 @@ func (b *EventBox) Wait(callback func(*Events)) {
} }
// Set turns on the event type on the box // Set turns on the event type on the box
func (b *EventBox) Set(event EventType, value interface{}) { func (b *EventBox) Set(event EventType, value any) {
b.cond.L.Lock() b.cond.L.Lock()
b.events[event] = value b.events[event] = value
if _, found := b.ignore[event]; !found { if _, found := b.ignore[event]; !found {

View File

@@ -238,6 +238,11 @@ class TestCore < TestInteractive
assert_equal %w[5555 55], fzf_output_lines assert_equal %w[5555 55], fzf_output_lines
end end
def test_select_1_accept_nth
tmux.send_keys "seq 1 100 | #{fzf(:with_nth, '..,..', :print_query, :q, 5555, :'1', :accept_nth, '"{1} // {1}"')}", :Enter
assert_equal ['5555', '55 // 55'], fzf_output_lines
end
def test_exit_0 def test_exit_0
tmux.send_keys "seq 1 100 | #{fzf(:with_nth, '..,..', :print_query, :q, 555_555, :'0')}", :Enter tmux.send_keys "seq 1 100 | #{fzf(:with_nth, '..,..', :print_query, :q, 555_555, :'0')}", :Enter
assert_equal %w[555555], fzf_output_lines assert_equal %w[555555], fzf_output_lines
@@ -827,6 +832,24 @@ class TestCore < TestInteractive
tmux.until { |lines| assert(lines.any? { it.include?('jump cancelled at 3') }) } tmux.until { |lines| assert(lines.any? { it.include?('jump cancelled at 3') }) }
end end
def test_jump_no_pointer
tmux.send_keys "seq 100 | #{FZF} --pointer= --jump-labels 12345 --bind ctrl-j:jump", :Enter
tmux.until { |lines| assert_equal 100, lines.match_count }
tmux.send_keys 'C-j'
tmux.until { |lines| assert_equal '5 5', lines[-7] }
tmux.send_keys 'C-c'
tmux.until { |lines| assert_equal ' 5', lines[-7] }
end
def test_jump_no_pointer_no_marker
tmux.send_keys "seq 100 | #{FZF} --pointer= --marker= --jump-labels 12345 --bind ctrl-j:jump", :Enter
tmux.until { |lines| assert_equal 100, lines.match_count }
tmux.send_keys 'C-j'
tmux.until { |lines| assert_equal '55', lines[-7] }
tmux.send_keys 'C-c'
tmux.until { |lines| assert_equal '5', lines[-7] }
end
def test_pointer def test_pointer
tmux.send_keys "seq 10 | #{fzf("--pointer '>>'")}", :Enter tmux.send_keys "seq 10 | #{fzf("--pointer '>>'")}", :Enter
# Assert that specified pointer is displayed # Assert that specified pointer is displayed
@@ -1773,12 +1796,139 @@ class TestCore < TestInteractive
end end
end end
def test_accept_nth_template def test_accept_nth_regex_delimiter_strip_last
tmux.send_keys %(echo "foo ,bar,baz" | #{FZF} -d, --accept-nth '1st: {1}, 3rd: {3}, 2nd: {2}' --sync --bind start:accept > #{tempname}), :Enter tmux.send_keys %((echo "foo:,bar:,baz"; echo "foo:,bar:,baz:,qux:,") | #{FZF} --multi --delimiter='[:,]+' --accept-nth 2.. --sync --bind 'load:select-all+accept' > #{tempname}), :Enter
wait do wait do
assert_path_exists tempname assert_path_exists tempname
# Last delimiter and the whitespaces are removed # Last delimiter and the whitespaces are removed
assert_equal ['1st: foo, 3rd: baz, 2nd: bar'], File.readlines(tempname, chomp: true) assert_equal ['bar:,baz', 'bar:,baz:,qux'], File.readlines(tempname, chomp: true)
end end
end end
def test_accept_nth_template
tmux.send_keys %(echo "foo ,bar,baz" | #{FZF} -d, --accept-nth '[{n}] 1st: {1}, 3rd: {3}, 2nd: {2}' --sync --bind start:accept > #{tempname}), :Enter
wait do
assert_path_exists tempname
# Last delimiter and the whitespaces are removed
assert_equal ['[0] 1st: foo, 3rd: baz, 2nd: bar'], File.readlines(tempname, chomp: true)
end
end
def test_ghost
tmux.send_keys %(seq 100 | #{FZF} --prompt 'X ' --ghost 'Type in query ...' --bind 'space:change-ghost:Y Z' --bind 'enter:transform-ghost:echo Z Y'), :Enter
tmux.until do |lines|
assert_equal 100, lines.match_count
assert_includes lines, 'X Type in query ...'
end
tmux.send_keys '100'
tmux.until do |lines|
assert_equal 1, lines.match_count
assert_includes lines, 'X 100'
end
tmux.send_keys 'C-u'
tmux.until do |lines|
assert_equal 100, lines.match_count
assert_includes lines, 'X Type in query ...'
end
tmux.send_keys :Space
tmux.until { |lines| assert_includes lines, 'X Y Z' }
tmux.send_keys :Enter
tmux.until { |lines| assert_includes lines, 'X Z Y' }
end
def test_ghost_inline
tmux.send_keys %(seq 100 | #{FZF} --info 'inline: Y' --no-separator --prompt 'X ' --ghost 'Type in query ...'), :Enter
tmux.until do |lines|
assert_includes lines, 'X Type in query ... Y100/100'
end
tmux.send_keys '100'
tmux.until do |lines|
assert_includes lines, 'X 100 Y1/100'
end
tmux.send_keys 'C-u'
tmux.until do |lines|
assert_includes lines, 'X Type in query ... Y100/100'
end
end
def test_offset_middle
tmux.send_keys %(seq 1000 | #{FZF} --sync --no-input --reverse --height 5 --scroll-off 0 --bind space:offset-middle), :Enter
line = nil
tmux.until { |lines| line = lines.index('> 1') }
tmux.send_keys :PgDn
tmux.until { |lines| assert_includes lines[line + 4], "> 5" }
tmux.send_keys :Space
tmux.until { |lines| assert_includes lines[line + 2], "> 5" }
end
def test_no_input_query
tmux.send_keys %(seq 1000 | #{FZF} --no-input --query 555 --bind space:toggle-input), :Enter
tmux.until { |lines| assert_includes lines, '> 555' }
tmux.send_keys :Space
tmux.until do |lines|
assert_equal 1, lines.match_count
assert_includes lines, '> 555'
end
end
def test_no_input_change_query
tmux.send_keys %(seq 1000 | #{FZF} --multi --query 999 --no-input --bind 'enter:show-input+change-query(555)+hide-input,space:change-query(555)+select'), :Enter
tmux.until { |lines| assert_includes lines, '> 999' }
tmux.send_keys :Space
tmux.until do |lines|
assert_includes lines, '>>999'
refute_includes lines, '> 555'
end
tmux.send_keys :Enter
tmux.until do |lines|
refute_includes lines, '>>999'
assert_includes lines, '> 555'
end
end
def test_search_override_query_in_no_input_mode
tmux.send_keys %(seq 1000 | #{FZF} --sync --no-input --bind 'enter:show-input+change-query(555)+hide-input+search(999),space:search(111)+show-input+change-query(777)'), :Enter
tmux.until { |lines| assert_includes lines, '> 1' }
tmux.send_keys :Enter
tmux.until { |lines| assert_includes lines, '> 999' }
tmux.send_keys :Space
tmux.until { |lines| assert_includes lines, '> 777' }
end
def test_change_pointer
tmux.send_keys %(seq 2 | #{FZF} --bind 'a:change-pointer(a),b:change-pointer(bb),c:change-pointer(),d:change-pointer(ddd)'), :Enter
tmux.until { |lines| assert_includes lines, '> 1' }
tmux.send_keys 'a'
tmux.until { |lines| assert_includes lines, 'a 1' }
tmux.send_keys 'b'
tmux.until { |lines| assert_includes lines, 'bb 1' }
tmux.send_keys 'c'
tmux.until { |lines| assert_includes lines, ' 1' }
tmux.send_keys 'd'
tmux.until { |lines| refute_includes lines, 'ddd 1' }
tmux.send_keys :Up
tmux.until { |lines| assert_includes lines, ' 2' }
end
def test_transform_pointer
tmux.send_keys %(seq 2 | #{FZF} --bind 'a:transform-pointer(echo a),b:transform-pointer(echo bb),c:transform-pointer(),d:transform-pointer(echo ddd)'), :Enter
tmux.until { |lines| assert_includes lines, '> 1' }
tmux.send_keys 'a'
tmux.until { |lines| assert_includes lines, 'a 1' }
tmux.send_keys 'b'
tmux.until { |lines| assert_includes lines, 'bb 1' }
tmux.send_keys 'c'
tmux.until { |lines| assert_includes lines, ' 1' }
tmux.send_keys 'd'
tmux.until { |lines| refute_includes lines, 'ddd 1' }
tmux.send_keys :Up
tmux.until { |lines| assert_includes lines, ' 2' }
end
def test_change_header_on_header_window
tmux.send_keys %(seq 100 | #{FZF} --list-border --input-border --bind 'start:change-header(foo),space:change-header(bar)'), :Enter
tmux.until { |lines| assert lines.any_include?('foo') }
tmux.send_keys :Space
tmux.until { |lines| assert lines.any_include?('bar') }
end
end end

View File

@@ -482,4 +482,36 @@ class TestFish < TestBase
tmux.send_keys "set -g #{name} '#{val}'", :Enter tmux.send_keys "set -g #{name} '#{val}'", :Enter
tmux.prepare tmux.prepare
end end
def test_ctrl_r_multi
tmux.send_keys ':', :Enter
tmux.send_keys 'echo "foo', :Enter, 'bar"', :Enter
tmux.prepare
tmux.send_keys 'echo "bar', :Enter, 'foo"', :Enter
tmux.prepare
tmux.send_keys 'C-l', 'C-r'
block = <<~BLOCK
echo "foo
bar"
echo "bar
foo"
BLOCK
tmux.until do |lines|
block.lines.each_with_index do |line, idx|
assert_includes lines[-6 + idx], line.chomp
end
end
tmux.send_keys :BTab, :BTab
tmux.until { |lines| assert_includes lines[-2], '(2)' }
tmux.send_keys :Enter
block = <<~BLOCK
echo "bar
foo"
echo "foo
bar"
BLOCK
tmux.until do |lines|
assert_equal block.lines.map(&:chomp), lines
end
end
end end