Compare commits

..

89 Commits

Author SHA1 Message Date
Junegunn Choi
d3311d9f43 0.43.0 2023-10-15 01:56:05 +09:00
LangLangBart
3e1735b06e [zsh] Fix 'emulate: unknown argument -o' error on old zsh (#3465)
Fix #2094
2023-10-14 17:41:01 +09:00
Junegunn Choi
de7ef7eace [fzf-tmux] Fix 'empty command' error on tmux 3.2
Fix #3474
2023-10-13 20:13:28 +09:00
Christoph Anton Mitterer
7e89458a3b [fish] exit as well when called from non-interactive shell (#3467)
Just like with the other shells, exit fish to, if called from a non-interactive
shell.

We cannot use `return`, as older versions of fish (namely < 3.4.0) did not
support to use `return` in `.`-scripts (this was only added with fish commit
3359e5d2e9bcbf19d1652636c8e448a6889302ae).

Unlike in POSIX, fish’s `exit` is however documented to no cause the calling
shell to exit when executed in a sourced script (see:
0f70b2c0d3/doc_src/cmds/exit.rst (L20)
)

Signed-off-by: Christoph Anton Mitterer <mail@christoph.anton.mitterer.name>
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2023-10-13 01:06:55 +09:00
Junegunn Choi
f212bafe46 [bash] Remove implicit bash-completion dependency 2023-10-13 01:00:43 +09:00
Christoph Anton Mitterer
86fe40708b [bash] statically define __fzf_list_hosts() with either method
When bash-completion (and thus `_known_hosts_real()`) is / is not available this
will typically not change during the lifetime of a shell.

The only exception is if the user would unset `_known_hosts_real()`, but well,
that would be his problem.

So we can easily define `__fzf_list_hosts()` either using `_known_hosts_real()`
or using the old code, and avoid checking every time whether
`_known_hosts_real()` is defined.

Signed-off-by: Christoph Anton Mitterer <mail@christoph.anton.mitterer.name>
2023-10-12 20:44:25 +09:00
Christoph Anton Mitterer
d718747c5b [bash] try to use bash-completions’s _known_hosts_real() for getting hostnames
If defined, use bash-completions’s `_known_hosts_real()`-function to create the
list of hostnames.
This obviously requires bash-completion to be sourced before fzf.

If not defined, fall back to the previous code.

Signed-off-by: Christoph Anton Mitterer <mail@christoph.anton.mitterer.name>
2023-10-12 20:44:25 +09:00
Christoph Anton Mitterer
46ee9ac41c [shell] make __fzf_list_hosts() definable by the user
Just like it’s already done for `_fzf_compgen_path()` and `_fzf_compgen_dir()`
allow a user to easily define his own version of `__fzf_list_hosts()`.

Also add some documentation on the expected “interface” of such custom function.

Signed-off-by: Christoph Anton Mitterer <mail@christoph.anton.mitterer.name>
2023-10-12 20:44:25 +09:00
Christoph Anton Mitterer
f1d306feab [shell] move username prefixing code where needed
`__fzf_list_hosts()` seems like a function a user may want to override with some
custom code.
For that reason it should be kept as simple as possible, that is printing only
hostnames, one per line, optionally in some sorting.

The handling of adding a `username@` (which is then the same for each line), if
any, would unnecessarily complicate that for people who want to override the
function.
Therefore this commit moves that to the places where it's actually used (as of
now only `_fzf_complete_ssh()`).

This also saves any such handling for `_fzf_host_completion()`, where this isn’t
needed at all.

Right now it comes at a cost, namely an extra invocation of `awk` in the
`_fzf_complete_ssh()`-case.
However, it should be easily possible to improve `__fzf_list_hosts()` to no
longer need the final `awk` in the pipeline there.

Signed-off-by: Christoph Anton Mitterer <mail@christoph.anton.mitterer.name>
2023-10-12 20:44:25 +09:00
Christoph Anton Mitterer
2d0db98e83 [shell] don’t print error on non-existent SSH files
Signed-off-by: Christoph Anton Mitterer <mail@christoph.anton.mitterer.name>
2023-10-12 20:44:25 +09:00
Junegunn Choi
3df06a1c68 Fix offset-up and offset-down with --layout=reverse (#3456) 2023-10-12 19:14:03 +09:00
dependabot[bot]
a8f9432a3a Bump golang.org/x/term from 0.12.0 to 0.13.0 (#3469)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.12.0 to 0.13.0.
- [Commits](https://github.com/golang/term/compare/v0.12.0...v0.13.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-11 13:17:47 +09:00
Christoph Anton Mitterer
561e0b04a8 [bash] Use command to “protect” further commands (#3462)
This commit causes all simple commands that are not built-ins or functions to be
invoked via `command` in order to protect them from alias substitution or from
accidentally taking functions of the same name.

It was decided to not “protect” `fzf` and `fzf-tmux` for now.
Maybe a better solution should be implemented for that in the future.

Signed-off-by: Christoph Anton Mitterer <mail@christoph.anton.mitterer.name>
2023-10-11 13:07:47 +09:00
Junegunn Choi
404b6a864b Add offset-up and offset-down
# Scrolling will behave similarly to CTRL-E and CTRL-Y of vim
  fzf --bind scroll-up:offset-up,scroll-down:offset-down \
      --bind ctrl-y:offset-up,ctrl-e:offset-down \
      --scroll-off=5

Close #3456
2023-10-11 12:53:51 +09:00
Christoph Anton Mitterer
4feaf31225 [bash] bring fzf’s own bash completion up to date (#3471)
* [bash] bring fzf’s own bash completion up to date

This orders and groups completed options and values in just as they appear in
the code respectively, for some option values, as they’d be printed in the
`--help`-output.

It does not add support for completion of `:` right after values that support an
optional `:some-further-value` postfix.
Neither does it add support for the `--option=value`-style.

Signed-off-by: Christoph Anton Mitterer <mail@christoph.anton.mitterer.name>

* [bash] drop unnecessary code in handling `history`

Presumably, the dropped code is not needed for any effect, thus drop it.

Signed-off-by: Christoph Anton Mitterer <mail@christoph.anton.mitterer.name>

---------

Signed-off-by: Christoph Anton Mitterer <mail@christoph.anton.mitterer.name>
2023-10-11 10:54:50 +09:00
Junegunn Choi
391aa14845 Add mouse events for --bind
Close #3473
2023-10-11 09:42:12 +09:00
Junegunn Choi
a0d61b4c37 [install] Remove redundant interactiveness check
Related #3449

/cc @calestyo
2023-10-09 10:00:55 +09:00
Junegunn Choi
2952737755 Update README: Experimental support for Kitty graphics protocol 2023-10-09 01:29:03 +09:00
Christoph Anton Mitterer
f103aa4753 Improve interactiveness checks (#3449)
* [bash] return instead of not executing an if-block, when non-interactive

This should keep the code more readable, be less error prone (accidentally doing
something outside the if-block and aligns the code with what’s already done for
zsh.

`0` is returned, because it shall not be considered an error when the script is
(accidentally) sourced from a non-interactive shell.

If executed as a script (rather than sourced), the results are not specified by
POSIX but depend on the shell, with bash giving an error in that case.

Signed-off-by: Christoph Anton Mitterer <mail@christoph.anton.mitterer.name>

* [shell] exit immediately when called from non-interactive shell

The shell execution environment shouldn’t be modified at all, when called from a
non-interactive shell.

It shall be noted that the current check may become error prone for bash, namely
in case there should ever be a differentiation between `i` and `I` in the
special variable `-` and bash’s `nocasematch`-shell-option be used.

Signed-off-by: Christoph Anton Mitterer <mail@christoph.anton.mitterer.name>
2023-10-09 01:19:28 +09:00
junegunn
884856023a Deploying to master from @ junegunn/fzf@d8188fce7b 🚀 2023-10-08 00:01:43 +00:00
Junegunn Choi
d8188fce7b Experimental support for Kitty image protocol in preview window
Close #3228

* Works inside and outside of tmux
* There is a problem where fzf unnecessarily displays the scroll offset
  indicator at the topbright of the screen when the image just fits the
  preview window. This is because `kitty icat` generates an extra line
  after the image area.

    # A 5-row images; an extra row at the end confuses fzf
    ["\e_Ga ... \e[9C􎻮̅̅ࠪ􎻮̅̍ࠪ􎻮̅̎ࠪ􎻮̅̐ࠪ􎻮̅̒ࠪ􎻮̅̽ࠪ􎻮̅̾ࠪ􎻮̅̿ࠪ􎻮̅͆ࠪ􎻮̅͊ࠪ􎻮̅͋ࠪ\n",
     "\r\e[9C􎻮̍̅ࠪ􎻮̍̍ࠪ􎻮̍̎ࠪ􎻮̍̐ࠪ􎻮̍̒ࠪ􎻮̍̽ࠪ􎻮̍̾ࠪ􎻮̍̿ࠪ􎻮̍͆ࠪ􎻮̍͊ࠪ􎻮̍͋ࠪ\n",
     "\r\e[9C􎻮̎̅ࠪ􎻮̎̍ࠪ􎻮̎̎ࠪ􎻮̎̐ࠪ􎻮̎̒ࠪ􎻮̎̽ࠪ􎻮̎̾ࠪ􎻮̎̿ࠪ􎻮̎͆ࠪ􎻮̎͊ࠪ􎻮̎͋ࠪ\n",
     "\r\e[9C􎻮̐̅ࠪ􎻮̐̍ࠪ􎻮̐̎ࠪ􎻮̐̐ࠪ􎻮̐̒ࠪ􎻮̐̽ࠪ􎻮̐̾ࠪ􎻮̐̿ࠪ􎻮̐͆ࠪ􎻮̐͊ࠪ􎻮̐͋ࠪ\n",
     "\r\e[9C􎻮̒̅ࠪ􎻮̒̍ࠪ􎻮̒̎ࠪ􎻮̒̐ࠪ􎻮̒̒ࠪ􎻮̒̽ࠪ􎻮̒̾ࠪ􎻮̒̿ࠪ􎻮̒͆ࠪ􎻮̒͊ࠪ􎻮̒͋ࠪ\n",
     "\r\e[39m\e8"]

* Example:

  fzf --preview='
    if file --mime-type {} | grep -qF 'image/'; then
      # --transfer-mode=memory is the fastest option but if you want fzf to be able
      # to redraw the image on terminal resize or on 'change-preview-window',
      # you need to use --transfer-mode=stream.
      kitty icat --clear --transfer-mode=memory --stdin=no --place=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}@0x0 {}
    else
      bat --color=always {}
    fi
  '
2023-10-07 18:36:33 +09:00
step
0f15f1ab73 [bash] Improve mawk detection (#3463)
* Use the all-compatible mawk `-W version` option.
  https://github.com/junegunn/fzf/pull/3313#issuecomment-1747934690.
* Run the command and not a function consistently with #3462.

The version check bash code relies on the following mawk source code,
extracted from mawk 1.3.4 20230322.

```
version.c:
18-  #include "init.h"
19-  #include "patchlev.h"
20-
21:  #define	 VERSION_STRING	 \
22-    "mawk %d.%d%s %s\n\
23-  Copyright 2008-2022,2023, Thomas E. Dickey\n\
24-  Copyright 1991-1996,2014, Michael D. Brennan\n\n"
....
30-  void
31-  print_version(FILE *fp)
32-  {
33:      fprintf(fp, VERSION_STRING, PATCH_BASE, PATCH_LEVEL, PATCH_STRING, DATE_STRING);
34-      fflush(fp);
35-
36-  #define SHOW_RANDOM "random-funcs:"

patchlev.h:
13-  /*
14-   * $MawkId: patchlev.h,v 1.128 2023/03/23 00:23:57 tom Exp $
15-   */
16:  #define  PATCH_BASE	1
17-  #define  PATCH_LEVEL	3
18-  #define  PATCH_STRING	".4"
19-  #define  DATE_STRING    "20230322"
```

Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2023-10-07 17:26:46 +09:00
Junegunn Choi
488a236b7a [shell] Avoid side-effects during eval (#3459)
Take two.

* Avoid eval if the prefix contains `:=`
    * This is not to evaluate variable assignment. e.g. ${FOO:=BAR}
* [zsh] Prevent `>(...)` form
* Suppress error message from prefix evaluation
* Stop completion when prefix evaluation failed

Thanks to @calestyo
2023-10-04 21:43:11 +09:00
Christoph Anton Mitterer
e833823e15 [bash] Don’t print function definition when checking for existence (#3448)
When just checking whether a function is already defined or not, it’s not
necessary to print out it’s definition (should it be defined).

bash’s `declare` provides the `-F`-option (which implies `-f`), which should
give a minor performance improvement

Signed-off-by: Christoph Anton Mitterer <mail@christoph.anton.mitterer.name>
2023-10-02 21:02:35 +09:00
Junegunn Choi
ee4ba104e7 [completion] Prevent running a command during 'eval'
Do not attempt to provide fuzzy completion if the prefix contains a
pattern that may start an arbitraty command.

* $(...)
* `...`
* <(...)

Close #3459
2023-10-02 20:40:49 +09:00
junegunn
4fdc08295b Deploying to master from @ junegunn/fzf@a3ff49aaf1 🚀 2023-10-01 00:01:42 +00:00
Junegunn Choi
a3ff49aaf1 [bash] CTRL-R on bash 3: Use backticks to avoid delay
e0b29e437b
2023-09-27 09:16:16 +09:00
Junegunn Choi
76364ea767 Remove unnecessary escaping in the default command 2023-09-24 13:23:40 +09:00
Christoph Anton Mitterer
8eec50d764 [shell] don’t needlessly escape . in shell pattern
`find`’s `-path`-option is described to use shell patterns (i.e. POSIX’ pattern
matching notation).

In that, `.` is not a special character, thus escaping it shouldn’t be
necessary.

Signed-off-by: Christoph Anton Mitterer <mail@christoph.anton.mitterer.name>
2023-09-24 13:23:40 +09:00
junegunn
32b659b346 Deploying to master from @ junegunn/fzf@00809909ae 🚀 2023-09-24 00:01:56 +00:00
Junegunn Choi
00809909ae Update CHANGELOG 2023-09-22 21:59:47 +09:00
step
9f7684f6fe [bash] History, use perl if installed otherwise awk (#3313)
While awk is POSIX, perl isn't pre-installed on all *nix flavors.
This commit eliminates the mandatory dependency on perl by using awk
when perl is not available.

Related: #3295, #3309, #3310.

Test suite passed:
* `make error` all test sections 'PASS'
* `make docker-test` 215 runs, 1884 assertions, 0 failures, 0 errors, 0 skips.

Manually tested in the following environments:
* Linux amd64 with bash 3.2, 4.4, 5.2; gawk -P, one true awk, mawk, busybox awk.
* macOS Catalina, bash 3.2, macOS awk 20070501.

**Performance comparison:**

Mawk turned out the fastest, then perl.
One true awk's implementation should be the closest to macOS awk.
Test data: 230 KB history, 15102 entries, including multi-line and duplicates.
Linux, bash 4.4. Times in milliseconds.

| Command                 | Mean | Min  | Max  | Relative |
| :---                    | ---: | ---: | ---: | -------: |
| `mawk 1.3.4`            | 22.9 | 22.3 | 25.6 | **1.00** |
| `perl 5.26.1`           | 34.3 | 33.6 | 35.1 |   1.49   |
| `one true awk 20221215` | 41.9 | 40.6 | 46.3 |   1.83   |
| `gawk 5.1.0`            | 46.1 | 44.4 | 50.3 |   2.01   |
| `busybox awk 1.27.0`    | 64.8 | 63.2 | 70.0 |   2.82   |

**Other Notes**

A bug affects bash, which fails restoring a saved multi-line history entry as a single entry. Bug fixed in version 5.0.[^1]

While developing this PR I discovered two unsubmitted issues affecting the current perl script. The output stream ends with `$'\n\0000'` instead of `$'\0000'`. Because of this, the script does not deduplicate a duplicated entry located at the end of the history list; therefore fzf displays two identical (not necessarily adjacent) entries. A minor point about the first issue is that the top fzf entry ends with a dangling line feed symbol, which is visible in the terminal.

[^1]: ec8113b986/CHANGES (L1511)
  To enable: `shopt -s cmdhist lithist; HISTTIMEFORMAT='%F %T '`.
2023-09-22 17:37:34 +09:00
Junegunn Choi
2bed7d370e [shell] Use --scheme=path when appropriate
Without the option, you may get suboptimal results if you have many
paths with spaces in their names.

e.g. https://github.com/junegunn/fzf/issues/2909#issuecomment-1207690770

Close #3433
2023-09-19 13:39:57 +09:00
dependabot[bot]
d2b852f7cb Bump golang.org/x/term from 0.11.0 to 0.12.0 (#3426)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.11.0 to 0.12.0.
- [Commits](https://github.com/golang/term/compare/v0.11.0...v0.12.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-18 09:20:46 +09:00
Junegunn Choi
901939bd96 Add support for limit and offset parameters for GET / endpoint
Related #3372
2023-09-18 00:55:20 +09:00
Timofei Bredov
edfdcc8cee Basic context-aware completion for ssh command (#3424)
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2023-09-18 00:15:04 +09:00
Junegunn Choi
3982c9a552 [vimdoc] Replace unicode figure space (U+2007) with regular space
Related: https://github.com/junegunn/fzf.vim/pull/1507

Thanks to @balki
2023-09-17 23:55:39 +09:00
Junegunn Choi
4490b2d209 Respect ANSI codes to reset properties
Fix #3441
2023-09-16 19:50:37 +09:00
Sam James
eb4bbf3294 Makefile: build 32-bit binary on armv8l (#3434)
armv8l is always 32-bit and should implement the armv7 ISA, so
just use the same filename as for armv7.

This avoids wrongly building a 64-bit binary because of an incorrect assumption
of what 'armv8l' is (a 32-bit system).

Obviously, we should not then build a 64-bit (arm64) binary. Especially
given armv8l is often used in arm32-on-arm64 chroots to build stuff for
weaker-powered arm32 devices.

Signed-off-by: Sam James <sam@gentoo.org>
2023-09-11 19:32:45 +09:00
junegunn
dc97d48491 Deploying to master from @ junegunn/fzf@0f50dc848e 🚀 2023-09-10 00:01:48 +00:00
Junegunn Choi
0f50dc848e Add 'GET /' endpoint for getting the program state (experimental)
Related #3372
2023-09-03 16:30:35 +09:00
Junegunn Choi
c5e4b83de3 Update sponsor list once a week 2023-09-02 20:48:10 +09:00
junegunn
a08ab46713 Deploying to master from @ junegunn/fzf@f50a7058d6 🚀 2023-09-02 00:03:23 +00:00
Junegunn Choi
f50a7058d6 Fix center-alignment of border/preview label
Fix #3421
2023-09-01 20:30:44 +09:00
junegunn
2c74f0a040 Deploying to master from @ junegunn/fzf@58835e40f3 🚀 2023-09-01 00:03:34 +00:00
Junegunn Choi
58835e40f3 Run GitHub Sponsors action once a day 2023-08-29 19:42:30 +09:00
junegunn
8befa5918a Deploying to master from @ junegunn/fzf@df80f7ff2a 🚀 2023-08-28 08:05:07 +00:00
junegunn
df80f7ff2a Deploying to master from @ junegunn/fzf@5f66786ef1 🚀 2023-08-28 07:04:03 +00:00
Junegunn Choi
5f66786ef1 [install] Replace go get with go install
Fix #3365
2023-08-26 20:00:16 +09:00
Junegunn Choi
3a965856a5 [vim] Keep jump list unaffected when calling term_start
Fix #3415
2023-08-26 19:53:57 +09:00
junegunn
03df609d77 Deploying to master from @ junegunn/fzf@178581b560 🚀 2023-08-26 02:06:35 +00:00
junegunn
178581b560 Deploying to master from @ junegunn/fzf@ffd2314120 🚀 2023-08-25 15:04:07 +00:00
Junegunn Choi
ffd2314120 Restore --no-clear option in man page
Close #3411
2023-08-25 17:59:50 +09:00
Chandan Mangu
815b595d2f [fzf-tmux] Turn off remain-on-exit only on fzf-tmux pane (#3410)
* fix: turn off remain-on-exit only on fzf-tmux pane

Using `fzf-tmux` overwrites `remain-on-exit` for all panes in a window,
if it is only set globally or at a higher scope than window.

	set-option -wg remain-on-exit on
	set-option -s remain-on-exit on

This makes other panes in that window close immediately on exit after
using `fzf-tmux`, even though I expect them to remain open.

Since TMux 3.0, `remain-on-exit` is a pane option that can be set via
`set-option -p`. This will limit the option's scope to just the
`fzf-tmux` pane, thus allowing us to close it immediately without
overriding `remain-on-exit` on other panes in the window.

Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
Link: 11e69f6025/CHANGES (L753-L760)
Link: https://github.com/tmux/tmux/releases/tag/3.0
Related: https://github.com/junegunn/fzf/issues/3397

* fix: turn off synchronize-panes only on fzf-tmux pane

Similar reason to 482fd2b (fix: turn off remain-on-exit only on fzf-tmux
pane, 2023-08-24).

	Limit scope on which option is set to bare minimum.

Have confirmed this will not feed input back to other panes which are
set to be synchronized. However, note that this will not stop `fzf-tmux`
from being launched by two synchronized panes in parallel.

Link: https://github.com/junegunn/fzf/issues/3397#issuecomment-1689295351

---------

Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2023-08-24 16:31:38 +09:00
Junegunn Choi
11e56403dd [man] Clarify --scheme option
Close #3387
2023-08-22 15:16:51 +09:00
junegunn
4baadecda5 Deploying to master from @ junegunn/fzf@cf552b5f3b 🚀 2023-08-21 21:04:10 +00:00
junegunn
cf552b5f3b Deploying to master from @ junegunn/fzf@1894304d33 🚀 2023-08-18 15:04:01 +00:00
Junegunn Choi
1894304d33 [bash] Disable pipefail in command substitution
Fix #3382
2023-08-18 13:37:56 +09:00
sitiom
9d5392fb02 Change Winget Releaser job to ubuntu-latest (#3403) 2023-08-17 17:17:38 +09:00
dependabot[bot]
c280645671 Bump crate-ci/typos from 1.15.0 to 1.16.4 (#3400)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.15.0 to 1.16.4.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.15.0...v1.16.4)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-17 11:51:31 +09:00
dependabot[bot]
45f92e6b38 Bump actions/checkout from 2 to 3 (#3399)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-17 11:51:13 +09:00
Junegunn Choi
6509f09961 Set up GitHub Sponsors Readme Action (#3398) 2023-08-14 16:39:39 +09:00
junegunn
3c279a6f0e Deploying to master from @ junegunn/fzf@40515f11a4 🚀 2023-08-14 07:11:33 +00:00
Nikita Kouevda
9ec3f03871 doc: Add different prefix for Homebrew on Apple Silicon (#3396)
Follow-up to d42e708d31.

Fixes #3095.
2023-08-12 23:29:00 +09:00
dependabot[bot]
84a9c2c112 Bump golang.org/x/term from 0.10.0 to 0.11.0 (#3393)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.10.0 to 0.11.0.
- [Commits](https://github.com/golang/term/compare/v0.10.0...v0.11.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-08 14:10:03 +09:00
dependabot[bot]
af368119cb Bump golang.org/x/sys from 0.10.0 to 0.11.0 (#3392)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.10.0 to 0.11.0.
- [Commits](https://github.com/golang/sys/compare/v0.10.0...v0.11.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-08 00:25:19 +09:00
Junegunn Choi
89b9189efa [fzf-tmux] Pass $RUNEWIDTH_EASTASIAN
Close #3385
2023-08-03 16:47:19 +09:00
Junegunn Choi
dd59b8c7b9 Fix ANSI color continuation in --header
# Both lines should be in red
  fzf --header $'\x1b[31mfoo\nbar'
2023-07-25 22:20:31 +09:00
Junegunn Choi
f83491274f Add toggle-header option
Close #3358
2023-07-25 22:11:15 +09:00
Boaz Yaniv
c0435fdff4 Add API Keys for fzf --listen (#3374) 2023-07-20 23:42:09 +09:00
Bart
3c09c77269 Fix deprecations of ioutil (#3370) 2023-07-16 17:14:22 +09:00
Junegunn Choi
547e101f1d Use $SHELL instead of bash if it's known to support 'pipefail'
when running the default find command

Close #3339
Close #3364
2023-07-12 13:55:59 +09:00
dependabot[bot]
0130f64934 Bump golang.org/x/term from 0.9.0 to 0.10.0 (#3360)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.9.0 to 0.10.0.
- [Commits](https://github.com/golang/term/compare/v0.9.0...v0.10.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-11 00:35:44 +09:00
dependabot[bot]
361e0543ee Bump golang.org/x/sys from 0.9.0 to 0.10.0 (#3361)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.9.0 to 0.10.0.
- [Commits](https://github.com/golang/sys/compare/v0.9.0...v0.10.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-10 22:43:45 +09:00
Junegunn Choi
63aa5d3b4e Correct outdated comment 2023-07-05 23:42:34 +09:00
dependabot[bot]
01302d097c Bump golang.org/x/term from 0.8.0 to 0.9.0 (#3338)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.8.0 to 0.9.0.
- [Commits](https://github.com/golang/term/compare/v0.8.0...v0.9.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-22 17:00:06 +09:00
dependabot[bot]
e6095cb7e8 Bump golang.org/x/sys from 0.8.0 to 0.9.0 (#3337)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.8.0 to 0.9.0.
- [Commits](https://github.com/golang/sys/compare/v0.8.0...v0.9.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-21 13:41:21 +09:00
dependabot[bot]
b876b8af11 Bump crate-ci/typos from 1.14.10 to 1.15.0 (#3331)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.14.10 to 1.15.0.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.14.10...v1.15.0)

---
updated-dependencies:
- dependency-name: crate-ci/typos
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-21 13:40:48 +09:00
Junegunn Choi
a7c41f3fcd Add '--info=right' to the man page
Close #3333
2023-06-17 19:15:29 +09:00
guangwu
4772bd8d4c Use strings.ContainsRune instead (#3335) 2023-06-17 19:10:12 +09:00
Junegunn Choi
d471067e3f 0.42.0 2023-06-15 00:37:41 +09:00
Junegunn Choi
d0b7780239 Add --info=right
Related: #3322
2023-06-11 16:09:15 +09:00
Junegunn Choi
e627ca6bd7 Add --info=inline-right
Close #3322
2023-06-10 23:11:05 +09:00
Junegunn Choi
c97172bdd4 Fix background color of spinner on the preview window 2023-06-10 14:48:47 +09:00
Mike
ce8a745fb4 Add new border style: 'thinblock' (#3327)
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2023-06-10 14:48:29 +09:00
Junegunn Choi
3e9efd1401 [vim] Only prepend --border option in $FZF_DEFAULT_OPTS
Fix #3318
2023-06-03 09:03:04 +09:00
Junegunn Choi
20340190b5 [fzf-tmux] Pass $BAT_THEME
This may anger some purists, but bat is widely used as the previewer so
I think it's worth it.
2023-05-31 20:16:30 +09:00
Junegunn Choi
265040a78c [vim] Respect --border optin in $FZF_DEFAULT_OPTS 2023-05-31 20:09:14 +09:00
Junegunn Choi
448d7e0c5a Update test case 2023-05-27 16:01:30 +09:00
41 changed files with 1210 additions and 273 deletions

24
.github/workflows/sponsors.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
---
name: Generate Sponsors README
on:
workflow_dispatch:
schedule:
- cron: 0 0 * * 0
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@v3
- name: Generate Sponsors 💖
uses: JamesIves/github-sponsors-readme-action@v1
with:
token: ${{ secrets.SPONSORS_TOKEN }}
file: 'README.md'
- name: Deploy to GitHub Pages 🚀
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: master
folder: '.'

View File

@@ -7,4 +7,4 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: crate-ci/typos@v1.14.10
- uses: crate-ci/typos@v1.16.4

View File

@@ -5,7 +5,7 @@ on:
jobs:
publish:
runs-on: windows-latest # Action can only run on Windows
runs-on: ubuntu-latest
steps:
- uses: vedantmgoyal2009/winget-releaser@v2
with:

View File

@@ -1,6 +1,91 @@
CHANGELOG
=========
0.43.0
------
- (Experimental) Added support for Kitty image protocol in the preview window
```sh
fzf --preview='
if file --mime-type {} | grep -qF image/; then
# --transfer-mode=memory is the fastest option but if you want fzf to be able
# to redraw the image on terminal resize or on 'change-preview-window',
# you need to use --transfer-mode=stream.
kitty icat --clear --transfer-mode=memory --stdin=no --place=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}@0x0 {} | sed \$d
else
bat --color=always {}
fi
'
```
- (Experimental) `--listen` server can report program state in JSON format (`GET /`)
```sh
# fzf server started in "headless" mode
fzf --listen 6266 2> /dev/null
# Get program state
curl localhost:6266 | jq .
# Increase the number of items returned (default: 100)
curl localhost:6266?limit=1000 | jq .
```
- `--listen` server can be secured by setting `$FZF_API_KEY` environment
variable.
```sh
export FZF_API_KEY="$(head -c 32 /dev/urandom | base64)"
# Server
fzf --listen 6266
# Client
curl localhost:6266 -H "x-api-key: $FZF_API_KEY" -d 'change-query(yo)'
```
- Added `toggle-header` action
- Added mouse events for `--bind`
- `scroll-up` (bound to `up`)
- `scroll-down` (bound to `down`)
- `shift-scroll-up` (bound to `toggle+up`)
- `shift-scroll-down` (bound to `toggle+down`)
- `shift-left-click` (bound to `toggle`)
- `shift-right-click` (bound to `toggle`)
- `preview-scroll-up` (bound to `preview-up`)
- `preview-scroll-down` (bound to `preview-down`)
```sh
# Twice faster scrolling both in the main window and the preview window
fzf --bind 'scroll-up:up+up,scroll-down:down+down' \
--bind 'preview-scroll-up:preview-up+preview-up' \
--bind 'preview-scroll-down:preview-down+preview-down' \
--preview 'cat {}'
```
- Added `offset-up` and `offset-down` actions
```sh
# Scrolling will behave similarly to CTRL-E and CTRL-Y of vim
fzf --bind scroll-up:offset-up,scroll-down:offset-down \
--bind ctrl-y:offset-up,ctrl-e:offset-down \
--scroll-off=5
```
- Shell extensions
- Updated bash completion for fzf options
- bash key bindings no longer requires perl; it will use awk or mawk
instead if perl is not found
- Basic context-aware completion for ssh command
- Applied `--scheme=path` for better ordering of the result
- Bug fixes and improvements
0.42.0
------
- Added new info style: `--info=right`
- Added new info style: `--info=inline-right`
- Added new border style `thinblock` which uses symbols for legacy computing
[one eighth block elements](https://en.wikipedia.org/wiki/Symbols_for_Legacy_Computing)
- Similarly to `block`, this style is suitable when using a different
background color because the window is completely contained within the border.
```sh
BAT_THEME=GitHub fzf --info=right --border=thinblock --preview-window=border-thinblock \
--margin=3 --scrollbar=▏▕ --preview='bat --color=always --style=numbers {}' \
--color=light,query:238,fg:238,bg:251,bg+:249,gutter:251,border:248,preview-bg:253
```
- This style may not render correctly depending on the font and the
terminal emulator.
0.41.1
------
- Fixed a bug where preview window is not updated when `--disabled` is set and
@@ -337,7 +422,7 @@ CHANGELOG
(sleep 2; seq 1000) | fzf --height ~50%
```
- Fixed tcell renderer used to render full-screen fzf on Windows
- `--no-clear` is deprecated. Use `reload` action instead.
- ~~`--no-clear` is deprecated. Use `reload` action instead.~~
0.33.0
------

View File

@@ -57,7 +57,9 @@ else ifeq ($(UNAME_M),armv6l)
else ifeq ($(UNAME_M),armv7l)
BINARY := $(BINARYARM7)
else ifeq ($(UNAME_M),armv8l)
BINARY := $(BINARYARM8)
# armv8l is always 32-bit and should implement the armv7 ISA, so
# just use the same filename as for armv7.
BINARY := $(BINARYARM7)
else ifeq ($(UNAME_M),arm64)
BINARY := $(BINARYARM8)
else ifeq ($(UNAME_M),aarch64)

View File

@@ -26,6 +26,9 @@ written as:
" If installed using Homebrew
Plug '/usr/local/opt/fzf'
" If installed using Homebrew on Apple Silicon
Plug '/opt/homebrew/opt/fzf'
" If you have cloned fzf on ~/.fzf directory
Plug '~/.fzf'
```

View File

@@ -19,6 +19,15 @@ Pros
- Batteries included
- Vim/Neovim plugin, key bindings, and fuzzy auto-completion
Sponsors ❤️
-----------
I would like to thank all the sponsors of this project who make it possible for me to continue to improve fzf.
If you'd like to sponsor this project, please visit https://github.com/sponsors/junegunn.
<!-- sponsors --><a href="https://github.com/miyanokomiya"><img src="https://github.com/miyanokomiya.png" width="60px" alt="miyanokomiya" /></a><a href="https://github.com/jonhoo"><img src="https://github.com/jonhoo.png" width="60px" alt="Jon Gjengset" /></a><a href="https://github.com/AceofSpades5757"><img src="https://github.com/AceofSpades5757.png" width="60px" alt="Kyle L. Davis" /></a><a href="https://github.com/Frederick888"><img src="https://github.com/Frederick888.png" width="60px" alt="Frederick Zhang" /></a><a href="https://github.com/moritzdietz"><img src="https://github.com/moritzdietz.png" width="60px" alt="Moritz Dietz" /></a><a href="https://github.com/mikker"><img src="https://github.com/mikker.png" width="60px" alt="Mikkel Malmberg" /></a><a href="https://github.com/pldubouilh"><img src="https://github.com/pldubouilh.png" width="60px" alt="Pierre Dubouilh" /></a><a href="https://github.com/rcorre"><img src="https://github.com/rcorre.png" width="60px" alt="Ryan Roden-Corrent" /></a><a href="https://github.com/blissdev"><img src="https://github.com/blissdev.png" width="60px" alt="Jordan Arentsen" /></a><a href="https://github.com/mislav"><img src="https://github.com/mislav.png" width="60px" alt="Mislav Marohnić" /></a><a href="https://github.com/aexvir"><img src="https://github.com/aexvir.png" width="60px" alt="Alex Viscreanu" /></a><a href="https://github.com/dbalatero"><img src="https://github.com/dbalatero.png" width="60px" alt="David Balatero" /></a><a href="https://github.com/comatory"><img src="https://github.com/comatory.png" width="60px" alt="Ondrej Synacek" /></a><a href="https://github.com/moobar"><img src="https://github.com/moobar.png" width="60px" alt="" /></a><a href="https://github.com/majjoha"><img src="https://github.com/majjoha.png" width="60px" alt="Mathias Jean Johansen" /></a><a href="https://github.com/benelan"><img src="https://github.com/benelan.png" width="60px" alt="Ben Elan" /></a><a href="https://github.com/jryom"><img src="https://github.com/jryom.png" width="60px" alt="Jesper" /></a><a href="https://github.com/nmrnv"><img src="https://github.com/nmrnv.png" width="60px" alt="Nikolay Marinov" /></a><a href="https://github.com/pawelduda"><img src="https://github.com/pawelduda.png" width="60px" alt="Paweł Duda" /></a><a href="https://github.com/slezica"><img src="https://github.com/slezica.png" width="60px" alt="Santiago Lezica" /></a><a href="https://github.com/pbwn"><img src="https://github.com/pbwn.png" width="60px" alt="" /></a><a href="https://github.com/timgluz"><img src="https://github.com/timgluz.png" width="60px" alt="Timo Sulg" /></a><a href="https://github.com/seanmorton"><img src="https://github.com/seanmorton.png" width="60px" alt="Sean Morton" /></a><a href="https://github.com/pyrho"><img src="https://github.com/pyrho.png" width="60px" alt="Damien Rajon" /></a><a href="https://github.com/ArtBIT"><img src="https://github.com/ArtBIT.png" width="60px" alt="ArtBIT" /></a><a href="https://github.com/da-moon"><img src="https://github.com/da-moon.png" width="60px" alt="" /></a><a href="https://github.com/hovissimo"><img src="https://github.com/hovissimo.png" width="60px" alt="Hovis" /></a><a href="https://github.com/dariusjonda"><img src="https://github.com/dariusjonda.png" width="60px" alt="Darius Jonda" /></a><a href="https://github.com/cristiand391"><img src="https://github.com/cristiand391.png" width="60px" alt="Cristian Dominguez" /></a><a href="https://github.com/eliangcs"><img src="https://github.com/eliangcs.png" width="60px" alt="Chang-Hung Liang" /></a><a href="https://github.com/raveensrk"><img src="https://github.com/raveensrk.png" width="60px" alt="Raveen Kumar" /></a><a href="https://github.com/asphaltbuffet"><img src="https://github.com/asphaltbuffet.png" width="60px" alt="Ben Lechlitner" /></a><a href="https://github.com/yash1th"><img src="https://github.com/yash1th.png" width="60px" alt="yash" /></a><a href="https://github.com/kg8m"><img src="https://github.com/kg8m.png" width="60px" alt="Takumi KAGIYAMA" /></a><a href="https://github.com/polm"><img src="https://github.com/polm.png" width="60px" alt="Paul O'Leary McCann" /></a><a href="https://github.com/rbeeger"><img src="https://github.com/rbeeger.png" width="60px" alt="Robert Beeger" /></a><a href="https://github.com/veebch"><img src="https://github.com/veebch.png" width="60px" alt="VEEB Projects" /></a><!-- sponsors -->
Table of Contents
-----------------
@@ -60,6 +69,7 @@ Table of Contents
* [2. Switch between sources by pressing CTRL-D or CTRL-F](#2-switch-between-sources-by-pressing-ctrl-d-or-ctrl-f)
* [3. Interactive ripgrep integration](#3-interactive-ripgrep-integration)
* [Preview window](#preview-window)
* [Previewing an image](#previewing-an-image)
* [Tips](#tips)
* [Respecting `.gitignore`](#respecting-gitignore)
* [Fish shell](#fish-shell)
@@ -721,6 +731,22 @@ seq 100 | fzf
history | fzf
```
### Previewing an image
Since 0.43.0, fzf has experimental support for [Kitty graphics
protocol](https://sw.kovidgoyal.net/kitty/graphics-protocol/), so if you use
Kitty, you can make fzf display an image in the preview window.
```sh
fzf --preview='
if file --mime-type {} | grep -qF image/; then
kitty icat --clear --transfer-mode=memory --stdin=no --place=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}@0x0 {} | sed \$d
else
bat --color=always {}
fi
'
```
Tips
----

View File

@@ -151,12 +151,18 @@ argsf="${TMPDIR:-/tmp}/fzf-args-$id"
fifo1="${TMPDIR:-/tmp}/fzf-fifo1-$id"
fifo2="${TMPDIR:-/tmp}/fzf-fifo2-$id"
fifo3="${TMPDIR:-/tmp}/fzf-fifo3-$id"
if tmux_win_opts=$(tmux show-options -p remain-on-exit \; show-options -p synchronize-panes 2> /dev/null); then
tmux_win_opts=( $(sed '/ off/d; s/synchronize-panes/set-option -p synchronize-panes/; s/remain-on-exit/set-option -p remain-on-exit/; s/$/ \\;/' <<< "$tmux_win_opts") )
tmux_off_opts='; set-option -p synchronize-panes off ; set-option -p remain-on-exit off'
else
tmux_win_opts=( $(tmux show-window-options remain-on-exit \; show-window-options synchronize-panes | sed '/ off/d; s/^/set-window-option /; s/$/ \\;/') )
tmux_off_opts='; set-window-option synchronize-panes off ; set-window-option remain-on-exit off'
fi
cleanup() {
\rm -f $argsf $fifo1 $fifo2 $fifo3
# Restore tmux window options
if [[ "${#tmux_win_opts[@]}" -gt 0 ]]; then
if [[ "${#tmux_win_opts[@]}" -gt 1 ]]; then
eval "tmux ${tmux_win_opts[*]}"
fi
@@ -192,6 +198,8 @@ if [[ "$opt" =~ "-E" ]]; then
fi
[[ -n "$FZF_DEFAULT_OPTS" ]] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
[[ -n "$FZF_DEFAULT_COMMAND" ]] && envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
[[ -n "$RUNEWIDTH_EASTASIAN" ]] && envs="$envs RUNEWIDTH_EASTASIAN=$(printf %q "$RUNEWIDTH_EASTASIAN")"
[[ -n "$BAT_THEME" ]] && envs="$envs BAT_THEME=$(printf %q "$BAT_THEME")"
echo "$envs;" > "$argsf"
# Build arguments to fzf
@@ -225,9 +233,9 @@ else
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; echo \$? > $fifo3 $close" >> $argsf
cat <&0 > $fifo1 &
fi
tmux set-window-option synchronize-panes off \;\
set-window-option remain-on-exit off \;\
tmux \
split-window -c "$PWD" $opt "bash -c 'exec -a fzf bash $argsf'" $swap \
$tmux_off_opts \
> /dev/null 2>&1 || { "$fzf" "${args[@]}"; exit $?; }
cat $fifo2
exit "$(cat $fifo3)"

View File

@@ -1,4 +1,4 @@
fzf.txt fzf Last change: Mar 20 2023
fzf.txt fzf Last change: September 17 2023
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
==============================================================================
@@ -32,6 +32,9 @@ depending on the package manager.
" If installed using Homebrew
set rtp+=/usr/local/opt/fzf
" If installed using Homebrew on Apple Silicon
set rtp+=/opt/homebrew/opt/fzf
" If you have cloned fzf on ~/.fzf directory
set rtp+=~/.fzf
<
@@ -40,6 +43,9 @@ If you use {vim-plug}{1}, the same can be written as:
" If installed using Homebrew
Plug '/usr/local/opt/fzf'
" If installed using Homebrew on Apple Silicon
Plug '/opt/homebrew/opt/fzf'
" If you have cloned fzf on ~/.fzf directory
Plug '~/.fzf'
<
@@ -68,16 +74,16 @@ SUMMARY *fzf-summary*
The Vim plugin of fzf provides two core functions, and `:FZF` command which is
the basic file selector command built on top of them.
1. `fzf#run([specdict])`
1. `fzf#run([spec dict])`
- Starts fzf inside Vim with the given spec
- `:callfzf#run({'source':'ls'})`
2. `fzf#wrap([specdict])->(dict)`
- `:call fzf#run({'source': 'ls'})`
2. `fzf#wrap([spec dict]) -> (dict)`
- Takes a spec for `fzf#run` and returns an extended version of it with
additional options for addressing global preferences (`g:fzf_xxx`)
- `:echofzf#wrap({'source':'ls'})`
- `:echo fzf#wrap({'source': 'ls'})`
- We usually wrap a spec with `fzf#wrap` before passing it to `fzf#run`
- `:callfzf#run(fzf#wrap({'source':'ls'}))`
3. `:FZF[fzf_optionsstring][pathstring]`
- `:call fzf#run(fzf#wrap({'source': 'ls'}))`
3. `:FZF [fzf_options string] [path string]`
- Basic fuzzy file selector
- A reference implementation for those who don't want to write VimScript to
implement custom commands
@@ -222,12 +228,12 @@ list:
`spinner` | Streaming input indicator
`query` | Query string
`disabled` | Query string when search is disabled
`prompt` | Prompt before query ( `>` )
`prompt` | Prompt before query ( `> ` )
`pointer` | Pointer to the current line ( `>` )
----------------------------+------------------------------------------------------
- `component` specifies the component (`fg` / `bg`) from which to extract the
color when considering each of the following highlight groups
- `group1[,group2,...]` is a list of highlight groups that are searched (in
- `group1 [, group2, ...]` is a list of highlight groups that are searched (in
order) for a matching color definition
For example, consider the following specification:
@@ -268,7 +274,7 @@ as the sink.
<
Instead of using the default find command, you can use any shell command as
the source. The following example will list the files managed by git. It's
equivalent to running `gitls-files|fzf` on shell.
equivalent to running `git ls-files | fzf` on shell.
>
call fzf#run({'source': 'git ls-files', 'sink': 'e'})
<
@@ -296,7 +302,7 @@ The following table summarizes the available options.
---------------------------+---------------+----------------------------------------------------------------------
Option name | Type | Description ~
---------------------------+---------------+----------------------------------------------------------------------
`source` | string | External command to generate input to fzf (e.g. `find.` )
`source` | string | External command to generate input to fzf (e.g. `find .` )
`source` | list | Vim list as input to fzf
`sink` | string | Vim command to handle the selected item (e.g. `e` , `tabe` )
`sink` | funcref | Reference to function to process each selected item
@@ -305,8 +311,8 @@ The following table summarizes the available options.
`dir` | string | Working directory
`up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` )
`tmux` | string | (Layout) fzf-tmux options (e.g. `-p90%,60%` )
`window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `verticalaboveleft30new` )
`window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width':0.9,'height':0.6}` )
`window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new` )
`window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width': 0.9, 'height': 0.6}` )
---------------------------+---------------+----------------------------------------------------------------------
`options` entry can be either a string or a list. For simple cases, string
@@ -343,7 +349,7 @@ So how can we make our custom `fzf#run` calls also respect those variables?
Simply by "wrapping" the spec dictionary with `fzf#wrap` before passing it to
`fzf#run`.
- `fzf#wrap([namestring],[specdict],[fullscreenbool])->(dict)`
- `fzf#wrap([name string], [spec dict], [fullscreen bool]) -> (dict)`
- All arguments are optional. Usually we only need to pass a spec
dictionary.
- `name` is for managing history files. It is ignored if `g:fzf_history_dir`
@@ -377,7 +383,7 @@ last `fullscreen` argument of `fzf#wrap` (see :help <bang>).
command! -bang LS call fzf#run(fzf#wrap({'source': 'ls'}, <bang>0))
<
Our `:LS` command will be much more useful if we can pass a directory argument
to it, so that something like `:LS/tmp` is possible.
to it, so that something like `:LS /tmp` is possible.
>
command! -bang -complete=dir -nargs=? LS
\ call fzf#run(fzf#wrap({'source': 'ls', 'dir': <q-args>}, <bang>0))
@@ -396,10 +402,10 @@ unique name to our command and pass it as the first argument to `fzf#wrap`.
- `g:fzf_layout`
- `g:fzf_action`
- Works only when no custom `sink` (or `sink*`) is provided
- Works only when no custom `sink` (or `sinklist`) is provided
- Having custom sink usually means that each entry is not an ordinary
file path (e.g. name of color scheme), so we can't blindly apply the
same strategy (i.e. `tabeditsome-color-scheme` doesn't make sense)
same strategy (i.e. `tabedit some-color-scheme` doesn't make sense)
- `g:fzf_colors`
- `g:fzf_history_dir`
@@ -488,7 +494,7 @@ or above) by putting fzf-tmux options in `tmux` key.
*fzf-hide-statusline*
When fzf starts in a terminal buffer, the file type of the buffer is set to
`fzf`. So you can set up `FileTypefzf` autocmd to customize the settings of
`fzf`. So you can set up `FileType fzf` autocmd to customize the settings of
the window.
For example, if you open fzf on the bottom on the screen (e.g. `{'down':

4
go.mod
View File

@@ -7,8 +7,8 @@ require (
github.com/mattn/go-shellwords v1.0.12
github.com/rivo/uniseg v0.4.4
github.com/saracen/walker v0.1.3
golang.org/x/sys v0.8.0
golang.org/x/term v0.8.0
golang.org/x/sys v0.13.0
golang.org/x/term v0.13.0
)
require (

8
go.sum
View File

@@ -32,12 +32,12 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=

View File

@@ -2,7 +2,7 @@
set -u
version=0.41.1
version=0.43.0
auto_completion=
key_bindings=
update_config=2
@@ -196,12 +196,12 @@ if [ -n "$binary_error" ]; then
echo " - $binary_error !!!"
fi
if command -v go > /dev/null; then
echo -n "Building binary (go get -u github.com/junegunn/fzf) ... "
echo -n "Building binary (go install github.com/junegunn/fzf) ... "
if [ -z "${GOPATH-}" ]; then
export GOPATH="${TMPDIR:-/tmp}/fzf-gopath"
mkdir -p "$GOPATH"
fi
if go get -ldflags "-s -w -X main.version=$version -X main.revision=go-get" github.com/junegunn/fzf; then
if go install -ldflags "-s -w -X main.version=$version -X main.revision=go-install" github.com/junegunn/fzf; then
echo "OK"
cp "$GOPATH/bin/fzf" "$fzf_base/bin/"
else
@@ -245,7 +245,7 @@ for shell in $shells; do
src=${prefix_expand}.${shell}
echo -n "Generate $src ... "
fzf_completion="[[ \$- == *i* ]] && source \"$fzf_base/shell/completion.${shell}\" 2> /dev/null"
fzf_completion="source \"$fzf_base/shell/completion.${shell}\""
if [ $auto_completion -eq 0 ]; then
fzf_completion="# $fzf_completion"
fi

View File

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

View File

@@ -5,7 +5,7 @@ import (
"github.com/junegunn/fzf/src/protector"
)
var version string = "0.41"
var version string = "0.43"
var revision string = "devel"
func main() {

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
THE SOFTWARE.
..
.TH fzf-tmux 1 "May 2023" "fzf 0.41.1" "fzf-tmux - open fzf in tmux split pane"
.TH fzf-tmux 1 "Oct 2023" "fzf 0.43.0" "fzf-tmux - open fzf in tmux split pane"
.SH NAME
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
THE SOFTWARE.
..
.TH fzf 1 "May 2023" "fzf 0.41.1" "fzf - a command-line fuzzy finder"
.TH fzf 1 "Oct 2023" "fzf 0.43.0" "fzf - a command-line fuzzy finder"
.SH NAME
fzf - a command-line fuzzy finder
@@ -57,9 +57,9 @@ Choose scoring scheme tailored for different types of input.
.br
.BR default " Generic scoring scheme designed to work well with any type of input"
.br
.BR path " Scoring scheme for paths (additional bonus point only after path separator)
.BR path " Scoring scheme well suited for file paths
.br
.BR history " Scoring scheme for command history (no additional bonus points).
.BR history " Scoring scheme well suited for command history or any input where chronological ordering is important
Sets \fB--tiebreak=index\fR as well.
.br
.TP
@@ -228,6 +228,10 @@ Draw border around the finder
.br
.BR double " Border with double lines"
.br
.BR block " Border using block elements; suitable when using different background colors"
.br
.BR thinblock " Border using legacy computing symbols; may not be displayed on some terminals"
.br
.BR horizontal " Horizontal lines above and below the finder"
.br
.BR vertical " Vertical lines on each side of the finder"
@@ -353,10 +357,14 @@ Determines the display style of finder info (match counters).
.br
.BR default " Display on the next line to the prompt"
.br
.BR right " Display on the right end of the next line to the prompt"
.br
.BR inline " Display on the same line with the default separator ' < '"
.br
.BR inline:SEPARATOR " Display on the same line with a non-default separator"
.br
.BR inline-right " Display on the right end of the same line
.br
.BR hidden " Do not display finder info"
.br
@@ -583,6 +591,18 @@ e.g.
echo "$i"
sleep 0.01
done'\fR
Since 0.43.0, fzf has experimental support for Kitty graphics protocol,
so if you use Kitty, you can make fzf display an image in the preview window.
e.g.
\fBfzf --preview='
if file --mime-type {} | grep -qF "image/"; then
kitty icat --clear --transfer-mode=memory --stdin=no --place=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}@0x0 {} | sed \\$d
else
bat --color=always {}
fi
'\fR
.RE
.TP
@@ -599,6 +619,10 @@ Should be used with one of the following \fB--preview-window\fR options.
.br
.B * border-double
.br
.B * border-block
.br
.B * border-thinblock
.br
.B * border-horizontal
.br
.B * border-top
@@ -746,6 +770,21 @@ Read input delimited by ASCII NUL characters instead of newline characters
.TP
.B "--print0"
Print output delimited by ASCII NUL characters instead of newline characters
.TP
.B "--no-clear"
Do not clear finder interface on exit. If fzf was started in full screen mode,
it will not switch back to the original screen, so you'll have to manually run
\fBtput rmcup\fR to return. This option can be used to avoid flickering of the
screen when your application needs to start fzf multiple times in order. (Note
that in most cases, it is preferable to use \fBreload\fR action instead.)
e.g.
\fBfoo=$(seq 100 | fzf --no-clear) || (
# Need to manually switch back to the main screen when cancelled
tput rmcup
exit 1
) && seq "$foo" 100 | fzf
.TP
.B "--sync"
Synchronous search for multi-staged filtering. If specified, fzf will launch
@@ -760,15 +799,24 @@ Start HTTP server on the given port. It allows external processes to send
actions to perform via POST method. If the port number is omitted or given as
0, fzf will choose the port automatically and export it as \fBFZF_PORT\fR
environment variable to the child processes started via \fBexecute\fR and
\fBexecute-silent\fR actions.
\fBexecute-silent\fR actions. If \fBFZF_API_KEY\fR environment variable is
set, the server would require sending an API key with the same value in the
\fBx-api-key\fR HTTP header.
e.g.
\fB# Start HTTP server on port 6266
fzf --listen 6266
# Get program state in JSON format (experimental)
curl localhost:6266
# Send action to the server
curl -XPOST localhost:6266 -d 'reload(seq 100)+change-prompt(hundred> )'
# Start HTTP server on port 6266 and send an authenticated action
export FZF_API_KEY="$(head -c 32 /dev/urandom | base64)"
curl -XPOST localhost:6266 -H "x-api-key: $FZF_API_KEY" -d 'change-query(yo)'
# Choose port automatically and export it as $FZF_PORT to the child process
fzf --listen --bind 'start:execute-silent:echo $FZF_PORT > /tmp/fzf-port'
\fR
@@ -788,6 +836,11 @@ this case make sure that the command is POSIX-compliant.
.TP
.B FZF_DEFAULT_OPTS
Default options. e.g. \fBexport FZF_DEFAULT_OPTS="--extended --cycle"\fR
.TP
.B FZF_API_KEY
Can be used to require an API key when using \fB--listen\fR option. If not set,
no authentication will be required by the server. You can set this value if
you need to protect against DNS rebinding and privilege escalation attacks.
.SH EXIT STATUS
.BR 0 " Normal exit"
@@ -959,6 +1012,22 @@ e.g.
.br
\fIdouble-click\fR
.br
\fIscroll-up\fR
.br
\fIscroll-down\fR
.br
\fIpreview-scroll-up\fR
.br
\fIpreview-scroll-down\fR
.br
\fIshift-left-click\fR
.br
\fIshift-right-click\fR
.br
\fIshift-scroll-up\fR
.br
\fIshift-scroll-down\fR
.br
or any single character
.SS AVAILABLE EVENTS:
@@ -1088,6 +1157,8 @@ A key or an event can be bound to one or more of the following actions.
\fBhalf-page-down\fR
\fBhalf-page-up\fR
\fBhide-preview\fR
\fBoffset-down\fR (similar to CTRL-E of Vim)
\fBoffset-up\fR (similar to CTRL-Y of Vim)
\fBpos(...)\fR (move cursor to the numeric position; negative number to count from the end)
\fBprev-history\fR (\fIctrl-p\fR on \fB--history\fR)
\fBprev-selected\fR (move to the previous selected item)
@@ -1114,6 +1185,7 @@ A key or an event can be bound to one or more of the following actions.
\fBtoggle\fR (\fIright-click\fR)
\fBtoggle-all\fR (toggle all matches)
\fBtoggle+down\fR \fIctrl-i (tab)\fR
\fBtoggle-header\fR
\fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
\fBtoggle-preview\fR

View File

@@ -456,6 +456,30 @@ function! s:writefile(...)
endif
endfunction
function! s:extract_option(opts, name)
let opt = ''
let expect = 0
" There are a few cases where this function doesn't work as expected.
" Let's just assume such cases are extremely unlikely in real world.
" e.g. --query --border
for word in split(a:opts)
if expect && word !~ '^"\=-'
let opt = opt . ' ' . word
let expect = 0
elseif word == '--no-'.a:name
let opt = ''
elseif word =~ '^--'.a:name.'='
let opt = word
elseif word =~ '^--'.a:name.'$'
let opt = word
let expect = 1
elseif expect
let expect = 0
endif
endfor
return opt
endfunction
function! fzf#run(...) abort
try
let [shell, shellslash, shellcmdflag, shellxquote] = s:use_sh()
@@ -511,8 +535,8 @@ try
let height = s:calc_size(&lines, dict.down, dict)
let optstr .= ' --height='.height
endif
" Respect --border option given in 'options'
let optstr = join([s:border_opt(get(dict, 'window', 0)), optstr])
" Respect --border option given in $FZF_DEFAULT_OPTS and 'options'
let optstr = join([s:border_opt(get(dict, 'window', 0)), s:extract_option($FZF_DEFAULT_OPTS, 'border'), optstr])
let prev_default_command = $FZF_DEFAULT_COMMAND
if len(source_command)
let $FZF_DEFAULT_COMMAND = source_command
@@ -921,7 +945,7 @@ function! s:execute_term(dict, command, temps) abort
let term_opts.curwin = 1
endif
call s:handle_ambidouble(term_opts)
let fzf.buf = term_start([&shell, &shellcmdflag, command], term_opts)
keepjumps let fzf.buf = term_start([&shell, &shellcmdflag, command], term_opts)
if is_popup && exists('#TerminalWinOpen')
doautocmd <nomodeline> TerminalWinOpen
endif

View File

@@ -9,23 +9,24 @@
# - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty)
if [[ $- =~ i ]]; then
[[ $- =~ i ]] || return 0
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
if ! declare -f _fzf_compgen_path > /dev/null; then
if ! declare -F _fzf_compgen_path > /dev/null; then
_fzf_compgen_path() {
echo "$1"
command find -L "$1" \
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
-a -not -path "$1" -print 2> /dev/null | command sed 's@^\./@@'
}
fi
if ! declare -f _fzf_compgen_dir > /dev/null; then
if ! declare -F _fzf_compgen_dir > /dev/null; then
_fzf_compgen_dir() {
command find -L "$1" \
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
-a -not -path "$1" -print 2> /dev/null | command sed 's@^\./@@'
}
fi
@@ -68,59 +69,185 @@ _fzf_opts_completion() {
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts="
-h --help
-x --extended
-e --exact
--extended-exact
+x --no-extended
+e --no-exact
-q --query
-f --filter
--literal
--no-literal
--algo
-i +i
--scheme
--expect
--no-expect
--enabled --no-phony
--disabled --phony
--tiebreak
--bind
--color
--toggle-sort
-d --delimiter
-n --nth
--with-nth
-d --delimiter
-s --sort
+s --no-sort
--track
--no-track
--tac
--tiebreak
--no-tac
-i
+i
-m --multi
+m --no-multi
--ansi
--no-ansi
--no-mouse
--bind
--cycle
--no-hscroll
--jump-labels
--height
--literal
+c --no-color
+2 --no-256
--black
--no-black
--bold
--no-bold
--layout
--reverse
--margin
--no-reverse
--cycle
--no-cycle
--keep-right
--no-keep-right
--hscroll
--no-hscroll
--hscroll-off
--scroll-off
--filepath-word
--no-filepath-word
--info
--no-info
--inline-info
--no-inline-info
--separator
--no-separator
--scrollbar
--no-scrollbar
--jump-labels
-1 --select-1
+1 --no-select-1
-0 --exit-0
+0 --no-exit-0
--read0
--no-read0
--print0
--no-print0
--print-query
--no-print-query
--prompt
--pointer
--marker
--header
--header-lines
--ansi
--tabstop
--color
--no-bold
--sync
--no-sync
--async
--no-history
--history
--history-size
--no-header
--no-header-lines
--header
--header-lines
--header-first
--no-header-first
--ellipsis
--preview
--no-preview
--preview-window
-q --query
-1 --select-1
-0 --exit-0
-f --filter
--print-query
--expect
--sync"
--height
--min-height
--no-height
--no-margin
--no-padding
--no-border
--border
--no-border-label
--border-label
--border-label-pos
--no-preview-label
--preview-label
--preview-label-pos
--no-unicode
--unicode
--margin
--padding
--tabstop
--listen
--no-listen
--clear
--no-clear
--version
--"
case "${prev}" in
--algo)
COMPREPLY=( $(compgen -W "v1 v2" -- "$cur") )
return 0
;;
--scheme)
COMPREPLY=( $(compgen -W "default path history" -- "$cur") )
return 0
;;
--tiebreak)
COMPREPLY=( $(compgen -W "length begin end index" -- "$cur") )
COMPREPLY=( $(compgen -W "length chunk begin end index" -- "$cur") )
return 0
;;
--color)
COMPREPLY=( $(compgen -W "dark light 16 bw" -- "$cur") )
COMPREPLY=( $(compgen -W "dark light 16 bw no" -- "$cur") )
return 0
;;
--history)
COMPREPLY=()
--layout)
COMPREPLY=( $(compgen -W "default reverse reverse-list" -- "$cur") )
return 0
;;
--info)
COMPREPLY=( $(compgen -W "default right hidden inline inline-right" -- "$cur") )
return 0
;;
--preview-window)
COMPREPLY=( $(compgen -W "
default
hidden
nohidden
wrap
nowrap
cycle
nocycle
up top
down bottom
left
right
rounded border border-rounded
sharp border-sharp
border-bold
border-block
border-thinblock
border-double
noborder border-none
border-horizontal
border-vertical
border-up border-top
border-down border-bottom
border-left
border-right
follow
nofollow" -- "$cur") )
return 0
;;
--border)
COMPREPLY=( $(compgen -W "rounded sharp bold block thinblock double horizontal vertical top bottom left right none" -- "$cur") )
return 0
;;
--border-label-pos|--preview-label-pos)
COMPREPLY=( $(compgen -W "center bottom top" -- "$cur") )
return 0
;;
esac
@@ -170,9 +297,9 @@ __fzf_generic_path_completion() {
COMPREPLY=()
trigger=${FZF_COMPLETION_TRIGGER-'**'}
cur="${COMP_WORDS[COMP_CWORD]}"
if [[ "$cur" == *"$trigger" ]]; then
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
base=${cur:0:${#cur}-${#trigger}}
eval "base=$base"
eval "base=$base" 2> /dev/null || return
dir=
[[ $base = *"/"* ]] && dir="$base"
@@ -182,7 +309,7 @@ __fzf_generic_path_completion() {
leftover=${leftover/#\/}
[[ -z "$dir" ]] && dir='.'
[[ "$dir" != "/" ]] && dir="${dir/%\//}"
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
printf "%q " "${item%$3}$3"
done)
matches=${matches% }
@@ -195,7 +322,7 @@ __fzf_generic_path_completion() {
printf '\e[5n'
return 0
fi
dir=$(dirname "$dir")
dir=$(command dirname "$dir")
[[ "$dir" =~ /$ ]] || dir="$dir"/
done
else
@@ -229,16 +356,16 @@ _fzf_complete() {
fi
local cur selected trigger cmd post
post="$(caller 0 | awk '{print $2}')_post"
type -t "$post" > /dev/null 2>&1 || post=cat
post="$(caller 0 | command awk '{print $2}')_post"
type -t "$post" > /dev/null 2>&1 || post='command cat'
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
trigger=${FZF_COMPLETION_TRIGGER-'**'}
cur="${COMP_WORDS[COMP_CWORD]}"
if [[ "$cur" == *"$trigger" ]]; then
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
cur=${cur:0:${#cur}-${#trigger}}
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | tr '\n' ' ')
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | command tr '\n' ' ')
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
if [[ -n "$selected" ]]; then
COMPREPLY=("$selected")
@@ -277,27 +404,61 @@ _fzf_proc_completion() {
}
_fzf_proc_completion_post() {
awk '{print $2}'
command awk '{print $2}'
}
# To use custom hostname lists, override __fzf_list_hosts.
# The function is expected to print hostnames, one per line as well as in the
# desired sorting and with any duplicates removed, to standard output.
#
# e.g.
# # Use bash-completionss _known_hosts_real() for getting the list of hosts
# __fzf_list_hosts() {
# # Set the local attribute for any non-local variable that is set by _known_hosts_real()
# local COMPREPLY=()
# _known_hosts_real ''
# printf '%s\n' "${COMPREPLY[@]}" | command sort -u --version-sort
# }
if ! declare -F __fzf_list_hosts > /dev/null; then
__fzf_list_hosts() {
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | command awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?%]') \
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts 2> /dev/null | command tr ',' '\n' | command tr -d '[' | command awk '{ print $1 " " $1 }') \
<(command grep -v '^\s*\(#\|$\)' /etc/hosts 2> /dev/null | command grep -Fv '0.0.0.0') |
command awk '{if (length($2) > 0) {print $2}}' | command sort -u
}
fi
_fzf_host_completion() {
_fzf_complete +m -- "$@" < <(
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?%]') \
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
awk '{if (length($2) > 0) {print $2}}' | sort -u
)
_fzf_complete +m -- "$@" < <(__fzf_list_hosts)
}
# Values for $1 $2 $3 are described here
# https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion.html
# > the first argument ($1) is the name of the command whose arguments are being completed,
# > the second argument ($2) is the word being completed,
# > and the third argument ($3) is the word preceding the word being completed on the current command line.
_fzf_complete_ssh() {
case $3 in
-i|-F|-E)
_fzf_path_completion "$@"
;;
*)
local user=
[[ "$2" =~ '@' ]] && user="${2%%@*}@"
_fzf_complete +m -- "$@" < <(__fzf_list_hosts | command awk -v user="$user" '{print user $0}')
;;
esac
}
_fzf_var_completion() {
_fzf_complete -m -- "$@" < <(
declare -xp | sed -En 's|^declare [^ ]+ ([^=]+).*|\1|p'
declare -xp | command sed -En 's|^declare [^ ]+ ([^=]+).*|\1|p'
)
}
_fzf_alias_completion() {
_fzf_complete -m -- "$@" < <(
alias | sed -En 's|^alias ([^=]+).*|\1|p'
alias | command sed -En 's|^alias ([^=]+).*|\1|p'
)
}
@@ -351,6 +512,9 @@ for cmd in $d_cmds; do
__fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames"
done
# ssh
__fzf_defc ssh _fzf_complete_ssh "-o default -o bashdefault"
unset cmd d_cmds a_cmds
_fzf_setup_completion() {
@@ -376,7 +540,5 @@ _fzf_setup_completion() {
# Environment variables / Aliases / Hosts / Process
_fzf_setup_completion 'var' export unset printenv
_fzf_setup_completion 'alias' unalias
_fzf_setup_completion 'host' ssh telnet
_fzf_setup_completion 'host' telnet
_fzf_setup_completion 'proc' kill
fi

View File

@@ -9,6 +9,9 @@
# - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty)
[[ -o interactive ]] || return 0
# Both branches of the following `if` do the same thing -- define
# __fzf_completion_options such that `eval $__fzf_completion_options` sets
# all options to the same values they currently have. We'll do just that at
@@ -67,15 +70,12 @@ fi
# control. There are several others that could wreck havoc if they are set
# to values we don't expect. With the following `emulate` command we
# sidestep this issue entirely.
'emulate' 'zsh' '-o' 'no_aliases'
'builtin' 'emulate' 'zsh' && 'builtin' 'setopt' 'no_aliases'
# This brace is the start of try-always block. The `always` part is like
# `finally` in lesser languages. We use it to *always* restore user options.
{
# Bail out if not interactive shell.
[[ -o interactive ]] || return 0
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
if ! declare -f _fzf_compgen_path > /dev/null; then
_fzf_compgen_path() {
@@ -137,7 +137,10 @@ __fzf_generic_path_completion() {
tail=$6
setopt localoptions nonomatch
eval "base=$base"
if [[ $base = *'$('* ]] || [[ $base = *'<('* ]] || [[ $base = *'>('* ]] || [[ $base = *':='* ]] || [[ $base = *'`'* ]]; then
return
fi
eval "base=$base" 2> /dev/null || return
[[ $base = *"/"* ]] && dir="$base"
while [ 1 ]; do
if [[ -z "$dir" || -d ${dir} ]]; then
@@ -145,7 +148,7 @@ __fzf_generic_path_completion() {
leftover=${leftover/#\/}
[ -z "$dir" ] && dir='.'
[ "$dir" != "/" ] && dir="${dir/%\//}"
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-}" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-}" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
item="${item%$suffix}$suffix"
echo -n "${(q)item} "
done)
@@ -215,21 +218,37 @@ _fzf_complete() {
command rm -f "$fifo"
}
_fzf_complete_telnet() {
_fzf_complete +m -- "$@" < <(
command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' |
awk '{if (length($2) > 0) {print $2}}' | sort -u
)
}
_fzf_complete_ssh() {
_fzf_complete +m -- "$@" < <(
# To use custom hostname lists, override __fzf_list_hosts.
# The function is expected to print hostnames, one per line as well as in the
# desired sorting and with any duplicates removed, to standard output.
if ! declare -f __fzf_list_hosts > /dev/null; then
__fzf_list_hosts() {
setopt localoptions nonomatch
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?%]') \
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts 2> /dev/null | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
<(command grep -v '^\s*\(#\|$\)' /etc/hosts 2> /dev/null | command grep -Fv '0.0.0.0') |
awk '{if (length($2) > 0) {print $2}}' | sort -u
)
}
fi
_fzf_complete_telnet() {
_fzf_complete +m -- "$@" < <(__fzf_list_hosts)
}
# The first and the only argument is the LBUFFER without the current word that contains the trigger.
# The current word without the trigger is in the $prefix variable passed from the caller.
_fzf_complete_ssh() {
local tokens=(${(z)1})
case ${tokens[-1]} in
-i|-F|-E)
_fzf_path_completion "$prefix" "$1"
;;
*)
local user=
[[ $prefix =~ @ ]] && user="${prefix%%@*}@"
_fzf_complete +m -- "$@" < <(__fzf_list_hosts | awk -v user="$user" '{print user $0}')
;;
esac
}
_fzf_complete_export() {
@@ -293,6 +312,9 @@ fzf-completion() {
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
if [[ $prefix = *'$('* ]] || [[ $prefix = *'<('* ]] || [[ $prefix = *'>('* ]] || [[ $prefix = *':='* ]] || [[ $prefix = *'`'* ]]; then
return
fi
[ -n "${tokens[-1]}" ] && lbuf=${lbuf:0:-${#tokens[-1]}}
if eval "type _fzf_complete_${cmd} > /dev/null"; then

View File

@@ -11,15 +11,18 @@
# - $FZF_ALT_C_COMMAND
# - $FZF_ALT_C_OPTS
[[ $- =~ i ]] || return 0
# Key bindings
# ------------
__fzf_select__() {
local cmd opts
cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | cut -b3-"}"
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-} -m"
-o -type l -print 2> /dev/null | command cut -b3-"}"
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse --scheme=path ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-} -m"
eval "$cmd" |
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) "$@" |
while read -r item; do
@@ -27,8 +30,6 @@ __fzf_select__() {
done
}
if [[ $- =~ i ]]; then
__fzfcmd() {
[[ -n "${TMUX_PANE-}" ]] && { [[ "${FZF_TMUX:-0}" != 0 ]] || [[ -n "${FZF_TMUX_OPTS-}" ]]; } &&
echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
@@ -42,19 +43,21 @@ fzf-file-widget() {
__fzf_cd__() {
local cmd opts dir
cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
-o -type d -print 2> /dev/null | cut -b3-"}"
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-} +m"
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="$opts" $(__fzfcmd)) && printf 'builtin cd -- %q' "$dir"
cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
-o -type d -print 2> /dev/null | command cut -b3-"}"
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse --scheme=path ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-} +m"
dir=$(set +o pipefail; eval "$cmd" | FZF_DEFAULT_OPTS="$opts" $(__fzfcmd)) && printf 'builtin cd -- %q' "$dir"
}
if command -v perl > /dev/null; then
__fzf_history__() {
local output opts script
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort ${FZF_CTRL_R_OPTS-} +m --read0"
script='BEGIN { getc; $/ = "\n\t"; $HISTCOUNT = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCOUNT - $. . "\t$_" if !$seen{$_}++'
output=$(
set +o pipefail
builtin fc -lnr -2147483648 |
last_hist=$(HISTTIMEFORMAT='' builtin history 1) perl -n -l0 -e "$script" |
last_hist=$(HISTTIMEFORMAT='' builtin history 1) command perl -n -l0 -e "$script" |
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) --query "$READLINE_LINE"
) || return
READLINE_LINE=${output#*$'\t'}
@@ -64,6 +67,36 @@ __fzf_history__() {
READLINE_POINT=0x7fffffff
fi
}
else # awk - fallback for POSIX systems
__fzf_history__() {
local output opts script n x y z d
if [[ -z $__fzf_awk ]]; then
__fzf_awk=awk
# choose the faster mawk if: it's installed && build date >= 20230322 && version >= 1.3.4
IFS=' .' read n x y z d <<< $(command mawk -W version 2> /dev/null)
[[ $n == mawk ]] && (( d >= 20230302 && (x *1000 +y) *1000 +z >= 1003004 )) && __fzf_awk=mawk
fi
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort ${FZF_CTRL_R_OPTS-} +m --read0"
[[ $(HISTTIMEFORMAT='' builtin history 1) =~ [[:digit:]]+ ]] # how many history entries
script='function P(b) { ++n; sub(/^[ *]/, "", b); if (!seen[b]++) { printf "%d\t%s%c", '$((BASH_REMATCH + 1))' - n, b, 0 } }
NR==1 { b = substr($0, 2); next }
/^\t/ { P(b); b = substr($0, 2); next }
{ b = b RS $0 }
END { if (NR) P(b) }'
output=$(
set +o pipefail
builtin fc -lnr -2147483648 2> /dev/null | # ( $'\t '<lines>$'\n' )* ; <lines> ::= [^\n]* ( $'\n'<lines> )*
command $__fzf_awk "$script" | # ( <counter>$'\t'<lines>$'\000' )*
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) --query "$READLINE_LINE"
) || return
READLINE_LINE=${output#*$'\t'}
if [[ -z "$READLINE_POINT" ]]; then
echo "$READLINE_LINE"
else
READLINE_POINT=0x7fffffff
fi
}
fi
# Required to refresh the prompt after fzf
bind -m emacs-standard '"\er": redraw-current-line'
@@ -79,7 +112,7 @@ if (( BASH_VERSINFO[0] < 4 )); then
bind -m vi-insert '"\C-t": "\C-z\C-t\C-z"'
# CTRL-R - Paste the selected command from history into the command line
bind -m emacs-standard '"\C-r": "\C-e \C-u\C-y\ey\C-u"$(__fzf_history__)"\e\C-e\er"'
bind -m emacs-standard '"\C-r": "\C-e \C-u\C-y\ey\C-u`__fzf_history__`\e\C-e\er"'
bind -m vi-command '"\C-r": "\C-z\C-r\C-z"'
bind -m vi-insert '"\C-r": "\C-z\C-r\C-z"'
else
@@ -98,5 +131,3 @@ fi
bind -m emacs-standard '"\ec": " \C-b\C-k \C-u`__fzf_cd__`\e\C-e\er\C-m\C-y\C-h\e \C-y\ey\C-x\C-x\C-d"'
bind -m vi-command '"\ec": "\C-z\ec\C-z"'
bind -m vi-insert '"\ec": "\C-z\ec\C-z"'
fi

View File

@@ -11,6 +11,9 @@
# - $FZF_ALT_C_COMMAND
# - $FZF_ALT_C_OPTS
status is-interactive; or exit 0
# Key bindings
# ------------
function fzf_key_bindings
@@ -22,17 +25,17 @@ function fzf_key_bindings
set -l fzf_query $commandline[2]
set -l prefix $commandline[3]
# "-path \$dir'*/\\.*'" matches hidden files/folders inside $dir but not
# "-path \$dir'*/.*'" matches hidden files/folders inside $dir but not
# $dir itself, even if hidden.
test -n "$FZF_CTRL_T_COMMAND"; or set -l FZF_CTRL_T_COMMAND "
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
command find -L \$dir -mindepth 1 \\( -path \$dir'*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | sed 's@^\./@@'"
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
end
if [ -z "$result" ]
@@ -79,11 +82,11 @@ function fzf_key_bindings
set -l prefix $commandline[3]
test -n "$FZF_ALT_C_COMMAND"; or set -l FZF_ALT_C_COMMAND "
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
command find -L \$dir -mindepth 1 \\( -path \$dir'*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
-o -type d -print 2> /dev/null | sed 's@^\./@@'"
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
if [ -n "$result" ]

View File

@@ -11,6 +11,9 @@
# - $FZF_ALT_C_COMMAND
# - $FZF_ALT_C_OPTS
[[ -o interactive ]] || return 0
# Key bindings
# ------------
@@ -32,21 +35,19 @@ else
}
fi
'emulate' 'zsh' '-o' 'no_aliases'
'builtin' 'emulate' 'zsh' && 'builtin' 'setopt' 'no_aliases'
{
[[ -o interactive ]] || return 0
# CTRL-T - Paste the selected file path(s) into the command line
__fsel() {
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | cut -b3-"}"
setopt localoptions pipefail no_aliases 2> /dev/null
local item
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-}" $(__fzfcmd) -m "$@" | while read item; do
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-}" $(__fzfcmd) -m "$@" | while read item; do
echo -n "${(q)item} "
done
local ret=$?
@@ -72,10 +73,10 @@ bindkey -M viins '^T' fzf-file-widget
# ALT-C - cd into the selected directory
fzf-cd-widget() {
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
-o -type d -print 2> /dev/null | cut -b3-"}"
setopt localoptions pipefail no_aliases 2> /dev/null
local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-}" $(__fzfcmd) +m)"
local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-}" $(__fzfcmd) +m)"
if [[ -z "$dir" ]]; then
zle redisplay
return 0

View File

@@ -221,9 +221,9 @@ func charClassOfAscii(char rune) charClass {
return charUpper
} else if char >= '0' && char <= '9' {
return charNumber
} else if strings.IndexRune(whiteChars, char) >= 0 {
} else if strings.ContainsRune(whiteChars, char) {
return charWhite
} else if strings.IndexRune(delimiterChars, char) >= 0 {
} else if strings.ContainsRune(delimiterChars, char) {
return charDelimiter
}
return charNonWord
@@ -240,7 +240,7 @@ func charClassOfNonAscii(char rune) charClass {
return charLetter
} else if unicode.IsSpace(char) {
return charWhite
} else if strings.IndexRune(delimiterChars, char) >= 0 {
} else if strings.ContainsRune(delimiterChars, char) {
return charDelimiter
}
return charNonWord

View File

@@ -381,10 +381,19 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
state.attr = state.attr | tui.Reverse
case 9:
state.attr = state.attr | tui.StrikeThrough
case 22:
state.attr = state.attr &^ tui.Bold
state.attr = state.attr &^ tui.Dim
case 23: // tput rmso
state.attr = state.attr &^ tui.Italic
case 24: // tput rmul
state.attr = state.attr &^ tui.Underline
case 25:
state.attr = state.attr &^ tui.Blink
case 27:
state.attr = state.attr &^ tui.Reverse
case 29:
state.attr = state.attr &^ tui.StrikeThrough
case 0:
state.fg = -1
state.bg = -1

View File

@@ -58,9 +58,9 @@ var defaultCommand string
func init() {
if !util.IsWindows() {
defaultCommand = `set -o pipefail; command find -L . -mindepth 1 \( -path '*/\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-`
defaultCommand = `set -o pipefail; command find -L . -mindepth 1 \( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-`
} else if os.Getenv("TERM") == "cygwin" {
defaultCommand = `sh -c "command find -L . -mindepth 1 -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-"`
defaultCommand = `sh -c "command find -L . -mindepth 1 -path '*/.*' -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-"`
}
}

View File

@@ -2,7 +2,6 @@ package fzf
import (
"errors"
"io/ioutil"
"os"
"strings"
)
@@ -26,12 +25,12 @@ func NewHistory(path string, maxSize int) (*History, error) {
}
// Read history file
data, err := ioutil.ReadFile(path)
data, err := os.ReadFile(path)
if err != nil {
// If it doesn't exist, check if we can create a file with the name
if os.IsNotExist(err) {
data = []byte{}
if err := ioutil.WriteFile(path, data, 0600); err != nil {
if err := os.WriteFile(path, data, 0600); err != nil {
return nil, fmtError(err)
}
} else {
@@ -62,11 +61,11 @@ func (h *History) append(line string) error {
lines = lines[len(lines)-h.maxSize:]
}
h.lines = append(lines, "")
return ioutil.WriteFile(h.path, []byte(strings.Join(h.lines, "\n")), 0600)
return os.WriteFile(h.path, []byte(strings.Join(h.lines, "\n")), 0600)
}
func (h *History) override(str string) {
// You can update the history but they're not written to the file
// You can update the history, but they're not written to the file
if h.cursor == len(h.lines)-1 {
h.lines[h.cursor] = str
} else if h.cursor < len(h.lines)-1 {

View File

@@ -1,7 +1,6 @@
package fzf
import (
"io/ioutil"
"os"
"runtime"
"testing"
@@ -25,7 +24,7 @@ func TestHistory(t *testing.T) {
}
}
f, _ := ioutil.TempFile("", "fzf-history")
f, _ := os.CreateTemp("", "fzf-history")
f.Close()
{ // Append lines

View File

@@ -23,10 +23,10 @@ func randResult() Result {
}
func TestEmptyMerger(t *testing.T) {
assert(t, EmptyMerger.Length() == 0, "Not empty")
assert(t, EmptyMerger.count == 0, "Invalid count")
assert(t, len(EmptyMerger.lists) == 0, "Invalid lists")
assert(t, len(EmptyMerger.merged) == 0, "Invalid merged list")
assert(t, EmptyMerger(0).Length() == 0, "Not empty")
assert(t, EmptyMerger(0).count == 0, "Invalid count")
assert(t, len(EmptyMerger(0).lists) == 0, "Invalid lists")
assert(t, len(EmptyMerger(0).merged) == 0, "Invalid merged list")
}
func buildLists(partiallySorted bool) ([][]Result, []Result) {

View File

@@ -63,7 +63,7 @@ const usage = `usage: fzf [options]
(default: 10)
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
--border[=STYLE] Draw border around the finder
[rounded|sharp|bold|block|double|horizontal|vertical|
[rounded|sharp|bold|block|thinblock|double|horizontal|vertical|
top|bottom|left|right|none] (default: rounded)
--border-label=LABEL Label to print on the border
--border-label-pos=COL Position of the border label
@@ -72,7 +72,8 @@ const usage = `usage: fzf [options]
(default: 0 or center)
--margin=MARGIN Screen margin (TRBL | TB,RL | T,RL,B | T,R,B,L)
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
--info=STYLE Finder info style [default|hidden|inline|inline:SEPARATOR]
--info=STYLE Finder info style
[default|right|hidden|inline[:SEPARATOR]|inline-right]
--separator=STR String to form horizontal separator on info line
--no-separator Hide info line separator
--scrollbar[=C1[C2]] Scrollbar character(s) (each for main and preview window)
@@ -124,6 +125,7 @@ const usage = `usage: fzf [options]
FZF_DEFAULT_COMMAND Default command to use when input is tty
FZF_DEFAULT_OPTS Default options
(e.g. '--layout=reverse --inline-info')
FZF_API_KEY X-API-Key header for HTTP server (--listen)
`
@@ -194,10 +196,16 @@ type infoStyle int
const (
infoDefault infoStyle = iota
infoRight
infoInline
infoInlineRight
infoHidden
)
func (s infoStyle) noExtraLine() bool {
return s == infoInline || s == infoInlineRight || s == infoHidden
}
type labelOpts struct {
label string
column int
@@ -546,6 +554,8 @@ func parseBorder(str string, optional bool) tui.BorderShape {
return tui.BorderBold
case "block":
return tui.BorderBlock
case "thinblock":
return tui.BorderThinBlock
case "double":
return tui.BorderDouble
case "horizontal":
@@ -566,7 +576,7 @@ func parseBorder(str string, optional bool) tui.BorderShape {
if optional && str == "" {
return tui.DefaultBorderShape
}
errorExit("invalid border style (expected: rounded|sharp|bold|block|double|horizontal|vertical|top|bottom|left|right|none)")
errorExit("invalid border style (expected: rounded|sharp|bold|block|thinblock|double|horizontal|vertical|top|bottom|left|right|none)")
}
return tui.BorderNone
}
@@ -692,8 +702,24 @@ func parseKeyChordsImpl(str string, message string, exit func(string)) map[tui.E
add(tui.LeftClick)
case "right-click":
add(tui.RightClick)
case "shift-left-click":
add(tui.SLeftClick)
case "shift-right-click":
add(tui.SRightClick)
case "double-click":
add(tui.DoubleClick)
case "scroll-up":
add(tui.ScrollUp)
case "scroll-down":
add(tui.ScrollDown)
case "shift-scroll-up":
add(tui.SScrollUp)
case "shift-scroll-down":
add(tui.SScrollDown)
case "preview-scroll-up":
add(tui.PreviewScrollUp)
case "preview-scroll-down":
add(tui.PreviewScrollDown)
case "f10":
add(tui.F10)
case "f11":
@@ -1105,6 +1131,8 @@ func parseActionList(masked string, original string, prevActions []*action, putA
appendAction(actToggleSearch)
case "toggle-track":
appendAction(actToggleTrack)
case "toggle-header":
appendAction(actToggleHeader)
case "track":
appendAction(actTrack)
case "select":
@@ -1151,6 +1179,10 @@ func parseActionList(masked string, original string, prevActions []*action, putA
appendAction(actTogglePreviewWrap)
case "toggle-sort":
appendAction(actToggleSort)
case "offset-up":
appendAction(actOffsetUp)
case "offset-down":
appendAction(actOffsetDown)
case "preview-top":
appendAction(actPreviewTop)
case "preview-bottom":
@@ -1374,8 +1406,12 @@ func parseInfoStyle(str string) (infoStyle, string) {
switch str {
case "default":
return infoDefault, ""
case "right":
return infoRight, ""
case "inline":
return infoInline, defaultInfoSep
case "inline-right":
return infoInlineRight, ""
case "hidden":
return infoHidden, ""
default:
@@ -1383,7 +1419,7 @@ func parseInfoStyle(str string) (infoStyle, string) {
if strings.HasPrefix(str, prefix) {
return infoInline, strings.ReplaceAll(str[len(prefix):], "\n", " ")
}
errorExit("invalid info style (expected: default|hidden|inline|inline:SEPARATOR)")
errorExit("invalid info style (expected: default|right|hidden|inline[:SEPARATOR]|inline-right)")
}
return infoDefault, ""
}
@@ -1438,6 +1474,8 @@ func parsePreviewWindowImpl(opts *previewOpts, input string, exit func(string))
opts.border = tui.BorderBold
case "border-block":
opts.border = tui.BorderBlock
case "border-thinblock":
opts.border = tui.BorderThinBlock
case "border-double":
opts.border = tui.BorderDouble
case "noborder", "border-none":

View File

@@ -2,7 +2,7 @@ package fzf
import (
"fmt"
"io/ioutil"
"os"
"testing"
"github.com/junegunn/fzf/src/tui"
@@ -357,7 +357,7 @@ func TestDefaultCtrlNP(t *testing.T) {
check([]string{"--bind=ctrl-n:accept"}, tui.CtrlN, actAccept)
check([]string{"--bind=ctrl-p:accept"}, tui.CtrlP, actAccept)
f, _ := ioutil.TempFile("", "fzf-history")
f, _ := os.CreateTemp("", "fzf-history")
f.Close()
hist := "--history=" + f.Name()
check([]string{hist}, tui.CtrlN, actNextHistory)

View File

@@ -6,6 +6,7 @@ import (
"io"
"os"
"os/exec"
"path"
"path/filepath"
"sync"
"sync/atomic"
@@ -98,8 +99,17 @@ func (r *Reader) ReadSource() {
r.startEventPoller()
var success bool
if util.IsTty() {
// The default command for *nix requires bash
// The default command for *nix requires a shell that supports "pipefail"
// https://unix.stackexchange.com/a/654932/62171
shell := "bash"
currentShell := os.Getenv("SHELL")
currentShellName := path.Base(currentShell)
for _, shellName := range []string{"bash", "zsh", "ksh", "ash", "hush", "mksh", "yash"} {
if currentShellName == shellName {
shell = currentShell
break
}
}
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
if len(cmd) == 0 {
if defaultCommand != "" {

View File

@@ -3,23 +3,44 @@ package fzf
import (
"bufio"
"bytes"
"crypto/subtle"
"errors"
"fmt"
"net"
"os"
"regexp"
"strconv"
"strings"
"time"
)
var getRegex *regexp.Regexp
func init() {
getRegex = regexp.MustCompile(`^GET /(?:\?([a-z0-9=&]+))? HTTP`)
}
type getParams struct {
limit int
offset int
}
const (
crlf = "\r\n"
httpOk = "HTTP/1.1 200 OK" + crlf
httpBadRequest = "HTTP/1.1 400 Bad Request" + crlf
httpUnauthorized = "HTTP/1.1 401 Unauthorized" + crlf
httpReadTimeout = 10 * time.Second
maxContentLength = 1024 * 1024
)
func startHttpServer(port int, channel chan []*action) (error, int) {
type httpServer struct {
apiKey []byte
actionChannel chan []*action
responseChannel chan string
}
func startHttpServer(port int, actionChannel chan []*action, responseChannel chan string) (error, int) {
if port < 0 {
return nil, port
}
@@ -41,6 +62,12 @@ func startHttpServer(port int, channel chan []*action) (error, int) {
}
}
server := httpServer{
apiKey: []byte(os.Getenv("FZF_API_KEY")),
actionChannel: actionChannel,
responseChannel: responseChannel,
}
go func() {
for {
conn, err := listener.Accept()
@@ -51,7 +78,7 @@ func startHttpServer(port int, channel chan []*action) (error, int) {
continue
}
}
conn.Write([]byte(handleHttpRequest(conn, channel)))
conn.Write([]byte(server.handleHttpRequest(conn)))
conn.Close()
}
listener.Close()
@@ -66,12 +93,22 @@ func startHttpServer(port int, channel chan []*action) (error, int) {
// * No --listen: 2.8MB
// * --listen with net/http: 5.7MB
// * --listen w/o net/http: 3.3MB
func handleHttpRequest(conn net.Conn, channel chan []*action) string {
func (server *httpServer) handleHttpRequest(conn net.Conn) string {
contentLength := 0
apiKey := ""
body := ""
bad := func(message string) string {
answer := func(code string, message string) string {
message += "\n"
return httpBadRequest + fmt.Sprintf("Content-Length: %d%s", len(message), crlf+crlf+message)
return code + fmt.Sprintf("Content-Length: %d%s", len(message), crlf+crlf+message)
}
unauthorized := func(message string) string {
return answer(httpUnauthorized, message)
}
bad := func(message string) string {
return answer(httpBadRequest, message)
}
good := func(message string) string {
return answer(httpOk+"Content-Type: application/json"+crlf, message)
}
conn.SetReadDeadline(time.Now().Add(httpReadTimeout))
scanner := bufio.NewScanner(conn)
@@ -92,7 +129,12 @@ func handleHttpRequest(conn net.Conn, channel chan []*action) string {
text := scanner.Text()
switch section {
case 0:
if !strings.HasPrefix(text, "POST / HTTP") {
getMatch := getRegex.FindStringSubmatch(text)
if len(getMatch) > 0 {
server.actionChannel <- []*action{{t: actResponse, a: getMatch[1]}}
response := <-server.responseChannel
return good(response)
} else if !strings.HasPrefix(text, "POST / HTTP") {
return bad("invalid request method")
}
section++
@@ -105,18 +147,27 @@ func handleHttpRequest(conn net.Conn, channel chan []*action) string {
continue
}
pair := strings.SplitN(text, ":", 2)
if len(pair) == 2 && strings.ToLower(pair[0]) == "content-length" {
if len(pair) == 2 {
switch strings.ToLower(pair[0]) {
case "content-length":
length, err := strconv.Atoi(strings.TrimSpace(pair[1]))
if err != nil || length <= 0 || length > maxContentLength {
return bad("invalid content length")
}
contentLength = length
case "x-api-key":
apiKey = strings.TrimSpace(pair[1])
}
}
case 2:
body += text
}
}
if len(server.apiKey) != 0 && subtle.ConstantTimeCompare([]byte(apiKey), server.apiKey) != 1 {
return unauthorized("invalid api key")
}
if len(body) < contentLength {
return bad("incomplete request")
}
@@ -133,6 +184,28 @@ func handleHttpRequest(conn net.Conn, channel chan []*action) string {
return bad("no action specified")
}
channel <- actions
server.actionChannel <- actions
return httpOk
}
func parseGetParams(query string) getParams {
params := getParams{limit: 100, offset: 0}
for _, pair := range strings.Split(query, "&") {
parts := strings.SplitN(pair, "=", 2)
if len(parts) == 2 {
switch parts[0] {
case "limit":
val, err := strconv.Atoi(parts[1])
if err == nil {
params.limit = val
}
case "offset":
val, err := strconv.Atoi(parts[1])
if err == nil {
params.offset = val
}
}
}
}
return params
}

View File

@@ -2,9 +2,9 @@ package fzf
import (
"bufio"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"math"
"os"
"os/exec"
@@ -51,6 +51,7 @@ var whiteSuffix *regexp.Regexp
var offsetComponentRegex *regexp.Regexp
var offsetTrimCharsRegex *regexp.Regexp
var activeTempFiles []string
var passThroughRegex *regexp.Regexp
const clearCode string = "\x1b[2J"
@@ -60,6 +61,11 @@ func init() {
offsetComponentRegex = regexp.MustCompile(`([+-][0-9]+)|(-?/[1-9][0-9]*)`)
offsetTrimCharsRegex = regexp.MustCompile(`[^0-9/+-]`)
activeTempFiles = []string{}
// Parts of the preview output that should be passed through to the terminal
// * https://github.com/tmux/tmux/wiki/FAQ#what-is-the-passthrough-escape-sequence-and-how-do-i-use-it
// * https://sw.kovidgoyal.net/kitty/graphics-protocol
passThroughRegex = regexp.MustCompile(`\x1bPtmux;\x1b\x1b.*?[^\x1b]\x1b\\|\x1b_G.*?\x1b\\`)
}
type jumpMode int
@@ -126,6 +132,7 @@ type eachLine struct {
}
type itemLine struct {
offset int
current bool
selected bool
label string
@@ -144,6 +151,24 @@ var emptyLine = itemLine{}
type labelPrinter func(tui.Window, int)
type StatusItem struct {
Index int `json:"index"`
Text string `json:"text"`
}
type Status struct {
Reading bool `json:"reading"`
Progress int `json:"progress"`
Query string `json:"query"`
Position int `json:"position"`
Sort bool `json:"sort"`
TotalCount int `json:"totalCount"`
MatchCount int `json:"matchCount"`
Current *StatusItem `json:"current"`
Matches []StatusItem `json:"matches"`
Selected []StatusItem `json:"selected"`
}
// Terminal represents terminal input/output
type Terminal struct {
initDelay time.Duration
@@ -193,6 +218,7 @@ type Terminal struct {
printQuery bool
history *History
cycle bool
headerVisible bool
headerFirst bool
headerLines int
header []string
@@ -244,7 +270,8 @@ type Terminal struct {
sigstop bool
startChan chan fitpad
killChan chan int
serverChan chan []*action
serverInputChan chan []*action
serverOutputChan chan string
eventChan chan tui.Event
slab *util.Slab
theme *tui.ColorTheme
@@ -342,6 +369,7 @@ const (
actToggleIn
actToggleOut
actToggleTrack
actToggleHeader
actTrack
actDown
actUp
@@ -350,6 +378,8 @@ const (
actPosition
actHalfPageUp
actHalfPageDown
actOffsetUp
actOffsetDown
actJump
actJumpAccept
actPrintQuery
@@ -396,6 +426,7 @@ const (
actUnbind
actRebind
actBecome
actResponse
)
type placeholderFlags struct {
@@ -496,6 +527,16 @@ func defaultKeymap() map[tui.Event][]*action {
add(tui.Mouse, actMouse)
add(tui.LeftClick, actIgnore)
add(tui.RightClick, actToggle)
add(tui.SLeftClick, actToggle)
add(tui.SRightClick, actToggle)
add(tui.ScrollUp, actUp)
add(tui.ScrollDown, actDown)
keymap[tui.SScrollUp.AsEvent()] = toActions(actToggle, actUp)
keymap[tui.SScrollDown.AsEvent()] = toActions(actToggle, actDown)
add(tui.PreviewScrollUp, actPreviewUp)
add(tui.PreviewScrollDown, actPreviewDown)
return keymap
}
@@ -531,13 +572,6 @@ func evaluateHeight(opts *Options, termHeight int) int {
// NewTerminal returns new Terminal object
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
input := trimQuery(opts.Query)
var header []string
switch opts.Layout {
case layoutDefault, layoutReverseList:
header = reverseStringArray(opts.Header)
default:
header = opts.Header
}
var delay time.Duration
if opts.Tac {
delay = initialDelayTac
@@ -565,7 +599,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
if previewBox != nil && opts.Preview.aboveOrBelow() {
effectiveMinHeight += 1 + borderLines(opts.Preview.border)
}
if opts.InfoStyle != infoDefault {
if opts.InfoStyle.noExtraLine() {
effectiveMinHeight--
}
effectiveMinHeight += borderLines(opts.BorderShape)
@@ -629,10 +663,11 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
cleanExit: opts.ClearOnExit,
paused: opts.Phony,
cycle: opts.Cycle,
headerVisible: true,
headerFirst: opts.HeaderFirst,
headerLines: opts.HeaderLines,
header: []string{},
header0: header,
header0: opts.Header,
ellipsis: opts.Ellipsis,
ansi: opts.Ansi,
tabstop: opts.Tabstop,
@@ -661,7 +696,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
theme: opts.Theme,
startChan: make(chan fitpad, 1),
killChan: make(chan int),
serverChan: make(chan []*action, 10),
serverInputChan: make(chan []*action, 10),
serverOutputChan: make(chan string),
eventChan: make(chan tui.Event, 1),
tui: renderer,
initFunc: func() { renderer.Init() },
@@ -707,7 +743,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
_, t.hasLoadActions = t.keymap[tui.Load.AsEvent()]
if t.listenPort != nil {
err, port := startHttpServer(*t.listenPort, t.serverChan)
err, port := startHttpServer(*t.listenPort, t.serverInputChan, t.serverOutputChan)
if err != nil {
errorExit(err.Error())
}
@@ -727,7 +763,7 @@ func (t *Terminal) environ() []string {
func borderLines(shape tui.BorderShape) int {
switch shape {
case tui.BorderHorizontal, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderDouble:
case tui.BorderHorizontal, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble:
return 2
case tui.BorderTop, tui.BorderBottom:
return 1
@@ -735,9 +771,16 @@ func borderLines(shape tui.BorderShape) int {
return 0
}
func (t *Terminal) visibleHeaderLines() int {
if !t.headerVisible {
return 0
}
return len(t.header0) + t.headerLines
}
// Extra number of lines needed to display fzf
func (t *Terminal) extraLines() int {
extra := len(t.header0) + t.headerLines + 1
extra := t.visibleHeaderLines() + 1
if !t.noInfoLine() {
extra++
}
@@ -763,7 +806,7 @@ func (t *Terminal) ansiLabelPrinter(str string, color *tui.ColorPair, fill bool)
runes := []rune(text)
// Simpler printer for strings without ANSI colors or tab characters
if colors == nil && strings.IndexRune(str, '\t') < 0 {
if colors == nil && !strings.ContainsRune(str, '\t') {
length := util.StringWidth(str)
if length == 0 {
return nil, 0
@@ -778,7 +821,7 @@ func (t *Terminal) ansiLabelPrinter(str string, color *tui.ColorPair, fill bool)
window.CPrint(*color, str)
}
}
return printFn, len(text)
return printFn, length
}
// Printer that correctly handles ANSI color codes and tab characters
@@ -845,7 +888,7 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) {
}
func (t *Terminal) noInfoLine() bool {
return t.infoStyle != infoDefault
return t.infoStyle.noExtraLine()
}
func getScrollbar(total int, height int, offset int) (int, int) {
@@ -889,21 +932,8 @@ func (t *Terminal) UpdateCount(cnt int, final bool, failedCommand *string) {
}
}
func reverseStringArray(input []string) []string {
size := len(input)
reversed := make([]string, size)
for idx, str := range input {
reversed[size-idx-1] = str
}
return reversed
}
func (t *Terminal) changeHeader(header string) bool {
lines := strings.Split(strings.TrimSuffix(header, "\n"), "\n")
switch t.layout {
case layoutDefault, layoutReverseList:
lines = reverseStringArray(lines)
}
needFullRedraw := len(t.header0) != len(lines)
t.header0 = lines
return needFullRedraw
@@ -1085,7 +1115,7 @@ func (t *Terminal) adjustMarginAndPadding() (int, int, [4]int, [4]int) {
if idx == 3 {
extraMargin[idx] += 1 + bw
}
case tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderDouble:
case tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble:
extraMargin[idx] += 1 + bw*(idx%2)
}
marginInt[idx] = sizeSpecToInt(idx, sizeSpec) + extraMargin[idx]
@@ -1178,7 +1208,7 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
t.border = t.tui.NewWindow(
marginInt[0], marginInt[3], width+(1+bw), height,
false, tui.MakeBorderStyle(tui.BorderRight, t.unicode))
case tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderDouble:
case tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble:
t.border = t.tui.NewWindow(
marginInt[0]-1, marginInt[3]-(1+bw), width+(1+bw)*2, height+2,
false, tui.MakeBorderStyle(t.borderShape, t.unicode))
@@ -1212,7 +1242,7 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
}
t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
switch previewOpts.border {
case tui.BorderSharp, tui.BorderRounded, tui.BorderBold, tui.BorderBlock, tui.BorderDouble:
case tui.BorderSharp, tui.BorderRounded, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble:
pwidth -= (1 + bw) * 2
pheight -= 2
x += 1 + bw
@@ -1356,7 +1386,7 @@ func (t *Terminal) printLabel(window tui.Window, render labelPrinter, opts label
}
switch borderShape {
case tui.BorderHorizontal, tui.BorderTop, tui.BorderBottom, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderDouble:
case tui.BorderHorizontal, tui.BorderTop, tui.BorderBottom, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble:
if redrawBorder {
window.DrawHBorder()
}
@@ -1387,7 +1417,7 @@ func (t *Terminal) move(y int, x int, clear bool) {
case layoutDefault:
y = h - y - 1
case layoutReverseList:
n := 2 + len(t.header0) + len(t.header)
n := 2 + t.visibleHeaderLines()
if t.noInfoLine() {
n--
}
@@ -1431,7 +1461,7 @@ func (t *Terminal) promptLine() int {
if !t.noInfoLine() {
max--
}
return util.Min(len(t.header0)+t.headerLines, max)
return util.Min(t.visibleHeaderLines(), max)
}
return 0
}
@@ -1464,9 +1494,7 @@ func (t *Terminal) trimMessage(message string, maxWidth int) string {
func (t *Terminal) printInfo() {
pos := 0
line := t.promptLine()
switch t.infoStyle {
case infoDefault:
t.move(line+1, 0, t.separatorLen == 0)
printSpinner := func() {
if t.reading {
duration := int64(spinnerDuration)
idx := (time.Now().UnixNano() % (duration * int64(len(t.spinner)))) / duration
@@ -1474,8 +1502,18 @@ func (t *Terminal) printInfo() {
} else {
t.window.Print(" ") // Clear spinner
}
}
switch t.infoStyle {
case infoDefault:
t.move(line+1, 0, t.separatorLen == 0)
printSpinner()
t.move(line+1, 2, false)
pos = 2
case infoRight:
t.move(line+1, 0, false)
case infoInlineRight:
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
t.move(line, pos, true)
case infoInline:
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
str := t.infoSep
@@ -1523,19 +1561,60 @@ func (t *Terminal) printInfo() {
if t.failed != nil && t.count == 0 {
output = fmt.Sprintf("[Command failed: %s]", *t.failed)
}
printSeparator := func(fillLength int, pad bool) {
// --------_
if t.separatorLen > 0 {
t.separator(t.window, fillLength)
t.window.Print(" ")
} else if pad {
t.window.Print(strings.Repeat(" ", fillLength+1))
}
}
if t.infoStyle == infoRight {
maxWidth := t.window.Width()
if t.reading {
// Need space for spinner and a margin column
maxWidth -= 2
}
output = t.trimMessage(output, maxWidth)
fillLength := t.window.Width() - len(output) - 2
if t.reading {
if fillLength >= 2 {
printSeparator(fillLength-2, true)
}
printSpinner()
t.window.Print(" ")
} else if fillLength >= 0 {
printSeparator(fillLength, true)
}
t.window.CPrint(tui.ColInfo, output)
return
}
if t.infoStyle == infoInlineRight {
pos = util.Max(pos, t.window.Width()-util.StringWidth(output)-3)
if pos >= t.window.Width() {
return
}
t.move(line, pos, false)
printSpinner()
t.window.Print(" ")
pos += 2
}
maxWidth := t.window.Width() - pos
output = t.trimMessage(output, maxWidth)
t.window.CPrint(tui.ColInfo, output)
fillLength := maxWidth - len(output) - 2
if t.separatorLen > 0 && fillLength > 0 {
if fillLength > 0 {
t.window.CPrint(tui.ColSeparator, " ")
t.separator(t.window, fillLength)
printSeparator(fillLength, false)
}
}
func (t *Terminal) printHeader() {
if len(t.header0)+len(t.header) == 0 {
if t.visibleHeaderLines() == 0 {
return
}
max := t.window.Height()
@@ -1546,8 +1625,16 @@ func (t *Terminal) printHeader() {
}
}
var state *ansiState
needReverse := false
switch t.layout {
case layoutDefault, layoutReverseList:
needReverse = true
}
for idx, lineStr := range append(append([]string{}, t.header0...), t.header...) {
line := idx
if needReverse && idx < len(t.header0) {
line = len(t.header0) - idx - 1
}
if !t.headerFirst {
line++
if !t.noInfoLine() {
@@ -1563,7 +1650,8 @@ func (t *Terminal) printHeader() {
text: util.ToChars([]byte(trimmed)),
colors: colors}
t.move(line, 2, true)
t.move(line, 0, true)
t.window.Print(" ")
t.printHighlighted(Result{item: item},
tui.ColHeader, tui.ColHeader, false, false)
}
@@ -1580,13 +1668,13 @@ func (t *Terminal) printList() {
if t.layout == layoutDefault {
i = maxy - 1 - j
}
line := i + 2 + len(t.header0) + len(t.header)
line := i + 2 + t.visibleHeaderLines()
if t.noInfoLine() {
line--
}
if i < count {
t.printItem(t.merger.Get(i+t.offset), line, i, i == t.cy-t.offset, i >= barStart && i < barStart+barLength)
} else if t.prevLines[i] != emptyLine {
} else if t.prevLines[i] != emptyLine || t.prevLines[i].offset != line {
t.prevLines[i] = emptyLine
t.move(line, 0, true)
}
@@ -1608,11 +1696,12 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool, bar b
}
// Avoid unnecessary redraw
newLine := itemLine{current: current, selected: selected, label: label,
newLine := itemLine{offset: line, current: current, selected: selected, label: label,
result: result, queryLen: len(t.input), width: 0, bar: bar}
prevLine := t.prevLines[i]
forceRedraw := prevLine.offset != newLine.offset
printBar := func() {
if len(t.scrollbar) > 0 && bar != prevLine.bar {
if len(t.scrollbar) > 0 && (bar != prevLine.bar || forceRedraw) {
t.prevLines[i].bar = bar
t.move(line, t.window.Width()-1, true)
if bar {
@@ -1621,7 +1710,8 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool, bar b
}
}
if prevLine.current == newLine.current &&
if !forceRedraw &&
prevLine.current == newLine.current &&
prevLine.selected == newLine.selected &&
prevLine.label == newLine.label &&
prevLine.queryLen == newLine.queryLen &&
@@ -1630,7 +1720,7 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool, bar b
return
}
t.move(line, 0, false)
t.move(line, 0, forceRedraw)
if current {
if len(label) == 0 {
t.window.CPrint(tui.ColCurrentCursorEmpty, t.pointerEmpty)
@@ -1823,7 +1913,7 @@ func (t *Terminal) renderPreviewSpinner() {
if !t.previewer.scrollable {
if maxWidth > 0 {
t.pwindow.Move(0, maxWidth-1)
t.pwindow.CPrint(tui.ColSpinner, spin)
t.pwindow.CPrint(tui.ColPreviewSpinner, spin)
}
} else {
offsetString := fmt.Sprintf("%d/%d", t.previewer.offset+1, numLines)
@@ -1835,7 +1925,7 @@ func (t *Terminal) renderPreviewSpinner() {
pos := maxWidth - t.displayWidth(offsetRunes)
t.pwindow.Move(0, pos)
if maxWidth > 0 {
t.pwindow.CPrint(tui.ColSpinner, spin)
t.pwindow.CPrint(tui.ColPreviewSpinner, spin)
t.pwindow.CPrint(tui.ColInfo.WithAttr(tui.Reverse), string(offsetRunes))
}
}
@@ -1886,7 +1976,13 @@ func (t *Terminal) renderPreviewText(height int, lines []string, lineNo int, unc
if ansi != nil {
ansi.lbg = -1
}
line = strings.TrimRight(line, "\r\n")
passThroughs := passThroughRegex.FindAllString(line, -1)
if passThroughs != nil {
line = passThroughRegex.ReplaceAllString(line, "")
}
line = strings.TrimLeft(strings.TrimRight(line, "\r\n"), "\r")
if lineNo >= height || t.pwindow.Y() == height-1 && t.pwindow.X() > 0 {
t.previewed.filled = true
t.previewer.scrollable = true
@@ -1899,6 +1995,9 @@ func (t *Terminal) renderPreviewText(height int, lines []string, lineNo int, unc
t.renderPreviewSpinner()
t.pwindow.Move(y, x)
}
for _, passThrough := range passThroughs {
t.tui.PassThrough(passThrough)
}
var fillRet tui.FillReturn
prefixWidth := 0
_, _, ansi = extractColor(line, ansi, func(str string, ansi *ansiState) bool {
@@ -2159,7 +2258,7 @@ func hasPreviewFlags(template string) (slot bool, plus bool, query bool) {
}
func writeTemporaryFile(data []string, printSep string) string {
f, err := ioutil.TempFile("", "fzf-preview-*")
f, err := os.CreateTemp("", "fzf-preview-*")
if err != nil {
errorExit("Unable to create temporary file")
}
@@ -2396,7 +2495,7 @@ func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item
}
// We would still want to update preview window even if there is no match if
// 1. command template contains {q} and the query string is not empty
// 1. the command template contains {q}
// 2. or it contains {+} and we have more than one item already selected.
// To do so, we pass an empty Item instead of nil to trigger an update.
if current == nil {
@@ -2763,7 +2862,7 @@ func (t *Terminal) Loop() {
t.printInfo()
}
if onFocus, prs := t.keymap[tui.Focus.AsEvent()]; prs && focusChanged {
t.serverChan <- onFocus
t.serverInputChan <- onFocus
}
if focusChanged || version != t.version {
version = t.version
@@ -2875,7 +2974,7 @@ func (t *Terminal) Loop() {
select {
case event = <-t.eventChan:
needBarrier = !event.Is(tui.Load, tui.One, tui.Zero)
case actions = <-t.serverChan:
case actions = <-t.serverInputChan:
event = tui.Invalid.AsEvent()
needBarrier = false
}
@@ -2950,6 +3049,8 @@ func (t *Terminal) Loop() {
doAction = func(a *action) bool {
switch a.t {
case actIgnore:
case actResponse:
t.serverOutputChan <- t.dumpStatus(parseGetParams(a.a))
case actBecome:
valid, list := t.buildPlusList(a.a, false)
if valid {
@@ -3305,6 +3406,25 @@ func (t *Terminal) Loop() {
case actHalfPageDown:
t.vmove(-(t.maxItems() / 2), false)
req(reqList)
case actOffsetUp, actOffsetDown:
diff := 1
if a.t == actOffsetDown {
diff = -1
}
if t.layout == layoutReverse {
diff *= -1
}
t.offset += diff
before := t.offset
t.constrain()
if before != t.offset {
t.offset = before
if t.layout == layoutReverse {
diff *= -1
}
t.vmove(diff, false)
}
req(reqList)
case actJump:
t.jumping = jumpEnabled
req(reqJump)
@@ -3355,6 +3475,9 @@ func (t *Terminal) Loop() {
t.track = trackEnabled
}
req(reqInfo)
case actToggleHeader:
t.headerVisible = !t.headerVisible
req(reqList, reqInfo, reqPrompt, reqHeader)
case actTrack:
if t.track == trackDisabled {
t.track = trackCurrent
@@ -3391,13 +3514,23 @@ func (t *Terminal) Loop() {
// Scrolling
if me.S != 0 {
if t.window.Enclose(my, mx) && t.merger.Length() > 0 {
if t.multi > 0 && me.Mod {
toggle()
evt := tui.ScrollUp
if me.Mod {
evt = tui.SScrollUp
}
t.vmove(me.S, true)
req(reqList)
if me.S < 0 {
evt = tui.ScrollDown
if me.Mod {
evt = tui.SScrollDown
}
}
return doActions(actionsFor(evt))
} else if t.hasPreviewWindow() && t.pwindow.Enclose(my, mx) {
scrollPreviewBy(-me.S)
evt := tui.PreviewScrollUp
if me.S < 0 {
evt = tui.PreviewScrollDown
}
return doActions(actionsFor(evt))
}
break
}
@@ -3437,7 +3570,7 @@ func (t *Terminal) Loop() {
// Translate coordinates
mx -= t.window.Left()
my -= t.window.Top()
min := 2 + len(t.header0) + len(t.header)
min := 2 + t.visibleHeaderLines()
if t.noInfoLine() {
min--
}
@@ -3488,15 +3621,19 @@ func (t *Terminal) Loop() {
// Prompt
t.cx = mx + t.xoffset
} else if my >= min {
// List
if t.vset(t.offset+my-min) && t.multi > 0 && me.Mod {
toggle()
}
t.vset(t.offset + my - min)
req(reqList)
if me.Left {
return doActions(actionsFor(tui.LeftClick))
evt := tui.RightClick
if me.Mod {
evt = tui.SRightClick
}
return doActions(actionsFor(tui.RightClick))
if me.Left {
evt = tui.LeftClick
if me.Mod {
evt = tui.SLeftClick
}
}
return doActions(actionsFor(evt))
}
}
case actReload, actReloadSync:
@@ -3709,9 +3846,54 @@ func (t *Terminal) vset(o int) bool {
}
func (t *Terminal) maxItems() int {
max := t.window.Height() - 2 - len(t.header0) - len(t.header)
max := t.window.Height() - 2 - t.visibleHeaderLines()
if t.noInfoLine() {
max++
}
return util.Max(max, 0)
}
func (t *Terminal) dumpItem(i *Item) StatusItem {
if i == nil {
return StatusItem{}
}
return StatusItem{
Index: int(i.Index()),
Text: i.AsString(t.ansi),
}
}
func (t *Terminal) dumpStatus(params getParams) string {
selectedItems := t.sortSelected()
selected := make([]StatusItem, util.Max(0, util.Min(params.limit, len(selectedItems)-params.offset)))
for i := range selected {
selected[i] = t.dumpItem(selectedItems[i+params.offset].item)
}
matches := make([]StatusItem, util.Max(0, util.Min(params.limit, t.merger.Length()-params.offset)))
for i := range matches {
matches[i] = t.dumpItem(t.merger.Get(i + params.offset).item)
}
var current *StatusItem
currentItem := t.currentItem()
if currentItem != nil {
item := t.dumpItem(currentItem)
current = &item
}
dump := Status{
Reading: t.reading,
Progress: t.progress,
Query: string(t.input),
Position: t.cy,
Sort: t.sort,
TotalCount: t.count,
MatchCount: t.merger.Length(),
Current: current,
Matches: matches,
Selected: selected,
}
bytes, _ := json.Marshal(&dump) // TODO: Errors?
return string(bytes)
}

View File

@@ -33,6 +33,7 @@ func (r *FullscreenRenderer) Init() {}
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
func (r *FullscreenRenderer) Pause(bool) {}
func (r *FullscreenRenderer) Resume(bool, bool) {}
func (r *FullscreenRenderer) PassThrough(string) {}
func (r *FullscreenRenderer) Clear() {}
func (r *FullscreenRenderer) NeedScrollbarRedraw() bool { return false }
func (r *FullscreenRenderer) Refresh() {}

View File

@@ -31,6 +31,11 @@ const consoleDevice string = "/dev/tty"
var offsetRegexp *regexp.Regexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R")
var offsetRegexpBegin *regexp.Regexp = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R")
func (r *LightRenderer) PassThrough(str string) {
r.queued.WriteString(str)
r.flush()
}
func (r *LightRenderer) stderr(str string) {
r.stderrInternal(str, true, "")
}
@@ -757,7 +762,7 @@ func (w *LightWindow) DrawHBorder() {
func (w *LightWindow) drawBorder(onlyHorizontal bool) {
switch w.border.shape {
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderDouble:
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble:
w.drawBorderAround(onlyHorizontal)
case BorderHorizontal:
w.drawBorderHorizontal(true, true)

View File

@@ -98,6 +98,11 @@ const (
AttrClear = Attr(1 << 8)
)
func (r *FullscreenRenderer) PassThrough(str string) {
// No-op
// https://github.com/gdamore/tcell/issues/363#issuecomment-680665073
}
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
@@ -715,7 +720,7 @@ func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
hw := runewidth.RuneWidth(w.borderStyle.top)
switch shape {
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderDouble, BorderHorizontal, BorderTop:
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderHorizontal, BorderTop:
max := right - 2*hw
if shape == BorderHorizontal || shape == BorderTop {
max = right - hw
@@ -730,7 +735,7 @@ func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
}
}
switch shape {
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderDouble, BorderHorizontal, BorderBottom:
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderHorizontal, BorderBottom:
max := right - 2*hw
if shape == BorderHorizontal || shape == BorderBottom {
max = right - hw
@@ -741,13 +746,13 @@ func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
}
if !onlyHorizontal {
switch shape {
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderDouble, BorderVertical, BorderLeft:
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderVertical, BorderLeft:
for y := top; y < bot; y++ {
_screen.SetContent(left, y, w.borderStyle.left, nil, style)
}
}
switch shape {
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderDouble, BorderVertical, BorderRight:
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderVertical, BorderRight:
vw := runewidth.RuneWidth(w.borderStyle.right)
for y := top; y < bot; y++ {
_screen.SetContent(right-vw, y, w.borderStyle.right, nil, style)
@@ -755,7 +760,7 @@ func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
}
}
switch shape {
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderDouble:
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble:
_screen.SetContent(left, top, w.borderStyle.topLeft, nil, style)
_screen.SetContent(right-runewidth.RuneWidth(w.borderStyle.topRight), top, w.borderStyle.topRight, nil, style)
_screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style)

View File

@@ -3,7 +3,6 @@
package tui
import (
"io/ioutil"
"os"
"syscall"
)
@@ -17,13 +16,17 @@ func ttyname() string {
}
for _, prefix := range devPrefixes {
files, err := ioutil.ReadDir(prefix)
files, err := os.ReadDir(prefix)
if err != nil {
continue
}
for _, file := range files {
if stat, ok := file.Sys().(*syscall.Stat_t); ok && stat.Rdev == stderr.Rdev {
info, err := file.Info()
if err != nil {
continue
}
if stat, ok := info.Sys().(*syscall.Stat_t); ok && stat.Rdev == stderr.Rdev {
return prefix + file.Name()
}
}

View File

@@ -55,6 +55,14 @@ const (
DoubleClick
LeftClick
RightClick
SLeftClick
SRightClick
ScrollUp
ScrollDown
SScrollUp
SScrollDown
PreviewScrollUp
PreviewScrollDown
BTab
BSpace
@@ -315,6 +323,7 @@ const (
BorderSharp
BorderBold
BorderBlock
BorderThinBlock
BorderDouble
BorderHorizontal
BorderVertical
@@ -408,6 +417,23 @@ func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
bottomLeft: '▙',
bottomRight: '▟',
}
case BorderThinBlock:
// 🭽▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔🭾
// ▏ ▕
// 🭼▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁🭿
return BorderStyle{
shape: shape,
top: '▔',
bottom: '▁',
left: '▏',
right: '▕',
topLeft: '🭽',
topRight: '🭾',
bottomLeft: '🭼',
bottomRight: '🭿',
}
case BorderDouble:
return BorderStyle{
shape: shape,
@@ -456,6 +482,7 @@ type Renderer interface {
RefreshWindows(windows []Window)
Refresh()
Close()
PassThrough(string)
NeedScrollbarRedraw() bool
GetChar() Event
@@ -538,6 +565,7 @@ var (
ColBorderLabel ColorPair
ColPreviewLabel ColorPair
ColPreviewScrollbar ColorPair
ColPreviewSpinner ColorPair
)
func EmptyTheme() *ColorTheme {
@@ -769,4 +797,5 @@ func initPalette(theme *ColorTheme) {
ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
ColPreviewBorder = pair(theme.PreviewBorder, theme.PreviewBg)
ColPreviewScrollbar = pair(theme.PreviewScrollbar, theme.PreviewBg)
ColPreviewSpinner = pair(theme.Spinner, theme.PreviewBg)
}

View File

@@ -184,3 +184,10 @@ func TestRepeatToFill(t *testing.T) {
t.Error("Expected:", strings.Repeat("abcde", 4)+"abcde"[:2])
}
}
func TestStringWidth(t *testing.T) {
w := StringWidth("─")
if w != 1 {
t.Errorf("Expected: %d, Actual: %d", 1, w)
}
}

View File

@@ -8,6 +8,7 @@ require 'shellwords'
require 'erb'
require 'tempfile'
require 'net/http'
require 'json'
TEMPLATE = DATA.read
UNSETS = %w[
@@ -16,6 +17,7 @@ UNSETS = %w[
FZF_CTRL_T_COMMAND FZF_CTRL_T_OPTS
FZF_ALT_C_COMMAND
FZF_ALT_C_OPTS FZF_CTRL_R_OPTS
FZF_API_KEY
fish_history
].freeze
DEFAULT_TIMEOUT = 10
@@ -1212,6 +1214,39 @@ class TestGoFZF < TestBase
end
end
def test_toggle_header
tmux.send_keys "seq 4 | #{FZF} --header-lines 2 --header foo --bind space:toggle-header --header-first --height 10 --border", :Enter
before = <<~OUTPUT
4
> 3
2/2
>
2
1
foo
OUTPUT
tmux.until { assert_block(before, _1) }
tmux.send_keys :Space
after = <<~OUTPUT
4
> 3
2/2
>
OUTPUT
tmux.until { assert_block(after, _1) }
tmux.send_keys :Space
tmux.until { assert_block(before, _1) }
end
def test_cancel
tmux.send_keys "seq 10 | #{fzf('--bind 2:cancel')}", :Enter
tmux.until { |lines| assert_equal ' 10/10', lines[-2] }
@@ -2707,6 +2742,16 @@ class TestGoFZF < TestBase
tmux.until { assert(_1[-2] == ' 1/100') }
end
def test_info_right
tmux.send_keys "#{FZF} --info=right --separator x --bind 'start:reload:seq 100; sleep 10'", :Enter
tmux.until { assert_match(%r{xxx [⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏] 100/100}, _1[-2]) }
end
def test_info_inline_right
tmux.send_keys "#{FZF} --info=inline-right --bind 'start:reload:seq 100; sleep 10'", :Enter
tmux.until { assert_match(%r{[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏] 100/100}, _1[-1]) }
end
def test_prev_next_selected
tmux.send_keys 'seq 10 | fzf --multi --bind ctrl-n:next-selected,ctrl-p:prev-selected', :Enter
tmux.until { |lines| assert_equal 10, lines.item_count }
@@ -2732,14 +2777,46 @@ class TestGoFZF < TestBase
-> { URI("http://localhost:#{File.read('/tmp/fzf-port').chomp}") } }.each do |opts, fn|
tmux.send_keys "seq 10 | fzf #{opts}", :Enter
tmux.until { |lines| assert_equal 10, lines.item_count }
state = JSON.parse(Net::HTTP.get(fn.call), symbolize_names: true)
assert_equal 10, state[:totalCount]
assert_equal 10, state[:matchCount]
assert_empty state[:query]
assert_equal({ index: 0, text: '1' }, state[:current])
Net::HTTP.post(fn.call, 'change-query(yo)+reload(seq 100)+change-prompt:hundred> ')
tmux.until { |lines| assert_equal 100, lines.item_count }
tmux.until { |lines| assert_equal 'hundred> yo', lines[-1] }
state = JSON.parse(Net::HTTP.get(fn.call), symbolize_names: true)
assert_equal 100, state[:totalCount]
assert_equal 0, state[:matchCount]
assert_equal 'yo', state[:query]
assert_nil state[:current]
teardown
setup
end
end
def test_listen_with_api_key
post_uri = URI('http://localhost:6266')
tmux.send_keys 'seq 10 | FZF_API_KEY=123abc fzf --listen 6266', :Enter
tmux.until { |lines| assert_equal 10, lines.item_count }
# Incorrect API Key
[nil, { 'x-api-key' => '' }, { 'x-api-key' => '124abc' }].each do |headers|
res = Net::HTTP.post(post_uri, 'change-query(yo)+reload(seq 100)+change-prompt:hundred> ', headers)
assert_equal '401', res.code
assert_equal 'Unauthorized', res.message
assert_equal "invalid api key\n", res.body
end
# Valid API Key
[{ 'x-api-key' => '123abc' }, { 'X-API-Key' => '123abc' }].each do |headers|
res = Net::HTTP.post(post_uri, 'change-query(yo)+reload(seq 100)+change-prompt:hundred> ', headers)
assert_equal '200', res.code
tmux.until { |lines| assert_equal 100, lines.item_count }
tmux.until { |lines| assert_equal 'hundred> yo', lines[-1] }
end
end
def test_toggle_alternative_preview_window
tmux.send_keys "seq 10 | #{FZF} --bind space:toggle-preview --preview-window '<100000(hidden,up,border-none)' --preview 'echo /{}/{}/'", :Enter
tmux.until { |lines| assert_equal 10, lines.item_count }
@@ -3288,6 +3365,34 @@ module CompletionTest
tmux.prepare
tmux.send_keys 'unset -f _fzf_comprun', :Enter
end
def test_ssh_completion
(1..5).each { |i| FileUtils.touch("/tmp/fzf-test-ssh-#{i}") }
tmux.send_keys 'ssh jg@localhost**', :Tab
tmux.until do |lines|
assert lines.match_count >= 1
end
tmux.send_keys :Enter
tmux.until { |lines| assert lines.any_include?('ssh jg@localhost') }
tmux.send_keys ' -i /tmp/fzf-test-ssh**', :Tab
tmux.until do |lines|
assert lines.match_count >= 5
assert_equal 0, lines.select_count
end
tmux.send_keys :Tab, :Tab, :Tab
tmux.until do |lines|
assert_equal 3, lines.select_count
end
tmux.send_keys :Enter
tmux.until { |lines| assert lines.any_include?('ssh jg@localhost -i /tmp/fzf-test-ssh-') }
tmux.send_keys 'localhost**', :Tab
tmux.until do |lines|
assert lines.match_count >= 1
end
end
end
class TestBash < TestBase

View File

@@ -4,3 +4,6 @@ ba = "ba"
fo = "fo"
enew = "enew"
tabe = "tabe"
[files]
extend-exclude = ["README.md"]