mirror of
https://github.com/junegunn/fzf.git
synced 2025-12-06 21:04:26 +08:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
352ea07226 | ||
|
|
27018787af | ||
|
|
4e305eca26 | ||
|
|
9e9c0ceaf4 | ||
|
|
b3bf18b1c0 | ||
|
|
b1619f675f | ||
|
|
96c3de12eb | ||
|
|
719dbb8bae | ||
|
|
f38a7f7f8f | ||
|
|
6ea38b4438 | ||
|
|
f7447aece1 | ||
|
|
aa2b9ec476 | ||
|
|
3ee00f8bc2 | ||
|
|
fccab60a5c | ||
|
|
0f4af38457 | ||
|
|
aef39f1160 | ||
|
|
2023012408 | ||
|
|
95a7661bb1 | ||
|
|
618d317803 | ||
|
|
ae897c8cdb | ||
|
|
d0a0f3c052 | ||
|
|
91b9591b10 | ||
|
|
aa7361337d | ||
|
|
284d77fe2e | ||
|
|
826178f1e2 | ||
|
|
acccf8a9b8 | ||
|
|
57c066f0be | ||
|
|
e44f64ae92 | ||
|
|
d51980a3f5 | ||
|
|
c3d73e7ecb | ||
|
|
b077f6821d | ||
|
|
a79de11af7 |
15
.github/workflows/winget.yml
vendored
Normal file
15
.github/workflows/winget.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
name: Publish to Winget
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [released]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: windows-latest # Action can only run on Windows
|
||||||
|
steps:
|
||||||
|
- uses: vedantmgoyal2009/winget-releaser@v2
|
||||||
|
with:
|
||||||
|
identifier: junegunn.fzf
|
||||||
|
version: ${{ github.event.release.tag_name }}
|
||||||
|
installers-regex: '-windows_(armv7|arm64|amd64)\.zip$'
|
||||||
|
token: ${{ secrets.WINGET_TOKEN }}
|
||||||
78
ADVANCED.md
78
ADVANCED.md
@@ -1,7 +1,10 @@
|
|||||||
Advanced fzf examples
|
Advanced fzf examples
|
||||||
======================
|
======================
|
||||||
|
|
||||||
*(Last update: 2022/08/25)*
|
* *Last update: 2023/02/15*
|
||||||
|
* *Requires fzf 0.38.0 or above*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
<!-- vim-markdown-toc GFM -->
|
<!-- vim-markdown-toc GFM -->
|
||||||
|
|
||||||
@@ -236,15 +239,13 @@ file called `rfv`.
|
|||||||
# 1. Search for text in files using Ripgrep
|
# 1. Search for text in files using Ripgrep
|
||||||
# 2. Interactively narrow down the list using fzf
|
# 2. Interactively narrow down the list using fzf
|
||||||
# 3. Open the file in Vim
|
# 3. Open the file in Vim
|
||||||
IFS=: read -ra selected < <(
|
rg --color=always --line-number --no-heading --smart-case "${*:-}" |
|
||||||
rg --color=always --line-number --no-heading --smart-case "${*:-}" |
|
|
||||||
fzf --ansi \
|
fzf --ansi \
|
||||||
--color "hl:-1:underline,hl+:-1:underline:reverse" \
|
--color "hl:-1:underline,hl+:-1:underline:reverse" \
|
||||||
--delimiter : \
|
--delimiter : \
|
||||||
--preview 'bat --color=always {1} --highlight-line {2}' \
|
--preview 'bat --color=always {1} --highlight-line {2}' \
|
||||||
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3'
|
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \
|
||||||
)
|
--bind 'enter:become(vim {1} +{2})'
|
||||||
[ -n "${selected[0]}" ] && vim "${selected[0]}" "+${selected[1]}"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
And run it with an initial query string.
|
And run it with an initial query string.
|
||||||
@@ -307,8 +308,12 @@ I know it's a lot to digest, let's try to break down the code.
|
|||||||
position in the window
|
position in the window
|
||||||
- `~3` makes the top three lines fixed header so that they are always
|
- `~3` makes the top three lines fixed header so that they are always
|
||||||
visible regardless of the scroll offset
|
visible regardless of the scroll offset
|
||||||
- Once we selected a line, we open the file with `vim` (`vim
|
- Instead of using shell script to process the final output of fzf, we use
|
||||||
"${selected[0]}"`) and move the cursor to the line (`+${selected[1]}`).
|
`become(...)` action which was added in [fzf 0.38.0][0.38.0] to turn fzf
|
||||||
|
into a new process that opens the file with `vim` (`vim {1}`) and move the
|
||||||
|
cursor to the line (`+{2}`).
|
||||||
|
|
||||||
|
[0.38.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0380
|
||||||
|
|
||||||
### Using fzf as interactive Ripgrep launcher
|
### Using fzf as interactive Ripgrep launcher
|
||||||
|
|
||||||
@@ -331,16 +336,14 @@ projects, and it will free up memory as you narrow down the results.
|
|||||||
# 3. Open the file in Vim
|
# 3. Open the file in Vim
|
||||||
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
||||||
INITIAL_QUERY="${*:-}"
|
INITIAL_QUERY="${*:-}"
|
||||||
IFS=: read -ra selected < <(
|
FZF_DEFAULT_COMMAND="$RG_PREFIX $(printf %q "$INITIAL_QUERY")" \
|
||||||
FZF_DEFAULT_COMMAND="$RG_PREFIX $(printf %q "$INITIAL_QUERY")" \
|
fzf --ansi \
|
||||||
fzf --ansi \
|
|
||||||
--disabled --query "$INITIAL_QUERY" \
|
--disabled --query "$INITIAL_QUERY" \
|
||||||
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
|
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
|
||||||
--delimiter : \
|
--delimiter : \
|
||||||
--preview 'bat --color=always {1} --highlight-line {2}' \
|
--preview 'bat --color=always {1} --highlight-line {2}' \
|
||||||
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3'
|
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \
|
||||||
)
|
--bind 'enter:become(vim {1} +{2})'
|
||||||
[ -n "${selected[0]}" ] && vim "${selected[0]}" "+${selected[1]}"
|
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
@@ -358,8 +361,6 @@ IFS=: read -ra selected < <(
|
|||||||
|
|
||||||
### Switching to fzf-only search mode
|
### Switching to fzf-only search mode
|
||||||
|
|
||||||
*(Requires fzf 0.27.1 or above)*
|
|
||||||
|
|
||||||
In the previous example, we lost fuzzy matching capability as we completely
|
In the previous example, we lost fuzzy matching capability as we completely
|
||||||
delegated search functionality to Ripgrep. But we can dynamically switch to
|
delegated search functionality to Ripgrep. But we can dynamically switch to
|
||||||
fzf-only search mode by *"unbinding"* `reload` action from `change` event.
|
fzf-only search mode by *"unbinding"* `reload` action from `change` event.
|
||||||
@@ -375,9 +376,8 @@ fzf-only search mode by *"unbinding"* `reload` action from `change` event.
|
|||||||
# 3. Open the file in Vim
|
# 3. Open the file in Vim
|
||||||
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
||||||
INITIAL_QUERY="${*:-}"
|
INITIAL_QUERY="${*:-}"
|
||||||
IFS=: read -ra selected < <(
|
FZF_DEFAULT_COMMAND="$RG_PREFIX $(printf %q "$INITIAL_QUERY")" \
|
||||||
FZF_DEFAULT_COMMAND="$RG_PREFIX $(printf %q "$INITIAL_QUERY")" \
|
fzf --ansi \
|
||||||
fzf --ansi \
|
|
||||||
--color "hl:-1:underline,hl+:-1:underline:reverse" \
|
--color "hl:-1:underline,hl+:-1:underline:reverse" \
|
||||||
--disabled --query "$INITIAL_QUERY" \
|
--disabled --query "$INITIAL_QUERY" \
|
||||||
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
|
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
|
||||||
@@ -385,9 +385,8 @@ IFS=: read -ra selected < <(
|
|||||||
--prompt '1. ripgrep> ' \
|
--prompt '1. ripgrep> ' \
|
||||||
--delimiter : \
|
--delimiter : \
|
||||||
--preview 'bat --color=always {1} --highlight-line {2}' \
|
--preview 'bat --color=always {1} --highlight-line {2}' \
|
||||||
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3'
|
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \
|
||||||
)
|
--bind 'enter:become(vim {1} +{2})'
|
||||||
[ -n "${selected[0]}" ] && vim "${selected[0]}" "+${selected[1]}"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
* Phase 1. Filtering with Ripgrep
|
* Phase 1. Filtering with Ripgrep
|
||||||
@@ -408,10 +407,8 @@ IFS=: read -ra selected < <(
|
|||||||
|
|
||||||
### Switching between Ripgrep mode and fzf mode
|
### Switching between Ripgrep mode and fzf mode
|
||||||
|
|
||||||
*(Requires fzf 0.30.0 or above)*
|
[fzf 0.30.0][0.30.0] added `rebind` action so we can "rebind" the bindings
|
||||||
|
that were previously "unbound" via `unbind`.
|
||||||
fzf 0.30.0 added `rebind` action so we can "rebind" the bindings that were
|
|
||||||
previously "unbound" via `unbind`.
|
|
||||||
|
|
||||||
This is an improved version of the previous example that allows us to switch
|
This is an improved version of the previous example that allows us to switch
|
||||||
between Ripgrep launcher mode and fzf-only filtering mode via CTRL-R and
|
between Ripgrep launcher mode and fzf-only filtering mode via CTRL-R and
|
||||||
@@ -421,25 +418,34 @@ CTRL-F.
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Switch between Ripgrep launcher mode (CTRL-R) and fzf filtering mode (CTRL-F)
|
# Switch between Ripgrep launcher mode (CTRL-R) and fzf filtering mode (CTRL-F)
|
||||||
|
rm -f /tmp/rg-fzf-{r,f}
|
||||||
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
|
||||||
INITIAL_QUERY="${*:-}"
|
INITIAL_QUERY="${*:-}"
|
||||||
IFS=: read -ra selected < <(
|
FZF_DEFAULT_COMMAND="$RG_PREFIX $(printf %q "$INITIAL_QUERY")" \
|
||||||
FZF_DEFAULT_COMMAND="$RG_PREFIX $(printf %q "$INITIAL_QUERY")" \
|
fzf --ansi \
|
||||||
fzf --ansi \
|
|
||||||
--color "hl:-1:underline,hl+:-1:underline:reverse" \
|
--color "hl:-1:underline,hl+:-1:underline:reverse" \
|
||||||
--disabled --query "$INITIAL_QUERY" \
|
--disabled --query "$INITIAL_QUERY" \
|
||||||
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
|
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
|
||||||
--bind "ctrl-f:unbind(change,ctrl-f)+change-prompt(2. fzf> )+enable-search+clear-query+rebind(ctrl-r)" \
|
--bind "ctrl-f:unbind(change,ctrl-f)+change-prompt(2. fzf> )+enable-search+rebind(ctrl-r)+transform-query(echo {q} > /tmp/rg-fzf-r; cat /tmp/rg-fzf-f)" \
|
||||||
--bind "ctrl-r:unbind(ctrl-r)+change-prompt(1. ripgrep> )+disable-search+reload($RG_PREFIX {q} || true)+rebind(change,ctrl-f)" \
|
--bind "ctrl-r:unbind(ctrl-r)+change-prompt(1. ripgrep> )+disable-search+reload($RG_PREFIX {q} || true)+rebind(change,ctrl-f)+transform-query(echo {q} > /tmp/rg-fzf-f; cat /tmp/rg-fzf-r)" \
|
||||||
--prompt '1. Ripgrep> ' \
|
--bind "start:unbind(ctrl-r)" \
|
||||||
|
--prompt '1. ripgrep> ' \
|
||||||
--delimiter : \
|
--delimiter : \
|
||||||
--header '╱ CTRL-R (Ripgrep mode) ╱ CTRL-F (fzf mode) ╱' \
|
--header '╱ CTRL-R (ripgrep mode) ╱ CTRL-F (fzf mode) ╱' \
|
||||||
--preview 'bat --color=always {1} --highlight-line {2}' \
|
--preview 'bat --color=always {1} --highlight-line {2}' \
|
||||||
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3'
|
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3' \
|
||||||
)
|
--bind 'enter:become(vim {1} +{2})'
|
||||||
[ -n "${selected[0]}" ] && vim "${selected[0]}" "+${selected[1]}"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- To restore the query string when switching between modes, we store the
|
||||||
|
current query in `/tmp/rg-fzf-{r,f}` files and restore the query using
|
||||||
|
`transform-query` action which was added in [fzf 0.36.0][0.36.0].
|
||||||
|
- Also note that we unbind `ctrl-r` binding on `start` event which is
|
||||||
|
triggered once when fzf starts.
|
||||||
|
|
||||||
|
[0.30.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0300
|
||||||
|
[0.36.0]: https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0360
|
||||||
|
|
||||||
Log tailing
|
Log tailing
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
|||||||
49
CHANGELOG.md
49
CHANGELOG.md
@@ -1,6 +1,55 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.38.0
|
||||||
|
------
|
||||||
|
- New actions
|
||||||
|
- `become(...)` - Replace the current fzf process with the specified
|
||||||
|
command using `execve(2)` system call.
|
||||||
|
See https://github.com/junegunn/fzf#turning-into-a-different-process for
|
||||||
|
more information.
|
||||||
|
```sh
|
||||||
|
# Open selected files in Vim
|
||||||
|
fzf --multi --bind 'enter:become(vim {+})'
|
||||||
|
|
||||||
|
# Open the file in Vim and go to the line
|
||||||
|
git grep --line-number . |
|
||||||
|
fzf --delimiter : --nth 3.. --bind 'enter:become(vim {1} +{2})'
|
||||||
|
```
|
||||||
|
- This action is not supported on Windows
|
||||||
|
- `show-preview`
|
||||||
|
- `hide-preview`
|
||||||
|
- Bug fixes
|
||||||
|
- `--preview-window 0,hidden` should not execute the preview command until
|
||||||
|
`toggle-preview` action is triggered
|
||||||
|
|
||||||
|
0.37.0
|
||||||
|
------
|
||||||
|
- Added a way to customize the separator of inline info
|
||||||
|
```sh
|
||||||
|
fzf --info 'inline: ╱ ' --prompt '╱ ' --color prompt:bright-yellow
|
||||||
|
```
|
||||||
|
- New event
|
||||||
|
- `focus` - Triggered when the focus changes due to a vertical cursor
|
||||||
|
movement or a search result update
|
||||||
|
```sh
|
||||||
|
fzf --bind 'focus:transform-preview-label:echo [ {} ]' --preview 'cat {}'
|
||||||
|
|
||||||
|
# Any action bound to the event runs synchronously and thus can make the interface sluggish
|
||||||
|
# e.g. lolcat isn't one of the fastest programs, and every cursor movement in
|
||||||
|
# fzf will be noticeably affected by its execution time
|
||||||
|
fzf --bind 'focus:transform-preview-label:echo [ {} ] | lolcat -f' --preview 'cat {}'
|
||||||
|
|
||||||
|
# Beware not to introduce an infinite loop
|
||||||
|
seq 10 | fzf --bind 'focus:up' --cycle
|
||||||
|
```
|
||||||
|
- New actions
|
||||||
|
- `change-border-label`
|
||||||
|
- `change-preview-label`
|
||||||
|
- `transform-border-label`
|
||||||
|
- `transform-preview-label`
|
||||||
|
- Bug fixes and improvements
|
||||||
|
|
||||||
0.36.0
|
0.36.0
|
||||||
------
|
------
|
||||||
- Added `--listen=HTTP_PORT` option to start HTTP server. It allows external
|
- Added `--listen=HTTP_PORT` option to start HTTP server. It allows external
|
||||||
|
|||||||
92
README.md
92
README.md
@@ -54,6 +54,7 @@ Table of Contents
|
|||||||
* [Advanced topics](#advanced-topics)
|
* [Advanced topics](#advanced-topics)
|
||||||
* [Performance](#performance)
|
* [Performance](#performance)
|
||||||
* [Executing external programs](#executing-external-programs)
|
* [Executing external programs](#executing-external-programs)
|
||||||
|
* [Turning into a different process](#turning-into-a-different-process)
|
||||||
* [Reloading the candidate list](#reloading-the-candidate-list)
|
* [Reloading the candidate list](#reloading-the-candidate-list)
|
||||||
* [1. Update the list of processes by pressing CTRL-R](#1-update-the-list-of-processes-by-pressing-ctrl-r)
|
* [1. Update the list of processes by pressing CTRL-R](#1-update-the-list-of-processes-by-pressing-ctrl-r)
|
||||||
* [2. Switch between sources by pressing CTRL-D or CTRL-F](#2-switch-between-sources-by-pressing-ctrl-d-or-ctrl-f)
|
* [2. Switch between sources by pressing CTRL-D or CTRL-F](#2-switch-between-sources-by-pressing-ctrl-d-or-ctrl-f)
|
||||||
@@ -136,15 +137,17 @@ git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
|
|||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
Pre-built binaries for Windows can be downloaded [here][bin]. fzf is also
|
Pre-built binaries for Windows can be downloaded [here][bin]. fzf is also
|
||||||
available via [Chocolatey][choco] and [Scoop][scoop]:
|
available via [Chocolatey][choco], [Scoop][scoop], and [Winget][winget]:
|
||||||
|
|
||||||
| Package manager | Command |
|
| Package manager | Command |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Chocolatey | `choco install fzf` |
|
| Chocolatey | `choco install fzf` |
|
||||||
| Scoop | `scoop install fzf` |
|
| Scoop | `scoop install fzf` |
|
||||||
|
| Winget | `winget install fzf` |
|
||||||
|
|
||||||
[choco]: https://chocolatey.org/packages/fzf
|
[choco]: https://chocolatey.org/packages/fzf
|
||||||
[scoop]: https://github.com/ScoopInstaller/Main/blob/master/bucket/fzf.json
|
[scoop]: https://github.com/ScoopInstaller/Main/blob/master/bucket/fzf.json
|
||||||
|
[winget]: https://github.com/microsoft/winget-pkgs/tree/master/manifests/j/junegunn/fzf
|
||||||
|
|
||||||
Known issues and limitations on Windows can be found on [the wiki
|
Known issues and limitations on Windows can be found on [the wiki
|
||||||
page][windows-wiki].
|
page][windows-wiki].
|
||||||
@@ -202,7 +205,23 @@ files excluding hidden ones. (You can override the default command with
|
|||||||
vim $(fzf)
|
vim $(fzf)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Using the finder
|
> *:bulb: A more robust solution would be to use `xargs` but we've presented
|
||||||
|
> the above as it's easier to grasp*
|
||||||
|
> ```sh
|
||||||
|
> fzf --print0 | xargs -0 -o vim
|
||||||
|
> ```
|
||||||
|
|
||||||
|
>
|
||||||
|
> *:bulb: fzf also has the ability to turn itself into a different process.*
|
||||||
|
>
|
||||||
|
> ```sh
|
||||||
|
> fzf --bind 'enter:become(vim {})'
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> *See [Turning into a different process](#turning-into-a-different-process)
|
||||||
|
> for more information.*
|
||||||
|
|
||||||
|
### Using the finder
|
||||||
|
|
||||||
- `CTRL-K` / `CTRL-J` (or `CTRL-P` / `CTRL-N`) to move cursor up and down
|
- `CTRL-K` / `CTRL-J` (or `CTRL-P` / `CTRL-N`) to move cursor up and down
|
||||||
- `Enter` key to select the item, `CTRL-C` / `CTRL-G` / `ESC` to exit
|
- `Enter` key to select the item, `CTRL-C` / `CTRL-G` / `ESC` to exit
|
||||||
@@ -211,7 +230,7 @@ vim $(fzf)
|
|||||||
- Mouse: scroll, click, double-click; shift-click and shift-scroll on
|
- Mouse: scroll, click, double-click; shift-click and shift-scroll on
|
||||||
multi-select mode
|
multi-select mode
|
||||||
|
|
||||||
#### Layout
|
### Layout
|
||||||
|
|
||||||
fzf by default starts in fullscreen mode, but you can make it start below the
|
fzf by default starts in fullscreen mode, but you can make it start below the
|
||||||
cursor with `--height` option.
|
cursor with `--height` option.
|
||||||
@@ -234,7 +253,7 @@ default. For example,
|
|||||||
export FZF_DEFAULT_OPTS='--height 40% --layout=reverse --border'
|
export FZF_DEFAULT_OPTS='--height 40% --layout=reverse --border'
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Search syntax
|
### Search syntax
|
||||||
|
|
||||||
Unless otherwise specified, fzf starts in "extended-search mode" where you can
|
Unless otherwise specified, fzf starts in "extended-search mode" where you can
|
||||||
type in multiple search terms delimited by spaces. e.g. `^music .mp3$ sbtrkt
|
type in multiple search terms delimited by spaces. e.g. `^music .mp3$ sbtrkt
|
||||||
@@ -262,7 +281,7 @@ or `py`.
|
|||||||
^core go$ | rb$ | py$
|
^core go$ | rb$ | py$
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Environment variables
|
### Environment variables
|
||||||
|
|
||||||
- `FZF_DEFAULT_COMMAND`
|
- `FZF_DEFAULT_COMMAND`
|
||||||
- Default command to use when input is tty
|
- Default command to use when input is tty
|
||||||
@@ -278,11 +297,11 @@ or `py`.
|
|||||||
- Default options
|
- Default options
|
||||||
- e.g. `export FZF_DEFAULT_OPTS="--layout=reverse --inline-info"`
|
- e.g. `export FZF_DEFAULT_OPTS="--layout=reverse --inline-info"`
|
||||||
|
|
||||||
#### Options
|
### Options
|
||||||
|
|
||||||
See the man page (`man fzf`) for the full list of options.
|
See the man page (`man fzf`) for the full list of options.
|
||||||
|
|
||||||
#### Demo
|
### Demo
|
||||||
If you learn by watching videos, check out this screencast by [@samoshkin](https://github.com/samoshkin) to explore `fzf` features.
|
If you learn by watching videos, check out this screencast by [@samoshkin](https://github.com/samoshkin) to explore `fzf` features.
|
||||||
|
|
||||||
<a title="fzf - command-line fuzzy finder" href="https://www.youtube.com/watch?v=qgG5Jhi_Els">
|
<a title="fzf - command-line fuzzy finder" href="https://www.youtube.com/watch?v=qgG5Jhi_Els">
|
||||||
@@ -335,7 +354,7 @@ fish.
|
|||||||
- Set `FZF_CTRL_T_COMMAND` to override the default command
|
- Set `FZF_CTRL_T_COMMAND` to override the default command
|
||||||
- Set `FZF_CTRL_T_OPTS` to pass additional options to fzf
|
- Set `FZF_CTRL_T_OPTS` to pass additional options to fzf
|
||||||
```sh
|
```sh
|
||||||
# Preview file content using bat (https://github.com/sharkdp/fd)
|
# Preview file content using bat (https://github.com/sharkdp/bat)
|
||||||
export FZF_CTRL_T_OPTS="
|
export FZF_CTRL_T_OPTS="
|
||||||
--preview 'bat -n --color=always {}'
|
--preview 'bat -n --color=always {}'
|
||||||
--bind 'ctrl-/:change-preview-window(down|hidden|)'"
|
--bind 'ctrl-/:change-preview-window(down|hidden|)'"
|
||||||
@@ -363,7 +382,7 @@ fish.
|
|||||||
```
|
```
|
||||||
|
|
||||||
If you're on a tmux session, you can start fzf in a tmux split-pane or in
|
If you're on a tmux session, you can start fzf in a tmux split-pane or in
|
||||||
a tmux popup window by setting `FZF_TMUX_OPTS` (e.g. `-d 40%`).
|
a tmux popup window by setting `FZF_TMUX_OPTS` (e.g. `export FZF_TMUX_OPTS='-p80%,60%'`).
|
||||||
See `fzf-tmux --help` for available options.
|
See `fzf-tmux --help` for available options.
|
||||||
|
|
||||||
More tips can be found on [the wiki page](https://github.com/junegunn/fzf/wiki/Configuring-shell-key-bindings).
|
More tips can be found on [the wiki page](https://github.com/junegunn/fzf/wiki/Configuring-shell-key-bindings).
|
||||||
@@ -371,7 +390,7 @@ More tips can be found on [the wiki page](https://github.com/junegunn/fzf/wiki/C
|
|||||||
Fuzzy completion for bash and zsh
|
Fuzzy completion for bash and zsh
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
#### Files and directories
|
### Files and directories
|
||||||
|
|
||||||
Fuzzy completion for files and directories can be triggered if the word before
|
Fuzzy completion for files and directories can be triggered if the word before
|
||||||
the cursor ends with the trigger sequence, which is by default `**`.
|
the cursor ends with the trigger sequence, which is by default `**`.
|
||||||
@@ -400,7 +419,7 @@ cd **<TAB>
|
|||||||
cd ~/github/fzf**<TAB>
|
cd ~/github/fzf**<TAB>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Process IDs
|
### Process IDs
|
||||||
|
|
||||||
Fuzzy completion for PIDs is provided for kill command.
|
Fuzzy completion for PIDs is provided for kill command.
|
||||||
|
|
||||||
@@ -409,7 +428,7 @@ Fuzzy completion for PIDs is provided for kill command.
|
|||||||
kill -9 **<TAB>
|
kill -9 **<TAB>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Host names
|
### Host names
|
||||||
|
|
||||||
For ssh and telnet commands, fuzzy completion for hostnames is provided. The
|
For ssh and telnet commands, fuzzy completion for hostnames is provided. The
|
||||||
names are extracted from /etc/hosts and ~/.ssh/config.
|
names are extracted from /etc/hosts and ~/.ssh/config.
|
||||||
@@ -419,7 +438,7 @@ ssh **<TAB>
|
|||||||
telnet **<TAB>
|
telnet **<TAB>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Environment variables / Aliases
|
### Environment variables / Aliases
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
unset **<TAB>
|
unset **<TAB>
|
||||||
@@ -427,7 +446,7 @@ export **<TAB>
|
|||||||
unalias **<TAB>
|
unalias **<TAB>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Settings
|
### Settings
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Use ~~ as the trigger sequence instead of the default **
|
# Use ~~ as the trigger sequence instead of the default **
|
||||||
@@ -465,7 +484,7 @@ _fzf_comprun() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Supported commands
|
### Supported commands
|
||||||
|
|
||||||
On bash, fuzzy completion is enabled only for a predefined set of commands
|
On bash, fuzzy completion is enabled only for a predefined set of commands
|
||||||
(`complete | grep _fzf` to see the list). But you can enable it for other
|
(`complete | grep _fzf` to see the list). But you can enable it for other
|
||||||
@@ -477,7 +496,7 @@ _fzf_setup_completion path ag git kubectl
|
|||||||
_fzf_setup_completion dir tree
|
_fzf_setup_completion dir tree
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Custom fuzzy completion
|
### Custom fuzzy completion
|
||||||
|
|
||||||
_**(Custom completion API is experimental and subject to change)**_
|
_**(Custom completion API is experimental and subject to change)**_
|
||||||
|
|
||||||
@@ -560,6 +579,47 @@ fzf --bind 'f1:execute(less -f {}),ctrl-y:execute-silent(echo {} | pbcopy)+abort
|
|||||||
|
|
||||||
See *KEY BINDINGS* section of the man page for details.
|
See *KEY BINDINGS* section of the man page for details.
|
||||||
|
|
||||||
|
### Turning into a different process
|
||||||
|
|
||||||
|
`become(...)` is similar to `execute(...)`/`execute-silent(...)` described
|
||||||
|
above, but instead of executing the command and coming back to fzf on
|
||||||
|
complete, it turns fzf into a new process for the command.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
fzf --bind 'enter:become(vim {})'
|
||||||
|
```
|
||||||
|
|
||||||
|
Compared to the seemingly equivalent command substitution `vim "$(fzf)"`, this
|
||||||
|
approach has several advantages:
|
||||||
|
|
||||||
|
* Vim will not open an empty file when you terminate fzf with
|
||||||
|
<kbd>CTRL-C</kbd>
|
||||||
|
* Vim will not open an empty file when you press <kbd>ENTER</kbd> on an empty
|
||||||
|
result
|
||||||
|
* Can handle multiple selections even when they have whitespaces
|
||||||
|
```sh
|
||||||
|
fzf --multi --bind 'enter:become(vim {+})'
|
||||||
|
```
|
||||||
|
|
||||||
|
To be fair, running `fzf --print0 | xargs -0 -o vim` instead of `vim "$(fzf)"`
|
||||||
|
resolves all of the issues mentioned. Nonetheless, `become(...)` still offers
|
||||||
|
additional benefits in different scenarios.
|
||||||
|
|
||||||
|
* You can set up multiple bindings to handle the result in different ways
|
||||||
|
without any wrapping script
|
||||||
|
```sh
|
||||||
|
fzf --bind 'enter:become(vim {}),ctrl-e:become(emacs {})'
|
||||||
|
```
|
||||||
|
* Previously, you would have to use `--expect=ctrl-e` and check the first
|
||||||
|
line of the output of fzf
|
||||||
|
* You can easily build the subsequent command using the field index
|
||||||
|
expressions of fzf
|
||||||
|
```sh
|
||||||
|
# Open the file in Vim and go to the line
|
||||||
|
git grep --line-number . |
|
||||||
|
fzf --delimiter : --nth 3.. --bind 'enter:become(vim {1} +{2})'
|
||||||
|
```
|
||||||
|
|
||||||
### Reloading the candidate list
|
### Reloading the candidate list
|
||||||
|
|
||||||
By binding `reload` action to a key or an event, you can make fzf dynamically
|
By binding `reload` action to a key or an event, you can make fzf dynamically
|
||||||
|
|||||||
@@ -179,9 +179,10 @@ trap 'cleanup' EXIT
|
|||||||
|
|
||||||
envs="export TERM=$TERM "
|
envs="export TERM=$TERM "
|
||||||
if [[ "$opt" =~ "-E" ]]; then
|
if [[ "$opt" =~ "-E" ]]; then
|
||||||
FZF_DEFAULT_OPTS="--margin 0,1 $FZF_DEFAULT_OPTS"
|
|
||||||
tmux_version=$(tmux -V)
|
tmux_version=$(tmux -V)
|
||||||
if [[ ! $tmux_version =~ 3\.2 ]]; then
|
if [[ $tmux_version =~ ^tmux\ 3\.2[a-z]?$ ]]; then
|
||||||
|
FZF_DEFAULT_OPTS="--margin 0,1 $FZF_DEFAULT_OPTS"
|
||||||
|
else
|
||||||
FZF_DEFAULT_OPTS="--border $FZF_DEFAULT_OPTS"
|
FZF_DEFAULT_OPTS="--border $FZF_DEFAULT_OPTS"
|
||||||
opt="-B $opt"
|
opt="-B $opt"
|
||||||
fi
|
fi
|
||||||
|
|||||||
2
install
2
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.36.0
|
version=0.38.0
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
$version="0.36.0"
|
$version="0.38.0"
|
||||||
|
|
||||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -5,7 +5,7 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/protector"
|
"github.com/junegunn/fzf/src/protector"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "0.36"
|
var version string = "0.38"
|
||||||
var revision string = "devel"
|
var revision string = "devel"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -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 "Jan 2023" "fzf 0.36.0" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf-tmux 1 "Feb 2023" "fzf 0.38.0" "fzf-tmux - open fzf in tmux split pane"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf-tmux - open fzf in tmux split pane
|
fzf-tmux - open fzf in tmux split pane
|
||||||
|
|||||||
@@ -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 "Jan 2023" "fzf 0.36.0" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Feb 2023" "fzf 0.38.0" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -339,7 +339,9 @@ Determines the display style of finder info (match counters).
|
|||||||
.br
|
.br
|
||||||
.BR default " Display on the next line to the prompt"
|
.BR default " Display on the next line to the prompt"
|
||||||
.br
|
.br
|
||||||
.BR inline " Display on the same line"
|
.BR inline " Display on the same line with the default separator ' < '"
|
||||||
|
.br
|
||||||
|
.BR inline:SEPARATOR " Display on the same line with a non-default separator"
|
||||||
.br
|
.br
|
||||||
.BR hidden " Do not display finder info"
|
.BR hidden " Do not display finder info"
|
||||||
.br
|
.br
|
||||||
@@ -959,6 +961,22 @@ e.g.
|
|||||||
\fB# Move cursor to the first entry whenever the query is changed
|
\fB# Move cursor to the first entry whenever the query is changed
|
||||||
fzf --bind change:first\fR
|
fzf --bind change:first\fR
|
||||||
.RE
|
.RE
|
||||||
|
\fIfocus\fR
|
||||||
|
.RS
|
||||||
|
Triggered when the focus changes due to a vertical cursor movement or a search
|
||||||
|
result update.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fBfzf --bind 'focus:transform-preview-label:echo [ {} ]' --preview 'cat {}'
|
||||||
|
|
||||||
|
# Any action bound to the event runs synchronously and thus can make the interface sluggish
|
||||||
|
# e.g. lolcat isn't one of the fastest programs, and every cursor movement in
|
||||||
|
# fzf will be noticeably affected by its execution time
|
||||||
|
fzf --bind 'focus:transform-preview-label:echo [ {} ] | lolcat -f' --preview 'cat {}'
|
||||||
|
|
||||||
|
# Beware not to introduce an infinite loop
|
||||||
|
seq 10 | fzf --bind 'focus:up' --cycle\fR
|
||||||
|
.RE
|
||||||
|
|
||||||
\fIbackward-eof\fR
|
\fIbackward-eof\fR
|
||||||
.RS
|
.RS
|
||||||
@@ -981,9 +999,12 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBbackward-delete-char/eof\fR (same as \fBbackward-delete-char\fR except aborts fzf if query is empty)
|
\fBbackward-delete-char/eof\fR (same as \fBbackward-delete-char\fR except aborts fzf if query is empty)
|
||||||
\fBbackward-kill-word\fR \fIalt-bs\fR
|
\fBbackward-kill-word\fR \fIalt-bs\fR
|
||||||
\fBbackward-word\fR \fIalt-b shift-left\fR
|
\fBbackward-word\fR \fIalt-b shift-left\fR
|
||||||
|
\fBbecome(...)\fR (replace fzf process with the specified command; see below for the details)
|
||||||
\fBbeginning-of-line\fR \fIctrl-a home\fR
|
\fBbeginning-of-line\fR \fIctrl-a home\fR
|
||||||
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
||||||
|
\fBchange-border-label(...)\fR (change \fB--border-label\fR to the given string)
|
||||||
\fBchange-preview(...)\fR (change \fB--preview\fR option)
|
\fBchange-preview(...)\fR (change \fB--preview\fR option)
|
||||||
|
\fBchange-preview-label(...)\fR (change \fB--preview-label\fR to the given string)
|
||||||
\fBchange-preview-window(...)\fR (change \fB--preview-window\fR option; rotate through the multiple option sets separated by '|')
|
\fBchange-preview-window(...)\fR (change \fB--preview-window\fR option; rotate through the multiple option sets separated by '|')
|
||||||
\fBchange-prompt(...)\fR (change prompt to the given string)
|
\fBchange-prompt(...)\fR (change prompt to the given string)
|
||||||
\fBchange-query(...)\fR (change query string to the given string)
|
\fBchange-query(...)\fR (change query string to the given string)
|
||||||
@@ -1016,6 +1037,7 @@ 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-preview\fR
|
||||||
\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)
|
||||||
@@ -1038,6 +1060,7 @@ 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-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
|
||||||
@@ -1048,6 +1071,8 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBtoggle-search\fR (toggle search functionality)
|
\fBtoggle-search\fR (toggle search functionality)
|
||||||
\fBtoggle-sort\fR
|
\fBtoggle-sort\fR
|
||||||
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
||||||
|
\fBtransform-border-label(...)\fR (transform border label using an external command)
|
||||||
|
\fBtransform-preview-label(...)\fR (transform preview label using an external command)
|
||||||
\fBtransform-prompt(...)\fR (transform prompt string using an external command)
|
\fBtransform-prompt(...)\fR (transform prompt string using an external command)
|
||||||
\fBtransform-query(...)\fR (transform query string using an external command)
|
\fBtransform-query(...)\fR (transform query string using an external command)
|
||||||
\fBunbind(...)\fR (unbind bindings)
|
\fBunbind(...)\fR (unbind bindings)
|
||||||
@@ -1119,6 +1144,14 @@ On *nix systems, fzf runs the command with \fB$SHELL -c\fR if \fBSHELL\fR is
|
|||||||
set, otherwise with \fBsh -c\fR, so in this case make sure that the command is
|
set, otherwise with \fBsh -c\fR, so in this case make sure that the command is
|
||||||
POSIX-compliant.
|
POSIX-compliant.
|
||||||
|
|
||||||
|
\fBbecome(...)\fR action is similar to \fBexecute(...)\fR, but it replaces the
|
||||||
|
current fzf process with the specifed command using \fBexecve(2)\fR system
|
||||||
|
call.
|
||||||
|
|
||||||
|
\fBfzf --bind "enter:become(vim {})"\fR
|
||||||
|
|
||||||
|
\fBbecome(...)\fR is not supported on Windows.
|
||||||
|
|
||||||
.SS RELOAD INPUT
|
.SS RELOAD INPUT
|
||||||
|
|
||||||
\fBreload(...)\fR action is used to dynamically update the input list
|
\fBreload(...)\fR action is used to dynamically update the input list
|
||||||
|
|||||||
@@ -512,9 +512,7 @@ try
|
|||||||
let optstr .= ' --height='.height
|
let optstr .= ' --height='.height
|
||||||
endif
|
endif
|
||||||
" Respect --border option given in 'options'
|
" Respect --border option given in 'options'
|
||||||
if stridx(optstr, '--border') < 0 && stridx(optstr, '--no-border') < 0
|
let optstr = join([s:border_opt(get(dict, 'window', 0)), optstr])
|
||||||
let optstr .= s:border_opt(get(dict, 'window', 0))
|
|
||||||
endif
|
|
||||||
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
|
||||||
@@ -741,7 +739,7 @@ function! s:calc_size(max, val, dict)
|
|||||||
return size
|
return size
|
||||||
endif
|
endif
|
||||||
let margin = match(opts, '--inline-info\|--info[^-]\{-}inline') > match(opts, '--no-inline-info\|--info[^-]\{-}\(default\|hidden\)') ? 1 : 2
|
let margin = match(opts, '--inline-info\|--info[^-]\{-}inline') > match(opts, '--no-inline-info\|--info[^-]\{-}\(default\|hidden\)') ? 1 : 2
|
||||||
let margin += stridx(opts, '--border') > stridx(opts, '--no-border') ? 2 : 0
|
let margin += match(opts, '--border\([^-]\|$\)') > match(opts, '--no-border\([^-]\|$\)') ? 2 : 0
|
||||||
if stridx(opts, '--header') > stridx(opts, '--no-header')
|
if stridx(opts, '--header') > stridx(opts, '--no-header')
|
||||||
let margin += len(split(opts, "\n"))
|
let margin += len(split(opts, "\n"))
|
||||||
endif
|
endif
|
||||||
|
|||||||
@@ -310,7 +310,7 @@ complete -o default -F _fzf_opts_completion fzf-tmux
|
|||||||
d_cmds="${FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir}"
|
d_cmds="${FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir}"
|
||||||
a_cmds="
|
a_cmds="
|
||||||
awk cat diff diff3
|
awk cat diff diff3
|
||||||
emacs emacsclient ex file ftp g++ gcc gvim head hg java
|
emacs emacsclient ex file ftp g++ gcc gvim head hg hx java
|
||||||
javac ld less more mvim nvim patch perl python ruby
|
javac ld less more mvim nvim patch perl python ruby
|
||||||
sed sftp sort source tail tee uniq vi view vim wc xdg-open
|
sed sftp sort source tail tee uniq vi view vim wc xdg-open
|
||||||
basename bunzip2 bzip2 chmod chown curl cp dirname du
|
basename bunzip2 bzip2 chmod chown curl cp dirname du
|
||||||
@@ -373,7 +373,7 @@ _fzf_setup_completion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Environment variables / Aliases / Hosts / Process
|
# Environment variables / Aliases / Hosts / Process
|
||||||
_fzf_setup_completion 'var' export unset
|
_fzf_setup_completion 'var' export unset printenv
|
||||||
_fzf_setup_completion 'alias' unalias
|
_fzf_setup_completion 'alias' unalias
|
||||||
_fzf_setup_completion 'host' ssh telnet
|
_fzf_setup_completion 'host' ssh telnet
|
||||||
_fzf_setup_completion 'proc' kill
|
_fzf_setup_completion 'proc' kill
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/junegunn/fzf/src/algo"
|
"github.com/junegunn/fzf/src/algo"
|
||||||
"github.com/junegunn/fzf/src/tui"
|
"github.com/junegunn/fzf/src/tui"
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
|
||||||
"github.com/mattn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
"github.com/mattn/go-shellwords"
|
"github.com/mattn/go-shellwords"
|
||||||
@@ -70,7 +71,7 @@ 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|inline|hidden]
|
--info=STYLE Finder info style [default|hidden|inline|inline:SEPARATOR]
|
||||||
--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[=CHAR] Scrollbar character
|
--scrollbar[=CHAR] Scrollbar character
|
||||||
@@ -125,6 +126,8 @@ const usage = `usage: fzf [options]
|
|||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const defaultInfoSep = " < "
|
||||||
|
|
||||||
// Case denotes case-sensitivity of search
|
// Case denotes case-sensitivity of search
|
||||||
type Case int
|
type Case int
|
||||||
|
|
||||||
@@ -246,6 +249,10 @@ func (a previewOpts) sameContentLayout(b previewOpts) bool {
|
|||||||
return a.wrap == b.wrap && a.headerLines == b.headerLines
|
return a.wrap == b.wrap && a.headerLines == b.headerLines
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func firstLine(s string) string {
|
||||||
|
return strings.SplitN(s, "\n", 2)[0]
|
||||||
|
}
|
||||||
|
|
||||||
// Options stores the values of command-line options
|
// Options stores the values of command-line options
|
||||||
type Options struct {
|
type Options struct {
|
||||||
Fuzzy bool
|
Fuzzy bool
|
||||||
@@ -277,6 +284,7 @@ type Options struct {
|
|||||||
ScrollOff int
|
ScrollOff int
|
||||||
FileWord bool
|
FileWord bool
|
||||||
InfoStyle infoStyle
|
InfoStyle infoStyle
|
||||||
|
InfoSep string
|
||||||
Separator *string
|
Separator *string
|
||||||
JumpLabels string
|
JumpLabels string
|
||||||
Prompt string
|
Prompt string
|
||||||
@@ -609,6 +617,8 @@ func parseKeyChordsImpl(str string, message string, exit func(string)) map[tui.E
|
|||||||
add(tui.Start)
|
add(tui.Start)
|
||||||
case "load":
|
case "load":
|
||||||
add(tui.Load)
|
add(tui.Load)
|
||||||
|
case "focus":
|
||||||
|
add(tui.Focus)
|
||||||
case "alt-enter", "alt-return":
|
case "alt-enter", "alt-return":
|
||||||
chords[tui.CtrlAltKey('m')] = key
|
chords[tui.CtrlAltKey('m')] = key
|
||||||
case "alt-space":
|
case "alt-space":
|
||||||
@@ -912,7 +922,7 @@ const (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
executeRegexp = regexp.MustCompile(
|
executeRegexp = regexp.MustCompile(
|
||||||
`(?si)[:+](execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:query|prompt)|change-preview-window|change-preview|(?:re|un)bind|pos|put)`)
|
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:query|prompt|border-label|preview-label)|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-]+")
|
||||||
}
|
}
|
||||||
@@ -954,7 +964,7 @@ Loop:
|
|||||||
ce = regexp.QuoteMeta(ce)
|
ce = regexp.QuoteMeta(ce)
|
||||||
|
|
||||||
// @$ or @+
|
// @$ or @+
|
||||||
loc = regexp.MustCompile(fmt.Sprintf(`^%s.*?(%s[+,]|%s$)`, cs, ce, ce)).FindStringIndex(action)
|
loc = regexp.MustCompile(fmt.Sprintf(`(?s)^%s.*?(%s[+,]|%s$)`, cs, ce, ce)).FindStringIndex(action)
|
||||||
if loc == nil {
|
if loc == nil {
|
||||||
masked += action
|
masked += action
|
||||||
break
|
break
|
||||||
@@ -1102,6 +1112,10 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
|||||||
appendAction(actPrevSelected)
|
appendAction(actPrevSelected)
|
||||||
case "next-selected":
|
case "next-selected":
|
||||||
appendAction(actNextSelected)
|
appendAction(actNextSelected)
|
||||||
|
case "show-preview":
|
||||||
|
appendAction(actShowPreview)
|
||||||
|
case "hide-preview":
|
||||||
|
appendAction(actHidePreview)
|
||||||
case "toggle-preview":
|
case "toggle-preview":
|
||||||
appendAction(actTogglePreview)
|
appendAction(actTogglePreview)
|
||||||
case "toggle-preview-wrap":
|
case "toggle-preview-wrap":
|
||||||
@@ -1158,6 +1172,10 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
|||||||
actions = append(actions, &action{t: t, a: actionArg})
|
actions = append(actions, &action{t: t, a: actionArg})
|
||||||
}
|
}
|
||||||
switch t {
|
switch t {
|
||||||
|
case actBecome:
|
||||||
|
if util.IsWindows() {
|
||||||
|
exit("become action is not supported on Windows")
|
||||||
|
}
|
||||||
case actUnbind, actRebind:
|
case actUnbind, actRebind:
|
||||||
parseKeyChordsImpl(actionArg, spec[0:offset]+" target required", exit)
|
parseKeyChordsImpl(actionArg, spec[0:offset]+" target required", exit)
|
||||||
case actChangePreviewWindow:
|
case actChangePreviewWindow:
|
||||||
@@ -1210,6 +1228,8 @@ func isExecuteAction(str string) actionType {
|
|||||||
|
|
||||||
prefix := actionNameRegexp.FindString(str)
|
prefix := actionNameRegexp.FindString(str)
|
||||||
switch prefix {
|
switch prefix {
|
||||||
|
case "become":
|
||||||
|
return actBecome
|
||||||
case "reload":
|
case "reload":
|
||||||
return actReload
|
return actReload
|
||||||
case "reload-sync":
|
case "reload-sync":
|
||||||
@@ -1220,6 +1240,10 @@ func isExecuteAction(str string) actionType {
|
|||||||
return actRebind
|
return actRebind
|
||||||
case "preview":
|
case "preview":
|
||||||
return actPreview
|
return actPreview
|
||||||
|
case "change-border-label":
|
||||||
|
return actChangeBorderLabel
|
||||||
|
case "change-preview-label":
|
||||||
|
return actChangePreviewLabel
|
||||||
case "change-preview-window":
|
case "change-preview-window":
|
||||||
return actChangePreviewWindow
|
return actChangePreviewWindow
|
||||||
case "change-preview":
|
case "change-preview":
|
||||||
@@ -1238,6 +1262,10 @@ func isExecuteAction(str string) actionType {
|
|||||||
return actExecuteMulti
|
return actExecuteMulti
|
||||||
case "put":
|
case "put":
|
||||||
return actPut
|
return actPut
|
||||||
|
case "transform-border-label":
|
||||||
|
return actTransformBorderLabel
|
||||||
|
case "transform-preview-label":
|
||||||
|
return actTransformPreviewLabel
|
||||||
case "transform-prompt":
|
case "transform-prompt":
|
||||||
return actTransformPrompt
|
return actTransformPrompt
|
||||||
case "transform-query":
|
case "transform-query":
|
||||||
@@ -1309,18 +1337,22 @@ func parseLayout(str string) layoutType {
|
|||||||
return layoutDefault
|
return layoutDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseInfoStyle(str string) infoStyle {
|
func parseInfoStyle(str string) (infoStyle, string) {
|
||||||
switch str {
|
switch str {
|
||||||
case "default":
|
case "default":
|
||||||
return infoDefault
|
return infoDefault, ""
|
||||||
case "inline":
|
case "inline":
|
||||||
return infoInline
|
return infoInline, defaultInfoSep
|
||||||
case "hidden":
|
case "hidden":
|
||||||
return infoHidden
|
return infoHidden, ""
|
||||||
default:
|
default:
|
||||||
errorExit("invalid info style (expected: default|inline|hidden)")
|
prefix := "inline:"
|
||||||
|
if strings.HasPrefix(str, prefix) {
|
||||||
|
return infoInline, strings.ReplaceAll(str[len(prefix):], "\n", " ")
|
||||||
}
|
}
|
||||||
return infoDefault
|
errorExit("invalid info style (expected: default|hidden|inline|inline:SEPARATOR)")
|
||||||
|
}
|
||||||
|
return infoDefault, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsePreviewWindow(opts *previewOpts, input string) {
|
func parsePreviewWindow(opts *previewOpts, input string) {
|
||||||
@@ -1588,12 +1620,13 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
case "--no-filepath-word":
|
case "--no-filepath-word":
|
||||||
opts.FileWord = false
|
opts.FileWord = false
|
||||||
case "--info":
|
case "--info":
|
||||||
opts.InfoStyle = parseInfoStyle(
|
opts.InfoStyle, opts.InfoSep = parseInfoStyle(
|
||||||
nextString(allArgs, &i, "info style required"))
|
nextString(allArgs, &i, "info style required"))
|
||||||
case "--no-info":
|
case "--no-info":
|
||||||
opts.InfoStyle = infoHidden
|
opts.InfoStyle = infoHidden
|
||||||
case "--inline-info":
|
case "--inline-info":
|
||||||
opts.InfoStyle = infoInline
|
opts.InfoStyle = infoInline
|
||||||
|
opts.InfoSep = defaultInfoSep
|
||||||
case "--no-inline-info":
|
case "--no-inline-info":
|
||||||
opts.InfoStyle = infoDefault
|
opts.InfoStyle = infoDefault
|
||||||
case "--separator":
|
case "--separator":
|
||||||
@@ -1640,10 +1673,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
case "--prompt":
|
case "--prompt":
|
||||||
opts.Prompt = nextString(allArgs, &i, "prompt string required")
|
opts.Prompt = nextString(allArgs, &i, "prompt string required")
|
||||||
case "--pointer":
|
case "--pointer":
|
||||||
opts.Pointer = nextString(allArgs, &i, "pointer sign string required")
|
opts.Pointer = firstLine(nextString(allArgs, &i, "pointer sign string required"))
|
||||||
validatePointer = true
|
validatePointer = true
|
||||||
case "--marker":
|
case "--marker":
|
||||||
opts.Marker = nextString(allArgs, &i, "selected sign string required")
|
opts.Marker = firstLine(nextString(allArgs, &i, "selected sign string required"))
|
||||||
validateMarker = true
|
validateMarker = true
|
||||||
case "--sync":
|
case "--sync":
|
||||||
opts.Sync = true
|
opts.Sync = true
|
||||||
@@ -1758,10 +1791,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
} else if match, value := optString(arg, "--prompt="); match {
|
} else if match, value := optString(arg, "--prompt="); match {
|
||||||
opts.Prompt = value
|
opts.Prompt = value
|
||||||
} else if match, value := optString(arg, "--pointer="); match {
|
} else if match, value := optString(arg, "--pointer="); match {
|
||||||
opts.Pointer = value
|
opts.Pointer = firstLine(value)
|
||||||
validatePointer = true
|
validatePointer = true
|
||||||
} else if match, value := optString(arg, "--marker="); match {
|
} else if match, value := optString(arg, "--marker="); match {
|
||||||
opts.Marker = value
|
opts.Marker = firstLine(value)
|
||||||
validateMarker = true
|
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)
|
||||||
@@ -1778,7 +1811,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
} else if match, value := optString(arg, "--layout="); match {
|
} else if match, value := optString(arg, "--layout="); match {
|
||||||
opts.Layout = parseLayout(value)
|
opts.Layout = parseLayout(value)
|
||||||
} else if match, value := optString(arg, "--info="); match {
|
} else if match, value := optString(arg, "--info="); match {
|
||||||
opts.InfoStyle = parseInfoStyle(value)
|
opts.InfoStyle, opts.InfoSep = parseInfoStyle(value)
|
||||||
} else if match, value := optString(arg, "--separator="); match {
|
} else if match, value := optString(arg, "--separator="); match {
|
||||||
opts.Separator = &value
|
opts.Separator = &value
|
||||||
} else if match, value := optString(arg, "--scrollbar="); match {
|
} else if match, value := optString(arg, "--scrollbar="); match {
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ func TestBind(t *testing.T) {
|
|||||||
}
|
}
|
||||||
parseKeymap(keymap,
|
parseKeymap(keymap,
|
||||||
"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+
|
"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+
|
||||||
"f1:execute(ls {+})+abort+execute(echo {+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
|
"f1:execute(ls {+})+abort+execute(echo \n{+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
|
||||||
"alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+
|
"alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+
|
||||||
"x:Execute(foo+bar),X:execute/bar+baz/"+
|
"x:Execute(foo+bar),X:execute/bar+baz/"+
|
||||||
",f1:+first,f1:+top"+
|
",f1:+first,f1:+top"+
|
||||||
|
|||||||
173
src/terminal.go
173
src/terminal.go
@@ -6,6 +6,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -104,7 +105,6 @@ type previewer struct {
|
|||||||
version int64
|
version int64
|
||||||
lines []string
|
lines []string
|
||||||
offset int
|
offset int
|
||||||
enabled bool
|
|
||||||
scrollable bool
|
scrollable bool
|
||||||
final bool
|
final bool
|
||||||
following resumableState
|
following resumableState
|
||||||
@@ -147,6 +147,7 @@ type labelPrinter func(tui.Window, int)
|
|||||||
type Terminal struct {
|
type Terminal struct {
|
||||||
initDelay time.Duration
|
initDelay time.Duration
|
||||||
infoStyle infoStyle
|
infoStyle infoStyle
|
||||||
|
infoSep string
|
||||||
separator labelPrinter
|
separator labelPrinter
|
||||||
separatorLen int
|
separatorLen int
|
||||||
spinner []string
|
spinner []string
|
||||||
@@ -276,6 +277,8 @@ const (
|
|||||||
reqRefresh
|
reqRefresh
|
||||||
reqReinit
|
reqReinit
|
||||||
reqFullRedraw
|
reqFullRedraw
|
||||||
|
reqRedrawBorderLabel
|
||||||
|
reqRedrawPreviewLabel
|
||||||
reqClose
|
reqClose
|
||||||
reqPrintQuery
|
reqPrintQuery
|
||||||
reqPreviewEnqueue
|
reqPreviewEnqueue
|
||||||
@@ -306,6 +309,8 @@ const (
|
|||||||
actBackwardDeleteCharEOF
|
actBackwardDeleteCharEOF
|
||||||
actBackwardWord
|
actBackwardWord
|
||||||
actCancel
|
actCancel
|
||||||
|
actChangeBorderLabel
|
||||||
|
actChangePreviewLabel
|
||||||
actChangePrompt
|
actChangePrompt
|
||||||
actChangeQuery
|
actChangeQuery
|
||||||
actClearScreen
|
actClearScreen
|
||||||
@@ -345,8 +350,12 @@ const (
|
|||||||
actRefreshPreview
|
actRefreshPreview
|
||||||
actReplaceQuery
|
actReplaceQuery
|
||||||
actToggleSort
|
actToggleSort
|
||||||
|
actShowPreview
|
||||||
|
actHidePreview
|
||||||
actTogglePreview
|
actTogglePreview
|
||||||
actTogglePreviewWrap
|
actTogglePreviewWrap
|
||||||
|
actTransformBorderLabel
|
||||||
|
actTransformPreviewLabel
|
||||||
actTransformPrompt
|
actTransformPrompt
|
||||||
actTransformQuery
|
actTransformQuery
|
||||||
actPreview
|
actPreview
|
||||||
@@ -379,6 +388,7 @@ const (
|
|||||||
actDeselect
|
actDeselect
|
||||||
actUnbind
|
actUnbind
|
||||||
actRebind
|
actRebind
|
||||||
|
actBecome
|
||||||
)
|
)
|
||||||
|
|
||||||
type placeholderFlags struct {
|
type placeholderFlags struct {
|
||||||
@@ -573,6 +583,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
t := Terminal{
|
t := Terminal{
|
||||||
initDelay: delay,
|
initDelay: delay,
|
||||||
infoStyle: opts.InfoStyle,
|
infoStyle: opts.InfoStyle,
|
||||||
|
infoSep: opts.InfoSep,
|
||||||
separator: nil,
|
separator: nil,
|
||||||
spinner: makeSpinner(opts.Unicode),
|
spinner: makeSpinner(opts.Unicode),
|
||||||
queryLen: [2]int{0, 0},
|
queryLen: [2]int{0, 0},
|
||||||
@@ -635,7 +646,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
reqBox: util.NewEventBox(),
|
reqBox: util.NewEventBox(),
|
||||||
initialPreviewOpts: opts.Preview,
|
initialPreviewOpts: opts.Preview,
|
||||||
previewOpts: opts.Preview,
|
previewOpts: opts.Preview,
|
||||||
previewer: previewer{0, []string{}, 0, len(opts.Preview.command) > 0, false, true, disabledState, "", []bool{}},
|
previewer: previewer{0, []string{}, 0, false, true, disabledState, "", []bool{}},
|
||||||
previewed: previewed{0, 0, 0, false},
|
previewed: previewed{0, 0, 0, false},
|
||||||
previewBox: previewBox,
|
previewBox: previewBox,
|
||||||
eventBox: eventBox,
|
eventBox: eventBox,
|
||||||
@@ -723,6 +734,7 @@ func (t *Terminal) ansiLabelPrinter(str string, color *tui.ColorPair, fill bool)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Extract ANSI color codes
|
// Extract ANSI color codes
|
||||||
|
str = firstLine(str)
|
||||||
text, colors, _ := extractColor(str, nil, nil)
|
text, colors, _ := extractColor(str, nil, nil)
|
||||||
runes := []rune(text)
|
runes := []rune(text)
|
||||||
|
|
||||||
@@ -777,6 +789,7 @@ func (t *Terminal) ansiLabelPrinter(str string, color *tui.ColorPair, fill bool)
|
|||||||
|
|
||||||
func (t *Terminal) parsePrompt(prompt string) (func(), int) {
|
func (t *Terminal) parsePrompt(prompt string) (func(), int) {
|
||||||
var state *ansiState
|
var state *ansiState
|
||||||
|
prompt = firstLine(prompt)
|
||||||
trimmed, colors, _ := extractColor(prompt, state, nil)
|
trimmed, colors, _ := extractColor(prompt, state, nil)
|
||||||
item := &Item{text: util.ToChars([]byte(trimmed)), colors: colors}
|
item := &Item{text: util.ToChars([]byte(trimmed)), colors: colors}
|
||||||
|
|
||||||
@@ -1022,7 +1035,7 @@ func (t *Terminal) adjustMarginAndPadding() (int, int, [4]int, [4]int) {
|
|||||||
if t.noInfoLine() {
|
if t.noInfoLine() {
|
||||||
minAreaHeight -= 1
|
minAreaHeight -= 1
|
||||||
}
|
}
|
||||||
if t.mayNeedPreviewWindow() {
|
if t.needPreviewWindow() {
|
||||||
minPreviewHeight := 1 + borderLines(t.previewOpts.border)
|
minPreviewHeight := 1 + borderLines(t.previewOpts.border)
|
||||||
minPreviewWidth := 5
|
minPreviewWidth := 5
|
||||||
switch t.previewOpts.position {
|
switch t.previewOpts.position {
|
||||||
@@ -1105,7 +1118,7 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
|
|||||||
|
|
||||||
// Set up preview window
|
// Set up preview window
|
||||||
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
|
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
|
||||||
if t.mayNeedPreviewWindow() {
|
if forcePreview || t.needPreviewWindow() {
|
||||||
var resizePreviewWindows func(previewOpts *previewOpts)
|
var resizePreviewWindows func(previewOpts *previewOpts)
|
||||||
resizePreviewWindows = func(previewOpts *previewOpts) {
|
resizePreviewWindows = func(previewOpts *previewOpts) {
|
||||||
t.activePreviewOpts = previewOpts
|
t.activePreviewOpts = previewOpts
|
||||||
@@ -1234,6 +1247,8 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
resizePreviewWindows(&t.previewOpts)
|
resizePreviewWindows(&t.previewOpts)
|
||||||
|
} else {
|
||||||
|
t.activePreviewOpts = &t.previewOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
// Without preview window
|
// Without preview window
|
||||||
@@ -1250,13 +1265,27 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Print border label
|
// Print border label
|
||||||
printLabel := func(window tui.Window, render labelPrinter, opts labelOpts, length int, borderShape tui.BorderShape) {
|
t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, false)
|
||||||
if window == nil || render == nil {
|
t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.previewOpts.border, false)
|
||||||
|
|
||||||
|
for i := 0; i < t.window.Height(); i++ {
|
||||||
|
t.window.MoveAndClear(i, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) printLabel(window tui.Window, render labelPrinter, opts labelOpts, length int, borderShape tui.BorderShape, redrawBorder bool) {
|
||||||
|
if window == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch borderShape {
|
switch borderShape {
|
||||||
case tui.BorderHorizontal, tui.BorderTop, tui.BorderBottom, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderDouble:
|
case tui.BorderHorizontal, tui.BorderTop, tui.BorderBottom, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderDouble:
|
||||||
|
if redrawBorder {
|
||||||
|
window.DrawHBorder()
|
||||||
|
}
|
||||||
|
if render == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
var col int
|
var col int
|
||||||
if opts.column == 0 {
|
if opts.column == 0 {
|
||||||
col = util.Max(0, (window.Width()-length)/2)
|
col = util.Max(0, (window.Width()-length)/2)
|
||||||
@@ -1272,13 +1301,6 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
|
|||||||
window.Move(row, col)
|
window.Move(row, col)
|
||||||
render(window, window.Width())
|
render(window, window.Width())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape)
|
|
||||||
printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.previewOpts.border)
|
|
||||||
|
|
||||||
for i := 0; i < t.window.Height(); i++ {
|
|
||||||
t.window.MoveAndClear(i, 0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) move(y int, x int, clear bool) {
|
func (t *Terminal) move(y int, x int, clear bool) {
|
||||||
@@ -1380,16 +1402,21 @@ func (t *Terminal) printInfo() {
|
|||||||
pos = 2
|
pos = 2
|
||||||
case infoInline:
|
case infoInline:
|
||||||
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
||||||
if pos+len(" < ") > t.window.Width() {
|
str := t.infoSep
|
||||||
return
|
maxWidth := t.window.Width() - pos
|
||||||
|
width := runewidth.StringWidth(str)
|
||||||
|
if width > maxWidth {
|
||||||
|
trimmed, _ := t.trimRight([]rune(str), maxWidth)
|
||||||
|
str = string(trimmed)
|
||||||
|
width = maxWidth
|
||||||
}
|
}
|
||||||
t.move(line, pos, t.separatorLen == 0)
|
t.move(line, pos, t.separatorLen == 0)
|
||||||
if t.reading {
|
if t.reading {
|
||||||
t.window.CPrint(tui.ColSpinner, " < ")
|
t.window.CPrint(tui.ColSpinner, str)
|
||||||
} else {
|
} else {
|
||||||
t.window.CPrint(tui.ColPrompt, " < ")
|
t.window.CPrint(tui.ColPrompt, str)
|
||||||
}
|
}
|
||||||
pos += len(" < ")
|
pos += width
|
||||||
case infoHidden:
|
case infoHidden:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -2212,7 +2239,7 @@ func (t *Terminal) redraw() {
|
|||||||
|
|
||||||
func (t *Terminal) executeCommand(template string, forcePlus bool, background bool, captureFirstLine bool) string {
|
func (t *Terminal) executeCommand(template string, forcePlus bool, background bool, captureFirstLine bool) string {
|
||||||
line := ""
|
line := ""
|
||||||
valid, list := t.buildPlusList(template, forcePlus, false)
|
valid, list := t.buildPlusList(template, forcePlus)
|
||||||
// captureFirstLine is used for transform-{prompt,query} and we don't want to
|
// captureFirstLine is used for transform-{prompt,query} and we don't want to
|
||||||
// return an empty string in those cases
|
// return an empty string in those cases
|
||||||
if !valid && !captureFirstLine {
|
if !valid && !captureFirstLine {
|
||||||
@@ -2231,7 +2258,6 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
|||||||
t.redraw()
|
t.redraw()
|
||||||
t.refresh()
|
t.refresh()
|
||||||
} else {
|
} else {
|
||||||
t.tui.Pause(false)
|
|
||||||
if captureFirstLine {
|
if captureFirstLine {
|
||||||
out, _ := cmd.StdoutPipe()
|
out, _ := cmd.StdoutPipe()
|
||||||
reader := bufio.NewReader(out)
|
reader := bufio.NewReader(out)
|
||||||
@@ -2242,7 +2268,6 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
|||||||
} else {
|
} else {
|
||||||
cmd.Run()
|
cmd.Run()
|
||||||
}
|
}
|
||||||
t.tui.Resume(false, false)
|
|
||||||
}
|
}
|
||||||
t.executing.Set(false)
|
t.executing.Set(false)
|
||||||
cleanTemporaryFiles()
|
cleanTemporaryFiles()
|
||||||
@@ -2253,13 +2278,13 @@ func (t *Terminal) hasPreviewer() bool {
|
|||||||
return t.previewBox != nil
|
return t.previewBox != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) mayNeedPreviewWindow() bool {
|
func (t *Terminal) needPreviewWindow() bool {
|
||||||
return t.hasPreviewer() && t.previewer.enabled && t.previewOpts.Visible()
|
return t.hasPreviewer() && len(t.previewOpts.command) > 0 && t.previewOpts.Visible()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if previewer is currently in action (invisible previewer with size 0 or visible previewer)
|
// Check if previewer is currently in action (invisible previewer with size 0 or visible previewer)
|
||||||
func (t *Terminal) isPreviewEnabled() bool {
|
func (t *Terminal) canPreview() bool {
|
||||||
return t.hasPreviewer() && t.previewer.enabled && (!t.previewOpts.Visible() || t.pwindow != nil)
|
return t.hasPreviewer() && (!t.previewOpts.Visible() && !t.previewOpts.hidden || t.hasPreviewWindow())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) hasPreviewWindow() bool {
|
func (t *Terminal) hasPreviewWindow() bool {
|
||||||
@@ -2274,10 +2299,10 @@ func (t *Terminal) currentItem() *Item {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) buildPlusList(template string, forcePlus bool, forceEvaluation bool) (bool, []*Item) {
|
func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item) {
|
||||||
current := t.currentItem()
|
current := t.currentItem()
|
||||||
slot, plus, query := hasPreviewFlags(template)
|
slot, plus, query := hasPreviewFlags(template)
|
||||||
if !forceEvaluation && !(!slot || query || (forcePlus || plus) && len(t.selected) > 0) {
|
if !(!slot || query || (forcePlus || plus) && len(t.selected) > 0) {
|
||||||
return current != nil, []*Item{current, current}
|
return current != nil, []*Item{current, current}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2367,7 +2392,7 @@ func (t *Terminal) Loop() {
|
|||||||
pad := fitpad.pad
|
pad := fitpad.pad
|
||||||
t.tui.Resize(func(termHeight int) int {
|
t.tui.Resize(func(termHeight int) int {
|
||||||
contentHeight := fit + t.extraLines()
|
contentHeight := fit + t.extraLines()
|
||||||
if t.mayNeedPreviewWindow() {
|
if t.needPreviewWindow() {
|
||||||
if t.previewOpts.aboveOrBelow() {
|
if t.previewOpts.aboveOrBelow() {
|
||||||
if t.previewOpts.size.percent {
|
if t.previewOpts.size.percent {
|
||||||
newContentHeight := int(float64(contentHeight) * 100. / (100. - t.previewOpts.size.size))
|
newContentHeight := int(float64(contentHeight) * 100. / (100. - t.previewOpts.size.size))
|
||||||
@@ -2601,13 +2626,18 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
refreshPreview := func(command string) {
|
refreshPreview := func(command string) {
|
||||||
if len(command) > 0 && t.isPreviewEnabled() {
|
if len(command) > 0 && t.canPreview() {
|
||||||
_, list := t.buildPlusList(command, false, false)
|
_, list := t.buildPlusList(command, false)
|
||||||
t.cancelPreview()
|
t.cancelPreview()
|
||||||
t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.pwindow, t.evaluateScrollOffset(), list})
|
t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.pwindow, t.evaluateScrollOffset(), list})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var onFocus []*action
|
||||||
|
if actions, prs := t.keymap[tui.Focus.AsEvent()]; prs {
|
||||||
|
onFocus = actions
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
var focusedIndex int32 = minItem.Index()
|
var focusedIndex int32 = minItem.Index()
|
||||||
var version int64 = -1
|
var version int64 = -1
|
||||||
@@ -2643,7 +2673,11 @@ func (t *Terminal) Loop() {
|
|||||||
if currentItem != nil {
|
if currentItem != nil {
|
||||||
currentIndex = currentItem.Index()
|
currentIndex = currentItem.Index()
|
||||||
}
|
}
|
||||||
if focusedIndex != currentIndex || version != t.version {
|
focusChanged := focusedIndex != currentIndex
|
||||||
|
if onFocus != nil && focusChanged {
|
||||||
|
t.serverChan <- onFocus
|
||||||
|
}
|
||||||
|
if focusChanged || version != t.version {
|
||||||
version = t.version
|
version = t.version
|
||||||
focusedIndex = currentIndex
|
focusedIndex = currentIndex
|
||||||
refreshPreview(t.previewOpts.command)
|
refreshPreview(t.previewOpts.command)
|
||||||
@@ -2657,13 +2691,17 @@ func (t *Terminal) Loop() {
|
|||||||
t.printHeader()
|
t.printHeader()
|
||||||
case reqRefresh:
|
case reqRefresh:
|
||||||
t.suppress = false
|
t.suppress = false
|
||||||
|
case reqRedrawBorderLabel:
|
||||||
|
t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, true)
|
||||||
|
case reqRedrawPreviewLabel:
|
||||||
|
t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.previewOpts.border, true)
|
||||||
case reqReinit:
|
case reqReinit:
|
||||||
t.tui.Resume(t.fullscreen, t.sigstop)
|
t.tui.Resume(t.fullscreen, t.sigstop)
|
||||||
t.redraw()
|
t.redraw()
|
||||||
case reqFullRedraw:
|
case reqFullRedraw:
|
||||||
wasHidden := t.pwindow == nil
|
wasHidden := t.pwindow == nil
|
||||||
t.redraw()
|
t.redraw()
|
||||||
if wasHidden && t.pwindow != nil {
|
if wasHidden && t.hasPreviewWindow() {
|
||||||
refreshPreview(t.previewOpts.command)
|
refreshPreview(t.previewOpts.command)
|
||||||
}
|
}
|
||||||
case reqClose:
|
case reqClose:
|
||||||
@@ -2824,6 +2862,24 @@ func (t *Terminal) Loop() {
|
|||||||
doAction = func(a *action) bool {
|
doAction = func(a *action) bool {
|
||||||
switch a.t {
|
switch a.t {
|
||||||
case actIgnore:
|
case actIgnore:
|
||||||
|
case actBecome:
|
||||||
|
valid, list := t.buildPlusList(a.a, false)
|
||||||
|
if valid {
|
||||||
|
command := t.replacePlaceholder(a.a, false, string(t.input), list)
|
||||||
|
shell := os.Getenv("SHELL")
|
||||||
|
if len(shell) == 0 {
|
||||||
|
shell = "sh"
|
||||||
|
}
|
||||||
|
shellPath, err := exec.LookPath(shell)
|
||||||
|
if err == nil {
|
||||||
|
t.tui.Close()
|
||||||
|
if t.history != nil {
|
||||||
|
t.history.append(string(t.input))
|
||||||
|
}
|
||||||
|
util.SetStdin(tui.TtyIn())
|
||||||
|
syscall.Exec(shellPath, []string{shell, "-c", command}, os.Environ())
|
||||||
|
}
|
||||||
|
}
|
||||||
case actExecute, actExecuteSilent:
|
case actExecute, actExecuteSilent:
|
||||||
t.executeCommand(a.a, false, a.t == actExecuteSilent, false)
|
t.executeCommand(a.a, false, a.t == actExecuteSilent, false)
|
||||||
case actExecuteMulti:
|
case actExecuteMulti:
|
||||||
@@ -2831,16 +2887,21 @@ func (t *Terminal) Loop() {
|
|||||||
case actInvalid:
|
case actInvalid:
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
return false
|
return false
|
||||||
|
case actTogglePreview, actShowPreview, actHidePreview:
|
||||||
|
var act bool
|
||||||
|
switch a.t {
|
||||||
|
case actShowPreview:
|
||||||
|
act = !t.hasPreviewWindow() && len(t.previewOpts.command) > 0
|
||||||
|
case actHidePreview:
|
||||||
|
act = t.hasPreviewWindow()
|
||||||
case actTogglePreview:
|
case actTogglePreview:
|
||||||
if t.hasPreviewer() {
|
act = t.hasPreviewWindow() || len(t.previewOpts.command) > 0
|
||||||
if t.activePreviewOpts != nil {
|
|
||||||
t.activePreviewOpts.Toggle()
|
|
||||||
} else if !t.previewOpts.Visible() {
|
|
||||||
t.previewer.enabled = !t.previewer.enabled
|
|
||||||
}
|
}
|
||||||
|
if act {
|
||||||
|
t.activePreviewOpts.Toggle()
|
||||||
updatePreviewWindow(false)
|
updatePreviewWindow(false)
|
||||||
if t.isPreviewEnabled() {
|
if t.canPreview() {
|
||||||
valid, list := t.buildPlusList(t.previewOpts.command, false, false)
|
valid, list := t.buildPlusList(t.previewOpts.command, false)
|
||||||
if valid {
|
if valid {
|
||||||
t.cancelPreview()
|
t.cancelPreview()
|
||||||
t.previewBox.Set(reqPreviewEnqueue,
|
t.previewBox.Set(reqPreviewEnqueue,
|
||||||
@@ -2909,11 +2970,32 @@ func (t *Terminal) Loop() {
|
|||||||
case actChangeQuery:
|
case actChangeQuery:
|
||||||
t.input = []rune(a.a)
|
t.input = []rune(a.a)
|
||||||
t.cx = len(t.input)
|
t.cx = len(t.input)
|
||||||
|
case actChangeBorderLabel:
|
||||||
|
if t.border != nil {
|
||||||
|
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(a.a, &tui.ColBorderLabel, false)
|
||||||
|
req(reqRedrawBorderLabel)
|
||||||
|
}
|
||||||
|
case actChangePreviewLabel:
|
||||||
|
if t.pborder != nil {
|
||||||
|
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(a.a, &tui.ColPreviewLabel, false)
|
||||||
|
req(reqRedrawPreviewLabel)
|
||||||
|
}
|
||||||
|
case actTransformBorderLabel:
|
||||||
|
if t.border != nil {
|
||||||
|
label := t.executeCommand(a.a, false, true, true)
|
||||||
|
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(label, &tui.ColBorderLabel, false)
|
||||||
|
req(reqRedrawBorderLabel)
|
||||||
|
}
|
||||||
|
case actTransformPreviewLabel:
|
||||||
|
if t.pborder != nil {
|
||||||
|
label := t.executeCommand(a.a, false, true, true)
|
||||||
|
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(label, &tui.ColPreviewLabel, false)
|
||||||
|
req(reqRedrawPreviewLabel)
|
||||||
|
}
|
||||||
case actChangePrompt:
|
case actChangePrompt:
|
||||||
t.prompt, t.promptLen = t.parsePrompt(a.a)
|
t.prompt, t.promptLen = t.parsePrompt(a.a)
|
||||||
req(reqPrompt)
|
req(reqPrompt)
|
||||||
case actPreview:
|
case actPreview:
|
||||||
t.previewer.enabled = true
|
|
||||||
updatePreviewWindow(true)
|
updatePreviewWindow(true)
|
||||||
refreshPreview(a.a)
|
refreshPreview(a.a)
|
||||||
case actRefreshPreview:
|
case actRefreshPreview:
|
||||||
@@ -3298,7 +3380,7 @@ func (t *Terminal) Loop() {
|
|||||||
case actReload, actReloadSync:
|
case actReload, actReloadSync:
|
||||||
t.failed = nil
|
t.failed = nil
|
||||||
|
|
||||||
valid, list := t.buildPlusList(a.a, false, false)
|
valid, list := t.buildPlusList(a.a, false)
|
||||||
if !valid {
|
if !valid {
|
||||||
// We run the command even when there's no match
|
// We run the command even when there's no match
|
||||||
// 1. If the template doesn't have any slots
|
// 1. If the template doesn't have any slots
|
||||||
@@ -3326,9 +3408,8 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
case actChangePreview:
|
case actChangePreview:
|
||||||
if t.previewOpts.command != a.a {
|
if t.previewOpts.command != a.a {
|
||||||
t.previewer.enabled = len(a.a) > 0
|
|
||||||
updatePreviewWindow(false)
|
|
||||||
t.previewOpts.command = a.a
|
t.previewOpts.command = a.a
|
||||||
|
updatePreviewWindow(false)
|
||||||
refreshPreview(t.previewOpts.command)
|
refreshPreview(t.previewOpts.command)
|
||||||
}
|
}
|
||||||
case actChangePreviewWindow:
|
case actChangePreviewWindow:
|
||||||
@@ -3348,7 +3429,7 @@ func (t *Terminal) Loop() {
|
|||||||
if !currentPreviewOpts.sameLayout(t.previewOpts) {
|
if !currentPreviewOpts.sameLayout(t.previewOpts) {
|
||||||
wasHidden := t.pwindow == nil
|
wasHidden := t.pwindow == nil
|
||||||
updatePreviewWindow(false)
|
updatePreviewWindow(false)
|
||||||
if wasHidden && t.pwindow != nil {
|
if wasHidden && t.hasPreviewWindow() {
|
||||||
refreshPreview(t.previewOpts.command)
|
refreshPreview(t.previewOpts.command)
|
||||||
} else {
|
} else {
|
||||||
req(reqPreviewRefresh)
|
req(reqPreviewRefresh)
|
||||||
@@ -3425,14 +3506,12 @@ func (t *Terminal) Loop() {
|
|||||||
req(reqList)
|
req(reqList)
|
||||||
}
|
}
|
||||||
|
|
||||||
if queryChanged {
|
if queryChanged && t.canPreview() && len(t.previewOpts.command) > 0 {
|
||||||
if t.isPreviewEnabled() {
|
|
||||||
_, _, q := hasPreviewFlags(t.previewOpts.command)
|
_, _, q := hasPreviewFlags(t.previewOpts.command)
|
||||||
if q {
|
if q {
|
||||||
t.version++
|
t.version++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if queryChanged || t.cx != previousCx {
|
if queryChanged || t.cx != previousCx {
|
||||||
req(reqPrompt)
|
req(reqPrompt)
|
||||||
|
|||||||
@@ -174,11 +174,7 @@ func (r *LightRenderer) Init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.mouse {
|
r.enableMouse()
|
||||||
r.csi("?1000h")
|
|
||||||
r.csi("?1002h")
|
|
||||||
r.csi("?1006h")
|
|
||||||
}
|
|
||||||
r.csi(fmt.Sprintf("%dA", r.MaxY()-1))
|
r.csi(fmt.Sprintf("%dA", r.MaxY()-1))
|
||||||
r.csi("G")
|
r.csi("G")
|
||||||
r.csi("K")
|
r.csi("K")
|
||||||
@@ -609,6 +605,7 @@ func (r *LightRenderer) rmcup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Pause(clear bool) {
|
func (r *LightRenderer) Pause(clear bool) {
|
||||||
|
r.disableMouse()
|
||||||
r.restoreTerminal()
|
r.restoreTerminal()
|
||||||
if clear {
|
if clear {
|
||||||
if r.fullscreen {
|
if r.fullscreen {
|
||||||
@@ -621,6 +618,22 @@ func (r *LightRenderer) Pause(clear bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) enableMouse() {
|
||||||
|
if r.mouse {
|
||||||
|
r.csi("?1000h")
|
||||||
|
r.csi("?1002h")
|
||||||
|
r.csi("?1006h")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) disableMouse() {
|
||||||
|
if r.mouse {
|
||||||
|
r.csi("?1000l")
|
||||||
|
r.csi("?1002l")
|
||||||
|
r.csi("?1006l")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Resume(clear bool, sigcont bool) {
|
func (r *LightRenderer) Resume(clear bool, sigcont bool) {
|
||||||
r.setupTerminal()
|
r.setupTerminal()
|
||||||
if clear {
|
if clear {
|
||||||
@@ -629,14 +642,13 @@ func (r *LightRenderer) Resume(clear bool, sigcont bool) {
|
|||||||
} else {
|
} else {
|
||||||
r.rmcup()
|
r.rmcup()
|
||||||
}
|
}
|
||||||
|
r.enableMouse()
|
||||||
r.flush()
|
r.flush()
|
||||||
} else if sigcont && !r.fullscreen && r.mouse {
|
} else if sigcont && !r.fullscreen && r.mouse {
|
||||||
// NOTE: SIGCONT (Coming back from CTRL-Z):
|
// NOTE: SIGCONT (Coming back from CTRL-Z):
|
||||||
// It's highly likely that the offset we obtained at the beginning is
|
// It's highly likely that the offset we obtained at the beginning is
|
||||||
// no longer correct, so we simply disable mouse input.
|
// no longer correct, so we simply disable mouse input.
|
||||||
r.csi("?1000l")
|
r.disableMouse()
|
||||||
r.csi("?1002l")
|
|
||||||
r.csi("?1006l")
|
|
||||||
r.mouse = false
|
r.mouse = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -678,11 +690,7 @@ func (r *LightRenderer) Close() {
|
|||||||
} else if !r.fullscreen {
|
} else if !r.fullscreen {
|
||||||
r.csi("u")
|
r.csi("u")
|
||||||
}
|
}
|
||||||
if r.mouse {
|
r.disableMouse()
|
||||||
r.csi("?1000l")
|
|
||||||
r.csi("?1002l")
|
|
||||||
r.csi("?1006l")
|
|
||||||
}
|
|
||||||
r.flush()
|
r.flush()
|
||||||
r.closePlatform()
|
r.closePlatform()
|
||||||
r.restoreTerminal()
|
r.restoreTerminal()
|
||||||
@@ -719,25 +727,38 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, prev
|
|||||||
w.fg = r.theme.Fg.Color
|
w.fg = r.theme.Fg.Color
|
||||||
w.bg = r.theme.Bg.Color
|
w.bg = r.theme.Bg.Color
|
||||||
}
|
}
|
||||||
w.drawBorder()
|
w.drawBorder(false)
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) drawBorder() {
|
func (w *LightWindow) DrawHBorder() {
|
||||||
|
w.drawBorder(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) drawBorder(onlyHorizontal bool) {
|
||||||
switch w.border.shape {
|
switch w.border.shape {
|
||||||
case BorderRounded, BorderSharp, BorderBold, BorderDouble:
|
case BorderRounded, BorderSharp, BorderBold, BorderDouble:
|
||||||
w.drawBorderAround()
|
w.drawBorderAround(onlyHorizontal)
|
||||||
case BorderHorizontal:
|
case BorderHorizontal:
|
||||||
w.drawBorderHorizontal(true, true)
|
w.drawBorderHorizontal(true, true)
|
||||||
case BorderVertical:
|
case BorderVertical:
|
||||||
|
if onlyHorizontal {
|
||||||
|
return
|
||||||
|
}
|
||||||
w.drawBorderVertical(true, true)
|
w.drawBorderVertical(true, true)
|
||||||
case BorderTop:
|
case BorderTop:
|
||||||
w.drawBorderHorizontal(true, false)
|
w.drawBorderHorizontal(true, false)
|
||||||
case BorderBottom:
|
case BorderBottom:
|
||||||
w.drawBorderHorizontal(false, true)
|
w.drawBorderHorizontal(false, true)
|
||||||
case BorderLeft:
|
case BorderLeft:
|
||||||
|
if onlyHorizontal {
|
||||||
|
return
|
||||||
|
}
|
||||||
w.drawBorderVertical(true, false)
|
w.drawBorderVertical(true, false)
|
||||||
case BorderRight:
|
case BorderRight:
|
||||||
|
if onlyHorizontal {
|
||||||
|
return
|
||||||
|
}
|
||||||
w.drawBorderVertical(false, true)
|
w.drawBorderVertical(false, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -779,24 +800,26 @@ func (w *LightWindow) drawBorderVertical(left, right bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) drawBorderAround() {
|
func (w *LightWindow) drawBorderAround(onlyHorizontal bool) {
|
||||||
w.Move(0, 0)
|
w.Move(0, 0)
|
||||||
color := ColBorder
|
color := ColBorder
|
||||||
if w.preview {
|
if w.preview {
|
||||||
color = ColPreviewBorder
|
color = ColPreviewBorder
|
||||||
}
|
}
|
||||||
hw := runewidth.RuneWidth(w.border.horizontal)
|
hw := runewidth.RuneWidth(w.border.horizontal)
|
||||||
vw := runewidth.RuneWidth(w.border.vertical)
|
|
||||||
tcw := runewidth.RuneWidth(w.border.topLeft) + runewidth.RuneWidth(w.border.topRight)
|
tcw := runewidth.RuneWidth(w.border.topLeft) + runewidth.RuneWidth(w.border.topRight)
|
||||||
bcw := runewidth.RuneWidth(w.border.bottomLeft) + runewidth.RuneWidth(w.border.bottomRight)
|
bcw := runewidth.RuneWidth(w.border.bottomLeft) + runewidth.RuneWidth(w.border.bottomRight)
|
||||||
rem := (w.width - tcw) % hw
|
rem := (w.width - tcw) % hw
|
||||||
w.CPrint(color, string(w.border.topLeft)+repeat(w.border.horizontal, (w.width-tcw)/hw)+repeat(' ', rem)+string(w.border.topRight))
|
w.CPrint(color, string(w.border.topLeft)+repeat(w.border.horizontal, (w.width-tcw)/hw)+repeat(' ', rem)+string(w.border.topRight))
|
||||||
|
if !onlyHorizontal {
|
||||||
|
vw := runewidth.RuneWidth(w.border.vertical)
|
||||||
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.vertical))
|
w.CPrint(color, string(w.border.vertical))
|
||||||
w.CPrint(color, repeat(' ', w.width-vw*2))
|
w.CPrint(color, repeat(' ', w.width-vw*2))
|
||||||
w.CPrint(color, string(w.border.vertical))
|
w.CPrint(color, string(w.border.vertical))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
w.Move(w.height-1, 0)
|
w.Move(w.height-1, 0)
|
||||||
rem = (w.width - bcw) % hw
|
rem = (w.width - bcw) % hw
|
||||||
w.CPrint(color, string(w.border.bottomLeft)+repeat(w.border.horizontal, (w.width-bcw)/hw)+repeat(' ', rem)+string(w.border.bottomRight))
|
w.CPrint(color, string(w.border.bottomLeft)+repeat(w.border.horizontal, (w.width-bcw)/hw)+repeat(' ', rem)+string(w.border.bottomRight))
|
||||||
@@ -1040,7 +1063,7 @@ func (w *LightWindow) FinishFill() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) Erase() {
|
func (w *LightWindow) Erase() {
|
||||||
w.drawBorder()
|
w.drawBorder(false)
|
||||||
// We don't erase the window here to avoid flickering during scroll
|
// We don't erase the window here to avoid flickering during scroll
|
||||||
w.Move(0, 0)
|
w.Move(0, 0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -512,7 +512,7 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int,
|
|||||||
height: height,
|
height: height,
|
||||||
normal: normal,
|
normal: normal,
|
||||||
borderStyle: borderStyle}
|
borderStyle: borderStyle}
|
||||||
w.drawBorder()
|
w.drawBorder(false)
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -670,7 +670,11 @@ func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
|
|||||||
return w.fillString(str, NewColorPair(fg, bg, a))
|
return w.fillString(str, NewColorPair(fg, bg, a))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) drawBorder() {
|
func (w *TcellWindow) DrawHBorder() {
|
||||||
|
w.drawBorder(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
|
||||||
shape := w.borderStyle.shape
|
shape := w.borderStyle.shape
|
||||||
if shape == BorderNone {
|
if shape == BorderNone {
|
||||||
return
|
return
|
||||||
@@ -718,6 +722,7 @@ func (w *TcellWindow) drawBorder() {
|
|||||||
_screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style)
|
_screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !onlyHorizontal {
|
||||||
switch shape {
|
switch shape {
|
||||||
case BorderRounded, BorderSharp, BorderBold, BorderDouble, BorderVertical, BorderLeft:
|
case BorderRounded, BorderSharp, BorderBold, BorderDouble, BorderVertical, BorderLeft:
|
||||||
for y := top; y < bot; y++ {
|
for y := top; y < bot; y++ {
|
||||||
@@ -731,6 +736,7 @@ func (w *TcellWindow) drawBorder() {
|
|||||||
_screen.SetContent(right-vw, y, w.borderStyle.vertical, nil, style)
|
_screen.SetContent(right-vw, y, w.borderStyle.vertical, nil, style)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
switch shape {
|
switch shape {
|
||||||
case BorderRounded, BorderSharp, BorderBold, BorderDouble:
|
case BorderRounded, BorderSharp, BorderBold, BorderDouble:
|
||||||
_screen.SetContent(left, top, w.borderStyle.topLeft, nil, style)
|
_screen.SetContent(left, top, w.borderStyle.topLeft, nil, style)
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ const (
|
|||||||
BackwardEOF
|
BackwardEOF
|
||||||
Start
|
Start
|
||||||
Load
|
Load
|
||||||
|
Focus
|
||||||
|
|
||||||
AltBS
|
AltBS
|
||||||
|
|
||||||
@@ -426,6 +427,7 @@ type Window interface {
|
|||||||
Width() int
|
Width() int
|
||||||
Height() int
|
Height() int
|
||||||
|
|
||||||
|
DrawHBorder()
|
||||||
Refresh()
|
Refresh()
|
||||||
FinishFill()
|
FinishFill()
|
||||||
Close()
|
Close()
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ func TestAsUint16(t *testing.T) {
|
|||||||
if AsUint16(math.MinInt16) != 0 {
|
if AsUint16(math.MinInt16) != 0 {
|
||||||
t.Error("Expected", 0)
|
t.Error("Expected", 0)
|
||||||
}
|
}
|
||||||
if AsUint16(math.MaxUint32) != math.MaxUint16 {
|
if AsUint16(math.MaxUint16+1) != math.MaxUint16 {
|
||||||
t.Error("Expected", math.MaxUint16)
|
t.Error("Expected", math.MaxUint16)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExecCommand executes the given command with $SHELL
|
// ExecCommand executes the given command with $SHELL
|
||||||
@@ -45,3 +47,7 @@ func SetNonblock(file *os.File, nonblock bool) {
|
|||||||
func Read(fd int, b []byte) (int, error) {
|
func Read(fd int, b []byte) (int, error) {
|
||||||
return syscall.Read(int(fd), b)
|
return syscall.Read(int(fd), b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetStdin(file *os.File) {
|
||||||
|
unix.Dup2(int(file.Fd()), 0)
|
||||||
|
}
|
||||||
|
|||||||
@@ -81,3 +81,7 @@ func SetNonblock(file *os.File, nonblock bool) {
|
|||||||
func Read(fd int, b []byte) (int, error) {
|
func Read(fd int, b []byte) (int, error) {
|
||||||
return syscall.Read(syscall.Handle(fd), b)
|
return syscall.Read(syscall.Handle(fd), b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetStdin(file *os.File) {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
|||||||
220
test/test_go.rb
220
test/test_go.rb
@@ -180,7 +180,7 @@ class TestBase < Minitest::Test
|
|||||||
end
|
end
|
||||||
|
|
||||||
def writelines(path, lines)
|
def writelines(path, lines)
|
||||||
File.unlink(path) while File.exist?(path)
|
FileUtils.rm_f(path) while File.exist?(path)
|
||||||
File.open(path, 'w') { |f| f.puts lines }
|
File.open(path, 'w') { |f| f.puts lines }
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -188,7 +188,7 @@ class TestBase < Minitest::Test
|
|||||||
wait { assert_path_exists tempname }
|
wait { assert_path_exists tempname }
|
||||||
File.read(tempname)
|
File.read(tempname)
|
||||||
ensure
|
ensure
|
||||||
File.unlink(tempname) while File.exist?(tempname)
|
FileUtils.rm_f(tempname) while File.exist?(tempname)
|
||||||
@temp_suffix += 1
|
@temp_suffix += 1
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
end
|
end
|
||||||
@@ -905,11 +905,7 @@ class TestGoFZF < TestBase
|
|||||||
history_file = '/tmp/fzf-test-history'
|
history_file = '/tmp/fzf-test-history'
|
||||||
|
|
||||||
# History with limited number of entries
|
# History with limited number of entries
|
||||||
begin
|
FileUtils.rm_f(history_file)
|
||||||
File.unlink(history_file)
|
|
||||||
rescue StandardError
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
opts = "--history=#{history_file} --history-size=4"
|
opts = "--history=#{history_file} --history-size=4"
|
||||||
input = %w[00 11 22 33 44]
|
input = %w[00 11 22 33 44]
|
||||||
input.each do |keys|
|
input.each do |keys|
|
||||||
@@ -955,7 +951,7 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| assert_equal '> 33', lines[-1] }
|
tmux.until { |lines| assert_equal '> 33', lines[-1] }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
ensure
|
ensure
|
||||||
File.unlink(history_file)
|
FileUtils.rm_f(history_file)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_execute
|
def test_execute
|
||||||
@@ -984,11 +980,7 @@ class TestGoFZF < TestBase
|
|||||||
], File.readlines(output, chomp: true)
|
], File.readlines(output, chomp: true)
|
||||||
end
|
end
|
||||||
ensure
|
ensure
|
||||||
begin
|
FileUtils.rm_f(output)
|
||||||
File.unlink(output)
|
|
||||||
rescue StandardError
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_execute_multi
|
def test_execute_multi
|
||||||
@@ -1013,20 +1005,12 @@ class TestGoFZF < TestBase
|
|||||||
], File.readlines(output, chomp: true)
|
], File.readlines(output, chomp: true)
|
||||||
end
|
end
|
||||||
ensure
|
ensure
|
||||||
begin
|
FileUtils.rm_f(output)
|
||||||
File.unlink(output)
|
|
||||||
rescue StandardError
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_execute_plus_flag
|
def test_execute_plus_flag
|
||||||
output = tempname + '.tmp'
|
output = tempname + '.tmp'
|
||||||
begin
|
FileUtils.rm_f(output)
|
||||||
File.unlink(output)
|
|
||||||
rescue StandardError
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
writelines(tempname, ['foo bar', '123 456'])
|
writelines(tempname, ['foo bar', '123 456'])
|
||||||
|
|
||||||
tmux.send_keys "cat #{tempname} | #{FZF} --multi --bind 'x:execute-silent(echo {+}/{}/{+2}/{2} >> #{output})'", :Enter
|
tmux.send_keys "cat #{tempname} | #{FZF} --multi --bind 'x:execute-silent(echo {+}/{}/{+2}/{2} >> #{output})'", :Enter
|
||||||
@@ -1059,21 +1043,13 @@ class TestGoFZF < TestBase
|
|||||||
], File.readlines(output, chomp: true)
|
], File.readlines(output, chomp: true)
|
||||||
end
|
end
|
||||||
rescue StandardError
|
rescue StandardError
|
||||||
begin
|
FileUtils.rm_f(output)
|
||||||
File.unlink(output)
|
|
||||||
rescue StandardError
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_execute_shell
|
def test_execute_shell
|
||||||
# Custom script to use as $SHELL
|
# Custom script to use as $SHELL
|
||||||
output = tempname + '.out'
|
output = tempname + '.out'
|
||||||
begin
|
FileUtils.rm_f(output)
|
||||||
File.unlink(output)
|
|
||||||
rescue StandardError
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
writelines(tempname,
|
writelines(tempname,
|
||||||
['#!/usr/bin/env bash', "echo $1 / $2 > #{output}"])
|
['#!/usr/bin/env bash', "echo $1 / $2 > #{output}"])
|
||||||
system("chmod +x #{tempname}")
|
system("chmod +x #{tempname}")
|
||||||
@@ -1087,11 +1063,7 @@ class TestGoFZF < TestBase
|
|||||||
assert_equal ["-c / 'foo'bar"], File.readlines(output, chomp: true)
|
assert_equal ["-c / 'foo'bar"], File.readlines(output, chomp: true)
|
||||||
end
|
end
|
||||||
ensure
|
ensure
|
||||||
begin
|
FileUtils.rm_f(output)
|
||||||
File.unlink(output)
|
|
||||||
rescue StandardError
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_cycle
|
def test_cycle
|
||||||
@@ -1485,6 +1457,83 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| assert_includes lines[1], ' {5-1 3 4} ' }
|
tmux.until { |lines| assert_includes lines[1], ' {5-1 3 4} ' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_toggle_preview_without_default_preview_command
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --bind 'space:preview(echo [{}]),enter:toggle-preview' --preview-window up,border-double), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 100, lines.match_count
|
||||||
|
refute_includes lines[1], '║ [1]'
|
||||||
|
end
|
||||||
|
|
||||||
|
# toggle-preview should do nothing
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| refute_includes lines[1], '║ [1]' }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until do |lines|
|
||||||
|
refute_includes lines[1], '║ [1]'
|
||||||
|
refute_includes lines[1], '║ [2]'
|
||||||
|
end
|
||||||
|
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines, '> 3'
|
||||||
|
refute_includes lines[1], '║ [3]'
|
||||||
|
end
|
||||||
|
|
||||||
|
# One-off preview action
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '║ [3]' }
|
||||||
|
|
||||||
|
# toggle-preview to hide it
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| refute_includes lines[1], '║ [3]' }
|
||||||
|
|
||||||
|
# toggle-preview again does nothing
|
||||||
|
tmux.send_keys :Enter, :Up
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines, '> 4'
|
||||||
|
refute_includes lines[1], '║ [4]'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_show_and_hide_preview
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --preview-window hidden,border-bold --preview 'echo [{}]' --bind 'a:show-preview,b:hide-preview'), :Enter
|
||||||
|
|
||||||
|
# Hidden by default
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 100, lines.match_count
|
||||||
|
refute_includes lines[1], '┃ [1]'
|
||||||
|
end
|
||||||
|
|
||||||
|
# Show
|
||||||
|
tmux.send_keys :a
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '┃ [1]' }
|
||||||
|
|
||||||
|
# Already shown
|
||||||
|
tmux.send_keys :a
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '┃ [2]' }
|
||||||
|
|
||||||
|
# Hide
|
||||||
|
tmux.send_keys :b
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines, '> 3'
|
||||||
|
refute_includes lines[1], '┃ [3]'
|
||||||
|
end
|
||||||
|
|
||||||
|
# Already hidden
|
||||||
|
tmux.send_keys :b
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines, '> 4'
|
||||||
|
refute_includes lines[1], '┃ [4]'
|
||||||
|
end
|
||||||
|
|
||||||
|
# Show it again
|
||||||
|
tmux.send_keys :a
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '┃ [4]' }
|
||||||
|
end
|
||||||
|
|
||||||
def test_preview_hidden
|
def test_preview_hidden
|
||||||
tmux.send_keys %(seq 1000 | #{FZF} --preview 'echo {{}-{}-$FZF_PREVIEW_LINES-$FZF_PREVIEW_COLUMNS}' --preview-window down:1:hidden --bind ?:toggle-preview), :Enter
|
tmux.send_keys %(seq 1000 | #{FZF} --preview 'echo {{}-{}-$FZF_PREVIEW_LINES-$FZF_PREVIEW_COLUMNS}' --preview-window down:1:hidden --bind ?:toggle-preview), :Enter
|
||||||
tmux.until { |lines| assert_equal '>', lines[-1] }
|
tmux.until { |lines| assert_equal '>', lines[-1] }
|
||||||
@@ -1497,11 +1546,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_preview_size_0
|
def test_preview_size_0
|
||||||
begin
|
FileUtils.rm_f(tempname)
|
||||||
File.unlink(tempname)
|
|
||||||
rescue StandardError
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
tmux.send_keys %(seq 100 | #{FZF} --reverse --preview 'echo {} >> #{tempname}; echo ' --preview-window 0 --bind space:toggle-preview), :Enter
|
tmux.send_keys %(seq 100 | #{FZF} --reverse --preview 'echo {} >> #{tempname}; echo ' --preview-window 0 --bind space:toggle-preview), :Enter
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
assert_equal 100, lines.item_count
|
assert_equal 100, lines.item_count
|
||||||
@@ -1526,6 +1571,32 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_preview_size_0_hidden
|
||||||
|
FileUtils.rm_f(tempname)
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --reverse --preview 'echo {} >> #{tempname}; echo ' --preview-window 0,hidden --bind space:toggle-preview), :Enter
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||||
|
tmux.send_keys :Down, :Down
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 3' }
|
||||||
|
wait { refute_path_exists tempname }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
wait do
|
||||||
|
assert_path_exists tempname
|
||||||
|
assert_equal %w[3], File.readlines(tempname, chomp: true)
|
||||||
|
end
|
||||||
|
tmux.send_keys :Down
|
||||||
|
wait do
|
||||||
|
assert_equal %w[3 4], File.readlines(tempname, chomp: true)
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space, :Down
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 5' }
|
||||||
|
tmux.send_keys :Down
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 6' }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
wait do
|
||||||
|
assert_equal %w[3 4 6], File.readlines(tempname, chomp: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_preview_flags
|
def test_preview_flags
|
||||||
tmux.send_keys %(seq 10 | sed 's/^/:: /; s/$/ /' |
|
tmux.send_keys %(seq 10 | sed 's/^/:: /; s/$/ /' |
|
||||||
#{FZF} --multi --preview 'echo {{2}/{s2}/{+2}/{+s2}/{q}/{n}/{+n}}'), :Enter
|
#{FZF} --multi --preview 'echo {{2}/{s2}/{+2}/{+s2}/{q}/{n}/{+n}}'), :Enter
|
||||||
@@ -1587,6 +1658,11 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| assert_equal '> 1', lines[-2] }
|
tmux.until { |lines| assert_equal '> 1', lines[-2] }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_info_inline_separator
|
||||||
|
tmux.send_keys 'seq 10 | fzf --info=inline:___ --no-separator', :Enter
|
||||||
|
tmux.until { |lines| assert_equal '> ___10/10', lines[-1] }
|
||||||
|
end
|
||||||
|
|
||||||
def test_change_first_last
|
def test_change_first_last
|
||||||
tmux.send_keys %(seq 1000 | #{FZF} --bind change:first,alt-Z:last), :Enter
|
tmux.send_keys %(seq 1000 | #{FZF} --bind change:first,alt-Z:last), :Enter
|
||||||
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||||
@@ -2082,11 +2158,7 @@ class TestGoFZF < TestBase
|
|||||||
wait { refute system("pgrep -f #{script}") }
|
wait { refute system("pgrep -f #{script}") }
|
||||||
ensure
|
ensure
|
||||||
system("pkill -9 -f #{script}")
|
system("pkill -9 -f #{script}")
|
||||||
begin
|
FileUtils.rm_f(script)
|
||||||
File.unlink(script)
|
|
||||||
rescue StandardError
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_kill_default_command_on_accept
|
def test_kill_default_command_on_accept
|
||||||
@@ -2104,11 +2176,7 @@ class TestGoFZF < TestBase
|
|||||||
wait { refute system("pgrep -f #{script}") }
|
wait { refute system("pgrep -f #{script}") }
|
||||||
ensure
|
ensure
|
||||||
system("pkill -9 -f #{script}")
|
system("pkill -9 -f #{script}")
|
||||||
begin
|
FileUtils.rm_f(script)
|
||||||
File.unlink(script)
|
|
||||||
rescue StandardError
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_kill_reload_command_on_abort
|
def test_kill_reload_command_on_abort
|
||||||
@@ -2129,11 +2197,7 @@ class TestGoFZF < TestBase
|
|||||||
wait { refute system("pgrep -f #{script}") }
|
wait { refute system("pgrep -f #{script}") }
|
||||||
ensure
|
ensure
|
||||||
system("pkill -9 -f #{script}")
|
system("pkill -9 -f #{script}")
|
||||||
begin
|
FileUtils.rm_f(script)
|
||||||
File.unlink(script)
|
|
||||||
rescue StandardError
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_kill_reload_command_on_accept
|
def test_kill_reload_command_on_accept
|
||||||
@@ -2153,11 +2217,7 @@ class TestGoFZF < TestBase
|
|||||||
wait { refute system("pgrep -f #{script}") }
|
wait { refute system("pgrep -f #{script}") }
|
||||||
ensure
|
ensure
|
||||||
system("pkill -9 -f #{script}")
|
system("pkill -9 -f #{script}")
|
||||||
begin
|
FileUtils.rm_f(script)
|
||||||
File.unlink(script)
|
|
||||||
rescue StandardError
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_preview_header
|
def test_preview_header
|
||||||
@@ -2473,12 +2533,31 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_focus_event
|
||||||
|
tmux.send_keys 'seq 100 | fzf --bind "focus:transform-prompt(echo [[{}]])"', :Enter
|
||||||
|
tmux.until { |lines| assert_includes(lines[-1], '[[1]]') }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_includes(lines[-1], '[[2]]') }
|
||||||
|
tmux.send_keys :X
|
||||||
|
tmux.until { |lines| assert_includes(lines[-1], '[[]]') }
|
||||||
|
end
|
||||||
|
|
||||||
def test_labels_center
|
def test_labels_center
|
||||||
tmux.send_keys ': | fzf --border --border-label foobar --preview : --preview-label barfoo', :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
|
||||||
assert_includes(_1[0], '─foobar─')
|
assert_includes(_1[0], '─foobar─')
|
||||||
assert_includes(_1[1], '─barfoo─')
|
assert_includes(_1[1], '─barfoo─')
|
||||||
end
|
end
|
||||||
|
tmux.send_keys :space
|
||||||
|
tmux.until do
|
||||||
|
assert_includes(_1[0], '─foobarfoo─')
|
||||||
|
assert_includes(_1[1], '─barfoobar─')
|
||||||
|
end
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until do
|
||||||
|
assert_includes(_1[0], '─fooxfoo─')
|
||||||
|
assert_includes(_1[1], '─barxbar─')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_labels_left
|
def test_labels_left
|
||||||
@@ -2564,6 +2643,19 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys :Space
|
tmux.send_keys :Space
|
||||||
tmux.until { |lines| assert_includes lines, '/1/1/' }
|
tmux.until { |lines| assert_includes lines, '/1/1/' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_become
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --bind 'enter:become:seq {} | #{FZF}'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||||
|
tmux.send_keys 999
|
||||||
|
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert_equal 99, lines.item_count }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module TestShell
|
module TestShell
|
||||||
|
|||||||
Reference in New Issue
Block a user