Compare commits

..

23 Commits

Author SHA1 Message Date
Junegunn Choi
e086f0b3fe 0.27.2 2021-06-01 17:00:24 +09:00
Junegunn Choi
8255aa23f4 Fix bug where --read0 not properly displaying long lines
Fix #2508
2021-06-01 16:55:51 +09:00
Junegunn Choi
a4bc08f5a3 Allow specifying 16 base ANSI colors by their names
Close #2502
2021-05-26 19:35:26 +09:00
Thomas Klausner
7e5aa1e2a5 Mention NetBSD package and how to install it (#2499)
Close #2487
2021-05-23 11:31:55 +09:00
Junegunn Choi
0818dbc36a 0.27.1 2021-05-22 13:19:57 +09:00
Junegunn Choi
347c4b2625 Add 'unbind' action
Fix #2486
2021-05-22 13:16:39 +09:00
Junegunn Choi
34f0d4d0c4 [man] Clarification on --select-1 and --exit-0 2021-05-22 09:47:02 +09:00
Junegunn Choi
cbedb57511 [vim] Workaround for Neovim bug of unconditionally evaluating unlet $ENV_VAR
See #2495
2021-05-21 13:17:19 +09:00
Junegunn Choi
9ef825d2fd [vim] Update README-VIM 2021-05-19 18:43:50 +09:00
Junegunn Choi
85ae745910 [vim] Use terminal buffer on 'down' layout on regular Vim on terminal
When 'down' layout was used on regular Vim on terminal, fzf would open
below the editor using `--height` option. This was the only case where
terminal buffer was not used (the code was written when Vim didn't have
builtin terminal) and this exception has been a constant source of
confusion.

This commit makes fzf open in a terminal buffer even in that case.
2021-05-19 18:39:12 +09:00
Junegunn Choi
7411da8d5a [vim] Use FZF_DEFAULT_COMMAND instead of STDIN pipe
So that fzf can finish immediately even when the input process doesn't
handle SIGPIPE and keeps running.

Fix #2481
2021-05-19 18:37:13 +09:00
Junegunn Choi
3f75a8369f Replace RuneWidth to StringWidth to handle grapheme clusters
Fix #2482
2021-05-14 11:44:44 +09:00
Junegunn Choi
4cd621e877 ADVANCED.md: tmux 3.2 is officially released 2021-04-29 09:37:17 +09:00
Junegunn Choi
6e3a2fe0bf [vim] Fix screen offset of relatively positioned popup window
Fix #2461
2021-04-28 10:17:46 +09:00
Tom Picton
8b0e1f941a [vim] Support relative-to-window positioning of popup (#2443)
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2021-04-17 20:48:10 +09:00
Junegunn Choi
c7c5e7670a Fix goreleaser.yml 2021-04-10 14:19:19 +09:00
Junegunn Choi
f6c621ef1b Update ADVANCED.md
Remove unnecessary --color option
2021-04-09 22:58:31 +09:00
Junegunn Choi
faf32d451d Update ADVANCED.md 2021-04-09 14:14:53 +09:00
Junegunn Choi
252fd7ecb1 Update ADVANCED.md 2021-04-09 13:43:16 +09:00
Junegunn Choi
7fa89dddb4 Update README.md: Examples page 2021-04-08 10:06:26 +09:00
Junegunn Choi
fefdb8c84e Fix typo 2021-04-07 13:28:26 +09:00
Junegunn Choi
a6cc05936e ADVANCED.md: Clarification on {q} 2021-04-07 07:12:39 +09:00
Junegunn Choi
b209843545 Advanced fzf examples 2021-04-07 06:46:17 +09:00
18 changed files with 980 additions and 215 deletions

View File

@@ -14,6 +14,25 @@ builds:
- amd64 - amd64
ldflags: ldflags:
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}" - "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
hooks:
post: |
sh -c '
cat > /tmp/fzf-gon-amd64.hcl << EOF
source = ["./dist/fzf-macos_darwin_amd64/fzf"]
bundle_id = "kr.junegunn.fzf"
apple_id {
username = "junegunn.c@gmail.com"
password = "@env:AC_PASSWORD"
}
sign {
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
}
zip {
output_path = "./dist/fzf-{{ .Version }}-darwin_amd64.zip"
}
EOF
gon /tmp/fzf-gon-amd64.hcl
'
- id: fzf-macos-arm - id: fzf-macos-arm
binary: fzf binary: fzf
@@ -23,6 +42,25 @@ builds:
- arm64 - arm64
ldflags: ldflags:
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}" - "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
hooks:
post: |
sh -c '
cat > /tmp/fzf-gon-arm64.hcl << EOF
source = ["./dist/fzf-macos-arm_darwin_arm64/fzf"]
bundle_id = "kr.junegunn.fzf"
apple_id {
username = "junegunn.c@gmail.com"
password = "@env:AC_PASSWORD"
}
sign {
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
}
zip {
output_path = "./dist/fzf-{{ .Version }}-darwin_arm64.zip"
}
EOF
gon /tmp/fzf-gon-arm64.hcl
'
- id: fzf - id: fzf
goos: goos:
@@ -61,53 +99,6 @@ archives:
files: files:
- non-existent* - non-existent*
signs:
- id: fzf-macos-sign
ids: [fzf-macos]
artifacts: all
cmd: sh
args:
- "-c"
- |-
cat > /tmp/fzf-gon-amd64.hcl << EOF
source = ["./dist/fzf-macos_darwin_amd64/fzf"]
bundle_id = "kr.junegunn.fzf"
apple_id {
username = "junegunn.c@gmail.com"
password = "@env:AC_PASSWORD"
}
sign {
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
}
zip {
output_path = "./dist/fzf-{{ .Version }}-darwin_amd64.zip"
}
EOF
gon /tmp/fzf-gon-amd64.hcl
- id: fzf-macos-arm-sign
ids: [fzf-macos-arm]
artifacts: all
cmd: sh
args:
- "-c"
- |-
cat > /tmp/fzf-gon-arm64.hcl << EOF
source = ["./dist/fzf-macos-arm_darwin_arm64/fzf"]
bundle_id = "kr.junegunn.fzf"
apple_id {
username = "junegunn.c@gmail.com"
password = "@env:AC_PASSWORD"
}
sign {
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
}
zip {
output_path = "./dist/fzf-{{ .Version }}-darwin_arm64.zip"
}
EOF
gon /tmp/fzf-gon-arm64.hcl
release: release:
github: github:
owner: junegunn owner: junegunn

565
ADVANCED.md Normal file
View File

@@ -0,0 +1,565 @@
Advanced fzf examples
======================
*(Last update: 2021/05/22)*
<!-- vim-markdown-toc GFM -->
* [Introduction](#introduction)
* [Screen Layout](#screen-layout)
* [`--height`](#--height)
* [`fzf-tmux`](#fzf-tmux)
* [Popup window support](#popup-window-support)
* [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)
* [Toggling between data sources](#toggling-between-data-sources)
* [Ripgrep integration](#ripgrep-integration)
* [Using fzf as the secondary filter](#using-fzf-as-the-secondary-filter)
* [Using fzf as interative Ripgrep launcher](#using-fzf-as-interative-ripgrep-launcher)
* [Switching to fzf-only search mode](#switching-to-fzf-only-search-mode)
* [Log tailing](#log-tailing)
* [Key bindings for git objects](#key-bindings-for-git-objects)
* [Files listed in `git status`](#files-listed-in-git-status)
* [Branches](#branches)
* [Commit hashes](#commit-hashes)
* [Color themes](#color-themes)
* [Generating fzf color theme from Vim color schemes](#generating-fzf-color-theme-from-vim-color-schemes)
<!-- vim-markdown-toc -->
Introduction
------------
fzf is an interactive [Unix filter][filter] program that is designed to be
used with other Unix tools. It reads a list of items from the standard input,
allows you to select a subset of the items, and prints the selected ones to
the standard output. You can think of it as an interactive version of *grep*,
and it's already useful even if you don't know any of its options.
```sh
# 1. ps: Feed the list of processes to fzf
# 2. fzf: Interactively select a process using fuzzy matching algorithm
# 3. awk: Take the PID from the selected line
# 3. kill: Kill the process with the PID
ps -ef | fzf | awk '{print $2}' | xargs kill -9
```
[filter]: https://en.wikipedia.org/wiki/Filter_(software)
While the above example succinctly summarizes the fundamental concept of fzf,
you can build much more sophisticated interactive workflows using fzf once you
learn its wide variety of features.
- To see the full list of options and features, see `man fzf`
- To see the latest additions, see [CHANGELOG.md](CHANGELOG.md)
This document will guide you through some examples that will familiarize you
with the advanced features of fzf.
Screen Layout
-------------
### `--height`
fzf by default opens in fullscreen mode, but it's not always desirable.
Oftentimes, you want to see the current context of the terminal while using
fzf. `--height` is an option for opening fzf below the cursor in
non-fullscreen mode so you can still see the previous commands and their
results above it.
```sh
fzf --height=40%
```
![image](https://user-images.githubusercontent.com/700826/113379893-c184c680-93b5-11eb-9676-c7c0a2f01748.png)
You might also want to experiment with other layout options such as
`--layout=reverse`, `--info=inline`, `--border`, `--margin`, etc.
```sh
fzf --height=40% --layout=reverse
fzf --height=40% --layout=reverse --info=inline
fzf --height=40% --layout=reverse --info=inline --border
fzf --height=40% --layout=reverse --info=inline --border --margin=1
fzf --height=40% --layout=reverse --info=inline --border --margin=1 --padding=1
```
![image](https://user-images.githubusercontent.com/700826/113379932-dfeac200-93b5-11eb-9e28-df1a2ee71f90.png)
*(See `Layout` section of the man page to see the full list of options)*
But you definitely don't want to repeat `--height=40% --layout=reverse
--info=inline --border --margin=1 --padding=1` every time you use fzf. You
could write a wrapper script or shell alias, but there is an easier option.
Define `$FZF_DEFAULT_OPTS` like so:
```sh
export FZF_DEFAULT_OPTS="--height=40% --layout=reverse --info=inline --border --margin=1 --padding=1"
```
### `fzf-tmux`
Before fzf had `--height` option, we would open fzf in a tmux split pane not
to take up the whole screen. This is done using `fzf-tmux` script.
```sh
# Open fzf on a tmux split pane below the current pane.
# Takes the same set of options.
fzf-tmux --layout=reverse
```
![image](https://user-images.githubusercontent.com/700826/113379973-f1cc6500-93b5-11eb-8860-c9bc4498aadf.png)
The limitation of `fzf-tmux` is that it only works when you're on tmux unlike
`--height` option. But the advantage of it is that it's more flexible.
(See `man fzf-tmux` for available options.)
```sh
# On the right (50%)
fzf-tmux -r
# On the left (30%)
fzf-tmux -l30%
# Above the cursor
fzf-tmux -u30%
```
![image](https://user-images.githubusercontent.com/700826/113379983-fa24a000-93b5-11eb-93eb-8a3d39b2f163.png)
![image](https://user-images.githubusercontent.com/700826/113380001-0577cb80-93b6-11eb-95d0-2ba453866882.png)
![image](https://user-images.githubusercontent.com/700826/113380040-1d4f4f80-93b6-11eb-9bef-737fb120aafe.png)
#### Popup window support
But here's the really cool part; tmux 3.2 added support for popup windows. So
you can open fzf in a popup window, which is quite useful if you frequently
use split panes.
```sh
# Open tmux in a tmux popup window (default size: 50% of the screen)
fzf-tmux -p
# 80% width, 60% height
fzf-tmux -p 80%,60%
```
![image](https://user-images.githubusercontent.com/700826/113380106-4a9bfd80-93b6-11eb-8cee-aeb1c4ce1a1f.png)
> You might also want to check out my tmux plugins which support this popup
> window layout.
>
> - https://github.com/junegunn/tmux-fzf-url
> - https://github.com/junegunn/tmux-fzf-maccy
Dynamic reloading of the list
-----------------------------
fzf can dynamically update the candidate list using an arbitrary program with
`reload` bindings (The design document for `reload` can be found
[here][reload]).
[reload]: https://github.com/junegunn/fzf/issues/1750
### Updating the list of processes by pressing CTRL-R
This example shows how you can set up a binding for dynamically updating the
list without restarting fzf.
```sh
(date; ps -ef) |
fzf --bind='ctrl-r:reload(date; ps -ef)' \
--header=$'Press CTRL-R to reload\n\n' --header-lines=2 \
--preview='echo {}' --preview-window=down,3,wrap \
--layout=reverse --height=80% | awk '{print $2}' | xargs kill -9
```
![image](https://user-images.githubusercontent.com/700826/113465047-200c7c00-946c-11eb-918c-268f37a900c8.png)
- The initial command is `(date; ps -ef)`. It prints the current date and
time, and the list of the processes.
- With `--header` option, you can show any message as the fixed header.
- To disallow selecting the first two lines (`date` and `ps` header), we use
`--header-lines=2` option.
- `--bind='ctrl-r:reload(date; ps -ef)'` binds CTRL-R to `reload` action that
runs `date; ps -ef`, so we can update the list of the processes by pressing
CTRL-R.
- We use simple `echo {}` preview option, so we can see the entire line on the
preview window below even if it's too long
### Toggling between data sources
You're not limiited to just one reload binding. Set up multiple bindings so
you can switch between data sources.
```sh
find * | fzf --prompt 'All> ' \
--header 'CTRL-D: Directories / CTRL-F: Files' \
--bind 'ctrl-d:change-prompt(Directories> )+reload(find * -type d)' \
--bind 'ctrl-f:change-prompt(Files> )+reload(find * -type f)'
```
![image](https://user-images.githubusercontent.com/700826/113465073-4af6d000-946c-11eb-858f-2372c0955f67.png)
![image](https://user-images.githubusercontent.com/700826/113465072-46321c00-946c-11eb-9b6f-cda3951df579.png)
Ripgrep integration
-------------------
### Using fzf as the secondary filter
* Requires [bat][bat]
* Requires [Ripgrep][rg]
[bat]: https://github.com/sharkdp/bat
[rg]: https://github.com/BurntSushi/ripgrep
fzf is pretty fast for filtering a list that you will rarely have to think
about its performance. But it is not the right tool for searching for text
inside many large files, and in that case you should definitely use something
like [Ripgrep][rg].
In the next example, Ripgrep is the primary filter that searches for the given
text in files, and fzf is used as the secondary fuzzy filter that adds
interactivity to the workflow. And we use [bat][bat] to show the matching line in
the preview window.
This is a bash script and it will not run as expected on other non-compliant
shells. To avoid the compatibility issue, let's save this snippet as a script
file called `rfv`.
```bash
#!/usr/bin/env bash
# 1. Search for text in files using Ripgrep
# 2. Interactively narrow down the list using fzf
# 3. Open the file in Vim
IFS=: read -ra selected < <(
rg --color=always --line-number --no-heading --smart-case "${*:-}" |
fzf --ansi \
--color "hl:-1:underline,hl+:-1:underline:reverse" \
--delimiter : \
--preview 'bat --color=always {1} --highlight-line {2}' \
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3'
)
[ -n "${selected[0]}" ] && vim "${selected[0]}" "+${selected[1]}"
```
And run it with an initial query string.
```sh
# Make the script executable
chmod +x rfv
# Run it with the initial query "algo"
./rfv algo
```
> Ripgrep will perform the initial search and list all the lines that contain
`algo`. Then we further narrow down the list on fzf.
![image](https://user-images.githubusercontent.com/700826/113683873-a42a6200-96ff-11eb-9666-26ce4091b0e4.png)
I know it's a lot to digest, let's try to break down the code.
- Ripgrep prints the matching lines in the following format
```
man/man1/fzf.1:54:.BI "--algo=" TYPE
man/man1/fzf.1:55:Fuzzy matching algorithm (default: v2)
man/man1/fzf.1:58:.BR v2 " Optimal scoring algorithm (quality)"
src/pattern_test.go:7: "github.com/junegunn/fzf/src/algo"
```
The first token delimited by `:` is the file path, and the second token is
the line number of the matching line. They respectively correspond to `{1}`
and `{2}` in the preview command.
- `--preview 'bat --color=always {1} --highlight-line {2}'`
- As we run `rg` with `--color=always` option, we should tell fzf to parse
ANSI color codes in the input by setting `--ansi`.
- We customize how fzf colors various text elements using `--color` option.
`-1` tells fzf to keep the original color from the input. See `man fzf` for
available color options.
- The value of `--preview-window` option consists of 5 components delimited
by `,`
1. `up` — Position of the preview window
1. `60%` — Size of the preview window
1. `border-bottom` — Preview window border only on the bottom side
1. `+{2}+3/3` — Scroll offset of the preview contents
1. `~3` — Fixed header
- Let's break down the latter two. We want to display the bat output in the
preview window with a certain scroll offset so that the matching line is
positioned near the center of the preview window.
- `+{2}` — The base offset is extracted from the second token
- `+3` — We add 3 lines to the base offset to compensate for the header
part of `bat` output
- ```
───────┬──────────────────────────────────────────────────────────
│ File: CHANGELOG.md
───────┼──────────────────────────────────────────────────────────
1 │ CHANGELOG
2 │ =========
3 │
4 │ 0.26.0
5 │ ------
```
- `/3` adjusts the offset so that the matching line is shown at a third
position in the window
- `~3` makes the top three lines fixed header so that they are always
visible regardless of the scroll offset
- Once we selected a line, we open the file with `vim` (`vim
"${selected[0]}"`) and move the cursor to the line (`+${selected[1]}`).
### Using fzf as interative Ripgrep launcher
We have learned that we can bind `reload` action to a key (e.g.
`--bind=ctrl-r:execute(ps -ef)`). In the next example, we are going to **bind
`reload` action to `change` event** so that whenever the user *changes* the
query string on fzf, `reload` action is triggered.
Here is a variation of the above `rfv` script. fzf will restart Ripgrep every
time the user updates the query string on fzf. Searching and filtering is
completely done by Ripgrep, and fzf merely provides the interactive interface.
So we lose the "fuzziness", but the performance will be better on larger
projects, and it will free up memory as you narrow down the results.
```bash
#!/usr/bin/env bash
# 1. Search for text in files using Ripgrep
# 2. Interactively restart Ripgrep with reload action
# 3. Open the file in Vim
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
INITIAL_QUERY="${*:-}"
IFS=: read -ra selected < <(
FZF_DEFAULT_COMMAND="$RG_PREFIX $(printf %q "$INITIAL_QUERY")" \
fzf --ansi \
--disabled --query "$INITIAL_QUERY" \
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \
--delimiter : \
--preview 'bat --color=always {1} --highlight-line {2}' \
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3'
)
[ -n "${selected[0]}" ] && vim "${selected[0]}" "+${selected[1]}"
```
![image](https://user-images.githubusercontent.com/700826/113684212-f9ff0a00-96ff-11eb-8737-7bb571d320cc.png)
- Instead of starting fzf in `rg ... | fzf` form, we start fzf without an
explicit input, but with a custom `FZF_DEFAULT_COMMAND` variable. This way
fzf can kill the initial Ripgrep process it starts with the initial query.
Otherwise, the initial Ripgrep process will keep consuming system resources
even after `reload` is triggered.
- Filtering is no longer a responsibitiliy of fzf; hence `--disabled`
- `{q}` in the reload command evaluates to the query string on fzf prompt.
- `sleep 0.1` in the reload command is for "debouncing". This small delay will
reduce the number of intermediate Ripgrep processes while we're typing in
a query.
### 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
delegated search functionality to Ripgrep. But we can dynamically switch to
fzf-only search mode by *"unbinding"* `reload` action from `change` event.
```sh
#!/usr/bin/env bash
# Two-phase filtering with Ripgrep and fzf
#
# 1. Search for text in files using Ripgrep
# 2. Interactively restart Ripgrep with reload action
# * Press alt-enter to switch to fzf-only filtering
# 3. Open the file in Vim
RG_PREFIX="rg --column --line-number --no-heading --color=always --smart-case "
INITIAL_QUERY="${*:-}"
IFS=: read -ra selected < <(
FZF_DEFAULT_COMMAND="$RG_PREFIX $(printf %q "$INITIAL_QUERY")" \
fzf --ansi \
--color "hl:-1:underline,hl+:-1:underline:reverse" \
--disabled --query "$INITIAL_QUERY" \
--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" \
--prompt '1. ripgrep> ' \
--delimiter : \
--preview 'bat --color=always {1} --highlight-line {2}' \
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3'
)
[ -n "${selected[0]}" ] && vim "${selected[0]}" "+${selected[1]}"
```
* Phase 1. Filtering with Ripgrep
![image](https://user-images.githubusercontent.com/700826/119213880-735e8a80-bafd-11eb-8493-123e4be24fbc.png)
* Phase 2. Filtering with fzf
![image](https://user-images.githubusercontent.com/700826/119213887-7e191f80-bafd-11eb-98c9-71a1af9d7aab.png)
- We added `--prompt` option to show that fzf is initially running in "Ripgrep
launcher mode".
- We added `alt-enter` binding that
1. unbinds `change` event, so Ripgrep is no longer restarted on key press
2. changes the prompt to `2. fzf>`
3. enables search functionality of fzf
4. clears the current query string that was used to start Ripgrep process
5. and unbinds `alt-enter` itself as this is a one-off event
- We reverted `--color` option for customizing how the matching chunks are
displayed in the second phase
Log tailing
-----------
fzf can run long-running preview commands and render partial results before
completion. And when you specify `follow` flag in `--preview-window` option,
fzf will "`tail -f`" the result, automatically scrolling to the bottom.
```bash
# With "follow", preview window will automatically scroll to the bottom.
# "\033[2J" is an ANSI escape sequence for clearing the screen.
# When fzf reads this code it clears the previous preview contents.
fzf --preview-window follow --preview 'for i in $(seq 100000); do
echo "$i"
sleep 0.01
(( i % 300 == 0 )) && printf "\033[2J"
done'
```
![image](https://user-images.githubusercontent.com/700826/113473303-dd669600-94a3-11eb-88a9-1f61b996bb0e.png)
Admittedly, that was a silly example. Here's a practical one for browsing
Kubernetes pods.
```bash
#!/usr/bin/env bash
read -ra tokens < <(
kubectl get pods --all-namespaces |
fzf --info=inline --layout=reverse --header-lines=1 --border \
--prompt "$(kubectl config current-context | sed 's/-context$//')> " \
--header $'Press CTRL-O to open log in editor\n\n' \
--bind ctrl-/:toggle-preview \
--bind 'ctrl-o:execute:${EDITOR:-vim} <(kubectl logs --namespace {1} {2}) > /dev/tty' \
--preview-window up,follow \
--preview 'kubectl logs --follow --tail=100000 --namespace {1} {2}' "$@"
)
[ ${#tokens} -gt 1 ] &&
kubectl exec -it --namespace "${tokens[0]}" "${tokens[1]}" -- bash
```
![image](https://user-images.githubusercontent.com/700826/113473547-1d7a4880-94a5-11eb-98ef-9aa6f0ed215a.png)
- The preview window will *"log tail"* the pod
- Holding on to a large amount of log will consume a lot of memory. So we
limited the initial log amount with `--tail=100000`.
- With `execute` binding, you can press CTRL-O to open the log in your editor
without leaving fzf
- Select a pod (with an enter key) to `kubectl exec` into it
Key bindings for git objects
----------------------------
I have [blogged](https://junegunn.kr/2016/07/fzf-git) about my fzf+git key
bindings a few years ago. I'm going to show them here again, because they are
seriously useful.
### Files listed in `git status`
<kbd>CTRL-G</kbd><kbd>CTRL-F</kbd>
![image](https://user-images.githubusercontent.com/700826/113473779-a9d93b00-94a6-11eb-87b5-f62a8d0a0efc.png)
### Branches
<kbd>CTRL-G</kbd><kbd>CTRL-B</kbd>
![image](https://user-images.githubusercontent.com/700826/113473758-87dfb880-94a6-11eb-82f4-9218103f10bd.png)
### Commit hashes
<kbd>CTRL-G</kbd><kbd>CTRL-H</kbd>
![image](https://user-images.githubusercontent.com/700826/113473765-91692080-94a6-11eb-8d38-ed4d41f27ac1.png)
The full source code can be found [here](https://gist.github.com/junegunn/8b572b8d4b5eddd8b85e5f4d40f17236).
Color themes
------------
You can customize how fzf colors the text elements with `--color` option. Here
are a few color themes. Note that you need a terminal emulator that can
display 24-bit colors.
```sh
# junegunn/seoul256.vim (dark)
export FZF_DEFAULT_OPTS='--color=bg+:#3F3F3F,bg:#4B4B4B,border:#6B6B6B,spinner:#98BC99,hl:#719872,fg:#D9D9D9,header:#719872,info:#BDBB72,pointer:#E12672,marker:#E17899,fg+:#D9D9D9,preview-bg:#3F3F3F,prompt:#98BEDE,hl+:#98BC99'
```
![seoul256](https://user-images.githubusercontent.com/700826/113475011-2c192d80-94ae-11eb-9d17-1e5867bae01f.png)
```sh
# junegunn/seoul256.vim (light)
export FZF_DEFAULT_OPTS='--color=bg+:#D9D9D9,bg:#E1E1E1,border:#C8C8C8,spinner:#719899,hl:#719872,fg:#616161,header:#719872,info:#727100,pointer:#E12672,marker:#E17899,fg+:#616161,preview-bg:#D9D9D9,prompt:#0099BD,hl+:#719899'
```
![seoul256-light](https://user-images.githubusercontent.com/700826/113475022-389d8600-94ae-11eb-905f-0939dd535837.png)
```sh
# morhetz/gruvbox
export FZF_DEFAULT_OPTS='--color=bg+:#3c3836,bg:#32302f,spinner:#fb4934,hl:#928374,fg:#ebdbb2,header:#928374,info:#8ec07c,pointer:#fb4934,marker:#fb4934,fg+:#ebdbb2,prompt:#fb4934,hl+:#fb4934'
```
![gruvbox](https://user-images.githubusercontent.com/700826/113475042-494dfc00-94ae-11eb-9322-cd03a027305a.png)
```sh
# arcticicestudio/nord-vim
export FZF_DEFAULT_OPTS='--color=bg+:#3B4252,bg:#2E3440,spinner:#81A1C1,hl:#616E88,fg:#D8DEE9,header:#616E88,info:#81A1C1,pointer:#81A1C1,marker:#81A1C1,fg+:#D8DEE9,prompt:#81A1C1,hl+:#81A1C1'
```
![nord](https://user-images.githubusercontent.com/700826/113475063-67b3f780-94ae-11eb-9b24-5f0d22b63399.png)
```sh
# tomasr/molokai
export FZF_DEFAULT_OPTS='--color=bg+:#293739,bg:#1B1D1E,border:#808080,spinner:#E6DB74,hl:#7E8E91,fg:#F8F8F2,header:#7E8E91,info:#A6E22E,pointer:#A6E22E,marker:#F92672,fg+:#F8F8F2,prompt:#F92672,hl+:#F92672'
```
![molokai](https://user-images.githubusercontent.com/700826/113475085-8619f300-94ae-11eb-85e4-2766fc3246bf.png)
### Generating fzf color theme from Vim color schemes
The Vim plugin of fzf can generate `--color` option from the current color
scheme according to `g:fzf_colors` variable. You can find the detailed
explanation [here](https://github.com/junegunn/fzf/blob/master/README-VIM.md#explanation-of-gfzf_colors).
Here is an example. Add this to your Vim configuration file.
```vim
let g:fzf_colors =
\ { 'fg': ['fg', 'Normal'],
\ 'bg': ['bg', 'Normal'],
\ 'preview-bg': ['bg', 'NormalFloat'],
\ 'hl': ['fg', 'Comment'],
\ 'fg+': ['fg', 'CursorLine', 'CursorColumn', 'Normal'],
\ 'bg+': ['bg', 'CursorLine', 'CursorColumn'],
\ 'hl+': ['fg', 'Statement'],
\ 'info': ['fg', 'PreProc'],
\ 'border': ['fg', 'Ignore'],
\ 'prompt': ['fg', 'Conditional'],
\ 'pointer': ['fg', 'Exception'],
\ 'marker': ['fg', 'Keyword'],
\ 'spinner': ['fg', 'Label'],
\ 'header': ['fg', 'Comment'] }
```
Then you can see how the `--color` option is generated by printing the result
of `fzf#wrap()`.
```vim
:echo fzf#wrap()
```
Use this command to append `export FZF_DEFAULT_OPTS="..."` line to the end of
the current file.
```vim
:call append('$', printf('export FZF_DEFAULT_OPTS="%s"', matchstr(fzf#wrap().options, "--color[^']*")))
```

View File

@@ -1,6 +1,31 @@
CHANGELOG CHANGELOG
========= =========
0.27.2
------
- 16 base ANSI colors can be specified by their names
```sh
fzf --color fg:3,fg+:11
fzf --color fg:yellow,fg+:bright-yellow
```
- Fix bug where `--read0` not properly displaying long lines
0.27.1
------
- Added `unbind` action. In the following Ripgrep launcher example, you can
use `unbind(reload)` to switch to fzf-only filtering mode.
- See https://github.com/junegunn/fzf/blob/master/ADVANCED.md#switching-to-fzf-only-search-mode
- Vim plugin
- Vim plugin will stop immediately even when the source command hasn't finished
```vim
" fzf will read the stream file while allowing other processes to append to it
call fzf#run({'source': 'cat /dev/null > /tmp/stream; tail -f /tmp/stream'})
```
- It is now possible to open popup window relative to the currrent window
```vim
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true, 'yoffset': 1.0 } }
```
0.27.0 0.27.0
------ ------
- More border options for `--preview-window` - More border options for `--preview-window`

View File

@@ -127,9 +127,15 @@ let g:fzf_action = {
\ 'ctrl-v': 'vsplit' } \ 'ctrl-v': 'vsplit' }
" Default fzf layout " Default fzf layout
" - Popup window " - Popup window (center of the screen)
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } } let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
" - Popup window (center of the current window)
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true } }
" - Popup window (anchored to the bottom of the current window)
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true, 'yoffset': 1.0 } }
" - down / up / left / right " - down / up / left / right
let g:fzf_layout = { 'down': '40%' } let g:fzf_layout = { 'down': '40%' }
@@ -302,6 +308,7 @@ following options are allowed:
- Optional: - Optional:
- `yoffset` [float default 0.5 range [0 ~ 1]] - `yoffset` [float default 0.5 range [0 ~ 1]]
- `xoffset` [float default 0.5 range [0 ~ 1]] - `xoffset` [float default 0.5 range [0 ~ 1]]
- `relative` [boolean default v:false]
- `border` [string default `rounded`]: Border style - `border` [string default `rounded`]: Border style
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right` / `no[ne]` - `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right` / `no[ne]`
@@ -392,15 +399,41 @@ Tips
### fzf inside terminal buffer ### fzf inside terminal buffer
The latest versions of Vim and Neovim include builtin terminal emulator On the latest versions of Vim and Neovim, fzf will start in a terminal buffer.
(`:terminal`) and fzf will start in a terminal buffer in the following cases: If you find the default ANSI colors to be different, consider configuring the
colors using `g:terminal_ansi_colors` in regular Vim or `g:terminal_color_x`
in Neovim.
- On Neovim ```vim
- On GVim " Terminal colors for seoul256 color scheme
- On Terminal Vim with a non-default layout if has('nvim')
- `call fzf#run({'left': '30%'})` or `let g:fzf_layout = {'left': '30%'}` let g:terminal_color_0 = '#4e4e4e'
let g:terminal_color_1 = '#d68787'
let g:terminal_color_2 = '#5f865f'
let g:terminal_color_3 = '#d8af5f'
let g:terminal_color_4 = '#85add4'
let g:terminal_color_5 = '#d7afaf'
let g:terminal_color_6 = '#87afaf'
let g:terminal_color_7 = '#d0d0d0'
let g:terminal_color_8 = '#626262'
let g:terminal_color_9 = '#d75f87'
let g:terminal_color_10 = '#87af87'
let g:terminal_color_11 = '#ffd787'
let g:terminal_color_12 = '#add4fb'
let g:terminal_color_13 = '#ffafaf'
let g:terminal_color_14 = '#87d7d7'
let g:terminal_color_15 = '#e4e4e4'
else
let g:terminal_ansi_colors = [
\ '#4e4e4e', '#d68787', '#5f865f', '#d8af5f',
\ '#85add4', '#d7afaf', '#87afaf', '#d0d0d0',
\ '#626262', '#d75f87', '#87af87', '#ffd787',
\ '#add4fb', '#ffafaf', '#87d7d7', '#e4e4e4'
\ ]
endif
```
#### Starting fzf in a popup window ### Starting fzf in a popup window
```vim ```vim
" Required: " Required:
@@ -410,6 +443,7 @@ The latest versions of Vim and Neovim include builtin terminal emulator
" Optional: " Optional:
" - xoffset [float default 0.5 range [0 ~ 1]] " - xoffset [float default 0.5 range [0 ~ 1]]
" - yoffset [float default 0.5 range [0 ~ 1]] " - yoffset [float default 0.5 range [0 ~ 1]]
" - relative [boolean default v:false]
" - border [string default 'rounded']: Border style " - border [string default 'rounded']: Border style
" - 'rounded' / 'sharp' / 'horizontal' / 'vertical' / 'top' / 'bottom' / 'left' / 'right' " - 'rounded' / 'sharp' / 'horizontal' / 'vertical' / 'top' / 'bottom' / 'left' / 'right'
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } } let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
@@ -427,21 +461,21 @@ else
endif endif
``` ```
#### Hide statusline ### Hide statusline
When fzf starts in a terminal buffer, the file type of the buffer is set to When fzf starts in a terminal buffer, the file type of the buffer is set to
`fzf`. So you can set up `FileType fzf` autocmd to customize the settings of `fzf`. So you can set up `FileType fzf` autocmd to customize the settings of
the window. the window.
For example, if you use a non-popup layout (e.g. `{'down': '40%'}`) on Neovim, For example, if you open fzf on the bottom on the screen (e.g. `{'down':
you might want to temporarily disable the statusline for a cleaner look. '40%'}`), you might want to temporarily disable the statusline for a cleaner
look.
```vim ```vim
if has('nvim') && !exists('g:fzf_layout') let g:fzf_layout = { 'down': '30%' }
autocmd! FileType fzf autocmd! FileType fzf
autocmd FileType fzf set laststatus=0 noshowmode noruler autocmd FileType fzf set laststatus=0 noshowmode noruler
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler \| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
endif
``` ```
[License](LICENSE) [License](LICENSE)

View File

@@ -121,6 +121,7 @@ git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
| Nix | NixOS, etc. | `nix-env -iA nixpkgs.fzf` | | Nix | NixOS, etc. | `nix-env -iA nixpkgs.fzf` |
| Pacman | Arch Linux | `sudo pacman -S fzf` | | Pacman | Arch Linux | `sudo pacman -S fzf` |
| pkg | FreeBSD | `pkg install fzf` | | pkg | FreeBSD | `pkg install fzf` |
| pkgin | NetBSD | `pkgin install fzf` |
| pkg_add | OpenBSD | `pkg_add fzf` | | pkg_add | OpenBSD | `pkg_add fzf` |
| XBPS | Void Linux | `sudo xbps-install -S fzf` | | XBPS | Void Linux | `sudo xbps-install -S fzf` |
| Zypper | openSUSE | `sudo zypper install fzf` | | Zypper | openSUSE | `sudo zypper install fzf` |
@@ -289,9 +290,10 @@ If you learn by watching videos, check out this screencast by [@samoshkin](https
Examples Examples
-------- --------
Many useful examples can be found on [the wiki * [Wiki page of examples](https://github.com/junegunn/fzf/wiki/examples)
page](https://github.com/junegunn/fzf/wiki/examples). Feel free to add your * *Disclaimer: The examples on this page are maintained by the community
own as well. and are not thoroughly tested*
* [Advanced fzf examples](https://github.com/junegunn/fzf/blob/master/ADVANCED.md)
`fzf-tmux` script `fzf-tmux` script
----------------- -----------------

View File

@@ -1,4 +1,4 @@
fzf.txt fzf Last change: January 3 2021 fzf.txt fzf Last change: May 19 2021
FZF - TABLE OF CONTENTS *fzf* *fzf-toc* FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
============================================================================== ==============================================================================
@@ -14,8 +14,8 @@ FZF - TABLE OF CONTENTS *fzf* *fzf-to
Global options supported by fzf#wrap |fzf-global-options-supported-by-fzf#wrap| Global options supported by fzf#wrap |fzf-global-options-supported-by-fzf#wrap|
Tips |fzf-tips| Tips |fzf-tips|
fzf inside terminal buffer |fzf-inside-terminal-buffer| fzf inside terminal buffer |fzf-inside-terminal-buffer|
Starting fzf in a popup window |fzf-starting-fzf-in-a-popup-window| Starting fzf in a popup window |fzf-starting-fzf-in-a-popup-window|
Hide statusline |fzf-hide-statusline| Hide statusline |fzf-hide-statusline|
License |fzf-license| License |fzf-license|
FZF VIM INTEGRATION *fzf-vim-integration* FZF VIM INTEGRATION *fzf-vim-integration*
@@ -155,9 +155,15 @@ Examples~
\ 'ctrl-v': 'vsplit' } \ 'ctrl-v': 'vsplit' }
" Default fzf layout " Default fzf layout
" - Popup window " - Popup window (center of the screen)
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } } let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
" - Popup window (center of the current window)
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true } }
" - Popup window (anchored to the bottom of the current window)
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true, 'yoffset': 1.0 } }
" - down / up / left / right " - down / up / left / right
let g:fzf_layout = { 'down': '40%' } let g:fzf_layout = { 'down': '40%' }
@@ -318,6 +324,7 @@ following options are allowed:
- Optional: - Optional:
- `yoffset` [float default 0.5 range [0 ~ 1]] - `yoffset` [float default 0.5 range [0 ~ 1]]
- `xoffset` [float default 0.5 range [0 ~ 1]] - `xoffset` [float default 0.5 range [0 ~ 1]]
- `relative` [boolean default v:false]
- `border` [string default `rounded`]: Border style - `border` [string default `rounded`]: Border style
- `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right` / `no[ne]` - `rounded` / `sharp` / `horizontal` / `vertical` / `top` / `bottom` / `left` / `right` / `no[ne]`
@@ -372,7 +379,7 @@ last `fullscreen` argument of `fzf#wrap` (see :help <bang>).
Our `:LS` command will be much more useful if we can pass a directory argument Our `:LS` command will be much more useful if we can pass a directory argument
to it, so that something like `:LS/tmp` is possible. to it, so that something like `:LS/tmp` is possible.
> >
command! -bang -complete=dir -nargs=* LS command! -bang -complete=dir -nargs=? LS
\ call fzf#run(fzf#wrap({'source': 'ls', 'dir': <q-args>}, <bang>0)) \ call fzf#run(fzf#wrap({'source': 'ls', 'dir': <q-args>}, <bang>0))
< <
Lastly, if you have enabled `g:fzf_history_dir`, you might want to assign a Lastly, if you have enabled `g:fzf_history_dir`, you might want to assign a
@@ -380,7 +387,7 @@ unique name to our command and pass it as the first argument to `fzf#wrap`.
> >
" The query history for this command will be stored as 'ls' inside g:fzf_history_dir. " The query history for this command will be stored as 'ls' inside g:fzf_history_dir.
" The name is ignored if g:fzf_history_dir is not defined. " The name is ignored if g:fzf_history_dir is not defined.
command! -bang -complete=dir -nargs=* LS command! -bang -complete=dir -nargs=? LS
\ call fzf#run(fzf#wrap('ls', {'source': 'ls', 'dir': <q-args>}, <bang>0)) \ call fzf#run(fzf#wrap('ls', {'source': 'ls', 'dir': <q-args>}, <bang>0))
< <
@@ -412,8 +419,46 @@ The latest versions of Vim and Neovim include builtin terminal emulator
- On Terminal Vim with a non-default layout - On Terminal Vim with a non-default layout
- `callfzf#run({'left':'30%'})` or `letg:fzf_layout={'left':'30%'}` - `callfzf#run({'left':'30%'})` or `letg:fzf_layout={'left':'30%'}`
On the latest versions of Vim and Neovim, fzf will start in a terminal buffer.
If you find the default ANSI colors to be different, consider configuring the
colors using `g:terminal_ansi_colors` in regular Vim or `g:terminal_color_x`
in Neovim.
Starting fzf in a popup window~ *g:terminal_color_15* *g:terminal_color_14* *g:terminal_color_13*
*g:terminal_color_12* *g:terminal_color_11* *g:terminal_color_10* *g:terminal_color_9*
*g:terminal_color_8* *g:terminal_color_7* *g:terminal_color_6* *g:terminal_color_5*
*g:terminal_color_4* *g:terminal_color_3* *g:terminal_color_2* *g:terminal_color_1*
*g:terminal_color_0*
>
" Terminal colors for seoul256 color scheme
if has('nvim')
let g:terminal_color_0 = '#4e4e4e'
let g:terminal_color_1 = '#d68787'
let g:terminal_color_2 = '#5f865f'
let g:terminal_color_3 = '#d8af5f'
let g:terminal_color_4 = '#85add4'
let g:terminal_color_5 = '#d7afaf'
let g:terminal_color_6 = '#87afaf'
let g:terminal_color_7 = '#d0d0d0'
let g:terminal_color_8 = '#626262'
let g:terminal_color_9 = '#d75f87'
let g:terminal_color_10 = '#87af87'
let g:terminal_color_11 = '#ffd787'
let g:terminal_color_12 = '#add4fb'
let g:terminal_color_13 = '#ffafaf'
let g:terminal_color_14 = '#87d7d7'
let g:terminal_color_15 = '#e4e4e4'
else
let g:terminal_ansi_colors = [
\ '#4e4e4e', '#d68787', '#5f865f', '#d8af5f',
\ '#85add4', '#d7afaf', '#87afaf', '#d0d0d0',
\ '#626262', '#d75f87', '#87af87', '#ffd787',
\ '#add4fb', '#ffafaf', '#87d7d7', '#e4e4e4'
\ ]
endif
<
< Starting fzf in a popup window >____________________________________________~
*fzf-starting-fzf-in-a-popup-window* *fzf-starting-fzf-in-a-popup-window*
> >
" Required: " Required:
@@ -423,6 +468,7 @@ Starting fzf in a popup window~
" Optional: " Optional:
" - xoffset [float default 0.5 range [0 ~ 1]] " - xoffset [float default 0.5 range [0 ~ 1]]
" - yoffset [float default 0.5 range [0 ~ 1]] " - yoffset [float default 0.5 range [0 ~ 1]]
" - relative [boolean default v:false]
" - border [string default 'rounded']: Border style " - border [string default 'rounded']: Border style
" - 'rounded' / 'sharp' / 'horizontal' / 'vertical' / 'top' / 'bottom' / 'left' / 'right' " - 'rounded' / 'sharp' / 'horizontal' / 'vertical' / 'top' / 'bottom' / 'left' / 'right'
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } } let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
@@ -438,21 +484,21 @@ or above) by putting fzf-tmux options in `tmux` key.
endif endif
< <
Hide statusline~ < Hide statusline >___________________________________________________________~
*fzf-hide-statusline* *fzf-hide-statusline*
When fzf starts in a terminal buffer, the file type of the buffer is set to When fzf starts in a terminal buffer, the file type of the buffer is set to
`fzf`. So you can set up `FileTypefzf` autocmd to customize the settings of `fzf`. So you can set up `FileTypefzf` autocmd to customize the settings of
the window. the window.
For example, if you use a non-popup layout (e.g. `{'down':'40%'}`) on Neovim, For example, if you open fzf on the bottom on the screen (e.g. `{'down':
you might want to temporarily disable the statusline for a cleaner look. '40%'}`), you might want to temporarily disable the statusline for a cleaner
look.
> >
if has('nvim') && !exists('g:fzf_layout') let g:fzf_layout = { 'down': '30%' }
autocmd! FileType fzf autocmd! FileType fzf
autocmd FileType fzf set laststatus=0 noshowmode noruler autocmd FileType fzf set laststatus=0 noshowmode noruler
\| autocmd BufLeave <buffer> set laststatus=2 showmode ruler \| autocmd BufLeave <buffer> set laststatus=2 showmode ruler
endif
< <
LICENSE *fzf-license* LICENSE *fzf-license*

2
go.mod
View File

@@ -6,7 +6,7 @@ require (
github.com/mattn/go-isatty v0.0.12 github.com/mattn/go-isatty v0.0.12
github.com/mattn/go-runewidth v0.0.12 github.com/mattn/go-runewidth v0.0.12
github.com/mattn/go-shellwords v1.0.11 github.com/mattn/go-shellwords v1.0.11
github.com/rivo/uniseg v0.2.0 // indirect github.com/rivo/uniseg v0.2.0
github.com/saracen/walker v0.1.2 github.com/saracen/walker v0.1.2
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57

View File

@@ -2,7 +2,7 @@
set -u set -u
version=0.27.0 version=0.27.2
auto_completion= auto_completion=
key_bindings= key_bindings=
update_config=2 update_config=2

View File

@@ -1,4 +1,4 @@
$version="0.27.0" $version="0.27.2"
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition $fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition

View File

@@ -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 "Apr 2021" "fzf 0.27.0" "fzf-tmux - open fzf in tmux split pane" .TH fzf-tmux 1 "Jun 2021" "fzf 0.27.2" "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

View File

@@ -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 "Apr 2021" "fzf 0.27.0" "fzf - a command-line fuzzy finder" .TH fzf 1 "Jun 2021" "fzf 0.27.2" "fzf - a command-line fuzzy finder"
.SH NAME .SH NAME
fzf - a command-line fuzzy finder fzf - a command-line fuzzy finder
@@ -340,6 +340,22 @@ color mappings.
\fB-1 \fRDefault terminal foreground/background color \fB-1 \fRDefault terminal foreground/background color
\fB \fR(or the original color of the text) \fB \fR(or the original color of the text)
\fB0 ~ 15 \fR16 base colors \fB0 ~ 15 \fR16 base colors
\fBblack\fR
\fBred\fR
\fBgreen\fR
\fByellow\fR
\fBblue\fR
\fBmagenta\fR
\fBcyan\fR
\fBwhite\fR
\fBbright-black\fR (gray | grey)
\fBbright-red\fR
\fBbright-green\fR
\fBbright-yellow\fR
\fBbright-blue\fR
\fBbright-magenta\fR
\fBbright-cyan\fR
\fBbright-white\fR
\fB16 ~ 255 \fRANSI 256 colors \fB16 ~ 255 \fRANSI 256 colors
\fB#rrggbb \fR24-bit colors \fB#rrggbb \fR24-bit colors
@@ -532,10 +548,12 @@ e.g.
Start the finder with the given query Start the finder with the given query
.TP .TP
.B "-1, --select-1" .B "-1, --select-1"
Automatically select the only match If there is only one match for the initial query (\fB--query\fR), do not start
interactive finder and automatically select the only match
.TP .TP
.B "-0, --exit-0" .B "-0, --exit-0"
Exit immediately when there's no match If there is no match for the initial query (\fB--query\fR), do not start
interactive finder and exit immediately
.TP .TP
.BI "-f, --filter=" "STR" .BI "-f, --filter=" "STR"
Filter mode. Do not start interactive finder. When used with \fB--no-sort\fR, Filter mode. Do not start interactive finder. When used with \fB--no-sort\fR,
@@ -850,6 +868,7 @@ 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
\fBunbind(...)\fR (unbind bindings)
\fBunix-line-discard\fR \fIctrl-u\fR \fBunix-line-discard\fR \fIctrl-u\fR
\fBunix-word-rubout\fR \fIctrl-w\fR \fBunix-word-rubout\fR \fIctrl-w\fR
\fBup\fR \fIctrl-k ctrl-p up\fR \fBup\fR \fIctrl-k ctrl-p up\fR

View File

@@ -465,19 +465,19 @@ try
endif endif
if has_key(dict, 'source') if has_key(dict, 'source')
let source = dict.source let source = remove(dict, 'source')
let type = type(source) let type = type(source)
if type == 1 if type == 1
let prefix = '( '.source.' )|' let source_command = source
elseif type == 3 elseif type == 3
let temps.input = s:fzf_tempname() let temps.input = s:fzf_tempname()
call writefile(source, temps.input) call writefile(source, temps.input)
let prefix = (s:is_win ? 'type ' : 'cat ').fzf#shellescape(temps.input).'|' let source_command = (s:is_win ? 'type ' : 'cat ').fzf#shellescape(temps.input)
else else
throw 'Invalid source type' throw 'Invalid source type'
endif endif
else else
let prefix = '' let source_command = ''
endif endif
let prefer_tmux = get(g:, 'fzf_prefer_tmux', 0) || has_key(dict, 'tmux') let prefer_tmux = get(g:, 'fzf_prefer_tmux', 0) || has_key(dict, 'tmux')
@@ -487,20 +487,24 @@ try
let has_vim8_term = has('terminal') && has('patch-8.0.995') let has_vim8_term = has('terminal') && has('patch-8.0.995')
let has_nvim_term = has('nvim-0.2.1') || has('nvim') && !s:is_win let has_nvim_term = has('nvim-0.2.1') || has('nvim') && !s:is_win
let use_term = has_nvim_term || let use_term = has_nvim_term ||
\ has_vim8_term && !has('win32unix') && (has('gui_running') || s:is_win || !use_height && s:present(dict, 'down', 'up', 'left', 'right', 'window')) \ has_vim8_term && !has('win32unix') && (has('gui_running') || s:is_win || s:present(dict, 'down', 'up', 'left', 'right', 'window'))
let use_tmux = (has_key(dict, 'tmux') || (!use_height && !use_term || prefer_tmux) && !has('win32unix') && s:splittable(dict)) && s:tmux_enabled() let use_tmux = (has_key(dict, 'tmux') || (!use_height && !use_term || prefer_tmux) && !has('win32unix') && s:splittable(dict)) && s:tmux_enabled()
if prefer_tmux && use_tmux if prefer_tmux && use_tmux
let use_height = 0 let use_height = 0
let use_term = 0 let use_term = 0
endif endif
if use_height if use_term
let optstr .= ' --no-height'
elseif use_height
let height = s:calc_size(&lines, dict.down, dict) let height = s:calc_size(&lines, dict.down, dict)
let optstr .= ' --height='.height let optstr .= ' --height='.height
elseif use_term
let optstr .= ' --no-height'
endif endif
let optstr .= s:border_opt(get(dict, 'window', 0)) let optstr .= s:border_opt(get(dict, 'window', 0))
let command = prefix.(use_tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result let prev_default_command = $FZF_DEFAULT_COMMAND
if len(source_command)
let $FZF_DEFAULT_COMMAND = source_command
endif
let command = (use_tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
if use_term if use_term
return s:execute_term(dict, command, temps) return s:execute_term(dict, command, temps)
@@ -511,6 +515,13 @@ try
call s:callback(dict, lines) call s:callback(dict, lines)
return lines return lines
finally finally
if len(source_command)
if len(prev_default_command)
let $FZF_DEFAULT_COMMAND = prev_default_command
else
execute 'unlet $FZF_DEFAULT_COMMAND'
endif
endif
let [&shell, &shellslash, &shellcmdflag, &shellxquote] = [shell, shellslash, shellcmdflag, shellxquote] let [&shell, &shellslash, &shellcmdflag, &shellxquote] = [shell, shellslash, shellcmdflag, shellxquote]
endtry endtry
endfunction endfunction
@@ -540,8 +551,8 @@ function! s:fzf_tmux(dict)
endif endif
endfor endfor
endif endif
return printf('LINES=%d COLUMNS=%d %s %s %s --', return printf('LINES=%d COLUMNS=%d %s %s - --',
\ &lines, &columns, fzf#shellescape(s:fzf_tmux), size, (has_key(a:dict, 'source') ? '' : '-')) \ &lines, &columns, fzf#shellescape(s:fzf_tmux), size)
endfunction endfunction
function! s:splittable(dict) function! s:splittable(dict)
@@ -671,8 +682,7 @@ function! s:execute(dict, command, use_height, temps) abort
let a:temps.shellscript = shellscript let a:temps.shellscript = shellscript
endif endif
if a:use_height if a:use_height
let stdin = has_key(a:dict, 'source') ? '' : '< /dev/tty' call system(printf('tput cup %d > /dev/tty; tput cnorm > /dev/tty; %s < /dev/tty 2> /dev/tty', &lines, command))
call system(printf('tput cup %d > /dev/tty; tput cnorm > /dev/tty; %s %s 2> /dev/tty', &lines, command, stdin))
else else
execute 'silent !'.command execute 'silent !'.command
endif endif
@@ -972,11 +982,19 @@ else
endif endif
function! s:popup(opts) abort function! s:popup(opts) abort
let xoffset = get(a:opts, 'xoffset', 0.5)
let yoffset = get(a:opts, 'yoffset', 0.5)
let relative = get(a:opts, 'relative', 0)
" Use current window size for positioning relatively positioned popups
let columns = relative ? winwidth(0) : &columns
let lines = relative ? winheight(0) : (&lines - has('nvim'))
" Size and position " Size and position
let width = min([max([8, a:opts.width > 1 ? a:opts.width : float2nr(&columns * a:opts.width)]), &columns]) let width = min([max([8, a:opts.width > 1 ? a:opts.width : float2nr(columns * a:opts.width)]), columns])
let height = min([max([4, a:opts.height > 1 ? a:opts.height : float2nr(&lines * a:opts.height)]), &lines - has('nvim')]) let height = min([max([4, a:opts.height > 1 ? a:opts.height : float2nr(lines * a:opts.height)]), lines])
let row = float2nr(get(a:opts, 'yoffset', 0.5) * (&lines - height)) let row = float2nr(yoffset * (lines - height)) + (relative ? win_screenpos(0)[0] - 1 : 0)
let col = float2nr(get(a:opts, 'xoffset', 0.5) * (&columns - width)) let col = float2nr(xoffset * (columns - width)) + (relative ? win_screenpos(0)[1] - 1 : 0)
" Managing the differences " Managing the differences
let row = min([max([0, row]), &lines - has('nvim') - height]) let row = min([max([0, row]), &lines - has('nvim') - height])

View File

@@ -670,6 +670,38 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
cattr.Attr |= tui.Blink cattr.Attr |= tui.Blink
case "reverse": case "reverse":
cattr.Attr |= tui.Reverse cattr.Attr |= tui.Reverse
case "black":
cattr.Color = tui.Color(0)
case "red":
cattr.Color = tui.Color(1)
case "green":
cattr.Color = tui.Color(2)
case "yellow":
cattr.Color = tui.Color(3)
case "blue":
cattr.Color = tui.Color(4)
case "magenta":
cattr.Color = tui.Color(5)
case "cyan":
cattr.Color = tui.Color(6)
case "white":
cattr.Color = tui.Color(7)
case "bright-black", "gray", "grey":
cattr.Color = tui.Color(8)
case "bright-red":
cattr.Color = tui.Color(9)
case "bright-green":
cattr.Color = tui.Color(10)
case "bright-yellow":
cattr.Color = tui.Color(11)
case "bright-blue":
cattr.Color = tui.Color(12)
case "bright-magenta":
cattr.Color = tui.Color(13)
case "bright-cyan":
cattr.Color = tui.Color(14)
case "bright-white":
cattr.Color = tui.Color(15)
case "": case "":
default: default:
if rrggbb.MatchString(component) { if rrggbb.MatchString(component) {
@@ -748,7 +780,7 @@ func init() {
// Backreferences are not supported. // Backreferences are not supported.
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|') // "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
executeRegexp = regexp.MustCompile( executeRegexp = regexp.MustCompile(
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`) `(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|unbind):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|unbind)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
} }
func parseKeymap(keymap map[tui.Event][]action, str string) { func parseKeymap(keymap map[tui.Event][]action, str string) {
@@ -762,6 +794,8 @@ func parseKeymap(keymap map[tui.Event][]action, str string) {
prefix = symbol + "reload" prefix = symbol + "reload"
} else if strings.HasPrefix(src[1:], "preview") { } else if strings.HasPrefix(src[1:], "preview") {
prefix = symbol + "preview" prefix = symbol + "preview"
} else if strings.HasPrefix(src[1:], "unbind") {
prefix = symbol + "unbind"
} else if strings.HasPrefix(src[1:], "change-prompt") { } else if strings.HasPrefix(src[1:], "change-prompt") {
prefix = symbol + "change-prompt" prefix = symbol + "change-prompt"
} else if src[len(prefix)] == '-' { } else if src[len(prefix)] == '-' {
@@ -957,6 +991,8 @@ func parseKeymap(keymap map[tui.Event][]action, str string) {
offset = len("preview") offset = len("preview")
case actChangePrompt: case actChangePrompt:
offset = len("change-prompt") offset = len("change-prompt")
case actUnbind:
offset = len("unbind")
case actExecuteSilent: case actExecuteSilent:
offset = len("execute-silent") offset = len("execute-silent")
case actExecuteMulti: case actExecuteMulti:
@@ -964,15 +1000,21 @@ func parseKeymap(keymap map[tui.Event][]action, str string) {
default: default:
offset = len("execute") offset = len("execute")
} }
var actionArg string
if spec[offset] == ':' { if spec[offset] == ':' {
if specIndex == len(specs)-1 { if specIndex == len(specs)-1 {
actions = append(actions, action{t: t, a: spec[offset+1:]}) actionArg = spec[offset+1:]
actions = append(actions, action{t: t, a: actionArg})
} else { } else {
prevSpec = spec + "+" prevSpec = spec + "+"
continue continue
} }
} else { } else {
actions = append(actions, action{t: t, a: spec[offset+1 : len(spec)-1]}) actionArg = spec[offset+1 : len(spec)-1]
actions = append(actions, action{t: t, a: actionArg})
}
if t == actUnbind {
parseKeyChords(actionArg, "unbind target required")
} }
} }
} }
@@ -994,6 +1036,8 @@ func isExecuteAction(str string) actionType {
switch prefix { switch prefix {
case "reload": case "reload":
return actReload return actReload
case "unbind":
return actUnbind
case "preview": case "preview":
return actPreview return actPreview
case "change-prompt": case "change-prompt":
@@ -1536,15 +1580,13 @@ func validateSign(sign string, signOptName string) error {
if sign == "" { if sign == "" {
return fmt.Errorf("%v cannot be empty", signOptName) return fmt.Errorf("%v cannot be empty", signOptName)
} }
widthSum := 0
for _, r := range sign { for _, r := range sign {
if !unicode.IsGraphic(r) { if !unicode.IsGraphic(r) {
return fmt.Errorf("invalid character in %v", signOptName) return fmt.Errorf("invalid character in %v", signOptName)
} }
widthSum += runewidth.RuneWidth(r) }
if widthSum > 2 { if runewidth.StringWidth(sign) > 2 {
return fmt.Errorf("%v display width should be up to 2", signOptName) return fmt.Errorf("%v display width should be up to 2", signOptName)
}
} }
return nil return nil
} }

View File

@@ -2,7 +2,6 @@ package fzf
import ( import (
"bufio" "bufio"
"bytes"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@@ -15,6 +14,9 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
"github.com/junegunn/fzf/src/tui" "github.com/junegunn/fzf/src/tui"
"github.com/junegunn/fzf/src/util" "github.com/junegunn/fzf/src/util"
) )
@@ -282,6 +284,7 @@ const (
actEnableSearch actEnableSearch
actSelect actSelect
actDeselect actDeselect
actUnbind
) )
type placeholderFlags struct { type placeholderFlags struct {
@@ -673,11 +676,8 @@ func (t *Terminal) sortSelected() []selectedItem {
} }
func (t *Terminal) displayWidth(runes []rune) int { func (t *Terminal) displayWidth(runes []rune) int {
l := 0 width, _ := util.RunesWidth(runes, 0, t.tabstop, 0)
for _, r := range runes { return width
l += util.RuneWidth(r, l, t.tabstop)
}
return l
} }
const ( const (
@@ -1141,28 +1141,18 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool) {
t.prevLines[i] = newLine t.prevLines[i] = newLine
} }
func (t *Terminal) trimRight(runes []rune, width int) ([]rune, int) { func (t *Terminal) trimRight(runes []rune, width int) ([]rune, bool) {
// We start from the beginning to handle tab characters // We start from the beginning to handle tab characters
l := 0 width, overflowIdx := util.RunesWidth(runes, 0, t.tabstop, width)
for idx, r := range runes { if overflowIdx >= 0 {
l += util.RuneWidth(r, l, t.tabstop) return runes[:overflowIdx], true
if l > width {
return runes[:idx], len(runes) - idx
}
} }
return runes, 0 return runes, false
} }
func (t *Terminal) displayWidthWithLimit(runes []rune, prefixWidth int, limit int) int { func (t *Terminal) displayWidthWithLimit(runes []rune, prefixWidth int, limit int) int {
l := 0 width, _ := util.RunesWidth(runes, prefixWidth, t.tabstop, limit)
for _, r := range runes { return width
l += util.RuneWidth(r, l+prefixWidth, t.tabstop)
if l > limit {
// Early exit
return l
}
}
return l
} }
func (t *Terminal) trimLeft(runes []rune, width int) ([]rune, int32) { func (t *Terminal) trimLeft(runes []rune, width int) ([]rune, int32) {
@@ -1362,9 +1352,9 @@ func (t *Terminal) renderPreviewText(height int, lines []string, lineNo int, unc
prefixWidth := 0 prefixWidth := 0
_, _, ansi = extractColor(line, ansi, func(str string, ansi *ansiState) bool { _, _, ansi = extractColor(line, ansi, func(str string, ansi *ansiState) bool {
trimmed := []rune(str) trimmed := []rune(str)
trimmedLen := 0 isTrimmed := false
if !t.previewOpts.wrap { if !t.previewOpts.wrap {
trimmed, trimmedLen = t.trimRight(trimmed, maxWidth-t.pwindow.X()) trimmed, isTrimmed = t.trimRight(trimmed, maxWidth-t.pwindow.X())
} }
str, width := t.processTabs(trimmed, prefixWidth) str, width := t.processTabs(trimmed, prefixWidth)
prefixWidth += width prefixWidth += width
@@ -1374,7 +1364,7 @@ func (t *Terminal) renderPreviewText(height int, lines []string, lineNo int, unc
} else { } else {
fillRet = t.pwindow.CFill(tui.ColPreview.Fg(), tui.ColPreview.Bg(), tui.AttrRegular, str) fillRet = t.pwindow.CFill(tui.ColPreview.Fg(), tui.ColPreview.Bg(), tui.AttrRegular, str)
} }
return trimmedLen == 0 && return !isTrimmed &&
(fillRet == tui.FillContinue || t.previewOpts.wrap && fillRet == tui.FillNextLine) (fillRet == tui.FillContinue || t.previewOpts.wrap && fillRet == tui.FillNextLine)
}) })
t.previewer.scrollable = t.previewer.scrollable || t.pwindow.Y() == height-1 && t.pwindow.X() == t.pwindow.Width() t.previewer.scrollable = t.previewer.scrollable || t.pwindow.Y() == height-1 && t.pwindow.X() == t.pwindow.Width()
@@ -1430,16 +1420,21 @@ func (t *Terminal) printPreviewDelayed() {
} }
func (t *Terminal) processTabs(runes []rune, prefixWidth int) (string, int) { func (t *Terminal) processTabs(runes []rune, prefixWidth int) (string, int) {
var strbuf bytes.Buffer var strbuf strings.Builder
l := prefixWidth l := prefixWidth
for _, r := range runes { gr := uniseg.NewGraphemes(string(runes))
w := util.RuneWidth(r, l, t.tabstop) for gr.Next() {
l += w rs := gr.Runes()
if r == '\t' { str := string(rs)
var w int
if len(rs) == 1 && rs[0] == '\t' {
w = t.tabstop - l%t.tabstop
strbuf.WriteString(strings.Repeat(" ", w)) strbuf.WriteString(strings.Repeat(" ", w))
} else { } else {
strbuf.WriteRune(r) w = runewidth.StringWidth(str)
strbuf.WriteString(str)
} }
l += w
} }
return strbuf.String(), l return strbuf.String(), l
} }
@@ -2663,6 +2658,11 @@ func (t *Terminal) Loop() {
command := t.replacePlaceholder(a.a, false, string(t.input), list) command := t.replacePlaceholder(a.a, false, string(t.input), list)
newCommand = &command newCommand = &command
} }
case actUnbind:
keys := parseKeyChords(a.a, "PANIC")
for key := range keys {
delete(t.keymap, key)
}
} }
return true return true
} }

View File

@@ -10,7 +10,8 @@ import (
"time" "time"
"unicode/utf8" "unicode/utf8"
"github.com/junegunn/fzf/src/util" "github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
"golang.org/x/term" "golang.org/x/term"
) )
@@ -50,7 +51,7 @@ func (r *LightRenderer) stderrInternal(str string, allowNLCR bool) {
} }
bytes = bytes[sz:] bytes = bytes[sz:]
} }
r.queued += string(runes) r.queued.WriteString(string(runes))
} }
func (r *LightRenderer) csi(code string) { func (r *LightRenderer) csi(code string) {
@@ -58,9 +59,9 @@ func (r *LightRenderer) csi(code string) {
} }
func (r *LightRenderer) flush() { func (r *LightRenderer) flush() {
if len(r.queued) > 0 { if r.queued.Len() > 0 {
fmt.Fprint(os.Stderr, r.queued) fmt.Fprint(os.Stderr, r.queued.String())
r.queued = "" r.queued.Reset()
} }
} }
@@ -82,7 +83,7 @@ type LightRenderer struct {
escDelay int escDelay int
fullscreen bool fullscreen bool
upOneLine bool upOneLine bool
queued string queued strings.Builder
y int y int
x int x int
maxHeightFunc func(int) int maxHeightFunc func(int) int
@@ -889,20 +890,26 @@ func wrapLine(input string, prefixLength int, max int, tabstop int) []wrappedLin
lines := []wrappedLine{} lines := []wrappedLine{}
width := 0 width := 0
line := "" line := ""
for _, r := range input { gr := uniseg.NewGraphemes(input)
w := util.RuneWidth(r, prefixLength+width, 8) for gr.Next() {
width += w rs := gr.Runes()
str := string(r) str := string(rs)
if r == '\t' { var w int
if len(rs) == 1 && rs[0] == '\t' {
w = tabstop - (prefixLength+width)%tabstop
str = repeat(' ', w) str = repeat(' ', w)
} else {
w = runewidth.StringWidth(str)
} }
width += w
if prefixLength+width <= max { if prefixLength+width <= max {
line += str line += str
} else { } else {
lines = append(lines, wrappedLine{string(line), width - w}) lines = append(lines, wrappedLine{string(line), width - w})
line = str line = str
prefixLength = 0 prefixLength = 0
width = util.RuneWidth(r, prefixLength, 8) width = w
} }
} }
lines = append(lines, wrappedLine{string(line), width}) lines = append(lines, wrappedLine{string(line), width})

View File

@@ -5,7 +5,6 @@ package tui
import ( import (
"os" "os"
"time" "time"
"unicode/utf8"
"runtime" "runtime"
@@ -13,6 +12,7 @@ import (
"github.com/gdamore/tcell/encoding" "github.com/gdamore/tcell/encoding"
"github.com/mattn/go-runewidth" "github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
) )
func HasFullscreenRenderer() bool { func HasFullscreenRenderer() bool {
@@ -482,7 +482,6 @@ func (w *TcellWindow) Print(text string) {
} }
func (w *TcellWindow) printString(text string, pair ColorPair) { func (w *TcellWindow) printString(text string, pair ColorPair) {
t := text
lx := 0 lx := 0
a := pair.Attr() a := pair.Attr()
@@ -496,33 +495,28 @@ func (w *TcellWindow) printString(text string, pair ColorPair) {
Dim(a&Attr(tcell.AttrDim) != 0) Dim(a&Attr(tcell.AttrDim) != 0)
} }
for { gr := uniseg.NewGraphemes(text)
if len(t) == 0 { for gr.Next() {
break rs := gr.Runes()
}
r, size := utf8.DecodeRuneInString(t)
t = t[size:]
if r < rune(' ') { // ignore control characters if len(rs) == 1 {
continue r := rs[0]
} if r < rune(' ') { // ignore control characters
continue
if r == '\n' { } else if r == '\n' {
w.lastY++ w.lastY++
lx = 0 lx = 0
} else { continue
} else if r == '\u000D' { // skip carriage return
if r == '\u000D' { // skip carriage return
continue continue
} }
var xPos = w.left + w.lastX + lx
var yPos = w.top + w.lastY
if xPos < (w.left+w.width) && yPos < (w.top+w.height) {
_screen.SetContent(xPos, yPos, r, nil, style)
}
lx += runewidth.RuneWidth(r)
} }
var xPos = w.left + w.lastX + lx
var yPos = w.top + w.lastY
if xPos < (w.left+w.width) && yPos < (w.top+w.height) {
_screen.SetContent(xPos, yPos, rs[0], rs[1:], style)
}
lx += runewidth.StringWidth(string(rs))
} }
w.lastX += lx w.lastX += lx
} }
@@ -549,30 +543,32 @@ func (w *TcellWindow) fillString(text string, pair ColorPair) FillReturn {
Underline(a&Attr(tcell.AttrUnderline) != 0). Underline(a&Attr(tcell.AttrUnderline) != 0).
Italic(a&Attr(tcell.AttrItalic) != 0) Italic(a&Attr(tcell.AttrItalic) != 0)
for _, r := range text { gr := uniseg.NewGraphemes(text)
if r == '\n' { for gr.Next() {
rs := gr.Runes()
if len(rs) == 1 && rs[0] == '\n' {
w.lastY++ w.lastY++
w.lastX = 0 w.lastX = 0
lx = 0 lx = 0
} else { continue
var xPos = w.left + w.lastX + lx
// word wrap:
if xPos >= (w.left + w.width) {
w.lastY++
w.lastX = 0
lx = 0
xPos = w.left
}
var yPos = w.top + w.lastY
if yPos >= (w.top + w.height) {
return FillSuspend
}
_screen.SetContent(xPos, yPos, r, nil, style)
lx += runewidth.RuneWidth(r)
} }
// word wrap:
xPos := w.left + w.lastX + lx
if xPos >= (w.left + w.width) {
w.lastY++
w.lastX = 0
lx = 0
xPos = w.left
}
yPos := w.top + w.lastY
if yPos >= (w.top + w.height) {
return FillSuspend
}
_screen.SetContent(xPos, yPos, rs[0], rs[1:], style)
lx += runewidth.StringWidth(string(rs))
} }
w.lastX += lx w.lastX += lx
if w.lastX == w.width { if w.lastX == w.width {

View File

@@ -3,26 +3,35 @@ package util
import ( import (
"math" "math"
"os" "os"
"strings"
"time" "time"
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
"github.com/mattn/go-runewidth" "github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
) )
var _runeWidths = make(map[rune]int) // RunesWidth returns runes width
func RunesWidth(runes []rune, prefixWidth int, tabstop int, limit int) (int, int) {
// RuneWidth returns rune width width := 0
func RuneWidth(r rune, prefixWidth int, tabstop int) int { gr := uniseg.NewGraphemes(string(runes))
if r == '\t' { idx := 0
return tabstop - prefixWidth%tabstop for gr.Next() {
} else if w, found := _runeWidths[r]; found { rs := gr.Runes()
return w var w int
} else if r == '\n' || r == '\r' { if len(rs) == 1 && rs[0] == '\t' {
return 1 w = tabstop - (prefixWidth+width)%tabstop
} else {
s := string(rs)
w = runewidth.StringWidth(s) + strings.Count(s, "\n")
}
width += w
if limit > 0 && width > limit {
return width, idx
}
idx += len(rs)
} }
w := runewidth.RuneWidth(r) return width, -1
_runeWidths[r] = w
return w
} }
// Max returns the largest integer // Max returns the largest integer

View File

@@ -2042,6 +2042,17 @@ class TestGoFZF < TestBase
tmux.send_keys 'C-K' tmux.send_keys 'C-K'
tmux.until { |lines| assert_equal(%w[1 2 3 4 5], top5[lines]) } tmux.until { |lines| assert_equal(%w[1 2 3 4 5], top5[lines]) }
end end
def test_unbind
tmux.send_keys "seq 100 | #{FZF} --bind 'c:clear-query,d:unbind(c,d)'", :Enter
tmux.until { |lines| assert_equal 100, lines.item_count }
tmux.send_keys 'ab'
tmux.until { |lines| assert_equal '> ab', lines[-1] }
tmux.send_keys 'c'
tmux.until { |lines| assert_equal '>', lines[-1] }
tmux.send_keys 'dabcd'
tmux.until { |lines| assert_equal '> abcd', lines[-1] }
end
end end
module TestShell module TestShell