mirror of
https://github.com/junegunn/fzf.git
synced 2025-08-15 20:23:50 -07:00
Compare commits
141 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
ff1687744d | ||
|
782c870fb2 | ||
|
71fad63829 | ||
|
d65c6101a8 | ||
|
3c40b1bd51 | ||
|
90a8800bb5 | ||
|
97f1dae2d1 | ||
|
e54ec05709 | ||
|
a24eb99679 | ||
|
ad113d00b7 | ||
|
7bd5884d12 | ||
|
c3505858a6 | ||
|
e76aa37fd4 | ||
|
1a32220ca9 | ||
|
4161403a1d | ||
|
53bcdc4294 | ||
|
30a8ef28cd | ||
|
855f90727a | ||
|
2191a44e36 | ||
|
952276dc2d | ||
|
2286edb329 | ||
|
a0f28583e7 | ||
|
8af0af3400 | ||
|
769e5cbb2d | ||
|
fc69308057 | ||
|
c6d620c99e | ||
|
f510a4def6 | ||
|
4ae3069c6f | ||
|
c0f27751d3 | ||
|
efbcd5a683 | ||
|
6a67712944 | ||
|
e8a690928d | ||
|
0eee95af57 | ||
|
a09c6e991a | ||
|
d06c5ab990 | ||
|
e0924d27b8 | ||
|
2775b771f2 | ||
|
cf7a3c6a0e | ||
|
230cc6acc3 | ||
|
626a23a585 | ||
|
74f196eebb | ||
|
cf2242aea3 | ||
|
8cb59e6fca | ||
|
5cce17e80a | ||
|
ee5302fb2d | ||
|
387c6ef664 | ||
|
581734c369 | ||
|
d90a969c00 | ||
|
2c8a96bb27 | ||
|
5da168db30 | ||
|
e215e2daf3 | ||
|
e28f5aa45b | ||
|
a2d0e8f233 | ||
|
303d04106a | ||
|
c423c496a1 | ||
|
4e85f72f0e | ||
|
dd0737aac0 | ||
|
f90985845d | ||
|
af4917dbb6 | ||
|
d9404fcce4 | ||
|
5c01fee5a9 | ||
|
9b27d68a37 | ||
|
b99d884e57 | ||
|
587df594b8 | ||
|
b896e0d314 | ||
|
559fb7ee45 | ||
|
62545cd983 | ||
|
c50812518e | ||
|
4cc5609d8b | ||
|
50fa90dfb8 | ||
|
a2c365e710 | ||
|
b4ddffdc61 | ||
|
8d4d184fc6 | ||
|
ea23539b59 | ||
|
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 |
5
.github/workflows/linux.yml
vendored
5
.github/workflows/linux.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
|||||||
run: sudo apt-get install --yes zsh fish tmux
|
run: sudo apt-get install --yes zsh fish tmux
|
||||||
|
|
||||||
- name: Install Ruby gems
|
- name: Install Ruby gems
|
||||||
run: sudo gem install --no-document minitest:5.17.0 rubocop:1.43.0 rubocop-minitest:0.25.1 rubocop-performance:1.15.2
|
run: sudo gem install --no-document minitest:5.25.1 rubocop:1.65.0 rubocop-minitest:0.35.1 rubocop-performance:1.21.1
|
||||||
|
|
||||||
- name: Rubocop
|
- name: Rubocop
|
||||||
run: rubocop --require rubocop-minitest --require rubocop-performance
|
run: rubocop --require rubocop-minitest --require rubocop-performance
|
||||||
@@ -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
|
|
||||||
|
2
.github/workflows/typos.yml
vendored
2
.github/workflows/typos.yml
vendored
@@ -7,4 +7,4 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: crate-ci/typos@v1.21.0
|
- uses: crate-ci/typos@v1.26.0
|
||||||
|
1
.github/workflows/winget.yml
vendored
1
.github/workflows/winget.yml
vendored
@@ -10,6 +10,5 @@ jobs:
|
|||||||
- uses: vedantmgoyal2009/winget-releaser@v2
|
- uses: vedantmgoyal2009/winget-releaser@v2
|
||||||
with:
|
with:
|
||||||
identifier: junegunn.fzf
|
identifier: junegunn.fzf
|
||||||
version: ${{ github.event.release.tag_name }}
|
|
||||||
installers-regex: '-windows_(armv7|arm64|amd64)\.zip$'
|
installers-regex: '-windows_(armv7|arm64|amd64)\.zip$'
|
||||||
token: ${{ secrets.WINGET_TOKEN }}
|
token: ${{ secrets.WINGET_TOKEN }}
|
||||||
|
100
.goreleaser.yml
100
.goreleaser.yml
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
version: 2
|
||||||
project_name: fzf
|
project_name: fzf
|
||||||
|
|
||||||
before:
|
before:
|
||||||
@@ -6,60 +7,9 @@ before:
|
|||||||
- go mod download
|
- go mod download
|
||||||
|
|
||||||
builds:
|
builds:
|
||||||
- id: fzf-macos
|
|
||||||
binary: fzf
|
|
||||||
goos:
|
|
||||||
- darwin
|
|
||||||
goarch:
|
|
||||||
- amd64
|
|
||||||
flags:
|
|
||||||
- -trimpath
|
|
||||||
ldflags:
|
|
||||||
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
|
||||||
hooks:
|
|
||||||
post: |
|
|
||||||
sh -c '
|
|
||||||
cat > /tmp/fzf-gon-amd64.hcl << EOF
|
|
||||||
source = ["./dist/fzf-macos_darwin_amd64_v1/fzf"]
|
|
||||||
bundle_id = "junegunn.fzf"
|
|
||||||
sign {
|
|
||||||
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
|
|
||||||
}
|
|
||||||
zip {
|
|
||||||
output_path = "./dist/fzf-{{ .Version }}-darwin_amd64.zip"
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
gon /tmp/fzf-gon-amd64.hcl
|
|
||||||
'
|
|
||||||
|
|
||||||
- id: fzf-macos-arm
|
|
||||||
binary: fzf
|
|
||||||
goos:
|
|
||||||
- darwin
|
|
||||||
goarch:
|
|
||||||
- arm64
|
|
||||||
flags:
|
|
||||||
- -trimpath
|
|
||||||
ldflags:
|
|
||||||
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
|
||||||
hooks:
|
|
||||||
post: |
|
|
||||||
sh -c '
|
|
||||||
cat > /tmp/fzf-gon-arm64.hcl << EOF
|
|
||||||
source = ["./dist/fzf-macos-arm_darwin_arm64/fzf"]
|
|
||||||
bundle_id = "junegunn.fzf"
|
|
||||||
sign {
|
|
||||||
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
|
|
||||||
}
|
|
||||||
zip {
|
|
||||||
output_path = "./dist/fzf-{{ .Version }}-darwin_arm64.zip"
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
gon /tmp/fzf-gon-arm64.hcl
|
|
||||||
'
|
|
||||||
|
|
||||||
- id: fzf
|
- id: fzf
|
||||||
goos:
|
goos:
|
||||||
|
- darwin
|
||||||
- linux
|
- linux
|
||||||
- windows
|
- windows
|
||||||
- freebsd
|
- freebsd
|
||||||
@@ -89,6 +39,42 @@ builds:
|
|||||||
- goos: openbsd
|
- goos: openbsd
|
||||||
goarch: arm64
|
goarch: arm64
|
||||||
|
|
||||||
|
# .goreleaser.yaml
|
||||||
|
notarize:
|
||||||
|
macos:
|
||||||
|
- # Whether this configuration is enabled or not.
|
||||||
|
#
|
||||||
|
# Default: false.
|
||||||
|
# Templates: allowed.
|
||||||
|
enabled: "{{ not .IsSnapshot }}"
|
||||||
|
|
||||||
|
# Before notarizing, we need to sign the binary.
|
||||||
|
# This blocks defines the configuration for doing so.
|
||||||
|
sign:
|
||||||
|
# The .p12 certificate file path or its base64'd contents.
|
||||||
|
certificate: "{{.Env.MACOS_SIGN_P12}}"
|
||||||
|
|
||||||
|
# The password to be used to open the certificate.
|
||||||
|
password: "{{.Env.MACOS_SIGN_PASSWORD}}"
|
||||||
|
|
||||||
|
# Then, we notarize the binaries.
|
||||||
|
notarize:
|
||||||
|
# The issuer ID.
|
||||||
|
# Its the UUID you see when creating the App Store Connect key.
|
||||||
|
issuer_id: "{{.Env.MACOS_NOTARY_ISSUER_ID}}"
|
||||||
|
|
||||||
|
# Key ID.
|
||||||
|
# You can see it in the list of App Store Connect Keys.
|
||||||
|
# It will also be in the ApiKey filename.
|
||||||
|
key_id: "{{.Env.MACOS_NOTARY_KEY_ID}}"
|
||||||
|
|
||||||
|
# The .p8 key file path or its base64'd contents.
|
||||||
|
key: "{{.Env.MACOS_NOTARY_KEY}}"
|
||||||
|
|
||||||
|
# Whether to wait for the notarization to finish.
|
||||||
|
# Not recommended, as it could take a really long time.
|
||||||
|
wait: true
|
||||||
|
|
||||||
archives:
|
archives:
|
||||||
- name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
|
- name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
|
||||||
builds:
|
builds:
|
||||||
@@ -100,21 +86,15 @@ archives:
|
|||||||
files:
|
files:
|
||||||
- non-existent*
|
- non-existent*
|
||||||
|
|
||||||
checksum:
|
|
||||||
extra_files:
|
|
||||||
- glob: ./dist/fzf-*darwin*.zip
|
|
||||||
|
|
||||||
release:
|
release:
|
||||||
github:
|
github:
|
||||||
owner: junegunn
|
owner: junegunn
|
||||||
name: fzf
|
name: fzf
|
||||||
prerelease: auto
|
prerelease: auto
|
||||||
name_template: '{{ .Tag }}'
|
name_template: '{{ .Version }}'
|
||||||
extra_files:
|
|
||||||
- glob: ./dist/fzf-*darwin*.zip
|
|
||||||
|
|
||||||
snapshot:
|
snapshot:
|
||||||
name_template: "{{ .Tag }}-devel"
|
name_template: "{{ .Version }}-devel"
|
||||||
|
|
||||||
changelog:
|
changelog:
|
||||||
sort: asc
|
sort: asc
|
||||||
|
@@ -6,7 +6,7 @@ Lint/ShadowingOuterLocalVariable:
|
|||||||
Enabled: false
|
Enabled: false
|
||||||
Style/MethodCallWithArgsParentheses:
|
Style/MethodCallWithArgsParentheses:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
IgnoredMethods:
|
AllowedMethods:
|
||||||
- assert
|
- assert
|
||||||
- exit
|
- exit
|
||||||
- paste
|
- paste
|
||||||
@@ -15,7 +15,7 @@ Style/MethodCallWithArgsParentheses:
|
|||||||
- refute
|
- refute
|
||||||
- require
|
- require
|
||||||
- send_keys
|
- send_keys
|
||||||
IgnoredPatterns:
|
AllowedPatterns:
|
||||||
- ^assert_
|
- ^assert_
|
||||||
- ^refute_
|
- ^refute_
|
||||||
Style/NumericPredicate:
|
Style/NumericPredicate:
|
||||||
|
22
ADVANCED.md
22
ADVANCED.md
@@ -1,8 +1,8 @@
|
|||||||
Advanced fzf examples
|
Advanced fzf examples
|
||||||
======================
|
======================
|
||||||
|
|
||||||
* *Last update: 2024/06/06*
|
* *Last update: 2024/06/24*
|
||||||
* *Requires fzf 0.53.0 or later*
|
* *Requires fzf 0.54.0 or later*
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -361,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 : \
|
||||||
@@ -372,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
|
||||||
@@ -400,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" \
|
||||||
@@ -444,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)" \
|
||||||
@@ -487,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 ]] &&
|
||||||
@@ -527,7 +525,7 @@ 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' \
|
||||||
|
148
CHANGELOG.md
148
CHANGELOG.md
@@ -1,8 +1,156 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.56.0
|
||||||
|
------
|
||||||
|
- Added `--gap[=N]` option to display empty lines between items.
|
||||||
|
- This can be useful to visually separate adjacent 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 --gap
|
||||||
|
```
|
||||||
|
- Or just to make the list easier to read. For single-line items, you probably want to set `--color gutter:-1` as well to hide the gutter.
|
||||||
|
```sh
|
||||||
|
fzf --info inline-right --gap --color gutter:-1
|
||||||
|
```
|
||||||
|
- Added `noinfo` option to `--preview-window` to hide the scroll indicator in the preview window
|
||||||
|
- Bug fixes
|
||||||
|
- Thanks to @LangLangBart, @akinomyoga, and @charlievieth for fixing the bugs
|
||||||
|
|
||||||
|
0.55.0
|
||||||
|
------
|
||||||
|
_Release highlights: https://junegunn.github.io/fzf/releases/0.55.0/_
|
||||||
|
|
||||||
|
- Added `exact-boundary-match` type to the search syntax. When a search term is single-quoted, fzf will search for the exact occurrences of the string with both ends at word boundaries.
|
||||||
|
```sh
|
||||||
|
fzf --query "'here'" << EOF
|
||||||
|
come here
|
||||||
|
not there
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
- [bash] Fuzzy path completion is enabled for all commands
|
||||||
|
- 1. If the default completion is not already set
|
||||||
|
- 2. And if the current bash supports `complete -D` option
|
||||||
|
- However, fuzzy completion for some commands can be "dynamically" disabled by the dynamic completion loader
|
||||||
|
- See the comment in `__fzf_default_completion` function for more information
|
||||||
|
- Comments are now allowed in `$FZF_DEFAULT_OPTS` and `$FZF_DEFAULT_OPTS_FILE`
|
||||||
|
```sh
|
||||||
|
export FZF_DEFAULT_OPTS='
|
||||||
|
# Layout options
|
||||||
|
--layout=reverse
|
||||||
|
--info=inline-right # Show info on the right side of the prompt line
|
||||||
|
# ...
|
||||||
|
'
|
||||||
|
```
|
||||||
|
- Hyperlinks (OSC 8) are now supported in the preview window and in the main window
|
||||||
|
```sh
|
||||||
|
printf '<< \e]8;;http://github.com/junegunn/fzf\e\\Link to \e[32mfz\e[0mf\e]8;;\e\\ >>' | fzf --ansi
|
||||||
|
|
||||||
|
fzf --preview "printf '<< \e]8;;http://github.com/junegunn/fzf\e\\Link to \e[32mfz\e[0mf\e]8;;\e\\ >>'"
|
||||||
|
```
|
||||||
|
- The default `--ellipsis` is now `··` instead of `..`.
|
||||||
|
- [vim] A spec can have `exit` callback that is called with the exit status of fzf
|
||||||
|
- This can be used to clean up temporary resources or restore the original state when fzf is closed without a selection
|
||||||
|
- Fixed `--tmux bottom` when the status line is not at the bottom
|
||||||
|
- Fixed extra scroll offset in multi-line mode (`--read0` or `--wrap`)
|
||||||
|
- Added fallback `ps` command for `kill` completion on Cygwin
|
||||||
|
|
||||||
|
0.54.3
|
||||||
|
------
|
||||||
|
- Fixed incompatibility of adaptive height specification and 'start:reload'
|
||||||
|
```sh
|
||||||
|
# A regression in 0.54.0 would cause this to fail
|
||||||
|
fzf --height '~100%' --bind 'start:reload:seq 10'
|
||||||
|
```
|
||||||
|
- Environment variables are now available to `$FZF_DEFAULT_COMMAND`
|
||||||
|
```sh
|
||||||
|
FZF_DEFAULT_COMMAND='echo $FZF_QUERY' fzf --query foo
|
||||||
|
```
|
||||||
|
|
||||||
|
0.54.2
|
||||||
|
------
|
||||||
|
- Fixed incorrect syntax highlighting of truncated multi-line entries
|
||||||
|
- Updated GoReleaser to 2.1.0 to simplify notarization of macOS binaries
|
||||||
|
- macOS archives will be in `tar.gz` format instead of `zip` format since we no longer notarize the zip files but binaries
|
||||||
|
- (Windows) Reverted a mintty fix in 0.54.0
|
||||||
|
- As a result, mouse may not work on mintty in fullscreen mode. However, fzf will correctly read non-ASCII input in fullscreen mode (`--no-height`).
|
||||||
|
- fzf unfortunately cannot read non-ASCII input when not in fullscreen mode on Windows. So if you need to input non-ASCII characters, add `--no-height` to your `$FZF_DEFAULT_OPTS`.
|
||||||
|
- Any help in fixing this issue will be appreciated (#3799, #3847).
|
||||||
|
|
||||||
|
0.54.1
|
||||||
|
------
|
||||||
|
- Updated [fastwalk](https://github.com/charlievieth/fastwalk) dependency for built-in directory walker
|
||||||
|
- [fastwalk: add optional sorting and improve documentation](https://github.com/charlievieth/fastwalk/pull/27)
|
||||||
|
- [fastwalk: only check if MSYSTEM is set during MSYS/MSYS2](https://github.com/charlievieth/fastwalk/pull/28)
|
||||||
|
- Thanks to @charlievieth
|
||||||
|
- Reverted ALT-C binding of fish to use `cd` instead of `builtin cd`
|
||||||
|
- `builtin cd` was introduced to work around a bug of `cd` coming from `zoxide init --cmd cd fish` where it cannot handle `--` argument.
|
||||||
|
- However, the default `cd` of fish is actually a wrapper function for supporting `cd -`, so we want to use it instead.
|
||||||
|
- See [#3928](https://github.com/junegunn/fzf/pull/3928) for more information and consider helping zoxide fix the bug.
|
||||||
|
|
||||||
|
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
|
0.53.0
|
||||||
------
|
------
|
||||||
|
_Release highlights: https://junegunn.github.io/fzf/releases/0.53.0/_
|
||||||
|
|
||||||
- Multi-line display
|
- Multi-line display
|
||||||
- See [Processing multi-line items](https://junegunn.github.io/fzf/tips/processing-multi-line-items/)
|
- See [Processing multi-line items](https://junegunn.github.io/fzf/tips/processing-multi-line-items/)
|
||||||
- fzf can now display multi-line items
|
- fzf can now display multi-line items
|
||||||
|
11
Makefile
11
Makefile
@@ -9,12 +9,12 @@ SOURCES := $(wildcard *.go src/*.go src/*/*.go shell/*sh man/man1/*.1) $(
|
|||||||
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
|
||||||
@@ -77,7 +77,6 @@ endif
|
|||||||
all: target/$(BINARY)
|
all: target/$(BINARY)
|
||||||
|
|
||||||
test: $(SOURCES)
|
test: $(SOURCES)
|
||||||
[ -z "$$(gofmt -s -d src)" ] || (gofmt -s -d src; exit 1)
|
|
||||||
SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \
|
SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \
|
||||||
github.com/junegunn/fzf/src \
|
github.com/junegunn/fzf/src \
|
||||||
github.com/junegunn/fzf/src/algo \
|
github.com/junegunn/fzf/src/algo \
|
||||||
@@ -87,6 +86,10 @@ test: $(SOURCES)
|
|||||||
bench:
|
bench:
|
||||||
cd src && SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" -run=Bench -bench=. -benchmem
|
cd src && SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" -run=Bench -bench=. -benchmem
|
||||||
|
|
||||||
|
lint: $(SOURCES) test/test_go.rb
|
||||||
|
[ -z "$$(gofmt -s -d src)" ] || (gofmt -s -d src; exit 1)
|
||||||
|
rubocop --require rubocop-minitest --require rubocop-performance
|
||||||
|
|
||||||
install: bin/fzf
|
install: bin/fzf
|
||||||
|
|
||||||
generate:
|
generate:
|
||||||
@@ -184,4 +187,4 @@ update:
|
|||||||
$(GO) get -u
|
$(GO) get -u
|
||||||
$(GO) mod tidy
|
$(GO) mod tidy
|
||||||
|
|
||||||
.PHONY: all generate build release test bench install clean docker docker-test update
|
.PHONY: all generate build release test bench lint install clean docker docker-test update
|
||||||
|
@@ -289,8 +289,9 @@ The following table summarizes the available options.
|
|||||||
| `source` | string | External command to generate input to fzf (e.g. `find .`) |
|
| `source` | string | External command to generate input to fzf (e.g. `find .`) |
|
||||||
| `source` | list | Vim list as input to fzf |
|
| `source` | list | Vim list as input to fzf |
|
||||||
| `sink` | string | Vim command to handle the selected item (e.g. `e`, `tabe`) |
|
| `sink` | string | Vim command to handle the selected item (e.g. `e`, `tabe`) |
|
||||||
| `sink` | funcref | Reference to function to process each selected item |
|
| `sink` | funcref | Function to be called with each selected item |
|
||||||
| `sinklist` (or `sink*`) | funcref | Similar to `sink`, but takes the list of output lines at once |
|
| `sinklist` (or `sink*`) | funcref | Similar to `sink`, but takes the list of output lines at once |
|
||||||
|
| `exit` | funcref | Function to be called with the exit status of fzf (e.g. 0, 1, 2, 130) |
|
||||||
| `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%`) |
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
24
bin/fzf-tmux
24
bin/fzf-tmux
@@ -19,6 +19,9 @@ term=""
|
|||||||
[[ -n "$LINES" ]] && lines=$LINES || lines=$(tput lines) || lines=$(tmux display-message -p "#{pane_height}")
|
[[ -n "$LINES" ]] && lines=$LINES || lines=$(tput lines) || lines=$(tmux display-message -p "#{pane_height}")
|
||||||
[[ -n "$COLUMNS" ]] && columns=$COLUMNS || columns=$(tput cols) || columns=$(tmux display-message -p "#{pane_width}")
|
[[ -n "$COLUMNS" ]] && columns=$COLUMNS || columns=$(tput cols) || columns=$(tmux display-message -p "#{pane_width}")
|
||||||
|
|
||||||
|
tmux_version=$(tmux -V | sed 's/[^0-9.]//g')
|
||||||
|
tmux_32=$(awk '{print ($1 >= 3.2)}' <<< "$tmux_version" 2> /dev/null || bc -l <<< "$tmux_version >= 3.2")
|
||||||
|
|
||||||
help() {
|
help() {
|
||||||
>&2 echo 'usage: fzf-tmux [LAYOUT OPTIONS] [--] [FZF OPTIONS]
|
>&2 echo 'usage: fzf-tmux [LAYOUT OPTIONS] [--] [FZF OPTIONS]
|
||||||
|
|
||||||
@@ -94,10 +97,18 @@ while [[ $# -gt 0 ]]; do
|
|||||||
opt="$opt ${arg:0:2}$size"
|
opt="$opt ${arg:0:2}$size"
|
||||||
elif [[ "$size" =~ %$ ]]; then
|
elif [[ "$size" =~ %$ ]]; then
|
||||||
size=${size:0:((${#size}-1))}
|
size=${size:0:((${#size}-1))}
|
||||||
if [[ -n "$swap" ]]; then
|
if [[ $tmux_32 = 1 ]]; then
|
||||||
opt="$opt -l $(( 100 - size ))%"
|
if [[ -n "$swap" ]]; then
|
||||||
|
opt="$opt -l $(( 100 - size ))%"
|
||||||
|
else
|
||||||
|
opt="$opt -l $size%"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
opt="$opt -l $size%"
|
if [[ -n "$swap" ]]; then
|
||||||
|
opt="$opt -p $(( 100 - size ))"
|
||||||
|
else
|
||||||
|
opt="$opt -p $size"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if [[ -n "$swap" ]]; then
|
if [[ -n "$swap" ]]; then
|
||||||
@@ -187,12 +198,11 @@ trap 'cleanup' EXIT
|
|||||||
|
|
||||||
envs="export TERM=$TERM "
|
envs="export TERM=$TERM "
|
||||||
if [[ "$opt" =~ "-E" ]]; then
|
if [[ "$opt" =~ "-E" ]]; then
|
||||||
tmux_version=$(tmux -V | sed 's/[^0-9.]//g')
|
if [[ $tmux_version = 3.2 ]]; then
|
||||||
if [[ $(awk '{print ($1 > 3.2)}' <<< "$tmux_version" 2> /dev/null || bc -l <<< "$tmux_version > 3.2") = 1 ]]; then
|
FZF_DEFAULT_OPTS="--margin 0,1 $FZF_DEFAULT_OPTS"
|
||||||
|
elif [[ $tmux_32 = 1 ]]; then
|
||||||
FZF_DEFAULT_OPTS="--border $FZF_DEFAULT_OPTS"
|
FZF_DEFAULT_OPTS="--border $FZF_DEFAULT_OPTS"
|
||||||
opt="-B $opt"
|
opt="-B $opt"
|
||||||
elif [[ $tmux_version = 3.2 ]]; then
|
|
||||||
FZF_DEFAULT_OPTS="--margin 0,1 $FZF_DEFAULT_OPTS"
|
|
||||||
else
|
else
|
||||||
echo "fzf-tmux: tmux 3.2 or above is required for popup mode" >&2
|
echo "fzf-tmux: tmux 3.2 or above is required for popup mode" >&2
|
||||||
exit 2
|
exit 2
|
||||||
|
@@ -306,8 +306,9 @@ The following table summarizes the available options.
|
|||||||
`source` | string | External command to generate input to fzf (e.g. `find .` )
|
`source` | string | External command to generate input to fzf (e.g. `find .` )
|
||||||
`source` | list | Vim list as input to fzf
|
`source` | list | Vim list as input to fzf
|
||||||
`sink` | string | Vim command to handle the selected item (e.g. `e` , `tabe` )
|
`sink` | string | Vim command to handle the selected item (e.g. `e` , `tabe` )
|
||||||
`sink` | funcref | Reference to function to process each selected item
|
`sink` | funcref | Function to be called with each selected item
|
||||||
`sinklist` (or `sink*` ) | funcref | Similar to `sink` , but takes the list of output lines at once
|
`sinklist` (or `sink*` ) | funcref | Similar to `sink` , but takes the list of output lines at once
|
||||||
|
`exit` | funcref | Function to be called with the exit status of fzf (e.g. 0, 1, 2, 130)
|
||||||
`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%` )
|
||||||
|
8
go.mod
8
go.mod
@@ -1,13 +1,13 @@
|
|||||||
module github.com/junegunn/fzf
|
module github.com/junegunn/fzf
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/charlievieth/fastwalk v1.0.3
|
github.com/charlievieth/fastwalk v1.0.9
|
||||||
github.com/gdamore/tcell/v2 v2.7.4
|
github.com/gdamore/tcell/v2 v2.7.4
|
||||||
|
github.com/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97
|
||||||
github.com/mattn/go-isatty v0.0.20
|
github.com/mattn/go-isatty v0.0.20
|
||||||
github.com/mattn/go-shellwords v1.0.12
|
|
||||||
github.com/rivo/uniseg v0.4.7
|
github.com/rivo/uniseg v0.4.7
|
||||||
golang.org/x/sys v0.20.0
|
golang.org/x/sys v0.26.0
|
||||||
golang.org/x/term v0.20.0
|
golang.org/x/term v0.25.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
16
go.sum
16
go.sum
@@ -1,17 +1,17 @@
|
|||||||
github.com/charlievieth/fastwalk v1.0.3 h1:eNWFaNPe5srPqQ5yyDbhAf11paeZaHWcihRhpuYFfSg=
|
github.com/charlievieth/fastwalk v1.0.9 h1:Odb92AfoReO3oFBfDGT5J+nwgzQPF/gWAw6E6/lkor0=
|
||||||
github.com/charlievieth/fastwalk v1.0.3/go.mod h1:JSfglY/gmL/rqsUS1NCsJTocB5n6sSl9ApAqif4CUbs=
|
github.com/charlievieth/fastwalk v1.0.9/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI=
|
||||||
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.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU=
|
github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU=
|
||||||
github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg=
|
github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg=
|
||||||
|
github.com/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97 h1:rqzLixVo1c/GQW6px9j1xQmlvQIn+lf/V6M1UQ7IFzw=
|
||||||
|
github.com/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97/go.mod h1:6EILKtGpo5t+KLb85LNZLAF6P9LKp78hJI80PXMcn3c=
|
||||||
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.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-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/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.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
@@ -36,14 +36,14 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.26.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.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
|
||||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
||||||
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=
|
||||||
|
8
install
8
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.53.0
|
version=0.56.0
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
@@ -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
|
||||||
@@ -168,8 +168,8 @@ archi=$(uname -sm)
|
|||||||
binary_available=1
|
binary_available=1
|
||||||
binary_error=""
|
binary_error=""
|
||||||
case "$archi" in
|
case "$archi" in
|
||||||
Darwin\ arm64) download fzf-$version-darwin_arm64.zip ;;
|
Darwin\ arm64) download fzf-$version-darwin_arm64.tar.gz ;;
|
||||||
Darwin\ x86_64) download fzf-$version-darwin_amd64.zip ;;
|
Darwin\ x86_64) download fzf-$version-darwin_amd64.tar.gz ;;
|
||||||
Linux\ armv5*) download fzf-$version-linux_armv5.tar.gz ;;
|
Linux\ armv5*) download fzf-$version-linux_armv5.tar.gz ;;
|
||||||
Linux\ armv6*) download fzf-$version-linux_armv6.tar.gz ;;
|
Linux\ armv6*) download fzf-$version-linux_armv6.tar.gz ;;
|
||||||
Linux\ armv7*) download fzf-$version-linux_armv7.tar.gz ;;
|
Linux\ armv7*) download fzf-$version-linux_armv7.tar.gz ;;
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
$version="0.53.0"
|
$version="0.56.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) {
|
||||||
|
4
main.go
4
main.go
@@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/protector"
|
"github.com/junegunn/fzf/src/protector"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "0.53"
|
var version = "0.56"
|
||||||
var revision = "devel"
|
var revision = "devel"
|
||||||
|
|
||||||
//go:embed shell/key-bindings.bash
|
//go:embed shell/key-bindings.bash
|
||||||
@@ -39,7 +39,7 @@ func printScript(label string, content []byte) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func exit(code int, err error) {
|
func exit(code int, err error) {
|
||||||
if code == fzf.ExitError {
|
if code == fzf.ExitError && err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err.Error())
|
fmt.Fprintln(os.Stderr, err.Error())
|
||||||
}
|
}
|
||||||
os.Exit(code)
|
os.Exit(code)
|
||||||
|
@@ -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 "Jun 2024" "fzf 0.53.0" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf\-tmux 1 "Oct 2024" "fzf 0.56.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 [\fILAYOUT OPTIONS\fR] [\-\-] [\fIFZF OPTIONS\fR]
|
||||||
|
|
||||||
.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
|
||||||
|
986
man/man1/fzf.1
986
man/man1/fzf.1
File diff suppressed because it is too large
Load Diff
@@ -198,6 +198,7 @@ function! s:compare_binary_versions(a, b)
|
|||||||
return s:compare_versions(s:get_version(a:a), s:get_version(a:b))
|
return s:compare_versions(s:get_version(a:a), s:get_version(a:b))
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
let s:min_version = '0.53.0'
|
||||||
let s:checked = {}
|
let s:checked = {}
|
||||||
function! fzf#exec(...)
|
function! fzf#exec(...)
|
||||||
if !exists('s:exec')
|
if !exists('s:exec')
|
||||||
@@ -225,7 +226,11 @@ function! fzf#exec(...)
|
|||||||
let s:exec = binaries[-1]
|
let s:exec = binaries[-1]
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if a:0 && !has_key(s:checked, a:1)
|
let min_version = s:min_version
|
||||||
|
if a:0 && s:compare_versions(a:1, min_version) > 0
|
||||||
|
let min_version = a:1
|
||||||
|
endif
|
||||||
|
if !has_key(s:checked, min_version)
|
||||||
let fzf_version = s:get_version(s:exec)
|
let fzf_version = s:get_version(s:exec)
|
||||||
if empty(fzf_version)
|
if empty(fzf_version)
|
||||||
let message = printf('Failed to run "%s --version"', s:exec)
|
let message = printf('Failed to run "%s --version"', s:exec)
|
||||||
@@ -233,17 +238,17 @@ function! fzf#exec(...)
|
|||||||
throw message
|
throw message
|
||||||
end
|
end
|
||||||
|
|
||||||
if s:compare_versions(fzf_version, a:1) >= 0
|
if s:compare_versions(fzf_version, min_version) >= 0
|
||||||
let s:checked[a:1] = 1
|
let s:checked[min_version] = 1
|
||||||
return s:exec
|
return s:exec
|
||||||
elseif a:0 < 2 && input(printf('You need fzf %s or above. Found: %s. Download binary? (y/n) ', a:1, fzf_version)) =~? '^y'
|
elseif a:0 < 2 && input(printf('You need fzf %s or above. Found: %s. Download binary? (y/n) ', min_version, fzf_version)) =~? '^y'
|
||||||
let s:versions = {}
|
let s:versions = {}
|
||||||
unlet s:exec
|
unlet s:exec
|
||||||
redraw
|
redraw
|
||||||
call fzf#install()
|
call fzf#install()
|
||||||
return fzf#exec(a:1, 1)
|
return fzf#exec(min_version, 1)
|
||||||
else
|
else
|
||||||
throw printf('You need to upgrade fzf (required: %s or above)', a:1)
|
throw printf('You need to upgrade fzf (required: %s or above)', min_version)
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@@ -665,21 +670,17 @@ else
|
|||||||
let s:launcher = function('s:xterm_launcher')
|
let s:launcher = function('s:xterm_launcher')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
function! s:exit_handler(code, command, ...)
|
function! s:exit_handler(dict, code, command, ...)
|
||||||
if a:code == 130
|
if has_key(a:dict, 'exit')
|
||||||
return 0
|
call a:dict.exit(a:code)
|
||||||
elseif has('nvim') && a:code == 129
|
endif
|
||||||
" When deleting the terminal buffer while fzf is still running,
|
if a:code == 2
|
||||||
" Nvim sends SIGHUP.
|
|
||||||
return 0
|
|
||||||
elseif a:code > 1
|
|
||||||
call s:error('Error running ' . a:command)
|
call s:error('Error running ' . a:command)
|
||||||
if !empty(a:000)
|
if !empty(a:000)
|
||||||
sleep
|
sleep
|
||||||
endif
|
endif
|
||||||
return 0
|
|
||||||
endif
|
endif
|
||||||
return 1
|
return a:code
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:execute(dict, command, use_height, temps) abort
|
function! s:execute(dict, command, use_height, temps) abort
|
||||||
@@ -731,7 +732,7 @@ function! s:execute(dict, command, use_height, temps) abort
|
|||||||
let exit_status = v:shell_error
|
let exit_status = v:shell_error
|
||||||
redraw!
|
redraw!
|
||||||
let lines = s:collect(a:temps)
|
let lines = s:collect(a:temps)
|
||||||
return s:exit_handler(exit_status, command) ? lines : []
|
return s:exit_handler(a:dict, exit_status, command) < 2 ? lines : []
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:execute_tmux(dict, command, temps) abort
|
function! s:execute_tmux(dict, command, temps) abort
|
||||||
@@ -746,7 +747,7 @@ function! s:execute_tmux(dict, command, temps) abort
|
|||||||
let exit_status = v:shell_error
|
let exit_status = v:shell_error
|
||||||
redraw!
|
redraw!
|
||||||
let lines = s:collect(a:temps)
|
let lines = s:collect(a:temps)
|
||||||
return s:exit_handler(exit_status, command) ? lines : []
|
return s:exit_handler(a:dict, exit_status, command) < 2 ? lines : []
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:calc_size(max, val, dict)
|
function! s:calc_size(max, val, dict)
|
||||||
@@ -912,7 +913,7 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
let lines = s:collect(self.temps)
|
let lines = s:collect(self.temps)
|
||||||
if !s:exit_handler(a:code, self.command, 1)
|
if s:exit_handler(self.dict, a:code, self.command, 1) >= 2
|
||||||
return
|
return
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
@@ -167,6 +167,7 @@ _fzf_opts_completion() {
|
|||||||
--version
|
--version
|
||||||
--with-nth
|
--with-nth
|
||||||
--with-shell
|
--with-shell
|
||||||
|
--wrap
|
||||||
--zsh
|
--zsh
|
||||||
-0 --exit-0
|
-0 --exit-0
|
||||||
-1 --select-1
|
-1 --select-1
|
||||||
@@ -263,6 +264,7 @@ _fzf_handle_dynamic_completion() {
|
|||||||
# _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)
|
||||||
|
__fzf_orig_completion_get_orig_func "$cmd" || ret=1
|
||||||
|
|
||||||
# Update orig_complete by _fzf_orig_completion entry
|
# Update orig_complete by _fzf_orig_completion entry
|
||||||
[[ $orig_complete =~ ' -F '(_fzf_[^ ]+)' ' ]] &&
|
[[ $orig_complete =~ ' -F '(_fzf_[^ ]+)' ' ]] &&
|
||||||
@@ -288,7 +290,7 @@ __fzf_generic_path_completion() {
|
|||||||
fi
|
fi
|
||||||
COMPREPLY=()
|
COMPREPLY=()
|
||||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
[[ $COMP_CWORD -ge 0 ]] && cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
|
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
|
||||||
base=${cur:0:${#cur}-${#trigger}}
|
base=${cur:0:${#cur}-${#trigger}}
|
||||||
eval "base=$base" 2> /dev/null || return
|
eval "base=$base" 2> /dev/null || return
|
||||||
@@ -375,7 +377,7 @@ _fzf_complete() {
|
|||||||
selected=$(
|
selected=$(
|
||||||
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse" "${FZF_COMPLETION_OPTS-} $str_arg") \
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse" "${FZF_COMPLETION_OPTS-} $str_arg") \
|
||||||
FZF_DEFAULT_OPTS_FILE='' \
|
FZF_DEFAULT_OPTS_FILE='' \
|
||||||
__fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | command tr '\n' ' ')
|
__fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | eval "$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")
|
||||||
@@ -407,9 +409,10 @@ _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 2> /dev/null || # For BusyBox
|
||||||
|
command ps --everyone --full --windows # For cygwin
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,12 +482,38 @@ 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
|
||||||
|
|
||||||
|
# Default path completion
|
||||||
|
__fzf_default_completion() {
|
||||||
|
__fzf_generic_path_completion _fzf_compgen_path "-m" "" "$@"
|
||||||
|
|
||||||
|
# Dynamic completion loader has updated the completion for the command
|
||||||
|
if [[ $? -eq 124 ]]; then
|
||||||
|
# We trigger _fzf_setup_completion so that fuzzy completion for the command
|
||||||
|
# still works. However, loader can update the completion for multiple
|
||||||
|
# commands at once, and fuzzy completion will no longer work for those
|
||||||
|
# other commands. e.g. pytest -> py.test, pytest-2, pytest-3, etc
|
||||||
|
_fzf_setup_completion path "$1"
|
||||||
|
return 124
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set fuzzy path completion as the default completion for all commands.
|
||||||
|
# We can't set up default completion,
|
||||||
|
# 1. if it's already set up by another script
|
||||||
|
# 2. or if the current version of bash doesn't support -D option
|
||||||
|
complete | command grep -q __fzf_default_completion ||
|
||||||
|
complete | command grep -- '-D$' | command grep -qv _comp_complete_load ||
|
||||||
|
complete -D -F __fzf_default_completion -o default -o bashdefault 2> /dev/null
|
||||||
|
|
||||||
d_cmds="${FZF_COMPLETION_DIR_COMMANDS-cd pushd rmdir}"
|
d_cmds="${FZF_COMPLETION_DIR_COMMANDS-cd pushd rmdir}"
|
||||||
|
|
||||||
# NOTE: $FZF_COMPLETION_PATH_COMMANDS and $FZF_COMPLETION_VAR_COMMANDS are
|
# NOTE: $FZF_COMPLETION_PATH_COMMANDS and $FZF_COMPLETION_VAR_COMMANDS are
|
||||||
# undocumented and subject to change in the future.
|
# undocumented and subject to change in the future.
|
||||||
|
#
|
||||||
|
# NOTE: Although we have default completion, we still need to set up completion
|
||||||
|
# for each command in case they already have completion set up by another script.
|
||||||
a_cmds="${FZF_COMPLETION_PATH_COMMANDS-"
|
a_cmds="${FZF_COMPLETION_PATH_COMMANDS-"
|
||||||
awk bat cat diff diff3
|
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
|
||||||
|
@@ -157,7 +157,8 @@ __fzf_generic_path_completion() {
|
|||||||
[ -z "$dir" ] && dir='.'
|
[ -z "$dir" ] && dir='.'
|
||||||
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
||||||
matches=$(
|
matches=$(
|
||||||
export FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --scheme=path" "${FZF_COMPLETION_OPTS-}")
|
export FZF_DEFAULT_OPTS
|
||||||
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --scheme=path" "${FZF_COMPLETION_OPTS-}")
|
||||||
unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE
|
unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE
|
||||||
if declare -f "$compgen" > /dev/null; then
|
if declare -f "$compgen" > /dev/null; then
|
||||||
eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover"
|
eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover"
|
||||||
@@ -170,9 +171,9 @@ __fzf_generic_path_completion() {
|
|||||||
rest=${FZF_COMPLETION_PATH_OPTS-}
|
rest=${FZF_COMPLETION_PATH_OPTS-}
|
||||||
fi
|
fi
|
||||||
__fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" --walker "$walker" --walker-root="$dir" ${(Q)${(Z+n+)rest}} < /dev/tty
|
__fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" --walker "$walker" --walker-root="$dir" ${(Q)${(Z+n+)rest}} < /dev/tty
|
||||||
fi | while read item; do
|
fi | while read -r item; do
|
||||||
item="${item%$suffix}$suffix"
|
item="${item%$suffix}$suffix"
|
||||||
echo -n "${(q)item} "
|
echo -n -E "${(q)item} "
|
||||||
done
|
done
|
||||||
)
|
)
|
||||||
matches=${matches% }
|
matches=${matches% }
|
||||||
@@ -197,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
|
||||||
@@ -264,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}')
|
||||||
;;
|
;;
|
||||||
@@ -296,9 +298,10 @@ _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 2> /dev/null || # For BusyBox
|
||||||
|
command ps --everyone --full --windows # For cygwin
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -62,7 +62,7 @@ if command -v perl > /dev/null; then
|
|||||||
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=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --highlight-line ${FZF_CTRL_R_OPTS-} +m --read0") \
|
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"
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) --query "$READLINE_LINE"
|
||||||
) || return
|
) || return
|
||||||
READLINE_LINE=$(command perl -pe 's/^\d*\t//' <<< "$output")
|
READLINE_LINE=$(command perl -pe 's/^\d*\t//' <<< "$output")
|
||||||
@@ -91,7 +91,7 @@ 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=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --highlight-line ${FZF_CTRL_R_OPTS-} +m --read0") \
|
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"
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) --query "$READLINE_LINE"
|
||||||
) || return
|
) || return
|
||||||
READLINE_LINE=${output#*$'\t'}
|
READLINE_LINE=${output#*$'\t'}
|
||||||
|
@@ -62,17 +62,22 @@ function fzf_key_bindings
|
|||||||
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 \) ];
|
||||||
if type -P perl > /dev/null 2>&1
|
if type -P perl > /dev/null 2>&1
|
||||||
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --highlight-line $FZF_CTRL_R_OPTS +m")
|
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 ''
|
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
|
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
|
and commandline -- $result
|
||||||
else
|
else
|
||||||
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "" "--scheme=history --bind=ctrl-r:toggle-sort --highlight-line $FZF_CTRL_R_OPTS +m")
|
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 ''
|
set -lx FZF_DEFAULT_OPTS_FILE ''
|
||||||
builtin history -z | eval (__fzfcmd) --read0 --print0 -q '(commandline)' | read -lz result
|
builtin history -z | eval (__fzfcmd) --read0 --print0 -q '(commandline)' | read -lz result
|
||||||
and commandline -- $result
|
and commandline -- $result
|
||||||
@@ -99,7 +104,7 @@ function fzf_key_bindings
|
|||||||
eval (__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
eval (__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
||||||
|
|
||||||
if [ -n "$result" ]
|
if [ -n "$result" ]
|
||||||
builtin cd -- $result
|
cd -- $result
|
||||||
|
|
||||||
# Remove last token from commandline.
|
# Remove last token from commandline.
|
||||||
commandline -t ""
|
commandline -t ""
|
||||||
|
@@ -52,8 +52,8 @@ __fzf_select() {
|
|||||||
local item
|
local item
|
||||||
FZF_DEFAULT_COMMAND=${FZF_CTRL_T_COMMAND:-} \
|
FZF_DEFAULT_COMMAND=${FZF_CTRL_T_COMMAND:-} \
|
||||||
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --walker=file,dir,follow,hidden --scheme=path" "${FZF_CTRL_T_OPTS-} -m") \
|
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 item; do
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) "$@" < /dev/tty | while read -r item; do
|
||||||
echo -n "${(q)item} "
|
echo -n -E "${(q)item} "
|
||||||
done
|
done
|
||||||
local ret=$?
|
local ret=$?
|
||||||
echo
|
echo
|
||||||
@@ -106,24 +106,24 @@ 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
|
||||||
# Ensure the associative history array, which maps event numbers to the full
|
# Ensure the associative history array, which maps event numbers to the full
|
||||||
# history lines, is loaded, and that Perl is installed for multi-line output.
|
# 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
|
if zmodload -F zsh/parameter p:history 2>/dev/null && (( ${#commands[perl]} )); then
|
||||||
selected="$(printf '%1$s\t%2$s\000' "${(vk)history[@]}" |
|
selected="$(printf '%s\t%s\000' "${(kv)history[@]}" |
|
||||||
perl -0 -ne 'if (!$seen{(/^\s*[0-9]+\**\s+(.*)/, $1)}++) { s/\n/\n\t/gm; print; }' |
|
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 --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m --read0") \
|
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))"
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
|
||||||
else
|
else
|
||||||
selected="$(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
|
selected="$(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
|
||||||
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m") \
|
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))"
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
|
||||||
fi
|
fi
|
||||||
local ret=$?
|
local ret=$?
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
if num=$(awk '{print $1; exit}' <<< "$selected" | grep -o '^[1-9][0-9]*'); then
|
if [[ $(awk '{print $1; exit}' <<< "$selected") =~ ^[1-9][0-9]* ]]; then
|
||||||
zle vi-fetch-history -n $num
|
zle vi-fetch-history -n $MATCH
|
||||||
else # selected is a custom query, not from history
|
else # selected is a custom query, not from history
|
||||||
LBUFFER="$selected"
|
LBUFFER="$selected"
|
||||||
fi
|
fi
|
||||||
|
@@ -58,73 +58,74 @@ func _() {
|
|||||||
_ = x[actToggleTrack-47]
|
_ = x[actToggleTrack-47]
|
||||||
_ = x[actToggleTrackCurrent-48]
|
_ = x[actToggleTrackCurrent-48]
|
||||||
_ = x[actToggleHeader-49]
|
_ = x[actToggleHeader-49]
|
||||||
_ = x[actTrackCurrent-50]
|
_ = x[actToggleWrap-50]
|
||||||
_ = x[actUntrackCurrent-51]
|
_ = x[actTrackCurrent-51]
|
||||||
_ = x[actDown-52]
|
_ = x[actUntrackCurrent-52]
|
||||||
_ = x[actUp-53]
|
_ = x[actDown-53]
|
||||||
_ = x[actPageUp-54]
|
_ = x[actUp-54]
|
||||||
_ = x[actPageDown-55]
|
_ = x[actPageUp-55]
|
||||||
_ = x[actPosition-56]
|
_ = x[actPageDown-56]
|
||||||
_ = x[actHalfPageUp-57]
|
_ = x[actPosition-57]
|
||||||
_ = x[actHalfPageDown-58]
|
_ = x[actHalfPageUp-58]
|
||||||
_ = x[actOffsetUp-59]
|
_ = x[actHalfPageDown-59]
|
||||||
_ = x[actOffsetDown-60]
|
_ = x[actOffsetUp-60]
|
||||||
_ = x[actJump-61]
|
_ = x[actOffsetDown-61]
|
||||||
_ = x[actJumpAccept-62]
|
_ = x[actOffsetMiddle-62]
|
||||||
_ = x[actPrintQuery-63]
|
_ = x[actJump-63]
|
||||||
_ = x[actRefreshPreview-64]
|
_ = x[actJumpAccept-64]
|
||||||
_ = x[actReplaceQuery-65]
|
_ = x[actPrintQuery-65]
|
||||||
_ = x[actToggleSort-66]
|
_ = x[actRefreshPreview-66]
|
||||||
_ = x[actShowPreview-67]
|
_ = x[actReplaceQuery-67]
|
||||||
_ = x[actHidePreview-68]
|
_ = x[actToggleSort-68]
|
||||||
_ = x[actTogglePreview-69]
|
_ = x[actShowPreview-69]
|
||||||
_ = x[actTogglePreviewWrap-70]
|
_ = x[actHidePreview-70]
|
||||||
_ = x[actTransform-71]
|
_ = x[actTogglePreview-71]
|
||||||
_ = x[actTransformBorderLabel-72]
|
_ = x[actTogglePreviewWrap-72]
|
||||||
_ = x[actTransformHeader-73]
|
_ = x[actTransform-73]
|
||||||
_ = x[actTransformPreviewLabel-74]
|
_ = x[actTransformBorderLabel-74]
|
||||||
_ = x[actTransformPrompt-75]
|
_ = x[actTransformHeader-75]
|
||||||
_ = x[actTransformQuery-76]
|
_ = x[actTransformPreviewLabel-76]
|
||||||
_ = x[actPreview-77]
|
_ = x[actTransformPrompt-77]
|
||||||
_ = x[actChangePreview-78]
|
_ = x[actTransformQuery-78]
|
||||||
_ = x[actChangePreviewWindow-79]
|
_ = x[actPreview-79]
|
||||||
_ = x[actPreviewTop-80]
|
_ = x[actChangePreview-80]
|
||||||
_ = x[actPreviewBottom-81]
|
_ = x[actChangePreviewWindow-81]
|
||||||
_ = x[actPreviewUp-82]
|
_ = x[actPreviewTop-82]
|
||||||
_ = x[actPreviewDown-83]
|
_ = x[actPreviewBottom-83]
|
||||||
_ = x[actPreviewPageUp-84]
|
_ = x[actPreviewUp-84]
|
||||||
_ = x[actPreviewPageDown-85]
|
_ = x[actPreviewDown-85]
|
||||||
_ = x[actPreviewHalfPageUp-86]
|
_ = x[actPreviewPageUp-86]
|
||||||
_ = x[actPreviewHalfPageDown-87]
|
_ = x[actPreviewPageDown-87]
|
||||||
_ = x[actPrevHistory-88]
|
_ = x[actPreviewHalfPageUp-88]
|
||||||
_ = x[actPrevSelected-89]
|
_ = x[actPreviewHalfPageDown-89]
|
||||||
_ = x[actPrint-90]
|
_ = x[actPrevHistory-90]
|
||||||
_ = x[actPut-91]
|
_ = x[actPrevSelected-91]
|
||||||
_ = x[actNextHistory-92]
|
_ = x[actPrint-92]
|
||||||
_ = x[actNextSelected-93]
|
_ = x[actPut-93]
|
||||||
_ = x[actExecute-94]
|
_ = x[actNextHistory-94]
|
||||||
_ = x[actExecuteSilent-95]
|
_ = x[actNextSelected-95]
|
||||||
_ = x[actExecuteMulti-96]
|
_ = x[actExecute-96]
|
||||||
_ = x[actSigStop-97]
|
_ = x[actExecuteSilent-97]
|
||||||
_ = x[actFirst-98]
|
_ = x[actExecuteMulti-98]
|
||||||
_ = x[actLast-99]
|
_ = x[actSigStop-99]
|
||||||
_ = x[actReload-100]
|
_ = x[actFirst-100]
|
||||||
_ = x[actReloadSync-101]
|
_ = x[actLast-101]
|
||||||
_ = x[actDisableSearch-102]
|
_ = x[actReload-102]
|
||||||
_ = x[actEnableSearch-103]
|
_ = x[actReloadSync-103]
|
||||||
_ = x[actSelect-104]
|
_ = x[actDisableSearch-104]
|
||||||
_ = x[actDeselect-105]
|
_ = x[actEnableSearch-105]
|
||||||
_ = x[actUnbind-106]
|
_ = x[actSelect-106]
|
||||||
_ = x[actRebind-107]
|
_ = x[actDeselect-107]
|
||||||
_ = x[actBecome-108]
|
_ = x[actUnbind-108]
|
||||||
_ = x[actResponse-109]
|
_ = x[actRebind-109]
|
||||||
_ = x[actShowHeader-110]
|
_ = x[actBecome-110]
|
||||||
_ = x[actHideHeader-111]
|
_ = x[actShowHeader-111]
|
||||||
|
_ = x[actHideHeader-112]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactResponseactShowHeaderactHideHeader"
|
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, 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, 692, 709, 716, 721, 730, 741, 752, 765, 780, 791, 804, 811, 824, 837, 854, 869, 882, 896, 910, 926, 946, 958, 981, 999, 1023, 1041, 1058, 1068, 1084, 1106, 1119, 1135, 1147, 1161, 1177, 1195, 1215, 1237, 1251, 1266, 1274, 1280, 1294, 1309, 1319, 1335, 1350, 1360, 1368, 1375, 1384, 1397, 1413, 1428, 1437, 1448, 1457, 1466, 1475, 1486, 1499, 1512}
|
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) {
|
||||||
|
@@ -798,6 +798,14 @@ func FuzzyMatchV1(caseSensitive bool, normalize bool, forward bool, text *util.C
|
|||||||
// The solution is much cheaper since there is only one possible alignment of
|
// The solution is much cheaper since there is only one possible alignment of
|
||||||
// the pattern.
|
// the pattern.
|
||||||
func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
|
return exactMatchNaive(caseSensitive, normalize, forward, false, text, pattern, withPos, slab)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExactMatchBoundary(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
|
return exactMatchNaive(caseSensitive, normalize, forward, true, text, pattern, withPos, slab)
|
||||||
|
}
|
||||||
|
|
||||||
|
func exactMatchNaive(caseSensitive bool, normalize bool, forward bool, boundaryCheck bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
if len(pattern) == 0 {
|
if len(pattern) == 0 {
|
||||||
return Result{0, 0, 0}, nil
|
return Result{0, 0, 0}, nil
|
||||||
}
|
}
|
||||||
@@ -832,10 +840,22 @@ func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *uti
|
|||||||
}
|
}
|
||||||
pidx_ := indexAt(pidx, lenPattern, forward)
|
pidx_ := indexAt(pidx, lenPattern, forward)
|
||||||
pchar := pattern[pidx_]
|
pchar := pattern[pidx_]
|
||||||
if pchar == char {
|
ok := pchar == char
|
||||||
|
if ok {
|
||||||
if pidx_ == 0 {
|
if pidx_ == 0 {
|
||||||
bonus = bonusAt(text, index_)
|
bonus = bonusAt(text, index_)
|
||||||
}
|
}
|
||||||
|
if boundaryCheck {
|
||||||
|
ok = bonus >= bonusBoundary
|
||||||
|
if ok && pidx_ == 0 {
|
||||||
|
ok = index_ == 0 || charClassOf(text.Get(index_-1)) <= charDelimiter
|
||||||
|
}
|
||||||
|
if ok && pidx_ == len(pattern)-1 {
|
||||||
|
ok = index_ == lenRunes-1 || charClassOf(text.Get(index_+1)) <= charDelimiter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
pidx++
|
pidx++
|
||||||
if pidx == lenPattern {
|
if pidx == lenPattern {
|
||||||
if bonus > bestBonus {
|
if bonus > bestBonus {
|
||||||
@@ -861,7 +881,23 @@ func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *uti
|
|||||||
sidx = lenRunes - (bestPos + 1)
|
sidx = lenRunes - (bestPos + 1)
|
||||||
eidx = lenRunes - (bestPos - lenPattern + 1)
|
eidx = lenRunes - (bestPos - lenPattern + 1)
|
||||||
}
|
}
|
||||||
score, _ := calculateScore(caseSensitive, normalize, text, pattern, sidx, eidx, false)
|
var score int
|
||||||
|
if boundaryCheck {
|
||||||
|
// Underscore boundaries should be ranked lower than the other types of boundaries
|
||||||
|
score = int(bonus)
|
||||||
|
deduct := int(bonus-bonusBoundary) + 1
|
||||||
|
if sidx > 0 && text.Get(sidx-1) == '_' {
|
||||||
|
score -= deduct + 1
|
||||||
|
deduct = 1
|
||||||
|
}
|
||||||
|
if eidx < lenRunes && text.Get(eidx) == '_' {
|
||||||
|
score -= deduct
|
||||||
|
}
|
||||||
|
// Add base score so that this can compete with other match types e.g. 'foo' | bar
|
||||||
|
score += scoreMatch*lenPattern + int(bonusBoundaryWhite)*(lenPattern+1)
|
||||||
|
} else {
|
||||||
|
score, _ = calculateScore(caseSensitive, normalize, text, pattern, sidx, eidx, false)
|
||||||
|
}
|
||||||
return Result{sidx, eidx, score}, nil
|
return Result{sidx, eidx, score}, nil
|
||||||
}
|
}
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
|
38
src/ansi.go
38
src/ansi.go
@@ -1,6 +1,7 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
@@ -13,22 +14,28 @@ type ansiOffset struct {
|
|||||||
color ansiState
|
color ansiState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type url struct {
|
||||||
|
uri string
|
||||||
|
params string
|
||||||
|
}
|
||||||
|
|
||||||
type ansiState struct {
|
type ansiState struct {
|
||||||
fg tui.Color
|
fg tui.Color
|
||||||
bg tui.Color
|
bg tui.Color
|
||||||
attr tui.Attr
|
attr tui.Attr
|
||||||
lbg tui.Color
|
lbg tui.Color
|
||||||
|
url *url
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ansiState) colored() bool {
|
func (s *ansiState) colored() bool {
|
||||||
return s.fg != -1 || s.bg != -1 || s.attr > 0 || s.lbg >= 0
|
return s.fg != -1 || s.bg != -1 || s.attr > 0 || s.lbg >= 0 || s.url != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ansiState) equals(t *ansiState) bool {
|
func (s *ansiState) equals(t *ansiState) bool {
|
||||||
if t == nil {
|
if t == nil {
|
||||||
return !s.colored()
|
return !s.colored()
|
||||||
}
|
}
|
||||||
return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr && s.lbg == t.lbg
|
return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr && s.lbg == t.lbg && s.url == t.url
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ansiState) ToString() string {
|
func (s *ansiState) ToString() string {
|
||||||
@@ -60,7 +67,11 @@ func (s *ansiState) ToString() string {
|
|||||||
}
|
}
|
||||||
ret += toAnsiString(s.fg, 30) + toAnsiString(s.bg, 40)
|
ret += toAnsiString(s.fg, 30) + toAnsiString(s.bg, 40)
|
||||||
|
|
||||||
return "\x1b[" + strings.TrimSuffix(ret, ";") + "m"
|
ret = "\x1b[" + strings.TrimSuffix(ret, ";") + "m"
|
||||||
|
if s.url != nil {
|
||||||
|
ret = fmt.Sprintf("\x1b]8;%s;%s\x1b\\%s\x1b]8;;\x1b", s.url.params, s.url.uri, ret)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func toAnsiString(color tui.Color, offset int) string {
|
func toAnsiString(color tui.Color, offset int) string {
|
||||||
@@ -98,10 +109,19 @@ func matchOperatingSystemCommand(s string) int {
|
|||||||
if s[i] == '\x07' {
|
if s[i] == '\x07' {
|
||||||
return i + 1
|
return i + 1
|
||||||
}
|
}
|
||||||
|
// `\x1b]8;PARAMS;URI\x1b\\TITLE\x1b]8;;\x1b`
|
||||||
|
// ------
|
||||||
if s[i] == '\x1b' && i < len(s)-1 && s[i+1] == '\\' {
|
if s[i] == '\x1b' && i < len(s)-1 && s[i+1] == '\\' {
|
||||||
return i + 2
|
return i + 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `\x1b]8;PARAMS;URI\x1b\\TITLE\x1b]8;;\x1b`
|
||||||
|
// ------------
|
||||||
|
if i < len(s) && s[:i+1] == "\x1b]8;;\x1b" {
|
||||||
|
return i + 1
|
||||||
|
}
|
||||||
|
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,13 +348,21 @@ func parseAnsiCode(s string, delimiter byte) (int, byte, string) {
|
|||||||
func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
||||||
var state ansiState
|
var state ansiState
|
||||||
if prevState == nil {
|
if prevState == nil {
|
||||||
state = ansiState{-1, -1, 0, -1}
|
state = ansiState{-1, -1, 0, -1, nil}
|
||||||
} else {
|
} else {
|
||||||
state = ansiState{prevState.fg, prevState.bg, prevState.attr, prevState.lbg}
|
state = ansiState{prevState.fg, prevState.bg, prevState.attr, prevState.lbg, prevState.url}
|
||||||
}
|
}
|
||||||
if ansiCode[0] != '\x1b' || ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' {
|
if ansiCode[0] != '\x1b' || ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' {
|
||||||
if prevState != nil && strings.HasSuffix(ansiCode, "0K") {
|
if prevState != nil && strings.HasSuffix(ansiCode, "0K") {
|
||||||
state.lbg = prevState.bg
|
state.lbg = prevState.bg
|
||||||
|
} else if ansiCode == "\x1b]8;;\x1b\\" { // End of a hyperlink
|
||||||
|
state.url = nil
|
||||||
|
} else if strings.HasPrefix(ansiCode, "\x1b]8;") && strings.HasSuffix(ansiCode, "\x1b\\") {
|
||||||
|
if paramsEnd := strings.IndexRune(ansiCode[4:], ';'); paramsEnd >= 0 {
|
||||||
|
params := ansiCode[4 : 4+paramsEnd]
|
||||||
|
uri := ansiCode[5+paramsEnd : len(ansiCode)-2]
|
||||||
|
state.url = &url{uri: uri, params: params}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
@@ -22,6 +22,14 @@ func (cc *ChunkCache) Clear() {
|
|||||||
cc.mutex.Unlock()
|
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
|
||||||
func (cc *ChunkCache) Add(chunk *Chunk, key string, list []Result) {
|
func (cc *ChunkCache) Add(chunk *Chunk, key string, list []Result) {
|
||||||
if len(key) == 0 || !chunk.IsFull() || len(list) > queryCacheMax {
|
if len(key) == 0 || !chunk.IsFull() || len(list) > queryCacheMax {
|
||||||
|
@@ -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 {
|
||||||
@@ -92,7 +94,9 @@ func (cl *ChunkList) Snapshot(tail int) ([]*Chunk, int, bool) {
|
|||||||
|
|
||||||
// Copy the chunks to keep
|
// Copy the chunks to keep
|
||||||
ret := make([]*Chunk, numChunks)
|
ret := make([]*Chunk, numChunks)
|
||||||
copy(ret, cl.chunks[len(cl.chunks)-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-- {
|
for left, i := tail, len(ret)-1; i >= 0; i-- {
|
||||||
chunk := ret[i]
|
chunk := ret[i]
|
||||||
@@ -104,6 +108,7 @@ func (cl *ChunkList) Snapshot(tail int) ([]*Chunk, int, bool) {
|
|||||||
newChunk.items[i] = chunk.items[oldCount-left+i]
|
newChunk.items[i] = chunk.items[oldCount-left+i]
|
||||||
}
|
}
|
||||||
ret[i] = &newChunk
|
ret[i] = &newChunk
|
||||||
|
cl.cache.retire(chunk)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
left -= chunk.count
|
left -= chunk.count
|
||||||
|
@@ -11,7 +11,7 @@ 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
|
||||||
})
|
})
|
||||||
@@ -80,7 +80,7 @@ func TestChunkList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestChunkListTail(t *testing.T) {
|
func TestChunkListTail(t *testing.T) {
|
||||||
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
|
||||||
})
|
})
|
||||||
|
58
src/core.go
58
src/core.go
@@ -32,22 +32,20 @@ func (r *revision) bumpMinor() {
|
|||||||
r.minor++
|
r.minor++
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r revision) equals(other revision) bool {
|
|
||||||
return r.major == other.major && r.minor == other.minor
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r revision) compatible(other revision) bool {
|
func (r revision) compatible(other revision) bool {
|
||||||
return r.major == other.major
|
return r.major == other.major
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run starts fzf
|
// Run starts fzf
|
||||||
func Run(opts *Options) (int, error) {
|
func Run(opts *Options) (int, error) {
|
||||||
if opts.Tmux != nil && len(os.Getenv("TMUX")) > 0 && opts.Tmux.index >= opts.Height.index {
|
if opts.Filter == nil {
|
||||||
return runTmux(os.Args, opts)
|
if opts.Tmux != nil && len(os.Getenv("TMUX")) > 0 && opts.Tmux.index >= opts.Height.index {
|
||||||
}
|
return runTmux(os.Args, opts)
|
||||||
|
}
|
||||||
|
|
||||||
if needWinpty(opts) {
|
if needWinpty(opts) {
|
||||||
return runWinpty(os.Args, opts)
|
return runWinpty(os.Args, opts)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := postProcessOptions(opts); err != nil {
|
if err := postProcessOptions(opts); err != nil {
|
||||||
@@ -94,11 +92,12 @@ func Run(opts *Options) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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, byteString(data))
|
header = append(header, byteString(data))
|
||||||
eventBox.Set(EvtHeader, header)
|
eventBox.Set(EvtHeader, header)
|
||||||
@@ -110,7 +109,7 @@ func Run(opts *Options) (int, error) {
|
|||||||
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(byteString(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
|
||||||
@@ -147,6 +146,24 @@ func Run(opts *Options) (int, error) {
|
|||||||
// Process executor
|
// Process executor
|
||||||
executor := util.NewExecutor(opts.WithShell)
|
executor := util.NewExecutor(opts.WithShell)
|
||||||
|
|
||||||
|
// Terminal I/O
|
||||||
|
var terminal *Terminal
|
||||||
|
var err error
|
||||||
|
var initialEnv []string
|
||||||
|
initialReload := opts.extractReloadOnStart()
|
||||||
|
if opts.Filter == nil {
|
||||||
|
terminal, err = NewTerminal(opts, eventBox, executor)
|
||||||
|
if err != nil {
|
||||||
|
return ExitError, err
|
||||||
|
}
|
||||||
|
if len(initialReload) > 0 {
|
||||||
|
var temps []string
|
||||||
|
initialReload, temps = terminal.replacePlaceholderInInitialCommand(initialReload)
|
||||||
|
initialEnv = terminal.environ()
|
||||||
|
defer removeFiles(temps)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Reader
|
// Reader
|
||||||
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
|
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
|
||||||
var reader *Reader
|
var reader *Reader
|
||||||
@@ -154,7 +171,8 @@ func Run(opts *Options) (int, error) {
|
|||||||
reader = NewReader(func(data []byte) bool {
|
reader = NewReader(func(data []byte) bool {
|
||||||
return chunkList.Push(data)
|
return chunkList.Push(data)
|
||||||
}, eventBox, executor, opts.ReadZero, opts.Filter == nil)
|
}, eventBox, executor, opts.ReadZero, opts.Filter == nil)
|
||||||
go reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
|
|
||||||
|
go reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Matcher
|
// Matcher
|
||||||
@@ -170,7 +188,6 @@ func Run(opts *Options) (int, error) {
|
|||||||
forward = true
|
forward = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cache := NewChunkCache()
|
|
||||||
patternCache := make(map[string]*Pattern)
|
patternCache := make(map[string]*Pattern)
|
||||||
patternBuilder := func(runes []rune) *Pattern {
|
patternBuilder := func(runes []rune) *Pattern {
|
||||||
return BuildPattern(cache, patternCache,
|
return BuildPattern(cache, patternCache,
|
||||||
@@ -207,7 +224,7 @@ func Run(opts *Options) (int, error) {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}, eventBox, executor, opts.ReadZero, false)
|
}, eventBox, executor, opts.ReadZero, false)
|
||||||
reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
|
reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv)
|
||||||
} else {
|
} else {
|
||||||
eventBox.Unwatch(EvtReadNew)
|
eventBox.Unwatch(EvtReadNew)
|
||||||
eventBox.WaitFor(EvtReadFin)
|
eventBox.WaitFor(EvtReadFin)
|
||||||
@@ -238,18 +255,14 @@ func Run(opts *Options) (int, error) {
|
|||||||
go matcher.Loop()
|
go matcher.Loop()
|
||||||
defer matcher.Stop()
|
defer matcher.Stop()
|
||||||
|
|
||||||
// Terminal I/O
|
// Handling adaptive height
|
||||||
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 || opts.Sync
|
||||||
go terminal.Loop()
|
go terminal.Loop()
|
||||||
if !deferred && !heightUnknown {
|
if !deferred && !heightUnknown {
|
||||||
// Start right away
|
// Start right away
|
||||||
@@ -341,9 +354,6 @@ func Run(opts *Options) (int, error) {
|
|||||||
}
|
}
|
||||||
total = count
|
total = count
|
||||||
terminal.UpdateCount(total, !reading, value.(*string))
|
terminal.UpdateCount(total, !reading, value.(*string))
|
||||||
if opts.Sync {
|
|
||||||
terminal.UpdateList(PassMerger(&snapshot, opts.Tac, snapshotRevision), false)
|
|
||||||
}
|
|
||||||
if heightUnknown && !deferred {
|
if heightUnknown && !deferred {
|
||||||
determine(!reading)
|
determine(!reading)
|
||||||
}
|
}
|
||||||
@@ -431,7 +441,7 @@ func Run(opts *Options) (int, error) {
|
|||||||
determine(val.final)
|
determine(val.final)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
terminal.UpdateList(val, true)
|
terminal.UpdateList(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -87,7 +87,9 @@ func (m *Matcher) Loop() {
|
|||||||
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)
|
||||||
m.cache.Clear()
|
if !request.revision.compatible(m.revision) {
|
||||||
|
m.cache.Clear()
|
||||||
|
}
|
||||||
cacheCleared = true
|
cacheCleared = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +102,7 @@ func (m *Matcher) Loop() {
|
|||||||
if !cacheCleared {
|
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 && cached.final == request.final {
|
||||||
merger = cached
|
merger = cached
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -193,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 {
|
||||||
@@ -234,7 +236,7 @@ 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, minIndex), 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
|
||||||
@@ -247,7 +249,7 @@ 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() {
|
func (m *Matcher) Stop() {
|
||||||
|
152
src/options.go
152
src/options.go
@@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/algo"
|
"github.com/junegunn/fzf/src/algo"
|
||||||
"github.com/junegunn/fzf/src/tui"
|
"github.com/junegunn/fzf/src/tui"
|
||||||
|
|
||||||
"github.com/mattn/go-shellwords"
|
"github.com/junegunn/go-shellwords"
|
||||||
"github.com/rivo/uniseg"
|
"github.com/rivo/uniseg"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -53,7 +53,10 @@ Usage: fzf [options]
|
|||||||
--no-mouse Disable mouse
|
--no-mouse Disable mouse
|
||||||
--bind=KEYBINDS Custom key bindings. Refer to the man page.
|
--bind=KEYBINDS Custom key bindings. Refer to the man page.
|
||||||
--cycle Enable cyclic scroll
|
--cycle Enable cyclic scroll
|
||||||
|
--wrap Enable line wrap
|
||||||
|
--wrap-sign=STR Indicator for wrapped lines
|
||||||
--no-multi-line Disable multi-line display of items when using --read0
|
--no-multi-line Disable multi-line display of items when using --read0
|
||||||
|
--gap[=N] Render empty lines between each item
|
||||||
--keep-right Keep the right end of the line visible on overflow
|
--keep-right Keep the right end of the line visible on overflow
|
||||||
--scroll-off=LINES Number of screen lines to keep above or below when
|
--scroll-off=LINES Number of screen lines to keep above or below when
|
||||||
scrolling to the top or to the bottom (default: 0)
|
scrolling to the top or to the bottom (default: 0)
|
||||||
@@ -88,6 +91,7 @@ Usage: fzf [options]
|
|||||||
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
||||||
--info=STYLE Finder info style
|
--info=STYLE Finder info style
|
||||||
[default|right|hidden|inline[-right][:PREFIX]]
|
[default|right|hidden|inline[-right][:PREFIX]]
|
||||||
|
--info-command=COMMAND Command to generate info line
|
||||||
--separator=STR String to form horizontal separator on info line
|
--separator=STR String to form horizontal separator on info line
|
||||||
--no-separator Hide info line separator
|
--no-separator Hide info line separator
|
||||||
--scrollbar[=C1[C2]] Scrollbar character(s) (each for main and preview window)
|
--scrollbar[=C1[C2]] Scrollbar character(s) (each for main and preview window)
|
||||||
@@ -100,7 +104,7 @@ Usage: fzf [options]
|
|||||||
--header=STR String to print as header
|
--header=STR String to print as header
|
||||||
--header-lines=N The first N lines of the input are treated as header
|
--header-lines=N The first N lines of the input are treated as header
|
||||||
--header-first Print header before the prompt line
|
--header-first Print header before the prompt line
|
||||||
--ellipsis=STR Ellipsis to show when line is truncated (default: '..')
|
--ellipsis=STR Ellipsis to show when line is truncated (default: '··')
|
||||||
|
|
||||||
Display
|
Display
|
||||||
--ansi Enable processing of ANSI color codes
|
--ansi Enable processing of ANSI color codes
|
||||||
@@ -117,8 +121,8 @@ Usage: fzf [options]
|
|||||||
--preview=COMMAND Command to preview highlighted line ({})
|
--preview=COMMAND Command to preview highlighted line ({})
|
||||||
--preview-window=OPT Preview window layout (default: right:50%)
|
--preview-window=OPT Preview window layout (default: right:50%)
|
||||||
[up|down|left|right][,SIZE[%]]
|
[up|down|left|right][,SIZE[%]]
|
||||||
[,[no]wrap][,[no]cycle][,[no]follow][,[no]hidden]
|
[,[no]wrap][,[no]cycle][,[no]follow][,[no]info]
|
||||||
[,border-BORDER_OPT]
|
[,[no]hidden][,border-BORDER_OPT]
|
||||||
[,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
|
[,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
|
||||||
[,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]
|
[,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]
|
||||||
--preview-label=LABEL
|
--preview-label=LABEL
|
||||||
@@ -268,6 +272,7 @@ type previewOpts struct {
|
|||||||
wrap bool
|
wrap bool
|
||||||
cycle bool
|
cycle bool
|
||||||
follow bool
|
follow bool
|
||||||
|
info bool
|
||||||
border tui.BorderShape
|
border tui.BorderShape
|
||||||
headerLines int
|
headerLines int
|
||||||
threshold int
|
threshold int
|
||||||
@@ -383,7 +388,7 @@ func (a previewOpts) sameLayout(b previewOpts) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a previewOpts) sameContentLayout(b previewOpts) bool {
|
func (a previewOpts) sameContentLayout(b previewOpts) bool {
|
||||||
return a.wrap == b.wrap && a.headerLines == b.headerLines
|
return a.wrap == b.wrap && a.headerLines == b.headerLines && a.info == b.info
|
||||||
}
|
}
|
||||||
|
|
||||||
func firstLine(s string) string {
|
func firstLine(s string) string {
|
||||||
@@ -434,6 +439,8 @@ type Options struct {
|
|||||||
MinHeight int
|
MinHeight int
|
||||||
Layout layoutType
|
Layout layoutType
|
||||||
Cycle bool
|
Cycle bool
|
||||||
|
Wrap bool
|
||||||
|
WrapSign *string
|
||||||
MultiLine bool
|
MultiLine bool
|
||||||
CursorLine bool
|
CursorLine bool
|
||||||
KeepRight bool
|
KeepRight bool
|
||||||
@@ -443,12 +450,13 @@ type Options struct {
|
|||||||
FileWord bool
|
FileWord bool
|
||||||
InfoStyle infoStyle
|
InfoStyle infoStyle
|
||||||
InfoPrefix string
|
InfoPrefix string
|
||||||
|
InfoCommand string
|
||||||
Separator *string
|
Separator *string
|
||||||
JumpLabels string
|
JumpLabels string
|
||||||
Prompt string
|
Prompt string
|
||||||
Pointer *string
|
Pointer *string
|
||||||
Marker *string
|
Marker *string
|
||||||
MarkerMulti [3]string
|
MarkerMulti *[3]string
|
||||||
Query string
|
Query string
|
||||||
Select1 bool
|
Select1 bool
|
||||||
Exit0 bool
|
Exit0 bool
|
||||||
@@ -466,7 +474,8 @@ type Options struct {
|
|||||||
Header []string
|
Header []string
|
||||||
HeaderLines int
|
HeaderLines int
|
||||||
HeaderFirst bool
|
HeaderFirst bool
|
||||||
Ellipsis string
|
Gap int
|
||||||
|
Ellipsis *string
|
||||||
Scrollbar *string
|
Scrollbar *string
|
||||||
Margin [4]sizeSpec
|
Margin [4]sizeSpec
|
||||||
Padding [4]sizeSpec
|
Padding [4]sizeSpec
|
||||||
@@ -502,7 +511,7 @@ func filterNonEmpty(input []string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func defaultPreviewOpts(command string) previewOpts {
|
func defaultPreviewOpts(command string) previewOpts {
|
||||||
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, tui.DefaultBorderShape, 0, 0, nil}
|
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, true, tui.DefaultBorderShape, 0, 0, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultOptions() *Options {
|
func defaultOptions() *Options {
|
||||||
@@ -541,6 +550,7 @@ func defaultOptions() *Options {
|
|||||||
MinHeight: 10,
|
MinHeight: 10,
|
||||||
Layout: layoutDefault,
|
Layout: layoutDefault,
|
||||||
Cycle: false,
|
Cycle: false,
|
||||||
|
Wrap: false,
|
||||||
MultiLine: true,
|
MultiLine: true,
|
||||||
KeepRight: false,
|
KeepRight: false,
|
||||||
Hscroll: true,
|
Hscroll: true,
|
||||||
@@ -553,6 +563,7 @@ func defaultOptions() *Options {
|
|||||||
Prompt: "> ",
|
Prompt: "> ",
|
||||||
Pointer: nil,
|
Pointer: nil,
|
||||||
Marker: nil,
|
Marker: nil,
|
||||||
|
MarkerMulti: nil,
|
||||||
Query: "",
|
Query: "",
|
||||||
Select1: false,
|
Select1: false,
|
||||||
Exit0: false,
|
Exit0: false,
|
||||||
@@ -570,7 +581,8 @@ func defaultOptions() *Options {
|
|||||||
Header: make([]string, 0),
|
Header: make([]string, 0),
|
||||||
HeaderLines: 0,
|
HeaderLines: 0,
|
||||||
HeaderFirst: false,
|
HeaderFirst: false,
|
||||||
Ellipsis: "..",
|
Gap: 0,
|
||||||
|
Ellipsis: nil,
|
||||||
Scrollbar: nil,
|
Scrollbar: nil,
|
||||||
Margin: defaultMargin(),
|
Margin: defaultMargin(),
|
||||||
Padding: defaultMargin(),
|
Padding: defaultMargin(),
|
||||||
@@ -1363,6 +1375,8 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
|||||||
appendAction(actToggleTrackCurrent)
|
appendAction(actToggleTrackCurrent)
|
||||||
case "toggle-header":
|
case "toggle-header":
|
||||||
appendAction(actToggleHeader)
|
appendAction(actToggleHeader)
|
||||||
|
case "toggle-wrap":
|
||||||
|
appendAction(actToggleWrap)
|
||||||
case "show-header":
|
case "show-header":
|
||||||
appendAction(actShowHeader)
|
appendAction(actShowHeader)
|
||||||
case "hide-header":
|
case "hide-header":
|
||||||
@@ -1419,6 +1433,8 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
|||||||
appendAction(actOffsetUp)
|
appendAction(actOffsetUp)
|
||||||
case "offset-down":
|
case "offset-down":
|
||||||
appendAction(actOffsetDown)
|
appendAction(actOffsetDown)
|
||||||
|
case "offset-middle":
|
||||||
|
appendAction(actOffsetMiddle)
|
||||||
case "preview-top":
|
case "preview-top":
|
||||||
appendAction(actPreviewTop)
|
appendAction(actPreviewTop)
|
||||||
case "preview-bottom":
|
case "preview-bottom":
|
||||||
@@ -1777,6 +1793,10 @@ func parsePreviewWindowImpl(opts *previewOpts, input string) error {
|
|||||||
opts.follow = true
|
opts.follow = true
|
||||||
case "nofollow":
|
case "nofollow":
|
||||||
opts.follow = false
|
opts.follow = false
|
||||||
|
case "info":
|
||||||
|
opts.info = true
|
||||||
|
case "noinfo":
|
||||||
|
opts.info = false
|
||||||
default:
|
default:
|
||||||
if headerRegex.MatchString(token) {
|
if headerRegex.MatchString(token) {
|
||||||
if opts.headerLines, err = atoi(token[1:]); err != nil {
|
if opts.headerLines, err = atoi(token[1:]); err != nil {
|
||||||
@@ -1858,7 +1878,10 @@ func parseMargin(opt string, margin string) ([4]sizeSpec, error) {
|
|||||||
return [4]sizeSpec{}, errors.New("invalid " + opt + ": " + margin)
|
return [4]sizeSpec{}, errors.New("invalid " + opt + ": " + margin)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseMarkerMultiLine(str string) ([3]string, error) {
|
func parseMarkerMultiLine(str string) (*[3]string, error) {
|
||||||
|
if str == "" {
|
||||||
|
return &[3]string{}, nil
|
||||||
|
}
|
||||||
gr := uniseg.NewGraphemes(str)
|
gr := uniseg.NewGraphemes(str)
|
||||||
parts := []string{}
|
parts := []string{}
|
||||||
totalWidth := 0
|
totalWidth := 0
|
||||||
@@ -1870,7 +1893,7 @@ func parseMarkerMultiLine(str string) ([3]string, error) {
|
|||||||
|
|
||||||
result := [3]string{}
|
result := [3]string{}
|
||||||
if totalWidth != 3 && totalWidth != 6 {
|
if totalWidth != 3 && totalWidth != 6 {
|
||||||
return result, fmt.Errorf("invalid total marker width: %d (expected: 3 or 6)", totalWidth)
|
return &result, fmt.Errorf("invalid total marker width: %d (expected: 0, 3 or 6)", totalWidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := totalWidth / 3
|
expected := totalWidth / 3
|
||||||
@@ -1886,7 +1909,7 @@ func parseMarkerMultiLine(str string) ([3]string, error) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result, nil
|
return &result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOptions(index *int, opts *Options, allArgs []string) error {
|
func parseOptions(index *int, opts *Options, allArgs []string) error {
|
||||||
@@ -2155,6 +2178,16 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
opts.CursorLine = false
|
opts.CursorLine = false
|
||||||
case "--no-cycle":
|
case "--no-cycle":
|
||||||
opts.Cycle = false
|
opts.Cycle = false
|
||||||
|
case "--wrap":
|
||||||
|
opts.Wrap = true
|
||||||
|
case "--no-wrap":
|
||||||
|
opts.Wrap = false
|
||||||
|
case "--wrap-sign":
|
||||||
|
str, err := nextString(allArgs, &i, "wrap sign required")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
opts.WrapSign = &str
|
||||||
case "--multi-line":
|
case "--multi-line":
|
||||||
opts.MultiLine = true
|
opts.MultiLine = true
|
||||||
case "--no-multi-line":
|
case "--no-multi-line":
|
||||||
@@ -2187,6 +2220,12 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
if opts.InfoStyle, opts.InfoPrefix, err = parseInfoStyle(str); err != nil {
|
if opts.InfoStyle, opts.InfoPrefix, err = parseInfoStyle(str); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
case "--info-command":
|
||||||
|
if opts.InfoCommand, err = nextString(allArgs, &i, "info command required"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "--no-info-command":
|
||||||
|
opts.InfoCommand = ""
|
||||||
case "--no-info":
|
case "--no-info":
|
||||||
opts.InfoStyle = infoHidden
|
opts.InfoStyle = infoHidden
|
||||||
case "--inline-info":
|
case "--inline-info":
|
||||||
@@ -2307,10 +2346,19 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
opts.HeaderFirst = true
|
opts.HeaderFirst = true
|
||||||
case "--no-header-first":
|
case "--no-header-first":
|
||||||
opts.HeaderFirst = false
|
opts.HeaderFirst = false
|
||||||
case "--ellipsis":
|
case "--gap":
|
||||||
if opts.Ellipsis, err = nextString(allArgs, &i, "ellipsis string required"); err != nil {
|
if opts.Gap, err = optionalNumeric(allArgs, &i, 1); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
case "--no-gap":
|
||||||
|
opts.Gap = 0
|
||||||
|
case "--ellipsis":
|
||||||
|
str, err := nextString(allArgs, &i, "ellipsis string required")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
str = firstLine(str)
|
||||||
|
opts.Ellipsis = &str
|
||||||
case "--preview":
|
case "--preview":
|
||||||
if opts.Preview.command, err = nextString(allArgs, &i, "preview command required"); err != nil {
|
if opts.Preview.command, err = nextString(allArgs, &i, "preview command required"); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -2499,6 +2547,8 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
if err := parseLabelPosition(&opts.PreviewLabel, value); err != nil {
|
if err := parseLabelPosition(&opts.PreviewLabel, value); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
} else if match, value := optString(arg, "--wrap-sign="); match {
|
||||||
|
opts.WrapSign = &value
|
||||||
} else if match, value := optString(arg, "--prompt="); match {
|
} else if match, value := optString(arg, "--prompt="); match {
|
||||||
opts.Prompt = value
|
opts.Prompt = value
|
||||||
} else if match, value := optString(arg, "--pointer="); match {
|
} else if match, value := optString(arg, "--pointer="); match {
|
||||||
@@ -2541,6 +2591,8 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
if opts.InfoStyle, opts.InfoPrefix, err = parseInfoStyle(value); err != nil {
|
if opts.InfoStyle, opts.InfoPrefix, err = parseInfoStyle(value); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
} else if match, value := optString(arg, "--info-command="); match {
|
||||||
|
opts.InfoCommand = value
|
||||||
} else if match, value := optString(arg, "--separator="); match {
|
} else if match, value := optString(arg, "--separator="); match {
|
||||||
opts.Separator = &value
|
opts.Separator = &value
|
||||||
} else if match, value := optString(arg, "--scrollbar="); match {
|
} else if match, value := optString(arg, "--scrollbar="); match {
|
||||||
@@ -2587,8 +2639,13 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
if opts.HeaderLines, err = atoi(value); err != nil {
|
if opts.HeaderLines, err = atoi(value); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
} else if match, value := optString(arg, "--gap="); match {
|
||||||
|
if opts.Gap, err = atoi(value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
} else if match, value := optString(arg, "--ellipsis="); match {
|
} else if match, value := optString(arg, "--ellipsis="); match {
|
||||||
opts.Ellipsis = value
|
str := firstLine(value)
|
||||||
|
opts.Ellipsis = &str
|
||||||
} else if match, value := optString(arg, "--preview="); match {
|
} else if match, value := optString(arg, "--preview="); match {
|
||||||
opts.Preview.command = value
|
opts.Preview.command = value
|
||||||
} else if match, value := optString(arg, "--preview-window="); match {
|
} else if match, value := optString(arg, "--preview-window="); match {
|
||||||
@@ -2687,9 +2744,6 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func validateSign(sign string, signOptName string) error {
|
func validateSign(sign string, signOptName string) error {
|
||||||
if sign == "" {
|
|
||||||
return fmt.Errorf("%v cannot be empty", signOptName)
|
|
||||||
}
|
|
||||||
if uniseg.StringWidth(sign) > 2 {
|
if uniseg.StringWidth(sign) > 2 {
|
||||||
return fmt.Errorf("%v display width should be up to 2", signOptName)
|
return fmt.Errorf("%v display width should be up to 2", signOptName)
|
||||||
}
|
}
|
||||||
@@ -2758,32 +2812,42 @@ func postProcessOptions(opts *Options) error {
|
|||||||
|
|
||||||
markerLen := 1
|
markerLen := 1
|
||||||
if opts.Marker == nil {
|
if opts.Marker == nil {
|
||||||
// "▎" looks better, but not all terminals render it correctly
|
if opts.MarkerMulti != nil && opts.MarkerMulti[0] == "" {
|
||||||
defaultMarker := "┃"
|
empty := ""
|
||||||
if !opts.Unicode {
|
opts.Marker = &empty
|
||||||
defaultMarker = ">"
|
markerLen = 0
|
||||||
|
} else {
|
||||||
|
// "▎" looks better, but not all terminals render it correctly
|
||||||
|
defaultMarker := "┃"
|
||||||
|
if !opts.Unicode {
|
||||||
|
defaultMarker = ">"
|
||||||
|
}
|
||||||
|
opts.Marker = &defaultMarker
|
||||||
}
|
}
|
||||||
opts.Marker = &defaultMarker
|
|
||||||
} else {
|
} else {
|
||||||
markerLen = uniseg.StringWidth(*opts.Marker)
|
markerLen = uniseg.StringWidth(*opts.Marker)
|
||||||
}
|
}
|
||||||
|
|
||||||
markerMultiLen := 1
|
markerMultiLen := 1
|
||||||
if len(opts.MarkerMulti[0]) == 0 {
|
if opts.MarkerMulti == nil {
|
||||||
if opts.Unicode {
|
if *opts.Marker == "" {
|
||||||
opts.MarkerMulti = [3]string{"╻", "┃", "╹"}
|
opts.MarkerMulti = &[3]string{}
|
||||||
|
markerMultiLen = 0
|
||||||
|
} else if opts.Unicode {
|
||||||
|
opts.MarkerMulti = &[3]string{"╻", "┃", "╹"}
|
||||||
} else {
|
} else {
|
||||||
opts.MarkerMulti = [3]string{".", "|", "'"}
|
opts.MarkerMulti = &[3]string{".", "|", "'"}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
markerMultiLen = uniseg.StringWidth(opts.MarkerMulti[0])
|
markerMultiLen = uniseg.StringWidth(opts.MarkerMulti[0])
|
||||||
}
|
}
|
||||||
if markerMultiLen > markerLen {
|
diff := markerMultiLen - markerLen
|
||||||
padded := *opts.Marker + " "
|
if diff > 0 {
|
||||||
|
padded := *opts.Marker + strings.Repeat(" ", diff)
|
||||||
opts.Marker = &padded
|
opts.Marker = &padded
|
||||||
} else if markerMultiLen < markerLen {
|
} else if diff < 0 {
|
||||||
for idx := range opts.MarkerMulti {
|
for idx := range opts.MarkerMulti {
|
||||||
opts.MarkerMulti[idx] += " "
|
opts.MarkerMulti[idx] += strings.Repeat(" ", -diff)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2873,6 +2937,12 @@ func postProcessOptions(opts *Options) error {
|
|||||||
return processScheme(opts)
|
return processScheme(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseShellWords(str string) ([]string, error) {
|
||||||
|
parser := shellwords.NewParser()
|
||||||
|
parser.ParseComment = true
|
||||||
|
return parser.Parse(str)
|
||||||
|
}
|
||||||
|
|
||||||
// ParseOptions parses command-line options
|
// ParseOptions parses command-line options
|
||||||
func ParseOptions(useDefaults bool, args []string) (*Options, error) {
|
func ParseOptions(useDefaults bool, args []string) (*Options, error) {
|
||||||
opts := defaultOptions()
|
opts := defaultOptions()
|
||||||
@@ -2886,7 +2956,7 @@ func ParseOptions(useDefaults bool, args []string) (*Options, error) {
|
|||||||
return nil, errors.New("$FZF_DEFAULT_OPTS_FILE: " + err.Error())
|
return nil, errors.New("$FZF_DEFAULT_OPTS_FILE: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
words, parseErr := shellwords.Parse(string(bytes))
|
words, parseErr := parseShellWords(string(bytes))
|
||||||
if parseErr != nil {
|
if parseErr != nil {
|
||||||
return nil, errors.New(path + ": " + parseErr.Error())
|
return nil, errors.New(path + ": " + parseErr.Error())
|
||||||
}
|
}
|
||||||
@@ -2898,7 +2968,7 @@ func ParseOptions(useDefaults bool, args []string) (*Options, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. Options from $FZF_DEFAULT_OPTS string
|
// 2. Options from $FZF_DEFAULT_OPTS string
|
||||||
words, parseErr := shellwords.Parse(os.Getenv("FZF_DEFAULT_OPTS"))
|
words, parseErr := parseShellWords(os.Getenv("FZF_DEFAULT_OPTS"))
|
||||||
if parseErr != nil {
|
if parseErr != nil {
|
||||||
return nil, errors.New("$FZF_DEFAULT_OPTS: " + parseErr.Error())
|
return nil, errors.New("$FZF_DEFAULT_OPTS: " + parseErr.Error())
|
||||||
}
|
}
|
||||||
@@ -2921,3 +2991,19 @@ func ParseOptions(useDefaults bool, args []string) (*Options, error) {
|
|||||||
|
|
||||||
return opts, nil
|
return opts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (opts *Options) extractReloadOnStart() string {
|
||||||
|
cmd := ""
|
||||||
|
if actions, prs := opts.Keymap[tui.Start.AsEvent()]; prs {
|
||||||
|
filtered := []*action{}
|
||||||
|
for _, action := range actions {
|
||||||
|
if action.t == actReload || action.t == actReloadSync {
|
||||||
|
cmd = action.a
|
||||||
|
} else {
|
||||||
|
filtered = append(filtered, action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opts.Keymap[tui.Start.AsEvent()] = filtered
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
@@ -454,7 +454,6 @@ func TestValidateSign(t *testing.T) {
|
|||||||
{"> ", true},
|
{"> ", true},
|
||||||
{"아", true},
|
{"아", true},
|
||||||
{"😀", true},
|
{"😀", true},
|
||||||
{"", false},
|
|
||||||
{">>>", false},
|
{">>>", false},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -23,6 +23,7 @@ type termType int
|
|||||||
const (
|
const (
|
||||||
termFuzzy termType = iota
|
termFuzzy termType = iota
|
||||||
termExact
|
termExact
|
||||||
|
termExactBoundary
|
||||||
termPrefix
|
termPrefix
|
||||||
termSuffix
|
termSuffix
|
||||||
termEqual
|
termEqual
|
||||||
@@ -147,6 +148,7 @@ func BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy boo
|
|||||||
ptr.procFun[termFuzzy] = fuzzyAlgo
|
ptr.procFun[termFuzzy] = fuzzyAlgo
|
||||||
ptr.procFun[termEqual] = algo.EqualMatch
|
ptr.procFun[termEqual] = algo.EqualMatch
|
||||||
ptr.procFun[termExact] = algo.ExactMatchNaive
|
ptr.procFun[termExact] = algo.ExactMatchNaive
|
||||||
|
ptr.procFun[termExactBoundary] = algo.ExactMatchBoundary
|
||||||
ptr.procFun[termPrefix] = algo.PrefixMatch
|
ptr.procFun[termPrefix] = algo.PrefixMatch
|
||||||
ptr.procFun[termSuffix] = algo.SuffixMatch
|
ptr.procFun[termSuffix] = algo.SuffixMatch
|
||||||
|
|
||||||
@@ -193,7 +195,10 @@ func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet
|
|||||||
text = text[:len(text)-1]
|
text = text[:len(text)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(text, "'") {
|
if len(text) > 2 && strings.HasPrefix(text, "'") && strings.HasSuffix(text, "'") {
|
||||||
|
typ = termExactBoundary
|
||||||
|
text = text[1 : len(text)-1]
|
||||||
|
} else if strings.HasPrefix(text, "'") {
|
||||||
// Flip exactness
|
// Flip exactness
|
||||||
if fuzzy && !inv {
|
if fuzzy && !inv {
|
||||||
typ = termExact
|
typ = termExact
|
||||||
|
@@ -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")
|
||||||
}
|
}
|
||||||
|
22
src/proxy.go
22
src/proxy.go
@@ -9,6 +9,7 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@ func fifo(name string) (string, error) {
|
|||||||
return output, nil
|
return output, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runProxy(commandPrefix string, cmdBuilder func(temp string) *exec.Cmd, opts *Options, withExports bool) (int, error) {
|
func runProxy(commandPrefix string, cmdBuilder func(temp string, needBash bool) (*exec.Cmd, error), opts *Options, withExports bool) (int, error) {
|
||||||
output, err := fifo("proxy-output")
|
output, err := fifo("proxy-output")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ExitError, err
|
return ExitError, err
|
||||||
@@ -92,17 +93,28 @@ func runProxy(commandPrefix string, cmdBuilder func(temp string) *exec.Cmd, opts
|
|||||||
// To ensure that the options are processed by a POSIX-compliant shell,
|
// 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.
|
// we need to write the command to a temporary file and execute it with sh.
|
||||||
var exports []string
|
var exports []string
|
||||||
|
needBash := false
|
||||||
if withExports {
|
if withExports {
|
||||||
exports = os.Environ()
|
validIdentifier := regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
|
||||||
for idx, pairStr := range exports {
|
for _, pairStr := range os.Environ() {
|
||||||
pair := strings.SplitN(pairStr, "=", 2)
|
pair := strings.SplitN(pairStr, "=", 2)
|
||||||
exports[idx] = fmt.Sprintf("export %s=%s", pair[0], escapeSingleQuote(pair[1]))
|
if validIdentifier.MatchString(pair[0]) {
|
||||||
|
exports = append(exports, fmt.Sprintf("export %s=%s", pair[0], escapeSingleQuote(pair[1])))
|
||||||
|
} else if strings.HasPrefix(pair[0], "BASH_FUNC_") && strings.HasSuffix(pair[0], "%%") {
|
||||||
|
name := pair[0][10 : len(pair[0])-2]
|
||||||
|
exports = append(exports, name+pair[1])
|
||||||
|
exports = append(exports, "export -f "+name)
|
||||||
|
needBash = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
temp := WriteTemporaryFile(append(exports, command), "\n")
|
temp := WriteTemporaryFile(append(exports, command), "\n")
|
||||||
defer os.Remove(temp)
|
defer os.Remove(temp)
|
||||||
|
|
||||||
cmd := cmdBuilder(temp)
|
cmd, err := cmdBuilder(temp, needBash)
|
||||||
|
if err != nil {
|
||||||
|
return ExitError, err
|
||||||
|
}
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
intChan := make(chan os.Signal, 1)
|
intChan := make(chan os.Signal, 1)
|
||||||
defer close(intChan)
|
defer close(intChan)
|
||||||
|
@@ -9,7 +9,10 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
func sh() (string, error) {
|
func sh(bash bool) (string, error) {
|
||||||
|
if bash {
|
||||||
|
return "bash", nil
|
||||||
|
}
|
||||||
return "sh", nil
|
return "sh", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -13,12 +13,16 @@ import (
|
|||||||
|
|
||||||
var shPath atomic.Value
|
var shPath atomic.Value
|
||||||
|
|
||||||
func sh() (string, error) {
|
func sh(bash bool) (string, error) {
|
||||||
if cached := shPath.Load(); cached != nil {
|
if cached := shPath.Load(); cached != nil {
|
||||||
return cached.(string), nil
|
return cached.(string), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command("cygpath", "-w", "/usr/bin/sh")
|
name := "sh"
|
||||||
|
if bash {
|
||||||
|
name = "bash"
|
||||||
|
}
|
||||||
|
cmd := exec.Command("cygpath", "-w", "/usr/bin/"+name)
|
||||||
bytes, err := cmd.Output()
|
bytes, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -31,7 +35,7 @@ func sh() (string, error) {
|
|||||||
|
|
||||||
func mkfifo(path string, mode uint32) (string, error) {
|
func mkfifo(path string, mode uint32) (string, error) {
|
||||||
m := strconv.FormatUint(uint64(mode), 8)
|
m := strconv.FormatUint(uint64(mode), 8)
|
||||||
sh, err := sh()
|
sh, err := sh(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return path, err
|
return path, err
|
||||||
}
|
}
|
||||||
@@ -43,7 +47,7 @@ func mkfifo(path string, mode uint32) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func withOutputPipe(output string, task func(io.ReadCloser)) error {
|
func withOutputPipe(output string, task func(io.ReadCloser)) error {
|
||||||
sh, err := sh()
|
sh, err := sh(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -62,7 +66,7 @@ func withOutputPipe(output string, task func(io.ReadCloser)) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func withInputPipe(input string, task func(io.WriteCloser)) error {
|
func withInputPipe(input string, task func(io.WriteCloser)) error {
|
||||||
sh, err := sh()
|
sh, err := sh(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -110,18 +111,19 @@ func (r *Reader) readChannel(inputChan chan string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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(inputChan chan string, root string, opts walkerOpts, ignores []string) {
|
func (r *Reader) ReadSource(inputChan chan string, root string, opts walkerOpts, ignores []string, initCmd string, initEnv []string) {
|
||||||
r.startEventPoller()
|
r.startEventPoller()
|
||||||
var success bool
|
var success bool
|
||||||
if inputChan != nil {
|
if inputChan != nil {
|
||||||
success = r.readChannel(inputChan)
|
success = r.readChannel(inputChan)
|
||||||
|
} else if len(initCmd) > 0 {
|
||||||
|
success = r.readFromCommand(initCmd, initEnv)
|
||||||
} else if util.IsTty(os.Stdin) {
|
} else if util.IsTty(os.Stdin) {
|
||||||
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
||||||
if len(cmd) == 0 {
|
if len(cmd) == 0 {
|
||||||
success = r.readFiles(root, opts, ignores)
|
success = r.readFiles(root, opts, ignores)
|
||||||
} else {
|
} else {
|
||||||
// We can't export FZF_* environment variables to the default command
|
success = r.readFromCommand(cmd, initEnv)
|
||||||
success = r.readFromCommand(cmd, nil)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
success = r.readFromStdin()
|
success = r.readFromStdin()
|
||||||
@@ -222,19 +224,48 @@ func (r *Reader) readFromStdin() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool {
|
||||||
r.killed = false
|
r.killed = false
|
||||||
conf := fastwalk.Config{Follow: opts.follow}
|
conf := fastwalk.Config{
|
||||||
|
Follow: opts.follow,
|
||||||
|
// Use forward slashes when running a Windows binary under WSL or MSYS
|
||||||
|
ToSlash: fastwalk.DefaultToSlash(),
|
||||||
|
Sort: fastwalk.SortFilesFirst,
|
||||||
|
}
|
||||||
fn := func(path string, de os.DirEntry, err error) error {
|
fn := func(path string, de os.DirEntry, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
path = filepath.Clean(path)
|
path = trimPath(path)
|
||||||
if path != "." {
|
if path != "." {
|
||||||
isDir := de.IsDir()
|
isDir := de.IsDir()
|
||||||
if isDir {
|
if isDir || opts.follow && isSymlinkToDir(path, de) {
|
||||||
base := filepath.Base(path)
|
base := filepath.Base(path)
|
||||||
if !opts.hidden && base[0] == '.' {
|
if !opts.hidden && base[0] == '.' && base != ".." {
|
||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
}
|
}
|
||||||
for _, ignore := range ignores {
|
for _, ignore := range ignores {
|
||||||
@@ -243,7 +274,7 @@ func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((opts.file && !isDir) || (opts.dir && 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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,6 +16,7 @@ type colorOffset struct {
|
|||||||
offset [2]int32
|
offset [2]int32
|
||||||
color tui.ColorPair
|
color tui.ColorPair
|
||||||
match bool
|
match bool
|
||||||
|
url *url
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result struct {
|
type Result struct {
|
||||||
@@ -81,7 +82,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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,8 +178,11 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
|
|||||||
if curr != 0 && idx > start {
|
if curr != 0 && idx > start {
|
||||||
if curr < 0 {
|
if curr < 0 {
|
||||||
color := colMatch
|
color := colMatch
|
||||||
|
var url *url
|
||||||
if curr < -1 && theme.Colored {
|
if curr < -1 && theme.Colored {
|
||||||
origColor := ansiToColorPair(itemColors[-curr-2], colMatch)
|
ansi := itemColors[-curr-2]
|
||||||
|
url = ansi.color.url
|
||||||
|
origColor := ansiToColorPair(ansi, colMatch)
|
||||||
// hl or hl+ only sets the foreground color, so colMatch is the
|
// hl or hl+ only sets the foreground color, so colMatch is the
|
||||||
// combination of either [hl and bg] or [hl+ and bg+].
|
// combination of either [hl and bg] or [hl+ and bg+].
|
||||||
//
|
//
|
||||||
@@ -194,13 +198,14 @@ 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, match: true})
|
offset: [2]int32{int32(start), int32(idx)}, color: color, match: true, url: url})
|
||||||
} 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})
|
match: false,
|
||||||
|
url: ansi.color.url})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -124,10 +124,10 @@ func TestColorOffset(t *testing.T) {
|
|||||||
item := Result{
|
item := Result{
|
||||||
item: &Item{
|
item: &Item{
|
||||||
colors: &[]ansiOffset{
|
colors: &[]ansiOffset{
|
||||||
{[2]int32{0, 20}, ansiState{1, 5, 0, -1}},
|
{[2]int32{0, 20}, ansiState{1, 5, 0, -1, nil}},
|
||||||
{[2]int32{22, 27}, ansiState{2, 6, tui.Bold, -1}},
|
{[2]int32{22, 27}, ansiState{2, 6, tui.Bold, -1, nil}},
|
||||||
{[2]int32{30, 32}, ansiState{3, 7, 0, -1}},
|
{[2]int32{30, 32}, ansiState{3, 7, 0, -1, nil}},
|
||||||
{[2]int32{33, 40}, ansiState{4, 8, tui.Bold, -1}}}}}
|
{[2]int32{33, 40}, ansiState{4, 8, tui.Bold, -1, nil}}}}}
|
||||||
|
|
||||||
colBase := tui.NewColorPair(89, 189, tui.AttrUndefined)
|
colBase := tui.NewColorPair(89, 189, tui.AttrUndefined)
|
||||||
colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined)
|
colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined)
|
||||||
|
@@ -38,9 +38,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
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 {
|
||||||
@@ -73,7 +73,7 @@ func parseListenAddress(address string) (listenAddress, error) {
|
|||||||
return listenAddress{parts[0], port}, nil
|
return listenAddress{parts[0], port}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func startHttpServer(address listenAddress, actionChannel chan []*action, responseChannel chan string) (net.Listener, int, error) {
|
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")
|
||||||
@@ -99,9 +99,9 @@ func startHttpServer(address listenAddress, actionChannel chan []*action, respon
|
|||||||
}
|
}
|
||||||
|
|
||||||
server := httpServer{
|
server := httpServer{
|
||||||
apiKey: []byte(apiKey),
|
apiKey: []byte(apiKey),
|
||||||
actionChannel: actionChannel,
|
actionChannel: actionChannel,
|
||||||
responseChannel: responseChannel,
|
getHandler: getHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@@ -165,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(channelTimeout):
|
|
||||||
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")
|
||||||
}
|
}
|
||||||
|
768
src/terminal.go
768
src/terminal.go
File diff suppressed because it is too large
Load Diff
@@ -20,9 +20,5 @@ func notifyStop(p *os.Process) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
pid = pgid * -1
|
pid = pgid * -1
|
||||||
}
|
}
|
||||||
unix.Kill(pid, syscall.SIGSTOP)
|
unix.Kill(pid, syscall.SIGTSTP)
|
||||||
}
|
|
||||||
|
|
||||||
func notifyOnCont(resizeChan chan<- os.Signal) {
|
|
||||||
signal.Notify(resizeChan, syscall.SIGCONT)
|
|
||||||
}
|
}
|
||||||
|
@@ -13,7 +13,3 @@ func notifyOnResize(resizeChan chan<- os.Signal) {
|
|||||||
func notifyStop(p *os.Process) {
|
func notifyStop(p *os.Process) {
|
||||||
// NOOP
|
// NOOP
|
||||||
}
|
}
|
||||||
|
|
||||||
func notifyOnCont(resizeChan chan<- os.Signal) {
|
|
||||||
// NOOP
|
|
||||||
}
|
|
||||||
|
11
src/tmux.go
11
src/tmux.go
@@ -38,7 +38,7 @@ func runTmux(args []string, opts *Options) (int, error) {
|
|||||||
case posUp:
|
case posUp:
|
||||||
tmuxArgs = append(tmuxArgs, "-xC", "-y0")
|
tmuxArgs = append(tmuxArgs, "-xC", "-y0")
|
||||||
case posDown:
|
case posDown:
|
||||||
tmuxArgs = append(tmuxArgs, "-xC", "-yS")
|
tmuxArgs = append(tmuxArgs, "-xC", "-y9999")
|
||||||
case posLeft:
|
case posLeft:
|
||||||
tmuxArgs = append(tmuxArgs, "-x0", "-yC")
|
tmuxArgs = append(tmuxArgs, "-x0", "-yC")
|
||||||
case posRight:
|
case posRight:
|
||||||
@@ -49,9 +49,12 @@ func runTmux(args []string, opts *Options) (int, error) {
|
|||||||
tmuxArgs = append(tmuxArgs, "-w"+opts.Tmux.width.String())
|
tmuxArgs = append(tmuxArgs, "-w"+opts.Tmux.width.String())
|
||||||
tmuxArgs = append(tmuxArgs, "-h"+opts.Tmux.height.String())
|
tmuxArgs = append(tmuxArgs, "-h"+opts.Tmux.height.String())
|
||||||
|
|
||||||
return runProxy(argStr, func(temp string) *exec.Cmd {
|
return runProxy(argStr, func(temp string, needBash bool) (*exec.Cmd, error) {
|
||||||
sh, _ := sh()
|
sh, err := sh(needBash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
tmuxArgs = append(tmuxArgs, sh, temp)
|
tmuxArgs = append(tmuxArgs, sh, temp)
|
||||||
return exec.Command("tmux", tmuxArgs...)
|
return exec.Command("tmux", tmuxArgs...), nil
|
||||||
}, opts, true)
|
}, opts, true)
|
||||||
}
|
}
|
||||||
|
@@ -794,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
|
||||||
}
|
}
|
||||||
@@ -1027,13 +1030,13 @@ func cleanse(str string) string {
|
|||||||
func (w *LightWindow) CPrint(pair ColorPair, text string) {
|
func (w *LightWindow) CPrint(pair ColorPair, text string) {
|
||||||
_, code := w.csiColor(pair.Fg(), pair.Bg(), pair.Attr())
|
_, code := w.csiColor(pair.Fg(), pair.Bg(), pair.Attr())
|
||||||
w.stderrInternal(cleanse(text), false, code)
|
w.stderrInternal(cleanse(text), false, code)
|
||||||
w.csi("m")
|
w.csi("0m")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) cprint2(fg Color, bg Color, attr Attr, text string) {
|
func (w *LightWindow) cprint2(fg Color, bg Color, attr Attr, text string) {
|
||||||
hasColors, code := w.csiColor(fg, bg, attr)
|
hasColors, code := w.csiColor(fg, bg, attr)
|
||||||
if hasColors {
|
if hasColors {
|
||||||
defer w.csi("m")
|
defer w.csi("0m")
|
||||||
}
|
}
|
||||||
w.stderrInternal(cleanse(text), false, code)
|
w.stderrInternal(cleanse(text), false, code)
|
||||||
}
|
}
|
||||||
@@ -1115,6 +1118,14 @@ func (w *LightWindow) setBg() string {
|
|||||||
return "\x1b[m"
|
return "\x1b[m"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) LinkBegin(uri string, params string) {
|
||||||
|
w.renderer.queued.WriteString("\x1b]8;" + params + ";" + uri + "\x1b\\")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) LinkEnd() {
|
||||||
|
w.renderer.queued.WriteString("\x1b]8;;\x1b\\")
|
||||||
|
}
|
||||||
|
|
||||||
func (w *LightWindow) Fill(text string) FillReturn {
|
func (w *LightWindow) Fill(text string) FillReturn {
|
||||||
w.Move(w.posy, w.posx)
|
w.Move(w.posy, w.posx)
|
||||||
code := w.setBg()
|
code := w.setBg()
|
||||||
@@ -1130,7 +1141,7 @@ func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillRetu
|
|||||||
bg = w.bg
|
bg = w.bg
|
||||||
}
|
}
|
||||||
if hasColors, resetCode := w.csiColor(fg, bg, attr); hasColors {
|
if hasColors, resetCode := w.csiColor(fg, bg, attr); hasColors {
|
||||||
defer w.csi("m")
|
defer w.csi("0m")
|
||||||
return w.fill(text, resetCode)
|
return w.fill(text, resetCode)
|
||||||
}
|
}
|
||||||
return w.fill(text, w.setBg())
|
return w.fill(text, w.setBg())
|
||||||
|
@@ -33,15 +33,9 @@ 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() {
|
||||||
|
@@ -139,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) {
|
||||||
|
@@ -4,6 +4,7 @@ package tui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
@@ -49,6 +50,8 @@ type TcellWindow struct {
|
|||||||
lastY int
|
lastY int
|
||||||
moveCursor bool
|
moveCursor bool
|
||||||
borderStyle BorderStyle
|
borderStyle BorderStyle
|
||||||
|
uri *string
|
||||||
|
params *string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Top() int {
|
func (w *TcellWindow) Top() int {
|
||||||
@@ -601,6 +604,16 @@ func (w *TcellWindow) Print(text string) {
|
|||||||
w.printString(text, w.normal)
|
w.printString(text, w.normal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *TcellWindow) withUrl(style tcell.Style) tcell.Style {
|
||||||
|
if w.uri != nil {
|
||||||
|
style = style.Url(*w.uri)
|
||||||
|
if md := regexp.MustCompile(`id=([^:]+)`).FindStringSubmatch(*w.params); len(md) > 1 {
|
||||||
|
style = style.UrlId(md[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return style
|
||||||
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) printString(text string, pair ColorPair) {
|
func (w *TcellWindow) printString(text string, pair ColorPair) {
|
||||||
lx := 0
|
lx := 0
|
||||||
a := pair.Attr()
|
a := pair.Attr()
|
||||||
@@ -615,6 +628,7 @@ func (w *TcellWindow) printString(text string, pair ColorPair) {
|
|||||||
Blink(a&Attr(tcell.AttrBlink) != 0).
|
Blink(a&Attr(tcell.AttrBlink) != 0).
|
||||||
Dim(a&Attr(tcell.AttrDim) != 0)
|
Dim(a&Attr(tcell.AttrDim) != 0)
|
||||||
}
|
}
|
||||||
|
style = w.withUrl(style)
|
||||||
|
|
||||||
gr := uniseg.NewGraphemes(text)
|
gr := uniseg.NewGraphemes(text)
|
||||||
for gr.Next() {
|
for gr.Next() {
|
||||||
@@ -665,6 +679,7 @@ func (w *TcellWindow) fillString(text string, pair ColorPair) FillReturn {
|
|||||||
Underline(a&Attr(tcell.AttrUnderline) != 0).
|
Underline(a&Attr(tcell.AttrUnderline) != 0).
|
||||||
StrikeThrough(a&Attr(tcell.AttrStrikeThrough) != 0).
|
StrikeThrough(a&Attr(tcell.AttrStrikeThrough) != 0).
|
||||||
Italic(a&Attr(tcell.AttrItalic) != 0)
|
Italic(a&Attr(tcell.AttrItalic) != 0)
|
||||||
|
style = w.withUrl(style)
|
||||||
|
|
||||||
gr := uniseg.NewGraphemes(text)
|
gr := uniseg.NewGraphemes(text)
|
||||||
Loop:
|
Loop:
|
||||||
@@ -716,6 +731,16 @@ func (w *TcellWindow) Fill(str string) FillReturn {
|
|||||||
return w.fillString(str, w.normal)
|
return w.fillString(str, w.normal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *TcellWindow) LinkBegin(uri string, params string) {
|
||||||
|
w.uri = &uri
|
||||||
|
w.params = ¶ms
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *TcellWindow) LinkEnd() {
|
||||||
|
w.uri = nil
|
||||||
|
w.params = nil
|
||||||
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
|
func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
|
||||||
if fg == colDefault {
|
if fg == colDefault {
|
||||||
fg = w.normal.Fg()
|
fg = w.normal.Fg()
|
||||||
|
@@ -334,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
|
||||||
@@ -573,6 +564,8 @@ type Window interface {
|
|||||||
CPrint(color ColorPair, text string)
|
CPrint(color ColorPair, text string)
|
||||||
Fill(text string) FillReturn
|
Fill(text string) FillReturn
|
||||||
CFill(fg Color, bg Color, attr Attr, text string) FillReturn
|
CFill(fg Color, bg Color, attr Attr, text string) FillReturn
|
||||||
|
LinkBegin(uri string, params string)
|
||||||
|
LinkEnd()
|
||||||
Erase()
|
Erase()
|
||||||
EraseMaybe() bool
|
EraseMaybe() bool
|
||||||
}
|
}
|
||||||
|
@@ -226,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)
|
||||||
|
}
|
||||||
|
@@ -144,12 +144,22 @@ func IsTty(file *os.File) bool {
|
|||||||
return isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd)
|
return isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RunOnce runs the given function only once
|
||||||
|
func RunOnce(f func()) func() {
|
||||||
|
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
|
||||||
func Once(nextResponse bool) func() bool {
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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) {
|
||||||
|
@@ -44,11 +44,6 @@ func needWinpty(opts *Options) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runWinpty(args []string, opts *Options) (int, error) {
|
func runWinpty(args []string, opts *Options) (int, error) {
|
||||||
sh, err := sh()
|
|
||||||
if err != nil {
|
|
||||||
return ExitError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
argStr := escapeSingleQuote(args[0])
|
argStr := escapeSingleQuote(args[0])
|
||||||
for _, arg := range args[1:] {
|
for _, arg := range args[1:] {
|
||||||
argStr += " " + escapeSingleQuote(arg)
|
argStr += " " + escapeSingleQuote(arg)
|
||||||
@@ -56,20 +51,30 @@ func runWinpty(args []string, opts *Options) (int, error) {
|
|||||||
argStr += ` --no-winpty`
|
argStr += ` --no-winpty`
|
||||||
|
|
||||||
if isMintty345() {
|
if isMintty345() {
|
||||||
return runProxy(argStr, func(temp string) *exec.Cmd {
|
return runProxy(argStr, func(temp string, needBash bool) (*exec.Cmd, error) {
|
||||||
|
sh, err := sh(needBash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
cmd := exec.Command(sh, temp)
|
cmd := exec.Command(sh, temp)
|
||||||
cmd.Env = append(os.Environ(), "MSYS=enable_pcon")
|
cmd.Env = append(os.Environ(), "MSYS=enable_pcon")
|
||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
return cmd
|
return cmd, nil
|
||||||
}, opts, false)
|
}, opts, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return runProxy(argStr, func(temp string) *exec.Cmd {
|
return runProxy(argStr, func(temp string, needBash bool) (*exec.Cmd, error) {
|
||||||
|
sh, err := sh(needBash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
cmd := exec.Command(sh, "-c", fmt.Sprintf(`winpty < /dev/tty > /dev/tty -- sh %q`, temp))
|
cmd := exec.Command(sh, "-c", fmt.Sprintf(`winpty < /dev/tty > /dev/tty -- sh %q`, temp))
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
return cmd
|
return cmd, nil
|
||||||
}, opts, false)
|
}, opts, false)
|
||||||
}
|
}
|
||||||
|
120
test/test_go.rb
120
test/test_go.rb
@@ -26,12 +26,12 @@ BASE = File.expand_path('..', __dir__)
|
|||||||
Dir.chdir(BASE)
|
Dir.chdir(BASE)
|
||||||
FZF = "FZF_DEFAULT_OPTS=\"--no-scrollbar --pointer \\> --marker \\>\" FZF_DEFAULT_COMMAND= #{BASE}/bin/fzf"
|
FZF = "FZF_DEFAULT_OPTS=\"--no-scrollbar --pointer \\> --marker \\>\" FZF_DEFAULT_COMMAND= #{BASE}/bin/fzf"
|
||||||
|
|
||||||
def wait
|
def wait(timeout = DEFAULT_TIMEOUT)
|
||||||
since = Time.now
|
since = Time.now
|
||||||
begin
|
begin
|
||||||
yield or raise Minitest::Assertion, 'Assertion failure'
|
yield or raise Minitest::Assertion, 'Assertion failure'
|
||||||
rescue Minitest::Assertion
|
rescue Minitest::Assertion
|
||||||
raise if Time.now - since > DEFAULT_TIMEOUT
|
raise if Time.now - since > timeout
|
||||||
|
|
||||||
sleep(0.05)
|
sleep(0.05)
|
||||||
retry
|
retry
|
||||||
@@ -66,7 +66,7 @@ class Shell
|
|||||||
end
|
end
|
||||||
|
|
||||||
def fish
|
def fish
|
||||||
"unset #{UNSETS.join(' ')}; FZF_DEFAULT_OPTS=\"--no-scrollbar --pointer '>' --marker '>'\" fish_history= fish"
|
"unset #{UNSETS.join(' ')}; rm -f ~/.local/share/fish/fzf_test_history; FZF_DEFAULT_OPTS=\"--no-scrollbar --pointer '>' --marker '>'\" fish_history=fzf_test fish"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -103,10 +103,10 @@ class Tmux
|
|||||||
go(%W[capture-pane -p -J -t #{win}]).map(&:rstrip).reverse.drop_while(&:empty?).reverse
|
go(%W[capture-pane -p -J -t #{win}]).map(&:rstrip).reverse.drop_while(&:empty?).reverse
|
||||||
end
|
end
|
||||||
|
|
||||||
def until(refresh = false)
|
def until(refresh = false, timeout: DEFAULT_TIMEOUT)
|
||||||
lines = nil
|
lines = nil
|
||||||
begin
|
begin
|
||||||
wait do
|
wait(timeout) do
|
||||||
lines = capture
|
lines = capture
|
||||||
class << lines
|
class << lines
|
||||||
def counts
|
def counts
|
||||||
@@ -1443,7 +1443,7 @@ class TestGoFZF < TestBase
|
|||||||
[0, 3, 6].each do |off|
|
[0, 3, 6].each do |off|
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys "#{FZF} --hscroll-off=#{off} -q 0 < #{tempname}", :Enter
|
tmux.send_keys "#{FZF} --hscroll-off=#{off} -q 0 < #{tempname}", :Enter
|
||||||
tmux.until { |lines| assert lines[-3]&.end_with?((0..off).to_a.join + '..') }
|
tmux.until { |lines| assert lines[-3]&.end_with?((0..off).to_a.join + '··') }
|
||||||
tmux.send_keys '9'
|
tmux.send_keys '9'
|
||||||
tmux.until { |lines| assert lines[-3]&.end_with?('789') }
|
tmux.until { |lines| assert lines[-3]&.end_with?('789') }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
@@ -2978,6 +2978,28 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { assert_match(%r{ 0/100000}, _1[-1]) }
|
tmux.until { assert_match(%r{ 0/100000}, _1[-1]) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_info_command
|
||||||
|
tmux.send_keys(%(seq 10000 | #{FZF} --separator x --info-command 'echo -e "--\\x1b[33m$FZF_POS\\x1b[m/$FZF_INFO--"'), :Enter)
|
||||||
|
tmux.until { assert_match(%r{^ --1/10000/10000-- xx}, _1[-2]) }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { assert_match(%r{^ --2/10000/10000-- xx}, _1[-2]) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_info_command_inline
|
||||||
|
tmux.send_keys(%(seq 10000 | #{FZF} --separator x --info-command 'echo -e "--\\x1b[33m$FZF_POS\\x1b[m/$FZF_INFO--"' --info inline:xx), :Enter)
|
||||||
|
tmux.until { assert_match(%r{^> xx--1/10000/10000-- xx}, _1[-1]) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_info_command_right
|
||||||
|
tmux.send_keys(%(seq 10000 | #{FZF} --separator x --info-command 'echo -e "--\\x1b[33m$FZF_POS\\x1b[m/$FZF_INFO--"' --info right), :Enter)
|
||||||
|
tmux.until { assert_match(%r{xx --1/10000/10000-- *$}, _1[-2]) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_info_command_inline_right
|
||||||
|
tmux.send_keys(%(seq 10000 | #{FZF} --info-command 'echo -e "--\\x1b[33m$FZF_POS\\x1b[m/$FZF_INFO--"' --info inline-right), :Enter)
|
||||||
|
tmux.until { assert_match(%r{ --1/10000/10000-- *$}, _1[-1]) }
|
||||||
|
end
|
||||||
|
|
||||||
def test_prev_next_selected
|
def test_prev_next_selected
|
||||||
tmux.send_keys 'seq 10 | fzf --multi --bind ctrl-n:next-selected,ctrl-p:prev-selected', :Enter
|
tmux.send_keys 'seq 10 | fzf --multi --bind ctrl-n:next-selected,ctrl-p:prev-selected', :Enter
|
||||||
tmux.until { |lines| assert_equal 10, lines.item_count }
|
tmux.until { |lines| assert_equal 10, lines.item_count }
|
||||||
@@ -3318,6 +3340,92 @@ class TestGoFZF < TestBase
|
|||||||
BLOCK
|
BLOCK
|
||||||
tmux.until { assert_block(block, _1) }
|
tmux.until { assert_block(block, _1) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_fzf_multi_line_no_pointer_and_marker
|
||||||
|
tmux.send_keys %[(echo -en '0\\0'; echo -en '1\\n2\\0'; seq 1000) | fzf --read0 --multi --bind load:select-all --border rounded --reverse --pointer '' --marker '' --marker-multi-line ''], :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
╭───────────
|
||||||
|
│ >
|
||||||
|
│ 3/3 (3)
|
||||||
|
│ 0
|
||||||
|
│ 1
|
||||||
|
│ 2
|
||||||
|
│ 1
|
||||||
|
│ 2
|
||||||
|
│ 3
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_start_on_reload
|
||||||
|
tmux.send_keys %(echo foo | #{FZF} --header Loading --header-lines 1 --bind 'start:reload:sleep 2; echo bar' --bind 'load:change-header:Loaded' --bind space:change-header:), :Enter
|
||||||
|
tmux.until(timeout: 1) { |lines| assert_includes lines[-3], 'Loading' }
|
||||||
|
tmux.until(timeout: 1) { |lines| refute_includes lines[-4], 'foo' }
|
||||||
|
tmux.until { |lines| assert_includes lines[-3], 'Loaded' }
|
||||||
|
tmux.until { |lines| assert_includes lines[-4], 'bar' }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert_includes lines[-3], 'bar' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_boundary_match
|
||||||
|
# Underscore boundaries should be ranked lower
|
||||||
|
{
|
||||||
|
default: [' x '] + %w[/x/ [x] -x- -x_ _x- _x_],
|
||||||
|
path: ['/x/', ' x '] + %w[[x] -x- -x_ _x- _x_],
|
||||||
|
history: ['[x]', '-x-', ' x '] + %w[/x/ -x_ _x- _x_]
|
||||||
|
}.each do |scheme, expected|
|
||||||
|
result = `printf -- 'xxx\n-xx\nxx-\n_x_\n_x-\n-x_\n[x]\n-x-\n x \n/x/\n' | #{FZF} -f"'x'" --scheme=#{scheme}`.lines(chomp: true)
|
||||||
|
assert_equal expected, result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_window_noinfo
|
||||||
|
# │ 1 ││
|
||||||
|
tmux.send_keys %(#{FZF} --preview 'seq 1000' --preview-window top,noinfo --scrollbar --bind space:change-preview-window:info), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines[1]&.start_with?('│ 1')
|
||||||
|
assert lines[1]&.end_with?(' ││')
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines[1]&.start_with?('│ 1')
|
||||||
|
assert lines[1]&.end_with?('1000││')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_gap
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --gap --border --reverse), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
╭─────────────────
|
||||||
|
│ >
|
||||||
|
│ 100/100 ──────
|
||||||
|
│ > 1
|
||||||
|
│
|
||||||
|
│ 2
|
||||||
|
│
|
||||||
|
│ 3
|
||||||
|
│
|
||||||
|
│ 4
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_gap_2
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --gap=2 --border --reverse), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
╭─────────────────
|
||||||
|
│ >
|
||||||
|
│ 100/100 ──────
|
||||||
|
│ > 1
|
||||||
|
│
|
||||||
|
│
|
||||||
|
│ 2
|
||||||
|
│
|
||||||
|
│
|
||||||
|
│ 3
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, _1) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module TestShell
|
module TestShell
|
||||||
|
Reference in New Issue
Block a user