m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-16 15:23:48 -05:00

Compare commits

..

28 Commits

Author SHA1 Message Date
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
34 changed files with 629 additions and 261 deletions

View File

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

View File

@@ -15,17 +15,17 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2
- uses: actions/checkout@61b9e3751b92087fd0b06925ba6dd6314e06f089
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@f6164bd8c8acb4a71fb2791a8b6c4024ff038dab # v2
uses: actions/setup-go@268d8c0ca0432bb2cf416faae41297df9d262d7f # v2
with:
go-version: 1.18
go-version: 1.19
- name: Setup Ruby
uses: ruby/setup-ruby@ebaea52cb20fea395b0904125276395e37183dac
uses: ruby/setup-ruby@3068fa83f9cbd7ae106cac45483635a2f3a195c9
with:
ruby-version: 3.0.0

View File

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

View File

@@ -1,7 +1,7 @@
Advanced fzf examples
======================
*(Last update: 2021/05/22)*
*(Last update: 2022/08/25)*
<!-- vim-markdown-toc GFM -->
@@ -496,9 +496,17 @@ pods() {
Key bindings for git objects
----------------------------
I have [blogged](https://junegunn.kr/2016/07/fzf-git) about my fzf+git key
bindings a few years ago. I'm going to show them here again, because they are
seriously useful.
Oftentimes, you want to put the identifiers of various Git object to the
command-line. For example, it is common to write commands like these:
```sh
git checkout [SOME_COMMIT_HASH or BRANCH or TAG]
git diff [SOME_COMMIT_HASH or BRANCH or TAG] [SOME_COMMIT_HASH or BRANCH or TAG]
```
[fzf-git.sh](https://github.com/junegunn/fzf-git.sh) project defines a set of
fzf-based key bindings for Git objects. I strongly recommend that you check
them out because they are seriously useful.
### Files listed in `git status`
@@ -518,9 +526,6 @@ seriously useful.
![image](https://user-images.githubusercontent.com/700826/113473765-91692080-94a6-11eb-8d38-ed4d41f27ac1.png)
The full source code can be found [here](https://gist.github.com/junegunn/8b572b8d4b5eddd8b85e5f4d40f17236).
Color themes
------------

View File

@@ -1,6 +1,62 @@
CHANGELOG
=========
0.34.0
------
- Added support for adaptive `--height`. If the `--height` value is prefixed
with `~`, fzf will automatically determine the height in the range according
to the input size.
```sh
seq 1 | fzf --height ~70% --border --padding 1 --margin 1
seq 10 | fzf --height ~70% --border --padding 1 --margin 1
seq 100 | fzf --height ~70% --border --padding 1 --margin 1
```
- There are a few limitations
- Not compatible with percent top/bottom margin/padding
```sh
# This is not allowed (top/bottom margin in percent value)
fzf --height ~50% --border --margin 5%,10%
# This is allowed (top/bottom margin in fixed value)
fzf --height ~50% --border --margin 2,10%
```
- fzf will not start until it can determine the right height for the input
```sh
# fzf will open immediately
(sleep 2; seq 10) | fzf --height 50%
# fzf will open after 2 seconds
(sleep 2; seq 10) | fzf --height ~50%
(sleep 2; seq 1000) | fzf --height ~50%
```
- Fixed tcell renderer used to render full-screen fzf on Windows
- `--no-clear` is deprecated. Use `reload` action instead.
0.33.0
------
- Added `--scheme=[default|path|history]` option to choose scoring scheme
- (Experimental)
- We updated the scoring algorithm in 0.32.0, however we have learned that
this new scheme (`default`) is not always giving the optimal result
- `path`: Additional bonus point is only given to the characters after
path separator. You might want to choose this scheme if you have many
files with spaces in their paths.
- `history`: No additional bonus points are given so that we give more
weight to the chronological ordering. This is equivalent to the scoring
scheme before 0.32.0. This also sets `--tiebreak=index`.
- ANSI color sequences with colon delimiters are now supported.
```sh
printf "\e[38;5;208mOption 1\e[m\nOption 2" | fzf --ansi
printf "\e[38:5:208mOption 1\e[m\nOption 2" | fzf --ansi
```
- Support `border-{up,down}` as the synonyms for `border-{top,bottom}` in
`--preview-window`
- Added support for ANSI `strikethrough`
```sh
printf "\e[9mdeleted" | fzf --ansi
fzf --color fg+:strikethrough
```
0.32.1
------
- Fixed incorrect ordering of `--tiebreak=chunk`

View File

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

View File

@@ -155,6 +155,7 @@ target/$(BINARYLOONG64): $(SOURCES)
GOARCH=loong64 $(GO) build $(BUILD_FLAGS) -o $@
bin/fzf: target/$(BINARY) | bin
-rm -f bin/fzf
cp -f target/$(BINARY) bin/fzf
docker:

View File

@@ -627,10 +627,7 @@ fzf --height 40% --layout reverse --info inline --border \
See the man page (`man fzf`) for the full list of options.
For more advanced examples, see [Key bindings for git with fzf][fzf-git]
([code](https://gist.github.com/junegunn/8b572b8d4b5eddd8b85e5f4d40f17236)).
[fzf-git]: https://junegunn.kr/2016/07/fzf-git/
More advanced examples can be found [here](https://github.com/junegunn/fzf/blob/master/ADVANCED.md).
----

14
go.mod
View File

@@ -1,20 +1,20 @@
module github.com/junegunn/fzf
require (
github.com/gdamore/tcell v1.4.0
github.com/mattn/go-isatty v0.0.14
github.com/mattn/go-runewidth v0.0.13
github.com/gdamore/tcell/v2 v2.5.3
github.com/mattn/go-isatty v0.0.16
github.com/mattn/go-runewidth v0.0.14
github.com/mattn/go-shellwords v1.0.12
github.com/rivo/uniseg v0.2.0
github.com/saracen/walker v0.1.2
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12
github.com/rivo/uniseg v0.4.2
github.com/saracen/walker v0.1.3
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
)
require (
github.com/gdamore/encoding v1.0.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect
golang.org/x/text v0.3.7 // indirect
)

34
go.sum
View File

@@ -1,29 +1,29 @@
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU=
github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/gdamore/tcell/v2 v2.5.3 h1:b9XQrT6QGbgI7JvZOJXFNczOQeIYbo8BfeSMzt2sAV0=
github.com/gdamore/tcell/v2 v2.5.3/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/saracen/walker v0.1.2 h1:/o1TxP82n8thLvmL4GpJXduYaRmJ7qXp8u9dSlV0zmo=
github.com/saracen/walker v0.1.2/go.mod h1:0oKYMsKVhSJ+ful4p/XbjvXbMgLEkLITZaxozsl4CGE=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/saracen/walker v0.1.3 h1:YtcKKmpRPy6XJTHJ75J2QYXXZYWnZNQxPCVqZSHVV/g=
github.com/saracen/walker v0.1.3/go.mod h1:FU+7qU8DeQQgSZDmmThMJi93kPkLFgy0oVAcLxurjIk=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12 h1:QyVthZKMsyaQwBTJE04jdNN0Pp5Fn9Qga0mrgxyERQM=
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@@ -2,7 +2,7 @@
set -u
version=0.32.1
version=0.34.0
auto_completion=
key_bindings=
update_config=2

View File

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

View File

@@ -5,7 +5,7 @@ import (
"github.com/junegunn/fzf/src/protector"
)
var version string = "0.32"
var version string = "0.34"
var revision string = "devel"
func main() {

View File

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
..
.TH fzf-tmux 1 "Aug 2022" "fzf 0.32.1" "fzf-tmux - open fzf in tmux split pane"
.TH fzf-tmux 1 "Sep 2022" "fzf 0.34.0" "fzf-tmux - open fzf in tmux split pane"
.SH NAME
fzf-tmux - open fzf in tmux split pane

View File

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
..
.TH fzf 1 "Aug 2022" "fzf 0.32.1" "fzf - a command-line fuzzy finder"
.TH fzf 1 "Sep 2022" "fzf 0.34.0" "fzf - a command-line fuzzy finder"
.SH NAME
fzf - a command-line fuzzy finder
@@ -51,6 +51,18 @@ Case-sensitive match
.B "--literal"
Do not normalize latin script letters for matching.
.TP
.BI "--scheme=" SCHEME
Choose scoring scheme tailored for different types of input.
.br
.BR default " Generic scoring scheme designed to work well with any type of input"
.br
.BR path " Scoring scheme for paths (additional bonus point only after path separator)
.br
.BR history " Scoring scheme for command history (no additional bonus points).
Sets \fB--tiebreak=index\fR as well.
.br
.TP
.BI "--algo=" TYPE
Fuzzy matching algorithm (default: v2)
@@ -165,9 +177,11 @@ actions are affected:
Label characters for \fBjump\fR and \fBjump-accept\fR
.SS Layout
.TP
.BI "--height=" "HEIGHT[%]"
.BI "--height=" "[~]HEIGHT[%]"
Display fzf window below the cursor with the given height instead of using
the full screen.
the full screen. When prefixed with \fB~\fR, fzf will automatically determine
the height in the range according to the input size. Note that adaptive height
is not compatible with top/bottom margin and padding given in percent size.
.TP
.BI "--min-height=" "HEIGHT"
Minimum height when \fB--height\fR is given in percent (default: 10).
@@ -189,21 +203,21 @@ Choose the layout (default: default)
A synonym for \fB--layout=reverse\fB
.TP
.BI "--border" [=STYLE]
.BI "--border" [=BORDER_OPT]
Draw border around the finder
.br
.BR rounded " Border with rounded corners (default)"
.BR rounded " Border with rounded corners (default)"
.br
.BR sharp " Border with sharp corners"
.BR sharp " Border with sharp corners"
.br
.BR horizontal " Horizontal lines above and below the finder"
.BR horizontal " Horizontal lines above and below the finder"
.br
.BR vertical " Vertical lines on each side of the finder"
.BR vertical " Vertical lines on each side of the finder"
.br
.BR top
.BR top " (up)"
.br
.BR bottom
.BR bottom " (down)"
.br
.BR left
.br
@@ -378,6 +392,7 @@ color mappings.
\fBreverse\fR
\fBdim\fR
\fBitalic\fR
\fBstrikethrough\fR
.B EXAMPLES:
@@ -603,12 +618,6 @@ Read input delimited by ASCII NUL characters instead of newline characters
.B "--print0"
Print output delimited by ASCII NUL characters instead of newline characters
.TP
.B "--no-clear"
Do not clear finder interface on exit. If fzf was started in full screen mode,
it will not switch back to the original screen, so you'll have to manually run
\fBtput rmcup\fR to return. This option can be used to avoid flickering of the
screen when your application needs to start fzf multiple times in order.
.TP
.B "--sync"
Synchronous search for multi-staged filtering. If specified, fzf will launch
ncurses finder only after the input stream is complete.

View File

@@ -181,7 +181,7 @@ __fzf_generic_path_completion() {
[[ -z "$dir" ]] && dir='.'
[[ "$dir" != "/" ]] && dir="${dir/%\//}"
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
printf "%q$3 " "$item"
printf "%q " "${item%$3}$3"
done)
matches=${matches% }
[[ -z "$3" ]] && [[ "$__fzf_nospace_commands" = *" ${COMP_WORDS[0]} "* ]] && matches="$matches "
@@ -288,13 +288,13 @@ _fzf_host_completion() {
_fzf_var_completion() {
_fzf_complete -m -- "$@" < <(
declare -xp | sed 's/=.*//' | sed 's/.* //'
declare -xp | sed -En 's|^declare [^ ]+ ([^=]+).*|\1|p'
)
}
_fzf_alias_completion() {
_fzf_complete -m -- "$@" < <(
alias | sed 's/=.*//' | sed 's/.* //'
alias | sed -En 's|^alias ([^=]+).*|\1|p'
)
}

View File

@@ -146,7 +146,8 @@ __fzf_generic_path_completion() {
[ -z "$dir" ] && dir='.'
[ "$dir" != "/" ] && dir="${dir/%\//}"
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
echo -n "${(q)item}$suffix "
item="${item%$suffix}$suffix"
echo -n "${(q)item} "
done)
matches=${matches% }
if [ -n "$matches" ]; then

View File

@@ -50,7 +50,7 @@ __fzf_cd__() {
__fzf_history__() {
local output opts script
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m --read0"
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m --read0"
script='BEGIN { getc; $/ = "\n\t"; $HISTCOUNT = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCOUNT - $. . "\t$_" if !$seen{$_}++'
output=$(
builtin fc -lnr -2147483648 |

View File

@@ -53,7 +53,7 @@ function fzf_key_bindings
function fzf-history-widget -d "Show command history"
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS +m"
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --scheme=history --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS +m"
set -l FISH_MAJOR (echo $version | cut -f1 -d.)
set -l FISH_MINOR (echo $version | cut -f2 -d.)
@@ -87,7 +87,7 @@ function fzf_key_bindings
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
if [ -n "$result" ]
builtin cd -- $result
cd -- $result
# Remove last token from commandline.
commandline -t ""

View File

@@ -98,7 +98,7 @@ fzf-history-widget() {
local selected num
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
selected=( $(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
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]

View File

@@ -80,6 +80,7 @@ Scoring criteria
import (
"bytes"
"fmt"
"os"
"strings"
"unicode"
"unicode/utf8"
@@ -89,7 +90,8 @@ import (
var DEBUG bool
const delimiterChars = "/,:;|"
var delimiterChars = "/,:;|"
const whiteChars = " \t\n\v\f\r\x85\xA0"
func indexAt(index int, max int, forward bool) int {
@@ -120,12 +122,6 @@ const (
// in web2 dictionary and my file system.
bonusBoundary = scoreMatch / 2
// Extra bonus for word boundary after whitespace character or beginning of the string
bonusBoundaryWhite = bonusBoundary + 2
// Extra bonus for word boundary after slash, colon, semi-colon, and comma
bonusBoundaryDelimiter = bonusBoundary + 1
// Although bonus point for non-word characters is non-contextual, we need it
// for computing bonus points for consecutive chunks starting with a non-word
// character.
@@ -149,6 +145,16 @@ const (
bonusFirstCharMultiplier = 2
)
var (
// Extra bonus for word boundary after whitespace character or beginning of the string
bonusBoundaryWhite int16 = bonusBoundary + 2
// Extra bonus for word boundary after slash, colon, semi-colon, and comma
bonusBoundaryDelimiter int16 = bonusBoundary + 1
initialCharClass charClass = charWhite
)
type charClass int
const (
@@ -161,6 +167,29 @@ const (
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)
@@ -407,7 +436,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), charWhite, 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 {
@@ -910,8 +939,8 @@ func EqualMatch(caseSensitive bool, normalize bool, forward bool, text *util.Cha
match = runesStr == string(pattern)
}
if match {
return Result{trimmedLen, trimmedLen + lenPattern, (scoreMatch+bonusBoundaryWhite)*lenPattern +
(bonusFirstCharMultiplier-1)*bonusBoundaryWhite}, nil
return Result{trimmedLen, trimmedLen + lenPattern, (scoreMatch+int(bonusBoundaryWhite))*lenPattern +
(bonusFirstCharMultiplier-1)*int(bonusBoundaryWhite)}, nil
}
return Result{-1, -1, 0}, nil
}

View File

@@ -45,29 +45,29 @@ func TestFuzzyMatch(t *testing.T) {
assertMatch(t, fn, false, forward, "fooBarbaz1", "oBZ", 2, 9,
scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtension*3)
assertMatch(t, fn, false, forward, "foo bar baz", "fbb", 0, 9,
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+
bonusBoundaryWhite*2+2*scoreGapStart+4*scoreGapExtension)
scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+
int(bonusBoundaryWhite)*2+2*scoreGapStart+4*scoreGapExtension)
assertMatch(t, fn, false, forward, "/AutomatorDocument.icns", "rdoc", 9, 13,
scoreMatch*4+bonusCamel123+bonusConsecutive*2)
assertMatch(t, fn, false, forward, "/man1/zshcompctl.1", "zshc", 6, 10,
scoreMatch*4+bonusBoundaryDelimiter*bonusFirstCharMultiplier+bonusBoundaryDelimiter*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*2+scoreGapStart+bonusBoundaryDelimiter)
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*2+scoreGapStart+int(bonusBoundaryDelimiter))
assertMatch(t, fn, false, forward, "ab0123 456", "12356", 3, 10,
scoreMatch*5+bonusConsecutive*3+scoreGapStart+scoreGapExtension)
assertMatch(t, fn, false, forward, "abc123 456", "12356", 3, 10,
scoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+bonusConsecutive+scoreGapStart+scoreGapExtension)
assertMatch(t, fn, false, forward, "foo/bar/baz", "fbb", 0, 9,
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+
bonusBoundaryDelimiter*2+2*scoreGapStart+4*scoreGapExtension)
scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+
int(bonusBoundaryDelimiter)*2+2*scoreGapStart+4*scoreGapExtension)
assertMatch(t, fn, false, forward, "fooBarBaz", "fbb", 0, 7,
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+
scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+
bonusCamel123*2+2*scoreGapStart+2*scoreGapExtension)
assertMatch(t, fn, false, forward, "foo barbaz", "fbb", 0, 8,
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryWhite+
scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryWhite)+
scoreGapStart*2+scoreGapExtension*3)
assertMatch(t, fn, false, forward, "fooBar Baz", "foob", 0, 4,
scoreMatch*4+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryWhite*3)
scoreMatch*4+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryWhite)*3)
assertMatch(t, fn, false, forward, "xFoo-Bar Baz", "foo-b", 1, 6,
scoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+
bonusNonWord+bonusBoundary)
@@ -75,14 +75,14 @@ func TestFuzzyMatch(t *testing.T) {
assertMatch(t, fn, true, forward, "fooBarbaz", "oBz", 2, 9,
scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtension*3)
assertMatch(t, fn, true, forward, "Foo/Bar/Baz", "FBB", 0, 9,
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryDelimiter*2+
scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryDelimiter)*2+
scoreGapStart*2+scoreGapExtension*4)
assertMatch(t, fn, true, forward, "FooBarBaz", "FBB", 0, 7,
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusCamel123*2+
scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+bonusCamel123*2+
scoreGapStart*2+scoreGapExtension*2)
assertMatch(t, fn, true, forward, "FooBar Baz", "FooB", 0, 4,
scoreMatch*4+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryWhite*2+
util.Max(bonusCamel123, bonusBoundaryWhite))
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+bonusBoundaryWhite*bonusFirstCharMultiplier+
scoreMatch*2+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+
scoreGapStart+scoreGapExtension)
assertMatch(t, FuzzyMatchV1, false, false, "foobar fb", "fb", 7, 9,
scoreMatch*2+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryWhite)
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+bonusBoundaryDelimiter*(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+3)+bonusBoundaryDelimiter)
scoreMatch*5+bonusBoundary*(bonusFirstCharMultiplier+3)+int(bonusBoundaryDelimiter))
}
}
@@ -128,7 +128,7 @@ func TestExactMatchNaiveBackward(t *testing.T) {
}
func TestPrefixMatch(t *testing.T) {
score := scoreMatch*3 + bonusBoundaryWhite*bonusFirstCharMultiplier + bonusBoundaryWhite*2
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)
@@ -159,7 +159,7 @@ func TestSuffixMatch(t *testing.T) {
// Only when the pattern doesn't end with a space
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz ", "baz ", 6, 10,
scoreMatch*4+bonusConsecutive*2+bonusBoundaryWhite)
scoreMatch*4+bonusConsecutive*2+int(bonusBoundaryWhite))
}
}

View File

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

View File

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

View File

@@ -1,28 +1,4 @@
/*
Package fzf implements fzf, a command-line fuzzy finder.
The MIT License (MIT)
Copyright (c) 2013-2021 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// Package fzf implements fzf, a command-line fuzzy finder.
package fzf
import (
@@ -218,10 +194,17 @@ func Run(opts *Options, version string, revision string) {
// Terminal I/O
terminal := NewTerminal(opts, eventBox)
maxFit := 0 // Maximum number of items that can fit on screen
padHeight := 0
heightUnknown := opts.Height.auto
if heightUnknown {
maxFit, padHeight = terminal.MaxFitAndPad(opts)
}
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
@@ -240,7 +223,19 @@ func Run(opts *Options, version string, revision string) {
go reader.restart(command)
}
eventBox.Watch(EvtReadNew)
total := 0
query := []rune{}
determine := func(final bool) {
if heightUnknown {
if total >= maxFit || final {
heightUnknown = false
terminal.startChan <- fitpad{util.Min(total, maxFit), padHeight}
}
} else if deferred {
deferred = false
terminal.startChan <- fitpad{-1, -1}
}
}
for {
delay := true
ticks++
@@ -273,11 +268,15 @@ func Run(opts *Options, version string, revision string) {
reading = reading && evt == EvtReadNew
}
snapshot, count := chunkList.Snapshot()
terminal.UpdateCount(count, !reading, value.(*string))
total = count
terminal.UpdateCount(total, !reading, value.(*string))
if opts.Sync {
opts.Sync = false
terminal.UpdateList(PassMerger(&snapshot, opts.Tac), false)
}
if heightUnknown && !deferred {
determine(!reading)
}
reset := clearCache()
matcher.Reset(snapshot, input(reset), false, !reading, sort, reset)
@@ -319,8 +318,7 @@ func Run(opts *Options, version string, revision string) {
if deferred {
count := val.Length()
if opts.Select1 && count > 1 || opts.Exit0 && !opts.Select1 && count > 0 {
deferred = false
terminal.startChan <- true
determine(val.final)
} else if val.final {
if opts.Exit0 && count == 0 || opts.Select1 && count == 1 {
if opts.PrintQuery {
@@ -337,8 +335,7 @@ func Run(opts *Options, version string, revision string) {
}
os.Exit(exitNoMatch)
}
deferred = false
terminal.startChan <- true
determine(val.final)
}
}
terminal.UpdateList(val, clearSelection())

View File

@@ -21,9 +21,9 @@ const usage = `usage: fzf [options]
-x, --extended Extended-search mode
(enabled by default; +x or --no-extended to disable)
-e, --exact Enable Exact-match
--algo=TYPE Fuzzy matching algorithm: [v1|v2] (default: v2)
-i Case-insensitive match (default: smart-case match)
+i Case-sensitive match
--scheme=SCHEME Scoring scheme [default|path|history]
--literal Do not normalize latin script letters before matching
-n, --nth=N[,..] Comma-separated list of field index expressions
for limiting search scope. Each can be a non-zero
@@ -53,8 +53,10 @@ const usage = `usage: fzf [options]
--jump-labels=CHARS Label characters for jump and jump-accept
Layout
--height=HEIGHT[%] Display fzf window below the cursor with the given
height instead of using fullscreen
--height=[~]HEIGHT[%] Display fzf window below the cursor with the given
height instead of using fullscreen.
If prefixed with '~', fzf will determine the height
according to the input size.
--min-height=HEIGHT Minimum height when --height is given in percent
(default: 10)
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
@@ -131,6 +133,12 @@ const (
byEnd
)
type heightSpec struct {
size float64
percent bool
auto bool
}
type sizeSpec struct {
size float64
percent bool
@@ -180,6 +188,10 @@ type previewOpts struct {
alternative *previewOpts
}
func (a previewOpts) aboveOrBelow() bool {
return a.size.size > 0 && (a.position == posUp || a.position == posDown)
}
func (a previewOpts) sameLayout(b previewOpts) bool {
return a.size == b.size && a.position == b.position && a.border == b.border && a.hidden == b.hidden && a.threshold == b.threshold &&
(a.alternative != nil && b.alternative != nil && a.alternative.sameLayout(*b.alternative) ||
@@ -194,6 +206,7 @@ func (a previewOpts) sameContentLayout(b previewOpts) bool {
type Options struct {
Fuzzy bool
FuzzyAlgo algo.Algo
Scheme string
Extended bool
Phony bool
Case Case
@@ -210,7 +223,7 @@ type Options struct {
Theme *tui.ColorTheme
Black bool
Bold bool
Height sizeSpec
Height heightSpec
MinHeight int
Layout layoutType
Cycle bool
@@ -259,6 +272,7 @@ func defaultOptions() *Options {
return &Options{
Fuzzy: true,
FuzzyAlgo: algo.FuzzyMatchV2,
Scheme: "default",
Extended: true,
Phony: false,
Case: CaseSmart,
@@ -441,6 +455,15 @@ func parseAlgo(str string) algo.Algo {
return algo.FuzzyMatchV2
}
func processScheme(opts *Options) {
if !algo.Init(opts.Scheme) {
errorExit("invalid scoring scheme (expected: default|path|history)")
}
if opts.Scheme == "history" {
opts.Criteria = []criterion{byScore}
}
}
func parseBorder(str string, optional bool) tui.BorderShape {
switch str {
case "rounded":
@@ -700,6 +723,8 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
cattr.Attr |= tui.Blink
case "reverse":
cattr.Attr |= tui.Reverse
case "strikethrough":
cattr.Attr |= tui.StrikeThrough
case "black":
cattr.Color = tui.Color(0)
case "red":
@@ -1063,11 +1088,6 @@ func parseKeymap(keymap map[tui.Event][]*action, str string) {
}
if t == actUnbind || t == actRebind {
parseKeyChords(actionArg, spec[0:offset]+" target required")
} else if t == actChangePreviewWindow {
opts := previewOpts{}
for _, arg := range strings.Split(actionArg, "|") {
parsePreviewWindow(&opts, arg)
}
}
}
}
@@ -1147,9 +1167,17 @@ func parseSize(str string, maxPercent float64, label string) sizeSpec {
return sizeSpec{val, percent}
}
func parseHeight(str string) sizeSpec {
func parseHeight(str string) heightSpec {
heightSpec := heightSpec{}
if strings.HasPrefix(str, "~") {
heightSpec.auto = true
str = str[1:]
}
size := parseSize(str, 100, "height")
return size
heightSpec.size = size.size
heightSpec.percent = size.percent
return heightSpec
}
func parseLayout(str string) layoutType {
@@ -1228,9 +1256,9 @@ func parsePreviewWindow(opts *previewOpts, input string) {
opts.border = tui.BorderHorizontal
case "border-vertical":
opts.border = tui.BorderVertical
case "border-top":
case "border-up", "border-top":
opts.border = tui.BorderTop
case "border-bottom":
case "border-down", "border-bottom":
opts.border = tui.BorderBottom
case "border-left":
opts.border = tui.BorderLeft
@@ -1343,6 +1371,8 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Normalize = true
case "--algo":
opts.FuzzyAlgo = parseAlgo(nextString(allArgs, &i, "algorithm required (v1|v2)"))
case "--scheme":
opts.Scheme = strings.ToLower(nextString(allArgs, &i, "scoring scheme required (default|path|history)"))
case "--expect":
for k, v := range parseKeyChords(nextString(allArgs, &i, "key names required"), "key names required") {
opts.Expect[k] = v
@@ -1510,11 +1540,11 @@ func parseOptions(opts *Options, allArgs []string) {
parsePreviewWindow(&opts.Preview,
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][,SIZE[%]][,border-BORDER_OPT][,wrap][,cycle][,hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default]"))
case "--height":
opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]"))
opts.Height = parseHeight(nextString(allArgs, &i, "height required: [~]HEIGHT[%]"))
case "--min-height":
opts.MinHeight = nextInt(allArgs, &i, "height required: HEIGHT")
case "--no-height":
opts.Height = sizeSpec{}
opts.Height = heightSpec{}
case "--no-margin":
opts.Margin = defaultMargin()
case "--no-padding":
@@ -1549,6 +1579,8 @@ func parseOptions(opts *Options, allArgs []string) {
default:
if match, value := optString(arg, "--algo="); match {
opts.FuzzyAlgo = parseAlgo(value)
} else if match, value := optString(arg, "--scheme="); match {
opts.Scheme = strings.ToLower(value)
} else if match, value := optString(arg, "-q", "--query="); match {
opts.Query = value
} else if match, value := optString(arg, "-f", "--filter="); match {
@@ -1692,6 +1724,7 @@ func postProcessOptions(opts *Options) {
}
// Extend the default key map
previewEnabled := len(opts.Preview.command) > 0 || hasPreviewAction(opts)
keymap := defaultKeymap()
for key, actions := range opts.Keymap {
var lastChangePreviewWindow *action
@@ -1702,8 +1735,18 @@ func postProcessOptions(opts *Options) {
opts.ToggleSort = true
case actChangePreviewWindow:
lastChangePreviewWindow = act
if !previewEnabled {
// Doesn't matter
continue
}
opts := previewOpts{}
for _, arg := range strings.Split(act.a, "|") {
// Make sure that each expression is valid
parsePreviewWindow(&opts, arg)
}
}
}
// Re-organize actions so that we only keep the last change-preview-window
// and it comes first in the list.
// * change-preview-window(up,+10)+preview(sleep 3; cat {})+change-preview-window(up,+20)
@@ -1721,6 +1764,19 @@ func postProcessOptions(opts *Options) {
}
opts.Keymap = keymap
if opts.Height.auto {
for _, s := range []sizeSpec{opts.Margin[0], opts.Margin[2]} {
if s.percent {
errorExit("adaptive height is not compatible with top/bottom percent margin")
}
}
for _, s := range []sizeSpec{opts.Padding[0], opts.Padding[2]} {
if s.percent {
errorExit("adaptive height is not compatible with top/bottom percent padding")
}
}
}
// If we're not using extended search mode, --nth option becomes irrelevant
// if it contains the whole range
if !opts.Extended || len(opts.Nth) == 1 {
@@ -1750,6 +1806,10 @@ func postProcessOptions(opts *Options) {
theme.Cursor = boldify(theme.Cursor)
theme.Spinner = boldify(theme.Spinner)
}
if opts.Scheme != "default" {
processScheme(opts)
}
}
func expectsArbitraryString(opt string) bool {

View File

@@ -25,14 +25,14 @@ import (
// import "github.com/pkg/profile"
/*
Placeholder regex is used to extract placeholders from fzf's template
strings. Acts as input validation for parsePlaceholder function.
Describes the syntax, but it is fairly lenient.
Placeholder regex is used to extract placeholders from fzf's template
strings. Acts as input validation for parsePlaceholder function.
Describes the syntax, but it is fairly lenient.
The following pseudo regex has been reverse engineered from the
implementation. It is overly strict, but better describes whats possible.
As such it is not useful for validation, but rather to generate test
cases for example.
The following pseudo regex has been reverse engineered from the
implementation. It is overly strict, but better describes whats possible.
As such it is not useful for validation, but rather to generate test
cases for example.
\\?(?: # escaped type
{\+?s?f?RANGE(?:,RANGE)*} # token type
@@ -100,6 +100,11 @@ type itemLine struct {
result Result
}
type fitpad struct {
fit int
pad int
}
var emptyLine = itemLine{}
// Terminal represents terminal input/output
@@ -183,7 +188,7 @@ type Terminal struct {
prevLines []itemLine
suppress bool
sigstop bool
startChan chan bool
startChan chan fitpad
killChan chan int
slab *util.Slab
theme *tui.ColorTheme
@@ -439,6 +444,13 @@ func makeSpinner(unicode bool) []string {
return []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
}
func evaluateHeight(opts *Options, termHeight int) int {
if opts.Height.percent {
return util.Max(int(opts.Height.size*float64(termHeight)/100.0), opts.MinHeight)
}
return int(opts.Height.size)
}
// NewTerminal returns new Terminal object
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
input := trimQuery(opts.Query)
@@ -465,7 +477,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
strongAttr = tui.AttrRegular
}
var renderer tui.Renderer
fullscreen := opts.Height.size == 0 || opts.Height.percent && opts.Height.size == 100
fullscreen := !opts.Height.auto && (opts.Height.size == 0 || opts.Height.percent && opts.Height.size == 100)
if fullscreen {
if tui.HasFullscreenRenderer() {
renderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse)
@@ -475,24 +487,16 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
}
} else {
maxHeightFunc := func(termHeight int) int {
var maxHeight int
if opts.Height.percent {
maxHeight = util.Max(int(opts.Height.size*float64(termHeight)/100.0), opts.MinHeight)
} else {
maxHeight = int(opts.Height.size)
}
// Minimum height required to render fzf excluding margin and padding
effectiveMinHeight := minHeight
if previewBox != nil && (opts.Preview.position == posUp || opts.Preview.position == posDown) {
effectiveMinHeight *= 2
if previewBox != nil && opts.Preview.aboveOrBelow() {
effectiveMinHeight += 1 + borderLines(opts.Preview.border)
}
if opts.InfoStyle != infoDefault {
effectiveMinHeight--
}
if opts.BorderShape != tui.BorderNone {
effectiveMinHeight += 2
}
return util.Min(termHeight, util.Max(maxHeight, effectiveMinHeight))
effectiveMinHeight += borderLines(opts.BorderShape)
return util.Min(termHeight, util.Max(evaluateHeight(opts, termHeight), effectiveMinHeight))
}
renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, false, maxHeightFunc)
}
@@ -572,7 +576,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
sigstop: false,
slab: util.MakeSlab(slab16Size, slab32Size),
theme: opts.Theme,
startChan: make(chan bool, 1),
startChan: make(chan fitpad, 1),
killChan: make(chan int),
tui: renderer,
initFunc: func() { renderer.Init() },
@@ -587,6 +591,32 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
return &t
}
func borderLines(shape tui.BorderShape) int {
switch shape {
case tui.BorderHorizontal, tui.BorderRounded, tui.BorderSharp:
return 2
case tui.BorderTop, tui.BorderBottom:
return 1
}
return 0
}
// Extra number of lines needed to display fzf
func (t *Terminal) extraLines() int {
extra := len(t.header0) + t.headerLines + 1
if !t.noInfoLine() {
extra++
}
return extra
}
func (t *Terminal) MaxFitAndPad(opts *Options) (int, int) {
_, screenHeight, marginInt, paddingInt := t.adjustMarginAndPadding()
padHeight := marginInt[0] + marginInt[2] + paddingInt[0] + paddingInt[2]
fit := screenHeight - padHeight - t.extraLines()
return fit, padHeight
}
func (t *Terminal) parsePrompt(prompt string) (func(), int) {
var state *ansiState
trimmed, colors, _ := extractColor(prompt, state, nil)
@@ -725,22 +755,23 @@ func (t *Terminal) displayWidth(runes []rune) int {
const (
minWidth = 4
minHeight = 4
minHeight = 3
)
func calculateSize(base int, size sizeSpec, occupied int, minSize int, pad int) int {
max := base - occupied
if max < minSize {
max = minSize
}
if size.percent {
return util.Constrain(int(float64(base)*0.01*size.size), minSize, max)
}
return util.Constrain(int(size.size)+pad, minSize, max)
}
func (t *Terminal) resizeWindows() {
func (t *Terminal) adjustMarginAndPadding() (int, int, [4]int, [4]int) {
screenWidth := t.tui.MaxX()
screenHeight := t.tui.MaxY()
t.prevLines = make([]itemLine, screenHeight)
marginInt := [4]int{} // TRBL
paddingInt := [4]int{} // TRBL
sizeSpecToInt := func(index int, spec sizeSpec) int {
@@ -789,31 +820,48 @@ func (t *Terminal) resizeWindows() {
}
adjust := func(idx1 int, idx2 int, max int, min int) {
if max >= min {
margin := marginInt[idx1] + marginInt[idx2] + paddingInt[idx1] + paddingInt[idx2]
if max-margin < min {
desired := max - min
paddingInt[idx1] = desired * paddingInt[idx1] / margin
paddingInt[idx2] = desired * paddingInt[idx2] / margin
marginInt[idx1] = util.Max(extraMargin[idx1], desired*marginInt[idx1]/margin)
marginInt[idx2] = util.Max(extraMargin[idx2], desired*marginInt[idx2]/margin)
}
if min > max {
min = max
}
margin := marginInt[idx1] + marginInt[idx2] + paddingInt[idx1] + paddingInt[idx2]
if max-margin < min {
desired := max - min
paddingInt[idx1] = desired * paddingInt[idx1] / margin
paddingInt[idx2] = desired * paddingInt[idx2] / margin
marginInt[idx1] = util.Max(extraMargin[idx1], desired*marginInt[idx1]/margin)
marginInt[idx2] = util.Max(extraMargin[idx2], desired*marginInt[idx2]/margin)
}
}
previewVisible := t.isPreviewEnabled() && t.previewOpts.size.size > 0
minAreaWidth := minWidth
minAreaHeight := minHeight
if previewVisible {
if t.noInfoLine() {
minAreaHeight -= 1
}
if t.isPreviewVisible() {
minPreviewHeight := 1 + borderLines(t.previewOpts.border)
minPreviewWidth := 5
switch t.previewOpts.position {
case posUp, posDown:
minAreaHeight *= 2
minAreaHeight += minPreviewHeight
minAreaWidth = util.Max(minPreviewWidth, minAreaWidth)
case posLeft, posRight:
minAreaWidth *= 2
minAreaWidth += minPreviewWidth
minAreaHeight = util.Max(minPreviewHeight, minAreaHeight)
}
}
adjust(1, 3, screenWidth, minAreaWidth)
adjust(0, 2, screenHeight, minAreaHeight)
return screenWidth, screenHeight, marginInt, paddingInt
}
func (t *Terminal) resizeWindows() {
screenWidth, screenHeight, marginInt, paddingInt := t.adjustMarginAndPadding()
width := screenWidth - marginInt[1] - marginInt[3]
height := screenHeight - marginInt[0] - marginInt[2]
t.prevLines = make([]itemLine, screenHeight)
if t.border != nil {
t.border.Close()
}
@@ -832,8 +880,6 @@ func (t *Terminal) resizeWindows() {
// Reset preview version so that full redraw occurs
t.previewed.version = 0
width := screenWidth - marginInt[1] - marginInt[3]
height := screenHeight - marginInt[0] - marginInt[2]
switch t.borderShape {
case tui.BorderHorizontal:
t.border = t.tui.NewWindow(
@@ -865,16 +911,16 @@ func (t *Terminal) resizeWindows() {
false, tui.MakeBorderStyle(t.borderShape, t.unicode))
}
// Add padding
// Add padding to margin
for idx, val := range paddingInt {
marginInt[idx] += val
}
width = screenWidth - marginInt[1] - marginInt[3]
height = screenHeight - marginInt[0] - marginInt[2]
width -= paddingInt[1] + paddingInt[3]
height -= paddingInt[0] + paddingInt[2]
// Set up preview window
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
if previewVisible {
if t.isPreviewVisible() {
var resizePreviewWindows func(previewOpts previewOpts)
resizePreviewWindows = func(previewOpts previewOpts) {
hasThreshold := previewOpts.threshold > 0 && previewOpts.alternative != nil
@@ -1863,6 +1909,10 @@ func (t *Terminal) isPreviewEnabled() bool {
return t.hasPreviewer() && t.previewer.enabled
}
func (t *Terminal) isPreviewVisible() bool {
return t.isPreviewEnabled() && t.previewOpts.size.size > 0
}
func (t *Terminal) hasPreviewWindow() bool {
return t.pwindow != nil && t.isPreviewEnabled()
}
@@ -1962,7 +2012,28 @@ func (t *Terminal) cancelPreview() {
// Loop is called to start Terminal I/O
func (t *Terminal) Loop() {
// prof := profile.Start(profile.ProfilePath("/tmp/"))
<-t.startChan
fitpad := <-t.startChan
fit := fitpad.fit
if fit >= 0 {
pad := fitpad.pad
t.tui.Resize(func(termHeight int) int {
contentHeight := fit + t.extraLines()
if t.hasPreviewer() {
if t.previewOpts.aboveOrBelow() {
if t.previewOpts.size.percent {
newContentHeight := int(float64(contentHeight) * 100. / (100. - t.previewOpts.size.size))
contentHeight = util.Max(contentHeight+1+borderLines(t.previewOpts.border), newContentHeight)
} else {
contentHeight += int(t.previewOpts.size.size) + borderLines(t.previewOpts.border)
}
} else {
// Minimum height if preview window can appear
contentHeight = util.Max(contentHeight, 1+borderLines(t.previewOpts.border))
}
}
return util.Min(termHeight, contentHeight+pad)
})
}
{ // Late initialization
intChan := make(chan os.Signal, 1)
signal.Notify(intChan, os.Interrupt, syscall.SIGTERM)

View File

@@ -421,10 +421,10 @@ func TestPowershellCommands(t *testing.T) {
}
/*
Test typical valid placeholders and parsing of them.
Test typical valid placeholders and parsing of them.
Also since the parser assumes the input is matched with `placeholder` regex,
the regex is tested here as well.
Also since the parser assumes the input is matched with `placeholder` regex,
the regex is tested here as well.
*/
func TestParsePlaceholder(t *testing.T) {
// give, want pairs

View File

@@ -14,24 +14,26 @@ func (a Attr) Merge(b Attr) Attr {
const (
AttrUndefined = Attr(0)
AttrRegular = Attr(1 << 7)
AttrClear = Attr(1 << 8)
AttrRegular = Attr(1 << 8)
AttrClear = Attr(1 << 9)
Bold = Attr(1)
Dim = Attr(1 << 1)
Italic = Attr(1 << 2)
Underline = Attr(1 << 3)
Blink = Attr(1 << 4)
Blink2 = Attr(1 << 5)
Reverse = Attr(1 << 6)
Bold = Attr(1)
Dim = Attr(1 << 1)
Italic = Attr(1 << 2)
Underline = Attr(1 << 3)
Blink = Attr(1 << 4)
Blink2 = Attr(1 << 5)
Reverse = Attr(1 << 6)
StrikeThrough = Attr(1 << 7)
)
func (r *FullscreenRenderer) Init() {}
func (r *FullscreenRenderer) Pause(bool) {}
func (r *FullscreenRenderer) Resume(bool, bool) {}
func (r *FullscreenRenderer) Clear() {}
func (r *FullscreenRenderer) Refresh() {}
func (r *FullscreenRenderer) Close() {}
func (r *FullscreenRenderer) Init() {}
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
func (r *FullscreenRenderer) Pause(bool) {}
func (r *FullscreenRenderer) Resume(bool, bool) {}
func (r *FullscreenRenderer) Clear() {}
func (r *FullscreenRenderer) Refresh() {}
func (r *FullscreenRenderer) Close() {}
func (r *FullscreenRenderer) GetChar() Event { return Event{} }
func (r *FullscreenRenderer) MaxX() int { return 0 }

View File

@@ -189,6 +189,10 @@ func (r *LightRenderer) Init() {
}
}
func (r *LightRenderer) Resize(maxHeightFunc func(int) int) {
r.maxHeightFunc = maxHeightFunc
}
func (r *LightRenderer) makeSpace() {
r.stderr("\n")
r.csi("G")
@@ -676,6 +680,9 @@ func (r *LightRenderer) MaxX() int {
}
func (r *LightRenderer) MaxY() int {
if r.height == 0 {
r.updateTerminalSize()
}
return r.height
}
@@ -856,6 +863,9 @@ func attrCodes(attr Attr) []string {
if (attr & Reverse) > 0 {
codes = append(codes, "7")
}
if (attr & StrikeThrough) > 0 {
codes = append(codes, "9")
}
return codes
}

View File

@@ -8,8 +8,8 @@ import (
"runtime"
"github.com/gdamore/tcell"
"github.com/gdamore/tcell/encoding"
"github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/v2/encoding"
"github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
@@ -19,12 +19,24 @@ 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()))
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
@@ -72,12 +84,13 @@ func (w *TcellWindow) FinishFill() {
}
const (
Bold Attr = Attr(tcell.AttrBold)
Dim = Attr(tcell.AttrDim)
Blink = Attr(tcell.AttrBlink)
Reverse = Attr(tcell.AttrReverse)
Underline = Attr(tcell.AttrUnderline)
Italic = Attr(tcell.AttrItalic)
Bold Attr = Attr(tcell.AttrBold)
Dim = Attr(tcell.AttrDim)
Blink = Attr(tcell.AttrBlink)
Reverse = Attr(tcell.AttrReverse)
Underline = Attr(tcell.AttrUnderline)
StrikeThrough = Attr(tcell.AttrStrikeThrough)
Italic = Attr(tcell.AttrItalic)
)
const (
@@ -86,6 +99,8 @@ const (
AttrClear = Attr(1 << 8)
)
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
if _screen.Colors() >= 256 {
return Dark256
@@ -561,6 +576,7 @@ func (w *TcellWindow) printString(text string, pair ColorPair) {
style = style.
Reverse(a&Attr(tcell.AttrReverse) != 0).
Underline(a&Attr(tcell.AttrUnderline) != 0).
StrikeThrough(a&Attr(tcell.AttrStrikeThrough) != 0).
Italic(a&Attr(tcell.AttrItalic) != 0).
Blink(a&Attr(tcell.AttrBlink) != 0).
Dim(a&Attr(tcell.AttrDim) != 0)
@@ -612,6 +628,7 @@ func (w *TcellWindow) fillString(text string, pair ColorPair) FillReturn {
Dim(a&Attr(tcell.AttrDim) != 0).
Reverse(a&Attr(tcell.AttrReverse) != 0).
Underline(a&Attr(tcell.AttrUnderline) != 0).
StrikeThrough(a&Attr(tcell.AttrStrikeThrough) != 0).
Italic(a&Attr(tcell.AttrItalic) != 0)
gr := uniseg.NewGraphemes(text)

View File

@@ -5,7 +5,7 @@ package tui
import (
"testing"
"github.com/gdamore/tcell"
"github.com/gdamore/tcell/v2"
"github.com/junegunn/fzf/src/util"
)

View File

@@ -358,6 +358,7 @@ func MakeTransparentBorder() BorderStyle {
type Renderer interface {
Init()
Resize(maxHeightFunc func(int) int)
Pause(clear bool)
Resume(clear bool, sigcont bool)
Clear()

View File

@@ -2245,6 +2245,93 @@ class TestGoFZF < TestBase
tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.until { |lines| assert_match(/^> SNIPSNIP.*SNIPSNIP$/, lines[-3]) }
end
def assert_block(expected, lines)
cols = expected.lines.map(&:chomp).map(&:length).max
actual = lines.reverse.take(expected.lines.length).reverse.map { _1[0, cols].rstrip + "\n" }.join
assert_equal expected, actual
end
def test_height_range_fit
tmux.send_keys 'seq 3 | fzf --height ~100% --info=inline --border', :Enter
expected = <<~OUTPUT
3
2
> 1
> < 3/3
OUTPUT
tmux.until { assert_block(expected, _1) }
end
def test_height_range_fit_preview_above
tmux.send_keys 'seq 3 | fzf --height ~100% --info=inline --border --preview "seq {}" --preview-window up,60%', :Enter
expected = <<~OUTPUT
1
3
2
> 1
> < 3/3
OUTPUT
tmux.until { assert_block(expected, _1) }
end
def test_height_range_fit_preview_above_alternative
tmux.send_keys 'seq 3 | fzf --height ~100% --border=sharp --preview "seq {}" --preview-window up,40%,border-bottom --padding 1 --exit-0 --header hello --header-lines=2', :Enter
expected = <<~OUTPUT
1
2
3
> 3
2
1
hello
1/1
>
OUTPUT
tmux.until { assert_block(expected, _1) }
end
def test_height_range_fit_preview_left
tmux.send_keys "seq 3 | fzf --height ~100% --border=vertical --preview 'seq {}' --preview-window left,5,border-right --padding 1 --exit-0 --header $'hello\\nworld' --header-lines=2", :Enter
expected = <<~OUTPUT
1 > 3
2 2
3 1
hello
world
1/1
>
OUTPUT
tmux.until { assert_block(expected, _1) }
end
def test_height_range_overflow
tmux.send_keys 'seq 100 | fzf --height ~5 --info=inline --border', :Enter
expected = <<~OUTPUT
2
> 1
> < 100/100
OUTPUT
tmux.until { assert_block(expected, _1) }
end
end
module TestShell