Compare commits

...

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

An example:

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

A known issue:

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

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

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

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

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

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

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

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

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

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

If not defined, fall back to the previous code.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

---------

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

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

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

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

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

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

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

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

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

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

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

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

* Example:

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

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

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

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

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

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

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

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

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

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

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

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

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

Related: #3295, #3309, #3310.

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

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

**Performance comparison:**

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

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

**Other Notes**

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	Limit scope on which option is set to bare minimum.

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

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

---------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-23 23:55:57 +09:00
dependabot[bot]
5620f70f9a Bump crate-ci/typos from 1.13.16 to 1.14.10 (#3306)
Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.13.16 to 1.14.10.
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crate-ci/typos/compare/v1.13.16...v1.14.10)

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

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

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

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

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

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

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-21 16:53:20 +09:00
dependabot[bot]
290ea6179d Bump golang.org/x/term from 0.0.0-20210927222741-03fcf44c2211 to 0.6.0 (#3203)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.0.0-20210927222741-03fcf44c2211 to 0.6.0.
- [Release notes](https://github.com/golang/term/releases)
- [Commits](https://github.com/golang/term/commits/v0.6.0)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* Apply suggestions from code review

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

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

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

* Update .github/workflows/depsreview.yaml

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

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

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

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

* Apply suggestions from code review

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

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

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

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

* Apply suggestions from code review

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* Update .github/workflows/macos.yml

* Update .github/workflows/linux.yml

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* Support for various bash declare options

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

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

This change makes ANSI parsing slightly slower.

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

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

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

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

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

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

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

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

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

Fix https://github.com/junegunn/fzf.vim/issues/1411
2022-07-30 19:08:04 +09:00
Junegunn Choi
3c08dca7e7 Fix README examples so that they work both on bash and zsh
Close #2887
2022-07-29 22:03:02 +09:00
Carl Kamholtz
d083f01d22 [vim] Add option to force 24 bit colors on Windows (#2889) 2022-07-29 21:51:35 +09:00
Bob Matcuk
68cf393644 [bash] Fix 'possible retry loop' problem of bash-completion (#2891)
Close #2474
Close #2583
2022-07-29 21:50:59 +09:00
Junegunn Choi
18f7230662 Fix mouse location in --height mode
Fix #2900
2022-07-29 15:42:44 +09:00
Carl Kamholtz
728f735281 [vim] Fix version check on windows/powershell (#2894)
- Replace fzf#shellescape with shellescape
- Prepend command with '&' in powershell to deal with quoted exe
2022-07-29 08:18:06 +09:00
Junegunn Choi
ecc418ba77 0.31.0 2022-07-21 23:04:57 +09:00
Junegunn Choi
3af5b7f2ac Do not validate other options when --version is present
Close #2690
2022-07-21 22:36:52 +09:00
Junegunn Choi
7a7cfcacbe Lift unicode.IsGraphic constraint for pointer, marker, and ellipsis
Use at your own risk.

Close #2709
Close #2055
2022-07-21 22:30:01 +09:00
Junegunn Choi
52594355bf [shell] 'kill' completion will now require trigger sequence (**)
'kill **<tab>' instead of 'kill <tab>' just like any other completions.

Close #2716
Close #385
2022-07-21 22:21:11 +09:00
Junegunn Choi
0d06c28b19 Fix delimiter regex to properly support caret (^)
Fix #2861
2022-07-21 21:21:06 +09:00
Junegunn Choi
ccc4677252 [vim] fzf#exec: Shell-escape fzf binary path
Fix #2877
2022-07-20 14:29:52 +09:00
Junegunn Choi
821fc9feed Fix failing test case 2022-07-20 12:29:45 +09:00
Junegunn Choi
82b46726fc Add support for an alternative preview window layout
Close #2804
Close #2844

Related #2277
2022-07-20 12:08:54 +09:00
Jakub Jirutka
8df872a482 [zsh] Replace perl with awk (#2777)
Unlike awk, which is even defined in POSIX, perl is not pre-installed
on all *nix systems. This awk command is functionally equivalent to
the original perl command.
2022-07-20 11:53:34 +09:00
Jonathan Zacsh
c79c306adb [bash] Fix fzf-tmux to have fzf's completion (#2871) 2022-07-15 15:53:23 +09:00
Junegunn Choi
51fdaad002 [uninstall] Remove readlink to support relative symlinks of dotfiles
Close #2853
2022-07-01 16:32:32 +09:00
znley
885cd8ff04 [make] Add loongarch64 support (#2857) 2022-06-28 09:16:26 +09:00
Tanish Yadav
2707af403a [shell] Don't export PATH in ~/.fzf.{bash,zsh} (#2852)
There is no use exporting PATH when it is already exported. Moreover, it
causes things like `typeset -U path` in zsh to break if done before
sourcing "~/.fzf.zsh".
2022-06-24 16:53:51 +09:00
Junegunn Choi
2d227e5222 [man] Fix unescaped backslash in awk example
Close #2854
2022-06-23 15:45:36 +09:00
Junegunn Choi
70529878e2 Use SGR mouse mode for larger terminals
Fix #2840
2022-06-17 12:48:04 +09:00
Daniel Zhang
3b7a962dc6 [vim] Fix fzf#shellescape when shell=fish (#2828)
`shellescape()` behavior is different when `shell=fish`, so we should set `shell` before calling `shellescape()`, otherwise an unexpected result may occur (e.g. https://github.com/kevinhwang91/nvim-bqf/issues/56).

Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2022-05-25 09:50:10 +09:00
Jan Warchoł
6dcf5c3d7d [bash] Make complex commands slightly more friendly to work with (#2784)
- extract logical parts to separate variables (e.g. $opts)
- put options in $opts in similar order
- move +/-m into $opts (at the end, so they won't be overridden)
- split pipelines into multiple lines
- remove "echo" that seems to be redundant

All this should help with readability and also result in cleaner diffs
when changes are made.
2022-04-29 19:04:16 +09:00
Junegunn Choi
b089bb5e7b Fix scrollability of the preview window in certain cases
Fix #2683

This commit fixes the cases where fzf incorrectly determines the
scrollability of the preview window when `--preview-window-wrap` is set.

Wrapping of the preview content happens during the rendering phase, so
it's currently not possible to know how many lines are actually needed
to display the content beforehand. So `preview-bottom` still may not
move to the very bottom with wrapping enabled.
2022-04-28 11:46:24 +09:00
dependabot[bot]
a91a67668e Bump github/codeql-action from 2.1.6 to 2.1.8 (#2787)
* Bump github/codeql-action from 2.1.6 to 2.1.8

Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2.1.6 to 2.1.8.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](28eead2408...1ed1437484)

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

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

* Delete incorrect comments

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2022-04-22 22:36:56 +09:00
dependabot[bot]
220a908118 Bump ruby/setup-ruby from 1.100.0 to 1.101.0 (#2795)
* Bump ruby/setup-ruby from 1.100.0 to 1.101.0

Bumps [ruby/setup-ruby](https://github.com/ruby/setup-ruby) from 1.100.0 to 1.101.0.
- [Release notes](https://github.com/ruby/setup-ruby/releases)
- [Commits](bd94d6a504...ebaea52cb2)

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

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

* Delete incorrect comments

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2022-04-22 22:35:51 +09:00
Ajeet D'Souza
54841248e7 [shell] ALT-C: Use builtin cd to avoid conflicts (#2799) 2022-04-22 21:58:32 +09:00
Junegunn Choi
a0b42e6538 Require Go 1.17 or above 2022-04-07 20:31:19 +09:00
Jan Warchoł
3312cf525d [bash] Allow passing args to __fzf_select__ via fzf-file-widget (#2783)
This makes it easier to make customizations, for example instead of

    bind -x '"\C-o\C-i": FZF_CTRL_T_COMMAND="fasd -Rl" FZF_DEFAULT_OPTS="$FZF_DEFAULT_OPTS --tiebreak=index " fzf-file-widget'

it's enough to just

    bind -x '"\C-o\C-i": FZF_CTRL_T_COMMAND="fasd -Rl" fzf-file-widget --tiebreak=index'
2022-04-06 11:29:01 +09:00
Junegunn Choi
2093667548 0.30.0 2022-04-04 23:01:43 +09:00
Junegunn Choi
3c868d7961 ADVANCED.md: Add rebind example 2022-04-04 22:59:26 +09:00
dependabot[bot]
707f4f5816 Bump github/codeql-action from 1.1.5 to 2.1.6 (#2782)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 1.1.5 to 2.1.6.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](8834766498...28eead2408)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-04 22:44:10 +09:00
Junegunn Choi
b3ab6311c5 Hide cursor while rendering the screen
Fix #2781
Fix #2588
Fix #1805

Fix https://github.com/junegunn/fzf.vim/issues/1370
Fix https://github.com/junegunn/fzf.vim/issues/1060
2022-04-04 22:06:16 +09:00
Junegunn Choi
d56f605b63 Add rebind action for restoring bindings after unbind
Fix #2752
Close #2564
2022-04-04 21:54:22 +09:00
Junegunn Choi
f8b713f425 Remove redundant state update on reload
Related: 5209e95
2022-03-31 10:05:28 +09:00
Junegunn Choi
5209e95bc7 Make preview updated when reload and change-query are combined
Fix #2744
2022-03-29 22:27:03 +09:00
Junegunn Choi
ef67a45702 Add --ellipsis=.. option
Close #2432

Also see
- #1769
- https://github.com/junegunn/fzf/pull/1844#issuecomment-586663660
2022-03-29 21:35:36 +09:00
Junegunn Choi
b88eb72ac2 Modernize build tags 2022-03-29 21:23:45 +09:00
dependabot[bot]
32847f7254 Bump actions/setup-go from 2.2.0 to 3 (#2776)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 2.2.0 to 3.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](bfdd3570ce...f6164bd8c8)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-29 21:17:44 +09:00
dependabot[bot]
71df93b534 Bump ruby/setup-ruby from 1.62.0 to 1.100.0 (#2775)
Bumps [ruby/setup-ruby](https://github.com/ruby/setup-ruby) from 1.62.0 to 1.100.0.
- [Release notes](https://github.com/ruby/setup-ruby/releases)
- [Commits](5aaa89ff0d...bd94d6a504)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-29 21:17:29 +09:00
Naveen
bb028191f8 Set up dependabot for GitHub actions (#2764) 2022-03-29 21:08:41 +09:00
Naveen
19af8fc7d8 Pin actions to a full length commit SHA (#2765)
- Pinned actions by SHA https://github.com/ossf/scorecard/blob/main/docs/checks.md#pinned-dependencies
- Included permissions for the action. https://github.com/ossf/scorecard/blob/main/docs/checks.md#token-permissions

>Pin actions to a full length commit SHA

>Pinning an action to a full length commit SHA is currently the only way to use an action as an immutable release. Pinning to a particular SHA helps mitigate the risk of a bad actor adding a backdoor to the action's repository, as they would need to generate a SHA-1 collision for a valid Git object payload.

https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-third-party-actions

Also, dependabot supports upgrade based on SHA.

Signed-off-by: naveensrinivasan <172697+naveensrinivasan@users.noreply.github.com>s
2022-03-29 21:08:01 +09:00
Junegunn Choi
a06671b47f Increase TTY buffer limit
Kitty's shell intergration generates a long sequence of key presses in
certain cases. As long as the length of the sequence is finite, fzf can
process it.

Close #2748
2022-03-09 17:02:06 +09:00
Junegunn Choi
5f385d88e0 [zsh] Set up bindings for all three keymaps: emacs, vicmd, and viins
Fix #2694
2022-02-23 15:36:49 +09:00
Junegunn Choi
9cb7a364a3 [install] Remove code that might delete user fish script
Fix #2703
2022-01-05 21:48:20 +09:00
Junegunn Choi
f68cbc577d Add link to ADVANCED.md
Related #2701
2022-01-03 13:52:46 +09:00
Junegunn Choi
dc975e8974 0.29.0 2021-12-25 01:46:01 +09:00
Junegunn Choi
4311ade535 ADVANCED.md: Add change-preview-window example 2021-12-24 14:41:47 +09:00
Junegunn Choi
cd23401411 Fix rendering of the prompt line when overflow occurs with --info=inline
Fix #2692
2021-12-22 23:23:50 +09:00
Martin Jindra
176ee6910f Update Dockerfile (#2662)
`archlinux/base:latest`cannot be found
2021-12-09 11:05:12 +09:00
Junegunn Choi
13c8f3d3aa [vim] Handle writefile() failure gracefully
Fix #2676
2021-12-07 17:33:26 +09:00
Junegunn Choi
ce9af687bc Remove unused code 2021-12-05 21:17:38 +09:00
Junegunn Choi
43f0d0cacd change-preview-window to take multiple option sets separated by '|'
So you can "rotate" through the different options with a single binding.

  fzf --preview 'cat {}' \
      --bind 'ctrl-/:change-preview-window(70%|down,40%,border-horizontal|hidden|)'

Close #2376
2021-12-05 21:13:10 +09:00
Junegunn Choi
20b4e6953e Implement change-preview and change-preview-window actions
The new actions are named with 'change-' prefix to differentiate from
the pre-existing, one-off 'preview(...)' action.

Fix #2360
Fix #2505
Fix #2666

Related #2435
Related #2376
  - Can set up multiple bindings with different change-preview-window actions
  - Not possible to "rotate" through the options with a single binding
  - Enlarge or shrink not possible
2021-11-30 23:57:46 +09:00
Kai
7da287e3aa README.md: HTTP => HTTPS (#2673) 2021-11-28 22:28:32 +09:00
zsugabubus
205f885d69 [shell] Use cd -- (#2659)
Otherwise directories starting with '-' may treated as options.
2021-11-19 10:36:28 +09:00
Junegunn Choi
3715cd349d Add repology packaging status badge 2021-11-18 15:47:06 +09:00
Junegunn Choi
e4c3ecc57e 0.28.0 2021-11-04 01:05:07 +09:00
Junegunn Choi
673c5d886d Add 'put' action for putting the character to the prompt
fzf --bind 'space:preview(date)+put'

Close #2456
2021-11-04 00:49:05 +09:00
Junegunn Choi
f799b568d1 [bash] Suppress error message from 'bind'
Fix #2618
2021-11-03 23:26:25 +09:00
Junegunn Choi
7bff4661f6 Add --header-first option to display header before prompt line
Close #2422
2021-11-03 21:19:22 +09:00
Junegunn Choi
ffd8bef808 Update CHANGELOG 2021-11-02 21:48:19 +09:00
Junegunn Choi
02cee2234d Implement --scroll-off=LINES
Close #2533
2021-11-02 21:48:19 +09:00
Vlastimil Ovčáčík
e0dd2be3fb Document escaping and expanding of quotes on Windows
Parsers included:
- go parser (well, this is easily dealt with using `` strings)
- win32 (shell-api) parser
- powershell parser (for powershell commands)
- powershell parsing rules for calling native commands
- internal parsers of select regex applications (like grep)
2021-11-02 15:56:20 +09:00
Vlastimil Ovčáčík
a33c011c21 Test escaping of powershell commands on Windows 2021-11-02 15:56:20 +09:00
Rashil Gandhi
7c3f42bbba Fix powershell escaping 2021-11-02 15:56:20 +09:00
Junegunn Choi
edac9820b5 Cache cygpath result
No need to repeatedly run cygpath process because $SHELL never changes.
2021-10-25 18:46:59 +09:00
Rashil Gandhi
84a47f7102 Respect SHELL env var on Windows (#2641)
This makes fzf respect SHELL environment variable on Windows, like it does on *nix, whenever defined.

Close #2638
2021-10-23 01:09:47 +09:00
Junegunn Choi
97ae8afb6f Reload should update preview window
Fix #2644
2021-10-23 01:06:15 +09:00
Junegunn Choi
4138333f5c 0.27.3 2021-10-15 23:59:56 +09:00
Vlastimil Ovčáčík
61339a8ae2 Add more tests of placeholder flags and simplify its logic (#2624)
* [tests] Test fzf's placeholders and escaping on practical commands

This tests some reasonable commands in fzf's templates (for commands,
previews, rebinds etc.), how are those commands escaped (backslashes,
double quotes), and documents if the output is executable in cmd.exe.
Both on Unix and Windows.

* [tests] Add testing of placeholder parsing and matching

Adds tests and bit of docs for the curly brackets placeholders in fzf's
template strings. Also tests the "placeholder" regex.

* [tests] Add more test cases of replacing placeholders focused on flags

Replacing placeholders in templates is already tested, this adds tests
that focus more on the parameters of placeholders - e.g. flags, token
ranges.

There is at least one test for each flag, not all combinations are
tested though.

* [refactoring] Split OS-specific function quoteEntry() to corresponding source file

This is minor refactoring, and also the function's test was made
crossplatform.

* [refactoring] Simplify replacePlaceholder function

Should be equivalent to the original, but has simpler structure.
2021-10-15 22:31:59 +09:00
Junegunn Choi
50eb2e3855 Render spinner on info line during "reload"
Fix #2637
2021-10-15 22:13:57 +09:00
Xeonacid
5fc78e4584 Add riscv64 build target (#2626)
Build successfully on Arch Linux RISC-V.
2021-10-10 21:33:06 +09:00
Junegunn Choi
2736a2f69e [vim] Empty out $FZF_DEFAULT_COMMAND before unletting it
For Vim 8.0.1831 and below
* https://github.com/vim/vim/issues/1116

Fix https://github.com/junegunn/fzf.vim/issues/1301
2021-10-10 20:19:48 +09:00
Vlastimil Ovčáčík
179993f0cd Enable manually trigger on GitHub Workflows (#2620) 2021-10-04 21:41:54 +09:00
dependabot[bot]
b734f657f9 Bump github.com/mattn/go-isatty from 0.0.12 to 0.0.14 (#2612)
Bumps [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) from 0.0.12 to 0.0.14.
- [Release notes](https://github.com/mattn/go-isatty/releases)
- [Commits](https://github.com/mattn/go-isatty/compare/v0.0.12...v0.0.14)

---
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>
2021-10-04 21:13:46 +09:00
dependabot[bot]
c29d7d02c2 Bump github.com/mattn/go-shellwords from 1.0.11 to 1.0.12 (#2592)
Bumps [github.com/mattn/go-shellwords](https://github.com/mattn/go-shellwords) from 1.0.11 to 1.0.12.
- [Release notes](https://github.com/mattn/go-shellwords/releases)
- [Commits](https://github.com/mattn/go-shellwords/compare/v1.0.11...v1.0.12)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-shellwords
  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>
2021-10-04 21:12:40 +09:00
dependabot[bot]
3df6b2a58c Bump github.com/mattn/go-runewidth from 0.0.12 to 0.0.13 (#2591)
Bumps [github.com/mattn/go-runewidth](https://github.com/mattn/go-runewidth) from 0.0.12 to 0.0.13.
- [Release notes](https://github.com/mattn/go-runewidth/releases)
- [Commits](https://github.com/mattn/go-runewidth/compare/v0.0.12...v0.0.13)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-04 21:01:03 +09:00
Vlastimil Ovčáčík
b8aa2d2c32 Minor refactoring tcell library from tui.go to tcell.go
To prevent including tcell library in non-windows builds.
2021-10-03 01:39:30 +09:00
Vlastimil Ovčáčík
0ff885461b Add mouse support to the FullscreenRenderer 2021-10-03 01:39:30 +09:00
Vlastimil Ovčáčík
ca43f95fb1 Fix Backspace key to emit BSpace and AltBS events instead of CtrlH
CtrlH events are still sent when appropriate. I have adjusted
FullscreenRenderer to match the LightRenderer's behaviour, which seems
to be correct.
2021-10-03 01:39:30 +09:00
Vlastimil Ovčáčík
09700f676b Add CtrlCaret keyboard event to FullscreenRenderer 2021-10-03 01:39:30 +09:00
Vlastimil Ovčáčík
4271e9cffa Fix Ctrl+Space key combination to emit CtrlSpace instead of Rune ' ' 2021-10-03 01:39:30 +09:00
Vlastimil Ovčáčík
f3dc8a10d5 Add ability to type AltGr characters in FullscreenRenderer on Windows. 2021-10-03 01:39:30 +09:00
Vlastimil Ovčáčík
00fb486f6a [tests] Add testing of keyboard events in FullscreenRenderer.GetChar()
This contains one test case of each tcell.Key* event type that can be
sent to and subsequently processed in fzf's GetChar(). The test cases
describe status quo, and all of them PASS.

Small function util.ToTty() was added. It is similar to util.IsTty(),
but for stdout (hence the To preposition).
2021-10-03 01:39:30 +09:00
Junegunn Choi
4173e94c6f Do not check for --height support on --version
https://github.com/junegunn/fzf.vim/issues/1329
2021-09-29 20:17:44 +09:00
Hiroki Konishi
261d3d3340 fix: replace broken links with archived ones 2021-09-28 18:07:22 +09:00
Hiroki Konishi
15e20fcae1 fix: spelling Refence -> Reference 2021-09-28 18:07:22 +09:00
Vlastimil Ovčáčík
f4f47f5fe3 Minor changes
- obsolete todo removed, I tested the ev.ch for " " char and it works just
fine
2021-09-24 16:04:36 +09:00
Vlastimil Ovčáčík
71d11de7ca [tests] Change tests to output to stdout only with verbose flag
This hides stdout output unless "go test -v" was run.
2021-09-24 16:04:36 +09:00
Vlastimil Ovčáčík
88d74a15aa Change the tests to run on Windows (#2615)
Most of the "expected" strings in terminal.go test were changed to
"text/template" values. Quotes in those string were parametrized in
the templates. Two functions handling templates were added
for convenience.

Templates has the advantage of:
- parametrize repetitive strings inside "expected" values
  - inner and outer quotes were parametrized in templates
  - long and confusing test values are more readable
- templates can be localized for other operating systems
2021-09-24 09:45:06 +09:00
Junegunn Choi
0f02fc0c77 Reset {n} after reload
Fix #2611
2021-09-14 20:36:10 +09:00
Keating950
3f90fb42d8 Fix spelling error (Extention -> Extension) (#2589) 2021-08-17 16:40:24 +09:00
Daniel Bast
9bd8988300 Add dependabot config for dependency updates (#2573) 2021-08-17 16:27:50 +09:00
a1346054
3c804bcfec fix spelling 2021-08-15 16:03:26 +09:00
a1346054
cca4cdc4f1 improve test logic and be explicit about the test 2021-08-15 16:03:26 +09:00
a1346054
8f899aaf8a use proper bash-style notation 2021-08-15 16:03:26 +09:00
a1346054
e53b4bb439 always use [[ ... ]] and not [ ... ] in bash completions 2021-08-15 16:03:26 +09:00
a1346054
ab247a1309 use consistent style for bash [[ ... ]] 2021-08-15 16:03:26 +09:00
Michael Kelley
c21e9edad4 Restore VT hack for Windows (#2580)
- restore VT enable hack
- resolve an issue reported in https://github.com/kelleyma49/PSFzf
2021-08-15 16:01:50 +09:00
Leon Tepe
9c21a20f8b Minor readme change (#2578)
`CTRL-K` moves up and `CTRL-J` moves down, not the other way around (same for `CTRL-P` and `CTRL-N`
2021-08-15 15:57:07 +09:00
Junegunn Choi
7191ebb615 Do not show preview window by default if --preview is empty
Close #2516
2021-06-08 08:53:29 +09:00
Junegunn Choi
a74731d7f5 [vim] Add 'sinklist' as a synonym to 'sink*'
So that it's easier to add a sinklist function to a spec dictionary.

  let spec = { 'source': source, 'options': ['--preview', preview] }
  function spec.sinklist(matches)
    echom string(a:matches)
  endfunction

  call fzf#run(fzf#wrap(spec))
2021-06-04 22:07:29 +09:00
Junegunn Choi
e086f0b3fe 0.27.2 2021-06-01 17:00:24 +09:00
Junegunn Choi
8255aa23f4 Fix bug where --read0 not properly displaying long lines
Fix #2508
2021-06-01 16:55:51 +09:00
Junegunn Choi
a4bc08f5a3 Allow specifying 16 base ANSI colors by their names
Close #2502
2021-05-26 19:35:26 +09:00
Thomas Klausner
7e5aa1e2a5 Mention NetBSD package and how to install it (#2499)
Close #2487
2021-05-23 11:31:55 +09:00
Junegunn Choi
0818dbc36a 0.27.1 2021-05-22 13:19:57 +09:00
Junegunn Choi
347c4b2625 Add 'unbind' action
Fix #2486
2021-05-22 13:16:39 +09:00
Junegunn Choi
34f0d4d0c4 [man] Clarification on --select-1 and --exit-0 2021-05-22 09:47:02 +09:00
Junegunn Choi
cbedb57511 [vim] Workaround for Neovim bug of unconditionally evaluating unlet $ENV_VAR
See #2495
2021-05-21 13:17:19 +09:00
Junegunn Choi
9ef825d2fd [vim] Update README-VIM 2021-05-19 18:43:50 +09:00
Junegunn Choi
85ae745910 [vim] Use terminal buffer on 'down' layout on regular Vim on terminal
When 'down' layout was used on regular Vim on terminal, fzf would open
below the editor using `--height` option. This was the only case where
terminal buffer was not used (the code was written when Vim didn't have
builtin terminal) and this exception has been a constant source of
confusion.

This commit makes fzf open in a terminal buffer even in that case.
2021-05-19 18:39:12 +09:00
Junegunn Choi
7411da8d5a [vim] Use FZF_DEFAULT_COMMAND instead of STDIN pipe
So that fzf can finish immediately even when the input process doesn't
handle SIGPIPE and keeps running.

Fix #2481
2021-05-19 18:37:13 +09:00
Junegunn Choi
3f75a8369f Replace RuneWidth to StringWidth to handle grapheme clusters
Fix #2482
2021-05-14 11:44:44 +09:00
Junegunn Choi
4cd621e877 ADVANCED.md: tmux 3.2 is officially released 2021-04-29 09:37:17 +09:00
Junegunn Choi
6e3a2fe0bf [vim] Fix screen offset of relatively positioned popup window
Fix #2461
2021-04-28 10:17:46 +09:00
Tom Picton
8b0e1f941a [vim] Support relative-to-window positioning of popup (#2443)
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2021-04-17 20:48:10 +09:00
Junegunn Choi
c7c5e7670a Fix goreleaser.yml 2021-04-10 14:19:19 +09:00
Junegunn Choi
f6c621ef1b Update ADVANCED.md
Remove unnecessary --color option
2021-04-09 22:58:31 +09:00
Junegunn Choi
faf32d451d Update ADVANCED.md 2021-04-09 14:14:53 +09:00
Junegunn Choi
252fd7ecb1 Update ADVANCED.md 2021-04-09 13:43:16 +09:00
Junegunn Choi
7fa89dddb4 Update README.md: Examples page 2021-04-08 10:06:26 +09:00
Junegunn Choi
fefdb8c84e Fix typo 2021-04-07 13:28:26 +09:00
Junegunn Choi
a6cc05936e ADVANCED.md: Clarification on {q} 2021-04-07 07:12:39 +09:00
Junegunn Choi
b209843545 Advanced fzf examples 2021-04-07 06:46:17 +09:00
Junegunn Choi
19759ed36e 0.27.0 2021-04-06 22:53:59 +09:00
Junegunn Choi
1a7ae8e7b9 Update dependencies
go get: upgraded github.com/lucasb-eyer/go-colorful v1.0.3 => v1.2.0
go get: upgraded github.com/mattn/go-runewidth v0.0.9 => v0.0.12
go get: upgraded github.com/mattn/go-shellwords v1.0.10 => v1.0.11
go get: added github.com/rivo/uniseg v0.2.0
go get: upgraded github.com/saracen/walker v0.1.1 => v0.1.2
go get: upgraded golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 => v0.0.0-20210220032951-036812b2e83c
go get: upgraded golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 => v0.0.0-20210403161142-5e06dd20ab57
go get: upgraded golang.org/x/text v0.3.3 => v0.3.6
2021-04-06 20:32:18 +09:00
Junegunn Choi
da1f645670 Change --preview-window delimiter from : to , for consistency
Delimiter : was chosen when --preview-option only supported position and
size attributes. e.g. up:50%
2021-04-06 20:10:55 +09:00
Junegunn Choi
3a2015ee26 Fix minimum preview window height 2021-04-06 20:05:54 +09:00
Junegunn Choi
c440418ce6 Sign and notarize macOS binaries
Close #2408
2021-04-06 18:09:06 +09:00
Junegunn Choi
3d37a5ba1d Apply preview-bg color to preview border of all shapes 2021-04-06 18:01:29 +09:00
Junegunn Choi
15f4cfb6d9 More border optins for preview window
Close #2431
2021-04-06 17:37:11 +09:00
Junegunn Choi
be36de2482 Ignore more ANSI escape sequences
Fix #2420
2021-04-06 00:51:39 +09:00
Junegunn Choi
391237f7df [vim] Compare binary versions
Close #2410
2021-04-06 00:24:20 +09:00
Junegunn Choi
977e5effd9 [vim] Fix paste on MacVim
Close https://github.com/junegunn/fzf.vim/issues/1233
2021-04-05 17:28:18 +09:00
Junegunn Choi
8b36a4cb19 Speed up preview switching when doing partial rendering
Fix #2417
2021-04-04 13:43:16 +09:00
Michael Kelley
c8cd94a772 Ensure proper ESC seq handling under Windows preview mode (#2430)
- Increase go routine buffer size
- Add time wait for nonblock getchr()
- Resolve #2429
2021-04-04 13:19:43 +09:00
Junegunn Choi
764316a53d Fix flaky test case: test_interrupt_execute
Try to avoid extraneous INT signal
2021-03-26 17:40:12 +09:00
Philipp Schmitt
2048fd4042 Update README (--phony -> --disabled) (#2404) 2021-03-25 20:36:01 +09:00
Junegunn Choi
f84b3de24b Automatically set /dev/tty as STDIN on execute action
https://github.com/junegunn/fzf/issues/1360#issuecomment-788178140

  # Redirect /dev/tty to suppress "Vim: Warning: Input is not from a terminal"
  ls | fzf --bind "enter:execute(vim {} < /dev/tty)"

  # With this change, we can omit "< /dev/tty" part
  ls | fzf --bind "enter:execute(vim {})"
2021-03-25 20:00:09 +09:00
Junegunn Choi
6a1f3ec08b [install] Download Darwin arm64 binary (#2400 #2401) 2021-03-25 10:56:21 +09:00
Mitsuo Heijo
2e353aee96 Replace golang.org/x/crypto/ssh/terminal with golang.org/x/term (#2395)
See https://github.com/golang/go/issues/31044
2021-03-20 14:38:34 +09:00
Mitsuo Heijo
8edfd14a37 Test against Golang 1.14 and 1.16 (#2396)
1.14 for 32-bit binaries
2021-03-20 12:32:44 +09:00
Junegunn Choi
1a191ec6f7 Update FUNDING.yml 2021-03-14 12:03:11 +09:00
Junegunn Choi
e7171e94b4 Update FUNDING.yml 2021-03-14 12:01:57 +09:00
Junegunn Choi
398d937419 Create FUNDING.yml 2021-03-14 11:59:56 +09:00
Junegunn Choi
34fe5ab143 0.26.0 2021-03-13 15:13:31 +09:00
Junegunn Choi
1b08f43f82 Advanced preview scroll offset expression to better support fixed header 2021-03-13 02:26:41 +09:00
Junegunn Choi
b24a2e2fdc Fix regression in preview window rendering 2021-03-12 21:23:16 +09:00
Junegunn Choi
4c4c6e626e Add support for preview window header
Fix #2373

  # Display top 3 lines as the fixed header
  fzf --preview 'bat --style=header,grid --color=always {}' --preview-window '~3'
2021-03-12 20:32:27 +09:00
Junegunn Choi
7310370a31 Fix truncation of colored line when --preview-window wrap is set
Fix #2346
2021-03-12 20:31:27 +09:00
Junegunn Choi
8ae94f0059 Fix premature truncation of colored line when --preview-window wrap is set
Fix #2346
2021-03-12 11:05:51 +09:00
Junegunn Choi
8fccf20892 Fix incorrect tab character handling
Fix #2372
2021-03-12 10:08:18 +09:00
Charlie Vieth
5a874ae241 Speed up ANSI code processing (#2368)
This commit speeds up the parsing/processing of ANSI escape codes by
roughly 7.5x. The speedup is mostly accomplished by replacing the regex
with dedicated parsing logic (nextAnsiEscapeSequence()) and reducing the
number of allocations in extractColor().

#### Benchmarks
```
name             old time/op    new time/op     delta
ExtractColor-16    4.89µs ± 5%     0.64µs ± 2%   -86.87%  (p=0.000 n=9+9)

name             old speed      new speed       delta
ExtractColor-16  25.6MB/s ± 5%  194.6MB/s ± 2%  +661.43%  (p=0.000 n=9+9)

name             old alloc/op   new alloc/op    delta
ExtractColor-16    1.37kB ± 0%     0.31kB ± 0%   -77.31%  (p=0.000 n=10+10)

name             old allocs/op  new allocs/op   delta
ExtractColor-16      48.0 ± 0%        4.0 ± 0%   -91.67%  (p=0.000 n=10+10)
```
2021-03-11 19:34:50 +09:00
Jannik Vieten
f4e1ed25f2 [fish] Make widgets work with --option= prefix (#2383)
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2021-03-08 22:59:56 +09:00
Junegunn Choi
cbfbb49ab4 [vim] Vim 8.0 compatibility
Fix #2367
2021-03-08 12:56:06 +09:00
solarizedalias
489b16efce [fzf-tmux] Adapt to tmux latest changes (#2379) 2021-03-08 12:44:36 +09:00
Junegunn Choi
b82c1693c0 Fix deadlocks 2021-03-08 00:08:10 +09:00
Junegunn Choi
019bfc4e35 Fix yet another deadlock
EventBox.Set should not be called while holding the terminal mutex

  goroutine 1 [semacquire]:
  sync.runtime_SemacquireMutex(0xc0001923bc, 0x1000001066200, 0x1)
          /usr/local/Cellar/go/1.16/libexec/src/runtime/sema.go:71 +0x47
  sync.(*Mutex).lockSlow(0xc0001923b8)
          /usr/local/Cellar/go/1.16/libexec/src/sync/mutex.go:138 +0x105
  sync.(*Mutex).Lock(...)
          /usr/local/Cellar/go/1.16/libexec/src/sync/mutex.go:81
  github.com/junegunn/fzf/src.(*Terminal).Input(0xc000192000, 0x0, 0x0, 0x0, 0x0)
          /fzf/src/terminal.go:581 +0x145
  github.com/junegunn/fzf/src.Run.func10(0xc00010c8a0, 0xc000092050, 0xa)
          /fzf/src/core.go:245 +0x37
  github.com/junegunn/fzf/src.Run.func11(0xc00011a4e0)
          /fzf/src/core.go:295 +0x5ce
  github.com/junegunn/fzf/src/util.(*EventBox).Wait(0xc00011a4e0, 0xc000127ec8)
          /fzf/src/util/eventbox.go:34 +0x5e
  github.com/junegunn/fzf/src.Run(0xc000180000, 0x11ac014, 0x6, 0x11ac158, 0x7)
          /fzf/src/core.go:251 +0xdac
  main.main()
          /fzf/main.go:13 +0x5a

  goroutine 11 [semacquire]:
  sync.runtime_SemacquireMutex(0xc00012c31c, 0xc00010e800, 0x1)
          /usr/local/Cellar/go/1.16/libexec/src/runtime/sema.go:71 +0x47
  sync.(*Mutex).lockSlow(0xc00012c318)
          /usr/local/Cellar/go/1.16/libexec/src/sync/mutex.go:138 +0x105
  sync.(*Mutex).Lock(0xc00012c318)
          /usr/local/Cellar/go/1.16/libexec/src/sync/mutex.go:81 +0x47
  github.com/junegunn/fzf/src/util.(*EventBox).Set(0xc00011a4e0, 0x7, 0x114eb40, 0x1265460)
          /fzf/src/util/eventbox.go:40 +0x3b
  github.com/junegunn/fzf/src.(*Terminal).killPreview(0xc000192000, 0x0)
          /fzf/src/terminal.go:1831 +0xa5
  github.com/junegunn/fzf/src.(*Terminal).exit(0xc000192000, 0xc000106e58)
          /fzf/src/terminal.go:1847 +0x75
  github.com/junegunn/fzf/src.(*Terminal).Loop.func8.1(0xc00011a540)
          /fzf/src/terminal.go:2148 +0x38f
  github.com/junegunn/fzf/src/util.(*EventBox).Wait(0xc00011a540, 0xc000106f90)
          /fzf/src/util/eventbox.go:34 +0x5e
  github.com/junegunn/fzf/src.(*Terminal).Loop.func8(0xc000192000, 0xc00010a2c0)
          /fzf/src/terminal.go:2077 +0xa5
  created by github.com/junegunn/fzf/src.(*Terminal).Loop
          /fzf/src/terminal.go:2072 +0x3e8
2021-03-07 23:35:19 +09:00
Junegunn Choi
dfda5c054a [actions] Install fish using apt-get
For some reason, `test_ctrl_r` and `test_ctrl_r_abort` are not passing
on GitHub Action runner with Fish 3.2.0.
2021-03-07 22:41:27 +09:00
Junegunn Choi
f657169616 Fix deadlock on exit 2021-03-07 21:44:08 +09:00
Junegunn Choi
4c06da8b70 Fix GitHub Action build
$USER is missing
2021-03-07 18:05:39 +09:00
yoshida.shinya
9fe2393a00 Add test cases for killing input command on terminate (#2381 #2382) 2021-03-07 11:36:00 +09:00
Junegunn Choi
e2e8d94b14 Kill input command on terminate
Fix #2381
Close #2382
2021-03-07 11:30:26 +09:00
bitterfox
4f9a7f8c87 Don't exit fzf by SIGINT while executing command (#2375)
Fix #2374

Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2021-02-28 21:01:03 +09:00
Junegunn Choi
bb0502ff44 Check gofmt in make test 2021-02-28 18:28:21 +09:00
Junegunn Choi
c256442245 Fix typo 2021-02-28 18:27:21 +09:00
Jonathan Müller
1137404190 [vim] Add keepjump to switch_back() function (#2363)
Otherwise, the jump list will contain a (hidden) entry for the FZF buffer if `window: enew` is used.
2021-02-25 21:58:13 +09:00
Junegunn Choi
d57c6d0284 Update build script to build macOS arm64 binary
Close #2361
2021-02-25 21:31:15 +09:00
Junegunn Choi
76bbf57b3d Add select and deselect actions
Close #2358
2021-02-25 21:23:05 +09:00
Hiroki Konishi
806a47a7cc [vim] Remove unnecessary border management in nvim floating window (#2370) 2021-02-25 14:41:23 +09:00
Junegunn Choi
29851c18aa [vim] Force redraw by exiting and re-entering terminal mode
Workaround for Neovim v0.5.0-dev

https://github.com/junegunn/fzf/issues/2352#issuecomment-782894123
2021-02-22 21:46:28 +09:00
Junegunn Choi
dea950c2c8 [vim] Call feedkeys only when the destination buffer is a terminal
Fix #2352
Fix https://github.com/junegunn/fzf.vim/issues/1216

Close #2364
2021-02-22 00:22:12 +09:00
Junegunn Choi
a367dfb22e README.md: Better example 2021-02-17 16:44:54 +09:00
odeson24
9fe1a7b373 Remove redundant assignment (#2356)
Co-authored-by: Ryan Ou <ryanou@aetherai.com>
2021-02-17 10:28:43 +09:00
Hussein Esmail
8e2d21c548 Update README.md (#2353)
Remove Linuxbrew links since Linuxbrew has been merged into Homebrew

* https://brew.sh/2019/02/02/homebrew-2.0.0/
2021-02-17 10:24:35 +09:00
Junegunn Choi
bedf1cd357 [vim] Use tnoremap only when it's available
Fix #2357
2021-02-17 10:04:38 +09:00
Junegunn Choi
13f180a70c [vim] Stay in terminal mode if fzf#run is called from sink
Fix #2352
2021-02-15 13:58:49 +09:00
Junegunn Choi
6654239c94 0.25.1 2021-02-03 22:32:52 +09:00
Junegunn Choi
1b61e5e9e9 Clarification on FZF_DEFAULT_COMMAND 2021-02-03 19:40:05 +09:00
Marlon Richert
43b3b907f8 [zsh] Don't run precmd hooks in cd widget (#2340)
`precmd` hooks expect the Zsh Line Editor to not be active.
Running these when the ZLE is active can lead to unpredictable results.
See https://github.com/marlonrichert/zsh-autocomplete/issues/180
2021-02-03 19:26:17 +09:00
Junegunn Choi
fcd896508b [vim] fzf#run should ignore empty 'dir' argument
Fix #2343
2021-02-03 13:51:56 +09:00
Junegunn Choi
f55c990e86 Add close action
Close #2331
2021-02-02 00:11:05 +09:00
Naveen
d110372f99 Create codeql-analysis.yml (#2338) 2021-02-01 23:54:45 +09:00
Junegunn Choi
c862af09f2 Fix toggle-preview-wrap action
Fix #2336
2021-02-01 23:14:21 +09:00
Junegunn Choi
1cfeec0ca3 Fix segmentation fault on \x1b[0K
Fix #2339
2021-02-01 22:59:11 +09:00
step
a0649edc1e [man] Clarify that $SHELL is used to run commands (#2334)
SHELL is used for execute actions and the preview and default commands.
2021-02-01 20:07:42 +09:00
Kovarththanan Rajaratnam
0e0bcb3e10 Update README.md (#2337)
--phony was renamed to --disabled in d779ff7e6d
2021-02-01 20:02:20 +09:00
Nanda Lopes
686528d627 BUILD.md: Update Go version requirement (#2332)
src/options.go:463:9: undefined: strings.ReplaceAll
    note: module requires Go 1.13make: *** [Makefile:122: target/fzf-linux_amd64] Erro 2
2021-01-28 11:43:15 +09:00
jiangjianshan
3afa920151 [install.ps1] Change permission of the downloaded binary (#2308)
Fix #2256
2021-01-28 11:41:23 +09:00
Junegunn Choi
32c493e994 [Makefile] Restore 32-bit targets
Close #2328
2021-01-20 18:40:17 +09:00
Olivier Roques
1a76bdf891 [vim] Exit terminal mode before closing FZF window (#2326)
Fix https://github.com/junegunn/fzf.vim/issues/1216
2021-01-17 22:39:40 +09:00
Junegunn Choi
af48b3df29 Replace Travis CI badge 2021-01-15 10:21:36 +09:00
freddii
58ac1fb2fa Fix typos in source code (#2322) 2021-01-15 10:10:09 +09:00
Junegunn Choi
e922704f72 Migrate to GitHub Actions 2021-01-13 19:10:24 +09:00
Vlad Doster
c6115735c7 Update README.md (#2321)
- Correct spelling/grammar
2021-01-13 04:26:10 +09:00
Ruslan Sayfutdinov
9ddf5c72be [zsh] Properly reset prompt after completion (#2318) 2021-01-13 04:09:34 +09:00
Junegunn Choi
cc5640326b [man] Fix typo 2021-01-10 04:46:31 +09:00
calvin ardi
bf447d7703 Update default number version (#2307) 2021-01-08 13:35:46 +09:00
nicolasbarra
cbb008c938 Update README to upgrade using brew upgrade (#2309) 2021-01-07 16:50:08 +09:00
E.L.K
eaa0c52b45 Fix selection changed on terminal resize (#2306) 2021-01-04 04:20:31 +09:00
Elliott Sales de Andrade
82791f7efc Use more explicit int-to-string conversion.
This fixes the following errors with Go 1.15:
```
src/options.go:452:69: conversion from untyped int to string yields a string of one rune, not a string of digits (did you mean fmt.Sprint(x)?)
src/options.go:463:33: conversion from untyped int to string yields a string of one rune, not a string of digits (did you mean fmt.Sprint(x)?)
```
2021-01-03 14:38:09 +09:00
Junegunn Choi
8c533e34ea 0.25.0 2021-01-03 00:56:11 +09:00
Junegunn Choi
37708ad9cd Revert "[zsh] Use shell redirection (#2281)"
This reverts commit e9bc7331bd.

The change is no longer necessary since 090dee8.
2021-01-03 00:48:40 +09:00
Junegunn Choi
090dee857f Do not disable mouse on SIGCONT before SIGSTOP
Fix #2161
2021-01-03 00:43:56 +09:00
Junegunn Choi
d779ff7e6d Make search toggleable
- `--phony` renamed to `--disabled` for consistency
    - `--no-phony` is now `--enabled`
- Added `enable-search`, `disable-search`, and `toggle-search` actions
  for `--bind`
- Added `--color` options: `query` and `disabled`

Close #2303
2021-01-03 00:15:00 +09:00
Junegunn Choi
fd8858f8c9 [fzf-tmux] Disable CTRL-Z 2021-01-01 22:43:36 +09:00
Junegunn Choi
b234647a63 [shell] Disable CTRL-Z
Fix #2289
2021-01-01 22:36:45 +09:00
Junegunn Choi
6e93eefc82 Update vimdoc 2020-12-31 19:12:57 +09:00
yhu266
38fca30125 Add tag links for "doc/fzf.txt" 2020-12-31 19:12:57 +09:00
Junegunn Choi
012ee9ca85 [Makefile] Make sure to use bash 2020-12-31 12:57:57 +09:00
Junegunn Choi
151252e33a Add preview-top and preview-bottom actions 2020-12-31 12:57:57 +09:00
Junegunn Choi
7136cfc68b Fix alt-, for --expect 2020-12-31 03:38:46 +09:00
Junegunn Choi
408c04f25f Update test case for 'first' and 'last' 2020-12-30 18:43:16 +09:00
Junegunn Choi
7f8e0dbc40 Extend support for alt key chords
"alt-" with any case-sensitive character is allowed
2020-12-30 18:39:17 +09:00
Junegunn Choi
0de7ab18f6 Add "last" action to move the cursor to the last match
This is the opposite of "first" (previously known as "top").
2020-12-30 18:39:17 +09:00
林千里
e9bc7331bd [zsh] Use shell redirection (#2281)
zsh sends SIGCONT when running fzf in a pipe in certain cases,
causing mouse mode to become disabled

Fix #2101
2020-12-23 14:38:37 +09:00
Loic Nageleisen
797dd7c449 [Makefile] Support building on machines with uname -m == "arm64" (#2291) 2020-12-23 14:27:46 +09:00
Junegunn Choi
f37ccaa64f Prevent index out of range error
Fix #2293
2020-12-23 10:34:31 +09:00
Junegunn Choi
ab3937ee5a [vim] Allow closing Vim running fzf without confirmation
Close #2287
2020-12-16 21:44:28 +09:00
Junegunn Choi
00f4551a7b Revert "[zsh] Reload shared history before searching (#2251)"
This reverts commit b62a74b315.

https://github.com/junegunn/fzf/pull/2251#issuecomment-740551383
2020-12-09 00:02:35 +09:00
Junegunn Choi
257ddd028d Update CHANGELOG 2020-12-07 19:35:48 +09:00
Junegunn Choi
e0a22e76f8 Make --color attributes mergeable
So you can override the colors and still have the text attributes

    # Default colors and attributes
    fzf

    export FZF_DEFAULT_OPTS='--color hl👎underline,hl+👎underline:reverse'

    # Default colors with underline+reverse attributes
    fzf

    # Different colors with underline+reverse attributes
    fzf --color hl:176,hl+:177

Related: https://github.com/junegunn/fzf.vim/issues/1197#issuecomment-739804363
2020-12-07 19:11:00 +09:00
Junegunn Choi
00a3610331 0.24.4 2020-12-05 23:24:55 +09:00
Junegunn Choi
f502725120 Fix slice bound error on extremely narrow screen 2020-12-05 22:00:42 +09:00
Martin Polden
b62a74b315 [zsh] Reload shared history before searching (#2251) 2020-12-05 21:48:54 +09:00
Junegunn Choi
2ec382ae0e Add --preview-window follow option 2020-12-05 21:16:35 +09:00
Junegunn Choi
cbfee31593 Fix typo in test case 2020-12-04 20:39:52 +09:00
Junegunn Choi
6d647e13ff Add change-prompt action
Close #2270
2020-12-04 20:34:41 +09:00
Junegunn Choi
d2af3ff98d Change how hl:-1 or hl+:-1 is applied to text with background color 2020-12-04 19:27:43 +09:00
Junegunn Choi
052d17e66a Fix Travis OSX build 2020-12-03 10:33:45 +09:00
Junegunn Choi
a9bc954e17 Do not update Homebrew on Travis OSX build
https://docs.travis-ci.com/user/installing-dependencies/#installing-packages-on-macos

> By default, the Homebrew addon will not run brew update before
> installing packages. brew update can take a long time and slow down your
> builds.
2020-12-03 10:24:06 +09:00
Junegunn Choi
2983426771 Fix unit tests 2020-11-25 13:08:28 +09:00
ratijas
c61eb94b3f [zsh] Declare variable as local before assignment (#2266) 2020-11-25 11:21:30 +09:00
Junegunn Choi
3829eab1cf Support ANSI code for clearing the rest of the line (ESC[0K)
Some programs use it to set the background color for the whole line.

  fzf --preview "printf 'normal \x1b[42mgreen\x1b[0K \x1b[43myellow\x1b[m\nnormal again'"

  fzf --preview 'delta <(echo foo) <(echo bar) < /dev/tty'

Fix #2249
2020-11-25 01:49:48 +09:00
Junegunn Choi
3fe8eeedc5 Fix handling of arrow keys with alt and/or shift modifier
Fix #2254

- Properly handle extra chars in the buffer. Patch suggested by @mckelly2833.
- Support alt-arrow sequences in \e[1;3A format
- Support shift-alt-arrow sequences in \e[1;10A format
2020-11-24 19:51:19 +09:00
Junegunn Choi
1efef88b6e Improve trim function to handle longer strings
Fix #2258
2020-11-24 19:03:59 +09:00
Junegunn Choi
7acdaf0b43 [vim] &termwinkey may not be available
/cc @Caid11
2020-11-19 09:55:57 +09:00
Michal Domonkos
1ed25d76ba [vim] Clean up temp files on interrupt (#2252)
The clean-up is done in s:collect(), so let's make sure it's run before
we may terminate due to CTRL-C or ESC (or some other error code) in
s:exit_handler().
2020-11-17 14:37:14 +09:00
Junegunn Choi
474c1f5e32 [vim] Map CTRL-Z to <nop> 2020-11-15 21:28:07 +09:00
Junegunn Choi
8b71fea5dc [vim] Set termwinkey to allow CTRL-W
Fix https://github.com/junegunn/fzf.vim/issues/1176
2020-11-15 21:27:57 +09:00
Tomas Janousek
7bd99a22ee [bash-completion] Fix endless loop when completion.bash sourced twice
I forgot to add the "not _fzf" check into __fzf_orig_completion, so
invoking it twice would rewrite the _fzf_orig_completion_xxx variables
and then cause an endless loop when completion is requested.

Fixes: ef2c29d5d4 ("[bash-completion] Optimize __fzf_orig_completion_filter")
2020-11-14 10:29:05 +09:00
Tomas Janousek
75b8cca3b3 [bash-completion] Unexport _fzf_orig_completion_* variables 2020-11-13 02:16:54 +09:00
Tomas Janousek
ef2c29d5d4 [bash-completion] Optimize __fzf_orig_completion_filter
Commit d4ad4a25 slowed loading of completion.bash significantly (on my
laptop from 10 ms to 30 ms), then 54891d11 improved that (to 20 ms) but
it still stands out as the heavy part of my .bashrc.

Rewriting __fzf_orig_completion_filter to pure bash without forking to
sed/awk brings this back under 10 ms.

before:

    $ HISTFILE=/tmp/bashhist hyperfine 'bash --rcfile shell/completion.bash -i'
    Benchmark #1: bash --rcfile shell/completion.bash -i
      Time (mean ± σ):      21.2 ms ±   0.3 ms    [User: 24.9 ms, System: 6.4 ms]
      Range (min … max):    20.7 ms …  23.3 ms    132 runs

after:

    $ HISTFILE=/tmp/bashhist hyperfine 'bash --rcfile shell/completion.bash -i'
    Benchmark #1: bash --rcfile shell/completion.bash -i
      Time (mean ± σ):       9.6 ms ±   0.3 ms    [User: 8.0 ms, System: 2.2 ms]
      Range (min … max):     9.3 ms …  11.4 ms    298 runs

Fixes: d4ad4a25db ("[bash-completion] Fix default alias/variable completion")
Fixes: 54891d11e0 ("[bash-completion] Minor optimization")
2020-11-13 02:16:54 +09:00
Tomas Janousek
218b3c8274 [bash-completion] Move -F/_fzf filter to __fzf_orig_completion_filter
This prevents mistakes like the one fixed by the previous commit, and
also speeds bash startup a tiny bit:

before:

    $ HISTFILE=/tmp/bashhist hyperfine 'bash --rcfile shell/completion.bash -i'
    Benchmark #1: bash --rcfile shell/completion.bash -i
      Time (mean ± σ):      22.4 ms ±   0.6 ms    [User: 28.7 ms, System: 7.8 ms]
      Range (min … max):    21.7 ms …  25.2 ms    123 runs

after:

    $ HISTFILE=/tmp/bashhist hyperfine 'bash --rcfile shell/completion.bash -i'
    Benchmark #1: bash --rcfile shell/completion.bash -i
      Time (mean ± σ):      21.2 ms ±   0.3 ms    [User: 24.9 ms, System: 6.4 ms]
      Range (min … max):    20.7 ms …  23.3 ms    132 runs
2020-11-13 02:16:54 +09:00
Tomas Janousek
db9cb2ddda [bash-completion] Avoid empty _a, _v completions
This doesn't look right:

    $ complete | grep ' _.$'
    complete _a
    complete _v

The __fzf_orig_completion_filter invocation in _fzf_setup_completion
needs the /-F/ filter, just like all the other invocations.

Fixes: d4ad4a25db ("[bash-completion] Fix default alias/variable completion")
2020-11-13 02:16:54 +09:00
Junegunn Choi
722d66e85a 0.24.3 2020-11-09 20:41:59 +09:00
Junegunn Choi
f6269f0193 Add --padding option
Close #2241
2020-11-09 20:37:17 +09:00
Junegunn Choi
520eae817a Remove print statement for debugging 2020-11-09 19:17:33 +09:00
Junegunn Choi
d099941360 [vim] Fix double path separator issue on Windows
Fix https://github.com/junegunn/fzf.vim/issues/1141
2020-11-05 18:14:45 +09:00
Junegunn Choi
e3e76fa8c5 0.24.2 2020-11-03 23:32:24 +09:00
Junegunn Choi
2553806e79 Allow preview window height shorter than 3
Fix #2231
2020-11-03 22:04:01 +09:00
Junegunn Choi
1bcbc5a353 Fix regression where lines are skipped in the preview window
Fix #2239
2020-11-03 21:31:19 +09:00
Junegunn Choi
15d351b0f0 Use default bg color when fg is set to -1 with reverse attribute 2020-11-03 20:51:44 +09:00
Junegunn Choi
c144c95cda [vim] Set maxwidth and maxheight when creating a popup
For me, this fixes invalid popup size problem on Windows GVim
2020-10-31 22:27:58 +09:00
Junegunn Choi
f08f4fd87d [vim] Remove dead code 2020-10-31 22:21:06 +09:00
Junegunn Choi
f8aaeef218 Revert "Prefer LightRenderer on Windows if it's available"
This reverts commit 7915e365b3
due to https://github.com/junegunn/fzf.vim/issues/1152#issuecomment-719696495.
2020-10-31 02:53:10 +09:00
Junegunn Choi
7915e365b3 Prefer LightRenderer on Windows if it's available
Fix #1766
2020-10-31 01:41:57 +09:00
Junegunn Choi
1c68f81c37 [vim] See the last line of "fzf --version" output
The output may contain some unexpected warning messages from the shell
if it's not properly configured. While such extra messages should be
properly addressed by the user, we can ignore them by checking the
last line of the output instead of the first line.

Related: bd3a021ec1
2020-10-30 21:57:09 +09:00
Junegunn Choi
d4c9db0a27 0.24.1 2020-10-29 01:45:55 +09:00
Junegunn Choi
b5e0e29ec6 Assign default number version (without patch version)
So that you can still build and use fzf even when the precise version
number is not injected via -ldflags.
2020-10-29 01:38:34 +09:00
Junegunn Choi
569be4c6c9 [vim] Allow 'border': 'no' to be consistent with --color=no 2020-10-29 01:32:41 +09:00
Junegunn Choi
e7ca237b07 Fix nil error on --color=bw
Fix #2229
2020-10-29 01:27:08 +09:00
Junegunn Choi
a7d3b72117 Make build flags consistent 2020-10-28 18:39:39 +09:00
Junegunn Choi
3ba7b5cf2d Make Makefile fail when git information is not available 2020-10-28 18:33:22 +09:00
Junegunn Choi
254e9765fe [install] Pass version number to go get command
Related: https://github.com/junegunn/fzf.vim/issues/1150#issuecomment-717735149
2020-10-28 15:56:05 +09:00
Junegunn Choi
3304f284a5 Panic when fzf was built without version information
So that the package maintainers would immediately know that the build is
incorrect. But is there a way to make build simply fail?

Related: https://github.com/junegunn/fzf.vim/issues/1150
2020-10-28 10:51:32 +09:00
Junegunn Choi
0d5f862daf 0.24.0 2020-10-27 23:57:18 +09:00
Junegunn Choi
51dfacd542 Merge branch 'devel' into master 2020-10-27 23:57:03 +09:00
Junegunn Choi
c691d52fa7 Fix: barbled multibyte text(exe. Japanese). (#2224)
* Fix: barbled multibyte text(exe. Japanese).

* fixup

Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2020-10-27 23:55:10 +09:00
Junegunn Choi
de3d09fe79 fixup 2020-10-27 23:53:25 +09:00
Junegunn Choi
eaa413c566 Fix error when preview command failed to start 2020-10-27 21:36:38 +09:00
nekowasabi
407205e52b Fix: barbled multibyte text(exe. Japanese). 2020-10-27 17:16:47 +09:00
Junegunn Choi
552414978e 0.24.0-rc1 2020-10-27 11:07:27 +09:00
Junegunn Choi
607081bbaa [vim] Download latest binary to meet version requirement 2020-10-27 01:01:58 +09:00
Junegunn Choi
e73383fbbb [vim] Add 'none' option for popup border 2020-10-26 23:41:20 +09:00
Junegunn Choi
2e8e63fb0b Add more --border options
Instead of drawing the window border in Vim using an extra window,
extend the --border option so that we do can it natively.

Close #2223
Fix #2184
2020-10-26 22:51:22 +09:00
Junegunn Choi
874f7dd416 Update --color example in CHANGELOG
New color name: input
2020-10-26 00:22:06 +09:00
Junegunn Choi
8b0e3b1624 Update --color docs 2020-10-26 00:15:30 +09:00
Junegunn Choi
9b946f2b7a Fix preview window of tcell renderer 2020-10-25 21:43:53 +09:00
Junegunn Choi
11841f688b Add support for text styling using --color
Close #1663
2020-10-25 19:30:41 +09:00
Junegunn Choi
03c4f04246 Use 64-bit integer for preview version 2020-10-24 16:55:55 +09:00
Junegunn Choi
a1f06ae27f Fix regression where empty preview content is not displayed 2020-10-23 23:52:05 +09:00
Junegunn Choi
69dffd78a6 Do not assume that each character takes at least 1 column
Fixes #2163, though this is not a proper fix to the problem.
2020-10-23 23:32:10 +09:00
Junegunn Choi
2750e19657 Update go-runewidth 2020-10-23 23:31:46 +09:00
Junegunn Choi
b0987f727b Clarify that additional installation steps may be required
Close #2211
2020-10-23 22:52:46 +09:00
Junegunn Choi
a4d9b0b468 Support ANSI escape sequence for clearing display in preview window
fzf --preview 'for i in $(seq 100000); do
    (( i % 200 == 0 )) && printf "\033[2J"
    echo "$i"
    sleep 0.01
  done'
2020-10-23 21:37:20 +09:00
Junegunn Choi
e2b87e0d74 Fix Travis CI build 2020-10-23 20:44:04 +09:00
Junegunn Choi
2166b4ca17 Fix test cases
We were not properly waiting for truthy-ness in until blocks.

Need Minitest with 21d9e804b6
2020-10-23 19:54:45 +09:00
Junegunn Choi
d2d4d68585 Always show the number of selected entries to indicate if --multi is enabled
Close #2217

  seq 100 | fzf
    # 100/100
  seq 100 | fzf --multi
    # 100/100 (0)
  seq 100 | fzf --multi 5
    # 100/100 (0/5)
2020-10-20 20:04:06 +09:00
Junegunn Choi
faf68dbc5c Implement streaming preview window (#2215)
Fix #2212

    # Will start rendering after 200ms, update every 100ms
    fzf --preview 'for i in $(seq 100); do echo $i; sleep 0.01; done'

    # Should print "Loading .." message after 500ms
    fzf --preview 'sleep 1; for i in $(seq 100); do echo $i; sleep 0.01; done'

    # The first line should appear after 200ms
    fzf --preview 'date; sleep 2; date'

    # Should not render before enough lines for the scroll offset are ready
    rg --line-number --no-heading --color=always ^ |
      fzf --delimiter : --ansi --preview-window '+{2}-/2' \
          --preview 'sleep 1; bat --style=numbers --color=always --pager=never --highlight-line={2} {1}'
2020-10-18 17:03:33 +09:00
Junegunn Choi
305896fcb3 README-VIM: g:fzf_action doesn't work with custom sink
Fix https://github.com/junegunn/fzf.vim/issues/1131
2020-10-18 13:23:40 +09:00
Andrew Zhou
6c9adea0d3 [fish] Fix parser handling of option-like args (#2208)
Fixes error when option-like args are parsed (e.g. "-1").
2020-10-12 12:58:37 +09:00
Junegunn Choi
fc7630a66d 0.23.1 2020-10-11 02:04:07 +09:00
Junegunn Choi
3248153d9f Add --preview-window=default for resetting the options 2020-10-11 01:54:39 +09:00
Junegunn Choi
246b9f3130 Simplify fzf-tmux script
# Should properly escape arguments
    FZF_DEFAULT_OPTS='--prompt "\$a`b\"c"' fzf-tmux --header $'$a\nb"c`d'
2020-10-09 23:02:03 +09:00
Junegunn Choi
865144850d Add nowrap, nocycle, nohidden for --preview-window
Close #2203
2020-10-09 21:56:16 +09:00
Junegunn Choi
d9752a4c21 Reset preview window flags that are not style-related
Fix #2203
2020-10-09 19:53:51 +09:00
Junegunn Choi
dba14d2630 0.23.0 2020-10-07 19:18:50 +09:00
Elvan Owen
2986e64a49 [completion] Make host completion handle source files without EOL 2020-10-06 20:54:42 +09:00
Junegunn Choi
1d8bd11b67 Fix preview window size calculation 2020-10-06 19:37:33 +09:00
Junegunn Choi
bafb99d520 Allow splitting preview-window options
e.g. --preview-window sharp --preview-window cycle
2020-10-06 18:44:13 +09:00
Junegunn Choi
3cc8a74a91 Add --preview-window option for cyclic scrolling
Close #2182
2020-10-06 10:05:57 +09:00
Tinmarino
c0aa5a438f Add preview-half-page-down and preview-half-page-up (#2145) 2020-10-05 21:58:56 +09:00
Junegunn Choi
825d401403 Show how to use reload action 2020-10-05 19:17:31 +09:00
Junegunn Choi
9dfca77c36 [zsh] Keep current $BUFFER on ALT-C
Ideally, we could only use `print -sr` to update the command history.
However, the "cd" command by ALT-C is added to the history only after we
finalize the current command by pressing an additional enter key.

i.e. The cd command from ALT-C is not visible when you hit Up arrow. But
it appears once you hit enter key.

So when the current buffer is empty, we use `zle accept-line` so that
the command history is immediately updated.

Close #2200
2020-10-03 14:55:02 +09:00
octaltree
82c4af2902 [zsh] Record cd execution in history (#2193) 2020-10-02 22:14:09 +09:00
Junegunn Choi
736344e151 Remove deprecated item from man page 2020-09-29 11:34:57 +09:00
Junegunn Choi
6f9663da62 Always allow preview/execute commands with no placeholder expressions
Fix #2017
2020-09-29 11:32:56 +09:00
Wenxuan
f8ae1786dd Fix items width limit (#2190) 2020-09-24 11:06:20 +09:00
Junegunn Choi
c60ed17583 [vim] Change the default layout to use popup window
The new default is

  { 'window' : { 'width': 0.9, 'height': 0.6, 'highlight': 'Normal' } }

The default highlight group for the border of the popup window is
'Comment', but 'Normal' seems to be a safer choice.

If you prefer the previous default, add this to your Vim configuration file:

  let g:fzf_layout = { 'down': '40%' }

(fzf will fall back to this if popup window is not supported)
2020-09-12 21:14:32 +09:00
Junegunn Choi
e0f0b5bcf9 Update CHANGELOG 2020-09-09 00:06:53 +09:00
Junegunn Choi
9e96073128 [vim] Expose fzf#exec() function 2020-09-09 00:02:37 +09:00
Junegunn Choi
0db65c22d3 [vim] Allow specifying popup width and height in absolute integer value
Fix https://github.com/junegunn/fzf.vim/issues/1116
2020-09-06 22:15:44 +09:00
Yuji Nakao
d785135606 [zsh] Fix the regular expression (#2140)
Fix the regular expression to capture the command containing asterisk.
2020-09-02 17:27:56 +09:00
Michael Kelley
ae15eda546 Add truecolor support for Windows, if available (#2156)
- Update to latest tcell which has 24 bit Windows support
- light renderer under Windows defaults to Dark256, if possible
- Respect TCELL_TRUECOLOR
- Remove tcell 1.3 references
2020-09-02 13:47:13 +09:00
Junegunn Choi
f2d44ab5a7 Revert horizontal padding around preview window on "noborder"
Use 2-space horizontal padding so that the preview content is aligned
with the candidate list when the position of the preview window is `up`
or `down`.
2020-08-23 17:17:57 +09:00
Junegunn Choi
43798fc2e8 Revert 1ab4289: Preview window of size 0 is allowed 2020-08-23 17:12:37 +09:00
Junegunn Choi
9dc4b40d7a Add more preview window options and reduce vertical padding on noborder
Fix #2138
Fix #2029
2020-08-23 17:05:45 +09:00
Junegunn Choi
1cb19dbf65 Support preview scroll offset relative to window height
Related: https://github.com/junegunn/fzf.vim/issues/1092
2020-08-23 15:57:49 +09:00
Junegunn Choi
1ab4289ad6 Disallow preview-window size of zero 2020-08-21 11:49:01 +09:00
Junegunn Choi
e2ae1b249c 0.22.0 2020-08-02 15:56:02 +09:00
Junegunn Choi
92b7efafca Ignore punctuation characters before and after preview offset column
This is to allow line numbers in a ctags output (e.g. 123;")
2020-08-02 10:03:17 +09:00
Junegunn Choi
f092e4038f Smart match of accented characters
Fix #1618
2020-07-28 13:06:57 +09:00
Junegunn Choi
aa5dae391b Fix handling of unicode characters in query string 2020-07-28 12:58:37 +09:00
Junegunn Choi
08a6fd4ad4 Fix Travis CI build: Use Go 1.14 2020-07-27 09:01:28 +09:00
Junegunn Choi
a61150a96c Allow negative field index in preview-window scroll offset 2020-07-27 00:30:25 +09:00
Junegunn Choi
0f9cb5590e Add preview window option for setting the initial scroll offset
Close #1057
Close #2120

  # Initial scroll offset is set to the line number of each line of
  # git grep output *minus* 5 lines
  git grep --line-number '' |
    fzf --delimiter : --preview 'nl {1}' --preview-window +{2}-5
2020-07-27 00:27:03 +09:00
yuki yano
c0a83b27eb Fix failure of w:fzf_pushd unlet depending on timing (#2119) 2020-07-26 13:52:50 +09:00
Yanlin Sun
f79b1f71b8 [vim] Preserve current directory in case someone changes it (#2096)
Preserve current directory in case current directory is changed by others
after the call of s:open

Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2020-07-15 13:20:56 +09:00
Junegunn Choi
8e027c445f Support ANSI colors in --prompt string
Close #2086
2020-07-05 16:16:46 +09:00
Atemu
dda3e3c39a README: Correct Nix distro support (#2051)
Nix can be installed on (almost) any Linux distro and on macOS.

See https://nixos.org/nix/ for more information.
2020-07-05 15:29:43 +09:00
anntnzrb
fd5157998c Void Linux installation instructions (#2100) 2020-07-03 23:14:12 +09:00
Junegunn Choi
e0217e8c79 Ignore cursor position report
Close #2081
2020-07-03 19:45:58 +09:00
Junegunn Choi
3ab1c42266 Use rune characters instaed of numbers in code 2020-07-03 19:41:19 +09:00
Junegunn Choi
d1676776aa Update CHANGELOG 2020-06-30 21:17:19 +09:00
Junegunn Choi
bdde69d011 [vim] Disable height calculation when 'preview' is found in the option string
Fix #2093

And we'll phase out height specification with `~` prefix.
2020-06-29 22:27:36 +09:00
Junegunn Choi
6dec42a33a Update version numbers in man pages 2020-06-29 22:07:48 +09:00
Junegunn Choi
199bc3f0ad Merge branch 'master' into devel 2020-06-21 22:43:03 +09:00
Junegunn Choi
17dd833925 Add preview action for --bind
Fix #2010
Fix #1638
2020-06-21 22:41:33 +09:00
Khon Trieu
4ec144c969 Accented character normalization for Vietnamese characters (#2090)
Fix #2088
2020-06-21 17:19:38 +09:00
Jan Edmund Lazo
3e36f2b0ac [nvim] Fix floating window requirements (#2089)
Vim 8.1.2371
05ad5ff0ab

Nvim 0.4.0
9a1675b065
2020-06-21 10:34:43 +09:00
Junegunn Choi
07a03b3e73 [vim] Make fzf#wrap support v:true and v:false as well
Fix #2087
2020-06-20 22:15:12 +09:00
Junegunn Choi
c33258832e Add refresh-preview action 2020-06-20 22:04:09 +09:00
Junegunn Choi
a7aa08ce07 Add backward-eof event for --bind 2020-06-07 23:07:03 +09:00
Ben
06d63a862e Fully qualify Expand-Archive (#2066)
If a user has the Powershell Community Extensions installed, it comes
with another command Expand-Archive that doesn't have a DestinationPath
argument, causing an error.
2020-06-07 10:57:23 +09:00
Janek
43d1c4c4b5 README: Use --line-range instead of head in bat example (#2064)
* Use --line-range instead of head in bat example

* README: extend preview section
2020-06-04 11:57:01 +09:00
Junegunn Choi
f81feb1e69 Revert file mode of key-bindings.zsh 2020-05-23 20:51:41 +09:00
karasu
01cf01e084 [fzf-tmux] Fix zoomed pane handling in popup mode (#2054)
When called with popup options, do not move to temp window.
2020-05-23 20:47:43 +09:00
Junegunn Choi
97a725fbd0 Do not disable mouse after execute(-silent) when --height option is used
The action takes place in the alternate screen so the offsets should
still be correct.
2020-05-18 02:43:58 +09:00
lacygoill
ace92ba281 [vim] Don't set wfw, wfh, bh options when opening popup (#2042)
* No need to restore &wfw and &wfh when using popup window

Co-authored-by: lacygoill <lacygoill@lacygoill.me>
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2020-05-17 23:28:23 +09:00
ichizok
d631c76e8d [vim] Don't start extra process when opening popup (#2000)
Fix #2038
2020-05-15 15:25:33 +09:00
Slaven Rezić
e6d33f77da [zsh] Make CTRL-R work with older Perls (#2003)
s///r is only available since perl 5.14. The Perl oneliner
was changed to work with older Perls, possibly even with 5.000.

Fix #2001
2020-04-24 22:56:55 +09:00
Jack Bates
a6d3e3687b Improve error messages (#1962)
* Add RuboCop Minitest extension
* Improve error messages
* Use chomp option
2020-04-21 10:12:00 +09:00
Raffaele
08c2bcb952 Quote LDFLAGS (#1995)
Make sure that `extldflags` is quoted so that LDFLAGS containing spaces won't break the build command.

Close #1994
2020-04-21 10:07:39 +09:00
Junegunn Choi
98ca4bdede Add conda installation instruction
Close #1949
2020-04-18 13:00:38 +09:00
Janek
3f8e741562 Add more details on apt installation in README.md (#1977) 2020-04-18 12:59:32 +09:00
Junegunn Choi
6e464ebd9b Remove dead code 2020-04-18 02:51:02 +09:00
Junegunn Choi
c329279339 [completion] Make kill completion more consistent with the others
Support both ordinary completion trigger and empty trigger

    kill <tab>
    kill foo**<tab>

Close #1988
Close #385
2020-04-18 02:46:40 +09:00
Jack Bates
cf04753ad7 Make flaky tests reliable (#1978) 2020-04-18 02:34:38 +09:00
Junegunn Choi
69e7eab11f [install] Clarify that .bashrc should be loaded from .bash_profile on macOS
Close #1986
2020-04-17 21:51:16 +09:00
Junegunn Choi
dea206b023 [zsh-completion] Fix error with backslash-prefixed commands
Fix #1973
Fix #1974
Fix #1975
2020-04-13 00:30:43 +09:00
Jack Bates
5deaf58928 Run rubocop --auto-correct --disable-uncorrectable (#1967)
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2020-04-13 00:23:31 +09:00
Junegunn Choi
15e2952a2b [fzf-tmux] Allow positional flags
Since we don't know in advance which flags tmux will support, simply
allow a single uppercase character ([A-Z]) for now.

    fzf-tmux -xR -yS
    fzf-tmux -x R -y S

Fix #1956
2020-04-07 09:55:48 +09:00
Junegunn Choi
a9fba1c849 Fix typo 2020-04-05 18:36:31 +09:00
Junegunn Choi
71e573d082 [vim] Add 'tmux' layout option to use fzf-tmux
e.g.

  if exists('$TMUX')
    let g:fzf_layout = { 'tmux': '-p90%,60%' }
  else
    let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
  endif
2020-04-05 18:16:36 +09:00
Junegunn Choi
334a4fa159 0.21.1 2020-04-03 17:33:29 +09:00
Junegunn Choi
21f94ee800 [fzf-tmux] Split zsh variable expansion for old zsh
The following code works in zsh 5.8 but not in 5.4

  ${(Q)${(Z+n+)FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}}}
2020-04-03 13:23:15 +09:00
Junegunn Choi
540bfd7a72 [fzf-tmux] Fall back to plain fzf when split failed 2020-04-03 13:23:15 +09:00
Junegunn Choi
8fbed2b13a [fzf-tmux] Use $PWD instead of #{pane_current_path}
Related: https://github.com/tmux/tmux/issues/1282
2020-04-03 13:23:15 +09:00
Junegunn Choi
aa17510e0a [fzf-tmux] Set default horizontal margin 2020-04-03 13:23:15 +09:00
Junegunn Choi
bf65e8cd12 [fzf-tmux] Add option to start fzf in tmux popup window
Requires latest tmux built from source (e.g. brew install tmux --HEAD)

Examples:

  # 50%/50% width and height on the center of the screen
  fzf-tmux -p

  # 80%/80%
  fzf-tmux -p80%

  # 80%/40%
  fzf-tmux -p80%,40%

  # Separate -w and -h
  fzf-tmux -w80% -h40%

  # 80%/40% at position (0, 0)
  fzf-tmux -w80% -h40% -x0 -y0

You can configure key bindings and fuzzy completion to open in tmux
popup window like so:

  FZF_TMUX_OPTS='-p 80%'
2020-04-03 13:23:15 +09:00
lacygoill
0f5c6e8f04 [vim] Fix issue with multiple popups (#1927)
Invoking fzf from an existing Vim popup terminal is a special case.
It requires some new code to avoid E994 from being raised and the user
being stuck in a non-closable popup window.

Fix #1916
2020-03-30 02:06:06 +09:00
Roman Perepelitsa
b1b916ce15 [zsh] Ensure that fzf code always parses the same way (#1944)
At the top of each zsh file options are set to their
standard values (those marked with <Z> in `man zshoptions`)
and `aliases` option is disabled.

At the bottom of the file the original options are restored.

Fix #1938
2020-03-30 01:52:48 +09:00
Alexandr
a6a732e1fc Update AtomicBool to use atomic memory operation (#1939) 2020-03-30 01:42:58 +09:00
Junegunn Choi
a5c2f28539 Fix failing test case 2020-03-29 22:06:06 +09:00
Junegunn Choi
18261fe31c [shell] Update CTRL-R to remove duplicate commands
Close #1940
Related: #1363 #749 #270 #49 #88 #492 #600
2020-03-29 21:30:37 +09:00
Chitoku
079046863c [zsh-completion] Fix a bug where _fzf_complete did not iterate through args (#1936) 2020-03-24 08:58:22 +09:00
Junegunn Choi
07b965bba1 Fix ANSI color offsets when --keep-right is used 2020-03-23 19:05:06 +09:00
Junegunn Choi
c39113ee41 [windows] Do not include directories in the list
Fix #1926
2020-03-14 21:43:35 +09:00
Junegunn Choi
14f90502a4 [bash] Restore --nth option in CTRL-R 2020-03-13 09:13:38 +09:00
93 changed files with 16412 additions and 4669 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
github: junegunn

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

10
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

44
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning
name: CodeQL
on:
push:
branches: [ master, devel ]
pull_request:
branches: [ master ]
workflow_dispatch:
permissions:
contents: read
jobs:
analyze:
permissions:
actions: read # for github/codeql-action/init to get workflow details
contents: read # for actions/checkout to fetch code
security-events: write # for github/codeql-action/autobuild to send a status report
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: ['go']
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3

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

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

51
.github/workflows/linux.yml vendored Normal file
View File

@@ -0,0 +1,51 @@
---
name: Test fzf on Linux
on:
push:
branches: [ master, devel ]
pull_request:
branches: [ master ]
workflow_dispatch:
permissions:
contents: read
env:
LANG: C.UTF-8
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.20"
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.1.0
- name: Install packages
run: sudo apt-get install --yes zsh fish tmux
- name: Install Ruby gems
run: sudo gem install --no-document minitest:5.17.0 rubocop:1.43.0 rubocop-minitest:0.25.1 rubocop-performance:1.15.2
- name: Rubocop
run: rubocop --require rubocop-minitest --require rubocop-performance
- name: Unit test
run: make test
- name: Integration test
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

45
.github/workflows/macos.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
---
name: Test fzf on macOS
on:
push:
branches: [ master, devel ]
pull_request:
branches: [ master ]
workflow_dispatch:
permissions:
contents: read
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.20"
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.0.0
- name: Install packages
run: HOMEBREW_NO_INSTALL_CLEANUP=1 brew install fish zsh tmux
- name: Install Ruby gems
run: gem install --no-document minitest:5.14.2 rubocop:1.0.0 rubocop-minitest:0.10.1 rubocop-performance:1.8.1
- name: Rubocop
run: rubocop --require rubocop-minitest --require rubocop-performance
- name: Unit test
run: make test
- name: Integration test
run: make install && ./install --all && LC_ALL=C tmux new-session -d && ruby test/test_go.rb --verbose

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

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

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

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

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

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

3
.gitignore vendored
View File

@@ -1,5 +1,6 @@
bin/fzf
bin/fzf.exe
dist
target
pkg
Gemfile.lock
@@ -9,3 +10,5 @@ vendor
gopath
*.zwc
fzf
tmp
*.patch

118
.goreleaser.yml Normal file
View File

@@ -0,0 +1,118 @@
---
project_name: fzf
before:
hooks:
- go mod download
builds:
- id: fzf-macos
binary: fzf
goos:
- darwin
goarch:
- amd64
ldflags:
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
hooks:
post: |
sh -c '
cat > /tmp/fzf-gon-amd64.hcl << EOF
source = ["./dist/fzf-macos_darwin_amd64_v1/fzf"]
bundle_id = "kr.junegunn.fzf"
sign {
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
}
zip {
output_path = "./dist/fzf-{{ .Version }}-darwin_amd64.zip"
}
EOF
gon /tmp/fzf-gon-amd64.hcl
'
- id: fzf-macos-arm
binary: fzf
goos:
- darwin
goarch:
- arm64
ldflags:
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
hooks:
post: |
sh -c '
cat > /tmp/fzf-gon-arm64.hcl << EOF
source = ["./dist/fzf-macos-arm_darwin_arm64/fzf"]
bundle_id = "kr.junegunn.fzf"
sign {
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
}
zip {
output_path = "./dist/fzf-{{ .Version }}-darwin_arm64.zip"
}
EOF
gon /tmp/fzf-gon-arm64.hcl
'
- id: fzf
goos:
- linux
- windows
- freebsd
- openbsd
goarch:
- amd64
- arm
- arm64
- loong64
- ppc64le
- s390x
goarm:
- 5
- 6
- 7
ldflags:
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
ignore:
- goos: freebsd
goarch: arm
- goos: openbsd
goarch: arm
- goos: freebsd
goarch: arm64
- goos: openbsd
goarch: arm64
archives:
- name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
builds:
- fzf
format: tar.gz
format_overrides:
- goos: windows
format: zip
files:
- non-existent*
checksum:
extra_files:
- glob: ./dist/fzf-*darwin*.zip
release:
github:
owner: junegunn
name: fzf
prerelease: auto
name_template: '{{ .Tag }}'
extra_files:
- glob: ./dist/fzf-*darwin*.zip
snapshot:
name_template: "{{ .Tag }}-devel"
changelog:
sort: asc
filters:
exclude:
- README
- test

32
.rubocop.yml Normal file
View File

@@ -0,0 +1,32 @@
Layout/LineLength:
Enabled: false
Metrics:
Enabled: false
Lint/ShadowingOuterLocalVariable:
Enabled: false
Style/MethodCallWithArgsParentheses:
Enabled: true
IgnoredMethods:
- assert
- exit
- paste
- puts
- raise
- refute
- require
- send_keys
IgnoredPatterns:
- ^assert_
- ^refute_
Style/NumericPredicate:
Enabled: false
Style/StringConcatenation:
Enabled: false
Style/OptionalBooleanParameter:
Enabled: false
Style/WordArray:
MinSize: 1
Minitest/AssertEqual:
Enabled: false
Naming/VariableNumber:
Enabled: false

1
.tool-versions Normal file
View File

@@ -0,0 +1 @@
golang 1.20.13

View File

@@ -1,22 +0,0 @@
language: go
env: GO111MODULE=on
os:
- linux
- osx
dist: bionic # For fish >= 2.3.0 string builtin
addons:
apt:
packages:
- fish
- zsh
homebrew:
packages:
- fish
- tmux
update: true
script:
- make test
# LC_ALL=C to avoid escape codes in
# printf %q $'\355\205\214\354\212\244\355\212\270' on macOS. Bash on
# macOS is built without HANDLE_MULTIBYTE?
- make install && ./install --all && LC_ALL=C tmux new-session -d && ruby test/test_go.rb --verbose

680
ADVANCED.md Normal file
View File

@@ -0,0 +1,680 @@
Advanced fzf examples
======================
* *Last update: 2024/01/20*
* *Requires fzf 0.46.0 or above*
---
<!-- vim-markdown-toc GFM -->
* [Introduction](#introduction)
* [Screen Layout](#screen-layout)
* [`--height`](#--height)
* [`fzf-tmux`](#fzf-tmux)
* [Popup window support](#popup-window-support)
* [Dynamic reloading of the list](#dynamic-reloading-of-the-list)
* [Updating the list of processes by pressing CTRL-R](#updating-the-list-of-processes-by-pressing-ctrl-r)
* [Toggling between data sources](#toggling-between-data-sources)
* [Toggling with a single key binding](#toggling-with-a-single-key-binding)
* [Ripgrep integration](#ripgrep-integration)
* [Using fzf as the secondary filter](#using-fzf-as-the-secondary-filter)
* [Using fzf as interactive Ripgrep launcher](#using-fzf-as-interactive-ripgrep-launcher)
* [Switching to fzf-only search mode](#switching-to-fzf-only-search-mode)
* [Switching between Ripgrep mode and fzf mode](#switching-between-ripgrep-mode-and-fzf-mode)
* [Switching between Ripgrep mode and fzf mode using a single key binding](#switching-between-ripgrep-mode-and-fzf-mode-using-a-single-key-binding)
* [Log tailing](#log-tailing)
* [Key bindings for git objects](#key-bindings-for-git-objects)
* [Files listed in `git status`](#files-listed-in-git-status)
* [Branches](#branches)
* [Commit hashes](#commit-hashes)
* [Color themes](#color-themes)
* [fzf Theme Playground](#fzf-theme-playground)
* [Generating fzf color theme from Vim color schemes](#generating-fzf-color-theme-from-vim-color-schemes)
<!-- vim-markdown-toc -->
Introduction
------------
fzf is an interactive [Unix filter][filter] program that is designed to be
used with other Unix tools. It reads a list of items from the standard input,
allows you to select a subset of the items, and prints the selected ones to
the standard output. You can think of it as an interactive version of *grep*,
and it's already useful even if you don't know any of its options.
```sh
# 1. ps: Feed the list of processes to fzf
# 2. fzf: Interactively select a process using fuzzy matching algorithm
# 3. awk: Take the PID from the selected line
# 3. kill: Kill the process with the PID
ps -ef | fzf | awk '{print $2}' | xargs kill -9
```
[filter]: https://en.wikipedia.org/wiki/Filter_(software)
While the above example succinctly summarizes the fundamental concept of fzf,
you can build much more sophisticated interactive workflows using fzf once you
learn its wide variety of features.
- To see the full list of options and features, see `man fzf`
- To see the latest additions, see [CHANGELOG.md](CHANGELOG.md)
This document will guide you through some examples that will familiarize you
with the advanced features of fzf.
Screen Layout
-------------
### `--height`
fzf by default opens in fullscreen mode, but it's not always desirable.
Oftentimes, you want to see the current context of the terminal while using
fzf. `--height` is an option for opening fzf below the cursor in
non-fullscreen mode so you can still see the previous commands and their
results above it.
```sh
fzf --height=40%
```
![image](https://user-images.githubusercontent.com/700826/113379893-c184c680-93b5-11eb-9676-c7c0a2f01748.png)
You might also want to experiment with other layout options such as
`--layout=reverse`, `--info=inline`, `--border`, `--margin`, etc.
```sh
fzf --height=40% --layout=reverse
fzf --height=40% --layout=reverse --info=inline
fzf --height=40% --layout=reverse --info=inline --border
fzf --height=40% --layout=reverse --info=inline --border --margin=1
fzf --height=40% --layout=reverse --info=inline --border --margin=1 --padding=1
```
![image](https://user-images.githubusercontent.com/700826/113379932-dfeac200-93b5-11eb-9e28-df1a2ee71f90.png)
*(See `Layout` section of the man page to see the full list of options)*
But you definitely don't want to repeat `--height=40% --layout=reverse
--info=inline --border --margin=1 --padding=1` every time you use fzf. You
could write a wrapper script or shell alias, but there is an easier option.
Define `$FZF_DEFAULT_OPTS` like so:
```sh
export FZF_DEFAULT_OPTS="--height=40% --layout=reverse --info=inline --border --margin=1 --padding=1"
```
### `fzf-tmux`
Before fzf had `--height` option, we would open fzf in a tmux split pane not
to take up the whole screen. This is done using `fzf-tmux` script.
```sh
# Open fzf on a tmux split pane below the current pane.
# Takes the same set of options.
fzf-tmux --layout=reverse
```
![image](https://user-images.githubusercontent.com/700826/113379973-f1cc6500-93b5-11eb-8860-c9bc4498aadf.png)
The limitation of `fzf-tmux` is that it only works when you're on tmux unlike
`--height` option. But the advantage of it is that it's more flexible.
(See `man fzf-tmux` for available options.)
```sh
# On the right (50%)
fzf-tmux -r
# On the left (30%)
fzf-tmux -l30%
# Above the cursor
fzf-tmux -u30%
```
![image](https://user-images.githubusercontent.com/700826/113379983-fa24a000-93b5-11eb-93eb-8a3d39b2f163.png)
![image](https://user-images.githubusercontent.com/700826/113380001-0577cb80-93b6-11eb-95d0-2ba453866882.png)
![image](https://user-images.githubusercontent.com/700826/113380040-1d4f4f80-93b6-11eb-9bef-737fb120aafe.png)
#### Popup window support
But here's the really cool part; tmux 3.2 added support for popup windows. So
you can open fzf in a popup window, which is quite useful if you frequently
use split panes.
```sh
# Open tmux in a tmux popup window (default size: 50% of the screen)
fzf-tmux -p
# 80% width, 60% height
fzf-tmux -p 80%,60%
```
![image](https://user-images.githubusercontent.com/700826/113380106-4a9bfd80-93b6-11eb-8cee-aeb1c4ce1a1f.png)
> You might also want to check out my tmux plugins which support this popup
> window layout.
>
> - https://github.com/junegunn/tmux-fzf-url
> - https://github.com/junegunn/tmux-fzf-maccy
Dynamic reloading of the list
-----------------------------
fzf can dynamically update the candidate list using an arbitrary program with
`reload` bindings (The design document for `reload` can be found
[here][reload]).
[reload]: https://github.com/junegunn/fzf/issues/1750
### Updating the list of processes by pressing CTRL-R
This example shows how you can set up a binding for dynamically updating the
list without restarting fzf.
```sh
(date; ps -ef) |
fzf --bind='ctrl-r:reload(date; ps -ef)' \
--header=$'Press CTRL-R to reload\n\n' --header-lines=2 \
--preview='echo {}' --preview-window=down,3,wrap \
--layout=reverse --height=80% | awk '{print $2}' | xargs kill -9
```
![image](https://user-images.githubusercontent.com/700826/113465047-200c7c00-946c-11eb-918c-268f37a900c8.png)
- The initial command is `(date; ps -ef)`. It prints the current date and
time, and the list of the processes.
- With `--header` option, you can show any message as the fixed header.
- To disallow selecting the first two lines (`date` and `ps` header), we use
`--header-lines=2` option.
- `--bind='ctrl-r:reload(date; ps -ef)'` binds CTRL-R to `reload` action that
runs `date; ps -ef`, so we can update the list of the processes by pressing
CTRL-R.
- We use simple `echo {}` preview option, so we can see the entire line on the
preview window below even if it's too long
### Toggling between data sources
You're not limited to just one reload binding. Set up multiple bindings so
you can switch between data sources.
```sh
find * | fzf --prompt 'All> ' \
--header 'CTRL-D: Directories / CTRL-F: Files' \
--bind 'ctrl-d:change-prompt(Directories> )+reload(find * -type d)' \
--bind 'ctrl-f:change-prompt(Files> )+reload(find * -type f)'
```
![image](https://user-images.githubusercontent.com/700826/113465073-4af6d000-946c-11eb-858f-2372c0955f67.png)
![image](https://user-images.githubusercontent.com/700826/113465072-46321c00-946c-11eb-9b6f-cda3951df579.png)
### Toggling with a single key binding
The above example uses two different key bindings to toggle between two modes,
but can we just use a single key binding?
To make a key binding behave differently each time it is pressed, we need:
1. a way to store the current state. i.e. "which mode are we in?"
2. and a way to dynamically perform different actions depending on the state.
The following example shows how to 1. store the current mode in the prompt
string, 2. and use this information (`$FZF_PROMPT`) to determine which
actions to perform using the `transform` action.
```sh
fd --type file |
fzf --prompt 'Files> ' \
--header 'CTRL-T: Switch between Files/Directories' \
--bind 'ctrl-t:transform:[[ ! $FZF_PROMPT =~ Files ]] &&
echo "change-prompt(Files> )+reload(fd --type file)" ||
echo "change-prompt(Directories> )+reload(fd --type directory)"' \
--preview '[[ $FZF_PROMPT =~ Files ]] && bat --color=always {} || tree -C {}'
```
Ripgrep integration
-------------------
### Using fzf as the secondary filter
* Requires [bat][bat]
* Requires [Ripgrep][rg]
[bat]: https://github.com/sharkdp/bat
[rg]: https://github.com/BurntSushi/ripgrep
fzf is pretty fast for filtering a list that you will rarely have to think
about its performance. But it is not the right tool for searching for text
inside many large files, and in that case you should definitely use something
like [Ripgrep][rg].
In the next example, Ripgrep is the primary filter that searches for the given
text in files, and fzf is used as the secondary fuzzy filter that adds
interactivity to the workflow. And we use [bat][bat] to show the matching line in
the preview window.
This is a bash script and it will not run as expected on other non-compliant
shells. To avoid the compatibility issue, let's save this snippet as a script
file called `rfv`.
```bash
#!/usr/bin/env bash
# 1. Search for text in files using Ripgrep
# 2. Interactively narrow down the list using fzf
# 3. Open the file in Vim
rg --color=always --line-number --no-heading --smart-case "${*:-}" |
fzf --ansi \
--color "hl:-1:underline,hl+:-1:underline:reverse" \
--delimiter : \
--preview 'bat --color=always {1} --highlight-line {2}' \
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \
--bind 'enter:become(vim {1} +{2})'
```
And run it with an initial query string.
```sh
# Make the script executable
chmod +x rfv
# Run it with the initial query "algo"
./rfv algo
```
> Ripgrep will perform the initial search and list all the lines that contain
`algo`. Then we further narrow down the list on fzf.
![image](https://user-images.githubusercontent.com/700826/113683873-a42a6200-96ff-11eb-9666-26ce4091b0e4.png)
I know it's a lot to digest, let's try to break down the code.
- Ripgrep prints the matching lines in the following format
```
man/man1/fzf.1:54:.BI "--algo=" TYPE
man/man1/fzf.1:55:Fuzzy matching algorithm (default: v2)
man/man1/fzf.1:58:.BR v2 " Optimal scoring algorithm (quality)"
src/pattern_test.go:7: "github.com/junegunn/fzf/src/algo"
```
The first token delimited by `:` is the file path, and the second token is
the line number of the matching line. They respectively correspond to `{1}`
and `{2}` in the preview command.
- `--preview 'bat --color=always {1} --highlight-line {2}'`
- As we run `rg` with `--color=always` option, we should tell fzf to parse
ANSI color codes in the input by setting `--ansi`.
- We customize how fzf colors various text elements using `--color` option.
`-1` tells fzf to keep the original color from the input. See `man fzf` for
available color options.
- The value of `--preview-window` option consists of 5 components delimited
by `,`
1. `up` — Position of the preview window
1. `60%` — Size of the preview window
1. `border-bottom` — Preview window border only on the bottom side
1. `+{2}+3/3` — Scroll offset of the preview contents
1. `~3` — Fixed header
- Let's break down the latter two. We want to display the bat output in the
preview window with a certain scroll offset so that the matching line is
positioned near the center of the preview window.
- `+{2}` — The base offset is extracted from the second token
- `+3` — We add 3 lines to the base offset to compensate for the header
part of `bat` output
- ```
───────┬──────────────────────────────────────────────────────────
│ File: CHANGELOG.md
───────┼──────────────────────────────────────────────────────────
1 │ CHANGELOG
2 │ =========
3 │
4 │ 0.26.0
5 │ ------
```
- `/3` adjusts the offset so that the matching line is shown at a third
position in the window
- `~3` makes the top three lines fixed header so that they are always
visible regardless of the scroll offset
- Instead of using shell script to process the final output of fzf, we use
`become(...)` action which was added in [fzf 0.38.0][0.38.0] to turn fzf
into a new process that opens the file with `vim` (`vim {1}`) and move the
cursor to the line (`+{2}`).
[0.38.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0380
### Using fzf as interactive Ripgrep launcher
We have learned that we can bind `reload` action to a key (e.g.
`--bind=ctrl-r:execute(ps -ef)`). In the next example, we are going to **bind
`reload` action to `change` event** so that whenever the user *changes* the
query string on fzf, `reload` action is triggered.
Here is a variation of the above `rfv` script. fzf will restart Ripgrep every
time the user updates the query string on fzf. Searching and filtering is
completely done by Ripgrep, and fzf merely provides the interactive interface.
So we lose the "fuzziness", but the performance will be better on larger
projects, and it will free up memory as you narrow down the results.
```bash
#!/usr/bin/env bash
# 1. Search for text in files using Ripgrep
# 2. Interactively restart Ripgrep with reload action
# 3. Open the file in Vim
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
INITIAL_QUERY="${*:-}"
: | fzf --ansi --disabled --query "$INITIAL_QUERY" \
--bind "start:reload:$RG_PREFIX {q}" \
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
--delimiter : \
--preview 'bat --color=always {1} --highlight-line {2}' \
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \
--bind 'enter:become(vim {1} +{2})'
```
![image](https://user-images.githubusercontent.com/700826/113684212-f9ff0a00-96ff-11eb-8737-7bb571d320cc.png)
- Instead of starting fzf in the usual `rg ... | fzf` form, we start fzf with
an empty input (`: | fzf`), then we make it start the initial Ripgrep
process immediately via `start:reload` binding. This way, fzf owns the
initial Ripgrep process so it can kill it on the next `reload`. Otherwise,
the process will keep running in the background.
- Filtering is no longer a responsibility of fzf; hence `--disabled`
- `{q}` in the reload command evaluates to the query string on fzf prompt.
- `sleep 0.1` in the reload command is for "debouncing". This small delay will
reduce the number of intermediate Ripgrep processes while we're typing in
a query.
### Switching to fzf-only search mode
In the previous example, we lost fuzzy matching capability as we completely
delegated search functionality to Ripgrep. But we can dynamically switch to
fzf-only search mode by *"unbinding"* `reload` action from `change` event.
```sh
#!/usr/bin/env bash
# Two-phase filtering with Ripgrep and fzf
#
# 1. Search for text in files using Ripgrep
# 2. Interactively restart Ripgrep with reload action
# * Press alt-enter to switch to fzf-only filtering
# 3. Open the file in Vim
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
INITIAL_QUERY="${*:-}"
: | fzf --ansi --disabled --query "$INITIAL_QUERY" \
--bind "start:reload:$RG_PREFIX {q}" \
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
--bind "alt-enter:unbind(change,alt-enter)+change-prompt(2. fzf> )+enable-search+clear-query" \
--color "hl:-1:underline,hl+:-1:underline:reverse" \
--prompt '1. ripgrep> ' \
--delimiter : \
--preview 'bat --color=always {1} --highlight-line {2}' \
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \
--bind 'enter:become(vim {1} +{2})'
```
* Phase 1. Filtering with Ripgrep
![image](https://user-images.githubusercontent.com/700826/119213880-735e8a80-bafd-11eb-8493-123e4be24fbc.png)
* Phase 2. Filtering with fzf
![image](https://user-images.githubusercontent.com/700826/119213887-7e191f80-bafd-11eb-98c9-71a1af9d7aab.png)
- We added `--prompt` option to show that fzf is initially running in "Ripgrep
launcher mode".
- We added `alt-enter` binding that
1. unbinds `change` event, so Ripgrep is no longer restarted on key press
2. changes the prompt to `2. fzf>`
3. enables search functionality of fzf
4. clears the current query string that was used to start Ripgrep process
5. and unbinds `alt-enter` itself as this is a one-off event
- We reverted `--color` option for customizing how the matching chunks are
displayed in the second phase
### Switching between Ripgrep mode and fzf mode
[fzf 0.30.0][0.30.0] added `rebind` action so we can "rebind" the bindings
that were previously "unbound" via `unbind`.
This is an improved version of the previous example that allows us to switch
between Ripgrep launcher mode and fzf-only filtering mode via CTRL-R and
CTRL-F.
```sh
#!/usr/bin/env bash
# Switch between Ripgrep launcher mode (CTRL-R) and fzf filtering mode (CTRL-F)
rm -f /tmp/rg-fzf-{r,f}
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
INITIAL_QUERY="${*:-}"
: | fzf --ansi --disabled --query "$INITIAL_QUERY" \
--bind "start:reload($RG_PREFIX {q})+unbind(ctrl-r)" \
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
--bind "ctrl-f:unbind(change,ctrl-f)+change-prompt(2. fzf> )+enable-search+rebind(ctrl-r)+transform-query(echo {q} > /tmp/rg-fzf-r; cat /tmp/rg-fzf-f)" \
--bind "ctrl-r:unbind(ctrl-r)+change-prompt(1. ripgrep> )+disable-search+reload($RG_PREFIX {q} || true)+rebind(change,ctrl-f)+transform-query(echo {q} > /tmp/rg-fzf-f; cat /tmp/rg-fzf-r)" \
--color "hl:-1:underline,hl+:-1:underline:reverse" \
--prompt '1. ripgrep> ' \
--delimiter : \
--header ' CTRL-R (ripgrep mode) CTRL-F (fzf mode) ' \
--preview 'bat --color=always {1} --highlight-line {2}' \
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \
--bind 'enter:become(vim {1} +{2})'
```
- To restore the query string when switching between modes, we store the
current query in `/tmp/rg-fzf-{r,f}` files and restore the query using
`transform-query` action which was added in [fzf 0.36.0][0.36.0].
- Also note that we unbind `ctrl-r` binding on `start` event which is
triggered once when fzf starts.
[0.30.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0300
[0.36.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0360
### Switching between Ripgrep mode and fzf mode using a single key binding
In contrast to the previous version, we use just one hotkey to toggle between
ripgrep and fzf mode. This is achieved by using the `$FZF_PROMPT` as a state
within the `transform` action, a feature introduced in [fzf 0.45.0][0.45.0]. A
more detailed explanation of this feature can be found in a previous section -
[Toggling with a single keybinding](#toggling-with-a-single-key-binding).
[0.45.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0450
When using the `transform` action, the placeholder (`\{q}`) should be escaped to
prevent immediate evaluation.
```sh
#!/usr/bin/env bash
# Switch between Ripgrep mode and fzf filtering mode (CTRL-T)
rm -f /tmp/rg-fzf-{r,f}
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
INITIAL_QUERY="${*:-}"
: | fzf --ansi --disabled --query "$INITIAL_QUERY" \
--bind "start:reload:$RG_PREFIX {q}" \
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
--bind 'ctrl-t:transform:[[ ! $FZF_PROMPT =~ ripgrep ]] &&
echo "rebind(change)+change-prompt(1. ripgrep> )+disable-search+transform-query:echo \{q} > /tmp/rg-fzf-f; cat /tmp/rg-fzf-r" ||
echo "unbind(change)+change-prompt(2. fzf> )+enable-search+transform-query:echo \{q} > /tmp/rg-fzf-r; cat /tmp/rg-fzf-f"' \
--color "hl:-1:underline,hl+:-1:underline:reverse" \
--prompt '1. ripgrep> ' \
--delimiter : \
--header 'CTRL-T: Switch between ripgrep/fzf' \
--preview 'bat --color=always {1} --highlight-line {2}' \
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \
--bind 'enter:become(vim {1} +{2})'
```
Log tailing
-----------
fzf can run long-running preview commands and render partial results before
completion. And when you specify `follow` flag in `--preview-window` option,
fzf will "`tail -f`" the result, automatically scrolling to the bottom.
```bash
# With "follow", preview window will automatically scroll to the bottom.
# "\033[2J" is an ANSI escape sequence for clearing the screen.
# When fzf reads this code it clears the previous preview contents.
fzf --preview-window follow --preview 'for i in $(seq 100000); do
echo "$i"
sleep 0.01
(( i % 300 == 0 )) && printf "\033[2J"
done'
```
![image](https://user-images.githubusercontent.com/700826/113473303-dd669600-94a3-11eb-88a9-1f61b996bb0e.png)
Admittedly, that was a silly example. Here's a practical one for browsing
Kubernetes pods.
```bash
pods() {
: | command='kubectl get pods --all-namespaces' fzf \
--info=inline --layout=reverse --header-lines=1 \
--prompt "$(kubectl config current-context | sed 's/-context$//')> " \
--header $' Enter (kubectl exec) CTRL-O (open log in editor) CTRL-R (reload) \n\n' \
--bind 'start:reload:$command' \
--bind 'ctrl-r:reload:$command' \
--bind 'ctrl-/:change-preview-window(80%,border-bottom|hidden|)' \
--bind 'enter:execute:kubectl exec -it --namespace {1} {2} -- bash > /dev/tty' \
--bind 'ctrl-o:execute:${EDITOR:-vim} <(kubectl logs --all-containers --namespace {1} {2}) > /dev/tty' \
--preview-window up:follow \
--preview 'kubectl logs --follow --all-containers --tail=10000 --namespace {1} {2}' "$@"
}
```
![image](https://user-images.githubusercontent.com/700826/113473547-1d7a4880-94a5-11eb-98ef-9aa6f0ed215a.png)
- The preview window will *"log tail"* the pod
- Holding on to a large amount of log will consume a lot of memory. So we
limited the initial log amount with `--tail=10000`.
- `execute` bindings allow you to run any command without leaving fzf
- Press enter key on a pod to `kubectl exec` into it
- Press CTRL-O to open the log in your editor
- Press CTRL-R to reload the pod list
- Press CTRL-/ repeatedly to rotate through a different sets of preview
window options
1. `80%,border-bottom`
1. `hidden`
1. Empty string after `|` translates to the default options from `--preview-window`
Key bindings for git objects
----------------------------
Oftentimes, you want to put the identifiers of various Git object to the
command-line. For example, it is common to write commands like these:
```sh
git checkout [SOME_COMMIT_HASH or BRANCH or TAG]
git diff [SOME_COMMIT_HASH or BRANCH or TAG] [SOME_COMMIT_HASH or BRANCH or TAG]
```
[fzf-git.sh](https://github.com/junegunn/fzf-git.sh) project defines a set of
fzf-based key bindings for Git objects. I strongly recommend that you check
them out because they are seriously useful.
### Files listed in `git status`
<kbd>CTRL-G</kbd><kbd>CTRL-F</kbd>
![image](https://user-images.githubusercontent.com/700826/113473779-a9d93b00-94a6-11eb-87b5-f62a8d0a0efc.png)
### Branches
<kbd>CTRL-G</kbd><kbd>CTRL-B</kbd>
![image](https://user-images.githubusercontent.com/700826/113473758-87dfb880-94a6-11eb-82f4-9218103f10bd.png)
### Commit hashes
<kbd>CTRL-G</kbd><kbd>CTRL-H</kbd>
![image](https://user-images.githubusercontent.com/700826/113473765-91692080-94a6-11eb-8d38-ed4d41f27ac1.png)
Color themes
------------
You can customize how fzf colors the text elements with `--color` option. Here
are a few color themes. Note that you need a terminal emulator that can
display 24-bit colors.
```sh
# junegunn/seoul256.vim (dark)
export FZF_DEFAULT_OPTS='--color=bg+:#3F3F3F,bg:#4B4B4B,border:#6B6B6B,spinner:#98BC99,hl:#719872,fg:#D9D9D9,header:#719872,info:#BDBB72,pointer:#E12672,marker:#E17899,fg+:#D9D9D9,preview-bg:#3F3F3F,prompt:#98BEDE,hl+:#98BC99'
```
![seoul256](https://user-images.githubusercontent.com/700826/113475011-2c192d80-94ae-11eb-9d17-1e5867bae01f.png)
```sh
# junegunn/seoul256.vim (light)
export FZF_DEFAULT_OPTS='--color=bg+:#D9D9D9,bg:#E1E1E1,border:#C8C8C8,spinner:#719899,hl:#719872,fg:#616161,header:#719872,info:#727100,pointer:#E12672,marker:#E17899,fg+:#616161,preview-bg:#D9D9D9,prompt:#0099BD,hl+:#719899'
```
![seoul256-light](https://user-images.githubusercontent.com/700826/113475022-389d8600-94ae-11eb-905f-0939dd535837.png)
```sh
# morhetz/gruvbox
export FZF_DEFAULT_OPTS='--color=bg+:#3c3836,bg:#32302f,spinner:#fb4934,hl:#928374,fg:#ebdbb2,header:#928374,info:#8ec07c,pointer:#fb4934,marker:#fb4934,fg+:#ebdbb2,prompt:#fb4934,hl+:#fb4934'
```
![gruvbox](https://user-images.githubusercontent.com/700826/113475042-494dfc00-94ae-11eb-9322-cd03a027305a.png)
```sh
# arcticicestudio/nord-vim
export FZF_DEFAULT_OPTS='--color=bg+:#3B4252,bg:#2E3440,spinner:#81A1C1,hl:#616E88,fg:#D8DEE9,header:#616E88,info:#81A1C1,pointer:#81A1C1,marker:#81A1C1,fg+:#D8DEE9,prompt:#81A1C1,hl+:#81A1C1'
```
![nord](https://user-images.githubusercontent.com/700826/113475063-67b3f780-94ae-11eb-9b24-5f0d22b63399.png)
```sh
# tomasr/molokai
export FZF_DEFAULT_OPTS='--color=bg+:#293739,bg:#1B1D1E,border:#808080,spinner:#E6DB74,hl:#7E8E91,fg:#F8F8F2,header:#7E8E91,info:#A6E22E,pointer:#A6E22E,marker:#F92672,fg+:#F8F8F2,prompt:#F92672,hl+:#F92672'
```
![molokai](https://user-images.githubusercontent.com/700826/113475085-8619f300-94ae-11eb-85e4-2766fc3246bf.png)
### fzf Theme Playground
[fzf Theme Playground](https://vitormv.github.io/fzf-themes/) created by
[Vitor Mello](https://github.com/vitormv) is a webpage where you can
interactively create fzf themes.
### Generating fzf color theme from Vim color schemes
The Vim plugin of fzf can generate `--color` option from the current color
scheme according to `g:fzf_colors` variable. You can find the detailed
explanation [here](https://github.com/junegunn/fzf/blob/master/README-VIM.md#explanation-of-gfzf_colors).
Here is an example. Add this to your Vim configuration file.
```vim
let g:fzf_colors =
\ { 'fg': ['fg', 'Normal'],
\ 'bg': ['bg', 'Normal'],
\ 'preview-bg': ['bg', 'NormalFloat'],
\ 'hl': ['fg', 'Comment'],
\ 'fg+': ['fg', 'CursorLine', 'CursorColumn', 'Normal'],
\ 'bg+': ['bg', 'CursorLine', 'CursorColumn'],
\ 'hl+': ['fg', 'Statement'],
\ 'info': ['fg', 'PreProc'],
\ 'border': ['fg', 'Ignore'],
\ 'prompt': ['fg', 'Conditional'],
\ 'pointer': ['fg', 'Exception'],
\ 'marker': ['fg', 'Keyword'],
\ 'spinner': ['fg', 'Label'],
\ 'header': ['fg', 'Comment'] }
```
Then you can see how the `--color` option is generated by printing the result
of `fzf#wrap()`.
```vim
:echo fzf#wrap()
```
Use this command to append `export FZF_DEFAULT_OPTS="..."` line to the end of
the current file.
```vim
:call append('$', printf('export FZF_DEFAULT_OPTS="%s"', matchstr(fzf#wrap().options, "--color[^']*")))
```

View File

@@ -6,7 +6,7 @@ Build instructions
### Prerequisites
- Go 1.11 or above
- Go 1.20 or above
### Using Makefile
@@ -17,33 +17,33 @@ make
# Build fzf binary and copy it to bin directory
make install
# Build 32-bit and 64-bit executables and tarballs in target
# Build fzf binaries and archives for all platforms using goreleaser
make build
# Publish GitHub release
make release
# Make release archives for all supported platforms in target
make release-all
```
### Using `go get`
Alternatively, you can build fzf directly with `go get` command without
manually cloning the repository.
```sh
go get -u github.com/junegunn/fzf
```
> :warning: Makefile uses git commands to determine the version and the
> revision information for `fzf --version`. So if you're building fzf from an
> environment where its git information is not available, you have to manually
> set `$FZF_VERSION` and `$FZF_REVISION`.
>
> e.g. `FZF_VERSION=0.24.0 FZF_REVISION=tarball make`
Third-party libraries used
--------------------------
- [mattn/go-runewidth](https://github.com/mattn/go-runewidth)
- Licensed under [MIT](http://mattn.mit-license.org)
- [rivo/uniseg](https://github.com/rivo/uniseg)
- Licensed under [MIT](https://raw.githubusercontent.com/rivo/uniseg/master/LICENSE.txt)
- [mattn/go-shellwords](https://github.com/mattn/go-shellwords)
- Licensed under [MIT](http://mattn.mit-license.org)
- [mattn/go-isatty](https://github.com/mattn/go-isatty)
- Licensed under [MIT](http://mattn.mit-license.org)
- [tcell](https://github.com/gdamore/tcell)
- Licensed under [Apache License 2.0](https://github.com/gdamore/tcell/blob/master/LICENSE)
- [fastwalk](https://github.com/charlievieth/fastwalk)
- Licensed under [MIT](https://raw.githubusercontent.com/charlievieth/fastwalk/master/LICENSE)
License
-------

File diff suppressed because it is too large Load Diff

View File

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

View File

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

143
Makefile
View File

@@ -1,28 +1,42 @@
SHELL := bash
GO ?= go
GOOS ?= $(word 1, $(subst /, " ", $(word 4, $(shell go version))))
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
ROOT_DIR := $(shell dirname $(MAKEFILE))
SOURCES := $(wildcard *.go src/*.go src/*/*.go) $(MAKEFILE)
SOURCES := $(wildcard *.go src/*.go src/*/*.go shell/*sh) $(MAKEFILE)
REVISION := $(shell git log -n 1 --pretty=format:%h -- $(SOURCES))
BUILD_FLAGS := -a -ldflags "-X main.revision=$(REVISION) -w -extldflags=$(LDFLAGS)" -tags "$(TAGS)"
ifdef FZF_VERSION
VERSION := $(FZF_VERSION)
else
VERSION := $(shell git describe --abbrev=0 2> /dev/null)
endif
ifeq ($(VERSION),)
$(error Not on git repository; cannot determine $$FZF_VERSION)
endif
VERSION_TRIM := $(shell sed "s/-.*//" <<< $(VERSION))
VERSION_REGEX := $(subst .,\.,$(VERSION_TRIM))
ifdef FZF_REVISION
REVISION := $(FZF_REVISION)
else
REVISION := $(shell git log -n 1 --pretty=format:%h --abbrev=8 -- $(SOURCES) 2> /dev/null)
endif
ifeq ($(REVISION),)
$(error Not on git repository; cannot determine $$FZF_REVISION)
endif
BUILD_FLAGS := -a -ldflags "-s -w -X main.version=$(VERSION) -X main.revision=$(REVISION)" -tags "$(TAGS)"
BINARY32 := fzf-$(GOOS)_386
BINARY64 := fzf-$(GOOS)_amd64
BINARYS390 := fzf-$(GOOS)_s390x
BINARYARM5 := fzf-$(GOOS)_arm5
BINARYARM6 := fzf-$(GOOS)_arm6
BINARYARM7 := fzf-$(GOOS)_arm7
BINARYARM8 := fzf-$(GOOS)_arm8
BINARYPPC64LE := fzf-$(GOOS)_ppc64le
VERSION := $(shell awk -F= '/version =/ {print $$2}' src/constants.go | tr -d "\" ")
RELEASE32 := fzf-$(VERSION)-$(GOOS)_386
RELEASE64 := fzf-$(VERSION)-$(GOOS)_amd64
RELEASEARM5 := fzf-$(VERSION)-$(GOOS)_arm5
RELEASEARM6 := fzf-$(VERSION)-$(GOOS)_arm6
RELEASEARM7 := fzf-$(VERSION)-$(GOOS)_arm7
RELEASEARM8 := fzf-$(VERSION)-$(GOOS)_arm8
RELEASEPPC64LE := fzf-$(VERSION)-$(GOOS)_ppc64le
BINARYRISCV64 := fzf-$(GOOS)_riscv64
BINARYLOONG64 := fzf-$(GOOS)_loong64
# https://en.wikipedia.org/wiki/Uname
UNAME_M := $(shell uname -m)
@@ -30,6 +44,8 @@ ifeq ($(UNAME_M),x86_64)
BINARY := $(BINARY64)
else ifeq ($(UNAME_M),amd64)
BINARY := $(BINARY64)
else ifeq ($(UNAME_M),s390x)
BINARY := $(BINARYS390)
else ifeq ($(UNAME_M),i686)
BINARY := $(BINARY32)
else ifeq ($(UNAME_M),i386)
@@ -41,60 +57,86 @@ else ifeq ($(UNAME_M),armv6l)
else ifeq ($(UNAME_M),armv7l)
BINARY := $(BINARYARM7)
else ifeq ($(UNAME_M),armv8l)
# armv8l is always 32-bit and should implement the armv7 ISA, so
# just use the same filename as for armv7.
BINARY := $(BINARYARM7)
else ifeq ($(UNAME_M),arm64)
BINARY := $(BINARYARM8)
else ifeq ($(UNAME_M),aarch64)
BINARY := $(BINARYARM8)
else ifeq ($(UNAME_M),ppc64le)
BINARY := $(BINARYPPC64LE)
else ifeq ($(UNAME_M),riscv64)
BINARY := $(BINARYRISCV64)
else ifeq ($(UNAME_M),loongarch64)
BINARY := $(BINARYLOONG64)
else
$(error "Build on $(UNAME_M) is not supported, yet.")
$(error Build on $(UNAME_M) is not supported, yet.)
endif
all: target/$(BINARY)
target:
mkdir -p $@
ifeq ($(GOOS),windows)
release: target/$(BINARY32) target/$(BINARY64)
cd target && cp -f $(BINARY32) fzf.exe && zip $(RELEASE32).zip fzf.exe
cd target && cp -f $(BINARY64) fzf.exe && zip $(RELEASE64).zip fzf.exe
cd target && rm -f fzf.exe
else ifeq ($(GOOS),linux)
release: target/$(BINARY32) target/$(BINARY64) target/$(BINARYARM5) target/$(BINARYARM6) target/$(BINARYARM7) target/$(BINARYARM8) target/$(BINARYPPC64LE)
cd target && cp -f $(BINARY32) fzf && tar -czf $(RELEASE32).tgz fzf
cd target && cp -f $(BINARY64) fzf && tar -czf $(RELEASE64).tgz fzf
cd target && cp -f $(BINARYARM5) fzf && tar -czf $(RELEASEARM5).tgz fzf
cd target && cp -f $(BINARYARM6) fzf && tar -czf $(RELEASEARM6).tgz fzf
cd target && cp -f $(BINARYARM7) fzf && tar -czf $(RELEASEARM7).tgz fzf
cd target && cp -f $(BINARYARM8) fzf && tar -czf $(RELEASEARM8).tgz fzf
cd target && cp -f $(BINARYPPC64LE) fzf && tar -czf $(RELEASEPPC64LE).tgz fzf
cd target && rm -f fzf
else
release: target/$(BINARY32) target/$(BINARY64)
cd target && cp -f $(BINARY32) fzf && tar -czf $(RELEASE32).tgz fzf
cd target && cp -f $(BINARY64) fzf && tar -czf $(RELEASE64).tgz fzf
cd target && rm -f fzf
endif
release-all: clean test
GOOS=darwin make release
GOOS=linux make release
GOOS=freebsd make release
GOOS=openbsd make release
GOOS=windows make release
test: $(SOURCES)
[ -z "$$(gofmt -s -d src)" ] || (gofmt -s -d src; exit 1)
SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \
github.com/junegunn/fzf/src \
github.com/junegunn/fzf/src/algo \
github.com/junegunn/fzf/src/tui \
github.com/junegunn/fzf/src/util
bench:
cd src && SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" -run=Bench -bench=. -benchmem
install: bin/fzf
generate:
PATH=$(PATH):$(GOPATH)/bin $(GO) generate ./...
build:
goreleaser build --clean --snapshot --skip=post-hooks
release:
# Make sure that the tests pass and the build works
TAGS=tcell make test
make test build clean
ifndef GITHUB_TOKEN
$(error GITHUB_TOKEN is not defined)
endif
# Check if we are on master branch
ifneq ($(shell git symbolic-ref --short HEAD),master)
$(error Not on master branch)
endif
# Check if version numbers are properly updated
grep -q ^$(VERSION_REGEX)$$ CHANGELOG.md
grep -qF '"fzf $(VERSION_TRIM)"' man/man1/fzf.1
grep -qF '"fzf $(VERSION_TRIM)"' man/man1/fzf-tmux.1
grep -qF $(VERSION) install
grep -qF $(VERSION) install.ps1
# Make release note out of CHANGELOG.md
mkdir -p tmp
sed -n '/^$(VERSION_REGEX)$$/,/^[0-9]/p' CHANGELOG.md | tail -r | \
sed '1,/^ *$$/d' | tail -r | sed 1,2d | tee tmp/release-note
# Push to temp branch first so that install scripts always works on master branch
git checkout -B temp master
git push origin temp --follow-tags --force
# Make a GitHub release
goreleaser --clean --release-notes tmp/release-note
# Push to master
git checkout master
git push origin master
# Delete temp branch
git push origin --delete temp
clean:
$(RM) -r target
$(RM) -r dist target
target/$(BINARY32): $(SOURCES)
GOARCH=386 $(GO) build $(BUILD_FLAGS) -o $@
@@ -102,6 +144,8 @@ target/$(BINARY32): $(SOURCES)
target/$(BINARY64): $(SOURCES)
GOARCH=amd64 $(GO) build $(BUILD_FLAGS) -o $@
target/$(BINARYS390): $(SOURCES)
GOARCH=s390x $(GO) build $(BUILD_FLAGS) -o $@
# https://github.com/golang/go/wiki/GoArm
target/$(BINARYARM5): $(SOURCES)
GOARCH=arm GOARM=5 $(GO) build $(BUILD_FLAGS) -o $@
@@ -118,7 +162,14 @@ target/$(BINARYARM8): $(SOURCES)
target/$(BINARYPPC64LE): $(SOURCES)
GOARCH=ppc64le $(GO) build $(BUILD_FLAGS) -o $@
target/$(BINARYRISCV64): $(SOURCES)
GOARCH=riscv64 $(GO) build $(BUILD_FLAGS) -o $@
target/$(BINARYLOONG64): $(SOURCES)
GOARCH=loong64 $(GO) build $(BUILD_FLAGS) -o $@
bin/fzf: target/$(BINARY) | bin
-rm -f bin/fzf
cp -f target/$(BINARY) bin/fzf
docker:
@@ -133,4 +184,4 @@ update:
$(GO) get -u
$(GO) mod tidy
.PHONY: all release release-all test install clean docker docker-test update
.PHONY: all generate build release test bench install clean docker docker-test update

View File

@@ -12,7 +12,10 @@ differ depending on the package manager.
" If installed using Homebrew
set rtp+=/usr/local/opt/fzf
" If installed using git
" If installed using Homebrew on Apple Silicon
set rtp+=/opt/homebrew/opt/fzf
" If you have cloned fzf on ~/.fzf directory
set rtp+=~/.fzf
```
@@ -23,7 +26,10 @@ written as:
" If installed using Homebrew
Plug '/usr/local/opt/fzf'
" If installed using git
" If installed using Homebrew on Apple Silicon
Plug '/opt/homebrew/opt/fzf'
" If you have cloned fzf on ~/.fzf directory
Plug '~/.fzf'
```
@@ -115,7 +121,7 @@ let g:fzf_action = {
" An action can be a reference to a function that processes selected lines
function! s:build_quickfix_list(lines)
call setqflist(map(copy(a:lines), '{ "filename": v:val }'))
call setqflist(map(copy(a:lines), '{ "filename": v:val, "lnum": 1 }'))
copen
cc
endfunction
@@ -127,10 +133,19 @@ let g:fzf_action = {
\ 'ctrl-v': 'vsplit' }
" Default fzf layout
" - down / up / left / right
let g:fzf_layout = { 'down': '~40%' }
" - Popup window (center of the screen)
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
" You can set up fzf window using a Vim command (Neovim or latest Vim 8 required)
" - Popup window (center of the current window)
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true } }
" - Popup window (anchored to the bottom of the current window)
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true, 'yoffset': 1.0 } }
" - down / up / left / right
let g:fzf_layout = { 'down': '40%' }
" - Window using a Vim command
let g:fzf_layout = { 'window': 'enew' }
let g:fzf_layout = { 'window': '-tabnew' }
let g:fzf_layout = { 'window': '10new' }
@@ -168,19 +183,23 @@ list:
- `element` is an fzf element to apply a color to:
| Element | Description |
| --- | --- |
| `fg` / `bg` / `hl` | Item (foreground / background / highlight) |
| `fg+` / `bg+` / `hl+` | Current item (foreground / background / highlight) |
| `hl` / `hl+` | Highlighted substrings (normal / current) |
| `gutter` | Background of the gutter on the left |
| `pointer` | Pointer to the current line (`>`) |
| `marker` | Multi-select marker (`>`) |
| `border` | Border around the window (`--border` and `--preview`) |
| `header` | Header (`--header` or `--header-lines`) |
| `info` | Info line (match counters) |
| `spinner` | Streaming input indicator |
| `prompt` | Prompt before query (`> `) |
| Element | Description |
| --- | --- |
| `fg` / `bg` / `hl` | Item (foreground / background / highlight) |
| `fg+` / `bg+` / `hl+` | Current item (foreground / background / highlight) |
| `preview-fg` / `preview-bg` | Preview window text and background |
| `hl` / `hl+` | Highlighted substrings (normal / current) |
| `gutter` | Background of the gutter on the left |
| `pointer` | Pointer to the current line (`>`) |
| `marker` | Multi-select marker (`>`) |
| `border` | Border around the window (`--border` and `--preview`) |
| `header` | Header (`--header` or `--header-lines`) |
| `info` | Info line (match counters) |
| `spinner` | Streaming input indicator |
| `query` | Query string |
| `disabled` | Query string when search is disabled |
| `prompt` | Prompt before query (`> `) |
| `pointer` | Pointer to the current line (`>`) |
- `component` specifies the component (`fg` / `bg`) from which to extract the
color when considering each of the following highlight groups
@@ -219,19 +238,20 @@ call fzf#run({'sink': 'e'})
```
We haven't specified the `source`, so this is equivalent to starting fzf on
command line without standard input pipe; fzf will use find command (or
`$FZF_DEFAULT_COMMAND` if defined) to list the files under the current
directory. When you select one, it will open it with the sink, `:e` command.
If you want to open it in a new tab, you can pass `:tabedit` command instead
as the sink.
command line without standard input pipe; fzf will traverse the file system
under the current directory to get the list of files. (If
`$FZF_DEFAULT_COMMAND` is set, fzf will use the output of the command
instead.) When you select one, it will open it with the sink, `:e` command. If
you want to open it in a new tab, you can pass `:tabedit` command instead as
the sink.
```vim
call fzf#run({'sink': 'tabedit'})
```
Instead of using the default find command, you can use any shell command as
the source. The following example will list the files managed by git. It's
equivalent to running `git ls-files | fzf` on shell.
You can use any shell command as the source to generate the list. The
following example will list the files managed by git. It's equivalent to
running `git ls-files | fzf` on shell.
```vim
call fzf#run({'source': 'git ls-files', 'sink': 'e'})
@@ -270,10 +290,11 @@ The following table summarizes the available options.
| `source` | list | Vim list as input to fzf |
| `sink` | string | Vim command to handle the selected item (e.g. `e`, `tabe`) |
| `sink` | funcref | Reference to function to process each selected item |
| `sink*` | funcref | Similar to `sink`, but takes the list of output lines at once |
| `sinklist` (or `sink*`) | funcref | Similar to `sink`, but takes the list of output lines at once |
| `options` | string/list | Options to fzf |
| `dir` | string | Working directory |
| `up`/`down`/`left`/`right` | number/string | (Layout) Window position and size (e.g. `20`, `50%`) |
| `tmux` | string | (Layout) fzf-tmux options (e.g. `-p90%,60%`) |
| `window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new`) |
| `window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width': 0.9, 'height': 0.6}`) |
@@ -289,14 +310,14 @@ When `window` entry is a dictionary, fzf will start in a popup window. The
following options are allowed:
- Required:
- `width` [float range [0 ~ 1]]
- `height` [float range [0 ~ 1]]
- `width` [float range [0 ~ 1]] or [integer range [8 ~ ]]
- `height` [float range [0 ~ 1]] or [integer range [4 ~ ]]
- Optional:
- `yoffset` [float default 0.5 range [0 ~ 1]]
- `xoffset` [float default 0.5 range [0 ~ 1]]
- `highlight` [string default `'Comment'`]: Highlight group for border
- `border` [string default `rounded`]: Border style
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right`
- `relative` [boolean default v:false]
- `border` [string default `rounded` (`sharp` on Windows)]: Border style
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right` / `no[ne]`
`fzf#wrap`
----------
@@ -330,8 +351,9 @@ After we *"wrap"* our spec, we pass it to `fzf#run`.
call fzf#run(fzf#wrap({'source': 'ls'}))
```
Now it supports `CTRL-T`, `CTRL-V`, and `CTRL-X` key bindings and it opens fzf
window according to `g:fzf_layout` setting.
Now it supports `CTRL-T`, `CTRL-V`, and `CTRL-X` key bindings (configurable
via `g:fzf_action`) and it opens fzf window according to `g:fzf_layout`
setting.
To make it easier to use, let's define `LS` command.
@@ -354,7 +376,7 @@ Our `:LS` command will be much more useful if we can pass a directory argument
to it, so that something like `:LS /tmp` is possible.
```vim
command! -bang -complete=dir -nargs=* LS
command! -bang -complete=dir -nargs=? LS
\ call fzf#run(fzf#wrap({'source': 'ls', 'dir': <q-args>}, <bang>0))
```
@@ -364,54 +386,103 @@ a unique name to our command and pass it as the first argument to `fzf#wrap`.
```vim
" The query history for this command will be stored as 'ls' inside g:fzf_history_dir.
" The name is ignored if g:fzf_history_dir is not defined.
command! -bang -complete=dir -nargs=* LS
command! -bang -complete=dir -nargs=? LS
\ call fzf#run(fzf#wrap('ls', {'source': 'ls', 'dir': <q-args>}, <bang>0))
```
### Global options supported by `fzf#wrap`
- `g:fzf_layout`
- `g:fzf_action`
- **Works only when no custom `sink` (or `sinklist`) is provided**
- Having custom sink usually means that each entry is not an ordinary
file path (e.g. name of color scheme), so we can't blindly apply the
same strategy (i.e. `tabedit some-color-scheme` doesn't make sense)
- `g:fzf_colors`
- `g:fzf_history_dir`
Tips
----
### 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 the latest versions of Vim and Neovim, fzf will start in a terminal buffer.
If you find the default ANSI colors to be different, consider configuring the
colors using `g:terminal_ansi_colors` in regular Vim or `g:terminal_color_x`
in Neovim.
- On Neovim
- On GVim
- On Terminal Vim with a non-default layout
- `call fzf#run({'left': '30%'})` or `let g:fzf_layout = {'left': '30%'}`
```vim
" Terminal colors for seoul256 color scheme
if has('nvim')
let g:terminal_color_0 = '#4e4e4e'
let g:terminal_color_1 = '#d68787'
let g:terminal_color_2 = '#5f865f'
let g:terminal_color_3 = '#d8af5f'
let g:terminal_color_4 = '#85add4'
let g:terminal_color_5 = '#d7afaf'
let g:terminal_color_6 = '#87afaf'
let g:terminal_color_7 = '#d0d0d0'
let g:terminal_color_8 = '#626262'
let g:terminal_color_9 = '#d75f87'
let g:terminal_color_10 = '#87af87'
let g:terminal_color_11 = '#ffd787'
let g:terminal_color_12 = '#add4fb'
let g:terminal_color_13 = '#ffafaf'
let g:terminal_color_14 = '#87d7d7'
let g:terminal_color_15 = '#e4e4e4'
else
let g:terminal_ansi_colors = [
\ '#4e4e4e', '#d68787', '#5f865f', '#d8af5f',
\ '#85add4', '#d7afaf', '#87afaf', '#d0d0d0',
\ '#626262', '#d75f87', '#87af87', '#ffd787',
\ '#add4fb', '#ffafaf', '#87d7d7', '#e4e4e4'
\ ]
endif
```
#### Starting fzf in a popup window
### Starting fzf in a popup window
```vim
" Required:
" - width [float range [0 ~ 1]]
" - height [float range [0 ~ 1]]
" - width [float range [0 ~ 1]] or [integer range [8 ~ ]]
" - height [float range [0 ~ 1]] or [integer range [4 ~ ]]
"
" Optional:
" - xoffset [float default 0.5 range [0 ~ 1]]
" - yoffset [float default 0.5 range [0 ~ 1]]
" - highlight [string default 'Comment']: Highlight group for border
" - relative [boolean default v:false]
" - border [string default 'rounded']: Border style
" - 'rounded' / 'sharp' / 'horizontal' / 'vertical' / 'top' / 'bottom' / 'left' / 'right'
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
```
#### Hide statusline
Alternatively, you can make fzf open in a tmux popup window (requires tmux 3.2
or above) by putting fzf-tmux options in `tmux` key.
```vim
" See `man fzf-tmux` for available options
if exists('$TMUX')
let g:fzf_layout = { 'tmux': '-p90%,60%' }
else
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
endif
```
### Hide statusline
When fzf starts in a terminal buffer, the file type of the buffer is set to
`fzf`. So you can set up `FileType fzf` autocmd to customize the settings of
the window.
For example, if you use the default layout (`{'down': '~40%'}`) on Neovim, you
might want to temporarily disable the statusline for a cleaner look.
For example, if you open fzf on the bottom on the screen (e.g. `{'down':
'40%'}`), you might want to temporarily disable the statusline for a cleaner
look.
```vim
if has('nvim') && !exists('g:fzf_layout')
autocmd! FileType fzf
autocmd FileType fzf set laststatus=0 noshowmode noruler
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
endif
let g:fzf_layout = { 'down': '30%' }
autocmd! FileType fzf
autocmd FileType fzf set laststatus=0 noshowmode noruler
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
```
[License](LICENSE)
@@ -419,4 +490,4 @@ endif
The MIT License (MIT)
Copyright (c) 2013-2020 Junegunn Choi
Copyright (c) 2013-2024 Junegunn Choi

586
README.md

File diff suppressed because one or more lines are too long

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

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

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# fzf-tmux: starts fzf in a tmux pane
# usage: fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS]
# usage: fzf-tmux [LAYOUT OPTIONS] [--] [FZF OPTIONS]
fail() {
>&2 echo "$1"
@@ -20,15 +20,23 @@ term=""
[[ -n "$COLUMNS" ]] && columns=$COLUMNS || columns=$(tput cols) || columns=$(tmux display-message -p "#{pane_width}")
help() {
>&2 echo 'usage: fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS]
>&2 echo 'usage: fzf-tmux [LAYOUT OPTIONS] [--] [FZF OPTIONS]
Layout
-u [HEIGHT[%]] Split above (up)
-d [HEIGHT[%]] Split below (down)
-l [WIDTH[%]] Split left
-r [WIDTH[%]] Split right
LAYOUT OPTIONS:
(default layout: -d 50%)
(default: -d 50%)
Popup window (requires tmux 3.2 or above):
-p [WIDTH[%][,HEIGHT[%]]] (default: 50%)
-w WIDTH[%]
-h HEIGHT[%]
-x COL
-y ROW
Split pane:
-u [HEIGHT[%]] Split above (up)
-d [HEIGHT[%]] Split below (down)
-l [WIDTH[%]] Split left
-r [WIDTH[%]] Split right
'
exit
}
@@ -47,8 +55,10 @@ while [[ $# -gt 0 ]]; do
echo "fzf-tmux (with fzf $("$fzf" --version))"
exit
;;
-w*|-h*|-d*|-u*|-r*|-l*)
if [[ "$arg" =~ ^.[lrw] ]]; then
-p*|-w*|-h*|-x*|-y*|-d*|-u*|-r*|-l*)
if [[ "$arg" =~ ^-[pwhxy] ]]; then
[[ "$opt" =~ "-E" ]] || opt="-E"
elif [[ "$arg" =~ ^.[lr] ]]; then
opt="-h"
if [[ "$arg" =~ ^.l ]]; then
opt="$opt -d"
@@ -66,7 +76,7 @@ while [[ $# -gt 0 ]]; do
if [[ ${#arg} -gt 2 ]]; then
size="${arg:2}"
else
if [[ "$1" =~ ^[0-9]+%?$ ]]; then
if [[ "$1" =~ ^[0-9%,]+$ ]] || [[ "$1" =~ ^[A-Z]$ ]]; then
size="$1"
shift
else
@@ -74,12 +84,20 @@ while [[ $# -gt 0 ]]; do
fi
fi
if [[ "$size" =~ %$ ]]; then
if [[ "$arg" =~ ^-p ]]; then
if [[ -n "$size" ]]; then
w=${size%%,*}
h=${size##*,}
opt="$opt -w$w -h$h"
fi
elif [[ "$arg" =~ ^-[whxy] ]]; then
opt="$opt ${arg:0:2}$size"
elif [[ "$size" =~ %$ ]]; then
size=${size:0:((${#size}-1))}
if [[ -n "$swap" ]]; then
opt="$opt -p $(( 100 - size ))"
opt="$opt -l $(( 100 - size ))%"
else
opt="$opt -p $size"
opt="$opt -l $size%"
fi
else
if [[ -n "$swap" ]]; then
@@ -109,17 +127,17 @@ while [[ $# -gt 0 ]]; do
[[ -n "$skip" ]] && args+=("$arg")
done
if [[ -z "$TMUX" || "$opt" =~ ^-h && "$columns" -le 40 || ! "$opt" =~ ^-h && "$lines" -le 15 ]]; then
if [[ -z "$TMUX" ]]; then
"$fzf" "${args[@]}"
exit $?
fi
# --height option is not allowed
args+=("--no-height")
# --height option is not allowed. CTRL-Z is also disabled.
args=("${args[@]}" "--no-height" "--bind=ctrl-z:ignore")
# Handle zoomed tmux pane by moving it to a temp window
if tmux list-panes -F '#F' | grep -q Z; then
zoomed=1
# Handle zoomed tmux pane without popup options by moving it to a temp window
if [[ ! "$opt" =~ "-E" ]] && tmux list-panes -F '#F' | grep -q Z; then
zoomed_without_popup=1
original_window=$(tmux display-message -p "#{window_id}")
tmp_window=$(tmux new-window -d -P -F "#{window_id}" "bash -c 'while :; do for c in \\| / - '\\;' do sleep 0.2; printf \"\\r\$c fzf-tmux is running\\r\"; done; done'")
tmux swap-pane -t $tmp_window \; select-window -t $tmp_window
@@ -133,16 +151,23 @@ argsf="${TMPDIR:-/tmp}/fzf-args-$id"
fifo1="${TMPDIR:-/tmp}/fzf-fifo1-$id"
fifo2="${TMPDIR:-/tmp}/fzf-fifo2-$id"
fifo3="${TMPDIR:-/tmp}/fzf-fifo3-$id"
if tmux_win_opts=$(tmux show-options -p remain-on-exit \; show-options -p synchronize-panes 2> /dev/null); then
tmux_win_opts=( $(sed '/ off/d; s/synchronize-panes/set-option -p synchronize-panes/; s/remain-on-exit/set-option -p remain-on-exit/; s/$/ \\;/' <<< "$tmux_win_opts") )
tmux_off_opts='; set-option -p synchronize-panes off ; set-option -p remain-on-exit off'
else
tmux_win_opts=( $(tmux show-window-options remain-on-exit \; show-window-options synchronize-panes | sed '/ off/d; s/^/set-window-option /; s/$/ \\;/') )
tmux_off_opts='; set-window-option synchronize-panes off ; set-window-option remain-on-exit off'
fi
cleanup() {
\rm -f $argsf $fifo1 $fifo2 $fifo3
# Restore tmux window options
if [[ "${#tmux_win_opts[@]}" -gt 0 ]]; then
eval "tmux ${tmux_win_opts[@]}"
if [[ "${#tmux_win_opts[@]}" -gt 1 ]]; then
eval "tmux ${tmux_win_opts[*]}"
fi
# Remove temp window if we were zoomed
if [[ -n "$zoomed" ]]; then
# Remove temp window if we were zoomed without popup options
if [[ -n "$zoomed_without_popup" ]]; then
tmux display-message -p "#{window_id}" > /dev/null
tmux swap-pane -t $original_window \; \
select-window -t $original_window \; \
@@ -150,7 +175,7 @@ cleanup() {
resize-pane -Z
fi
if [ $# -gt 0 ]; then
if [[ $# -gt 0 ]]; then
trap - EXIT
exit 130
fi
@@ -158,44 +183,60 @@ cleanup() {
trap 'cleanup 1' SIGUSR1
trap 'cleanup' EXIT
envs="env TERM=$TERM "
[[ -n "$FZF_DEFAULT_OPTS" ]] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
[[ -n "$FZF_DEFAULT_COMMAND" ]] && envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
mkfifo -m o+w $fifo2
mkfifo -m o+w $fifo3
envs="export TERM=$TERM "
if [[ "$opt" =~ "-E" ]]; then
tmux_version=$(tmux -V | sed 's/[^0-9.]//g')
if [[ $(awk '{print ($1 > 3.2)}' <<< "$tmux_version" 2> /dev/null || bc -l <<< "$tmux_version > 3.2") = 1 ]]; then
FZF_DEFAULT_OPTS="--border $FZF_DEFAULT_OPTS"
opt="-B $opt"
elif [[ $tmux_version = 3.2 ]]; then
FZF_DEFAULT_OPTS="--margin 0,1 $FZF_DEFAULT_OPTS"
else
echo "fzf-tmux: tmux 3.2 or above is required for popup mode" >&2
exit 2
fi
fi
envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
envs="$envs FZF_DEFAULT_OPTS_FILE=$(printf %q "$FZF_DEFAULT_OPTS_FILE")"
[[ -n "$RUNEWIDTH_EASTASIAN" ]] && envs="$envs RUNEWIDTH_EASTASIAN=$(printf %q "$RUNEWIDTH_EASTASIAN")"
[[ -n "$BAT_THEME" ]] && envs="$envs BAT_THEME=$(printf %q "$BAT_THEME")"
echo "$envs;" > "$argsf"
# Build arguments to fzf
opts=""
for arg in "${args[@]}"; do
arg="${arg//\\/\\\\}"
arg="${arg//\"/\\\"}"
arg="${arg//\`/\\\`}"
arg="${arg//$/\\$}"
opts="$opts \"$arg\""
done
opts=$(printf "%q " "${args[@]}")
pppid=$$
echo -n "trap 'kill -SIGUSR1 -$pppid' EXIT SIGINT SIGTERM;" > $argsf
echo -n "trap 'kill -SIGUSR1 -$pppid' EXIT SIGINT SIGTERM;" >> $argsf
close="; trap - EXIT SIGINT SIGTERM $close"
tmux_win_opts=( $(tmux show-window-options remain-on-exit \; show-window-options synchronize-panes | sed '/ off/d; s/^/set-window-option /; s/$/ \\;/') )
export TMUX=$(cut -d , -f 1,2 <<< "$TMUX")
mkfifo -m o+w $fifo2
if [[ "$opt" =~ "-E" ]]; then
cat $fifo2 &
if [[ -n "$term" ]] || [[ -t 0 ]]; then
cat <<< "\"$fzf\" $opts > $fifo2; out=\$? $close; exit \$out" >> $argsf
else
mkfifo $fifo1
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; out=\$? $close; exit \$out" >> $argsf
cat <&0 > $fifo1 &
fi
tmux popup -d "$PWD" $opt "bash $argsf" > /dev/null 2>&1
exit $?
fi
mkfifo -m o+w $fifo3
if [[ -n "$term" ]] || [[ -t 0 ]]; then
cat <<< "\"$fzf\" $opts > $fifo2; echo \$? > $fifo3 $close" >> $argsf
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
set-window-option remain-on-exit off \;\
split-window $opt "$envs bash -c 'cd $(printf %q "$PWD"); exec -a fzf bash $argsf'" $swap \
> /dev/null 2>&1
else
mkfifo $fifo1
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; echo \$? > $fifo3 $close" >> $argsf
TMUX=$(echo $TMUX | cut -d , -f 1,2) tmux set-window-option synchronize-panes off \;\
set-window-option remain-on-exit off \;\
split-window $opt "$envs bash -c 'exec -a fzf bash $argsf'" $swap \
> /dev/null 2>&1
cat <&0 > $fifo1 &
fi
tmux \
split-window -c "$PWD" $opt "bash -c 'exec -a fzf bash $argsf'" $swap \
$tmux_off_opts \
> /dev/null 2>&1 || { "$fzf" "${args[@]}"; exit $?; }
cat $fifo2
exit "$(cat $fifo3)"

View File

@@ -1,49 +1,98 @@
fzf.txt fzf Last change: February 14 2020
fzf.txt fzf Last change: February 15 2024
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
==============================================================================
FZF Vim integration
Summary
:FZF[!]
Configuration
Examples
fzf#run
fzf#wrap
Tips
fzf inside terminal buffer
Starting fzf in a popup window
Hide statusline
License
FZF Vim integration |fzf-vim-integration|
Installation |fzf-installation|
Summary |fzf-summary|
:FZF[!] |:FZF|
Configuration |fzf-configuration|
Examples |fzf-examples|
Explanation of g:fzf_colors |fzf-explanation-of-gfzfcolors|
fzf#run |fzf#run|
fzf#wrap |fzf#wrap|
Global options supported by fzf#wrap |fzf-global-options-supported-by-fzf#wrap|
Tips |fzf-tips|
fzf inside terminal buffer |fzf-inside-terminal-buffer|
Starting fzf in a popup window |fzf-starting-fzf-in-a-popup-window|
Hide statusline |fzf-hide-statusline|
License |fzf-license|
FZF VIM INTEGRATION *fzf-vim-integration*
==============================================================================
INSTALLATION *fzf-installation*
==============================================================================
Once you have fzf installed, you can enable it inside Vim simply by adding the
directory to 'runtimepath' in your Vim configuration file. The path may differ
depending on the package manager.
>
" If installed using Homebrew
set rtp+=/usr/local/opt/fzf
" If installed using Homebrew on Apple Silicon
set rtp+=/opt/homebrew/opt/fzf
" If you have cloned fzf on ~/.fzf directory
set rtp+=~/.fzf
<
If you use {vim-plug}{1}, the same can be written as:
>
" If installed using Homebrew
Plug '/usr/local/opt/fzf'
" If installed using Homebrew on Apple Silicon
Plug '/opt/homebrew/opt/fzf'
" If you have cloned fzf on ~/.fzf directory
Plug '~/.fzf'
<
But if you want the latest Vim plugin file from GitHub rather than the one
included in the package, write:
>
Plug 'junegunn/fzf'
<
The Vim plugin will pick up fzf binary available on the system. If fzf is not
found on `$PATH`, it will ask you if it should download the latest binary for
you.
To make sure that you have the latest version of the binary, set up
post-update hook like so:
*fzf#install*
>
Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
<
{1} https://github.com/junegunn/vim-plug
SUMMARY *fzf-summary*
==============================================================================
The Vim plugin of fzf provides two core functions, and `:FZF` command which is
the basic file selector command built on top of them.
1. `fzf#run([specdict])`
1. `fzf#run([spec dict])`
- Starts fzf inside Vim with the given spec
- `:callfzf#run({'source':'ls'})`
2. `fzf#wrap([specdict])->(dict)`
- `:call fzf#run({'source': 'ls'})`
2. `fzf#wrap([spec dict]) -> (dict)`
- Takes a spec for `fzf#run` and returns an extended version of it with
additional options for addressing global preferences (`g:fzf_xxx`)
- `:echofzf#wrap({'source':'ls'})`
- `:echo fzf#wrap({'source': 'ls'})`
- We usually wrap a spec with `fzf#wrap` before passing it to `fzf#run`
- `:callfzf#run(fzf#wrap({'source':'ls'}))`
3. `:FZF[fzf_optionsstring][pathstring]`
- `:call fzf#run(fzf#wrap({'source': 'ls'}))`
3. `:FZF [fzf_options string] [path string]`
- Basic fuzzy file selector
- A reference implementation for those who don't want to write VimScript to
implement custom commands
- If you're looking for more such commands, check out {fzf.vim}{1} project.
- If you're looking for more such commands, check out {fzf.vim}{2} project.
The most important of all is `fzf#run`, but it would be easier to understand
the whole if we start off with `:FZF` command.
{1} https://github.com/junegunn/fzf.vim
{2} https://github.com/junegunn/fzf.vim
:FZF[!]
@@ -63,14 +112,14 @@ the whole if we start off with `:FZF` command.
" Bang version starts fzf in fullscreen mode
:FZF!
<
Similarly to {ctrlp.vim}{2}, use enter key, CTRL-T, CTRL-X or CTRL-V to open
Similarly to {ctrlp.vim}{3}, use enter key, CTRL-T, CTRL-X or CTRL-V to open
selected files in the current window, in new tabs, in horizontal splits, or in
vertical splits respectively.
Note that the environment variables `FZF_DEFAULT_COMMAND` and
`FZF_DEFAULT_OPTS` also apply here.
{2} https://github.com/kien/ctrlp.vim
{3} https://github.com/kien/ctrlp.vim
< Configuration >_____________________________________________________________~
@@ -100,7 +149,7 @@ Examples~
" An action can be a reference to a function that processes selected lines
function! s:build_quickfix_list(lines)
call setqflist(map(copy(a:lines), '{ "filename": v:val }'))
call setqflist(map(copy(a:lines), '{ "filename": v:val, "lnum": 1 }'))
copen
cc
endfunction
@@ -112,10 +161,19 @@ Examples~
\ 'ctrl-v': 'vsplit' }
" Default fzf layout
" - down / up / left / right
let g:fzf_layout = { 'down': '~40%' }
" - Popup window (center of the screen)
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
" You can set up fzf window using a Vim command (Neovim or latest Vim 8 required)
" - Popup window (center of the current window)
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true } }
" - Popup window (anchored to the bottom of the current window)
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true, 'yoffset': 1.0 } }
" - down / up / left / right
let g:fzf_layout = { 'down': '40%' }
" - Window using a Vim command
let g:fzf_layout = { 'window': 'enew' }
let g:fzf_layout = { 'window': '-tabnew' }
let g:fzf_layout = { 'window': '10new' }
@@ -144,6 +202,55 @@ Examples~
let g:fzf_history_dir = '~/.local/share/fzf-history'
<
Explanation of g:fzf_colors~
*fzf-explanation-of-gfzfcolors*
`g:fzf_colors` is a dictionary mapping fzf elements to a color specification
list:
>
element: [ component, group1 [, group2, ...] ]
<
- `element` is an fzf element to apply a color to:
----------------------------+------------------------------------------------------
Element | Description ~
----------------------------+------------------------------------------------------
`fg` / `bg` / `hl` | Item (foreground / background / highlight)
`fg+` / `bg+` / `hl+` | Current item (foreground / background / highlight)
`preview-fg` / `preview-bg` | Preview window text and background
`hl` / `hl+` | Highlighted substrings (normal / current)
`gutter` | Background of the gutter on the left
`pointer` | Pointer to the current line ( `>` )
`marker` | Multi-select marker ( `>` )
`border` | Border around the window ( `--border` and `--preview` )
`header` | Header ( `--header` or `--header-lines` )
`info` | Info line (match counters)
`spinner` | Streaming input indicator
`query` | Query string
`disabled` | Query string when search is disabled
`prompt` | Prompt before query ( `> ` )
`pointer` | Pointer to the current line ( `>` )
----------------------------+------------------------------------------------------
- `component` specifies the component (`fg` / `bg`) from which to extract the
color when considering each of the following highlight groups
- `group1 [, group2, ...]` is a list of highlight groups that are searched (in
order) for a matching color definition
For example, consider the following specification:
>
'prompt': ['fg', 'Conditional', 'Comment'],
<
This means we color the prompt - using the `fg` attribute of the `Conditional`
if it exists, - otherwise use the `fg` attribute of the `Comment` highlight
group if it exists, - otherwise fall back to the default color settings for
the prompt.
You can examine the color option generated according the setting by printing
the result of `fzf#wrap()` function like so:
>
:echo fzf#wrap()
<
FZF#RUN
==============================================================================
@@ -157,17 +264,18 @@ entry.
call fzf#run({'sink': 'e'})
<
We haven't specified the `source`, so this is equivalent to starting fzf on
command line without standard input pipe; fzf will use find command (or
`$FZF_DEFAULT_COMMAND` if defined) to list the files under the current
directory. When you select one, it will open it with the sink, `:e` command.
If you want to open it in a new tab, you can pass `:tabedit` command instead
as the sink.
command line without standard input pipe; fzf will traverse the file system
under the current directory to get the list of files. (If
`$FZF_DEFAULT_COMMAND` is set, fzf will use the output of the command
instead.) When you select one, it will open it with the sink, `:e` command. If
you want to open it in a new tab, you can pass `:tabedit` command instead as
the sink.
>
call fzf#run({'sink': 'tabedit'})
<
Instead of using the default find command, you can use any shell command as
the source. The following example will list the files managed by git. It's
equivalent to running `gitls-files|fzf` on shell.
You can use any shell command as the source to generate the list. The
following example will list the files managed by git. It's equivalent to
running `git ls-files | fzf` on shell.
>
call fzf#run({'source': 'git ls-files', 'sink': 'e'})
<
@@ -195,16 +303,17 @@ The following table summarizes the available options.
---------------------------+---------------+----------------------------------------------------------------------
Option name | Type | Description ~
---------------------------+---------------+----------------------------------------------------------------------
`source` | string | External command to generate input to fzf (e.g. `find.` )
`source` | string | External command to generate input to fzf (e.g. `find .` )
`source` | list | Vim list as input to fzf
`sink` | string | Vim command to handle the selected item (e.g. `e` , `tabe` )
`sink` | funcref | Reference to function to process each selected item
`sink*` | funcref | Similar to `sink` , but takes the list of output lines at once
`sinklist` (or `sink*` ) | funcref | Similar to `sink` , but takes the list of output lines at once
`options` | string/list | Options to fzf
`dir` | string | Working directory
`up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` )
`window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `verticalaboveleft30new` )
`window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width':0.9,'height':0.6}` )
`tmux` | string | (Layout) fzf-tmux options (e.g. `-p90%,60%` )
`window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new` )
`window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width': 0.9, 'height': 0.6}` )
---------------------------+---------------+----------------------------------------------------------------------
`options` entry can be either a string or a list. For simple cases, string
@@ -217,14 +326,14 @@ When `window` entry is a dictionary, fzf will start in a popup window. The
following options are allowed:
- Required:
- `width` [float range [0 ~ 1]]
- `height` [float range [0 ~ 1]]
- `width` [float range [0 ~ 1]] or [integer range [8 ~ ]]
- `height` [float range [0 ~ 1]] or [integer range [4 ~ ]]
- Optional:
- `yoffset` [float default 0.5 range [0 ~ 1]]
- `xoffset` [float default 0.5 range [0 ~ 1]]
- `highlight` [string default `'Comment'`]: Highlight group for border
- `border` [string default `rounded`]: Border style
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right`
- `relative` [boolean default v:false]
- `border` [string default `rounded` (`sharp` on Windows)]: Border style
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right` / `no[ne]`
FZF#WRAP
@@ -241,7 +350,7 @@ So how can we make our custom `fzf#run` calls also respect those variables?
Simply by "wrapping" the spec dictionary with `fzf#wrap` before passing it to
`fzf#run`.
- `fzf#wrap([namestring],[specdict],[fullscreenbool])->(dict)`
- `fzf#wrap([name string], [spec dict], [fullscreen bool]) -> (dict)`
- All arguments are optional. Usually we only need to pass a spec
dictionary.
- `name` is for managing history files. It is ignored if `g:fzf_history_dir`
@@ -258,8 +367,8 @@ After we "wrap" our spec, we pass it to `fzf#run`.
>
call fzf#run(fzf#wrap({'source': 'ls'}))
<
Now it supports CTRL-T, CTRL-V, and CTRL-X key bindings and it opens fzf
window according to `g:fzf_layout` setting.
Now it supports CTRL-T, CTRL-V, and CTRL-X key bindings (configurable via
`g:fzf_action`) and it opens fzf window according to `g:fzf_layout` setting.
To make it easier to use, let's define `LS` command.
>
@@ -275,9 +384,9 @@ last `fullscreen` argument of `fzf#wrap` (see :help <bang>).
command! -bang LS call fzf#run(fzf#wrap({'source': 'ls'}, <bang>0))
<
Our `:LS` command will be much more useful if we can pass a directory argument
to it, so that something like `:LS/tmp` is possible.
to it, so that something like `:LS /tmp` is possible.
>
command! -bang -complete=dir -nargs=* LS
command! -bang -complete=dir -nargs=? LS
\ call fzf#run(fzf#wrap({'source': 'ls', 'dir': <q-args>}, <bang>0))
<
Lastly, if you have enabled `g:fzf_history_dir`, you might want to assign a
@@ -285,10 +394,23 @@ unique name to our command and pass it as the first argument to `fzf#wrap`.
>
" The query history for this command will be stored as 'ls' inside g:fzf_history_dir.
" The name is ignored if g:fzf_history_dir is not defined.
command! -bang -complete=dir -nargs=* LS
command! -bang -complete=dir -nargs=? LS
\ call fzf#run(fzf#wrap('ls', {'source': 'ls', 'dir': <q-args>}, <bang>0))
<
< Global options supported by fzf#wrap >______________________________________~
*fzf-global-options-supported-by-fzf#wrap*
- `g:fzf_layout`
- `g:fzf_action`
- Works only when no custom `sink` (or `sinklist`) is provided
- Having custom sink usually means that each entry is not an ordinary
file path (e.g. name of color scheme), so we can't blindly apply the
same strategy (i.e. `tabedit some-color-scheme` doesn't make sense)
- `g:fzf_colors`
- `g:fzf_history_dir`
TIPS *fzf-tips*
==============================================================================
@@ -296,46 +418,82 @@ TIPS *fzf-tips*
< fzf inside terminal buffer >________________________________________________~
*fzf-inside-terminal-buffer*
The latest versions of Vim and Neovim include builtin terminal emulator
(`:terminal`) and fzf will start in a terminal buffer in the following cases:
- On Neovim
- On GVim
- On Terminal Vim with a non-default layout
- `callfzf#run({'left':'30%'})` or `letg:fzf_layout={'left':'30%'}`
On the latest versions of Vim and Neovim, fzf will start in a terminal buffer.
If you find the default ANSI colors to be different, consider configuring the
colors using `g:terminal_ansi_colors` in regular Vim or `g:terminal_color_x`
in Neovim.
>
" Terminal colors for seoul256 color scheme
if has('nvim')
let g:terminal_color_0 = '#4e4e4e'
let g:terminal_color_1 = '#d68787'
let g:terminal_color_2 = '#5f865f'
let g:terminal_color_3 = '#d8af5f'
let g:terminal_color_4 = '#85add4'
let g:terminal_color_5 = '#d7afaf'
let g:terminal_color_6 = '#87afaf'
let g:terminal_color_7 = '#d0d0d0'
let g:terminal_color_8 = '#626262'
let g:terminal_color_9 = '#d75f87'
let g:terminal_color_10 = '#87af87'
let g:terminal_color_11 = '#ffd787'
let g:terminal_color_12 = '#add4fb'
let g:terminal_color_13 = '#ffafaf'
let g:terminal_color_14 = '#87d7d7'
let g:terminal_color_15 = '#e4e4e4'
else
let g:terminal_ansi_colors = [
\ '#4e4e4e', '#d68787', '#5f865f', '#d8af5f',
\ '#85add4', '#d7afaf', '#87afaf', '#d0d0d0',
\ '#626262', '#d75f87', '#87af87', '#ffd787',
\ '#add4fb', '#ffafaf', '#87d7d7', '#e4e4e4'
\ ]
endif
<
Starting fzf in a popup window~
< Starting fzf in a popup window >____________________________________________~
*fzf-starting-fzf-in-a-popup-window*
>
" Required:
" - width [float range [0 ~ 1]]
" - height [float range [0 ~ 1]]
" - width [float range [0 ~ 1]] or [integer range [8 ~ ]]
" - height [float range [0 ~ 1]] or [integer range [4 ~ ]]
"
" Optional:
" - xoffset [float default 0.5 range [0 ~ 1]]
" - yoffset [float default 0.5 range [0 ~ 1]]
" - highlight [string default 'Comment']: Highlight group for border
" - relative [boolean default v:false]
" - border [string default 'rounded']: Border style
" - 'rounded' / 'sharp' / 'horizontal' / 'vertical' / 'top' / 'bottom' / 'left' / 'right'
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
<
Alternatively, you can make fzf open in a tmux popup window (requires tmux 3.2
or above) by putting fzf-tmux options in `tmux` key.
>
" See `man fzf-tmux` for available options
if exists('$TMUX')
let g:fzf_layout = { 'tmux': '-p90%,60%' }
else
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
endif
<
Hide statusline~
< Hide statusline >___________________________________________________________~
*fzf-hide-statusline*
When fzf starts in a terminal buffer, the file type of the buffer is set to
`fzf`. So you can set up `FileTypefzf` autocmd to customize the settings of
`fzf`. So you can set up `FileType fzf` autocmd to customize the settings of
the window.
For example, if you use the default layout (`{'down':'~40%'}`) on Neovim, you
might want to temporarily disable the statusline for a cleaner look.
For example, if you open fzf on the bottom on the screen (e.g. `{'down':
'40%'}`), you might want to temporarily disable the statusline for a cleaner
look.
>
if has('nvim') && !exists('g:fzf_layout')
autocmd! FileType fzf
autocmd FileType fzf set laststatus=0 noshowmode noruler
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
endif
let g:fzf_layout = { 'down': '30%' }
autocmd! FileType fzf
autocmd FileType fzf set laststatus=0 noshowmode noruler
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
<
LICENSE *fzf-license*
@@ -343,7 +501,7 @@ LICENSE *fzf-license*
The MIT License (MIT)
Copyright (c) 2013-2020 Junegunn Choi
Copyright (c) 2013-2024 Junegunn Choi
==============================================================================
vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap:

25
go.mod
View File

@@ -1,15 +1,20 @@
module github.com/junegunn/fzf
require (
github.com/gdamore/tcell v1.3.0
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
github.com/mattn/go-isatty v0.0.12
github.com/mattn/go-runewidth v0.0.8
github.com/mattn/go-shellwords v1.0.9
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5
golang.org/x/text v0.3.2 // indirect
github.com/charlievieth/fastwalk v1.0.3
github.com/gdamore/tcell/v2 v2.7.4
github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-shellwords v1.0.12
github.com/rivo/uniseg v0.4.7
golang.org/x/sys v0.18.0
golang.org/x/term v0.18.0
)
go 1.13
require (
github.com/gdamore/encoding v1.0.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
golang.org/x/text v0.14.0 // indirect
)
go 1.20

83
go.sum
View File

@@ -1,44 +1,57 @@
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
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/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell v1.3.0 h1:r35w0JBADPZCVQijYebl6YMWWtHRqVEGt7kL2eBADRM=
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
github.com/lucasb-eyer/go-colorful v1.0.2 h1:mCMFu6PgSozg9tDNMMK3g18oJBX7oYGrC09mS6CXfO4=
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0=
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-shellwords v1.0.9 h1:eaB5JspOwiKKcHdqcjbfe5lA9cNn/4NRRtddXJCimqk=
github.com/mattn/go-shellwords v1.0.9/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e h1:NO86zOn5ScSKW8wRbMaSIcjDZUFpWdCQQnexRqZ9h9A=
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e/go.mod h1:G0Z6yVPru183i2MuRJx1DcR4dgIZtLcTdaaE/pC1BJU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU=
github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U=
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191011211836-4c025a95b26e h1:1o2bDs9pCd2xFhdwqJTrCIswAeEsn4h/PCNelWpfcsI=
golang.org/x/tools v0.0.0-20191011211836-4c025a95b26e/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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

118
install
View File

@@ -2,11 +2,10 @@
set -u
version=0.21.0
version=0.49.0
auto_completion=
key_bindings=
update_config=2
binary_arch=
shells="bash zsh fish"
prefix='~/.fzf'
prefix_expand=~/.fzf
@@ -28,9 +27,6 @@ usage: $0 [OPTIONS]
--no-bash Do not set up bash configuration
--no-zsh Do not set up zsh configuration
--no-fish Do not set up fish configuration
--32 Download 32-bit binary
--64 Download 64-bit binary
EOF
}
@@ -56,8 +52,6 @@ for opt in "$@"; do
--no-completion) auto_completion=0 ;;
--update-rc) update_config=1 ;;
--no-update-rc) update_config=0 ;;
--32) binary_arch=386 ;;
--64) binary_arch=amd64 ;;
--bin) ;;
--no-bash) shells=${shells/bash/} ;;
--no-zsh) shells=${shells/zsh/} ;;
@@ -120,7 +114,7 @@ link_fzf_in_path() {
try_curl() {
command -v curl > /dev/null &&
if [[ $1 =~ tgz$ ]]; then
if [[ $1 =~ tar.gz$ ]]; then
curl -fL $1 | tar -xzf -
else
local temp=${TMPDIR:-/tmp}/fzf.zip
@@ -130,7 +124,7 @@ try_curl() {
try_wget() {
command -v wget > /dev/null &&
if [[ $1 =~ tgz$ ]]; then
if [[ $1 =~ tar.gz$ ]]; then
wget -O - $1 | tar -xzf -
else
local temp=${TMPDIR:-/tmp}/fzf.zip
@@ -140,13 +134,11 @@ try_wget() {
download() {
echo "Downloading bin/fzf ..."
if [[ ! "$version" =~ alpha ]]; then
if [ -x "$fzf_base"/bin/fzf ]; then
echo " - Already exists"
check_binary && return
fi
link_fzf_in_path && return
if [ -x "$fzf_base"/bin/fzf ]; then
echo " - Already exists"
check_binary && return
fi
link_fzf_in_path && return
mkdir -p "$fzf_base"/bin && cd "$fzf_base"/bin
if [ $? -ne 0 ]; then
binary_error="Failed to create bin directory"
@@ -154,9 +146,7 @@ download() {
fi
local url
[[ "$version" =~ alpha ]] &&
url=https://github.com/junegunn/fzf-bin/releases/download/alpha/${1} ||
url=https://github.com/junegunn/fzf-bin/releases/download/$version/${1}
url=https://github.com/junegunn/fzf/releases/download/$version/${1}
set -o pipefail
if ! (try_curl $url || try_wget $url); then
set +o pipefail
@@ -178,27 +168,24 @@ archi=$(uname -sm)
binary_available=1
binary_error=""
case "$archi" in
Darwin\ *64) download fzf-$version-darwin_${binary_arch:-amd64}.tgz ;;
Darwin\ *86) download fzf-$version-darwin_${binary_arch:-386}.tgz ;;
Linux\ armv5*) download fzf-$version-linux_${binary_arch:-arm5}.tgz ;;
Linux\ armv6*) download fzf-$version-linux_${binary_arch:-arm6}.tgz ;;
Linux\ armv7*) download fzf-$version-linux_${binary_arch:-arm7}.tgz ;;
Linux\ armv8*) download fzf-$version-linux_${binary_arch:-arm8}.tgz ;;
Linux\ aarch64*) download fzf-$version-linux_${binary_arch:-arm8}.tgz ;;
Linux\ *64) download fzf-$version-linux_${binary_arch:-amd64}.tgz ;;
Linux\ *86) download fzf-$version-linux_${binary_arch:-386}.tgz ;;
FreeBSD\ *64) download fzf-$version-freebsd_${binary_arch:-amd64}.tgz ;;
FreeBSD\ *86) download fzf-$version-freebsd_${binary_arch:-386}.tgz ;;
OpenBSD\ *64) download fzf-$version-openbsd_${binary_arch:-amd64}.tgz ;;
OpenBSD\ *86) download fzf-$version-openbsd_${binary_arch:-386}.tgz ;;
CYGWIN*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
MINGW*\ *86) download fzf-$version-windows_${binary_arch:-386}.zip ;;
MINGW*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
MSYS*\ *86) download fzf-$version-windows_${binary_arch:-386}.zip ;;
MSYS*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
Windows*\ *86) download fzf-$version-windows_${binary_arch:-386}.zip ;;
Windows*\ *64) download fzf-$version-windows_${binary_arch:-amd64}.zip ;;
*) binary_available=0 binary_error=1 ;;
Darwin\ arm64) download fzf-$version-darwin_arm64.zip ;;
Darwin\ x86_64) download fzf-$version-darwin_amd64.zip ;;
Linux\ armv5*) download fzf-$version-linux_armv5.tar.gz ;;
Linux\ armv6*) download fzf-$version-linux_armv6.tar.gz ;;
Linux\ armv7*) download fzf-$version-linux_armv7.tar.gz ;;
Linux\ armv8*) download fzf-$version-linux_arm64.tar.gz ;;
Linux\ aarch64*) download fzf-$version-linux_arm64.tar.gz ;;
Linux\ loongarch64) download fzf-$version-linux_loong64.tar.gz ;;
Linux\ ppc64le) download fzf-$version-linux_ppc64le.tar.gz ;;
Linux\ *64) download fzf-$version-linux_amd64.tar.gz ;;
Linux\ s390x) download fzf-$version-linux_s390x.tar.gz ;;
FreeBSD\ *64) download fzf-$version-freebsd_amd64.tar.gz ;;
OpenBSD\ *64) download fzf-$version-openbsd_amd64.tar.gz ;;
CYGWIN*\ *64) download fzf-$version-windows_amd64.zip ;;
MINGW*\ *64) download fzf-$version-windows_amd64.zip ;;
MSYS*\ *64) download fzf-$version-windows_amd64.zip ;;
Windows*\ *64) download fzf-$version-windows_amd64.zip ;;
*) binary_available=0 binary_error=1 ;;
esac
cd "$fzf_base"
@@ -209,12 +196,12 @@ if [ -n "$binary_error" ]; then
echo " - $binary_error !!!"
fi
if command -v go > /dev/null; then
echo -n "Building binary (go get -u github.com/junegunn/fzf) ... "
echo -n "Building binary (go install github.com/junegunn/fzf) ... "
if [ -z "${GOPATH-}" ]; then
export GOPATH="${TMPDIR:-/tmp}/fzf-gopath"
mkdir -p "$GOPATH"
fi
if go get -u github.com/junegunn/fzf; then
if go install -ldflags "-s -w -X main.version=$version -X main.revision=go-install" github.com/junegunn/fzf; then
echo "OK"
cp "$GOPATH/bin/fzf" "$fzf_base/bin/"
else
@@ -258,7 +245,7 @@ for shell in $shells; do
src=${prefix_expand}.${shell}
echo -n "Generate $src ... "
fzf_completion="[[ \$- == *i* ]] && source \"$fzf_base/shell/completion.${shell}\" 2> /dev/null"
fzf_completion="source \"$fzf_base/shell/completion.${shell}\""
if [ $auto_completion -eq 0 ]; then
fzf_completion="# $fzf_completion"
fi
@@ -272,9 +259,15 @@ for shell in $shells; do
# Setup fzf
# ---------
if [[ ! "\$PATH" == *$fzf_base_esc/bin* ]]; then
export PATH="\${PATH:+\${PATH}:}$fzf_base/bin"
PATH="\${PATH:+\${PATH}:}$fzf_base/bin"
fi
EOF
if [[ $auto_completion -eq 1 ]] && [[ $key_bindings -eq 1 ]]; then
echo "eval \"\$(fzf --$shell)\"" >> "$src"
else
cat >> "$src" << EOF
# Auto-completion
# ---------------
$fzf_completion
@@ -283,6 +276,7 @@ $fzf_completion
# ------------
$fzf_key_bindings
EOF
fi
echo "OK"
done
@@ -294,23 +288,6 @@ if [[ "$shells" =~ fish ]]; then
or set --universal fish_user_paths \$fish_user_paths "$fzf_base"/bin
EOF
[ $? -eq 0 ] && echo "OK" || echo "Failed"
mkdir -p "${fish_dir}/functions"
if [ -e "${fish_dir}/functions/fzf.fish" ]; then
echo -n "Remove unnecessary ${fish_dir}/functions/fzf.fish ... "
rm -f "${fish_dir}/functions/fzf.fish" && echo "OK" || echo "Failed"
fi
fish_binding="${fish_dir}/functions/fzf_key_bindings.fish"
if [ $key_bindings -ne 0 ]; then
echo -n "Symlink $fish_binding ... "
ln -sf "$fzf_base/shell/key-bindings.fish" \
"$fish_binding" && echo "OK" || echo "Failed"
else
echo -n "Removing $fish_binding ... "
rm -f "$fish_binding"
echo "OK"
fi
fi
append_line() {
@@ -373,18 +350,33 @@ done
if [ $key_bindings -eq 1 ] && [[ "$shells" =~ fish ]]; then
bind_file="${fish_dir}/functions/fish_user_key_bindings.fish"
if [ ! -e "$bind_file" ]; then
mkdir -p "${fish_dir}/functions"
create_file "$bind_file" \
'function fish_user_key_bindings' \
' fzf_key_bindings' \
' fzf --fish | source' \
'end'
else
append_line $update_config "fzf_key_bindings" "$bind_file"
echo "Check $bind_file:"
lno=$(\grep -nF "fzf_key_bindings" "$bind_file" | sed 's/:.*//' | tr '\n' ' ')
if [[ -n $lno ]]; then
echo " ** Found 'fzf_key_bindings' in line #$lno"
echo " ** You have to replace the line to 'fzf --fish | source'"
echo
else
echo " - Clear"
echo
append_line $update_config "fzf --fish | source" "$bind_file"
fi
fi
fi
if [ $update_config -eq 1 ]; then
echo 'Finished. Restart your shell or reload config file.'
[[ "$shells" =~ bash ]] && echo ' source ~/.bashrc # bash'
if [[ "$shells" =~ bash ]]; then
echo -n ' source ~/.bashrc # bash'
[[ "$archi" =~ Darwin ]] && echo -n ' (.bashrc should be loaded from .bash_profile)'
echo
fi
[[ "$shells" =~ zsh ]] && echo " source ${ZDOTDIR:-~}/.zshrc # zsh"
[[ "$shells" =~ fish ]] && [ $key_bindings -eq 1 ] && echo ' fzf_key_bindings # fish'
echo

View File

@@ -1,10 +1,4 @@
$version="0.21.0"
if ([Environment]::Is64BitProcess) {
$binary_arch="amd64"
} else {
$binary_arch="386"
}
$version="0.49.0"
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
@@ -32,12 +26,10 @@ function check_binary () {
function download {
param($file)
Write-Host "Downloading bin/fzf ..."
if ("$version" -ne "alpha") {
if (Test-Path "$fzf_base\bin\fzf.exe") {
Write-Host " - Already exists"
if (check_binary) {
return
}
if (Test-Path "$fzf_base\bin\fzf.exe") {
Write-Host " - Already exists"
if (check_binary) {
return
}
}
if (-not (Test-Path "$fzf_base\bin")) {
@@ -48,16 +40,16 @@ function download {
return
}
cd "$fzf_base\bin"
if ("$version" -eq "alpha") {
$url="https://github.com/junegunn/fzf-bin/releases/download/alpha/$file"
} else {
$url="https://github.com/junegunn/fzf-bin/releases/download/$version/$file"
}
$url="https://github.com/junegunn/fzf/releases/download/$version/$file"
$temp=$env:TMP + "\fzf.zip"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
(New-Object Net.WebClient).DownloadFile($url, $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$temp"))
if ($PSVersionTable.PSVersion.Major -ge 3) {
Invoke-WebRequest -Uri $url -OutFile $temp
} else {
(New-Object Net.WebClient).DownloadFile($url, $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("$temp"))
}
if ($?) {
(Expand-Archive -Path $temp -DestinationPath .); (Remove-Item $temp)
(Microsoft.PowerShell.Archive\Expand-Archive -Path $temp -DestinationPath .); (Remove-Item $temp)
} else {
$binary_error="Failed to download with powershell"
}
@@ -65,9 +57,9 @@ function download {
$binary_error="Failed to download $file"
return
}
check_binary >$null
echo y | icacls $fzf_base\bin\fzf.exe /grant Administrator:F ; check_binary >$null
}
download "fzf-$version-windows_$binary_arch.zip"
download "fzf-$version-windows_amd64.zip"
Write-Host 'For more information, see: https://github.com/junegunn/fzf'

48
main.go
View File

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

View File

@@ -1,7 +1,7 @@
.ig
The MIT License (MIT)
Copyright (c) 2013-2020 Junegunn Choi
Copyright (c) 2013-2024 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -21,25 +21,39 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
..
.TH fzf-tmux 1 "Mar 2020" "fzf 0.21.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
fzf-tmux - open fzf in tmux split pane
.SH SYNOPSIS
.B fzf-tmux [-u|-d [HEIGHT[%]]] [-l|-r [WIDTH[%]]] [--] [FZF OPTIONS]
.B fzf-tmux [LAYOUT OPTIONS] [--] [FZF OPTIONS]
.SH DESCRIPTION
fzf-tmux is a wrapper script for fzf that opens fzf in a tmux split pane. It is
designed to work just like fzf except that it does not take up the whole
screen. You can safely use fzf-tmux instead of fzf in your scripts as the extra
options will be silently ignored if you're not on tmux.
fzf-tmux is a wrapper script for fzf that opens fzf in a tmux split pane or in
a tmux popup window. It is designed to work just like fzf except that it does
not take up the whole screen. You can safely use fzf-tmux instead of fzf in
your scripts as the extra options will be silently ignored if you're not on
tmux.
.SH OPTIONS
.SS Layout
.SH LAYOUT OPTIONS
(default: \fB-d 50%\fR)
(default layout: \fB-d 50%\fR)
.SS Popup window
(requires tmux 3.2 or above)
.TP
.B "-p [WIDTH[%][,HEIGHT[%]]]"
.TP
.B "-w WIDTH[%]"
.TP
.B "-h WIDTH[%]"
.TP
.B "-x COL"
.TP
.B "-y ROW"
.SS Split pane
.TP
.B "-u [height[%]]"
Split above (up)

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
" Copyright (c) 2017 Junegunn Choi
" Copyright (c) 2013-2024 Junegunn Choi
"
" MIT License
"
@@ -96,7 +96,12 @@ function! fzf#shellescape(arg, ...)
if shell =~# 'cmd.exe$'
return s:shellesc_cmd(a:arg)
endif
return s:fzf_call('shellescape', a:arg)
try
let [shell, &shell] = [&shell, shell]
return s:fzf_call('shellescape', a:arg)
finally
let [shell, &shell] = [&shell, shell]
endtry
endfunction
function! s:fzf_getcwd()
@@ -115,21 +120,30 @@ function! s:fzf_tempname()
return s:fzf_call('tempname')
endfunction
let s:default_layout = { 'down': '~40%' }
let s:layout_keys = ['window', 'up', 'down', 'left', 'right']
let s:layout_keys = ['window', 'tmux', 'up', 'down', 'left', 'right']
let s:fzf_go = s:base_dir.'/bin/fzf'
let s:fzf_tmux = s:base_dir.'/bin/fzf-tmux'
let s:cpo_save = &cpo
set cpo&vim
function! s:popup_support()
return has('nvim') ? has('nvim-0.4') : has('popupwin') && has('patch-8.2.191')
endfunction
function! s:default_layout()
return s:popup_support()
\ ? { 'window' : { 'width': 0.9, 'height': 0.6 } }
\ : { 'down': '~40%' }
endfunction
function! fzf#install()
if s:is_win && !has('win32unix')
let script = s:base_dir.'/install.ps1'
if !filereadable(script)
throw script.' not found'
endif
let script = 'powershell -ExecutionPolicy Bypass -file ' . script
let script = 'powershell -ExecutionPolicy Bypass -file ' . shellescape(script)
else
let script = s:base_dir.'/install'
if !executable(script)
@@ -145,22 +159,88 @@ function! fzf#install()
endif
endfunction
function! s:fzf_exec()
let s:versions = {}
function s:get_version(bin)
if has_key(s:versions, a:bin)
return s:versions[a:bin]
end
let command = (&shell =~ 'powershell\|pwsh' ? '&' : '') . s:fzf_call('shellescape', a:bin) . ' --version --no-height'
let output = systemlist(command)
if v:shell_error || empty(output)
return ''
endif
let ver = matchstr(output[-1], '[0-9.]\+')
let s:versions[a:bin] = ver
return ver
endfunction
function! s:compare_versions(a, b)
let a = split(a:a, '\.')
let b = split(a:b, '\.')
for idx in range(0, max([len(a), len(b)]) - 1)
let v1 = str2nr(get(a, idx, 0))
let v2 = str2nr(get(b, idx, 0))
if v1 < v2 | return -1
elseif v1 > v2 | return 1
endif
endfor
return 0
endfunction
function! s:compare_binary_versions(a, b)
return s:compare_versions(s:get_version(a:a), s:get_version(a:b))
endfunction
let s:checked = {}
function! fzf#exec(...)
if !exists('s:exec')
let binaries = []
if executable('fzf')
call add(binaries, 'fzf')
endif
if executable(s:fzf_go)
let s:exec = s:fzf_go
elseif executable('fzf')
let s:exec = 'fzf'
elseif input('fzf executable not found. Download binary? (y/n) ') =~? '^y'
call add(binaries, s:fzf_go)
endif
if empty(binaries)
if input('fzf executable not found. Download binary? (y/n) ') =~? '^y'
redraw
call fzf#install()
return fzf#exec()
else
redraw
throw 'fzf executable not found'
endif
elseif len(binaries) > 1
call sort(binaries, 's:compare_binary_versions')
endif
let s:exec = binaries[-1]
endif
if a:0 && !has_key(s:checked, a:1)
let fzf_version = s:get_version(s:exec)
if empty(fzf_version)
let message = printf('Failed to run "%s --version"', s:exec)
unlet s:exec
throw message
end
if s:compare_versions(fzf_version, a:1) >= 0
let s:checked[a:1] = 1
return s:exec
elseif a:0 < 2 && input(printf('You need fzf %s or above. Found: %s. Download binary? (y/n) ', a:1, fzf_version)) =~? '^y'
let s:versions = {}
unlet s:exec
redraw
call fzf#install()
return s:fzf_exec()
return fzf#exec(a:1, 1)
else
redraw
throw 'fzf executable not found'
throw printf('You need to upgrade fzf (required: %s or above)', a:1)
endif
endif
return fzf#shellescape(s:exec)
return s:exec
endfunction
function! s:tmux_enabled()
@@ -191,21 +271,6 @@ function! s:escape(path)
return s:is_win ? escape(path, '$') : path
endfunction
" Upgrade legacy options
function! s:upgrade(dict)
let copy = copy(a:dict)
if has_key(copy, 'tmux')
let copy.down = remove(copy, 'tmux')
endif
if has_key(copy, 'tmux_height')
let copy.down = remove(copy, 'tmux_height')
endif
if has_key(copy, 'tmux_width')
let copy.right = remove(copy, 'tmux_width')
endif
return copy
endfunction
function! s:error(msg)
echohl ErrorMsg
echom a:msg
@@ -251,9 +316,14 @@ function! s:common_sink(action, lines) abort
endif
try
let empty = empty(s:fzf_expand('%')) && line('$') == 1 && empty(getline(1)) && !&modified
let autochdir = &autochdir
set noautochdir
" Preserve the current working directory in case it's changed during
" the execution (e.g. `set autochdir` or `autocmd BufEnter * lcd ...`)
let cwd = exists('w:fzf_pushd') ? w:fzf_pushd.dir : expand('%:p:h')
for item in a:lines
if item[0] != '~' && item !~ (s:is_win ? '^[A-Z]:\' : '^/')
let sep = s:is_win ? '\' : '/'
let item = join([cwd, item], cwd[len(cwd)-1] == sep ? '' : sep)
endif
if empty
execute 'e' s:escape(item)
let empty = 0
@@ -267,13 +337,13 @@ function! s:common_sink(action, lines) abort
endfor
catch /^Vim:Interrupt$/
finally
let &autochdir = autochdir
silent! autocmd! fzf_swap
endtry
endfunction
function! s:get_color(attr, ...)
let gui = !s:is_win && !has('win32unix') && has('termguicolors') && &termguicolors
" Force 24 bit colors: g:fzf_force_termguicolors (temporary workaround for https://github.com/junegunn/fzf.vim/issues/1152)
let gui = get(g:, 'fzf_force_termguicolors', 0) || (!s:is_win && !has('win32unix') && has('termguicolors') && &termguicolors)
let fam = gui ? 'gui' : 'cterm'
let pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$'
for group in a:000
@@ -312,7 +382,7 @@ function! fzf#wrap(...)
let expects = map(copy(args), 'type(v:val)')
let tidx = 0
for arg in copy(a:000)
let tidx = index(expects, type(arg), tidx)
let tidx = index(expects, type(arg) == 6 ? type(0) : type(arg), tidx)
if tidx < 0
throw 'Invalid arguments (expected: [name string] [opts dict] [fullscreen boolean])'
endif
@@ -337,7 +407,7 @@ function! fzf#wrap(...)
if !exists('g:fzf_layout') && exists('g:fzf_height')
let opts.down = g:fzf_height
else
let opts = extend(opts, s:validate_layout(get(g:, 'fzf_layout', s:default_layout)))
let opts = extend(opts, s:validate_layout(get(g:, 'fzf_layout', s:default_layout())))
endif
endif
@@ -355,13 +425,13 @@ function! fzf#wrap(...)
endif
" Action: g:fzf_action
if !s:has_any(opts, ['sink', 'sink*'])
if !s:has_any(opts, ['sink', 'sinklist', 'sink*'])
let opts._action = get(g:, 'fzf_action', s:default_action)
let opts.options .= ' --expect='.join(keys(opts._action), ',')
function! opts.sink(lines) abort
function! opts.sinklist(lines) abort
return s:common_sink(self._action, a:lines)
endfunction
let opts['sink*'] = remove(opts, 'sink')
let opts['sink*'] = opts.sinklist " For backward compatibility
endif
return opts
@@ -380,62 +450,98 @@ function! s:use_sh()
return [shell, shellslash, shellcmdflag, shellxquote]
endfunction
function! s:writefile(...)
if call('writefile', a:000) == -1
throw 'Failed to write temporary file. Check if you can write to the path tempname() returns.'
endif
endfunction
function! s:extract_option(opts, name)
let opt = ''
let expect = 0
" There are a few cases where this function doesn't work as expected.
" Let's just assume such cases are extremely unlikely in real world.
" e.g. --query --border
for word in split(a:opts)
if expect && word !~ '^"\=-'
let opt = opt . ' ' . word
let expect = 0
elseif word == '--no-'.a:name
let opt = ''
elseif word =~ '^--'.a:name.'='
let opt = word
elseif word =~ '^--'.a:name.'$'
let opt = word
let expect = 1
elseif expect
let expect = 0
endif
endfor
return opt
endfunction
function! fzf#run(...) abort
try
let [shell, shellslash, shellcmdflag, shellxquote] = s:use_sh()
let dict = exists('a:1') ? s:upgrade(a:1) : {}
let dict = exists('a:1') ? copy(a:1) : {}
let temps = { 'result': s:fzf_tempname() }
let optstr = s:evaluate_opts(get(dict, 'options', ''))
try
let fzf_exec = s:fzf_exec()
let fzf_exec = shellescape(fzf#exec())
catch
throw v:exception
endtry
if !has_key(dict, 'dir')
if !s:present(dict, 'dir')
let dict.dir = s:fzf_getcwd()
endif
if has('win32unix') && has_key(dict, 'dir')
if has('win32unix') && s:present(dict, 'dir')
let dict.dir = fnamemodify(dict.dir, ':p')
endif
if has_key(dict, 'source')
let source = dict.source
let source = remove(dict, 'source')
let type = type(source)
if type == 1
let prefix = '( '.source.' )|'
let source_command = source
elseif type == 3
let temps.input = s:fzf_tempname()
call writefile(map(source, '<SID>enc_to_cp(v:val)'), temps.input)
let prefix = (s:is_win ? 'type ' : 'cat ').fzf#shellescape(temps.input).'|'
call s:writefile(source, temps.input)
let source_command = (s:is_win ? 'type ' : 'cat ').fzf#shellescape(temps.input)
else
throw 'Invalid source type'
endif
else
let prefix = ''
let source_command = ''
endif
let prefer_tmux = get(g:, 'fzf_prefer_tmux', 0)
let prefer_tmux = get(g:, 'fzf_prefer_tmux', 0) || has_key(dict, 'tmux')
let use_height = has_key(dict, 'down') && !has('gui_running') &&
\ !(has('nvim') || s:is_win || has('win32unix') || s:present(dict, 'up', 'left', 'right', 'window')) &&
\ executable('tput') && filereadable('/dev/tty')
let has_vim8_term = has('terminal') && has('patch-8.0.995')
let has_nvim_term = has('nvim-0.2.1') || has('nvim') && !s:is_win
let use_term = has_nvim_term ||
\ has_vim8_term && !has('win32unix') && (has('gui_running') || s:is_win || !use_height && s:present(dict, 'down', 'up', 'left', 'right', 'window'))
let use_tmux = (!use_height && !use_term || prefer_tmux) && !has('win32unix') && s:tmux_enabled() && s:splittable(dict)
\ has_vim8_term && !has('win32unix') && (has('gui_running') || s:is_win || s:present(dict, 'down', 'up', 'left', 'right', 'window'))
let use_tmux = (has_key(dict, 'tmux') || (!use_height && !use_term || prefer_tmux) && !has('win32unix') && s:splittable(dict)) && s:tmux_enabled()
if prefer_tmux && use_tmux
let use_height = 0
let use_term = 0
endif
if use_height
if use_term
let optstr .= ' --no-height'
elseif use_height
let height = s:calc_size(&lines, dict.down, dict)
let optstr .= ' --height='.height
elseif use_term
let optstr .= ' --no-height'
endif
let command = prefix.(use_tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
" Respect --border option given in $FZF_DEFAULT_OPTS and 'options'
let optstr = join([s:border_opt(get(dict, 'window', 0)), s:extract_option($FZF_DEFAULT_OPTS, 'border'), optstr])
let prev_default_command = $FZF_DEFAULT_COMMAND
if len(source_command)
let $FZF_DEFAULT_COMMAND = source_command
endif
let command = (use_tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
if use_term
return s:execute_term(dict, command, temps)
@@ -446,6 +552,14 @@ try
call s:callback(dict, lines)
return lines
finally
if exists('source_command') && len(source_command)
if len(prev_default_command)
let $FZF_DEFAULT_COMMAND = prev_default_command
else
let $FZF_DEFAULT_COMMAND = ''
silent! execute 'unlet $FZF_DEFAULT_COMMAND'
endif
endif
let [&shell, &shellslash, &shellcmdflag, &shellxquote] = [shell, shellslash, shellcmdflag, shellxquote]
endtry
endfunction
@@ -460,21 +574,23 @@ function! s:present(dict, ...)
endfunction
function! s:fzf_tmux(dict)
let size = ''
for o in ['up', 'down', 'left', 'right']
if s:present(a:dict, o)
let spec = a:dict[o]
if (o == 'up' || o == 'down') && spec[0] == '~'
let size = '-'.o[0].s:calc_size(&lines, spec, a:dict)
else
" Legacy boolean option
let size = '-'.o[0].(spec == 1 ? '' : substitute(spec, '^\~', '', ''))
let size = get(a:dict, 'tmux', '')
if empty(size)
for o in ['up', 'down', 'left', 'right']
if s:present(a:dict, o)
let spec = a:dict[o]
if (o == 'up' || o == 'down') && spec[0] == '~'
let size = '-'.o[0].s:calc_size(&lines, spec, a:dict)
else
" Legacy boolean option
let size = '-'.o[0].(spec == 1 ? '' : substitute(spec, '^\~', '', ''))
endif
break
endif
break
endif
endfor
return printf('LINES=%d COLUMNS=%d %s %s %s --',
\ &lines, &columns, fzf#shellescape(s:fzf_tmux), size, (has_key(a:dict, 'source') ? '' : '-'))
endfor
endif
return printf('LINES=%d COLUMNS=%d %s %s - --',
\ &lines, &columns, fzf#shellescape(s:fzf_tmux), size)
endfunction
function! s:splittable(dict)
@@ -527,7 +643,7 @@ function! s:dopopd()
if s:fzf_getcwd() ==# w:fzf_pushd.dir && (!&autochdir || w:fzf_pushd.bufname ==# bufname(''))
execute w:fzf_pushd.command s:escape(w:fzf_pushd.origin)
endif
unlet w:fzf_pushd
unlet! w:fzf_pushd
endfunction
function! s:xterm_launcher()
@@ -581,7 +697,7 @@ function! s:execute(dict, command, use_height, temps) abort
endif
if s:is_win
let batchfile = s:fzf_tempname().'.bat'
call writefile(s:wrap_cmds(command), batchfile)
call s:writefile(s:wrap_cmds(command), batchfile)
let command = batchfile
let a:temps.batchfile = batchfile
if has('nvim')
@@ -599,19 +715,19 @@ function! s:execute(dict, command, use_height, temps) abort
endif
elseif has('win32unix') && $TERM !=# 'cygwin'
let shellscript = s:fzf_tempname()
call writefile([command], shellscript)
call s:writefile([command], shellscript)
let command = 'cmd.exe /C '.fzf#shellescape('set "TERM=" & start /WAIT sh -c '.shellscript)
let a:temps.shellscript = shellscript
endif
if a:use_height
let stdin = has_key(a:dict, 'source') ? '' : '< /dev/tty'
call system(printf('tput cup %d > /dev/tty; tput cnorm > /dev/tty; %s %s 2> /dev/tty', &lines, command, stdin))
call system(printf('tput cup %d > /dev/tty; tput cnorm > /dev/tty; %s < /dev/tty 2> /dev/tty', &lines, command))
else
execute 'silent !'.command
endif
let exit_status = v:shell_error
redraw!
return s:exit_handler(exit_status, command) ? s:collect(a:temps) : []
let lines = s:collect(a:temps)
return s:exit_handler(exit_status, command) ? lines : []
endfunction
function! s:execute_tmux(dict, command, temps) abort
@@ -625,7 +741,8 @@ function! s:execute_tmux(dict, command, temps) abort
call system(command)
let exit_status = v:shell_error
redraw!
return s:exit_handler(exit_status, command) ? s:collect(a:temps) : []
let lines = s:collect(a:temps)
return s:exit_handler(exit_status, command) ? lines : []
endfunction
function! s:calc_size(max, val, dict)
@@ -642,8 +759,11 @@ function! s:calc_size(max, val, dict)
endif
let opts = $FZF_DEFAULT_OPTS.' '.s:evaluate_opts(get(a:dict, 'options', ''))
if opts =~ 'preview'
return size
endif
let margin = match(opts, '--inline-info\|--info[^-]\{-}inline') > match(opts, '--no-inline-info\|--info[^-]\{-}\(default\|hidden\)') ? 1 : 2
let margin += stridx(opts, '--border') > stridx(opts, '--no-border') ? 2 : 0
let margin += match(opts, '--border\([^-]\|$\)') > match(opts, '--no-border\([^-]\|$\)') ? 2 : 0
if stridx(opts, '--header') > stridx(opts, '--no-header')
let margin += len(split(opts, "\n"))
endif
@@ -651,7 +771,33 @@ function! s:calc_size(max, val, dict)
endfunction
function! s:getpos()
return {'tab': tabpagenr(), 'win': winnr(), 'cnt': winnr('$'), 'tcnt': tabpagenr('$')}
return {'tab': tabpagenr(), 'win': winnr(), 'winid': win_getid(), 'cnt': winnr('$'), 'tcnt': tabpagenr('$')}
endfunction
function! s:border_opt(window)
if type(a:window) != type({})
return ''
endif
" Border style
let style = tolower(get(a:window, 'border', ''))
if !has_key(a:window, 'border') && has_key(a:window, 'rounded')
let style = a:window.rounded ? 'rounded' : 'sharp'
endif
if style == 'none' || style == 'no'
return ''
endif
" For --border styles, we need fzf 0.24.0 or above
call fzf#exec('0.24.0')
let opt = ' --border ' . style
if has_key(a:window, 'highlight')
let color = s:get_color('fg', a:window.highlight)
if len(color)
let opt .= ' --color=border:' . color
endif
endif
return opt
endfunction
function! s:split(dict)
@@ -661,13 +807,15 @@ function! s:split(dict)
\ 'left': ['vertical topleft', 'vertical resize', &columns],
\ 'right': ['vertical botright', 'vertical resize', &columns] }
let ppos = s:getpos()
let is_popup = 0
try
if s:present(a:dict, 'window')
if type(a:dict.window) == type({})
if !has('nvim') && !has('patch-8.2.191')
throw 'Vim 8.2.191 or later is required for pop-up window'
if !s:popup_support()
throw 'Nvim 0.4+ or Vim 8.2.191+ with popupwin feature is required for pop-up window'
end
call s:popup(a:dict.window)
let is_popup = 1
else
execute 'keepalt' a:dict.window
endif
@@ -685,20 +833,40 @@ function! s:split(dict)
endif
execute cmd sz.'new'
execute resz sz
return [ppos, {}]
return [ppos, {}, is_popup]
endif
endfor
endif
return [ppos, { '&l:wfw': &l:wfw, '&l:wfh': &l:wfh }]
return [ppos, is_popup ? {} : { '&l:wfw': &l:wfw, '&l:wfh': &l:wfh }, is_popup]
finally
setlocal winfixwidth winfixheight
if !is_popup
setlocal winfixwidth winfixheight
endif
endtry
endfunction
nnoremap <silent> <Plug>(fzf-insert) i
nnoremap <silent> <Plug>(fzf-normal) <Nop>
if exists(':tnoremap')
tnoremap <silent> <Plug>(fzf-insert) <C-\><C-n>i
tnoremap <silent> <Plug>(fzf-normal) <C-\><C-n>
endif
let s:warned = 0
function! s:handle_ambidouble(dict)
if &ambiwidth == 'double'
let a:dict.env = { 'RUNEWIDTH_EASTASIAN': '1' }
elseif !s:warned && $RUNEWIDTH_EASTASIAN == '1' && &ambiwidth !=# 'double'
call s:warn("$RUNEWIDTH_EASTASIAN is '1' but &ambiwidth is not 'double'")
2sleep
let s:warned = 1
endif
endfunction
function! s:execute_term(dict, command, temps) abort
let winrest = winrestcmd()
let pbuf = bufnr('')
let [ppos, winopts] = s:split(a:dict)
let [ppos, winopts, is_popup] = s:split(a:dict)
call s:use_sh()
let b:fzf = a:dict
let fzf = { 'buf': bufnr(''), 'pbuf': pbuf, 'ppos': ppos, 'dict': a:dict, 'temps': a:temps,
@@ -707,7 +875,7 @@ function! s:execute_term(dict, command, temps) abort
function! fzf.switch_back(inplace)
if a:inplace && bufnr('') == self.buf
if bufexists(self.pbuf)
execute 'keepalt b' self.pbuf
execute 'keepalt keepjumps b' self.pbuf
endif
" No other listed buffer
if bufnr('') == self.buf
@@ -727,8 +895,8 @@ function! s:execute_term(dict, command, temps) abort
" there's no other listed buffer (nvim +'set nobuflisted')
close
endif
execute 'tabnext' self.ppos.tab
execute self.ppos.win.'wincmd w'
silent! execute 'tabnext' self.ppos.tab
silent! execute self.ppos.win.'wincmd w'
endif
if bufexists(self.buf)
@@ -739,37 +907,56 @@ function! s:execute_term(dict, command, temps) abort
execute self.winrest
endif
let lines = s:collect(self.temps)
if !s:exit_handler(a:code, self.command, 1)
return
endif
call s:pushd(self.dict)
let lines = s:collect(self.temps)
call s:callback(self.dict, lines)
call self.switch_back(s:getpos() == self.ppos)
if &buftype == 'terminal'
call feedkeys(&filetype == 'fzf' ? "\<Plug>(fzf-insert)" : "\<Plug>(fzf-normal)")
endif
endfunction
try
call s:pushd(a:dict)
if s:is_win
let fzf.temps.batchfile = s:fzf_tempname().'.bat'
call writefile(s:wrap_cmds(a:command), fzf.temps.batchfile)
call s:writefile(s:wrap_cmds(a:command), fzf.temps.batchfile)
let command = fzf.temps.batchfile
else
let command = a:command
endif
let command .= s:term_marker
if has('nvim')
call s:handle_ambidouble(fzf)
call termopen(command, fzf)
else
if !len(&bufhidden)
setlocal bufhidden=hide
let term_opts = {'exit_cb': function(fzf.on_exit)}
if v:version >= 802
let term_opts.term_kill = 'term'
endif
let fzf.buf = term_start([&shell, &shellcmdflag, command], {'curwin': 1, 'exit_cb': function(fzf.on_exit)})
if !has('patch-8.0.1261') && !has('nvim') && !s:is_win
if is_popup
let term_opts.hidden = 1
else
let term_opts.curwin = 1
endif
call s:handle_ambidouble(term_opts)
keepjumps let fzf.buf = term_start([&shell, &shellcmdflag, command], term_opts)
if is_popup && exists('#TerminalWinOpen')
doautocmd <nomodeline> TerminalWinOpen
endif
if !has('patch-8.0.1261') && !s:is_win
call term_wait(fzf.buf, 20)
endif
endif
tnoremap <buffer> <c-z> <nop>
if exists('&termwinkey') && (empty(&termwinkey) || &termwinkey =~? '<c-w>')
tnoremap <buffer> <c-w> <c-w>.
endif
finally
call s:dopopd()
endtry
@@ -807,6 +994,8 @@ function! s:callback(dict, lines) abort
endif
if has_key(a:dict, 'sink*')
call a:dict['sink*'](a:lines)
elseif has_key(a:dict, 'sinklist')
call a:dict['sinklist'](a:lines)
endif
catch
if stridx(v:exception, ':E325:') < 0
@@ -822,51 +1011,43 @@ function! s:callback(dict, lines) abort
endfunction
if has('nvim')
function s:create_popup(hl, opts) abort
function s:create_popup(opts) abort
let buf = nvim_create_buf(v:false, v:true)
let opts = extend({'relative': 'editor', 'style': 'minimal'}, a:opts)
let border = has_key(opts, 'border') ? remove(opts, 'border') : []
let win = nvim_open_win(buf, v:true, opts)
call setwinvar(win, '&winhighlight', 'NormalFloat:'..a:hl)
silent! call setwinvar(win, '&winhighlight', 'Pmenu:,Normal:Normal')
call setwinvar(win, '&colorcolumn', '')
if !empty(border)
call nvim_buf_set_lines(buf, 0, -1, v:true, border)
endif
return buf
endfunction
else
function! s:create_popup(hl, opts) abort
let is_frame = has_key(a:opts, 'border')
let buf = is_frame ? '' : term_start(&shell, #{hidden: 1})
let id = popup_create(buf, #{
function! s:create_popup(opts) abort
let s:popup_create = {buf -> popup_create(buf, #{
\ line: a:opts.row,
\ col: a:opts.col,
\ minwidth: a:opts.width,
\ maxwidth: a:opts.width,
\ minheight: a:opts.height,
\ zindex: 50 - is_frame,
\ })
if is_frame
call setwinvar(id, '&wincolor', a:hl)
call setbufline(winbufnr(id), 1, a:opts.border)
execute 'autocmd BufWipeout * ++once call popup_close('..id..')'
else
execute 'autocmd BufWipeout * ++once bwipeout! '..buf
endif
return winbufnr(id)
\ maxheight: a:opts.height,
\ zindex: 1000,
\ })}
autocmd TerminalOpen * ++once call s:popup_create(str2nr(expand('<abuf>')))
endfunction
endif
function! s:popup(opts) abort
" Support ambiwidth == 'double'
let ambidouble = &ambiwidth == 'double' ? 2 : 1
let xoffset = get(a:opts, 'xoffset', 0.5)
let yoffset = get(a:opts, 'yoffset', 0.5)
let relative = get(a:opts, 'relative', 0)
" Use current window size for positioning relatively positioned popups
let columns = relative ? winwidth(0) : &columns
let lines = relative ? winheight(0) : (&lines - has('nvim'))
" Size and position
let width = min([max([0, float2nr(&columns * a:opts.width)]), &columns])
let width += width % ambidouble
let height = min([max([0, float2nr(&lines * a:opts.height)]), &lines - has('nvim')])
let row = float2nr(get(a:opts, 'yoffset', 0.5) * (&lines - height))
let col = float2nr(get(a:opts, 'xoffset', 0.5) * (&columns - width))
let width = min([max([8, a:opts.width > 1 ? a:opts.width : float2nr(columns * a:opts.width)]), columns])
let height = min([max([4, a:opts.height > 1 ? a:opts.height : float2nr(lines * a:opts.height)]), lines])
let row = float2nr(yoffset * (lines - height)) + (relative ? win_screenpos(0)[0] - 1 : 0)
let col = float2nr(xoffset * (columns - width)) + (relative ? win_screenpos(0)[1] - 1 : 0)
" Managing the differences
let row = min([max([0, row]), &lines - has('nvim') - height])
@@ -874,45 +1055,9 @@ function! s:popup(opts) abort
let row += !has('nvim')
let col += !has('nvim')
" Border style
let style = tolower(get(a:opts, 'border', 'rounded'))
if !has_key(a:opts, 'border') && !get(a:opts, 'rounded', 1)
let style = 'sharp'
endif
if style =~ 'vertical\|left\|right'
let mid = style == 'vertical' ? '│' .. repeat(' ', width - 2 * ambidouble) .. '│' :
\ style == 'left' ? '│' .. repeat(' ', width - 1 * ambidouble)
\ : repeat(' ', width - 1 * ambidouble) .. '│'
let border = repeat([mid], height)
let shift = { 'row': 0, 'col': style == 'right' ? 0 : 2, 'width': style == 'vertical' ? -4 : -2, 'height': 0 }
elseif style =~ 'horizontal\|top\|bottom'
let hor = repeat('─', width / ambidouble)
let mid = repeat(' ', width)
let border = style == 'horizontal' ? [hor] + repeat([mid], height - 2) + [hor] :
\ style == 'top' ? [hor] + repeat([mid], height - 1)
\ : repeat([mid], height - 1) + [hor]
let shift = { 'row': style == 'bottom' ? 0 : 1, 'col': 0, 'width': 0, 'height': style == 'horizontal' ? -2 : -1 }
else
let edges = style == 'sharp' ? ['┌', '┐', '└', '┘'] : ['╭', '╮', '╰', '╯']
let bar = repeat('─', width / ambidouble - 2)
let top = edges[0] .. bar .. edges[1]
let mid = '│' .. repeat(' ', width - 2 * ambidouble) .. '│'
let bot = edges[2] .. bar .. edges[3]
let border = [top] + repeat([mid], height - 2) + [bot]
let shift = { 'row': 1, 'col': 2, 'width': -4, 'height': -2 }
endif
let highlight = get(a:opts, 'highlight', 'Comment')
let frame = s:create_popup(highlight, {
\ 'row': row, 'col': col, 'width': width, 'height': height, 'border': border
call s:create_popup({
\ 'row': row, 'col': col, 'width': width, 'height': height
\ })
call s:create_popup('Normal', {
\ 'row': row + shift.row, 'col': col + shift.col, 'width': width + shift.width, 'height': height + shift.height
\ })
if has('nvim')
execute 'autocmd BufWipeout <buffer> bwipeout '..frame
endif
endfunction
let s:default_action = {

View File

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

View File

@@ -2,41 +2,107 @@
# / __/___ / __/
# / /_/_ / / /_
# / __/ / /_/ __/
# /_/ /___/_/-completion.zsh
# /_/ /___/_/ completion.zsh
#
# - $FZF_TMUX (default: 0)
# - $FZF_TMUX_HEIGHT (default: '40%')
# - $FZF_TMUX_OPTS (default: '-d 40%')
# - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty)
if [[ $- =~ i ]]; then
[[ -o interactive ]] || return 0
# Both branches of the following `if` do the same thing -- define
# __fzf_completion_options such that `eval $__fzf_completion_options` sets
# all options to the same values they currently have. We'll do just that at
# the bottom of the file after changing options to what we prefer.
#
# IMPORTANT: Until we get to the `emulate` line, all words that *can* be quoted
# *must* be quoted in order to prevent alias expansion. In addition, code must
# be written in a way works with any set of zsh options. This is very tricky, so
# careful when you change it.
#
# Start by loading the builtin zsh/parameter module. It provides `options`
# associative array that stores current shell options.
if 'zmodload' 'zsh/parameter' 2>'/dev/null' && (( ${+options} )); then
# This is the fast branch and it gets taken on virtually all Zsh installations.
#
# ${(kv)options[@]} expands to array of keys (option names) and values ("on"
# or "off"). The subsequent expansion# with (j: :) flag joins all elements
# together separated by spaces. __fzf_completion_options ends up with a value
# like this: "options=(shwordsplit off aliases on ...)".
__fzf_completion_options="options=(${(j: :)${(kv)options[@]}})"
else
# This branch is much slower because it forks to get the names of all
# zsh options. It's possible to eliminate this fork but it's not worth the
# trouble because this branch gets taken only on very ancient or broken
# zsh installations.
() {
# That `()` above defines an anonymous function. This is essentially a scope
# for local parameters. We use it to avoid polluting global scope.
'local' '__fzf_opt'
__fzf_completion_options="setopt"
# `set -o` prints one line for every zsh option. Each line contains option
# name, some spaces, and then either "on" or "off". We just want option names.
# Expansion with (@f) flag splits a string into lines. The outer expansion
# removes spaces and everything that follow them on every line. __fzf_opt
# ends up iterating over option names: shwordsplit, aliases, etc.
for __fzf_opt in "${(@)${(@f)$(set -o)}%% *}"; do
if [[ -o "$__fzf_opt" ]]; then
# Option $__fzf_opt is currently on, so remember to set it back on.
__fzf_completion_options+=" -o $__fzf_opt"
else
# Option $__fzf_opt is currently off, so remember to set it back off.
__fzf_completion_options+=" +o $__fzf_opt"
fi
done
# The value of __fzf_completion_options here looks like this:
# "setopt +o shwordsplit -o aliases ..."
}
fi
# Enable the default zsh options (those marked with <Z> in `man zshoptions`)
# but without `aliases`. Aliases in functions are expanded when functions are
# defined, so if we disable aliases here, we'll be sure to have no pesky
# aliases in any of our functions. This way we won't need prefix every
# command with `command` or to quote every word to defend against global
# aliases. Note that `aliases` is not the only option that's important to
# control. There are several others that could wreck havoc if they are set
# to values we don't expect. With the following `emulate` command we
# sidestep this issue entirely.
'builtin' 'emulate' 'zsh' && 'builtin' 'setopt' 'no_aliases'
# This brace is the start of try-always block. The `always` part is like
# `finally` in lesser languages. We use it to *always* restore user options.
{
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
if ! declare -f _fzf_compgen_path > /dev/null; then
_fzf_compgen_path() {
echo "$1"
command find -L "$1" \
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
}
fi
if ! declare -f _fzf_compgen_dir > /dev/null; then
_fzf_compgen_dir() {
command find -L "$1" \
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
}
fi
#
# _fzf_compgen_path() {
# echo "$1"
# command find -L "$1" \
# -name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
# -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
# }
#
# _fzf_compgen_dir() {
# command find -L "$1" \
# -name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
# -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
# }
###########################################################
__fzf_comprun() {
if [[ "$(type _fzf_comprun 2>&1)" =~ function ]]; then
_fzf_comprun "$@"
elif [ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ]; then
elif [ -n "${TMUX_PANE-}" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "${FZF_TMUX_OPTS-}" ]; }; then
shift
fzf-tmux -d "${FZF_TMUX_HEIGHT:-40%}" "$@"
if [ -n "${FZF_TMUX_OPTS-}" ]; then
fzf-tmux ${(Q)${(Z+n+)FZF_TMUX_OPTS}} -- "$@"
else
fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%} -- "$@"
fi
else
shift
fzf "$@"
@@ -48,6 +114,7 @@ __fzf_extract_command() {
local token tokens
tokens=(${(z)1})
for token in $tokens; do
token=${(Q)token}
if [[ "$token" =~ [[:alnum:]] && ! "$token" =~ "=" ]]; then
echo "$token"
return
@@ -67,7 +134,10 @@ __fzf_generic_path_completion() {
tail=$6
setopt localoptions nonomatch
eval "base=$base"
if [[ $base = *'$('* ]] || [[ $base = *'<('* ]] || [[ $base = *'>('* ]] || [[ $base = *':='* ]] || [[ $base = *'`'* ]]; then
return
fi
eval "base=$base" 2> /dev/null || return
[[ $base = *"/"* ]] && dir="$base"
while [ 1 ]; do
if [[ -z "$dir" || -d ${dir} ]]; then
@@ -75,9 +145,19 @@ __fzf_generic_path_completion() {
leftover=${leftover/#\/}
[ -z "$dir" ] && dir='.'
[ "$dir" != "/" ] && dir="${dir/%\//}"
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
echo -n "${(q)item}$suffix "
done)
matches=$(
unset FZF_DEFAULT_COMMAND
export FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-}"
if declare -f "$compgen" > /dev/null; then
eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover"
else
[[ $compgen =~ dir ]] && walker=dir,follow || walker=file,dir,follow,hidden
__fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" --walker "$walker" --walker-root="$dir" < /dev/tty
fi | while read item; do
item="${item%$suffix}$suffix"
echo -n "${(q)item} "
done
)
matches=${matches% }
if [ -n "$matches" ]; then
LBUFFER="$lbuf$matches$tail"
@@ -112,8 +192,8 @@ _fzf_complete() {
local args rest str_arg i sep
args=("$@")
sep=
for i in {0..$#args}; do
if [[ "${args[$i]}" = -- ]]; then
for i in {0..${#args[@]}}; do
if [[ "${args[$i]-}" = -- ]]; then
sep=$i
break
fi
@@ -137,29 +217,44 @@ _fzf_complete() {
type $post > /dev/null 2>&1 || post=cat
_fzf_feed_fifo "$fifo"
matches=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
matches=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $str_arg" __fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
if [ -n "$matches" ]; then
LBUFFER="$lbuf$matches"
fi
zle reset-prompt
command rm -f "$fifo"
}
# To use custom hostname lists, override __fzf_list_hosts.
# The function is expected to print hostnames, one per line as well as in the
# desired sorting and with any duplicates removed, to standard output.
if ! declare -f __fzf_list_hosts > /dev/null; then
__fzf_list_hosts() {
setopt localoptions nonomatch
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?%]') \
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts 2> /dev/null | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
<(command grep -v '^\s*\(#\|$\)' /etc/hosts 2> /dev/null | command grep -Fv '0.0.0.0' | command sed 's/#.*//') |
awk '{for (i = 2; i <= NF; i++) print $i}' | sort -u
}
fi
_fzf_complete_telnet() {
_fzf_complete +m -- "$@" < <(
command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' |
awk '{if (length($2) > 0) {print $2}}' | sort -u
)
_fzf_complete +m -- "$@" < <(__fzf_list_hosts)
}
# The first and the only argument is the LBUFFER without the current word that contains the trigger.
# The current word without the trigger is in the $prefix variable passed from the caller.
_fzf_complete_ssh() {
_fzf_complete +m -- "$@" < <(
setopt localoptions nonomatch
command cat <(cat ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?]') \
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
awk '{if (length($2) > 0) {print $2}}' | sort -u
)
local tokens=(${(z)1})
case ${tokens[-1]} in
-i|-F|-E)
_fzf_path_completion "$prefix" "$1"
;;
*)
local user=
[[ $prefix =~ @ ]] && user="${prefix%%@*}@"
_fzf_complete +m -- "$@" < <(__fzf_list_hosts | awk -v user="$user" '{print user $0}')
;;
esac
}
_fzf_complete_export() {
@@ -180,6 +275,17 @@ _fzf_complete_unalias() {
)
}
_fzf_complete_kill() {
_fzf_complete -m --header-lines=1 --preview 'echo {}' --preview-window down:3:wrap --min-height 15 -- "$@" < <(
command ps -eo user,pid,ppid,start,time,command 2> /dev/null ||
command ps -eo user,pid,ppid,time,args # For BusyBox
)
}
_fzf_complete_kill_post() {
awk '{print $2}'
}
fzf-completion() {
local tokens cmd prefix trigger tail matches lbuf d_cmds
setopt localoptions noshwordsplit noksh_arrays noposixbuiltins
@@ -199,28 +305,27 @@ fzf-completion() {
[ -z "$trigger" -a ${LBUFFER[-1]} = ' ' ] && tokens+=("")
# When the trigger starts with ';', it becomes a separate token
if [[ ${LBUFFER} = *"${tokens[-2]}${tokens[-1]}" ]]; then
tokens[-2]="${tokens[-2]}${tokens[-1]}"
if [[ ${LBUFFER} = *"${tokens[-2]-}${tokens[-1]}" ]]; then
tokens[-2]="${tokens[-2]-}${tokens[-1]}"
tokens=(${tokens[0,-2]})
fi
lbuf=$LBUFFER
tail=${LBUFFER:$(( ${#LBUFFER} - ${#trigger} ))}
# Kill completion (do not require trigger sequence)
if [ $cmd = kill -a ${LBUFFER[-1]} = ' ' ]; then
matches=$(command ps -ef | sed 1d | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-50%} --min-height 15 --reverse $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS --preview 'echo {}' --preview-window down:3:wrap" __fzf_comprun "$cmd" -m | awk '{print $2}' | tr '\n' ' ')
if [ -n "$matches" ]; then
LBUFFER="$LBUFFER$matches"
fi
zle reset-prompt
# Trigger sequence given
elif [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
[ -z "${tokens[-1]}" ] && lbuf=$LBUFFER || lbuf=${LBUFFER:0:-${#tokens[-1]}}
if [[ $prefix = *'$('* ]] || [[ $prefix = *'<('* ]] || [[ $prefix = *'>('* ]] || [[ $prefix = *':='* ]] || [[ $prefix = *'`'* ]]; then
return
fi
[ -n "${tokens[-1]}" ] && lbuf=${lbuf:0:-${#tokens[-1]}}
if eval "type _fzf_complete_${cmd} > /dev/null"; then
prefix="$prefix" eval _fzf_complete_${cmd} ${(q)lbuf}
zle reset-prompt
elif [ ${d_cmds[(i)$cmd]} -le ${#d_cmds} ]; then
_fzf_dir_completion "$prefix" "$lbuf"
else
@@ -241,4 +346,8 @@ fzf-completion() {
zle -N fzf-completion
bindkey '^I' fzf-completion
fi
} always {
# Restore the original options.
eval $__fzf_completion_options
'unset' '__fzf_completion_options'
}

View File

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

View File

@@ -1,3 +1,19 @@
# ____ ____
# / __/___ / __/
# / /_/_ / / /_
# / __/ / /_/ __/
# /_/ /___/_/ key-bindings.fish
#
# - $FZF_TMUX_OPTS
# - $FZF_CTRL_T_COMMAND
# - $FZF_CTRL_T_OPTS
# - $FZF_CTRL_R_OPTS
# - $FZF_ALT_C_COMMAND
# - $FZF_ALT_C_OPTS
status is-interactive; or exit 0
# Key bindings
# ------------
function fzf_key_bindings
@@ -5,21 +21,15 @@ function fzf_key_bindings
# Store current token in $dir as root for the 'find' command
function fzf-file-widget -d "List files and folders"
set -l commandline (__fzf_parse_commandline)
set -l dir $commandline[1]
set -lx dir $commandline[1]
set -l fzf_query $commandline[2]
# "-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@^\./@@'"
set -l prefix $commandline[3]
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --walker=file,dir,follow,hidden --walker-root='$dir' --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND"
eval (__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
end
if [ -z "$result" ]
commandline -f repaint
@@ -29,6 +39,7 @@ function fzf_key_bindings
commandline -t ""
end
for i in $result
commandline -it -- $prefix
commandline -it -- (string escape $i)
commandline -it -- ' '
end
@@ -38,7 +49,7 @@ function fzf_key_bindings
function fzf-history-widget -d "Show command history"
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m"
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --scheme=history --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS +m"
set -l FISH_MAJOR (echo $version | cut -f1 -d.)
set -l FISH_MINOR (echo $version | cut -f2 -d.)
@@ -59,22 +70,22 @@ function fzf_key_bindings
function fzf-cd-widget -d "Change directory"
set -l commandline (__fzf_parse_commandline)
set -l dir $commandline[1]
set -lx dir $commandline[1]
set -l fzf_query $commandline[2]
set -l prefix $commandline[3]
test -n "$FZF_ALT_C_COMMAND"; or set -l FZF_ALT_C_COMMAND "
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
-o -type d -print 2> /dev/null | sed 's@^\./@@'"
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --walker=dir,follow,hidden --walker-root='$dir' --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND"
eval (__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
if [ -n "$result" ]
cd $result
cd -- $result
# Remove last token from commandline.
commandline -t ""
commandline -it -- $prefix
end
end
@@ -84,26 +95,42 @@ function fzf_key_bindings
function __fzfcmd
test -n "$FZF_TMUX"; or set FZF_TMUX 0
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
if [ $FZF_TMUX -eq 1 ]
echo "fzf-tmux -d$FZF_TMUX_HEIGHT"
if [ -n "$FZF_TMUX_OPTS" ]
echo "fzf-tmux $FZF_TMUX_OPTS -- "
else if [ $FZF_TMUX -eq 1 ]
echo "fzf-tmux -d$FZF_TMUX_HEIGHT -- "
else
echo "fzf"
end
end
bind \ct fzf-file-widget
bind \cr fzf-history-widget
bind \ec fzf-cd-widget
if bind -M insert > /dev/null 2>&1
bind -M insert \ct fzf-file-widget
bind -M insert \cr fzf-history-widget
bind -M insert \ec fzf-cd-widget
if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND"
bind \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
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath and rest of token'
if bind -M insert > /dev/null 2>&1
bind -M insert \cr fzf-history-widget
if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND"
bind -M insert \ct fzf-file-widget
end
if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND"
bind -M insert \ec fzf-cd-widget
end
end
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix'
set -l commandline (commandline -t)
# strip -option= from token if present
set -l prefix (string match -r -- '^-[^\s=]+=' $commandline)
set commandline (string replace -- "$prefix" '' $commandline)
# eval is used to do shell expansion on paths
set -l commandline (eval "printf '%s' "(commandline -t))
eval set commandline $commandline
if [ -z $commandline ]
# Default to current directory with no --query
@@ -112,32 +139,33 @@ function fzf_key_bindings
else
set dir (__fzf_get_dir $commandline)
if [ "$dir" = "." -a (string sub -l 1 $commandline) != '.' ]
if [ "$dir" = "." -a (string sub -l 1 -- $commandline) != '.' ]
# if $dir is "." but commandline is not a relative path, this means no file path found
set fzf_query $commandline
else
# Also remove trailing slash after dir, to "split" input properly
set fzf_query (string replace -r "^$dir/?" '' "$commandline")
set fzf_query (string replace -r "^$dir/?" -- '' "$commandline")
end
end
echo $dir
echo $fzf_query
echo $prefix
end
function __fzf_get_dir -d 'Find the longest existing filepath from input string'
set dir $argv
# Strip all trailing slashes. Ignore if $dir is root dir (/)
if [ (string length $dir) -gt 1 ]
set dir (string replace -r '/*$' '' $dir)
if [ (string length -- $dir) -gt 1 ]
set dir (string replace -r '/*$' -- '' $dir)
end
# Iteratively check if dir exists and strip tail end of path
while [ ! -d "$dir" ]
# If path is absolute, this can keep going until ends up at /
# If path is relative, this can keep going until entire input is consumed, dirname returns "."
set dir (dirname "$dir")
set dir (dirname -- "$dir")
end
echo $dir

View File

@@ -1,15 +1,49 @@
# ____ ____
# / __/___ / __/
# / /_/_ / / /_
# / __/ / /_/ __/
# /_/ /___/_/ key-bindings.zsh
#
# - $FZF_TMUX_OPTS
# - $FZF_CTRL_T_COMMAND
# - $FZF_CTRL_T_OPTS
# - $FZF_CTRL_R_OPTS
# - $FZF_ALT_C_COMMAND
# - $FZF_ALT_C_OPTS
[[ -o interactive ]] || return 0
# Key bindings
# ------------
if [[ $- == *i* ]]; then
# The code at the top and the bottom of this file is the same as in completion.zsh.
# Refer to that file for explanation.
if 'zmodload' 'zsh/parameter' 2>'/dev/null' && (( ${+options} )); then
__fzf_key_bindings_options="options=(${(j: :)${(kv)options[@]}})"
else
() {
__fzf_key_bindings_options="setopt"
'local' '__fzf_opt'
for __fzf_opt in "${(@)${(@f)$(set -o)}%% *}"; do
if [[ -o "$__fzf_opt" ]]; then
__fzf_key_bindings_options+=" -o $__fzf_opt"
else
__fzf_key_bindings_options+=" +o $__fzf_opt"
fi
done
}
fi
'builtin' 'emulate' 'zsh' && 'builtin' 'setopt' 'no_aliases'
{
# CTRL-T - Paste the selected file path(s) into the command line
__fsel() {
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | cut -b3-"}"
setopt localoptions pipefail no_aliases 2> /dev/null
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read item; do
local item
FZF_DEFAULT_COMMAND=${FZF_CTRL_T_COMMAND:-} FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --walker=file,dir,follow,hidden --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-}" $(__fzfcmd) -m "$@" < /dev/tty | while read item; do
echo -n "${(q)item} "
done
local ret=$?
@@ -17,13 +51,9 @@ __fsel() {
return $ret
}
__fzf_use_tmux__() {
[ -n "$TMUX_PANE" ] && [ "${FZF_TMUX:-0}" != 0 ] && [ ${LINES:-40} -gt 15 ]
}
__fzfcmd() {
__fzf_use_tmux__ &&
echo "fzf-tmux -d${FZF_TMUX_HEIGHT:-40%}" || echo "fzf"
[ -n "${TMUX_PANE-}" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "${FZF_TMUX_OPTS-}" ]; } &&
echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
}
fzf-file-widget() {
@@ -32,55 +62,60 @@ fzf-file-widget() {
zle reset-prompt
return $ret
}
zle -N fzf-file-widget
bindkey '^T' fzf-file-widget
# Ensure precmds are run after cd
fzf-redraw-prompt() {
local precmd
for precmd in $precmd_functions; do
$precmd
done
zle reset-prompt
}
zle -N fzf-redraw-prompt
if [[ "${FZF_CTRL_T_COMMAND-x}" != "" ]]; then
zle -N fzf-file-widget
bindkey -M emacs '^T' fzf-file-widget
bindkey -M vicmd '^T' fzf-file-widget
bindkey -M viins '^T' fzf-file-widget
fi
# ALT-C - cd into the selected directory
fzf-cd-widget() {
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
-o -type d -print 2> /dev/null | cut -b3-"}"
setopt localoptions pipefail no_aliases 2> /dev/null
local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m)"
local dir="$(FZF_DEFAULT_COMMAND=${FZF_ALT_C_COMMAND:-} FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --walker=dir,follow,hidden --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-}" $(__fzfcmd) +m < /dev/tty)"
if [[ -z "$dir" ]]; then
zle redisplay
return 0
fi
cd "$dir"
unset dir # ensure this doesn't end up appearing in prompt expansion
zle push-line # Clear buffer. Auto-restored on next prompt.
BUFFER="builtin cd -- ${(q)dir:a}"
zle accept-line
local ret=$?
zle fzf-redraw-prompt
unset dir # ensure this doesn't end up appearing in prompt expansion
zle reset-prompt
return $ret
}
zle -N fzf-cd-widget
bindkey '\ec' fzf-cd-widget
if [[ "${FZF_ALT_C_COMMAND-x}" != "" ]]; then
zle -N fzf-cd-widget
bindkey -M emacs '\ec' fzf-cd-widget
bindkey -M vicmd '\ec' fzf-cd-widget
bindkey -M viins '\ec' fzf-cd-widget
fi
# CTRL-R - Paste the selected command from history into the command line
fzf-history-widget() {
local selected num
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
selected=( $(fc -rl 1 |
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
selected="$(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} ${FZF_DEFAULT_OPTS-} -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,ctrl-z:ignore ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m" $(__fzfcmd))"
local ret=$?
if [ -n "$selected" ]; then
num=$selected[1]
if [ -n "$num" ]; then
zle vi-fetch-history -n $num
num=$(awk '{print $1}' <<< "$selected")
if [[ "$num" =~ '^[1-9][0-9]*\*?$' ]]; then
zle vi-fetch-history -n ${num%\*}
else # selected is a custom query, not from history
LBUFFER="$selected"
fi
fi
zle reset-prompt
return $ret
}
zle -N fzf-history-widget
bindkey '^R' fzf-history-widget
zle -N fzf-history-widget
bindkey -M emacs '^R' fzf-history-widget
bindkey -M vicmd '^R' fzf-history-widget
bindkey -M viins '^R' fzf-history-widget
fi
} always {
eval $__fzf_key_bindings_options
'unset' '__fzf_key_bindings_options'
}

View File

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

131
src/actiontype_string.go Normal file
View File

@@ -0,0 +1,131 @@
// Code generated by "stringer -type=actionType"; DO NOT EDIT.
package fzf
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[actIgnore-0]
_ = x[actStart-1]
_ = x[actClick-2]
_ = x[actInvalid-3]
_ = x[actChar-4]
_ = x[actMouse-5]
_ = x[actBeginningOfLine-6]
_ = x[actAbort-7]
_ = x[actAccept-8]
_ = x[actAcceptNonEmpty-9]
_ = x[actAcceptOrPrintQuery-10]
_ = x[actBackwardChar-11]
_ = x[actBackwardDeleteChar-12]
_ = x[actBackwardDeleteCharEof-13]
_ = x[actBackwardWord-14]
_ = x[actCancel-15]
_ = x[actChangeBorderLabel-16]
_ = x[actChangeHeader-17]
_ = x[actChangePreviewLabel-18]
_ = x[actChangePrompt-19]
_ = x[actChangeQuery-20]
_ = x[actClearScreen-21]
_ = x[actClearQuery-22]
_ = x[actClearSelection-23]
_ = x[actClose-24]
_ = x[actDeleteChar-25]
_ = x[actDeleteCharEof-26]
_ = x[actEndOfLine-27]
_ = x[actForwardChar-28]
_ = x[actForwardWord-29]
_ = x[actKillLine-30]
_ = x[actKillWord-31]
_ = x[actUnixLineDiscard-32]
_ = x[actUnixWordRubout-33]
_ = x[actYank-34]
_ = x[actBackwardKillWord-35]
_ = x[actSelectAll-36]
_ = x[actDeselectAll-37]
_ = x[actToggle-38]
_ = x[actToggleSearch-39]
_ = x[actToggleAll-40]
_ = x[actToggleDown-41]
_ = x[actToggleUp-42]
_ = x[actToggleIn-43]
_ = x[actToggleOut-44]
_ = x[actToggleTrack-45]
_ = x[actToggleTrackCurrent-46]
_ = x[actToggleHeader-47]
_ = x[actTrackCurrent-48]
_ = x[actUntrackCurrent-49]
_ = x[actDown-50]
_ = x[actUp-51]
_ = x[actPageUp-52]
_ = x[actPageDown-53]
_ = x[actPosition-54]
_ = x[actHalfPageUp-55]
_ = x[actHalfPageDown-56]
_ = x[actOffsetUp-57]
_ = x[actOffsetDown-58]
_ = x[actJump-59]
_ = x[actJumpAccept-60]
_ = x[actPrintQuery-61]
_ = x[actRefreshPreview-62]
_ = x[actReplaceQuery-63]
_ = x[actToggleSort-64]
_ = x[actShowPreview-65]
_ = x[actHidePreview-66]
_ = x[actTogglePreview-67]
_ = x[actTogglePreviewWrap-68]
_ = x[actTransform-69]
_ = x[actTransformBorderLabel-70]
_ = x[actTransformHeader-71]
_ = x[actTransformPreviewLabel-72]
_ = x[actTransformPrompt-73]
_ = x[actTransformQuery-74]
_ = x[actPreview-75]
_ = x[actChangePreview-76]
_ = x[actChangePreviewWindow-77]
_ = x[actPreviewTop-78]
_ = x[actPreviewBottom-79]
_ = x[actPreviewUp-80]
_ = x[actPreviewDown-81]
_ = x[actPreviewPageUp-82]
_ = x[actPreviewPageDown-83]
_ = x[actPreviewHalfPageUp-84]
_ = x[actPreviewHalfPageDown-85]
_ = x[actPrevHistory-86]
_ = x[actPrevSelected-87]
_ = x[actPut-88]
_ = x[actNextHistory-89]
_ = x[actNextSelected-90]
_ = x[actExecute-91]
_ = x[actExecuteSilent-92]
_ = x[actExecuteMulti-93]
_ = x[actSigStop-94]
_ = x[actFirst-95]
_ = x[actLast-96]
_ = x[actReload-97]
_ = x[actReloadSync-98]
_ = x[actDisableSearch-99]
_ = x[actEnableSearch-100]
_ = x[actSelect-101]
_ = x[actDeselect-102]
_ = x[actUnbind-103]
_ = x[actRebind-104]
_ = x[actBecome-105]
_ = x[actResponse-106]
_ = x[actShowHeader-107]
_ = x[actHideHeader-108]
}
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, 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 {
if i < 0 || i >= actionType(len(_actionType_index)-1) {
return "actionType(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _actionType_name[_actionType_index[i]:_actionType_index[i+1]]
}

View File

@@ -80,6 +80,7 @@ Scoring criteria
import (
"bytes"
"fmt"
"os"
"strings"
"unicode"
"unicode/utf8"
@@ -89,6 +90,10 @@ import (
var DEBUG bool
var delimiterChars = "/,:;|"
const whiteChars = " \t\n\v\f\r\x85\xA0"
func indexAt(index int, max int, forward bool) int {
if forward {
return index
@@ -107,7 +112,7 @@ type Result struct {
const (
scoreMatch = 16
scoreGapStart = -3
scoreGapExtention = -1
scoreGapExtension = -1
// We prefer matches at the beginning of a word, but the bonus should not be
// too great to prevent the longer acronym matches from always winning over
@@ -125,31 +130,66 @@ const (
// Edge-triggered bonus for matches in camelCase words.
// Compared to word-boundary case, they don't accompany single-character gaps
// (e.g. FooBar vs. foo-bar), so we deduct bonus point accordingly.
bonusCamel123 = bonusBoundary + scoreGapExtention
bonusCamel123 = bonusBoundary + scoreGapExtension
// Minimum bonus point given to characters in consecutive chunks.
// Note that bonus points for consecutive matches shouldn't have needed if we
// used fixed match score as in the original algorithm.
bonusConsecutive = -(scoreGapStart + scoreGapExtention)
bonusConsecutive = -(scoreGapStart + scoreGapExtension)
// The first character in the typed pattern usually has more significance
// than the rest so it's important that it appears at special positions where
// bonus points are given. e.g. "to-go" vs. "ongoing" on "og" or on "ogo".
// bonus points are given, e.g. "to-go" vs. "ongoing" on "og" or on "ogo".
// The amount of the extra bonus should be limited so that the gap penalty is
// still respected.
bonusFirstCharMultiplier = 2
)
var (
// Extra bonus for word boundary after whitespace character or beginning of the string
bonusBoundaryWhite int16 = bonusBoundary + 2
// Extra bonus for word boundary after slash, colon, semi-colon, and comma
bonusBoundaryDelimiter int16 = bonusBoundary + 1
initialCharClass charClass = charWhite
)
type charClass int
const (
charNonWord charClass = iota
charWhite charClass = iota
charNonWord
charDelimiter
charLower
charUpper
charLetter
charNumber
)
func Init(scheme string) bool {
switch scheme {
case "default":
bonusBoundaryWhite = bonusBoundary + 2
bonusBoundaryDelimiter = bonusBoundary + 1
case "path":
bonusBoundaryWhite = bonusBoundary
bonusBoundaryDelimiter = bonusBoundary + 1
if os.PathSeparator == '/' {
delimiterChars = "/"
} else {
delimiterChars = string([]rune{os.PathSeparator, '/'})
}
initialCharClass = charDelimiter
case "history":
bonusBoundaryWhite = bonusBoundary
bonusBoundaryDelimiter = bonusBoundary
default:
return false
}
return true
}
func posArray(withPos bool, len int) *[]int {
if withPos {
pos := make([]int, 0, len)
@@ -181,6 +221,10 @@ func charClassOfAscii(char rune) charClass {
return charUpper
} else if char >= '0' && char <= '9' {
return charNumber
} else if strings.ContainsRune(whiteChars, char) {
return charWhite
} else if strings.ContainsRune(delimiterChars, char) {
return charDelimiter
}
return charNonWord
}
@@ -194,6 +238,10 @@ func charClassOfNonAscii(char rune) charClass {
return charNumber
} else if unicode.IsLetter(char) {
return charLetter
} else if unicode.IsSpace(char) {
return charWhite
} else if strings.ContainsRune(delimiterChars, char) {
return charDelimiter
}
return charNonWord
}
@@ -206,22 +254,38 @@ func charClassOf(char rune) charClass {
}
func bonusFor(prevClass charClass, class charClass) int16 {
if prevClass == charNonWord && class != charNonWord {
// Word boundary
return bonusBoundary
} else if prevClass == charLower && class == charUpper ||
if class > charNonWord {
switch prevClass {
case charWhite:
// Word boundary after whitespace
return bonusBoundaryWhite
case charDelimiter:
// Word boundary after a delimiter character
return bonusBoundaryDelimiter
case charNonWord:
// Word boundary
return bonusBoundary
}
}
if prevClass == charLower && class == charUpper ||
prevClass != charNumber && class == charNumber {
// camelCase letter123
return bonusCamel123
} else if class == charNonWord {
}
switch class {
case charNonWord, charDelimiter:
return bonusNonWord
case charWhite:
return bonusBoundaryWhite
}
return 0
}
func bonusAt(input *util.Chars, idx int) int16 {
if idx == 0 {
return bonusBoundary
return bonusBoundaryWhite
}
return bonusFor(charClassOf(input.Get(idx-1)), charClassOf(input.Get(idx)))
}
@@ -377,7 +441,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
// Phase 2. Calculate bonus for each point
maxScore, maxScorePos := int16(0), 0
pidx, lastIdx := 0, 0
pchar0, pchar, prevH0, prevClass, inGap := pattern[0], pattern[0], int16(0), charNonWord, false
pchar0, pchar, prevH0, prevClass, inGap := pattern[0], pattern[0], int16(0), initialCharClass, false
Tsub := T[idx:]
H0sub, C0sub, Bsub := H0[idx:][:len(Tsub)], C0[idx:][:len(Tsub)], B[idx:][:len(Tsub)]
for off, char := range Tsub {
@@ -417,14 +481,14 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
C0sub[off] = 1
if M == 1 && (forward && score > maxScore || !forward && score >= maxScore) {
maxScore, maxScorePos = score, idx+off
if forward && bonus == bonusBoundary {
if forward && bonus >= bonusBoundary {
break
}
}
inGap = false
} else {
if inGap {
H0sub[off] = util.Max16(prevH0+scoreGapExtention, 0)
H0sub[off] = util.Max16(prevH0+scoreGapExtension, 0)
} else {
H0sub[off] = util.Max16(prevH0+scoreGapStart, 0)
}
@@ -477,7 +541,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
var s1, s2, consecutive int16
if inGap {
s2 = Hleft[off] + scoreGapExtention
s2 = Hleft[off] + scoreGapExtension
} else {
s2 = Hleft[off] + scoreGapStart
}
@@ -486,11 +550,14 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
s1 = Hdiag[off] + scoreMatch
b := Bsub[off]
consecutive = Cdiag[off] + 1
// Break consecutive chunk
if b == bonusBoundary {
consecutive = 1
} else if consecutive > 1 {
b = util.Max16(b, util.Max16(bonusConsecutive, B[col-int(consecutive)+1]))
if consecutive > 1 {
fb := B[col-int(consecutive)+1]
// Break consecutive chunk
if b >= bonusBoundary && b > fb {
consecutive = 1
} else {
b = util.Max16(b, util.Max16(bonusConsecutive, fb))
}
}
if s1+b < s2 {
s1 += Bsub[off]
@@ -555,7 +622,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
func calculateScore(caseSensitive bool, normalize bool, text *util.Chars, pattern []rune, sidx int, eidx int, withPos bool) (int, *[]int) {
pidx, score, inGap, consecutive, firstBonus := 0, 0, false, 0, int16(0)
pos := posArray(withPos, len(pattern))
prevClass := charNonWord
prevClass := initialCharClass
if sidx > 0 {
prevClass = charClassOf(text.Get(sidx - 1))
}
@@ -583,7 +650,7 @@ func calculateScore(caseSensitive bool, normalize bool, text *util.Chars, patter
firstBonus = bonus
} else {
// Break consecutive chunk
if bonus == bonusBoundary {
if bonus >= bonusBoundary && bonus > firstBonus {
firstBonus = bonus
}
bonus = util.Max16(util.Max16(bonus, firstBonus), bonusConsecutive)
@@ -598,7 +665,7 @@ func calculateScore(caseSensitive bool, normalize bool, text *util.Chars, patter
pidx++
} else {
if inGap {
score += scoreGapExtention
score += scoreGapExtension
} else {
score += scoreGapStart
}
@@ -741,7 +808,7 @@ func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *uti
if bonus > bestBonus {
bestPos, bestBonus = index, bonus
}
if bonus == bonusBoundary {
if bonus >= bonusBoundary {
break
}
index -= pidx - 1
@@ -877,8 +944,8 @@ func EqualMatch(caseSensitive bool, normalize bool, forward bool, text *util.Cha
match = runesStr == string(pattern)
}
if match {
return Result{trimmedLen, trimmedLen + lenPattern, (scoreMatch+bonusBoundary)*lenPattern +
(bonusFirstCharMultiplier-1)*bonusBoundary}, nil
return Result{trimmedLen, trimmedLen + lenPattern, (scoreMatch+int(bonusBoundaryWhite))*lenPattern +
(bonusFirstCharMultiplier-1)*int(bonusBoundaryWhite)}, nil
}
return Result{-1, -1, 0}, nil
}

View File

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

View File

@@ -405,6 +405,74 @@ var normalized map[rune]rune = map[rune]rune{
0x024E: 'Y', // WITH STROKE, LATIN CAPITAL LETTER
0x028F: 'Y', // , LATIN LETTER SMALL CAPITAL
0x1D22: 'Z', // , LATIN LETTER SMALL CAPITAL
'Ắ': 'A',
'Ấ': 'A',
'Ằ': 'A',
'Ầ': 'A',
'Ẳ': 'A',
'Ẩ': 'A',
'Ẵ': 'A',
'Ẫ': 'A',
'Ặ': 'A',
'Ậ': 'A',
'ắ': 'a',
'ấ': 'a',
'ằ': 'a',
'ầ': 'a',
'ẳ': 'a',
'ẩ': 'a',
'ẵ': 'a',
'ẫ': 'a',
'ặ': 'a',
'ậ': 'a',
'Ế': 'E',
'Ề': 'E',
'Ể': 'E',
'Ễ': 'E',
'Ệ': 'E',
'ế': 'e',
'ề': 'e',
'ể': 'e',
'ễ': 'e',
'ệ': 'e',
'Ố': 'O',
'Ớ': 'O',
'Ồ': 'O',
'Ờ': 'O',
'Ổ': 'O',
'Ở': 'O',
'Ỗ': 'O',
'Ỡ': 'O',
'Ộ': 'O',
'Ợ': 'O',
'ố': 'o',
'ớ': 'o',
'ồ': 'o',
'ờ': 'o',
'ổ': 'o',
'ở': 'o',
'ỗ': 'o',
'ỡ': 'o',
'ộ': 'o',
'ợ': 'o',
'Ứ': 'U',
'Ừ': 'U',
'Ử': 'U',
'Ữ': 'U',
'Ự': 'U',
'ứ': 'u',
'ừ': 'u',
'ử': 'u',
'ữ': 'u',
'ự': 'u',
}
// NormalizeRunes normalizes latin script letters

View File

@@ -1,8 +1,6 @@
package fzf
import (
"bytes"
"regexp"
"strconv"
"strings"
"unicode/utf8"
@@ -19,17 +17,18 @@ type ansiState struct {
fg tui.Color
bg tui.Color
attr tui.Attr
lbg tui.Color
}
func (s *ansiState) colored() bool {
return s.fg != -1 || s.bg != -1 || s.attr > 0
return s.fg != -1 || s.bg != -1 || s.attr > 0 || s.lbg >= 0
}
func (s *ansiState) equals(t *ansiState) bool {
if t == nil {
return !s.colored()
}
return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr
return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr && s.lbg == t.lbg
}
func (s *ansiState) ToString() string {
@@ -56,6 +55,9 @@ func (s *ansiState) ToString() string {
if s.attr&tui.Reverse > 0 {
ret += "7;"
}
if s.attr&tui.StrikeThrough > 0 {
ret += "9;"
}
ret += toAnsiString(s.fg, 30) + toAnsiString(s.bg, 40)
return "\x1b[" + strings.TrimSuffix(ret, ";") + "m"
@@ -81,73 +83,161 @@ func toAnsiString(color tui.Color, offset int) string {
return ret + ";"
}
var ansiRegex *regexp.Regexp
func init() {
/*
References:
- https://github.com/gnachman/iTerm2
- http://ascii-table.com/ansi-escape-sequences.php
- http://ascii-table.com/ansi-escape-sequences-vt-100.php
- http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
- https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
*/
// The following regular expression will include not all but most of the
// frequently used ANSI sequences
ansiRegex = regexp.MustCompile("(?:\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)")
func isPrint(c uint8) bool {
return '\x20' <= c && c <= '\x7e'
}
func findAnsiStart(str string) int {
idx := 0
for ; idx < len(str); idx++ {
b := str[idx]
if b == 0x1b || b == 0x0e || b == 0x0f {
return idx
func matchOperatingSystemCommand(s string) int {
// `\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)`
// ^ match starting here
//
i := 5 // prefix matched in nextAnsiEscapeSequence()
for ; i < len(s) && isPrint(s[i]); i++ {
}
if i < len(s) {
if s[i] == '\x07' {
return i + 1
}
if b == 0x08 && idx > 0 {
return idx - 1
if s[i] == '\x1b' && i < len(s)-1 && s[i+1] == '\\' {
return i + 2
}
}
return idx
return -1
}
func matchControlSequence(s string) int {
// `\x1b[\\[()][0-9;:?]*[a-zA-Z@]`
// ^ match starting here
//
i := 2 // prefix matched in nextAnsiEscapeSequence()
for ; i < len(s); i++ {
c := s[i]
switch c {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ';', ':', '?':
// ok
default:
if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '@' {
return i + 1
}
return -1
}
}
return -1
}
func isCtrlSeqStart(c uint8) bool {
switch c {
case '\\', '[', '(', ')':
return true
}
return false
}
// nextAnsiEscapeSequence returns the ANSI escape sequence and is equivalent to
// calling FindStringIndex() on the below regex (which was originally used):
//
// "(?:\x1b[\\[()][0-9;:?]*[a-zA-Z@]|\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)"
func nextAnsiEscapeSequence(s string) (int, int) {
// fast check for ANSI escape sequences
i := 0
for ; i < len(s); i++ {
switch s[i] {
case '\x0e', '\x0f', '\x1b', '\x08':
// We ignore the fact that '\x08' cannot be the first char
// in the string and be an escape sequence for the sake of
// speed and simplicity.
goto Loop
}
}
return -1, -1
Loop:
for ; i < len(s); i++ {
switch s[i] {
case '\x08':
// backtrack to match: `.\x08`
if i > 0 && s[i-1] != '\n' {
if s[i-1] < utf8.RuneSelf {
return i - 1, i + 1
}
_, n := utf8.DecodeLastRuneInString(s[:i])
return i - n, i + 1
}
case '\x1b':
// match: `\x1b[\\[()][0-9;:?]*[a-zA-Z@]`
if i+2 < len(s) && isCtrlSeqStart(s[i+1]) {
if j := matchControlSequence(s[i:]); j != -1 {
return i, i + j
}
}
// match: `\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)`
if i+5 < len(s) && s[i+1] == ']' && isNumeric(s[i+2]) &&
(s[i+3] == ';' || s[i+3] == ':') && isPrint(s[i+4]) {
if j := matchOperatingSystemCommand(s[i:]); j != -1 {
return i, i + j
}
}
// match: `\x1b.`
if i+1 < len(s) && s[i+1] != '\n' {
if s[i+1] < utf8.RuneSelf {
return i, i + 2
}
_, n := utf8.DecodeRuneInString(s[i+1:])
return i, i + n + 1
}
case '\x0e', '\x0f':
// match: `[\x0e\x0f]`
return i, i + 1
}
}
return -1, -1
}
func extractColor(str string, state *ansiState, proc func(string, *ansiState) bool) (string, *[]ansiOffset, *ansiState) {
var offsets []ansiOffset
var output bytes.Buffer
// We append to a stack allocated variable that we'll
// later copy and return, to save on allocations.
offsets := make([]ansiOffset, 0, 32)
if state != nil {
offsets = append(offsets, ansiOffset{[2]int32{0, 0}, *state})
}
prevIdx := 0
runeCount := 0
var (
pstate *ansiState // lazily allocated
output strings.Builder
prevIdx int
runeCount int
)
for idx := 0; idx < len(str); {
idx += findAnsiStart(str[idx:])
if idx == len(str) {
// Make sure that we found an ANSI code
start, end := nextAnsiEscapeSequence(str[idx:])
if start == -1 {
break
}
// Make sure that we found an ANSI code
offset := ansiRegex.FindStringIndex(str[idx:])
if len(offset) < 2 {
idx++
continue
}
offset[0] += idx
offset[1] += idx
idx = offset[1]
start += idx
idx += end
// Check if we should continue
prev := str[prevIdx:offset[0]]
prev := str[prevIdx:start]
if proc != nil && !proc(prev, state) {
return "", nil, nil
}
prevIdx = idx
prevIdx = offset[1]
runeCount += utf8.RuneCountInString(prev)
output.WriteString(prev)
if len(prev) != 0 {
runeCount += utf8.RuneCountInString(prev)
// Grow the buffer size to the maximum possible length (string length
// containing ansi codes) to avoid repetitive allocation
if output.Cap() == 0 {
output.Grow(len(str))
}
output.WriteString(prev)
}
newState := interpretCode(str[offset[0]:offset[1]], state)
newState := interpretCode(str[start:idx], state)
if !newState.equals(state) {
if state != nil {
// Update last offset
@@ -156,8 +246,15 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
if newState.colored() {
// Append new offset
state = newState
offsets = append(offsets, ansiOffset{[2]int32{int32(runeCount), int32(runeCount)}, *state})
if pstate == nil {
pstate = &ansiState{}
}
*pstate = newState
state = pstate
offsets = append(offsets, ansiOffset{
[2]int32{int32(runeCount), int32(runeCount)},
newState,
})
} else {
// Discard state
state = nil
@@ -167,7 +264,6 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
var rest string
var trimmed string
if prevIdx == 0 {
// No ANSI code found
rest = str
@@ -177,48 +273,89 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
output.WriteString(rest)
trimmed = output.String()
}
if len(rest) > 0 && state != nil {
// Update last offset
runeCount += utf8.RuneCountInString(rest)
(&offsets[len(offsets)-1]).offset[1] = int32(runeCount)
}
if proc != nil {
proc(rest, state)
}
if len(offsets) == 0 {
return trimmed, nil, state
if len(offsets) > 0 {
if len(rest) > 0 && state != nil {
// Update last offset
runeCount += utf8.RuneCountInString(rest)
(&offsets[len(offsets)-1]).offset[1] = int32(runeCount)
}
// Return a copy of the offsets slice
a := make([]ansiOffset, len(offsets))
copy(a, offsets)
return trimmed, &a, state
}
return trimmed, &offsets, state
return trimmed, nil, state
}
func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
// State
var state *ansiState
if prevState == nil {
state = &ansiState{-1, -1, 0}
func parseAnsiCode(s string, delimiter byte) (int, byte, string) {
var remaining string
i := -1
if delimiter == 0 {
// Faster than strings.IndexAny(";:")
i = strings.IndexByte(s, ';')
if i < 0 {
i = strings.IndexByte(s, ':')
}
} else {
state = &ansiState{prevState.fg, prevState.bg, prevState.attr}
i = strings.IndexByte(s, delimiter)
}
if i >= 0 {
delimiter = s[i]
remaining = s[i+1:]
s = s[:i]
}
if len(s) > 0 {
// Inlined version of strconv.Atoi() that only handles positive
// integers and does not allocate on error.
code := 0
for _, ch := range sbytes(s) {
ch -= '0'
if ch > 9 {
return -1, delimiter, remaining
}
code = code*10 + int(ch)
}
return code, delimiter, remaining
}
return -1, delimiter, remaining
}
func interpretCode(ansiCode string, prevState *ansiState) ansiState {
var state ansiState
if prevState == nil {
state = ansiState{-1, -1, 0, -1}
} else {
state = ansiState{prevState.fg, prevState.bg, prevState.attr, prevState.lbg}
}
if ansiCode[0] != '\x1b' || ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' {
if prevState != nil && strings.HasSuffix(ansiCode, "0K") {
state.lbg = prevState.bg
}
return state
}
ptr := &state.fg
state256 := 0
init := func() {
if len(ansiCode) <= 3 {
state.fg = -1
state.bg = -1
state.attr = 0
state256 = 0
return state
}
ansiCode = ansiCode[2 : len(ansiCode)-1]
if len(ansiCode) == 0 {
init()
}
for _, code := range strings.Split(ansiCode, ";") {
if num, err := strconv.Atoi(code); err == nil {
state256 := 0
ptr := &state.fg
var delimiter byte = 0
count := 0
for len(ansiCode) != 0 {
var num int
if num, delimiter, ansiCode = parseAnsiCode(ansiCode, delimiter); num != -1 {
count++
switch state256 {
case 0:
switch num {
@@ -244,12 +381,26 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
state.attr = state.attr | tui.Blink
case 7:
state.attr = state.attr | tui.Reverse
case 9:
state.attr = state.attr | tui.StrikeThrough
case 22:
state.attr = state.attr &^ tui.Bold
state.attr = state.attr &^ tui.Dim
case 23: // tput rmso
state.attr = state.attr &^ tui.Italic
case 24: // tput rmul
state.attr = state.attr &^ tui.Underline
case 25:
state.attr = state.attr &^ tui.Blink
case 27:
state.attr = state.attr &^ tui.Reverse
case 29:
state.attr = state.attr &^ tui.StrikeThrough
case 0:
init()
state.fg = -1
state.bg = -1
state.attr = 0
state256 = 0
default:
if num >= 30 && num <= 37 {
state.fg = tui.Color(num - 30)
@@ -285,6 +436,14 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
}
}
}
// Empty sequence: reset
if count == 0 {
state.fg = -1
state.bg = -1
state.attr = 0
}
if state256 > 0 {
*ptr = -1
}

View File

@@ -1,13 +1,192 @@
package fzf
import (
"fmt"
"math/rand"
"regexp"
"strings"
"testing"
"unicode/utf8"
"github.com/junegunn/fzf/src/tui"
)
// The following regular expression will include not all but most of the
// frequently used ANSI sequences. This regex is used as a reference for
// testing nextAnsiEscapeSequence().
//
// References:
// - https://github.com/gnachman/iTerm2
// - https://web.archive.org/web/20090204053813/http://ascii-table.com/ansi-escape-sequences.php
// (archived from http://ascii-table.com/ansi-escape-sequences.php)
// - https://web.archive.org/web/20090227051140/http://ascii-table.com/ansi-escape-sequences-vt-100.php
// (archived from http://ascii-table.com/ansi-escape-sequences-vt-100.php)
// - http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
// - https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
var ansiRegexReference = regexp.MustCompile("(?:\x1b[\\[()][0-9;:]*[a-zA-Z@]|\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)")
func testParserReference(t testing.TB, str string) {
t.Helper()
toSlice := func(start, end int) []int {
if start == -1 {
return nil
}
return []int{start, end}
}
s := str
for i := 0; ; i++ {
got := toSlice(nextAnsiEscapeSequence(s))
exp := ansiRegexReference.FindStringIndex(s)
equal := len(got) == len(exp)
if equal {
for i := 0; i < len(got); i++ {
if got[i] != exp[i] {
equal = false
break
}
}
}
if !equal {
var exps, gots []rune
if len(got) == 2 {
gots = []rune(s[got[0]:got[1]])
}
if len(exp) == 2 {
exps = []rune(s[exp[0]:exp[1]])
}
t.Errorf("%d: %q: got: %v (%q) want: %v (%q)", i, s, got, gots, exp, exps)
return
}
if len(exp) == 0 {
return
}
s = s[exp[1]:]
}
}
func TestNextAnsiEscapeSequence(t *testing.T) {
testStrs := []string{
"\x1b[0mhello world",
"\x1b[1mhello world",
"椙\x1b[1m椙",
"椙\x1b[1椙m椙",
"\x1b[1mhello \x1b[mw\x1b7o\x1b8r\x1b(Bl\x1b[2@d",
"\x1b[1mhello \x1b[Kworld",
"hello \x1b[34;45;1mworld",
"hello \x1b[34;45;1mwor\x1b[34;45;1mld",
"hello \x1b[34;45;1mwor\x1b[0mld",
"hello \x1b[34;48;5;233;1mwo\x1b[38;5;161mr\x1b[0ml\x1b[38;5;161md",
"hello \x1b[38;5;38;48;5;48;1mwor\x1b[38;5;48;48;5;38ml\x1b[0md",
"hello \x1b[32;1mworld",
"hello world",
"hello \x1b[0;38;5;200;48;5;100mworld",
"\x1b椙",
"椙\x08",
"\n\x08",
"X\x08",
"",
"\x1b]4;3;rgb:aa/bb/cc\x07 ",
"\x1b]4;3;rgb:aa/bb/cc\x1b\\ ",
ansiBenchmarkString,
}
for _, s := range testStrs {
testParserReference(t, s)
}
}
func TestNextAnsiEscapeSequence_Fuzz_Modified(t *testing.T) {
t.Parallel()
if testing.Short() {
t.Skip("short test")
}
testStrs := []string{
"\x1b[0mhello world",
"\x1b[1mhello world",
"椙\x1b[1m椙",
"椙\x1b[1椙m椙",
"\x1b[1mhello \x1b[mw\x1b7o\x1b8r\x1b(Bl\x1b[2@d",
"\x1b[1mhello \x1b[Kworld",
"hello \x1b[34;45;1mworld",
"hello \x1b[34;45;1mwor\x1b[34;45;1mld",
"hello \x1b[34;45;1mwor\x1b[0mld",
"hello \x1b[34;48;5;233;1mwo\x1b[38;5;161mr\x1b[0ml\x1b[38;5;161md",
"hello \x1b[38;5;38;48;5;48;1mwor\x1b[38;5;48;48;5;38ml\x1b[0md",
"hello \x1b[32;1mworld",
"hello world",
"hello \x1b[0;38;5;200;48;5;100mworld",
ansiBenchmarkString,
}
replacementBytes := [...]rune{'\x0e', '\x0f', '\x1b', '\x08'}
modifyString := func(s string, rr *rand.Rand) string {
n := rr.Intn(len(s))
b := []rune(s)
for ; n >= 0 && len(b) != 0; n-- {
i := rr.Intn(len(b))
switch x := rr.Intn(4); x {
case 0:
b = append(b[:i], b[i+1:]...)
case 1:
j := rr.Intn(len(replacementBytes) - 1)
b[i] = replacementBytes[j]
case 2:
x := rune(rr.Intn(utf8.MaxRune))
for !utf8.ValidRune(x) {
x = rune(rr.Intn(utf8.MaxRune))
}
b[i] = x
case 3:
b[i] = rune(rr.Intn(utf8.MaxRune)) // potentially invalid
default:
t.Fatalf("unsupported value: %d", x)
}
}
return string(b)
}
rr := rand.New(rand.NewSource(1))
for _, s := range testStrs {
for i := 1_000; i >= 0; i-- {
testParserReference(t, modifyString(s, rr))
}
}
}
func TestNextAnsiEscapeSequence_Fuzz_Random(t *testing.T) {
t.Parallel()
if testing.Short() {
t.Skip("short test")
}
randomString := func(rr *rand.Rand) string {
numChars := rand.Intn(50)
codePoints := make([]rune, numChars)
for i := 0; i < len(codePoints); i++ {
var r rune
for n := 0; n < 1000; n++ {
r = rune(rr.Intn(utf8.MaxRune))
// Allow 10% of runes to be invalid
if utf8.ValidRune(r) || rr.Float64() < 0.10 {
break
}
}
codePoints[i] = r
}
return string(codePoints)
}
rr := rand.New(rand.NewSource(1))
for i := 0; i < 100_000; i++ {
testParserReference(t, randomString(rr))
}
}
func TestExtractColor(t *testing.T) {
assert := func(offset ansiOffset, b int32, e int32, fg tui.Color, bg tui.Color, bold bool) {
var attr tui.Attr
@@ -29,7 +208,7 @@ func TestExtractColor(t *testing.T) {
if output != "hello world" {
t.Errorf("Invalid output: %s %v", output, []rune(output))
}
fmt.Println(src, ansiOffsets, clean)
t.Log(src, ansiOffsets, clean)
assertion(ansiOffsets, state)
}
@@ -168,7 +347,10 @@ func TestAnsiCodeStringConversion(t *testing.T) {
}
}
assert("\x1b[m", nil, "")
assert("\x1b[m", &ansiState{attr: tui.Blink}, "")
assert("\x1b[m", &ansiState{attr: tui.Blink, lbg: -1}, "")
assert("\x1b[0m", &ansiState{fg: 4, bg: 4, lbg: -1}, "")
assert("\x1b[;m", &ansiState{fg: 4, bg: 4, lbg: -1}, "")
assert("\x1b[;;m", &ansiState{fg: 4, bg: 4, lbg: -1}, "")
assert("\x1b[31m", nil, "\x1b[31;49m")
assert("\x1b[41m", nil, "\x1b[39;41m")
@@ -176,12 +358,74 @@ func TestAnsiCodeStringConversion(t *testing.T) {
assert("\x1b[92m", nil, "\x1b[92;49m")
assert("\x1b[102m", nil, "\x1b[39;102m")
assert("\x1b[31m", &ansiState{fg: 4, bg: 4}, "\x1b[31;44m")
assert("\x1b[1;2;31m", &ansiState{fg: 2, bg: -1, attr: tui.Reverse}, "\x1b[1;2;7;31;49m")
assert("\x1b[31m", &ansiState{fg: 4, bg: 4, lbg: -1}, "\x1b[31;44m")
assert("\x1b[1;2;31m", &ansiState{fg: 2, bg: -1, attr: tui.Reverse, lbg: -1}, "\x1b[1;2;7;31;49m")
assert("\x1b[38;5;100;48;5;200m", nil, "\x1b[38;5;100;48;5;200m")
assert("\x1b[38:5:100:48:5:200m", nil, "\x1b[38;5;100;48;5;200m")
assert("\x1b[48;5;100;38;5;200m", nil, "\x1b[38;5;200;48;5;100m")
assert("\x1b[48;5;100;38;2;10;20;30;1m", nil, "\x1b[1;38;2;10;20;30;48;5;100m")
assert("\x1b[48;5;100;38;2;10;20;30;7m",
&ansiState{attr: tui.Dim | tui.Italic, fg: 1, bg: 1},
"\x1b[2;3;7;38;2;10;20;30;48;5;100m")
}
func TestParseAnsiCode(t *testing.T) {
tests := []struct {
In, Exp string
N int
}{
{"123", "", 123},
{"1a", "", -1},
{"1a;12", "12", -1},
{"12;a", "a", 12},
{"-2", "", -1},
}
for _, x := range tests {
n, _, s := parseAnsiCode(x.In, 0)
if n != x.N || s != x.Exp {
t.Fatalf("%q: got: (%d %q) want: (%d %q)", x.In, n, s, x.N, x.Exp)
}
}
}
// kernel/bpf/preload/iterators/README
const ansiBenchmarkString = "\x1b[38;5;81m\x1b[01;31m\x1b[Kkernel/\x1b[0m\x1b[38:5:81mbpf/" +
"\x1b[0m\x1b[38:5:81mpreload/\x1b[0m\x1b[38;5;81miterators/" +
"\x1b[0m\x1b[38:5:149mMakefile\x1b[m\x1b[K\x1b[0m"
func BenchmarkNextAnsiEscapeSequence(b *testing.B) {
b.SetBytes(int64(len(ansiBenchmarkString)))
for i := 0; i < b.N; i++ {
s := ansiBenchmarkString
for {
_, o := nextAnsiEscapeSequence(s)
if o == -1 {
break
}
s = s[o:]
}
}
}
// Baseline test to compare the speed of nextAnsiEscapeSequence() to the
// previously used regex based implementation.
func BenchmarkNextAnsiEscapeSequence_Regex(b *testing.B) {
b.SetBytes(int64(len(ansiBenchmarkString)))
for i := 0; i < b.N; i++ {
s := ansiBenchmarkString
for {
a := ansiRegexReference.FindStringIndex(s)
if len(a) == 0 {
break
}
s = s[a[1]:]
}
}
}
func BenchmarkExtractColor(b *testing.B) {
b.SetBytes(int64(len(ansiBenchmarkString)))
for i := 0; i < b.N; i++ {
extractColor(ansiBenchmarkString, nil, nil)
}
}

View File

@@ -6,8 +6,8 @@ func TestChunkCache(t *testing.T) {
cache := NewChunkCache()
chunk1p := &Chunk{}
chunk2p := &Chunk{count: chunkSize}
items1 := []Result{Result{}}
items2 := []Result{Result{}, Result{}}
items1 := []Result{{}}
items2 := []Result{{}, {}}
cache.Add(chunk1p, "foo", items1)
cache.Add(chunk2p, "foo", items1)
cache.Add(chunk2p, "bar", items2)

View File

@@ -2,22 +2,19 @@ package fzf
import (
"math"
"os"
"time"
"github.com/junegunn/fzf/src/util"
)
const (
// Current version
version = "0.21.0"
// Core
coordinatorDelayMax time.Duration = 100 * time.Millisecond
coordinatorDelayStep time.Duration = 10 * time.Millisecond
// Reader
readerBufferSize = 64 * 1024
readerSlabSize = 128 * 1024
readerPollIntervalMin = 10 * time.Millisecond
readerPollIntervalStep = 5 * time.Millisecond
readerPollIntervalMax = 50 * time.Millisecond
@@ -27,6 +24,8 @@ const (
initialDelayTac = 100 * time.Millisecond
spinnerDuration = 100 * time.Millisecond
previewCancelWait = 500 * time.Millisecond
previewChunkDelay = 100 * time.Millisecond
previewDelayed = 500 * time.Millisecond
maxPatternLength = 300
maxMulti = math.MaxInt32
@@ -55,16 +54,6 @@ const (
defaultJumpLabels string = "asdfghjklqwertyuiopzxcvbnm1234567890ASDFGHJKLQWERTYUIOPZXCVBNM`~;:,<.>/?'\"!@#$%^&*()[{]}-_=+"
)
var defaultCommand string
func init() {
if !util.IsWindows() {
defaultCommand = `set -o pipefail; command find -L . -mindepth 1 \( -path '*/\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-`
} else if os.Getenv("TERM") == "cygwin" {
defaultCommand = `sh -c "command find -L . -mindepth 1 -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-"`
}
}
// fzf events
const (
EvtReadNew util.EventType = iota
@@ -74,6 +63,7 @@ const (
EvtSearchFin
EvtHeader
EvtReady
EvtQuit
)
const (

View File

@@ -1,34 +1,11 @@
/*
Package fzf implements fzf, a command-line fuzzy finder.
The MIT License (MIT)
Copyright (c) 2017 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// Package fzf implements fzf, a command-line fuzzy finder.
package fzf
import (
"fmt"
"os"
"time"
"unsafe"
"github.com/junegunn/fzf/src/util"
)
@@ -42,8 +19,16 @@ Matcher -> EvtSearchFin -> Terminal (update list)
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
func Run(opts *Options, revision string) {
func Run(opts *Options, version string, revision string) {
sort := opts.Sort > 0
sortCriteria = opts.Criteria
@@ -66,19 +51,19 @@ func Run(opts *Options, revision string) {
var lineAnsiState, prevLineAnsiState *ansiState
if opts.Ansi {
if opts.Theme != nil {
if opts.Theme.Colored {
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
prevLineAnsiState = lineAnsiState
trimmed, offsets, newState := extractColor(string(data), lineAnsiState, nil)
trimmed, offsets, newState := extractColor(ustring(data), lineAnsiState, nil)
lineAnsiState = newState
return util.ToChars([]byte(trimmed)), offsets
return util.ToChars(sbytes(trimmed)), offsets
}
} else {
// When color is disabled but ansi option is given,
// we simply strip out ANSI codes from the input
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
trimmed, _, _ := extractColor(string(data), nil, nil)
return util.ToChars([]byte(trimmed)), nil
trimmed, _, _ := extractColor(ustring(data), nil, nil)
return util.ToChars(sbytes(trimmed)), nil
}
}
}
@@ -90,7 +75,7 @@ func Run(opts *Options, revision string) {
if len(opts.WithNth) == 0 {
chunkList = NewChunkList(func(item *Item, data []byte) bool {
if len(header) < opts.HeaderLines {
header = append(header, string(data))
header = append(header, ustring(data))
eventBox.Set(EvtHeader, header)
return false
}
@@ -101,8 +86,8 @@ func Run(opts *Options, revision string) {
})
} else {
chunkList = NewChunkList(func(item *Item, data []byte) bool {
tokens := Tokenize(string(data), opts.Delimiter)
if opts.Ansi && opts.Theme != nil && len(tokens) > 1 {
tokens := Tokenize(ustring(data), opts.Delimiter)
if opts.Ansi && opts.Theme.Colored && len(tokens) > 1 {
var ansiState *ansiState
if prevLineAnsiState != nil {
ansiStateDup := *prevLineAnsiState
@@ -125,7 +110,7 @@ func Run(opts *Options, revision string) {
eventBox.Set(EvtHeader, header)
return false
}
item.text, item.colors = ansiProcessor([]byte(transformed))
item.text, item.colors = ansiProcessor(sbytes(transformed))
item.text.TrimTrailingWhitespaces()
item.text.Index = itemIndex
item.origText = &data
@@ -141,26 +126,30 @@ func Run(opts *Options, revision string) {
reader = NewReader(func(data []byte) bool {
return chunkList.Push(data)
}, eventBox, opts.ReadZero, opts.Filter == nil)
go reader.ReadSource()
go reader.ReadSource(opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
}
// Matcher
forward := true
for _, cri := range opts.Criteria[1:] {
if cri == byEnd {
withPos := false
for idx := len(opts.Criteria) - 1; idx > 0; idx-- {
switch opts.Criteria[idx] {
case byChunk:
withPos = true
case byEnd:
forward = false
break
}
if cri == byBegin {
break
case byBegin:
forward = true
}
}
patternBuilder := func(runes []rune) *Pattern {
return BuildPattern(
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward,
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward, withPos,
opts.Filter == nil, opts.Nth, opts.Delimiter, runes)
}
matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox)
inputRevision := 0
snapshotRevision := 0
matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox, inputRevision)
// Filtering mode
if opts.Filter != nil {
@@ -185,7 +174,7 @@ func Run(opts *Options, revision string) {
}
return false
}, eventBox, opts.ReadZero, false)
reader.ReadSource()
reader.ReadSource(opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
} else {
eventBox.Unwatch(EvtReadNew)
eventBox.WaitFor(EvtReadFin)
@@ -216,35 +205,60 @@ func Run(opts *Options, revision string) {
// Terminal I/O
terminal := NewTerminal(opts, eventBox)
maxFit := 0 // Maximum number of items that can fit on screen
padHeight := 0
heightUnknown := opts.Height.auto
if heightUnknown {
maxFit, padHeight = terminal.MaxFitAndPad()
}
deferred := opts.Select1 || opts.Exit0
go terminal.Loop()
if !deferred {
terminal.startChan <- true
if !deferred && !heightUnknown {
// Start right away
terminal.startChan <- fitpad{-1, -1}
}
// Event coordination
reading := true
clearCache := util.Once(false)
clearSelection := util.Once(false)
ticks := 0
var nextCommand *string
restart := func(command string) {
reading = true
clearCache = util.Once(true)
clearSelection = util.Once(true)
chunkList.Clear()
header = make([]string, 0, opts.HeaderLines)
go reader.restart(command)
}
var nextEnviron []string
eventBox.Watch(EvtReadNew)
total := 0
query := []rune{}
determine := func(final bool) {
if heightUnknown {
if total >= maxFit || final {
deferred = false
heightUnknown = false
terminal.startChan <- fitpad{util.Min(total, maxFit), padHeight}
}
} else if deferred {
deferred = false
terminal.startChan <- fitpad{-1, -1}
}
}
useSnapshot := false
var snapshot []*Chunk
var count int
restart := func(command string, environ []string) {
reading = true
chunkList.Clear()
itemIndex = 0
inputRevision++
header = make([]string, 0, opts.HeaderLines)
go reader.restart(command, environ)
}
for {
delay := true
ticks++
input := func() []rune {
if opts.Phony {
return []rune{}
paused, input := terminal.Input()
if !paused {
query = input
}
return []rune(terminal.Input())
return query
}
eventBox.Wait(func(events *util.Events) {
if _, fin := (*events)[EvtReadFin]; fin {
@@ -252,41 +266,80 @@ func Run(opts *Options, revision string) {
}
for evt, value := range *events {
switch evt {
case EvtQuit:
if reading {
reader.terminate()
}
os.Exit(value.(int))
case EvtReadNew, EvtReadFin:
if evt == EvtReadFin && nextCommand != nil {
restart(*nextCommand)
restart(*nextCommand, nextEnviron)
nextCommand = nil
nextEnviron = nil
break
} else {
reading = reading && evt == EvtReadNew
}
snapshot, count := chunkList.Snapshot()
terminal.UpdateCount(count, !reading, value.(*string))
if useSnapshot && evt == EvtReadFin {
useSnapshot = false
}
if !useSnapshot {
if snapshotRevision != inputRevision {
query = []rune{}
}
snapshot, count = chunkList.Snapshot()
snapshotRevision = inputRevision
}
total = count
terminal.UpdateCount(total, !reading, value.(*string))
if opts.Sync {
opts.Sync = false
terminal.UpdateList(PassMerger(&snapshot, opts.Tac), false)
terminal.UpdateList(PassMerger(&snapshot, opts.Tac, snapshotRevision))
}
matcher.Reset(snapshot, input(), false, !reading, sort, clearCache())
if heightUnknown && !deferred {
determine(!reading)
}
matcher.Reset(snapshot, input(), false, !reading, sort, snapshotRevision)
case EvtSearchNew:
var command *string
var environ []string
var changed bool
switch val := value.(type) {
case searchRequest:
sort = val.sort
command = val.command
environ = val.environ
changed = val.changed
if command != nil {
useSnapshot = val.sync
}
}
if command != nil {
if reading {
reader.terminate()
nextCommand = command
nextEnviron = environ
} else {
restart(*command)
restart(*command, environ)
}
}
if !changed {
break
}
snapshot, _ := chunkList.Snapshot()
matcher.Reset(snapshot, input(), true, !reading, sort, clearCache())
if !useSnapshot {
newSnapshot, newCount := chunkList.Snapshot()
// We want to avoid showing empty list when reload is triggered
// and the query string is changed at the same time i.e. command != nil && changed
if command == nil || newCount > 0 {
if snapshotRevision != inputRevision {
query = []rune{}
}
snapshot = newSnapshot
snapshotRevision = inputRevision
}
}
matcher.Reset(snapshot, input(), true, !reading, sort, snapshotRevision)
delay = false
case EvtSearchProgress:
@@ -306,8 +359,7 @@ func Run(opts *Options, revision string) {
if deferred {
count := val.Length()
if opts.Select1 && count > 1 || opts.Exit0 && !opts.Select1 && count > 0 {
deferred = false
terminal.startChan <- true
determine(val.final)
} else if val.final {
if opts.Exit0 && count == 0 || opts.Select1 && count == 1 {
if opts.PrintQuery {
@@ -324,11 +376,10 @@ func Run(opts *Options, revision string) {
}
os.Exit(exitNoMatch)
}
deferred = false
terminal.startChan <- true
determine(val.final)
}
}
terminal.UpdateList(val, clearSelection())
terminal.UpdateList(val)
}
}
}

View File

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

View File

@@ -1,9 +1,7 @@
package fzf
import (
"io/ioutil"
"os"
"os/user"
"runtime"
"testing"
)
@@ -12,16 +10,12 @@ func TestHistory(t *testing.T) {
maxHistory := 50
// Invalid arguments
user, _ := user.Current()
var paths []string
if runtime.GOOS == "windows" {
// GOPATH should exist, so we shouldn't be able to override it
paths = []string{os.Getenv("GOPATH")}
} else {
paths = []string{"/etc", "/proc"}
if user.Name != "root" {
paths = append(paths, "/etc/sudoers")
}
}
for _, path := range paths {
@@ -30,7 +24,7 @@ func TestHistory(t *testing.T) {
}
}
f, _ := ioutil.TempFile("", "fzf-history")
f, _ := os.CreateTemp("", "fzf-history")
f.Close()
{ // Append lines

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@ package fzf
import (
"fmt"
"io/ioutil"
"os"
"testing"
"github.com/junegunn/fzf/src/tui"
@@ -65,6 +65,19 @@ func TestDelimiterRegexRegex(t *testing.T) {
}
}
func TestDelimiterRegexRegexCaret(t *testing.T) {
delim := delimiterRegexp(`(^\s*|\s+)`)
tokens := Tokenize("foo bar baz", delim)
if delim.str != nil ||
len(tokens) != 4 ||
tokens[0].text.ToString() != "" ||
tokens[1].text.ToString() != "foo " ||
tokens[2].text.ToString() != "bar " ||
tokens[3].text.ToString() != "baz" {
t.Errorf("%s %d", tokens, len(tokens))
}
}
func TestSplitNth(t *testing.T) {
{
ranges := splitNth("..")
@@ -102,7 +115,7 @@ func TestIrrelevantNth(t *testing.T) {
t.Errorf("nth should be empty: %v", opts.Nth)
}
}
for _, words := range [][]string{[]string{"--nth", "..,3", "+x"}, []string{"--nth", "3,1..", "+x"}, []string{"--nth", "..-1,1", "+x"}} {
for _, words := range [][]string{{"--nth", "..,3", "+x"}, {"--nth", "3,1..", "+x"}, {"--nth", "..-1,1", "+x"}} {
{
opts := defaultOptions()
parseOptions(opts, words)
@@ -125,26 +138,29 @@ func TestIrrelevantNth(t *testing.T) {
func TestParseKeys(t *testing.T) {
pairs := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ctrl-alt-a,ALT-enter,alt-SPACE", "")
check := func(i int, s string) {
if pairs[i] != s {
t.Errorf("%s != %s", pairs[i], s)
checkEvent := func(e tui.Event, s string) {
if pairs[e] != s {
t.Errorf("%s != %s", pairs[e], s)
}
}
check := func(et tui.EventType, s string) {
checkEvent(et.AsEvent(), s)
}
if len(pairs) != 12 {
t.Error(12)
}
check(tui.CtrlZ, "ctrl-z")
check(tui.AltZ, "alt-z")
check(tui.F2, "f2")
check(tui.AltZ+'@', "@")
check(tui.AltA, "Alt-a")
check(tui.AltZ+'!', "!")
check(tui.CtrlA+'g'-'a', "ctrl-G")
check(tui.AltZ+'J', "J")
check(tui.AltZ+'g', "g")
check(tui.CtrlAltA, "ctrl-alt-a")
check(tui.CtrlAltM, "ALT-enter")
check(tui.AltSpace, "alt-SPACE")
check(tui.CtrlG, "ctrl-G")
checkEvent(tui.AltKey('z'), "alt-z")
checkEvent(tui.Key('@'), "@")
checkEvent(tui.AltKey('a'), "Alt-a")
checkEvent(tui.Key('!'), "!")
checkEvent(tui.Key('J'), "J")
checkEvent(tui.Key('g'), "g")
checkEvent(tui.CtrlAltKey('a'), "ctrl-alt-a")
checkEvent(tui.CtrlAltKey('m'), "ALT-enter")
checkEvent(tui.AltKey(' '), "alt-SPACE")
// Synonyms
pairs = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
@@ -152,7 +168,7 @@ func TestParseKeys(t *testing.T) {
t.Error(9)
}
check(tui.CtrlM, "Return")
check(tui.AltZ+' ', "space")
checkEvent(tui.Key(' '), "space")
check(tui.Tab, "tab")
check(tui.BTab, "btab")
check(tui.ESC, "esc")
@@ -184,93 +200,105 @@ func TestParseKeysWithComma(t *testing.T) {
t.Errorf("%d != %d", a, b)
}
}
check := func(pairs map[int]string, i int, s string) {
if pairs[i] != s {
t.Errorf("%s != %s", pairs[i], s)
check := func(pairs map[tui.Event]string, e tui.Event, s string) {
if pairs[e] != s {
t.Errorf("%s != %s", pairs[e], s)
}
}
pairs := parseKeyChords(",", "")
checkN(len(pairs), 1)
check(pairs, tui.AltZ+',', ",")
check(pairs, tui.Key(','), ",")
pairs = parseKeyChords(",,a,b", "")
checkN(len(pairs), 3)
check(pairs, tui.AltZ+'a', "a")
check(pairs, tui.AltZ+'b', "b")
check(pairs, tui.AltZ+',', ",")
check(pairs, tui.Key('a'), "a")
check(pairs, tui.Key('b'), "b")
check(pairs, tui.Key(','), ",")
pairs = parseKeyChords("a,b,,", "")
checkN(len(pairs), 3)
check(pairs, tui.AltZ+'a', "a")
check(pairs, tui.AltZ+'b', "b")
check(pairs, tui.AltZ+',', ",")
check(pairs, tui.Key('a'), "a")
check(pairs, tui.Key('b'), "b")
check(pairs, tui.Key(','), ",")
pairs = parseKeyChords("a,,,b", "")
checkN(len(pairs), 3)
check(pairs, tui.AltZ+'a', "a")
check(pairs, tui.AltZ+'b', "b")
check(pairs, tui.AltZ+',', ",")
check(pairs, tui.Key('a'), "a")
check(pairs, tui.Key('b'), "b")
check(pairs, tui.Key(','), ",")
pairs = parseKeyChords("a,,,b,c", "")
checkN(len(pairs), 4)
check(pairs, tui.AltZ+'a', "a")
check(pairs, tui.AltZ+'b', "b")
check(pairs, tui.AltZ+'c', "c")
check(pairs, tui.AltZ+',', ",")
check(pairs, tui.Key('a'), "a")
check(pairs, tui.Key('b'), "b")
check(pairs, tui.Key('c'), "c")
check(pairs, tui.Key(','), ",")
pairs = parseKeyChords(",,,", "")
checkN(len(pairs), 1)
check(pairs, tui.AltZ+',', ",")
check(pairs, tui.Key(','), ",")
pairs = parseKeyChords(",ALT-,,", "")
checkN(len(pairs), 1)
check(pairs, tui.AltKey(','), "ALT-,")
}
func TestBind(t *testing.T) {
keymap := defaultKeymap()
check := func(keyName int, arg1 string, types ...actionType) {
if len(keymap[keyName]) != len(types) {
t.Errorf("invalid number of actions (%d != %d)", len(types), len(keymap[keyName]))
check := func(event tui.Event, arg1 string, types ...actionType) {
if len(keymap[event]) != len(types) {
t.Errorf("invalid number of actions for %v (%d != %d)",
event, len(types), len(keymap[event]))
return
}
for idx, action := range keymap[keyName] {
for idx, action := range keymap[event] {
if types[idx] != action.t {
t.Errorf("invalid action type (%d != %d)", types[idx], action.t)
}
}
if len(arg1) > 0 && keymap[keyName][0].a != arg1 {
t.Errorf("invalid action argument: (%s != %s)", arg1, keymap[keyName][0].a)
if len(arg1) > 0 && keymap[event][0].a != arg1 {
t.Errorf("invalid action argument: (%s != %s)", arg1, keymap[event][0].a)
}
}
check(tui.CtrlA, "", actBeginningOfLine)
check(tui.CtrlA.AsEvent(), "", actBeginningOfLine)
errorString := ""
errorFn := func(e string) {
errorString = e
}
parseKeymap(keymap,
"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+
"f1:execute(ls {+})+abort+execute(echo {+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
"f1:execute(ls {+})+abort+execute(echo \n{+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
"alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+
"x:Execute(foo+bar),X:execute/bar+baz/"+
",f1:+top,f1:+top"+
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up")
check(tui.CtrlA, "", actKillLine)
check(tui.CtrlB, "", actToggleSort, actUp, actDown)
check(tui.AltZ+'c', "", actPageUp)
check(tui.AltZ+',', "", actAbort)
check(tui.AltZ+':', "", actAccept)
check(tui.AltZ, "", actPageDown)
check(tui.F1, "ls {+}", actExecute, actAbort, actExecute, actSelectAll, actTop, actTop)
check(tui.F2, "echo {}, {}, {}", actExecute)
check(tui.F3, "echo '({})'", actExecute)
check(tui.F4, "less {}", actExecute)
check(tui.AltZ+'x', "foo+bar", actExecute)
check(tui.AltZ+'X', "bar+baz", actExecute)
check(tui.AltA, "echo (,),[,],/,:,;,%,{}", actExecuteMulti)
check(tui.AltB, "echo (,),[,],/,:,@,%,{}", actExecute)
check(tui.AltZ+'+', "++\nfoobar,Y:execute(baz)+up", actExecute)
",f1:+first,f1:+top"+
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up", errorFn)
check(tui.CtrlA.AsEvent(), "", actKillLine)
check(tui.CtrlB.AsEvent(), "", actToggleSort, actUp, actDown)
check(tui.Key('c'), "", actPageUp)
check(tui.Key(','), "", actAbort)
check(tui.Key(':'), "", actAccept)
check(tui.AltKey('z'), "", actPageDown)
check(tui.F1.AsEvent(), "ls {+}", actExecute, actAbort, actExecute, actSelectAll, actFirst, actFirst)
check(tui.F2.AsEvent(), "echo {}, {}, {}", actExecute)
check(tui.F3.AsEvent(), "echo '({})'", actExecute)
check(tui.F4.AsEvent(), "less {}", actExecute)
check(tui.Key('x'), "foo+bar", actExecute)
check(tui.Key('X'), "bar+baz", actExecute)
check(tui.AltKey('a'), "echo (,),[,],/,:,;,%,{}", actExecuteMulti)
check(tui.AltKey('b'), "echo (,),[,],/,:,@,%,{}", actExecute)
check(tui.Key('+'), "++\nfoobar,Y:execute(baz)+up", actExecute)
for idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} {
parseKeymap(keymap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char))
check(tui.AltZ+int([]rune(fmt.Sprintf("%d", idx%10))[0]), "foobar", actExecute)
parseKeymap(keymap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char), errorFn)
check(tui.Key([]rune(fmt.Sprintf("%d", idx%10))[0]), "foobar", actExecute)
}
parseKeymap(keymap, "f1:abort")
check(tui.F1, "", actAbort)
parseKeymap(keymap, "f1:abort", errorFn)
check(tui.F1.AsEvent(), "", actAbort)
if len(errorString) > 0 {
t.Errorf("error parsing keymap: %s", errorString)
}
}
func TestColorSpec(t *testing.T) {
@@ -295,7 +323,7 @@ func TestColorSpec(t *testing.T) {
}
customized := parseTheme(theme, "fg:231,bg:232")
if customized.Fg != 231 || customized.Bg != 232 {
if customized.Fg.Color != 231 || customized.Bg.Color != 232 {
t.Errorf("color not customized")
}
if *tui.Dark256 == *customized {
@@ -313,24 +341,13 @@ func TestColorSpec(t *testing.T) {
}
}
func TestParseNilTheme(t *testing.T) {
var theme *tui.ColorTheme
newTheme := parseTheme(theme, "prompt:12")
if newTheme != nil {
t.Errorf("color is disabled. keep it that way.")
}
newTheme = parseTheme(theme, "prompt:12,dark,prompt:13")
if newTheme.Prompt != 13 {
t.Errorf("color should now be enabled and customized")
}
}
func TestDefaultCtrlNP(t *testing.T) {
check := func(words []string, key int, expected actionType) {
check := func(words []string, et tui.EventType, expected actionType) {
e := et.AsEvent()
opts := defaultOptions()
parseOptions(opts, words)
postProcessOptions(opts)
if opts.Keymap[key][0].t != expected {
if opts.Keymap[e][0].t != expected {
t.Error()
}
}
@@ -340,14 +357,14 @@ func TestDefaultCtrlNP(t *testing.T) {
check([]string{"--bind=ctrl-n:accept"}, tui.CtrlN, actAccept)
check([]string{"--bind=ctrl-p:accept"}, tui.CtrlP, actAccept)
f, _ := ioutil.TempFile("", "fzf-history")
f, _ := os.CreateTemp("", "fzf-history")
f.Close()
hist := "--history=" + f.Name()
check([]string{hist}, tui.CtrlN, actNextHistory)
check([]string{hist}, tui.CtrlP, actPreviousHistory)
check([]string{hist}, tui.CtrlP, actPrevHistory)
check([]string{hist, "--bind=ctrl-n:accept"}, tui.CtrlN, actAccept)
check([]string{hist, "--bind=ctrl-n:accept"}, tui.CtrlP, actPreviousHistory)
check([]string{hist, "--bind=ctrl-n:accept"}, tui.CtrlP, actPrevHistory)
check([]string{hist, "--bind=ctrl-p:accept"}, tui.CtrlN, actNextHistory)
check([]string{hist, "--bind=ctrl-p:accept"}, tui.CtrlP, actAccept)
@@ -387,23 +404,26 @@ func TestPreviewOpts(t *testing.T) {
opts.Preview.size.size == 50) {
t.Error()
}
opts = optsFor("--preview", "cat {}", "--preview-window=left:15:hidden:wrap")
opts = optsFor("--preview", "cat {}", "--preview-window=left:15,hidden,wrap:+{1}-/2")
if !(opts.Preview.command == "cat {}" &&
opts.Preview.hidden == true &&
opts.Preview.wrap == true &&
opts.Preview.position == posLeft &&
opts.Preview.scroll == "+{1}-/2" &&
opts.Preview.size.percent == false &&
opts.Preview.size.size == 15+2+2) {
opts.Preview.size.size == 15) {
t.Error(opts.Preview)
}
opts = optsFor("--preview-window=up:15:wrap:hidden", "--preview-window=down")
opts = optsFor("--preview-window=up,15,wrap,hidden,+{1}+3-1-2/2", "--preview-window=down", "--preview-window=cycle")
if !(opts.Preview.command == "" &&
opts.Preview.hidden == false &&
opts.Preview.wrap == false &&
opts.Preview.hidden == true &&
opts.Preview.wrap == true &&
opts.Preview.cycle == true &&
opts.Preview.position == posDown &&
opts.Preview.size.percent == true &&
opts.Preview.size.size == 50) {
t.Error(opts.Preview)
opts.Preview.scroll == "+{1}+3-1-2/2" &&
opts.Preview.size.percent == false &&
opts.Preview.size.size == 15) {
t.Error(opts.Preview.size.size)
}
opts = optsFor("--preview-window=up:15:wrap:hidden")
if !(opts.Preview.command == "" &&
@@ -411,7 +431,14 @@ func TestPreviewOpts(t *testing.T) {
opts.Preview.wrap == true &&
opts.Preview.position == posUp &&
opts.Preview.size.percent == false &&
opts.Preview.size.size == 15+2) {
opts.Preview.size.size == 15) {
t.Error(opts.Preview)
}
opts = optsFor("--preview=foo", "--preview-window=up", "--preview-window=default:70%")
if !(opts.Preview.command == "foo" &&
opts.Preview.position == posRight &&
opts.Preview.size.percent == true &&
opts.Preview.size.size == 70) {
t.Error(opts.Preview)
}
}
@@ -433,8 +460,6 @@ func TestValidateSign(t *testing.T) {
{"😀", true},
{"", false},
{">>>", false},
{"\n", false},
{"\t", false},
}
for _, testCase := range testCases {
@@ -448,3 +473,38 @@ func TestValidateSign(t *testing.T) {
}
}
}
func TestParseSingleActionList(t *testing.T) {
actions := parseSingleActionList("Execute@foo+bar,baz@+up+up+reload:down+down", func(string) {})
if len(actions) != 4 {
t.Errorf("Invalid number of actions parsed:%d", len(actions))
}
if actions[0].t != actExecute || actions[0].a != "foo+bar,baz" {
t.Errorf("Invalid action parsed: %v", actions[0])
}
if actions[1].t != actUp || actions[2].t != actUp {
t.Errorf("Invalid action parsed: %v / %v", actions[1], actions[2])
}
if actions[3].t != actReload || actions[3].a != "down+down" {
t.Errorf("Invalid action parsed: %v", actions[3])
}
}
func TestParseSingleActionListError(t *testing.T) {
err := ""
parseSingleActionList("change-query(foobar)baz", func(e string) {
err = e
})
if len(err) == 0 {
t.Errorf("Failed to detect error")
}
}
func TestMaskActionContents(t *testing.T) {
original := ":execute((f)(o)(o)(b)(a)(r))+change-query@qu@ry@+up,x:reload:hello:world"
expected := ":execute +change-query +up,x:reload "
masked := maskActionContents(original)
if masked != expected {
t.Errorf("Not masked: %s", masked)
}
}

View File

@@ -33,6 +33,7 @@ type term struct {
inv bool
text []rune
caseSensitive bool
normalize bool
}
// String returns the string representation of a term.
@@ -50,6 +51,7 @@ type Pattern struct {
caseSensitive bool
normalize bool
forward bool
withPos bool
text []rune
termSets []termSet
sortable bool
@@ -84,7 +86,7 @@ func clearChunkCache() {
// BuildPattern builds Pattern object from the given arguments
func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool,
cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
withPos bool, cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
var asString string
if extended {
@@ -128,6 +130,8 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
}
} else {
lowerString := strings.ToLower(asString)
normalize = normalize &&
lowerString == string(algo.NormalizeRunes([]rune(lowerString)))
caseSensitive = caseMode == CaseRespect ||
caseMode == CaseSmart && lowerString != asString
if !caseSensitive {
@@ -142,6 +146,7 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
caseSensitive: caseSensitive,
normalize: normalize,
forward: forward,
withPos: withPos,
text: []rune(asString),
termSets: termSets,
sortable: sortable,
@@ -173,6 +178,8 @@ func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet
lowerText := strings.ToLower(text)
caseSensitive := caseMode == CaseRespect ||
caseMode == CaseSmart && text != lowerText
normalizeTerm := normalize &&
lowerText == string(algo.NormalizeRunes([]rune(lowerText)))
if !caseSensitive {
text = lowerText
}
@@ -202,11 +209,10 @@ func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet
// Flip exactness
if fuzzy && !inv {
typ = termExact
text = text[1:]
} else {
typ = termFuzzy
text = text[1:]
}
text = text[1:]
} else if strings.HasPrefix(text, "^") {
if typ == termSuffix {
typ = termEqual
@@ -222,14 +228,15 @@ func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet
set = termSet{}
}
textRunes := []rune(text)
if normalize {
if normalizeTerm {
textRunes = algo.NormalizeRunes(textRunes)
}
set = append(set, term{
typ: typ,
inv: inv,
text: textRunes,
caseSensitive: caseSensitive})
caseSensitive: caseSensitive,
normalize: normalizeTerm})
switchSet = true
}
}
@@ -296,13 +303,13 @@ func (p *Pattern) matchChunk(chunk *Chunk, space []Result, slab *util.Slab) []Re
if space == nil {
for idx := 0; idx < chunk.count; idx++ {
if match, _, _ := p.MatchItem(&chunk.items[idx], false, slab); match != nil {
if match, _, _ := p.MatchItem(&chunk.items[idx], p.withPos, slab); match != nil {
matches = append(matches, *match)
}
}
} else {
for _, result := range space {
if match, _, _ := p.MatchItem(result.item, false, slab); match != nil {
if match, _, _ := p.MatchItem(result.item, p.withPos, slab); match != nil {
matches = append(matches, *match)
}
}
@@ -331,7 +338,7 @@ func (p *Pattern) MatchItem(item *Item, withPos bool, slab *util.Slab) (*Result,
func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset, int, *[]int) {
var input []Token
if len(p.nth) == 0 {
input = []Token{Token{text: &item.text, prefixLength: 0}}
input = []Token{{text: &item.text, prefixLength: 0}}
} else {
input = p.transformInput(item)
}
@@ -344,7 +351,7 @@ func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset,
func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Offset, int, *[]int) {
var input []Token
if len(p.nth) == 0 {
input = []Token{Token{text: &item.text, prefixLength: 0}}
input = []Token{{text: &item.text, prefixLength: 0}}
} else {
input = p.transformInput(item)
}
@@ -360,7 +367,7 @@ func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Of
matched := false
for _, term := range termSet {
pfun := p.procFun[term.typ]
off, score, pos := p.iter(pfun, input, term.caseSensitive, p.normalize, p.forward, term.text, withPos, slab)
off, score, pos := p.iter(pfun, input, term.caseSensitive, term.normalize, p.forward, term.text, withPos, slab)
if sidx := off[0]; sidx >= 0 {
if term.inv {
continue

View File

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

View File

@@ -1,4 +1,4 @@
// +build !openbsd
//go:build !openbsd
package protector

View File

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

View File

@@ -1,7 +1,7 @@
package fzf
import (
"bufio"
"bytes"
"context"
"io"
"os"
@@ -11,8 +11,8 @@ import (
"sync/atomic"
"time"
"github.com/charlievieth/fastwalk"
"github.com/junegunn/fzf/src/util"
"github.com/saracen/walker"
)
// Reader reads from command or standard input
@@ -76,39 +76,33 @@ func (r *Reader) fin(success bool) {
func (r *Reader) terminate() {
r.mutex.Lock()
defer func() { r.mutex.Unlock() }()
r.killed = true
if r.exec != nil && r.exec.Process != nil {
util.KillCommand(r.exec)
} else if defaultCommand != "" {
} else {
os.Stdin.Close()
}
r.mutex.Unlock()
}
func (r *Reader) restart(command string) {
func (r *Reader) restart(command string, environ []string) {
r.event = int32(EvtReady)
r.startEventPoller()
success := r.readFromCommand(nil, command)
success := r.readFromCommand(command, environ)
r.fin(success)
}
// ReadSource reads data from the default command or from standard input
func (r *Reader) ReadSource() {
func (r *Reader) ReadSource(root string, opts walkerOpts, ignores []string) {
r.startEventPoller()
var success bool
if util.IsTty() {
// The default command for *nix requires bash
shell := "bash"
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
if len(cmd) == 0 {
if defaultCommand != "" {
success = r.readFromCommand(&shell, defaultCommand)
} else {
success = r.readFiles()
}
success = r.readFiles(root, opts, ignores)
} else {
success = r.readFromCommand(nil, cmd)
// We can't export FZF_* environment variables to the default command
success = r.readFromCommand(cmd, nil)
}
} else {
success = r.readFromStdin()
@@ -117,32 +111,84 @@ func (r *Reader) ReadSource() {
}
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')
trimCR := util.IsWindows()
if r.delimNil {
delim = '\000'
trimCR = false
}
reader := bufio.NewReaderSize(src, readerBufferSize)
slab := make([]byte, readerSlabSize)
leftover := []byte{}
var err error
for {
// ReadBytes returns err != nil if and only if the returned data does not
// end in delim.
bytea, err := reader.ReadBytes(delim)
byteaLen := len(bytea)
if byteaLen > 0 {
if err == nil {
// 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))
n := 0
scope := slab[:util.Min(len(slab), readerBufferSize)]
for i := 0; i < 100; i++ {
n, err = src.Read(scope)
if n > 0 || err != nil {
break
}
}
if err != nil {
// We're not making any progress after 100 tries. Stop.
if n == 0 && err == nil {
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))
}
}
@@ -151,15 +197,28 @@ func (r *Reader) readFromStdin() bool {
return true
}
func (r *Reader) readFiles() bool {
func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool {
r.killed = false
fn := func(path string, mode os.FileInfo) error {
conf := fastwalk.Config{Follow: opts.follow}
fn := func(path string, de os.DirEntry, err error) error {
if err != nil {
return nil
}
path = filepath.Clean(path)
if path != "." {
if mode.Mode().IsDir() && filepath.Base(path)[0] == '.' {
return filepath.SkipDir
isDir := de.IsDir()
if isDir {
base := filepath.Base(path)
if !opts.hidden && base[0] == '.' {
return filepath.SkipDir
}
for _, ignore := range ignores {
if ignore == base {
return filepath.SkipDir
}
}
}
if r.pusher([]byte(path)) {
if ((opts.file && !isDir) || (opts.dir && isDir)) && r.pusher([]byte(path)) {
atomic.StoreInt32(&r.event, int32(EvtReadNew))
}
}
@@ -170,20 +229,16 @@ func (r *Reader) readFiles() bool {
}
return nil
}
cb := walker.WithErrorCallback(func(pathname string, err error) error {
return nil
})
return walker.Walk(".", fn, cb) == nil
return fastwalk.Walk(&conf, root, fn) == nil
}
func (r *Reader) readFromCommand(shell *string, command string) bool {
func (r *Reader) readFromCommand(command string, environ []string) bool {
r.mutex.Lock()
r.killed = false
r.command = &command
if shell != nil {
r.exec = util.ExecCommandWith(*shell, command, true)
} else {
r.exec = util.ExecCommand(command, true)
r.exec = util.ExecCommand(command, true)
if environ != nil {
r.exec.Env = environ
}
out, err := r.exec.StdoutPipe()
if err != nil {

View File

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

View File

@@ -15,7 +15,6 @@ type Offset [2]int32
type colorOffset struct {
offset [2]int32
color tui.ColorPair
attr tui.Attr
}
type Result struct {
@@ -50,6 +49,22 @@ func buildResult(item *Item, offsets []Offset, score int) Result {
case byScore:
// Higher is better
val = math.MaxUint16 - util.AsUint16(score)
case byChunk:
if validOffsetFound {
b := minBegin
e := maxEnd
for ; b >= 1; b-- {
if unicode.IsSpace(item.text.Get(b - 1)) {
break
}
}
for ; e < numChars; e++ {
if unicode.IsSpace(item.text.Get(e)) {
break
}
}
val = util.AsUint16(e - b)
}
case byLength:
val = item.TrimLength()
case byBegin, byEnd:
@@ -65,7 +80,7 @@ func buildResult(item *Item, offsets []Offset, score int) Result {
if criterion == byBegin {
val = util.AsUint16(minEnd - whitePrefixLen)
} else {
val = util.AsUint16(math.MaxUint16 - math.MaxUint16*(maxEnd-whitePrefixLen)/int(item.TrimLength()))
val = util.AsUint16(math.MaxUint16 - math.MaxUint16*(maxEnd-whitePrefixLen)/int(item.TrimLength()+1))
}
}
}
@@ -87,14 +102,14 @@ func minRank() Result {
return Result{item: &minItem, points: [4]uint16{math.MaxUint16, 0, 0, 0}}
}
func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme, color tui.ColorPair, attr tui.Attr, current bool) []colorOffset {
func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme, colBase tui.ColorPair, colMatch tui.ColorPair, current bool) []colorOffset {
itemColors := result.item.Colors()
// No ANSI code, or --color=no
// No ANSI codes
if len(itemColors) == 0 {
var offsets []colorOffset
for _, off := range matchOffsets {
offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: color, attr: attr})
offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: colMatch})
}
return offsets
}
@@ -111,17 +126,21 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
maxCol = ansi.offset[1]
}
}
cols := make([]int, maxCol)
cols := make([]int, maxCol)
for colorIndex, ansi := range itemColors {
for i := ansi.offset[0]; i < ansi.offset[1]; i++ {
cols[i] = colorIndex + 1 // XXX
cols[i] = colorIndex + 1 // 1-based index of itemColors
}
}
for _, off := range matchOffsets {
for i := off[0]; i < off[1]; i++ {
cols[i] = -1
// Negative of 1-based index of itemColors
// - The extra -1 means highlighted
if cols[i] >= 0 {
cols[i] = cols[i]*-1 - 1
}
}
}
@@ -133,36 +152,53 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
// --++++++++-- --++++++++++---
curr := 0
start := 0
ansiToColorPair := func(ansi ansiOffset, base tui.ColorPair) tui.ColorPair {
fg := ansi.color.fg
bg := ansi.color.bg
if fg == -1 {
if current {
fg = theme.Current.Color
} else {
fg = theme.Fg.Color
}
}
if bg == -1 {
if current {
bg = theme.DarkBg.Color
} else {
bg = theme.Bg.Color
}
}
return tui.NewColorPair(fg, bg, ansi.color.attr).MergeAttr(base)
}
var colors []colorOffset
add := func(idx int) {
if curr != 0 && idx > start {
if curr == -1 {
colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)}, color: color, attr: attr})
} else {
ansi := itemColors[curr-1]
fg := ansi.color.fg
bg := ansi.color.bg
if theme != nil {
if fg == -1 {
if current {
fg = theme.Current
} else {
fg = theme.Fg
}
}
if bg == -1 {
if current {
bg = theme.DarkBg
} else {
bg = theme.Bg
}
if curr < 0 {
color := colMatch
if curr < -1 && theme.Colored {
origColor := ansiToColorPair(itemColors[-curr-2], colMatch)
// hl or hl+ only sets the foreground color, so colMatch is the
// combination of either [hl and bg] or [hl+ and bg+].
//
// If the original text already has background color, and the
// foreground color of colMatch is -1, we shouldn't only apply the
// background color of colMatch.
// e.g. echo -e "\x1b[32;7mfoo\x1b[mbar" | fzf --ansi --color bg+:1,hl+:-1:underline
// echo -e "\x1b[42mfoo\x1b[mbar" | fzf --ansi --color bg+:1,hl+:-1:underline
if color.Fg().IsDefault() && origColor.HasBg() {
color = origColor
} else {
color = origColor.MergeNonDefault(color)
}
}
colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)}, color: color})
} else {
ansi := itemColors[curr-1]
colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)},
color: tui.NewColorPair(fg, bg),
attr: ansi.color.attr.Merge(attr)})
color: ansiToColorPair(ansi, colBase)})
}
}
}

View File

@@ -1,4 +1,4 @@
// +build !386,!amd64
//go:build !386 && !amd64
package fzf

View File

@@ -1,5 +1,3 @@
// +build !tcell
package fzf
import (
@@ -18,8 +16,8 @@ func withIndex(i *Item, index int) *Item {
func TestOffsetSort(t *testing.T) {
offsets := []Offset{
Offset{3, 5}, Offset{2, 7},
Offset{1, 3}, Offset{2, 9}}
{3, 5}, {2, 7},
{1, 3}, {2, 9}}
sort.Sort(ByOrder(offsets))
if offsets[0][0] != 1 || offsets[0][1] != 3 ||
@@ -56,9 +54,9 @@ func TestResultRank(t *testing.T) {
// FIXME global
sortCriteria = []criterion{byScore, byLength}
strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")}
str := []rune("foo")
item1 := buildResult(
withIndex(&Item{text: util.RunesToChars(strs[0])}, 1), []Offset{}, 2)
withIndex(&Item{text: util.RunesToChars(str)}, 1), []Offset{}, 2)
if item1.points[3] != math.MaxUint16-2 || // Bonus
item1.points[2] != 3 || // Length
item1.points[1] != 0 || // Unused
@@ -67,7 +65,7 @@ func TestResultRank(t *testing.T) {
t.Error(item1)
}
// Only differ in index
item2 := buildResult(&Item{text: util.RunesToChars(strs[0])}, []Offset{}, 2)
item2 := buildResult(&Item{text: util.RunesToChars(str)}, []Offset{}, 2)
items := []Result{item1, item2}
sort.Sort(ByRelevance(items))
@@ -84,13 +82,13 @@ func TestResultRank(t *testing.T) {
// Sort by relevance
item3 := buildResult(
withIndex(&Item{}, 2), []Offset{Offset{1, 3}, Offset{5, 7}}, 3)
withIndex(&Item{}, 2), []Offset{{1, 3}, {5, 7}}, 3)
item4 := buildResult(
withIndex(&Item{}, 2), []Offset{Offset{1, 2}, Offset{6, 7}}, 4)
withIndex(&Item{}, 2), []Offset{{1, 2}, {6, 7}}, 4)
item5 := buildResult(
withIndex(&Item{}, 2), []Offset{Offset{1, 3}, Offset{5, 7}}, 5)
withIndex(&Item{}, 2), []Offset{{1, 3}, {5, 7}}, 5)
item6 := buildResult(
withIndex(&Item{}, 2), []Offset{Offset{1, 2}, Offset{6, 7}}, 6)
withIndex(&Item{}, 2), []Offset{{1, 2}, {6, 7}}, 6)
items = []Result{item1, item2, item3, item4, item5, item6}
sort.Sort(ByRelevance(items))
if !(items[0] == item6 && items[1] == item5 &&
@@ -100,37 +98,77 @@ func TestResultRank(t *testing.T) {
}
}
func TestChunkTiebreak(t *testing.T) {
// FIXME global
sortCriteria = []criterion{byScore, byChunk}
score := 100
test := func(input string, offset Offset, chunk string) {
item := buildResult(withIndex(&Item{text: util.RunesToChars([]rune(input))}, 1), []Offset{offset}, score)
if !(item.points[3] == math.MaxUint16-uint16(score) && item.points[2] == uint16(len(chunk))) {
t.Error(item.points)
}
}
test("hello foobar goodbye", Offset{8, 9}, "foobar")
test("hello foobar goodbye", Offset{7, 18}, "foobar goodbye")
test("hello foobar goodbye", Offset{0, 1}, "hello")
test("hello foobar goodbye", Offset{5, 7}, "hello foobar") // TBD
}
func TestColorOffset(t *testing.T) {
// ------------ 20 ---- -- ----
// ++++++++ ++++++++++
// --++++++++-- --++++++++++---
offsets := []Offset{Offset{5, 15}, Offset{25, 35}}
offsets := []Offset{{5, 15}, {10, 12}, {25, 35}}
item := Result{
item: &Item{
colors: &[]ansiOffset{
ansiOffset{[2]int32{0, 20}, ansiState{1, 5, 0}},
ansiOffset{[2]int32{22, 27}, ansiState{2, 6, tui.Bold}},
ansiOffset{[2]int32{30, 32}, ansiState{3, 7, 0}},
ansiOffset{[2]int32{33, 40}, ansiState{4, 8, tui.Bold}}}}}
// [{[0 5] 9 false} {[5 15] 99 false} {[15 20] 9 false} {[22 25] 10 true} {[25 35] 99 false} {[35 40] 11 true}]
{[2]int32{0, 20}, ansiState{1, 5, 0, -1}},
{[2]int32{22, 27}, ansiState{2, 6, tui.Bold, -1}},
{[2]int32{30, 32}, ansiState{3, 7, 0, -1}},
{[2]int32{33, 40}, ansiState{4, 8, tui.Bold, -1}}}}}
pair := tui.NewColorPair(99, 199)
colors := item.colorOffsets(offsets, tui.Dark256, pair, tui.AttrRegular, true)
assert := func(idx int, b int32, e int32, c tui.ColorPair, bold bool) {
var attr tui.Attr
if bold {
attr = tui.Bold
}
colBase := tui.NewColorPair(89, 189, tui.AttrUndefined)
colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined)
colors := item.colorOffsets(offsets, tui.Dark256, colBase, colMatch, true)
assert := func(idx int, b int32, e int32, c tui.ColorPair) {
o := colors[idx]
if o.offset[0] != b || o.offset[1] != e || o.color != c || o.attr != attr {
t.Error(o)
if o.offset[0] != b || o.offset[1] != e || o.color != c {
t.Error(o, b, e, c)
}
}
assert(0, 0, 5, tui.NewColorPair(1, 5), false)
assert(1, 5, 15, pair, false)
assert(2, 15, 20, tui.NewColorPair(1, 5), false)
assert(3, 22, 25, tui.NewColorPair(2, 6), true)
assert(4, 25, 35, pair, false)
assert(5, 35, 40, tui.NewColorPair(4, 8), true)
// [{[0 5] {1 5 0}} {[5 15] {99 199 0}} {[15 20] {1 5 0}}
// {[22 25] {2 6 1}} {[25 27] {99 199 1}} {[27 30] {99 199 0}}
// {[30 32] {99 199 0}} {[32 33] {99 199 0}} {[33 35] {99 199 1}}
// {[35 40] {4 8 1}}]
assert(0, 0, 5, tui.NewColorPair(1, 5, tui.AttrUndefined))
assert(1, 5, 15, colMatch)
assert(2, 15, 20, tui.NewColorPair(1, 5, tui.AttrUndefined))
assert(3, 22, 25, tui.NewColorPair(2, 6, tui.Bold))
assert(4, 25, 27, colMatch.WithAttr(tui.Bold))
assert(5, 27, 30, colMatch)
assert(6, 30, 32, colMatch)
assert(7, 32, 33, colMatch) // TODO: Should we merge consecutive blocks?
assert(8, 33, 35, colMatch.WithAttr(tui.Bold))
assert(9, 35, 40, tui.NewColorPair(4, 8, tui.Bold))
colRegular := tui.NewColorPair(-1, -1, tui.AttrUndefined)
colUnderline := tui.NewColorPair(-1, -1, tui.Underline)
colors = item.colorOffsets(offsets, tui.Dark256, colRegular, colUnderline, true)
// [{[0 5] {1 5 0}} {[5 15] {1 5 8}} {[15 20] {1 5 0}}
// {[22 25] {2 6 1}} {[25 27] {2 6 9}} {[27 30] {-1 -1 8}}
// {[30 32] {3 7 8}} {[32 33] {-1 -1 8}} {[33 35] {4 8 9}}
// {[35 40] {4 8 1}}]
assert(0, 0, 5, tui.NewColorPair(1, 5, tui.AttrUndefined))
assert(1, 5, 15, tui.NewColorPair(1, 5, tui.Underline))
assert(2, 15, 20, tui.NewColorPair(1, 5, tui.AttrUndefined))
assert(3, 22, 25, tui.NewColorPair(2, 6, tui.Bold))
assert(4, 25, 27, tui.NewColorPair(2, 6, tui.Bold|tui.Underline))
assert(5, 27, 30, colUnderline)
assert(6, 30, 32, tui.NewColorPair(3, 7, tui.Underline))
assert(7, 32, 33, colUnderline)
assert(8, 33, 35, tui.NewColorPair(4, 8, tui.Bold|tui.Underline))
assert(9, 35, 40, tui.NewColorPair(4, 8, tui.Bold))
}

View File

@@ -1,4 +1,4 @@
// +build 386 amd64
//go:build 386 || amd64
package fzf

257
src/server.go Normal file
View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +1,29 @@
package fzf
import (
"bytes"
"io"
"os"
"regexp"
"strings"
"testing"
"text/template"
"github.com/junegunn/fzf/src/util"
)
func newItem(str string) *Item {
bytes := []byte(str)
trimmed, _, _ := extractColor(str, nil, nil)
return &Item{origText: &bytes, text: util.ToChars([]byte(trimmed))}
func replacePlaceholderTest(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
return replacePlaceholder(replacePlaceholderParams{
template: template,
stripAnsi: stripAnsi,
delimiter: delimiter,
printsep: printsep,
forcePlus: forcePlus,
query: query,
allItems: allItems,
lastAction: actBackwardDeleteCharEof,
prompt: "prompt",
})
}
func TestReplacePlaceholder(t *testing.T) {
@@ -30,110 +43,614 @@ func TestReplacePlaceholder(t *testing.T) {
t.Errorf("expected: %s, actual: %s", expected, result)
}
}
// helper function that converts template format into string and carries out the check()
checkFormat := func(format string) {
type quotes struct{ O, I, S string } // outer, inner quotes, print separator
unixStyle := quotes{`'`, `'\''`, "\n"}
windowsStyle := quotes{`^"`, `'`, "\n"}
var effectiveStyle quotes
if util.IsWindows() {
effectiveStyle = windowsStyle
} else {
effectiveStyle = unixStyle
}
expected := templateToString(format, effectiveStyle)
check(expected)
}
printsep := "\n"
/*
Test multiple placeholders and the function parameters.
*/
// {}, preserve ansi
result = replacePlaceholder("echo {}", false, Delimiter{}, printsep, false, "query", items1)
check("echo ' foo'\\''bar \x1b[31mbaz\x1b[m'")
result = replacePlaceholderTest("echo {}", false, Delimiter{}, printsep, false, "query", items1)
checkFormat("echo {{.O}} foo{{.I}}bar \x1b[31mbaz\x1b[m{{.O}}")
// {}, strip ansi
result = replacePlaceholder("echo {}", true, Delimiter{}, printsep, false, "query", items1)
check("echo ' foo'\\''bar baz'")
result = replacePlaceholderTest("echo {}", true, Delimiter{}, printsep, false, "query", items1)
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}")
// {}, with multiple items
result = replacePlaceholder("echo {}", true, Delimiter{}, printsep, false, "query", items2)
check("echo 'foo'\\''bar baz'")
result = replacePlaceholderTest("echo {}", true, Delimiter{}, printsep, false, "query", items2)
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}}")
// {..}, strip leading whitespaces, preserve ansi
result = replacePlaceholder("echo {..}", false, Delimiter{}, printsep, false, "query", items1)
check("echo 'foo'\\''bar \x1b[31mbaz\x1b[m'")
result = replacePlaceholderTest("echo {..}", false, Delimiter{}, printsep, false, "query", items1)
checkFormat("echo {{.O}}foo{{.I}}bar \x1b[31mbaz\x1b[m{{.O}}")
// {..}, strip leading whitespaces, strip ansi
result = replacePlaceholder("echo {..}", true, Delimiter{}, printsep, false, "query", items1)
check("echo 'foo'\\''bar baz'")
result = replacePlaceholderTest("echo {..}", true, Delimiter{}, printsep, false, "query", items1)
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}}")
// {q}
result = replacePlaceholder("echo {} {q}", true, Delimiter{}, printsep, false, "query", items1)
check("echo ' foo'\\''bar baz' 'query'")
result = replacePlaceholderTest("echo {} {q}", true, Delimiter{}, printsep, false, "query", items1)
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}} {{.O}}query{{.O}}")
// {q}, multiple items
result = replacePlaceholder("echo {+}{q}{+}", true, Delimiter{}, printsep, false, "query 'string'", items2)
check("echo 'foo'\\''bar baz' 'FOO'\\''BAR BAZ''query '\\''string'\\''''foo'\\''bar baz' 'FOO'\\''BAR BAZ'")
result = replacePlaceholderTest("echo {+}{q}{+}", true, Delimiter{}, printsep, false, "query 'string'", items2)
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}{{.O}}query {{.I}}string{{.I}}{{.O}}{{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}")
result = replacePlaceholder("echo {}{q}{}", true, Delimiter{}, printsep, false, "query 'string'", items2)
check("echo 'foo'\\''bar baz''query '\\''string'\\''''foo'\\''bar baz'")
result = replacePlaceholderTest("echo {}{q}{}", true, Delimiter{}, printsep, false, "query 'string'", items2)
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}}{{.O}}query {{.I}}string{{.I}}{{.O}}{{.O}}foo{{.I}}bar baz{{.O}}")
result = replacePlaceholder("echo {1}/{2}/{2,1}/{-1}/{-2}/{}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, false, "query", items1)
check("echo 'foo'\\''bar'/'baz'/'bazfoo'\\''bar'/'baz'/'foo'\\''bar'/' foo'\\''bar baz'/'foo'\\''bar baz'/{n.t}/{}/{1}/{q}/''")
result = replacePlaceholderTest("echo {1}/{2}/{2,1}/{-1}/{-2}/{}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, false, "query", items1)
checkFormat("echo {{.O}}foo{{.I}}bar{{.O}}/{{.O}}baz{{.O}}/{{.O}}bazfoo{{.I}}bar{{.O}}/{{.O}}baz{{.O}}/{{.O}}foo{{.I}}bar{{.O}}/{{.O}} foo{{.I}}bar baz{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}}")
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, false, "query", items2)
check("echo 'foo'\\''bar'/'baz'/'baz'/'foo'\\''bar'/'foo'\\''bar baz'/{n.t}/{}/{1}/{q}/''")
result = replacePlaceholderTest("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, false, "query", items2)
checkFormat("echo {{.O}}foo{{.I}}bar{{.O}}/{{.O}}baz{{.O}}/{{.O}}baz{{.O}}/{{.O}}foo{{.I}}bar{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}}")
result = replacePlaceholder("echo {+1}/{+2}/{+-1}/{+-2}/{+..}/{n.t}/\\{}/\\{1}/\\{q}/{+3}", true, Delimiter{}, printsep, false, "query", items2)
check("echo 'foo'\\''bar' 'FOO'\\''BAR'/'baz' 'BAZ'/'baz' 'BAZ'/'foo'\\''bar' 'FOO'\\''BAR'/'foo'\\''bar baz' 'FOO'\\''BAR BAZ'/{n.t}/{}/{1}/{q}/'' ''")
result = replacePlaceholderTest("echo {+1}/{+2}/{+-1}/{+-2}/{+..}/{n.t}/\\{}/\\{1}/\\{q}/{+3}", true, Delimiter{}, printsep, false, "query", items2)
checkFormat("echo {{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}} {{.O}}{{.O}}")
// forcePlus
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, true, "query", items2)
check("echo 'foo'\\''bar' 'FOO'\\''BAR'/'baz' 'BAZ'/'baz' 'BAZ'/'foo'\\''bar' 'FOO'\\''BAR'/'foo'\\''bar baz' 'FOO'\\''BAR BAZ'/{n.t}/{}/{1}/{q}/'' ''")
result = replacePlaceholderTest("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, true, "query", items2)
checkFormat("echo {{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}} {{.O}}{{.O}}")
// Whitespace preserving flag with "'" delimiter
result = replacePlaceholder("echo {s1}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
check("echo ' foo'")
result = replacePlaceholderTest("echo {s1}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
checkFormat("echo {{.O}} foo{{.O}}")
result = replacePlaceholder("echo {s2}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
check("echo 'bar baz'")
result = replacePlaceholderTest("echo {s2}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
checkFormat("echo {{.O}}bar baz{{.O}}")
result = replacePlaceholder("echo {s}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
check("echo ' foo'\\''bar baz'")
result = replacePlaceholderTest("echo {s}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}")
result = replacePlaceholder("echo {s..}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
check("echo ' foo'\\''bar baz'")
result = replacePlaceholderTest("echo {s..}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}")
// Whitespace preserving flag with regex delimiter
regex = regexp.MustCompile(`\w+`)
result = replacePlaceholder("echo {s1}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
check("echo ' '")
result = replacePlaceholderTest("echo {s1}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
checkFormat("echo {{.O}} {{.O}}")
result = replacePlaceholder("echo {s2}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
check("echo ''\\'''")
result = replacePlaceholderTest("echo {s2}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
checkFormat("echo {{.O}}{{.I}}{{.O}}")
result = replacePlaceholder("echo {s3}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
check("echo ' '")
result = replacePlaceholderTest("echo {s3}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
checkFormat("echo {{.O}} {{.O}}")
// No match
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, nil})
result = replacePlaceholderTest("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, nil})
check("echo /")
// No match, but with selections
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, item1})
check("echo /' foo'\\''bar baz'")
result = replacePlaceholderTest("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, item1})
checkFormat("echo /{{.O}} foo{{.I}}bar baz{{.O}}")
// String delimiter
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
check("echo ' foo'\\''bar baz'/'foo'/'bar baz'")
result = replacePlaceholderTest("echo {}/{1}/{2}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}/{{.O}}foo{{.O}}/{{.O}}bar baz{{.O}}")
// Regex delimiter
regex = regexp.MustCompile("[oa]+")
// foo'bar baz
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
check("echo ' foo'\\''bar baz'/'f'/'r b'/''\\''bar b'")
result = replacePlaceholderTest("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}/{{.O}}f{{.O}}/{{.O}}r b{{.O}}/{{.O}}{{.I}}bar b{{.O}}")
/*
Test single placeholders, but focus on the placeholders' parameters (e.g. flags).
see: TestParsePlaceholder
*/
items3 := []*Item{
// single line
newItem("1a 1b 1c 1d 1e 1f"),
// multi line
newItem("1a 1b 1c 1d 1e 1f"),
newItem("2a 2b 2c 2d 2e 2f"),
newItem("3a 3b 3c 3d 3e 3f"),
newItem("4a 4b 4c 4d 4e 4f"),
newItem("5a 5b 5c 5d 5e 5f"),
newItem("6a 6b 6c 6d 6e 6f"),
newItem("7a 7b 7c 7d 7e 7f"),
}
stripAnsi := false
forcePlus := false
query := "sample query"
templateToOutput := make(map[string]string)
templateToFile := make(map[string]string) // same as above, but the file contents will be matched
// I. item type placeholder
templateToOutput[`{}`] = `{{.O}}1a 1b 1c 1d 1e 1f{{.O}}`
templateToOutput[`{+}`] = `{{.O}}1a 1b 1c 1d 1e 1f{{.O}} {{.O}}2a 2b 2c 2d 2e 2f{{.O}} {{.O}}3a 3b 3c 3d 3e 3f{{.O}} {{.O}}4a 4b 4c 4d 4e 4f{{.O}} {{.O}}5a 5b 5c 5d 5e 5f{{.O}} {{.O}}6a 6b 6c 6d 6e 6f{{.O}} {{.O}}7a 7b 7c 7d 7e 7f{{.O}}`
templateToOutput[`{n}`] = `0`
templateToOutput[`{+n}`] = `0 0 0 0 0 0 0`
templateToFile[`{f}`] = `1a 1b 1c 1d 1e 1f{{.S}}`
templateToFile[`{+f}`] = `1a 1b 1c 1d 1e 1f{{.S}}2a 2b 2c 2d 2e 2f{{.S}}3a 3b 3c 3d 3e 3f{{.S}}4a 4b 4c 4d 4e 4f{{.S}}5a 5b 5c 5d 5e 5f{{.S}}6a 6b 6c 6d 6e 6f{{.S}}7a 7b 7c 7d 7e 7f{{.S}}`
templateToFile[`{nf}`] = `0{{.S}}`
templateToFile[`{+nf}`] = `0{{.S}}0{{.S}}0{{.S}}0{{.S}}0{{.S}}0{{.S}}0{{.S}}`
// II. token type placeholders
templateToOutput[`{..}`] = templateToOutput[`{}`]
templateToOutput[`{1..}`] = templateToOutput[`{}`]
templateToOutput[`{..2}`] = `{{.O}}1a 1b{{.O}}`
templateToOutput[`{1..2}`] = templateToOutput[`{..2}`]
templateToOutput[`{-2..-1}`] = `{{.O}}1e 1f{{.O}}`
// shorthand for x..x range
templateToOutput[`{1}`] = `{{.O}}1a{{.O}}`
templateToOutput[`{1..1}`] = templateToOutput[`{1}`]
templateToOutput[`{-6}`] = templateToOutput[`{1}`]
// multiple ranges
templateToOutput[`{1,2}`] = templateToOutput[`{1..2}`]
templateToOutput[`{1,2,4}`] = `{{.O}}1a 1b 1d{{.O}}`
templateToOutput[`{1,2..4}`] = `{{.O}}1a 1b 1c 1d{{.O}}`
templateToOutput[`{1..2,-4..-3}`] = `{{.O}}1a 1b 1c 1d{{.O}}`
// flags
templateToOutput[`{+1}`] = `{{.O}}1a{{.O}} {{.O}}2a{{.O}} {{.O}}3a{{.O}} {{.O}}4a{{.O}} {{.O}}5a{{.O}} {{.O}}6a{{.O}} {{.O}}7a{{.O}}`
templateToOutput[`{+-1}`] = `{{.O}}1f{{.O}} {{.O}}2f{{.O}} {{.O}}3f{{.O}} {{.O}}4f{{.O}} {{.O}}5f{{.O}} {{.O}}6f{{.O}} {{.O}}7f{{.O}}`
templateToOutput[`{s1}`] = `{{.O}}1a {{.O}}`
templateToFile[`{f1}`] = `1a{{.S}}`
templateToOutput[`{+s1..2}`] = `{{.O}}1a 1b {{.O}} {{.O}}2a 2b {{.O}} {{.O}}3a 3b {{.O}} {{.O}}4a 4b {{.O}} {{.O}}5a 5b {{.O}} {{.O}}6a 6b {{.O}} {{.O}}7a 7b {{.O}}`
templateToFile[`{+sf1..2}`] = `1a 1b {{.S}}2a 2b {{.S}}3a 3b {{.S}}4a 4b {{.S}}5a 5b {{.S}}6a 6b {{.S}}7a 7b {{.S}}`
// III. query type placeholder
// query flag is not removed after parsing, so it gets doubled
// while the double q is invalid, it is useful here for testing purposes
templateToOutput[`{q}`] = "{{.O}}" + query + "{{.O}}"
templateToOutput[`{fzf:query}`] = "{{.O}}" + query + "{{.O}}"
templateToOutput[`{fzf:action} {fzf:prompt}`] = "backward-delete-char-eof 'prompt'"
// IV. escaping placeholder
templateToOutput[`\{}`] = `{}`
templateToOutput[`\{q}`] = `{q}`
templateToOutput[`\{fzf:query}`] = `{fzf:query}`
templateToOutput[`\{fzf:action}`] = `{fzf:action}`
templateToOutput[`\{++}`] = `{++}`
templateToOutput[`{++}`] = templateToOutput[`{+}`]
for giveTemplate, wantOutput := range templateToOutput {
result = replacePlaceholderTest(giveTemplate, stripAnsi, Delimiter{}, printsep, forcePlus, query, items3)
checkFormat(wantOutput)
}
for giveTemplate, wantOutput := range templateToFile {
path := replacePlaceholderTest(giveTemplate, stripAnsi, Delimiter{}, printsep, forcePlus, query, items3)
data, err := readFile(path)
if err != nil {
t.Errorf("Cannot read the content of the temp file %s.", path)
}
result = string(data)
checkFormat(wantOutput)
}
}
func TestQuoteEntryCmd(t *testing.T) {
func TestQuoteEntry(t *testing.T) {
type quotes struct{ E, O, SQ, DQ, BS string } // standalone escape, outer, single and double quotes, backslash
unixStyle := quotes{``, `'`, `'\''`, `"`, `\`}
windowsStyle := quotes{`^`, `^"`, `'`, `\^"`, `\\`}
var effectiveStyle quotes
if util.IsWindows() {
effectiveStyle = windowsStyle
} else {
effectiveStyle = unixStyle
}
tests := map[string]string{
`"`: `^"\^"^"`,
`\`: `^"\\^"`,
`\"`: `^"\\\^"^"`,
`"\\\"`: `^"\^"\\\\\\\^"^"`,
`&|<>()@^%!`: `^"^&^|^<^>^(^)^@^^^%^!^"`,
`%USERPROFILE%`: `^"^%USERPROFILE^%^"`,
`C:\Program Files (x86)\`: `^"C:\\Program Files ^(x86^)\\^"`,
`'`: `{{.O}}{{.SQ}}{{.O}}`,
`"`: `{{.O}}{{.DQ}}{{.O}}`,
`\`: `{{.O}}{{.BS}}{{.O}}`,
`\"`: `{{.O}}{{.BS}}{{.DQ}}{{.O}}`,
`"\\\"`: `{{.O}}{{.DQ}}{{.BS}}{{.BS}}{{.BS}}{{.DQ}}{{.O}}`,
`$`: `{{.O}}${{.O}}`,
`$HOME`: `{{.O}}$HOME{{.O}}`,
`'$HOME'`: `{{.O}}{{.SQ}}$HOME{{.SQ}}{{.O}}`,
`&`: `{{.O}}{{.E}}&{{.O}}`,
`|`: `{{.O}}{{.E}}|{{.O}}`,
`<`: `{{.O}}{{.E}}<{{.O}}`,
`>`: `{{.O}}{{.E}}>{{.O}}`,
`(`: `{{.O}}{{.E}}({{.O}}`,
`)`: `{{.O}}{{.E}}){{.O}}`,
`@`: `{{.O}}{{.E}}@{{.O}}`,
`^`: `{{.O}}{{.E}}^{{.O}}`,
`%`: `{{.O}}{{.E}}%{{.O}}`,
`!`: `{{.O}}{{.E}}!{{.O}}`,
`%USERPROFILE%`: `{{.O}}{{.E}}%USERPROFILE{{.E}}%{{.O}}`,
`C:\Program Files (x86)\`: `{{.O}}C:{{.BS}}Program Files {{.E}}(x86{{.E}}){{.BS}}{{.O}}`,
`"C:\Program Files"`: `{{.O}}{{.DQ}}C:{{.BS}}Program Files{{.DQ}}{{.O}}`,
}
for input, expected := range tests {
escaped := quoteEntryCmd(input)
escaped := quoteEntry(input)
expected = templateToString(expected, effectiveStyle)
if escaped != expected {
t.Errorf("Input: %s, expected: %s, actual %s", input, expected, escaped)
}
}
}
// purpose of this test is to demonstrate some shortcomings of fzf's templating system on Unix
func TestUnixCommands(t *testing.T) {
if util.IsWindows() {
t.SkipNow()
}
tests := []testCase{
// reference: give{template, query, items}, want{output OR match}
// 1) working examples
// paths that does not have to evaluated will work fine, when quoted
{give{`grep foo {}`, ``, newItems(`test`)}, want{output: `grep foo 'test'`}},
{give{`grep foo {}`, ``, newItems(`/home/user/test`)}, want{output: `grep foo '/home/user/test'`}},
{give{`grep foo {}`, ``, newItems(`./test`)}, want{output: `grep foo './test'`}},
// only placeholders are escaped as data, this will lookup tilde character in a test file in your home directory
// quoting the tilde is required (to be treated as string)
{give{`grep {} ~/test`, ``, newItems(`~`)}, want{output: `grep '~' ~/test`}},
// 2) problematic examples
// (not necessarily unexpected)
// paths that need to expand some part of it won't work (special characters and variables)
{give{`cat {}`, ``, newItems(`~/test`)}, want{output: `cat '~/test'`}},
{give{`cat {}`, ``, newItems(`$HOME/test`)}, want{output: `cat '$HOME/test'`}},
}
testCommands(t, tests)
}
// purpose of this test is to demonstrate some shortcomings of fzf's templating system on Windows
func TestWindowsCommands(t *testing.T) {
if !util.IsWindows() {
t.SkipNow()
}
tests := []testCase{
// reference: give{template, query, items}, want{output OR match}
// 1) working examples
// example of redundantly escaped backslash in the output, besides looking bit ugly, it won't cause any issue
{give{`type {}`, ``, newItems(`C:\test.txt`)}, want{output: `type ^"C:\\test.txt^"`}},
{give{`rg -- "package" {}`, ``, newItems(`.\test.go`)}, want{output: `rg -- "package" ^".\\test.go^"`}},
// example of mandatorily escaped backslash in the output, otherwise `rg -- "C:\test.txt"` is matching for tabulator
{give{`rg -- {}`, ``, newItems(`C:\test.txt`)}, want{output: `rg -- ^"C:\\test.txt^"`}},
// example of mandatorily escaped double quote in the output, otherwise `rg -- ""C:\\test.txt""` is not matching for the double quotes around the path
{give{`rg -- {}`, ``, newItems(`"C:\test.txt"`)}, want{output: `rg -- ^"\^"C:\\test.txt\^"^"`}},
// 2) problematic examples
// (not necessarily unexpected)
// notepad++'s parser can't handle `-n"12"` generate by fzf, expects `-n12`
{give{`notepad++ -n{1} {2}`, ``, newItems(`12 C:\Work\Test Folder\File.txt`)}, want{output: `notepad++ -n^"12^" ^"C:\\Work\\Test Folder\\File.txt^"`}},
// cat is parsing `\"` as a part of the file path, double quote is illegal character for paths on Windows
// cat: "C:\\test.txt: Invalid argument
{give{`cat {}`, ``, newItems(`"C:\test.txt"`)}, want{output: `cat ^"\^"C:\\test.txt\^"^"`}},
// cat: "C:\\test.txt": Invalid argument
{give{`cmd /c {}`, ``, newItems(`cat "C:\test.txt"`)}, want{output: `cmd /c ^"cat \^"C:\\test.txt\^"^"`}},
// the "file" flag in the pattern won't create *.bat or *.cmd file so the command in the output tries to edit the file, instead of executing it
// the temp file contains: `cat "C:\test.txt"`
// TODO this should actually work
{give{`cmd /c {f}`, ``, newItems(`cat "C:\test.txt"`)}, want{match: `^cmd /c .*\fzf-preview-[0-9]{9}$`}},
}
testCommands(t, tests)
}
// purpose of this test is to demonstrate some shortcomings of fzf's templating system on Windows in Powershell
func TestPowershellCommands(t *testing.T) {
if !util.IsWindows() {
t.SkipNow()
}
tests := []testCase{
// reference: give{template, query, items}, want{output OR match}
/*
You can read each line in the following table as a pipeline that
consist of series of parsers that act upon your input (col. 1) and
each cell represents the output value.
For example:
- exec.Command("program.exe", `\''`)
- goes to win32 api which will process it transparently as it contains no special characters, see [CommandLineToArgvW][].
- powershell command will receive it as is, that is two arguments: a literal backslash and empty string in single quotes
- native command run via/from powershell will receive only one argument: a literal backslash. Because extra parsing rules apply, see [NativeCallsFromPowershell][].
- some¹ apps have internal parser, that requires one more level of escaping (yes, this is completely application-specific, but see terminal_test.go#TestWindowsCommands)
Character⁰ CommandLineToArgvW Powershell commands Native commands from Powershell Apps requiring escapes¹ | Being tested below
---------- ------------------ ------------------------------ ------------------------------- -------------------------- | ------------------
" empty string² missing argument error ... ... |
\" literal " unbalanced quote error ... ... |
'\"' literal '"' literal " empty string empty string (match all) | yes
'\\\"' literal '\"' literal \" literal " literal " |
---------- ------------------ ------------------------------ ------------------------------- -------------------------- | ------------------
\ transparent transparent transparent regex error |
'\' transparent literal \ literal \ regex error | yes
\\ transparent transparent transparent literal \ |
'\\' transparent literal \\ literal \\ literal \ |
---------- ------------------ ------------------------------ ------------------------------- -------------------------- | ------------------
' transparent unbalanced quote error ... ... |
\' transparent literal \ and unb. quote error ... ... |
\'' transparent literal \ and empty string literal \ regex error | no, but given as example above
''' transparent unbalanced quote error ... ... |
'''' transparent literal ' literal ' literal ' | yes
---------- ------------------ ------------------------------ ------------------------------- -------------------------- | ------------------
⁰: charatecter or characters 'x' as an argument to a program in go's call: exec.Command("program.exe", `x`)
¹: native commands like grep, git grep, ripgrep
²: interpreted as a grouping quote, affects argument parser and gets removed from the result
[CommandLineToArgvW]: https://docs.microsoft.com/en-gb/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw#remarks
[NativeCallsFromPowershell]: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_parsing?view=powershell-7.1#passing-arguments-that-contain-quote-characters
*/
// 1) working examples
{give{`Get-Content {}`, ``, newItems(`C:\test.txt`)}, want{output: `Get-Content 'C:\test.txt'`}},
{give{`rg -- "package" {}`, ``, newItems(`.\test.go`)}, want{output: `rg -- "package" '.\test.go'`}},
// example of escaping single quotes
{give{`rg -- {}`, ``, newItems(`'foobar'`)}, want{output: `rg -- '''foobar'''`}},
// chaining powershells
{give{`powershell -NoProfile -Command {}`, ``, newItems(`cat "C:\test.txt"`)}, want{output: `powershell -NoProfile -Command 'cat \"C:\test.txt\"'`}},
// 2) problematic examples
// (not necessarily unexpected)
// looking for a path string will only work with escaped backslashes
{give{`rg -- {}`, ``, newItems(`C:\test.txt`)}, want{output: `rg -- 'C:\test.txt'`}},
// looking for a literal double quote will only work with triple escaped double quotes
{give{`rg -- {}`, ``, newItems(`"C:\test.txt"`)}, want{output: `rg -- '\"C:\test.txt\"'`}},
// Get-Content (i.e. cat alias) is parsing `"` as a part of the file path, returns an error:
// Get-Content : Cannot find drive. A drive with the name '"C:' does not exist.
{give{`cat {}`, ``, newItems(`"C:\test.txt"`)}, want{output: `cat '\"C:\test.txt\"'`}},
// the "file" flag in the pattern won't create *.ps1 file so the powershell will offload this "unknown" filetype
// to explorer, which will prompt user to pick editing program for the fzf-preview file
// the temp file contains: `cat "C:\test.txt"`
// TODO this should actually work
{give{`powershell -NoProfile -Command {f}`, ``, newItems(`cat "C:\test.txt"`)}, want{match: `^powershell -NoProfile -Command .*\fzf-preview-[0-9]{9}$`}},
}
// to force powershell-style escaping we temporarily set environment variable that fzf honors
shellBackup := os.Getenv("SHELL")
os.Setenv("SHELL", "powershell")
testCommands(t, tests)
os.Setenv("SHELL", shellBackup)
}
/*
Test typical valid placeholders and parsing of them.
Also since the parser assumes the input is matched with `placeholder` regex,
the regex is tested here as well.
*/
func TestParsePlaceholder(t *testing.T) {
// give, want pairs
templates := map[string]string{
// I. item type placeholder
`{}`: `{}`,
`{+}`: `{+}`,
`{n}`: `{n}`,
`{+n}`: `{+n}`,
`{f}`: `{f}`,
`{+nf}`: `{+nf}`,
// II. token type placeholders
`{..}`: `{..}`,
`{1..}`: `{1..}`,
`{..2}`: `{..2}`,
`{1..2}`: `{1..2}`,
`{-2..-1}`: `{-2..-1}`,
// shorthand for x..x range
`{1}`: `{1}`,
`{1..1}`: `{1..1}`,
`{-6}`: `{-6}`,
// multiple ranges
`{1,2}`: `{1,2}`,
`{1,2,4}`: `{1,2,4}`,
`{1,2..4}`: `{1,2..4}`,
`{1..2,-4..-3}`: `{1..2,-4..-3}`,
// flags
`{+1}`: `{+1}`,
`{+-1}`: `{+-1}`,
`{s1}`: `{s1}`,
`{f1}`: `{f1}`,
`{+s1..2}`: `{+s1..2}`,
`{+sf1..2}`: `{+sf1..2}`,
// III. query type placeholder
// query flag is not removed after parsing, so it gets doubled
// while the double q is invalid, it is useful here for testing purposes
`{q}`: `{qq}`,
// IV. escaping placeholder
`\{}`: `{}`,
`\{++}`: `{++}`,
`{++}`: `{+}`,
}
for giveTemplate, wantTemplate := range templates {
if !placeholder.MatchString(giveTemplate) {
t.Errorf(`given placeholder %s does not match placeholder regex, so attempt to parse it is unexpected`, giveTemplate)
continue
}
_, placeholderWithoutFlags, flags := parsePlaceholder(giveTemplate)
gotTemplate := placeholderWithoutFlags[:1] + flags.encodePlaceholder() + placeholderWithoutFlags[1:]
if gotTemplate != wantTemplate {
t.Errorf(`parsed placeholder "%s" into "%s", but want "%s"`, giveTemplate, gotTemplate, wantTemplate)
}
}
}
/* utilities section */
// Item represents one line in fzf UI. Usually it is relative path to files and folders.
func newItem(str string) *Item {
bytes := []byte(str)
trimmed, _, _ := extractColor(str, nil, nil)
return &Item{origText: &bytes, text: util.ToChars([]byte(trimmed))}
}
// Functions tested in this file require array of items (allItems). The array needs
// to consist of at least two nils. This is helper function.
func newItems(str ...string) []*Item {
result := make([]*Item, util.Max(len(str), 2))
for i, s := range str {
result[i] = newItem(s)
}
return result
}
// (for logging purposes)
func (item *Item) String() string {
return item.AsString(true)
}
// Helper function to parse, execute and convert "text/template" to string. Panics on error.
func templateToString(format string, data interface{}) string {
bb := &bytes.Buffer{}
err := template.Must(template.New("").Parse(format)).Execute(bb, data)
if err != nil {
panic(err)
}
return bb.String()
}
// ad hoc types for test cases
type give struct {
template string
query string
allItems []*Item
}
type want struct {
/*
Unix:
The `want.output` string is supposed to be formatted for evaluation by
`sh -c command` system call.
Windows:
The `want.output` string is supposed to be formatted for evaluation by
`cmd.exe /s /c "command"` system call. The `/s` switch enables so called old
behaviour, which is more favourable for nesting (possibly escaped)
special characters. This is the relevant section of `help cmd`:
...old behavior is to see if the first character is
a quote character and if so, strip the leading character and
remove the last quote character on the command line, preserving
any text after the last quote character.
*/
output string // literal output
match string // output is matched against this regex (when output is empty string)
}
type testCase struct {
give
want
}
func testCommands(t *testing.T, tests []testCase) {
// common test parameters
delim := "\t"
delimiter := Delimiter{str: &delim}
printsep := ""
stripAnsi := false
forcePlus := false
// evaluate the test cases
for idx, test := range tests {
gotOutput := replacePlaceholderTest(
test.give.template, stripAnsi, delimiter, printsep, forcePlus,
test.give.query,
test.give.allItems)
switch {
case test.want.output != "":
if gotOutput != test.want.output {
t.Errorf("tests[%v]:\ngave{\n\ttemplate: '%s',\n\tquery: '%s',\n\tallItems: %s}\nand got '%s',\nbut want '%s'",
idx,
test.give.template, test.give.query, test.give.allItems,
gotOutput, test.want.output)
}
case test.want.match != "":
wantMatch := strings.ReplaceAll(test.want.match, `\`, `\\`)
wantRegex := regexp.MustCompile(wantMatch)
if !wantRegex.MatchString(gotOutput) {
t.Errorf("tests[%v]:\ngave{\n\ttemplate: '%s',\n\tquery: '%s',\n\tallItems: %s}\nand got '%s',\nbut want '%s'",
idx,
test.give.template, test.give.query, test.give.allItems,
gotOutput, test.want.match)
}
default:
t.Errorf("tests[%v]: test case does not describe 'want' property", idx)
}
}
}
// naive encoder of placeholder flags
func (flags placeholderFlags) encodePlaceholder() string {
encoded := ""
if flags.plus {
encoded += "+"
}
if flags.preserveSpace {
encoded += "s"
}
if flags.number {
encoded += "n"
}
if flags.file {
encoded += "f"
}
if flags.forceUpdate { // FIXME
encoded += "q"
}
return encoded
}
// can be replaced with os.ReadFile() in go 1.16+
func readFile(path string) ([]byte, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
data := make([]byte, 0, 128)
for {
if len(data) >= cap(data) {
d := append(data[:cap(data)], 0)
data = d[:len(data)]
}
n, err := file.Read(data[len(data):cap(data)])
data = data[:len(data)+n]
if err != nil {
if err == io.EOF {
err = nil
}
return data, err
}
}
}

View File

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

View File

@@ -1,9 +1,11 @@
// +build windows
//go:build windows
package fzf
import (
"os"
"regexp"
"strings"
)
func notifyOnResize(resizeChan chan<- os.Signal) {
@@ -17,3 +19,27 @@ func notifyStop(p *os.Process) {
func notifyOnCont(resizeChan chan<- os.Signal) {
// NOOP
}
func quoteEntry(entry string) string {
shell := os.Getenv("SHELL")
if len(shell) == 0 {
shell = "cmd"
}
if strings.Contains(shell, "cmd") {
// backslash escaping is done here for applications
// (see ripgrep test case in terminal_test.go#TestWindowsCommands)
escaped := strings.Replace(entry, `\`, `\\`, -1)
escaped = `"` + strings.Replace(escaped, `"`, `\"`, -1) + `"`
// caret is the escape character for cmd shell
r, _ := regexp.Compile(`[&|<>()@^%!"]`)
return r.ReplaceAllStringFunc(escaped, func(match string) string {
return "^" + match
})
} else if strings.Contains(shell, "pwsh") || strings.Contains(shell, "powershell") {
escaped := strings.Replace(entry, `"`, `\"`, -1)
return "'" + strings.Replace(escaped, "'", "''", -1) + "'"
} else {
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
}
}

View File

@@ -35,7 +35,7 @@ type Delimiter struct {
str *string
}
// String returns the string representation of a Delimeter.
// String returns the string representation of a Delimiter.
func (d Delimiter) String() string {
return fmt.Sprintf("Delimiter{regex: %v, str: &%q}", d.regex, *d.str)
}
@@ -91,7 +91,7 @@ func withPrefixLengths(tokens []string, begin int) []Token {
prefixLength := begin
for idx := range tokens {
chars := util.ToChars([]byte(tokens[idx]))
chars := util.ToChars(sbytes(tokens[idx]))
ret[idx] = Token{&chars, int32(prefixLength)}
prefixLength += chars.Length()
}
@@ -156,14 +156,14 @@ func Tokenize(text string, delimiter Delimiter) []Token {
// FIXME performance
var tokens []string
if delimiter.regex != nil {
for len(text) > 0 {
loc := delimiter.regex.FindStringIndex(text)
if len(loc) < 2 {
loc = []int{0, len(text)}
}
last := util.Max(loc[1], 1)
tokens = append(tokens, text[:last])
text = text[last:]
locs := delimiter.regex.FindAllStringIndex(text, -1)
begin := 0
for _, loc := range locs {
tokens = append(tokens, text[begin:loc[1]])
begin = loc[1]
}
if begin < len(text) {
tokens = append(tokens, text[begin:])
}
}
return withPrefixLengths(tokens, 0)
@@ -187,7 +187,7 @@ func Transform(tokens []Token, withNth []Range) []Token {
if r.begin == r.end {
idx := r.begin
if idx == rangeEllipsis {
chars := util.ToChars([]byte(joinTokens(tokens)))
chars := util.ToChars(sbytes(joinTokens(tokens)))
parts = append(parts, &chars)
} else {
if idx < 0 {

View File

@@ -1,41 +1,50 @@
// +build !ncurses
// +build !tcell
// +build !windows
//go:build !tcell && !windows
package tui
type Attr int
type Attr int32
func HasFullscreenRenderer() bool {
return false
}
var DefaultBorderShape BorderShape = BorderRounded
func (a Attr) Merge(b Attr) Attr {
return a | b
}
const (
AttrRegular Attr = Attr(0)
Bold = Attr(1)
Dim = Attr(1 << 1)
Italic = Attr(1 << 2)
Underline = Attr(1 << 3)
Blink = Attr(1 << 4)
Blink2 = Attr(1 << 5)
Reverse = Attr(1 << 6)
AttrUndefined = Attr(0)
AttrRegular = Attr(1 << 8)
AttrClear = Attr(1 << 9)
Bold = Attr(1)
Dim = Attr(1 << 1)
Italic = Attr(1 << 2)
Underline = Attr(1 << 3)
Blink = Attr(1 << 4)
Blink2 = Attr(1 << 5)
Reverse = Attr(1 << 6)
StrikeThrough = Attr(1 << 7)
)
func (r *FullscreenRenderer) Init() {}
func (r *FullscreenRenderer) Pause(bool) {}
func (r *FullscreenRenderer) Resume(bool) {}
func (r *FullscreenRenderer) Clear() {}
func (r *FullscreenRenderer) Refresh() {}
func (r *FullscreenRenderer) Close() {}
func (r *FullscreenRenderer) Init() {}
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
func (r *FullscreenRenderer) Pause(bool) {}
func (r *FullscreenRenderer) Resume(bool, bool) {}
func (r *FullscreenRenderer) PassThrough(string) {}
func (r *FullscreenRenderer) Clear() {}
func (r *FullscreenRenderer) NeedScrollbarRedraw() bool { return false }
func (r *FullscreenRenderer) ShouldEmitResizeEvent() bool { return false }
func (r *FullscreenRenderer) Refresh() {}
func (r *FullscreenRenderer) Close() {}
func (r *FullscreenRenderer) Size() TermSize { return TermSize{} }
func (r *FullscreenRenderer) DoesAutoWrap() bool { return false }
func (r *FullscreenRenderer) GetChar() Event { return Event{} }
func (r *FullscreenRenderer) MaxX() int { return 0 }
func (r *FullscreenRenderer) MaxY() int { return 0 }
func (r *FullscreenRenderer) GetChar() Event { return Event{} }
func (r *FullscreenRenderer) Top() int { return 0 }
func (r *FullscreenRenderer) MaxX() int { return 0 }
func (r *FullscreenRenderer) MaxY() int { return 0 }
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {}

File diff suppressed because it is too large Load Diff

View File

@@ -1,32 +1,46 @@
// +build !windows
//go:build !windows
package tui
import (
"fmt"
"os"
"os/exec"
"strings"
"syscall"
"github.com/junegunn/fzf/src/util"
"golang.org/x/crypto/ssh/terminal"
"golang.org/x/sys/unix"
"golang.org/x/term"
)
func IsLightRendererSupported() bool {
return true
}
func (r *LightRenderer) defaultTheme() *ColorTheme {
if strings.Contains(os.Getenv("TERM"), "256") {
return Dark256
}
colors, err := exec.Command("tput", "colors").Output()
if err == nil && atoi(strings.TrimSpace(string(colors)), 16) > 16 {
return Dark256
}
return Default16
}
func (r *LightRenderer) fd() int {
return int(r.ttyin.Fd())
}
func (r *LightRenderer) initPlatform() error {
fd := r.fd()
origState, err := terminal.GetState(fd)
origState, err := term.GetState(fd)
if err != nil {
return err
}
r.origState = origState
terminal.MakeRaw(fd)
term.MakeRaw(fd)
return nil
}
@@ -50,15 +64,15 @@ func openTtyIn() *os.File {
}
func (r *LightRenderer) setupTerminal() {
terminal.MakeRaw(r.fd())
term.MakeRaw(r.fd())
}
func (r *LightRenderer) restoreTerminal() {
terminal.Restore(r.fd(), r.origState)
term.Restore(r.fd(), r.origState)
}
func (r *LightRenderer) updateTerminalSize() {
width, height, err := terminal.GetSize(r.fd())
width, height, err := term.GetSize(r.fd())
if err == nil {
r.width = width
@@ -95,3 +109,11 @@ func (r *LightRenderer) getch(nonblock bool) (int, bool) {
}
return int(b[0]), true
}
func (r *LightRenderer) Size() TermSize {
ws, err := unix.IoctlGetWinsize(int(r.ttyin.Fd()), unix.TIOCGWINSZ)
if err != nil {
return TermSize{}
}
return TermSize{int(ws.Row), int(ws.Col), int(ws.Xpixel), int(ws.Ypixel)}
}

View File

@@ -1,15 +1,20 @@
//+build windows
//go:build windows
package tui
import (
"os"
"syscall"
"time"
"github.com/junegunn/fzf/src/util"
"golang.org/x/sys/windows"
)
const (
timeoutInterval = 10
)
var (
consoleFlagsInput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_EXTENDED_FLAGS)
consoleFlagsOutput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING | windows.ENABLE_PROCESSED_OUTPUT | windows.DISABLE_NEWLINE_AUTO_RETURN)
@@ -34,6 +39,14 @@ func IsLightRendererSupported() bool {
return canSetVt100
}
func (r *LightRenderer) defaultTheme() *ColorTheme {
// the getenv check is borrowed from here: https://github.com/gdamore/tcell/commit/0c473b86d82f68226a142e96cc5a34c5a29b3690#diff-b008fcd5e6934bf31bc3d33bf49f47d8R178:
if !IsLightRendererSupported() || os.Getenv("ConEmuPID") != "" || os.Getenv("TCELL_TRUECOLOR") == "disable" {
return Default16
}
return Dark256
}
func (r *LightRenderer) initPlatform() error {
//outHandle := windows.Stdout
outHandle, _ := syscall.Open("CONOUT$", syscall.O_RDWR, 0)
@@ -52,7 +65,7 @@ func (r *LightRenderer) initPlatform() error {
// channel for non-blocking reads. Buffer to make sure
// we get the ESC sets:
r.ttyinChannel = make(chan byte, 12)
r.ttyinChannel = make(chan byte, 1024)
// the following allows for non-blocking IO.
// syscall.SetNonblock() is a NOOP under Windows.
@@ -97,16 +110,24 @@ func (r *LightRenderer) restoreTerminal() error {
return windows.SetConsoleMode(windows.Handle(r.outHandle), r.origStateOutput)
}
func (r *LightRenderer) updateTerminalSize() {
func (r *LightRenderer) Size() TermSize {
var w, h int
var bufferInfo windows.ConsoleScreenBufferInfo
if err := windows.GetConsoleScreenBufferInfo(windows.Handle(r.outHandle), &bufferInfo); err != nil {
r.width = getEnv("COLUMNS", defaultWidth)
r.height = r.maxHeightFunc(getEnv("LINES", defaultHeight))
w = getEnv("COLUMNS", defaultWidth)
h = r.maxHeightFunc(getEnv("LINES", defaultHeight))
} else {
r.width = int(bufferInfo.Window.Right - bufferInfo.Window.Left)
r.height = r.maxHeightFunc(int(bufferInfo.Window.Bottom - bufferInfo.Window.Top))
w = int(bufferInfo.Window.Right - bufferInfo.Window.Left)
h = r.maxHeightFunc(int(bufferInfo.Window.Bottom - bufferInfo.Window.Top))
}
return TermSize{h, w, 0, 0}
}
func (r *LightRenderer) updateTerminalSize() {
size := r.Size()
r.width = size.Columns
r.height = size.Lines
}
func (r *LightRenderer) findOffset() (row int, col int) {
@@ -122,7 +143,7 @@ func (r *LightRenderer) getch(nonblock bool) (int, bool) {
select {
case bc := <-r.ttyinChannel:
return int(bc), true
default:
case <-time.After(timeoutInterval * time.Millisecond):
return 0, false
}
} else {

View File

@@ -1,30 +1,42 @@
// +build tcell windows
//go:build tcell || windows
package tui
import (
"os"
"time"
"unicode/utf8"
"runtime"
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v2/encoding"
"github.com/junegunn/fzf/src/util"
"github.com/gdamore/tcell"
"github.com/gdamore/tcell/encoding"
"github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
)
func HasFullscreenRenderer() bool {
return true
}
func (p ColorPair) style() tcell.Style {
style := tcell.StyleDefault
return style.Foreground(tcell.Color(p.Fg())).Background(tcell.Color(p.Bg()))
var DefaultBorderShape BorderShape = BorderSharp
func asTcellColor(color Color) tcell.Color {
if color == colDefault {
return tcell.ColorDefault
}
value := uint64(tcell.ColorValid) + uint64(color)
if color.is24() {
value = value | uint64(tcell.ColorIsRGB)
}
return tcell.Color(value)
}
type Attr tcell.Style
func (p ColorPair) style() tcell.Style {
style := tcell.StyleDefault
return style.Foreground(asTcellColor(p.Fg())).Background(asTcellColor(p.Bg()))
}
type Attr int32
type TcellWindow struct {
color bool
@@ -63,8 +75,6 @@ func (w *TcellWindow) Refresh() {
}
w.lastX = 0
w.lastY = 0
w.drawBorder()
}
func (w *TcellWindow) FinishFill() {
@@ -72,18 +82,28 @@ func (w *TcellWindow) FinishFill() {
}
const (
Bold Attr = Attr(tcell.AttrBold)
Dim = Attr(tcell.AttrDim)
Blink = Attr(tcell.AttrBlink)
Reverse = Attr(tcell.AttrReverse)
Underline = Attr(tcell.AttrUnderline)
Italic = Attr(tcell.AttrNone) // Not supported
Bold Attr = Attr(tcell.AttrBold)
Dim = Attr(tcell.AttrDim)
Blink = Attr(tcell.AttrBlink)
Reverse = Attr(tcell.AttrReverse)
Underline = Attr(tcell.AttrUnderline)
StrikeThrough = Attr(tcell.AttrStrikeThrough)
Italic = Attr(tcell.AttrItalic)
)
const (
AttrRegular Attr = 0
AttrUndefined = Attr(0)
AttrRegular = Attr(1 << 7)
AttrClear = Attr(1 << 8)
)
func (r *FullscreenRenderer) PassThrough(str string) {
// No-op
// https://github.com/gdamore/tcell/pull/650#issuecomment-1806442846
}
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
if _screen.Colors() >= 256 {
return Dark256
@@ -118,8 +138,12 @@ func (a Attr) Merge(b Attr) Attr {
return a | b
}
// handle the following as private members of FullscreenRenderer instance
// they are declared here to prevent introducing tcell library in non-windows builds
var (
_screen tcell.Screen
_screen tcell.Screen
_prevMouseButton tcell.ButtonMask
_initialResize bool = true
)
func (r *FullscreenRenderer) initScreen() {
@@ -148,6 +172,10 @@ func (r *FullscreenRenderer) Init() {
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
}
func (r *FullscreenRenderer) Top() int {
return 0
}
func (r *FullscreenRenderer) MaxX() int {
ncols, _ := _screen.Size()
return int(ncols)
@@ -166,164 +194,256 @@ func (w *TcellWindow) Y() int {
return w.lastY
}
func (r *FullscreenRenderer) DoesAutoWrap() bool {
return false
}
func (r *FullscreenRenderer) Clear() {
_screen.Sync()
_screen.Clear()
}
func (r *FullscreenRenderer) NeedScrollbarRedraw() bool {
return true
}
func (r *FullscreenRenderer) ShouldEmitResizeEvent() bool {
return true
}
func (r *FullscreenRenderer) Refresh() {
// noop
}
// TODO: Pixel width and height not implemented
func (r *FullscreenRenderer) Size() TermSize {
cols, lines := _screen.Size()
return TermSize{lines, cols, 0, 0}
}
func (r *FullscreenRenderer) GetChar() Event {
ev := _screen.PollEvent()
switch ev := ev.(type) {
case *tcell.EventResize:
// Ignore the first resize event
// https://github.com/gdamore/tcell/blob/v2.7.0/TUTORIAL.md?plain=1#L18
if _initialResize {
_initialResize = false
return Event{Invalid, 0, nil}
}
return Event{Resize, 0, nil}
// process mouse events:
case *tcell.EventMouse:
// mouse down events have zeroed buttons, so we can't use them
// mouse up event consists of two events, 1. (main) event with modifier and other metadata, 2. event with zeroed buttons
// so mouse click is three consecutive events, but the first and last are indistinguishable from movement events (with released buttons)
// dragging has same structure, it only repeats the middle (main) event appropriately
x, y := ev.Position()
button := ev.Buttons()
mod := ev.Modifiers() != 0
if button&tcell.WheelDown != 0 {
// since we dont have mouse down events (unlike LightRenderer), we need to track state in prevButton
prevButton, button := _prevMouseButton, ev.Buttons()
_prevMouseButton = button
drag := prevButton == button
switch {
case button&tcell.WheelDown != 0:
return Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, false, mod}}
} else if button&tcell.WheelUp != 0 {
case button&tcell.WheelUp != 0:
return Event{Mouse, 0, &MouseEvent{y, x, +1, false, false, false, mod}}
} else if runtime.GOOS != "windows" {
case button&tcell.Button1 != 0:
double := false
if !drag {
// all potential double click events put their coordinates in the clicks array
// double click event has two conditions, temporal and spatial, the first is checked here
now := time.Now()
if now.Sub(r.prevDownTime) < doubleClickDuration {
r.clicks = append(r.clicks, [2]int{x, y})
} else {
r.clicks = [][2]int{{x, y}}
}
r.prevDownTime = now
// detect double clicks (also check for spatial condition)
n := len(r.clicks)
double = n > 1 && r.clicks[n-2][0] == r.clicks[n-1][0] && r.clicks[n-2][1] == r.clicks[n-1][1]
if double {
// make sure two consecutive double clicks require four clicks
r.clicks = [][2]int{}
}
}
// fire single or double click event
return Event{Mouse, 0, &MouseEvent{y, x, 0, true, !double, double, mod}}
case button&tcell.Button2 != 0:
return Event{Mouse, 0, &MouseEvent{y, x, 0, false, true, false, mod}}
default:
// double and single taps on Windows don't quite work due to
// the console acting on the events and not allowing us
// to consume them.
left := button&tcell.Button1 != 0
down := left || button&tcell.Button3 != 0
double := false
if down {
now := time.Now()
if !left {
r.clickY = []int{}
} else if now.Sub(r.prevDownTime) < doubleClickDuration {
r.clickY = append(r.clickY, x)
} else {
r.clickY = []int{x}
r.prevDownTime = now
}
} else {
if len(r.clickY) > 1 && r.clickY[0] == r.clickY[1] &&
time.Now().Sub(r.prevDownTime) < doubleClickDuration {
double = true
}
}
return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, mod}}
}
// process keyboard:
case *tcell.EventKey:
alt := (ev.Modifiers() & tcell.ModAlt) > 0
keyfn := func(r rune) int {
mods := ev.Modifiers()
none := mods == tcell.ModNone
alt := (mods & tcell.ModAlt) > 0
ctrl := (mods & tcell.ModCtrl) > 0
shift := (mods & tcell.ModShift) > 0
ctrlAlt := ctrl && alt
altShift := alt && shift
keyfn := func(r rune) Event {
if alt {
return CtrlAltA - 'a' + int(r)
return CtrlAltKey(r)
}
return CtrlA - 'a' + int(r)
return EventType(CtrlA.Int() - 'a' + int(r)).AsEvent()
}
switch ev.Key() {
// section 1: Ctrl+(Alt)+[a-z]
case tcell.KeyCtrlA:
return Event{keyfn('a'), 0, nil}
return keyfn('a')
case tcell.KeyCtrlB:
return Event{keyfn('b'), 0, nil}
return keyfn('b')
case tcell.KeyCtrlC:
return Event{keyfn('c'), 0, nil}
return keyfn('c')
case tcell.KeyCtrlD:
return Event{keyfn('d'), 0, nil}
return keyfn('d')
case tcell.KeyCtrlE:
return Event{keyfn('e'), 0, nil}
return keyfn('e')
case tcell.KeyCtrlF:
return Event{keyfn('f'), 0, nil}
return keyfn('f')
case tcell.KeyCtrlG:
return Event{keyfn('g'), 0, nil}
return keyfn('g')
case tcell.KeyCtrlH:
return Event{keyfn('h'), 0, nil}
switch ev.Rune() {
case 0:
if ctrl {
return Event{BSpace, 0, nil}
}
case rune(tcell.KeyCtrlH):
switch {
case ctrl:
return keyfn('h')
case alt:
return Event{AltBS, 0, nil}
case none, shift:
return Event{BSpace, 0, nil}
}
}
case tcell.KeyCtrlI:
return Event{keyfn('i'), 0, nil}
return keyfn('i')
case tcell.KeyCtrlJ:
return Event{keyfn('j'), 0, nil}
return keyfn('j')
case tcell.KeyCtrlK:
return Event{keyfn('k'), 0, nil}
return keyfn('k')
case tcell.KeyCtrlL:
return Event{keyfn('l'), 0, nil}
return keyfn('l')
case tcell.KeyCtrlM:
return Event{keyfn('m'), 0, nil}
return keyfn('m')
case tcell.KeyCtrlN:
return Event{keyfn('n'), 0, nil}
return keyfn('n')
case tcell.KeyCtrlO:
return Event{keyfn('o'), 0, nil}
return keyfn('o')
case tcell.KeyCtrlP:
return Event{keyfn('p'), 0, nil}
return keyfn('p')
case tcell.KeyCtrlQ:
return Event{keyfn('q'), 0, nil}
return keyfn('q')
case tcell.KeyCtrlR:
return Event{keyfn('r'), 0, nil}
return keyfn('r')
case tcell.KeyCtrlS:
return Event{keyfn('s'), 0, nil}
return keyfn('s')
case tcell.KeyCtrlT:
return Event{keyfn('t'), 0, nil}
return keyfn('t')
case tcell.KeyCtrlU:
return Event{keyfn('u'), 0, nil}
return keyfn('u')
case tcell.KeyCtrlV:
return Event{keyfn('v'), 0, nil}
return keyfn('v')
case tcell.KeyCtrlW:
return Event{keyfn('w'), 0, nil}
return keyfn('w')
case tcell.KeyCtrlX:
return Event{keyfn('x'), 0, nil}
return keyfn('x')
case tcell.KeyCtrlY:
return Event{keyfn('y'), 0, nil}
return keyfn('y')
case tcell.KeyCtrlZ:
return Event{keyfn('z'), 0, nil}
return keyfn('z')
// section 2: Ctrl+[ \]_]
case tcell.KeyCtrlSpace:
return Event{CtrlSpace, 0, nil}
case tcell.KeyCtrlBackslash:
return Event{CtrlBackSlash, 0, nil}
case tcell.KeyCtrlRightSq:
return Event{CtrlRightBracket, 0, nil}
case tcell.KeyCtrlCarat:
return Event{CtrlCaret, 0, nil}
case tcell.KeyCtrlUnderscore:
return Event{CtrlSlash, 0, nil}
// section 3: (Alt)+Backspace2
case tcell.KeyBackspace2:
if alt {
return Event{AltBS, 0, nil}
}
return Event{BSpace, 0, nil}
// section 4: (Alt+Shift)+Key(Up|Down|Left|Right)
case tcell.KeyUp:
if altShift {
return Event{AltSUp, 0, nil}
}
if shift {
return Event{SUp, 0, nil}
}
if alt {
return Event{AltUp, 0, nil}
}
return Event{Up, 0, nil}
case tcell.KeyDown:
if altShift {
return Event{AltSDown, 0, nil}
}
if shift {
return Event{SDown, 0, nil}
}
if alt {
return Event{AltDown, 0, nil}
}
return Event{Down, 0, nil}
case tcell.KeyLeft:
if altShift {
return Event{AltSLeft, 0, nil}
}
if shift {
return Event{SLeft, 0, nil}
}
if alt {
return Event{AltLeft, 0, nil}
}
return Event{Left, 0, nil}
case tcell.KeyRight:
if altShift {
return Event{AltSRight, 0, nil}
}
if shift {
return Event{SRight, 0, nil}
}
if alt {
return Event{AltRight, 0, nil}
}
return Event{Right, 0, nil}
// section 5: (Insert|Home|Delete|End|PgUp|PgDn|BackTab|F1-F12)
case tcell.KeyInsert:
return Event{Insert, 0, nil}
case tcell.KeyHome:
return Event{Home, 0, nil}
case tcell.KeyDelete:
if ctrl {
return Event{CtrlDelete, 0, nil}
}
if shift {
return Event{SDelete, 0, nil}
}
return Event{Del, 0, nil}
case tcell.KeyEnd:
return Event{End, 0, nil}
@@ -331,10 +451,8 @@ func (r *FullscreenRenderer) GetChar() Event {
return Event{PgUp, 0, nil}
case tcell.KeyPgDn:
return Event{PgDn, 0, nil}
case tcell.KeyBacktab:
return Event{BTab, 0, nil}
case tcell.KeyF1:
return Event{F1, 0, nil}
case tcell.KeyF2:
@@ -360,31 +478,31 @@ func (r *FullscreenRenderer) GetChar() Event {
case tcell.KeyF12:
return Event{F12, 0, nil}
// ev.Ch doesn't work for some reason for space:
// section 6: (Ctrl+Alt)+'rune'
case tcell.KeyRune:
r := ev.Rune()
if alt {
switch r {
case ' ':
return Event{AltSpace, 0, nil}
case '/':
return Event{AltSlash, 0, nil}
}
if r >= 'a' && r <= 'z' {
return Event{AltA + int(r) - 'a', 0, nil}
}
if r >= '0' && r <= '9' {
return Event{Alt0 + int(r) - '0', 0, nil}
}
}
return Event{Rune, r, nil}
switch {
// translate native key events to ascii control characters
case r == ' ' && ctrl:
return Event{CtrlSpace, 0, nil}
// handle AltGr characters
case ctrlAlt:
return Event{Rune, r, nil} // dropping modifiers
// simple characters (possibly with modifier)
case alt:
return AltKey(r)
default:
return Event{Rune, r, nil}
}
// section 7: Esc
case tcell.KeyEsc:
return Event{ESC, 0, nil}
}
}
// section 8: Invalid
return Event{Invalid, 0, nil}
}
@@ -394,7 +512,7 @@ func (r *FullscreenRenderer) Pause(clear bool) {
}
}
func (r *FullscreenRenderer) Resume(clear bool) {
func (r *FullscreenRenderer) Resume(clear bool, sigcont bool) {
if clear {
r.initScreen()
}
@@ -417,8 +535,8 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int,
if preview {
normal = ColPreview
}
return &TcellWindow{
color: r.theme != nil,
w := &TcellWindow{
color: r.theme.Colored,
preview: preview,
top: top,
left: left,
@@ -426,6 +544,8 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int,
height: height,
normal: normal,
borderStyle: borderStyle}
w.Erase()
return w
}
func (w *TcellWindow) Close() {
@@ -441,7 +561,13 @@ func fill(x, y, w, h int, n ColorPair, r rune) {
}
func (w *TcellWindow) Erase() {
fill(w.left-1, w.top, w.width+1, w.height, w.normal, ' ')
fill(w.left-1, w.top, w.width+1, w.height-1, w.normal, ' ')
w.drawBorder(false)
}
func (w *TcellWindow) EraseMaybe() bool {
w.Erase()
return true
}
func (w *TcellWindow) Enclose(y int, x int) bool {
@@ -464,65 +590,58 @@ func (w *TcellWindow) MoveAndClear(y int, x int) {
}
func (w *TcellWindow) Print(text string) {
w.printString(text, w.normal, 0)
w.printString(text, w.normal)
}
func (w *TcellWindow) printString(text string, pair ColorPair, a Attr) {
t := text
func (w *TcellWindow) printString(text string, pair ColorPair) {
lx := 0
a := pair.Attr()
var style tcell.Style
if w.color {
style = pair.style().
style := pair.style()
if a&AttrClear == 0 {
style = style.
Reverse(a&Attr(tcell.AttrReverse) != 0).
Underline(a&Attr(tcell.AttrUnderline) != 0)
} else {
style = w.normal.style().
Reverse(a&Attr(tcell.AttrReverse) != 0 || pair == ColCurrent || pair == ColCurrentMatch).
Underline(a&Attr(tcell.AttrUnderline) != 0 || pair == ColMatch || pair == ColCurrentMatch)
Underline(a&Attr(tcell.AttrUnderline) != 0).
StrikeThrough(a&Attr(tcell.AttrStrikeThrough) != 0).
Italic(a&Attr(tcell.AttrItalic) != 0).
Blink(a&Attr(tcell.AttrBlink) != 0).
Dim(a&Attr(tcell.AttrDim) != 0)
}
style = style.
Blink(a&Attr(tcell.AttrBlink) != 0).
Bold(a&Attr(tcell.AttrBold) != 0).
Dim(a&Attr(tcell.AttrDim) != 0)
for {
if len(t) == 0 {
break
}
r, size := utf8.DecodeRuneInString(t)
t = t[size:]
gr := uniseg.NewGraphemes(text)
for gr.Next() {
st := style
rs := gr.Runes()
if r < rune(' ') { // ignore control characters
continue
}
if r == '\n' {
w.lastY++
lx = 0
} else {
if r == '\u000D' { // skip carriage return
if len(rs) == 1 {
r := rs[0]
if r == '\r' {
st = style.Dim(true)
rs[0] = '␍'
} else if r == '\n' {
st = style.Dim(true)
rs[0] = '␊'
} else if r < rune(' ') { // ignore control characters
continue
}
var xPos = w.left + w.lastX + lx
var yPos = w.top + w.lastY
if xPos < (w.left+w.width) && yPos < (w.top+w.height) {
_screen.SetContent(xPos, yPos, r, nil, style)
}
lx += runewidth.RuneWidth(r)
}
var xPos = w.left + w.lastX + lx
var yPos = w.top + w.lastY
if xPos < (w.left+w.width) && yPos < (w.top+w.height) {
_screen.SetContent(xPos, yPos, rs[0], rs[1:], st)
}
lx += util.StringWidth(string(rs))
}
w.lastX += lx
}
func (w *TcellWindow) CPrint(pair ColorPair, attr Attr, text string) {
w.printString(text, pair, attr)
func (w *TcellWindow) CPrint(pair ColorPair, text string) {
w.printString(text, pair)
}
func (w *TcellWindow) fillString(text string, pair ColorPair, a Attr) FillReturn {
func (w *TcellWindow) fillString(text string, pair ColorPair) FillReturn {
lx := 0
a := pair.Attr()
var style tcell.Style
if w.color {
@@ -535,40 +654,58 @@ func (w *TcellWindow) fillString(text string, pair ColorPair, a Attr) FillReturn
Bold(a&Attr(tcell.AttrBold) != 0).
Dim(a&Attr(tcell.AttrDim) != 0).
Reverse(a&Attr(tcell.AttrReverse) != 0).
Underline(a&Attr(tcell.AttrUnderline) != 0)
Underline(a&Attr(tcell.AttrUnderline) != 0).
StrikeThrough(a&Attr(tcell.AttrStrikeThrough) != 0).
Italic(a&Attr(tcell.AttrItalic) != 0)
for _, r := range text {
if r == '\n' {
w.lastY++
w.lastX = 0
lx = 0
} else {
var xPos = w.left + w.lastX + lx
// word wrap:
if xPos >= (w.left + w.width) {
gr := uniseg.NewGraphemes(text)
Loop:
for gr.Next() {
st := style
rs := gr.Runes()
if len(rs) == 1 {
r := rs[0]
switch r {
case '\r':
st = style.Dim(true)
rs[0] = '␍'
case '\n':
w.lastY++
w.lastX = 0
lx = 0
xPos = w.left
continue Loop
}
var yPos = w.top + w.lastY
if yPos >= (w.top + w.height) {
return FillSuspend
}
_screen.SetContent(xPos, yPos, r, nil, style)
lx += runewidth.RuneWidth(r)
}
// word wrap:
xPos := w.left + w.lastX + lx
if xPos >= (w.left + w.width) {
w.lastY++
w.lastX = 0
lx = 0
xPos = w.left
}
yPos := w.top + w.lastY
if yPos >= (w.top + w.height) {
return FillSuspend
}
_screen.SetContent(xPos, yPos, rs[0], rs[1:], st)
lx += util.StringWidth(string(rs))
}
w.lastX += lx
if w.lastX == w.width {
w.lastY++
w.lastX = 0
return FillNextLine
}
return FillContinue
}
func (w *TcellWindow) Fill(str string) FillReturn {
return w.fillString(str, w.normal, 0)
return w.fillString(str, w.normal)
}
func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
@@ -578,11 +715,20 @@ func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
if bg == colDefault {
bg = w.normal.Bg()
}
return w.fillString(str, NewColorPair(fg, bg), a)
return w.fillString(str, NewColorPair(fg, bg, a))
}
func (w *TcellWindow) drawBorder() {
if w.borderStyle.shape == BorderNone {
func (w *TcellWindow) DrawBorder() {
w.drawBorder(false)
}
func (w *TcellWindow) DrawHBorder() {
w.drawBorder(true)
}
func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
shape := w.borderStyle.shape
if shape == BorderNone {
return
}
@@ -602,20 +748,52 @@ func (w *TcellWindow) drawBorder() {
style = w.normal.style()
}
for x := left; x < right; x++ {
_screen.SetContent(x, top, w.borderStyle.horizontal, nil, style)
_screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style)
}
if w.borderStyle.shape != BorderHorizontal {
for y := top; y < bot; y++ {
_screen.SetContent(left, y, w.borderStyle.vertical, nil, style)
_screen.SetContent(right-1, y, w.borderStyle.vertical, nil, style)
hw := runeWidth(w.borderStyle.top)
switch shape {
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderHorizontal, BorderTop:
max := right - 2*hw
if shape == BorderHorizontal || shape == BorderTop {
max = right - hw
}
// tcell has an issue displaying two overlapping wide runes
// e.g. SetContent( HH )
// SetContent( TR )
// ==================
// ( HH ) => TR is ignored
for x := left; x <= max; x += hw {
_screen.SetContent(x, top, w.borderStyle.top, nil, style)
}
}
switch shape {
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderHorizontal, BorderBottom:
max := right - 2*hw
if shape == BorderHorizontal || shape == BorderBottom {
max = right - hw
}
for x := left; x <= max; x += hw {
_screen.SetContent(x, bot-1, w.borderStyle.bottom, nil, style)
}
}
if !onlyHorizontal {
switch shape {
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderVertical, BorderLeft:
for y := top; y < bot; y++ {
_screen.SetContent(left, y, w.borderStyle.left, nil, style)
}
}
switch shape {
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderVertical, BorderRight:
vw := runeWidth(w.borderStyle.right)
for y := top; y < bot; y++ {
_screen.SetContent(right-vw, y, w.borderStyle.right, nil, style)
}
}
}
switch shape {
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble:
_screen.SetContent(left, top, w.borderStyle.topLeft, nil, style)
_screen.SetContent(right-1, top, w.borderStyle.topRight, nil, style)
_screen.SetContent(right-runeWidth(w.borderStyle.topRight), top, w.borderStyle.topRight, nil, style)
_screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style)
_screen.SetContent(right-1, bot-1, w.borderStyle.bottomRight, nil, style)
_screen.SetContent(right-runeWidth(w.borderStyle.bottomRight), bot-1, w.borderStyle.bottomRight, nil, style)
}
}

394
src/tui/tcell_test.go Normal file
View File

@@ -0,0 +1,394 @@
//go:build tcell || windows
package tui
import (
"testing"
"github.com/gdamore/tcell/v2"
"github.com/junegunn/fzf/src/util"
)
func assert(t *testing.T, context string, got interface{}, want interface{}) bool {
if got == want {
return true
} else {
t.Errorf("%s = (%T)%v, want (%T)%v", context, got, got, want, want)
return false
}
}
// Test the handling of the tcell keyboard events.
func TestGetCharEventKey(t *testing.T) {
if util.ToTty() {
// This test is skipped when output goes to terminal, because it causes
// some glitches:
// - output lines may not start at the beginning of a row which makes
// the output unreadable
// - terminal may get cleared which prevents you from seeing results of
// previous tests
// Good ways to prevent the glitches are piping the output to a pager
// or redirecting to a file. I've found `less +G` to be trouble-free.
t.Skip("Skipped because this test misbehaves in terminal, pipe to a pager or redirect output to a file to run it safely.")
} else if testing.Verbose() {
// I have observed a behaviour when this test outputted more than 8192
// bytes (32*256) into the 'less' pager, both the go's test executable
// and the pager hanged. The go's executable was blocking on printing.
// I was able to create minimal working example of that behaviour, but
// that example hanged after 12256 bytes (32*(256+127)).
t.Log("If you are piping this test to a pager and it hangs, make the pager greedy for input, e.g. 'less +G'.")
}
if !HasFullscreenRenderer() {
t.Skip("Can't test FullscreenRenderer.")
}
// construct test cases
type giveKey struct {
Type tcell.Key
Char rune
Mods tcell.ModMask
}
type wantKey = Event
type testCase struct {
giveKey
wantKey
}
/*
Some test cases are marked "fabricated". It means that giveKey value
is valid, but it is not what you get when you press the keys. For
example Ctrl+C will NOT give you tcell.KeyCtrlC, but tcell.KeyETX
(End-Of-Text character, causing SIGINT).
I was trying to accompany the fabricated test cases with real ones.
Some test cases are marked "unhandled". It means that giveKey.Type
is not present in tcell.go source code. It can still be handled via
implicit or explicit alias.
If not said otherwise, test cases are for US keyboard.
(tabstop=44)
*/
tests := []testCase{
// section 1: Ctrl+(Alt)+[a-z]
{giveKey{tcell.KeyCtrlA, rune(tcell.KeyCtrlA), tcell.ModCtrl}, wantKey{CtrlA, 0, nil}},
{giveKey{tcell.KeyCtrlC, rune(tcell.KeyCtrlC), tcell.ModCtrl}, wantKey{CtrlC, 0, nil}}, // fabricated
{giveKey{tcell.KeyETX, rune(tcell.KeyETX), tcell.ModCtrl}, wantKey{CtrlC, 0, nil}}, // this is SIGINT (Ctrl+C)
{giveKey{tcell.KeyCtrlZ, rune(tcell.KeyCtrlZ), tcell.ModCtrl}, wantKey{CtrlZ, 0, nil}}, // fabricated
// KeyTab is alias for KeyTAB
{giveKey{tcell.KeyCtrlI, rune(tcell.KeyCtrlI), tcell.ModCtrl}, wantKey{Tab, 0, nil}}, // fabricated
{giveKey{tcell.KeyTab, rune(tcell.KeyTab), tcell.ModNone}, wantKey{Tab, 0, nil}}, // unhandled, actual "Tab" keystroke
{giveKey{tcell.KeyTAB, rune(tcell.KeyTAB), tcell.ModNone}, wantKey{Tab, 0, nil}}, // fabricated, unhandled
// KeyEnter is alias for KeyCR
{giveKey{tcell.KeyCtrlM, rune(tcell.KeyCtrlM), tcell.ModNone}, wantKey{CtrlM, 0, nil}}, // actual "Enter" keystroke
{giveKey{tcell.KeyCR, rune(tcell.KeyCR), tcell.ModNone}, wantKey{CtrlM, 0, nil}}, // fabricated, unhandled
{giveKey{tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone}, wantKey{CtrlM, 0, nil}}, // fabricated, unhandled
// Ctrl+Alt keys
{giveKey{tcell.KeyCtrlA, rune(tcell.KeyCtrlA), tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAlt, 'a', nil}}, // fabricated
{giveKey{tcell.KeyCtrlA, rune(tcell.KeyCtrlA), tcell.ModCtrl | tcell.ModAlt | tcell.ModShift}, wantKey{CtrlAlt, 'a', nil}}, // fabricated
// section 2: Ctrl+[ \]_]
{giveKey{tcell.KeyCtrlSpace, rune(tcell.KeyCtrlSpace), tcell.ModCtrl}, wantKey{CtrlSpace, 0, nil}}, // fabricated
{giveKey{tcell.KeyNUL, rune(tcell.KeyNUL), tcell.ModNone}, wantKey{CtrlSpace, 0, nil}}, // fabricated, unhandled
{giveKey{tcell.KeyRune, ' ', tcell.ModCtrl}, wantKey{CtrlSpace, 0, nil}}, // actual Ctrl+' '
{giveKey{tcell.KeyCtrlBackslash, rune(tcell.KeyCtrlBackslash), tcell.ModCtrl}, wantKey{CtrlBackSlash, 0, nil}},
{giveKey{tcell.KeyCtrlRightSq, rune(tcell.KeyCtrlRightSq), tcell.ModCtrl}, wantKey{CtrlRightBracket, 0, nil}},
{giveKey{tcell.KeyCtrlCarat, rune(tcell.KeyCtrlCarat), tcell.ModShift | tcell.ModCtrl}, wantKey{CtrlCaret, 0, nil}}, // fabricated
{giveKey{tcell.KeyRS, rune(tcell.KeyRS), tcell.ModShift | tcell.ModCtrl}, wantKey{CtrlCaret, 0, nil}}, // actual Ctrl+Shift+6 (i.e. Ctrl+^) keystroke
{giveKey{tcell.KeyCtrlUnderscore, rune(tcell.KeyCtrlUnderscore), tcell.ModShift | tcell.ModCtrl}, wantKey{CtrlSlash, 0, nil}},
// section 3: (Alt)+Backspace2
// KeyBackspace2 is alias for KeyDEL = 0x7F (ASCII) (allegedly unused by Windows)
// KeyDelete = 0x2E (VK_DELETE constant in Windows)
// KeyBackspace is alias for KeyBS = 0x08 (ASCII) (implicit alias with KeyCtrlH)
{giveKey{tcell.KeyBackspace2, 0, tcell.ModNone}, wantKey{BSpace, 0, nil}}, // fabricated
{giveKey{tcell.KeyBackspace2, 0, tcell.ModAlt}, wantKey{AltBS, 0, nil}}, // fabricated
{giveKey{tcell.KeyDEL, 0, tcell.ModNone}, wantKey{BSpace, 0, nil}}, // fabricated, unhandled
{giveKey{tcell.KeyDelete, 0, tcell.ModNone}, wantKey{Del, 0, nil}},
{giveKey{tcell.KeyDelete, 0, tcell.ModAlt}, wantKey{Del, 0, nil}},
{giveKey{tcell.KeyBackspace, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
{giveKey{tcell.KeyBS, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
{giveKey{tcell.KeyCtrlH, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModNone}, wantKey{BSpace, 0, nil}}, // actual "Backspace" keystroke
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModAlt}, wantKey{AltBS, 0, nil}}, // actual "Alt+Backspace" keystroke
{giveKey{tcell.KeyDEL, rune(tcell.KeyDEL), tcell.ModCtrl}, wantKey{BSpace, 0, nil}}, // actual "Ctrl+Backspace" keystroke
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModShift}, wantKey{BSpace, 0, nil}}, // actual "Shift+Backspace" keystroke
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModAlt}, wantKey{BSpace, 0, nil}}, // actual "Ctrl+Alt+Backspace" keystroke
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModShift}, wantKey{BSpace, 0, nil}}, // actual "Ctrl+Shift+Backspace" keystroke
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModShift | tcell.ModAlt}, wantKey{AltBS, 0, nil}}, // actual "Shift+Alt+Backspace" keystroke
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModAlt | tcell.ModShift}, wantKey{BSpace, 0, nil}}, // actual "Ctrl+Shift+Alt+Backspace" keystroke
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl}, wantKey{CtrlH, 0, nil}}, // actual "Ctrl+H" keystroke
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAlt, 'h', nil}}, // fabricated "Ctrl+Alt+H" keystroke
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl | tcell.ModShift}, wantKey{CtrlH, 0, nil}}, // actual "Ctrl+Shift+H" keystroke
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl | tcell.ModAlt | tcell.ModShift}, wantKey{CtrlAlt, 'h', nil}}, // fabricated "Ctrl+Shift+Alt+H" keystroke
// section 4: (Alt+Shift)+Key(Up|Down|Left|Right)
{giveKey{tcell.KeyUp, 0, tcell.ModNone}, wantKey{Up, 0, nil}},
{giveKey{tcell.KeyDown, 0, tcell.ModAlt}, wantKey{AltDown, 0, nil}},
{giveKey{tcell.KeyLeft, 0, tcell.ModShift}, wantKey{SLeft, 0, nil}},
{giveKey{tcell.KeyRight, 0, tcell.ModShift | tcell.ModAlt}, wantKey{AltSRight, 0, nil}},
{giveKey{tcell.KeyUpLeft, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
{giveKey{tcell.KeyUpRight, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
{giveKey{tcell.KeyDownLeft, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
{giveKey{tcell.KeyDownRight, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
{giveKey{tcell.KeyCenter, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
// section 5: (Insert|Home|Delete|End|PgUp|PgDn|BackTab|F1-F12)
{giveKey{tcell.KeyInsert, 0, tcell.ModNone}, wantKey{Insert, 0, nil}},
{giveKey{tcell.KeyF1, 0, tcell.ModNone}, wantKey{F1, 0, nil}},
// section 6: (Ctrl+Alt)+'rune'
{giveKey{tcell.KeyRune, 'a', tcell.ModNone}, wantKey{Rune, 'a', nil}},
{giveKey{tcell.KeyRune, 'a', tcell.ModCtrl}, wantKey{Rune, 'a', nil}}, // fabricated
{giveKey{tcell.KeyRune, 'a', tcell.ModAlt}, wantKey{Alt, 'a', nil}},
{giveKey{tcell.KeyRune, 'A', tcell.ModAlt}, wantKey{Alt, 'A', nil}},
{giveKey{tcell.KeyRune, '`', tcell.ModAlt}, wantKey{Alt, '`', nil}},
/*
"Input method" in Windows Language options:
US: "US Keyboard" does not generate any characters (and thus any events) in Ctrl+Alt+[a-z] range
CS: "Czech keyboard"
DE: "German keyboard"
Note that right Alt is not just `tcell.ModAlt` on foreign language keyboards, but it is the AltGr `tcell.ModCtrl|tcell.ModAlt`.
*/
{giveKey{tcell.KeyRune, '{', tcell.ModCtrl | tcell.ModAlt}, wantKey{Rune, '{', nil}}, // CS: Ctrl+Alt+b = "{" // Note that this does not interfere with CtrlB, since the "b" is replaced with "{" on OS level
{giveKey{tcell.KeyRune, '$', tcell.ModCtrl | tcell.ModAlt}, wantKey{Rune, '$', nil}}, // CS: Ctrl+Alt+ů = "$"
{giveKey{tcell.KeyRune, '~', tcell.ModCtrl | tcell.ModAlt}, wantKey{Rune, '~', nil}}, // CS: Ctrl+Alt++ = "~"
{giveKey{tcell.KeyRune, '`', tcell.ModCtrl | tcell.ModAlt}, wantKey{Rune, '`', nil}}, // CS: Ctrl+Alt+ý,Space = "`" // this is dead key, space is required to emit the char
{giveKey{tcell.KeyRune, '{', tcell.ModCtrl | tcell.ModAlt}, wantKey{Rune, '{', nil}}, // DE: Ctrl+Alt+7 = "{"
{giveKey{tcell.KeyRune, '@', tcell.ModCtrl | tcell.ModAlt}, wantKey{Rune, '@', nil}}, // DE: Ctrl+Alt+q = "@"
{giveKey{tcell.KeyRune, 'µ', tcell.ModCtrl | tcell.ModAlt}, wantKey{Rune, 'µ', nil}}, // DE: Ctrl+Alt+m = "µ"
// section 7: Esc
// KeyEsc and KeyEscape are aliases for KeyESC
{giveKey{tcell.KeyEsc, rune(tcell.KeyEsc), tcell.ModNone}, wantKey{ESC, 0, nil}}, // fabricated
{giveKey{tcell.KeyESC, rune(tcell.KeyESC), tcell.ModNone}, wantKey{ESC, 0, nil}}, // unhandled
{giveKey{tcell.KeyEscape, rune(tcell.KeyEscape), tcell.ModNone}, wantKey{ESC, 0, nil}}, // fabricated, unhandled
{giveKey{tcell.KeyESC, rune(tcell.KeyESC), tcell.ModCtrl}, wantKey{ESC, 0, nil}}, // actual Ctrl+[ keystroke
{giveKey{tcell.KeyCtrlLeftSq, rune(tcell.KeyCtrlLeftSq), tcell.ModCtrl}, wantKey{ESC, 0, nil}}, // fabricated, unhandled
// section 8: Invalid
{giveKey{tcell.KeyRune, 'a', tcell.ModMeta}, wantKey{Rune, 'a', nil}}, // fabricated
{giveKey{tcell.KeyF24, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}},
{giveKey{tcell.KeyHelp, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
{giveKey{tcell.KeyExit, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
{giveKey{tcell.KeyClear, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // unhandled, actual keystroke Numpad_5 with Numlock OFF
{giveKey{tcell.KeyCancel, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
{giveKey{tcell.KeyPrint, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
{giveKey{tcell.KeyPause, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // unhandled
}
r := NewFullscreenRenderer(&ColorTheme{}, false, false)
r.Init()
// run and evaluate the tests
initialResizeAsInvalid := true
for _, test := range tests {
// generate key event
giveEvent := tcell.NewEventKey(test.giveKey.Type, test.giveKey.Char, test.giveKey.Mods)
_screen.PostEventWait(giveEvent)
t.Logf("giveEvent = %T{key: %v, ch: %q (%[3]v), mod: %#04b}\n", giveEvent, giveEvent.Key(), giveEvent.Rune(), giveEvent.Modifiers())
// process the event in fzf and evaluate the test
gotEvent := r.GetChar()
// skip Resize events, those are sometimes put in the buffer outside of this test
if initialResizeAsInvalid && gotEvent.Type == Invalid {
t.Logf("Resize as Invalid swallowed")
initialResizeAsInvalid = false
gotEvent = r.GetChar()
}
t.Logf("wantEvent = %T{Type: %v, Char: %q (%[3]v)}\n", test.wantKey, test.wantKey.Type, test.wantKey.Char)
t.Logf("gotEvent = %T{Type: %v, Char: %q (%[3]v)}\n", gotEvent, gotEvent.Type, gotEvent.Char)
assert(t, "r.GetChar().Type", gotEvent.Type, test.wantKey.Type)
assert(t, "r.GetChar().Char", gotEvent.Char, test.wantKey.Char)
}
r.Close()
}
/*
Quick reference
---------------
(tabstop=18)
(this is not mapping table, it merely puts multiple constants ranges in one table)
¹) the two columns are each other implicit alias
²) explicit aliases here
%v section # tcell ctrl key¹ tcell ctrl char¹ tcell alias² tui constants tcell named keys tcell mods
-- --------- -------------- --------------- ----------- ------------- ---------------- ----------
0 2 KeyCtrlSpace KeyNUL = ^@ Rune ModNone
1 1 KeyCtrlA KeySOH = ^A CtrlA ModShift
2 1 KeyCtrlB KeySTX = ^B CtrlB ModCtrl
3 1 KeyCtrlC KeyETX = ^C CtrlC
4 1 KeyCtrlD KeyEOT = ^D CtrlD ModAlt
5 1 KeyCtrlE KeyENQ = ^E CtrlE
6 1 KeyCtrlF KeyACK = ^F CtrlF
7 1 KeyCtrlG KeyBEL = ^G CtrlG
8 1 KeyCtrlH KeyBS = ^H KeyBackspace CtrlH ModMeta
9 1 KeyCtrlI KeyTAB = ^I KeyTab Tab
10 1 KeyCtrlJ KeyLF = ^J CtrlJ
11 1 KeyCtrlK KeyVT = ^K CtrlK
12 1 KeyCtrlL KeyFF = ^L CtrlL
13 1 KeyCtrlM KeyCR = ^M KeyEnter CtrlM
14 1 KeyCtrlN KeySO = ^N CtrlN
15 1 KeyCtrlO KeySI = ^O CtrlO
16 1 KeyCtrlP KeyDLE = ^P CtrlP
17 1 KeyCtrlQ KeyDC1 = ^Q CtrlQ
18 1 KeyCtrlR KeyDC2 = ^R CtrlR
19 1 KeyCtrlS KeyDC3 = ^S CtrlS
20 1 KeyCtrlT KeyDC4 = ^T CtrlT
21 1 KeyCtrlU KeyNAK = ^U CtrlU
22 1 KeyCtrlV KeySYN = ^V CtrlV
23 1 KeyCtrlW KeyETB = ^W CtrlW
24 1 KeyCtrlX KeyCAN = ^X CtrlX
25 1 KeyCtrlY KeyEM = ^Y CtrlY
26 1 KeyCtrlZ KeySUB = ^Z CtrlZ
27 7 KeyCtrlLeftSq KeyESC = ^[ KeyEsc, KeyEscape ESC
28 2 KeyCtrlBackslash KeyFS = ^\ CtrlSpace
29 2 KeyCtrlRightSq KeyGS = ^] CtrlBackSlash
30 2 KeyCtrlCarat KeyRS = ^^ CtrlRightBracket
31 2 KeyCtrlUnderscore KeyUS = ^_ CtrlCaret
32 CtrlSlash
33 Invalid
34 Resize
35 Mouse
36 DoubleClick
37 LeftClick
38 RightClick
39 BTab
40 BSpace
41 Del
42 PgUp
43 PgDn
44 Up
45 Down
46 Left
47 Right
48 Home
49 End
50 Insert
51 SUp
52 SDown
53 SLeft
54 SRight
55 F1
56 F2
57 F3
58 F4
59 F5
60 F6
61 F7
62 F8
63 F9
64 F10
65 F11
66 F12
67 Change
68 BackwardEOF
69 AltBS
70 AltUp
71 AltDown
72 AltLeft
73 AltRight
74 AltSUp
75 AltSDown
76 AltSLeft
77 AltSRight
78 Alt
79 CtrlAlt
..
127 3 KeyDEL KeyBackspace2
..
256 6 KeyRune
257 4 KeyUp
258 4 KeyDown
259 4 KeyRight
260 4 KeyLeft
261 8 KeyUpLeft
262 8 KeyUpRight
263 8 KeyDownLeft
264 8 KeyDownRight
265 8 KeyCenter
266 5 KeyPgUp
267 5 KeyPgDn
268 5 KeyHome
269 5 KeyEnd
270 5 KeyInsert
271 5 KeyDelete
272 8 KeyHelp
273 8 KeyExit
274 8 KeyClear
275 8 KeyCancel
276 8 KeyPrint
277 8 KeyPause
278 5 KeyBacktab
279 5 KeyF1
280 5 KeyF2
281 5 KeyF3
282 5 KeyF4
283 5 KeyF5
284 5 KeyF6
285 5 KeyF7
286 5 KeyF8
287 5 KeyF9
288 5 KeyF10
289 5 KeyF11
290 5 KeyF12
291 8 KeyF13
292 8 KeyF14
293 8 KeyF15
294 8 KeyF16
295 8 KeyF17
296 8 KeyF18
297 8 KeyF19
298 8 KeyF20
299 8 KeyF21
300 8 KeyF22
301 8 KeyF23
302 8 KeyF24
303 8 KeyF25
304 8 KeyF26
305 8 KeyF27
306 8 KeyF28
307 8 KeyF29
308 8 KeyF30
309 8 KeyF31
310 8 KeyF32
311 8 KeyF33
312 8 KeyF34
313 8 KeyF35
314 8 KeyF36
315 8 KeyF37
316 8 KeyF38
317 8 KeyF39
318 8 KeyF40
319 8 KeyF41
320 8 KeyF42
321 8 KeyF43
322 8 KeyF44
323 8 KeyF45
324 8 KeyF46
325 8 KeyF47
326 8 KeyF48
327 8 KeyF49
328 8 KeyF50
329 8 KeyF51
330 8 KeyF52
331 8 KeyF53
332 8 KeyF54
333 8 KeyF55
334 8 KeyF56
335 8 KeyF57
336 8 KeyF58
337 8 KeyF59
338 8 KeyF60
339 8 KeyF61
340 8 KeyF62
341 8 KeyF63
342 8 KeyF64
-- --------- -------------- --------------- ----------- ------------- ---------------- ----------
%v section # tcell ctrl key tcell ctrl char tcell alias tui constants tcell named keys tcell mods
*/

View File

@@ -1,9 +1,9 @@
// +build !windows
//go:build !windows
package tui
import (
"io/ioutil"
"os"
"syscall"
)
@@ -16,16 +16,35 @@ func ttyname() string {
}
for _, prefix := range devPrefixes {
files, err := ioutil.ReadDir(prefix)
files, err := os.ReadDir(prefix)
if err != nil {
continue
}
for _, file := range files {
if stat, ok := file.Sys().(*syscall.Stat_t); ok && stat.Rdev == stderr.Rdev {
info, err := file.Info()
if err != nil {
continue
}
if stat, ok := info.Sys().(*syscall.Stat_t); ok && stat.Rdev == stderr.Rdev {
return prefix + file.Name()
}
}
}
return ""
}
// TtyIn returns terminal device to be used as STDIN, falls back to os.Stdin
func TtyIn() *os.File {
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
if err != nil {
tty := ttyname()
if len(tty) > 0 {
if in, err := os.OpenFile(tty, syscall.O_RDONLY, 0); err == nil {
return in
}
}
return os.Stdin
}
return in
}

View File

@@ -1,7 +1,14 @@
// +build windows
//go:build windows
package tui
import "os"
func ttyname() string {
return ""
}
// TtyIn on Windows returns os.Stdin
func TtyIn() *os.File {
return os.Stdin
}

View File

@@ -5,11 +5,15 @@ import (
"os"
"strconv"
"time"
"github.com/rivo/uniseg"
)
// Types of user action
type EventType int
const (
Rune = iota
Rune EventType = iota
CtrlA
CtrlB
@@ -39,6 +43,7 @@ const (
CtrlZ
ESC
CtrlSpace
CtrlDelete
// https://apple.stackexchange.com/questions/24261/how-do-i-send-c-that-is-control-slash-to-the-terminal
CtrlBackSlash
@@ -52,6 +57,14 @@ const (
DoubleClick
LeftClick
RightClick
SLeftClick
SRightClick
ScrollUp
ScrollDown
SScrollUp
SScrollDown
PreviewScrollUp
PreviewScrollDown
BTab
BSpace
@@ -72,6 +85,7 @@ const (
SDown
SLeft
SRight
SDelete
F1
F2
@@ -87,9 +101,14 @@ const (
F12
Change
BackwardEOF
Start
Load
Focus
One
Zero
Result
AltSpace
AltSlash
AltBS
AltUp
@@ -97,20 +116,43 @@ const (
AltLeft
AltRight
Alt0
AltSUp
AltSDown
AltSLeft
AltSRight
Alt
CtrlAlt
)
const ( // Reset iota
AltA = Alt0 + 'a' - '0' + iota
AltB
AltC
AltD
AltE
AltF
AltZ = AltA + 'z' - 'a'
CtrlAltA = AltZ + 1
CtrlAltM = CtrlAltA + 'm' - 'a'
)
func (t EventType) AsEvent() Event {
return Event{t, 0, nil}
}
func (t EventType) Int() int {
return int(t)
}
func (t EventType) Byte() byte {
return byte(t)
}
func (e Event) Comparable() Event {
// Ignore MouseEvent pointer
return Event{e.Type, e.Char, nil}
}
func Key(r rune) Event {
return Event{Rune, r, nil}
}
func AltKey(r rune) Event {
return Event{Alt, r, nil}
}
func CtrlAltKey(r rune) Event {
return Event{CtrlAlt, r, nil}
}
const (
doubleClickDuration = 500 * time.Millisecond
@@ -118,10 +160,23 @@ const (
type Color int32
func (c Color) IsDefault() bool {
return c == colDefault
}
func (c Color) is24() bool {
return c > 0 && (c&(1<<24)) > 0
}
type ColorAttr struct {
Color Color
Attr Attr
}
func NewColorAttr() ColorAttr {
return ColorAttr{Color: colUndefined, Attr: AttrUndefined}
}
const (
colUndefined Color = -2
colDefault Color = -1
@@ -147,9 +202,9 @@ const (
)
type ColorPair struct {
fg Color
bg Color
id int
fg Color
bg Color
attr Attr
}
func HexToColor(rrggbb string) Color {
@@ -159,8 +214,8 @@ func HexToColor(rrggbb string) Color {
return Color((1 << 24) + (r << 16) + (g << 8) + b)
}
func NewColorPair(fg Color, bg Color) ColorPair {
return ColorPair{fg, bg, -1}
func NewColorPair(fg Color, bg Color, attr Attr) ColorPair {
return ColorPair{fg, bg, attr}
}
func (p ColorPair) Fg() Color {
@@ -171,31 +226,88 @@ func (p ColorPair) Bg() Color {
return p.bg
}
func (p ColorPair) Attr() Attr {
return p.attr
}
func (p ColorPair) HasBg() bool {
return p.attr&Reverse == 0 && p.bg != colDefault ||
p.attr&Reverse > 0 && p.fg != colDefault
}
func (p ColorPair) merge(other ColorPair, except Color) ColorPair {
dup := p
dup.attr = dup.attr.Merge(other.attr)
if other.fg != except {
dup.fg = other.fg
}
if other.bg != except {
dup.bg = other.bg
}
return dup
}
func (p ColorPair) WithAttr(attr Attr) ColorPair {
dup := p
dup.attr = dup.attr.Merge(attr)
return dup
}
func (p ColorPair) MergeAttr(other ColorPair) ColorPair {
return p.WithAttr(other.attr)
}
func (p ColorPair) Merge(other ColorPair) ColorPair {
return p.merge(other, colUndefined)
}
func (p ColorPair) MergeNonDefault(other ColorPair) ColorPair {
return p.merge(other, colDefault)
}
type ColorTheme struct {
Fg Color
Bg Color
PreviewFg Color
PreviewBg Color
DarkBg Color
Gutter Color
Prompt Color
Match Color
Current Color
CurrentMatch Color
Spinner Color
Info Color
Cursor Color
Selected Color
Header Color
Border Color
Colored bool
Input ColorAttr
Disabled ColorAttr
Fg ColorAttr
Bg ColorAttr
PreviewFg ColorAttr
PreviewBg ColorAttr
DarkBg ColorAttr
Gutter ColorAttr
Prompt ColorAttr
Match ColorAttr
Current ColorAttr
CurrentMatch ColorAttr
Spinner ColorAttr
Info ColorAttr
Cursor ColorAttr
Selected ColorAttr
Header ColorAttr
Separator ColorAttr
Scrollbar ColorAttr
Border ColorAttr
PreviewBorder ColorAttr
PreviewScrollbar ColorAttr
BorderLabel ColorAttr
PreviewLabel ColorAttr
}
type Event struct {
Type int
Type EventType
Char rune
MouseEvent *MouseEvent
}
func (e Event) Is(types ...EventType) bool {
for _, t := range types {
if e.Type == t {
return true
}
}
return false
}
type MouseEvent struct {
Y int
X int
@@ -212,13 +324,40 @@ const (
BorderNone BorderShape = iota
BorderRounded
BorderSharp
BorderBold
BorderBlock
BorderThinBlock
BorderDouble
BorderHorizontal
BorderVertical
BorderTop
BorderBottom
BorderLeft
BorderRight
)
func (s BorderShape) HasRight() bool {
switch s {
case BorderNone, BorderLeft, BorderTop, BorderBottom, BorderHorizontal: // No right
return false
}
return true
}
func (s BorderShape) HasTop() bool {
switch s {
case BorderNone, BorderLeft, BorderRight, BorderBottom, BorderVertical: // No top
return false
}
return true
}
type BorderStyle struct {
shape BorderShape
horizontal rune
vertical rune
top rune
bottom rune
left rune
right rune
topLeft rune
topRight rune
bottomLeft rune
@@ -228,64 +367,142 @@ type BorderStyle struct {
type BorderCharacter int
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
if unicode {
if shape == BorderRounded {
return BorderStyle{
shape: shape,
horizontal: '─',
vertical: '│',
topLeft: '╭',
topRight: '╮',
bottomLeft: '╰',
bottomRight: '╯',
}
}
if !unicode {
return BorderStyle{
shape: shape,
horizontal: '',
vertical: '',
top: '-',
bottom: '-',
left: '|',
right: '|',
topLeft: '+',
topRight: '+',
bottomLeft: '+',
bottomRight: '+',
}
}
switch shape {
case BorderSharp:
return BorderStyle{
shape: shape,
top: '─',
bottom: '─',
left: '│',
right: '│',
topLeft: '┌',
topRight: '┐',
bottomLeft: '└',
bottomRight: '┘',
}
case BorderBold:
return BorderStyle{
shape: shape,
top: '━',
bottom: '━',
left: '┃',
right: '┃',
topLeft: '┏',
topRight: '┓',
bottomLeft: '┗',
bottomRight: '┛',
}
case BorderBlock:
// ▛▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▜
// ▌ ▐
// ▙▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▟
return BorderStyle{
shape: shape,
top: '▀',
bottom: '▄',
left: '▌',
right: '▐',
topLeft: '▛',
topRight: '▜',
bottomLeft: '▙',
bottomRight: '▟',
}
case BorderThinBlock:
// 🭽▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔🭾
// ▏ ▕
// 🭼▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁🭿
return BorderStyle{
shape: shape,
top: '▔',
bottom: '▁',
left: '▏',
right: '▕',
topLeft: '🭽',
topRight: '🭾',
bottomLeft: '🭼',
bottomRight: '🭿',
}
case BorderDouble:
return BorderStyle{
shape: shape,
top: '═',
bottom: '═',
left: '║',
right: '║',
topLeft: '╔',
topRight: '╗',
bottomLeft: '╚',
bottomRight: '╝',
}
}
return BorderStyle{
shape: shape,
horizontal: '-',
vertical: '|',
topLeft: '+',
topRight: '+',
bottomLeft: '+',
bottomRight: '+',
top: '',
bottom: '',
left: '',
right: '',
topLeft: '',
topRight: '',
bottomLeft: '╰',
bottomRight: '╯',
}
}
func MakeTransparentBorder() BorderStyle {
return BorderStyle{
shape: BorderRounded,
horizontal: ' ',
vertical: ' ',
top: ' ',
bottom: ' ',
left: ' ',
right: ' ',
topLeft: ' ',
topRight: ' ',
bottomLeft: ' ',
bottomRight: ' '}
}
type TermSize struct {
Lines int
Columns int
PxWidth int
PxHeight int
}
type Renderer interface {
Init()
Resize(maxHeightFunc func(int) int)
Pause(clear bool)
Resume(clear bool)
Resume(clear bool, sigcont bool)
Clear()
RefreshWindows(windows []Window)
Refresh()
Close()
PassThrough(string)
NeedScrollbarRedraw() bool
ShouldEmitResizeEvent() bool
GetChar() Event
Top() int
MaxX() int
MaxY() int
DoesAutoWrap() bool
Size() TermSize
NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window
}
@@ -296,6 +513,8 @@ type Window interface {
Width() int
Height() int
DrawBorder()
DrawHBorder()
Refresh()
FinishFill()
Close()
@@ -307,10 +526,11 @@ type Window interface {
Move(y int, x int)
MoveAndClear(y int, x int)
Print(text string)
CPrint(color ColorPair, attr Attr, text string)
CPrint(color ColorPair, text string)
Fill(text string) FillReturn
CFill(fg Color, bg Color, attr Attr, text string) FillReturn
Erase()
EraseMaybe() bool
}
type FullscreenRenderer struct {
@@ -318,7 +538,7 @@ type FullscreenRenderer struct {
mouse bool
forceBlack bool
prevDownTime time.Time
clickY []int
clicks [][2]int
}
func NewFullscreenRenderer(theme *ColorTheme, forceBlack bool, mouse bool) Renderer {
@@ -327,7 +547,7 @@ func NewFullscreenRenderer(theme *ColorTheme, forceBlack bool, mouse bool) Rende
mouse: mouse,
forceBlack: forceBlack,
prevDownTime: time.Unix(0, 0),
clickY: []int{}}
clicks: [][2]int{}}
return r
}
@@ -336,41 +556,92 @@ var (
Dark256 *ColorTheme
Light256 *ColorTheme
ColPrompt ColorPair
ColNormal ColorPair
ColMatch ColorPair
ColCursor ColorPair
ColSelected ColorPair
ColCurrent ColorPair
ColCurrentMatch ColorPair
ColCurrentCursor ColorPair
ColCurrentSelected ColorPair
ColSpinner ColorPair
ColInfo ColorPair
ColHeader ColorPair
ColBorder ColorPair
ColPreview ColorPair
ColPreviewBorder ColorPair
ColPrompt ColorPair
ColNormal ColorPair
ColInput ColorPair
ColDisabled ColorPair
ColMatch ColorPair
ColCursor ColorPair
ColCursorEmpty ColorPair
ColSelected ColorPair
ColCurrent ColorPair
ColCurrentMatch ColorPair
ColCurrentCursor ColorPair
ColCurrentCursorEmpty ColorPair
ColCurrentSelected ColorPair
ColCurrentSelectedEmpty ColorPair
ColSpinner ColorPair
ColInfo ColorPair
ColHeader ColorPair
ColSeparator ColorPair
ColScrollbar ColorPair
ColBorder ColorPair
ColPreview ColorPair
ColPreviewBorder ColorPair
ColBorderLabel ColorPair
ColPreviewLabel ColorPair
ColPreviewScrollbar ColorPair
ColPreviewSpinner ColorPair
)
func EmptyTheme() *ColorTheme {
return &ColorTheme{
Fg: colUndefined,
Bg: colUndefined,
PreviewFg: colUndefined,
PreviewBg: colUndefined,
DarkBg: colUndefined,
Gutter: colUndefined,
Prompt: colUndefined,
Match: colUndefined,
Current: colUndefined,
CurrentMatch: colUndefined,
Spinner: colUndefined,
Info: colUndefined,
Cursor: colUndefined,
Selected: colUndefined,
Header: colUndefined,
Border: colUndefined}
Colored: true,
Input: ColorAttr{colUndefined, AttrUndefined},
Fg: ColorAttr{colUndefined, AttrUndefined},
Bg: ColorAttr{colUndefined, AttrUndefined},
DarkBg: ColorAttr{colUndefined, AttrUndefined},
Prompt: ColorAttr{colUndefined, AttrUndefined},
Match: ColorAttr{colUndefined, AttrUndefined},
Current: ColorAttr{colUndefined, AttrUndefined},
CurrentMatch: ColorAttr{colUndefined, AttrUndefined},
Spinner: ColorAttr{colUndefined, AttrUndefined},
Info: ColorAttr{colUndefined, AttrUndefined},
Cursor: ColorAttr{colUndefined, AttrUndefined},
Selected: ColorAttr{colUndefined, AttrUndefined},
Header: ColorAttr{colUndefined, AttrUndefined},
Border: ColorAttr{colUndefined, AttrUndefined},
BorderLabel: ColorAttr{colUndefined, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
Gutter: ColorAttr{colUndefined, AttrUndefined},
PreviewBorder: ColorAttr{colUndefined, AttrUndefined},
PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined},
PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
Separator: ColorAttr{colUndefined, AttrUndefined},
Scrollbar: ColorAttr{colUndefined, AttrUndefined},
}
}
func NoColorTheme() *ColorTheme {
return &ColorTheme{
Colored: false,
Input: ColorAttr{colDefault, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined},
DarkBg: ColorAttr{colDefault, AttrUndefined},
Prompt: ColorAttr{colDefault, AttrUndefined},
Match: ColorAttr{colDefault, Underline},
Current: ColorAttr{colDefault, Reverse},
CurrentMatch: ColorAttr{colDefault, Reverse | Underline},
Spinner: ColorAttr{colDefault, AttrUndefined},
Info: ColorAttr{colDefault, AttrUndefined},
Cursor: ColorAttr{colDefault, AttrUndefined},
Selected: ColorAttr{colDefault, AttrUndefined},
Header: ColorAttr{colDefault, AttrUndefined},
Border: ColorAttr{colDefault, AttrUndefined},
BorderLabel: ColorAttr{colDefault, AttrUndefined},
Disabled: ColorAttr{colDefault, AttrUndefined},
PreviewFg: ColorAttr{colDefault, AttrUndefined},
PreviewBg: ColorAttr{colDefault, AttrUndefined},
Gutter: ColorAttr{colDefault, AttrUndefined},
PreviewBorder: ColorAttr{colDefault, AttrUndefined},
PreviewScrollbar: ColorAttr{colDefault, AttrUndefined},
PreviewLabel: ColorAttr{colDefault, AttrUndefined},
Separator: ColorAttr{colDefault, AttrUndefined},
Scrollbar: ColorAttr{colDefault, AttrUndefined},
}
}
func errorExit(message string) {
@@ -380,80 +651,107 @@ func errorExit(message string) {
func init() {
Default16 = &ColorTheme{
Fg: colDefault,
Bg: colDefault,
PreviewFg: colUndefined,
PreviewBg: colUndefined,
DarkBg: colBlack,
Gutter: colUndefined,
Prompt: colBlue,
Match: colGreen,
Current: colYellow,
CurrentMatch: colGreen,
Spinner: colGreen,
Info: colWhite,
Cursor: colRed,
Selected: colMagenta,
Header: colCyan,
Border: colBlack}
Colored: true,
Input: ColorAttr{colDefault, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined},
DarkBg: ColorAttr{colBlack, AttrUndefined},
Prompt: ColorAttr{colBlue, AttrUndefined},
Match: ColorAttr{colGreen, AttrUndefined},
Current: ColorAttr{colYellow, AttrUndefined},
CurrentMatch: ColorAttr{colGreen, AttrUndefined},
Spinner: ColorAttr{colGreen, AttrUndefined},
Info: ColorAttr{colWhite, AttrUndefined},
Cursor: ColorAttr{colRed, AttrUndefined},
Selected: ColorAttr{colMagenta, AttrUndefined},
Header: ColorAttr{colCyan, AttrUndefined},
Border: ColorAttr{colBlack, AttrUndefined},
BorderLabel: ColorAttr{colWhite, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
Gutter: ColorAttr{colUndefined, AttrUndefined},
PreviewBorder: ColorAttr{colUndefined, AttrUndefined},
PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined},
PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
Separator: ColorAttr{colUndefined, AttrUndefined},
Scrollbar: ColorAttr{colUndefined, AttrUndefined},
}
Dark256 = &ColorTheme{
Fg: colDefault,
Bg: colDefault,
PreviewFg: colUndefined,
PreviewBg: colUndefined,
DarkBg: 236,
Gutter: colUndefined,
Prompt: 110,
Match: 108,
Current: 254,
CurrentMatch: 151,
Spinner: 148,
Info: 144,
Cursor: 161,
Selected: 168,
Header: 109,
Border: 59}
Colored: true,
Input: ColorAttr{colDefault, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined},
DarkBg: ColorAttr{236, AttrUndefined},
Prompt: ColorAttr{110, AttrUndefined},
Match: ColorAttr{108, AttrUndefined},
Current: ColorAttr{254, AttrUndefined},
CurrentMatch: ColorAttr{151, AttrUndefined},
Spinner: ColorAttr{148, AttrUndefined},
Info: ColorAttr{144, AttrUndefined},
Cursor: ColorAttr{161, AttrUndefined},
Selected: ColorAttr{168, AttrUndefined},
Header: ColorAttr{109, AttrUndefined},
Border: ColorAttr{59, AttrUndefined},
BorderLabel: ColorAttr{145, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
Gutter: ColorAttr{colUndefined, AttrUndefined},
PreviewBorder: ColorAttr{colUndefined, AttrUndefined},
PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined},
PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
Separator: ColorAttr{colUndefined, AttrUndefined},
Scrollbar: ColorAttr{colUndefined, AttrUndefined},
}
Light256 = &ColorTheme{
Fg: colDefault,
Bg: colDefault,
PreviewFg: colUndefined,
PreviewBg: colUndefined,
DarkBg: 251,
Gutter: colUndefined,
Prompt: 25,
Match: 66,
Current: 237,
CurrentMatch: 23,
Spinner: 65,
Info: 101,
Cursor: 161,
Selected: 168,
Header: 31,
Border: 145}
Colored: true,
Input: ColorAttr{colDefault, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined},
DarkBg: ColorAttr{251, AttrUndefined},
Prompt: ColorAttr{25, AttrUndefined},
Match: ColorAttr{66, AttrUndefined},
Current: ColorAttr{237, AttrUndefined},
CurrentMatch: ColorAttr{23, AttrUndefined},
Spinner: ColorAttr{65, AttrUndefined},
Info: ColorAttr{101, AttrUndefined},
Cursor: ColorAttr{161, AttrUndefined},
Selected: ColorAttr{168, AttrUndefined},
Header: ColorAttr{31, AttrUndefined},
Border: ColorAttr{145, AttrUndefined},
BorderLabel: ColorAttr{59, AttrUndefined},
Disabled: ColorAttr{colUndefined, AttrUndefined},
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
Gutter: ColorAttr{colUndefined, AttrUndefined},
PreviewBorder: ColorAttr{colUndefined, AttrUndefined},
PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined},
PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
Separator: ColorAttr{colUndefined, AttrUndefined},
Scrollbar: ColorAttr{colUndefined, AttrUndefined},
}
}
func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
if theme == nil {
initPalette(theme)
return
}
if forceBlack {
theme.Bg = colBlack
theme.Bg = ColorAttr{colBlack, AttrUndefined}
}
o := func(a Color, b Color) Color {
if b == colUndefined {
return a
o := func(a ColorAttr, b ColorAttr) ColorAttr {
c := a
if b.Color != colUndefined {
c.Color = b.Color
}
return b
if b.Attr != AttrUndefined {
c.Attr = b.Attr
}
return c
}
theme.Input = o(baseTheme.Input, theme.Input)
theme.Fg = o(baseTheme.Fg, theme.Fg)
theme.Bg = o(baseTheme.Bg, theme.Bg)
theme.PreviewFg = o(theme.Fg, o(baseTheme.PreviewFg, theme.PreviewFg))
theme.PreviewBg = o(theme.Bg, o(baseTheme.PreviewBg, theme.PreviewBg))
theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg)
theme.Gutter = o(theme.DarkBg, o(baseTheme.Gutter, theme.Gutter))
theme.Prompt = o(baseTheme.Prompt, theme.Prompt)
theme.Match = o(baseTheme.Match, theme.Match)
theme.Current = o(baseTheme.Current, theme.Current)
@@ -464,59 +762,60 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
theme.Selected = o(baseTheme.Selected, theme.Selected)
theme.Header = o(baseTheme.Header, theme.Header)
theme.Border = o(baseTheme.Border, theme.Border)
theme.BorderLabel = o(baseTheme.BorderLabel, theme.BorderLabel)
// These colors are not defined in the base themes
theme.Disabled = o(theme.Input, theme.Disabled)
theme.Gutter = o(theme.DarkBg, theme.Gutter)
theme.PreviewFg = o(theme.Fg, theme.PreviewFg)
theme.PreviewBg = o(theme.Bg, theme.PreviewBg)
theme.PreviewLabel = o(theme.BorderLabel, theme.PreviewLabel)
theme.PreviewBorder = o(theme.Border, theme.PreviewBorder)
theme.Separator = o(theme.Border, theme.Separator)
theme.Scrollbar = o(theme.Border, theme.Scrollbar)
theme.PreviewScrollbar = o(theme.PreviewBorder, theme.PreviewScrollbar)
initPalette(theme)
}
func initPalette(theme *ColorTheme) {
idx := 0
pair := func(fg, bg Color) ColorPair {
idx++
return ColorPair{fg, bg, idx}
}
if theme != nil {
ColPrompt = pair(theme.Prompt, theme.Bg)
ColNormal = pair(theme.Fg, theme.Bg)
ColMatch = pair(theme.Match, theme.Bg)
ColCursor = pair(theme.Cursor, theme.Gutter)
ColSelected = pair(theme.Selected, theme.Gutter)
ColCurrent = pair(theme.Current, theme.DarkBg)
ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg)
ColCurrentCursor = pair(theme.Cursor, theme.DarkBg)
ColCurrentSelected = pair(theme.Selected, theme.DarkBg)
ColSpinner = pair(theme.Spinner, theme.Bg)
ColInfo = pair(theme.Info, theme.Bg)
ColHeader = pair(theme.Header, theme.Bg)
ColBorder = pair(theme.Border, theme.Bg)
ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
ColPreviewBorder = pair(theme.Border, theme.PreviewBg)
} else {
ColPrompt = pair(colDefault, colDefault)
ColNormal = pair(colDefault, colDefault)
ColMatch = pair(colDefault, colDefault)
ColCursor = pair(colDefault, colDefault)
ColSelected = pair(colDefault, colDefault)
ColCurrent = pair(colDefault, colDefault)
ColCurrentMatch = pair(colDefault, colDefault)
ColCurrentCursor = pair(colDefault, colDefault)
ColCurrentSelected = pair(colDefault, colDefault)
ColSpinner = pair(colDefault, colDefault)
ColInfo = pair(colDefault, colDefault)
ColHeader = pair(colDefault, colDefault)
ColBorder = pair(colDefault, colDefault)
ColPreview = pair(colDefault, colDefault)
ColPreviewBorder = pair(colDefault, colDefault)
pair := func(fg, bg ColorAttr) ColorPair {
if fg.Color == colDefault && (fg.Attr&Reverse) > 0 {
bg.Color = colDefault
}
return ColorPair{fg.Color, bg.Color, fg.Attr}
}
blank := theme.Fg
blank.Attr = AttrRegular
ColPrompt = pair(theme.Prompt, theme.Bg)
ColNormal = pair(theme.Fg, theme.Bg)
ColInput = pair(theme.Input, theme.Bg)
ColDisabled = pair(theme.Disabled, theme.Bg)
ColMatch = pair(theme.Match, theme.Bg)
ColCursor = pair(theme.Cursor, theme.Gutter)
ColCursorEmpty = pair(blank, theme.Gutter)
ColSelected = pair(theme.Selected, theme.Gutter)
ColCurrent = pair(theme.Current, theme.DarkBg)
ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg)
ColCurrentCursor = pair(theme.Cursor, theme.DarkBg)
ColCurrentCursorEmpty = pair(blank, theme.DarkBg)
ColCurrentSelected = pair(theme.Selected, theme.DarkBg)
ColCurrentSelectedEmpty = pair(blank, theme.DarkBg)
ColSpinner = pair(theme.Spinner, theme.Bg)
ColInfo = pair(theme.Info, theme.Bg)
ColHeader = pair(theme.Header, theme.Bg)
ColSeparator = pair(theme.Separator, theme.Bg)
ColScrollbar = pair(theme.Scrollbar, theme.Bg)
ColBorder = pair(theme.Border, theme.Bg)
ColBorderLabel = pair(theme.BorderLabel, theme.Bg)
ColPreviewLabel = pair(theme.PreviewLabel, theme.PreviewBg)
ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
ColPreviewBorder = pair(theme.PreviewBorder, theme.PreviewBg)
ColPreviewScrollbar = pair(theme.PreviewScrollbar, theme.PreviewBg)
ColPreviewSpinner = pair(theme.Spinner, theme.PreviewBg)
}
func attrFor(color ColorPair, attr Attr) Attr {
switch color {
case ColCurrent:
return attr | Reverse
case ColMatch:
return attr | Underline
case ColCurrentMatch:
return attr | Underline | Reverse
}
return attr
func runeWidth(r rune) int {
return uniseg.StringWidth(string(r))
}

View File

@@ -1,45 +0,0 @@
#!/usr/bin/env ruby
# http://www.rubydoc.info/github/rest-client/rest-client/RestClient
require 'rest_client'
require 'json'
if ARGV.length < 3
puts "usage: #$0 <token> <version> <files...>"
exit 1
end
token, version, *files = ARGV
base = "https://api.github.com/repos/junegunn/fzf-bin/releases"
# List releases
rels = JSON.parse(RestClient.get(base, :authorization => "token #{token}"))
rel = rels.find { |r| r['tag_name'] == version }
unless rel
puts "#{version} not found"
exit 1
end
# List assets
assets = Hash[rel['assets'].map { |a| a.values_at *%w[name id] }]
files.select { |f| File.exists? f }.map do |file|
Thread.new do
name = File.basename file
if asset_id = assets[name]
puts "#{name} found. Deleting asset id #{asset_id}."
RestClient.delete "#{base}/assets/#{asset_id}",
:authorization => "token #{token}"
else
puts "#{name} not found"
end
puts "Uploading #{name}"
RestClient.post(
"#{base.sub 'api', 'uploads'}/#{rel['id']}/assets?name=#{name}",
File.read(file),
:authorization => "token #{token}",
:content_type => "application/octet-stream")
end
end.each(&:join)

View File

@@ -1,32 +1,34 @@
package util
import "sync"
import (
"sync/atomic"
)
func convertBoolToInt32(b bool) int32 {
if b {
return 1
}
return 0
}
// AtomicBool is a boxed-class that provides synchronized access to the
// underlying boolean value
type AtomicBool struct {
mutex sync.Mutex
state bool
state int32 // "1" is true, "0" is false
}
// NewAtomicBool returns a new AtomicBool
func NewAtomicBool(initialState bool) *AtomicBool {
return &AtomicBool{
mutex: sync.Mutex{},
state: initialState}
return &AtomicBool{state: convertBoolToInt32(initialState)}
}
// Get returns the current boolean value synchronously
func (a *AtomicBool) Get() bool {
a.mutex.Lock()
defer a.mutex.Unlock()
return a.state
return atomic.LoadInt32(&a.state) == 1
}
// Set updates the boolean value synchronously
func (a *AtomicBool) Set(newState bool) bool {
a.mutex.Lock()
defer a.mutex.Unlock()
a.state = newState
return a.state
atomic.StoreInt32(&a.state, convertBoolToInt32(newState))
return newState
}

View File

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

View File

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

View File

@@ -3,26 +3,55 @@ package util
import (
"math"
"os"
"strings"
"time"
"github.com/mattn/go-isatty"
"github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
)
var _runeWidths = make(map[rune]int)
// StringWidth returns string width where each CR/LF character takes 1 column
func StringWidth(s string) int {
return uniseg.StringWidth(s) + strings.Count(s, "\n") + strings.Count(s, "\r")
}
// RuneWidth returns rune width
func RuneWidth(r rune, prefixWidth int, tabstop int) int {
if r == '\t' {
return tabstop - prefixWidth%tabstop
} else if w, found := _runeWidths[r]; found {
return w
} else if r == '\n' || r == '\r' {
return 1
// RunesWidth returns runes width
func RunesWidth(runes []rune, prefixWidth int, tabstop int, limit int) (int, int) {
width := 0
gr := uniseg.NewGraphemes(string(runes))
idx := 0
for gr.Next() {
rs := gr.Runes()
var w int
if len(rs) == 1 && rs[0] == '\t' {
w = tabstop - (prefixWidth+width)%tabstop
} else {
w = StringWidth(string(rs))
}
width += w
if width > limit {
return width, idx
}
idx += len(rs)
}
w := runewidth.RuneWidth(r)
_runeWidths[r] = w
return w
return width, -1
}
// Truncate returns the truncated runes and its width
func Truncate(input string, limit int) ([]rune, int) {
runes := []rune{}
width := 0
gr := uniseg.NewGraphemes(input)
for gr.Next() {
rs := gr.Runes()
w := StringWidth(string(rs))
if width+w > limit {
return runes, width
}
width += w
runes = append(runes, rs...)
}
return runes, width
}
// Max returns the largest integer
@@ -108,11 +137,16 @@ func DurWithin(
return val
}
// IsTty returns true is stdin is a terminal
// IsTty returns true if stdin is a terminal
func IsTty() bool {
return isatty.IsTerminal(os.Stdin.Fd())
}
// ToTty returns true if stdout is a terminal
func ToTty() bool {
return isatty.IsTerminal(os.Stdout.Fd())
}
// Once returns a function that returns the specified boolean value only once
func Once(nextResponse bool) func() bool {
state := nextResponse
@@ -122,3 +156,23 @@ func Once(nextResponse bool) func() bool {
return prevState
}
}
// RepeatToFill repeats the given string to fill the given width
func RepeatToFill(str string, length int, limit int) string {
times := limit / length
rest := limit % length
output := strings.Repeat(str, times)
if rest > 0 {
for _, r := range str {
rest -= uniseg.StringWidth(string(r))
if rest < 0 {
break
}
output += string(r)
if rest == 0 {
break
}
}
}
return output
}

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
// +build windows
//go:build windows
package util
@@ -6,23 +6,57 @@ import (
"fmt"
"os"
"os/exec"
"strings"
"sync/atomic"
"syscall"
)
// ExecCommand executes the given command with cmd
var shellPath atomic.Value
// ExecCommand executes the given command with $SHELL
func ExecCommand(command string, setpgid bool) *exec.Cmd {
return ExecCommandWith("cmd", command, setpgid)
var shell string
if cached := shellPath.Load(); cached != nil {
shell = cached.(string)
} else {
shell = os.Getenv("SHELL")
if len(shell) == 0 {
shell = "cmd"
} else if strings.Contains(shell, "/") {
out, err := exec.Command("cygpath", "-w", shell).Output()
if err == nil {
shell = strings.Trim(string(out), "\n")
}
}
shellPath.Store(shell)
}
return ExecCommandWith(shell, command, setpgid)
}
// ExecCommandWith executes the given command with cmd. _shell parameter is
// ignored on Windows.
// ExecCommandWith executes the given command with the specified shell
// FIXME: setpgid is unused. We set it in the Unix implementation so that we
// can kill preview process with its child processes at once.
func ExecCommandWith(_shell string, command string, setpgid bool) *exec.Cmd {
cmd := exec.Command("cmd")
// NOTE: For "powershell", we should ideally set output encoding to UTF8,
// but it is left as is now because no adverse effect has been observed.
func ExecCommandWith(shell string, command string, setpgid bool) *exec.Cmd {
var cmd *exec.Cmd
if strings.Contains(shell, "cmd") {
cmd = exec.Command(shell)
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: false,
CmdLine: fmt.Sprintf(` /v:on/s/c "%s"`, command),
CreationFlags: 0,
}
return cmd
}
if strings.Contains(shell, "pwsh") || strings.Contains(shell, "powershell") {
cmd = exec.Command(shell, "-NoProfile", "-Command", command)
} else {
cmd = exec.Command(shell, "-c", command)
}
cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: false,
CmdLine: fmt.Sprintf(` /v:on/s/c "%s"`, command),
CreationFlags: 0,
}
return cmd
@@ -47,3 +81,7 @@ func SetNonblock(file *os.File, nonblock bool) {
func Read(fd int, b []byte) (int, error) {
return syscall.Read(syscall.Handle(fd), b)
}
func SetStdin(file *os.File) {
// No-op
}

File diff suppressed because it is too large Load Diff

10
typos.toml Normal file
View File

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

View File

@@ -51,13 +51,8 @@ remove() {
}
remove_line() {
src=$(readlink "$1")
if [ $? -eq 0 ]; then
echo "Remove from $1 ($src):"
else
src=$1
echo "Remove from $1:"
fi
src=$1
echo "Remove from $1:"
shift
line_no=1
@@ -75,8 +70,9 @@ remove_line() {
echo " - Line #$line_no: $content"
[ "$content" = "$1" ] || ask " - Remove?"
if [ $? -eq 0 ]; then
awk -v n=$line_no 'NR == n {next} {print}' "$src" > "$src.bak" &&
mv "$src.bak" "$src" || break
temp=$(mktemp)
awk -v n=$line_no 'NR == n {next} {print}' "$src" > "$temp" &&
cat "$temp" > "$src" && rm -f "$temp" || break
echo " - Removed"
else
echo " - Skipped"
@@ -98,16 +94,17 @@ done
bind_file="${fish_dir}/functions/fish_user_key_bindings.fish"
if [ -f "$bind_file" ]; then
remove_line "$bind_file" "fzf_key_bindings"
remove_line "$bind_file" "fzf --fish | source"
fi
if [ -d "${fish_dir}/functions" ]; then
remove "${fish_dir}/functions/fzf.fish"
remove "${fish_dir}/functions/fzf_key_bindings.fish"
if [ "$(ls -A "${fish_dir}/functions")" ]; then
echo "Can't delete non-empty directory: \"${fish_dir}/functions\""
else
if [ -z "$(ls -A "${fish_dir}/functions")" ]; then
rmdir "${fish_dir}/functions"
else
echo "Can't delete non-empty directory: \"${fish_dir}/functions\""
fi
fi