Compare commits

...

188 Commits

Author SHA1 Message Date
Junegunn Choi
beb2de2dd9 0.46.0 2024-01-23 23:47:24 +09:00
Junegunn Choi
2a8b65e105 Fix highlighting of regions that are matched multiple times
Fix #3596
2024-01-23 12:19:32 +09:00
dependabot[bot]
62a916bc24 Bump crate-ci/typos from 1.16.4 to 1.17.2 (#3595)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.16.4 to 1.17.2.
- [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.16.4...v1.17.2)

---
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>
2024-01-23 10:21:01 +09:00
dependabot[bot]
c47b833e7b Bump actions/dependency-review-action from 3 to 4 (#3594)
Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 3 to 4.
- [Release notes](https://github.com/actions/dependency-review-action/releases)
- [Commits](https://github.com/actions/dependency-review-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/dependency-review-action
  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>
2024-01-23 10:20:39 +09:00
LangLangBart
09b0958b5f docs(ADVANCED.md): replace placeholder with env variable for toggle single key binding (#3593) 2024-01-22 13:17:03 +09:00
Junegunn Choi
3a4c3d3e58 Add link to fzf Theme Playground by @vitormv
https://vitormv.github.io/fzf-themes/
2024-01-21 23:12:57 +09:00
Junegunn Choi
7484292e63 Avoid deadlocks by adding a 2 second timeout to GET / endpoint
Because fzf processes HTTP GET requests in the main event loop,
accessing the endpoint from within execute/transform actions would
result in a deadlock and hang fzf indefinitely. This commit sets
a 2 second timeout to avoid the deadlock.
2024-01-21 23:04:37 +09:00
Junegunn Choi
687c2741b8 Add 'resize' event
Close #3570
2024-01-21 15:30:59 +09:00
junegunn
2fb285e530 Deploying to master from @ junegunn/fzf@16f6473938 🚀 2024-01-21 00:01:45 +00:00
Junegunn Choi
16f6473938 Change mattn/go-runewidth dependency to rivo/uniseg for accurate results
Related #3588 #3588 #3567
2024-01-21 02:54:41 +09:00
Junegunn Choi
66546208b2 Update goreleaser flags 2024-01-20 14:36:52 +09:00
Junegunn Choi
532274045e Update to the latest go 2024-01-20 14:35:00 +09:00
LangLangBart
9347c72fb6 docs(ADVANCED.md): Add fzf example switching ripgrep/fzf with single hotkey (#3590) 2024-01-20 13:47:54 +09:00
Junegunn Choi
e90bb7169c [zsh] Handle '*' suffix in history line numbers
Fix #3591
2024-01-20 13:43:15 +09:00
Junegunn Choi
8a2c41e183 Handle ambiguous emoji width
Fix #3588
2024-01-19 16:41:50 +09:00
Junegunn Choi
59fb65293a README.md: More information on image support 2024-01-17 13:21:00 +09:00
Junegunn Choi
e7718b92b7 Kitty image support improvements
* Use `--unicode-placeholder` for consistent result in and out of tmux
* Use updated version of junegunn/go-runewidth that handles diacritics
  used in Kitty Unicode placeholder

Close #3567
2024-01-17 00:17:22 +09:00
Junegunn Choi
cdfaf761df Expose state information via environment variables to child processes
Close #3582
2024-01-16 14:18:31 +09:00
Junegunn Choi
1a9ea6f738 Remove 'replace' directive for 'go install' compatibility
Close #3577
2024-01-14 17:12:24 +09:00
junegunn
945c1c8597 Deploying to master from @ junegunn/fzf@e4d0f7acd5 🚀 2024-01-14 00:01:41 +00:00
dependabot[bot]
e4d0f7acd5 Bump golang.org/x/term from 0.15.0 to 0.16.0 (#3564)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.15.0 to 0.16.0.
- [Commits](https://github.com/golang/term/compare/v0.15.0...v0.16.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>
2024-01-09 12:31:14 +09:00
Junegunn Choi
250496c953 Add 'result' event that is triggered when the result list is ready
Close #3560
2024-01-07 17:46:21 +09:00
Junegunn Choi
e47dc758c9 Fix focus event not triggered in certain cases 2024-01-07 15:43:17 +09:00
Junegunn Choi
b92a843c5f Use Ubuntu 22 to match GitHub Actions environment 2024-01-07 15:10:06 +09:00
Junegunn Choi
91bea9c5b3 Use forked version of go-runewidth
Fix #3558

  go get github.com/junegunn/go-runewidth@fzf
2024-01-07 15:09:46 +09:00
junegunn
d75bb5cbe1 Deploying to master from @ junegunn/fzf@2671259fdb 🚀 2024-01-07 00:01:39 +00:00
danztran
2671259fdb [zsh] Make CTRL-R compatible with accept-or-print-query (#3557)
Fix #3556

Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2024-01-05 10:47:54 +09:00
Junegunn Choi
2024010119 0.45.0 2024-01-01 15:38:35 +09:00
Junegunn Choi
412040f77e Enable preview if 'transform' action is bound to a key 2023-12-31 20:56:33 +09:00
Junegunn Choi
d210660ce8 Add actions: show-header and hide-header 2023-12-31 16:01:00 +09:00
Junegunn Choi
863a12562b Trigger focus actions synchronously 2023-12-31 15:54:37 +09:00
junegunn
5da606a9ac Deploying to master from @ junegunn/fzf@8d20f3d5c4 🚀 2023-12-31 00:01:48 +00:00
Junegunn Choi
8d20f3d5c4 ADVANCED.md: Add toggling example with transform and {fzf:prompt}
Courtesy of @LangLangBart
2023-12-29 12:27:12 +09:00
Junegunn Choi
5d360180af Add {fzf:prompt} placeholder expression
Close #3354
2023-12-28 17:10:06 +09:00
Junegunn Choi
f0fbed6007 Fix RuboCop error 2023-12-27 01:33:34 +09:00
Junegunn Choi
519de7c833 Fix unexpected result of --tiebreak=end
See https://github.com/junegunn/fzf/issues/3255#issuecomment-1869580320
2023-12-26 23:42:14 +09:00
Junegunn Choi
97ccef1a04 {fzf:query} should trigger preview update
fzf --preview 'echo {fzf:query}'
    fzf --preview 'echo {q}'
2023-12-26 16:51:41 +09:00
Junegunn Choi
c4df0dd06e Add TRANSFORM ACTIONS section to man page 2023-12-26 12:21:06 +09:00
Junegunn Choi
cd114c6818 Change transform action to directly execute actions
To avoid filling up input channel for HTTP server
2023-12-26 10:15:53 +09:00
Junegunn Choi
1707b8cdba Add 'transform' action to conditionally perform a series of actions
'transform' action runs an external command that prints a series of
actions to perform.

  # Disallow selecting an empty line
  echo -e "1. Hello\n2. Goodbye\n\n3. Exit" |
    fzf --reverse --header 'Select one' \
        --bind 'enter:transform:[[ -n {} ]] && echo accept || echo "change-header:Invalid selection"'

  # Move cursor past the empty line
  echo -e "1. Hello\n2. Goodbye\n\n3. Exit" |
    fzf --reverse --header 'Select one' \
        --bind 'enter:transform:[[ -n {} ]] && echo accept || echo "change-header:Invalid selection"' \
        --bind 'focus:transform:[[ -n {} ]] && exit; [[ {fzf:action} =~ up$ ]] && echo up || echo down'

Close #3368
Close #2980
2023-12-26 00:14:05 +09:00
Junegunn Choi
41d4d70b98 Fix shell escaping for fish
Fix #3224
2023-12-25 17:35:44 +09:00
Junegunn Choi
0e999482cb Fix handling of empty ANSI color sequence
Fix #3320
2023-12-25 17:05:54 +09:00
junegunn
65b2c06027 Deploying to master from @ junegunn/fzf@d7b61ede07 🚀 2023-12-24 00:01:38 +00:00
Junegunn Choi
d7b61ede07 Add support for negative --height
fzf --height=-1

Close #3487
2023-12-21 18:42:23 +09:00
dependabot[bot]
87fc1c84b8 Bump actions/setup-go from 4 to 5 (#3537)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4 to 5.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-go
  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-12-19 10:50:47 +09:00
dependabot[bot]
d4b5f12383 Bump github/codeql-action from 2 to 3 (#3544)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: github/codeql-action
  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-12-19 09:59:59 +09:00
junegunn
eb62b0d665 Deploying to master from @ junegunn/fzf@91387a741b 🚀 2023-12-17 00:01:44 +00:00
Jan Verbeek
91387a741b Terminate simple server success response with double CRLF (#3542)
The simple success case had only the status line plus a single CRLF,
and pedantic HTTP client implementations (`hyper`) stumbled over
this. A double CRLF makes it OK.

Fixes #3541.
2023-12-16 15:15:00 +09:00
Junegunn Choi
e8b34cb00d Clarification on accept-or-print-query vs. become 2023-12-14 23:13:52 +09:00
Alec Scott
82954258c1 Add Spack installation instructions to README (#3526) 2023-12-10 16:08:54 +09:00
Junegunn Choi
50f092551b Lint: RuboCop 2023-12-10 16:04:30 +09:00
Junegunn Choi
c36a64be68 Add accept-or-print-query
Close #3528
2023-12-10 15:59:45 +09:00
dependabot[bot]
a343b20775 Bump golang.org/x/term from 0.13.0 to 0.15.0 (#3525)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.13.0 to 0.15.0.
- [Commits](https://github.com/golang/term/compare/v0.13.0...v0.15.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-12-10 15:16:47 +09:00
junegunn
a714e76ae1 Deploying to master from @ junegunn/fzf@d21d5c9510 🚀 2023-12-10 00:01:46 +00:00
junegunn
d21d5c9510 Deploying to master from @ junegunn/fzf@cd6788a2bb 🚀 2023-12-03 00:01:38 +00:00
Junegunn Choi
cd6788a2bb Increase buffer size of event channel to avoid freeze on zero event
Fix #3516
2023-11-30 17:23:46 +09:00
junegunn
6b99399c41 Deploying to master from @ junegunn/fzf@952b6af445 🚀 2023-11-26 00:01:42 +00:00
Laurent Cheylus
952b6af445 Allow files creation in /tmp on OpenBSD (#3512)
- src/protector/protector_openbsd.go: add tmppath for pledge
    permissions
  - fix junegunn/fzf#3511

Signed-off-by: Laurent Cheylus <foxy@free.fr>
2023-11-21 16:03:12 +09:00
junegunn
7c674ad7fa Deploying to master from @ junegunn/fzf@d7d2ac3951 🚀 2023-11-19 00:01:41 +00:00
Junegunn Choi
d7d2ac3951 0.44.1 2023-11-17 19:17:00 +09:00
Junegunn Choi
29e67d307a Fix crash when preview window is hidden on focus event 2023-11-17 19:13:37 +09:00
Junegunn Choi
7320b7df62 0.44.0 2023-11-12 22:08:08 +09:00
Tomáš Janoušek
11fb4233f7 Fix Home, End on rxvt-unicode (#3507) 2023-11-12 22:06:38 +09:00
Junegunn Choi
84bb350b14 Reset horizontal offset of the prompt on 'beginning-of-line'
https://github.com/junegunn/fzf/issues/3498#issuecomment-1806651174
2023-11-12 21:41:34 +09:00
Junegunn Choi
38e3694d1c Revert "Sixel and Kitty image support on Windows binary (#2544)"
This reverts commit 68db9cb499.
2023-11-10 13:16:11 +09:00
dependabot[bot]
1084935241 Bump golang.org/x/sys from 0.13.0 to 0.14.0 (#3503)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.13.0 to 0.14.0.
- [Commits](https://github.com/golang/sys/compare/v0.13.0...v0.14.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-11-08 12:03:04 +09:00
Junegunn Choi
f5f0b9ecaa Fix a typo 2023-11-08 11:18:36 +09:00
Junegunn Choi
230fc49ae2 (Experimental) Add support for iTerm2 inline image protocol
Close #1102

  fzf --preview 'imgcat -W $FZF_PREVIEW_COLUMNS -H $FZF_PREVIEW_LINES {}'

Notes:
* There is no good way to determine the height of the rendered image,
  so we assume that the image takes the full height of the preview
  window. So the image cannot be displayed with the other text.
* fzf-preview.sh script was updated to use `imgcat` if it's available
  but `chafa` is not.
* iTerm2 also supports Sixel, so adding support for this protocol is not
  quite necessary but it renders animated GIFs much better (e.g. looping).
2023-11-07 11:51:21 +09:00
Junegunn Choi
250d507bdf Fix a typo on CHANGELOG 2023-11-07 10:50:08 +09:00
Junegunn Choi
a818653174 Add --listen-unsafe=ADDR to allow remote process execution (#3498) 2023-11-05 10:53:46 +09:00
junegunn
5c3b044740 Deploying to master from @ junegunn/fzf@c5aa8729a1 🚀 2023-11-05 00:01:39 +00:00
Junegunn Choi
c5aa8729a1 Fix failing test case 2023-11-04 16:27:24 +09:00
Junegunn Choi
3f78d76da1 Allow accepting remote connections
Close #3498

  # FZF_API_KEY is required for a non-localhost listen address
  FZF_API_KEY=xxx fzf --listen 0.0.0.0:6266
2023-11-04 16:19:16 +09:00
Junegunn Choi
70c19ccf16 Fix CTRL-Z handling: Signal SIGSTOP to PGID
Fix #3501
2023-11-04 13:46:29 +09:00
Junegunn Choi
68db9cb499 Sixel and Kitty image support on Windows binary (#2544) 2023-11-03 00:09:02 +09:00
Junegunn Choi
d0466fa777 Fix regression where tcell renderer not clearing the preview window 2023-11-02 21:00:07 +09:00
Junegunn Choi
21ab64e962 sixel: Export $FZF_PREVIEW_TOP to the preview command (#2544)
So that it can determine if it should subtract 1 from $FZF_PREVIEW_LINES
to avoid scrolling issue of Sixel image that touches the bottom of the
screen.
2023-11-02 01:35:36 +09:00
Junegunn Choi
a0145cebf2 sixel: Better handling of animated GIFs (#2544) 2023-11-02 00:15:42 +09:00
Junegunn Choi
69176fc5f4 fzf-preview.sh: Fall back to stty size (#2544) 2023-11-02 00:15:39 +09:00
Junegunn Choi
278dce9ba6 Restore scroll after rendering full-height Sixel image (#2544)
When a Sixel image touches the bottom of the screen, the whole screen
scrolls up one line to make room for the cursor. Add an ANSI escape
code to compensate for the movement. Unfortunately, the movement of the
screen is sometimes noticeable.

  fzf --preview='fzf-preview.sh {}' --preview-window border-left
2023-10-31 23:38:01 +09:00
Junegunn Choi
1cfa3ee4c7 fzf-preview.sh: Check the number of arguments 2023-10-30 00:05:54 +09:00
Junegunn Choi
9a95cd5794 Fix Sixel height calculation (#2544) 2023-10-29 23:34:33 +09:00
akdevservices
a62fe3df6f [completion] Handle all hostaliases in /etc/hosts (#3495)
* Fix #3488
* Handle inline comments in hosts file
2023-10-29 09:05:30 +09:00
junegunn
7701244a08 Deploying to master from @ junegunn/fzf@96e31e4b78 🚀 2023-10-29 00:01:39 +00:00
Junegunn Choi
96e31e4b78 Fix Sixel issues (#2544)
* Fix regression where previous image is not properly cleared
* Change the way fzf calculates the number of required lines to display
  an image (ceil -> floor) to fix the issue where an image is always
  rendered as a wireframe.
2023-10-27 14:36:14 +09:00
Junegunn Choi
ec208af474 Go 1.18 or above is required
Close #3492
2023-10-26 23:30:46 +09:00
Junegunn Choi
242641264d Clear previous non-Sixel text before rendering Sixel image (#2544) 2023-10-26 22:28:44 +09:00
Junegunn Choi
d3a9a0615b Fix kitty icat handling 2023-10-26 22:28:44 +09:00
Junegunn Choi
3277e8c89c Remove $FZF_PREVIEW_PIXEL_{WIDTH,HEIGHT} (#2544)
They are not neccessary because we can use a program such as chafa that
can resize images by the terminal columns and lines.
2023-10-26 22:28:15 +09:00
Junegunn Choi
d02b9442a5 (Experimental) Improve Sixel graphics support (#2544)
Progress:

* Sixel image can now be displayed with other text, and is scrollable
* If an image can't be displayed entirely due to the scroll offset, fzf
  will render a wireframe to indicate that an image should be displayed
* Renamed $FZF_PREVIEW_{WIDTH,HEIGHT} to $FZF_PREVIEW_PIXEL_{WIDTH,HEIGHT}
  for clarity
* Added bin/fzf-preview.sh script to demonstrate how to display an image
  using Kitty or Sixel protocol

An example:

  ls *.jpg | fzf --preview='seq $((FZF_PREVIEW_LINES*9/10)); fzf-preview.sh {}; seq 100'

A known issue:

* If you reduce the size of the preview window, the image may extend
  beyond the preview window
2023-10-26 00:49:16 +09:00
Junegunn Choi
bac385b59c Simplify LightRenderer.Size() 2023-10-23 23:40:56 +09:00
Junegunn Choi
b1a0ab8086 Experimental Sixel support (#2544) 2023-10-23 01:05:30 +09:00
junegunn
a33749eb71 Deploying to master from @ junegunn/fzf@f5e4ee90e4 🚀 2023-10-22 00:01:50 +00:00
Junegunn Choi
f5e4ee90e4 Fix bug where top section of the previous preview content appearing
when the preview window is re-enabled and the current preview process is
taking more than 500ms and previewDelayed is triggered

  fzf --preview 'sleep 1; date; seq 1000' --bind space:toggle-preview
2023-10-21 16:11:15 +09:00
Junegunn Choi
690d5e6dbd Fix scrollability of the preview window when preview offset is specified
This should not be scrollable

  fzf --preview 'seq $FZF_PREVIEW_LINES' --preview-window '~5'
2023-10-20 17:37:08 +09:00
Junegunn Choi
a76c055b63 Fix inconsistent preview window width with --border
fzf --preview 'cat {}' --bind 'space:change-preview-window:up|right' --border
2023-10-20 16:03:41 +09:00
Junegunn Choi
70c461c60b [bash] Preserve existing completion for ssh
Fix #3484
2023-10-19 09:58:36 +09:00
Laurent Cheylus
d51b71ee80 Fix crash on OpenBSD with --listen (#3483)
- src/protector/protector_openbsd.go: add inet permissions for pledge
  - fix #3481

Signed-off-by: Laurent Cheylus <foxy@free.fr>
2023-10-17 08:46:43 +09:00
junegunn
3666448ca6 Deploying to master from @ junegunn/fzf@d3311d9f43 🚀 2023-10-15 00:01:43 +00:00
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
63 changed files with 2547 additions and 517 deletions

View File

@@ -33,12 +33,12 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3

View File

@@ -11,4 +11,4 @@ jobs:
- name: 'Checkout Repository'
uses: actions/checkout@v3
- name: 'Dependency Review'
uses: actions/dependency-review-action@v3
uses: actions/dependency-review-action@v4

View File

@@ -20,7 +20,7 @@ jobs:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: 1.19

View File

@@ -20,7 +20,7 @@ jobs:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: 1.18

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.17.2

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 +1 @@
golang 1.20.4
golang 1.21.6

View File

@@ -1,8 +1,8 @@
Advanced fzf examples
======================
* *Last update: 2023/05/26*
* *Requires fzf 0.41.0 or above*
* *Last update: 2024/01/20*
* *Requires fzf 0.46.0 or above*
---
@@ -16,17 +16,20 @@ Advanced fzf examples
* [Dynamic reloading of the list](#dynamic-reloading-of-the-list)
* [Updating the list of processes by pressing CTRL-R](#updating-the-list-of-processes-by-pressing-ctrl-r)
* [Toggling between data sources](#toggling-between-data-sources)
* [Toggling with a single key binding](#toggling-with-a-single-key-binding)
* [Ripgrep integration](#ripgrep-integration)
* [Using fzf as the secondary filter](#using-fzf-as-the-secondary-filter)
* [Using fzf as interactive Ripgrep launcher](#using-fzf-as-interactive-ripgrep-launcher)
* [Switching to fzf-only search mode](#switching-to-fzf-only-search-mode)
* [Switching between Ripgrep mode and fzf mode](#switching-between-ripgrep-mode-and-fzf-mode)
* [Switching between Ripgrep mode and fzf mode using a single key binding](#switching-between-ripgrep-mode-and-fzf-mode-using-a-single-key-binding)
* [Log tailing](#log-tailing)
* [Key bindings for git objects](#key-bindings-for-git-objects)
* [Files listed in `git status`](#files-listed-in-git-status)
* [Branches](#branches)
* [Commit hashes](#commit-hashes)
* [Color themes](#color-themes)
* [fzf Theme Playground](#fzf-theme-playground)
* [Generating fzf color theme from Vim color schemes](#generating-fzf-color-theme-from-vim-color-schemes)
<!-- vim-markdown-toc -->
@@ -208,6 +211,30 @@ find * | fzf --prompt 'All> ' \
![image](https://user-images.githubusercontent.com/700826/113465072-46321c00-946c-11eb-9b6f-cda3951df579.png)
### Toggling with a single key binding
The above example uses two different key bindings to toggle between two modes,
but can we just use a single key binding?
To make a key binding behave differently each time it is pressed, we need:
1. a way to store the current state. i.e. "which mode are we in?"
2. and a way to dynamically perform different actions depending on the state.
The following example shows how to 1. store the current mode in the prompt
string, 2. and use this information (`$FZF_PROMPT`) to determine which
actions to perform using the `transform` action.
```sh
fd --type file |
fzf --prompt 'Files> ' \
--header 'CTRL-T: Switch between Files/Directories' \
--bind 'ctrl-t:transform:[[ ! $FZF_PROMPT =~ Files ]] &&
echo "change-prompt(Files> )+reload(fd --type file)" ||
echo "change-prompt(Directories> )+reload(fd --type directory)"' \
--preview '[[ $FZF_PROMPT =~ Files ]] && bat --color=always {} || tree -C {}'
```
Ripgrep integration
-------------------
@@ -442,6 +469,41 @@ INITIAL_QUERY="${*:-}"
[0.30.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0300
[0.36.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0360
### Switching between Ripgrep mode and fzf mode using a single key binding
In contrast to the previous version, we use just one hotkey to toggle between
ripgrep and fzf mode. This is achieved by using the `$FZF_PROMPT` as a state
within the `transform` action, a feature introduced in [fzf 0.45.0][0.45.0]. A
more detailed explanation of this feature can be found in a previous section -
[Toggling with a single keybinding](#toggling-with-a-single-key-binding).
[0.45.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0450
When using the `transform` action, the placeholder (`\{q}`) should be escaped to
prevent immediate evaluation.
```sh
#!/usr/bin/env bash
# Switch between Ripgrep mode and fzf filtering mode (CTRL-T)
rm -f /tmp/rg-fzf-{r,f}
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
INITIAL_QUERY="${*:-}"
: | fzf --ansi --disabled --query "$INITIAL_QUERY" \
--bind "start:reload:$RG_PREFIX {q}" \
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
--bind 'ctrl-t:transform:[[ ! $FZF_PROMPT =~ ripgrep ]] &&
echo "rebind(change)+change-prompt(1. ripgrep> )+disable-search+transform-query:echo \{q} > /tmp/rg-fzf-f; cat /tmp/rg-fzf-r" ||
echo "unbind(change)+change-prompt(2. fzf> )+enable-search+transform-query:echo \{q} > /tmp/rg-fzf-r; cat /tmp/rg-fzf-f"' \
--color "hl:-1:underline,hl+:-1:underline:reverse" \
--prompt '1. ripgrep> ' \
--delimiter : \
--header 'CTRL-T: Switch between ripgrep/fzf' \
--preview 'bat --color=always {1} --highlight-line {2}' \
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \
--bind 'enter:become(vim {1} +{2})'
```
Log tailing
-----------
@@ -571,6 +633,12 @@ export FZF_DEFAULT_OPTS='--color=bg+:#293739,bg:#1B1D1E,border:#808080,spinner:#
![molokai](https://user-images.githubusercontent.com/700826/113475085-8619f300-94ae-11eb-85e4-2766fc3246bf.png)
### fzf Theme Playground
[fzf Theme Playground](https://vitormv.github.io/fzf-themes/) created by
[Vitor Mello](https://github.com/vitormv) is a webpage where you can
interactively create fzf themes.
### Generating fzf color theme from Vim color schemes
The Vim plugin of fzf can generate `--color` option from the current color

View File

@@ -6,7 +6,7 @@ Build instructions
### Prerequisites
- Go 1.17 or above
- Go 1.18 or above
### Using Makefile
@@ -34,8 +34,8 @@ make release
Third-party libraries used
--------------------------
- [mattn/go-runewidth](https://github.com/mattn/go-runewidth)
- Licensed under [MIT](http://mattn.mit-license.org)
- [rivo/uniseg](https://github.com/rivo/uniseg)
- Licensed under [MIT](https://raw.githubusercontent.com/rivo/uniseg/master/LICENSE.txt)
- [mattn/go-shellwords](https://github.com/mattn/go-shellwords)
- Licensed under [MIT](http://mattn.mit-license.org)
- [mattn/go-isatty](https://github.com/mattn/go-isatty)

View File

@@ -1,6 +1,213 @@
CHANGELOG
=========
0.46.0
------
- Added two new events
- `result` - triggered when the filtering for the current query is complete and the result list is ready
- `resize` - triggered when the terminal size is changed
- fzf now exports the following environment variables to the child processes
| Variable | Description |
| --- | --- |
| `FZF_LINES` | Number of lines fzf takes up excluding padding and margin |
| `FZF_COLUMNS` | Number of columns fzf takes up excluding padding and margin |
| `FZF_TOTAL_COUNT` | Total number of items |
| `FZF_MATCH_COUNT` | Number of matched items |
| `FZF_SELECT_COUNT` | Number of selected items |
| `FZF_QUERY` | Current query string |
| `FZF_PROMPT` | Prompt string |
| `FZF_ACTION` | The name of the last action performed |
- This allows you to write sophisticated transformations like so
```sh
# Script to dynamically resize the preview window
transformer='
# 1 line for info, another for prompt, and 2 more lines for preview window border
lines=$(( FZF_LINES - FZF_MATCH_COUNT - 4 ))
if [[ $FZF_MATCH_COUNT -eq 0 ]]; then
echo "change-preview-window:hidden"
elif [[ $lines -gt 3 ]]; then
echo "change-preview-window:$lines"
elif [[ $FZF_PREVIEW_LINES -ne 3 ]]; then
echo "change-preview-window:3"
fi
'
seq 10000 | fzf --preview 'seq {} 10000' --preview-window up \
--bind "result:transform:$transformer" \
--bind "resize:transform:$transformer"
```
- And we're phasing out `{fzf:prompt}` and `{fzf:action}`
- Changed [mattn/go-runewidth](https://github.com/mattn/go-runewidth) dependency to [rivo/uniseg](https://github.com/rivo/uniseg) for accurate results
- Set `--ambidouble` if your terminal displays ambiguous width characters (e.g. box-drawing characters for borders) as 2 columns
- `RUNEWIDTH_EASTASIAN=1` is still respected for backward compatibility, but it's recommended that you use this new option instead
- Bug fixes
0.45.0
------
- Added `transform` action to conditionally perform a series of actions
```sh
# Disallow selecting an empty line
echo -e "1. Hello\n2. Goodbye\n\n3. Exit" |
fzf --height '~100%' --reverse --header 'Select one' \
--bind 'enter:transform:[[ -n {} ]] && echo accept || echo "change-header:Invalid selection"'
# Move cursor past the empty line
echo -e "1. Hello\n2. Goodbye\n\n3. Exit" |
fzf --height '~100%' --reverse --header 'Select one' \
--bind 'enter:transform:[[ -n {} ]] && echo accept || echo "change-header:Invalid selection"' \
--bind 'focus:transform:[[ -n {} ]] && exit; [[ {fzf:action} =~ up$ ]] && echo up || echo down'
# A single key binding to toggle between modes
fd --type file |
fzf --prompt 'Files> ' \
--header 'CTRL-T: Switch between Files/Directories' \
--bind 'ctrl-t:transform:[[ ! {fzf:prompt} =~ Files ]] &&
echo "change-prompt(Files> )+reload(fd --type file)" ||
echo "change-prompt(Directories> )+reload(fd --type directory)"'
```
- Added placeholder expressions
- `{fzf:action}` - The name of the last action performed
- `{fzf:prompt}` - Prompt string (including ANSI color codes)
- `{fzf:query}` - Synonym for `{q}`
- Added support for negative height
```sh
# Terminal height minus 1, so you can still see the command line
fzf --height=-1
```
- This handles a terminal resize better than `--height=$(($(tput lines) - 1))`
- Added `accept-or-print-query` action that acts like `accept` but prints the
current query when there's no match for the query
```sh
# You can make CTRL-R paste the current query when there's no match
export FZF_CTRL_R_OPTS='--bind enter:accept-or-print-query'
```
- Note that there are alternative ways to implement the same strategy
```sh
# 'become' is apparently more versatile but it's not available on Windows.
export FZF_CTRL_R_OPTS='--bind "enter:become:if [ -z {} ]; then echo {q}; else echo {}; fi"'
# Using the new 'transform' action
export FZF_CTRL_R_OPTS='--bind "enter:transform:[ -z {} ] && echo print-query || echo accept"'
```
- Added `show-header` and `hide-header` actions
- Bug fixes
0.44.1
------
- Fixed crash when preview window is hidden on `focus` event
0.44.0
------
- (Experimental) Sixel image support in preview window (not available on Windows)
- [bin/fzf-preview.sh](bin/fzf-preview.sh) is added to demonstrate how to
display an image using Kitty image protocol or Sixel. You can use it
like so:
```sh
fzf --preview='fzf-preview.sh {}'
```
- (Experimental) iTerm2 inline image protocol support in preview window (not available on Windows)
```sh
# Using https://iterm2.com/utilities/imgcat
fzf --preview 'imgcat -W $FZF_PREVIEW_COLUMNS -H $FZF_PREVIEW_LINES {}'
```
- HTTP server can be configured to accept remote connections
```sh
# FZF_API_KEY is required for a non-localhost listen address
export FZF_API_KEY="$(head -c 32 /dev/urandom | base64)"
fzf --listen 0.0.0.0:6266
```
- To allow remote process execution, use `--listen-unsafe` instead
(`execute*`, `reload*`, `become`, `preview`, `change-preview`, `transform-*`)
```sh
fzf --listen-unsafe 0.0.0.0:6266
```
- Bug fixes
0.43.0
------
- (Experimental) Added support for Kitty image protocol in the preview window
(not available on Windows)
```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 --unicode-placeholder --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 +544,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

@@ -1,5 +1,5 @@
FROM --platform=linux/amd64 archlinux
RUN pacman -Sy && pacman --noconfirm -S awk git tmux zsh fish ruby procps go make gcc
FROM --platform=linux/amd64 ubuntu:22.04
RUN apt-get update -y && apt install -y git make golang zsh fish ruby tmux
RUN gem install --no-document -v 5.14.2 minitest
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
RUN echo '. ~/.bashrc' >> ~/.bash_profile
@@ -8,4 +8,5 @@ RUN echo '. ~/.bashrc' >> ~/.bash_profile
RUN rm -f /etc/bash.bashrc
COPY . /fzf
RUN cd /fzf && make install && ./install --all
ENV LANG C.UTF-8
CMD tmux new 'set -o pipefail; ruby /fzf/test/test_go.rb | tee out && touch ok' && cat out && [ -e ok ]

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2013-2023 Junegunn Choi
Copyright (c) 2013-2024 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

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)
@@ -87,10 +89,17 @@ bench:
install: bin/fzf
generate:
PATH=$(PATH):$(GOPATH)/bin $(GO) generate ./...
build:
goreleaser build --rm-dist --snapshot --skip-post-hooks
goreleaser build --clean --snapshot --skip=post-hooks
release:
# Make sure that the tests pass and the build works
TAGS=tcell make test
make test build clean
ifndef GITHUB_TOKEN
$(error GITHUB_TOKEN is not defined)
endif
@@ -117,7 +126,7 @@ endif
git push origin temp --follow-tags --force
# Make a GitHub release
goreleaser --rm-dist --release-notes tmp/release-note
goreleaser --clean --release-notes tmp/release-note
# Push to master
git checkout master
@@ -175,4 +184,4 @@ update:
$(GO) get -u
$(GO) mod tidy
.PHONY: all build release test bench install clean docker docker-test update
.PHONY: all generate build release test bench install clean docker docker-test update

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'
```
@@ -486,4 +489,4 @@ autocmd FileType fzf set laststatus=0 noshowmode noruler
The MIT License (MIT)
Copyright (c) 2013-2023 Junegunn Choi
Copyright (c) 2013-2024 Junegunn Choi

File diff suppressed because one or more lines are too long

74
bin/fzf-preview.sh Executable file
View File

@@ -0,0 +1,74 @@
#!/usr/bin/env bash
#
# The purpose of this script is to demonstrate how to preview a file or an
# image in the preview window of fzf.
#
# Dependencies:
# - https://github.com/sharkdp/bat
# - https://github.com/hpjansson/chafa
# - https://iterm2.com/utilities/imgcat
if [[ $# -ne 1 ]]; then
>&2 echo "usage: $0 FILENAME"
exit 1
fi
file=${1/#\~\//$HOME/}
type=$(file --dereference --mime -- "$file")
if [[ ! $type =~ image/ ]]; then
if [[ $type =~ =binary ]]; then
file "$1"
exit
fi
# Sometimes bat is installed as batcat.
if command -v batcat > /dev/null; then
batname="batcat"
elif command -v bat > /dev/null; then
batname="bat"
else
cat "$1"
exit
fi
${batname} --style="${BAT_STYLE:-numbers}" --color=always --pager=never -- "$file"
exit
fi
dim=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}
if [[ $dim = x ]]; then
dim=$(stty size < /dev/tty | awk '{print $2 "x" $1}')
elif ! [[ $KITTY_WINDOW_ID ]] && (( FZF_PREVIEW_TOP + FZF_PREVIEW_LINES == $(stty size < /dev/tty | awk '{print $1}') )); then
# Avoid scrolling issue when the Sixel image touches the bottom of the screen
# * https://github.com/junegunn/fzf/issues/2544
dim=${FZF_PREVIEW_COLUMNS}x$((FZF_PREVIEW_LINES - 1))
fi
# 1. Use kitty icat on kitty terminal
if [[ $KITTY_WINDOW_ID ]]; then
# 1. 'memory' is the fastest option but if you want the image to be scrollable,
# you have to use 'stream'.
#
# 2. The last line of the output is the ANSI reset code without newline.
# This confuses fzf and makes it render scroll offset indicator.
# So we remove the last line and append the reset code to its previous line.
kitty icat --clear --transfer-mode=memory --unicode-placeholder --stdin=no --place="$dim@0x0" "$file" | sed '$d' | sed $'$s/$/\e[m/'
# 2. Use chafa with Sixel output
elif command -v chafa > /dev/null; then
chafa -f sixel -s "$dim" "$file"
# Add a new line character so that fzf can display multiple images in the preview window
echo
# 3. If chafa is not found but imgcat is available, use it on iTerm2
elif command -v imgcat > /dev/null; then
# NOTE: We should use https://iterm2.com/utilities/it2check to check if the
# user is running iTerm2. But for the sake of simplicity, we just assume
# that's the case here.
imgcat -W "${dim%%x*}" -H "${dim##*x}" "$file"
# 4. Cannot find any suitable method to preview the image
else
file "$file"
fi

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"
tmux_win_opts=( $(tmux show-window-options remain-on-exit \; show-window-options synchronize-panes | sed '/ off/d; s/^/set-window-option /; s/$/ \\;/') )
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: January 1 2024
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':
@@ -506,7 +512,7 @@ LICENSE *fzf-license*
The MIT License (MIT)
Copyright (c) 2013-2023 Junegunn Choi
Copyright (c) 2013-2024 Junegunn Choi
==============================================================================
vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap:

11
go.mod
View File

@@ -2,19 +2,20 @@ module github.com/junegunn/fzf
require (
github.com/gdamore/tcell/v2 v2.5.4
github.com/junegunn/uniseg v0.0.0-20240120174029-b504da4f6ed2
github.com/mattn/go-isatty v0.0.17
github.com/mattn/go-runewidth v0.0.14
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.16.0
golang.org/x/term v0.16.0
)
require (
github.com/gdamore/encoding v1.0.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
golang.org/x/sync v0.5.0 // indirect
golang.org/x/text v0.5.0 // indirect
)

13
go.sum
View File

@@ -2,6 +2,8 @@ github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdk
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.5.4 h1:TGU4tSjD3sCL788vFNeJnTdzpNKIw1H5dgLnJRQVv/k=
github.com/gdamore/tcell/v2 v2.5.4/go.mod h1:dZgRy5v4iMobMEcWNYBtREnDZAT9DYmfqIkrgEMxLyw=
github.com/junegunn/uniseg v0.0.0-20240120174029-b504da4f6ed2 h1:oEwPBh29BPu1MaTsz2dM9bDrkOgKBoYFC0u6uY2izWo=
github.com/junegunn/uniseg v0.0.0-20240120174029-b504da4f6ed2/go.mod h1:ywqF55XaSE3/uS2tkJqVFKiE0oIYAXRvU2N7DU4y3XQ=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
@@ -24,20 +26,21 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-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.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
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.46.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.46.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.46"
var revision string = "devel"
func main() {

View File

@@ -1,7 +1,7 @@
.ig
The MIT License (MIT)
Copyright (c) 2013-2023 Junegunn Choi
Copyright (c) 2013-2024 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
..
.TH fzf-tmux 1 "May 2023" "fzf 0.41.1" "fzf-tmux - open fzf in tmux split pane"
.TH fzf-tmux 1 "Jan 2024" "fzf 0.46.0" "fzf-tmux - open fzf in tmux split pane"
.SH NAME
fzf-tmux - open fzf in tmux split pane

View File

@@ -1,7 +1,7 @@
.ig
The MIT License (MIT)
Copyright (c) 2013-2023 Junegunn Choi
Copyright (c) 2013-2024 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
..
.TH fzf 1 "May 2023" "fzf 0.41.1" "fzf - a command-line fuzzy finder"
.TH fzf 1 "Jan 2024" "fzf 0.46.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
@@ -192,9 +192,21 @@ Label characters for \fBjump\fR and \fBjump-accept\fR
.TP
.BI "--height=" "[~]HEIGHT[%]"
Display fzf window below the cursor with the given height instead of using
the full screen. When prefixed with \fB~\fR, fzf will automatically determine
the height in the range according to the input size. Note that adaptive height
is not compatible with top/bottom margin and padding given in percent size.
the full screen.
If a negative value is specified, the height is calculated as the terminal
height minus the given value.
fzf --height=-1
When prefixed with \fB~\fR, fzf will automatically determine the height in the
range according to the input size. Note that adaptive height is not compatible
with top/bottom margin and padding given in percent size. It is also not
compatible with a negative height value.
# Will not take up 100% of the screen
seq 5 | fzf --height=~100%
.TP
.BI "--min-height=" "HEIGHT"
Minimum height when \fB--height\fR is given in percent (default: 10).
@@ -228,6 +240,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"
@@ -244,9 +260,8 @@ Draw border around the finder
.br
If you use a terminal emulator where each box-drawing character takes
2 columns, try setting \fBRUNEWIDTH_EASTASIAN\fR environment variable to
\fB0\fR or \fB1\fR. If the border is still not properly rendered, set
\fB--no-unicode\fR.
2 columns, try setting \fB--ambidouble\fR. If the border is still not properly
rendered, set \fB--no-unicode\fR.
.TP
.BI "--border-label" [=LABEL]
@@ -297,6 +312,11 @@ the label. Label is printed on the top border line by default, add
Use ASCII characters instead of Unicode drawing characters to draw borders,
the spinner and the horizontal separator.
.TP
.B "--ambidouble"
Set this option if your terminal displays ambiguous width characters (e.g.
box-drawing characters for borders) as 2 columns.
.TP
.BI "--margin=" MARGIN
Comma-separated expression for margins around the finder.
@@ -353,10 +373,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
@@ -540,6 +564,9 @@ they represent the exact size of the preview window. (It also overrides
by the default shell, so prefer to refer to the ones with \fBFZF_PREVIEW_\fR
prefix.)
fzf also exports \fB$FZF_PREVIEW_TOP\fR and \fB$FZF_PREVIEW_LEFT\fR so that
the preview command can determine the position of the preview window.
A placeholder expression starting with \fB+\fR flag will be replaced to the
space-separated list of the selected lines (or the current line if no selection
was made) individually quoted.
@@ -551,10 +578,6 @@ e.g.
When using a field index expression, leading and trailing whitespace is stripped
from the replacement string. To preserve the whitespace, use the \fBs\fR flag.
Also, \fB{q}\fR is replaced to the current query string, and \fB{n}\fR is
replaced to zero-based ordinal index of the line. Use \fB{+n}\fR if you want
all index numbers when multiple lines are selected.
A placeholder expression with \fBf\fR flag is replaced to the path of
a temporary file that holds the evaluated list. This is useful when you
multi-select a large number of items and the length of the evaluated string may
@@ -566,6 +589,14 @@ e.g.
seq 100000 | fzf --multi --bind ctrl-a:select-all \\
--preview "awk '{sum+=\\$1} END {print sum}' {+f}"\fR
Also,
* \fB{q}\fR (or \fB{fzf:query}\fR) is replaced to the current query string
.br
* \fB{n}\fR is replaced to the zero-based ordinal index of the current item.
Use \fB{+n}\fR if you want all index numbers when multiple lines are selected.
.br
Note that you can escape a placeholder pattern by prepending a backslash.
Preview window will be updated even when there is no match for the current
@@ -583,6 +614,14 @@ e.g.
echo "$i"
sleep 0.01
done'\fR
fzf has experimental support for Kitty graphics protocol and Sixel graphics.
The following example uses https://github.com/junegunn/fzf/blob/master/bin/fzf-preview.sh
script to render an image using either of the protocols inside the preview window.
e.g.
\fBfzf --preview='fzf-preview.sh {}'
.RE
.TP
@@ -599,6 +638,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 +789,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
@@ -755,12 +813,19 @@ ncurses finder only after the input stream is complete.
e.g. \fBfzf --multi | fzf --sync\fR
.RE
.TP
.B "--listen[=HTTP_PORT]"
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.
.B "--listen[=[ADDR:]PORT]" "--listen-unsafe[=[ADDR:]PORT]"
Start HTTP server and listen on the given address. It allows external processes
to send actions to perform via POST method.
- If the port number is omitted or given as 0, fzf will automatically choose
a port and export it as \fBFZF_PORT\fR environment variable to the child processes
- 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
- \fBFZF_API_KEY\fR is required for a non-localhost listen address
- To allow remote process execution, use \fB--listen-unsafe\fR
e.g.
\fB# Start HTTP server on port 6266
@@ -769,6 +834,19 @@ e.g.
# Send action to the server
curl -XPOST localhost:6266 -d 'reload(seq 100)+change-prompt(hundred> )'
# Get program state in JSON format (experimental)
# * Make sure NOT to access this endpoint from execute/transform actions
# as it will result in a timeout
curl localhost:6266
# Start HTTP server on port 6266 with remote connections allowed
# * Listening on non-localhost address requires using an API key
export FZF_API_KEY="$(head -c 32 /dev/urandom | base64)"
fzf --listen 0.0.0.0:6266
# Send an authenticated action
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 +866,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"
@@ -822,6 +905,39 @@ of field index expressions.
.BR .. " All the fields"
.br
.SH ENVIRONMENT VARIABLES EXPORTED TO CHILD PROCESSES
fzf exports the following environment variables to its child processes.
.BR FZF_LINES " Number of lines fzf takes up excluding padding and margin"
.br
.BR FZF_COLUMNS " Number of columns fzf takes up excluding padding and margin"
.br
.BR FZF_TOTAL_COUNT " Total number of items"
.br
.BR FZF_MATCH_COUNT " Number of matched items"
.br
.BR FZF_SELECT_COUNT " Number of selected items"
.br
.BR FZF_QUERY " Current query string"
.br
.BR FZF_PROMPT " Prompt string"
.br
.BR FZF_ACTION " The name of the last action performed"
.br
.BR FZF_PORT " Port number when --listen option is used"
.br
The following variables are additionally exported to the preview commands.
.BR FZF_PREVIEW_TOP " Top position of the preview window"
.br
.BR FZF_PREVIEW_LEFT " Left position of the preview window"
.br
.BR FZF_PREVIEW_LINES " Number of lines in the preview window"
.br
.BR FZF_PREVIEW_COLUMNS " Number of columns in the preview window"
.SH EXTENDED SEARCH MODE
Unless specified otherwise, fzf will start in "extended-search mode". In this
@@ -959,6 +1075,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:
@@ -980,6 +1112,22 @@ e.g.
\fB# Change the prompt to "loaded" when the input stream is complete
(seq 10; sleep 1; seq 11 20) | fzf --prompt 'Loading> ' --bind 'load:change-prompt:Loaded> '\fR
.RE
\fIresize\fR
.RS
Triggered when the terminal size is changed.
e.g.
\fBfzf --bind 'resize:transform-header:echo Resized: ${FZF_COLUMNS}x${FZF_LINES}'\fR
.RE
\fIresult\fR
.RS
Triggered when the filtering for the current query is complete and the result list is ready.
e.g.
\fB# Put the cursor on the second item when the query string is empty
# * Note that you can't use 'change' event in this case because the second position may not be available
fzf --sync --bind 'result:transform:[[ -z {fzf:query} ]] && echo "pos(2)"'\fR
.RE
\fIchange\fR
.RS
Triggered whenever the query string is changed
@@ -1043,6 +1191,7 @@ A key or an event can be bound to one or more of the following actions.
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
\fBaccept\fR \fIenter double-click\fR
\fBaccept-non-empty\fR (same as \fBaccept\fR except that it prevents fzf from exiting without selection)
\fBaccept-or-print-query\fR (same as \fBaccept\fR except that it prints the query when there's no match)
\fBbackward-char\fR \fIctrl-b left\fR
\fBbackward-delete-char\fR \fIctrl-h bspace\fR
\fBbackward-delete-char/eof\fR (same as \fBbackward-delete-char\fR except aborts fzf if query is empty)
@@ -1087,7 +1236,10 @@ A key or an event can be bound to one or more of the following actions.
\fBpage-up\fR \fIpgup\fR
\fBhalf-page-down\fR
\fBhalf-page-up\fR
\fBhide-header\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)
@@ -1110,10 +1262,12 @@ A key or an event can be bound to one or more of the following actions.
\fBreplace-query\fR (replace query string with the current selection)
\fBselect\fR
\fBselect-all\fR (select all matches)
\fBshow-header\fR
\fBshow-preview\fR
\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
@@ -1123,6 +1277,7 @@ A key or an event can be bound to one or more of the following actions.
\fBtoggle-track\fR
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
\fBtrack\fR (track the current item; automatically disabled if focus changes)
\fBtransform(...)\fR (transform states using the output of an external command)
\fBtransform-border-label(...)\fR (transform border label using an external command)
\fBtransform-header(...)\fR (transform header using an external command)
\fBtransform-preview-label(...)\fR (transform preview label using an external command)
@@ -1235,6 +1390,28 @@ e.g.
\fB# You can still filter and select entries from the initial list for 3 seconds
seq 100 | fzf --bind 'load:reload-sync(sleep 3; seq 1000)+unbind(load)'\fR
.SS TRANSFORM ACTIONS
Actions with \fBtransform-\fR prefix are used to transform the states of fzf
using the output of an external command. The output of these commands are
expected to be a single line of text.
e.g.
\fBfzf --bind 'focus:transform-header:file --brief {}'\fR
\fBtransform(...)\fR action runs an external command that should print a series
of actions to be performed. The output should be in the same format as the
payload of HTTP POST request to the \fB--listen\fR server.
e.g.
\fB# Disallow selecting an empty line
echo -e "1. Hello\\n2. Goodbye\\n\\n3. Exit" |
fzf --height '~100%' --reverse --header 'Select one' \\
--bind 'enter:transform:[[ -n {} ]] &&
echo accept ||
echo "change-header:Invalid selection"'
\fR
.SS PREVIEW BINDING
With \fBpreview(...)\fR action, you can specify multiple different preview

View File

@@ -1,4 +1,4 @@
" Copyright (c) 2013-2023 Junegunn Choi
" Copyright (c) 2013-2024 Junegunn Choi
"
" MIT License
"
@@ -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 sed 's/#.*//') |
command awk '{for (i = 2; i <= NF; i++) print $i}' | 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'
)
}
@@ -320,7 +481,7 @@ a_cmds="
svn tar unzip zip"
# Preserve existing completion
__fzf_orig_completion < <(complete -p $d_cmds $a_cmds 2> /dev/null)
__fzf_orig_completion < <(complete -p $d_cmds $a_cmds ssh 2> /dev/null)
if type _completion_loader > /dev/null 2>&1; then
_fzf_completion_loader=1
@@ -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') |
awk '{if (length($2) > 0) {print $2}}' | sort -u
)
<(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' | command sed 's/#.*//') |
awk '{for (i = 2; i <= NF; i++) print $i}' | 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,28 +43,60 @@ 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"
}
__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=$(
builtin fc -lnr -2147483648 |
last_hist=$(HISTTIMEFORMAT='' builtin history 1) perl -n -l0 -e "$script" |
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
}
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) command perl -n -l0 -e "$script" |
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
}
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
@@ -97,13 +98,15 @@ bindkey -M viins '\ec' fzf-cd-widget
fzf-history-widget() {
local selected num
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
selected=( $(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} ${FZF_DEFAULT_OPTS-} -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,ctrl-z:ignore ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
selected="$(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} ${FZF_DEFAULT_OPTS-} -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,ctrl-z:ignore ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m" $(__fzfcmd))"
local ret=$?
if [ -n "$selected" ]; then
num=$selected[1]
if [ -n "$num" ]; then
zle vi-fetch-history -n $num
num=$(awk '{print $1}' <<< "$selected")
if [[ "$num" =~ '^[1-9][0-9]*\*?$' ]]; then
zle vi-fetch-history -n ${num%\*}
else # selected is a custom query, not from history
LBUFFER="$selected"
fi
fi
zle reset-prompt

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2013-2023 Junegunn Choi
Copyright (c) 2013-2024 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

129
src/actiontype_string.go Normal file
View File

@@ -0,0 +1,129 @@
// Code generated by "stringer -type=actionType"; DO NOT EDIT.
package fzf
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[actIgnore-0]
_ = x[actStart-1]
_ = x[actClick-2]
_ = x[actInvalid-3]
_ = x[actChar-4]
_ = x[actMouse-5]
_ = x[actBeginningOfLine-6]
_ = x[actAbort-7]
_ = x[actAccept-8]
_ = x[actAcceptNonEmpty-9]
_ = x[actAcceptOrPrintQuery-10]
_ = x[actBackwardChar-11]
_ = x[actBackwardDeleteChar-12]
_ = x[actBackwardDeleteCharEof-13]
_ = x[actBackwardWord-14]
_ = x[actCancel-15]
_ = x[actChangeBorderLabel-16]
_ = x[actChangeHeader-17]
_ = x[actChangePreviewLabel-18]
_ = x[actChangePrompt-19]
_ = x[actChangeQuery-20]
_ = x[actClearScreen-21]
_ = x[actClearQuery-22]
_ = x[actClearSelection-23]
_ = x[actClose-24]
_ = x[actDeleteChar-25]
_ = x[actDeleteCharEof-26]
_ = x[actEndOfLine-27]
_ = x[actForwardChar-28]
_ = x[actForwardWord-29]
_ = x[actKillLine-30]
_ = x[actKillWord-31]
_ = x[actUnixLineDiscard-32]
_ = x[actUnixWordRubout-33]
_ = x[actYank-34]
_ = x[actBackwardKillWord-35]
_ = x[actSelectAll-36]
_ = x[actDeselectAll-37]
_ = x[actToggle-38]
_ = x[actToggleSearch-39]
_ = x[actToggleAll-40]
_ = x[actToggleDown-41]
_ = x[actToggleUp-42]
_ = x[actToggleIn-43]
_ = x[actToggleOut-44]
_ = x[actToggleTrack-45]
_ = x[actToggleHeader-46]
_ = x[actTrack-47]
_ = x[actDown-48]
_ = x[actUp-49]
_ = x[actPageUp-50]
_ = x[actPageDown-51]
_ = x[actPosition-52]
_ = x[actHalfPageUp-53]
_ = x[actHalfPageDown-54]
_ = x[actOffsetUp-55]
_ = x[actOffsetDown-56]
_ = x[actJump-57]
_ = x[actJumpAccept-58]
_ = x[actPrintQuery-59]
_ = x[actRefreshPreview-60]
_ = x[actReplaceQuery-61]
_ = x[actToggleSort-62]
_ = x[actShowPreview-63]
_ = x[actHidePreview-64]
_ = x[actTogglePreview-65]
_ = x[actTogglePreviewWrap-66]
_ = x[actTransform-67]
_ = x[actTransformBorderLabel-68]
_ = x[actTransformHeader-69]
_ = x[actTransformPreviewLabel-70]
_ = x[actTransformPrompt-71]
_ = x[actTransformQuery-72]
_ = x[actPreview-73]
_ = x[actChangePreview-74]
_ = x[actChangePreviewWindow-75]
_ = x[actPreviewTop-76]
_ = x[actPreviewBottom-77]
_ = x[actPreviewUp-78]
_ = x[actPreviewDown-79]
_ = x[actPreviewPageUp-80]
_ = x[actPreviewPageDown-81]
_ = x[actPreviewHalfPageUp-82]
_ = x[actPreviewHalfPageDown-83]
_ = x[actPrevHistory-84]
_ = x[actPrevSelected-85]
_ = x[actPut-86]
_ = x[actNextHistory-87]
_ = x[actNextSelected-88]
_ = x[actExecute-89]
_ = x[actExecuteSilent-90]
_ = x[actExecuteMulti-91]
_ = x[actSigStop-92]
_ = x[actFirst-93]
_ = x[actLast-94]
_ = x[actReload-95]
_ = x[actReloadSync-96]
_ = x[actDisableSearch-97]
_ = x[actEnableSearch-98]
_ = x[actSelect-99]
_ = x[actDeselect-100]
_ = x[actUnbind-101]
_ = x[actRebind-102]
_ = x[actBecome-103]
_ = x[actResponse-104]
_ = x[actShowHeader-105]
_ = x[actHideHeader-106]
}
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleHeaderactTrackactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactResponseactShowHeaderactHideHeader"
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 263, 278, 292, 306, 319, 336, 344, 357, 373, 385, 399, 413, 424, 435, 453, 470, 477, 496, 508, 522, 531, 546, 558, 571, 582, 593, 605, 619, 634, 642, 649, 654, 663, 674, 685, 698, 713, 724, 737, 744, 757, 770, 787, 802, 815, 829, 843, 859, 879, 891, 914, 932, 956, 974, 991, 1001, 1017, 1039, 1052, 1068, 1080, 1094, 1110, 1128, 1148, 1170, 1184, 1199, 1205, 1219, 1234, 1244, 1260, 1275, 1285, 1293, 1300, 1309, 1322, 1338, 1353, 1362, 1373, 1382, 1391, 1400, 1411, 1424, 1437}
func (i actionType) String() string {
if i < 0 || i >= actionType(len(_actionType_index)-1) {
return "actionType(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _actionType_name[_actionType_index[i]:_actionType_index[i+1]]
}

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

@@ -351,9 +351,11 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
ptr := &state.fg
var delimiter byte = 0
count := 0
for len(ansiCode) != 0 {
var num int
if num, delimiter, ansiCode = parseAnsiCode(ansiCode, delimiter); num != -1 {
count++
switch state256 {
case 0:
switch num {
@@ -381,10 +383,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
@@ -426,6 +437,13 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
}
}
// Empty sequence: reset
if count == 0 {
state.fg = -1
state.bg = -1
state.attr = 0
}
if state256 > 0 {
*ptr = -1
}

View File

@@ -348,6 +348,9 @@ func TestAnsiCodeStringConversion(t *testing.T) {
}
assert("\x1b[m", nil, "")
assert("\x1b[m", &ansiState{attr: tui.Blink, lbg: -1}, "")
assert("\x1b[0m", &ansiState{fg: 4, bg: 4, lbg: -1}, "")
assert("\x1b[;m", &ansiState{fg: 4, bg: 4, lbg: -1}, "")
assert("\x1b[;;m", &ansiState{fg: 4, bg: 4, lbg: -1}, "")
assert("\x1b[31m", nil, "\x1b[31;49m")
assert("\x1b[41m", nil, "\x1b[39;41m")

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

@@ -200,7 +200,7 @@ func Run(opts *Options, version string, revision string) {
padHeight := 0
heightUnknown := opts.Height.auto
if heightUnknown {
maxFit, padHeight = terminal.MaxFitAndPad(opts)
maxFit, padHeight = terminal.MaxFitAndPad()
}
deferred := opts.Select1 || opts.Exit0
go terminal.Loop()

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

@@ -11,8 +11,8 @@ import (
"github.com/junegunn/fzf/src/algo"
"github.com/junegunn/fzf/src/tui"
"github.com/junegunn/fzf/src/util"
"github.com/junegunn/uniseg"
"github.com/mattn/go-runewidth"
"github.com/mattn/go-shellwords"
)
@@ -57,13 +57,15 @@ const usage = `usage: fzf [options]
Layout
--height=[~]HEIGHT[%] Display fzf window below the cursor with the given
height instead of using fullscreen.
A negative value is calcalated as the terminal height
minus the given value.
If prefixed with '~', fzf will determine the height
according to the input size.
--min-height=HEIGHT Minimum height when --height is given in percent
(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 +74,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)
@@ -117,13 +120,15 @@ const usage = `usage: fzf [options]
--read0 Read input delimited by ASCII NUL characters
--print0 Print output delimited by ASCII NUL characters
--sync Synchronous search for multi-staged filtering
--listen[=HTTP_PORT] Start HTTP server to receive actions (POST /)
--listen[=[ADDR:]PORT] Start HTTP server to receive actions (POST /)
(To allow remote process execution, use --listen-unsafe)
--version Display version information and exit
Environment variables
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)
`
@@ -154,6 +159,7 @@ type heightSpec struct {
size float64
percent bool
auto bool
inverse bool
}
type sizeSpec struct {
@@ -194,10 +200,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
@@ -325,8 +337,10 @@ type Options struct {
BorderLabel labelOpts
PreviewLabel labelOpts
Unicode bool
Ambidouble bool
Tabstop int
ListenPort *int
ListenAddr *listenAddress
Unsafe bool
ClearOnExit bool
Version bool
}
@@ -393,9 +407,11 @@ func defaultOptions() *Options {
Margin: defaultMargin(),
Padding: defaultMargin(),
Unicode: true,
Ambidouble: os.Getenv("RUNEWIDTH_EASTASIAN") == "1",
Tabstop: 8,
BorderLabel: labelOpts{},
PreviewLabel: labelOpts{},
Unsafe: false,
ClearOnExit: true,
Version: false}
}
@@ -546,6 +562,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 +584,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
}
@@ -634,6 +652,10 @@ func parseKeyChordsImpl(str string, message string, exit func(string)) map[tui.E
add(tui.Load)
case "focus":
add(tui.Focus)
case "result":
add(tui.Result)
case "resize":
add(tui.Resize)
case "one":
add(tui.One)
case "zero":
@@ -692,8 +714,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":
@@ -947,7 +985,7 @@ const (
func init() {
executeRegexp = regexp.MustCompile(
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:header|query|prompt|border-label|preview-label)|change-preview-window|change-preview|(?:re|un)bind|pos|put)`)
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:header|query|prompt|border-label|preview-label)|transform|change-preview-window|change-preview|(?:re|un)bind|pos|put)`)
splitRegexp = regexp.MustCompile("[,:]+")
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
}
@@ -1041,6 +1079,8 @@ func parseActionList(masked string, original string, prevActions []*action, putA
appendAction(actAccept)
case "accept-non-empty":
appendAction(actAcceptNonEmpty)
case "accept-or-print-query":
appendAction(actAcceptOrPrintQuery)
case "print-query":
appendAction(actPrintQuery)
case "refresh-preview":
@@ -1052,7 +1092,7 @@ func parseActionList(masked string, original string, prevActions []*action, putA
case "backward-delete-char":
appendAction(actBackwardDeleteChar)
case "backward-delete-char/eof":
appendAction(actBackwardDeleteCharEOF)
appendAction(actBackwardDeleteCharEof)
case "backward-word":
appendAction(actBackwardWord)
case "clear-screen":
@@ -1060,7 +1100,7 @@ func parseActionList(masked string, original string, prevActions []*action, putA
case "delete-char":
appendAction(actDeleteChar)
case "delete-char/eof":
appendAction(actDeleteCharEOF)
appendAction(actDeleteCharEof)
case "deselect":
appendAction(actDeselect)
case "end-of-line":
@@ -1105,6 +1145,12 @@ func parseActionList(masked string, original string, prevActions []*action, putA
appendAction(actToggleSearch)
case "toggle-track":
appendAction(actToggleTrack)
case "toggle-header":
appendAction(actToggleHeader)
case "show-header":
appendAction(actShowHeader)
case "hide-header":
appendAction(actHideHeader)
case "track":
appendAction(actTrack)
case "select":
@@ -1151,6 +1197,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":
@@ -1173,7 +1223,7 @@ func parseActionList(masked string, original string, prevActions []*action, putA
appendAction(actDisableSearch)
case "put":
if putAllowed {
appendAction(actRune)
appendAction(actChar)
} else {
exit("unable to put non-printable character")
}
@@ -1293,6 +1343,8 @@ func isExecuteAction(str string) actionType {
return actExecuteMulti
case "put":
return actPut
case "transform":
return actTransform
case "transform-border-label":
return actTransformBorderLabel
case "transform-preview-label":
@@ -1349,6 +1401,13 @@ func parseHeight(str string) heightSpec {
heightSpec.auto = true
str = str[1:]
}
if strings.HasPrefix(str, "-") {
if heightSpec.auto {
errorExit("negative(-) height is not compatible with adaptive(~) height")
}
heightSpec.inverse = true
str = str[1:]
}
size := parseSize(str, 100, "height")
heightSpec.size = size.size
@@ -1374,8 +1433,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 +1446,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 +1501,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":
@@ -1532,8 +1597,6 @@ func parseOptions(opts *Options, allArgs []string) {
}
}
validateJumpLabels := false
validatePointer := false
validateMarker := false
for i := 0; i < len(allArgs); i++ {
arg := allArgs[i]
switch arg {
@@ -1713,10 +1776,8 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Prompt = nextString(allArgs, &i, "prompt string required")
case "--pointer":
opts.Pointer = firstLine(nextString(allArgs, &i, "pointer sign string required"))
validatePointer = true
case "--marker":
opts.Marker = firstLine(nextString(allArgs, &i, "selected sign string required"))
validateMarker = true
case "--sync":
opts.Sync = true
case "--no-sync":
@@ -1784,6 +1845,10 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Unicode = false
case "--unicode":
opts.Unicode = true
case "--ambidouble":
opts.Ambidouble = true
case "--no-ambidouble":
opts.Ambidouble = false
case "--margin":
opts.Margin = parseMargin(
"margin",
@@ -1794,11 +1859,21 @@ func parseOptions(opts *Options, allArgs []string) {
nextString(allArgs, &i, "padding required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
case "--tabstop":
opts.Tabstop = nextInt(allArgs, &i, "tab stop required")
case "--listen":
port := optionalNumeric(allArgs, &i, 0)
opts.ListenPort = &port
case "--no-listen":
opts.ListenPort = nil
case "--listen", "--listen-unsafe":
given, str := optionalNextString(allArgs, &i)
addr := defaultListenAddr
if given {
var err error
err, addr = parseListenAddress(str)
if err != nil {
errorExit(err.Error())
}
}
opts.ListenAddr = &addr
opts.Unsafe = arg == "--listen-unsafe"
case "--no-listen", "--no-listen-unsafe":
opts.ListenAddr = nil
opts.Unsafe = false
case "--clear":
opts.ClearOnExit = true
case "--no-clear":
@@ -1832,10 +1907,8 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Prompt = value
} else if match, value := optString(arg, "--pointer="); match {
opts.Pointer = firstLine(value)
validatePointer = true
} else if match, value := optString(arg, "--marker="); match {
opts.Marker = firstLine(value)
validateMarker = true
} else if match, value := optString(arg, "-n", "--nth="); match {
opts.Nth = splitNth(value)
} else if match, value := optString(arg, "--with-nth="); match {
@@ -1889,8 +1962,19 @@ func parseOptions(opts *Options, allArgs []string) {
} else if match, value := optString(arg, "--tabstop="); match {
opts.Tabstop = atoi(value)
} else if match, value := optString(arg, "--listen="); match {
port := atoi(value)
opts.ListenPort = &port
err, addr := parseListenAddress(value)
if err != nil {
errorExit(err.Error())
}
opts.ListenAddr = &addr
opts.Unsafe = false
} else if match, value := optString(arg, "--listen-unsafe="); match {
err, addr := parseListenAddress(value)
if err != nil {
errorExit(err.Error())
}
opts.ListenAddr = &addr
opts.Unsafe = true
} else if match, value := optString(arg, "--hscroll-off="); match {
opts.HscrollOff = atoi(value)
} else if match, value := optString(arg, "--scroll-off="); match {
@@ -1920,10 +2004,6 @@ func parseOptions(opts *Options, allArgs []string) {
errorExit("tab stop must be a positive integer")
}
if opts.ListenPort != nil && (*opts.ListenPort < 0 || *opts.ListenPort > 65535) {
errorExit("invalid listen port")
}
if len(opts.JumpLabels) == 0 {
errorExit("empty jump labels")
}
@@ -1935,31 +2015,31 @@ func parseOptions(opts *Options, allArgs []string) {
}
}
}
if validatePointer {
if err := validateSign(opts.Pointer, "pointer"); err != nil {
errorExit(err.Error())
}
}
if validateMarker {
if err := validateSign(opts.Marker, "marker"); err != nil {
errorExit(err.Error())
}
}
}
func validateSign(sign string, signOptName string) error {
if sign == "" {
return fmt.Errorf("%v cannot be empty", signOptName)
}
if runewidth.StringWidth(sign) > 2 {
if uniseg.StringWidth(sign) > 2 {
return fmt.Errorf("%v display width should be up to 2", signOptName)
}
return nil
}
func postProcessOptions(opts *Options) {
if opts.Ambidouble {
uniseg.EastAsianAmbiguousWidth = 2
}
if err := validateSign(opts.Pointer, "pointer"); err != nil {
errorExit(err.Error())
}
if err := validateSign(opts.Marker, "marker"); err != nil {
errorExit(err.Error())
}
if !opts.Version && !tui.IsLightRendererSupported() && opts.Height.size > 0 {
errorExit("--height option is currently not supported on this platform")
}
@@ -1970,7 +2050,7 @@ func postProcessOptions(opts *Options) {
errorExit("--scrollbar should be given one or two characters")
}
for _, r := range runes {
if runewidth.RuneWidth(r) != 1 {
if uniseg.StringWidth(string(r)) != 1 {
errorExit("scrollbar display width should be 1")
}
}

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,5 +6,5 @@ import "golang.org/x/sys/unix"
// Protect calls OS specific protections like pledge on OpenBSD
func Protect() {
unix.PledgePromises("stdio rpath tty proc exec")
unix.PledgePromises("stdio rpath tty proc exec inet tmppath")
}

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

@@ -80,7 +80,7 @@ func buildResult(item *Item, offsets []Offset, score int) Result {
if criterion == byBegin {
val = util.AsUint16(minEnd - whitePrefixLen)
} else {
val = util.AsUint16(math.MaxUint16 - math.MaxUint16*(maxEnd-whitePrefixLen)/int(item.TrimLength()))
val = util.AsUint16(math.MaxUint16 - math.MaxUint16*(maxEnd-whitePrefixLen)/int(item.TrimLength()+1))
}
}
}
@@ -138,7 +138,9 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
for i := off[0]; i < off[1]; i++ {
// Negative of 1-based index of itemColors
// - The extra -1 means highlighted
cols[i] = cols[i]*-1 - 1
if cols[i] >= 0 {
cols[i] = cols[i]*-1 - 1
}
}
}

View File

@@ -120,7 +120,7 @@ func TestColorOffset(t *testing.T) {
// ++++++++ ++++++++++
// --++++++++-- --++++++++++---
offsets := []Offset{{5, 15}, {25, 35}}
offsets := []Offset{{5, 15}, {10, 12}, {25, 35}}
item := Result{
item: &Item{
colors: &[]ansiOffset{

View File

@@ -3,44 +3,106 @@ 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
httpUnavailable = "HTTP/1.1 503 Service Unavailable" + crlf
httpReadTimeout = 10 * time.Second
jsonContentType = "Content-Type: application/json" + crlf
maxContentLength = 1024 * 1024
)
func startHttpServer(port int, channel chan []*action) (error, int) {
if port < 0 {
return nil, port
}
type httpServer struct {
apiKey []byte
actionChannel chan []*action
responseChannel chan string
}
listener, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", port))
type listenAddress struct {
host string
port int
}
func (addr listenAddress) IsLocal() bool {
return addr.host == "localhost" || addr.host == "127.0.0.1"
}
var defaultListenAddr = listenAddress{"localhost", 0}
func parseListenAddress(address string) (error, listenAddress) {
parts := strings.SplitN(address, ":", 3)
if len(parts) == 1 {
parts = []string{"localhost", parts[0]}
}
if len(parts) != 2 {
return fmt.Errorf("invalid listen address: %s", address), defaultListenAddr
}
portStr := parts[len(parts)-1]
port, err := strconv.Atoi(portStr)
if err != nil || port < 0 || port > 65535 {
return fmt.Errorf("invalid listen port: %s", portStr), defaultListenAddr
}
if len(parts[0]) == 0 {
parts[0] = "localhost"
}
return nil, listenAddress{parts[0], port}
}
func startHttpServer(address listenAddress, actionChannel chan []*action, responseChannel chan string) (error, int) {
host := address.host
port := address.port
apiKey := os.Getenv("FZF_API_KEY")
if !address.IsLocal() && len(apiKey) == 0 {
return fmt.Errorf("FZF_API_KEY is required to allow remote access"), port
}
addrStr := fmt.Sprintf("%s:%d", host, port)
listener, err := net.Listen("tcp", addrStr)
if err != nil {
return fmt.Errorf("port not available: %d", port), port
return fmt.Errorf("failed to listen on %s", addrStr), port
}
if port == 0 {
addr := listener.Addr().String()
parts := strings.SplitN(addr, ":", 2)
parts := strings.Split(addr, ":")
if len(parts) < 2 {
return fmt.Errorf("cannot extract port: %s", addr), port
}
var err error
port, err = strconv.Atoi(parts[1])
port, err = strconv.Atoi(parts[len(parts)-1])
if err != nil {
return err, port
}
}
server := httpServer{
apiKey: []byte(apiKey),
actionChannel: actionChannel,
responseChannel: responseChannel,
}
go func() {
for {
conn, err := listener.Accept()
@@ -51,7 +113,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 +128,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+jsonContentType, message)
}
conn.SetReadDeadline(time.Now().Add(httpReadTimeout))
scanner := bufio.NewScanner(conn)
@@ -92,7 +164,20 @@ 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]}}
select {
case response := <-server.responseChannel:
return good(response)
case <-time.After(2 * time.Second):
go func() {
// Drain the channel
<-server.responseChannel
}()
return answer(httpUnavailable+jsonContentType, `{"error":"timeout"}`)
}
} else if !strings.HasPrefix(text, "POST / HTTP") {
return bad("invalid request method")
}
section++
@@ -105,18 +190,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" {
length, err := strconv.Atoi(strings.TrimSpace(pair[1]))
if err != nil || length <= 0 || length > maxContentLength {
return bad("invalid 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])
}
contentLength = length
}
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 +227,28 @@ func handleHttpRequest(conn net.Conn, channel chan []*action) string {
return bad("no action specified")
}
channel <- actions
return httpOk
server.actionChannel <- actions
return httpOk + crlf
}
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
}

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,20 @@ import (
"github.com/junegunn/fzf/src/util"
)
func replacePlaceholderTest(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
return replacePlaceholder(replacePlaceholderParams{
template: template,
stripAnsi: stripAnsi,
delimiter: delimiter,
printsep: printsep,
forcePlus: forcePlus,
query: query,
allItems: allItems,
lastAction: actBackwardDeleteCharEof,
prompt: "prompt",
})
}
func TestReplacePlaceholder(t *testing.T) {
item1 := newItem(" foo'bar \x1b[31mbaz\x1b[m")
items1 := []*Item{item1, item1}
@@ -52,90 +66,90 @@ func TestReplacePlaceholder(t *testing.T) {
*/
// {}, preserve ansi
result = replacePlaceholder("echo {}", false, Delimiter{}, printsep, false, "query", items1)
result = replacePlaceholderTest("echo {}", false, Delimiter{}, printsep, false, "query", items1)
checkFormat("echo {{.O}} foo{{.I}}bar \x1b[31mbaz\x1b[m{{.O}}")
// {}, strip ansi
result = replacePlaceholder("echo {}", true, Delimiter{}, printsep, false, "query", items1)
result = replacePlaceholderTest("echo {}", true, Delimiter{}, printsep, false, "query", items1)
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}")
// {}, with multiple items
result = replacePlaceholder("echo {}", true, Delimiter{}, printsep, false, "query", items2)
result = replacePlaceholderTest("echo {}", true, Delimiter{}, printsep, false, "query", items2)
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}}")
// {..}, strip leading whitespaces, preserve ansi
result = replacePlaceholder("echo {..}", false, Delimiter{}, printsep, false, "query", items1)
result = replacePlaceholderTest("echo {..}", false, Delimiter{}, printsep, false, "query", items1)
checkFormat("echo {{.O}}foo{{.I}}bar \x1b[31mbaz\x1b[m{{.O}}")
// {..}, strip leading whitespaces, strip ansi
result = replacePlaceholder("echo {..}", true, Delimiter{}, printsep, false, "query", items1)
result = replacePlaceholderTest("echo {..}", true, Delimiter{}, printsep, false, "query", items1)
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}}")
// {q}
result = replacePlaceholder("echo {} {q}", true, Delimiter{}, printsep, false, "query", items1)
result = replacePlaceholderTest("echo {} {q}", true, Delimiter{}, printsep, false, "query", items1)
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}} {{.O}}query{{.O}}")
// {q}, multiple items
result = replacePlaceholder("echo {+}{q}{+}", true, Delimiter{}, printsep, false, "query 'string'", items2)
result = replacePlaceholderTest("echo {+}{q}{+}", true, Delimiter{}, printsep, false, "query 'string'", items2)
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}{{.O}}query {{.I}}string{{.I}}{{.O}}{{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}")
result = replacePlaceholder("echo {}{q}{}", true, Delimiter{}, printsep, false, "query 'string'", items2)
result = replacePlaceholderTest("echo {}{q}{}", true, Delimiter{}, printsep, false, "query 'string'", items2)
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}}{{.O}}query {{.I}}string{{.I}}{{.O}}{{.O}}foo{{.I}}bar baz{{.O}}")
result = replacePlaceholder("echo {1}/{2}/{2,1}/{-1}/{-2}/{}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, false, "query", items1)
result = replacePlaceholderTest("echo {1}/{2}/{2,1}/{-1}/{-2}/{}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, false, "query", items1)
checkFormat("echo {{.O}}foo{{.I}}bar{{.O}}/{{.O}}baz{{.O}}/{{.O}}bazfoo{{.I}}bar{{.O}}/{{.O}}baz{{.O}}/{{.O}}foo{{.I}}bar{{.O}}/{{.O}} foo{{.I}}bar baz{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}}")
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, false, "query", items2)
result = replacePlaceholderTest("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, false, "query", items2)
checkFormat("echo {{.O}}foo{{.I}}bar{{.O}}/{{.O}}baz{{.O}}/{{.O}}baz{{.O}}/{{.O}}foo{{.I}}bar{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}}")
result = replacePlaceholder("echo {+1}/{+2}/{+-1}/{+-2}/{+..}/{n.t}/\\{}/\\{1}/\\{q}/{+3}", true, Delimiter{}, printsep, false, "query", items2)
result = replacePlaceholderTest("echo {+1}/{+2}/{+-1}/{+-2}/{+..}/{n.t}/\\{}/\\{1}/\\{q}/{+3}", true, Delimiter{}, printsep, false, "query", items2)
checkFormat("echo {{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}} {{.O}}{{.O}}")
// forcePlus
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, true, "query", items2)
result = replacePlaceholderTest("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, true, "query", items2)
checkFormat("echo {{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}} {{.O}}{{.O}}")
// Whitespace preserving flag with "'" delimiter
result = replacePlaceholder("echo {s1}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
result = replacePlaceholderTest("echo {s1}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
checkFormat("echo {{.O}} foo{{.O}}")
result = replacePlaceholder("echo {s2}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
result = replacePlaceholderTest("echo {s2}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
checkFormat("echo {{.O}}bar baz{{.O}}")
result = replacePlaceholder("echo {s}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
result = replacePlaceholderTest("echo {s}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}")
result = replacePlaceholder("echo {s..}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
result = replacePlaceholderTest("echo {s..}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}")
// Whitespace preserving flag with regex delimiter
regex = regexp.MustCompile(`\w+`)
result = replacePlaceholder("echo {s1}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
result = replacePlaceholderTest("echo {s1}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
checkFormat("echo {{.O}} {{.O}}")
result = replacePlaceholder("echo {s2}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
result = replacePlaceholderTest("echo {s2}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
checkFormat("echo {{.O}}{{.I}}{{.O}}")
result = replacePlaceholder("echo {s3}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
result = replacePlaceholderTest("echo {s3}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
checkFormat("echo {{.O}} {{.O}}")
// No match
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, nil})
result = replacePlaceholderTest("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, nil})
check("echo /")
// No match, but with selections
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, item1})
result = replacePlaceholderTest("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, item1})
checkFormat("echo /{{.O}} foo{{.I}}bar baz{{.O}}")
// String delimiter
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
result = replacePlaceholderTest("echo {}/{1}/{2}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}/{{.O}}foo{{.O}}/{{.O}}bar baz{{.O}}")
// Regex delimiter
regex = regexp.MustCompile("[oa]+")
// foo'bar baz
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
result = replacePlaceholderTest("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}/{{.O}}f{{.O}}/{{.O}}r b{{.O}}/{{.O}}{{.I}}bar b{{.O}}")
/*
@@ -155,7 +169,6 @@ func TestReplacePlaceholder(t *testing.T) {
newItem("7a 7b 7c 7d 7e 7f"),
}
stripAnsi := false
printsep = "\n"
forcePlus := false
query := "sample query"
@@ -198,18 +211,23 @@ func TestReplacePlaceholder(t *testing.T) {
// query flag is not removed after parsing, so it gets doubled
// while the double q is invalid, it is useful here for testing purposes
templateToOutput[`{q}`] = "{{.O}}" + query + "{{.O}}"
templateToOutput[`{fzf:query}`] = "{{.O}}" + query + "{{.O}}"
templateToOutput[`{fzf:action} {fzf:prompt}`] = "backward-delete-char-eof 'prompt'"
// IV. escaping placeholder
templateToOutput[`\{}`] = `{}`
templateToOutput[`\{q}`] = `{q}`
templateToOutput[`\{fzf:query}`] = `{fzf:query}`
templateToOutput[`\{fzf:action}`] = `{fzf:action}`
templateToOutput[`\{++}`] = `{++}`
templateToOutput[`{++}`] = templateToOutput[`{+}`]
for giveTemplate, wantOutput := range templateToOutput {
result = replacePlaceholder(giveTemplate, stripAnsi, Delimiter{}, printsep, forcePlus, query, items3)
result = replacePlaceholderTest(giveTemplate, stripAnsi, Delimiter{}, printsep, forcePlus, query, items3)
checkFormat(wantOutput)
}
for giveTemplate, wantOutput := range templateToFile {
path := replacePlaceholder(giveTemplate, stripAnsi, Delimiter{}, printsep, forcePlus, query, items3)
path := replacePlaceholderTest(giveTemplate, stripAnsi, Delimiter{}, printsep, forcePlus, query, items3)
data, err := readFile(path)
if err != nil {
@@ -563,7 +581,7 @@ func testCommands(t *testing.T, tests []testCase) {
// evaluate the test cases
for idx, test := range tests {
gotOutput := replacePlaceholder(
gotOutput := replacePlaceholderTest(
test.give.template, stripAnsi, delimiter, printsep, forcePlus,
test.give.query,
test.give.allItems)
@@ -605,7 +623,7 @@ func (flags placeholderFlags) encodePlaceholder() string {
if flags.file {
encoded += "f"
}
if flags.query {
if flags.forceUpdate { // FIXME
encoded += "q"
}
return encoded

View File

@@ -7,14 +7,35 @@ import (
"os/signal"
"strings"
"syscall"
"golang.org/x/sys/unix"
)
var escaper *strings.Replacer
func init() {
tokens := strings.Split(os.Getenv("SHELL"), "/")
if tokens[len(tokens)-1] == "fish" {
// https://fishshell.com/docs/current/language.html#quotes
// > The only meaningful escape sequences in single quotes are \', which
// > escapes a single quote and \\, which escapes the backslash symbol.
escaper = strings.NewReplacer("\\", "\\\\", "'", "\\'")
} else {
escaper = strings.NewReplacer("'", "'\\''")
}
}
func notifyOnResize(resizeChan chan<- os.Signal) {
signal.Notify(resizeChan, syscall.SIGWINCH)
}
func notifyStop(p *os.Process) {
p.Signal(syscall.SIGSTOP)
pid := p.Pid
pgid, err := unix.Getpgid(pid)
if err == nil {
pid = pgid * -1
}
unix.Kill(pid, syscall.SIGSTOP)
}
func notifyOnCont(resizeChan chan<- os.Signal) {
@@ -22,5 +43,5 @@ func notifyOnCont(resizeChan chan<- os.Signal) {
}
func quoteEntry(entry string) string {
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
return "'" + escaper.Replace(entry) + "'"
}

View File

@@ -33,12 +33,15 @@ 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() {}
func (r *FullscreenRenderer) Close() {}
func (r *FullscreenRenderer) Size() TermSize { return TermSize{} }
func (r *FullscreenRenderer) GetChar() Event { return Event{} }
func (r *FullscreenRenderer) Top() int { return 0 }
func (r *FullscreenRenderer) MaxX() int { return 0 }
func (r *FullscreenRenderer) MaxY() int { return 0 }

View File

@@ -10,8 +10,7 @@ import (
"time"
"unicode/utf8"
"github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
"github.com/junegunn/uniseg"
"golang.org/x/term"
)
@@ -31,6 +30,10 @@ 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("\x1b7" + str + "\x1b8")
}
func (r *LightRenderer) stderr(str string) {
r.stderrInternal(str, true, "")
}
@@ -398,7 +401,7 @@ func (r *LightRenderer) escSequence(sz *int) Event {
return Event{F3, 0, nil}
case 'S':
return Event{F4, 0, nil}
case '1', '2', '3', '4', '5', '6':
case '1', '2', '3', '4', '5', '6', '7', '8':
if len(r.buffer) < 4 {
return Event{Invalid, 0, nil}
}
@@ -449,6 +452,10 @@ func (r *LightRenderer) escSequence(sz *int) Event {
return Event{PgUp, 0, nil}
case '6':
return Event{PgDn, 0, nil}
case '7':
return Event{Home, 0, nil}
case '8':
return Event{End, 0, nil}
case '1':
switch r.buffer[3] {
case '~':
@@ -716,6 +723,10 @@ func (r *LightRenderer) Close() {
r.restoreTerminal()
}
func (r *LightRenderer) Top() int {
return r.yoffset
}
func (r *LightRenderer) MaxX() int {
return r.width
}
@@ -751,13 +762,17 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, prev
return w
}
func (w *LightWindow) DrawBorder() {
w.drawBorder(false)
}
func (w *LightWindow) DrawHBorder() {
w.drawBorder(true)
}
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)
@@ -788,7 +803,7 @@ func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
if w.preview {
color = ColPreviewBorder
}
hw := runewidth.RuneWidth(w.border.top)
hw := runeWidth(w.border.top)
if top {
w.Move(0, 0)
w.CPrint(color, repeat(w.border.top, w.width/hw))
@@ -826,13 +841,13 @@ func (w *LightWindow) drawBorderAround(onlyHorizontal bool) {
if w.preview {
color = ColPreviewBorder
}
hw := runewidth.RuneWidth(w.border.top)
tcw := runewidth.RuneWidth(w.border.topLeft) + runewidth.RuneWidth(w.border.topRight)
bcw := runewidth.RuneWidth(w.border.bottomLeft) + runewidth.RuneWidth(w.border.bottomRight)
hw := runeWidth(w.border.top)
tcw := runeWidth(w.border.topLeft) + runeWidth(w.border.topRight)
bcw := runeWidth(w.border.bottomLeft) + runeWidth(w.border.bottomRight)
rem := (w.width - tcw) % hw
w.CPrint(color, string(w.border.topLeft)+repeat(w.border.top, (w.width-tcw)/hw)+repeat(' ', rem)+string(w.border.topRight))
if !onlyHorizontal {
vw := runewidth.RuneWidth(w.border.left)
vw := runeWidth(w.border.left)
for y := 1; y < w.height-1; y++ {
w.Move(y, 0)
w.CPrint(color, string(w.border.left))
@@ -1004,7 +1019,7 @@ func wrapLine(input string, prefixLength int, max int, tabstop int) []wrappedLin
} else if rs[0] == '\r' {
w++
} else {
w = runewidth.StringWidth(str)
w = uniseg.StringWidth(str)
}
width += w
@@ -1083,14 +1098,21 @@ func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillRetu
}
func (w *LightWindow) FinishFill() {
w.MoveAndClear(w.posy, w.posx)
if w.posy < w.height {
w.MoveAndClear(w.posy, w.posx)
}
for y := w.posy + 1; y < w.height; y++ {
w.MoveAndClear(y, 0)
}
}
func (w *LightWindow) Erase() {
w.drawBorder(false)
// We don't erase the window here to avoid flickering during scroll
w.DrawBorder()
w.Move(0, 0)
w.FinishFill()
w.Move(0, 0)
}
func (w *LightWindow) EraseMaybe() bool {
return false
}

View File

@@ -10,6 +10,7 @@ import (
"syscall"
"github.com/junegunn/fzf/src/util"
"golang.org/x/sys/unix"
"golang.org/x/term"
)
@@ -108,3 +109,11 @@ func (r *LightRenderer) getch(nonblock bool) (int, bool) {
}
return int(b[0]), true
}
func (r *LightRenderer) Size() TermSize {
ws, err := unix.IoctlGetWinsize(int(r.ttyin.Fd()), unix.TIOCGWINSZ)
if err != nil {
return TermSize{}
}
return TermSize{int(ws.Row), int(ws.Col), int(ws.Xpixel), int(ws.Ypixel)}
}

View File

@@ -110,16 +110,24 @@ func (r *LightRenderer) restoreTerminal() error {
return windows.SetConsoleMode(windows.Handle(r.outHandle), r.origStateOutput)
}
func (r *LightRenderer) updateTerminalSize() {
func (r *LightRenderer) Size() TermSize {
var w, h int
var bufferInfo windows.ConsoleScreenBufferInfo
if err := windows.GetConsoleScreenBufferInfo(windows.Handle(r.outHandle), &bufferInfo); err != nil {
r.width = getEnv("COLUMNS", defaultWidth)
r.height = r.maxHeightFunc(getEnv("LINES", defaultHeight))
w = getEnv("COLUMNS", defaultWidth)
h = r.maxHeightFunc(getEnv("LINES", defaultHeight))
} else {
r.width = int(bufferInfo.Window.Right - bufferInfo.Window.Left)
r.height = r.maxHeightFunc(int(bufferInfo.Window.Bottom - bufferInfo.Window.Top))
w = int(bufferInfo.Window.Right - bufferInfo.Window.Left)
h = r.maxHeightFunc(int(bufferInfo.Window.Bottom - bufferInfo.Window.Top))
}
return TermSize{h, w, 0, 0}
}
func (r *LightRenderer) updateTerminalSize() {
size := r.Size()
r.width = size.Columns
r.height = size.Lines
}
func (r *LightRenderer) findOffset() (row int, col int) {

View File

@@ -10,8 +10,7 @@ import (
"github.com/gdamore/tcell/v2/encoding"
"github.com/junegunn/fzf/src/util"
"github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
"github.com/junegunn/uniseg"
)
func HasFullscreenRenderer() bool {
@@ -98,6 +97,11 @@ const (
AttrClear = Attr(1 << 8)
)
func (r *FullscreenRenderer) PassThrough(str string) {
// No-op
// https://github.com/gdamore/tcell/pull/650#issuecomment-1806442846
}
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
@@ -167,6 +171,10 @@ func (r *FullscreenRenderer) Init() {
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
}
func (r *FullscreenRenderer) Top() int {
return 0
}
func (r *FullscreenRenderer) MaxX() int {
ncols, _ := _screen.Size()
return int(ncols)
@@ -198,6 +206,12 @@ func (r *FullscreenRenderer) Refresh() {
// noop
}
// TODO: Pixel width and height not implemented
func (r *FullscreenRenderer) Size() TermSize {
cols, lines := _screen.Size()
return TermSize{lines, cols, 0, 0}
}
func (r *FullscreenRenderer) GetChar() Event {
ev := _screen.PollEvent()
switch ev := ev.(type) {
@@ -536,9 +550,15 @@ func fill(x, y, w, h int, n ColorPair, r rune) {
}
func (w *TcellWindow) Erase() {
w.drawBorder(false)
fill(w.left-1, w.top, w.width+1, w.height-1, w.normal, ' ')
}
func (w *TcellWindow) EraseMaybe() bool {
w.Erase()
return true
}
func (w *TcellWindow) Enclose(y int, x int) bool {
return x >= w.left && x < (w.left+w.width) &&
y >= w.top && y < (w.top+w.height)
@@ -687,6 +707,10 @@ func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
return w.fillString(str, NewColorPair(fg, bg, a))
}
func (w *TcellWindow) DrawBorder() {
w.drawBorder(false)
}
func (w *TcellWindow) DrawHBorder() {
w.drawBorder(true)
}
@@ -713,9 +737,9 @@ func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
style = w.normal.style()
}
hw := runewidth.RuneWidth(w.borderStyle.top)
hw := 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 +754,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,24 +765,24 @@ 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:
vw := runewidth.RuneWidth(w.borderStyle.right)
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderVertical, BorderRight:
vw := runeWidth(w.borderStyle.right)
for y := top; y < bot; y++ {
_screen.SetContent(right-vw, y, w.borderStyle.right, nil, style)
}
}
}
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(right-runeWidth(w.borderStyle.topRight), top, w.borderStyle.topRight, nil, style)
_screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style)
_screen.SetContent(right-runewidth.RuneWidth(w.borderStyle.bottomRight), bot-1, w.borderStyle.bottomRight, nil, style)
_screen.SetContent(right-runeWidth(w.borderStyle.bottomRight), bot-1, w.borderStyle.bottomRight, 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

@@ -5,6 +5,8 @@ import (
"os"
"strconv"
"time"
"github.com/junegunn/uniseg"
)
// Types of user action
@@ -55,6 +57,14 @@ const (
DoubleClick
LeftClick
RightClick
SLeftClick
SRightClick
ScrollUp
ScrollDown
SScrollUp
SScrollDown
PreviewScrollUp
PreviewScrollDown
BTab
BSpace
@@ -97,6 +107,7 @@ const (
Focus
One
Zero
Result
AltBS
@@ -315,6 +326,7 @@ const (
BorderSharp
BorderBold
BorderBlock
BorderThinBlock
BorderDouble
BorderHorizontal
BorderVertical
@@ -408,6 +420,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,
@@ -447,6 +476,13 @@ func MakeTransparentBorder() BorderStyle {
bottomRight: ' '}
}
type TermSize struct {
Lines int
Columns int
PxWidth int
PxHeight int
}
type Renderer interface {
Init()
Resize(maxHeightFunc func(int) int)
@@ -456,13 +492,17 @@ type Renderer interface {
RefreshWindows(windows []Window)
Refresh()
Close()
PassThrough(string)
NeedScrollbarRedraw() bool
GetChar() Event
Top() int
MaxX() int
MaxY() int
Size() TermSize
NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window
}
@@ -472,6 +512,7 @@ type Window interface {
Width() int
Height() int
DrawBorder()
DrawHBorder()
Refresh()
FinishFill()
@@ -488,6 +529,7 @@ type Window interface {
Fill(text string) FillReturn
CFill(fg Color, bg Color, attr Attr, text string) FillReturn
Erase()
EraseMaybe() bool
}
type FullscreenRenderer struct {
@@ -538,6 +580,7 @@ var (
ColBorderLabel ColorPair
ColPreviewLabel ColorPair
ColPreviewScrollbar ColorPair
ColPreviewSpinner ColorPair
)
func EmptyTheme() *ColorTheme {
@@ -769,4 +812,9 @@ 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)
}
func runeWidth(r rune) int {
return uniseg.StringWidth(string(r))
}

View File

@@ -6,14 +6,13 @@ import (
"strings"
"time"
"github.com/junegunn/uniseg"
"github.com/mattn/go-isatty"
"github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
)
// StringWidth returns string width where each CR/LF character takes 1 column
func StringWidth(s string) int {
return runewidth.StringWidth(s) + strings.Count(s, "\n") + strings.Count(s, "\r")
return uniseg.StringWidth(s) + strings.Count(s, "\n") + strings.Count(s, "\r")
}
// RunesWidth returns runes width
@@ -165,7 +164,7 @@ func RepeatToFill(str string, length int, limit int) string {
output := strings.Repeat(str, times)
if rest > 0 {
for _, r := range str {
rest -= runewidth.RuneWidth(r)
rest -= uniseg.StringWidth(string(r))
if rest < 0 {
break
}

View File

@@ -164,6 +164,18 @@ func TestRunesWidth(t *testing.T) {
t.Errorf("Expected overflow index: %d, actual: %d", args[2], overflowIdx)
}
}
for _, input := range []struct {
s string
w int
}{
{"▶", 1},
{"▶️", 2},
} {
width, _ := RunesWidth([]rune(input.s), 0, 0, 100)
if width != input.w {
t.Errorf("Expected width of %s: %d, actual: %d", input.s, input.w, width)
}
}
}
func TestTruncate(t *testing.T) {
@@ -184,3 +196,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
@@ -739,6 +741,12 @@ class TestGoFZF < TestBase
'xxoxxxxxxx',
'xoxxxxxxxx'
], `#{FZF} -fo --tiebreak=end,length,begin < #{tempname}`.lines(chomp: true)
writelines(tempname, ['/bar/baz', '/foo/bar/baz'])
assert_equal [
'/foo/bar/baz',
'/bar/baz'
], `#{FZF} -fbaz --tiebreak=end < #{tempname}`.lines(chomp: true)
end
def test_tiebreak_length_with_nth
@@ -1212,6 +1220,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] }
@@ -1741,6 +1782,35 @@ class TestGoFZF < TestBase
assert_equal %w[foo], readonce.lines(chomp: true)
end
def test_accept_or_print_query_without_match
tmux.send_keys %(seq 1000 | #{fzf('--bind enter:accept-or-print-query')}), :Enter
tmux.until { |lines| assert_equal 1000, lines.match_count }
tmux.send_keys 99_999
tmux.until { |lines| assert_equal 0, lines.match_count }
tmux.send_keys :Enter
assert_equal %w[99999], readonce.lines(chomp: true)
end
def test_accept_or_print_query_with_match
tmux.send_keys %(seq 1000 | #{fzf('--bind enter:accept-or-print-query')}), :Enter
tmux.until { |lines| assert_equal 1000, lines.match_count }
tmux.send_keys '^99$'
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.send_keys :Enter
assert_equal %w[99], readonce.lines(chomp: true)
end
def test_accept_or_print_query_with_multi_selection
tmux.send_keys %(seq 1000 | #{fzf('--bind enter:accept-or-print-query --multi')}), :Enter
tmux.until { |lines| assert_equal 1000, lines.match_count }
tmux.send_keys :BTab, :BTab, :BTab
tmux.until { |lines| assert_equal 3, lines.select_count }
tmux.send_keys 99_999
tmux.until { |lines| assert_equal 0, lines.match_count }
tmux.send_keys :Enter
assert_equal %w[1 2 3], readonce.lines(chomp: true)
end
def test_preview_update_on_select
tmux.send_keys %(seq 10 | fzf -m --preview 'echo {+}' --bind a:toggle-all),
:Enter
@@ -1952,6 +2022,13 @@ class TestGoFZF < TestBase
tmux.until { |lines| assert_equal '> RAB', lines[-1] }
end
def test_transform
tmux.send_keys %{#{FZF} --bind 'focus:transform:echo "change-prompt({fzf:action})"'}, :Enter
tmux.until { |lines| assert_equal 'start', lines[-1] }
tmux.send_keys :Up
tmux.until { |lines| assert_equal 'up', lines[-1] }
end
def test_clear_selection
tmux.send_keys %(seq 100 | #{FZF} --multi --bind space:clear-selection), :Enter
tmux.until { |lines| assert_equal 100, lines.match_count }
@@ -2634,12 +2711,26 @@ class TestGoFZF < TestBase
tmux.until { |lines| assert_includes(lines[-1], '[[2]]') }
tmux.send_keys :X
tmux.until { |lines| assert_includes(lines[-1], '[[]]') }
tmux.send_keys :BSpace
tmux.until { |lines| assert_includes(lines[-1], '[[1]]') }
tmux.send_keys :X
tmux.until { |lines| assert_includes(lines[-1], '[[]]') }
tmux.send_keys '?'
tmux.send_keys :BSpace
tmux.until { |lines| assert_equal 100, lines.match_count }
tmux.until { |lines| refute_includes(lines[-1], '[[1]]') }
end
def test_result_event
tmux.send_keys '(echo 0; seq 10) | fzf --bind "result:pos(2)"', :Enter
tmux.until { |lines| assert_equal 11, lines.item_count }
tmux.until { |lines| assert_includes lines, '> 1' }
tmux.send_keys '9'
tmux.until { |lines| assert_includes lines, '> 9' }
tmux.send_keys :BSpace
tmux.until { |lines| assert_includes lines, '> 1' }
end
def test_labels_center
tmux.send_keys 'echo x | fzf --border --border-label foobar --preview : --preview-label barfoo --bind "space:change-border-label(foobarfoo)+change-preview-label(barfoobar),enter:transform-border-label(echo foo{}foo)+transform-preview-label(echo bar{}bar)"', :Enter
tmux.until do
@@ -2707,6 +2798,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 +2833,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 }
@@ -2943,6 +3076,13 @@ class TestGoFZF < TestBase
tmux.send_keys :x
tmux.until { |lines| assert(lines.any? { |line| line.include?('[x-foo]') }) }
end
def test_preview_window_hidden_on_focus
tmux.send_keys "seq 3 | #{FZF} --preview 'echo {}' --bind focus:hide-preview", :Enter
tmux.until { |lines| assert_includes lines, '> 1' }
tmux.send_keys :Up
tmux.until { |lines| assert_includes lines, '> 2' }
end
end
module TestShell
@@ -3288,6 +3428,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,7 @@ ba = "ba"
fo = "fo"
enew = "enew"
tabe = "tabe"
Iterm = "Iterm"
[files]
extend-exclude = ["README.md"]