mirror of
https://github.com/junegunn/fzf.git
synced 2025-08-16 12:43:50 -07:00
Compare commits
296 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
d471067e3f | ||
|
d0b7780239 | ||
|
e627ca6bd7 | ||
|
c97172bdd4 | ||
|
ce8a745fb4 | ||
|
3e9efd1401 | ||
|
20340190b5 | ||
|
265040a78c | ||
|
448d7e0c5a | ||
|
6eb1874c5a | ||
|
4c70745cc1 | ||
|
7795748a3f | ||
|
098ef4d7cf | ||
|
e3f91bfe1b | ||
|
7374fe73a3 | ||
|
d2bde205f0 | ||
|
5620f70f9a | ||
|
37f258b1bf | ||
|
e2dd2a133e | ||
|
7514644e07 | ||
|
16b0aeda7d | ||
|
86e4f4a841 | ||
|
607eacf8c7 | ||
|
7a049644a8 | ||
|
17a13f00f8 | ||
|
43436e48e0 | ||
|
5a39102405 | ||
|
94999101e3 | ||
|
e619b7c4f4 | ||
|
b7c2e8cb67 | ||
|
fb76893e18 | ||
|
88d812fe82 | ||
|
77f9f4664a | ||
|
5c2f85c39e | ||
|
ac4d22cd12 | ||
|
cf95e44cb4 | ||
|
65dd2bb429 | ||
|
6be855be6a | ||
|
b6e3f4423b | ||
|
0c61d81713 | ||
|
7c6f5dba63 | ||
|
44cfc7e62a | ||
|
96670d5f16 | ||
|
36b971ee4e | ||
|
f1a9629652 | ||
|
20230402d0 | ||
|
5c2c3a6c88 | ||
|
fb019d43bf | ||
|
025aa33773 | ||
|
302e21fd58 | ||
|
211512ae64 | ||
|
8ec917b1c3 | ||
|
1c7534f009 | ||
|
ae745d9397 | ||
|
60f37aae2f | ||
|
d7daf5f724 | ||
|
e5103d9429 | ||
|
8fecb29848 | ||
|
290ea6179d | ||
|
9695a40fc9 | ||
|
1913b95227 | ||
|
a874aea692 | ||
|
69c52099e7 | ||
|
cfc0747d5d | ||
|
fcd7e8768d | ||
|
3c34dd8275 | ||
|
1116e481be | ||
|
63cf9d04de | ||
|
3364d4d147 | ||
|
57ad21e4bd | ||
|
414f87981f | ||
|
b1459c79cf | ||
|
352ea07226 | ||
|
27018787af | ||
|
4e305eca26 | ||
|
9e9c0ceaf4 | ||
|
b3bf18b1c0 | ||
|
b1619f675f | ||
|
96c3de12eb | ||
|
719dbb8bae | ||
|
f38a7f7f8f | ||
|
6ea38b4438 | ||
|
f7447aece1 | ||
|
aa2b9ec476 | ||
|
3ee00f8bc2 | ||
|
fccab60a5c | ||
|
0f4af38457 | ||
|
aef39f1160 | ||
|
2023012408 | ||
|
95a7661bb1 | ||
|
618d317803 | ||
|
ae897c8cdb | ||
|
d0a0f3c052 | ||
|
91b9591b10 | ||
|
aa7361337d | ||
|
284d77fe2e | ||
|
826178f1e2 | ||
|
acccf8a9b8 | ||
|
57c066f0be | ||
|
e44f64ae92 | ||
|
d51980a3f5 | ||
|
c3d73e7ecb | ||
|
b077f6821d | ||
|
a79de11af7 | ||
|
2023011763 | ||
|
b46e40e86b | ||
|
a6d6cdd165 | ||
|
dc8da605f9 | ||
|
8b299a29c7 | ||
|
3109b865d2 | ||
|
0c5956c43c | ||
|
1c83b39691 | ||
|
77874b473c | ||
|
b7cce7be15 | ||
|
3cd3362417 | ||
|
e97e925efb | ||
|
0f032235cf | ||
|
e0f0984da7 | ||
|
4d22b5aaef | ||
|
80b8846318 | ||
|
bf641faafa | ||
|
23d8b78ce1 | ||
|
3b2244077d | ||
|
ee5cdb9713 | ||
|
03d02d67f7 | ||
|
5798145581 | ||
|
51ef0b7f66 | ||
|
97b4542c73 | ||
|
c1cd0c09a2 | ||
|
1fc1f47d80 | ||
|
ec471a5bc2 | ||
|
a893fc0ca2 | ||
|
3761dc0433 | ||
|
aa71a07fbe | ||
|
088293f5e7 | ||
|
7c660aa86e | ||
|
435d8fa0a2 | ||
|
5cd6f1d064 | ||
|
ec20dfe312 | ||
|
924ffb5a35 | ||
|
62c7f59b94 | ||
|
e97176b1d7 | ||
|
d649f5d826 | ||
|
6c37177cf5 | ||
|
14775aa975 | ||
|
44b6336372 | ||
|
36d2bb332b | ||
|
4dbe45640a | ||
|
4b3f0b9f08 | ||
|
12af069dca | ||
|
d42e708d31 | ||
|
b7bb973118 | ||
|
750b2a6313 | ||
|
de0da86bd7 | ||
|
8e283f512a | ||
|
73162a4bc3 | ||
|
1a9761736e | ||
|
fd1f7665a7 | ||
|
6d14573fd0 | ||
|
cf69b836ac | ||
|
a7a771b92b | ||
|
def011c029 | ||
|
4b055bf260 | ||
|
1ba7484d60 | ||
|
51c518da1e | ||
|
a3b6b03dfb | ||
|
18e3b38c69 | ||
|
0ad30063ff | ||
|
7812c64a31 | ||
|
3d2376ab52 | ||
|
6b207bbf2b | ||
|
3f079ba7c6 | ||
|
8f4c89f50e | ||
|
6b7a543c82 | ||
|
2ba68d24f2 | ||
|
46877e0a92 | ||
|
b55f555487 | ||
|
a38b63be18 | ||
|
1bebd6f4f5 | ||
|
3da63f394d | ||
|
2a54e3d770 | ||
|
06b02ba46e | ||
|
d01ae55109 | ||
|
8868d7d188 | ||
|
2eec9892be | ||
|
01ae621f11 | ||
|
f984aa0d2c | ||
|
0881a6bc17 | ||
|
2c6a73546d | ||
|
a29944660e | ||
|
f6ce624c6f | ||
|
c09ec8e4d1 | ||
|
31bbaad06e | ||
|
b9ca1fe830 | ||
|
e61585f2f3 | ||
|
0de1aacb0c | ||
|
168829b555 | ||
|
170fc517d0 | ||
|
0fbf1c7c71 | ||
|
694be39c71 | ||
|
dad26d81df | ||
|
bcaea097ea | ||
|
d56fe74e24 | ||
|
4603d540c3 | ||
|
f9d53303bb | ||
|
d04faa6505 | ||
|
07da058eae | ||
|
cefa6b9878 | ||
|
04d0b0223f | ||
|
78ad6d2d88 | ||
|
22cbd9fa58 | ||
|
984049586a | ||
|
cdfc2b92e3 | ||
|
4530abe8df | ||
|
586020b8b6 | ||
|
3a8626fd04 | ||
|
a6e483a434 | ||
|
6a942e56b1 | ||
|
87c91550ad | ||
|
731daf0f37 | ||
|
f931e53890 | ||
|
b5efc68737 | ||
|
b9e6e7926c | ||
|
845034c81c | ||
|
54d42e3f40 | ||
|
e03ac3136e | ||
|
6fb41a202a | ||
|
4bef330ce1 | ||
|
8a5f719964 | ||
|
209d5e8e90 | ||
|
9d041aa582 | ||
|
6532b3e655 | ||
|
c1c355160d | ||
|
83515d5610 | ||
|
aa10dccf90 | ||
|
f4fd53211a | ||
|
4993d19466 | ||
|
19f9bbca0d | ||
|
779d8e1627 | ||
|
bb07410448 | ||
|
d826f9e72f | ||
|
6a6130615d | ||
|
a8e7021be2 | ||
|
38259d0382 | ||
|
f7e7259910 | ||
|
f0bfeba733 | ||
|
c3a7a24eea | ||
|
bbbcd780c9 | ||
|
475469a2e7 | ||
|
3a7447dcb6 | ||
|
e5d8cbd383 | ||
|
3c08dca7e7 | ||
|
d083f01d22 | ||
|
68cf393644 | ||
|
18f7230662 | ||
|
728f735281 | ||
|
ecc418ba77 | ||
|
3af5b7f2ac | ||
|
7a7cfcacbe | ||
|
52594355bf | ||
|
0d06c28b19 | ||
|
ccc4677252 | ||
|
821fc9feed | ||
|
82b46726fc | ||
|
8df872a482 | ||
|
c79c306adb | ||
|
51fdaad002 | ||
|
885cd8ff04 | ||
|
2707af403a | ||
|
2d227e5222 | ||
|
70529878e2 | ||
|
3b7a962dc6 | ||
|
6dcf5c3d7d | ||
|
b089bb5e7b | ||
|
a91a67668e | ||
|
220a908118 | ||
|
54841248e7 | ||
|
a0b42e6538 | ||
|
3312cf525d | ||
|
2093667548 | ||
|
3c868d7961 | ||
|
707f4f5816 | ||
|
b3ab6311c5 | ||
|
d56f605b63 | ||
|
f8b713f425 | ||
|
5209e95bc7 | ||
|
ef67a45702 | ||
|
b88eb72ac2 | ||
|
32847f7254 | ||
|
71df93b534 | ||
|
bb028191f8 | ||
|
19af8fc7d8 | ||
|
a06671b47f | ||
|
5f385d88e0 | ||
|
9cb7a364a3 | ||
|
f68cbc577d |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1 +1 @@
|
|||||||
custom: ["https://paypal.me/junegunn", "https://www.buymeacoffee.com/junegunn"]
|
github: junegunn
|
||||||
|
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -4,3 +4,7 @@ updates:
|
|||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
15
.github/workflows/codeql-analysis.yml
vendored
15
.github/workflows/codeql-analysis.yml
vendored
@@ -8,8 +8,15 @@ on:
|
|||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
analyze:
|
analyze:
|
||||||
|
permissions:
|
||||||
|
actions: read # for github/codeql-action/init to get workflow details
|
||||||
|
contents: read # for actions/checkout to fetch code
|
||||||
|
security-events: write # for github/codeql-action/autobuild to send a status report
|
||||||
name: Analyze
|
name: Analyze
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
@@ -20,18 +27,18 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@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@v1
|
uses: github/codeql-action/init@v2
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v1
|
uses: github/codeql-action/autobuild@v2
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v1
|
uses: github/codeql-action/analyze@v2
|
||||||
|
14
.github/workflows/depsreview.yaml
vendored
Normal file
14
.github/workflows/depsreview.yaml
vendored
Normal 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@v3
|
18
.github/workflows/linux.yml
vendored
18
.github/workflows/linux.yml
vendored
@@ -8,32 +8,32 @@ on:
|
|||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
go: [1.14, 1.16]
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@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@v2
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: 1.19
|
||||||
|
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
uses: ruby/setup-ruby@v1.62.0
|
uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
ruby-version: 3.0.0
|
ruby-version: 3.1.0
|
||||||
|
|
||||||
- name: Install packages
|
- name: Install packages
|
||||||
run: sudo apt-get install --yes zsh fish tmux
|
run: sudo apt-get install --yes zsh fish tmux
|
||||||
|
|
||||||
- name: Install Ruby gems
|
- name: Install Ruby gems
|
||||||
run: sudo gem install --no-document minitest:5.14.2 rubocop:1.0.0 rubocop-minitest:0.10.1 rubocop-performance:1.8.1
|
run: sudo gem install --no-document minitest:5.17.0 rubocop:1.43.0 rubocop-minitest:0.25.1 rubocop-performance:1.15.2
|
||||||
|
|
||||||
- name: Rubocop
|
- name: Rubocop
|
||||||
run: rubocop --require rubocop-minitest --require rubocop-performance
|
run: rubocop --require rubocop-minitest --require rubocop-performance
|
||||||
|
14
.github/workflows/macos.yml
vendored
14
.github/workflows/macos.yml
vendored
@@ -8,24 +8,24 @@ on:
|
|||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
go: [1.14, 1.16]
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@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@v2
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: 1.18
|
||||||
|
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
uses: ruby/setup-ruby@v1.62.0
|
uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
ruby-version: 3.0.0
|
ruby-version: 3.0.0
|
||||||
|
|
||||||
|
10
.github/workflows/typos.yml
vendored
Normal file
10
.github/workflows/typos.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
name: "Spell Check"
|
||||||
|
on: [pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
typos:
|
||||||
|
name: Spell Check with Typos
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: crate-ci/typos@v1.14.10
|
15
.github/workflows/winget.yml
vendored
Normal file
15
.github/workflows/winget.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
name: Publish to Winget
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [released]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: windows-latest # Action can only run on Windows
|
||||||
|
steps:
|
||||||
|
- uses: vedantmgoyal2009/winget-releaser@v2
|
||||||
|
with:
|
||||||
|
identifier: junegunn.fzf
|
||||||
|
version: ${{ github.event.release.tag_name }}
|
||||||
|
installers-regex: '-windows_(armv7|arm64|amd64)\.zip$'
|
||||||
|
token: ${{ secrets.WINGET_TOKEN }}
|
@@ -18,7 +18,7 @@ builds:
|
|||||||
post: |
|
post: |
|
||||||
sh -c '
|
sh -c '
|
||||||
cat > /tmp/fzf-gon-amd64.hcl << EOF
|
cat > /tmp/fzf-gon-amd64.hcl << EOF
|
||||||
source = ["./dist/fzf-macos_darwin_amd64/fzf"]
|
source = ["./dist/fzf-macos_darwin_amd64_v1/fzf"]
|
||||||
bundle_id = "kr.junegunn.fzf"
|
bundle_id = "kr.junegunn.fzf"
|
||||||
apple_id {
|
apple_id {
|
||||||
username = "junegunn.c@gmail.com"
|
username = "junegunn.c@gmail.com"
|
||||||
@@ -72,6 +72,9 @@ builds:
|
|||||||
- amd64
|
- amd64
|
||||||
- arm
|
- arm
|
||||||
- arm64
|
- arm64
|
||||||
|
- loong64
|
||||||
|
- ppc64le
|
||||||
|
- s390x
|
||||||
goarm:
|
goarm:
|
||||||
- 5
|
- 5
|
||||||
- 6
|
- 6
|
||||||
@@ -99,6 +102,10 @@ archives:
|
|||||||
files:
|
files:
|
||||||
- non-existent*
|
- non-existent*
|
||||||
|
|
||||||
|
checksum:
|
||||||
|
extra_files:
|
||||||
|
- glob: ./dist/fzf-*darwin*.zip
|
||||||
|
|
||||||
release:
|
release:
|
||||||
github:
|
github:
|
||||||
owner: junegunn
|
owner: junegunn
|
||||||
|
@@ -26,3 +26,7 @@ Style/OptionalBooleanParameter:
|
|||||||
Enabled: false
|
Enabled: false
|
||||||
Style/WordArray:
|
Style/WordArray:
|
||||||
MinSize: 1
|
MinSize: 1
|
||||||
|
Minitest/AssertEqual:
|
||||||
|
Enabled: false
|
||||||
|
Naming/VariableNumber:
|
||||||
|
Enabled: false
|
||||||
|
1
.tool-versions
Normal file
1
.tool-versions
Normal file
@@ -0,0 +1 @@
|
|||||||
|
golang 1.20.4
|
185
ADVANCED.md
185
ADVANCED.md
@@ -1,29 +1,33 @@
|
|||||||
Advanced fzf examples
|
Advanced fzf examples
|
||||||
======================
|
======================
|
||||||
|
|
||||||
*(Last update: 2021/05/22)*
|
* *Last update: 2023/05/26*
|
||||||
|
* *Requires fzf 0.41.0 or above*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
<!-- vim-markdown-toc GFM -->
|
<!-- vim-markdown-toc GFM -->
|
||||||
|
|
||||||
* [Introduction](#introduction)
|
* [Introduction](#introduction)
|
||||||
* [Screen Layout](#screen-layout)
|
* [Screen Layout](#screen-layout)
|
||||||
* [`--height`](#--height)
|
* [`--height`](#--height)
|
||||||
* [`fzf-tmux`](#fzf-tmux)
|
* [`fzf-tmux`](#fzf-tmux)
|
||||||
* [Popup window support](#popup-window-support)
|
* [Popup window support](#popup-window-support)
|
||||||
* [Dynamic reloading of the list](#dynamic-reloading-of-the-list)
|
* [Dynamic reloading of the list](#dynamic-reloading-of-the-list)
|
||||||
* [Updating the list of processes by pressing CTRL-R](#updating-the-list-of-processes-by-pressing-ctrl-r)
|
* [Updating the list of processes by pressing CTRL-R](#updating-the-list-of-processes-by-pressing-ctrl-r)
|
||||||
* [Toggling between data sources](#toggling-between-data-sources)
|
* [Toggling between data sources](#toggling-between-data-sources)
|
||||||
* [Ripgrep integration](#ripgrep-integration)
|
* [Ripgrep integration](#ripgrep-integration)
|
||||||
* [Using fzf as the secondary filter](#using-fzf-as-the-secondary-filter)
|
* [Using fzf as the secondary filter](#using-fzf-as-the-secondary-filter)
|
||||||
* [Using fzf as interative Ripgrep launcher](#using-fzf-as-interative-ripgrep-launcher)
|
* [Using fzf as interactive Ripgrep launcher](#using-fzf-as-interactive-ripgrep-launcher)
|
||||||
* [Switching to fzf-only search mode](#switching-to-fzf-only-search-mode)
|
* [Switching to fzf-only search mode](#switching-to-fzf-only-search-mode)
|
||||||
|
* [Switching between Ripgrep mode and fzf mode](#switching-between-ripgrep-mode-and-fzf-mode)
|
||||||
* [Log tailing](#log-tailing)
|
* [Log tailing](#log-tailing)
|
||||||
* [Key bindings for git objects](#key-bindings-for-git-objects)
|
* [Key bindings for git objects](#key-bindings-for-git-objects)
|
||||||
* [Files listed in `git status`](#files-listed-in-git-status)
|
* [Files listed in `git status`](#files-listed-in-git-status)
|
||||||
* [Branches](#branches)
|
* [Branches](#branches)
|
||||||
* [Commit hashes](#commit-hashes)
|
* [Commit hashes](#commit-hashes)
|
||||||
* [Color themes](#color-themes)
|
* [Color themes](#color-themes)
|
||||||
* [Generating fzf color theme from Vim color schemes](#generating-fzf-color-theme-from-vim-color-schemes)
|
* [Generating fzf color theme from Vim color schemes](#generating-fzf-color-theme-from-vim-color-schemes)
|
||||||
|
|
||||||
<!-- vim-markdown-toc -->
|
<!-- vim-markdown-toc -->
|
||||||
|
|
||||||
@@ -235,15 +239,13 @@ file called `rfv`.
|
|||||||
# 1. Search for text in files using Ripgrep
|
# 1. Search for text in files using Ripgrep
|
||||||
# 2. Interactively narrow down the list using fzf
|
# 2. Interactively narrow down the list using fzf
|
||||||
# 3. Open the file in Vim
|
# 3. Open the file in Vim
|
||||||
IFS=: read -ra selected < <(
|
rg --color=always --line-number --no-heading --smart-case "${*:-}" |
|
||||||
rg --color=always --line-number --no-heading --smart-case "${*:-}" |
|
fzf --ansi \
|
||||||
fzf --ansi \
|
--color "hl:-1:underline,hl+:-1:underline:reverse" \
|
||||||
--color "hl:-1:underline,hl+:-1:underline:reverse" \
|
--delimiter : \
|
||||||
--delimiter : \
|
--preview 'bat --color=always {1} --highlight-line {2}' \
|
||||||
--preview 'bat --color=always {1} --highlight-line {2}' \
|
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \
|
||||||
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3'
|
--bind 'enter:become(vim {1} +{2})'
|
||||||
)
|
|
||||||
[ -n "${selected[0]}" ] && vim "${selected[0]}" "+${selected[1]}"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
And run it with an initial query string.
|
And run it with an initial query string.
|
||||||
@@ -306,10 +308,14 @@ I know it's a lot to digest, let's try to break down the code.
|
|||||||
position in the window
|
position in the window
|
||||||
- `~3` makes the top three lines fixed header so that they are always
|
- `~3` makes the top three lines fixed header so that they are always
|
||||||
visible regardless of the scroll offset
|
visible regardless of the scroll offset
|
||||||
- Once we selected a line, we open the file with `vim` (`vim
|
- Instead of using shell script to process the final output of fzf, we use
|
||||||
"${selected[0]}"`) and move the cursor to the line (`+${selected[1]}`).
|
`become(...)` action which was added in [fzf 0.38.0][0.38.0] to turn fzf
|
||||||
|
into a new process that opens the file with `vim` (`vim {1}`) and move the
|
||||||
|
cursor to the line (`+{2}`).
|
||||||
|
|
||||||
### Using fzf as interative Ripgrep launcher
|
[0.38.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0380
|
||||||
|
|
||||||
|
### Using fzf as interactive Ripgrep launcher
|
||||||
|
|
||||||
We have learned that we can bind `reload` action to a key (e.g.
|
We have learned that we can bind `reload` action to a key (e.g.
|
||||||
`--bind=ctrl-r:execute(ps -ef)`). In the next example, we are going to **bind
|
`--bind=ctrl-r:execute(ps -ef)`). In the next example, we are going to **bind
|
||||||
@@ -330,25 +336,22 @@ projects, and it will free up memory as you narrow down the results.
|
|||||||
# 3. Open the file in Vim
|
# 3. Open the file in Vim
|
||||||
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
||||||
INITIAL_QUERY="${*:-}"
|
INITIAL_QUERY="${*:-}"
|
||||||
IFS=: read -ra selected < <(
|
: | fzf --ansi --disabled --query "$INITIAL_QUERY" \
|
||||||
FZF_DEFAULT_COMMAND="$RG_PREFIX $(printf %q "$INITIAL_QUERY")" \
|
--bind "start:reload:$RG_PREFIX {q}" \
|
||||||
fzf --ansi \
|
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
|
||||||
--disabled --query "$INITIAL_QUERY" \
|
--delimiter : \
|
||||||
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
|
--preview 'bat --color=always {1} --highlight-line {2}' \
|
||||||
--delimiter : \
|
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \
|
||||||
--preview 'bat --color=always {1} --highlight-line {2}' \
|
--bind 'enter:become(vim {1} +{2})'
|
||||||
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3'
|
|
||||||
)
|
|
||||||
[ -n "${selected[0]}" ] && vim "${selected[0]}" "+${selected[1]}"
|
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
- Instead of starting fzf in `rg ... | fzf` form, we start fzf without an
|
- Instead of starting fzf in the usual `rg ... | fzf` form, we start fzf with
|
||||||
explicit input, but with a custom `FZF_DEFAULT_COMMAND` variable. This way
|
an empty input (`: | fzf`), then we make it start the initial Ripgrep
|
||||||
fzf can kill the initial Ripgrep process it starts with the initial query.
|
process immediately via `start:reload` binding. This way, fzf owns the
|
||||||
Otherwise, the initial Ripgrep process will keep consuming system resources
|
initial Ripgrep process so it can kill it on the next `reload`. Otherwise,
|
||||||
even after `reload` is triggered.
|
the process will keep running in the background.
|
||||||
- Filtering is no longer a responsibility of fzf; hence `--disabled`
|
- Filtering is no longer a responsibility of fzf; hence `--disabled`
|
||||||
- `{q}` in the reload command evaluates to the query string on fzf prompt.
|
- `{q}` in the reload command evaluates to the query string on fzf prompt.
|
||||||
- `sleep 0.1` in the reload command is for "debouncing". This small delay will
|
- `sleep 0.1` in the reload command is for "debouncing". This small delay will
|
||||||
@@ -357,8 +360,6 @@ IFS=: read -ra selected < <(
|
|||||||
|
|
||||||
### Switching to fzf-only search mode
|
### Switching to fzf-only search mode
|
||||||
|
|
||||||
*(Requires fzf 0.27.1 or above)*
|
|
||||||
|
|
||||||
In the previous example, we lost fuzzy matching capability as we completely
|
In the previous example, we lost fuzzy matching capability as we completely
|
||||||
delegated search functionality to Ripgrep. But we can dynamically switch to
|
delegated search functionality to Ripgrep. But we can dynamically switch to
|
||||||
fzf-only search mode by *"unbinding"* `reload` action from `change` event.
|
fzf-only search mode by *"unbinding"* `reload` action from `change` event.
|
||||||
@@ -374,19 +375,16 @@ fzf-only search mode by *"unbinding"* `reload` action from `change` event.
|
|||||||
# 3. Open the file in Vim
|
# 3. Open the file in Vim
|
||||||
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
||||||
INITIAL_QUERY="${*:-}"
|
INITIAL_QUERY="${*:-}"
|
||||||
IFS=: read -ra selected < <(
|
: | fzf --ansi --disabled --query "$INITIAL_QUERY" \
|
||||||
FZF_DEFAULT_COMMAND="$RG_PREFIX $(printf %q "$INITIAL_QUERY")" \
|
--bind "start:reload:$RG_PREFIX {q}" \
|
||||||
fzf --ansi \
|
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
|
||||||
--color "hl:-1:underline,hl+:-1:underline:reverse" \
|
--bind "alt-enter:unbind(change,alt-enter)+change-prompt(2. fzf> )+enable-search+clear-query" \
|
||||||
--disabled --query "$INITIAL_QUERY" \
|
--color "hl:-1:underline,hl+:-1:underline:reverse" \
|
||||||
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
|
--prompt '1. ripgrep> ' \
|
||||||
--bind "alt-enter:unbind(change,alt-enter)+change-prompt(2. fzf> )+enable-search+clear-query" \
|
--delimiter : \
|
||||||
--prompt '1. ripgrep> ' \
|
--preview 'bat --color=always {1} --highlight-line {2}' \
|
||||||
--delimiter : \
|
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \
|
||||||
--preview 'bat --color=always {1} --highlight-line {2}' \
|
--bind 'enter:become(vim {1} +{2})'
|
||||||
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3'
|
|
||||||
)
|
|
||||||
[ -n "${selected[0]}" ] && vim "${selected[0]}" "+${selected[1]}"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
* Phase 1. Filtering with Ripgrep
|
* Phase 1. Filtering with Ripgrep
|
||||||
@@ -405,6 +403,45 @@ IFS=: read -ra selected < <(
|
|||||||
- We reverted `--color` option for customizing how the matching chunks are
|
- We reverted `--color` option for customizing how the matching chunks are
|
||||||
displayed in the second phase
|
displayed in the second phase
|
||||||
|
|
||||||
|
### Switching between Ripgrep mode and fzf mode
|
||||||
|
|
||||||
|
[fzf 0.30.0][0.30.0] added `rebind` action so we can "rebind" the bindings
|
||||||
|
that were previously "unbound" via `unbind`.
|
||||||
|
|
||||||
|
This is an improved version of the previous example that allows us to switch
|
||||||
|
between Ripgrep launcher mode and fzf-only filtering mode via CTRL-R and
|
||||||
|
CTRL-F.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Switch between Ripgrep launcher mode (CTRL-R) and fzf filtering mode (CTRL-F)
|
||||||
|
rm -f /tmp/rg-fzf-{r,f}
|
||||||
|
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
||||||
|
INITIAL_QUERY="${*:-}"
|
||||||
|
: | fzf --ansi --disabled --query "$INITIAL_QUERY" \
|
||||||
|
--bind "start:reload($RG_PREFIX {q})+unbind(ctrl-r)" \
|
||||||
|
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
|
||||||
|
--bind "ctrl-f:unbind(change,ctrl-f)+change-prompt(2. fzf> )+enable-search+rebind(ctrl-r)+transform-query(echo {q} > /tmp/rg-fzf-r; cat /tmp/rg-fzf-f)" \
|
||||||
|
--bind "ctrl-r:unbind(ctrl-r)+change-prompt(1. ripgrep> )+disable-search+reload($RG_PREFIX {q} || true)+rebind(change,ctrl-f)+transform-query(echo {q} > /tmp/rg-fzf-f; cat /tmp/rg-fzf-r)" \
|
||||||
|
--color "hl:-1:underline,hl+:-1:underline:reverse" \
|
||||||
|
--prompt '1. ripgrep> ' \
|
||||||
|
--delimiter : \
|
||||||
|
--header '╱ CTRL-R (ripgrep mode) ╱ CTRL-F (fzf mode) ╱' \
|
||||||
|
--preview 'bat --color=always {1} --highlight-line {2}' \
|
||||||
|
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \
|
||||||
|
--bind 'enter:become(vim {1} +{2})'
|
||||||
|
```
|
||||||
|
|
||||||
|
- To restore the query string when switching between modes, we store the
|
||||||
|
current query in `/tmp/rg-fzf-{r,f}` files and restore the query using
|
||||||
|
`transform-query` action which was added in [fzf 0.36.0][0.36.0].
|
||||||
|
- Also note that we unbind `ctrl-r` binding on `start` event which is
|
||||||
|
triggered once when fzf starts.
|
||||||
|
|
||||||
|
[0.30.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0300
|
||||||
|
[0.36.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0360
|
||||||
|
|
||||||
Log tailing
|
Log tailing
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
@@ -430,16 +467,17 @@ Kubernetes pods.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
pods() {
|
pods() {
|
||||||
FZF_DEFAULT_COMMAND="kubectl get pods --all-namespaces" \
|
: | command='kubectl get pods --all-namespaces' fzf \
|
||||||
fzf --info=inline --layout=reverse --header-lines=1 \
|
--info=inline --layout=reverse --header-lines=1 \
|
||||||
--prompt "$(kubectl config current-context | sed 's/-context$//')> " \
|
--prompt "$(kubectl config current-context | sed 's/-context$//')> " \
|
||||||
--header $'╱ Enter (kubectl exec) ╱ CTRL-O (open log in editor) ╱ CTRL-R (reload) ╱\n\n' \
|
--header $'╱ Enter (kubectl exec) ╱ CTRL-O (open log in editor) ╱ CTRL-R (reload) ╱\n\n' \
|
||||||
--bind 'ctrl-/:change-preview-window(80%,border-bottom|hidden|)' \
|
--bind 'start:reload:$command' \
|
||||||
--bind 'enter:execute:kubectl exec -it --namespace {1} {2} -- bash > /dev/tty' \
|
--bind 'ctrl-r:reload:$command' \
|
||||||
--bind 'ctrl-o:execute:${EDITOR:-vim} <(kubectl logs --all-containers --namespace {1} {2}) > /dev/tty' \
|
--bind 'ctrl-/:change-preview-window(80%,border-bottom|hidden|)' \
|
||||||
--bind 'ctrl-r:reload:$FZF_DEFAULT_COMMAND' \
|
--bind 'enter:execute:kubectl exec -it --namespace {1} {2} -- bash > /dev/tty' \
|
||||||
--preview-window up:follow \
|
--bind 'ctrl-o:execute:${EDITOR:-vim} <(kubectl logs --all-containers --namespace {1} {2}) > /dev/tty' \
|
||||||
--preview 'kubectl logs --follow --all-containers --tail=10000 --namespace {1} {2}' "$@"
|
--preview-window up:follow \
|
||||||
|
--preview 'kubectl logs --follow --all-containers --tail=10000 --namespace {1} {2}' "$@"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -461,9 +499,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`
|
||||||
|
|
||||||
@@ -483,9 +529,6 @@ seriously useful.
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
The full source code can be found [here](https://gist.github.com/junegunn/8b572b8d4b5eddd8b85e5f4d40f17236).
|
|
||||||
|
|
||||||
Color themes
|
Color themes
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
2
BUILD.md
2
BUILD.md
@@ -6,7 +6,7 @@ Build instructions
|
|||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- Go 1.13 or above
|
- Go 1.17 or above
|
||||||
|
|
||||||
### Using Makefile
|
### Using Makefile
|
||||||
|
|
||||||
|
462
CHANGELOG.md
462
CHANGELOG.md
@@ -1,6 +1,468 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.42.0
|
||||||
|
------
|
||||||
|
- Added new info style: `--info=right`
|
||||||
|
- Added new info style: `--info=inline-right`
|
||||||
|
- Added new border style `thinblock` which uses symbols for legacy computing
|
||||||
|
[one eighth block elements](https://en.wikipedia.org/wiki/Symbols_for_Legacy_Computing)
|
||||||
|
- Similarly to `block`, this style is suitable when using a different
|
||||||
|
background color because the window is completely contained within the border.
|
||||||
|
```sh
|
||||||
|
BAT_THEME=GitHub fzf --info=right --border=thinblock --preview-window=border-thinblock \
|
||||||
|
--margin=3 --scrollbar=▏▕ --preview='bat --color=always --style=numbers {}' \
|
||||||
|
--color=light,query:238,fg:238,bg:251,bg+:249,gutter:251,border:248,preview-bg:253
|
||||||
|
```
|
||||||
|
- This style may not render correctly depending on the font and the
|
||||||
|
terminal emulator.
|
||||||
|
|
||||||
|
0.41.1
|
||||||
|
------
|
||||||
|
- Fixed a bug where preview window is not updated when `--disabled` is set and
|
||||||
|
a reload is triggered by `change:reload` binding
|
||||||
|
|
||||||
|
0.41.0
|
||||||
|
------
|
||||||
|
- Added color name `preview-border` and `preview-scrollbar`
|
||||||
|
- Added new border style `block` which uses [block elements](https://en.wikipedia.org/wiki/Block_Elements)
|
||||||
|
- `--scrollbar` can take two characters, one for the main window, the other
|
||||||
|
for the preview window
|
||||||
|
- Putting it altogether:
|
||||||
|
```sh
|
||||||
|
fzf-tmux -p 80% --padding 1,2 --preview 'bat --style=plain --color=always {}' \
|
||||||
|
--color 'bg:237,bg+:235,gutter:237,border:238,scrollbar:236' \
|
||||||
|
--color 'preview-bg:235,preview-border:236,preview-scrollbar:234' \
|
||||||
|
--preview-window 'border-block' --border block --scrollbar '▌▐'
|
||||||
|
```
|
||||||
|
- Bug fixes and improvements
|
||||||
|
|
||||||
|
0.40.0
|
||||||
|
------
|
||||||
|
- Added `zero` event that is triggered when there's no match
|
||||||
|
```sh
|
||||||
|
# Reload the candidate list when there's no match
|
||||||
|
echo $RANDOM | fzf --bind 'zero:reload(echo $RANDOM)+clear-query' --height 3
|
||||||
|
```
|
||||||
|
- New actions
|
||||||
|
- Added `track` action which makes fzf track the current item when the
|
||||||
|
search result is updated. If the user manually moves the cursor, or the
|
||||||
|
item is not in the updated search result, tracking is automatically
|
||||||
|
disabled. Tracking is useful when you want to see the surrounding items
|
||||||
|
by deleting the query string.
|
||||||
|
```sh
|
||||||
|
# Narrow down the list with a query, point to a command,
|
||||||
|
# and hit CTRL-T to see its surrounding commands.
|
||||||
|
export FZF_CTRL_R_OPTS="
|
||||||
|
--preview 'echo {}' --preview-window up:3:hidden:wrap
|
||||||
|
--bind 'ctrl-/:toggle-preview'
|
||||||
|
--bind 'ctrl-t:track+clear-query'
|
||||||
|
--bind 'ctrl-y:execute-silent(echo -n {2..} | pbcopy)+abort'
|
||||||
|
--color header:italic
|
||||||
|
--header 'Press CTRL-Y to copy command into clipboard'"
|
||||||
|
```
|
||||||
|
- Added `change-header(...)`
|
||||||
|
- Added `transform-header(...)`
|
||||||
|
- Added `toggle-track` action
|
||||||
|
- Fixed `--track` behavior when used with `--tac`
|
||||||
|
- However, using `--track` with `--tac` is not recommended. The resulting
|
||||||
|
behavior can be very confusing.
|
||||||
|
- Bug fixes and improvements
|
||||||
|
|
||||||
|
0.39.0
|
||||||
|
------
|
||||||
|
- Added `one` event that is triggered when there's only one match
|
||||||
|
```sh
|
||||||
|
# Automatically select the only match
|
||||||
|
seq 10 | fzf --bind one:accept
|
||||||
|
```
|
||||||
|
- Added `--track` option that makes fzf track the current selection when the
|
||||||
|
result list is updated. This can be useful when browsing logs using fzf with
|
||||||
|
sorting disabled.
|
||||||
|
```sh
|
||||||
|
git log --oneline --graph --color=always | nl |
|
||||||
|
fzf --ansi --track --no-sort --layout=reverse-list
|
||||||
|
```
|
||||||
|
- If you use `--listen` option without a port number fzf will automatically
|
||||||
|
allocate an available port and export it as `$FZF_PORT` environment
|
||||||
|
variable.
|
||||||
|
```sh
|
||||||
|
# Automatic port assignment
|
||||||
|
fzf --listen --bind 'start:execute-silent:echo $FZF_PORT > /tmp/fzf-port'
|
||||||
|
|
||||||
|
# Say hello
|
||||||
|
curl "localhost:$(cat /tmp/fzf-port)" -d 'preview:echo Hello, fzf is listening on $FZF_PORT.'
|
||||||
|
```
|
||||||
|
- A carriage return and a line feed character will be rendered as dim ␍ and
|
||||||
|
␊ respectively.
|
||||||
|
```sh
|
||||||
|
printf "foo\rbar\nbaz" | fzf --read0 --preview 'echo {}'
|
||||||
|
```
|
||||||
|
- fzf will stop rendering a non-displayable characters as a space. This will
|
||||||
|
likely cause less glitches in the preview window.
|
||||||
|
```sh
|
||||||
|
fzf --preview 'head -1000 /dev/random'
|
||||||
|
```
|
||||||
|
- Bug fixes and improvements
|
||||||
|
|
||||||
|
0.38.0
|
||||||
|
------
|
||||||
|
- New actions
|
||||||
|
- `become(...)` - Replace the current fzf process with the specified
|
||||||
|
command using `execve(2)` system call.
|
||||||
|
See https://github.com/junegunn/fzf#turning-into-a-different-process for
|
||||||
|
more information.
|
||||||
|
```sh
|
||||||
|
# Open selected files in Vim
|
||||||
|
fzf --multi --bind 'enter:become(vim {+})'
|
||||||
|
|
||||||
|
# Open the file in Vim and go to the line
|
||||||
|
git grep --line-number . |
|
||||||
|
fzf --delimiter : --nth 3.. --bind 'enter:become(vim {1} +{2})'
|
||||||
|
```
|
||||||
|
- This action is not supported on Windows
|
||||||
|
- `show-preview`
|
||||||
|
- `hide-preview`
|
||||||
|
- Bug fixes
|
||||||
|
- `--preview-window 0,hidden` should not execute the preview command until
|
||||||
|
`toggle-preview` action is triggered
|
||||||
|
|
||||||
|
0.37.0
|
||||||
|
------
|
||||||
|
- Added a way to customize the separator of inline info
|
||||||
|
```sh
|
||||||
|
fzf --info 'inline: ╱ ' --prompt '╱ ' --color prompt:bright-yellow
|
||||||
|
```
|
||||||
|
- New event
|
||||||
|
- `focus` - Triggered when the focus changes due to a vertical cursor
|
||||||
|
movement or a search result update
|
||||||
|
```sh
|
||||||
|
fzf --bind 'focus:transform-preview-label:echo [ {} ]' --preview 'cat {}'
|
||||||
|
|
||||||
|
# Any action bound to the event runs synchronously and thus can make the interface sluggish
|
||||||
|
# e.g. lolcat isn't one of the fastest programs, and every cursor movement in
|
||||||
|
# fzf will be noticeably affected by its execution time
|
||||||
|
fzf --bind 'focus:transform-preview-label:echo [ {} ] | lolcat -f' --preview 'cat {}'
|
||||||
|
|
||||||
|
# Beware not to introduce an infinite loop
|
||||||
|
seq 10 | fzf --bind 'focus:up' --cycle
|
||||||
|
```
|
||||||
|
- New actions
|
||||||
|
- `change-border-label`
|
||||||
|
- `change-preview-label`
|
||||||
|
- `transform-border-label`
|
||||||
|
- `transform-preview-label`
|
||||||
|
- Bug fixes and improvements
|
||||||
|
|
||||||
|
0.36.0
|
||||||
|
------
|
||||||
|
- Added `--listen=HTTP_PORT` option to start HTTP server. It allows external
|
||||||
|
processes to send actions to perform via POST method.
|
||||||
|
```sh
|
||||||
|
# Start HTTP server on port 6266
|
||||||
|
fzf --listen 6266
|
||||||
|
|
||||||
|
# Send actions to the server
|
||||||
|
curl -XPOST localhost:6266 -d 'reload(seq 100)+change-prompt(hundred> )'
|
||||||
|
```
|
||||||
|
- Added draggable scrollbar to the main search window and the preview window
|
||||||
|
```sh
|
||||||
|
# Hide scrollbar
|
||||||
|
fzf --no-scrollbar
|
||||||
|
|
||||||
|
# Customize scrollbar
|
||||||
|
fzf --scrollbar ┆ --color scrollbar:blue
|
||||||
|
```
|
||||||
|
- New event
|
||||||
|
- Added `load` event that is triggered when the input stream is complete
|
||||||
|
and the initial processing of the list is complete.
|
||||||
|
```sh
|
||||||
|
# Change the prompt to "loaded" when the input stream is complete
|
||||||
|
(seq 10; sleep 1; seq 11 20) | fzf --prompt 'Loading> ' --bind 'load:change-prompt:Loaded> '
|
||||||
|
|
||||||
|
# You can use it instead of 'start' event without `--sync` if asynchronous
|
||||||
|
# trigger is not an issue.
|
||||||
|
(seq 10; sleep 1; seq 11 20) | fzf --bind 'load:last'
|
||||||
|
```
|
||||||
|
- New actions
|
||||||
|
- Added `pos(...)` action to move the cursor to the numeric position
|
||||||
|
- `first` and `last` are equivalent to `pos(1)` and `pos(-1)` respectively
|
||||||
|
```sh
|
||||||
|
# Put the cursor on the 10th item
|
||||||
|
seq 100 | fzf --sync --bind 'start:pos(10)'
|
||||||
|
|
||||||
|
# Put the cursor on the 10th to last item
|
||||||
|
seq 100 | fzf --sync --bind 'start:pos(-10)'
|
||||||
|
```
|
||||||
|
- Added `reload-sync(...)` action which replaces the current list only after
|
||||||
|
the reload process is complete. This is useful when the command takes
|
||||||
|
a while to produce the initial output and you don't want fzf to run against
|
||||||
|
an empty list while the command is running.
|
||||||
|
```sh
|
||||||
|
# You can still filter and select entries from the initial list for 3 seconds
|
||||||
|
seq 100 | fzf --bind 'load:reload-sync(sleep 3; seq 1000)+unbind(load)'
|
||||||
|
```
|
||||||
|
- Added `next-selected` and `prev-selected` actions to move between selected
|
||||||
|
items
|
||||||
|
```sh
|
||||||
|
# `next-selected` will move the pointer to the next selected item below the current line
|
||||||
|
# `prev-selected` will move the pointer to the previous selected item above the current line
|
||||||
|
seq 10 | fzf --multi --bind ctrl-n:next-selected,ctrl-p:prev-selected
|
||||||
|
|
||||||
|
# Both actions respect --layout option
|
||||||
|
seq 10 | fzf --multi --bind ctrl-n:next-selected,ctrl-p:prev-selected --layout reverse
|
||||||
|
```
|
||||||
|
- Added `change-query(...)` action that simply changes the query string to the
|
||||||
|
given static string. This can be useful when used with `--listen`.
|
||||||
|
```sh
|
||||||
|
curl localhost:6266 -d "change-query:$(date)"
|
||||||
|
```
|
||||||
|
- Added `transform-prompt(...)` action for transforming the prompt string
|
||||||
|
using an external command
|
||||||
|
```sh
|
||||||
|
# Press space to change the prompt string using an external command
|
||||||
|
# (only the first line of the output is taken)
|
||||||
|
fzf --bind 'space:reload(ls),load:transform-prompt(printf "%s> " "$(date)")'
|
||||||
|
```
|
||||||
|
- Added `transform-query(...)` action for transforming the query string using
|
||||||
|
an external command
|
||||||
|
```sh
|
||||||
|
# Press space to convert the query to uppercase letters
|
||||||
|
fzf --bind 'space:transform-query(tr "[:lower:]" "[:upper:]" <<< {q})'
|
||||||
|
|
||||||
|
# Bind it to 'change' event for automatic conversion
|
||||||
|
fzf --bind 'change:transform-query(tr "[:lower:]" "[:upper:]" <<< {q})'
|
||||||
|
|
||||||
|
# Can only type numbers
|
||||||
|
fzf --bind 'change:transform-query(sed "s/[^0-9]//g" <<< {q})'
|
||||||
|
```
|
||||||
|
- `put` action can optionally take an argument string
|
||||||
|
```sh
|
||||||
|
# a will put 'alpha' on the prompt, ctrl-b will put 'bravo'
|
||||||
|
fzf --bind 'a:put+put(lpha),ctrl-b:put(bravo)'
|
||||||
|
```
|
||||||
|
- Added color name `preview-label` for `--preview-label` (defaults to `label`
|
||||||
|
for `--border-label`)
|
||||||
|
- Better support for (Windows) terminals where each box-drawing character
|
||||||
|
takes 2 columns. Set `RUNEWIDTH_EASTASIAN` environment variable to `0` or `1`.
|
||||||
|
- On Vim, the variable will be automatically set if `&ambiwidth` is `double`
|
||||||
|
- Behavior changes
|
||||||
|
- fzf will always execute the preview command if the command template
|
||||||
|
contains `{q}` even when it's empty. If you prefer the old behavior,
|
||||||
|
you'll have to check if `{q}` is empty in your command.
|
||||||
|
```sh
|
||||||
|
# This will show // even when the query is empty
|
||||||
|
: | fzf --preview 'echo /{q}/'
|
||||||
|
|
||||||
|
# But if you don't want it,
|
||||||
|
: | fzf --preview '[ -n {q} ] || exit; echo /{q}/'
|
||||||
|
```
|
||||||
|
- `double-click` will behave the same as `enter` unless otherwise specified,
|
||||||
|
so you don't have to repeat the same action twice in `--bind` in most cases.
|
||||||
|
```sh
|
||||||
|
# No need to bind 'double-click' to the same action
|
||||||
|
fzf --bind 'enter:execute:less {}' # --bind 'double-click:execute:less {}'
|
||||||
|
```
|
||||||
|
- If the color for `separator` is not specified, it will default to the
|
||||||
|
color for `border`. Same holds true for `scrollbar`. This is to reduce
|
||||||
|
the number of configuration items required to achieve a consistent color
|
||||||
|
scheme.
|
||||||
|
- If `follow` flag is specified in `--preview-window` option, fzf will
|
||||||
|
automatically scroll to the bottom of the streaming preview output. But
|
||||||
|
when the user manually scrolls the window, the following stops. With
|
||||||
|
this version, fzf will resume following if the user scrolls the window
|
||||||
|
to the bottom.
|
||||||
|
- Default border style on Windows is changed to `sharp` because some
|
||||||
|
Windows terminals are not capable of displaying `rounded` border
|
||||||
|
characters correctly.
|
||||||
|
- Minor bug fixes and improvements
|
||||||
|
|
||||||
|
0.35.1
|
||||||
|
------
|
||||||
|
- Fixed a bug where fzf with `--tiebreak=chunk` crashes on inverse match query
|
||||||
|
- Fixed a bug where clicking above fzf would paste escape sequences
|
||||||
|
|
||||||
|
0.35.0
|
||||||
|
------
|
||||||
|
- Added `start` event that is triggered only once when fzf finder starts.
|
||||||
|
Since fzf consumes the input stream asynchronously, the input list is not
|
||||||
|
available unless you use `--sync`.
|
||||||
|
```sh
|
||||||
|
seq 100 | fzf --multi --sync --bind 'start:last+select-all+preview(echo welcome)'
|
||||||
|
```
|
||||||
|
- Added `--border-label` and `--border-label-pos` for putting label on the border
|
||||||
|
```sh
|
||||||
|
# ANSI color codes are supported
|
||||||
|
# (with https://github.com/busyloop/lolcat)
|
||||||
|
label=$(curl -s http://metaphorpsum.com/sentences/1 | lolcat -f)
|
||||||
|
|
||||||
|
# Border label at the center
|
||||||
|
fzf --height=10 --border --border-label="╢ $label ╟" --color=label:italic:black
|
||||||
|
|
||||||
|
# Left-aligned (positive integer)
|
||||||
|
fzf --height=10 --border --border-label="╢ $label ╟" --border-label-pos=3 --color=label:italic:black
|
||||||
|
|
||||||
|
# Right-aligned (negative integer) on the bottom line (:bottom)
|
||||||
|
fzf --height=10 --border --border-label="╢ $label ╟" --border-label-pos=-3:bottom --color=label:italic:black
|
||||||
|
```
|
||||||
|
- Also added `--preview-label` and `--preview-label-pos` for the border of the
|
||||||
|
preview window
|
||||||
|
```sh
|
||||||
|
fzf --preview 'cat {}' --border --preview-label=' Preview ' --preview-label-pos=2
|
||||||
|
```
|
||||||
|
- Info panel (match counter) will be followed by a horizontal separator by
|
||||||
|
default
|
||||||
|
- Use `--no-separator` or `--separator=''` to hide the separator
|
||||||
|
- You can specify an arbitrary string that is repeated to form the
|
||||||
|
horizontal separator. e.g. `--separator=╸`
|
||||||
|
- The color of the separator can be customized via `--color=separator:...`
|
||||||
|
- ANSI color codes are also supported
|
||||||
|
```sh
|
||||||
|
fzf --separator=╸ --color=separator:green
|
||||||
|
fzf --separator=$(lolcat -f -F 1.4 <<< ▁▁▂▃▄▅▆▆▅▄▃▂▁▁) --info=inline
|
||||||
|
```
|
||||||
|
- Added `--border=bold` and `--border=double` along with
|
||||||
|
`--preview-window=border-bold` and `--preview-window=border-double`
|
||||||
|
|
||||||
|
0.34.0
|
||||||
|
------
|
||||||
|
- Added support for adaptive `--height`. If the `--height` value is prefixed
|
||||||
|
with `~`, fzf will automatically determine the height in the range according
|
||||||
|
to the input size.
|
||||||
|
```sh
|
||||||
|
seq 1 | fzf --height ~70% --border --padding 1 --margin 1
|
||||||
|
seq 10 | fzf --height ~70% --border --padding 1 --margin 1
|
||||||
|
seq 100 | fzf --height ~70% --border --padding 1 --margin 1
|
||||||
|
```
|
||||||
|
- There are a few limitations
|
||||||
|
- Not compatible with percent top/bottom margin/padding
|
||||||
|
```sh
|
||||||
|
# This is not allowed (top/bottom margin in percent value)
|
||||||
|
fzf --height ~50% --border --margin 5%,10%
|
||||||
|
|
||||||
|
# This is allowed (top/bottom margin in fixed value)
|
||||||
|
fzf --height ~50% --border --margin 2,10%
|
||||||
|
```
|
||||||
|
- fzf will not start until it can determine the right height for the input
|
||||||
|
```sh
|
||||||
|
# fzf will open immediately
|
||||||
|
(sleep 2; seq 10) | fzf --height 50%
|
||||||
|
|
||||||
|
# fzf will open after 2 seconds
|
||||||
|
(sleep 2; seq 10) | fzf --height ~50%
|
||||||
|
(sleep 2; seq 1000) | fzf --height ~50%
|
||||||
|
```
|
||||||
|
- Fixed tcell renderer used to render full-screen fzf on Windows
|
||||||
|
- `--no-clear` is deprecated. Use `reload` action instead.
|
||||||
|
|
||||||
|
0.33.0
|
||||||
|
------
|
||||||
|
- Added `--scheme=[default|path|history]` option to choose scoring scheme
|
||||||
|
- (Experimental)
|
||||||
|
- We updated the scoring algorithm in 0.32.0, however we have learned that
|
||||||
|
this new scheme (`default`) is not always giving the optimal result
|
||||||
|
- `path`: Additional bonus point is only given to the characters after
|
||||||
|
path separator. You might want to choose this scheme if you have many
|
||||||
|
files with spaces in their paths.
|
||||||
|
- `history`: No additional bonus points are given so that we give more
|
||||||
|
weight to the chronological ordering. This is equivalent to the scoring
|
||||||
|
scheme before 0.32.0. This also sets `--tiebreak=index`.
|
||||||
|
- ANSI color sequences with colon delimiters are now supported.
|
||||||
|
```sh
|
||||||
|
printf "\e[38;5;208mOption 1\e[m\nOption 2" | fzf --ansi
|
||||||
|
printf "\e[38:5:208mOption 1\e[m\nOption 2" | fzf --ansi
|
||||||
|
```
|
||||||
|
- Support `border-{up,down}` as the synonyms for `border-{top,bottom}` in
|
||||||
|
`--preview-window`
|
||||||
|
- Added support for ANSI `strikethrough`
|
||||||
|
```sh
|
||||||
|
printf "\e[9mdeleted" | fzf --ansi
|
||||||
|
fzf --color fg+:strikethrough
|
||||||
|
```
|
||||||
|
|
||||||
|
0.32.1
|
||||||
|
------
|
||||||
|
- Fixed incorrect ordering of `--tiebreak=chunk`
|
||||||
|
- fzf-tmux will show fzf border instead of tmux popup border (requires tmux 3.3)
|
||||||
|
```sh
|
||||||
|
fzf-tmux -p70%
|
||||||
|
fzf-tmux -p70% --color=border:bright-red
|
||||||
|
fzf-tmux -p100%,60% --color=border:bright-yellow --border=horizontal --padding 1,5 --margin 1,0
|
||||||
|
fzf-tmux -p70%,100% --color=border:bright-green --border=vertical
|
||||||
|
|
||||||
|
# Key bindings (CTRL-T, CTRL-R, ALT-C) will use these options
|
||||||
|
export FZF_TMUX_OPTS='-p100%,60% --color=border:green --border=horizontal --padding 1,5 --margin 1,0'
|
||||||
|
```
|
||||||
|
|
||||||
|
0.32.0
|
||||||
|
------
|
||||||
|
- Updated the scoring algorithm
|
||||||
|
- Different bonus points to different categories of word boundaries
|
||||||
|
(listed higher to lower bonus point)
|
||||||
|
- Word after whitespace characters or beginning of the string
|
||||||
|
- Word after common delimiter characters (`/,:;|`)
|
||||||
|
- Word after other non-word characters
|
||||||
|
```sh
|
||||||
|
# foo/bar.sh` is preferred over `foo-bar.sh` on `bar`
|
||||||
|
fzf --query=bar --height=4 << EOF
|
||||||
|
foo-bar.sh
|
||||||
|
foo/bar.sh
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
- Added a new tiebreak `chunk`
|
||||||
|
- Favors the line with shorter matched chunk. A chunk is a set of
|
||||||
|
consecutive non-whitespace characters.
|
||||||
|
- Unlike the default `length`, this scheme works well with tabular input
|
||||||
|
```sh
|
||||||
|
# length prefers item #1, because the whole line is shorter,
|
||||||
|
# chunk prefers item #2, because the matched chunk ("foo") is shorter
|
||||||
|
fzf --height=6 --header-lines=2 --tiebreak=chunk --reverse --query=fo << "EOF"
|
||||||
|
N | Field1 | Field2 | Field3
|
||||||
|
- | ------ | ------ | ------
|
||||||
|
1 | hello | foobar | baz
|
||||||
|
2 | world | foo | bazbaz
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
- If the input does not contain any spaces, `chunk` is equivalent to
|
||||||
|
`length`. But we're not going to set it as the default because it is
|
||||||
|
computationally more expensive.
|
||||||
|
- Bug fixes and improvements
|
||||||
|
|
||||||
|
0.31.0
|
||||||
|
------
|
||||||
|
- Added support for an alternative preview window layout that is activated
|
||||||
|
when the size of the preview window is smaller than a certain threshold.
|
||||||
|
```sh
|
||||||
|
# If the width of the preview window is smaller than 50 columns,
|
||||||
|
# it will be displayed above the search window.
|
||||||
|
fzf --preview 'cat {}' --preview-window 'right,50%,border-left,<50(up,30%,border-bottom)'
|
||||||
|
|
||||||
|
# Or you can just hide it like so
|
||||||
|
fzf --preview 'cat {}' --preview-window '<50(hidden)'
|
||||||
|
```
|
||||||
|
- fzf now uses SGR mouse mode to properly support mouse on larger terminals
|
||||||
|
- You can now use characters that do not satisfy `unicode.IsGraphic` constraint
|
||||||
|
for `--marker`, `--pointer`, and `--ellipsis`. Allows Nerd Fonts and stuff.
|
||||||
|
Use at your own risk.
|
||||||
|
- Bug fixes and improvements
|
||||||
|
- Shell extension
|
||||||
|
- `kill` completion now requires trigger sequence (`**`) for consistency
|
||||||
|
|
||||||
|
0.30.0
|
||||||
|
------
|
||||||
|
- Fixed cursor flickering over the screen by hiding it during rendering
|
||||||
|
- Added `--ellipsis` option. You can take advantage of it to make fzf
|
||||||
|
effectively search non-visible parts of the item.
|
||||||
|
```sh
|
||||||
|
# Search against hidden line numbers on the far right
|
||||||
|
nl /usr/share/dict/words |
|
||||||
|
awk '{printf "%s%1000s\n", $2, $1}' |
|
||||||
|
fzf --nth=-1 --no-hscroll --ellipsis='' |
|
||||||
|
awk '{print $2}'
|
||||||
|
```
|
||||||
|
- Added `rebind` action for restoring bindings after `unbind`
|
||||||
|
- Bug fixes and improvements
|
||||||
|
|
||||||
0.29.0
|
0.29.0
|
||||||
------
|
------
|
||||||
- Added `change-preview(...)` action to change the `--preview` command
|
- Added `change-preview(...)` action to change the `--preview` command
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
FROM archlinux
|
FROM --platform=linux/amd64 archlinux
|
||||||
RUN pacman -Sy && pacman --noconfirm -S awk git tmux zsh fish ruby procps go make gcc
|
RUN pacman -Sy && pacman --noconfirm -S awk git tmux zsh fish ruby procps go make gcc
|
||||||
RUN gem install --no-document -v 5.14.2 minitest
|
RUN gem install --no-document -v 5.14.2 minitest
|
||||||
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
||||||
|
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2021 Junegunn Choi
|
Copyright (c) 2013-2023 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
|
||||||
|
16
Makefile
16
Makefile
@@ -20,7 +20,7 @@ VERSION_REGEX := $(subst .,\.,$(VERSION_TRIM))
|
|||||||
ifdef FZF_REVISION
|
ifdef FZF_REVISION
|
||||||
REVISION := $(FZF_REVISION)
|
REVISION := $(FZF_REVISION)
|
||||||
else
|
else
|
||||||
REVISION := $(shell git log -n 1 --pretty=format:%h -- $(SOURCES) 2> /dev/null)
|
REVISION := $(shell git log -n 1 --pretty=format:%h --abbrev=8 -- $(SOURCES) 2> /dev/null)
|
||||||
endif
|
endif
|
||||||
ifeq ($(REVISION),)
|
ifeq ($(REVISION),)
|
||||||
$(error Not on git repository; cannot determine $$FZF_REVISION)
|
$(error Not on git repository; cannot determine $$FZF_REVISION)
|
||||||
@@ -29,12 +29,14 @@ BUILD_FLAGS := -a -ldflags "-s -w -X main.version=$(VERSION) -X main.revision
|
|||||||
|
|
||||||
BINARY32 := fzf-$(GOOS)_386
|
BINARY32 := fzf-$(GOOS)_386
|
||||||
BINARY64 := fzf-$(GOOS)_amd64
|
BINARY64 := fzf-$(GOOS)_amd64
|
||||||
|
BINARYS390 := fzf-$(GOOS)_s390x
|
||||||
BINARYARM5 := fzf-$(GOOS)_arm5
|
BINARYARM5 := fzf-$(GOOS)_arm5
|
||||||
BINARYARM6 := fzf-$(GOOS)_arm6
|
BINARYARM6 := fzf-$(GOOS)_arm6
|
||||||
BINARYARM7 := fzf-$(GOOS)_arm7
|
BINARYARM7 := fzf-$(GOOS)_arm7
|
||||||
BINARYARM8 := fzf-$(GOOS)_arm8
|
BINARYARM8 := fzf-$(GOOS)_arm8
|
||||||
BINARYPPC64LE := fzf-$(GOOS)_ppc64le
|
BINARYPPC64LE := fzf-$(GOOS)_ppc64le
|
||||||
BINARYRISCV64 := fzf-$(GOOS)_riscv64
|
BINARYRISCV64 := fzf-$(GOOS)_riscv64
|
||||||
|
BINARYLOONG64 := fzf-$(GOOS)_loong64
|
||||||
|
|
||||||
# https://en.wikipedia.org/wiki/Uname
|
# https://en.wikipedia.org/wiki/Uname
|
||||||
UNAME_M := $(shell uname -m)
|
UNAME_M := $(shell uname -m)
|
||||||
@@ -42,6 +44,8 @@ ifeq ($(UNAME_M),x86_64)
|
|||||||
BINARY := $(BINARY64)
|
BINARY := $(BINARY64)
|
||||||
else ifeq ($(UNAME_M),amd64)
|
else ifeq ($(UNAME_M),amd64)
|
||||||
BINARY := $(BINARY64)
|
BINARY := $(BINARY64)
|
||||||
|
else ifeq ($(UNAME_M),s390x)
|
||||||
|
BINARY := $(BINARYS390)
|
||||||
else ifeq ($(UNAME_M),i686)
|
else ifeq ($(UNAME_M),i686)
|
||||||
BINARY := $(BINARY32)
|
BINARY := $(BINARY32)
|
||||||
else ifeq ($(UNAME_M),i386)
|
else ifeq ($(UNAME_M),i386)
|
||||||
@@ -62,6 +66,8 @@ else ifeq ($(UNAME_M),ppc64le)
|
|||||||
BINARY := $(BINARYPPC64LE)
|
BINARY := $(BINARYPPC64LE)
|
||||||
else ifeq ($(UNAME_M),riscv64)
|
else ifeq ($(UNAME_M),riscv64)
|
||||||
BINARY := $(BINARYRISCV64)
|
BINARY := $(BINARYRISCV64)
|
||||||
|
else ifeq ($(UNAME_M),loongarch64)
|
||||||
|
BINARY := $(BINARYLOONG64)
|
||||||
else
|
else
|
||||||
$(error Build on $(UNAME_M) is not supported, yet.)
|
$(error Build on $(UNAME_M) is not supported, yet.)
|
||||||
endif
|
endif
|
||||||
@@ -82,7 +88,7 @@ bench:
|
|||||||
install: bin/fzf
|
install: bin/fzf
|
||||||
|
|
||||||
build:
|
build:
|
||||||
goreleaser --rm-dist --snapshot
|
goreleaser build --rm-dist --snapshot --skip-post-hooks
|
||||||
|
|
||||||
release:
|
release:
|
||||||
ifndef GITHUB_TOKEN
|
ifndef GITHUB_TOKEN
|
||||||
@@ -129,6 +135,8 @@ target/$(BINARY32): $(SOURCES)
|
|||||||
target/$(BINARY64): $(SOURCES)
|
target/$(BINARY64): $(SOURCES)
|
||||||
GOARCH=amd64 $(GO) build $(BUILD_FLAGS) -o $@
|
GOARCH=amd64 $(GO) build $(BUILD_FLAGS) -o $@
|
||||||
|
|
||||||
|
target/$(BINARYS390): $(SOURCES)
|
||||||
|
GOARCH=s390x $(GO) build $(BUILD_FLAGS) -o $@
|
||||||
# https://github.com/golang/go/wiki/GoArm
|
# https://github.com/golang/go/wiki/GoArm
|
||||||
target/$(BINARYARM5): $(SOURCES)
|
target/$(BINARYARM5): $(SOURCES)
|
||||||
GOARCH=arm GOARM=5 $(GO) build $(BUILD_FLAGS) -o $@
|
GOARCH=arm GOARM=5 $(GO) build $(BUILD_FLAGS) -o $@
|
||||||
@@ -148,7 +156,11 @@ target/$(BINARYPPC64LE): $(SOURCES)
|
|||||||
target/$(BINARYRISCV64): $(SOURCES)
|
target/$(BINARYRISCV64): $(SOURCES)
|
||||||
GOARCH=riscv64 $(GO) build $(BUILD_FLAGS) -o $@
|
GOARCH=riscv64 $(GO) build $(BUILD_FLAGS) -o $@
|
||||||
|
|
||||||
|
target/$(BINARYLOONG64): $(SOURCES)
|
||||||
|
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:
|
||||||
|
@@ -12,7 +12,10 @@ differ depending on the package manager.
|
|||||||
" If installed using Homebrew
|
" If installed using Homebrew
|
||||||
set rtp+=/usr/local/opt/fzf
|
set rtp+=/usr/local/opt/fzf
|
||||||
|
|
||||||
" If installed using git
|
" If installed using Homebrew on Apple Silicon
|
||||||
|
set rtp+=/opt/homebrew/opt/fzf
|
||||||
|
|
||||||
|
" If you have cloned fzf on ~/.fzf directory
|
||||||
set rtp+=~/.fzf
|
set rtp+=~/.fzf
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -23,7 +26,7 @@ written as:
|
|||||||
" If installed using Homebrew
|
" If installed using Homebrew
|
||||||
Plug '/usr/local/opt/fzf'
|
Plug '/usr/local/opt/fzf'
|
||||||
|
|
||||||
" If installed using git
|
" If you have cloned fzf on ~/.fzf directory
|
||||||
Plug '~/.fzf'
|
Plug '~/.fzf'
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -115,7 +118,7 @@ let g:fzf_action = {
|
|||||||
|
|
||||||
" An action can be a reference to a function that processes selected lines
|
" An action can be a reference to a function that processes selected lines
|
||||||
function! s:build_quickfix_list(lines)
|
function! s:build_quickfix_list(lines)
|
||||||
call setqflist(map(copy(a:lines), '{ "filename": v:val }'))
|
call setqflist(map(copy(a:lines), '{ "filename": v:val, "lnum": 1 }'))
|
||||||
copen
|
copen
|
||||||
cc
|
cc
|
||||||
endfunction
|
endfunction
|
||||||
@@ -309,7 +312,7 @@ following options are allowed:
|
|||||||
- `yoffset` [float default 0.5 range [0 ~ 1]]
|
- `yoffset` [float default 0.5 range [0 ~ 1]]
|
||||||
- `xoffset` [float default 0.5 range [0 ~ 1]]
|
- `xoffset` [float default 0.5 range [0 ~ 1]]
|
||||||
- `relative` [boolean default v:false]
|
- `relative` [boolean default v:false]
|
||||||
- `border` [string default `rounded`]: Border style
|
- `border` [string default `rounded` (`sharp` on Windows)]: Border style
|
||||||
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right` / `no[ne]`
|
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right` / `no[ne]`
|
||||||
|
|
||||||
`fzf#wrap`
|
`fzf#wrap`
|
||||||
@@ -483,4 +486,4 @@ autocmd FileType fzf set laststatus=0 noshowmode noruler
|
|||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2021 Junegunn Choi
|
Copyright (c) 2013-2023 Junegunn Choi
|
||||||
|
220
README.md
220
README.md
@@ -25,11 +25,11 @@ Table of Contents
|
|||||||
<!-- vim-markdown-toc GFM -->
|
<!-- vim-markdown-toc GFM -->
|
||||||
|
|
||||||
* [Installation](#installation)
|
* [Installation](#installation)
|
||||||
* [Using Homebrew](#using-homebrew)
|
* [Using Homebrew](#using-homebrew)
|
||||||
* [Using git](#using-git)
|
* [Using git](#using-git)
|
||||||
* [Using Linux package managers](#using-linux-package-managers)
|
* [Using Linux package managers](#using-linux-package-managers)
|
||||||
* [Windows](#windows)
|
* [Windows](#windows)
|
||||||
* [As Vim plugin](#as-vim-plugin)
|
* [As Vim plugin](#as-vim-plugin)
|
||||||
* [Upgrading fzf](#upgrading-fzf)
|
* [Upgrading fzf](#upgrading-fzf)
|
||||||
* [Building fzf](#building-fzf)
|
* [Building fzf](#building-fzf)
|
||||||
* [Usage](#usage)
|
* [Usage](#usage)
|
||||||
@@ -52,13 +52,14 @@ Table of Contents
|
|||||||
* [Custom fuzzy completion](#custom-fuzzy-completion)
|
* [Custom fuzzy completion](#custom-fuzzy-completion)
|
||||||
* [Vim plugin](#vim-plugin)
|
* [Vim plugin](#vim-plugin)
|
||||||
* [Advanced topics](#advanced-topics)
|
* [Advanced topics](#advanced-topics)
|
||||||
* [Performance](#performance)
|
* [Performance](#performance)
|
||||||
* [Executing external programs](#executing-external-programs)
|
* [Executing external programs](#executing-external-programs)
|
||||||
* [Reloading the candidate list](#reloading-the-candidate-list)
|
* [Turning into a different process](#turning-into-a-different-process)
|
||||||
* [1. Update the list of processes by pressing CTRL-R](#1-update-the-list-of-processes-by-pressing-ctrl-r)
|
* [Reloading the candidate list](#reloading-the-candidate-list)
|
||||||
* [2. Switch between sources by pressing CTRL-D or CTRL-F](#2-switch-between-sources-by-pressing-ctrl-d-or-ctrl-f)
|
* [1. Update the list of processes by pressing CTRL-R](#1-update-the-list-of-processes-by-pressing-ctrl-r)
|
||||||
* [3. Interactive ripgrep integration](#3-interactive-ripgrep-integration)
|
* [2. Switch between sources by pressing CTRL-D or CTRL-F](#2-switch-between-sources-by-pressing-ctrl-d-or-ctrl-f)
|
||||||
* [Preview window](#preview-window)
|
* [3. Interactive ripgrep integration](#3-interactive-ripgrep-integration)
|
||||||
|
* [Preview window](#preview-window)
|
||||||
* [Tips](#tips)
|
* [Tips](#tips)
|
||||||
* [Respecting `.gitignore`](#respecting-gitignore)
|
* [Respecting `.gitignore`](#respecting-gitignore)
|
||||||
* [Fish shell](#fish-shell)
|
* [Fish shell](#fish-shell)
|
||||||
@@ -115,7 +116,7 @@ git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
|
|||||||
| Package Manager | Linux Distribution | Command |
|
| Package Manager | Linux Distribution | Command |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| APK | Alpine Linux | `sudo apk add fzf` |
|
| APK | Alpine Linux | `sudo apk add fzf` |
|
||||||
| APT | Debian 9+/Ubuntu 19.10+ | `sudo apt-get install fzf` |
|
| APT | Debian 9+/Ubuntu 19.10+ | `sudo apt install fzf` |
|
||||||
| Conda | | `conda install -c conda-forge fzf` |
|
| Conda | | `conda install -c conda-forge fzf` |
|
||||||
| DNF | Fedora | `sudo dnf install fzf` |
|
| DNF | Fedora | `sudo dnf install fzf` |
|
||||||
| Nix | NixOS, etc. | `nix-env -iA nixpkgs.fzf` |
|
| Nix | NixOS, etc. | `nix-env -iA nixpkgs.fzf` |
|
||||||
@@ -123,28 +124,31 @@ git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
|
|||||||
| pkg | FreeBSD | `pkg install fzf` |
|
| pkg | FreeBSD | `pkg install fzf` |
|
||||||
| pkgin | NetBSD | `pkgin install fzf` |
|
| pkgin | NetBSD | `pkgin install fzf` |
|
||||||
| pkg_add | OpenBSD | `pkg_add fzf` |
|
| pkg_add | OpenBSD | `pkg_add fzf` |
|
||||||
|
| Portage | Gentoo | `emerge --ask app-shells/fzf` |
|
||||||
| XBPS | Void Linux | `sudo xbps-install -S fzf` |
|
| XBPS | Void Linux | `sudo xbps-install -S fzf` |
|
||||||
| Zypper | openSUSE | `sudo zypper install fzf` |
|
| Zypper | openSUSE | `sudo zypper install fzf` |
|
||||||
|
|
||||||
> :warning: **Key bindings (CTRL-T / CTRL-R / ALT-C) and fuzzy auto-completion
|
> :warning: **Key bindings (CTRL-T / CTRL-R / ALT-C) and fuzzy auto-completion
|
||||||
> may not be enabled by default.**
|
> may not be enabled by default.**
|
||||||
>
|
>
|
||||||
> Refer to the package documentation for more information. (e.g. `apt-cache show fzf`)
|
> Refer to the package documentation for more information. (e.g. `apt show fzf`)
|
||||||
|
|
||||||
[](https://repology.org/project/fzf/versions)
|
[](https://repology.org/project/fzf/versions)
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
Pre-built binaries for Windows can be downloaded [here][bin]. fzf is also
|
Pre-built binaries for Windows can be downloaded [here][bin]. fzf is also
|
||||||
available via [Chocolatey][choco] and [Scoop][scoop]:
|
available via [Chocolatey][choco], [Scoop][scoop], and [Winget][winget]:
|
||||||
|
|
||||||
| Package manager | Command |
|
| Package manager | Command |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Chocolatey | `choco install fzf` |
|
| Chocolatey | `choco install fzf` |
|
||||||
| Scoop | `scoop install fzf` |
|
| Scoop | `scoop install fzf` |
|
||||||
|
| Winget | `winget install fzf` |
|
||||||
|
|
||||||
[choco]: https://chocolatey.org/packages/fzf
|
[choco]: https://chocolatey.org/packages/fzf
|
||||||
[scoop]: https://github.com/ScoopInstaller/Main/blob/master/bucket/fzf.json
|
[scoop]: https://github.com/ScoopInstaller/Main/blob/master/bucket/fzf.json
|
||||||
|
[winget]: https://github.com/microsoft/winget-pkgs/tree/master/manifests/j/junegunn/fzf
|
||||||
|
|
||||||
Known issues and limitations on Windows can be found on [the wiki
|
Known issues and limitations on Windows can be found on [the wiki
|
||||||
page][windows-wiki].
|
page][windows-wiki].
|
||||||
@@ -202,7 +206,23 @@ files excluding hidden ones. (You can override the default command with
|
|||||||
vim $(fzf)
|
vim $(fzf)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Using the finder
|
> *:bulb: A more robust solution would be to use `xargs` but we've presented
|
||||||
|
> the above as it's easier to grasp*
|
||||||
|
> ```sh
|
||||||
|
> fzf --print0 | xargs -0 -o vim
|
||||||
|
> ```
|
||||||
|
|
||||||
|
>
|
||||||
|
> *:bulb: fzf also has the ability to turn itself into a different process.*
|
||||||
|
>
|
||||||
|
> ```sh
|
||||||
|
> fzf --bind 'enter:become(vim {})'
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> *See [Turning into a different process](#turning-into-a-different-process)
|
||||||
|
> for more information.*
|
||||||
|
|
||||||
|
### Using the finder
|
||||||
|
|
||||||
- `CTRL-K` / `CTRL-J` (or `CTRL-P` / `CTRL-N`) to move cursor up and down
|
- `CTRL-K` / `CTRL-J` (or `CTRL-P` / `CTRL-N`) to move cursor up and down
|
||||||
- `Enter` key to select the item, `CTRL-C` / `CTRL-G` / `ESC` to exit
|
- `Enter` key to select the item, `CTRL-C` / `CTRL-G` / `ESC` to exit
|
||||||
@@ -211,7 +231,7 @@ vim $(fzf)
|
|||||||
- Mouse: scroll, click, double-click; shift-click and shift-scroll on
|
- Mouse: scroll, click, double-click; shift-click and shift-scroll on
|
||||||
multi-select mode
|
multi-select mode
|
||||||
|
|
||||||
#### Layout
|
### Layout
|
||||||
|
|
||||||
fzf by default starts in fullscreen mode, but you can make it start below the
|
fzf by default starts in fullscreen mode, but you can make it start below the
|
||||||
cursor with `--height` option.
|
cursor with `--height` option.
|
||||||
@@ -234,7 +254,7 @@ default. For example,
|
|||||||
export FZF_DEFAULT_OPTS='--height 40% --layout=reverse --border'
|
export FZF_DEFAULT_OPTS='--height 40% --layout=reverse --border'
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Search syntax
|
### Search syntax
|
||||||
|
|
||||||
Unless otherwise specified, fzf starts in "extended-search mode" where you can
|
Unless otherwise specified, fzf starts in "extended-search mode" where you can
|
||||||
type in multiple search terms delimited by spaces. e.g. `^music .mp3$ sbtrkt
|
type in multiple search terms delimited by spaces. e.g. `^music .mp3$ sbtrkt
|
||||||
@@ -262,7 +282,7 @@ or `py`.
|
|||||||
^core go$ | rb$ | py$
|
^core go$ | rb$ | py$
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Environment variables
|
### Environment variables
|
||||||
|
|
||||||
- `FZF_DEFAULT_COMMAND`
|
- `FZF_DEFAULT_COMMAND`
|
||||||
- Default command to use when input is tty
|
- Default command to use when input is tty
|
||||||
@@ -278,11 +298,11 @@ or `py`.
|
|||||||
- Default options
|
- Default options
|
||||||
- e.g. `export FZF_DEFAULT_OPTS="--layout=reverse --inline-info"`
|
- e.g. `export FZF_DEFAULT_OPTS="--layout=reverse --inline-info"`
|
||||||
|
|
||||||
#### Options
|
### Options
|
||||||
|
|
||||||
See the man page (`man fzf`) for the full list of options.
|
See the man page (`man fzf`) for the full list of options.
|
||||||
|
|
||||||
#### Demo
|
### Demo
|
||||||
If you learn by watching videos, check out this screencast by [@samoshkin](https://github.com/samoshkin) to explore `fzf` features.
|
If you learn by watching videos, check out this screencast by [@samoshkin](https://github.com/samoshkin) to explore `fzf` features.
|
||||||
|
|
||||||
<a title="fzf - command-line fuzzy finder" href="https://www.youtube.com/watch?v=qgG5Jhi_Els">
|
<a title="fzf - command-line fuzzy finder" href="https://www.youtube.com/watch?v=qgG5Jhi_Els">
|
||||||
@@ -333,17 +353,37 @@ fish.
|
|||||||
|
|
||||||
- `CTRL-T` - Paste the selected files and directories onto the command-line
|
- `CTRL-T` - Paste the selected files and directories onto the command-line
|
||||||
- Set `FZF_CTRL_T_COMMAND` to override the default command
|
- Set `FZF_CTRL_T_COMMAND` to override the default command
|
||||||
- Set `FZF_CTRL_T_OPTS` to pass additional options
|
- Set `FZF_CTRL_T_OPTS` to pass additional options to fzf
|
||||||
|
```sh
|
||||||
|
# Preview file content using bat (https://github.com/sharkdp/bat)
|
||||||
|
export FZF_CTRL_T_OPTS="
|
||||||
|
--preview 'bat -n --color=always {}'
|
||||||
|
--bind 'ctrl-/:change-preview-window(down|hidden|)'"
|
||||||
|
```
|
||||||
- `CTRL-R` - Paste the selected command from history onto the command-line
|
- `CTRL-R` - Paste the selected command from history onto the command-line
|
||||||
- If you want to see the commands in chronological order, press `CTRL-R`
|
- If you want to see the commands in chronological order, press `CTRL-R`
|
||||||
again which toggles sorting by relevance
|
again which toggles sorting by relevance
|
||||||
- Set `FZF_CTRL_R_OPTS` to pass additional options
|
- Set `FZF_CTRL_R_OPTS` to pass additional options to fzf
|
||||||
|
```sh
|
||||||
|
# CTRL-/ to toggle small preview window to see the full command
|
||||||
|
# CTRL-Y to copy the command into clipboard using pbcopy
|
||||||
|
export FZF_CTRL_R_OPTS="
|
||||||
|
--preview 'echo {}' --preview-window up:3:hidden:wrap
|
||||||
|
--bind 'ctrl-/:toggle-preview'
|
||||||
|
--bind 'ctrl-y:execute-silent(echo -n {2..} | pbcopy)+abort'
|
||||||
|
--color header:italic
|
||||||
|
--header 'Press CTRL-Y to copy command into clipboard'"
|
||||||
|
```
|
||||||
- `ALT-C` - cd into the selected directory
|
- `ALT-C` - cd into the selected directory
|
||||||
- Set `FZF_ALT_C_COMMAND` to override the default command
|
- Set `FZF_ALT_C_COMMAND` to override the default command
|
||||||
- Set `FZF_ALT_C_OPTS` to pass additional options
|
- Set `FZF_ALT_C_OPTS` to pass additional options to fzf
|
||||||
|
```sh
|
||||||
|
# Print tree structure in the preview window
|
||||||
|
export FZF_ALT_C_OPTS="--preview 'tree -C {}'"
|
||||||
|
```
|
||||||
|
|
||||||
If you're on a tmux session, you can start fzf in a tmux split-pane or in
|
If you're on a tmux session, you can start fzf in a tmux split-pane or in
|
||||||
a tmux popup window by setting `FZF_TMUX_OPTS` (e.g. `-d 40%`).
|
a tmux popup window by setting `FZF_TMUX_OPTS` (e.g. `export FZF_TMUX_OPTS='-p80%,60%'`).
|
||||||
See `fzf-tmux --help` for available options.
|
See `fzf-tmux --help` for available options.
|
||||||
|
|
||||||
More tips can be found on [the wiki page](https://github.com/junegunn/fzf/wiki/Configuring-shell-key-bindings).
|
More tips can be found on [the wiki page](https://github.com/junegunn/fzf/wiki/Configuring-shell-key-bindings).
|
||||||
@@ -351,7 +391,7 @@ More tips can be found on [the wiki page](https://github.com/junegunn/fzf/wiki/C
|
|||||||
Fuzzy completion for bash and zsh
|
Fuzzy completion for bash and zsh
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
#### Files and directories
|
### Files and directories
|
||||||
|
|
||||||
Fuzzy completion for files and directories can be triggered if the word before
|
Fuzzy completion for files and directories can be triggered if the word before
|
||||||
the cursor ends with the trigger sequence, which is by default `**`.
|
the cursor ends with the trigger sequence, which is by default `**`.
|
||||||
@@ -380,17 +420,16 @@ cd **<TAB>
|
|||||||
cd ~/github/fzf**<TAB>
|
cd ~/github/fzf**<TAB>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Process IDs
|
### Process IDs
|
||||||
|
|
||||||
Fuzzy completion for PIDs is provided for kill command. In this case,
|
Fuzzy completion for PIDs is provided for kill command.
|
||||||
there is no trigger sequence; just press the tab key after the kill command.
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Can select multiple processes with <TAB> or <Shift-TAB> keys
|
# Can select multiple processes with <TAB> or <Shift-TAB> keys
|
||||||
kill -9 <TAB>
|
kill -9 **<TAB>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Host names
|
### Host names
|
||||||
|
|
||||||
For ssh and telnet commands, fuzzy completion for hostnames is provided. The
|
For ssh and telnet commands, fuzzy completion for hostnames is provided. The
|
||||||
names are extracted from /etc/hosts and ~/.ssh/config.
|
names are extracted from /etc/hosts and ~/.ssh/config.
|
||||||
@@ -400,7 +439,7 @@ ssh **<TAB>
|
|||||||
telnet **<TAB>
|
telnet **<TAB>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Environment variables / Aliases
|
### Environment variables / Aliases
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
unset **<TAB>
|
unset **<TAB>
|
||||||
@@ -408,7 +447,7 @@ export **<TAB>
|
|||||||
unalias **<TAB>
|
unalias **<TAB>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Settings
|
### Settings
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Use ~~ as the trigger sequence instead of the default **
|
# Use ~~ as the trigger sequence instead of the default **
|
||||||
@@ -430,7 +469,7 @@ _fzf_compgen_dir() {
|
|||||||
fd --type d --hidden --follow --exclude ".git" . "$1"
|
fd --type d --hidden --follow --exclude ".git" . "$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
# (EXPERIMENTAL) Advanced customization of fzf options via _fzf_comprun function
|
# Advanced customization of fzf options via _fzf_comprun function
|
||||||
# - The first argument to the function is the name of the command.
|
# - The first argument to the function is the name of the command.
|
||||||
# - You should make sure to pass the rest of the arguments to fzf.
|
# - You should make sure to pass the rest of the arguments to fzf.
|
||||||
_fzf_comprun() {
|
_fzf_comprun() {
|
||||||
@@ -438,15 +477,15 @@ _fzf_comprun() {
|
|||||||
shift
|
shift
|
||||||
|
|
||||||
case "$command" in
|
case "$command" in
|
||||||
cd) fzf "$@" --preview 'tree -C {} | head -200' ;;
|
cd) fzf --preview 'tree -C {} | head -200' "$@" ;;
|
||||||
export|unset) fzf "$@" --preview "eval 'echo \$'{}" ;;
|
export|unset) fzf --preview "eval 'echo \$'{}" "$@" ;;
|
||||||
ssh) fzf "$@" --preview 'dig {}' ;;
|
ssh) fzf --preview 'dig {}' "$@" ;;
|
||||||
*) fzf "$@" ;;
|
*) fzf --preview 'bat -n --color=always {}' "$@" ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Supported commands
|
### Supported commands
|
||||||
|
|
||||||
On bash, fuzzy completion is enabled only for a predefined set of commands
|
On bash, fuzzy completion is enabled only for a predefined set of commands
|
||||||
(`complete | grep _fzf` to see the list). But you can enable it for other
|
(`complete | grep _fzf` to see the list). But you can enable it for other
|
||||||
@@ -458,7 +497,7 @@ _fzf_setup_completion path ag git kubectl
|
|||||||
_fzf_setup_completion dir tree
|
_fzf_setup_completion dir tree
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Custom fuzzy completion
|
### Custom fuzzy completion
|
||||||
|
|
||||||
_**(Custom completion API is experimental and subject to change)**_
|
_**(Custom completion API is experimental and subject to change)**_
|
||||||
|
|
||||||
@@ -518,9 +557,8 @@ Advanced topics
|
|||||||
|
|
||||||
### Performance
|
### Performance
|
||||||
|
|
||||||
fzf is fast and is [getting even faster][perf]. Performance should not be
|
fzf is fast. Performance should not be a problem in most use cases. However,
|
||||||
a problem in most use cases. However, you might want to be aware of the
|
you might want to be aware of the options that can affect performance.
|
||||||
options that affect performance.
|
|
||||||
|
|
||||||
- `--ansi` tells fzf to extract and parse ANSI color codes in the input, and it
|
- `--ansi` tells fzf to extract and parse ANSI color codes in the input, and it
|
||||||
makes the initial scanning slower. So it's not recommended that you add it
|
makes the initial scanning slower. So it's not recommended that you add it
|
||||||
@@ -528,12 +566,6 @@ options that affect performance.
|
|||||||
- `--nth` makes fzf slower because it has to tokenize each line.
|
- `--nth` makes fzf slower because it has to tokenize each line.
|
||||||
- `--with-nth` makes fzf slower as fzf has to tokenize and reassemble each
|
- `--with-nth` makes fzf slower as fzf has to tokenize and reassemble each
|
||||||
line.
|
line.
|
||||||
- If you absolutely need better performance, you can consider using
|
|
||||||
`--algo=v1` (the default being `v2`) to make fzf use a faster greedy
|
|
||||||
algorithm. However, this algorithm is not guaranteed to find the optimal
|
|
||||||
ordering of the matches and is not recommended.
|
|
||||||
|
|
||||||
[perf]: https://junegunn.kr/images/fzf-0.17.0.png
|
|
||||||
|
|
||||||
### Executing external programs
|
### Executing external programs
|
||||||
|
|
||||||
@@ -548,6 +580,47 @@ fzf --bind 'f1:execute(less -f {}),ctrl-y:execute-silent(echo {} | pbcopy)+abort
|
|||||||
|
|
||||||
See *KEY BINDINGS* section of the man page for details.
|
See *KEY BINDINGS* section of the man page for details.
|
||||||
|
|
||||||
|
### Turning into a different process
|
||||||
|
|
||||||
|
`become(...)` is similar to `execute(...)`/`execute-silent(...)` described
|
||||||
|
above, but instead of executing the command and coming back to fzf on
|
||||||
|
complete, it turns fzf into a new process for the command.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
fzf --bind 'enter:become(vim {})'
|
||||||
|
```
|
||||||
|
|
||||||
|
Compared to the seemingly equivalent command substitution `vim "$(fzf)"`, this
|
||||||
|
approach has several advantages:
|
||||||
|
|
||||||
|
* Vim will not open an empty file when you terminate fzf with
|
||||||
|
<kbd>CTRL-C</kbd>
|
||||||
|
* Vim will not open an empty file when you press <kbd>ENTER</kbd> on an empty
|
||||||
|
result
|
||||||
|
* Can handle multiple selections even when they have whitespaces
|
||||||
|
```sh
|
||||||
|
fzf --multi --bind 'enter:become(vim {+})'
|
||||||
|
```
|
||||||
|
|
||||||
|
To be fair, running `fzf --print0 | xargs -0 -o vim` instead of `vim "$(fzf)"`
|
||||||
|
resolves all of the issues mentioned. Nonetheless, `become(...)` still offers
|
||||||
|
additional benefits in different scenarios.
|
||||||
|
|
||||||
|
* You can set up multiple bindings to handle the result in different ways
|
||||||
|
without any wrapping script
|
||||||
|
```sh
|
||||||
|
fzf --bind 'enter:become(vim {}),ctrl-e:become(emacs {})'
|
||||||
|
```
|
||||||
|
* Previously, you would have to use `--expect=ctrl-e` and check the first
|
||||||
|
line of the output of fzf
|
||||||
|
* You can easily build the subsequent command using the field index
|
||||||
|
expressions of fzf
|
||||||
|
```sh
|
||||||
|
# Open the file in Vim and go to the line
|
||||||
|
git grep --line-number . |
|
||||||
|
fzf --delimiter : --nth 3.. --bind 'enter:become(vim {1} +{2})'
|
||||||
|
```
|
||||||
|
|
||||||
### Reloading the candidate list
|
### Reloading the candidate list
|
||||||
|
|
||||||
By binding `reload` action to a key or an event, you can make fzf dynamically
|
By binding `reload` action to a key or an event, you can make fzf dynamically
|
||||||
@@ -557,8 +630,8 @@ more details.
|
|||||||
#### 1. Update the list of processes by pressing CTRL-R
|
#### 1. Update the list of processes by pressing CTRL-R
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
FZF_DEFAULT_COMMAND='ps -ef' \
|
ps -ef |
|
||||||
fzf --bind 'ctrl-r:reload($FZF_DEFAULT_COMMAND)' \
|
fzf --bind 'ctrl-r:reload(ps -ef)' \
|
||||||
--header 'Press CTRL-R to reload' --header-lines=1 \
|
--header 'Press CTRL-R to reload' --header-lines=1 \
|
||||||
--height=50% --layout=reverse
|
--height=50% --layout=reverse
|
||||||
```
|
```
|
||||||
@@ -567,7 +640,7 @@ FZF_DEFAULT_COMMAND='ps -ef' \
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
FZF_DEFAULT_COMMAND='find . -type f' \
|
FZF_DEFAULT_COMMAND='find . -type f' \
|
||||||
fzf --bind 'ctrl-d:reload(find . -type d),ctrl-f:reload($FZF_DEFAULT_COMMAND)' \
|
fzf --bind 'ctrl-d:reload(find . -type d),ctrl-f:reload(eval "$FZF_DEFAULT_COMMAND")' \
|
||||||
--height=50% --layout=reverse
|
--height=50% --layout=reverse
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -580,18 +653,21 @@ expression `{q}`. Also, note that we used `--disabled` option so that fzf
|
|||||||
doesn't perform any secondary filtering.
|
doesn't perform any secondary filtering.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
INITIAL_QUERY=""
|
: | rg_prefix='rg --column --line-number --no-heading --color=always --smart-case' \
|
||||||
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
fzf --bind 'start:reload:$rg_prefix ""' \
|
||||||
FZF_DEFAULT_COMMAND="$RG_PREFIX '$INITIAL_QUERY'" \
|
--bind 'change:reload:$rg_prefix {q} || true' \
|
||||||
fzf --bind "change:reload:$RG_PREFIX {q} || true" \
|
--bind 'enter:become(vim {1} +{2})' \
|
||||||
--ansi --disabled --query "$INITIAL_QUERY" \
|
--ansi --disabled \
|
||||||
--height=50% --layout=reverse
|
--height=50% --layout=reverse
|
||||||
```
|
```
|
||||||
|
|
||||||
If ripgrep doesn't find any matches, it will exit with a non-zero exit status,
|
If ripgrep doesn't find any matches, it will exit with a non-zero exit status,
|
||||||
and fzf will warn you about it. To suppress the warning message, we added
|
and fzf will warn you about it. To suppress the warning message, we added
|
||||||
`|| true` to the command, so that it always exits with 0.
|
`|| true` to the command, so that it always exits with 0.
|
||||||
|
|
||||||
|
See ["Using fzf as interactive Ripgrep launcher"](https://github.com/junegunn/fzf/blob/master/ADVANCED.md#using-fzf-as-interactive-ripgrep-launcher)
|
||||||
|
for more sophisticated examples.
|
||||||
|
|
||||||
### Preview window
|
### Preview window
|
||||||
|
|
||||||
When the `--preview` option is set, fzf automatically starts an external process
|
When the `--preview` option is set, fzf automatically starts an external process
|
||||||
@@ -610,7 +686,7 @@ syntax-highlights the content of a file, such as
|
|||||||
[Highlight](http://www.andre-simon.de/doku/highlight/en/highlight.php):
|
[Highlight](http://www.andre-simon.de/doku/highlight/en/highlight.php):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fzf --preview 'bat --style=numbers --color=always --line-range :500 {}'
|
fzf --preview 'bat --color=always {}' --preview-window '~3'
|
||||||
```
|
```
|
||||||
|
|
||||||
You can customize the size, position, and border of the preview window using
|
You can customize the size, position, and border of the preview window using
|
||||||
@@ -620,15 +696,13 @@ You can customize the size, position, and border of the preview window using
|
|||||||
```bash
|
```bash
|
||||||
fzf --height 40% --layout reverse --info inline --border \
|
fzf --height 40% --layout reverse --info inline --border \
|
||||||
--preview 'file {}' --preview-window up,1,border-horizontal \
|
--preview 'file {}' --preview-window up,1,border-horizontal \
|
||||||
|
--bind 'ctrl-/:change-preview-window(50%|hidden|)' \
|
||||||
--color 'fg:#bbccdd,fg+:#ddeeff,bg:#334455,preview-bg:#223344,border:#778899'
|
--color 'fg:#bbccdd,fg+:#ddeeff,bg:#334455,preview-bg:#223344,border:#778899'
|
||||||
```
|
```
|
||||||
|
|
||||||
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/
|
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
@@ -650,7 +724,7 @@ history | fzf
|
|||||||
Tips
|
Tips
|
||||||
----
|
----
|
||||||
|
|
||||||
#### Respecting `.gitignore`
|
### Respecting `.gitignore`
|
||||||
|
|
||||||
You can use [fd](https://github.com/sharkdp/fd),
|
You can use [fd](https://github.com/sharkdp/fd),
|
||||||
[ripgrep](https://github.com/BurntSushi/ripgrep), or [the silver
|
[ripgrep](https://github.com/BurntSushi/ripgrep), or [the silver
|
||||||
@@ -660,10 +734,10 @@ default find command to traverse the file system while respecting
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Feed the output of fd into fzf
|
# Feed the output of fd into fzf
|
||||||
fd --type f | fzf
|
fd --type f --strip-cwd-prefix | fzf
|
||||||
|
|
||||||
# Setting fd as the default source for fzf
|
# Setting fd as the default source for fzf
|
||||||
export FZF_DEFAULT_COMMAND='fd --type f'
|
export FZF_DEFAULT_COMMAND='fd --type f --strip-cwd-prefix'
|
||||||
|
|
||||||
# Now fzf (w/o pipe) will use fd instead of find
|
# Now fzf (w/o pipe) will use fd instead of find
|
||||||
fzf
|
fzf
|
||||||
@@ -676,10 +750,10 @@ If you want the command to follow symbolic links and don't want it to exclude
|
|||||||
hidden files, use the following command:
|
hidden files, use the following command:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git'
|
export FZF_DEFAULT_COMMAND='fd --type f --strip-cwd-prefix --hidden --follow --exclude .git'
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Fish shell
|
### Fish shell
|
||||||
|
|
||||||
`CTRL-T` key binding of fish, unlike those of bash and zsh, will use the last
|
`CTRL-T` key binding of fish, unlike those of bash and zsh, will use the last
|
||||||
token on the command-line as the root directory for the recursive search. For
|
token on the command-line as the root directory for the recursive search. For
|
||||||
@@ -709,4 +783,4 @@ https://github.com/junegunn/fzf/wiki/Related-projects
|
|||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2021 Junegunn Choi
|
Copyright (c) 2013-2023 Junegunn Choi
|
||||||
|
35
bin/fzf-tmux
35
bin/fzf-tmux
@@ -10,7 +10,6 @@ fail() {
|
|||||||
fzf="$(command -v fzf 2> /dev/null)" || fzf="$(dirname "$0")/fzf"
|
fzf="$(command -v fzf 2> /dev/null)" || fzf="$(dirname "$0")/fzf"
|
||||||
[[ -x "$fzf" ]] || fail 'fzf executable not found'
|
[[ -x "$fzf" ]] || fail 'fzf executable not found'
|
||||||
|
|
||||||
tmux_args=()
|
|
||||||
args=()
|
args=()
|
||||||
opt=""
|
opt=""
|
||||||
skip=""
|
skip=""
|
||||||
@@ -58,7 +57,7 @@ while [[ $# -gt 0 ]]; do
|
|||||||
;;
|
;;
|
||||||
-p*|-w*|-h*|-x*|-y*|-d*|-u*|-r*|-l*)
|
-p*|-w*|-h*|-x*|-y*|-d*|-u*|-r*|-l*)
|
||||||
if [[ "$arg" =~ ^-[pwhxy] ]]; then
|
if [[ "$arg" =~ ^-[pwhxy] ]]; then
|
||||||
[[ "$opt" =~ "-K -E" ]] || opt="-K -E"
|
[[ "$opt" =~ "-E" ]] || opt="-E"
|
||||||
elif [[ "$arg" =~ ^.[lr] ]]; then
|
elif [[ "$arg" =~ ^.[lr] ]]; then
|
||||||
opt="-h"
|
opt="-h"
|
||||||
if [[ "$arg" =~ ^.l ]]; then
|
if [[ "$arg" =~ ^.l ]]; then
|
||||||
@@ -119,8 +118,6 @@ while [[ $# -gt 0 ]]; do
|
|||||||
# "--" can be used to separate fzf-tmux options from fzf options to
|
# "--" can be used to separate fzf-tmux options from fzf options to
|
||||||
# avoid conflicts
|
# avoid conflicts
|
||||||
skip=1
|
skip=1
|
||||||
tmux_args=("${args[@]}")
|
|
||||||
args=()
|
|
||||||
continue
|
continue
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
@@ -139,7 +136,7 @@ fi
|
|||||||
args=("${args[@]}" "--no-height" "--bind=ctrl-z:ignore")
|
args=("${args[@]}" "--no-height" "--bind=ctrl-z:ignore")
|
||||||
|
|
||||||
# Handle zoomed tmux pane without popup options by moving it to a temp window
|
# Handle zoomed tmux pane without popup options by moving it to a temp window
|
||||||
if [[ ! "$opt" =~ "-K -E" ]] && tmux list-panes -F '#F' | grep -q Z; then
|
if [[ ! "$opt" =~ "-E" ]] && tmux list-panes -F '#F' | grep -q Z; then
|
||||||
zoomed_without_popup=1
|
zoomed_without_popup=1
|
||||||
original_window=$(tmux display-message -p "#{window_id}")
|
original_window=$(tmux display-message -p "#{window_id}")
|
||||||
tmp_window=$(tmux new-window -d -P -F "#{window_id}" "bash -c 'while :; do for c in \\| / - '\\;' do sleep 0.2; printf \"\\r\$c fzf-tmux is running\\r\"; done; done'")
|
tmp_window=$(tmux new-window -d -P -F "#{window_id}" "bash -c 'while :; do for c in \\| / - '\\;' do sleep 0.2; printf \"\\r\$c fzf-tmux is running\\r\"; done; done'")
|
||||||
@@ -181,9 +178,21 @@ trap 'cleanup 1' SIGUSR1
|
|||||||
trap 'cleanup' EXIT
|
trap 'cleanup' EXIT
|
||||||
|
|
||||||
envs="export TERM=$TERM "
|
envs="export TERM=$TERM "
|
||||||
[[ "$opt" =~ "-K -E" ]] && FZF_DEFAULT_OPTS="--margin 0,1 $FZF_DEFAULT_OPTS"
|
if [[ "$opt" =~ "-E" ]]; then
|
||||||
|
tmux_version=$(tmux -V | sed 's/[^0-9.]//g')
|
||||||
|
if [[ $(awk '{print ($1 > 3.2)}' <<< "$tmux_version" 2> /dev/null || bc -l <<< "$tmux_version > 3.2") = 1 ]]; then
|
||||||
|
FZF_DEFAULT_OPTS="--border $FZF_DEFAULT_OPTS"
|
||||||
|
opt="-B $opt"
|
||||||
|
elif [[ $tmux_version = 3.2 ]]; then
|
||||||
|
FZF_DEFAULT_OPTS="--margin 0,1 $FZF_DEFAULT_OPTS"
|
||||||
|
else
|
||||||
|
echo "fzf-tmux: tmux 3.2 or above is required for popup mode" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
[[ -n "$FZF_DEFAULT_OPTS" ]] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
|
[[ -n "$FZF_DEFAULT_OPTS" ]] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
|
||||||
[[ -n "$FZF_DEFAULT_COMMAND" ]] && envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
|
[[ -n "$FZF_DEFAULT_COMMAND" ]] && envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
|
||||||
|
[[ -n "$BAT_THEME" ]] && envs="$envs BAT_THEME=$(printf %q "$BAT_THEME")"
|
||||||
echo "$envs;" > "$argsf"
|
echo "$envs;" > "$argsf"
|
||||||
|
|
||||||
# Build arguments to fzf
|
# Build arguments to fzf
|
||||||
@@ -195,7 +204,7 @@ close="; trap - EXIT SIGINT SIGTERM $close"
|
|||||||
|
|
||||||
export TMUX=$(cut -d , -f 1,2 <<< "$TMUX")
|
export TMUX=$(cut -d , -f 1,2 <<< "$TMUX")
|
||||||
mkfifo -m o+w $fifo2
|
mkfifo -m o+w $fifo2
|
||||||
if [[ "$opt" =~ "-K -E" ]]; then
|
if [[ "$opt" =~ "-E" ]]; then
|
||||||
cat $fifo2 &
|
cat $fifo2 &
|
||||||
if [[ -n "$term" ]] || [[ -t 0 ]]; then
|
if [[ -n "$term" ]] || [[ -t 0 ]]; then
|
||||||
cat <<< "\"$fzf\" $opts > $fifo2; out=\$? $close; exit \$out" >> $argsf
|
cat <<< "\"$fzf\" $opts > $fifo2; out=\$? $close; exit \$out" >> $argsf
|
||||||
@@ -205,15 +214,7 @@ if [[ "$opt" =~ "-K -E" ]]; then
|
|||||||
cat <&0 > $fifo1 &
|
cat <&0 > $fifo1 &
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# tmux dropped the support for `-K`, `-R` to popup command
|
tmux popup -d "$PWD" $opt "bash $argsf" > /dev/null 2>&1
|
||||||
# TODO: We can remove this once tmux 3.2 is released
|
|
||||||
if [[ ! "$(tmux popup --help 2>&1)" =~ '-R shell-command' ]]; then
|
|
||||||
opt="${opt/-K/}"
|
|
||||||
else
|
|
||||||
opt="${opt} -R"
|
|
||||||
fi
|
|
||||||
|
|
||||||
tmux popup -d "$PWD" "${tmux_args[@]}" $opt "bash $argsf" > /dev/null 2>&1
|
|
||||||
exit $?
|
exit $?
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -227,7 +228,7 @@ else
|
|||||||
fi
|
fi
|
||||||
tmux set-window-option synchronize-panes off \;\
|
tmux set-window-option synchronize-panes off \;\
|
||||||
set-window-option remain-on-exit off \;\
|
set-window-option remain-on-exit off \;\
|
||||||
split-window -c "$PWD" $opt "${tmux_args[@]}" "bash -c 'exec -a fzf bash $argsf'" $swap \
|
split-window -c "$PWD" $opt "bash -c 'exec -a fzf bash $argsf'" $swap \
|
||||||
> /dev/null 2>&1 || { "$fzf" "${args[@]}"; exit $?; }
|
> /dev/null 2>&1 || { "$fzf" "${args[@]}"; exit $?; }
|
||||||
cat $fifo2
|
cat $fifo2
|
||||||
exit "$(cat $fifo3)"
|
exit "$(cat $fifo3)"
|
||||||
|
12
doc/fzf.txt
12
doc/fzf.txt
@@ -1,4 +1,4 @@
|
|||||||
fzf.txt fzf Last change: May 19 2021
|
fzf.txt fzf Last change: Mar 20 2023
|
||||||
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
|
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
|
||||||
==============================================================================
|
==============================================================================
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ depending on the package manager.
|
|||||||
" If installed using Homebrew
|
" If installed using Homebrew
|
||||||
set rtp+=/usr/local/opt/fzf
|
set rtp+=/usr/local/opt/fzf
|
||||||
|
|
||||||
" If installed using git
|
" If you have cloned fzf on ~/.fzf directory
|
||||||
set rtp+=~/.fzf
|
set rtp+=~/.fzf
|
||||||
<
|
<
|
||||||
If you use {vim-plug}{1}, the same can be written as:
|
If you use {vim-plug}{1}, the same can be written as:
|
||||||
@@ -40,7 +40,7 @@ If you use {vim-plug}{1}, the same can be written as:
|
|||||||
" If installed using Homebrew
|
" If installed using Homebrew
|
||||||
Plug '/usr/local/opt/fzf'
|
Plug '/usr/local/opt/fzf'
|
||||||
|
|
||||||
" If installed using git
|
" If you have cloned fzf on ~/.fzf directory
|
||||||
Plug '~/.fzf'
|
Plug '~/.fzf'
|
||||||
<
|
<
|
||||||
But if you want the latest Vim plugin file from GitHub rather than the one
|
But if you want the latest Vim plugin file from GitHub rather than the one
|
||||||
@@ -143,7 +143,7 @@ Examples~
|
|||||||
|
|
||||||
" An action can be a reference to a function that processes selected lines
|
" An action can be a reference to a function that processes selected lines
|
||||||
function! s:build_quickfix_list(lines)
|
function! s:build_quickfix_list(lines)
|
||||||
call setqflist(map(copy(a:lines), '{ "filename": v:val }'))
|
call setqflist(map(copy(a:lines), '{ "filename": v:val, "lnum": 1 }'))
|
||||||
copen
|
copen
|
||||||
cc
|
cc
|
||||||
endfunction
|
endfunction
|
||||||
@@ -325,7 +325,7 @@ following options are allowed:
|
|||||||
- `yoffset` [float default 0.5 range [0 ~ 1]]
|
- `yoffset` [float default 0.5 range [0 ~ 1]]
|
||||||
- `xoffset` [float default 0.5 range [0 ~ 1]]
|
- `xoffset` [float default 0.5 range [0 ~ 1]]
|
||||||
- `relative` [boolean default v:false]
|
- `relative` [boolean default v:false]
|
||||||
- `border` [string default `rounded`]: Border style
|
- `border` [string default `rounded` (`sharp` on Windows)]: Border style
|
||||||
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right` / `no[ne]`
|
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right` / `no[ne]`
|
||||||
|
|
||||||
|
|
||||||
@@ -506,7 +506,7 @@ LICENSE *fzf-license*
|
|||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2021 Junegunn Choi
|
Copyright (c) 2013-2023 Junegunn Choi
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap:
|
vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap:
|
||||||
|
26
go.mod
26
go.mod
@@ -1,17 +1,21 @@
|
|||||||
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.4
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/mattn/go-isatty v0.0.17
|
||||||
github.com/mattn/go-isatty v0.0.14
|
github.com/mattn/go-runewidth v0.0.14
|
||||||
github.com/mattn/go-runewidth v0.0.13
|
|
||||||
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.4
|
||||||
github.com/saracen/walker v0.1.2
|
github.com/saracen/walker v0.1.3
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
golang.org/x/sys v0.8.0
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
golang.org/x/term v0.8.0
|
||||||
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6
|
|
||||||
golang.org/x/text v0.3.6 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.13
|
require (
|
||||||
|
github.com/gdamore/encoding v1.0.0 // indirect
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
|
||||||
|
golang.org/x/text v0.5.0 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
go 1.17
|
||||||
|
60
go.sum
60
go.sum
@@ -1,31 +1,49 @@
|
|||||||
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.4 h1:TGU4tSjD3sCL788vFNeJnTdzpNKIw1H5dgLnJRQVv/k=
|
||||||
github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0=
|
github.com/gdamore/tcell/v2 v2.5.4/go.mod h1:dZgRy5v4iMobMEcWNYBtREnDZAT9DYmfqIkrgEMxLyw=
|
||||||
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.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.17/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.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-runewidth v0.0.13/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.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||||
github.com/saracen/walker v0.1.2/go.mod h1:0oKYMsKVhSJ+ful4p/XbjvXbMgLEkLITZaxozsl4CGE=
|
github.com/rivo/uniseg v0.4.4/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=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 h1:EC6+IGYTjPpRfv9a2b/6Puw0W+hLtAhkV1tPsXhutqs=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
|
||||||
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
|
||||||
|
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
42
install
42
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.29.0
|
version=0.42.0
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
@@ -168,21 +168,24 @@ archi=$(uname -sm)
|
|||||||
binary_available=1
|
binary_available=1
|
||||||
binary_error=""
|
binary_error=""
|
||||||
case "$archi" in
|
case "$archi" in
|
||||||
Darwin\ arm64) download fzf-$version-darwin_arm64.zip ;;
|
Darwin\ arm64) download fzf-$version-darwin_arm64.zip ;;
|
||||||
Darwin\ x86_64) download fzf-$version-darwin_amd64.zip ;;
|
Darwin\ x86_64) download fzf-$version-darwin_amd64.zip ;;
|
||||||
Linux\ armv5*) download fzf-$version-linux_armv5.tar.gz ;;
|
Linux\ armv5*) download fzf-$version-linux_armv5.tar.gz ;;
|
||||||
Linux\ armv6*) download fzf-$version-linux_armv6.tar.gz ;;
|
Linux\ armv6*) download fzf-$version-linux_armv6.tar.gz ;;
|
||||||
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\ *64) download fzf-$version-linux_amd64.tar.gz ;;
|
Linux\ loongarch64) download fzf-$version-linux_loong64.tar.gz ;;
|
||||||
FreeBSD\ *64) download fzf-$version-freebsd_amd64.tar.gz ;;
|
Linux\ ppc64le) download fzf-$version-linux_ppc64le.tar.gz ;;
|
||||||
OpenBSD\ *64) download fzf-$version-openbsd_amd64.tar.gz ;;
|
Linux\ *64) download fzf-$version-linux_amd64.tar.gz ;;
|
||||||
CYGWIN*\ *64) download fzf-$version-windows_amd64.zip ;;
|
Linux\ s390x) download fzf-$version-linux_s390x.tar.gz ;;
|
||||||
MINGW*\ *64) download fzf-$version-windows_amd64.zip ;;
|
FreeBSD\ *64) download fzf-$version-freebsd_amd64.tar.gz ;;
|
||||||
MSYS*\ *64) download fzf-$version-windows_amd64.zip ;;
|
OpenBSD\ *64) download fzf-$version-openbsd_amd64.tar.gz ;;
|
||||||
Windows*\ *64) download fzf-$version-windows_amd64.zip ;;
|
CYGWIN*\ *64) download fzf-$version-windows_amd64.zip ;;
|
||||||
*) binary_available=0 binary_error=1 ;;
|
MINGW*\ *64) download fzf-$version-windows_amd64.zip ;;
|
||||||
|
MSYS*\ *64) download fzf-$version-windows_amd64.zip ;;
|
||||||
|
Windows*\ *64) download fzf-$version-windows_amd64.zip ;;
|
||||||
|
*) binary_available=0 binary_error=1 ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
cd "$fzf_base"
|
cd "$fzf_base"
|
||||||
@@ -256,7 +259,7 @@ for shell in $shells; do
|
|||||||
# Setup fzf
|
# Setup fzf
|
||||||
# ---------
|
# ---------
|
||||||
if [[ ! "\$PATH" == *$fzf_base_esc/bin* ]]; then
|
if [[ ! "\$PATH" == *$fzf_base_esc/bin* ]]; then
|
||||||
export PATH="\${PATH:+\${PATH}:}$fzf_base/bin"
|
PATH="\${PATH:+\${PATH}:}$fzf_base/bin"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Auto-completion
|
# Auto-completion
|
||||||
@@ -280,11 +283,6 @@ EOF
|
|||||||
[ $? -eq 0 ] && echo "OK" || echo "Failed"
|
[ $? -eq 0 ] && echo "OK" || echo "Failed"
|
||||||
|
|
||||||
mkdir -p "${fish_dir}/functions"
|
mkdir -p "${fish_dir}/functions"
|
||||||
if [ -e "${fish_dir}/functions/fzf.fish" ]; then
|
|
||||||
echo -n "Remove unnecessary ${fish_dir}/functions/fzf.fish ... "
|
|
||||||
rm -f "${fish_dir}/functions/fzf.fish" && echo "OK" || echo "Failed"
|
|
||||||
fi
|
|
||||||
|
|
||||||
fish_binding="${fish_dir}/functions/fzf_key_bindings.fish"
|
fish_binding="${fish_dir}/functions/fzf_key_bindings.fish"
|
||||||
if [ $key_bindings -ne 0 ]; then
|
if [ $key_bindings -ne 0 ]; then
|
||||||
echo -n "Symlink $fish_binding ... "
|
echo -n "Symlink $fish_binding ... "
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
$version="0.29.0"
|
$version="0.42.0"
|
||||||
|
|
||||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
|
||||||
|
2
main.go
2
main.go
@@ -5,7 +5,7 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/protector"
|
"github.com/junegunn/fzf/src/protector"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "0.29"
|
var version string = "0.42"
|
||||||
var revision string = "devel"
|
var revision string = "devel"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
.ig
|
.ig
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2021 Junegunn Choi
|
Copyright (c) 2013-2023 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
|
||||||
@@ -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 "Dec 2021" "fzf 0.29.0" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf-tmux 1 "Jun 2023" "fzf 0.42.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
|
||||||
|
490
man/man1/fzf.1
490
man/man1/fzf.1
@@ -1,7 +1,7 @@
|
|||||||
.ig
|
.ig
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2021 Junegunn Choi
|
Copyright (c) 2013-2023 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
|
||||||
@@ -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 "Dec 2021" "fzf 0.29.0" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Jun 2023" "fzf 0.42.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)
|
||||||
|
|
||||||
@@ -80,6 +92,19 @@ interface rather than a "fuzzy finder". You can later enable the search using
|
|||||||
.B "+s, --no-sort"
|
.B "+s, --no-sort"
|
||||||
Do not sort the result
|
Do not sort the result
|
||||||
.TP
|
.TP
|
||||||
|
.B "--track"
|
||||||
|
Make fzf track the current selection when the result list is updated.
|
||||||
|
This can be useful when browsing logs using fzf with sorting disabled. It is
|
||||||
|
not recommended to use this option with \fB--tac\fR as the resulting behavior
|
||||||
|
can be confusing. Also, consider using \fBtrack\fR action instead of this
|
||||||
|
option.
|
||||||
|
|
||||||
|
.RS
|
||||||
|
e.g.
|
||||||
|
\fBgit log --oneline --graph --color=always | nl |
|
||||||
|
fzf --ansi --track --no-sort --layout=reverse-list\fR
|
||||||
|
.RE
|
||||||
|
.TP
|
||||||
.B "--tac"
|
.B "--tac"
|
||||||
Reverse the order of the input
|
Reverse the order of the input
|
||||||
|
|
||||||
@@ -95,6 +120,8 @@ Comma-separated list of sort criteria to apply when the scores are tied.
|
|||||||
.br
|
.br
|
||||||
.BR length " Prefers line with shorter length"
|
.BR length " Prefers line with shorter length"
|
||||||
.br
|
.br
|
||||||
|
.BR chunk " Prefers line with shorter matched chunk (delimited by whitespaces)"
|
||||||
|
.br
|
||||||
.BR begin " Prefers line with matched substring closer to the beginning"
|
.BR begin " Prefers line with matched substring closer to the beginning"
|
||||||
.br
|
.br
|
||||||
.BR end " Prefers line with matched substring closer to the end"
|
.BR end " Prefers line with matched substring closer to the end"
|
||||||
@@ -163,9 +190,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).
|
||||||
@@ -187,21 +216,29 @@ 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
|
||||||
.BR rounded " Border with rounded corners (default)"
|
.BR rounded " Border with rounded corners (default)"
|
||||||
.br
|
.br
|
||||||
.BR sharp " Border with sharp corners"
|
.BR sharp " Border with sharp corners"
|
||||||
.br
|
.br
|
||||||
.BR horizontal " Horizontal lines above and below the finder"
|
.BR bold " Border with bold lines"
|
||||||
.br
|
.br
|
||||||
.BR vertical " Vertical lines on each side of the finder"
|
.BR double " Border with double lines"
|
||||||
.br
|
.br
|
||||||
.BR top
|
.BR block " Border using block elements; suitable when using different background colors"
|
||||||
.br
|
.br
|
||||||
.BR bottom
|
.BR thinblock " Border using legacy computing symbols; may not be displayed on some terminals"
|
||||||
|
.br
|
||||||
|
.BR horizontal " Horizontal lines above and below the finder"
|
||||||
|
.br
|
||||||
|
.BR vertical " Vertical lines on each side of the finder"
|
||||||
|
.br
|
||||||
|
.BR top " (up)"
|
||||||
|
.br
|
||||||
|
.BR bottom " (down)"
|
||||||
.br
|
.br
|
||||||
.BR left
|
.BR left
|
||||||
.br
|
.br
|
||||||
@@ -210,9 +247,59 @@ Draw border around the finder
|
|||||||
.BR none
|
.BR none
|
||||||
.br
|
.br
|
||||||
|
|
||||||
|
If you use a terminal emulator where each box-drawing character takes
|
||||||
|
2 columns, try setting \fBRUNEWIDTH_EASTASIAN\fR environment variable to
|
||||||
|
\fB0\fR or \fB1\fR. If the border is still not properly rendered, set
|
||||||
|
\fB--no-unicode\fR.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "--border-label" [=LABEL]
|
||||||
|
Label to print on the horizontal border line. Should be used with one of the
|
||||||
|
following \fB--border\fR options.
|
||||||
|
|
||||||
|
.br
|
||||||
|
.B * rounded
|
||||||
|
.br
|
||||||
|
.B * sharp
|
||||||
|
.br
|
||||||
|
.B * bold
|
||||||
|
.br
|
||||||
|
.B * double
|
||||||
|
.br
|
||||||
|
.B * horizontal
|
||||||
|
.br
|
||||||
|
.BR "* top" " (up)"
|
||||||
|
.br
|
||||||
|
.BR "* bottom" " (down)"
|
||||||
|
.br
|
||||||
|
|
||||||
|
.br
|
||||||
|
e.g.
|
||||||
|
\fB# ANSI color codes are supported
|
||||||
|
# (with https://github.com/busyloop/lolcat)
|
||||||
|
label=$(curl -s http://metaphorpsum.com/sentences/1 | lolcat -f)
|
||||||
|
|
||||||
|
# Border label at the center
|
||||||
|
fzf --height=10 --border --border-label="╢ $label ╟" --color=label:italic:black
|
||||||
|
|
||||||
|
# Left-aligned (positive integer)
|
||||||
|
fzf --height=10 --border --border-label="╢ $label ╟" --border-label-pos=3 --color=label:italic:black
|
||||||
|
|
||||||
|
# Right-aligned (negative integer) on the bottom line (:bottom)
|
||||||
|
fzf --height=10 --border --border-label="╢ $label ╟" --border-label-pos=-3:bottom --color=label:italic:black\fR
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "--border-label-pos" [=N[:top|bottom]]
|
||||||
|
Position of the border label on the border line. Specify a positive integer as
|
||||||
|
the column position from the left. Specify a negative integer to right-align
|
||||||
|
the label. Label is printed on the top border line by default, add
|
||||||
|
\fB:bottom\fR to put it on the border line on the bottom. The default value
|
||||||
|
\fB0 (or \fBcenter\fR) will put the label at the center of the border line.
|
||||||
|
|
||||||
.TP
|
.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 drawing characters to draw borders,
|
||||||
|
the spinner and the horizontal separator.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "--margin=" MARGIN
|
.BI "--margin=" MARGIN
|
||||||
@@ -265,20 +352,46 @@ 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"
|
||||||
.br
|
.br
|
||||||
.BR inline " Display on the same line"
|
.BR inline " Display on the same line with the default separator ' < '"
|
||||||
.br
|
.br
|
||||||
.BR hidden " Do not display finder info"
|
.BR inline:SEPARATOR " Display on the same line with a non-default separator"
|
||||||
|
.br
|
||||||
|
.BR inline-right " Display on the right end of the same line
|
||||||
|
.br
|
||||||
|
.BR hidden " Do not display finder info"
|
||||||
.br
|
.br
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.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
|
||||||
|
.BI "--scrollbar=" "CHAR1[CHAR2]"
|
||||||
|
Use the given character to render scrollbar. (default: '│' or ':' depending on
|
||||||
|
\fB--no-unicode\fR). The optional \fBCHAR2\fR is used to render scrollbar of
|
||||||
|
the preview window.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B "--no-scrollbar"
|
||||||
|
Do not display scrollbar. A synonym for \fB--scrollbar=''\fB
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "--prompt=" "STR"
|
.BI "--prompt=" "STR"
|
||||||
Input prompt (default: '> ')
|
Input prompt (default: '> ')
|
||||||
@@ -302,6 +415,9 @@ lines that follow.
|
|||||||
.TP
|
.TP
|
||||||
.B "--header-first"
|
.B "--header-first"
|
||||||
Print header before the prompt line
|
Print header before the prompt line
|
||||||
|
.TP
|
||||||
|
.BI "--ellipsis=" "STR"
|
||||||
|
Ellipsis to show when line is truncated (default: '..')
|
||||||
.SS Display
|
.SS Display
|
||||||
.TP
|
.TP
|
||||||
.B "--ansi"
|
.B "--ansi"
|
||||||
@@ -324,24 +440,30 @@ color mappings.
|
|||||||
\fBbw \fRNo colors (equivalent to \fB--no-color\fR)
|
\fBbw \fRNo colors (equivalent to \fB--no-color\fR)
|
||||||
|
|
||||||
.B COLOR NAMES:
|
.B COLOR NAMES:
|
||||||
\fBfg \fRText
|
\fBfg \fRText
|
||||||
\fBbg \fRBackground
|
\fBpreview-fg \fRPreview window text
|
||||||
\fBpreview-fg \fRPreview window text
|
\fBbg \fRBackground
|
||||||
\fBpreview-bg \fRPreview window background
|
\fBpreview-bg \fRPreview window background
|
||||||
\fBhl \fRHighlighted substrings
|
\fBhl \fRHighlighted substrings
|
||||||
\fBfg+ \fRText (current line)
|
\fBfg+ \fRText (current line)
|
||||||
\fBbg+ \fRBackground (current line)
|
\fBbg+ \fRBackground (current line)
|
||||||
\fBgutter \fRGutter on the left (defaults to \fBbg+\fR)
|
\fBgutter \fRGutter on the left
|
||||||
\fBhl+ \fRHighlighted substrings (current line)
|
\fBhl+ \fRHighlighted substrings (current line)
|
||||||
\fBquery \fRQuery string
|
\fBquery \fRQuery string
|
||||||
\fBdisabled \fRQuery string when search is disabled
|
\fBdisabled \fRQuery string when search is disabled (\fB--disabled\fR)
|
||||||
\fBinfo \fRInfo line (match counters)
|
\fBinfo \fRInfo 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)
|
||||||
\fBprompt \fRPrompt
|
\fBscrollbar \fRScrollbar
|
||||||
\fBpointer \fRPointer to the current line
|
\fBpreview-border \fRBorder around the preview window (\fB--preview\fR)
|
||||||
\fBmarker \fRMulti-select marker
|
\fBpreview-scrollbar \fRScrollbar
|
||||||
\fBspinner \fRStreaming input indicator
|
\fBseparator \fRHorizontal separator on info line
|
||||||
\fBheader \fRHeader
|
\fBlabel \fRBorder label (\fB--border-label\fR and \fB--preview-label\fR)
|
||||||
|
\fBpreview-label \fRBorder label of the preview window (\fB--preview-label\fR)
|
||||||
|
\fBprompt \fRPrompt
|
||||||
|
\fBpointer \fRPointer to the current line
|
||||||
|
\fBmarker \fRMulti-select marker
|
||||||
|
\fBspinner \fRStreaming input indicator
|
||||||
|
\fBheader \fRHeader
|
||||||
|
|
||||||
.B ANSI COLORS:
|
.B ANSI COLORS:
|
||||||
\fB-1 \fRDefault terminal foreground/background color
|
\fB-1 \fRDefault terminal foreground/background color
|
||||||
@@ -373,6 +495,7 @@ color mappings.
|
|||||||
\fBreverse\fR
|
\fBreverse\fR
|
||||||
\fBdim\fR
|
\fBdim\fR
|
||||||
\fBitalic\fR
|
\fBitalic\fR
|
||||||
|
\fBstrikethrough\fR
|
||||||
|
|
||||||
.B EXAMPLES:
|
.B EXAMPLES:
|
||||||
|
|
||||||
@@ -398,7 +521,7 @@ Use black background
|
|||||||
.BI "--history=" "HISTORY_FILE"
|
.BI "--history=" "HISTORY_FILE"
|
||||||
Load search history from the specified file and update the file on completion.
|
Load search history from the specified file and update the file on completion.
|
||||||
When enabled, \fBCTRL-N\fR and \fBCTRL-P\fR are automatically remapped to
|
When enabled, \fBCTRL-N\fR and \fBCTRL-P\fR are automatically remapped to
|
||||||
\fBnext-history\fR and \fBprevious-history\fR.
|
\fBnext-history\fR and \fBprev-history\fR.
|
||||||
.TP
|
.TP
|
||||||
.BI "--history-size=" "N"
|
.BI "--history-size=" "N"
|
||||||
Maximum number of entries in the history file (default: 1000). The file is
|
Maximum number of entries in the history file (default: 1000). The file is
|
||||||
@@ -447,12 +570,13 @@ e.g.
|
|||||||
\fB# Press CTRL-A to select 100K items and see the sum of all the numbers.
|
\fB# Press CTRL-A to select 100K items and see the sum of all the numbers.
|
||||||
# This won't work properly without 'f' flag due to ARG_MAX limit.
|
# This won't work properly without 'f' flag due to ARG_MAX limit.
|
||||||
seq 100000 | fzf --multi --bind ctrl-a:select-all \\
|
seq 100000 | fzf --multi --bind ctrl-a:select-all \\
|
||||||
--preview "awk '{sum+=\$1} END {print sum}' {+f}"\fR
|
--preview "awk '{sum+=\\$1} END {print sum}' {+f}"\fR
|
||||||
|
|
||||||
Note that you can escape a placeholder pattern by prepending a backslash.
|
Note that you can escape a placeholder pattern by prepending a backslash.
|
||||||
|
|
||||||
Preview window will be updated even when there is no match for the current
|
Preview window will be updated even when there is no match for the current
|
||||||
query if any of the placeholder expressions evaluates to a non-empty string.
|
query if any of the placeholder expressions evaluates to a non-empty string
|
||||||
|
or \fB{q}\fR is in the command template.
|
||||||
|
|
||||||
Since 0.24.0, fzf can render partial preview content before the preview command
|
Since 0.24.0, fzf can render partial preview content before the preview command
|
||||||
completes. ANSI escape sequence for clearing the display (\fBCSI 2 J\fR) is
|
completes. ANSI escape sequence for clearing the display (\fBCSI 2 J\fR) is
|
||||||
@@ -466,8 +590,43 @@ e.g.
|
|||||||
sleep 0.01
|
sleep 0.01
|
||||||
done'\fR
|
done'\fR
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "--preview-window=" "[POSITION][,SIZE[%]][,border-BORDER_OPT][,[no]wrap][,[no]follow][,[no]cycle][,[no]hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default]"
|
.BI "--preview-label" [=LABEL]
|
||||||
|
Label to print on the horizontal border line of the preview window.
|
||||||
|
Should be used with one of the following \fB--preview-window\fR options.
|
||||||
|
|
||||||
|
.br
|
||||||
|
.B * border-rounded (default on non-Windows platforms)
|
||||||
|
.br
|
||||||
|
.B * border-sharp (default on Windows)
|
||||||
|
.br
|
||||||
|
.B * border-bold
|
||||||
|
.br
|
||||||
|
.B * border-double
|
||||||
|
.br
|
||||||
|
.B * border-block
|
||||||
|
.br
|
||||||
|
.B * border-thinblock
|
||||||
|
.br
|
||||||
|
.B * border-horizontal
|
||||||
|
.br
|
||||||
|
.B * border-top
|
||||||
|
.br
|
||||||
|
.B * border-bottom
|
||||||
|
.br
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "--preview-label-pos" [=N[:top|bottom]]
|
||||||
|
Position of the border label on the border line of the preview window. Specify
|
||||||
|
a positive integer as the column position from the left. Specify a negative
|
||||||
|
integer to right-align the label. Label is printed on the top border line by
|
||||||
|
default, add \fB:bottom\fR to put it on the border line on the bottom. The
|
||||||
|
default value 0 (or \fBcenter\fR) will put the label at the center of the
|
||||||
|
border line.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "--preview-window=" "[POSITION][,SIZE[%]][,border-BORDER_OPT][,[no]wrap][,[no]follow][,[no]cycle][,[no]hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]"
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
.B POSITION: (default: right)
|
.B POSITION: (default: right)
|
||||||
@@ -485,9 +644,9 @@ default until \fBtoggle-preview\fR action is triggered.
|
|||||||
execute the command in the background.
|
execute the command in the background.
|
||||||
|
|
||||||
* Long lines are truncated by default. Line wrap can be enabled with
|
* Long lines are truncated by default. Line wrap can be enabled with
|
||||||
\fB:wrap\fR flag.
|
\fBwrap\fR flag.
|
||||||
|
|
||||||
* Preview window will automatically scroll to the bottom when \fB:follow\fR
|
* Preview window will automatically scroll to the bottom when \fBfollow\fR
|
||||||
flag is set, similarly to how \fBtail -f\fR works.
|
flag is set, similarly to how \fBtail -f\fR works.
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
@@ -499,7 +658,7 @@ e.g.
|
|||||||
done'\fR
|
done'\fR
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
* Cyclic scrolling is enabled with \fB:cycle\fR flag.
|
* Cyclic scrolling is enabled with \fBcycle\fR flag.
|
||||||
|
|
||||||
* To change the style of the border of the preview window, specify one of
|
* To change the style of the border of the preview window, specify one of
|
||||||
the options for \fB--border\fR with \fBborder-\fR prefix.
|
the options for \fB--border\fR with \fBborder-\fR prefix.
|
||||||
@@ -549,6 +708,15 @@ e.g.
|
|||||||
fzf --preview 'bat --style=full --color=always {}' --preview-window '~3'\fR
|
fzf --preview 'bat --style=full --color=always {}' --preview-window '~3'\fR
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
|
* You can specify an alternative set of options that are used only when the size
|
||||||
|
of the preview window is below a certain threshold. Note that only one
|
||||||
|
alternative layout is allowed.
|
||||||
|
|
||||||
|
.RS
|
||||||
|
e.g.
|
||||||
|
\fBfzf --preview 'cat {}' --preview-window 'right,border-left,<30(up,30%,border-bottom)'\fR
|
||||||
|
.RE
|
||||||
|
|
||||||
.SS Scripting
|
.SS Scripting
|
||||||
.TP
|
.TP
|
||||||
.BI "-q, --query=" "STR"
|
.BI "-q, --query=" "STR"
|
||||||
@@ -589,12 +757,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.
|
||||||
@@ -603,6 +765,24 @@ ncurses finder only after the input stream is complete.
|
|||||||
e.g. \fBfzf --multi | fzf --sync\fR
|
e.g. \fBfzf --multi | fzf --sync\fR
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
|
.B "--listen[=HTTP_PORT]"
|
||||||
|
Start HTTP server on the given port. It allows external processes to send
|
||||||
|
actions to perform via POST method. If the port number is omitted or given as
|
||||||
|
0, fzf will choose the port automatically and export it as \fBFZF_PORT\fR
|
||||||
|
environment variable to the child processes started via \fBexecute\fR and
|
||||||
|
\fBexecute-silent\fR actions.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fB# Start HTTP server on port 6266
|
||||||
|
fzf --listen 6266
|
||||||
|
|
||||||
|
# Send action to the server
|
||||||
|
curl -XPOST localhost:6266 -d 'reload(seq 100)+change-prompt(hundred> )'
|
||||||
|
|
||||||
|
# Choose port automatically and export it as $FZF_PORT to the child process
|
||||||
|
fzf --listen --bind 'start:execute-silent:echo $FZF_PORT > /tmp/fzf-port'
|
||||||
|
\fR
|
||||||
|
.TP
|
||||||
.B "--version"
|
.B "--version"
|
||||||
Display version information and exit
|
Display version information and exit
|
||||||
|
|
||||||
@@ -703,6 +883,8 @@ e.g.
|
|||||||
.br
|
.br
|
||||||
\fIctrl-space\fR
|
\fIctrl-space\fR
|
||||||
.br
|
.br
|
||||||
|
\fIctrl-delete\fR
|
||||||
|
.br
|
||||||
\fIctrl-\\\fR
|
\fIctrl-\\\fR
|
||||||
.br
|
.br
|
||||||
\fIctrl-]\fR
|
\fIctrl-]\fR
|
||||||
@@ -771,6 +953,8 @@ e.g.
|
|||||||
.br
|
.br
|
||||||
\fIshift-right\fR
|
\fIshift-right\fR
|
||||||
.br
|
.br
|
||||||
|
\fIshift-delete\fR
|
||||||
|
.br
|
||||||
\fIalt-shift-up\fR
|
\fIalt-shift-up\fR
|
||||||
.br
|
.br
|
||||||
\fIalt-shift-down\fR
|
\fIalt-shift-down\fR
|
||||||
@@ -788,6 +972,24 @@ 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
|
||||||
|
\fIload\fR
|
||||||
|
.RS
|
||||||
|
Triggered when the input stream is complete and the initial processing of the
|
||||||
|
list is complete.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fB# Change the prompt to "loaded" when the input stream is complete
|
||||||
|
(seq 10; sleep 1; seq 11 20) | fzf --prompt 'Loading> ' --bind 'load:change-prompt:Loaded> '\fR
|
||||||
|
.RE
|
||||||
\fIchange\fR
|
\fIchange\fR
|
||||||
.RS
|
.RS
|
||||||
Triggered whenever the query string is changed
|
Triggered whenever the query string is changed
|
||||||
@@ -796,6 +998,44 @@ e.g.
|
|||||||
\fB# Move cursor to the first entry whenever the query is changed
|
\fB# Move cursor to the first entry whenever the query is changed
|
||||||
fzf --bind change:first\fR
|
fzf --bind change:first\fR
|
||||||
.RE
|
.RE
|
||||||
|
\fIfocus\fR
|
||||||
|
.RS
|
||||||
|
Triggered when the focus changes due to a vertical cursor movement or a search
|
||||||
|
result update.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fBfzf --bind 'focus:transform-preview-label:echo [ {} ]' --preview 'cat {}'
|
||||||
|
|
||||||
|
# Any action bound to the event runs synchronously and thus can make the interface sluggish
|
||||||
|
# e.g. lolcat isn't one of the fastest programs, and every cursor movement in
|
||||||
|
# fzf will be noticeably affected by its execution time
|
||||||
|
fzf --bind 'focus:transform-preview-label:echo [ {} ] | lolcat -f' --preview 'cat {}'
|
||||||
|
|
||||||
|
# Beware not to introduce an infinite loop
|
||||||
|
seq 10 | fzf --bind 'focus:up' --cycle\fR
|
||||||
|
.RE
|
||||||
|
\fIone\fR
|
||||||
|
.RS
|
||||||
|
Triggered when there's only one match. \fBone:accept\fR binding is comparable
|
||||||
|
to \fB--select-1\fR option, but the difference is that \fB--select-1\fR is only
|
||||||
|
effective before the interactive finder starts but \fBone\fR event is triggered
|
||||||
|
by the interactive finder.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fB# Automatically select the only match
|
||||||
|
seq 10 | fzf --bind one:accept\fR
|
||||||
|
.RE
|
||||||
|
\fIzero\fR
|
||||||
|
.RS
|
||||||
|
Triggered when there's no match. \fBzero:abort\fR binding is comparable to
|
||||||
|
\fB--exit-0\fR option, but the difference is that \fB--exit-0\fR is only
|
||||||
|
effective before the interactive finder starts but \fBzero\fR event is
|
||||||
|
triggered by the interactive finder.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fB# Reload the candidate list when there's no match
|
||||||
|
echo $RANDOM | fzf --bind 'zero:reload(echo $RANDOM)+clear-query' --height 3\fR
|
||||||
|
.RE
|
||||||
|
|
||||||
\fIbackward-eof\fR
|
\fIbackward-eof\fR
|
||||||
.RS
|
.RS
|
||||||
@@ -809,80 +1049,100 @@ e.g.
|
|||||||
.SS AVAILABLE ACTIONS:
|
.SS AVAILABLE ACTIONS:
|
||||||
A key or an event can be bound to one or more of the following actions.
|
A key or an event can be bound to one or more of the following actions.
|
||||||
|
|
||||||
\fBACTION: DEFAULT BINDINGS (NOTES):
|
\fBACTION: DEFAULT BINDINGS (NOTES):
|
||||||
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
|
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
|
||||||
\fBaccept\fR \fIenter double-click\fR
|
\fBaccept\fR \fIenter double-click\fR
|
||||||
\fBaccept-non-empty\fR (same as \fBaccept\fR except that it prevents fzf from exiting without selection)
|
\fBaccept-non-empty\fR (same as \fBaccept\fR except that it prevents fzf from exiting without selection)
|
||||||
\fBbackward-char\fR \fIctrl-b left\fR
|
\fBbackward-char\fR \fIctrl-b left\fR
|
||||||
\fBbackward-delete-char\fR \fIctrl-h bspace\fR
|
\fBbackward-delete-char\fR \fIctrl-h bspace\fR
|
||||||
\fBbackward-delete-char/eof\fR (same as \fBbackward-delete-char\fR except aborts fzf if query is empty)
|
\fBbackward-delete-char/eof\fR (same as \fBbackward-delete-char\fR except aborts fzf if query is empty)
|
||||||
\fBbackward-kill-word\fR \fIalt-bs\fR
|
\fBbackward-kill-word\fR \fIalt-bs\fR
|
||||||
\fBbackward-word\fR \fIalt-b shift-left\fR
|
\fBbackward-word\fR \fIalt-b shift-left\fR
|
||||||
\fBbeginning-of-line\fR \fIctrl-a home\fR
|
\fBbecome(...)\fR (replace fzf process with the specified command; see below for the details)
|
||||||
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
\fBbeginning-of-line\fR \fIctrl-a home\fR
|
||||||
\fBchange-preview(...)\fR (change \fB--preview\fR option)
|
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
||||||
\fBchange-preview-window(...)\fR (change \fB--preview-window\fR option; rotate through the multiple option sets separated by '|')
|
\fBchange-border-label(...)\fR (change \fB--border-label\fR to the given string)
|
||||||
\fBchange-prompt(...)\fR (change prompt to the given string)
|
\fBchange-header(...)\fR (change header to the given string; doesn't affect \fB--header-lines\fR)
|
||||||
\fBclear-screen\fR \fIctrl-l\fR
|
\fBchange-preview(...)\fR (change \fB--preview\fR option)
|
||||||
\fBclear-selection\fR (clear multi-selection)
|
\fBchange-preview-label(...)\fR (change \fB--preview-label\fR to the given string)
|
||||||
\fBclose\fR (close preview window if open, abort fzf otherwise)
|
\fBchange-preview-window(...)\fR (change \fB--preview-window\fR option; rotate through the multiple option sets separated by '|')
|
||||||
\fBclear-query\fR (clear query string)
|
\fBchange-prompt(...)\fR (change prompt to the given string)
|
||||||
\fBdelete-char\fR \fIdel\fR
|
\fBchange-query(...)\fR (change query string to the given string)
|
||||||
\fBdelete-char/eof\fR \fIctrl-d\fR (same as \fBdelete-char\fR except aborts fzf if query is empty)
|
\fBclear-screen\fR \fIctrl-l\fR
|
||||||
|
\fBclear-selection\fR (clear multi-selection)
|
||||||
|
\fBclose\fR (close preview window if open, abort fzf otherwise)
|
||||||
|
\fBclear-query\fR (clear query string)
|
||||||
|
\fBdelete-char\fR \fIdel\fR
|
||||||
|
\fBdelete-char/eof\fR \fIctrl-d\fR (same as \fBdelete-char\fR except aborts fzf if query is empty)
|
||||||
\fBdeselect\fR
|
\fBdeselect\fR
|
||||||
\fBdeselect-all\fR (deselect all matches)
|
\fBdeselect-all\fR (deselect all matches)
|
||||||
\fBdisable-search\fR (disable search functionality)
|
\fBdisable-search\fR (disable search functionality)
|
||||||
\fBdown\fR \fIctrl-j ctrl-n down\fR
|
\fBdown\fR \fIctrl-j ctrl-n down\fR
|
||||||
\fBenable-search\fR (enable search functionality)
|
\fBenable-search\fR (enable search functionality)
|
||||||
\fBend-of-line\fR \fIctrl-e end\fR
|
\fBend-of-line\fR \fIctrl-e end\fR
|
||||||
\fBexecute(...)\fR (see below for the details)
|
\fBexecute(...)\fR (see below for the details)
|
||||||
\fBexecute-silent(...)\fR (see below for the details)
|
\fBexecute-silent(...)\fR (see below for the details)
|
||||||
\fBfirst\fR (move to the first match)
|
\fBfirst\fR (move to the first match; same as \fBpos(1)\fR)
|
||||||
\fBforward-char\fR \fIctrl-f right\fR
|
\fBforward-char\fR \fIctrl-f right\fR
|
||||||
\fBforward-word\fR \fIalt-f shift-right\fR
|
\fBforward-word\fR \fIalt-f shift-right\fR
|
||||||
\fBignore\fR
|
\fBignore\fR
|
||||||
\fBjump\fR (EasyMotion-like 2-keystroke movement)
|
\fBjump\fR (EasyMotion-like 2-keystroke movement)
|
||||||
\fBjump-accept\fR (jump and accept)
|
\fBjump-accept\fR (jump and accept)
|
||||||
\fBkill-line\fR
|
\fBkill-line\fR
|
||||||
\fBkill-word\fR \fIalt-d\fR
|
\fBkill-word\fR \fIalt-d\fR
|
||||||
\fBlast\fR (move to the last match)
|
\fBlast\fR (move to the last match; same as \fBpos(-1)\fR)
|
||||||
\fBnext-history\fR (\fIctrl-n\fR on \fB--history\fR)
|
\fBnext-history\fR (\fIctrl-n\fR on \fB--history\fR)
|
||||||
\fBpage-down\fR \fIpgdn\fR
|
\fBnext-selected\fR (move to the next selected item)
|
||||||
\fBpage-up\fR \fIpgup\fR
|
\fBpage-down\fR \fIpgdn\fR
|
||||||
|
\fBpage-up\fR \fIpgup\fR
|
||||||
\fBhalf-page-down\fR
|
\fBhalf-page-down\fR
|
||||||
\fBhalf-page-up\fR
|
\fBhalf-page-up\fR
|
||||||
\fBpreview(...)\fR (see below for the details)
|
\fBhide-preview\fR
|
||||||
\fBpreview-down\fR \fIshift-down\fR
|
\fBpos(...)\fR (move cursor to the numeric position; negative number to count from the end)
|
||||||
\fBpreview-up\fR \fIshift-up\fR
|
\fBprev-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
||||||
|
\fBprev-selected\fR (move to the previous selected item)
|
||||||
|
\fBpreview(...)\fR (see below for the details)
|
||||||
|
\fBpreview-down\fR \fIshift-down\fR
|
||||||
|
\fBpreview-up\fR \fIshift-up\fR
|
||||||
\fBpreview-page-down\fR
|
\fBpreview-page-down\fR
|
||||||
\fBpreview-page-up\fR
|
\fBpreview-page-up\fR
|
||||||
\fBpreview-half-page-down\fR
|
\fBpreview-half-page-down\fR
|
||||||
\fBpreview-half-page-up\fR
|
\fBpreview-half-page-up\fR
|
||||||
\fBpreview-bottom\fR
|
\fBpreview-bottom\fR
|
||||||
\fBpreview-top\fR
|
\fBpreview-top\fR
|
||||||
\fBprevious-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
\fBprint-query\fR (print query and exit)
|
||||||
\fBprint-query\fR (print query and exit)
|
\fBput\fR (put the character to the prompt)
|
||||||
\fBput\fR (put the character to the prompt)
|
\fBput(...)\fR (put the given string to the prompt)
|
||||||
\fBrefresh-preview\fR
|
\fBrefresh-preview\fR
|
||||||
\fBreload(...)\fR (see below for the details)
|
\fBrebind(...)\fR (rebind bindings after \fBunbind\fR)
|
||||||
\fBreplace-query\fR (replace query string with the current selection)
|
\fBreload(...)\fR (see below for the details)
|
||||||
|
\fBreload-sync(...)\fR (see below for the details)
|
||||||
|
\fBreplace-query\fR (replace query string with the current selection)
|
||||||
\fBselect\fR
|
\fBselect\fR
|
||||||
\fBselect-all\fR (select all matches)
|
\fBselect-all\fR (select all matches)
|
||||||
\fBtoggle\fR (\fIright-click\fR)
|
\fBshow-preview\fR
|
||||||
\fBtoggle-all\fR (toggle all matches)
|
\fBtoggle\fR (\fIright-click\fR)
|
||||||
\fBtoggle+down\fR \fIctrl-i (tab)\fR
|
\fBtoggle-all\fR (toggle all matches)
|
||||||
\fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
\fBtoggle+down\fR \fIctrl-i (tab)\fR
|
||||||
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
\fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
||||||
|
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
||||||
\fBtoggle-preview\fR
|
\fBtoggle-preview\fR
|
||||||
\fBtoggle-preview-wrap\fR
|
\fBtoggle-preview-wrap\fR
|
||||||
\fBtoggle-search\fR (toggle search functionality)
|
\fBtoggle-search\fR (toggle search functionality)
|
||||||
\fBtoggle-sort\fR
|
\fBtoggle-sort\fR
|
||||||
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
\fBtoggle-track\fR
|
||||||
\fBunbind(...)\fR (unbind bindings)
|
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
||||||
\fBunix-line-discard\fR \fIctrl-u\fR
|
\fBtrack\fR (track the current item; automatically disabled if focus changes)
|
||||||
\fBunix-word-rubout\fR \fIctrl-w\fR
|
\fBtransform-border-label(...)\fR (transform border label using an external command)
|
||||||
\fBup\fR \fIctrl-k ctrl-p up\fR
|
\fBtransform-header(...)\fR (transform header using an external command)
|
||||||
\fByank\fR \fIctrl-y\fR
|
\fBtransform-preview-label(...)\fR (transform preview label using an external command)
|
||||||
|
\fBtransform-prompt(...)\fR (transform prompt string using an external command)
|
||||||
|
\fBtransform-query(...)\fR (transform query string using an external command)
|
||||||
|
\fBunbind(...)\fR (unbind bindings)
|
||||||
|
\fBunix-line-discard\fR \fIctrl-u\fR
|
||||||
|
\fBunix-word-rubout\fR \fIctrl-w\fR
|
||||||
|
\fBup\fR \fIctrl-k ctrl-p up\fR
|
||||||
|
\fByank\fR \fIctrl-y\fR
|
||||||
|
|
||||||
.SS ACTION COMPOSITION
|
.SS ACTION COMPOSITION
|
||||||
|
|
||||||
@@ -905,6 +1165,8 @@ that case, you can use any of the following alternative notations to avoid
|
|||||||
parse errors.
|
parse errors.
|
||||||
|
|
||||||
\fBaction-name[...]\fR
|
\fBaction-name[...]\fR
|
||||||
|
\fBaction-name{...}\fR
|
||||||
|
\fBaction-name<...>\fR
|
||||||
\fBaction-name~...~\fR
|
\fBaction-name~...~\fR
|
||||||
\fBaction-name!...!\fR
|
\fBaction-name!...!\fR
|
||||||
\fBaction-name@...@\fR
|
\fBaction-name@...@\fR
|
||||||
@@ -945,6 +1207,14 @@ On *nix systems, fzf runs the command with \fB$SHELL -c\fR if \fBSHELL\fR is
|
|||||||
set, otherwise with \fBsh -c\fR, so in this case make sure that the command is
|
set, otherwise with \fBsh -c\fR, so in this case make sure that the command is
|
||||||
POSIX-compliant.
|
POSIX-compliant.
|
||||||
|
|
||||||
|
\fBbecome(...)\fR action is similar to \fBexecute(...)\fR, but it replaces the
|
||||||
|
current fzf process with the specified command using \fBexecve(2)\fR system
|
||||||
|
call.
|
||||||
|
|
||||||
|
\fBfzf --bind "enter:become(vim {})"\fR
|
||||||
|
|
||||||
|
\fBbecome(...)\fR is not supported on Windows.
|
||||||
|
|
||||||
.SS RELOAD INPUT
|
.SS RELOAD INPUT
|
||||||
|
|
||||||
\fBreload(...)\fR action is used to dynamically update the input list
|
\fBreload(...)\fR action is used to dynamically update the input list
|
||||||
@@ -965,6 +1235,16 @@ e.g.
|
|||||||
fzf --bind "change:reload:$RG_PREFIX {q} || true" \\
|
fzf --bind "change:reload:$RG_PREFIX {q} || true" \\
|
||||||
--ansi --disabled --query "$INITIAL_QUERY"\fR
|
--ansi --disabled --query "$INITIAL_QUERY"\fR
|
||||||
|
|
||||||
|
\fBreload-sync(...)\fR is a synchronous version of \fBreload\fR that replaces
|
||||||
|
the list only when the command is complete. This is useful when the command
|
||||||
|
takes a while to produce the initial output and you don't want fzf to run
|
||||||
|
against an empty list while the command is running.
|
||||||
|
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fB# You can still filter and select entries from the initial list for 3 seconds
|
||||||
|
seq 100 | fzf --bind 'load:reload-sync(sleep 3; seq 1000)+unbind(load)'\fR
|
||||||
|
|
||||||
.SS PREVIEW BINDING
|
.SS PREVIEW BINDING
|
||||||
|
|
||||||
With \fBpreview(...)\fR action, you can specify multiple different preview
|
With \fBpreview(...)\fR action, you can specify multiple different preview
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
" Copyright (c) 2017 Junegunn Choi
|
" Copyright (c) 2013-2023 Junegunn Choi
|
||||||
"
|
"
|
||||||
" MIT License
|
" MIT License
|
||||||
"
|
"
|
||||||
@@ -96,7 +96,12 @@ function! fzf#shellescape(arg, ...)
|
|||||||
if shell =~# 'cmd.exe$'
|
if shell =~# 'cmd.exe$'
|
||||||
return s:shellesc_cmd(a:arg)
|
return s:shellesc_cmd(a:arg)
|
||||||
endif
|
endif
|
||||||
return s:fzf_call('shellescape', a:arg)
|
try
|
||||||
|
let [shell, &shell] = [&shell, shell]
|
||||||
|
return s:fzf_call('shellescape', a:arg)
|
||||||
|
finally
|
||||||
|
let [shell, &shell] = [&shell, shell]
|
||||||
|
endtry
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:fzf_getcwd()
|
function! s:fzf_getcwd()
|
||||||
@@ -138,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)
|
||||||
@@ -159,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 = a:bin . ' --version --no-height'
|
let command = (&shell =~ 'powershell\|pwsh' ? '&' : '') . 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 ''
|
||||||
@@ -337,7 +342,8 @@ function! s:common_sink(action, lines) abort
|
|||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:get_color(attr, ...)
|
function! s:get_color(attr, ...)
|
||||||
let gui = !s:is_win && !has('win32unix') && has('termguicolors') && &termguicolors
|
" Force 24 bit colors: g:fzf_force_termguicolors (temporary workaround for https://github.com/junegunn/fzf.vim/issues/1152)
|
||||||
|
let gui = get(g:, 'fzf_force_termguicolors', 0) || (!s:is_win && !has('win32unix') && has('termguicolors') && &termguicolors)
|
||||||
let fam = gui ? 'gui' : 'cterm'
|
let fam = gui ? 'gui' : 'cterm'
|
||||||
let pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$'
|
let pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$'
|
||||||
for group in a:000
|
for group in a:000
|
||||||
@@ -450,6 +456,30 @@ function! s:writefile(...)
|
|||||||
endif
|
endif
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
function! s:extract_option(opts, name)
|
||||||
|
let opt = ''
|
||||||
|
let expect = 0
|
||||||
|
" There are a few cases where this function doesn't work as expected.
|
||||||
|
" Let's just assume such cases are extremely unlikely in real world.
|
||||||
|
" e.g. --query --border
|
||||||
|
for word in split(a:opts)
|
||||||
|
if expect && word !~ '^"\=-'
|
||||||
|
let opt = opt . ' ' . word
|
||||||
|
let expect = 0
|
||||||
|
elseif word == '--no-'.a:name
|
||||||
|
let opt = ''
|
||||||
|
elseif word =~ '^--'.a:name.'='
|
||||||
|
let opt = word
|
||||||
|
elseif word =~ '^--'.a:name.'$'
|
||||||
|
let opt = word
|
||||||
|
let expect = 1
|
||||||
|
elseif expect
|
||||||
|
let expect = 0
|
||||||
|
endif
|
||||||
|
endfor
|
||||||
|
return opt
|
||||||
|
endfunction
|
||||||
|
|
||||||
function! fzf#run(...) abort
|
function! fzf#run(...) abort
|
||||||
try
|
try
|
||||||
let [shell, shellslash, shellcmdflag, shellxquote] = s:use_sh()
|
let [shell, shellslash, shellcmdflag, shellxquote] = s:use_sh()
|
||||||
@@ -458,7 +488,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
|
||||||
@@ -505,7 +535,8 @@ try
|
|||||||
let height = s:calc_size(&lines, dict.down, dict)
|
let height = s:calc_size(&lines, dict.down, dict)
|
||||||
let optstr .= ' --height='.height
|
let optstr .= ' --height='.height
|
||||||
endif
|
endif
|
||||||
let optstr .= s:border_opt(get(dict, 'window', 0))
|
" Respect --border option given in $FZF_DEFAULT_OPTS and 'options'
|
||||||
|
let optstr = join([s:border_opt(get(dict, 'window', 0)), s:extract_option($FZF_DEFAULT_OPTS, 'border'), optstr])
|
||||||
let prev_default_command = $FZF_DEFAULT_COMMAND
|
let prev_default_command = $FZF_DEFAULT_COMMAND
|
||||||
if len(source_command)
|
if len(source_command)
|
||||||
let $FZF_DEFAULT_COMMAND = source_command
|
let $FZF_DEFAULT_COMMAND = source_command
|
||||||
@@ -732,7 +763,7 @@ function! s:calc_size(max, val, dict)
|
|||||||
return size
|
return size
|
||||||
endif
|
endif
|
||||||
let margin = match(opts, '--inline-info\|--info[^-]\{-}inline') > match(opts, '--no-inline-info\|--info[^-]\{-}\(default\|hidden\)') ? 1 : 2
|
let margin = match(opts, '--inline-info\|--info[^-]\{-}inline') > match(opts, '--no-inline-info\|--info[^-]\{-}\(default\|hidden\)') ? 1 : 2
|
||||||
let margin += stridx(opts, '--border') > stridx(opts, '--no-border') ? 2 : 0
|
let margin += match(opts, '--border\([^-]\|$\)') > match(opts, '--no-border\([^-]\|$\)') ? 2 : 0
|
||||||
if stridx(opts, '--header') > stridx(opts, '--no-header')
|
if stridx(opts, '--header') > stridx(opts, '--no-header')
|
||||||
let margin += len(split(opts, "\n"))
|
let margin += len(split(opts, "\n"))
|
||||||
endif
|
endif
|
||||||
@@ -749,9 +780,9 @@ function! s:border_opt(window)
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
" Border style
|
" Border style
|
||||||
let style = tolower(get(a:window, 'border', 'rounded'))
|
let style = tolower(get(a:window, 'border', ''))
|
||||||
if !has_key(a:window, 'border') && !get(a:window, 'rounded', 1)
|
if !has_key(a:window, 'border') && has_key(a:window, 'rounded')
|
||||||
let style = 'sharp'
|
let style = a:window.rounded ? 'rounded' : 'sharp'
|
||||||
endif
|
endif
|
||||||
if style == 'none' || style == 'no'
|
if style == 'none' || style == 'no'
|
||||||
return ''
|
return ''
|
||||||
@@ -759,7 +790,7 @@ function! s:border_opt(window)
|
|||||||
|
|
||||||
" For --border styles, we need fzf 0.24.0 or above
|
" For --border styles, we need fzf 0.24.0 or above
|
||||||
call fzf#exec('0.24.0')
|
call fzf#exec('0.24.0')
|
||||||
let opt = ' --border=' . style
|
let opt = ' --border ' . style
|
||||||
if has_key(a:window, 'highlight')
|
if has_key(a:window, 'highlight')
|
||||||
let color = s:get_color('fg', a:window.highlight)
|
let color = s:get_color('fg', a:window.highlight)
|
||||||
if len(color)
|
if len(color)
|
||||||
@@ -821,6 +852,17 @@ if exists(':tnoremap')
|
|||||||
tnoremap <silent> <Plug>(fzf-normal) <C-\><C-n>
|
tnoremap <silent> <Plug>(fzf-normal) <C-\><C-n>
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
let s:warned = 0
|
||||||
|
function! s:handle_ambidouble(dict)
|
||||||
|
if &ambiwidth == 'double'
|
||||||
|
let a:dict.env = { 'RUNEWIDTH_EASTASIAN': '1' }
|
||||||
|
elseif !s:warned && $RUNEWIDTH_EASTASIAN == '1' && &ambiwidth !=# 'double'
|
||||||
|
call s:warn("$RUNEWIDTH_EASTASIAN is '1' but &ambiwidth is not 'double'")
|
||||||
|
2sleep
|
||||||
|
let s:warned = 1
|
||||||
|
endif
|
||||||
|
endfunction
|
||||||
|
|
||||||
function! s:execute_term(dict, command, temps) abort
|
function! s:execute_term(dict, command, temps) abort
|
||||||
let winrest = winrestcmd()
|
let winrest = winrestcmd()
|
||||||
let pbuf = bufnr('')
|
let pbuf = bufnr('')
|
||||||
@@ -890,6 +932,7 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
endif
|
endif
|
||||||
let command .= s:term_marker
|
let command .= s:term_marker
|
||||||
if has('nvim')
|
if has('nvim')
|
||||||
|
call s:handle_ambidouble(fzf)
|
||||||
call termopen(command, fzf)
|
call termopen(command, fzf)
|
||||||
else
|
else
|
||||||
let term_opts = {'exit_cb': function(fzf.on_exit)}
|
let term_opts = {'exit_cb': function(fzf.on_exit)}
|
||||||
@@ -901,6 +944,7 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
else
|
else
|
||||||
let term_opts.curwin = 1
|
let term_opts.curwin = 1
|
||||||
endif
|
endif
|
||||||
|
call s:handle_ambidouble(term_opts)
|
||||||
let fzf.buf = term_start([&shell, &shellcmdflag, command], term_opts)
|
let fzf.buf = term_start([&shell, &shellcmdflag, command], term_opts)
|
||||||
if is_popup && exists('#TerminalWinOpen')
|
if is_popup && exists('#TerminalWinOpen')
|
||||||
doautocmd <nomodeline> TerminalWinOpen
|
doautocmd <nomodeline> TerminalWinOpen
|
||||||
@@ -967,16 +1011,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,
|
||||||
@@ -1011,7 +1055,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
|
||||||
|
@@ -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"
|
||||||
@@ -161,7 +162,11 @@ _fzf_handle_dynamic_completion() {
|
|||||||
|
|
||||||
__fzf_generic_path_completion() {
|
__fzf_generic_path_completion() {
|
||||||
local cur base dir leftover matches trigger cmd
|
local cur base dir leftover matches trigger cmd
|
||||||
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
|
cmd="${COMP_WORDS[0]}"
|
||||||
|
if [[ $cmd == \\* ]]; then
|
||||||
|
cmd="${cmd:1}"
|
||||||
|
fi
|
||||||
|
cmd="${cmd//[^A-Za-z0-9_=]/_}"
|
||||||
COMPREPLY=()
|
COMPREPLY=()
|
||||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
@@ -169,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
|
||||||
@@ -176,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
|
||||||
@@ -232,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")
|
||||||
@@ -260,20 +266,13 @@ _fzf_dir_completion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_kill() {
|
_fzf_complete_kill() {
|
||||||
local trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
|
||||||
local cur="${COMP_WORDS[COMP_CWORD]}"
|
|
||||||
if [[ -z "$cur" ]]; then
|
|
||||||
COMP_WORDS[$COMP_CWORD]=$trigger
|
|
||||||
elif [[ "$cur" != *"$trigger" ]]; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
_fzf_proc_completion "$@"
|
_fzf_proc_completion "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_proc_completion() {
|
_fzf_proc_completion() {
|
||||||
_fzf_complete -m --preview 'echo {}' --preview-window down:3:wrap --min-height 15 -- "$@" < <(
|
_fzf_complete -m --header-lines=1 --preview 'echo {}' --preview-window down:3:wrap --min-height 15 -- "$@" < <(
|
||||||
command ps -ef | sed 1d
|
command ps -eo user,pid,ppid,start,time,command 2> /dev/null ||
|
||||||
|
command ps -eo user,pid,ppid,time,args # For BusyBox
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,7 +282,7 @@ _fzf_proc_completion_post() {
|
|||||||
|
|
||||||
_fzf_host_completion() {
|
_fzf_host_completion() {
|
||||||
_fzf_complete +m -- "$@" < <(
|
_fzf_complete +m -- "$@" < <(
|
||||||
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?]') \
|
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?%]') \
|
||||||
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
||||||
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
||||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||||
@@ -292,23 +291,27 @@ _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'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
# fzf options
|
# fzf options
|
||||||
complete -o default -F _fzf_opts_completion fzf
|
complete -o default -F _fzf_opts_completion fzf
|
||||||
|
# fzf-tmux is a thin fzf wrapper that has only a few more options than fzf
|
||||||
|
# itself. As a quick improvement we take fzf's completion. Adding the few extra
|
||||||
|
# fzf-tmux specific options (like `-w WIDTH`) are left as a future patch.
|
||||||
|
complete -o default -F _fzf_opts_completion fzf-tmux
|
||||||
|
|
||||||
d_cmds="${FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir}"
|
d_cmds="${FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir}"
|
||||||
a_cmds="
|
a_cmds="
|
||||||
awk cat diff diff3
|
awk bat cat diff diff3
|
||||||
emacs emacsclient ex file ftp g++ gcc gvim head hg java
|
emacs emacsclient ex file ftp g++ gcc gvim head hg hx java
|
||||||
javac ld less more mvim nvim patch perl python ruby
|
javac ld less more mvim nvim patch perl python ruby
|
||||||
sed sftp sort source tail tee uniq vi view vim wc xdg-open
|
sed sftp sort source tail tee uniq vi view vim wc xdg-open
|
||||||
basename bunzip2 bzip2 chmod chown curl cp dirname du
|
basename bunzip2 bzip2 chmod chown curl cp dirname du
|
||||||
@@ -329,7 +332,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"
|
||||||
@@ -348,9 +351,6 @@ for cmd in $d_cmds; do
|
|||||||
__fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames"
|
__fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Kill completion (supports empty completion trigger)
|
|
||||||
complete -F _fzf_complete_kill -o default -o bashdefault kill
|
|
||||||
|
|
||||||
unset cmd d_cmds a_cmds
|
unset cmd d_cmds a_cmds
|
||||||
|
|
||||||
_fzf_setup_completion() {
|
_fzf_setup_completion() {
|
||||||
@@ -373,9 +373,10 @@ _fzf_setup_completion() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# Environment variables / Aliases / Hosts
|
# Environment variables / Aliases / Hosts / Process
|
||||||
_fzf_setup_completion 'var' export unset
|
_fzf_setup_completion 'var' export unset printenv
|
||||||
_fzf_setup_completion 'alias' unalias
|
_fzf_setup_completion 'alias' unalias
|
||||||
_fzf_setup_completion 'host' ssh telnet
|
_fzf_setup_completion 'host' ssh telnet
|
||||||
|
_fzf_setup_completion 'proc' kill
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
@@ -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
|
||||||
@@ -224,7 +225,7 @@ _fzf_complete_telnet() {
|
|||||||
_fzf_complete_ssh() {
|
_fzf_complete_ssh() {
|
||||||
_fzf_complete +m -- "$@" < <(
|
_fzf_complete +m -- "$@" < <(
|
||||||
setopt localoptions nonomatch
|
setopt localoptions nonomatch
|
||||||
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?]') \
|
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?%]') \
|
||||||
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
||||||
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
||||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||||
@@ -250,8 +251,9 @@ _fzf_complete_unalias() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_kill() {
|
_fzf_complete_kill() {
|
||||||
_fzf_complete -m --preview 'echo {}' --preview-window down:3:wrap --min-height 15 -- "$@" < <(
|
_fzf_complete -m --header-lines=1 --preview 'echo {}' --preview-window down:3:wrap --min-height 15 -- "$@" < <(
|
||||||
command ps -ef | sed 1d
|
command ps -eo user,pid,ppid,start,time,command 2> /dev/null ||
|
||||||
|
command ps -eo user,pid,ppid,time,args # For BusyBox
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,19 +280,13 @@ 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
|
||||||
|
|
||||||
lbuf=$LBUFFER
|
lbuf=$LBUFFER
|
||||||
tail=${LBUFFER:$(( ${#LBUFFER} - ${#trigger} ))}
|
tail=${LBUFFER:$(( ${#LBUFFER} - ${#trigger} ))}
|
||||||
# Kill completion (do not require trigger sequence)
|
|
||||||
if [ "$cmd" = kill -a ${LBUFFER[-1]} = ' ' ]; then
|
|
||||||
tail=$trigger
|
|
||||||
tokens+=$trigger
|
|
||||||
lbuf="$lbuf$trigger"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Trigger sequence given
|
# Trigger sequence given
|
||||||
if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
|
if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
|
||||||
|
@@ -14,42 +14,48 @@
|
|||||||
# Key bindings
|
# Key bindings
|
||||||
# ------------
|
# ------------
|
||||||
__fzf_select__() {
|
__fzf_select__() {
|
||||||
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
local cmd opts
|
||||||
|
cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||||
-o -type f -print \
|
-o -type 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-"}"
|
||||||
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 -r item; do
|
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-} -m"
|
||||||
printf '%q ' "$item"
|
eval "$cmd" |
|
||||||
done
|
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) "$@" |
|
||||||
echo
|
while read -r item; do
|
||||||
|
printf '%q ' "$item" # escape special chars
|
||||||
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
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"
|
||||||
}
|
}
|
||||||
|
|
||||||
fzf-file-widget() {
|
fzf-file-widget() {
|
||||||
local selected="$(__fzf_select__)"
|
local selected="$(__fzf_select__ "$@")"
|
||||||
READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}$selected${READLINE_LINE:$READLINE_POINT}"
|
READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}$selected${READLINE_LINE:$READLINE_POINT}"
|
||||||
READLINE_POINT=$(( READLINE_POINT + ${#selected} ))
|
READLINE_POINT=$(( READLINE_POINT + ${#selected} ))
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzf_cd__() {
|
__fzf_cd__() {
|
||||||
local cmd 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-"}"
|
||||||
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) && printf 'cd -- %q' "$dir"
|
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"
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzf_history__() {
|
__fzf_history__() {
|
||||||
local output
|
local output opts script
|
||||||
|
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort ${FZF_CTRL_R_OPTS-} +m --read0"
|
||||||
|
script='BEGIN { getc; $/ = "\n\t"; $HISTCOUNT = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCOUNT - $. . "\t$_" if !$seen{$_}++'
|
||||||
output=$(
|
output=$(
|
||||||
builtin fc -lnr -2147483648 |
|
builtin fc -lnr -2147483648 |
|
||||||
last_hist=$(HISTTIMEFORMAT='' builtin history 1) perl -n -l0 -e 'BEGIN { getc; $/ = "\n\t"; $HISTCMD = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCMD - $. . "\t$_" if !$seen{$_}++' |
|
last_hist=$(HISTTIMEFORMAT='' builtin history 1) perl -n -l0 -e "$script" |
|
||||||
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 +m --read0" $(__fzfcmd) --query "$READLINE_LINE"
|
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) --query "$READLINE_LINE"
|
||||||
) || return
|
) || return
|
||||||
READLINE_LINE=${output#*$'\t'}
|
READLINE_LINE=${output#*$'\t'}
|
||||||
if [[ -z "$READLINE_POINT" ]]; then
|
if [[ -z "$READLINE_POINT" ]]; then
|
||||||
|
@@ -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.)
|
||||||
|
@@ -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"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,36 +65,40 @@ fzf-file-widget() {
|
|||||||
zle reset-prompt
|
zle reset-prompt
|
||||||
return $ret
|
return $ret
|
||||||
}
|
}
|
||||||
zle -N fzf-file-widget
|
zle -N fzf-file-widget
|
||||||
bindkey '^T' fzf-file-widget
|
bindkey -M emacs '^T' fzf-file-widget
|
||||||
|
bindkey -M vicmd '^T' fzf-file-widget
|
||||||
|
bindkey -M viins '^T' fzf-file-widget
|
||||||
|
|
||||||
# ALT-C - cd into the selected directory
|
# ALT-C - cd into the selected directory
|
||||||
fzf-cd-widget() {
|
fzf-cd-widget() {
|
||||||
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
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
|
||||||
fi
|
fi
|
||||||
zle push-line # Clear buffer. Auto-restored on next prompt.
|
zle push-line # Clear buffer. Auto-restored on next prompt.
|
||||||
BUFFER="cd -- ${(q)dir}"
|
BUFFER="builtin cd -- ${(q)dir}"
|
||||||
zle accept-line
|
zle accept-line
|
||||||
local ret=$?
|
local ret=$?
|
||||||
unset dir # ensure this doesn't end up appearing in prompt expansion
|
unset dir # ensure this doesn't end up appearing in prompt expansion
|
||||||
zle reset-prompt
|
zle reset-prompt
|
||||||
return $ret
|
return $ret
|
||||||
}
|
}
|
||||||
zle -N fzf-cd-widget
|
zle -N fzf-cd-widget
|
||||||
bindkey '\ec' fzf-cd-widget
|
bindkey -M emacs '\ec' fzf-cd-widget
|
||||||
|
bindkey -M vicmd '\ec' fzf-cd-widget
|
||||||
|
bindkey -M viins '\ec' fzf-cd-widget
|
||||||
|
|
||||||
# CTRL-R - Paste the selected command from history into the command line
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
fzf-history-widget() {
|
fzf-history-widget() {
|
||||||
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 | perl -ne 'print if !$seen{(/^\s*[0-9]+\**\s+(.*)/, $1)}++' |
|
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]
|
||||||
@@ -105,8 +109,10 @@ fzf-history-widget() {
|
|||||||
zle reset-prompt
|
zle reset-prompt
|
||||||
return $ret
|
return $ret
|
||||||
}
|
}
|
||||||
zle -N fzf-history-widget
|
zle -N fzf-history-widget
|
||||||
bindkey '^R' fzf-history-widget
|
bindkey -M emacs '^R' fzf-history-widget
|
||||||
|
bindkey -M vicmd '^R' fzf-history-widget
|
||||||
|
bindkey -M viins '^R' fzf-history-widget
|
||||||
|
|
||||||
} always {
|
} always {
|
||||||
eval $__fzf_key_bindings_options
|
eval $__fzf_key_bindings_options
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2021 Junegunn Choi
|
Copyright (c) 2013-2023 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
|
||||||
|
@@ -80,6 +80,7 @@ Scoring criteria
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
@@ -89,6 +90,10 @@ import (
|
|||||||
|
|
||||||
var DEBUG bool
|
var DEBUG bool
|
||||||
|
|
||||||
|
var delimiterChars = "/,:;|"
|
||||||
|
|
||||||
|
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 {
|
||||||
if forward {
|
if forward {
|
||||||
return index
|
return index
|
||||||
@@ -140,16 +145,51 @@ 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 (
|
||||||
charNonWord charClass = iota
|
charWhite charClass = iota
|
||||||
|
charNonWord
|
||||||
|
charDelimiter
|
||||||
charLower
|
charLower
|
||||||
charUpper
|
charUpper
|
||||||
charLetter
|
charLetter
|
||||||
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)
|
||||||
@@ -181,6 +221,10 @@ func charClassOfAscii(char rune) charClass {
|
|||||||
return charUpper
|
return charUpper
|
||||||
} else if char >= '0' && char <= '9' {
|
} else if char >= '0' && char <= '9' {
|
||||||
return charNumber
|
return charNumber
|
||||||
|
} else if strings.IndexRune(whiteChars, char) >= 0 {
|
||||||
|
return charWhite
|
||||||
|
} else if strings.IndexRune(delimiterChars, char) >= 0 {
|
||||||
|
return charDelimiter
|
||||||
}
|
}
|
||||||
return charNonWord
|
return charNonWord
|
||||||
}
|
}
|
||||||
@@ -194,6 +238,10 @@ func charClassOfNonAscii(char rune) charClass {
|
|||||||
return charNumber
|
return charNumber
|
||||||
} else if unicode.IsLetter(char) {
|
} else if unicode.IsLetter(char) {
|
||||||
return charLetter
|
return charLetter
|
||||||
|
} else if unicode.IsSpace(char) {
|
||||||
|
return charWhite
|
||||||
|
} else if strings.IndexRune(delimiterChars, char) >= 0 {
|
||||||
|
return charDelimiter
|
||||||
}
|
}
|
||||||
return charNonWord
|
return charNonWord
|
||||||
}
|
}
|
||||||
@@ -206,22 +254,33 @@ func charClassOf(char rune) charClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func bonusFor(prevClass charClass, class charClass) int16 {
|
func bonusFor(prevClass charClass, class charClass) int16 {
|
||||||
if prevClass == charNonWord && class != charNonWord {
|
if class > charNonWord {
|
||||||
// Word boundary
|
if prevClass == charWhite {
|
||||||
return bonusBoundary
|
// Word boundary after whitespace
|
||||||
} else if prevClass == charLower && class == charUpper ||
|
return bonusBoundaryWhite
|
||||||
|
} else if prevClass == charDelimiter {
|
||||||
|
// Word boundary after a delimiter character
|
||||||
|
return bonusBoundaryDelimiter
|
||||||
|
} else if prevClass == charNonWord {
|
||||||
|
// Word boundary
|
||||||
|
return bonusBoundary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if prevClass == charLower && class == charUpper ||
|
||||||
prevClass != charNumber && class == charNumber {
|
prevClass != charNumber && class == charNumber {
|
||||||
// camelCase letter123
|
// camelCase letter123
|
||||||
return bonusCamel123
|
return bonusCamel123
|
||||||
} else if class == charNonWord {
|
} else if class == charNonWord {
|
||||||
return bonusNonWord
|
return bonusNonWord
|
||||||
|
} else if class == charWhite {
|
||||||
|
return bonusBoundaryWhite
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func bonusAt(input *util.Chars, idx int) int16 {
|
func bonusAt(input *util.Chars, idx int) int16 {
|
||||||
if idx == 0 {
|
if idx == 0 {
|
||||||
return bonusBoundary
|
return bonusBoundaryWhite
|
||||||
}
|
}
|
||||||
return bonusFor(charClassOf(input.Get(idx-1)), charClassOf(input.Get(idx)))
|
return bonusFor(charClassOf(input.Get(idx-1)), charClassOf(input.Get(idx)))
|
||||||
}
|
}
|
||||||
@@ -377,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), charNonWord, 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 {
|
||||||
@@ -417,7 +476,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
C0sub[off] = 1
|
C0sub[off] = 1
|
||||||
if M == 1 && (forward && score > maxScore || !forward && score >= maxScore) {
|
if M == 1 && (forward && score > maxScore || !forward && score >= maxScore) {
|
||||||
maxScore, maxScorePos = score, idx+off
|
maxScore, maxScorePos = score, idx+off
|
||||||
if forward && bonus == bonusBoundary {
|
if forward && bonus >= bonusBoundary {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -486,11 +545,14 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
s1 = Hdiag[off] + scoreMatch
|
s1 = Hdiag[off] + scoreMatch
|
||||||
b := Bsub[off]
|
b := Bsub[off]
|
||||||
consecutive = Cdiag[off] + 1
|
consecutive = Cdiag[off] + 1
|
||||||
// Break consecutive chunk
|
if consecutive > 1 {
|
||||||
if b == bonusBoundary {
|
fb := B[col-int(consecutive)+1]
|
||||||
consecutive = 1
|
// Break consecutive chunk
|
||||||
} else if consecutive > 1 {
|
if b >= bonusBoundary && b > fb {
|
||||||
b = util.Max16(b, util.Max16(bonusConsecutive, B[col-int(consecutive)+1]))
|
consecutive = 1
|
||||||
|
} else {
|
||||||
|
b = util.Max16(b, util.Max16(bonusConsecutive, fb))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if s1+b < s2 {
|
if s1+b < s2 {
|
||||||
s1 += Bsub[off]
|
s1 += Bsub[off]
|
||||||
@@ -555,7 +617,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
func calculateScore(caseSensitive bool, normalize bool, text *util.Chars, pattern []rune, sidx int, eidx int, withPos bool) (int, *[]int) {
|
func calculateScore(caseSensitive bool, normalize bool, text *util.Chars, pattern []rune, sidx int, eidx int, withPos bool) (int, *[]int) {
|
||||||
pidx, score, inGap, consecutive, firstBonus := 0, 0, false, 0, int16(0)
|
pidx, score, inGap, consecutive, firstBonus := 0, 0, false, 0, int16(0)
|
||||||
pos := posArray(withPos, len(pattern))
|
pos := posArray(withPos, len(pattern))
|
||||||
prevClass := charNonWord
|
prevClass := initialCharClass
|
||||||
if sidx > 0 {
|
if sidx > 0 {
|
||||||
prevClass = charClassOf(text.Get(sidx - 1))
|
prevClass = charClassOf(text.Get(sidx - 1))
|
||||||
}
|
}
|
||||||
@@ -583,7 +645,7 @@ func calculateScore(caseSensitive bool, normalize bool, text *util.Chars, patter
|
|||||||
firstBonus = bonus
|
firstBonus = bonus
|
||||||
} else {
|
} else {
|
||||||
// Break consecutive chunk
|
// Break consecutive chunk
|
||||||
if bonus == bonusBoundary {
|
if bonus >= bonusBoundary && bonus > firstBonus {
|
||||||
firstBonus = bonus
|
firstBonus = bonus
|
||||||
}
|
}
|
||||||
bonus = util.Max16(util.Max16(bonus, firstBonus), bonusConsecutive)
|
bonus = util.Max16(util.Max16(bonus, firstBonus), bonusConsecutive)
|
||||||
@@ -741,7 +803,7 @@ func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *uti
|
|||||||
if bonus > bestBonus {
|
if bonus > bestBonus {
|
||||||
bestPos, bestBonus = index, bonus
|
bestPos, bestBonus = index, bonus
|
||||||
}
|
}
|
||||||
if bonus == bonusBoundary {
|
if bonus >= bonusBoundary {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
index -= pidx - 1
|
index -= pidx - 1
|
||||||
@@ -877,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+bonusBoundary)*lenPattern +
|
return Result{trimmedLen, trimmedLen + lenPattern, (scoreMatch+int(bonusBoundaryWhite))*lenPattern +
|
||||||
(bonusFirstCharMultiplier-1)*bonusBoundary}, nil
|
(bonusFirstCharMultiplier-1)*int(bonusBoundaryWhite)}, nil
|
||||||
}
|
}
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
}
|
}
|
||||||
|
@@ -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+bonusBoundary*bonusFirstCharMultiplier+
|
scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+
|
||||||
bonusBoundary*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+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*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*3+scoreGapStart)
|
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+bonusBoundary*bonusFirstCharMultiplier+
|
scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+
|
||||||
bonusBoundary*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+bonusBoundary*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+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary+
|
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+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*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+bonusBoundary*(bonusFirstCharMultiplier+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+bonusBoundary*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+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*2+
|
scoreMatch*4+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryWhite)*2+
|
||||||
util.Max(bonusCamel123, bonusBoundary))
|
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+bonusBoundary*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+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary)
|
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+bonusBoundary*(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+4))
|
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+bonusBoundary)*3 + bonusBoundary*(bonusFirstCharMultiplier-1)
|
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)
|
||||||
@@ -156,9 +156,10 @@ func TestSuffixMatch(t *testing.T) {
|
|||||||
// Strip trailing white space from the string
|
// Strip trailing white space from the string
|
||||||
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz ", "baz", 6, 9,
|
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz ", "baz", 6, 9,
|
||||||
scoreMatch*3+bonusConsecutive*2)
|
scoreMatch*3+bonusConsecutive*2)
|
||||||
|
|
||||||
// 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+bonusNonWord)
|
scoreMatch*4+bonusConsecutive*2+int(bonusBoundaryWhite))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,9 +183,9 @@ func TestNormalize(t *testing.T) {
|
|||||||
input, pattern, sidx, eidx, score)
|
input, pattern, sidx, eidx, score)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
test("Só Danço Samba", "So", 0, 2, 56, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, ExactMatchNaive)
|
test("Só Danço Samba", "So", 0, 2, 62, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, ExactMatchNaive)
|
||||||
test("Só Danço Samba", "sodc", 0, 7, 89, FuzzyMatchV1, FuzzyMatchV2)
|
test("Só Danço Samba", "sodc", 0, 7, 97, FuzzyMatchV1, FuzzyMatchV2)
|
||||||
test("Danço", "danco", 0, 5, 128, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, SuffixMatch, ExactMatchNaive, EqualMatch)
|
test("Danço", "danco", 0, 5, 140, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, SuffixMatch, ExactMatchNaive, EqualMatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLongString(t *testing.T) {
|
func TestLongString(t *testing.T) {
|
||||||
|
64
src/ansi.go
64
src/ansi.go
@@ -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]
|
||||||
if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '@' {
|
switch c {
|
||||||
return i + 1
|
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ';', ':', '?':
|
||||||
|
// ok
|
||||||
|
default:
|
||||||
|
if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '@' {
|
||||||
|
return i + 1
|
||||||
|
}
|
||||||
|
return -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1
|
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
|
||||||
|
@@ -15,14 +15,14 @@ import (
|
|||||||
// testing nextAnsiEscapeSequence().
|
// testing nextAnsiEscapeSequence().
|
||||||
//
|
//
|
||||||
// References:
|
// References:
|
||||||
// - https://github.com/gnachman/iTerm2
|
// - https://github.com/gnachman/iTerm2
|
||||||
// - https://web.archive.org/web/20090204053813/http://ascii-table.com/ansi-escape-sequences.php
|
// - https://web.archive.org/web/20090204053813/http://ascii-table.com/ansi-escape-sequences.php
|
||||||
// (archived from http://ascii-table.com/ansi-escape-sequences.php)
|
// (archived from http://ascii-table.com/ansi-escape-sequences.php)
|
||||||
// - https://web.archive.org/web/20090227051140/http://ascii-table.com/ansi-escape-sequences-vt-100.php
|
// - https://web.archive.org/web/20090227051140/http://ascii-table.com/ansi-escape-sequences-vt-100.php
|
||||||
// (archived from http://ascii-table.com/ansi-escape-sequences-vt-100.php)
|
// (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)))
|
||||||
|
133
src/core.go
133
src/core.go
@@ -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 (
|
||||||
@@ -146,21 +122,25 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
|
|
||||||
// Matcher
|
// Matcher
|
||||||
forward := true
|
forward := true
|
||||||
for _, cri := range opts.Criteria[1:] {
|
withPos := false
|
||||||
if cri == byEnd {
|
for idx := len(opts.Criteria) - 1; idx > 0; idx-- {
|
||||||
|
switch opts.Criteria[idx] {
|
||||||
|
case byChunk:
|
||||||
|
withPos = true
|
||||||
|
case byEnd:
|
||||||
forward = false
|
forward = false
|
||||||
break
|
case byBegin:
|
||||||
}
|
forward = true
|
||||||
if cri == byBegin {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
patternBuilder := func(runes []rune) *Pattern {
|
patternBuilder := func(runes []rune) *Pattern {
|
||||||
return BuildPattern(
|
return BuildPattern(
|
||||||
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward,
|
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward, withPos,
|
||||||
opts.Filter == nil, opts.Nth, opts.Delimiter, runes)
|
opts.Filter == nil, opts.Nth, opts.Delimiter, runes)
|
||||||
}
|
}
|
||||||
matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox)
|
inputRevision := 0
|
||||||
|
snapshotRevision := 0
|
||||||
|
matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox, inputRevision)
|
||||||
|
|
||||||
// Filtering mode
|
// Filtering mode
|
||||||
if opts.Filter != nil {
|
if opts.Filter != nil {
|
||||||
@@ -216,35 +196,59 @@ 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
|
||||||
reading := true
|
reading := true
|
||||||
clearCache := util.Once(false)
|
|
||||||
clearSelection := util.Once(false)
|
|
||||||
ticks := 0
|
ticks := 0
|
||||||
var nextCommand *string
|
var nextCommand *string
|
||||||
|
eventBox.Watch(EvtReadNew)
|
||||||
|
total := 0
|
||||||
|
query := []rune{}
|
||||||
|
determine := func(final bool) {
|
||||||
|
if heightUnknown {
|
||||||
|
if total >= maxFit || final {
|
||||||
|
deferred = false
|
||||||
|
heightUnknown = false
|
||||||
|
terminal.startChan <- fitpad{util.Min(total, maxFit), padHeight}
|
||||||
|
}
|
||||||
|
} else if deferred {
|
||||||
|
deferred = false
|
||||||
|
terminal.startChan <- fitpad{-1, -1}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useSnapshot := false
|
||||||
|
var snapshot []*Chunk
|
||||||
|
var count int
|
||||||
restart := func(command string) {
|
restart := func(command string) {
|
||||||
reading = true
|
reading = true
|
||||||
clearCache = util.Once(true)
|
|
||||||
clearSelection = util.Once(true)
|
|
||||||
chunkList.Clear()
|
chunkList.Clear()
|
||||||
itemIndex = 0
|
itemIndex = 0
|
||||||
|
inputRevision++
|
||||||
header = make([]string, 0, opts.HeaderLines)
|
header = make([]string, 0, opts.HeaderLines)
|
||||||
go reader.restart(command)
|
go reader.restart(command)
|
||||||
}
|
}
|
||||||
eventBox.Watch(EvtReadNew)
|
|
||||||
query := []rune{}
|
|
||||||
for {
|
for {
|
||||||
delay := true
|
delay := true
|
||||||
ticks++
|
ticks++
|
||||||
input := func() []rune {
|
input := func() []rune {
|
||||||
|
reloaded := snapshotRevision != inputRevision
|
||||||
paused, input := terminal.Input()
|
paused, input := terminal.Input()
|
||||||
if !paused {
|
if reloaded && paused {
|
||||||
|
query = []rune{}
|
||||||
|
} else if !paused {
|
||||||
query = input
|
query = input
|
||||||
}
|
}
|
||||||
return query
|
return query
|
||||||
@@ -268,20 +272,35 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
} else {
|
} else {
|
||||||
reading = reading && evt == EvtReadNew
|
reading = reading && evt == EvtReadNew
|
||||||
}
|
}
|
||||||
snapshot, count := chunkList.Snapshot()
|
if useSnapshot && evt == EvtReadFin {
|
||||||
terminal.UpdateCount(count, !reading, value.(*string))
|
useSnapshot = false
|
||||||
|
}
|
||||||
|
if !useSnapshot {
|
||||||
|
snapshot, count = chunkList.Snapshot()
|
||||||
|
snapshotRevision = inputRevision
|
||||||
|
}
|
||||||
|
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, snapshotRevision))
|
||||||
}
|
}
|
||||||
matcher.Reset(snapshot, input(), false, !reading, sort, clearCache())
|
if heightUnknown && !deferred {
|
||||||
|
determine(!reading)
|
||||||
|
}
|
||||||
|
matcher.Reset(snapshot, input(), false, !reading, sort, snapshotRevision)
|
||||||
|
|
||||||
case EvtSearchNew:
|
case EvtSearchNew:
|
||||||
var command *string
|
var command *string
|
||||||
|
var changed bool
|
||||||
switch val := value.(type) {
|
switch val := value.(type) {
|
||||||
case searchRequest:
|
case searchRequest:
|
||||||
sort = val.sort
|
sort = val.sort
|
||||||
command = val.command
|
command = val.command
|
||||||
|
changed = val.changed
|
||||||
|
if command != nil {
|
||||||
|
useSnapshot = val.sync
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if command != nil {
|
if command != nil {
|
||||||
if reading {
|
if reading {
|
||||||
@@ -290,10 +309,20 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
} else {
|
} else {
|
||||||
restart(*command)
|
restart(*command)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if !changed {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
snapshot, _ := chunkList.Snapshot()
|
if !useSnapshot {
|
||||||
matcher.Reset(snapshot, input(), true, !reading, sort, clearCache())
|
newSnapshot, _ := chunkList.Snapshot()
|
||||||
|
// We want to avoid showing empty list when reload is triggered
|
||||||
|
// and the query string is changed at the same time i.e. command != nil && changed
|
||||||
|
if command == nil || len(newSnapshot) > 0 {
|
||||||
|
snapshot = newSnapshot
|
||||||
|
snapshotRevision = inputRevision
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matcher.Reset(snapshot, input(), true, !reading, sort, snapshotRevision)
|
||||||
delay = false
|
delay = false
|
||||||
|
|
||||||
case EvtSearchProgress:
|
case EvtSearchProgress:
|
||||||
@@ -313,8 +342,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 {
|
||||||
@@ -331,11 +359,10 @@ 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,11 +12,11 @@ import (
|
|||||||
|
|
||||||
// MatchRequest represents a search request
|
// MatchRequest represents a search request
|
||||||
type MatchRequest struct {
|
type MatchRequest struct {
|
||||||
chunks []*Chunk
|
chunks []*Chunk
|
||||||
pattern *Pattern
|
pattern *Pattern
|
||||||
final bool
|
final bool
|
||||||
sort bool
|
sort bool
|
||||||
clearCache bool
|
revision int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Matcher is responsible for performing search
|
// Matcher is responsible for performing search
|
||||||
@@ -29,6 +29,7 @@ type Matcher struct {
|
|||||||
partitions int
|
partitions int
|
||||||
slab []*util.Slab
|
slab []*util.Slab
|
||||||
mergerCache map[string]*Merger
|
mergerCache map[string]*Merger
|
||||||
|
revision int
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -38,7 +39,7 @@ const (
|
|||||||
|
|
||||||
// NewMatcher returns a new Matcher
|
// NewMatcher returns a new Matcher
|
||||||
func NewMatcher(patternBuilder func([]rune) *Pattern,
|
func NewMatcher(patternBuilder func([]rune) *Pattern,
|
||||||
sort bool, tac bool, eventBox *util.EventBox) *Matcher {
|
sort bool, tac bool, eventBox *util.EventBox, revision int) *Matcher {
|
||||||
partitions := util.Min(numPartitionsMultiplier*runtime.NumCPU(), maxPartitions)
|
partitions := util.Min(numPartitionsMultiplier*runtime.NumCPU(), maxPartitions)
|
||||||
return &Matcher{
|
return &Matcher{
|
||||||
patternBuilder: patternBuilder,
|
patternBuilder: patternBuilder,
|
||||||
@@ -48,7 +49,8 @@ func NewMatcher(patternBuilder func([]rune) *Pattern,
|
|||||||
reqBox: util.NewEventBox(),
|
reqBox: util.NewEventBox(),
|
||||||
partitions: partitions,
|
partitions: partitions,
|
||||||
slab: make([]*util.Slab, partitions),
|
slab: make([]*util.Slab, partitions),
|
||||||
mergerCache: make(map[string]*Merger)}
|
mergerCache: make(map[string]*Merger),
|
||||||
|
revision: revision}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop puts Matcher in action
|
// Loop puts Matcher in action
|
||||||
@@ -70,8 +72,9 @@ func (m *Matcher) Loop() {
|
|||||||
events.Clear()
|
events.Clear()
|
||||||
})
|
})
|
||||||
|
|
||||||
if request.sort != m.sort || request.clearCache {
|
if request.sort != m.sort || request.revision != m.revision {
|
||||||
m.sort = request.sort
|
m.sort = request.sort
|
||||||
|
m.revision = request.revision
|
||||||
m.mergerCache = make(map[string]*Merger)
|
m.mergerCache = make(map[string]*Merger)
|
||||||
clearChunkCache()
|
clearChunkCache()
|
||||||
}
|
}
|
||||||
@@ -140,11 +143,11 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
|||||||
|
|
||||||
numChunks := len(request.chunks)
|
numChunks := len(request.chunks)
|
||||||
if numChunks == 0 {
|
if numChunks == 0 {
|
||||||
return EmptyMerger, false
|
return EmptyMerger(request.revision), false
|
||||||
}
|
}
|
||||||
pattern := request.pattern
|
pattern := request.pattern
|
||||||
if pattern.IsEmpty() {
|
if pattern.IsEmpty() {
|
||||||
return PassMerger(&request.chunks, m.tac), false
|
return PassMerger(&request.chunks, m.tac, request.revision), false
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelled := util.NewAtomicBool(false)
|
cancelled := util.NewAtomicBool(false)
|
||||||
@@ -218,11 +221,11 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
|||||||
partialResult := <-resultChan
|
partialResult := <-resultChan
|
||||||
partialResults[partialResult.index] = partialResult.matches
|
partialResults[partialResult.index] = partialResult.matches
|
||||||
}
|
}
|
||||||
return NewMerger(pattern, partialResults, m.sort, m.tac), false
|
return NewMerger(pattern, partialResults, m.sort, m.tac, request.revision), false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset is called to interrupt/signal the ongoing search
|
// Reset is called to interrupt/signal the ongoing search
|
||||||
func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final bool, sort bool, clearCache bool) {
|
func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final bool, sort bool, revision int) {
|
||||||
pattern := m.patternBuilder(patternRunes)
|
pattern := m.patternBuilder(patternRunes)
|
||||||
|
|
||||||
var event util.EventType
|
var event util.EventType
|
||||||
@@ -231,5 +234,5 @@ func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final
|
|||||||
} else {
|
} else {
|
||||||
event = reqRetry
|
event = reqRetry
|
||||||
}
|
}
|
||||||
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable, clearCache})
|
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable, revision})
|
||||||
}
|
}
|
||||||
|
@@ -3,30 +3,36 @@ package fzf
|
|||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
// EmptyMerger is a Merger with no data
|
// EmptyMerger is a Merger with no data
|
||||||
var EmptyMerger = NewMerger(nil, [][]Result{}, false, false)
|
func EmptyMerger(revision int) *Merger {
|
||||||
|
return NewMerger(nil, [][]Result{}, false, false, revision)
|
||||||
|
}
|
||||||
|
|
||||||
// Merger holds a set of locally sorted lists of items and provides the view of
|
// Merger holds a set of locally sorted lists of items and provides the view of
|
||||||
// a single, globally-sorted list
|
// a single, globally-sorted list
|
||||||
type Merger struct {
|
type Merger struct {
|
||||||
pattern *Pattern
|
pattern *Pattern
|
||||||
lists [][]Result
|
lists [][]Result
|
||||||
merged []Result
|
merged []Result
|
||||||
chunks *[]*Chunk
|
chunks *[]*Chunk
|
||||||
cursors []int
|
cursors []int
|
||||||
sorted bool
|
sorted bool
|
||||||
tac bool
|
tac bool
|
||||||
final bool
|
final bool
|
||||||
count int
|
count int
|
||||||
|
pass bool
|
||||||
|
revision int
|
||||||
}
|
}
|
||||||
|
|
||||||
// PassMerger returns a new Merger that simply returns the items in the
|
// PassMerger returns a new Merger that simply returns the items in the
|
||||||
// original order
|
// original order
|
||||||
func PassMerger(chunks *[]*Chunk, tac bool) *Merger {
|
func PassMerger(chunks *[]*Chunk, tac bool, revision int) *Merger {
|
||||||
mg := Merger{
|
mg := Merger{
|
||||||
pattern: nil,
|
pattern: nil,
|
||||||
chunks: chunks,
|
chunks: chunks,
|
||||||
tac: tac,
|
tac: tac,
|
||||||
count: 0}
|
count: 0,
|
||||||
|
pass: true,
|
||||||
|
revision: revision}
|
||||||
|
|
||||||
for _, chunk := range *mg.chunks {
|
for _, chunk := range *mg.chunks {
|
||||||
mg.count += chunk.count
|
mg.count += chunk.count
|
||||||
@@ -35,17 +41,18 @@ func PassMerger(chunks *[]*Chunk, tac bool) *Merger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewMerger returns a new Merger
|
// NewMerger returns a new Merger
|
||||||
func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool) *Merger {
|
func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool, revision int) *Merger {
|
||||||
mg := Merger{
|
mg := Merger{
|
||||||
pattern: pattern,
|
pattern: pattern,
|
||||||
lists: lists,
|
lists: lists,
|
||||||
merged: []Result{},
|
merged: []Result{},
|
||||||
chunks: nil,
|
chunks: nil,
|
||||||
cursors: make([]int, len(lists)),
|
cursors: make([]int, len(lists)),
|
||||||
sorted: sorted,
|
sorted: sorted,
|
||||||
tac: tac,
|
tac: tac,
|
||||||
final: false,
|
final: false,
|
||||||
count: 0}
|
count: 0,
|
||||||
|
revision: revision}
|
||||||
|
|
||||||
for _, list := range mg.lists {
|
for _, list := range mg.lists {
|
||||||
mg.count += len(list)
|
mg.count += len(list)
|
||||||
@@ -53,11 +60,42 @@ func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool) *Merge
|
|||||||
return &mg
|
return &mg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Revision returns revision number
|
||||||
|
func (mg *Merger) Revision() int {
|
||||||
|
return mg.revision
|
||||||
|
}
|
||||||
|
|
||||||
// Length returns the number of items
|
// Length returns the number of items
|
||||||
func (mg *Merger) Length() int {
|
func (mg *Merger) Length() int {
|
||||||
return mg.count
|
return mg.count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mg *Merger) First() Result {
|
||||||
|
if mg.tac && !mg.sorted {
|
||||||
|
return mg.Get(mg.count - 1)
|
||||||
|
}
|
||||||
|
return mg.Get(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindIndex returns the index of the item with the given item index
|
||||||
|
func (mg *Merger) FindIndex(itemIndex int32) int {
|
||||||
|
index := -1
|
||||||
|
if mg.pass {
|
||||||
|
index = int(itemIndex)
|
||||||
|
if mg.tac {
|
||||||
|
index = mg.count - index - 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i := 0; i < mg.count; i++ {
|
||||||
|
if mg.Get(i).item.Index() == itemIndex {
|
||||||
|
index = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
// Get returns the pointer to the Result object indexed by the given integer
|
// Get returns the pointer to the Result object indexed by the given integer
|
||||||
func (mg *Merger) Get(idx int) Result {
|
func (mg *Merger) Get(idx int) Result {
|
||||||
if mg.chunks != nil {
|
if mg.chunks != nil {
|
||||||
|
@@ -23,10 +23,10 @@ func randResult() Result {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEmptyMerger(t *testing.T) {
|
func TestEmptyMerger(t *testing.T) {
|
||||||
assert(t, EmptyMerger.Length() == 0, "Not empty")
|
assert(t, EmptyMerger(0).Length() == 0, "Not empty")
|
||||||
assert(t, EmptyMerger.count == 0, "Invalid count")
|
assert(t, EmptyMerger(0).count == 0, "Invalid count")
|
||||||
assert(t, len(EmptyMerger.lists) == 0, "Invalid lists")
|
assert(t, len(EmptyMerger(0).lists) == 0, "Invalid lists")
|
||||||
assert(t, len(EmptyMerger.merged) == 0, "Invalid merged list")
|
assert(t, len(EmptyMerger(0).merged) == 0, "Invalid merged list")
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildLists(partiallySorted bool) ([][]Result, []Result) {
|
func buildLists(partiallySorted bool) ([][]Result, []Result) {
|
||||||
@@ -57,7 +57,7 @@ func TestMergerUnsorted(t *testing.T) {
|
|||||||
cnt := len(items)
|
cnt := len(items)
|
||||||
|
|
||||||
// Not sorted: same order
|
// Not sorted: same order
|
||||||
mg := NewMerger(nil, lists, false, false)
|
mg := NewMerger(nil, lists, false, false, 0)
|
||||||
assert(t, cnt == mg.Length(), "Invalid Length")
|
assert(t, cnt == mg.Length(), "Invalid Length")
|
||||||
for i := 0; i < cnt; i++ {
|
for i := 0; i < cnt; i++ {
|
||||||
assert(t, items[i] == mg.Get(i), "Invalid Get")
|
assert(t, items[i] == mg.Get(i), "Invalid Get")
|
||||||
@@ -69,7 +69,7 @@ func TestMergerSorted(t *testing.T) {
|
|||||||
cnt := len(items)
|
cnt := len(items)
|
||||||
|
|
||||||
// Sorted sorted order
|
// Sorted sorted order
|
||||||
mg := NewMerger(nil, lists, true, false)
|
mg := NewMerger(nil, lists, true, false, 0)
|
||||||
assert(t, cnt == mg.Length(), "Invalid Length")
|
assert(t, cnt == mg.Length(), "Invalid Length")
|
||||||
sort.Sort(ByRelevance(items))
|
sort.Sort(ByRelevance(items))
|
||||||
for i := 0; i < cnt; i++ {
|
for i := 0; i < cnt; i++ {
|
||||||
@@ -79,7 +79,7 @@ func TestMergerSorted(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Inverse order
|
// Inverse order
|
||||||
mg2 := NewMerger(nil, lists, true, false)
|
mg2 := NewMerger(nil, lists, true, false, 0)
|
||||||
for i := cnt - 1; i >= 0; i-- {
|
for i := cnt - 1; i >= 0; i-- {
|
||||||
if items[i] != mg2.Get(i) {
|
if items[i] != mg2.Get(i) {
|
||||||
t.Error("Not sorted", items[i], mg2.Get(i))
|
t.Error("Not sorted", items[i], mg2.Get(i))
|
||||||
|
1332
src/options.go
1332
src/options.go
File diff suppressed because it is too large
Load Diff
@@ -65,6 +65,19 @@ func TestDelimiterRegexRegex(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDelimiterRegexRegexCaret(t *testing.T) {
|
||||||
|
delim := delimiterRegexp(`(^\s*|\s+)`)
|
||||||
|
tokens := Tokenize("foo bar baz", delim)
|
||||||
|
if delim.str != nil ||
|
||||||
|
len(tokens) != 4 ||
|
||||||
|
tokens[0].text.ToString() != "" ||
|
||||||
|
tokens[1].text.ToString() != "foo " ||
|
||||||
|
tokens[2].text.ToString() != "bar " ||
|
||||||
|
tokens[3].text.ToString() != "baz" {
|
||||||
|
t.Errorf("%s %d", tokens, len(tokens))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSplitNth(t *testing.T) {
|
func TestSplitNth(t *testing.T) {
|
||||||
{
|
{
|
||||||
ranges := splitNth("..")
|
ranges := splitNth("..")
|
||||||
@@ -249,13 +262,17 @@ func TestBind(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
check(tui.CtrlA.AsEvent(), "", actBeginningOfLine)
|
check(tui.CtrlA.AsEvent(), "", actBeginningOfLine)
|
||||||
|
errorString := ""
|
||||||
|
errorFn := func(e string) {
|
||||||
|
errorString = e
|
||||||
|
}
|
||||||
parseKeymap(keymap,
|
parseKeymap(keymap,
|
||||||
"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+
|
"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+
|
||||||
"f1:execute(ls {+})+abort+execute(echo {+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
|
"f1:execute(ls {+})+abort+execute(echo \n{+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
|
||||||
"alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+
|
"alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+
|
||||||
"x:Execute(foo+bar),X:execute/bar+baz/"+
|
"x:Execute(foo+bar),X:execute/bar+baz/"+
|
||||||
",f1:+first,f1:+top"+
|
",f1:+first,f1:+top"+
|
||||||
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up")
|
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up", errorFn)
|
||||||
check(tui.CtrlA.AsEvent(), "", actKillLine)
|
check(tui.CtrlA.AsEvent(), "", actKillLine)
|
||||||
check(tui.CtrlB.AsEvent(), "", actToggleSort, actUp, actDown)
|
check(tui.CtrlB.AsEvent(), "", actToggleSort, actUp, actDown)
|
||||||
check(tui.Key('c'), "", actPageUp)
|
check(tui.Key('c'), "", actPageUp)
|
||||||
@@ -273,12 +290,15 @@ func TestBind(t *testing.T) {
|
|||||||
check(tui.Key('+'), "++\nfoobar,Y:execute(baz)+up", actExecute)
|
check(tui.Key('+'), "++\nfoobar,Y:execute(baz)+up", actExecute)
|
||||||
|
|
||||||
for idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} {
|
for idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} {
|
||||||
parseKeymap(keymap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char))
|
parseKeymap(keymap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char), errorFn)
|
||||||
check(tui.Key([]rune(fmt.Sprintf("%d", idx%10))[0]), "foobar", actExecute)
|
check(tui.Key([]rune(fmt.Sprintf("%d", idx%10))[0]), "foobar", actExecute)
|
||||||
}
|
}
|
||||||
|
|
||||||
parseKeymap(keymap, "f1:abort")
|
parseKeymap(keymap, "f1:abort", errorFn)
|
||||||
check(tui.F1.AsEvent(), "", actAbort)
|
check(tui.F1.AsEvent(), "", actAbort)
|
||||||
|
if len(errorString) > 0 {
|
||||||
|
t.Errorf("error parsing keymap: %s", errorString)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestColorSpec(t *testing.T) {
|
func TestColorSpec(t *testing.T) {
|
||||||
@@ -341,10 +361,10 @@ func TestDefaultCtrlNP(t *testing.T) {
|
|||||||
f.Close()
|
f.Close()
|
||||||
hist := "--history=" + f.Name()
|
hist := "--history=" + f.Name()
|
||||||
check([]string{hist}, tui.CtrlN, actNextHistory)
|
check([]string{hist}, tui.CtrlN, actNextHistory)
|
||||||
check([]string{hist}, tui.CtrlP, actPreviousHistory)
|
check([]string{hist}, tui.CtrlP, actPrevHistory)
|
||||||
|
|
||||||
check([]string{hist, "--bind=ctrl-n:accept"}, tui.CtrlN, actAccept)
|
check([]string{hist, "--bind=ctrl-n:accept"}, tui.CtrlN, actAccept)
|
||||||
check([]string{hist, "--bind=ctrl-n:accept"}, tui.CtrlP, actPreviousHistory)
|
check([]string{hist, "--bind=ctrl-n:accept"}, tui.CtrlP, actPrevHistory)
|
||||||
|
|
||||||
check([]string{hist, "--bind=ctrl-p:accept"}, tui.CtrlN, actNextHistory)
|
check([]string{hist, "--bind=ctrl-p:accept"}, tui.CtrlN, actNextHistory)
|
||||||
check([]string{hist, "--bind=ctrl-p:accept"}, tui.CtrlP, actAccept)
|
check([]string{hist, "--bind=ctrl-p:accept"}, tui.CtrlP, actAccept)
|
||||||
@@ -440,8 +460,6 @@ func TestValidateSign(t *testing.T) {
|
|||||||
{"😀", true},
|
{"😀", true},
|
||||||
{"", false},
|
{"", false},
|
||||||
{">>>", false},
|
{">>>", false},
|
||||||
{"\n", false},
|
|
||||||
{"\t", false},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
@@ -455,3 +473,38 @@ func TestValidateSign(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseSingleActionList(t *testing.T) {
|
||||||
|
actions := parseSingleActionList("Execute@foo+bar,baz@+up+up+reload:down+down", func(string) {})
|
||||||
|
if len(actions) != 4 {
|
||||||
|
t.Errorf("Invalid number of actions parsed:%d", len(actions))
|
||||||
|
}
|
||||||
|
if actions[0].t != actExecute || actions[0].a != "foo+bar,baz" {
|
||||||
|
t.Errorf("Invalid action parsed: %v", actions[0])
|
||||||
|
}
|
||||||
|
if actions[1].t != actUp || actions[2].t != actUp {
|
||||||
|
t.Errorf("Invalid action parsed: %v / %v", actions[1], actions[2])
|
||||||
|
}
|
||||||
|
if actions[3].t != actReload || actions[3].a != "down+down" {
|
||||||
|
t.Errorf("Invalid action parsed: %v", actions[3])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseSingleActionListError(t *testing.T) {
|
||||||
|
err := ""
|
||||||
|
parseSingleActionList("change-query(foobar)baz", func(e string) {
|
||||||
|
err = e
|
||||||
|
})
|
||||||
|
if len(err) == 0 {
|
||||||
|
t.Errorf("Failed to detect error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMaskActionContents(t *testing.T) {
|
||||||
|
original := ":execute((f)(o)(o)(b)(a)(r))+change-query@qu@ry@+up,x:reload:hello:world"
|
||||||
|
expected := ":execute +change-query +up,x:reload "
|
||||||
|
masked := maskActionContents(original)
|
||||||
|
if masked != expected {
|
||||||
|
t.Errorf("Not masked: %s", masked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -51,6 +51,7 @@ type Pattern struct {
|
|||||||
caseSensitive bool
|
caseSensitive bool
|
||||||
normalize bool
|
normalize bool
|
||||||
forward bool
|
forward bool
|
||||||
|
withPos bool
|
||||||
text []rune
|
text []rune
|
||||||
termSets []termSet
|
termSets []termSet
|
||||||
sortable bool
|
sortable bool
|
||||||
@@ -85,7 +86,7 @@ func clearChunkCache() {
|
|||||||
|
|
||||||
// BuildPattern builds Pattern object from the given arguments
|
// BuildPattern builds Pattern object from the given arguments
|
||||||
func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool,
|
func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool,
|
||||||
cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
|
withPos bool, cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
|
||||||
|
|
||||||
var asString string
|
var asString string
|
||||||
if extended {
|
if extended {
|
||||||
@@ -145,6 +146,7 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
|||||||
caseSensitive: caseSensitive,
|
caseSensitive: caseSensitive,
|
||||||
normalize: normalize,
|
normalize: normalize,
|
||||||
forward: forward,
|
forward: forward,
|
||||||
|
withPos: withPos,
|
||||||
text: []rune(asString),
|
text: []rune(asString),
|
||||||
termSets: termSets,
|
termSets: termSets,
|
||||||
sortable: sortable,
|
sortable: sortable,
|
||||||
@@ -302,13 +304,13 @@ func (p *Pattern) matchChunk(chunk *Chunk, space []Result, slab *util.Slab) []Re
|
|||||||
|
|
||||||
if space == nil {
|
if space == nil {
|
||||||
for idx := 0; idx < chunk.count; idx++ {
|
for idx := 0; idx < chunk.count; idx++ {
|
||||||
if match, _, _ := p.MatchItem(&chunk.items[idx], false, slab); match != nil {
|
if match, _, _ := p.MatchItem(&chunk.items[idx], p.withPos, slab); match != nil {
|
||||||
matches = append(matches, *match)
|
matches = append(matches, *match)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, result := range space {
|
for _, result := range space {
|
||||||
if match, _, _ := p.MatchItem(result.item, false, slab); match != nil {
|
if match, _, _ := p.MatchItem(result.item, p.withPos, slab); match != nil {
|
||||||
matches = append(matches, *match)
|
matches = append(matches, *match)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -67,7 +67,7 @@ func TestParseTermsEmpty(t *testing.T) {
|
|||||||
func TestExact(t *testing.T) {
|
func TestExact(t *testing.T) {
|
||||||
defer clearPatternCache()
|
defer clearPatternCache()
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true,
|
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true,
|
||||||
[]Range{}, Delimiter{}, []rune("'abc"))
|
[]Range{}, Delimiter{}, []rune("'abc"))
|
||||||
chars := util.ToChars([]byte("aabbcc abc"))
|
chars := util.ToChars([]byte("aabbcc abc"))
|
||||||
res, pos := algo.ExactMatchNaive(
|
res, pos := algo.ExactMatchNaive(
|
||||||
@@ -83,7 +83,7 @@ func TestExact(t *testing.T) {
|
|||||||
func TestEqual(t *testing.T) {
|
func TestEqual(t *testing.T) {
|
||||||
defer clearPatternCache()
|
defer clearPatternCache()
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("^AbC$"))
|
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("^AbC$"))
|
||||||
|
|
||||||
match := func(str string, sidxExpected int, eidxExpected int) {
|
match := func(str string, sidxExpected int, eidxExpected int) {
|
||||||
chars := util.ToChars([]byte(str))
|
chars := util.ToChars([]byte(str))
|
||||||
@@ -106,17 +106,17 @@ func TestEqual(t *testing.T) {
|
|||||||
func TestCaseSensitivity(t *testing.T) {
|
func TestCaseSensitivity(t *testing.T) {
|
||||||
defer clearPatternCache()
|
defer clearPatternCache()
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pat1 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("abc"))
|
pat1 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("abc"))
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pat2 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("Abc"))
|
pat2 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc"))
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pat3 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, true, []Range{}, Delimiter{}, []rune("abc"))
|
pat3 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, false, true, []Range{}, Delimiter{}, []rune("abc"))
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pat4 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, true, []Range{}, Delimiter{}, []rune("Abc"))
|
pat4 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc"))
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pat5 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, true, []Range{}, Delimiter{}, []rune("abc"))
|
pat5 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, false, true, []Range{}, Delimiter{}, []rune("abc"))
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pat6 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, true, []Range{}, Delimiter{}, []rune("Abc"))
|
pat6 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc"))
|
||||||
|
|
||||||
if string(pat1.text) != "abc" || pat1.caseSensitive != false ||
|
if string(pat1.text) != "abc" || pat1.caseSensitive != false ||
|
||||||
string(pat2.text) != "Abc" || pat2.caseSensitive != true ||
|
string(pat2.text) != "Abc" || pat2.caseSensitive != true ||
|
||||||
@@ -129,7 +129,7 @@ func TestCaseSensitivity(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOrigTextAndTransformed(t *testing.T) {
|
func TestOrigTextAndTransformed(t *testing.T) {
|
||||||
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("jg"))
|
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("jg"))
|
||||||
tokens := Tokenize("junegunn", Delimiter{})
|
tokens := Tokenize("junegunn", Delimiter{})
|
||||||
trans := Transform(tokens, []Range{{1, 1}})
|
trans := Transform(tokens, []Range{{1, 1}})
|
||||||
|
|
||||||
@@ -164,7 +164,7 @@ func TestOrigTextAndTransformed(t *testing.T) {
|
|||||||
func TestCacheKey(t *testing.T) {
|
func TestCacheKey(t *testing.T) {
|
||||||
test := func(extended bool, patStr string, expected string, cacheable bool) {
|
test := func(extended bool, patStr string, expected string, cacheable bool) {
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pat := BuildPattern(true, algo.FuzzyMatchV2, extended, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune(patStr))
|
pat := BuildPattern(true, algo.FuzzyMatchV2, extended, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune(patStr))
|
||||||
if pat.CacheKey() != expected {
|
if pat.CacheKey() != expected {
|
||||||
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
|
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
|
||||||
}
|
}
|
||||||
@@ -188,7 +188,7 @@ func TestCacheKey(t *testing.T) {
|
|||||||
func TestCacheable(t *testing.T) {
|
func TestCacheable(t *testing.T) {
|
||||||
test := func(fuzzy bool, str string, expected string, cacheable bool) {
|
test := func(fuzzy bool, str string, expected string, cacheable bool) {
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pat := BuildPattern(fuzzy, algo.FuzzyMatchV2, true, CaseSmart, true, true, true, []Range{}, Delimiter{}, []rune(str))
|
pat := BuildPattern(fuzzy, algo.FuzzyMatchV2, true, CaseSmart, true, true, false, true, []Range{}, Delimiter{}, []rune(str))
|
||||||
if pat.CacheKey() != expected {
|
if pat.CacheKey() != expected {
|
||||||
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
|
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
// +build !openbsd
|
//go:build !openbsd
|
||||||
|
|
||||||
package protector
|
package protector
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
// +build openbsd
|
//go:build openbsd
|
||||||
|
|
||||||
package protector
|
package protector
|
||||||
|
|
||||||
|
@@ -49,6 +49,22 @@ func buildResult(item *Item, offsets []Offset, score int) Result {
|
|||||||
case byScore:
|
case byScore:
|
||||||
// Higher is better
|
// Higher is better
|
||||||
val = math.MaxUint16 - util.AsUint16(score)
|
val = math.MaxUint16 - util.AsUint16(score)
|
||||||
|
case byChunk:
|
||||||
|
if validOffsetFound {
|
||||||
|
b := minBegin
|
||||||
|
e := maxEnd
|
||||||
|
for ; b >= 1; b-- {
|
||||||
|
if unicode.IsSpace(item.text.Get(b - 1)) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ; e < numChars; e++ {
|
||||||
|
if unicode.IsSpace(item.text.Get(e)) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val = util.AsUint16(e - b)
|
||||||
|
}
|
||||||
case byLength:
|
case byLength:
|
||||||
val = item.TrimLength()
|
val = item.TrimLength()
|
||||||
case byBegin, byEnd:
|
case byBegin, byEnd:
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
// +build !386,!amd64
|
//go:build !386 && !amd64
|
||||||
|
|
||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
|
@@ -1,5 +1,3 @@
|
|||||||
// +build !tcell
|
|
||||||
|
|
||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -56,9 +54,9 @@ func TestResultRank(t *testing.T) {
|
|||||||
// FIXME global
|
// FIXME global
|
||||||
sortCriteria = []criterion{byScore, byLength}
|
sortCriteria = []criterion{byScore, byLength}
|
||||||
|
|
||||||
strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")}
|
str := []rune("foo")
|
||||||
item1 := buildResult(
|
item1 := buildResult(
|
||||||
withIndex(&Item{text: util.RunesToChars(strs[0])}, 1), []Offset{}, 2)
|
withIndex(&Item{text: util.RunesToChars(str)}, 1), []Offset{}, 2)
|
||||||
if item1.points[3] != math.MaxUint16-2 || // Bonus
|
if item1.points[3] != math.MaxUint16-2 || // Bonus
|
||||||
item1.points[2] != 3 || // Length
|
item1.points[2] != 3 || // Length
|
||||||
item1.points[1] != 0 || // Unused
|
item1.points[1] != 0 || // Unused
|
||||||
@@ -67,7 +65,7 @@ func TestResultRank(t *testing.T) {
|
|||||||
t.Error(item1)
|
t.Error(item1)
|
||||||
}
|
}
|
||||||
// Only differ in index
|
// Only differ in index
|
||||||
item2 := buildResult(&Item{text: util.RunesToChars(strs[0])}, []Offset{}, 2)
|
item2 := buildResult(&Item{text: util.RunesToChars(str)}, []Offset{}, 2)
|
||||||
|
|
||||||
items := []Result{item1, item2}
|
items := []Result{item1, item2}
|
||||||
sort.Sort(ByRelevance(items))
|
sort.Sort(ByRelevance(items))
|
||||||
@@ -100,6 +98,23 @@ func TestResultRank(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestChunkTiebreak(t *testing.T) {
|
||||||
|
// FIXME global
|
||||||
|
sortCriteria = []criterion{byScore, byChunk}
|
||||||
|
|
||||||
|
score := 100
|
||||||
|
test := func(input string, offset Offset, chunk string) {
|
||||||
|
item := buildResult(withIndex(&Item{text: util.RunesToChars([]rune(input))}, 1), []Offset{offset}, score)
|
||||||
|
if !(item.points[3] == math.MaxUint16-uint16(score) && item.points[2] == uint16(len(chunk))) {
|
||||||
|
t.Error(item.points)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
test("hello foobar goodbye", Offset{8, 9}, "foobar")
|
||||||
|
test("hello foobar goodbye", Offset{7, 18}, "foobar goodbye")
|
||||||
|
test("hello foobar goodbye", Offset{0, 1}, "hello")
|
||||||
|
test("hello foobar goodbye", Offset{5, 7}, "hello foobar") // TBD
|
||||||
|
}
|
||||||
|
|
||||||
func TestColorOffset(t *testing.T) {
|
func TestColorOffset(t *testing.T) {
|
||||||
// ------------ 20 ---- -- ----
|
// ------------ 20 ---- -- ----
|
||||||
// ++++++++ ++++++++++
|
// ++++++++ ++++++++++
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
// +build 386 amd64
|
//go:build 386 || amd64
|
||||||
|
|
||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
|
138
src/server.go
Normal file
138
src/server.go
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
package fzf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
crlf = "\r\n"
|
||||||
|
httpOk = "HTTP/1.1 200 OK" + crlf
|
||||||
|
httpBadRequest = "HTTP/1.1 400 Bad Request" + crlf
|
||||||
|
httpReadTimeout = 10 * time.Second
|
||||||
|
maxContentLength = 1024 * 1024
|
||||||
|
)
|
||||||
|
|
||||||
|
func startHttpServer(port int, channel chan []*action) (error, int) {
|
||||||
|
if port < 0 {
|
||||||
|
return nil, port
|
||||||
|
}
|
||||||
|
|
||||||
|
listener, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", port))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("port not available: %d", port), port
|
||||||
|
}
|
||||||
|
if port == 0 {
|
||||||
|
addr := listener.Addr().String()
|
||||||
|
parts := strings.SplitN(addr, ":", 2)
|
||||||
|
if len(parts) < 2 {
|
||||||
|
return fmt.Errorf("cannot extract port: %s", addr), port
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
port, err = strconv.Atoi(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return err, port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, net.ErrClosed) {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conn.Write([]byte(handleHttpRequest(conn, channel)))
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
listener.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil, port
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here we are writing a simplistic HTTP server without using net/http
|
||||||
|
// package to reduce the size of the binary.
|
||||||
|
//
|
||||||
|
// * No --listen: 2.8MB
|
||||||
|
// * --listen with net/http: 5.7MB
|
||||||
|
// * --listen w/o net/http: 3.3MB
|
||||||
|
func handleHttpRequest(conn net.Conn, channel chan []*action) string {
|
||||||
|
contentLength := 0
|
||||||
|
body := ""
|
||||||
|
bad := func(message string) string {
|
||||||
|
message += "\n"
|
||||||
|
return httpBadRequest + fmt.Sprintf("Content-Length: %d%s", len(message), crlf+crlf+message)
|
||||||
|
}
|
||||||
|
conn.SetReadDeadline(time.Now().Add(httpReadTimeout))
|
||||||
|
scanner := bufio.NewScanner(conn)
|
||||||
|
scanner.Split(func(data []byte, atEOF bool) (int, []byte, error) {
|
||||||
|
found := bytes.Index(data, []byte(crlf))
|
||||||
|
if found >= 0 {
|
||||||
|
token := data[:found+len(crlf)]
|
||||||
|
return len(token), token, nil
|
||||||
|
}
|
||||||
|
if atEOF || len(body)+len(data) >= contentLength {
|
||||||
|
return 0, data, bufio.ErrFinalToken
|
||||||
|
}
|
||||||
|
return 0, nil, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
section := 0
|
||||||
|
for scanner.Scan() {
|
||||||
|
text := scanner.Text()
|
||||||
|
switch section {
|
||||||
|
case 0:
|
||||||
|
if !strings.HasPrefix(text, "POST / HTTP") {
|
||||||
|
return bad("invalid request method")
|
||||||
|
}
|
||||||
|
section++
|
||||||
|
case 1:
|
||||||
|
if text == crlf {
|
||||||
|
if contentLength == 0 {
|
||||||
|
return bad("content-length header missing")
|
||||||
|
}
|
||||||
|
section++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pair := strings.SplitN(text, ":", 2)
|
||||||
|
if len(pair) == 2 && strings.ToLower(pair[0]) == "content-length" {
|
||||||
|
length, err := strconv.Atoi(strings.TrimSpace(pair[1]))
|
||||||
|
if err != nil || length <= 0 || length > maxContentLength {
|
||||||
|
return bad("invalid content length")
|
||||||
|
}
|
||||||
|
contentLength = length
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
body += text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(body) < contentLength {
|
||||||
|
return bad("incomplete request")
|
||||||
|
}
|
||||||
|
body = body[:contentLength]
|
||||||
|
|
||||||
|
errorMessage := ""
|
||||||
|
actions := parseSingleActionList(strings.Trim(string(body), "\r\n"), func(message string) {
|
||||||
|
errorMessage = message
|
||||||
|
})
|
||||||
|
if len(errorMessage) > 0 {
|
||||||
|
return bad(errorMessage)
|
||||||
|
}
|
||||||
|
if len(actions) == 0 {
|
||||||
|
return bad("no action specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
channel <- actions
|
||||||
|
return httpOk
|
||||||
|
}
|
1504
src/terminal.go
1504
src/terminal.go
File diff suppressed because it is too large
Load Diff
@@ -421,10 +421,10 @@ func TestPowershellCommands(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Test typical valid placeholders and parsing of them.
|
Test typical valid placeholders and parsing of them.
|
||||||
|
|
||||||
Also since the parser assumes the input is matched with `placeholder` regex,
|
Also since the parser assumes the input is matched with `placeholder` regex,
|
||||||
the regex is tested here as well.
|
the regex is tested here as well.
|
||||||
*/
|
*/
|
||||||
func TestParsePlaceholder(t *testing.T) {
|
func TestParsePlaceholder(t *testing.T) {
|
||||||
// give, want pairs
|
// give, want pairs
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
// +build !windows
|
//go:build !windows
|
||||||
|
|
||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
// +build windows
|
//go:build windows
|
||||||
|
|
||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
|
@@ -156,14 +156,14 @@ func Tokenize(text string, delimiter Delimiter) []Token {
|
|||||||
// FIXME performance
|
// FIXME performance
|
||||||
var tokens []string
|
var tokens []string
|
||||||
if delimiter.regex != nil {
|
if delimiter.regex != nil {
|
||||||
for len(text) > 0 {
|
locs := delimiter.regex.FindAllStringIndex(text, -1)
|
||||||
loc := delimiter.regex.FindStringIndex(text)
|
begin := 0
|
||||||
if len(loc) < 2 {
|
for _, loc := range locs {
|
||||||
loc = []int{0, len(text)}
|
tokens = append(tokens, text[begin:loc[1]])
|
||||||
}
|
begin = loc[1]
|
||||||
last := util.Max(loc[1], 1)
|
}
|
||||||
tokens = append(tokens, text[:last])
|
if begin < len(text) {
|
||||||
text = text[last:]
|
tokens = append(tokens, text[begin:])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return withPrefixLengths(tokens, 0)
|
return withPrefixLengths(tokens, 0)
|
||||||
|
@@ -1,6 +1,4 @@
|
|||||||
// +build !ncurses
|
//go:build !tcell && !windows
|
||||||
// +build !tcell
|
|
||||||
// +build !windows
|
|
||||||
|
|
||||||
package tui
|
package tui
|
||||||
|
|
||||||
@@ -10,30 +8,35 @@ func HasFullscreenRenderer() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var DefaultBorderShape BorderShape = BorderRounded
|
||||||
|
|
||||||
func (a Attr) Merge(b Attr) Attr {
|
func (a Attr) Merge(b Attr) Attr {
|
||||||
return a | b
|
return a | b
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
Italic = Attr(1 << 2)
|
Italic = Attr(1 << 2)
|
||||||
Underline = Attr(1 << 3)
|
Underline = Attr(1 << 3)
|
||||||
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) Pause(bool) {}
|
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
|
||||||
func (r *FullscreenRenderer) Resume(bool, bool) {}
|
func (r *FullscreenRenderer) Pause(bool) {}
|
||||||
func (r *FullscreenRenderer) Clear() {}
|
func (r *FullscreenRenderer) Resume(bool, bool) {}
|
||||||
func (r *FullscreenRenderer) Refresh() {}
|
func (r *FullscreenRenderer) Clear() {}
|
||||||
func (r *FullscreenRenderer) Close() {}
|
func (r *FullscreenRenderer) NeedScrollbarRedraw() bool { return false }
|
||||||
|
func (r *FullscreenRenderer) Refresh() {}
|
||||||
|
func (r *FullscreenRenderer) Close() {}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) GetChar() Event { return Event{} }
|
func (r *FullscreenRenderer) GetChar() Event { return Event{} }
|
||||||
func (r *FullscreenRenderer) MaxX() int { return 0 }
|
func (r *FullscreenRenderer) MaxX() int { return 0 }
|
||||||
|
295
src/tui/light.go
295
src/tui/light.go
@@ -23,7 +23,7 @@ const (
|
|||||||
defaultEscDelay = 100
|
defaultEscDelay = 100
|
||||||
escPollInterval = 5
|
escPollInterval = 5
|
||||||
offsetPollTries = 10
|
offsetPollTries = 10
|
||||||
maxInputBuffer = 10 * 1024
|
maxInputBuffer = 1024 * 1024
|
||||||
)
|
)
|
||||||
|
|
||||||
const consoleDevice string = "/dev/tty"
|
const consoleDevice string = "/dev/tty"
|
||||||
@@ -32,20 +32,26 @@ var offsetRegexp *regexp.Regexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]
|
|||||||
var offsetRegexpBegin *regexp.Regexp = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R")
|
var offsetRegexpBegin *regexp.Regexp = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R")
|
||||||
|
|
||||||
func (r *LightRenderer) stderr(str string) {
|
func (r *LightRenderer) stderr(str string) {
|
||||||
r.stderrInternal(str, true)
|
r.stderrInternal(str, true, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Need better handling of non-displayable characters
|
const CR string = "\x1b[2m␍"
|
||||||
func (r *LightRenderer) stderrInternal(str string, allowNLCR bool) {
|
const LF string = "\x1b[2m␊"
|
||||||
|
|
||||||
|
func (r *LightRenderer) stderrInternal(str string, allowNLCR bool, resetCode string) {
|
||||||
bytes := []byte(str)
|
bytes := []byte(str)
|
||||||
runes := []rune{}
|
runes := []rune{}
|
||||||
for len(bytes) > 0 {
|
for len(bytes) > 0 {
|
||||||
r, sz := utf8.DecodeRune(bytes)
|
r, sz := utf8.DecodeRune(bytes)
|
||||||
nlcr := r == '\n' || r == '\r'
|
nlcr := r == '\n' || r == '\r'
|
||||||
if r >= 32 || r == '\x1b' || nlcr {
|
if r >= 32 || r == '\x1b' || nlcr {
|
||||||
if r == utf8.RuneError || nlcr && !allowNLCR {
|
if nlcr && !allowNLCR {
|
||||||
runes = append(runes, ' ')
|
if r == '\r' {
|
||||||
} else {
|
runes = append(runes, []rune(CR+resetCode)...)
|
||||||
|
} else {
|
||||||
|
runes = append(runes, []rune(LF+resetCode)...)
|
||||||
|
}
|
||||||
|
} else if r != utf8.RuneError {
|
||||||
runes = append(runes, r)
|
runes = append(runes, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,13 +60,15 @@ func (r *LightRenderer) stderrInternal(str string, allowNLCR bool) {
|
|||||||
r.queued.WriteString(string(runes))
|
r.queued.WriteString(string(runes))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) csi(code string) {
|
func (r *LightRenderer) csi(code string) string {
|
||||||
r.stderr("\x1b[" + code)
|
fullcode := "\x1b[" + code
|
||||||
|
r.stderr(fullcode)
|
||||||
|
return fullcode
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) flush() {
|
func (r *LightRenderer) flush() {
|
||||||
if r.queued.Len() > 0 {
|
if r.queued.Len() > 0 {
|
||||||
fmt.Fprint(os.Stderr, r.queued.String())
|
fmt.Fprint(os.Stderr, "\x1b[?25l"+r.queued.String()+"\x1b[?25h")
|
||||||
r.queued.Reset()
|
r.queued.Reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,7 +80,7 @@ type LightRenderer struct {
|
|||||||
forceBlack bool
|
forceBlack bool
|
||||||
clearOnExit bool
|
clearOnExit bool
|
||||||
prevDownTime time.Time
|
prevDownTime time.Time
|
||||||
clickY []int
|
clicks [][2]int
|
||||||
ttyin *os.File
|
ttyin *os.File
|
||||||
buffer []byte
|
buffer []byte
|
||||||
origState *term.State
|
origState *term.State
|
||||||
@@ -174,9 +182,7 @@ func (r *LightRenderer) Init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.mouse {
|
r.enableMouse()
|
||||||
r.csi("?1000h")
|
|
||||||
}
|
|
||||||
r.csi(fmt.Sprintf("%dA", r.MaxY()-1))
|
r.csi(fmt.Sprintf("%dA", r.MaxY()-1))
|
||||||
r.csi("G")
|
r.csi("G")
|
||||||
r.csi("K")
|
r.csi("K")
|
||||||
@@ -188,6 +194,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")
|
||||||
@@ -378,7 +388,7 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
return Event{Home, 0, nil}
|
return Event{Home, 0, nil}
|
||||||
case 'F':
|
case 'F':
|
||||||
return Event{End, 0, nil}
|
return Event{End, 0, nil}
|
||||||
case 'M':
|
case '<':
|
||||||
return r.mouseSequence(sz)
|
return r.mouseSequence(sz)
|
||||||
case 'P':
|
case 'P':
|
||||||
return Event{F1, 0, nil}
|
return Event{F1, 0, nil}
|
||||||
@@ -420,7 +430,19 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
}
|
}
|
||||||
return Event{Invalid, 0, nil} // INS
|
return Event{Invalid, 0, nil} // INS
|
||||||
case '3':
|
case '3':
|
||||||
return Event{Del, 0, nil}
|
if r.buffer[3] == '~' {
|
||||||
|
return Event{Del, 0, nil}
|
||||||
|
}
|
||||||
|
if len(r.buffer) == 6 && r.buffer[5] == '~' {
|
||||||
|
*sz = 6
|
||||||
|
switch r.buffer[4] {
|
||||||
|
case '5':
|
||||||
|
return Event{CtrlDelete, 0, nil}
|
||||||
|
case '2':
|
||||||
|
return Event{SDelete, 0, nil}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Event{Invalid, 0, nil}
|
||||||
case '4':
|
case '4':
|
||||||
return Event{End, 0, nil}
|
return Event{End, 0, nil}
|
||||||
case '5':
|
case '5':
|
||||||
@@ -519,47 +541,79 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
|
||||||
func (r *LightRenderer) mouseSequence(sz *int) Event {
|
func (r *LightRenderer) mouseSequence(sz *int) Event {
|
||||||
if len(r.buffer) < 6 || !r.mouse {
|
// "\e[<0;0;0M"
|
||||||
|
if len(r.buffer) < 9 || !r.mouse {
|
||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
}
|
}
|
||||||
*sz = 6
|
|
||||||
switch r.buffer[3] {
|
rest := r.buffer[*sz:]
|
||||||
case 32, 34, 36, 40, 48, // mouse-down / shift / cmd / ctrl
|
end := bytes.IndexAny(rest, "mM")
|
||||||
35, 39, 43, 51: // mouse-up / shift / cmd / ctrl
|
if end == -1 {
|
||||||
mod := r.buffer[3] >= 36
|
return Event{Invalid, 0, nil}
|
||||||
left := r.buffer[3] == 32
|
}
|
||||||
down := r.buffer[3]%2 == 0
|
|
||||||
x := int(r.buffer[4] - 33)
|
elems := strings.SplitN(string(rest[:end]), ";", 3)
|
||||||
y := int(r.buffer[5]-33) - r.yoffset
|
if len(elems) != 3 {
|
||||||
double := false
|
return Event{Invalid, 0, nil}
|
||||||
if down {
|
}
|
||||||
now := time.Now()
|
|
||||||
if !left { // Right double click is not allowed
|
t := atoi(elems[0], -1)
|
||||||
r.clickY = []int{}
|
x := atoi(elems[1], -1) - 1
|
||||||
} else if now.Sub(r.prevDownTime) < doubleClickDuration {
|
y := atoi(elems[2], -1) - 1 - r.yoffset
|
||||||
r.clickY = append(r.clickY, y)
|
if t < 0 || x < 0 {
|
||||||
} else {
|
return Event{Invalid, 0, nil}
|
||||||
r.clickY = []int{y}
|
}
|
||||||
}
|
*sz += end + 1
|
||||||
r.prevDownTime = now
|
|
||||||
|
down := rest[end] == 'M'
|
||||||
|
|
||||||
|
scroll := 0
|
||||||
|
if t >= 64 {
|
||||||
|
t -= 64
|
||||||
|
if t&0b1 == 1 {
|
||||||
|
scroll = -1
|
||||||
} else {
|
} else {
|
||||||
if len(r.clickY) > 1 && r.clickY[0] == r.clickY[1] &&
|
scroll = 1
|
||||||
time.Since(r.prevDownTime) < doubleClickDuration {
|
}
|
||||||
double = true
|
}
|
||||||
|
|
||||||
|
// middle := t & 0b1
|
||||||
|
left := t&0b11 == 0
|
||||||
|
|
||||||
|
// shift := t & 0b100
|
||||||
|
// ctrl := t & 0b1000
|
||||||
|
mod := t&0b1100 > 0
|
||||||
|
|
||||||
|
drag := t&0b100000 > 0
|
||||||
|
|
||||||
|
if scroll != 0 {
|
||||||
|
return Event{Mouse, 0, &MouseEvent{y, x, scroll, false, false, false, mod}}
|
||||||
|
}
|
||||||
|
|
||||||
|
double := false
|
||||||
|
if down && !drag {
|
||||||
|
now := time.Now()
|
||||||
|
if !left { // Right double click is not allowed
|
||||||
|
r.clicks = [][2]int{}
|
||||||
|
} else if now.Sub(r.prevDownTime) < doubleClickDuration {
|
||||||
|
r.clicks = append(r.clicks, [2]int{x, y})
|
||||||
|
} else {
|
||||||
|
r.clicks = [][2]int{{x, y}}
|
||||||
|
}
|
||||||
|
r.prevDownTime = now
|
||||||
|
} else {
|
||||||
|
n := len(r.clicks)
|
||||||
|
if len(r.clicks) > 1 && r.clicks[n-2][0] == r.clicks[n-1][0] && r.clicks[n-2][1] == r.clicks[n-1][1] &&
|
||||||
|
time.Since(r.prevDownTime) < doubleClickDuration {
|
||||||
|
double = true
|
||||||
|
if double {
|
||||||
|
r.clicks = [][2]int{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, mod}}
|
|
||||||
case 96, 100, 104, 112, // scroll-up / shift / cmd / ctrl
|
|
||||||
97, 101, 105, 113: // scroll-down / shift / cmd / ctrl
|
|
||||||
mod := r.buffer[3] >= 100
|
|
||||||
s := 1 - int(r.buffer[3]%2)*2
|
|
||||||
x := int(r.buffer[4] - 33)
|
|
||||||
y := int(r.buffer[5]-33) - r.yoffset
|
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, s, false, false, false, mod}}
|
|
||||||
}
|
}
|
||||||
return Event{Invalid, 0, nil}
|
return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, mod}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) smcup() {
|
func (r *LightRenderer) smcup() {
|
||||||
@@ -571,6 +625,7 @@ func (r *LightRenderer) rmcup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Pause(clear bool) {
|
func (r *LightRenderer) Pause(clear bool) {
|
||||||
|
r.disableMouse()
|
||||||
r.restoreTerminal()
|
r.restoreTerminal()
|
||||||
if clear {
|
if clear {
|
||||||
if r.fullscreen {
|
if r.fullscreen {
|
||||||
@@ -583,6 +638,22 @@ func (r *LightRenderer) Pause(clear bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) enableMouse() {
|
||||||
|
if r.mouse {
|
||||||
|
r.csi("?1000h")
|
||||||
|
r.csi("?1002h")
|
||||||
|
r.csi("?1006h")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) disableMouse() {
|
||||||
|
if r.mouse {
|
||||||
|
r.csi("?1000l")
|
||||||
|
r.csi("?1002l")
|
||||||
|
r.csi("?1006l")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Resume(clear bool, sigcont bool) {
|
func (r *LightRenderer) Resume(clear bool, sigcont bool) {
|
||||||
r.setupTerminal()
|
r.setupTerminal()
|
||||||
if clear {
|
if clear {
|
||||||
@@ -591,12 +662,13 @@ func (r *LightRenderer) Resume(clear bool, sigcont bool) {
|
|||||||
} else {
|
} else {
|
||||||
r.rmcup()
|
r.rmcup()
|
||||||
}
|
}
|
||||||
|
r.enableMouse()
|
||||||
r.flush()
|
r.flush()
|
||||||
} else if sigcont && !r.fullscreen && r.mouse {
|
} else if sigcont && !r.fullscreen && r.mouse {
|
||||||
// NOTE: SIGCONT (Coming back from CTRL-Z):
|
// NOTE: SIGCONT (Coming back from CTRL-Z):
|
||||||
// It's highly likely that the offset we obtained at the beginning is
|
// It's highly likely that the offset we obtained at the beginning is
|
||||||
// no longer correct, so we simply disable mouse input.
|
// no longer correct, so we simply disable mouse input.
|
||||||
r.csi("?1000l")
|
r.disableMouse()
|
||||||
r.mouse = false
|
r.mouse = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -611,6 +683,10 @@ func (r *LightRenderer) Clear() {
|
|||||||
r.flush()
|
r.flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) NeedScrollbarRedraw() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) RefreshWindows(windows []Window) {
|
func (r *LightRenderer) RefreshWindows(windows []Window) {
|
||||||
r.flush()
|
r.flush()
|
||||||
}
|
}
|
||||||
@@ -634,9 +710,7 @@ func (r *LightRenderer) Close() {
|
|||||||
} else if !r.fullscreen {
|
} else if !r.fullscreen {
|
||||||
r.csi("u")
|
r.csi("u")
|
||||||
}
|
}
|
||||||
if r.mouse {
|
r.disableMouse()
|
||||||
r.csi("?1000l")
|
|
||||||
}
|
|
||||||
r.flush()
|
r.flush()
|
||||||
r.closePlatform()
|
r.closePlatform()
|
||||||
r.restoreTerminal()
|
r.restoreTerminal()
|
||||||
@@ -647,6 +721,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -670,25 +747,38 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, prev
|
|||||||
w.fg = r.theme.Fg.Color
|
w.fg = r.theme.Fg.Color
|
||||||
w.bg = r.theme.Bg.Color
|
w.bg = r.theme.Bg.Color
|
||||||
}
|
}
|
||||||
w.drawBorder()
|
w.drawBorder(false)
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) drawBorder() {
|
func (w *LightWindow) DrawHBorder() {
|
||||||
|
w.drawBorder(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) drawBorder(onlyHorizontal bool) {
|
||||||
switch w.border.shape {
|
switch w.border.shape {
|
||||||
case BorderRounded, BorderSharp:
|
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble:
|
||||||
w.drawBorderAround()
|
w.drawBorderAround(onlyHorizontal)
|
||||||
case BorderHorizontal:
|
case BorderHorizontal:
|
||||||
w.drawBorderHorizontal(true, true)
|
w.drawBorderHorizontal(true, true)
|
||||||
case BorderVertical:
|
case BorderVertical:
|
||||||
|
if onlyHorizontal {
|
||||||
|
return
|
||||||
|
}
|
||||||
w.drawBorderVertical(true, true)
|
w.drawBorderVertical(true, true)
|
||||||
case BorderTop:
|
case BorderTop:
|
||||||
w.drawBorderHorizontal(true, false)
|
w.drawBorderHorizontal(true, false)
|
||||||
case BorderBottom:
|
case BorderBottom:
|
||||||
w.drawBorderHorizontal(false, true)
|
w.drawBorderHorizontal(false, true)
|
||||||
case BorderLeft:
|
case BorderLeft:
|
||||||
|
if onlyHorizontal {
|
||||||
|
return
|
||||||
|
}
|
||||||
w.drawBorderVertical(true, false)
|
w.drawBorderVertical(true, false)
|
||||||
case BorderRight:
|
case BorderRight:
|
||||||
|
if onlyHorizontal {
|
||||||
|
return
|
||||||
|
}
|
||||||
w.drawBorderVertical(false, true)
|
w.drawBorderVertical(false, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -698,13 +788,14 @@ func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
|
|||||||
if w.preview {
|
if w.preview {
|
||||||
color = ColPreviewBorder
|
color = ColPreviewBorder
|
||||||
}
|
}
|
||||||
|
hw := runewidth.RuneWidth(w.border.top)
|
||||||
if top {
|
if top {
|
||||||
w.Move(0, 0)
|
w.Move(0, 0)
|
||||||
w.CPrint(color, repeat(w.border.horizontal, w.width))
|
w.CPrint(color, repeat(w.border.top, w.width/hw))
|
||||||
}
|
}
|
||||||
if bottom {
|
if bottom {
|
||||||
w.Move(w.height-1, 0)
|
w.Move(w.height-1, 0)
|
||||||
w.CPrint(color, repeat(w.border.horizontal, w.width))
|
w.CPrint(color, repeat(w.border.bottom, w.width/hw))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -720,38 +811,46 @@ func (w *LightWindow) drawBorderVertical(left, right bool) {
|
|||||||
for y := 0; y < w.height; y++ {
|
for y := 0; y < w.height; y++ {
|
||||||
w.Move(y, 0)
|
w.Move(y, 0)
|
||||||
if left {
|
if left {
|
||||||
w.CPrint(color, string(w.border.vertical))
|
w.CPrint(color, string(w.border.left))
|
||||||
}
|
}
|
||||||
w.CPrint(color, repeat(' ', width))
|
w.CPrint(color, repeat(' ', width))
|
||||||
if right {
|
if right {
|
||||||
w.CPrint(color, string(w.border.vertical))
|
w.CPrint(color, string(w.border.right))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) drawBorderAround() {
|
func (w *LightWindow) drawBorderAround(onlyHorizontal bool) {
|
||||||
w.Move(0, 0)
|
w.Move(0, 0)
|
||||||
color := ColBorder
|
color := ColBorder
|
||||||
if w.preview {
|
if w.preview {
|
||||||
color = ColPreviewBorder
|
color = ColPreviewBorder
|
||||||
}
|
}
|
||||||
w.CPrint(color, string(w.border.topLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.topRight))
|
hw := runewidth.RuneWidth(w.border.top)
|
||||||
for y := 1; y < w.height-1; y++ {
|
tcw := runewidth.RuneWidth(w.border.topLeft) + runewidth.RuneWidth(w.border.topRight)
|
||||||
w.Move(y, 0)
|
bcw := runewidth.RuneWidth(w.border.bottomLeft) + runewidth.RuneWidth(w.border.bottomRight)
|
||||||
w.CPrint(color, string(w.border.vertical))
|
rem := (w.width - tcw) % hw
|
||||||
w.CPrint(color, repeat(' ', w.width-2))
|
w.CPrint(color, string(w.border.topLeft)+repeat(w.border.top, (w.width-tcw)/hw)+repeat(' ', rem)+string(w.border.topRight))
|
||||||
w.CPrint(color, string(w.border.vertical))
|
if !onlyHorizontal {
|
||||||
|
vw := runewidth.RuneWidth(w.border.left)
|
||||||
|
for y := 1; y < w.height-1; y++ {
|
||||||
|
w.Move(y, 0)
|
||||||
|
w.CPrint(color, string(w.border.left))
|
||||||
|
w.CPrint(color, repeat(' ', w.width-vw*2))
|
||||||
|
w.CPrint(color, string(w.border.right))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
w.Move(w.height-1, 0)
|
w.Move(w.height-1, 0)
|
||||||
w.CPrint(color, string(w.border.bottomLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.bottomRight))
|
rem = (w.width - bcw) % hw
|
||||||
|
w.CPrint(color, string(w.border.bottomLeft)+repeat(w.border.bottom, (w.width-bcw)/hw)+repeat(' ', rem)+string(w.border.bottomRight))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) csi(code string) {
|
func (w *LightWindow) csi(code string) string {
|
||||||
w.renderer.csi(code)
|
return w.renderer.csi(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) stderrInternal(str string, allowNLCR bool) {
|
func (w *LightWindow) stderrInternal(str string, allowNLCR bool, resetCode string) {
|
||||||
w.renderer.stderrInternal(str, allowNLCR)
|
w.renderer.stderrInternal(str, allowNLCR, resetCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) Top() int {
|
func (w *LightWindow) Top() int {
|
||||||
@@ -827,6 +926,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -854,10 +956,10 @@ func colorCodes(fg Color, bg Color) []string {
|
|||||||
return codes
|
return codes
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) csiColor(fg Color, bg Color, attr Attr) bool {
|
func (w *LightWindow) csiColor(fg Color, bg Color, attr Attr) (bool, string) {
|
||||||
codes := append(attrCodes(attr), colorCodes(fg, bg)...)
|
codes := append(attrCodes(attr), colorCodes(fg, bg)...)
|
||||||
w.csi(";" + strings.Join(codes, ";") + "m")
|
code := w.csi(";" + strings.Join(codes, ";") + "m")
|
||||||
return len(codes) > 0
|
return len(codes) > 0, code
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) Print(text string) {
|
func (w *LightWindow) Print(text string) {
|
||||||
@@ -869,16 +971,17 @@ func cleanse(str string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) CPrint(pair ColorPair, text string) {
|
func (w *LightWindow) CPrint(pair ColorPair, text string) {
|
||||||
w.csiColor(pair.Fg(), pair.Bg(), pair.Attr())
|
_, code := w.csiColor(pair.Fg(), pair.Bg(), pair.Attr())
|
||||||
w.stderrInternal(cleanse(text), false)
|
w.stderrInternal(cleanse(text), false, code)
|
||||||
w.csi("m")
|
w.csi("m")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) cprint2(fg Color, bg Color, attr Attr, text string) {
|
func (w *LightWindow) cprint2(fg Color, bg Color, attr Attr, text string) {
|
||||||
if w.csiColor(fg, bg, attr) {
|
hasColors, code := w.csiColor(fg, bg, attr)
|
||||||
|
if hasColors {
|
||||||
defer w.csi("m")
|
defer w.csi("m")
|
||||||
}
|
}
|
||||||
w.stderrInternal(cleanse(text), false)
|
w.stderrInternal(cleanse(text), false, code)
|
||||||
}
|
}
|
||||||
|
|
||||||
type wrappedLine struct {
|
type wrappedLine struct {
|
||||||
@@ -898,6 +1001,8 @@ func wrapLine(input string, prefixLength int, max int, tabstop int) []wrappedLin
|
|||||||
if len(rs) == 1 && rs[0] == '\t' {
|
if len(rs) == 1 && rs[0] == '\t' {
|
||||||
w = tabstop - (prefixLength+width)%tabstop
|
w = tabstop - (prefixLength+width)%tabstop
|
||||||
str = repeat(' ', w)
|
str = repeat(' ', w)
|
||||||
|
} else if rs[0] == '\r' {
|
||||||
|
w++
|
||||||
} else {
|
} else {
|
||||||
w = runewidth.StringWidth(str)
|
w = runewidth.StringWidth(str)
|
||||||
}
|
}
|
||||||
@@ -916,12 +1021,12 @@ func wrapLine(input string, prefixLength int, max int, tabstop int) []wrappedLin
|
|||||||
return lines
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) fill(str string, onMove func()) FillReturn {
|
func (w *LightWindow) fill(str string, resetCode string) FillReturn {
|
||||||
allLines := strings.Split(str, "\n")
|
allLines := strings.Split(str, "\n")
|
||||||
for i, line := range allLines {
|
for i, line := range allLines {
|
||||||
lines := wrapLine(line, w.posx, w.width, w.tabstop)
|
lines := wrapLine(line, w.posx, w.width, w.tabstop)
|
||||||
for j, wl := range lines {
|
for j, wl := range lines {
|
||||||
w.stderrInternal(wl.text, false)
|
w.stderrInternal(wl.text, false, resetCode)
|
||||||
w.posx += wl.displayWidth
|
w.posx += wl.displayWidth
|
||||||
|
|
||||||
// Wrap line
|
// Wrap line
|
||||||
@@ -931,7 +1036,7 @@ func (w *LightWindow) fill(str string, onMove func()) FillReturn {
|
|||||||
}
|
}
|
||||||
w.MoveAndClear(w.posy, w.posx)
|
w.MoveAndClear(w.posy, w.posx)
|
||||||
w.Move(w.posy+1, 0)
|
w.Move(w.posy+1, 0)
|
||||||
onMove()
|
w.renderer.stderr(resetCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -940,22 +1045,26 @@ func (w *LightWindow) fill(str string, onMove func()) FillReturn {
|
|||||||
return FillSuspend
|
return FillSuspend
|
||||||
}
|
}
|
||||||
w.Move(w.posy+1, 0)
|
w.Move(w.posy+1, 0)
|
||||||
onMove()
|
w.renderer.stderr(resetCode)
|
||||||
return FillNextLine
|
return FillNextLine
|
||||||
}
|
}
|
||||||
return FillContinue
|
return FillContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) setBg() {
|
func (w *LightWindow) setBg() string {
|
||||||
if w.bg != colDefault {
|
if w.bg != colDefault {
|
||||||
w.csiColor(colDefault, w.bg, AttrRegular)
|
_, code := w.csiColor(colDefault, w.bg, AttrRegular)
|
||||||
|
return code
|
||||||
}
|
}
|
||||||
|
// Should clear dim attribute after ␍ in the preview window
|
||||||
|
// e.g. printf "foo\rbar" | fzf --ansi --preview 'printf "foo\rbar"'
|
||||||
|
return "\x1b[m"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) Fill(text string) FillReturn {
|
func (w *LightWindow) Fill(text string) FillReturn {
|
||||||
w.Move(w.posy, w.posx)
|
w.Move(w.posy, w.posx)
|
||||||
w.setBg()
|
code := w.setBg()
|
||||||
return w.fill(text, w.setBg)
|
return w.fill(text, code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillReturn {
|
func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillReturn {
|
||||||
@@ -966,11 +1075,11 @@ func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillRetu
|
|||||||
if bg == colDefault {
|
if bg == colDefault {
|
||||||
bg = w.bg
|
bg = w.bg
|
||||||
}
|
}
|
||||||
if w.csiColor(fg, bg, attr) {
|
if hasColors, resetCode := w.csiColor(fg, bg, attr); hasColors {
|
||||||
defer w.csi("m")
|
defer w.csi("m")
|
||||||
return w.fill(text, func() { w.csiColor(fg, bg, attr) })
|
return w.fill(text, resetCode)
|
||||||
}
|
}
|
||||||
return w.fill(text, w.setBg)
|
return w.fill(text, w.setBg())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) FinishFill() {
|
func (w *LightWindow) FinishFill() {
|
||||||
@@ -981,7 +1090,7 @@ func (w *LightWindow) FinishFill() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) Erase() {
|
func (w *LightWindow) Erase() {
|
||||||
w.drawBorder()
|
w.drawBorder(false)
|
||||||
// We don't erase the window here to avoid flickering during scroll
|
// We don't erase the window here to avoid flickering during scroll
|
||||||
w.Move(0, 0)
|
w.Move(0, 0)
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
// +build !windows
|
//go:build !windows
|
||||||
|
|
||||||
package tui
|
package tui
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
//+build windows
|
//go:build windows
|
||||||
|
|
||||||
package tui
|
package tui
|
||||||
|
|
||||||
|
229
src/tui/tcell.go
229
src/tui/tcell.go
@@ -1,4 +1,4 @@
|
|||||||
// +build tcell windows
|
//go:build tcell || windows
|
||||||
|
|
||||||
package tui
|
package tui
|
||||||
|
|
||||||
@@ -6,10 +6,9 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"runtime"
|
"github.com/gdamore/tcell/v2"
|
||||||
|
"github.com/gdamore/tcell/v2/encoding"
|
||||||
"github.com/gdamore/tcell"
|
"github.com/junegunn/fzf/src/util"
|
||||||
"github.com/gdamore/tcell/encoding"
|
|
||||||
|
|
||||||
"github.com/mattn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
"github.com/rivo/uniseg"
|
"github.com/rivo/uniseg"
|
||||||
@@ -19,12 +18,26 @@ func HasFullscreenRenderer() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p ColorPair) style() tcell.Style {
|
var DefaultBorderShape BorderShape = BorderSharp
|
||||||
style := tcell.StyleDefault
|
|
||||||
return style.Foreground(tcell.Color(p.Fg())).Background(tcell.Color(p.Bg()))
|
func asTcellColor(color Color) tcell.Color {
|
||||||
|
if color == colDefault {
|
||||||
|
return tcell.ColorDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
value := uint64(tcell.ColorValid) + uint64(color)
|
||||||
|
if color.is24() {
|
||||||
|
value = value | uint64(tcell.ColorIsRGB)
|
||||||
|
}
|
||||||
|
return tcell.Color(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Attr tcell.Style
|
func (p ColorPair) style() tcell.Style {
|
||||||
|
style := tcell.StyleDefault
|
||||||
|
return style.Foreground(asTcellColor(p.Fg())).Background(asTcellColor(p.Bg()))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Attr int32
|
||||||
|
|
||||||
type TcellWindow struct {
|
type TcellWindow struct {
|
||||||
color bool
|
color bool
|
||||||
@@ -63,8 +76,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() {
|
||||||
@@ -72,12 +83,13 @@ func (w *TcellWindow) FinishFill() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Bold Attr = Attr(tcell.AttrBold)
|
Bold Attr = Attr(tcell.AttrBold)
|
||||||
Dim = Attr(tcell.AttrDim)
|
Dim = Attr(tcell.AttrDim)
|
||||||
Blink = Attr(tcell.AttrBlink)
|
Blink = Attr(tcell.AttrBlink)
|
||||||
Reverse = Attr(tcell.AttrReverse)
|
Reverse = Attr(tcell.AttrReverse)
|
||||||
Underline = Attr(tcell.AttrUnderline)
|
Underline = Attr(tcell.AttrUnderline)
|
||||||
Italic = Attr(tcell.AttrItalic)
|
StrikeThrough = Attr(tcell.AttrStrikeThrough)
|
||||||
|
Italic = Attr(tcell.AttrItalic)
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -86,6 +98,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
|
||||||
@@ -176,6 +190,10 @@ func (r *FullscreenRenderer) Clear() {
|
|||||||
_screen.Clear()
|
_screen.Clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *FullscreenRenderer) NeedScrollbarRedraw() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Refresh() {
|
func (r *FullscreenRenderer) Refresh() {
|
||||||
// noop
|
// noop
|
||||||
}
|
}
|
||||||
@@ -205,54 +223,38 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
return Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, false, mod}}
|
return Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, false, mod}}
|
||||||
case button&tcell.WheelUp != 0:
|
case button&tcell.WheelUp != 0:
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, +1, false, false, false, mod}}
|
return Event{Mouse, 0, &MouseEvent{y, x, +1, false, false, false, mod}}
|
||||||
case button&tcell.Button1 != 0 && !drag:
|
case button&tcell.Button1 != 0:
|
||||||
// all potential double click events put their 'line' coordinate in the clickY array
|
double := false
|
||||||
// double click event has two conditions, temporal and spatial, the first is checked here
|
if !drag {
|
||||||
now := time.Now()
|
// all potential double click events put their coordinates in the clicks array
|
||||||
if now.Sub(r.prevDownTime) < doubleClickDuration {
|
// double click event has two conditions, temporal and spatial, the first is checked here
|
||||||
r.clickY = append(r.clickY, y)
|
now := time.Now()
|
||||||
} else {
|
if now.Sub(r.prevDownTime) < doubleClickDuration {
|
||||||
r.clickY = []int{y}
|
r.clicks = append(r.clicks, [2]int{x, y})
|
||||||
}
|
} else {
|
||||||
r.prevDownTime = now
|
r.clicks = [][2]int{{x, y}}
|
||||||
|
}
|
||||||
|
r.prevDownTime = now
|
||||||
|
|
||||||
// detect double clicks (also check for spatial condition)
|
// detect double clicks (also check for spatial condition)
|
||||||
n := len(r.clickY)
|
n := len(r.clicks)
|
||||||
double := n > 1 && r.clickY[n-2] == r.clickY[n-1]
|
double = n > 1 && r.clicks[n-2][0] == r.clicks[n-1][0] && r.clicks[n-2][1] == r.clicks[n-1][1]
|
||||||
if double {
|
if double {
|
||||||
// make sure two consecutive double clicks require four clicks
|
// make sure two consecutive double clicks require four clicks
|
||||||
r.clickY = []int{}
|
r.clicks = [][2]int{}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fire single or double click event
|
// fire single or double click event
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, true, !double, double, mod}}
|
return Event{Mouse, 0, &MouseEvent{y, x, 0, true, !double, double, mod}}
|
||||||
case button&tcell.Button2 != 0 && !drag:
|
case button&tcell.Button2 != 0:
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, false, true, false, mod}}
|
return Event{Mouse, 0, &MouseEvent{y, x, 0, false, true, false, mod}}
|
||||||
case runtime.GOOS != "windows":
|
default:
|
||||||
|
|
||||||
// double and single taps on Windows don't quite work due to
|
// double and single taps on Windows don't quite work due to
|
||||||
// the console acting on the events and not allowing us
|
// the console acting on the events and not allowing us
|
||||||
// to consume them.
|
// to consume them.
|
||||||
|
|
||||||
left := button&tcell.Button1 != 0
|
left := button&tcell.Button1 != 0
|
||||||
down := left || button&tcell.Button3 != 0
|
down := left || button&tcell.Button3 != 0
|
||||||
double := false
|
double := false
|
||||||
if down {
|
|
||||||
now := time.Now()
|
|
||||||
if !left {
|
|
||||||
r.clickY = []int{}
|
|
||||||
} else if now.Sub(r.prevDownTime) < doubleClickDuration {
|
|
||||||
r.clickY = append(r.clickY, x)
|
|
||||||
} else {
|
|
||||||
r.clickY = []int{x}
|
|
||||||
r.prevDownTime = now
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if len(r.clickY) > 1 && r.clickY[0] == r.clickY[1] &&
|
|
||||||
time.Now().Sub(r.prevDownTime) < doubleClickDuration {
|
|
||||||
double = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, mod}}
|
return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, mod}}
|
||||||
}
|
}
|
||||||
@@ -411,6 +413,12 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
case tcell.KeyHome:
|
case tcell.KeyHome:
|
||||||
return Event{Home, 0, nil}
|
return Event{Home, 0, nil}
|
||||||
case tcell.KeyDelete:
|
case tcell.KeyDelete:
|
||||||
|
if ctrl {
|
||||||
|
return Event{CtrlDelete, 0, nil}
|
||||||
|
}
|
||||||
|
if shift {
|
||||||
|
return Event{SDelete, 0, nil}
|
||||||
|
}
|
||||||
return Event{Del, 0, nil}
|
return Event{Del, 0, nil}
|
||||||
case tcell.KeyEnd:
|
case tcell.KeyEnd:
|
||||||
return Event{End, 0, nil}
|
return Event{End, 0, nil}
|
||||||
@@ -502,7 +510,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 +519,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(false)
|
||||||
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Close() {
|
func (w *TcellWindow) Close() {
|
||||||
@@ -526,7 +536,7 @@ func fill(x, y, w, h int, n ColorPair, r rune) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Erase() {
|
func (w *TcellWindow) Erase() {
|
||||||
fill(w.left-1, w.top, w.width+1, w.height, w.normal, ' ')
|
fill(w.left-1, w.top, w.width+1, w.height-1, w.normal, ' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Enclose(y int, x int) bool {
|
func (w *TcellWindow) Enclose(y int, x int) bool {
|
||||||
@@ -561,6 +571,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)
|
||||||
@@ -568,26 +579,27 @@ func (w *TcellWindow) printString(text string, pair ColorPair) {
|
|||||||
|
|
||||||
gr := uniseg.NewGraphemes(text)
|
gr := uniseg.NewGraphemes(text)
|
||||||
for gr.Next() {
|
for gr.Next() {
|
||||||
|
st := style
|
||||||
rs := gr.Runes()
|
rs := gr.Runes()
|
||||||
|
|
||||||
if len(rs) == 1 {
|
if len(rs) == 1 {
|
||||||
r := rs[0]
|
r := rs[0]
|
||||||
if r < rune(' ') { // ignore control characters
|
if r == '\r' {
|
||||||
continue
|
st = style.Dim(true)
|
||||||
|
rs[0] = '␍'
|
||||||
} else if r == '\n' {
|
} else if r == '\n' {
|
||||||
w.lastY++
|
st = style.Dim(true)
|
||||||
lx = 0
|
rs[0] = '␊'
|
||||||
continue
|
} else if r < rune(' ') { // ignore control characters
|
||||||
} else if r == '\u000D' { // skip carriage return
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var xPos = w.left + w.lastX + lx
|
var xPos = w.left + w.lastX + lx
|
||||||
var yPos = w.top + w.lastY
|
var yPos = w.top + w.lastY
|
||||||
if xPos < (w.left+w.width) && yPos < (w.top+w.height) {
|
if xPos < (w.left+w.width) && yPos < (w.top+w.height) {
|
||||||
_screen.SetContent(xPos, yPos, rs[0], rs[1:], style)
|
_screen.SetContent(xPos, yPos, rs[0], rs[1:], st)
|
||||||
}
|
}
|
||||||
lx += runewidth.StringWidth(string(rs))
|
lx += util.StringWidth(string(rs))
|
||||||
}
|
}
|
||||||
w.lastX += lx
|
w.lastX += lx
|
||||||
}
|
}
|
||||||
@@ -612,16 +624,26 @@ 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)
|
||||||
|
Loop:
|
||||||
for gr.Next() {
|
for gr.Next() {
|
||||||
|
st := style
|
||||||
rs := gr.Runes()
|
rs := gr.Runes()
|
||||||
if len(rs) == 1 && rs[0] == '\n' {
|
if len(rs) == 1 {
|
||||||
w.lastY++
|
r := rs[0]
|
||||||
w.lastX = 0
|
switch r {
|
||||||
lx = 0
|
case '\r':
|
||||||
continue
|
st = style.Dim(true)
|
||||||
|
rs[0] = '␍'
|
||||||
|
case '\n':
|
||||||
|
w.lastY++
|
||||||
|
w.lastX = 0
|
||||||
|
lx = 0
|
||||||
|
continue Loop
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// word wrap:
|
// word wrap:
|
||||||
@@ -638,8 +660,8 @@ func (w *TcellWindow) fillString(text string, pair ColorPair) FillReturn {
|
|||||||
return FillSuspend
|
return FillSuspend
|
||||||
}
|
}
|
||||||
|
|
||||||
_screen.SetContent(xPos, yPos, rs[0], rs[1:], style)
|
_screen.SetContent(xPos, yPos, rs[0], rs[1:], st)
|
||||||
lx += runewidth.StringWidth(string(rs))
|
lx += util.StringWidth(string(rs))
|
||||||
}
|
}
|
||||||
w.lastX += lx
|
w.lastX += lx
|
||||||
if w.lastX == w.width {
|
if w.lastX == w.width {
|
||||||
@@ -665,7 +687,11 @@ func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
|
|||||||
return w.fillString(str, NewColorPair(fg, bg, a))
|
return w.fillString(str, NewColorPair(fg, bg, a))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) drawBorder() {
|
func (w *TcellWindow) DrawHBorder() {
|
||||||
|
w.drawBorder(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
|
||||||
shape := w.borderStyle.shape
|
shape := w.borderStyle.shape
|
||||||
if shape == BorderNone {
|
if shape == BorderNone {
|
||||||
return
|
return
|
||||||
@@ -687,35 +713,52 @@ func (w *TcellWindow) drawBorder() {
|
|||||||
style = w.normal.style()
|
style = w.normal.style()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hw := runewidth.RuneWidth(w.borderStyle.top)
|
||||||
switch shape {
|
switch shape {
|
||||||
case BorderRounded, BorderSharp, BorderHorizontal, BorderTop:
|
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderHorizontal, BorderTop:
|
||||||
for x := left; x < right; x++ {
|
max := right - 2*hw
|
||||||
_screen.SetContent(x, top, w.borderStyle.horizontal, nil, style)
|
if shape == BorderHorizontal || shape == BorderTop {
|
||||||
|
max = right - hw
|
||||||
|
}
|
||||||
|
// tcell has an issue displaying two overlapping wide runes
|
||||||
|
// e.g. SetContent( HH )
|
||||||
|
// SetContent( TR )
|
||||||
|
// ==================
|
||||||
|
// ( HH ) => TR is ignored
|
||||||
|
for x := left; x <= max; x += hw {
|
||||||
|
_screen.SetContent(x, top, w.borderStyle.top, nil, style)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch shape {
|
switch shape {
|
||||||
case BorderRounded, BorderSharp, BorderHorizontal, BorderBottom:
|
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderHorizontal, BorderBottom:
|
||||||
for x := left; x < right; x++ {
|
max := right - 2*hw
|
||||||
_screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style)
|
if shape == BorderHorizontal || shape == BorderBottom {
|
||||||
|
max = right - hw
|
||||||
|
}
|
||||||
|
for x := left; x <= max; x += hw {
|
||||||
|
_screen.SetContent(x, bot-1, w.borderStyle.bottom, nil, style)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !onlyHorizontal {
|
||||||
|
switch shape {
|
||||||
|
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderVertical, BorderLeft:
|
||||||
|
for y := top; y < bot; y++ {
|
||||||
|
_screen.SetContent(left, y, w.borderStyle.left, nil, style)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch shape {
|
||||||
|
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderVertical, BorderRight:
|
||||||
|
vw := runewidth.RuneWidth(w.borderStyle.right)
|
||||||
|
for y := top; y < bot; y++ {
|
||||||
|
_screen.SetContent(right-vw, y, w.borderStyle.right, nil, style)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch shape {
|
switch shape {
|
||||||
case BorderRounded, BorderSharp, BorderVertical, BorderLeft:
|
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble:
|
||||||
for y := top; y < bot; y++ {
|
|
||||||
_screen.SetContent(left, y, w.borderStyle.vertical, nil, style)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch shape {
|
|
||||||
case BorderRounded, BorderSharp, BorderVertical, BorderRight:
|
|
||||||
for y := top; y < bot; y++ {
|
|
||||||
_screen.SetContent(right-1, y, w.borderStyle.vertical, nil, style)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch shape {
|
|
||||||
case BorderRounded, BorderSharp:
|
|
||||||
_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-runewidth.RuneWidth(w.borderStyle.topRight), 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)
|
||||||
_screen.SetContent(right-1, bot-1, w.borderStyle.bottomRight, nil, style)
|
_screen.SetContent(right-runewidth.RuneWidth(w.borderStyle.bottomRight), bot-1, w.borderStyle.bottomRight, nil, style)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
// +build tcell windows
|
//go:build tcell || windows
|
||||||
|
|
||||||
package tui
|
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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
// +build !windows
|
//go:build !windows
|
||||||
|
|
||||||
package tui
|
package tui
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
// +build windows
|
//go:build windows
|
||||||
|
|
||||||
package tui
|
package tui
|
||||||
|
|
||||||
|
457
src/tui/tui.go
457
src/tui/tui.go
@@ -41,6 +41,7 @@ const (
|
|||||||
CtrlZ
|
CtrlZ
|
||||||
ESC
|
ESC
|
||||||
CtrlSpace
|
CtrlSpace
|
||||||
|
CtrlDelete
|
||||||
|
|
||||||
// https://apple.stackexchange.com/questions/24261/how-do-i-send-c-that-is-control-slash-to-the-terminal
|
// https://apple.stackexchange.com/questions/24261/how-do-i-send-c-that-is-control-slash-to-the-terminal
|
||||||
CtrlBackSlash
|
CtrlBackSlash
|
||||||
@@ -74,6 +75,7 @@ const (
|
|||||||
SDown
|
SDown
|
||||||
SLeft
|
SLeft
|
||||||
SRight
|
SRight
|
||||||
|
SDelete
|
||||||
|
|
||||||
F1
|
F1
|
||||||
F2
|
F2
|
||||||
@@ -90,6 +92,11 @@ const (
|
|||||||
|
|
||||||
Change
|
Change
|
||||||
BackwardEOF
|
BackwardEOF
|
||||||
|
Start
|
||||||
|
Load
|
||||||
|
Focus
|
||||||
|
One
|
||||||
|
Zero
|
||||||
|
|
||||||
AltBS
|
AltBS
|
||||||
|
|
||||||
@@ -248,25 +255,31 @@ func (p ColorPair) MergeNonDefault(other ColorPair) ColorPair {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ColorTheme struct {
|
type ColorTheme struct {
|
||||||
Colored bool
|
Colored bool
|
||||||
Input ColorAttr
|
Input ColorAttr
|
||||||
Disabled ColorAttr
|
Disabled ColorAttr
|
||||||
Fg ColorAttr
|
Fg ColorAttr
|
||||||
Bg ColorAttr
|
Bg ColorAttr
|
||||||
PreviewFg ColorAttr
|
PreviewFg ColorAttr
|
||||||
PreviewBg ColorAttr
|
PreviewBg ColorAttr
|
||||||
DarkBg ColorAttr
|
DarkBg ColorAttr
|
||||||
Gutter ColorAttr
|
Gutter ColorAttr
|
||||||
Prompt ColorAttr
|
Prompt ColorAttr
|
||||||
Match ColorAttr
|
Match ColorAttr
|
||||||
Current ColorAttr
|
Current ColorAttr
|
||||||
CurrentMatch ColorAttr
|
CurrentMatch ColorAttr
|
||||||
Spinner ColorAttr
|
Spinner ColorAttr
|
||||||
Info ColorAttr
|
Info ColorAttr
|
||||||
Cursor ColorAttr
|
Cursor ColorAttr
|
||||||
Selected ColorAttr
|
Selected ColorAttr
|
||||||
Header ColorAttr
|
Header ColorAttr
|
||||||
Border ColorAttr
|
Separator ColorAttr
|
||||||
|
Scrollbar ColorAttr
|
||||||
|
Border ColorAttr
|
||||||
|
PreviewBorder ColorAttr
|
||||||
|
PreviewScrollbar ColorAttr
|
||||||
|
BorderLabel ColorAttr
|
||||||
|
PreviewLabel ColorAttr
|
||||||
}
|
}
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
@@ -275,6 +288,15 @@ type Event struct {
|
|||||||
MouseEvent *MouseEvent
|
MouseEvent *MouseEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e Event) Is(types ...EventType) bool {
|
||||||
|
for _, t := range types {
|
||||||
|
if e.Type == t {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type MouseEvent struct {
|
type MouseEvent struct {
|
||||||
Y int
|
Y int
|
||||||
X int
|
X int
|
||||||
@@ -291,6 +313,10 @@ const (
|
|||||||
BorderNone BorderShape = iota
|
BorderNone BorderShape = iota
|
||||||
BorderRounded
|
BorderRounded
|
||||||
BorderSharp
|
BorderSharp
|
||||||
|
BorderBold
|
||||||
|
BorderBlock
|
||||||
|
BorderThinBlock
|
||||||
|
BorderDouble
|
||||||
BorderHorizontal
|
BorderHorizontal
|
||||||
BorderVertical
|
BorderVertical
|
||||||
BorderTop
|
BorderTop
|
||||||
@@ -299,10 +325,28 @@ const (
|
|||||||
BorderRight
|
BorderRight
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (s BorderShape) HasRight() bool {
|
||||||
|
switch s {
|
||||||
|
case BorderNone, BorderLeft, BorderTop, BorderBottom, BorderHorizontal: // No right
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s BorderShape) HasTop() bool {
|
||||||
|
switch s {
|
||||||
|
case BorderNone, BorderLeft, BorderRight, BorderBottom, BorderVertical: // No top
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
type BorderStyle struct {
|
type BorderStyle struct {
|
||||||
shape BorderShape
|
shape BorderShape
|
||||||
horizontal rune
|
top rune
|
||||||
vertical rune
|
bottom rune
|
||||||
|
left rune
|
||||||
|
right rune
|
||||||
topLeft rune
|
topLeft rune
|
||||||
topRight rune
|
topRight rune
|
||||||
bottomLeft rune
|
bottomLeft rune
|
||||||
@@ -312,44 +356,109 @@ 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{
|
|
||||||
shape: shape,
|
|
||||||
horizontal: '─',
|
|
||||||
vertical: '│',
|
|
||||||
topLeft: '╭',
|
|
||||||
topRight: '╮',
|
|
||||||
bottomLeft: '╰',
|
|
||||||
bottomRight: '╯',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return BorderStyle{
|
return BorderStyle{
|
||||||
shape: shape,
|
shape: shape,
|
||||||
horizontal: '─',
|
top: '-',
|
||||||
vertical: '│',
|
bottom: '-',
|
||||||
|
left: '|',
|
||||||
|
right: '|',
|
||||||
|
topLeft: '+',
|
||||||
|
topRight: '+',
|
||||||
|
bottomLeft: '+',
|
||||||
|
bottomRight: '+',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch shape {
|
||||||
|
case BorderSharp:
|
||||||
|
return BorderStyle{
|
||||||
|
shape: shape,
|
||||||
|
top: '─',
|
||||||
|
bottom: '─',
|
||||||
|
left: '│',
|
||||||
|
right: '│',
|
||||||
topLeft: '┌',
|
topLeft: '┌',
|
||||||
topRight: '┐',
|
topRight: '┐',
|
||||||
bottomLeft: '└',
|
bottomLeft: '└',
|
||||||
bottomRight: '┘',
|
bottomRight: '┘',
|
||||||
}
|
}
|
||||||
|
case BorderBold:
|
||||||
|
return BorderStyle{
|
||||||
|
shape: shape,
|
||||||
|
top: '━',
|
||||||
|
bottom: '━',
|
||||||
|
left: '┃',
|
||||||
|
right: '┃',
|
||||||
|
topLeft: '┏',
|
||||||
|
topRight: '┓',
|
||||||
|
bottomLeft: '┗',
|
||||||
|
bottomRight: '┛',
|
||||||
|
}
|
||||||
|
case BorderBlock:
|
||||||
|
// ▛▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▜
|
||||||
|
// ▌ ▐
|
||||||
|
// ▙▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▟
|
||||||
|
return BorderStyle{
|
||||||
|
shape: shape,
|
||||||
|
top: '▀',
|
||||||
|
bottom: '▄',
|
||||||
|
left: '▌',
|
||||||
|
right: '▐',
|
||||||
|
topLeft: '▛',
|
||||||
|
topRight: '▜',
|
||||||
|
bottomLeft: '▙',
|
||||||
|
bottomRight: '▟',
|
||||||
|
}
|
||||||
|
|
||||||
|
case BorderThinBlock:
|
||||||
|
// 🭽▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔🭾
|
||||||
|
// ▏ ▕
|
||||||
|
// 🭼▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁🭿
|
||||||
|
return BorderStyle{
|
||||||
|
shape: shape,
|
||||||
|
top: '▔',
|
||||||
|
bottom: '▁',
|
||||||
|
left: '▏',
|
||||||
|
right: '▕',
|
||||||
|
topLeft: '🭽',
|
||||||
|
topRight: '🭾',
|
||||||
|
bottomLeft: '🭼',
|
||||||
|
bottomRight: '🭿',
|
||||||
|
}
|
||||||
|
|
||||||
|
case BorderDouble:
|
||||||
|
return BorderStyle{
|
||||||
|
shape: shape,
|
||||||
|
top: '═',
|
||||||
|
bottom: '═',
|
||||||
|
left: '║',
|
||||||
|
right: '║',
|
||||||
|
topLeft: '╔',
|
||||||
|
topRight: '╗',
|
||||||
|
bottomLeft: '╚',
|
||||||
|
bottomRight: '╝',
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return BorderStyle{
|
return BorderStyle{
|
||||||
shape: shape,
|
shape: shape,
|
||||||
horizontal: '-',
|
top: '─',
|
||||||
vertical: '|',
|
bottom: '─',
|
||||||
topLeft: '+',
|
left: '│',
|
||||||
topRight: '+',
|
right: '│',
|
||||||
bottomLeft: '+',
|
topLeft: '╭',
|
||||||
bottomRight: '+',
|
topRight: '╮',
|
||||||
|
bottomLeft: '╰',
|
||||||
|
bottomRight: '╯',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeTransparentBorder() BorderStyle {
|
func MakeTransparentBorder() BorderStyle {
|
||||||
return BorderStyle{
|
return BorderStyle{
|
||||||
shape: BorderRounded,
|
shape: BorderRounded,
|
||||||
horizontal: ' ',
|
top: ' ',
|
||||||
vertical: ' ',
|
bottom: ' ',
|
||||||
|
left: ' ',
|
||||||
|
right: ' ',
|
||||||
topLeft: ' ',
|
topLeft: ' ',
|
||||||
topRight: ' ',
|
topRight: ' ',
|
||||||
bottomLeft: ' ',
|
bottomLeft: ' ',
|
||||||
@@ -358,12 +467,14 @@ 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()
|
||||||
RefreshWindows(windows []Window)
|
RefreshWindows(windows []Window)
|
||||||
Refresh()
|
Refresh()
|
||||||
Close()
|
Close()
|
||||||
|
NeedScrollbarRedraw() bool
|
||||||
|
|
||||||
GetChar() Event
|
GetChar() Event
|
||||||
|
|
||||||
@@ -379,6 +490,7 @@ type Window interface {
|
|||||||
Width() int
|
Width() int
|
||||||
Height() int
|
Height() int
|
||||||
|
|
||||||
|
DrawHBorder()
|
||||||
Refresh()
|
Refresh()
|
||||||
FinishFill()
|
FinishFill()
|
||||||
Close()
|
Close()
|
||||||
@@ -401,7 +513,7 @@ type FullscreenRenderer struct {
|
|||||||
mouse bool
|
mouse bool
|
||||||
forceBlack bool
|
forceBlack bool
|
||||||
prevDownTime time.Time
|
prevDownTime time.Time
|
||||||
clickY []int
|
clicks [][2]int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFullscreenRenderer(theme *ColorTheme, forceBlack bool, mouse bool) Renderer {
|
func NewFullscreenRenderer(theme *ColorTheme, forceBlack bool, mouse bool) Renderer {
|
||||||
@@ -410,7 +522,7 @@ func NewFullscreenRenderer(theme *ColorTheme, forceBlack bool, mouse bool) Rende
|
|||||||
mouse: mouse,
|
mouse: mouse,
|
||||||
forceBlack: forceBlack,
|
forceBlack: forceBlack,
|
||||||
prevDownTime: time.Unix(0, 0),
|
prevDownTime: time.Unix(0, 0),
|
||||||
clickY: []int{}}
|
clicks: [][2]int{}}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,55 +548,75 @@ var (
|
|||||||
ColSpinner ColorPair
|
ColSpinner ColorPair
|
||||||
ColInfo ColorPair
|
ColInfo ColorPair
|
||||||
ColHeader ColorPair
|
ColHeader ColorPair
|
||||||
|
ColSeparator ColorPair
|
||||||
|
ColScrollbar ColorPair
|
||||||
ColBorder ColorPair
|
ColBorder ColorPair
|
||||||
ColPreview ColorPair
|
ColPreview ColorPair
|
||||||
ColPreviewBorder ColorPair
|
ColPreviewBorder ColorPair
|
||||||
|
ColBorderLabel ColorPair
|
||||||
|
ColPreviewLabel ColorPair
|
||||||
|
ColPreviewScrollbar ColorPair
|
||||||
|
ColPreviewSpinner ColorPair
|
||||||
)
|
)
|
||||||
|
|
||||||
func EmptyTheme() *ColorTheme {
|
func EmptyTheme() *ColorTheme {
|
||||||
return &ColorTheme{
|
return &ColorTheme{
|
||||||
Colored: true,
|
Colored: true,
|
||||||
Input: ColorAttr{colUndefined, AttrUndefined},
|
Input: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Disabled: ColorAttr{colUndefined, AttrUndefined},
|
Fg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Fg: ColorAttr{colUndefined, AttrUndefined},
|
Bg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Bg: ColorAttr{colUndefined, AttrUndefined},
|
DarkBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
|
Prompt: ColorAttr{colUndefined, AttrUndefined},
|
||||||
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
|
Match: ColorAttr{colUndefined, AttrUndefined},
|
||||||
DarkBg: ColorAttr{colUndefined, AttrUndefined},
|
Current: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Gutter: ColorAttr{colUndefined, AttrUndefined},
|
CurrentMatch: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Prompt: ColorAttr{colUndefined, AttrUndefined},
|
Spinner: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Match: ColorAttr{colUndefined, AttrUndefined},
|
Info: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Current: ColorAttr{colUndefined, AttrUndefined},
|
Cursor: ColorAttr{colUndefined, AttrUndefined},
|
||||||
CurrentMatch: ColorAttr{colUndefined, AttrUndefined},
|
Selected: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Spinner: ColorAttr{colUndefined, AttrUndefined},
|
Header: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Info: ColorAttr{colUndefined, AttrUndefined},
|
Border: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Cursor: ColorAttr{colUndefined, AttrUndefined},
|
BorderLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Selected: ColorAttr{colUndefined, AttrUndefined},
|
Disabled: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Header: ColorAttr{colUndefined, AttrUndefined},
|
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Border: ColorAttr{colUndefined, AttrUndefined}}
|
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
Gutter: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
PreviewBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
Separator: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
Scrollbar: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NoColorTheme() *ColorTheme {
|
func NoColorTheme() *ColorTheme {
|
||||||
return &ColorTheme{
|
return &ColorTheme{
|
||||||
Colored: false,
|
Colored: false,
|
||||||
Input: ColorAttr{colDefault, AttrRegular},
|
Input: ColorAttr{colDefault, AttrUndefined},
|
||||||
Disabled: ColorAttr{colDefault, AttrRegular},
|
Fg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Fg: ColorAttr{colDefault, AttrRegular},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Bg: ColorAttr{colDefault, AttrRegular},
|
DarkBg: ColorAttr{colDefault, AttrUndefined},
|
||||||
PreviewFg: ColorAttr{colDefault, AttrRegular},
|
Prompt: ColorAttr{colDefault, AttrUndefined},
|
||||||
PreviewBg: ColorAttr{colDefault, AttrRegular},
|
Match: ColorAttr{colDefault, Underline},
|
||||||
DarkBg: ColorAttr{colDefault, AttrRegular},
|
Current: ColorAttr{colDefault, Reverse},
|
||||||
Gutter: ColorAttr{colDefault, AttrRegular},
|
CurrentMatch: ColorAttr{colDefault, Reverse | Underline},
|
||||||
Prompt: ColorAttr{colDefault, AttrRegular},
|
Spinner: ColorAttr{colDefault, AttrUndefined},
|
||||||
Match: ColorAttr{colDefault, Underline},
|
Info: ColorAttr{colDefault, AttrUndefined},
|
||||||
Current: ColorAttr{colDefault, Reverse},
|
Cursor: ColorAttr{colDefault, AttrUndefined},
|
||||||
CurrentMatch: ColorAttr{colDefault, Reverse | Underline},
|
Selected: ColorAttr{colDefault, AttrUndefined},
|
||||||
Spinner: ColorAttr{colDefault, AttrRegular},
|
Header: ColorAttr{colDefault, AttrUndefined},
|
||||||
Info: ColorAttr{colDefault, AttrRegular},
|
Border: ColorAttr{colDefault, AttrUndefined},
|
||||||
Cursor: ColorAttr{colDefault, AttrRegular},
|
BorderLabel: ColorAttr{colDefault, AttrUndefined},
|
||||||
Selected: ColorAttr{colDefault, AttrRegular},
|
Disabled: ColorAttr{colDefault, AttrUndefined},
|
||||||
Header: ColorAttr{colDefault, AttrRegular},
|
PreviewFg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Border: ColorAttr{colDefault, AttrRegular}}
|
PreviewBg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
Gutter: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
PreviewBorder: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
PreviewScrollbar: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
PreviewLabel: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
Separator: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
Scrollbar: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func errorExit(message string) {
|
func errorExit(message string) {
|
||||||
@@ -494,65 +626,86 @@ func errorExit(message string) {
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
Default16 = &ColorTheme{
|
Default16 = &ColorTheme{
|
||||||
Colored: true,
|
Colored: true,
|
||||||
Input: ColorAttr{colDefault, AttrUndefined},
|
Input: ColorAttr{colDefault, AttrUndefined},
|
||||||
Disabled: ColorAttr{colUndefined, AttrUndefined},
|
Fg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Fg: ColorAttr{colDefault, AttrUndefined},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
DarkBg: ColorAttr{colBlack, AttrUndefined},
|
||||||
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
|
Prompt: ColorAttr{colBlue, AttrUndefined},
|
||||||
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
|
Match: ColorAttr{colGreen, AttrUndefined},
|
||||||
DarkBg: ColorAttr{colBlack, AttrUndefined},
|
Current: ColorAttr{colYellow, AttrUndefined},
|
||||||
Gutter: ColorAttr{colUndefined, AttrUndefined},
|
CurrentMatch: ColorAttr{colGreen, AttrUndefined},
|
||||||
Prompt: ColorAttr{colBlue, AttrUndefined},
|
Spinner: ColorAttr{colGreen, AttrUndefined},
|
||||||
Match: ColorAttr{colGreen, AttrUndefined},
|
Info: ColorAttr{colWhite, AttrUndefined},
|
||||||
Current: ColorAttr{colYellow, AttrUndefined},
|
Cursor: ColorAttr{colRed, AttrUndefined},
|
||||||
CurrentMatch: ColorAttr{colGreen, AttrUndefined},
|
Selected: ColorAttr{colMagenta, AttrUndefined},
|
||||||
Spinner: ColorAttr{colGreen, AttrUndefined},
|
Header: ColorAttr{colCyan, AttrUndefined},
|
||||||
Info: ColorAttr{colWhite, AttrUndefined},
|
Border: ColorAttr{colBlack, AttrUndefined},
|
||||||
Cursor: ColorAttr{colRed, AttrUndefined},
|
BorderLabel: ColorAttr{colWhite, AttrUndefined},
|
||||||
Selected: ColorAttr{colMagenta, AttrUndefined},
|
Disabled: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Header: ColorAttr{colCyan, AttrUndefined},
|
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Border: ColorAttr{colBlack, AttrUndefined}}
|
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
Gutter: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
PreviewBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
Separator: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
Scrollbar: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
}
|
||||||
Dark256 = &ColorTheme{
|
Dark256 = &ColorTheme{
|
||||||
Colored: true,
|
Colored: true,
|
||||||
Input: ColorAttr{colDefault, AttrUndefined},
|
Input: ColorAttr{colDefault, AttrUndefined},
|
||||||
Disabled: ColorAttr{colUndefined, AttrUndefined},
|
Fg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Fg: ColorAttr{colDefault, AttrUndefined},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
DarkBg: ColorAttr{236, AttrUndefined},
|
||||||
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
|
Prompt: ColorAttr{110, AttrUndefined},
|
||||||
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
|
Match: ColorAttr{108, AttrUndefined},
|
||||||
DarkBg: ColorAttr{236, AttrUndefined},
|
Current: ColorAttr{254, AttrUndefined},
|
||||||
Gutter: ColorAttr{colUndefined, AttrUndefined},
|
CurrentMatch: ColorAttr{151, AttrUndefined},
|
||||||
Prompt: ColorAttr{110, AttrUndefined},
|
Spinner: ColorAttr{148, AttrUndefined},
|
||||||
Match: ColorAttr{108, AttrUndefined},
|
Info: ColorAttr{144, AttrUndefined},
|
||||||
Current: ColorAttr{254, AttrUndefined},
|
Cursor: ColorAttr{161, AttrUndefined},
|
||||||
CurrentMatch: ColorAttr{151, AttrUndefined},
|
Selected: ColorAttr{168, AttrUndefined},
|
||||||
Spinner: ColorAttr{148, AttrUndefined},
|
Header: ColorAttr{109, AttrUndefined},
|
||||||
Info: ColorAttr{144, AttrUndefined},
|
Border: ColorAttr{59, AttrUndefined},
|
||||||
Cursor: ColorAttr{161, AttrUndefined},
|
BorderLabel: ColorAttr{145, AttrUndefined},
|
||||||
Selected: ColorAttr{168, AttrUndefined},
|
Disabled: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Header: ColorAttr{109, AttrUndefined},
|
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Border: ColorAttr{59, AttrUndefined}}
|
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
Gutter: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
PreviewBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
Separator: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
Scrollbar: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
}
|
||||||
Light256 = &ColorTheme{
|
Light256 = &ColorTheme{
|
||||||
Colored: true,
|
Colored: true,
|
||||||
Input: ColorAttr{colDefault, AttrUndefined},
|
Input: ColorAttr{colDefault, AttrUndefined},
|
||||||
Disabled: ColorAttr{colUndefined, AttrUndefined},
|
Fg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Fg: ColorAttr{colDefault, AttrUndefined},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
DarkBg: ColorAttr{251, AttrUndefined},
|
||||||
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
|
Prompt: ColorAttr{25, AttrUndefined},
|
||||||
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
|
Match: ColorAttr{66, AttrUndefined},
|
||||||
DarkBg: ColorAttr{251, AttrUndefined},
|
Current: ColorAttr{237, AttrUndefined},
|
||||||
Gutter: ColorAttr{colUndefined, AttrUndefined},
|
CurrentMatch: ColorAttr{23, AttrUndefined},
|
||||||
Prompt: ColorAttr{25, AttrUndefined},
|
Spinner: ColorAttr{65, AttrUndefined},
|
||||||
Match: ColorAttr{66, AttrUndefined},
|
Info: ColorAttr{101, AttrUndefined},
|
||||||
Current: ColorAttr{237, AttrUndefined},
|
Cursor: ColorAttr{161, AttrUndefined},
|
||||||
CurrentMatch: ColorAttr{23, AttrUndefined},
|
Selected: ColorAttr{168, AttrUndefined},
|
||||||
Spinner: ColorAttr{65, AttrUndefined},
|
Header: ColorAttr{31, AttrUndefined},
|
||||||
Info: ColorAttr{101, AttrUndefined},
|
Border: ColorAttr{145, AttrUndefined},
|
||||||
Cursor: ColorAttr{161, AttrUndefined},
|
BorderLabel: ColorAttr{59, AttrUndefined},
|
||||||
Selected: ColorAttr{168, AttrUndefined},
|
Disabled: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Header: ColorAttr{31, AttrUndefined},
|
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Border: ColorAttr{145, AttrUndefined}}
|
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
Gutter: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
PreviewBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
Separator: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
Scrollbar: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
|
func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
|
||||||
@@ -571,13 +724,9 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
theme.Input = o(baseTheme.Input, theme.Input)
|
theme.Input = o(baseTheme.Input, theme.Input)
|
||||||
theme.Disabled = o(theme.Input, o(baseTheme.Disabled, theme.Disabled))
|
|
||||||
theme.Fg = o(baseTheme.Fg, theme.Fg)
|
theme.Fg = o(baseTheme.Fg, theme.Fg)
|
||||||
theme.Bg = o(baseTheme.Bg, theme.Bg)
|
theme.Bg = o(baseTheme.Bg, theme.Bg)
|
||||||
theme.PreviewFg = o(theme.Fg, o(baseTheme.PreviewFg, theme.PreviewFg))
|
|
||||||
theme.PreviewBg = o(theme.Bg, o(baseTheme.PreviewBg, theme.PreviewBg))
|
|
||||||
theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg)
|
theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg)
|
||||||
theme.Gutter = o(theme.DarkBg, o(baseTheme.Gutter, theme.Gutter))
|
|
||||||
theme.Prompt = o(baseTheme.Prompt, theme.Prompt)
|
theme.Prompt = o(baseTheme.Prompt, theme.Prompt)
|
||||||
theme.Match = o(baseTheme.Match, theme.Match)
|
theme.Match = o(baseTheme.Match, theme.Match)
|
||||||
theme.Current = o(baseTheme.Current, theme.Current)
|
theme.Current = o(baseTheme.Current, theme.Current)
|
||||||
@@ -588,6 +737,18 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
|
|||||||
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.Border = o(baseTheme.Border, theme.Border)
|
theme.Border = o(baseTheme.Border, theme.Border)
|
||||||
|
theme.BorderLabel = o(baseTheme.BorderLabel, theme.BorderLabel)
|
||||||
|
|
||||||
|
// These colors are not defined in the base themes
|
||||||
|
theme.Disabled = o(theme.Input, theme.Disabled)
|
||||||
|
theme.Gutter = o(theme.DarkBg, theme.Gutter)
|
||||||
|
theme.PreviewFg = o(theme.Fg, theme.PreviewFg)
|
||||||
|
theme.PreviewBg = o(theme.Bg, theme.PreviewBg)
|
||||||
|
theme.PreviewLabel = o(theme.BorderLabel, theme.PreviewLabel)
|
||||||
|
theme.PreviewBorder = o(theme.Border, theme.PreviewBorder)
|
||||||
|
theme.Separator = o(theme.Border, theme.Separator)
|
||||||
|
theme.Scrollbar = o(theme.Border, theme.Scrollbar)
|
||||||
|
theme.PreviewScrollbar = o(theme.PreviewBorder, theme.PreviewScrollbar)
|
||||||
|
|
||||||
initPalette(theme)
|
initPalette(theme)
|
||||||
}
|
}
|
||||||
@@ -619,7 +780,13 @@ 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)
|
||||||
|
ColScrollbar = pair(theme.Scrollbar, theme.Bg)
|
||||||
ColBorder = pair(theme.Border, theme.Bg)
|
ColBorder = pair(theme.Border, theme.Bg)
|
||||||
|
ColBorderLabel = pair(theme.BorderLabel, theme.Bg)
|
||||||
|
ColPreviewLabel = pair(theme.PreviewLabel, theme.PreviewBg)
|
||||||
ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
|
ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
|
||||||
ColPreviewBorder = pair(theme.Border, theme.PreviewBg)
|
ColPreviewBorder = pair(theme.PreviewBorder, theme.PreviewBg)
|
||||||
|
ColPreviewScrollbar = pair(theme.PreviewScrollbar, theme.PreviewBg)
|
||||||
|
ColPreviewSpinner = pair(theme.Spinner, theme.PreviewBg)
|
||||||
}
|
}
|
||||||
|
@@ -9,7 +9,6 @@ const (
|
|||||||
EvtSearchNew
|
EvtSearchNew
|
||||||
EvtSearchProgress
|
EvtSearchProgress
|
||||||
EvtSearchFin
|
EvtSearchFin
|
||||||
EvtClose
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEventBox(t *testing.T) {
|
func TestEventBox(t *testing.T) {
|
||||||
|
@@ -11,6 +11,11 @@ import (
|
|||||||
"github.com/rivo/uniseg"
|
"github.com/rivo/uniseg"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// StringWidth returns string width where each CR/LF character takes 1 column
|
||||||
|
func StringWidth(s string) int {
|
||||||
|
return runewidth.StringWidth(s) + strings.Count(s, "\n") + strings.Count(s, "\r")
|
||||||
|
}
|
||||||
|
|
||||||
// RunesWidth returns runes width
|
// RunesWidth returns runes width
|
||||||
func RunesWidth(runes []rune, prefixWidth int, tabstop int, limit int) (int, int) {
|
func RunesWidth(runes []rune, prefixWidth int, tabstop int, limit int) (int, int) {
|
||||||
width := 0
|
width := 0
|
||||||
@@ -22,8 +27,7 @@ func RunesWidth(runes []rune, prefixWidth int, tabstop int, limit int) (int, int
|
|||||||
if len(rs) == 1 && rs[0] == '\t' {
|
if len(rs) == 1 && rs[0] == '\t' {
|
||||||
w = tabstop - (prefixWidth+width)%tabstop
|
w = tabstop - (prefixWidth+width)%tabstop
|
||||||
} else {
|
} else {
|
||||||
s := string(rs)
|
w = StringWidth(string(rs))
|
||||||
w = runewidth.StringWidth(s) + strings.Count(s, "\n")
|
|
||||||
}
|
}
|
||||||
width += w
|
width += w
|
||||||
if width > limit {
|
if width > limit {
|
||||||
@@ -34,6 +38,23 @@ func RunesWidth(runes []rune, prefixWidth int, tabstop int, limit int) (int, int
|
|||||||
return width, -1
|
return width, -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Truncate returns the truncated runes and its width
|
||||||
|
func Truncate(input string, limit int) ([]rune, int) {
|
||||||
|
runes := []rune{}
|
||||||
|
width := 0
|
||||||
|
gr := uniseg.NewGraphemes(input)
|
||||||
|
for gr.Next() {
|
||||||
|
rs := gr.Runes()
|
||||||
|
w := StringWidth(string(rs))
|
||||||
|
if width+w > limit {
|
||||||
|
return runes, width
|
||||||
|
}
|
||||||
|
width += w
|
||||||
|
runes = append(runes, rs...)
|
||||||
|
}
|
||||||
|
return runes, width
|
||||||
|
}
|
||||||
|
|
||||||
// Max returns the largest integer
|
// Max returns the largest integer
|
||||||
func Max(first int, second int) int {
|
func Max(first int, second int) int {
|
||||||
if first >= second {
|
if first >= second {
|
||||||
@@ -136,3 +157,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
|
||||||
|
}
|
||||||
|
@@ -1,14 +1,76 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
func TestMax(t *testing.T) {
|
func TestMax(t *testing.T) {
|
||||||
|
if Max(10, 1) != 10 {
|
||||||
|
t.Error("Expected", 10)
|
||||||
|
}
|
||||||
if Max(-2, 5) != 5 {
|
if Max(-2, 5) != 5 {
|
||||||
t.Error("Invalid result")
|
t.Error("Expected", 5)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContrain(t *testing.T) {
|
func TestMax16(t *testing.T) {
|
||||||
|
if Max16(10, 1) != 10 {
|
||||||
|
t.Error("Expected", 10)
|
||||||
|
}
|
||||||
|
if Max16(-2, 5) != 5 {
|
||||||
|
t.Error("Expected", 5)
|
||||||
|
}
|
||||||
|
if Max16(math.MaxInt16, 0) != math.MaxInt16 {
|
||||||
|
t.Error("Expected", math.MaxInt16)
|
||||||
|
}
|
||||||
|
if Max16(0, math.MinInt16) != 0 {
|
||||||
|
t.Error("Expected", 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMax32(t *testing.T) {
|
||||||
|
if Max32(10, 1) != 10 {
|
||||||
|
t.Error("Expected", 10)
|
||||||
|
}
|
||||||
|
if Max32(-2, 5) != 5 {
|
||||||
|
t.Error("Expected", 5)
|
||||||
|
}
|
||||||
|
if Max32(math.MaxInt32, 0) != math.MaxInt32 {
|
||||||
|
t.Error("Expected", math.MaxInt32)
|
||||||
|
}
|
||||||
|
if Max32(0, math.MinInt32) != 0 {
|
||||||
|
t.Error("Expected", 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMin(t *testing.T) {
|
||||||
|
if Min(10, 1) != 1 {
|
||||||
|
t.Error("Expected", 1)
|
||||||
|
}
|
||||||
|
if Min(-2, 5) != -2 {
|
||||||
|
t.Error("Expected", -2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMin32(t *testing.T) {
|
||||||
|
if Min32(10, 1) != 1 {
|
||||||
|
t.Error("Expected", 1)
|
||||||
|
}
|
||||||
|
if Min32(-2, 5) != -2 {
|
||||||
|
t.Error("Expected", -2)
|
||||||
|
}
|
||||||
|
if Min32(math.MaxInt32, 0) != 0 {
|
||||||
|
t.Error("Expected", 0)
|
||||||
|
}
|
||||||
|
if Min32(0, math.MinInt32) != math.MinInt32 {
|
||||||
|
t.Error("Expected", math.MinInt32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConstrain(t *testing.T) {
|
||||||
if Constrain(-3, -1, 3) != -1 {
|
if Constrain(-3, -1, 3) != -1 {
|
||||||
t.Error("Expected", -1)
|
t.Error("Expected", -1)
|
||||||
}
|
}
|
||||||
@@ -21,6 +83,55 @@ func TestContrain(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConstrain32(t *testing.T) {
|
||||||
|
if Constrain32(-3, -1, 3) != -1 {
|
||||||
|
t.Error("Expected", -1)
|
||||||
|
}
|
||||||
|
if Constrain32(2, -1, 3) != 2 {
|
||||||
|
t.Error("Expected", 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
if Constrain32(5, -1, 3) != 3 {
|
||||||
|
t.Error("Expected", 3)
|
||||||
|
}
|
||||||
|
if Constrain32(0, math.MinInt32, math.MaxInt32) != 0 {
|
||||||
|
t.Error("Expected", 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAsUint16(t *testing.T) {
|
||||||
|
if AsUint16(5) != 5 {
|
||||||
|
t.Error("Expected", 5)
|
||||||
|
}
|
||||||
|
if AsUint16(-10) != 0 {
|
||||||
|
t.Error("Expected", 0)
|
||||||
|
}
|
||||||
|
if AsUint16(math.MaxUint16) != math.MaxUint16 {
|
||||||
|
t.Error("Expected", math.MaxUint16)
|
||||||
|
}
|
||||||
|
if AsUint16(math.MinInt32) != 0 {
|
||||||
|
t.Error("Expected", 0)
|
||||||
|
}
|
||||||
|
if AsUint16(math.MinInt16) != 0 {
|
||||||
|
t.Error("Expected", 0)
|
||||||
|
}
|
||||||
|
if AsUint16(math.MaxUint16+1) != math.MaxUint16 {
|
||||||
|
t.Error("Expected", math.MaxUint16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDurWithIn(t *testing.T) {
|
||||||
|
if DurWithin(time.Duration(5), time.Duration(1), time.Duration(8)) != time.Duration(5) {
|
||||||
|
t.Error("Expected", time.Duration(0))
|
||||||
|
}
|
||||||
|
if DurWithin(time.Duration(0)*time.Second, time.Second, time.Duration(3)*time.Second) != time.Second {
|
||||||
|
t.Error("Expected", time.Second)
|
||||||
|
}
|
||||||
|
if DurWithin(time.Duration(10)*time.Second, time.Duration(0), time.Second) != time.Second {
|
||||||
|
t.Error("Expected", time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestOnce(t *testing.T) {
|
func TestOnce(t *testing.T) {
|
||||||
o := Once(false)
|
o := Once(false)
|
||||||
if o() {
|
if o() {
|
||||||
@@ -54,3 +165,22 @@ func TestRunesWidth(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTruncate(t *testing.T) {
|
||||||
|
truncated, width := Truncate("가나다라마", 7)
|
||||||
|
if string(truncated) != "가나다" {
|
||||||
|
t.Errorf("Expected: 가나다, actual: %s", string(truncated))
|
||||||
|
}
|
||||||
|
if width != 6 {
|
||||||
|
t.Errorf("Expected: 6, actual: %d", width)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRepeatToFill(t *testing.T) {
|
||||||
|
if RepeatToFill("abcde", 10, 50) != strings.Repeat("abcde", 5) {
|
||||||
|
t.Error("Expected:", strings.Repeat("abcde", 5))
|
||||||
|
}
|
||||||
|
if RepeatToFill("abcde", 10, 42) != strings.Repeat("abcde", 4)+"abcde"[:2] {
|
||||||
|
t.Error("Expected:", strings.Repeat("abcde", 4)+"abcde"[:2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
// +build !windows
|
//go:build !windows
|
||||||
|
|
||||||
package util
|
package util
|
||||||
|
|
||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExecCommand executes the given command with $SHELL
|
// ExecCommand executes the given command with $SHELL
|
||||||
@@ -45,3 +47,7 @@ func SetNonblock(file *os.File, nonblock bool) {
|
|||||||
func Read(fd int, b []byte) (int, error) {
|
func Read(fd int, b []byte) (int, error) {
|
||||||
return syscall.Read(int(fd), b)
|
return syscall.Read(int(fd), b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetStdin(file *os.File) {
|
||||||
|
unix.Dup2(int(file.Fd()), 0)
|
||||||
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
// +build windows
|
//go:build windows
|
||||||
|
|
||||||
package util
|
package util
|
||||||
|
|
||||||
@@ -81,3 +81,7 @@ func SetNonblock(file *os.File, nonblock bool) {
|
|||||||
func Read(fd int, b []byte) (int, error) {
|
func Read(fd int, b []byte) (int, error) {
|
||||||
return syscall.Read(syscall.Handle(fd), b)
|
return syscall.Read(syscall.Handle(fd), b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetStdin(file *os.File) {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
940
test/test_go.rb
940
test/test_go.rb
File diff suppressed because it is too large
Load Diff
6
typos.toml
Normal file
6
typos.toml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# See https://github.com/crate-ci/typos/blob/master/docs/reference.md to configure typos
|
||||||
|
[default.extend-words]
|
||||||
|
ba = "ba"
|
||||||
|
fo = "fo"
|
||||||
|
enew = "enew"
|
||||||
|
tabe = "tabe"
|
14
uninstall
14
uninstall
@@ -51,13 +51,8 @@ remove() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
remove_line() {
|
remove_line() {
|
||||||
src=$(readlink "$1")
|
src=$1
|
||||||
if [ $? -eq 0 ]; then
|
echo "Remove from $1:"
|
||||||
echo "Remove from $1 ($src):"
|
|
||||||
else
|
|
||||||
src=$1
|
|
||||||
echo "Remove from $1:"
|
|
||||||
fi
|
|
||||||
|
|
||||||
shift
|
shift
|
||||||
line_no=1
|
line_no=1
|
||||||
@@ -75,8 +70,9 @@ remove_line() {
|
|||||||
echo " - Line #$line_no: $content"
|
echo " - Line #$line_no: $content"
|
||||||
[ "$content" = "$1" ] || ask " - Remove?"
|
[ "$content" = "$1" ] || ask " - Remove?"
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
awk -v n=$line_no 'NR == n {next} {print}' "$src" > "$src.bak" &&
|
temp=$(mktemp)
|
||||||
mv "$src.bak" "$src" || break
|
awk -v n=$line_no 'NR == n {next} {print}' "$src" > "$temp" &&
|
||||||
|
cat "$temp" > "$src" && rm -f "$temp" || break
|
||||||
echo " - Removed"
|
echo " - Removed"
|
||||||
else
|
else
|
||||||
echo " - Skipped"
|
echo " - Skipped"
|
||||||
|
Reference in New Issue
Block a user