mirror of
https://github.com/junegunn/fzf.git
synced 2025-08-24 00:43:49 -07:00
Compare commits
301 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
9e92b6f11e | ||
|
6cbde812f6 | ||
|
3b2e932c13 | ||
|
8ff4e52641 | ||
|
2dbc874e3d | ||
|
039a2f1d04 | ||
|
4ef1cf5b35 | ||
|
b44ab9e33c | ||
|
8f4c23f1c4 | ||
|
23a391e715 | ||
|
035b0be29f | ||
|
e1fcdbc337 | ||
|
cfc149e994 | ||
|
2faffbd1b7 | ||
|
8db65704b9 | ||
|
797a01aed4 | ||
|
bf515a3d32 | ||
|
a06745826a | ||
|
0420ed4f2a | ||
|
3b944addd4 | ||
|
70bf8bc35d | ||
|
724f8a1d45 | ||
|
cc2b2146ee | ||
|
8689f5f230 | ||
|
e9e0011f1d | ||
|
5b52833785 | ||
|
1525768094 | ||
|
a70ea4654e | ||
|
b02bf9b6bb | ||
|
bee7bc5324 | ||
|
7c2ffd3fef | ||
|
db01e7dab6 | ||
|
2326c74eb2 | ||
|
b9d15569e8 | ||
|
c3cc378d89 | ||
|
27d1f5e0a8 | ||
|
540632bb9e | ||
|
d9c028c934 | ||
|
c54ad82e8d | ||
|
295b89631b | ||
|
6179faf778 | ||
|
16dc236a25 | ||
|
61ae9d75b6 | ||
|
e2401aca68 | ||
|
59943cbb48 | ||
|
02634d404d | ||
|
ed12925f7d | ||
|
e0ddb97ab4 | ||
|
b8c01af0fc | ||
|
6de0a7ddc1 | ||
|
79196c025d | ||
|
94c33ac020 | ||
|
b2ecb6352c | ||
|
9dc3ed638a | ||
|
0acace1ace | ||
|
1a2d37e1e6 | ||
|
22adb6494f | ||
|
e023736c30 | ||
|
dca2262fe6 | ||
|
0684a20ea3 | ||
|
a1a72bb8d1 | ||
|
144d55a5be | ||
|
7fc13c5cfd | ||
|
dfee7af57b | ||
|
9b0e2daf02 | ||
|
590060a16b | ||
|
368294edf6 | ||
|
c4a9ccd6af | ||
|
cbf91f2ed3 | ||
|
b1460d4787 | ||
|
7dc9e14874 | ||
|
1616ed543d | ||
|
dc73fba188 | ||
|
ef148dfd37 | ||
|
93bbb3032d | ||
|
4c83d8596d | ||
|
d453e6d7db | ||
|
c29533994f | ||
|
1afe13b5b5 | ||
|
36600eaaa9 | ||
|
3ee1fc2034 | ||
|
e2f93e5a2d | ||
|
cfdf2f1153 | ||
|
e042143e3f | ||
|
7c613d0d9b | ||
|
b00d46bc14 | ||
|
555b0d235b | ||
|
564daf9a7d | ||
|
41bcbe342f | ||
|
dbe8dc344e | ||
|
e33fb59da1 | ||
|
7aa88aa115 | ||
|
2b6d600879 | ||
|
05c765d442 | ||
|
49b496269c | ||
|
7405925952 | ||
|
3afd543a7e | ||
|
b4f2cde5ac | ||
|
ed53ef7cee | ||
|
12630b124d | ||
|
1d59ac09d2 | ||
|
a8f3a0dd59 | ||
|
124cd70710 | ||
|
782de139c8 | ||
|
32eb32ee5e | ||
|
2f51eb2b41 | ||
|
0ccbd79e10 | ||
|
99bd6de541 | ||
|
1fef36e4bc | ||
|
89375005b5 | ||
|
88e78c9193 | ||
|
29a19ad080 | ||
|
2a039ab746 | ||
|
7e9a0fcdbd | ||
|
7a97532547 | ||
|
996abb2831 | ||
|
da500a358f | ||
|
c36b846acc | ||
|
d9b5c9b2be | ||
|
3dee8778d0 | ||
|
d4216b0dcc | ||
|
bfe2bf4dce | ||
|
561f9291fd | ||
|
b5b0d6b3ea | ||
|
a90426b7ca | ||
|
303c3bae7f | ||
|
6b4358f641 | ||
|
552158f3ad | ||
|
7205203dc8 | ||
|
0cadf70072 | ||
|
076b3d0a9a | ||
|
7b0c9e04d3 | ||
|
573df524fe | ||
|
aee417c46a | ||
|
04db44067d | ||
|
5b204c54f9 | ||
|
daa602422d | ||
|
04dfb14e32 | ||
|
c24256cba3 | ||
|
685fb71d89 | ||
|
83b6033906 | ||
|
01e7668915 | ||
|
0994d9c881 | ||
|
030428ba43 | ||
|
8a110e02b9 | ||
|
86d92c17c4 | ||
|
c4cc7891b4 | ||
|
218843b9f1 | ||
|
d274d093af | ||
|
6432f00f0d | ||
|
4e9e842aa4 | ||
|
07880ca441 | ||
|
bcda25a513 | ||
|
8256fcde15 | ||
|
af65aa298a | ||
|
6834d17844 | ||
|
ed511d7867 | ||
|
cd8d736a9f | ||
|
0952b2dfd4 | ||
|
4bedd33c59 | ||
|
c5fb0c43f9 | ||
|
9e4780510e | ||
|
e8405f40fe | ||
|
065b9e6fb2 | ||
|
98141ca7d8 | ||
|
501577ab28 | ||
|
5669f48343 | ||
|
24ff66d4a9 | ||
|
bf184449bc | ||
|
7b98c2c653 | ||
|
b6add2a257 | ||
|
2bd41f1330 | ||
|
c37cd11ca5 | ||
|
9dee8edc0c | ||
|
f6aa28c380 | ||
|
dba1644518 | ||
|
260a65b0fb | ||
|
835d2fb98c | ||
|
a9811addaa | ||
|
ee9d88b637 | ||
|
194a763c46 | ||
|
8d74446bef | ||
|
7ed6c7905c | ||
|
159a37fa37 | ||
|
f39ae0e7c1 | ||
|
4a68eac99b | ||
|
2665580120 | ||
|
a4391aeedd | ||
|
b86a967ee2 | ||
|
608232568b | ||
|
7f85beccb5 | ||
|
767f1255ab | ||
|
fddbfe7b0e | ||
|
4ab7fdc28e | ||
|
e352b68878 | ||
|
207deeadba | ||
|
d18d92f925 | ||
|
af3ce47c44 | ||
|
d8bfb6712d | ||
|
f864f8b5f7 | ||
|
31d72efba7 | ||
|
d169c951f3 | ||
|
90d7e38909 | ||
|
938f23e429 | ||
|
f97d275413 | ||
|
3acb4ca90e | ||
|
e86b81bbf5 | ||
|
a5447b8b75 | ||
|
7ce6452d83 | ||
|
5643a306bd | ||
|
3c877c504b | ||
|
892d1acccb | ||
|
1a9c282f76 | ||
|
fd1ba46f77 | ||
|
a4745626dd | ||
|
17bb7ad278 | ||
|
152988c17b | ||
|
4cd37fc02b | ||
|
69b9d674a3 | ||
|
bad8061547 | ||
|
62963dcefd | ||
|
68a35e4735 | ||
|
9b9ad77e1c | ||
|
118b4d4a01 | ||
|
da14ab6f16 | ||
|
09a4ca6ab5 | ||
|
8a2df79711 | ||
|
c30e486b64 | ||
|
a575c0c54b | ||
|
77fe96ac0d | ||
|
5234c3759a | ||
|
41b3511ad9 | ||
|
128e4a2e8d | ||
|
07ac90d798 | ||
|
7de87a9b2c | ||
|
dff865239a | ||
|
07f8f70c5b | ||
|
f625c5aabe | ||
|
8a74976c1f | ||
|
b6bfd4a5cb | ||
|
008fb9d258 | ||
|
db6db49ed6 | ||
|
05453881c3 | ||
|
5e47ab9431 | ||
|
ec70acd0b9 | ||
|
25e61056b6 | ||
|
d579e335b5 | ||
|
7e344ceb85 | ||
|
0145b82ea0 | ||
|
b4efe7aab7 | ||
|
9ffe951f6d | ||
|
a5ea4f57bd | ||
|
88f4c16755 | ||
|
c7ee071efa | ||
|
0740ef7ceb | ||
|
b29bd809ac | ||
|
8977c9257a | ||
|
091b7eacba | ||
|
e74b1251c0 | ||
|
d282a1649d | ||
|
6ce8d49d1b | ||
|
c5b197078a | ||
|
0494f20d62 | ||
|
73aff476dd | ||
|
98ee5e651a | ||
|
01871ea383 | ||
|
1dbdb9438f | ||
|
c70f0eadb8 | ||
|
26244ad8c2 | ||
|
fa0aa5510d | ||
|
eec557b6aa | ||
|
0cc27c3cc1 | ||
|
507089d7b2 | ||
|
a6b3517b75 | ||
|
2d6beb7813 | ||
|
61bc129e1d | ||
|
52210a57f0 | ||
|
8061a2f108 | ||
|
7444eff6d4 | ||
|
f35a9da99a | ||
|
c3098e9ab2 | ||
|
686f9288fc | ||
|
1833670fb9 | ||
|
3dd42f5aa2 | ||
|
99a7beba57 | ||
|
edee2b753c | ||
|
545d5770be | ||
|
ca747a2b54 | ||
|
17da165cfe | ||
|
5e6788c679 | ||
|
425deadca9 | ||
|
2c8e9dd3a5 | ||
|
7a72f1a253 | ||
|
208e556332 | ||
|
c65d11bfb5 | ||
|
3b5b52d89a | ||
|
a4f6c8f990 | ||
|
670c329852 | ||
|
f3551c8422 | ||
|
90b8187882 | ||
|
1a43259989 |
22
.github/ISSUE_TEMPLATE.md
vendored
22
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,22 +0,0 @@
|
|||||||
<!-- ISSUES NOT FOLLOWING THIS TEMPLATE WILL BE CLOSED AND DELETED -->
|
|
||||||
|
|
||||||
<!-- Check all that apply [x] -->
|
|
||||||
|
|
||||||
- [ ] I have read through the manual page (`man fzf`)
|
|
||||||
- [ ] I have the latest version of fzf
|
|
||||||
- [ ] I have searched through the existing issues
|
|
||||||
|
|
||||||
## Info
|
|
||||||
|
|
||||||
- OS
|
|
||||||
- [ ] Linux
|
|
||||||
- [ ] Mac OS X
|
|
||||||
- [ ] Windows
|
|
||||||
- [ ] Etc.
|
|
||||||
- Shell
|
|
||||||
- [ ] bash
|
|
||||||
- [ ] zsh
|
|
||||||
- [ ] fish
|
|
||||||
|
|
||||||
## Problem / Steps to reproduce
|
|
||||||
|
|
49
.github/ISSUE_TEMPLATE/issue_template.yml
vendored
Normal file
49
.github/ISSUE_TEMPLATE/issue_template.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
---
|
||||||
|
name: Issue Template
|
||||||
|
description: Report a problem or bug related to fzf to help us improve
|
||||||
|
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: ISSUES NOT FOLLOWING THIS TEMPLATE WILL BE CLOSED AND DELETED
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Checklist
|
||||||
|
options:
|
||||||
|
- label: I have read through the manual page (`man fzf`)
|
||||||
|
required: true
|
||||||
|
- label: I have searched through the existing issues
|
||||||
|
required: true
|
||||||
|
- label: For bug reports, I have checked if the bug is reproducible in the latest version of fzf
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Output of `fzf --version`
|
||||||
|
placeholder: e.g. 0.48.1 (d579e33)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: OS
|
||||||
|
options:
|
||||||
|
- label: Linux
|
||||||
|
- label: macOS
|
||||||
|
- label: Windows
|
||||||
|
- label: Etc.
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Shell
|
||||||
|
options:
|
||||||
|
- label: bash
|
||||||
|
- label: zsh
|
||||||
|
- label: fish
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Problem / Steps to reproduce
|
||||||
|
validations:
|
||||||
|
required: true
|
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
2
.github/workflows/depsreview.yaml
vendored
2
.github/workflows/depsreview.yaml
vendored
@@ -9,6 +9,6 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: 'Checkout Repository'
|
- name: 'Checkout Repository'
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: 'Dependency Review'
|
- name: 'Dependency Review'
|
||||||
uses: actions/dependency-review-action@v4
|
uses: actions/dependency-review-action@v4
|
||||||
|
7
.github/workflows/linux.yml
vendored
7
.github/workflows/linux.yml
vendored
@@ -18,14 +18,14 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.19
|
go-version: "1.20"
|
||||||
|
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
uses: ruby/setup-ruby@v1
|
uses: ruby/setup-ruby@v1
|
||||||
@@ -46,6 +46,3 @@ jobs:
|
|||||||
|
|
||||||
- name: Integration test
|
- name: Integration test
|
||||||
run: make install && ./install --all && tmux new-session -d && ruby test/test_go.rb --verbose
|
run: make install && ./install --all && tmux new-session -d && ruby test/test_go.rb --verbose
|
||||||
|
|
||||||
- name: Integration test (tcell)
|
|
||||||
run: TAGS=tcell make clean install && ruby test/test_go.rb --verbose
|
|
||||||
|
4
.github/workflows/macos.yml
vendored
4
.github/workflows/macos.yml
vendored
@@ -15,14 +15,14 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.18
|
go-version: "1.20"
|
||||||
|
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
uses: ruby/setup-ruby@v1
|
uses: ruby/setup-ruby@v1
|
||||||
|
2
.github/workflows/sponsors.yml
vendored
2
.github/workflows/sponsors.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout 🛎️
|
- name: Checkout 🛎️
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Generate Sponsors 💖
|
- name: Generate Sponsors 💖
|
||||||
uses: JamesIves/github-sponsors-readme-action@v1
|
uses: JamesIves/github-sponsors-readme-action@v1
|
||||||
|
4
.github/workflows/typos.yml
vendored
4
.github/workflows/typos.yml
vendored
@@ -6,5 +6,5 @@ jobs:
|
|||||||
name: Spell Check with Typos
|
name: Spell Check with Typos
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: crate-ci/typos@v1.17.2
|
- uses: crate-ci/typos@v1.23.1
|
||||||
|
@@ -12,6 +12,8 @@ builds:
|
|||||||
- darwin
|
- darwin
|
||||||
goarch:
|
goarch:
|
||||||
- amd64
|
- amd64
|
||||||
|
flags:
|
||||||
|
- -trimpath
|
||||||
ldflags:
|
ldflags:
|
||||||
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
||||||
hooks:
|
hooks:
|
||||||
@@ -19,11 +21,7 @@ builds:
|
|||||||
sh -c '
|
sh -c '
|
||||||
cat > /tmp/fzf-gon-amd64.hcl << EOF
|
cat > /tmp/fzf-gon-amd64.hcl << EOF
|
||||||
source = ["./dist/fzf-macos_darwin_amd64_v1/fzf"]
|
source = ["./dist/fzf-macos_darwin_amd64_v1/fzf"]
|
||||||
bundle_id = "kr.junegunn.fzf"
|
bundle_id = "junegunn.fzf"
|
||||||
apple_id {
|
|
||||||
username = "junegunn.c@gmail.com"
|
|
||||||
password = "@env:AC_PASSWORD"
|
|
||||||
}
|
|
||||||
sign {
|
sign {
|
||||||
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
|
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
|
||||||
}
|
}
|
||||||
@@ -40,6 +38,8 @@ builds:
|
|||||||
- darwin
|
- darwin
|
||||||
goarch:
|
goarch:
|
||||||
- arm64
|
- arm64
|
||||||
|
flags:
|
||||||
|
- -trimpath
|
||||||
ldflags:
|
ldflags:
|
||||||
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
||||||
hooks:
|
hooks:
|
||||||
@@ -47,11 +47,7 @@ builds:
|
|||||||
sh -c '
|
sh -c '
|
||||||
cat > /tmp/fzf-gon-arm64.hcl << EOF
|
cat > /tmp/fzf-gon-arm64.hcl << EOF
|
||||||
source = ["./dist/fzf-macos-arm_darwin_arm64/fzf"]
|
source = ["./dist/fzf-macos-arm_darwin_arm64/fzf"]
|
||||||
bundle_id = "kr.junegunn.fzf"
|
bundle_id = "junegunn.fzf"
|
||||||
apple_id {
|
|
||||||
username = "junegunn.c@gmail.com"
|
|
||||||
password = "@env:AC_PASSWORD"
|
|
||||||
}
|
|
||||||
sign {
|
sign {
|
||||||
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
|
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
|
||||||
}
|
}
|
||||||
@@ -79,6 +75,8 @@ builds:
|
|||||||
- 5
|
- 5
|
||||||
- 6
|
- 6
|
||||||
- 7
|
- 7
|
||||||
|
flags:
|
||||||
|
- -trimpath
|
||||||
ldflags:
|
ldflags:
|
||||||
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
||||||
ignore:
|
ignore:
|
||||||
@@ -111,12 +109,12 @@ release:
|
|||||||
owner: junegunn
|
owner: junegunn
|
||||||
name: fzf
|
name: fzf
|
||||||
prerelease: auto
|
prerelease: auto
|
||||||
name_template: '{{ .Tag }}'
|
name_template: '{{ .Version }}'
|
||||||
extra_files:
|
extra_files:
|
||||||
- glob: ./dist/fzf-*darwin*.zip
|
- glob: ./dist/fzf-*darwin*.zip
|
||||||
|
|
||||||
snapshot:
|
snapshot:
|
||||||
name_template: "{{ .Tag }}-devel"
|
name_template: "{{ .Version }}-devel"
|
||||||
|
|
||||||
changelog:
|
changelog:
|
||||||
sort: asc
|
sort: asc
|
||||||
|
102
ADVANCED.md
102
ADVANCED.md
@@ -1,18 +1,17 @@
|
|||||||
Advanced fzf examples
|
Advanced fzf examples
|
||||||
======================
|
======================
|
||||||
|
|
||||||
* *Last update: 2024/01/20*
|
* *Last update: 2024/06/24*
|
||||||
* *Requires fzf 0.46.0 or above*
|
* *Requires fzf 0.54.0 or later*
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- vim-markdown-toc GFM -->
|
<!-- vim-markdown-toc GFM -->
|
||||||
|
|
||||||
* [Introduction](#introduction)
|
* [Introduction](#introduction)
|
||||||
* [Screen Layout](#screen-layout)
|
* [Display modes](#display-modes)
|
||||||
* [`--height`](#--height)
|
* [`--height`](#--height)
|
||||||
* [`fzf-tmux`](#fzf-tmux)
|
* [`--tmux`](#--tmux)
|
||||||
* [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)
|
||||||
@@ -63,7 +62,7 @@ learn its wide variety of features.
|
|||||||
This document will guide you through some examples that will familiarize you
|
This document will guide you through some examples that will familiarize you
|
||||||
with the advanced features of fzf.
|
with the advanced features of fzf.
|
||||||
|
|
||||||
Screen Layout
|
Display modes
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
### `--height`
|
### `--height`
|
||||||
@@ -104,56 +103,55 @@ Define `$FZF_DEFAULT_OPTS` like so:
|
|||||||
export FZF_DEFAULT_OPTS="--height=40% --layout=reverse --info=inline --border --margin=1 --padding=1"
|
export FZF_DEFAULT_OPTS="--height=40% --layout=reverse --info=inline --border --margin=1 --padding=1"
|
||||||
```
|
```
|
||||||
|
|
||||||
### `fzf-tmux`
|
### `--tmux`
|
||||||
|
|
||||||
Before fzf had `--height` option, we would open fzf in a tmux split pane not
|
(Requires tmux 3.3 or later)
|
||||||
to take up the whole screen. This is done using `fzf-tmux` script.
|
|
||||||
|
If you're using tmux, you can open fzf in a tmux popup using `--tmux` option.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Open fzf on a tmux split pane below the current pane.
|
# Open fzf in a tmux popup at the center of the screen with 70% width and height
|
||||||
# Takes the same set of options.
|
fzf --tmux 70%
|
||||||
fzf-tmux --layout=reverse
|
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
The limitation of `fzf-tmux` is that it only works when you're on tmux unlike
|
`--tmux` option is silently ignored if you're not on tmux. So if you're trying
|
||||||
`--height` option. But the advantage of it is that it's more flexible.
|
to avoid opening fzf in fullscreen, try specifying both `--height` and `--tmux`.
|
||||||
(See `man fzf-tmux` for available options.)
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# On the right (50%)
|
# --tmux is specified later so it takes precedence over --height when on tmux.
|
||||||
fzf-tmux -r
|
# If you're not on tmux, --tmux is ignored and --height is used instead.
|
||||||
|
fzf --height 70% --tmux 70%
|
||||||
# On the left (30%)
|
|
||||||
fzf-tmux -l30%
|
|
||||||
|
|
||||||
# Above the cursor
|
|
||||||
fzf-tmux -u30%
|
|
||||||
```
|
```
|
||||||
|
|
||||||

|
You can also specify the position, width, and height of the popup window in
|
||||||
|
the following format:
|
||||||
|
|
||||||

|
* `[center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]`
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
#### Popup window support
|
|
||||||
|
|
||||||
But here's the really cool part; tmux 3.2 added support for popup windows. So
|
|
||||||
you can open fzf in a popup window, which is quite useful if you frequently
|
|
||||||
use split panes.
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Open tmux in a tmux popup window (default size: 50% of the screen)
|
# 100% width and 60% height
|
||||||
fzf-tmux -p
|
fzf --tmux 100%,60% --border horizontal
|
||||||
|
|
||||||
# 80% width, 60% height
|
|
||||||
fzf-tmux -p 80%,60%
|
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
```sh
|
||||||
|
# On the right (50% width)
|
||||||
|
fzf --tmux right
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```sh
|
||||||
|
# On the left (40% width and 70% height)
|
||||||
|
fzf --tmux left,40%,70%
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
> You might also want to check out my tmux plugins which support this popup
|
> You might also want to check out my tmux plugins which support this popup
|
||||||
> window layout.
|
> window layout.
|
||||||
>
|
>
|
||||||
@@ -363,7 +361,7 @@ 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="${*:-}"
|
||||||
: | fzf --ansi --disabled --query "$INITIAL_QUERY" \
|
fzf --ansi --disabled --query "$INITIAL_QUERY" \
|
||||||
--bind "start:reload:$RG_PREFIX {q}" \
|
--bind "start:reload:$RG_PREFIX {q}" \
|
||||||
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
|
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
|
||||||
--delimiter : \
|
--delimiter : \
|
||||||
@@ -374,11 +372,9 @@ INITIAL_QUERY="${*:-}"
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
- Instead of starting fzf in the usual `rg ... | fzf` form, we start fzf with
|
- Instead of starting fzf in the usual `rg ... | fzf` form, we make it start
|
||||||
an empty input (`: | fzf`), then we make it start the initial Ripgrep
|
the initial Ripgrep process immediately via `start:reload` binding for the
|
||||||
process immediately via `start:reload` binding. This way, fzf owns the
|
consistency of the code.
|
||||||
initial Ripgrep process so it can kill it on the next `reload`. Otherwise,
|
|
||||||
the process will keep running in the background.
|
|
||||||
- Filtering is no longer a responsibility of fzf; hence `--disabled`
|
- 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
|
||||||
@@ -402,7 +398,7 @@ 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="${*:-}"
|
||||||
: | fzf --ansi --disabled --query "$INITIAL_QUERY" \
|
fzf --ansi --disabled --query "$INITIAL_QUERY" \
|
||||||
--bind "start:reload:$RG_PREFIX {q}" \
|
--bind "start:reload:$RG_PREFIX {q}" \
|
||||||
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
|
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
|
||||||
--bind "alt-enter:unbind(change,alt-enter)+change-prompt(2. fzf> )+enable-search+clear-query" \
|
--bind "alt-enter:unbind(change,alt-enter)+change-prompt(2. fzf> )+enable-search+clear-query" \
|
||||||
@@ -446,7 +442,7 @@ CTRL-F.
|
|||||||
rm -f /tmp/rg-fzf-{r,f}
|
rm -f /tmp/rg-fzf-{r,f}
|
||||||
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="${*:-}"
|
||||||
: | fzf --ansi --disabled --query "$INITIAL_QUERY" \
|
fzf --ansi --disabled --query "$INITIAL_QUERY" \
|
||||||
--bind "start:reload($RG_PREFIX {q})+unbind(ctrl-r)" \
|
--bind "start:reload($RG_PREFIX {q})+unbind(ctrl-r)" \
|
||||||
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
|
--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-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)" \
|
||||||
@@ -489,7 +485,7 @@ prevent immediate evaluation.
|
|||||||
rm -f /tmp/rg-fzf-{r,f}
|
rm -f /tmp/rg-fzf-{r,f}
|
||||||
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="${*:-}"
|
||||||
: | fzf --ansi --disabled --query "$INITIAL_QUERY" \
|
fzf --ansi --disabled --query "$INITIAL_QUERY" \
|
||||||
--bind "start:reload:$RG_PREFIX {q}" \
|
--bind "start:reload:$RG_PREFIX {q}" \
|
||||||
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
|
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
|
||||||
--bind 'ctrl-t:transform:[[ ! $FZF_PROMPT =~ ripgrep ]] &&
|
--bind 'ctrl-t:transform:[[ ! $FZF_PROMPT =~ ripgrep ]] &&
|
||||||
@@ -529,15 +525,15 @@ Kubernetes pods.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
pods() {
|
pods() {
|
||||||
: | command='kubectl get pods --all-namespaces' fzf \
|
command='kubectl get pods --all-namespaces' 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 'start:reload:$command' \
|
--bind 'start:reload:$command' \
|
||||||
--bind 'ctrl-r:reload:$command' \
|
--bind 'ctrl-r:reload:$command' \
|
||||||
--bind 'ctrl-/:change-preview-window(80%,border-bottom|hidden|)' \
|
--bind 'ctrl-/:change-preview-window(80%,border-bottom|hidden|)' \
|
||||||
--bind 'enter:execute:kubectl exec -it --namespace {1} {2} -- bash > /dev/tty' \
|
--bind 'enter:execute:kubectl exec -it --namespace {1} {2} -- bash' \
|
||||||
--bind 'ctrl-o:execute:${EDITOR:-vim} <(kubectl logs --all-containers --namespace {1} {2}) > /dev/tty' \
|
--bind 'ctrl-o:execute:${EDITOR:-vim} <(kubectl logs --all-containers --namespace {1} {2})' \
|
||||||
--preview-window up:follow \
|
--preview-window up:follow \
|
||||||
--preview 'kubectl logs --follow --all-containers --tail=10000 --namespace {1} {2}' "$@"
|
--preview 'kubectl logs --follow --all-containers --tail=10000 --namespace {1} {2}' "$@"
|
||||||
}
|
}
|
||||||
@@ -552,7 +548,7 @@ pods() {
|
|||||||
- Press enter key on a pod to `kubectl exec` into it
|
- Press enter key on a pod to `kubectl exec` into it
|
||||||
- Press CTRL-O to open the log in your editor
|
- Press CTRL-O to open the log in your editor
|
||||||
- Press CTRL-R to reload the pod list
|
- Press CTRL-R to reload the pod list
|
||||||
- Press CTRL-/ repeatedly to to rotate through a different sets of preview
|
- Press CTRL-/ repeatedly to rotate through a different sets of preview
|
||||||
window options
|
window options
|
||||||
1. `80%,border-bottom`
|
1. `80%,border-bottom`
|
||||||
1. `hidden`
|
1. `hidden`
|
||||||
|
18
BUILD.md
18
BUILD.md
@@ -6,7 +6,7 @@ Build instructions
|
|||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- Go 1.18 or above
|
- Go 1.20 or above
|
||||||
|
|
||||||
### Using Makefile
|
### Using Makefile
|
||||||
|
|
||||||
@@ -24,13 +24,23 @@ make build
|
|||||||
make release
|
make release
|
||||||
```
|
```
|
||||||
|
|
||||||
> :warning: Makefile uses git commands to determine the version and the
|
> [!WARNING]
|
||||||
> revision information for `fzf --version`. So if you're building fzf from an
|
> Makefile uses git commands to determine the version and the revision
|
||||||
|
> information for `fzf --version`. So if you're building fzf from an
|
||||||
> environment where its git information is not available, you have to manually
|
> environment where its git information is not available, you have to manually
|
||||||
> set `$FZF_VERSION` and `$FZF_REVISION`.
|
> set `$FZF_VERSION` and `$FZF_REVISION`.
|
||||||
>
|
>
|
||||||
> e.g. `FZF_VERSION=0.24.0 FZF_REVISION=tarball make`
|
> e.g. `FZF_VERSION=0.24.0 FZF_REVISION=tarball make`
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> To build fzf with profiling options enabled, set `TAGS=pprof`
|
||||||
|
>
|
||||||
|
> ```sh
|
||||||
|
> TAGS=pprof make clean install
|
||||||
|
> fzf --profile-cpu /tmp/cpu.pprof --profile-mem /tmp/mem.pprof \
|
||||||
|
> --profile-block /tmp/block.pprof --profile-mutex /tmp/mutex.pprof
|
||||||
|
> ```
|
||||||
|
|
||||||
Third-party libraries used
|
Third-party libraries used
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
@@ -42,6 +52,8 @@ Third-party libraries used
|
|||||||
- Licensed under [MIT](http://mattn.mit-license.org)
|
- Licensed under [MIT](http://mattn.mit-license.org)
|
||||||
- [tcell](https://github.com/gdamore/tcell)
|
- [tcell](https://github.com/gdamore/tcell)
|
||||||
- Licensed under [Apache License 2.0](https://github.com/gdamore/tcell/blob/master/LICENSE)
|
- Licensed under [Apache License 2.0](https://github.com/gdamore/tcell/blob/master/LICENSE)
|
||||||
|
- [fastwalk](https://github.com/charlievieth/fastwalk)
|
||||||
|
- Licensed under [MIT](https://raw.githubusercontent.com/charlievieth/fastwalk/master/LICENSE)
|
||||||
|
|
||||||
License
|
License
|
||||||
-------
|
-------
|
||||||
|
343
CHANGELOG.md
343
CHANGELOG.md
@@ -1,6 +1,349 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.54.0
|
||||||
|
------
|
||||||
|
_Release highlights: https://junegunn.github.io/fzf/releases/0.54.0/_
|
||||||
|
|
||||||
|
- Implemented line wrap of long items
|
||||||
|
- `--wrap` option enables line wrap
|
||||||
|
- `--wrap-sign` customizes the sign for wrapped lines (default: `↳ `)
|
||||||
|
- `toggle-wrap` action toggles line wrap
|
||||||
|
```sh
|
||||||
|
history | fzf --tac --wrap --bind 'ctrl-/:toggle-wrap' --wrap-sign $'\t↳ '
|
||||||
|
```
|
||||||
|
- fzf by default binds `CTRL-/` and `ALT-/` to `toggle-wrap`
|
||||||
|
- Updated shell integration scripts to leverage line wrap
|
||||||
|
- CTRL-R binding includes `--wrap-sign $'\t↳ '` to indent wrapped lines
|
||||||
|
- `kill **` completion uses `--wrap` to show the whole line by default
|
||||||
|
instead of showing it in the preview window
|
||||||
|
- Added `--info-command` option for customizing the info line
|
||||||
|
```sh
|
||||||
|
# Prepend the current cursor position in yellow
|
||||||
|
fzf --info-command='echo -e "\x1b[33;1m$FZF_POS\x1b[m/$FZF_INFO 💛"'
|
||||||
|
```
|
||||||
|
- `$FZF_INFO` is set to the original info text
|
||||||
|
- ANSI color codes are supported
|
||||||
|
- Pointer and marker signs can be set to empty strings
|
||||||
|
```sh
|
||||||
|
# Minimal style
|
||||||
|
fzf --pointer '' --marker '' --prompt '' --info hidden
|
||||||
|
```
|
||||||
|
- Better cache management and improved rendering for `--tail`
|
||||||
|
- Improved `--sync` behavior
|
||||||
|
- When `--sync` is provided, fzf will not render the interface until the initial filtering and the associated actions (bound to any of `start`, `load`, `result`, or `focus`) are complete.
|
||||||
|
```sh
|
||||||
|
# fzf will not render intermediate states
|
||||||
|
(sleep 1; seq 1000000; sleep 1) |
|
||||||
|
fzf --sync --query 5 --listen --bind start:up,load:up,result:up,focus:change-header:Ready
|
||||||
|
```
|
||||||
|
- GET endpoint is now available from `execute` and `transform` actions (it used to timeout due to lock conflict)
|
||||||
|
```sh
|
||||||
|
fzf --listen --sync --bind 'focus:transform-header:curl -s localhost:$FZF_PORT?limit=0 | jq .'
|
||||||
|
```
|
||||||
|
- Added `offset-middle` action to place the current item is in the middle of the screen
|
||||||
|
- fzf will not start the initial reader when `reload` or `reload-sync` is bound to `start` event. `fzf < /dev/null` or `: | fzf` are no longer required and extraneous `load` event will not fire due to the empty list.
|
||||||
|
```sh
|
||||||
|
# Now this will work as expected. Previously, this would print an invalid header line.
|
||||||
|
# `fzf < /dev/null` or `: | fzf` would fix the problem, but then an extraneous
|
||||||
|
# `load` event would fire and the header would be prematurely updated.
|
||||||
|
fzf --header 'Loading ...' --header-lines 1 \
|
||||||
|
--bind 'start:reload:sleep 1; ps -ef' \
|
||||||
|
--bind 'load:change-header:Loaded!'
|
||||||
|
```
|
||||||
|
- Fixed mouse support on Windows
|
||||||
|
- Fixed crash when using `--tiebreak=end` with very long items
|
||||||
|
- zsh 5.0 compatibility (thanks to @LangLangBart)
|
||||||
|
- Fixed `--walker-skip` to also skip symlinks to directories
|
||||||
|
- Fixed `result` event not fired when input stream is not complete
|
||||||
|
- New tags will have `v` prefix so that they are available on https://proxy.golang.org/
|
||||||
|
|
||||||
|
0.53.0
|
||||||
|
------
|
||||||
|
_Release highlights: https://junegunn.github.io/fzf/releases/0.53.0/_
|
||||||
|
|
||||||
|
- Multi-line display
|
||||||
|
- See [Processing multi-line items](https://junegunn.github.io/fzf/tips/processing-multi-line-items/)
|
||||||
|
- fzf can now display multi-line items
|
||||||
|
```sh
|
||||||
|
# All bash functions, highlighted
|
||||||
|
declare -f | perl -0777 -pe 's/^}\n/}\0/gm' |
|
||||||
|
bat --plain --language bash --color always |
|
||||||
|
fzf --read0 --ansi --reverse --multi --highlight-line
|
||||||
|
|
||||||
|
# Ripgrep multi-line output
|
||||||
|
rg --pretty bash | perl -0777 -pe 's/\n\n/\n\0/gm' |
|
||||||
|
fzf --read0 --ansi --multi --highlight-line --reverse --tmux 70%
|
||||||
|
```
|
||||||
|
- To disable multi-line display, use `--no-multi-line`
|
||||||
|
- CTRL-R bindings of bash, zsh, and fish have been updated to leverage multi-line display
|
||||||
|
- The default `--pointer` and `--marker` have been changed from `>` to Unicode bar characters as they look better with multi-line items
|
||||||
|
- Added `--marker-multi-line` to customize the select marker for multi-line entries with the default set to `╻┃╹`
|
||||||
|
```
|
||||||
|
╻First line
|
||||||
|
┃...
|
||||||
|
╹Last line
|
||||||
|
```
|
||||||
|
- Native tmux integration
|
||||||
|
- Added `--tmux` option to replace fzf-tmux script and simplify distribution
|
||||||
|
```sh
|
||||||
|
# --tmux [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]
|
||||||
|
# Center, 100% width and 70% height
|
||||||
|
fzf --tmux 100%,70% --border horizontal --padding 1,2
|
||||||
|
|
||||||
|
# Left, 30% width
|
||||||
|
fzf --tmux left,30%
|
||||||
|
|
||||||
|
# Bottom, 50% height
|
||||||
|
fzf --tmux bottom,50%
|
||||||
|
```
|
||||||
|
- To keep the implementation simple, it only uses popups. You need tmux 3.3 or later.
|
||||||
|
- To use `--tmux` in Vim plugin:
|
||||||
|
```vim
|
||||||
|
let g:fzf_layout = { 'tmux': '100%,70%' }
|
||||||
|
```
|
||||||
|
- Added support for endless input streams
|
||||||
|
- See [Browsing log stream with fzf](https://junegunn.github.io/fzf/tips/browsing-log-streams/)
|
||||||
|
- Added `--tail=NUM` option to limit the number of items to keep in memory. This is useful when you want to browse an endless stream of data (e.g. log stream) with fzf while limiting memory usage.
|
||||||
|
```sh
|
||||||
|
# Interactive filtering of a log stream
|
||||||
|
tail -f *.log | fzf --tail 100000 --tac --no-sort --exact
|
||||||
|
```
|
||||||
|
- Better Windows Support
|
||||||
|
- fzf now works on Git bash (mintty) out of the box via winpty integration
|
||||||
|
- Many fixes and improvements for Windows
|
||||||
|
- man page is now embedded in the binary; `fzf --man` to see it
|
||||||
|
- Changed the default `--scroll-off` to 3, as we think it's a better default
|
||||||
|
- Process started by `execute` action now directly writes to and reads from `/dev/tty`. Manual `/dev/tty` redirection for interactive programs is no longer required.
|
||||||
|
```sh
|
||||||
|
# Vim will work fine without /dev/tty redirection
|
||||||
|
ls | fzf --bind 'space:execute:vim {}' > selected
|
||||||
|
```
|
||||||
|
- Added `print(...)` action to queue an arbitrary string to be printed on exit. This was mainly added to work around the limitation of `--expect` where it's not compatible with `--bind` on the same key and it would ignore other actions bound to it.
|
||||||
|
```sh
|
||||||
|
# This doesn't work as expected because --expect is not compatible with --bind
|
||||||
|
fzf --multi --expect ctrl-y --bind 'ctrl-y:select-all'
|
||||||
|
|
||||||
|
# This is something you can do instead
|
||||||
|
fzf --multi --bind 'enter:print()+accept,ctrl-y:select-all+print(ctrl-y)+accept'
|
||||||
|
```
|
||||||
|
- We also considered making them compatible, but realized that some users may have been relying on the current behavior.
|
||||||
|
- [`NO_COLOR`](https://no-color.org/) environment variable is now respected. If the variable is set, fzf defaults to `--no-color` unless otherwise specified.
|
||||||
|
|
||||||
|
0.52.1
|
||||||
|
------
|
||||||
|
- Fixed a critical bug in the Windows version
|
||||||
|
- Windows users are strongly encouraged to upgrade to this version
|
||||||
|
|
||||||
|
0.52.0
|
||||||
|
------
|
||||||
|
- Added `--highlight-line` to highlight the whole current line (à la `set cursorline` of Vim)
|
||||||
|
- Added color names for selected lines: `selected-fg`, `selected-bg`, and `selected-hl`
|
||||||
|
```sh
|
||||||
|
fzf --border --multi --info inline-right --layout reverse --marker ▏ --pointer ▌ --prompt '▌ ' \
|
||||||
|
--highlight-line --color gutter:-1,selected-bg:238,selected-fg:146,current-fg:189
|
||||||
|
```
|
||||||
|
- Added `click-header` event that is triggered when the header section is clicked. When the event is triggered, `$FZF_CLICK_HEADER_COLUMN` and `$FZF_CLICK_HEADER_LINE` are set.
|
||||||
|
```sh
|
||||||
|
fd --type f |
|
||||||
|
fzf --header $'[Files] [Directories]' --header-first \
|
||||||
|
--bind 'click-header:transform:
|
||||||
|
(( FZF_CLICK_HEADER_COLUMN <= 7 )) && echo "reload(fd --type f)"
|
||||||
|
(( FZF_CLICK_HEADER_COLUMN >= 9 )) && echo "reload(fd --type d)"
|
||||||
|
'
|
||||||
|
```
|
||||||
|
- Add `$FZF_COMPLETION_{DIR,PATH}_OPTS` for separately customizing the behavior of fuzzy completion
|
||||||
|
```sh
|
||||||
|
# Set --walker options without 'follow' not to follow symbolic links
|
||||||
|
FZF_COMPLETION_PATH_OPTS="--walker=file,dir,hidden"
|
||||||
|
FZF_COMPLETION_DIR_OPTS="--walker=dir,hidden"
|
||||||
|
```
|
||||||
|
- Fixed Windows argument escaping
|
||||||
|
- Bug fixes and improvements
|
||||||
|
- The code was heavily refactored to allow using fzf as a library in Go programs. The API is still experimental and subject to change.
|
||||||
|
- https://gist.github.com/junegunn/193990b65be48a38aac6ac49d5669170
|
||||||
|
|
||||||
|
0.51.0
|
||||||
|
------
|
||||||
|
- Added a new environment variable `$FZF_POS` exported to the child processes. It's the vertical position of the cursor in the list starting from 1.
|
||||||
|
```sh
|
||||||
|
# Toggle selection to the top or to the bottom
|
||||||
|
seq 30 | fzf --multi --bind 'load:pos(10)' \
|
||||||
|
--bind 'shift-up:transform:for _ in $(seq $FZF_POS $FZF_MATCH_COUNT); do echo -n +toggle+up; done' \
|
||||||
|
--bind 'shift-down:transform:for _ in $(seq 1 $FZF_POS); do echo -n +toggle+down; done'
|
||||||
|
```
|
||||||
|
- Added `--with-shell` option to start child processes with a custom shell command and flags
|
||||||
|
```sh
|
||||||
|
gem list | fzf --with-shell 'ruby -e' \
|
||||||
|
--preview 'pp Gem::Specification.find_by_name({1})' \
|
||||||
|
--bind 'ctrl-o:execute-silent:
|
||||||
|
spec = Gem::Specification.find_by_name({1})
|
||||||
|
[spec.homepage, *spec.metadata.filter { _1.end_with?("uri") }.values].uniq.each do
|
||||||
|
system "open", _1
|
||||||
|
end
|
||||||
|
'
|
||||||
|
```
|
||||||
|
- Added `change-multi` action for dynamically changing `--multi` option
|
||||||
|
- `change-multi` - enable multi-select mode with no limit
|
||||||
|
- `change-multi(NUM)` - enable multi-select mode with a limit
|
||||||
|
- `change-multi(0)` - disable multi-select mode
|
||||||
|
- Windows improvements
|
||||||
|
- `become` action is now supported on Windows
|
||||||
|
- Unlike in *nix, this does not use `execve(2)`. Instead it spawns a new process and waits for it to finish, so the exact behavior may differ.
|
||||||
|
- Fixed argument escaping for Windows cmd.exe. No redundant escaping of backslashes.
|
||||||
|
- Bug fixes and improvements
|
||||||
|
|
||||||
|
0.50.0
|
||||||
|
------
|
||||||
|
- Search performance optimization. You can observe 50%+ improvement in some scenarios.
|
||||||
|
```
|
||||||
|
$ rg --line-number --no-heading --smart-case . > $DATA
|
||||||
|
|
||||||
|
$ wc < $DATA
|
||||||
|
5520118 26862362 897487793
|
||||||
|
|
||||||
|
$ hyperfine -w 1 -L bin fzf-0.49.0,fzf-7ce6452,fzf-a5447b8,fzf '{bin} --filter "///" < $DATA | head -30'
|
||||||
|
Summary
|
||||||
|
fzf --filter "///" < $DATA | head -30 ran
|
||||||
|
1.16 ± 0.03 times faster than fzf-a5447b8 --filter "///" < $DATA | head -30
|
||||||
|
1.23 ± 0.03 times faster than fzf-7ce6452 --filter "///" < $DATA | head -30
|
||||||
|
1.52 ± 0.03 times faster than fzf-0.49.0 --filter "///" < $DATA | head -30
|
||||||
|
```
|
||||||
|
- Added `jump` and `jump-cancel` events that are triggered when leaving `jump` mode
|
||||||
|
```sh
|
||||||
|
# Default behavior
|
||||||
|
fzf --bind space:jump
|
||||||
|
|
||||||
|
# Same as jump-accept action
|
||||||
|
fzf --bind space:jump,jump:accept
|
||||||
|
|
||||||
|
# Accept on jump, abort on cancel
|
||||||
|
fzf --bind space:jump,jump:accept,jump-cancel:abort
|
||||||
|
|
||||||
|
# Change header on jump-cancel
|
||||||
|
fzf --bind 'space:change-header(Type jump label)+jump,jump-cancel:change-header:Jump cancelled'
|
||||||
|
```
|
||||||
|
- Added a new environment variable `$FZF_KEY` exported to the child processes. It's the name of the last key pressed.
|
||||||
|
```sh
|
||||||
|
fzf --bind 'space:jump,jump:accept,jump-cancel:transform:[[ $FZF_KEY =~ ctrl-c ]] && echo abort'
|
||||||
|
```
|
||||||
|
- fzf can be built with profiling options. See [BUILD.md](BUILD.md) for more information.
|
||||||
|
- Bug fixes
|
||||||
|
|
||||||
|
0.49.0
|
||||||
|
------
|
||||||
|
- Ingestion performance improved by around 40% (more or less depending on options)
|
||||||
|
- `--info=hidden` and `--info=inline-right` will no longer hide the horizontal separator by default. This gives you more flexibility in customizing the layout.
|
||||||
|
```sh
|
||||||
|
fzf --border --info=inline-right
|
||||||
|
fzf --border --info=inline-right --separator ═
|
||||||
|
fzf --border --info=inline-right --no-separator
|
||||||
|
fzf --border --info=hidden
|
||||||
|
fzf --border --info=hidden --separator ━
|
||||||
|
fzf --border --info=hidden --no-separator
|
||||||
|
```
|
||||||
|
- Added two environment variables exported to the child processes
|
||||||
|
- `FZF_PREVIEW_LABEL`
|
||||||
|
- `FZF_BORDER_LABEL`
|
||||||
|
```sh
|
||||||
|
# Use the current value of $FZF_PREVIEW_LABEL to determine which actions to perform
|
||||||
|
git ls-files |
|
||||||
|
fzf --header 'Press CTRL-P to change preview mode' \
|
||||||
|
--bind='ctrl-p:transform:[[ $FZF_PREVIEW_LABEL =~ cat ]] \
|
||||||
|
&& echo "change-preview(git log --color=always \{})+change-preview-label([[ log ]])" \
|
||||||
|
|| echo "change-preview(bat --color=always \{})+change-preview-label([[ cat ]])"'
|
||||||
|
```
|
||||||
|
- Renamed `track` action to `track-current` to highlight the difference between the global tracking state set by `--track` and a one-off tracking action
|
||||||
|
- `track` is still available as an alias
|
||||||
|
- Added `untrack-current` and `toggle-track-current` actions
|
||||||
|
- `*-current` actions are no-op when the global tracking state is set
|
||||||
|
- Bug fixes and minor improvements
|
||||||
|
|
||||||
|
0.48.1
|
||||||
|
------
|
||||||
|
- CTRL-T and ALT-C bindings can be disabled by setting `FZF_CTRL_T_COMMAND` and `FZF_ALT_C_COMMAND` to empty strings respectively when sourcing the script
|
||||||
|
```sh
|
||||||
|
# bash
|
||||||
|
FZF_CTRL_T_COMMAND= FZF_ALT_C_COMMAND= eval "$(fzf --bash)"
|
||||||
|
|
||||||
|
# zsh
|
||||||
|
FZF_CTRL_T_COMMAND= FZF_ALT_C_COMMAND= eval "$(fzf --zsh)"
|
||||||
|
|
||||||
|
# fish
|
||||||
|
fzf --fish | FZF_CTRL_T_COMMAND= FZF_ALT_C_COMMAND= source
|
||||||
|
```
|
||||||
|
- Setting the variables after sourcing the script will have no effect
|
||||||
|
- Bug fixes
|
||||||
|
|
||||||
|
0.48.0
|
||||||
|
------
|
||||||
|
- Shell integration scripts are now embedded in the fzf binary. This simplifies the distribution, and the users are less likely to have problems caused by using incompatible scripts and binaries.
|
||||||
|
- bash
|
||||||
|
```sh
|
||||||
|
# Set up fzf key bindings and fuzzy completion
|
||||||
|
eval "$(fzf --bash)"
|
||||||
|
```
|
||||||
|
- zsh
|
||||||
|
```sh
|
||||||
|
# Set up fzf key bindings and fuzzy completion
|
||||||
|
eval "$(fzf --zsh)"
|
||||||
|
```
|
||||||
|
- fish
|
||||||
|
```fish
|
||||||
|
# Set up fzf key bindings
|
||||||
|
fzf --fish | source
|
||||||
|
```
|
||||||
|
- Added options for customizing the behavior of the built-in walker
|
||||||
|
| Option | Description | Default |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `--walker=OPTS` | Walker options (`[file][,dir][,follow][,hidden]`) | `file,follow,hidden` |
|
||||||
|
| `--walker-root=DIR` | Root directory from which to start walker | `.` |
|
||||||
|
| `--walker-skip=DIRS` | Comma-separated list of directory names to skip | `.git,node_modules` |
|
||||||
|
- Examples
|
||||||
|
```sh
|
||||||
|
# Built-in walker is only used by standalone fzf when $FZF_DEFAULT_COMMAND is not set
|
||||||
|
unset FZF_DEFAULT_COMMAND
|
||||||
|
|
||||||
|
fzf # default: --walker=file,follow,hidden --walker-root=. --walker-skip=.git,node_modules
|
||||||
|
fzf --walker=file,dir,hidden,follow --walker-skip=.git,node_modules,target
|
||||||
|
|
||||||
|
# Walker options in $FZF_DEFAULT_OPTS
|
||||||
|
export FZF_DEFAULT_OPTS="--walker=file,dir,hidden,follow --walker-skip=.git,node_modules,target"
|
||||||
|
fzf
|
||||||
|
|
||||||
|
# Reading from STDIN; --walker is ignored
|
||||||
|
seq 100 | fzf --walker=dir
|
||||||
|
|
||||||
|
# Reading from $FZF_DEFAULT_COMMAND; --walker is ignored
|
||||||
|
export FZF_DEFAULT_COMMAND='seq 100'
|
||||||
|
fzf --walker=dir
|
||||||
|
```
|
||||||
|
- Shell integration scripts have been updated to use the built-in walker with these new options and they are now much faster out of the box.
|
||||||
|
|
||||||
|
0.47.0
|
||||||
|
------
|
||||||
|
- Replaced ["the default find command"][find] with a built-in directory walker to simplify the code and to achieve better performance and consistent behavior across platforms.
|
||||||
|
This doesn't affect you if you have `$FZF_DEFAULT_COMMAND` set.
|
||||||
|
- Breaking changes:
|
||||||
|
- Unlike [the previous "find" command][find], the new traversal code will list hidden files, but hidden directories will still be ignored
|
||||||
|
- No filtering of `devtmpfs` or `proc` types
|
||||||
|
- Traversal is parallelized, so the order of the entries will be different each time
|
||||||
|
- You may wonder why fzf implements directory walker anyway when it's a filter program following the [Unix philosophy][unix].
|
||||||
|
But fzf has had [the walker code for years][walker] to tackle the performance problem on Windows. And I decided to use the same approach on different platforms as well for the benefits listed above.
|
||||||
|
- Built-in walker is using the excellent [charlievieth/fastwalk][fastwalk] library, which easily outperforms its competitors and supports safely following symlinks.
|
||||||
|
- Added `$FZF_DEFAULT_OPTS_FILE` to allow managing default options in a file
|
||||||
|
- See [#3618](https://github.com/junegunn/fzf/pull/3618)
|
||||||
|
- Option precedence from lower to higher
|
||||||
|
1. Options read from `$FZF_DEFAULT_OPTS_FILE`
|
||||||
|
1. Options from `$FZF_DEFAULT_OPTS`
|
||||||
|
1. Options from command-line arguments
|
||||||
|
- Bug fixes and improvements
|
||||||
|
|
||||||
|
[find]: https://github.com/junegunn/fzf/blob/0.46.1/src/constants.go#L60-L64
|
||||||
|
[walker]: https://github.com/junegunn/fzf/pull/1847
|
||||||
|
[fastwalk]: https://github.com/charlievieth/fastwalk
|
||||||
|
[unix]: https://en.wikipedia.org/wiki/Unix_philosophy
|
||||||
|
|
||||||
0.46.1
|
0.46.1
|
||||||
------
|
------
|
||||||
- Bug fixes and improvements
|
- Bug fixes and improvements
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
FROM --platform=linux/amd64 ubuntu:22.04
|
FROM ubuntu:24.04
|
||||||
RUN apt-get update -y && apt install -y git make golang zsh fish ruby tmux
|
RUN apt-get update -y && apt install -y git make golang zsh fish ruby tmux
|
||||||
RUN gem install --no-document -v 5.14.2 minitest
|
RUN gem install --no-document -v 5.22.3 minitest
|
||||||
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
||||||
RUN echo '. ~/.bashrc' >> ~/.bash_profile
|
RUN echo '. ~/.bashrc' >> ~/.bash_profile
|
||||||
|
|
||||||
|
18
Makefile
18
Makefile
@@ -1,20 +1,20 @@
|
|||||||
SHELL := bash
|
SHELL := bash
|
||||||
GO ?= go
|
GO ?= go
|
||||||
GOOS ?= $(word 1, $(subst /, " ", $(word 4, $(shell go version))))
|
GOOS ?= $(shell $(GO) env GOOS)
|
||||||
|
|
||||||
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
|
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
|
||||||
ROOT_DIR := $(shell dirname $(MAKEFILE))
|
ROOT_DIR := $(shell dirname $(MAKEFILE))
|
||||||
SOURCES := $(wildcard *.go src/*.go src/*/*.go) $(MAKEFILE)
|
SOURCES := $(wildcard *.go src/*.go src/*/*.go shell/*sh man/man1/*.1) $(MAKEFILE)
|
||||||
|
|
||||||
ifdef FZF_VERSION
|
ifdef FZF_VERSION
|
||||||
VERSION := $(FZF_VERSION)
|
VERSION := $(FZF_VERSION)
|
||||||
else
|
else
|
||||||
VERSION := $(shell git describe --abbrev=0 2> /dev/null)
|
VERSION := $(shell git describe --abbrev=0 2> /dev/null | sed "s/^v//")
|
||||||
endif
|
endif
|
||||||
ifeq ($(VERSION),)
|
ifeq ($(VERSION),)
|
||||||
$(error Not on git repository; cannot determine $$FZF_VERSION)
|
$(error Not on git repository; cannot determine $$FZF_VERSION)
|
||||||
endif
|
endif
|
||||||
VERSION_TRIM := $(shell sed "s/-.*//" <<< $(VERSION))
|
VERSION_TRIM := $(shell sed "s/^v//; s/-.*//" <<< $(VERSION))
|
||||||
VERSION_REGEX := $(subst .,\.,$(VERSION_TRIM))
|
VERSION_REGEX := $(subst .,\.,$(VERSION_TRIM))
|
||||||
|
|
||||||
ifdef FZF_REVISION
|
ifdef FZF_REVISION
|
||||||
@@ -25,7 +25,7 @@ endif
|
|||||||
ifeq ($(REVISION),)
|
ifeq ($(REVISION),)
|
||||||
$(error Not on git repository; cannot determine $$FZF_REVISION)
|
$(error Not on git repository; cannot determine $$FZF_REVISION)
|
||||||
endif
|
endif
|
||||||
BUILD_FLAGS := -a -ldflags "-s -w -X main.version=$(VERSION) -X main.revision=$(REVISION)" -tags "$(TAGS)"
|
BUILD_FLAGS := -a -ldflags "-s -w -X main.version=$(VERSION) -X main.revision=$(REVISION)" -tags "$(TAGS)" -trimpath
|
||||||
|
|
||||||
BINARY32 := fzf-$(GOOS)_386
|
BINARY32 := fzf-$(GOOS)_386
|
||||||
BINARY64 := fzf-$(GOOS)_amd64
|
BINARY64 := fzf-$(GOOS)_amd64
|
||||||
@@ -173,12 +173,12 @@ bin/fzf: target/$(BINARY) | bin
|
|||||||
cp -f target/$(BINARY) bin/fzf
|
cp -f target/$(BINARY) bin/fzf
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
docker build -t fzf-arch .
|
docker build -t fzf-ubuntu .
|
||||||
docker run -it fzf-arch tmux
|
docker run -it fzf-ubuntu tmux
|
||||||
|
|
||||||
docker-test:
|
docker-test:
|
||||||
docker build -t fzf-arch .
|
docker build -t fzf-ubuntu .
|
||||||
docker run -it fzf-arch
|
docker run -it fzf-ubuntu
|
||||||
|
|
||||||
update:
|
update:
|
||||||
$(GO) get -u
|
$(GO) get -u
|
||||||
|
@@ -238,19 +238,20 @@ call fzf#run({'sink': 'e'})
|
|||||||
```
|
```
|
||||||
|
|
||||||
We haven't specified the `source`, so this is equivalent to starting fzf on
|
We haven't specified the `source`, so this is equivalent to starting fzf on
|
||||||
command line without standard input pipe; fzf will use find command (or
|
command line without standard input pipe; fzf will traverse the file system
|
||||||
`$FZF_DEFAULT_COMMAND` if defined) to list the files under the current
|
under the current directory to get the list of files. (If
|
||||||
directory. When you select one, it will open it with the sink, `:e` command.
|
`$FZF_DEFAULT_COMMAND` is set, fzf will use the output of the command
|
||||||
If you want to open it in a new tab, you can pass `:tabedit` command instead
|
instead.) When you select one, it will open it with the sink, `:e` command. If
|
||||||
as the sink.
|
you want to open it in a new tab, you can pass `:tabedit` command instead as
|
||||||
|
the sink.
|
||||||
|
|
||||||
```vim
|
```vim
|
||||||
call fzf#run({'sink': 'tabedit'})
|
call fzf#run({'sink': 'tabedit'})
|
||||||
```
|
```
|
||||||
|
|
||||||
Instead of using the default find command, you can use any shell command as
|
You can use any shell command as the source to generate the list. The
|
||||||
the source. The following example will list the files managed by git. It's
|
following example will list the files managed by git. It's equivalent to
|
||||||
equivalent to running `git ls-files | fzf` on shell.
|
running `git ls-files | fzf` on shell.
|
||||||
|
|
||||||
```vim
|
```vim
|
||||||
call fzf#run({'source': 'git ls-files', 'sink': 'e'})
|
call fzf#run({'source': 'git ls-files', 'sink': 'e'})
|
||||||
@@ -293,7 +294,7 @@ The following table summarizes the available options.
|
|||||||
| `options` | string/list | Options to fzf |
|
| `options` | string/list | Options to fzf |
|
||||||
| `dir` | string | Working directory |
|
| `dir` | string | Working directory |
|
||||||
| `up`/`down`/`left`/`right` | number/string | (Layout) Window position and size (e.g. `20`, `50%`) |
|
| `up`/`down`/`left`/`right` | number/string | (Layout) Window position and size (e.g. `20`, `50%`) |
|
||||||
| `tmux` | string | (Layout) fzf-tmux options (e.g. `-p90%,60%`) |
|
| `tmux` | string | (Layout) `--tmux` options (e.g. `90%,70%`) |
|
||||||
| `window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new`) |
|
| `window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new`) |
|
||||||
| `window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width': 0.9, 'height': 0.6}`) |
|
| `window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width': 0.9, 'height': 0.6}`) |
|
||||||
|
|
||||||
@@ -456,12 +457,13 @@ let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
|||||||
```
|
```
|
||||||
|
|
||||||
Alternatively, you can make fzf open in a tmux popup window (requires tmux 3.2
|
Alternatively, you can make fzf open in a tmux popup window (requires tmux 3.2
|
||||||
or above) by putting fzf-tmux options in `tmux` key.
|
or above) by putting `--tmux` option value in `tmux` key.
|
||||||
|
|
||||||
```vim
|
```vim
|
||||||
" See `man fzf-tmux` for available options
|
" See `--tmux` option in `man fzf` for available options
|
||||||
|
" [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]
|
||||||
if exists('$TMUX')
|
if exists('$TMUX')
|
||||||
let g:fzf_layout = { 'tmux': '-p90%,60%' }
|
let g:fzf_layout = { 'tmux': '90%,70%' }
|
||||||
else
|
else
|
||||||
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
||||||
endif
|
endif
|
||||||
|
@@ -57,7 +57,7 @@ if [[ $KITTY_WINDOW_ID ]]; then
|
|||||||
|
|
||||||
# 2. Use chafa with Sixel output
|
# 2. Use chafa with Sixel output
|
||||||
elif command -v chafa > /dev/null; then
|
elif command -v chafa > /dev/null; then
|
||||||
chafa -f sixel -s "$dim" "$file"
|
chafa -s "$dim" "$file"
|
||||||
# Add a new line character so that fzf can display multiple images in the preview window
|
# Add a new line character so that fzf can display multiple images in the preview window
|
||||||
echo
|
echo
|
||||||
|
|
||||||
|
17
bin/fzf-tmux
17
bin/fzf-tmux
@@ -7,7 +7,7 @@ fail() {
|
|||||||
exit 2
|
exit 2
|
||||||
}
|
}
|
||||||
|
|
||||||
fzf="$(command -v fzf 2> /dev/null)" || fzf="$(dirname "$0")/fzf"
|
fzf="$(command which fzf)" || fzf="$(dirname "$0")/fzf"
|
||||||
[[ -x "$fzf" ]] || fail 'fzf executable not found'
|
[[ -x "$fzf" ]] || fail 'fzf executable not found'
|
||||||
|
|
||||||
args=()
|
args=()
|
||||||
@@ -95,9 +95,9 @@ while [[ $# -gt 0 ]]; do
|
|||||||
elif [[ "$size" =~ %$ ]]; then
|
elif [[ "$size" =~ %$ ]]; then
|
||||||
size=${size:0:((${#size}-1))}
|
size=${size:0:((${#size}-1))}
|
||||||
if [[ -n "$swap" ]]; then
|
if [[ -n "$swap" ]]; then
|
||||||
opt="$opt -p $(( 100 - size ))"
|
opt="$opt -l $(( 100 - size ))%"
|
||||||
else
|
else
|
||||||
opt="$opt -p $size"
|
opt="$opt -l $size%"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if [[ -n "$swap" ]]; then
|
if [[ -n "$swap" ]]; then
|
||||||
@@ -132,8 +132,10 @@ if [[ -z "$TMUX" ]]; then
|
|||||||
exit $?
|
exit $?
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --height option is not allowed. CTRL-Z is also disabled.
|
# * --height option is not allowed
|
||||||
args=("${args[@]}" "--no-height" "--bind=ctrl-z:ignore")
|
# * CTRL-Z is also disabled
|
||||||
|
# * fzf-tmux script is not compatible with --tmux option in fzf 0.53.0 or later
|
||||||
|
args=("${args[@]}" "--no-height" "--bind=ctrl-z:ignore" "--no-tmux")
|
||||||
|
|
||||||
# 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" =~ "-E" ]] && tmux list-panes -F '#F' | grep -q Z; then
|
if [[ ! "$opt" =~ "-E" ]] && tmux list-panes -F '#F' | grep -q Z; then
|
||||||
@@ -196,8 +198,9 @@ if [[ "$opt" =~ "-E" ]]; then
|
|||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
[[ -n "$FZF_DEFAULT_OPTS" ]] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
|
envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
|
||||||
[[ -n "$FZF_DEFAULT_COMMAND" ]] && envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
|
envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
|
||||||
|
envs="$envs FZF_DEFAULT_OPTS_FILE=$(printf %q "$FZF_DEFAULT_OPTS_FILE")"
|
||||||
[[ -n "$RUNEWIDTH_EASTASIAN" ]] && envs="$envs RUNEWIDTH_EASTASIAN=$(printf %q "$RUNEWIDTH_EASTASIAN")"
|
[[ -n "$RUNEWIDTH_EASTASIAN" ]] && envs="$envs RUNEWIDTH_EASTASIAN=$(printf %q "$RUNEWIDTH_EASTASIAN")"
|
||||||
[[ -n "$BAT_THEME" ]] && envs="$envs BAT_THEME=$(printf %q "$BAT_THEME")"
|
[[ -n "$BAT_THEME" ]] && envs="$envs BAT_THEME=$(printf %q "$BAT_THEME")"
|
||||||
echo "$envs;" > "$argsf"
|
echo "$envs;" > "$argsf"
|
||||||
|
40
doc/fzf.txt
40
doc/fzf.txt
@@ -1,4 +1,4 @@
|
|||||||
fzf.txt fzf Last change: January 1 2024
|
fzf.txt fzf Last change: February 15 2024
|
||||||
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
|
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
|
||||||
==============================================================================
|
==============================================================================
|
||||||
|
|
||||||
@@ -264,17 +264,18 @@ entry.
|
|||||||
call fzf#run({'sink': 'e'})
|
call fzf#run({'sink': 'e'})
|
||||||
<
|
<
|
||||||
We haven't specified the `source`, so this is equivalent to starting fzf on
|
We haven't specified the `source`, so this is equivalent to starting fzf on
|
||||||
command line without standard input pipe; fzf will use find command (or
|
command line without standard input pipe; fzf will traverse the file system
|
||||||
`$FZF_DEFAULT_COMMAND` if defined) to list the files under the current
|
under the current directory to get the list of files. (If
|
||||||
directory. When you select one, it will open it with the sink, `:e` command.
|
`$FZF_DEFAULT_COMMAND` is set, fzf will use the output of the command
|
||||||
If you want to open it in a new tab, you can pass `:tabedit` command instead
|
instead.) When you select one, it will open it with the sink, `:e` command. If
|
||||||
as the sink.
|
you want to open it in a new tab, you can pass `:tabedit` command instead as
|
||||||
|
the sink.
|
||||||
>
|
>
|
||||||
call fzf#run({'sink': 'tabedit'})
|
call fzf#run({'sink': 'tabedit'})
|
||||||
<
|
<
|
||||||
Instead of using the default find command, you can use any shell command as
|
You can use any shell command as the source to generate the list. The
|
||||||
the source. The following example will list the files managed by git. It's
|
following example will list the files managed by git. It's equivalent to
|
||||||
equivalent to running `git ls-files | fzf` on shell.
|
running `git ls-files | fzf` on shell.
|
||||||
>
|
>
|
||||||
call fzf#run({'source': 'git ls-files', 'sink': 'e'})
|
call fzf#run({'source': 'git ls-files', 'sink': 'e'})
|
||||||
<
|
<
|
||||||
@@ -310,7 +311,7 @@ The following table summarizes the available options.
|
|||||||
`options` | string/list | Options to fzf
|
`options` | string/list | Options to fzf
|
||||||
`dir` | string | Working directory
|
`dir` | string | Working directory
|
||||||
`up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` )
|
`up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` )
|
||||||
`tmux` | string | (Layout) fzf-tmux options (e.g. `-p90%,60%` )
|
`tmux` | string | (Layout) `--tmux` options (e.g. `90%,70%` )
|
||||||
`window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new` )
|
`window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new` )
|
||||||
`window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width': 0.9, 'height': 0.6}` )
|
`window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width': 0.9, 'height': 0.6}` )
|
||||||
---------------------------+---------------+----------------------------------------------------------------------
|
---------------------------+---------------+----------------------------------------------------------------------
|
||||||
@@ -417,24 +418,12 @@ TIPS *fzf-tips*
|
|||||||
< fzf inside terminal buffer >________________________________________________~
|
< fzf inside terminal buffer >________________________________________________~
|
||||||
*fzf-inside-terminal-buffer*
|
*fzf-inside-terminal-buffer*
|
||||||
|
|
||||||
The latest versions of Vim and Neovim include builtin terminal emulator
|
|
||||||
(`:terminal`) and fzf will start in a terminal buffer in the following cases:
|
|
||||||
|
|
||||||
- On Neovim
|
|
||||||
- On GVim
|
|
||||||
- On Terminal Vim with a non-default layout
|
|
||||||
- `call fzf#run({'left': '30%'})` or `let g:fzf_layout = {'left': '30%'}`
|
|
||||||
|
|
||||||
On the latest versions of Vim and Neovim, fzf will start in a terminal buffer.
|
On the latest versions of Vim and Neovim, fzf will start in a terminal buffer.
|
||||||
If you find the default ANSI colors to be different, consider configuring the
|
If you find the default ANSI colors to be different, consider configuring the
|
||||||
colors using `g:terminal_ansi_colors` in regular Vim or `g:terminal_color_x`
|
colors using `g:terminal_ansi_colors` in regular Vim or `g:terminal_color_x`
|
||||||
in Neovim.
|
in Neovim.
|
||||||
|
|
||||||
*g:terminal_color_15* *g:terminal_color_14* *g:terminal_color_13*
|
|
||||||
*g:terminal_color_12* *g:terminal_color_11* *g:terminal_color_10* *g:terminal_color_9*
|
|
||||||
*g:terminal_color_8* *g:terminal_color_7* *g:terminal_color_6* *g:terminal_color_5*
|
|
||||||
*g:terminal_color_4* *g:terminal_color_3* *g:terminal_color_2* *g:terminal_color_1*
|
|
||||||
*g:terminal_color_0*
|
|
||||||
>
|
>
|
||||||
" Terminal colors for seoul256 color scheme
|
" Terminal colors for seoul256 color scheme
|
||||||
if has('nvim')
|
if has('nvim')
|
||||||
@@ -480,11 +469,12 @@ in Neovim.
|
|||||||
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
||||||
<
|
<
|
||||||
Alternatively, you can make fzf open in a tmux popup window (requires tmux 3.2
|
Alternatively, you can make fzf open in a tmux popup window (requires tmux 3.2
|
||||||
or above) by putting fzf-tmux options in `tmux` key.
|
or above) by putting `--tmux` options in `tmux` key.
|
||||||
>
|
>
|
||||||
" See `man fzf-tmux` for available options
|
" See `--tmux` option in `man fzf` for available options
|
||||||
|
" [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]
|
||||||
if exists('$TMUX')
|
if exists('$TMUX')
|
||||||
let g:fzf_layout = { 'tmux': '-p90%,60%' }
|
let g:fzf_layout = { 'tmux': '90%,70%' }
|
||||||
else
|
else
|
||||||
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
||||||
endif
|
endif
|
||||||
|
15
go.mod
15
go.mod
@@ -1,21 +1,20 @@
|
|||||||
module github.com/junegunn/fzf
|
module github.com/junegunn/fzf
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gdamore/tcell/v2 v2.7.0
|
github.com/charlievieth/fastwalk v1.0.7-0.20240703190418-87029d931815
|
||||||
github.com/mattn/go-isatty v0.0.17
|
github.com/gdamore/tcell/v2 v2.7.4
|
||||||
|
github.com/mattn/go-isatty v0.0.20
|
||||||
github.com/mattn/go-shellwords v1.0.12
|
github.com/mattn/go-shellwords v1.0.12
|
||||||
github.com/rivo/uniseg v0.4.6
|
github.com/rivo/uniseg v0.4.7
|
||||||
github.com/saracen/walker v0.1.3
|
golang.org/x/sys v0.22.0
|
||||||
golang.org/x/sys v0.16.0
|
golang.org/x/term v0.22.0
|
||||||
golang.org/x/term v0.16.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gdamore/encoding v1.0.0 // indirect
|
github.com/gdamore/encoding v1.0.0 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||||
golang.org/x/sync v0.5.0 // indirect
|
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.17
|
go 1.20
|
||||||
|
33
go.sum
33
go.sum
@@ -1,21 +1,21 @@
|
|||||||
|
github.com/charlievieth/fastwalk v1.0.7-0.20240703190418-87029d931815 h1:4PRbYm9OMgH0bcdZZqMXA/AoOvpGy4l0H6g9Au/kgGA=
|
||||||
|
github.com/charlievieth/fastwalk v1.0.7-0.20240703190418-87029d931815/go.mod h1:rV19+IF9Y2TYQNy4MqEk5M/spNHjKsA0i71yrsv2p4E=
|
||||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||||
github.com/gdamore/tcell/v2 v2.7.0 h1:I5LiGTQuwrysAt1KS9wg1yFfOI3arI3ucFrxtd/xqaA=
|
github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU=
|
||||||
github.com/gdamore/tcell/v2 v2.7.0/go.mod h1:hl/KtAANGBecfIPxk+FzKvThTqI84oplgbPEmVX60b8=
|
github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
||||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/saracen/walker v0.1.3 h1:YtcKKmpRPy6XJTHJ75J2QYXXZYWnZNQxPCVqZSHVV/g=
|
|
||||||
github.com/saracen/walker v0.1.3/go.mod h1:FU+7qU8DeQQgSZDmmThMJi93kPkLFgy0oVAcLxurjIk=
|
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
@@ -26,27 +26,24 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
|||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
|
||||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||||
|
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
|
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
|
||||||
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
46
install
46
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.46.1
|
version=0.54.0
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
@@ -115,7 +115,7 @@ link_fzf_in_path() {
|
|||||||
try_curl() {
|
try_curl() {
|
||||||
command -v curl > /dev/null &&
|
command -v curl > /dev/null &&
|
||||||
if [[ $1 =~ tar.gz$ ]]; then
|
if [[ $1 =~ tar.gz$ ]]; then
|
||||||
curl -fL $1 | tar -xzf -
|
curl -fL $1 | tar --no-same-owner -xzf -
|
||||||
else
|
else
|
||||||
local temp=${TMPDIR:-/tmp}/fzf.zip
|
local temp=${TMPDIR:-/tmp}/fzf.zip
|
||||||
curl -fLo "$temp" $1 && unzip -o "$temp" && rm -f "$temp"
|
curl -fLo "$temp" $1 && unzip -o "$temp" && rm -f "$temp"
|
||||||
@@ -125,7 +125,7 @@ try_curl() {
|
|||||||
try_wget() {
|
try_wget() {
|
||||||
command -v wget > /dev/null &&
|
command -v wget > /dev/null &&
|
||||||
if [[ $1 =~ tar.gz$ ]]; then
|
if [[ $1 =~ tar.gz$ ]]; then
|
||||||
wget -O - $1 | tar -xzf -
|
wget -O - $1 | tar --no-same-owner -xzf -
|
||||||
else
|
else
|
||||||
local temp=${TMPDIR:-/tmp}/fzf.zip
|
local temp=${TMPDIR:-/tmp}/fzf.zip
|
||||||
wget -O "$temp" $1 && unzip -o "$temp" && rm -f "$temp"
|
wget -O "$temp" $1 && unzip -o "$temp" && rm -f "$temp"
|
||||||
@@ -146,7 +146,7 @@ download() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
local url
|
local url
|
||||||
url=https://github.com/junegunn/fzf/releases/download/$version/${1}
|
url=https://github.com/junegunn/fzf/releases/download/v$version/${1}
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
if ! (try_curl $url || try_wget $url); then
|
if ! (try_curl $url || try_wget $url); then
|
||||||
set +o pipefail
|
set +o pipefail
|
||||||
@@ -262,6 +262,16 @@ if [[ ! "\$PATH" == *$fzf_base_esc/bin* ]]; then
|
|||||||
PATH="\${PATH:+\${PATH}:}$fzf_base/bin"
|
PATH="\${PATH:+\${PATH}:}$fzf_base/bin"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [[ $auto_completion -eq 1 ]] && [[ $key_bindings -eq 1 ]]; then
|
||||||
|
if [[ "$shell" = zsh ]]; then
|
||||||
|
echo "source <(fzf --$shell)" >> "$src"
|
||||||
|
else
|
||||||
|
echo "eval \"\$(fzf --$shell)\"" >> "$src"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
cat >> "$src" << EOF
|
||||||
# Auto-completion
|
# Auto-completion
|
||||||
# ---------------
|
# ---------------
|
||||||
$fzf_completion
|
$fzf_completion
|
||||||
@@ -270,6 +280,7 @@ $fzf_completion
|
|||||||
# ------------
|
# ------------
|
||||||
$fzf_key_bindings
|
$fzf_key_bindings
|
||||||
EOF
|
EOF
|
||||||
|
fi
|
||||||
echo "OK"
|
echo "OK"
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -281,18 +292,6 @@ if [[ "$shells" =~ fish ]]; then
|
|||||||
or set --universal fish_user_paths \$fish_user_paths "$fzf_base"/bin
|
or set --universal fish_user_paths \$fish_user_paths "$fzf_base"/bin
|
||||||
EOF
|
EOF
|
||||||
[ $? -eq 0 ] && echo "OK" || echo "Failed"
|
[ $? -eq 0 ] && echo "OK" || echo "Failed"
|
||||||
|
|
||||||
mkdir -p "${fish_dir}/functions"
|
|
||||||
fish_binding="${fish_dir}/functions/fzf_key_bindings.fish"
|
|
||||||
if [ $key_bindings -ne 0 ]; then
|
|
||||||
echo -n "Symlink $fish_binding ... "
|
|
||||||
ln -sf "$fzf_base/shell/key-bindings.fish" \
|
|
||||||
"$fish_binding" && echo "OK" || echo "Failed"
|
|
||||||
else
|
|
||||||
echo -n "Removing $fish_binding ... "
|
|
||||||
rm -f "$fish_binding"
|
|
||||||
echo "OK"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
append_line() {
|
append_line() {
|
||||||
@@ -355,12 +354,23 @@ done
|
|||||||
if [ $key_bindings -eq 1 ] && [[ "$shells" =~ fish ]]; then
|
if [ $key_bindings -eq 1 ] && [[ "$shells" =~ fish ]]; then
|
||||||
bind_file="${fish_dir}/functions/fish_user_key_bindings.fish"
|
bind_file="${fish_dir}/functions/fish_user_key_bindings.fish"
|
||||||
if [ ! -e "$bind_file" ]; then
|
if [ ! -e "$bind_file" ]; then
|
||||||
|
mkdir -p "${fish_dir}/functions"
|
||||||
create_file "$bind_file" \
|
create_file "$bind_file" \
|
||||||
'function fish_user_key_bindings' \
|
'function fish_user_key_bindings' \
|
||||||
' fzf_key_bindings' \
|
' fzf --fish | source' \
|
||||||
'end'
|
'end'
|
||||||
else
|
else
|
||||||
append_line $update_config "fzf_key_bindings" "$bind_file"
|
echo "Check $bind_file:"
|
||||||
|
lno=$(\grep -nF "fzf_key_bindings" "$bind_file" | sed 's/:.*//' | tr '\n' ' ')
|
||||||
|
if [[ -n $lno ]]; then
|
||||||
|
echo " ** Found 'fzf_key_bindings' in line #$lno"
|
||||||
|
echo " ** You have to replace the line to 'fzf --fish | source'"
|
||||||
|
echo
|
||||||
|
else
|
||||||
|
echo " - Clear"
|
||||||
|
echo
|
||||||
|
append_line $update_config "fzf --fish | source" "$bind_file"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
$version="0.46.1"
|
$version="0.54.0"
|
||||||
|
|
||||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ function download {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
cd "$fzf_base\bin"
|
cd "$fzf_base\bin"
|
||||||
$url="https://github.com/junegunn/fzf/releases/download/$version/$file"
|
$url="https://github.com/junegunn/fzf/releases/download/v$version/$file"
|
||||||
$temp=$env:TMP + "\fzf.zip"
|
$temp=$env:TMP + "\fzf.zip"
|
||||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
if ($PSVersionTable.PSVersion.Major -ge 3) {
|
if ($PSVersionTable.PSVersion.Major -ge 3) {
|
||||||
|
93
main.go
93
main.go
@@ -1,14 +1,101 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
fzf "github.com/junegunn/fzf/src"
|
fzf "github.com/junegunn/fzf/src"
|
||||||
"github.com/junegunn/fzf/src/protector"
|
"github.com/junegunn/fzf/src/protector"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "0.46"
|
var version = "0.54"
|
||||||
var revision string = "devel"
|
var revision = "devel"
|
||||||
|
|
||||||
|
//go:embed shell/key-bindings.bash
|
||||||
|
var bashKeyBindings []byte
|
||||||
|
|
||||||
|
//go:embed shell/completion.bash
|
||||||
|
var bashCompletion []byte
|
||||||
|
|
||||||
|
//go:embed shell/key-bindings.zsh
|
||||||
|
var zshKeyBindings []byte
|
||||||
|
|
||||||
|
//go:embed shell/completion.zsh
|
||||||
|
var zshCompletion []byte
|
||||||
|
|
||||||
|
//go:embed shell/key-bindings.fish
|
||||||
|
var fishKeyBindings []byte
|
||||||
|
|
||||||
|
//go:embed man/man1/fzf.1
|
||||||
|
var manPage []byte
|
||||||
|
|
||||||
|
func printScript(label string, content []byte) {
|
||||||
|
fmt.Println("### " + label + " ###")
|
||||||
|
fmt.Println(strings.TrimSpace(string(content)))
|
||||||
|
fmt.Println("### end: " + label + " ###")
|
||||||
|
}
|
||||||
|
|
||||||
|
func exit(code int, err error) {
|
||||||
|
if code == fzf.ExitError && err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err.Error())
|
||||||
|
}
|
||||||
|
os.Exit(code)
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
protector.Protect()
|
protector.Protect()
|
||||||
fzf.Run(fzf.ParseOptions(), version, revision)
|
|
||||||
|
options, err := fzf.ParseOptions(true, os.Args[1:])
|
||||||
|
if err != nil {
|
||||||
|
exit(fzf.ExitError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if options.Bash {
|
||||||
|
printScript("key-bindings.bash", bashKeyBindings)
|
||||||
|
printScript("completion.bash", bashCompletion)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if options.Zsh {
|
||||||
|
printScript("key-bindings.zsh", zshKeyBindings)
|
||||||
|
printScript("completion.zsh", zshCompletion)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if options.Fish {
|
||||||
|
printScript("key-bindings.fish", fishKeyBindings)
|
||||||
|
fmt.Println("fzf_key_bindings")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if options.Help {
|
||||||
|
fmt.Print(fzf.Usage)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if options.Version {
|
||||||
|
if len(revision) > 0 {
|
||||||
|
fmt.Printf("%s (%s)\n", version, revision)
|
||||||
|
} else {
|
||||||
|
fmt.Println(version)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if options.Man {
|
||||||
|
file := fzf.WriteTemporaryFile([]string{string(manPage)}, "\n")
|
||||||
|
if len(file) == 0 {
|
||||||
|
fmt.Print(string(manPage))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer os.Remove(file)
|
||||||
|
cmd := exec.Command("man", file)
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
fmt.Print(string(manPage))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
code, err := fzf.Run(options)
|
||||||
|
exit(code, err)
|
||||||
}
|
}
|
||||||
|
@@ -21,48 +21,48 @@ 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 "Feb 2024" "fzf 0.46.1" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf\-tmux 1 "Jul 2024" "fzf 0.54.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
|
||||||
|
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
.B fzf-tmux [LAYOUT OPTIONS] [--] [FZF OPTIONS]
|
.B fzf\-tmux [LAYOUT OPTIONS] [\-\-] [FZF OPTIONS]
|
||||||
|
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
fzf-tmux is a wrapper script for fzf that opens fzf in a tmux split pane or in
|
fzf\-tmux is a wrapper script for fzf that opens fzf in a tmux split pane or in
|
||||||
a tmux popup window. It is designed to work just like fzf except that it does
|
a tmux popup window. It is designed to work just like fzf except that it does
|
||||||
not take up the whole screen. You can safely use fzf-tmux instead of fzf in
|
not take up the whole screen. You can safely use fzf\-tmux instead of fzf in
|
||||||
your scripts as the extra options will be silently ignored if you're not on
|
your scripts as the extra options will be silently ignored if you're not on
|
||||||
tmux.
|
tmux.
|
||||||
|
|
||||||
.SH LAYOUT OPTIONS
|
.SH LAYOUT OPTIONS
|
||||||
|
|
||||||
(default layout: \fB-d 50%\fR)
|
(default layout: \fB\-d 50%\fR)
|
||||||
|
|
||||||
.SS Popup window
|
.SS Popup window
|
||||||
(requires tmux 3.2 or above)
|
(requires tmux 3.2 or above)
|
||||||
.TP
|
.TP
|
||||||
.B "-p [WIDTH[%][,HEIGHT[%]]]"
|
.B "\-p [WIDTH[%][,HEIGHT[%]]]"
|
||||||
.TP
|
.TP
|
||||||
.B "-w WIDTH[%]"
|
.B "\-w WIDTH[%]"
|
||||||
.TP
|
.TP
|
||||||
.B "-h WIDTH[%]"
|
.B "\-h WIDTH[%]"
|
||||||
.TP
|
.TP
|
||||||
.B "-x COL"
|
.B "\-x COL"
|
||||||
.TP
|
.TP
|
||||||
.B "-y ROW"
|
.B "\-y ROW"
|
||||||
|
|
||||||
.SS Split pane
|
.SS Split pane
|
||||||
.TP
|
.TP
|
||||||
.B "-u [height[%]]"
|
.B "\-u [height[%]]"
|
||||||
Split above (up)
|
Split above (up)
|
||||||
.TP
|
.TP
|
||||||
.B "-d [height[%]]"
|
.B "\-d [height[%]]"
|
||||||
Split below (down)
|
Split below (down)
|
||||||
.TP
|
.TP
|
||||||
.B "-l [width[%]]"
|
.B "\-l [width[%]]"
|
||||||
Split left
|
Split left
|
||||||
.TP
|
.TP
|
||||||
.B "-r [width[%]]"
|
.B "\-r [width[%]]"
|
||||||
Split right
|
Split right
|
||||||
|
1110
man/man1/fzf.1
1110
man/man1/fzf.1
File diff suppressed because it is too large
Load Diff
@@ -59,12 +59,9 @@ if s:is_win
|
|||||||
return iconv(a:str, &encoding, 'cp'.s:codepage)
|
return iconv(a:str, &encoding, 'cp'.s:codepage)
|
||||||
endfunction
|
endfunction
|
||||||
function! s:wrap_cmds(cmds)
|
function! s:wrap_cmds(cmds)
|
||||||
return map([
|
return map(['@echo off']
|
||||||
\ '@echo off',
|
|
||||||
\ 'setlocal enabledelayedexpansion']
|
|
||||||
\ + (has('gui_running') ? ['set TERM= > nul'] : [])
|
\ + (has('gui_running') ? ['set TERM= > nul'] : [])
|
||||||
\ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds])
|
\ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds]),
|
||||||
\ + ['endlocal'],
|
|
||||||
\ '<SID>enc_to_cp(v:val."\r")')
|
\ '<SID>enc_to_cp(v:val."\r")')
|
||||||
endfunction
|
endfunction
|
||||||
else
|
else
|
||||||
@@ -84,11 +81,21 @@ else
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
function! s:shellesc_cmd(arg)
|
function! s:shellesc_cmd(arg)
|
||||||
let escaped = substitute(a:arg, '[&|<>()@^]', '^&', 'g')
|
let e = '"'
|
||||||
let escaped = substitute(escaped, '%', '%%', 'g')
|
let slashes = 0
|
||||||
let escaped = substitute(escaped, '"', '\\^&', 'g')
|
for c in split(a:arg, '\zs')
|
||||||
let escaped = substitute(escaped, '\(\\\+\)\(\\^\)', '\1\1\2', 'g')
|
if c ==# '\'
|
||||||
return '^"'.substitute(escaped, '\(\\\+\)$', '\1\1', '').'^"'
|
let slashes += 1
|
||||||
|
elseif c ==# '"'
|
||||||
|
let e .= repeat('\', slashes + 1)
|
||||||
|
let slashes = 0
|
||||||
|
else
|
||||||
|
let slashes = 0
|
||||||
|
endif
|
||||||
|
let e .= c
|
||||||
|
endfor
|
||||||
|
let e .= repeat('\', slashes) .'"'
|
||||||
|
return substitute(substitute(e, '[&|<>()^!"]', '^&', 'g'), '%', '%%', 'g')
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! fzf#shellescape(arg, ...)
|
function! fzf#shellescape(arg, ...)
|
||||||
@@ -320,7 +327,10 @@ function! s:common_sink(action, lines) abort
|
|||||||
" the execution (e.g. `set autochdir` or `autocmd BufEnter * lcd ...`)
|
" the execution (e.g. `set autochdir` or `autocmd BufEnter * lcd ...`)
|
||||||
let cwd = exists('w:fzf_pushd') ? w:fzf_pushd.dir : expand('%:p:h')
|
let cwd = exists('w:fzf_pushd') ? w:fzf_pushd.dir : expand('%:p:h')
|
||||||
for item in a:lines
|
for item in a:lines
|
||||||
if item[0] != '~' && item !~ (s:is_win ? '^[A-Z]:\' : '^/')
|
if has('win32unix') && item !~ '/'
|
||||||
|
let item = substitute(item, '\', '/', 'g')
|
||||||
|
end
|
||||||
|
if item[0] != '~' && item !~ (s:is_win ? '^\([A-Z]:\)\?\' : '^/')
|
||||||
let sep = s:is_win ? '\' : '/'
|
let sep = s:is_win ? '\' : '/'
|
||||||
let item = join([cwd, item], cwd[len(cwd)-1] == sep ? '' : sep)
|
let item = join([cwd, item], cwd[len(cwd)-1] == sep ? '' : sep)
|
||||||
endif
|
endif
|
||||||
@@ -480,6 +490,8 @@ function! s:extract_option(opts, name)
|
|||||||
return opt
|
return opt
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
let s:need_cmd_window = has('win32unix') && $TERM_PROGRAM ==# 'mintty' && s:compare_versions($TERM_PROGRAM_VERSION, '3.4.5') < 0 && !executable('winpty')
|
||||||
|
|
||||||
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()
|
||||||
@@ -501,19 +513,19 @@ try
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
if has_key(dict, 'source')
|
if has_key(dict, 'source')
|
||||||
let source = remove(dict, 'source')
|
let source = dict.source
|
||||||
let type = type(source)
|
let type = type(source)
|
||||||
if type == 1
|
if type == 1
|
||||||
let source_command = source
|
let prefix = '('.source.')|'
|
||||||
elseif type == 3
|
elseif type == 3
|
||||||
let temps.input = s:fzf_tempname()
|
let temps.input = s:fzf_tempname()
|
||||||
call s:writefile(source, temps.input)
|
call s:writefile(source, temps.input)
|
||||||
let source_command = (s:is_win ? 'type ' : 'cat ').fzf#shellescape(temps.input)
|
let prefix = (s:is_win ? 'type ' : 'command cat ').fzf#shellescape(temps.input).'|'
|
||||||
else
|
else
|
||||||
throw 'Invalid source type'
|
throw 'Invalid source type'
|
||||||
endif
|
endif
|
||||||
else
|
else
|
||||||
let source_command = ''
|
let prefix = ''
|
||||||
endif
|
endif
|
||||||
|
|
||||||
let prefer_tmux = get(g:, 'fzf_prefer_tmux', 0) || has_key(dict, 'tmux')
|
let prefer_tmux = get(g:, 'fzf_prefer_tmux', 0) || has_key(dict, 'tmux')
|
||||||
@@ -522,26 +534,23 @@ try
|
|||||||
\ executable('tput') && filereadable('/dev/tty')
|
\ executable('tput') && filereadable('/dev/tty')
|
||||||
let has_vim8_term = has('terminal') && has('patch-8.0.995')
|
let has_vim8_term = has('terminal') && has('patch-8.0.995')
|
||||||
let has_nvim_term = has('nvim-0.2.1') || has('nvim') && !s:is_win
|
let has_nvim_term = has('nvim-0.2.1') || has('nvim') && !s:is_win
|
||||||
let use_term = has_nvim_term ||
|
let use_term = has_nvim_term || has_vim8_term
|
||||||
\ has_vim8_term && !has('win32unix') && (has('gui_running') || s:is_win || s:present(dict, 'down', 'up', 'left', 'right', 'window'))
|
\ && !s:need_cmd_window
|
||||||
|
\ && (has('gui_running') || s:is_win || s:present(dict, 'down', 'up', 'left', 'right', 'window'))
|
||||||
let use_tmux = (has_key(dict, 'tmux') || (!use_height && !use_term || prefer_tmux) && !has('win32unix') && s:splittable(dict)) && s:tmux_enabled()
|
let use_tmux = (has_key(dict, 'tmux') || (!use_height && !use_term || prefer_tmux) && !has('win32unix') && s:splittable(dict)) && s:tmux_enabled()
|
||||||
if prefer_tmux && use_tmux
|
if prefer_tmux && use_tmux
|
||||||
let use_height = 0
|
let use_height = 0
|
||||||
let use_term = 0
|
let use_term = 0
|
||||||
endif
|
endif
|
||||||
if use_term
|
if use_term
|
||||||
let optstr .= ' --no-height'
|
let optstr .= ' --no-height --no-tmux'
|
||||||
elseif use_height
|
elseif use_height
|
||||||
let height = s:calc_size(&lines, dict.down, dict)
|
let height = s:calc_size(&lines, dict.down, dict)
|
||||||
let optstr .= ' --height='.height
|
let optstr .= ' --no-tmux --height='.height
|
||||||
endif
|
endif
|
||||||
" Respect --border option given in $FZF_DEFAULT_OPTS and 'options'
|
" 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 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 command = prefix.(use_tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
|
||||||
if len(source_command)
|
|
||||||
let $FZF_DEFAULT_COMMAND = source_command
|
|
||||||
endif
|
|
||||||
let command = (use_tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
|
|
||||||
|
|
||||||
if use_term
|
if use_term
|
||||||
return s:execute_term(dict, command, temps)
|
return s:execute_term(dict, command, temps)
|
||||||
@@ -552,14 +561,6 @@ try
|
|||||||
call s:callback(dict, lines)
|
call s:callback(dict, lines)
|
||||||
return lines
|
return lines
|
||||||
finally
|
finally
|
||||||
if exists('source_command') && len(source_command)
|
|
||||||
if len(prev_default_command)
|
|
||||||
let $FZF_DEFAULT_COMMAND = prev_default_command
|
|
||||||
else
|
|
||||||
let $FZF_DEFAULT_COMMAND = ''
|
|
||||||
silent! execute 'unlet $FZF_DEFAULT_COMMAND'
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
let [&shell, &shellslash, &shellcmdflag, &shellxquote] = [shell, shellslash, shellcmdflag, shellxquote]
|
let [&shell, &shellslash, &shellcmdflag, &shellxquote] = [shell, shellslash, shellcmdflag, shellxquote]
|
||||||
endtry
|
endtry
|
||||||
endfunction
|
endfunction
|
||||||
@@ -578,19 +579,21 @@ function! s:fzf_tmux(dict)
|
|||||||
if empty(size)
|
if empty(size)
|
||||||
for o in ['up', 'down', 'left', 'right']
|
for o in ['up', 'down', 'left', 'right']
|
||||||
if s:present(a:dict, o)
|
if s:present(a:dict, o)
|
||||||
let spec = a:dict[o]
|
let size = o . ',' . a:dict[o]
|
||||||
if (o == 'up' || o == 'down') && spec[0] == '~'
|
|
||||||
let size = '-'.o[0].s:calc_size(&lines, spec, a:dict)
|
|
||||||
else
|
|
||||||
" Legacy boolean option
|
|
||||||
let size = '-'.o[0].(spec == 1 ? '' : substitute(spec, '^\~', '', ''))
|
|
||||||
endif
|
|
||||||
break
|
break
|
||||||
endif
|
endif
|
||||||
endfor
|
endfor
|
||||||
endif
|
endif
|
||||||
return printf('LINES=%d COLUMNS=%d %s %s - --',
|
|
||||||
\ &lines, &columns, fzf#shellescape(s:fzf_tmux), size)
|
" Legacy fzf-tmux options
|
||||||
|
if size =~ '-'
|
||||||
|
return printf('LINES=%d COLUMNS=%d %s %s %s --',
|
||||||
|
\ &lines, &columns, fzf#shellescape(s:fzf_tmux), size, (has_key(a:dict, 'source') ? '' : '-'))
|
||||||
|
end
|
||||||
|
|
||||||
|
" Using native --tmux option
|
||||||
|
let in = (has_key(a:dict, 'source') ? '' : ' --force-tty-in')
|
||||||
|
return printf('%s --tmux %s%s', fzf#shellescape(fzf#exec()), size, in)
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:splittable(dict)
|
function! s:splittable(dict)
|
||||||
@@ -713,14 +716,15 @@ function! s:execute(dict, command, use_height, temps) abort
|
|||||||
call jobstart(cmd, fzf)
|
call jobstart(cmd, fzf)
|
||||||
return []
|
return []
|
||||||
endif
|
endif
|
||||||
elseif has('win32unix') && $TERM !=# 'cygwin'
|
elseif s:need_cmd_window
|
||||||
let shellscript = s:fzf_tempname()
|
let shellscript = s:fzf_tempname()
|
||||||
call s:writefile([command], shellscript)
|
call s:writefile([command], shellscript)
|
||||||
let command = 'cmd.exe /C '.fzf#shellescape('set "TERM=" & start /WAIT sh -c '.shellscript)
|
let command = 'start //WAIT sh -c '.shellscript
|
||||||
let a:temps.shellscript = shellscript
|
let a:temps.shellscript = shellscript
|
||||||
endif
|
endif
|
||||||
if a:use_height
|
if a:use_height
|
||||||
call system(printf('tput cup %d > /dev/tty; tput cnorm > /dev/tty; %s < /dev/tty 2> /dev/tty', &lines, command))
|
let stdin = has_key(a:dict, 'source') ? '' : '< /dev/tty'
|
||||||
|
call system(printf('tput cup %d > /dev/tty; tput cnorm > /dev/tty; %s %s 2> /dev/tty', &lines, command, stdin))
|
||||||
else
|
else
|
||||||
execute 'silent !'.command
|
execute 'silent !'.command
|
||||||
endif
|
endif
|
||||||
|
@@ -4,37 +4,44 @@
|
|||||||
# / __/ / /_/ __/
|
# / __/ / /_/ __/
|
||||||
# /_/ /___/_/ completion.bash
|
# /_/ /___/_/ completion.bash
|
||||||
#
|
#
|
||||||
# - $FZF_TMUX (default: 0)
|
# - $FZF_TMUX (default: 0)
|
||||||
# - $FZF_TMUX_OPTS (default: empty)
|
# - $FZF_TMUX_OPTS (default: empty)
|
||||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||||
|
# - $FZF_COMPLETION_PATH_OPTS (default: empty)
|
||||||
|
# - $FZF_COMPLETION_DIR_OPTS (default: empty)
|
||||||
|
|
||||||
[[ $- =~ i ]] || return 0
|
if [[ $- =~ i ]]; then
|
||||||
|
|
||||||
|
|
||||||
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
||||||
if ! declare -F _fzf_compgen_path > /dev/null; then
|
#
|
||||||
_fzf_compgen_path() {
|
# _fzf_compgen_path() {
|
||||||
echo "$1"
|
# echo "$1"
|
||||||
command find -L "$1" \
|
# command find -L "$1" \
|
||||||
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
# -name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
||||||
-a -not -path "$1" -print 2> /dev/null | command sed 's@^\./@@'
|
# -a -not -path "$1" -print 2> /dev/null | command sed 's@^\./@@'
|
||||||
}
|
# }
|
||||||
fi
|
#
|
||||||
|
# _fzf_compgen_dir() {
|
||||||
if ! declare -F _fzf_compgen_dir > /dev/null; then
|
# command find -L "$1" \
|
||||||
_fzf_compgen_dir() {
|
# -name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
|
||||||
command find -L "$1" \
|
# -a -not -path "$1" -print 2> /dev/null | command sed 's@^\./@@'
|
||||||
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
|
# }
|
||||||
-a -not -path "$1" -print 2> /dev/null | command sed 's@^\./@@'
|
|
||||||
}
|
|
||||||
fi
|
|
||||||
|
|
||||||
###########################################################
|
###########################################################
|
||||||
|
|
||||||
# To redraw line after fzf closes (printf '\e[5n')
|
# To redraw line after fzf closes (printf '\e[5n')
|
||||||
bind '"\e[0n": redraw-current-line' 2> /dev/null
|
bind '"\e[0n": redraw-current-line' 2> /dev/null
|
||||||
|
|
||||||
|
__fzf_defaults() {
|
||||||
|
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
echo "--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore $1"
|
||||||
|
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
||||||
|
echo "${FZF_DEFAULT_OPTS-} $2"
|
||||||
|
}
|
||||||
|
|
||||||
__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 "$@"
|
||||||
@@ -63,135 +70,118 @@ __fzf_orig_completion() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# @param $1 cmd - Command name for which the original completion is searched
|
||||||
|
# @var[out] REPLY - Original function name is returned
|
||||||
|
__fzf_orig_completion_get_orig_func() {
|
||||||
|
local cmd orig_var orig
|
||||||
|
cmd=$1
|
||||||
|
orig_var="_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}"
|
||||||
|
orig="${!orig_var-}"
|
||||||
|
REPLY="${orig##*#}"
|
||||||
|
[[ $REPLY ]] && type "$REPLY" &> /dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# @param $1 cmd - Command name for which the original completion is searched
|
||||||
|
# @param $2 func - Fzf's completion function to replace the original function
|
||||||
|
# @var[out] REPLY - Completion setting is returned as a string to "eval"
|
||||||
|
__fzf_orig_completion_instantiate() {
|
||||||
|
local cmd func orig_var orig
|
||||||
|
cmd=$1
|
||||||
|
func=$2
|
||||||
|
orig_var="_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}"
|
||||||
|
orig="${!orig_var-}"
|
||||||
|
orig="${orig%#*}"
|
||||||
|
[[ $orig == *' %s '* ]] || return 1
|
||||||
|
printf -v REPLY "$orig" "$func"
|
||||||
|
}
|
||||||
|
|
||||||
_fzf_opts_completion() {
|
_fzf_opts_completion() {
|
||||||
local cur prev opts
|
local cur prev opts
|
||||||
COMPREPLY=()
|
COMPREPLY=()
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||||
opts="
|
opts="
|
||||||
-h --help
|
|
||||||
-x --extended
|
|
||||||
-e --exact
|
|
||||||
--extended-exact
|
|
||||||
+x --no-extended
|
|
||||||
+e --no-exact
|
|
||||||
-q --query
|
|
||||||
-f --filter
|
|
||||||
--literal
|
|
||||||
--no-literal
|
|
||||||
--algo
|
|
||||||
--scheme
|
|
||||||
--expect
|
|
||||||
--no-expect
|
|
||||||
--enabled --no-phony
|
|
||||||
--disabled --phony
|
|
||||||
--tiebreak
|
|
||||||
--bind
|
|
||||||
--color
|
|
||||||
--toggle-sort
|
|
||||||
-d --delimiter
|
|
||||||
-n --nth
|
|
||||||
--with-nth
|
|
||||||
-s --sort
|
|
||||||
+s --no-sort
|
|
||||||
--track
|
|
||||||
--no-track
|
|
||||||
--tac
|
|
||||||
--no-tac
|
|
||||||
-i
|
|
||||||
+i
|
|
||||||
-m --multi
|
|
||||||
+m --no-multi
|
|
||||||
--ansi
|
|
||||||
--no-ansi
|
|
||||||
--no-mouse
|
|
||||||
+c --no-color
|
+c --no-color
|
||||||
+2 --no-256
|
+i --no-ignore-case
|
||||||
--black
|
+s --no-sort
|
||||||
--no-black
|
+x --no-extended
|
||||||
--bold
|
--ansi
|
||||||
--no-bold
|
--bash
|
||||||
--layout
|
--bind
|
||||||
--reverse
|
|
||||||
--no-reverse
|
|
||||||
--cycle
|
|
||||||
--no-cycle
|
|
||||||
--keep-right
|
|
||||||
--no-keep-right
|
|
||||||
--hscroll
|
|
||||||
--no-hscroll
|
|
||||||
--hscroll-off
|
|
||||||
--scroll-off
|
|
||||||
--filepath-word
|
|
||||||
--no-filepath-word
|
|
||||||
--info
|
|
||||||
--no-info
|
|
||||||
--inline-info
|
|
||||||
--no-inline-info
|
|
||||||
--separator
|
|
||||||
--no-separator
|
|
||||||
--scrollbar
|
|
||||||
--no-scrollbar
|
|
||||||
--jump-labels
|
|
||||||
-1 --select-1
|
|
||||||
+1 --no-select-1
|
|
||||||
-0 --exit-0
|
|
||||||
+0 --no-exit-0
|
|
||||||
--read0
|
|
||||||
--no-read0
|
|
||||||
--print0
|
|
||||||
--no-print0
|
|
||||||
--print-query
|
|
||||||
--no-print-query
|
|
||||||
--prompt
|
|
||||||
--pointer
|
|
||||||
--marker
|
|
||||||
--sync
|
|
||||||
--no-sync
|
|
||||||
--async
|
|
||||||
--no-history
|
|
||||||
--history
|
|
||||||
--history-size
|
|
||||||
--no-header
|
|
||||||
--no-header-lines
|
|
||||||
--header
|
|
||||||
--header-lines
|
|
||||||
--header-first
|
|
||||||
--no-header-first
|
|
||||||
--ellipsis
|
|
||||||
--preview
|
|
||||||
--no-preview
|
|
||||||
--preview-window
|
|
||||||
--height
|
|
||||||
--min-height
|
|
||||||
--no-height
|
|
||||||
--no-margin
|
|
||||||
--no-padding
|
|
||||||
--no-border
|
|
||||||
--border
|
--border
|
||||||
--no-border-label
|
|
||||||
--border-label
|
--border-label
|
||||||
--border-label-pos
|
--border-label-pos
|
||||||
--no-preview-label
|
--color
|
||||||
|
--cycle
|
||||||
|
--disabled
|
||||||
|
--ellipsis
|
||||||
|
--expect
|
||||||
|
--filepath-word
|
||||||
|
--fish
|
||||||
|
--header
|
||||||
|
--header-first
|
||||||
|
--header-lines
|
||||||
|
--height
|
||||||
|
--highlight-line
|
||||||
|
--history
|
||||||
|
--history-size
|
||||||
|
--hscroll-off
|
||||||
|
--info
|
||||||
|
--jump-labels
|
||||||
|
--keep-right
|
||||||
|
--layout
|
||||||
|
--listen
|
||||||
|
--listen-unsafe
|
||||||
|
--literal
|
||||||
|
--man
|
||||||
|
--margin
|
||||||
|
--marker
|
||||||
|
--min-height
|
||||||
|
--no-bold
|
||||||
|
--no-clear
|
||||||
|
--no-hscroll
|
||||||
|
--no-mouse
|
||||||
|
--no-scrollbar
|
||||||
|
--no-separator
|
||||||
|
--no-unicode
|
||||||
|
--padding
|
||||||
|
--pointer
|
||||||
|
--preview
|
||||||
--preview-label
|
--preview-label
|
||||||
--preview-label-pos
|
--preview-label-pos
|
||||||
--no-unicode
|
--preview-window
|
||||||
--unicode
|
--print-query
|
||||||
--margin
|
--print0
|
||||||
--padding
|
--prompt
|
||||||
|
--read0
|
||||||
|
--reverse
|
||||||
|
--scheme
|
||||||
|
--scroll-off
|
||||||
|
--separator
|
||||||
|
--sync
|
||||||
--tabstop
|
--tabstop
|
||||||
--listen
|
--tac
|
||||||
--no-listen
|
--tiebreak
|
||||||
--clear
|
--tmux
|
||||||
--no-clear
|
--track
|
||||||
--version
|
--version
|
||||||
|
--with-nth
|
||||||
|
--with-shell
|
||||||
|
--wrap
|
||||||
|
--zsh
|
||||||
|
-0 --exit-0
|
||||||
|
-1 --select-1
|
||||||
|
-d --delimiter
|
||||||
|
-e --exact
|
||||||
|
-f --filter
|
||||||
|
-h --help
|
||||||
|
-i --ignore-case
|
||||||
|
-m --multi
|
||||||
|
-n --nth
|
||||||
|
-q --query
|
||||||
--"
|
--"
|
||||||
|
|
||||||
case "${prev}" in
|
case "${prev}" in
|
||||||
--algo)
|
|
||||||
COMPREPLY=( $(compgen -W "v1 v2" -- "$cur") )
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
--scheme)
|
--scheme)
|
||||||
COMPREPLY=( $(compgen -W "default path history" -- "$cur") )
|
COMPREPLY=( $(compgen -W "default path history" -- "$cur") )
|
||||||
return 0
|
return 0
|
||||||
@@ -261,28 +251,32 @@ _fzf_opts_completion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_fzf_handle_dynamic_completion() {
|
_fzf_handle_dynamic_completion() {
|
||||||
local cmd orig_var orig ret orig_cmd orig_complete
|
local cmd ret REPLY orig_cmd orig_complete
|
||||||
cmd="$1"
|
cmd="$1"
|
||||||
shift
|
shift
|
||||||
orig_cmd="$1"
|
orig_cmd="$1"
|
||||||
orig_var="_fzf_orig_completion_$cmd"
|
if __fzf_orig_completion_get_orig_func "$cmd"; then
|
||||||
orig="${!orig_var-}"
|
"$REPLY" "$@"
|
||||||
orig="${orig##*#}"
|
|
||||||
if [[ -n "$orig" ]] && type "$orig" > /dev/null 2>&1; then
|
|
||||||
$orig "$@"
|
|
||||||
elif [[ -n "${_fzf_completion_loader-}" ]]; then
|
elif [[ -n "${_fzf_completion_loader-}" ]]; then
|
||||||
orig_complete=$(complete -p "$orig_cmd" 2> /dev/null)
|
orig_complete=$(complete -p "$orig_cmd" 2> /dev/null)
|
||||||
_completion_loader "$@"
|
$_fzf_completion_loader "$@"
|
||||||
ret=$?
|
ret=$?
|
||||||
# _completion_loader may not have updated completion for the command
|
# _completion_loader may not have updated completion for the command
|
||||||
if [[ "$(complete -p "$orig_cmd" 2> /dev/null)" != "$orig_complete" ]]; then
|
if [[ "$(complete -p "$orig_cmd" 2> /dev/null)" != "$orig_complete" ]]; then
|
||||||
__fzf_orig_completion < <(complete -p "$orig_cmd" 2> /dev/null)
|
__fzf_orig_completion < <(complete -p "$orig_cmd" 2> /dev/null)
|
||||||
|
|
||||||
|
# Update orig_complete by _fzf_orig_completion entry
|
||||||
|
[[ $orig_complete =~ ' -F '(_fzf_[^ ]+)' ' ]] &&
|
||||||
|
__fzf_orig_completion_instantiate "$cmd" "${BASH_REMATCH[1]}" &&
|
||||||
|
orig_complete=$REPLY
|
||||||
|
|
||||||
if [[ "${__fzf_nospace_commands-}" = *" $orig_cmd "* ]]; then
|
if [[ "${__fzf_nospace_commands-}" = *" $orig_cmd "* ]]; then
|
||||||
eval "${orig_complete/ -F / -o nospace -F }"
|
eval "${orig_complete/ -F / -o nospace -F }"
|
||||||
else
|
else
|
||||||
eval "$orig_complete"
|
eval "$orig_complete"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
[[ $ret -eq 0 ]] && return 124
|
||||||
return $ret
|
return $ret
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -293,7 +287,6 @@ __fzf_generic_path_completion() {
|
|||||||
if [[ $cmd == \\* ]]; then
|
if [[ $cmd == \\* ]]; then
|
||||||
cmd="${cmd:1}"
|
cmd="${cmd:1}"
|
||||||
fi
|
fi
|
||||||
cmd="${cmd//[^A-Za-z0-9_=]/_}"
|
|
||||||
COMPREPLY=()
|
COMPREPLY=()
|
||||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
@@ -309,9 +302,24 @@ __fzf_generic_path_completion() {
|
|||||||
leftover=${leftover/#\/}
|
leftover=${leftover/#\/}
|
||||||
[[ -z "$dir" ]] && dir='.'
|
[[ -z "$dir" ]] && dir='.'
|
||||||
[[ "$dir" != "/" ]] && dir="${dir/%\//}"
|
[[ "$dir" != "/" ]] && dir="${dir/%\//}"
|
||||||
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
|
matches=$(
|
||||||
printf "%q " "${item%$3}$3"
|
export FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --scheme=path" "${FZF_COMPLETION_OPTS-} $2")
|
||||||
done)
|
unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE
|
||||||
|
if declare -F "$1" > /dev/null; then
|
||||||
|
eval "$1 $(printf %q "$dir")" | __fzf_comprun "$4" -q "$leftover"
|
||||||
|
else
|
||||||
|
if [[ $1 =~ dir ]]; then
|
||||||
|
walker=dir,follow
|
||||||
|
rest=${FZF_COMPLETION_DIR_OPTS-}
|
||||||
|
else
|
||||||
|
walker=file,dir,follow,hidden
|
||||||
|
rest=${FZF_COMPLETION_PATH_OPTS-}
|
||||||
|
fi
|
||||||
|
__fzf_comprun "$4" -q "$leftover" --walker "$walker" --walker-root="$dir" $rest
|
||||||
|
fi | while read -r item; do
|
||||||
|
printf "%q " "${item%$3}$3"
|
||||||
|
done
|
||||||
|
)
|
||||||
matches=${matches% }
|
matches=${matches% }
|
||||||
[[ -z "$3" ]] && [[ "${__fzf_nospace_commands-}" = *" ${COMP_WORDS[0]} "* ]] && matches="$matches "
|
[[ -z "$3" ]] && [[ "${__fzf_nospace_commands-}" = *" ${COMP_WORDS[0]} "* ]] && matches="$matches "
|
||||||
if [[ -n "$matches" ]]; then
|
if [[ -n "$matches" ]]; then
|
||||||
@@ -359,13 +367,16 @@ _fzf_complete() {
|
|||||||
post="$(caller 0 | command awk '{print $2}')_post"
|
post="$(caller 0 | command awk '{print $2}')_post"
|
||||||
type -t "$post" > /dev/null 2>&1 || post='command cat'
|
type -t "$post" > /dev/null 2>&1 || post='command cat'
|
||||||
|
|
||||||
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
|
|
||||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||||
|
cmd="${COMP_WORDS[0]}"
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
|
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
|
||||||
cur=${cur:0:${#cur}-${#trigger}}
|
cur=${cur:0:${#cur}-${#trigger}}
|
||||||
|
|
||||||
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | command tr '\n' ' ')
|
selected=$(
|
||||||
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse" "${FZF_COMPLETION_OPTS-} $str_arg") \
|
||||||
|
FZF_DEFAULT_OPTS_FILE='' \
|
||||||
|
__fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | command 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")
|
||||||
@@ -397,7 +408,7 @@ _fzf_complete_kill() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_fzf_proc_completion() {
|
_fzf_proc_completion() {
|
||||||
_fzf_complete -m --header-lines=1 --preview 'echo {}' --preview-window down:3:wrap --min-height 15 -- "$@" < <(
|
_fzf_complete -m --header-lines=1 --no-preview --wrap -- "$@" < <(
|
||||||
command ps -eo user,pid,ppid,start,time,command 2> /dev/null ||
|
command ps -eo user,pid,ppid,start,time,command 2> /dev/null ||
|
||||||
command ps -eo user,pid,ppid,time,args # For BusyBox
|
command ps -eo user,pid,ppid,time,args # For BusyBox
|
||||||
)
|
)
|
||||||
@@ -469,34 +480,43 @@ complete -o default -F _fzf_opts_completion fzf
|
|||||||
# fzf-tmux specific options (like `-w WIDTH`) are left as a future patch.
|
# fzf-tmux specific options (like `-w WIDTH`) are left as a future patch.
|
||||||
complete -o default -F _fzf_opts_completion fzf-tmux
|
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="
|
|
||||||
awk bat cat diff diff3
|
# NOTE: $FZF_COMPLETION_PATH_COMMANDS and $FZF_COMPLETION_VAR_COMMANDS are
|
||||||
|
# undocumented and subject to change in the future.
|
||||||
|
a_cmds="${FZF_COMPLETION_PATH_COMMANDS-"
|
||||||
|
awk bat cat code diff diff3
|
||||||
emacs emacsclient ex file ftp g++ gcc gvim head hg hx 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
|
||||||
find git grep gunzip gzip hg jar
|
find git grep gunzip gzip hg jar
|
||||||
ln ls mv open rm rsync scp
|
ln ls mv open rm rsync scp
|
||||||
svn tar unzip zip"
|
svn tar unzip zip"}"
|
||||||
|
v_cmds="${FZF_COMPLETION_VAR_COMMANDS-export unset printenv}"
|
||||||
|
|
||||||
# Preserve existing completion
|
# Preserve existing completion
|
||||||
__fzf_orig_completion < <(complete -p $d_cmds $a_cmds ssh 2> /dev/null)
|
__fzf_orig_completion < <(complete -p $d_cmds $a_cmds $v_cmds unalias kill ssh 2> /dev/null)
|
||||||
|
|
||||||
if type _completion_loader > /dev/null 2>&1; then
|
if type _comp_load > /dev/null 2>&1; then
|
||||||
_fzf_completion_loader=1
|
# _comp_load was added in bash-completion 2.12 to replace _completion_loader.
|
||||||
|
# We use it without -D option so that it does not use _comp_complete_minimal as the fallback.
|
||||||
|
_fzf_completion_loader=_comp_load
|
||||||
|
elif type __load_completion > /dev/null 2>&1; then
|
||||||
|
# In bash-completion 2.11, _completion_loader internally calls __load_completion
|
||||||
|
# and if it returns a non-zero status, it sets the default 'minimal' completion.
|
||||||
|
_fzf_completion_loader=__load_completion
|
||||||
|
elif type _completion_loader > /dev/null 2>&1; then
|
||||||
|
_fzf_completion_loader=_completion_loader
|
||||||
fi
|
fi
|
||||||
|
|
||||||
__fzf_defc() {
|
__fzf_defc() {
|
||||||
local cmd func opts orig_var orig def
|
local cmd func opts REPLY
|
||||||
cmd="$1"
|
cmd="$1"
|
||||||
func="$2"
|
func="$2"
|
||||||
opts="$3"
|
opts="$3"
|
||||||
orig_var="_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}"
|
if __fzf_orig_completion_instantiate "$cmd" "$func"; then
|
||||||
orig="${!orig_var-}"
|
eval "$REPLY"
|
||||||
if [[ -n "$orig" ]]; then
|
|
||||||
printf -v def "$orig" "$func"
|
|
||||||
eval "$def"
|
|
||||||
else
|
else
|
||||||
complete -F "$func" $opts "$cmd"
|
complete -F "$func" $opts "$cmd"
|
||||||
fi
|
fi
|
||||||
@@ -509,13 +529,24 @@ done
|
|||||||
|
|
||||||
# Directory
|
# Directory
|
||||||
for cmd in $d_cmds; do
|
for cmd in $d_cmds; do
|
||||||
__fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames"
|
__fzf_defc "$cmd" _fzf_dir_completion "-o bashdefault -o nospace -o dirnames"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Variables
|
||||||
|
for cmd in $v_cmds; do
|
||||||
|
__fzf_defc "$cmd" _fzf_var_completion "-o default -o nospace -v"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Aliases
|
||||||
|
__fzf_defc unalias _fzf_alias_completion "-a"
|
||||||
|
|
||||||
|
# Processes
|
||||||
|
__fzf_defc kill _fzf_proc_completion "-o default -o bashdefault"
|
||||||
|
|
||||||
# ssh
|
# ssh
|
||||||
__fzf_defc ssh _fzf_complete_ssh "-o default -o bashdefault"
|
__fzf_defc ssh _fzf_complete_ssh "-o default -o bashdefault"
|
||||||
|
|
||||||
unset cmd d_cmds a_cmds
|
unset cmd d_cmds a_cmds v_cmds
|
||||||
|
|
||||||
_fzf_setup_completion() {
|
_fzf_setup_completion() {
|
||||||
local kind fn cmd
|
local kind fn cmd
|
||||||
@@ -537,8 +568,4 @@ _fzf_setup_completion() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# Environment variables / Aliases / Hosts / Process
|
fi
|
||||||
_fzf_setup_completion 'var' export unset printenv
|
|
||||||
_fzf_setup_completion 'alias' unalias
|
|
||||||
_fzf_setup_completion 'host' telnet
|
|
||||||
_fzf_setup_completion 'proc' kill
|
|
||||||
|
@@ -4,12 +4,12 @@
|
|||||||
# / __/ / /_/ __/
|
# / __/ / /_/ __/
|
||||||
# /_/ /___/_/ completion.zsh
|
# /_/ /___/_/ completion.zsh
|
||||||
#
|
#
|
||||||
# - $FZF_TMUX (default: 0)
|
# - $FZF_TMUX (default: 0)
|
||||||
# - $FZF_TMUX_OPTS (default: '-d 40%')
|
# - $FZF_TMUX_OPTS (default: empty)
|
||||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||||
|
# - $FZF_COMPLETION_PATH_OPTS (default: empty)
|
||||||
[[ -o interactive ]] || return 0
|
# - $FZF_COMPLETION_DIR_OPTS (default: empty)
|
||||||
|
|
||||||
|
|
||||||
# Both branches of the following `if` do the same thing -- define
|
# Both branches of the following `if` do the same thing -- define
|
||||||
@@ -75,27 +75,35 @@ fi
|
|||||||
# This brace is the start of try-always block. The `always` part is like
|
# This brace is the start of try-always block. The `always` part is like
|
||||||
# `finally` in lesser languages. We use it to *always* restore user options.
|
# `finally` in lesser languages. We use it to *always* restore user options.
|
||||||
{
|
{
|
||||||
|
# The 'emulate' command should not be placed inside the interactive if check;
|
||||||
|
# placing it there fails to disable alias expansion. See #3731.
|
||||||
|
if [[ -o interactive ]]; then
|
||||||
|
|
||||||
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
||||||
if ! declare -f _fzf_compgen_path > /dev/null; then
|
#
|
||||||
_fzf_compgen_path() {
|
# _fzf_compgen_path() {
|
||||||
echo "$1"
|
# echo "$1"
|
||||||
command find -L "$1" \
|
# command find -L "$1" \
|
||||||
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
# -name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
||||||
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
# -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||||
}
|
# }
|
||||||
fi
|
#
|
||||||
|
# _fzf_compgen_dir() {
|
||||||
if ! declare -f _fzf_compgen_dir > /dev/null; then
|
# command find -L "$1" \
|
||||||
_fzf_compgen_dir() {
|
# -name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
|
||||||
command find -L "$1" \
|
# -a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
||||||
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
|
# }
|
||||||
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
|
||||||
}
|
|
||||||
fi
|
|
||||||
|
|
||||||
###########################################################
|
###########################################################
|
||||||
|
|
||||||
|
__fzf_defaults() {
|
||||||
|
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
echo "--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore $1"
|
||||||
|
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
||||||
|
echo "${FZF_DEFAULT_OPTS-} $2"
|
||||||
|
}
|
||||||
|
|
||||||
__fzf_comprun() {
|
__fzf_comprun() {
|
||||||
if [[ "$(type _fzf_comprun 2>&1)" =~ function ]]; then
|
if [[ "$(type _fzf_comprun 2>&1)" =~ function ]]; then
|
||||||
_fzf_comprun "$@"
|
_fzf_comprun "$@"
|
||||||
@@ -148,10 +156,26 @@ __fzf_generic_path_completion() {
|
|||||||
leftover=${leftover/#\/}
|
leftover=${leftover/#\/}
|
||||||
[ -z "$dir" ] && dir='.'
|
[ -z "$dir" ] && dir='.'
|
||||||
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
||||||
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-}" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
|
matches=$(
|
||||||
item="${item%$suffix}$suffix"
|
export FZF_DEFAULT_OPTS
|
||||||
echo -n "${(q)item} "
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --scheme=path" "${FZF_COMPLETION_OPTS-}")
|
||||||
done)
|
unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE
|
||||||
|
if declare -f "$compgen" > /dev/null; then
|
||||||
|
eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover"
|
||||||
|
else
|
||||||
|
if [[ $compgen =~ dir ]]; then
|
||||||
|
walker=dir,follow
|
||||||
|
rest=${FZF_COMPLETION_DIR_OPTS-}
|
||||||
|
else
|
||||||
|
walker=file,dir,follow,hidden
|
||||||
|
rest=${FZF_COMPLETION_PATH_OPTS-}
|
||||||
|
fi
|
||||||
|
__fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" --walker "$walker" --walker-root="$dir" ${(Q)${(Z+n+)rest}} < /dev/tty
|
||||||
|
fi | while read -r item; do
|
||||||
|
item="${item%$suffix}$suffix"
|
||||||
|
echo -n -E "${(q)item} "
|
||||||
|
done
|
||||||
|
)
|
||||||
matches=${matches% }
|
matches=${matches% }
|
||||||
if [ -n "$matches" ]; then
|
if [ -n "$matches" ]; then
|
||||||
LBUFFER="$lbuf$matches$tail"
|
LBUFFER="$lbuf$matches$tail"
|
||||||
@@ -174,11 +198,11 @@ _fzf_dir_completion() {
|
|||||||
"" "/" ""
|
"" "/" ""
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_feed_fifo() (
|
_fzf_feed_fifo() {
|
||||||
command rm -f "$1"
|
command rm -f "$1"
|
||||||
mkfifo "$1"
|
mkfifo "$1"
|
||||||
cat <&0 > "$1" &
|
cat <&0 > "$1" &|
|
||||||
)
|
}
|
||||||
|
|
||||||
_fzf_complete() {
|
_fzf_complete() {
|
||||||
setopt localoptions ksh_arrays
|
setopt localoptions ksh_arrays
|
||||||
@@ -211,7 +235,10 @@ _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=$(__fzf_defaults "--reverse" "${FZF_COMPLETION_OPTS-} $str_arg") \
|
||||||
|
FZF_DEFAULT_OPTS_FILE='' \
|
||||||
|
__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
|
||||||
@@ -238,13 +265,14 @@ _fzf_complete_telnet() {
|
|||||||
# The first and the only argument is the LBUFFER without the current word that contains the trigger.
|
# The first and the only argument is the LBUFFER without the current word that contains the trigger.
|
||||||
# The current word without the trigger is in the $prefix variable passed from the caller.
|
# The current word without the trigger is in the $prefix variable passed from the caller.
|
||||||
_fzf_complete_ssh() {
|
_fzf_complete_ssh() {
|
||||||
local tokens=(${(z)1})
|
local -a tokens
|
||||||
|
tokens=(${(z)1})
|
||||||
case ${tokens[-1]} in
|
case ${tokens[-1]} in
|
||||||
-i|-F|-E)
|
-i|-F|-E)
|
||||||
_fzf_path_completion "$prefix" "$1"
|
_fzf_path_completion "$prefix" "$1"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
local user=
|
local user
|
||||||
[[ $prefix =~ @ ]] && user="${prefix%%@*}@"
|
[[ $prefix =~ @ ]] && user="${prefix%%@*}@"
|
||||||
_fzf_complete +m -- "$@" < <(__fzf_list_hosts | awk -v user="$user" '{print user $0}')
|
_fzf_complete +m -- "$@" < <(__fzf_list_hosts | awk -v user="$user" '{print user $0}')
|
||||||
;;
|
;;
|
||||||
@@ -270,7 +298,7 @@ _fzf_complete_unalias() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_kill() {
|
_fzf_complete_kill() {
|
||||||
_fzf_complete -m --header-lines=1 --preview 'echo {}' --preview-window down:3:wrap --min-height 15 -- "$@" < <(
|
_fzf_complete -m --header-lines=1 --no-preview --wrap -- "$@" < <(
|
||||||
command ps -eo user,pid,ppid,start,time,command 2> /dev/null ||
|
command ps -eo user,pid,ppid,start,time,command 2> /dev/null ||
|
||||||
command ps -eo user,pid,ppid,time,args # For BusyBox
|
command ps -eo user,pid,ppid,time,args # For BusyBox
|
||||||
)
|
)
|
||||||
@@ -309,7 +337,7 @@ fzf-completion() {
|
|||||||
|
|
||||||
# Trigger sequence given
|
# Trigger sequence given
|
||||||
if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
|
if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
|
||||||
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})
|
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS-cd pushd rmdir})
|
||||||
|
|
||||||
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
|
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
|
||||||
if [[ $prefix = *'$('* ]] || [[ $prefix = *'<('* ]] || [[ $prefix = *'>('* ]] || [[ $prefix = *':='* ]] || [[ $prefix = *'`'* ]]; then
|
if [[ $prefix = *'$('* ]] || [[ $prefix = *'<('* ]] || [[ $prefix = *'>('* ]] || [[ $prefix = *':='* ]] || [[ $prefix = *'`'* ]]; then
|
||||||
@@ -339,6 +367,7 @@ fzf-completion() {
|
|||||||
|
|
||||||
zle -N fzf-completion
|
zle -N fzf-completion
|
||||||
bindkey '^I' fzf-completion
|
bindkey '^I' fzf-completion
|
||||||
|
fi
|
||||||
|
|
||||||
} always {
|
} always {
|
||||||
# Restore the original options.
|
# Restore the original options.
|
||||||
|
@@ -11,20 +11,24 @@
|
|||||||
# - $FZF_ALT_C_COMMAND
|
# - $FZF_ALT_C_COMMAND
|
||||||
# - $FZF_ALT_C_OPTS
|
# - $FZF_ALT_C_OPTS
|
||||||
|
|
||||||
[[ $- =~ i ]] || return 0
|
if [[ $- =~ i ]]; then
|
||||||
|
|
||||||
|
|
||||||
# Key bindings
|
# Key bindings
|
||||||
# ------------
|
# ------------
|
||||||
|
|
||||||
|
__fzf_defaults() {
|
||||||
|
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
echo "--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore $1"
|
||||||
|
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
||||||
|
echo "${FZF_DEFAULT_OPTS-} $2"
|
||||||
|
}
|
||||||
|
|
||||||
__fzf_select__() {
|
__fzf_select__() {
|
||||||
local cmd opts
|
FZF_DEFAULT_COMMAND=${FZF_CTRL_T_COMMAND:-} \
|
||||||
cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --walker=file,dir,follow,hidden --scheme=path" "${FZF_CTRL_T_OPTS-} -m") \
|
||||||
-o -type f -print \
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) "$@" |
|
||||||
-o -type d -print \
|
|
||||||
-o -type l -print 2> /dev/null | command cut -b3-"}"
|
|
||||||
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse --scheme=path ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-} -m"
|
|
||||||
eval "$cmd" |
|
|
||||||
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) "$@" |
|
|
||||||
while read -r item; do
|
while read -r item; do
|
||||||
printf '%q ' "$item" # escape special chars
|
printf '%q ' "$item" # escape special chars
|
||||||
done
|
done
|
||||||
@@ -42,25 +46,26 @@ fzf-file-widget() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
__fzf_cd__() {
|
__fzf_cd__() {
|
||||||
local cmd opts dir
|
local 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 \
|
dir=$(
|
||||||
-o -type d -print 2> /dev/null | command cut -b3-"}"
|
FZF_DEFAULT_COMMAND=${FZF_ALT_C_COMMAND:-} \
|
||||||
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse --scheme=path ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-} +m"
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --walker=dir,follow,hidden --scheme=path" "${FZF_ALT_C_OPTS-} +m") \
|
||||||
dir=$(set +o pipefail; eval "$cmd" | FZF_DEFAULT_OPTS="$opts" $(__fzfcmd)) && printf 'builtin cd -- %q' "$dir"
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd)
|
||||||
|
) && printf 'builtin cd -- %q' "$(builtin unset CDPATH && builtin cd -- "$dir" && builtin pwd)"
|
||||||
}
|
}
|
||||||
|
|
||||||
if command -v perl > /dev/null; then
|
if command -v perl > /dev/null; then
|
||||||
__fzf_history__() {
|
__fzf_history__() {
|
||||||
local output opts script
|
local output 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/^[ *]//; s/\n/\n\t/gm; print $HISTCOUNT - $. . "\t$_" if !$seen{$_}++'
|
||||||
script='BEGIN { getc; $/ = "\n\t"; $HISTCOUNT = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCOUNT - $. . "\t$_" if !$seen{$_}++'
|
|
||||||
output=$(
|
output=$(
|
||||||
set +o pipefail
|
set +o pipefail
|
||||||
builtin fc -lnr -2147483648 |
|
builtin fc -lnr -2147483648 |
|
||||||
last_hist=$(HISTTIMEFORMAT='' builtin history 1) command perl -n -l0 -e "$script" |
|
last_hist=$(HISTTIMEFORMAT='' builtin history 1) command perl -n -l0 -e "$script" |
|
||||||
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) --query "$READLINE_LINE"
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '"$'\t'"↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} +m --read0") \
|
||||||
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) --query "$READLINE_LINE"
|
||||||
) || return
|
) || return
|
||||||
READLINE_LINE=${output#*$'\t'}
|
READLINE_LINE=$(command perl -pe 's/^\d*\t//' <<< "$output")
|
||||||
if [[ -z "$READLINE_POINT" ]]; then
|
if [[ -z "$READLINE_POINT" ]]; then
|
||||||
echo "$READLINE_LINE"
|
echo "$READLINE_LINE"
|
||||||
else
|
else
|
||||||
@@ -69,14 +74,13 @@ if command -v perl > /dev/null; then
|
|||||||
}
|
}
|
||||||
else # awk - fallback for POSIX systems
|
else # awk - fallback for POSIX systems
|
||||||
__fzf_history__() {
|
__fzf_history__() {
|
||||||
local output opts script n x y z d
|
local output script n x y z d
|
||||||
if [[ -z $__fzf_awk ]]; then
|
if [[ -z $__fzf_awk ]]; then
|
||||||
__fzf_awk=awk
|
__fzf_awk=awk
|
||||||
# choose the faster mawk if: it's installed && build date >= 20230322 && version >= 1.3.4
|
# choose the faster mawk if: it's installed && build date >= 20230322 && version >= 1.3.4
|
||||||
IFS=' .' read n x y z d <<< $(command mawk -W version 2> /dev/null)
|
IFS=' .' read n x y z d <<< $(command mawk -W version 2> /dev/null)
|
||||||
[[ $n == mawk ]] && (( d >= 20230302 && (x *1000 +y) *1000 +z >= 1003004 )) && __fzf_awk=mawk
|
[[ $n == mawk ]] && (( d >= 20230302 && (x *1000 +y) *1000 +z >= 1003004 )) && __fzf_awk=mawk
|
||||||
fi
|
fi
|
||||||
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort ${FZF_CTRL_R_OPTS-} +m --read0"
|
|
||||||
[[ $(HISTTIMEFORMAT='' builtin history 1) =~ [[:digit:]]+ ]] # how many history entries
|
[[ $(HISTTIMEFORMAT='' builtin history 1) =~ [[:digit:]]+ ]] # how many history entries
|
||||||
script='function P(b) { ++n; sub(/^[ *]/, "", b); if (!seen[b]++) { printf "%d\t%s%c", '$((BASH_REMATCH + 1))' - n, b, 0 } }
|
script='function P(b) { ++n; sub(/^[ *]/, "", b); if (!seen[b]++) { printf "%d\t%s%c", '$((BASH_REMATCH + 1))' - n, b, 0 } }
|
||||||
NR==1 { b = substr($0, 2); next }
|
NR==1 { b = substr($0, 2); next }
|
||||||
@@ -87,7 +91,8 @@ else # awk - fallback for POSIX systems
|
|||||||
set +o pipefail
|
set +o pipefail
|
||||||
builtin fc -lnr -2147483648 2> /dev/null | # ( $'\t '<lines>$'\n' )* ; <lines> ::= [^\n]* ( $'\n'<lines> )*
|
builtin fc -lnr -2147483648 2> /dev/null | # ( $'\t '<lines>$'\n' )* ; <lines> ::= [^\n]* ( $'\n'<lines> )*
|
||||||
command $__fzf_awk "$script" | # ( <counter>$'\t'<lines>$'\000' )*
|
command $__fzf_awk "$script" | # ( <counter>$'\t'<lines>$'\000' )*
|
||||||
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) --query "$READLINE_LINE"
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '"$'\t'"↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} +m --read0") \
|
||||||
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) --query "$READLINE_LINE"
|
||||||
) || return
|
) || return
|
||||||
READLINE_LINE=${output#*$'\t'}
|
READLINE_LINE=${output#*$'\t'}
|
||||||
if [[ -z "$READLINE_POINT" ]]; then
|
if [[ -z "$READLINE_POINT" ]]; then
|
||||||
@@ -107,9 +112,11 @@ bind -m emacs-standard '"\C-z": vi-editing-mode'
|
|||||||
|
|
||||||
if (( BASH_VERSINFO[0] < 4 )); then
|
if (( BASH_VERSINFO[0] < 4 )); then
|
||||||
# CTRL-T - Paste the selected file path into the command line
|
# CTRL-T - Paste the selected file path into the command line
|
||||||
bind -m emacs-standard '"\C-t": " \C-b\C-k \C-u`__fzf_select__`\e\C-e\er\C-a\C-y\C-h\C-e\e \C-y\ey\C-x\C-x\C-f"'
|
if [[ "${FZF_CTRL_T_COMMAND-x}" != "" ]]; then
|
||||||
bind -m vi-command '"\C-t": "\C-z\C-t\C-z"'
|
bind -m emacs-standard '"\C-t": " \C-b\C-k \C-u`__fzf_select__`\e\C-e\er\C-a\C-y\C-h\C-e\e \C-y\ey\C-x\C-x\C-f"'
|
||||||
bind -m vi-insert '"\C-t": "\C-z\C-t\C-z"'
|
bind -m vi-command '"\C-t": "\C-z\C-t\C-z"'
|
||||||
|
bind -m vi-insert '"\C-t": "\C-z\C-t\C-z"'
|
||||||
|
fi
|
||||||
|
|
||||||
# CTRL-R - Paste the selected command from history into the command line
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
bind -m emacs-standard '"\C-r": "\C-e \C-u\C-y\ey\C-u`__fzf_history__`\e\C-e\er"'
|
bind -m emacs-standard '"\C-r": "\C-e \C-u\C-y\ey\C-u`__fzf_history__`\e\C-e\er"'
|
||||||
@@ -117,9 +124,11 @@ if (( BASH_VERSINFO[0] < 4 )); then
|
|||||||
bind -m vi-insert '"\C-r": "\C-z\C-r\C-z"'
|
bind -m vi-insert '"\C-r": "\C-z\C-r\C-z"'
|
||||||
else
|
else
|
||||||
# CTRL-T - Paste the selected file path into the command line
|
# CTRL-T - Paste the selected file path into the command line
|
||||||
bind -m emacs-standard -x '"\C-t": fzf-file-widget'
|
if [[ "${FZF_CTRL_T_COMMAND-x}" != "" ]]; then
|
||||||
bind -m vi-command -x '"\C-t": fzf-file-widget'
|
bind -m emacs-standard -x '"\C-t": fzf-file-widget'
|
||||||
bind -m vi-insert -x '"\C-t": fzf-file-widget'
|
bind -m vi-command -x '"\C-t": fzf-file-widget'
|
||||||
|
bind -m vi-insert -x '"\C-t": fzf-file-widget'
|
||||||
|
fi
|
||||||
|
|
||||||
# CTRL-R - Paste the selected command from history into the command line
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
bind -m emacs-standard -x '"\C-r": __fzf_history__'
|
bind -m emacs-standard -x '"\C-r": __fzf_history__'
|
||||||
@@ -128,6 +137,10 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# ALT-C - cd into the selected directory
|
# ALT-C - cd into the selected directory
|
||||||
bind -m emacs-standard '"\ec": " \C-b\C-k \C-u`__fzf_cd__`\e\C-e\er\C-m\C-y\C-h\e \C-y\ey\C-x\C-x\C-d"'
|
if [[ "${FZF_ALT_C_COMMAND-x}" != "" ]]; then
|
||||||
bind -m vi-command '"\ec": "\C-z\ec\C-z"'
|
bind -m emacs-standard '"\ec": " \C-b\C-k \C-u`__fzf_cd__`\e\C-e\er\C-m\C-y\C-h\e \C-y\ey\C-x\C-x\C-d"'
|
||||||
bind -m vi-insert '"\ec": "\C-z\ec\C-z"'
|
bind -m vi-command '"\ec": "\C-z\ec\C-z"'
|
||||||
|
bind -m vi-insert '"\ec": "\C-z\ec\C-z"'
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
@@ -18,25 +18,28 @@ status is-interactive; or exit 0
|
|||||||
# ------------
|
# ------------
|
||||||
function fzf_key_bindings
|
function fzf_key_bindings
|
||||||
|
|
||||||
|
function __fzf_defaults
|
||||||
|
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||||
|
echo "--height $FZF_TMUX_HEIGHT --bind=ctrl-z:ignore" $argv[1]
|
||||||
|
command cat "$FZF_DEFAULT_OPTS_FILE" 2> /dev/null
|
||||||
|
echo $FZF_DEFAULT_OPTS $argv[2]
|
||||||
|
end
|
||||||
|
|
||||||
# Store current token in $dir as root for the 'find' command
|
# Store current token in $dir as root for the 'find' command
|
||||||
function fzf-file-widget -d "List files and folders"
|
function fzf-file-widget -d "List files and folders"
|
||||||
set -l commandline (__fzf_parse_commandline)
|
set -l commandline (__fzf_parse_commandline)
|
||||||
set -l dir $commandline[1]
|
set -lx dir $commandline[1]
|
||||||
set -l fzf_query $commandline[2]
|
set -l fzf_query $commandline[2]
|
||||||
set -l prefix $commandline[3]
|
set -l prefix $commandline[3]
|
||||||
|
|
||||||
# "-path \$dir'*/.*'" matches hidden files/folders inside $dir but not
|
|
||||||
# $dir itself, even if hidden.
|
|
||||||
test -n "$FZF_CTRL_T_COMMAND"; or set -l FZF_CTRL_T_COMMAND "
|
|
||||||
command find -L \$dir -mindepth 1 \\( -path \$dir'*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
|
||||||
-o -type f -print \
|
|
||||||
-o -type d -print \
|
|
||||||
-o -type l -print 2> /dev/null | sed 's@^\./@@'"
|
|
||||||
|
|
||||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||||
begin
|
begin
|
||||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
|
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "--reverse --walker=file,dir,follow,hidden --scheme=path --walker-root='$dir'" "$FZF_CTRL_T_OPTS")
|
||||||
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
|
set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND"
|
||||||
|
set -lx FZF_DEFAULT_OPTS_FILE ''
|
||||||
|
eval (__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
|
||||||
end
|
end
|
||||||
if [ -z "$result" ]
|
if [ -z "$result" ]
|
||||||
commandline -f repaint
|
commandline -f repaint
|
||||||
@@ -56,19 +59,31 @@ 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 --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.)
|
||||||
|
|
||||||
|
# merge history from other sessions before searching
|
||||||
|
if test -z "$fish_private_mode"
|
||||||
|
builtin history merge
|
||||||
|
end
|
||||||
|
|
||||||
# history's -z flag is needed for multi-line support.
|
# history's -z flag is needed for multi-line support.
|
||||||
# history's -z flag was added in fish 2.4.0, so don't use it for versions
|
# history's -z flag was added in fish 2.4.0, so don't use it for versions
|
||||||
# before 2.4.0.
|
# before 2.4.0.
|
||||||
if [ "$FISH_MAJOR" -gt 2 -o \( "$FISH_MAJOR" -eq 2 -a "$FISH_MINOR" -ge 4 \) ];
|
if [ "$FISH_MAJOR" -gt 2 -o \( "$FISH_MAJOR" -eq 2 -a "$FISH_MINOR" -ge 4 \) ];
|
||||||
history -z | eval (__fzfcmd) --read0 --print0 -q '(commandline)' | read -lz result
|
if type -P perl > /dev/null 2>&1
|
||||||
and commandline -- $result
|
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '"\t"↳ ' --highlight-line $FZF_CTRL_R_OPTS +m")
|
||||||
|
set -lx FZF_DEFAULT_OPTS_FILE ''
|
||||||
|
builtin history -z --reverse | command perl -0 -pe 's/^/$.\t/g; s/\n/\n\t/gm' | eval (__fzfcmd) --tac --read0 --print0 -q '(commandline)' | command perl -pe 's/^\d*\t//' | read -lz result
|
||||||
|
and commandline -- $result
|
||||||
|
else
|
||||||
|
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "" "--scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '"\t"↳ ' --highlight-line $FZF_CTRL_R_OPTS +m")
|
||||||
|
set -lx FZF_DEFAULT_OPTS_FILE ''
|
||||||
|
builtin history -z | eval (__fzfcmd) --read0 --print0 -q '(commandline)' | read -lz result
|
||||||
|
and commandline -- $result
|
||||||
|
end
|
||||||
else
|
else
|
||||||
history | eval (__fzfcmd) -q '(commandline)' | read -l result
|
builtin history | eval (__fzfcmd) -q '(commandline)' | read -l result
|
||||||
and commandline -- $result
|
and commandline -- $result
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -77,20 +92,19 @@ function fzf_key_bindings
|
|||||||
|
|
||||||
function fzf-cd-widget -d "Change directory"
|
function fzf-cd-widget -d "Change directory"
|
||||||
set -l commandline (__fzf_parse_commandline)
|
set -l commandline (__fzf_parse_commandline)
|
||||||
set -l dir $commandline[1]
|
set -lx dir $commandline[1]
|
||||||
set -l fzf_query $commandline[2]
|
set -l fzf_query $commandline[2]
|
||||||
set -l prefix $commandline[3]
|
set -l prefix $commandline[3]
|
||||||
|
|
||||||
test -n "$FZF_ALT_C_COMMAND"; or set -l FZF_ALT_C_COMMAND "
|
|
||||||
command find -L \$dir -mindepth 1 \\( -path \$dir'*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
|
||||||
-o -type d -print 2> /dev/null | sed 's@^\./@@'"
|
|
||||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||||
begin
|
begin
|
||||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
|
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "--reverse --walker=dir,follow,hidden --scheme=path --walker-root='$dir'" "$FZF_ALT_C_OPTS")
|
||||||
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
set -lx FZF_DEFAULT_OPTS_FILE ''
|
||||||
|
set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND"
|
||||||
|
eval (__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
||||||
|
|
||||||
if [ -n "$result" ]
|
if [ -n "$result" ]
|
||||||
cd -- $result
|
builtin cd -- $result
|
||||||
|
|
||||||
# Remove last token from commandline.
|
# Remove last token from commandline.
|
||||||
commandline -t ""
|
commandline -t ""
|
||||||
@@ -113,14 +127,22 @@ function fzf_key_bindings
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
bind \ct fzf-file-widget
|
|
||||||
bind \cr fzf-history-widget
|
bind \cr fzf-history-widget
|
||||||
bind \ec fzf-cd-widget
|
if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND"
|
||||||
|
bind \ct fzf-file-widget
|
||||||
|
end
|
||||||
|
if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND"
|
||||||
|
bind \ec fzf-cd-widget
|
||||||
|
end
|
||||||
|
|
||||||
if bind -M insert > /dev/null 2>&1
|
if bind -M insert > /dev/null 2>&1
|
||||||
bind -M insert \ct fzf-file-widget
|
|
||||||
bind -M insert \cr fzf-history-widget
|
bind -M insert \cr fzf-history-widget
|
||||||
bind -M insert \ec fzf-cd-widget
|
if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND"
|
||||||
|
bind -M insert \ct fzf-file-widget
|
||||||
|
end
|
||||||
|
if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND"
|
||||||
|
bind -M insert \ec fzf-cd-widget
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix'
|
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix'
|
||||||
|
@@ -11,8 +11,6 @@
|
|||||||
# - $FZF_ALT_C_COMMAND
|
# - $FZF_ALT_C_COMMAND
|
||||||
# - $FZF_ALT_C_OPTS
|
# - $FZF_ALT_C_OPTS
|
||||||
|
|
||||||
[[ -o interactive ]] || return 0
|
|
||||||
|
|
||||||
|
|
||||||
# Key bindings
|
# Key bindings
|
||||||
# ------------
|
# ------------
|
||||||
@@ -38,17 +36,24 @@ fi
|
|||||||
'builtin' 'emulate' 'zsh' && 'builtin' 'setopt' 'no_aliases'
|
'builtin' 'emulate' 'zsh' && 'builtin' 'setopt' 'no_aliases'
|
||||||
|
|
||||||
{
|
{
|
||||||
|
if [[ -o interactive ]]; then
|
||||||
|
|
||||||
|
__fzf_defaults() {
|
||||||
|
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
echo "--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore $1"
|
||||||
|
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
||||||
|
echo "${FZF_DEFAULT_OPTS-} $2"
|
||||||
|
}
|
||||||
|
|
||||||
# CTRL-T - Paste the selected file path(s) into the command line
|
# CTRL-T - Paste the selected file path(s) into the command line
|
||||||
__fsel() {
|
__fzf_select() {
|
||||||
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
|
||||||
-o -type f -print \
|
|
||||||
-o -type d -print \
|
|
||||||
-o -type l -print 2> /dev/null | cut -b3-"}"
|
|
||||||
setopt localoptions pipefail no_aliases 2> /dev/null
|
setopt localoptions pipefail no_aliases 2> /dev/null
|
||||||
local item
|
local item
|
||||||
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-}" $(__fzfcmd) -m "$@" | while read item; do
|
FZF_DEFAULT_COMMAND=${FZF_CTRL_T_COMMAND:-} \
|
||||||
echo -n "${(q)item} "
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --walker=file,dir,follow,hidden --scheme=path" "${FZF_CTRL_T_OPTS-} -m") \
|
||||||
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) "$@" < /dev/tty | while read -r item; do
|
||||||
|
echo -n -E "${(q)item} "
|
||||||
done
|
done
|
||||||
local ret=$?
|
local ret=$?
|
||||||
echo
|
echo
|
||||||
@@ -61,50 +66,64 @@ __fzfcmd() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fzf-file-widget() {
|
fzf-file-widget() {
|
||||||
LBUFFER="${LBUFFER}$(__fsel)"
|
LBUFFER="${LBUFFER}$(__fzf_select)"
|
||||||
local ret=$?
|
local ret=$?
|
||||||
zle reset-prompt
|
zle reset-prompt
|
||||||
return $ret
|
return $ret
|
||||||
}
|
}
|
||||||
zle -N fzf-file-widget
|
if [[ "${FZF_CTRL_T_COMMAND-x}" != "" ]]; then
|
||||||
bindkey -M emacs '^T' fzf-file-widget
|
zle -N fzf-file-widget
|
||||||
bindkey -M vicmd '^T' fzf-file-widget
|
bindkey -M emacs '^T' fzf-file-widget
|
||||||
bindkey -M viins '^T' fzf-file-widget
|
bindkey -M vicmd '^T' fzf-file-widget
|
||||||
|
bindkey -M viins '^T' fzf-file-widget
|
||||||
|
fi
|
||||||
|
|
||||||
# ALT-C - cd into the selected directory
|
# ALT-C - cd into the selected directory
|
||||||
fzf-cd-widget() {
|
fzf-cd-widget() {
|
||||||
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
|
||||||
-o -type d -print 2> /dev/null | cut -b3-"}"
|
|
||||||
setopt localoptions pipefail no_aliases 2> /dev/null
|
setopt localoptions pipefail no_aliases 2> /dev/null
|
||||||
local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-}" $(__fzfcmd) +m)"
|
local dir="$(
|
||||||
|
FZF_DEFAULT_COMMAND=${FZF_ALT_C_COMMAND:-} \
|
||||||
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --walker=dir,follow,hidden --scheme=path" "${FZF_ALT_C_OPTS-} +m") \
|
||||||
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) < /dev/tty)"
|
||||||
if [[ -z "$dir" ]]; then
|
if [[ -z "$dir" ]]; then
|
||||||
zle redisplay
|
zle redisplay
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
zle push-line # Clear buffer. Auto-restored on next prompt.
|
zle push-line # Clear buffer. Auto-restored on next prompt.
|
||||||
BUFFER="builtin cd -- ${(q)dir}"
|
BUFFER="builtin cd -- ${(q)dir:a}"
|
||||||
zle accept-line
|
zle accept-line
|
||||||
local ret=$?
|
local ret=$?
|
||||||
unset dir # ensure this doesn't end up appearing in prompt expansion
|
unset dir # ensure this doesn't end up appearing in prompt expansion
|
||||||
zle reset-prompt
|
zle reset-prompt
|
||||||
return $ret
|
return $ret
|
||||||
}
|
}
|
||||||
zle -N fzf-cd-widget
|
if [[ "${FZF_ALT_C_COMMAND-x}" != "" ]]; then
|
||||||
bindkey -M emacs '\ec' fzf-cd-widget
|
zle -N fzf-cd-widget
|
||||||
bindkey -M vicmd '\ec' fzf-cd-widget
|
bindkey -M emacs '\ec' fzf-cd-widget
|
||||||
bindkey -M viins '\ec' fzf-cd-widget
|
bindkey -M vicmd '\ec' fzf-cd-widget
|
||||||
|
bindkey -M viins '\ec' fzf-cd-widget
|
||||||
|
fi
|
||||||
|
|
||||||
# CTRL-R - Paste the selected command from history into the command line
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
fzf-history-widget() {
|
fzf-history-widget() {
|
||||||
local selected num
|
local selected
|
||||||
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
|
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases noglob nobash_rematch 2> /dev/null
|
||||||
selected="$(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
|
# Ensure the associative history array, which maps event numbers to the full
|
||||||
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))"
|
# history lines, is loaded, and that Perl is installed for multi-line output.
|
||||||
|
if zmodload -F zsh/parameter p:history 2>/dev/null && (( ${#commands[perl]} )); then
|
||||||
|
selected="$(printf '%s\t%s\000' "${(kv)history[@]}" |
|
||||||
|
perl -0 -ne 'if (!$seen{(/^\s*[0-9]+\**\t(.*)/s, $1)}++) { s/\n/\n\t/g; print; }' |
|
||||||
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '\t↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m --read0") \
|
||||||
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
|
||||||
|
else
|
||||||
|
selected="$(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
|
||||||
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '\t↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m") \
|
||||||
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
|
||||||
|
fi
|
||||||
local ret=$?
|
local ret=$?
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
num=$(awk '{print $1}' <<< "$selected")
|
if [[ $(awk '{print $1; exit}' <<< "$selected") =~ ^[1-9][0-9]* ]]; then
|
||||||
if [[ "$num" =~ '^[1-9][0-9]*\*?$' ]]; then
|
zle vi-fetch-history -n $MATCH
|
||||||
zle vi-fetch-history -n ${num%\*}
|
|
||||||
else # selected is a custom query, not from history
|
else # selected is a custom query, not from history
|
||||||
LBUFFER="$selected"
|
LBUFFER="$selected"
|
||||||
fi
|
fi
|
||||||
@@ -116,6 +135,7 @@ zle -N fzf-history-widget
|
|||||||
bindkey -M emacs '^R' fzf-history-widget
|
bindkey -M emacs '^R' fzf-history-widget
|
||||||
bindkey -M vicmd '^R' fzf-history-widget
|
bindkey -M vicmd '^R' fzf-history-widget
|
||||||
bindkey -M viins '^R' fzf-history-widget
|
bindkey -M viins '^R' fzf-history-widget
|
||||||
|
fi
|
||||||
|
|
||||||
} always {
|
} always {
|
||||||
eval $__fzf_key_bindings_options
|
eval $__fzf_key_bindings_options
|
||||||
|
@@ -26,100 +26,106 @@ func _() {
|
|||||||
_ = x[actCancel-15]
|
_ = x[actCancel-15]
|
||||||
_ = x[actChangeBorderLabel-16]
|
_ = x[actChangeBorderLabel-16]
|
||||||
_ = x[actChangeHeader-17]
|
_ = x[actChangeHeader-17]
|
||||||
_ = x[actChangePreviewLabel-18]
|
_ = x[actChangeMulti-18]
|
||||||
_ = x[actChangePrompt-19]
|
_ = x[actChangePreviewLabel-19]
|
||||||
_ = x[actChangeQuery-20]
|
_ = x[actChangePrompt-20]
|
||||||
_ = x[actClearScreen-21]
|
_ = x[actChangeQuery-21]
|
||||||
_ = x[actClearQuery-22]
|
_ = x[actClearScreen-22]
|
||||||
_ = x[actClearSelection-23]
|
_ = x[actClearQuery-23]
|
||||||
_ = x[actClose-24]
|
_ = x[actClearSelection-24]
|
||||||
_ = x[actDeleteChar-25]
|
_ = x[actClose-25]
|
||||||
_ = x[actDeleteCharEof-26]
|
_ = x[actDeleteChar-26]
|
||||||
_ = x[actEndOfLine-27]
|
_ = x[actDeleteCharEof-27]
|
||||||
_ = x[actForwardChar-28]
|
_ = x[actEndOfLine-28]
|
||||||
_ = x[actForwardWord-29]
|
_ = x[actFatal-29]
|
||||||
_ = x[actKillLine-30]
|
_ = x[actForwardChar-30]
|
||||||
_ = x[actKillWord-31]
|
_ = x[actForwardWord-31]
|
||||||
_ = x[actUnixLineDiscard-32]
|
_ = x[actKillLine-32]
|
||||||
_ = x[actUnixWordRubout-33]
|
_ = x[actKillWord-33]
|
||||||
_ = x[actYank-34]
|
_ = x[actUnixLineDiscard-34]
|
||||||
_ = x[actBackwardKillWord-35]
|
_ = x[actUnixWordRubout-35]
|
||||||
_ = x[actSelectAll-36]
|
_ = x[actYank-36]
|
||||||
_ = x[actDeselectAll-37]
|
_ = x[actBackwardKillWord-37]
|
||||||
_ = x[actToggle-38]
|
_ = x[actSelectAll-38]
|
||||||
_ = x[actToggleSearch-39]
|
_ = x[actDeselectAll-39]
|
||||||
_ = x[actToggleAll-40]
|
_ = x[actToggle-40]
|
||||||
_ = x[actToggleDown-41]
|
_ = x[actToggleSearch-41]
|
||||||
_ = x[actToggleUp-42]
|
_ = x[actToggleAll-42]
|
||||||
_ = x[actToggleIn-43]
|
_ = x[actToggleDown-43]
|
||||||
_ = x[actToggleOut-44]
|
_ = x[actToggleUp-44]
|
||||||
_ = x[actToggleTrack-45]
|
_ = x[actToggleIn-45]
|
||||||
_ = x[actToggleHeader-46]
|
_ = x[actToggleOut-46]
|
||||||
_ = x[actTrack-47]
|
_ = x[actToggleTrack-47]
|
||||||
_ = x[actDown-48]
|
_ = x[actToggleTrackCurrent-48]
|
||||||
_ = x[actUp-49]
|
_ = x[actToggleHeader-49]
|
||||||
_ = x[actPageUp-50]
|
_ = x[actToggleWrap-50]
|
||||||
_ = x[actPageDown-51]
|
_ = x[actTrackCurrent-51]
|
||||||
_ = x[actPosition-52]
|
_ = x[actUntrackCurrent-52]
|
||||||
_ = x[actHalfPageUp-53]
|
_ = x[actDown-53]
|
||||||
_ = x[actHalfPageDown-54]
|
_ = x[actUp-54]
|
||||||
_ = x[actOffsetUp-55]
|
_ = x[actPageUp-55]
|
||||||
_ = x[actOffsetDown-56]
|
_ = x[actPageDown-56]
|
||||||
_ = x[actJump-57]
|
_ = x[actPosition-57]
|
||||||
_ = x[actJumpAccept-58]
|
_ = x[actHalfPageUp-58]
|
||||||
_ = x[actPrintQuery-59]
|
_ = x[actHalfPageDown-59]
|
||||||
_ = x[actRefreshPreview-60]
|
_ = x[actOffsetUp-60]
|
||||||
_ = x[actReplaceQuery-61]
|
_ = x[actOffsetDown-61]
|
||||||
_ = x[actToggleSort-62]
|
_ = x[actOffsetMiddle-62]
|
||||||
_ = x[actShowPreview-63]
|
_ = x[actJump-63]
|
||||||
_ = x[actHidePreview-64]
|
_ = x[actJumpAccept-64]
|
||||||
_ = x[actTogglePreview-65]
|
_ = x[actPrintQuery-65]
|
||||||
_ = x[actTogglePreviewWrap-66]
|
_ = x[actRefreshPreview-66]
|
||||||
_ = x[actTransform-67]
|
_ = x[actReplaceQuery-67]
|
||||||
_ = x[actTransformBorderLabel-68]
|
_ = x[actToggleSort-68]
|
||||||
_ = x[actTransformHeader-69]
|
_ = x[actShowPreview-69]
|
||||||
_ = x[actTransformPreviewLabel-70]
|
_ = x[actHidePreview-70]
|
||||||
_ = x[actTransformPrompt-71]
|
_ = x[actTogglePreview-71]
|
||||||
_ = x[actTransformQuery-72]
|
_ = x[actTogglePreviewWrap-72]
|
||||||
_ = x[actPreview-73]
|
_ = x[actTransform-73]
|
||||||
_ = x[actChangePreview-74]
|
_ = x[actTransformBorderLabel-74]
|
||||||
_ = x[actChangePreviewWindow-75]
|
_ = x[actTransformHeader-75]
|
||||||
_ = x[actPreviewTop-76]
|
_ = x[actTransformPreviewLabel-76]
|
||||||
_ = x[actPreviewBottom-77]
|
_ = x[actTransformPrompt-77]
|
||||||
_ = x[actPreviewUp-78]
|
_ = x[actTransformQuery-78]
|
||||||
_ = x[actPreviewDown-79]
|
_ = x[actPreview-79]
|
||||||
_ = x[actPreviewPageUp-80]
|
_ = x[actChangePreview-80]
|
||||||
_ = x[actPreviewPageDown-81]
|
_ = x[actChangePreviewWindow-81]
|
||||||
_ = x[actPreviewHalfPageUp-82]
|
_ = x[actPreviewTop-82]
|
||||||
_ = x[actPreviewHalfPageDown-83]
|
_ = x[actPreviewBottom-83]
|
||||||
_ = x[actPrevHistory-84]
|
_ = x[actPreviewUp-84]
|
||||||
_ = x[actPrevSelected-85]
|
_ = x[actPreviewDown-85]
|
||||||
_ = x[actPut-86]
|
_ = x[actPreviewPageUp-86]
|
||||||
_ = x[actNextHistory-87]
|
_ = x[actPreviewPageDown-87]
|
||||||
_ = x[actNextSelected-88]
|
_ = x[actPreviewHalfPageUp-88]
|
||||||
_ = x[actExecute-89]
|
_ = x[actPreviewHalfPageDown-89]
|
||||||
_ = x[actExecuteSilent-90]
|
_ = x[actPrevHistory-90]
|
||||||
_ = x[actExecuteMulti-91]
|
_ = x[actPrevSelected-91]
|
||||||
_ = x[actSigStop-92]
|
_ = x[actPrint-92]
|
||||||
_ = x[actFirst-93]
|
_ = x[actPut-93]
|
||||||
_ = x[actLast-94]
|
_ = x[actNextHistory-94]
|
||||||
_ = x[actReload-95]
|
_ = x[actNextSelected-95]
|
||||||
_ = x[actReloadSync-96]
|
_ = x[actExecute-96]
|
||||||
_ = x[actDisableSearch-97]
|
_ = x[actExecuteSilent-97]
|
||||||
_ = x[actEnableSearch-98]
|
_ = x[actExecuteMulti-98]
|
||||||
_ = x[actSelect-99]
|
_ = x[actSigStop-99]
|
||||||
_ = x[actDeselect-100]
|
_ = x[actFirst-100]
|
||||||
_ = x[actUnbind-101]
|
_ = x[actLast-101]
|
||||||
_ = x[actRebind-102]
|
_ = x[actReload-102]
|
||||||
_ = x[actBecome-103]
|
_ = x[actReloadSync-103]
|
||||||
_ = x[actResponse-104]
|
_ = x[actDisableSearch-104]
|
||||||
_ = x[actShowHeader-105]
|
_ = x[actEnableSearch-105]
|
||||||
_ = x[actHideHeader-106]
|
_ = x[actSelect-106]
|
||||||
|
_ = x[actDeselect-107]
|
||||||
|
_ = x[actUnbind-108]
|
||||||
|
_ = x[actRebind-109]
|
||||||
|
_ = x[actBecome-110]
|
||||||
|
_ = x[actShowHeader-111]
|
||||||
|
_ = x[actHideHeader-112]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleHeaderactTrackactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactResponseactShowHeaderactHideHeader"
|
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactShowHeaderactHideHeader"
|
||||||
|
|
||||||
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 263, 278, 292, 306, 319, 336, 344, 357, 373, 385, 399, 413, 424, 435, 453, 470, 477, 496, 508, 522, 531, 546, 558, 571, 582, 593, 605, 619, 634, 642, 649, 654, 663, 674, 685, 698, 713, 724, 737, 744, 757, 770, 787, 802, 815, 829, 843, 859, 879, 891, 914, 932, 956, 974, 991, 1001, 1017, 1039, 1052, 1068, 1080, 1094, 1110, 1128, 1148, 1170, 1184, 1199, 1205, 1219, 1234, 1244, 1260, 1275, 1285, 1293, 1300, 1309, 1322, 1338, 1353, 1362, 1373, 1382, 1391, 1400, 1411, 1424, 1437}
|
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 256, 277, 292, 306, 320, 333, 350, 358, 371, 387, 399, 407, 421, 435, 446, 457, 475, 492, 499, 518, 530, 544, 553, 568, 580, 593, 604, 615, 627, 641, 662, 677, 690, 705, 722, 729, 734, 743, 754, 765, 778, 793, 804, 817, 832, 839, 852, 865, 882, 897, 910, 924, 938, 954, 974, 986, 1009, 1027, 1051, 1069, 1086, 1096, 1112, 1134, 1147, 1163, 1175, 1189, 1205, 1223, 1243, 1265, 1279, 1294, 1302, 1308, 1322, 1337, 1347, 1363, 1378, 1388, 1396, 1403, 1412, 1425, 1441, 1456, 1465, 1476, 1485, 1494, 1503, 1516, 1529}
|
||||||
|
|
||||||
func (i actionType) String() string {
|
func (i actionType) String() string {
|
||||||
if i < 0 || i >= actionType(len(_actionType_index)-1) {
|
if i < 0 || i >= actionType(len(_actionType_index)-1) {
|
||||||
|
150
src/algo/algo.go
150
src/algo/algo.go
@@ -152,7 +152,13 @@ var (
|
|||||||
// Extra bonus for word boundary after slash, colon, semi-colon, and comma
|
// Extra bonus for word boundary after slash, colon, semi-colon, and comma
|
||||||
bonusBoundaryDelimiter int16 = bonusBoundary + 1
|
bonusBoundaryDelimiter int16 = bonusBoundary + 1
|
||||||
|
|
||||||
initialCharClass charClass = charWhite
|
initialCharClass = charWhite
|
||||||
|
|
||||||
|
// A minor optimization that can give 15%+ performance boost
|
||||||
|
asciiCharClasses [unicode.MaxASCII + 1]charClass
|
||||||
|
|
||||||
|
// A minor optimization that can give yet another 5% performance boost
|
||||||
|
bonusMatrix [charNumber + 1][charNumber + 1]int16
|
||||||
)
|
)
|
||||||
|
|
||||||
type charClass int
|
type charClass int
|
||||||
@@ -187,6 +193,27 @@ func Init(scheme string) bool {
|
|||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
for i := 0; i <= unicode.MaxASCII; i++ {
|
||||||
|
char := rune(i)
|
||||||
|
c := charNonWord
|
||||||
|
if char >= 'a' && char <= 'z' {
|
||||||
|
c = charLower
|
||||||
|
} else if char >= 'A' && char <= 'Z' {
|
||||||
|
c = charUpper
|
||||||
|
} else if char >= '0' && char <= '9' {
|
||||||
|
c = charNumber
|
||||||
|
} else if strings.ContainsRune(whiteChars, char) {
|
||||||
|
c = charWhite
|
||||||
|
} else if strings.ContainsRune(delimiterChars, char) {
|
||||||
|
c = charDelimiter
|
||||||
|
}
|
||||||
|
asciiCharClasses[i] = c
|
||||||
|
}
|
||||||
|
for i := 0; i <= int(charNumber); i++ {
|
||||||
|
for j := 0; j <= int(charNumber); j++ {
|
||||||
|
bonusMatrix[i][j] = bonusFor(charClass(i), charClass(j))
|
||||||
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,21 +241,6 @@ func alloc32(offset int, slab *util.Slab, size int) (int, []int32) {
|
|||||||
return offset, make([]int32, size)
|
return offset, make([]int32, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func charClassOfAscii(char rune) charClass {
|
|
||||||
if char >= 'a' && char <= 'z' {
|
|
||||||
return charLower
|
|
||||||
} else if char >= 'A' && char <= 'Z' {
|
|
||||||
return charUpper
|
|
||||||
} else if char >= '0' && char <= '9' {
|
|
||||||
return charNumber
|
|
||||||
} else if strings.ContainsRune(whiteChars, char) {
|
|
||||||
return charWhite
|
|
||||||
} else if strings.ContainsRune(delimiterChars, char) {
|
|
||||||
return charDelimiter
|
|
||||||
}
|
|
||||||
return charNonWord
|
|
||||||
}
|
|
||||||
|
|
||||||
func charClassOfNonAscii(char rune) charClass {
|
func charClassOfNonAscii(char rune) charClass {
|
||||||
if unicode.IsLower(char) {
|
if unicode.IsLower(char) {
|
||||||
return charLower
|
return charLower
|
||||||
@@ -248,31 +260,36 @@ func charClassOfNonAscii(char rune) charClass {
|
|||||||
|
|
||||||
func charClassOf(char rune) charClass {
|
func charClassOf(char rune) charClass {
|
||||||
if char <= unicode.MaxASCII {
|
if char <= unicode.MaxASCII {
|
||||||
return charClassOfAscii(char)
|
return asciiCharClasses[char]
|
||||||
}
|
}
|
||||||
return charClassOfNonAscii(char)
|
return charClassOfNonAscii(char)
|
||||||
}
|
}
|
||||||
|
|
||||||
func bonusFor(prevClass charClass, class charClass) int16 {
|
func bonusFor(prevClass charClass, class charClass) int16 {
|
||||||
if class > charNonWord {
|
if class > charNonWord {
|
||||||
if prevClass == charWhite {
|
switch prevClass {
|
||||||
|
case charWhite:
|
||||||
// Word boundary after whitespace
|
// Word boundary after whitespace
|
||||||
return bonusBoundaryWhite
|
return bonusBoundaryWhite
|
||||||
} else if prevClass == charDelimiter {
|
case charDelimiter:
|
||||||
// Word boundary after a delimiter character
|
// Word boundary after a delimiter character
|
||||||
return bonusBoundaryDelimiter
|
return bonusBoundaryDelimiter
|
||||||
} else if prevClass == charNonWord {
|
case charNonWord:
|
||||||
// Word boundary
|
// Word boundary
|
||||||
return bonusBoundary
|
return bonusBoundary
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if prevClass == charLower && class == charUpper ||
|
if prevClass == charLower && class == charUpper ||
|
||||||
prevClass != charNumber && class == charNumber {
|
prevClass != charNumber && class == charNumber {
|
||||||
// camelCase letter123
|
// camelCase letter123
|
||||||
return bonusCamel123
|
return bonusCamel123
|
||||||
} else if class == charNonWord {
|
}
|
||||||
|
|
||||||
|
switch class {
|
||||||
|
case charNonWord, charDelimiter:
|
||||||
return bonusNonWord
|
return bonusNonWord
|
||||||
} else if class == charWhite {
|
case charWhite:
|
||||||
return bonusBoundaryWhite
|
return bonusBoundaryWhite
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
@@ -282,7 +299,7 @@ func bonusAt(input *util.Chars, idx int) int16 {
|
|||||||
if idx == 0 {
|
if idx == 0 {
|
||||||
return bonusBoundaryWhite
|
return bonusBoundaryWhite
|
||||||
}
|
}
|
||||||
return bonusFor(charClassOf(input.Get(idx-1)), charClassOf(input.Get(idx)))
|
return bonusMatrix[charClassOf(input.Get(idx-1))][charClassOf(input.Get(idx))]
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizeRune(r rune) rune {
|
func normalizeRune(r rune) rune {
|
||||||
@@ -335,30 +352,45 @@ func isAscii(runes []rune) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func asciiFuzzyIndex(input *util.Chars, pattern []rune, caseSensitive bool) int {
|
func asciiFuzzyIndex(input *util.Chars, pattern []rune, caseSensitive bool) (int, int) {
|
||||||
// Can't determine
|
// Can't determine
|
||||||
if !input.IsBytes() {
|
if !input.IsBytes() {
|
||||||
return 0
|
return 0, input.Length()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not possible
|
// Not possible
|
||||||
if !isAscii(pattern) {
|
if !isAscii(pattern) {
|
||||||
return -1
|
return -1, -1
|
||||||
}
|
}
|
||||||
|
|
||||||
firstIdx, idx := 0, 0
|
firstIdx, idx, lastIdx := 0, 0, 0
|
||||||
|
var b byte
|
||||||
for pidx := 0; pidx < len(pattern); pidx++ {
|
for pidx := 0; pidx < len(pattern); pidx++ {
|
||||||
idx = trySkip(input, caseSensitive, byte(pattern[pidx]), idx)
|
b = byte(pattern[pidx])
|
||||||
|
idx = trySkip(input, caseSensitive, b, idx)
|
||||||
if idx < 0 {
|
if idx < 0 {
|
||||||
return -1
|
return -1, -1
|
||||||
}
|
}
|
||||||
if pidx == 0 && idx > 0 {
|
if pidx == 0 && idx > 0 {
|
||||||
// Step back to find the right bonus point
|
// Step back to find the right bonus point
|
||||||
firstIdx = idx - 1
|
firstIdx = idx - 1
|
||||||
}
|
}
|
||||||
|
lastIdx = idx
|
||||||
idx++
|
idx++
|
||||||
}
|
}
|
||||||
return firstIdx
|
|
||||||
|
// Find the last appearance of the last character of the pattern to limit the search scope
|
||||||
|
bu := b
|
||||||
|
if !caseSensitive && b >= 'a' && b <= 'z' {
|
||||||
|
bu = b - 32
|
||||||
|
}
|
||||||
|
scope := input.Bytes()[lastIdx:]
|
||||||
|
for offset := len(scope) - 1; offset > 0; offset-- {
|
||||||
|
if scope[offset] == b || scope[offset] == bu {
|
||||||
|
return firstIdx, lastIdx + offset + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return firstIdx, lastIdx + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func debugV2(T []rune, pattern []rune, F []int32, lastIdx int, H []int16, C []int16) {
|
func debugV2(T []rune, pattern []rune, F []int32, lastIdx int, H []int16, C []int16) {
|
||||||
@@ -407,6 +439,9 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
return Result{0, 0, 0}, posArray(withPos, M)
|
return Result{0, 0, 0}, posArray(withPos, M)
|
||||||
}
|
}
|
||||||
N := input.Length()
|
N := input.Length()
|
||||||
|
if M > N {
|
||||||
|
return Result{-1, -1, 0}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Since O(nm) algorithm can be prohibitively expensive for large input,
|
// Since O(nm) algorithm can be prohibitively expensive for large input,
|
||||||
// we fall back to the greedy algorithm.
|
// we fall back to the greedy algorithm.
|
||||||
@@ -415,10 +450,12 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Phase 1. Optimized search for ASCII string
|
// Phase 1. Optimized search for ASCII string
|
||||||
idx := asciiFuzzyIndex(input, pattern, caseSensitive)
|
minIdx, maxIdx := asciiFuzzyIndex(input, pattern, caseSensitive)
|
||||||
if idx < 0 {
|
if minIdx < 0 {
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
}
|
}
|
||||||
|
// fmt.Println(N, maxIdx, idx, maxIdx-idx, input.ToString())
|
||||||
|
N = maxIdx - minIdx
|
||||||
|
|
||||||
// Reuse pre-allocated integer slice to avoid unnecessary sweeping of garbages
|
// Reuse pre-allocated integer slice to avoid unnecessary sweeping of garbages
|
||||||
offset16 := 0
|
offset16 := 0
|
||||||
@@ -431,20 +468,19 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
offset32, F := alloc32(offset32, slab, M)
|
offset32, F := alloc32(offset32, slab, M)
|
||||||
// Rune array
|
// Rune array
|
||||||
_, T := alloc32(offset32, slab, N)
|
_, T := alloc32(offset32, slab, N)
|
||||||
input.CopyRunes(T)
|
input.CopyRunes(T, minIdx)
|
||||||
|
|
||||||
// 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), initialCharClass, false
|
pchar0, pchar, prevH0, prevClass, inGap := pattern[0], pattern[0], int16(0), initialCharClass, false
|
||||||
Tsub := T[idx:]
|
for off, char := range T {
|
||||||
H0sub, C0sub, Bsub := H0[idx:][:len(Tsub)], C0[idx:][:len(Tsub)], B[idx:][:len(Tsub)]
|
|
||||||
for off, char := range Tsub {
|
|
||||||
var class charClass
|
var class charClass
|
||||||
if char <= unicode.MaxASCII {
|
if char <= unicode.MaxASCII {
|
||||||
class = charClassOfAscii(char)
|
class = asciiCharClasses[char]
|
||||||
if !caseSensitive && class == charUpper {
|
if !caseSensitive && class == charUpper {
|
||||||
char += 32
|
char += 32
|
||||||
|
T[off] = char
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
class = charClassOfNonAscii(char)
|
class = charClassOfNonAscii(char)
|
||||||
@@ -454,28 +490,28 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
if normalize {
|
if normalize {
|
||||||
char = normalizeRune(char)
|
char = normalizeRune(char)
|
||||||
}
|
}
|
||||||
|
T[off] = char
|
||||||
}
|
}
|
||||||
|
|
||||||
Tsub[off] = char
|
bonus := bonusMatrix[prevClass][class]
|
||||||
bonus := bonusFor(prevClass, class)
|
B[off] = bonus
|
||||||
Bsub[off] = bonus
|
|
||||||
prevClass = class
|
prevClass = class
|
||||||
|
|
||||||
if char == pchar {
|
if char == pchar {
|
||||||
if pidx < M {
|
if pidx < M {
|
||||||
F[pidx] = int32(idx + off)
|
F[pidx] = int32(off)
|
||||||
pidx++
|
pidx++
|
||||||
pchar = pattern[util.Min(pidx, M-1)]
|
pchar = pattern[util.Min(pidx, M-1)]
|
||||||
}
|
}
|
||||||
lastIdx = idx + off
|
lastIdx = off
|
||||||
}
|
}
|
||||||
|
|
||||||
if char == pchar0 {
|
if char == pchar0 {
|
||||||
score := scoreMatch + bonus*bonusFirstCharMultiplier
|
score := scoreMatch + bonus*bonusFirstCharMultiplier
|
||||||
H0sub[off] = score
|
H0[off] = score
|
||||||
C0sub[off] = 1
|
C0[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, off
|
||||||
if forward && bonus >= bonusBoundary {
|
if forward && bonus >= bonusBoundary {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -483,24 +519,24 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
inGap = false
|
inGap = false
|
||||||
} else {
|
} else {
|
||||||
if inGap {
|
if inGap {
|
||||||
H0sub[off] = util.Max16(prevH0+scoreGapExtension, 0)
|
H0[off] = util.Max16(prevH0+scoreGapExtension, 0)
|
||||||
} else {
|
} else {
|
||||||
H0sub[off] = util.Max16(prevH0+scoreGapStart, 0)
|
H0[off] = util.Max16(prevH0+scoreGapStart, 0)
|
||||||
}
|
}
|
||||||
C0sub[off] = 0
|
C0[off] = 0
|
||||||
inGap = true
|
inGap = true
|
||||||
}
|
}
|
||||||
prevH0 = H0sub[off]
|
prevH0 = H0[off]
|
||||||
}
|
}
|
||||||
if pidx != M {
|
if pidx != M {
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
}
|
}
|
||||||
if M == 1 {
|
if M == 1 {
|
||||||
result := Result{maxScorePos, maxScorePos + 1, int(maxScore)}
|
result := Result{minIdx + maxScorePos, minIdx + maxScorePos + 1, int(maxScore)}
|
||||||
if !withPos {
|
if !withPos {
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
pos := []int{maxScorePos}
|
pos := []int{minIdx + maxScorePos}
|
||||||
return result, &pos
|
return result, &pos
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -597,7 +633,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
}
|
}
|
||||||
|
|
||||||
if s > s1 && (s > s2 || s == s2 && preferMatch) {
|
if s > s1 && (s > s2 || s == s2 && preferMatch) {
|
||||||
*pos = append(*pos, j)
|
*pos = append(*pos, j+minIdx)
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -610,7 +646,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
// Start offset we return here is only relevant when begin tiebreak is used.
|
// Start offset we return here is only relevant when begin tiebreak is used.
|
||||||
// However finding the accurate offset requires backtracking, and we don't
|
// However finding the accurate offset requires backtracking, and we don't
|
||||||
// want to pay extra cost for the option that has lost its importance.
|
// want to pay extra cost for the option that has lost its importance.
|
||||||
return Result{j, maxScorePos + 1, int(maxScore)}, pos
|
return Result{minIdx + j, minIdx + maxScorePos + 1, int(maxScore)}, pos
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement the same sorting criteria as V2
|
// Implement the same sorting criteria as V2
|
||||||
@@ -640,7 +676,7 @@ func calculateScore(caseSensitive bool, normalize bool, text *util.Chars, patter
|
|||||||
*pos = append(*pos, idx)
|
*pos = append(*pos, idx)
|
||||||
}
|
}
|
||||||
score += scoreMatch
|
score += scoreMatch
|
||||||
bonus := bonusFor(prevClass, class)
|
bonus := bonusMatrix[prevClass][class]
|
||||||
if consecutive == 0 {
|
if consecutive == 0 {
|
||||||
firstBonus = bonus
|
firstBonus = bonus
|
||||||
} else {
|
} else {
|
||||||
@@ -678,7 +714,8 @@ func FuzzyMatchV1(caseSensitive bool, normalize bool, forward bool, text *util.C
|
|||||||
if len(pattern) == 0 {
|
if len(pattern) == 0 {
|
||||||
return Result{0, 0, 0}, nil
|
return Result{0, 0, 0}, nil
|
||||||
}
|
}
|
||||||
if asciiFuzzyIndex(text, pattern, caseSensitive) < 0 {
|
idx, _ := asciiFuzzyIndex(text, pattern, caseSensitive)
|
||||||
|
if idx < 0 {
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -772,7 +809,8 @@ func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *uti
|
|||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if asciiFuzzyIndex(text, pattern, caseSensitive) < 0 {
|
idx, _ := asciiFuzzyIndex(text, pattern, caseSensitive)
|
||||||
|
if idx < 0 {
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -9,6 +9,10 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Init("default")
|
||||||
|
}
|
||||||
|
|
||||||
func assertMatch(t *testing.T, fun Algo, caseSensitive, forward bool, input, pattern string, sidx int, eidx int, score int) {
|
func assertMatch(t *testing.T, fun Algo, caseSensitive, forward bool, input, pattern string, sidx int, eidx int, score int) {
|
||||||
assertMatch2(t, fun, caseSensitive, false, forward, input, pattern, sidx, eidx, score)
|
assertMatch2(t, fun, caseSensitive, false, forward, input, pattern, sidx, eidx, score)
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
package algo
|
package algo
|
||||||
|
|
||||||
var normalized map[rune]rune = map[rune]rune{
|
var normalized = map[rune]rune{
|
||||||
0x00E1: 'a', // WITH ACUTE, LATIN SMALL LETTER
|
0x00E1: 'a', // WITH ACUTE, LATIN SMALL LETTER
|
||||||
0x0103: 'a', // WITH BREVE, LATIN SMALL LETTER
|
0x0103: 'a', // WITH BREVE, LATIN SMALL LETTER
|
||||||
0x01CE: 'a', // WITH CARON, LATIN SMALL LETTER
|
0x01CE: 'a', // WITH CARON, LATIN SMALL LETTER
|
||||||
|
@@ -292,7 +292,7 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
|
|||||||
|
|
||||||
func parseAnsiCode(s string, delimiter byte) (int, byte, string) {
|
func parseAnsiCode(s string, delimiter byte) (int, byte, string) {
|
||||||
var remaining string
|
var remaining string
|
||||||
i := -1
|
var i int
|
||||||
if delimiter == 0 {
|
if delimiter == 0 {
|
||||||
// Faster than strings.IndexAny(";:")
|
// Faster than strings.IndexAny(";:")
|
||||||
i = strings.IndexByte(s, ';')
|
i = strings.IndexByte(s, ';')
|
||||||
@@ -312,7 +312,7 @@ func parseAnsiCode(s string, delimiter byte) (int, byte, string) {
|
|||||||
// Inlined version of strconv.Atoi() that only handles positive
|
// Inlined version of strconv.Atoi() that only handles positive
|
||||||
// integers and does not allocate on error.
|
// integers and does not allocate on error.
|
||||||
code := 0
|
code := 0
|
||||||
for _, ch := range []byte(s) {
|
for _, ch := range stringBytes(s) {
|
||||||
ch -= '0'
|
ch -= '0'
|
||||||
if ch > 9 {
|
if ch > 9 {
|
||||||
return -1, delimiter, remaining
|
return -1, delimiter, remaining
|
||||||
@@ -350,7 +350,7 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
|||||||
state256 := 0
|
state256 := 0
|
||||||
ptr := &state.fg
|
ptr := &state.fg
|
||||||
|
|
||||||
var delimiter byte = 0
|
var delimiter byte
|
||||||
count := 0
|
count := 0
|
||||||
for len(ansiCode) != 0 {
|
for len(ansiCode) != 0 {
|
||||||
var num int
|
var num int
|
||||||
|
@@ -342,8 +342,8 @@ func TestAnsiCodeStringConversion(t *testing.T) {
|
|||||||
state := interpretCode(code, prevState)
|
state := interpretCode(code, prevState)
|
||||||
if expected != state.ToString() {
|
if expected != state.ToString() {
|
||||||
t.Errorf("expected: %s, actual: %s",
|
t.Errorf("expected: %s, actual: %s",
|
||||||
strings.Replace(expected, "\x1b[", "\\x1b[", -1),
|
strings.ReplaceAll(expected, "\x1b[", "\\x1b["),
|
||||||
strings.Replace(state.ToString(), "\x1b[", "\\x1b[", -1))
|
strings.ReplaceAll(state.ToString(), "\x1b[", "\\x1b["))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert("\x1b[m", nil, "")
|
assert("\x1b[m", nil, "")
|
||||||
|
18
src/cache.go
18
src/cache.go
@@ -12,8 +12,22 @@ type ChunkCache struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewChunkCache returns a new ChunkCache
|
// NewChunkCache returns a new ChunkCache
|
||||||
func NewChunkCache() ChunkCache {
|
func NewChunkCache() *ChunkCache {
|
||||||
return ChunkCache{sync.Mutex{}, make(map[*Chunk]*queryCache)}
|
return &ChunkCache{sync.Mutex{}, make(map[*Chunk]*queryCache)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *ChunkCache) Clear() {
|
||||||
|
cc.mutex.Lock()
|
||||||
|
cc.cache = make(map[*Chunk]*queryCache)
|
||||||
|
cc.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *ChunkCache) retire(chunk ...*Chunk) {
|
||||||
|
cc.mutex.Lock()
|
||||||
|
for _, c := range chunk {
|
||||||
|
delete(cc.cache, c)
|
||||||
|
}
|
||||||
|
cc.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add adds the list to the cache
|
// Add adds the list to the cache
|
||||||
|
@@ -16,14 +16,16 @@ type ChunkList struct {
|
|||||||
chunks []*Chunk
|
chunks []*Chunk
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
trans ItemBuilder
|
trans ItemBuilder
|
||||||
|
cache *ChunkCache
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewChunkList returns a new ChunkList
|
// NewChunkList returns a new ChunkList
|
||||||
func NewChunkList(trans ItemBuilder) *ChunkList {
|
func NewChunkList(cache *ChunkCache, trans ItemBuilder) *ChunkList {
|
||||||
return &ChunkList{
|
return &ChunkList{
|
||||||
chunks: []*Chunk{},
|
chunks: []*Chunk{},
|
||||||
mutex: sync.Mutex{},
|
mutex: sync.Mutex{},
|
||||||
trans: trans}
|
trans: trans,
|
||||||
|
cache: cache}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Chunk) push(trans ItemBuilder, data []byte) bool {
|
func (c *Chunk) push(trans ItemBuilder, data []byte) bool {
|
||||||
@@ -48,7 +50,12 @@ func CountItems(cs []*Chunk) int {
|
|||||||
if len(cs) == 0 {
|
if len(cs) == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return chunkSize*(len(cs)-1) + cs[len(cs)-1].count
|
if len(cs) == 1 {
|
||||||
|
return cs[0].count
|
||||||
|
}
|
||||||
|
|
||||||
|
// First chunk might not be full due to --tail=N
|
||||||
|
return cs[0].count + chunkSize*(len(cs)-2) + cs[len(cs)-1].count
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push adds the item to the list
|
// Push adds the item to the list
|
||||||
@@ -72,18 +79,56 @@ func (cl *ChunkList) Clear() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Snapshot returns immutable snapshot of the ChunkList
|
// Snapshot returns immutable snapshot of the ChunkList
|
||||||
func (cl *ChunkList) Snapshot() ([]*Chunk, int) {
|
func (cl *ChunkList) Snapshot(tail int) ([]*Chunk, int, bool) {
|
||||||
cl.mutex.Lock()
|
cl.mutex.Lock()
|
||||||
|
|
||||||
|
changed := false
|
||||||
|
if tail > 0 && CountItems(cl.chunks) > tail {
|
||||||
|
changed = true
|
||||||
|
// Find the number of chunks to keep
|
||||||
|
numChunks := 0
|
||||||
|
for left, i := tail, len(cl.chunks)-1; left > 0 && i >= 0; i-- {
|
||||||
|
numChunks++
|
||||||
|
left -= cl.chunks[i].count
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the chunks to keep
|
||||||
|
ret := make([]*Chunk, numChunks)
|
||||||
|
minIndex := len(cl.chunks) - numChunks
|
||||||
|
cl.cache.retire(cl.chunks[:minIndex]...)
|
||||||
|
copy(ret, cl.chunks[minIndex:])
|
||||||
|
|
||||||
|
for left, i := tail, len(ret)-1; i >= 0; i-- {
|
||||||
|
chunk := ret[i]
|
||||||
|
if chunk.count > left {
|
||||||
|
newChunk := *chunk
|
||||||
|
newChunk.count = left
|
||||||
|
oldCount := chunk.count
|
||||||
|
for i := 0; i < left; i++ {
|
||||||
|
newChunk.items[i] = chunk.items[oldCount-left+i]
|
||||||
|
}
|
||||||
|
ret[i] = &newChunk
|
||||||
|
cl.cache.retire(chunk)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
left -= chunk.count
|
||||||
|
}
|
||||||
|
cl.chunks = ret
|
||||||
|
}
|
||||||
|
|
||||||
ret := make([]*Chunk, len(cl.chunks))
|
ret := make([]*Chunk, len(cl.chunks))
|
||||||
copy(ret, cl.chunks)
|
copy(ret, cl.chunks)
|
||||||
|
|
||||||
// Duplicate the last chunk
|
// Duplicate the first and the last chunk
|
||||||
if cnt := len(ret); cnt > 0 {
|
if cnt := len(ret); cnt > 0 {
|
||||||
|
if tail > 0 && cnt > 1 {
|
||||||
|
newChunk := *ret[0]
|
||||||
|
ret[0] = &newChunk
|
||||||
|
}
|
||||||
newChunk := *ret[cnt-1]
|
newChunk := *ret[cnt-1]
|
||||||
ret[cnt-1] = &newChunk
|
ret[cnt-1] = &newChunk
|
||||||
}
|
}
|
||||||
|
|
||||||
cl.mutex.Unlock()
|
cl.mutex.Unlock()
|
||||||
return ret, CountItems(ret)
|
return ret, CountItems(ret), changed
|
||||||
}
|
}
|
||||||
|
@@ -11,13 +11,13 @@ func TestChunkList(t *testing.T) {
|
|||||||
// FIXME global
|
// FIXME global
|
||||||
sortCriteria = []criterion{byScore, byLength}
|
sortCriteria = []criterion{byScore, byLength}
|
||||||
|
|
||||||
cl := NewChunkList(func(item *Item, s []byte) bool {
|
cl := NewChunkList(NewChunkCache(), func(item *Item, s []byte) bool {
|
||||||
item.text = util.ToChars(s)
|
item.text = util.ToChars(s)
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
// Snapshot
|
// Snapshot
|
||||||
snapshot, count := cl.Snapshot()
|
snapshot, count, _ := cl.Snapshot(0)
|
||||||
if len(snapshot) > 0 || count > 0 {
|
if len(snapshot) > 0 || count > 0 {
|
||||||
t.Error("Snapshot should be empty now")
|
t.Error("Snapshot should be empty now")
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,7 @@ func TestChunkList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// But the new snapshot should contain the added items
|
// But the new snapshot should contain the added items
|
||||||
snapshot, count = cl.Snapshot()
|
snapshot, count, _ = cl.Snapshot(0)
|
||||||
if len(snapshot) != 1 && count != 2 {
|
if len(snapshot) != 1 && count != 2 {
|
||||||
t.Error("Snapshot should not be empty now")
|
t.Error("Snapshot should not be empty now")
|
||||||
}
|
}
|
||||||
@@ -61,7 +61,7 @@ func TestChunkList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New snapshot
|
// New snapshot
|
||||||
snapshot, count = cl.Snapshot()
|
snapshot, count, _ = cl.Snapshot(0)
|
||||||
if len(snapshot) != 3 || !snapshot[0].IsFull() ||
|
if len(snapshot) != 3 || !snapshot[0].IsFull() ||
|
||||||
!snapshot[1].IsFull() || snapshot[2].IsFull() || count != chunkSize*2+2 {
|
!snapshot[1].IsFull() || snapshot[2].IsFull() || count != chunkSize*2+2 {
|
||||||
t.Error("Expected two full chunks and one more chunk")
|
t.Error("Expected two full chunks and one more chunk")
|
||||||
@@ -78,3 +78,39 @@ func TestChunkList(t *testing.T) {
|
|||||||
t.Error("Unexpected number of items:", lastChunkCount)
|
t.Error("Unexpected number of items:", lastChunkCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestChunkListTail(t *testing.T) {
|
||||||
|
cl := NewChunkList(NewChunkCache(), func(item *Item, s []byte) bool {
|
||||||
|
item.text = util.ToChars(s)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
total := chunkSize*2 + chunkSize/2
|
||||||
|
for i := 0; i < total; i++ {
|
||||||
|
cl.Push([]byte(fmt.Sprintf("item %d", i)))
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot, count, changed := cl.Snapshot(0)
|
||||||
|
assertCount := func(expected int, shouldChange bool) {
|
||||||
|
if count != expected || CountItems(snapshot) != expected {
|
||||||
|
t.Errorf("Unexpected count: %d (expected: %d)", count, expected)
|
||||||
|
}
|
||||||
|
if changed != shouldChange {
|
||||||
|
t.Error("Unexpected change status")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertCount(total, false)
|
||||||
|
|
||||||
|
tail := chunkSize + chunkSize/2
|
||||||
|
snapshot, count, changed = cl.Snapshot(tail)
|
||||||
|
assertCount(tail, true)
|
||||||
|
|
||||||
|
snapshot, count, changed = cl.Snapshot(tail)
|
||||||
|
assertCount(tail, false)
|
||||||
|
|
||||||
|
snapshot, count, changed = cl.Snapshot(0)
|
||||||
|
assertCount(tail, false)
|
||||||
|
|
||||||
|
tail = chunkSize / 2
|
||||||
|
snapshot, count, changed = cl.Snapshot(tail)
|
||||||
|
assertCount(tail, true)
|
||||||
|
}
|
||||||
|
@@ -2,7 +2,6 @@ package fzf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
"os"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
@@ -15,6 +14,7 @@ const (
|
|||||||
|
|
||||||
// Reader
|
// Reader
|
||||||
readerBufferSize = 64 * 1024
|
readerBufferSize = 64 * 1024
|
||||||
|
readerSlabSize = 128 * 1024
|
||||||
readerPollIntervalMin = 10 * time.Millisecond
|
readerPollIntervalMin = 10 * time.Millisecond
|
||||||
readerPollIntervalStep = 5 * time.Millisecond
|
readerPollIntervalStep = 5 * time.Millisecond
|
||||||
readerPollIntervalMax = 50 * time.Millisecond
|
readerPollIntervalMax = 50 * time.Millisecond
|
||||||
@@ -54,20 +54,11 @@ const (
|
|||||||
defaultJumpLabels string = "asdfghjklqwertyuiopzxcvbnm1234567890ASDFGHJKLQWERTYUIOPZXCVBNM`~;:,<.>/?'\"!@#$%^&*()[{]}-_=+"
|
defaultJumpLabels string = "asdfghjklqwertyuiopzxcvbnm1234567890ASDFGHJKLQWERTYUIOPZXCVBNM`~;:,<.>/?'\"!@#$%^&*()[{]}-_=+"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultCommand string
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
if !util.IsWindows() {
|
|
||||||
defaultCommand = `set -o pipefail; command find -L . -mindepth 1 \( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-`
|
|
||||||
} else if os.Getenv("TERM") == "cygwin" {
|
|
||||||
defaultCommand = `sh -c "command find -L . -mindepth 1 -path '*/.*' -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-"`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fzf events
|
// fzf events
|
||||||
const (
|
const (
|
||||||
EvtReadNew util.EventType = iota
|
EvtReadNew util.EventType = iota
|
||||||
EvtReadFin
|
EvtReadFin
|
||||||
|
EvtReadNone
|
||||||
EvtSearchNew
|
EvtSearchNew
|
||||||
EvtSearchProgress
|
EvtSearchProgress
|
||||||
EvtSearchFin
|
EvtSearchFin
|
||||||
@@ -77,9 +68,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
exitCancel = -1
|
ExitOk = 0
|
||||||
exitOk = 0
|
ExitNoMatch = 1
|
||||||
exitNoMatch = 1
|
ExitError = 2
|
||||||
exitError = 2
|
ExitBecome = 126
|
||||||
exitInterrupt = 130
|
ExitInterrupt = 130
|
||||||
)
|
)
|
||||||
|
191
src/core.go
191
src/core.go
@@ -2,8 +2,8 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
@@ -18,20 +18,52 @@ Matcher -> EvtSearchFin -> Terminal (update list)
|
|||||||
Matcher -> EvtHeader -> Terminal (update header)
|
Matcher -> EvtHeader -> Terminal (update header)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
type revision struct {
|
||||||
|
major int
|
||||||
|
minor int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *revision) bumpMajor() {
|
||||||
|
r.major++
|
||||||
|
r.minor = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *revision) bumpMinor() {
|
||||||
|
r.minor++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r revision) compatible(other revision) bool {
|
||||||
|
return r.major == other.major
|
||||||
|
}
|
||||||
|
|
||||||
// Run starts fzf
|
// Run starts fzf
|
||||||
func Run(opts *Options, version string, revision string) {
|
func Run(opts *Options) (int, error) {
|
||||||
|
if opts.Filter == nil {
|
||||||
|
if opts.Tmux != nil && len(os.Getenv("TMUX")) > 0 && opts.Tmux.index >= opts.Height.index {
|
||||||
|
return runTmux(os.Args, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
if needWinpty(opts) {
|
||||||
|
return runWinpty(os.Args, opts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := postProcessOptions(opts); err != nil {
|
||||||
|
return ExitError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer util.RunAtExitFuncs()
|
||||||
|
|
||||||
|
// Output channel given
|
||||||
|
if opts.Output != nil {
|
||||||
|
opts.Printer = func(str string) {
|
||||||
|
opts.Output <- str
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sort := opts.Sort > 0
|
sort := opts.Sort > 0
|
||||||
sortCriteria = opts.Criteria
|
sortCriteria = opts.Criteria
|
||||||
|
|
||||||
if opts.Version {
|
|
||||||
if len(revision) > 0 {
|
|
||||||
fmt.Printf("%s (%s)\n", version, revision)
|
|
||||||
} else {
|
|
||||||
fmt.Println(version)
|
|
||||||
}
|
|
||||||
os.Exit(exitOk)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event channel
|
// Event channel
|
||||||
eventBox := util.NewEventBox()
|
eventBox := util.NewEventBox()
|
||||||
|
|
||||||
@@ -45,28 +77,29 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
if opts.Theme.Colored {
|
if opts.Theme.Colored {
|
||||||
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
||||||
prevLineAnsiState = lineAnsiState
|
prevLineAnsiState = lineAnsiState
|
||||||
trimmed, offsets, newState := extractColor(string(data), lineAnsiState, nil)
|
trimmed, offsets, newState := extractColor(byteString(data), lineAnsiState, nil)
|
||||||
lineAnsiState = newState
|
lineAnsiState = newState
|
||||||
return util.ToChars([]byte(trimmed)), offsets
|
return util.ToChars(stringBytes(trimmed)), offsets
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// When color is disabled but ansi option is given,
|
// When color is disabled but ansi option is given,
|
||||||
// we simply strip out ANSI codes from the input
|
// we simply strip out ANSI codes from the input
|
||||||
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
||||||
trimmed, _, _ := extractColor(string(data), nil, nil)
|
trimmed, _, _ := extractColor(byteString(data), nil, nil)
|
||||||
return util.ToChars([]byte(trimmed)), nil
|
return util.ToChars(stringBytes(trimmed)), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chunk list
|
// Chunk list
|
||||||
|
cache := NewChunkCache()
|
||||||
var chunkList *ChunkList
|
var chunkList *ChunkList
|
||||||
var itemIndex int32
|
var itemIndex int32
|
||||||
header := make([]string, 0, opts.HeaderLines)
|
header := make([]string, 0, opts.HeaderLines)
|
||||||
if len(opts.WithNth) == 0 {
|
if len(opts.WithNth) == 0 {
|
||||||
chunkList = NewChunkList(func(item *Item, data []byte) bool {
|
chunkList = NewChunkList(cache, func(item *Item, data []byte) bool {
|
||||||
if len(header) < opts.HeaderLines {
|
if len(header) < opts.HeaderLines {
|
||||||
header = append(header, string(data))
|
header = append(header, byteString(data))
|
||||||
eventBox.Set(EvtHeader, header)
|
eventBox.Set(EvtHeader, header)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -76,8 +109,8 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
chunkList = NewChunkList(func(item *Item, data []byte) bool {
|
chunkList = NewChunkList(cache, func(item *Item, data []byte) bool {
|
||||||
tokens := Tokenize(string(data), opts.Delimiter)
|
tokens := Tokenize(byteString(data), opts.Delimiter)
|
||||||
if opts.Ansi && opts.Theme.Colored && len(tokens) > 1 {
|
if opts.Ansi && opts.Theme.Colored && len(tokens) > 1 {
|
||||||
var ansiState *ansiState
|
var ansiState *ansiState
|
||||||
if prevLineAnsiState != nil {
|
if prevLineAnsiState != nil {
|
||||||
@@ -101,7 +134,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
eventBox.Set(EvtHeader, header)
|
eventBox.Set(EvtHeader, header)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
item.text, item.colors = ansiProcessor([]byte(transformed))
|
item.text, item.colors = ansiProcessor(stringBytes(transformed))
|
||||||
item.text.TrimTrailingWhitespaces()
|
item.text.TrimTrailingWhitespaces()
|
||||||
item.text.Index = itemIndex
|
item.text.Index = itemIndex
|
||||||
item.origText = &data
|
item.origText = &data
|
||||||
@@ -110,14 +143,24 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process executor
|
||||||
|
executor := util.NewExecutor(opts.WithShell)
|
||||||
|
|
||||||
// Reader
|
// Reader
|
||||||
|
reloadOnStart := opts.reloadOnStart()
|
||||||
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
|
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
|
||||||
var reader *Reader
|
var reader *Reader
|
||||||
if !streamingFilter {
|
if !streamingFilter {
|
||||||
reader = NewReader(func(data []byte) bool {
|
reader = NewReader(func(data []byte) bool {
|
||||||
return chunkList.Push(data)
|
return chunkList.Push(data)
|
||||||
}, eventBox, opts.ReadZero, opts.Filter == nil)
|
}, eventBox, executor, opts.ReadZero, opts.Filter == nil)
|
||||||
go reader.ReadSource()
|
|
||||||
|
if reloadOnStart {
|
||||||
|
// reload or reload-sync action is bound to 'start' event, no need to start the reader
|
||||||
|
eventBox.Set(EvtReadNone, nil)
|
||||||
|
} else {
|
||||||
|
go reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Matcher
|
// Matcher
|
||||||
@@ -133,14 +176,15 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
forward = true
|
forward = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
patternCache := make(map[string]*Pattern)
|
||||||
patternBuilder := func(runes []rune) *Pattern {
|
patternBuilder := func(runes []rune) *Pattern {
|
||||||
return BuildPattern(
|
return BuildPattern(cache, patternCache,
|
||||||
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward, withPos,
|
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)
|
||||||
}
|
}
|
||||||
inputRevision := 0
|
inputRevision := revision{}
|
||||||
snapshotRevision := 0
|
snapshotRevision := revision{}
|
||||||
matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox, inputRevision)
|
matcher := NewMatcher(cache, patternBuilder, sort, opts.Tac, eventBox, inputRevision)
|
||||||
|
|
||||||
// Filtering mode
|
// Filtering mode
|
||||||
if opts.Filter != nil {
|
if opts.Filter != nil {
|
||||||
@@ -154,23 +198,27 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
found := false
|
found := false
|
||||||
if streamingFilter {
|
if streamingFilter {
|
||||||
slab := util.MakeSlab(slab16Size, slab32Size)
|
slab := util.MakeSlab(slab16Size, slab32Size)
|
||||||
|
mutex := sync.Mutex{}
|
||||||
reader := NewReader(
|
reader := NewReader(
|
||||||
func(runes []byte) bool {
|
func(runes []byte) bool {
|
||||||
item := Item{}
|
item := Item{}
|
||||||
if chunkList.trans(&item, runes) {
|
if chunkList.trans(&item, runes) {
|
||||||
|
mutex.Lock()
|
||||||
if result, _, _ := pattern.MatchItem(&item, false, slab); result != nil {
|
if result, _, _ := pattern.MatchItem(&item, false, slab); result != nil {
|
||||||
opts.Printer(item.text.ToString())
|
opts.Printer(item.text.ToString())
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
|
mutex.Unlock()
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}, eventBox, opts.ReadZero, false)
|
}, eventBox, executor, opts.ReadZero, false)
|
||||||
reader.ReadSource()
|
reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
|
||||||
} else {
|
} else {
|
||||||
eventBox.Unwatch(EvtReadNew)
|
eventBox.Unwatch(EvtReadNew)
|
||||||
eventBox.WaitFor(EvtReadFin)
|
eventBox.WaitFor(EvtReadFin)
|
||||||
|
|
||||||
snapshot, _ := chunkList.Snapshot()
|
// NOTE: Streaming filter is inherently not compatible with --tail
|
||||||
|
snapshot, _, _ := chunkList.Snapshot(opts.Tail)
|
||||||
merger, _ := matcher.scan(MatchRequest{
|
merger, _ := matcher.scan(MatchRequest{
|
||||||
chunks: snapshot,
|
chunks: snapshot,
|
||||||
pattern: pattern})
|
pattern: pattern})
|
||||||
@@ -180,29 +228,34 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if found {
|
if found {
|
||||||
os.Exit(exitOk)
|
return ExitOk, nil
|
||||||
}
|
}
|
||||||
os.Exit(exitNoMatch)
|
return ExitNoMatch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Synchronous search
|
// Synchronous search
|
||||||
if opts.Sync {
|
sync := opts.Sync && !reloadOnStart
|
||||||
|
if sync {
|
||||||
eventBox.Unwatch(EvtReadNew)
|
eventBox.Unwatch(EvtReadNew)
|
||||||
eventBox.WaitFor(EvtReadFin)
|
eventBox.WaitFor(EvtReadFin)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Go interactive
|
// Go interactive
|
||||||
go matcher.Loop()
|
go matcher.Loop()
|
||||||
|
defer matcher.Stop()
|
||||||
|
|
||||||
// Terminal I/O
|
// Terminal I/O
|
||||||
terminal := NewTerminal(opts, eventBox)
|
terminal, err := NewTerminal(opts, eventBox, executor)
|
||||||
|
if err != nil {
|
||||||
|
return ExitError, err
|
||||||
|
}
|
||||||
maxFit := 0 // Maximum number of items that can fit on screen
|
maxFit := 0 // Maximum number of items that can fit on screen
|
||||||
padHeight := 0
|
padHeight := 0
|
||||||
heightUnknown := opts.Height.auto
|
heightUnknown := opts.Height.auto
|
||||||
if heightUnknown {
|
if heightUnknown {
|
||||||
maxFit, padHeight = terminal.MaxFitAndPad()
|
maxFit, padHeight = terminal.MaxFitAndPad()
|
||||||
}
|
}
|
||||||
deferred := opts.Select1 || opts.Exit0
|
deferred := opts.Select1 || opts.Exit0 || sync
|
||||||
go terminal.Loop()
|
go terminal.Loop()
|
||||||
if !deferred && !heightUnknown {
|
if !deferred && !heightUnknown {
|
||||||
// Start right away
|
// Start right away
|
||||||
@@ -212,7 +265,8 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
// Event coordination
|
// Event coordination
|
||||||
reading := true
|
reading := true
|
||||||
ticks := 0
|
ticks := 0
|
||||||
var nextCommand *string
|
var nextCommand *commandSpec
|
||||||
|
var nextEnviron []string
|
||||||
eventBox.Watch(EvtReadNew)
|
eventBox.Watch(EvtReadNew)
|
||||||
total := 0
|
total := 0
|
||||||
query := []rune{}
|
query := []rune{}
|
||||||
@@ -232,23 +286,23 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
useSnapshot := false
|
useSnapshot := false
|
||||||
var snapshot []*Chunk
|
var snapshot []*Chunk
|
||||||
var count int
|
var count int
|
||||||
restart := func(command string) {
|
restart := func(command commandSpec, environ []string) {
|
||||||
reading = true
|
reading = true
|
||||||
chunkList.Clear()
|
chunkList.Clear()
|
||||||
itemIndex = 0
|
itemIndex = 0
|
||||||
inputRevision++
|
inputRevision.bumpMajor()
|
||||||
header = make([]string, 0, opts.HeaderLines)
|
header = make([]string, 0, opts.HeaderLines)
|
||||||
go reader.restart(command)
|
go reader.restart(command, environ)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exitCode := ExitOk
|
||||||
|
stop := false
|
||||||
for {
|
for {
|
||||||
delay := true
|
delay := true
|
||||||
ticks++
|
ticks++
|
||||||
input := func() []rune {
|
input := func() []rune {
|
||||||
reloaded := snapshotRevision != inputRevision
|
|
||||||
paused, input := terminal.Input()
|
paused, input := terminal.Input()
|
||||||
if reloaded && paused {
|
if !paused {
|
||||||
query = []rune{}
|
|
||||||
} else if !paused {
|
|
||||||
query = input
|
query = input
|
||||||
}
|
}
|
||||||
return query
|
return query
|
||||||
@@ -263,11 +317,19 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
if reading {
|
if reading {
|
||||||
reader.terminate()
|
reader.terminate()
|
||||||
}
|
}
|
||||||
os.Exit(value.(int))
|
quitSignal := value.(quitSignal)
|
||||||
|
exitCode = quitSignal.code
|
||||||
|
err = quitSignal.err
|
||||||
|
stop = true
|
||||||
|
return
|
||||||
|
case EvtReadNone:
|
||||||
|
reading = false
|
||||||
|
terminal.UpdateCount(0, false, nil)
|
||||||
case EvtReadNew, EvtReadFin:
|
case EvtReadNew, EvtReadFin:
|
||||||
if evt == EvtReadFin && nextCommand != nil {
|
if evt == EvtReadFin && nextCommand != nil {
|
||||||
restart(*nextCommand)
|
restart(*nextCommand, nextEnviron)
|
||||||
nextCommand = nil
|
nextCommand = nil
|
||||||
|
nextEnviron = nil
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
reading = reading && evt == EvtReadNew
|
reading = reading && evt == EvtReadNew
|
||||||
@@ -276,27 +338,32 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
useSnapshot = false
|
useSnapshot = false
|
||||||
}
|
}
|
||||||
if !useSnapshot {
|
if !useSnapshot {
|
||||||
snapshot, count = chunkList.Snapshot()
|
if !snapshotRevision.compatible(inputRevision) {
|
||||||
|
query = []rune{}
|
||||||
|
}
|
||||||
|
var changed bool
|
||||||
|
snapshot, count, changed = chunkList.Snapshot(opts.Tail)
|
||||||
|
if changed {
|
||||||
|
inputRevision.bumpMinor()
|
||||||
|
}
|
||||||
snapshotRevision = inputRevision
|
snapshotRevision = inputRevision
|
||||||
}
|
}
|
||||||
total = count
|
total = count
|
||||||
terminal.UpdateCount(total, !reading, value.(*string))
|
terminal.UpdateCount(total, !reading, value.(*string))
|
||||||
if opts.Sync {
|
|
||||||
opts.Sync = false
|
|
||||||
terminal.UpdateList(PassMerger(&snapshot, opts.Tac, snapshotRevision))
|
|
||||||
}
|
|
||||||
if heightUnknown && !deferred {
|
if heightUnknown && !deferred {
|
||||||
determine(!reading)
|
determine(!reading)
|
||||||
}
|
}
|
||||||
matcher.Reset(snapshot, input(), false, !reading, sort, snapshotRevision)
|
matcher.Reset(snapshot, input(), false, !reading, sort, snapshotRevision)
|
||||||
|
|
||||||
case EvtSearchNew:
|
case EvtSearchNew:
|
||||||
var command *string
|
var command *commandSpec
|
||||||
|
var environ []string
|
||||||
var changed bool
|
var changed bool
|
||||||
switch val := value.(type) {
|
switch val := value.(type) {
|
||||||
case searchRequest:
|
case searchRequest:
|
||||||
sort = val.sort
|
sort = val.sort
|
||||||
command = val.command
|
command = val.command
|
||||||
|
environ = val.environ
|
||||||
changed = val.changed
|
changed = val.changed
|
||||||
if command != nil {
|
if command != nil {
|
||||||
useSnapshot = val.sync
|
useSnapshot = val.sync
|
||||||
@@ -306,18 +373,25 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
if reading {
|
if reading {
|
||||||
reader.terminate()
|
reader.terminate()
|
||||||
nextCommand = command
|
nextCommand = command
|
||||||
|
nextEnviron = environ
|
||||||
} else {
|
} else {
|
||||||
restart(*command)
|
restart(*command, environ)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !changed {
|
if !changed {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if !useSnapshot {
|
if !useSnapshot {
|
||||||
newSnapshot, _ := chunkList.Snapshot()
|
newSnapshot, newCount, changed := chunkList.Snapshot(opts.Tail)
|
||||||
|
if changed {
|
||||||
|
inputRevision.bumpMinor()
|
||||||
|
}
|
||||||
// We want to avoid showing empty list when reload is triggered
|
// We want to avoid showing empty list when reload is triggered
|
||||||
// and the query string is changed at the same time i.e. command != nil && changed
|
// and the query string is changed at the same time i.e. command != nil && changed
|
||||||
if command == nil || len(newSnapshot) > 0 {
|
if command == nil || newCount > 0 {
|
||||||
|
if snapshotRevision != inputRevision {
|
||||||
|
query = []rune{}
|
||||||
|
}
|
||||||
snapshot = newSnapshot
|
snapshot = newSnapshot
|
||||||
snapshotRevision = inputRevision
|
snapshotRevision = inputRevision
|
||||||
}
|
}
|
||||||
@@ -354,10 +428,11 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
opts.Printer(val.Get(i).item.AsString(opts.Ansi))
|
opts.Printer(val.Get(i).item.AsString(opts.Ansi))
|
||||||
}
|
}
|
||||||
if count > 0 {
|
if count == 0 {
|
||||||
os.Exit(exitOk)
|
exitCode = ExitNoMatch
|
||||||
}
|
}
|
||||||
os.Exit(exitNoMatch)
|
stop = true
|
||||||
|
return
|
||||||
}
|
}
|
||||||
determine(val.final)
|
determine(val.final)
|
||||||
}
|
}
|
||||||
@@ -368,6 +443,9 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
}
|
}
|
||||||
events.Clear()
|
events.Clear()
|
||||||
})
|
})
|
||||||
|
if stop {
|
||||||
|
break
|
||||||
|
}
|
||||||
if delay && reading {
|
if delay && reading {
|
||||||
dur := util.DurWithin(
|
dur := util.DurWithin(
|
||||||
time.Duration(ticks)*coordinatorDelayStep,
|
time.Duration(ticks)*coordinatorDelayStep,
|
||||||
@@ -375,4 +453,5 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
time.Sleep(dur)
|
time.Sleep(dur)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return exitCode, err
|
||||||
}
|
}
|
||||||
|
35
src/functions.go
Normal file
35
src/functions.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package fzf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func WriteTemporaryFile(data []string, printSep string) string {
|
||||||
|
f, err := os.CreateTemp("", "fzf-temp-*")
|
||||||
|
if err != nil {
|
||||||
|
// Unable to create temporary file
|
||||||
|
// FIXME: Should we terminate the program?
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
f.WriteString(strings.Join(data, printSep))
|
||||||
|
f.WriteString(printSep)
|
||||||
|
return f.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeFiles(files []string) {
|
||||||
|
for _, filename := range files {
|
||||||
|
os.Remove(filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringBytes(data string) []byte {
|
||||||
|
return unsafe.Slice(unsafe.StringData(data), len(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func byteString(data []byte) string {
|
||||||
|
return unsafe.String(unsafe.SliceData(data), len(data))
|
||||||
|
}
|
@@ -1,6 +1,8 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,7 +19,7 @@ func (item *Item) Index() int32 {
|
|||||||
return item.text.Index
|
return item.text.Index
|
||||||
}
|
}
|
||||||
|
|
||||||
var minItem = Item{text: util.Chars{Index: -1}}
|
var minItem = Item{text: util.Chars{Index: math.MinInt32}}
|
||||||
|
|
||||||
func (item *Item) TrimLength() uint16 {
|
func (item *Item) TrimLength() uint16 {
|
||||||
return item.text.TrimLength()
|
return item.text.TrimLength()
|
||||||
|
@@ -16,11 +16,12 @@ type MatchRequest struct {
|
|||||||
pattern *Pattern
|
pattern *Pattern
|
||||||
final bool
|
final bool
|
||||||
sort bool
|
sort bool
|
||||||
revision int
|
revision revision
|
||||||
}
|
}
|
||||||
|
|
||||||
// Matcher is responsible for performing search
|
// Matcher is responsible for performing search
|
||||||
type Matcher struct {
|
type Matcher struct {
|
||||||
|
cache *ChunkCache
|
||||||
patternBuilder func([]rune) *Pattern
|
patternBuilder func([]rune) *Pattern
|
||||||
sort bool
|
sort bool
|
||||||
tac bool
|
tac bool
|
||||||
@@ -29,7 +30,7 @@ type Matcher struct {
|
|||||||
partitions int
|
partitions int
|
||||||
slab []*util.Slab
|
slab []*util.Slab
|
||||||
mergerCache map[string]*Merger
|
mergerCache map[string]*Merger
|
||||||
revision int
|
revision revision
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -38,10 +39,11 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewMatcher returns a new Matcher
|
// NewMatcher returns a new Matcher
|
||||||
func NewMatcher(patternBuilder func([]rune) *Pattern,
|
func NewMatcher(cache *ChunkCache, patternBuilder func([]rune) *Pattern,
|
||||||
sort bool, tac bool, eventBox *util.EventBox, revision int) *Matcher {
|
sort bool, tac bool, eventBox *util.EventBox, revision revision) *Matcher {
|
||||||
partitions := util.Min(numPartitionsMultiplier*runtime.NumCPU(), maxPartitions)
|
partitions := util.Min(numPartitionsMultiplier*runtime.NumCPU(), maxPartitions)
|
||||||
return &Matcher{
|
return &Matcher{
|
||||||
|
cache: cache,
|
||||||
patternBuilder: patternBuilder,
|
patternBuilder: patternBuilder,
|
||||||
sort: sort,
|
sort: sort,
|
||||||
tac: tac,
|
tac: tac,
|
||||||
@@ -60,8 +62,13 @@ func (m *Matcher) Loop() {
|
|||||||
for {
|
for {
|
||||||
var request MatchRequest
|
var request MatchRequest
|
||||||
|
|
||||||
|
stop := false
|
||||||
m.reqBox.Wait(func(events *util.Events) {
|
m.reqBox.Wait(func(events *util.Events) {
|
||||||
for _, val := range *events {
|
for t, val := range *events {
|
||||||
|
if t == reqQuit {
|
||||||
|
stop = true
|
||||||
|
return
|
||||||
|
}
|
||||||
switch val := val.(type) {
|
switch val := val.(type) {
|
||||||
case MatchRequest:
|
case MatchRequest:
|
||||||
request = val
|
request = val
|
||||||
@@ -71,12 +78,19 @@ func (m *Matcher) Loop() {
|
|||||||
}
|
}
|
||||||
events.Clear()
|
events.Clear()
|
||||||
})
|
})
|
||||||
|
if stop {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheCleared := false
|
||||||
if request.sort != m.sort || request.revision != m.revision {
|
if request.sort != m.sort || request.revision != m.revision {
|
||||||
m.sort = request.sort
|
m.sort = request.sort
|
||||||
m.revision = request.revision
|
m.revision = request.revision
|
||||||
m.mergerCache = make(map[string]*Merger)
|
m.mergerCache = make(map[string]*Merger)
|
||||||
clearChunkCache()
|
if !request.revision.compatible(m.revision) {
|
||||||
|
m.cache.Clear()
|
||||||
|
}
|
||||||
|
cacheCleared = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart search
|
// Restart search
|
||||||
@@ -85,20 +99,20 @@ func (m *Matcher) Loop() {
|
|||||||
cancelled := false
|
cancelled := false
|
||||||
count := CountItems(request.chunks)
|
count := CountItems(request.chunks)
|
||||||
|
|
||||||
foundCache := false
|
if !cacheCleared {
|
||||||
if count == prevCount {
|
if count == prevCount {
|
||||||
// Look up mergerCache
|
// Look up mergerCache
|
||||||
if cached, found := m.mergerCache[patternString]; found {
|
if cached, found := m.mergerCache[patternString]; found {
|
||||||
foundCache = true
|
merger = cached
|
||||||
merger = cached
|
}
|
||||||
|
} else {
|
||||||
|
// Invalidate mergerCache
|
||||||
|
prevCount = count
|
||||||
|
m.mergerCache = make(map[string]*Merger)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Invalidate mergerCache
|
|
||||||
prevCount = count
|
|
||||||
m.mergerCache = make(map[string]*Merger)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !foundCache {
|
if merger == nil {
|
||||||
merger, cancelled = m.scan(request)
|
merger, cancelled = m.scan(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,6 +164,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
|||||||
return PassMerger(&request.chunks, m.tac, request.revision), false
|
return PassMerger(&request.chunks, m.tac, request.revision), false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
minIndex := request.chunks[0].items[0].Index()
|
||||||
cancelled := util.NewAtomicBool(false)
|
cancelled := util.NewAtomicBool(false)
|
||||||
|
|
||||||
slices := m.sliceChunks(request.chunks)
|
slices := m.sliceChunks(request.chunks)
|
||||||
@@ -180,7 +195,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
|||||||
for _, matches := range allMatches {
|
for _, matches := range allMatches {
|
||||||
sliceMatches = append(sliceMatches, matches...)
|
sliceMatches = append(sliceMatches, matches...)
|
||||||
}
|
}
|
||||||
if m.sort {
|
if m.sort && request.pattern.sortable {
|
||||||
if m.tac {
|
if m.tac {
|
||||||
sort.Sort(ByRelevanceTac(sliceMatches))
|
sort.Sort(ByRelevanceTac(sliceMatches))
|
||||||
} else {
|
} else {
|
||||||
@@ -221,11 +236,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, request.revision), false
|
return NewMerger(pattern, partialResults, m.sort && request.pattern.sortable, m.tac, request.revision, minIndex), 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, revision int) {
|
func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final bool, sort bool, revision revision) {
|
||||||
pattern := m.patternBuilder(patternRunes)
|
pattern := m.patternBuilder(patternRunes)
|
||||||
|
|
||||||
var event util.EventType
|
var event util.EventType
|
||||||
@@ -234,5 +249,9 @@ 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, revision})
|
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort, revision})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Matcher) Stop() {
|
||||||
|
m.reqBox.Set(reqQuit, nil)
|
||||||
}
|
}
|
||||||
|
@@ -3,8 +3,8 @@ package fzf
|
|||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
// EmptyMerger is a Merger with no data
|
// EmptyMerger is a Merger with no data
|
||||||
func EmptyMerger(revision int) *Merger {
|
func EmptyMerger(revision revision) *Merger {
|
||||||
return NewMerger(nil, [][]Result{}, false, false, revision)
|
return NewMerger(nil, [][]Result{}, false, false, revision, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@@ -20,19 +20,25 @@ type Merger struct {
|
|||||||
final bool
|
final bool
|
||||||
count int
|
count int
|
||||||
pass bool
|
pass bool
|
||||||
revision int
|
revision revision
|
||||||
|
minIndex int32
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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, revision int) *Merger {
|
func PassMerger(chunks *[]*Chunk, tac bool, revision revision) *Merger {
|
||||||
|
var minIndex int32
|
||||||
|
if len(*chunks) > 0 {
|
||||||
|
minIndex = (*chunks)[0].items[0].Index()
|
||||||
|
}
|
||||||
mg := Merger{
|
mg := Merger{
|
||||||
pattern: nil,
|
pattern: nil,
|
||||||
chunks: chunks,
|
chunks: chunks,
|
||||||
tac: tac,
|
tac: tac,
|
||||||
count: 0,
|
count: 0,
|
||||||
pass: true,
|
pass: true,
|
||||||
revision: revision}
|
revision: revision,
|
||||||
|
minIndex: minIndex}
|
||||||
|
|
||||||
for _, chunk := range *mg.chunks {
|
for _, chunk := range *mg.chunks {
|
||||||
mg.count += chunk.count
|
mg.count += chunk.count
|
||||||
@@ -41,7 +47,7 @@ func PassMerger(chunks *[]*Chunk, tac bool, revision int) *Merger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewMerger returns a new Merger
|
// NewMerger returns a new Merger
|
||||||
func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool, revision int) *Merger {
|
func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool, revision revision, minIndex int32) *Merger {
|
||||||
mg := Merger{
|
mg := Merger{
|
||||||
pattern: pattern,
|
pattern: pattern,
|
||||||
lists: lists,
|
lists: lists,
|
||||||
@@ -52,7 +58,8 @@ func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool, revisi
|
|||||||
tac: tac,
|
tac: tac,
|
||||||
final: false,
|
final: false,
|
||||||
count: 0,
|
count: 0,
|
||||||
revision: revision}
|
revision: revision,
|
||||||
|
minIndex: minIndex}
|
||||||
|
|
||||||
for _, list := range mg.lists {
|
for _, list := range mg.lists {
|
||||||
mg.count += len(list)
|
mg.count += len(list)
|
||||||
@@ -61,7 +68,7 @@ func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool, revisi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Revision returns revision number
|
// Revision returns revision number
|
||||||
func (mg *Merger) Revision() int {
|
func (mg *Merger) Revision() revision {
|
||||||
return mg.revision
|
return mg.revision
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +88,7 @@ func (mg *Merger) First() Result {
|
|||||||
func (mg *Merger) FindIndex(itemIndex int32) int {
|
func (mg *Merger) FindIndex(itemIndex int32) int {
|
||||||
index := -1
|
index := -1
|
||||||
if mg.pass {
|
if mg.pass {
|
||||||
index = int(itemIndex)
|
index = int(itemIndex - mg.minIndex)
|
||||||
if mg.tac {
|
if mg.tac {
|
||||||
index = mg.count - index - 1
|
index = mg.count - index - 1
|
||||||
}
|
}
|
||||||
@@ -102,6 +109,13 @@ func (mg *Merger) Get(idx int) Result {
|
|||||||
if mg.tac {
|
if mg.tac {
|
||||||
idx = mg.count - idx - 1
|
idx = mg.count - idx - 1
|
||||||
}
|
}
|
||||||
|
firstChunk := (*mg.chunks)[0]
|
||||||
|
if firstChunk.count < chunkSize && idx >= firstChunk.count {
|
||||||
|
idx -= firstChunk.count
|
||||||
|
|
||||||
|
chunk := (*mg.chunks)[idx/chunkSize+1]
|
||||||
|
return Result{item: &chunk.items[idx%chunkSize]}
|
||||||
|
}
|
||||||
chunk := (*mg.chunks)[idx/chunkSize]
|
chunk := (*mg.chunks)[idx/chunkSize]
|
||||||
return Result{item: &chunk.items[idx%chunkSize]}
|
return Result{item: &chunk.items[idx%chunkSize]}
|
||||||
}
|
}
|
||||||
|
@@ -23,10 +23,11 @@ func randResult() Result {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEmptyMerger(t *testing.T) {
|
func TestEmptyMerger(t *testing.T) {
|
||||||
assert(t, EmptyMerger(0).Length() == 0, "Not empty")
|
r := revision{}
|
||||||
assert(t, EmptyMerger(0).count == 0, "Invalid count")
|
assert(t, EmptyMerger(r).Length() == 0, "Not empty")
|
||||||
assert(t, len(EmptyMerger(0).lists) == 0, "Invalid lists")
|
assert(t, EmptyMerger(r).count == 0, "Invalid count")
|
||||||
assert(t, len(EmptyMerger(0).merged) == 0, "Invalid merged list")
|
assert(t, len(EmptyMerger(r).lists) == 0, "Invalid lists")
|
||||||
|
assert(t, len(EmptyMerger(r).merged) == 0, "Invalid merged list")
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildLists(partiallySorted bool) ([][]Result, []Result) {
|
func buildLists(partiallySorted bool) ([][]Result, []Result) {
|
||||||
@@ -57,7 +58,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, 0)
|
mg := NewMerger(nil, lists, false, false, revision{}, 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 +70,7 @@ func TestMergerSorted(t *testing.T) {
|
|||||||
cnt := len(items)
|
cnt := len(items)
|
||||||
|
|
||||||
// Sorted sorted order
|
// Sorted sorted order
|
||||||
mg := NewMerger(nil, lists, true, false, 0)
|
mg := NewMerger(nil, lists, true, false, revision{}, 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 +80,7 @@ func TestMergerSorted(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Inverse order
|
// Inverse order
|
||||||
mg2 := NewMerger(nil, lists, true, false, 0)
|
mg2 := NewMerger(nil, lists, true, false, revision{}, 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))
|
||||||
|
1715
src/options.go
1715
src/options.go
File diff suppressed because it is too large
Load Diff
13
src/options_no_pprof.go
Normal file
13
src/options_no_pprof.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
//go:build !pprof
|
||||||
|
// +build !pprof
|
||||||
|
|
||||||
|
package fzf
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
func (o *Options) initProfiling() error {
|
||||||
|
if o.CPUProfile != "" || o.MEMProfile != "" || o.BlockProfile != "" || o.MutexProfile != "" {
|
||||||
|
return errors.New("error: profiling not supported: FZF must be built with '-tags=pprof' to enable profiling")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
73
src/options_pprof.go
Normal file
73
src/options_pprof.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
//go:build pprof
|
||||||
|
// +build pprof
|
||||||
|
|
||||||
|
package fzf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"runtime/pprof"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (o *Options) initProfiling() error {
|
||||||
|
if o.CPUProfile != "" {
|
||||||
|
f, err := os.Create(o.CPUProfile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create CPU profile: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := pprof.StartCPUProfile(f); err != nil {
|
||||||
|
return fmt.Errorf("could not start CPU profile: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
util.AtExit(func() {
|
||||||
|
pprof.StopCPUProfile()
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "Error: closing cpu profile:", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
stopProfile := func(name string, f *os.File) {
|
||||||
|
if err := pprof.Lookup(name).WriteTo(f, 0); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error: could not write %s profile: %v\n", name, err)
|
||||||
|
}
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error: closing %s profile: %v\n", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.MEMProfile != "" {
|
||||||
|
f, err := os.Create(o.MEMProfile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create MEM profile: %w", err)
|
||||||
|
}
|
||||||
|
util.AtExit(func() {
|
||||||
|
runtime.GC()
|
||||||
|
stopProfile("allocs", f)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.BlockProfile != "" {
|
||||||
|
runtime.SetBlockProfileRate(1)
|
||||||
|
f, err := os.Create(o.BlockProfile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create BLOCK profile: %w", err)
|
||||||
|
}
|
||||||
|
util.AtExit(func() { stopProfile("block", f) })
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.MutexProfile != "" {
|
||||||
|
runtime.SetMutexProfileFraction(1)
|
||||||
|
f, err := os.Create(o.MutexProfile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not create MUTEX profile: %w", err)
|
||||||
|
}
|
||||||
|
util.AtExit(func() { stopProfile("mutex", f) })
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
89
src/options_pprof_test.go
Normal file
89
src/options_pprof_test.go
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
//go:build pprof
|
||||||
|
// +build pprof
|
||||||
|
|
||||||
|
package fzf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// runInitProfileTests is an internal flag used TestInitProfiling
|
||||||
|
var runInitProfileTests = flag.Bool("test-init-profile", false, "run init profile tests")
|
||||||
|
|
||||||
|
func TestInitProfiling(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("short test")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run this test in a separate process since it interferes with
|
||||||
|
// profiling and modifies the global atexit state. Without this
|
||||||
|
// running `go test -bench . -cpuprofile cpu.out` will fail.
|
||||||
|
if !*runInitProfileTests {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Make sure we are not the child process.
|
||||||
|
if os.Getenv("_FZF_CHILD_PROC") != "" {
|
||||||
|
t.Fatal("already running as child process!")
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(os.Args[0],
|
||||||
|
"-test.timeout", "30s",
|
||||||
|
"-test.run", "^"+t.Name()+"$",
|
||||||
|
"-test-init-profile",
|
||||||
|
)
|
||||||
|
cmd.Env = append(os.Environ(), "_FZF_CHILD_PROC=1")
|
||||||
|
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
out = bytes.TrimSpace(out)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Child test process failed: %v:\n%s", err, out)
|
||||||
|
}
|
||||||
|
// Make sure the test actually ran
|
||||||
|
if bytes.Contains(out, []byte("no tests to run")) {
|
||||||
|
t.Fatalf("Failed to run test %q:\n%s", t.Name(), out)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Child process
|
||||||
|
|
||||||
|
tempdir := t.TempDir()
|
||||||
|
t.Cleanup(util.RunAtExitFuncs)
|
||||||
|
|
||||||
|
o := Options{
|
||||||
|
CPUProfile: filepath.Join(tempdir, "cpu.prof"),
|
||||||
|
MEMProfile: filepath.Join(tempdir, "mem.prof"),
|
||||||
|
BlockProfile: filepath.Join(tempdir, "block.prof"),
|
||||||
|
MutexProfile: filepath.Join(tempdir, "mutex.prof"),
|
||||||
|
}
|
||||||
|
if err := o.initProfiling(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
profiles := []string{
|
||||||
|
o.CPUProfile,
|
||||||
|
o.MEMProfile,
|
||||||
|
o.BlockProfile,
|
||||||
|
o.MutexProfile,
|
||||||
|
}
|
||||||
|
for _, name := range profiles {
|
||||||
|
if _, err := os.Stat(name); err != nil {
|
||||||
|
t.Errorf("Failed to create profile %s: %v", filepath.Base(name), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
util.RunAtExitFuncs()
|
||||||
|
|
||||||
|
for _, name := range profiles {
|
||||||
|
if _, err := os.Stat(name); err != nil {
|
||||||
|
t.Errorf("Failed to write profile %s: %v", filepath.Base(name), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -80,7 +80,7 @@ func TestDelimiterRegexRegexCaret(t *testing.T) {
|
|||||||
|
|
||||||
func TestSplitNth(t *testing.T) {
|
func TestSplitNth(t *testing.T) {
|
||||||
{
|
{
|
||||||
ranges := splitNth("..")
|
ranges, _ := splitNth("..")
|
||||||
if len(ranges) != 1 ||
|
if len(ranges) != 1 ||
|
||||||
ranges[0].begin != rangeEllipsis ||
|
ranges[0].begin != rangeEllipsis ||
|
||||||
ranges[0].end != rangeEllipsis {
|
ranges[0].end != rangeEllipsis {
|
||||||
@@ -88,7 +88,7 @@ func TestSplitNth(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
ranges := splitNth("..3,1..,2..3,4..-1,-3..-2,..,2,-2,2..-2,1..-1")
|
ranges, _ := splitNth("..3,1..,2..3,4..-1,-3..-2,..,2,-2,2..-2,1..-1")
|
||||||
if len(ranges) != 10 ||
|
if len(ranges) != 10 ||
|
||||||
ranges[0].begin != rangeEllipsis || ranges[0].end != 3 ||
|
ranges[0].begin != rangeEllipsis || ranges[0].end != 3 ||
|
||||||
ranges[1].begin != rangeEllipsis || ranges[1].end != rangeEllipsis ||
|
ranges[1].begin != rangeEllipsis || ranges[1].end != rangeEllipsis ||
|
||||||
@@ -106,10 +106,11 @@ func TestSplitNth(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIrrelevantNth(t *testing.T) {
|
func TestIrrelevantNth(t *testing.T) {
|
||||||
|
index := 0
|
||||||
{
|
{
|
||||||
opts := defaultOptions()
|
opts := defaultOptions()
|
||||||
words := []string{"--nth", "..", "-x"}
|
words := []string{"--nth", "..", "-x"}
|
||||||
parseOptions(opts, words)
|
parseOptions(&index, opts, words)
|
||||||
postProcessOptions(opts)
|
postProcessOptions(opts)
|
||||||
if len(opts.Nth) != 0 {
|
if len(opts.Nth) != 0 {
|
||||||
t.Errorf("nth should be empty: %v", opts.Nth)
|
t.Errorf("nth should be empty: %v", opts.Nth)
|
||||||
@@ -118,7 +119,7 @@ func TestIrrelevantNth(t *testing.T) {
|
|||||||
for _, words := range [][]string{{"--nth", "..,3", "+x"}, {"--nth", "3,1..", "+x"}, {"--nth", "..-1,1", "+x"}} {
|
for _, words := range [][]string{{"--nth", "..,3", "+x"}, {"--nth", "3,1..", "+x"}, {"--nth", "..-1,1", "+x"}} {
|
||||||
{
|
{
|
||||||
opts := defaultOptions()
|
opts := defaultOptions()
|
||||||
parseOptions(opts, words)
|
parseOptions(&index, opts, words)
|
||||||
postProcessOptions(opts)
|
postProcessOptions(opts)
|
||||||
if len(opts.Nth) != 0 {
|
if len(opts.Nth) != 0 {
|
||||||
t.Errorf("nth should be empty: %v", opts.Nth)
|
t.Errorf("nth should be empty: %v", opts.Nth)
|
||||||
@@ -127,7 +128,7 @@ func TestIrrelevantNth(t *testing.T) {
|
|||||||
{
|
{
|
||||||
opts := defaultOptions()
|
opts := defaultOptions()
|
||||||
words = append(words, "-x")
|
words = append(words, "-x")
|
||||||
parseOptions(opts, words)
|
parseOptions(&index, opts, words)
|
||||||
postProcessOptions(opts)
|
postProcessOptions(opts)
|
||||||
if len(opts.Nth) != 2 {
|
if len(opts.Nth) != 2 {
|
||||||
t.Errorf("nth should not be empty: %v", opts.Nth)
|
t.Errorf("nth should not be empty: %v", opts.Nth)
|
||||||
@@ -137,7 +138,7 @@ func TestIrrelevantNth(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseKeys(t *testing.T) {
|
func TestParseKeys(t *testing.T) {
|
||||||
pairs := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ctrl-alt-a,ALT-enter,alt-SPACE", "")
|
pairs, _ := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ctrl-alt-a,ALT-enter,alt-SPACE", "")
|
||||||
checkEvent := func(e tui.Event, s string) {
|
checkEvent := func(e tui.Event, s string) {
|
||||||
if pairs[e] != s {
|
if pairs[e] != s {
|
||||||
t.Errorf("%s != %s", pairs[e], s)
|
t.Errorf("%s != %s", pairs[e], s)
|
||||||
@@ -163,35 +164,35 @@ func TestParseKeys(t *testing.T) {
|
|||||||
checkEvent(tui.AltKey(' '), "alt-SPACE")
|
checkEvent(tui.AltKey(' '), "alt-SPACE")
|
||||||
|
|
||||||
// Synonyms
|
// Synonyms
|
||||||
pairs = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
|
pairs, _ = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
|
||||||
if len(pairs) != 9 {
|
if len(pairs) != 9 {
|
||||||
t.Error(9)
|
t.Error(9)
|
||||||
}
|
}
|
||||||
check(tui.CtrlM, "Return")
|
check(tui.CtrlM, "Return")
|
||||||
checkEvent(tui.Key(' '), "space")
|
checkEvent(tui.Key(' '), "space")
|
||||||
check(tui.Tab, "tab")
|
check(tui.Tab, "tab")
|
||||||
check(tui.BTab, "btab")
|
check(tui.ShiftTab, "btab")
|
||||||
check(tui.ESC, "esc")
|
check(tui.Esc, "esc")
|
||||||
check(tui.Up, "up")
|
check(tui.Up, "up")
|
||||||
check(tui.Down, "down")
|
check(tui.Down, "down")
|
||||||
check(tui.Left, "left")
|
check(tui.Left, "left")
|
||||||
check(tui.Right, "right")
|
check(tui.Right, "right")
|
||||||
|
|
||||||
pairs = parseKeyChords("Tab,Ctrl-I,PgUp,page-up,pgdn,Page-Down,Home,End,Alt-BS,Alt-BSpace,shift-left,shift-right,btab,shift-tab,return,Enter,bspace", "")
|
pairs, _ = parseKeyChords("Tab,Ctrl-I,PgUp,page-up,pgdn,Page-Down,Home,End,Alt-BS,Alt-BSpace,shift-left,shift-right,btab,shift-tab,return,Enter,bspace", "")
|
||||||
if len(pairs) != 11 {
|
if len(pairs) != 11 {
|
||||||
t.Error(11)
|
t.Error(11)
|
||||||
}
|
}
|
||||||
check(tui.Tab, "Ctrl-I")
|
check(tui.Tab, "Ctrl-I")
|
||||||
check(tui.PgUp, "page-up")
|
check(tui.PageUp, "page-up")
|
||||||
check(tui.PgDn, "Page-Down")
|
check(tui.PageDown, "Page-Down")
|
||||||
check(tui.Home, "Home")
|
check(tui.Home, "Home")
|
||||||
check(tui.End, "End")
|
check(tui.End, "End")
|
||||||
check(tui.AltBS, "Alt-BSpace")
|
check(tui.AltBackspace, "Alt-BSpace")
|
||||||
check(tui.SLeft, "shift-left")
|
check(tui.ShiftLeft, "shift-left")
|
||||||
check(tui.SRight, "shift-right")
|
check(tui.ShiftRight, "shift-right")
|
||||||
check(tui.BTab, "shift-tab")
|
check(tui.ShiftTab, "shift-tab")
|
||||||
check(tui.CtrlM, "Enter")
|
check(tui.CtrlM, "Enter")
|
||||||
check(tui.BSpace, "bspace")
|
check(tui.Backspace, "bspace")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseKeysWithComma(t *testing.T) {
|
func TestParseKeysWithComma(t *testing.T) {
|
||||||
@@ -206,40 +207,40 @@ func TestParseKeysWithComma(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pairs := parseKeyChords(",", "")
|
pairs, _ := parseKeyChords(",", "")
|
||||||
checkN(len(pairs), 1)
|
checkN(len(pairs), 1)
|
||||||
check(pairs, tui.Key(','), ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs = parseKeyChords(",,a,b", "")
|
pairs, _ = parseKeyChords(",,a,b", "")
|
||||||
checkN(len(pairs), 3)
|
checkN(len(pairs), 3)
|
||||||
check(pairs, tui.Key('a'), "a")
|
check(pairs, tui.Key('a'), "a")
|
||||||
check(pairs, tui.Key('b'), "b")
|
check(pairs, tui.Key('b'), "b")
|
||||||
check(pairs, tui.Key(','), ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs = parseKeyChords("a,b,,", "")
|
pairs, _ = parseKeyChords("a,b,,", "")
|
||||||
checkN(len(pairs), 3)
|
checkN(len(pairs), 3)
|
||||||
check(pairs, tui.Key('a'), "a")
|
check(pairs, tui.Key('a'), "a")
|
||||||
check(pairs, tui.Key('b'), "b")
|
check(pairs, tui.Key('b'), "b")
|
||||||
check(pairs, tui.Key(','), ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs = parseKeyChords("a,,,b", "")
|
pairs, _ = parseKeyChords("a,,,b", "")
|
||||||
checkN(len(pairs), 3)
|
checkN(len(pairs), 3)
|
||||||
check(pairs, tui.Key('a'), "a")
|
check(pairs, tui.Key('a'), "a")
|
||||||
check(pairs, tui.Key('b'), "b")
|
check(pairs, tui.Key('b'), "b")
|
||||||
check(pairs, tui.Key(','), ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs = parseKeyChords("a,,,b,c", "")
|
pairs, _ = parseKeyChords("a,,,b,c", "")
|
||||||
checkN(len(pairs), 4)
|
checkN(len(pairs), 4)
|
||||||
check(pairs, tui.Key('a'), "a")
|
check(pairs, tui.Key('a'), "a")
|
||||||
check(pairs, tui.Key('b'), "b")
|
check(pairs, tui.Key('b'), "b")
|
||||||
check(pairs, tui.Key('c'), "c")
|
check(pairs, tui.Key('c'), "c")
|
||||||
check(pairs, tui.Key(','), ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs = parseKeyChords(",,,", "")
|
pairs, _ = parseKeyChords(",,,", "")
|
||||||
checkN(len(pairs), 1)
|
checkN(len(pairs), 1)
|
||||||
check(pairs, tui.Key(','), ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs = parseKeyChords(",ALT-,,", "")
|
pairs, _ = parseKeyChords(",ALT-,,", "")
|
||||||
checkN(len(pairs), 1)
|
checkN(len(pairs), 1)
|
||||||
check(pairs, tui.AltKey(','), "ALT-,")
|
check(pairs, tui.AltKey(','), "ALT-,")
|
||||||
}
|
}
|
||||||
@@ -262,17 +263,13 @@ 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 \n{+})+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", errorFn)
|
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up")
|
||||||
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)
|
||||||
@@ -290,20 +287,17 @@ 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), errorFn)
|
parseKeymap(keymap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char))
|
||||||
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", errorFn)
|
parseKeymap(keymap, "f1:abort")
|
||||||
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) {
|
||||||
theme := tui.Dark256
|
theme := tui.Dark256
|
||||||
dark := parseTheme(theme, "dark")
|
dark, _ := parseTheme(theme, "dark")
|
||||||
if *dark != *theme {
|
if *dark != *theme {
|
||||||
t.Errorf("colors should be equivalent")
|
t.Errorf("colors should be equivalent")
|
||||||
}
|
}
|
||||||
@@ -311,7 +305,7 @@ func TestColorSpec(t *testing.T) {
|
|||||||
t.Errorf("point should not be equivalent")
|
t.Errorf("point should not be equivalent")
|
||||||
}
|
}
|
||||||
|
|
||||||
light := parseTheme(theme, "dark,light")
|
light, _ := parseTheme(theme, "dark,light")
|
||||||
if *light == *theme {
|
if *light == *theme {
|
||||||
t.Errorf("should not be equivalent")
|
t.Errorf("should not be equivalent")
|
||||||
}
|
}
|
||||||
@@ -322,7 +316,7 @@ func TestColorSpec(t *testing.T) {
|
|||||||
t.Errorf("point should not be equivalent")
|
t.Errorf("point should not be equivalent")
|
||||||
}
|
}
|
||||||
|
|
||||||
customized := parseTheme(theme, "fg:231,bg:232")
|
customized, _ := parseTheme(theme, "fg:231,bg:232")
|
||||||
if customized.Fg.Color != 231 || customized.Bg.Color != 232 {
|
if customized.Fg.Color != 231 || customized.Bg.Color != 232 {
|
||||||
t.Errorf("color not customized")
|
t.Errorf("color not customized")
|
||||||
}
|
}
|
||||||
@@ -335,17 +329,18 @@ func TestColorSpec(t *testing.T) {
|
|||||||
t.Errorf("colors should now be equivalent: %v, %v", tui.Dark256, customized)
|
t.Errorf("colors should now be equivalent: %v, %v", tui.Dark256, customized)
|
||||||
}
|
}
|
||||||
|
|
||||||
customized = parseTheme(theme, "fg:231,dark,bg:232")
|
customized, _ = parseTheme(theme, "fg:231,dark,bg:232")
|
||||||
if customized.Fg != tui.Dark256.Fg || customized.Bg == tui.Dark256.Bg {
|
if customized.Fg != tui.Dark256.Fg || customized.Bg == tui.Dark256.Bg {
|
||||||
t.Errorf("color not customized")
|
t.Errorf("color not customized")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultCtrlNP(t *testing.T) {
|
func TestDefaultCtrlNP(t *testing.T) {
|
||||||
|
index := 0
|
||||||
check := func(words []string, et tui.EventType, expected actionType) {
|
check := func(words []string, et tui.EventType, expected actionType) {
|
||||||
e := et.AsEvent()
|
e := et.AsEvent()
|
||||||
opts := defaultOptions()
|
opts := defaultOptions()
|
||||||
parseOptions(opts, words)
|
parseOptions(&index, opts, words)
|
||||||
postProcessOptions(opts)
|
postProcessOptions(opts)
|
||||||
if opts.Keymap[e][0].t != expected {
|
if opts.Keymap[e][0].t != expected {
|
||||||
t.Error()
|
t.Error()
|
||||||
@@ -371,8 +366,9 @@ func TestDefaultCtrlNP(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func optsFor(words ...string) *Options {
|
func optsFor(words ...string) *Options {
|
||||||
|
index := 0
|
||||||
opts := defaultOptions()
|
opts := defaultOptions()
|
||||||
parseOptions(opts, words)
|
parseOptions(&index, opts, words)
|
||||||
postProcessOptions(opts)
|
postProcessOptions(opts)
|
||||||
return opts
|
return opts
|
||||||
}
|
}
|
||||||
@@ -458,7 +454,6 @@ func TestValidateSign(t *testing.T) {
|
|||||||
{"> ", true},
|
{"> ", true},
|
||||||
{"아", true},
|
{"아", true},
|
||||||
{"😀", true},
|
{"😀", true},
|
||||||
{"", false},
|
|
||||||
{">>>", false},
|
{">>>", false},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -475,7 +470,7 @@ func TestValidateSign(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseSingleActionList(t *testing.T) {
|
func TestParseSingleActionList(t *testing.T) {
|
||||||
actions := parseSingleActionList("Execute@foo+bar,baz@+up+up+reload:down+down", func(string) {})
|
actions, _ := parseSingleActionList("Execute@foo+bar,baz@+up+up+reload:down+down")
|
||||||
if len(actions) != 4 {
|
if len(actions) != 4 {
|
||||||
t.Errorf("Invalid number of actions parsed:%d", len(actions))
|
t.Errorf("Invalid number of actions parsed:%d", len(actions))
|
||||||
}
|
}
|
||||||
@@ -491,11 +486,8 @@ func TestParseSingleActionList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseSingleActionListError(t *testing.T) {
|
func TestParseSingleActionListError(t *testing.T) {
|
||||||
err := ""
|
_, err := parseSingleActionList("change-query(foobar)baz")
|
||||||
parseSingleActionList("change-query(foobar)baz", func(e string) {
|
if err == nil {
|
||||||
err = e
|
|
||||||
})
|
|
||||||
if len(err) == 0 {
|
|
||||||
t.Errorf("Failed to detect error")
|
t.Errorf("Failed to detect error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -60,32 +60,17 @@ type Pattern struct {
|
|||||||
delimiter Delimiter
|
delimiter Delimiter
|
||||||
nth []Range
|
nth []Range
|
||||||
procFun map[termType]algo.Algo
|
procFun map[termType]algo.Algo
|
||||||
|
cache *ChunkCache
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var _splitRegex *regexp.Regexp
|
||||||
_patternCache map[string]*Pattern
|
|
||||||
_splitRegex *regexp.Regexp
|
|
||||||
_cache ChunkCache
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
_splitRegex = regexp.MustCompile(" +")
|
_splitRegex = regexp.MustCompile(" +")
|
||||||
clearPatternCache()
|
|
||||||
clearChunkCache()
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearPatternCache() {
|
|
||||||
// We can uniquely identify the pattern for a given string since
|
|
||||||
// search mode and caseMode do not change while the program is running
|
|
||||||
_patternCache = make(map[string]*Pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearChunkCache() {
|
|
||||||
_cache = NewChunkCache()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool,
|
||||||
withPos 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
|
||||||
@@ -98,7 +83,9 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
|||||||
asString = string(runes)
|
asString = string(runes)
|
||||||
}
|
}
|
||||||
|
|
||||||
cached, found := _patternCache[asString]
|
// We can uniquely identify the pattern for a given string since
|
||||||
|
// search mode and caseMode do not change while the program is running
|
||||||
|
cached, found := patternCache[asString]
|
||||||
if found {
|
if found {
|
||||||
return cached
|
return cached
|
||||||
}
|
}
|
||||||
@@ -153,6 +140,7 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
|||||||
cacheable: cacheable,
|
cacheable: cacheable,
|
||||||
nth: nth,
|
nth: nth,
|
||||||
delimiter: delimiter,
|
delimiter: delimiter,
|
||||||
|
cache: cache,
|
||||||
procFun: make(map[termType]algo.Algo)}
|
procFun: make(map[termType]algo.Algo)}
|
||||||
|
|
||||||
ptr.cacheKey = ptr.buildCacheKey()
|
ptr.cacheKey = ptr.buildCacheKey()
|
||||||
@@ -162,19 +150,19 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
|||||||
ptr.procFun[termPrefix] = algo.PrefixMatch
|
ptr.procFun[termPrefix] = algo.PrefixMatch
|
||||||
ptr.procFun[termSuffix] = algo.SuffixMatch
|
ptr.procFun[termSuffix] = algo.SuffixMatch
|
||||||
|
|
||||||
_patternCache[asString] = ptr
|
patternCache[asString] = ptr
|
||||||
return ptr
|
return ptr
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet {
|
func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet {
|
||||||
str = strings.Replace(str, "\\ ", "\t", -1)
|
str = strings.ReplaceAll(str, "\\ ", "\t")
|
||||||
tokens := _splitRegex.Split(str, -1)
|
tokens := _splitRegex.Split(str, -1)
|
||||||
sets := []termSet{}
|
sets := []termSet{}
|
||||||
set := termSet{}
|
set := termSet{}
|
||||||
switchSet := false
|
switchSet := false
|
||||||
afterBar := false
|
afterBar := false
|
||||||
for _, token := range tokens {
|
for _, token := range tokens {
|
||||||
typ, inv, text := termFuzzy, false, strings.Replace(token, "\t", " ", -1)
|
typ, inv, text := termFuzzy, false, strings.ReplaceAll(token, "\t", " ")
|
||||||
lowerText := strings.ToLower(text)
|
lowerText := strings.ToLower(text)
|
||||||
caseSensitive := caseMode == CaseRespect ||
|
caseSensitive := caseMode == CaseRespect ||
|
||||||
caseMode == CaseSmart && text != lowerText
|
caseMode == CaseSmart && text != lowerText
|
||||||
@@ -209,11 +197,10 @@ func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet
|
|||||||
// Flip exactness
|
// Flip exactness
|
||||||
if fuzzy && !inv {
|
if fuzzy && !inv {
|
||||||
typ = termExact
|
typ = termExact
|
||||||
text = text[1:]
|
|
||||||
} else {
|
} else {
|
||||||
typ = termFuzzy
|
typ = termFuzzy
|
||||||
text = text[1:]
|
|
||||||
}
|
}
|
||||||
|
text = text[1:]
|
||||||
} else if strings.HasPrefix(text, "^") {
|
} else if strings.HasPrefix(text, "^") {
|
||||||
if typ == termSuffix {
|
if typ == termSuffix {
|
||||||
typ = termEqual
|
typ = termEqual
|
||||||
@@ -283,18 +270,18 @@ func (p *Pattern) Match(chunk *Chunk, slab *util.Slab) []Result {
|
|||||||
// ChunkCache: Exact match
|
// ChunkCache: Exact match
|
||||||
cacheKey := p.CacheKey()
|
cacheKey := p.CacheKey()
|
||||||
if p.cacheable {
|
if p.cacheable {
|
||||||
if cached := _cache.Lookup(chunk, cacheKey); cached != nil {
|
if cached := p.cache.Lookup(chunk, cacheKey); cached != nil {
|
||||||
return cached
|
return cached
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prefix/suffix cache
|
// Prefix/suffix cache
|
||||||
space := _cache.Search(chunk, cacheKey)
|
space := p.cache.Search(chunk, cacheKey)
|
||||||
|
|
||||||
matches := p.matchChunk(chunk, space, slab)
|
matches := p.matchChunk(chunk, space, slab)
|
||||||
|
|
||||||
if p.cacheable {
|
if p.cacheable {
|
||||||
_cache.Add(chunk, cacheKey, matches)
|
p.cache.Add(chunk, cacheKey, matches)
|
||||||
}
|
}
|
||||||
return matches
|
return matches
|
||||||
}
|
}
|
||||||
|
@@ -64,10 +64,15 @@ func TestParseTermsEmpty(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool,
|
||||||
|
withPos bool, cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
|
||||||
|
return BuildPattern(NewChunkCache(), make(map[string]*Pattern),
|
||||||
|
fuzzy, fuzzyAlgo, extended, caseMode, normalize, forward,
|
||||||
|
withPos, cacheable, nth, delimiter, runes)
|
||||||
|
}
|
||||||
|
|
||||||
func TestExact(t *testing.T) {
|
func TestExact(t *testing.T) {
|
||||||
defer clearPatternCache()
|
pattern := buildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true,
|
||||||
clearPatternCache()
|
|
||||||
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(
|
||||||
@@ -81,9 +86,7 @@ func TestExact(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEqual(t *testing.T) {
|
func TestEqual(t *testing.T) {
|
||||||
defer clearPatternCache()
|
pattern := buildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("^AbC$"))
|
||||||
clearPatternCache()
|
|
||||||
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))
|
||||||
@@ -104,19 +107,12 @@ func TestEqual(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCaseSensitivity(t *testing.T) {
|
func TestCaseSensitivity(t *testing.T) {
|
||||||
defer clearPatternCache()
|
pat1 := buildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("abc"))
|
||||||
clearPatternCache()
|
pat2 := buildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc"))
|
||||||
pat1 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("abc"))
|
pat3 := buildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, false, true, []Range{}, Delimiter{}, []rune("abc"))
|
||||||
clearPatternCache()
|
pat4 := buildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc"))
|
||||||
pat2 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc"))
|
pat5 := buildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, false, true, []Range{}, Delimiter{}, []rune("abc"))
|
||||||
clearPatternCache()
|
pat6 := buildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc"))
|
||||||
pat3 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, false, true, []Range{}, Delimiter{}, []rune("abc"))
|
|
||||||
clearPatternCache()
|
|
||||||
pat4 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc"))
|
|
||||||
clearPatternCache()
|
|
||||||
pat5 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, false, true, []Range{}, Delimiter{}, []rune("abc"))
|
|
||||||
clearPatternCache()
|
|
||||||
pat6 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, 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 +125,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, false, 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}})
|
||||||
|
|
||||||
@@ -163,15 +159,13 @@ 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()
|
pat := buildPattern(true, algo.FuzzyMatchV2, extended, CaseSmart, false, true, false, 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())
|
||||||
}
|
}
|
||||||
if pat.cacheable != cacheable {
|
if pat.cacheable != cacheable {
|
||||||
t.Errorf("Expected: %t, actual: %t (%s)", cacheable, pat.cacheable, patStr)
|
t.Errorf("Expected: %t, actual: %t (%s)", cacheable, pat.cacheable, patStr)
|
||||||
}
|
}
|
||||||
clearPatternCache()
|
|
||||||
}
|
}
|
||||||
test(false, "foo !bar", "foo !bar", true)
|
test(false, "foo !bar", "foo !bar", true)
|
||||||
test(false, "foo | bar !baz", "foo | bar !baz", true)
|
test(false, "foo | bar !baz", "foo | bar !baz", true)
|
||||||
@@ -187,15 +181,13 @@ 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()
|
pat := buildPattern(fuzzy, algo.FuzzyMatchV2, true, CaseSmart, true, true, false, 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())
|
||||||
}
|
}
|
||||||
if cacheable != pat.cacheable {
|
if cacheable != pat.cacheable {
|
||||||
t.Errorf("Invalid Pattern.cacheable for \"%s\": %v (expected: %v)", str, pat.cacheable, cacheable)
|
t.Errorf("Invalid Pattern.cacheable for \"%s\": %v (expected: %v)", str, pat.cacheable, cacheable)
|
||||||
}
|
}
|
||||||
clearPatternCache()
|
|
||||||
}
|
}
|
||||||
test(true, "foo bar", "foo\tbar", true)
|
test(true, "foo bar", "foo\tbar", true)
|
||||||
test(true, "foo 'bar", "foo\tbar", false)
|
test(true, "foo 'bar", "foo\tbar", false)
|
||||||
|
@@ -3,6 +3,4 @@
|
|||||||
package protector
|
package protector
|
||||||
|
|
||||||
// Protect calls OS specific protections like pledge on OpenBSD
|
// Protect calls OS specific protections like pledge on OpenBSD
|
||||||
func Protect() {
|
func Protect() {}
|
||||||
return
|
|
||||||
}
|
|
||||||
|
@@ -6,5 +6,5 @@ import "golang.org/x/sys/unix"
|
|||||||
|
|
||||||
// Protect calls OS specific protections like pledge on OpenBSD
|
// Protect calls OS specific protections like pledge on OpenBSD
|
||||||
func Protect() {
|
func Protect() {
|
||||||
unix.PledgePromises("stdio rpath tty proc exec inet tmppath")
|
unix.PledgePromises("stdio dpath wpath rpath tty proc exec inet tmppath")
|
||||||
}
|
}
|
||||||
|
146
src/proxy.go
Normal file
146
src/proxy.go
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
package fzf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/tui"
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const becomeSuffix = ".become"
|
||||||
|
|
||||||
|
func escapeSingleQuote(str string) string {
|
||||||
|
return "'" + strings.ReplaceAll(str, "'", "'\\''") + "'"
|
||||||
|
}
|
||||||
|
|
||||||
|
func fifo(name string) (string, error) {
|
||||||
|
ns := time.Now().UnixNano()
|
||||||
|
output := filepath.Join(os.TempDir(), fmt.Sprintf("fzf-%s-%d", name, ns))
|
||||||
|
output, err := mkfifo(output, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return output, err
|
||||||
|
}
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runProxy(commandPrefix string, cmdBuilder func(temp string) *exec.Cmd, opts *Options, withExports bool) (int, error) {
|
||||||
|
output, err := fifo("proxy-output")
|
||||||
|
if err != nil {
|
||||||
|
return ExitError, err
|
||||||
|
}
|
||||||
|
defer os.Remove(output)
|
||||||
|
|
||||||
|
// Take the output
|
||||||
|
go func() {
|
||||||
|
withOutputPipe(output, func(outputFile io.ReadCloser) {
|
||||||
|
if opts.Output == nil {
|
||||||
|
io.Copy(os.Stdout, outputFile)
|
||||||
|
} else {
|
||||||
|
reader := bufio.NewReader(outputFile)
|
||||||
|
sep := opts.PrintSep[0]
|
||||||
|
for {
|
||||||
|
item, err := reader.ReadString(sep)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
opts.Output <- item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
|
var command string
|
||||||
|
commandPrefix += ` --no-force-tty-in --proxy-script "$0"`
|
||||||
|
if opts.Input == nil && (opts.ForceTtyIn || util.IsTty(os.Stdin)) {
|
||||||
|
command = fmt.Sprintf(`%s > %q`, commandPrefix, output)
|
||||||
|
} else {
|
||||||
|
input, err := fifo("proxy-input")
|
||||||
|
if err != nil {
|
||||||
|
return ExitError, err
|
||||||
|
}
|
||||||
|
defer os.Remove(input)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
withInputPipe(input, func(inputFile io.WriteCloser) {
|
||||||
|
if opts.Input == nil {
|
||||||
|
io.Copy(inputFile, os.Stdin)
|
||||||
|
} else {
|
||||||
|
for item := range opts.Input {
|
||||||
|
fmt.Fprint(inputFile, item+opts.PrintSep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
|
if withExports {
|
||||||
|
command = fmt.Sprintf(`%s < %q > %q`, commandPrefix, input, output)
|
||||||
|
} else {
|
||||||
|
// For mintty: cannot directly read named pipe from Go code
|
||||||
|
command = fmt.Sprintf(`command cat %q | %s > %q`, input, commandPrefix, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// To ensure that the options are processed by a POSIX-compliant shell,
|
||||||
|
// we need to write the command to a temporary file and execute it with sh.
|
||||||
|
var exports []string
|
||||||
|
if withExports {
|
||||||
|
exports = os.Environ()
|
||||||
|
for idx, pairStr := range exports {
|
||||||
|
pair := strings.SplitN(pairStr, "=", 2)
|
||||||
|
exports[idx] = fmt.Sprintf("export %s=%s", pair[0], escapeSingleQuote(pair[1]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
temp := WriteTemporaryFile(append(exports, command), "\n")
|
||||||
|
defer os.Remove(temp)
|
||||||
|
|
||||||
|
cmd := cmdBuilder(temp)
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
intChan := make(chan os.Signal, 1)
|
||||||
|
defer close(intChan)
|
||||||
|
go func() {
|
||||||
|
if sig, valid := <-intChan; valid {
|
||||||
|
cmd.Process.Signal(sig)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
signal.Notify(intChan, os.Interrupt)
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
if exitError, ok := err.(*exec.ExitError); ok {
|
||||||
|
code := exitError.ExitCode()
|
||||||
|
if code == ExitBecome {
|
||||||
|
becomeFile := temp + becomeSuffix
|
||||||
|
data, err := os.ReadFile(becomeFile)
|
||||||
|
os.Remove(becomeFile)
|
||||||
|
if err != nil {
|
||||||
|
return ExitError, err
|
||||||
|
}
|
||||||
|
elems := strings.Split(string(data), "\x00")
|
||||||
|
if len(elems) < 1 {
|
||||||
|
return ExitError, errors.New("invalid become command")
|
||||||
|
}
|
||||||
|
command := elems[0]
|
||||||
|
env := []string{}
|
||||||
|
if len(elems) > 1 {
|
||||||
|
env = elems[1:]
|
||||||
|
}
|
||||||
|
executor := util.NewExecutor(opts.WithShell)
|
||||||
|
ttyin, err := tui.TtyIn()
|
||||||
|
if err != nil {
|
||||||
|
return ExitError, err
|
||||||
|
}
|
||||||
|
executor.Become(ttyin, env, command)
|
||||||
|
}
|
||||||
|
return code, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExitOk, nil
|
||||||
|
}
|
38
src/proxy_unix.go
Normal file
38
src/proxy_unix.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package fzf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func sh() (string, error) {
|
||||||
|
return "sh", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mkfifo(path string, mode uint32) (string, error) {
|
||||||
|
return path, unix.Mkfifo(path, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func withOutputPipe(output string, task func(io.ReadCloser)) error {
|
||||||
|
outputFile, err := os.OpenFile(output, os.O_RDONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
task(outputFile)
|
||||||
|
outputFile.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func withInputPipe(input string, task func(io.WriteCloser)) error {
|
||||||
|
inputFile, err := os.OpenFile(input, os.O_WRONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
task(inputFile)
|
||||||
|
inputFile.Close()
|
||||||
|
return nil
|
||||||
|
}
|
81
src/proxy_windows.go
Normal file
81
src/proxy_windows.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package fzf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
var shPath atomic.Value
|
||||||
|
|
||||||
|
func sh() (string, error) {
|
||||||
|
if cached := shPath.Load(); cached != nil {
|
||||||
|
return cached.(string), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command("cygpath", "-w", "/usr/bin/sh")
|
||||||
|
bytes, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
sh := strings.TrimSpace(string(bytes))
|
||||||
|
shPath.Store(sh)
|
||||||
|
return sh, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mkfifo(path string, mode uint32) (string, error) {
|
||||||
|
m := strconv.FormatUint(uint64(mode), 8)
|
||||||
|
sh, err := sh()
|
||||||
|
if err != nil {
|
||||||
|
return path, err
|
||||||
|
}
|
||||||
|
cmd := exec.Command(sh, "-c", fmt.Sprintf(`command mkfifo -m %s %q`, m, path))
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return path, err
|
||||||
|
}
|
||||||
|
return path + ".lnk", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func withOutputPipe(output string, task func(io.ReadCloser)) error {
|
||||||
|
sh, err := sh()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd := exec.Command(sh, "-c", fmt.Sprintf(`command cat %q`, output))
|
||||||
|
outputFile, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
task(outputFile)
|
||||||
|
cmd.Wait()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func withInputPipe(input string, task func(io.WriteCloser)) error {
|
||||||
|
sh, err := sh()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd := exec.Command(sh, "-c", fmt.Sprintf(`command cat - > %q`, input))
|
||||||
|
inputFile, err := cmd.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
task(inputFile)
|
||||||
|
inputFile.Close()
|
||||||
|
cmd.Wait()
|
||||||
|
return nil
|
||||||
|
}
|
235
src/reader.go
235
src/reader.go
@@ -1,38 +1,40 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/charlievieth/fastwalk"
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
"github.com/saracen/walker"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Reader reads from command or standard input
|
// Reader reads from command or standard input
|
||||||
type Reader struct {
|
type Reader struct {
|
||||||
pusher func([]byte) bool
|
pusher func([]byte) bool
|
||||||
|
executor *util.Executor
|
||||||
eventBox *util.EventBox
|
eventBox *util.EventBox
|
||||||
delimNil bool
|
delimNil bool
|
||||||
event int32
|
event int32
|
||||||
finChan chan bool
|
finChan chan bool
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
exec *exec.Cmd
|
exec *exec.Cmd
|
||||||
|
execOut io.ReadCloser
|
||||||
command *string
|
command *string
|
||||||
killed bool
|
killed bool
|
||||||
wait bool
|
wait bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewReader returns new Reader object
|
// NewReader returns new Reader object
|
||||||
func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, delimNil bool, wait bool) *Reader {
|
func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, executor *util.Executor, delimNil bool, wait bool) *Reader {
|
||||||
return &Reader{pusher, eventBox, delimNil, int32(EvtReady), make(chan bool, 1), sync.Mutex{}, nil, nil, false, wait}
|
return &Reader{pusher, executor, eventBox, delimNil, int32(EvtReady), make(chan bool, 1), sync.Mutex{}, nil, nil, nil, false, wait}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) startEventPoller() {
|
func (r *Reader) startEventPoller() {
|
||||||
@@ -77,48 +79,50 @@ func (r *Reader) fin(success bool) {
|
|||||||
|
|
||||||
func (r *Reader) terminate() {
|
func (r *Reader) terminate() {
|
||||||
r.mutex.Lock()
|
r.mutex.Lock()
|
||||||
defer func() { r.mutex.Unlock() }()
|
|
||||||
|
|
||||||
r.killed = true
|
r.killed = true
|
||||||
if r.exec != nil && r.exec.Process != nil {
|
if r.exec != nil && r.exec.Process != nil {
|
||||||
|
r.execOut.Close()
|
||||||
util.KillCommand(r.exec)
|
util.KillCommand(r.exec)
|
||||||
} else if defaultCommand != "" {
|
} else {
|
||||||
os.Stdin.Close()
|
os.Stdin.Close()
|
||||||
}
|
}
|
||||||
|
r.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) restart(command string) {
|
func (r *Reader) restart(command commandSpec, environ []string) {
|
||||||
r.event = int32(EvtReady)
|
r.event = int32(EvtReady)
|
||||||
r.startEventPoller()
|
r.startEventPoller()
|
||||||
success := r.readFromCommand(nil, command)
|
success := r.readFromCommand(command.command, environ)
|
||||||
r.fin(success)
|
r.fin(success)
|
||||||
|
removeFiles(command.tempFiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readChannel(inputChan chan string) bool {
|
||||||
|
for {
|
||||||
|
item, more := <-inputChan
|
||||||
|
if !more {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if r.pusher([]byte(item)) {
|
||||||
|
atomic.StoreInt32(&r.event, int32(EvtReadNew))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadSource reads data from the default command or from standard input
|
// ReadSource reads data from the default command or from standard input
|
||||||
func (r *Reader) ReadSource() {
|
func (r *Reader) ReadSource(inputChan chan string, root string, opts walkerOpts, ignores []string) {
|
||||||
r.startEventPoller()
|
r.startEventPoller()
|
||||||
var success bool
|
var success bool
|
||||||
if util.IsTty() {
|
if inputChan != nil {
|
||||||
// The default command for *nix requires a shell that supports "pipefail"
|
success = r.readChannel(inputChan)
|
||||||
// https://unix.stackexchange.com/a/654932/62171
|
} else if util.IsTty(os.Stdin) {
|
||||||
shell := "bash"
|
|
||||||
currentShell := os.Getenv("SHELL")
|
|
||||||
currentShellName := path.Base(currentShell)
|
|
||||||
for _, shellName := range []string{"bash", "zsh", "ksh", "ash", "hush", "mksh", "yash"} {
|
|
||||||
if currentShellName == shellName {
|
|
||||||
shell = currentShell
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
||||||
if len(cmd) == 0 {
|
if len(cmd) == 0 {
|
||||||
if defaultCommand != "" {
|
success = r.readFiles(root, opts, ignores)
|
||||||
success = r.readFromCommand(&shell, defaultCommand)
|
|
||||||
} else {
|
|
||||||
success = r.readFiles()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
success = r.readFromCommand(nil, cmd)
|
// We can't export FZF_* environment variables to the default command
|
||||||
|
success = r.readFromCommand(cmd, nil)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
success = r.readFromStdin()
|
success = r.readFromStdin()
|
||||||
@@ -127,32 +131,90 @@ func (r *Reader) ReadSource() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) feed(src io.Reader) {
|
func (r *Reader) feed(src io.Reader) {
|
||||||
|
/*
|
||||||
|
readerSlabSize, ae := strconv.Atoi(os.Getenv("SLAB_KB"))
|
||||||
|
if ae != nil {
|
||||||
|
readerSlabSize = 128 * 1024
|
||||||
|
} else {
|
||||||
|
readerSlabSize *= 1024
|
||||||
|
}
|
||||||
|
readerBufferSize, be := strconv.Atoi(os.Getenv("BUF_KB"))
|
||||||
|
if be != nil {
|
||||||
|
readerBufferSize = 64 * 1024
|
||||||
|
} else {
|
||||||
|
readerBufferSize *= 1024
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
delim := byte('\n')
|
delim := byte('\n')
|
||||||
|
trimCR := util.IsWindows()
|
||||||
if r.delimNil {
|
if r.delimNil {
|
||||||
delim = '\000'
|
delim = '\000'
|
||||||
|
trimCR = false
|
||||||
}
|
}
|
||||||
reader := bufio.NewReaderSize(src, readerBufferSize)
|
|
||||||
|
slab := make([]byte, readerSlabSize)
|
||||||
|
leftover := []byte{}
|
||||||
|
var err error
|
||||||
for {
|
for {
|
||||||
// ReadBytes returns err != nil if and only if the returned data does not
|
n := 0
|
||||||
// end in delim.
|
scope := slab[:util.Min(len(slab), readerBufferSize)]
|
||||||
bytea, err := reader.ReadBytes(delim)
|
for i := 0; i < 100; i++ {
|
||||||
byteaLen := len(bytea)
|
n, err = src.Read(scope)
|
||||||
if byteaLen > 0 {
|
if n > 0 || err != nil {
|
||||||
if err == nil {
|
break
|
||||||
// get rid of carriage return if under Windows:
|
|
||||||
if util.IsWindows() && byteaLen >= 2 && bytea[byteaLen-2] == byte('\r') {
|
|
||||||
bytea = bytea[:byteaLen-2]
|
|
||||||
} else {
|
|
||||||
bytea = bytea[:byteaLen-1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if r.pusher(bytea) {
|
|
||||||
atomic.StoreInt32(&r.event, int32(EvtReadNew))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
|
// We're not making any progress after 100 tries. Stop.
|
||||||
|
if n == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buf := slab[:n]
|
||||||
|
slab = slab[n:]
|
||||||
|
|
||||||
|
for len(buf) > 0 {
|
||||||
|
if i := bytes.IndexByte(buf, delim); i >= 0 {
|
||||||
|
// Found the delimiter
|
||||||
|
slice := buf[:i+1]
|
||||||
|
buf = buf[i+1:]
|
||||||
|
if trimCR && len(slice) >= 2 && slice[len(slice)-2] == byte('\r') {
|
||||||
|
slice = slice[:len(slice)-2]
|
||||||
|
} else {
|
||||||
|
slice = slice[:len(slice)-1]
|
||||||
|
}
|
||||||
|
if len(leftover) > 0 {
|
||||||
|
slice = append(leftover, slice...)
|
||||||
|
leftover = []byte{}
|
||||||
|
}
|
||||||
|
if (err == nil || len(slice) > 0) && r.pusher(slice) {
|
||||||
|
atomic.StoreInt32(&r.event, int32(EvtReadNew))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Could not find the delimiter in the buffer
|
||||||
|
// NOTE: We can further optimize this by keeping track of the cursor
|
||||||
|
// position in the slab so that a straddling item that doesn't go
|
||||||
|
// beyond the boundary of a slab doesn't need to be copied to
|
||||||
|
// another buffer. However, the performance gain is negligible in
|
||||||
|
// practice (< 0.1%) and is not
|
||||||
|
// worth the added complexity.
|
||||||
|
leftover = append(leftover, buf...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == io.EOF {
|
||||||
|
leftover = append(leftover, buf...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(slab) == 0 {
|
||||||
|
slab = make([]byte, readerSlabSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(leftover) > 0 && r.pusher(leftover) {
|
||||||
|
atomic.StoreInt32(&r.event, int32(EvtReadNew))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,16 +223,56 @@ func (r *Reader) readFromStdin() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readFiles() bool {
|
func isSymlinkToDir(path string, de os.DirEntry) bool {
|
||||||
|
if de.Type()&fs.ModeSymlink == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if s, err := os.Stat(path); err == nil {
|
||||||
|
return s.IsDir()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimPath(path string) string {
|
||||||
|
bytes := stringBytes(path)
|
||||||
|
|
||||||
|
for len(bytes) > 1 && bytes[0] == '.' && (bytes[1] == '/' || bytes[1] == '\\') {
|
||||||
|
bytes = bytes[2:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(bytes) == 0 {
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
|
||||||
|
return byteString(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool {
|
||||||
r.killed = false
|
r.killed = false
|
||||||
fn := func(path string, mode os.FileInfo) error {
|
conf := fastwalk.Config{
|
||||||
path = filepath.Clean(path)
|
Follow: opts.follow,
|
||||||
|
// Use forward slashes when running a Windows binary under WSL or MSYS
|
||||||
|
ToSlash: fastwalk.DefaultToSlash(),
|
||||||
|
}
|
||||||
|
fn := func(path string, de os.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
path = trimPath(path)
|
||||||
if path != "." {
|
if path != "." {
|
||||||
isDir := mode.Mode().IsDir()
|
isDir := de.IsDir()
|
||||||
if isDir && filepath.Base(path)[0] == '.' {
|
if isDir || opts.follow && isSymlinkToDir(path, de) {
|
||||||
return filepath.SkipDir
|
base := filepath.Base(path)
|
||||||
|
if !opts.hidden && base[0] == '.' {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
for _, ignore := range ignores {
|
||||||
|
if ignore == base {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !isDir && r.pusher([]byte(path)) {
|
if ((opts.file && !isDir) || (opts.dir && isDir)) && r.pusher(stringBytes(path)) {
|
||||||
atomic.StoreInt32(&r.event, int32(EvtReadNew))
|
atomic.StoreInt32(&r.event, int32(EvtReadNew))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,31 +283,34 @@ func (r *Reader) readFiles() bool {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
cb := walker.WithErrorCallback(func(pathname string, err error) error {
|
return fastwalk.Walk(&conf, root, fn) == nil
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return walker.Walk(".", fn, cb) == nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readFromCommand(shell *string, command string) bool {
|
func (r *Reader) readFromCommand(command string, environ []string) bool {
|
||||||
r.mutex.Lock()
|
r.mutex.Lock()
|
||||||
r.killed = false
|
r.killed = false
|
||||||
r.command = &command
|
r.command = &command
|
||||||
if shell != nil {
|
r.exec = r.executor.ExecCommand(command, true)
|
||||||
r.exec = util.ExecCommandWith(*shell, command, true)
|
if environ != nil {
|
||||||
} else {
|
r.exec.Env = environ
|
||||||
r.exec = util.ExecCommand(command, true)
|
|
||||||
}
|
}
|
||||||
out, err := r.exec.StdoutPipe()
|
|
||||||
|
var err error
|
||||||
|
r.execOut, err = r.exec.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
r.exec = nil
|
||||||
r.mutex.Unlock()
|
r.mutex.Unlock()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
err = r.exec.Start()
|
err = r.exec.Start()
|
||||||
r.mutex.Unlock()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
r.exec = nil
|
||||||
|
r.mutex.Unlock()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
r.feed(out)
|
|
||||||
|
r.mutex.Unlock()
|
||||||
|
r.feed(r.execOut)
|
||||||
return r.exec.Wait() == nil
|
return r.exec.Wait() == nil
|
||||||
}
|
}
|
||||||
|
@@ -10,9 +10,10 @@ import (
|
|||||||
func TestReadFromCommand(t *testing.T) {
|
func TestReadFromCommand(t *testing.T) {
|
||||||
strs := []string{}
|
strs := []string{}
|
||||||
eb := util.NewEventBox()
|
eb := util.NewEventBox()
|
||||||
|
exec := util.NewExecutor("")
|
||||||
reader := NewReader(
|
reader := NewReader(
|
||||||
func(s []byte) bool { strs = append(strs, string(s)); return true },
|
func(s []byte) bool { strs = append(strs, string(s)); return true },
|
||||||
eb, false, true)
|
eb, exec, false, true)
|
||||||
|
|
||||||
reader.startEventPoller()
|
reader.startEventPoller()
|
||||||
|
|
||||||
@@ -22,7 +23,7 @@ func TestReadFromCommand(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Normal command
|
// Normal command
|
||||||
reader.fin(reader.readFromCommand(nil, `echo abc&&echo def`))
|
reader.fin(reader.readFromCommand(`echo abc&&echo def`, nil))
|
||||||
if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" {
|
if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" {
|
||||||
t.Errorf("%s", strs)
|
t.Errorf("%s", strs)
|
||||||
}
|
}
|
||||||
@@ -47,7 +48,7 @@ func TestReadFromCommand(t *testing.T) {
|
|||||||
reader.startEventPoller()
|
reader.startEventPoller()
|
||||||
|
|
||||||
// Failing command
|
// Failing command
|
||||||
reader.fin(reader.readFromCommand(nil, `no-such-command`))
|
reader.fin(reader.readFromCommand(`no-such-command`, nil))
|
||||||
strs = []string{}
|
strs = []string{}
|
||||||
if len(strs) > 0 {
|
if len(strs) > 0 {
|
||||||
t.Errorf("%s", strs)
|
t.Errorf("%s", strs)
|
||||||
|
@@ -15,6 +15,7 @@ type Offset [2]int32
|
|||||||
type colorOffset struct {
|
type colorOffset struct {
|
||||||
offset [2]int32
|
offset [2]int32
|
||||||
color tui.ColorPair
|
color tui.ColorPair
|
||||||
|
match bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result struct {
|
type Result struct {
|
||||||
@@ -80,7 +81,7 @@ func buildResult(item *Item, offsets []Offset, score int) Result {
|
|||||||
if criterion == byBegin {
|
if criterion == byBegin {
|
||||||
val = util.AsUint16(minEnd - whitePrefixLen)
|
val = util.AsUint16(minEnd - whitePrefixLen)
|
||||||
} else {
|
} else {
|
||||||
val = util.AsUint16(math.MaxUint16 - math.MaxUint16*(maxEnd-whitePrefixLen)/int(item.TrimLength()+1))
|
val = util.AsUint16(math.MaxUint16 - math.MaxUint16*(maxEnd-whitePrefixLen)/(int(item.TrimLength())+1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,7 +110,7 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
|
|||||||
if len(itemColors) == 0 {
|
if len(itemColors) == 0 {
|
||||||
var offsets []colorOffset
|
var offsets []colorOffset
|
||||||
for _, off := range matchOffsets {
|
for _, off := range matchOffsets {
|
||||||
offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: colMatch})
|
offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: colMatch, match: true})
|
||||||
}
|
}
|
||||||
return offsets
|
return offsets
|
||||||
}
|
}
|
||||||
@@ -193,12 +194,13 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
colors = append(colors, colorOffset{
|
colors = append(colors, colorOffset{
|
||||||
offset: [2]int32{int32(start), int32(idx)}, color: color})
|
offset: [2]int32{int32(start), int32(idx)}, color: color, match: true})
|
||||||
} else {
|
} else {
|
||||||
ansi := itemColors[curr-1]
|
ansi := itemColors[curr-1]
|
||||||
colors = append(colors, colorOffset{
|
colors = append(colors, colorOffset{
|
||||||
offset: [2]int32{int32(start), int32(idx)},
|
offset: [2]int32{int32(start), int32(idx)},
|
||||||
color: ansiToColorPair(ansi, colBase)})
|
color: ansiToColorPair(ansi, colBase),
|
||||||
|
match: false})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -32,14 +32,15 @@ const (
|
|||||||
httpUnauthorized = "HTTP/1.1 401 Unauthorized" + crlf
|
httpUnauthorized = "HTTP/1.1 401 Unauthorized" + crlf
|
||||||
httpUnavailable = "HTTP/1.1 503 Service Unavailable" + crlf
|
httpUnavailable = "HTTP/1.1 503 Service Unavailable" + crlf
|
||||||
httpReadTimeout = 10 * time.Second
|
httpReadTimeout = 10 * time.Second
|
||||||
|
channelTimeout = 2 * time.Second
|
||||||
jsonContentType = "Content-Type: application/json" + crlf
|
jsonContentType = "Content-Type: application/json" + crlf
|
||||||
maxContentLength = 1024 * 1024
|
maxContentLength = 1024 * 1024
|
||||||
)
|
)
|
||||||
|
|
||||||
type httpServer struct {
|
type httpServer struct {
|
||||||
apiKey []byte
|
apiKey []byte
|
||||||
actionChannel chan []*action
|
actionChannel chan []*action
|
||||||
responseChannel chan string
|
getHandler func(getParams) string
|
||||||
}
|
}
|
||||||
|
|
||||||
type listenAddress struct {
|
type listenAddress struct {
|
||||||
@@ -53,54 +54,54 @@ func (addr listenAddress) IsLocal() bool {
|
|||||||
|
|
||||||
var defaultListenAddr = listenAddress{"localhost", 0}
|
var defaultListenAddr = listenAddress{"localhost", 0}
|
||||||
|
|
||||||
func parseListenAddress(address string) (error, listenAddress) {
|
func parseListenAddress(address string) (listenAddress, error) {
|
||||||
parts := strings.SplitN(address, ":", 3)
|
parts := strings.SplitN(address, ":", 3)
|
||||||
if len(parts) == 1 {
|
if len(parts) == 1 {
|
||||||
parts = []string{"localhost", parts[0]}
|
parts = []string{"localhost", parts[0]}
|
||||||
}
|
}
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
return fmt.Errorf("invalid listen address: %s", address), defaultListenAddr
|
return defaultListenAddr, fmt.Errorf("invalid listen address: %s", address)
|
||||||
}
|
}
|
||||||
portStr := parts[len(parts)-1]
|
portStr := parts[len(parts)-1]
|
||||||
port, err := strconv.Atoi(portStr)
|
port, err := strconv.Atoi(portStr)
|
||||||
if err != nil || port < 0 || port > 65535 {
|
if err != nil || port < 0 || port > 65535 {
|
||||||
return fmt.Errorf("invalid listen port: %s", portStr), defaultListenAddr
|
return defaultListenAddr, fmt.Errorf("invalid listen port: %s", portStr)
|
||||||
}
|
}
|
||||||
if len(parts[0]) == 0 {
|
if len(parts[0]) == 0 {
|
||||||
parts[0] = "localhost"
|
parts[0] = "localhost"
|
||||||
}
|
}
|
||||||
return nil, listenAddress{parts[0], port}
|
return listenAddress{parts[0], port}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func startHttpServer(address listenAddress, actionChannel chan []*action, responseChannel chan string) (error, int) {
|
func startHttpServer(address listenAddress, actionChannel chan []*action, getHandler func(getParams) string) (net.Listener, int, error) {
|
||||||
host := address.host
|
host := address.host
|
||||||
port := address.port
|
port := address.port
|
||||||
apiKey := os.Getenv("FZF_API_KEY")
|
apiKey := os.Getenv("FZF_API_KEY")
|
||||||
if !address.IsLocal() && len(apiKey) == 0 {
|
if !address.IsLocal() && len(apiKey) == 0 {
|
||||||
return fmt.Errorf("FZF_API_KEY is required to allow remote access"), port
|
return nil, port, errors.New("FZF_API_KEY is required to allow remote access")
|
||||||
}
|
}
|
||||||
addrStr := fmt.Sprintf("%s:%d", host, port)
|
addrStr := fmt.Sprintf("%s:%d", host, port)
|
||||||
listener, err := net.Listen("tcp", addrStr)
|
listener, err := net.Listen("tcp", addrStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to listen on %s", addrStr), port
|
return nil, port, fmt.Errorf("failed to listen on %s", addrStr)
|
||||||
}
|
}
|
||||||
if port == 0 {
|
if port == 0 {
|
||||||
addr := listener.Addr().String()
|
addr := listener.Addr().String()
|
||||||
parts := strings.Split(addr, ":")
|
parts := strings.Split(addr, ":")
|
||||||
if len(parts) < 2 {
|
if len(parts) < 2 {
|
||||||
return fmt.Errorf("cannot extract port: %s", addr), port
|
return nil, port, fmt.Errorf("cannot extract port: %s", addr)
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
port, err = strconv.Atoi(parts[len(parts)-1])
|
port, err = strconv.Atoi(parts[len(parts)-1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err, port
|
return nil, port, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
server := httpServer{
|
server := httpServer{
|
||||||
apiKey: []byte(apiKey),
|
apiKey: []byte(apiKey),
|
||||||
actionChannel: actionChannel,
|
actionChannel: actionChannel,
|
||||||
responseChannel: responseChannel,
|
getHandler: getHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@@ -108,18 +109,16 @@ func startHttpServer(address listenAddress, actionChannel chan []*action, respon
|
|||||||
conn, err := listener.Accept()
|
conn, err := listener.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, net.ErrClosed) {
|
if errors.Is(err, net.ErrClosed) {
|
||||||
break
|
return
|
||||||
} else {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
conn.Write([]byte(server.handleHttpRequest(conn)))
|
conn.Write([]byte(server.handleHttpRequest(conn)))
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}
|
}
|
||||||
listener.Close()
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nil, port
|
return listener, port, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Here we are writing a simplistic HTTP server without using net/http
|
// Here we are writing a simplistic HTTP server without using net/http
|
||||||
@@ -166,17 +165,11 @@ func (server *httpServer) handleHttpRequest(conn net.Conn) string {
|
|||||||
case 0:
|
case 0:
|
||||||
getMatch := getRegex.FindStringSubmatch(text)
|
getMatch := getRegex.FindStringSubmatch(text)
|
||||||
if len(getMatch) > 0 {
|
if len(getMatch) > 0 {
|
||||||
server.actionChannel <- []*action{{t: actResponse, a: getMatch[1]}}
|
response := server.getHandler(parseGetParams(getMatch[1]))
|
||||||
select {
|
if len(response) > 0 {
|
||||||
case response := <-server.responseChannel:
|
|
||||||
return good(response)
|
return good(response)
|
||||||
case <-time.After(2 * time.Second):
|
|
||||||
go func() {
|
|
||||||
// Drain the channel
|
|
||||||
<-server.responseChannel
|
|
||||||
}()
|
|
||||||
return answer(httpUnavailable+jsonContentType, `{"error":"timeout"}`)
|
|
||||||
}
|
}
|
||||||
|
return answer(httpUnavailable+jsonContentType, `{"error":"timeout"}`)
|
||||||
} else if !strings.HasPrefix(text, "POST / HTTP") {
|
} else if !strings.HasPrefix(text, "POST / HTTP") {
|
||||||
return bad("invalid request method")
|
return bad("invalid request method")
|
||||||
}
|
}
|
||||||
@@ -216,18 +209,19 @@ func (server *httpServer) handleHttpRequest(conn net.Conn) string {
|
|||||||
}
|
}
|
||||||
body = body[:contentLength]
|
body = body[:contentLength]
|
||||||
|
|
||||||
errorMessage := ""
|
actions, err := parseSingleActionList(strings.Trim(string(body), "\r\n"))
|
||||||
actions := parseSingleActionList(strings.Trim(string(body), "\r\n"), func(message string) {
|
if err != nil {
|
||||||
errorMessage = message
|
return bad(err.Error())
|
||||||
})
|
|
||||||
if len(errorMessage) > 0 {
|
|
||||||
return bad(errorMessage)
|
|
||||||
}
|
}
|
||||||
if len(actions) == 0 {
|
if len(actions) == 0 {
|
||||||
return bad("no action specified")
|
return bad("no action specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
server.actionChannel <- actions
|
select {
|
||||||
|
case server.actionChannel <- actions:
|
||||||
|
case <-time.After(channelTimeout):
|
||||||
|
return httpUnavailable + crlf
|
||||||
|
}
|
||||||
return httpOk + crlf
|
return httpOk + crlf
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,15 +231,13 @@ func parseGetParams(query string) getParams {
|
|||||||
parts := strings.SplitN(pair, "=", 2)
|
parts := strings.SplitN(pair, "=", 2)
|
||||||
if len(parts) == 2 {
|
if len(parts) == 2 {
|
||||||
switch parts[0] {
|
switch parts[0] {
|
||||||
case "limit":
|
case "limit", "offset":
|
||||||
val, err := strconv.Atoi(parts[1])
|
if val, err := strconv.Atoi(parts[1]); err == nil {
|
||||||
if err == nil {
|
if parts[0] == "limit" {
|
||||||
params.limit = val
|
params.limit = val
|
||||||
}
|
} else {
|
||||||
case "offset":
|
params.offset = val
|
||||||
val, err := strconv.Atoi(parts[1])
|
}
|
||||||
if err == nil {
|
|
||||||
params.offset = val
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1827
src/terminal.go
1827
src/terminal.go
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func replacePlaceholderTest(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
|
func replacePlaceholderTest(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
|
||||||
return replacePlaceholder(replacePlaceholderParams{
|
replaced, _ := replacePlaceholder(replacePlaceholderParams{
|
||||||
template: template,
|
template: template,
|
||||||
stripAnsi: stripAnsi,
|
stripAnsi: stripAnsi,
|
||||||
delimiter: delimiter,
|
delimiter: delimiter,
|
||||||
@@ -23,7 +23,9 @@ func replacePlaceholderTest(template string, stripAnsi bool, delimiter Delimiter
|
|||||||
allItems: allItems,
|
allItems: allItems,
|
||||||
lastAction: actBackwardDeleteCharEof,
|
lastAction: actBackwardDeleteCharEof,
|
||||||
prompt: "prompt",
|
prompt: "prompt",
|
||||||
|
executor: util.NewExecutor(""),
|
||||||
})
|
})
|
||||||
|
return replaced
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReplacePlaceholder(t *testing.T) {
|
func TestReplacePlaceholder(t *testing.T) {
|
||||||
@@ -244,6 +246,7 @@ func TestQuoteEntry(t *testing.T) {
|
|||||||
unixStyle := quotes{``, `'`, `'\''`, `"`, `\`}
|
unixStyle := quotes{``, `'`, `'\''`, `"`, `\`}
|
||||||
windowsStyle := quotes{`^`, `^"`, `'`, `\^"`, `\\`}
|
windowsStyle := quotes{`^`, `^"`, `'`, `\^"`, `\\`}
|
||||||
var effectiveStyle quotes
|
var effectiveStyle quotes
|
||||||
|
exec := util.NewExecutor("")
|
||||||
|
|
||||||
if util.IsWindows() {
|
if util.IsWindows() {
|
||||||
effectiveStyle = windowsStyle
|
effectiveStyle = windowsStyle
|
||||||
@@ -278,7 +281,7 @@ func TestQuoteEntry(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for input, expected := range tests {
|
for input, expected := range tests {
|
||||||
escaped := quoteEntry(input)
|
escaped := exec.QuoteEntry(input)
|
||||||
expected = templateToString(expected, effectiveStyle)
|
expected = templateToString(expected, effectiveStyle)
|
||||||
if escaped != expected {
|
if escaped != expected {
|
||||||
t.Errorf("Input: %s, expected: %s, actual %s", input, expected, escaped)
|
t.Errorf("Input: %s, expected: %s, actual %s", input, expected, escaped)
|
||||||
@@ -317,9 +320,9 @@ func TestUnixCommands(t *testing.T) {
|
|||||||
|
|
||||||
// purpose of this test is to demonstrate some shortcomings of fzf's templating system on Windows
|
// purpose of this test is to demonstrate some shortcomings of fzf's templating system on Windows
|
||||||
func TestWindowsCommands(t *testing.T) {
|
func TestWindowsCommands(t *testing.T) {
|
||||||
if !util.IsWindows() {
|
// XXX Deprecated
|
||||||
t.SkipNow()
|
t.SkipNow()
|
||||||
}
|
|
||||||
tests := []testCase{
|
tests := []testCase{
|
||||||
// reference: give{template, query, items}, want{output OR match}
|
// reference: give{template, query, items}, want{output OR match}
|
||||||
|
|
||||||
|
@@ -5,26 +5,11 @@ package fzf
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
var escaper *strings.Replacer
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
tokens := strings.Split(os.Getenv("SHELL"), "/")
|
|
||||||
if tokens[len(tokens)-1] == "fish" {
|
|
||||||
// https://fishshell.com/docs/current/language.html#quotes
|
|
||||||
// > The only meaningful escape sequences in single quotes are \', which
|
|
||||||
// > escapes a single quote and \\, which escapes the backslash symbol.
|
|
||||||
escaper = strings.NewReplacer("\\", "\\\\", "'", "\\'")
|
|
||||||
} else {
|
|
||||||
escaper = strings.NewReplacer("'", "'\\''")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func notifyOnResize(resizeChan chan<- os.Signal) {
|
func notifyOnResize(resizeChan chan<- os.Signal) {
|
||||||
signal.Notify(resizeChan, syscall.SIGWINCH)
|
signal.Notify(resizeChan, syscall.SIGWINCH)
|
||||||
}
|
}
|
||||||
@@ -41,7 +26,3 @@ func notifyStop(p *os.Process) {
|
|||||||
func notifyOnCont(resizeChan chan<- os.Signal) {
|
func notifyOnCont(resizeChan chan<- os.Signal) {
|
||||||
signal.Notify(resizeChan, syscall.SIGCONT)
|
signal.Notify(resizeChan, syscall.SIGCONT)
|
||||||
}
|
}
|
||||||
|
|
||||||
func quoteEntry(entry string) string {
|
|
||||||
return "'" + escaper.Replace(entry) + "'"
|
|
||||||
}
|
|
||||||
|
@@ -4,8 +4,6 @@ package fzf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func notifyOnResize(resizeChan chan<- os.Signal) {
|
func notifyOnResize(resizeChan chan<- os.Signal) {
|
||||||
@@ -19,27 +17,3 @@ func notifyStop(p *os.Process) {
|
|||||||
func notifyOnCont(resizeChan chan<- os.Signal) {
|
func notifyOnCont(resizeChan chan<- os.Signal) {
|
||||||
// NOOP
|
// NOOP
|
||||||
}
|
}
|
||||||
|
|
||||||
func quoteEntry(entry string) string {
|
|
||||||
shell := os.Getenv("SHELL")
|
|
||||||
if len(shell) == 0 {
|
|
||||||
shell = "cmd"
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(shell, "cmd") {
|
|
||||||
// backslash escaping is done here for applications
|
|
||||||
// (see ripgrep test case in terminal_test.go#TestWindowsCommands)
|
|
||||||
escaped := strings.Replace(entry, `\`, `\\`, -1)
|
|
||||||
escaped = `"` + strings.Replace(escaped, `"`, `\"`, -1) + `"`
|
|
||||||
// caret is the escape character for cmd shell
|
|
||||||
r, _ := regexp.Compile(`[&|<>()@^%!"]`)
|
|
||||||
return r.ReplaceAllStringFunc(escaped, func(match string) string {
|
|
||||||
return "^" + match
|
|
||||||
})
|
|
||||||
} else if strings.Contains(shell, "pwsh") || strings.Contains(shell, "powershell") {
|
|
||||||
escaped := strings.Replace(entry, `"`, `\"`, -1)
|
|
||||||
return "'" + strings.Replace(escaped, "'", "''", -1) + "'"
|
|
||||||
} else {
|
|
||||||
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
57
src/tmux.go
Normal file
57
src/tmux.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package fzf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/tui"
|
||||||
|
)
|
||||||
|
|
||||||
|
func runTmux(args []string, opts *Options) (int, error) {
|
||||||
|
// Prepare arguments
|
||||||
|
fzf := args[0]
|
||||||
|
args = append([]string{"--bind=ctrl-z:ignore"}, args[1:]...)
|
||||||
|
if opts.BorderShape == tui.BorderUndefined {
|
||||||
|
args = append(args, "--border")
|
||||||
|
}
|
||||||
|
argStr := escapeSingleQuote(fzf)
|
||||||
|
for _, arg := range args {
|
||||||
|
argStr += " " + escapeSingleQuote(arg)
|
||||||
|
}
|
||||||
|
argStr += ` --no-tmux --no-height`
|
||||||
|
|
||||||
|
// Get current directory
|
||||||
|
dir, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
dir = "."
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set tmux options for popup placement
|
||||||
|
// C Both The centre of the terminal
|
||||||
|
// R -x The right side of the terminal
|
||||||
|
// P Both The bottom left of the pane
|
||||||
|
// M Both The mouse position
|
||||||
|
// W Both The window position on the status line
|
||||||
|
// S -y The line above or below the status line
|
||||||
|
tmuxArgs := []string{"display-popup", "-E", "-B", "-d", dir}
|
||||||
|
switch opts.Tmux.position {
|
||||||
|
case posUp:
|
||||||
|
tmuxArgs = append(tmuxArgs, "-xC", "-y0")
|
||||||
|
case posDown:
|
||||||
|
tmuxArgs = append(tmuxArgs, "-xC", "-yS")
|
||||||
|
case posLeft:
|
||||||
|
tmuxArgs = append(tmuxArgs, "-x0", "-yC")
|
||||||
|
case posRight:
|
||||||
|
tmuxArgs = append(tmuxArgs, "-xR", "-yC")
|
||||||
|
case posCenter:
|
||||||
|
tmuxArgs = append(tmuxArgs, "-xC", "-yC")
|
||||||
|
}
|
||||||
|
tmuxArgs = append(tmuxArgs, "-w"+opts.Tmux.width.String())
|
||||||
|
tmuxArgs = append(tmuxArgs, "-h"+opts.Tmux.height.String())
|
||||||
|
|
||||||
|
return runProxy(argStr, func(temp string) *exec.Cmd {
|
||||||
|
sh, _ := sh()
|
||||||
|
tmuxArgs = append(tmuxArgs, sh, temp)
|
||||||
|
return exec.Command("tmux", tmuxArgs...)
|
||||||
|
}, opts, true)
|
||||||
|
}
|
@@ -91,7 +91,7 @@ func withPrefixLengths(tokens []string, begin int) []Token {
|
|||||||
|
|
||||||
prefixLength := begin
|
prefixLength := begin
|
||||||
for idx := range tokens {
|
for idx := range tokens {
|
||||||
chars := util.ToChars([]byte(tokens[idx]))
|
chars := util.ToChars(stringBytes(tokens[idx]))
|
||||||
ret[idx] = Token{&chars, int32(prefixLength)}
|
ret[idx] = Token{&chars, int32(prefixLength)}
|
||||||
prefixLength += chars.Length()
|
prefixLength += chars.Length()
|
||||||
}
|
}
|
||||||
@@ -187,7 +187,7 @@ func Transform(tokens []Token, withNth []Range) []Token {
|
|||||||
if r.begin == r.end {
|
if r.begin == r.end {
|
||||||
idx := r.begin
|
idx := r.begin
|
||||||
if idx == rangeEllipsis {
|
if idx == rangeEllipsis {
|
||||||
chars := util.ToChars([]byte(joinTokens(tokens)))
|
chars := util.ToChars(stringBytes(joinTokens(tokens)))
|
||||||
parts = append(parts, &chars)
|
parts = append(parts, &chars)
|
||||||
} else {
|
} else {
|
||||||
if idx < 0 {
|
if idx < 0 {
|
||||||
|
@@ -71,14 +71,14 @@ func TestTransform(t *testing.T) {
|
|||||||
{
|
{
|
||||||
tokens := Tokenize(input, Delimiter{})
|
tokens := Tokenize(input, Delimiter{})
|
||||||
{
|
{
|
||||||
ranges := splitNth("1,2,3")
|
ranges, _ := splitNth("1,2,3")
|
||||||
tx := Transform(tokens, ranges)
|
tx := Transform(tokens, ranges)
|
||||||
if joinTokens(tx) != "abc: def: ghi: " {
|
if joinTokens(tx) != "abc: def: ghi: " {
|
||||||
t.Errorf("%s", tx)
|
t.Errorf("%s", tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
ranges := splitNth("1..2,3,2..,1")
|
ranges, _ := splitNth("1..2,3,2..,1")
|
||||||
tx := Transform(tokens, ranges)
|
tx := Transform(tokens, ranges)
|
||||||
if string(joinTokens(tx)) != "abc: def: ghi: def: ghi: jklabc: " ||
|
if string(joinTokens(tx)) != "abc: def: ghi: def: ghi: jklabc: " ||
|
||||||
len(tx) != 4 ||
|
len(tx) != 4 ||
|
||||||
@@ -93,7 +93,7 @@ func TestTransform(t *testing.T) {
|
|||||||
{
|
{
|
||||||
tokens := Tokenize(input, delimiterRegexp(":"))
|
tokens := Tokenize(input, delimiterRegexp(":"))
|
||||||
{
|
{
|
||||||
ranges := splitNth("1..2,3,2..,1")
|
ranges, _ := splitNth("1..2,3,2..,1")
|
||||||
tx := Transform(tokens, ranges)
|
tx := Transform(tokens, ranges)
|
||||||
if joinTokens(tx) != " abc: def: ghi: def: ghi: jkl abc:" ||
|
if joinTokens(tx) != " abc: def: ghi: def: ghi: jkl abc:" ||
|
||||||
len(tx) != 4 ||
|
len(tx) != 4 ||
|
||||||
@@ -108,5 +108,6 @@ func TestTransform(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTransformIndexOutOfBounds(t *testing.T) {
|
func TestTransformIndexOutOfBounds(t *testing.T) {
|
||||||
Transform([]Token{}, splitNth("1"))
|
s, _ := splitNth("1")
|
||||||
|
Transform([]Token{}, s)
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,7 @@ func HasFullscreenRenderer() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var DefaultBorderShape BorderShape = BorderRounded
|
var DefaultBorderShape = BorderRounded
|
||||||
|
|
||||||
func (a Attr) Merge(b Attr) Attr {
|
func (a Attr) Merge(b Attr) Attr {
|
||||||
return a | b
|
return a | b
|
||||||
@@ -29,7 +29,7 @@ const (
|
|||||||
StrikeThrough = Attr(1 << 7)
|
StrikeThrough = Attr(1 << 7)
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Init() {}
|
func (r *FullscreenRenderer) Init() error { return nil }
|
||||||
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
|
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
|
||||||
func (r *FullscreenRenderer) Pause(bool) {}
|
func (r *FullscreenRenderer) Pause(bool) {}
|
||||||
func (r *FullscreenRenderer) Resume(bool, bool) {}
|
func (r *FullscreenRenderer) Resume(bool, bool) {}
|
||||||
|
122
src/tui/eventtype_string.go
Normal file
122
src/tui/eventtype_string.go
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
// Code generated by "stringer -type=EventType"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package tui
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[Rune-0]
|
||||||
|
_ = x[CtrlA-1]
|
||||||
|
_ = x[CtrlB-2]
|
||||||
|
_ = x[CtrlC-3]
|
||||||
|
_ = x[CtrlD-4]
|
||||||
|
_ = x[CtrlE-5]
|
||||||
|
_ = x[CtrlF-6]
|
||||||
|
_ = x[CtrlG-7]
|
||||||
|
_ = x[CtrlH-8]
|
||||||
|
_ = x[Tab-9]
|
||||||
|
_ = x[CtrlJ-10]
|
||||||
|
_ = x[CtrlK-11]
|
||||||
|
_ = x[CtrlL-12]
|
||||||
|
_ = x[CtrlM-13]
|
||||||
|
_ = x[CtrlN-14]
|
||||||
|
_ = x[CtrlO-15]
|
||||||
|
_ = x[CtrlP-16]
|
||||||
|
_ = x[CtrlQ-17]
|
||||||
|
_ = x[CtrlR-18]
|
||||||
|
_ = x[CtrlS-19]
|
||||||
|
_ = x[CtrlT-20]
|
||||||
|
_ = x[CtrlU-21]
|
||||||
|
_ = x[CtrlV-22]
|
||||||
|
_ = x[CtrlW-23]
|
||||||
|
_ = x[CtrlX-24]
|
||||||
|
_ = x[CtrlY-25]
|
||||||
|
_ = x[CtrlZ-26]
|
||||||
|
_ = x[Esc-27]
|
||||||
|
_ = x[CtrlSpace-28]
|
||||||
|
_ = x[CtrlDelete-29]
|
||||||
|
_ = x[CtrlBackSlash-30]
|
||||||
|
_ = x[CtrlRightBracket-31]
|
||||||
|
_ = x[CtrlCaret-32]
|
||||||
|
_ = x[CtrlSlash-33]
|
||||||
|
_ = x[ShiftTab-34]
|
||||||
|
_ = x[Backspace-35]
|
||||||
|
_ = x[Delete-36]
|
||||||
|
_ = x[PageUp-37]
|
||||||
|
_ = x[PageDown-38]
|
||||||
|
_ = x[Up-39]
|
||||||
|
_ = x[Down-40]
|
||||||
|
_ = x[Left-41]
|
||||||
|
_ = x[Right-42]
|
||||||
|
_ = x[Home-43]
|
||||||
|
_ = x[End-44]
|
||||||
|
_ = x[Insert-45]
|
||||||
|
_ = x[ShiftUp-46]
|
||||||
|
_ = x[ShiftDown-47]
|
||||||
|
_ = x[ShiftLeft-48]
|
||||||
|
_ = x[ShiftRight-49]
|
||||||
|
_ = x[ShiftDelete-50]
|
||||||
|
_ = x[F1-51]
|
||||||
|
_ = x[F2-52]
|
||||||
|
_ = x[F3-53]
|
||||||
|
_ = x[F4-54]
|
||||||
|
_ = x[F5-55]
|
||||||
|
_ = x[F6-56]
|
||||||
|
_ = x[F7-57]
|
||||||
|
_ = x[F8-58]
|
||||||
|
_ = x[F9-59]
|
||||||
|
_ = x[F10-60]
|
||||||
|
_ = x[F11-61]
|
||||||
|
_ = x[F12-62]
|
||||||
|
_ = x[AltBackspace-63]
|
||||||
|
_ = x[AltUp-64]
|
||||||
|
_ = x[AltDown-65]
|
||||||
|
_ = x[AltLeft-66]
|
||||||
|
_ = x[AltRight-67]
|
||||||
|
_ = x[AltShiftUp-68]
|
||||||
|
_ = x[AltShiftDown-69]
|
||||||
|
_ = x[AltShiftLeft-70]
|
||||||
|
_ = x[AltShiftRight-71]
|
||||||
|
_ = x[Alt-72]
|
||||||
|
_ = x[CtrlAlt-73]
|
||||||
|
_ = x[Invalid-74]
|
||||||
|
_ = x[Fatal-75]
|
||||||
|
_ = x[Mouse-76]
|
||||||
|
_ = x[DoubleClick-77]
|
||||||
|
_ = x[LeftClick-78]
|
||||||
|
_ = x[RightClick-79]
|
||||||
|
_ = x[SLeftClick-80]
|
||||||
|
_ = x[SRightClick-81]
|
||||||
|
_ = x[ScrollUp-82]
|
||||||
|
_ = x[ScrollDown-83]
|
||||||
|
_ = x[SScrollUp-84]
|
||||||
|
_ = x[SScrollDown-85]
|
||||||
|
_ = x[PreviewScrollUp-86]
|
||||||
|
_ = x[PreviewScrollDown-87]
|
||||||
|
_ = x[Resize-88]
|
||||||
|
_ = x[Change-89]
|
||||||
|
_ = x[BackwardEOF-90]
|
||||||
|
_ = x[Start-91]
|
||||||
|
_ = x[Load-92]
|
||||||
|
_ = x[Focus-93]
|
||||||
|
_ = x[One-94]
|
||||||
|
_ = x[Zero-95]
|
||||||
|
_ = x[Result-96]
|
||||||
|
_ = x[Jump-97]
|
||||||
|
_ = x[JumpCancel-98]
|
||||||
|
_ = x[ClickHeader-99]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLCtrlMCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlDeleteCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltCtrlAltInvalidFatalMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancelClickHeader"
|
||||||
|
|
||||||
|
var _EventType_index = [...]uint16{0, 4, 9, 14, 19, 24, 29, 34, 39, 44, 47, 52, 57, 62, 67, 72, 77, 82, 87, 92, 97, 102, 107, 112, 117, 122, 127, 132, 135, 144, 154, 167, 183, 192, 201, 209, 218, 224, 230, 238, 240, 244, 248, 253, 257, 260, 266, 273, 282, 291, 301, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 333, 336, 339, 351, 356, 363, 370, 378, 388, 400, 412, 425, 428, 435, 442, 447, 452, 463, 472, 482, 492, 503, 511, 521, 530, 541, 556, 573, 579, 585, 596, 601, 605, 610, 613, 617, 623, 627, 637, 648}
|
||||||
|
|
||||||
|
func (i EventType) String() string {
|
||||||
|
if i < 0 || i >= EventType(len(_EventType_index)-1) {
|
||||||
|
return "EventType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _EventType_name[_EventType_index[i]:_EventType_index[i+1]]
|
||||||
|
}
|
140
src/tui/light.go
140
src/tui/light.go
@@ -2,6 +2,7 @@ package tui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -10,6 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
"github.com/rivo/uniseg"
|
"github.com/rivo/uniseg"
|
||||||
|
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
@@ -27,8 +29,8 @@ const (
|
|||||||
|
|
||||||
const consoleDevice string = "/dev/tty"
|
const consoleDevice string = "/dev/tty"
|
||||||
|
|
||||||
var offsetRegexp *regexp.Regexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R")
|
var offsetRegexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R")
|
||||||
var offsetRegexpBegin *regexp.Regexp = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R")
|
var offsetRegexpBegin = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R")
|
||||||
|
|
||||||
func (r *LightRenderer) PassThrough(str string) {
|
func (r *LightRenderer) PassThrough(str string) {
|
||||||
r.queued.WriteString("\x1b7" + str + "\x1b8")
|
r.queued.WriteString("\x1b7" + str + "\x1b8")
|
||||||
@@ -71,13 +73,14 @@ func (r *LightRenderer) csi(code string) string {
|
|||||||
|
|
||||||
func (r *LightRenderer) flush() {
|
func (r *LightRenderer) flush() {
|
||||||
if r.queued.Len() > 0 {
|
if r.queued.Len() > 0 {
|
||||||
fmt.Fprint(os.Stderr, "\x1b[?25l"+r.queued.String()+"\x1b[?25h")
|
fmt.Fprint(r.ttyout, "\x1b[?7l\x1b[?25l"+r.queued.String()+"\x1b[?25h\x1b[?7h")
|
||||||
r.queued.Reset()
|
r.queued.Reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Light renderer
|
// Light renderer
|
||||||
type LightRenderer struct {
|
type LightRenderer struct {
|
||||||
|
closed *util.AtomicBool
|
||||||
theme *ColorTheme
|
theme *ColorTheme
|
||||||
mouse bool
|
mouse bool
|
||||||
forceBlack bool
|
forceBlack bool
|
||||||
@@ -85,6 +88,7 @@ type LightRenderer struct {
|
|||||||
prevDownTime time.Time
|
prevDownTime time.Time
|
||||||
clicks [][2]int
|
clicks [][2]int
|
||||||
ttyin *os.File
|
ttyin *os.File
|
||||||
|
ttyout *os.File
|
||||||
buffer []byte
|
buffer []byte
|
||||||
origState *term.State
|
origState *term.State
|
||||||
width int
|
width int
|
||||||
@@ -123,19 +127,25 @@ type LightWindow struct {
|
|||||||
bg Color
|
bg Color
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLightRenderer(theme *ColorTheme, forceBlack bool, mouse bool, tabstop int, clearOnExit bool, fullscreen bool, maxHeightFunc func(int) int) Renderer {
|
func NewLightRenderer(ttyin *os.File, theme *ColorTheme, forceBlack bool, mouse bool, tabstop int, clearOnExit bool, fullscreen bool, maxHeightFunc func(int) int) (Renderer, error) {
|
||||||
|
out, err := openTtyOut()
|
||||||
|
if err != nil {
|
||||||
|
out = os.Stderr
|
||||||
|
}
|
||||||
r := LightRenderer{
|
r := LightRenderer{
|
||||||
|
closed: util.NewAtomicBool(false),
|
||||||
theme: theme,
|
theme: theme,
|
||||||
forceBlack: forceBlack,
|
forceBlack: forceBlack,
|
||||||
mouse: mouse,
|
mouse: mouse,
|
||||||
clearOnExit: clearOnExit,
|
clearOnExit: clearOnExit,
|
||||||
ttyin: openTtyIn(),
|
ttyin: ttyin,
|
||||||
|
ttyout: out,
|
||||||
yoffset: 0,
|
yoffset: 0,
|
||||||
tabstop: tabstop,
|
tabstop: tabstop,
|
||||||
fullscreen: fullscreen,
|
fullscreen: fullscreen,
|
||||||
upOneLine: false,
|
upOneLine: false,
|
||||||
maxHeightFunc: maxHeightFunc}
|
maxHeightFunc: maxHeightFunc}
|
||||||
return &r
|
return &r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func repeat(r rune, times int) string {
|
func repeat(r rune, times int) string {
|
||||||
@@ -153,11 +163,11 @@ func atoi(s string, defaultValue int) int {
|
|||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Init() {
|
func (r *LightRenderer) Init() error {
|
||||||
r.escDelay = atoi(os.Getenv("ESCDELAY"), defaultEscDelay)
|
r.escDelay = atoi(os.Getenv("ESCDELAY"), defaultEscDelay)
|
||||||
|
|
||||||
if err := r.initPlatform(); err != nil {
|
if err := r.initPlatform(); err != nil {
|
||||||
errorExit(err.Error())
|
return err
|
||||||
}
|
}
|
||||||
r.updateTerminalSize()
|
r.updateTerminalSize()
|
||||||
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
||||||
@@ -195,6 +205,7 @@ func (r *LightRenderer) Init() {
|
|||||||
if !r.fullscreen && r.mouse {
|
if !r.fullscreen && r.mouse {
|
||||||
r.yoffset, _ = r.findOffset()
|
r.yoffset, _ = r.findOffset()
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Resize(maxHeightFunc func(int) int) {
|
func (r *LightRenderer) Resize(maxHeightFunc func(int) int) {
|
||||||
@@ -233,19 +244,20 @@ func getEnv(name string, defaultValue int) int {
|
|||||||
return atoi(env, defaultValue)
|
return atoi(env, defaultValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) getBytes() []byte {
|
func (r *LightRenderer) getBytes() ([]byte, error) {
|
||||||
return r.getBytesInternal(r.buffer, false)
|
bytes, err := r.getBytesInternal(r.buffer, false)
|
||||||
|
return bytes, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
|
func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) ([]byte, error) {
|
||||||
c, ok := r.getch(nonblock)
|
c, ok := r.getch(nonblock)
|
||||||
if !nonblock && !ok {
|
if !nonblock && !ok {
|
||||||
r.Close()
|
r.Close()
|
||||||
errorExit("Failed to read " + consoleDevice)
|
return nil, errors.New("failed to read " + consoleDevice)
|
||||||
}
|
}
|
||||||
|
|
||||||
retries := 0
|
retries := 0
|
||||||
if c == ESC.Int() || nonblock {
|
if c == Esc.Int() || nonblock {
|
||||||
retries = r.escDelay / escPollInterval
|
retries = r.escDelay / escPollInterval
|
||||||
}
|
}
|
||||||
buffer = append(buffer, byte(c))
|
buffer = append(buffer, byte(c))
|
||||||
@@ -260,7 +272,7 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
} else if c == ESC.Int() && pc != c {
|
} else if c == Esc.Int() && pc != c {
|
||||||
retries = r.escDelay / escPollInterval
|
retries = r.escDelay / escPollInterval
|
||||||
} else {
|
} else {
|
||||||
retries = 0
|
retries = 0
|
||||||
@@ -272,19 +284,23 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
|
|||||||
// so terminate fzf immediately.
|
// so terminate fzf immediately.
|
||||||
if len(buffer) > maxInputBuffer {
|
if len(buffer) > maxInputBuffer {
|
||||||
r.Close()
|
r.Close()
|
||||||
panic(fmt.Sprintf("Input buffer overflow (%d): %v", len(buffer), buffer))
|
return nil, fmt.Errorf("input buffer overflow (%d): %v", len(buffer), buffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return buffer
|
return buffer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) GetChar() Event {
|
func (r *LightRenderer) GetChar() Event {
|
||||||
|
var err error
|
||||||
if len(r.buffer) == 0 {
|
if len(r.buffer) == 0 {
|
||||||
r.buffer = r.getBytes()
|
r.buffer, err = r.getBytes()
|
||||||
|
if err != nil {
|
||||||
|
return Event{Fatal, 0, nil}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if len(r.buffer) == 0 {
|
if len(r.buffer) == 0 {
|
||||||
panic("Empty buffer")
|
return Event{Fatal, 0, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
sz := 1
|
sz := 1
|
||||||
@@ -300,7 +316,7 @@ func (r *LightRenderer) GetChar() Event {
|
|||||||
case CtrlQ.Byte():
|
case CtrlQ.Byte():
|
||||||
return Event{CtrlQ, 0, nil}
|
return Event{CtrlQ, 0, nil}
|
||||||
case 127:
|
case 127:
|
||||||
return Event{BSpace, 0, nil}
|
return Event{Backspace, 0, nil}
|
||||||
case 0:
|
case 0:
|
||||||
return Event{CtrlSpace, 0, nil}
|
return Event{CtrlSpace, 0, nil}
|
||||||
case 28:
|
case 28:
|
||||||
@@ -311,11 +327,13 @@ func (r *LightRenderer) GetChar() Event {
|
|||||||
return Event{CtrlCaret, 0, nil}
|
return Event{CtrlCaret, 0, nil}
|
||||||
case 31:
|
case 31:
|
||||||
return Event{CtrlSlash, 0, nil}
|
return Event{CtrlSlash, 0, nil}
|
||||||
case ESC.Byte():
|
case Esc.Byte():
|
||||||
ev := r.escSequence(&sz)
|
ev := r.escSequence(&sz)
|
||||||
// Second chance
|
// Second chance
|
||||||
if ev.Type == Invalid {
|
if ev.Type == Invalid {
|
||||||
r.buffer = r.getBytes()
|
if r.buffer, err = r.getBytes(); err != nil {
|
||||||
|
return Event{Fatal, 0, nil}
|
||||||
|
}
|
||||||
ev = r.escSequence(&sz)
|
ev = r.escSequence(&sz)
|
||||||
}
|
}
|
||||||
return ev
|
return ev
|
||||||
@@ -327,7 +345,7 @@ func (r *LightRenderer) GetChar() Event {
|
|||||||
}
|
}
|
||||||
char, rsz := utf8.DecodeRune(r.buffer)
|
char, rsz := utf8.DecodeRune(r.buffer)
|
||||||
if char == utf8.RuneError {
|
if char == utf8.RuneError {
|
||||||
return Event{ESC, 0, nil}
|
return Event{Esc, 0, nil}
|
||||||
}
|
}
|
||||||
sz = rsz
|
sz = rsz
|
||||||
return Event{Rune, char, nil}
|
return Event{Rune, char, nil}
|
||||||
@@ -335,7 +353,7 @@ func (r *LightRenderer) GetChar() Event {
|
|||||||
|
|
||||||
func (r *LightRenderer) escSequence(sz *int) Event {
|
func (r *LightRenderer) escSequence(sz *int) Event {
|
||||||
if len(r.buffer) < 2 {
|
if len(r.buffer) < 2 {
|
||||||
return Event{ESC, 0, nil}
|
return Event{Esc, 0, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
loc := offsetRegexpBegin.FindIndex(r.buffer)
|
loc := offsetRegexpBegin.FindIndex(r.buffer)
|
||||||
@@ -349,15 +367,15 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
return CtrlAltKey(rune(r.buffer[1] + 'a' - 1))
|
return CtrlAltKey(rune(r.buffer[1] + 'a' - 1))
|
||||||
}
|
}
|
||||||
alt := false
|
alt := false
|
||||||
if len(r.buffer) > 2 && r.buffer[1] == ESC.Byte() {
|
if len(r.buffer) > 2 && r.buffer[1] == Esc.Byte() {
|
||||||
r.buffer = r.buffer[1:]
|
r.buffer = r.buffer[1:]
|
||||||
alt = true
|
alt = true
|
||||||
}
|
}
|
||||||
switch r.buffer[1] {
|
switch r.buffer[1] {
|
||||||
case ESC.Byte():
|
case Esc.Byte():
|
||||||
return Event{ESC, 0, nil}
|
return Event{Esc, 0, nil}
|
||||||
case 127:
|
case 127:
|
||||||
return Event{AltBS, 0, nil}
|
return Event{AltBackspace, 0, nil}
|
||||||
case '[', 'O':
|
case '[', 'O':
|
||||||
if len(r.buffer) < 3 {
|
if len(r.buffer) < 3 {
|
||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
@@ -386,7 +404,7 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
}
|
}
|
||||||
return Event{Up, 0, nil}
|
return Event{Up, 0, nil}
|
||||||
case 'Z':
|
case 'Z':
|
||||||
return Event{BTab, 0, nil}
|
return Event{ShiftTab, 0, nil}
|
||||||
case 'H':
|
case 'H':
|
||||||
return Event{Home, 0, nil}
|
return Event{Home, 0, nil}
|
||||||
case 'F':
|
case 'F':
|
||||||
@@ -434,7 +452,7 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
return Event{Invalid, 0, nil} // INS
|
return Event{Invalid, 0, nil} // INS
|
||||||
case '3':
|
case '3':
|
||||||
if r.buffer[3] == '~' {
|
if r.buffer[3] == '~' {
|
||||||
return Event{Del, 0, nil}
|
return Event{Delete, 0, nil}
|
||||||
}
|
}
|
||||||
if len(r.buffer) == 6 && r.buffer[5] == '~' {
|
if len(r.buffer) == 6 && r.buffer[5] == '~' {
|
||||||
*sz = 6
|
*sz = 6
|
||||||
@@ -442,16 +460,16 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
case '5':
|
case '5':
|
||||||
return Event{CtrlDelete, 0, nil}
|
return Event{CtrlDelete, 0, nil}
|
||||||
case '2':
|
case '2':
|
||||||
return Event{SDelete, 0, nil}
|
return Event{ShiftDelete, 0, nil}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Event{Invalid, 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':
|
||||||
return Event{PgUp, 0, nil}
|
return Event{PageUp, 0, nil}
|
||||||
case '6':
|
case '6':
|
||||||
return Event{PgDn, 0, nil}
|
return Event{PageDown, 0, nil}
|
||||||
case '7':
|
case '7':
|
||||||
return Event{Home, 0, nil}
|
return Event{Home, 0, nil}
|
||||||
case '8':
|
case '8':
|
||||||
@@ -489,16 +507,29 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
}
|
}
|
||||||
*sz = 6
|
*sz = 6
|
||||||
switch r.buffer[4] {
|
switch r.buffer[4] {
|
||||||
case '1', '2', '3', '5':
|
case '1', '2', '3', '4', '5':
|
||||||
|
// Kitty iTerm2 WezTerm
|
||||||
|
// SHIFT-ARROW "\e[1;2D"
|
||||||
|
// ALT-SHIFT-ARROW "\e[1;4D" "\e[1;10D" "\e[1;4D"
|
||||||
|
// CTRL-SHIFT-ARROW "\e[1;6D" N/A
|
||||||
|
// CMD-SHIFT-ARROW "\e[1;10D" N/A N/A ("\e[1;2D")
|
||||||
alt := r.buffer[4] == '3'
|
alt := r.buffer[4] == '3'
|
||||||
altShift := r.buffer[4] == '1' && r.buffer[5] == '0'
|
|
||||||
char := r.buffer[5]
|
char := r.buffer[5]
|
||||||
if altShift {
|
altShift := false
|
||||||
|
if r.buffer[4] == '1' && r.buffer[5] == '0' {
|
||||||
|
altShift = true
|
||||||
if len(r.buffer) < 7 {
|
if len(r.buffer) < 7 {
|
||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
}
|
}
|
||||||
*sz = 7
|
*sz = 7
|
||||||
char = r.buffer[6]
|
char = r.buffer[6]
|
||||||
|
} else if r.buffer[4] == '4' {
|
||||||
|
altShift = true
|
||||||
|
if len(r.buffer) < 6 {
|
||||||
|
return Event{Invalid, 0, nil}
|
||||||
|
}
|
||||||
|
*sz = 6
|
||||||
|
char = r.buffer[5]
|
||||||
}
|
}
|
||||||
switch char {
|
switch char {
|
||||||
case 'A':
|
case 'A':
|
||||||
@@ -506,33 +537,33 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
return Event{AltUp, 0, nil}
|
return Event{AltUp, 0, nil}
|
||||||
}
|
}
|
||||||
if altShift {
|
if altShift {
|
||||||
return Event{AltSUp, 0, nil}
|
return Event{AltShiftUp, 0, nil}
|
||||||
}
|
}
|
||||||
return Event{SUp, 0, nil}
|
return Event{ShiftUp, 0, nil}
|
||||||
case 'B':
|
case 'B':
|
||||||
if alt {
|
if alt {
|
||||||
return Event{AltDown, 0, nil}
|
return Event{AltDown, 0, nil}
|
||||||
}
|
}
|
||||||
if altShift {
|
if altShift {
|
||||||
return Event{AltSDown, 0, nil}
|
return Event{AltShiftDown, 0, nil}
|
||||||
}
|
}
|
||||||
return Event{SDown, 0, nil}
|
return Event{ShiftDown, 0, nil}
|
||||||
case 'C':
|
case 'C':
|
||||||
if alt {
|
if alt {
|
||||||
return Event{AltRight, 0, nil}
|
return Event{AltRight, 0, nil}
|
||||||
}
|
}
|
||||||
if altShift {
|
if altShift {
|
||||||
return Event{AltSRight, 0, nil}
|
return Event{AltShiftRight, 0, nil}
|
||||||
}
|
}
|
||||||
return Event{SRight, 0, nil}
|
return Event{ShiftRight, 0, nil}
|
||||||
case 'D':
|
case 'D':
|
||||||
if alt {
|
if alt {
|
||||||
return Event{AltLeft, 0, nil}
|
return Event{AltLeft, 0, nil}
|
||||||
}
|
}
|
||||||
if altShift {
|
if altShift {
|
||||||
return Event{AltSLeft, 0, nil}
|
return Event{AltShiftLeft, 0, nil}
|
||||||
}
|
}
|
||||||
return Event{SLeft, 0, nil}
|
return Event{ShiftLeft, 0, nil}
|
||||||
}
|
}
|
||||||
} // r.buffer[4]
|
} // r.buffer[4]
|
||||||
} // r.buffer[3]
|
} // r.buffer[3]
|
||||||
@@ -725,6 +756,7 @@ func (r *LightRenderer) Close() {
|
|||||||
r.flush()
|
r.flush()
|
||||||
r.closePlatform()
|
r.closePlatform()
|
||||||
r.restoreTerminal()
|
r.restoreTerminal()
|
||||||
|
r.closed.Set(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Top() int {
|
func (r *LightRenderer) Top() int {
|
||||||
@@ -762,6 +794,9 @@ 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
|
||||||
}
|
}
|
||||||
|
if !w.bg.IsDefault() && w.border.shape != BorderNone {
|
||||||
|
w.Erase()
|
||||||
|
}
|
||||||
w.drawBorder(false)
|
w.drawBorder(false)
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
@@ -812,6 +847,7 @@ func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
|
|||||||
w.Move(0, 0)
|
w.Move(0, 0)
|
||||||
w.CPrint(color, repeat(w.border.top, w.width/hw))
|
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.bottom, w.width/hw))
|
w.CPrint(color, repeat(w.border.bottom, w.width/hw))
|
||||||
@@ -819,21 +855,20 @@ func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) drawBorderVertical(left, right bool) {
|
func (w *LightWindow) drawBorderVertical(left, right bool) {
|
||||||
width := w.width - 2
|
vw := runeWidth(w.border.left)
|
||||||
if !left || !right {
|
|
||||||
width++
|
|
||||||
}
|
|
||||||
color := ColBorder
|
color := ColBorder
|
||||||
if w.preview {
|
if w.preview {
|
||||||
color = ColPreviewBorder
|
color = ColPreviewBorder
|
||||||
}
|
}
|
||||||
for y := 0; y < w.height; y++ {
|
for y := 0; y < w.height; y++ {
|
||||||
w.Move(y, 0)
|
|
||||||
if left {
|
if left {
|
||||||
|
w.Move(y, 0)
|
||||||
w.CPrint(color, string(w.border.left))
|
w.CPrint(color, string(w.border.left))
|
||||||
|
w.CPrint(color, " ") // Margin
|
||||||
}
|
}
|
||||||
w.CPrint(color, repeat(' ', width))
|
|
||||||
if right {
|
if right {
|
||||||
|
w.Move(y, w.width-vw-1)
|
||||||
|
w.CPrint(color, " ") // Margin
|
||||||
w.CPrint(color, string(w.border.right))
|
w.CPrint(color, string(w.border.right))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -855,7 +890,10 @@ func (w *LightWindow) drawBorderAround(onlyHorizontal bool) {
|
|||||||
for y := 1; y < w.height-1; y++ {
|
for y := 1; y < w.height-1; y++ {
|
||||||
w.Move(y, 0)
|
w.Move(y, 0)
|
||||||
w.CPrint(color, string(w.border.left))
|
w.CPrint(color, string(w.border.left))
|
||||||
w.CPrint(color, repeat(' ', w.width-vw*2))
|
w.CPrint(color, " ") // Margin
|
||||||
|
|
||||||
|
w.Move(y, w.width-vw-1)
|
||||||
|
w.CPrint(color, " ") // Margin
|
||||||
w.CPrint(color, string(w.border.right))
|
w.CPrint(color, string(w.border.right))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -986,7 +1024,7 @@ func (w *LightWindow) Print(text string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func cleanse(str string) string {
|
func cleanse(str string) string {
|
||||||
return strings.Replace(str, "\x1b", "", -1)
|
return strings.ReplaceAll(str, "\x1b", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) CPrint(pair ColorPair, text string) {
|
func (w *LightWindow) CPrint(pair ColorPair, text string) {
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
package tui
|
package tui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -33,34 +33,35 @@ func (r *LightRenderer) fd() int {
|
|||||||
return int(r.ttyin.Fd())
|
return int(r.ttyin.Fd())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) initPlatform() error {
|
func (r *LightRenderer) initPlatform() (err error) {
|
||||||
fd := r.fd()
|
r.origState, err = term.MakeRaw(r.fd())
|
||||||
origState, err := term.GetState(fd)
|
return err
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.origState = origState
|
|
||||||
term.MakeRaw(fd)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) closePlatform() {
|
func (r *LightRenderer) closePlatform() {
|
||||||
// NOOP
|
r.ttyout.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func openTtyIn() *os.File {
|
func openTty(mode int) (*os.File, error) {
|
||||||
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
|
in, err := os.OpenFile(consoleDevice, mode, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tty := ttyname()
|
tty := ttyname()
|
||||||
if len(tty) > 0 {
|
if len(tty) > 0 {
|
||||||
if in, err := os.OpenFile(tty, syscall.O_RDONLY, 0); err == nil {
|
if in, err := os.OpenFile(tty, mode, 0); err == nil {
|
||||||
return in
|
return in, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Fprintln(os.Stderr, "Failed to open "+consoleDevice)
|
return nil, errors.New("failed to open " + consoleDevice)
|
||||||
os.Exit(2)
|
|
||||||
}
|
}
|
||||||
return in
|
return in, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func openTtyIn() (*os.File, error) {
|
||||||
|
return openTty(syscall.O_RDONLY)
|
||||||
|
}
|
||||||
|
|
||||||
|
func openTtyOut() (*os.File, error) {
|
||||||
|
return openTty(syscall.O_WRONLY)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) setupTerminal() {
|
func (r *LightRenderer) setupTerminal() {
|
||||||
@@ -86,9 +87,14 @@ func (r *LightRenderer) updateTerminalSize() {
|
|||||||
func (r *LightRenderer) findOffset() (row int, col int) {
|
func (r *LightRenderer) findOffset() (row int, col int) {
|
||||||
r.csi("6n")
|
r.csi("6n")
|
||||||
r.flush()
|
r.flush()
|
||||||
|
var err error
|
||||||
bytes := []byte{}
|
bytes := []byte{}
|
||||||
for tries := 0; tries < offsetPollTries; tries++ {
|
for tries := 0; tries < offsetPollTries; tries++ {
|
||||||
bytes = r.getBytesInternal(bytes, tries > 0)
|
bytes, err = r.getBytesInternal(bytes, tries > 0)
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1
|
||||||
|
}
|
||||||
|
|
||||||
offsets := offsetRegexp.FindSubmatch(bytes)
|
offsets := offsetRegexp.FindSubmatch(bytes)
|
||||||
if len(offsets) > 3 {
|
if len(offsets) > 3 {
|
||||||
// Add anything we skipped over to the input buffer
|
// Add anything we skipped over to the input buffer
|
||||||
|
@@ -72,7 +72,7 @@ func (r *LightRenderer) initPlatform() error {
|
|||||||
go func() {
|
go func() {
|
||||||
fd := int(r.inHandle)
|
fd := int(r.inHandle)
|
||||||
b := make([]byte, 1)
|
b := make([]byte, 1)
|
||||||
for {
|
for !r.closed.Get() {
|
||||||
// HACK: if run from PSReadline, something resets ConsoleMode to remove ENABLE_VIRTUAL_TERMINAL_INPUT.
|
// HACK: if run from PSReadline, something resets ConsoleMode to remove ENABLE_VIRTUAL_TERMINAL_INPUT.
|
||||||
_ = windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
|
_ = windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
|
||||||
|
|
||||||
@@ -91,9 +91,13 @@ func (r *LightRenderer) closePlatform() {
|
|||||||
windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput)
|
windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput)
|
||||||
}
|
}
|
||||||
|
|
||||||
func openTtyIn() *os.File {
|
func openTtyIn() (*os.File, error) {
|
||||||
// not used
|
// not used
|
||||||
return nil
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func openTtyOut() (*os.File, error) {
|
||||||
|
return os.Stderr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) setupTerminal() error {
|
func (r *LightRenderer) setupTerminal() error {
|
||||||
@@ -135,7 +139,7 @@ func (r *LightRenderer) findOffset() (row int, col int) {
|
|||||||
if err := windows.GetConsoleScreenBufferInfo(windows.Handle(r.outHandle), &bufferInfo); err != nil {
|
if err := windows.GetConsoleScreenBufferInfo(windows.Handle(r.outHandle), &bufferInfo); err != nil {
|
||||||
return -1, -1
|
return -1, -1
|
||||||
}
|
}
|
||||||
return int(bufferInfo.CursorPosition.X), int(bufferInfo.CursorPosition.Y)
|
return int(bufferInfo.CursorPosition.Y), int(bufferInfo.CursorPosition.X)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) getch(nonblock bool) (int, bool) {
|
func (r *LightRenderer) getch(nonblock bool) (int, bool) {
|
||||||
|
@@ -7,7 +7,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
"github.com/gdamore/tcell/v2/encoding"
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
|
||||||
"github.com/rivo/uniseg"
|
"github.com/rivo/uniseg"
|
||||||
@@ -146,13 +145,13 @@ var (
|
|||||||
_initialResize bool = true
|
_initialResize bool = true
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *FullscreenRenderer) initScreen() {
|
func (r *FullscreenRenderer) initScreen() error {
|
||||||
s, e := tcell.NewScreen()
|
s, e := tcell.NewScreen()
|
||||||
if e != nil {
|
if e != nil {
|
||||||
errorExit(e.Error())
|
return e
|
||||||
}
|
}
|
||||||
if e = s.Init(); e != nil {
|
if e = s.Init(); e != nil {
|
||||||
errorExit(e.Error())
|
return e
|
||||||
}
|
}
|
||||||
if r.mouse {
|
if r.mouse {
|
||||||
s.EnableMouse()
|
s.EnableMouse()
|
||||||
@@ -160,16 +159,21 @@ func (r *FullscreenRenderer) initScreen() {
|
|||||||
s.DisableMouse()
|
s.DisableMouse()
|
||||||
}
|
}
|
||||||
_screen = s
|
_screen = s
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Init() {
|
func (r *FullscreenRenderer) Init() error {
|
||||||
if os.Getenv("TERM") == "cygwin" {
|
if os.Getenv("TERM") == "cygwin" {
|
||||||
os.Setenv("TERM", "")
|
os.Setenv("TERM", "")
|
||||||
}
|
}
|
||||||
encoding.Register()
|
|
||||||
|
|
||||||
r.initScreen()
|
if err := r.initScreen(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Top() int {
|
func (r *FullscreenRenderer) Top() int {
|
||||||
@@ -320,16 +324,16 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
switch ev.Rune() {
|
switch ev.Rune() {
|
||||||
case 0:
|
case 0:
|
||||||
if ctrl {
|
if ctrl {
|
||||||
return Event{BSpace, 0, nil}
|
return Event{Backspace, 0, nil}
|
||||||
}
|
}
|
||||||
case rune(tcell.KeyCtrlH):
|
case rune(tcell.KeyCtrlH):
|
||||||
switch {
|
switch {
|
||||||
case ctrl:
|
case ctrl:
|
||||||
return keyfn('h')
|
return keyfn('h')
|
||||||
case alt:
|
case alt:
|
||||||
return Event{AltBS, 0, nil}
|
return Event{AltBackspace, 0, nil}
|
||||||
case none, shift:
|
case none, shift:
|
||||||
return Event{BSpace, 0, nil}
|
return Event{Backspace, 0, nil}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case tcell.KeyCtrlI:
|
case tcell.KeyCtrlI:
|
||||||
@@ -382,17 +386,17 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
// section 3: (Alt)+Backspace2
|
// section 3: (Alt)+Backspace2
|
||||||
case tcell.KeyBackspace2:
|
case tcell.KeyBackspace2:
|
||||||
if alt {
|
if alt {
|
||||||
return Event{AltBS, 0, nil}
|
return Event{AltBackspace, 0, nil}
|
||||||
}
|
}
|
||||||
return Event{BSpace, 0, nil}
|
return Event{Backspace, 0, nil}
|
||||||
|
|
||||||
// section 4: (Alt+Shift)+Key(Up|Down|Left|Right)
|
// section 4: (Alt+Shift)+Key(Up|Down|Left|Right)
|
||||||
case tcell.KeyUp:
|
case tcell.KeyUp:
|
||||||
if altShift {
|
if altShift {
|
||||||
return Event{AltSUp, 0, nil}
|
return Event{AltShiftUp, 0, nil}
|
||||||
}
|
}
|
||||||
if shift {
|
if shift {
|
||||||
return Event{SUp, 0, nil}
|
return Event{ShiftUp, 0, nil}
|
||||||
}
|
}
|
||||||
if alt {
|
if alt {
|
||||||
return Event{AltUp, 0, nil}
|
return Event{AltUp, 0, nil}
|
||||||
@@ -400,10 +404,10 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
return Event{Up, 0, nil}
|
return Event{Up, 0, nil}
|
||||||
case tcell.KeyDown:
|
case tcell.KeyDown:
|
||||||
if altShift {
|
if altShift {
|
||||||
return Event{AltSDown, 0, nil}
|
return Event{AltShiftDown, 0, nil}
|
||||||
}
|
}
|
||||||
if shift {
|
if shift {
|
||||||
return Event{SDown, 0, nil}
|
return Event{ShiftDown, 0, nil}
|
||||||
}
|
}
|
||||||
if alt {
|
if alt {
|
||||||
return Event{AltDown, 0, nil}
|
return Event{AltDown, 0, nil}
|
||||||
@@ -411,10 +415,10 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
return Event{Down, 0, nil}
|
return Event{Down, 0, nil}
|
||||||
case tcell.KeyLeft:
|
case tcell.KeyLeft:
|
||||||
if altShift {
|
if altShift {
|
||||||
return Event{AltSLeft, 0, nil}
|
return Event{AltShiftLeft, 0, nil}
|
||||||
}
|
}
|
||||||
if shift {
|
if shift {
|
||||||
return Event{SLeft, 0, nil}
|
return Event{ShiftLeft, 0, nil}
|
||||||
}
|
}
|
||||||
if alt {
|
if alt {
|
||||||
return Event{AltLeft, 0, nil}
|
return Event{AltLeft, 0, nil}
|
||||||
@@ -422,10 +426,10 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
return Event{Left, 0, nil}
|
return Event{Left, 0, nil}
|
||||||
case tcell.KeyRight:
|
case tcell.KeyRight:
|
||||||
if altShift {
|
if altShift {
|
||||||
return Event{AltSRight, 0, nil}
|
return Event{AltShiftRight, 0, nil}
|
||||||
}
|
}
|
||||||
if shift {
|
if shift {
|
||||||
return Event{SRight, 0, nil}
|
return Event{ShiftRight, 0, nil}
|
||||||
}
|
}
|
||||||
if alt {
|
if alt {
|
||||||
return Event{AltRight, 0, nil}
|
return Event{AltRight, 0, nil}
|
||||||
@@ -442,17 +446,17 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
return Event{CtrlDelete, 0, nil}
|
return Event{CtrlDelete, 0, nil}
|
||||||
}
|
}
|
||||||
if shift {
|
if shift {
|
||||||
return Event{SDelete, 0, nil}
|
return Event{ShiftDelete, 0, nil}
|
||||||
}
|
}
|
||||||
return Event{Del, 0, nil}
|
return Event{Delete, 0, nil}
|
||||||
case tcell.KeyEnd:
|
case tcell.KeyEnd:
|
||||||
return Event{End, 0, nil}
|
return Event{End, 0, nil}
|
||||||
case tcell.KeyPgUp:
|
case tcell.KeyPgUp:
|
||||||
return Event{PgUp, 0, nil}
|
return Event{PageUp, 0, nil}
|
||||||
case tcell.KeyPgDn:
|
case tcell.KeyPgDn:
|
||||||
return Event{PgDn, 0, nil}
|
return Event{PageDown, 0, nil}
|
||||||
case tcell.KeyBacktab:
|
case tcell.KeyBacktab:
|
||||||
return Event{BTab, 0, nil}
|
return Event{ShiftTab, 0, nil}
|
||||||
case tcell.KeyF1:
|
case tcell.KeyF1:
|
||||||
return Event{F1, 0, nil}
|
return Event{F1, 0, nil}
|
||||||
case tcell.KeyF2:
|
case tcell.KeyF2:
|
||||||
@@ -498,7 +502,7 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
|
|
||||||
// section 7: Esc
|
// section 7: Esc
|
||||||
case tcell.KeyEsc:
|
case tcell.KeyEsc:
|
||||||
return Event{ESC, 0, nil}
|
return Event{Esc, 0, nil}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -544,7 +548,7 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int,
|
|||||||
height: height,
|
height: height,
|
||||||
normal: normal,
|
normal: normal,
|
||||||
borderStyle: borderStyle}
|
borderStyle: borderStyle}
|
||||||
w.drawBorder(false)
|
w.Erase()
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -561,8 +565,12 @@ func fill(x, y, w, h int, n ColorPair, r rune) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Erase() {
|
func (w *TcellWindow) Erase() {
|
||||||
|
if w.borderStyle.shape.HasLeft() {
|
||||||
|
fill(w.left-1, w.top, w.width, w.height-1, w.normal, ' ')
|
||||||
|
} else {
|
||||||
|
fill(w.left, w.top, w.width-1, w.height-1, w.normal, ' ')
|
||||||
|
}
|
||||||
w.drawBorder(false)
|
w.drawBorder(false)
|
||||||
fill(w.left-1, w.top, w.width+1, w.height-1, w.normal, ' ')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) EraseMaybe() bool {
|
func (w *TcellWindow) EraseMaybe() bool {
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
package tui
|
package tui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
@@ -20,7 +21,7 @@ func assert(t *testing.T, context string, got interface{}, want interface{}) boo
|
|||||||
|
|
||||||
// Test the handling of the tcell keyboard events.
|
// Test the handling of the tcell keyboard events.
|
||||||
func TestGetCharEventKey(t *testing.T) {
|
func TestGetCharEventKey(t *testing.T) {
|
||||||
if util.ToTty() {
|
if util.IsTty(os.Stdout) {
|
||||||
// This test is skipped when output goes to terminal, because it causes
|
// This test is skipped when output goes to terminal, because it causes
|
||||||
// some glitches:
|
// some glitches:
|
||||||
// - output lines may not start at the beginning of a row which makes
|
// - output lines may not start at the beginning of a row which makes
|
||||||
@@ -102,22 +103,22 @@ func TestGetCharEventKey(t *testing.T) {
|
|||||||
// KeyBackspace2 is alias for KeyDEL = 0x7F (ASCII) (allegedly unused by Windows)
|
// KeyBackspace2 is alias for KeyDEL = 0x7F (ASCII) (allegedly unused by Windows)
|
||||||
// KeyDelete = 0x2E (VK_DELETE constant in Windows)
|
// KeyDelete = 0x2E (VK_DELETE constant in Windows)
|
||||||
// KeyBackspace is alias for KeyBS = 0x08 (ASCII) (implicit alias with KeyCtrlH)
|
// KeyBackspace is alias for KeyBS = 0x08 (ASCII) (implicit alias with KeyCtrlH)
|
||||||
{giveKey{tcell.KeyBackspace2, 0, tcell.ModNone}, wantKey{BSpace, 0, nil}}, // fabricated
|
{giveKey{tcell.KeyBackspace2, 0, tcell.ModNone}, wantKey{Backspace, 0, nil}}, // fabricated
|
||||||
{giveKey{tcell.KeyBackspace2, 0, tcell.ModAlt}, wantKey{AltBS, 0, nil}}, // fabricated
|
{giveKey{tcell.KeyBackspace2, 0, tcell.ModAlt}, wantKey{AltBackspace, 0, nil}}, // fabricated
|
||||||
{giveKey{tcell.KeyDEL, 0, tcell.ModNone}, wantKey{BSpace, 0, nil}}, // fabricated, unhandled
|
{giveKey{tcell.KeyDEL, 0, tcell.ModNone}, wantKey{Backspace, 0, nil}}, // fabricated, unhandled
|
||||||
{giveKey{tcell.KeyDelete, 0, tcell.ModNone}, wantKey{Del, 0, nil}},
|
{giveKey{tcell.KeyDelete, 0, tcell.ModNone}, wantKey{Delete, 0, nil}},
|
||||||
{giveKey{tcell.KeyDelete, 0, tcell.ModAlt}, wantKey{Del, 0, nil}},
|
{giveKey{tcell.KeyDelete, 0, tcell.ModAlt}, wantKey{Delete, 0, nil}},
|
||||||
{giveKey{tcell.KeyBackspace, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
{giveKey{tcell.KeyBackspace, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||||
{giveKey{tcell.KeyBS, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
{giveKey{tcell.KeyBS, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||||
{giveKey{tcell.KeyCtrlH, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
{giveKey{tcell.KeyCtrlH, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModNone}, wantKey{BSpace, 0, nil}}, // actual "Backspace" keystroke
|
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModNone}, wantKey{Backspace, 0, nil}}, // actual "Backspace" keystroke
|
||||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModAlt}, wantKey{AltBS, 0, nil}}, // actual "Alt+Backspace" keystroke
|
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModAlt}, wantKey{AltBackspace, 0, nil}}, // actual "Alt+Backspace" keystroke
|
||||||
{giveKey{tcell.KeyDEL, rune(tcell.KeyDEL), tcell.ModCtrl}, wantKey{BSpace, 0, nil}}, // actual "Ctrl+Backspace" keystroke
|
{giveKey{tcell.KeyDEL, rune(tcell.KeyDEL), tcell.ModCtrl}, wantKey{Backspace, 0, nil}}, // actual "Ctrl+Backspace" keystroke
|
||||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModShift}, wantKey{BSpace, 0, nil}}, // actual "Shift+Backspace" keystroke
|
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModShift}, wantKey{Backspace, 0, nil}}, // actual "Shift+Backspace" keystroke
|
||||||
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModAlt}, wantKey{BSpace, 0, nil}}, // actual "Ctrl+Alt+Backspace" keystroke
|
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModAlt}, wantKey{Backspace, 0, nil}}, // actual "Ctrl+Alt+Backspace" keystroke
|
||||||
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModShift}, wantKey{BSpace, 0, nil}}, // actual "Ctrl+Shift+Backspace" keystroke
|
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModShift}, wantKey{Backspace, 0, nil}}, // actual "Ctrl+Shift+Backspace" keystroke
|
||||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModShift | tcell.ModAlt}, wantKey{AltBS, 0, nil}}, // actual "Shift+Alt+Backspace" keystroke
|
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModShift | tcell.ModAlt}, wantKey{AltBackspace, 0, nil}}, // actual "Shift+Alt+Backspace" keystroke
|
||||||
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModAlt | tcell.ModShift}, wantKey{BSpace, 0, nil}}, // actual "Ctrl+Shift+Alt+Backspace" keystroke
|
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModAlt | tcell.ModShift}, wantKey{Backspace, 0, nil}}, // actual "Ctrl+Shift+Alt+Backspace" keystroke
|
||||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl}, wantKey{CtrlH, 0, nil}}, // actual "Ctrl+H" keystroke
|
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl}, wantKey{CtrlH, 0, nil}}, // actual "Ctrl+H" keystroke
|
||||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAlt, 'h', nil}}, // fabricated "Ctrl+Alt+H" keystroke
|
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAlt, 'h', nil}}, // fabricated "Ctrl+Alt+H" keystroke
|
||||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl | tcell.ModShift}, wantKey{CtrlH, 0, nil}}, // actual "Ctrl+Shift+H" keystroke
|
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl | tcell.ModShift}, wantKey{CtrlH, 0, nil}}, // actual "Ctrl+Shift+H" keystroke
|
||||||
@@ -126,8 +127,8 @@ func TestGetCharEventKey(t *testing.T) {
|
|||||||
// section 4: (Alt+Shift)+Key(Up|Down|Left|Right)
|
// section 4: (Alt+Shift)+Key(Up|Down|Left|Right)
|
||||||
{giveKey{tcell.KeyUp, 0, tcell.ModNone}, wantKey{Up, 0, nil}},
|
{giveKey{tcell.KeyUp, 0, tcell.ModNone}, wantKey{Up, 0, nil}},
|
||||||
{giveKey{tcell.KeyDown, 0, tcell.ModAlt}, wantKey{AltDown, 0, nil}},
|
{giveKey{tcell.KeyDown, 0, tcell.ModAlt}, wantKey{AltDown, 0, nil}},
|
||||||
{giveKey{tcell.KeyLeft, 0, tcell.ModShift}, wantKey{SLeft, 0, nil}},
|
{giveKey{tcell.KeyLeft, 0, tcell.ModShift}, wantKey{ShiftLeft, 0, nil}},
|
||||||
{giveKey{tcell.KeyRight, 0, tcell.ModShift | tcell.ModAlt}, wantKey{AltSRight, 0, nil}},
|
{giveKey{tcell.KeyRight, 0, tcell.ModShift | tcell.ModAlt}, wantKey{AltShiftRight, 0, nil}},
|
||||||
{giveKey{tcell.KeyUpLeft, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
{giveKey{tcell.KeyUpLeft, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||||
{giveKey{tcell.KeyUpRight, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
{giveKey{tcell.KeyUpRight, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||||
{giveKey{tcell.KeyDownLeft, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
{giveKey{tcell.KeyDownLeft, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||||
@@ -161,11 +162,11 @@ func TestGetCharEventKey(t *testing.T) {
|
|||||||
|
|
||||||
// section 7: Esc
|
// section 7: Esc
|
||||||
// KeyEsc and KeyEscape are aliases for KeyESC
|
// KeyEsc and KeyEscape are aliases for KeyESC
|
||||||
{giveKey{tcell.KeyEsc, rune(tcell.KeyEsc), tcell.ModNone}, wantKey{ESC, 0, nil}}, // fabricated
|
{giveKey{tcell.KeyEsc, rune(tcell.KeyEsc), tcell.ModNone}, wantKey{Esc, 0, nil}}, // fabricated
|
||||||
{giveKey{tcell.KeyESC, rune(tcell.KeyESC), tcell.ModNone}, wantKey{ESC, 0, nil}}, // unhandled
|
{giveKey{tcell.KeyESC, rune(tcell.KeyESC), tcell.ModNone}, wantKey{Esc, 0, nil}}, // unhandled
|
||||||
{giveKey{tcell.KeyEscape, rune(tcell.KeyEscape), tcell.ModNone}, wantKey{ESC, 0, nil}}, // fabricated, unhandled
|
{giveKey{tcell.KeyEscape, rune(tcell.KeyEscape), tcell.ModNone}, wantKey{Esc, 0, nil}}, // fabricated, unhandled
|
||||||
{giveKey{tcell.KeyESC, rune(tcell.KeyESC), tcell.ModCtrl}, wantKey{ESC, 0, nil}}, // actual Ctrl+[ keystroke
|
{giveKey{tcell.KeyESC, rune(tcell.KeyESC), tcell.ModCtrl}, wantKey{Esc, 0, nil}}, // actual Ctrl+[ keystroke
|
||||||
{giveKey{tcell.KeyCtrlLeftSq, rune(tcell.KeyCtrlLeftSq), tcell.ModCtrl}, wantKey{ESC, 0, nil}}, // fabricated, unhandled
|
{giveKey{tcell.KeyCtrlLeftSq, rune(tcell.KeyCtrlLeftSq), tcell.ModCtrl}, wantKey{Esc, 0, nil}}, // fabricated, unhandled
|
||||||
|
|
||||||
// section 8: Invalid
|
// section 8: Invalid
|
||||||
{giveKey{tcell.KeyRune, 'a', tcell.ModMeta}, wantKey{Rune, 'a', nil}}, // fabricated
|
{giveKey{tcell.KeyRune, 'a', tcell.ModMeta}, wantKey{Rune, 'a', nil}}, // fabricated
|
||||||
@@ -259,7 +260,7 @@ Quick reference
|
|||||||
37 LeftClick
|
37 LeftClick
|
||||||
38 RightClick
|
38 RightClick
|
||||||
39 BTab
|
39 BTab
|
||||||
40 BSpace
|
40 Backspace
|
||||||
41 Del
|
41 Del
|
||||||
42 PgUp
|
42 PgUp
|
||||||
43 PgDn
|
43 PgDn
|
||||||
@@ -272,7 +273,7 @@ Quick reference
|
|||||||
50 Insert
|
50 Insert
|
||||||
51 SUp
|
51 SUp
|
||||||
52 SDown
|
52 SDown
|
||||||
53 SLeft
|
53 ShiftLeft
|
||||||
54 SRight
|
54 SRight
|
||||||
55 F1
|
55 F1
|
||||||
56 F2
|
56 F2
|
||||||
@@ -288,15 +289,15 @@ Quick reference
|
|||||||
66 F12
|
66 F12
|
||||||
67 Change
|
67 Change
|
||||||
68 BackwardEOF
|
68 BackwardEOF
|
||||||
69 AltBS
|
69 AltBackspace
|
||||||
70 AltUp
|
70 AltUp
|
||||||
71 AltDown
|
71 AltDown
|
||||||
72 AltLeft
|
72 AltLeft
|
||||||
73 AltRight
|
73 AltRight
|
||||||
74 AltSUp
|
74 AltSUp
|
||||||
75 AltSDown
|
75 AltSDown
|
||||||
76 AltSLeft
|
76 AltShiftLeft
|
||||||
77 AltSRight
|
77 AltShiftRight
|
||||||
78 Alt
|
78 Alt
|
||||||
79 CtrlAlt
|
79 CtrlAlt
|
||||||
..
|
..
|
||||||
|
@@ -4,12 +4,19 @@ package tui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
var devPrefixes = [...]string{"/dev/pts/", "/dev/"}
|
var devPrefixes = [...]string{"/dev/pts/", "/dev/"}
|
||||||
|
|
||||||
|
var tty atomic.Value
|
||||||
|
|
||||||
func ttyname() string {
|
func ttyname() string {
|
||||||
|
if cached := tty.Load(); cached != nil {
|
||||||
|
return cached.(string)
|
||||||
|
}
|
||||||
|
|
||||||
var stderr syscall.Stat_t
|
var stderr syscall.Stat_t
|
||||||
if syscall.Fstat(2, &stderr) != nil {
|
if syscall.Fstat(2, &stderr) != nil {
|
||||||
return ""
|
return ""
|
||||||
@@ -27,24 +34,21 @@ func ttyname() string {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if stat, ok := info.Sys().(*syscall.Stat_t); ok && stat.Rdev == stderr.Rdev {
|
if stat, ok := info.Sys().(*syscall.Stat_t); ok && stat.Rdev == stderr.Rdev {
|
||||||
return prefix + file.Name()
|
value := prefix + file.Name()
|
||||||
|
tty.Store(value)
|
||||||
|
return value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// TtyIn returns terminal device to be used as STDIN, falls back to os.Stdin
|
// TtyIn returns terminal device to read user input
|
||||||
func TtyIn() *os.File {
|
func TtyIn() (*os.File, error) {
|
||||||
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
|
return openTtyIn()
|
||||||
if err != nil {
|
}
|
||||||
tty := ttyname()
|
|
||||||
if len(tty) > 0 {
|
// TtyIn returns terminal device to write to
|
||||||
if in, err := os.OpenFile(tty, syscall.O_RDONLY, 0); err == nil {
|
func TtyOut() (*os.File, error) {
|
||||||
return in
|
return openTtyOut()
|
||||||
}
|
|
||||||
}
|
|
||||||
return os.Stdin
|
|
||||||
}
|
|
||||||
return in
|
|
||||||
}
|
}
|
||||||
|
@@ -2,13 +2,20 @@
|
|||||||
|
|
||||||
package tui
|
package tui
|
||||||
|
|
||||||
import "os"
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
func ttyname() string {
|
func ttyname() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// TtyIn on Windows returns os.Stdin
|
// TtyIn on Windows returns os.Stdin
|
||||||
func TtyIn() *os.File {
|
func TtyIn() (*os.File, error) {
|
||||||
return os.Stdin
|
return os.Stdin, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TtyIn on Windows returns nil
|
||||||
|
func TtyOut() (*os.File, error) {
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
194
src/tui/tui.go
194
src/tui/tui.go
@@ -1,15 +1,16 @@
|
|||||||
package tui
|
package tui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
"github.com/rivo/uniseg"
|
"github.com/rivo/uniseg"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Types of user action
|
// Types of user action
|
||||||
|
//
|
||||||
|
//go:generate stringer -type=EventType
|
||||||
type EventType int
|
type EventType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -41,7 +42,7 @@ const (
|
|||||||
CtrlX
|
CtrlX
|
||||||
CtrlY
|
CtrlY
|
||||||
CtrlZ
|
CtrlZ
|
||||||
ESC
|
Esc
|
||||||
CtrlSpace
|
CtrlSpace
|
||||||
CtrlDelete
|
CtrlDelete
|
||||||
|
|
||||||
@@ -51,27 +52,12 @@ const (
|
|||||||
CtrlCaret
|
CtrlCaret
|
||||||
CtrlSlash
|
CtrlSlash
|
||||||
|
|
||||||
Invalid
|
ShiftTab
|
||||||
Resize
|
Backspace
|
||||||
Mouse
|
|
||||||
DoubleClick
|
|
||||||
LeftClick
|
|
||||||
RightClick
|
|
||||||
SLeftClick
|
|
||||||
SRightClick
|
|
||||||
ScrollUp
|
|
||||||
ScrollDown
|
|
||||||
SScrollUp
|
|
||||||
SScrollDown
|
|
||||||
PreviewScrollUp
|
|
||||||
PreviewScrollDown
|
|
||||||
|
|
||||||
BTab
|
Delete
|
||||||
BSpace
|
PageUp
|
||||||
|
PageDown
|
||||||
Del
|
|
||||||
PgUp
|
|
||||||
PgDn
|
|
||||||
|
|
||||||
Up
|
Up
|
||||||
Down
|
Down
|
||||||
@@ -81,11 +67,11 @@ const (
|
|||||||
End
|
End
|
||||||
Insert
|
Insert
|
||||||
|
|
||||||
SUp
|
ShiftUp
|
||||||
SDown
|
ShiftDown
|
||||||
SLeft
|
ShiftLeft
|
||||||
SRight
|
ShiftRight
|
||||||
SDelete
|
ShiftDelete
|
||||||
|
|
||||||
F1
|
F1
|
||||||
F2
|
F2
|
||||||
@@ -100,6 +86,39 @@ const (
|
|||||||
F11
|
F11
|
||||||
F12
|
F12
|
||||||
|
|
||||||
|
AltBackspace
|
||||||
|
|
||||||
|
AltUp
|
||||||
|
AltDown
|
||||||
|
AltLeft
|
||||||
|
AltRight
|
||||||
|
|
||||||
|
AltShiftUp
|
||||||
|
AltShiftDown
|
||||||
|
AltShiftLeft
|
||||||
|
AltShiftRight
|
||||||
|
|
||||||
|
Alt
|
||||||
|
CtrlAlt
|
||||||
|
|
||||||
|
Invalid
|
||||||
|
Fatal
|
||||||
|
|
||||||
|
Mouse
|
||||||
|
DoubleClick
|
||||||
|
LeftClick
|
||||||
|
RightClick
|
||||||
|
SLeftClick
|
||||||
|
SRightClick
|
||||||
|
ScrollUp
|
||||||
|
ScrollDown
|
||||||
|
SScrollUp
|
||||||
|
SScrollDown
|
||||||
|
PreviewScrollUp
|
||||||
|
PreviewScrollDown
|
||||||
|
|
||||||
|
// Events
|
||||||
|
Resize
|
||||||
Change
|
Change
|
||||||
BackwardEOF
|
BackwardEOF
|
||||||
Start
|
Start
|
||||||
@@ -108,21 +127,9 @@ const (
|
|||||||
One
|
One
|
||||||
Zero
|
Zero
|
||||||
Result
|
Result
|
||||||
|
Jump
|
||||||
AltBS
|
JumpCancel
|
||||||
|
ClickHeader
|
||||||
AltUp
|
|
||||||
AltDown
|
|
||||||
AltLeft
|
|
||||||
AltRight
|
|
||||||
|
|
||||||
AltSUp
|
|
||||||
AltSDown
|
|
||||||
AltSLeft
|
|
||||||
AltSRight
|
|
||||||
|
|
||||||
Alt
|
|
||||||
CtrlAlt
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t EventType) AsEvent() Event {
|
func (t EventType) AsEvent() Event {
|
||||||
@@ -142,6 +149,31 @@ func (e Event) Comparable() Event {
|
|||||||
return Event{e.Type, e.Char, nil}
|
return Event{e.Type, e.Char, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e Event) KeyName() string {
|
||||||
|
if e.Type >= Invalid {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
switch e.Type {
|
||||||
|
case Rune:
|
||||||
|
return string(e.Char)
|
||||||
|
case Alt:
|
||||||
|
return "alt-" + string(e.Char)
|
||||||
|
case CtrlAlt:
|
||||||
|
return "ctrl-alt-" + string(e.Char)
|
||||||
|
case CtrlBackSlash:
|
||||||
|
return "ctrl-\\"
|
||||||
|
case CtrlRightBracket:
|
||||||
|
return "ctrl-]"
|
||||||
|
case CtrlCaret:
|
||||||
|
return "ctrl-^"
|
||||||
|
case CtrlSlash:
|
||||||
|
return "ctrl-/"
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.ToKebabCase(e.Type.String())
|
||||||
|
}
|
||||||
|
|
||||||
func Key(r rune) Event {
|
func Key(r rune) Event {
|
||||||
return Event{Rune, r, nil}
|
return Event{Rune, r, nil}
|
||||||
}
|
}
|
||||||
@@ -271,6 +303,9 @@ type ColorTheme struct {
|
|||||||
Disabled ColorAttr
|
Disabled ColorAttr
|
||||||
Fg ColorAttr
|
Fg ColorAttr
|
||||||
Bg ColorAttr
|
Bg ColorAttr
|
||||||
|
SelectedFg ColorAttr
|
||||||
|
SelectedBg ColorAttr
|
||||||
|
SelectedMatch ColorAttr
|
||||||
PreviewFg ColorAttr
|
PreviewFg ColorAttr
|
||||||
PreviewBg ColorAttr
|
PreviewBg ColorAttr
|
||||||
DarkBg ColorAttr
|
DarkBg ColorAttr
|
||||||
@@ -282,7 +317,7 @@ type ColorTheme struct {
|
|||||||
Spinner ColorAttr
|
Spinner ColorAttr
|
||||||
Info ColorAttr
|
Info ColorAttr
|
||||||
Cursor ColorAttr
|
Cursor ColorAttr
|
||||||
Selected ColorAttr
|
Marker ColorAttr
|
||||||
Header ColorAttr
|
Header ColorAttr
|
||||||
Separator ColorAttr
|
Separator ColorAttr
|
||||||
Scrollbar ColorAttr
|
Scrollbar ColorAttr
|
||||||
@@ -299,15 +334,6 @@ 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
|
||||||
@@ -321,7 +347,8 @@ type MouseEvent struct {
|
|||||||
type BorderShape int
|
type BorderShape int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
BorderNone BorderShape = iota
|
BorderUndefined BorderShape = iota
|
||||||
|
BorderNone
|
||||||
BorderRounded
|
BorderRounded
|
||||||
BorderSharp
|
BorderSharp
|
||||||
BorderBold
|
BorderBold
|
||||||
@@ -336,6 +363,14 @@ const (
|
|||||||
BorderRight
|
BorderRight
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (s BorderShape) HasLeft() bool {
|
||||||
|
switch s {
|
||||||
|
case BorderNone, BorderRight, BorderTop, BorderBottom, BorderHorizontal: // No Left
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (s BorderShape) HasRight() bool {
|
func (s BorderShape) HasRight() bool {
|
||||||
switch s {
|
switch s {
|
||||||
case BorderNone, BorderLeft, BorderTop, BorderBottom, BorderHorizontal: // No right
|
case BorderNone, BorderLeft, BorderTop, BorderBottom, BorderHorizontal: // No right
|
||||||
@@ -484,7 +519,7 @@ type TermSize struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Renderer interface {
|
type Renderer interface {
|
||||||
Init()
|
Init() error
|
||||||
Resize(maxHeightFunc func(int) int)
|
Resize(maxHeightFunc func(int) int)
|
||||||
Pause(clear bool)
|
Pause(clear bool)
|
||||||
Resume(clear bool, sigcont bool)
|
Resume(clear bool, sigcont bool)
|
||||||
@@ -563,12 +598,14 @@ var (
|
|||||||
ColMatch ColorPair
|
ColMatch ColorPair
|
||||||
ColCursor ColorPair
|
ColCursor ColorPair
|
||||||
ColCursorEmpty ColorPair
|
ColCursorEmpty ColorPair
|
||||||
|
ColMarker ColorPair
|
||||||
ColSelected ColorPair
|
ColSelected ColorPair
|
||||||
|
ColSelectedMatch ColorPair
|
||||||
ColCurrent ColorPair
|
ColCurrent ColorPair
|
||||||
ColCurrentMatch ColorPair
|
ColCurrentMatch ColorPair
|
||||||
ColCurrentCursor ColorPair
|
ColCurrentCursor ColorPair
|
||||||
ColCurrentCursorEmpty ColorPair
|
ColCurrentCursorEmpty ColorPair
|
||||||
ColCurrentSelected ColorPair
|
ColCurrentMarker ColorPair
|
||||||
ColCurrentSelectedEmpty ColorPair
|
ColCurrentSelectedEmpty ColorPair
|
||||||
ColSpinner ColorPair
|
ColSpinner ColorPair
|
||||||
ColInfo ColorPair
|
ColInfo ColorPair
|
||||||
@@ -590,6 +627,9 @@ func EmptyTheme() *ColorTheme {
|
|||||||
Input: ColorAttr{colUndefined, AttrUndefined},
|
Input: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Fg: ColorAttr{colUndefined, AttrUndefined},
|
Fg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Bg: ColorAttr{colUndefined, AttrUndefined},
|
Bg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
||||||
DarkBg: ColorAttr{colUndefined, AttrUndefined},
|
DarkBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Prompt: ColorAttr{colUndefined, AttrUndefined},
|
Prompt: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Match: ColorAttr{colUndefined, AttrUndefined},
|
Match: ColorAttr{colUndefined, AttrUndefined},
|
||||||
@@ -598,7 +638,7 @@ func EmptyTheme() *ColorTheme {
|
|||||||
Spinner: ColorAttr{colUndefined, AttrUndefined},
|
Spinner: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Info: ColorAttr{colUndefined, AttrUndefined},
|
Info: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Cursor: ColorAttr{colUndefined, AttrUndefined},
|
Cursor: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Selected: ColorAttr{colUndefined, AttrUndefined},
|
Marker: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Header: ColorAttr{colUndefined, AttrUndefined},
|
Header: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Border: ColorAttr{colUndefined, AttrUndefined},
|
Border: ColorAttr{colUndefined, AttrUndefined},
|
||||||
BorderLabel: ColorAttr{colUndefined, AttrUndefined},
|
BorderLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
@@ -620,6 +660,9 @@ func NoColorTheme() *ColorTheme {
|
|||||||
Input: ColorAttr{colDefault, AttrUndefined},
|
Input: ColorAttr{colDefault, AttrUndefined},
|
||||||
Fg: ColorAttr{colDefault, AttrUndefined},
|
Fg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
SelectedFg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
SelectedBg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
SelectedMatch: ColorAttr{colDefault, AttrUndefined},
|
||||||
DarkBg: ColorAttr{colDefault, AttrUndefined},
|
DarkBg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Prompt: ColorAttr{colDefault, AttrUndefined},
|
Prompt: ColorAttr{colDefault, AttrUndefined},
|
||||||
Match: ColorAttr{colDefault, Underline},
|
Match: ColorAttr{colDefault, Underline},
|
||||||
@@ -628,7 +671,7 @@ func NoColorTheme() *ColorTheme {
|
|||||||
Spinner: ColorAttr{colDefault, AttrUndefined},
|
Spinner: ColorAttr{colDefault, AttrUndefined},
|
||||||
Info: ColorAttr{colDefault, AttrUndefined},
|
Info: ColorAttr{colDefault, AttrUndefined},
|
||||||
Cursor: ColorAttr{colDefault, AttrUndefined},
|
Cursor: ColorAttr{colDefault, AttrUndefined},
|
||||||
Selected: ColorAttr{colDefault, AttrUndefined},
|
Marker: ColorAttr{colDefault, AttrUndefined},
|
||||||
Header: ColorAttr{colDefault, AttrUndefined},
|
Header: ColorAttr{colDefault, AttrUndefined},
|
||||||
Border: ColorAttr{colDefault, AttrUndefined},
|
Border: ColorAttr{colDefault, AttrUndefined},
|
||||||
BorderLabel: ColorAttr{colDefault, AttrUndefined},
|
BorderLabel: ColorAttr{colDefault, AttrUndefined},
|
||||||
@@ -644,17 +687,15 @@ func NoColorTheme() *ColorTheme {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func errorExit(message string) {
|
|
||||||
fmt.Fprintln(os.Stderr, message)
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
Default16 = &ColorTheme{
|
Default16 = &ColorTheme{
|
||||||
Colored: true,
|
Colored: true,
|
||||||
Input: ColorAttr{colDefault, AttrUndefined},
|
Input: ColorAttr{colDefault, AttrUndefined},
|
||||||
Fg: ColorAttr{colDefault, AttrUndefined},
|
Fg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
||||||
DarkBg: ColorAttr{colBlack, AttrUndefined},
|
DarkBg: ColorAttr{colBlack, AttrUndefined},
|
||||||
Prompt: ColorAttr{colBlue, AttrUndefined},
|
Prompt: ColorAttr{colBlue, AttrUndefined},
|
||||||
Match: ColorAttr{colGreen, AttrUndefined},
|
Match: ColorAttr{colGreen, AttrUndefined},
|
||||||
@@ -663,7 +704,7 @@ func init() {
|
|||||||
Spinner: ColorAttr{colGreen, AttrUndefined},
|
Spinner: ColorAttr{colGreen, AttrUndefined},
|
||||||
Info: ColorAttr{colWhite, AttrUndefined},
|
Info: ColorAttr{colWhite, AttrUndefined},
|
||||||
Cursor: ColorAttr{colRed, AttrUndefined},
|
Cursor: ColorAttr{colRed, AttrUndefined},
|
||||||
Selected: ColorAttr{colMagenta, AttrUndefined},
|
Marker: ColorAttr{colMagenta, AttrUndefined},
|
||||||
Header: ColorAttr{colCyan, AttrUndefined},
|
Header: ColorAttr{colCyan, AttrUndefined},
|
||||||
Border: ColorAttr{colBlack, AttrUndefined},
|
Border: ColorAttr{colBlack, AttrUndefined},
|
||||||
BorderLabel: ColorAttr{colWhite, AttrUndefined},
|
BorderLabel: ColorAttr{colWhite, AttrUndefined},
|
||||||
@@ -682,6 +723,9 @@ func init() {
|
|||||||
Input: ColorAttr{colDefault, AttrUndefined},
|
Input: ColorAttr{colDefault, AttrUndefined},
|
||||||
Fg: ColorAttr{colDefault, AttrUndefined},
|
Fg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
||||||
DarkBg: ColorAttr{236, AttrUndefined},
|
DarkBg: ColorAttr{236, AttrUndefined},
|
||||||
Prompt: ColorAttr{110, AttrUndefined},
|
Prompt: ColorAttr{110, AttrUndefined},
|
||||||
Match: ColorAttr{108, AttrUndefined},
|
Match: ColorAttr{108, AttrUndefined},
|
||||||
@@ -690,7 +734,7 @@ func init() {
|
|||||||
Spinner: ColorAttr{148, AttrUndefined},
|
Spinner: ColorAttr{148, AttrUndefined},
|
||||||
Info: ColorAttr{144, AttrUndefined},
|
Info: ColorAttr{144, AttrUndefined},
|
||||||
Cursor: ColorAttr{161, AttrUndefined},
|
Cursor: ColorAttr{161, AttrUndefined},
|
||||||
Selected: ColorAttr{168, AttrUndefined},
|
Marker: ColorAttr{168, AttrUndefined},
|
||||||
Header: ColorAttr{109, AttrUndefined},
|
Header: ColorAttr{109, AttrUndefined},
|
||||||
Border: ColorAttr{59, AttrUndefined},
|
Border: ColorAttr{59, AttrUndefined},
|
||||||
BorderLabel: ColorAttr{145, AttrUndefined},
|
BorderLabel: ColorAttr{145, AttrUndefined},
|
||||||
@@ -709,6 +753,9 @@ func init() {
|
|||||||
Input: ColorAttr{colDefault, AttrUndefined},
|
Input: ColorAttr{colDefault, AttrUndefined},
|
||||||
Fg: ColorAttr{colDefault, AttrUndefined},
|
Fg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
||||||
DarkBg: ColorAttr{251, AttrUndefined},
|
DarkBg: ColorAttr{251, AttrUndefined},
|
||||||
Prompt: ColorAttr{25, AttrUndefined},
|
Prompt: ColorAttr{25, AttrUndefined},
|
||||||
Match: ColorAttr{66, AttrUndefined},
|
Match: ColorAttr{66, AttrUndefined},
|
||||||
@@ -717,7 +764,7 @@ func init() {
|
|||||||
Spinner: ColorAttr{65, AttrUndefined},
|
Spinner: ColorAttr{65, AttrUndefined},
|
||||||
Info: ColorAttr{101, AttrUndefined},
|
Info: ColorAttr{101, AttrUndefined},
|
||||||
Cursor: ColorAttr{161, AttrUndefined},
|
Cursor: ColorAttr{161, AttrUndefined},
|
||||||
Selected: ColorAttr{168, AttrUndefined},
|
Marker: ColorAttr{168, AttrUndefined},
|
||||||
Header: ColorAttr{31, AttrUndefined},
|
Header: ColorAttr{31, AttrUndefined},
|
||||||
Border: ColorAttr{145, AttrUndefined},
|
Border: ColorAttr{145, AttrUndefined},
|
||||||
BorderLabel: ColorAttr{59, AttrUndefined},
|
BorderLabel: ColorAttr{59, AttrUndefined},
|
||||||
@@ -759,12 +806,15 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
|
|||||||
theme.Spinner = o(baseTheme.Spinner, theme.Spinner)
|
theme.Spinner = o(baseTheme.Spinner, theme.Spinner)
|
||||||
theme.Info = o(baseTheme.Info, theme.Info)
|
theme.Info = o(baseTheme.Info, theme.Info)
|
||||||
theme.Cursor = o(baseTheme.Cursor, theme.Cursor)
|
theme.Cursor = o(baseTheme.Cursor, theme.Cursor)
|
||||||
theme.Selected = o(baseTheme.Selected, theme.Selected)
|
theme.Marker = o(baseTheme.Marker, theme.Marker)
|
||||||
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)
|
theme.BorderLabel = o(baseTheme.BorderLabel, theme.BorderLabel)
|
||||||
|
|
||||||
// These colors are not defined in the base themes
|
// These colors are not defined in the base themes
|
||||||
|
theme.SelectedFg = o(theme.Fg, theme.SelectedFg)
|
||||||
|
theme.SelectedBg = o(theme.Bg, theme.SelectedBg)
|
||||||
|
theme.SelectedMatch = o(theme.Match, theme.SelectedMatch)
|
||||||
theme.Disabled = o(theme.Input, theme.Disabled)
|
theme.Disabled = o(theme.Input, theme.Disabled)
|
||||||
theme.Gutter = o(theme.DarkBg, theme.Gutter)
|
theme.Gutter = o(theme.DarkBg, theme.Gutter)
|
||||||
theme.PreviewFg = o(theme.Fg, theme.PreviewFg)
|
theme.PreviewFg = o(theme.Fg, theme.PreviewFg)
|
||||||
@@ -790,17 +840,23 @@ func initPalette(theme *ColorTheme) {
|
|||||||
|
|
||||||
ColPrompt = pair(theme.Prompt, theme.Bg)
|
ColPrompt = pair(theme.Prompt, theme.Bg)
|
||||||
ColNormal = pair(theme.Fg, theme.Bg)
|
ColNormal = pair(theme.Fg, theme.Bg)
|
||||||
|
ColSelected = pair(theme.SelectedFg, theme.SelectedBg)
|
||||||
ColInput = pair(theme.Input, theme.Bg)
|
ColInput = pair(theme.Input, theme.Bg)
|
||||||
ColDisabled = pair(theme.Disabled, theme.Bg)
|
ColDisabled = pair(theme.Disabled, theme.Bg)
|
||||||
ColMatch = pair(theme.Match, theme.Bg)
|
ColMatch = pair(theme.Match, theme.Bg)
|
||||||
|
ColSelectedMatch = pair(theme.SelectedMatch, theme.SelectedBg)
|
||||||
ColCursor = pair(theme.Cursor, theme.Gutter)
|
ColCursor = pair(theme.Cursor, theme.Gutter)
|
||||||
ColCursorEmpty = pair(blank, theme.Gutter)
|
ColCursorEmpty = pair(blank, theme.Gutter)
|
||||||
ColSelected = pair(theme.Selected, theme.Gutter)
|
if theme.SelectedBg.Color != theme.Bg.Color {
|
||||||
|
ColMarker = pair(theme.Marker, theme.SelectedBg)
|
||||||
|
} else {
|
||||||
|
ColMarker = pair(theme.Marker, theme.Gutter)
|
||||||
|
}
|
||||||
ColCurrent = pair(theme.Current, theme.DarkBg)
|
ColCurrent = pair(theme.Current, theme.DarkBg)
|
||||||
ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg)
|
ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg)
|
||||||
ColCurrentCursor = pair(theme.Cursor, theme.DarkBg)
|
ColCurrentCursor = pair(theme.Cursor, theme.DarkBg)
|
||||||
ColCurrentCursorEmpty = pair(blank, theme.DarkBg)
|
ColCurrentCursorEmpty = pair(blank, theme.DarkBg)
|
||||||
ColCurrentSelected = pair(theme.Selected, theme.DarkBg)
|
ColCurrentMarker = pair(theme.Marker, theme.DarkBg)
|
||||||
ColCurrentSelectedEmpty = pair(blank, theme.DarkBg)
|
ColCurrentSelectedEmpty = pair(blank, theme.DarkBg)
|
||||||
ColSpinner = pair(theme.Spinner, theme.Bg)
|
ColSpinner = pair(theme.Spinner, theme.Bg)
|
||||||
ColInfo = pair(theme.Info, theme.Bg)
|
ColInfo = pair(theme.Info, theme.Bg)
|
||||||
|
28
src/util/atexit.go
Normal file
28
src/util/atexit.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var atExitFuncs []func()
|
||||||
|
|
||||||
|
// AtExit registers the function fn to be called on program termination.
|
||||||
|
// The functions will be called in reverse order they were registered.
|
||||||
|
func AtExit(fn func()) {
|
||||||
|
if fn == nil {
|
||||||
|
panic("AtExit called with nil func")
|
||||||
|
}
|
||||||
|
once := &sync.Once{}
|
||||||
|
atExitFuncs = append(atExitFuncs, func() {
|
||||||
|
once.Do(fn)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunAtExitFuncs runs any functions registered with AtExit().
|
||||||
|
func RunAtExitFuncs() {
|
||||||
|
fns := atExitFuncs
|
||||||
|
for i := len(fns) - 1; i >= 0; i-- {
|
||||||
|
fns[i]()
|
||||||
|
}
|
||||||
|
atExitFuncs = nil
|
||||||
|
}
|
24
src/util/atexit_test.go
Normal file
24
src/util/atexit_test.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAtExit(t *testing.T) {
|
||||||
|
want := []int{3, 2, 1, 0}
|
||||||
|
var called []int
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
n := i
|
||||||
|
AtExit(func() { called = append(called, n) })
|
||||||
|
}
|
||||||
|
RunAtExitFuncs()
|
||||||
|
if !reflect.DeepEqual(called, want) {
|
||||||
|
t.Errorf("AtExit: want call order: %v got: %v", want, called)
|
||||||
|
}
|
||||||
|
|
||||||
|
RunAtExitFuncs()
|
||||||
|
if !reflect.DeepEqual(called, want) {
|
||||||
|
t.Error("AtExit: should only call exit funcs once")
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
@@ -74,6 +75,35 @@ func (chars *Chars) Bytes() []byte {
|
|||||||
return chars.slice
|
return chars.slice
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) NumLines(atMost int) (int, bool) {
|
||||||
|
lines := 1
|
||||||
|
if runes := chars.optionalRunes(); runes != nil {
|
||||||
|
for _, r := range runes {
|
||||||
|
if r == '\n' {
|
||||||
|
lines++
|
||||||
|
}
|
||||||
|
if lines > atMost {
|
||||||
|
return atMost, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lines, false
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx := 0; idx < len(chars.slice); idx++ {
|
||||||
|
found := bytes.IndexByte(chars.slice[idx:], '\n')
|
||||||
|
if found < 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
idx += found
|
||||||
|
lines++
|
||||||
|
if lines > atMost {
|
||||||
|
return atMost, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lines, false
|
||||||
|
}
|
||||||
|
|
||||||
func (chars *Chars) optionalRunes() []rune {
|
func (chars *Chars) optionalRunes() []rune {
|
||||||
if chars.inBytes {
|
if chars.inBytes {
|
||||||
return nil
|
return nil
|
||||||
@@ -163,7 +193,7 @@ func (chars *Chars) ToString() string {
|
|||||||
if runes := chars.optionalRunes(); runes != nil {
|
if runes := chars.optionalRunes(); runes != nil {
|
||||||
return string(runes)
|
return string(runes)
|
||||||
}
|
}
|
||||||
return string(chars.slice)
|
return unsafe.String(unsafe.SliceData(chars.slice), len(chars.slice))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chars *Chars) ToRunes() []rune {
|
func (chars *Chars) ToRunes() []rune {
|
||||||
@@ -178,12 +208,12 @@ func (chars *Chars) ToRunes() []rune {
|
|||||||
return runes
|
return runes
|
||||||
}
|
}
|
||||||
|
|
||||||
func (chars *Chars) CopyRunes(dest []rune) {
|
func (chars *Chars) CopyRunes(dest []rune, from int) {
|
||||||
if runes := chars.optionalRunes(); runes != nil {
|
if runes := chars.optionalRunes(); runes != nil {
|
||||||
copy(dest, runes)
|
copy(dest, runes[from:])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for idx, b := range chars.slice[:len(dest)] {
|
for idx, b := range chars.slice[from:][:len(dest)] {
|
||||||
dest[idx] = rune(b)
|
dest[idx] = rune(b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,3 +226,85 @@ func (chars *Chars) Prepend(prefix string) {
|
|||||||
chars.slice = append([]byte(prefix), chars.slice...)
|
chars.slice = append([]byte(prefix), chars.slice...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) Lines(multiLine bool, maxLines int, wrapCols int, wrapSignWidth int, tabstop int) ([][]rune, bool) {
|
||||||
|
text := make([]rune, chars.Length())
|
||||||
|
copy(text, chars.ToRunes())
|
||||||
|
|
||||||
|
lines := [][]rune{}
|
||||||
|
overflow := false
|
||||||
|
if !multiLine {
|
||||||
|
lines = append(lines, text)
|
||||||
|
} else {
|
||||||
|
from := 0
|
||||||
|
for off := 0; off < len(text); off++ {
|
||||||
|
if text[off] == '\n' {
|
||||||
|
lines = append(lines, text[from:off+1]) // Include '\n'
|
||||||
|
from = off + 1
|
||||||
|
if len(lines) >= maxLines {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastLine []rune
|
||||||
|
if from < len(text) {
|
||||||
|
lastLine = text[from:]
|
||||||
|
}
|
||||||
|
|
||||||
|
overflow = false
|
||||||
|
if len(lines) >= maxLines {
|
||||||
|
overflow = true
|
||||||
|
} else {
|
||||||
|
lines = append(lines, lastLine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If wrapping is disabled, we're done
|
||||||
|
if wrapCols == 0 {
|
||||||
|
return lines, overflow
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapped := [][]rune{}
|
||||||
|
for _, line := range lines {
|
||||||
|
// Remove trailing '\n' and remember if it was there
|
||||||
|
newline := len(line) > 0 && line[len(line)-1] == '\n'
|
||||||
|
if newline {
|
||||||
|
line = line[:len(line)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
cols := wrapCols
|
||||||
|
if len(wrapped) > 0 {
|
||||||
|
cols -= wrapSignWidth
|
||||||
|
}
|
||||||
|
_, overflowIdx := RunesWidth(line, 0, tabstop, cols)
|
||||||
|
if overflowIdx >= 0 {
|
||||||
|
// Might be a wide character
|
||||||
|
if overflowIdx == 0 {
|
||||||
|
overflowIdx = 1
|
||||||
|
}
|
||||||
|
if len(wrapped) >= maxLines {
|
||||||
|
return wrapped, true
|
||||||
|
}
|
||||||
|
wrapped = append(wrapped, line[:overflowIdx])
|
||||||
|
line = line[overflowIdx:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore trailing '\n'
|
||||||
|
if newline {
|
||||||
|
line = append(line, '\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(wrapped) >= maxLines {
|
||||||
|
return wrapped, true
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapped = append(wrapped, line)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapped, false
|
||||||
|
}
|
||||||
|
@@ -1,6 +1,9 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func TestToCharsAscii(t *testing.T) {
|
func TestToCharsAscii(t *testing.T) {
|
||||||
chars := ToChars([]byte("foobar"))
|
chars := ToChars([]byte("foobar"))
|
||||||
@@ -44,3 +47,37 @@ func TestTrimLength(t *testing.T) {
|
|||||||
check(" h o ", 5)
|
check(" h o ", 5)
|
||||||
check(" ", 0)
|
check(" ", 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCharsLines(t *testing.T) {
|
||||||
|
chars := ToChars([]byte("abcdef\n가나다\n\tdef"))
|
||||||
|
check := func(multiLine bool, maxLines int, wrapCols int, wrapSignWidth int, tabstop int, expectedNumLines int, expectedOverflow bool) {
|
||||||
|
lines, overflow := chars.Lines(multiLine, maxLines, wrapCols, wrapSignWidth, tabstop)
|
||||||
|
fmt.Println(lines, overflow)
|
||||||
|
if len(lines) != expectedNumLines || overflow != expectedOverflow {
|
||||||
|
t.Errorf("Invalid result: %d %v (expected %d %v)", len(lines), overflow, expectedNumLines, expectedOverflow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No wrap
|
||||||
|
check(true, 1, 0, 0, 8, 1, true)
|
||||||
|
check(true, 2, 0, 0, 8, 2, true)
|
||||||
|
check(true, 3, 0, 0, 8, 3, false)
|
||||||
|
|
||||||
|
// Wrap (2)
|
||||||
|
check(true, 4, 2, 0, 8, 4, true)
|
||||||
|
check(true, 5, 2, 0, 8, 5, true)
|
||||||
|
check(true, 6, 2, 0, 8, 6, true)
|
||||||
|
check(true, 7, 2, 0, 8, 7, true)
|
||||||
|
check(true, 8, 2, 0, 8, 8, true)
|
||||||
|
check(true, 9, 2, 0, 8, 9, false)
|
||||||
|
check(true, 9, 2, 0, 1, 8, false) // Smaller tab size
|
||||||
|
|
||||||
|
// With wrap sign (3 + 1)
|
||||||
|
check(true, 100, 3, 1, 1, 8, false)
|
||||||
|
|
||||||
|
// With wrap sign (3 + 2)
|
||||||
|
check(true, 100, 3, 2, 1, 12, false)
|
||||||
|
|
||||||
|
// With wrap sign (3 + 2) and no multi-line
|
||||||
|
check(false, 100, 3, 2, 1, 13, false)
|
||||||
|
}
|
||||||
|
@@ -3,6 +3,7 @@ package util
|
|||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -137,14 +138,20 @@ func DurWithin(
|
|||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsTty returns true if stdin is a terminal
|
// IsTty returns true if the file is a terminal
|
||||||
func IsTty() bool {
|
func IsTty(file *os.File) bool {
|
||||||
return isatty.IsTerminal(os.Stdin.Fd())
|
fd := file.Fd()
|
||||||
|
return isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToTty returns true if stdout is a terminal
|
// RunOnce runs the given function only once
|
||||||
func ToTty() bool {
|
func RunOnce(f func()) func() {
|
||||||
return isatty.IsTerminal(os.Stdout.Fd())
|
once := Once(true)
|
||||||
|
return func() {
|
||||||
|
if once() {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Once returns a function that returns the specified boolean value only once
|
// Once returns a function that returns the specified boolean value only once
|
||||||
@@ -152,7 +159,7 @@ func Once(nextResponse bool) func() bool {
|
|||||||
state := nextResponse
|
state := nextResponse
|
||||||
return func() bool {
|
return func() bool {
|
||||||
prevState := state
|
prevState := state
|
||||||
state = false
|
state = !nextResponse
|
||||||
return prevState
|
return prevState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,3 +183,46 @@ func RepeatToFill(str string, length int, limit int) string {
|
|||||||
}
|
}
|
||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToKebabCase converts the given CamelCase string to kebab-case
|
||||||
|
func ToKebabCase(s string) string {
|
||||||
|
name := ""
|
||||||
|
for i, r := range s {
|
||||||
|
if i > 0 && r >= 'A' && r <= 'Z' {
|
||||||
|
name += "-"
|
||||||
|
}
|
||||||
|
name += string(r)
|
||||||
|
}
|
||||||
|
return strings.ToLower(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompareVersions compares two version strings
|
||||||
|
func CompareVersions(v1, v2 string) int {
|
||||||
|
parts1 := strings.Split(v1, ".")
|
||||||
|
parts2 := strings.Split(v2, ".")
|
||||||
|
|
||||||
|
atoi := func(s string) int {
|
||||||
|
n, e := strconv.Atoi(s)
|
||||||
|
if e != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < Max(len(parts1), len(parts2)); i++ {
|
||||||
|
var p1, p2 int
|
||||||
|
if i < len(parts1) {
|
||||||
|
p1 = atoi(parts1[i])
|
||||||
|
}
|
||||||
|
if i < len(parts2) {
|
||||||
|
p2 = atoi(parts2[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
if p1 > p2 {
|
||||||
|
return 1
|
||||||
|
} else if p1 < p2 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
@@ -137,8 +137,11 @@ func TestOnce(t *testing.T) {
|
|||||||
if o() {
|
if o() {
|
||||||
t.Error("Expected: false")
|
t.Error("Expected: false")
|
||||||
}
|
}
|
||||||
if o() {
|
if !o() {
|
||||||
t.Error("Expected: false")
|
t.Error("Expected: true")
|
||||||
|
}
|
||||||
|
if !o() {
|
||||||
|
t.Error("Expected: true")
|
||||||
}
|
}
|
||||||
|
|
||||||
o = Once(true)
|
o = Once(true)
|
||||||
@@ -148,6 +151,9 @@ func TestOnce(t *testing.T) {
|
|||||||
if o() {
|
if o() {
|
||||||
t.Error("Expected: false")
|
t.Error("Expected: false")
|
||||||
}
|
}
|
||||||
|
if o() {
|
||||||
|
t.Error("Expected: false")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunesWidth(t *testing.T) {
|
func TestRunesWidth(t *testing.T) {
|
||||||
@@ -203,3 +209,34 @@ func TestStringWidth(t *testing.T) {
|
|||||||
t.Errorf("Expected: %d, Actual: %d", 1, w)
|
t.Errorf("Expected: %d, Actual: %d", 1, w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCompareVersions(t *testing.T) {
|
||||||
|
assert := func(a, b string, expected int) {
|
||||||
|
if result := CompareVersions(a, b); result != expected {
|
||||||
|
t.Errorf("Expected: %d, Actual: %d", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert("2", "1", 1)
|
||||||
|
assert("2", "2", 0)
|
||||||
|
assert("2", "10", -1)
|
||||||
|
|
||||||
|
assert("2.1", "2.2", -1)
|
||||||
|
assert("2.1", "2.1.1", -1)
|
||||||
|
|
||||||
|
assert("1.2.3", "1.2.2", 1)
|
||||||
|
assert("1.2.3", "1.2.3", 0)
|
||||||
|
assert("1.2.3", "1.2.3.0", 0)
|
||||||
|
assert("1.2.3", "1.2.4", -1)
|
||||||
|
|
||||||
|
// Different number of parts
|
||||||
|
assert("1.0.0", "1", 0)
|
||||||
|
assert("1.0.0", "1.0", 0)
|
||||||
|
assert("1.0.0", "1.0.0", 0)
|
||||||
|
assert("1.0", "1.0.0", 0)
|
||||||
|
assert("1", "1.0.0", 0)
|
||||||
|
assert("1.0.0", "1.0.0.1", -1)
|
||||||
|
assert("1.0.0.1.0", "1.0.0.1", 0)
|
||||||
|
|
||||||
|
assert("", "3.4.5", -1)
|
||||||
|
}
|
||||||
|
@@ -3,31 +3,71 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExecCommand executes the given command with $SHELL
|
type Executor struct {
|
||||||
func ExecCommand(command string, setpgid bool) *exec.Cmd {
|
shell string
|
||||||
shell := os.Getenv("SHELL")
|
args []string
|
||||||
if len(shell) == 0 {
|
escaper *strings.Replacer
|
||||||
shell = "sh"
|
|
||||||
}
|
|
||||||
return ExecCommandWith(shell, command, setpgid)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecCommandWith executes the given command with the specified shell
|
func NewExecutor(withShell string) *Executor {
|
||||||
func ExecCommandWith(shell string, command string, setpgid bool) *exec.Cmd {
|
shell := os.Getenv("SHELL")
|
||||||
cmd := exec.Command(shell, "-c", command)
|
args := strings.Fields(withShell)
|
||||||
|
if len(args) > 0 {
|
||||||
|
shell = args[0]
|
||||||
|
args = args[1:]
|
||||||
|
} else {
|
||||||
|
if len(shell) == 0 {
|
||||||
|
shell = "sh"
|
||||||
|
}
|
||||||
|
args = []string{"-c"}
|
||||||
|
}
|
||||||
|
|
||||||
|
var escaper *strings.Replacer
|
||||||
|
tokens := strings.Split(shell, "/")
|
||||||
|
if tokens[len(tokens)-1] == "fish" {
|
||||||
|
// https://fishshell.com/docs/current/language.html#quotes
|
||||||
|
// > The only meaningful escape sequences in single quotes are \', which
|
||||||
|
// > escapes a single quote and \\, which escapes the backslash symbol.
|
||||||
|
escaper = strings.NewReplacer("\\", "\\\\", "'", "\\'")
|
||||||
|
} else {
|
||||||
|
escaper = strings.NewReplacer("'", "'\\''")
|
||||||
|
}
|
||||||
|
return &Executor{shell, args, escaper}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecCommand executes the given command with $SHELL
|
||||||
|
func (x *Executor) ExecCommand(command string, setpgid bool) *exec.Cmd {
|
||||||
|
cmd := exec.Command(x.shell, append(x.args, command)...)
|
||||||
if setpgid {
|
if setpgid {
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||||
}
|
}
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Executor) QuoteEntry(entry string) string {
|
||||||
|
return "'" + x.escaper.Replace(entry) + "'"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Executor) Become(stdin *os.File, environ []string, command string) {
|
||||||
|
shellPath, err := exec.LookPath(x.shell)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "fzf (become): %s\n", err.Error())
|
||||||
|
os.Exit(127)
|
||||||
|
}
|
||||||
|
args := append([]string{shellPath}, append(x.args, command)...)
|
||||||
|
SetStdin(stdin)
|
||||||
|
syscall.Exec(shellPath, args, environ)
|
||||||
|
}
|
||||||
|
|
||||||
// KillCommand kills the process for the given command
|
// KillCommand kills the process for the given command
|
||||||
func KillCommand(cmd *exec.Cmd) error {
|
func KillCommand(cmd *exec.Cmd) error {
|
||||||
return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
|
return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
|
||||||
|
@@ -6,62 +6,164 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
var shellPath atomic.Value
|
type shellType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
shellTypeUnknown shellType = iota
|
||||||
|
shellTypeCmd
|
||||||
|
shellTypePowerShell
|
||||||
|
)
|
||||||
|
|
||||||
|
var escapeRegex = regexp.MustCompile(`[&|<>()^%!"]`)
|
||||||
|
|
||||||
|
type Executor struct {
|
||||||
|
shell string
|
||||||
|
shellType shellType
|
||||||
|
args []string
|
||||||
|
shellPath atomic.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExecutor(withShell string) *Executor {
|
||||||
|
shell := os.Getenv("SHELL")
|
||||||
|
args := strings.Fields(withShell)
|
||||||
|
if len(args) > 0 {
|
||||||
|
shell = args[0]
|
||||||
|
} else if len(shell) == 0 {
|
||||||
|
shell = "cmd"
|
||||||
|
}
|
||||||
|
|
||||||
|
shellType := shellTypeUnknown
|
||||||
|
basename := filepath.Base(shell)
|
||||||
|
if len(args) > 0 {
|
||||||
|
args = args[1:]
|
||||||
|
} else if strings.HasPrefix(basename, "cmd") {
|
||||||
|
shellType = shellTypeCmd
|
||||||
|
args = []string{"/s/c"}
|
||||||
|
} else if strings.HasPrefix(basename, "pwsh") || strings.HasPrefix(basename, "powershell") {
|
||||||
|
shellType = shellTypePowerShell
|
||||||
|
args = []string{"-NoProfile", "-Command"}
|
||||||
|
} else {
|
||||||
|
args = []string{"-c"}
|
||||||
|
}
|
||||||
|
return &Executor{shell: shell, shellType: shellType, args: args}
|
||||||
|
}
|
||||||
|
|
||||||
// ExecCommand executes the given command with $SHELL
|
// ExecCommand executes the given command with $SHELL
|
||||||
func ExecCommand(command string, setpgid bool) *exec.Cmd {
|
// FIXME: setpgid is unused. We set it in the Unix implementation so that we
|
||||||
var shell string
|
// can kill preview process with its child processes at once.
|
||||||
if cached := shellPath.Load(); cached != nil {
|
// NOTE: For "powershell", we should ideally set output encoding to UTF8,
|
||||||
|
// but it is left as is now because no adverse effect has been observed.
|
||||||
|
func (x *Executor) ExecCommand(command string, setpgid bool) *exec.Cmd {
|
||||||
|
shell := x.shell
|
||||||
|
if cached := x.shellPath.Load(); cached != nil {
|
||||||
shell = cached.(string)
|
shell = cached.(string)
|
||||||
} else {
|
} else {
|
||||||
shell = os.Getenv("SHELL")
|
if strings.Contains(shell, "/") {
|
||||||
if len(shell) == 0 {
|
|
||||||
shell = "cmd"
|
|
||||||
} else if strings.Contains(shell, "/") {
|
|
||||||
out, err := exec.Command("cygpath", "-w", shell).Output()
|
out, err := exec.Command("cygpath", "-w", shell).Output()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
shell = strings.Trim(string(out), "\n")
|
shell = strings.Trim(string(out), "\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shellPath.Store(shell)
|
x.shellPath.Store(shell)
|
||||||
}
|
}
|
||||||
return ExecCommandWith(shell, command, setpgid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExecCommandWith executes the given command with the specified shell
|
|
||||||
// FIXME: setpgid is unused. We set it in the Unix implementation so that we
|
|
||||||
// can kill preview process with its child processes at once.
|
|
||||||
// NOTE: For "powershell", we should ideally set output encoding to UTF8,
|
|
||||||
// but it is left as is now because no adverse effect has been observed.
|
|
||||||
func ExecCommandWith(shell string, command string, setpgid bool) *exec.Cmd {
|
|
||||||
var cmd *exec.Cmd
|
var cmd *exec.Cmd
|
||||||
if strings.Contains(shell, "cmd") {
|
if x.shellType == shellTypeCmd {
|
||||||
cmd = exec.Command(shell)
|
cmd = exec.Command(shell)
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
HideWindow: false,
|
HideWindow: false,
|
||||||
CmdLine: fmt.Sprintf(` /v:on/s/c "%s"`, command),
|
CmdLine: fmt.Sprintf(`%s "%s"`, strings.Join(x.args, " "), command),
|
||||||
CreationFlags: 0,
|
CreationFlags: 0,
|
||||||
}
|
}
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(shell, "pwsh") || strings.Contains(shell, "powershell") {
|
|
||||||
cmd = exec.Command(shell, "-NoProfile", "-Command", command)
|
|
||||||
} else {
|
} else {
|
||||||
cmd = exec.Command(shell, "-c", command)
|
cmd = exec.Command(shell, append(x.args, command)...)
|
||||||
}
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
HideWindow: false,
|
||||||
HideWindow: false,
|
CreationFlags: 0,
|
||||||
CreationFlags: 0,
|
}
|
||||||
}
|
}
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Executor) Become(stdin *os.File, environ []string, command string) {
|
||||||
|
cmd := x.ExecCommand(command, false)
|
||||||
|
cmd.Stdin = stdin
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Env = environ
|
||||||
|
err := cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "fzf (become): %s\n", err.Error())
|
||||||
|
os.Exit(127)
|
||||||
|
}
|
||||||
|
err = cmd.Wait()
|
||||||
|
if err != nil {
|
||||||
|
if exitError, ok := err.(*exec.ExitError); ok {
|
||||||
|
os.Exit(exitError.ExitCode())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func escapeArg(s string) string {
|
||||||
|
b := make([]byte, 0, len(s)+2)
|
||||||
|
b = append(b, '"')
|
||||||
|
slashes := 0
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
c := s[i]
|
||||||
|
switch c {
|
||||||
|
default:
|
||||||
|
slashes = 0
|
||||||
|
case '\\':
|
||||||
|
slashes++
|
||||||
|
case '"':
|
||||||
|
for ; slashes > 0; slashes-- {
|
||||||
|
b = append(b, '\\')
|
||||||
|
}
|
||||||
|
b = append(b, '\\')
|
||||||
|
}
|
||||||
|
b = append(b, c)
|
||||||
|
}
|
||||||
|
for ; slashes > 0; slashes-- {
|
||||||
|
b = append(b, '\\')
|
||||||
|
}
|
||||||
|
b = append(b, '"')
|
||||||
|
return escapeRegex.ReplaceAllStringFunc(string(b), func(match string) string {
|
||||||
|
return "^" + match
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Executor) QuoteEntry(entry string) string {
|
||||||
|
switch x.shellType {
|
||||||
|
case shellTypeCmd:
|
||||||
|
/* Manually tested with the following commands:
|
||||||
|
fzf --preview "echo {}"
|
||||||
|
fzf --preview "type {}"
|
||||||
|
echo .git\refs\| fzf --preview "dir {}"
|
||||||
|
echo .git\refs\\| fzf --preview "dir {}"
|
||||||
|
echo .git\refs\\\| fzf --preview "dir {}"
|
||||||
|
reg query HKCU | fzf --reverse --bind "enter:reload(reg query {})"
|
||||||
|
fzf --disabled --preview "echo {q} {n} {}" --query "&|<>()@^%!"
|
||||||
|
fd -H --no-ignore -td -d 4 | fzf --preview "dir {}"
|
||||||
|
fd -H --no-ignore -td -d 4 | fzf --preview "eza {}" --preview-window up
|
||||||
|
fd -H --no-ignore -td -d 4 | fzf --preview "eza --color=always --tree --level=3 --icons=always {}"
|
||||||
|
fd -H --no-ignore -td -d 4 | fzf --preview ".\eza.exe --color=always --tree --level=3 --icons=always {}" --with-shell "powershell -NoProfile -Command"
|
||||||
|
*/
|
||||||
|
return escapeArg(entry)
|
||||||
|
case shellTypePowerShell:
|
||||||
|
escaped := strings.ReplaceAll(entry, `"`, `\"`)
|
||||||
|
return "'" + strings.ReplaceAll(escaped, "'", "''") + "'"
|
||||||
|
default:
|
||||||
|
return "'" + strings.ReplaceAll(entry, "'", "'\\''") + "'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// KillCommand kills the process for the given command
|
// KillCommand kills the process for the given command
|
||||||
func KillCommand(cmd *exec.Cmd) error {
|
func KillCommand(cmd *exec.Cmd) error {
|
||||||
return cmd.Process.Kill()
|
return cmd.Process.Kill()
|
||||||
|
13
src/winpty.go
Normal file
13
src/winpty.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package fzf
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
func needWinpty(_ *Options) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func runWinpty(_ []string, _ *Options) (int, error) {
|
||||||
|
return ExitError, errors.New("Not supported")
|
||||||
|
}
|
75
src/winpty_windows.go
Normal file
75
src/winpty_windows.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package fzf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func isMintty345() bool {
|
||||||
|
return util.CompareVersions(os.Getenv("TERM_PROGRAM_VERSION"), "3.4.5") >= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func needWinpty(opts *Options) bool {
|
||||||
|
if os.Getenv("TERM_PROGRAM") != "mintty" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if isMintty345() {
|
||||||
|
/*
|
||||||
|
See: https://github.com/junegunn/fzf/issues/3809
|
||||||
|
|
||||||
|
"MSYS=enable_pcon" allows fzf to run properly on mintty 3.4.5 or later.
|
||||||
|
*/
|
||||||
|
if strings.Contains(os.Getenv("MSYS"), "enable_pcon") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setting the environment variable here unfortunately doesn't help,
|
||||||
|
// so we need to start a child process with "MSYS=enable_pcon"
|
||||||
|
// os.Setenv("MSYS", "enable_pcon")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if opts.NoWinpty {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if _, err := exec.LookPath("winpty"); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func runWinpty(args []string, opts *Options) (int, error) {
|
||||||
|
sh, err := sh()
|
||||||
|
if err != nil {
|
||||||
|
return ExitError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
argStr := escapeSingleQuote(args[0])
|
||||||
|
for _, arg := range args[1:] {
|
||||||
|
argStr += " " + escapeSingleQuote(arg)
|
||||||
|
}
|
||||||
|
argStr += ` --no-winpty`
|
||||||
|
|
||||||
|
if isMintty345() {
|
||||||
|
return runProxy(argStr, func(temp string) *exec.Cmd {
|
||||||
|
cmd := exec.Command(sh, temp)
|
||||||
|
cmd.Env = append(os.Environ(), "MSYS=enable_pcon")
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
return cmd
|
||||||
|
}, opts, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return runProxy(argStr, func(temp string) *exec.Cmd {
|
||||||
|
cmd := exec.Command(sh, "-c", fmt.Sprintf(`winpty < /dev/tty > /dev/tty -- sh %q`, temp))
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
return cmd
|
||||||
|
}, opts, false)
|
||||||
|
}
|
601
test/test_go.rb
601
test/test_go.rb
File diff suppressed because it is too large
Load Diff
@@ -94,6 +94,7 @@ done
|
|||||||
bind_file="${fish_dir}/functions/fish_user_key_bindings.fish"
|
bind_file="${fish_dir}/functions/fish_user_key_bindings.fish"
|
||||||
if [ -f "$bind_file" ]; then
|
if [ -f "$bind_file" ]; then
|
||||||
remove_line "$bind_file" "fzf_key_bindings"
|
remove_line "$bind_file" "fzf_key_bindings"
|
||||||
|
remove_line "$bind_file" "fzf --fish | source"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -d "${fish_dir}/functions" ]; then
|
if [ -d "${fish_dir}/functions" ]; then
|
||||||
|
Reference in New Issue
Block a user