Compare commits

...

501 Commits

Author SHA1 Message Date
Junegunn Choi
d579e335b5 0.48.1 2024-03-17 16:35:35 +09:00
Junegunn Choi
7e344ceb85 Update README 2024-03-17 16:21:07 +09:00
Junegunn Choi
0145b82ea0 Update README 2024-03-17 16:20:14 +09:00
Junegunn Choi
b4efe7aab7 Show how to disable a key binding 2024-03-17 16:18:19 +09:00
Junegunn Choi
9ffe951f6d Update Makefile target dependencies
Because shell integration scripts are now embedded in the binary
2024-03-17 16:11:21 +09:00
Brayden Hill
a5ea4f57bd Updated link for highlight command (#3680) 2024-03-17 16:09:39 +09:00
Eli Barzilay
88f4c16755 Make it possible to disable Ctrl+T / Alt+C / completions (#3678)
This makes it possible to skip one of the above key bindings or
completions by setting a variable to an empty string. For example,

    FZF_CTRL_T_COMMAND= FZF_ALT_C_COMMAND= \
      eval "$(fzf --zsh)"

Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2024-03-17 16:06:48 +09:00
Junegunn Choi
c7ee071efa Fix panic caused by invalid cursor index
Fix #3681
2024-03-17 15:55:16 +09:00
Junegunn Choi
0740ef7ceb [bash] Fix default completion of unset, unalias, etc
Fix #3679
2024-03-17 15:38:11 +09:00
junegunn
b29bd809ac Deploying to master from @ junegunn/fzf@8977c9257a 🚀 2024-03-17 00:01:33 +00:00
Junegunn Choi
8977c9257a Limit the maximum number of focus events to process at once 2024-03-14 11:18:47 +09:00
Junegunn Choi
091b7eacba 0.48.0 2024-03-14 00:02:53 +09:00
Junegunn Choi
e74b1251c0 Embed shell integration scripts in fzf binary (--bash / --zsh / --fish) (#3675)
This simplifies the distribution, and the users are less likely to have
problems caused by using incompatible scripts and binaries.

    # Set up fzf key bindings and fuzzy completion
    eval "$(fzf --bash)"

    # Set up fzf key bindings and fuzzy completion
    eval "$(fzf --zsh)"

    # Set up fzf key bindings
    fzf --fish | source
2024-03-13 23:59:34 +09:00
Junegunn Choi
d282a1649d Add walker options and replace 'find' with the built-in walker (#3649) 2024-03-13 20:56:31 +09:00
Junegunn Choi
6ce8d49d1b [bash] Fix regression in dynamic completion
Fix #3674
2024-03-13 08:31:31 +09:00
dependabot[bot]
c5b197078a Bump golang.org/x/term from 0.17.0 to 0.18.0 (#3670)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.17.0 to 0.18.0.
- [Commits](https://github.com/golang/term/compare/v0.17.0...v0.18.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-03-12 08:11:25 +09:00
Junegunn Choi
0494f20d62 Revert "Fix CHANGELOG"
This reverts commit 73aff476dd.
2024-03-10 23:24:59 +09:00
Junegunn Choi
73aff476dd Fix CHANGELOG 2024-03-10 21:52:39 +09:00
Junegunn Choi
98ee5e651a 0.47.0 2024-03-10 21:43:41 +09:00
Koichi Murase
01871ea383 [bash] Update orig_complete after _completion_loader 2024-03-10 21:41:42 +09:00
Koichi Murase
1dbdb9438f [bash] Refactor access to "_fzf_orig_complete_${cmd//[^A-Za-z0-9_]/_}"
In the current codebase, for the original completion settings, the
pieces of the codes to determine the variable name and to access the
stored data are scattered.  In this patch, we define functions to
access these variables.  Those functions will be used in a coming
patch.

* This patch also resolves an inconsistent escaping of "$cmd": $cmd is
  escaped as ${...//[^A-Za-z0-9_]/_} in some places, but it is escaped
  as ${...//[^A-Za-z0-9_=]/_} in some other places.  The latter leaves
  the character "=" in the command name, which causes an issue because
  "=" cannot be a part of a variable name.  For example, the following
  test case produces an error message:

  $ COMP_WORDBREAKS=${COMP_WORDBREAKS//=}
  $ _test1() { COMPREPLY=(); }
  $ complete -vF _test1 cmd.v=1.0
  $ _fzf_setup_completion path cmd.v=1.0
  $ cmd.v=1.0 [TAB]
  bash: _fzf_orig_completion_cmd_v=1_0: invalid variable name

  The behavior of leaving "=" was present from the beginning when
  saving the original completion is introduced in commit 91401514, and
  this does not seem to be a specific reasoning.  In this patch, we
  replace "=" as well as the other non-identifier characters.

* Note: In this patch, the variable REPLY is used to return values
  from functions.  This design is to make it useful with the value
  substitutions, a new Bash feature of the next release 5.3, which is
  taken from mksh.
2024-03-10 21:41:42 +09:00
junegunn
c70f0eadb8 Deploying to master from @ junegunn/fzf@26244ad8c2 🚀 2024-03-10 00:01:32 +00:00
Junegunn Choi
26244ad8c2 Fix preview area not being cleared when using certain types of border styles
fzf --preview 'sleep 3; date' --preview-window hidden \
      --bind ctrl-/:change-preview-window:up,border-bottom
2024-03-09 14:14:42 +09:00
Junegunn Choi
fa0aa5510d Kill preview process when hiding the preview window
via toggle-preview, hide-preview, or change-preview-window
2024-03-08 22:01:45 +09:00
Junegunn Choi
eec557b6aa Fix invalid memory access when the preview window becomes hidden 2024-03-08 17:57:09 +09:00
huajin tong
0cc27c3cc1 Fix typo (#3661) 2024-03-07 01:35:31 +09:00
dependabot[bot]
507089d7b2 Bump actions/checkout from 3 to 4 (#3428)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-05 11:48:46 +09:00
dependabot[bot]
a6b3517b75 Bump github.com/gdamore/tcell/v2 from 2.7.1 to 2.7.4 (#3658)
Bumps [github.com/gdamore/tcell/v2](https://github.com/gdamore/tcell) from 2.7.1 to 2.7.4.
- [Release notes](https://github.com/gdamore/tcell/releases)
- [Changelog](https://github.com/gdamore/tcell/blob/main/CHANGESv2.md)
- [Commits](https://github.com/gdamore/tcell/compare/v2.7.1...v2.7.4)

---
updated-dependencies:
- dependency-name: github.com/gdamore/tcell/v2
  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-03-05 11:04:38 +09:00
dependabot[bot]
2d6beb7813 Bump crate-ci/typos from 1.18.2 to 1.19.0 (#3657)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.18.2 to 1.19.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.18.2...v1.19.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-03-05 11:04:17 +09:00
onee-only
61bc129e1d Update parseGetParams to call strconv.Atoi when params are valid 2024-03-05 11:03:56 +09:00
onee-only
52210a57f0 Update error return position according to convention 2024-03-05 11:03:56 +09:00
onee-only
8061a2f108 Remove duplicate code 2024-03-05 11:03:56 +09:00
junegunn
7444eff6d4 Deploying to master from @ junegunn/fzf@f35a9da99a 🚀 2024-03-03 00:01:39 +00:00
dependabot[bot]
f35a9da99a Bump crate-ci/typos from 1.17.2 to 1.18.2 (#3624)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.17.2 to 1.18.2.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.17.2...v1.18.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-02 18:40:13 +09:00
dependabot[bot]
c3098e9ab2 Bump github.com/mattn/go-isatty from 0.0.17 to 0.0.20 (#3489)
Bumps [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) from 0.0.17 to 0.0.20.
- [Commits](https://github.com/mattn/go-isatty/compare/v0.0.17...v0.0.20)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-isatty
  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-03-02 18:39:37 +09:00
Junegunn Choi
686f9288fc Allow iTerm2 image data that ends with 'ESC \' (#3646) 2024-03-02 18:24:54 +09:00
Junegunn Choi
1833670fb9 Add $FZF_DEFAULT_OPTS_FILE (#3618)
For those who prefer to manage default options in a file.
If the file is not found, fzf will exit with an error.

We're not setting a default value for it because:

1. it's hard to find a default value that can be universally agreed upon
2. to avoid fzf having to check for the existence of the file even when it's not used
2024-02-29 09:49:33 +09:00
junegunn
3dd42f5aa2 Deploying to master from @ junegunn/fzf@99a7beba57 🚀 2024-02-25 00:01:35 +00:00
Junegunn Choi
99a7beba57 Fix missing bonus score on a delimiter character
Fix #3645
2024-02-22 23:19:11 +09:00
Junegunn Choi
edee2b753c fzf-tmux: Workaround for tmux 3.4 bug
Close #3635

https://github.com/tmux/tmux/pull/3840
2024-02-21 14:39:03 +09:00
dependabot[bot]
545d5770be Bump github.com/gdamore/tcell/v2 from 2.7.0 to 2.7.1 (#3639)
Bumps [github.com/gdamore/tcell/v2](https://github.com/gdamore/tcell) from 2.7.0 to 2.7.1.
- [Release notes](https://github.com/gdamore/tcell/releases)
- [Changelog](https://github.com/gdamore/tcell/blob/main/CHANGESv2.md)
- [Commits](https://github.com/gdamore/tcell/compare/v2.7.0...v2.7.1)

---
updated-dependencies:
- dependency-name: github.com/gdamore/tcell/v2
  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-02-20 10:35:23 +09:00
Junegunn Choi
ca747a2b54 Fix unit tests 2024-02-19 12:39:04 +09:00
Junegunn Choi
17da165cfe CHANGELOG: charlievieth/fastwalk 2024-02-19 12:36:14 +09:00
Junegunn Choi
5e6788c679 Export FZF_* variables to 'reload' process as well 2024-02-19 12:36:14 +09:00
Charlie Vieth
425deadca9 dep: update github.com/charlievieth/fastwalk to v1.0.2 (#3631)
This fixes the build for solaris/illumos and removes the extraneous
godirwalk dependency.
2024-02-18 13:20:50 +09:00
junegunn
2c8e9dd3a5 Deploying to master from @ junegunn/fzf@7a72f1a253 🚀 2024-02-18 00:01:35 +00:00
Junegunn Choi
7a72f1a253 Code cleanup: Remove unused argument 2024-02-15 17:11:30 +09:00
Junegunn Choi
208e556332 Replace "default find command" with built-in directory traversal 2024-02-15 16:55:43 +09:00
Junegunn Choi
c65d11bfb5 Update README: warp.dev 2024-02-15 14:30:44 +09:00
Junegunn Choi
3b5b52d89a Update README: warp.dev 2024-02-13 08:45:33 +09:00
dependabot[bot]
a4f6c8f990 Bump github.com/rivo/uniseg from 0.4.6 to 0.4.7 (#3623)
Bumps [github.com/rivo/uniseg](https://github.com/rivo/uniseg) from 0.4.6 to 0.4.7.
- [Release notes](https://github.com/rivo/uniseg/releases)
- [Commits](https://github.com/rivo/uniseg/compare/v0.4.6...v0.4.7)

---
updated-dependencies:
- dependency-name: github.com/rivo/uniseg
  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-02-11 08:53:03 +09:00
dependabot[bot]
670c329852 Bump golang.org/x/term from 0.16.0 to 0.17.0 (#3622)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.16.0 to 0.17.0.
- [Commits](https://github.com/golang/term/compare/v0.16.0...v0.17.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-02-11 08:52:31 +09:00
dependabot[bot]
f3551c8422 Bump golang.org/x/sys from 0.16.0 to 0.17.0 (#3621)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.16.0 to 0.17.0.
- [Commits](https://github.com/golang/sys/compare/v0.16.0...v0.17.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-02-11 08:50:49 +09:00
Konstantin Podsvirov
90b8187882 Add info about MSYS2 distro to README.md (#3610) 2024-02-04 19:02:32 +09:00
junegunn
1a43259989 Deploying to master from @ junegunn/fzf@3c0a630475 🚀 2024-02-04 00:01:39 +00:00
Junegunn Choi
3c0a630475 0.46.1 2024-02-01 18:13:00 +09:00
Junegunn Choi
2a1e5a9729 More test fixes for tcell on GitHub Actions 2024-02-01 17:39:18 +09:00
Junegunn Choi
413c66beba Fix tests for tcell build 2024-02-01 16:25:53 +09:00
Junegunn Choi
1416e696b1 Avoid full redraw on 'preview' action when preview window exists 2024-02-01 15:50:48 +09:00
Junegunn Choi
d373cf89c7 Retain preview window on resize after 'preview' action 2024-02-01 15:46:42 +09:00
dependabot[bot]
dd886d22f0 Bump github.com/rivo/uniseg from 0.4.5 to 0.4.6 (#3605)
Bumps [github.com/rivo/uniseg](https://github.com/rivo/uniseg) from 0.4.5 to 0.4.6.
- [Release notes](https://github.com/rivo/uniseg/releases)
- [Commits](https://github.com/rivo/uniseg/compare/v0.4.5...v0.4.6)

---
updated-dependencies:
- dependency-name: github.com/rivo/uniseg
  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-01-30 11:23:08 +09:00
junegunn
472569a27c Deploying to master from @ junegunn/fzf@76cf6559cc 🚀 2024-01-28 00:01:29 +00:00
Junegunn Choi
76cf6559cc junegunn/uniseg -> rivo/uniseg
https://github.com/rivo/uniseg/pull/47
2024-01-27 22:18:43 +09:00
Junegunn Choi
a34e8dcdc9 Downgrade Go version to keep support for old Windows (#3601)
Go 1.21 dropped support for older versions of Windows.

* https://tip.golang.org/doc/go1.21#windows

But there is no absolute reason for fzf to use Go 1.21, so we downgrade
the dependency.
2024-01-26 13:07:35 +09:00
Junegunn Choi
da752fc9a4 Fix Windows build
Fix #3598
2024-01-24 15:59:54 +09:00
Junegunn Choi
beb2de2dd9 0.46.0 2024-01-23 23:47:24 +09:00
Junegunn Choi
2a8b65e105 Fix highlighting of regions that are matched multiple times
Fix #3596
2024-01-23 12:19:32 +09:00
dependabot[bot]
62a916bc24 Bump crate-ci/typos from 1.16.4 to 1.17.2 (#3595)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.16.4 to 1.17.2.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.16.4...v1.17.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-23 10:21:01 +09:00
dependabot[bot]
c47b833e7b Bump actions/dependency-review-action from 3 to 4 (#3594)
Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 3 to 4.
- [Release notes](https://github.com/actions/dependency-review-action/releases)
- [Commits](https://github.com/actions/dependency-review-action/compare/v3...v4)

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

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

Close #3567
2024-01-17 00:17:22 +09:00
Junegunn Choi
cdfaf761df Expose state information via environment variables to child processes
Close #3582
2024-01-16 14:18:31 +09:00
Junegunn Choi
1a9ea6f738 Remove 'replace' directive for 'go install' compatibility
Close #3577
2024-01-14 17:12:24 +09:00
junegunn
945c1c8597 Deploying to master from @ junegunn/fzf@e4d0f7acd5 🚀 2024-01-14 00:01:41 +00:00
dependabot[bot]
e4d0f7acd5 Bump golang.org/x/term from 0.15.0 to 0.16.0 (#3564)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.15.0 to 0.16.0.
- [Commits](https://github.com/golang/term/compare/v0.15.0...v0.16.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-09 12:31:14 +09:00
Junegunn Choi
250496c953 Add 'result' event that is triggered when the result list is ready
Close #3560
2024-01-07 17:46:21 +09:00
Junegunn Choi
e47dc758c9 Fix focus event not triggered in certain cases 2024-01-07 15:43:17 +09:00
Junegunn Choi
b92a843c5f Use Ubuntu 22 to match GitHub Actions environment 2024-01-07 15:10:06 +09:00
Junegunn Choi
91bea9c5b3 Use forked version of go-runewidth
Fix #3558

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

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

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-19 10:50:47 +09:00
dependabot[bot]
d4b5f12383 Bump github/codeql-action from 2 to 3 (#3544)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-19 09:59:59 +09:00
junegunn
eb62b0d665 Deploying to master from @ junegunn/fzf@91387a741b 🚀 2023-12-17 00:01:44 +00:00
Jan Verbeek
91387a741b Terminate simple server success response with double CRLF (#3542)
The simple success case had only the status line plus a single CRLF,
and pedantic HTTP client implementations (`hyper`) stumbled over
this. A double CRLF makes it OK.

Fixes #3541.
2023-12-16 15:15:00 +09:00
Junegunn Choi
e8b34cb00d Clarification on accept-or-print-query vs. become 2023-12-14 23:13:52 +09:00
Alec Scott
82954258c1 Add Spack installation instructions to README (#3526) 2023-12-10 16:08:54 +09:00
Junegunn Choi
50f092551b Lint: RuboCop 2023-12-10 16:04:30 +09:00
Junegunn Choi
c36a64be68 Add accept-or-print-query
Close #3528
2023-12-10 15:59:45 +09:00
dependabot[bot]
a343b20775 Bump golang.org/x/term from 0.13.0 to 0.15.0 (#3525)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.13.0 to 0.15.0.
- [Commits](https://github.com/golang/term/compare/v0.13.0...v0.15.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-10 15:16:47 +09:00
junegunn
a714e76ae1 Deploying to master from @ junegunn/fzf@d21d5c9510 🚀 2023-12-10 00:01:46 +00:00
junegunn
d21d5c9510 Deploying to master from @ junegunn/fzf@cd6788a2bb 🚀 2023-12-03 00:01:38 +00:00
Junegunn Choi
cd6788a2bb Increase buffer size of event channel to avoid freeze on zero event
Fix #3516
2023-11-30 17:23:46 +09:00
junegunn
6b99399c41 Deploying to master from @ junegunn/fzf@952b6af445 🚀 2023-11-26 00:01:42 +00:00
Laurent Cheylus
952b6af445 Allow files creation in /tmp on OpenBSD (#3512)
- src/protector/protector_openbsd.go: add tmppath for pledge
    permissions
  - fix junegunn/fzf#3511

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-08 12:03:04 +09:00
Junegunn Choi
f5f0b9ecaa Fix a typo 2023-11-08 11:18:36 +09:00
Junegunn Choi
230fc49ae2 (Experimental) Add support for iTerm2 inline image protocol
Close #1102

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

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

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

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

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

An example:

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

A known issue:

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

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

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

Signed-off-by: Laurent Cheylus <foxy@free.fr>
2023-10-17 08:46:43 +09:00
junegunn
3666448ca6 Deploying to master from @ junegunn/fzf@d3311d9f43 🚀 2023-10-15 00:01:43 +00:00
Junegunn Choi
d3311d9f43 0.43.0 2023-10-15 01:56:05 +09:00
LangLangBart
3e1735b06e [zsh] Fix 'emulate: unknown argument -o' error on old zsh (#3465)
Fix #2094
2023-10-14 17:41:01 +09:00
Junegunn Choi
de7ef7eace [fzf-tmux] Fix 'empty command' error on tmux 3.2
Fix #3474
2023-10-13 20:13:28 +09:00
Christoph Anton Mitterer
7e89458a3b [fish] exit as well when called from non-interactive shell (#3467)
Just like with the other shells, exit fish to, if called from a non-interactive
shell.

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

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

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

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

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

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

If not defined, fall back to the previous code.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

---------

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

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

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

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

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

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

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

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

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

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

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

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

* Example:

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

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

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

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

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

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

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

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

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

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

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

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

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

Related: #3295, #3309, #3310.

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

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

**Performance comparison:**

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

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

**Other Notes**

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	Limit scope on which option is set to bare minimum.

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

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

---------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-21 13:40:48 +09:00
Junegunn Choi
a7c41f3fcd Add '--info=right' to the man page
Close #3333
2023-06-17 19:15:29 +09:00
guangwu
4772bd8d4c Use strings.ContainsRune instead (#3335) 2023-06-17 19:10:12 +09:00
Junegunn Choi
d471067e3f 0.42.0 2023-06-15 00:37:41 +09:00
Junegunn Choi
d0b7780239 Add --info=right
Related: #3322
2023-06-11 16:09:15 +09:00
Junegunn Choi
e627ca6bd7 Add --info=inline-right
Close #3322
2023-06-10 23:11:05 +09:00
Junegunn Choi
c97172bdd4 Fix background color of spinner on the preview window 2023-06-10 14:48:47 +09:00
Mike
ce8a745fb4 Add new border style: 'thinblock' (#3327)
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2023-06-10 14:48:29 +09:00
Junegunn Choi
3e9efd1401 [vim] Only prepend --border option in $FZF_DEFAULT_OPTS
Fix #3318
2023-06-03 09:03:04 +09:00
Junegunn Choi
20340190b5 [fzf-tmux] Pass $BAT_THEME
This may anger some purists, but bat is widely used as the previewer so
I think it's worth it.
2023-05-31 20:16:30 +09:00
Junegunn Choi
265040a78c [vim] Respect --border optin in $FZF_DEFAULT_OPTS 2023-05-31 20:09:14 +09:00
Junegunn Choi
448d7e0c5a Update test case 2023-05-27 16:01:30 +09:00
Junegunn Choi
6eb1874c5a 0.41.1 2023-05-27 15:54:22 +09:00
Junegunn Choi
4c70745cc1 Fix bug where preview is not updated after reload when --disabled is set
Fix #3311
2023-05-27 15:51:04 +09:00
Junegunn Choi
7795748a3f Remove dead code 2023-05-27 15:10:47 +09:00
Junegunn Choi
098ef4d7cf 0.41.0 2023-05-26 00:25:09 +09:00
Junegunn Choi
e3f91bfe1b Use Golang 1.20.4 2023-05-26 00:08:20 +09:00
Junegunn Choi
7374fe73a3 Avoid setting $FZF_DEFAULT_COMMAND
So that it's not propagated to the child processes and affect the
behavior of fzf started by them.

fzf 0.41.0 or above is required as it fixed the issue where
'become' process is not given a proper tty device.

Close #3299
2023-05-26 00:08:20 +09:00
dependabot[bot]
d2bde205f0 Bump golang.org/x/term from 0.7.0 to 0.8.0 (#3285)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.7.0 to 0.8.0.
- [Commits](https://github.com/golang/term/compare/v0.7.0...v0.8.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-23 23:55:57 +09:00
dependabot[bot]
5620f70f9a Bump crate-ci/typos from 1.13.16 to 1.14.10 (#3306)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.13.16 to 1.14.10.
- [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.13.16...v1.14.10)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-23 23:55:34 +09:00
Syphdias
37f258b1bf Add key combinations for ctrl-delete and shift-delete (#3284)
Currently there is not option to bind ctrl-delete and shift-delete. As
suggested by issue #3240, shift-delete could be used to bind "delete
entry from history" as it is a common way to do so in other
applications, e.g. browsers.

This, however, does only implement to use the key combination itself and
does not assign a default action to any of them. This does enable to
call one's all predefined actions. With the exec action this can
expanded like the issue #3240 suggested.
If desirable, the key combinations could later get a default behavior.

Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2023-05-21 18:40:05 +09:00
Junegunn Choi
e2dd2a133e Skip post hooks on 'make build'
https://github.com/goreleaser/goreleaser/issues/1469
2023-05-21 18:00:17 +09:00
Junegunn Choi
7514644e07 Update Dockerfile: --platform=linux/amd64 2023-05-21 17:59:18 +09:00
Junegunn Choi
16b0aeda7d Make sure 'become' process is given a proper tty device 2023-05-21 14:01:27 +09:00
Junegunn Choi
86e4f4a841 Update tcell renderer to support block border 2023-05-20 18:24:23 +09:00
Junegunn Choi
607eacf8c7 Allow unbind(focus)
Fix #3279
2023-05-17 10:58:03 +09:00
Junegunn Choi
7a049644a8 Fix panic when trying to render preview window of a negative height
Fix #3292
2023-05-17 00:06:35 +09:00
Junegunn Choi
17a13f00f8 Allow customizing scrollbar of the preview window via --scrollbar=xy 2023-05-16 23:59:08 +09:00
Junegunn Choi
43436e48e0 Add new border style: 'block' 2023-05-16 23:45:31 +09:00
Junegunn Choi
5a39102405 Allow customizing the color of preview scrollbar via 'preview-scrollbar' 2023-05-16 23:24:05 +09:00
Junegunn Choi
94999101e3 Fix the behavior of change-preview-window action (#3280)
* change-preview-window restores the initial preview window options,
  and overrides the properties that are specified
* However, 'hidden' property is treated differently. It is set to
  'false' if the specified properties of the action is non-empty.
* cf. toggle-preview takes the "current" preview window options and
  toggles the 'hidden' property.
2023-05-05 15:33:03 +09:00
Junegunn Choi
e619b7c4f4 Fix the background color of the scrollbar inside the preview window 2023-05-01 16:18:26 +09:00
Junegunn Choi
b7c2e8cb67 Fix caching when reload and query change triggered by the same binding 2023-05-01 13:53:34 +09:00
Junegunn Choi
fb76893e18 0.40.0 2023-05-01 01:59:21 +09:00
Junegunn Choi
88d812fe82 Do not display trailing carriage returns in the preview window
Close #3269
2023-04-30 18:14:40 +09:00
Junegunn Choi
77f9f4664a Fix search not triggered when query change and reload happen at the same time
Fix #3268
2023-04-30 18:14:40 +09:00
dependabot[bot]
5c2f85c39e Bump golang.org/x/term from 0.6.0 to 0.7.0 (#3249)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.6.0 to 0.7.0.
- [Release notes](https://github.com/golang/term/releases)
- [Commits](https://github.com/golang/term/compare/v0.6.0...v0.7.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-28 23:24:56 +09:00
dependabot[bot]
ac4d22cd12 Bump golang.org/x/sys from 0.6.0 to 0.7.0 (#3248)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.6.0 to 0.7.0.
- [Release notes](https://github.com/golang/sys/releases)
- [Commits](https://github.com/golang/sys/compare/v0.6.0...v0.7.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-28 23:20:28 +09:00
Junegunn Choi
cf95e44cb4 Add 'zero' event
Close #3263
2023-04-26 15:13:08 +09:00
Junegunn Choi
65dd2bb429 Add 'track' action 2023-04-22 23:42:09 +09:00
Junegunn Choi
6be855be6a Add change-header and transform-header
Close #3237
2023-04-22 22:01:37 +09:00
Junegunn Choi
b6e3f4423b [man] Suggest setting RUNEWIDTH_EASTASIAN to 0 or 1
Close #2389
2023-04-22 16:35:46 +09:00
Junegunn Choi
0c61d81713 Add toggle-track action 2023-04-22 15:48:51 +09:00
Junegunn Choi
7c6f5dba63 Fixed --track when used with --tac
Fix #3234
2023-04-22 15:09:43 +09:00
psarlov
44cfc7e62a [vim] Add check for powershell 7 users (#3257)
Co-authored-by: Pavel Sarlov <psarlov@asteasolutions.com>
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2023-04-21 20:23:03 +09:00
Junegunn Choi
96670d5f16 Disallow using --track with --tac
Close #3234
2023-04-12 13:23:10 +09:00
Junegunn Choi
36b971ee4e [fzf-tmux] Try awk before bc 2023-04-11 16:29:29 +09:00
Junegunn Choi
f1a9629652 [fzf-tmux] Use awk if bc is not found
Fix #3235
2023-04-06 13:43:18 +09:00
Junegunn Choi
20230402d0 0.39.0 2023-04-02 23:33:37 +09:00
Junegunn Choi
5c2c3a6c88 Use Go 1.20.2 2023-04-02 23:27:22 +09:00
tyama711
fb019d43bf Fix a bug of height range with -1 or -0 (#3226)
Fixed a bug that when both heightUnknown and deferred are true, deferred is not properly reset and the program terminates abnormally.

Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2023-04-02 23:26:13 +09:00
Junegunn Choi
025aa33773 [fzf-tmux] Disallow popup mode on tmux 3.1 or below
Close #3198
2023-04-02 00:16:00 +09:00
Junegunn Choi
302e21fd58 [shell] Update kill completion
* Explicitly specify the list of fields for consistent experience
* Add fallback command for BusyBox (Close #3219)
* Apply `--header-lines=1` to show the column header
2023-04-01 19:52:34 +09:00
Junegunn Choi
211512ae64 Fix Rubocop error 2023-04-01 17:29:13 +09:00
Junegunn Choi
8ec917b1c3 Add 'one' event
Close #2629
Close #2494
Close #459
2023-04-01 17:25:47 +09:00
Junegunn Choi
1c7534f009 Add --track option to track the current selection
Close #3186
Related #1890
2023-04-01 12:59:44 +09:00
Sten Arthur Laane
ae745d9397 Add bat to bash autocomplete commands (#3223)
Bat is a common alternative to cat, it's even referenced multiple times
in fzf docs. This makes `bat **` work by default.
2023-03-27 12:21:37 +09:00
Junegunn Choi
60f37aae2f Respect 'regular' attribute in 'bw' base theme
Don't make the text bold if an element is explicitly specified as
'regular'.

Fix #3222
2023-03-26 23:39:05 +09:00
Junegunn Choi
d7daf5f724 Render CR and LF as ␍ and ␊
Close #2529
2023-03-25 10:41:19 +09:00
Vitaly Zdanevich
e5103d9429 README.md: package managers: add Portage/Gentoo (#3205) 2023-03-22 09:57:50 +09:00
dependabot[bot]
8fecb29848 Bump github.com/rivo/uniseg from 0.4.2 to 0.4.4 (#3192)
Bumps [github.com/rivo/uniseg](https://github.com/rivo/uniseg) from 0.4.2 to 0.4.4.
- [Release notes](https://github.com/rivo/uniseg/releases)
- [Commits](https://github.com/rivo/uniseg/compare/v0.4.2...v0.4.4)

---
updated-dependencies:
- dependency-name: github.com/rivo/uniseg
  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>
2023-03-21 16:53:20 +09:00
dependabot[bot]
290ea6179d Bump golang.org/x/term from 0.0.0-20210927222741-03fcf44c2211 to 0.6.0 (#3203)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.0.0-20210927222741-03fcf44c2211 to 0.6.0.
- [Release notes](https://github.com/golang/term/releases)
- [Commits](https://github.com/golang/term/commits/v0.6.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-21 16:19:55 +09:00
dependabot[bot]
9695a40fc9 Bump golang.org/x/sys from 0.0.0-20220811171246-fbc7d0a398ab to 0.6.0 (#3202)
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.0.0-20220811171246-fbc7d0a398ab to 0.6.0.
- [Release notes](https://github.com/golang/sys/releases)
- [Commits](https://github.com/golang/sys/commits/v0.6.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-21 16:14:14 +09:00
dependabot[bot]
1913b95227 Bump actions/setup-go from 3 to 4 (#3216)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3 to 4.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v3...v4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-21 16:13:02 +09:00
Junegunn Choi
a874aea692 [vim] More explanation on 'set rtp+=~/.fzf' instruction
Close #3171
2023-03-20 22:33:14 +09:00
Michael Vorburger ⛑️
69c52099e7 docs: Fix intention of README (#3214)
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2023-03-20 13:00:33 +09:00
Junegunn Choi
cfc0747d5d Follow Rubocop suggestion 2023-03-19 15:53:31 +09:00
Junegunn Choi
fcd7e8768d Omit port number in --listen for automatic port assignment
Close #3200
2023-03-19 15:48:39 +09:00
Junegunn Choi
3c34dd8275 Fix extra new line in the preview window
When a colored text ends at the right end of the window

Fix #3209
2023-03-17 13:22:20 +09:00
Junegunn Choi
1116e481be [vim] Update setqflist example
Without 'lnum', cfdo doesn't work

Close https://github.com/junegunn/fzf.vim/issues/1435
2023-03-10 22:22:22 +09:00
dependabot[bot]
63cf9d04de Bump crate-ci/typos from 1.13.10 to 1.13.16 (#3194)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2023-03-07 01:21:23 +09:00
Zhizhen He
3364d4d147 Add spell check workflow (#3183) 2023-02-23 00:36:04 +09:00
Junegunn Choi
57ad21e4bd Build and release s390x binaries 2023-02-22 14:31:41 +09:00
Julian Ruess
414f87981f Add support for s390x architecture
Signed-off-by: Julian Ruess <julianr@linux.ibm.com>
2023-02-22 14:31:41 +09:00
Junegunn Choi
b1459c79cf Make sure that the query before the cursor is not hidden
Close #3176
2023-02-19 16:38:26 +09:00
Junegunn Choi
352ea07226 0.38.0 2023-02-15 23:24:42 +09:00
Junegunn Choi
27018787af Describe become(...) action and use it to simplify examples 2023-02-15 23:24:42 +09:00
Junegunn Choi
4e305eca26 become: Set stdin to /dev/tty 2023-02-15 23:24:42 +09:00
sitiom
9e9c0ceaf4 Add Winget Releaser workflow (#3164) 2023-02-15 16:47:12 +09:00
Junegunn Choi
b3bf18b1c0 [fzf-tmux] Fix version check
The output of `tmux -V` starts with "tmux ".
2023-02-13 15:25:39 +09:00
Junegunn Choi
b1619f675f [fzf-tmux] Do not set --margin 0,1 on tmux 3.3 or above
Close #3162
2023-02-13 14:49:02 +09:00
Junegunn Choi
96c3de12eb Run 'become' only when the command template is properly evaluated 2023-02-12 22:06:21 +09:00
Junegunn Choi
719dbb8bae Update ADVANCED.md: transform-query to restore the query string
Close #2961
2023-02-12 17:23:17 +09:00
Junegunn Choi
f38a7f7f8f [bash] Enable environment variable completion for printenv
Close #3145
2023-02-12 16:58:36 +09:00
Junegunn Choi
6ea38b4438 Add become(...) action that replaces current fzf process
Close #3159
2023-02-11 20:26:31 +09:00
Junegunn Choi
f7447aece1 Code cleanup 2023-02-01 18:16:58 +09:00
Junegunn Choi
aa2b9ec476 Add 'show-preview' and 'hide-preview'
For cases where 'toggle-preview' is not enough
2023-01-31 17:34:11 +09:00
Junegunn Choi
3ee00f8bc2 toggle-preview should not show empty preview window 2023-01-30 22:13:29 +09:00
Junegunn Choi
fccab60a5c --preview-window 0,hidden should not execute the preview command
Until `toggle-preview` action is triggered

Fix #3149
2023-01-30 21:39:18 +09:00
Junegunn Choi
0f4af38457 [vim] Simplify --border injection
Prepend the border options so that the user can override them in
'options' entry of the spec.
2023-01-27 14:00:22 +09:00
Junegunn Choi
aef39f1160 [vim] Fix missing --border when --border-label is present 2023-01-27 11:00:59 +09:00
Junegunn Choi
2023012408 0.37.0 2023-01-24 22:11:14 +09:00
Junegunn Choi
95a7661bb1 Sanitize input strings that should be a single line 2023-01-24 19:35:29 +09:00
Junegunn Choi
618d317803 Support custom separator of inline info
Close #2030
Close #3084
2023-01-24 17:55:06 +09:00
Junegunn Choi
ae897c8cdb No need to touch mouse flag if it's already false 2023-01-24 12:45:07 +09:00
Junegunn Choi
d0a0f3c052 Temporarily disable mouse mode when switching to an external command 2023-01-24 12:41:40 +09:00
Junegunn Choi
91b9591b10 Reenable mouse mode when coming back from an external program
Close #3141
2023-01-24 12:00:41 +09:00
Junegunn Choi
aa7361337d Make test case pass on 32-bit platforms
Close #3127
2023-01-23 18:30:36 +09:00
Junegunn Choi
284d77fe2e Add 'focus' event
Can we find a better name? I have considered the followings.

* 'point', because "the pointer" points to the current item.
* 'shift', 'switch', 'move', etc. These are not technically correct
  because the current item can change without cursor movement (--tac,
  reload, search update)
* 'change' is already taken. 'change-current' feels a bit wordy and
  sounds wrong, 'current-changed' is wordy and doesn't go well with the
  other event names
* 'target', not straightforward

Close #3053
2023-01-23 16:38:24 +09:00
Junegunn Choi
826178f1e2 Do not restore terminal state while running an external command 2023-01-23 02:24:13 +09:00
Junegunn Choi
acccf8a9b8 Fix TOC 2023-01-23 02:24:13 +09:00
Francesco Bigagnoli
57c066f0be Fix bat url in README (#3129) 2023-01-23 02:21:16 +09:00
Nachum Barcohen
e44f64ae92 Add Helix editor to bash autocompletion (#3137) 2023-01-23 02:21:04 +09:00
Junegunn Choi
d51980a3f5 Add 'transform-border-label' and 'transform-preview-label' 2023-01-22 02:18:19 +09:00
jpcrs
c3d73e7ecb Add change-border-label and change-preview-label actions, update man 2023-01-22 02:18:19 +09:00
Junegunn Choi
b077f6821d Action argument in enclosed form should allow new lines
Close #3138
2023-01-21 22:20:26 +09:00
Junegunn Choi
a79de11af7 README: Add FZF_TMUX_OPTS example for tmux popup 2023-01-19 13:25:08 +09:00
Junegunn Choi
2023011763 0.36.0 2023-01-17 01:33:05 +09:00
Junegunn Choi
b46e40e86b [vim] Automatically set RUNEWIDTH_EASTASIAN=1 when &ambiwidth == double 2023-01-17 01:03:16 +09:00
Junegunn Choi
a6d6cdd165 [vim] Use system-default border style
* 'rounded' on non-Windows platforms
* 'sharp' on Windows
2023-01-16 22:44:22 +09:00
Junegunn Choi
dc8da605f9 Fix rendering of double-column borders on light renderer 2023-01-16 19:55:44 +09:00
Junegunn Choi
8b299a29c7 Fix rendering of double-column borders 2023-01-16 19:34:28 +09:00
Junegunn Choi
3109b865d2 Fix typo on man page 2023-01-16 01:32:48 +09:00
Junegunn Choi
0c5956c43c Better support for Windows terminals
* Default border style on Windows is changed to `sharp` because some
  Windows terminals are not capable of displaying `rounded` border
  characters correctly.
* If your terminal emulator renders each box-drawing character with
  2 columns, set `RUNEWIDTH_EASTASIAN` environment variable to `1`.
2023-01-16 01:26:39 +09:00
Junegunn Choi
1c83b39691 Update README examples 2023-01-13 21:15:03 +09:00
Junegunn Choi
77874b473c Update Rubocop dependencies 2023-01-12 23:37:23 +09:00
Junegunn Choi
b7cce7be15 Remove unused block argument 2023-01-12 23:24:39 +09:00
Junegunn Choi
3cd3362417 Fix test failure 2023-01-12 23:23:29 +09:00
Junegunn Choi
e97e925efb Resume preview following if the user scrolls the window to the bottom 2023-01-12 23:18:41 +09:00
Farooq Karimi Zadeh
0f032235cf Correct package manager commands for apt (#3117) 2023-01-10 10:46:13 +09:00
Junegunn Choi
e0f0984da7 Allow re-enabling preview follow on change-preview-window 2023-01-09 11:08:06 +09:00
Junegunn Choi
4d22b5aaef Disable preview follow after dragging the scrollbar
TBD: Should we re-enable follow once the offset reaches the bottom?
2023-01-09 11:07:31 +09:00
Junegunn Choi
80b8846318 Run preview command when preview window appears after resize (#3113)
# Start fzf in a small screen so that the preview window is hidden
  fzf --bind 'ctrl-p:toggle-preview' --preview 'stat {}' --preview-window='right,50%,<100(down,50%,hidden)'

  # Enlarge the screen until the preview window appears. It should not be empty.
2023-01-07 16:11:35 +09:00
Junegunn Choi
bf641faafa Prevent fzf crashing on malformed remote action 2023-01-07 15:38:02 +09:00
Junegunn Choi
23d8b78ce1 Allow toggling of alternative preview window layout that is hidden
Fix #3113
2023-01-07 15:12:31 +09:00
Junegunn Choi
3b2244077d Add scrollbar to the preview window 2023-01-06 15:36:12 +09:00
Junegunn Choi
ee5cdb9713 Reduce flickering of the scroll info panel on the preview window 2023-01-05 01:59:22 +09:00
Junegunn Choi
03d02d67f7 Fix cyclic scrolling with non-zero preview header lines
e.g. fzf --preview-window 'cycle,~2' --preview 'echo foo; echo bar; seq 100'
2023-01-04 22:00:00 +09:00
Junegunn Choi
5798145581 Fix preview border on tcell renderer 2023-01-04 10:09:39 +09:00
dependabot[bot]
51ef0b7f66 Bump github.com/gdamore/tcell/v2 from 2.5.3 to 2.5.4
Bumps [github.com/gdamore/tcell/v2](https://github.com/gdamore/tcell) from 2.5.3 to 2.5.4.
- [Release notes](https://github.com/gdamore/tcell/releases)
- [Changelog](https://github.com/gdamore/tcell/blob/main/CHANGESv2.md)
- [Commits](https://github.com/gdamore/tcell/compare/v2.5.3...v2.5.4)

---
updated-dependencies:
- dependency-name: github.com/gdamore/tcell/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-03 02:58:09 +09:00
dependabot[bot]
97b4542c73 Bump github.com/mattn/go-isatty from 0.0.16 to 0.0.17
Bumps [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) from 0.0.16 to 0.0.17.
- [Release notes](https://github.com/mattn/go-isatty/releases)
- [Commits](https://github.com/mattn/go-isatty/compare/v0.0.16...v0.0.17)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-isatty
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-03 02:38:57 +09:00
Junegunn Choi
c1cd0c09a2 Allow dragging of the preview window 2023-01-03 01:46:37 +09:00
Junegunn Choi
1fc1f47d80 Fix double-click on light renderer 2023-01-03 01:39:16 +09:00
Junegunn Choi
ec471a5bc2 Make sure two consecutive double clicks require four clicks 2023-01-03 01:36:51 +09:00
Junegunn Choi
a893fc0ca2 Clicks with different x coordinates shouldn't be seen as a double-click 2023-01-03 01:21:40 +09:00
Junegunn Choi
3761dc0433 Avoid superfluous update of scrollbar 2023-01-02 01:10:01 +09:00
Junegunn Choi
aa71a07fbe Fix scrollbar rendering (#3096) 2023-01-01 21:18:20 +09:00
Junegunn Choi
088293f5e7 Restore mouse drag mode (#3096) 2023-01-01 21:04:40 +09:00
Junegunn Choi
7c660aa86e Allow dragging of scrollbar 2023-01-01 19:01:56 +09:00
Junegunn Choi
435d8fa0a2 Colors for 'separator' and 'scrollbar' will default to that for 'border' 2023-01-01 19:01:56 +09:00
Junegunn Choi
5cd6f1d064 Add scrollbar
Close #3096
2023-01-01 14:48:14 +09:00
Junegunn Choi
ec20dfe312 Only allow local requests 2022-12-31 23:13:14 +09:00
Junegunn Choi
924ffb5a35 Fix cache being immediately cleared on reload-sync 2022-12-31 09:33:23 +09:00
Junegunn Choi
62c7f59b94 Add transform-prompt(...) action 2022-12-31 09:27:11 +09:00
Junegunn Choi
e97176b1d7 Update transform-query examples for zsh
Close #3107
2022-12-30 17:12:55 +09:00
Junegunn Choi
d649f5d826 Always execute preview command if {q} is in the template
Even when {q} is empty. Because, why not?

While this can be seen as a breaking change, there is an easy workaround
to keep the old behavior.

    # This will show // even when the query is empty
    : | fzf --preview 'echo /{q}/'

    # But if you don't want it,
    : | fzf --preview '[ -n {q} ] || exit; echo /{q}/'

Close #2759
2022-12-30 14:29:17 +09:00
Junegunn Choi
6c37177cf5 Add reload-sync action
Close #2816
2022-12-29 20:04:33 +09:00
Junegunn Choi
14775aa975 Add 'load' event that is triggered when the input stream is complete
and the first search (with or without query) is complete
2022-12-29 20:01:50 +09:00
Junegunn Choi
44b6336372 Make server channel buffered
Not to block an action that calls the API

  fzf --listen 6266 --bind 'space:execute-silent:curl localhost:6266 -d up'
2022-12-28 12:52:19 +09:00
Junegunn Choi
36d2bb332b Add transform-query(...) action
Test case authored by @SpicyLemon

Close #1930
Close #2465
Close #2559
Close #2509 (e.g. fzf --bind 'space:transform-query:printf %s%s {q} {}')
2022-12-28 00:05:31 +09:00
Junegunn Choi
4dbe45640a Remove $FZF_LISTEN_PORT
It is not worth the added complexity.
2022-12-27 22:12:06 +09:00
Junegunn Choi
4b3f0b9f08 Allow put action with an argument i.e. put(...) 2022-12-27 19:54:46 +09:00
Junegunn Choi
12af069dca Add pos(...) action to move the cursor to the numeric position
# Put the cursor on the 10th item
  seq 100 | fzf --sync --bind 'start:pos(10)'

  # Put the cursor on the 10th to last item
  seq 100 | fzf --sync --bind 'start:pos(-10)'

Close #3069
Close #395
2022-12-27 01:08:42 +09:00
Junegunn Choi
d42e708d31 Update README-VIM: Different homebrew prefix on Apple Silicon
Close #3095
2022-12-26 16:25:36 +09:00
Junegunn Choi
b7bb973118 Revert "Add GET endpoints for getting the state of the finder"
This reverts commit 750b2a6313.

This can cause a deadlock if the endpoints are accessed in the core event
loop via execute action.

  fzf --listen 6266 --bind 'space:execute:curl localhost:6266'

Technically, there's no reason to use the API because the information is
already available via `{}` and `{q}`, but I'd like to completely remove
the risk of misuse.
2022-12-25 20:00:00 +09:00
Junegunn Choi
750b2a6313 Add GET endpoints for getting the state of the finder
* GET / (or GET /current)
* GET /query
2022-12-25 16:27:02 +09:00
Philipp Wagner
de0da86bd7 Add ppc64le binaries (#3067)
Little-endian 64 bit PowerPC (ppc64le) is the "normal" PowerPC
architecture supported by standard Linux distributions (RedHat, SUSE,
Ubuntu, etc.).

Add support for this architecture in the install script, and add binary
builds for it as well.
2022-12-23 15:41:13 +09:00
Junegunn Choi
8e283f512a Fix bind spec parser 2022-12-23 15:37:39 +09:00
Junegunn Choi
73162a4bc3 Rewrite bind spec parser 2022-12-23 03:28:16 +09:00
Junegunn Choi
1a9761736e Add time and size limit to remote requests 2022-12-22 20:44:49 +09:00
Junegunn Choi
fd1f7665a7 Abort fzf if --listen port is unavailable 2022-12-21 13:02:25 +09:00
Junegunn Choi
6d14573fd0 Add test case for --listen 2022-12-21 01:35:08 +09:00
Junegunn Choi
cf69b836ac Only trim CR and NF from the submitted expression
So the trailing space in the following case is respected.

  curl -XPOST localhost:6266 -d "change-prompt:$(date)> "
2022-12-21 01:35:08 +09:00
Junegunn Choi
a7a771b92b Break out of jump mode when any action is submitted to the server 2022-12-21 01:35:08 +09:00
Junegunn Choi
def011c029 Fix parse error of actions with arguments 2022-12-21 01:35:08 +09:00
Junegunn Choi
4b055bf260 Rewrite HTTP server without net/http
This cuts down the binary size from 5.7MB to 3.3MB.
2022-12-21 01:35:08 +09:00
Junegunn Choi
1ba7484d60 Add --listen=HTTP_PORT option to receive actions
Supersedes #2019

See also:
* #1728
* https://github.com/junegunn/fzf.vim/pull/1044
2022-12-21 01:35:08 +09:00
Junegunn Choi
51c518da1e Add change-query(...) action 2022-12-18 00:26:31 +09:00
polluks2
a3b6b03dfb Fix typo (#3093)
Co-authored-by: polluks <polluks@sdf.lonestar.org>
2022-12-17 23:58:43 +09:00
Junegunn Choi
18e3b38c69 Add 'next-selected' and 'prev-selected' actions
Close #2749
2022-12-11 00:59:34 +09:00
Junegunn Choi
0ad30063ff Rename previous-history to prev-history
previous-history is still supported for backward compatibility
2022-12-10 22:42:56 +09:00
Junegunn Choi
7812c64a31 Fix uninitialized colors in base themes
Fix #3079
2022-12-10 16:55:19 +09:00
Junegunn Choi
3d2376ab52 Add color name 'preview-label' (#3053) 2022-12-09 12:05:27 +09:00
Junegunn Choi
6b207bbf2b Fix inconsistent bonus points in exact match
Exact match would assign a different bonus point to the first character
when non-default --scheme was used.

Fix #3073
2022-12-04 22:17:39 +09:00
Bjørn Forsman
3f079ba7c6 README.md: Clarify on FZF_*_OPTS (#3064)
At first I thought they were appended to FZF_*_COMMAND. Let's make it
clear that these are passed to `fzf` itself.
2022-12-02 14:57:32 +09:00
Junegunn Choi
8f4c89f50e Make 'double-click' behave the same as 'enter' by default
Close #3061
2022-11-29 20:27:29 +09:00
OHZEKI Naoki
6b7a543c82 Add more util tests (#3062)
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2022-11-27 16:30:59 +09:00
Junegunn Choi
2ba68d24f2 Do not erase info separator before redrawing it 2022-11-25 23:51:49 +09:00
Bruno Heridet
46877e0a92 test(eventbox): remove obsolete EvtClose const (#3059) 2022-11-23 19:38:14 +09:00
Junegunn Choi
b55f555487 0.35.1 2022-11-18 20:42:56 +09:00
Junegunn Choi
a38b63be18 Fix mouse event above fzf finder
Fix #2949
2022-11-18 20:32:59 +09:00
Junegunn Choi
1bebd6f4f5 Fix panic on inverse match query with --tiebreak=chunk
Fix #3055
2022-11-18 20:16:43 +09:00
Bruno Heridet
3da63f394d doc(man): complete the definition of what --no-unicode impacts (#3054) 2022-11-18 10:23:21 +09:00
Zhizhen He
2a54e3d770 Fix typos in the source code (#3048) 2022-11-18 10:23:04 +09:00
dependabot[bot]
06b02ba46e Bump actions/dependency-review-action from 2 to 3 (#3046)
Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 2 to 3.
- [Release notes](https://github.com/actions/dependency-review-action/releases)
- [Commits](https://github.com/actions/dependency-review-action/compare/v2...v3)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-18 10:22:08 +09:00
Junegunn Choi
d01ae55109 0.35.0 2022-11-12 00:58:06 +09:00
Junegunn Choi
8868d7d188 Add --separator to customize the info separator 2022-11-10 16:23:33 +09:00
Junegunn Choi
2eec9892be [neovim] Use Normal group colors for floating window
Instead of NormalFloat.

https://github.com/junegunn/fzf/issues/3035#issuecomment-1305094043
2022-11-08 00:32:41 +09:00
Junegunn Choi
01ae621f11 Add --border=[bold|double] and --preview-window=border-[bold|double] 2022-11-06 14:38:31 +09:00
Junegunn Choi
f984aa0d2c Fix --border-label and --preview-label on tcell renderer 2022-11-06 14:35:20 +09:00
Junegunn Choi
0881a6bc17 [neovim] Do not use Pmenu group colors for floating window
In Neovim, the foreground and background colors of a floating window
defaults to those of Pmenu highlight group, which yields unexpected
results.

This commit makes the colors of fzf window defaults to those of 'Normal'
group (or 'NormalFloat' if defined), by ignoring Pmenu group.

Then the colors can be configured via --color option of fzf.

NOTE: An error from setwinvar call is ignored because the exact
behavior of &winhighlight with an empty target group is not clearly
documented.

Close #3035
Close https://github.com/junegunn/fzf.vim/issues/1431
See https://github.com/neovim/neovim/pull/9722#discussion_r264777602
2022-11-05 16:52:23 +09:00
Junegunn Choi
2c6a73546d Fix rubocop check 2022-11-01 13:59:17 +09:00
Junegunn Choi
a29944660e Fix typo in CHANGELOG 2022-11-01 13:33:09 +09:00
Junegunn Choi
f6ce624c6f Add tests for --border-label and --preview-label
Also fix failing tests due to info separator

Related #3022 #3029
2022-11-01 13:30:41 +09:00
Junegunn Choi
c09ec8e4d1 Allow putting border label on the bottom line
Related #3022
2022-11-01 13:30:40 +09:00
Junegunn Choi
31bbaad06e Add --preview-label and --preview-label-pos
Close #3022
2022-11-01 13:27:22 +09:00
Junegunn Choi
b9ca1fe830 Add horizontal separator after info panel (counter)
Close #3029
2022-11-01 13:27:11 +09:00
Junegunn Choi
e61585f2f3 Add --border-label and --border-label-pos
Close #3022
2022-11-01 13:27:00 +09:00
Junegunn Choi
0de1aacb0c [vim] Fix version check on Windows when shellslash is set 2022-10-31 10:57:05 +09:00
Junegunn Choi
168829b555 Add 'start' event that is triggered once when fzf finder starts
Close #1622
2022-10-27 00:38:38 +09:00
dependabot[bot]
170fc517d0 Use actions/setup-go v3 (#3021)
* Bump actions/setup-go from 3.3.0 to 3.3.1

Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3.3.0 to 3.3.1.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](268d8c0ca0...c4a742cab1)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Apply suggestions from code review

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2022-10-25 15:54:44 +09:00
Naveen
0fbf1c7c71 Add dependency review (#2817)
* chore(deps): Included dependency review

> Dependency Review GitHub Action in your repository to enforce dependency
> reviews on your pull requests.
> The action scans for vulnerable versions of dependencies introduced by package version
> changes in pull requests,
> and warns you about the associated security vulnerabilities.
> This gives you better visibility of what's changing in a pull request,
> and helps prevent vulnerabilities being added to your repository.

https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement
Signed-off-by: naveensrinivasan <172697+naveensrinivasan@users.noreply.github.com>

* Update .github/workflows/depsreview.yaml

Signed-off-by: naveensrinivasan <172697+naveensrinivasan@users.noreply.github.com>
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2022-10-25 09:34:58 +09:00
dependabot[bot]
694be39c71 Use ruby/setup-ruby v1 (#3012)
* Bump ruby/setup-ruby from 1.117.0 to 1.118.0

Bumps [ruby/setup-ruby](https://github.com/ruby/setup-ruby) from 1.117.0 to 1.118.0.
- [Release notes](https://github.com/ruby/setup-ruby/releases)
- [Commits](3068fa83f9...eae47962ba)

---
updated-dependencies:
- dependency-name: ruby/setup-ruby
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Apply suggestions from code review

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2022-10-24 20:07:31 +09:00
dependabot[bot]
dad26d81df Use github/codeql-action@v2 (#2998)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2022-10-16 22:36:59 +09:00
dependabot[bot]
bcaea097ea Bump actions/checkout from 61b9e3751b92087fd0b06925ba6dd6314e06f089 to v3 (#2997)
* Bump actions/checkout

Bumps [actions/checkout](https://github.com/actions/checkout) from 61b9e3751b92087fd0b06925ba6dd6314e06f089 to 3.1.0. This release includes the previously tagged commit.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](61b9e3751b...93ea575cb5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* Apply suggestions from code review

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2022-10-16 22:33:49 +09:00
Junegunn Choi
d56fe74e24 Add checksums of Darwin binaries
Close #2989
2022-10-16 22:06:40 +09:00
John Fred Fadrigalan
4603d540c3 [shell] Make bash/zsh completion and bindings work with 'set -u' (#2999)
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2022-10-16 17:15:19 +09:00
Junegunn Choi
f9d53303bb [vim] Remove unnecessary powershell check
&shell is guaranteed to be cmd.exe on windows because we call s:use_sh()
2022-10-13 14:48:32 +09:00
Junegunn Choi
d04faa6505 [vim] Fix escaping of fzf binary path containing spaces on Windows
Fix #2992
2022-10-12 20:07:58 +09:00
Kyle L. Davis
07da058eae [vim] Update fzf#install to handle spaces on Windows (#2993) 2022-10-08 19:20:39 +09:00
Bruno Heridet
cefa6b9878 doc(man): add a hint about which UI element is the finder info (#2991)
While reading the description of the --info flag, it's not
immediately obvious that the "finder info" is in fact the
UI element representing the match counters.
2022-10-04 09:52:28 +09:00
Junegunn Choi
04d0b0223f 0.34.0 2022-09-28 23:22:31 +09:00
Junegunn Choi
78ad6d2d88 Phase out --no-clear in favor of bindable 'reload' action 2022-09-28 23:22:31 +09:00
Junegunn Choi
22cbd9fa58 Implement height range (--height ~[VALUE][%])
Close #2953
2022-09-28 23:22:31 +09:00
dependabot[bot]
984049586a Bump github.com/mattn/go-runewidth from 0.0.13 to 0.0.14 (#2984)
Bumps [github.com/mattn/go-runewidth](https://github.com/mattn/go-runewidth) from 0.0.13 to 0.0.14.
- [Release notes](https://github.com/mattn/go-runewidth/releases)
- [Commits](https://github.com/mattn/go-runewidth/compare/v0.0.13...v0.0.14)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-runewidth
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-28 17:03:19 +09:00
dependabot[bot]
cdfc2b92e3 Bump github.com/rivo/uniseg from 0.2.0 to 0.4.2 (#2964)
Bumps [github.com/rivo/uniseg](https://github.com/rivo/uniseg) from 0.2.0 to 0.4.2.
- [Release notes](https://github.com/rivo/uniseg/releases)
- [Commits](https://github.com/rivo/uniseg/compare/v0.2.0...v0.4.2)

---
updated-dependencies:
- dependency-name: github.com/rivo/uniseg
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-28 17:02:55 +09:00
dependabot[bot]
4530abe8df Bump ruby/setup-ruby from 1.101.0 to 1.117.0 (#2974)
Bumps [ruby/setup-ruby](https://github.com/ruby/setup-ruby) from 1.101.0 to 1.117.0.
- [Release notes](https://github.com/ruby/setup-ruby/releases)
- [Commits](ebaea52cb2...3068fa83f9)

---
updated-dependencies:
- dependency-name: ruby/setup-ruby
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-28 16:55:35 +09:00
dependabot[bot]
586020b8b6 Bump actions/checkout from 629c2de402a417ea7690ca6ce3f33229e27606a5 to 61b9e3751b92087fd0b06925ba6dd6314e06f089 (#2965)
* Bump actions/checkout

Bumps [actions/checkout](https://github.com/actions/checkout) from 629c2de402a417ea7690ca6ce3f33229e27606a5 to 61b9e3751b92087fd0b06925ba6dd6314e06f089.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](629c2de402...61b9e3751b)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update .github/workflows/codeql-analysis.yml

* Update .github/workflows/macos.yml

* Update .github/workflows/linux.yml

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2022-09-28 16:55:13 +09:00
dependabot[bot]
3a8626fd04 Bump github.com/saracen/walker from 0.1.2 to 0.1.3 (#2880)
Bumps [github.com/saracen/walker](https://github.com/saracen/walker) from 0.1.2 to 0.1.3.
- [Release notes](https://github.com/saracen/walker/releases)
- [Commits](https://github.com/saracen/walker/compare/v0.1.2...v0.1.3)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-28 16:53:56 +09:00
dependabot[bot]
a6e483a434 Bump actions/setup-go from 3.0.0 to 3.3.0 (#2946)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3.0.0 to 3.3.0.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](f6164bd8c8...268d8c0ca0)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-28 16:53:04 +09:00
dependabot[bot]
6a942e56b1 Bump github/codeql-action from 2.1.8 to 2.1.25 (#2985)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2.1.8 to 2.1.25.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](1ed1437484...86f3159a69)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-28 16:50:36 +09:00
dependabot[bot]
87c91550ad Bump github.com/mattn/go-isatty from 0.0.14 to 0.0.16 (#2926)
Bumps [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) from 0.0.14 to 0.0.16.
- [Release notes](https://github.com/mattn/go-isatty/releases)
- [Commits](https://github.com/mattn/go-isatty/compare/v0.0.14...v0.0.16)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-isatty
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-28 16:50:19 +09:00
Junegunn Choi
731daf0f37 Fix tcell renderer
Fix #2954
2022-09-26 14:09:38 +09:00
Junegunn Choi
f931e53890 [fish] Do not use builtin cd
`builtin cd` of fish doesn't support `cd -`

Close #2967
2022-09-20 16:59:07 +09:00
Junegunn Choi
b5efc68737 Revert "Add Sponsor Labels action"
This reverts commit 845034c81c.
2022-09-13 09:43:11 +09:00
knutze
b9e6e7926c [bash] Fix completion of var or alias containing newlines (#2952)
* Fix bash completion var or aliase containing newlines

* Support for various bash declare options

Co-authored-by: knutze <shakte@gmail.com>
2022-09-10 11:38:41 +09:00
Junegunn Choi
845034c81c Add Sponsor Labels action 2022-09-10 11:25:32 +09:00
Abirdcfly
54d42e3f40 Fix typo in CHANGELOG (#2948) 2022-08-30 13:28:01 +09:00
Junegunn Choi
e03ac3136e 0.33.0 2022-08-29 07:23:14 +09:00
Junegunn Choi
6fb41a202a Add --scheme=[default|path|history] option to choose scoring scheme
Close #2909
Close #2930
2022-08-28 22:22:39 +09:00
Emil Vanherp
4bef330ce1 Add support for ANSI strike-through (#2932)
Close #2932

Co-authored-by: Emil Vanherp <emil@vanherp.me>
2022-08-26 09:27:49 +09:00
Junegunn Choi
8a5f719964 ADVANCED: fzf-git.sh 2022-08-25 17:54:35 +09:00
Charlie Vieth
209d5e8e90 ansi: speed up escape sequence parsing (#2927) 2022-08-25 14:02:08 +09:00
Junegunn Choi
9d041aa582 Update README 2022-08-23 13:29:41 +09:00
Junegunn Choi
6532b3e655 [completion] Remove extra trailing slash on directory completion
Fix #2931
2022-08-22 22:29:51 +09:00
Junegunn Choi
c1c355160d Support border-{up,down} as the synonyms for border-{top,bottom} 2022-08-20 00:11:17 +09:00
Junegunn Choi
83515d5610 Update ANSI test cases 2022-08-13 22:40:37 +09:00
Junegunn Choi
aa10dccf90 Support colon delimiter in ANSI escape sequences
# Both should work
    printf "\e[38;5;208mOption 1\e[m\nOption 2" | fzf --ansi
    printf "\e[38:5:208mOption 1\e[m\nOption 2" | fzf --ansi

This change makes ANSI parsing slightly slower.

    cpu: Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz

    Before:
      BenchmarkNextAnsiEscapeSequence-12            992.22 MB/s
      BenchmarkExtractColor-12                      174.35 MB/s

    After:
      BenchmarkNextAnsiEscapeSequence-12            925.05 MB/s
      BenchmarkExtractColor-12                      163.33 MB/s

Fix #2913
2022-08-13 22:30:50 +09:00
Junegunn Choi
f4fd53211a Reformat comments adhere to gofmt 2022-08-12 22:11:15 +09:00
Junegunn Choi
4993d19466 0.32.1 2022-08-08 23:57:32 +09:00
Junegunn Choi
19f9bbca0d Allow specifying fzf options in $FZF_TMUX_OPTS without '--' 2022-08-08 23:50:47 +09:00
Junegunn Choi
779d8e1627 Use go 1.19 2022-08-08 13:39:00 +09:00
Junegunn Choi
bb07410448 Add --strip-cwd-prefix to fd examples 2022-08-08 13:38:21 +09:00
Yang Tian
d826f9e72f [fzf-tmux] Use fzf border instead of tmux popup border (#2908)
Co-authored-by: Yang Tian <yang.tian@getcruise.com>
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2022-08-07 10:28:45 +09:00
Junegunn Choi
6a6130615d [fzf-tmux] Remove code for tmux 3.2 beta compatibility 2022-08-06 19:08:13 +09:00
lbesnard
a8e7021be2 [completion] ssh: Remove values with '%' (#2548)
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2022-08-03 22:39:42 +09:00
Junegunn Choi
38259d0382 Fix incorrect ordering of --tiebreak=chunk 2022-08-03 22:18:26 +09:00
Junegunn Choi
f7e7259910 0.32.0 2022-08-02 21:56:14 +09:00
Junegunn Choi
f0bfeba733 Add new tiebreak: 'chunk'
Favors the line with shorter matched chunk. A chunk is a set of
consecutive non-whitespace characters.

Unlike the default `length`, this new scheme works well with tabular input.

  # length prefers item #1, because the whole line is shorter,
  # chunk prefers item #2, because the matched chunk ("foo") is shorter
  fzf --height=6 --header-lines=2 --tiebreak=chunk --reverse --query=fo << "EOF"
  N | Field1 | Field2 | Field3
  - | ------ | ------ | ------
  1 | hello  | foobar | baz
  2 | world  | foo    | bazbaz
  EOF

If the input does not contain any spaces, `chunk` is equivalent to
`length`. But we're not going to set it as the default because it is
computationally more expensive.

Close #2285
Close #2537
- Not the exact solution to --tiebreak=length not taking --nth into account,
  but this should work. And the added benefit is that it works well even
  when --nth is not provided.
- Adding a bonus point to the last character of a word didn't turn out great.
  The order of the result suddenly changes when you type in the last
  character in the word producing a jarring effect.
2022-08-02 21:48:19 +09:00
Junegunn Choi
c3a7a24eea Tweak bonus points to word boundaries
Close https://github.com/junegunn/fzf.vim/issues/1004

  # jobs/latency.js is favored over job_latency.js
  printf 'job_latency.js\njobs/latency.js' | fzf -qlatency
2022-08-02 20:57:13 +09:00
Junegunn Choi
bbbcd780c9 Allow "--version" to be used as the argument to --query, --header, et al.
But why?
2022-08-02 20:57:12 +09:00
kissge
475469a2e7 [zsh] Make awk regex compatible with both GNU and macOS (#2906) 2022-08-02 20:56:25 +09:00
Junegunn Choi
3a7447dcb6 Update FUNDING.yml 2022-07-30 19:35:41 +09:00
Junegunn Choi
e5d8cbd383 [vim] Fix version check on windows/powershell (addendum)
Should handle powershell.exe as well

Fix https://github.com/junegunn/fzf.vim/issues/1411
2022-07-30 19:08:04 +09:00
Junegunn Choi
3c08dca7e7 Fix README examples so that they work both on bash and zsh
Close #2887
2022-07-29 22:03:02 +09:00
Carl Kamholtz
d083f01d22 [vim] Add option to force 24 bit colors on Windows (#2889) 2022-07-29 21:51:35 +09:00
Bob Matcuk
68cf393644 [bash] Fix 'possible retry loop' problem of bash-completion (#2891)
Close #2474
Close #2583
2022-07-29 21:50:59 +09:00
Junegunn Choi
18f7230662 Fix mouse location in --height mode
Fix #2900
2022-07-29 15:42:44 +09:00
Carl Kamholtz
728f735281 [vim] Fix version check on windows/powershell (#2894)
- Replace fzf#shellescape with shellescape
- Prepend command with '&' in powershell to deal with quoted exe
2022-07-29 08:18:06 +09:00
Junegunn Choi
ecc418ba77 0.31.0 2022-07-21 23:04:57 +09:00
77 changed files with 8351 additions and 2313 deletions

2
.github/FUNDING.yml vendored
View File

@@ -1 +1 @@
custom: ["https://paypal.me/junegunn", "https://www.buymeacoffee.com/junegunn"]
github: junegunn

View File

@@ -27,18 +27,18 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2
uses: actions/checkout@v4
with:
fetch-depth: 0
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@1ed1437484560351c5be56cf73a48a279d116b78
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@1ed1437484560351c5be56cf73a48a279d116b78
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@1ed1437484560351c5be56cf73a48a279d116b78
uses: github/codeql-action/analyze@v3

14
.github/workflows/depsreview.yaml vendored Normal file
View File

@@ -0,0 +1,14 @@
name: 'Dependency Review'
on: [pull_request]
permissions:
contents: read
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
uses: actions/checkout@v4
- name: 'Dependency Review'
uses: actions/dependency-review-action@v4

View File

@@ -11,29 +11,32 @@ on:
permissions:
contents: read
env:
LANG: C.UTF-8
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@f6164bd8c8acb4a71fb2791a8b6c4024ff038dab # v2
uses: actions/setup-go@v5
with:
go-version: 1.18
go-version: 1.19
- name: Setup Ruby
uses: ruby/setup-ruby@ebaea52cb20fea395b0904125276395e37183dac
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.0.0
ruby-version: 3.1.0
- name: Install packages
run: sudo apt-get install --yes zsh fish tmux
- name: Install Ruby gems
run: sudo gem install --no-document minitest:5.14.2 rubocop:1.0.0 rubocop-minitest:0.10.1 rubocop-performance:1.8.1
run: sudo gem install --no-document minitest:5.17.0 rubocop:1.43.0 rubocop-minitest:0.25.1 rubocop-performance:1.15.2
- name: Rubocop
run: rubocop --require rubocop-minitest --require rubocop-performance
@@ -42,4 +45,7 @@ jobs:
run: make test
- name: Integration test
run: make install && ./install --all && LC_ALL=C 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

@@ -15,17 +15,17 @@ jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@f6164bd8c8acb4a71fb2791a8b6c4024ff038dab # v2
uses: actions/setup-go@v5
with:
go-version: 1.18
- name: Setup Ruby
uses: ruby/setup-ruby@ebaea52cb20fea395b0904125276395e37183dac
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.0.0

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

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

10
.github/workflows/typos.yml vendored Normal file
View File

@@ -0,0 +1,10 @@
name: "Spell Check"
on: [pull_request]
jobs:
typos:
name: Spell Check with Typos
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: crate-ci/typos@v1.19.0

15
.github/workflows/winget.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
name: Publish to Winget
on:
release:
types: [released]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: vedantmgoyal2009/winget-releaser@v2
with:
identifier: junegunn.fzf
version: ${{ github.event.release.tag_name }}
installers-regex: '-windows_(armv7|arm64|amd64)\.zip$'
token: ${{ secrets.WINGET_TOKEN }}

View File

@@ -18,7 +18,7 @@ builds:
post: |
sh -c '
cat > /tmp/fzf-gon-amd64.hcl << EOF
source = ["./dist/fzf-macos_darwin_amd64/fzf"]
source = ["./dist/fzf-macos_darwin_amd64_v1/fzf"]
bundle_id = "kr.junegunn.fzf"
apple_id {
username = "junegunn.c@gmail.com"
@@ -72,6 +72,9 @@ builds:
- amd64
- arm
- arm64
- loong64
- ppc64le
- s390x
goarm:
- 5
- 6
@@ -99,6 +102,10 @@ archives:
files:
- non-existent*
checksum:
extra_files:
- glob: ./dist/fzf-*darwin*.zip
release:
github:
owner: junegunn

View File

@@ -26,3 +26,7 @@ Style/OptionalBooleanParameter:
Enabled: false
Style/WordArray:
MinSize: 1
Minitest/AssertEqual:
Enabled: false
Naming/VariableNumber:
Enabled: false

View File

@@ -1 +1 @@
golang 1.18
golang 1.20.13

View File

@@ -1,30 +1,36 @@
Advanced fzf examples
======================
*(Last update: 2021/05/22)*
* *Last update: 2024/01/20*
* *Requires fzf 0.46.0 or above*
---
<!-- vim-markdown-toc GFM -->
* [Introduction](#introduction)
* [Screen Layout](#screen-layout)
* [`--height`](#--height)
* [`fzf-tmux`](#fzf-tmux)
* [Popup window support](#popup-window-support)
* [`--height`](#--height)
* [`fzf-tmux`](#fzf-tmux)
* [Popup window support](#popup-window-support)
* [Dynamic reloading of the list](#dynamic-reloading-of-the-list)
* [Updating the list of processes by pressing CTRL-R](#updating-the-list-of-processes-by-pressing-ctrl-r)
* [Toggling between data sources](#toggling-between-data-sources)
* [Updating the list of processes by pressing CTRL-R](#updating-the-list-of-processes-by-pressing-ctrl-r)
* [Toggling between data sources](#toggling-between-data-sources)
* [Toggling with a single key binding](#toggling-with-a-single-key-binding)
* [Ripgrep integration](#ripgrep-integration)
* [Using fzf as the secondary filter](#using-fzf-as-the-secondary-filter)
* [Using fzf as interative Ripgrep launcher](#using-fzf-as-interative-ripgrep-launcher)
* [Switching to fzf-only search mode](#switching-to-fzf-only-search-mode)
* [Switching between Ripgrep mode and fzf mode](#switching-between-ripgrep-mode-and-fzf-mode)
* [Using fzf as the secondary filter](#using-fzf-as-the-secondary-filter)
* [Using fzf as interactive Ripgrep launcher](#using-fzf-as-interactive-ripgrep-launcher)
* [Switching to fzf-only search mode](#switching-to-fzf-only-search-mode)
* [Switching between Ripgrep mode and fzf mode](#switching-between-ripgrep-mode-and-fzf-mode)
* [Switching between Ripgrep mode and fzf mode using a single key binding](#switching-between-ripgrep-mode-and-fzf-mode-using-a-single-key-binding)
* [Log tailing](#log-tailing)
* [Key bindings for git objects](#key-bindings-for-git-objects)
* [Files listed in `git status`](#files-listed-in-git-status)
* [Branches](#branches)
* [Commit hashes](#commit-hashes)
* [Files listed in `git status`](#files-listed-in-git-status)
* [Branches](#branches)
* [Commit hashes](#commit-hashes)
* [Color themes](#color-themes)
* [Generating fzf color theme from Vim color schemes](#generating-fzf-color-theme-from-vim-color-schemes)
* [fzf Theme Playground](#fzf-theme-playground)
* [Generating fzf color theme from Vim color schemes](#generating-fzf-color-theme-from-vim-color-schemes)
<!-- vim-markdown-toc -->
@@ -205,6 +211,30 @@ find * | fzf --prompt 'All> ' \
![image](https://user-images.githubusercontent.com/700826/113465072-46321c00-946c-11eb-9b6f-cda3951df579.png)
### Toggling with a single key binding
The above example uses two different key bindings to toggle between two modes,
but can we just use a single key binding?
To make a key binding behave differently each time it is pressed, we need:
1. a way to store the current state. i.e. "which mode are we in?"
2. and a way to dynamically perform different actions depending on the state.
The following example shows how to 1. store the current mode in the prompt
string, 2. and use this information (`$FZF_PROMPT`) to determine which
actions to perform using the `transform` action.
```sh
fd --type file |
fzf --prompt 'Files> ' \
--header 'CTRL-T: Switch between Files/Directories' \
--bind 'ctrl-t:transform:[[ ! $FZF_PROMPT =~ Files ]] &&
echo "change-prompt(Files> )+reload(fd --type file)" ||
echo "change-prompt(Directories> )+reload(fd --type directory)"' \
--preview '[[ $FZF_PROMPT =~ Files ]] && bat --color=always {} || tree -C {}'
```
Ripgrep integration
-------------------
@@ -236,15 +266,13 @@ file called `rfv`.
# 1. Search for text in files using Ripgrep
# 2. Interactively narrow down the list using fzf
# 3. Open the file in Vim
IFS=: read -ra selected < <(
rg --color=always --line-number --no-heading --smart-case "${*:-}" |
fzf --ansi \
--color "hl:-1:underline,hl+:-1:underline:reverse" \
--delimiter : \
--preview 'bat --color=always {1} --highlight-line {2}' \
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3'
)
[ -n "${selected[0]}" ] && vim "${selected[0]}" "+${selected[1]}"
rg --color=always --line-number --no-heading --smart-case "${*:-}" |
fzf --ansi \
--color "hl:-1:underline,hl+:-1:underline:reverse" \
--delimiter : \
--preview 'bat --color=always {1} --highlight-line {2}' \
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \
--bind 'enter:become(vim {1} +{2})'
```
And run it with an initial query string.
@@ -307,10 +335,14 @@ I know it's a lot to digest, let's try to break down the code.
position in the window
- `~3` makes the top three lines fixed header so that they are always
visible regardless of the scroll offset
- Once we selected a line, we open the file with `vim` (`vim
"${selected[0]}"`) and move the cursor to the line (`+${selected[1]}`).
- Instead of using shell script to process the final output of fzf, we use
`become(...)` action which was added in [fzf 0.38.0][0.38.0] to turn fzf
into a new process that opens the file with `vim` (`vim {1}`) and move the
cursor to the line (`+{2}`).
### Using fzf as interative Ripgrep launcher
[0.38.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0380
### Using fzf as interactive Ripgrep launcher
We have learned that we can bind `reload` action to a key (e.g.
`--bind=ctrl-r:execute(ps -ef)`). In the next example, we are going to **bind
@@ -331,25 +363,22 @@ projects, and it will free up memory as you narrow down the results.
# 3. Open the file in Vim
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
INITIAL_QUERY="${*:-}"
IFS=: read -ra selected < <(
FZF_DEFAULT_COMMAND="$RG_PREFIX $(printf %q "$INITIAL_QUERY")" \
fzf --ansi \
--disabled --query "$INITIAL_QUERY" \
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
--delimiter : \
--preview 'bat --color=always {1} --highlight-line {2}' \
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3'
)
[ -n "${selected[0]}" ] && vim "${selected[0]}" "+${selected[1]}"
: | fzf --ansi --disabled --query "$INITIAL_QUERY" \
--bind "start:reload:$RG_PREFIX {q}" \
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
--delimiter : \
--preview 'bat --color=always {1} --highlight-line {2}' \
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \
--bind 'enter:become(vim {1} +{2})'
```
![image](https://user-images.githubusercontent.com/700826/113684212-f9ff0a00-96ff-11eb-8737-7bb571d320cc.png)
- Instead of starting fzf in `rg ... | fzf` form, we start fzf without an
explicit input, but with a custom `FZF_DEFAULT_COMMAND` variable. This way
fzf can kill the initial Ripgrep process it starts with the initial query.
Otherwise, the initial Ripgrep process will keep consuming system resources
even after `reload` is triggered.
- Instead of starting fzf in the usual `rg ... | fzf` form, we start fzf with
an empty input (`: | fzf`), then we make it start the initial Ripgrep
process immediately via `start:reload` binding. This way, fzf owns the
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`
- `{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
@@ -358,8 +387,6 @@ IFS=: read -ra selected < <(
### Switching to fzf-only search mode
*(Requires fzf 0.27.1 or above)*
In the previous example, we lost fuzzy matching capability as we completely
delegated search functionality to Ripgrep. But we can dynamically switch to
fzf-only search mode by *"unbinding"* `reload` action from `change` event.
@@ -375,19 +402,16 @@ fzf-only search mode by *"unbinding"* `reload` action from `change` event.
# 3. Open the file in Vim
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
INITIAL_QUERY="${*:-}"
IFS=: read -ra selected < <(
FZF_DEFAULT_COMMAND="$RG_PREFIX $(printf %q "$INITIAL_QUERY")" \
fzf --ansi \
--color "hl:-1:underline,hl+:-1:underline:reverse" \
--disabled --query "$INITIAL_QUERY" \
--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" \
--prompt '1. ripgrep> ' \
--delimiter : \
--preview 'bat --color=always {1} --highlight-line {2}' \
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3'
)
[ -n "${selected[0]}" ] && vim "${selected[0]}" "+${selected[1]}"
: | fzf --ansi --disabled --query "$INITIAL_QUERY" \
--bind "start:reload:$RG_PREFIX {q}" \
--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" \
--color "hl:-1:underline,hl+:-1:underline:reverse" \
--prompt '1. ripgrep> ' \
--delimiter : \
--preview 'bat --color=always {1} --highlight-line {2}' \
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \
--bind 'enter:become(vim {1} +{2})'
```
* Phase 1. Filtering with Ripgrep
@@ -408,10 +432,8 @@ IFS=: read -ra selected < <(
### Switching between Ripgrep mode and fzf mode
*(Requires fzf 0.30.0 or above)*
fzf 0.30.0 added `rebind` action so we can "rebind" the bindings that were
previously "unbound" via `unbind`.
[fzf 0.30.0][0.30.0] added `rebind` action so we can "rebind" the bindings
that were previously "unbound" via `unbind`.
This is an improved version of the previous example that allows us to switch
between Ripgrep launcher mode and fzf-only filtering mode via CTRL-R and
@@ -421,23 +443,65 @@ CTRL-F.
#!/usr/bin/env bash
# Switch between Ripgrep launcher mode (CTRL-R) and fzf filtering mode (CTRL-F)
rm -f /tmp/rg-fzf-{r,f}
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
INITIAL_QUERY="${*:-}"
IFS=: read -ra selected < <(
FZF_DEFAULT_COMMAND="$RG_PREFIX $(printf %q "$INITIAL_QUERY")" \
fzf --ansi \
--color "hl:-1:underline,hl+:-1:underline:reverse" \
--disabled --query "$INITIAL_QUERY" \
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
--bind "ctrl-f:unbind(change,ctrl-f)+change-prompt(2. fzf> )+enable-search+clear-query+rebind(ctrl-r)" \
--bind "ctrl-r:unbind(ctrl-r)+change-prompt(1. ripgrep> )+disable-search+reload($RG_PREFIX {q} || true)+rebind(change,ctrl-f)" \
--prompt '1. Ripgrep> ' \
--delimiter : \
--header ' CTRL-R (Ripgrep mode) CTRL-F (fzf mode) ' \
--preview 'bat --color=always {1} --highlight-line {2}' \
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3'
)
[ -n "${selected[0]}" ] && vim "${selected[0]}" "+${selected[1]}"
: | fzf --ansi --disabled --query "$INITIAL_QUERY" \
--bind "start:reload($RG_PREFIX {q})+unbind(ctrl-r)" \
--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-r:unbind(ctrl-r)+change-prompt(1. ripgrep> )+disable-search+reload($RG_PREFIX {q} || true)+rebind(change,ctrl-f)+transform-query(echo {q} > /tmp/rg-fzf-f; cat /tmp/rg-fzf-r)" \
--color "hl:-1:underline,hl+:-1:underline:reverse" \
--prompt '1. ripgrep> ' \
--delimiter : \
--header ' CTRL-R (ripgrep mode) CTRL-F (fzf mode) ' \
--preview 'bat --color=always {1} --highlight-line {2}' \
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \
--bind 'enter:become(vim {1} +{2})'
```
- To restore the query string when switching between modes, we store the
current query in `/tmp/rg-fzf-{r,f}` files and restore the query using
`transform-query` action which was added in [fzf 0.36.0][0.36.0].
- Also note that we unbind `ctrl-r` binding on `start` event which is
triggered once when fzf starts.
[0.30.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0300
[0.36.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0360
### Switching between Ripgrep mode and fzf mode using a single key binding
In contrast to the previous version, we use just one hotkey to toggle between
ripgrep and fzf mode. This is achieved by using the `$FZF_PROMPT` as a state
within the `transform` action, a feature introduced in [fzf 0.45.0][0.45.0]. A
more detailed explanation of this feature can be found in a previous section -
[Toggling with a single keybinding](#toggling-with-a-single-key-binding).
[0.45.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0450
When using the `transform` action, the placeholder (`\{q}`) should be escaped to
prevent immediate evaluation.
```sh
#!/usr/bin/env bash
# Switch between Ripgrep mode and fzf filtering mode (CTRL-T)
rm -f /tmp/rg-fzf-{r,f}
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
INITIAL_QUERY="${*:-}"
: | fzf --ansi --disabled --query "$INITIAL_QUERY" \
--bind "start:reload:$RG_PREFIX {q}" \
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
--bind 'ctrl-t:transform:[[ ! $FZF_PROMPT =~ ripgrep ]] &&
echo "rebind(change)+change-prompt(1. ripgrep> )+disable-search+transform-query:echo \{q} > /tmp/rg-fzf-f; cat /tmp/rg-fzf-r" ||
echo "unbind(change)+change-prompt(2. fzf> )+enable-search+transform-query:echo \{q} > /tmp/rg-fzf-r; cat /tmp/rg-fzf-f"' \
--color "hl:-1:underline,hl+:-1:underline:reverse" \
--prompt '1. ripgrep> ' \
--delimiter : \
--header 'CTRL-T: Switch between ripgrep/fzf' \
--preview 'bat --color=always {1} --highlight-line {2}' \
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \
--bind 'enter:become(vim {1} +{2})'
```
Log tailing
@@ -465,16 +529,17 @@ Kubernetes pods.
```bash
pods() {
FZF_DEFAULT_COMMAND="kubectl get pods --all-namespaces" \
fzf --info=inline --layout=reverse --header-lines=1 \
--prompt "$(kubectl config current-context | sed 's/-context$//')> " \
--header $' Enter (kubectl exec) CTRL-O (open log in editor) CTRL-R (reload) \n\n' \
--bind 'ctrl-/:change-preview-window(80%,border-bottom|hidden|)' \
--bind 'enter:execute:kubectl exec -it --namespace {1} {2} -- bash > /dev/tty' \
--bind 'ctrl-o:execute:${EDITOR:-vim} <(kubectl logs --all-containers --namespace {1} {2}) > /dev/tty' \
--bind 'ctrl-r:reload:$FZF_DEFAULT_COMMAND' \
--preview-window up:follow \
--preview 'kubectl logs --follow --all-containers --tail=10000 --namespace {1} {2}' "$@"
: | command='kubectl get pods --all-namespaces' fzf \
--info=inline --layout=reverse --header-lines=1 \
--prompt "$(kubectl config current-context | sed 's/-context$//')> " \
--header $' Enter (kubectl exec) CTRL-O (open log in editor) CTRL-R (reload) \n\n' \
--bind 'start:reload:$command' \
--bind 'ctrl-r:reload:$command' \
--bind 'ctrl-/:change-preview-window(80%,border-bottom|hidden|)' \
--bind 'enter:execute:kubectl exec -it --namespace {1} {2} -- bash > /dev/tty' \
--bind 'ctrl-o:execute:${EDITOR:-vim} <(kubectl logs --all-containers --namespace {1} {2}) > /dev/tty' \
--preview-window up:follow \
--preview 'kubectl logs --follow --all-containers --tail=10000 --namespace {1} {2}' "$@"
}
```
@@ -487,7 +552,7 @@ pods() {
- Press enter key on a pod to `kubectl exec` into it
- Press CTRL-O to open the log in your editor
- Press CTRL-R to reload the pod list
- Press CTRL-/ repeatedly to to rotate through a different sets of preview
- Press CTRL-/ repeatedly to rotate through a different sets of preview
window options
1. `80%,border-bottom`
1. `hidden`
@@ -496,9 +561,17 @@ pods() {
Key bindings for git objects
----------------------------
I have [blogged](https://junegunn.kr/2016/07/fzf-git) about my fzf+git key
bindings a few years ago. I'm going to show them here again, because they are
seriously useful.
Oftentimes, you want to put the identifiers of various Git object to the
command-line. For example, it is common to write commands like these:
```sh
git checkout [SOME_COMMIT_HASH or BRANCH or TAG]
git diff [SOME_COMMIT_HASH or BRANCH or TAG] [SOME_COMMIT_HASH or BRANCH or TAG]
```
[fzf-git.sh](https://github.com/junegunn/fzf-git.sh) project defines a set of
fzf-based key bindings for Git objects. I strongly recommend that you check
them out because they are seriously useful.
### Files listed in `git status`
@@ -518,9 +591,6 @@ seriously useful.
![image](https://user-images.githubusercontent.com/700826/113473765-91692080-94a6-11eb-8d38-ed4d41f27ac1.png)
The full source code can be found [here](https://gist.github.com/junegunn/8b572b8d4b5eddd8b85e5f4d40f17236).
Color themes
------------
@@ -563,6 +633,12 @@ export FZF_DEFAULT_OPTS='--color=bg+:#293739,bg:#1B1D1E,border:#808080,spinner:#
![molokai](https://user-images.githubusercontent.com/700826/113475085-8619f300-94ae-11eb-85e4-2766fc3246bf.png)
### fzf Theme Playground
[fzf Theme Playground](https://vitormv.github.io/fzf-themes/) created by
[Vitor Mello](https://github.com/vitormv) is a webpage where you can
interactively create fzf themes.
### Generating fzf color theme from Vim color schemes
The Vim plugin of fzf can generate `--color` option from the current color

View File

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

View File

@@ -1,6 +1,717 @@
CHANGELOG
=========
0.48.1
------
- CTRL-T and ALT-C bindings can be disabled by setting `FZF_CTRL_T_COMMAND` and `FZF_ALT_C_COMMAND` to empty strings respectively when sourcing the script
```sh
# bash
FZF_CTRL_T_COMMAND= FZF_ALT_C_COMMAND= eval "$(fzf --bash)"
# zsh
FZF_CTRL_T_COMMAND= FZF_ALT_C_COMMAND= eval "$(fzf --zsh)"
# fish
fzf --fish | FZF_CTRL_T_COMMAND= FZF_ALT_C_COMMAND= source
```
- Setting the variables after sourcing the script will have no effect
- Bug fixes
0.48.0
------
- Shell integration scripts are now embedded in the fzf binary. This simplifies the distribution, and the users are less likely to have problems caused by using incompatible scripts and binaries.
- bash
```sh
# Set up fzf key bindings and fuzzy completion
eval "$(fzf --bash)"
```
- zsh
```sh
# Set up fzf key bindings and fuzzy completion
eval "$(fzf --zsh)"
```
- fish
```fish
# Set up fzf key bindings
fzf --fish | source
```
- Added options for customizing the behavior of the built-in walker
| Option | Description | Default |
| --- | --- | --- |
| `--walker=OPTS` | Walker options (`[file][,dir][,follow][,hidden]`) | `file,follow,hidden` |
| `--walker-root=DIR` | Root directory from which to start walker | `.` |
| `--walker-skip=DIRS` | Comma-separated list of directory names to skip | `.git,node_modules` |
- Examples
```sh
# Built-in walker is only used by standalone fzf when $FZF_DEFAULT_COMMAND is not set
unset FZF_DEFAULT_COMMAND
fzf # default: --walker=file,follow,hidden --walker-root=. --walker-skip=.git,node_modules
fzf --walker=file,dir,hidden,follow --walker-skip=.git,node_modules,target
# Walker options in $FZF_DEFAULT_OPTS
export FZF_DEFAULT_OPTS="--walker=file,dir,hidden,follow --walker-skip=.git,node_modules,target"
fzf
# Reading from STDIN; --walker is ignored
seq 100 | fzf --walker=dir
# Reading from $FZF_DEFAULT_COMMAND; --walker is ignored
export FZF_DEFAULT_COMMAND='seq 100'
fzf --walker=dir
```
- Shell integration scripts have been updated to use the built-in walker with these new options and they are now much faster out of the box.
0.47.0
------
- Replaced ["the default find command"][find] with a built-in directory walker to simplify the code and to achieve better performance and consistent behavior across platforms.
This doesn't affect you if you have `$FZF_DEFAULT_COMMAND` set.
- Breaking changes:
- Unlike [the previous "find" command][find], the new traversal code will list hidden files, but hidden directories will still be ignored
- No filtering of `devtmpfs` or `proc` types
- Traversal is parallelized, so the order of the entries will be different each time
- You may wonder why fzf implements directory walker anyway when it's a filter program following the [Unix philosophy][unix].
But fzf has had [the walker code for years][walker] to tackle the performance problem on Windows. And I decided to use the same approach on different platforms as well for the benefits listed above.
- Built-in walker is using the excellent [charlievieth/fastwalk][fastwalk] library, which easily outperforms its competitors and supports safely following symlinks.
- Added `$FZF_DEFAULT_OPTS_FILE` to allow managing default options in a file
- See [#3618](https://github.com/junegunn/fzf/pull/3618)
- Option precedence from lower to higher
1. Options read from `$FZF_DEFAULT_OPTS_FILE`
1. Options from `$FZF_DEFAULT_OPTS`
1. Options from command-line arguments
- Bug fixes and improvements
[find]: https://github.com/junegunn/fzf/blob/0.46.1/src/constants.go#L60-L64
[walker]: https://github.com/junegunn/fzf/pull/1847
[fastwalk]: https://github.com/charlievieth/fastwalk
[unix]: https://en.wikipedia.org/wiki/Unix_philosophy
0.46.1
------
- Bug fixes and improvements
- Fixed Windows binaries
- Downgraded Go version to 1.20 to support older versions of Windows
- https://tip.golang.org/doc/go1.21#windows
- Updated [rivo/uniseg](https://github.com/rivo/uniseg) dependency to v0.4.6
0.46.0
------
- Added two new events
- `result` - triggered when the filtering for the current query is complete and the result list is ready
- `resize` - triggered when the terminal size is changed
- fzf now exports the following environment variables to the child processes
| Variable | Description |
| --- | --- |
| `FZF_LINES` | Number of lines fzf takes up excluding padding and margin |
| `FZF_COLUMNS` | Number of columns fzf takes up excluding padding and margin |
| `FZF_TOTAL_COUNT` | Total number of items |
| `FZF_MATCH_COUNT` | Number of matched items |
| `FZF_SELECT_COUNT` | Number of selected items |
| `FZF_QUERY` | Current query string |
| `FZF_PROMPT` | Prompt string |
| `FZF_ACTION` | The name of the last action performed |
- This allows you to write sophisticated transformations like so
```sh
# Script to dynamically resize the preview window
transformer='
# 1 line for info, another for prompt, and 2 more lines for preview window border
lines=$(( FZF_LINES - FZF_MATCH_COUNT - 4 ))
if [[ $FZF_MATCH_COUNT -eq 0 ]]; then
echo "change-preview-window:hidden"
elif [[ $lines -gt 3 ]]; then
echo "change-preview-window:$lines"
elif [[ $FZF_PREVIEW_LINES -ne 3 ]]; then
echo "change-preview-window:3"
fi
'
seq 10000 | fzf --preview 'seq {} 10000' --preview-window up \
--bind "result:transform:$transformer" \
--bind "resize:transform:$transformer"
```
- And we're phasing out `{fzf:prompt}` and `{fzf:action}`
- Changed [mattn/go-runewidth](https://github.com/mattn/go-runewidth) dependency to [rivo/uniseg](https://github.com/rivo/uniseg) for accurate results
- Set `--ambidouble` if your terminal displays ambiguous width characters (e.g. box-drawing characters for borders) as 2 columns
- `RUNEWIDTH_EASTASIAN=1` is still respected for backward compatibility, but it's recommended that you use this new option instead
- Bug fixes
0.45.0
------
- Added `transform` action to conditionally perform a series of actions
```sh
# Disallow selecting an empty line
echo -e "1. Hello\n2. Goodbye\n\n3. Exit" |
fzf --height '~100%' --reverse --header 'Select one' \
--bind 'enter:transform:[[ -n {} ]] && echo accept || echo "change-header:Invalid selection"'
# Move cursor past the empty line
echo -e "1. Hello\n2. Goodbye\n\n3. Exit" |
fzf --height '~100%' --reverse --header 'Select one' \
--bind 'enter:transform:[[ -n {} ]] && echo accept || echo "change-header:Invalid selection"' \
--bind 'focus:transform:[[ -n {} ]] && exit; [[ {fzf:action} =~ up$ ]] && echo up || echo down'
# A single key binding to toggle between modes
fd --type file |
fzf --prompt 'Files> ' \
--header 'CTRL-T: Switch between Files/Directories' \
--bind 'ctrl-t:transform:[[ ! {fzf:prompt} =~ Files ]] &&
echo "change-prompt(Files> )+reload(fd --type file)" ||
echo "change-prompt(Directories> )+reload(fd --type directory)"'
```
- Added placeholder expressions
- `{fzf:action}` - The name of the last action performed
- `{fzf:prompt}` - Prompt string (including ANSI color codes)
- `{fzf:query}` - Synonym for `{q}`
- Added support for negative height
```sh
# Terminal height minus 1, so you can still see the command line
fzf --height=-1
```
- This handles a terminal resize better than `--height=$(($(tput lines) - 1))`
- Added `accept-or-print-query` action that acts like `accept` but prints the
current query when there's no match for the query
```sh
# You can make CTRL-R paste the current query when there's no match
export FZF_CTRL_R_OPTS='--bind enter:accept-or-print-query'
```
- Note that there are alternative ways to implement the same strategy
```sh
# 'become' is apparently more versatile but it's not available on Windows.
export FZF_CTRL_R_OPTS='--bind "enter:become:if [ -z {} ]; then echo {q}; else echo {}; fi"'
# Using the new 'transform' action
export FZF_CTRL_R_OPTS='--bind "enter:transform:[ -z {} ] && echo print-query || echo accept"'
```
- Added `show-header` and `hide-header` actions
- Bug fixes
0.44.1
------
- Fixed crash when preview window is hidden on `focus` event
0.44.0
------
- (Experimental) Sixel image support in preview window (not available on Windows)
- [bin/fzf-preview.sh](bin/fzf-preview.sh) is added to demonstrate how to
display an image using Kitty image protocol or Sixel. You can use it
like so:
```sh
fzf --preview='fzf-preview.sh {}'
```
- (Experimental) iTerm2 inline image protocol support in preview window (not available on Windows)
```sh
# Using https://iterm2.com/utilities/imgcat
fzf --preview 'imgcat -W $FZF_PREVIEW_COLUMNS -H $FZF_PREVIEW_LINES {}'
```
- HTTP server can be configured to accept remote connections
```sh
# FZF_API_KEY is required for a non-localhost listen address
export FZF_API_KEY="$(head -c 32 /dev/urandom | base64)"
fzf --listen 0.0.0.0:6266
```
- To allow remote process execution, use `--listen-unsafe` instead
(`execute*`, `reload*`, `become`, `preview`, `change-preview`, `transform-*`)
```sh
fzf --listen-unsafe 0.0.0.0:6266
```
- Bug fixes
0.43.0
------
- (Experimental) Added support for Kitty image protocol in the preview window
(not available on Windows)
```sh
fzf --preview='
if file --mime-type {} | grep -qF image/; then
# --transfer-mode=memory is the fastest option but if you want fzf to be able
# to redraw the image on terminal resize or on 'change-preview-window',
# you need to use --transfer-mode=stream.
kitty icat --clear --transfer-mode=memory --unicode-placeholder --stdin=no --place=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}@0x0 {} | sed \$d
else
bat --color=always {}
fi
'
```
- (Experimental) `--listen` server can report program state in JSON format (`GET /`)
```sh
# fzf server started in "headless" mode
fzf --listen 6266 2> /dev/null
# Get program state
curl localhost:6266 | jq .
# Increase the number of items returned (default: 100)
curl localhost:6266?limit=1000 | jq .
```
- `--listen` server can be secured by setting `$FZF_API_KEY` environment
variable.
```sh
export FZF_API_KEY="$(head -c 32 /dev/urandom | base64)"
# Server
fzf --listen 6266
# Client
curl localhost:6266 -H "x-api-key: $FZF_API_KEY" -d 'change-query(yo)'
```
- Added `toggle-header` action
- Added mouse events for `--bind`
- `scroll-up` (bound to `up`)
- `scroll-down` (bound to `down`)
- `shift-scroll-up` (bound to `toggle+up`)
- `shift-scroll-down` (bound to `toggle+down`)
- `shift-left-click` (bound to `toggle`)
- `shift-right-click` (bound to `toggle`)
- `preview-scroll-up` (bound to `preview-up`)
- `preview-scroll-down` (bound to `preview-down`)
```sh
# Twice faster scrolling both in the main window and the preview window
fzf --bind 'scroll-up:up+up,scroll-down:down+down' \
--bind 'preview-scroll-up:preview-up+preview-up' \
--bind 'preview-scroll-down:preview-down+preview-down' \
--preview 'cat {}'
```
- Added `offset-up` and `offset-down` actions
```sh
# Scrolling will behave similarly to CTRL-E and CTRL-Y of vim
fzf --bind scroll-up:offset-up,scroll-down:offset-down \
--bind ctrl-y:offset-up,ctrl-e:offset-down \
--scroll-off=5
```
- Shell extensions
- Updated bash completion for fzf options
- bash key bindings no longer requires perl; it will use awk or mawk
instead if perl is not found
- Basic context-aware completion for ssh command
- Applied `--scheme=path` for better ordering of the result
- Bug fixes and improvements
0.42.0
------
- Added new info style: `--info=right`
- Added new info style: `--info=inline-right`
- Added new border style `thinblock` which uses symbols for legacy computing
[one eighth block elements](https://en.wikipedia.org/wiki/Symbols_for_Legacy_Computing)
- Similarly to `block`, this style is suitable when using a different
background color because the window is completely contained within the border.
```sh
BAT_THEME=GitHub fzf --info=right --border=thinblock --preview-window=border-thinblock \
--margin=3 --scrollbar=▏▕ --preview='bat --color=always --style=numbers {}' \
--color=light,query:238,fg:238,bg:251,bg+:249,gutter:251,border:248,preview-bg:253
```
- This style may not render correctly depending on the font and the
terminal emulator.
0.41.1
------
- Fixed a bug where preview window is not updated when `--disabled` is set and
a reload is triggered by `change:reload` binding
0.41.0
------
- Added color name `preview-border` and `preview-scrollbar`
- Added new border style `block` which uses [block elements](https://en.wikipedia.org/wiki/Block_Elements)
- `--scrollbar` can take two characters, one for the main window, the other
for the preview window
- Putting it altogether:
```sh
fzf-tmux -p 80% --padding 1,2 --preview 'bat --style=plain --color=always {}' \
--color 'bg:237,bg+:235,gutter:237,border:238,scrollbar:236' \
--color 'preview-bg:235,preview-border:236,preview-scrollbar:234' \
--preview-window 'border-block' --border block --scrollbar '▌▐'
```
- Bug fixes and improvements
0.40.0
------
- Added `zero` event that is triggered when there's no match
```sh
# Reload the candidate list when there's no match
echo $RANDOM | fzf --bind 'zero:reload(echo $RANDOM)+clear-query' --height 3
```
- New actions
- Added `track` action which makes fzf track the current item when the
search result is updated. If the user manually moves the cursor, or the
item is not in the updated search result, tracking is automatically
disabled. Tracking is useful when you want to see the surrounding items
by deleting the query string.
```sh
# Narrow down the list with a query, point to a command,
# and hit CTRL-T to see its surrounding commands.
export FZF_CTRL_R_OPTS="
--preview 'echo {}' --preview-window up:3:hidden:wrap
--bind 'ctrl-/:toggle-preview'
--bind 'ctrl-t:track+clear-query'
--bind 'ctrl-y:execute-silent(echo -n {2..} | pbcopy)+abort'
--color header:italic
--header 'Press CTRL-Y to copy command into clipboard'"
```
- Added `change-header(...)`
- Added `transform-header(...)`
- Added `toggle-track` action
- Fixed `--track` behavior when used with `--tac`
- However, using `--track` with `--tac` is not recommended. The resulting
behavior can be very confusing.
- Bug fixes and improvements
0.39.0
------
- Added `one` event that is triggered when there's only one match
```sh
# Automatically select the only match
seq 10 | fzf --bind one:accept
```
- Added `--track` option that makes fzf track the current selection when the
result list is updated. This can be useful when browsing logs using fzf with
sorting disabled.
```sh
git log --oneline --graph --color=always | nl |
fzf --ansi --track --no-sort --layout=reverse-list
```
- If you use `--listen` option without a port number fzf will automatically
allocate an available port and export it as `$FZF_PORT` environment
variable.
```sh
# Automatic port assignment
fzf --listen --bind 'start:execute-silent:echo $FZF_PORT > /tmp/fzf-port'
# Say hello
curl "localhost:$(cat /tmp/fzf-port)" -d 'preview:echo Hello, fzf is listening on $FZF_PORT.'
```
- A carriage return and a line feed character will be rendered as dim ␍ and
␊ respectively.
```sh
printf "foo\rbar\nbaz" | fzf --read0 --preview 'echo {}'
```
- fzf will stop rendering a non-displayable characters as a space. This will
likely cause less glitches in the preview window.
```sh
fzf --preview 'head -1000 /dev/random'
```
- Bug fixes and improvements
0.38.0
------
- New actions
- `become(...)` - Replace the current fzf process with the specified
command using `execve(2)` system call.
See https://github.com/junegunn/fzf#turning-into-a-different-process for
more information.
```sh
# Open selected files in Vim
fzf --multi --bind 'enter:become(vim {+})'
# Open the file in Vim and go to the line
git grep --line-number . |
fzf --delimiter : --nth 3.. --bind 'enter:become(vim {1} +{2})'
```
- This action is not supported on Windows
- `show-preview`
- `hide-preview`
- Bug fixes
- `--preview-window 0,hidden` should not execute the preview command until
`toggle-preview` action is triggered
0.37.0
------
- Added a way to customize the separator of inline info
```sh
fzf --info 'inline: ' --prompt ' ' --color prompt:bright-yellow
```
- New event
- `focus` - Triggered when the focus changes due to a vertical cursor
movement or a search result update
```sh
fzf --bind 'focus:transform-preview-label:echo [ {} ]' --preview 'cat {}'
# Any action bound to the event runs synchronously and thus can make the interface sluggish
# e.g. lolcat isn't one of the fastest programs, and every cursor movement in
# fzf will be noticeably affected by its execution time
fzf --bind 'focus:transform-preview-label:echo [ {} ] | lolcat -f' --preview 'cat {}'
# Beware not to introduce an infinite loop
seq 10 | fzf --bind 'focus:up' --cycle
```
- New actions
- `change-border-label`
- `change-preview-label`
- `transform-border-label`
- `transform-preview-label`
- Bug fixes and improvements
0.36.0
------
- Added `--listen=HTTP_PORT` option to start HTTP server. It allows external
processes to send actions to perform via POST method.
```sh
# Start HTTP server on port 6266
fzf --listen 6266
# Send actions to the server
curl -XPOST localhost:6266 -d 'reload(seq 100)+change-prompt(hundred> )'
```
- Added draggable scrollbar to the main search window and the preview window
```sh
# Hide scrollbar
fzf --no-scrollbar
# Customize scrollbar
fzf --scrollbar ┆ --color scrollbar:blue
```
- New event
- Added `load` event that is triggered when the input stream is complete
and the initial processing of the list is complete.
```sh
# Change the prompt to "loaded" when the input stream is complete
(seq 10; sleep 1; seq 11 20) | fzf --prompt 'Loading> ' --bind 'load:change-prompt:Loaded> '
# You can use it instead of 'start' event without `--sync` if asynchronous
# trigger is not an issue.
(seq 10; sleep 1; seq 11 20) | fzf --bind 'load:last'
```
- New actions
- Added `pos(...)` action to move the cursor to the numeric position
- `first` and `last` are equivalent to `pos(1)` and `pos(-1)` respectively
```sh
# Put the cursor on the 10th item
seq 100 | fzf --sync --bind 'start:pos(10)'
# Put the cursor on the 10th to last item
seq 100 | fzf --sync --bind 'start:pos(-10)'
```
- Added `reload-sync(...)` action which replaces the current list only after
the reload process is complete. This is useful when the command takes
a while to produce the initial output and you don't want fzf to run against
an empty list while the command is running.
```sh
# You can still filter and select entries from the initial list for 3 seconds
seq 100 | fzf --bind 'load:reload-sync(sleep 3; seq 1000)+unbind(load)'
```
- Added `next-selected` and `prev-selected` actions to move between selected
items
```sh
# `next-selected` will move the pointer to the next selected item below the current line
# `prev-selected` will move the pointer to the previous selected item above the current line
seq 10 | fzf --multi --bind ctrl-n:next-selected,ctrl-p:prev-selected
# Both actions respect --layout option
seq 10 | fzf --multi --bind ctrl-n:next-selected,ctrl-p:prev-selected --layout reverse
```
- Added `change-query(...)` action that simply changes the query string to the
given static string. This can be useful when used with `--listen`.
```sh
curl localhost:6266 -d "change-query:$(date)"
```
- Added `transform-prompt(...)` action for transforming the prompt string
using an external command
```sh
# Press space to change the prompt string using an external command
# (only the first line of the output is taken)
fzf --bind 'space:reload(ls),load:transform-prompt(printf "%s> " "$(date)")'
```
- Added `transform-query(...)` action for transforming the query string using
an external command
```sh
# Press space to convert the query to uppercase letters
fzf --bind 'space:transform-query(tr "[:lower:]" "[:upper:]" <<< {q})'
# Bind it to 'change' event for automatic conversion
fzf --bind 'change:transform-query(tr "[:lower:]" "[:upper:]" <<< {q})'
# Can only type numbers
fzf --bind 'change:transform-query(sed "s/[^0-9]//g" <<< {q})'
```
- `put` action can optionally take an argument string
```sh
# a will put 'alpha' on the prompt, ctrl-b will put 'bravo'
fzf --bind 'a:put+put(lpha),ctrl-b:put(bravo)'
```
- Added color name `preview-label` for `--preview-label` (defaults to `label`
for `--border-label`)
- Better support for (Windows) terminals where each box-drawing character
takes 2 columns. Set `RUNEWIDTH_EASTASIAN` environment variable to `0` or `1`.
- On Vim, the variable will be automatically set if `&ambiwidth` is `double`
- Behavior changes
- fzf will always execute the preview command if the command template
contains `{q}` even when it's empty. If you prefer the old behavior,
you'll have to check if `{q}` is empty in your command.
```sh
# This will show // even when the query is empty
: | fzf --preview 'echo /{q}/'
# But if you don't want it,
: | fzf --preview '[ -n {q} ] || exit; echo /{q}/'
```
- `double-click` will behave the same as `enter` unless otherwise specified,
so you don't have to repeat the same action twice in `--bind` in most cases.
```sh
# No need to bind 'double-click' to the same action
fzf --bind 'enter:execute:less {}' # --bind 'double-click:execute:less {}'
```
- If the color for `separator` is not specified, it will default to the
color for `border`. Same holds true for `scrollbar`. This is to reduce
the number of configuration items required to achieve a consistent color
scheme.
- If `follow` flag is specified in `--preview-window` option, fzf will
automatically scroll to the bottom of the streaming preview output. But
when the user manually scrolls the window, the following stops. With
this version, fzf will resume following if the user scrolls the window
to the bottom.
- Default border style on Windows is changed to `sharp` because some
Windows terminals are not capable of displaying `rounded` border
characters correctly.
- Minor bug fixes and improvements
0.35.1
------
- Fixed a bug where fzf with `--tiebreak=chunk` crashes on inverse match query
- Fixed a bug where clicking above fzf would paste escape sequences
0.35.0
------
- Added `start` event that is triggered only once when fzf finder starts.
Since fzf consumes the input stream asynchronously, the input list is not
available unless you use `--sync`.
```sh
seq 100 | fzf --multi --sync --bind 'start:last+select-all+preview(echo welcome)'
```
- Added `--border-label` and `--border-label-pos` for putting label on the border
```sh
# ANSI color codes are supported
# (with https://github.com/busyloop/lolcat)
label=$(curl -s http://metaphorpsum.com/sentences/1 | lolcat -f)
# Border label at the center
fzf --height=10 --border --border-label="╢ $label ╟" --color=label:italic:black
# Left-aligned (positive integer)
fzf --height=10 --border --border-label="╢ $label ╟" --border-label-pos=3 --color=label:italic:black
# Right-aligned (negative integer) on the bottom line (:bottom)
fzf --height=10 --border --border-label="╢ $label ╟" --border-label-pos=-3:bottom --color=label:italic:black
```
- Also added `--preview-label` and `--preview-label-pos` for the border of the
preview window
```sh
fzf --preview 'cat {}' --border --preview-label=' Preview ' --preview-label-pos=2
```
- Info panel (match counter) will be followed by a horizontal separator by
default
- Use `--no-separator` or `--separator=''` to hide the separator
- You can specify an arbitrary string that is repeated to form the
horizontal separator. e.g. `--separator=╸`
- The color of the separator can be customized via `--color=separator:...`
- ANSI color codes are also supported
```sh
fzf --separator=╸ --color=separator:green
fzf --separator=$(lolcat -f -F 1.4 <<< ▁▁▂▃▄▅▆▆▅▄▃▂▁▁) --info=inline
```
- Added `--border=bold` and `--border=double` along with
`--preview-window=border-bold` and `--preview-window=border-double`
0.34.0
------
- Added support for adaptive `--height`. If the `--height` value is prefixed
with `~`, fzf will automatically determine the height in the range according
to the input size.
```sh
seq 1 | fzf --height ~70% --border --padding 1 --margin 1
seq 10 | fzf --height ~70% --border --padding 1 --margin 1
seq 100 | fzf --height ~70% --border --padding 1 --margin 1
```
- There are a few limitations
- Not compatible with percent top/bottom margin/padding
```sh
# This is not allowed (top/bottom margin in percent value)
fzf --height ~50% --border --margin 5%,10%
# This is allowed (top/bottom margin in fixed value)
fzf --height ~50% --border --margin 2,10%
```
- fzf will not start until it can determine the right height for the input
```sh
# fzf will open immediately
(sleep 2; seq 10) | fzf --height 50%
# fzf will open after 2 seconds
(sleep 2; seq 10) | fzf --height ~50%
(sleep 2; seq 1000) | fzf --height ~50%
```
- Fixed tcell renderer used to render full-screen fzf on Windows
- ~~`--no-clear` is deprecated. Use `reload` action instead.~~
0.33.0
------
- Added `--scheme=[default|path|history]` option to choose scoring scheme
- (Experimental)
- We updated the scoring algorithm in 0.32.0, however we have learned that
this new scheme (`default`) is not always giving the optimal result
- `path`: Additional bonus point is only given to the characters after
path separator. You might want to choose this scheme if you have many
files with spaces in their paths.
- `history`: No additional bonus points are given so that we give more
weight to the chronological ordering. This is equivalent to the scoring
scheme before 0.32.0. This also sets `--tiebreak=index`.
- ANSI color sequences with colon delimiters are now supported.
```sh
printf "\e[38;5;208mOption 1\e[m\nOption 2" | fzf --ansi
printf "\e[38:5:208mOption 1\e[m\nOption 2" | fzf --ansi
```
- Support `border-{up,down}` as the synonyms for `border-{top,bottom}` in
`--preview-window`
- Added support for ANSI `strikethrough`
```sh
printf "\e[9mdeleted" | fzf --ansi
fzf --color fg+:strikethrough
```
0.32.1
------
- Fixed incorrect ordering of `--tiebreak=chunk`
- fzf-tmux will show fzf border instead of tmux popup border (requires tmux 3.3)
```sh
fzf-tmux -p70%
fzf-tmux -p70% --color=border:bright-red
fzf-tmux -p100%,60% --color=border:bright-yellow --border=horizontal --padding 1,5 --margin 1,0
fzf-tmux -p70%,100% --color=border:bright-green --border=vertical
# Key bindings (CTRL-T, CTRL-R, ALT-C) will use these options
export FZF_TMUX_OPTS='-p100%,60% --color=border:green --border=horizontal --padding 1,5 --margin 1,0'
```
0.32.0
------
- Updated the scoring algorithm
- Different bonus points to different categories of word boundaries
(listed higher to lower bonus point)
- Word after whitespace characters or beginning of the string
- Word after common delimiter characters (`/,:;|`)
- Word after other non-word characters
```sh
# foo/bar.sh` is preferred over `foo-bar.sh` on `bar`
fzf --query=bar --height=4 << EOF
foo-bar.sh
foo/bar.sh
EOF
```
- Added a new tiebreak `chunk`
- Favors the line with shorter matched chunk. A chunk is a set of
consecutive non-whitespace characters.
- Unlike the default `length`, this scheme works well with tabular input
```sh
# length prefers item #1, because the whole line is shorter,
# chunk prefers item #2, because the matched chunk ("foo") is shorter
fzf --height=6 --header-lines=2 --tiebreak=chunk --reverse --query=fo << "EOF"
N | Field1 | Field2 | Field3
- | ------ | ------ | ------
1 | hello | foobar | baz
2 | world | foo | bazbaz
EOF
```
- If the input does not contain any spaces, `chunk` is equivalent to
`length`. But we're not going to set it as the default because it is
computationally more expensive.
- Bug fixes and improvements
0.31.0
------
- Added support for an alternative preview window layout that is activated
@@ -13,8 +724,8 @@ CHANGELOG
# Or you can just hide it like so
fzf --preview 'cat {}' --preview-window '<50(hidden)'
```
- Use SGR mouse mode to support larger terminals
- You can now use characters that does not satisfy `unicode.IsGraphic` constraint
- fzf now uses SGR mouse mode to properly support mouse on larger terminals
- You can now use characters that do not satisfy `unicode.IsGraphic` constraint
for `--marker`, `--pointer`, and `--ellipsis`. Allows Nerd Fonts and stuff.
Use at your own risk.
- Bug fixes and improvements

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ GOOS ?= $(word 1, $(subst /, " ", $(word 4, $(shell go version))))
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
ROOT_DIR := $(shell dirname $(MAKEFILE))
SOURCES := $(wildcard *.go src/*.go src/*/*.go) $(MAKEFILE)
SOURCES := $(wildcard *.go src/*.go src/*/*.go shell/*sh) $(MAKEFILE)
ifdef FZF_VERSION
VERSION := $(FZF_VERSION)
@@ -20,7 +20,7 @@ VERSION_REGEX := $(subst .,\.,$(VERSION_TRIM))
ifdef FZF_REVISION
REVISION := $(FZF_REVISION)
else
REVISION := $(shell git log -n 1 --pretty=format:%h -- $(SOURCES) 2> /dev/null)
REVISION := $(shell git log -n 1 --pretty=format:%h --abbrev=8 -- $(SOURCES) 2> /dev/null)
endif
ifeq ($(REVISION),)
$(error Not on git repository; cannot determine $$FZF_REVISION)
@@ -29,6 +29,7 @@ BUILD_FLAGS := -a -ldflags "-s -w -X main.version=$(VERSION) -X main.revision
BINARY32 := fzf-$(GOOS)_386
BINARY64 := fzf-$(GOOS)_amd64
BINARYS390 := fzf-$(GOOS)_s390x
BINARYARM5 := fzf-$(GOOS)_arm5
BINARYARM6 := fzf-$(GOOS)_arm6
BINARYARM7 := fzf-$(GOOS)_arm7
@@ -43,6 +44,8 @@ ifeq ($(UNAME_M),x86_64)
BINARY := $(BINARY64)
else ifeq ($(UNAME_M),amd64)
BINARY := $(BINARY64)
else ifeq ($(UNAME_M),s390x)
BINARY := $(BINARYS390)
else ifeq ($(UNAME_M),i686)
BINARY := $(BINARY32)
else ifeq ($(UNAME_M),i386)
@@ -54,7 +57,9 @@ else ifeq ($(UNAME_M),armv6l)
else ifeq ($(UNAME_M),armv7l)
BINARY := $(BINARYARM7)
else ifeq ($(UNAME_M),armv8l)
BINARY := $(BINARYARM8)
# armv8l is always 32-bit and should implement the armv7 ISA, so
# just use the same filename as for armv7.
BINARY := $(BINARYARM7)
else ifeq ($(UNAME_M),arm64)
BINARY := $(BINARYARM8)
else ifeq ($(UNAME_M),aarch64)
@@ -84,10 +89,17 @@ bench:
install: bin/fzf
generate:
PATH=$(PATH):$(GOPATH)/bin $(GO) generate ./...
build:
goreleaser --rm-dist --snapshot
goreleaser build --clean --snapshot --skip=post-hooks
release:
# Make sure that the tests pass and the build works
TAGS=tcell make test
make test build clean
ifndef GITHUB_TOKEN
$(error GITHUB_TOKEN is not defined)
endif
@@ -114,7 +126,7 @@ endif
git push origin temp --follow-tags --force
# Make a GitHub release
goreleaser --rm-dist --release-notes tmp/release-note
goreleaser --clean --release-notes tmp/release-note
# Push to master
git checkout master
@@ -132,6 +144,8 @@ target/$(BINARY32): $(SOURCES)
target/$(BINARY64): $(SOURCES)
GOARCH=amd64 $(GO) build $(BUILD_FLAGS) -o $@
target/$(BINARYS390): $(SOURCES)
GOARCH=s390x $(GO) build $(BUILD_FLAGS) -o $@
# https://github.com/golang/go/wiki/GoArm
target/$(BINARYARM5): $(SOURCES)
GOARCH=arm GOARM=5 $(GO) build $(BUILD_FLAGS) -o $@
@@ -155,6 +169,7 @@ target/$(BINARYLOONG64): $(SOURCES)
GOARCH=loong64 $(GO) build $(BUILD_FLAGS) -o $@
bin/fzf: target/$(BINARY) | bin
-rm -f bin/fzf
cp -f target/$(BINARY) bin/fzf
docker:
@@ -169,4 +184,4 @@ update:
$(GO) get -u
$(GO) mod tidy
.PHONY: all build release test bench install clean docker docker-test update
.PHONY: all generate build release test bench install clean docker docker-test update

View File

@@ -12,7 +12,10 @@ differ depending on the package manager.
" If installed using Homebrew
set rtp+=/usr/local/opt/fzf
" If installed using git
" If installed using Homebrew on Apple Silicon
set rtp+=/opt/homebrew/opt/fzf
" If you have cloned fzf on ~/.fzf directory
set rtp+=~/.fzf
```
@@ -23,7 +26,10 @@ written as:
" If installed using Homebrew
Plug '/usr/local/opt/fzf'
" If installed using git
" If installed using Homebrew on Apple Silicon
Plug '/opt/homebrew/opt/fzf'
" If you have cloned fzf on ~/.fzf directory
Plug '~/.fzf'
```
@@ -115,7 +121,7 @@ let g:fzf_action = {
" An action can be a reference to a function that processes selected lines
function! s:build_quickfix_list(lines)
call setqflist(map(copy(a:lines), '{ "filename": v:val }'))
call setqflist(map(copy(a:lines), '{ "filename": v:val, "lnum": 1 }'))
copen
cc
endfunction
@@ -232,19 +238,20 @@ call fzf#run({'sink': 'e'})
```
We haven't specified the `source`, so this is equivalent to starting fzf on
command line without standard input pipe; fzf will use find command (or
`$FZF_DEFAULT_COMMAND` if defined) to list the files under the current
directory. When you select one, it will open it with the sink, `:e` command.
If you want to open it in a new tab, you can pass `:tabedit` command instead
as the sink.
command line without standard input pipe; fzf will traverse the file system
under the current directory to get the list of files. (If
`$FZF_DEFAULT_COMMAND` is set, fzf will use the output of the command
instead.) When you select one, it will open it with the sink, `:e` command. If
you want to open it in a new tab, you can pass `:tabedit` command instead as
the sink.
```vim
call fzf#run({'sink': 'tabedit'})
```
Instead of using the default find command, you can use any shell command as
the source. The following example will list the files managed by git. It's
equivalent to running `git ls-files | fzf` on shell.
You can use any shell command as the source to generate the list. The
following example will list the files managed by git. It's equivalent to
running `git ls-files | fzf` on shell.
```vim
call fzf#run({'source': 'git ls-files', 'sink': 'e'})
@@ -309,7 +316,7 @@ following options are allowed:
- `yoffset` [float default 0.5 range [0 ~ 1]]
- `xoffset` [float default 0.5 range [0 ~ 1]]
- `relative` [boolean default v:false]
- `border` [string default `rounded`]: Border style
- `border` [string default `rounded` (`sharp` on Windows)]: Border style
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right` / `no[ne]`
`fzf#wrap`
@@ -483,4 +490,4 @@ autocmd FileType fzf set laststatus=0 noshowmode noruler
The MIT License (MIT)
Copyright (c) 2013-2021 Junegunn Choi
Copyright (c) 2013-2024 Junegunn Choi

405
README.md

File diff suppressed because one or more lines are too long

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

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

View File

@@ -10,7 +10,6 @@ fail() {
fzf="$(command -v fzf 2> /dev/null)" || fzf="$(dirname "$0")/fzf"
[[ -x "$fzf" ]] || fail 'fzf executable not found'
tmux_args=()
args=()
opt=""
skip=""
@@ -58,7 +57,7 @@ while [[ $# -gt 0 ]]; do
;;
-p*|-w*|-h*|-x*|-y*|-d*|-u*|-r*|-l*)
if [[ "$arg" =~ ^-[pwhxy] ]]; then
[[ "$opt" =~ "-K -E" ]] || opt="-K -E"
[[ "$opt" =~ "-E" ]] || opt="-E"
elif [[ "$arg" =~ ^.[lr] ]]; then
opt="-h"
if [[ "$arg" =~ ^.l ]]; then
@@ -96,9 +95,9 @@ while [[ $# -gt 0 ]]; do
elif [[ "$size" =~ %$ ]]; then
size=${size:0:((${#size}-1))}
if [[ -n "$swap" ]]; then
opt="$opt -p $(( 100 - size ))"
opt="$opt -l $(( 100 - size ))%"
else
opt="$opt -p $size"
opt="$opt -l $size%"
fi
else
if [[ -n "$swap" ]]; then
@@ -119,8 +118,6 @@ while [[ $# -gt 0 ]]; do
# "--" can be used to separate fzf-tmux options from fzf options to
# avoid conflicts
skip=1
tmux_args=("${args[@]}")
args=()
continue
;;
*)
@@ -139,7 +136,7 @@ fi
args=("${args[@]}" "--no-height" "--bind=ctrl-z:ignore")
# Handle zoomed tmux pane without popup options by moving it to a temp window
if [[ ! "$opt" =~ "-K -E" ]] && tmux list-panes -F '#F' | grep -q Z; then
if [[ ! "$opt" =~ "-E" ]] && tmux list-panes -F '#F' | grep -q Z; then
zoomed_without_popup=1
original_window=$(tmux display-message -p "#{window_id}")
tmp_window=$(tmux new-window -d -P -F "#{window_id}" "bash -c 'while :; do for c in \\| / - '\\;' do sleep 0.2; printf \"\\r\$c fzf-tmux is running\\r\"; done; done'")
@@ -154,12 +151,18 @@ argsf="${TMPDIR:-/tmp}/fzf-args-$id"
fifo1="${TMPDIR:-/tmp}/fzf-fifo1-$id"
fifo2="${TMPDIR:-/tmp}/fzf-fifo2-$id"
fifo3="${TMPDIR:-/tmp}/fzf-fifo3-$id"
tmux_win_opts=( $(tmux show-window-options remain-on-exit \; show-window-options synchronize-panes | sed '/ off/d; s/^/set-window-option /; s/$/ \\;/') )
if tmux_win_opts=$(tmux show-options -p remain-on-exit \; show-options -p synchronize-panes 2> /dev/null); then
tmux_win_opts=( $(sed '/ off/d; s/synchronize-panes/set-option -p synchronize-panes/; s/remain-on-exit/set-option -p remain-on-exit/; s/$/ \\;/' <<< "$tmux_win_opts") )
tmux_off_opts='; set-option -p synchronize-panes off ; set-option -p remain-on-exit off'
else
tmux_win_opts=( $(tmux show-window-options remain-on-exit \; show-window-options synchronize-panes | sed '/ off/d; s/^/set-window-option /; s/$/ \\;/') )
tmux_off_opts='; set-window-option synchronize-panes off ; set-window-option remain-on-exit off'
fi
cleanup() {
\rm -f $argsf $fifo1 $fifo2 $fifo3
# Restore tmux window options
if [[ "${#tmux_win_opts[@]}" -gt 0 ]]; then
if [[ "${#tmux_win_opts[@]}" -gt 1 ]]; then
eval "tmux ${tmux_win_opts[*]}"
fi
@@ -181,9 +184,23 @@ trap 'cleanup 1' SIGUSR1
trap 'cleanup' EXIT
envs="export TERM=$TERM "
[[ "$opt" =~ "-K -E" ]] && FZF_DEFAULT_OPTS="--margin 0,1 $FZF_DEFAULT_OPTS"
[[ -n "$FZF_DEFAULT_OPTS" ]] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
[[ -n "$FZF_DEFAULT_COMMAND" ]] && envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
if [[ "$opt" =~ "-E" ]]; then
tmux_version=$(tmux -V | sed 's/[^0-9.]//g')
if [[ $(awk '{print ($1 > 3.2)}' <<< "$tmux_version" 2> /dev/null || bc -l <<< "$tmux_version > 3.2") = 1 ]]; then
FZF_DEFAULT_OPTS="--border $FZF_DEFAULT_OPTS"
opt="-B $opt"
elif [[ $tmux_version = 3.2 ]]; then
FZF_DEFAULT_OPTS="--margin 0,1 $FZF_DEFAULT_OPTS"
else
echo "fzf-tmux: tmux 3.2 or above is required for popup mode" >&2
exit 2
fi
fi
envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
envs="$envs FZF_DEFAULT_OPTS_FILE=$(printf %q "$FZF_DEFAULT_OPTS_FILE")"
[[ -n "$RUNEWIDTH_EASTASIAN" ]] && envs="$envs RUNEWIDTH_EASTASIAN=$(printf %q "$RUNEWIDTH_EASTASIAN")"
[[ -n "$BAT_THEME" ]] && envs="$envs BAT_THEME=$(printf %q "$BAT_THEME")"
echo "$envs;" > "$argsf"
# Build arguments to fzf
@@ -195,7 +212,7 @@ close="; trap - EXIT SIGINT SIGTERM $close"
export TMUX=$(cut -d , -f 1,2 <<< "$TMUX")
mkfifo -m o+w $fifo2
if [[ "$opt" =~ "-K -E" ]]; then
if [[ "$opt" =~ "-E" ]]; then
cat $fifo2 &
if [[ -n "$term" ]] || [[ -t 0 ]]; then
cat <<< "\"$fzf\" $opts > $fifo2; out=\$? $close; exit \$out" >> $argsf
@@ -205,15 +222,7 @@ if [[ "$opt" =~ "-K -E" ]]; then
cat <&0 > $fifo1 &
fi
# tmux dropped the support for `-K`, `-R` to popup command
# TODO: We can remove this once tmux 3.2 is released
if [[ ! "$(tmux popup --help 2>&1)" =~ '-R shell-command' ]]; then
opt="${opt/-K/}"
else
opt="${opt} -R"
fi
tmux popup -d "$PWD" "${tmux_args[@]}" $opt "bash $argsf" > /dev/null 2>&1
tmux popup -d "$PWD" $opt "bash $argsf" > /dev/null 2>&1
exit $?
fi
@@ -225,9 +234,9 @@ else
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; echo \$? > $fifo3 $close" >> $argsf
cat <&0 > $fifo1 &
fi
tmux set-window-option synchronize-panes off \;\
set-window-option remain-on-exit off \;\
split-window -c "$PWD" $opt "${tmux_args[@]}" "bash -c 'exec -a fzf bash $argsf'" $swap \
tmux \
split-window -c "$PWD" $opt "bash -c 'exec -a fzf bash $argsf'" $swap \
$tmux_off_opts \
> /dev/null 2>&1 || { "$fzf" "${args[@]}"; exit $?; }
cat $fifo2
exit "$(cat $fifo3)"

View File

@@ -1,4 +1,4 @@
fzf.txt fzf Last change: May 19 2021
fzf.txt fzf Last change: February 15 2024
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
==============================================================================
@@ -32,7 +32,10 @@ depending on the package manager.
" If installed using Homebrew
set rtp+=/usr/local/opt/fzf
" If installed using git
" If installed using Homebrew on Apple Silicon
set rtp+=/opt/homebrew/opt/fzf
" If you have cloned fzf on ~/.fzf directory
set rtp+=~/.fzf
<
If you use {vim-plug}{1}, the same can be written as:
@@ -40,7 +43,10 @@ If you use {vim-plug}{1}, the same can be written as:
" If installed using Homebrew
Plug '/usr/local/opt/fzf'
" If installed using git
" If installed using Homebrew on Apple Silicon
Plug '/opt/homebrew/opt/fzf'
" If you have cloned fzf on ~/.fzf directory
Plug '~/.fzf'
<
But if you want the latest Vim plugin file from GitHub rather than the one
@@ -68,16 +74,16 @@ SUMMARY *fzf-summary*
The Vim plugin of fzf provides two core functions, and `:FZF` command which is
the basic file selector command built on top of them.
1. `fzf#run([specdict])`
1. `fzf#run([spec dict])`
- Starts fzf inside Vim with the given spec
- `:callfzf#run({'source':'ls'})`
2. `fzf#wrap([specdict])->(dict)`
- `:call fzf#run({'source': 'ls'})`
2. `fzf#wrap([spec dict]) -> (dict)`
- Takes a spec for `fzf#run` and returns an extended version of it with
additional options for addressing global preferences (`g:fzf_xxx`)
- `:echofzf#wrap({'source':'ls'})`
- `:echo fzf#wrap({'source': 'ls'})`
- We usually wrap a spec with `fzf#wrap` before passing it to `fzf#run`
- `:callfzf#run(fzf#wrap({'source':'ls'}))`
3. `:FZF[fzf_optionsstring][pathstring]`
- `:call fzf#run(fzf#wrap({'source': 'ls'}))`
3. `:FZF [fzf_options string] [path string]`
- Basic fuzzy file selector
- A reference implementation for those who don't want to write VimScript to
implement custom commands
@@ -143,7 +149,7 @@ Examples~
" An action can be a reference to a function that processes selected lines
function! s:build_quickfix_list(lines)
call setqflist(map(copy(a:lines), '{ "filename": v:val }'))
call setqflist(map(copy(a:lines), '{ "filename": v:val, "lnum": 1 }'))
copen
cc
endfunction
@@ -222,12 +228,12 @@ list:
`spinner` | Streaming input indicator
`query` | Query string
`disabled` | Query string when search is disabled
`prompt` | Prompt before query ( `>` )
`prompt` | Prompt before query ( `> ` )
`pointer` | Pointer to the current line ( `>` )
----------------------------+------------------------------------------------------
- `component` specifies the component (`fg` / `bg`) from which to extract the
color when considering each of the following highlight groups
- `group1[,group2,...]` is a list of highlight groups that are searched (in
- `group1 [, group2, ...]` is a list of highlight groups that are searched (in
order) for a matching color definition
For example, consider the following specification:
@@ -258,17 +264,18 @@ entry.
call fzf#run({'sink': 'e'})
<
We haven't specified the `source`, so this is equivalent to starting fzf on
command line without standard input pipe; fzf will use find command (or
`$FZF_DEFAULT_COMMAND` if defined) to list the files under the current
directory. When you select one, it will open it with the sink, `:e` command.
If you want to open it in a new tab, you can pass `:tabedit` command instead
as the sink.
command line without standard input pipe; fzf will traverse the file system
under the current directory to get the list of files. (If
`$FZF_DEFAULT_COMMAND` is set, fzf will use the output of the command
instead.) When you select one, it will open it with the sink, `:e` command. If
you want to open it in a new tab, you can pass `:tabedit` command instead as
the sink.
>
call fzf#run({'sink': 'tabedit'})
<
Instead of using the default find command, you can use any shell command as
the source. The following example will list the files managed by git. It's
equivalent to running `gitls-files|fzf` on shell.
You can use any shell command as the source to generate the list. The
following example will list the files managed by git. It's equivalent to
running `git ls-files | fzf` on shell.
>
call fzf#run({'source': 'git ls-files', 'sink': 'e'})
<
@@ -296,7 +303,7 @@ The following table summarizes the available options.
---------------------------+---------------+----------------------------------------------------------------------
Option name | Type | Description ~
---------------------------+---------------+----------------------------------------------------------------------
`source` | string | External command to generate input to fzf (e.g. `find.` )
`source` | string | External command to generate input to fzf (e.g. `find .` )
`source` | list | Vim list as input to fzf
`sink` | string | Vim command to handle the selected item (e.g. `e` , `tabe` )
`sink` | funcref | Reference to function to process each selected item
@@ -305,8 +312,8 @@ The following table summarizes the available options.
`dir` | string | Working directory
`up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` )
`tmux` | string | (Layout) fzf-tmux options (e.g. `-p90%,60%` )
`window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `verticalaboveleft30new` )
`window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width':0.9,'height':0.6}` )
`window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new` )
`window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width': 0.9, 'height': 0.6}` )
---------------------------+---------------+----------------------------------------------------------------------
`options` entry can be either a string or a list. For simple cases, string
@@ -325,7 +332,7 @@ following options are allowed:
- `yoffset` [float default 0.5 range [0 ~ 1]]
- `xoffset` [float default 0.5 range [0 ~ 1]]
- `relative` [boolean default v:false]
- `border` [string default `rounded`]: Border style
- `border` [string default `rounded` (`sharp` on Windows)]: Border style
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right` / `no[ne]`
@@ -343,7 +350,7 @@ So how can we make our custom `fzf#run` calls also respect those variables?
Simply by "wrapping" the spec dictionary with `fzf#wrap` before passing it to
`fzf#run`.
- `fzf#wrap([namestring],[specdict],[fullscreenbool])->(dict)`
- `fzf#wrap([name string], [spec dict], [fullscreen bool]) -> (dict)`
- All arguments are optional. Usually we only need to pass a spec
dictionary.
- `name` is for managing history files. It is ignored if `g:fzf_history_dir`
@@ -377,7 +384,7 @@ last `fullscreen` argument of `fzf#wrap` (see :help <bang>).
command! -bang LS call fzf#run(fzf#wrap({'source': 'ls'}, <bang>0))
<
Our `:LS` command will be much more useful if we can pass a directory argument
to it, so that something like `:LS/tmp` is possible.
to it, so that something like `:LS /tmp` is possible.
>
command! -bang -complete=dir -nargs=? LS
\ call fzf#run(fzf#wrap({'source': 'ls', 'dir': <q-args>}, <bang>0))
@@ -396,10 +403,10 @@ unique name to our command and pass it as the first argument to `fzf#wrap`.
- `g:fzf_layout`
- `g:fzf_action`
- Works only when no custom `sink` (or `sink*`) is provided
- Works only when no custom `sink` (or `sinklist`) is provided
- Having custom sink usually means that each entry is not an ordinary
file path (e.g. name of color scheme), so we can't blindly apply the
same strategy (i.e. `tabeditsome-color-scheme` doesn't make sense)
same strategy (i.e. `tabedit some-color-scheme` doesn't make sense)
- `g:fzf_colors`
- `g:fzf_history_dir`
@@ -411,24 +418,12 @@ TIPS *fzf-tips*
< fzf inside terminal buffer >________________________________________________~
*fzf-inside-terminal-buffer*
The latest versions of Vim and Neovim include builtin terminal emulator
(`:terminal`) and fzf will start in a terminal buffer in the following cases:
- On Neovim
- On GVim
- On Terminal Vim with a non-default layout
- `callfzf#run({'left':'30%'})` or `letg:fzf_layout={'left':'30%'}`
On the latest versions of Vim and Neovim, fzf will start in a terminal buffer.
If you find the default ANSI colors to be different, consider configuring the
colors using `g:terminal_ansi_colors` in regular Vim or `g:terminal_color_x`
in Neovim.
*g:terminal_color_15* *g:terminal_color_14* *g:terminal_color_13*
*g:terminal_color_12* *g:terminal_color_11* *g:terminal_color_10* *g:terminal_color_9*
*g:terminal_color_8* *g:terminal_color_7* *g:terminal_color_6* *g:terminal_color_5*
*g:terminal_color_4* *g:terminal_color_3* *g:terminal_color_2* *g:terminal_color_1*
*g:terminal_color_0*
>
" Terminal colors for seoul256 color scheme
if has('nvim')
@@ -488,7 +483,7 @@ or above) by putting fzf-tmux options in `tmux` key.
*fzf-hide-statusline*
When fzf starts in a terminal buffer, the file type of the buffer is set to
`fzf`. So you can set up `FileTypefzf` autocmd to customize the settings of
`fzf`. So you can set up `FileType fzf` autocmd to customize the settings of
the window.
For example, if you open fzf on the bottom on the screen (e.g. `{'down':
@@ -506,7 +501,7 @@ LICENSE *fzf-license*
The MIT License (MIT)
Copyright (c) 2013-2021 Junegunn Choi
Copyright (c) 2013-2024 Junegunn Choi
==============================================================================
vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap:

17
go.mod
View File

@@ -1,21 +1,20 @@
module github.com/junegunn/fzf
require (
github.com/gdamore/tcell v1.4.0
github.com/mattn/go-isatty v0.0.14
github.com/mattn/go-runewidth v0.0.13
github.com/charlievieth/fastwalk v1.0.2
github.com/gdamore/tcell/v2 v2.7.4
github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-shellwords v1.0.12
github.com/rivo/uniseg v0.2.0
github.com/saracen/walker v0.1.2
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
github.com/rivo/uniseg v0.4.7
golang.org/x/sys v0.18.0
golang.org/x/term v0.18.0
)
require (
github.com/gdamore/encoding v1.0.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/text v0.3.7 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
golang.org/x/text v0.14.0 // indirect
)
go 1.17

65
go.sum
View File

@@ -1,32 +1,57 @@
github.com/charlievieth/fastwalk v1.0.2 h1:KYWo7xszmoldOGrwdNIeznSzhj9mhgk+6DwHunG99bc=
github.com/charlievieth/fastwalk v1.0.2/go.mod h1:JSfglY/gmL/rqsUS1NCsJTocB5n6sSl9ApAqif4CUbs=
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/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU=
github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
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/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
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-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
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 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/saracen/walker v0.1.2 h1:/o1TxP82n8thLvmL4GpJXduYaRmJ7qXp8u9dSlV0zmo=
github.com/saracen/walker v0.1.2/go.mod h1:0oKYMsKVhSJ+ful4p/XbjvXbMgLEkLITZaxozsl4CGE=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12 h1:QyVthZKMsyaQwBTJE04jdNN0Pp5Fn9Qga0mrgxyERQM=
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.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.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.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.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

75
install
View File

@@ -2,7 +2,7 @@
set -u
version=0.30.0
version=0.48.1
auto_completion=
key_bindings=
update_config=2
@@ -168,21 +168,24 @@ archi=$(uname -sm)
binary_available=1
binary_error=""
case "$archi" in
Darwin\ arm64) download fzf-$version-darwin_arm64.zip ;;
Darwin\ x86_64) download fzf-$version-darwin_amd64.zip ;;
Linux\ armv5*) download fzf-$version-linux_armv5.tar.gz ;;
Linux\ armv6*) download fzf-$version-linux_armv6.tar.gz ;;
Linux\ armv7*) download fzf-$version-linux_armv7.tar.gz ;;
Linux\ armv8*) download fzf-$version-linux_arm64.tar.gz ;;
Linux\ aarch64*) download fzf-$version-linux_arm64.tar.gz ;;
Linux\ *64) download fzf-$version-linux_amd64.tar.gz ;;
FreeBSD\ *64) download fzf-$version-freebsd_amd64.tar.gz ;;
OpenBSD\ *64) download fzf-$version-openbsd_amd64.tar.gz ;;
CYGWIN*\ *64) download fzf-$version-windows_amd64.zip ;;
MINGW*\ *64) download fzf-$version-windows_amd64.zip ;;
MSYS*\ *64) download fzf-$version-windows_amd64.zip ;;
Windows*\ *64) download fzf-$version-windows_amd64.zip ;;
*) binary_available=0 binary_error=1 ;;
Darwin\ arm64) download fzf-$version-darwin_arm64.zip ;;
Darwin\ x86_64) download fzf-$version-darwin_amd64.zip ;;
Linux\ armv5*) download fzf-$version-linux_armv5.tar.gz ;;
Linux\ armv6*) download fzf-$version-linux_armv6.tar.gz ;;
Linux\ armv7*) download fzf-$version-linux_armv7.tar.gz ;;
Linux\ armv8*) download fzf-$version-linux_arm64.tar.gz ;;
Linux\ aarch64*) download fzf-$version-linux_arm64.tar.gz ;;
Linux\ loongarch64) download fzf-$version-linux_loong64.tar.gz ;;
Linux\ ppc64le) download fzf-$version-linux_ppc64le.tar.gz ;;
Linux\ *64) download fzf-$version-linux_amd64.tar.gz ;;
Linux\ s390x) download fzf-$version-linux_s390x.tar.gz ;;
FreeBSD\ *64) download fzf-$version-freebsd_amd64.tar.gz ;;
OpenBSD\ *64) download fzf-$version-openbsd_amd64.tar.gz ;;
CYGWIN*\ *64) download fzf-$version-windows_amd64.zip ;;
MINGW*\ *64) download fzf-$version-windows_amd64.zip ;;
MSYS*\ *64) download fzf-$version-windows_amd64.zip ;;
Windows*\ *64) download fzf-$version-windows_amd64.zip ;;
*) binary_available=0 binary_error=1 ;;
esac
cd "$fzf_base"
@@ -193,12 +196,12 @@ if [ -n "$binary_error" ]; then
echo " - $binary_error !!!"
fi
if command -v go > /dev/null; then
echo -n "Building binary (go get -u github.com/junegunn/fzf) ... "
echo -n "Building binary (go install github.com/junegunn/fzf) ... "
if [ -z "${GOPATH-}" ]; then
export GOPATH="${TMPDIR:-/tmp}/fzf-gopath"
mkdir -p "$GOPATH"
fi
if go get -ldflags "-s -w -X main.version=$version -X main.revision=go-get" github.com/junegunn/fzf; then
if go install -ldflags "-s -w -X main.version=$version -X main.revision=go-install" github.com/junegunn/fzf; then
echo "OK"
cp "$GOPATH/bin/fzf" "$fzf_base/bin/"
else
@@ -242,7 +245,7 @@ for shell in $shells; do
src=${prefix_expand}.${shell}
echo -n "Generate $src ... "
fzf_completion="[[ \$- == *i* ]] && source \"$fzf_base/shell/completion.${shell}\" 2> /dev/null"
fzf_completion="source \"$fzf_base/shell/completion.${shell}\""
if [ $auto_completion -eq 0 ]; then
fzf_completion="# $fzf_completion"
fi
@@ -259,6 +262,12 @@ if [[ ! "\$PATH" == *$fzf_base_esc/bin* ]]; then
PATH="\${PATH:+\${PATH}:}$fzf_base/bin"
fi
EOF
if [[ $auto_completion -eq 1 ]] && [[ $key_bindings -eq 1 ]]; then
echo "eval \"\$(fzf --$shell)\"" >> "$src"
else
cat >> "$src" << EOF
# Auto-completion
# ---------------
$fzf_completion
@@ -267,6 +276,7 @@ $fzf_completion
# ------------
$fzf_key_bindings
EOF
fi
echo "OK"
done
@@ -278,18 +288,6 @@ if [[ "$shells" =~ fish ]]; then
or set --universal fish_user_paths \$fish_user_paths "$fzf_base"/bin
EOF
[ $? -eq 0 ] && echo "OK" || echo "Failed"
mkdir -p "${fish_dir}/functions"
fish_binding="${fish_dir}/functions/fzf_key_bindings.fish"
if [ $key_bindings -ne 0 ]; then
echo -n "Symlink $fish_binding ... "
ln -sf "$fzf_base/shell/key-bindings.fish" \
"$fish_binding" && echo "OK" || echo "Failed"
else
echo -n "Removing $fish_binding ... "
rm -f "$fish_binding"
echo "OK"
fi
fi
append_line() {
@@ -352,12 +350,23 @@ done
if [ $key_bindings -eq 1 ] && [[ "$shells" =~ fish ]]; then
bind_file="${fish_dir}/functions/fish_user_key_bindings.fish"
if [ ! -e "$bind_file" ]; then
mkdir -p "${fish_dir}/functions"
create_file "$bind_file" \
'function fish_user_key_bindings' \
' fzf_key_bindings' \
' fzf --fish | source' \
'end'
else
append_line $update_config "fzf_key_bindings" "$bind_file"
echo "Check $bind_file:"
lno=$(\grep -nF "fzf_key_bindings" "$bind_file" | sed 's/:.*//' | tr '\n' ' ')
if [[ -n $lno ]]; then
echo " ** Found 'fzf_key_bindings' in line #$lno"
echo " ** You have to replace the line to 'fzf --fish | source'"
echo
else
echo " - Clear"
echo
append_line $update_config "fzf --fish | source" "$bind_file"
fi
fi
fi

View File

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

45
main.go
View File

@@ -1,14 +1,55 @@
package main
import (
_ "embed"
"fmt"
"strings"
fzf "github.com/junegunn/fzf/src"
"github.com/junegunn/fzf/src/protector"
)
var version string = "0.30"
var version string = "0.48"
var revision string = "devel"
//go:embed shell/key-bindings.bash
var bashKeyBindings []byte
//go:embed shell/completion.bash
var bashCompletion []byte
//go:embed shell/key-bindings.zsh
var zshKeyBindings []byte
//go:embed shell/completion.zsh
var zshCompletion []byte
//go:embed shell/key-bindings.fish
var fishKeyBindings []byte
func printScript(label string, content []byte) {
fmt.Println("### " + label + " ###")
fmt.Println(strings.TrimSpace(string(content)))
fmt.Println("### end: " + label + " ###")
}
func main() {
protector.Protect()
fzf.Run(fzf.ParseOptions(), version, revision)
options := fzf.ParseOptions()
if options.Bash {
printScript("key-bindings.bash", bashKeyBindings)
printScript("completion.bash", bashCompletion)
return
}
if options.Zsh {
printScript("key-bindings.zsh", zshKeyBindings)
printScript("completion.zsh", zshCompletion)
return
}
if options.Fish {
printScript("key-bindings.fish", fishKeyBindings)
fmt.Println("fzf_key_bindings")
return
}
fzf.Run(options, version, revision)
}

View File

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

View File

@@ -1,7 +1,7 @@
.ig
The MIT License (MIT)
Copyright (c) 2013-2021 Junegunn Choi
Copyright (c) 2013-2024 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
..
.TH fzf 1 "Jul 2022" "fzf 0.31.0" "fzf - a command-line fuzzy finder"
.TH fzf 1 "Mar 2024" "fzf 0.48.1" "fzf - a command-line fuzzy finder"
.SH NAME
fzf - a command-line fuzzy finder
@@ -33,6 +33,10 @@ fzf [options]
fzf is a general-purpose command-line fuzzy finder.
.SH OPTIONS
.SS Note
.TP
Most long options have the opposite version with \fB--no-\fR prefix.
.SS Search mode
.TP
.B "-x, --extended"
@@ -51,6 +55,18 @@ Case-sensitive match
.B "--literal"
Do not normalize latin script letters for matching.
.TP
.BI "--scheme=" SCHEME
Choose scoring scheme tailored for different types of input.
.br
.BR default " Generic scoring scheme designed to work well with any type of input"
.br
.BR path " Scoring scheme well suited for file paths
.br
.BR history " Scoring scheme well suited for command history or any input where chronological ordering is important
Sets \fB--tiebreak=index\fR as well.
.br
.TP
.BI "--algo=" TYPE
Fuzzy matching algorithm (default: v2)
@@ -80,6 +96,19 @@ interface rather than a "fuzzy finder". You can later enable the search using
.B "+s, --no-sort"
Do not sort the result
.TP
.B "--track"
Make fzf track the current selection when the result list is updated.
This can be useful when browsing logs using fzf with sorting disabled. It is
not recommended to use this option with \fB--tac\fR as the resulting behavior
can be confusing. Also, consider using \fBtrack\fR action instead of this
option.
.RS
e.g.
\fBgit log --oneline --graph --color=always | nl |
fzf --ansi --track --no-sort --layout=reverse-list\fR
.RE
.TP
.B "--tac"
Reverse the order of the input
@@ -95,6 +124,8 @@ Comma-separated list of sort criteria to apply when the scores are tied.
.br
.BR length " Prefers line with shorter length"
.br
.BR chunk " Prefers line with shorter matched chunk (delimited by whitespaces)"
.br
.BR begin " Prefers line with matched substring closer to the beginning"
.br
.BR end " Prefers line with matched substring closer to the end"
@@ -163,9 +194,23 @@ actions are affected:
Label characters for \fBjump\fR and \fBjump-accept\fR
.SS Layout
.TP
.BI "--height=" "HEIGHT[%]"
.BI "--height=" "[~]HEIGHT[%]"
Display fzf window below the cursor with the given height instead of using
the full screen.
If a negative value is specified, the height is calculated as the terminal
height minus the given value.
fzf --height=-1
When prefixed with \fB~\fR, fzf will automatically determine the height in the
range according to the input size. Note that adaptive height is not compatible
with top/bottom margin and padding given in percent size. It is also not
compatible with a negative height value.
# Will not take up 100% of the screen
seq 5 | fzf --height=~100%
.TP
.BI "--min-height=" "HEIGHT"
Minimum height when \fB--height\fR is given in percent (default: 10).
@@ -187,21 +232,29 @@ Choose the layout (default: default)
A synonym for \fB--layout=reverse\fB
.TP
.BI "--border" [=STYLE]
.BI "--border" [=BORDER_OPT]
Draw border around the finder
.br
.BR rounded " Border with rounded corners (default)"
.BR rounded " Border with rounded corners (default)"
.br
.BR sharp " Border with sharp corners"
.BR sharp " Border with sharp corners"
.br
.BR horizontal " Horizontal lines above and below the finder"
.BR bold " Border with bold lines"
.br
.BR vertical " Vertical lines on each side of the finder"
.BR double " Border with double lines"
.br
.BR top
.BR block " Border using block elements; suitable when using different background colors"
.br
.BR bottom
.BR thinblock " Border using legacy computing symbols; may not be displayed on some terminals"
.br
.BR horizontal " Horizontal lines above and below the finder"
.br
.BR vertical " Vertical lines on each side of the finder"
.br
.BR top " (up)"
.br
.BR bottom " (down)"
.br
.BR left
.br
@@ -210,9 +263,63 @@ Draw border around the finder
.BR none
.br
If you use a terminal emulator where each box-drawing character takes
2 columns, try setting \fB--ambidouble\fR. If the border is still not properly
rendered, set \fB--no-unicode\fR.
.TP
.BI "--border-label" [=LABEL]
Label to print on the horizontal border line. Should be used with one of the
following \fB--border\fR options.
.br
.B * rounded
.br
.B * sharp
.br
.B * bold
.br
.B * double
.br
.B * horizontal
.br
.BR "* top" " (up)"
.br
.BR "* bottom" " (down)"
.br
.br
e.g.
\fB# ANSI color codes are supported
# (with https://github.com/busyloop/lolcat)
label=$(curl -s http://metaphorpsum.com/sentences/1 | lolcat -f)
# Border label at the center
fzf --height=10 --border --border-label="╢ $label ╟" --color=label:italic:black
# Left-aligned (positive integer)
fzf --height=10 --border --border-label="╢ $label ╟" --border-label-pos=3 --color=label:italic:black
# Right-aligned (negative integer) on the bottom line (:bottom)
fzf --height=10 --border --border-label="╢ $label ╟" --border-label-pos=-3:bottom --color=label:italic:black\fR
.TP
.BI "--border-label-pos" [=N[:top|bottom]]
Position of the border label on the border line. Specify a positive integer as
the column position from the left. Specify a negative integer to right-align
the label. Label is printed on the top border line by default, add
\fB:bottom\fR to put it on the border line on the bottom. The default value
\fB0 (or \fBcenter\fR) will put the label at the center of the border line.
.TP
.B "--no-unicode"
Use ASCII characters instead of Unicode box drawing characters to draw border
Use ASCII characters instead of Unicode drawing characters to draw borders,
the spinner and the horizontal separator.
.TP
.B "--ambidouble"
Set this option if your terminal displays ambiguous width characters (e.g.
box-drawing characters for borders) as 2 columns.
.TP
.BI "--margin=" MARGIN
@@ -265,20 +372,48 @@ e.g.
.TP
.BI "--info=" "STYLE"
Determines the display style of finder info.
Determines the display style of finder info (match counters).
.br
.BR default " Display on the next line to the prompt"
.BR default " Display on the next line to the prompt"
.br
.BR inline " Display on the same line"
.BR right " Display on the right end of the next line to the prompt"
.br
.BR hidden " Do not display finder info"
.BR inline " Display on the same line with the default separator ' < '"
.br
.BR inline:SEPARATOR " Display on the same line with a non-default separator"
.br
.BR inline-right " Display on the right end of the same line
.br
.BR hidden " Do not display finder info"
.br
.TP
.B "--no-info"
A synonym for \fB--info=hidden\fB
.TP
.BI "--separator=" "STR"
The given string will be repeated to form the horizontal separator on the info
line (default: '─' or '-' depending on \fB--no-unicode\fR).
ANSI color codes are supported.
.TP
.B "--no-separator"
Do not display horizontal separator on the info line. A synonym for
\fB--separator=''\fB
.TP
.BI "--scrollbar=" "CHAR1[CHAR2]"
Use the given character to render scrollbar. (default: '│' or ':' depending on
\fB--no-unicode\fR). The optional \fBCHAR2\fR is used to render scrollbar of
the preview window.
.TP
.B "--no-scrollbar"
Do not display scrollbar. A synonym for \fB--scrollbar=''\fB
.TP
.BI "--prompt=" "STR"
Input prompt (default: '> ')
@@ -327,24 +462,30 @@ color mappings.
\fBbw \fRNo colors (equivalent to \fB--no-color\fR)
.B COLOR NAMES:
\fBfg \fRText
\fBbg \fRBackground
\fBpreview-fg \fRPreview window text
\fBpreview-bg \fRPreview window background
\fBhl \fRHighlighted substrings
\fBfg+ \fRText (current line)
\fBbg+ \fRBackground (current line)
\fBgutter \fRGutter on the left (defaults to \fBbg+\fR)
\fBhl+ \fRHighlighted substrings (current line)
\fBquery \fRQuery string
\fBdisabled \fRQuery string when search is disabled
\fBinfo \fRInfo line (match counters)
\fBborder \fRBorder around the window (\fB--border\fR and \fB--preview\fR)
\fBprompt \fRPrompt
\fBpointer \fRPointer to the current line
\fBmarker \fRMulti-select marker
\fBspinner \fRStreaming input indicator
\fBheader \fRHeader
\fBfg \fRText
\fBpreview-fg \fRPreview window text
\fBbg \fRBackground
\fBpreview-bg \fRPreview window background
\fBhl \fRHighlighted substrings
\fBfg+ \fRText (current line)
\fBbg+ \fRBackground (current line)
\fBgutter \fRGutter on the left
\fBhl+ \fRHighlighted substrings (current line)
\fBquery \fRQuery string
\fBdisabled \fRQuery string when search is disabled (\fB--disabled\fR)
\fBinfo \fRInfo line (match counters)
\fBborder \fRBorder around the window (\fB--border\fR and \fB--preview\fR)
\fBscrollbar \fRScrollbar
\fBpreview-border \fRBorder around the preview window (\fB--preview\fR)
\fBpreview-scrollbar \fRScrollbar
\fBseparator \fRHorizontal separator on info line
\fBlabel \fRBorder label (\fB--border-label\fR and \fB--preview-label\fR)
\fBpreview-label \fRBorder label of the preview window (\fB--preview-label\fR)
\fBprompt \fRPrompt
\fBpointer \fRPointer to the current line
\fBmarker \fRMulti-select marker
\fBspinner \fRStreaming input indicator
\fBheader \fRHeader
.B ANSI COLORS:
\fB-1 \fRDefault terminal foreground/background color
@@ -376,6 +517,7 @@ color mappings.
\fBreverse\fR
\fBdim\fR
\fBitalic\fR
\fBstrikethrough\fR
.B EXAMPLES:
@@ -401,7 +543,7 @@ Use black background
.BI "--history=" "HISTORY_FILE"
Load search history from the specified file and update the file on completion.
When enabled, \fBCTRL-N\fR and \fBCTRL-P\fR are automatically remapped to
\fBnext-history\fR and \fBprevious-history\fR.
\fBnext-history\fR and \fBprev-history\fR.
.TP
.BI "--history-size=" "N"
Maximum number of entries in the history file (default: 1000). The file is
@@ -426,6 +568,9 @@ they represent the exact size of the preview window. (It also overrides
by the default shell, so prefer to refer to the ones with \fBFZF_PREVIEW_\fR
prefix.)
fzf also exports \fB$FZF_PREVIEW_TOP\fR and \fB$FZF_PREVIEW_LEFT\fR so that
the preview command can determine the position of the preview window.
A placeholder expression starting with \fB+\fR flag will be replaced to the
space-separated list of the selected lines (or the current line if no selection
was made) individually quoted.
@@ -437,10 +582,6 @@ e.g.
When using a field index expression, leading and trailing whitespace is stripped
from the replacement string. To preserve the whitespace, use the \fBs\fR flag.
Also, \fB{q}\fR is replaced to the current query string, and \fB{n}\fR is
replaced to zero-based ordinal index of the line. Use \fB{+n}\fR if you want
all index numbers when multiple lines are selected.
A placeholder expression with \fBf\fR flag is replaced to the path of
a temporary file that holds the evaluated list. This is useful when you
multi-select a large number of items and the length of the evaluated string may
@@ -452,10 +593,19 @@ e.g.
seq 100000 | fzf --multi --bind ctrl-a:select-all \\
--preview "awk '{sum+=\\$1} END {print sum}' {+f}"\fR
Also,
* \fB{q}\fR (or \fB{fzf:query}\fR) is replaced to the current query string
.br
* \fB{n}\fR is replaced to the zero-based ordinal index of the current item.
Use \fB{+n}\fR if you want all index numbers when multiple lines are selected.
.br
Note that you can escape a placeholder pattern by prepending a backslash.
Preview window will be updated even when there is no match for the current
query if any of the placeholder expressions evaluates to a non-empty string.
query if any of the placeholder expressions evaluates to a non-empty string
or \fB{q}\fR is in the command template.
Since 0.24.0, fzf can render partial preview content before the preview command
completes. ANSI escape sequence for clearing the display (\fBCSI 2 J\fR) is
@@ -468,7 +618,50 @@ e.g.
echo "$i"
sleep 0.01
done'\fR
fzf has experimental support for Kitty graphics protocol and Sixel graphics.
The following example uses https://github.com/junegunn/fzf/blob/master/bin/fzf-preview.sh
script to render an image using either of the protocols inside the preview window.
e.g.
\fBfzf --preview='fzf-preview.sh {}'\fR
.RE
.TP
.BI "--preview-label" [=LABEL]
Label to print on the horizontal border line of the preview window.
Should be used with one of the following \fB--preview-window\fR options.
.br
.B * border-rounded (default on non-Windows platforms)
.br
.B * border-sharp (default on Windows)
.br
.B * border-bold
.br
.B * border-double
.br
.B * border-block
.br
.B * border-thinblock
.br
.B * border-horizontal
.br
.B * border-top
.br
.B * border-bottom
.br
.TP
.BI "--preview-label-pos" [=N[:top|bottom]]
Position of the border label on the border line of the preview window. Specify
a positive integer as the column position from the left. Specify a negative
integer to right-align the label. Label is printed on the top border line by
default, add \fB:bottom\fR to put it on the border line on the bottom. The
default value 0 (or \fBcenter\fR) will put the label at the center of the
border line.
.TP
.BI "--preview-window=" "[POSITION][,SIZE[%]][,border-BORDER_OPT][,[no]wrap][,[no]follow][,[no]cycle][,[no]hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]"
@@ -605,7 +798,16 @@ Print output delimited by ASCII NUL characters instead of newline characters
Do not clear finder interface on exit. If fzf was started in full screen mode,
it will not switch back to the original screen, so you'll have to manually run
\fBtput rmcup\fR to return. This option can be used to avoid flickering of the
screen when your application needs to start fzf multiple times in order.
screen when your application needs to start fzf multiple times in order. (Note
that in most cases, it is preferable to use \fBreload\fR action instead.)
e.g.
\fBfoo=$(seq 100 | fzf --no-clear) || (
# Need to manually switch back to the main screen when cancelled
tput rmcup
exit 1
) && seq "$foo" 100 | fzf
.TP
.B "--sync"
Synchronous search for multi-staged filtering. If specified, fzf will launch
@@ -615,11 +817,90 @@ ncurses finder only after the input stream is complete.
e.g. \fBfzf --multi | fzf --sync\fR
.RE
.TP
.B "--listen[=[ADDR:]PORT]" "--listen-unsafe[=[ADDR:]PORT]"
Start HTTP server and listen on the given address. It allows external processes
to send actions to perform via POST method.
- If the port number is omitted or given as 0, fzf will automatically choose
a port and export it as \fBFZF_PORT\fR environment variable to the child processes
- If \fBFZF_API_KEY\fR environment variable is set, the server would require
sending an API key with the same value in the \fBx-api-key\fR HTTP header
- \fBFZF_API_KEY\fR is required for a non-localhost listen address
- To allow remote process execution, use \fB--listen-unsafe\fR
e.g.
\fB# Start HTTP server on port 6266
fzf --listen 6266
# Send action to the server
curl -XPOST localhost:6266 -d 'reload(seq 100)+change-prompt(hundred> )'
# Get program state in JSON format (experimental)
# * Make sure NOT to access this endpoint from execute/transform actions
# as it will result in a timeout
curl localhost:6266
# Start HTTP server on port 6266 with remote connections allowed
# * Listening on non-localhost address requires using an API key
export FZF_API_KEY="$(head -c 32 /dev/urandom | base64)"
fzf --listen 0.0.0.0:6266
# Send an authenticated action
curl -XPOST localhost:6266 -H "x-api-key: $FZF_API_KEY" -d 'change-query(yo)'
# Choose port automatically and export it as $FZF_PORT to the child process
fzf --listen --bind 'start:execute-silent:echo $FZF_PORT > /tmp/fzf-port'
\fR
.TP
.B "--version"
Display version information and exit
.SS Directory traversal
.TP
Note that most options have the opposite versions with \fB--no-\fR prefix.
.B "--walker=[file][,dir][,follow][,hidden]"
Determines the behavior of the built-in directory walker that is used when
\fB$FZF_DEFAULT_COMMAND\fR is not set. The default value is \fBfile,follow,hidden\fR.
* \fBfile\fR: Include files in the search result
.br
* \fBdir\fR: Include directories in the search result
.br
* \fBhidden\fR: Include and follow hidden directories
.br
* \fBfollow\fR: Follow symbolic links
.br
.TP
.B "--walker-root=DIR"
The root directory from which to start the built-in directory walker.
The default value is the current working directory.
.TP
.B "--walker-skip=DIRS"
Comma-separated list of directory names to skip during the directory walk.
The default value is \fB.git,node_modules\fR.
.SS Shell integration
.TP
.B "--bash"
Print script to set up Bash shell integration
e.g. \fBeval "$(fzf --bash)"\fR
.TP
.B "--zsh"
Print script to set up Zsh shell integration
e.g. \fBeval "$(fzf --zsh)"\fR
.TP
.B "--fish"
Print script to set up Fish shell integration
e.g. \fBfzf --fish | source\fR
.SH ENVIRONMENT VARIABLES
.TP
@@ -629,7 +910,19 @@ with \fB$SHELL -c\fR if \fBSHELL\fR is set, otherwise with \fBsh -c\fR, so in
this case make sure that the command is POSIX-compliant.
.TP
.B FZF_DEFAULT_OPTS
Default options. e.g. \fBexport FZF_DEFAULT_OPTS="--extended --cycle"\fR
Default options.
.br
e.g. \fBexport FZF_DEFAULT_OPTS="--layout=reverse --border --cycle"\fR
.TP
.B FZF_DEFAULT_OPTS_FILE
The location of the file that contains the default options.
.br
e.g. \fBexport FZF_DEFAULT_OPTS_FILE=~/.fzfrc\fR
.TP
.B FZF_API_KEY
Can be used to require an API key when using \fB--listen\fR option. If not set,
no authentication will be required by the server. You can set this value if
you need to protect against DNS rebinding and privilege escalation attacks.
.SH EXIT STATUS
.BR 0 " Normal exit"
@@ -664,6 +957,39 @@ of field index expressions.
.BR .. " All the fields"
.br
.SH ENVIRONMENT VARIABLES EXPORTED TO CHILD PROCESSES
fzf exports the following environment variables to its child processes.
.BR FZF_LINES " Number of lines fzf takes up excluding padding and margin"
.br
.BR FZF_COLUMNS " Number of columns fzf takes up excluding padding and margin"
.br
.BR FZF_TOTAL_COUNT " Total number of items"
.br
.BR FZF_MATCH_COUNT " Number of matched items"
.br
.BR FZF_SELECT_COUNT " Number of selected items"
.br
.BR FZF_QUERY " Current query string"
.br
.BR FZF_PROMPT " Prompt string"
.br
.BR FZF_ACTION " The name of the last action performed"
.br
.BR FZF_PORT " Port number when --listen option is used"
.br
The following variables are additionally exported to the preview commands.
.BR FZF_PREVIEW_TOP " Top position of the preview window"
.br
.BR FZF_PREVIEW_LEFT " Left position of the preview window"
.br
.BR FZF_PREVIEW_LINES " Number of lines in the preview window"
.br
.BR FZF_PREVIEW_COLUMNS " Number of columns in the preview window"
.SH EXTENDED SEARCH MODE
Unless specified otherwise, fzf will start in "extended-search mode". In this
@@ -715,6 +1041,8 @@ e.g.
.br
\fIctrl-space\fR
.br
\fIctrl-delete\fR
.br
\fIctrl-\\\fR
.br
\fIctrl-]\fR
@@ -783,6 +1111,8 @@ e.g.
.br
\fIshift-right\fR
.br
\fIshift-delete\fR
.br
\fIalt-shift-up\fR
.br
\fIalt-shift-down\fR
@@ -797,9 +1127,59 @@ e.g.
.br
\fIdouble-click\fR
.br
\fIscroll-up\fR
.br
\fIscroll-down\fR
.br
\fIpreview-scroll-up\fR
.br
\fIpreview-scroll-down\fR
.br
\fIshift-left-click\fR
.br
\fIshift-right-click\fR
.br
\fIshift-scroll-up\fR
.br
\fIshift-scroll-down\fR
.br
or any single character
.SS AVAILABLE EVENTS:
\fIstart\fR
.RS
Triggered only once when fzf finder starts. Since fzf consumes the input stream
asynchronously, the input list is not available unless you use \fI--sync\fR.
e.g.
\fB# Move cursor to the last item and select all items
seq 1000 | fzf --multi --sync --bind start:last+select-all\fR
.RE
\fIload\fR
.RS
Triggered when the input stream is complete and the initial processing of the
list is complete.
e.g.
\fB# Change the prompt to "loaded" when the input stream is complete
(seq 10; sleep 1; seq 11 20) | fzf --prompt 'Loading> ' --bind 'load:change-prompt:Loaded> '\fR
.RE
\fIresize\fR
.RS
Triggered when the terminal size is changed.
e.g.
\fBfzf --bind 'resize:transform-header:echo Resized: ${FZF_COLUMNS}x${FZF_LINES}'\fR
.RE
\fIresult\fR
.RS
Triggered when the filtering for the current query is complete and the result list is ready.
e.g.
\fB# Put the cursor on the second item when the query string is empty
# * Note that you can't use 'change' event in this case because the second position may not be available
fzf --sync --bind 'result:transform:[[ -z {fzf:query} ]] && echo "pos(2)"'\fR
.RE
\fIchange\fR
.RS
Triggered whenever the query string is changed
@@ -808,6 +1188,44 @@ e.g.
\fB# Move cursor to the first entry whenever the query is changed
fzf --bind change:first\fR
.RE
\fIfocus\fR
.RS
Triggered when the focus changes due to a vertical cursor movement or a search
result update.
e.g.
\fBfzf --bind 'focus:transform-preview-label:echo [ {} ]' --preview 'cat {}'
# Any action bound to the event runs synchronously and thus can make the interface sluggish
# e.g. lolcat isn't one of the fastest programs, and every cursor movement in
# fzf will be noticeably affected by its execution time
fzf --bind 'focus:transform-preview-label:echo [ {} ] | lolcat -f' --preview 'cat {}'
# Beware not to introduce an infinite loop
seq 10 | fzf --bind 'focus:up' --cycle\fR
.RE
\fIone\fR
.RS
Triggered when there's only one match. \fBone:accept\fR binding is comparable
to \fB--select-1\fR option, but the difference is that \fB--select-1\fR is only
effective before the interactive finder starts but \fBone\fR event is triggered
by the interactive finder.
e.g.
\fB# Automatically select the only match
seq 10 | fzf --bind one:accept\fR
.RE
\fIzero\fR
.RS
Triggered when there's no match. \fBzero:abort\fR binding is comparable to
\fB--exit-0\fR option, but the difference is that \fB--exit-0\fR is only
effective before the interactive finder starts but \fBzero\fR event is
triggered by the interactive finder.
e.g.
\fB# Reload the candidate list when there's no match
echo $RANDOM | fzf --bind 'zero:reload(echo $RANDOM)+clear-query' --height 3\fR
.RE
\fIbackward-eof\fR
.RS
@@ -821,81 +1239,107 @@ e.g.
.SS AVAILABLE ACTIONS:
A key or an event can be bound to one or more of the following actions.
\fBACTION: DEFAULT BINDINGS (NOTES):
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
\fBaccept\fR \fIenter double-click\fR
\fBaccept-non-empty\fR (same as \fBaccept\fR except that it prevents fzf from exiting without selection)
\fBbackward-char\fR \fIctrl-b left\fR
\fBbackward-delete-char\fR \fIctrl-h bspace\fR
\fBbackward-delete-char/eof\fR (same as \fBbackward-delete-char\fR except aborts fzf if query is empty)
\fBbackward-kill-word\fR \fIalt-bs\fR
\fBbackward-word\fR \fIalt-b shift-left\fR
\fBbeginning-of-line\fR \fIctrl-a home\fR
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
\fBchange-preview(...)\fR (change \fB--preview\fR option)
\fBchange-preview-window(...)\fR (change \fB--preview-window\fR option; rotate through the multiple option sets separated by '|')
\fBchange-prompt(...)\fR (change prompt to the given string)
\fBclear-screen\fR \fIctrl-l\fR
\fBclear-selection\fR (clear multi-selection)
\fBclose\fR (close preview window if open, abort fzf otherwise)
\fBclear-query\fR (clear query string)
\fBdelete-char\fR \fIdel\fR
\fBdelete-char/eof\fR \fIctrl-d\fR (same as \fBdelete-char\fR except aborts fzf if query is empty)
\fBACTION: DEFAULT BINDINGS (NOTES):
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
\fBaccept\fR \fIenter double-click\fR
\fBaccept-non-empty\fR (same as \fBaccept\fR except that it prevents fzf from exiting without selection)
\fBaccept-or-print-query\fR (same as \fBaccept\fR except that it prints the query when there's no match)
\fBbackward-char\fR \fIctrl-b left\fR
\fBbackward-delete-char\fR \fIctrl-h bspace\fR
\fBbackward-delete-char/eof\fR (same as \fBbackward-delete-char\fR except aborts fzf if query is empty)
\fBbackward-kill-word\fR \fIalt-bs\fR
\fBbackward-word\fR \fIalt-b shift-left\fR
\fBbecome(...)\fR (replace fzf process with the specified command; see below for the details)
\fBbeginning-of-line\fR \fIctrl-a home\fR
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
\fBchange-border-label(...)\fR (change \fB--border-label\fR to the given string)
\fBchange-header(...)\fR (change header to the given string; doesn't affect \fB--header-lines\fR)
\fBchange-preview(...)\fR (change \fB--preview\fR option)
\fBchange-preview-label(...)\fR (change \fB--preview-label\fR to the given string)
\fBchange-preview-window(...)\fR (change \fB--preview-window\fR option; rotate through the multiple option sets separated by '|')
\fBchange-prompt(...)\fR (change prompt to the given string)
\fBchange-query(...)\fR (change query string to the given string)
\fBclear-screen\fR \fIctrl-l\fR
\fBclear-selection\fR (clear multi-selection)
\fBclose\fR (close preview window if open, abort fzf otherwise)
\fBclear-query\fR (clear query string)
\fBdelete-char\fR \fIdel\fR
\fBdelete-char/eof\fR \fIctrl-d\fR (same as \fBdelete-char\fR except aborts fzf if query is empty)
\fBdeselect\fR
\fBdeselect-all\fR (deselect all matches)
\fBdisable-search\fR (disable search functionality)
\fBdown\fR \fIctrl-j ctrl-n down\fR
\fBenable-search\fR (enable search functionality)
\fBend-of-line\fR \fIctrl-e end\fR
\fBexecute(...)\fR (see below for the details)
\fBexecute-silent(...)\fR (see below for the details)
\fBfirst\fR (move to the first match)
\fBforward-char\fR \fIctrl-f right\fR
\fBforward-word\fR \fIalt-f shift-right\fR
\fBdeselect-all\fR (deselect all matches)
\fBdisable-search\fR (disable search functionality)
\fBdown\fR \fIctrl-j ctrl-n down\fR
\fBenable-search\fR (enable search functionality)
\fBend-of-line\fR \fIctrl-e end\fR
\fBexecute(...)\fR (see below for the details)
\fBexecute-silent(...)\fR (see below for the details)
\fBfirst\fR (move to the first match; same as \fBpos(1)\fR)
\fBforward-char\fR \fIctrl-f right\fR
\fBforward-word\fR \fIalt-f shift-right\fR
\fBignore\fR
\fBjump\fR (EasyMotion-like 2-keystroke movement)
\fBjump-accept\fR (jump and accept)
\fBjump\fR (EasyMotion-like 2-keystroke movement)
\fBjump-accept\fR (jump and accept)
\fBkill-line\fR
\fBkill-word\fR \fIalt-d\fR
\fBlast\fR (move to the last match)
\fBnext-history\fR (\fIctrl-n\fR on \fB--history\fR)
\fBpage-down\fR \fIpgdn\fR
\fBpage-up\fR \fIpgup\fR
\fBkill-word\fR \fIalt-d\fR
\fBlast\fR (move to the last match; same as \fBpos(-1)\fR)
\fBnext-history\fR (\fIctrl-n\fR on \fB--history\fR)
\fBnext-selected\fR (move to the next selected item)
\fBpage-down\fR \fIpgdn\fR
\fBpage-up\fR \fIpgup\fR
\fBhalf-page-down\fR
\fBhalf-page-up\fR
\fBpreview(...)\fR (see below for the details)
\fBpreview-down\fR \fIshift-down\fR
\fBpreview-up\fR \fIshift-up\fR
\fBhide-header\fR
\fBhide-preview\fR
\fBoffset-down\fR (similar to CTRL-E of Vim)
\fBoffset-up\fR (similar to CTRL-Y of Vim)
\fBpos(...)\fR (move cursor to the numeric position; negative number to count from the end)
\fBprev-history\fR (\fIctrl-p\fR on \fB--history\fR)
\fBprev-selected\fR (move to the previous selected item)
\fBpreview(...)\fR (see below for the details)
\fBpreview-down\fR \fIshift-down\fR
\fBpreview-up\fR \fIshift-up\fR
\fBpreview-page-down\fR
\fBpreview-page-up\fR
\fBpreview-half-page-down\fR
\fBpreview-half-page-up\fR
\fBpreview-bottom\fR
\fBpreview-top\fR
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
\fBprint-query\fR (print query and exit)
\fBput\fR (put the character to the prompt)
\fBprint-query\fR (print query and exit)
\fBput\fR (put the character to the prompt)
\fBput(...)\fR (put the given string to the prompt)
\fBrefresh-preview\fR
\fBrebind(...)\fR (rebind bindings after \fBunbind\fR)
\fBreload(...)\fR (see below for the details)
\fBreplace-query\fR (replace query string with the current selection)
\fBrebind(...)\fR (rebind bindings after \fBunbind\fR)
\fBreload(...)\fR (see below for the details)
\fBreload-sync(...)\fR (see below for the details)
\fBreplace-query\fR (replace query string with the current selection)
\fBselect\fR
\fBselect-all\fR (select all matches)
\fBtoggle\fR (\fIright-click\fR)
\fBtoggle-all\fR (toggle all matches)
\fBtoggle+down\fR \fIctrl-i (tab)\fR
\fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
\fBselect-all\fR (select all matches)
\fBshow-header\fR
\fBshow-preview\fR
\fBtoggle\fR (\fIright-click\fR)
\fBtoggle-all\fR (toggle all matches)
\fBtoggle+down\fR \fIctrl-i (tab)\fR
\fBtoggle-header\fR
\fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
\fBtoggle-preview\fR
\fBtoggle-preview-wrap\fR
\fBtoggle-search\fR (toggle search functionality)
\fBtoggle-search\fR (toggle search functionality)
\fBtoggle-sort\fR
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
\fBunbind(...)\fR (unbind bindings)
\fBunix-line-discard\fR \fIctrl-u\fR
\fBunix-word-rubout\fR \fIctrl-w\fR
\fBup\fR \fIctrl-k ctrl-p up\fR
\fByank\fR \fIctrl-y\fR
\fBtoggle-track\fR
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
\fBtrack\fR (track the current item; automatically disabled if focus changes)
\fBtransform(...)\fR (transform states using the output of an external command)
\fBtransform-border-label(...)\fR (transform border label using an external command)
\fBtransform-header(...)\fR (transform header using an external command)
\fBtransform-preview-label(...)\fR (transform preview label using an external command)
\fBtransform-prompt(...)\fR (transform prompt string using an external command)
\fBtransform-query(...)\fR (transform query string using an external command)
\fBunbind(...)\fR (unbind bindings)
\fBunix-line-discard\fR \fIctrl-u\fR
\fBunix-word-rubout\fR \fIctrl-w\fR
\fBup\fR \fIctrl-k ctrl-p up\fR
\fByank\fR \fIctrl-y\fR
.SS ACTION COMPOSITION
@@ -918,6 +1362,8 @@ that case, you can use any of the following alternative notations to avoid
parse errors.
\fBaction-name[...]\fR
\fBaction-name{...}\fR
\fBaction-name<...>\fR
\fBaction-name~...~\fR
\fBaction-name!...!\fR
\fBaction-name@...@\fR
@@ -958,6 +1404,14 @@ On *nix systems, fzf runs the command with \fB$SHELL -c\fR if \fBSHELL\fR is
set, otherwise with \fBsh -c\fR, so in this case make sure that the command is
POSIX-compliant.
\fBbecome(...)\fR action is similar to \fBexecute(...)\fR, but it replaces the
current fzf process with the specified command using \fBexecve(2)\fR system
call.
\fBfzf --bind "enter:become(vim {})"\fR
\fBbecome(...)\fR is not supported on Windows.
.SS RELOAD INPUT
\fBreload(...)\fR action is used to dynamically update the input list
@@ -978,6 +1432,38 @@ e.g.
fzf --bind "change:reload:$RG_PREFIX {q} || true" \\
--ansi --disabled --query "$INITIAL_QUERY"\fR
\fBreload-sync(...)\fR is a synchronous version of \fBreload\fR that replaces
the list only when the command is complete. This is useful when the command
takes a while to produce the initial output and you don't want fzf to run
against an empty list while the command is running.
e.g.
\fB# You can still filter and select entries from the initial list for 3 seconds
seq 100 | fzf --bind 'load:reload-sync(sleep 3; seq 1000)+unbind(load)'\fR
.SS TRANSFORM ACTIONS
Actions with \fBtransform-\fR prefix are used to transform the states of fzf
using the output of an external command. The output of these commands are
expected to be a single line of text.
e.g.
\fBfzf --bind 'focus:transform-header:file --brief {}'\fR
\fBtransform(...)\fR action runs an external command that should print a series
of actions to be performed. The output should be in the same format as the
payload of HTTP POST request to the \fB--listen\fR server.
e.g.
\fB# Disallow selecting an empty line
echo -e "1. Hello\\n2. Goodbye\\n\\n3. Exit" |
fzf --height '~100%' --reverse --header 'Select one' \\
--bind 'enter:transform:[[ -n {} ]] &&
echo accept ||
echo "change-header:Invalid selection"'
\fR
.SS PREVIEW BINDING
With \fBpreview(...)\fR action, you can specify multiple different preview

View File

@@ -1,4 +1,4 @@
" Copyright (c) 2017 Junegunn Choi
" Copyright (c) 2013-2024 Junegunn Choi
"
" MIT License
"
@@ -143,7 +143,7 @@ function! fzf#install()
if !filereadable(script)
throw script.' not found'
endif
let script = 'powershell -ExecutionPolicy Bypass -file ' . script
let script = 'powershell -ExecutionPolicy Bypass -file ' . shellescape(script)
else
let script = s:base_dir.'/install'
if !executable(script)
@@ -164,7 +164,7 @@ function s:get_version(bin)
if has_key(s:versions, a:bin)
return s:versions[a:bin]
end
let command = fzf#shellescape(a:bin) . ' --version --no-height'
let command = (&shell =~ 'powershell\|pwsh' ? '&' : '') . s:fzf_call('shellescape', a:bin) . ' --version --no-height'
let output = systemlist(command)
if v:shell_error || empty(output)
return ''
@@ -342,7 +342,8 @@ function! s:common_sink(action, lines) abort
endfunction
function! s:get_color(attr, ...)
let gui = !s:is_win && !has('win32unix') && has('termguicolors') && &termguicolors
" Force 24 bit colors: g:fzf_force_termguicolors (temporary workaround for https://github.com/junegunn/fzf.vim/issues/1152)
let gui = get(g:, 'fzf_force_termguicolors', 0) || (!s:is_win && !has('win32unix') && has('termguicolors') && &termguicolors)
let fam = gui ? 'gui' : 'cterm'
let pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$'
for group in a:000
@@ -455,6 +456,30 @@ function! s:writefile(...)
endif
endfunction
function! s:extract_option(opts, name)
let opt = ''
let expect = 0
" There are a few cases where this function doesn't work as expected.
" Let's just assume such cases are extremely unlikely in real world.
" e.g. --query --border
for word in split(a:opts)
if expect && word !~ '^"\=-'
let opt = opt . ' ' . word
let expect = 0
elseif word == '--no-'.a:name
let opt = ''
elseif word =~ '^--'.a:name.'='
let opt = word
elseif word =~ '^--'.a:name.'$'
let opt = word
let expect = 1
elseif expect
let expect = 0
endif
endfor
return opt
endfunction
function! fzf#run(...) abort
try
let [shell, shellslash, shellcmdflag, shellxquote] = s:use_sh()
@@ -463,7 +488,7 @@ try
let temps = { 'result': s:fzf_tempname() }
let optstr = s:evaluate_opts(get(dict, 'options', ''))
try
let fzf_exec = fzf#shellescape(fzf#exec())
let fzf_exec = shellescape(fzf#exec())
catch
throw v:exception
endtry
@@ -510,7 +535,8 @@ try
let height = s:calc_size(&lines, dict.down, dict)
let optstr .= ' --height='.height
endif
let optstr .= s:border_opt(get(dict, 'window', 0))
" Respect --border option given in $FZF_DEFAULT_OPTS and 'options'
let optstr = join([s:border_opt(get(dict, 'window', 0)), s:extract_option($FZF_DEFAULT_OPTS, 'border'), optstr])
let prev_default_command = $FZF_DEFAULT_COMMAND
if len(source_command)
let $FZF_DEFAULT_COMMAND = source_command
@@ -737,7 +763,7 @@ function! s:calc_size(max, val, dict)
return size
endif
let margin = match(opts, '--inline-info\|--info[^-]\{-}inline') > match(opts, '--no-inline-info\|--info[^-]\{-}\(default\|hidden\)') ? 1 : 2
let margin += stridx(opts, '--border') > stridx(opts, '--no-border') ? 2 : 0
let margin += match(opts, '--border\([^-]\|$\)') > match(opts, '--no-border\([^-]\|$\)') ? 2 : 0
if stridx(opts, '--header') > stridx(opts, '--no-header')
let margin += len(split(opts, "\n"))
endif
@@ -754,9 +780,9 @@ function! s:border_opt(window)
endif
" Border style
let style = tolower(get(a:window, 'border', 'rounded'))
if !has_key(a:window, 'border') && !get(a:window, 'rounded', 1)
let style = 'sharp'
let style = tolower(get(a:window, 'border', ''))
if !has_key(a:window, 'border') && has_key(a:window, 'rounded')
let style = a:window.rounded ? 'rounded' : 'sharp'
endif
if style == 'none' || style == 'no'
return ''
@@ -764,7 +790,7 @@ function! s:border_opt(window)
" For --border styles, we need fzf 0.24.0 or above
call fzf#exec('0.24.0')
let opt = ' --border=' . style
let opt = ' --border ' . style
if has_key(a:window, 'highlight')
let color = s:get_color('fg', a:window.highlight)
if len(color)
@@ -826,6 +852,17 @@ if exists(':tnoremap')
tnoremap <silent> <Plug>(fzf-normal) <C-\><C-n>
endif
let s:warned = 0
function! s:handle_ambidouble(dict)
if &ambiwidth == 'double'
let a:dict.env = { 'RUNEWIDTH_EASTASIAN': '1' }
elseif !s:warned && $RUNEWIDTH_EASTASIAN == '1' && &ambiwidth !=# 'double'
call s:warn("$RUNEWIDTH_EASTASIAN is '1' but &ambiwidth is not 'double'")
2sleep
let s:warned = 1
endif
endfunction
function! s:execute_term(dict, command, temps) abort
let winrest = winrestcmd()
let pbuf = bufnr('')
@@ -895,6 +932,7 @@ function! s:execute_term(dict, command, temps) abort
endif
let command .= s:term_marker
if has('nvim')
call s:handle_ambidouble(fzf)
call termopen(command, fzf)
else
let term_opts = {'exit_cb': function(fzf.on_exit)}
@@ -906,7 +944,8 @@ function! s:execute_term(dict, command, temps) abort
else
let term_opts.curwin = 1
endif
let fzf.buf = term_start([&shell, &shellcmdflag, command], term_opts)
call s:handle_ambidouble(term_opts)
keepjumps let fzf.buf = term_start([&shell, &shellcmdflag, command], term_opts)
if is_popup && exists('#TerminalWinOpen')
doautocmd <nomodeline> TerminalWinOpen
endif
@@ -972,16 +1011,16 @@ function! s:callback(dict, lines) abort
endfunction
if has('nvim')
function s:create_popup(hl, opts) abort
function s:create_popup(opts) abort
let buf = nvim_create_buf(v:false, v:true)
let opts = extend({'relative': 'editor', 'style': 'minimal'}, a:opts)
let win = nvim_open_win(buf, v:true, opts)
call setwinvar(win, '&winhighlight', 'NormalFloat:'..a:hl)
silent! call setwinvar(win, '&winhighlight', 'Pmenu:,Normal:Normal')
call setwinvar(win, '&colorcolumn', '')
return buf
endfunction
else
function! s:create_popup(hl, opts) abort
function! s:create_popup(opts) abort
let s:popup_create = {buf -> popup_create(buf, #{
\ line: a:opts.row,
\ col: a:opts.col,
@@ -1016,7 +1055,7 @@ function! s:popup(opts) abort
let row += !has('nvim')
let col += !has('nvim')
call s:create_popup('Normal', {
call s:create_popup({
\ 'row': row, 'col': col, 'width': width, 'height': height
\ })
endfunction

View File

@@ -9,25 +9,23 @@
# - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty)
if [[ $- =~ i ]]; then
[[ $- =~ i ]] || return 0
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
if ! declare -f _fzf_compgen_path > /dev/null; then
_fzf_compgen_path() {
echo "$1"
command find -L "$1" \
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
}
fi
if ! declare -f _fzf_compgen_dir > /dev/null; then
_fzf_compgen_dir() {
command find -L "$1" \
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
}
fi
#
# _fzf_compgen_path() {
# echo "$1"
# command find -L "$1" \
# -name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
# -a -not -path "$1" -print 2> /dev/null | command sed 's@^\./@@'
# }
#
# _fzf_compgen_dir() {
# command find -L "$1" \
# -name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
# -a -not -path "$1" -print 2> /dev/null | command sed 's@^\./@@'
# }
###########################################################
@@ -37,7 +35,7 @@ bind '"\e[0n": redraw-current-line' 2> /dev/null
__fzf_comprun() {
if [[ "$(type -t _fzf_comprun 2>&1)" = function ]]; then
_fzf_comprun "$@"
elif [[ -n "$TMUX_PANE" ]] && { [[ "${FZF_TMUX:-0}" != 0 ]] || [[ -n "$FZF_TMUX_OPTS" ]]; }; then
elif [[ -n "${TMUX_PANE-}" ]] && { [[ "${FZF_TMUX:-0}" != 0 ]] || [[ -n "${FZF_TMUX_OPTS-}" ]]; }; then
shift
fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- "$@"
else
@@ -55,72 +53,223 @@ __fzf_orig_completion() {
cmd="${BASH_REMATCH[3]}"
[[ "$f" = _fzf_* ]] && continue
printf -v "_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}" "%s" "${comp} %s ${cmd} #${f}"
if [[ "$l" = *" -o nospace "* ]] && [[ ! "$__fzf_nospace_commands" = *" $cmd "* ]]; then
__fzf_nospace_commands="$__fzf_nospace_commands $cmd "
if [[ "$l" = *" -o nospace "* ]] && [[ ! "${__fzf_nospace_commands-}" = *" $cmd "* ]]; then
__fzf_nospace_commands="${__fzf_nospace_commands-} $cmd "
fi
fi
done
}
# @param $1 cmd - Command name for which the original completion is searched
# @var[out] REPLY - Original function name is returned
__fzf_orig_completion_get_orig_func() {
local cmd orig_var orig
cmd=$1
orig_var="_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}"
orig="${!orig_var-}"
REPLY="${orig##*#}"
[[ $REPLY ]] && type "$REPLY" &> /dev/null
}
# @param $1 cmd - Command name for which the original completion is searched
# @param $2 func - Fzf's completion function to replace the original function
# @var[out] REPLY - Completion setting is returned as a string to "eval"
__fzf_orig_completion_instantiate() {
local cmd func orig_var orig
cmd=$1
func=$2
orig_var="_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}"
orig="${!orig_var-}"
orig="${orig%#*}"
[[ $orig == *' %s '* ]] || return 1
printf -v REPLY "$orig" "$func"
}
_fzf_opts_completion() {
local cur prev opts
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts="
-h --help
-x --extended
-e --exact
--extended-exact
+x --no-extended
+e --no-exact
-q --query
-f --filter
--literal
--no-literal
--algo
-i +i
--scheme
--expect
--no-expect
--enabled --no-phony
--disabled --phony
--tiebreak
--bind
--color
--toggle-sort
-d --delimiter
-n --nth
--with-nth
-d --delimiter
-s --sort
+s --no-sort
--track
--no-track
--tac
--tiebreak
--no-tac
-i
+i
-m --multi
+m --no-multi
--ansi
--no-ansi
--no-mouse
--bind
--cycle
--no-hscroll
--jump-labels
--height
--literal
+c --no-color
+2 --no-256
--black
--no-black
--bold
--no-bold
--layout
--reverse
--margin
--no-reverse
--cycle
--no-cycle
--keep-right
--no-keep-right
--hscroll
--no-hscroll
--hscroll-off
--scroll-off
--filepath-word
--no-filepath-word
--info
--no-info
--inline-info
--no-inline-info
--separator
--no-separator
--scrollbar
--no-scrollbar
--jump-labels
-1 --select-1
+1 --no-select-1
-0 --exit-0
+0 --no-exit-0
--read0
--no-read0
--print0
--no-print0
--print-query
--no-print-query
--prompt
--pointer
--marker
--header
--header-lines
--ansi
--tabstop
--color
--no-bold
--sync
--no-sync
--async
--no-history
--history
--history-size
--no-header
--no-header-lines
--header
--header-lines
--header-first
--no-header-first
--ellipsis
--preview
--no-preview
--preview-window
-q --query
-1 --select-1
-0 --exit-0
-f --filter
--print-query
--expect
--sync"
--height
--min-height
--no-height
--no-margin
--no-padding
--no-border
--border
--no-border-label
--border-label
--border-label-pos
--no-preview-label
--preview-label
--preview-label-pos
--no-unicode
--unicode
--margin
--padding
--tabstop
--listen
--no-listen
--clear
--no-clear
--version
--"
case "${prev}" in
--algo)
COMPREPLY=( $(compgen -W "v1 v2" -- "$cur") )
return 0
;;
--scheme)
COMPREPLY=( $(compgen -W "default path history" -- "$cur") )
return 0
;;
--tiebreak)
COMPREPLY=( $(compgen -W "length begin end index" -- "$cur") )
COMPREPLY=( $(compgen -W "length chunk begin end index" -- "$cur") )
return 0
;;
--color)
COMPREPLY=( $(compgen -W "dark light 16 bw" -- "$cur") )
COMPREPLY=( $(compgen -W "dark light 16 bw no" -- "$cur") )
return 0
;;
--history)
COMPREPLY=()
--layout)
COMPREPLY=( $(compgen -W "default reverse reverse-list" -- "$cur") )
return 0
;;
--info)
COMPREPLY=( $(compgen -W "default right hidden inline inline-right" -- "$cur") )
return 0
;;
--preview-window)
COMPREPLY=( $(compgen -W "
default
hidden
nohidden
wrap
nowrap
cycle
nocycle
up top
down bottom
left
right
rounded border border-rounded
sharp border-sharp
border-bold
border-block
border-thinblock
border-double
noborder border-none
border-horizontal
border-vertical
border-up border-top
border-down border-bottom
border-left
border-right
follow
nofollow" -- "$cur") )
return 0
;;
--border)
COMPREPLY=( $(compgen -W "rounded sharp bold block thinblock double horizontal vertical top bottom left right none" -- "$cur") )
return 0
;;
--border-label-pos|--preview-label-pos)
COMPREPLY=( $(compgen -W "center bottom top" -- "$cur") )
return 0
;;
esac
@@ -134,22 +283,26 @@ _fzf_opts_completion() {
}
_fzf_handle_dynamic_completion() {
local cmd orig_var orig ret orig_cmd orig_complete
local cmd ret REPLY orig_cmd orig_complete
cmd="$1"
shift
orig_cmd="$1"
orig_var="_fzf_orig_completion_$cmd"
orig="${!orig_var##*#}"
if [[ -n "$orig" ]] && type "$orig" > /dev/null 2>&1; then
$orig "$@"
elif [[ -n "$_fzf_completion_loader" ]]; then
if __fzf_orig_completion_get_orig_func "$cmd"; then
"$REPLY" "$@"
elif [[ -n "${_fzf_completion_loader-}" ]]; then
orig_complete=$(complete -p "$orig_cmd" 2> /dev/null)
_completion_loader "$@"
$_fzf_completion_loader "$@"
ret=$?
# _completion_loader may not have updated completion for the command
if [[ "$(complete -p "$orig_cmd" 2> /dev/null)" != "$orig_complete" ]]; then
__fzf_orig_completion < <(complete -p "$orig_cmd" 2> /dev/null)
if [[ "$__fzf_nospace_commands" = *" $orig_cmd "* ]]; then
# Update orig_complete by _fzf_orig_completion entry
[[ $orig_complete =~ ' -F '(_fzf_[^ ]+)' ' ]] &&
__fzf_orig_completion_instantiate "$cmd" "${BASH_REMATCH[1]}" &&
orig_complete=$REPLY
if [[ "${__fzf_nospace_commands-}" = *" $orig_cmd "* ]]; then
eval "${orig_complete/ -F / -o nospace -F }"
else
eval "$orig_complete"
@@ -161,14 +314,18 @@ _fzf_handle_dynamic_completion() {
__fzf_generic_path_completion() {
local cur base dir leftover matches trigger cmd
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
cmd="${COMP_WORDS[0]}"
if [[ $cmd == \\* ]]; then
cmd="${cmd:1}"
fi
COMPREPLY=()
trigger=${FZF_COMPLETION_TRIGGER-'**'}
cur="${COMP_WORDS[COMP_CWORD]}"
if [[ "$cur" == *"$trigger" ]]; then
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
base=${cur:0:${#cur}-${#trigger}}
eval "base=$base"
eval "base=$base" 2> /dev/null || return
dir=
[[ $base = *"/"* ]] && dir="$base"
while true; do
if [[ -z "$dir" ]] || [[ -d "$dir" ]]; then
@@ -176,11 +333,20 @@ __fzf_generic_path_completion() {
leftover=${leftover/#\/}
[[ -z "$dir" ]] && dir='.'
[[ "$dir" != "/" ]] && dir="${dir/%\//}"
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
printf "%q$3 " "$item"
done)
matches=$(
unset FZF_DEFAULT_COMMAND
export FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $2"
if declare -F "$1" > /dev/null; then
eval "$1 $(printf %q "$dir")" | __fzf_comprun "$4" -q "$leftover"
else
[[ $1 =~ dir ]] && walker=dir,follow || walker=file,dir,follow,hidden
__fzf_comprun "$4" -q "$leftover" --walker "$walker" --walker-root="$dir"
fi | while read -r item; do
printf "%q " "${item%$3}$3"
done
)
matches=${matches% }
[[ -z "$3" ]] && [[ "$__fzf_nospace_commands" = *" ${COMP_WORDS[0]} "* ]] && matches="$matches "
[[ -z "$3" ]] && [[ "${__fzf_nospace_commands-}" = *" ${COMP_WORDS[0]} "* ]] && matches="$matches "
if [[ -n "$matches" ]]; then
COMPREPLY=( "$matches" )
else
@@ -189,7 +355,7 @@ __fzf_generic_path_completion() {
printf '\e[5n'
return 0
fi
dir=$(dirname "$dir")
dir=$(command dirname "$dir")
[[ "$dir" =~ /$ ]] || dir="$dir"/
done
else
@@ -223,16 +389,16 @@ _fzf_complete() {
fi
local cur selected trigger cmd post
post="$(caller 0 | awk '{print $2}')_post"
type -t "$post" > /dev/null 2>&1 || post=cat
post="$(caller 0 | command awk '{print $2}')_post"
type -t "$post" > /dev/null 2>&1 || post='command cat'
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
trigger=${FZF_COMPLETION_TRIGGER-'**'}
cmd="${COMP_WORDS[0]}"
cur="${COMP_WORDS[COMP_CWORD]}"
if [[ "$cur" == *"$trigger" ]]; then
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
cur=${cur:0:${#cur}-${#trigger}}
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | tr '\n' ' ')
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | command tr '\n' ' ')
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
if [[ -n "$selected" ]]; then
COMPREPLY=("$selected")
@@ -264,33 +430,68 @@ _fzf_complete_kill() {
}
_fzf_proc_completion() {
_fzf_complete -m --preview 'echo {}' --preview-window down:3:wrap --min-height 15 -- "$@" < <(
command ps -ef | sed 1d
_fzf_complete -m --header-lines=1 --preview 'echo {}' --preview-window down:3:wrap --min-height 15 -- "$@" < <(
command ps -eo user,pid,ppid,start,time,command 2> /dev/null ||
command ps -eo user,pid,ppid,time,args # For BusyBox
)
}
_fzf_proc_completion_post() {
awk '{print $2}'
command awk '{print $2}'
}
# To use custom hostname lists, override __fzf_list_hosts.
# The function is expected to print hostnames, one per line as well as in the
# desired sorting and with any duplicates removed, to standard output.
#
# e.g.
# # Use bash-completionss _known_hosts_real() for getting the list of hosts
# __fzf_list_hosts() {
# # Set the local attribute for any non-local variable that is set by _known_hosts_real()
# local COMPREPLY=()
# _known_hosts_real ''
# printf '%s\n' "${COMPREPLY[@]}" | command sort -u --version-sort
# }
if ! declare -F __fzf_list_hosts > /dev/null; then
__fzf_list_hosts() {
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | command awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?%]') \
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts 2> /dev/null | command tr ',' '\n' | command tr -d '[' | command awk '{ print $1 " " $1 }') \
<(command grep -v '^\s*\(#\|$\)' /etc/hosts 2> /dev/null | command grep -Fv '0.0.0.0' | command sed 's/#.*//') |
command awk '{for (i = 2; i <= NF; i++) print $i}' | command sort -u
}
fi
_fzf_host_completion() {
_fzf_complete +m -- "$@" < <(
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?]') \
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
awk '{if (length($2) > 0) {print $2}}' | sort -u
)
_fzf_complete +m -- "$@" < <(__fzf_list_hosts)
}
# Values for $1 $2 $3 are described here
# https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion.html
# > the first argument ($1) is the name of the command whose arguments are being completed,
# > the second argument ($2) is the word being completed,
# > and the third argument ($3) is the word preceding the word being completed on the current command line.
_fzf_complete_ssh() {
case $3 in
-i|-F|-E)
_fzf_path_completion "$@"
;;
*)
local user=
[[ "$2" =~ '@' ]] && user="${2%%@*}@"
_fzf_complete +m -- "$@" < <(__fzf_list_hosts | command awk -v user="$user" '{print user $0}')
;;
esac
}
_fzf_var_completion() {
_fzf_complete -m -- "$@" < <(
declare -xp | sed 's/=.*//' | sed 's/.* //'
declare -xp | command sed -En 's|^declare [^ ]+ ([^=]+).*|\1|p'
)
}
_fzf_alias_completion() {
_fzf_complete -m -- "$@" < <(
alias | sed 's/=.*//' | sed 's/.* //'
alias | command sed -En 's|^alias ([^=]+).*|\1|p'
)
}
@@ -303,8 +504,8 @@ complete -o default -F _fzf_opts_completion fzf-tmux
d_cmds="${FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir}"
a_cmds="
awk cat diff diff3
emacs emacsclient ex file ftp g++ gcc gvim head hg java
awk bat cat diff diff3
emacs emacsclient ex file ftp g++ gcc gvim head hg hx java
javac ld less more mvim nvim patch perl python ruby
sed sftp sort source tail tee uniq vi view vim wc xdg-open
basename bunzip2 bzip2 chmod chown curl cp dirname du
@@ -313,22 +514,27 @@ a_cmds="
svn tar unzip zip"
# Preserve existing completion
__fzf_orig_completion < <(complete -p $d_cmds $a_cmds 2> /dev/null)
__fzf_orig_completion < <(complete -p $d_cmds $a_cmds ssh 2> /dev/null)
if type _completion_loader > /dev/null 2>&1; then
_fzf_completion_loader=1
if type _comp_load > /dev/null 2>&1; then
# _comp_load was added in bash-completion 2.12 to replace _completion_loader.
# We use it without -D option so that it does not use _comp_complete_minimal as the fallback.
_fzf_completion_loader=_comp_load
elif type __load_completion > /dev/null 2>&1; then
# In bash-completion 2.11, _completion_loader internally calls __load_completion
# and if it returns a non-zero status, it sets the default 'minimal' completion.
_fzf_completion_loader=__load_completion
elif type _completion_loader > /dev/null 2>&1; then
_fzf_completion_loader=_completion_loader
fi
__fzf_defc() {
local cmd func opts orig_var orig def
local cmd func opts REPLY
cmd="$1"
func="$2"
opts="$3"
orig_var="_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}"
orig="${!orig_var}"
if [[ -n "$orig" ]]; then
printf -v def "$orig" "$func"
eval "$def"
if __fzf_orig_completion_instantiate "$cmd" "$func"; then
eval "$REPLY"
else
complete -F "$func" $opts "$cmd"
fi
@@ -344,6 +550,9 @@ for cmd in $d_cmds; do
__fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames"
done
# ssh
__fzf_defc ssh _fzf_complete_ssh "-o default -o bashdefault"
unset cmd d_cmds a_cmds
_fzf_setup_completion() {
@@ -367,9 +576,7 @@ _fzf_setup_completion() {
}
# Environment variables / Aliases / Hosts / Process
_fzf_setup_completion 'var' export unset
_fzf_setup_completion 'var' export unset printenv
_fzf_setup_completion 'alias' unalias
_fzf_setup_completion 'host' ssh telnet
_fzf_setup_completion 'host' telnet
_fzf_setup_completion 'proc' kill
fi

View File

@@ -9,6 +9,9 @@
# - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty)
[[ -o interactive ]] || return 0
# Both branches of the following `if` do the same thing -- define
# __fzf_completion_options such that `eval $__fzf_completion_options` sets
# all options to the same values they currently have. We'll do just that at
@@ -67,41 +70,35 @@ fi
# control. There are several others that could wreck havoc if they are set
# to values we don't expect. With the following `emulate` command we
# sidestep this issue entirely.
'emulate' 'zsh' '-o' 'no_aliases'
'builtin' 'emulate' 'zsh' && 'builtin' 'setopt' 'no_aliases'
# This brace is the start of try-always block. The `always` part is like
# `finally` in lesser languages. We use it to *always* restore user options.
{
# Bail out if not interactive shell.
[[ -o interactive ]] || return 0
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
if ! declare -f _fzf_compgen_path > /dev/null; then
_fzf_compgen_path() {
echo "$1"
command find -L "$1" \
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
}
fi
if ! declare -f _fzf_compgen_dir > /dev/null; then
_fzf_compgen_dir() {
command find -L "$1" \
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
}
fi
#
# _fzf_compgen_path() {
# echo "$1"
# command find -L "$1" \
# -name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
# -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
# }
#
# _fzf_compgen_dir() {
# command find -L "$1" \
# -name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
# -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
# }
###########################################################
__fzf_comprun() {
if [[ "$(type _fzf_comprun 2>&1)" =~ function ]]; then
_fzf_comprun "$@"
elif [ -n "$TMUX_PANE" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "$FZF_TMUX_OPTS" ]; }; then
elif [ -n "${TMUX_PANE-}" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "${FZF_TMUX_OPTS-}" ]; }; then
shift
if [ -n "$FZF_TMUX_OPTS" ]; then
if [ -n "${FZF_TMUX_OPTS-}" ]; then
fzf-tmux ${(Q)${(Z+n+)FZF_TMUX_OPTS}} -- "$@"
else
fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%} -- "$@"
@@ -137,7 +134,10 @@ __fzf_generic_path_completion() {
tail=$6
setopt localoptions nonomatch
eval "base=$base"
if [[ $base = *'$('* ]] || [[ $base = *'<('* ]] || [[ $base = *'>('* ]] || [[ $base = *':='* ]] || [[ $base = *'`'* ]]; then
return
fi
eval "base=$base" 2> /dev/null || return
[[ $base = *"/"* ]] && dir="$base"
while [ 1 ]; do
if [[ -z "$dir" || -d ${dir} ]]; then
@@ -145,9 +145,19 @@ __fzf_generic_path_completion() {
leftover=${leftover/#\/}
[ -z "$dir" ] && dir='.'
[ "$dir" != "/" ] && dir="${dir/%\//}"
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
echo -n "${(q)item}$suffix "
done)
matches=$(
unset FZF_DEFAULT_COMMAND
export FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-}"
if declare -f "$compgen" > /dev/null; then
eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover"
else
[[ $compgen =~ dir ]] && walker=dir,follow || walker=file,dir,follow,hidden
__fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" --walker "$walker" --walker-root="$dir" < /dev/tty
fi | while read item; do
item="${item%$suffix}$suffix"
echo -n "${(q)item} "
done
)
matches=${matches% }
if [ -n "$matches" ]; then
LBUFFER="$lbuf$matches$tail"
@@ -183,7 +193,7 @@ _fzf_complete() {
args=("$@")
sep=
for i in {0..${#args[@]}}; do
if [[ "${args[$i]}" = -- ]]; then
if [[ "${args[$i]-}" = -- ]]; then
sep=$i
break
fi
@@ -207,28 +217,44 @@ _fzf_complete() {
type $post > /dev/null 2>&1 || post=cat
_fzf_feed_fifo "$fifo"
matches=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
matches=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $str_arg" __fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
if [ -n "$matches" ]; then
LBUFFER="$lbuf$matches"
fi
command rm -f "$fifo"
}
# To use custom hostname lists, override __fzf_list_hosts.
# The function is expected to print hostnames, one per line as well as in the
# desired sorting and with any duplicates removed, to standard output.
if ! declare -f __fzf_list_hosts > /dev/null; then
__fzf_list_hosts() {
setopt localoptions nonomatch
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?%]') \
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts 2> /dev/null | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
<(command grep -v '^\s*\(#\|$\)' /etc/hosts 2> /dev/null | command grep -Fv '0.0.0.0' | command sed 's/#.*//') |
awk '{for (i = 2; i <= NF; i++) print $i}' | sort -u
}
fi
_fzf_complete_telnet() {
_fzf_complete +m -- "$@" < <(
command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' |
awk '{if (length($2) > 0) {print $2}}' | sort -u
)
_fzf_complete +m -- "$@" < <(__fzf_list_hosts)
}
# The first and the only argument is the LBUFFER without the current word that contains the trigger.
# The current word without the trigger is in the $prefix variable passed from the caller.
_fzf_complete_ssh() {
_fzf_complete +m -- "$@" < <(
setopt localoptions nonomatch
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?]') \
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
awk '{if (length($2) > 0) {print $2}}' | sort -u
)
local tokens=(${(z)1})
case ${tokens[-1]} in
-i|-F|-E)
_fzf_path_completion "$prefix" "$1"
;;
*)
local user=
[[ $prefix =~ @ ]] && user="${prefix%%@*}@"
_fzf_complete +m -- "$@" < <(__fzf_list_hosts | awk -v user="$user" '{print user $0}')
;;
esac
}
_fzf_complete_export() {
@@ -250,8 +276,9 @@ _fzf_complete_unalias() {
}
_fzf_complete_kill() {
_fzf_complete -m --preview 'echo {}' --preview-window down:3:wrap --min-height 15 -- "$@" < <(
command ps -ef | sed 1d
_fzf_complete -m --header-lines=1 --preview 'echo {}' --preview-window down:3:wrap --min-height 15 -- "$@" < <(
command ps -eo user,pid,ppid,start,time,command 2> /dev/null ||
command ps -eo user,pid,ppid,time,args # For BusyBox
)
}
@@ -278,8 +305,8 @@ fzf-completion() {
[ -z "$trigger" -a ${LBUFFER[-1]} = ' ' ] && tokens+=("")
# When the trigger starts with ';', it becomes a separate token
if [[ ${LBUFFER} = *"${tokens[-2]}${tokens[-1]}" ]]; then
tokens[-2]="${tokens[-2]}${tokens[-1]}"
if [[ ${LBUFFER} = *"${tokens[-2]-}${tokens[-1]}" ]]; then
tokens[-2]="${tokens[-2]-}${tokens[-1]}"
tokens=(${tokens[0,-2]})
fi
@@ -291,6 +318,9 @@ fzf-completion() {
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
if [[ $prefix = *'$('* ]] || [[ $prefix = *'<('* ]] || [[ $prefix = *'>('* ]] || [[ $prefix = *':='* ]] || [[ $prefix = *'`'* ]]; then
return
fi
[ -n "${tokens[-1]}" ] && lbuf=${lbuf:0:-${#tokens[-1]}}
if eval "type _fzf_complete_${cmd} > /dev/null"; then

View File

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

View File

@@ -11,6 +11,9 @@
# - $FZF_ALT_C_COMMAND
# - $FZF_ALT_C_OPTS
status is-interactive; or exit 0
# Key bindings
# ------------
function fzf_key_bindings
@@ -22,18 +25,11 @@ function fzf_key_bindings
set -l fzf_query $commandline[2]
set -l prefix $commandline[3]
# "-path \$dir'*/\\.*'" matches hidden files/folders inside $dir but not
# $dir itself, even if hidden.
test -n "$FZF_CTRL_T_COMMAND"; or set -l FZF_CTRL_T_COMMAND "
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | sed 's@^\./@@'"
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --walker=file,dir,follow,hidden --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND"
eval (__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
end
if [ -z "$result" ]
commandline -f repaint
@@ -53,7 +49,7 @@ function fzf_key_bindings
function fzf-history-widget -d "Show command history"
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS +m"
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --scheme=history --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS +m"
set -l FISH_MAJOR (echo $version | cut -f1 -d.)
set -l FISH_MINOR (echo $version | cut -f2 -d.)
@@ -78,16 +74,14 @@ function fzf_key_bindings
set -l fzf_query $commandline[2]
set -l prefix $commandline[3]
test -n "$FZF_ALT_C_COMMAND"; or set -l FZF_ALT_C_COMMAND "
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
-o -type d -print 2> /dev/null | sed 's@^\./@@'"
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --walker=dir,follow,hidden --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND"
eval (__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
if [ -n "$result" ]
builtin cd -- $result
cd -- $result
# Remove last token from commandline.
commandline -t ""
@@ -110,14 +104,22 @@ function fzf_key_bindings
end
end
bind \ct fzf-file-widget
bind \cr fzf-history-widget
bind \ec fzf-cd-widget
if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND"
bind \ct fzf-file-widget
end
if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND"
bind \ec fzf-cd-widget
end
if bind -M insert > /dev/null 2>&1
bind -M insert \ct fzf-file-widget
bind -M insert \cr fzf-history-widget
bind -M insert \ec fzf-cd-widget
if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND"
bind -M insert \ct fzf-file-widget
end
if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND"
bind -M insert \ec fzf-cd-widget
end
end
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix'

View File

@@ -11,6 +11,9 @@
# - $FZF_ALT_C_COMMAND
# - $FZF_ALT_C_OPTS
[[ -o interactive ]] || return 0
# Key bindings
# ------------
@@ -32,21 +35,15 @@ else
}
fi
'emulate' 'zsh' '-o' 'no_aliases'
'builtin' 'emulate' 'zsh' && 'builtin' 'setopt' 'no_aliases'
{
[[ -o interactive ]] || return 0
# CTRL-T - Paste the selected file path(s) into the command line
__fsel() {
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | cut -b3-"}"
setopt localoptions pipefail no_aliases 2> /dev/null
local item
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read item; do
FZF_DEFAULT_COMMAND=${FZF_CTRL_T_COMMAND:-} FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --walker=file,dir,follow,hidden --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-}" $(__fzfcmd) -m "$@" < /dev/tty | while read item; do
echo -n "${(q)item} "
done
local ret=$?
@@ -55,7 +52,7 @@ __fsel() {
}
__fzfcmd() {
[ -n "$TMUX_PANE" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "$FZF_TMUX_OPTS" ]; } &&
[ -n "${TMUX_PANE-}" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "${FZF_TMUX_OPTS-}" ]; } &&
echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
}
@@ -65,17 +62,17 @@ fzf-file-widget() {
zle reset-prompt
return $ret
}
zle -N fzf-file-widget
bindkey -M emacs '^T' fzf-file-widget
bindkey -M vicmd '^T' fzf-file-widget
bindkey -M viins '^T' fzf-file-widget
if [[ "${FZF_CTRL_T_COMMAND-x}" != "" ]]; then
zle -N fzf-file-widget
bindkey -M emacs '^T' fzf-file-widget
bindkey -M vicmd '^T' fzf-file-widget
bindkey -M viins '^T' fzf-file-widget
fi
# ALT-C - cd into the selected directory
fzf-cd-widget() {
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
-o -type d -print 2> /dev/null | cut -b3-"}"
setopt localoptions pipefail no_aliases 2> /dev/null
local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m)"
local dir="$(FZF_DEFAULT_COMMAND=${FZF_ALT_C_COMMAND:-} FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --walker=dir,follow,hidden --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-}" $(__fzfcmd) +m < /dev/tty)"
if [[ -z "$dir" ]]; then
zle redisplay
return 0
@@ -88,22 +85,26 @@ fzf-cd-widget() {
zle reset-prompt
return $ret
}
zle -N fzf-cd-widget
bindkey -M emacs '\ec' fzf-cd-widget
bindkey -M vicmd '\ec' fzf-cd-widget
bindkey -M viins '\ec' fzf-cd-widget
if [[ "${FZF_ALT_C_COMMAND-x}" != "" ]]; then
zle -N fzf-cd-widget
bindkey -M emacs '\ec' fzf-cd-widget
bindkey -M vicmd '\ec' fzf-cd-widget
bindkey -M viins '\ec' fzf-cd-widget
fi
# CTRL-R - Paste the selected command from history into the command line
fzf-history-widget() {
local selected num
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
selected=( $(fc -rl 1 | awk '{ cmd=$0; sub(/^\s*[0-9]+\**\s+/, "", cmd); if (!seen[cmd]++) print $0 }' |
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
selected="$(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} ${FZF_DEFAULT_OPTS-} -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,ctrl-z:ignore ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m" $(__fzfcmd))"
local ret=$?
if [ -n "$selected" ]; then
num=$selected[1]
if [ -n "$num" ]; then
zle vi-fetch-history -n $num
num=$(awk '{print $1}' <<< "$selected")
if [[ "$num" =~ '^[1-9][0-9]*\*?$' ]]; then
zle vi-fetch-history -n ${num%\*}
else # selected is a custom query, not from history
LBUFFER="$selected"
fi
fi
zle reset-prompt

View File

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

129
src/actiontype_string.go Normal file
View File

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

View File

@@ -80,6 +80,7 @@ Scoring criteria
import (
"bytes"
"fmt"
"os"
"strings"
"unicode"
"unicode/utf8"
@@ -89,6 +90,10 @@ import (
var DEBUG bool
var delimiterChars = "/,:;|"
const whiteChars = " \t\n\v\f\r\x85\xA0"
func indexAt(index int, max int, forward bool) int {
if forward {
return index
@@ -140,16 +145,51 @@ const (
bonusFirstCharMultiplier = 2
)
var (
// Extra bonus for word boundary after whitespace character or beginning of the string
bonusBoundaryWhite int16 = bonusBoundary + 2
// Extra bonus for word boundary after slash, colon, semi-colon, and comma
bonusBoundaryDelimiter int16 = bonusBoundary + 1
initialCharClass charClass = charWhite
)
type charClass int
const (
charNonWord charClass = iota
charWhite charClass = iota
charNonWord
charDelimiter
charLower
charUpper
charLetter
charNumber
)
func Init(scheme string) bool {
switch scheme {
case "default":
bonusBoundaryWhite = bonusBoundary + 2
bonusBoundaryDelimiter = bonusBoundary + 1
case "path":
bonusBoundaryWhite = bonusBoundary
bonusBoundaryDelimiter = bonusBoundary + 1
if os.PathSeparator == '/' {
delimiterChars = "/"
} else {
delimiterChars = string([]rune{os.PathSeparator, '/'})
}
initialCharClass = charDelimiter
case "history":
bonusBoundaryWhite = bonusBoundary
bonusBoundaryDelimiter = bonusBoundary
default:
return false
}
return true
}
func posArray(withPos bool, len int) *[]int {
if withPos {
pos := make([]int, 0, len)
@@ -181,6 +221,10 @@ func charClassOfAscii(char rune) charClass {
return charUpper
} else if char >= '0' && char <= '9' {
return charNumber
} else if strings.ContainsRune(whiteChars, char) {
return charWhite
} else if strings.ContainsRune(delimiterChars, char) {
return charDelimiter
}
return charNonWord
}
@@ -194,6 +238,10 @@ func charClassOfNonAscii(char rune) charClass {
return charNumber
} else if unicode.IsLetter(char) {
return charLetter
} else if unicode.IsSpace(char) {
return charWhite
} else if strings.ContainsRune(delimiterChars, char) {
return charDelimiter
}
return charNonWord
}
@@ -206,22 +254,38 @@ func charClassOf(char rune) charClass {
}
func bonusFor(prevClass charClass, class charClass) int16 {
if prevClass == charNonWord && class != charNonWord {
// Word boundary
return bonusBoundary
} else if prevClass == charLower && class == charUpper ||
if class > charNonWord {
switch prevClass {
case charWhite:
// Word boundary after whitespace
return bonusBoundaryWhite
case charDelimiter:
// Word boundary after a delimiter character
return bonusBoundaryDelimiter
case charNonWord:
// Word boundary
return bonusBoundary
}
}
if prevClass == charLower && class == charUpper ||
prevClass != charNumber && class == charNumber {
// camelCase letter123
return bonusCamel123
} else if class == charNonWord {
}
switch class {
case charNonWord, charDelimiter:
return bonusNonWord
case charWhite:
return bonusBoundaryWhite
}
return 0
}
func bonusAt(input *util.Chars, idx int) int16 {
if idx == 0 {
return bonusBoundary
return bonusBoundaryWhite
}
return bonusFor(charClassOf(input.Get(idx-1)), charClassOf(input.Get(idx)))
}
@@ -377,7 +441,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
// Phase 2. Calculate bonus for each point
maxScore, maxScorePos := int16(0), 0
pidx, lastIdx := 0, 0
pchar0, pchar, prevH0, prevClass, inGap := pattern[0], pattern[0], int16(0), charNonWord, false
pchar0, pchar, prevH0, prevClass, inGap := pattern[0], pattern[0], int16(0), initialCharClass, false
Tsub := T[idx:]
H0sub, C0sub, Bsub := H0[idx:][:len(Tsub)], C0[idx:][:len(Tsub)], B[idx:][:len(Tsub)]
for off, char := range Tsub {
@@ -417,7 +481,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
C0sub[off] = 1
if M == 1 && (forward && score > maxScore || !forward && score >= maxScore) {
maxScore, maxScorePos = score, idx+off
if forward && bonus == bonusBoundary {
if forward && bonus >= bonusBoundary {
break
}
}
@@ -486,11 +550,14 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
s1 = Hdiag[off] + scoreMatch
b := Bsub[off]
consecutive = Cdiag[off] + 1
// Break consecutive chunk
if b == bonusBoundary {
consecutive = 1
} else if consecutive > 1 {
b = util.Max16(b, util.Max16(bonusConsecutive, B[col-int(consecutive)+1]))
if consecutive > 1 {
fb := B[col-int(consecutive)+1]
// Break consecutive chunk
if b >= bonusBoundary && b > fb {
consecutive = 1
} else {
b = util.Max16(b, util.Max16(bonusConsecutive, fb))
}
}
if s1+b < s2 {
s1 += Bsub[off]
@@ -555,7 +622,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
func calculateScore(caseSensitive bool, normalize bool, text *util.Chars, pattern []rune, sidx int, eidx int, withPos bool) (int, *[]int) {
pidx, score, inGap, consecutive, firstBonus := 0, 0, false, 0, int16(0)
pos := posArray(withPos, len(pattern))
prevClass := charNonWord
prevClass := initialCharClass
if sidx > 0 {
prevClass = charClassOf(text.Get(sidx - 1))
}
@@ -583,7 +650,7 @@ func calculateScore(caseSensitive bool, normalize bool, text *util.Chars, patter
firstBonus = bonus
} else {
// Break consecutive chunk
if bonus == bonusBoundary {
if bonus >= bonusBoundary && bonus > firstBonus {
firstBonus = bonus
}
bonus = util.Max16(util.Max16(bonus, firstBonus), bonusConsecutive)
@@ -741,7 +808,7 @@ func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *uti
if bonus > bestBonus {
bestPos, bestBonus = index, bonus
}
if bonus == bonusBoundary {
if bonus >= bonusBoundary {
break
}
index -= pidx - 1
@@ -877,8 +944,8 @@ func EqualMatch(caseSensitive bool, normalize bool, forward bool, text *util.Cha
match = runesStr == string(pattern)
}
if match {
return Result{trimmedLen, trimmedLen + lenPattern, (scoreMatch+bonusBoundary)*lenPattern +
(bonusFirstCharMultiplier-1)*bonusBoundary}, nil
return Result{trimmedLen, trimmedLen + lenPattern, (scoreMatch+int(bonusBoundaryWhite))*lenPattern +
(bonusFirstCharMultiplier-1)*int(bonusBoundaryWhite)}, nil
}
return Result{-1, -1, 0}, nil
}

View File

@@ -45,29 +45,29 @@ func TestFuzzyMatch(t *testing.T) {
assertMatch(t, fn, false, forward, "fooBarbaz1", "oBZ", 2, 9,
scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtension*3)
assertMatch(t, fn, false, forward, "foo bar baz", "fbb", 0, 9,
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+
bonusBoundary*2+2*scoreGapStart+4*scoreGapExtension)
scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+
int(bonusBoundaryWhite)*2+2*scoreGapStart+4*scoreGapExtension)
assertMatch(t, fn, false, forward, "/AutomatorDocument.icns", "rdoc", 9, 13,
scoreMatch*4+bonusCamel123+bonusConsecutive*2)
assertMatch(t, fn, false, forward, "/man1/zshcompctl.1", "zshc", 6, 10,
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*3)
scoreMatch*4+int(bonusBoundaryDelimiter)*bonusFirstCharMultiplier+int(bonusBoundaryDelimiter)*3)
assertMatch(t, fn, false, forward, "/.oh-my-zsh/cache", "zshc", 8, 13,
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*3+scoreGapStart)
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*2+scoreGapStart+int(bonusBoundaryDelimiter))
assertMatch(t, fn, false, forward, "ab0123 456", "12356", 3, 10,
scoreMatch*5+bonusConsecutive*3+scoreGapStart+scoreGapExtension)
assertMatch(t, fn, false, forward, "abc123 456", "12356", 3, 10,
scoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+bonusConsecutive+scoreGapStart+scoreGapExtension)
assertMatch(t, fn, false, forward, "foo/bar/baz", "fbb", 0, 9,
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+
bonusBoundary*2+2*scoreGapStart+4*scoreGapExtension)
scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+
int(bonusBoundaryDelimiter)*2+2*scoreGapStart+4*scoreGapExtension)
assertMatch(t, fn, false, forward, "fooBarBaz", "fbb", 0, 7,
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+
scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+
bonusCamel123*2+2*scoreGapStart+2*scoreGapExtension)
assertMatch(t, fn, false, forward, "foo barbaz", "fbb", 0, 8,
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary+
scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryWhite)+
scoreGapStart*2+scoreGapExtension*3)
assertMatch(t, fn, false, forward, "fooBar Baz", "foob", 0, 4,
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*3)
scoreMatch*4+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryWhite)*3)
assertMatch(t, fn, false, forward, "xFoo-Bar Baz", "foo-b", 1, 6,
scoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+
bonusNonWord+bonusBoundary)
@@ -75,14 +75,14 @@ func TestFuzzyMatch(t *testing.T) {
assertMatch(t, fn, true, forward, "fooBarbaz", "oBz", 2, 9,
scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtension*3)
assertMatch(t, fn, true, forward, "Foo/Bar/Baz", "FBB", 0, 9,
scoreMatch*3+bonusBoundary*(bonusFirstCharMultiplier+2)+
scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryDelimiter)*2+
scoreGapStart*2+scoreGapExtension*4)
assertMatch(t, fn, true, forward, "FooBarBaz", "FBB", 0, 7,
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+bonusCamel123*2+
scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+bonusCamel123*2+
scoreGapStart*2+scoreGapExtension*2)
assertMatch(t, fn, true, forward, "FooBar Baz", "FooB", 0, 4,
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*2+
util.Max(bonusCamel123, bonusBoundary))
scoreMatch*4+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryWhite)*2+
util.Max(bonusCamel123, int(bonusBoundaryWhite)))
// Consecutive bonus updated
assertMatch(t, fn, true, forward, "foo-bar", "o-ba", 2, 6,
@@ -98,10 +98,10 @@ func TestFuzzyMatch(t *testing.T) {
func TestFuzzyMatchBackward(t *testing.T) {
assertMatch(t, FuzzyMatchV1, false, true, "foobar fb", "fb", 0, 4,
scoreMatch*2+bonusBoundary*bonusFirstCharMultiplier+
scoreMatch*2+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+
scoreGapStart+scoreGapExtension)
assertMatch(t, FuzzyMatchV1, false, false, "foobar fb", "fb", 7, 9,
scoreMatch*2+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary)
scoreMatch*2+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryWhite))
}
func TestExactMatchNaive(t *testing.T) {
@@ -114,9 +114,9 @@ func TestExactMatchNaive(t *testing.T) {
assertMatch(t, ExactMatchNaive, false, dir, "/AutomatorDocument.icns", "rdoc", 9, 13,
scoreMatch*4+bonusCamel123+bonusConsecutive*2)
assertMatch(t, ExactMatchNaive, false, dir, "/man1/zshcompctl.1", "zshc", 6, 10,
scoreMatch*4+bonusBoundary*(bonusFirstCharMultiplier+3))
scoreMatch*4+int(bonusBoundaryDelimiter)*(bonusFirstCharMultiplier+3))
assertMatch(t, ExactMatchNaive, false, dir, "/.oh-my-zsh/cache", "zsh/c", 8, 13,
scoreMatch*5+bonusBoundary*(bonusFirstCharMultiplier+4))
scoreMatch*5+bonusBoundary*(bonusFirstCharMultiplier+3)+int(bonusBoundaryDelimiter))
}
}
@@ -128,7 +128,7 @@ func TestExactMatchNaiveBackward(t *testing.T) {
}
func TestPrefixMatch(t *testing.T) {
score := (scoreMatch+bonusBoundary)*3 + bonusBoundary*(bonusFirstCharMultiplier-1)
score := scoreMatch*3 + int(bonusBoundaryWhite)*bonusFirstCharMultiplier + int(bonusBoundaryWhite)*2
for _, dir := range []bool{true, false} {
assertMatch(t, PrefixMatch, true, dir, "fooBarbaz", "Foo", -1, -1, 0)
@@ -156,9 +156,10 @@ func TestSuffixMatch(t *testing.T) {
// Strip trailing white space from the string
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz ", "baz", 6, 9,
scoreMatch*3+bonusConsecutive*2)
// Only when the pattern doesn't end with a space
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz ", "baz ", 6, 10,
scoreMatch*4+bonusConsecutive*2+bonusNonWord)
scoreMatch*4+bonusConsecutive*2+int(bonusBoundaryWhite))
}
}
@@ -182,9 +183,9 @@ func TestNormalize(t *testing.T) {
input, pattern, sidx, eidx, score)
}
}
test("Só Danço Samba", "So", 0, 2, 56, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, ExactMatchNaive)
test("Só Danço Samba", "sodc", 0, 7, 89, FuzzyMatchV1, FuzzyMatchV2)
test("Danço", "danco", 0, 5, 128, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, SuffixMatch, ExactMatchNaive, EqualMatch)
test("Só Danço Samba", "So", 0, 2, 62, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, ExactMatchNaive)
test("Só Danço Samba", "sodc", 0, 7, 97, FuzzyMatchV1, FuzzyMatchV2)
test("Danço", "danco", 0, 5, 140, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, SuffixMatch, ExactMatchNaive, EqualMatch)
}
func TestLongString(t *testing.T) {

View File

@@ -55,6 +55,9 @@ func (s *ansiState) ToString() string {
if s.attr&tui.Reverse > 0 {
ret += "7;"
}
if s.attr&tui.StrikeThrough > 0 {
ret += "9;"
}
ret += toAnsiString(s.fg, 30) + toAnsiString(s.bg, 40)
return "\x1b[" + strings.TrimSuffix(ret, ";") + "m"
@@ -85,7 +88,7 @@ func isPrint(c uint8) bool {
}
func matchOperatingSystemCommand(s string) int {
// `\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)`
// `\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)`
// ^ match starting here
//
i := 5 // prefix matched in nextAnsiEscapeSequence()
@@ -103,30 +106,37 @@ func matchOperatingSystemCommand(s string) int {
}
func matchControlSequence(s string) int {
// `\x1b[\\[()][0-9;?]*[a-zA-Z@]`
// ^ match starting here
// `\x1b[\\[()][0-9;:?]*[a-zA-Z@]`
// ^ match starting here
//
i := 2 // prefix matched in nextAnsiEscapeSequence()
for ; i < len(s) && (isNumeric(s[i]) || s[i] == ';' || s[i] == '?'); i++ {
}
if i < len(s) {
for ; i < len(s); i++ {
c := s[i]
if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '@' {
return i + 1
switch c {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ';', ':', '?':
// ok
default:
if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '@' {
return i + 1
}
return -1
}
}
return -1
}
func isCtrlSeqStart(c uint8) bool {
return c == '\\' || c == '[' || c == '(' || c == ')'
switch c {
case '\\', '[', '(', ')':
return true
}
return false
}
// nextAnsiEscapeSequence returns the ANSI escape sequence and is equivalent to
// calling FindStringIndex() on the below regex (which was originally used):
//
// "(?:\x1b[\\[()][0-9;?]*[a-zA-Z@]|\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)"
//
// "(?:\x1b[\\[()][0-9;:?]*[a-zA-Z@]|\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)"
func nextAnsiEscapeSequence(s string) (int, int) {
// fast check for ANSI escape sequences
i := 0
@@ -154,16 +164,16 @@ Loop:
return i - n, i + 1
}
case '\x1b':
// match: `\x1b[\\[()][0-9;?]*[a-zA-Z@]`
// match: `\x1b[\\[()][0-9;:?]*[a-zA-Z@]`
if i+2 < len(s) && isCtrlSeqStart(s[i+1]) {
if j := matchControlSequence(s[i:]); j != -1 {
return i, i + j
}
}
// match: `\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)`
// match: `\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)`
if i+5 < len(s) && s[i+1] == ']' && isNumeric(s[i+2]) &&
s[i+3] == ';' && isPrint(s[i+4]) {
(s[i+3] == ';' || s[i+3] == ':') && isPrint(s[i+4]) {
if j := matchOperatingSystemCommand(s[i:]); j != -1 {
return i, i + j
@@ -280,9 +290,20 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
return trimmed, nil, state
}
func parseAnsiCode(s string) (int, string) {
func parseAnsiCode(s string, delimiter byte) (int, byte, string) {
var remaining string
if i := strings.IndexByte(s, ';'); i >= 0 {
i := -1
if delimiter == 0 {
// Faster than strings.IndexAny(";:")
i = strings.IndexByte(s, ';')
if i < 0 {
i = strings.IndexByte(s, ':')
}
} else {
i = strings.IndexByte(s, delimiter)
}
if i >= 0 {
delimiter = s[i]
remaining = s[i+1:]
s = s[:i]
}
@@ -294,14 +315,14 @@ func parseAnsiCode(s string) (int, string) {
for _, ch := range []byte(s) {
ch -= '0'
if ch > 9 {
return -1, remaining
return -1, delimiter, remaining
}
code = code*10 + int(ch)
}
return code, remaining
return code, delimiter, remaining
}
return -1, remaining
return -1, delimiter, remaining
}
func interpretCode(ansiCode string, prevState *ansiState) ansiState {
@@ -329,9 +350,12 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
state256 := 0
ptr := &state.fg
var delimiter byte = 0
count := 0
for len(ansiCode) != 0 {
var num int
if num, ansiCode = parseAnsiCode(ansiCode); num != -1 {
if num, delimiter, ansiCode = parseAnsiCode(ansiCode, delimiter); num != -1 {
count++
switch state256 {
case 0:
switch num {
@@ -357,10 +381,21 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
state.attr = state.attr | tui.Blink
case 7:
state.attr = state.attr | tui.Reverse
case 9:
state.attr = state.attr | tui.StrikeThrough
case 22:
state.attr = state.attr &^ tui.Bold
state.attr = state.attr &^ tui.Dim
case 23: // tput rmso
state.attr = state.attr &^ tui.Italic
case 24: // tput rmul
state.attr = state.attr &^ tui.Underline
case 25:
state.attr = state.attr &^ tui.Blink
case 27:
state.attr = state.attr &^ tui.Reverse
case 29:
state.attr = state.attr &^ tui.StrikeThrough
case 0:
state.fg = -1
state.bg = -1
@@ -402,6 +437,13 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
}
}
// Empty sequence: reset
if count == 0 {
state.fg = -1
state.bg = -1
state.attr = 0
}
if state256 > 0 {
*ptr = -1
}

View File

@@ -15,14 +15,14 @@ import (
// testing nextAnsiEscapeSequence().
//
// References:
// - https://github.com/gnachman/iTerm2
// - https://web.archive.org/web/20090204053813/http://ascii-table.com/ansi-escape-sequences.php
// (archived from http://ascii-table.com/ansi-escape-sequences.php)
// - https://web.archive.org/web/20090227051140/http://ascii-table.com/ansi-escape-sequences-vt-100.php
// (archived from http://ascii-table.com/ansi-escape-sequences-vt-100.php)
// - http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
// - https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
var ansiRegexReference = regexp.MustCompile("(?:\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)")
// - https://github.com/gnachman/iTerm2
// - https://web.archive.org/web/20090204053813/http://ascii-table.com/ansi-escape-sequences.php
// (archived from http://ascii-table.com/ansi-escape-sequences.php)
// - https://web.archive.org/web/20090227051140/http://ascii-table.com/ansi-escape-sequences-vt-100.php
// (archived from http://ascii-table.com/ansi-escape-sequences-vt-100.php)
// - http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
// - https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
var ansiRegexReference = regexp.MustCompile("(?:\x1b[\\[()][0-9;:]*[a-zA-Z@]|\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)")
func testParserReference(t testing.TB, str string) {
t.Helper()
@@ -348,6 +348,9 @@ func TestAnsiCodeStringConversion(t *testing.T) {
}
assert("\x1b[m", nil, "")
assert("\x1b[m", &ansiState{attr: tui.Blink, lbg: -1}, "")
assert("\x1b[0m", &ansiState{fg: 4, bg: 4, lbg: -1}, "")
assert("\x1b[;m", &ansiState{fg: 4, bg: 4, lbg: -1}, "")
assert("\x1b[;;m", &ansiState{fg: 4, bg: 4, lbg: -1}, "")
assert("\x1b[31m", nil, "\x1b[31;49m")
assert("\x1b[41m", nil, "\x1b[39;41m")
@@ -358,6 +361,7 @@ func TestAnsiCodeStringConversion(t *testing.T) {
assert("\x1b[31m", &ansiState{fg: 4, bg: 4, lbg: -1}, "\x1b[31;44m")
assert("\x1b[1;2;31m", &ansiState{fg: 2, bg: -1, attr: tui.Reverse, lbg: -1}, "\x1b[1;2;7;31;49m")
assert("\x1b[38;5;100;48;5;200m", nil, "\x1b[38;5;100;48;5;200m")
assert("\x1b[38:5:100:48:5:200m", nil, "\x1b[38;5;100;48;5;200m")
assert("\x1b[48;5;100;38;5;200m", nil, "\x1b[38;5;200;48;5;100m")
assert("\x1b[48;5;100;38;2;10;20;30;1m", nil, "\x1b[1;38;2;10;20;30;48;5;100m")
assert("\x1b[48;5;100;38;2;10;20;30;7m",
@@ -377,7 +381,7 @@ func TestParseAnsiCode(t *testing.T) {
{"-2", "", -1},
}
for _, x := range tests {
n, s := parseAnsiCode(x.In)
n, _, s := parseAnsiCode(x.In, 0)
if n != x.N || s != x.Exp {
t.Fatalf("%q: got: (%d %q) want: (%d %q)", x.In, n, s, x.N, x.Exp)
}
@@ -385,9 +389,9 @@ func TestParseAnsiCode(t *testing.T) {
}
// kernel/bpf/preload/iterators/README
const ansiBenchmarkString = "\x1b[38;5;81m\x1b[01;31m\x1b[Kkernel/\x1b[0m\x1b[38;5;81mbpf/" +
"\x1b[0m\x1b[38;5;81mpreload/\x1b[0m\x1b[38;5;81miterators/" +
"\x1b[0m\x1b[38;5;149mMakefile\x1b[m\x1b[K\x1b[0m"
const ansiBenchmarkString = "\x1b[38;5;81m\x1b[01;31m\x1b[Kkernel/\x1b[0m\x1b[38:5:81mbpf/" +
"\x1b[0m\x1b[38:5:81mpreload/\x1b[0m\x1b[38;5;81miterators/" +
"\x1b[0m\x1b[38:5:149mMakefile\x1b[m\x1b[K\x1b[0m"
func BenchmarkNextAnsiEscapeSequence(b *testing.B) {
b.SetBytes(int64(len(ansiBenchmarkString)))

View File

@@ -2,7 +2,6 @@ package fzf
import (
"math"
"os"
"time"
"github.com/junegunn/fzf/src/util"
@@ -54,16 +53,6 @@ const (
defaultJumpLabels string = "asdfghjklqwertyuiopzxcvbnm1234567890ASDFGHJKLQWERTYUIOPZXCVBNM`~;:,<.>/?'\"!@#$%^&*()[{]}-_=+"
)
var defaultCommand string
func init() {
if !util.IsWindows() {
defaultCommand = `set -o pipefail; command find -L . -mindepth 1 \( -path '*/\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-`
} else if os.Getenv("TERM") == "cygwin" {
defaultCommand = `sh -c "command find -L . -mindepth 1 -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-"`
}
}
// fzf events
const (
EvtReadNew util.EventType = iota

View File

@@ -1,28 +1,4 @@
/*
Package fzf implements fzf, a command-line fuzzy finder.
The MIT License (MIT)
Copyright (c) 2013-2021 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// Package fzf implements fzf, a command-line fuzzy finder.
package fzf
import (
@@ -141,26 +117,30 @@ func Run(opts *Options, version string, revision string) {
reader = NewReader(func(data []byte) bool {
return chunkList.Push(data)
}, eventBox, opts.ReadZero, opts.Filter == nil)
go reader.ReadSource()
go reader.ReadSource(opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
}
// Matcher
forward := true
for _, cri := range opts.Criteria[1:] {
if cri == byEnd {
withPos := false
for idx := len(opts.Criteria) - 1; idx > 0; idx-- {
switch opts.Criteria[idx] {
case byChunk:
withPos = true
case byEnd:
forward = false
break
}
if cri == byBegin {
break
case byBegin:
forward = true
}
}
patternBuilder := func(runes []rune) *Pattern {
return BuildPattern(
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward,
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward, withPos,
opts.Filter == nil, opts.Nth, opts.Delimiter, runes)
}
matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox)
inputRevision := 0
snapshotRevision := 0
matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox, inputRevision)
// Filtering mode
if opts.Filter != nil {
@@ -185,7 +165,7 @@ func Run(opts *Options, version string, revision string) {
}
return false
}, eventBox, opts.ReadZero, false)
reader.ReadSource()
reader.ReadSource(opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
} else {
eventBox.Unwatch(EvtReadNew)
eventBox.WaitFor(EvtReadFin)
@@ -216,33 +196,56 @@ func Run(opts *Options, version string, revision string) {
// Terminal I/O
terminal := NewTerminal(opts, eventBox)
maxFit := 0 // Maximum number of items that can fit on screen
padHeight := 0
heightUnknown := opts.Height.auto
if heightUnknown {
maxFit, padHeight = terminal.MaxFitAndPad()
}
deferred := opts.Select1 || opts.Exit0
go terminal.Loop()
if !deferred {
terminal.startChan <- true
if !deferred && !heightUnknown {
// Start right away
terminal.startChan <- fitpad{-1, -1}
}
// Event coordination
reading := true
clearCache := util.Once(false)
clearSelection := util.Once(false)
ticks := 0
var nextCommand *string
restart := func(command string) {
var nextEnviron []string
eventBox.Watch(EvtReadNew)
total := 0
query := []rune{}
determine := func(final bool) {
if heightUnknown {
if total >= maxFit || final {
deferred = false
heightUnknown = false
terminal.startChan <- fitpad{util.Min(total, maxFit), padHeight}
}
} else if deferred {
deferred = false
terminal.startChan <- fitpad{-1, -1}
}
}
useSnapshot := false
var snapshot []*Chunk
var count int
restart := func(command string, environ []string) {
reading = true
clearCache = util.Once(true)
clearSelection = util.Once(true)
chunkList.Clear()
itemIndex = 0
inputRevision++
header = make([]string, 0, opts.HeaderLines)
go reader.restart(command)
go reader.restart(command, environ)
}
eventBox.Watch(EvtReadNew)
query := []rune{}
for {
delay := true
ticks++
input := func(reloaded bool) []rune {
input := func() []rune {
reloaded := snapshotRevision != inputRevision
paused, input := terminal.Input()
if reloaded && paused {
query = []rune{}
@@ -264,40 +267,67 @@ func Run(opts *Options, version string, revision string) {
os.Exit(value.(int))
case EvtReadNew, EvtReadFin:
if evt == EvtReadFin && nextCommand != nil {
restart(*nextCommand)
restart(*nextCommand, nextEnviron)
nextCommand = nil
nextEnviron = nil
break
} else {
reading = reading && evt == EvtReadNew
}
snapshot, count := chunkList.Snapshot()
terminal.UpdateCount(count, !reading, value.(*string))
if useSnapshot && evt == EvtReadFin {
useSnapshot = false
}
if !useSnapshot {
snapshot, count = chunkList.Snapshot()
snapshotRevision = inputRevision
}
total = count
terminal.UpdateCount(total, !reading, value.(*string))
if opts.Sync {
opts.Sync = false
terminal.UpdateList(PassMerger(&snapshot, opts.Tac), false)
terminal.UpdateList(PassMerger(&snapshot, opts.Tac, snapshotRevision))
}
reset := clearCache()
matcher.Reset(snapshot, input(reset), false, !reading, sort, reset)
if heightUnknown && !deferred {
determine(!reading)
}
matcher.Reset(snapshot, input(), false, !reading, sort, snapshotRevision)
case EvtSearchNew:
var command *string
var environ []string
var changed bool
switch val := value.(type) {
case searchRequest:
sort = val.sort
command = val.command
environ = val.environ
changed = val.changed
if command != nil {
useSnapshot = val.sync
}
}
if command != nil {
if reading {
reader.terminate()
nextCommand = command
nextEnviron = environ
} else {
restart(*command)
restart(*command, environ)
}
}
if !changed {
break
}
snapshot, _ := chunkList.Snapshot()
reset := clearCache()
matcher.Reset(snapshot, input(reset), true, !reading, sort, reset)
if !useSnapshot {
newSnapshot, _ := chunkList.Snapshot()
// We want to avoid showing empty list when reload is triggered
// and the query string is changed at the same time i.e. command != nil && changed
if command == nil || len(newSnapshot) > 0 {
snapshot = newSnapshot
snapshotRevision = inputRevision
}
}
matcher.Reset(snapshot, input(), true, !reading, sort, snapshotRevision)
delay = false
case EvtSearchProgress:
@@ -317,8 +347,7 @@ func Run(opts *Options, version string, revision string) {
if deferred {
count := val.Length()
if opts.Select1 && count > 1 || opts.Exit0 && !opts.Select1 && count > 0 {
deferred = false
terminal.startChan <- true
determine(val.final)
} else if val.final {
if opts.Exit0 && count == 0 || opts.Select1 && count == 1 {
if opts.PrintQuery {
@@ -335,11 +364,10 @@ func Run(opts *Options, version string, revision string) {
}
os.Exit(exitNoMatch)
}
deferred = false
terminal.startChan <- true
determine(val.final)
}
}
terminal.UpdateList(val, clearSelection())
terminal.UpdateList(val)
}
}
}

View File

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

View File

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

View File

@@ -12,11 +12,11 @@ import (
// MatchRequest represents a search request
type MatchRequest struct {
chunks []*Chunk
pattern *Pattern
final bool
sort bool
clearCache bool
chunks []*Chunk
pattern *Pattern
final bool
sort bool
revision int
}
// Matcher is responsible for performing search
@@ -29,6 +29,7 @@ type Matcher struct {
partitions int
slab []*util.Slab
mergerCache map[string]*Merger
revision int
}
const (
@@ -38,7 +39,7 @@ const (
// NewMatcher returns a new Matcher
func NewMatcher(patternBuilder func([]rune) *Pattern,
sort bool, tac bool, eventBox *util.EventBox) *Matcher {
sort bool, tac bool, eventBox *util.EventBox, revision int) *Matcher {
partitions := util.Min(numPartitionsMultiplier*runtime.NumCPU(), maxPartitions)
return &Matcher{
patternBuilder: patternBuilder,
@@ -48,7 +49,8 @@ func NewMatcher(patternBuilder func([]rune) *Pattern,
reqBox: util.NewEventBox(),
partitions: partitions,
slab: make([]*util.Slab, partitions),
mergerCache: make(map[string]*Merger)}
mergerCache: make(map[string]*Merger),
revision: revision}
}
// Loop puts Matcher in action
@@ -70,8 +72,9 @@ func (m *Matcher) Loop() {
events.Clear()
})
if request.sort != m.sort || request.clearCache {
if request.sort != m.sort || request.revision != m.revision {
m.sort = request.sort
m.revision = request.revision
m.mergerCache = make(map[string]*Merger)
clearChunkCache()
}
@@ -140,11 +143,11 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
numChunks := len(request.chunks)
if numChunks == 0 {
return EmptyMerger, false
return EmptyMerger(request.revision), false
}
pattern := request.pattern
if pattern.IsEmpty() {
return PassMerger(&request.chunks, m.tac), false
return PassMerger(&request.chunks, m.tac, request.revision), false
}
cancelled := util.NewAtomicBool(false)
@@ -218,11 +221,11 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
partialResult := <-resultChan
partialResults[partialResult.index] = partialResult.matches
}
return NewMerger(pattern, partialResults, m.sort, m.tac), false
return NewMerger(pattern, partialResults, m.sort, m.tac, request.revision), false
}
// Reset is called to interrupt/signal the ongoing search
func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final bool, sort bool, clearCache bool) {
func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final bool, sort bool, revision int) {
pattern := m.patternBuilder(patternRunes)
var event util.EventType
@@ -231,5 +234,5 @@ func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final
} else {
event = reqRetry
}
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable, clearCache})
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable, revision})
}

View File

@@ -3,30 +3,36 @@ package fzf
import "fmt"
// EmptyMerger is a Merger with no data
var EmptyMerger = NewMerger(nil, [][]Result{}, false, false)
func EmptyMerger(revision int) *Merger {
return NewMerger(nil, [][]Result{}, false, false, revision)
}
// Merger holds a set of locally sorted lists of items and provides the view of
// a single, globally-sorted list
type Merger struct {
pattern *Pattern
lists [][]Result
merged []Result
chunks *[]*Chunk
cursors []int
sorted bool
tac bool
final bool
count int
pattern *Pattern
lists [][]Result
merged []Result
chunks *[]*Chunk
cursors []int
sorted bool
tac bool
final bool
count int
pass bool
revision int
}
// PassMerger returns a new Merger that simply returns the items in the
// original order
func PassMerger(chunks *[]*Chunk, tac bool) *Merger {
func PassMerger(chunks *[]*Chunk, tac bool, revision int) *Merger {
mg := Merger{
pattern: nil,
chunks: chunks,
tac: tac,
count: 0}
pattern: nil,
chunks: chunks,
tac: tac,
count: 0,
pass: true,
revision: revision}
for _, chunk := range *mg.chunks {
mg.count += chunk.count
@@ -35,17 +41,18 @@ func PassMerger(chunks *[]*Chunk, tac bool) *Merger {
}
// NewMerger returns a new Merger
func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool) *Merger {
func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool, revision int) *Merger {
mg := Merger{
pattern: pattern,
lists: lists,
merged: []Result{},
chunks: nil,
cursors: make([]int, len(lists)),
sorted: sorted,
tac: tac,
final: false,
count: 0}
pattern: pattern,
lists: lists,
merged: []Result{},
chunks: nil,
cursors: make([]int, len(lists)),
sorted: sorted,
tac: tac,
final: false,
count: 0,
revision: revision}
for _, list := range mg.lists {
mg.count += len(list)
@@ -53,11 +60,42 @@ func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool) *Merge
return &mg
}
// Revision returns revision number
func (mg *Merger) Revision() int {
return mg.revision
}
// Length returns the number of items
func (mg *Merger) Length() int {
return mg.count
}
func (mg *Merger) First() Result {
if mg.tac && !mg.sorted {
return mg.Get(mg.count - 1)
}
return mg.Get(0)
}
// FindIndex returns the index of the item with the given item index
func (mg *Merger) FindIndex(itemIndex int32) int {
index := -1
if mg.pass {
index = int(itemIndex)
if mg.tac {
index = mg.count - index - 1
}
} else {
for i := 0; i < mg.count; i++ {
if mg.Get(i).item.Index() == itemIndex {
index = i
break
}
}
}
return index
}
// Get returns the pointer to the Result object indexed by the given integer
func (mg *Merger) Get(idx int) Result {
if mg.chunks != nil {

View File

@@ -23,10 +23,10 @@ func randResult() Result {
}
func TestEmptyMerger(t *testing.T) {
assert(t, EmptyMerger.Length() == 0, "Not empty")
assert(t, EmptyMerger.count == 0, "Invalid count")
assert(t, len(EmptyMerger.lists) == 0, "Invalid lists")
assert(t, len(EmptyMerger.merged) == 0, "Invalid merged list")
assert(t, EmptyMerger(0).Length() == 0, "Not empty")
assert(t, EmptyMerger(0).count == 0, "Invalid count")
assert(t, len(EmptyMerger(0).lists) == 0, "Invalid lists")
assert(t, len(EmptyMerger(0).merged) == 0, "Invalid merged list")
}
func buildLists(partiallySorted bool) ([][]Result, []Result) {
@@ -57,7 +57,7 @@ func TestMergerUnsorted(t *testing.T) {
cnt := len(items)
// Not sorted: same order
mg := NewMerger(nil, lists, false, false)
mg := NewMerger(nil, lists, false, false, 0)
assert(t, cnt == mg.Length(), "Invalid Length")
for i := 0; i < cnt; i++ {
assert(t, items[i] == mg.Get(i), "Invalid Get")
@@ -69,7 +69,7 @@ func TestMergerSorted(t *testing.T) {
cnt := len(items)
// Sorted sorted order
mg := NewMerger(nil, lists, true, false)
mg := NewMerger(nil, lists, true, false, 0)
assert(t, cnt == mg.Length(), "Invalid Length")
sort.Sort(ByRelevance(items))
for i := 0; i < cnt; i++ {
@@ -79,7 +79,7 @@ func TestMergerSorted(t *testing.T) {
}
// Inverse order
mg2 := NewMerger(nil, lists, true, false)
mg2 := NewMerger(nil, lists, true, false, 0)
for i := cnt - 1; i >= 0; i-- {
if items[i] != mg2.Get(i) {
t.Error("Not sorted", items[i], mg2.Get(i))

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@ package fzf
import (
"fmt"
"io/ioutil"
"os"
"testing"
"github.com/junegunn/fzf/src/tui"
@@ -262,13 +262,17 @@ func TestBind(t *testing.T) {
}
}
check(tui.CtrlA.AsEvent(), "", actBeginningOfLine)
errorString := ""
errorFn := func(e string) {
errorString = e
}
parseKeymap(keymap,
"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+
"f1:execute(ls {+})+abort+execute(echo {+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
"f1:execute(ls {+})+abort+execute(echo \n{+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
"alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+
"x:Execute(foo+bar),X:execute/bar+baz/"+
",f1:+first,f1:+top"+
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up")
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up", errorFn)
check(tui.CtrlA.AsEvent(), "", actKillLine)
check(tui.CtrlB.AsEvent(), "", actToggleSort, actUp, actDown)
check(tui.Key('c'), "", actPageUp)
@@ -286,12 +290,15 @@ func TestBind(t *testing.T) {
check(tui.Key('+'), "++\nfoobar,Y:execute(baz)+up", actExecute)
for idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} {
parseKeymap(keymap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char))
parseKeymap(keymap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char), errorFn)
check(tui.Key([]rune(fmt.Sprintf("%d", idx%10))[0]), "foobar", actExecute)
}
parseKeymap(keymap, "f1:abort")
parseKeymap(keymap, "f1:abort", errorFn)
check(tui.F1.AsEvent(), "", actAbort)
if len(errorString) > 0 {
t.Errorf("error parsing keymap: %s", errorString)
}
}
func TestColorSpec(t *testing.T) {
@@ -350,14 +357,14 @@ func TestDefaultCtrlNP(t *testing.T) {
check([]string{"--bind=ctrl-n:accept"}, tui.CtrlN, actAccept)
check([]string{"--bind=ctrl-p:accept"}, tui.CtrlP, actAccept)
f, _ := ioutil.TempFile("", "fzf-history")
f, _ := os.CreateTemp("", "fzf-history")
f.Close()
hist := "--history=" + f.Name()
check([]string{hist}, tui.CtrlN, actNextHistory)
check([]string{hist}, tui.CtrlP, actPreviousHistory)
check([]string{hist}, tui.CtrlP, actPrevHistory)
check([]string{hist, "--bind=ctrl-n:accept"}, tui.CtrlN, actAccept)
check([]string{hist, "--bind=ctrl-n:accept"}, tui.CtrlP, actPreviousHistory)
check([]string{hist, "--bind=ctrl-n:accept"}, tui.CtrlP, actPrevHistory)
check([]string{hist, "--bind=ctrl-p:accept"}, tui.CtrlN, actNextHistory)
check([]string{hist, "--bind=ctrl-p:accept"}, tui.CtrlP, actAccept)
@@ -466,3 +473,38 @@ func TestValidateSign(t *testing.T) {
}
}
}
func TestParseSingleActionList(t *testing.T) {
actions := parseSingleActionList("Execute@foo+bar,baz@+up+up+reload:down+down", func(string) {})
if len(actions) != 4 {
t.Errorf("Invalid number of actions parsed:%d", len(actions))
}
if actions[0].t != actExecute || actions[0].a != "foo+bar,baz" {
t.Errorf("Invalid action parsed: %v", actions[0])
}
if actions[1].t != actUp || actions[2].t != actUp {
t.Errorf("Invalid action parsed: %v / %v", actions[1], actions[2])
}
if actions[3].t != actReload || actions[3].a != "down+down" {
t.Errorf("Invalid action parsed: %v", actions[3])
}
}
func TestParseSingleActionListError(t *testing.T) {
err := ""
parseSingleActionList("change-query(foobar)baz", func(e string) {
err = e
})
if len(err) == 0 {
t.Errorf("Failed to detect error")
}
}
func TestMaskActionContents(t *testing.T) {
original := ":execute((f)(o)(o)(b)(a)(r))+change-query@qu@ry@+up,x:reload:hello:world"
expected := ":execute +change-query +up,x:reload "
masked := maskActionContents(original)
if masked != expected {
t.Errorf("Not masked: %s", masked)
}
}

View File

@@ -51,6 +51,7 @@ type Pattern struct {
caseSensitive bool
normalize bool
forward bool
withPos bool
text []rune
termSets []termSet
sortable bool
@@ -85,7 +86,7 @@ func clearChunkCache() {
// BuildPattern builds Pattern object from the given arguments
func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool,
cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
withPos bool, cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
var asString string
if extended {
@@ -145,6 +146,7 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
caseSensitive: caseSensitive,
normalize: normalize,
forward: forward,
withPos: withPos,
text: []rune(asString),
termSets: termSets,
sortable: sortable,
@@ -207,11 +209,10 @@ func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet
// Flip exactness
if fuzzy && !inv {
typ = termExact
text = text[1:]
} else {
typ = termFuzzy
text = text[1:]
}
text = text[1:]
} else if strings.HasPrefix(text, "^") {
if typ == termSuffix {
typ = termEqual
@@ -302,13 +303,13 @@ func (p *Pattern) matchChunk(chunk *Chunk, space []Result, slab *util.Slab) []Re
if space == nil {
for idx := 0; idx < chunk.count; idx++ {
if match, _, _ := p.MatchItem(&chunk.items[idx], false, slab); match != nil {
if match, _, _ := p.MatchItem(&chunk.items[idx], p.withPos, slab); match != nil {
matches = append(matches, *match)
}
}
} else {
for _, result := range space {
if match, _, _ := p.MatchItem(result.item, false, slab); match != nil {
if match, _, _ := p.MatchItem(result.item, p.withPos, slab); match != nil {
matches = append(matches, *match)
}
}

View File

@@ -67,7 +67,7 @@ func TestParseTermsEmpty(t *testing.T) {
func TestExact(t *testing.T) {
defer clearPatternCache()
clearPatternCache()
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true,
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true,
[]Range{}, Delimiter{}, []rune("'abc"))
chars := util.ToChars([]byte("aabbcc abc"))
res, pos := algo.ExactMatchNaive(
@@ -83,7 +83,7 @@ func TestExact(t *testing.T) {
func TestEqual(t *testing.T) {
defer clearPatternCache()
clearPatternCache()
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("^AbC$"))
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("^AbC$"))
match := func(str string, sidxExpected int, eidxExpected int) {
chars := util.ToChars([]byte(str))
@@ -106,17 +106,17 @@ func TestEqual(t *testing.T) {
func TestCaseSensitivity(t *testing.T) {
defer clearPatternCache()
clearPatternCache()
pat1 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("abc"))
pat1 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("abc"))
clearPatternCache()
pat2 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("Abc"))
pat2 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc"))
clearPatternCache()
pat3 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, true, []Range{}, Delimiter{}, []rune("abc"))
pat3 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, false, true, []Range{}, Delimiter{}, []rune("abc"))
clearPatternCache()
pat4 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, true, []Range{}, Delimiter{}, []rune("Abc"))
pat4 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc"))
clearPatternCache()
pat5 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, true, []Range{}, Delimiter{}, []rune("abc"))
pat5 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, false, true, []Range{}, Delimiter{}, []rune("abc"))
clearPatternCache()
pat6 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, true, []Range{}, Delimiter{}, []rune("Abc"))
pat6 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc"))
if string(pat1.text) != "abc" || pat1.caseSensitive != false ||
string(pat2.text) != "Abc" || pat2.caseSensitive != true ||
@@ -129,7 +129,7 @@ func TestCaseSensitivity(t *testing.T) {
}
func TestOrigTextAndTransformed(t *testing.T) {
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("jg"))
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("jg"))
tokens := Tokenize("junegunn", Delimiter{})
trans := Transform(tokens, []Range{{1, 1}})
@@ -164,7 +164,7 @@ func TestOrigTextAndTransformed(t *testing.T) {
func TestCacheKey(t *testing.T) {
test := func(extended bool, patStr string, expected string, cacheable bool) {
clearPatternCache()
pat := BuildPattern(true, algo.FuzzyMatchV2, extended, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune(patStr))
pat := BuildPattern(true, algo.FuzzyMatchV2, extended, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune(patStr))
if pat.CacheKey() != expected {
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
}
@@ -188,7 +188,7 @@ func TestCacheKey(t *testing.T) {
func TestCacheable(t *testing.T) {
test := func(fuzzy bool, str string, expected string, cacheable bool) {
clearPatternCache()
pat := BuildPattern(fuzzy, algo.FuzzyMatchV2, true, CaseSmart, true, true, true, []Range{}, Delimiter{}, []rune(str))
pat := BuildPattern(fuzzy, algo.FuzzyMatchV2, true, CaseSmart, true, true, false, true, []Range{}, Delimiter{}, []rune(str))
if pat.CacheKey() != expected {
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
}

View File

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

View File

@@ -11,8 +11,8 @@ import (
"sync/atomic"
"time"
"github.com/charlievieth/fastwalk"
"github.com/junegunn/fzf/src/util"
"github.com/saracen/walker"
)
// Reader reads from command or standard input
@@ -76,39 +76,33 @@ func (r *Reader) fin(success bool) {
func (r *Reader) terminate() {
r.mutex.Lock()
defer func() { r.mutex.Unlock() }()
r.killed = true
if r.exec != nil && r.exec.Process != nil {
util.KillCommand(r.exec)
} else if defaultCommand != "" {
} else {
os.Stdin.Close()
}
r.mutex.Unlock()
}
func (r *Reader) restart(command string) {
func (r *Reader) restart(command string, environ []string) {
r.event = int32(EvtReady)
r.startEventPoller()
success := r.readFromCommand(nil, command)
success := r.readFromCommand(command, environ)
r.fin(success)
}
// ReadSource reads data from the default command or from standard input
func (r *Reader) ReadSource() {
func (r *Reader) ReadSource(root string, opts walkerOpts, ignores []string) {
r.startEventPoller()
var success bool
if util.IsTty() {
// The default command for *nix requires bash
shell := "bash"
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
if len(cmd) == 0 {
if defaultCommand != "" {
success = r.readFromCommand(&shell, defaultCommand)
} else {
success = r.readFiles()
}
success = r.readFiles(root, opts, ignores)
} else {
success = r.readFromCommand(nil, cmd)
// We can't export FZF_* environment variables to the default command
success = r.readFromCommand(cmd, nil)
}
} else {
success = r.readFromStdin()
@@ -151,16 +145,28 @@ func (r *Reader) readFromStdin() bool {
return true
}
func (r *Reader) readFiles() bool {
func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool {
r.killed = false
fn := func(path string, mode os.FileInfo) error {
conf := fastwalk.Config{Follow: opts.follow}
fn := func(path string, de os.DirEntry, err error) error {
if err != nil {
return nil
}
path = filepath.Clean(path)
if path != "." {
isDir := mode.Mode().IsDir()
if isDir && filepath.Base(path)[0] == '.' {
return filepath.SkipDir
isDir := de.IsDir()
if isDir {
base := filepath.Base(path)
if !opts.hidden && base[0] == '.' {
return filepath.SkipDir
}
for _, ignore := range ignores {
if ignore == base {
return filepath.SkipDir
}
}
}
if !isDir && r.pusher([]byte(path)) {
if ((opts.file && !isDir) || (opts.dir && isDir)) && r.pusher([]byte(path)) {
atomic.StoreInt32(&r.event, int32(EvtReadNew))
}
}
@@ -171,20 +177,16 @@ func (r *Reader) readFiles() bool {
}
return nil
}
cb := walker.WithErrorCallback(func(pathname string, err error) error {
return nil
})
return walker.Walk(".", fn, cb) == nil
return fastwalk.Walk(&conf, root, fn) == nil
}
func (r *Reader) readFromCommand(shell *string, command string) bool {
func (r *Reader) readFromCommand(command string, environ []string) bool {
r.mutex.Lock()
r.killed = false
r.command = &command
if shell != nil {
r.exec = util.ExecCommandWith(*shell, command, true)
} else {
r.exec = util.ExecCommand(command, true)
r.exec = util.ExecCommand(command, true)
if environ != nil {
r.exec.Env = environ
}
out, err := r.exec.StdoutPipe()
if err != nil {

View File

@@ -22,7 +22,7 @@ func TestReadFromCommand(t *testing.T) {
}
// Normal command
reader.fin(reader.readFromCommand(nil, `echo abc&&echo def`))
reader.fin(reader.readFromCommand(`echo abc&&echo def`, nil))
if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" {
t.Errorf("%s", strs)
}
@@ -47,7 +47,7 @@ func TestReadFromCommand(t *testing.T) {
reader.startEventPoller()
// Failing command
reader.fin(reader.readFromCommand(nil, `no-such-command`))
reader.fin(reader.readFromCommand(`no-such-command`, nil))
strs = []string{}
if len(strs) > 0 {
t.Errorf("%s", strs)

View File

@@ -49,6 +49,22 @@ func buildResult(item *Item, offsets []Offset, score int) Result {
case byScore:
// Higher is better
val = math.MaxUint16 - util.AsUint16(score)
case byChunk:
if validOffsetFound {
b := minBegin
e := maxEnd
for ; b >= 1; b-- {
if unicode.IsSpace(item.text.Get(b - 1)) {
break
}
}
for ; e < numChars; e++ {
if unicode.IsSpace(item.text.Get(e)) {
break
}
}
val = util.AsUint16(e - b)
}
case byLength:
val = item.TrimLength()
case byBegin, byEnd:
@@ -64,7 +80,7 @@ func buildResult(item *Item, offsets []Offset, score int) Result {
if criterion == byBegin {
val = util.AsUint16(minEnd - whitePrefixLen)
} else {
val = util.AsUint16(math.MaxUint16 - math.MaxUint16*(maxEnd-whitePrefixLen)/int(item.TrimLength()))
val = util.AsUint16(math.MaxUint16 - math.MaxUint16*(maxEnd-whitePrefixLen)/int(item.TrimLength()+1))
}
}
}
@@ -122,7 +138,9 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
for i := off[0]; i < off[1]; i++ {
// Negative of 1-based index of itemColors
// - The extra -1 means highlighted
cols[i] = cols[i]*-1 - 1
if cols[i] >= 0 {
cols[i] = cols[i]*-1 - 1
}
}
}

View File

@@ -54,9 +54,9 @@ func TestResultRank(t *testing.T) {
// FIXME global
sortCriteria = []criterion{byScore, byLength}
strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")}
str := []rune("foo")
item1 := buildResult(
withIndex(&Item{text: util.RunesToChars(strs[0])}, 1), []Offset{}, 2)
withIndex(&Item{text: util.RunesToChars(str)}, 1), []Offset{}, 2)
if item1.points[3] != math.MaxUint16-2 || // Bonus
item1.points[2] != 3 || // Length
item1.points[1] != 0 || // Unused
@@ -65,7 +65,7 @@ func TestResultRank(t *testing.T) {
t.Error(item1)
}
// Only differ in index
item2 := buildResult(&Item{text: util.RunesToChars(strs[0])}, []Offset{}, 2)
item2 := buildResult(&Item{text: util.RunesToChars(str)}, []Offset{}, 2)
items := []Result{item1, item2}
sort.Sort(ByRelevance(items))
@@ -98,12 +98,29 @@ func TestResultRank(t *testing.T) {
}
}
func TestChunkTiebreak(t *testing.T) {
// FIXME global
sortCriteria = []criterion{byScore, byChunk}
score := 100
test := func(input string, offset Offset, chunk string) {
item := buildResult(withIndex(&Item{text: util.RunesToChars([]rune(input))}, 1), []Offset{offset}, score)
if !(item.points[3] == math.MaxUint16-uint16(score) && item.points[2] == uint16(len(chunk))) {
t.Error(item.points)
}
}
test("hello foobar goodbye", Offset{8, 9}, "foobar")
test("hello foobar goodbye", Offset{7, 18}, "foobar goodbye")
test("hello foobar goodbye", Offset{0, 1}, "hello")
test("hello foobar goodbye", Offset{5, 7}, "hello foobar") // TBD
}
func TestColorOffset(t *testing.T) {
// ------------ 20 ---- -- ----
// ++++++++ ++++++++++
// --++++++++-- --++++++++++---
offsets := []Offset{{5, 15}, {25, 35}}
offsets := []Offset{{5, 15}, {10, 12}, {25, 35}}
item := Result{
item: &Item{
colors: &[]ansiOffset{

252
src/server.go Normal file
View File

@@ -0,0 +1,252 @@
package fzf
import (
"bufio"
"bytes"
"crypto/subtle"
"errors"
"fmt"
"net"
"os"
"regexp"
"strconv"
"strings"
"time"
)
var getRegex *regexp.Regexp
func init() {
getRegex = regexp.MustCompile(`^GET /(?:\?([a-z0-9=&]+))? HTTP`)
}
type getParams struct {
limit int
offset int
}
const (
crlf = "\r\n"
httpOk = "HTTP/1.1 200 OK" + crlf
httpBadRequest = "HTTP/1.1 400 Bad Request" + crlf
httpUnauthorized = "HTTP/1.1 401 Unauthorized" + crlf
httpUnavailable = "HTTP/1.1 503 Service Unavailable" + crlf
httpReadTimeout = 10 * time.Second
jsonContentType = "Content-Type: application/json" + crlf
maxContentLength = 1024 * 1024
)
type httpServer struct {
apiKey []byte
actionChannel chan []*action
responseChannel chan string
}
type listenAddress struct {
host string
port int
}
func (addr listenAddress) IsLocal() bool {
return addr.host == "localhost" || addr.host == "127.0.0.1"
}
var defaultListenAddr = listenAddress{"localhost", 0}
func parseListenAddress(address string) (listenAddress, error) {
parts := strings.SplitN(address, ":", 3)
if len(parts) == 1 {
parts = []string{"localhost", parts[0]}
}
if len(parts) != 2 {
return defaultListenAddr, fmt.Errorf("invalid listen address: %s", address)
}
portStr := parts[len(parts)-1]
port, err := strconv.Atoi(portStr)
if err != nil || port < 0 || port > 65535 {
return defaultListenAddr, fmt.Errorf("invalid listen port: %s", portStr)
}
if len(parts[0]) == 0 {
parts[0] = "localhost"
}
return listenAddress{parts[0], port}, nil
}
func startHttpServer(address listenAddress, actionChannel chan []*action, responseChannel chan string) (int, error) {
host := address.host
port := address.port
apiKey := os.Getenv("FZF_API_KEY")
if !address.IsLocal() && len(apiKey) == 0 {
return port, fmt.Errorf("FZF_API_KEY is required to allow remote access")
}
addrStr := fmt.Sprintf("%s:%d", host, port)
listener, err := net.Listen("tcp", addrStr)
if err != nil {
return port, fmt.Errorf("failed to listen on %s", addrStr)
}
if port == 0 {
addr := listener.Addr().String()
parts := strings.Split(addr, ":")
if len(parts) < 2 {
return port, fmt.Errorf("cannot extract port: %s", addr)
}
var err error
port, err = strconv.Atoi(parts[len(parts)-1])
if err != nil {
return port, err
}
}
server := httpServer{
apiKey: []byte(apiKey),
actionChannel: actionChannel,
responseChannel: responseChannel,
}
go func() {
for {
conn, err := listener.Accept()
if err != nil {
if errors.Is(err, net.ErrClosed) {
break
} else {
continue
}
}
conn.Write([]byte(server.handleHttpRequest(conn)))
conn.Close()
}
listener.Close()
}()
return port, nil
}
// Here we are writing a simplistic HTTP server without using net/http
// package to reduce the size of the binary.
//
// * No --listen: 2.8MB
// * --listen with net/http: 5.7MB
// * --listen w/o net/http: 3.3MB
func (server *httpServer) handleHttpRequest(conn net.Conn) string {
contentLength := 0
apiKey := ""
body := ""
answer := func(code string, message string) string {
message += "\n"
return code + fmt.Sprintf("Content-Length: %d%s", len(message), crlf+crlf+message)
}
unauthorized := func(message string) string {
return answer(httpUnauthorized, message)
}
bad := func(message string) string {
return answer(httpBadRequest, message)
}
good := func(message string) string {
return answer(httpOk+jsonContentType, message)
}
conn.SetReadDeadline(time.Now().Add(httpReadTimeout))
scanner := bufio.NewScanner(conn)
scanner.Split(func(data []byte, atEOF bool) (int, []byte, error) {
found := bytes.Index(data, []byte(crlf))
if found >= 0 {
token := data[:found+len(crlf)]
return len(token), token, nil
}
if atEOF || len(body)+len(data) >= contentLength {
return 0, data, bufio.ErrFinalToken
}
return 0, nil, nil
})
section := 0
for scanner.Scan() {
text := scanner.Text()
switch section {
case 0:
getMatch := getRegex.FindStringSubmatch(text)
if len(getMatch) > 0 {
server.actionChannel <- []*action{{t: actResponse, a: getMatch[1]}}
select {
case response := <-server.responseChannel:
return good(response)
case <-time.After(2 * time.Second):
go func() {
// Drain the channel
<-server.responseChannel
}()
return answer(httpUnavailable+jsonContentType, `{"error":"timeout"}`)
}
} else if !strings.HasPrefix(text, "POST / HTTP") {
return bad("invalid request method")
}
section++
case 1:
if text == crlf {
if contentLength == 0 {
return bad("content-length header missing")
}
section++
continue
}
pair := strings.SplitN(text, ":", 2)
if len(pair) == 2 {
switch strings.ToLower(pair[0]) {
case "content-length":
length, err := strconv.Atoi(strings.TrimSpace(pair[1]))
if err != nil || length <= 0 || length > maxContentLength {
return bad("invalid content length")
}
contentLength = length
case "x-api-key":
apiKey = strings.TrimSpace(pair[1])
}
}
case 2:
body += text
}
}
if len(server.apiKey) != 0 && subtle.ConstantTimeCompare([]byte(apiKey), server.apiKey) != 1 {
return unauthorized("invalid api key")
}
if len(body) < contentLength {
return bad("incomplete request")
}
body = body[:contentLength]
errorMessage := ""
actions := parseSingleActionList(strings.Trim(string(body), "\r\n"), func(message string) {
errorMessage = message
})
if len(errorMessage) > 0 {
return bad(errorMessage)
}
if len(actions) == 0 {
return bad("no action specified")
}
server.actionChannel <- actions
return httpOk + crlf
}
func parseGetParams(query string) getParams {
params := getParams{limit: 100, offset: 0}
for _, pair := range strings.Split(query, "&") {
parts := strings.SplitN(pair, "=", 2)
if len(parts) == 2 {
switch parts[0] {
case "limit", "offset":
if val, err := strconv.Atoi(parts[1]); err == nil {
if parts[0] == "limit" {
params.limit = val
} else {
params.offset = val
}
}
}
}
}
return params
}

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -8,32 +8,41 @@ func HasFullscreenRenderer() bool {
return false
}
var DefaultBorderShape BorderShape = BorderRounded
func (a Attr) Merge(b Attr) Attr {
return a | b
}
const (
AttrUndefined = Attr(0)
AttrRegular = Attr(1 << 7)
AttrClear = Attr(1 << 8)
AttrRegular = Attr(1 << 8)
AttrClear = Attr(1 << 9)
Bold = Attr(1)
Dim = Attr(1 << 1)
Italic = Attr(1 << 2)
Underline = Attr(1 << 3)
Blink = Attr(1 << 4)
Blink2 = Attr(1 << 5)
Reverse = Attr(1 << 6)
Bold = Attr(1)
Dim = Attr(1 << 1)
Italic = Attr(1 << 2)
Underline = Attr(1 << 3)
Blink = Attr(1 << 4)
Blink2 = Attr(1 << 5)
Reverse = Attr(1 << 6)
StrikeThrough = Attr(1 << 7)
)
func (r *FullscreenRenderer) Init() {}
func (r *FullscreenRenderer) Pause(bool) {}
func (r *FullscreenRenderer) Resume(bool, bool) {}
func (r *FullscreenRenderer) Clear() {}
func (r *FullscreenRenderer) Refresh() {}
func (r *FullscreenRenderer) Close() {}
func (r *FullscreenRenderer) Init() {}
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
func (r *FullscreenRenderer) Pause(bool) {}
func (r *FullscreenRenderer) Resume(bool, bool) {}
func (r *FullscreenRenderer) PassThrough(string) {}
func (r *FullscreenRenderer) Clear() {}
func (r *FullscreenRenderer) NeedScrollbarRedraw() bool { return false }
func (r *FullscreenRenderer) ShouldEmitResizeEvent() bool { return false }
func (r *FullscreenRenderer) Refresh() {}
func (r *FullscreenRenderer) Close() {}
func (r *FullscreenRenderer) Size() TermSize { return TermSize{} }
func (r *FullscreenRenderer) GetChar() Event { return Event{} }
func (r *FullscreenRenderer) Top() int { return 0 }
func (r *FullscreenRenderer) MaxX() int { return 0 }
func (r *FullscreenRenderer) MaxY() int { return 0 }

View File

@@ -10,7 +10,6 @@ import (
"time"
"unicode/utf8"
"github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
"golang.org/x/term"
@@ -31,21 +30,31 @@ const consoleDevice string = "/dev/tty"
var offsetRegexp *regexp.Regexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R")
var offsetRegexpBegin *regexp.Regexp = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R")
func (r *LightRenderer) stderr(str string) {
r.stderrInternal(str, true)
func (r *LightRenderer) PassThrough(str string) {
r.queued.WriteString("\x1b7" + str + "\x1b8")
}
// FIXME: Need better handling of non-displayable characters
func (r *LightRenderer) stderrInternal(str string, allowNLCR bool) {
func (r *LightRenderer) stderr(str string) {
r.stderrInternal(str, true, "")
}
const CR string = "\x1b[2m␍"
const LF string = "\x1b[2m␊"
func (r *LightRenderer) stderrInternal(str string, allowNLCR bool, resetCode string) {
bytes := []byte(str)
runes := []rune{}
for len(bytes) > 0 {
r, sz := utf8.DecodeRune(bytes)
nlcr := r == '\n' || r == '\r'
if r >= 32 || r == '\x1b' || nlcr {
if r == utf8.RuneError || nlcr && !allowNLCR {
runes = append(runes, ' ')
} else {
if nlcr && !allowNLCR {
if r == '\r' {
runes = append(runes, []rune(CR+resetCode)...)
} else {
runes = append(runes, []rune(LF+resetCode)...)
}
} else if r != utf8.RuneError {
runes = append(runes, r)
}
}
@@ -54,8 +63,10 @@ func (r *LightRenderer) stderrInternal(str string, allowNLCR bool) {
r.queued.WriteString(string(runes))
}
func (r *LightRenderer) csi(code string) {
r.stderr("\x1b[" + code)
func (r *LightRenderer) csi(code string) string {
fullcode := "\x1b[" + code
r.stderr(fullcode)
return fullcode
}
func (r *LightRenderer) flush() {
@@ -72,7 +83,7 @@ type LightRenderer struct {
forceBlack bool
clearOnExit bool
prevDownTime time.Time
clickY []int
clicks [][2]int
ttyin *os.File
buffer []byte
origState *term.State
@@ -174,10 +185,7 @@ func (r *LightRenderer) Init() {
}
}
if r.mouse {
r.csi("?1000h")
r.csi("?1006h")
}
r.enableMouse()
r.csi(fmt.Sprintf("%dA", r.MaxY()-1))
r.csi("G")
r.csi("K")
@@ -189,6 +197,10 @@ func (r *LightRenderer) Init() {
}
}
func (r *LightRenderer) Resize(maxHeightFunc func(int) int) {
r.maxHeightFunc = maxHeightFunc
}
func (r *LightRenderer) makeSpace() {
r.stderr("\n")
r.csi("G")
@@ -389,7 +401,7 @@ func (r *LightRenderer) escSequence(sz *int) Event {
return Event{F3, 0, nil}
case 'S':
return Event{F4, 0, nil}
case '1', '2', '3', '4', '5', '6':
case '1', '2', '3', '4', '5', '6', '7', '8':
if len(r.buffer) < 4 {
return Event{Invalid, 0, nil}
}
@@ -421,13 +433,29 @@ func (r *LightRenderer) escSequence(sz *int) Event {
}
return Event{Invalid, 0, nil} // INS
case '3':
return Event{Del, 0, nil}
if r.buffer[3] == '~' {
return Event{Del, 0, nil}
}
if len(r.buffer) == 6 && r.buffer[5] == '~' {
*sz = 6
switch r.buffer[4] {
case '5':
return Event{CtrlDelete, 0, nil}
case '2':
return Event{SDelete, 0, nil}
}
}
return Event{Invalid, 0, nil}
case '4':
return Event{End, 0, nil}
case '5':
return Event{PgUp, 0, nil}
case '6':
return Event{PgDn, 0, nil}
case '7':
return Event{Home, 0, nil}
case '8':
return Event{End, 0, nil}
case '1':
switch r.buffer[3] {
case '~':
@@ -540,8 +568,8 @@ func (r *LightRenderer) mouseSequence(sz *int) Event {
t := atoi(elems[0], -1)
x := atoi(elems[1], -1) - 1
y := atoi(elems[2], -1) - 1
if t < 0 || x < 0 || y < 0 {
y := atoi(elems[2], -1) - 1 - r.yoffset
if t < 0 || x < 0 {
return Event{Invalid, 0, nil}
}
*sz += end + 1
@@ -565,25 +593,31 @@ func (r *LightRenderer) mouseSequence(sz *int) Event {
// ctrl := t & 0b1000
mod := t&0b1100 > 0
drag := t&0b100000 > 0
if scroll != 0 {
return Event{Mouse, 0, &MouseEvent{y, x, scroll, false, false, false, mod}}
}
double := false
if down {
if down && !drag {
now := time.Now()
if !left { // Right double click is not allowed
r.clickY = []int{}
r.clicks = [][2]int{}
} else if now.Sub(r.prevDownTime) < doubleClickDuration {
r.clickY = append(r.clickY, y)
r.clicks = append(r.clicks, [2]int{x, y})
} else {
r.clickY = []int{y}
r.clicks = [][2]int{{x, y}}
}
r.prevDownTime = now
} else {
if len(r.clickY) > 1 && r.clickY[0] == r.clickY[1] &&
n := len(r.clicks)
if len(r.clicks) > 1 && r.clicks[n-2][0] == r.clicks[n-1][0] && r.clicks[n-2][1] == r.clicks[n-1][1] &&
time.Since(r.prevDownTime) < doubleClickDuration {
double = true
if double {
r.clicks = [][2]int{}
}
}
}
return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, mod}}
@@ -598,6 +632,7 @@ func (r *LightRenderer) rmcup() {
}
func (r *LightRenderer) Pause(clear bool) {
r.disableMouse()
r.restoreTerminal()
if clear {
if r.fullscreen {
@@ -610,6 +645,22 @@ func (r *LightRenderer) Pause(clear bool) {
}
}
func (r *LightRenderer) enableMouse() {
if r.mouse {
r.csi("?1000h")
r.csi("?1002h")
r.csi("?1006h")
}
}
func (r *LightRenderer) disableMouse() {
if r.mouse {
r.csi("?1000l")
r.csi("?1002l")
r.csi("?1006l")
}
}
func (r *LightRenderer) Resume(clear bool, sigcont bool) {
r.setupTerminal()
if clear {
@@ -618,13 +669,13 @@ func (r *LightRenderer) Resume(clear bool, sigcont bool) {
} else {
r.rmcup()
}
r.enableMouse()
r.flush()
} else if sigcont && !r.fullscreen && r.mouse {
// NOTE: SIGCONT (Coming back from CTRL-Z):
// It's highly likely that the offset we obtained at the beginning is
// no longer correct, so we simply disable mouse input.
r.csi("?1000l")
r.csi("?1006l")
r.disableMouse()
r.mouse = false
}
}
@@ -639,6 +690,14 @@ func (r *LightRenderer) Clear() {
r.flush()
}
func (r *LightRenderer) NeedScrollbarRedraw() bool {
return false
}
func (r *LightRenderer) ShouldEmitResizeEvent() bool {
return false
}
func (r *LightRenderer) RefreshWindows(windows []Window) {
r.flush()
}
@@ -662,20 +721,24 @@ func (r *LightRenderer) Close() {
} else if !r.fullscreen {
r.csi("u")
}
if r.mouse {
r.csi("?1000l")
r.csi("?1006l")
}
r.disableMouse()
r.flush()
r.closePlatform()
r.restoreTerminal()
}
func (r *LightRenderer) Top() int {
return r.yoffset
}
func (r *LightRenderer) MaxX() int {
return r.width
}
func (r *LightRenderer) MaxY() int {
if r.height == 0 {
r.updateTerminalSize()
}
return r.height
}
@@ -699,25 +762,42 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, prev
w.fg = r.theme.Fg.Color
w.bg = r.theme.Bg.Color
}
w.drawBorder()
w.drawBorder(false)
return w
}
func (w *LightWindow) drawBorder() {
func (w *LightWindow) DrawBorder() {
w.drawBorder(false)
}
func (w *LightWindow) DrawHBorder() {
w.drawBorder(true)
}
func (w *LightWindow) drawBorder(onlyHorizontal bool) {
switch w.border.shape {
case BorderRounded, BorderSharp:
w.drawBorderAround()
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble:
w.drawBorderAround(onlyHorizontal)
case BorderHorizontal:
w.drawBorderHorizontal(true, true)
case BorderVertical:
if onlyHorizontal {
return
}
w.drawBorderVertical(true, true)
case BorderTop:
w.drawBorderHorizontal(true, false)
case BorderBottom:
w.drawBorderHorizontal(false, true)
case BorderLeft:
if onlyHorizontal {
return
}
w.drawBorderVertical(true, false)
case BorderRight:
if onlyHorizontal {
return
}
w.drawBorderVertical(false, true)
}
}
@@ -727,13 +807,26 @@ func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
if w.preview {
color = ColPreviewBorder
}
hw := runeWidth(w.border.top)
pad := repeat(' ', w.width/hw)
w.Move(0, 0)
if top {
w.Move(0, 0)
w.CPrint(color, repeat(w.border.horizontal, w.width))
w.CPrint(color, repeat(w.border.top, w.width/hw))
} else {
w.CPrint(color, pad)
}
for y := 1; y < w.height-1; y++ {
w.Move(y, 0)
w.CPrint(color, pad)
}
w.Move(w.height-1, 0)
if bottom {
w.Move(w.height-1, 0)
w.CPrint(color, repeat(w.border.horizontal, w.width))
w.CPrint(color, repeat(w.border.bottom, w.width/hw))
} else {
w.CPrint(color, pad)
}
}
@@ -749,38 +842,46 @@ func (w *LightWindow) drawBorderVertical(left, right bool) {
for y := 0; y < w.height; y++ {
w.Move(y, 0)
if left {
w.CPrint(color, string(w.border.vertical))
w.CPrint(color, string(w.border.left))
}
w.CPrint(color, repeat(' ', width))
if right {
w.CPrint(color, string(w.border.vertical))
w.CPrint(color, string(w.border.right))
}
}
}
func (w *LightWindow) drawBorderAround() {
func (w *LightWindow) drawBorderAround(onlyHorizontal bool) {
w.Move(0, 0)
color := ColBorder
if w.preview {
color = ColPreviewBorder
}
w.CPrint(color, string(w.border.topLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.topRight))
for y := 1; y < w.height-1; y++ {
w.Move(y, 0)
w.CPrint(color, string(w.border.vertical))
w.CPrint(color, repeat(' ', w.width-2))
w.CPrint(color, string(w.border.vertical))
hw := runeWidth(w.border.top)
tcw := runeWidth(w.border.topLeft) + runeWidth(w.border.topRight)
bcw := runeWidth(w.border.bottomLeft) + runeWidth(w.border.bottomRight)
rem := (w.width - tcw) % hw
w.CPrint(color, string(w.border.topLeft)+repeat(w.border.top, (w.width-tcw)/hw)+repeat(' ', rem)+string(w.border.topRight))
if !onlyHorizontal {
vw := runeWidth(w.border.left)
for y := 1; y < w.height-1; y++ {
w.Move(y, 0)
w.CPrint(color, string(w.border.left))
w.CPrint(color, repeat(' ', w.width-vw*2))
w.CPrint(color, string(w.border.right))
}
}
w.Move(w.height-1, 0)
w.CPrint(color, string(w.border.bottomLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.bottomRight))
rem = (w.width - bcw) % hw
w.CPrint(color, string(w.border.bottomLeft)+repeat(w.border.bottom, (w.width-bcw)/hw)+repeat(' ', rem)+string(w.border.bottomRight))
}
func (w *LightWindow) csi(code string) {
w.renderer.csi(code)
func (w *LightWindow) csi(code string) string {
return w.renderer.csi(code)
}
func (w *LightWindow) stderrInternal(str string, allowNLCR bool) {
w.renderer.stderrInternal(str, allowNLCR)
func (w *LightWindow) stderrInternal(str string, allowNLCR bool, resetCode string) {
w.renderer.stderrInternal(str, allowNLCR, resetCode)
}
func (w *LightWindow) Top() int {
@@ -856,6 +957,9 @@ func attrCodes(attr Attr) []string {
if (attr & Reverse) > 0 {
codes = append(codes, "7")
}
if (attr & StrikeThrough) > 0 {
codes = append(codes, "9")
}
return codes
}
@@ -883,10 +987,10 @@ func colorCodes(fg Color, bg Color) []string {
return codes
}
func (w *LightWindow) csiColor(fg Color, bg Color, attr Attr) bool {
func (w *LightWindow) csiColor(fg Color, bg Color, attr Attr) (bool, string) {
codes := append(attrCodes(attr), colorCodes(fg, bg)...)
w.csi(";" + strings.Join(codes, ";") + "m")
return len(codes) > 0
code := w.csi(";" + strings.Join(codes, ";") + "m")
return len(codes) > 0, code
}
func (w *LightWindow) Print(text string) {
@@ -898,16 +1002,17 @@ func cleanse(str string) string {
}
func (w *LightWindow) CPrint(pair ColorPair, text string) {
w.csiColor(pair.Fg(), pair.Bg(), pair.Attr())
w.stderrInternal(cleanse(text), false)
_, code := w.csiColor(pair.Fg(), pair.Bg(), pair.Attr())
w.stderrInternal(cleanse(text), false, code)
w.csi("m")
}
func (w *LightWindow) cprint2(fg Color, bg Color, attr Attr, text string) {
if w.csiColor(fg, bg, attr) {
hasColors, code := w.csiColor(fg, bg, attr)
if hasColors {
defer w.csi("m")
}
w.stderrInternal(cleanse(text), false)
w.stderrInternal(cleanse(text), false, code)
}
type wrappedLine struct {
@@ -927,8 +1032,10 @@ func wrapLine(input string, prefixLength int, max int, tabstop int) []wrappedLin
if len(rs) == 1 && rs[0] == '\t' {
w = tabstop - (prefixLength+width)%tabstop
str = repeat(' ', w)
} else if rs[0] == '\r' {
w++
} else {
w = runewidth.StringWidth(str)
w = uniseg.StringWidth(str)
}
width += w
@@ -945,12 +1052,12 @@ func wrapLine(input string, prefixLength int, max int, tabstop int) []wrappedLin
return lines
}
func (w *LightWindow) fill(str string, onMove func()) FillReturn {
func (w *LightWindow) fill(str string, resetCode string) FillReturn {
allLines := strings.Split(str, "\n")
for i, line := range allLines {
lines := wrapLine(line, w.posx, w.width, w.tabstop)
for j, wl := range lines {
w.stderrInternal(wl.text, false)
w.stderrInternal(wl.text, false, resetCode)
w.posx += wl.displayWidth
// Wrap line
@@ -960,7 +1067,7 @@ func (w *LightWindow) fill(str string, onMove func()) FillReturn {
}
w.MoveAndClear(w.posy, w.posx)
w.Move(w.posy+1, 0)
onMove()
w.renderer.stderr(resetCode)
}
}
}
@@ -969,22 +1076,26 @@ func (w *LightWindow) fill(str string, onMove func()) FillReturn {
return FillSuspend
}
w.Move(w.posy+1, 0)
onMove()
w.renderer.stderr(resetCode)
return FillNextLine
}
return FillContinue
}
func (w *LightWindow) setBg() {
func (w *LightWindow) setBg() string {
if w.bg != colDefault {
w.csiColor(colDefault, w.bg, AttrRegular)
_, code := w.csiColor(colDefault, w.bg, AttrRegular)
return code
}
// Should clear dim attribute after ␍ in the preview window
// e.g. printf "foo\rbar" | fzf --ansi --preview 'printf "foo\rbar"'
return "\x1b[m"
}
func (w *LightWindow) Fill(text string) FillReturn {
w.Move(w.posy, w.posx)
w.setBg()
return w.fill(text, w.setBg)
code := w.setBg()
return w.fill(text, code)
}
func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillReturn {
@@ -995,22 +1106,29 @@ func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillRetu
if bg == colDefault {
bg = w.bg
}
if w.csiColor(fg, bg, attr) {
if hasColors, resetCode := w.csiColor(fg, bg, attr); hasColors {
defer w.csi("m")
return w.fill(text, func() { w.csiColor(fg, bg, attr) })
return w.fill(text, resetCode)
}
return w.fill(text, w.setBg)
return w.fill(text, w.setBg())
}
func (w *LightWindow) FinishFill() {
w.MoveAndClear(w.posy, w.posx)
if w.posy < w.height {
w.MoveAndClear(w.posy, w.posx)
}
for y := w.posy + 1; y < w.height; y++ {
w.MoveAndClear(y, 0)
}
}
func (w *LightWindow) Erase() {
w.drawBorder()
// We don't erase the window here to avoid flickering during scroll
w.DrawBorder()
w.Move(0, 0)
w.FinishFill()
w.Move(0, 0)
}
func (w *LightWindow) EraseMaybe() bool {
return false
}

View File

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

View File

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

View File

@@ -6,12 +6,10 @@ import (
"os"
"time"
"runtime"
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v2/encoding"
"github.com/junegunn/fzf/src/util"
"github.com/gdamore/tcell"
"github.com/gdamore/tcell/encoding"
"github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
)
@@ -19,12 +17,26 @@ func HasFullscreenRenderer() bool {
return true
}
func (p ColorPair) style() tcell.Style {
style := tcell.StyleDefault
return style.Foreground(tcell.Color(p.Fg())).Background(tcell.Color(p.Bg()))
var DefaultBorderShape BorderShape = BorderSharp
func asTcellColor(color Color) tcell.Color {
if color == colDefault {
return tcell.ColorDefault
}
value := uint64(tcell.ColorValid) + uint64(color)
if color.is24() {
value = value | uint64(tcell.ColorIsRGB)
}
return tcell.Color(value)
}
type Attr tcell.Style
func (p ColorPair) style() tcell.Style {
style := tcell.StyleDefault
return style.Foreground(asTcellColor(p.Fg())).Background(asTcellColor(p.Bg()))
}
type Attr int32
type TcellWindow struct {
color bool
@@ -63,8 +75,6 @@ func (w *TcellWindow) Refresh() {
}
w.lastX = 0
w.lastY = 0
w.drawBorder()
}
func (w *TcellWindow) FinishFill() {
@@ -72,12 +82,13 @@ func (w *TcellWindow) FinishFill() {
}
const (
Bold Attr = Attr(tcell.AttrBold)
Dim = Attr(tcell.AttrDim)
Blink = Attr(tcell.AttrBlink)
Reverse = Attr(tcell.AttrReverse)
Underline = Attr(tcell.AttrUnderline)
Italic = Attr(tcell.AttrItalic)
Bold Attr = Attr(tcell.AttrBold)
Dim = Attr(tcell.AttrDim)
Blink = Attr(tcell.AttrBlink)
Reverse = Attr(tcell.AttrReverse)
Underline = Attr(tcell.AttrUnderline)
StrikeThrough = Attr(tcell.AttrStrikeThrough)
Italic = Attr(tcell.AttrItalic)
)
const (
@@ -86,6 +97,13 @@ const (
AttrClear = Attr(1 << 8)
)
func (r *FullscreenRenderer) PassThrough(str string) {
// No-op
// https://github.com/gdamore/tcell/pull/650#issuecomment-1806442846
}
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
if _screen.Colors() >= 256 {
return Dark256
@@ -125,6 +143,7 @@ func (a Attr) Merge(b Attr) Attr {
var (
_screen tcell.Screen
_prevMouseButton tcell.ButtonMask
_initialResize bool = true
)
func (r *FullscreenRenderer) initScreen() {
@@ -153,6 +172,10 @@ func (r *FullscreenRenderer) Init() {
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
}
func (r *FullscreenRenderer) Top() int {
return 0
}
func (r *FullscreenRenderer) MaxX() int {
ncols, _ := _screen.Size()
return int(ncols)
@@ -176,14 +199,34 @@ func (r *FullscreenRenderer) Clear() {
_screen.Clear()
}
func (r *FullscreenRenderer) NeedScrollbarRedraw() bool {
return true
}
func (r *FullscreenRenderer) ShouldEmitResizeEvent() bool {
return true
}
func (r *FullscreenRenderer) Refresh() {
// noop
}
// TODO: Pixel width and height not implemented
func (r *FullscreenRenderer) Size() TermSize {
cols, lines := _screen.Size()
return TermSize{lines, cols, 0, 0}
}
func (r *FullscreenRenderer) GetChar() Event {
ev := _screen.PollEvent()
switch ev := ev.(type) {
case *tcell.EventResize:
// Ignore the first resize event
// https://github.com/gdamore/tcell/blob/v2.7.0/TUTORIAL.md?plain=1#L18
if _initialResize {
_initialResize = false
return Event{Invalid, 0, nil}
}
return Event{Resize, 0, nil}
// process mouse events:
@@ -205,54 +248,38 @@ func (r *FullscreenRenderer) GetChar() Event {
return Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, false, mod}}
case button&tcell.WheelUp != 0:
return Event{Mouse, 0, &MouseEvent{y, x, +1, false, false, false, mod}}
case button&tcell.Button1 != 0 && !drag:
// all potential double click events put their 'line' coordinate in the clickY array
// double click event has two conditions, temporal and spatial, the first is checked here
now := time.Now()
if now.Sub(r.prevDownTime) < doubleClickDuration {
r.clickY = append(r.clickY, y)
} else {
r.clickY = []int{y}
}
r.prevDownTime = now
case button&tcell.Button1 != 0:
double := false
if !drag {
// all potential double click events put their coordinates in the clicks array
// double click event has two conditions, temporal and spatial, the first is checked here
now := time.Now()
if now.Sub(r.prevDownTime) < doubleClickDuration {
r.clicks = append(r.clicks, [2]int{x, y})
} else {
r.clicks = [][2]int{{x, y}}
}
r.prevDownTime = now
// detect double clicks (also check for spatial condition)
n := len(r.clickY)
double := n > 1 && r.clickY[n-2] == r.clickY[n-1]
if double {
// make sure two consecutive double clicks require four clicks
r.clickY = []int{}
// detect double clicks (also check for spatial condition)
n := len(r.clicks)
double = n > 1 && r.clicks[n-2][0] == r.clicks[n-1][0] && r.clicks[n-2][1] == r.clicks[n-1][1]
if double {
// make sure two consecutive double clicks require four clicks
r.clicks = [][2]int{}
}
}
// fire single or double click event
return Event{Mouse, 0, &MouseEvent{y, x, 0, true, !double, double, mod}}
case button&tcell.Button2 != 0 && !drag:
case button&tcell.Button2 != 0:
return Event{Mouse, 0, &MouseEvent{y, x, 0, false, true, false, mod}}
case runtime.GOOS != "windows":
default:
// double and single taps on Windows don't quite work due to
// the console acting on the events and not allowing us
// to consume them.
left := button&tcell.Button1 != 0
down := left || button&tcell.Button3 != 0
double := false
if down {
now := time.Now()
if !left {
r.clickY = []int{}
} else if now.Sub(r.prevDownTime) < doubleClickDuration {
r.clickY = append(r.clickY, x)
} else {
r.clickY = []int{x}
r.prevDownTime = now
}
} else {
if len(r.clickY) > 1 && r.clickY[0] == r.clickY[1] &&
time.Now().Sub(r.prevDownTime) < doubleClickDuration {
double = true
}
}
return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, mod}}
}
@@ -411,6 +438,12 @@ func (r *FullscreenRenderer) GetChar() Event {
case tcell.KeyHome:
return Event{Home, 0, nil}
case tcell.KeyDelete:
if ctrl {
return Event{CtrlDelete, 0, nil}
}
if shift {
return Event{SDelete, 0, nil}
}
return Event{Del, 0, nil}
case tcell.KeyEnd:
return Event{End, 0, nil}
@@ -502,7 +535,7 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int,
if preview {
normal = ColPreview
}
return &TcellWindow{
w := &TcellWindow{
color: r.theme.Colored,
preview: preview,
top: top,
@@ -511,6 +544,8 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int,
height: height,
normal: normal,
borderStyle: borderStyle}
w.Erase()
return w
}
func (w *TcellWindow) Close() {
@@ -526,7 +561,13 @@ func fill(x, y, w, h int, n ColorPair, r rune) {
}
func (w *TcellWindow) Erase() {
fill(w.left-1, w.top, w.width+1, w.height, w.normal, ' ')
fill(w.left-1, w.top, w.width+1, w.height-1, w.normal, ' ')
w.drawBorder(false)
}
func (w *TcellWindow) EraseMaybe() bool {
w.Erase()
return true
}
func (w *TcellWindow) Enclose(y int, x int) bool {
@@ -561,6 +602,7 @@ func (w *TcellWindow) printString(text string, pair ColorPair) {
style = style.
Reverse(a&Attr(tcell.AttrReverse) != 0).
Underline(a&Attr(tcell.AttrUnderline) != 0).
StrikeThrough(a&Attr(tcell.AttrStrikeThrough) != 0).
Italic(a&Attr(tcell.AttrItalic) != 0).
Blink(a&Attr(tcell.AttrBlink) != 0).
Dim(a&Attr(tcell.AttrDim) != 0)
@@ -568,26 +610,27 @@ func (w *TcellWindow) printString(text string, pair ColorPair) {
gr := uniseg.NewGraphemes(text)
for gr.Next() {
st := style
rs := gr.Runes()
if len(rs) == 1 {
r := rs[0]
if r < rune(' ') { // ignore control characters
continue
if r == '\r' {
st = style.Dim(true)
rs[0] = '␍'
} else if r == '\n' {
w.lastY++
lx = 0
continue
} else if r == '\u000D' { // skip carriage return
st = style.Dim(true)
rs[0] = '␊'
} else if r < rune(' ') { // ignore control characters
continue
}
}
var xPos = w.left + w.lastX + lx
var yPos = w.top + w.lastY
if xPos < (w.left+w.width) && yPos < (w.top+w.height) {
_screen.SetContent(xPos, yPos, rs[0], rs[1:], style)
_screen.SetContent(xPos, yPos, rs[0], rs[1:], st)
}
lx += runewidth.StringWidth(string(rs))
lx += util.StringWidth(string(rs))
}
w.lastX += lx
}
@@ -612,16 +655,26 @@ func (w *TcellWindow) fillString(text string, pair ColorPair) FillReturn {
Dim(a&Attr(tcell.AttrDim) != 0).
Reverse(a&Attr(tcell.AttrReverse) != 0).
Underline(a&Attr(tcell.AttrUnderline) != 0).
StrikeThrough(a&Attr(tcell.AttrStrikeThrough) != 0).
Italic(a&Attr(tcell.AttrItalic) != 0)
gr := uniseg.NewGraphemes(text)
Loop:
for gr.Next() {
st := style
rs := gr.Runes()
if len(rs) == 1 && rs[0] == '\n' {
w.lastY++
w.lastX = 0
lx = 0
continue
if len(rs) == 1 {
r := rs[0]
switch r {
case '\r':
st = style.Dim(true)
rs[0] = '␍'
case '\n':
w.lastY++
w.lastX = 0
lx = 0
continue Loop
}
}
// word wrap:
@@ -638,8 +691,8 @@ func (w *TcellWindow) fillString(text string, pair ColorPair) FillReturn {
return FillSuspend
}
_screen.SetContent(xPos, yPos, rs[0], rs[1:], style)
lx += runewidth.StringWidth(string(rs))
_screen.SetContent(xPos, yPos, rs[0], rs[1:], st)
lx += util.StringWidth(string(rs))
}
w.lastX += lx
if w.lastX == w.width {
@@ -665,7 +718,15 @@ func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
return w.fillString(str, NewColorPair(fg, bg, a))
}
func (w *TcellWindow) drawBorder() {
func (w *TcellWindow) DrawBorder() {
w.drawBorder(false)
}
func (w *TcellWindow) DrawHBorder() {
w.drawBorder(true)
}
func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
shape := w.borderStyle.shape
if shape == BorderNone {
return
@@ -687,35 +748,52 @@ func (w *TcellWindow) drawBorder() {
style = w.normal.style()
}
hw := runeWidth(w.borderStyle.top)
switch shape {
case BorderRounded, BorderSharp, BorderHorizontal, BorderTop:
for x := left; x < right; x++ {
_screen.SetContent(x, top, w.borderStyle.horizontal, nil, style)
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderHorizontal, BorderTop:
max := right - 2*hw
if shape == BorderHorizontal || shape == BorderTop {
max = right - hw
}
// tcell has an issue displaying two overlapping wide runes
// e.g. SetContent( HH )
// SetContent( TR )
// ==================
// ( HH ) => TR is ignored
for x := left; x <= max; x += hw {
_screen.SetContent(x, top, w.borderStyle.top, nil, style)
}
}
switch shape {
case BorderRounded, BorderSharp, BorderHorizontal, BorderBottom:
for x := left; x < right; x++ {
_screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style)
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderHorizontal, BorderBottom:
max := right - 2*hw
if shape == BorderHorizontal || shape == BorderBottom {
max = right - hw
}
for x := left; x <= max; x += hw {
_screen.SetContent(x, bot-1, w.borderStyle.bottom, nil, style)
}
}
if !onlyHorizontal {
switch shape {
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderVertical, BorderLeft:
for y := top; y < bot; y++ {
_screen.SetContent(left, y, w.borderStyle.left, nil, style)
}
}
switch shape {
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderVertical, BorderRight:
vw := runeWidth(w.borderStyle.right)
for y := top; y < bot; y++ {
_screen.SetContent(right-vw, y, w.borderStyle.right, nil, style)
}
}
}
switch shape {
case BorderRounded, BorderSharp, BorderVertical, BorderLeft:
for y := top; y < bot; y++ {
_screen.SetContent(left, y, w.borderStyle.vertical, nil, style)
}
}
switch shape {
case BorderRounded, BorderSharp, BorderVertical, BorderRight:
for y := top; y < bot; y++ {
_screen.SetContent(right-1, y, w.borderStyle.vertical, nil, style)
}
}
switch shape {
case BorderRounded, BorderSharp:
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble:
_screen.SetContent(left, top, w.borderStyle.topLeft, nil, style)
_screen.SetContent(right-1, top, w.borderStyle.topRight, nil, style)
_screen.SetContent(right-runeWidth(w.borderStyle.topRight), top, w.borderStyle.topRight, nil, style)
_screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style)
_screen.SetContent(right-1, bot-1, w.borderStyle.bottomRight, nil, style)
_screen.SetContent(right-runeWidth(w.borderStyle.bottomRight), bot-1, w.borderStyle.bottomRight, nil, style)
}
}

View File

@@ -5,7 +5,7 @@ package tui
import (
"testing"
"github.com/gdamore/tcell"
"github.com/gdamore/tcell/v2"
"github.com/junegunn/fzf/src/util"
)
@@ -182,6 +182,7 @@ func TestGetCharEventKey(t *testing.T) {
r.Init()
// run and evaluate the tests
initialResizeAsInvalid := true
for _, test := range tests {
// generate key event
giveEvent := tcell.NewEventKey(test.giveKey.Type, test.giveKey.Char, test.giveKey.Mods)
@@ -191,8 +192,9 @@ func TestGetCharEventKey(t *testing.T) {
// process the event in fzf and evaluate the test
gotEvent := r.GetChar()
// skip Resize events, those are sometimes put in the buffer outside of this test
for gotEvent.Type == Resize {
t.Logf("Resize swallowed")
if initialResizeAsInvalid && gotEvent.Type == Invalid {
t.Logf("Resize as Invalid swallowed")
initialResizeAsInvalid = false
gotEvent = r.GetChar()
}
t.Logf("wantEvent = %T{Type: %v, Char: %q (%[3]v)}\n", test.wantKey, test.wantKey.Type, test.wantKey.Char)

View File

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

View File

@@ -5,6 +5,8 @@ import (
"os"
"strconv"
"time"
"github.com/rivo/uniseg"
)
// Types of user action
@@ -41,6 +43,7 @@ const (
CtrlZ
ESC
CtrlSpace
CtrlDelete
// https://apple.stackexchange.com/questions/24261/how-do-i-send-c-that-is-control-slash-to-the-terminal
CtrlBackSlash
@@ -54,6 +57,14 @@ const (
DoubleClick
LeftClick
RightClick
SLeftClick
SRightClick
ScrollUp
ScrollDown
SScrollUp
SScrollDown
PreviewScrollUp
PreviewScrollDown
BTab
BSpace
@@ -74,6 +85,7 @@ const (
SDown
SLeft
SRight
SDelete
F1
F2
@@ -90,6 +102,12 @@ const (
Change
BackwardEOF
Start
Load
Focus
One
Zero
Result
AltBS
@@ -248,25 +266,31 @@ func (p ColorPair) MergeNonDefault(other ColorPair) ColorPair {
}
type ColorTheme struct {
Colored bool
Input ColorAttr
Disabled ColorAttr
Fg ColorAttr
Bg ColorAttr
PreviewFg ColorAttr
PreviewBg ColorAttr
DarkBg ColorAttr
Gutter ColorAttr
Prompt ColorAttr
Match ColorAttr
Current ColorAttr
CurrentMatch ColorAttr
Spinner ColorAttr
Info ColorAttr
Cursor ColorAttr
Selected ColorAttr
Header ColorAttr
Border ColorAttr
Colored bool
Input ColorAttr
Disabled ColorAttr
Fg ColorAttr
Bg ColorAttr
PreviewFg ColorAttr
PreviewBg ColorAttr
DarkBg ColorAttr
Gutter ColorAttr
Prompt ColorAttr
Match ColorAttr
Current ColorAttr
CurrentMatch ColorAttr
Spinner ColorAttr
Info ColorAttr
Cursor ColorAttr
Selected ColorAttr
Header ColorAttr
Separator ColorAttr
Scrollbar ColorAttr
Border ColorAttr
PreviewBorder ColorAttr
PreviewScrollbar ColorAttr
BorderLabel ColorAttr
PreviewLabel ColorAttr
}
type Event struct {
@@ -275,6 +299,15 @@ type Event struct {
MouseEvent *MouseEvent
}
func (e Event) Is(types ...EventType) bool {
for _, t := range types {
if e.Type == t {
return true
}
}
return false
}
type MouseEvent struct {
Y int
X int
@@ -291,6 +324,10 @@ const (
BorderNone BorderShape = iota
BorderRounded
BorderSharp
BorderBold
BorderBlock
BorderThinBlock
BorderDouble
BorderHorizontal
BorderVertical
BorderTop
@@ -299,10 +336,28 @@ const (
BorderRight
)
func (s BorderShape) HasRight() bool {
switch s {
case BorderNone, BorderLeft, BorderTop, BorderBottom, BorderHorizontal: // No right
return false
}
return true
}
func (s BorderShape) HasTop() bool {
switch s {
case BorderNone, BorderLeft, BorderRight, BorderBottom, BorderVertical: // No top
return false
}
return true
}
type BorderStyle struct {
shape BorderShape
horizontal rune
vertical rune
top rune
bottom rune
left rune
right rune
topLeft rune
topRight rune
bottomLeft rune
@@ -312,64 +367,143 @@ type BorderStyle struct {
type BorderCharacter int
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
if unicode {
if shape == BorderRounded {
return BorderStyle{
shape: shape,
horizontal: '─',
vertical: '│',
topLeft: '╭',
topRight: '╮',
bottomLeft: '╰',
bottomRight: '╯',
}
}
if !unicode {
return BorderStyle{
shape: shape,
horizontal: '',
vertical: '',
top: '-',
bottom: '-',
left: '|',
right: '|',
topLeft: '+',
topRight: '+',
bottomLeft: '+',
bottomRight: '+',
}
}
switch shape {
case BorderSharp:
return BorderStyle{
shape: shape,
top: '─',
bottom: '─',
left: '│',
right: '│',
topLeft: '┌',
topRight: '┐',
bottomLeft: '└',
bottomRight: '┘',
}
case BorderBold:
return BorderStyle{
shape: shape,
top: '━',
bottom: '━',
left: '┃',
right: '┃',
topLeft: '┏',
topRight: '┓',
bottomLeft: '┗',
bottomRight: '┛',
}
case BorderBlock:
// ▛▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▜
// ▌ ▐
// ▙▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▟
return BorderStyle{
shape: shape,
top: '▀',
bottom: '▄',
left: '▌',
right: '▐',
topLeft: '▛',
topRight: '▜',
bottomLeft: '▙',
bottomRight: '▟',
}
case BorderThinBlock:
// 🭽▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔🭾
// ▏ ▕
// 🭼▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁🭿
return BorderStyle{
shape: shape,
top: '▔',
bottom: '▁',
left: '▏',
right: '▕',
topLeft: '🭽',
topRight: '🭾',
bottomLeft: '🭼',
bottomRight: '🭿',
}
case BorderDouble:
return BorderStyle{
shape: shape,
top: '═',
bottom: '═',
left: '║',
right: '║',
topLeft: '╔',
topRight: '╗',
bottomLeft: '╚',
bottomRight: '╝',
}
}
return BorderStyle{
shape: shape,
horizontal: '-',
vertical: '|',
topLeft: '+',
topRight: '+',
bottomLeft: '+',
bottomRight: '+',
top: '',
bottom: '',
left: '',
right: '',
topLeft: '',
topRight: '',
bottomLeft: '╰',
bottomRight: '╯',
}
}
func MakeTransparentBorder() BorderStyle {
return BorderStyle{
shape: BorderRounded,
horizontal: ' ',
vertical: ' ',
top: ' ',
bottom: ' ',
left: ' ',
right: ' ',
topLeft: ' ',
topRight: ' ',
bottomLeft: ' ',
bottomRight: ' '}
}
type TermSize struct {
Lines int
Columns int
PxWidth int
PxHeight int
}
type Renderer interface {
Init()
Resize(maxHeightFunc func(int) int)
Pause(clear bool)
Resume(clear bool, sigcont bool)
Clear()
RefreshWindows(windows []Window)
Refresh()
Close()
PassThrough(string)
NeedScrollbarRedraw() bool
ShouldEmitResizeEvent() bool
GetChar() Event
Top() int
MaxX() int
MaxY() int
Size() TermSize
NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window
}
@@ -379,6 +513,8 @@ type Window interface {
Width() int
Height() int
DrawBorder()
DrawHBorder()
Refresh()
FinishFill()
Close()
@@ -394,6 +530,7 @@ type Window interface {
Fill(text string) FillReturn
CFill(fg Color, bg Color, attr Attr, text string) FillReturn
Erase()
EraseMaybe() bool
}
type FullscreenRenderer struct {
@@ -401,7 +538,7 @@ type FullscreenRenderer struct {
mouse bool
forceBlack bool
prevDownTime time.Time
clickY []int
clicks [][2]int
}
func NewFullscreenRenderer(theme *ColorTheme, forceBlack bool, mouse bool) Renderer {
@@ -410,7 +547,7 @@ func NewFullscreenRenderer(theme *ColorTheme, forceBlack bool, mouse bool) Rende
mouse: mouse,
forceBlack: forceBlack,
prevDownTime: time.Unix(0, 0),
clickY: []int{}}
clicks: [][2]int{}}
return r
}
@@ -436,55 +573,75 @@ var (
ColSpinner ColorPair
ColInfo ColorPair
ColHeader ColorPair
ColSeparator ColorPair
ColScrollbar ColorPair
ColBorder ColorPair
ColPreview ColorPair
ColPreviewBorder ColorPair
ColBorderLabel ColorPair
ColPreviewLabel ColorPair
ColPreviewScrollbar ColorPair
ColPreviewSpinner ColorPair
)
func EmptyTheme() *ColorTheme {
return &ColorTheme{
Colored: true,
Input: ColorAttr{colUndefined, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined},
Fg: ColorAttr{colUndefined, AttrUndefined},
Bg: ColorAttr{colUndefined, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
DarkBg: ColorAttr{colUndefined, AttrUndefined},
Gutter: ColorAttr{colUndefined, AttrUndefined},
Prompt: ColorAttr{colUndefined, AttrUndefined},
Match: ColorAttr{colUndefined, AttrUndefined},
Current: ColorAttr{colUndefined, AttrUndefined},
CurrentMatch: ColorAttr{colUndefined, AttrUndefined},
Spinner: ColorAttr{colUndefined, AttrUndefined},
Info: ColorAttr{colUndefined, AttrUndefined},
Cursor: ColorAttr{colUndefined, AttrUndefined},
Selected: ColorAttr{colUndefined, AttrUndefined},
Header: ColorAttr{colUndefined, AttrUndefined},
Border: ColorAttr{colUndefined, AttrUndefined}}
Colored: true,
Input: ColorAttr{colUndefined, AttrUndefined},
Fg: ColorAttr{colUndefined, AttrUndefined},
Bg: ColorAttr{colUndefined, AttrUndefined},
DarkBg: ColorAttr{colUndefined, AttrUndefined},
Prompt: ColorAttr{colUndefined, AttrUndefined},
Match: ColorAttr{colUndefined, AttrUndefined},
Current: ColorAttr{colUndefined, AttrUndefined},
CurrentMatch: ColorAttr{colUndefined, AttrUndefined},
Spinner: ColorAttr{colUndefined, AttrUndefined},
Info: ColorAttr{colUndefined, AttrUndefined},
Cursor: ColorAttr{colUndefined, AttrUndefined},
Selected: ColorAttr{colUndefined, AttrUndefined},
Header: ColorAttr{colUndefined, AttrUndefined},
Border: ColorAttr{colUndefined, AttrUndefined},
BorderLabel: ColorAttr{colUndefined, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
Gutter: ColorAttr{colUndefined, AttrUndefined},
PreviewBorder: ColorAttr{colUndefined, AttrUndefined},
PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined},
PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
Separator: ColorAttr{colUndefined, AttrUndefined},
Scrollbar: ColorAttr{colUndefined, AttrUndefined},
}
}
func NoColorTheme() *ColorTheme {
return &ColorTheme{
Colored: false,
Input: ColorAttr{colDefault, AttrRegular},
Disabled: ColorAttr{colDefault, AttrRegular},
Fg: ColorAttr{colDefault, AttrRegular},
Bg: ColorAttr{colDefault, AttrRegular},
PreviewFg: ColorAttr{colDefault, AttrRegular},
PreviewBg: ColorAttr{colDefault, AttrRegular},
DarkBg: ColorAttr{colDefault, AttrRegular},
Gutter: ColorAttr{colDefault, AttrRegular},
Prompt: ColorAttr{colDefault, AttrRegular},
Match: ColorAttr{colDefault, Underline},
Current: ColorAttr{colDefault, Reverse},
CurrentMatch: ColorAttr{colDefault, Reverse | Underline},
Spinner: ColorAttr{colDefault, AttrRegular},
Info: ColorAttr{colDefault, AttrRegular},
Cursor: ColorAttr{colDefault, AttrRegular},
Selected: ColorAttr{colDefault, AttrRegular},
Header: ColorAttr{colDefault, AttrRegular},
Border: ColorAttr{colDefault, AttrRegular}}
Colored: false,
Input: ColorAttr{colDefault, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined},
DarkBg: ColorAttr{colDefault, AttrUndefined},
Prompt: ColorAttr{colDefault, AttrUndefined},
Match: ColorAttr{colDefault, Underline},
Current: ColorAttr{colDefault, Reverse},
CurrentMatch: ColorAttr{colDefault, Reverse | Underline},
Spinner: ColorAttr{colDefault, AttrUndefined},
Info: ColorAttr{colDefault, AttrUndefined},
Cursor: ColorAttr{colDefault, AttrUndefined},
Selected: ColorAttr{colDefault, AttrUndefined},
Header: ColorAttr{colDefault, AttrUndefined},
Border: ColorAttr{colDefault, AttrUndefined},
BorderLabel: ColorAttr{colDefault, AttrUndefined},
Disabled: ColorAttr{colDefault, AttrUndefined},
PreviewFg: ColorAttr{colDefault, AttrUndefined},
PreviewBg: ColorAttr{colDefault, AttrUndefined},
Gutter: ColorAttr{colDefault, AttrUndefined},
PreviewBorder: ColorAttr{colDefault, AttrUndefined},
PreviewScrollbar: ColorAttr{colDefault, AttrUndefined},
PreviewLabel: ColorAttr{colDefault, AttrUndefined},
Separator: ColorAttr{colDefault, AttrUndefined},
Scrollbar: ColorAttr{colDefault, AttrUndefined},
}
}
func errorExit(message string) {
@@ -494,65 +651,86 @@ func errorExit(message string) {
func init() {
Default16 = &ColorTheme{
Colored: true,
Input: ColorAttr{colDefault, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
DarkBg: ColorAttr{colBlack, AttrUndefined},
Gutter: ColorAttr{colUndefined, AttrUndefined},
Prompt: ColorAttr{colBlue, AttrUndefined},
Match: ColorAttr{colGreen, AttrUndefined},
Current: ColorAttr{colYellow, AttrUndefined},
CurrentMatch: ColorAttr{colGreen, AttrUndefined},
Spinner: ColorAttr{colGreen, AttrUndefined},
Info: ColorAttr{colWhite, AttrUndefined},
Cursor: ColorAttr{colRed, AttrUndefined},
Selected: ColorAttr{colMagenta, AttrUndefined},
Header: ColorAttr{colCyan, AttrUndefined},
Border: ColorAttr{colBlack, AttrUndefined}}
Colored: true,
Input: ColorAttr{colDefault, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined},
DarkBg: ColorAttr{colBlack, AttrUndefined},
Prompt: ColorAttr{colBlue, AttrUndefined},
Match: ColorAttr{colGreen, AttrUndefined},
Current: ColorAttr{colYellow, AttrUndefined},
CurrentMatch: ColorAttr{colGreen, AttrUndefined},
Spinner: ColorAttr{colGreen, AttrUndefined},
Info: ColorAttr{colWhite, AttrUndefined},
Cursor: ColorAttr{colRed, AttrUndefined},
Selected: ColorAttr{colMagenta, AttrUndefined},
Header: ColorAttr{colCyan, AttrUndefined},
Border: ColorAttr{colBlack, AttrUndefined},
BorderLabel: ColorAttr{colWhite, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
Gutter: ColorAttr{colUndefined, AttrUndefined},
PreviewBorder: ColorAttr{colUndefined, AttrUndefined},
PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined},
PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
Separator: ColorAttr{colUndefined, AttrUndefined},
Scrollbar: ColorAttr{colUndefined, AttrUndefined},
}
Dark256 = &ColorTheme{
Colored: true,
Input: ColorAttr{colDefault, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
DarkBg: ColorAttr{236, AttrUndefined},
Gutter: ColorAttr{colUndefined, AttrUndefined},
Prompt: ColorAttr{110, AttrUndefined},
Match: ColorAttr{108, AttrUndefined},
Current: ColorAttr{254, AttrUndefined},
CurrentMatch: ColorAttr{151, AttrUndefined},
Spinner: ColorAttr{148, AttrUndefined},
Info: ColorAttr{144, AttrUndefined},
Cursor: ColorAttr{161, AttrUndefined},
Selected: ColorAttr{168, AttrUndefined},
Header: ColorAttr{109, AttrUndefined},
Border: ColorAttr{59, AttrUndefined}}
Colored: true,
Input: ColorAttr{colDefault, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined},
DarkBg: ColorAttr{236, AttrUndefined},
Prompt: ColorAttr{110, AttrUndefined},
Match: ColorAttr{108, AttrUndefined},
Current: ColorAttr{254, AttrUndefined},
CurrentMatch: ColorAttr{151, AttrUndefined},
Spinner: ColorAttr{148, AttrUndefined},
Info: ColorAttr{144, AttrUndefined},
Cursor: ColorAttr{161, AttrUndefined},
Selected: ColorAttr{168, AttrUndefined},
Header: ColorAttr{109, AttrUndefined},
Border: ColorAttr{59, AttrUndefined},
BorderLabel: ColorAttr{145, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
Gutter: ColorAttr{colUndefined, AttrUndefined},
PreviewBorder: ColorAttr{colUndefined, AttrUndefined},
PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined},
PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
Separator: ColorAttr{colUndefined, AttrUndefined},
Scrollbar: ColorAttr{colUndefined, AttrUndefined},
}
Light256 = &ColorTheme{
Colored: true,
Input: ColorAttr{colDefault, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
DarkBg: ColorAttr{251, AttrUndefined},
Gutter: ColorAttr{colUndefined, AttrUndefined},
Prompt: ColorAttr{25, AttrUndefined},
Match: ColorAttr{66, AttrUndefined},
Current: ColorAttr{237, AttrUndefined},
CurrentMatch: ColorAttr{23, AttrUndefined},
Spinner: ColorAttr{65, AttrUndefined},
Info: ColorAttr{101, AttrUndefined},
Cursor: ColorAttr{161, AttrUndefined},
Selected: ColorAttr{168, AttrUndefined},
Header: ColorAttr{31, AttrUndefined},
Border: ColorAttr{145, AttrUndefined}}
Colored: true,
Input: ColorAttr{colDefault, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined},
DarkBg: ColorAttr{251, AttrUndefined},
Prompt: ColorAttr{25, AttrUndefined},
Match: ColorAttr{66, AttrUndefined},
Current: ColorAttr{237, AttrUndefined},
CurrentMatch: ColorAttr{23, AttrUndefined},
Spinner: ColorAttr{65, AttrUndefined},
Info: ColorAttr{101, AttrUndefined},
Cursor: ColorAttr{161, AttrUndefined},
Selected: ColorAttr{168, AttrUndefined},
Header: ColorAttr{31, AttrUndefined},
Border: ColorAttr{145, AttrUndefined},
BorderLabel: ColorAttr{59, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
Gutter: ColorAttr{colUndefined, AttrUndefined},
PreviewBorder: ColorAttr{colUndefined, AttrUndefined},
PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined},
PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
Separator: ColorAttr{colUndefined, AttrUndefined},
Scrollbar: ColorAttr{colUndefined, AttrUndefined},
}
}
func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
@@ -571,13 +749,9 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
return c
}
theme.Input = o(baseTheme.Input, theme.Input)
theme.Disabled = o(theme.Input, o(baseTheme.Disabled, theme.Disabled))
theme.Fg = o(baseTheme.Fg, theme.Fg)
theme.Bg = o(baseTheme.Bg, theme.Bg)
theme.PreviewFg = o(theme.Fg, o(baseTheme.PreviewFg, theme.PreviewFg))
theme.PreviewBg = o(theme.Bg, o(baseTheme.PreviewBg, theme.PreviewBg))
theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg)
theme.Gutter = o(theme.DarkBg, o(baseTheme.Gutter, theme.Gutter))
theme.Prompt = o(baseTheme.Prompt, theme.Prompt)
theme.Match = o(baseTheme.Match, theme.Match)
theme.Current = o(baseTheme.Current, theme.Current)
@@ -588,6 +762,18 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
theme.Selected = o(baseTheme.Selected, theme.Selected)
theme.Header = o(baseTheme.Header, theme.Header)
theme.Border = o(baseTheme.Border, theme.Border)
theme.BorderLabel = o(baseTheme.BorderLabel, theme.BorderLabel)
// These colors are not defined in the base themes
theme.Disabled = o(theme.Input, theme.Disabled)
theme.Gutter = o(theme.DarkBg, theme.Gutter)
theme.PreviewFg = o(theme.Fg, theme.PreviewFg)
theme.PreviewBg = o(theme.Bg, theme.PreviewBg)
theme.PreviewLabel = o(theme.BorderLabel, theme.PreviewLabel)
theme.PreviewBorder = o(theme.Border, theme.PreviewBorder)
theme.Separator = o(theme.Border, theme.Separator)
theme.Scrollbar = o(theme.Border, theme.Scrollbar)
theme.PreviewScrollbar = o(theme.PreviewBorder, theme.PreviewScrollbar)
initPalette(theme)
}
@@ -619,7 +805,17 @@ func initPalette(theme *ColorTheme) {
ColSpinner = pair(theme.Spinner, theme.Bg)
ColInfo = pair(theme.Info, theme.Bg)
ColHeader = pair(theme.Header, theme.Bg)
ColSeparator = pair(theme.Separator, theme.Bg)
ColScrollbar = pair(theme.Scrollbar, theme.Bg)
ColBorder = pair(theme.Border, theme.Bg)
ColBorderLabel = pair(theme.BorderLabel, theme.Bg)
ColPreviewLabel = pair(theme.PreviewLabel, theme.PreviewBg)
ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
ColPreviewBorder = pair(theme.Border, theme.PreviewBg)
ColPreviewBorder = pair(theme.PreviewBorder, theme.PreviewBg)
ColPreviewScrollbar = pair(theme.PreviewScrollbar, theme.PreviewBg)
ColPreviewSpinner = pair(theme.Spinner, theme.PreviewBg)
}
func runeWidth(r rune) int {
return uniseg.StringWidth(string(r))
}

View File

@@ -9,7 +9,6 @@ const (
EvtSearchNew
EvtSearchProgress
EvtSearchFin
EvtClose
)
func TestEventBox(t *testing.T) {

View File

@@ -7,10 +7,14 @@ import (
"time"
"github.com/mattn/go-isatty"
"github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
)
// StringWidth returns string width where each CR/LF character takes 1 column
func StringWidth(s string) int {
return uniseg.StringWidth(s) + strings.Count(s, "\n") + strings.Count(s, "\r")
}
// RunesWidth returns runes width
func RunesWidth(runes []rune, prefixWidth int, tabstop int, limit int) (int, int) {
width := 0
@@ -22,8 +26,7 @@ func RunesWidth(runes []rune, prefixWidth int, tabstop int, limit int) (int, int
if len(rs) == 1 && rs[0] == '\t' {
w = tabstop - (prefixWidth+width)%tabstop
} else {
s := string(rs)
w = runewidth.StringWidth(s) + strings.Count(s, "\n")
w = StringWidth(string(rs))
}
width += w
if width > limit {
@@ -41,7 +44,7 @@ func Truncate(input string, limit int) ([]rune, int) {
gr := uniseg.NewGraphemes(input)
for gr.Next() {
rs := gr.Runes()
w := runewidth.StringWidth(string(rs))
w := StringWidth(string(rs))
if width+w > limit {
return runes, width
}
@@ -153,3 +156,23 @@ func Once(nextResponse bool) func() bool {
return prevState
}
}
// RepeatToFill repeats the given string to fill the given width
func RepeatToFill(str string, length int, limit int) string {
times := limit / length
rest := limit % length
output := strings.Repeat(str, times)
if rest > 0 {
for _, r := range str {
rest -= uniseg.StringWidth(string(r))
if rest < 0 {
break
}
output += string(r)
if rest == 0 {
break
}
}
}
return output
}

View File

@@ -1,14 +1,76 @@
package util
import "testing"
import (
"math"
"strings"
"testing"
"time"
)
func TestMax(t *testing.T) {
if Max(10, 1) != 10 {
t.Error("Expected", 10)
}
if Max(-2, 5) != 5 {
t.Error("Invalid result")
t.Error("Expected", 5)
}
}
func TestContrain(t *testing.T) {
func TestMax16(t *testing.T) {
if Max16(10, 1) != 10 {
t.Error("Expected", 10)
}
if Max16(-2, 5) != 5 {
t.Error("Expected", 5)
}
if Max16(math.MaxInt16, 0) != math.MaxInt16 {
t.Error("Expected", math.MaxInt16)
}
if Max16(0, math.MinInt16) != 0 {
t.Error("Expected", 0)
}
}
func TestMax32(t *testing.T) {
if Max32(10, 1) != 10 {
t.Error("Expected", 10)
}
if Max32(-2, 5) != 5 {
t.Error("Expected", 5)
}
if Max32(math.MaxInt32, 0) != math.MaxInt32 {
t.Error("Expected", math.MaxInt32)
}
if Max32(0, math.MinInt32) != 0 {
t.Error("Expected", 0)
}
}
func TestMin(t *testing.T) {
if Min(10, 1) != 1 {
t.Error("Expected", 1)
}
if Min(-2, 5) != -2 {
t.Error("Expected", -2)
}
}
func TestMin32(t *testing.T) {
if Min32(10, 1) != 1 {
t.Error("Expected", 1)
}
if Min32(-2, 5) != -2 {
t.Error("Expected", -2)
}
if Min32(math.MaxInt32, 0) != 0 {
t.Error("Expected", 0)
}
if Min32(0, math.MinInt32) != math.MinInt32 {
t.Error("Expected", math.MinInt32)
}
}
func TestConstrain(t *testing.T) {
if Constrain(-3, -1, 3) != -1 {
t.Error("Expected", -1)
}
@@ -21,6 +83,55 @@ func TestContrain(t *testing.T) {
}
}
func TestConstrain32(t *testing.T) {
if Constrain32(-3, -1, 3) != -1 {
t.Error("Expected", -1)
}
if Constrain32(2, -1, 3) != 2 {
t.Error("Expected", 2)
}
if Constrain32(5, -1, 3) != 3 {
t.Error("Expected", 3)
}
if Constrain32(0, math.MinInt32, math.MaxInt32) != 0 {
t.Error("Expected", 0)
}
}
func TestAsUint16(t *testing.T) {
if AsUint16(5) != 5 {
t.Error("Expected", 5)
}
if AsUint16(-10) != 0 {
t.Error("Expected", 0)
}
if AsUint16(math.MaxUint16) != math.MaxUint16 {
t.Error("Expected", math.MaxUint16)
}
if AsUint16(math.MinInt32) != 0 {
t.Error("Expected", 0)
}
if AsUint16(math.MinInt16) != 0 {
t.Error("Expected", 0)
}
if AsUint16(math.MaxUint16+1) != math.MaxUint16 {
t.Error("Expected", math.MaxUint16)
}
}
func TestDurWithIn(t *testing.T) {
if DurWithin(time.Duration(5), time.Duration(1), time.Duration(8)) != time.Duration(5) {
t.Error("Expected", time.Duration(0))
}
if DurWithin(time.Duration(0)*time.Second, time.Second, time.Duration(3)*time.Second) != time.Second {
t.Error("Expected", time.Second)
}
if DurWithin(time.Duration(10)*time.Second, time.Duration(0), time.Second) != time.Second {
t.Error("Expected", time.Second)
}
}
func TestOnce(t *testing.T) {
o := Once(false)
if o() {
@@ -53,6 +164,18 @@ func TestRunesWidth(t *testing.T) {
t.Errorf("Expected overflow index: %d, actual: %d", args[2], overflowIdx)
}
}
for _, input := range []struct {
s string
w int
}{
{"▶", 1},
{"▶️", 2},
} {
width, _ := RunesWidth([]rune(input.s), 0, 0, 100)
if width != input.w {
t.Errorf("Expected width of %s: %d, actual: %d", input.s, input.w, width)
}
}
}
func TestTruncate(t *testing.T) {
@@ -64,3 +187,19 @@ func TestTruncate(t *testing.T) {
t.Errorf("Expected: 6, actual: %d", width)
}
}
func TestRepeatToFill(t *testing.T) {
if RepeatToFill("abcde", 10, 50) != strings.Repeat("abcde", 5) {
t.Error("Expected:", strings.Repeat("abcde", 5))
}
if RepeatToFill("abcde", 10, 42) != strings.Repeat("abcde", 4)+"abcde"[:2] {
t.Error("Expected:", strings.Repeat("abcde", 4)+"abcde"[:2])
}
}
func TestStringWidth(t *testing.T) {
w := StringWidth("─")
if w != 1 {
t.Errorf("Expected: %d, Actual: %d", 1, w)
}
}

View File

@@ -6,6 +6,8 @@ import (
"os"
"os/exec"
"syscall"
"golang.org/x/sys/unix"
)
// ExecCommand executes the given command with $SHELL
@@ -45,3 +47,7 @@ func SetNonblock(file *os.File, nonblock bool) {
func Read(fd int, b []byte) (int, error) {
return syscall.Read(int(fd), b)
}
func SetStdin(file *os.File) {
unix.Dup2(int(file.Fd()), 0)
}

View File

@@ -81,3 +81,7 @@ func SetNonblock(file *os.File, nonblock bool) {
func Read(fd int, b []byte) (int, error) {
return syscall.Read(syscall.Handle(fd), b)
}
func SetStdin(file *os.File) {
// No-op
}

File diff suppressed because it is too large Load Diff

10
typos.toml Normal file
View File

@@ -0,0 +1,10 @@
# See https://github.com/crate-ci/typos/blob/master/docs/reference.md to configure typos
[default.extend-words]
ba = "ba"
fo = "fo"
enew = "enew"
tabe = "tabe"
Iterm = "Iterm"
[files]
extend-exclude = ["README.md"]

View File

@@ -94,6 +94,7 @@ done
bind_file="${fish_dir}/functions/fish_user_key_bindings.fish"
if [ -f "$bind_file" ]; then
remove_line "$bind_file" "fzf_key_bindings"
remove_line "$bind_file" "fzf --fish | source"
fi
if [ -d "${fish_dir}/functions" ]; then