Compare commits

...

141 Commits

Author SHA1 Message Date
Junegunn Choi
ff1687744d 0.56.0 2024-10-27 12:03:01 +09:00
junegunn
782c870fb2 Deploying to master from @ junegunn/fzf@71fad63829 🚀 2024-10-27 00:02:14 +00:00
Charlie Vieth
71fad63829 Update fastwalk to v1.0.9 to fix handling of disk root paths on Windows (#4063)
Fixes: https://github.com/junegunn/fzf/issues/4027
2024-10-25 23:57:46 +09:00
Junegunn Choi
d65c6101a8 walker: Do not treat '..' as a hidden entry
Thanks to @LangLangBart for the suggested fix

Fix #4048
2024-10-25 13:50:58 +09:00
junegunn
3c40b1bd51 Deploying to master from @ junegunn/fzf@90a8800bb5 🚀 2024-10-20 00:02:15 +00:00
Junegunn Choi
90a8800bb5 Avoid selecting an outdated merger from cache
We cache a merger for partial input as well, because it is automatically
invalidated as soon as the new data comes in.

However, there was a race condition where a cached merger for a partial
input is used even after the input stream was complete. This commit
fixes the problem.

Fix #4034
2024-10-16 00:45:12 +09:00
Thomas Martitz
97f1dae2d1 Use eval to evaluate $post variable as command. (#4023) 2024-10-15 18:00:27 +09:00
dependabot[bot]
e54ec05709 Bump crate-ci/typos from 1.24.1 to 1.26.0 (#4036)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.24.1 to 1.26.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.24.1...v1.26.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>
2024-10-15 17:38:14 +09:00
Junegunn Choi
a24eb99679 Fix full line background in preview window 2024-10-15 17:35:11 +09:00
dependabot[bot]
ad113d00b7 Bump golang.org/x/term from 0.24.0 to 0.25.0 (#4031)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.24.0 to 0.25.0.
- [Commits](https://github.com/golang/term/compare/v0.24.0...v0.25.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-10-15 16:56:37 +09:00
junegunn
7bd5884d12 Deploying to master from @ junegunn/fzf@c3505858a6 🚀 2024-10-13 00:02:13 +00:00
junegunn
c3505858a6 Deploying to master from @ junegunn/fzf@e76aa37fd4 🚀 2024-10-06 00:02:11 +00:00
Junegunn Choi
e76aa37fd4 Make RuboCop happy 2024-10-01 19:45:53 +09:00
Junegunn Choi
1a32220ca9 Add --gap option to put empty lines between items 2024-10-01 19:15:17 +09:00
Junegunn Choi
4161403a1d --tmux: Export bash functions
Fix #4001
2024-09-29 19:33:42 +09:00
junegunn
53bcdc4294 Deploying to master from @ junegunn/fzf@30a8ef28cd 🚀 2024-09-29 00:02:07 +00:00
Koichi Murase
30a8ef28cd [bash] Fix infinite retry loop for completion setting without "-F func" (#4004) 2024-09-23 19:16:59 +09:00
junegunn
855f90727a Deploying to master from @ junegunn/fzf@2191a44e36 🚀 2024-09-15 00:02:03 +00:00
Junegunn Choi
2191a44e36 Redraw/hide scroll offset when 'info' property is changed 2024-09-12 22:04:19 +09:00
Junegunn Choi
952276dc2d Add 'noinfo' option to hide scroll offset information in preview window
fzf --preview 'seq 1000' --preview-window noinfo

Close #2525
2024-09-12 18:31:14 +09:00
dependabot[bot]
2286edb329 Bump golang.org/x/term from 0.23.0 to 0.24.0 (#3991)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.23.0 to 0.24.0.
- [Commits](https://github.com/golang/term/compare/v0.23.0...v0.24.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-09-10 21:25:57 +09:00
junegunn
a0f28583e7 Deploying to master from @ junegunn/fzf@8af0af3400 🚀 2024-09-08 00:02:05 +00:00
Junegunn Choi
8af0af3400 Highlights
Close #3942
2024-09-01 16:20:25 +09:00
junegunn
769e5cbb2d Deploying to master from @ junegunn/fzf@fc69308057 🚀 2024-09-01 00:02:21 +00:00
Junegunn Choi
fc69308057 0.55.0 2024-08-29 17:10:58 +09:00
Junegunn Choi
c6d620c99e Add to CHANGELOG 2024-08-29 17:08:23 +09:00
Junegunn Choi
f510a4def6 Test cases for non-default --scheme options 2024-08-29 17:08:23 +09:00
Junegunn Choi
4ae3069c6f Underscore boundaries should be ranked lower 2024-08-29 17:08:23 +09:00
Junegunn Choi
c0f27751d3 Add exact-boundary-match to man page 2024-08-29 17:08:23 +09:00
Junegunn Choi
efbcd5a683 Require quotes on both sides for boundary matching even in --exact mode
Only requiring '-suffix in --exact mode is confusing and not
straightforward. Requiring '-prefix in --exact mode means that
the users can experience unintended mode switches while typing.

e.g.
     'it   -> fuzzy (many results)
     'it'  -> boundary (few results)
     'it's -> fuzzy (many results)

However, user who intends to input a boundary query should not be
interested in the intermediate results, and the number of matches
decreases as she types, so it should be okay.

On the other hand, user who does intend to type "it's" will be surprised
by the sudden decrease of the match count, but eventually get the right
result.
2024-08-29 17:08:23 +09:00
Junegunn Choi
6a67712944 Implement exact-boundary match type
Close #3963
2024-08-29 17:08:23 +09:00
dependabot[bot]
e8a690928d Bump crate-ci/typos from 1.23.6 to 1.24.1 (#3978)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.23.6 to 1.24.1.
- [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.23.6...v1.24.1)

---
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-08-29 17:04:44 +09:00
Junegunn Choi
0eee95af57 Fix CTRL-Z handling
Fix #3802

This fixes `xterm -e fzf` hangs on CTRL-Z

* Replace SIGSTOP with SIGTSTP
* Do not rely on SIGCONT
2024-08-29 16:41:52 +09:00
Junegunn Choi
a09c6e991a [vim] Ignore argument to fzf#exec if it's lower than minimum requirement 2024-08-28 13:49:41 +09:00
Junegunn Choi
d06c5ab990 [vim] Require fzf 0.53.0 or above (with --tmux)
Close #3980
2024-08-28 13:41:46 +09:00
Junegunn Choi
e0924d27b8 Change default --ellipsis to '··' 2024-08-27 19:41:39 +09:00
polluks2
2775b771f2 [man] Add italics (#3097) 2024-08-25 22:39:20 +09:00
junegunn
cf7a3c6a0e Deploying to master from @ junegunn/fzf@230cc6acc3 🚀 2024-08-25 00:01:56 +00:00
Junegunn Choi
230cc6acc3 Fix fzf-tmux on tmux 3.0
* Fix #3959
* https://github.com/junegunn/fzf/issues/3635#issuecomment-2085988777
2024-08-24 22:54:29 +09:00
dependabot[bot]
626a23a585 Bump crate-ci/typos from 1.23.1 to 1.23.6 (#3956)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.23.1 to 1.23.6.
- [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.23.1...v1.23.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-24 16:12:01 +09:00
Junegunn Choi
74f196eebb [vim] Fix callback not run on exit code 1 2024-08-23 19:35:25 +09:00
Junegunn Choi
cf2242aea3 [bash] Revert skipping setting up fuzzy path completion
This partially reverts a2d0e8f not to break backward compatibility.
2024-08-22 19:39:22 +09:00
Junegunn Choi
8cb59e6fca [vim] Add 'exit' callback
A spec can have `exit` callback that is called with the exit status of fzf.
This can be used to clean up temporary resources or restore the original
state when fzf is closed without a selection.
2024-08-19 20:51:26 +09:00
Junegunn Choi
5cce17e80a Fix preview window incorrectly rendering empty line at the bottom 2024-08-18 11:38:37 +09:00
junegunn
ee5302fb2d Deploying to master from @ junegunn/fzf@387c6ef664 🚀 2024-08-18 00:02:08 +00:00
Junegunn Choi
387c6ef664 Support hyperlinks (OSC 8) in the main window
Close #2557
2024-08-14 23:04:05 +09:00
Junegunn Choi
581734c369 Fix OSC 8 parser 2024-08-14 19:19:28 +09:00
Junegunn Choi
d90a969c00 Add support for hyperlinks in preview window
Close #2165
2024-08-14 08:51:34 +09:00
dependabot[bot]
2c8a96bb27 Bump golang.org/x/sys from 0.22.0 to 0.24.0 (#3968)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.22.0 to 0.24.0.
- [Commits](https://github.com/golang/sys/compare/v0.22.0...v0.24.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>
2024-08-13 23:22:55 +09:00
dependabot[bot]
5da168db30 Bump golang.org/x/term from 0.22.0 to 0.23.0 (#3966)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.22.0 to 0.23.0.
- [Commits](https://github.com/golang/term/compare/v0.22.0...v0.23.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-08-13 22:37:21 +09:00
Junegunn Choi
e215e2daf3 Allow comments in $FZF_DEFAULT_OPTS and $FZF_DEFAULT_OPTS_FILE
Close #3961
2024-08-13 18:51:02 +09:00
Junegunn Choi
e28f5aa45b Make sure preview command is not run before Terminal is ready 2024-08-11 14:48:52 +09:00
Junegunn Choi
a2d0e8f233 [bash] Enable fuzzy path completion for all commands (#3958)
All commands with no custom completion defined.

Close #3957
2024-08-11 14:22:21 +09:00
junegunn
303d04106a Deploying to master from @ junegunn/fzf@c423c496a1 🚀 2024-08-11 00:01:54 +00:00
Eduardo D Sanchez
c423c496a1 fix: Add fallback for cygwin ps (#3955) 2024-08-07 14:42:27 +09:00
Junegunn Choi
4e85f72f0e Fix extra scroll offset in multi-line mode (--read0 or --wrap)
Fix #3950
2024-08-04 10:52:17 +09:00
junegunn
dd0737aac0 Deploying to master from @ junegunn/fzf@f90985845d 🚀 2024-08-04 00:02:03 +00:00
Junegunn Choi
f90985845d Fix '--tmux bottom' when the status line is not at the bottom
Fix #3948
2024-08-02 23:11:20 +09:00
Junegunn Choi
af4917dbb6 0.54.3 2024-07-31 21:51:54 +09:00
Junegunn Choi
d9404fcce4 Remove stale comment 2024-07-28 10:13:41 +09:00
junegunn
5c01fee5a9 Deploying to master from @ junegunn/fzf@9b27d68a37 🚀 2024-07-28 00:02:00 +00:00
Junegunn Choi
9b27d68a37 Fix build error 2024-07-27 19:13:24 +09:00
Junegunn Choi
b99d884e57 Minor refactoring 2024-07-27 18:59:50 +09:00
Junegunn Choi
587df594b8 Fix incompatibility of adaptive height and 'start:reload'
This command would cause a deadlock and make fzf crash:

  fzf --bind 'start:reload:ls' --height ~100%

Because,

1. 'start' event is handled by Terminal
2. When 'reload' is bound to 'start', fzf avoids starting the initial reader
3. Terminal waits for the initial input to find the right height when
   adaptive height is used
4. Because the initial reader is not started, Terminal never gets the
   initial list
5. No chance to trigger 'start:reload', hence deadlock

This commit fixes the above problem by extracting the reload command
bound to 'start' event and starting the initial reader with that command
instead of letting Terminal start it.

This commit also makes the environment variables available to
$FZF_DEFAULT_COMMAND.

  FZF_DEFAULT_COMMAND='echo $FZF_QUERY' fzf --query foo

Fix #3944
2024-07-27 11:30:25 +09:00
Junegunn Choi
b896e0d314 0.54.2 2024-07-26 17:44:09 +09:00
Junegunn Choi
559fb7ee45 Revert "Prefer LightRenderer over tcell on Windows"
This reverts commit dca2262fe6.

> For mouse support on mintty
> Fix #3847

The current implementation LightRenderer for Windows is unable to accept
non-ASCII input unlike the tcell renderer. So even though it supports
mouse on mintty, we shouldn't use it as the default.

* #3799
* #3847
2024-07-26 01:43:21 +09:00
Junegunn Choi
62545cd983 Display repology image in 3 columns 2024-07-25 12:49:02 +09:00
junegunn
c50812518e Deploying to master from @ junegunn/fzf@4cc5609d8b 🚀 2024-07-21 00:02:12 +00:00
Junegunn Choi
4cc5609d8b Fix invalid highlighting of truncated multi-line items 2024-07-21 01:09:39 +09:00
Junegunn Choi
50fa90dfb8 0.54.1 2024-07-19 17:10:49 +09:00
Charlie Vieth
a2c365e710 Update fastwalk to v1.0.8 for better MSYS detection and sorting (#3930)
This commit updates github.com/charlievieth/fastwalk to v1.0.8 which
improves MSYS detection and adds optional sorting of directory entries.
It also updates fzf to use the SortFilesFirst sort mode which improves
the output by making it a bit more sorted and grouped by directory
previously entries were visited in directory order (which is basically
random).

PRs Included:

  * https://github.com/charlievieth/fastwalk/pull/25
  * https://github.com/charlievieth/fastwalk/pull/27
  * https://github.com/charlievieth/fastwalk/pull/28
2024-07-19 13:17:41 +09:00
LangLangBart
b4ddffdc61 fix(fish): partially revert dbe8dc3 by removing the 'builtin' for cd 2024-07-17 22:13:01 +09:00
Junegunn Choi
8d4d184fc6 Change WinGet workflow to drop 'v' prefix from tag (#3927)
https://github.com/vedantmgoyal9/winget-releaser?tab=readme-ov-file#configuration-options-%EF%B8%8F
2024-07-17 12:38:10 +09:00
junegunn
ea23539b59 Deploying to master from @ junegunn/fzf@9e92b6f11e 🚀 2024-07-14 00:02:04 +00:00
Junegunn Choi
9e92b6f11e 0.54.0
New tags will have `v` prefix.

* https://github.com/junegunn/fzf/issues/2879
* https://github.com/golang/go/issues/32945

Close #2879
2024-07-08 22:51:48 +09:00
Junegunn Choi
6cbde812f6 [bash] Add code to the default list of programs to support completion
Close #3843
2024-07-08 22:51:47 +09:00
Junegunn Choi
3b2e932c13 Bind CTRL-/ and ALT-/ to toggle-wrap by default 2024-07-08 22:51:47 +09:00
dependabot[bot]
8ff4e52641 Bump golang.org/x/term from 0.21.0 to 0.22.0 (#3913)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.21.0 to 0.22.0.
- [Commits](https://github.com/golang/term/compare/v0.21.0...v0.22.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-07-08 22:51:27 +09:00
Charlie Vieth
2dbc874e3d Update charlievieth/fastwalk to use forward-slashes on WSL and MSYS (#3907)
This commit changes FZF to enforce that all paths are joined with
forward-slashes when running on WSL or MSYS
even when the FZF binary was compiled for Windows.

Update: github.com/charlievieth/fastwalk
Fixes:  https://github.com/junegunn/fzf/issues/3859

---------

Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2024-07-08 22:21:37 +09:00
dependabot[bot]
039a2f1d04 Bump crate-ci/typos from 1.22.9 to 1.23.1 (#3912)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.22.9 to 1.23.1.
- [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.22.9...v1.23.1)

---
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-07-08 22:20:30 +09:00
junegunn
4ef1cf5b35 Deploying to master from @ junegunn/fzf@b44ab9e33c 🚀 2024-07-07 00:01:56 +00:00
Junegunn Choi
b44ab9e33c [completion] Use --wrap option in process completion
And remove the short preview window for showing the whole command.

Because it is important to be able to see the whole command before
deciding to kill it.
2024-07-06 10:20:58 +09:00
Junegunn Choi
8f4c23f1c4 Remove --walker-path-sep
Related: #3859 #3907 #3909
2024-07-05 20:15:03 +09:00
Junegunn Choi
23a391e715 [zsh] Fix backslash escaping (#3909)
Fix #3859

To test:

  FZF_CTRL_T_COMMAND="echo -E 'foo\bar\baz'; echo -E 'hello\world'"

  _fzf_compgen_path() {
    eval $FZF_CTRL_T_COMMAND
  }

  source shell/key-bindings.zsh
  source shell/completion.zsh
2024-07-05 01:46:36 +09:00
Junegunn Choi
035b0be29f Adjust offset immediately after 'first', 'last', and 'pos'
seq 100 | fzf --multi --sync --bind 'result:last+transform:for _ in $(seq 10); do echo -n "+select+down"; done'
2024-07-03 22:06:17 +09:00
LangLangBart
e1fcdbc337 fix(zsh): use the '=~' operator instead of grep (#3906) 2024-07-03 11:03:21 +09:00
Junegunn Choi
cfc149e994 Avoid printing ANSI reset codes
fzf --border --bind 'start:transform-border-label:echo -e "\x1b[0mfoo"'
2024-07-02 09:20:10 +09:00
Junegunn Choi
2faffbd1b7 Fill background color in padding area
fzf --color bg:blue --border --padding 1,2
2024-07-01 21:39:09 +09:00
junegunn
8db65704b9 Deploying to master from @ junegunn/fzf@797a01aed4 🚀 2024-06-30 00:01:54 +00:00
Junegunn Choi
797a01aed4 [man] Clarify --walker-path-sep=CHAR 2024-06-29 18:44:28 +09:00
Junegunn Choi
bf515a3d32 Add --walker-path-sep=CHAR to use a different path separator
This is needed when you run a Windows binary on WSL or zsh on Windows
where forward slashes are expected.

  export FZF_DEFAULT_OPTS='--walker-path-sep /'

Close #3859
2024-06-29 17:13:31 +09:00
Junegunn Choi
a06745826a [zsh] Fix completion error on openSUSE Tumbleweed
Fix suggested by @LangLangBart

Fix #3890
2024-06-28 16:59:56 +09:00
Junegunn Choi
0420ed4f2a Empty --marker-multi-line if --marker is empty 2024-06-25 20:49:42 +09:00
Junegunn Choi
3b944addd4 Allow removing header line with change-header and transform-header
If the new header is an empty string.

  fzf --header loading --bind 'start:reload:sleep 3; ls' --bind 'load:change-header:'
  fzf --header loading --bind 'start:reload:sleep 3; ls' --bind 'load:transform-header:'
2024-06-25 17:14:11 +09:00
Junegunn Choi
70bf8bc35d Add --wrap option and 'toggle-wrap' action (#3887)
* `--wrap`
* `--wrap-sign`
* `toggle-wrap`

Close #3619
Close #2236
Close #577
Close #461
2024-06-25 17:08:47 +09:00
dependabot[bot]
724f8a1d45 Bump crate-ci/typos from 1.22.7 to 1.22.9 (#3894)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.22.7 to 1.22.9.
- [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.22.7...v1.22.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-24 23:55:53 +09:00
Junegunn Choi
cc2b2146ee ADVANCED.md: Remove unnecessary : | fzf
See 5b52833
2024-06-24 17:23:14 +09:00
Junegunn Choi
8689f5f230 Fix rubocop warning 2024-06-24 17:16:56 +09:00
林千里
e9e0011f1d fix zsh ${(@)history} syntax does not work with ksh_arrays (#3893)
When `setopt ksh_arrays` in zsh, `${(@kv)history}` gives only the first entry rather than all.
2024-06-24 17:07:55 +09:00
Junegunn Choi
5b52833785 Do not start the initial reader if 'reload*' is bound to 'start' 2024-06-24 17:05:53 +09:00
Koichi Murase
1525768094 [man] Escape hyphens to prevent conversion to Unicode hyphens (#3885)
ASCII hyphens (U+002D HYPHEN-MINUS) in the option names (e.g. -x and
--extended) and the code examples in the man pages should be escaped
as \- (e.g. \-x and \-\-extended) to prevent them being converted to
Unicode hyphens in some environments.

For example, in openSUSE Tumbleweed, the raw ASCII hyphens in the
man-page sources are configured to be the Unicode hyphen (U+2010
HYPHEN).  This makes it impossible to search the option name in the
man page by e.g. /--extended[RET].  A problem also arises in copying
and pasting option names and code examples from the man page.  It
appears to be the normal ASCII hyphens by appearance (in typical
terminal fonts) but are not recognized as the ASCII hyphens by the
`fzf` command.
2024-06-24 09:32:35 +09:00
Junegunn Choi
a70ea4654e Fix regression in separator display 2024-06-23 18:23:46 +09:00
Junegunn Choi
b02bf9b6bb Fix panic on extremely short terminals
Fix #3889
2024-06-23 11:27:03 +09:00
junegunn
bee7bc5324 Deploying to master from @ junegunn/fzf@7c2ffd3fef 🚀 2024-06-23 00:01:48 +00:00
Junegunn Choi
7c2ffd3fef Make transform*, --info-command, and execute-silent cancellable
Users can press CTRL-C after 1 second to terminate the command.

Close #3883
2024-06-22 17:24:47 +09:00
LangLangBart
db01e7dab6 fix(zsh): add (s) modifier to perl command (#3882) 2024-06-20 17:51:52 +09:00
Junegunn Choi
2326c74eb2 Code cleanup 2024-06-20 17:06:44 +09:00
Junegunn Choi
b9d15569e8 Fix test case for validateSign 2024-06-20 01:48:24 +09:00
Junegunn Choi
c3cc378d89 Allow empty pointer and marker
Close #3879
2024-06-20 01:45:06 +09:00
Junegunn Choi
27d1f5e0a8 Fix typos 2024-06-20 00:58:51 +09:00
Junegunn Choi
540632bb9e Add --info-command for customizing the input text
Close #3866
2024-06-20 00:53:18 +09:00
Junegunn Choi
d9c028c934 fzf-preview.sh: Let chafa decide the right format
Close #3822

  Output encoding:
    -f, --format=FORMAT  Set output format; one of [iterm, kitty, sixels,
                       symbols]. Iterm, kitty and sixels yield much higher
                       quality but enjoy limited support. Symbols mode yields
                       beautiful character art.
2024-06-19 19:25:46 +09:00
Junegunn Choi
c54ad82e8d Clarify that --nth applies after --with-nth transformation
Close #3873
2024-06-19 17:01:35 +09:00
bsdmp
295b89631b Add wpath and dpath pledges on OpenBSD to make --tmux work (#3877) 2024-06-19 11:26:08 +09:00
Jan Palus
6179faf778 mod: upgrade fastwalk to 1.0.4 (#3878)
Fixes #3832
2024-06-19 11:23:43 +09:00
dependabot[bot]
16dc236a25 Bump crate-ci/typos from 1.22.3 to 1.22.7 (#3871)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.22.3 to 1.22.7.
- [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.22.3...v1.22.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-19 11:23:30 +09:00
Hexin
61ae9d75b6 Remove comment in command substitution (#3875) 2024-06-19 11:22:57 +09:00
Junegunn Choi
e2401aca68 Add 'offset-middle' action 2024-06-17 18:34:10 +09:00
Junegunn Choi
59943cbb48 Fire 'result' even when input stream is not complete
Related: #3866
2024-06-17 17:54:52 +09:00
Junegunn Choi
02634d404d Remove {fzf:query} from man page 2024-06-17 17:53:02 +09:00
Junegunn Choi
ed12925f7d --sync: Suppress initial render also when focus event is bound 2024-06-17 17:00:49 +09:00
Junegunn Choi
e0ddb97ab4 Improved --sync behavior
When --sync is provided, fzf will not render the interface until the
initial filtering and associated actions (bound to any of 'start',
'load', or 'result') are complete.
2024-06-17 00:11:57 +09:00
junegunn
b8c01af0fc Deploying to master from @ junegunn/fzf@6de0a7ddc1 🚀 2024-06-16 00:01:59 +00:00
Junegunn Choi
6de0a7ddc1 --sync: Do not start TUI until initial filtering is complete 2024-06-15 10:46:28 +09:00
Junegunn Choi
79196c025d Clean up GitHub Actions workflow
fzf does not uses tcell-based renderer on systems where light renderer
can be used since dca2262. So this has become meaningless.
2024-06-15 10:27:54 +09:00
Junegunn Choi
94c33ac020 Fix panic when parent process is killed
Fix #3863
2024-06-15 10:23:03 +09:00
Junegunn Choi
b2ecb6352c Make GET endpoint available from 'execute' and 'transform' actions 2024-06-14 21:33:42 +09:00
Junegunn Choi
9dc3ed638a --walker-skip should also handle symlinks to directories
Fix #3858
2024-06-13 22:55:31 +09:00
dependabot[bot]
0acace1ace Bump crate-ci/typos from 1.21.0 to 1.22.3 (#3850)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.21.0 to 1.22.3.
- [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.21.0...v1.22.3)

---
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-06-13 20:51:13 +09:00
dependabot[bot]
1a2d37e1e6 Bump golang.org/x/term from 0.20.0 to 0.21.0 (#3849)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.20.0 to 0.21.0.
- [Commits](https://github.com/golang/term/compare/v0.20.0...v0.21.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-06-13 20:50:57 +09:00
LangLangBart
22adb6494f chore(shell): Separate declaration and assignment for zsh legacy versions (#3856) 2024-06-13 17:33:23 +09:00
Samara Jinnah
e023736c30 [zsh] Prevent glob expansion in history widget (#3855) 2024-06-13 10:43:33 +09:00
Junegunn Choi
dca2262fe6 Prefer LightRenderer over tcell on Windows
For mouse support on mintty

Fix #3847
2024-06-12 21:53:18 +09:00
Junegunn Choi
0684a20ea3 Fix invalid mouse offset for --height on Windows 2024-06-12 21:18:02 +09:00
Junegunn Choi
a1a72bb8d1 Do not open tmux or winpty in --filter mode 2024-06-12 21:02:48 +09:00
ismay
144d55a5be [fish] Merge history before searching (#3852)
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2024-06-12 13:56:20 +09:00
Junegunn Choi
7fc13c5cfd Less aggressive chunk cache invalidation for --tail 2024-06-10 20:33:55 +09:00
Junegunn Choi
dfee7af57b Fix divide by zero error with --tiebreak=end for long items
Fix #3846
2024-06-10 08:26:53 +09:00
junegunn
9b0e2daf02 Deploying to master from @ junegunn/fzf@590060a16b 🚀 2024-06-09 00:02:07 +00:00
Junegunn Choi
590060a16b Remove unused field 2024-06-07 17:05:33 +09:00
Junegunn Choi
368294edf6 Reduce flickering of the list when the list is truncated by --tail 2024-06-07 16:59:09 +09:00
60 changed files with 2182 additions and 1114 deletions

View File

@@ -36,7 +36,7 @@ jobs:
run: sudo apt-get install --yes zsh fish tmux run: sudo apt-get install --yes zsh fish tmux
- name: Install Ruby gems - name: Install Ruby gems
run: sudo gem install --no-document minitest:5.17.0 rubocop:1.43.0 rubocop-minitest:0.25.1 rubocop-performance:1.15.2 run: sudo gem install --no-document minitest:5.25.1 rubocop:1.65.0 rubocop-minitest:0.35.1 rubocop-performance:1.21.1
- name: Rubocop - name: Rubocop
run: rubocop --require rubocop-minitest --require rubocop-performance run: rubocop --require rubocop-minitest --require rubocop-performance
@@ -46,6 +46,3 @@ jobs:
- name: Integration test - name: Integration test
run: make install && ./install --all && tmux new-session -d && ruby test/test_go.rb --verbose run: make install && ./install --all && tmux new-session -d && ruby test/test_go.rb --verbose
- name: Integration test (tcell)
run: TAGS=tcell make clean install && ruby test/test_go.rb --verbose

View File

@@ -7,4 +7,4 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: crate-ci/typos@v1.21.0 - uses: crate-ci/typos@v1.26.0

View File

@@ -10,6 +10,5 @@ jobs:
- uses: vedantmgoyal2009/winget-releaser@v2 - uses: vedantmgoyal2009/winget-releaser@v2
with: with:
identifier: junegunn.fzf identifier: junegunn.fzf
version: ${{ github.event.release.tag_name }}
installers-regex: '-windows_(armv7|arm64|amd64)\.zip$' installers-regex: '-windows_(armv7|arm64|amd64)\.zip$'
token: ${{ secrets.WINGET_TOKEN }} token: ${{ secrets.WINGET_TOKEN }}

View File

@@ -1,4 +1,5 @@
--- ---
version: 2
project_name: fzf project_name: fzf
before: before:
@@ -6,60 +7,9 @@ before:
- go mod download - go mod download
builds: builds:
- id: fzf-macos
binary: fzf
goos:
- darwin
goarch:
- amd64
flags:
- -trimpath
ldflags:
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
hooks:
post: |
sh -c '
cat > /tmp/fzf-gon-amd64.hcl << EOF
source = ["./dist/fzf-macos_darwin_amd64_v1/fzf"]
bundle_id = "junegunn.fzf"
sign {
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
}
zip {
output_path = "./dist/fzf-{{ .Version }}-darwin_amd64.zip"
}
EOF
gon /tmp/fzf-gon-amd64.hcl
'
- id: fzf-macos-arm
binary: fzf
goos:
- darwin
goarch:
- arm64
flags:
- -trimpath
ldflags:
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
hooks:
post: |
sh -c '
cat > /tmp/fzf-gon-arm64.hcl << EOF
source = ["./dist/fzf-macos-arm_darwin_arm64/fzf"]
bundle_id = "junegunn.fzf"
sign {
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
}
zip {
output_path = "./dist/fzf-{{ .Version }}-darwin_arm64.zip"
}
EOF
gon /tmp/fzf-gon-arm64.hcl
'
- id: fzf - id: fzf
goos: goos:
- darwin
- linux - linux
- windows - windows
- freebsd - freebsd
@@ -89,6 +39,42 @@ builds:
- goos: openbsd - goos: openbsd
goarch: arm64 goarch: arm64
# .goreleaser.yaml
notarize:
macos:
- # Whether this configuration is enabled or not.
#
# Default: false.
# Templates: allowed.
enabled: "{{ not .IsSnapshot }}"
# Before notarizing, we need to sign the binary.
# This blocks defines the configuration for doing so.
sign:
# The .p12 certificate file path or its base64'd contents.
certificate: "{{.Env.MACOS_SIGN_P12}}"
# The password to be used to open the certificate.
password: "{{.Env.MACOS_SIGN_PASSWORD}}"
# Then, we notarize the binaries.
notarize:
# The issuer ID.
# Its the UUID you see when creating the App Store Connect key.
issuer_id: "{{.Env.MACOS_NOTARY_ISSUER_ID}}"
# Key ID.
# You can see it in the list of App Store Connect Keys.
# It will also be in the ApiKey filename.
key_id: "{{.Env.MACOS_NOTARY_KEY_ID}}"
# The .p8 key file path or its base64'd contents.
key: "{{.Env.MACOS_NOTARY_KEY}}"
# Whether to wait for the notarization to finish.
# Not recommended, as it could take a really long time.
wait: true
archives: archives:
- name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" - name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
builds: builds:
@@ -100,21 +86,15 @@ archives:
files: files:
- non-existent* - non-existent*
checksum:
extra_files:
- glob: ./dist/fzf-*darwin*.zip
release: release:
github: github:
owner: junegunn owner: junegunn
name: fzf name: fzf
prerelease: auto prerelease: auto
name_template: '{{ .Tag }}' name_template: '{{ .Version }}'
extra_files:
- glob: ./dist/fzf-*darwin*.zip
snapshot: snapshot:
name_template: "{{ .Tag }}-devel" name_template: "{{ .Version }}-devel"
changelog: changelog:
sort: asc sort: asc

View File

@@ -6,7 +6,7 @@ Lint/ShadowingOuterLocalVariable:
Enabled: false Enabled: false
Style/MethodCallWithArgsParentheses: Style/MethodCallWithArgsParentheses:
Enabled: true Enabled: true
IgnoredMethods: AllowedMethods:
- assert - assert
- exit - exit
- paste - paste
@@ -15,7 +15,7 @@ Style/MethodCallWithArgsParentheses:
- refute - refute
- require - require
- send_keys - send_keys
IgnoredPatterns: AllowedPatterns:
- ^assert_ - ^assert_
- ^refute_ - ^refute_
Style/NumericPredicate: Style/NumericPredicate:

View File

@@ -1,8 +1,8 @@
Advanced fzf examples Advanced fzf examples
====================== ======================
* *Last update: 2024/06/06* * *Last update: 2024/06/24*
* *Requires fzf 0.53.0 or later* * *Requires fzf 0.54.0 or later*
--- ---
@@ -361,7 +361,7 @@ projects, and it will free up memory as you narrow down the results.
# 3. Open the file in Vim # 3. Open the file in Vim
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case " RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
INITIAL_QUERY="${*:-}" INITIAL_QUERY="${*:-}"
: | fzf --ansi --disabled --query "$INITIAL_QUERY" \ fzf --ansi --disabled --query "$INITIAL_QUERY" \
--bind "start:reload:$RG_PREFIX {q}" \ --bind "start:reload:$RG_PREFIX {q}" \
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \ --bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
--delimiter : \ --delimiter : \
@@ -372,11 +372,9 @@ INITIAL_QUERY="${*:-}"
![image](https://user-images.githubusercontent.com/700826/113684212-f9ff0a00-96ff-11eb-8737-7bb571d320cc.png) ![image](https://user-images.githubusercontent.com/700826/113684212-f9ff0a00-96ff-11eb-8737-7bb571d320cc.png)
- Instead of starting fzf in the usual `rg ... | fzf` form, we start fzf with - Instead of starting fzf in the usual `rg ... | fzf` form, we make it start
an empty input (`: | fzf`), then we make it start the initial Ripgrep the initial Ripgrep process immediately via `start:reload` binding for the
process immediately via `start:reload` binding. This way, fzf owns the consistency of the code.
initial Ripgrep process so it can kill it on the next `reload`. Otherwise,
the process will keep running in the background.
- Filtering is no longer a responsibility of fzf; hence `--disabled` - Filtering is no longer a responsibility of fzf; hence `--disabled`
- `{q}` in the reload command evaluates to the query string on fzf prompt. - `{q}` in the reload command evaluates to the query string on fzf prompt.
- `sleep 0.1` in the reload command is for "debouncing". This small delay will - `sleep 0.1` in the reload command is for "debouncing". This small delay will
@@ -400,7 +398,7 @@ fzf-only search mode by *"unbinding"* `reload` action from `change` event.
# 3. Open the file in Vim # 3. Open the file in Vim
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case " RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
INITIAL_QUERY="${*:-}" INITIAL_QUERY="${*:-}"
: | fzf --ansi --disabled --query "$INITIAL_QUERY" \ fzf --ansi --disabled --query "$INITIAL_QUERY" \
--bind "start:reload:$RG_PREFIX {q}" \ --bind "start:reload:$RG_PREFIX {q}" \
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \ --bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
--bind "alt-enter:unbind(change,alt-enter)+change-prompt(2. fzf> )+enable-search+clear-query" \ --bind "alt-enter:unbind(change,alt-enter)+change-prompt(2. fzf> )+enable-search+clear-query" \
@@ -444,7 +442,7 @@ CTRL-F.
rm -f /tmp/rg-fzf-{r,f} rm -f /tmp/rg-fzf-{r,f}
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case " RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
INITIAL_QUERY="${*:-}" INITIAL_QUERY="${*:-}"
: | fzf --ansi --disabled --query "$INITIAL_QUERY" \ fzf --ansi --disabled --query "$INITIAL_QUERY" \
--bind "start:reload($RG_PREFIX {q})+unbind(ctrl-r)" \ --bind "start:reload($RG_PREFIX {q})+unbind(ctrl-r)" \
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \ --bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
--bind "ctrl-f:unbind(change,ctrl-f)+change-prompt(2. fzf> )+enable-search+rebind(ctrl-r)+transform-query(echo {q} > /tmp/rg-fzf-r; cat /tmp/rg-fzf-f)" \ --bind "ctrl-f:unbind(change,ctrl-f)+change-prompt(2. fzf> )+enable-search+rebind(ctrl-r)+transform-query(echo {q} > /tmp/rg-fzf-r; cat /tmp/rg-fzf-f)" \
@@ -487,7 +485,7 @@ prevent immediate evaluation.
rm -f /tmp/rg-fzf-{r,f} rm -f /tmp/rg-fzf-{r,f}
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case " RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
INITIAL_QUERY="${*:-}" INITIAL_QUERY="${*:-}"
: | fzf --ansi --disabled --query "$INITIAL_QUERY" \ fzf --ansi --disabled --query "$INITIAL_QUERY" \
--bind "start:reload:$RG_PREFIX {q}" \ --bind "start:reload:$RG_PREFIX {q}" \
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \ --bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
--bind 'ctrl-t:transform:[[ ! $FZF_PROMPT =~ ripgrep ]] && --bind 'ctrl-t:transform:[[ ! $FZF_PROMPT =~ ripgrep ]] &&
@@ -527,7 +525,7 @@ Kubernetes pods.
```bash ```bash
pods() { pods() {
: | command='kubectl get pods --all-namespaces' fzf \ command='kubectl get pods --all-namespaces' fzf \
--info=inline --layout=reverse --header-lines=1 \ --info=inline --layout=reverse --header-lines=1 \
--prompt "$(kubectl config current-context | sed 's/-context$//')> " \ --prompt "$(kubectl config current-context | sed 's/-context$//')> " \
--header $' Enter (kubectl exec) CTRL-O (open log in editor) CTRL-R (reload) \n\n' \ --header $' Enter (kubectl exec) CTRL-O (open log in editor) CTRL-R (reload) \n\n' \

View File

@@ -1,8 +1,156 @@
CHANGELOG CHANGELOG
========= =========
0.56.0
------
- Added `--gap[=N]` option to display empty lines between items.
- This can be useful to visually separate adjacent multi-line items.
```sh
# All bash functions, highlighted
declare -f | perl -0777 -pe 's/^}\n/}\0/gm' |
bat --plain --language bash --color always |
fzf --read0 --ansi --reverse --multi --highlight-line --gap
```
- Or just to make the list easier to read. For single-line items, you probably want to set `--color gutter:-1` as well to hide the gutter.
```sh
fzf --info inline-right --gap --color gutter:-1
```
- Added `noinfo` option to `--preview-window` to hide the scroll indicator in the preview window
- Bug fixes
- Thanks to @LangLangBart, @akinomyoga, and @charlievieth for fixing the bugs
0.55.0
------
_Release highlights: https://junegunn.github.io/fzf/releases/0.55.0/_
- Added `exact-boundary-match` type to the search syntax. When a search term is single-quoted, fzf will search for the exact occurrences of the string with both ends at word boundaries.
```sh
fzf --query "'here'" << EOF
come here
not there
EOF
```
- [bash] Fuzzy path completion is enabled for all commands
- 1. If the default completion is not already set
- 2. And if the current bash supports `complete -D` option
- However, fuzzy completion for some commands can be "dynamically" disabled by the dynamic completion loader
- See the comment in `__fzf_default_completion` function for more information
- Comments are now allowed in `$FZF_DEFAULT_OPTS` and `$FZF_DEFAULT_OPTS_FILE`
```sh
export FZF_DEFAULT_OPTS='
# Layout options
--layout=reverse
--info=inline-right # Show info on the right side of the prompt line
# ...
'
```
- Hyperlinks (OSC 8) are now supported in the preview window and in the main window
```sh
printf '<< \e]8;;http://github.com/junegunn/fzf\e\\Link to \e[32mfz\e[0mf\e]8;;\e\\ >>' | fzf --ansi
fzf --preview "printf '<< \e]8;;http://github.com/junegunn/fzf\e\\Link to \e[32mfz\e[0mf\e]8;;\e\\ >>'"
```
- The default `--ellipsis` is now `··` instead of `..`.
- [vim] A spec can have `exit` callback that is called with the exit status of fzf
- This can be used to clean up temporary resources or restore the original state when fzf is closed without a selection
- Fixed `--tmux bottom` when the status line is not at the bottom
- Fixed extra scroll offset in multi-line mode (`--read0` or `--wrap`)
- Added fallback `ps` command for `kill` completion on Cygwin
0.54.3
------
- Fixed incompatibility of adaptive height specification and 'start:reload'
```sh
# A regression in 0.54.0 would cause this to fail
fzf --height '~100%' --bind 'start:reload:seq 10'
```
- Environment variables are now available to `$FZF_DEFAULT_COMMAND`
```sh
FZF_DEFAULT_COMMAND='echo $FZF_QUERY' fzf --query foo
```
0.54.2
------
- Fixed incorrect syntax highlighting of truncated multi-line entries
- Updated GoReleaser to 2.1.0 to simplify notarization of macOS binaries
- macOS archives will be in `tar.gz` format instead of `zip` format since we no longer notarize the zip files but binaries
- (Windows) Reverted a mintty fix in 0.54.0
- As a result, mouse may not work on mintty in fullscreen mode. However, fzf will correctly read non-ASCII input in fullscreen mode (`--no-height`).
- fzf unfortunately cannot read non-ASCII input when not in fullscreen mode on Windows. So if you need to input non-ASCII characters, add `--no-height` to your `$FZF_DEFAULT_OPTS`.
- Any help in fixing this issue will be appreciated (#3799, #3847).
0.54.1
------
- Updated [fastwalk](https://github.com/charlievieth/fastwalk) dependency for built-in directory walker
- [fastwalk: add optional sorting and improve documentation](https://github.com/charlievieth/fastwalk/pull/27)
- [fastwalk: only check if MSYSTEM is set during MSYS/MSYS2](https://github.com/charlievieth/fastwalk/pull/28)
- Thanks to @charlievieth
- Reverted ALT-C binding of fish to use `cd` instead of `builtin cd`
- `builtin cd` was introduced to work around a bug of `cd` coming from `zoxide init --cmd cd fish` where it cannot handle `--` argument.
- However, the default `cd` of fish is actually a wrapper function for supporting `cd -`, so we want to use it instead.
- See [#3928](https://github.com/junegunn/fzf/pull/3928) for more information and consider helping zoxide fix the bug.
0.54.0
------
_Release highlights: https://junegunn.github.io/fzf/releases/0.54.0/_
- Implemented line wrap of long items
- `--wrap` option enables line wrap
- `--wrap-sign` customizes the sign for wrapped lines (default: ``)
- `toggle-wrap` action toggles line wrap
```sh
history | fzf --tac --wrap --bind 'ctrl-/:toggle-wrap' --wrap-sign $'\t↳ '
```
- fzf by default binds `CTRL-/` and `ALT-/` to `toggle-wrap`
- Updated shell integration scripts to leverage line wrap
- CTRL-R binding includes `--wrap-sign $'\t↳ '` to indent wrapped lines
- `kill **` completion uses `--wrap` to show the whole line by default
instead of showing it in the preview window
- Added `--info-command` option for customizing the info line
```sh
# Prepend the current cursor position in yellow
fzf --info-command='echo -e "\x1b[33;1m$FZF_POS\x1b[m/$FZF_INFO 💛"'
```
- `$FZF_INFO` is set to the original info text
- ANSI color codes are supported
- Pointer and marker signs can be set to empty strings
```sh
# Minimal style
fzf --pointer '' --marker '' --prompt '' --info hidden
```
- Better cache management and improved rendering for `--tail`
- Improved `--sync` behavior
- When `--sync` is provided, fzf will not render the interface until the initial filtering and the associated actions (bound to any of `start`, `load`, `result`, or `focus`) are complete.
```sh
# fzf will not render intermediate states
(sleep 1; seq 1000000; sleep 1) |
fzf --sync --query 5 --listen --bind start:up,load:up,result:up,focus:change-header:Ready
```
- GET endpoint is now available from `execute` and `transform` actions (it used to timeout due to lock conflict)
```sh
fzf --listen --sync --bind 'focus:transform-header:curl -s localhost:$FZF_PORT?limit=0 | jq .'
```
- Added `offset-middle` action to place the current item is in the middle of the screen
- fzf will not start the initial reader when `reload` or `reload-sync` is bound to `start` event. `fzf < /dev/null` or `: | fzf` are no longer required and extraneous `load` event will not fire due to the empty list.
```sh
# Now this will work as expected. Previously, this would print an invalid header line.
# `fzf < /dev/null` or `: | fzf` would fix the problem, but then an extraneous
# `load` event would fire and the header would be prematurely updated.
fzf --header 'Loading ...' --header-lines 1 \
--bind 'start:reload:sleep 1; ps -ef' \
--bind 'load:change-header:Loaded!'
```
- Fixed mouse support on Windows
- Fixed crash when using `--tiebreak=end` with very long items
- zsh 5.0 compatibility (thanks to @LangLangBart)
- Fixed `--walker-skip` to also skip symlinks to directories
- Fixed `result` event not fired when input stream is not complete
- New tags will have `v` prefix so that they are available on https://proxy.golang.org/
0.53.0 0.53.0
------ ------
_Release highlights: https://junegunn.github.io/fzf/releases/0.53.0/_
- Multi-line display - Multi-line display
- See [Processing multi-line items](https://junegunn.github.io/fzf/tips/processing-multi-line-items/) - See [Processing multi-line items](https://junegunn.github.io/fzf/tips/processing-multi-line-items/)
- fzf can now display multi-line items - fzf can now display multi-line items

View File

@@ -9,12 +9,12 @@ SOURCES := $(wildcard *.go src/*.go src/*/*.go shell/*sh man/man1/*.1) $(
ifdef FZF_VERSION ifdef FZF_VERSION
VERSION := $(FZF_VERSION) VERSION := $(FZF_VERSION)
else else
VERSION := $(shell git describe --abbrev=0 2> /dev/null) VERSION := $(shell git describe --abbrev=0 2> /dev/null | sed "s/^v//")
endif endif
ifeq ($(VERSION),) ifeq ($(VERSION),)
$(error Not on git repository; cannot determine $$FZF_VERSION) $(error Not on git repository; cannot determine $$FZF_VERSION)
endif endif
VERSION_TRIM := $(shell sed "s/-.*//" <<< $(VERSION)) VERSION_TRIM := $(shell sed "s/^v//; s/-.*//" <<< $(VERSION))
VERSION_REGEX := $(subst .,\.,$(VERSION_TRIM)) VERSION_REGEX := $(subst .,\.,$(VERSION_TRIM))
ifdef FZF_REVISION ifdef FZF_REVISION
@@ -77,7 +77,6 @@ endif
all: target/$(BINARY) all: target/$(BINARY)
test: $(SOURCES) test: $(SOURCES)
[ -z "$$(gofmt -s -d src)" ] || (gofmt -s -d src; exit 1)
SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \ SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \
github.com/junegunn/fzf/src \ github.com/junegunn/fzf/src \
github.com/junegunn/fzf/src/algo \ github.com/junegunn/fzf/src/algo \
@@ -87,6 +86,10 @@ test: $(SOURCES)
bench: bench:
cd src && SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" -run=Bench -bench=. -benchmem cd src && SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" -run=Bench -bench=. -benchmem
lint: $(SOURCES) test/test_go.rb
[ -z "$$(gofmt -s -d src)" ] || (gofmt -s -d src; exit 1)
rubocop --require rubocop-minitest --require rubocop-performance
install: bin/fzf install: bin/fzf
generate: generate:
@@ -184,4 +187,4 @@ update:
$(GO) get -u $(GO) get -u
$(GO) mod tidy $(GO) mod tidy
.PHONY: all generate build release test bench install clean docker docker-test update .PHONY: all generate build release test bench lint install clean docker docker-test update

View File

@@ -289,8 +289,9 @@ The following table summarizes the available options.
| `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 | | `source` | list | Vim list as input to fzf |
| `sink` | string | Vim command to handle the selected item (e.g. `e`, `tabe`) | | `sink` | string | Vim command to handle the selected item (e.g. `e`, `tabe`) |
| `sink` | funcref | Reference to function to process each selected item | | `sink` | funcref | Function to be called with each selected item |
| `sinklist` (or `sink*`) | funcref | Similar to `sink`, but takes the list of output lines at once | | `sinklist` (or `sink*`) | funcref | Similar to `sink`, but takes the list of output lines at once |
| `exit` | funcref | Function to be called with the exit status of fzf (e.g. 0, 1, 2, 130) |
| `options` | string/list | Options to fzf | | `options` | string/list | Options to fzf |
| `dir` | string | Working directory | | `dir` | string | Working directory |
| `up`/`down`/`left`/`right` | number/string | (Layout) Window position and size (e.g. `20`, `50%`) | | `up`/`down`/`left`/`right` | number/string | (Layout) Window position and size (e.g. `20`, `50%`) |

File diff suppressed because one or more lines are too long

View File

@@ -57,7 +57,7 @@ if [[ $KITTY_WINDOW_ID ]]; then
# 2. Use chafa with Sixel output # 2. Use chafa with Sixel output
elif command -v chafa > /dev/null; then elif command -v chafa > /dev/null; then
chafa -f sixel -s "$dim" "$file" chafa -s "$dim" "$file"
# Add a new line character so that fzf can display multiple images in the preview window # Add a new line character so that fzf can display multiple images in the preview window
echo echo

View File

@@ -19,6 +19,9 @@ term=""
[[ -n "$LINES" ]] && lines=$LINES || lines=$(tput lines) || lines=$(tmux display-message -p "#{pane_height}") [[ -n "$LINES" ]] && lines=$LINES || lines=$(tput lines) || lines=$(tmux display-message -p "#{pane_height}")
[[ -n "$COLUMNS" ]] && columns=$COLUMNS || columns=$(tput cols) || columns=$(tmux display-message -p "#{pane_width}") [[ -n "$COLUMNS" ]] && columns=$COLUMNS || columns=$(tput cols) || columns=$(tmux display-message -p "#{pane_width}")
tmux_version=$(tmux -V | sed 's/[^0-9.]//g')
tmux_32=$(awk '{print ($1 >= 3.2)}' <<< "$tmux_version" 2> /dev/null || bc -l <<< "$tmux_version >= 3.2")
help() { help() {
>&2 echo 'usage: fzf-tmux [LAYOUT OPTIONS] [--] [FZF OPTIONS] >&2 echo 'usage: fzf-tmux [LAYOUT OPTIONS] [--] [FZF OPTIONS]
@@ -94,10 +97,18 @@ while [[ $# -gt 0 ]]; do
opt="$opt ${arg:0:2}$size" opt="$opt ${arg:0:2}$size"
elif [[ "$size" =~ %$ ]]; then elif [[ "$size" =~ %$ ]]; then
size=${size:0:((${#size}-1))} size=${size:0:((${#size}-1))}
if [[ -n "$swap" ]]; then if [[ $tmux_32 = 1 ]]; then
opt="$opt -l $(( 100 - size ))%" if [[ -n "$swap" ]]; then
opt="$opt -l $(( 100 - size ))%"
else
opt="$opt -l $size%"
fi
else else
opt="$opt -l $size%" if [[ -n "$swap" ]]; then
opt="$opt -p $(( 100 - size ))"
else
opt="$opt -p $size"
fi
fi fi
else else
if [[ -n "$swap" ]]; then if [[ -n "$swap" ]]; then
@@ -187,12 +198,11 @@ trap 'cleanup' EXIT
envs="export TERM=$TERM " envs="export TERM=$TERM "
if [[ "$opt" =~ "-E" ]]; then if [[ "$opt" =~ "-E" ]]; then
tmux_version=$(tmux -V | sed 's/[^0-9.]//g') if [[ $tmux_version = 3.2 ]]; then
if [[ $(awk '{print ($1 > 3.2)}' <<< "$tmux_version" 2> /dev/null || bc -l <<< "$tmux_version > 3.2") = 1 ]]; then FZF_DEFAULT_OPTS="--margin 0,1 $FZF_DEFAULT_OPTS"
elif [[ $tmux_32 = 1 ]]; then
FZF_DEFAULT_OPTS="--border $FZF_DEFAULT_OPTS" FZF_DEFAULT_OPTS="--border $FZF_DEFAULT_OPTS"
opt="-B $opt" opt="-B $opt"
elif [[ $tmux_version = 3.2 ]]; then
FZF_DEFAULT_OPTS="--margin 0,1 $FZF_DEFAULT_OPTS"
else else
echo "fzf-tmux: tmux 3.2 or above is required for popup mode" >&2 echo "fzf-tmux: tmux 3.2 or above is required for popup mode" >&2
exit 2 exit 2

View File

@@ -306,8 +306,9 @@ The following table summarizes the available options.
`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 `source` | list | Vim list as input to fzf
`sink` | string | Vim command to handle the selected item (e.g. `e` , `tabe` ) `sink` | string | Vim command to handle the selected item (e.g. `e` , `tabe` )
`sink` | funcref | Reference to function to process each selected item `sink` | funcref | Function to be called with each selected item
`sinklist` (or `sink*` ) | funcref | Similar to `sink` , but takes the list of output lines at once `sinklist` (or `sink*` ) | funcref | Similar to `sink` , but takes the list of output lines at once
`exit` | funcref | Function to be called with the exit status of fzf (e.g. 0, 1, 2, 130)
`options` | string/list | Options to fzf `options` | string/list | Options to fzf
`dir` | string | Working directory `dir` | string | Working directory
`up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` ) `up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` )

8
go.mod
View File

@@ -1,13 +1,13 @@
module github.com/junegunn/fzf module github.com/junegunn/fzf
require ( require (
github.com/charlievieth/fastwalk v1.0.3 github.com/charlievieth/fastwalk v1.0.9
github.com/gdamore/tcell/v2 v2.7.4 github.com/gdamore/tcell/v2 v2.7.4
github.com/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97
github.com/mattn/go-isatty v0.0.20 github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-shellwords v1.0.12
github.com/rivo/uniseg v0.4.7 github.com/rivo/uniseg v0.4.7
golang.org/x/sys v0.20.0 golang.org/x/sys v0.26.0
golang.org/x/term v0.20.0 golang.org/x/term v0.25.0
) )
require ( require (

16
go.sum
View File

@@ -1,17 +1,17 @@
github.com/charlievieth/fastwalk v1.0.3 h1:eNWFaNPe5srPqQ5yyDbhAf11paeZaHWcihRhpuYFfSg= github.com/charlievieth/fastwalk v1.0.9 h1:Odb92AfoReO3oFBfDGT5J+nwgzQPF/gWAw6E6/lkor0=
github.com/charlievieth/fastwalk v1.0.3/go.mod h1:JSfglY/gmL/rqsUS1NCsJTocB5n6sSl9ApAqif4CUbs= github.com/charlievieth/fastwalk v1.0.9/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU= github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU=
github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg= github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg=
github.com/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97 h1:rqzLixVo1c/GQW6px9j1xQmlvQIn+lf/V6M1UQ7IFzw=
github.com/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97/go.mod h1:6EILKtGpo5t+KLb85LNZLAF6P9LKp78hJI80PXMcn3c=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 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/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
@@ -36,14 +36,14 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=

View File

@@ -2,7 +2,7 @@
set -u set -u
version=0.53.0 version=0.56.0
auto_completion= auto_completion=
key_bindings= key_bindings=
update_config=2 update_config=2
@@ -146,7 +146,7 @@ download() {
fi fi
local url local url
url=https://github.com/junegunn/fzf/releases/download/$version/${1} url=https://github.com/junegunn/fzf/releases/download/v$version/${1}
set -o pipefail set -o pipefail
if ! (try_curl $url || try_wget $url); then if ! (try_curl $url || try_wget $url); then
set +o pipefail set +o pipefail
@@ -168,8 +168,8 @@ archi=$(uname -sm)
binary_available=1 binary_available=1
binary_error="" binary_error=""
case "$archi" in case "$archi" in
Darwin\ arm64) download fzf-$version-darwin_arm64.zip ;; Darwin\ arm64) download fzf-$version-darwin_arm64.tar.gz ;;
Darwin\ x86_64) download fzf-$version-darwin_amd64.zip ;; Darwin\ x86_64) download fzf-$version-darwin_amd64.tar.gz ;;
Linux\ armv5*) download fzf-$version-linux_armv5.tar.gz ;; Linux\ armv5*) download fzf-$version-linux_armv5.tar.gz ;;
Linux\ armv6*) download fzf-$version-linux_armv6.tar.gz ;; Linux\ armv6*) download fzf-$version-linux_armv6.tar.gz ;;
Linux\ armv7*) download fzf-$version-linux_armv7.tar.gz ;; Linux\ armv7*) download fzf-$version-linux_armv7.tar.gz ;;

View File

@@ -1,4 +1,4 @@
$version="0.53.0" $version="0.56.0"
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition $fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
@@ -40,7 +40,7 @@ function download {
return return
} }
cd "$fzf_base\bin" cd "$fzf_base\bin"
$url="https://github.com/junegunn/fzf/releases/download/$version/$file" $url="https://github.com/junegunn/fzf/releases/download/v$version/$file"
$temp=$env:TMP + "\fzf.zip" $temp=$env:TMP + "\fzf.zip"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
if ($PSVersionTable.PSVersion.Major -ge 3) { if ($PSVersionTable.PSVersion.Major -ge 3) {

View File

@@ -11,7 +11,7 @@ import (
"github.com/junegunn/fzf/src/protector" "github.com/junegunn/fzf/src/protector"
) )
var version = "0.53" var version = "0.56"
var revision = "devel" var revision = "devel"
//go:embed shell/key-bindings.bash //go:embed shell/key-bindings.bash
@@ -39,7 +39,7 @@ func printScript(label string, content []byte) {
} }
func exit(code int, err error) { func exit(code int, err error) {
if code == fzf.ExitError { if code == fzf.ExitError && err != nil {
fmt.Fprintln(os.Stderr, err.Error()) fmt.Fprintln(os.Stderr, err.Error())
} }
os.Exit(code) os.Exit(code)

View File

@@ -21,48 +21,48 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf-tmux 1 "Jun 2024" "fzf 0.53.0" "fzf-tmux - open fzf in tmux split pane" .TH fzf\-tmux 1 "Oct 2024" "fzf 0.56.0" "fzf\-tmux - open fzf in tmux split pane"
.SH NAME .SH NAME
fzf-tmux - open fzf in tmux split pane fzf\-tmux - open fzf in tmux split pane
.SH SYNOPSIS .SH SYNOPSIS
.B fzf-tmux [LAYOUT OPTIONS] [--] [FZF OPTIONS] .B fzf\-tmux [\fILAYOUT OPTIONS\fR] [\-\-] [\fIFZF OPTIONS\fR]
.SH DESCRIPTION .SH DESCRIPTION
fzf-tmux is a wrapper script for fzf that opens fzf in a tmux split pane or in fzf\-tmux is a wrapper script for fzf that opens fzf in a tmux split pane or in
a tmux popup window. It is designed to work just like fzf except that it does a tmux popup window. It is designed to work just like fzf except that it does
not take up the whole screen. You can safely use fzf-tmux instead of fzf in not take up the whole screen. You can safely use fzf\-tmux instead of fzf in
your scripts as the extra options will be silently ignored if you're not on your scripts as the extra options will be silently ignored if you're not on
tmux. tmux.
.SH LAYOUT OPTIONS .SH LAYOUT OPTIONS
(default layout: \fB-d 50%\fR) (default layout: \fB\-d 50%\fR)
.SS Popup window .SS Popup window
(requires tmux 3.2 or above) (requires tmux 3.2 or above)
.TP .TP
.B "-p [WIDTH[%][,HEIGHT[%]]]" .B "\-p [WIDTH[%][,HEIGHT[%]]]"
.TP .TP
.B "-w WIDTH[%]" .B "\-w WIDTH[%]"
.TP .TP
.B "-h WIDTH[%]" .B "\-h WIDTH[%]"
.TP .TP
.B "-x COL" .B "\-x COL"
.TP .TP
.B "-y ROW" .B "\-y ROW"
.SS Split pane .SS Split pane
.TP .TP
.B "-u [height[%]]" .B "\-u [height[%]]"
Split above (up) Split above (up)
.TP .TP
.B "-d [height[%]]" .B "\-d [height[%]]"
Split below (down) Split below (down)
.TP .TP
.B "-l [width[%]]" .B "\-l [width[%]]"
Split left Split left
.TP .TP
.B "-r [width[%]]" .B "\-r [width[%]]"
Split right Split right

File diff suppressed because it is too large Load Diff

View File

@@ -198,6 +198,7 @@ function! s:compare_binary_versions(a, b)
return s:compare_versions(s:get_version(a:a), s:get_version(a:b)) return s:compare_versions(s:get_version(a:a), s:get_version(a:b))
endfunction endfunction
let s:min_version = '0.53.0'
let s:checked = {} let s:checked = {}
function! fzf#exec(...) function! fzf#exec(...)
if !exists('s:exec') if !exists('s:exec')
@@ -225,7 +226,11 @@ function! fzf#exec(...)
let s:exec = binaries[-1] let s:exec = binaries[-1]
endif endif
if a:0 && !has_key(s:checked, a:1) let min_version = s:min_version
if a:0 && s:compare_versions(a:1, min_version) > 0
let min_version = a:1
endif
if !has_key(s:checked, min_version)
let fzf_version = s:get_version(s:exec) let fzf_version = s:get_version(s:exec)
if empty(fzf_version) if empty(fzf_version)
let message = printf('Failed to run "%s --version"', s:exec) let message = printf('Failed to run "%s --version"', s:exec)
@@ -233,17 +238,17 @@ function! fzf#exec(...)
throw message throw message
end end
if s:compare_versions(fzf_version, a:1) >= 0 if s:compare_versions(fzf_version, min_version) >= 0
let s:checked[a:1] = 1 let s:checked[min_version] = 1
return s:exec return s:exec
elseif a:0 < 2 && input(printf('You need fzf %s or above. Found: %s. Download binary? (y/n) ', a:1, fzf_version)) =~? '^y' elseif a:0 < 2 && input(printf('You need fzf %s or above. Found: %s. Download binary? (y/n) ', min_version, fzf_version)) =~? '^y'
let s:versions = {} let s:versions = {}
unlet s:exec unlet s:exec
redraw redraw
call fzf#install() call fzf#install()
return fzf#exec(a:1, 1) return fzf#exec(min_version, 1)
else else
throw printf('You need to upgrade fzf (required: %s or above)', a:1) throw printf('You need to upgrade fzf (required: %s or above)', min_version)
endif endif
endif endif
@@ -665,21 +670,17 @@ else
let s:launcher = function('s:xterm_launcher') let s:launcher = function('s:xterm_launcher')
endif endif
function! s:exit_handler(code, command, ...) function! s:exit_handler(dict, code, command, ...)
if a:code == 130 if has_key(a:dict, 'exit')
return 0 call a:dict.exit(a:code)
elseif has('nvim') && a:code == 129 endif
" When deleting the terminal buffer while fzf is still running, if a:code == 2
" Nvim sends SIGHUP.
return 0
elseif a:code > 1
call s:error('Error running ' . a:command) call s:error('Error running ' . a:command)
if !empty(a:000) if !empty(a:000)
sleep sleep
endif endif
return 0
endif endif
return 1 return a:code
endfunction endfunction
function! s:execute(dict, command, use_height, temps) abort function! s:execute(dict, command, use_height, temps) abort
@@ -731,7 +732,7 @@ function! s:execute(dict, command, use_height, temps) abort
let exit_status = v:shell_error let exit_status = v:shell_error
redraw! redraw!
let lines = s:collect(a:temps) let lines = s:collect(a:temps)
return s:exit_handler(exit_status, command) ? lines : [] return s:exit_handler(a:dict, exit_status, command) < 2 ? lines : []
endfunction endfunction
function! s:execute_tmux(dict, command, temps) abort function! s:execute_tmux(dict, command, temps) abort
@@ -746,7 +747,7 @@ function! s:execute_tmux(dict, command, temps) abort
let exit_status = v:shell_error let exit_status = v:shell_error
redraw! redraw!
let lines = s:collect(a:temps) let lines = s:collect(a:temps)
return s:exit_handler(exit_status, command) ? lines : [] return s:exit_handler(a:dict, exit_status, command) < 2 ? lines : []
endfunction endfunction
function! s:calc_size(max, val, dict) function! s:calc_size(max, val, dict)
@@ -912,7 +913,7 @@ function! s:execute_term(dict, command, temps) abort
endif endif
let lines = s:collect(self.temps) let lines = s:collect(self.temps)
if !s:exit_handler(a:code, self.command, 1) if s:exit_handler(self.dict, a:code, self.command, 1) >= 2
return return
endif endif

View File

@@ -167,6 +167,7 @@ _fzf_opts_completion() {
--version --version
--with-nth --with-nth
--with-shell --with-shell
--wrap
--zsh --zsh
-0 --exit-0 -0 --exit-0
-1 --select-1 -1 --select-1
@@ -263,6 +264,7 @@ _fzf_handle_dynamic_completion() {
# _completion_loader may not have updated completion for the command # _completion_loader may not have updated completion for the command
if [[ "$(complete -p "$orig_cmd" 2> /dev/null)" != "$orig_complete" ]]; then if [[ "$(complete -p "$orig_cmd" 2> /dev/null)" != "$orig_complete" ]]; then
__fzf_orig_completion < <(complete -p "$orig_cmd" 2> /dev/null) __fzf_orig_completion < <(complete -p "$orig_cmd" 2> /dev/null)
__fzf_orig_completion_get_orig_func "$cmd" || ret=1
# Update orig_complete by _fzf_orig_completion entry # Update orig_complete by _fzf_orig_completion entry
[[ $orig_complete =~ ' -F '(_fzf_[^ ]+)' ' ]] && [[ $orig_complete =~ ' -F '(_fzf_[^ ]+)' ' ]] &&
@@ -288,7 +290,7 @@ __fzf_generic_path_completion() {
fi fi
COMPREPLY=() COMPREPLY=()
trigger=${FZF_COMPLETION_TRIGGER-'**'} trigger=${FZF_COMPLETION_TRIGGER-'**'}
cur="${COMP_WORDS[COMP_CWORD]}" [[ $COMP_CWORD -ge 0 ]] && cur="${COMP_WORDS[COMP_CWORD]}"
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
base=${cur:0:${#cur}-${#trigger}} base=${cur:0:${#cur}-${#trigger}}
eval "base=$base" 2> /dev/null || return eval "base=$base" 2> /dev/null || return
@@ -375,7 +377,7 @@ _fzf_complete() {
selected=$( selected=$(
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse" "${FZF_COMPLETION_OPTS-} $str_arg") \ FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse" "${FZF_COMPLETION_OPTS-} $str_arg") \
FZF_DEFAULT_OPTS_FILE='' \ FZF_DEFAULT_OPTS_FILE='' \
__fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | command tr '\n' ' ') __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | eval "$post" | command tr '\n' ' ')
selected=${selected% } # Strip trailing space not to repeat "-o nospace" selected=${selected% } # Strip trailing space not to repeat "-o nospace"
if [[ -n "$selected" ]]; then if [[ -n "$selected" ]]; then
COMPREPLY=("$selected") COMPREPLY=("$selected")
@@ -407,9 +409,10 @@ _fzf_complete_kill() {
} }
_fzf_proc_completion() { _fzf_proc_completion() {
_fzf_complete -m --header-lines=1 --preview 'echo {}' --preview-window down:3:wrap --min-height 15 -- "$@" < <( _fzf_complete -m --header-lines=1 --no-preview --wrap -- "$@" < <(
command ps -eo user,pid,ppid,start,time,command 2> /dev/null || command ps -eo user,pid,ppid,start,time,command 2> /dev/null ||
command ps -eo user,pid,ppid,time,args # For BusyBox command ps -eo user,pid,ppid,time,args 2> /dev/null || # For BusyBox
command ps --everyone --full --windows # For cygwin
) )
} }
@@ -479,12 +482,38 @@ complete -o default -F _fzf_opts_completion fzf
# fzf-tmux specific options (like `-w WIDTH`) are left as a future patch. # fzf-tmux specific options (like `-w WIDTH`) are left as a future patch.
complete -o default -F _fzf_opts_completion fzf-tmux complete -o default -F _fzf_opts_completion fzf-tmux
# Default path completion
__fzf_default_completion() {
__fzf_generic_path_completion _fzf_compgen_path "-m" "" "$@"
# Dynamic completion loader has updated the completion for the command
if [[ $? -eq 124 ]]; then
# We trigger _fzf_setup_completion so that fuzzy completion for the command
# still works. However, loader can update the completion for multiple
# commands at once, and fuzzy completion will no longer work for those
# other commands. e.g. pytest -> py.test, pytest-2, pytest-3, etc
_fzf_setup_completion path "$1"
return 124
fi
}
# Set fuzzy path completion as the default completion for all commands.
# We can't set up default completion,
# 1. if it's already set up by another script
# 2. or if the current version of bash doesn't support -D option
complete | command grep -q __fzf_default_completion ||
complete | command grep -- '-D$' | command grep -qv _comp_complete_load ||
complete -D -F __fzf_default_completion -o default -o bashdefault 2> /dev/null
d_cmds="${FZF_COMPLETION_DIR_COMMANDS-cd pushd rmdir}" d_cmds="${FZF_COMPLETION_DIR_COMMANDS-cd pushd rmdir}"
# NOTE: $FZF_COMPLETION_PATH_COMMANDS and $FZF_COMPLETION_VAR_COMMANDS are # NOTE: $FZF_COMPLETION_PATH_COMMANDS and $FZF_COMPLETION_VAR_COMMANDS are
# undocumented and subject to change in the future. # undocumented and subject to change in the future.
#
# NOTE: Although we have default completion, we still need to set up completion
# for each command in case they already have completion set up by another script.
a_cmds="${FZF_COMPLETION_PATH_COMMANDS-" a_cmds="${FZF_COMPLETION_PATH_COMMANDS-"
awk bat cat diff diff3 awk bat cat code diff diff3
emacs emacsclient ex file ftp g++ gcc gvim head hg hx java emacs emacsclient ex file ftp g++ gcc gvim head hg hx java
javac ld less more mvim nvim patch perl python ruby javac ld less more mvim nvim patch perl python ruby
sed sftp sort source tail tee uniq vi view vim wc xdg-open sed sftp sort source tail tee uniq vi view vim wc xdg-open

View File

@@ -157,7 +157,8 @@ __fzf_generic_path_completion() {
[ -z "$dir" ] && dir='.' [ -z "$dir" ] && dir='.'
[ "$dir" != "/" ] && dir="${dir/%\//}" [ "$dir" != "/" ] && dir="${dir/%\//}"
matches=$( matches=$(
export FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --scheme=path" "${FZF_COMPLETION_OPTS-}") export FZF_DEFAULT_OPTS
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --scheme=path" "${FZF_COMPLETION_OPTS-}")
unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE
if declare -f "$compgen" > /dev/null; then if declare -f "$compgen" > /dev/null; then
eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover"
@@ -170,9 +171,9 @@ __fzf_generic_path_completion() {
rest=${FZF_COMPLETION_PATH_OPTS-} rest=${FZF_COMPLETION_PATH_OPTS-}
fi fi
__fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" --walker "$walker" --walker-root="$dir" ${(Q)${(Z+n+)rest}} < /dev/tty __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" --walker "$walker" --walker-root="$dir" ${(Q)${(Z+n+)rest}} < /dev/tty
fi | while read item; do fi | while read -r item; do
item="${item%$suffix}$suffix" item="${item%$suffix}$suffix"
echo -n "${(q)item} " echo -n -E "${(q)item} "
done done
) )
matches=${matches% } matches=${matches% }
@@ -197,11 +198,11 @@ _fzf_dir_completion() {
"" "/" "" "" "/" ""
} }
_fzf_feed_fifo() ( _fzf_feed_fifo() {
command rm -f "$1" command rm -f "$1"
mkfifo "$1" mkfifo "$1"
cat <&0 > "$1" & cat <&0 > "$1" &|
) }
_fzf_complete() { _fzf_complete() {
setopt localoptions ksh_arrays setopt localoptions ksh_arrays
@@ -264,13 +265,14 @@ _fzf_complete_telnet() {
# The first and the only argument is the LBUFFER without the current word that contains the trigger. # 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. # The current word without the trigger is in the $prefix variable passed from the caller.
_fzf_complete_ssh() { _fzf_complete_ssh() {
local tokens=(${(z)1}) local -a tokens
tokens=(${(z)1})
case ${tokens[-1]} in case ${tokens[-1]} in
-i|-F|-E) -i|-F|-E)
_fzf_path_completion "$prefix" "$1" _fzf_path_completion "$prefix" "$1"
;; ;;
*) *)
local user= local user
[[ $prefix =~ @ ]] && user="${prefix%%@*}@" [[ $prefix =~ @ ]] && user="${prefix%%@*}@"
_fzf_complete +m -- "$@" < <(__fzf_list_hosts | awk -v user="$user" '{print user $0}') _fzf_complete +m -- "$@" < <(__fzf_list_hosts | awk -v user="$user" '{print user $0}')
;; ;;
@@ -296,9 +298,10 @@ _fzf_complete_unalias() {
} }
_fzf_complete_kill() { _fzf_complete_kill() {
_fzf_complete -m --header-lines=1 --preview 'echo {}' --preview-window down:3:wrap --min-height 15 -- "$@" < <( _fzf_complete -m --header-lines=1 --no-preview --wrap -- "$@" < <(
command ps -eo user,pid,ppid,start,time,command 2> /dev/null || command ps -eo user,pid,ppid,start,time,command 2> /dev/null ||
command ps -eo user,pid,ppid,time,args # For BusyBox command ps -eo user,pid,ppid,time,args 2> /dev/null || # For BusyBox
command ps --everyone --full --windows # For cygwin
) )
} }

View File

@@ -62,7 +62,7 @@ if command -v perl > /dev/null; then
set +o pipefail set +o pipefail
builtin fc -lnr -2147483648 | builtin fc -lnr -2147483648 |
last_hist=$(HISTTIMEFORMAT='' builtin history 1) command perl -n -l0 -e "$script" | last_hist=$(HISTTIMEFORMAT='' builtin history 1) command perl -n -l0 -e "$script" |
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --highlight-line ${FZF_CTRL_R_OPTS-} +m --read0") \ FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '"$'\t'"↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} +m --read0") \
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) --query "$READLINE_LINE" FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) --query "$READLINE_LINE"
) || return ) || return
READLINE_LINE=$(command perl -pe 's/^\d*\t//' <<< "$output") READLINE_LINE=$(command perl -pe 's/^\d*\t//' <<< "$output")
@@ -91,7 +91,7 @@ else # awk - fallback for POSIX systems
set +o pipefail set +o pipefail
builtin fc -lnr -2147483648 2> /dev/null | # ( $'\t '<lines>$'\n' )* ; <lines> ::= [^\n]* ( $'\n'<lines> )* builtin fc -lnr -2147483648 2> /dev/null | # ( $'\t '<lines>$'\n' )* ; <lines> ::= [^\n]* ( $'\n'<lines> )*
command $__fzf_awk "$script" | # ( <counter>$'\t'<lines>$'\000' )* command $__fzf_awk "$script" | # ( <counter>$'\t'<lines>$'\000' )*
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --highlight-line ${FZF_CTRL_R_OPTS-} +m --read0") \ FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '"$'\t'"↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} +m --read0") \
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) --query "$READLINE_LINE" FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) --query "$READLINE_LINE"
) || return ) || return
READLINE_LINE=${output#*$'\t'} READLINE_LINE=${output#*$'\t'}

View File

@@ -62,17 +62,22 @@ function fzf_key_bindings
set -l FISH_MAJOR (echo $version | cut -f1 -d.) set -l FISH_MAJOR (echo $version | cut -f1 -d.)
set -l FISH_MINOR (echo $version | cut -f2 -d.) set -l FISH_MINOR (echo $version | cut -f2 -d.)
# merge history from other sessions before searching
if test -z "$fish_private_mode"
builtin history merge
end
# history's -z flag is needed for multi-line support. # history's -z flag is needed for multi-line support.
# history's -z flag was added in fish 2.4.0, so don't use it for versions # history's -z flag was added in fish 2.4.0, so don't use it for versions
# before 2.4.0. # before 2.4.0.
if [ "$FISH_MAJOR" -gt 2 -o \( "$FISH_MAJOR" -eq 2 -a "$FISH_MINOR" -ge 4 \) ]; if [ "$FISH_MAJOR" -gt 2 -o \( "$FISH_MAJOR" -eq 2 -a "$FISH_MINOR" -ge 4 \) ];
if type -P perl > /dev/null 2>&1 if type -P perl > /dev/null 2>&1
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --highlight-line $FZF_CTRL_R_OPTS +m") set -lx FZF_DEFAULT_OPTS (__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '"\t"↳ ' --highlight-line $FZF_CTRL_R_OPTS +m")
set -lx FZF_DEFAULT_OPTS_FILE '' set -lx FZF_DEFAULT_OPTS_FILE ''
builtin history -z --reverse | command perl -0 -pe 's/^/$.\t/g; s/\n/\n\t/gm' | eval (__fzfcmd) --tac --read0 --print0 -q '(commandline)' | command perl -pe 's/^\d*\t//' | read -lz result builtin history -z --reverse | command perl -0 -pe 's/^/$.\t/g; s/\n/\n\t/gm' | eval (__fzfcmd) --tac --read0 --print0 -q '(commandline)' | command perl -pe 's/^\d*\t//' | read -lz result
and commandline -- $result and commandline -- $result
else else
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "" "--scheme=history --bind=ctrl-r:toggle-sort --highlight-line $FZF_CTRL_R_OPTS +m") set -lx FZF_DEFAULT_OPTS (__fzf_defaults "" "--scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '"\t"↳ ' --highlight-line $FZF_CTRL_R_OPTS +m")
set -lx FZF_DEFAULT_OPTS_FILE '' set -lx FZF_DEFAULT_OPTS_FILE ''
builtin history -z | eval (__fzfcmd) --read0 --print0 -q '(commandline)' | read -lz result builtin history -z | eval (__fzfcmd) --read0 --print0 -q '(commandline)' | read -lz result
and commandline -- $result and commandline -- $result
@@ -99,7 +104,7 @@ function fzf_key_bindings
eval (__fzfcmd)' +m --query "'$fzf_query'"' | read -l result eval (__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
if [ -n "$result" ] if [ -n "$result" ]
builtin cd -- $result cd -- $result
# Remove last token from commandline. # Remove last token from commandline.
commandline -t "" commandline -t ""

View File

@@ -52,8 +52,8 @@ __fzf_select() {
local item local item
FZF_DEFAULT_COMMAND=${FZF_CTRL_T_COMMAND:-} \ FZF_DEFAULT_COMMAND=${FZF_CTRL_T_COMMAND:-} \
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --walker=file,dir,follow,hidden --scheme=path" "${FZF_CTRL_T_OPTS-} -m") \ FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --walker=file,dir,follow,hidden --scheme=path" "${FZF_CTRL_T_OPTS-} -m") \
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) "$@" < /dev/tty | while read item; do FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) "$@" < /dev/tty | while read -r item; do
echo -n "${(q)item} " echo -n -E "${(q)item} "
done done
local ret=$? local ret=$?
echo echo
@@ -106,24 +106,24 @@ fi
# CTRL-R - Paste the selected command from history into the command line # CTRL-R - Paste the selected command from history into the command line
fzf-history-widget() { fzf-history-widget() {
local selected num local selected
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases noglob nobash_rematch 2> /dev/null
# Ensure the associative history array, which maps event numbers to the full # Ensure the associative history array, which maps event numbers to the full
# history lines, is loaded, and that Perl is installed for multi-line output. # history lines, is loaded, and that Perl is installed for multi-line output.
if zmodload -F zsh/parameter p:history 2>/dev/null && (( ${#commands[perl]} )); then if zmodload -F zsh/parameter p:history 2>/dev/null && (( ${#commands[perl]} )); then
selected="$(printf '%1$s\t%2$s\000' "${(vk)history[@]}" | selected="$(printf '%s\t%s\000' "${(kv)history[@]}" |
perl -0 -ne 'if (!$seen{(/^\s*[0-9]+\**\s+(.*)/, $1)}++) { s/\n/\n\t/gm; print; }' | perl -0 -ne 'if (!$seen{(/^\s*[0-9]+\**\t(.*)/s, $1)}++) { s/\n/\n\t/g; print; }' |
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m --read0") \ FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '\t↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m --read0") \
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))" FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
else else
selected="$(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' | selected="$(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m") \ FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '\t↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m") \
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))" FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
fi fi
local ret=$? local ret=$?
if [ -n "$selected" ]; then if [ -n "$selected" ]; then
if num=$(awk '{print $1; exit}' <<< "$selected" | grep -o '^[1-9][0-9]*'); then if [[ $(awk '{print $1; exit}' <<< "$selected") =~ ^[1-9][0-9]* ]]; then
zle vi-fetch-history -n $num zle vi-fetch-history -n $MATCH
else # selected is a custom query, not from history else # selected is a custom query, not from history
LBUFFER="$selected" LBUFFER="$selected"
fi fi

View File

@@ -58,73 +58,74 @@ func _() {
_ = x[actToggleTrack-47] _ = x[actToggleTrack-47]
_ = x[actToggleTrackCurrent-48] _ = x[actToggleTrackCurrent-48]
_ = x[actToggleHeader-49] _ = x[actToggleHeader-49]
_ = x[actTrackCurrent-50] _ = x[actToggleWrap-50]
_ = x[actUntrackCurrent-51] _ = x[actTrackCurrent-51]
_ = x[actDown-52] _ = x[actUntrackCurrent-52]
_ = x[actUp-53] _ = x[actDown-53]
_ = x[actPageUp-54] _ = x[actUp-54]
_ = x[actPageDown-55] _ = x[actPageUp-55]
_ = x[actPosition-56] _ = x[actPageDown-56]
_ = x[actHalfPageUp-57] _ = x[actPosition-57]
_ = x[actHalfPageDown-58] _ = x[actHalfPageUp-58]
_ = x[actOffsetUp-59] _ = x[actHalfPageDown-59]
_ = x[actOffsetDown-60] _ = x[actOffsetUp-60]
_ = x[actJump-61] _ = x[actOffsetDown-61]
_ = x[actJumpAccept-62] _ = x[actOffsetMiddle-62]
_ = x[actPrintQuery-63] _ = x[actJump-63]
_ = x[actRefreshPreview-64] _ = x[actJumpAccept-64]
_ = x[actReplaceQuery-65] _ = x[actPrintQuery-65]
_ = x[actToggleSort-66] _ = x[actRefreshPreview-66]
_ = x[actShowPreview-67] _ = x[actReplaceQuery-67]
_ = x[actHidePreview-68] _ = x[actToggleSort-68]
_ = x[actTogglePreview-69] _ = x[actShowPreview-69]
_ = x[actTogglePreviewWrap-70] _ = x[actHidePreview-70]
_ = x[actTransform-71] _ = x[actTogglePreview-71]
_ = x[actTransformBorderLabel-72] _ = x[actTogglePreviewWrap-72]
_ = x[actTransformHeader-73] _ = x[actTransform-73]
_ = x[actTransformPreviewLabel-74] _ = x[actTransformBorderLabel-74]
_ = x[actTransformPrompt-75] _ = x[actTransformHeader-75]
_ = x[actTransformQuery-76] _ = x[actTransformPreviewLabel-76]
_ = x[actPreview-77] _ = x[actTransformPrompt-77]
_ = x[actChangePreview-78] _ = x[actTransformQuery-78]
_ = x[actChangePreviewWindow-79] _ = x[actPreview-79]
_ = x[actPreviewTop-80] _ = x[actChangePreview-80]
_ = x[actPreviewBottom-81] _ = x[actChangePreviewWindow-81]
_ = x[actPreviewUp-82] _ = x[actPreviewTop-82]
_ = x[actPreviewDown-83] _ = x[actPreviewBottom-83]
_ = x[actPreviewPageUp-84] _ = x[actPreviewUp-84]
_ = x[actPreviewPageDown-85] _ = x[actPreviewDown-85]
_ = x[actPreviewHalfPageUp-86] _ = x[actPreviewPageUp-86]
_ = x[actPreviewHalfPageDown-87] _ = x[actPreviewPageDown-87]
_ = x[actPrevHistory-88] _ = x[actPreviewHalfPageUp-88]
_ = x[actPrevSelected-89] _ = x[actPreviewHalfPageDown-89]
_ = x[actPrint-90] _ = x[actPrevHistory-90]
_ = x[actPut-91] _ = x[actPrevSelected-91]
_ = x[actNextHistory-92] _ = x[actPrint-92]
_ = x[actNextSelected-93] _ = x[actPut-93]
_ = x[actExecute-94] _ = x[actNextHistory-94]
_ = x[actExecuteSilent-95] _ = x[actNextSelected-95]
_ = x[actExecuteMulti-96] _ = x[actExecute-96]
_ = x[actSigStop-97] _ = x[actExecuteSilent-97]
_ = x[actFirst-98] _ = x[actExecuteMulti-98]
_ = x[actLast-99] _ = x[actSigStop-99]
_ = x[actReload-100] _ = x[actFirst-100]
_ = x[actReloadSync-101] _ = x[actLast-101]
_ = x[actDisableSearch-102] _ = x[actReload-102]
_ = x[actEnableSearch-103] _ = x[actReloadSync-103]
_ = x[actSelect-104] _ = x[actDisableSearch-104]
_ = x[actDeselect-105] _ = x[actEnableSearch-105]
_ = x[actUnbind-106] _ = x[actSelect-106]
_ = x[actRebind-107] _ = x[actDeselect-107]
_ = x[actBecome-108] _ = x[actUnbind-108]
_ = x[actResponse-109] _ = x[actRebind-109]
_ = x[actShowHeader-110] _ = x[actBecome-110]
_ = x[actHideHeader-111] _ = x[actShowHeader-111]
_ = x[actHideHeader-112]
} }
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactResponseactShowHeaderactHideHeader" const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactShowHeaderactHideHeader"
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 256, 277, 292, 306, 320, 333, 350, 358, 371, 387, 399, 407, 421, 435, 446, 457, 475, 492, 499, 518, 530, 544, 553, 568, 580, 593, 604, 615, 627, 641, 662, 677, 692, 709, 716, 721, 730, 741, 752, 765, 780, 791, 804, 811, 824, 837, 854, 869, 882, 896, 910, 926, 946, 958, 981, 999, 1023, 1041, 1058, 1068, 1084, 1106, 1119, 1135, 1147, 1161, 1177, 1195, 1215, 1237, 1251, 1266, 1274, 1280, 1294, 1309, 1319, 1335, 1350, 1360, 1368, 1375, 1384, 1397, 1413, 1428, 1437, 1448, 1457, 1466, 1475, 1486, 1499, 1512} var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 256, 277, 292, 306, 320, 333, 350, 358, 371, 387, 399, 407, 421, 435, 446, 457, 475, 492, 499, 518, 530, 544, 553, 568, 580, 593, 604, 615, 627, 641, 662, 677, 690, 705, 722, 729, 734, 743, 754, 765, 778, 793, 804, 817, 832, 839, 852, 865, 882, 897, 910, 924, 938, 954, 974, 986, 1009, 1027, 1051, 1069, 1086, 1096, 1112, 1134, 1147, 1163, 1175, 1189, 1205, 1223, 1243, 1265, 1279, 1294, 1302, 1308, 1322, 1337, 1347, 1363, 1378, 1388, 1396, 1403, 1412, 1425, 1441, 1456, 1465, 1476, 1485, 1494, 1503, 1516, 1529}
func (i actionType) String() string { func (i actionType) String() string {
if i < 0 || i >= actionType(len(_actionType_index)-1) { if i < 0 || i >= actionType(len(_actionType_index)-1) {

View File

@@ -798,6 +798,14 @@ func FuzzyMatchV1(caseSensitive bool, normalize bool, forward bool, text *util.C
// The solution is much cheaper since there is only one possible alignment of // The solution is much cheaper since there is only one possible alignment of
// the pattern. // the pattern.
func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) { func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
return exactMatchNaive(caseSensitive, normalize, forward, false, text, pattern, withPos, slab)
}
func ExactMatchBoundary(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
return exactMatchNaive(caseSensitive, normalize, forward, true, text, pattern, withPos, slab)
}
func exactMatchNaive(caseSensitive bool, normalize bool, forward bool, boundaryCheck bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
if len(pattern) == 0 { if len(pattern) == 0 {
return Result{0, 0, 0}, nil return Result{0, 0, 0}, nil
} }
@@ -832,10 +840,22 @@ func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *uti
} }
pidx_ := indexAt(pidx, lenPattern, forward) pidx_ := indexAt(pidx, lenPattern, forward)
pchar := pattern[pidx_] pchar := pattern[pidx_]
if pchar == char { ok := pchar == char
if ok {
if pidx_ == 0 { if pidx_ == 0 {
bonus = bonusAt(text, index_) bonus = bonusAt(text, index_)
} }
if boundaryCheck {
ok = bonus >= bonusBoundary
if ok && pidx_ == 0 {
ok = index_ == 0 || charClassOf(text.Get(index_-1)) <= charDelimiter
}
if ok && pidx_ == len(pattern)-1 {
ok = index_ == lenRunes-1 || charClassOf(text.Get(index_+1)) <= charDelimiter
}
}
}
if ok {
pidx++ pidx++
if pidx == lenPattern { if pidx == lenPattern {
if bonus > bestBonus { if bonus > bestBonus {
@@ -861,7 +881,23 @@ func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *uti
sidx = lenRunes - (bestPos + 1) sidx = lenRunes - (bestPos + 1)
eidx = lenRunes - (bestPos - lenPattern + 1) eidx = lenRunes - (bestPos - lenPattern + 1)
} }
score, _ := calculateScore(caseSensitive, normalize, text, pattern, sidx, eidx, false) var score int
if boundaryCheck {
// Underscore boundaries should be ranked lower than the other types of boundaries
score = int(bonus)
deduct := int(bonus-bonusBoundary) + 1
if sidx > 0 && text.Get(sidx-1) == '_' {
score -= deduct + 1
deduct = 1
}
if eidx < lenRunes && text.Get(eidx) == '_' {
score -= deduct
}
// Add base score so that this can compete with other match types e.g. 'foo' | bar
score += scoreMatch*lenPattern + int(bonusBoundaryWhite)*(lenPattern+1)
} else {
score, _ = calculateScore(caseSensitive, normalize, text, pattern, sidx, eidx, false)
}
return Result{sidx, eidx, score}, nil return Result{sidx, eidx, score}, nil
} }
return Result{-1, -1, 0}, nil return Result{-1, -1, 0}, nil

View File

@@ -1,6 +1,7 @@
package fzf package fzf
import ( import (
"fmt"
"strconv" "strconv"
"strings" "strings"
"unicode/utf8" "unicode/utf8"
@@ -13,22 +14,28 @@ type ansiOffset struct {
color ansiState color ansiState
} }
type url struct {
uri string
params string
}
type ansiState struct { type ansiState struct {
fg tui.Color fg tui.Color
bg tui.Color bg tui.Color
attr tui.Attr attr tui.Attr
lbg tui.Color lbg tui.Color
url *url
} }
func (s *ansiState) colored() bool { func (s *ansiState) colored() bool {
return s.fg != -1 || s.bg != -1 || s.attr > 0 || s.lbg >= 0 return s.fg != -1 || s.bg != -1 || s.attr > 0 || s.lbg >= 0 || s.url != nil
} }
func (s *ansiState) equals(t *ansiState) bool { func (s *ansiState) equals(t *ansiState) bool {
if t == nil { if t == nil {
return !s.colored() return !s.colored()
} }
return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr && s.lbg == t.lbg return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr && s.lbg == t.lbg && s.url == t.url
} }
func (s *ansiState) ToString() string { func (s *ansiState) ToString() string {
@@ -60,7 +67,11 @@ func (s *ansiState) ToString() string {
} }
ret += toAnsiString(s.fg, 30) + toAnsiString(s.bg, 40) ret += toAnsiString(s.fg, 30) + toAnsiString(s.bg, 40)
return "\x1b[" + strings.TrimSuffix(ret, ";") + "m" ret = "\x1b[" + strings.TrimSuffix(ret, ";") + "m"
if s.url != nil {
ret = fmt.Sprintf("\x1b]8;%s;%s\x1b\\%s\x1b]8;;\x1b", s.url.params, s.url.uri, ret)
}
return ret
} }
func toAnsiString(color tui.Color, offset int) string { func toAnsiString(color tui.Color, offset int) string {
@@ -98,10 +109,19 @@ func matchOperatingSystemCommand(s string) int {
if s[i] == '\x07' { if s[i] == '\x07' {
return i + 1 return i + 1
} }
// `\x1b]8;PARAMS;URI\x1b\\TITLE\x1b]8;;\x1b`
// ------
if s[i] == '\x1b' && i < len(s)-1 && s[i+1] == '\\' { if s[i] == '\x1b' && i < len(s)-1 && s[i+1] == '\\' {
return i + 2 return i + 2
} }
} }
// `\x1b]8;PARAMS;URI\x1b\\TITLE\x1b]8;;\x1b`
// ------------
if i < len(s) && s[:i+1] == "\x1b]8;;\x1b" {
return i + 1
}
return -1 return -1
} }
@@ -328,13 +348,21 @@ func parseAnsiCode(s string, delimiter byte) (int, byte, string) {
func interpretCode(ansiCode string, prevState *ansiState) ansiState { func interpretCode(ansiCode string, prevState *ansiState) ansiState {
var state ansiState var state ansiState
if prevState == nil { if prevState == nil {
state = ansiState{-1, -1, 0, -1} state = ansiState{-1, -1, 0, -1, nil}
} else { } else {
state = ansiState{prevState.fg, prevState.bg, prevState.attr, prevState.lbg} state = ansiState{prevState.fg, prevState.bg, prevState.attr, prevState.lbg, prevState.url}
} }
if ansiCode[0] != '\x1b' || ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' { if ansiCode[0] != '\x1b' || ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' {
if prevState != nil && strings.HasSuffix(ansiCode, "0K") { if prevState != nil && strings.HasSuffix(ansiCode, "0K") {
state.lbg = prevState.bg state.lbg = prevState.bg
} else if ansiCode == "\x1b]8;;\x1b\\" { // End of a hyperlink
state.url = nil
} else if strings.HasPrefix(ansiCode, "\x1b]8;") && strings.HasSuffix(ansiCode, "\x1b\\") {
if paramsEnd := strings.IndexRune(ansiCode[4:], ';'); paramsEnd >= 0 {
params := ansiCode[4 : 4+paramsEnd]
uri := ansiCode[5+paramsEnd : len(ansiCode)-2]
state.url = &url{uri: uri, params: params}
}
} }
return state return state
} }

View File

@@ -22,6 +22,14 @@ func (cc *ChunkCache) Clear() {
cc.mutex.Unlock() cc.mutex.Unlock()
} }
func (cc *ChunkCache) retire(chunk ...*Chunk) {
cc.mutex.Lock()
for _, c := range chunk {
delete(cc.cache, c)
}
cc.mutex.Unlock()
}
// Add adds the list to the cache // Add adds the list to the cache
func (cc *ChunkCache) Add(chunk *Chunk, key string, list []Result) { func (cc *ChunkCache) Add(chunk *Chunk, key string, list []Result) {
if len(key) == 0 || !chunk.IsFull() || len(list) > queryCacheMax { if len(key) == 0 || !chunk.IsFull() || len(list) > queryCacheMax {

View File

@@ -16,14 +16,16 @@ type ChunkList struct {
chunks []*Chunk chunks []*Chunk
mutex sync.Mutex mutex sync.Mutex
trans ItemBuilder trans ItemBuilder
cache *ChunkCache
} }
// NewChunkList returns a new ChunkList // NewChunkList returns a new ChunkList
func NewChunkList(trans ItemBuilder) *ChunkList { func NewChunkList(cache *ChunkCache, trans ItemBuilder) *ChunkList {
return &ChunkList{ return &ChunkList{
chunks: []*Chunk{}, chunks: []*Chunk{},
mutex: sync.Mutex{}, mutex: sync.Mutex{},
trans: trans} trans: trans,
cache: cache}
} }
func (c *Chunk) push(trans ItemBuilder, data []byte) bool { func (c *Chunk) push(trans ItemBuilder, data []byte) bool {
@@ -92,7 +94,9 @@ func (cl *ChunkList) Snapshot(tail int) ([]*Chunk, int, bool) {
// Copy the chunks to keep // Copy the chunks to keep
ret := make([]*Chunk, numChunks) ret := make([]*Chunk, numChunks)
copy(ret, cl.chunks[len(cl.chunks)-numChunks:]) minIndex := len(cl.chunks) - numChunks
cl.cache.retire(cl.chunks[:minIndex]...)
copy(ret, cl.chunks[minIndex:])
for left, i := tail, len(ret)-1; i >= 0; i-- { for left, i := tail, len(ret)-1; i >= 0; i-- {
chunk := ret[i] chunk := ret[i]
@@ -104,6 +108,7 @@ func (cl *ChunkList) Snapshot(tail int) ([]*Chunk, int, bool) {
newChunk.items[i] = chunk.items[oldCount-left+i] newChunk.items[i] = chunk.items[oldCount-left+i]
} }
ret[i] = &newChunk ret[i] = &newChunk
cl.cache.retire(chunk)
break break
} }
left -= chunk.count left -= chunk.count

View File

@@ -11,7 +11,7 @@ func TestChunkList(t *testing.T) {
// FIXME global // FIXME global
sortCriteria = []criterion{byScore, byLength} sortCriteria = []criterion{byScore, byLength}
cl := NewChunkList(func(item *Item, s []byte) bool { cl := NewChunkList(NewChunkCache(), func(item *Item, s []byte) bool {
item.text = util.ToChars(s) item.text = util.ToChars(s)
return true return true
}) })
@@ -80,7 +80,7 @@ func TestChunkList(t *testing.T) {
} }
func TestChunkListTail(t *testing.T) { func TestChunkListTail(t *testing.T) {
cl := NewChunkList(func(item *Item, s []byte) bool { cl := NewChunkList(NewChunkCache(), func(item *Item, s []byte) bool {
item.text = util.ToChars(s) item.text = util.ToChars(s)
return true return true
}) })

View File

@@ -32,22 +32,20 @@ func (r *revision) bumpMinor() {
r.minor++ r.minor++
} }
func (r revision) equals(other revision) bool {
return r.major == other.major && r.minor == other.minor
}
func (r revision) compatible(other revision) bool { func (r revision) compatible(other revision) bool {
return r.major == other.major return r.major == other.major
} }
// Run starts fzf // Run starts fzf
func Run(opts *Options) (int, error) { func Run(opts *Options) (int, error) {
if opts.Tmux != nil && len(os.Getenv("TMUX")) > 0 && opts.Tmux.index >= opts.Height.index { if opts.Filter == nil {
return runTmux(os.Args, opts) if opts.Tmux != nil && len(os.Getenv("TMUX")) > 0 && opts.Tmux.index >= opts.Height.index {
} return runTmux(os.Args, opts)
}
if needWinpty(opts) { if needWinpty(opts) {
return runWinpty(os.Args, opts) return runWinpty(os.Args, opts)
}
} }
if err := postProcessOptions(opts); err != nil { if err := postProcessOptions(opts); err != nil {
@@ -94,11 +92,12 @@ func Run(opts *Options) (int, error) {
} }
// Chunk list // Chunk list
cache := NewChunkCache()
var chunkList *ChunkList var chunkList *ChunkList
var itemIndex int32 var itemIndex int32
header := make([]string, 0, opts.HeaderLines) header := make([]string, 0, opts.HeaderLines)
if len(opts.WithNth) == 0 { if len(opts.WithNth) == 0 {
chunkList = NewChunkList(func(item *Item, data []byte) bool { chunkList = NewChunkList(cache, func(item *Item, data []byte) bool {
if len(header) < opts.HeaderLines { if len(header) < opts.HeaderLines {
header = append(header, byteString(data)) header = append(header, byteString(data))
eventBox.Set(EvtHeader, header) eventBox.Set(EvtHeader, header)
@@ -110,7 +109,7 @@ func Run(opts *Options) (int, error) {
return true return true
}) })
} else { } else {
chunkList = NewChunkList(func(item *Item, data []byte) bool { chunkList = NewChunkList(cache, func(item *Item, data []byte) bool {
tokens := Tokenize(byteString(data), opts.Delimiter) tokens := Tokenize(byteString(data), opts.Delimiter)
if opts.Ansi && opts.Theme.Colored && len(tokens) > 1 { if opts.Ansi && opts.Theme.Colored && len(tokens) > 1 {
var ansiState *ansiState var ansiState *ansiState
@@ -147,6 +146,24 @@ func Run(opts *Options) (int, error) {
// Process executor // Process executor
executor := util.NewExecutor(opts.WithShell) executor := util.NewExecutor(opts.WithShell)
// Terminal I/O
var terminal *Terminal
var err error
var initialEnv []string
initialReload := opts.extractReloadOnStart()
if opts.Filter == nil {
terminal, err = NewTerminal(opts, eventBox, executor)
if err != nil {
return ExitError, err
}
if len(initialReload) > 0 {
var temps []string
initialReload, temps = terminal.replacePlaceholderInInitialCommand(initialReload)
initialEnv = terminal.environ()
defer removeFiles(temps)
}
}
// Reader // Reader
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
var reader *Reader var reader *Reader
@@ -154,7 +171,8 @@ func Run(opts *Options) (int, error) {
reader = NewReader(func(data []byte) bool { reader = NewReader(func(data []byte) bool {
return chunkList.Push(data) return chunkList.Push(data)
}, eventBox, executor, opts.ReadZero, opts.Filter == nil) }, eventBox, executor, opts.ReadZero, opts.Filter == nil)
go reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
go reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv)
} }
// Matcher // Matcher
@@ -170,7 +188,6 @@ func Run(opts *Options) (int, error) {
forward = true forward = true
} }
} }
cache := NewChunkCache()
patternCache := make(map[string]*Pattern) patternCache := make(map[string]*Pattern)
patternBuilder := func(runes []rune) *Pattern { patternBuilder := func(runes []rune) *Pattern {
return BuildPattern(cache, patternCache, return BuildPattern(cache, patternCache,
@@ -207,7 +224,7 @@ func Run(opts *Options) (int, error) {
} }
return false return false
}, eventBox, executor, opts.ReadZero, false) }, eventBox, executor, opts.ReadZero, false)
reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip) reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv)
} else { } else {
eventBox.Unwatch(EvtReadNew) eventBox.Unwatch(EvtReadNew)
eventBox.WaitFor(EvtReadFin) eventBox.WaitFor(EvtReadFin)
@@ -238,18 +255,14 @@ func Run(opts *Options) (int, error) {
go matcher.Loop() go matcher.Loop()
defer matcher.Stop() defer matcher.Stop()
// Terminal I/O // Handling adaptive height
terminal, err := NewTerminal(opts, eventBox, executor)
if err != nil {
return ExitError, err
}
maxFit := 0 // Maximum number of items that can fit on screen maxFit := 0 // Maximum number of items that can fit on screen
padHeight := 0 padHeight := 0
heightUnknown := opts.Height.auto heightUnknown := opts.Height.auto
if heightUnknown { if heightUnknown {
maxFit, padHeight = terminal.MaxFitAndPad() maxFit, padHeight = terminal.MaxFitAndPad()
} }
deferred := opts.Select1 || opts.Exit0 deferred := opts.Select1 || opts.Exit0 || opts.Sync
go terminal.Loop() go terminal.Loop()
if !deferred && !heightUnknown { if !deferred && !heightUnknown {
// Start right away // Start right away
@@ -341,9 +354,6 @@ func Run(opts *Options) (int, error) {
} }
total = count total = count
terminal.UpdateCount(total, !reading, value.(*string)) terminal.UpdateCount(total, !reading, value.(*string))
if opts.Sync {
terminal.UpdateList(PassMerger(&snapshot, opts.Tac, snapshotRevision), false)
}
if heightUnknown && !deferred { if heightUnknown && !deferred {
determine(!reading) determine(!reading)
} }
@@ -431,7 +441,7 @@ func Run(opts *Options) (int, error) {
determine(val.final) determine(val.final)
} }
} }
terminal.UpdateList(val, true) terminal.UpdateList(val)
} }
} }
} }

View File

@@ -87,7 +87,9 @@ func (m *Matcher) Loop() {
m.sort = request.sort m.sort = request.sort
m.revision = request.revision m.revision = request.revision
m.mergerCache = make(map[string]*Merger) m.mergerCache = make(map[string]*Merger)
m.cache.Clear() if !request.revision.compatible(m.revision) {
m.cache.Clear()
}
cacheCleared = true cacheCleared = true
} }
@@ -100,7 +102,7 @@ func (m *Matcher) Loop() {
if !cacheCleared { if !cacheCleared {
if count == prevCount { if count == prevCount {
// Look up mergerCache // Look up mergerCache
if cached, found := m.mergerCache[patternString]; found { if cached, found := m.mergerCache[patternString]; found && cached.final == request.final {
merger = cached merger = cached
} }
} else { } else {
@@ -193,7 +195,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
for _, matches := range allMatches { for _, matches := range allMatches {
sliceMatches = append(sliceMatches, matches...) sliceMatches = append(sliceMatches, matches...)
} }
if m.sort { if m.sort && request.pattern.sortable {
if m.tac { if m.tac {
sort.Sort(ByRelevanceTac(sliceMatches)) sort.Sort(ByRelevanceTac(sliceMatches))
} else { } else {
@@ -234,7 +236,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
partialResult := <-resultChan partialResult := <-resultChan
partialResults[partialResult.index] = partialResult.matches partialResults[partialResult.index] = partialResult.matches
} }
return NewMerger(pattern, partialResults, m.sort, m.tac, request.revision, minIndex), false return NewMerger(pattern, partialResults, m.sort && request.pattern.sortable, m.tac, request.revision, minIndex), false
} }
// Reset is called to interrupt/signal the ongoing search // Reset is called to interrupt/signal the ongoing search
@@ -247,7 +249,7 @@ func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final
} else { } else {
event = reqRetry event = reqRetry
} }
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable, revision}) m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort, revision})
} }
func (m *Matcher) Stop() { func (m *Matcher) Stop() {

View File

@@ -12,7 +12,7 @@ import (
"github.com/junegunn/fzf/src/algo" "github.com/junegunn/fzf/src/algo"
"github.com/junegunn/fzf/src/tui" "github.com/junegunn/fzf/src/tui"
"github.com/mattn/go-shellwords" "github.com/junegunn/go-shellwords"
"github.com/rivo/uniseg" "github.com/rivo/uniseg"
) )
@@ -53,7 +53,10 @@ Usage: fzf [options]
--no-mouse Disable mouse --no-mouse Disable mouse
--bind=KEYBINDS Custom key bindings. Refer to the man page. --bind=KEYBINDS Custom key bindings. Refer to the man page.
--cycle Enable cyclic scroll --cycle Enable cyclic scroll
--wrap Enable line wrap
--wrap-sign=STR Indicator for wrapped lines
--no-multi-line Disable multi-line display of items when using --read0 --no-multi-line Disable multi-line display of items when using --read0
--gap[=N] Render empty lines between each item
--keep-right Keep the right end of the line visible on overflow --keep-right Keep the right end of the line visible on overflow
--scroll-off=LINES Number of screen lines to keep above or below when --scroll-off=LINES Number of screen lines to keep above or below when
scrolling to the top or to the bottom (default: 0) scrolling to the top or to the bottom (default: 0)
@@ -88,6 +91,7 @@ Usage: fzf [options]
--padding=PADDING Padding inside border (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 --info=STYLE Finder info style
[default|right|hidden|inline[-right][:PREFIX]] [default|right|hidden|inline[-right][:PREFIX]]
--info-command=COMMAND Command to generate info line
--separator=STR String to form horizontal separator on info line --separator=STR String to form horizontal separator on info line
--no-separator Hide info line separator --no-separator Hide info line separator
--scrollbar[=C1[C2]] Scrollbar character(s) (each for main and preview window) --scrollbar[=C1[C2]] Scrollbar character(s) (each for main and preview window)
@@ -100,7 +104,7 @@ Usage: fzf [options]
--header=STR String to print as header --header=STR String to print as header
--header-lines=N The first N lines of the input are treated as header --header-lines=N The first N lines of the input are treated as header
--header-first Print header before the prompt line --header-first Print header before the prompt line
--ellipsis=STR Ellipsis to show when line is truncated (default: '..') --ellipsis=STR Ellipsis to show when line is truncated (default: '··')
Display Display
--ansi Enable processing of ANSI color codes --ansi Enable processing of ANSI color codes
@@ -117,8 +121,8 @@ Usage: fzf [options]
--preview=COMMAND Command to preview highlighted line ({}) --preview=COMMAND Command to preview highlighted line ({})
--preview-window=OPT Preview window layout (default: right:50%) --preview-window=OPT Preview window layout (default: right:50%)
[up|down|left|right][,SIZE[%]] [up|down|left|right][,SIZE[%]]
[,[no]wrap][,[no]cycle][,[no]follow][,[no]hidden] [,[no]wrap][,[no]cycle][,[no]follow][,[no]info]
[,border-BORDER_OPT] [,[no]hidden][,border-BORDER_OPT]
[,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES] [,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
[,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)] [,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]
--preview-label=LABEL --preview-label=LABEL
@@ -268,6 +272,7 @@ type previewOpts struct {
wrap bool wrap bool
cycle bool cycle bool
follow bool follow bool
info bool
border tui.BorderShape border tui.BorderShape
headerLines int headerLines int
threshold int threshold int
@@ -383,7 +388,7 @@ func (a previewOpts) sameLayout(b previewOpts) bool {
} }
func (a previewOpts) sameContentLayout(b previewOpts) bool { func (a previewOpts) sameContentLayout(b previewOpts) bool {
return a.wrap == b.wrap && a.headerLines == b.headerLines return a.wrap == b.wrap && a.headerLines == b.headerLines && a.info == b.info
} }
func firstLine(s string) string { func firstLine(s string) string {
@@ -434,6 +439,8 @@ type Options struct {
MinHeight int MinHeight int
Layout layoutType Layout layoutType
Cycle bool Cycle bool
Wrap bool
WrapSign *string
MultiLine bool MultiLine bool
CursorLine bool CursorLine bool
KeepRight bool KeepRight bool
@@ -443,12 +450,13 @@ type Options struct {
FileWord bool FileWord bool
InfoStyle infoStyle InfoStyle infoStyle
InfoPrefix string InfoPrefix string
InfoCommand string
Separator *string Separator *string
JumpLabels string JumpLabels string
Prompt string Prompt string
Pointer *string Pointer *string
Marker *string Marker *string
MarkerMulti [3]string MarkerMulti *[3]string
Query string Query string
Select1 bool Select1 bool
Exit0 bool Exit0 bool
@@ -466,7 +474,8 @@ type Options struct {
Header []string Header []string
HeaderLines int HeaderLines int
HeaderFirst bool HeaderFirst bool
Ellipsis string Gap int
Ellipsis *string
Scrollbar *string Scrollbar *string
Margin [4]sizeSpec Margin [4]sizeSpec
Padding [4]sizeSpec Padding [4]sizeSpec
@@ -502,7 +511,7 @@ func filterNonEmpty(input []string) []string {
} }
func defaultPreviewOpts(command string) previewOpts { func defaultPreviewOpts(command string) previewOpts {
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, tui.DefaultBorderShape, 0, 0, nil} return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, true, tui.DefaultBorderShape, 0, 0, nil}
} }
func defaultOptions() *Options { func defaultOptions() *Options {
@@ -541,6 +550,7 @@ func defaultOptions() *Options {
MinHeight: 10, MinHeight: 10,
Layout: layoutDefault, Layout: layoutDefault,
Cycle: false, Cycle: false,
Wrap: false,
MultiLine: true, MultiLine: true,
KeepRight: false, KeepRight: false,
Hscroll: true, Hscroll: true,
@@ -553,6 +563,7 @@ func defaultOptions() *Options {
Prompt: "> ", Prompt: "> ",
Pointer: nil, Pointer: nil,
Marker: nil, Marker: nil,
MarkerMulti: nil,
Query: "", Query: "",
Select1: false, Select1: false,
Exit0: false, Exit0: false,
@@ -570,7 +581,8 @@ func defaultOptions() *Options {
Header: make([]string, 0), Header: make([]string, 0),
HeaderLines: 0, HeaderLines: 0,
HeaderFirst: false, HeaderFirst: false,
Ellipsis: "..", Gap: 0,
Ellipsis: nil,
Scrollbar: nil, Scrollbar: nil,
Margin: defaultMargin(), Margin: defaultMargin(),
Padding: defaultMargin(), Padding: defaultMargin(),
@@ -1363,6 +1375,8 @@ func parseActionList(masked string, original string, prevActions []*action, putA
appendAction(actToggleTrackCurrent) appendAction(actToggleTrackCurrent)
case "toggle-header": case "toggle-header":
appendAction(actToggleHeader) appendAction(actToggleHeader)
case "toggle-wrap":
appendAction(actToggleWrap)
case "show-header": case "show-header":
appendAction(actShowHeader) appendAction(actShowHeader)
case "hide-header": case "hide-header":
@@ -1419,6 +1433,8 @@ func parseActionList(masked string, original string, prevActions []*action, putA
appendAction(actOffsetUp) appendAction(actOffsetUp)
case "offset-down": case "offset-down":
appendAction(actOffsetDown) appendAction(actOffsetDown)
case "offset-middle":
appendAction(actOffsetMiddle)
case "preview-top": case "preview-top":
appendAction(actPreviewTop) appendAction(actPreviewTop)
case "preview-bottom": case "preview-bottom":
@@ -1777,6 +1793,10 @@ func parsePreviewWindowImpl(opts *previewOpts, input string) error {
opts.follow = true opts.follow = true
case "nofollow": case "nofollow":
opts.follow = false opts.follow = false
case "info":
opts.info = true
case "noinfo":
opts.info = false
default: default:
if headerRegex.MatchString(token) { if headerRegex.MatchString(token) {
if opts.headerLines, err = atoi(token[1:]); err != nil { if opts.headerLines, err = atoi(token[1:]); err != nil {
@@ -1858,7 +1878,10 @@ func parseMargin(opt string, margin string) ([4]sizeSpec, error) {
return [4]sizeSpec{}, errors.New("invalid " + opt + ": " + margin) return [4]sizeSpec{}, errors.New("invalid " + opt + ": " + margin)
} }
func parseMarkerMultiLine(str string) ([3]string, error) { func parseMarkerMultiLine(str string) (*[3]string, error) {
if str == "" {
return &[3]string{}, nil
}
gr := uniseg.NewGraphemes(str) gr := uniseg.NewGraphemes(str)
parts := []string{} parts := []string{}
totalWidth := 0 totalWidth := 0
@@ -1870,7 +1893,7 @@ func parseMarkerMultiLine(str string) ([3]string, error) {
result := [3]string{} result := [3]string{}
if totalWidth != 3 && totalWidth != 6 { if totalWidth != 3 && totalWidth != 6 {
return result, fmt.Errorf("invalid total marker width: %d (expected: 3 or 6)", totalWidth) return &result, fmt.Errorf("invalid total marker width: %d (expected: 0, 3 or 6)", totalWidth)
} }
expected := totalWidth / 3 expected := totalWidth / 3
@@ -1886,7 +1909,7 @@ func parseMarkerMultiLine(str string) ([3]string, error) {
break break
} }
} }
return result, nil return &result, nil
} }
func parseOptions(index *int, opts *Options, allArgs []string) error { func parseOptions(index *int, opts *Options, allArgs []string) error {
@@ -2155,6 +2178,16 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
opts.CursorLine = false opts.CursorLine = false
case "--no-cycle": case "--no-cycle":
opts.Cycle = false opts.Cycle = false
case "--wrap":
opts.Wrap = true
case "--no-wrap":
opts.Wrap = false
case "--wrap-sign":
str, err := nextString(allArgs, &i, "wrap sign required")
if err != nil {
return err
}
opts.WrapSign = &str
case "--multi-line": case "--multi-line":
opts.MultiLine = true opts.MultiLine = true
case "--no-multi-line": case "--no-multi-line":
@@ -2187,6 +2220,12 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if opts.InfoStyle, opts.InfoPrefix, err = parseInfoStyle(str); err != nil { if opts.InfoStyle, opts.InfoPrefix, err = parseInfoStyle(str); err != nil {
return err return err
} }
case "--info-command":
if opts.InfoCommand, err = nextString(allArgs, &i, "info command required"); err != nil {
return err
}
case "--no-info-command":
opts.InfoCommand = ""
case "--no-info": case "--no-info":
opts.InfoStyle = infoHidden opts.InfoStyle = infoHidden
case "--inline-info": case "--inline-info":
@@ -2307,10 +2346,19 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
opts.HeaderFirst = true opts.HeaderFirst = true
case "--no-header-first": case "--no-header-first":
opts.HeaderFirst = false opts.HeaderFirst = false
case "--ellipsis": case "--gap":
if opts.Ellipsis, err = nextString(allArgs, &i, "ellipsis string required"); err != nil { if opts.Gap, err = optionalNumeric(allArgs, &i, 1); err != nil {
return err return err
} }
case "--no-gap":
opts.Gap = 0
case "--ellipsis":
str, err := nextString(allArgs, &i, "ellipsis string required")
if err != nil {
return err
}
str = firstLine(str)
opts.Ellipsis = &str
case "--preview": case "--preview":
if opts.Preview.command, err = nextString(allArgs, &i, "preview command required"); err != nil { if opts.Preview.command, err = nextString(allArgs, &i, "preview command required"); err != nil {
return err return err
@@ -2499,6 +2547,8 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if err := parseLabelPosition(&opts.PreviewLabel, value); err != nil { if err := parseLabelPosition(&opts.PreviewLabel, value); err != nil {
return err return err
} }
} else if match, value := optString(arg, "--wrap-sign="); match {
opts.WrapSign = &value
} else if match, value := optString(arg, "--prompt="); match { } else if match, value := optString(arg, "--prompt="); match {
opts.Prompt = value opts.Prompt = value
} else if match, value := optString(arg, "--pointer="); match { } else if match, value := optString(arg, "--pointer="); match {
@@ -2541,6 +2591,8 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if opts.InfoStyle, opts.InfoPrefix, err = parseInfoStyle(value); err != nil { if opts.InfoStyle, opts.InfoPrefix, err = parseInfoStyle(value); err != nil {
return err return err
} }
} else if match, value := optString(arg, "--info-command="); match {
opts.InfoCommand = value
} else if match, value := optString(arg, "--separator="); match { } else if match, value := optString(arg, "--separator="); match {
opts.Separator = &value opts.Separator = &value
} else if match, value := optString(arg, "--scrollbar="); match { } else if match, value := optString(arg, "--scrollbar="); match {
@@ -2587,8 +2639,13 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if opts.HeaderLines, err = atoi(value); err != nil { if opts.HeaderLines, err = atoi(value); err != nil {
return err return err
} }
} else if match, value := optString(arg, "--gap="); match {
if opts.Gap, err = atoi(value); err != nil {
return err
}
} else if match, value := optString(arg, "--ellipsis="); match { } else if match, value := optString(arg, "--ellipsis="); match {
opts.Ellipsis = value str := firstLine(value)
opts.Ellipsis = &str
} else if match, value := optString(arg, "--preview="); match { } else if match, value := optString(arg, "--preview="); match {
opts.Preview.command = value opts.Preview.command = value
} else if match, value := optString(arg, "--preview-window="); match { } else if match, value := optString(arg, "--preview-window="); match {
@@ -2687,9 +2744,6 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
} }
func validateSign(sign string, signOptName string) error { func validateSign(sign string, signOptName string) error {
if sign == "" {
return fmt.Errorf("%v cannot be empty", signOptName)
}
if uniseg.StringWidth(sign) > 2 { if uniseg.StringWidth(sign) > 2 {
return fmt.Errorf("%v display width should be up to 2", signOptName) return fmt.Errorf("%v display width should be up to 2", signOptName)
} }
@@ -2758,32 +2812,42 @@ func postProcessOptions(opts *Options) error {
markerLen := 1 markerLen := 1
if opts.Marker == nil { if opts.Marker == nil {
// "▎" looks better, but not all terminals render it correctly if opts.MarkerMulti != nil && opts.MarkerMulti[0] == "" {
defaultMarker := "" empty := ""
if !opts.Unicode { opts.Marker = &empty
defaultMarker = ">" markerLen = 0
} else {
// "▎" looks better, but not all terminals render it correctly
defaultMarker := "┃"
if !opts.Unicode {
defaultMarker = ">"
}
opts.Marker = &defaultMarker
} }
opts.Marker = &defaultMarker
} else { } else {
markerLen = uniseg.StringWidth(*opts.Marker) markerLen = uniseg.StringWidth(*opts.Marker)
} }
markerMultiLen := 1 markerMultiLen := 1
if len(opts.MarkerMulti[0]) == 0 { if opts.MarkerMulti == nil {
if opts.Unicode { if *opts.Marker == "" {
opts.MarkerMulti = [3]string{"╻", "┃", "╹"} opts.MarkerMulti = &[3]string{}
markerMultiLen = 0
} else if opts.Unicode {
opts.MarkerMulti = &[3]string{"╻", "┃", "╹"}
} else { } else {
opts.MarkerMulti = [3]string{".", "|", "'"} opts.MarkerMulti = &[3]string{".", "|", "'"}
} }
} else { } else {
markerMultiLen = uniseg.StringWidth(opts.MarkerMulti[0]) markerMultiLen = uniseg.StringWidth(opts.MarkerMulti[0])
} }
if markerMultiLen > markerLen { diff := markerMultiLen - markerLen
padded := *opts.Marker + " " if diff > 0 {
padded := *opts.Marker + strings.Repeat(" ", diff)
opts.Marker = &padded opts.Marker = &padded
} else if markerMultiLen < markerLen { } else if diff < 0 {
for idx := range opts.MarkerMulti { for idx := range opts.MarkerMulti {
opts.MarkerMulti[idx] += " " opts.MarkerMulti[idx] += strings.Repeat(" ", -diff)
} }
} }
@@ -2873,6 +2937,12 @@ func postProcessOptions(opts *Options) error {
return processScheme(opts) return processScheme(opts)
} }
func parseShellWords(str string) ([]string, error) {
parser := shellwords.NewParser()
parser.ParseComment = true
return parser.Parse(str)
}
// ParseOptions parses command-line options // ParseOptions parses command-line options
func ParseOptions(useDefaults bool, args []string) (*Options, error) { func ParseOptions(useDefaults bool, args []string) (*Options, error) {
opts := defaultOptions() opts := defaultOptions()
@@ -2886,7 +2956,7 @@ func ParseOptions(useDefaults bool, args []string) (*Options, error) {
return nil, errors.New("$FZF_DEFAULT_OPTS_FILE: " + err.Error()) return nil, errors.New("$FZF_DEFAULT_OPTS_FILE: " + err.Error())
} }
words, parseErr := shellwords.Parse(string(bytes)) words, parseErr := parseShellWords(string(bytes))
if parseErr != nil { if parseErr != nil {
return nil, errors.New(path + ": " + parseErr.Error()) return nil, errors.New(path + ": " + parseErr.Error())
} }
@@ -2898,7 +2968,7 @@ func ParseOptions(useDefaults bool, args []string) (*Options, error) {
} }
// 2. Options from $FZF_DEFAULT_OPTS string // 2. Options from $FZF_DEFAULT_OPTS string
words, parseErr := shellwords.Parse(os.Getenv("FZF_DEFAULT_OPTS")) words, parseErr := parseShellWords(os.Getenv("FZF_DEFAULT_OPTS"))
if parseErr != nil { if parseErr != nil {
return nil, errors.New("$FZF_DEFAULT_OPTS: " + parseErr.Error()) return nil, errors.New("$FZF_DEFAULT_OPTS: " + parseErr.Error())
} }
@@ -2921,3 +2991,19 @@ func ParseOptions(useDefaults bool, args []string) (*Options, error) {
return opts, nil return opts, nil
} }
func (opts *Options) extractReloadOnStart() string {
cmd := ""
if actions, prs := opts.Keymap[tui.Start.AsEvent()]; prs {
filtered := []*action{}
for _, action := range actions {
if action.t == actReload || action.t == actReloadSync {
cmd = action.a
} else {
filtered = append(filtered, action)
}
}
opts.Keymap[tui.Start.AsEvent()] = filtered
}
return cmd
}

View File

@@ -454,7 +454,6 @@ func TestValidateSign(t *testing.T) {
{"> ", true}, {"> ", true},
{"아", true}, {"아", true},
{"😀", true}, {"😀", true},
{"", false},
{">>>", false}, {">>>", false},
} }

View File

@@ -23,6 +23,7 @@ type termType int
const ( const (
termFuzzy termType = iota termFuzzy termType = iota
termExact termExact
termExactBoundary
termPrefix termPrefix
termSuffix termSuffix
termEqual termEqual
@@ -147,6 +148,7 @@ func BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy boo
ptr.procFun[termFuzzy] = fuzzyAlgo ptr.procFun[termFuzzy] = fuzzyAlgo
ptr.procFun[termEqual] = algo.EqualMatch ptr.procFun[termEqual] = algo.EqualMatch
ptr.procFun[termExact] = algo.ExactMatchNaive ptr.procFun[termExact] = algo.ExactMatchNaive
ptr.procFun[termExactBoundary] = algo.ExactMatchBoundary
ptr.procFun[termPrefix] = algo.PrefixMatch ptr.procFun[termPrefix] = algo.PrefixMatch
ptr.procFun[termSuffix] = algo.SuffixMatch ptr.procFun[termSuffix] = algo.SuffixMatch
@@ -193,7 +195,10 @@ func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet
text = text[:len(text)-1] text = text[:len(text)-1]
} }
if strings.HasPrefix(text, "'") { if len(text) > 2 && strings.HasPrefix(text, "'") && strings.HasSuffix(text, "'") {
typ = termExactBoundary
text = text[1 : len(text)-1]
} else if strings.HasPrefix(text, "'") {
// Flip exactness // Flip exactness
if fuzzy && !inv { if fuzzy && !inv {
typ = termExact typ = termExact

View File

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

View File

@@ -9,6 +9,7 @@ import (
"os/exec" "os/exec"
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"regexp"
"strings" "strings"
"time" "time"
@@ -32,7 +33,7 @@ func fifo(name string) (string, error) {
return output, nil return output, nil
} }
func runProxy(commandPrefix string, cmdBuilder func(temp string) *exec.Cmd, opts *Options, withExports bool) (int, error) { func runProxy(commandPrefix string, cmdBuilder func(temp string, needBash bool) (*exec.Cmd, error), opts *Options, withExports bool) (int, error) {
output, err := fifo("proxy-output") output, err := fifo("proxy-output")
if err != nil { if err != nil {
return ExitError, err return ExitError, err
@@ -92,17 +93,28 @@ func runProxy(commandPrefix string, cmdBuilder func(temp string) *exec.Cmd, opts
// To ensure that the options are processed by a POSIX-compliant shell, // To ensure that the options are processed by a POSIX-compliant shell,
// we need to write the command to a temporary file and execute it with sh. // we need to write the command to a temporary file and execute it with sh.
var exports []string var exports []string
needBash := false
if withExports { if withExports {
exports = os.Environ() validIdentifier := regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
for idx, pairStr := range exports { for _, pairStr := range os.Environ() {
pair := strings.SplitN(pairStr, "=", 2) pair := strings.SplitN(pairStr, "=", 2)
exports[idx] = fmt.Sprintf("export %s=%s", pair[0], escapeSingleQuote(pair[1])) if validIdentifier.MatchString(pair[0]) {
exports = append(exports, fmt.Sprintf("export %s=%s", pair[0], escapeSingleQuote(pair[1])))
} else if strings.HasPrefix(pair[0], "BASH_FUNC_") && strings.HasSuffix(pair[0], "%%") {
name := pair[0][10 : len(pair[0])-2]
exports = append(exports, name+pair[1])
exports = append(exports, "export -f "+name)
needBash = true
}
} }
} }
temp := WriteTemporaryFile(append(exports, command), "\n") temp := WriteTemporaryFile(append(exports, command), "\n")
defer os.Remove(temp) defer os.Remove(temp)
cmd := cmdBuilder(temp) cmd, err := cmdBuilder(temp, needBash)
if err != nil {
return ExitError, err
}
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
intChan := make(chan os.Signal, 1) intChan := make(chan os.Signal, 1)
defer close(intChan) defer close(intChan)

View File

@@ -9,7 +9,10 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
func sh() (string, error) { func sh(bash bool) (string, error) {
if bash {
return "bash", nil
}
return "sh", nil return "sh", nil
} }

View File

@@ -13,12 +13,16 @@ import (
var shPath atomic.Value var shPath atomic.Value
func sh() (string, error) { func sh(bash bool) (string, error) {
if cached := shPath.Load(); cached != nil { if cached := shPath.Load(); cached != nil {
return cached.(string), nil return cached.(string), nil
} }
cmd := exec.Command("cygpath", "-w", "/usr/bin/sh") name := "sh"
if bash {
name = "bash"
}
cmd := exec.Command("cygpath", "-w", "/usr/bin/"+name)
bytes, err := cmd.Output() bytes, err := cmd.Output()
if err != nil { if err != nil {
return "", err return "", err
@@ -31,7 +35,7 @@ func sh() (string, error) {
func mkfifo(path string, mode uint32) (string, error) { func mkfifo(path string, mode uint32) (string, error) {
m := strconv.FormatUint(uint64(mode), 8) m := strconv.FormatUint(uint64(mode), 8)
sh, err := sh() sh, err := sh(false)
if err != nil { if err != nil {
return path, err return path, err
} }
@@ -43,7 +47,7 @@ func mkfifo(path string, mode uint32) (string, error) {
} }
func withOutputPipe(output string, task func(io.ReadCloser)) error { func withOutputPipe(output string, task func(io.ReadCloser)) error {
sh, err := sh() sh, err := sh(false)
if err != nil { if err != nil {
return err return err
} }
@@ -62,7 +66,7 @@ func withOutputPipe(output string, task func(io.ReadCloser)) error {
} }
func withInputPipe(input string, task func(io.WriteCloser)) error { func withInputPipe(input string, task func(io.WriteCloser)) error {
sh, err := sh() sh, err := sh(false)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"context" "context"
"io" "io"
"io/fs"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@@ -110,18 +111,19 @@ func (r *Reader) readChannel(inputChan chan string) bool {
} }
// ReadSource reads data from the default command or from standard input // ReadSource reads data from the default command or from standard input
func (r *Reader) ReadSource(inputChan chan string, root string, opts walkerOpts, ignores []string) { func (r *Reader) ReadSource(inputChan chan string, root string, opts walkerOpts, ignores []string, initCmd string, initEnv []string) {
r.startEventPoller() r.startEventPoller()
var success bool var success bool
if inputChan != nil { if inputChan != nil {
success = r.readChannel(inputChan) success = r.readChannel(inputChan)
} else if len(initCmd) > 0 {
success = r.readFromCommand(initCmd, initEnv)
} else if util.IsTty(os.Stdin) { } else if util.IsTty(os.Stdin) {
cmd := os.Getenv("FZF_DEFAULT_COMMAND") cmd := os.Getenv("FZF_DEFAULT_COMMAND")
if len(cmd) == 0 { if len(cmd) == 0 {
success = r.readFiles(root, opts, ignores) success = r.readFiles(root, opts, ignores)
} else { } else {
// We can't export FZF_* environment variables to the default command success = r.readFromCommand(cmd, initEnv)
success = r.readFromCommand(cmd, nil)
} }
} else { } else {
success = r.readFromStdin() success = r.readFromStdin()
@@ -222,19 +224,48 @@ func (r *Reader) readFromStdin() bool {
return true return true
} }
func isSymlinkToDir(path string, de os.DirEntry) bool {
if de.Type()&fs.ModeSymlink == 0 {
return false
}
if s, err := os.Stat(path); err == nil {
return s.IsDir()
}
return false
}
func trimPath(path string) string {
bytes := stringBytes(path)
for len(bytes) > 1 && bytes[0] == '.' && (bytes[1] == '/' || bytes[1] == '\\') {
bytes = bytes[2:]
}
if len(bytes) == 0 {
return "."
}
return byteString(bytes)
}
func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool { func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool {
r.killed = false r.killed = false
conf := fastwalk.Config{Follow: opts.follow} conf := fastwalk.Config{
Follow: opts.follow,
// Use forward slashes when running a Windows binary under WSL or MSYS
ToSlash: fastwalk.DefaultToSlash(),
Sort: fastwalk.SortFilesFirst,
}
fn := func(path string, de os.DirEntry, err error) error { fn := func(path string, de os.DirEntry, err error) error {
if err != nil { if err != nil {
return nil return nil
} }
path = filepath.Clean(path) path = trimPath(path)
if path != "." { if path != "." {
isDir := de.IsDir() isDir := de.IsDir()
if isDir { if isDir || opts.follow && isSymlinkToDir(path, de) {
base := filepath.Base(path) base := filepath.Base(path)
if !opts.hidden && base[0] == '.' { if !opts.hidden && base[0] == '.' && base != ".." {
return filepath.SkipDir return filepath.SkipDir
} }
for _, ignore := range ignores { for _, ignore := range ignores {
@@ -243,7 +274,7 @@ func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool
} }
} }
} }
if ((opts.file && !isDir) || (opts.dir && isDir)) && r.pusher([]byte(path)) { if ((opts.file && !isDir) || (opts.dir && isDir)) && r.pusher(stringBytes(path)) {
atomic.StoreInt32(&r.event, int32(EvtReadNew)) atomic.StoreInt32(&r.event, int32(EvtReadNew))
} }
} }

View File

@@ -16,6 +16,7 @@ type colorOffset struct {
offset [2]int32 offset [2]int32
color tui.ColorPair color tui.ColorPair
match bool match bool
url *url
} }
type Result struct { type Result struct {
@@ -81,7 +82,7 @@ func buildResult(item *Item, offsets []Offset, score int) Result {
if criterion == byBegin { if criterion == byBegin {
val = util.AsUint16(minEnd - whitePrefixLen) val = util.AsUint16(minEnd - whitePrefixLen)
} else { } else {
val = util.AsUint16(math.MaxUint16 - math.MaxUint16*(maxEnd-whitePrefixLen)/int(item.TrimLength()+1)) val = util.AsUint16(math.MaxUint16 - math.MaxUint16*(maxEnd-whitePrefixLen)/(int(item.TrimLength())+1))
} }
} }
} }
@@ -177,8 +178,11 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
if curr != 0 && idx > start { if curr != 0 && idx > start {
if curr < 0 { if curr < 0 {
color := colMatch color := colMatch
var url *url
if curr < -1 && theme.Colored { if curr < -1 && theme.Colored {
origColor := ansiToColorPair(itemColors[-curr-2], colMatch) ansi := itemColors[-curr-2]
url = ansi.color.url
origColor := ansiToColorPair(ansi, colMatch)
// hl or hl+ only sets the foreground color, so colMatch is the // hl or hl+ only sets the foreground color, so colMatch is the
// combination of either [hl and bg] or [hl+ and bg+]. // combination of either [hl and bg] or [hl+ and bg+].
// //
@@ -194,13 +198,14 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
} }
} }
colors = append(colors, colorOffset{ colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)}, color: color, match: true}) offset: [2]int32{int32(start), int32(idx)}, color: color, match: true, url: url})
} else { } else {
ansi := itemColors[curr-1] ansi := itemColors[curr-1]
colors = append(colors, colorOffset{ colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)}, offset: [2]int32{int32(start), int32(idx)},
color: ansiToColorPair(ansi, colBase), color: ansiToColorPair(ansi, colBase),
match: false}) match: false,
url: ansi.color.url})
} }
} }
} }

View File

@@ -124,10 +124,10 @@ func TestColorOffset(t *testing.T) {
item := Result{ item := Result{
item: &Item{ item: &Item{
colors: &[]ansiOffset{ colors: &[]ansiOffset{
{[2]int32{0, 20}, ansiState{1, 5, 0, -1}}, {[2]int32{0, 20}, ansiState{1, 5, 0, -1, nil}},
{[2]int32{22, 27}, ansiState{2, 6, tui.Bold, -1}}, {[2]int32{22, 27}, ansiState{2, 6, tui.Bold, -1, nil}},
{[2]int32{30, 32}, ansiState{3, 7, 0, -1}}, {[2]int32{30, 32}, ansiState{3, 7, 0, -1, nil}},
{[2]int32{33, 40}, ansiState{4, 8, tui.Bold, -1}}}}} {[2]int32{33, 40}, ansiState{4, 8, tui.Bold, -1, nil}}}}}
colBase := tui.NewColorPair(89, 189, tui.AttrUndefined) colBase := tui.NewColorPair(89, 189, tui.AttrUndefined)
colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined) colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined)

View File

@@ -38,9 +38,9 @@ const (
) )
type httpServer struct { type httpServer struct {
apiKey []byte apiKey []byte
actionChannel chan []*action actionChannel chan []*action
responseChannel chan string getHandler func(getParams) string
} }
type listenAddress struct { type listenAddress struct {
@@ -73,7 +73,7 @@ func parseListenAddress(address string) (listenAddress, error) {
return listenAddress{parts[0], port}, nil return listenAddress{parts[0], port}, nil
} }
func startHttpServer(address listenAddress, actionChannel chan []*action, responseChannel chan string) (net.Listener, int, error) { func startHttpServer(address listenAddress, actionChannel chan []*action, getHandler func(getParams) string) (net.Listener, int, error) {
host := address.host host := address.host
port := address.port port := address.port
apiKey := os.Getenv("FZF_API_KEY") apiKey := os.Getenv("FZF_API_KEY")
@@ -99,9 +99,9 @@ func startHttpServer(address listenAddress, actionChannel chan []*action, respon
} }
server := httpServer{ server := httpServer{
apiKey: []byte(apiKey), apiKey: []byte(apiKey),
actionChannel: actionChannel, actionChannel: actionChannel,
responseChannel: responseChannel, getHandler: getHandler,
} }
go func() { go func() {
@@ -165,17 +165,11 @@ func (server *httpServer) handleHttpRequest(conn net.Conn) string {
case 0: case 0:
getMatch := getRegex.FindStringSubmatch(text) getMatch := getRegex.FindStringSubmatch(text)
if len(getMatch) > 0 { if len(getMatch) > 0 {
server.actionChannel <- []*action{{t: actResponse, a: getMatch[1]}} response := server.getHandler(parseGetParams(getMatch[1]))
select { if len(response) > 0 {
case response := <-server.responseChannel:
return good(response) return good(response)
case <-time.After(channelTimeout):
go func() {
// Drain the channel
<-server.responseChannel
}()
return answer(httpUnavailable+jsonContentType, `{"error":"timeout"}`)
} }
return answer(httpUnavailable+jsonContentType, `{"error":"timeout"}`)
} else if !strings.HasPrefix(text, "POST / HTTP") { } else if !strings.HasPrefix(text, "POST / HTTP") {
return bad("invalid request method") return bad("invalid request method")
} }

File diff suppressed because it is too large Load Diff

View File

@@ -20,9 +20,5 @@ func notifyStop(p *os.Process) {
if err == nil { if err == nil {
pid = pgid * -1 pid = pgid * -1
} }
unix.Kill(pid, syscall.SIGSTOP) unix.Kill(pid, syscall.SIGTSTP)
}
func notifyOnCont(resizeChan chan<- os.Signal) {
signal.Notify(resizeChan, syscall.SIGCONT)
} }

View File

@@ -13,7 +13,3 @@ func notifyOnResize(resizeChan chan<- os.Signal) {
func notifyStop(p *os.Process) { func notifyStop(p *os.Process) {
// NOOP // NOOP
} }
func notifyOnCont(resizeChan chan<- os.Signal) {
// NOOP
}

View File

@@ -38,7 +38,7 @@ func runTmux(args []string, opts *Options) (int, error) {
case posUp: case posUp:
tmuxArgs = append(tmuxArgs, "-xC", "-y0") tmuxArgs = append(tmuxArgs, "-xC", "-y0")
case posDown: case posDown:
tmuxArgs = append(tmuxArgs, "-xC", "-yS") tmuxArgs = append(tmuxArgs, "-xC", "-y9999")
case posLeft: case posLeft:
tmuxArgs = append(tmuxArgs, "-x0", "-yC") tmuxArgs = append(tmuxArgs, "-x0", "-yC")
case posRight: case posRight:
@@ -49,9 +49,12 @@ func runTmux(args []string, opts *Options) (int, error) {
tmuxArgs = append(tmuxArgs, "-w"+opts.Tmux.width.String()) tmuxArgs = append(tmuxArgs, "-w"+opts.Tmux.width.String())
tmuxArgs = append(tmuxArgs, "-h"+opts.Tmux.height.String()) tmuxArgs = append(tmuxArgs, "-h"+opts.Tmux.height.String())
return runProxy(argStr, func(temp string) *exec.Cmd { return runProxy(argStr, func(temp string, needBash bool) (*exec.Cmd, error) {
sh, _ := sh() sh, err := sh(needBash)
if err != nil {
return nil, err
}
tmuxArgs = append(tmuxArgs, sh, temp) tmuxArgs = append(tmuxArgs, sh, temp)
return exec.Command("tmux", tmuxArgs...) return exec.Command("tmux", tmuxArgs...), nil
}, opts, true) }, opts, true)
} }

View File

@@ -794,6 +794,9 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, prev
w.fg = r.theme.Fg.Color w.fg = r.theme.Fg.Color
w.bg = r.theme.Bg.Color w.bg = r.theme.Bg.Color
} }
if !w.bg.IsDefault() && w.border.shape != BorderNone {
w.Erase()
}
w.drawBorder(false) w.drawBorder(false)
return w return w
} }
@@ -1027,13 +1030,13 @@ func cleanse(str string) string {
func (w *LightWindow) CPrint(pair ColorPair, text string) { func (w *LightWindow) CPrint(pair ColorPair, text string) {
_, code := w.csiColor(pair.Fg(), pair.Bg(), pair.Attr()) _, code := w.csiColor(pair.Fg(), pair.Bg(), pair.Attr())
w.stderrInternal(cleanse(text), false, code) w.stderrInternal(cleanse(text), false, code)
w.csi("m") w.csi("0m")
} }
func (w *LightWindow) cprint2(fg Color, bg Color, attr Attr, text string) { func (w *LightWindow) cprint2(fg Color, bg Color, attr Attr, text string) {
hasColors, code := w.csiColor(fg, bg, attr) hasColors, code := w.csiColor(fg, bg, attr)
if hasColors { if hasColors {
defer w.csi("m") defer w.csi("0m")
} }
w.stderrInternal(cleanse(text), false, code) w.stderrInternal(cleanse(text), false, code)
} }
@@ -1115,6 +1118,14 @@ func (w *LightWindow) setBg() string {
return "\x1b[m" return "\x1b[m"
} }
func (w *LightWindow) LinkBegin(uri string, params string) {
w.renderer.queued.WriteString("\x1b]8;" + params + ";" + uri + "\x1b\\")
}
func (w *LightWindow) LinkEnd() {
w.renderer.queued.WriteString("\x1b]8;;\x1b\\")
}
func (w *LightWindow) Fill(text string) FillReturn { func (w *LightWindow) Fill(text string) FillReturn {
w.Move(w.posy, w.posx) w.Move(w.posy, w.posx)
code := w.setBg() code := w.setBg()
@@ -1130,7 +1141,7 @@ func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillRetu
bg = w.bg bg = w.bg
} }
if hasColors, resetCode := w.csiColor(fg, bg, attr); hasColors { if hasColors, resetCode := w.csiColor(fg, bg, attr); hasColors {
defer w.csi("m") defer w.csi("0m")
return w.fill(text, resetCode) return w.fill(text, resetCode)
} }
return w.fill(text, w.setBg()) return w.fill(text, w.setBg())

View File

@@ -33,15 +33,9 @@ func (r *LightRenderer) fd() int {
return int(r.ttyin.Fd()) return int(r.ttyin.Fd())
} }
func (r *LightRenderer) initPlatform() error { func (r *LightRenderer) initPlatform() (err error) {
fd := r.fd() r.origState, err = term.MakeRaw(r.fd())
origState, err := term.GetState(fd) return err
if err != nil {
return err
}
r.origState = origState
term.MakeRaw(fd)
return nil
} }
func (r *LightRenderer) closePlatform() { func (r *LightRenderer) closePlatform() {

View File

@@ -139,7 +139,7 @@ func (r *LightRenderer) findOffset() (row int, col int) {
if err := windows.GetConsoleScreenBufferInfo(windows.Handle(r.outHandle), &bufferInfo); err != nil { if err := windows.GetConsoleScreenBufferInfo(windows.Handle(r.outHandle), &bufferInfo); err != nil {
return -1, -1 return -1, -1
} }
return int(bufferInfo.CursorPosition.X), int(bufferInfo.CursorPosition.Y) return int(bufferInfo.CursorPosition.Y), int(bufferInfo.CursorPosition.X)
} }
func (r *LightRenderer) getch(nonblock bool) (int, bool) { func (r *LightRenderer) getch(nonblock bool) (int, bool) {

View File

@@ -4,6 +4,7 @@ package tui
import ( import (
"os" "os"
"regexp"
"time" "time"
"github.com/gdamore/tcell/v2" "github.com/gdamore/tcell/v2"
@@ -49,6 +50,8 @@ type TcellWindow struct {
lastY int lastY int
moveCursor bool moveCursor bool
borderStyle BorderStyle borderStyle BorderStyle
uri *string
params *string
} }
func (w *TcellWindow) Top() int { func (w *TcellWindow) Top() int {
@@ -601,6 +604,16 @@ func (w *TcellWindow) Print(text string) {
w.printString(text, w.normal) w.printString(text, w.normal)
} }
func (w *TcellWindow) withUrl(style tcell.Style) tcell.Style {
if w.uri != nil {
style = style.Url(*w.uri)
if md := regexp.MustCompile(`id=([^:]+)`).FindStringSubmatch(*w.params); len(md) > 1 {
style = style.UrlId(md[1])
}
}
return style
}
func (w *TcellWindow) printString(text string, pair ColorPair) { func (w *TcellWindow) printString(text string, pair ColorPair) {
lx := 0 lx := 0
a := pair.Attr() a := pair.Attr()
@@ -615,6 +628,7 @@ func (w *TcellWindow) printString(text string, pair ColorPair) {
Blink(a&Attr(tcell.AttrBlink) != 0). Blink(a&Attr(tcell.AttrBlink) != 0).
Dim(a&Attr(tcell.AttrDim) != 0) Dim(a&Attr(tcell.AttrDim) != 0)
} }
style = w.withUrl(style)
gr := uniseg.NewGraphemes(text) gr := uniseg.NewGraphemes(text)
for gr.Next() { for gr.Next() {
@@ -665,6 +679,7 @@ func (w *TcellWindow) fillString(text string, pair ColorPair) FillReturn {
Underline(a&Attr(tcell.AttrUnderline) != 0). Underline(a&Attr(tcell.AttrUnderline) != 0).
StrikeThrough(a&Attr(tcell.AttrStrikeThrough) != 0). StrikeThrough(a&Attr(tcell.AttrStrikeThrough) != 0).
Italic(a&Attr(tcell.AttrItalic) != 0) Italic(a&Attr(tcell.AttrItalic) != 0)
style = w.withUrl(style)
gr := uniseg.NewGraphemes(text) gr := uniseg.NewGraphemes(text)
Loop: Loop:
@@ -716,6 +731,16 @@ func (w *TcellWindow) Fill(str string) FillReturn {
return w.fillString(str, w.normal) return w.fillString(str, w.normal)
} }
func (w *TcellWindow) LinkBegin(uri string, params string) {
w.uri = &uri
w.params = &params
}
func (w *TcellWindow) LinkEnd() {
w.uri = nil
w.params = nil
}
func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn { func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
if fg == colDefault { if fg == colDefault {
fg = w.normal.Fg() fg = w.normal.Fg()

View File

@@ -334,15 +334,6 @@ type Event struct {
MouseEvent *MouseEvent MouseEvent *MouseEvent
} }
func (e Event) Is(types ...EventType) bool {
for _, t := range types {
if e.Type == t {
return true
}
}
return false
}
type MouseEvent struct { type MouseEvent struct {
Y int Y int
X int X int
@@ -573,6 +564,8 @@ type Window interface {
CPrint(color ColorPair, text string) CPrint(color ColorPair, text string)
Fill(text string) FillReturn Fill(text string) FillReturn
CFill(fg Color, bg Color, attr Attr, text string) FillReturn CFill(fg Color, bg Color, attr Attr, text string) FillReturn
LinkBegin(uri string, params string)
LinkEnd()
Erase() Erase()
EraseMaybe() bool EraseMaybe() bool
} }

View File

@@ -226,3 +226,85 @@ func (chars *Chars) Prepend(prefix string) {
chars.slice = append([]byte(prefix), chars.slice...) chars.slice = append([]byte(prefix), chars.slice...)
} }
} }
func (chars *Chars) Lines(multiLine bool, maxLines int, wrapCols int, wrapSignWidth int, tabstop int) ([][]rune, bool) {
text := make([]rune, chars.Length())
copy(text, chars.ToRunes())
lines := [][]rune{}
overflow := false
if !multiLine {
lines = append(lines, text)
} else {
from := 0
for off := 0; off < len(text); off++ {
if text[off] == '\n' {
lines = append(lines, text[from:off+1]) // Include '\n'
from = off + 1
if len(lines) >= maxLines {
break
}
}
}
var lastLine []rune
if from < len(text) {
lastLine = text[from:]
}
overflow = false
if len(lines) >= maxLines {
overflow = true
} else {
lines = append(lines, lastLine)
}
}
// If wrapping is disabled, we're done
if wrapCols == 0 {
return lines, overflow
}
wrapped := [][]rune{}
for _, line := range lines {
// Remove trailing '\n' and remember if it was there
newline := len(line) > 0 && line[len(line)-1] == '\n'
if newline {
line = line[:len(line)-1]
}
for {
cols := wrapCols
if len(wrapped) > 0 {
cols -= wrapSignWidth
}
_, overflowIdx := RunesWidth(line, 0, tabstop, cols)
if overflowIdx >= 0 {
// Might be a wide character
if overflowIdx == 0 {
overflowIdx = 1
}
if len(wrapped) >= maxLines {
return wrapped, true
}
wrapped = append(wrapped, line[:overflowIdx])
line = line[overflowIdx:]
continue
}
// Restore trailing '\n'
if newline {
line = append(line, '\n')
}
if len(wrapped) >= maxLines {
return wrapped, true
}
wrapped = append(wrapped, line)
break
}
}
return wrapped, false
}

View File

@@ -1,6 +1,9 @@
package util package util
import "testing" import (
"fmt"
"testing"
)
func TestToCharsAscii(t *testing.T) { func TestToCharsAscii(t *testing.T) {
chars := ToChars([]byte("foobar")) chars := ToChars([]byte("foobar"))
@@ -44,3 +47,37 @@ func TestTrimLength(t *testing.T) {
check(" h o ", 5) check(" h o ", 5)
check(" ", 0) check(" ", 0)
} }
func TestCharsLines(t *testing.T) {
chars := ToChars([]byte("abcdef\n가나다\n\tdef"))
check := func(multiLine bool, maxLines int, wrapCols int, wrapSignWidth int, tabstop int, expectedNumLines int, expectedOverflow bool) {
lines, overflow := chars.Lines(multiLine, maxLines, wrapCols, wrapSignWidth, tabstop)
fmt.Println(lines, overflow)
if len(lines) != expectedNumLines || overflow != expectedOverflow {
t.Errorf("Invalid result: %d %v (expected %d %v)", len(lines), overflow, expectedNumLines, expectedOverflow)
}
}
// No wrap
check(true, 1, 0, 0, 8, 1, true)
check(true, 2, 0, 0, 8, 2, true)
check(true, 3, 0, 0, 8, 3, false)
// Wrap (2)
check(true, 4, 2, 0, 8, 4, true)
check(true, 5, 2, 0, 8, 5, true)
check(true, 6, 2, 0, 8, 6, true)
check(true, 7, 2, 0, 8, 7, true)
check(true, 8, 2, 0, 8, 8, true)
check(true, 9, 2, 0, 8, 9, false)
check(true, 9, 2, 0, 1, 8, false) // Smaller tab size
// With wrap sign (3 + 1)
check(true, 100, 3, 1, 1, 8, false)
// With wrap sign (3 + 2)
check(true, 100, 3, 2, 1, 12, false)
// With wrap sign (3 + 2) and no multi-line
check(false, 100, 3, 2, 1, 13, false)
}

View File

@@ -144,12 +144,22 @@ func IsTty(file *os.File) bool {
return isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd) return isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd)
} }
// RunOnce runs the given function only once
func RunOnce(f func()) func() {
once := Once(true)
return func() {
if once() {
f()
}
}
}
// Once returns a function that returns the specified boolean value only once // Once returns a function that returns the specified boolean value only once
func Once(nextResponse bool) func() bool { func Once(nextResponse bool) func() bool {
state := nextResponse state := nextResponse
return func() bool { return func() bool {
prevState := state prevState := state
state = false state = !nextResponse
return prevState return prevState
} }
} }

View File

@@ -137,8 +137,11 @@ func TestOnce(t *testing.T) {
if o() { if o() {
t.Error("Expected: false") t.Error("Expected: false")
} }
if o() { if !o() {
t.Error("Expected: false") t.Error("Expected: true")
}
if !o() {
t.Error("Expected: true")
} }
o = Once(true) o = Once(true)
@@ -148,6 +151,9 @@ func TestOnce(t *testing.T) {
if o() { if o() {
t.Error("Expected: false") t.Error("Expected: false")
} }
if o() {
t.Error("Expected: false")
}
} }
func TestRunesWidth(t *testing.T) { func TestRunesWidth(t *testing.T) {

View File

@@ -44,11 +44,6 @@ func needWinpty(opts *Options) bool {
} }
func runWinpty(args []string, opts *Options) (int, error) { func runWinpty(args []string, opts *Options) (int, error) {
sh, err := sh()
if err != nil {
return ExitError, err
}
argStr := escapeSingleQuote(args[0]) argStr := escapeSingleQuote(args[0])
for _, arg := range args[1:] { for _, arg := range args[1:] {
argStr += " " + escapeSingleQuote(arg) argStr += " " + escapeSingleQuote(arg)
@@ -56,20 +51,30 @@ func runWinpty(args []string, opts *Options) (int, error) {
argStr += ` --no-winpty` argStr += ` --no-winpty`
if isMintty345() { if isMintty345() {
return runProxy(argStr, func(temp string) *exec.Cmd { return runProxy(argStr, func(temp string, needBash bool) (*exec.Cmd, error) {
sh, err := sh(needBash)
if err != nil {
return nil, err
}
cmd := exec.Command(sh, temp) cmd := exec.Command(sh, temp)
cmd.Env = append(os.Environ(), "MSYS=enable_pcon") cmd.Env = append(os.Environ(), "MSYS=enable_pcon")
cmd.Stdin = os.Stdin cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
return cmd return cmd, nil
}, opts, false) }, opts, false)
} }
return runProxy(argStr, func(temp string) *exec.Cmd { return runProxy(argStr, func(temp string, needBash bool) (*exec.Cmd, error) {
sh, err := sh(needBash)
if err != nil {
return nil, err
}
cmd := exec.Command(sh, "-c", fmt.Sprintf(`winpty < /dev/tty > /dev/tty -- sh %q`, temp)) cmd := exec.Command(sh, "-c", fmt.Sprintf(`winpty < /dev/tty > /dev/tty -- sh %q`, temp))
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
return cmd return cmd, nil
}, opts, false) }, opts, false)
} }

View File

@@ -26,12 +26,12 @@ BASE = File.expand_path('..', __dir__)
Dir.chdir(BASE) Dir.chdir(BASE)
FZF = "FZF_DEFAULT_OPTS=\"--no-scrollbar --pointer \\> --marker \\>\" FZF_DEFAULT_COMMAND= #{BASE}/bin/fzf" FZF = "FZF_DEFAULT_OPTS=\"--no-scrollbar --pointer \\> --marker \\>\" FZF_DEFAULT_COMMAND= #{BASE}/bin/fzf"
def wait def wait(timeout = DEFAULT_TIMEOUT)
since = Time.now since = Time.now
begin begin
yield or raise Minitest::Assertion, 'Assertion failure' yield or raise Minitest::Assertion, 'Assertion failure'
rescue Minitest::Assertion rescue Minitest::Assertion
raise if Time.now - since > DEFAULT_TIMEOUT raise if Time.now - since > timeout
sleep(0.05) sleep(0.05)
retry retry
@@ -66,7 +66,7 @@ class Shell
end end
def fish def fish
"unset #{UNSETS.join(' ')}; FZF_DEFAULT_OPTS=\"--no-scrollbar --pointer '>' --marker '>'\" fish_history= fish" "unset #{UNSETS.join(' ')}; rm -f ~/.local/share/fish/fzf_test_history; FZF_DEFAULT_OPTS=\"--no-scrollbar --pointer '>' --marker '>'\" fish_history=fzf_test fish"
end end
end end
end end
@@ -103,10 +103,10 @@ class Tmux
go(%W[capture-pane -p -J -t #{win}]).map(&:rstrip).reverse.drop_while(&:empty?).reverse go(%W[capture-pane -p -J -t #{win}]).map(&:rstrip).reverse.drop_while(&:empty?).reverse
end end
def until(refresh = false) def until(refresh = false, timeout: DEFAULT_TIMEOUT)
lines = nil lines = nil
begin begin
wait do wait(timeout) do
lines = capture lines = capture
class << lines class << lines
def counts def counts
@@ -1443,7 +1443,7 @@ class TestGoFZF < TestBase
[0, 3, 6].each do |off| [0, 3, 6].each do |off|
tmux.prepare tmux.prepare
tmux.send_keys "#{FZF} --hscroll-off=#{off} -q 0 < #{tempname}", :Enter tmux.send_keys "#{FZF} --hscroll-off=#{off} -q 0 < #{tempname}", :Enter
tmux.until { |lines| assert lines[-3]&.end_with?((0..off).to_a.join + '..') } tmux.until { |lines| assert lines[-3]&.end_with?((0..off).to_a.join + '··') }
tmux.send_keys '9' tmux.send_keys '9'
tmux.until { |lines| assert lines[-3]&.end_with?('789') } tmux.until { |lines| assert lines[-3]&.end_with?('789') }
tmux.send_keys :Enter tmux.send_keys :Enter
@@ -2978,6 +2978,28 @@ class TestGoFZF < TestBase
tmux.until { assert_match(%r{ 0/100000}, _1[-1]) } tmux.until { assert_match(%r{ 0/100000}, _1[-1]) }
end end
def test_info_command
tmux.send_keys(%(seq 10000 | #{FZF} --separator x --info-command 'echo -e "--\\x1b[33m$FZF_POS\\x1b[m/$FZF_INFO--"'), :Enter)
tmux.until { assert_match(%r{^ --1/10000/10000-- xx}, _1[-2]) }
tmux.send_keys :Up
tmux.until { assert_match(%r{^ --2/10000/10000-- xx}, _1[-2]) }
end
def test_info_command_inline
tmux.send_keys(%(seq 10000 | #{FZF} --separator x --info-command 'echo -e "--\\x1b[33m$FZF_POS\\x1b[m/$FZF_INFO--"' --info inline:xx), :Enter)
tmux.until { assert_match(%r{^> xx--1/10000/10000-- xx}, _1[-1]) }
end
def test_info_command_right
tmux.send_keys(%(seq 10000 | #{FZF} --separator x --info-command 'echo -e "--\\x1b[33m$FZF_POS\\x1b[m/$FZF_INFO--"' --info right), :Enter)
tmux.until { assert_match(%r{xx --1/10000/10000-- *$}, _1[-2]) }
end
def test_info_command_inline_right
tmux.send_keys(%(seq 10000 | #{FZF} --info-command 'echo -e "--\\x1b[33m$FZF_POS\\x1b[m/$FZF_INFO--"' --info inline-right), :Enter)
tmux.until { assert_match(%r{ --1/10000/10000-- *$}, _1[-1]) }
end
def test_prev_next_selected def test_prev_next_selected
tmux.send_keys 'seq 10 | fzf --multi --bind ctrl-n:next-selected,ctrl-p:prev-selected', :Enter 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 } tmux.until { |lines| assert_equal 10, lines.item_count }
@@ -3318,6 +3340,92 @@ class TestGoFZF < TestBase
BLOCK BLOCK
tmux.until { assert_block(block, _1) } tmux.until { assert_block(block, _1) }
end end
def test_fzf_multi_line_no_pointer_and_marker
tmux.send_keys %[(echo -en '0\\0'; echo -en '1\\n2\\0'; seq 1000) | fzf --read0 --multi --bind load:select-all --border rounded --reverse --pointer '' --marker '' --marker-multi-line ''], :Enter
block = <<~BLOCK
>
3/3 (3)
0
1
2
1
2
3
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_start_on_reload
tmux.send_keys %(echo foo | #{FZF} --header Loading --header-lines 1 --bind 'start:reload:sleep 2; echo bar' --bind 'load:change-header:Loaded' --bind space:change-header:), :Enter
tmux.until(timeout: 1) { |lines| assert_includes lines[-3], 'Loading' }
tmux.until(timeout: 1) { |lines| refute_includes lines[-4], 'foo' }
tmux.until { |lines| assert_includes lines[-3], 'Loaded' }
tmux.until { |lines| assert_includes lines[-4], 'bar' }
tmux.send_keys :Space
tmux.until { |lines| assert_includes lines[-3], 'bar' }
end
def test_boundary_match
# Underscore boundaries should be ranked lower
{
default: [' x '] + %w[/x/ [x] -x- -x_ _x- _x_],
path: ['/x/', ' x '] + %w[[x] -x- -x_ _x- _x_],
history: ['[x]', '-x-', ' x '] + %w[/x/ -x_ _x- _x_]
}.each do |scheme, expected|
result = `printf -- 'xxx\n-xx\nxx-\n_x_\n_x-\n-x_\n[x]\n-x-\n x \n/x/\n' | #{FZF} -f"'x'" --scheme=#{scheme}`.lines(chomp: true)
assert_equal expected, result
end
end
def test_preview_window_noinfo
# │ 1 ││
tmux.send_keys %(#{FZF} --preview 'seq 1000' --preview-window top,noinfo --scrollbar --bind space:change-preview-window:info), :Enter
tmux.until do |lines|
assert lines[1]&.start_with?('│ 1')
assert lines[1]&.end_with?(' ││')
end
tmux.send_keys :Space
tmux.until do |lines|
assert lines[1]&.start_with?('│ 1')
assert lines[1]&.end_with?('1000││')
end
end
def test_gap
tmux.send_keys %(seq 100 | #{FZF} --gap --border --reverse), :Enter
block = <<~BLOCK
>
100/100
> 1
2
3
4
BLOCK
tmux.until { assert_block(block, _1) }
end
def test_gap_2
tmux.send_keys %(seq 100 | #{FZF} --gap=2 --border --reverse), :Enter
block = <<~BLOCK
>
100/100
> 1
2
3
BLOCK
tmux.until { assert_block(block, _1) }
end
end end
module TestShell module TestShell