Compare commits

...

91 Commits

Author SHA1 Message Date
Junegunn Choi
62963dcefd 0.49.0 2024-04-05 00:20:26 +09:00
Junegunn Choi
68a35e4735 Do not trim CR on Windows when --read0 is set 2024-04-04 23:39:29 +09:00
Charlie Vieth
9b9ad77e1c mod: update changes/fastwalk to v1.0.3 (#3709)
Update charlievieth/fastwalk to resolve issue #3706.
2024-04-04 13:29:32 +09:00
Junegunn Choi
118b4d4a01 [bash] Add -o nospace to dir completion options (#1987) 2024-04-04 13:20:31 +09:00
Junegunn Choi
da14ab6f16 [bash] Remove -o default from dir completion options (#1987) 2024-04-04 12:58:52 +09:00
Junegunn Choi
09a4ca6ab5 [bash] Fix variable completion of directory-related commands
Fix #1987
2024-04-04 12:43:35 +09:00
Junegunn Choi
8a2df79711 Do not hide separator by default on --info=inline-right|hidden 2024-04-04 00:05:55 +09:00
Junegunn Choi
c30e486b64 Further performance improvements by removing unnecessary copies 2024-04-02 08:43:08 +09:00
Junegunn Choi
a575c0c54b GitHub Actions: Use Go "1.20" 2024-04-02 01:47:24 +09:00
Junegunn Choi
77fe96ac0d GitHub Actions: Use Go 1.20 2024-04-02 01:44:18 +09:00
Junegunn Choi
5234c3759a Improve ingestion performance (by around 40%)
Summary
    fzf --sync --bind load:accept < 27M-lines ran
      1.16 ± 0.01 times faster than fzf-41b3511 --sync --bind load:accept < 27M-lines
      1.44 ± 0.01 times faster than fzf-0.48.1 --sync --bind load:accept < 27M-lines
2024-04-02 01:38:12 +09:00
Junegunn Choi
41b3511ad9 Improve ingestion performance (by around 20%) 2024-04-01 23:38:46 +09:00
Junegunn Choi
128e4a2e8d [fish] Fix $dir in FZF_{CTRL_T,ALT_C}_COMMAND not evaluated
Fix #3705
2024-03-31 20:37:20 +09:00
junegunn
07ac90d798 Deploying to master from @ junegunn/fzf@7de87a9b2c 🚀 2024-03-31 00:01:48 +00:00
Emilio Vesprini
7de87a9b2c [shell] Make ALT-C use the absolute path to the selected directory (#3688)
Rationale: this way the resulting cd command that ends up in the shell
history can be reused to get to the same location regardless of
the current working directory.

Co-authored-by: LangLangBart <92653266+LangLangBart@users.noreply.github.com>
2024-03-31 01:13:15 +09:00
Junegunn Choi
dff865239a [bash-completion] Make dynamic loader return 124 to retry completion
Close #3702
2024-03-29 16:21:43 +09:00
Junegunn Choi
07f8f70c5b Fix flaky test case 2024-03-29 16:15:53 +09:00
Matthieu Cneude
f625c5aabe Add environment variables: FZF_{BORDER,PREVIEW}_LABEL (#3693)
The environment variable get the value of the preview label, even if it
has been updated with an action. It can be useful to track the label of
the preview and be able to switch between previews using only one
binding.

Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2024-03-29 16:14:08 +09:00
Junegunn Choi
8a74976c1f Add track-current, untrack-current, and toggle-track-current (#3699)
Close #3691
2024-03-28 20:42:01 +09:00
Junegunn Choi
b6bfd4a5cb Fix typo in comment 2024-03-27 17:36:08 +09:00
Junegunn Choi
008fb9d258 Fix reload and reload-sync behaviors
https://github.com/junegunn/fzf/discussions/3696#discussioncomment-8915593
2024-03-27 17:25:56 +09:00
Junegunn Choi
db6db49ed6 Increase the buffer size for POST requests
Close #3685
2024-03-21 19:18:43 +09:00
Junegunn Choi
05453881c3 Set a 2-second timeout for POST requests
Close #3685
2024-03-21 19:18:38 +09:00
Junegunn Choi
5e47ab9431 README: Mention that you can source individual script files 2024-03-21 12:35:52 +09:00
LangLangBart
ec70acd0b9 chore: transition from markdown to YAML for issue template (#3687) 2024-03-21 08:33:28 +09:00
zeertzjq
25e61056b6 [fish] Fix Ctrl-T and Alt-C not using last token as search root (#3684) 2024-03-19 14:44:42 +09:00
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
51 changed files with 1464 additions and 649 deletions

View File

@@ -1,22 +0,0 @@
<!-- ISSUES NOT FOLLOWING THIS TEMPLATE WILL BE CLOSED AND DELETED -->
<!-- Check all that apply [x] -->
- [ ] I have read through the manual page (`man fzf`)
- [ ] I have the latest version of fzf
- [ ] I have searched through the existing issues
## Info
- OS
- [ ] Linux
- [ ] Mac OS X
- [ ] Windows
- [ ] Etc.
- Shell
- [ ] bash
- [ ] zsh
- [ ] fish
## Problem / Steps to reproduce

View File

@@ -0,0 +1,47 @@
---
name: Issue Template
description: Report a problem or bug related to fzf to help us improve
body:
- type: markdown
attributes:
value: ISSUES NOT FOLLOWING THIS TEMPLATE WILL BE CLOSED AND DELETED
- type: checkboxes
attributes:
label: Checklist
options:
- label: I have read through the manual page (`man fzf`)
required: true
- label: I have searched through the existing issues
required: true
- type: input
attributes:
label: Output of `fzf --version`
placeholder: e.g. 0.48.1 (d579e33)
validations:
required: true
- type: checkboxes
attributes:
label: OS
options:
- label: Linux
- label: macOS
- label: Windows
- label: Etc.
- type: checkboxes
attributes:
label: Shell
options:
- label: bash
- label: zsh
- label: fish
- type: textarea
attributes:
label: Problem / Steps to reproduce
validations:
required: true

View File

@@ -27,7 +27,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0

View File

@@ -9,6 +9,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: 'Checkout Repository' - name: 'Checkout Repository'
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: 'Dependency Review' - name: 'Dependency Review'
uses: actions/dependency-review-action@v4 uses: actions/dependency-review-action@v4

View File

@@ -11,18 +11,21 @@ on:
permissions: permissions:
contents: read contents: read
env:
LANG: C.UTF-8
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: 1.19 go-version: "1.20"
- name: Setup Ruby - name: Setup Ruby
uses: ruby/setup-ruby@v1 uses: ruby/setup-ruby@v1
@@ -42,4 +45,7 @@ jobs:
run: make test run: make test
- name: Integration 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,14 +15,14 @@ jobs:
build: build:
runs-on: macos-latest runs-on: macos-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: 1.18 go-version: "1.20"
- name: Setup Ruby - name: Setup Ruby
uses: ruby/setup-ruby@v1 uses: ruby/setup-ruby@v1

View File

@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout 🛎️ - name: Checkout 🛎️
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Generate Sponsors 💖 - name: Generate Sponsors 💖
uses: JamesIves/github-sponsors-readme-action@v1 uses: JamesIves/github-sponsors-readme-action@v1

View File

@@ -6,5 +6,5 @@ jobs:
name: Spell Check with Typos name: Spell Check with Typos
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: crate-ci/typos@v1.17.2 - uses: crate-ci/typos@v1.19.0

View File

@@ -20,10 +20,6 @@ builds:
cat > /tmp/fzf-gon-amd64.hcl << EOF cat > /tmp/fzf-gon-amd64.hcl << EOF
source = ["./dist/fzf-macos_darwin_amd64_v1/fzf"] source = ["./dist/fzf-macos_darwin_amd64_v1/fzf"]
bundle_id = "kr.junegunn.fzf" bundle_id = "kr.junegunn.fzf"
apple_id {
username = "junegunn.c@gmail.com"
password = "@env:AC_PASSWORD"
}
sign { sign {
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)" application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
} }
@@ -48,10 +44,6 @@ builds:
cat > /tmp/fzf-gon-arm64.hcl << EOF cat > /tmp/fzf-gon-arm64.hcl << EOF
source = ["./dist/fzf-macos-arm_darwin_arm64/fzf"] source = ["./dist/fzf-macos-arm_darwin_arm64/fzf"]
bundle_id = "kr.junegunn.fzf" bundle_id = "kr.junegunn.fzf"
apple_id {
username = "junegunn.c@gmail.com"
password = "@env:AC_PASSWORD"
}
sign { sign {
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)" application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
} }

View File

@@ -1 +1 @@
golang 1.21.6 golang 1.20.13

View File

@@ -552,7 +552,7 @@ pods() {
- Press enter key on a pod to `kubectl exec` into it - Press enter key on a pod to `kubectl exec` into it
- Press CTRL-O to open the log in your editor - Press CTRL-O to open the log in your editor
- Press CTRL-R to reload the pod list - 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 window options
1. `80%,border-bottom` 1. `80%,border-bottom`
1. `hidden` 1. `hidden`

View File

@@ -6,7 +6,7 @@ Build instructions
### Prerequisites ### Prerequisites
- Go 1.18 or above - Go 1.20 or above
### Using Makefile ### Using Makefile
@@ -42,6 +42,8 @@ Third-party libraries used
- Licensed under [MIT](http://mattn.mit-license.org) - Licensed under [MIT](http://mattn.mit-license.org)
- [tcell](https://github.com/gdamore/tcell) - [tcell](https://github.com/gdamore/tcell)
- Licensed under [Apache License 2.0](https://github.com/gdamore/tcell/blob/master/LICENSE) - 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 License
------- -------

View File

@@ -1,6 +1,128 @@
CHANGELOG CHANGELOG
========= =========
0.49.0
------
- Ingestion performance improved by around 40% (more or less depending on options)
- `--info=hidden` and `--info=inline-right` will no longer hide the horizontal separator by default. This gives you more flexibility in customizing the layout.
```sh
fzf --border --info=inline-right
fzf --border --info=inline-right --separator ═
fzf --border --info=inline-right --no-separator
fzf --border --info=hidden
fzf --border --info=hidden --separator ━
fzf --border --info=hidden --no-separator
```
- Added two environment variables exported to the child processes
- `FZF_PREVIEW_LABEL`
- `FZF_BORDER_LABEL`
```sh
# Use the current value of $FZF_PREVIEW_LABEL to determine which actions to perform
git ls-files |
fzf --header 'Press CTRL-P to change preview mode' \
--bind='ctrl-p:transform:[[ $FZF_PREVIEW_LABEL =~ cat ]] \
&& echo "change-preview(git log --color=always \{})+change-preview-label([[ log ]])" \
|| echo "change-preview(bat --color=always \{})+change-preview-label([[ cat ]])"'
```
- Renamed `track` action to `track-current` to highlight the difference between the global tracking state set by `--track` and a one-off tracking action
- `track` is still available as an alias
- Added `untrack-current` and `toggle-track-current` actions
- `*-current` actions are no-op when the global tracking state is set
- Bug fixes and minor improvements
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 0.46.0
------ ------
- Added two new events - Added two new events

View File

@@ -4,7 +4,7 @@ GOOS ?= $(word 1, $(subst /, " ", $(word 4, $(shell go version))))
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST))) MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
ROOT_DIR := $(shell dirname $(MAKEFILE)) 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 ifdef FZF_VERSION
VERSION := $(FZF_VERSION) VERSION := $(FZF_VERSION)

View File

@@ -238,19 +238,20 @@ call fzf#run({'sink': 'e'})
``` ```
We haven't specified the `source`, so this is equivalent to starting fzf on 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 command line without standard input pipe; fzf will traverse the file system
`$FZF_DEFAULT_COMMAND` if defined) to list the files under the current under the current directory to get the list of files. (If
directory. When you select one, it will open it with the sink, `:e` command. `$FZF_DEFAULT_COMMAND` is set, fzf will use the output of the command
If you want to open it in a new tab, you can pass `:tabedit` command instead instead.) When you select one, it will open it with the sink, `:e` command. If
as the sink. you want to open it in a new tab, you can pass `:tabedit` command instead as
the sink.
```vim ```vim
call fzf#run({'sink': 'tabedit'}) call fzf#run({'sink': 'tabedit'})
``` ```
Instead of using the default find command, you can use any shell command as You can use any shell command as the source to generate the list. The
the source. The following example will list the files managed by git. It's following example will list the files managed by git. It's equivalent to
equivalent to running `git ls-files | fzf` on shell. running `git ls-files | fzf` on shell.
```vim ```vim
call fzf#run({'source': 'git ls-files', 'sink': 'e'}) call fzf#run({'source': 'git ls-files', 'sink': 'e'})

194
README.md

File diff suppressed because one or more lines are too long

View File

@@ -95,9 +95,9 @@ while [[ $# -gt 0 ]]; do
elif [[ "$size" =~ %$ ]]; then elif [[ "$size" =~ %$ ]]; then
size=${size:0:((${#size}-1))} size=${size:0:((${#size}-1))}
if [[ -n "$swap" ]]; then if [[ -n "$swap" ]]; then
opt="$opt -p $(( 100 - size ))" opt="$opt -l $(( 100 - size ))%"
else else
opt="$opt -p $size" opt="$opt -l $size%"
fi fi
else else
if [[ -n "$swap" ]]; then if [[ -n "$swap" ]]; then
@@ -196,8 +196,9 @@ if [[ "$opt" =~ "-E" ]]; then
exit 2 exit 2
fi fi
fi fi
[[ -n "$FZF_DEFAULT_OPTS" ]] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")" envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
[[ -n "$FZF_DEFAULT_COMMAND" ]] && 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 "$RUNEWIDTH_EASTASIAN" ]] && envs="$envs RUNEWIDTH_EASTASIAN=$(printf %q "$RUNEWIDTH_EASTASIAN")"
[[ -n "$BAT_THEME" ]] && envs="$envs BAT_THEME=$(printf %q "$BAT_THEME")" [[ -n "$BAT_THEME" ]] && envs="$envs BAT_THEME=$(printf %q "$BAT_THEME")"
echo "$envs;" > "$argsf" echo "$envs;" > "$argsf"

View File

@@ -1,4 +1,4 @@
fzf.txt fzf Last change: January 1 2024 fzf.txt fzf Last change: February 15 2024
FZF - TABLE OF CONTENTS *fzf* *fzf-toc* FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
============================================================================== ==============================================================================
@@ -264,17 +264,18 @@ entry.
call fzf#run({'sink': 'e'}) call fzf#run({'sink': 'e'})
< <
We haven't specified the `source`, so this is equivalent to starting fzf on 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 command line without standard input pipe; fzf will traverse the file system
`$FZF_DEFAULT_COMMAND` if defined) to list the files under the current under the current directory to get the list of files. (If
directory. When you select one, it will open it with the sink, `:e` command. `$FZF_DEFAULT_COMMAND` is set, fzf will use the output of the command
If you want to open it in a new tab, you can pass `:tabedit` command instead instead.) When you select one, it will open it with the sink, `:e` command. If
as the sink. you want to open it in a new tab, you can pass `:tabedit` command instead as
the sink.
> >
call fzf#run({'sink': 'tabedit'}) call fzf#run({'sink': 'tabedit'})
< <
Instead of using the default find command, you can use any shell command as You can use any shell command as the source to generate the list. The
the source. The following example will list the files managed by git. It's following example will list the files managed by git. It's equivalent to
equivalent to running `git ls-files | fzf` on shell. running `git ls-files | fzf` on shell.
> >
call fzf#run({'source': 'git ls-files', 'sink': 'e'}) call fzf#run({'source': 'git ls-files', 'sink': 'e'})
< <
@@ -417,24 +418,12 @@ TIPS *fzf-tips*
< fzf inside terminal buffer >________________________________________________~ < fzf inside terminal buffer >________________________________________________~
*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. 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 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` colors using `g:terminal_ansi_colors` in regular Vim or `g:terminal_color_x`
in Neovim. 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 " Terminal colors for seoul256 color scheme
if has('nvim') if has('nvim')

20
go.mod
View File

@@ -1,22 +1,20 @@
module github.com/junegunn/fzf module github.com/junegunn/fzf
require ( require (
github.com/gdamore/tcell/v2 v2.5.4 github.com/charlievieth/fastwalk v1.0.3
github.com/junegunn/uniseg v0.0.0-20240120174029-b504da4f6ed2 github.com/gdamore/tcell/v2 v2.7.4
github.com/mattn/go-isatty v0.0.17 github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-shellwords v1.0.12 github.com/mattn/go-shellwords v1.0.12
github.com/saracen/walker v0.1.3 github.com/rivo/uniseg v0.4.7
golang.org/x/sys v0.16.0 golang.org/x/sys v0.18.0
golang.org/x/term v0.16.0 golang.org/x/term v0.18.0
) )
require ( require (
github.com/gdamore/encoding v1.0.0 // indirect github.com/gdamore/encoding v1.0.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/rivo/uniseg v0.4.4 // indirect golang.org/x/text v0.14.0 // indirect
golang.org/x/sync v0.5.0 // indirect
golang.org/x/text v0.5.0 // indirect
) )
go 1.17 go 1.20

49
go.sum
View File

@@ -1,52 +1,57 @@
github.com/charlievieth/fastwalk v1.0.3 h1:eNWFaNPe5srPqQ5yyDbhAf11paeZaHWcihRhpuYFfSg=
github.com/charlievieth/fastwalk v1.0.3/go.mod h1:JSfglY/gmL/rqsUS1NCsJTocB5n6sSl9ApAqif4CUbs=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.5.4 h1:TGU4tSjD3sCL788vFNeJnTdzpNKIw1H5dgLnJRQVv/k= github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU=
github.com/gdamore/tcell/v2 v2.5.4/go.mod h1:dZgRy5v4iMobMEcWNYBtREnDZAT9DYmfqIkrgEMxLyw= github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg=
github.com/junegunn/uniseg v0.0.0-20240120174029-b504da4f6ed2 h1:oEwPBh29BPu1MaTsz2dM9bDrkOgKBoYFC0u6uY2izWo=
github.com/junegunn/uniseg v0.0.0-20240120174029-b504da4f6ed2/go.mod h1:ywqF55XaSE3/uS2tkJqVFKiE0oIYAXRvU2N7DU4y3XQ=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/saracen/walker v0.1.3 h1:YtcKKmpRPy6XJTHJ75J2QYXXZYWnZNQxPCVqZSHVV/g= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/saracen/walker v0.1.3/go.mod h1:FU+7qU8DeQQgSZDmmThMJi93kPkLFgy0oVAcLxurjIk=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 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-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/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.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-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-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.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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/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.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-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-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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= 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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.5.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-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.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.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= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

36
install
View File

@@ -2,7 +2,7 @@
set -u set -u
version=0.46.0 version=0.49.0
auto_completion= auto_completion=
key_bindings= key_bindings=
update_config=2 update_config=2
@@ -262,6 +262,12 @@ if [[ ! "\$PATH" == *$fzf_base_esc/bin* ]]; then
PATH="\${PATH:+\${PATH}:}$fzf_base/bin" PATH="\${PATH:+\${PATH}:}$fzf_base/bin"
fi fi
EOF
if [[ $auto_completion -eq 1 ]] && [[ $key_bindings -eq 1 ]]; then
echo "eval \"\$(fzf --$shell)\"" >> "$src"
else
cat >> "$src" << EOF
# Auto-completion # Auto-completion
# --------------- # ---------------
$fzf_completion $fzf_completion
@@ -270,6 +276,7 @@ $fzf_completion
# ------------ # ------------
$fzf_key_bindings $fzf_key_bindings
EOF EOF
fi
echo "OK" echo "OK"
done done
@@ -281,18 +288,6 @@ if [[ "$shells" =~ fish ]]; then
or set --universal fish_user_paths \$fish_user_paths "$fzf_base"/bin or set --universal fish_user_paths \$fish_user_paths "$fzf_base"/bin
EOF EOF
[ $? -eq 0 ] && echo "OK" || echo "Failed" [ $? -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 fi
append_line() { append_line() {
@@ -355,12 +350,23 @@ done
if [ $key_bindings -eq 1 ] && [[ "$shells" =~ fish ]]; then if [ $key_bindings -eq 1 ] && [[ "$shells" =~ fish ]]; then
bind_file="${fish_dir}/functions/fish_user_key_bindings.fish" bind_file="${fish_dir}/functions/fish_user_key_bindings.fish"
if [ ! -e "$bind_file" ]; then if [ ! -e "$bind_file" ]; then
mkdir -p "${fish_dir}/functions"
create_file "$bind_file" \ create_file "$bind_file" \
'function fish_user_key_bindings' \ 'function fish_user_key_bindings' \
' fzf_key_bindings' \ ' fzf --fish | source' \
'end' 'end'
else 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
fi fi

View File

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

45
main.go
View File

@@ -1,14 +1,55 @@
package main package main
import ( import (
_ "embed"
"fmt"
"strings"
fzf "github.com/junegunn/fzf/src" fzf "github.com/junegunn/fzf/src"
"github.com/junegunn/fzf/src/protector" "github.com/junegunn/fzf/src/protector"
) )
var version string = "0.46" var version string = "0.49"
var revision string = "devel" 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() { func main() {
protector.Protect() 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

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf-tmux 1 "Jan 2024" "fzf 0.46.0" "fzf-tmux - open fzf in tmux split pane" .TH fzf-tmux 1 "Apr 2024" "fzf 0.49.0" "fzf-tmux - open fzf in tmux split pane"
.SH NAME .SH NAME
fzf-tmux - open fzf in tmux split pane fzf-tmux - open fzf in tmux split pane

View File

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf 1 "Jan 2024" "fzf 0.46.0" "fzf - a command-line fuzzy finder" .TH fzf 1 "Apr 2024" "fzf 0.49.0" "fzf - a command-line fuzzy finder"
.SH NAME .SH NAME
fzf - a command-line fuzzy finder fzf - a command-line fuzzy finder
@@ -33,6 +33,10 @@ fzf [options]
fzf is a general-purpose command-line fuzzy finder. fzf is a general-purpose command-line fuzzy finder.
.SH OPTIONS .SH OPTIONS
.SS Note
.TP
Most long options have the opposite version with \fB--no-\fR prefix.
.SS Search mode .SS Search mode
.TP .TP
.B "-x, --extended" .B "-x, --extended"
@@ -368,20 +372,21 @@ e.g.
.TP .TP
.BI "--info=" "STYLE" .BI "--info=" "STYLE"
Determines the display style of finder info (match counters). Determines the display style of the finder info. (e.g. match counter, loading indicator, etc.)
.BR default " On the left end of the horizontal separator"
.br .br
.BR default " Display on the next line to the prompt" .BR right " On the right end of the horizontal separator"
.br .br
.BR right " Display on the right end of the next line to the prompt" .BR hidden " Do not display finder info"
.br .br
.BR inline " Display on the same line with the default separator ' < '" .BR inline " After the prompt with the default prefix ' < '"
.br .br
.BR inline:SEPARATOR " Display on the same line with a non-default separator" .BR inline:PREFIX " After the prompt with a non-default prefix"
.br .br
.BR inline-right " Display on the right end of the same line .BR inline-right " On the right end of the prompt line"
.br .br
.BR hidden " Do not display finder info" .BR inline-right:PREFIX " On the right end of the prompt line with a custom prefix"
.br .br
.TP .TP
@@ -620,7 +625,7 @@ The following example uses https://github.com/junegunn/fzf/blob/master/bin/fzf-p
script to render an image using either of the protocols inside the preview window. script to render an image using either of the protocols inside the preview window.
e.g. e.g.
\fBfzf --preview='fzf-preview.sh {}' \fBfzf --preview='fzf-preview.sh {}'\fR
.RE .RE
@@ -854,8 +859,49 @@ e.g.
.B "--version" .B "--version"
Display version information and exit Display version information and exit
.SS Directory traversal
.TP .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 .SH ENVIRONMENT VARIABLES
.TP .TP
@@ -865,7 +911,14 @@ 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. this case make sure that the command is POSIX-compliant.
.TP .TP
.B FZF_DEFAULT_OPTS .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 .TP
.B FZF_API_KEY .B FZF_API_KEY
Can be used to require an API key when using \fB--listen\fR option. If not set, Can be used to require an API key when using \fB--listen\fR option. If not set,
@@ -923,6 +976,10 @@ fzf exports the following environment variables to its child processes.
.br .br
.BR FZF_PROMPT " Prompt string" .BR FZF_PROMPT " Prompt string"
.br .br
.BR FZF_PREVIEW_LABEL " Preview label string"
.br
.BR FZF_BORDER_LABEL " Border label string"
.br
.BR FZF_ACTION " The name of the last action performed" .BR FZF_ACTION " The name of the last action performed"
.br .br
.BR FZF_PORT " Port number when --listen option is used" .BR FZF_PORT " Port number when --listen option is used"
@@ -1274,9 +1331,10 @@ A key or an event can be bound to one or more of the following actions.
\fBtoggle-preview-wrap\fR \fBtoggle-preview-wrap\fR
\fBtoggle-search\fR (toggle search functionality) \fBtoggle-search\fR (toggle search functionality)
\fBtoggle-sort\fR \fBtoggle-sort\fR
\fBtoggle-track\fR \fBtoggle-track\fR (toggle global tracking option (\fB--track\fR))
\fBtoggle-track-current\fR (toggle tracking of the current item)
\fBtoggle+up\fR \fIbtab (shift-tab)\fR \fBtoggle+up\fR \fIbtab (shift-tab)\fR
\fBtrack\fR (track the current item; automatically disabled if focus changes) \fBtrack-current\fR (track the current item; automatically disabled if focus changes)
\fBtransform(...)\fR (transform states using the output of an external command) \fBtransform(...)\fR (transform states using the output of an external command)
\fBtransform-border-label(...)\fR (transform border label using an external command) \fBtransform-border-label(...)\fR (transform border label using an external command)
\fBtransform-header(...)\fR (transform header using an external command) \fBtransform-header(...)\fR (transform header using an external command)
@@ -1286,6 +1344,7 @@ A key or an event can be bound to one or more of the following actions.
\fBunbind(...)\fR (unbind bindings) \fBunbind(...)\fR (unbind bindings)
\fBunix-line-discard\fR \fIctrl-u\fR \fBunix-line-discard\fR \fIctrl-u\fR
\fBunix-word-rubout\fR \fIctrl-w\fR \fBunix-word-rubout\fR \fIctrl-w\fR
\fBuntrack-current\fR (stop tracking the current item; no-op if global tracking is enabled)
\fBup\fR \fIctrl-k ctrl-p up\fR \fBup\fR \fIctrl-k ctrl-p up\fR
\fByank\fR \fIctrl-y\fR \fByank\fR \fIctrl-y\fR

View File

@@ -13,22 +13,19 @@
# To use custom commands instead of find, override _fzf_compgen_{path,dir} # To use custom commands instead of find, override _fzf_compgen_{path,dir}
if ! declare -F _fzf_compgen_path > /dev/null; then #
_fzf_compgen_path() { # _fzf_compgen_path() {
echo "$1" # echo "$1"
command find -L "$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 \) \ # -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@^\./@@' # -a -not -path "$1" -print 2> /dev/null | command sed 's@^\./@@'
} # }
fi #
# _fzf_compgen_dir() {
if ! declare -F _fzf_compgen_dir > /dev/null; then # command find -L "$1" \
_fzf_compgen_dir() { # -name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
command find -L "$1" \ # -a -not -path "$1" -print 2> /dev/null | command sed 's@^\./@@'
-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@^\./@@'
}
fi
########################################################### ###########################################################
@@ -63,6 +60,31 @@ __fzf_orig_completion() {
done 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() { _fzf_opts_completion() {
local cur prev opts local cur prev opts
COMPREPLY=() COMPREPLY=()
@@ -261,28 +283,32 @@ _fzf_opts_completion() {
} }
_fzf_handle_dynamic_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" cmd="$1"
shift shift
orig_cmd="$1" orig_cmd="$1"
orig_var="_fzf_orig_completion_$cmd" if __fzf_orig_completion_get_orig_func "$cmd"; then
orig="${!orig_var-}" "$REPLY" "$@"
orig="${orig##*#}"
if [[ -n "$orig" ]] && type "$orig" > /dev/null 2>&1; then
$orig "$@"
elif [[ -n "${_fzf_completion_loader-}" ]]; then elif [[ -n "${_fzf_completion_loader-}" ]]; then
orig_complete=$(complete -p "$orig_cmd" 2> /dev/null) orig_complete=$(complete -p "$orig_cmd" 2> /dev/null)
_completion_loader "$@" $_fzf_completion_loader "$@"
ret=$? ret=$?
# _completion_loader may not have updated completion for the command # _completion_loader may not have updated completion for the command
if [[ "$(complete -p "$orig_cmd" 2> /dev/null)" != "$orig_complete" ]]; then if [[ "$(complete -p "$orig_cmd" 2> /dev/null)" != "$orig_complete" ]]; then
__fzf_orig_completion < <(complete -p "$orig_cmd" 2> /dev/null) __fzf_orig_completion < <(complete -p "$orig_cmd" 2> /dev/null)
# 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 if [[ "${__fzf_nospace_commands-}" = *" $orig_cmd "* ]]; then
eval "${orig_complete/ -F / -o nospace -F }" eval "${orig_complete/ -F / -o nospace -F }"
else else
eval "$orig_complete" eval "$orig_complete"
fi fi
fi fi
[[ $ret -eq 0 ]] && return 124
return $ret return $ret
fi fi
} }
@@ -293,7 +319,6 @@ __fzf_generic_path_completion() {
if [[ $cmd == \\* ]]; then if [[ $cmd == \\* ]]; then
cmd="${cmd:1}" cmd="${cmd:1}"
fi fi
cmd="${cmd//[^A-Za-z0-9_=]/_}"
COMPREPLY=() COMPREPLY=()
trigger=${FZF_COMPLETION_TRIGGER-'**'} trigger=${FZF_COMPLETION_TRIGGER-'**'}
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
@@ -309,9 +334,18 @@ __fzf_generic_path_completion() {
leftover=${leftover/#\/} leftover=${leftover/#\/}
[[ -z "$dir" ]] && dir='.' [[ -z "$dir" ]] && dir='.'
[[ "$dir" != "/" ]] && dir="${dir/%\//}" [[ "$dir" != "/" ]] && dir="${dir/%\//}"
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do matches=$(
printf "%q " "${item%$3}$3" unset FZF_DEFAULT_COMMAND
done) 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% } 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 if [[ -n "$matches" ]]; then
@@ -359,8 +393,8 @@ _fzf_complete() {
post="$(caller 0 | command awk '{print $2}')_post" post="$(caller 0 | command awk '{print $2}')_post"
type -t "$post" > /dev/null 2>&1 || post='command cat' type -t "$post" > /dev/null 2>&1 || post='command cat'
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
trigger=${FZF_COMPLETION_TRIGGER-'**'} trigger=${FZF_COMPLETION_TRIGGER-'**'}
cmd="${COMP_WORDS[0]}"
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
cur=${cur:0:${#cur}-${#trigger}} cur=${cur:0:${#cur}-${#trigger}}
@@ -483,20 +517,25 @@ a_cmds="
# Preserve existing completion # Preserve existing completion
__fzf_orig_completion < <(complete -p $d_cmds $a_cmds ssh 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 if type _comp_load > /dev/null 2>&1; then
_fzf_completion_loader=1 # _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 fi
__fzf_defc() { __fzf_defc() {
local cmd func opts orig_var orig def local cmd func opts REPLY
cmd="$1" cmd="$1"
func="$2" func="$2"
opts="$3" opts="$3"
orig_var="_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}" if __fzf_orig_completion_instantiate "$cmd" "$func"; then
orig="${!orig_var-}" eval "$REPLY"
if [[ -n "$orig" ]]; then
printf -v def "$orig" "$func"
eval "$def"
else else
complete -F "$func" $opts "$cmd" complete -F "$func" $opts "$cmd"
fi fi
@@ -509,7 +548,7 @@ done
# Directory # Directory
for cmd in $d_cmds; do for cmd in $d_cmds; do
__fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames" __fzf_defc "$cmd" _fzf_dir_completion "-o bashdefault -o nospace -o dirnames"
done done
# ssh # ssh

View File

@@ -77,22 +77,19 @@ fi
{ {
# To use custom commands instead of find, override _fzf_compgen_{path,dir} # To use custom commands instead of find, override _fzf_compgen_{path,dir}
if ! declare -f _fzf_compgen_path > /dev/null; then #
_fzf_compgen_path() { # _fzf_compgen_path() {
echo "$1" # echo "$1"
command find -L "$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 \) \ # -name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@' # -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
} # }
fi #
# _fzf_compgen_dir() {
if ! declare -f _fzf_compgen_dir > /dev/null; then # command find -L "$1" \
_fzf_compgen_dir() { # -name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
command find -L "$1" \ # -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
-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
########################################################### ###########################################################
@@ -148,10 +145,19 @@ __fzf_generic_path_completion() {
leftover=${leftover/#\/} leftover=${leftover/#\/}
[ -z "$dir" ] && dir='.' [ -z "$dir" ] && dir='.'
[ "$dir" != "/" ] && dir="${dir/%\//}" [ "$dir" != "/" ] && dir="${dir/%\//}"
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-}" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do matches=$(
item="${item%$suffix}$suffix" unset FZF_DEFAULT_COMMAND
echo -n "${(q)item} " export FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-}"
done) 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% } matches=${matches% }
if [ -n "$matches" ]; then if [ -n "$matches" ]; then
LBUFFER="$lbuf$matches$tail" LBUFFER="$lbuf$matches$tail"

View File

@@ -17,14 +17,9 @@
# Key bindings # Key bindings
# ------------ # ------------
__fzf_select__() { __fzf_select__() {
local cmd opts local 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 \ 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"
-o -type f -print \ FZF_DEFAULT_COMMAND=${FZF_CTRL_T_COMMAND:-} FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) "$@" |
-o -type d -print \
-o -type l -print 2> /dev/null | command cut -b3-"}"
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse --scheme=path ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-} -m"
eval "$cmd" |
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) "$@" |
while read -r item; do while read -r item; do
printf '%q ' "$item" # escape special chars printf '%q ' "$item" # escape special chars
done done
@@ -42,11 +37,11 @@ fzf-file-widget() {
} }
__fzf_cd__() { __fzf_cd__() {
local cmd opts dir local 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 \ 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"
-o -type d -print 2> /dev/null | command cut -b3-"}" dir=$(
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse --scheme=path ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-} +m" FZF_DEFAULT_COMMAND=${FZF_ALT_C_COMMAND:-} FZF_DEFAULT_OPTS="$opts" $(__fzfcmd)
dir=$(set +o pipefail; eval "$cmd" | FZF_DEFAULT_OPTS="$opts" $(__fzfcmd)) && printf 'builtin cd -- %q' "$dir" ) && printf 'builtin cd -- %q' "$(builtin unset CDPATH && builtin cd -- "$dir" && builtin pwd)"
} }
if command -v perl > /dev/null; then if command -v perl > /dev/null; then
@@ -107,9 +102,11 @@ bind -m emacs-standard '"\C-z": vi-editing-mode'
if (( BASH_VERSINFO[0] < 4 )); then if (( BASH_VERSINFO[0] < 4 )); then
# CTRL-T - Paste the selected file path into the command line # 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"' if [[ "${FZF_CTRL_T_COMMAND-x}" != "" ]]; then
bind -m vi-command '"\C-t": "\C-z\C-t\C-z"' 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-insert '"\C-t": "\C-z\C-t\C-z"' 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 # 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"'
@@ -117,9 +114,11 @@ if (( BASH_VERSINFO[0] < 4 )); then
bind -m vi-insert '"\C-r": "\C-z\C-r\C-z"' bind -m vi-insert '"\C-r": "\C-z\C-r\C-z"'
else else
# CTRL-T - Paste the selected file path into the command line # CTRL-T - Paste the selected file path into the command line
bind -m emacs-standard -x '"\C-t": fzf-file-widget' if [[ "${FZF_CTRL_T_COMMAND-x}" != "" ]]; then
bind -m vi-command -x '"\C-t": fzf-file-widget' bind -m emacs-standard -x '"\C-t": fzf-file-widget'
bind -m vi-insert -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 # CTRL-R - Paste the selected command from history into the command line
bind -m emacs-standard -x '"\C-r": __fzf_history__' bind -m emacs-standard -x '"\C-r": __fzf_history__'
@@ -128,6 +127,8 @@ else
fi fi
# ALT-C - cd into the selected directory # 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"' if [[ "${FZF_ALT_C_COMMAND-x}" != "" ]]; then
bind -m vi-command '"\ec": "\C-z\ec\C-z"' 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-insert '"\ec": "\C-z\ec\C-z"' bind -m vi-command '"\ec": "\C-z\ec\C-z"'
bind -m vi-insert '"\ec": "\C-z\ec\C-z"'
fi

View File

@@ -21,22 +21,15 @@ function fzf_key_bindings
# Store current token in $dir as root for the 'find' command # Store current token in $dir as root for the 'find' command
function fzf-file-widget -d "List files and folders" function fzf-file-widget -d "List files and folders"
set -l commandline (__fzf_parse_commandline) set -l commandline (__fzf_parse_commandline)
set -l dir $commandline[1] set -lx dir $commandline[1]
set -l fzf_query $commandline[2] set -l fzf_query $commandline[2]
set -l prefix $commandline[3] 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% test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin begin
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --walker=file,dir,follow,hidden --walker-root='$dir' --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end 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 end
if [ -z "$result" ] if [ -z "$result" ]
commandline -f repaint commandline -f repaint
@@ -77,17 +70,15 @@ function fzf_key_bindings
function fzf-cd-widget -d "Change directory" function fzf-cd-widget -d "Change directory"
set -l commandline (__fzf_parse_commandline) set -l commandline (__fzf_parse_commandline)
set -l dir $commandline[1] set -lx dir $commandline[1]
set -l fzf_query $commandline[2] set -l fzf_query $commandline[2]
set -l prefix $commandline[3] 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% test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin begin
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --walker=dir,follow,hidden --walker-root='$dir' --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND"
eval (__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
if [ -n "$result" ] if [ -n "$result" ]
cd -- $result cd -- $result
@@ -113,14 +104,22 @@ function fzf_key_bindings
end end
end end
bind \ct fzf-file-widget
bind \cr fzf-history-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 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 \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 end
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix' 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

@@ -41,13 +41,9 @@ fi
# CTRL-T - Paste the selected file path(s) into the command line # CTRL-T - Paste the selected file path(s) into the command line
__fsel() { __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 setopt localoptions pipefail no_aliases 2> /dev/null
local item local item
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-}" $(__fzfcmd) -m "$@" | while read item; do 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} " echo -n "${(q)item} "
done done
local ret=$? local ret=$?
@@ -66,33 +62,35 @@ fzf-file-widget() {
zle reset-prompt zle reset-prompt
return $ret return $ret
} }
zle -N fzf-file-widget if [[ "${FZF_CTRL_T_COMMAND-x}" != "" ]]; then
bindkey -M emacs '^T' fzf-file-widget zle -N fzf-file-widget
bindkey -M vicmd '^T' fzf-file-widget bindkey -M emacs '^T' fzf-file-widget
bindkey -M viins '^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 # ALT-C - cd into the selected directory
fzf-cd-widget() { 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 setopt localoptions pipefail no_aliases 2> /dev/null
local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-}" $(__fzfcmd) +m)" 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 if [[ -z "$dir" ]]; then
zle redisplay zle redisplay
return 0 return 0
fi fi
zle push-line # Clear buffer. Auto-restored on next prompt. zle push-line # Clear buffer. Auto-restored on next prompt.
BUFFER="builtin cd -- ${(q)dir}" BUFFER="builtin cd -- ${(q)dir:a}"
zle accept-line zle accept-line
local ret=$? local ret=$?
unset dir # ensure this doesn't end up appearing in prompt expansion unset dir # ensure this doesn't end up appearing in prompt expansion
zle reset-prompt zle reset-prompt
return $ret return $ret
} }
zle -N fzf-cd-widget if [[ "${FZF_ALT_C_COMMAND-x}" != "" ]]; then
bindkey -M emacs '\ec' fzf-cd-widget zle -N fzf-cd-widget
bindkey -M vicmd '\ec' fzf-cd-widget bindkey -M emacs '\ec' fzf-cd-widget
bindkey -M viins '\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 # CTRL-R - Paste the selected command from history into the command line
fzf-history-widget() { fzf-history-widget() {

View File

@@ -54,72 +54,74 @@ func _() {
_ = x[actToggleIn-43] _ = x[actToggleIn-43]
_ = x[actToggleOut-44] _ = x[actToggleOut-44]
_ = x[actToggleTrack-45] _ = x[actToggleTrack-45]
_ = x[actToggleHeader-46] _ = x[actToggleTrackCurrent-46]
_ = x[actTrack-47] _ = x[actToggleHeader-47]
_ = x[actDown-48] _ = x[actTrackCurrent-48]
_ = x[actUp-49] _ = x[actUntrackCurrent-49]
_ = x[actPageUp-50] _ = x[actDown-50]
_ = x[actPageDown-51] _ = x[actUp-51]
_ = x[actPosition-52] _ = x[actPageUp-52]
_ = x[actHalfPageUp-53] _ = x[actPageDown-53]
_ = x[actHalfPageDown-54] _ = x[actPosition-54]
_ = x[actOffsetUp-55] _ = x[actHalfPageUp-55]
_ = x[actOffsetDown-56] _ = x[actHalfPageDown-56]
_ = x[actJump-57] _ = x[actOffsetUp-57]
_ = x[actJumpAccept-58] _ = x[actOffsetDown-58]
_ = x[actPrintQuery-59] _ = x[actJump-59]
_ = x[actRefreshPreview-60] _ = x[actJumpAccept-60]
_ = x[actReplaceQuery-61] _ = x[actPrintQuery-61]
_ = x[actToggleSort-62] _ = x[actRefreshPreview-62]
_ = x[actShowPreview-63] _ = x[actReplaceQuery-63]
_ = x[actHidePreview-64] _ = x[actToggleSort-64]
_ = x[actTogglePreview-65] _ = x[actShowPreview-65]
_ = x[actTogglePreviewWrap-66] _ = x[actHidePreview-66]
_ = x[actTransform-67] _ = x[actTogglePreview-67]
_ = x[actTransformBorderLabel-68] _ = x[actTogglePreviewWrap-68]
_ = x[actTransformHeader-69] _ = x[actTransform-69]
_ = x[actTransformPreviewLabel-70] _ = x[actTransformBorderLabel-70]
_ = x[actTransformPrompt-71] _ = x[actTransformHeader-71]
_ = x[actTransformQuery-72] _ = x[actTransformPreviewLabel-72]
_ = x[actPreview-73] _ = x[actTransformPrompt-73]
_ = x[actChangePreview-74] _ = x[actTransformQuery-74]
_ = x[actChangePreviewWindow-75] _ = x[actPreview-75]
_ = x[actPreviewTop-76] _ = x[actChangePreview-76]
_ = x[actPreviewBottom-77] _ = x[actChangePreviewWindow-77]
_ = x[actPreviewUp-78] _ = x[actPreviewTop-78]
_ = x[actPreviewDown-79] _ = x[actPreviewBottom-79]
_ = x[actPreviewPageUp-80] _ = x[actPreviewUp-80]
_ = x[actPreviewPageDown-81] _ = x[actPreviewDown-81]
_ = x[actPreviewHalfPageUp-82] _ = x[actPreviewPageUp-82]
_ = x[actPreviewHalfPageDown-83] _ = x[actPreviewPageDown-83]
_ = x[actPrevHistory-84] _ = x[actPreviewHalfPageUp-84]
_ = x[actPrevSelected-85] _ = x[actPreviewHalfPageDown-85]
_ = x[actPut-86] _ = x[actPrevHistory-86]
_ = x[actNextHistory-87] _ = x[actPrevSelected-87]
_ = x[actNextSelected-88] _ = x[actPut-88]
_ = x[actExecute-89] _ = x[actNextHistory-89]
_ = x[actExecuteSilent-90] _ = x[actNextSelected-90]
_ = x[actExecuteMulti-91] _ = x[actExecute-91]
_ = x[actSigStop-92] _ = x[actExecuteSilent-92]
_ = x[actFirst-93] _ = x[actExecuteMulti-93]
_ = x[actLast-94] _ = x[actSigStop-94]
_ = x[actReload-95] _ = x[actFirst-95]
_ = x[actReloadSync-96] _ = x[actLast-96]
_ = x[actDisableSearch-97] _ = x[actReload-97]
_ = x[actEnableSearch-98] _ = x[actReloadSync-98]
_ = x[actSelect-99] _ = x[actDisableSearch-99]
_ = x[actDeselect-100] _ = x[actEnableSearch-100]
_ = x[actUnbind-101] _ = x[actSelect-101]
_ = x[actRebind-102] _ = x[actDeselect-102]
_ = x[actBecome-103] _ = x[actUnbind-103]
_ = x[actResponse-104] _ = x[actRebind-104]
_ = x[actShowHeader-105] _ = x[actBecome-105]
_ = x[actHideHeader-106] _ = x[actResponse-106]
_ = x[actShowHeader-107]
_ = x[actHideHeader-108]
} }
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleHeaderactTrackactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactResponseactShowHeaderactHideHeader" const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactResponseactShowHeaderactHideHeader"
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} 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, 640, 655, 670, 687, 694, 699, 708, 719, 730, 743, 758, 769, 782, 789, 802, 815, 832, 847, 860, 874, 888, 904, 924, 936, 959, 977, 1001, 1019, 1036, 1046, 1062, 1084, 1097, 1113, 1125, 1139, 1155, 1173, 1193, 1215, 1229, 1244, 1250, 1264, 1279, 1289, 1305, 1320, 1330, 1338, 1345, 1354, 1367, 1383, 1398, 1407, 1418, 1427, 1436, 1445, 1456, 1469, 1482}
func (i actionType) String() string { func (i actionType) String() string {
if i < 0 || i >= actionType(len(_actionType_index)-1) { if i < 0 || i >= actionType(len(_actionType_index)-1) {

View File

@@ -255,24 +255,29 @@ func charClassOf(char rune) charClass {
func bonusFor(prevClass charClass, class charClass) int16 { func bonusFor(prevClass charClass, class charClass) int16 {
if class > charNonWord { if class > charNonWord {
if prevClass == charWhite { switch prevClass {
case charWhite:
// Word boundary after whitespace // Word boundary after whitespace
return bonusBoundaryWhite return bonusBoundaryWhite
} else if prevClass == charDelimiter { case charDelimiter:
// Word boundary after a delimiter character // Word boundary after a delimiter character
return bonusBoundaryDelimiter return bonusBoundaryDelimiter
} else if prevClass == charNonWord { case charNonWord:
// Word boundary // Word boundary
return bonusBoundary return bonusBoundary
} }
} }
if prevClass == charLower && class == charUpper || if prevClass == charLower && class == charUpper ||
prevClass != charNumber && class == charNumber { prevClass != charNumber && class == charNumber {
// camelCase letter123 // camelCase letter123
return bonusCamel123 return bonusCamel123
} else if class == charNonWord { }
switch class {
case charNonWord, charDelimiter:
return bonusNonWord return bonusNonWord
} else if class == charWhite { case charWhite:
return bonusBoundaryWhite return bonusBoundaryWhite
} }
return 0 return 0

View File

@@ -312,7 +312,7 @@ func parseAnsiCode(s string, delimiter byte) (int, byte, string) {
// Inlined version of strconv.Atoi() that only handles positive // Inlined version of strconv.Atoi() that only handles positive
// integers and does not allocate on error. // integers and does not allocate on error.
code := 0 code := 0
for _, ch := range []byte(s) { for _, ch := range sbytes(s) {
ch -= '0' ch -= '0'
if ch > 9 { if ch > 9 {
return -1, delimiter, remaining return -1, delimiter, remaining

View File

@@ -2,7 +2,6 @@ package fzf
import ( import (
"math" "math"
"os"
"time" "time"
"github.com/junegunn/fzf/src/util" "github.com/junegunn/fzf/src/util"
@@ -15,6 +14,7 @@ const (
// Reader // Reader
readerBufferSize = 64 * 1024 readerBufferSize = 64 * 1024
readerSlabSize = 128 * 1024
readerPollIntervalMin = 10 * time.Millisecond readerPollIntervalMin = 10 * time.Millisecond
readerPollIntervalStep = 5 * time.Millisecond readerPollIntervalStep = 5 * time.Millisecond
readerPollIntervalMax = 50 * time.Millisecond readerPollIntervalMax = 50 * time.Millisecond
@@ -54,16 +54,6 @@ const (
defaultJumpLabels string = "asdfghjklqwertyuiopzxcvbnm1234567890ASDFGHJKLQWERTYUIOPZXCVBNM`~;:,<.>/?'\"!@#$%^&*()[{]}-_=+" 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 // fzf events
const ( const (
EvtReadNew util.EventType = iota EvtReadNew util.EventType = iota

View File

@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"os" "os"
"time" "time"
"unsafe"
"github.com/junegunn/fzf/src/util" "github.com/junegunn/fzf/src/util"
) )
@@ -18,6 +19,14 @@ Matcher -> EvtSearchFin -> Terminal (update list)
Matcher -> EvtHeader -> Terminal (update header) Matcher -> EvtHeader -> Terminal (update header)
*/ */
func ustring(data []byte) string {
return unsafe.String(unsafe.SliceData(data), len(data))
}
func sbytes(data string) []byte {
return unsafe.Slice(unsafe.StringData(data), len(data))
}
// Run starts fzf // Run starts fzf
func Run(opts *Options, version string, revision string) { func Run(opts *Options, version string, revision string) {
sort := opts.Sort > 0 sort := opts.Sort > 0
@@ -45,16 +54,16 @@ func Run(opts *Options, version string, revision string) {
if opts.Theme.Colored { if opts.Theme.Colored {
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) { ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
prevLineAnsiState = lineAnsiState prevLineAnsiState = lineAnsiState
trimmed, offsets, newState := extractColor(string(data), lineAnsiState, nil) trimmed, offsets, newState := extractColor(ustring(data), lineAnsiState, nil)
lineAnsiState = newState lineAnsiState = newState
return util.ToChars([]byte(trimmed)), offsets return util.ToChars(sbytes(trimmed)), offsets
} }
} else { } else {
// When color is disabled but ansi option is given, // When color is disabled but ansi option is given,
// we simply strip out ANSI codes from the input // we simply strip out ANSI codes from the input
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) { ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
trimmed, _, _ := extractColor(string(data), nil, nil) trimmed, _, _ := extractColor(ustring(data), nil, nil)
return util.ToChars([]byte(trimmed)), nil return util.ToChars(sbytes(trimmed)), nil
} }
} }
} }
@@ -66,7 +75,7 @@ func Run(opts *Options, version string, revision string) {
if len(opts.WithNth) == 0 { if len(opts.WithNth) == 0 {
chunkList = NewChunkList(func(item *Item, data []byte) bool { chunkList = NewChunkList(func(item *Item, data []byte) bool {
if len(header) < opts.HeaderLines { if len(header) < opts.HeaderLines {
header = append(header, string(data)) header = append(header, ustring(data))
eventBox.Set(EvtHeader, header) eventBox.Set(EvtHeader, header)
return false return false
} }
@@ -77,7 +86,7 @@ func Run(opts *Options, version string, revision string) {
}) })
} else { } else {
chunkList = NewChunkList(func(item *Item, data []byte) bool { chunkList = NewChunkList(func(item *Item, data []byte) bool {
tokens := Tokenize(string(data), opts.Delimiter) tokens := Tokenize(ustring(data), opts.Delimiter)
if opts.Ansi && opts.Theme.Colored && len(tokens) > 1 { if opts.Ansi && opts.Theme.Colored && len(tokens) > 1 {
var ansiState *ansiState var ansiState *ansiState
if prevLineAnsiState != nil { if prevLineAnsiState != nil {
@@ -101,7 +110,7 @@ func Run(opts *Options, version string, revision string) {
eventBox.Set(EvtHeader, header) eventBox.Set(EvtHeader, header)
return false return false
} }
item.text, item.colors = ansiProcessor([]byte(transformed)) item.text, item.colors = ansiProcessor(sbytes(transformed))
item.text.TrimTrailingWhitespaces() item.text.TrimTrailingWhitespaces()
item.text.Index = itemIndex item.text.Index = itemIndex
item.origText = &data item.origText = &data
@@ -117,7 +126,7 @@ func Run(opts *Options, version string, revision string) {
reader = NewReader(func(data []byte) bool { reader = NewReader(func(data []byte) bool {
return chunkList.Push(data) return chunkList.Push(data)
}, eventBox, opts.ReadZero, opts.Filter == nil) }, eventBox, opts.ReadZero, opts.Filter == nil)
go reader.ReadSource() go reader.ReadSource(opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
} }
// Matcher // Matcher
@@ -165,7 +174,7 @@ func Run(opts *Options, version string, revision string) {
} }
return false return false
}, eventBox, opts.ReadZero, false) }, eventBox, opts.ReadZero, false)
reader.ReadSource() reader.ReadSource(opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
} else { } else {
eventBox.Unwatch(EvtReadNew) eventBox.Unwatch(EvtReadNew)
eventBox.WaitFor(EvtReadFin) eventBox.WaitFor(EvtReadFin)
@@ -213,6 +222,7 @@ func Run(opts *Options, version string, revision string) {
reading := true reading := true
ticks := 0 ticks := 0
var nextCommand *string var nextCommand *string
var nextEnviron []string
eventBox.Watch(EvtReadNew) eventBox.Watch(EvtReadNew)
total := 0 total := 0
query := []rune{} query := []rune{}
@@ -232,23 +242,20 @@ func Run(opts *Options, version string, revision string) {
useSnapshot := false useSnapshot := false
var snapshot []*Chunk var snapshot []*Chunk
var count int var count int
restart := func(command string) { restart := func(command string, environ []string) {
reading = true reading = true
chunkList.Clear() chunkList.Clear()
itemIndex = 0 itemIndex = 0
inputRevision++ inputRevision++
header = make([]string, 0, opts.HeaderLines) header = make([]string, 0, opts.HeaderLines)
go reader.restart(command) go reader.restart(command, environ)
} }
for { for {
delay := true delay := true
ticks++ ticks++
input := func() []rune { input := func() []rune {
reloaded := snapshotRevision != inputRevision
paused, input := terminal.Input() paused, input := terminal.Input()
if reloaded && paused { if !paused {
query = []rune{}
} else if !paused {
query = input query = input
} }
return query return query
@@ -266,8 +273,9 @@ func Run(opts *Options, version string, revision string) {
os.Exit(value.(int)) os.Exit(value.(int))
case EvtReadNew, EvtReadFin: case EvtReadNew, EvtReadFin:
if evt == EvtReadFin && nextCommand != nil { if evt == EvtReadFin && nextCommand != nil {
restart(*nextCommand) restart(*nextCommand, nextEnviron)
nextCommand = nil nextCommand = nil
nextEnviron = nil
break break
} else { } else {
reading = reading && evt == EvtReadNew reading = reading && evt == EvtReadNew
@@ -276,6 +284,9 @@ func Run(opts *Options, version string, revision string) {
useSnapshot = false useSnapshot = false
} }
if !useSnapshot { if !useSnapshot {
if snapshotRevision != inputRevision {
query = []rune{}
}
snapshot, count = chunkList.Snapshot() snapshot, count = chunkList.Snapshot()
snapshotRevision = inputRevision snapshotRevision = inputRevision
} }
@@ -292,11 +303,13 @@ func Run(opts *Options, version string, revision string) {
case EvtSearchNew: case EvtSearchNew:
var command *string var command *string
var environ []string
var changed bool var changed bool
switch val := value.(type) { switch val := value.(type) {
case searchRequest: case searchRequest:
sort = val.sort sort = val.sort
command = val.command command = val.command
environ = val.environ
changed = val.changed changed = val.changed
if command != nil { if command != nil {
useSnapshot = val.sync useSnapshot = val.sync
@@ -306,18 +319,22 @@ func Run(opts *Options, version string, revision string) {
if reading { if reading {
reader.terminate() reader.terminate()
nextCommand = command nextCommand = command
nextEnviron = environ
} else { } else {
restart(*command) restart(*command, environ)
} }
} }
if !changed { if !changed {
break break
} }
if !useSnapshot { if !useSnapshot {
newSnapshot, _ := chunkList.Snapshot() newSnapshot, newCount := chunkList.Snapshot()
// We want to avoid showing empty list when reload is triggered // 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 // and the query string is changed at the same time i.e. command != nil && changed
if command == nil || len(newSnapshot) > 0 { if command == nil || newCount > 0 {
if snapshotRevision != inputRevision {
query = []rune{}
}
snapshot = newSnapshot snapshot = newSnapshot
snapshotRevision = inputRevision snapshotRevision = inputRevision
} }

View File

@@ -11,9 +11,9 @@ import (
"github.com/junegunn/fzf/src/algo" "github.com/junegunn/fzf/src/algo"
"github.com/junegunn/fzf/src/tui" "github.com/junegunn/fzf/src/tui"
"github.com/junegunn/fzf/src/util" "github.com/junegunn/fzf/src/util"
"github.com/junegunn/uniseg"
"github.com/mattn/go-shellwords" "github.com/mattn/go-shellwords"
"github.com/rivo/uniseg"
) )
const usage = `usage: fzf [options] const usage = `usage: fzf [options]
@@ -75,7 +75,7 @@ const usage = `usage: fzf [options]
--margin=MARGIN Screen margin (TRBL | TB,RL | T,RL,B | T,R,B,L) --margin=MARGIN Screen margin (TRBL | TB,RL | T,RL,B | T,R,B,L)
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L) --padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
--info=STYLE Finder info style --info=STYLE Finder info style
[default|right|hidden|inline[:SEPARATOR]|inline-right] [default|right|hidden|inline[-right][:PREFIX]]
--separator=STR String to form horizontal separator on info line --separator=STR String to form horizontal separator on info line
--no-separator Hide info line separator --no-separator Hide info line separator
--scrollbar[=C1[C2]] Scrollbar character(s) (each for main and preview window) --scrollbar[=C1[C2]] Scrollbar character(s) (each for main and preview window)
@@ -124,15 +124,26 @@ const usage = `usage: fzf [options]
(To allow remote process execution, use --listen-unsafe) (To allow remote process execution, use --listen-unsafe)
--version Display version information and exit --version Display version information and exit
Directory traversal (Only used when $FZF_DEFAULT_COMMAND is not set)
--walker=OPTS [file][,dir][,follow][,hidden] (default: file,follow,hidden)
--walker-root=DIR Root directory from which to start walker (default: .)
--walker-skip=DIRS Comma-separated list of directory names to skip
(default: .git,node_modules)
Shell integration
--bash Print script to set up Bash shell integration
--zsh Print script to set up Zsh shell integration
--fish Print script to set up Fish shell integration
Environment variables Environment variables
FZF_DEFAULT_COMMAND Default command to use when input is tty FZF_DEFAULT_COMMAND Default command to use when input is tty
FZF_DEFAULT_OPTS Default options FZF_DEFAULT_OPTS Default options (e.g. '--layout=reverse --info=inline')
(e.g. '--layout=reverse --inline-info') FZF_DEFAULT_OPTS_FILE Location of the file to read default options from
FZF_API_KEY X-API-Key header for HTTP server (--listen) FZF_API_KEY X-API-Key header for HTTP server (--listen)
` `
const defaultInfoSep = " < " const defaultInfoPrefix = " < "
// Case denotes case-sensitivity of search // Case denotes case-sensitivity of search
type Case int type Case int
@@ -206,10 +217,6 @@ const (
infoHidden infoHidden
) )
func (s infoStyle) noExtraLine() bool {
return s == infoInline || s == infoInlineRight || s == infoHidden
}
type labelOpts struct { type labelOpts struct {
label string label string
column int column int
@@ -274,8 +281,18 @@ func firstLine(s string) string {
return strings.SplitN(s, "\n", 2)[0] return strings.SplitN(s, "\n", 2)[0]
} }
type walkerOpts struct {
file bool
dir bool
hidden bool
follow bool
}
// Options stores the values of command-line options // Options stores the values of command-line options
type Options struct { type Options struct {
Bash bool
Zsh bool
Fish bool
Fuzzy bool Fuzzy bool
FuzzyAlgo algo.Algo FuzzyAlgo algo.Algo
Scheme string Scheme string
@@ -306,7 +323,7 @@ type Options struct {
ScrollOff int ScrollOff int
FileWord bool FileWord bool
InfoStyle infoStyle InfoStyle infoStyle
InfoSep string InfoPrefix string
Separator *string Separator *string
JumpLabels string JumpLabels string
Prompt string Prompt string
@@ -342,15 +359,31 @@ type Options struct {
ListenAddr *listenAddress ListenAddr *listenAddress
Unsafe bool Unsafe bool
ClearOnExit bool ClearOnExit bool
WalkerOpts walkerOpts
WalkerRoot string
WalkerSkip []string
Version bool Version bool
} }
func filterNonEmpty(input []string) []string {
output := make([]string, 0, len(input))
for _, str := range input {
if len(str) > 0 {
output = append(output, str)
}
}
return output
}
func defaultPreviewOpts(command string) previewOpts { func defaultPreviewOpts(command string) previewOpts {
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, tui.DefaultBorderShape, 0, 0, nil} return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, tui.DefaultBorderShape, 0, 0, nil}
} }
func defaultOptions() *Options { func defaultOptions() *Options {
return &Options{ return &Options{
Bash: false,
Zsh: false,
Fish: false,
Fuzzy: true, Fuzzy: true,
FuzzyAlgo: algo.FuzzyMatchV2, FuzzyAlgo: algo.FuzzyMatchV2,
Scheme: "default", Scheme: "default",
@@ -413,6 +446,9 @@ func defaultOptions() *Options {
PreviewLabel: labelOpts{}, PreviewLabel: labelOpts{},
Unsafe: false, Unsafe: false,
ClearOnExit: true, ClearOnExit: true,
WalkerOpts: walkerOpts{file: true, hidden: true, follow: true},
WalkerRoot: ".",
WalkerSkip: []string{".git", "node_modules"},
Version: false} Version: false}
} }
@@ -421,8 +457,10 @@ func help(code int) {
os.Exit(code) os.Exit(code)
} }
var errorContext = ""
func errorExit(msg string) { func errorExit(msg string) {
os.Stderr.WriteString(msg + "\n") os.Stderr.WriteString(errorContext + msg + "\n")
os.Exit(exitError) os.Exit(exitError)
} }
@@ -964,6 +1002,30 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
return theme return theme
} }
func parseWalkerOpts(str string) walkerOpts {
opts := walkerOpts{}
for _, str := range strings.Split(strings.ToLower(str), ",") {
switch str {
case "file":
opts.file = true
case "dir":
opts.dir = true
case "hidden":
opts.hidden = true
case "follow":
opts.follow = true
case "":
// Ignored
default:
errorExit("invalid walker option: " + str)
}
}
if !opts.file && !opts.dir {
errorExit("at least one of 'file' or 'dir' should be specified")
}
return opts
}
var ( var (
executeRegexp *regexp.Regexp executeRegexp *regexp.Regexp
splitRegexp *regexp.Regexp splitRegexp *regexp.Regexp
@@ -1145,14 +1207,18 @@ func parseActionList(masked string, original string, prevActions []*action, putA
appendAction(actToggleSearch) appendAction(actToggleSearch)
case "toggle-track": case "toggle-track":
appendAction(actToggleTrack) appendAction(actToggleTrack)
case "toggle-track-current":
appendAction(actToggleTrackCurrent)
case "toggle-header": case "toggle-header":
appendAction(actToggleHeader) appendAction(actToggleHeader)
case "show-header": case "show-header":
appendAction(actShowHeader) appendAction(actShowHeader)
case "hide-header": case "hide-header":
appendAction(actHideHeader) appendAction(actHideHeader)
case "track": case "track", "track-current":
appendAction(actTrack) appendAction(actTrackCurrent)
case "untrack-current":
appendAction(actUntrackCurrent)
case "select": case "select":
appendAction(actSelect) appendAction(actSelect)
case "select-all": case "select-all":
@@ -1436,17 +1502,24 @@ func parseInfoStyle(str string) (infoStyle, string) {
case "right": case "right":
return infoRight, "" return infoRight, ""
case "inline": case "inline":
return infoInline, defaultInfoSep return infoInline, defaultInfoPrefix
case "inline-right": case "inline-right":
return infoInlineRight, "" return infoInlineRight, ""
case "hidden": case "hidden":
return infoHidden, "" return infoHidden, ""
default: default:
prefix := "inline:" type infoSpec struct {
if strings.HasPrefix(str, prefix) { name string
return infoInline, strings.ReplaceAll(str[len(prefix):], "\n", " ") style infoStyle
} }
errorExit("invalid info style (expected: default|right|hidden|inline[:SEPARATOR]|inline-right)") for _, spec := range []infoSpec{
{"inline", infoInline},
{"inline-right", infoInlineRight}} {
if strings.HasPrefix(str, spec.name+":") {
return spec.style, strings.ReplaceAll(str[len(spec.name)+1:], "\n", " ")
}
}
errorExit("invalid info style (expected: default|right|hidden|inline[-right][:PREFIX])")
} }
return infoDefault, "" return infoDefault, ""
} }
@@ -1600,6 +1673,21 @@ func parseOptions(opts *Options, allArgs []string) {
for i := 0; i < len(allArgs); i++ { for i := 0; i < len(allArgs); i++ {
arg := allArgs[i] arg := allArgs[i]
switch arg { switch arg {
case "--bash":
opts.Bash = true
if opts.Zsh || opts.Fish {
errorExit("cannot specify --bash with --zsh or --fish")
}
case "--zsh":
opts.Zsh = true
if opts.Bash || opts.Fish {
errorExit("cannot specify --zsh with --bash or --fish")
}
case "--fish":
opts.Fish = true
if opts.Bash || opts.Zsh {
errorExit("cannot specify --fish with --bash or --zsh")
}
case "-h", "--help": case "-h", "--help":
help(exitOk) help(exitOk)
case "-x", "--extended": case "-x", "--extended":
@@ -1722,13 +1810,13 @@ func parseOptions(opts *Options, allArgs []string) {
case "--no-filepath-word": case "--no-filepath-word":
opts.FileWord = false opts.FileWord = false
case "--info": case "--info":
opts.InfoStyle, opts.InfoSep = parseInfoStyle( opts.InfoStyle, opts.InfoPrefix = parseInfoStyle(
nextString(allArgs, &i, "info style required")) nextString(allArgs, &i, "info style required"))
case "--no-info": case "--no-info":
opts.InfoStyle = infoHidden opts.InfoStyle = infoHidden
case "--inline-info": case "--inline-info":
opts.InfoStyle = infoInline opts.InfoStyle = infoInline
opts.InfoSep = defaultInfoSep opts.InfoPrefix = defaultInfoPrefix
case "--no-inline-info": case "--no-inline-info":
opts.InfoStyle = infoDefault opts.InfoStyle = infoDefault
case "--separator": case "--separator":
@@ -1864,7 +1952,7 @@ func parseOptions(opts *Options, allArgs []string) {
addr := defaultListenAddr addr := defaultListenAddr
if given { if given {
var err error var err error
err, addr = parseListenAddress(str) addr, err = parseListenAddress(str)
if err != nil { if err != nil {
errorExit(err.Error()) errorExit(err.Error())
} }
@@ -1878,6 +1966,12 @@ func parseOptions(opts *Options, allArgs []string) {
opts.ClearOnExit = true opts.ClearOnExit = true
case "--no-clear": case "--no-clear":
opts.ClearOnExit = false opts.ClearOnExit = false
case "--walker":
opts.WalkerOpts = parseWalkerOpts(nextString(allArgs, &i, "walker options required [file][,dir][,follow][,hidden]"))
case "--walker-root":
opts.WalkerRoot = nextString(allArgs, &i, "directory required")
case "--walker-skip":
opts.WalkerSkip = filterNonEmpty(strings.Split(nextString(allArgs, &i, "directory names to ignore required"), ","))
case "--version": case "--version":
opts.Version = true opts.Version = true
case "--": case "--":
@@ -1924,7 +2018,7 @@ func parseOptions(opts *Options, allArgs []string) {
} else if match, value := optString(arg, "--layout="); match { } else if match, value := optString(arg, "--layout="); match {
opts.Layout = parseLayout(value) opts.Layout = parseLayout(value)
} else if match, value := optString(arg, "--info="); match { } else if match, value := optString(arg, "--info="); match {
opts.InfoStyle, opts.InfoSep = parseInfoStyle(value) opts.InfoStyle, opts.InfoPrefix = parseInfoStyle(value)
} else if match, value := optString(arg, "--separator="); match { } else if match, value := optString(arg, "--separator="); match {
opts.Separator = &value opts.Separator = &value
} else if match, value := optString(arg, "--scrollbar="); match { } else if match, value := optString(arg, "--scrollbar="); match {
@@ -1962,19 +2056,25 @@ func parseOptions(opts *Options, allArgs []string) {
} else if match, value := optString(arg, "--tabstop="); match { } else if match, value := optString(arg, "--tabstop="); match {
opts.Tabstop = atoi(value) opts.Tabstop = atoi(value)
} else if match, value := optString(arg, "--listen="); match { } else if match, value := optString(arg, "--listen="); match {
err, addr := parseListenAddress(value) addr, err := parseListenAddress(value)
if err != nil { if err != nil {
errorExit(err.Error()) errorExit(err.Error())
} }
opts.ListenAddr = &addr opts.ListenAddr = &addr
opts.Unsafe = false opts.Unsafe = false
} else if match, value := optString(arg, "--listen-unsafe="); match { } else if match, value := optString(arg, "--listen-unsafe="); match {
err, addr := parseListenAddress(value) addr, err := parseListenAddress(value)
if err != nil { if err != nil {
errorExit(err.Error()) errorExit(err.Error())
} }
opts.ListenAddr = &addr opts.ListenAddr = &addr
opts.Unsafe = true opts.Unsafe = true
} else if match, value := optString(arg, "--walker="); match {
opts.WalkerOpts = parseWalkerOpts(value)
} else if match, value := optString(arg, "--walker-root="); match {
opts.WalkerRoot = value
} else if match, value := optString(arg, "--walker-skip="); match {
opts.WalkerSkip = filterNonEmpty(strings.Split(value, ","))
} else if match, value := optString(arg, "--hscroll-off="); match { } else if match, value := optString(arg, "--hscroll-off="); match {
opts.HscrollOff = atoi(value) opts.HscrollOff = atoi(value)
} else if match, value := optString(arg, "--scroll-off="); match { } else if match, value := optString(arg, "--scroll-off="); match {
@@ -2167,13 +2267,36 @@ func ParseOptions() *Options {
} }
} }
// Options from Env var // 1. Options from $FZF_DEFAULT_OPTS_FILE
words, _ := shellwords.Parse(os.Getenv("FZF_DEFAULT_OPTS")) if path := os.Getenv("FZF_DEFAULT_OPTS_FILE"); path != "" {
bytes, err := os.ReadFile(path)
if err != nil {
errorContext = "$FZF_DEFAULT_OPTS_FILE: "
errorExit(err.Error())
}
words, parseErr := shellwords.Parse(string(bytes))
if parseErr != nil {
errorContext = path + ": "
errorExit(parseErr.Error())
}
if len(words) > 0 {
parseOptions(opts, words)
}
}
// 2. Options from $FZF_DEFAULT_OPTS string
words, parseErr := shellwords.Parse(os.Getenv("FZF_DEFAULT_OPTS"))
errorContext = "$FZF_DEFAULT_OPTS: "
if parseErr != nil {
errorExit(parseErr.Error())
}
if len(words) > 0 { if len(words) > 0 {
parseOptions(opts, words) parseOptions(opts, words)
} }
// Options from command-line arguments // 3. Options from command-line arguments
errorContext = ""
parseOptions(opts, os.Args[1:]) parseOptions(opts, os.Args[1:])
postProcessOptions(opts) postProcessOptions(opts)

View File

@@ -209,11 +209,10 @@ func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet
// Flip exactness // Flip exactness
if fuzzy && !inv { if fuzzy && !inv {
typ = termExact typ = termExact
text = text[1:]
} else { } else {
typ = termFuzzy typ = termFuzzy
text = text[1:]
} }
text = text[1:]
} else if strings.HasPrefix(text, "^") { } else if strings.HasPrefix(text, "^") {
if typ == termSuffix { if typ == termSuffix {
typ = termEqual typ = termEqual

View File

@@ -1,19 +1,18 @@
package fzf package fzf
import ( import (
"bufio" "bytes"
"context" "context"
"io" "io"
"os" "os"
"os/exec" "os/exec"
"path"
"path/filepath" "path/filepath"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/charlievieth/fastwalk"
"github.com/junegunn/fzf/src/util" "github.com/junegunn/fzf/src/util"
"github.com/saracen/walker"
) )
// Reader reads from command or standard input // Reader reads from command or standard input
@@ -77,48 +76,33 @@ func (r *Reader) fin(success bool) {
func (r *Reader) terminate() { func (r *Reader) terminate() {
r.mutex.Lock() r.mutex.Lock()
defer func() { r.mutex.Unlock() }()
r.killed = true r.killed = true
if r.exec != nil && r.exec.Process != nil { if r.exec != nil && r.exec.Process != nil {
util.KillCommand(r.exec) util.KillCommand(r.exec)
} else if defaultCommand != "" { } else {
os.Stdin.Close() 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.event = int32(EvtReady)
r.startEventPoller() r.startEventPoller()
success := r.readFromCommand(nil, command) success := r.readFromCommand(command, environ)
r.fin(success) r.fin(success)
} }
// ReadSource reads data from the default command or from standard input // ReadSource reads data from the default command or from standard input
func (r *Reader) ReadSource() { func (r *Reader) ReadSource(root string, opts walkerOpts, ignores []string) {
r.startEventPoller() r.startEventPoller()
var success bool var success bool
if util.IsTty() { if util.IsTty() {
// The default command for *nix requires a shell that supports "pipefail"
// https://unix.stackexchange.com/a/654932/62171
shell := "bash"
currentShell := os.Getenv("SHELL")
currentShellName := path.Base(currentShell)
for _, shellName := range []string{"bash", "zsh", "ksh", "ash", "hush", "mksh", "yash"} {
if currentShellName == shellName {
shell = currentShell
break
}
}
cmd := os.Getenv("FZF_DEFAULT_COMMAND") cmd := os.Getenv("FZF_DEFAULT_COMMAND")
if len(cmd) == 0 { if len(cmd) == 0 {
if defaultCommand != "" { success = r.readFiles(root, opts, ignores)
success = r.readFromCommand(&shell, defaultCommand)
} else {
success = r.readFiles()
}
} else { } else {
success = r.readFromCommand(nil, cmd) // We can't export FZF_* environment variables to the default command
success = r.readFromCommand(cmd, nil)
} }
} else { } else {
success = r.readFromStdin() success = r.readFromStdin()
@@ -127,32 +111,84 @@ func (r *Reader) ReadSource() {
} }
func (r *Reader) feed(src io.Reader) { func (r *Reader) feed(src io.Reader) {
/*
readerSlabSize, ae := strconv.Atoi(os.Getenv("SLAB_KB"))
if ae != nil {
readerSlabSize = 128 * 1024
} else {
readerSlabSize *= 1024
}
readerBufferSize, be := strconv.Atoi(os.Getenv("BUF_KB"))
if be != nil {
readerBufferSize = 64 * 1024
} else {
readerBufferSize *= 1024
}
*/
delim := byte('\n') delim := byte('\n')
trimCR := util.IsWindows()
if r.delimNil { if r.delimNil {
delim = '\000' delim = '\000'
trimCR = false
} }
reader := bufio.NewReaderSize(src, readerBufferSize)
slab := make([]byte, readerSlabSize)
leftover := []byte{}
var err error
for { for {
// ReadBytes returns err != nil if and only if the returned data does not n := 0
// end in delim. scope := slab[:util.Min(len(slab), readerBufferSize)]
bytea, err := reader.ReadBytes(delim) for i := 0; i < 100; i++ {
byteaLen := len(bytea) n, err = src.Read(scope)
if byteaLen > 0 { if n > 0 || err != nil {
if err == nil { break
// get rid of carriage return if under Windows:
if util.IsWindows() && byteaLen >= 2 && bytea[byteaLen-2] == byte('\r') {
bytea = bytea[:byteaLen-2]
} else {
bytea = bytea[:byteaLen-1]
}
}
if r.pusher(bytea) {
atomic.StoreInt32(&r.event, int32(EvtReadNew))
} }
} }
if err != nil {
// We're not making any progress after 100 tries. Stop.
if n == 0 && err == nil {
break break
} }
buf := slab[:n]
slab = slab[n:]
for len(buf) > 0 {
if i := bytes.IndexByte(buf, delim); i >= 0 {
// Found the delimiter
slice := buf[:i+1]
buf = buf[i+1:]
if trimCR && len(slice) >= 2 && slice[len(slice)-2] == byte('\r') {
slice = slice[:len(slice)-2]
} else {
slice = slice[:len(slice)-1]
}
if len(leftover) > 0 {
slice = append(leftover, slice...)
leftover = []byte{}
}
if (err == nil || len(slice) > 0) && r.pusher(slice) {
atomic.StoreInt32(&r.event, int32(EvtReadNew))
}
} else {
// Could not find the delimiter in the buffer
leftover = append(leftover, buf...)
break
}
}
if err == io.EOF {
leftover = append(leftover, buf...)
break
}
if len(slab) == 0 {
slab = make([]byte, readerSlabSize)
}
}
if len(leftover) > 0 && r.pusher(leftover) {
atomic.StoreInt32(&r.event, int32(EvtReadNew))
} }
} }
@@ -161,16 +197,28 @@ func (r *Reader) readFromStdin() bool {
return true return true
} }
func (r *Reader) readFiles() bool { func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool {
r.killed = false 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) path = filepath.Clean(path)
if path != "." { if path != "." {
isDir := mode.Mode().IsDir() isDir := de.IsDir()
if isDir && filepath.Base(path)[0] == '.' { if isDir {
return filepath.SkipDir 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)) atomic.StoreInt32(&r.event, int32(EvtReadNew))
} }
} }
@@ -181,20 +229,16 @@ func (r *Reader) readFiles() bool {
} }
return nil return nil
} }
cb := walker.WithErrorCallback(func(pathname string, err error) error { return fastwalk.Walk(&conf, root, fn) == nil
return nil
})
return walker.Walk(".", fn, cb) == nil
} }
func (r *Reader) readFromCommand(shell *string, command string) bool { func (r *Reader) readFromCommand(command string, environ []string) bool {
r.mutex.Lock() r.mutex.Lock()
r.killed = false r.killed = false
r.command = &command r.command = &command
if shell != nil { r.exec = util.ExecCommand(command, true)
r.exec = util.ExecCommandWith(*shell, command, true) if environ != nil {
} else { r.exec.Env = environ
r.exec = util.ExecCommand(command, true)
} }
out, err := r.exec.StdoutPipe() out, err := r.exec.StdoutPipe()
if err != nil { if err != nil {

View File

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

View File

@@ -32,6 +32,7 @@ const (
httpUnauthorized = "HTTP/1.1 401 Unauthorized" + crlf httpUnauthorized = "HTTP/1.1 401 Unauthorized" + crlf
httpUnavailable = "HTTP/1.1 503 Service Unavailable" + crlf httpUnavailable = "HTTP/1.1 503 Service Unavailable" + crlf
httpReadTimeout = 10 * time.Second httpReadTimeout = 10 * time.Second
channelTimeout = 2 * time.Second
jsonContentType = "Content-Type: application/json" + crlf jsonContentType = "Content-Type: application/json" + crlf
maxContentLength = 1024 * 1024 maxContentLength = 1024 * 1024
) )
@@ -53,47 +54,47 @@ func (addr listenAddress) IsLocal() bool {
var defaultListenAddr = listenAddress{"localhost", 0} var defaultListenAddr = listenAddress{"localhost", 0}
func parseListenAddress(address string) (error, listenAddress) { func parseListenAddress(address string) (listenAddress, error) {
parts := strings.SplitN(address, ":", 3) parts := strings.SplitN(address, ":", 3)
if len(parts) == 1 { if len(parts) == 1 {
parts = []string{"localhost", parts[0]} parts = []string{"localhost", parts[0]}
} }
if len(parts) != 2 { if len(parts) != 2 {
return fmt.Errorf("invalid listen address: %s", address), defaultListenAddr return defaultListenAddr, fmt.Errorf("invalid listen address: %s", address)
} }
portStr := parts[len(parts)-1] portStr := parts[len(parts)-1]
port, err := strconv.Atoi(portStr) port, err := strconv.Atoi(portStr)
if err != nil || port < 0 || port > 65535 { if err != nil || port < 0 || port > 65535 {
return fmt.Errorf("invalid listen port: %s", portStr), defaultListenAddr return defaultListenAddr, fmt.Errorf("invalid listen port: %s", portStr)
} }
if len(parts[0]) == 0 { if len(parts[0]) == 0 {
parts[0] = "localhost" parts[0] = "localhost"
} }
return nil, listenAddress{parts[0], port} return listenAddress{parts[0], port}, nil
} }
func startHttpServer(address listenAddress, actionChannel chan []*action, responseChannel chan string) (error, int) { func startHttpServer(address listenAddress, actionChannel chan []*action, responseChannel chan string) (int, error) {
host := address.host host := address.host
port := address.port port := address.port
apiKey := os.Getenv("FZF_API_KEY") apiKey := os.Getenv("FZF_API_KEY")
if !address.IsLocal() && len(apiKey) == 0 { if !address.IsLocal() && len(apiKey) == 0 {
return fmt.Errorf("FZF_API_KEY is required to allow remote access"), port return port, fmt.Errorf("FZF_API_KEY is required to allow remote access")
} }
addrStr := fmt.Sprintf("%s:%d", host, port) addrStr := fmt.Sprintf("%s:%d", host, port)
listener, err := net.Listen("tcp", addrStr) listener, err := net.Listen("tcp", addrStr)
if err != nil { if err != nil {
return fmt.Errorf("failed to listen on %s", addrStr), port return port, fmt.Errorf("failed to listen on %s", addrStr)
} }
if port == 0 { if port == 0 {
addr := listener.Addr().String() addr := listener.Addr().String()
parts := strings.Split(addr, ":") parts := strings.Split(addr, ":")
if len(parts) < 2 { if len(parts) < 2 {
return fmt.Errorf("cannot extract port: %s", addr), port return port, fmt.Errorf("cannot extract port: %s", addr)
} }
var err error var err error
port, err = strconv.Atoi(parts[len(parts)-1]) port, err = strconv.Atoi(parts[len(parts)-1])
if err != nil { if err != nil {
return err, port return port, err
} }
} }
@@ -119,7 +120,7 @@ func startHttpServer(address listenAddress, actionChannel chan []*action, respon
listener.Close() listener.Close()
}() }()
return nil, port return port, nil
} }
// Here we are writing a simplistic HTTP server without using net/http // Here we are writing a simplistic HTTP server without using net/http
@@ -170,7 +171,7 @@ func (server *httpServer) handleHttpRequest(conn net.Conn) string {
select { select {
case response := <-server.responseChannel: case response := <-server.responseChannel:
return good(response) return good(response)
case <-time.After(2 * time.Second): case <-time.After(channelTimeout):
go func() { go func() {
// Drain the channel // Drain the channel
<-server.responseChannel <-server.responseChannel
@@ -227,7 +228,11 @@ func (server *httpServer) handleHttpRequest(conn net.Conn) string {
return bad("no action specified") return bad("no action specified")
} }
server.actionChannel <- actions select {
case server.actionChannel <- actions:
case <-time.After(channelTimeout):
return httpUnavailable + crlf
}
return httpOk + crlf return httpOk + crlf
} }
@@ -237,15 +242,13 @@ func parseGetParams(query string) getParams {
parts := strings.SplitN(pair, "=", 2) parts := strings.SplitN(pair, "=", 2)
if len(parts) == 2 { if len(parts) == 2 {
switch parts[0] { switch parts[0] {
case "limit": case "limit", "offset":
val, err := strconv.Atoi(parts[1]) if val, err := strconv.Atoi(parts[1]); err == nil {
if err == nil { if parts[0] == "limit" {
params.limit = val params.limit = val
} } else {
case "offset": params.offset = val
val, err := strconv.Atoi(parts[1]) }
if err == nil {
params.offset = val
} }
} }
} }

View File

@@ -17,7 +17,7 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/junegunn/uniseg" "github.com/rivo/uniseg"
"github.com/junegunn/fzf/src/tui" "github.com/junegunn/fzf/src/tui"
"github.com/junegunn/fzf/src/util" "github.com/junegunn/fzf/src/util"
@@ -55,6 +55,9 @@ var actionTypeRegex *regexp.Regexp
const clearCode string = "\x1b[2J" const clearCode string = "\x1b[2J"
// Number of maximum focus events to process synchronously
const maxFocusEvents = 10000
func init() { func init() {
placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{fzf:(?:query|action|prompt)}|{\+?f?nf?})`) placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{fzf:(?:query|action|prompt)}|{\+?f?nf?})`)
whiteSuffix = regexp.MustCompile(`\s*$`) whiteSuffix = regexp.MustCompile(`\s*$`)
@@ -67,7 +70,7 @@ func init() {
// * https://sw.kovidgoyal.net/kitty/graphics-protocol // * https://sw.kovidgoyal.net/kitty/graphics-protocol
// * https://en.wikipedia.org/wiki/Sixel // * https://en.wikipedia.org/wiki/Sixel
// * https://iterm2.com/documentation-images.html // * https://iterm2.com/documentation-images.html
passThroughRegex = regexp.MustCompile(`\x1bPtmux;\x1b\x1b.*?[^\x1b]\x1b\\|\x1b(_G|P[0-9;]*q).*?\x1b\\\r?|\x1b]1337;.*?\a`) passThroughRegex = regexp.MustCompile(`\x1bPtmux;\x1b\x1b.*?[^\x1b]\x1b\\|\x1b(_G|P[0-9;]*q).*?\x1b\\\r?|\x1b]1337;.*?(\a|\x1b\\)`)
} }
type jumpMode int type jumpMode int
@@ -178,7 +181,7 @@ type Status struct {
type Terminal struct { type Terminal struct {
initDelay time.Duration initDelay time.Duration
infoStyle infoStyle infoStyle infoStyle
infoSep string infoPrefix string
separator labelPrinter separator labelPrinter
separatorLen int separatorLen int
spinner []string spinner []string
@@ -293,6 +296,7 @@ type Terminal struct {
lastFocus int32 lastFocus int32
areaLines int areaLines int
areaColumns int areaColumns int
forcePreview bool
} }
type selectedItem struct { type selectedItem struct {
@@ -390,8 +394,10 @@ const (
actToggleIn actToggleIn
actToggleOut actToggleOut
actToggleTrack actToggleTrack
actToggleTrackCurrent
actToggleHeader actToggleHeader
actTrack actTrackCurrent
actUntrackCurrent
actDown actDown
actUp actUp
actPageUp actPageUp
@@ -498,6 +504,7 @@ type searchRequest struct {
sort bool sort bool
sync bool sync bool
command *string command *string
environ []string
changed bool changed bool
} }
@@ -670,7 +677,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
if previewBox != nil && opts.Preview.aboveOrBelow() { if previewBox != nil && opts.Preview.aboveOrBelow() {
effectiveMinHeight += 1 + borderLines(opts.Preview.border) effectiveMinHeight += 1 + borderLines(opts.Preview.border)
} }
if opts.InfoStyle.noExtraLine() { if noSeparatorLine(opts.InfoStyle, opts.Separator == nil || uniseg.StringWidth(*opts.Separator) > 0) {
effectiveMinHeight-- effectiveMinHeight--
} }
effectiveMinHeight += borderLines(opts.BorderShape) effectiveMinHeight += borderLines(opts.BorderShape)
@@ -692,7 +699,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
t := Terminal{ t := Terminal{
initDelay: delay, initDelay: delay,
infoStyle: opts.InfoStyle, infoStyle: opts.InfoStyle,
infoSep: opts.InfoSep, infoPrefix: opts.InfoPrefix,
separator: nil, separator: nil,
spinner: makeSpinner(opts.Unicode), spinner: makeSpinner(opts.Unicode),
promptString: opts.Prompt, promptString: opts.Prompt,
@@ -771,7 +778,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
theme: opts.Theme, theme: opts.Theme,
startChan: make(chan fitpad, 1), startChan: make(chan fitpad, 1),
killChan: make(chan int), killChan: make(chan int),
serverInputChan: make(chan []*action, 10), serverInputChan: make(chan []*action, 100),
serverOutputChan: make(chan string), serverOutputChan: make(chan string),
eventChan: make(chan tui.Event, 6), // (load + result + zero|one) | (focus) | (resize) | (GetChar) eventChan: make(chan tui.Event, 6), // (load + result + zero|one) | (focus) | (resize) | (GetChar)
tui: renderer, tui: renderer,
@@ -817,13 +824,17 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
} }
} }
_, t.hasResizeActions = t.keymap[tui.Resize.AsEvent()] var resizeActions []*action
resizeActions, t.hasResizeActions = t.keymap[tui.Resize.AsEvent()]
if t.tui.ShouldEmitResizeEvent() {
t.keymap[tui.Resize.AsEvent()] = append(toActions(actClearScreen), resizeActions...)
}
_, t.hasResultActions = t.keymap[tui.Result.AsEvent()] _, t.hasResultActions = t.keymap[tui.Result.AsEvent()]
_, t.hasFocusActions = t.keymap[tui.Focus.AsEvent()] _, t.hasFocusActions = t.keymap[tui.Focus.AsEvent()]
_, t.hasLoadActions = t.keymap[tui.Load.AsEvent()] _, t.hasLoadActions = t.keymap[tui.Load.AsEvent()]
if t.listenAddr != nil { if t.listenAddr != nil {
err, port := startHttpServer(*t.listenAddr, t.serverInputChan, t.serverOutputChan) port, err := startHttpServer(*t.listenAddr, t.serverInputChan, t.serverOutputChan)
if err != nil { if err != nil {
errorExit(err.Error()) errorExit(err.Error())
} }
@@ -841,6 +852,8 @@ func (t *Terminal) environ() []string {
env = append(env, "FZF_QUERY="+string(t.input)) env = append(env, "FZF_QUERY="+string(t.input))
env = append(env, "FZF_ACTION="+t.lastAction.Name()) env = append(env, "FZF_ACTION="+t.lastAction.Name())
env = append(env, "FZF_PROMPT="+string(t.promptString)) env = append(env, "FZF_PROMPT="+string(t.promptString))
env = append(env, "FZF_PREVIEW_LABEL="+t.previewLabelOpts.label)
env = append(env, "FZF_BORDER_LABEL="+t.borderLabelOpts.label)
env = append(env, fmt.Sprintf("FZF_TOTAL_COUNT=%d", t.count)) env = append(env, fmt.Sprintf("FZF_TOTAL_COUNT=%d", t.count))
env = append(env, fmt.Sprintf("FZF_MATCH_COUNT=%d", t.merger.Length())) env = append(env, fmt.Sprintf("FZF_MATCH_COUNT=%d", t.merger.Length()))
env = append(env, fmt.Sprintf("FZF_SELECT_COUNT=%d", len(t.selected))) env = append(env, fmt.Sprintf("FZF_SELECT_COUNT=%d", len(t.selected)))
@@ -869,7 +882,7 @@ func (t *Terminal) visibleHeaderLines() int {
// Extra number of lines needed to display fzf // Extra number of lines needed to display fzf
func (t *Terminal) extraLines() int { func (t *Terminal) extraLines() int {
extra := t.visibleHeaderLines() + 1 extra := t.visibleHeaderLines() + 1
if !t.noInfoLine() { if !t.noSeparatorLine() {
extra++ extra++
} }
return extra return extra
@@ -975,8 +988,18 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) {
return output, promptLen return output, promptLen
} }
func (t *Terminal) noInfoLine() bool { func noSeparatorLine(style infoStyle, separator bool) bool {
return t.infoStyle.noExtraLine() switch style {
case infoInline:
return true
case infoHidden, infoInlineRight:
return !separator
}
return false
}
func (t *Terminal) noSeparatorLine() bool {
return noSeparatorLine(t.infoStyle, t.separatorLen > 0)
} }
func getScrollbar(total int, height int, offset int) (int, int) { func getScrollbar(total int, height int, offset int) (int, int) {
@@ -1051,11 +1074,11 @@ func (t *Terminal) UpdateProgress(progress float32) {
// UpdateList updates Merger to display the list // UpdateList updates Merger to display the list
func (t *Terminal) UpdateList(merger *Merger) { func (t *Terminal) UpdateList(merger *Merger) {
t.mutex.Lock() t.mutex.Lock()
var prevIndex int32 = -1 prevIndex := minItem.Index()
reset := t.revision != merger.Revision() reset := t.revision != merger.Revision()
if !reset && t.track != trackDisabled { if !reset && t.track != trackDisabled {
if t.merger.Length() > 0 { if t.merger.Length() > 0 {
prevIndex = t.merger.Get(t.cy).item.Index() prevIndex = t.currentIndex()
} else if merger.Length() > 0 { } else if merger.Length() > 0 {
prevIndex = merger.First().item.Index() prevIndex = merger.First().item.Index()
} }
@@ -1228,7 +1251,7 @@ func (t *Terminal) adjustMarginAndPadding() (int, int, [4]int, [4]int) {
minAreaWidth := minWidth minAreaWidth := minWidth
minAreaHeight := minHeight minAreaHeight := minHeight
if t.noInfoLine() { if t.noSeparatorLine() {
minAreaHeight -= 1 minAreaHeight -= 1
} }
if t.needPreviewWindow() { if t.needPreviewWindow() {
@@ -1250,6 +1273,7 @@ func (t *Terminal) adjustMarginAndPadding() (int, int, [4]int, [4]int) {
} }
func (t *Terminal) resizeWindows(forcePreview bool) { func (t *Terminal) resizeWindows(forcePreview bool) {
t.forcePreview = forcePreview
screenWidth, screenHeight, marginInt, paddingInt := t.adjustMarginAndPadding() screenWidth, screenHeight, marginInt, paddingInt := t.adjustMarginAndPadding()
width := screenWidth - marginInt[1] - marginInt[3] width := screenWidth - marginInt[1] - marginInt[3]
height := screenHeight - marginInt[0] - marginInt[2] height := screenHeight - marginInt[0] - marginInt[2]
@@ -1508,7 +1532,7 @@ func (t *Terminal) move(y int, x int, clear bool) {
y = h - y - 1 y = h - y - 1
case layoutReverseList: case layoutReverseList:
n := 2 + t.visibleHeaderLines() n := 2 + t.visibleHeaderLines()
if t.noInfoLine() { if t.noSeparatorLine() {
n-- n--
} }
if y < n { if y < n {
@@ -1548,7 +1572,7 @@ func (t *Terminal) updatePromptOffset() ([]rune, []rune) {
func (t *Terminal) promptLine() int { func (t *Terminal) promptLine() int {
if t.headerFirst { if t.headerFirst {
max := t.window.Height() - 1 max := t.window.Height() - 1
if !t.noInfoLine() { if !t.noSeparatorLine() {
max-- max--
} }
return util.Min(t.visibleHeaderLines(), max) return util.Min(t.visibleHeaderLines(), max)
@@ -1593,20 +1617,8 @@ func (t *Terminal) printInfo() {
t.window.Print(" ") // Clear spinner t.window.Print(" ") // Clear spinner
} }
} }
switch t.infoStyle { printInfoPrefix := func() {
case infoDefault: str := t.infoPrefix
t.move(line+1, 0, t.separatorLen == 0)
printSpinner()
t.move(line+1, 2, false)
pos = 2
case infoRight:
t.move(line+1, 0, false)
case infoInlineRight:
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
t.move(line, pos, true)
case infoInline:
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
str := t.infoSep
maxWidth := t.window.Width() - pos maxWidth := t.window.Width() - pos
width := util.StringWidth(str) width := util.StringWidth(str)
if width > maxWidth { if width > maxWidth {
@@ -1621,7 +1633,34 @@ func (t *Terminal) printInfo() {
t.window.CPrint(tui.ColPrompt, str) t.window.CPrint(tui.ColPrompt, str)
} }
pos += width pos += width
}
printSeparator := func(fillLength int, pad bool) {
// --------_
if t.separatorLen > 0 {
t.separator(t.window, fillLength)
t.window.Print(" ")
} else if pad {
t.window.Print(strings.Repeat(" ", fillLength+1))
}
}
switch t.infoStyle {
case infoDefault:
t.move(line+1, 0, t.separatorLen == 0)
printSpinner()
t.move(line+1, 2, false)
pos = 2
case infoRight:
t.move(line+1, 0, false)
case infoInlineRight:
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
case infoInline:
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
printInfoPrefix()
case infoHidden: case infoHidden:
if t.separatorLen > 0 {
t.move(line+1, 0, false)
printSeparator(t.window.Width()-1, false)
}
return return
} }
@@ -1635,8 +1674,11 @@ func (t *Terminal) printInfo() {
output += " -S" output += " -S"
} }
} }
if t.track != trackDisabled { switch t.track {
case trackEnabled:
output += " +T" output += " +T"
case trackCurrent:
output += " +t"
} }
if t.multi > 0 { if t.multi > 0 {
if t.multi == maxMulti { if t.multi == maxMulti {
@@ -1652,15 +1694,6 @@ func (t *Terminal) printInfo() {
output = fmt.Sprintf("[Command failed: %s]", *t.failed) output = fmt.Sprintf("[Command failed: %s]", *t.failed)
} }
printSeparator := func(fillLength int, pad bool) {
// --------_
if t.separatorLen > 0 {
t.separator(t.window, fillLength)
t.window.Print(" ")
} else if pad {
t.window.Print(strings.Repeat(" ", fillLength+1))
}
}
if t.infoStyle == infoRight { if t.infoStyle == infoRight {
maxWidth := t.window.Width() maxWidth := t.window.Width()
if t.reading { if t.reading {
@@ -1683,19 +1716,35 @@ func (t *Terminal) printInfo() {
} }
if t.infoStyle == infoInlineRight { if t.infoStyle == infoInlineRight {
pos = util.Max(pos, t.window.Width()-util.StringWidth(output)-3) if len(t.infoPrefix) == 0 {
if pos >= t.window.Width() { pos = util.Max(pos, t.window.Width()-util.StringWidth(output)-3)
return if pos < t.window.Width() {
t.move(line, pos, false)
printSpinner()
pos++
}
if pos < t.window.Width()-1 {
t.window.Print(" ")
pos++
}
} else {
pos = util.Max(pos, t.window.Width()-util.StringWidth(output)-util.StringWidth(t.infoPrefix)-1)
printInfoPrefix()
} }
t.move(line, pos, false)
printSpinner()
t.window.Print(" ")
pos += 2
} }
maxWidth := t.window.Width() - pos maxWidth := t.window.Width() - pos
output = t.trimMessage(output, maxWidth) output = t.trimMessage(output, maxWidth)
t.window.CPrint(tui.ColInfo, output) t.window.CPrint(tui.ColInfo, output)
if t.infoStyle == infoInlineRight {
if t.separatorLen > 0 {
t.move(line+1, 0, false)
printSeparator(t.window.Width()-1, false)
}
return
}
fillLength := maxWidth - len(output) - 2 fillLength := maxWidth - len(output) - 2
if fillLength > 0 { if fillLength > 0 {
t.window.CPrint(tui.ColSeparator, " ") t.window.CPrint(tui.ColSeparator, " ")
@@ -1710,7 +1759,7 @@ func (t *Terminal) printHeader() {
max := t.window.Height() max := t.window.Height()
if t.headerFirst { if t.headerFirst {
max-- max--
if !t.noInfoLine() { if !t.noSeparatorLine() {
max-- max--
} }
} }
@@ -1727,7 +1776,7 @@ func (t *Terminal) printHeader() {
} }
if !t.headerFirst { if !t.headerFirst {
line++ line++
if !t.noInfoLine() { if !t.noSeparatorLine() {
line++ line++
} }
} }
@@ -1759,7 +1808,7 @@ func (t *Terminal) printList() {
i = maxy - 1 - j i = maxy - 1 - j
} }
line := i + 2 + t.visibleHeaderLines() line := i + 2 + t.visibleHeaderLines()
if t.noInfoLine() { if t.noSeparatorLine() {
line-- line--
} }
if i < count { if i < count {
@@ -2312,7 +2361,7 @@ func (t *Terminal) processTabs(runes []rune, prefixWidth int) (string, int) {
} }
func (t *Terminal) printAll() { func (t *Terminal) printAll() {
t.resizeWindows(false) t.resizeWindows(t.forcePreview)
t.printList() t.printList()
t.printPrompt() t.printPrompt()
t.printInfo() t.printInfo()
@@ -2852,14 +2901,16 @@ func (t *Terminal) Loop() {
} }
}() }()
resizeChan := make(chan os.Signal, 1) if !t.tui.ShouldEmitResizeEvent() {
notifyOnResize(resizeChan) // Non-portable resizeChan := make(chan os.Signal, 1)
go func() { notifyOnResize(resizeChan) // Non-portable
for { go func() {
<-resizeChan for {
t.reqBox.Set(reqResize, nil) <-resizeChan
} t.reqBox.Set(reqResize, nil)
}() }
}()
}
t.mutex.Lock() t.mutex.Lock()
t.initFunc() t.initFunc()
@@ -3083,7 +3134,7 @@ func (t *Terminal) Loop() {
switch req { switch req {
case reqPrompt: case reqPrompt:
t.printPrompt() t.printPrompt()
if t.noInfoLine() { if t.infoStyle == infoInline || t.infoStyle == infoInlineRight {
t.printInfo() t.printInfo()
} }
case reqInfo: case reqInfo:
@@ -3152,7 +3203,7 @@ func (t *Terminal) Loop() {
} }
t.previewer.lines = result.lines t.previewer.lines = result.lines
t.previewer.spinner = result.spinner t.previewer.spinner = result.spinner
if t.previewer.following.Enabled() { if t.hasPreviewWindow() && t.previewer.following.Enabled() {
t.previewer.offset = util.Max(t.previewer.offset, len(t.previewer.lines)-(t.pwindow.Height()-t.previewOpts.headerLines)) t.previewer.offset = util.Max(t.previewer.offset, len(t.previewer.lines)-(t.pwindow.Height()-t.previewOpts.headerLines))
} else if result.offset >= 0 { } else if result.offset >= 0 {
t.previewer.offset = util.Constrain(result.offset, t.previewOpts.headerLines, len(t.previewer.lines)-1) t.previewer.offset = util.Constrain(result.offset, t.previewOpts.headerLines, len(t.previewer.lines)-1)
@@ -3215,7 +3266,11 @@ func (t *Terminal) Loop() {
} }
select { select {
case event = <-t.eventChan: case event = <-t.eventChan:
needBarrier = !event.Is(tui.Load, tui.Result, tui.Focus, tui.One, tui.Zero, tui.Resize) if t.tui.ShouldEmitResizeEvent() {
needBarrier = !event.Is(tui.Load, tui.Result, tui.Focus, tui.One, tui.Zero)
} else {
needBarrier = !event.Is(tui.Load, tui.Result, tui.Focus, tui.One, tui.Zero, tui.Resize)
}
case serverActions := <-t.serverInputChan: case serverActions := <-t.serverInputChan:
event = tui.Invalid.AsEvent() event = tui.Invalid.AsEvent()
if t.listenAddr == nil || t.listenAddr.IsLocal() || t.listenUnsafe { if t.listenAddr == nil || t.listenAddr.IsLocal() || t.listenUnsafe {
@@ -3292,18 +3347,22 @@ func (t *Terminal) Loop() {
var doAction func(*action) bool var doAction func(*action) bool
var doActions func(actions []*action) bool var doActions func(actions []*action) bool
doActions = func(actions []*action) bool { doActions = func(actions []*action) bool {
currentIndex := t.currentIndex() for iter := 0; iter <= maxFocusEvents; iter++ {
for _, action := range actions { currentIndex := t.currentIndex()
if !doAction(action) { for _, action := range actions {
return false if !doAction(action) {
return false
}
} }
}
if onFocus, prs := t.keymap[tui.Focus.AsEvent()]; prs { if onFocus, prs := t.keymap[tui.Focus.AsEvent()]; prs && iter < maxFocusEvents {
if newIndex := t.currentIndex(); newIndex != currentIndex { if newIndex := t.currentIndex(); newIndex != currentIndex {
t.lastFocus = newIndex t.lastFocus = newIndex
return doActions(onFocus) actions = onFocus
continue
}
} }
break
} }
return true return true
} }
@@ -3369,6 +3428,9 @@ func (t *Terminal) Loop() {
// Discard the preview content so that it won't accidentally appear // Discard the preview content so that it won't accidentally appear
// when preview window is re-enabled and previewDelay is triggered // when preview window is re-enabled and previewDelay is triggered
t.previewer.lines = nil t.previewer.lines = nil
// Also kill the preview process if it's still running
t.cancelPreview()
} }
} }
case actTogglePreviewWrap: case actTogglePreviewWrap:
@@ -3448,11 +3510,13 @@ func (t *Terminal) Loop() {
req(reqHeader) req(reqHeader)
} }
case actChangeBorderLabel: case actChangeBorderLabel:
t.borderLabelOpts.label = a.a
if t.border != nil { if t.border != nil {
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(a.a, &tui.ColBorderLabel, false) t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(a.a, &tui.ColBorderLabel, false)
req(reqRedrawBorderLabel) req(reqRedrawBorderLabel)
} }
case actChangePreviewLabel: case actChangePreviewLabel:
t.previewLabelOpts.label = a.a
if t.pborder != nil { if t.pborder != nil {
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(a.a, &tui.ColPreviewLabel, false) t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(a.a, &tui.ColPreviewLabel, false)
req(reqRedrawPreviewLabel) req(reqRedrawPreviewLabel)
@@ -3462,14 +3526,16 @@ func (t *Terminal) Loop() {
actions := parseSingleActionList(strings.Trim(body, "\r\n"), func(message string) {}) actions := parseSingleActionList(strings.Trim(body, "\r\n"), func(message string) {})
return doActions(actions) return doActions(actions)
case actTransformBorderLabel: case actTransformBorderLabel:
label := t.executeCommand(a.a, false, true, true, true)
t.borderLabelOpts.label = label
if t.border != nil { if t.border != nil {
label := t.executeCommand(a.a, false, true, true, true)
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(label, &tui.ColBorderLabel, false) t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(label, &tui.ColBorderLabel, false)
req(reqRedrawBorderLabel) req(reqRedrawBorderLabel)
} }
case actTransformPreviewLabel: case actTransformPreviewLabel:
label := t.executeCommand(a.a, false, true, true, true)
t.previewLabelOpts.label = label
if t.pborder != nil { if t.pborder != nil {
label := t.executeCommand(a.a, false, true, true, true)
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(label, &tui.ColPreviewLabel, false) t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(label, &tui.ColPreviewLabel, false)
req(reqRedrawPreviewLabel) req(reqRedrawPreviewLabel)
} }
@@ -3478,7 +3544,9 @@ func (t *Terminal) Loop() {
t.prompt, t.promptLen = t.parsePrompt(a.a) t.prompt, t.promptLen = t.parsePrompt(a.a)
req(reqPrompt) req(reqPrompt)
case actPreview: case actPreview:
updatePreviewWindow(true) if !t.hasPreviewWindow() {
updatePreviewWindow(true)
}
refreshPreview(a.a) refreshPreview(a.a)
case actRefreshPreview: case actRefreshPreview:
refreshPreview(t.previewOpts.command) refreshPreview(t.previewOpts.command)
@@ -3753,6 +3821,14 @@ func (t *Terminal) Loop() {
t.track = trackEnabled t.track = trackEnabled
} }
req(reqInfo) req(reqInfo)
case actToggleTrackCurrent:
switch t.track {
case trackCurrent:
t.track = trackDisabled
case trackDisabled:
t.track = trackCurrent
}
req(reqInfo)
case actShowHeader: case actShowHeader:
t.headerVisible = true t.headerVisible = true
req(reqList, reqInfo, reqPrompt, reqHeader) req(reqList, reqInfo, reqPrompt, reqHeader)
@@ -3762,11 +3838,16 @@ func (t *Terminal) Loop() {
case actToggleHeader: case actToggleHeader:
t.headerVisible = !t.headerVisible t.headerVisible = !t.headerVisible
req(reqList, reqInfo, reqPrompt, reqHeader) req(reqList, reqInfo, reqPrompt, reqHeader)
case actTrack: case actTrackCurrent:
if t.track == trackDisabled { if t.track == trackDisabled {
t.track = trackCurrent t.track = trackCurrent
} }
req(reqInfo) req(reqInfo)
case actUntrackCurrent:
if t.track == trackCurrent {
t.track = trackDisabled
}
req(reqInfo)
case actEnableSearch: case actEnableSearch:
t.paused = false t.paused = false
changed = true changed = true
@@ -3855,7 +3936,7 @@ func (t *Terminal) Loop() {
mx -= t.window.Left() mx -= t.window.Left()
my -= t.window.Top() my -= t.window.Top()
min := 2 + t.visibleHeaderLines() min := 2 + t.visibleHeaderLines()
if t.noInfoLine() { if t.noSeparatorLine() {
min-- min--
} }
h := t.window.Height() h := t.window.Height()
@@ -3973,11 +4054,18 @@ func (t *Terminal) Loop() {
// Full redraw // Full redraw
if !currentPreviewOpts.sameLayout(t.previewOpts) { if !currentPreviewOpts.sameLayout(t.previewOpts) {
wasHidden := t.pwindow == nil // Preview command can be running in the background if the size of
// the preview window is 0 but not 'hidden'
wasHidden := currentPreviewOpts.hidden
updatePreviewWindow(false) updatePreviewWindow(false)
if wasHidden && t.hasPreviewWindow() { if wasHidden && t.hasPreviewWindow() {
// Restart
refreshPreview(t.previewOpts.command) refreshPreview(t.previewOpts.command)
} else if t.previewOpts.hidden {
// Cancel
t.cancelPreview()
} else { } else {
// Refresh
req(reqPreviewRefresh) req(reqPreviewRefresh)
} }
} else if !currentPreviewOpts.sameContentLayout(t.previewOpts) { } else if !currentPreviewOpts.sameContentLayout(t.previewOpts) {
@@ -4067,10 +4155,15 @@ func (t *Terminal) Loop() {
req(reqPrompt) req(reqPrompt)
} }
reload := changed || newCommand != nil
var reloadRequest *searchRequest
if reload {
reloadRequest = &searchRequest{sort: t.sort, sync: reloadSync, command: newCommand, environ: t.environ(), changed: changed}
}
t.mutex.Unlock() // Must be unlocked before touching reqBox t.mutex.Unlock() // Must be unlocked before touching reqBox
if changed || newCommand != nil { if reload {
t.eventBox.Set(EvtSearchNew, searchRequest{sort: t.sort, sync: reloadSync, command: newCommand, changed: changed}) t.eventBox.Set(EvtSearchNew, *reloadRequest)
} }
for _, event := range events { for _, event := range events {
t.reqBox.Set(event, nil) t.reqBox.Set(event, nil)
@@ -4084,7 +4177,7 @@ func (t *Terminal) constrain() {
// count of lines can be displayed // count of lines can be displayed
height := t.maxItems() height := t.maxItems()
t.cy = util.Constrain(t.cy, 0, count-1) t.cy = util.Constrain(t.cy, 0, util.Max(0, count-1))
minOffset := util.Max(t.cy-height+1, 0) minOffset := util.Max(t.cy-height+1, 0)
maxOffset := util.Max(util.Min(count-height, t.cy), 0) maxOffset := util.Max(util.Min(count-height, t.cy), 0)
@@ -4135,7 +4228,7 @@ func (t *Terminal) vset(o int) bool {
func (t *Terminal) maxItems() int { func (t *Terminal) maxItems() int {
max := t.window.Height() - 2 - t.visibleHeaderLines() max := t.window.Height() - 2 - t.visibleHeaderLines()
if t.noInfoLine() { if t.noSeparatorLine() {
max++ max++
} }
return util.Max(max, 0) return util.Max(max, 0)

View File

@@ -91,7 +91,7 @@ func withPrefixLengths(tokens []string, begin int) []Token {
prefixLength := begin prefixLength := begin
for idx := range tokens { for idx := range tokens {
chars := util.ToChars([]byte(tokens[idx])) chars := util.ToChars(sbytes(tokens[idx]))
ret[idx] = Token{&chars, int32(prefixLength)} ret[idx] = Token{&chars, int32(prefixLength)}
prefixLength += chars.Length() prefixLength += chars.Length()
} }
@@ -187,7 +187,7 @@ func Transform(tokens []Token, withNth []Range) []Token {
if r.begin == r.end { if r.begin == r.end {
idx := r.begin idx := r.begin
if idx == rangeEllipsis { if idx == rangeEllipsis {
chars := util.ToChars([]byte(joinTokens(tokens))) chars := util.ToChars(sbytes(joinTokens(tokens)))
parts = append(parts, &chars) parts = append(parts, &chars)
} else { } else {
if idx < 0 { if idx < 0 {

View File

@@ -36,6 +36,7 @@ func (r *FullscreenRenderer) Resume(bool, bool) {}
func (r *FullscreenRenderer) PassThrough(string) {} func (r *FullscreenRenderer) PassThrough(string) {}
func (r *FullscreenRenderer) Clear() {} func (r *FullscreenRenderer) Clear() {}
func (r *FullscreenRenderer) NeedScrollbarRedraw() bool { return false } func (r *FullscreenRenderer) NeedScrollbarRedraw() bool { return false }
func (r *FullscreenRenderer) ShouldEmitResizeEvent() bool { return false }
func (r *FullscreenRenderer) Refresh() {} func (r *FullscreenRenderer) Refresh() {}
func (r *FullscreenRenderer) Close() {} func (r *FullscreenRenderer) Close() {}
func (r *FullscreenRenderer) Size() TermSize { return TermSize{} } func (r *FullscreenRenderer) Size() TermSize { return TermSize{} }

View File

@@ -10,7 +10,7 @@ import (
"time" "time"
"unicode/utf8" "unicode/utf8"
"github.com/junegunn/uniseg" "github.com/rivo/uniseg"
"golang.org/x/term" "golang.org/x/term"
) )
@@ -694,6 +694,10 @@ func (r *LightRenderer) NeedScrollbarRedraw() bool {
return false return false
} }
func (r *LightRenderer) ShouldEmitResizeEvent() bool {
return false
}
func (r *LightRenderer) RefreshWindows(windows []Window) { func (r *LightRenderer) RefreshWindows(windows []Window) {
r.flush() r.flush()
} }
@@ -804,13 +808,25 @@ func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
color = ColPreviewBorder color = ColPreviewBorder
} }
hw := runeWidth(w.border.top) hw := runeWidth(w.border.top)
pad := repeat(' ', w.width/hw)
w.Move(0, 0)
if top { if top {
w.Move(0, 0)
w.CPrint(color, repeat(w.border.top, w.width/hw)) 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 { if bottom {
w.Move(w.height-1, 0)
w.CPrint(color, repeat(w.border.bottom, w.width/hw)) w.CPrint(color, repeat(w.border.bottom, w.width/hw))
} else {
w.CPrint(color, pad)
} }
} }

View File

@@ -10,7 +10,7 @@ import (
"github.com/gdamore/tcell/v2/encoding" "github.com/gdamore/tcell/v2/encoding"
"github.com/junegunn/fzf/src/util" "github.com/junegunn/fzf/src/util"
"github.com/junegunn/uniseg" "github.com/rivo/uniseg"
) )
func HasFullscreenRenderer() bool { func HasFullscreenRenderer() bool {
@@ -143,6 +143,7 @@ func (a Attr) Merge(b Attr) Attr {
var ( var (
_screen tcell.Screen _screen tcell.Screen
_prevMouseButton tcell.ButtonMask _prevMouseButton tcell.ButtonMask
_initialResize bool = true
) )
func (r *FullscreenRenderer) initScreen() { func (r *FullscreenRenderer) initScreen() {
@@ -202,6 +203,10 @@ func (r *FullscreenRenderer) NeedScrollbarRedraw() bool {
return true return true
} }
func (r *FullscreenRenderer) ShouldEmitResizeEvent() bool {
return true
}
func (r *FullscreenRenderer) Refresh() { func (r *FullscreenRenderer) Refresh() {
// noop // noop
} }
@@ -216,6 +221,12 @@ func (r *FullscreenRenderer) GetChar() Event {
ev := _screen.PollEvent() ev := _screen.PollEvent()
switch ev := ev.(type) { switch ev := ev.(type) {
case *tcell.EventResize: 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} return Event{Resize, 0, nil}
// process mouse events: // process mouse events:
@@ -533,7 +544,7 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int,
height: height, height: height,
normal: normal, normal: normal,
borderStyle: borderStyle} borderStyle: borderStyle}
w.drawBorder(false) w.Erase()
return w return w
} }
@@ -550,8 +561,8 @@ func fill(x, y, w, h int, n ColorPair, r rune) {
} }
func (w *TcellWindow) Erase() { func (w *TcellWindow) Erase() {
w.drawBorder(false)
fill(w.left-1, w.top, w.width+1, w.height-1, w.normal, ' ') fill(w.left-1, w.top, w.width+1, w.height-1, w.normal, ' ')
w.drawBorder(false)
} }
func (w *TcellWindow) EraseMaybe() bool { func (w *TcellWindow) EraseMaybe() bool {

View File

@@ -182,6 +182,7 @@ func TestGetCharEventKey(t *testing.T) {
r.Init() r.Init()
// run and evaluate the tests // run and evaluate the tests
initialResizeAsInvalid := true
for _, test := range tests { for _, test := range tests {
// generate key event // generate key event
giveEvent := tcell.NewEventKey(test.giveKey.Type, test.giveKey.Char, test.giveKey.Mods) 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 // process the event in fzf and evaluate the test
gotEvent := r.GetChar() gotEvent := r.GetChar()
// skip Resize events, those are sometimes put in the buffer outside of this test // skip Resize events, those are sometimes put in the buffer outside of this test
for gotEvent.Type == Resize { if initialResizeAsInvalid && gotEvent.Type == Invalid {
t.Logf("Resize swallowed") t.Logf("Resize as Invalid swallowed")
initialResizeAsInvalid = false
gotEvent = r.GetChar() gotEvent = r.GetChar()
} }
t.Logf("wantEvent = %T{Type: %v, Char: %q (%[3]v)}\n", test.wantKey, test.wantKey.Type, test.wantKey.Char) t.Logf("wantEvent = %T{Type: %v, Char: %q (%[3]v)}\n", test.wantKey, test.wantKey.Type, test.wantKey.Char)

View File

@@ -6,7 +6,7 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/junegunn/uniseg" "github.com/rivo/uniseg"
) )
// Types of user action // Types of user action
@@ -494,6 +494,7 @@ type Renderer interface {
Close() Close()
PassThrough(string) PassThrough(string)
NeedScrollbarRedraw() bool NeedScrollbarRedraw() bool
ShouldEmitResizeEvent() bool
GetChar() Event GetChar() Event

View File

@@ -163,7 +163,7 @@ func (chars *Chars) ToString() string {
if runes := chars.optionalRunes(); runes != nil { if runes := chars.optionalRunes(); runes != nil {
return string(runes) return string(runes)
} }
return string(chars.slice) return unsafe.String(unsafe.SliceData(chars.slice), len(chars.slice))
} }
func (chars *Chars) ToRunes() []rune { func (chars *Chars) ToRunes() []rune {

View File

@@ -6,8 +6,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/junegunn/uniseg"
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
"github.com/rivo/uniseg"
) )
// StringWidth returns string width where each CR/LF character takes 1 column // StringWidth returns string width where each CR/LF character takes 1 column

View File

@@ -18,7 +18,6 @@ UNSETS = %w[
FZF_ALT_C_COMMAND FZF_ALT_C_COMMAND
FZF_ALT_C_OPTS FZF_CTRL_R_OPTS FZF_ALT_C_OPTS FZF_CTRL_R_OPTS
FZF_API_KEY FZF_API_KEY
fish_history
].freeze ].freeze
DEFAULT_TIMEOUT = 10 DEFAULT_TIMEOUT = 10
@@ -67,7 +66,7 @@ class Shell
end end
def fish def fish
UNSETS.map { |v| v + '= ' }.join + ' FZF_DEFAULT_OPTS=--no-scrollbar fish' "unset #{UNSETS.join(' ')}; FZF_DEFAULT_OPTS=--no-scrollbar fish_history= fish"
end end
end end
end end
@@ -964,26 +963,40 @@ class TestGoFZF < TestBase
def test_execute def test_execute
output = '/tmp/fzf-test-execute' output = '/tmp/fzf-test-execute'
opts = %[--bind "alt-a:execute(echo /{}/ >> #{output}),alt-b:execute[echo /{}{}/ >> #{output}],C:execute:echo /{}{}{}/ >> #{output}"] opts = %[--bind "alt-a:execute(echo /{}/ >> #{output})+change-header(alt-a),alt-b:execute[echo /{}{}/ >> #{output}]+change-header(alt-b),C:execute(echo /{}{}{}/ >> #{output})+change-header(C)"]
writelines(tempname, %w[foo'bar foo"bar foo$bar]) writelines(tempname, %w[foo'bar foo"bar foo$bar])
tmux.send_keys "cat #{tempname} | #{fzf(opts)}", :Enter tmux.send_keys "cat #{tempname} | #{fzf(opts)}", :Enter
tmux.until { |lines| assert_equal ' 3/3', lines[-2] } tmux.until { |lines| assert_equal 3, lines.item_count }
tmux.send_keys :Escape, :a
ready = ->(s) { tmux.until { |lines| assert_includes lines[-3], s } }
tmux.send_keys :Escape, :a tmux.send_keys :Escape, :a
ready.call('alt-a')
tmux.send_keys :Escape, :b
ready.call('alt-b')
tmux.send_keys :Up tmux.send_keys :Up
tmux.send_keys :Escape, :a
ready.call('alt-a')
tmux.send_keys :Escape, :b tmux.send_keys :Escape, :b
tmux.send_keys :Escape, :b ready.call('alt-b')
tmux.send_keys :Up tmux.send_keys :Up
tmux.send_keys :C tmux.send_keys :C
ready.call('C')
tmux.send_keys 'barfoo' tmux.send_keys 'barfoo'
tmux.until { |lines| assert_equal ' 0/3', lines[-2] } tmux.until { |lines| assert_equal ' 0/3', lines[-2] }
tmux.send_keys :Escape, :a tmux.send_keys :Escape, :a
ready.call('alt-a')
tmux.send_keys :Escape, :b tmux.send_keys :Escape, :b
ready.call('alt-b')
wait do wait do
assert_path_exists output assert_path_exists output
assert_equal %w[ assert_equal %w[
/foo'bar/ /foo'bar/ /foo'bar/ /foo'barfoo'bar/
/foo"barfoo"bar/ /foo"barfoo"bar/ /foo"bar/ /foo"barfoo"bar/
/foo$barfoo$barfoo$bar/ /foo$barfoo$barfoo$bar/
], File.readlines(output, chomp: true) ], File.readlines(output, chomp: true)
end end
@@ -993,17 +1006,28 @@ class TestGoFZF < TestBase
def test_execute_multi def test_execute_multi
output = '/tmp/fzf-test-execute-multi' output = '/tmp/fzf-test-execute-multi'
opts = %[--multi --bind "alt-a:execute-multi(echo {}/{+} >> #{output})"] opts = %[--multi --bind "alt-a:execute-multi(echo {}/{+} >> #{output})+change-header(alt-a),alt-b:change-header(alt-b)"]
writelines(tempname, %w[foo'bar foo"bar foo$bar foobar]) writelines(tempname, %w[foo'bar foo"bar foo$bar foobar])
tmux.send_keys "cat #{tempname} | #{fzf(opts)}", :Enter tmux.send_keys "cat #{tempname} | #{fzf(opts)}", :Enter
ready = ->(s) { tmux.until { |lines| assert_includes lines[-3], s } }
tmux.until { |lines| assert_equal ' 4/4 (0)', lines[-2] } tmux.until { |lines| assert_equal ' 4/4 (0)', lines[-2] }
tmux.send_keys :Escape, :a tmux.send_keys :Escape, :a
ready.call('alt-a')
tmux.send_keys :Escape, :b
ready.call('alt-b')
tmux.send_keys :BTab, :BTab, :BTab tmux.send_keys :BTab, :BTab, :BTab
tmux.until { |lines| assert_equal ' 4/4 (3)', lines[-2] } tmux.until { |lines| assert_equal ' 4/4 (3)', lines[-2] }
tmux.send_keys :Escape, :a tmux.send_keys :Escape, :a
ready.call('alt-a')
tmux.send_keys :Escape, :b
ready.call('alt-b')
tmux.send_keys :Tab, :Tab tmux.send_keys :Tab, :Tab
tmux.until { |lines| assert_equal ' 4/4 (3)', lines[-2] } tmux.until { |lines| assert_equal ' 4/4 (3)', lines[-2] }
tmux.send_keys :Escape, :a tmux.send_keys :Escape, :a
ready.call('alt-a')
wait do wait do
assert_path_exists output assert_path_exists output
assert_equal [ assert_equal [
@@ -1221,7 +1245,7 @@ class TestGoFZF < TestBase
end end
def test_toggle_header def test_toggle_header
tmux.send_keys "seq 4 | #{FZF} --header-lines 2 --header foo --bind space:toggle-header --header-first --height 10 --border", :Enter tmux.send_keys "seq 4 | #{FZF} --header-lines 2 --header foo --bind space:toggle-header --header-first --height 10 --border rounded", :Enter
before = <<~OUTPUT before = <<~OUTPUT
@@ -1695,7 +1719,7 @@ class TestGoFZF < TestBase
end end
def test_info_hidden def test_info_hidden
tmux.send_keys 'seq 10 | fzf --info=hidden', :Enter tmux.send_keys 'seq 10 | fzf --info=hidden --no-separator', :Enter
tmux.until { |lines| assert_equal '> 1', lines[-2] } tmux.until { |lines| assert_equal '> 1', lines[-2] }
end end
@@ -1893,7 +1917,7 @@ class TestGoFZF < TestBase
end end
def test_reload def test_reload
tmux.send_keys %(seq 1000 | #{FZF} --bind 'change:reload(seq {q}),a:reload(seq 100),b:reload:seq 200' --header-lines 2 --multi 2), :Enter tmux.send_keys %(seq 1000 | #{FZF} --bind 'change:reload(seq $FZF_QUERY),a:reload(seq 100),b:reload:seq 200' --header-lines 2 --multi 2), :Enter
tmux.until { |lines| assert_equal 998, lines.match_count } tmux.until { |lines| assert_equal 998, lines.match_count }
tmux.send_keys 'a' tmux.send_keys 'a'
tmux.until do |lines| tmux.until do |lines|
@@ -2160,14 +2184,15 @@ class TestGoFZF < TestBase
file = Tempfile.new('fzf-follow') file = Tempfile.new('fzf-follow')
file.sync = true file.sync = true
tmux.send_keys %(seq 100 | #{FZF} --preview 'tail -f "#{file.path}"' --preview-window follow --bind 'up:preview-up,down:preview-down,space:change-preview-window:follow|nofollow' --preview-window '~3'), :Enter tmux.send_keys %(seq 100 | #{FZF} --preview 'echo start; tail -f "#{file.path}"' --preview-window follow --bind 'up:preview-up,down:preview-down,space:change-preview-window:follow|nofollow' --preview-window '~4'), :Enter
tmux.until { |lines| lines.item_count == 100 } tmux.until { |lines| lines.item_count == 100 }
# Write to the temporary file, and check if the preview window is showing # Write to the temporary file, and check if the preview window is showing
# the last line of the file # the last line of the file
tmux.until { |lines| assert_includes lines[1], 'start' }
3.times { file.puts _1 } # header lines 3.times { file.puts _1 } # header lines
1000.times { file.puts _1 } 1000.times { file.puts _1 }
tmux.until { |lines| assert_includes lines[1], '/1003' } tmux.until { |lines| assert_includes lines[1], '/1004' }
tmux.until { |lines| assert_includes lines[-2], '999' } tmux.until { |lines| assert_includes lines[-2], '999' }
# Scroll the preview window and fzf should stop following the file content # Scroll the preview window and fzf should stop following the file content
@@ -2175,7 +2200,7 @@ class TestGoFZF < TestBase
tmux.until { |lines| assert_includes lines[-2], '998' } tmux.until { |lines| assert_includes lines[-2], '998' }
file.puts 'foo', 'bar' file.puts 'foo', 'bar'
tmux.until do |lines| tmux.until do |lines|
assert_includes lines[1], '/1005' assert_includes lines[1], '/1006'
assert_includes lines[-2], '998' assert_includes lines[-2], '998'
end end
@@ -2188,7 +2213,7 @@ class TestGoFZF < TestBase
end end
file.puts 'baz' file.puts 'baz'
tmux.until do |lines| tmux.until do |lines|
assert_includes lines[1], '/1006' assert_includes lines[1], '/1007'
assert_includes lines[-2], 'baz' assert_includes lines[-2], 'baz'
end end
@@ -2197,7 +2222,7 @@ class TestGoFZF < TestBase
wait { assert_includes lines[-2], 'bar' } wait { assert_includes lines[-2], 'bar' }
file.puts 'aaa' file.puts 'aaa'
tmux.until do |lines| tmux.until do |lines|
assert_includes lines[1], '/1007' assert_includes lines[1], '/1008'
assert_includes lines[-2], 'bar' assert_includes lines[-2], 'bar'
end end
@@ -2206,7 +2231,7 @@ class TestGoFZF < TestBase
tmux.until { |lines| assert_includes lines[-2], 'aaa' } tmux.until { |lines| assert_includes lines[-2], 'aaa' }
file.puts 'bbb' file.puts 'bbb'
tmux.until do |lines| tmux.until do |lines|
assert_includes lines[1], '/1008' assert_includes lines[1], '/1009'
assert_includes lines[-2], 'bbb' assert_includes lines[-2], 'bbb'
end end
@@ -2214,7 +2239,7 @@ class TestGoFZF < TestBase
tmux.send_keys :Space tmux.send_keys :Space
file.puts 'ccc', 'ddd' file.puts 'ccc', 'ddd'
tmux.until do |lines| tmux.until do |lines|
assert_includes lines[1], '/1010' assert_includes lines[1], '/1011'
assert_includes lines[-2], 'bbb' assert_includes lines[-2], 'bbb'
end end
rescue StandardError rescue StandardError
@@ -2438,6 +2463,84 @@ class TestGoFZF < TestBase
tmux.until { |lines| assert_equal 10, lines.match_count } tmux.until { |lines| assert_equal 10, lines.match_count }
end end
def test_reload_disabled_case1
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload(sleep 2; seq 1000)'", :Enter
tmux.until do |lines|
assert_equal 100, lines.item_count
assert_equal 1, lines.match_count
end
tmux.send_keys :Space
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.send_keys :BSpace
tmux.until { |lines| assert_equal 0, lines.match_count }
tmux.until { |lines| assert_equal 1000, lines.match_count }
end
def test_reload_disabled_case2
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload-sync(sleep 2; seq 1000)'", :Enter
tmux.until do |lines|
assert_equal 100, lines.item_count
assert_equal 1, lines.match_count
end
tmux.send_keys :Space
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.send_keys :BSpace
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.until { |lines| assert_equal 1000, lines.match_count }
end
def test_reload_disabled_case3
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload(sleep 2; seq 1000)+backward-delete-char'", :Enter
tmux.until do |lines|
assert_equal 100, lines.item_count
assert_equal 1, lines.match_count
end
tmux.send_keys :Space
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.send_keys :BSpace
tmux.until { |lines| assert_equal 0, lines.match_count }
tmux.until { |lines| assert_equal 1000, lines.match_count }
end
def test_reload_disabled_case4
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload-sync(sleep 2; seq 1000)+backward-delete-char'", :Enter
tmux.until do |lines|
assert_equal 100, lines.item_count
assert_equal 1, lines.match_count
end
tmux.send_keys :Space
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.send_keys :BSpace
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.until { |lines| assert_equal 1000, lines.match_count }
end
def test_reload_disabled_case5
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload(echo xx; sleep 2; seq 1000)'", :Enter
tmux.until do |lines|
assert_equal 100, lines.item_count
assert_equal 1, lines.match_count
end
tmux.send_keys :Space
tmux.until do |lines|
assert_equal 1, lines.item_count
assert_equal 1, lines.match_count
end
tmux.send_keys :BSpace
tmux.until { |lines| assert_equal 1001, lines.match_count }
end
def test_reload_disabled_case6
tmux.send_keys "seq 1000 | #{FZF} --disabled --bind 'change:reload:sleep 0.5; seq {q}'", :Enter
tmux.until { |lines| assert_equal 1000, lines.match_count }
tmux.send_keys '9'
tmux.until { |lines| assert_equal 9, lines.match_count }
tmux.send_keys '9'
tmux.until { |lines| assert_equal 99, lines.match_count }
# TODO: How do we verify if an intermediate empty list is not shown?
end
def test_scroll_off def test_scroll_off
tmux.send_keys "seq 1000 | #{FZF} --scroll-off=3 --bind l:last", :Enter tmux.send_keys "seq 1000 | #{FZF} --scroll-off=3 --bind l:last", :Enter
tmux.until { |lines| assert_equal 1000, lines.item_count } tmux.until { |lines| assert_equal 1000, lines.item_count }
@@ -2556,6 +2659,7 @@ class TestGoFZF < TestBase
def test_change_preview_window_rotate def test_change_preview_window_rotate
tmux.send_keys "seq 100 | #{FZF} --preview-window left,border-none --preview 'echo hello' --bind '" \ tmux.send_keys "seq 100 | #{FZF} --preview-window left,border-none --preview 'echo hello' --bind '" \
"a:change-preview-window(right|down|up|hidden|)'", :Enter "a:change-preview-window(right|down|up|hidden|)'", :Enter
tmux.until { |lines| assert(lines.any? { _1.include?('100/100') }) }
3.times do 3.times do
tmux.until { |lines| lines[0].start_with?('hello') } tmux.until { |lines| lines[0].start_with?('hello') }
tmux.send_keys 'a' tmux.send_keys 'a'
@@ -2616,7 +2720,7 @@ class TestGoFZF < TestBase
end end
def test_height_range_fit def test_height_range_fit
tmux.send_keys 'seq 3 | fzf --height ~100% --info=inline --border', :Enter tmux.send_keys 'seq 3 | fzf --height ~100% --info=inline --border rounded', :Enter
expected = <<~OUTPUT expected = <<~OUTPUT
3 3
@@ -2629,7 +2733,7 @@ class TestGoFZF < TestBase
end end
def test_height_range_fit_preview_above def test_height_range_fit_preview_above
tmux.send_keys 'seq 3 | fzf --height ~100% --info=inline --border --preview "seq {}" --preview-window up,60%', :Enter tmux.send_keys 'seq 3 | fzf --height ~100% --info=inline --border rounded --preview-window border-rounded --preview "seq {}" --preview-window up,60%', :Enter
expected = <<~OUTPUT expected = <<~OUTPUT
@@ -2685,7 +2789,7 @@ class TestGoFZF < TestBase
end end
def test_height_range_overflow def test_height_range_overflow
tmux.send_keys 'seq 100 | fzf --height ~5 --info=inline --border', :Enter tmux.send_keys 'seq 100 | fzf --height ~5 --info=inline --border rounded', :Enter
expected = <<~OUTPUT expected = <<~OUTPUT
2 2
@@ -2750,7 +2854,7 @@ class TestGoFZF < TestBase
end end
def test_labels_left def test_labels_left
tmux.send_keys ': | fzf --border --border-label foobar --border-label-pos 2 --preview : --preview-label barfoo --preview-label-pos 2', :Enter tmux.send_keys ': | fzf --border rounded --preview-window border-rounded --border-label foobar --border-label-pos 2 --preview : --preview-label barfoo --preview-label-pos 2', :Enter
tmux.until do tmux.until do
assert_includes(_1[0], '╭foobar─') assert_includes(_1[0], '╭foobar─')
assert_includes(_1[1], '╭barfoo─') assert_includes(_1[1], '╭barfoo─')
@@ -2758,7 +2862,7 @@ class TestGoFZF < TestBase
end end
def test_labels_right def test_labels_right
tmux.send_keys ': | fzf --border --border-label foobar --border-label-pos -2 --preview : --preview-label barfoo --preview-label-pos -2', :Enter tmux.send_keys ': | fzf --border rounded --preview-window border-rounded --border-label foobar --border-label-pos -2 --preview : --preview-label barfoo --preview-label-pos -2', :Enter
tmux.until do tmux.until do
assert_includes(_1[0], '─foobar╮') assert_includes(_1[0], '─foobar╮')
assert_includes(_1[1], '─barfoo╮') assert_includes(_1[1], '─barfoo╮')
@@ -2766,13 +2870,34 @@ class TestGoFZF < TestBase
end end
def test_labels_bottom def test_labels_bottom
tmux.send_keys ': | fzf --border --border-label foobar --border-label-pos 2:bottom --preview : --preview-label barfoo --preview-label-pos -2:bottom', :Enter tmux.send_keys ': | fzf --border rounded --preview-window border-rounded --border-label foobar --border-label-pos 2:bottom --preview : --preview-label barfoo --preview-label-pos -2:bottom', :Enter
tmux.until do tmux.until do
assert_includes(_1[-1], '╰foobar─') assert_includes(_1[-1], '╰foobar─')
assert_includes(_1[-2], '─barfoo╯') assert_includes(_1[-2], '─barfoo╯')
end end
end end
def test_labels_variables
tmux.send_keys ': | fzf --border --border-label foobar --preview "echo \$FZF_BORDER_LABEL // \$FZF_PREVIEW_LABEL" --preview-label barfoo --bind "space:change-border-label(barbaz)+change-preview-label(bazbar)+refresh-preview,enter:transform-border-label(echo 123)+transform-preview-label(echo 456)+refresh-preview"', :Enter
tmux.until do
assert_includes(_1[0], '─foobar─')
assert_includes(_1[1], '─barfoo─')
assert_includes(_1[2], ' foobar // barfoo ')
end
tmux.send_keys :Space
tmux.until do
assert_includes(_1[0], '─barbaz─')
assert_includes(_1[1], '─bazbar─')
assert_includes(_1[2], ' barbaz // bazbar ')
end
tmux.send_keys :Enter
tmux.until do
assert_includes(_1[0], '─123─')
assert_includes(_1[1], '─456─')
assert_includes(_1[2], ' 123 // 456 ')
end
end
def test_info_separator_unicode def test_info_separator_unicode
tmux.send_keys 'seq 100 | fzf -q55', :Enter tmux.send_keys 'seq 100 | fzf -q55', :Enter
tmux.until { assert_includes(_1[-2], ' 1/100 ─') } tmux.until { assert_includes(_1[-2], ' 1/100 ─') }
@@ -2895,7 +3020,7 @@ class TestGoFZF < TestBase
end end
def test_no_extra_newline_issue_3209 def test_no_extra_newline_issue_3209
tmux.send_keys(%(seq 100 | #{FZF} --height 10 --preview-window up,wrap --preview 'printf "─%.0s" $(seq 1 "$((FZF_PREVIEW_COLUMNS - 5))"); printf $"\\e[7m%s\\e[0m" title; echo; echo something'), :Enter) tmux.send_keys(%(seq 100 | #{FZF} --height 10 --preview-window up,wrap,border-rounded --preview 'printf "─%.0s" $(seq 1 "$((FZF_PREVIEW_COLUMNS - 5))"); printf $"\\e[7m%s\\e[0m" title; echo; echo something'), :Enter)
expected = <<~OUTPUT expected = <<~OUTPUT
@@ -2976,7 +3101,7 @@ class TestGoFZF < TestBase
end end
tmux.send_keys :t tmux.send_keys :t
tmux.until do |lines| tmux.until do |lines|
assert_includes lines[-2], '+T' assert_includes lines[-2], '+t'
end end
tmux.send_keys :BSpace tmux.send_keys :BSpace
tmux.until do |lines| tmux.until do |lines|
@@ -2988,7 +3113,7 @@ class TestGoFZF < TestBase
tmux.send_keys '4' tmux.send_keys '4'
tmux.until do |lines| tmux.until do |lines|
assert_equal 28, lines.match_count assert_equal 28, lines.match_count
refute_includes lines[-2], '+T' refute_includes lines[-2], '+t'
end end
tmux.send_keys :BSpace tmux.send_keys :BSpace
tmux.until do |lines| tmux.until do |lines|
@@ -2997,11 +3122,11 @@ class TestGoFZF < TestBase
end end
tmux.send_keys :t tmux.send_keys :t
tmux.until do |lines| tmux.until do |lines|
assert_includes lines[-2], '+T' assert_includes lines[-2], '+t'
end end
tmux.send_keys :Up tmux.send_keys :Up
tmux.until do |lines| tmux.until do |lines|
refute_includes lines[-2], '+T' refute_includes lines[-2], '+t'
end end
end end
@@ -3056,6 +3181,11 @@ class TestGoFZF < TestBase
end end
def test_delete_with_modifiers def test_delete_with_modifiers
if ENV['GITHUB_ACTION']
# Expected: "[3]"
# Actual: "[]3;5~"
skip('CTRL-DELETE is not properly handled in GitHub Actions environment')
end
tmux.send_keys "seq 100 | #{FZF} --bind 'ctrl-delete:up+up,shift-delete:down,focus:transform-prompt:echo [{}]'", :Enter tmux.send_keys "seq 100 | #{FZF} --bind 'ctrl-delete:up+up,shift-delete:down,focus:transform-prompt:echo [{}]'", :Enter
tmux.until { |lines| assert_equal 100, lines.item_count } tmux.until { |lines| assert_equal 100, lines.item_count }
tmux.send_keys 'C-Delete' tmux.send_keys 'C-Delete'
@@ -3276,7 +3406,10 @@ module CompletionTest
tmux.send_keys 'cat /tmp/fzf\ test/**', :Tab tmux.send_keys 'cat /tmp/fzf\ test/**', :Tab
tmux.until { |lines| assert_operator lines.match_count, :>, 0 } tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
tmux.send_keys 'foobar$' tmux.send_keys 'foobar$'
tmux.until { |lines| assert_equal 1, lines.match_count } tmux.until do |lines|
assert_equal 1, lines.match_count
assert lines.any_include?('> /tmp/fzf test/foobar')
end
tmux.send_keys :Enter tmux.send_keys :Enter
tmux.until(true) { |lines| assert_equal 'cat /tmp/fzf\ test/foobar', lines[-1] } tmux.until(true) { |lines| assert_equal 'cat /tmp/fzf\ test/foobar', lines[-1] }

View File

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