m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-15 06:43:47 -05:00

Compare commits

...

54 Commits

Author SHA1 Message Date
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
39 changed files with 1432 additions and 537 deletions

View File

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

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@v3
- name: 'Dependency Review'
uses: actions/dependency-review-action@v2

View File

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

View File

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

View File

@@ -72,6 +72,7 @@ builds:
- amd64 - amd64
- arm - arm
- arm64 - arm64
- loong64
goarm: goarm:
- 5 - 5
- 6 - 6
@@ -99,6 +100,10 @@ archives:
files: files:
- non-existent* - non-existent*
checksum:
extra_files:
- glob: ./dist/fzf-*darwin*.zip
release: release:
github: github:
owner: junegunn owner: junegunn

View File

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

View File

@@ -1,7 +1,7 @@
Advanced fzf examples Advanced fzf examples
====================== ======================
*(Last update: 2021/05/22)* *(Last update: 2022/08/25)*
<!-- vim-markdown-toc GFM --> <!-- vim-markdown-toc GFM -->
@@ -496,9 +496,17 @@ pods() {
Key bindings for git objects Key bindings for git objects
---------------------------- ----------------------------
I have [blogged](https://junegunn.kr/2016/07/fzf-git) about my fzf+git key Oftentimes, you want to put the identifiers of various Git object to the
bindings a few years ago. I'm going to show them here again, because they are command-line. For example, it is common to write commands like these:
seriously useful.
```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` ### 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) ![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 Color themes
------------ ------------

View File

@@ -1,6 +1,104 @@
CHANGELOG CHANGELOG
========= =========
0.35.0
------
- Added `start` event that is triggered only once when fzf finder starts.
Since fzf consumes the input stream asynchronously, the input list is not
available unless you use `--sync`.
```sh
seq 100 | fzf --multi --sync --bind 'start:last+select-all+preview(echo welcome)'
```
- Added `--border-label` and `--border-label-pos` for putting label on the border
```sh
# ANSI color codes are supported
# (with https://github.com/busyloop/lolcat)
label=$(curl -s http://metaphorpsum.com/sentences/1 | lolcat -f)
# Border label at the center
fzf --height=10 --border --border-label="╢ $label ╟" --color=label:italic:black
# Left-aligned (positive integer)
fzf --height=10 --border --border-label="╢ $label ╟" --border-label-pos=3 --color=label:italic:black
# Right-aligned (negative integer) on the bottom line (:bottom)
fzf --height=10 --border --border-label="╢ $label ╟" --border-label-pos=-3:bottom --color=label:italic:black
```
- Also added `--preview-label` and `--preview-label-pos` for the border of the
preview window
```sh
fzf --preview 'cat {}' --border --preview-label=' Preview ' --preview-label-pos=2
```
- Info panel (match counter) will be followed by a horizontal separator by
default
- Use `--no-separator` or `--separator=''` to hide the separator
- You can specify an arbitrary string that is repeated to form the
horizontal separator. e.g. `--separator=╸`
- The color of the separator can be customized via `--color=separator:...`
- ANSI color codes are also supported
```sh
fzf --separator=╸ --color=separator:green
fzf --separator=$(lolcat -f -F 1.4 <<< ▁▁▂▃▄▅▆▆▅▄▃▂▁▁) --info=inline
```
- Added `--border=bold` and `--border=double` along with
`--preview-window=border-bold` and `--preview-window=border-double`
0.34.0
------
- Added support for adaptive `--height`. If the `--height` value is prefixed
with `~`, fzf will automatically determine the height in the range according
to the input size.
```sh
seq 1 | fzf --height ~70% --border --padding 1 --margin 1
seq 10 | fzf --height ~70% --border --padding 1 --margin 1
seq 100 | fzf --height ~70% --border --padding 1 --margin 1
```
- There are a few limitations
- Not compatible with percent top/bottom margin/padding
```sh
# This is not allowed (top/bottom margin in percent value)
fzf --height ~50% --border --margin 5%,10%
# This is allowed (top/bottom margin in fixed value)
fzf --height ~50% --border --margin 2,10%
```
- fzf will not start until it can determine the right height for the input
```sh
# fzf will open immediately
(sleep 2; seq 10) | fzf --height 50%
# fzf will open after 2 seconds
(sleep 2; seq 10) | fzf --height ~50%
(sleep 2; seq 1000) | fzf --height ~50%
```
- Fixed tcell renderer used to render full-screen fzf on Windows
- `--no-clear` is deprecated. Use `reload` action instead.
0.33.0
------
- Added `--scheme=[default|path|history]` option to choose scoring scheme
- (Experimental)
- We updated the scoring algorithm in 0.32.0, however we have learned that
this new scheme (`default`) is not always giving the optimal result
- `path`: Additional bonus point is only given to the characters after
path separator. You might want to choose this scheme if you have many
files with spaces in their paths.
- `history`: No additional bonus points are given so that we give more
weight to the chronological ordering. This is equivalent to the scoring
scheme before 0.32.0. This also sets `--tiebreak=index`.
- ANSI color sequences with colon delimiters are now supported.
```sh
printf "\e[38;5;208mOption 1\e[m\nOption 2" | fzf --ansi
printf "\e[38:5:208mOption 1\e[m\nOption 2" | fzf --ansi
```
- Support `border-{up,down}` as the synonyms for `border-{top,bottom}` in
`--preview-window`
- Added support for ANSI `strikethrough`
```sh
printf "\e[9mdeleted" | fzf --ansi
fzf --color fg+:strikethrough
```
0.32.1 0.32.1
------ ------
- Fixed incorrect ordering of `--tiebreak=chunk` - Fixed incorrect ordering of `--tiebreak=chunk`

View File

@@ -1,6 +1,6 @@
The MIT License (MIT) 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal 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 $@ GOARCH=loong64 $(GO) build $(BUILD_FLAGS) -o $@
bin/fzf: target/$(BINARY) | bin bin/fzf: target/$(BINARY) | bin
-rm -f bin/fzf
cp -f target/$(BINARY) bin/fzf cp -f target/$(BINARY) bin/fzf
docker: 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. 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] More advanced examples can be found [here](https://github.com/junegunn/fzf/blob/master/ADVANCED.md).
([code](https://gist.github.com/junegunn/8b572b8d4b5eddd8b85e5f4d40f17236)).
[fzf-git]: https://junegunn.kr/2016/07/fzf-git/
---- ----

14
go.mod
View File

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

View File

@@ -2,7 +2,7 @@
set -u set -u
version=0.32.1 version=0.35.0
auto_completion= auto_completion=
key_bindings= key_bindings=
update_config=2 update_config=2
@@ -175,6 +175,7 @@ case "$archi" in
Linux\ armv7*) download fzf-$version-linux_armv7.tar.gz ;; Linux\ armv7*) download fzf-$version-linux_armv7.tar.gz ;;
Linux\ armv8*) download fzf-$version-linux_arm64.tar.gz ;; Linux\ armv8*) download fzf-$version-linux_arm64.tar.gz ;;
Linux\ aarch64*) 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\ *64) download fzf-$version-linux_amd64.tar.gz ;; Linux\ *64) download fzf-$version-linux_amd64.tar.gz ;;
FreeBSD\ *64) download fzf-$version-freebsd_amd64.tar.gz ;; FreeBSD\ *64) download fzf-$version-freebsd_amd64.tar.gz ;;
OpenBSD\ *64) download fzf-$version-openbsd_amd64.tar.gz ;; OpenBSD\ *64) download fzf-$version-openbsd_amd64.tar.gz ;;

View File

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

View File

@@ -5,7 +5,7 @@ import (
"github.com/junegunn/fzf/src/protector" "github.com/junegunn/fzf/src/protector"
) )
var version string = "0.32" var version string = "0.35"
var revision string = "devel" var revision string = "devel"
func main() { 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf-tmux 1 "Aug 2022" "fzf 0.32.1" "fzf-tmux - open fzf in tmux split pane" .TH fzf-tmux 1 "Nov 2022" "fzf 0.35.0" "fzf-tmux - open fzf in tmux split pane"
.SH NAME .SH NAME
fzf-tmux - open fzf in tmux split pane fzf-tmux - open fzf in tmux split pane

View File

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf 1 "Aug 2022" "fzf 0.32.1" "fzf - a command-line fuzzy finder" .TH fzf 1 "Nov 2022" "fzf 0.35.0" "fzf - a command-line fuzzy finder"
.SH NAME .SH NAME
fzf - a command-line fuzzy finder fzf - a command-line fuzzy finder
@@ -51,6 +51,18 @@ Case-sensitive match
.B "--literal" .B "--literal"
Do not normalize latin script letters for matching. Do not normalize latin script letters for matching.
.TP .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 .BI "--algo=" TYPE
Fuzzy matching algorithm (default: v2) Fuzzy matching algorithm (default: v2)
@@ -165,9 +177,11 @@ actions are affected:
Label characters for \fBjump\fR and \fBjump-accept\fR Label characters for \fBjump\fR and \fBjump-accept\fR
.SS Layout .SS Layout
.TP .TP
.BI "--height=" "HEIGHT[%]" .BI "--height=" "[~]HEIGHT[%]"
Display fzf window below the cursor with the given height instead of using 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 .TP
.BI "--min-height=" "HEIGHT" .BI "--min-height=" "HEIGHT"
Minimum height when \fB--height\fR is given in percent (default: 10). Minimum height when \fB--height\fR is given in percent (default: 10).
@@ -189,7 +203,7 @@ Choose the layout (default: default)
A synonym for \fB--layout=reverse\fB A synonym for \fB--layout=reverse\fB
.TP .TP
.BI "--border" [=STYLE] .BI "--border" [=BORDER_OPT]
Draw border around the finder Draw border around the finder
.br .br
@@ -197,13 +211,17 @@ Draw border around the finder
.br .br
.BR sharp " Border with sharp corners" .BR sharp " Border with sharp corners"
.br .br
.BR bold " Border with bold lines"
.br
.BR double " Border with double lines"
.br
.BR horizontal " Horizontal lines above and below the finder" .BR horizontal " Horizontal lines above and below the finder"
.br .br
.BR vertical " Vertical lines on each side of the finder" .BR vertical " Vertical lines on each side of the finder"
.br .br
.BR top .BR top " (up)"
.br .br
.BR bottom .BR bottom " (down)"
.br .br
.BR left .BR left
.br .br
@@ -212,6 +230,50 @@ Draw border around the finder
.BR none .BR none
.br .br
.TP
.BI "--border-label" [=LABEL]
Label to print on the horizontal border line. Should be used with one of the
following \fB--border\fR options.
.br
.B * rounded
.br
.B * sharp
.br
.B * bold
.br
.B * double
.br
.B * horizontal
.br
.BR "* top" " (up)"
.br
.BR "* bottom" " (down)"
.br
.br
e.g.
\fB# ANSI color codes are supported
# (with https://github.com/busyloop/lolcat)
label=$(curl -s http://metaphorpsum.com/sentences/1 | lolcat -f)
# Border label at the center
fzf --height=10 --border --border-label="╢ $label ╟" --color=label:italic:black
# Left-aligned (positive integer)
fzf --height=10 --border --border-label="╢ $label ╟" --border-label-pos=3 --color=label:italic:black
# Right-aligned (negative integer) on the bottom line (:bottom)
fzf --height=10 --border --border-label="╢ $label ╟" --border-label-pos=-3:bottom --color=label:italic:black\fR
.TP
.BI "--border-label-pos" [=N[:top|bottom]]
Position of the border label on the border line. Specify a positive integer as
the column position from the left. Specify a negative integer to right-align
the label. Label is printed on the top border line by default, add
\fB:bottom\fR to put it on the border line on the bottom. The default value
\fB0 (or \fBcenter\fR) will put the label at the center of the border line.
.TP .TP
.B "--no-unicode" .B "--no-unicode"
Use ASCII characters instead of Unicode box drawing characters to draw border Use ASCII characters instead of Unicode box drawing characters to draw border
@@ -267,7 +329,7 @@ e.g.
.TP .TP
.BI "--info=" "STYLE" .BI "--info=" "STYLE"
Determines the display style of finder info. Determines the display style of finder info (match counters).
.br .br
.BR default " Display on the next line to the prompt" .BR default " Display on the next line to the prompt"
@@ -281,6 +343,18 @@ Determines the display style of finder info.
.B "--no-info" .B "--no-info"
A synonym for \fB--info=hidden\fB A synonym for \fB--info=hidden\fB
.TP
.BI "--separator=" "STR"
The given string will be repeated to form the horizontal separator on the info
line (default: '─' or '-' depending on \fB--no-unicode\fR).
ANSI color codes are supported.
.TP
.B "--no-separator"
Do not display horizontal separator on the info line. A synonym for
\fB--separator=''\fB
.TP .TP
.BI "--prompt=" "STR" .BI "--prompt=" "STR"
Input prompt (default: '> ') Input prompt (default: '> ')
@@ -341,7 +415,9 @@ color mappings.
\fBquery \fRQuery string \fBquery \fRQuery string
\fBdisabled \fRQuery string when search is disabled \fBdisabled \fRQuery string when search is disabled
\fBinfo \fRInfo line (match counters) \fBinfo \fRInfo line (match counters)
\fBseparator \fRHorizontal separator on info line (match counters)
\fBborder \fRBorder around the window (\fB--border\fR and \fB--preview\fR) \fBborder \fRBorder around the window (\fB--border\fR and \fB--preview\fR)
\fBlabel \fRBorder label (\fB--border-label\fR and \fB--preview-label\fR)
\fBprompt \fRPrompt \fBprompt \fRPrompt
\fBpointer \fRPointer to the current line \fBpointer \fRPointer to the current line
\fBmarker \fRMulti-select marker \fBmarker \fRMulti-select marker
@@ -378,6 +454,7 @@ color mappings.
\fBreverse\fR \fBreverse\fR
\fBdim\fR \fBdim\fR
\fBitalic\fR \fBitalic\fR
\fBstrikethrough\fR
.B EXAMPLES: .B EXAMPLES:
@@ -471,6 +548,37 @@ e.g.
sleep 0.01 sleep 0.01
done'\fR done'\fR
.RE .RE
.TP
.BI "--preview-label" [=LABEL]
Label to print on the horizontal border line of the preview window.
Should be used with one of the following \fB--preview-window\fR options.
.br
.B * border-rounded (default)
.br
.B * border-sharp
.br
.B * border-bold
.br
.B * border-double
.br
.B * border-horizontal
.br
.B * border-top
.br
.B * border-bottom
.br
.TP
.BI "--preview-label-pos" [=N[:top|bottom]]
Position of the border label on the border line of the preview window. Specify
a positive integer as the column position from the left. Specify a negative
integer to right-align the label. Label is printed on the top border line by
default, add \fB:bottom\fR to put it on the border line on the bottom. The
default value 0 (or \fBcenter\fR) will put the label at the center of the
border line.
.TP .TP
.BI "--preview-window=" "[POSITION][,SIZE[%]][,border-BORDER_OPT][,[no]wrap][,[no]follow][,[no]cycle][,[no]hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]" .BI "--preview-window=" "[POSITION][,SIZE[%]][,border-BORDER_OPT][,[no]wrap][,[no]follow][,[no]cycle][,[no]hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]"
@@ -603,12 +711,6 @@ Read input delimited by ASCII NUL characters instead of newline characters
.B "--print0" .B "--print0"
Print output delimited by ASCII NUL characters instead of newline characters Print output delimited by ASCII NUL characters instead of newline characters
.TP .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" .B "--sync"
Synchronous search for multi-staged filtering. If specified, fzf will launch Synchronous search for multi-staged filtering. If specified, fzf will launch
ncurses finder only after the input stream is complete. ncurses finder only after the input stream is complete.
@@ -802,6 +904,15 @@ e.g.
or any single character or any single character
.SS AVAILABLE EVENTS: .SS AVAILABLE EVENTS:
\fIstart\fR
.RS
Triggered only once when fzf finder starts. Since fzf consumes the input stream
asynchronously, the input list is not available unless you use \fI--sync\fR.
e.g.
\fB# Move cursor to the last item and select all items
seq 1000 | fzf --multi --sync --bind start:last+select-all\fR
.RE
\fIchange\fR \fIchange\fR
.RS .RS
Triggered whenever the query string is changed Triggered whenever the query string is changed

View File

@@ -143,7 +143,7 @@ function! fzf#install()
if !filereadable(script) if !filereadable(script)
throw script.' not found' throw script.' not found'
endif endif
let script = 'powershell -ExecutionPolicy Bypass -file ' . script let script = 'powershell -ExecutionPolicy Bypass -file ' . shellescape(script)
else else
let script = s:base_dir.'/install' let script = s:base_dir.'/install'
if !executable(script) if !executable(script)
@@ -164,7 +164,7 @@ function s:get_version(bin)
if has_key(s:versions, a:bin) if has_key(s:versions, a:bin)
return s:versions[a:bin] return s:versions[a:bin]
end end
let command = (&shell =~ 'powershell' ? '&' : '') . shellescape(a:bin) . ' --version --no-height' let command = (&shell =~ 'powershell' ? '&' : '') . s:fzf_call('shellescape', a:bin) . ' --version --no-height'
let output = systemlist(command) let output = systemlist(command)
if v:shell_error || empty(output) if v:shell_error || empty(output)
return '' return ''
@@ -464,7 +464,7 @@ try
let temps = { 'result': s:fzf_tempname() } let temps = { 'result': s:fzf_tempname() }
let optstr = s:evaluate_opts(get(dict, 'options', '')) let optstr = s:evaluate_opts(get(dict, 'options', ''))
try try
let fzf_exec = fzf#shellescape(fzf#exec()) let fzf_exec = shellescape(fzf#exec())
catch catch
throw v:exception throw v:exception
endtry endtry
@@ -973,16 +973,16 @@ function! s:callback(dict, lines) abort
endfunction endfunction
if has('nvim') 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 buf = nvim_create_buf(v:false, v:true)
let opts = extend({'relative': 'editor', 'style': 'minimal'}, a:opts) let opts = extend({'relative': 'editor', 'style': 'minimal'}, a:opts)
let win = nvim_open_win(buf, v:true, opts) let win = nvim_open_win(buf, v:true, opts)
call setwinvar(win, '&winhighlight', 'NormalFloat:'..a:hl) silent! call setwinvar(win, '&winhighlight', 'Pmenu:,Normal:Normal')
call setwinvar(win, '&colorcolumn', '') call setwinvar(win, '&colorcolumn', '')
return buf return buf
endfunction endfunction
else else
function! s:create_popup(hl, opts) abort function! s:create_popup(opts) abort
let s:popup_create = {buf -> popup_create(buf, #{ let s:popup_create = {buf -> popup_create(buf, #{
\ line: a:opts.row, \ line: a:opts.row,
\ col: a:opts.col, \ col: a:opts.col,
@@ -1017,7 +1017,7 @@ function! s:popup(opts) abort
let row += !has('nvim') let row += !has('nvim')
let col += !has('nvim') let col += !has('nvim')
call s:create_popup('Normal', { call s:create_popup({
\ 'row': row, 'col': col, 'width': width, 'height': height \ 'row': row, 'col': col, 'width': width, 'height': height
\ }) \ })
endfunction endfunction

View File

@@ -37,7 +37,7 @@ bind '"\e[0n": redraw-current-line' 2> /dev/null
__fzf_comprun() { __fzf_comprun() {
if [[ "$(type -t _fzf_comprun 2>&1)" = function ]]; then if [[ "$(type -t _fzf_comprun 2>&1)" = function ]]; then
_fzf_comprun "$@" _fzf_comprun "$@"
elif [[ -n "$TMUX_PANE" ]] && { [[ "${FZF_TMUX:-0}" != 0 ]] || [[ -n "$FZF_TMUX_OPTS" ]]; }; then elif [[ -n "${TMUX_PANE-}" ]] && { [[ "${FZF_TMUX:-0}" != 0 ]] || [[ -n "${FZF_TMUX_OPTS-}" ]]; }; then
shift shift
fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- "$@" fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- "$@"
else else
@@ -55,8 +55,8 @@ __fzf_orig_completion() {
cmd="${BASH_REMATCH[3]}" cmd="${BASH_REMATCH[3]}"
[[ "$f" = _fzf_* ]] && continue [[ "$f" = _fzf_* ]] && continue
printf -v "_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}" "%s" "${comp} %s ${cmd} #${f}" printf -v "_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}" "%s" "${comp} %s ${cmd} #${f}"
if [[ "$l" = *" -o nospace "* ]] && [[ ! "$__fzf_nospace_commands" = *" $cmd "* ]]; then if [[ "$l" = *" -o nospace "* ]] && [[ ! "${__fzf_nospace_commands-}" = *" $cmd "* ]]; then
__fzf_nospace_commands="$__fzf_nospace_commands $cmd " __fzf_nospace_commands="${__fzf_nospace_commands-} $cmd "
fi fi
fi fi
done done
@@ -139,17 +139,18 @@ _fzf_handle_dynamic_completion() {
shift shift
orig_cmd="$1" orig_cmd="$1"
orig_var="_fzf_orig_completion_$cmd" orig_var="_fzf_orig_completion_$cmd"
orig="${!orig_var##*#}" orig="${!orig_var-}"
orig="${orig##*#}"
if [[ -n "$orig" ]] && type "$orig" > /dev/null 2>&1; then if [[ -n "$orig" ]] && type "$orig" > /dev/null 2>&1; then
$orig "$@" $orig "$@"
elif [[ -n "$_fzf_completion_loader" ]]; then elif [[ -n "${_fzf_completion_loader-}" ]]; then
orig_complete=$(complete -p "$orig_cmd" 2> /dev/null) orig_complete=$(complete -p "$orig_cmd" 2> /dev/null)
_completion_loader "$@" _completion_loader "$@"
ret=$? ret=$?
# _completion_loader may not have updated completion for the command # _completion_loader may not have updated completion for the command
if [[ "$(complete -p "$orig_cmd" 2> /dev/null)" != "$orig_complete" ]]; then if [[ "$(complete -p "$orig_cmd" 2> /dev/null)" != "$orig_complete" ]]; then
__fzf_orig_completion < <(complete -p "$orig_cmd" 2> /dev/null) __fzf_orig_completion < <(complete -p "$orig_cmd" 2> /dev/null)
if [[ "$__fzf_nospace_commands" = *" $orig_cmd "* ]]; then if [[ "${__fzf_nospace_commands-}" = *" $orig_cmd "* ]]; then
eval "${orig_complete/ -F / -o nospace -F }" eval "${orig_complete/ -F / -o nospace -F }"
else else
eval "$orig_complete" eval "$orig_complete"
@@ -173,6 +174,7 @@ __fzf_generic_path_completion() {
base=${cur:0:${#cur}-${#trigger}} base=${cur:0:${#cur}-${#trigger}}
eval "base=$base" eval "base=$base"
dir=
[[ $base = *"/"* ]] && dir="$base" [[ $base = *"/"* ]] && dir="$base"
while true; do while true; do
if [[ -z "$dir" ]] || [[ -d "$dir" ]]; then if [[ -z "$dir" ]] || [[ -d "$dir" ]]; then
@@ -180,11 +182,11 @@ __fzf_generic_path_completion() {
leftover=${leftover/#\/} leftover=${leftover/#\/}
[[ -z "$dir" ]] && dir='.' [[ -z "$dir" ]] && dir='.'
[[ "$dir" != "/" ]] && dir="${dir/%\//}" [[ "$dir" != "/" ]] && dir="${dir/%\//}"
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do 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) done)
matches=${matches% } matches=${matches% }
[[ -z "$3" ]] && [[ "$__fzf_nospace_commands" = *" ${COMP_WORDS[0]} "* ]] && matches="$matches " [[ -z "$3" ]] && [[ "${__fzf_nospace_commands-}" = *" ${COMP_WORDS[0]} "* ]] && matches="$matches "
if [[ -n "$matches" ]]; then if [[ -n "$matches" ]]; then
COMPREPLY=( "$matches" ) COMPREPLY=( "$matches" )
else else
@@ -236,7 +238,7 @@ _fzf_complete() {
if [[ "$cur" == *"$trigger" ]]; then if [[ "$cur" == *"$trigger" ]]; then
cur=${cur:0:${#cur}-${#trigger}} cur=${cur:0:${#cur}-${#trigger}}
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | tr '\n' ' ') selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | tr '\n' ' ')
selected=${selected% } # Strip trailing space not to repeat "-o nospace" selected=${selected% } # Strip trailing space not to repeat "-o nospace"
if [[ -n "$selected" ]]; then if [[ -n "$selected" ]]; then
COMPREPLY=("$selected") COMPREPLY=("$selected")
@@ -288,13 +290,13 @@ _fzf_host_completion() {
_fzf_var_completion() { _fzf_var_completion() {
_fzf_complete -m -- "$@" < <( _fzf_complete -m -- "$@" < <(
declare -xp | sed 's/=.*//' | sed 's/.* //' declare -xp | sed -En 's|^declare [^ ]+ ([^=]+).*|\1|p'
) )
} }
_fzf_alias_completion() { _fzf_alias_completion() {
_fzf_complete -m -- "$@" < <( _fzf_complete -m -- "$@" < <(
alias | sed 's/=.*//' | sed 's/.* //' alias | sed -En 's|^alias ([^=]+).*|\1|p'
) )
} }
@@ -329,7 +331,7 @@ __fzf_defc() {
func="$2" func="$2"
opts="$3" opts="$3"
orig_var="_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}" orig_var="_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}"
orig="${!orig_var}" orig="${!orig_var-}"
if [[ -n "$orig" ]]; then if [[ -n "$orig" ]]; then
printf -v def "$orig" "$func" printf -v def "$orig" "$func"
eval "$def" eval "$def"

View File

@@ -99,9 +99,9 @@ fi
__fzf_comprun() { __fzf_comprun() {
if [[ "$(type _fzf_comprun 2>&1)" =~ function ]]; then if [[ "$(type _fzf_comprun 2>&1)" =~ function ]]; then
_fzf_comprun "$@" _fzf_comprun "$@"
elif [ -n "$TMUX_PANE" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "$FZF_TMUX_OPTS" ]; }; then elif [ -n "${TMUX_PANE-}" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "${FZF_TMUX_OPTS-}" ]; }; then
shift shift
if [ -n "$FZF_TMUX_OPTS" ]; then if [ -n "${FZF_TMUX_OPTS-}" ]; then
fzf-tmux ${(Q)${(Z+n+)FZF_TMUX_OPTS}} -- "$@" fzf-tmux ${(Q)${(Z+n+)FZF_TMUX_OPTS}} -- "$@"
else else
fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%} -- "$@" fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%} -- "$@"
@@ -145,8 +145,9 @@ __fzf_generic_path_completion() {
leftover=${leftover/#\/} leftover=${leftover/#\/}
[ -z "$dir" ] && dir='.' [ -z "$dir" ] && dir='.'
[ "$dir" != "/" ] && dir="${dir/%\//}" [ "$dir" != "/" ] && dir="${dir/%\//}"
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do matches=$(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) done)
matches=${matches% } matches=${matches% }
if [ -n "$matches" ]; then if [ -n "$matches" ]; then
@@ -183,7 +184,7 @@ _fzf_complete() {
args=("$@") args=("$@")
sep= sep=
for i in {0..${#args[@]}}; do for i in {0..${#args[@]}}; do
if [[ "${args[$i]}" = -- ]]; then if [[ "${args[$i]-}" = -- ]]; then
sep=$i sep=$i
break break
fi fi
@@ -207,7 +208,7 @@ _fzf_complete() {
type $post > /dev/null 2>&1 || post=cat type $post > /dev/null 2>&1 || post=cat
_fzf_feed_fifo "$fifo" _fzf_feed_fifo "$fifo"
matches=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ') matches=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $str_arg" __fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
if [ -n "$matches" ]; then if [ -n "$matches" ]; then
LBUFFER="$lbuf$matches" LBUFFER="$lbuf$matches"
fi fi
@@ -278,8 +279,8 @@ fzf-completion() {
[ -z "$trigger" -a ${LBUFFER[-1]} = ' ' ] && tokens+=("") [ -z "$trigger" -a ${LBUFFER[-1]} = ' ' ] && tokens+=("")
# When the trigger starts with ';', it becomes a separate token # When the trigger starts with ';', it becomes a separate token
if [[ ${LBUFFER} = *"${tokens[-2]}${tokens[-1]}" ]]; then if [[ ${LBUFFER} = *"${tokens[-2]-}${tokens[-1]}" ]]; then
tokens[-2]="${tokens[-2]}${tokens[-1]}" tokens[-2]="${tokens[-2]-}${tokens[-1]}"
tokens=(${tokens[0,-2]}) tokens=(${tokens[0,-2]})
fi fi

View File

@@ -19,7 +19,7 @@ __fzf_select__() {
-o -type f -print \ -o -type f -print \
-o -type d -print \ -o -type d -print \
-o -type l -print 2> /dev/null | cut -b3-"}" -o -type l -print 2> /dev/null | cut -b3-"}"
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS -m" opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-} -m"
eval "$cmd" | eval "$cmd" |
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) "$@" | FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) "$@" |
while read -r item; do while read -r item; do
@@ -30,7 +30,7 @@ __fzf_select__() {
if [[ $- =~ i ]]; then if [[ $- =~ i ]]; then
__fzfcmd() { __fzfcmd() {
[[ -n "$TMUX_PANE" ]] && { [[ "${FZF_TMUX:-0}" != 0 ]] || [[ -n "$FZF_TMUX_OPTS" ]]; } && [[ -n "${TMUX_PANE-}" ]] && { [[ "${FZF_TMUX:-0}" != 0 ]] || [[ -n "${FZF_TMUX_OPTS-}" ]]; } &&
echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf" echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
} }
@@ -44,13 +44,13 @@ __fzf_cd__() {
local cmd opts dir local cmd opts dir
cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \ 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-"}" -o -type d -print 2> /dev/null | cut -b3-"}"
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS +m" opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-} +m"
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="$opts" $(__fzfcmd)) && printf 'builtin cd -- %q' "$dir" dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="$opts" $(__fzfcmd)) && printf 'builtin cd -- %q' "$dir"
} }
__fzf_history__() { __fzf_history__() {
local output opts script 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{$_}++' script='BEGIN { getc; $/ = "\n\t"; $HISTCOUNT = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCOUNT - $. . "\t$_" if !$seen{$_}++'
output=$( output=$(
builtin fc -lnr -2147483648 | builtin fc -lnr -2147483648 |

View File

@@ -53,7 +53,7 @@ function fzf_key_bindings
function fzf-history-widget -d "Show command history" function fzf-history-widget -d "Show command history"
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40% test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
begin begin
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $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_MAJOR (echo $version | cut -f1 -d.)
set -l FISH_MINOR (echo $version | cut -f2 -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 eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
if [ -n "$result" ] if [ -n "$result" ]
builtin cd -- $result cd -- $result
# Remove last token from commandline. # Remove last token from commandline.
commandline -t "" commandline -t ""

View File

@@ -46,7 +46,7 @@ __fsel() {
-o -type l -print 2> /dev/null | cut -b3-"}" -o -type l -print 2> /dev/null | cut -b3-"}"
setopt localoptions pipefail no_aliases 2> /dev/null setopt localoptions pipefail no_aliases 2> /dev/null
local item local item
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read item; do eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-}" $(__fzfcmd) -m "$@" | while read item; do
echo -n "${(q)item} " echo -n "${(q)item} "
done done
local ret=$? local ret=$?
@@ -55,7 +55,7 @@ __fsel() {
} }
__fzfcmd() { __fzfcmd() {
[ -n "$TMUX_PANE" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "$FZF_TMUX_OPTS" ]; } && [ -n "${TMUX_PANE-}" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "${FZF_TMUX_OPTS-}" ]; } &&
echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf" echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
} }
@@ -75,7 +75,7 @@ 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 \ 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-"}" -o -type d -print 2> /dev/null | cut -b3-"}"
setopt localoptions pipefail no_aliases 2> /dev/null setopt localoptions pipefail no_aliases 2> /dev/null
local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m)" local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-}" $(__fzfcmd) +m)"
if [[ -z "$dir" ]]; then if [[ -z "$dir" ]]; then
zle redisplay zle redisplay
return 0 return 0
@@ -98,7 +98,7 @@ fzf-history-widget() {
local selected num local selected num
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null 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 }' | 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=$? local ret=$?
if [ -n "$selected" ]; then if [ -n "$selected" ]; then
num=$selected[1] num=$selected[1]

View File

@@ -80,6 +80,7 @@ Scoring criteria
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"os"
"strings" "strings"
"unicode" "unicode"
"unicode/utf8" "unicode/utf8"
@@ -89,7 +90,8 @@ import (
var DEBUG bool var DEBUG bool
const delimiterChars = "/,:;|" var delimiterChars = "/,:;|"
const whiteChars = " \t\n\v\f\r\x85\xA0" const whiteChars = " \t\n\v\f\r\x85\xA0"
func indexAt(index int, max int, forward bool) int { func indexAt(index int, max int, forward bool) int {
@@ -120,12 +122,6 @@ const (
// in web2 dictionary and my file system. // in web2 dictionary and my file system.
bonusBoundary = scoreMatch / 2 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 // 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 // for computing bonus points for consecutive chunks starting with a non-word
// character. // character.
@@ -149,6 +145,16 @@ const (
bonusFirstCharMultiplier = 2 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 type charClass int
const ( const (
@@ -161,6 +167,29 @@ const (
charNumber 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 { func posArray(withPos bool, len int) *[]int {
if withPos { if withPos {
pos := make([]int, 0, len) 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 // Phase 2. Calculate bonus for each point
maxScore, maxScorePos := int16(0), 0 maxScore, maxScorePos := int16(0), 0
pidx, lastIdx := 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:] Tsub := T[idx:]
H0sub, C0sub, Bsub := H0[idx:][:len(Tsub)], C0[idx:][:len(Tsub)], B[idx:][:len(Tsub)] H0sub, C0sub, Bsub := H0[idx:][:len(Tsub)], C0[idx:][:len(Tsub)], B[idx:][:len(Tsub)]
for off, char := range 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) match = runesStr == string(pattern)
} }
if match { if match {
return Result{trimmedLen, trimmedLen + lenPattern, (scoreMatch+bonusBoundaryWhite)*lenPattern + return Result{trimmedLen, trimmedLen + lenPattern, (scoreMatch+int(bonusBoundaryWhite))*lenPattern +
(bonusFirstCharMultiplier-1)*bonusBoundaryWhite}, nil (bonusFirstCharMultiplier-1)*int(bonusBoundaryWhite)}, nil
} }
return Result{-1, -1, 0}, 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, assertMatch(t, fn, false, forward, "fooBarbaz1", "oBZ", 2, 9,
scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtension*3) scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtension*3)
assertMatch(t, fn, false, forward, "foo bar baz", "fbb", 0, 9, assertMatch(t, fn, false, forward, "foo bar baz", "fbb", 0, 9,
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+ scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+
bonusBoundaryWhite*2+2*scoreGapStart+4*scoreGapExtension) int(bonusBoundaryWhite)*2+2*scoreGapStart+4*scoreGapExtension)
assertMatch(t, fn, false, forward, "/AutomatorDocument.icns", "rdoc", 9, 13, assertMatch(t, fn, false, forward, "/AutomatorDocument.icns", "rdoc", 9, 13,
scoreMatch*4+bonusCamel123+bonusConsecutive*2) scoreMatch*4+bonusCamel123+bonusConsecutive*2)
assertMatch(t, fn, false, forward, "/man1/zshcompctl.1", "zshc", 6, 10, 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, 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, assertMatch(t, fn, false, forward, "ab0123 456", "12356", 3, 10,
scoreMatch*5+bonusConsecutive*3+scoreGapStart+scoreGapExtension) scoreMatch*5+bonusConsecutive*3+scoreGapStart+scoreGapExtension)
assertMatch(t, fn, false, forward, "abc123 456", "12356", 3, 10, assertMatch(t, fn, false, forward, "abc123 456", "12356", 3, 10,
scoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+bonusConsecutive+scoreGapStart+scoreGapExtension) scoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+bonusConsecutive+scoreGapStart+scoreGapExtension)
assertMatch(t, fn, false, forward, "foo/bar/baz", "fbb", 0, 9, assertMatch(t, fn, false, forward, "foo/bar/baz", "fbb", 0, 9,
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+ scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+
bonusBoundaryDelimiter*2+2*scoreGapStart+4*scoreGapExtension) int(bonusBoundaryDelimiter)*2+2*scoreGapStart+4*scoreGapExtension)
assertMatch(t, fn, false, forward, "fooBarBaz", "fbb", 0, 7, assertMatch(t, fn, false, forward, "fooBarBaz", "fbb", 0, 7,
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+ scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+
bonusCamel123*2+2*scoreGapStart+2*scoreGapExtension) bonusCamel123*2+2*scoreGapStart+2*scoreGapExtension)
assertMatch(t, fn, false, forward, "foo barbaz", "fbb", 0, 8, 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) scoreGapStart*2+scoreGapExtension*3)
assertMatch(t, fn, false, forward, "fooBar Baz", "foob", 0, 4, 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, assertMatch(t, fn, false, forward, "xFoo-Bar Baz", "foo-b", 1, 6,
scoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+ scoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+
bonusNonWord+bonusBoundary) bonusNonWord+bonusBoundary)
@@ -75,14 +75,14 @@ func TestFuzzyMatch(t *testing.T) {
assertMatch(t, fn, true, forward, "fooBarbaz", "oBz", 2, 9, assertMatch(t, fn, true, forward, "fooBarbaz", "oBz", 2, 9,
scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtension*3) scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtension*3)
assertMatch(t, fn, true, forward, "Foo/Bar/Baz", "FBB", 0, 9, 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) scoreGapStart*2+scoreGapExtension*4)
assertMatch(t, fn, true, forward, "FooBarBaz", "FBB", 0, 7, 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) scoreGapStart*2+scoreGapExtension*2)
assertMatch(t, fn, true, forward, "FooBar Baz", "FooB", 0, 4, assertMatch(t, fn, true, forward, "FooBar Baz", "FooB", 0, 4,
scoreMatch*4+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryWhite*2+ scoreMatch*4+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryWhite)*2+
util.Max(bonusCamel123, bonusBoundaryWhite)) util.Max(bonusCamel123, int(bonusBoundaryWhite)))
// Consecutive bonus updated // Consecutive bonus updated
assertMatch(t, fn, true, forward, "foo-bar", "o-ba", 2, 6, 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) { func TestFuzzyMatchBackward(t *testing.T) {
assertMatch(t, FuzzyMatchV1, false, true, "foobar fb", "fb", 0, 4, assertMatch(t, FuzzyMatchV1, false, true, "foobar fb", "fb", 0, 4,
scoreMatch*2+bonusBoundaryWhite*bonusFirstCharMultiplier+ scoreMatch*2+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+
scoreGapStart+scoreGapExtension) scoreGapStart+scoreGapExtension)
assertMatch(t, FuzzyMatchV1, false, false, "foobar fb", "fb", 7, 9, 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) { func TestExactMatchNaive(t *testing.T) {
@@ -114,9 +114,9 @@ func TestExactMatchNaive(t *testing.T) {
assertMatch(t, ExactMatchNaive, false, dir, "/AutomatorDocument.icns", "rdoc", 9, 13, assertMatch(t, ExactMatchNaive, false, dir, "/AutomatorDocument.icns", "rdoc", 9, 13,
scoreMatch*4+bonusCamel123+bonusConsecutive*2) scoreMatch*4+bonusCamel123+bonusConsecutive*2)
assertMatch(t, ExactMatchNaive, false, dir, "/man1/zshcompctl.1", "zshc", 6, 10, 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, 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) { 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} { for _, dir := range []bool{true, false} {
assertMatch(t, PrefixMatch, true, dir, "fooBarbaz", "Foo", -1, -1, 0) 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 // Only when the pattern doesn't end with a space
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz ", "baz ", 6, 10, 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 { if s.attr&tui.Reverse > 0 {
ret += "7;" ret += "7;"
} }
if s.attr&tui.StrikeThrough > 0 {
ret += "9;"
}
ret += toAnsiString(s.fg, 30) + toAnsiString(s.bg, 40) ret += toAnsiString(s.fg, 30) + toAnsiString(s.bg, 40)
return "\x1b[" + strings.TrimSuffix(ret, ";") + "m" return "\x1b[" + strings.TrimSuffix(ret, ";") + "m"
@@ -85,7 +88,7 @@ func isPrint(c uint8) bool {
} }
func matchOperatingSystemCommand(s string) int { func matchOperatingSystemCommand(s string) int {
// `\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)` // `\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)`
// ^ match starting here // ^ match starting here
// //
i := 5 // prefix matched in nextAnsiEscapeSequence() i := 5 // prefix matched in nextAnsiEscapeSequence()
@@ -103,30 +106,37 @@ func matchOperatingSystemCommand(s string) int {
} }
func matchControlSequence(s string) int { func matchControlSequence(s string) int {
// `\x1b[\\[()][0-9;?]*[a-zA-Z@]` // `\x1b[\\[()][0-9;:?]*[a-zA-Z@]`
// ^ match starting here // ^ match starting here
// //
i := 2 // prefix matched in nextAnsiEscapeSequence() i := 2 // prefix matched in nextAnsiEscapeSequence()
for ; i < len(s) && (isNumeric(s[i]) || s[i] == ';' || s[i] == '?'); i++ { for ; i < len(s); i++ {
}
if i < len(s) {
c := 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 == '@' { if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '@' {
return i + 1 return i + 1
} }
return -1
}
} }
return -1 return -1
} }
func isCtrlSeqStart(c uint8) bool { 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 // nextAnsiEscapeSequence returns the ANSI escape sequence and is equivalent to
// calling FindStringIndex() on the below regex (which was originally used): // 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) { func nextAnsiEscapeSequence(s string) (int, int) {
// fast check for ANSI escape sequences // fast check for ANSI escape sequences
i := 0 i := 0
@@ -154,16 +164,16 @@ Loop:
return i - n, i + 1 return i - n, i + 1
} }
case '\x1b': 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 i+2 < len(s) && isCtrlSeqStart(s[i+1]) {
if j := matchControlSequence(s[i:]); j != -1 { if j := matchControlSequence(s[i:]); j != -1 {
return i, i + j 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]) && 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 { if j := matchOperatingSystemCommand(s[i:]); j != -1 {
return i, i + j return i, i + j
@@ -280,9 +290,20 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
return trimmed, nil, state return trimmed, nil, state
} }
func parseAnsiCode(s string) (int, string) { func parseAnsiCode(s string, delimiter byte) (int, byte, string) {
var remaining 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:] remaining = s[i+1:]
s = s[:i] s = s[:i]
} }
@@ -294,14 +315,14 @@ func parseAnsiCode(s string) (int, string) {
for _, ch := range []byte(s) { for _, ch := range []byte(s) {
ch -= '0' ch -= '0'
if ch > 9 { if ch > 9 {
return -1, remaining return -1, delimiter, remaining
} }
code = code*10 + int(ch) 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 { func interpretCode(ansiCode string, prevState *ansiState) ansiState {
@@ -329,9 +350,10 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
state256 := 0 state256 := 0
ptr := &state.fg ptr := &state.fg
var delimiter byte = 0
for len(ansiCode) != 0 { for len(ansiCode) != 0 {
var num int var num int
if num, ansiCode = parseAnsiCode(ansiCode); num != -1 { if num, delimiter, ansiCode = parseAnsiCode(ansiCode, delimiter); num != -1 {
switch state256 { switch state256 {
case 0: case 0:
switch num { switch num {
@@ -357,6 +379,8 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
state.attr = state.attr | tui.Blink state.attr = state.attr | tui.Blink
case 7: case 7:
state.attr = state.attr | tui.Reverse state.attr = state.attr | tui.Reverse
case 9:
state.attr = state.attr | tui.StrikeThrough
case 23: // tput rmso case 23: // tput rmso
state.attr = state.attr &^ tui.Italic state.attr = state.attr &^ tui.Italic
case 24: // tput rmul case 24: // tput rmul

View File

@@ -22,7 +22,7 @@ import (
// (archived from 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 // - http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
// - https://invisible-island.net/xterm/ctlseqs/ctlseqs.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)") 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) { func testParserReference(t testing.TB, str string) {
t.Helper() 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[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[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[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;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;1m", nil, "\x1b[1;38;2;10;20;30;48;5;100m")
assert("\x1b[48;5;100;38;2;10;20;30;7m", assert("\x1b[48;5;100;38;2;10;20;30;7m",
@@ -377,7 +378,7 @@ func TestParseAnsiCode(t *testing.T) {
{"-2", "", -1}, {"-2", "", -1},
} }
for _, x := range tests { for _, x := range tests {
n, s := parseAnsiCode(x.In) n, _, s := parseAnsiCode(x.In, 0)
if n != x.N || s != x.Exp { if n != x.N || s != x.Exp {
t.Fatalf("%q: got: (%d %q) want: (%d %q)", x.In, n, s, x.N, 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 // kernel/bpf/preload/iterators/README
const ansiBenchmarkString = "\x1b[38;5;81m\x1b[01;31m\x1b[Kkernel/\x1b[0m\x1b[38;5;81mbpf/" + 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:81mpreload/\x1b[0m\x1b[38;5;81miterators/" +
"\x1b[0m\x1b[38;5;149mMakefile\x1b[m\x1b[K\x1b[0m" "\x1b[0m\x1b[38:5:149mMakefile\x1b[m\x1b[K\x1b[0m"
func BenchmarkNextAnsiEscapeSequence(b *testing.B) { func BenchmarkNextAnsiEscapeSequence(b *testing.B) {
b.SetBytes(int64(len(ansiBenchmarkString))) b.SetBytes(int64(len(ansiBenchmarkString)))

View File

@@ -1,28 +1,4 @@
/* // Package fzf implements fzf, a command-line fuzzy finder.
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 package fzf
import ( import (
@@ -218,10 +194,17 @@ func Run(opts *Options, version string, revision string) {
// Terminal I/O // Terminal I/O
terminal := NewTerminal(opts, eventBox) 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 deferred := opts.Select1 || opts.Exit0
go terminal.Loop() go terminal.Loop()
if !deferred { if !deferred && !heightUnknown {
terminal.startChan <- true // Start right away
terminal.startChan <- fitpad{-1, -1}
} }
// Event coordination // Event coordination
@@ -240,7 +223,19 @@ func Run(opts *Options, version string, revision string) {
go reader.restart(command) go reader.restart(command)
} }
eventBox.Watch(EvtReadNew) eventBox.Watch(EvtReadNew)
total := 0
query := []rune{} 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 { for {
delay := true delay := true
ticks++ ticks++
@@ -273,11 +268,15 @@ func Run(opts *Options, version string, revision string) {
reading = reading && evt == EvtReadNew reading = reading && evt == EvtReadNew
} }
snapshot, count := chunkList.Snapshot() snapshot, count := chunkList.Snapshot()
terminal.UpdateCount(count, !reading, value.(*string)) total = count
terminal.UpdateCount(total, !reading, value.(*string))
if opts.Sync { if opts.Sync {
opts.Sync = false opts.Sync = false
terminal.UpdateList(PassMerger(&snapshot, opts.Tac), false) terminal.UpdateList(PassMerger(&snapshot, opts.Tac), false)
} }
if heightUnknown && !deferred {
determine(!reading)
}
reset := clearCache() reset := clearCache()
matcher.Reset(snapshot, input(reset), false, !reading, sort, reset) matcher.Reset(snapshot, input(reset), false, !reading, sort, reset)
@@ -319,8 +318,7 @@ func Run(opts *Options, version string, revision string) {
if deferred { if deferred {
count := val.Length() count := val.Length()
if opts.Select1 && count > 1 || opts.Exit0 && !opts.Select1 && count > 0 { if opts.Select1 && count > 1 || opts.Exit0 && !opts.Select1 && count > 0 {
deferred = false determine(val.final)
terminal.startChan <- true
} else if val.final { } else if val.final {
if opts.Exit0 && count == 0 || opts.Select1 && count == 1 { if opts.Exit0 && count == 0 || opts.Select1 && count == 1 {
if opts.PrintQuery { if opts.PrintQuery {
@@ -337,8 +335,7 @@ func Run(opts *Options, version string, revision string) {
} }
os.Exit(exitNoMatch) os.Exit(exitNoMatch)
} }
deferred = false determine(val.final)
terminal.startChan <- true
} }
} }
terminal.UpdateList(val, clearSelection()) terminal.UpdateList(val, clearSelection())

View File

@@ -21,9 +21,9 @@ const usage = `usage: fzf [options]
-x, --extended Extended-search mode -x, --extended Extended-search mode
(enabled by default; +x or --no-extended to disable) (enabled by default; +x or --no-extended to disable)
-e, --exact Enable Exact-match -e, --exact Enable Exact-match
--algo=TYPE Fuzzy matching algorithm: [v1|v2] (default: v2)
-i Case-insensitive match (default: smart-case match) -i Case-insensitive match (default: smart-case match)
+i Case-sensitive match +i Case-sensitive match
--scheme=SCHEME Scoring scheme [default|path|history]
--literal Do not normalize latin script letters before matching --literal Do not normalize latin script letters before matching
-n, --nth=N[,..] Comma-separated list of field index expressions -n, --nth=N[,..] Comma-separated list of field index expressions
for limiting search scope. Each can be a non-zero for limiting search scope. Each can be a non-zero
@@ -53,17 +53,26 @@ const usage = `usage: fzf [options]
--jump-labels=CHARS Label characters for jump and jump-accept --jump-labels=CHARS Label characters for jump and jump-accept
Layout Layout
--height=HEIGHT[%] Display fzf window below the cursor with the given --height=[~]HEIGHT[%] Display fzf window below the cursor with the given
height instead of using fullscreen 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 --min-height=HEIGHT Minimum height when --height is given in percent
(default: 10) (default: 10)
--layout=LAYOUT Choose layout: [default|reverse|reverse-list] --layout=LAYOUT Choose layout: [default|reverse|reverse-list]
--border[=STYLE] Draw border around the finder --border[=STYLE] Draw border around the finder
[rounded|sharp|horizontal|vertical| [rounded|sharp|horizontal|vertical|
top|bottom|left|right|none] (default: rounded) top|bottom|left|right|none] (default: rounded)
--border-label=LABEL Label to print on the border
--border-label-pos=COL Position of the border label
[POSITIVE_INTEGER: columns from left|
NEGATIVE_INTEGER: columns from right][:bottom]
(default: 0 or center)
--margin=MARGIN Screen margin (TRBL | TB,RL | T,RL,B | T,R,B,L) --margin=MARGIN Screen margin (TRBL | TB,RL | T,RL,B | T,R,B,L)
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L) --padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
--info=STYLE Finder info style [default|inline|hidden] --info=STYLE Finder info style [default|inline|hidden]
--separator=STR String to form horizontal separator on info line
--no-separator Hide info line separator
--prompt=STR Input prompt (default: '> ') --prompt=STR Input prompt (default: '> ')
--pointer=STR Pointer to the current line (default: '>') --pointer=STR Pointer to the current line (default: '>')
--marker=STR Multi-select marker (default: '>') --marker=STR Multi-select marker (default: '>')
@@ -90,6 +99,9 @@ const usage = `usage: fzf [options]
[,border-BORDER_OPT] [,border-BORDER_OPT]
[,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES] [,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
[,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)] [,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]
--preview-label=LABEL
--preview-label-pos=N Same as --border-label and --border-label-pos,
but for preview window
Scripting Scripting
-q, --query=STR Start the finder with the given query -q, --query=STR Start the finder with the given query
@@ -131,6 +143,12 @@ const (
byEnd byEnd
) )
type heightSpec struct {
size float64
percent bool
auto bool
}
type sizeSpec struct { type sizeSpec struct {
size float64 size float64
percent bool percent bool
@@ -165,6 +183,12 @@ const (
infoHidden infoHidden
) )
type labelOpts struct {
label string
column int
bottom bool
}
type previewOpts struct { type previewOpts struct {
command string command string
position windowPosition position windowPosition
@@ -180,6 +204,27 @@ type previewOpts struct {
alternative *previewOpts alternative *previewOpts
} }
func parseLabelPosition(opts *labelOpts, arg string) {
opts.column = 0
opts.bottom = false
for _, token := range splitRegexp.Split(strings.ToLower(arg), -1) {
switch token {
case "center":
opts.column = 0
case "bottom":
opts.bottom = true
case "top":
opts.bottom = false
default:
opts.column = atoi(token)
}
}
}
func (a previewOpts) aboveOrBelow() bool {
return a.size.size > 0 && (a.position == posUp || a.position == posDown)
}
func (a previewOpts) sameLayout(b previewOpts) bool { 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 && 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) || (a.alternative != nil && b.alternative != nil && a.alternative.sameLayout(*b.alternative) ||
@@ -194,6 +239,7 @@ func (a previewOpts) sameContentLayout(b previewOpts) bool {
type Options struct { type Options struct {
Fuzzy bool Fuzzy bool
FuzzyAlgo algo.Algo FuzzyAlgo algo.Algo
Scheme string
Extended bool Extended bool
Phony bool Phony bool
Case Case Case Case
@@ -210,7 +256,7 @@ type Options struct {
Theme *tui.ColorTheme Theme *tui.ColorTheme
Black bool Black bool
Bold bool Bold bool
Height sizeSpec Height heightSpec
MinHeight int MinHeight int
Layout layoutType Layout layoutType
Cycle bool Cycle bool
@@ -220,6 +266,7 @@ type Options struct {
ScrollOff int ScrollOff int
FileWord bool FileWord bool
InfoStyle infoStyle InfoStyle infoStyle
Separator *string
JumpLabels string JumpLabels string
Prompt string Prompt string
Pointer string Pointer string
@@ -245,6 +292,8 @@ type Options struct {
Margin [4]sizeSpec Margin [4]sizeSpec
Padding [4]sizeSpec Padding [4]sizeSpec
BorderShape tui.BorderShape BorderShape tui.BorderShape
BorderLabel labelOpts
PreviewLabel labelOpts
Unicode bool Unicode bool
Tabstop int Tabstop int
ClearOnExit bool ClearOnExit bool
@@ -259,6 +308,7 @@ func defaultOptions() *Options {
return &Options{ return &Options{
Fuzzy: true, Fuzzy: true,
FuzzyAlgo: algo.FuzzyMatchV2, FuzzyAlgo: algo.FuzzyMatchV2,
Scheme: "default",
Extended: true, Extended: true,
Phony: false, Phony: false,
Case: CaseSmart, Case: CaseSmart,
@@ -284,6 +334,7 @@ func defaultOptions() *Options {
ScrollOff: 0, ScrollOff: 0,
FileWord: false, FileWord: false,
InfoStyle: infoDefault, InfoStyle: infoDefault,
Separator: nil,
JumpLabels: defaultJumpLabels, JumpLabels: defaultJumpLabels,
Prompt: "> ", Prompt: "> ",
Pointer: ">", Pointer: ">",
@@ -310,6 +361,8 @@ func defaultOptions() *Options {
Padding: defaultMargin(), Padding: defaultMargin(),
Unicode: true, Unicode: true,
Tabstop: 8, Tabstop: 8,
BorderLabel: labelOpts{},
PreviewLabel: labelOpts{},
ClearOnExit: true, ClearOnExit: true,
Version: false} Version: false}
} }
@@ -441,12 +494,25 @@ func parseAlgo(str string) algo.Algo {
return algo.FuzzyMatchV2 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 { func parseBorder(str string, optional bool) tui.BorderShape {
switch str { switch str {
case "rounded": case "rounded":
return tui.BorderRounded return tui.BorderRounded
case "sharp": case "sharp":
return tui.BorderSharp return tui.BorderSharp
case "bold":
return tui.BorderBold
case "double":
return tui.BorderDouble
case "horizontal": case "horizontal":
return tui.BorderHorizontal return tui.BorderHorizontal
case "vertical": case "vertical":
@@ -465,7 +531,7 @@ func parseBorder(str string, optional bool) tui.BorderShape {
if optional && str == "" { if optional && str == "" {
return tui.BorderRounded return tui.BorderRounded
} }
errorExit("invalid border style (expected: rounded|sharp|horizontal|vertical|top|bottom|left|right|none)") errorExit("invalid border style (expected: rounded|sharp|bold|double|horizontal|vertical|top|bottom|left|right|none)")
} }
return tui.BorderNone return tui.BorderNone
} }
@@ -520,6 +586,8 @@ func parseKeyChords(str string, message string) map[tui.Event]string {
add(tui.Change) add(tui.Change)
case "backward-eof": case "backward-eof":
add(tui.BackwardEOF) add(tui.BackwardEOF)
case "start":
add(tui.Start)
case "alt-enter", "alt-return": case "alt-enter", "alt-return":
chords[tui.CtrlAltKey('m')] = key chords[tui.CtrlAltKey('m')] = key
case "alt-space": case "alt-space":
@@ -700,6 +768,8 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
cattr.Attr |= tui.Blink cattr.Attr |= tui.Blink
case "reverse": case "reverse":
cattr.Attr |= tui.Reverse cattr.Attr |= tui.Reverse
case "strikethrough":
cattr.Attr |= tui.StrikeThrough
case "black": case "black":
cattr.Color = tui.Color(0) cattr.Color = tui.Color(0)
case "red": case "red":
@@ -771,6 +841,10 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
mergeAttr(&theme.CurrentMatch) mergeAttr(&theme.CurrentMatch)
case "border": case "border":
mergeAttr(&theme.Border) mergeAttr(&theme.Border)
case "separator":
mergeAttr(&theme.Separator)
case "label":
mergeAttr(&theme.BorderLabel)
case "prompt": case "prompt":
mergeAttr(&theme.Prompt) mergeAttr(&theme.Prompt)
case "spinner": case "spinner":
@@ -791,7 +865,10 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
return theme return theme
} }
var executeRegexp *regexp.Regexp var (
executeRegexp *regexp.Regexp
splitRegexp *regexp.Regexp
)
func firstKey(keymap map[tui.Event]string) tui.Event { func firstKey(keymap map[tui.Event]string) tui.Event {
for k := range keymap { for k := range keymap {
@@ -811,6 +888,7 @@ func init() {
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|') // "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
executeRegexp = regexp.MustCompile( executeRegexp = regexp.MustCompile(
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|change-preview-window|change-preview|(?:re|un)bind):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|change-preview-window|change-preview|(?:re|un)bind)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`) `(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|change-preview-window|change-preview|(?:re|un)bind):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|change-preview-window|change-preview|(?:re|un)bind)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
splitRegexp = regexp.MustCompile("[,:]+")
} }
func parseKeymap(keymap map[tui.Event][]*action, str string) { func parseKeymap(keymap map[tui.Event][]*action, str string) {
@@ -1063,11 +1141,6 @@ func parseKeymap(keymap map[tui.Event][]*action, str string) {
} }
if t == actUnbind || t == actRebind { if t == actUnbind || t == actRebind {
parseKeyChords(actionArg, spec[0:offset]+" target required") parseKeyChords(actionArg, spec[0:offset]+" target required")
} else if t == actChangePreviewWindow {
opts := previewOpts{}
for _, arg := range strings.Split(actionArg, "|") {
parsePreviewWindow(&opts, arg)
}
} }
} }
} }
@@ -1147,9 +1220,17 @@ func parseSize(str string, maxPercent float64, label string) sizeSpec {
return sizeSpec{val, percent} 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") size := parseSize(str, 100, "height")
return size heightSpec.size = size.size
heightSpec.percent = size.percent
return heightSpec
} }
func parseLayout(str string) layoutType { func parseLayout(str string) layoutType {
@@ -1175,7 +1256,7 @@ func parseInfoStyle(str string) infoStyle {
case "hidden": case "hidden":
return infoHidden return infoHidden
default: default:
errorExit("invalid info style (expected: default / inline / hidden)") errorExit("invalid info style (expected: default|inline|hidden)")
} }
return infoDefault return infoDefault
} }
@@ -1222,15 +1303,19 @@ func parsePreviewWindow(opts *previewOpts, input string) {
opts.border = tui.BorderRounded opts.border = tui.BorderRounded
case "sharp", "border-sharp": case "sharp", "border-sharp":
opts.border = tui.BorderSharp opts.border = tui.BorderSharp
case "border-bold":
opts.border = tui.BorderBold
case "border-double":
opts.border = tui.BorderDouble
case "noborder", "border-none": case "noborder", "border-none":
opts.border = tui.BorderNone opts.border = tui.BorderNone
case "border-horizontal": case "border-horizontal":
opts.border = tui.BorderHorizontal opts.border = tui.BorderHorizontal
case "border-vertical": case "border-vertical":
opts.border = tui.BorderVertical opts.border = tui.BorderVertical
case "border-top": case "border-up", "border-top":
opts.border = tui.BorderTop opts.border = tui.BorderTop
case "border-bottom": case "border-down", "border-bottom":
opts.border = tui.BorderBottom opts.border = tui.BorderBottom
case "border-left": case "border-left":
opts.border = tui.BorderLeft opts.border = tui.BorderLeft
@@ -1343,6 +1428,8 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Normalize = true opts.Normalize = true
case "--algo": case "--algo":
opts.FuzzyAlgo = parseAlgo(nextString(allArgs, &i, "algorithm required (v1|v2)")) 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": case "--expect":
for k, v := range parseKeyChords(nextString(allArgs, &i, "key names required"), "key names required") { for k, v := range parseKeyChords(nextString(allArgs, &i, "key names required"), "key names required") {
opts.Expect[k] = v opts.Expect[k] = v
@@ -1442,6 +1529,12 @@ func parseOptions(opts *Options, allArgs []string) {
opts.InfoStyle = infoInline opts.InfoStyle = infoInline
case "--no-inline-info": case "--no-inline-info":
opts.InfoStyle = infoDefault opts.InfoStyle = infoDefault
case "--separator":
separator := nextString(allArgs, &i, "separator character required")
opts.Separator = &separator
case "--no-separator":
nosep := ""
opts.Separator = &nosep
case "--jump-labels": case "--jump-labels":
opts.JumpLabels = nextString(allArgs, &i, "label characters required") opts.JumpLabels = nextString(allArgs, &i, "label characters required")
validateJumpLabels = true validateJumpLabels = true
@@ -1510,11 +1603,11 @@ func parseOptions(opts *Options, allArgs []string) {
parsePreviewWindow(&opts.Preview, 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]")) 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": case "--height":
opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]")) opts.Height = parseHeight(nextString(allArgs, &i, "height required: [~]HEIGHT[%]"))
case "--min-height": case "--min-height":
opts.MinHeight = nextInt(allArgs, &i, "height required: HEIGHT") opts.MinHeight = nextInt(allArgs, &i, "height required: HEIGHT")
case "--no-height": case "--no-height":
opts.Height = sizeSpec{} opts.Height = heightSpec{}
case "--no-margin": case "--no-margin":
opts.Margin = defaultMargin() opts.Margin = defaultMargin()
case "--no-padding": case "--no-padding":
@@ -1524,6 +1617,20 @@ func parseOptions(opts *Options, allArgs []string) {
case "--border": case "--border":
hasArg, arg := optionalNextString(allArgs, &i) hasArg, arg := optionalNextString(allArgs, &i)
opts.BorderShape = parseBorder(arg, !hasArg) opts.BorderShape = parseBorder(arg, !hasArg)
case "--no-border-label":
opts.BorderLabel.label = ""
case "--border-label":
opts.BorderLabel.label = nextString(allArgs, &i, "label required")
case "--border-label-pos":
pos := nextString(allArgs, &i, "label position required (positive or negative integer or 'center')")
parseLabelPosition(&opts.BorderLabel, pos)
case "--no-preview-label":
opts.PreviewLabel.label = ""
case "--preview-label":
opts.PreviewLabel.label = nextString(allArgs, &i, "preview label required")
case "--preview-label-pos":
pos := nextString(allArgs, &i, "preview label position required (positive or negative integer or 'center')")
parseLabelPosition(&opts.PreviewLabel, pos)
case "--no-unicode": case "--no-unicode":
opts.Unicode = false opts.Unicode = false
case "--unicode": case "--unicode":
@@ -1549,6 +1656,8 @@ func parseOptions(opts *Options, allArgs []string) {
default: default:
if match, value := optString(arg, "--algo="); match { if match, value := optString(arg, "--algo="); match {
opts.FuzzyAlgo = parseAlgo(value) 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 { } else if match, value := optString(arg, "-q", "--query="); match {
opts.Query = value opts.Query = value
} else if match, value := optString(arg, "-f", "--filter="); match { } else if match, value := optString(arg, "-f", "--filter="); match {
@@ -1557,6 +1666,14 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Delimiter = delimiterRegexp(value) opts.Delimiter = delimiterRegexp(value)
} else if match, value := optString(arg, "--border="); match { } else if match, value := optString(arg, "--border="); match {
opts.BorderShape = parseBorder(value, false) opts.BorderShape = parseBorder(value, false)
} else if match, value := optString(arg, "--border-label="); match {
opts.BorderLabel.label = value
} else if match, value := optString(arg, "--border-label-pos="); match {
parseLabelPosition(&opts.BorderLabel, value)
} else if match, value := optString(arg, "--preview-label="); match {
opts.PreviewLabel.label = value
} else if match, value := optString(arg, "--preview-label-pos="); match {
parseLabelPosition(&opts.PreviewLabel, value)
} else if match, value := optString(arg, "--prompt="); match { } else if match, value := optString(arg, "--prompt="); match {
opts.Prompt = value opts.Prompt = value
} else if match, value := optString(arg, "--pointer="); match { } else if match, value := optString(arg, "--pointer="); match {
@@ -1581,6 +1698,8 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Layout = parseLayout(value) opts.Layout = parseLayout(value)
} else if match, value := optString(arg, "--info="); match { } else if match, value := optString(arg, "--info="); match {
opts.InfoStyle = parseInfoStyle(value) opts.InfoStyle = parseInfoStyle(value)
} else if match, value := optString(arg, "--separator="); match {
opts.Separator = &value
} else if match, value := optString(arg, "--toggle-sort="); match { } else if match, value := optString(arg, "--toggle-sort="); match {
parseToggleSort(opts.Keymap, value) parseToggleSort(opts.Keymap, value)
} else if match, value := optString(arg, "--expect="); match { } else if match, value := optString(arg, "--expect="); match {
@@ -1692,6 +1811,7 @@ func postProcessOptions(opts *Options) {
} }
// Extend the default key map // Extend the default key map
previewEnabled := len(opts.Preview.command) > 0 || hasPreviewAction(opts)
keymap := defaultKeymap() keymap := defaultKeymap()
for key, actions := range opts.Keymap { for key, actions := range opts.Keymap {
var lastChangePreviewWindow *action var lastChangePreviewWindow *action
@@ -1702,8 +1822,18 @@ func postProcessOptions(opts *Options) {
opts.ToggleSort = true opts.ToggleSort = true
case actChangePreviewWindow: case actChangePreviewWindow:
lastChangePreviewWindow = act 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 // Re-organize actions so that we only keep the last change-preview-window
// and it comes first in the list. // and it comes first in the list.
// * change-preview-window(up,+10)+preview(sleep 3; cat {})+change-preview-window(up,+20) // * change-preview-window(up,+10)+preview(sleep 3; cat {})+change-preview-window(up,+20)
@@ -1721,6 +1851,19 @@ func postProcessOptions(opts *Options) {
} }
opts.Keymap = keymap 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 we're not using extended search mode, --nth option becomes irrelevant
// if it contains the whole range // if it contains the whole range
if !opts.Extended || len(opts.Nth) == 1 { if !opts.Extended || len(opts.Nth) == 1 {
@@ -1750,6 +1893,10 @@ func postProcessOptions(opts *Options) {
theme.Cursor = boldify(theme.Cursor) theme.Cursor = boldify(theme.Cursor)
theme.Spinner = boldify(theme.Spinner) theme.Spinner = boldify(theme.Spinner)
} }
if opts.Scheme != "default" {
processScheme(opts)
}
} }
func expectsArbitraryString(opt string) bool { func expectsArbitraryString(opt string) bool {

View File

@@ -100,15 +100,30 @@ type itemLine struct {
result Result result Result
} }
type fitpad struct {
fit int
pad int
}
var emptyLine = itemLine{} var emptyLine = itemLine{}
type labelPrinter func(tui.Window, int)
// Terminal represents terminal input/output // Terminal represents terminal input/output
type Terminal struct { type Terminal struct {
initDelay time.Duration initDelay time.Duration
infoStyle infoStyle infoStyle infoStyle
separator labelPrinter
separatorLen int
spinner []string spinner []string
prompt func() prompt func()
promptLen int promptLen int
borderLabel labelPrinter
borderLabelLen int
borderLabelOpts labelOpts
previewLabel labelPrinter
previewLabelLen int
previewLabelOpts labelOpts
pointer string pointer string
pointerLen int pointerLen int
pointerEmpty string pointerEmpty string
@@ -183,7 +198,7 @@ type Terminal struct {
prevLines []itemLine prevLines []itemLine
suppress bool suppress bool
sigstop bool sigstop bool
startChan chan bool startChan chan fitpad
killChan chan int killChan chan int
slab *util.Slab slab *util.Slab
theme *tui.ColorTheme theme *tui.ColorTheme
@@ -439,6 +454,13 @@ func makeSpinner(unicode bool) []string {
return []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 // NewTerminal returns new Terminal object
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal { func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
input := trimQuery(opts.Query) input := trimQuery(opts.Query)
@@ -465,7 +487,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
strongAttr = tui.AttrRegular strongAttr = tui.AttrRegular
} }
var renderer tui.Renderer 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 fullscreen {
if tui.HasFullscreenRenderer() { if tui.HasFullscreenRenderer() {
renderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse) renderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse)
@@ -475,24 +497,16 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
} }
} else { } else {
maxHeightFunc := func(termHeight int) int { maxHeightFunc := func(termHeight int) int {
var maxHeight int // Minimum height required to render fzf excluding margin and padding
if opts.Height.percent {
maxHeight = util.Max(int(opts.Height.size*float64(termHeight)/100.0), opts.MinHeight)
} else {
maxHeight = int(opts.Height.size)
}
effectiveMinHeight := minHeight effectiveMinHeight := minHeight
if previewBox != nil && (opts.Preview.position == posUp || opts.Preview.position == posDown) { if previewBox != nil && opts.Preview.aboveOrBelow() {
effectiveMinHeight *= 2 effectiveMinHeight += 1 + borderLines(opts.Preview.border)
} }
if opts.InfoStyle != infoDefault { if opts.InfoStyle != infoDefault {
effectiveMinHeight-- effectiveMinHeight--
} }
if opts.BorderShape != tui.BorderNone { effectiveMinHeight += borderLines(opts.BorderShape)
effectiveMinHeight += 2 return util.Min(termHeight, util.Max(evaluateHeight(opts, termHeight), effectiveMinHeight))
}
return util.Min(termHeight, util.Max(maxHeight, effectiveMinHeight))
} }
renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, false, maxHeightFunc) renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, false, maxHeightFunc)
} }
@@ -510,6 +524,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
t := Terminal{ t := Terminal{
initDelay: delay, initDelay: delay,
infoStyle: opts.InfoStyle, infoStyle: opts.InfoStyle,
separator: nil,
spinner: makeSpinner(opts.Unicode), spinner: makeSpinner(opts.Unicode),
queryLen: [2]int{0, 0}, queryLen: [2]int{0, 0},
layout: opts.Layout, layout: opts.Layout,
@@ -540,6 +555,10 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
padding: opts.Padding, padding: opts.Padding,
unicode: opts.Unicode, unicode: opts.Unicode,
borderShape: opts.BorderShape, borderShape: opts.BorderShape,
borderLabel: nil,
borderLabelOpts: opts.BorderLabel,
previewLabel: nil,
previewLabelOpts: opts.PreviewLabel,
cleanExit: opts.ClearOnExit, cleanExit: opts.ClearOnExit,
paused: opts.Phony, paused: opts.Phony,
strong: strongAttr, strong: strongAttr,
@@ -572,7 +591,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
sigstop: false, sigstop: false,
slab: util.MakeSlab(slab16Size, slab32Size), slab: util.MakeSlab(slab16Size, slab32Size),
theme: opts.Theme, theme: opts.Theme,
startChan: make(chan bool, 1), startChan: make(chan fitpad, 1),
killChan: make(chan int), killChan: make(chan int),
tui: renderer, tui: renderer,
initFunc: func() { renderer.Init() }, initFunc: func() { renderer.Init() },
@@ -583,10 +602,106 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
// Pre-calculated empty pointer and marker signs // Pre-calculated empty pointer and marker signs
t.pointerEmpty = strings.Repeat(" ", t.pointerLen) t.pointerEmpty = strings.Repeat(" ", t.pointerLen)
t.markerEmpty = strings.Repeat(" ", t.markerLen) t.markerEmpty = strings.Repeat(" ", t.markerLen)
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(opts.BorderLabel.label, &tui.ColBorderLabel, false)
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(opts.PreviewLabel.label, &tui.ColBorderLabel, false)
if opts.Separator == nil || len(*opts.Separator) > 0 {
bar := "─"
if opts.Separator != nil {
bar = *opts.Separator
} else if !t.unicode {
bar = "-"
}
t.separator, t.separatorLen = t.ansiLabelPrinter(bar, &tui.ColSeparator, true)
}
return &t return &t
} }
func borderLines(shape tui.BorderShape) int {
switch shape {
case tui.BorderHorizontal, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderDouble:
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) ansiLabelPrinter(str string, color *tui.ColorPair, fill bool) (labelPrinter, int) {
// Nothing to do
if len(str) == 0 {
return nil, 0
}
// Extract ANSI color codes
text, colors, _ := extractColor(str, nil, nil)
runes := []rune(text)
// Simpler printer for strings without ANSI colors or tab characters
if colors == nil && strings.IndexRune(str, '\t') < 0 {
length := runewidth.StringWidth(str)
if length == 0 {
return nil, 0
}
printFn := func(window tui.Window, limit int) {
if length > limit {
trimmedRunes, _ := t.trimRight(runes, limit)
window.CPrint(*color, string(trimmedRunes))
} else if fill {
window.CPrint(*color, util.RepeatToFill(str, length, limit))
} else {
window.CPrint(*color, str)
}
}
return printFn, len(text)
}
// Printer that correctly handles ANSI color codes and tab characters
item := &Item{text: util.RunesToChars(runes), colors: colors}
length := t.displayWidth(runes)
if length == 0 {
return nil, 0
}
result := Result{item: item}
var offsets []colorOffset
printFn := func(window tui.Window, limit int) {
if offsets == nil {
// tui.Col* are not initialized until renderer.Init()
offsets = result.colorOffsets(nil, t.theme, *color, *color, false)
}
for limit > 0 {
if length > limit {
trimmedRunes, _ := t.trimRight(runes, limit)
t.printColoredString(window, trimmedRunes, offsets, *color)
break
} else if fill {
t.printColoredString(window, runes, offsets, *color)
limit -= length
} else {
t.printColoredString(window, runes, offsets, *color)
break
}
}
}
return printFn, length
}
func (t *Terminal) parsePrompt(prompt string) (func(), int) { func (t *Terminal) parsePrompt(prompt string) (func(), int) {
var state *ansiState var state *ansiState
trimmed, colors, _ := extractColor(prompt, state, nil) trimmed, colors, _ := extractColor(prompt, state, nil)
@@ -725,22 +840,23 @@ func (t *Terminal) displayWidth(runes []rune) int {
const ( const (
minWidth = 4 minWidth = 4
minHeight = 4 minHeight = 3
) )
func calculateSize(base int, size sizeSpec, occupied int, minSize int, pad int) int { func calculateSize(base int, size sizeSpec, occupied int, minSize int, pad int) int {
max := base - occupied max := base - occupied
if max < minSize {
max = minSize
}
if size.percent { if size.percent {
return util.Constrain(int(float64(base)*0.01*size.size), minSize, max) return util.Constrain(int(float64(base)*0.01*size.size), minSize, max)
} }
return util.Constrain(int(size.size)+pad, 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() screenWidth := t.tui.MaxX()
screenHeight := t.tui.MaxY() screenHeight := t.tui.MaxY()
t.prevLines = make([]itemLine, screenHeight)
marginInt := [4]int{} // TRBL marginInt := [4]int{} // TRBL
paddingInt := [4]int{} // TRBL paddingInt := [4]int{} // TRBL
sizeSpecToInt := func(index int, spec sizeSpec) int { sizeSpecToInt := func(index int, spec sizeSpec) int {
@@ -782,14 +898,16 @@ func (t *Terminal) resizeWindows() {
if idx == 3 { if idx == 3 {
extraMargin[idx] += 2 extraMargin[idx] += 2
} }
case tui.BorderRounded, tui.BorderSharp: case tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderDouble:
extraMargin[idx] += 1 + idx%2 extraMargin[idx] += 1 + idx%2
} }
marginInt[idx] = sizeSpecToInt(idx, sizeSpec) + extraMargin[idx] marginInt[idx] = sizeSpecToInt(idx, sizeSpec) + extraMargin[idx]
} }
adjust := func(idx1 int, idx2 int, max int, min int) { adjust := func(idx1 int, idx2 int, max int, min int) {
if max >= min { if min > max {
min = max
}
margin := marginInt[idx1] + marginInt[idx2] + paddingInt[idx1] + paddingInt[idx2] margin := marginInt[idx1] + marginInt[idx2] + paddingInt[idx1] + paddingInt[idx2]
if max-margin < min { if max-margin < min {
desired := max - min desired := max - min
@@ -799,21 +917,36 @@ func (t *Terminal) resizeWindows() {
marginInt[idx2] = util.Max(extraMargin[idx2], desired*marginInt[idx2]/margin) marginInt[idx2] = util.Max(extraMargin[idx2], desired*marginInt[idx2]/margin)
} }
} }
}
previewVisible := t.isPreviewEnabled() && t.previewOpts.size.size > 0
minAreaWidth := minWidth minAreaWidth := minWidth
minAreaHeight := minHeight minAreaHeight := minHeight
if previewVisible { if t.noInfoLine() {
minAreaHeight -= 1
}
if t.isPreviewVisible() {
minPreviewHeight := 1 + borderLines(t.previewOpts.border)
minPreviewWidth := 5
switch t.previewOpts.position { switch t.previewOpts.position {
case posUp, posDown: case posUp, posDown:
minAreaHeight *= 2 minAreaHeight += minPreviewHeight
minAreaWidth = util.Max(minPreviewWidth, minAreaWidth)
case posLeft, posRight: case posLeft, posRight:
minAreaWidth *= 2 minAreaWidth += minPreviewWidth
minAreaHeight = util.Max(minPreviewHeight, minAreaHeight)
} }
} }
adjust(1, 3, screenWidth, minAreaWidth) adjust(1, 3, screenWidth, minAreaWidth)
adjust(0, 2, screenHeight, minAreaHeight) 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 { if t.border != nil {
t.border.Close() t.border.Close()
} }
@@ -832,8 +965,6 @@ func (t *Terminal) resizeWindows() {
// Reset preview version so that full redraw occurs // Reset preview version so that full redraw occurs
t.previewed.version = 0 t.previewed.version = 0
width := screenWidth - marginInt[1] - marginInt[3]
height := screenHeight - marginInt[0] - marginInt[2]
switch t.borderShape { switch t.borderShape {
case tui.BorderHorizontal: case tui.BorderHorizontal:
t.border = t.tui.NewWindow( t.border = t.tui.NewWindow(
@@ -859,22 +990,22 @@ func (t *Terminal) resizeWindows() {
t.border = t.tui.NewWindow( t.border = t.tui.NewWindow(
marginInt[0], marginInt[3], width+2, height, marginInt[0], marginInt[3], width+2, height,
false, tui.MakeBorderStyle(tui.BorderRight, t.unicode)) false, tui.MakeBorderStyle(tui.BorderRight, t.unicode))
case tui.BorderRounded, tui.BorderSharp: case tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderDouble:
t.border = t.tui.NewWindow( t.border = t.tui.NewWindow(
marginInt[0]-1, marginInt[3]-2, width+4, height+2, marginInt[0]-1, marginInt[3]-2, width+4, height+2,
false, tui.MakeBorderStyle(t.borderShape, t.unicode)) false, tui.MakeBorderStyle(t.borderShape, t.unicode))
} }
// Add padding // Add padding to margin
for idx, val := range paddingInt { for idx, val := range paddingInt {
marginInt[idx] += val marginInt[idx] += val
} }
width = screenWidth - marginInt[1] - marginInt[3] width -= paddingInt[1] + paddingInt[3]
height = screenHeight - marginInt[0] - marginInt[2] height -= paddingInt[0] + paddingInt[2]
// Set up preview window // Set up preview window
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode) noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
if previewVisible { if t.isPreviewVisible() {
var resizePreviewWindows func(previewOpts previewOpts) var resizePreviewWindows func(previewOpts previewOpts)
resizePreviewWindows = func(previewOpts previewOpts) { resizePreviewWindows = func(previewOpts previewOpts) {
hasThreshold := previewOpts.threshold > 0 && previewOpts.alternative != nil hasThreshold := previewOpts.threshold > 0 && previewOpts.alternative != nil
@@ -889,7 +1020,7 @@ func (t *Terminal) resizeWindows() {
} }
t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder) t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
switch previewOpts.border { switch previewOpts.border {
case tui.BorderSharp, tui.BorderRounded: case tui.BorderSharp, tui.BorderRounded, tui.BorderBold, tui.BorderDouble:
pwidth -= 4 pwidth -= 4
pheight -= 2 pheight -= 2
x += 2 x += 2
@@ -969,6 +1100,34 @@ func (t *Terminal) resizeWindows() {
width, width,
height, false, noBorder) height, false, noBorder)
} }
// Print border label
printLabel := func(window tui.Window, render labelPrinter, opts labelOpts, length int, borderShape tui.BorderShape) {
if window == nil || render == nil {
return
}
switch borderShape {
case tui.BorderHorizontal, tui.BorderTop, tui.BorderBottom, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderDouble:
var col int
if opts.column == 0 {
col = util.Max(0, (window.Width()-length)/2)
} else if opts.column < 0 {
col = util.Max(0, window.Width()+opts.column+1-length)
} else {
col = util.Min(opts.column-1, window.Width()-length)
}
row := 0
if borderShape == tui.BorderBottom || opts.bottom {
row = window.Height() - 1
}
window.Move(row, col)
render(window, window.Width())
}
}
printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape)
printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.previewOpts.border)
for i := 0; i < t.window.Height(); i++ { for i := 0; i < t.window.Height(); i++ {
t.window.MoveAndClear(i, 0) t.window.MoveAndClear(i, 0)
} }
@@ -1108,8 +1267,15 @@ func (t *Terminal) printInfo() {
if t.failed != nil && t.count == 0 { if t.failed != nil && t.count == 0 {
output = fmt.Sprintf("[Command failed: %s]", *t.failed) output = fmt.Sprintf("[Command failed: %s]", *t.failed)
} }
output = t.trimMessage(output, t.window.Width()-pos) maxWidth := t.window.Width() - pos
output = t.trimMessage(output, maxWidth)
t.window.CPrint(tui.ColInfo, output) t.window.CPrint(tui.ColInfo, output)
fillLength := maxWidth - len(output) - 2
if t.separatorLen > 0 && fillLength > 0 {
t.window.CPrint(tui.ColSeparator, " ")
t.separator(t.window, fillLength)
}
} }
func (t *Terminal) printHeader() { func (t *Terminal) printHeader() {
@@ -1348,6 +1514,11 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
displayWidth = t.displayWidthWithLimit(text, 0, displayWidth) displayWidth = t.displayWidthWithLimit(text, 0, displayWidth)
} }
t.printColoredString(t.window, text, offsets, colBase)
return displayWidth
}
func (t *Terminal) printColoredString(window tui.Window, text []rune, offsets []colorOffset, colBase tui.ColorPair) {
var index int32 var index int32
var substr string var substr string
var prefixWidth int var prefixWidth int
@@ -1357,11 +1528,11 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
e := util.Constrain32(offset.offset[1], index, maxOffset) e := util.Constrain32(offset.offset[1], index, maxOffset)
substr, prefixWidth = t.processTabs(text[index:b], prefixWidth) substr, prefixWidth = t.processTabs(text[index:b], prefixWidth)
t.window.CPrint(colBase, substr) window.CPrint(colBase, substr)
if b < e { if b < e {
substr, prefixWidth = t.processTabs(text[b:e], prefixWidth) substr, prefixWidth = t.processTabs(text[b:e], prefixWidth)
t.window.CPrint(offset.color, substr) window.CPrint(offset.color, substr)
} }
index = e index = e
@@ -1371,9 +1542,8 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
} }
if index < maxOffset { if index < maxOffset {
substr, _ = t.processTabs(text[index:], prefixWidth) substr, _ = t.processTabs(text[index:], prefixWidth)
t.window.CPrint(colBase, substr) window.CPrint(colBase, substr)
} }
return displayWidth
} }
func (t *Terminal) renderPreviewSpinner() { func (t *Terminal) renderPreviewSpinner() {
@@ -1863,6 +2033,10 @@ func (t *Terminal) isPreviewEnabled() bool {
return t.hasPreviewer() && t.previewer.enabled return t.hasPreviewer() && t.previewer.enabled
} }
func (t *Terminal) isPreviewVisible() bool {
return t.isPreviewEnabled() && t.previewOpts.size.size > 0
}
func (t *Terminal) hasPreviewWindow() bool { func (t *Terminal) hasPreviewWindow() bool {
return t.pwindow != nil && t.isPreviewEnabled() return t.pwindow != nil && t.isPreviewEnabled()
} }
@@ -1962,7 +2136,28 @@ func (t *Terminal) cancelPreview() {
// Loop is called to start Terminal I/O // Loop is called to start Terminal I/O
func (t *Terminal) Loop() { func (t *Terminal) Loop() {
// prof := profile.Start(profile.ProfilePath("/tmp/")) // 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 { // Late initialization
intChan := make(chan os.Signal, 1) intChan := make(chan os.Signal, 1)
signal.Notify(intChan, os.Interrupt, syscall.SIGTERM) signal.Notify(intChan, os.Interrupt, syscall.SIGTERM)
@@ -2291,13 +2486,21 @@ func (t *Terminal) Loop() {
}() }()
looping := true looping := true
_, startEvent := t.keymap[tui.Start.AsEvent()]
for looping { for looping {
var newCommand *string var newCommand *string
changed := false changed := false
beof := false beof := false
queryChanged := false queryChanged := false
event := t.tui.GetChar() var event tui.Event
if startEvent {
event = tui.Start.AsEvent()
startEvent = false
} else {
event = t.tui.GetChar()
}
t.mutex.Lock() t.mutex.Lock()
previousInput := t.input previousInput := t.input

View File

@@ -14,8 +14,8 @@ func (a Attr) Merge(b Attr) Attr {
const ( const (
AttrUndefined = Attr(0) AttrUndefined = Attr(0)
AttrRegular = Attr(1 << 7) AttrRegular = Attr(1 << 8)
AttrClear = Attr(1 << 8) AttrClear = Attr(1 << 9)
Bold = Attr(1) Bold = Attr(1)
Dim = Attr(1 << 1) Dim = Attr(1 << 1)
@@ -24,9 +24,11 @@ const (
Blink = Attr(1 << 4) Blink = Attr(1 << 4)
Blink2 = Attr(1 << 5) Blink2 = Attr(1 << 5)
Reverse = Attr(1 << 6) Reverse = Attr(1 << 6)
StrikeThrough = Attr(1 << 7)
) )
func (r *FullscreenRenderer) Init() {} func (r *FullscreenRenderer) Init() {}
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
func (r *FullscreenRenderer) Pause(bool) {} func (r *FullscreenRenderer) Pause(bool) {}
func (r *FullscreenRenderer) Resume(bool, bool) {} func (r *FullscreenRenderer) Resume(bool, bool) {}
func (r *FullscreenRenderer) Clear() {} func (r *FullscreenRenderer) Clear() {}

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() { func (r *LightRenderer) makeSpace() {
r.stderr("\n") r.stderr("\n")
r.csi("G") r.csi("G")
@@ -676,6 +680,9 @@ func (r *LightRenderer) MaxX() int {
} }
func (r *LightRenderer) MaxY() int { func (r *LightRenderer) MaxY() int {
if r.height == 0 {
r.updateTerminalSize()
}
return r.height return r.height
} }
@@ -705,7 +712,7 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, prev
func (w *LightWindow) drawBorder() { func (w *LightWindow) drawBorder() {
switch w.border.shape { switch w.border.shape {
case BorderRounded, BorderSharp: case BorderRounded, BorderSharp, BorderBold, BorderDouble:
w.drawBorderAround() w.drawBorderAround()
case BorderHorizontal: case BorderHorizontal:
w.drawBorderHorizontal(true, true) w.drawBorderHorizontal(true, true)
@@ -856,6 +863,9 @@ func attrCodes(attr Attr) []string {
if (attr & Reverse) > 0 { if (attr & Reverse) > 0 {
codes = append(codes, "7") codes = append(codes, "7")
} }
if (attr & StrikeThrough) > 0 {
codes = append(codes, "9")
}
return codes return codes
} }

View File

@@ -8,8 +8,8 @@ import (
"runtime" "runtime"
"github.com/gdamore/tcell" "github.com/gdamore/tcell/v2"
"github.com/gdamore/tcell/encoding" "github.com/gdamore/tcell/v2/encoding"
"github.com/mattn/go-runewidth" "github.com/mattn/go-runewidth"
"github.com/rivo/uniseg" "github.com/rivo/uniseg"
@@ -19,12 +19,24 @@ func HasFullscreenRenderer() bool {
return true return true
} }
func (p ColorPair) style() tcell.Style { func asTcellColor(color Color) tcell.Color {
style := tcell.StyleDefault if color == colDefault {
return style.Foreground(tcell.Color(p.Fg())).Background(tcell.Color(p.Bg())) return tcell.ColorDefault
} }
type Attr tcell.Style value := uint64(tcell.ColorValid) + uint64(color)
if color.is24() {
value = value | uint64(tcell.ColorIsRGB)
}
return tcell.Color(value)
}
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 { type TcellWindow struct {
color bool color bool
@@ -63,8 +75,6 @@ func (w *TcellWindow) Refresh() {
} }
w.lastX = 0 w.lastX = 0
w.lastY = 0 w.lastY = 0
w.drawBorder()
} }
func (w *TcellWindow) FinishFill() { func (w *TcellWindow) FinishFill() {
@@ -77,6 +87,7 @@ const (
Blink = Attr(tcell.AttrBlink) Blink = Attr(tcell.AttrBlink)
Reverse = Attr(tcell.AttrReverse) Reverse = Attr(tcell.AttrReverse)
Underline = Attr(tcell.AttrUnderline) Underline = Attr(tcell.AttrUnderline)
StrikeThrough = Attr(tcell.AttrStrikeThrough)
Italic = Attr(tcell.AttrItalic) Italic = Attr(tcell.AttrItalic)
) )
@@ -86,6 +97,8 @@ const (
AttrClear = Attr(1 << 8) AttrClear = Attr(1 << 8)
) )
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
func (r *FullscreenRenderer) defaultTheme() *ColorTheme { func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
if _screen.Colors() >= 256 { if _screen.Colors() >= 256 {
return Dark256 return Dark256
@@ -502,7 +515,7 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int,
if preview { if preview {
normal = ColPreview normal = ColPreview
} }
return &TcellWindow{ w := &TcellWindow{
color: r.theme.Colored, color: r.theme.Colored,
preview: preview, preview: preview,
top: top, top: top,
@@ -511,6 +524,8 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int,
height: height, height: height,
normal: normal, normal: normal,
borderStyle: borderStyle} borderStyle: borderStyle}
w.drawBorder()
return w
} }
func (w *TcellWindow) Close() { func (w *TcellWindow) Close() {
@@ -561,6 +576,7 @@ func (w *TcellWindow) printString(text string, pair ColorPair) {
style = style. style = style.
Reverse(a&Attr(tcell.AttrReverse) != 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). Italic(a&Attr(tcell.AttrItalic) != 0).
Blink(a&Attr(tcell.AttrBlink) != 0). Blink(a&Attr(tcell.AttrBlink) != 0).
Dim(a&Attr(tcell.AttrDim) != 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). Dim(a&Attr(tcell.AttrDim) != 0).
Reverse(a&Attr(tcell.AttrReverse) != 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) Italic(a&Attr(tcell.AttrItalic) != 0)
gr := uniseg.NewGraphemes(text) gr := uniseg.NewGraphemes(text)
@@ -688,31 +705,31 @@ func (w *TcellWindow) drawBorder() {
} }
switch shape { switch shape {
case BorderRounded, BorderSharp, BorderHorizontal, BorderTop: case BorderRounded, BorderSharp, BorderBold, BorderDouble, BorderHorizontal, BorderTop:
for x := left; x < right; x++ { for x := left; x < right; x++ {
_screen.SetContent(x, top, w.borderStyle.horizontal, nil, style) _screen.SetContent(x, top, w.borderStyle.horizontal, nil, style)
} }
} }
switch shape { switch shape {
case BorderRounded, BorderSharp, BorderHorizontal, BorderBottom: case BorderRounded, BorderSharp, BorderBold, BorderDouble, BorderHorizontal, BorderBottom:
for x := left; x < right; x++ { for x := left; x < right; x++ {
_screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style) _screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style)
} }
} }
switch shape { switch shape {
case BorderRounded, BorderSharp, BorderVertical, BorderLeft: case BorderRounded, BorderSharp, BorderBold, BorderDouble, BorderVertical, BorderLeft:
for y := top; y < bot; y++ { for y := top; y < bot; y++ {
_screen.SetContent(left, y, w.borderStyle.vertical, nil, style) _screen.SetContent(left, y, w.borderStyle.vertical, nil, style)
} }
} }
switch shape { switch shape {
case BorderRounded, BorderSharp, BorderVertical, BorderRight: case BorderRounded, BorderSharp, BorderBold, BorderDouble, BorderVertical, BorderRight:
for y := top; y < bot; y++ { for y := top; y < bot; y++ {
_screen.SetContent(right-1, y, w.borderStyle.vertical, nil, style) _screen.SetContent(right-1, y, w.borderStyle.vertical, nil, style)
} }
} }
switch shape { switch shape {
case BorderRounded, BorderSharp: case BorderRounded, BorderSharp, BorderBold, BorderDouble:
_screen.SetContent(left, top, w.borderStyle.topLeft, nil, style) _screen.SetContent(left, top, w.borderStyle.topLeft, nil, style)
_screen.SetContent(right-1, top, w.borderStyle.topRight, nil, style) _screen.SetContent(right-1, top, w.borderStyle.topRight, nil, style)
_screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style) _screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style)

View File

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

View File

@@ -90,6 +90,7 @@ const (
Change Change
BackwardEOF BackwardEOF
Start
AltBS AltBS
@@ -266,7 +267,9 @@ type ColorTheme struct {
Cursor ColorAttr Cursor ColorAttr
Selected ColorAttr Selected ColorAttr
Header ColorAttr Header ColorAttr
Separator ColorAttr
Border ColorAttr Border ColorAttr
BorderLabel ColorAttr
} }
type Event struct { type Event struct {
@@ -291,6 +294,8 @@ const (
BorderNone BorderShape = iota BorderNone BorderShape = iota
BorderRounded BorderRounded
BorderSharp BorderSharp
BorderBold
BorderDouble
BorderHorizontal BorderHorizontal
BorderVertical BorderVertical
BorderTop BorderTop
@@ -312,18 +317,19 @@ type BorderStyle struct {
type BorderCharacter int type BorderCharacter int
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle { func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
if unicode { if !unicode {
if shape == BorderRounded {
return BorderStyle{ return BorderStyle{
shape: shape, shape: shape,
horizontal: '', horizontal: '-',
vertical: '', vertical: '|',
topLeft: '', topLeft: '+',
topRight: '', topRight: '+',
bottomLeft: '', bottomLeft: '+',
bottomRight: '', bottomRight: '+',
} }
} }
switch shape {
case BorderSharp:
return BorderStyle{ return BorderStyle{
shape: shape, shape: shape,
horizontal: '─', horizontal: '─',
@@ -333,15 +339,35 @@ func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
bottomLeft: '└', bottomLeft: '└',
bottomRight: '┘', bottomRight: '┘',
} }
case BorderBold:
return BorderStyle{
shape: shape,
horizontal: '━',
vertical: '┃',
topLeft: '┏',
topRight: '┓',
bottomLeft: '┗',
bottomRight: '┛',
}
case BorderDouble:
return BorderStyle{
shape: shape,
horizontal: '═',
vertical: '║',
topLeft: '╔',
topRight: '╗',
bottomLeft: '╚',
bottomRight: '╝',
}
} }
return BorderStyle{ return BorderStyle{
shape: shape, shape: shape,
horizontal: '-', horizontal: '',
vertical: '|', vertical: '',
topLeft: '+', topLeft: '',
topRight: '+', topRight: '',
bottomLeft: '+', bottomLeft: '',
bottomRight: '+', bottomRight: '',
} }
} }
@@ -358,6 +384,7 @@ func MakeTransparentBorder() BorderStyle {
type Renderer interface { type Renderer interface {
Init() Init()
Resize(maxHeightFunc func(int) int)
Pause(clear bool) Pause(clear bool)
Resume(clear bool, sigcont bool) Resume(clear bool, sigcont bool)
Clear() Clear()
@@ -436,9 +463,11 @@ var (
ColSpinner ColorPair ColSpinner ColorPair
ColInfo ColorPair ColInfo ColorPair
ColHeader ColorPair ColHeader ColorPair
ColSeparator ColorPair
ColBorder ColorPair ColBorder ColorPair
ColPreview ColorPair ColPreview ColorPair
ColPreviewBorder ColorPair ColPreviewBorder ColorPair
ColBorderLabel ColorPair
) )
func EmptyTheme() *ColorTheme { func EmptyTheme() *ColorTheme {
@@ -461,7 +490,10 @@ func EmptyTheme() *ColorTheme {
Cursor: ColorAttr{colUndefined, AttrUndefined}, Cursor: ColorAttr{colUndefined, AttrUndefined},
Selected: ColorAttr{colUndefined, AttrUndefined}, Selected: ColorAttr{colUndefined, AttrUndefined},
Header: ColorAttr{colUndefined, AttrUndefined}, Header: ColorAttr{colUndefined, AttrUndefined},
Border: ColorAttr{colUndefined, AttrUndefined}} Separator: ColorAttr{colUndefined, AttrUndefined},
Border: ColorAttr{colUndefined, AttrUndefined},
BorderLabel: ColorAttr{colUndefined, AttrUndefined},
}
} }
func NoColorTheme() *ColorTheme { func NoColorTheme() *ColorTheme {
@@ -484,7 +516,10 @@ func NoColorTheme() *ColorTheme {
Cursor: ColorAttr{colDefault, AttrRegular}, Cursor: ColorAttr{colDefault, AttrRegular},
Selected: ColorAttr{colDefault, AttrRegular}, Selected: ColorAttr{colDefault, AttrRegular},
Header: ColorAttr{colDefault, AttrRegular}, Header: ColorAttr{colDefault, AttrRegular},
Border: ColorAttr{colDefault, AttrRegular}} Separator: ColorAttr{colDefault, AttrRegular},
Border: ColorAttr{colDefault, AttrRegular},
BorderLabel: ColorAttr{colDefault, AttrRegular},
}
} }
func errorExit(message string) { func errorExit(message string) {
@@ -512,7 +547,10 @@ func init() {
Cursor: ColorAttr{colRed, AttrUndefined}, Cursor: ColorAttr{colRed, AttrUndefined},
Selected: ColorAttr{colMagenta, AttrUndefined}, Selected: ColorAttr{colMagenta, AttrUndefined},
Header: ColorAttr{colCyan, AttrUndefined}, Header: ColorAttr{colCyan, AttrUndefined},
Border: ColorAttr{colBlack, AttrUndefined}} Separator: ColorAttr{colBlack, AttrUndefined},
Border: ColorAttr{colBlack, AttrUndefined},
BorderLabel: ColorAttr{colWhite, AttrUndefined},
}
Dark256 = &ColorTheme{ Dark256 = &ColorTheme{
Colored: true, Colored: true,
Input: ColorAttr{colDefault, AttrUndefined}, Input: ColorAttr{colDefault, AttrUndefined},
@@ -532,7 +570,10 @@ func init() {
Cursor: ColorAttr{161, AttrUndefined}, Cursor: ColorAttr{161, AttrUndefined},
Selected: ColorAttr{168, AttrUndefined}, Selected: ColorAttr{168, AttrUndefined},
Header: ColorAttr{109, AttrUndefined}, Header: ColorAttr{109, AttrUndefined},
Border: ColorAttr{59, AttrUndefined}} Separator: ColorAttr{59, AttrUndefined},
Border: ColorAttr{59, AttrUndefined},
BorderLabel: ColorAttr{145, AttrUndefined},
}
Light256 = &ColorTheme{ Light256 = &ColorTheme{
Colored: true, Colored: true,
Input: ColorAttr{colDefault, AttrUndefined}, Input: ColorAttr{colDefault, AttrUndefined},
@@ -552,7 +593,10 @@ func init() {
Cursor: ColorAttr{161, AttrUndefined}, Cursor: ColorAttr{161, AttrUndefined},
Selected: ColorAttr{168, AttrUndefined}, Selected: ColorAttr{168, AttrUndefined},
Header: ColorAttr{31, AttrUndefined}, Header: ColorAttr{31, AttrUndefined},
Border: ColorAttr{145, AttrUndefined}} Separator: ColorAttr{145, AttrUndefined},
Border: ColorAttr{145, AttrUndefined},
BorderLabel: ColorAttr{59, AttrUndefined},
}
} }
func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) { func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
@@ -587,7 +631,9 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
theme.Cursor = o(baseTheme.Cursor, theme.Cursor) theme.Cursor = o(baseTheme.Cursor, theme.Cursor)
theme.Selected = o(baseTheme.Selected, theme.Selected) theme.Selected = o(baseTheme.Selected, theme.Selected)
theme.Header = o(baseTheme.Header, theme.Header) theme.Header = o(baseTheme.Header, theme.Header)
theme.Separator = o(baseTheme.Separator, theme.Separator)
theme.Border = o(baseTheme.Border, theme.Border) theme.Border = o(baseTheme.Border, theme.Border)
theme.BorderLabel = o(baseTheme.BorderLabel, theme.BorderLabel)
initPalette(theme) initPalette(theme)
} }
@@ -619,7 +665,9 @@ func initPalette(theme *ColorTheme) {
ColSpinner = pair(theme.Spinner, theme.Bg) ColSpinner = pair(theme.Spinner, theme.Bg)
ColInfo = pair(theme.Info, theme.Bg) ColInfo = pair(theme.Info, theme.Bg)
ColHeader = pair(theme.Header, theme.Bg) ColHeader = pair(theme.Header, theme.Bg)
ColSeparator = pair(theme.Separator, theme.Bg)
ColBorder = pair(theme.Border, theme.Bg) ColBorder = pair(theme.Border, theme.Bg)
ColBorderLabel = pair(theme.BorderLabel, theme.Bg)
ColPreview = pair(theme.PreviewFg, theme.PreviewBg) ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
ColPreviewBorder = pair(theme.Border, theme.PreviewBg) ColPreviewBorder = pair(theme.Border, theme.PreviewBg)
} }

View File

@@ -153,3 +153,23 @@ func Once(nextResponse bool) func() bool {
return prevState 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 -= runewidth.RuneWidth(r)
if rest < 0 {
break
}
output += string(r)
if rest == 0 {
break
}
}
}
return output
}

View File

@@ -192,6 +192,13 @@ class TestBase < Minitest::Test
tmux.prepare tmux.prepare
end end
alias assert_equal_org assert_equal
def assert_equal(expected, actual)
# Ignore info separator
actual = actual&.sub(/\s*─+$/, '') if actual.is_a?(String) && actual&.match?(%r{\d+/\d+})
assert_equal_org(expected, actual)
end
def fzf(*opts) def fzf(*opts)
fzf!(*opts) + " > #{tempname}.tmp; mv #{tempname}.tmp #{tempname}" fzf!(*opts) + " > #{tempname}.tmp; mv #{tempname}.tmp #{tempname}"
end end
@@ -255,7 +262,7 @@ class TestGoFZF < TestBase
def test_fzf_default_command_failure def test_fzf_default_command_failure
tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', 'FZF_DEFAULT_COMMAND=false'), :Enter tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', 'FZF_DEFAULT_COMMAND=false'), :Enter
tmux.until { |lines| assert_equal ' [Command failed: false]', lines[-2] } tmux.until { |lines| assert_includes lines[-2], ' [Command failed: false]' }
tmux.send_keys :Enter tmux.send_keys :Enter
end end
@@ -447,7 +454,7 @@ class TestGoFZF < TestBase
def test_scroll def test_scroll
[true, false].each do |rev| [true, false].each do |rev|
tmux.send_keys "seq 1 100 | #{fzf(rev && :reverse)}", :Enter tmux.send_keys "seq 1 100 | #{fzf(rev && :reverse)}", :Enter
tmux.until { |lines| assert_includes lines, ' 100/100' } tmux.until { |lines| assert_equal ' 100/100', lines[rev ? 1 : -2] }
tmux.send_keys(*Array.new(110) { rev ? :Down : :Up }) tmux.send_keys(*Array.new(110) { rev ? :Down : :Up })
tmux.until { |lines| assert_includes lines, '> 100' } tmux.until { |lines| assert_includes lines, '> 100' }
tmux.send_keys :Enter tmux.send_keys :Enter
@@ -2245,6 +2252,158 @@ class TestGoFZF < TestBase
tmux.until { |lines| assert_equal 1, lines.match_count } tmux.until { |lines| assert_equal 1, lines.match_count }
tmux.until { |lines| assert_match(/^> SNIPSNIP.*SNIPSNIP$/, lines[-3]) } tmux.until { |lines| assert_match(/^> SNIPSNIP.*SNIPSNIP$/, lines[-3]) }
end 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_org 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
def test_start_event
tmux.send_keys 'seq 100 | fzf --multi --sync --preview-window border-none --bind "start:select-all+last+preview(echo welcome)"', :Enter
tmux.until do |lines|
assert_match(/>100.*welcome/, lines[0])
assert_includes(lines[-2], '100/100 (100)')
end
end
def test_labels_center
tmux.send_keys ': | fzf --border --border-label foobar --preview : --preview-label barfoo', :Enter
tmux.until do
assert_includes(_1[0], '─foobar─')
assert_includes(_1[1], '─barfoo─')
end
end
def test_labels_left
tmux.send_keys ': | fzf --border --border-label foobar --border-label-pos 2 --preview : --preview-label barfoo --preview-label-pos 2', :Enter
tmux.until do
assert_includes(_1[0], '╭foobar─')
assert_includes(_1[1], '╭barfoo─')
end
end
def test_labels_right
tmux.send_keys ': | fzf --border --border-label foobar --border-label-pos -2 --preview : --preview-label barfoo --preview-label-pos -2', :Enter
tmux.until do
assert_includes(_1[0], '─foobar╮')
assert_includes(_1[1], '─barfoo╮')
end
end
def test_labels_bottom
tmux.send_keys ': | fzf --border --border-label foobar --border-label-pos 2:bottom --preview : --preview-label barfoo --preview-label-pos -2:bottom', :Enter
tmux.until do
assert_includes(_1[-1], '╰foobar─')
assert_includes(_1[-2], '─barfoo╯')
end
end
def test_info_separator_unicode
tmux.send_keys 'seq 100 | fzf -q55', :Enter
tmux.until { assert_includes(_1[-2], ' 1/100 ─') }
end
def test_info_separator_no_unicode
tmux.send_keys 'seq 100 | fzf -q55 --no-unicode', :Enter
tmux.until { assert_includes(_1[-2], ' 1/100 -') }
end
def test_info_separator_repeat
tmux.send_keys 'seq 100 | fzf -q55 --separator _-', :Enter
tmux.until { assert_includes(_1[-2], ' 1/100 _-_-') }
end
def test_info_separator_ansi_colors_and_tabs
tmux.send_keys "seq 100 | fzf -q55 --tabstop 4 --separator $'\\x1b[33ma\\tb'", :Enter
tmux.until { assert_includes(_1[-2], ' 1/100 a ba ba') }
end
def test_info_no_separator
tmux.send_keys 'seq 100 | fzf -q55 --no-separator', :Enter
tmux.until { assert(_1[-2] == ' 1/100') }
end
end end
module TestShell module TestShell
@@ -2671,6 +2830,7 @@ class TestFish < TestBase
end end
__END__ __END__
set -u
PS1= PROMPT_COMMAND= HISTFILE= HISTSIZE=100 PS1= PROMPT_COMMAND= HISTFILE= HISTSIZE=100
unset <%= UNSETS.join(' ') %> unset <%= UNSETS.join(' ') %>
unset $(env | sed -n /^_fzf_orig/s/=.*//p) unset $(env | sed -n /^_fzf_orig/s/=.*//p)
@@ -2714,8 +2874,8 @@ _fzf_complete_g_post() {
awk '{print "g" $0 $0}' awk '{print "g" $0 $0}'
} }
[ -n "$BASH" ] && complete -F _fzf_complete_f -o default -o bashdefault f [ -n "${BASH-}" ] && complete -F _fzf_complete_f -o default -o bashdefault f
[ -n "$BASH" ] && complete -F _fzf_complete_g -o default -o bashdefault g [ -n "${BASH-}" ] && complete -F _fzf_complete_g -o default -o bashdefault g
_comprun() { _comprun() {
local command=$1 local command=$1