Describe become(...) action and use it to simplify examples

pull/3183/head
Junegunn Choi 1 year ago
parent 4e305eca26
commit 27018787af
No known key found for this signature in database
GPG Key ID: 254BC280FEF9C627

@ -1,30 +1,33 @@
Advanced fzf examples Advanced fzf examples
====================== ======================
*(Last update: 2023/02/12)* * *Last update: 2023/02/15*
* *Requires fzf 0.38.0 or above*
---
<!-- vim-markdown-toc GFM --> <!-- vim-markdown-toc GFM -->
* [Introduction](#introduction) * [Introduction](#introduction)
* [Screen Layout](#screen-layout) * [Screen Layout](#screen-layout)
* [`--height`](#--height) * [`--height`](#--height)
* [`fzf-tmux`](#fzf-tmux) * [`fzf-tmux`](#fzf-tmux)
* [Popup window support](#popup-window-support) * [Popup window support](#popup-window-support)
* [Dynamic reloading of the list](#dynamic-reloading-of-the-list) * [Dynamic reloading of the list](#dynamic-reloading-of-the-list)
* [Updating the list of processes by pressing CTRL-R](#updating-the-list-of-processes-by-pressing-ctrl-r) * [Updating the list of processes by pressing CTRL-R](#updating-the-list-of-processes-by-pressing-ctrl-r)
* [Toggling between data sources](#toggling-between-data-sources) * [Toggling between data sources](#toggling-between-data-sources)
* [Ripgrep integration](#ripgrep-integration) * [Ripgrep integration](#ripgrep-integration)
* [Using fzf as the secondary filter](#using-fzf-as-the-secondary-filter) * [Using fzf as the secondary filter](#using-fzf-as-the-secondary-filter)
* [Using fzf as interactive Ripgrep launcher](#using-fzf-as-interactive-ripgrep-launcher) * [Using fzf as interactive Ripgrep launcher](#using-fzf-as-interactive-ripgrep-launcher)
* [Switching to fzf-only search mode](#switching-to-fzf-only-search-mode) * [Switching to fzf-only search mode](#switching-to-fzf-only-search-mode)
* [Switching between Ripgrep mode and fzf mode](#switching-between-ripgrep-mode-and-fzf-mode) * [Switching between Ripgrep mode and fzf mode](#switching-between-ripgrep-mode-and-fzf-mode)
* [Log tailing](#log-tailing) * [Log tailing](#log-tailing)
* [Key bindings for git objects](#key-bindings-for-git-objects) * [Key bindings for git objects](#key-bindings-for-git-objects)
* [Files listed in `git status`](#files-listed-in-git-status) * [Files listed in `git status`](#files-listed-in-git-status)
* [Branches](#branches) * [Branches](#branches)
* [Commit hashes](#commit-hashes) * [Commit hashes](#commit-hashes)
* [Color themes](#color-themes) * [Color themes](#color-themes)
* [Generating fzf color theme from Vim color schemes](#generating-fzf-color-theme-from-vim-color-schemes) * [Generating fzf color theme from Vim color schemes](#generating-fzf-color-theme-from-vim-color-schemes)
<!-- vim-markdown-toc --> <!-- vim-markdown-toc -->
@ -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]}"
``` ```
![image](https://user-images.githubusercontent.com/700826/113684212-f9ff0a00-96ff-11eb-8737-7bb571d320cc.png) ![image](https://user-images.githubusercontent.com/700826/113684212-f9ff0a00-96ff-11eb-8737-7bb571d320cc.png)
@ -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,19 +376,17 @@ 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" \ --bind "alt-enter:unbind(change,alt-enter)+change-prompt(2. fzf> )+enable-search+clear-query" \
--bind "alt-enter:unbind(change,alt-enter)+change-prompt(2. fzf> )+enable-search+clear-query" \ --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,8 +407,6 @@ IFS=: read -ra selected < <(
### Switching between Ripgrep mode and fzf mode ### Switching between Ripgrep mode and fzf mode
*(Requires fzf 0.36.0 or above)*
[fzf 0.30.0][0.30.0] added `rebind` action so we can "rebind" the bindings [fzf 0.30.0][0.30.0] added `rebind` action so we can "rebind" the bindings
that were previously "unbound" via `unbind`. that were previously "unbound" via `unbind`.
@ -424,22 +421,20 @@ CTRL-F.
rm -f /tmp/rg-fzf-{r,f} rm -f /tmp/rg-fzf-{r,f}
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case " RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
INITIAL_QUERY="${*:-}" INITIAL_QUERY="${*:-}"
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+rebind(ctrl-r)+transform-query(echo {q} > /tmp/rg-fzf-r; cat /tmp/rg-fzf-f)" \
--bind "ctrl-f:unbind(change,ctrl-f)+change-prompt(2. fzf> )+enable-search+rebind(ctrl-r)+transform-query(echo {q} > /tmp/rg-fzf-r; cat /tmp/rg-fzf-f)" \ --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)" \
--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)" \ --bind "start:unbind(ctrl-r)" \
--bind "start:unbind(ctrl-r)" \ --prompt '1. ripgrep> ' \
--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 - To restore the query string when switching between modes, we store the

@ -5,12 +5,12 @@ CHANGELOG
------ ------
- New actions - New actions
- `become(...)` - Replace the current fzf process with the specified - `become(...)` - Replace the current fzf process with the specified
command using `execve(2)` system call. This action enables a simpler command using `execve(2)` system call.
alternative to using `--expect` and checking the output in the wrapping See https://github.com/junegunn/fzf#turning-into-a-different-process.
script.
```sh ```sh
# Open selected files in different editors # Open the file in Vim and go to the line
fzf --multi --bind 'enter:become($EDITOR {+}),ctrl-n:become(nano {+})' git grep --line-number . |
fzf --delimiter : --nth 3.. --bind 'enter:become(vim {1} +{2})'
``` ```
- This action is not supported on Windows - This action is not supported on Windows
- `show-preview` - `show-preview`

@ -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)
@ -204,6 +205,22 @@ files excluding hidden ones. (You can override the default command with
vim $(fzf) vim $(fzf)
``` ```
> *: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 ### 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
@ -562,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

Loading…
Cancel
Save