mirror of
https://github.com/junegunn/fzf.git
synced 2025-08-24 00:43:49 -07:00
Compare commits
198 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
3c0a630475 | ||
|
2a1e5a9729 | ||
|
413c66beba | ||
|
1416e696b1 | ||
|
d373cf89c7 | ||
|
dd886d22f0 | ||
|
472569a27c | ||
|
76cf6559cc | ||
|
a34e8dcdc9 | ||
|
da752fc9a4 | ||
|
beb2de2dd9 | ||
|
2a8b65e105 | ||
|
62a916bc24 | ||
|
c47b833e7b | ||
|
09b0958b5f | ||
|
3a4c3d3e58 | ||
|
7484292e63 | ||
|
687c2741b8 | ||
|
2fb285e530 | ||
|
16f6473938 | ||
|
66546208b2 | ||
|
532274045e | ||
|
9347c72fb6 | ||
|
e90bb7169c | ||
|
8a2c41e183 | ||
|
59fb65293a | ||
|
e7718b92b7 | ||
|
cdfaf761df | ||
|
1a9ea6f738 | ||
|
945c1c8597 | ||
|
e4d0f7acd5 | ||
|
250496c953 | ||
|
e47dc758c9 | ||
|
b92a843c5f | ||
|
91bea9c5b3 | ||
|
d75bb5cbe1 | ||
|
2671259fdb | ||
|
2024010119 | ||
|
412040f77e | ||
|
d210660ce8 | ||
|
863a12562b | ||
|
5da606a9ac | ||
|
8d20f3d5c4 | ||
|
5d360180af | ||
|
f0fbed6007 | ||
|
519de7c833 | ||
|
97ccef1a04 | ||
|
c4df0dd06e | ||
|
cd114c6818 | ||
|
1707b8cdba | ||
|
41d4d70b98 | ||
|
0e999482cb | ||
|
65b2c06027 | ||
|
d7b61ede07 | ||
|
87fc1c84b8 | ||
|
d4b5f12383 | ||
|
eb62b0d665 | ||
|
91387a741b | ||
|
e8b34cb00d | ||
|
82954258c1 | ||
|
50f092551b | ||
|
c36a64be68 | ||
|
a343b20775 | ||
|
a714e76ae1 | ||
|
d21d5c9510 | ||
|
cd6788a2bb | ||
|
6b99399c41 | ||
|
952b6af445 | ||
|
7c674ad7fa | ||
|
d7d2ac3951 | ||
|
29e67d307a | ||
|
7320b7df62 | ||
|
11fb4233f7 | ||
|
84bb350b14 | ||
|
38e3694d1c | ||
|
1084935241 | ||
|
f5f0b9ecaa | ||
|
230fc49ae2 | ||
|
250d507bdf | ||
|
a818653174 | ||
|
5c3b044740 | ||
|
c5aa8729a1 | ||
|
3f78d76da1 | ||
|
70c19ccf16 | ||
|
68db9cb499 | ||
|
d0466fa777 | ||
|
21ab64e962 | ||
|
a0145cebf2 | ||
|
69176fc5f4 | ||
|
278dce9ba6 | ||
|
1cfa3ee4c7 | ||
|
9a95cd5794 | ||
|
a62fe3df6f | ||
|
7701244a08 | ||
|
96e31e4b78 | ||
|
ec208af474 | ||
|
242641264d | ||
|
d3a9a0615b | ||
|
3277e8c89c | ||
|
d02b9442a5 | ||
|
bac385b59c | ||
|
b1a0ab8086 | ||
|
a33749eb71 | ||
|
f5e4ee90e4 | ||
|
690d5e6dbd | ||
|
a76c055b63 | ||
|
70c461c60b | ||
|
d51b71ee80 | ||
|
3666448ca6 | ||
|
d3311d9f43 | ||
|
3e1735b06e | ||
|
de7ef7eace | ||
|
7e89458a3b | ||
|
f212bafe46 | ||
|
86fe40708b | ||
|
d718747c5b | ||
|
46ee9ac41c | ||
|
f1d306feab | ||
|
2d0db98e83 | ||
|
3df06a1c68 | ||
|
a8f9432a3a | ||
|
561e0b04a8 | ||
|
404b6a864b | ||
|
4feaf31225 | ||
|
391aa14845 | ||
|
a0d61b4c37 | ||
|
2952737755 | ||
|
f103aa4753 | ||
|
884856023a | ||
|
d8188fce7b | ||
|
0f15f1ab73 | ||
|
488a236b7a | ||
|
e833823e15 | ||
|
ee4ba104e7 | ||
|
4fdc08295b | ||
|
a3ff49aaf1 | ||
|
76364ea767 | ||
|
8eec50d764 | ||
|
32b659b346 | ||
|
00809909ae | ||
|
9f7684f6fe | ||
|
2bed7d370e | ||
|
d2b852f7cb | ||
|
901939bd96 | ||
|
edfdcc8cee | ||
|
3982c9a552 | ||
|
4490b2d209 | ||
|
eb4bbf3294 | ||
|
dc97d48491 | ||
|
0f50dc848e | ||
|
c5e4b83de3 | ||
|
a08ab46713 | ||
|
f50a7058d6 | ||
|
2c74f0a040 | ||
|
58835e40f3 | ||
|
8befa5918a | ||
|
df80f7ff2a | ||
|
5f66786ef1 | ||
|
3a965856a5 | ||
|
03df609d77 | ||
|
178581b560 | ||
|
ffd2314120 | ||
|
815b595d2f | ||
|
11e56403dd | ||
|
4baadecda5 | ||
|
cf552b5f3b | ||
|
1894304d33 | ||
|
9d5392fb02 | ||
|
c280645671 | ||
|
45f92e6b38 | ||
|
6509f09961 | ||
|
3c279a6f0e | ||
|
9ec3f03871 | ||
|
84a9c2c112 | ||
|
af368119cb | ||
|
89b9189efa | ||
|
dd59b8c7b9 | ||
|
f83491274f | ||
|
c0435fdff4 | ||
|
3c09c77269 | ||
|
547e101f1d | ||
|
0130f64934 | ||
|
361e0543ee | ||
|
63aa5d3b4e | ||
|
01302d097c | ||
|
e6095cb7e8 | ||
|
b876b8af11 | ||
|
a7c41f3fcd | ||
|
4772bd8d4c | ||
|
d471067e3f | ||
|
d0b7780239 | ||
|
e627ca6bd7 | ||
|
c97172bdd4 | ||
|
ce8a745fb4 | ||
|
3e9efd1401 | ||
|
20340190b5 | ||
|
265040a78c | ||
|
448d7e0c5a |
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
@@ -33,12 +33,12 @@ jobs:
|
|||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v2
|
uses: github/codeql-action/init@v3
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v2
|
uses: github/codeql-action/autobuild@v3
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v2
|
uses: github/codeql-action/analyze@v3
|
||||||
|
2
.github/workflows/depsreview.yaml
vendored
2
.github/workflows/depsreview.yaml
vendored
@@ -11,4 +11,4 @@ jobs:
|
|||||||
- name: 'Checkout Repository'
|
- name: 'Checkout Repository'
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
- name: 'Dependency Review'
|
- name: 'Dependency Review'
|
||||||
uses: actions/dependency-review-action@v3
|
uses: actions/dependency-review-action@v4
|
||||||
|
10
.github/workflows/linux.yml
vendored
10
.github/workflows/linux.yml
vendored
@@ -11,6 +11,9 @@ on:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
|
env:
|
||||||
|
LANG: C.UTF-8
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -20,7 +23,7 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.19
|
go-version: 1.19
|
||||||
|
|
||||||
@@ -42,4 +45,7 @@ jobs:
|
|||||||
run: make test
|
run: make test
|
||||||
|
|
||||||
- name: Integration test
|
- name: Integration test
|
||||||
run: make install && ./install --all && LC_ALL=C 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/macos.yml
vendored
2
.github/workflows/macos.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.18
|
go-version: 1.18
|
||||||
|
|
||||||
|
24
.github/workflows/sponsors.yml
vendored
Normal file
24
.github/workflows/sponsors.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
name: Generate Sponsors README
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: 0 0 * * 0
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout 🛎️
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Generate Sponsors 💖
|
||||||
|
uses: JamesIves/github-sponsors-readme-action@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.SPONSORS_TOKEN }}
|
||||||
|
file: 'README.md'
|
||||||
|
|
||||||
|
- name: Deploy to GitHub Pages 🚀
|
||||||
|
uses: JamesIves/github-pages-deploy-action@v4
|
||||||
|
with:
|
||||||
|
branch: master
|
||||||
|
folder: '.'
|
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@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: crate-ci/typos@v1.14.10
|
- uses: crate-ci/typos@v1.17.2
|
||||||
|
2
.github/workflows/winget.yml
vendored
2
.github/workflows/winget.yml
vendored
@@ -5,7 +5,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
runs-on: windows-latest # Action can only run on Windows
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: vedantmgoyal2009/winget-releaser@v2
|
- uses: vedantmgoyal2009/winget-releaser@v2
|
||||||
with:
|
with:
|
||||||
|
@@ -1 +1 @@
|
|||||||
golang 1.20.4
|
golang 1.20.13
|
||||||
|
72
ADVANCED.md
72
ADVANCED.md
@@ -1,8 +1,8 @@
|
|||||||
Advanced fzf examples
|
Advanced fzf examples
|
||||||
======================
|
======================
|
||||||
|
|
||||||
* *Last update: 2023/05/26*
|
* *Last update: 2024/01/20*
|
||||||
* *Requires fzf 0.41.0 or above*
|
* *Requires fzf 0.46.0 or above*
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -16,17 +16,20 @@ Advanced fzf examples
|
|||||||
* [Dynamic reloading of the list](#dynamic-reloading-of-the-list)
|
* [Dynamic reloading of the list](#dynamic-reloading-of-the-list)
|
||||||
* [Updating the list of processes by pressing CTRL-R](#updating-the-list-of-processes-by-pressing-ctrl-r)
|
* [Updating the list of processes by pressing CTRL-R](#updating-the-list-of-processes-by-pressing-ctrl-r)
|
||||||
* [Toggling between data sources](#toggling-between-data-sources)
|
* [Toggling between data sources](#toggling-between-data-sources)
|
||||||
|
* [Toggling with a single key binding](#toggling-with-a-single-key-binding)
|
||||||
* [Ripgrep integration](#ripgrep-integration)
|
* [Ripgrep integration](#ripgrep-integration)
|
||||||
* [Using fzf as the secondary filter](#using-fzf-as-the-secondary-filter)
|
* [Using fzf as the secondary filter](#using-fzf-as-the-secondary-filter)
|
||||||
* [Using fzf as interactive Ripgrep launcher](#using-fzf-as-interactive-ripgrep-launcher)
|
* [Using fzf as interactive Ripgrep launcher](#using-fzf-as-interactive-ripgrep-launcher)
|
||||||
* [Switching to fzf-only search mode](#switching-to-fzf-only-search-mode)
|
* [Switching to fzf-only search mode](#switching-to-fzf-only-search-mode)
|
||||||
* [Switching between Ripgrep mode and fzf mode](#switching-between-ripgrep-mode-and-fzf-mode)
|
* [Switching between Ripgrep mode and fzf mode](#switching-between-ripgrep-mode-and-fzf-mode)
|
||||||
|
* [Switching between Ripgrep mode and fzf mode using a single key binding](#switching-between-ripgrep-mode-and-fzf-mode-using-a-single-key-binding)
|
||||||
* [Log tailing](#log-tailing)
|
* [Log tailing](#log-tailing)
|
||||||
* [Key bindings for git objects](#key-bindings-for-git-objects)
|
* [Key bindings for git objects](#key-bindings-for-git-objects)
|
||||||
* [Files listed in `git status`](#files-listed-in-git-status)
|
* [Files listed in `git status`](#files-listed-in-git-status)
|
||||||
* [Branches](#branches)
|
* [Branches](#branches)
|
||||||
* [Commit hashes](#commit-hashes)
|
* [Commit hashes](#commit-hashes)
|
||||||
* [Color themes](#color-themes)
|
* [Color themes](#color-themes)
|
||||||
|
* [fzf Theme Playground](#fzf-theme-playground)
|
||||||
* [Generating fzf color theme from Vim color schemes](#generating-fzf-color-theme-from-vim-color-schemes)
|
* [Generating fzf color theme from Vim color schemes](#generating-fzf-color-theme-from-vim-color-schemes)
|
||||||
|
|
||||||
<!-- vim-markdown-toc -->
|
<!-- vim-markdown-toc -->
|
||||||
@@ -208,6 +211,30 @@ find * | fzf --prompt 'All> ' \
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
### Toggling with a single key binding
|
||||||
|
|
||||||
|
The above example uses two different key bindings to toggle between two modes,
|
||||||
|
but can we just use a single key binding?
|
||||||
|
|
||||||
|
To make a key binding behave differently each time it is pressed, we need:
|
||||||
|
|
||||||
|
1. a way to store the current state. i.e. "which mode are we in?"
|
||||||
|
2. and a way to dynamically perform different actions depending on the state.
|
||||||
|
|
||||||
|
The following example shows how to 1. store the current mode in the prompt
|
||||||
|
string, 2. and use this information (`$FZF_PROMPT`) to determine which
|
||||||
|
actions to perform using the `transform` action.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
fd --type file |
|
||||||
|
fzf --prompt 'Files> ' \
|
||||||
|
--header 'CTRL-T: Switch between Files/Directories' \
|
||||||
|
--bind 'ctrl-t:transform:[[ ! $FZF_PROMPT =~ Files ]] &&
|
||||||
|
echo "change-prompt(Files> )+reload(fd --type file)" ||
|
||||||
|
echo "change-prompt(Directories> )+reload(fd --type directory)"' \
|
||||||
|
--preview '[[ $FZF_PROMPT =~ Files ]] && bat --color=always {} || tree -C {}'
|
||||||
|
```
|
||||||
|
|
||||||
Ripgrep integration
|
Ripgrep integration
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
@@ -442,6 +469,41 @@ INITIAL_QUERY="${*:-}"
|
|||||||
[0.30.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0300
|
[0.30.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0300
|
||||||
[0.36.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0360
|
[0.36.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0360
|
||||||
|
|
||||||
|
### Switching between Ripgrep mode and fzf mode using a single key binding
|
||||||
|
|
||||||
|
In contrast to the previous version, we use just one hotkey to toggle between
|
||||||
|
ripgrep and fzf mode. This is achieved by using the `$FZF_PROMPT` as a state
|
||||||
|
within the `transform` action, a feature introduced in [fzf 0.45.0][0.45.0]. A
|
||||||
|
more detailed explanation of this feature can be found in a previous section -
|
||||||
|
[Toggling with a single keybinding](#toggling-with-a-single-key-binding).
|
||||||
|
|
||||||
|
[0.45.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0450
|
||||||
|
|
||||||
|
When using the `transform` action, the placeholder (`\{q}`) should be escaped to
|
||||||
|
prevent immediate evaluation.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Switch between Ripgrep mode and fzf filtering mode (CTRL-T)
|
||||||
|
rm -f /tmp/rg-fzf-{r,f}
|
||||||
|
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
||||||
|
INITIAL_QUERY="${*:-}"
|
||||||
|
: | fzf --ansi --disabled --query "$INITIAL_QUERY" \
|
||||||
|
--bind "start:reload:$RG_PREFIX {q}" \
|
||||||
|
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
|
||||||
|
--bind 'ctrl-t:transform:[[ ! $FZF_PROMPT =~ ripgrep ]] &&
|
||||||
|
echo "rebind(change)+change-prompt(1. ripgrep> )+disable-search+transform-query:echo \{q} > /tmp/rg-fzf-f; cat /tmp/rg-fzf-r" ||
|
||||||
|
echo "unbind(change)+change-prompt(2. fzf> )+enable-search+transform-query:echo \{q} > /tmp/rg-fzf-r; cat /tmp/rg-fzf-f"' \
|
||||||
|
--color "hl:-1:underline,hl+:-1:underline:reverse" \
|
||||||
|
--prompt '1. ripgrep> ' \
|
||||||
|
--delimiter : \
|
||||||
|
--header 'CTRL-T: Switch between ripgrep/fzf' \
|
||||||
|
--preview 'bat --color=always {1} --highlight-line {2}' \
|
||||||
|
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \
|
||||||
|
--bind 'enter:become(vim {1} +{2})'
|
||||||
|
```
|
||||||
|
|
||||||
Log tailing
|
Log tailing
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
@@ -571,6 +633,12 @@ export FZF_DEFAULT_OPTS='--color=bg+:#293739,bg:#1B1D1E,border:#808080,spinner:#
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
### fzf Theme Playground
|
||||||
|
|
||||||
|
[fzf Theme Playground](https://vitormv.github.io/fzf-themes/) created by
|
||||||
|
[Vitor Mello](https://github.com/vitormv) is a webpage where you can
|
||||||
|
interactively create fzf themes.
|
||||||
|
|
||||||
### Generating fzf color theme from Vim color schemes
|
### Generating fzf color theme from Vim color schemes
|
||||||
|
|
||||||
The Vim plugin of fzf can generate `--color` option from the current color
|
The Vim plugin of fzf can generate `--color` option from the current color
|
||||||
|
6
BUILD.md
6
BUILD.md
@@ -6,7 +6,7 @@ Build instructions
|
|||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- Go 1.17 or above
|
- Go 1.18 or above
|
||||||
|
|
||||||
### Using Makefile
|
### Using Makefile
|
||||||
|
|
||||||
@@ -34,8 +34,8 @@ make release
|
|||||||
Third-party libraries used
|
Third-party libraries used
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
- [mattn/go-runewidth](https://github.com/mattn/go-runewidth)
|
- [rivo/uniseg](https://github.com/rivo/uniseg)
|
||||||
- Licensed under [MIT](http://mattn.mit-license.org)
|
- Licensed under [MIT](https://raw.githubusercontent.com/rivo/uniseg/master/LICENSE.txt)
|
||||||
- [mattn/go-shellwords](https://github.com/mattn/go-shellwords)
|
- [mattn/go-shellwords](https://github.com/mattn/go-shellwords)
|
||||||
- Licensed under [MIT](http://mattn.mit-license.org)
|
- Licensed under [MIT](http://mattn.mit-license.org)
|
||||||
- [mattn/go-isatty](https://github.com/mattn/go-isatty)
|
- [mattn/go-isatty](https://github.com/mattn/go-isatty)
|
||||||
|
217
CHANGELOG.md
217
CHANGELOG.md
@@ -1,6 +1,221 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.46.1
|
||||||
|
------
|
||||||
|
- Bug fixes and improvements
|
||||||
|
- Fixed Windows binaries
|
||||||
|
- Downgraded Go version to 1.20 to support older versions of Windows
|
||||||
|
- https://tip.golang.org/doc/go1.21#windows
|
||||||
|
- Updated [rivo/uniseg](https://github.com/rivo/uniseg) dependency to v0.4.6
|
||||||
|
|
||||||
|
0.46.0
|
||||||
|
------
|
||||||
|
- Added two new events
|
||||||
|
- `result` - triggered when the filtering for the current query is complete and the result list is ready
|
||||||
|
- `resize` - triggered when the terminal size is changed
|
||||||
|
- fzf now exports the following environment variables to the child processes
|
||||||
|
| Variable | Description |
|
||||||
|
| --- | --- |
|
||||||
|
| `FZF_LINES` | Number of lines fzf takes up excluding padding and margin |
|
||||||
|
| `FZF_COLUMNS` | Number of columns fzf takes up excluding padding and margin |
|
||||||
|
| `FZF_TOTAL_COUNT` | Total number of items |
|
||||||
|
| `FZF_MATCH_COUNT` | Number of matched items |
|
||||||
|
| `FZF_SELECT_COUNT` | Number of selected items |
|
||||||
|
| `FZF_QUERY` | Current query string |
|
||||||
|
| `FZF_PROMPT` | Prompt string |
|
||||||
|
| `FZF_ACTION` | The name of the last action performed |
|
||||||
|
- This allows you to write sophisticated transformations like so
|
||||||
|
```sh
|
||||||
|
# Script to dynamically resize the preview window
|
||||||
|
transformer='
|
||||||
|
# 1 line for info, another for prompt, and 2 more lines for preview window border
|
||||||
|
lines=$(( FZF_LINES - FZF_MATCH_COUNT - 4 ))
|
||||||
|
if [[ $FZF_MATCH_COUNT -eq 0 ]]; then
|
||||||
|
echo "change-preview-window:hidden"
|
||||||
|
elif [[ $lines -gt 3 ]]; then
|
||||||
|
echo "change-preview-window:$lines"
|
||||||
|
elif [[ $FZF_PREVIEW_LINES -ne 3 ]]; then
|
||||||
|
echo "change-preview-window:3"
|
||||||
|
fi
|
||||||
|
'
|
||||||
|
seq 10000 | fzf --preview 'seq {} 10000' --preview-window up \
|
||||||
|
--bind "result:transform:$transformer" \
|
||||||
|
--bind "resize:transform:$transformer"
|
||||||
|
```
|
||||||
|
- And we're phasing out `{fzf:prompt}` and `{fzf:action}`
|
||||||
|
- Changed [mattn/go-runewidth](https://github.com/mattn/go-runewidth) dependency to [rivo/uniseg](https://github.com/rivo/uniseg) for accurate results
|
||||||
|
- Set `--ambidouble` if your terminal displays ambiguous width characters (e.g. box-drawing characters for borders) as 2 columns
|
||||||
|
- `RUNEWIDTH_EASTASIAN=1` is still respected for backward compatibility, but it's recommended that you use this new option instead
|
||||||
|
- Bug fixes
|
||||||
|
|
||||||
|
0.45.0
|
||||||
|
------
|
||||||
|
- Added `transform` action to conditionally perform a series of actions
|
||||||
|
```sh
|
||||||
|
# Disallow selecting an empty line
|
||||||
|
echo -e "1. Hello\n2. Goodbye\n\n3. Exit" |
|
||||||
|
fzf --height '~100%' --reverse --header 'Select one' \
|
||||||
|
--bind 'enter:transform:[[ -n {} ]] && echo accept || echo "change-header:Invalid selection"'
|
||||||
|
|
||||||
|
# Move cursor past the empty line
|
||||||
|
echo -e "1. Hello\n2. Goodbye\n\n3. Exit" |
|
||||||
|
fzf --height '~100%' --reverse --header 'Select one' \
|
||||||
|
--bind 'enter:transform:[[ -n {} ]] && echo accept || echo "change-header:Invalid selection"' \
|
||||||
|
--bind 'focus:transform:[[ -n {} ]] && exit; [[ {fzf:action} =~ up$ ]] && echo up || echo down'
|
||||||
|
|
||||||
|
# A single key binding to toggle between modes
|
||||||
|
fd --type file |
|
||||||
|
fzf --prompt 'Files> ' \
|
||||||
|
--header 'CTRL-T: Switch between Files/Directories' \
|
||||||
|
--bind 'ctrl-t:transform:[[ ! {fzf:prompt} =~ Files ]] &&
|
||||||
|
echo "change-prompt(Files> )+reload(fd --type file)" ||
|
||||||
|
echo "change-prompt(Directories> )+reload(fd --type directory)"'
|
||||||
|
```
|
||||||
|
- Added placeholder expressions
|
||||||
|
- `{fzf:action}` - The name of the last action performed
|
||||||
|
- `{fzf:prompt}` - Prompt string (including ANSI color codes)
|
||||||
|
- `{fzf:query}` - Synonym for `{q}`
|
||||||
|
- Added support for negative height
|
||||||
|
```sh
|
||||||
|
# Terminal height minus 1, so you can still see the command line
|
||||||
|
fzf --height=-1
|
||||||
|
```
|
||||||
|
- This handles a terminal resize better than `--height=$(($(tput lines) - 1))`
|
||||||
|
- Added `accept-or-print-query` action that acts like `accept` but prints the
|
||||||
|
current query when there's no match for the query
|
||||||
|
```sh
|
||||||
|
# You can make CTRL-R paste the current query when there's no match
|
||||||
|
export FZF_CTRL_R_OPTS='--bind enter:accept-or-print-query'
|
||||||
|
```
|
||||||
|
- Note that there are alternative ways to implement the same strategy
|
||||||
|
```sh
|
||||||
|
# 'become' is apparently more versatile but it's not available on Windows.
|
||||||
|
export FZF_CTRL_R_OPTS='--bind "enter:become:if [ -z {} ]; then echo {q}; else echo {}; fi"'
|
||||||
|
|
||||||
|
# Using the new 'transform' action
|
||||||
|
export FZF_CTRL_R_OPTS='--bind "enter:transform:[ -z {} ] && echo print-query || echo accept"'
|
||||||
|
```
|
||||||
|
- Added `show-header` and `hide-header` actions
|
||||||
|
- Bug fixes
|
||||||
|
|
||||||
|
0.44.1
|
||||||
|
------
|
||||||
|
- Fixed crash when preview window is hidden on `focus` event
|
||||||
|
|
||||||
|
0.44.0
|
||||||
|
------
|
||||||
|
- (Experimental) Sixel image support in preview window (not available on Windows)
|
||||||
|
- [bin/fzf-preview.sh](bin/fzf-preview.sh) is added to demonstrate how to
|
||||||
|
display an image using Kitty image protocol or Sixel. You can use it
|
||||||
|
like so:
|
||||||
|
```sh
|
||||||
|
fzf --preview='fzf-preview.sh {}'
|
||||||
|
```
|
||||||
|
- (Experimental) iTerm2 inline image protocol support in preview window (not available on Windows)
|
||||||
|
```sh
|
||||||
|
# Using https://iterm2.com/utilities/imgcat
|
||||||
|
fzf --preview 'imgcat -W $FZF_PREVIEW_COLUMNS -H $FZF_PREVIEW_LINES {}'
|
||||||
|
```
|
||||||
|
- HTTP server can be configured to accept remote connections
|
||||||
|
```sh
|
||||||
|
# FZF_API_KEY is required for a non-localhost listen address
|
||||||
|
export FZF_API_KEY="$(head -c 32 /dev/urandom | base64)"
|
||||||
|
fzf --listen 0.0.0.0:6266
|
||||||
|
```
|
||||||
|
- To allow remote process execution, use `--listen-unsafe` instead
|
||||||
|
(`execute*`, `reload*`, `become`, `preview`, `change-preview`, `transform-*`)
|
||||||
|
```sh
|
||||||
|
fzf --listen-unsafe 0.0.0.0:6266
|
||||||
|
```
|
||||||
|
- Bug fixes
|
||||||
|
|
||||||
|
0.43.0
|
||||||
|
------
|
||||||
|
- (Experimental) Added support for Kitty image protocol in the preview window
|
||||||
|
(not available on Windows)
|
||||||
|
```sh
|
||||||
|
fzf --preview='
|
||||||
|
if file --mime-type {} | grep -qF image/; then
|
||||||
|
# --transfer-mode=memory is the fastest option but if you want fzf to be able
|
||||||
|
# to redraw the image on terminal resize or on 'change-preview-window',
|
||||||
|
# you need to use --transfer-mode=stream.
|
||||||
|
kitty icat --clear --transfer-mode=memory --unicode-placeholder --stdin=no --place=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}@0x0 {} | sed \$d
|
||||||
|
else
|
||||||
|
bat --color=always {}
|
||||||
|
fi
|
||||||
|
'
|
||||||
|
```
|
||||||
|
- (Experimental) `--listen` server can report program state in JSON format (`GET /`)
|
||||||
|
```sh
|
||||||
|
# fzf server started in "headless" mode
|
||||||
|
fzf --listen 6266 2> /dev/null
|
||||||
|
|
||||||
|
# Get program state
|
||||||
|
curl localhost:6266 | jq .
|
||||||
|
|
||||||
|
# Increase the number of items returned (default: 100)
|
||||||
|
curl localhost:6266?limit=1000 | jq .
|
||||||
|
```
|
||||||
|
- `--listen` server can be secured by setting `$FZF_API_KEY` environment
|
||||||
|
variable.
|
||||||
|
```sh
|
||||||
|
export FZF_API_KEY="$(head -c 32 /dev/urandom | base64)"
|
||||||
|
|
||||||
|
# Server
|
||||||
|
fzf --listen 6266
|
||||||
|
|
||||||
|
# Client
|
||||||
|
curl localhost:6266 -H "x-api-key: $FZF_API_KEY" -d 'change-query(yo)'
|
||||||
|
```
|
||||||
|
- Added `toggle-header` action
|
||||||
|
- Added mouse events for `--bind`
|
||||||
|
- `scroll-up` (bound to `up`)
|
||||||
|
- `scroll-down` (bound to `down`)
|
||||||
|
- `shift-scroll-up` (bound to `toggle+up`)
|
||||||
|
- `shift-scroll-down` (bound to `toggle+down`)
|
||||||
|
- `shift-left-click` (bound to `toggle`)
|
||||||
|
- `shift-right-click` (bound to `toggle`)
|
||||||
|
- `preview-scroll-up` (bound to `preview-up`)
|
||||||
|
- `preview-scroll-down` (bound to `preview-down`)
|
||||||
|
```sh
|
||||||
|
# Twice faster scrolling both in the main window and the preview window
|
||||||
|
fzf --bind 'scroll-up:up+up,scroll-down:down+down' \
|
||||||
|
--bind 'preview-scroll-up:preview-up+preview-up' \
|
||||||
|
--bind 'preview-scroll-down:preview-down+preview-down' \
|
||||||
|
--preview 'cat {}'
|
||||||
|
```
|
||||||
|
- Added `offset-up` and `offset-down` actions
|
||||||
|
```sh
|
||||||
|
# Scrolling will behave similarly to CTRL-E and CTRL-Y of vim
|
||||||
|
fzf --bind scroll-up:offset-up,scroll-down:offset-down \
|
||||||
|
--bind ctrl-y:offset-up,ctrl-e:offset-down \
|
||||||
|
--scroll-off=5
|
||||||
|
```
|
||||||
|
- Shell extensions
|
||||||
|
- Updated bash completion for fzf options
|
||||||
|
- bash key bindings no longer requires perl; it will use awk or mawk
|
||||||
|
instead if perl is not found
|
||||||
|
- Basic context-aware completion for ssh command
|
||||||
|
- Applied `--scheme=path` for better ordering of the result
|
||||||
|
- Bug fixes and improvements
|
||||||
|
|
||||||
|
0.42.0
|
||||||
|
------
|
||||||
|
- Added new info style: `--info=right`
|
||||||
|
- Added new info style: `--info=inline-right`
|
||||||
|
- Added new border style `thinblock` which uses symbols for legacy computing
|
||||||
|
[one eighth block elements](https://en.wikipedia.org/wiki/Symbols_for_Legacy_Computing)
|
||||||
|
- Similarly to `block`, this style is suitable when using a different
|
||||||
|
background color because the window is completely contained within the border.
|
||||||
|
```sh
|
||||||
|
BAT_THEME=GitHub fzf --info=right --border=thinblock --preview-window=border-thinblock \
|
||||||
|
--margin=3 --scrollbar=▏▕ --preview='bat --color=always --style=numbers {}' \
|
||||||
|
--color=light,query:238,fg:238,bg:251,bg+:249,gutter:251,border:248,preview-bg:253
|
||||||
|
```
|
||||||
|
- This style may not render correctly depending on the font and the
|
||||||
|
terminal emulator.
|
||||||
|
|
||||||
0.41.1
|
0.41.1
|
||||||
------
|
------
|
||||||
- Fixed a bug where preview window is not updated when `--disabled` is set and
|
- Fixed a bug where preview window is not updated when `--disabled` is set and
|
||||||
@@ -337,7 +552,7 @@ CHANGELOG
|
|||||||
(sleep 2; seq 1000) | fzf --height ~50%
|
(sleep 2; seq 1000) | fzf --height ~50%
|
||||||
```
|
```
|
||||||
- Fixed tcell renderer used to render full-screen fzf on Windows
|
- Fixed tcell renderer used to render full-screen fzf on Windows
|
||||||
- `--no-clear` is deprecated. Use `reload` action instead.
|
- ~~`--no-clear` is deprecated. Use `reload` action instead.~~
|
||||||
|
|
||||||
0.33.0
|
0.33.0
|
||||||
------
|
------
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
FROM --platform=linux/amd64 archlinux
|
FROM --platform=linux/amd64 ubuntu:22.04
|
||||||
RUN pacman -Sy && pacman --noconfirm -S awk git tmux zsh fish ruby procps go make gcc
|
RUN apt-get update -y && apt install -y git make golang zsh fish ruby tmux
|
||||||
RUN gem install --no-document -v 5.14.2 minitest
|
RUN gem install --no-document -v 5.14.2 minitest
|
||||||
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
||||||
RUN echo '. ~/.bashrc' >> ~/.bash_profile
|
RUN echo '. ~/.bashrc' >> ~/.bash_profile
|
||||||
@@ -8,4 +8,5 @@ RUN echo '. ~/.bashrc' >> ~/.bash_profile
|
|||||||
RUN rm -f /etc/bash.bashrc
|
RUN rm -f /etc/bash.bashrc
|
||||||
COPY . /fzf
|
COPY . /fzf
|
||||||
RUN cd /fzf && make install && ./install --all
|
RUN cd /fzf && make install && ./install --all
|
||||||
|
ENV LANG C.UTF-8
|
||||||
CMD tmux new 'set -o pipefail; ruby /fzf/test/test_go.rb | tee out && touch ok' && cat out && [ -e ok ]
|
CMD tmux new 'set -o pipefail; ruby /fzf/test/test_go.rb | tee out && touch ok' && cat out && [ -e ok ]
|
||||||
|
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2023 Junegunn Choi
|
Copyright (c) 2013-2024 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
17
Makefile
17
Makefile
@@ -57,7 +57,9 @@ else ifeq ($(UNAME_M),armv6l)
|
|||||||
else ifeq ($(UNAME_M),armv7l)
|
else ifeq ($(UNAME_M),armv7l)
|
||||||
BINARY := $(BINARYARM7)
|
BINARY := $(BINARYARM7)
|
||||||
else ifeq ($(UNAME_M),armv8l)
|
else ifeq ($(UNAME_M),armv8l)
|
||||||
BINARY := $(BINARYARM8)
|
# armv8l is always 32-bit and should implement the armv7 ISA, so
|
||||||
|
# just use the same filename as for armv7.
|
||||||
|
BINARY := $(BINARYARM7)
|
||||||
else ifeq ($(UNAME_M),arm64)
|
else ifeq ($(UNAME_M),arm64)
|
||||||
BINARY := $(BINARYARM8)
|
BINARY := $(BINARYARM8)
|
||||||
else ifeq ($(UNAME_M),aarch64)
|
else ifeq ($(UNAME_M),aarch64)
|
||||||
@@ -87,10 +89,17 @@ bench:
|
|||||||
|
|
||||||
install: bin/fzf
|
install: bin/fzf
|
||||||
|
|
||||||
|
generate:
|
||||||
|
PATH=$(PATH):$(GOPATH)/bin $(GO) generate ./...
|
||||||
|
|
||||||
build:
|
build:
|
||||||
goreleaser build --rm-dist --snapshot --skip-post-hooks
|
goreleaser build --clean --snapshot --skip=post-hooks
|
||||||
|
|
||||||
release:
|
release:
|
||||||
|
# Make sure that the tests pass and the build works
|
||||||
|
TAGS=tcell make test
|
||||||
|
make test build clean
|
||||||
|
|
||||||
ifndef GITHUB_TOKEN
|
ifndef GITHUB_TOKEN
|
||||||
$(error GITHUB_TOKEN is not defined)
|
$(error GITHUB_TOKEN is not defined)
|
||||||
endif
|
endif
|
||||||
@@ -117,7 +126,7 @@ endif
|
|||||||
git push origin temp --follow-tags --force
|
git push origin temp --follow-tags --force
|
||||||
|
|
||||||
# Make a GitHub release
|
# Make a GitHub release
|
||||||
goreleaser --rm-dist --release-notes tmp/release-note
|
goreleaser --clean --release-notes tmp/release-note
|
||||||
|
|
||||||
# Push to master
|
# Push to master
|
||||||
git checkout master
|
git checkout master
|
||||||
@@ -175,4 +184,4 @@ update:
|
|||||||
$(GO) get -u
|
$(GO) get -u
|
||||||
$(GO) mod tidy
|
$(GO) mod tidy
|
||||||
|
|
||||||
.PHONY: all build release test bench install clean docker docker-test update
|
.PHONY: all generate build release test bench install clean docker docker-test update
|
||||||
|
@@ -26,6 +26,9 @@ written as:
|
|||||||
" If installed using Homebrew
|
" If installed using Homebrew
|
||||||
Plug '/usr/local/opt/fzf'
|
Plug '/usr/local/opt/fzf'
|
||||||
|
|
||||||
|
" If installed using Homebrew on Apple Silicon
|
||||||
|
Plug '/opt/homebrew/opt/fzf'
|
||||||
|
|
||||||
" If you have cloned fzf on ~/.fzf directory
|
" If you have cloned fzf on ~/.fzf directory
|
||||||
Plug '~/.fzf'
|
Plug '~/.fzf'
|
||||||
```
|
```
|
||||||
@@ -486,4 +489,4 @@ autocmd FileType fzf set laststatus=0 noshowmode noruler
|
|||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2023 Junegunn Choi
|
Copyright (c) 2013-2024 Junegunn Choi
|
||||||
|
74
bin/fzf-preview.sh
Executable file
74
bin/fzf-preview.sh
Executable file
@@ -0,0 +1,74 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# The purpose of this script is to demonstrate how to preview a file or an
|
||||||
|
# image in the preview window of fzf.
|
||||||
|
#
|
||||||
|
# Dependencies:
|
||||||
|
# - https://github.com/sharkdp/bat
|
||||||
|
# - https://github.com/hpjansson/chafa
|
||||||
|
# - https://iterm2.com/utilities/imgcat
|
||||||
|
|
||||||
|
if [[ $# -ne 1 ]]; then
|
||||||
|
>&2 echo "usage: $0 FILENAME"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
file=${1/#\~\//$HOME/}
|
||||||
|
type=$(file --dereference --mime -- "$file")
|
||||||
|
|
||||||
|
if [[ ! $type =~ image/ ]]; then
|
||||||
|
if [[ $type =~ =binary ]]; then
|
||||||
|
file "$1"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Sometimes bat is installed as batcat.
|
||||||
|
if command -v batcat > /dev/null; then
|
||||||
|
batname="batcat"
|
||||||
|
elif command -v bat > /dev/null; then
|
||||||
|
batname="bat"
|
||||||
|
else
|
||||||
|
cat "$1"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
${batname} --style="${BAT_STYLE:-numbers}" --color=always --pager=never -- "$file"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
dim=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}
|
||||||
|
if [[ $dim = x ]]; then
|
||||||
|
dim=$(stty size < /dev/tty | awk '{print $2 "x" $1}')
|
||||||
|
elif ! [[ $KITTY_WINDOW_ID ]] && (( FZF_PREVIEW_TOP + FZF_PREVIEW_LINES == $(stty size < /dev/tty | awk '{print $1}') )); then
|
||||||
|
# Avoid scrolling issue when the Sixel image touches the bottom of the screen
|
||||||
|
# * https://github.com/junegunn/fzf/issues/2544
|
||||||
|
dim=${FZF_PREVIEW_COLUMNS}x$((FZF_PREVIEW_LINES - 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 1. Use kitty icat on kitty terminal
|
||||||
|
if [[ $KITTY_WINDOW_ID ]]; then
|
||||||
|
# 1. 'memory' is the fastest option but if you want the image to be scrollable,
|
||||||
|
# you have to use 'stream'.
|
||||||
|
#
|
||||||
|
# 2. The last line of the output is the ANSI reset code without newline.
|
||||||
|
# This confuses fzf and makes it render scroll offset indicator.
|
||||||
|
# So we remove the last line and append the reset code to its previous line.
|
||||||
|
kitty icat --clear --transfer-mode=memory --unicode-placeholder --stdin=no --place="$dim@0x0" "$file" | sed '$d' | sed $'$s/$/\e[m/'
|
||||||
|
|
||||||
|
# 2. Use chafa with Sixel output
|
||||||
|
elif command -v chafa > /dev/null; then
|
||||||
|
chafa -f sixel -s "$dim" "$file"
|
||||||
|
# Add a new line character so that fzf can display multiple images in the preview window
|
||||||
|
echo
|
||||||
|
|
||||||
|
# 3. If chafa is not found but imgcat is available, use it on iTerm2
|
||||||
|
elif command -v imgcat > /dev/null; then
|
||||||
|
# NOTE: We should use https://iterm2.com/utilities/it2check to check if the
|
||||||
|
# user is running iTerm2. But for the sake of simplicity, we just assume
|
||||||
|
# that's the case here.
|
||||||
|
imgcat -W "${dim%%x*}" -H "${dim##*x}" "$file"
|
||||||
|
|
||||||
|
# 4. Cannot find any suitable method to preview the image
|
||||||
|
else
|
||||||
|
file "$file"
|
||||||
|
fi
|
16
bin/fzf-tmux
16
bin/fzf-tmux
@@ -151,12 +151,18 @@ argsf="${TMPDIR:-/tmp}/fzf-args-$id"
|
|||||||
fifo1="${TMPDIR:-/tmp}/fzf-fifo1-$id"
|
fifo1="${TMPDIR:-/tmp}/fzf-fifo1-$id"
|
||||||
fifo2="${TMPDIR:-/tmp}/fzf-fifo2-$id"
|
fifo2="${TMPDIR:-/tmp}/fzf-fifo2-$id"
|
||||||
fifo3="${TMPDIR:-/tmp}/fzf-fifo3-$id"
|
fifo3="${TMPDIR:-/tmp}/fzf-fifo3-$id"
|
||||||
tmux_win_opts=( $(tmux show-window-options remain-on-exit \; show-window-options synchronize-panes | sed '/ off/d; s/^/set-window-option /; s/$/ \\;/') )
|
if tmux_win_opts=$(tmux show-options -p remain-on-exit \; show-options -p synchronize-panes 2> /dev/null); then
|
||||||
|
tmux_win_opts=( $(sed '/ off/d; s/synchronize-panes/set-option -p synchronize-panes/; s/remain-on-exit/set-option -p remain-on-exit/; s/$/ \\;/' <<< "$tmux_win_opts") )
|
||||||
|
tmux_off_opts='; set-option -p synchronize-panes off ; set-option -p remain-on-exit off'
|
||||||
|
else
|
||||||
|
tmux_win_opts=( $(tmux show-window-options remain-on-exit \; show-window-options synchronize-panes | sed '/ off/d; s/^/set-window-option /; s/$/ \\;/') )
|
||||||
|
tmux_off_opts='; set-window-option synchronize-panes off ; set-window-option remain-on-exit off'
|
||||||
|
fi
|
||||||
cleanup() {
|
cleanup() {
|
||||||
\rm -f $argsf $fifo1 $fifo2 $fifo3
|
\rm -f $argsf $fifo1 $fifo2 $fifo3
|
||||||
|
|
||||||
# Restore tmux window options
|
# Restore tmux window options
|
||||||
if [[ "${#tmux_win_opts[@]}" -gt 0 ]]; then
|
if [[ "${#tmux_win_opts[@]}" -gt 1 ]]; then
|
||||||
eval "tmux ${tmux_win_opts[*]}"
|
eval "tmux ${tmux_win_opts[*]}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -192,6 +198,8 @@ if [[ "$opt" =~ "-E" ]]; then
|
|||||||
fi
|
fi
|
||||||
[[ -n "$FZF_DEFAULT_OPTS" ]] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
|
[[ -n "$FZF_DEFAULT_OPTS" ]] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
|
||||||
[[ -n "$FZF_DEFAULT_COMMAND" ]] && envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
|
[[ -n "$FZF_DEFAULT_COMMAND" ]] && envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
|
||||||
|
[[ -n "$RUNEWIDTH_EASTASIAN" ]] && envs="$envs RUNEWIDTH_EASTASIAN=$(printf %q "$RUNEWIDTH_EASTASIAN")"
|
||||||
|
[[ -n "$BAT_THEME" ]] && envs="$envs BAT_THEME=$(printf %q "$BAT_THEME")"
|
||||||
echo "$envs;" > "$argsf"
|
echo "$envs;" > "$argsf"
|
||||||
|
|
||||||
# Build arguments to fzf
|
# Build arguments to fzf
|
||||||
@@ -225,9 +233,9 @@ else
|
|||||||
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; echo \$? > $fifo3 $close" >> $argsf
|
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; echo \$? > $fifo3 $close" >> $argsf
|
||||||
cat <&0 > $fifo1 &
|
cat <&0 > $fifo1 &
|
||||||
fi
|
fi
|
||||||
tmux set-window-option synchronize-panes off \;\
|
tmux \
|
||||||
set-window-option remain-on-exit off \;\
|
|
||||||
split-window -c "$PWD" $opt "bash -c 'exec -a fzf bash $argsf'" $swap \
|
split-window -c "$PWD" $opt "bash -c 'exec -a fzf bash $argsf'" $swap \
|
||||||
|
$tmux_off_opts \
|
||||||
> /dev/null 2>&1 || { "$fzf" "${args[@]}"; exit $?; }
|
> /dev/null 2>&1 || { "$fzf" "${args[@]}"; exit $?; }
|
||||||
cat $fifo2
|
cat $fifo2
|
||||||
exit "$(cat $fifo3)"
|
exit "$(cat $fifo3)"
|
||||||
|
44
doc/fzf.txt
44
doc/fzf.txt
@@ -1,4 +1,4 @@
|
|||||||
fzf.txt fzf Last change: Mar 20 2023
|
fzf.txt fzf Last change: January 1 2024
|
||||||
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
|
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
|
||||||
==============================================================================
|
==============================================================================
|
||||||
|
|
||||||
@@ -32,6 +32,9 @@ depending on the package manager.
|
|||||||
" If installed using Homebrew
|
" If installed using Homebrew
|
||||||
set rtp+=/usr/local/opt/fzf
|
set rtp+=/usr/local/opt/fzf
|
||||||
|
|
||||||
|
" If installed using Homebrew on Apple Silicon
|
||||||
|
set rtp+=/opt/homebrew/opt/fzf
|
||||||
|
|
||||||
" If you have cloned fzf on ~/.fzf directory
|
" If you have cloned fzf on ~/.fzf directory
|
||||||
set rtp+=~/.fzf
|
set rtp+=~/.fzf
|
||||||
<
|
<
|
||||||
@@ -40,6 +43,9 @@ If you use {vim-plug}{1}, the same can be written as:
|
|||||||
" If installed using Homebrew
|
" If installed using Homebrew
|
||||||
Plug '/usr/local/opt/fzf'
|
Plug '/usr/local/opt/fzf'
|
||||||
|
|
||||||
|
" If installed using Homebrew on Apple Silicon
|
||||||
|
Plug '/opt/homebrew/opt/fzf'
|
||||||
|
|
||||||
" If you have cloned fzf on ~/.fzf directory
|
" If you have cloned fzf on ~/.fzf directory
|
||||||
Plug '~/.fzf'
|
Plug '~/.fzf'
|
||||||
<
|
<
|
||||||
@@ -68,16 +74,16 @@ SUMMARY *fzf-summary*
|
|||||||
The Vim plugin of fzf provides two core functions, and `:FZF` command which is
|
The Vim plugin of fzf provides two core functions, and `:FZF` command which is
|
||||||
the basic file selector command built on top of them.
|
the basic file selector command built on top of them.
|
||||||
|
|
||||||
1. `fzf#run([spec dict])`
|
1. `fzf#run([spec dict])`
|
||||||
- Starts fzf inside Vim with the given spec
|
- Starts fzf inside Vim with the given spec
|
||||||
- `:call fzf#run({'source': 'ls'})`
|
- `:call fzf#run({'source': 'ls'})`
|
||||||
2. `fzf#wrap([spec dict]) -> (dict)`
|
2. `fzf#wrap([spec dict]) -> (dict)`
|
||||||
- Takes a spec for `fzf#run` and returns an extended version of it with
|
- Takes a spec for `fzf#run` and returns an extended version of it with
|
||||||
additional options for addressing global preferences (`g:fzf_xxx`)
|
additional options for addressing global preferences (`g:fzf_xxx`)
|
||||||
- `:echo fzf#wrap({'source': 'ls'})`
|
- `:echo fzf#wrap({'source': 'ls'})`
|
||||||
- We usually wrap a spec with `fzf#wrap` before passing it to `fzf#run`
|
- We usually wrap a spec with `fzf#wrap` before passing it to `fzf#run`
|
||||||
- `:call fzf#run(fzf#wrap({'source': 'ls'}))`
|
- `:call fzf#run(fzf#wrap({'source': 'ls'}))`
|
||||||
3. `:FZF [fzf_options string] [path string]`
|
3. `:FZF [fzf_options string] [path string]`
|
||||||
- Basic fuzzy file selector
|
- Basic fuzzy file selector
|
||||||
- A reference implementation for those who don't want to write VimScript to
|
- A reference implementation for those who don't want to write VimScript to
|
||||||
implement custom commands
|
implement custom commands
|
||||||
@@ -222,12 +228,12 @@ list:
|
|||||||
`spinner` | Streaming input indicator
|
`spinner` | Streaming input indicator
|
||||||
`query` | Query string
|
`query` | Query string
|
||||||
`disabled` | Query string when search is disabled
|
`disabled` | Query string when search is disabled
|
||||||
`prompt` | Prompt before query ( `> ` )
|
`prompt` | Prompt before query ( `> ` )
|
||||||
`pointer` | Pointer to the current line ( `>` )
|
`pointer` | Pointer to the current line ( `>` )
|
||||||
----------------------------+------------------------------------------------------
|
----------------------------+------------------------------------------------------
|
||||||
- `component` specifies the component (`fg` / `bg`) from which to extract the
|
- `component` specifies the component (`fg` / `bg`) from which to extract the
|
||||||
color when considering each of the following highlight groups
|
color when considering each of the following highlight groups
|
||||||
- `group1 [, group2, ...]` is a list of highlight groups that are searched (in
|
- `group1 [, group2, ...]` is a list of highlight groups that are searched (in
|
||||||
order) for a matching color definition
|
order) for a matching color definition
|
||||||
|
|
||||||
For example, consider the following specification:
|
For example, consider the following specification:
|
||||||
@@ -268,7 +274,7 @@ as the sink.
|
|||||||
<
|
<
|
||||||
Instead of using the default find command, you can use any shell command as
|
Instead of using the default find command, you can use any shell command as
|
||||||
the source. The following example will list the files managed by git. It's
|
the source. The following example will list the files managed by git. It's
|
||||||
equivalent to running `git ls-files | fzf` on shell.
|
equivalent to running `git ls-files | fzf` on shell.
|
||||||
>
|
>
|
||||||
call fzf#run({'source': 'git ls-files', 'sink': 'e'})
|
call fzf#run({'source': 'git ls-files', 'sink': 'e'})
|
||||||
<
|
<
|
||||||
@@ -296,7 +302,7 @@ The following table summarizes the available options.
|
|||||||
---------------------------+---------------+----------------------------------------------------------------------
|
---------------------------+---------------+----------------------------------------------------------------------
|
||||||
Option name | Type | Description ~
|
Option name | Type | Description ~
|
||||||
---------------------------+---------------+----------------------------------------------------------------------
|
---------------------------+---------------+----------------------------------------------------------------------
|
||||||
`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 | Reference to function to process each selected item
|
||||||
@@ -305,8 +311,8 @@ The following table summarizes the available options.
|
|||||||
`dir` | string | Working directory
|
`dir` | string | Working directory
|
||||||
`up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` )
|
`up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` )
|
||||||
`tmux` | string | (Layout) fzf-tmux options (e.g. `-p90%,60%` )
|
`tmux` | string | (Layout) fzf-tmux options (e.g. `-p90%,60%` )
|
||||||
`window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new` )
|
`window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new` )
|
||||||
`window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width': 0.9, 'height': 0.6}` )
|
`window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width': 0.9, 'height': 0.6}` )
|
||||||
---------------------------+---------------+----------------------------------------------------------------------
|
---------------------------+---------------+----------------------------------------------------------------------
|
||||||
|
|
||||||
`options` entry can be either a string or a list. For simple cases, string
|
`options` entry can be either a string or a list. For simple cases, string
|
||||||
@@ -343,7 +349,7 @@ So how can we make our custom `fzf#run` calls also respect those variables?
|
|||||||
Simply by "wrapping" the spec dictionary with `fzf#wrap` before passing it to
|
Simply by "wrapping" the spec dictionary with `fzf#wrap` before passing it to
|
||||||
`fzf#run`.
|
`fzf#run`.
|
||||||
|
|
||||||
- `fzf#wrap([name string], [spec dict], [fullscreen bool]) -> (dict)`
|
- `fzf#wrap([name string], [spec dict], [fullscreen bool]) -> (dict)`
|
||||||
- All arguments are optional. Usually we only need to pass a spec
|
- All arguments are optional. Usually we only need to pass a spec
|
||||||
dictionary.
|
dictionary.
|
||||||
- `name` is for managing history files. It is ignored if `g:fzf_history_dir`
|
- `name` is for managing history files. It is ignored if `g:fzf_history_dir`
|
||||||
@@ -377,7 +383,7 @@ last `fullscreen` argument of `fzf#wrap` (see :help <bang>).
|
|||||||
command! -bang LS call fzf#run(fzf#wrap({'source': 'ls'}, <bang>0))
|
command! -bang LS call fzf#run(fzf#wrap({'source': 'ls'}, <bang>0))
|
||||||
<
|
<
|
||||||
Our `:LS` command will be much more useful if we can pass a directory argument
|
Our `:LS` command will be much more useful if we can pass a directory argument
|
||||||
to it, so that something like `:LS /tmp` is possible.
|
to it, so that something like `:LS /tmp` is possible.
|
||||||
>
|
>
|
||||||
command! -bang -complete=dir -nargs=? LS
|
command! -bang -complete=dir -nargs=? LS
|
||||||
\ call fzf#run(fzf#wrap({'source': 'ls', 'dir': <q-args>}, <bang>0))
|
\ call fzf#run(fzf#wrap({'source': 'ls', 'dir': <q-args>}, <bang>0))
|
||||||
@@ -396,10 +402,10 @@ unique name to our command and pass it as the first argument to `fzf#wrap`.
|
|||||||
|
|
||||||
- `g:fzf_layout`
|
- `g:fzf_layout`
|
||||||
- `g:fzf_action`
|
- `g:fzf_action`
|
||||||
- Works only when no custom `sink` (or `sink*`) is provided
|
- Works only when no custom `sink` (or `sinklist`) is provided
|
||||||
- Having custom sink usually means that each entry is not an ordinary
|
- Having custom sink usually means that each entry is not an ordinary
|
||||||
file path (e.g. name of color scheme), so we can't blindly apply the
|
file path (e.g. name of color scheme), so we can't blindly apply the
|
||||||
same strategy (i.e. `tabedit some-color-scheme` doesn't make sense)
|
same strategy (i.e. `tabedit some-color-scheme` doesn't make sense)
|
||||||
- `g:fzf_colors`
|
- `g:fzf_colors`
|
||||||
- `g:fzf_history_dir`
|
- `g:fzf_history_dir`
|
||||||
|
|
||||||
@@ -488,7 +494,7 @@ or above) by putting fzf-tmux options in `tmux` key.
|
|||||||
*fzf-hide-statusline*
|
*fzf-hide-statusline*
|
||||||
|
|
||||||
When fzf starts in a terminal buffer, the file type of the buffer is set to
|
When fzf starts in a terminal buffer, the file type of the buffer is set to
|
||||||
`fzf`. So you can set up `FileType fzf` autocmd to customize the settings of
|
`fzf`. So you can set up `FileType fzf` autocmd to customize the settings of
|
||||||
the window.
|
the window.
|
||||||
|
|
||||||
For example, if you open fzf on the bottom on the screen (e.g. `{'down':
|
For example, if you open fzf on the bottom on the screen (e.g. `{'down':
|
||||||
@@ -506,7 +512,7 @@ LICENSE *fzf-license*
|
|||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2023 Junegunn Choi
|
Copyright (c) 2013-2024 Junegunn Choi
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap:
|
vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap:
|
||||||
|
14
go.mod
14
go.mod
@@ -1,21 +1,21 @@
|
|||||||
module github.com/junegunn/fzf
|
module github.com/junegunn/fzf
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gdamore/tcell/v2 v2.5.4
|
github.com/gdamore/tcell/v2 v2.7.0
|
||||||
github.com/mattn/go-isatty v0.0.17
|
github.com/mattn/go-isatty v0.0.17
|
||||||
github.com/mattn/go-runewidth v0.0.14
|
|
||||||
github.com/mattn/go-shellwords v1.0.12
|
github.com/mattn/go-shellwords v1.0.12
|
||||||
github.com/rivo/uniseg v0.4.4
|
github.com/rivo/uniseg v0.4.6
|
||||||
github.com/saracen/walker v0.1.3
|
github.com/saracen/walker v0.1.3
|
||||||
golang.org/x/sys v0.8.0
|
golang.org/x/sys v0.16.0
|
||||||
golang.org/x/term v0.8.0
|
golang.org/x/term v0.16.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gdamore/encoding v1.0.0 // indirect
|
github.com/gdamore/encoding v1.0.0 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
|
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||||
golang.org/x/text v0.5.0 // indirect
|
golang.org/x/sync v0.5.0 // indirect
|
||||||
|
golang.org/x/text v0.14.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.17
|
go 1.17
|
||||||
|
37
go.sum
37
go.sum
@@ -1,49 +1,60 @@
|
|||||||
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.5.4 h1:TGU4tSjD3sCL788vFNeJnTdzpNKIw1H5dgLnJRQVv/k=
|
github.com/gdamore/tcell/v2 v2.7.0 h1:I5LiGTQuwrysAt1KS9wg1yFfOI3arI3ucFrxtd/xqaA=
|
||||||
github.com/gdamore/tcell/v2 v2.5.4/go.mod h1:dZgRy5v4iMobMEcWNYBtREnDZAT9DYmfqIkrgEMxLyw=
|
github.com/gdamore/tcell/v2 v2.7.0/go.mod h1:hl/KtAANGBecfIPxk+FzKvThTqI84oplgbPEmVX60b8=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
||||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg=
|
||||||
|
github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/saracen/walker v0.1.3 h1:YtcKKmpRPy6XJTHJ75J2QYXXZYWnZNQxPCVqZSHVV/g=
|
github.com/saracen/walker v0.1.3 h1:YtcKKmpRPy6XJTHJ75J2QYXXZYWnZNQxPCVqZSHVV/g=
|
||||||
github.com/saracen/walker v0.1.3/go.mod h1:FU+7qU8DeQQgSZDmmThMJi93kPkLFgy0oVAcLxurjIk=
|
github.com/saracen/walker v0.1.3/go.mod h1:FU+7qU8DeQQgSZDmmThMJi93kPkLFgy0oVAcLxurjIk=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||||
|
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||||
|
golang.org/x/sys v0.16.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.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||||
|
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
|
||||||
|
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||||
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=
|
||||||
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
8
install
8
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.41.1
|
version=0.46.1
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
@@ -196,12 +196,12 @@ if [ -n "$binary_error" ]; then
|
|||||||
echo " - $binary_error !!!"
|
echo " - $binary_error !!!"
|
||||||
fi
|
fi
|
||||||
if command -v go > /dev/null; then
|
if command -v go > /dev/null; then
|
||||||
echo -n "Building binary (go get -u github.com/junegunn/fzf) ... "
|
echo -n "Building binary (go install github.com/junegunn/fzf) ... "
|
||||||
if [ -z "${GOPATH-}" ]; then
|
if [ -z "${GOPATH-}" ]; then
|
||||||
export GOPATH="${TMPDIR:-/tmp}/fzf-gopath"
|
export GOPATH="${TMPDIR:-/tmp}/fzf-gopath"
|
||||||
mkdir -p "$GOPATH"
|
mkdir -p "$GOPATH"
|
||||||
fi
|
fi
|
||||||
if go get -ldflags "-s -w -X main.version=$version -X main.revision=go-get" github.com/junegunn/fzf; then
|
if go install -ldflags "-s -w -X main.version=$version -X main.revision=go-install" github.com/junegunn/fzf; then
|
||||||
echo "OK"
|
echo "OK"
|
||||||
cp "$GOPATH/bin/fzf" "$fzf_base/bin/"
|
cp "$GOPATH/bin/fzf" "$fzf_base/bin/"
|
||||||
else
|
else
|
||||||
@@ -245,7 +245,7 @@ for shell in $shells; do
|
|||||||
src=${prefix_expand}.${shell}
|
src=${prefix_expand}.${shell}
|
||||||
echo -n "Generate $src ... "
|
echo -n "Generate $src ... "
|
||||||
|
|
||||||
fzf_completion="[[ \$- == *i* ]] && source \"$fzf_base/shell/completion.${shell}\" 2> /dev/null"
|
fzf_completion="source \"$fzf_base/shell/completion.${shell}\""
|
||||||
if [ $auto_completion -eq 0 ]; then
|
if [ $auto_completion -eq 0 ]; then
|
||||||
fzf_completion="# $fzf_completion"
|
fzf_completion="# $fzf_completion"
|
||||||
fi
|
fi
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
$version="0.41.1"
|
$version="0.46.1"
|
||||||
|
|
||||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
|
||||||
|
2
main.go
2
main.go
@@ -5,7 +5,7 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/protector"
|
"github.com/junegunn/fzf/src/protector"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "0.41"
|
var version string = "0.46"
|
||||||
var revision string = "devel"
|
var revision string = "devel"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
.ig
|
.ig
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2023 Junegunn Choi
|
Copyright (c) 2013-2024 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf-tmux 1 "May 2023" "fzf 0.41.1" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf-tmux 1 "Feb 2024" "fzf 0.46.1" "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
|
||||||
|
217
man/man1/fzf.1
217
man/man1/fzf.1
@@ -1,7 +1,7 @@
|
|||||||
.ig
|
.ig
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2023 Junegunn Choi
|
Copyright (c) 2013-2024 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf 1 "May 2023" "fzf 0.41.1" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Feb 2024" "fzf 0.46.1" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -57,9 +57,9 @@ Choose scoring scheme tailored for different types of input.
|
|||||||
.br
|
.br
|
||||||
.BR default " Generic scoring scheme designed to work well with any type of input"
|
.BR default " Generic scoring scheme designed to work well with any type of input"
|
||||||
.br
|
.br
|
||||||
.BR path " Scoring scheme for paths (additional bonus point only after path separator)
|
.BR path " Scoring scheme well suited for file paths
|
||||||
.br
|
.br
|
||||||
.BR history " Scoring scheme for command history (no additional bonus points).
|
.BR history " Scoring scheme well suited for command history or any input where chronological ordering is important
|
||||||
Sets \fB--tiebreak=index\fR as well.
|
Sets \fB--tiebreak=index\fR as well.
|
||||||
.br
|
.br
|
||||||
.TP
|
.TP
|
||||||
@@ -192,9 +192,21 @@ Label characters for \fBjump\fR and \fBjump-accept\fR
|
|||||||
.TP
|
.TP
|
||||||
.BI "--height=" "[~]HEIGHT[%]"
|
.BI "--height=" "[~]HEIGHT[%]"
|
||||||
Display fzf window below the cursor with the given height instead of using
|
Display fzf window below the cursor with the given height instead of using
|
||||||
the full screen. When prefixed with \fB~\fR, fzf will automatically determine
|
the full screen.
|
||||||
the height in the range according to the input size. Note that adaptive height
|
|
||||||
is not compatible with top/bottom margin and padding given in percent size.
|
If a negative value is specified, the height is calculated as the terminal
|
||||||
|
height minus the given value.
|
||||||
|
|
||||||
|
fzf --height=-1
|
||||||
|
|
||||||
|
When prefixed with \fB~\fR, fzf will automatically determine the height in the
|
||||||
|
range according to the input size. Note that adaptive height is not compatible
|
||||||
|
with top/bottom margin and padding given in percent size. It is also not
|
||||||
|
compatible with a negative height value.
|
||||||
|
|
||||||
|
# Will not take up 100% of the screen
|
||||||
|
seq 5 | fzf --height=~100%
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "--min-height=" "HEIGHT"
|
.BI "--min-height=" "HEIGHT"
|
||||||
Minimum height when \fB--height\fR is given in percent (default: 10).
|
Minimum height when \fB--height\fR is given in percent (default: 10).
|
||||||
@@ -228,6 +240,10 @@ Draw border around the finder
|
|||||||
.br
|
.br
|
||||||
.BR double " Border with double lines"
|
.BR double " Border with double lines"
|
||||||
.br
|
.br
|
||||||
|
.BR block " Border using block elements; suitable when using different background colors"
|
||||||
|
.br
|
||||||
|
.BR thinblock " Border using legacy computing symbols; may not be displayed on some terminals"
|
||||||
|
.br
|
||||||
.BR horizontal " Horizontal lines above and below the finder"
|
.BR horizontal " Horizontal lines above and below the finder"
|
||||||
.br
|
.br
|
||||||
.BR vertical " Vertical lines on each side of the finder"
|
.BR vertical " Vertical lines on each side of the finder"
|
||||||
@@ -244,9 +260,8 @@ Draw border around the finder
|
|||||||
.br
|
.br
|
||||||
|
|
||||||
If you use a terminal emulator where each box-drawing character takes
|
If you use a terminal emulator where each box-drawing character takes
|
||||||
2 columns, try setting \fBRUNEWIDTH_EASTASIAN\fR environment variable to
|
2 columns, try setting \fB--ambidouble\fR. If the border is still not properly
|
||||||
\fB0\fR or \fB1\fR. If the border is still not properly rendered, set
|
rendered, set \fB--no-unicode\fR.
|
||||||
\fB--no-unicode\fR.
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "--border-label" [=LABEL]
|
.BI "--border-label" [=LABEL]
|
||||||
@@ -297,6 +312,11 @@ the label. Label is printed on the top border line by default, add
|
|||||||
Use ASCII characters instead of Unicode drawing characters to draw borders,
|
Use ASCII characters instead of Unicode drawing characters to draw borders,
|
||||||
the spinner and the horizontal separator.
|
the spinner and the horizontal separator.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B "--ambidouble"
|
||||||
|
Set this option if your terminal displays ambiguous width characters (e.g.
|
||||||
|
box-drawing characters for borders) as 2 columns.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "--margin=" MARGIN
|
.BI "--margin=" MARGIN
|
||||||
Comma-separated expression for margins around the finder.
|
Comma-separated expression for margins around the finder.
|
||||||
@@ -353,10 +373,14 @@ Determines the display style of finder info (match counters).
|
|||||||
.br
|
.br
|
||||||
.BR default " Display on the next line to the prompt"
|
.BR default " Display on the next line to the prompt"
|
||||||
.br
|
.br
|
||||||
|
.BR right " Display on the right end of the next line to the prompt"
|
||||||
|
.br
|
||||||
.BR inline " Display on the same line with the default separator ' < '"
|
.BR inline " Display on the same line with the default separator ' < '"
|
||||||
.br
|
.br
|
||||||
.BR inline:SEPARATOR " Display on the same line with a non-default separator"
|
.BR inline:SEPARATOR " Display on the same line with a non-default separator"
|
||||||
.br
|
.br
|
||||||
|
.BR inline-right " Display on the right end of the same line
|
||||||
|
.br
|
||||||
.BR hidden " Do not display finder info"
|
.BR hidden " Do not display finder info"
|
||||||
.br
|
.br
|
||||||
|
|
||||||
@@ -540,6 +564,9 @@ they represent the exact size of the preview window. (It also overrides
|
|||||||
by the default shell, so prefer to refer to the ones with \fBFZF_PREVIEW_\fR
|
by the default shell, so prefer to refer to the ones with \fBFZF_PREVIEW_\fR
|
||||||
prefix.)
|
prefix.)
|
||||||
|
|
||||||
|
fzf also exports \fB$FZF_PREVIEW_TOP\fR and \fB$FZF_PREVIEW_LEFT\fR so that
|
||||||
|
the preview command can determine the position of the preview window.
|
||||||
|
|
||||||
A placeholder expression starting with \fB+\fR flag will be replaced to the
|
A placeholder expression starting with \fB+\fR flag will be replaced to the
|
||||||
space-separated list of the selected lines (or the current line if no selection
|
space-separated list of the selected lines (or the current line if no selection
|
||||||
was made) individually quoted.
|
was made) individually quoted.
|
||||||
@@ -551,10 +578,6 @@ e.g.
|
|||||||
When using a field index expression, leading and trailing whitespace is stripped
|
When using a field index expression, leading and trailing whitespace is stripped
|
||||||
from the replacement string. To preserve the whitespace, use the \fBs\fR flag.
|
from the replacement string. To preserve the whitespace, use the \fBs\fR flag.
|
||||||
|
|
||||||
Also, \fB{q}\fR is replaced to the current query string, and \fB{n}\fR is
|
|
||||||
replaced to zero-based ordinal index of the line. Use \fB{+n}\fR if you want
|
|
||||||
all index numbers when multiple lines are selected.
|
|
||||||
|
|
||||||
A placeholder expression with \fBf\fR flag is replaced to the path of
|
A placeholder expression with \fBf\fR flag is replaced to the path of
|
||||||
a temporary file that holds the evaluated list. This is useful when you
|
a temporary file that holds the evaluated list. This is useful when you
|
||||||
multi-select a large number of items and the length of the evaluated string may
|
multi-select a large number of items and the length of the evaluated string may
|
||||||
@@ -566,6 +589,14 @@ e.g.
|
|||||||
seq 100000 | fzf --multi --bind ctrl-a:select-all \\
|
seq 100000 | fzf --multi --bind ctrl-a:select-all \\
|
||||||
--preview "awk '{sum+=\\$1} END {print sum}' {+f}"\fR
|
--preview "awk '{sum+=\\$1} END {print sum}' {+f}"\fR
|
||||||
|
|
||||||
|
Also,
|
||||||
|
|
||||||
|
* \fB{q}\fR (or \fB{fzf:query}\fR) is replaced to the current query string
|
||||||
|
.br
|
||||||
|
* \fB{n}\fR is replaced to the zero-based ordinal index of the current item.
|
||||||
|
Use \fB{+n}\fR if you want all index numbers when multiple lines are selected.
|
||||||
|
.br
|
||||||
|
|
||||||
Note that you can escape a placeholder pattern by prepending a backslash.
|
Note that you can escape a placeholder pattern by prepending a backslash.
|
||||||
|
|
||||||
Preview window will be updated even when there is no match for the current
|
Preview window will be updated even when there is no match for the current
|
||||||
@@ -583,6 +614,14 @@ e.g.
|
|||||||
echo "$i"
|
echo "$i"
|
||||||
sleep 0.01
|
sleep 0.01
|
||||||
done'\fR
|
done'\fR
|
||||||
|
|
||||||
|
fzf has experimental support for Kitty graphics protocol and Sixel graphics.
|
||||||
|
The following example uses https://github.com/junegunn/fzf/blob/master/bin/fzf-preview.sh
|
||||||
|
script to render an image using either of the protocols inside the preview window.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fBfzf --preview='fzf-preview.sh {}'
|
||||||
|
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
@@ -599,6 +638,10 @@ Should be used with one of the following \fB--preview-window\fR options.
|
|||||||
.br
|
.br
|
||||||
.B * border-double
|
.B * border-double
|
||||||
.br
|
.br
|
||||||
|
.B * border-block
|
||||||
|
.br
|
||||||
|
.B * border-thinblock
|
||||||
|
.br
|
||||||
.B * border-horizontal
|
.B * border-horizontal
|
||||||
.br
|
.br
|
||||||
.B * border-top
|
.B * border-top
|
||||||
@@ -746,6 +789,21 @@ Read input delimited by ASCII NUL characters instead of newline characters
|
|||||||
.TP
|
.TP
|
||||||
.B "--print0"
|
.B "--print0"
|
||||||
Print output delimited by ASCII NUL characters instead of newline characters
|
Print output delimited by ASCII NUL characters instead of newline characters
|
||||||
|
.TP
|
||||||
|
.B "--no-clear"
|
||||||
|
Do not clear finder interface on exit. If fzf was started in full screen mode,
|
||||||
|
it will not switch back to the original screen, so you'll have to manually run
|
||||||
|
\fBtput rmcup\fR to return. This option can be used to avoid flickering of the
|
||||||
|
screen when your application needs to start fzf multiple times in order. (Note
|
||||||
|
that in most cases, it is preferable to use \fBreload\fR action instead.)
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fBfoo=$(seq 100 | fzf --no-clear) || (
|
||||||
|
# Need to manually switch back to the main screen when cancelled
|
||||||
|
tput rmcup
|
||||||
|
exit 1
|
||||||
|
) && seq "$foo" 100 | fzf
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B "--sync"
|
.B "--sync"
|
||||||
Synchronous search for multi-staged filtering. If specified, fzf will launch
|
Synchronous search for multi-staged filtering. If specified, fzf will launch
|
||||||
@@ -755,12 +813,19 @@ ncurses finder only after the input stream is complete.
|
|||||||
e.g. \fBfzf --multi | fzf --sync\fR
|
e.g. \fBfzf --multi | fzf --sync\fR
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.B "--listen[=HTTP_PORT]"
|
.B "--listen[=[ADDR:]PORT]" "--listen-unsafe[=[ADDR:]PORT]"
|
||||||
Start HTTP server on the given port. It allows external processes to send
|
Start HTTP server and listen on the given address. It allows external processes
|
||||||
actions to perform via POST method. If the port number is omitted or given as
|
to send actions to perform via POST method.
|
||||||
0, fzf will choose the port automatically and export it as \fBFZF_PORT\fR
|
|
||||||
environment variable to the child processes started via \fBexecute\fR and
|
- If the port number is omitted or given as 0, fzf will automatically choose
|
||||||
\fBexecute-silent\fR actions.
|
a port and export it as \fBFZF_PORT\fR environment variable to the child processes
|
||||||
|
|
||||||
|
- If \fBFZF_API_KEY\fR environment variable is set, the server would require
|
||||||
|
sending an API key with the same value in the \fBx-api-key\fR HTTP header
|
||||||
|
|
||||||
|
- \fBFZF_API_KEY\fR is required for a non-localhost listen address
|
||||||
|
|
||||||
|
- To allow remote process execution, use \fB--listen-unsafe\fR
|
||||||
|
|
||||||
e.g.
|
e.g.
|
||||||
\fB# Start HTTP server on port 6266
|
\fB# Start HTTP server on port 6266
|
||||||
@@ -769,6 +834,19 @@ e.g.
|
|||||||
# Send action to the server
|
# Send action to the server
|
||||||
curl -XPOST localhost:6266 -d 'reload(seq 100)+change-prompt(hundred> )'
|
curl -XPOST localhost:6266 -d 'reload(seq 100)+change-prompt(hundred> )'
|
||||||
|
|
||||||
|
# Get program state in JSON format (experimental)
|
||||||
|
# * Make sure NOT to access this endpoint from execute/transform actions
|
||||||
|
# as it will result in a timeout
|
||||||
|
curl localhost:6266
|
||||||
|
|
||||||
|
# Start HTTP server on port 6266 with remote connections allowed
|
||||||
|
# * Listening on non-localhost address requires using an API key
|
||||||
|
export FZF_API_KEY="$(head -c 32 /dev/urandom | base64)"
|
||||||
|
fzf --listen 0.0.0.0:6266
|
||||||
|
|
||||||
|
# Send an authenticated action
|
||||||
|
curl -XPOST localhost:6266 -H "x-api-key: $FZF_API_KEY" -d 'change-query(yo)'
|
||||||
|
|
||||||
# Choose port automatically and export it as $FZF_PORT to the child process
|
# Choose port automatically and export it as $FZF_PORT to the child process
|
||||||
fzf --listen --bind 'start:execute-silent:echo $FZF_PORT > /tmp/fzf-port'
|
fzf --listen --bind 'start:execute-silent:echo $FZF_PORT > /tmp/fzf-port'
|
||||||
\fR
|
\fR
|
||||||
@@ -788,6 +866,11 @@ this case make sure that the command is POSIX-compliant.
|
|||||||
.TP
|
.TP
|
||||||
.B FZF_DEFAULT_OPTS
|
.B FZF_DEFAULT_OPTS
|
||||||
Default options. e.g. \fBexport FZF_DEFAULT_OPTS="--extended --cycle"\fR
|
Default options. e.g. \fBexport FZF_DEFAULT_OPTS="--extended --cycle"\fR
|
||||||
|
.TP
|
||||||
|
.B FZF_API_KEY
|
||||||
|
Can be used to require an API key when using \fB--listen\fR option. If not set,
|
||||||
|
no authentication will be required by the server. You can set this value if
|
||||||
|
you need to protect against DNS rebinding and privilege escalation attacks.
|
||||||
|
|
||||||
.SH EXIT STATUS
|
.SH EXIT STATUS
|
||||||
.BR 0 " Normal exit"
|
.BR 0 " Normal exit"
|
||||||
@@ -822,6 +905,39 @@ of field index expressions.
|
|||||||
.BR .. " All the fields"
|
.BR .. " All the fields"
|
||||||
.br
|
.br
|
||||||
|
|
||||||
|
.SH ENVIRONMENT VARIABLES EXPORTED TO CHILD PROCESSES
|
||||||
|
|
||||||
|
fzf exports the following environment variables to its child processes.
|
||||||
|
|
||||||
|
.BR FZF_LINES " Number of lines fzf takes up excluding padding and margin"
|
||||||
|
.br
|
||||||
|
.BR FZF_COLUMNS " Number of columns fzf takes up excluding padding and margin"
|
||||||
|
.br
|
||||||
|
.BR FZF_TOTAL_COUNT " Total number of items"
|
||||||
|
.br
|
||||||
|
.BR FZF_MATCH_COUNT " Number of matched items"
|
||||||
|
.br
|
||||||
|
.BR FZF_SELECT_COUNT " Number of selected items"
|
||||||
|
.br
|
||||||
|
.BR FZF_QUERY " Current query string"
|
||||||
|
.br
|
||||||
|
.BR FZF_PROMPT " Prompt string"
|
||||||
|
.br
|
||||||
|
.BR FZF_ACTION " The name of the last action performed"
|
||||||
|
.br
|
||||||
|
.BR FZF_PORT " Port number when --listen option is used"
|
||||||
|
.br
|
||||||
|
|
||||||
|
The following variables are additionally exported to the preview commands.
|
||||||
|
|
||||||
|
.BR FZF_PREVIEW_TOP " Top position of the preview window"
|
||||||
|
.br
|
||||||
|
.BR FZF_PREVIEW_LEFT " Left position of the preview window"
|
||||||
|
.br
|
||||||
|
.BR FZF_PREVIEW_LINES " Number of lines in the preview window"
|
||||||
|
.br
|
||||||
|
.BR FZF_PREVIEW_COLUMNS " Number of columns in the preview window"
|
||||||
|
|
||||||
.SH EXTENDED SEARCH MODE
|
.SH EXTENDED SEARCH MODE
|
||||||
|
|
||||||
Unless specified otherwise, fzf will start in "extended-search mode". In this
|
Unless specified otherwise, fzf will start in "extended-search mode". In this
|
||||||
@@ -959,6 +1075,22 @@ e.g.
|
|||||||
.br
|
.br
|
||||||
\fIdouble-click\fR
|
\fIdouble-click\fR
|
||||||
.br
|
.br
|
||||||
|
\fIscroll-up\fR
|
||||||
|
.br
|
||||||
|
\fIscroll-down\fR
|
||||||
|
.br
|
||||||
|
\fIpreview-scroll-up\fR
|
||||||
|
.br
|
||||||
|
\fIpreview-scroll-down\fR
|
||||||
|
.br
|
||||||
|
\fIshift-left-click\fR
|
||||||
|
.br
|
||||||
|
\fIshift-right-click\fR
|
||||||
|
.br
|
||||||
|
\fIshift-scroll-up\fR
|
||||||
|
.br
|
||||||
|
\fIshift-scroll-down\fR
|
||||||
|
.br
|
||||||
or any single character
|
or any single character
|
||||||
|
|
||||||
.SS AVAILABLE EVENTS:
|
.SS AVAILABLE EVENTS:
|
||||||
@@ -980,6 +1112,22 @@ e.g.
|
|||||||
\fB# Change the prompt to "loaded" when the input stream is complete
|
\fB# Change the prompt to "loaded" when the input stream is complete
|
||||||
(seq 10; sleep 1; seq 11 20) | fzf --prompt 'Loading> ' --bind 'load:change-prompt:Loaded> '\fR
|
(seq 10; sleep 1; seq 11 20) | fzf --prompt 'Loading> ' --bind 'load:change-prompt:Loaded> '\fR
|
||||||
.RE
|
.RE
|
||||||
|
\fIresize\fR
|
||||||
|
.RS
|
||||||
|
Triggered when the terminal size is changed.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fBfzf --bind 'resize:transform-header:echo Resized: ${FZF_COLUMNS}x${FZF_LINES}'\fR
|
||||||
|
.RE
|
||||||
|
\fIresult\fR
|
||||||
|
.RS
|
||||||
|
Triggered when the filtering for the current query is complete and the result list is ready.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fB# Put the cursor on the second item when the query string is empty
|
||||||
|
# * Note that you can't use 'change' event in this case because the second position may not be available
|
||||||
|
fzf --sync --bind 'result:transform:[[ -z {fzf:query} ]] && echo "pos(2)"'\fR
|
||||||
|
.RE
|
||||||
\fIchange\fR
|
\fIchange\fR
|
||||||
.RS
|
.RS
|
||||||
Triggered whenever the query string is changed
|
Triggered whenever the query string is changed
|
||||||
@@ -1043,6 +1191,7 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
|
\fBabort\fR \fIctrl-c ctrl-g ctrl-q esc\fR
|
||||||
\fBaccept\fR \fIenter double-click\fR
|
\fBaccept\fR \fIenter double-click\fR
|
||||||
\fBaccept-non-empty\fR (same as \fBaccept\fR except that it prevents fzf from exiting without selection)
|
\fBaccept-non-empty\fR (same as \fBaccept\fR except that it prevents fzf from exiting without selection)
|
||||||
|
\fBaccept-or-print-query\fR (same as \fBaccept\fR except that it prints the query when there's no match)
|
||||||
\fBbackward-char\fR \fIctrl-b left\fR
|
\fBbackward-char\fR \fIctrl-b left\fR
|
||||||
\fBbackward-delete-char\fR \fIctrl-h bspace\fR
|
\fBbackward-delete-char\fR \fIctrl-h bspace\fR
|
||||||
\fBbackward-delete-char/eof\fR (same as \fBbackward-delete-char\fR except aborts fzf if query is empty)
|
\fBbackward-delete-char/eof\fR (same as \fBbackward-delete-char\fR except aborts fzf if query is empty)
|
||||||
@@ -1087,7 +1236,10 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBpage-up\fR \fIpgup\fR
|
\fBpage-up\fR \fIpgup\fR
|
||||||
\fBhalf-page-down\fR
|
\fBhalf-page-down\fR
|
||||||
\fBhalf-page-up\fR
|
\fBhalf-page-up\fR
|
||||||
|
\fBhide-header\fR
|
||||||
\fBhide-preview\fR
|
\fBhide-preview\fR
|
||||||
|
\fBoffset-down\fR (similar to CTRL-E of Vim)
|
||||||
|
\fBoffset-up\fR (similar to CTRL-Y of Vim)
|
||||||
\fBpos(...)\fR (move cursor to the numeric position; negative number to count from the end)
|
\fBpos(...)\fR (move cursor to the numeric position; negative number to count from the end)
|
||||||
\fBprev-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
\fBprev-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
||||||
\fBprev-selected\fR (move to the previous selected item)
|
\fBprev-selected\fR (move to the previous selected item)
|
||||||
@@ -1110,10 +1262,12 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBreplace-query\fR (replace query string with the current selection)
|
\fBreplace-query\fR (replace query string with the current selection)
|
||||||
\fBselect\fR
|
\fBselect\fR
|
||||||
\fBselect-all\fR (select all matches)
|
\fBselect-all\fR (select all matches)
|
||||||
|
\fBshow-header\fR
|
||||||
\fBshow-preview\fR
|
\fBshow-preview\fR
|
||||||
\fBtoggle\fR (\fIright-click\fR)
|
\fBtoggle\fR (\fIright-click\fR)
|
||||||
\fBtoggle-all\fR (toggle all matches)
|
\fBtoggle-all\fR (toggle all matches)
|
||||||
\fBtoggle+down\fR \fIctrl-i (tab)\fR
|
\fBtoggle+down\fR \fIctrl-i (tab)\fR
|
||||||
|
\fBtoggle-header\fR
|
||||||
\fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
\fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
||||||
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
||||||
\fBtoggle-preview\fR
|
\fBtoggle-preview\fR
|
||||||
@@ -1123,6 +1277,7 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBtoggle-track\fR
|
\fBtoggle-track\fR
|
||||||
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
||||||
\fBtrack\fR (track the current item; automatically disabled if focus changes)
|
\fBtrack\fR (track the current item; automatically disabled if focus changes)
|
||||||
|
\fBtransform(...)\fR (transform states using the output of an external command)
|
||||||
\fBtransform-border-label(...)\fR (transform border label using an external command)
|
\fBtransform-border-label(...)\fR (transform border label using an external command)
|
||||||
\fBtransform-header(...)\fR (transform header using an external command)
|
\fBtransform-header(...)\fR (transform header using an external command)
|
||||||
\fBtransform-preview-label(...)\fR (transform preview label using an external command)
|
\fBtransform-preview-label(...)\fR (transform preview label using an external command)
|
||||||
@@ -1235,6 +1390,28 @@ e.g.
|
|||||||
\fB# You can still filter and select entries from the initial list for 3 seconds
|
\fB# You can still filter and select entries from the initial list for 3 seconds
|
||||||
seq 100 | fzf --bind 'load:reload-sync(sleep 3; seq 1000)+unbind(load)'\fR
|
seq 100 | fzf --bind 'load:reload-sync(sleep 3; seq 1000)+unbind(load)'\fR
|
||||||
|
|
||||||
|
.SS TRANSFORM ACTIONS
|
||||||
|
|
||||||
|
Actions with \fBtransform-\fR prefix are used to transform the states of fzf
|
||||||
|
using the output of an external command. The output of these commands are
|
||||||
|
expected to be a single line of text.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fBfzf --bind 'focus:transform-header:file --brief {}'\fR
|
||||||
|
|
||||||
|
\fBtransform(...)\fR action runs an external command that should print a series
|
||||||
|
of actions to be performed. The output should be in the same format as the
|
||||||
|
payload of HTTP POST request to the \fB--listen\fR server.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fB# Disallow selecting an empty line
|
||||||
|
echo -e "1. Hello\\n2. Goodbye\\n\\n3. Exit" |
|
||||||
|
fzf --height '~100%' --reverse --header 'Select one' \\
|
||||||
|
--bind 'enter:transform:[[ -n {} ]] &&
|
||||||
|
echo accept ||
|
||||||
|
echo "change-header:Invalid selection"'
|
||||||
|
\fR
|
||||||
|
|
||||||
.SS PREVIEW BINDING
|
.SS PREVIEW BINDING
|
||||||
|
|
||||||
With \fBpreview(...)\fR action, you can specify multiple different preview
|
With \fBpreview(...)\fR action, you can specify multiple different preview
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
" Copyright (c) 2013-2023 Junegunn Choi
|
" Copyright (c) 2013-2024 Junegunn Choi
|
||||||
"
|
"
|
||||||
" MIT License
|
" MIT License
|
||||||
"
|
"
|
||||||
@@ -456,6 +456,30 @@ function! s:writefile(...)
|
|||||||
endif
|
endif
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
function! s:extract_option(opts, name)
|
||||||
|
let opt = ''
|
||||||
|
let expect = 0
|
||||||
|
" There are a few cases where this function doesn't work as expected.
|
||||||
|
" Let's just assume such cases are extremely unlikely in real world.
|
||||||
|
" e.g. --query --border
|
||||||
|
for word in split(a:opts)
|
||||||
|
if expect && word !~ '^"\=-'
|
||||||
|
let opt = opt . ' ' . word
|
||||||
|
let expect = 0
|
||||||
|
elseif word == '--no-'.a:name
|
||||||
|
let opt = ''
|
||||||
|
elseif word =~ '^--'.a:name.'='
|
||||||
|
let opt = word
|
||||||
|
elseif word =~ '^--'.a:name.'$'
|
||||||
|
let opt = word
|
||||||
|
let expect = 1
|
||||||
|
elseif expect
|
||||||
|
let expect = 0
|
||||||
|
endif
|
||||||
|
endfor
|
||||||
|
return opt
|
||||||
|
endfunction
|
||||||
|
|
||||||
function! fzf#run(...) abort
|
function! fzf#run(...) abort
|
||||||
try
|
try
|
||||||
let [shell, shellslash, shellcmdflag, shellxquote] = s:use_sh()
|
let [shell, shellslash, shellcmdflag, shellxquote] = s:use_sh()
|
||||||
@@ -511,8 +535,8 @@ try
|
|||||||
let height = s:calc_size(&lines, dict.down, dict)
|
let height = s:calc_size(&lines, dict.down, dict)
|
||||||
let optstr .= ' --height='.height
|
let optstr .= ' --height='.height
|
||||||
endif
|
endif
|
||||||
" Respect --border option given in 'options'
|
" Respect --border option given in $FZF_DEFAULT_OPTS and 'options'
|
||||||
let optstr = join([s:border_opt(get(dict, 'window', 0)), optstr])
|
let optstr = join([s:border_opt(get(dict, 'window', 0)), s:extract_option($FZF_DEFAULT_OPTS, 'border'), optstr])
|
||||||
let prev_default_command = $FZF_DEFAULT_COMMAND
|
let prev_default_command = $FZF_DEFAULT_COMMAND
|
||||||
if len(source_command)
|
if len(source_command)
|
||||||
let $FZF_DEFAULT_COMMAND = source_command
|
let $FZF_DEFAULT_COMMAND = source_command
|
||||||
@@ -921,7 +945,7 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
let term_opts.curwin = 1
|
let term_opts.curwin = 1
|
||||||
endif
|
endif
|
||||||
call s:handle_ambidouble(term_opts)
|
call s:handle_ambidouble(term_opts)
|
||||||
let fzf.buf = term_start([&shell, &shellcmdflag, command], term_opts)
|
keepjumps let fzf.buf = term_start([&shell, &shellcmdflag, command], term_opts)
|
||||||
if is_popup && exists('#TerminalWinOpen')
|
if is_popup && exists('#TerminalWinOpen')
|
||||||
doautocmd <nomodeline> TerminalWinOpen
|
doautocmd <nomodeline> TerminalWinOpen
|
||||||
endif
|
endif
|
||||||
|
@@ -9,23 +9,24 @@
|
|||||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||||
|
|
||||||
if [[ $- =~ i ]]; then
|
[[ $- =~ i ]] || return 0
|
||||||
|
|
||||||
|
|
||||||
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
||||||
if ! declare -f _fzf_compgen_path > /dev/null; then
|
if ! declare -F _fzf_compgen_path > /dev/null; then
|
||||||
_fzf_compgen_path() {
|
_fzf_compgen_path() {
|
||||||
echo "$1"
|
echo "$1"
|
||||||
command find -L "$1" \
|
command find -L "$1" \
|
||||||
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
||||||
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
-a -not -path "$1" -print 2> /dev/null | command sed 's@^\./@@'
|
||||||
}
|
}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! declare -f _fzf_compgen_dir > /dev/null; then
|
if ! declare -F _fzf_compgen_dir > /dev/null; then
|
||||||
_fzf_compgen_dir() {
|
_fzf_compgen_dir() {
|
||||||
command find -L "$1" \
|
command find -L "$1" \
|
||||||
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
|
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
|
||||||
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
-a -not -path "$1" -print 2> /dev/null | command sed 's@^\./@@'
|
||||||
}
|
}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -68,59 +69,185 @@ _fzf_opts_completion() {
|
|||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||||
opts="
|
opts="
|
||||||
|
-h --help
|
||||||
-x --extended
|
-x --extended
|
||||||
-e --exact
|
-e --exact
|
||||||
|
--extended-exact
|
||||||
|
+x --no-extended
|
||||||
|
+e --no-exact
|
||||||
|
-q --query
|
||||||
|
-f --filter
|
||||||
|
--literal
|
||||||
|
--no-literal
|
||||||
--algo
|
--algo
|
||||||
-i +i
|
--scheme
|
||||||
|
--expect
|
||||||
|
--no-expect
|
||||||
|
--enabled --no-phony
|
||||||
|
--disabled --phony
|
||||||
|
--tiebreak
|
||||||
|
--bind
|
||||||
|
--color
|
||||||
|
--toggle-sort
|
||||||
|
-d --delimiter
|
||||||
-n --nth
|
-n --nth
|
||||||
--with-nth
|
--with-nth
|
||||||
-d --delimiter
|
-s --sort
|
||||||
+s --no-sort
|
+s --no-sort
|
||||||
|
--track
|
||||||
|
--no-track
|
||||||
--tac
|
--tac
|
||||||
--tiebreak
|
--no-tac
|
||||||
|
-i
|
||||||
|
+i
|
||||||
-m --multi
|
-m --multi
|
||||||
|
+m --no-multi
|
||||||
|
--ansi
|
||||||
|
--no-ansi
|
||||||
--no-mouse
|
--no-mouse
|
||||||
--bind
|
+c --no-color
|
||||||
--cycle
|
+2 --no-256
|
||||||
--no-hscroll
|
--black
|
||||||
--jump-labels
|
--no-black
|
||||||
--height
|
--bold
|
||||||
--literal
|
--no-bold
|
||||||
|
--layout
|
||||||
--reverse
|
--reverse
|
||||||
--margin
|
--no-reverse
|
||||||
|
--cycle
|
||||||
|
--no-cycle
|
||||||
|
--keep-right
|
||||||
|
--no-keep-right
|
||||||
|
--hscroll
|
||||||
|
--no-hscroll
|
||||||
|
--hscroll-off
|
||||||
|
--scroll-off
|
||||||
|
--filepath-word
|
||||||
|
--no-filepath-word
|
||||||
|
--info
|
||||||
|
--no-info
|
||||||
--inline-info
|
--inline-info
|
||||||
|
--no-inline-info
|
||||||
|
--separator
|
||||||
|
--no-separator
|
||||||
|
--scrollbar
|
||||||
|
--no-scrollbar
|
||||||
|
--jump-labels
|
||||||
|
-1 --select-1
|
||||||
|
+1 --no-select-1
|
||||||
|
-0 --exit-0
|
||||||
|
+0 --no-exit-0
|
||||||
|
--read0
|
||||||
|
--no-read0
|
||||||
|
--print0
|
||||||
|
--no-print0
|
||||||
|
--print-query
|
||||||
|
--no-print-query
|
||||||
--prompt
|
--prompt
|
||||||
--pointer
|
--pointer
|
||||||
--marker
|
--marker
|
||||||
--header
|
--sync
|
||||||
--header-lines
|
--no-sync
|
||||||
--ansi
|
--async
|
||||||
--tabstop
|
--no-history
|
||||||
--color
|
|
||||||
--no-bold
|
|
||||||
--history
|
--history
|
||||||
--history-size
|
--history-size
|
||||||
|
--no-header
|
||||||
|
--no-header-lines
|
||||||
|
--header
|
||||||
|
--header-lines
|
||||||
|
--header-first
|
||||||
|
--no-header-first
|
||||||
|
--ellipsis
|
||||||
--preview
|
--preview
|
||||||
|
--no-preview
|
||||||
--preview-window
|
--preview-window
|
||||||
-q --query
|
--height
|
||||||
-1 --select-1
|
--min-height
|
||||||
-0 --exit-0
|
--no-height
|
||||||
-f --filter
|
--no-margin
|
||||||
--print-query
|
--no-padding
|
||||||
--expect
|
--no-border
|
||||||
--sync"
|
--border
|
||||||
|
--no-border-label
|
||||||
|
--border-label
|
||||||
|
--border-label-pos
|
||||||
|
--no-preview-label
|
||||||
|
--preview-label
|
||||||
|
--preview-label-pos
|
||||||
|
--no-unicode
|
||||||
|
--unicode
|
||||||
|
--margin
|
||||||
|
--padding
|
||||||
|
--tabstop
|
||||||
|
--listen
|
||||||
|
--no-listen
|
||||||
|
--clear
|
||||||
|
--no-clear
|
||||||
|
--version
|
||||||
|
--"
|
||||||
|
|
||||||
case "${prev}" in
|
case "${prev}" in
|
||||||
|
--algo)
|
||||||
|
COMPREPLY=( $(compgen -W "v1 v2" -- "$cur") )
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--scheme)
|
||||||
|
COMPREPLY=( $(compgen -W "default path history" -- "$cur") )
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
--tiebreak)
|
--tiebreak)
|
||||||
COMPREPLY=( $(compgen -W "length begin end index" -- "$cur") )
|
COMPREPLY=( $(compgen -W "length chunk begin end index" -- "$cur") )
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
--color)
|
--color)
|
||||||
COMPREPLY=( $(compgen -W "dark light 16 bw" -- "$cur") )
|
COMPREPLY=( $(compgen -W "dark light 16 bw no" -- "$cur") )
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
--history)
|
--layout)
|
||||||
COMPREPLY=()
|
COMPREPLY=( $(compgen -W "default reverse reverse-list" -- "$cur") )
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--info)
|
||||||
|
COMPREPLY=( $(compgen -W "default right hidden inline inline-right" -- "$cur") )
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--preview-window)
|
||||||
|
COMPREPLY=( $(compgen -W "
|
||||||
|
default
|
||||||
|
hidden
|
||||||
|
nohidden
|
||||||
|
wrap
|
||||||
|
nowrap
|
||||||
|
cycle
|
||||||
|
nocycle
|
||||||
|
up top
|
||||||
|
down bottom
|
||||||
|
left
|
||||||
|
right
|
||||||
|
rounded border border-rounded
|
||||||
|
sharp border-sharp
|
||||||
|
border-bold
|
||||||
|
border-block
|
||||||
|
border-thinblock
|
||||||
|
border-double
|
||||||
|
noborder border-none
|
||||||
|
border-horizontal
|
||||||
|
border-vertical
|
||||||
|
border-up border-top
|
||||||
|
border-down border-bottom
|
||||||
|
border-left
|
||||||
|
border-right
|
||||||
|
follow
|
||||||
|
nofollow" -- "$cur") )
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--border)
|
||||||
|
COMPREPLY=( $(compgen -W "rounded sharp bold block thinblock double horizontal vertical top bottom left right none" -- "$cur") )
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--border-label-pos|--preview-label-pos)
|
||||||
|
COMPREPLY=( $(compgen -W "center bottom top" -- "$cur") )
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@@ -170,9 +297,9 @@ __fzf_generic_path_completion() {
|
|||||||
COMPREPLY=()
|
COMPREPLY=()
|
||||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
if [[ "$cur" == *"$trigger" ]]; then
|
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
|
||||||
base=${cur:0:${#cur}-${#trigger}}
|
base=${cur:0:${#cur}-${#trigger}}
|
||||||
eval "base=$base"
|
eval "base=$base" 2> /dev/null || return
|
||||||
|
|
||||||
dir=
|
dir=
|
||||||
[[ $base = *"/"* ]] && dir="$base"
|
[[ $base = *"/"* ]] && dir="$base"
|
||||||
@@ -182,7 +309,7 @@ __fzf_generic_path_completion() {
|
|||||||
leftover=${leftover/#\/}
|
leftover=${leftover/#\/}
|
||||||
[[ -z "$dir" ]] && dir='.'
|
[[ -z "$dir" ]] && dir='.'
|
||||||
[[ "$dir" != "/" ]] && dir="${dir/%\//}"
|
[[ "$dir" != "/" ]] && dir="${dir/%\//}"
|
||||||
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
|
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
|
||||||
printf "%q " "${item%$3}$3"
|
printf "%q " "${item%$3}$3"
|
||||||
done)
|
done)
|
||||||
matches=${matches% }
|
matches=${matches% }
|
||||||
@@ -195,7 +322,7 @@ __fzf_generic_path_completion() {
|
|||||||
printf '\e[5n'
|
printf '\e[5n'
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
dir=$(dirname "$dir")
|
dir=$(command dirname "$dir")
|
||||||
[[ "$dir" =~ /$ ]] || dir="$dir"/
|
[[ "$dir" =~ /$ ]] || dir="$dir"/
|
||||||
done
|
done
|
||||||
else
|
else
|
||||||
@@ -229,16 +356,16 @@ _fzf_complete() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
local cur selected trigger cmd post
|
local cur selected trigger cmd post
|
||||||
post="$(caller 0 | awk '{print $2}')_post"
|
post="$(caller 0 | command awk '{print $2}')_post"
|
||||||
type -t "$post" > /dev/null 2>&1 || post=cat
|
type -t "$post" > /dev/null 2>&1 || post='command cat'
|
||||||
|
|
||||||
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
|
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
|
||||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
if [[ "$cur" == *"$trigger" ]]; then
|
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
|
||||||
cur=${cur:0:${#cur}-${#trigger}}
|
cur=${cur:0:${#cur}-${#trigger}}
|
||||||
|
|
||||||
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | tr '\n' ' ')
|
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | 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")
|
||||||
@@ -277,27 +404,61 @@ _fzf_proc_completion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_fzf_proc_completion_post() {
|
_fzf_proc_completion_post() {
|
||||||
awk '{print $2}'
|
command awk '{print $2}'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# To use custom hostname lists, override __fzf_list_hosts.
|
||||||
|
# The function is expected to print hostnames, one per line as well as in the
|
||||||
|
# desired sorting and with any duplicates removed, to standard output.
|
||||||
|
#
|
||||||
|
# e.g.
|
||||||
|
# # Use bash-completions’s _known_hosts_real() for getting the list of hosts
|
||||||
|
# __fzf_list_hosts() {
|
||||||
|
# # Set the local attribute for any non-local variable that is set by _known_hosts_real()
|
||||||
|
# local COMPREPLY=()
|
||||||
|
# _known_hosts_real ''
|
||||||
|
# printf '%s\n' "${COMPREPLY[@]}" | command sort -u --version-sort
|
||||||
|
# }
|
||||||
|
if ! declare -F __fzf_list_hosts > /dev/null; then
|
||||||
|
__fzf_list_hosts() {
|
||||||
|
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | command awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?%]') \
|
||||||
|
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts 2> /dev/null | command tr ',' '\n' | command tr -d '[' | command awk '{ print $1 " " $1 }') \
|
||||||
|
<(command grep -v '^\s*\(#\|$\)' /etc/hosts 2> /dev/null | command grep -Fv '0.0.0.0' | command sed 's/#.*//') |
|
||||||
|
command awk '{for (i = 2; i <= NF; i++) print $i}' | command sort -u
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
_fzf_host_completion() {
|
_fzf_host_completion() {
|
||||||
_fzf_complete +m -- "$@" < <(
|
_fzf_complete +m -- "$@" < <(__fzf_list_hosts)
|
||||||
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?%]') \
|
}
|
||||||
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
|
||||||
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
# Values for $1 $2 $3 are described here
|
||||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
# https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion.html
|
||||||
)
|
# > the first argument ($1) is the name of the command whose arguments are being completed,
|
||||||
|
# > the second argument ($2) is the word being completed,
|
||||||
|
# > and the third argument ($3) is the word preceding the word being completed on the current command line.
|
||||||
|
_fzf_complete_ssh() {
|
||||||
|
case $3 in
|
||||||
|
-i|-F|-E)
|
||||||
|
_fzf_path_completion "$@"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
local user=
|
||||||
|
[[ "$2" =~ '@' ]] && user="${2%%@*}@"
|
||||||
|
_fzf_complete +m -- "$@" < <(__fzf_list_hosts | command awk -v user="$user" '{print user $0}')
|
||||||
|
;;
|
||||||
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_var_completion() {
|
_fzf_var_completion() {
|
||||||
_fzf_complete -m -- "$@" < <(
|
_fzf_complete -m -- "$@" < <(
|
||||||
declare -xp | sed -En 's|^declare [^ ]+ ([^=]+).*|\1|p'
|
declare -xp | command sed -En 's|^declare [^ ]+ ([^=]+).*|\1|p'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_alias_completion() {
|
_fzf_alias_completion() {
|
||||||
_fzf_complete -m -- "$@" < <(
|
_fzf_complete -m -- "$@" < <(
|
||||||
alias | sed -En 's|^alias ([^=]+).*|\1|p'
|
alias | command sed -En 's|^alias ([^=]+).*|\1|p'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,7 +481,7 @@ a_cmds="
|
|||||||
svn tar unzip zip"
|
svn tar unzip zip"
|
||||||
|
|
||||||
# Preserve existing completion
|
# Preserve existing completion
|
||||||
__fzf_orig_completion < <(complete -p $d_cmds $a_cmds 2> /dev/null)
|
__fzf_orig_completion < <(complete -p $d_cmds $a_cmds ssh 2> /dev/null)
|
||||||
|
|
||||||
if type _completion_loader > /dev/null 2>&1; then
|
if type _completion_loader > /dev/null 2>&1; then
|
||||||
_fzf_completion_loader=1
|
_fzf_completion_loader=1
|
||||||
@@ -351,6 +512,9 @@ for cmd in $d_cmds; do
|
|||||||
__fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames"
|
__fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# ssh
|
||||||
|
__fzf_defc ssh _fzf_complete_ssh "-o default -o bashdefault"
|
||||||
|
|
||||||
unset cmd d_cmds a_cmds
|
unset cmd d_cmds a_cmds
|
||||||
|
|
||||||
_fzf_setup_completion() {
|
_fzf_setup_completion() {
|
||||||
@@ -376,7 +540,5 @@ _fzf_setup_completion() {
|
|||||||
# Environment variables / Aliases / Hosts / Process
|
# Environment variables / Aliases / Hosts / Process
|
||||||
_fzf_setup_completion 'var' export unset printenv
|
_fzf_setup_completion 'var' export unset printenv
|
||||||
_fzf_setup_completion 'alias' unalias
|
_fzf_setup_completion 'alias' unalias
|
||||||
_fzf_setup_completion 'host' ssh telnet
|
_fzf_setup_completion 'host' telnet
|
||||||
_fzf_setup_completion 'proc' kill
|
_fzf_setup_completion 'proc' kill
|
||||||
|
|
||||||
fi
|
|
||||||
|
@@ -9,6 +9,9 @@
|
|||||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||||
|
|
||||||
|
[[ -o interactive ]] || return 0
|
||||||
|
|
||||||
|
|
||||||
# Both branches of the following `if` do the same thing -- define
|
# Both branches of the following `if` do the same thing -- define
|
||||||
# __fzf_completion_options such that `eval $__fzf_completion_options` sets
|
# __fzf_completion_options such that `eval $__fzf_completion_options` sets
|
||||||
# all options to the same values they currently have. We'll do just that at
|
# all options to the same values they currently have. We'll do just that at
|
||||||
@@ -67,15 +70,12 @@ fi
|
|||||||
# control. There are several others that could wreck havoc if they are set
|
# control. There are several others that could wreck havoc if they are set
|
||||||
# to values we don't expect. With the following `emulate` command we
|
# to values we don't expect. With the following `emulate` command we
|
||||||
# sidestep this issue entirely.
|
# sidestep this issue entirely.
|
||||||
'emulate' 'zsh' '-o' 'no_aliases'
|
'builtin' 'emulate' 'zsh' && 'builtin' 'setopt' 'no_aliases'
|
||||||
|
|
||||||
# This brace is the start of try-always block. The `always` part is like
|
# This brace is the start of try-always block. The `always` part is like
|
||||||
# `finally` in lesser languages. We use it to *always* restore user options.
|
# `finally` in lesser languages. We use it to *always* restore user options.
|
||||||
{
|
{
|
||||||
|
|
||||||
# Bail out if not interactive shell.
|
|
||||||
[[ -o interactive ]] || return 0
|
|
||||||
|
|
||||||
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
||||||
if ! declare -f _fzf_compgen_path > /dev/null; then
|
if ! declare -f _fzf_compgen_path > /dev/null; then
|
||||||
_fzf_compgen_path() {
|
_fzf_compgen_path() {
|
||||||
@@ -137,7 +137,10 @@ __fzf_generic_path_completion() {
|
|||||||
tail=$6
|
tail=$6
|
||||||
|
|
||||||
setopt localoptions nonomatch
|
setopt localoptions nonomatch
|
||||||
eval "base=$base"
|
if [[ $base = *'$('* ]] || [[ $base = *'<('* ]] || [[ $base = *'>('* ]] || [[ $base = *':='* ]] || [[ $base = *'`'* ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
eval "base=$base" 2> /dev/null || return
|
||||||
[[ $base = *"/"* ]] && dir="$base"
|
[[ $base = *"/"* ]] && dir="$base"
|
||||||
while [ 1 ]; do
|
while [ 1 ]; do
|
||||||
if [[ -z "$dir" || -d ${dir} ]]; then
|
if [[ -z "$dir" || -d ${dir} ]]; then
|
||||||
@@ -145,7 +148,7 @@ __fzf_generic_path_completion() {
|
|||||||
leftover=${leftover/#\/}
|
leftover=${leftover/#\/}
|
||||||
[ -z "$dir" ] && dir='.'
|
[ -z "$dir" ] && dir='.'
|
||||||
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
||||||
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-}" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
|
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-}" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
|
||||||
item="${item%$suffix}$suffix"
|
item="${item%$suffix}$suffix"
|
||||||
echo -n "${(q)item} "
|
echo -n "${(q)item} "
|
||||||
done)
|
done)
|
||||||
@@ -215,21 +218,37 @@ _fzf_complete() {
|
|||||||
command rm -f "$fifo"
|
command rm -f "$fifo"
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_telnet() {
|
# To use custom hostname lists, override __fzf_list_hosts.
|
||||||
_fzf_complete +m -- "$@" < <(
|
# The function is expected to print hostnames, one per line as well as in the
|
||||||
command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' |
|
# desired sorting and with any duplicates removed, to standard output.
|
||||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
if ! declare -f __fzf_list_hosts > /dev/null; then
|
||||||
)
|
__fzf_list_hosts() {
|
||||||
}
|
|
||||||
|
|
||||||
_fzf_complete_ssh() {
|
|
||||||
_fzf_complete +m -- "$@" < <(
|
|
||||||
setopt localoptions nonomatch
|
setopt localoptions nonomatch
|
||||||
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?%]') \
|
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?%]') \
|
||||||
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts 2> /dev/null | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
||||||
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
<(command grep -v '^\s*\(#\|$\)' /etc/hosts 2> /dev/null | command grep -Fv '0.0.0.0' | command sed 's/#.*//') |
|
||||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
awk '{for (i = 2; i <= NF; i++) print $i}' | sort -u
|
||||||
)
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
_fzf_complete_telnet() {
|
||||||
|
_fzf_complete +m -- "$@" < <(__fzf_list_hosts)
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
_fzf_complete_ssh() {
|
||||||
|
local tokens=(${(z)1})
|
||||||
|
case ${tokens[-1]} in
|
||||||
|
-i|-F|-E)
|
||||||
|
_fzf_path_completion "$prefix" "$1"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
local user=
|
||||||
|
[[ $prefix =~ @ ]] && user="${prefix%%@*}@"
|
||||||
|
_fzf_complete +m -- "$@" < <(__fzf_list_hosts | awk -v user="$user" '{print user $0}')
|
||||||
|
;;
|
||||||
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_export() {
|
_fzf_complete_export() {
|
||||||
@@ -293,6 +312,9 @@ fzf-completion() {
|
|||||||
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})
|
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})
|
||||||
|
|
||||||
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
|
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
|
||||||
|
if [[ $prefix = *'$('* ]] || [[ $prefix = *'<('* ]] || [[ $prefix = *'>('* ]] || [[ $prefix = *':='* ]] || [[ $prefix = *'`'* ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
[ -n "${tokens[-1]}" ] && lbuf=${lbuf:0:-${#tokens[-1]}}
|
[ -n "${tokens[-1]}" ] && lbuf=${lbuf:0:-${#tokens[-1]}}
|
||||||
|
|
||||||
if eval "type _fzf_complete_${cmd} > /dev/null"; then
|
if eval "type _fzf_complete_${cmd} > /dev/null"; then
|
||||||
|
@@ -11,15 +11,18 @@
|
|||||||
# - $FZF_ALT_C_COMMAND
|
# - $FZF_ALT_C_COMMAND
|
||||||
# - $FZF_ALT_C_OPTS
|
# - $FZF_ALT_C_OPTS
|
||||||
|
|
||||||
|
[[ $- =~ i ]] || return 0
|
||||||
|
|
||||||
|
|
||||||
# Key bindings
|
# Key bindings
|
||||||
# ------------
|
# ------------
|
||||||
__fzf_select__() {
|
__fzf_select__() {
|
||||||
local cmd opts
|
local cmd opts
|
||||||
cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||||
-o -type f -print \
|
-o -type f -print \
|
||||||
-o -type d -print \
|
-o -type d -print \
|
||||||
-o -type l -print 2> /dev/null | cut -b3-"}"
|
-o -type l -print 2> /dev/null | command cut -b3-"}"
|
||||||
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-} -m"
|
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse --scheme=path ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-} -m"
|
||||||
eval "$cmd" |
|
eval "$cmd" |
|
||||||
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) "$@" |
|
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) "$@" |
|
||||||
while read -r item; do
|
while read -r item; do
|
||||||
@@ -27,8 +30,6 @@ __fzf_select__() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ $- =~ i ]]; then
|
|
||||||
|
|
||||||
__fzfcmd() {
|
__fzfcmd() {
|
||||||
[[ -n "${TMUX_PANE-}" ]] && { [[ "${FZF_TMUX:-0}" != 0 ]] || [[ -n "${FZF_TMUX_OPTS-}" ]]; } &&
|
[[ -n "${TMUX_PANE-}" ]] && { [[ "${FZF_TMUX:-0}" != 0 ]] || [[ -n "${FZF_TMUX_OPTS-}" ]]; } &&
|
||||||
echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
|
echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
|
||||||
@@ -42,19 +43,21 @@ fzf-file-widget() {
|
|||||||
|
|
||||||
__fzf_cd__() {
|
__fzf_cd__() {
|
||||||
local cmd opts dir
|
local cmd opts dir
|
||||||
cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||||
-o -type d -print 2> /dev/null | cut -b3-"}"
|
-o -type d -print 2> /dev/null | command cut -b3-"}"
|
||||||
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-} +m"
|
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse --scheme=path ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-} +m"
|
||||||
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="$opts" $(__fzfcmd)) && printf 'builtin cd -- %q' "$dir"
|
dir=$(set +o pipefail; eval "$cmd" | FZF_DEFAULT_OPTS="$opts" $(__fzfcmd)) && printf 'builtin cd -- %q' "$dir"
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzf_history__() {
|
if command -v perl > /dev/null; then
|
||||||
|
__fzf_history__() {
|
||||||
local output opts script
|
local output opts script
|
||||||
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort ${FZF_CTRL_R_OPTS-} +m --read0"
|
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort ${FZF_CTRL_R_OPTS-} +m --read0"
|
||||||
script='BEGIN { getc; $/ = "\n\t"; $HISTCOUNT = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCOUNT - $. . "\t$_" if !$seen{$_}++'
|
script='BEGIN { getc; $/ = "\n\t"; $HISTCOUNT = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCOUNT - $. . "\t$_" if !$seen{$_}++'
|
||||||
output=$(
|
output=$(
|
||||||
|
set +o pipefail
|
||||||
builtin fc -lnr -2147483648 |
|
builtin fc -lnr -2147483648 |
|
||||||
last_hist=$(HISTTIMEFORMAT='' builtin history 1) perl -n -l0 -e "$script" |
|
last_hist=$(HISTTIMEFORMAT='' builtin history 1) command perl -n -l0 -e "$script" |
|
||||||
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) --query "$READLINE_LINE"
|
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) --query "$READLINE_LINE"
|
||||||
) || return
|
) || return
|
||||||
READLINE_LINE=${output#*$'\t'}
|
READLINE_LINE=${output#*$'\t'}
|
||||||
@@ -63,7 +66,37 @@ __fzf_history__() {
|
|||||||
else
|
else
|
||||||
READLINE_POINT=0x7fffffff
|
READLINE_POINT=0x7fffffff
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
else # awk - fallback for POSIX systems
|
||||||
|
__fzf_history__() {
|
||||||
|
local output opts script n x y z d
|
||||||
|
if [[ -z $__fzf_awk ]]; then
|
||||||
|
__fzf_awk=awk
|
||||||
|
# choose the faster mawk if: it's installed && build date >= 20230322 && version >= 1.3.4
|
||||||
|
IFS=' .' read n x y z d <<< $(command mawk -W version 2> /dev/null)
|
||||||
|
[[ $n == mawk ]] && (( d >= 20230302 && (x *1000 +y) *1000 +z >= 1003004 )) && __fzf_awk=mawk
|
||||||
|
fi
|
||||||
|
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort ${FZF_CTRL_R_OPTS-} +m --read0"
|
||||||
|
[[ $(HISTTIMEFORMAT='' builtin history 1) =~ [[:digit:]]+ ]] # how many history entries
|
||||||
|
script='function P(b) { ++n; sub(/^[ *]/, "", b); if (!seen[b]++) { printf "%d\t%s%c", '$((BASH_REMATCH + 1))' - n, b, 0 } }
|
||||||
|
NR==1 { b = substr($0, 2); next }
|
||||||
|
/^\t/ { P(b); b = substr($0, 2); next }
|
||||||
|
{ b = b RS $0 }
|
||||||
|
END { if (NR) P(b) }'
|
||||||
|
output=$(
|
||||||
|
set +o pipefail
|
||||||
|
builtin fc -lnr -2147483648 2> /dev/null | # ( $'\t '<lines>$'\n' )* ; <lines> ::= [^\n]* ( $'\n'<lines> )*
|
||||||
|
command $__fzf_awk "$script" | # ( <counter>$'\t'<lines>$'\000' )*
|
||||||
|
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) --query "$READLINE_LINE"
|
||||||
|
) || return
|
||||||
|
READLINE_LINE=${output#*$'\t'}
|
||||||
|
if [[ -z "$READLINE_POINT" ]]; then
|
||||||
|
echo "$READLINE_LINE"
|
||||||
|
else
|
||||||
|
READLINE_POINT=0x7fffffff
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
# Required to refresh the prompt after fzf
|
# Required to refresh the prompt after fzf
|
||||||
bind -m emacs-standard '"\er": redraw-current-line'
|
bind -m emacs-standard '"\er": redraw-current-line'
|
||||||
@@ -79,7 +112,7 @@ if (( BASH_VERSINFO[0] < 4 )); then
|
|||||||
bind -m vi-insert '"\C-t": "\C-z\C-t\C-z"'
|
bind -m vi-insert '"\C-t": "\C-z\C-t\C-z"'
|
||||||
|
|
||||||
# CTRL-R - Paste the selected command from history into the command line
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
bind -m emacs-standard '"\C-r": "\C-e \C-u\C-y\ey\C-u"$(__fzf_history__)"\e\C-e\er"'
|
bind -m emacs-standard '"\C-r": "\C-e \C-u\C-y\ey\C-u`__fzf_history__`\e\C-e\er"'
|
||||||
bind -m vi-command '"\C-r": "\C-z\C-r\C-z"'
|
bind -m vi-command '"\C-r": "\C-z\C-r\C-z"'
|
||||||
bind -m vi-insert '"\C-r": "\C-z\C-r\C-z"'
|
bind -m vi-insert '"\C-r": "\C-z\C-r\C-z"'
|
||||||
else
|
else
|
||||||
@@ -98,5 +131,3 @@ fi
|
|||||||
bind -m emacs-standard '"\ec": " \C-b\C-k \C-u`__fzf_cd__`\e\C-e\er\C-m\C-y\C-h\e \C-y\ey\C-x\C-x\C-d"'
|
bind -m emacs-standard '"\ec": " \C-b\C-k \C-u`__fzf_cd__`\e\C-e\er\C-m\C-y\C-h\e \C-y\ey\C-x\C-x\C-d"'
|
||||||
bind -m vi-command '"\ec": "\C-z\ec\C-z"'
|
bind -m vi-command '"\ec": "\C-z\ec\C-z"'
|
||||||
bind -m vi-insert '"\ec": "\C-z\ec\C-z"'
|
bind -m vi-insert '"\ec": "\C-z\ec\C-z"'
|
||||||
|
|
||||||
fi
|
|
||||||
|
@@ -11,6 +11,9 @@
|
|||||||
# - $FZF_ALT_C_COMMAND
|
# - $FZF_ALT_C_COMMAND
|
||||||
# - $FZF_ALT_C_OPTS
|
# - $FZF_ALT_C_OPTS
|
||||||
|
|
||||||
|
status is-interactive; or exit 0
|
||||||
|
|
||||||
|
|
||||||
# Key bindings
|
# Key bindings
|
||||||
# ------------
|
# ------------
|
||||||
function fzf_key_bindings
|
function fzf_key_bindings
|
||||||
@@ -22,17 +25,17 @@ function fzf_key_bindings
|
|||||||
set -l fzf_query $commandline[2]
|
set -l fzf_query $commandline[2]
|
||||||
set -l prefix $commandline[3]
|
set -l prefix $commandline[3]
|
||||||
|
|
||||||
# "-path \$dir'*/\\.*'" matches hidden files/folders inside $dir but not
|
# "-path \$dir'*/.*'" matches hidden files/folders inside $dir but not
|
||||||
# $dir itself, even if hidden.
|
# $dir itself, even if hidden.
|
||||||
test -n "$FZF_CTRL_T_COMMAND"; or set -l FZF_CTRL_T_COMMAND "
|
test -n "$FZF_CTRL_T_COMMAND"; or set -l FZF_CTRL_T_COMMAND "
|
||||||
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
command find -L \$dir -mindepth 1 \\( -path \$dir'*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
||||||
-o -type f -print \
|
-o -type f -print \
|
||||||
-o -type d -print \
|
-o -type d -print \
|
||||||
-o -type l -print 2> /dev/null | sed 's@^\./@@'"
|
-o -type l -print 2> /dev/null | sed 's@^\./@@'"
|
||||||
|
|
||||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||||
begin
|
begin
|
||||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
|
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
|
||||||
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
|
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
|
||||||
end
|
end
|
||||||
if [ -z "$result" ]
|
if [ -z "$result" ]
|
||||||
@@ -79,11 +82,11 @@ function fzf_key_bindings
|
|||||||
set -l prefix $commandline[3]
|
set -l prefix $commandline[3]
|
||||||
|
|
||||||
test -n "$FZF_ALT_C_COMMAND"; or set -l FZF_ALT_C_COMMAND "
|
test -n "$FZF_ALT_C_COMMAND"; or set -l FZF_ALT_C_COMMAND "
|
||||||
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
command find -L \$dir -mindepth 1 \\( -path \$dir'*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
||||||
-o -type d -print 2> /dev/null | sed 's@^\./@@'"
|
-o -type d -print 2> /dev/null | sed 's@^\./@@'"
|
||||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||||
begin
|
begin
|
||||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
|
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
|
||||||
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
||||||
|
|
||||||
if [ -n "$result" ]
|
if [ -n "$result" ]
|
||||||
|
@@ -11,6 +11,9 @@
|
|||||||
# - $FZF_ALT_C_COMMAND
|
# - $FZF_ALT_C_COMMAND
|
||||||
# - $FZF_ALT_C_OPTS
|
# - $FZF_ALT_C_OPTS
|
||||||
|
|
||||||
|
[[ -o interactive ]] || return 0
|
||||||
|
|
||||||
|
|
||||||
# Key bindings
|
# Key bindings
|
||||||
# ------------
|
# ------------
|
||||||
|
|
||||||
@@ -32,21 +35,19 @@ else
|
|||||||
}
|
}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
'emulate' 'zsh' '-o' 'no_aliases'
|
'builtin' 'emulate' 'zsh' && 'builtin' 'setopt' 'no_aliases'
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
||||||
[[ -o interactive ]] || return 0
|
|
||||||
|
|
||||||
# CTRL-T - Paste the selected file path(s) into the command line
|
# CTRL-T - Paste the selected file path(s) into the command line
|
||||||
__fsel() {
|
__fsel() {
|
||||||
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||||
-o -type f -print \
|
-o -type f -print \
|
||||||
-o -type d -print \
|
-o -type d -print \
|
||||||
-o -type l -print 2> /dev/null | cut -b3-"}"
|
-o -type l -print 2> /dev/null | cut -b3-"}"
|
||||||
setopt localoptions pipefail no_aliases 2> /dev/null
|
setopt localoptions pipefail no_aliases 2> /dev/null
|
||||||
local item
|
local item
|
||||||
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-}" $(__fzfcmd) -m "$@" | while read item; do
|
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-}" $(__fzfcmd) -m "$@" | while read item; do
|
||||||
echo -n "${(q)item} "
|
echo -n "${(q)item} "
|
||||||
done
|
done
|
||||||
local ret=$?
|
local ret=$?
|
||||||
@@ -72,10 +73,10 @@ bindkey -M viins '^T' fzf-file-widget
|
|||||||
|
|
||||||
# ALT-C - cd into the selected directory
|
# ALT-C - cd into the selected directory
|
||||||
fzf-cd-widget() {
|
fzf-cd-widget() {
|
||||||
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||||
-o -type d -print 2> /dev/null | cut -b3-"}"
|
-o -type d -print 2> /dev/null | cut -b3-"}"
|
||||||
setopt localoptions pipefail no_aliases 2> /dev/null
|
setopt localoptions pipefail no_aliases 2> /dev/null
|
||||||
local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-}" $(__fzfcmd) +m)"
|
local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-}" $(__fzfcmd) +m)"
|
||||||
if [[ -z "$dir" ]]; then
|
if [[ -z "$dir" ]]; then
|
||||||
zle redisplay
|
zle redisplay
|
||||||
return 0
|
return 0
|
||||||
@@ -97,13 +98,15 @@ bindkey -M viins '\ec' fzf-cd-widget
|
|||||||
fzf-history-widget() {
|
fzf-history-widget() {
|
||||||
local selected num
|
local selected num
|
||||||
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
|
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
|
||||||
selected=( $(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
|
selected="$(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
|
||||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} ${FZF_DEFAULT_OPTS-} -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,ctrl-z:ignore ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
|
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} ${FZF_DEFAULT_OPTS-} -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,ctrl-z:ignore ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m" $(__fzfcmd))"
|
||||||
local ret=$?
|
local ret=$?
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
num=$selected[1]
|
num=$(awk '{print $1}' <<< "$selected")
|
||||||
if [ -n "$num" ]; then
|
if [[ "$num" =~ '^[1-9][0-9]*\*?$' ]]; then
|
||||||
zle vi-fetch-history -n $num
|
zle vi-fetch-history -n ${num%\*}
|
||||||
|
else # selected is a custom query, not from history
|
||||||
|
LBUFFER="$selected"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
zle reset-prompt
|
zle reset-prompt
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2023 Junegunn Choi
|
Copyright (c) 2013-2024 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
129
src/actiontype_string.go
Normal file
129
src/actiontype_string.go
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
// Code generated by "stringer -type=actionType"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package fzf
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[actIgnore-0]
|
||||||
|
_ = x[actStart-1]
|
||||||
|
_ = x[actClick-2]
|
||||||
|
_ = x[actInvalid-3]
|
||||||
|
_ = x[actChar-4]
|
||||||
|
_ = x[actMouse-5]
|
||||||
|
_ = x[actBeginningOfLine-6]
|
||||||
|
_ = x[actAbort-7]
|
||||||
|
_ = x[actAccept-8]
|
||||||
|
_ = x[actAcceptNonEmpty-9]
|
||||||
|
_ = x[actAcceptOrPrintQuery-10]
|
||||||
|
_ = x[actBackwardChar-11]
|
||||||
|
_ = x[actBackwardDeleteChar-12]
|
||||||
|
_ = x[actBackwardDeleteCharEof-13]
|
||||||
|
_ = x[actBackwardWord-14]
|
||||||
|
_ = x[actCancel-15]
|
||||||
|
_ = x[actChangeBorderLabel-16]
|
||||||
|
_ = x[actChangeHeader-17]
|
||||||
|
_ = x[actChangePreviewLabel-18]
|
||||||
|
_ = x[actChangePrompt-19]
|
||||||
|
_ = x[actChangeQuery-20]
|
||||||
|
_ = x[actClearScreen-21]
|
||||||
|
_ = x[actClearQuery-22]
|
||||||
|
_ = x[actClearSelection-23]
|
||||||
|
_ = x[actClose-24]
|
||||||
|
_ = x[actDeleteChar-25]
|
||||||
|
_ = x[actDeleteCharEof-26]
|
||||||
|
_ = x[actEndOfLine-27]
|
||||||
|
_ = x[actForwardChar-28]
|
||||||
|
_ = x[actForwardWord-29]
|
||||||
|
_ = x[actKillLine-30]
|
||||||
|
_ = x[actKillWord-31]
|
||||||
|
_ = x[actUnixLineDiscard-32]
|
||||||
|
_ = x[actUnixWordRubout-33]
|
||||||
|
_ = x[actYank-34]
|
||||||
|
_ = x[actBackwardKillWord-35]
|
||||||
|
_ = x[actSelectAll-36]
|
||||||
|
_ = x[actDeselectAll-37]
|
||||||
|
_ = x[actToggle-38]
|
||||||
|
_ = x[actToggleSearch-39]
|
||||||
|
_ = x[actToggleAll-40]
|
||||||
|
_ = x[actToggleDown-41]
|
||||||
|
_ = x[actToggleUp-42]
|
||||||
|
_ = x[actToggleIn-43]
|
||||||
|
_ = x[actToggleOut-44]
|
||||||
|
_ = x[actToggleTrack-45]
|
||||||
|
_ = x[actToggleHeader-46]
|
||||||
|
_ = x[actTrack-47]
|
||||||
|
_ = x[actDown-48]
|
||||||
|
_ = x[actUp-49]
|
||||||
|
_ = x[actPageUp-50]
|
||||||
|
_ = x[actPageDown-51]
|
||||||
|
_ = x[actPosition-52]
|
||||||
|
_ = x[actHalfPageUp-53]
|
||||||
|
_ = x[actHalfPageDown-54]
|
||||||
|
_ = x[actOffsetUp-55]
|
||||||
|
_ = x[actOffsetDown-56]
|
||||||
|
_ = x[actJump-57]
|
||||||
|
_ = x[actJumpAccept-58]
|
||||||
|
_ = x[actPrintQuery-59]
|
||||||
|
_ = x[actRefreshPreview-60]
|
||||||
|
_ = x[actReplaceQuery-61]
|
||||||
|
_ = x[actToggleSort-62]
|
||||||
|
_ = x[actShowPreview-63]
|
||||||
|
_ = x[actHidePreview-64]
|
||||||
|
_ = x[actTogglePreview-65]
|
||||||
|
_ = x[actTogglePreviewWrap-66]
|
||||||
|
_ = x[actTransform-67]
|
||||||
|
_ = x[actTransformBorderLabel-68]
|
||||||
|
_ = x[actTransformHeader-69]
|
||||||
|
_ = x[actTransformPreviewLabel-70]
|
||||||
|
_ = x[actTransformPrompt-71]
|
||||||
|
_ = x[actTransformQuery-72]
|
||||||
|
_ = x[actPreview-73]
|
||||||
|
_ = x[actChangePreview-74]
|
||||||
|
_ = x[actChangePreviewWindow-75]
|
||||||
|
_ = x[actPreviewTop-76]
|
||||||
|
_ = x[actPreviewBottom-77]
|
||||||
|
_ = x[actPreviewUp-78]
|
||||||
|
_ = x[actPreviewDown-79]
|
||||||
|
_ = x[actPreviewPageUp-80]
|
||||||
|
_ = x[actPreviewPageDown-81]
|
||||||
|
_ = x[actPreviewHalfPageUp-82]
|
||||||
|
_ = x[actPreviewHalfPageDown-83]
|
||||||
|
_ = x[actPrevHistory-84]
|
||||||
|
_ = x[actPrevSelected-85]
|
||||||
|
_ = x[actPut-86]
|
||||||
|
_ = x[actNextHistory-87]
|
||||||
|
_ = x[actNextSelected-88]
|
||||||
|
_ = x[actExecute-89]
|
||||||
|
_ = x[actExecuteSilent-90]
|
||||||
|
_ = x[actExecuteMulti-91]
|
||||||
|
_ = x[actSigStop-92]
|
||||||
|
_ = x[actFirst-93]
|
||||||
|
_ = x[actLast-94]
|
||||||
|
_ = x[actReload-95]
|
||||||
|
_ = x[actReloadSync-96]
|
||||||
|
_ = x[actDisableSearch-97]
|
||||||
|
_ = x[actEnableSearch-98]
|
||||||
|
_ = x[actSelect-99]
|
||||||
|
_ = x[actDeselect-100]
|
||||||
|
_ = x[actUnbind-101]
|
||||||
|
_ = x[actRebind-102]
|
||||||
|
_ = x[actBecome-103]
|
||||||
|
_ = x[actResponse-104]
|
||||||
|
_ = x[actShowHeader-105]
|
||||||
|
_ = x[actHideHeader-106]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleHeaderactTrackactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactResponseactShowHeaderactHideHeader"
|
||||||
|
|
||||||
|
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 263, 278, 292, 306, 319, 336, 344, 357, 373, 385, 399, 413, 424, 435, 453, 470, 477, 496, 508, 522, 531, 546, 558, 571, 582, 593, 605, 619, 634, 642, 649, 654, 663, 674, 685, 698, 713, 724, 737, 744, 757, 770, 787, 802, 815, 829, 843, 859, 879, 891, 914, 932, 956, 974, 991, 1001, 1017, 1039, 1052, 1068, 1080, 1094, 1110, 1128, 1148, 1170, 1184, 1199, 1205, 1219, 1234, 1244, 1260, 1275, 1285, 1293, 1300, 1309, 1322, 1338, 1353, 1362, 1373, 1382, 1391, 1400, 1411, 1424, 1437}
|
||||||
|
|
||||||
|
func (i actionType) String() string {
|
||||||
|
if i < 0 || i >= actionType(len(_actionType_index)-1) {
|
||||||
|
return "actionType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _actionType_name[_actionType_index[i]:_actionType_index[i+1]]
|
||||||
|
}
|
@@ -221,9 +221,9 @@ func charClassOfAscii(char rune) charClass {
|
|||||||
return charUpper
|
return charUpper
|
||||||
} else if char >= '0' && char <= '9' {
|
} else if char >= '0' && char <= '9' {
|
||||||
return charNumber
|
return charNumber
|
||||||
} else if strings.IndexRune(whiteChars, char) >= 0 {
|
} else if strings.ContainsRune(whiteChars, char) {
|
||||||
return charWhite
|
return charWhite
|
||||||
} else if strings.IndexRune(delimiterChars, char) >= 0 {
|
} else if strings.ContainsRune(delimiterChars, char) {
|
||||||
return charDelimiter
|
return charDelimiter
|
||||||
}
|
}
|
||||||
return charNonWord
|
return charNonWord
|
||||||
@@ -240,7 +240,7 @@ func charClassOfNonAscii(char rune) charClass {
|
|||||||
return charLetter
|
return charLetter
|
||||||
} else if unicode.IsSpace(char) {
|
} else if unicode.IsSpace(char) {
|
||||||
return charWhite
|
return charWhite
|
||||||
} else if strings.IndexRune(delimiterChars, char) >= 0 {
|
} else if strings.ContainsRune(delimiterChars, char) {
|
||||||
return charDelimiter
|
return charDelimiter
|
||||||
}
|
}
|
||||||
return charNonWord
|
return charNonWord
|
||||||
|
18
src/ansi.go
18
src/ansi.go
@@ -351,9 +351,11 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
|||||||
ptr := &state.fg
|
ptr := &state.fg
|
||||||
|
|
||||||
var delimiter byte = 0
|
var delimiter byte = 0
|
||||||
|
count := 0
|
||||||
for len(ansiCode) != 0 {
|
for len(ansiCode) != 0 {
|
||||||
var num int
|
var num int
|
||||||
if num, delimiter, ansiCode = parseAnsiCode(ansiCode, delimiter); num != -1 {
|
if num, delimiter, ansiCode = parseAnsiCode(ansiCode, delimiter); num != -1 {
|
||||||
|
count++
|
||||||
switch state256 {
|
switch state256 {
|
||||||
case 0:
|
case 0:
|
||||||
switch num {
|
switch num {
|
||||||
@@ -381,10 +383,19 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
|||||||
state.attr = state.attr | tui.Reverse
|
state.attr = state.attr | tui.Reverse
|
||||||
case 9:
|
case 9:
|
||||||
state.attr = state.attr | tui.StrikeThrough
|
state.attr = state.attr | tui.StrikeThrough
|
||||||
|
case 22:
|
||||||
|
state.attr = state.attr &^ tui.Bold
|
||||||
|
state.attr = state.attr &^ tui.Dim
|
||||||
case 23: // tput rmso
|
case 23: // tput rmso
|
||||||
state.attr = state.attr &^ tui.Italic
|
state.attr = state.attr &^ tui.Italic
|
||||||
case 24: // tput rmul
|
case 24: // tput rmul
|
||||||
state.attr = state.attr &^ tui.Underline
|
state.attr = state.attr &^ tui.Underline
|
||||||
|
case 25:
|
||||||
|
state.attr = state.attr &^ tui.Blink
|
||||||
|
case 27:
|
||||||
|
state.attr = state.attr &^ tui.Reverse
|
||||||
|
case 29:
|
||||||
|
state.attr = state.attr &^ tui.StrikeThrough
|
||||||
case 0:
|
case 0:
|
||||||
state.fg = -1
|
state.fg = -1
|
||||||
state.bg = -1
|
state.bg = -1
|
||||||
@@ -426,6 +437,13 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Empty sequence: reset
|
||||||
|
if count == 0 {
|
||||||
|
state.fg = -1
|
||||||
|
state.bg = -1
|
||||||
|
state.attr = 0
|
||||||
|
}
|
||||||
|
|
||||||
if state256 > 0 {
|
if state256 > 0 {
|
||||||
*ptr = -1
|
*ptr = -1
|
||||||
}
|
}
|
||||||
|
@@ -348,6 +348,9 @@ func TestAnsiCodeStringConversion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
assert("\x1b[m", nil, "")
|
assert("\x1b[m", nil, "")
|
||||||
assert("\x1b[m", &ansiState{attr: tui.Blink, lbg: -1}, "")
|
assert("\x1b[m", &ansiState{attr: tui.Blink, lbg: -1}, "")
|
||||||
|
assert("\x1b[0m", &ansiState{fg: 4, bg: 4, lbg: -1}, "")
|
||||||
|
assert("\x1b[;m", &ansiState{fg: 4, bg: 4, lbg: -1}, "")
|
||||||
|
assert("\x1b[;;m", &ansiState{fg: 4, bg: 4, lbg: -1}, "")
|
||||||
|
|
||||||
assert("\x1b[31m", nil, "\x1b[31;49m")
|
assert("\x1b[31m", nil, "\x1b[31;49m")
|
||||||
assert("\x1b[41m", nil, "\x1b[39;41m")
|
assert("\x1b[41m", nil, "\x1b[39;41m")
|
||||||
|
@@ -58,9 +58,9 @@ var defaultCommand string
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if !util.IsWindows() {
|
if !util.IsWindows() {
|
||||||
defaultCommand = `set -o pipefail; command find -L . -mindepth 1 \( -path '*/\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-`
|
defaultCommand = `set -o pipefail; command find -L . -mindepth 1 \( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-`
|
||||||
} else if os.Getenv("TERM") == "cygwin" {
|
} else if os.Getenv("TERM") == "cygwin" {
|
||||||
defaultCommand = `sh -c "command find -L . -mindepth 1 -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-"`
|
defaultCommand = `sh -c "command find -L . -mindepth 1 -path '*/.*' -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-"`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -200,7 +200,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
padHeight := 0
|
padHeight := 0
|
||||||
heightUnknown := opts.Height.auto
|
heightUnknown := opts.Height.auto
|
||||||
if heightUnknown {
|
if heightUnknown {
|
||||||
maxFit, padHeight = terminal.MaxFitAndPad(opts)
|
maxFit, padHeight = terminal.MaxFitAndPad()
|
||||||
}
|
}
|
||||||
deferred := opts.Select1 || opts.Exit0
|
deferred := opts.Select1 || opts.Exit0
|
||||||
go terminal.Loop()
|
go terminal.Loop()
|
||||||
|
@@ -2,7 +2,6 @@ package fzf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -26,12 +25,12 @@ func NewHistory(path string, maxSize int) (*History, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read history file
|
// Read history file
|
||||||
data, err := ioutil.ReadFile(path)
|
data, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If it doesn't exist, check if we can create a file with the name
|
// If it doesn't exist, check if we can create a file with the name
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
data = []byte{}
|
data = []byte{}
|
||||||
if err := ioutil.WriteFile(path, data, 0600); err != nil {
|
if err := os.WriteFile(path, data, 0600); err != nil {
|
||||||
return nil, fmtError(err)
|
return nil, fmtError(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -62,11 +61,11 @@ func (h *History) append(line string) error {
|
|||||||
lines = lines[len(lines)-h.maxSize:]
|
lines = lines[len(lines)-h.maxSize:]
|
||||||
}
|
}
|
||||||
h.lines = append(lines, "")
|
h.lines = append(lines, "")
|
||||||
return ioutil.WriteFile(h.path, []byte(strings.Join(h.lines, "\n")), 0600)
|
return os.WriteFile(h.path, []byte(strings.Join(h.lines, "\n")), 0600)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *History) override(str string) {
|
func (h *History) override(str string) {
|
||||||
// You can update the history but they're not written to the file
|
// You can update the history, but they're not written to the file
|
||||||
if h.cursor == len(h.lines)-1 {
|
if h.cursor == len(h.lines)-1 {
|
||||||
h.lines[h.cursor] = str
|
h.lines[h.cursor] = str
|
||||||
} else if h.cursor < len(h.lines)-1 {
|
} else if h.cursor < len(h.lines)-1 {
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -25,7 +24,7 @@ func TestHistory(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f, _ := ioutil.TempFile("", "fzf-history")
|
f, _ := os.CreateTemp("", "fzf-history")
|
||||||
f.Close()
|
f.Close()
|
||||||
|
|
||||||
{ // Append lines
|
{ // Append lines
|
||||||
|
@@ -23,10 +23,10 @@ func randResult() Result {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEmptyMerger(t *testing.T) {
|
func TestEmptyMerger(t *testing.T) {
|
||||||
assert(t, EmptyMerger.Length() == 0, "Not empty")
|
assert(t, EmptyMerger(0).Length() == 0, "Not empty")
|
||||||
assert(t, EmptyMerger.count == 0, "Invalid count")
|
assert(t, EmptyMerger(0).count == 0, "Invalid count")
|
||||||
assert(t, len(EmptyMerger.lists) == 0, "Invalid lists")
|
assert(t, len(EmptyMerger(0).lists) == 0, "Invalid lists")
|
||||||
assert(t, len(EmptyMerger.merged) == 0, "Invalid merged list")
|
assert(t, len(EmptyMerger(0).merged) == 0, "Invalid merged list")
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildLists(partiallySorted bool) ([][]Result, []Result) {
|
func buildLists(partiallySorted bool) ([][]Result, []Result) {
|
||||||
|
164
src/options.go
164
src/options.go
@@ -12,8 +12,8 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/tui"
|
"github.com/junegunn/fzf/src/tui"
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
|
||||||
"github.com/mattn/go-runewidth"
|
|
||||||
"github.com/mattn/go-shellwords"
|
"github.com/mattn/go-shellwords"
|
||||||
|
"github.com/rivo/uniseg"
|
||||||
)
|
)
|
||||||
|
|
||||||
const usage = `usage: fzf [options]
|
const usage = `usage: fzf [options]
|
||||||
@@ -57,13 +57,15 @@ const usage = `usage: fzf [options]
|
|||||||
Layout
|
Layout
|
||||||
--height=[~]HEIGHT[%] Display fzf window below the cursor with the given
|
--height=[~]HEIGHT[%] Display fzf window below the cursor with the given
|
||||||
height instead of using fullscreen.
|
height instead of using fullscreen.
|
||||||
|
A negative value is calcalated as the terminal height
|
||||||
|
minus the given value.
|
||||||
If prefixed with '~', fzf will determine the height
|
If prefixed with '~', fzf will determine the height
|
||||||
according to the input size.
|
according to the input size.
|
||||||
--min-height=HEIGHT Minimum height when --height is given in percent
|
--min-height=HEIGHT Minimum height when --height is given in percent
|
||||||
(default: 10)
|
(default: 10)
|
||||||
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
|
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
|
||||||
--border[=STYLE] Draw border around the finder
|
--border[=STYLE] Draw border around the finder
|
||||||
[rounded|sharp|bold|block|double|horizontal|vertical|
|
[rounded|sharp|bold|block|thinblock|double|horizontal|vertical|
|
||||||
top|bottom|left|right|none] (default: rounded)
|
top|bottom|left|right|none] (default: rounded)
|
||||||
--border-label=LABEL Label to print on the border
|
--border-label=LABEL Label to print on the border
|
||||||
--border-label-pos=COL Position of the border label
|
--border-label-pos=COL Position of the border label
|
||||||
@@ -72,7 +74,8 @@ const usage = `usage: fzf [options]
|
|||||||
(default: 0 or center)
|
(default: 0 or center)
|
||||||
--margin=MARGIN Screen margin (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
--margin=MARGIN Screen margin (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
||||||
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
||||||
--info=STYLE Finder info style [default|hidden|inline|inline:SEPARATOR]
|
--info=STYLE Finder info style
|
||||||
|
[default|right|hidden|inline[:SEPARATOR]|inline-right]
|
||||||
--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)
|
||||||
@@ -117,13 +120,15 @@ const usage = `usage: fzf [options]
|
|||||||
--read0 Read input delimited by ASCII NUL characters
|
--read0 Read input delimited by ASCII NUL characters
|
||||||
--print0 Print output delimited by ASCII NUL characters
|
--print0 Print output delimited by ASCII NUL characters
|
||||||
--sync Synchronous search for multi-staged filtering
|
--sync Synchronous search for multi-staged filtering
|
||||||
--listen[=HTTP_PORT] Start HTTP server to receive actions (POST /)
|
--listen[=[ADDR:]PORT] Start HTTP server to receive actions (POST /)
|
||||||
|
(To allow remote process execution, use --listen-unsafe)
|
||||||
--version Display version information and exit
|
--version Display version information and exit
|
||||||
|
|
||||||
Environment variables
|
Environment variables
|
||||||
FZF_DEFAULT_COMMAND Default command to use when input is tty
|
FZF_DEFAULT_COMMAND Default command to use when input is tty
|
||||||
FZF_DEFAULT_OPTS Default options
|
FZF_DEFAULT_OPTS Default options
|
||||||
(e.g. '--layout=reverse --inline-info')
|
(e.g. '--layout=reverse --inline-info')
|
||||||
|
FZF_API_KEY X-API-Key header for HTTP server (--listen)
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -154,6 +159,7 @@ type heightSpec struct {
|
|||||||
size float64
|
size float64
|
||||||
percent bool
|
percent bool
|
||||||
auto bool
|
auto bool
|
||||||
|
inverse bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type sizeSpec struct {
|
type sizeSpec struct {
|
||||||
@@ -194,10 +200,16 @@ type infoStyle int
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
infoDefault infoStyle = iota
|
infoDefault infoStyle = iota
|
||||||
|
infoRight
|
||||||
infoInline
|
infoInline
|
||||||
|
infoInlineRight
|
||||||
infoHidden
|
infoHidden
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (s infoStyle) noExtraLine() bool {
|
||||||
|
return s == infoInline || s == infoInlineRight || s == infoHidden
|
||||||
|
}
|
||||||
|
|
||||||
type labelOpts struct {
|
type labelOpts struct {
|
||||||
label string
|
label string
|
||||||
column int
|
column int
|
||||||
@@ -325,8 +337,10 @@ type Options struct {
|
|||||||
BorderLabel labelOpts
|
BorderLabel labelOpts
|
||||||
PreviewLabel labelOpts
|
PreviewLabel labelOpts
|
||||||
Unicode bool
|
Unicode bool
|
||||||
|
Ambidouble bool
|
||||||
Tabstop int
|
Tabstop int
|
||||||
ListenPort *int
|
ListenAddr *listenAddress
|
||||||
|
Unsafe bool
|
||||||
ClearOnExit bool
|
ClearOnExit bool
|
||||||
Version bool
|
Version bool
|
||||||
}
|
}
|
||||||
@@ -393,9 +407,11 @@ func defaultOptions() *Options {
|
|||||||
Margin: defaultMargin(),
|
Margin: defaultMargin(),
|
||||||
Padding: defaultMargin(),
|
Padding: defaultMargin(),
|
||||||
Unicode: true,
|
Unicode: true,
|
||||||
|
Ambidouble: os.Getenv("RUNEWIDTH_EASTASIAN") == "1",
|
||||||
Tabstop: 8,
|
Tabstop: 8,
|
||||||
BorderLabel: labelOpts{},
|
BorderLabel: labelOpts{},
|
||||||
PreviewLabel: labelOpts{},
|
PreviewLabel: labelOpts{},
|
||||||
|
Unsafe: false,
|
||||||
ClearOnExit: true,
|
ClearOnExit: true,
|
||||||
Version: false}
|
Version: false}
|
||||||
}
|
}
|
||||||
@@ -546,6 +562,8 @@ func parseBorder(str string, optional bool) tui.BorderShape {
|
|||||||
return tui.BorderBold
|
return tui.BorderBold
|
||||||
case "block":
|
case "block":
|
||||||
return tui.BorderBlock
|
return tui.BorderBlock
|
||||||
|
case "thinblock":
|
||||||
|
return tui.BorderThinBlock
|
||||||
case "double":
|
case "double":
|
||||||
return tui.BorderDouble
|
return tui.BorderDouble
|
||||||
case "horizontal":
|
case "horizontal":
|
||||||
@@ -566,7 +584,7 @@ func parseBorder(str string, optional bool) tui.BorderShape {
|
|||||||
if optional && str == "" {
|
if optional && str == "" {
|
||||||
return tui.DefaultBorderShape
|
return tui.DefaultBorderShape
|
||||||
}
|
}
|
||||||
errorExit("invalid border style (expected: rounded|sharp|bold|block|double|horizontal|vertical|top|bottom|left|right|none)")
|
errorExit("invalid border style (expected: rounded|sharp|bold|block|thinblock|double|horizontal|vertical|top|bottom|left|right|none)")
|
||||||
}
|
}
|
||||||
return tui.BorderNone
|
return tui.BorderNone
|
||||||
}
|
}
|
||||||
@@ -634,6 +652,10 @@ func parseKeyChordsImpl(str string, message string, exit func(string)) map[tui.E
|
|||||||
add(tui.Load)
|
add(tui.Load)
|
||||||
case "focus":
|
case "focus":
|
||||||
add(tui.Focus)
|
add(tui.Focus)
|
||||||
|
case "result":
|
||||||
|
add(tui.Result)
|
||||||
|
case "resize":
|
||||||
|
add(tui.Resize)
|
||||||
case "one":
|
case "one":
|
||||||
add(tui.One)
|
add(tui.One)
|
||||||
case "zero":
|
case "zero":
|
||||||
@@ -692,8 +714,24 @@ func parseKeyChordsImpl(str string, message string, exit func(string)) map[tui.E
|
|||||||
add(tui.LeftClick)
|
add(tui.LeftClick)
|
||||||
case "right-click":
|
case "right-click":
|
||||||
add(tui.RightClick)
|
add(tui.RightClick)
|
||||||
|
case "shift-left-click":
|
||||||
|
add(tui.SLeftClick)
|
||||||
|
case "shift-right-click":
|
||||||
|
add(tui.SRightClick)
|
||||||
case "double-click":
|
case "double-click":
|
||||||
add(tui.DoubleClick)
|
add(tui.DoubleClick)
|
||||||
|
case "scroll-up":
|
||||||
|
add(tui.ScrollUp)
|
||||||
|
case "scroll-down":
|
||||||
|
add(tui.ScrollDown)
|
||||||
|
case "shift-scroll-up":
|
||||||
|
add(tui.SScrollUp)
|
||||||
|
case "shift-scroll-down":
|
||||||
|
add(tui.SScrollDown)
|
||||||
|
case "preview-scroll-up":
|
||||||
|
add(tui.PreviewScrollUp)
|
||||||
|
case "preview-scroll-down":
|
||||||
|
add(tui.PreviewScrollDown)
|
||||||
case "f10":
|
case "f10":
|
||||||
add(tui.F10)
|
add(tui.F10)
|
||||||
case "f11":
|
case "f11":
|
||||||
@@ -947,7 +985,7 @@ const (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
executeRegexp = regexp.MustCompile(
|
executeRegexp = regexp.MustCompile(
|
||||||
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:header|query|prompt|border-label|preview-label)|change-preview-window|change-preview|(?:re|un)bind|pos|put)`)
|
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:header|query|prompt|border-label|preview-label)|transform|change-preview-window|change-preview|(?:re|un)bind|pos|put)`)
|
||||||
splitRegexp = regexp.MustCompile("[,:]+")
|
splitRegexp = regexp.MustCompile("[,:]+")
|
||||||
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
|
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
|
||||||
}
|
}
|
||||||
@@ -1041,6 +1079,8 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
|||||||
appendAction(actAccept)
|
appendAction(actAccept)
|
||||||
case "accept-non-empty":
|
case "accept-non-empty":
|
||||||
appendAction(actAcceptNonEmpty)
|
appendAction(actAcceptNonEmpty)
|
||||||
|
case "accept-or-print-query":
|
||||||
|
appendAction(actAcceptOrPrintQuery)
|
||||||
case "print-query":
|
case "print-query":
|
||||||
appendAction(actPrintQuery)
|
appendAction(actPrintQuery)
|
||||||
case "refresh-preview":
|
case "refresh-preview":
|
||||||
@@ -1052,7 +1092,7 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
|||||||
case "backward-delete-char":
|
case "backward-delete-char":
|
||||||
appendAction(actBackwardDeleteChar)
|
appendAction(actBackwardDeleteChar)
|
||||||
case "backward-delete-char/eof":
|
case "backward-delete-char/eof":
|
||||||
appendAction(actBackwardDeleteCharEOF)
|
appendAction(actBackwardDeleteCharEof)
|
||||||
case "backward-word":
|
case "backward-word":
|
||||||
appendAction(actBackwardWord)
|
appendAction(actBackwardWord)
|
||||||
case "clear-screen":
|
case "clear-screen":
|
||||||
@@ -1060,7 +1100,7 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
|||||||
case "delete-char":
|
case "delete-char":
|
||||||
appendAction(actDeleteChar)
|
appendAction(actDeleteChar)
|
||||||
case "delete-char/eof":
|
case "delete-char/eof":
|
||||||
appendAction(actDeleteCharEOF)
|
appendAction(actDeleteCharEof)
|
||||||
case "deselect":
|
case "deselect":
|
||||||
appendAction(actDeselect)
|
appendAction(actDeselect)
|
||||||
case "end-of-line":
|
case "end-of-line":
|
||||||
@@ -1105,6 +1145,12 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
|||||||
appendAction(actToggleSearch)
|
appendAction(actToggleSearch)
|
||||||
case "toggle-track":
|
case "toggle-track":
|
||||||
appendAction(actToggleTrack)
|
appendAction(actToggleTrack)
|
||||||
|
case "toggle-header":
|
||||||
|
appendAction(actToggleHeader)
|
||||||
|
case "show-header":
|
||||||
|
appendAction(actShowHeader)
|
||||||
|
case "hide-header":
|
||||||
|
appendAction(actHideHeader)
|
||||||
case "track":
|
case "track":
|
||||||
appendAction(actTrack)
|
appendAction(actTrack)
|
||||||
case "select":
|
case "select":
|
||||||
@@ -1151,6 +1197,10 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
|||||||
appendAction(actTogglePreviewWrap)
|
appendAction(actTogglePreviewWrap)
|
||||||
case "toggle-sort":
|
case "toggle-sort":
|
||||||
appendAction(actToggleSort)
|
appendAction(actToggleSort)
|
||||||
|
case "offset-up":
|
||||||
|
appendAction(actOffsetUp)
|
||||||
|
case "offset-down":
|
||||||
|
appendAction(actOffsetDown)
|
||||||
case "preview-top":
|
case "preview-top":
|
||||||
appendAction(actPreviewTop)
|
appendAction(actPreviewTop)
|
||||||
case "preview-bottom":
|
case "preview-bottom":
|
||||||
@@ -1173,7 +1223,7 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
|||||||
appendAction(actDisableSearch)
|
appendAction(actDisableSearch)
|
||||||
case "put":
|
case "put":
|
||||||
if putAllowed {
|
if putAllowed {
|
||||||
appendAction(actRune)
|
appendAction(actChar)
|
||||||
} else {
|
} else {
|
||||||
exit("unable to put non-printable character")
|
exit("unable to put non-printable character")
|
||||||
}
|
}
|
||||||
@@ -1293,6 +1343,8 @@ func isExecuteAction(str string) actionType {
|
|||||||
return actExecuteMulti
|
return actExecuteMulti
|
||||||
case "put":
|
case "put":
|
||||||
return actPut
|
return actPut
|
||||||
|
case "transform":
|
||||||
|
return actTransform
|
||||||
case "transform-border-label":
|
case "transform-border-label":
|
||||||
return actTransformBorderLabel
|
return actTransformBorderLabel
|
||||||
case "transform-preview-label":
|
case "transform-preview-label":
|
||||||
@@ -1349,6 +1401,13 @@ func parseHeight(str string) heightSpec {
|
|||||||
heightSpec.auto = true
|
heightSpec.auto = true
|
||||||
str = str[1:]
|
str = str[1:]
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(str, "-") {
|
||||||
|
if heightSpec.auto {
|
||||||
|
errorExit("negative(-) height is not compatible with adaptive(~) height")
|
||||||
|
}
|
||||||
|
heightSpec.inverse = true
|
||||||
|
str = str[1:]
|
||||||
|
}
|
||||||
|
|
||||||
size := parseSize(str, 100, "height")
|
size := parseSize(str, 100, "height")
|
||||||
heightSpec.size = size.size
|
heightSpec.size = size.size
|
||||||
@@ -1374,8 +1433,12 @@ func parseInfoStyle(str string) (infoStyle, string) {
|
|||||||
switch str {
|
switch str {
|
||||||
case "default":
|
case "default":
|
||||||
return infoDefault, ""
|
return infoDefault, ""
|
||||||
|
case "right":
|
||||||
|
return infoRight, ""
|
||||||
case "inline":
|
case "inline":
|
||||||
return infoInline, defaultInfoSep
|
return infoInline, defaultInfoSep
|
||||||
|
case "inline-right":
|
||||||
|
return infoInlineRight, ""
|
||||||
case "hidden":
|
case "hidden":
|
||||||
return infoHidden, ""
|
return infoHidden, ""
|
||||||
default:
|
default:
|
||||||
@@ -1383,7 +1446,7 @@ func parseInfoStyle(str string) (infoStyle, string) {
|
|||||||
if strings.HasPrefix(str, prefix) {
|
if strings.HasPrefix(str, prefix) {
|
||||||
return infoInline, strings.ReplaceAll(str[len(prefix):], "\n", " ")
|
return infoInline, strings.ReplaceAll(str[len(prefix):], "\n", " ")
|
||||||
}
|
}
|
||||||
errorExit("invalid info style (expected: default|hidden|inline|inline:SEPARATOR)")
|
errorExit("invalid info style (expected: default|right|hidden|inline[:SEPARATOR]|inline-right)")
|
||||||
}
|
}
|
||||||
return infoDefault, ""
|
return infoDefault, ""
|
||||||
}
|
}
|
||||||
@@ -1438,6 +1501,8 @@ func parsePreviewWindowImpl(opts *previewOpts, input string, exit func(string))
|
|||||||
opts.border = tui.BorderBold
|
opts.border = tui.BorderBold
|
||||||
case "border-block":
|
case "border-block":
|
||||||
opts.border = tui.BorderBlock
|
opts.border = tui.BorderBlock
|
||||||
|
case "border-thinblock":
|
||||||
|
opts.border = tui.BorderThinBlock
|
||||||
case "border-double":
|
case "border-double":
|
||||||
opts.border = tui.BorderDouble
|
opts.border = tui.BorderDouble
|
||||||
case "noborder", "border-none":
|
case "noborder", "border-none":
|
||||||
@@ -1532,8 +1597,6 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
validateJumpLabels := false
|
validateJumpLabels := false
|
||||||
validatePointer := false
|
|
||||||
validateMarker := false
|
|
||||||
for i := 0; i < len(allArgs); i++ {
|
for i := 0; i < len(allArgs); i++ {
|
||||||
arg := allArgs[i]
|
arg := allArgs[i]
|
||||||
switch arg {
|
switch arg {
|
||||||
@@ -1713,10 +1776,8 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Prompt = nextString(allArgs, &i, "prompt string required")
|
opts.Prompt = nextString(allArgs, &i, "prompt string required")
|
||||||
case "--pointer":
|
case "--pointer":
|
||||||
opts.Pointer = firstLine(nextString(allArgs, &i, "pointer sign string required"))
|
opts.Pointer = firstLine(nextString(allArgs, &i, "pointer sign string required"))
|
||||||
validatePointer = true
|
|
||||||
case "--marker":
|
case "--marker":
|
||||||
opts.Marker = firstLine(nextString(allArgs, &i, "selected sign string required"))
|
opts.Marker = firstLine(nextString(allArgs, &i, "selected sign string required"))
|
||||||
validateMarker = true
|
|
||||||
case "--sync":
|
case "--sync":
|
||||||
opts.Sync = true
|
opts.Sync = true
|
||||||
case "--no-sync":
|
case "--no-sync":
|
||||||
@@ -1784,6 +1845,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Unicode = false
|
opts.Unicode = false
|
||||||
case "--unicode":
|
case "--unicode":
|
||||||
opts.Unicode = true
|
opts.Unicode = true
|
||||||
|
case "--ambidouble":
|
||||||
|
opts.Ambidouble = true
|
||||||
|
case "--no-ambidouble":
|
||||||
|
opts.Ambidouble = false
|
||||||
case "--margin":
|
case "--margin":
|
||||||
opts.Margin = parseMargin(
|
opts.Margin = parseMargin(
|
||||||
"margin",
|
"margin",
|
||||||
@@ -1794,11 +1859,21 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
nextString(allArgs, &i, "padding required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
|
nextString(allArgs, &i, "padding required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
|
||||||
case "--tabstop":
|
case "--tabstop":
|
||||||
opts.Tabstop = nextInt(allArgs, &i, "tab stop required")
|
opts.Tabstop = nextInt(allArgs, &i, "tab stop required")
|
||||||
case "--listen":
|
case "--listen", "--listen-unsafe":
|
||||||
port := optionalNumeric(allArgs, &i, 0)
|
given, str := optionalNextString(allArgs, &i)
|
||||||
opts.ListenPort = &port
|
addr := defaultListenAddr
|
||||||
case "--no-listen":
|
if given {
|
||||||
opts.ListenPort = nil
|
var err error
|
||||||
|
err, addr = parseListenAddress(str)
|
||||||
|
if err != nil {
|
||||||
|
errorExit(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opts.ListenAddr = &addr
|
||||||
|
opts.Unsafe = arg == "--listen-unsafe"
|
||||||
|
case "--no-listen", "--no-listen-unsafe":
|
||||||
|
opts.ListenAddr = nil
|
||||||
|
opts.Unsafe = false
|
||||||
case "--clear":
|
case "--clear":
|
||||||
opts.ClearOnExit = true
|
opts.ClearOnExit = true
|
||||||
case "--no-clear":
|
case "--no-clear":
|
||||||
@@ -1832,10 +1907,8 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Prompt = value
|
opts.Prompt = value
|
||||||
} else if match, value := optString(arg, "--pointer="); match {
|
} else if match, value := optString(arg, "--pointer="); match {
|
||||||
opts.Pointer = firstLine(value)
|
opts.Pointer = firstLine(value)
|
||||||
validatePointer = true
|
|
||||||
} else if match, value := optString(arg, "--marker="); match {
|
} else if match, value := optString(arg, "--marker="); match {
|
||||||
opts.Marker = firstLine(value)
|
opts.Marker = firstLine(value)
|
||||||
validateMarker = true
|
|
||||||
} else if match, value := optString(arg, "-n", "--nth="); match {
|
} else if match, value := optString(arg, "-n", "--nth="); match {
|
||||||
opts.Nth = splitNth(value)
|
opts.Nth = splitNth(value)
|
||||||
} else if match, value := optString(arg, "--with-nth="); match {
|
} else if match, value := optString(arg, "--with-nth="); match {
|
||||||
@@ -1889,8 +1962,19 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
} else if match, value := optString(arg, "--tabstop="); match {
|
} else if match, value := optString(arg, "--tabstop="); match {
|
||||||
opts.Tabstop = atoi(value)
|
opts.Tabstop = atoi(value)
|
||||||
} else if match, value := optString(arg, "--listen="); match {
|
} else if match, value := optString(arg, "--listen="); match {
|
||||||
port := atoi(value)
|
err, addr := parseListenAddress(value)
|
||||||
opts.ListenPort = &port
|
if err != nil {
|
||||||
|
errorExit(err.Error())
|
||||||
|
}
|
||||||
|
opts.ListenAddr = &addr
|
||||||
|
opts.Unsafe = false
|
||||||
|
} else if match, value := optString(arg, "--listen-unsafe="); match {
|
||||||
|
err, addr := parseListenAddress(value)
|
||||||
|
if err != nil {
|
||||||
|
errorExit(err.Error())
|
||||||
|
}
|
||||||
|
opts.ListenAddr = &addr
|
||||||
|
opts.Unsafe = true
|
||||||
} else if match, value := optString(arg, "--hscroll-off="); match {
|
} else if match, value := optString(arg, "--hscroll-off="); match {
|
||||||
opts.HscrollOff = atoi(value)
|
opts.HscrollOff = atoi(value)
|
||||||
} else if match, value := optString(arg, "--scroll-off="); match {
|
} else if match, value := optString(arg, "--scroll-off="); match {
|
||||||
@@ -1920,10 +2004,6 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
errorExit("tab stop must be a positive integer")
|
errorExit("tab stop must be a positive integer")
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.ListenPort != nil && (*opts.ListenPort < 0 || *opts.ListenPort > 65535) {
|
|
||||||
errorExit("invalid listen port")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(opts.JumpLabels) == 0 {
|
if len(opts.JumpLabels) == 0 {
|
||||||
errorExit("empty jump labels")
|
errorExit("empty jump labels")
|
||||||
}
|
}
|
||||||
@@ -1935,31 +2015,31 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if validatePointer {
|
|
||||||
if err := validateSign(opts.Pointer, "pointer"); err != nil {
|
|
||||||
errorExit(err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if validateMarker {
|
|
||||||
if err := validateSign(opts.Marker, "marker"); err != nil {
|
|
||||||
errorExit(err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateSign(sign string, signOptName string) error {
|
func validateSign(sign string, signOptName string) error {
|
||||||
if sign == "" {
|
if sign == "" {
|
||||||
return fmt.Errorf("%v cannot be empty", signOptName)
|
return fmt.Errorf("%v cannot be empty", signOptName)
|
||||||
}
|
}
|
||||||
if runewidth.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)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func postProcessOptions(opts *Options) {
|
func postProcessOptions(opts *Options) {
|
||||||
|
if opts.Ambidouble {
|
||||||
|
uniseg.EastAsianAmbiguousWidth = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateSign(opts.Pointer, "pointer"); err != nil {
|
||||||
|
errorExit(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateSign(opts.Marker, "marker"); err != nil {
|
||||||
|
errorExit(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
if !opts.Version && !tui.IsLightRendererSupported() && opts.Height.size > 0 {
|
if !opts.Version && !tui.IsLightRendererSupported() && opts.Height.size > 0 {
|
||||||
errorExit("--height option is currently not supported on this platform")
|
errorExit("--height option is currently not supported on this platform")
|
||||||
}
|
}
|
||||||
@@ -1970,7 +2050,7 @@ func postProcessOptions(opts *Options) {
|
|||||||
errorExit("--scrollbar should be given one or two characters")
|
errorExit("--scrollbar should be given one or two characters")
|
||||||
}
|
}
|
||||||
for _, r := range runes {
|
for _, r := range runes {
|
||||||
if runewidth.RuneWidth(r) != 1 {
|
if uniseg.StringWidth(string(r)) != 1 {
|
||||||
errorExit("scrollbar display width should be 1")
|
errorExit("scrollbar display width should be 1")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@ package fzf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/tui"
|
"github.com/junegunn/fzf/src/tui"
|
||||||
@@ -357,7 +357,7 @@ func TestDefaultCtrlNP(t *testing.T) {
|
|||||||
check([]string{"--bind=ctrl-n:accept"}, tui.CtrlN, actAccept)
|
check([]string{"--bind=ctrl-n:accept"}, tui.CtrlN, actAccept)
|
||||||
check([]string{"--bind=ctrl-p:accept"}, tui.CtrlP, actAccept)
|
check([]string{"--bind=ctrl-p:accept"}, tui.CtrlP, actAccept)
|
||||||
|
|
||||||
f, _ := ioutil.TempFile("", "fzf-history")
|
f, _ := os.CreateTemp("", "fzf-history")
|
||||||
f.Close()
|
f.Close()
|
||||||
hist := "--history=" + f.Name()
|
hist := "--history=" + f.Name()
|
||||||
check([]string{hist}, tui.CtrlN, actNextHistory)
|
check([]string{hist}, tui.CtrlN, actNextHistory)
|
||||||
|
@@ -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")
|
unix.PledgePromises("stdio rpath tty proc exec inet tmppath")
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -98,8 +99,17 @@ func (r *Reader) ReadSource() {
|
|||||||
r.startEventPoller()
|
r.startEventPoller()
|
||||||
var success bool
|
var success bool
|
||||||
if util.IsTty() {
|
if util.IsTty() {
|
||||||
// The default command for *nix requires bash
|
// The default command for *nix requires a shell that supports "pipefail"
|
||||||
|
// https://unix.stackexchange.com/a/654932/62171
|
||||||
shell := "bash"
|
shell := "bash"
|
||||||
|
currentShell := os.Getenv("SHELL")
|
||||||
|
currentShellName := path.Base(currentShell)
|
||||||
|
for _, shellName := range []string{"bash", "zsh", "ksh", "ash", "hush", "mksh", "yash"} {
|
||||||
|
if currentShellName == shellName {
|
||||||
|
shell = currentShell
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
||||||
if len(cmd) == 0 {
|
if len(cmd) == 0 {
|
||||||
if defaultCommand != "" {
|
if defaultCommand != "" {
|
||||||
|
@@ -80,7 +80,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()))
|
val = util.AsUint16(math.MaxUint16 - math.MaxUint16*(maxEnd-whitePrefixLen)/int(item.TrimLength()+1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,9 +138,11 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
|
|||||||
for i := off[0]; i < off[1]; i++ {
|
for i := off[0]; i < off[1]; i++ {
|
||||||
// Negative of 1-based index of itemColors
|
// Negative of 1-based index of itemColors
|
||||||
// - The extra -1 means highlighted
|
// - The extra -1 means highlighted
|
||||||
|
if cols[i] >= 0 {
|
||||||
cols[i] = cols[i]*-1 - 1
|
cols[i] = cols[i]*-1 - 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// sort.Sort(ByOrder(offsets))
|
// sort.Sort(ByOrder(offsets))
|
||||||
|
|
||||||
|
@@ -120,7 +120,7 @@ func TestColorOffset(t *testing.T) {
|
|||||||
// ++++++++ ++++++++++
|
// ++++++++ ++++++++++
|
||||||
// --++++++++-- --++++++++++---
|
// --++++++++-- --++++++++++---
|
||||||
|
|
||||||
offsets := []Offset{{5, 15}, {25, 35}}
|
offsets := []Offset{{5, 15}, {10, 12}, {25, 35}}
|
||||||
item := Result{
|
item := Result{
|
||||||
item: &Item{
|
item: &Item{
|
||||||
colors: &[]ansiOffset{
|
colors: &[]ansiOffset{
|
||||||
|
148
src/server.go
148
src/server.go
@@ -3,44 +3,106 @@ package fzf
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/subtle"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var getRegex *regexp.Regexp
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
getRegex = regexp.MustCompile(`^GET /(?:\?([a-z0-9=&]+))? HTTP`)
|
||||||
|
}
|
||||||
|
|
||||||
|
type getParams struct {
|
||||||
|
limit int
|
||||||
|
offset int
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
crlf = "\r\n"
|
crlf = "\r\n"
|
||||||
httpOk = "HTTP/1.1 200 OK" + crlf
|
httpOk = "HTTP/1.1 200 OK" + crlf
|
||||||
httpBadRequest = "HTTP/1.1 400 Bad Request" + crlf
|
httpBadRequest = "HTTP/1.1 400 Bad Request" + crlf
|
||||||
|
httpUnauthorized = "HTTP/1.1 401 Unauthorized" + crlf
|
||||||
|
httpUnavailable = "HTTP/1.1 503 Service Unavailable" + crlf
|
||||||
httpReadTimeout = 10 * time.Second
|
httpReadTimeout = 10 * time.Second
|
||||||
|
jsonContentType = "Content-Type: application/json" + crlf
|
||||||
maxContentLength = 1024 * 1024
|
maxContentLength = 1024 * 1024
|
||||||
)
|
)
|
||||||
|
|
||||||
func startHttpServer(port int, channel chan []*action) (error, int) {
|
type httpServer struct {
|
||||||
if port < 0 {
|
apiKey []byte
|
||||||
return nil, port
|
actionChannel chan []*action
|
||||||
}
|
responseChannel chan string
|
||||||
|
}
|
||||||
|
|
||||||
listener, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", port))
|
type listenAddress struct {
|
||||||
|
host string
|
||||||
|
port int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (addr listenAddress) IsLocal() bool {
|
||||||
|
return addr.host == "localhost" || addr.host == "127.0.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultListenAddr = listenAddress{"localhost", 0}
|
||||||
|
|
||||||
|
func parseListenAddress(address string) (error, listenAddress) {
|
||||||
|
parts := strings.SplitN(address, ":", 3)
|
||||||
|
if len(parts) == 1 {
|
||||||
|
parts = []string{"localhost", parts[0]}
|
||||||
|
}
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return fmt.Errorf("invalid listen address: %s", address), defaultListenAddr
|
||||||
|
}
|
||||||
|
portStr := parts[len(parts)-1]
|
||||||
|
port, err := strconv.Atoi(portStr)
|
||||||
|
if err != nil || port < 0 || port > 65535 {
|
||||||
|
return fmt.Errorf("invalid listen port: %s", portStr), defaultListenAddr
|
||||||
|
}
|
||||||
|
if len(parts[0]) == 0 {
|
||||||
|
parts[0] = "localhost"
|
||||||
|
}
|
||||||
|
return nil, listenAddress{parts[0], port}
|
||||||
|
}
|
||||||
|
|
||||||
|
func startHttpServer(address listenAddress, actionChannel chan []*action, responseChannel chan string) (error, int) {
|
||||||
|
host := address.host
|
||||||
|
port := address.port
|
||||||
|
apiKey := os.Getenv("FZF_API_KEY")
|
||||||
|
if !address.IsLocal() && len(apiKey) == 0 {
|
||||||
|
return fmt.Errorf("FZF_API_KEY is required to allow remote access"), port
|
||||||
|
}
|
||||||
|
addrStr := fmt.Sprintf("%s:%d", host, port)
|
||||||
|
listener, err := net.Listen("tcp", addrStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("port not available: %d", port), port
|
return fmt.Errorf("failed to listen on %s", addrStr), port
|
||||||
}
|
}
|
||||||
if port == 0 {
|
if port == 0 {
|
||||||
addr := listener.Addr().String()
|
addr := listener.Addr().String()
|
||||||
parts := strings.SplitN(addr, ":", 2)
|
parts := strings.Split(addr, ":")
|
||||||
if len(parts) < 2 {
|
if len(parts) < 2 {
|
||||||
return fmt.Errorf("cannot extract port: %s", addr), port
|
return fmt.Errorf("cannot extract port: %s", addr), port
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
port, err = strconv.Atoi(parts[1])
|
port, err = strconv.Atoi(parts[len(parts)-1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err, port
|
return err, port
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server := httpServer{
|
||||||
|
apiKey: []byte(apiKey),
|
||||||
|
actionChannel: actionChannel,
|
||||||
|
responseChannel: responseChannel,
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
conn, err := listener.Accept()
|
conn, err := listener.Accept()
|
||||||
@@ -51,7 +113,7 @@ func startHttpServer(port int, channel chan []*action) (error, int) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
conn.Write([]byte(handleHttpRequest(conn, channel)))
|
conn.Write([]byte(server.handleHttpRequest(conn)))
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}
|
}
|
||||||
listener.Close()
|
listener.Close()
|
||||||
@@ -66,12 +128,22 @@ func startHttpServer(port int, channel chan []*action) (error, int) {
|
|||||||
// * No --listen: 2.8MB
|
// * No --listen: 2.8MB
|
||||||
// * --listen with net/http: 5.7MB
|
// * --listen with net/http: 5.7MB
|
||||||
// * --listen w/o net/http: 3.3MB
|
// * --listen w/o net/http: 3.3MB
|
||||||
func handleHttpRequest(conn net.Conn, channel chan []*action) string {
|
func (server *httpServer) handleHttpRequest(conn net.Conn) string {
|
||||||
contentLength := 0
|
contentLength := 0
|
||||||
|
apiKey := ""
|
||||||
body := ""
|
body := ""
|
||||||
bad := func(message string) string {
|
answer := func(code string, message string) string {
|
||||||
message += "\n"
|
message += "\n"
|
||||||
return httpBadRequest + fmt.Sprintf("Content-Length: %d%s", len(message), crlf+crlf+message)
|
return code + fmt.Sprintf("Content-Length: %d%s", len(message), crlf+crlf+message)
|
||||||
|
}
|
||||||
|
unauthorized := func(message string) string {
|
||||||
|
return answer(httpUnauthorized, message)
|
||||||
|
}
|
||||||
|
bad := func(message string) string {
|
||||||
|
return answer(httpBadRequest, message)
|
||||||
|
}
|
||||||
|
good := func(message string) string {
|
||||||
|
return answer(httpOk+jsonContentType, message)
|
||||||
}
|
}
|
||||||
conn.SetReadDeadline(time.Now().Add(httpReadTimeout))
|
conn.SetReadDeadline(time.Now().Add(httpReadTimeout))
|
||||||
scanner := bufio.NewScanner(conn)
|
scanner := bufio.NewScanner(conn)
|
||||||
@@ -92,7 +164,20 @@ func handleHttpRequest(conn net.Conn, channel chan []*action) string {
|
|||||||
text := scanner.Text()
|
text := scanner.Text()
|
||||||
switch section {
|
switch section {
|
||||||
case 0:
|
case 0:
|
||||||
if !strings.HasPrefix(text, "POST / HTTP") {
|
getMatch := getRegex.FindStringSubmatch(text)
|
||||||
|
if len(getMatch) > 0 {
|
||||||
|
server.actionChannel <- []*action{{t: actResponse, a: getMatch[1]}}
|
||||||
|
select {
|
||||||
|
case response := <-server.responseChannel:
|
||||||
|
return good(response)
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
go func() {
|
||||||
|
// Drain the channel
|
||||||
|
<-server.responseChannel
|
||||||
|
}()
|
||||||
|
return answer(httpUnavailable+jsonContentType, `{"error":"timeout"}`)
|
||||||
|
}
|
||||||
|
} else if !strings.HasPrefix(text, "POST / HTTP") {
|
||||||
return bad("invalid request method")
|
return bad("invalid request method")
|
||||||
}
|
}
|
||||||
section++
|
section++
|
||||||
@@ -105,18 +190,27 @@ func handleHttpRequest(conn net.Conn, channel chan []*action) string {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
pair := strings.SplitN(text, ":", 2)
|
pair := strings.SplitN(text, ":", 2)
|
||||||
if len(pair) == 2 && strings.ToLower(pair[0]) == "content-length" {
|
if len(pair) == 2 {
|
||||||
|
switch strings.ToLower(pair[0]) {
|
||||||
|
case "content-length":
|
||||||
length, err := strconv.Atoi(strings.TrimSpace(pair[1]))
|
length, err := strconv.Atoi(strings.TrimSpace(pair[1]))
|
||||||
if err != nil || length <= 0 || length > maxContentLength {
|
if err != nil || length <= 0 || length > maxContentLength {
|
||||||
return bad("invalid content length")
|
return bad("invalid content length")
|
||||||
}
|
}
|
||||||
contentLength = length
|
contentLength = length
|
||||||
|
case "x-api-key":
|
||||||
|
apiKey = strings.TrimSpace(pair[1])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case 2:
|
case 2:
|
||||||
body += text
|
body += text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(server.apiKey) != 0 && subtle.ConstantTimeCompare([]byte(apiKey), server.apiKey) != 1 {
|
||||||
|
return unauthorized("invalid api key")
|
||||||
|
}
|
||||||
|
|
||||||
if len(body) < contentLength {
|
if len(body) < contentLength {
|
||||||
return bad("incomplete request")
|
return bad("incomplete request")
|
||||||
}
|
}
|
||||||
@@ -133,6 +227,28 @@ func handleHttpRequest(conn net.Conn, channel chan []*action) string {
|
|||||||
return bad("no action specified")
|
return bad("no action specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
channel <- actions
|
server.actionChannel <- actions
|
||||||
return httpOk
|
return httpOk + crlf
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseGetParams(query string) getParams {
|
||||||
|
params := getParams{limit: 100, offset: 0}
|
||||||
|
for _, pair := range strings.Split(query, "&") {
|
||||||
|
parts := strings.SplitN(pair, "=", 2)
|
||||||
|
if len(parts) == 2 {
|
||||||
|
switch parts[0] {
|
||||||
|
case "limit":
|
||||||
|
val, err := strconv.Atoi(parts[1])
|
||||||
|
if err == nil {
|
||||||
|
params.limit = val
|
||||||
|
}
|
||||||
|
case "offset":
|
||||||
|
val, err := strconv.Atoi(parts[1])
|
||||||
|
if err == nil {
|
||||||
|
params.offset = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return params
|
||||||
}
|
}
|
||||||
|
788
src/terminal.go
788
src/terminal.go
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,20 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func replacePlaceholderTest(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
|
||||||
|
return replacePlaceholder(replacePlaceholderParams{
|
||||||
|
template: template,
|
||||||
|
stripAnsi: stripAnsi,
|
||||||
|
delimiter: delimiter,
|
||||||
|
printsep: printsep,
|
||||||
|
forcePlus: forcePlus,
|
||||||
|
query: query,
|
||||||
|
allItems: allItems,
|
||||||
|
lastAction: actBackwardDeleteCharEof,
|
||||||
|
prompt: "prompt",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestReplacePlaceholder(t *testing.T) {
|
func TestReplacePlaceholder(t *testing.T) {
|
||||||
item1 := newItem(" foo'bar \x1b[31mbaz\x1b[m")
|
item1 := newItem(" foo'bar \x1b[31mbaz\x1b[m")
|
||||||
items1 := []*Item{item1, item1}
|
items1 := []*Item{item1, item1}
|
||||||
@@ -52,90 +66,90 @@ func TestReplacePlaceholder(t *testing.T) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// {}, preserve ansi
|
// {}, preserve ansi
|
||||||
result = replacePlaceholder("echo {}", false, Delimiter{}, printsep, false, "query", items1)
|
result = replacePlaceholderTest("echo {}", false, Delimiter{}, printsep, false, "query", items1)
|
||||||
checkFormat("echo {{.O}} foo{{.I}}bar \x1b[31mbaz\x1b[m{{.O}}")
|
checkFormat("echo {{.O}} foo{{.I}}bar \x1b[31mbaz\x1b[m{{.O}}")
|
||||||
|
|
||||||
// {}, strip ansi
|
// {}, strip ansi
|
||||||
result = replacePlaceholder("echo {}", true, Delimiter{}, printsep, false, "query", items1)
|
result = replacePlaceholderTest("echo {}", true, Delimiter{}, printsep, false, "query", items1)
|
||||||
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}")
|
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}")
|
||||||
|
|
||||||
// {}, with multiple items
|
// {}, with multiple items
|
||||||
result = replacePlaceholder("echo {}", true, Delimiter{}, printsep, false, "query", items2)
|
result = replacePlaceholderTest("echo {}", true, Delimiter{}, printsep, false, "query", items2)
|
||||||
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}}")
|
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}}")
|
||||||
|
|
||||||
// {..}, strip leading whitespaces, preserve ansi
|
// {..}, strip leading whitespaces, preserve ansi
|
||||||
result = replacePlaceholder("echo {..}", false, Delimiter{}, printsep, false, "query", items1)
|
result = replacePlaceholderTest("echo {..}", false, Delimiter{}, printsep, false, "query", items1)
|
||||||
checkFormat("echo {{.O}}foo{{.I}}bar \x1b[31mbaz\x1b[m{{.O}}")
|
checkFormat("echo {{.O}}foo{{.I}}bar \x1b[31mbaz\x1b[m{{.O}}")
|
||||||
|
|
||||||
// {..}, strip leading whitespaces, strip ansi
|
// {..}, strip leading whitespaces, strip ansi
|
||||||
result = replacePlaceholder("echo {..}", true, Delimiter{}, printsep, false, "query", items1)
|
result = replacePlaceholderTest("echo {..}", true, Delimiter{}, printsep, false, "query", items1)
|
||||||
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}}")
|
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}}")
|
||||||
|
|
||||||
// {q}
|
// {q}
|
||||||
result = replacePlaceholder("echo {} {q}", true, Delimiter{}, printsep, false, "query", items1)
|
result = replacePlaceholderTest("echo {} {q}", true, Delimiter{}, printsep, false, "query", items1)
|
||||||
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}} {{.O}}query{{.O}}")
|
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}} {{.O}}query{{.O}}")
|
||||||
|
|
||||||
// {q}, multiple items
|
// {q}, multiple items
|
||||||
result = replacePlaceholder("echo {+}{q}{+}", true, Delimiter{}, printsep, false, "query 'string'", items2)
|
result = replacePlaceholderTest("echo {+}{q}{+}", true, Delimiter{}, printsep, false, "query 'string'", items2)
|
||||||
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}{{.O}}query {{.I}}string{{.I}}{{.O}}{{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}")
|
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}{{.O}}query {{.I}}string{{.I}}{{.O}}{{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {}{q}{}", true, Delimiter{}, printsep, false, "query 'string'", items2)
|
result = replacePlaceholderTest("echo {}{q}{}", true, Delimiter{}, printsep, false, "query 'string'", items2)
|
||||||
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}}{{.O}}query {{.I}}string{{.I}}{{.O}}{{.O}}foo{{.I}}bar baz{{.O}}")
|
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}}{{.O}}query {{.I}}string{{.I}}{{.O}}{{.O}}foo{{.I}}bar baz{{.O}}")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {1}/{2}/{2,1}/{-1}/{-2}/{}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, false, "query", items1)
|
result = replacePlaceholderTest("echo {1}/{2}/{2,1}/{-1}/{-2}/{}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, false, "query", items1)
|
||||||
checkFormat("echo {{.O}}foo{{.I}}bar{{.O}}/{{.O}}baz{{.O}}/{{.O}}bazfoo{{.I}}bar{{.O}}/{{.O}}baz{{.O}}/{{.O}}foo{{.I}}bar{{.O}}/{{.O}} foo{{.I}}bar baz{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}}")
|
checkFormat("echo {{.O}}foo{{.I}}bar{{.O}}/{{.O}}baz{{.O}}/{{.O}}bazfoo{{.I}}bar{{.O}}/{{.O}}baz{{.O}}/{{.O}}foo{{.I}}bar{{.O}}/{{.O}} foo{{.I}}bar baz{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}}")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, false, "query", items2)
|
result = replacePlaceholderTest("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, false, "query", items2)
|
||||||
checkFormat("echo {{.O}}foo{{.I}}bar{{.O}}/{{.O}}baz{{.O}}/{{.O}}baz{{.O}}/{{.O}}foo{{.I}}bar{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}}")
|
checkFormat("echo {{.O}}foo{{.I}}bar{{.O}}/{{.O}}baz{{.O}}/{{.O}}baz{{.O}}/{{.O}}foo{{.I}}bar{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}}")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {+1}/{+2}/{+-1}/{+-2}/{+..}/{n.t}/\\{}/\\{1}/\\{q}/{+3}", true, Delimiter{}, printsep, false, "query", items2)
|
result = replacePlaceholderTest("echo {+1}/{+2}/{+-1}/{+-2}/{+..}/{n.t}/\\{}/\\{1}/\\{q}/{+3}", true, Delimiter{}, printsep, false, "query", items2)
|
||||||
checkFormat("echo {{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}} {{.O}}{{.O}}")
|
checkFormat("echo {{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}} {{.O}}{{.O}}")
|
||||||
|
|
||||||
// forcePlus
|
// forcePlus
|
||||||
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, true, "query", items2)
|
result = replacePlaceholderTest("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, true, "query", items2)
|
||||||
checkFormat("echo {{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}} {{.O}}{{.O}}")
|
checkFormat("echo {{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}} {{.O}}{{.O}}")
|
||||||
|
|
||||||
// Whitespace preserving flag with "'" delimiter
|
// Whitespace preserving flag with "'" delimiter
|
||||||
result = replacePlaceholder("echo {s1}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
result = replacePlaceholderTest("echo {s1}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
||||||
checkFormat("echo {{.O}} foo{{.O}}")
|
checkFormat("echo {{.O}} foo{{.O}}")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {s2}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
result = replacePlaceholderTest("echo {s2}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
||||||
checkFormat("echo {{.O}}bar baz{{.O}}")
|
checkFormat("echo {{.O}}bar baz{{.O}}")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {s}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
result = replacePlaceholderTest("echo {s}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
||||||
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}")
|
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {s..}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
result = replacePlaceholderTest("echo {s..}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
||||||
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}")
|
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}")
|
||||||
|
|
||||||
// Whitespace preserving flag with regex delimiter
|
// Whitespace preserving flag with regex delimiter
|
||||||
regex = regexp.MustCompile(`\w+`)
|
regex = regexp.MustCompile(`\w+`)
|
||||||
|
|
||||||
result = replacePlaceholder("echo {s1}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
result = replacePlaceholderTest("echo {s1}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
||||||
checkFormat("echo {{.O}} {{.O}}")
|
checkFormat("echo {{.O}} {{.O}}")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {s2}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
result = replacePlaceholderTest("echo {s2}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
||||||
checkFormat("echo {{.O}}{{.I}}{{.O}}")
|
checkFormat("echo {{.O}}{{.I}}{{.O}}")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {s3}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
result = replacePlaceholderTest("echo {s3}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
||||||
checkFormat("echo {{.O}} {{.O}}")
|
checkFormat("echo {{.O}} {{.O}}")
|
||||||
|
|
||||||
// No match
|
// No match
|
||||||
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, nil})
|
result = replacePlaceholderTest("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, nil})
|
||||||
check("echo /")
|
check("echo /")
|
||||||
|
|
||||||
// No match, but with selections
|
// No match, but with selections
|
||||||
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, item1})
|
result = replacePlaceholderTest("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, item1})
|
||||||
checkFormat("echo /{{.O}} foo{{.I}}bar baz{{.O}}")
|
checkFormat("echo /{{.O}} foo{{.I}}bar baz{{.O}}")
|
||||||
|
|
||||||
// String delimiter
|
// String delimiter
|
||||||
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
result = replacePlaceholderTest("echo {}/{1}/{2}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
||||||
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}/{{.O}}foo{{.O}}/{{.O}}bar baz{{.O}}")
|
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}/{{.O}}foo{{.O}}/{{.O}}bar baz{{.O}}")
|
||||||
|
|
||||||
// Regex delimiter
|
// Regex delimiter
|
||||||
regex = regexp.MustCompile("[oa]+")
|
regex = regexp.MustCompile("[oa]+")
|
||||||
// foo'bar baz
|
// foo'bar baz
|
||||||
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
result = replacePlaceholderTest("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
||||||
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}/{{.O}}f{{.O}}/{{.O}}r b{{.O}}/{{.O}}{{.I}}bar b{{.O}}")
|
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}/{{.O}}f{{.O}}/{{.O}}r b{{.O}}/{{.O}}{{.I}}bar b{{.O}}")
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -155,7 +169,6 @@ func TestReplacePlaceholder(t *testing.T) {
|
|||||||
newItem("7a 7b 7c 7d 7e 7f"),
|
newItem("7a 7b 7c 7d 7e 7f"),
|
||||||
}
|
}
|
||||||
stripAnsi := false
|
stripAnsi := false
|
||||||
printsep = "\n"
|
|
||||||
forcePlus := false
|
forcePlus := false
|
||||||
query := "sample query"
|
query := "sample query"
|
||||||
|
|
||||||
@@ -198,18 +211,23 @@ func TestReplacePlaceholder(t *testing.T) {
|
|||||||
// query flag is not removed after parsing, so it gets doubled
|
// query flag is not removed after parsing, so it gets doubled
|
||||||
// while the double q is invalid, it is useful here for testing purposes
|
// while the double q is invalid, it is useful here for testing purposes
|
||||||
templateToOutput[`{q}`] = "{{.O}}" + query + "{{.O}}"
|
templateToOutput[`{q}`] = "{{.O}}" + query + "{{.O}}"
|
||||||
|
templateToOutput[`{fzf:query}`] = "{{.O}}" + query + "{{.O}}"
|
||||||
|
templateToOutput[`{fzf:action} {fzf:prompt}`] = "backward-delete-char-eof 'prompt'"
|
||||||
|
|
||||||
// IV. escaping placeholder
|
// IV. escaping placeholder
|
||||||
templateToOutput[`\{}`] = `{}`
|
templateToOutput[`\{}`] = `{}`
|
||||||
|
templateToOutput[`\{q}`] = `{q}`
|
||||||
|
templateToOutput[`\{fzf:query}`] = `{fzf:query}`
|
||||||
|
templateToOutput[`\{fzf:action}`] = `{fzf:action}`
|
||||||
templateToOutput[`\{++}`] = `{++}`
|
templateToOutput[`\{++}`] = `{++}`
|
||||||
templateToOutput[`{++}`] = templateToOutput[`{+}`]
|
templateToOutput[`{++}`] = templateToOutput[`{+}`]
|
||||||
|
|
||||||
for giveTemplate, wantOutput := range templateToOutput {
|
for giveTemplate, wantOutput := range templateToOutput {
|
||||||
result = replacePlaceholder(giveTemplate, stripAnsi, Delimiter{}, printsep, forcePlus, query, items3)
|
result = replacePlaceholderTest(giveTemplate, stripAnsi, Delimiter{}, printsep, forcePlus, query, items3)
|
||||||
checkFormat(wantOutput)
|
checkFormat(wantOutput)
|
||||||
}
|
}
|
||||||
for giveTemplate, wantOutput := range templateToFile {
|
for giveTemplate, wantOutput := range templateToFile {
|
||||||
path := replacePlaceholder(giveTemplate, stripAnsi, Delimiter{}, printsep, forcePlus, query, items3)
|
path := replacePlaceholderTest(giveTemplate, stripAnsi, Delimiter{}, printsep, forcePlus, query, items3)
|
||||||
|
|
||||||
data, err := readFile(path)
|
data, err := readFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -563,7 +581,7 @@ func testCommands(t *testing.T, tests []testCase) {
|
|||||||
|
|
||||||
// evaluate the test cases
|
// evaluate the test cases
|
||||||
for idx, test := range tests {
|
for idx, test := range tests {
|
||||||
gotOutput := replacePlaceholder(
|
gotOutput := replacePlaceholderTest(
|
||||||
test.give.template, stripAnsi, delimiter, printsep, forcePlus,
|
test.give.template, stripAnsi, delimiter, printsep, forcePlus,
|
||||||
test.give.query,
|
test.give.query,
|
||||||
test.give.allItems)
|
test.give.allItems)
|
||||||
@@ -605,7 +623,7 @@ func (flags placeholderFlags) encodePlaceholder() string {
|
|||||||
if flags.file {
|
if flags.file {
|
||||||
encoded += "f"
|
encoded += "f"
|
||||||
}
|
}
|
||||||
if flags.query {
|
if flags.forceUpdate { // FIXME
|
||||||
encoded += "q"
|
encoded += "q"
|
||||||
}
|
}
|
||||||
return encoded
|
return encoded
|
||||||
|
@@ -7,14 +7,35 @@ import (
|
|||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var escaper *strings.Replacer
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
tokens := strings.Split(os.Getenv("SHELL"), "/")
|
||||||
|
if tokens[len(tokens)-1] == "fish" {
|
||||||
|
// https://fishshell.com/docs/current/language.html#quotes
|
||||||
|
// > The only meaningful escape sequences in single quotes are \', which
|
||||||
|
// > escapes a single quote and \\, which escapes the backslash symbol.
|
||||||
|
escaper = strings.NewReplacer("\\", "\\\\", "'", "\\'")
|
||||||
|
} else {
|
||||||
|
escaper = strings.NewReplacer("'", "'\\''")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func notifyOnResize(resizeChan chan<- os.Signal) {
|
func notifyOnResize(resizeChan chan<- os.Signal) {
|
||||||
signal.Notify(resizeChan, syscall.SIGWINCH)
|
signal.Notify(resizeChan, syscall.SIGWINCH)
|
||||||
}
|
}
|
||||||
|
|
||||||
func notifyStop(p *os.Process) {
|
func notifyStop(p *os.Process) {
|
||||||
p.Signal(syscall.SIGSTOP)
|
pid := p.Pid
|
||||||
|
pgid, err := unix.Getpgid(pid)
|
||||||
|
if err == nil {
|
||||||
|
pid = pgid * -1
|
||||||
|
}
|
||||||
|
unix.Kill(pid, syscall.SIGSTOP)
|
||||||
}
|
}
|
||||||
|
|
||||||
func notifyOnCont(resizeChan chan<- os.Signal) {
|
func notifyOnCont(resizeChan chan<- os.Signal) {
|
||||||
@@ -22,5 +43,5 @@ func notifyOnCont(resizeChan chan<- os.Signal) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func quoteEntry(entry string) string {
|
func quoteEntry(entry string) string {
|
||||||
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
|
return "'" + escaper.Replace(entry) + "'"
|
||||||
}
|
}
|
||||||
|
@@ -33,12 +33,16 @@ func (r *FullscreenRenderer) Init() {}
|
|||||||
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
|
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
|
||||||
func (r *FullscreenRenderer) Pause(bool) {}
|
func (r *FullscreenRenderer) Pause(bool) {}
|
||||||
func (r *FullscreenRenderer) Resume(bool, bool) {}
|
func (r *FullscreenRenderer) Resume(bool, bool) {}
|
||||||
|
func (r *FullscreenRenderer) PassThrough(string) {}
|
||||||
func (r *FullscreenRenderer) Clear() {}
|
func (r *FullscreenRenderer) Clear() {}
|
||||||
func (r *FullscreenRenderer) NeedScrollbarRedraw() bool { return false }
|
func (r *FullscreenRenderer) NeedScrollbarRedraw() bool { return false }
|
||||||
|
func (r *FullscreenRenderer) ShouldEmitResizeEvent() bool { return false }
|
||||||
func (r *FullscreenRenderer) Refresh() {}
|
func (r *FullscreenRenderer) Refresh() {}
|
||||||
func (r *FullscreenRenderer) Close() {}
|
func (r *FullscreenRenderer) Close() {}
|
||||||
|
func (r *FullscreenRenderer) Size() TermSize { return TermSize{} }
|
||||||
|
|
||||||
func (r *FullscreenRenderer) GetChar() Event { return Event{} }
|
func (r *FullscreenRenderer) GetChar() Event { return Event{} }
|
||||||
|
func (r *FullscreenRenderer) Top() int { return 0 }
|
||||||
func (r *FullscreenRenderer) MaxX() int { return 0 }
|
func (r *FullscreenRenderer) MaxX() int { return 0 }
|
||||||
func (r *FullscreenRenderer) MaxY() int { return 0 }
|
func (r *FullscreenRenderer) MaxY() int { return 0 }
|
||||||
|
|
||||||
|
@@ -10,7 +10,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/mattn/go-runewidth"
|
|
||||||
"github.com/rivo/uniseg"
|
"github.com/rivo/uniseg"
|
||||||
|
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
@@ -31,6 +30,10 @@ const consoleDevice string = "/dev/tty"
|
|||||||
var offsetRegexp *regexp.Regexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R")
|
var offsetRegexp *regexp.Regexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R")
|
||||||
var offsetRegexpBegin *regexp.Regexp = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R")
|
var offsetRegexpBegin *regexp.Regexp = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R")
|
||||||
|
|
||||||
|
func (r *LightRenderer) PassThrough(str string) {
|
||||||
|
r.queued.WriteString("\x1b7" + str + "\x1b8")
|
||||||
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) stderr(str string) {
|
func (r *LightRenderer) stderr(str string) {
|
||||||
r.stderrInternal(str, true, "")
|
r.stderrInternal(str, true, "")
|
||||||
}
|
}
|
||||||
@@ -398,7 +401,7 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
return Event{F3, 0, nil}
|
return Event{F3, 0, nil}
|
||||||
case 'S':
|
case 'S':
|
||||||
return Event{F4, 0, nil}
|
return Event{F4, 0, nil}
|
||||||
case '1', '2', '3', '4', '5', '6':
|
case '1', '2', '3', '4', '5', '6', '7', '8':
|
||||||
if len(r.buffer) < 4 {
|
if len(r.buffer) < 4 {
|
||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
}
|
}
|
||||||
@@ -449,6 +452,10 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
return Event{PgUp, 0, nil}
|
return Event{PgUp, 0, nil}
|
||||||
case '6':
|
case '6':
|
||||||
return Event{PgDn, 0, nil}
|
return Event{PgDn, 0, nil}
|
||||||
|
case '7':
|
||||||
|
return Event{Home, 0, nil}
|
||||||
|
case '8':
|
||||||
|
return Event{End, 0, nil}
|
||||||
case '1':
|
case '1':
|
||||||
switch r.buffer[3] {
|
switch r.buffer[3] {
|
||||||
case '~':
|
case '~':
|
||||||
@@ -687,6 +694,10 @@ func (r *LightRenderer) NeedScrollbarRedraw() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) ShouldEmitResizeEvent() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) RefreshWindows(windows []Window) {
|
func (r *LightRenderer) RefreshWindows(windows []Window) {
|
||||||
r.flush()
|
r.flush()
|
||||||
}
|
}
|
||||||
@@ -716,6 +727,10 @@ func (r *LightRenderer) Close() {
|
|||||||
r.restoreTerminal()
|
r.restoreTerminal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) Top() int {
|
||||||
|
return r.yoffset
|
||||||
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) MaxX() int {
|
func (r *LightRenderer) MaxX() int {
|
||||||
return r.width
|
return r.width
|
||||||
}
|
}
|
||||||
@@ -751,13 +766,17 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, prev
|
|||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) DrawBorder() {
|
||||||
|
w.drawBorder(false)
|
||||||
|
}
|
||||||
|
|
||||||
func (w *LightWindow) DrawHBorder() {
|
func (w *LightWindow) DrawHBorder() {
|
||||||
w.drawBorder(true)
|
w.drawBorder(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) drawBorder(onlyHorizontal bool) {
|
func (w *LightWindow) drawBorder(onlyHorizontal bool) {
|
||||||
switch w.border.shape {
|
switch w.border.shape {
|
||||||
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderDouble:
|
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble:
|
||||||
w.drawBorderAround(onlyHorizontal)
|
w.drawBorderAround(onlyHorizontal)
|
||||||
case BorderHorizontal:
|
case BorderHorizontal:
|
||||||
w.drawBorderHorizontal(true, true)
|
w.drawBorderHorizontal(true, true)
|
||||||
@@ -788,7 +807,7 @@ func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
|
|||||||
if w.preview {
|
if w.preview {
|
||||||
color = ColPreviewBorder
|
color = ColPreviewBorder
|
||||||
}
|
}
|
||||||
hw := runewidth.RuneWidth(w.border.top)
|
hw := runeWidth(w.border.top)
|
||||||
if top {
|
if top {
|
||||||
w.Move(0, 0)
|
w.Move(0, 0)
|
||||||
w.CPrint(color, repeat(w.border.top, w.width/hw))
|
w.CPrint(color, repeat(w.border.top, w.width/hw))
|
||||||
@@ -826,13 +845,13 @@ func (w *LightWindow) drawBorderAround(onlyHorizontal bool) {
|
|||||||
if w.preview {
|
if w.preview {
|
||||||
color = ColPreviewBorder
|
color = ColPreviewBorder
|
||||||
}
|
}
|
||||||
hw := runewidth.RuneWidth(w.border.top)
|
hw := runeWidth(w.border.top)
|
||||||
tcw := runewidth.RuneWidth(w.border.topLeft) + runewidth.RuneWidth(w.border.topRight)
|
tcw := runeWidth(w.border.topLeft) + runeWidth(w.border.topRight)
|
||||||
bcw := runewidth.RuneWidth(w.border.bottomLeft) + runewidth.RuneWidth(w.border.bottomRight)
|
bcw := runeWidth(w.border.bottomLeft) + runeWidth(w.border.bottomRight)
|
||||||
rem := (w.width - tcw) % hw
|
rem := (w.width - tcw) % hw
|
||||||
w.CPrint(color, string(w.border.topLeft)+repeat(w.border.top, (w.width-tcw)/hw)+repeat(' ', rem)+string(w.border.topRight))
|
w.CPrint(color, string(w.border.topLeft)+repeat(w.border.top, (w.width-tcw)/hw)+repeat(' ', rem)+string(w.border.topRight))
|
||||||
if !onlyHorizontal {
|
if !onlyHorizontal {
|
||||||
vw := runewidth.RuneWidth(w.border.left)
|
vw := runeWidth(w.border.left)
|
||||||
for y := 1; y < w.height-1; y++ {
|
for y := 1; y < w.height-1; y++ {
|
||||||
w.Move(y, 0)
|
w.Move(y, 0)
|
||||||
w.CPrint(color, string(w.border.left))
|
w.CPrint(color, string(w.border.left))
|
||||||
@@ -1004,7 +1023,7 @@ func wrapLine(input string, prefixLength int, max int, tabstop int) []wrappedLin
|
|||||||
} else if rs[0] == '\r' {
|
} else if rs[0] == '\r' {
|
||||||
w++
|
w++
|
||||||
} else {
|
} else {
|
||||||
w = runewidth.StringWidth(str)
|
w = uniseg.StringWidth(str)
|
||||||
}
|
}
|
||||||
width += w
|
width += w
|
||||||
|
|
||||||
@@ -1083,14 +1102,21 @@ func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillRetu
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) FinishFill() {
|
func (w *LightWindow) FinishFill() {
|
||||||
|
if w.posy < w.height {
|
||||||
w.MoveAndClear(w.posy, w.posx)
|
w.MoveAndClear(w.posy, w.posx)
|
||||||
|
}
|
||||||
for y := w.posy + 1; y < w.height; y++ {
|
for y := w.posy + 1; y < w.height; y++ {
|
||||||
w.MoveAndClear(y, 0)
|
w.MoveAndClear(y, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) Erase() {
|
func (w *LightWindow) Erase() {
|
||||||
w.drawBorder(false)
|
w.DrawBorder()
|
||||||
// We don't erase the window here to avoid flickering during scroll
|
w.Move(0, 0)
|
||||||
|
w.FinishFill()
|
||||||
w.Move(0, 0)
|
w.Move(0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) EraseMaybe() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@@ -10,6 +10,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -108,3 +109,11 @@ func (r *LightRenderer) getch(nonblock bool) (int, bool) {
|
|||||||
}
|
}
|
||||||
return int(b[0]), true
|
return int(b[0]), true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) Size() TermSize {
|
||||||
|
ws, err := unix.IoctlGetWinsize(int(r.ttyin.Fd()), unix.TIOCGWINSZ)
|
||||||
|
if err != nil {
|
||||||
|
return TermSize{}
|
||||||
|
}
|
||||||
|
return TermSize{int(ws.Row), int(ws.Col), int(ws.Xpixel), int(ws.Ypixel)}
|
||||||
|
}
|
||||||
|
@@ -110,16 +110,24 @@ func (r *LightRenderer) restoreTerminal() error {
|
|||||||
return windows.SetConsoleMode(windows.Handle(r.outHandle), r.origStateOutput)
|
return windows.SetConsoleMode(windows.Handle(r.outHandle), r.origStateOutput)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) updateTerminalSize() {
|
func (r *LightRenderer) Size() TermSize {
|
||||||
|
var w, h int
|
||||||
var bufferInfo windows.ConsoleScreenBufferInfo
|
var bufferInfo windows.ConsoleScreenBufferInfo
|
||||||
if err := windows.GetConsoleScreenBufferInfo(windows.Handle(r.outHandle), &bufferInfo); err != nil {
|
if err := windows.GetConsoleScreenBufferInfo(windows.Handle(r.outHandle), &bufferInfo); err != nil {
|
||||||
r.width = getEnv("COLUMNS", defaultWidth)
|
w = getEnv("COLUMNS", defaultWidth)
|
||||||
r.height = r.maxHeightFunc(getEnv("LINES", defaultHeight))
|
h = r.maxHeightFunc(getEnv("LINES", defaultHeight))
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
r.width = int(bufferInfo.Window.Right - bufferInfo.Window.Left)
|
w = int(bufferInfo.Window.Right - bufferInfo.Window.Left)
|
||||||
r.height = r.maxHeightFunc(int(bufferInfo.Window.Bottom - bufferInfo.Window.Top))
|
h = r.maxHeightFunc(int(bufferInfo.Window.Bottom - bufferInfo.Window.Top))
|
||||||
}
|
}
|
||||||
|
return TermSize{h, w, 0, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) updateTerminalSize() {
|
||||||
|
size := r.Size()
|
||||||
|
r.width = size.Columns
|
||||||
|
r.height = size.Lines
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) findOffset() (row int, col int) {
|
func (r *LightRenderer) findOffset() (row int, col int) {
|
||||||
|
@@ -10,7 +10,6 @@ import (
|
|||||||
"github.com/gdamore/tcell/v2/encoding"
|
"github.com/gdamore/tcell/v2/encoding"
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
|
||||||
"github.com/mattn/go-runewidth"
|
|
||||||
"github.com/rivo/uniseg"
|
"github.com/rivo/uniseg"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -98,6 +97,11 @@ const (
|
|||||||
AttrClear = Attr(1 << 8)
|
AttrClear = Attr(1 << 8)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (r *FullscreenRenderer) PassThrough(str string) {
|
||||||
|
// No-op
|
||||||
|
// https://github.com/gdamore/tcell/pull/650#issuecomment-1806442846
|
||||||
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
|
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
|
func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
|
||||||
@@ -139,6 +143,7 @@ func (a Attr) Merge(b Attr) Attr {
|
|||||||
var (
|
var (
|
||||||
_screen tcell.Screen
|
_screen tcell.Screen
|
||||||
_prevMouseButton tcell.ButtonMask
|
_prevMouseButton tcell.ButtonMask
|
||||||
|
_initialResize bool = true
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *FullscreenRenderer) initScreen() {
|
func (r *FullscreenRenderer) initScreen() {
|
||||||
@@ -167,6 +172,10 @@ func (r *FullscreenRenderer) Init() {
|
|||||||
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *FullscreenRenderer) Top() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) MaxX() int {
|
func (r *FullscreenRenderer) MaxX() int {
|
||||||
ncols, _ := _screen.Size()
|
ncols, _ := _screen.Size()
|
||||||
return int(ncols)
|
return int(ncols)
|
||||||
@@ -194,14 +203,30 @@ func (r *FullscreenRenderer) NeedScrollbarRedraw() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *FullscreenRenderer) ShouldEmitResizeEvent() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Refresh() {
|
func (r *FullscreenRenderer) Refresh() {
|
||||||
// noop
|
// noop
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Pixel width and height not implemented
|
||||||
|
func (r *FullscreenRenderer) Size() TermSize {
|
||||||
|
cols, lines := _screen.Size()
|
||||||
|
return TermSize{lines, cols, 0, 0}
|
||||||
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) GetChar() Event {
|
func (r *FullscreenRenderer) GetChar() Event {
|
||||||
ev := _screen.PollEvent()
|
ev := _screen.PollEvent()
|
||||||
switch ev := ev.(type) {
|
switch ev := ev.(type) {
|
||||||
case *tcell.EventResize:
|
case *tcell.EventResize:
|
||||||
|
// Ignore the first resize event
|
||||||
|
// https://github.com/gdamore/tcell/blob/v2.7.0/TUTORIAL.md?plain=1#L18
|
||||||
|
if _initialResize {
|
||||||
|
_initialResize = false
|
||||||
|
return Event{Invalid, 0, nil}
|
||||||
|
}
|
||||||
return Event{Resize, 0, nil}
|
return Event{Resize, 0, nil}
|
||||||
|
|
||||||
// process mouse events:
|
// process mouse events:
|
||||||
@@ -536,9 +561,15 @@ func fill(x, y, w, h int, n ColorPair, r rune) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Erase() {
|
func (w *TcellWindow) Erase() {
|
||||||
|
w.drawBorder(false)
|
||||||
fill(w.left-1, w.top, w.width+1, w.height-1, w.normal, ' ')
|
fill(w.left-1, w.top, w.width+1, w.height-1, w.normal, ' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *TcellWindow) EraseMaybe() bool {
|
||||||
|
w.Erase()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Enclose(y int, x int) bool {
|
func (w *TcellWindow) Enclose(y int, x int) bool {
|
||||||
return x >= w.left && x < (w.left+w.width) &&
|
return x >= w.left && x < (w.left+w.width) &&
|
||||||
y >= w.top && y < (w.top+w.height)
|
y >= w.top && y < (w.top+w.height)
|
||||||
@@ -687,6 +718,10 @@ func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
|
|||||||
return w.fillString(str, NewColorPair(fg, bg, a))
|
return w.fillString(str, NewColorPair(fg, bg, a))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *TcellWindow) DrawBorder() {
|
||||||
|
w.drawBorder(false)
|
||||||
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) DrawHBorder() {
|
func (w *TcellWindow) DrawHBorder() {
|
||||||
w.drawBorder(true)
|
w.drawBorder(true)
|
||||||
}
|
}
|
||||||
@@ -713,9 +748,9 @@ func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
|
|||||||
style = w.normal.style()
|
style = w.normal.style()
|
||||||
}
|
}
|
||||||
|
|
||||||
hw := runewidth.RuneWidth(w.borderStyle.top)
|
hw := runeWidth(w.borderStyle.top)
|
||||||
switch shape {
|
switch shape {
|
||||||
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderDouble, BorderHorizontal, BorderTop:
|
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderHorizontal, BorderTop:
|
||||||
max := right - 2*hw
|
max := right - 2*hw
|
||||||
if shape == BorderHorizontal || shape == BorderTop {
|
if shape == BorderHorizontal || shape == BorderTop {
|
||||||
max = right - hw
|
max = right - hw
|
||||||
@@ -730,7 +765,7 @@ func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch shape {
|
switch shape {
|
||||||
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderDouble, BorderHorizontal, BorderBottom:
|
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderHorizontal, BorderBottom:
|
||||||
max := right - 2*hw
|
max := right - 2*hw
|
||||||
if shape == BorderHorizontal || shape == BorderBottom {
|
if shape == BorderHorizontal || shape == BorderBottom {
|
||||||
max = right - hw
|
max = right - hw
|
||||||
@@ -741,24 +776,24 @@ func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
|
|||||||
}
|
}
|
||||||
if !onlyHorizontal {
|
if !onlyHorizontal {
|
||||||
switch shape {
|
switch shape {
|
||||||
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderDouble, BorderVertical, BorderLeft:
|
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderVertical, BorderLeft:
|
||||||
for y := top; y < bot; y++ {
|
for y := top; y < bot; y++ {
|
||||||
_screen.SetContent(left, y, w.borderStyle.left, nil, style)
|
_screen.SetContent(left, y, w.borderStyle.left, nil, style)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch shape {
|
switch shape {
|
||||||
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderDouble, BorderVertical, BorderRight:
|
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderVertical, BorderRight:
|
||||||
vw := runewidth.RuneWidth(w.borderStyle.right)
|
vw := runeWidth(w.borderStyle.right)
|
||||||
for y := top; y < bot; y++ {
|
for y := top; y < bot; y++ {
|
||||||
_screen.SetContent(right-vw, y, w.borderStyle.right, nil, style)
|
_screen.SetContent(right-vw, y, w.borderStyle.right, nil, style)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch shape {
|
switch shape {
|
||||||
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderDouble:
|
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble:
|
||||||
_screen.SetContent(left, top, w.borderStyle.topLeft, nil, style)
|
_screen.SetContent(left, top, w.borderStyle.topLeft, nil, style)
|
||||||
_screen.SetContent(right-runewidth.RuneWidth(w.borderStyle.topRight), top, w.borderStyle.topRight, nil, style)
|
_screen.SetContent(right-runeWidth(w.borderStyle.topRight), top, w.borderStyle.topRight, nil, style)
|
||||||
_screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style)
|
_screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style)
|
||||||
_screen.SetContent(right-runewidth.RuneWidth(w.borderStyle.bottomRight), bot-1, w.borderStyle.bottomRight, nil, style)
|
_screen.SetContent(right-runeWidth(w.borderStyle.bottomRight), bot-1, w.borderStyle.bottomRight, nil, style)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -182,6 +182,7 @@ func TestGetCharEventKey(t *testing.T) {
|
|||||||
r.Init()
|
r.Init()
|
||||||
|
|
||||||
// run and evaluate the tests
|
// run and evaluate the tests
|
||||||
|
initialResizeAsInvalid := true
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
// generate key event
|
// generate key event
|
||||||
giveEvent := tcell.NewEventKey(test.giveKey.Type, test.giveKey.Char, test.giveKey.Mods)
|
giveEvent := tcell.NewEventKey(test.giveKey.Type, test.giveKey.Char, test.giveKey.Mods)
|
||||||
@@ -191,8 +192,9 @@ func TestGetCharEventKey(t *testing.T) {
|
|||||||
// process the event in fzf and evaluate the test
|
// process the event in fzf and evaluate the test
|
||||||
gotEvent := r.GetChar()
|
gotEvent := r.GetChar()
|
||||||
// skip Resize events, those are sometimes put in the buffer outside of this test
|
// skip Resize events, those are sometimes put in the buffer outside of this test
|
||||||
for gotEvent.Type == Resize {
|
if initialResizeAsInvalid && gotEvent.Type == Invalid {
|
||||||
t.Logf("Resize swallowed")
|
t.Logf("Resize as Invalid swallowed")
|
||||||
|
initialResizeAsInvalid = false
|
||||||
gotEvent = r.GetChar()
|
gotEvent = r.GetChar()
|
||||||
}
|
}
|
||||||
t.Logf("wantEvent = %T{Type: %v, Char: %q (%[3]v)}\n", test.wantKey, test.wantKey.Type, test.wantKey.Char)
|
t.Logf("wantEvent = %T{Type: %v, Char: %q (%[3]v)}\n", test.wantKey, test.wantKey.Type, test.wantKey.Char)
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
package tui
|
package tui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
@@ -17,13 +16,17 @@ func ttyname() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, prefix := range devPrefixes {
|
for _, prefix := range devPrefixes {
|
||||||
files, err := ioutil.ReadDir(prefix)
|
files, err := os.ReadDir(prefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
if stat, ok := file.Sys().(*syscall.Stat_t); ok && stat.Rdev == stderr.Rdev {
|
info, err := file.Info()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if stat, ok := info.Sys().(*syscall.Stat_t); ok && stat.Rdev == stderr.Rdev {
|
||||||
return prefix + file.Name()
|
return prefix + file.Name()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/rivo/uniseg"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Types of user action
|
// Types of user action
|
||||||
@@ -55,6 +57,14 @@ const (
|
|||||||
DoubleClick
|
DoubleClick
|
||||||
LeftClick
|
LeftClick
|
||||||
RightClick
|
RightClick
|
||||||
|
SLeftClick
|
||||||
|
SRightClick
|
||||||
|
ScrollUp
|
||||||
|
ScrollDown
|
||||||
|
SScrollUp
|
||||||
|
SScrollDown
|
||||||
|
PreviewScrollUp
|
||||||
|
PreviewScrollDown
|
||||||
|
|
||||||
BTab
|
BTab
|
||||||
BSpace
|
BSpace
|
||||||
@@ -97,6 +107,7 @@ const (
|
|||||||
Focus
|
Focus
|
||||||
One
|
One
|
||||||
Zero
|
Zero
|
||||||
|
Result
|
||||||
|
|
||||||
AltBS
|
AltBS
|
||||||
|
|
||||||
@@ -315,6 +326,7 @@ const (
|
|||||||
BorderSharp
|
BorderSharp
|
||||||
BorderBold
|
BorderBold
|
||||||
BorderBlock
|
BorderBlock
|
||||||
|
BorderThinBlock
|
||||||
BorderDouble
|
BorderDouble
|
||||||
BorderHorizontal
|
BorderHorizontal
|
||||||
BorderVertical
|
BorderVertical
|
||||||
@@ -408,6 +420,23 @@ func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
|
|||||||
bottomLeft: '▙',
|
bottomLeft: '▙',
|
||||||
bottomRight: '▟',
|
bottomRight: '▟',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case BorderThinBlock:
|
||||||
|
// 🭽▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔🭾
|
||||||
|
// ▏ ▕
|
||||||
|
// 🭼▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁🭿
|
||||||
|
return BorderStyle{
|
||||||
|
shape: shape,
|
||||||
|
top: '▔',
|
||||||
|
bottom: '▁',
|
||||||
|
left: '▏',
|
||||||
|
right: '▕',
|
||||||
|
topLeft: '🭽',
|
||||||
|
topRight: '🭾',
|
||||||
|
bottomLeft: '🭼',
|
||||||
|
bottomRight: '🭿',
|
||||||
|
}
|
||||||
|
|
||||||
case BorderDouble:
|
case BorderDouble:
|
||||||
return BorderStyle{
|
return BorderStyle{
|
||||||
shape: shape,
|
shape: shape,
|
||||||
@@ -447,6 +476,13 @@ func MakeTransparentBorder() BorderStyle {
|
|||||||
bottomRight: ' '}
|
bottomRight: ' '}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TermSize struct {
|
||||||
|
Lines int
|
||||||
|
Columns int
|
||||||
|
PxWidth int
|
||||||
|
PxHeight int
|
||||||
|
}
|
||||||
|
|
||||||
type Renderer interface {
|
type Renderer interface {
|
||||||
Init()
|
Init()
|
||||||
Resize(maxHeightFunc func(int) int)
|
Resize(maxHeightFunc func(int) int)
|
||||||
@@ -456,13 +492,18 @@ type Renderer interface {
|
|||||||
RefreshWindows(windows []Window)
|
RefreshWindows(windows []Window)
|
||||||
Refresh()
|
Refresh()
|
||||||
Close()
|
Close()
|
||||||
|
PassThrough(string)
|
||||||
NeedScrollbarRedraw() bool
|
NeedScrollbarRedraw() bool
|
||||||
|
ShouldEmitResizeEvent() bool
|
||||||
|
|
||||||
GetChar() Event
|
GetChar() Event
|
||||||
|
|
||||||
|
Top() int
|
||||||
MaxX() int
|
MaxX() int
|
||||||
MaxY() int
|
MaxY() int
|
||||||
|
|
||||||
|
Size() TermSize
|
||||||
|
|
||||||
NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window
|
NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -472,6 +513,7 @@ type Window interface {
|
|||||||
Width() int
|
Width() int
|
||||||
Height() int
|
Height() int
|
||||||
|
|
||||||
|
DrawBorder()
|
||||||
DrawHBorder()
|
DrawHBorder()
|
||||||
Refresh()
|
Refresh()
|
||||||
FinishFill()
|
FinishFill()
|
||||||
@@ -488,6 +530,7 @@ type Window interface {
|
|||||||
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
|
||||||
Erase()
|
Erase()
|
||||||
|
EraseMaybe() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type FullscreenRenderer struct {
|
type FullscreenRenderer struct {
|
||||||
@@ -538,6 +581,7 @@ var (
|
|||||||
ColBorderLabel ColorPair
|
ColBorderLabel ColorPair
|
||||||
ColPreviewLabel ColorPair
|
ColPreviewLabel ColorPair
|
||||||
ColPreviewScrollbar ColorPair
|
ColPreviewScrollbar ColorPair
|
||||||
|
ColPreviewSpinner ColorPair
|
||||||
)
|
)
|
||||||
|
|
||||||
func EmptyTheme() *ColorTheme {
|
func EmptyTheme() *ColorTheme {
|
||||||
@@ -769,4 +813,9 @@ func initPalette(theme *ColorTheme) {
|
|||||||
ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
|
ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
|
||||||
ColPreviewBorder = pair(theme.PreviewBorder, theme.PreviewBg)
|
ColPreviewBorder = pair(theme.PreviewBorder, theme.PreviewBg)
|
||||||
ColPreviewScrollbar = pair(theme.PreviewScrollbar, theme.PreviewBg)
|
ColPreviewScrollbar = pair(theme.PreviewScrollbar, theme.PreviewBg)
|
||||||
|
ColPreviewSpinner = pair(theme.Spinner, theme.PreviewBg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runeWidth(r rune) int {
|
||||||
|
return uniseg.StringWidth(string(r))
|
||||||
}
|
}
|
||||||
|
@@ -7,13 +7,12 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mattn/go-isatty"
|
"github.com/mattn/go-isatty"
|
||||||
"github.com/mattn/go-runewidth"
|
|
||||||
"github.com/rivo/uniseg"
|
"github.com/rivo/uniseg"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StringWidth returns string width where each CR/LF character takes 1 column
|
// StringWidth returns string width where each CR/LF character takes 1 column
|
||||||
func StringWidth(s string) int {
|
func StringWidth(s string) int {
|
||||||
return runewidth.StringWidth(s) + strings.Count(s, "\n") + strings.Count(s, "\r")
|
return uniseg.StringWidth(s) + strings.Count(s, "\n") + strings.Count(s, "\r")
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunesWidth returns runes width
|
// RunesWidth returns runes width
|
||||||
@@ -165,7 +164,7 @@ func RepeatToFill(str string, length int, limit int) string {
|
|||||||
output := strings.Repeat(str, times)
|
output := strings.Repeat(str, times)
|
||||||
if rest > 0 {
|
if rest > 0 {
|
||||||
for _, r := range str {
|
for _, r := range str {
|
||||||
rest -= runewidth.RuneWidth(r)
|
rest -= uniseg.StringWidth(string(r))
|
||||||
if rest < 0 {
|
if rest < 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@@ -164,6 +164,18 @@ func TestRunesWidth(t *testing.T) {
|
|||||||
t.Errorf("Expected overflow index: %d, actual: %d", args[2], overflowIdx)
|
t.Errorf("Expected overflow index: %d, actual: %d", args[2], overflowIdx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for _, input := range []struct {
|
||||||
|
s string
|
||||||
|
w int
|
||||||
|
}{
|
||||||
|
{"▶", 1},
|
||||||
|
{"▶️", 2},
|
||||||
|
} {
|
||||||
|
width, _ := RunesWidth([]rune(input.s), 0, 0, 100)
|
||||||
|
if width != input.w {
|
||||||
|
t.Errorf("Expected width of %s: %d, actual: %d", input.s, input.w, width)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTruncate(t *testing.T) {
|
func TestTruncate(t *testing.T) {
|
||||||
@@ -184,3 +196,10 @@ func TestRepeatToFill(t *testing.T) {
|
|||||||
t.Error("Expected:", strings.Repeat("abcde", 4)+"abcde"[:2])
|
t.Error("Expected:", strings.Repeat("abcde", 4)+"abcde"[:2])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStringWidth(t *testing.T) {
|
||||||
|
w := StringWidth("─")
|
||||||
|
if w != 1 {
|
||||||
|
t.Errorf("Expected: %d, Actual: %d", 1, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
241
test/test_go.rb
241
test/test_go.rb
@@ -8,6 +8,7 @@ require 'shellwords'
|
|||||||
require 'erb'
|
require 'erb'
|
||||||
require 'tempfile'
|
require 'tempfile'
|
||||||
require 'net/http'
|
require 'net/http'
|
||||||
|
require 'json'
|
||||||
|
|
||||||
TEMPLATE = DATA.read
|
TEMPLATE = DATA.read
|
||||||
UNSETS = %w[
|
UNSETS = %w[
|
||||||
@@ -16,6 +17,7 @@ UNSETS = %w[
|
|||||||
FZF_CTRL_T_COMMAND FZF_CTRL_T_OPTS
|
FZF_CTRL_T_COMMAND FZF_CTRL_T_OPTS
|
||||||
FZF_ALT_C_COMMAND
|
FZF_ALT_C_COMMAND
|
||||||
FZF_ALT_C_OPTS FZF_CTRL_R_OPTS
|
FZF_ALT_C_OPTS FZF_CTRL_R_OPTS
|
||||||
|
FZF_API_KEY
|
||||||
fish_history
|
fish_history
|
||||||
].freeze
|
].freeze
|
||||||
DEFAULT_TIMEOUT = 10
|
DEFAULT_TIMEOUT = 10
|
||||||
@@ -739,6 +741,12 @@ class TestGoFZF < TestBase
|
|||||||
'xxoxxxxxxx',
|
'xxoxxxxxxx',
|
||||||
'xoxxxxxxxx'
|
'xoxxxxxxxx'
|
||||||
], `#{FZF} -fo --tiebreak=end,length,begin < #{tempname}`.lines(chomp: true)
|
], `#{FZF} -fo --tiebreak=end,length,begin < #{tempname}`.lines(chomp: true)
|
||||||
|
|
||||||
|
writelines(tempname, ['/bar/baz', '/foo/bar/baz'])
|
||||||
|
assert_equal [
|
||||||
|
'/foo/bar/baz',
|
||||||
|
'/bar/baz'
|
||||||
|
], `#{FZF} -fbaz --tiebreak=end < #{tempname}`.lines(chomp: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_tiebreak_length_with_nth
|
def test_tiebreak_length_with_nth
|
||||||
@@ -956,26 +964,40 @@ class TestGoFZF < TestBase
|
|||||||
|
|
||||||
def test_execute
|
def test_execute
|
||||||
output = '/tmp/fzf-test-execute'
|
output = '/tmp/fzf-test-execute'
|
||||||
opts = %[--bind "alt-a:execute(echo /{}/ >> #{output}),alt-b:execute[echo /{}{}/ >> #{output}],C:execute:echo /{}{}{}/ >> #{output}"]
|
opts = %[--bind "alt-a:execute(echo /{}/ >> #{output})+change-header(alt-a),alt-b:execute[echo /{}{}/ >> #{output}]+change-header(alt-b),C:execute(echo /{}{}{}/ >> #{output})+change-header(C)"]
|
||||||
writelines(tempname, %w[foo'bar foo"bar foo$bar])
|
writelines(tempname, %w[foo'bar foo"bar foo$bar])
|
||||||
tmux.send_keys "cat #{tempname} | #{fzf(opts)}", :Enter
|
tmux.send_keys "cat #{tempname} | #{fzf(opts)}", :Enter
|
||||||
tmux.until { |lines| assert_equal ' 3/3', lines[-2] }
|
tmux.until { |lines| assert_equal 3, lines.item_count }
|
||||||
tmux.send_keys :Escape, :a
|
|
||||||
|
ready = ->(s) { tmux.until { |lines| assert_includes lines[-3], s } }
|
||||||
tmux.send_keys :Escape, :a
|
tmux.send_keys :Escape, :a
|
||||||
|
ready.call('alt-a')
|
||||||
|
tmux.send_keys :Escape, :b
|
||||||
|
ready.call('alt-b')
|
||||||
|
|
||||||
tmux.send_keys :Up
|
tmux.send_keys :Up
|
||||||
|
tmux.send_keys :Escape, :a
|
||||||
|
ready.call('alt-a')
|
||||||
tmux.send_keys :Escape, :b
|
tmux.send_keys :Escape, :b
|
||||||
tmux.send_keys :Escape, :b
|
ready.call('alt-b')
|
||||||
|
|
||||||
tmux.send_keys :Up
|
tmux.send_keys :Up
|
||||||
tmux.send_keys :C
|
tmux.send_keys :C
|
||||||
|
ready.call('C')
|
||||||
|
|
||||||
tmux.send_keys 'barfoo'
|
tmux.send_keys 'barfoo'
|
||||||
tmux.until { |lines| assert_equal ' 0/3', lines[-2] }
|
tmux.until { |lines| assert_equal ' 0/3', lines[-2] }
|
||||||
|
|
||||||
tmux.send_keys :Escape, :a
|
tmux.send_keys :Escape, :a
|
||||||
|
ready.call('alt-a')
|
||||||
tmux.send_keys :Escape, :b
|
tmux.send_keys :Escape, :b
|
||||||
|
ready.call('alt-b')
|
||||||
|
|
||||||
wait do
|
wait do
|
||||||
assert_path_exists output
|
assert_path_exists output
|
||||||
assert_equal %w[
|
assert_equal %w[
|
||||||
/foo'bar/ /foo'bar/
|
/foo'bar/ /foo'barfoo'bar/
|
||||||
/foo"barfoo"bar/ /foo"barfoo"bar/
|
/foo"bar/ /foo"barfoo"bar/
|
||||||
/foo$barfoo$barfoo$bar/
|
/foo$barfoo$barfoo$bar/
|
||||||
], File.readlines(output, chomp: true)
|
], File.readlines(output, chomp: true)
|
||||||
end
|
end
|
||||||
@@ -985,17 +1007,28 @@ class TestGoFZF < TestBase
|
|||||||
|
|
||||||
def test_execute_multi
|
def test_execute_multi
|
||||||
output = '/tmp/fzf-test-execute-multi'
|
output = '/tmp/fzf-test-execute-multi'
|
||||||
opts = %[--multi --bind "alt-a:execute-multi(echo {}/{+} >> #{output})"]
|
opts = %[--multi --bind "alt-a:execute-multi(echo {}/{+} >> #{output})+change-header(alt-a),alt-b:change-header(alt-b)"]
|
||||||
writelines(tempname, %w[foo'bar foo"bar foo$bar foobar])
|
writelines(tempname, %w[foo'bar foo"bar foo$bar foobar])
|
||||||
tmux.send_keys "cat #{tempname} | #{fzf(opts)}", :Enter
|
tmux.send_keys "cat #{tempname} | #{fzf(opts)}", :Enter
|
||||||
|
ready = ->(s) { tmux.until { |lines| assert_includes lines[-3], s } }
|
||||||
|
|
||||||
tmux.until { |lines| assert_equal ' 4/4 (0)', lines[-2] }
|
tmux.until { |lines| assert_equal ' 4/4 (0)', lines[-2] }
|
||||||
tmux.send_keys :Escape, :a
|
tmux.send_keys :Escape, :a
|
||||||
|
ready.call('alt-a')
|
||||||
|
tmux.send_keys :Escape, :b
|
||||||
|
ready.call('alt-b')
|
||||||
|
|
||||||
tmux.send_keys :BTab, :BTab, :BTab
|
tmux.send_keys :BTab, :BTab, :BTab
|
||||||
tmux.until { |lines| assert_equal ' 4/4 (3)', lines[-2] }
|
tmux.until { |lines| assert_equal ' 4/4 (3)', lines[-2] }
|
||||||
tmux.send_keys :Escape, :a
|
tmux.send_keys :Escape, :a
|
||||||
|
ready.call('alt-a')
|
||||||
|
tmux.send_keys :Escape, :b
|
||||||
|
ready.call('alt-b')
|
||||||
|
|
||||||
tmux.send_keys :Tab, :Tab
|
tmux.send_keys :Tab, :Tab
|
||||||
tmux.until { |lines| assert_equal ' 4/4 (3)', lines[-2] }
|
tmux.until { |lines| assert_equal ' 4/4 (3)', lines[-2] }
|
||||||
tmux.send_keys :Escape, :a
|
tmux.send_keys :Escape, :a
|
||||||
|
ready.call('alt-a')
|
||||||
wait do
|
wait do
|
||||||
assert_path_exists output
|
assert_path_exists output
|
||||||
assert_equal [
|
assert_equal [
|
||||||
@@ -1212,6 +1245,39 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_toggle_header
|
||||||
|
tmux.send_keys "seq 4 | #{FZF} --header-lines 2 --header foo --bind space:toggle-header --header-first --height 10 --border rounded", :Enter
|
||||||
|
before = <<~OUTPUT
|
||||||
|
╭───────
|
||||||
|
│
|
||||||
|
│ 4
|
||||||
|
│ > 3
|
||||||
|
│ 2/2
|
||||||
|
│ >
|
||||||
|
│ 2
|
||||||
|
│ 1
|
||||||
|
│ foo
|
||||||
|
╰───────
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(before, _1) }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
after = <<~OUTPUT
|
||||||
|
╭───────
|
||||||
|
│
|
||||||
|
│
|
||||||
|
│
|
||||||
|
│
|
||||||
|
│ 4
|
||||||
|
│ > 3
|
||||||
|
│ 2/2
|
||||||
|
│ >
|
||||||
|
╰───────
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(after, _1) }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { assert_block(before, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
def test_cancel
|
def test_cancel
|
||||||
tmux.send_keys "seq 10 | #{fzf('--bind 2:cancel')}", :Enter
|
tmux.send_keys "seq 10 | #{fzf('--bind 2:cancel')}", :Enter
|
||||||
tmux.until { |lines| assert_equal ' 10/10', lines[-2] }
|
tmux.until { |lines| assert_equal ' 10/10', lines[-2] }
|
||||||
@@ -1741,6 +1807,35 @@ class TestGoFZF < TestBase
|
|||||||
assert_equal %w[foo], readonce.lines(chomp: true)
|
assert_equal %w[foo], readonce.lines(chomp: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_accept_or_print_query_without_match
|
||||||
|
tmux.send_keys %(seq 1000 | #{fzf('--bind enter:accept-or-print-query')}), :Enter
|
||||||
|
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||||
|
tmux.send_keys 99_999
|
||||||
|
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
assert_equal %w[99999], readonce.lines(chomp: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_accept_or_print_query_with_match
|
||||||
|
tmux.send_keys %(seq 1000 | #{fzf('--bind enter:accept-or-print-query')}), :Enter
|
||||||
|
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||||
|
tmux.send_keys '^99$'
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
assert_equal %w[99], readonce.lines(chomp: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_accept_or_print_query_with_multi_selection
|
||||||
|
tmux.send_keys %(seq 1000 | #{fzf('--bind enter:accept-or-print-query --multi')}), :Enter
|
||||||
|
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||||
|
tmux.send_keys :BTab, :BTab, :BTab
|
||||||
|
tmux.until { |lines| assert_equal 3, lines.select_count }
|
||||||
|
tmux.send_keys 99_999
|
||||||
|
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
assert_equal %w[1 2 3], readonce.lines(chomp: true)
|
||||||
|
end
|
||||||
|
|
||||||
def test_preview_update_on_select
|
def test_preview_update_on_select
|
||||||
tmux.send_keys %(seq 10 | fzf -m --preview 'echo {+}' --bind a:toggle-all),
|
tmux.send_keys %(seq 10 | fzf -m --preview 'echo {+}' --bind a:toggle-all),
|
||||||
:Enter
|
:Enter
|
||||||
@@ -1952,6 +2047,13 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| assert_equal '> RAB', lines[-1] }
|
tmux.until { |lines| assert_equal '> RAB', lines[-1] }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_transform
|
||||||
|
tmux.send_keys %{#{FZF} --bind 'focus:transform:echo "change-prompt({fzf:action})"'}, :Enter
|
||||||
|
tmux.until { |lines| assert_equal 'start', lines[-1] }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_equal 'up', lines[-1] }
|
||||||
|
end
|
||||||
|
|
||||||
def test_clear_selection
|
def test_clear_selection
|
||||||
tmux.send_keys %(seq 100 | #{FZF} --multi --bind space:clear-selection), :Enter
|
tmux.send_keys %(seq 100 | #{FZF} --multi --bind space:clear-selection), :Enter
|
||||||
tmux.until { |lines| assert_equal 100, lines.match_count }
|
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||||
@@ -2083,14 +2185,15 @@ class TestGoFZF < TestBase
|
|||||||
file = Tempfile.new('fzf-follow')
|
file = Tempfile.new('fzf-follow')
|
||||||
file.sync = true
|
file.sync = true
|
||||||
|
|
||||||
tmux.send_keys %(seq 100 | #{FZF} --preview 'tail -f "#{file.path}"' --preview-window follow --bind 'up:preview-up,down:preview-down,space:change-preview-window:follow|nofollow' --preview-window '~3'), :Enter
|
tmux.send_keys %(seq 100 | #{FZF} --preview 'echo start; tail -f "#{file.path}"' --preview-window follow --bind 'up:preview-up,down:preview-down,space:change-preview-window:follow|nofollow' --preview-window '~4'), :Enter
|
||||||
tmux.until { |lines| lines.item_count == 100 }
|
tmux.until { |lines| lines.item_count == 100 }
|
||||||
|
|
||||||
# Write to the temporary file, and check if the preview window is showing
|
# Write to the temporary file, and check if the preview window is showing
|
||||||
# the last line of the file
|
# the last line of the file
|
||||||
|
tmux.until { |lines| assert_includes lines[1], 'start' }
|
||||||
3.times { file.puts _1 } # header lines
|
3.times { file.puts _1 } # header lines
|
||||||
1000.times { file.puts _1 }
|
1000.times { file.puts _1 }
|
||||||
tmux.until { |lines| assert_includes lines[1], '/1003' }
|
tmux.until { |lines| assert_includes lines[1], '/1004' }
|
||||||
tmux.until { |lines| assert_includes lines[-2], '999' }
|
tmux.until { |lines| assert_includes lines[-2], '999' }
|
||||||
|
|
||||||
# Scroll the preview window and fzf should stop following the file content
|
# Scroll the preview window and fzf should stop following the file content
|
||||||
@@ -2098,7 +2201,7 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| assert_includes lines[-2], '998' }
|
tmux.until { |lines| assert_includes lines[-2], '998' }
|
||||||
file.puts 'foo', 'bar'
|
file.puts 'foo', 'bar'
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
assert_includes lines[1], '/1005'
|
assert_includes lines[1], '/1006'
|
||||||
assert_includes lines[-2], '998'
|
assert_includes lines[-2], '998'
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -2111,7 +2214,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
file.puts 'baz'
|
file.puts 'baz'
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
assert_includes lines[1], '/1006'
|
assert_includes lines[1], '/1007'
|
||||||
assert_includes lines[-2], 'baz'
|
assert_includes lines[-2], 'baz'
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -2120,7 +2223,7 @@ class TestGoFZF < TestBase
|
|||||||
wait { assert_includes lines[-2], 'bar' }
|
wait { assert_includes lines[-2], 'bar' }
|
||||||
file.puts 'aaa'
|
file.puts 'aaa'
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
assert_includes lines[1], '/1007'
|
assert_includes lines[1], '/1008'
|
||||||
assert_includes lines[-2], 'bar'
|
assert_includes lines[-2], 'bar'
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -2129,7 +2232,7 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| assert_includes lines[-2], 'aaa' }
|
tmux.until { |lines| assert_includes lines[-2], 'aaa' }
|
||||||
file.puts 'bbb'
|
file.puts 'bbb'
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
assert_includes lines[1], '/1008'
|
assert_includes lines[1], '/1009'
|
||||||
assert_includes lines[-2], 'bbb'
|
assert_includes lines[-2], 'bbb'
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -2137,7 +2240,7 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys :Space
|
tmux.send_keys :Space
|
||||||
file.puts 'ccc', 'ddd'
|
file.puts 'ccc', 'ddd'
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
assert_includes lines[1], '/1010'
|
assert_includes lines[1], '/1011'
|
||||||
assert_includes lines[-2], 'bbb'
|
assert_includes lines[-2], 'bbb'
|
||||||
end
|
end
|
||||||
rescue StandardError
|
rescue StandardError
|
||||||
@@ -2539,7 +2642,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_height_range_fit
|
def test_height_range_fit
|
||||||
tmux.send_keys 'seq 3 | fzf --height ~100% --info=inline --border', :Enter
|
tmux.send_keys 'seq 3 | fzf --height ~100% --info=inline --border rounded', :Enter
|
||||||
expected = <<~OUTPUT
|
expected = <<~OUTPUT
|
||||||
╭──────────
|
╭──────────
|
||||||
│ 3
|
│ 3
|
||||||
@@ -2552,7 +2655,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_height_range_fit_preview_above
|
def test_height_range_fit_preview_above
|
||||||
tmux.send_keys 'seq 3 | fzf --height ~100% --info=inline --border --preview "seq {}" --preview-window up,60%', :Enter
|
tmux.send_keys 'seq 3 | fzf --height ~100% --info=inline --border rounded --preview-window border-rounded --preview "seq {}" --preview-window up,60%', :Enter
|
||||||
expected = <<~OUTPUT
|
expected = <<~OUTPUT
|
||||||
╭──────────
|
╭──────────
|
||||||
│ ╭────────
|
│ ╭────────
|
||||||
@@ -2608,7 +2711,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_height_range_overflow
|
def test_height_range_overflow
|
||||||
tmux.send_keys 'seq 100 | fzf --height ~5 --info=inline --border', :Enter
|
tmux.send_keys 'seq 100 | fzf --height ~5 --info=inline --border rounded', :Enter
|
||||||
expected = <<~OUTPUT
|
expected = <<~OUTPUT
|
||||||
╭──────────────
|
╭──────────────
|
||||||
│ 2
|
│ 2
|
||||||
@@ -2634,12 +2737,26 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| assert_includes(lines[-1], '[[2]]') }
|
tmux.until { |lines| assert_includes(lines[-1], '[[2]]') }
|
||||||
tmux.send_keys :X
|
tmux.send_keys :X
|
||||||
tmux.until { |lines| assert_includes(lines[-1], '[[]]') }
|
tmux.until { |lines| assert_includes(lines[-1], '[[]]') }
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| assert_includes(lines[-1], '[[1]]') }
|
||||||
|
tmux.send_keys :X
|
||||||
|
tmux.until { |lines| assert_includes(lines[-1], '[[]]') }
|
||||||
tmux.send_keys '?'
|
tmux.send_keys '?'
|
||||||
tmux.send_keys :BSpace
|
tmux.send_keys :BSpace
|
||||||
tmux.until { |lines| assert_equal 100, lines.match_count }
|
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||||
tmux.until { |lines| refute_includes(lines[-1], '[[1]]') }
|
tmux.until { |lines| refute_includes(lines[-1], '[[1]]') }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_result_event
|
||||||
|
tmux.send_keys '(echo 0; seq 10) | fzf --bind "result:pos(2)"', :Enter
|
||||||
|
tmux.until { |lines| assert_equal 11, lines.item_count }
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 1' }
|
||||||
|
tmux.send_keys '9'
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 9' }
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 1' }
|
||||||
|
end
|
||||||
|
|
||||||
def test_labels_center
|
def test_labels_center
|
||||||
tmux.send_keys 'echo x | fzf --border --border-label foobar --preview : --preview-label barfoo --bind "space:change-border-label(foobarfoo)+change-preview-label(barfoobar),enter:transform-border-label(echo foo{}foo)+transform-preview-label(echo bar{}bar)"', :Enter
|
tmux.send_keys 'echo x | fzf --border --border-label foobar --preview : --preview-label barfoo --bind "space:change-border-label(foobarfoo)+change-preview-label(barfoobar),enter:transform-border-label(echo foo{}foo)+transform-preview-label(echo bar{}bar)"', :Enter
|
||||||
tmux.until do
|
tmux.until do
|
||||||
@@ -2659,7 +2776,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_labels_left
|
def test_labels_left
|
||||||
tmux.send_keys ': | fzf --border --border-label foobar --border-label-pos 2 --preview : --preview-label barfoo --preview-label-pos 2', :Enter
|
tmux.send_keys ': | fzf --border rounded --preview-window border-rounded --border-label foobar --border-label-pos 2 --preview : --preview-label barfoo --preview-label-pos 2', :Enter
|
||||||
tmux.until do
|
tmux.until do
|
||||||
assert_includes(_1[0], '╭foobar─')
|
assert_includes(_1[0], '╭foobar─')
|
||||||
assert_includes(_1[1], '╭barfoo─')
|
assert_includes(_1[1], '╭barfoo─')
|
||||||
@@ -2667,7 +2784,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_labels_right
|
def test_labels_right
|
||||||
tmux.send_keys ': | fzf --border --border-label foobar --border-label-pos -2 --preview : --preview-label barfoo --preview-label-pos -2', :Enter
|
tmux.send_keys ': | fzf --border rounded --preview-window border-rounded --border-label foobar --border-label-pos -2 --preview : --preview-label barfoo --preview-label-pos -2', :Enter
|
||||||
tmux.until do
|
tmux.until do
|
||||||
assert_includes(_1[0], '─foobar╮')
|
assert_includes(_1[0], '─foobar╮')
|
||||||
assert_includes(_1[1], '─barfoo╮')
|
assert_includes(_1[1], '─barfoo╮')
|
||||||
@@ -2675,7 +2792,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_labels_bottom
|
def test_labels_bottom
|
||||||
tmux.send_keys ': | fzf --border --border-label foobar --border-label-pos 2:bottom --preview : --preview-label barfoo --preview-label-pos -2:bottom', :Enter
|
tmux.send_keys ': | fzf --border rounded --preview-window border-rounded --border-label foobar --border-label-pos 2:bottom --preview : --preview-label barfoo --preview-label-pos -2:bottom', :Enter
|
||||||
tmux.until do
|
tmux.until do
|
||||||
assert_includes(_1[-1], '╰foobar─')
|
assert_includes(_1[-1], '╰foobar─')
|
||||||
assert_includes(_1[-2], '─barfoo╯')
|
assert_includes(_1[-2], '─barfoo╯')
|
||||||
@@ -2707,6 +2824,16 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { assert(_1[-2] == ' 1/100') }
|
tmux.until { assert(_1[-2] == ' 1/100') }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_info_right
|
||||||
|
tmux.send_keys "#{FZF} --info=right --separator x --bind 'start:reload:seq 100; sleep 10'", :Enter
|
||||||
|
tmux.until { assert_match(%r{xxx [⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏] 100/100}, _1[-2]) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_info_inline_right
|
||||||
|
tmux.send_keys "#{FZF} --info=inline-right --bind 'start:reload:seq 100; sleep 10'", :Enter
|
||||||
|
tmux.until { assert_match(%r{[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏] 100/100}, _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 }
|
||||||
@@ -2732,14 +2859,46 @@ class TestGoFZF < TestBase
|
|||||||
-> { URI("http://localhost:#{File.read('/tmp/fzf-port').chomp}") } }.each do |opts, fn|
|
-> { URI("http://localhost:#{File.read('/tmp/fzf-port').chomp}") } }.each do |opts, fn|
|
||||||
tmux.send_keys "seq 10 | fzf #{opts}", :Enter
|
tmux.send_keys "seq 10 | fzf #{opts}", :Enter
|
||||||
tmux.until { |lines| assert_equal 10, lines.item_count }
|
tmux.until { |lines| assert_equal 10, lines.item_count }
|
||||||
|
state = JSON.parse(Net::HTTP.get(fn.call), symbolize_names: true)
|
||||||
|
assert_equal 10, state[:totalCount]
|
||||||
|
assert_equal 10, state[:matchCount]
|
||||||
|
assert_empty state[:query]
|
||||||
|
assert_equal({ index: 0, text: '1' }, state[:current])
|
||||||
|
|
||||||
Net::HTTP.post(fn.call, 'change-query(yo)+reload(seq 100)+change-prompt:hundred> ')
|
Net::HTTP.post(fn.call, 'change-query(yo)+reload(seq 100)+change-prompt:hundred> ')
|
||||||
tmux.until { |lines| assert_equal 100, lines.item_count }
|
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||||
tmux.until { |lines| assert_equal 'hundred> yo', lines[-1] }
|
tmux.until { |lines| assert_equal 'hundred> yo', lines[-1] }
|
||||||
|
state = JSON.parse(Net::HTTP.get(fn.call), symbolize_names: true)
|
||||||
|
assert_equal 100, state[:totalCount]
|
||||||
|
assert_equal 0, state[:matchCount]
|
||||||
|
assert_equal 'yo', state[:query]
|
||||||
|
assert_nil state[:current]
|
||||||
|
|
||||||
teardown
|
teardown
|
||||||
setup
|
setup
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_listen_with_api_key
|
||||||
|
post_uri = URI('http://localhost:6266')
|
||||||
|
tmux.send_keys 'seq 10 | FZF_API_KEY=123abc fzf --listen 6266', :Enter
|
||||||
|
tmux.until { |lines| assert_equal 10, lines.item_count }
|
||||||
|
# Incorrect API Key
|
||||||
|
[nil, { 'x-api-key' => '' }, { 'x-api-key' => '124abc' }].each do |headers|
|
||||||
|
res = Net::HTTP.post(post_uri, 'change-query(yo)+reload(seq 100)+change-prompt:hundred> ', headers)
|
||||||
|
assert_equal '401', res.code
|
||||||
|
assert_equal 'Unauthorized', res.message
|
||||||
|
assert_equal "invalid api key\n", res.body
|
||||||
|
end
|
||||||
|
# Valid API Key
|
||||||
|
[{ 'x-api-key' => '123abc' }, { 'X-API-Key' => '123abc' }].each do |headers|
|
||||||
|
res = Net::HTTP.post(post_uri, 'change-query(yo)+reload(seq 100)+change-prompt:hundred> ', headers)
|
||||||
|
assert_equal '200', res.code
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||||
|
tmux.until { |lines| assert_equal 'hundred> yo', lines[-1] }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_toggle_alternative_preview_window
|
def test_toggle_alternative_preview_window
|
||||||
tmux.send_keys "seq 10 | #{FZF} --bind space:toggle-preview --preview-window '<100000(hidden,up,border-none)' --preview 'echo /{}/{}/'", :Enter
|
tmux.send_keys "seq 10 | #{FZF} --bind space:toggle-preview --preview-window '<100000(hidden,up,border-none)' --preview 'echo /{}/{}/'", :Enter
|
||||||
tmux.until { |lines| assert_equal 10, lines.item_count }
|
tmux.until { |lines| assert_equal 10, lines.item_count }
|
||||||
@@ -2762,7 +2921,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_no_extra_newline_issue_3209
|
def test_no_extra_newline_issue_3209
|
||||||
tmux.send_keys(%(seq 100 | #{FZF} --height 10 --preview-window up,wrap --preview 'printf "─%.0s" $(seq 1 "$((FZF_PREVIEW_COLUMNS - 5))"); printf $"\\e[7m%s\\e[0m" title; echo; echo something'), :Enter)
|
tmux.send_keys(%(seq 100 | #{FZF} --height 10 --preview-window up,wrap,border-rounded --preview 'printf "─%.0s" $(seq 1 "$((FZF_PREVIEW_COLUMNS - 5))"); printf $"\\e[7m%s\\e[0m" title; echo; echo something'), :Enter)
|
||||||
expected = <<~OUTPUT
|
expected = <<~OUTPUT
|
||||||
╭──────────
|
╭──────────
|
||||||
│ ─────────
|
│ ─────────
|
||||||
@@ -2923,6 +3082,11 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_delete_with_modifiers
|
def test_delete_with_modifiers
|
||||||
|
if ENV['GITHUB_ACTION']
|
||||||
|
# Expected: "[3]"
|
||||||
|
# Actual: "[]3;5~"
|
||||||
|
skip('CTRL-DELETE is not properly handled in GitHub Actions environment')
|
||||||
|
end
|
||||||
tmux.send_keys "seq 100 | #{FZF} --bind 'ctrl-delete:up+up,shift-delete:down,focus:transform-prompt:echo [{}]'", :Enter
|
tmux.send_keys "seq 100 | #{FZF} --bind 'ctrl-delete:up+up,shift-delete:down,focus:transform-prompt:echo [{}]'", :Enter
|
||||||
tmux.until { |lines| assert_equal 100, lines.item_count }
|
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||||
tmux.send_keys 'C-Delete'
|
tmux.send_keys 'C-Delete'
|
||||||
@@ -2943,6 +3107,13 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys :x
|
tmux.send_keys :x
|
||||||
tmux.until { |lines| assert(lines.any? { |line| line.include?('[x-foo]') }) }
|
tmux.until { |lines| assert(lines.any? { |line| line.include?('[x-foo]') }) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_preview_window_hidden_on_focus
|
||||||
|
tmux.send_keys "seq 3 | #{FZF} --preview 'echo {}' --bind focus:hide-preview", :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 1' }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 2' }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module TestShell
|
module TestShell
|
||||||
@@ -3288,6 +3459,34 @@ module CompletionTest
|
|||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'unset -f _fzf_comprun', :Enter
|
tmux.send_keys 'unset -f _fzf_comprun', :Enter
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_ssh_completion
|
||||||
|
(1..5).each { |i| FileUtils.touch("/tmp/fzf-test-ssh-#{i}") }
|
||||||
|
|
||||||
|
tmux.send_keys 'ssh jg@localhost**', :Tab
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.match_count >= 1
|
||||||
|
end
|
||||||
|
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert lines.any_include?('ssh jg@localhost') }
|
||||||
|
tmux.send_keys ' -i /tmp/fzf-test-ssh**', :Tab
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.match_count >= 5
|
||||||
|
assert_equal 0, lines.select_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :Tab, :Tab, :Tab
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 3, lines.select_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert lines.any_include?('ssh jg@localhost -i /tmp/fzf-test-ssh-') }
|
||||||
|
|
||||||
|
tmux.send_keys 'localhost**', :Tab
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.match_count >= 1
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class TestBash < TestBase
|
class TestBash < TestBase
|
||||||
|
@@ -4,3 +4,7 @@ ba = "ba"
|
|||||||
fo = "fo"
|
fo = "fo"
|
||||||
enew = "enew"
|
enew = "enew"
|
||||||
tabe = "tabe"
|
tabe = "tabe"
|
||||||
|
Iterm = "Iterm"
|
||||||
|
|
||||||
|
[files]
|
||||||
|
extend-exclude = ["README.md"]
|
||||||
|
Reference in New Issue
Block a user