mirror of
https://github.com/junegunn/fzf.git
synced 2025-08-15 04:05:48 -07:00
Compare commits
89 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
d3311d9f43 | ||
|
3e1735b06e | ||
|
de7ef7eace | ||
|
7e89458a3b | ||
|
f212bafe46 | ||
|
86fe40708b | ||
|
d718747c5b | ||
|
46ee9ac41c | ||
|
f1d306feab | ||
|
2d0db98e83 | ||
|
3df06a1c68 | ||
|
a8f9432a3a | ||
|
561e0b04a8 | ||
|
404b6a864b | ||
|
4feaf31225 | ||
|
391aa14845 | ||
|
a0d61b4c37 | ||
|
2952737755 | ||
|
f103aa4753 | ||
|
884856023a | ||
|
d8188fce7b | ||
|
0f15f1ab73 | ||
|
488a236b7a | ||
|
e833823e15 | ||
|
ee4ba104e7 | ||
|
4fdc08295b | ||
|
a3ff49aaf1 | ||
|
76364ea767 | ||
|
8eec50d764 | ||
|
32b659b346 | ||
|
00809909ae | ||
|
9f7684f6fe | ||
|
2bed7d370e | ||
|
d2b852f7cb | ||
|
901939bd96 | ||
|
edfdcc8cee | ||
|
3982c9a552 | ||
|
4490b2d209 | ||
|
eb4bbf3294 | ||
|
dc97d48491 | ||
|
0f50dc848e | ||
|
c5e4b83de3 | ||
|
a08ab46713 | ||
|
f50a7058d6 | ||
|
2c74f0a040 | ||
|
58835e40f3 | ||
|
8befa5918a | ||
|
df80f7ff2a | ||
|
5f66786ef1 | ||
|
3a965856a5 | ||
|
03df609d77 | ||
|
178581b560 | ||
|
ffd2314120 | ||
|
815b595d2f | ||
|
11e56403dd | ||
|
4baadecda5 | ||
|
cf552b5f3b | ||
|
1894304d33 | ||
|
9d5392fb02 | ||
|
c280645671 | ||
|
45f92e6b38 | ||
|
6509f09961 | ||
|
3c279a6f0e | ||
|
9ec3f03871 | ||
|
84a9c2c112 | ||
|
af368119cb | ||
|
89b9189efa | ||
|
dd59b8c7b9 | ||
|
f83491274f | ||
|
c0435fdff4 | ||
|
3c09c77269 | ||
|
547e101f1d | ||
|
0130f64934 | ||
|
361e0543ee | ||
|
63aa5d3b4e | ||
|
01302d097c | ||
|
e6095cb7e8 | ||
|
b876b8af11 | ||
|
a7c41f3fcd | ||
|
4772bd8d4c | ||
|
d471067e3f | ||
|
d0b7780239 | ||
|
e627ca6bd7 | ||
|
c97172bdd4 | ||
|
ce8a745fb4 | ||
|
3e9efd1401 | ||
|
20340190b5 | ||
|
265040a78c | ||
|
448d7e0c5a |
24
.github/workflows/sponsors.yml
vendored
Normal file
24
.github/workflows/sponsors.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
name: Generate Sponsors README
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: 0 0 * * 0
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout 🛎️
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Generate Sponsors 💖
|
||||||
|
uses: JamesIves/github-sponsors-readme-action@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.SPONSORS_TOKEN }}
|
||||||
|
file: 'README.md'
|
||||||
|
|
||||||
|
- name: Deploy to GitHub Pages 🚀
|
||||||
|
uses: JamesIves/github-pages-deploy-action@v4
|
||||||
|
with:
|
||||||
|
branch: master
|
||||||
|
folder: '.'
|
2
.github/workflows/typos.yml
vendored
2
.github/workflows/typos.yml
vendored
@@ -7,4 +7,4 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: crate-ci/typos@v1.14.10
|
- uses: crate-ci/typos@v1.16.4
|
||||||
|
2
.github/workflows/winget.yml
vendored
2
.github/workflows/winget.yml
vendored
@@ -5,7 +5,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
runs-on: windows-latest # Action can only run on Windows
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: vedantmgoyal2009/winget-releaser@v2
|
- uses: vedantmgoyal2009/winget-releaser@v2
|
||||||
with:
|
with:
|
||||||
|
87
CHANGELOG.md
87
CHANGELOG.md
@@ -1,6 +1,91 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.43.0
|
||||||
|
------
|
||||||
|
- (Experimental) Added support for Kitty image protocol in the preview window
|
||||||
|
```sh
|
||||||
|
fzf --preview='
|
||||||
|
if file --mime-type {} | grep -qF image/; then
|
||||||
|
# --transfer-mode=memory is the fastest option but if you want fzf to be able
|
||||||
|
# to redraw the image on terminal resize or on 'change-preview-window',
|
||||||
|
# you need to use --transfer-mode=stream.
|
||||||
|
kitty icat --clear --transfer-mode=memory --stdin=no --place=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}@0x0 {} | sed \$d
|
||||||
|
else
|
||||||
|
bat --color=always {}
|
||||||
|
fi
|
||||||
|
'
|
||||||
|
```
|
||||||
|
- (Experimental) `--listen` server can report program state in JSON format (`GET /`)
|
||||||
|
```sh
|
||||||
|
# fzf server started in "headless" mode
|
||||||
|
fzf --listen 6266 2> /dev/null
|
||||||
|
|
||||||
|
# Get program state
|
||||||
|
curl localhost:6266 | jq .
|
||||||
|
|
||||||
|
# Increase the number of items returned (default: 100)
|
||||||
|
curl localhost:6266?limit=1000 | jq .
|
||||||
|
```
|
||||||
|
- `--listen` server can be secured by setting `$FZF_API_KEY` environment
|
||||||
|
variable.
|
||||||
|
```sh
|
||||||
|
export FZF_API_KEY="$(head -c 32 /dev/urandom | base64)"
|
||||||
|
|
||||||
|
# Server
|
||||||
|
fzf --listen 6266
|
||||||
|
|
||||||
|
# Client
|
||||||
|
curl localhost:6266 -H "x-api-key: $FZF_API_KEY" -d 'change-query(yo)'
|
||||||
|
```
|
||||||
|
- Added `toggle-header` action
|
||||||
|
- Added mouse events for `--bind`
|
||||||
|
- `scroll-up` (bound to `up`)
|
||||||
|
- `scroll-down` (bound to `down`)
|
||||||
|
- `shift-scroll-up` (bound to `toggle+up`)
|
||||||
|
- `shift-scroll-down` (bound to `toggle+down`)
|
||||||
|
- `shift-left-click` (bound to `toggle`)
|
||||||
|
- `shift-right-click` (bound to `toggle`)
|
||||||
|
- `preview-scroll-up` (bound to `preview-up`)
|
||||||
|
- `preview-scroll-down` (bound to `preview-down`)
|
||||||
|
```sh
|
||||||
|
# Twice faster scrolling both in the main window and the preview window
|
||||||
|
fzf --bind 'scroll-up:up+up,scroll-down:down+down' \
|
||||||
|
--bind 'preview-scroll-up:preview-up+preview-up' \
|
||||||
|
--bind 'preview-scroll-down:preview-down+preview-down' \
|
||||||
|
--preview 'cat {}'
|
||||||
|
```
|
||||||
|
- Added `offset-up` and `offset-down` actions
|
||||||
|
```sh
|
||||||
|
# Scrolling will behave similarly to CTRL-E and CTRL-Y of vim
|
||||||
|
fzf --bind scroll-up:offset-up,scroll-down:offset-down \
|
||||||
|
--bind ctrl-y:offset-up,ctrl-e:offset-down \
|
||||||
|
--scroll-off=5
|
||||||
|
```
|
||||||
|
- Shell extensions
|
||||||
|
- Updated bash completion for fzf options
|
||||||
|
- bash key bindings no longer requires perl; it will use awk or mawk
|
||||||
|
instead if perl is not found
|
||||||
|
- Basic context-aware completion for ssh command
|
||||||
|
- Applied `--scheme=path` for better ordering of the result
|
||||||
|
- Bug fixes and improvements
|
||||||
|
|
||||||
|
0.42.0
|
||||||
|
------
|
||||||
|
- Added new info style: `--info=right`
|
||||||
|
- Added new info style: `--info=inline-right`
|
||||||
|
- Added new border style `thinblock` which uses symbols for legacy computing
|
||||||
|
[one eighth block elements](https://en.wikipedia.org/wiki/Symbols_for_Legacy_Computing)
|
||||||
|
- Similarly to `block`, this style is suitable when using a different
|
||||||
|
background color because the window is completely contained within the border.
|
||||||
|
```sh
|
||||||
|
BAT_THEME=GitHub fzf --info=right --border=thinblock --preview-window=border-thinblock \
|
||||||
|
--margin=3 --scrollbar=▏▕ --preview='bat --color=always --style=numbers {}' \
|
||||||
|
--color=light,query:238,fg:238,bg:251,bg+:249,gutter:251,border:248,preview-bg:253
|
||||||
|
```
|
||||||
|
- This style may not render correctly depending on the font and the
|
||||||
|
terminal emulator.
|
||||||
|
|
||||||
0.41.1
|
0.41.1
|
||||||
------
|
------
|
||||||
- Fixed a bug where preview window is not updated when `--disabled` is set and
|
- Fixed a bug where preview window is not updated when `--disabled` is set and
|
||||||
@@ -337,7 +422,7 @@ CHANGELOG
|
|||||||
(sleep 2; seq 1000) | fzf --height ~50%
|
(sleep 2; seq 1000) | fzf --height ~50%
|
||||||
```
|
```
|
||||||
- Fixed tcell renderer used to render full-screen fzf on Windows
|
- Fixed tcell renderer used to render full-screen fzf on Windows
|
||||||
- `--no-clear` is deprecated. Use `reload` action instead.
|
- ~~`--no-clear` is deprecated. Use `reload` action instead.~~
|
||||||
|
|
||||||
0.33.0
|
0.33.0
|
||||||
------
|
------
|
||||||
|
4
Makefile
4
Makefile
@@ -57,7 +57,9 @@ else ifeq ($(UNAME_M),armv6l)
|
|||||||
else ifeq ($(UNAME_M),armv7l)
|
else ifeq ($(UNAME_M),armv7l)
|
||||||
BINARY := $(BINARYARM7)
|
BINARY := $(BINARYARM7)
|
||||||
else ifeq ($(UNAME_M),armv8l)
|
else ifeq ($(UNAME_M),armv8l)
|
||||||
BINARY := $(BINARYARM8)
|
# armv8l is always 32-bit and should implement the armv7 ISA, so
|
||||||
|
# just use the same filename as for armv7.
|
||||||
|
BINARY := $(BINARYARM7)
|
||||||
else ifeq ($(UNAME_M),arm64)
|
else ifeq ($(UNAME_M),arm64)
|
||||||
BINARY := $(BINARYARM8)
|
BINARY := $(BINARYARM8)
|
||||||
else ifeq ($(UNAME_M),aarch64)
|
else ifeq ($(UNAME_M),aarch64)
|
||||||
|
@@ -26,6 +26,9 @@ written as:
|
|||||||
" If installed using Homebrew
|
" If installed using Homebrew
|
||||||
Plug '/usr/local/opt/fzf'
|
Plug '/usr/local/opt/fzf'
|
||||||
|
|
||||||
|
" If installed using Homebrew on Apple Silicon
|
||||||
|
Plug '/opt/homebrew/opt/fzf'
|
||||||
|
|
||||||
" If you have cloned fzf on ~/.fzf directory
|
" If you have cloned fzf on ~/.fzf directory
|
||||||
Plug '~/.fzf'
|
Plug '~/.fzf'
|
||||||
```
|
```
|
||||||
|
26
README.md
26
README.md
@@ -19,6 +19,15 @@ Pros
|
|||||||
- Batteries included
|
- Batteries included
|
||||||
- Vim/Neovim plugin, key bindings, and fuzzy auto-completion
|
- Vim/Neovim plugin, key bindings, and fuzzy auto-completion
|
||||||
|
|
||||||
|
Sponsors ❤️
|
||||||
|
-----------
|
||||||
|
|
||||||
|
I would like to thank all the sponsors of this project who make it possible for me to continue to improve fzf.
|
||||||
|
|
||||||
|
If you'd like to sponsor this project, please visit https://github.com/sponsors/junegunn.
|
||||||
|
|
||||||
|
<!-- sponsors --><a href="https://github.com/miyanokomiya"><img src="https://github.com/miyanokomiya.png" width="60px" alt="miyanokomiya" /></a><a href="https://github.com/jonhoo"><img src="https://github.com/jonhoo.png" width="60px" alt="Jon Gjengset" /></a><a href="https://github.com/AceofSpades5757"><img src="https://github.com/AceofSpades5757.png" width="60px" alt="Kyle L. Davis" /></a><a href="https://github.com/Frederick888"><img src="https://github.com/Frederick888.png" width="60px" alt="Frederick Zhang" /></a><a href="https://github.com/moritzdietz"><img src="https://github.com/moritzdietz.png" width="60px" alt="Moritz Dietz" /></a><a href="https://github.com/mikker"><img src="https://github.com/mikker.png" width="60px" alt="Mikkel Malmberg" /></a><a href="https://github.com/pldubouilh"><img src="https://github.com/pldubouilh.png" width="60px" alt="Pierre Dubouilh" /></a><a href="https://github.com/rcorre"><img src="https://github.com/rcorre.png" width="60px" alt="Ryan Roden-Corrent" /></a><a href="https://github.com/blissdev"><img src="https://github.com/blissdev.png" width="60px" alt="Jordan Arentsen" /></a><a href="https://github.com/mislav"><img src="https://github.com/mislav.png" width="60px" alt="Mislav Marohnić" /></a><a href="https://github.com/aexvir"><img src="https://github.com/aexvir.png" width="60px" alt="Alex Viscreanu" /></a><a href="https://github.com/dbalatero"><img src="https://github.com/dbalatero.png" width="60px" alt="David Balatero" /></a><a href="https://github.com/comatory"><img src="https://github.com/comatory.png" width="60px" alt="Ondrej Synacek" /></a><a href="https://github.com/moobar"><img src="https://github.com/moobar.png" width="60px" alt="" /></a><a href="https://github.com/majjoha"><img src="https://github.com/majjoha.png" width="60px" alt="Mathias Jean Johansen" /></a><a href="https://github.com/benelan"><img src="https://github.com/benelan.png" width="60px" alt="Ben Elan" /></a><a href="https://github.com/jryom"><img src="https://github.com/jryom.png" width="60px" alt="Jesper" /></a><a href="https://github.com/nmrnv"><img src="https://github.com/nmrnv.png" width="60px" alt="Nikolay Marinov" /></a><a href="https://github.com/pawelduda"><img src="https://github.com/pawelduda.png" width="60px" alt="Paweł Duda" /></a><a href="https://github.com/slezica"><img src="https://github.com/slezica.png" width="60px" alt="Santiago Lezica" /></a><a href="https://github.com/pbwn"><img src="https://github.com/pbwn.png" width="60px" alt="" /></a><a href="https://github.com/timgluz"><img src="https://github.com/timgluz.png" width="60px" alt="Timo Sulg" /></a><a href="https://github.com/seanmorton"><img src="https://github.com/seanmorton.png" width="60px" alt="Sean Morton" /></a><a href="https://github.com/pyrho"><img src="https://github.com/pyrho.png" width="60px" alt="Damien Rajon" /></a><a href="https://github.com/ArtBIT"><img src="https://github.com/ArtBIT.png" width="60px" alt="ArtBIT" /></a><a href="https://github.com/da-moon"><img src="https://github.com/da-moon.png" width="60px" alt="" /></a><a href="https://github.com/hovissimo"><img src="https://github.com/hovissimo.png" width="60px" alt="Hovis" /></a><a href="https://github.com/dariusjonda"><img src="https://github.com/dariusjonda.png" width="60px" alt="Darius Jonda" /></a><a href="https://github.com/cristiand391"><img src="https://github.com/cristiand391.png" width="60px" alt="Cristian Dominguez" /></a><a href="https://github.com/eliangcs"><img src="https://github.com/eliangcs.png" width="60px" alt="Chang-Hung Liang" /></a><a href="https://github.com/raveensrk"><img src="https://github.com/raveensrk.png" width="60px" alt="Raveen Kumar" /></a><a href="https://github.com/asphaltbuffet"><img src="https://github.com/asphaltbuffet.png" width="60px" alt="Ben Lechlitner" /></a><a href="https://github.com/yash1th"><img src="https://github.com/yash1th.png" width="60px" alt="yash" /></a><a href="https://github.com/kg8m"><img src="https://github.com/kg8m.png" width="60px" alt="Takumi KAGIYAMA" /></a><a href="https://github.com/polm"><img src="https://github.com/polm.png" width="60px" alt="Paul O'Leary McCann" /></a><a href="https://github.com/rbeeger"><img src="https://github.com/rbeeger.png" width="60px" alt="Robert Beeger" /></a><a href="https://github.com/veebch"><img src="https://github.com/veebch.png" width="60px" alt="VEEB Projects" /></a><!-- sponsors -->
|
||||||
|
|
||||||
Table of Contents
|
Table of Contents
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
@@ -60,6 +69,7 @@ Table of Contents
|
|||||||
* [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)
|
||||||
* [3. Interactive ripgrep integration](#3-interactive-ripgrep-integration)
|
* [3. Interactive ripgrep integration](#3-interactive-ripgrep-integration)
|
||||||
* [Preview window](#preview-window)
|
* [Preview window](#preview-window)
|
||||||
|
* [Previewing an image](#previewing-an-image)
|
||||||
* [Tips](#tips)
|
* [Tips](#tips)
|
||||||
* [Respecting `.gitignore`](#respecting-gitignore)
|
* [Respecting `.gitignore`](#respecting-gitignore)
|
||||||
* [Fish shell](#fish-shell)
|
* [Fish shell](#fish-shell)
|
||||||
@@ -721,6 +731,22 @@ seq 100 | fzf
|
|||||||
history | fzf
|
history | fzf
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Previewing an image
|
||||||
|
|
||||||
|
Since 0.43.0, fzf has experimental support for [Kitty graphics
|
||||||
|
protocol](https://sw.kovidgoyal.net/kitty/graphics-protocol/), so if you use
|
||||||
|
Kitty, you can make fzf display an image in the preview window.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
fzf --preview='
|
||||||
|
if file --mime-type {} | grep -qF image/; then
|
||||||
|
kitty icat --clear --transfer-mode=memory --stdin=no --place=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}@0x0 {} | sed \$d
|
||||||
|
else
|
||||||
|
bat --color=always {}
|
||||||
|
fi
|
||||||
|
'
|
||||||
|
```
|
||||||
|
|
||||||
Tips
|
Tips
|
||||||
----
|
----
|
||||||
|
|
||||||
|
16
bin/fzf-tmux
16
bin/fzf-tmux
@@ -151,12 +151,18 @@ argsf="${TMPDIR:-/tmp}/fzf-args-$id"
|
|||||||
fifo1="${TMPDIR:-/tmp}/fzf-fifo1-$id"
|
fifo1="${TMPDIR:-/tmp}/fzf-fifo1-$id"
|
||||||
fifo2="${TMPDIR:-/tmp}/fzf-fifo2-$id"
|
fifo2="${TMPDIR:-/tmp}/fzf-fifo2-$id"
|
||||||
fifo3="${TMPDIR:-/tmp}/fzf-fifo3-$id"
|
fifo3="${TMPDIR:-/tmp}/fzf-fifo3-$id"
|
||||||
tmux_win_opts=( $(tmux show-window-options remain-on-exit \; show-window-options synchronize-panes | sed '/ off/d; s/^/set-window-option /; s/$/ \\;/') )
|
if tmux_win_opts=$(tmux show-options -p remain-on-exit \; show-options -p synchronize-panes 2> /dev/null); then
|
||||||
|
tmux_win_opts=( $(sed '/ off/d; s/synchronize-panes/set-option -p synchronize-panes/; s/remain-on-exit/set-option -p remain-on-exit/; s/$/ \\;/' <<< "$tmux_win_opts") )
|
||||||
|
tmux_off_opts='; set-option -p synchronize-panes off ; set-option -p remain-on-exit off'
|
||||||
|
else
|
||||||
|
tmux_win_opts=( $(tmux show-window-options remain-on-exit \; show-window-options synchronize-panes | sed '/ off/d; s/^/set-window-option /; s/$/ \\;/') )
|
||||||
|
tmux_off_opts='; set-window-option synchronize-panes off ; set-window-option remain-on-exit off'
|
||||||
|
fi
|
||||||
cleanup() {
|
cleanup() {
|
||||||
\rm -f $argsf $fifo1 $fifo2 $fifo3
|
\rm -f $argsf $fifo1 $fifo2 $fifo3
|
||||||
|
|
||||||
# Restore tmux window options
|
# Restore tmux window options
|
||||||
if [[ "${#tmux_win_opts[@]}" -gt 0 ]]; then
|
if [[ "${#tmux_win_opts[@]}" -gt 1 ]]; then
|
||||||
eval "tmux ${tmux_win_opts[*]}"
|
eval "tmux ${tmux_win_opts[*]}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -192,6 +198,8 @@ if [[ "$opt" =~ "-E" ]]; then
|
|||||||
fi
|
fi
|
||||||
[[ -n "$FZF_DEFAULT_OPTS" ]] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
|
[[ -n "$FZF_DEFAULT_OPTS" ]] && envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
|
||||||
[[ -n "$FZF_DEFAULT_COMMAND" ]] && envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
|
[[ -n "$FZF_DEFAULT_COMMAND" ]] && envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
|
||||||
|
[[ -n "$RUNEWIDTH_EASTASIAN" ]] && envs="$envs RUNEWIDTH_EASTASIAN=$(printf %q "$RUNEWIDTH_EASTASIAN")"
|
||||||
|
[[ -n "$BAT_THEME" ]] && envs="$envs BAT_THEME=$(printf %q "$BAT_THEME")"
|
||||||
echo "$envs;" > "$argsf"
|
echo "$envs;" > "$argsf"
|
||||||
|
|
||||||
# Build arguments to fzf
|
# Build arguments to fzf
|
||||||
@@ -225,9 +233,9 @@ else
|
|||||||
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; echo \$? > $fifo3 $close" >> $argsf
|
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; echo \$? > $fifo3 $close" >> $argsf
|
||||||
cat <&0 > $fifo1 &
|
cat <&0 > $fifo1 &
|
||||||
fi
|
fi
|
||||||
tmux set-window-option synchronize-panes off \;\
|
tmux \
|
||||||
set-window-option remain-on-exit off \;\
|
|
||||||
split-window -c "$PWD" $opt "bash -c 'exec -a fzf bash $argsf'" $swap \
|
split-window -c "$PWD" $opt "bash -c 'exec -a fzf bash $argsf'" $swap \
|
||||||
|
$tmux_off_opts \
|
||||||
> /dev/null 2>&1 || { "$fzf" "${args[@]}"; exit $?; }
|
> /dev/null 2>&1 || { "$fzf" "${args[@]}"; exit $?; }
|
||||||
cat $fifo2
|
cat $fifo2
|
||||||
exit "$(cat $fifo3)"
|
exit "$(cat $fifo3)"
|
||||||
|
42
doc/fzf.txt
42
doc/fzf.txt
@@ -1,4 +1,4 @@
|
|||||||
fzf.txt fzf Last change: Mar 20 2023
|
fzf.txt fzf Last change: September 17 2023
|
||||||
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
|
FZF - TABLE OF CONTENTS *fzf* *fzf-toc*
|
||||||
==============================================================================
|
==============================================================================
|
||||||
|
|
||||||
@@ -32,6 +32,9 @@ depending on the package manager.
|
|||||||
" If installed using Homebrew
|
" If installed using Homebrew
|
||||||
set rtp+=/usr/local/opt/fzf
|
set rtp+=/usr/local/opt/fzf
|
||||||
|
|
||||||
|
" If installed using Homebrew on Apple Silicon
|
||||||
|
set rtp+=/opt/homebrew/opt/fzf
|
||||||
|
|
||||||
" If you have cloned fzf on ~/.fzf directory
|
" If you have cloned fzf on ~/.fzf directory
|
||||||
set rtp+=~/.fzf
|
set rtp+=~/.fzf
|
||||||
<
|
<
|
||||||
@@ -40,6 +43,9 @@ If you use {vim-plug}{1}, the same can be written as:
|
|||||||
" If installed using Homebrew
|
" If installed using Homebrew
|
||||||
Plug '/usr/local/opt/fzf'
|
Plug '/usr/local/opt/fzf'
|
||||||
|
|
||||||
|
" If installed using Homebrew on Apple Silicon
|
||||||
|
Plug '/opt/homebrew/opt/fzf'
|
||||||
|
|
||||||
" If you have cloned fzf on ~/.fzf directory
|
" If you have cloned fzf on ~/.fzf directory
|
||||||
Plug '~/.fzf'
|
Plug '~/.fzf'
|
||||||
<
|
<
|
||||||
@@ -68,16 +74,16 @@ SUMMARY *fzf-summary*
|
|||||||
The Vim plugin of fzf provides two core functions, and `:FZF` command which is
|
The Vim plugin of fzf provides two core functions, and `:FZF` command which is
|
||||||
the basic file selector command built on top of them.
|
the basic file selector command built on top of them.
|
||||||
|
|
||||||
1. `fzf#run([spec dict])`
|
1. `fzf#run([spec dict])`
|
||||||
- Starts fzf inside Vim with the given spec
|
- Starts fzf inside Vim with the given spec
|
||||||
- `:call fzf#run({'source': 'ls'})`
|
- `:call fzf#run({'source': 'ls'})`
|
||||||
2. `fzf#wrap([spec dict]) -> (dict)`
|
2. `fzf#wrap([spec dict]) -> (dict)`
|
||||||
- Takes a spec for `fzf#run` and returns an extended version of it with
|
- Takes a spec for `fzf#run` and returns an extended version of it with
|
||||||
additional options for addressing global preferences (`g:fzf_xxx`)
|
additional options for addressing global preferences (`g:fzf_xxx`)
|
||||||
- `:echo fzf#wrap({'source': 'ls'})`
|
- `:echo fzf#wrap({'source': 'ls'})`
|
||||||
- We usually wrap a spec with `fzf#wrap` before passing it to `fzf#run`
|
- We usually wrap a spec with `fzf#wrap` before passing it to `fzf#run`
|
||||||
- `:call fzf#run(fzf#wrap({'source': 'ls'}))`
|
- `:call fzf#run(fzf#wrap({'source': 'ls'}))`
|
||||||
3. `:FZF [fzf_options string] [path string]`
|
3. `:FZF [fzf_options string] [path string]`
|
||||||
- Basic fuzzy file selector
|
- Basic fuzzy file selector
|
||||||
- A reference implementation for those who don't want to write VimScript to
|
- A reference implementation for those who don't want to write VimScript to
|
||||||
implement custom commands
|
implement custom commands
|
||||||
@@ -222,12 +228,12 @@ list:
|
|||||||
`spinner` | Streaming input indicator
|
`spinner` | Streaming input indicator
|
||||||
`query` | Query string
|
`query` | Query string
|
||||||
`disabled` | Query string when search is disabled
|
`disabled` | Query string when search is disabled
|
||||||
`prompt` | Prompt before query ( `> ` )
|
`prompt` | Prompt before query ( `> ` )
|
||||||
`pointer` | Pointer to the current line ( `>` )
|
`pointer` | Pointer to the current line ( `>` )
|
||||||
----------------------------+------------------------------------------------------
|
----------------------------+------------------------------------------------------
|
||||||
- `component` specifies the component (`fg` / `bg`) from which to extract the
|
- `component` specifies the component (`fg` / `bg`) from which to extract the
|
||||||
color when considering each of the following highlight groups
|
color when considering each of the following highlight groups
|
||||||
- `group1 [, group2, ...]` is a list of highlight groups that are searched (in
|
- `group1 [, group2, ...]` is a list of highlight groups that are searched (in
|
||||||
order) for a matching color definition
|
order) for a matching color definition
|
||||||
|
|
||||||
For example, consider the following specification:
|
For example, consider the following specification:
|
||||||
@@ -268,7 +274,7 @@ as the sink.
|
|||||||
<
|
<
|
||||||
Instead of using the default find command, you can use any shell command as
|
Instead of using the default find command, you can use any shell command as
|
||||||
the source. The following example will list the files managed by git. It's
|
the source. The following example will list the files managed by git. It's
|
||||||
equivalent to running `git ls-files | fzf` on shell.
|
equivalent to running `git ls-files | fzf` on shell.
|
||||||
>
|
>
|
||||||
call fzf#run({'source': 'git ls-files', 'sink': 'e'})
|
call fzf#run({'source': 'git ls-files', 'sink': 'e'})
|
||||||
<
|
<
|
||||||
@@ -296,7 +302,7 @@ The following table summarizes the available options.
|
|||||||
---------------------------+---------------+----------------------------------------------------------------------
|
---------------------------+---------------+----------------------------------------------------------------------
|
||||||
Option name | Type | Description ~
|
Option name | Type | Description ~
|
||||||
---------------------------+---------------+----------------------------------------------------------------------
|
---------------------------+---------------+----------------------------------------------------------------------
|
||||||
`source` | string | External command to generate input to fzf (e.g. `find .` )
|
`source` | string | External command to generate input to fzf (e.g. `find .` )
|
||||||
`source` | list | Vim list as input to fzf
|
`source` | list | Vim list as input to fzf
|
||||||
`sink` | string | Vim command to handle the selected item (e.g. `e` , `tabe` )
|
`sink` | string | Vim command to handle the selected item (e.g. `e` , `tabe` )
|
||||||
`sink` | funcref | Reference to function to process each selected item
|
`sink` | funcref | Reference to function to process each selected item
|
||||||
@@ -305,8 +311,8 @@ The following table summarizes the available options.
|
|||||||
`dir` | string | Working directory
|
`dir` | string | Working directory
|
||||||
`up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` )
|
`up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` )
|
||||||
`tmux` | string | (Layout) fzf-tmux options (e.g. `-p90%,60%` )
|
`tmux` | string | (Layout) fzf-tmux options (e.g. `-p90%,60%` )
|
||||||
`window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new` )
|
`window` (Vim 8 / Neovim) | string | (Layout) Command to open fzf window (e.g. `vertical aboveleft 30new` )
|
||||||
`window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width': 0.9, 'height': 0.6}` )
|
`window` (Vim 8 / Neovim) | dict | (Layout) Popup window settings (e.g. `{'width': 0.9, 'height': 0.6}` )
|
||||||
---------------------------+---------------+----------------------------------------------------------------------
|
---------------------------+---------------+----------------------------------------------------------------------
|
||||||
|
|
||||||
`options` entry can be either a string or a list. For simple cases, string
|
`options` entry can be either a string or a list. For simple cases, string
|
||||||
@@ -343,7 +349,7 @@ So how can we make our custom `fzf#run` calls also respect those variables?
|
|||||||
Simply by "wrapping" the spec dictionary with `fzf#wrap` before passing it to
|
Simply by "wrapping" the spec dictionary with `fzf#wrap` before passing it to
|
||||||
`fzf#run`.
|
`fzf#run`.
|
||||||
|
|
||||||
- `fzf#wrap([name string], [spec dict], [fullscreen bool]) -> (dict)`
|
- `fzf#wrap([name string], [spec dict], [fullscreen bool]) -> (dict)`
|
||||||
- All arguments are optional. Usually we only need to pass a spec
|
- All arguments are optional. Usually we only need to pass a spec
|
||||||
dictionary.
|
dictionary.
|
||||||
- `name` is for managing history files. It is ignored if `g:fzf_history_dir`
|
- `name` is for managing history files. It is ignored if `g:fzf_history_dir`
|
||||||
@@ -377,7 +383,7 @@ last `fullscreen` argument of `fzf#wrap` (see :help <bang>).
|
|||||||
command! -bang LS call fzf#run(fzf#wrap({'source': 'ls'}, <bang>0))
|
command! -bang LS call fzf#run(fzf#wrap({'source': 'ls'}, <bang>0))
|
||||||
<
|
<
|
||||||
Our `:LS` command will be much more useful if we can pass a directory argument
|
Our `:LS` command will be much more useful if we can pass a directory argument
|
||||||
to it, so that something like `:LS /tmp` is possible.
|
to it, so that something like `:LS /tmp` is possible.
|
||||||
>
|
>
|
||||||
command! -bang -complete=dir -nargs=? LS
|
command! -bang -complete=dir -nargs=? LS
|
||||||
\ call fzf#run(fzf#wrap({'source': 'ls', 'dir': <q-args>}, <bang>0))
|
\ call fzf#run(fzf#wrap({'source': 'ls', 'dir': <q-args>}, <bang>0))
|
||||||
@@ -396,10 +402,10 @@ unique name to our command and pass it as the first argument to `fzf#wrap`.
|
|||||||
|
|
||||||
- `g:fzf_layout`
|
- `g:fzf_layout`
|
||||||
- `g:fzf_action`
|
- `g:fzf_action`
|
||||||
- Works only when no custom `sink` (or `sink*`) is provided
|
- Works only when no custom `sink` (or `sinklist`) is provided
|
||||||
- Having custom sink usually means that each entry is not an ordinary
|
- Having custom sink usually means that each entry is not an ordinary
|
||||||
file path (e.g. name of color scheme), so we can't blindly apply the
|
file path (e.g. name of color scheme), so we can't blindly apply the
|
||||||
same strategy (i.e. `tabedit some-color-scheme` doesn't make sense)
|
same strategy (i.e. `tabedit some-color-scheme` doesn't make sense)
|
||||||
- `g:fzf_colors`
|
- `g:fzf_colors`
|
||||||
- `g:fzf_history_dir`
|
- `g:fzf_history_dir`
|
||||||
|
|
||||||
@@ -488,7 +494,7 @@ or above) by putting fzf-tmux options in `tmux` key.
|
|||||||
*fzf-hide-statusline*
|
*fzf-hide-statusline*
|
||||||
|
|
||||||
When fzf starts in a terminal buffer, the file type of the buffer is set to
|
When fzf starts in a terminal buffer, the file type of the buffer is set to
|
||||||
`fzf`. So you can set up `FileType fzf` autocmd to customize the settings of
|
`fzf`. So you can set up `FileType fzf` autocmd to customize the settings of
|
||||||
the window.
|
the window.
|
||||||
|
|
||||||
For example, if you open fzf on the bottom on the screen (e.g. `{'down':
|
For example, if you open fzf on the bottom on the screen (e.g. `{'down':
|
||||||
|
4
go.mod
4
go.mod
@@ -7,8 +7,8 @@ require (
|
|||||||
github.com/mattn/go-shellwords v1.0.12
|
github.com/mattn/go-shellwords v1.0.12
|
||||||
github.com/rivo/uniseg v0.4.4
|
github.com/rivo/uniseg v0.4.4
|
||||||
github.com/saracen/walker v0.1.3
|
github.com/saracen/walker v0.1.3
|
||||||
golang.org/x/sys v0.8.0
|
golang.org/x/sys v0.13.0
|
||||||
golang.org/x/term v0.8.0
|
golang.org/x/term v0.13.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
8
go.sum
8
go.sum
@@ -32,12 +32,12 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
|
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
8
install
8
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.41.1
|
version=0.43.0
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
@@ -196,12 +196,12 @@ if [ -n "$binary_error" ]; then
|
|||||||
echo " - $binary_error !!!"
|
echo " - $binary_error !!!"
|
||||||
fi
|
fi
|
||||||
if command -v go > /dev/null; then
|
if command -v go > /dev/null; then
|
||||||
echo -n "Building binary (go get -u github.com/junegunn/fzf) ... "
|
echo -n "Building binary (go install github.com/junegunn/fzf) ... "
|
||||||
if [ -z "${GOPATH-}" ]; then
|
if [ -z "${GOPATH-}" ]; then
|
||||||
export GOPATH="${TMPDIR:-/tmp}/fzf-gopath"
|
export GOPATH="${TMPDIR:-/tmp}/fzf-gopath"
|
||||||
mkdir -p "$GOPATH"
|
mkdir -p "$GOPATH"
|
||||||
fi
|
fi
|
||||||
if go get -ldflags "-s -w -X main.version=$version -X main.revision=go-get" github.com/junegunn/fzf; then
|
if go install -ldflags "-s -w -X main.version=$version -X main.revision=go-install" github.com/junegunn/fzf; then
|
||||||
echo "OK"
|
echo "OK"
|
||||||
cp "$GOPATH/bin/fzf" "$fzf_base/bin/"
|
cp "$GOPATH/bin/fzf" "$fzf_base/bin/"
|
||||||
else
|
else
|
||||||
@@ -245,7 +245,7 @@ for shell in $shells; do
|
|||||||
src=${prefix_expand}.${shell}
|
src=${prefix_expand}.${shell}
|
||||||
echo -n "Generate $src ... "
|
echo -n "Generate $src ... "
|
||||||
|
|
||||||
fzf_completion="[[ \$- == *i* ]] && source \"$fzf_base/shell/completion.${shell}\" 2> /dev/null"
|
fzf_completion="source \"$fzf_base/shell/completion.${shell}\""
|
||||||
if [ $auto_completion -eq 0 ]; then
|
if [ $auto_completion -eq 0 ]; then
|
||||||
fzf_completion="# $fzf_completion"
|
fzf_completion="# $fzf_completion"
|
||||||
fi
|
fi
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
$version="0.41.1"
|
$version="0.43.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.41"
|
var version string = "0.43"
|
||||||
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 "May 2023" "fzf 0.41.1" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf-tmux 1 "Oct 2023" "fzf 0.43.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 "May 2023" "fzf 0.41.1" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Oct 2023" "fzf 0.43.0" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -57,9 +57,9 @@ Choose scoring scheme tailored for different types of input.
|
|||||||
.br
|
.br
|
||||||
.BR default " Generic scoring scheme designed to work well with any type of input"
|
.BR default " Generic scoring scheme designed to work well with any type of input"
|
||||||
.br
|
.br
|
||||||
.BR path " Scoring scheme for paths (additional bonus point only after path separator)
|
.BR path " Scoring scheme well suited for file paths
|
||||||
.br
|
.br
|
||||||
.BR history " Scoring scheme for command history (no additional bonus points).
|
.BR history " Scoring scheme well suited for command history or any input where chronological ordering is important
|
||||||
Sets \fB--tiebreak=index\fR as well.
|
Sets \fB--tiebreak=index\fR as well.
|
||||||
.br
|
.br
|
||||||
.TP
|
.TP
|
||||||
@@ -228,6 +228,10 @@ Draw border around the finder
|
|||||||
.br
|
.br
|
||||||
.BR double " Border with double lines"
|
.BR double " Border with double lines"
|
||||||
.br
|
.br
|
||||||
|
.BR block " Border using block elements; suitable when using different background colors"
|
||||||
|
.br
|
||||||
|
.BR thinblock " Border using legacy computing symbols; may not be displayed on some terminals"
|
||||||
|
.br
|
||||||
.BR horizontal " Horizontal lines above and below the finder"
|
.BR horizontal " Horizontal lines above and below the finder"
|
||||||
.br
|
.br
|
||||||
.BR vertical " Vertical lines on each side of the finder"
|
.BR vertical " Vertical lines on each side of the finder"
|
||||||
@@ -353,10 +357,14 @@ Determines the display style of finder info (match counters).
|
|||||||
.br
|
.br
|
||||||
.BR default " Display on the next line to the prompt"
|
.BR default " Display on the next line to the prompt"
|
||||||
.br
|
.br
|
||||||
|
.BR right " Display on the right end of the next line to the prompt"
|
||||||
|
.br
|
||||||
.BR inline " Display on the same line with the default separator ' < '"
|
.BR inline " Display on the same line with the default separator ' < '"
|
||||||
.br
|
.br
|
||||||
.BR inline:SEPARATOR " Display on the same line with a non-default separator"
|
.BR inline:SEPARATOR " Display on the same line with a non-default separator"
|
||||||
.br
|
.br
|
||||||
|
.BR inline-right " Display on the right end of the same line
|
||||||
|
.br
|
||||||
.BR hidden " Do not display finder info"
|
.BR hidden " Do not display finder info"
|
||||||
.br
|
.br
|
||||||
|
|
||||||
@@ -583,6 +591,18 @@ e.g.
|
|||||||
echo "$i"
|
echo "$i"
|
||||||
sleep 0.01
|
sleep 0.01
|
||||||
done'\fR
|
done'\fR
|
||||||
|
|
||||||
|
Since 0.43.0, fzf has experimental support for Kitty graphics protocol,
|
||||||
|
so if you use Kitty, you can make fzf display an image in the preview window.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fBfzf --preview='
|
||||||
|
if file --mime-type {} | grep -qF "image/"; then
|
||||||
|
kitty icat --clear --transfer-mode=memory --stdin=no --place=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}@0x0 {} | sed \\$d
|
||||||
|
else
|
||||||
|
bat --color=always {}
|
||||||
|
fi
|
||||||
|
'\fR
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
@@ -599,6 +619,10 @@ Should be used with one of the following \fB--preview-window\fR options.
|
|||||||
.br
|
.br
|
||||||
.B * border-double
|
.B * border-double
|
||||||
.br
|
.br
|
||||||
|
.B * border-block
|
||||||
|
.br
|
||||||
|
.B * border-thinblock
|
||||||
|
.br
|
||||||
.B * border-horizontal
|
.B * border-horizontal
|
||||||
.br
|
.br
|
||||||
.B * border-top
|
.B * border-top
|
||||||
@@ -746,6 +770,21 @@ Read input delimited by ASCII NUL characters instead of newline characters
|
|||||||
.TP
|
.TP
|
||||||
.B "--print0"
|
.B "--print0"
|
||||||
Print output delimited by ASCII NUL characters instead of newline characters
|
Print output delimited by ASCII NUL characters instead of newline characters
|
||||||
|
.TP
|
||||||
|
.B "--no-clear"
|
||||||
|
Do not clear finder interface on exit. If fzf was started in full screen mode,
|
||||||
|
it will not switch back to the original screen, so you'll have to manually run
|
||||||
|
\fBtput rmcup\fR to return. This option can be used to avoid flickering of the
|
||||||
|
screen when your application needs to start fzf multiple times in order. (Note
|
||||||
|
that in most cases, it is preferable to use \fBreload\fR action instead.)
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fBfoo=$(seq 100 | fzf --no-clear) || (
|
||||||
|
# Need to manually switch back to the main screen when cancelled
|
||||||
|
tput rmcup
|
||||||
|
exit 1
|
||||||
|
) && seq "$foo" 100 | fzf
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B "--sync"
|
.B "--sync"
|
||||||
Synchronous search for multi-staged filtering. If specified, fzf will launch
|
Synchronous search for multi-staged filtering. If specified, fzf will launch
|
||||||
@@ -760,15 +799,24 @@ Start HTTP server on the given port. It allows external processes to send
|
|||||||
actions to perform via POST method. If the port number is omitted or given as
|
actions to perform via POST method. If the port number is omitted or given as
|
||||||
0, fzf will choose the port automatically and export it as \fBFZF_PORT\fR
|
0, fzf will choose the port automatically and export it as \fBFZF_PORT\fR
|
||||||
environment variable to the child processes started via \fBexecute\fR and
|
environment variable to the child processes started via \fBexecute\fR and
|
||||||
\fBexecute-silent\fR actions.
|
\fBexecute-silent\fR actions. If \fBFZF_API_KEY\fR environment variable is
|
||||||
|
set, the server would require sending an API key with the same value in the
|
||||||
|
\fBx-api-key\fR HTTP header.
|
||||||
|
|
||||||
e.g.
|
e.g.
|
||||||
\fB# Start HTTP server on port 6266
|
\fB# Start HTTP server on port 6266
|
||||||
fzf --listen 6266
|
fzf --listen 6266
|
||||||
|
|
||||||
|
# Get program state in JSON format (experimental)
|
||||||
|
curl localhost:6266
|
||||||
|
|
||||||
# Send action to the server
|
# Send action to the server
|
||||||
curl -XPOST localhost:6266 -d 'reload(seq 100)+change-prompt(hundred> )'
|
curl -XPOST localhost:6266 -d 'reload(seq 100)+change-prompt(hundred> )'
|
||||||
|
|
||||||
|
# Start HTTP server on port 6266 and send an authenticated action
|
||||||
|
export FZF_API_KEY="$(head -c 32 /dev/urandom | base64)"
|
||||||
|
curl -XPOST localhost:6266 -H "x-api-key: $FZF_API_KEY" -d 'change-query(yo)'
|
||||||
|
|
||||||
# Choose port automatically and export it as $FZF_PORT to the child process
|
# Choose port automatically and export it as $FZF_PORT to the child process
|
||||||
fzf --listen --bind 'start:execute-silent:echo $FZF_PORT > /tmp/fzf-port'
|
fzf --listen --bind 'start:execute-silent:echo $FZF_PORT > /tmp/fzf-port'
|
||||||
\fR
|
\fR
|
||||||
@@ -788,6 +836,11 @@ this case make sure that the command is POSIX-compliant.
|
|||||||
.TP
|
.TP
|
||||||
.B FZF_DEFAULT_OPTS
|
.B FZF_DEFAULT_OPTS
|
||||||
Default options. e.g. \fBexport FZF_DEFAULT_OPTS="--extended --cycle"\fR
|
Default options. e.g. \fBexport FZF_DEFAULT_OPTS="--extended --cycle"\fR
|
||||||
|
.TP
|
||||||
|
.B FZF_API_KEY
|
||||||
|
Can be used to require an API key when using \fB--listen\fR option. If not set,
|
||||||
|
no authentication will be required by the server. You can set this value if
|
||||||
|
you need to protect against DNS rebinding and privilege escalation attacks.
|
||||||
|
|
||||||
.SH EXIT STATUS
|
.SH EXIT STATUS
|
||||||
.BR 0 " Normal exit"
|
.BR 0 " Normal exit"
|
||||||
@@ -959,6 +1012,22 @@ e.g.
|
|||||||
.br
|
.br
|
||||||
\fIdouble-click\fR
|
\fIdouble-click\fR
|
||||||
.br
|
.br
|
||||||
|
\fIscroll-up\fR
|
||||||
|
.br
|
||||||
|
\fIscroll-down\fR
|
||||||
|
.br
|
||||||
|
\fIpreview-scroll-up\fR
|
||||||
|
.br
|
||||||
|
\fIpreview-scroll-down\fR
|
||||||
|
.br
|
||||||
|
\fIshift-left-click\fR
|
||||||
|
.br
|
||||||
|
\fIshift-right-click\fR
|
||||||
|
.br
|
||||||
|
\fIshift-scroll-up\fR
|
||||||
|
.br
|
||||||
|
\fIshift-scroll-down\fR
|
||||||
|
.br
|
||||||
or any single character
|
or any single character
|
||||||
|
|
||||||
.SS AVAILABLE EVENTS:
|
.SS AVAILABLE EVENTS:
|
||||||
@@ -1088,6 +1157,8 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBhalf-page-down\fR
|
\fBhalf-page-down\fR
|
||||||
\fBhalf-page-up\fR
|
\fBhalf-page-up\fR
|
||||||
\fBhide-preview\fR
|
\fBhide-preview\fR
|
||||||
|
\fBoffset-down\fR (similar to CTRL-E of Vim)
|
||||||
|
\fBoffset-up\fR (similar to CTRL-Y of Vim)
|
||||||
\fBpos(...)\fR (move cursor to the numeric position; negative number to count from the end)
|
\fBpos(...)\fR (move cursor to the numeric position; negative number to count from the end)
|
||||||
\fBprev-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
\fBprev-history\fR (\fIctrl-p\fR on \fB--history\fR)
|
||||||
\fBprev-selected\fR (move to the previous selected item)
|
\fBprev-selected\fR (move to the previous selected item)
|
||||||
@@ -1114,6 +1185,7 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBtoggle\fR (\fIright-click\fR)
|
\fBtoggle\fR (\fIright-click\fR)
|
||||||
\fBtoggle-all\fR (toggle all matches)
|
\fBtoggle-all\fR (toggle all matches)
|
||||||
\fBtoggle+down\fR \fIctrl-i (tab)\fR
|
\fBtoggle+down\fR \fIctrl-i (tab)\fR
|
||||||
|
\fBtoggle-header\fR
|
||||||
\fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
\fBtoggle-in\fR (\fB--layout=reverse*\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
||||||
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
\fBtoggle-out\fR (\fB--layout=reverse*\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
||||||
\fBtoggle-preview\fR
|
\fBtoggle-preview\fR
|
||||||
|
@@ -456,6 +456,30 @@ function! s:writefile(...)
|
|||||||
endif
|
endif
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
function! s:extract_option(opts, name)
|
||||||
|
let opt = ''
|
||||||
|
let expect = 0
|
||||||
|
" There are a few cases where this function doesn't work as expected.
|
||||||
|
" Let's just assume such cases are extremely unlikely in real world.
|
||||||
|
" e.g. --query --border
|
||||||
|
for word in split(a:opts)
|
||||||
|
if expect && word !~ '^"\=-'
|
||||||
|
let opt = opt . ' ' . word
|
||||||
|
let expect = 0
|
||||||
|
elseif word == '--no-'.a:name
|
||||||
|
let opt = ''
|
||||||
|
elseif word =~ '^--'.a:name.'='
|
||||||
|
let opt = word
|
||||||
|
elseif word =~ '^--'.a:name.'$'
|
||||||
|
let opt = word
|
||||||
|
let expect = 1
|
||||||
|
elseif expect
|
||||||
|
let expect = 0
|
||||||
|
endif
|
||||||
|
endfor
|
||||||
|
return opt
|
||||||
|
endfunction
|
||||||
|
|
||||||
function! fzf#run(...) abort
|
function! fzf#run(...) abort
|
||||||
try
|
try
|
||||||
let [shell, shellslash, shellcmdflag, shellxquote] = s:use_sh()
|
let [shell, shellslash, shellcmdflag, shellxquote] = s:use_sh()
|
||||||
@@ -511,8 +535,8 @@ try
|
|||||||
let height = s:calc_size(&lines, dict.down, dict)
|
let height = s:calc_size(&lines, dict.down, dict)
|
||||||
let optstr .= ' --height='.height
|
let optstr .= ' --height='.height
|
||||||
endif
|
endif
|
||||||
" Respect --border option given in 'options'
|
" Respect --border option given in $FZF_DEFAULT_OPTS and 'options'
|
||||||
let optstr = join([s:border_opt(get(dict, 'window', 0)), optstr])
|
let optstr = join([s:border_opt(get(dict, 'window', 0)), s:extract_option($FZF_DEFAULT_OPTS, 'border'), optstr])
|
||||||
let prev_default_command = $FZF_DEFAULT_COMMAND
|
let prev_default_command = $FZF_DEFAULT_COMMAND
|
||||||
if len(source_command)
|
if len(source_command)
|
||||||
let $FZF_DEFAULT_COMMAND = source_command
|
let $FZF_DEFAULT_COMMAND = source_command
|
||||||
@@ -921,7 +945,7 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
let term_opts.curwin = 1
|
let term_opts.curwin = 1
|
||||||
endif
|
endif
|
||||||
call s:handle_ambidouble(term_opts)
|
call s:handle_ambidouble(term_opts)
|
||||||
let fzf.buf = term_start([&shell, &shellcmdflag, command], term_opts)
|
keepjumps let fzf.buf = term_start([&shell, &shellcmdflag, command], term_opts)
|
||||||
if is_popup && exists('#TerminalWinOpen')
|
if is_popup && exists('#TerminalWinOpen')
|
||||||
doautocmd <nomodeline> TerminalWinOpen
|
doautocmd <nomodeline> TerminalWinOpen
|
||||||
endif
|
endif
|
||||||
|
@@ -9,23 +9,24 @@
|
|||||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||||
|
|
||||||
if [[ $- =~ i ]]; then
|
[[ $- =~ i ]] || return 0
|
||||||
|
|
||||||
|
|
||||||
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
||||||
if ! declare -f _fzf_compgen_path > /dev/null; then
|
if ! declare -F _fzf_compgen_path > /dev/null; then
|
||||||
_fzf_compgen_path() {
|
_fzf_compgen_path() {
|
||||||
echo "$1"
|
echo "$1"
|
||||||
command find -L "$1" \
|
command find -L "$1" \
|
||||||
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
||||||
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
-a -not -path "$1" -print 2> /dev/null | command sed 's@^\./@@'
|
||||||
}
|
}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! declare -f _fzf_compgen_dir > /dev/null; then
|
if ! declare -F _fzf_compgen_dir > /dev/null; then
|
||||||
_fzf_compgen_dir() {
|
_fzf_compgen_dir() {
|
||||||
command find -L "$1" \
|
command find -L "$1" \
|
||||||
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
|
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
|
||||||
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
-a -not -path "$1" -print 2> /dev/null | command sed 's@^\./@@'
|
||||||
}
|
}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -68,59 +69,185 @@ _fzf_opts_completion() {
|
|||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||||
opts="
|
opts="
|
||||||
|
-h --help
|
||||||
-x --extended
|
-x --extended
|
||||||
-e --exact
|
-e --exact
|
||||||
|
--extended-exact
|
||||||
|
+x --no-extended
|
||||||
|
+e --no-exact
|
||||||
|
-q --query
|
||||||
|
-f --filter
|
||||||
|
--literal
|
||||||
|
--no-literal
|
||||||
--algo
|
--algo
|
||||||
-i +i
|
--scheme
|
||||||
|
--expect
|
||||||
|
--no-expect
|
||||||
|
--enabled --no-phony
|
||||||
|
--disabled --phony
|
||||||
|
--tiebreak
|
||||||
|
--bind
|
||||||
|
--color
|
||||||
|
--toggle-sort
|
||||||
|
-d --delimiter
|
||||||
-n --nth
|
-n --nth
|
||||||
--with-nth
|
--with-nth
|
||||||
-d --delimiter
|
-s --sort
|
||||||
+s --no-sort
|
+s --no-sort
|
||||||
|
--track
|
||||||
|
--no-track
|
||||||
--tac
|
--tac
|
||||||
--tiebreak
|
--no-tac
|
||||||
|
-i
|
||||||
|
+i
|
||||||
-m --multi
|
-m --multi
|
||||||
|
+m --no-multi
|
||||||
|
--ansi
|
||||||
|
--no-ansi
|
||||||
--no-mouse
|
--no-mouse
|
||||||
--bind
|
+c --no-color
|
||||||
--cycle
|
+2 --no-256
|
||||||
--no-hscroll
|
--black
|
||||||
--jump-labels
|
--no-black
|
||||||
--height
|
--bold
|
||||||
--literal
|
--no-bold
|
||||||
|
--layout
|
||||||
--reverse
|
--reverse
|
||||||
--margin
|
--no-reverse
|
||||||
|
--cycle
|
||||||
|
--no-cycle
|
||||||
|
--keep-right
|
||||||
|
--no-keep-right
|
||||||
|
--hscroll
|
||||||
|
--no-hscroll
|
||||||
|
--hscroll-off
|
||||||
|
--scroll-off
|
||||||
|
--filepath-word
|
||||||
|
--no-filepath-word
|
||||||
|
--info
|
||||||
|
--no-info
|
||||||
--inline-info
|
--inline-info
|
||||||
|
--no-inline-info
|
||||||
|
--separator
|
||||||
|
--no-separator
|
||||||
|
--scrollbar
|
||||||
|
--no-scrollbar
|
||||||
|
--jump-labels
|
||||||
|
-1 --select-1
|
||||||
|
+1 --no-select-1
|
||||||
|
-0 --exit-0
|
||||||
|
+0 --no-exit-0
|
||||||
|
--read0
|
||||||
|
--no-read0
|
||||||
|
--print0
|
||||||
|
--no-print0
|
||||||
|
--print-query
|
||||||
|
--no-print-query
|
||||||
--prompt
|
--prompt
|
||||||
--pointer
|
--pointer
|
||||||
--marker
|
--marker
|
||||||
--header
|
--sync
|
||||||
--header-lines
|
--no-sync
|
||||||
--ansi
|
--async
|
||||||
--tabstop
|
--no-history
|
||||||
--color
|
|
||||||
--no-bold
|
|
||||||
--history
|
--history
|
||||||
--history-size
|
--history-size
|
||||||
|
--no-header
|
||||||
|
--no-header-lines
|
||||||
|
--header
|
||||||
|
--header-lines
|
||||||
|
--header-first
|
||||||
|
--no-header-first
|
||||||
|
--ellipsis
|
||||||
--preview
|
--preview
|
||||||
|
--no-preview
|
||||||
--preview-window
|
--preview-window
|
||||||
-q --query
|
--height
|
||||||
-1 --select-1
|
--min-height
|
||||||
-0 --exit-0
|
--no-height
|
||||||
-f --filter
|
--no-margin
|
||||||
--print-query
|
--no-padding
|
||||||
--expect
|
--no-border
|
||||||
--sync"
|
--border
|
||||||
|
--no-border-label
|
||||||
|
--border-label
|
||||||
|
--border-label-pos
|
||||||
|
--no-preview-label
|
||||||
|
--preview-label
|
||||||
|
--preview-label-pos
|
||||||
|
--no-unicode
|
||||||
|
--unicode
|
||||||
|
--margin
|
||||||
|
--padding
|
||||||
|
--tabstop
|
||||||
|
--listen
|
||||||
|
--no-listen
|
||||||
|
--clear
|
||||||
|
--no-clear
|
||||||
|
--version
|
||||||
|
--"
|
||||||
|
|
||||||
case "${prev}" in
|
case "${prev}" in
|
||||||
|
--algo)
|
||||||
|
COMPREPLY=( $(compgen -W "v1 v2" -- "$cur") )
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--scheme)
|
||||||
|
COMPREPLY=( $(compgen -W "default path history" -- "$cur") )
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
--tiebreak)
|
--tiebreak)
|
||||||
COMPREPLY=( $(compgen -W "length begin end index" -- "$cur") )
|
COMPREPLY=( $(compgen -W "length chunk begin end index" -- "$cur") )
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
--color)
|
--color)
|
||||||
COMPREPLY=( $(compgen -W "dark light 16 bw" -- "$cur") )
|
COMPREPLY=( $(compgen -W "dark light 16 bw no" -- "$cur") )
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
--history)
|
--layout)
|
||||||
COMPREPLY=()
|
COMPREPLY=( $(compgen -W "default reverse reverse-list" -- "$cur") )
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--info)
|
||||||
|
COMPREPLY=( $(compgen -W "default right hidden inline inline-right" -- "$cur") )
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--preview-window)
|
||||||
|
COMPREPLY=( $(compgen -W "
|
||||||
|
default
|
||||||
|
hidden
|
||||||
|
nohidden
|
||||||
|
wrap
|
||||||
|
nowrap
|
||||||
|
cycle
|
||||||
|
nocycle
|
||||||
|
up top
|
||||||
|
down bottom
|
||||||
|
left
|
||||||
|
right
|
||||||
|
rounded border border-rounded
|
||||||
|
sharp border-sharp
|
||||||
|
border-bold
|
||||||
|
border-block
|
||||||
|
border-thinblock
|
||||||
|
border-double
|
||||||
|
noborder border-none
|
||||||
|
border-horizontal
|
||||||
|
border-vertical
|
||||||
|
border-up border-top
|
||||||
|
border-down border-bottom
|
||||||
|
border-left
|
||||||
|
border-right
|
||||||
|
follow
|
||||||
|
nofollow" -- "$cur") )
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--border)
|
||||||
|
COMPREPLY=( $(compgen -W "rounded sharp bold block thinblock double horizontal vertical top bottom left right none" -- "$cur") )
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--border-label-pos|--preview-label-pos)
|
||||||
|
COMPREPLY=( $(compgen -W "center bottom top" -- "$cur") )
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@@ -170,9 +297,9 @@ __fzf_generic_path_completion() {
|
|||||||
COMPREPLY=()
|
COMPREPLY=()
|
||||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
if [[ "$cur" == *"$trigger" ]]; then
|
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
|
||||||
base=${cur:0:${#cur}-${#trigger}}
|
base=${cur:0:${#cur}-${#trigger}}
|
||||||
eval "base=$base"
|
eval "base=$base" 2> /dev/null || return
|
||||||
|
|
||||||
dir=
|
dir=
|
||||||
[[ $base = *"/"* ]] && dir="$base"
|
[[ $base = *"/"* ]] && dir="$base"
|
||||||
@@ -182,7 +309,7 @@ __fzf_generic_path_completion() {
|
|||||||
leftover=${leftover/#\/}
|
leftover=${leftover/#\/}
|
||||||
[[ -z "$dir" ]] && dir='.'
|
[[ -z "$dir" ]] && dir='.'
|
||||||
[[ "$dir" != "/" ]] && dir="${dir/%\//}"
|
[[ "$dir" != "/" ]] && dir="${dir/%\//}"
|
||||||
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
|
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
|
||||||
printf "%q " "${item%$3}$3"
|
printf "%q " "${item%$3}$3"
|
||||||
done)
|
done)
|
||||||
matches=${matches% }
|
matches=${matches% }
|
||||||
@@ -195,7 +322,7 @@ __fzf_generic_path_completion() {
|
|||||||
printf '\e[5n'
|
printf '\e[5n'
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
dir=$(dirname "$dir")
|
dir=$(command dirname "$dir")
|
||||||
[[ "$dir" =~ /$ ]] || dir="$dir"/
|
[[ "$dir" =~ /$ ]] || dir="$dir"/
|
||||||
done
|
done
|
||||||
else
|
else
|
||||||
@@ -229,16 +356,16 @@ _fzf_complete() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
local cur selected trigger cmd post
|
local cur selected trigger cmd post
|
||||||
post="$(caller 0 | awk '{print $2}')_post"
|
post="$(caller 0 | command awk '{print $2}')_post"
|
||||||
type -t "$post" > /dev/null 2>&1 || post=cat
|
type -t "$post" > /dev/null 2>&1 || post='command cat'
|
||||||
|
|
||||||
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
|
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
|
||||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
if [[ "$cur" == *"$trigger" ]]; then
|
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
|
||||||
cur=${cur:0:${#cur}-${#trigger}}
|
cur=${cur:0:${#cur}-${#trigger}}
|
||||||
|
|
||||||
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | tr '\n' ' ')
|
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | command tr '\n' ' ')
|
||||||
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
|
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
|
||||||
if [[ -n "$selected" ]]; then
|
if [[ -n "$selected" ]]; then
|
||||||
COMPREPLY=("$selected")
|
COMPREPLY=("$selected")
|
||||||
@@ -277,27 +404,61 @@ _fzf_proc_completion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_fzf_proc_completion_post() {
|
_fzf_proc_completion_post() {
|
||||||
awk '{print $2}'
|
command awk '{print $2}'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# To use custom hostname lists, override __fzf_list_hosts.
|
||||||
|
# The function is expected to print hostnames, one per line as well as in the
|
||||||
|
# desired sorting and with any duplicates removed, to standard output.
|
||||||
|
#
|
||||||
|
# e.g.
|
||||||
|
# # Use bash-completions’s _known_hosts_real() for getting the list of hosts
|
||||||
|
# __fzf_list_hosts() {
|
||||||
|
# # Set the local attribute for any non-local variable that is set by _known_hosts_real()
|
||||||
|
# local COMPREPLY=()
|
||||||
|
# _known_hosts_real ''
|
||||||
|
# printf '%s\n' "${COMPREPLY[@]}" | command sort -u --version-sort
|
||||||
|
# }
|
||||||
|
if ! declare -F __fzf_list_hosts > /dev/null; then
|
||||||
|
__fzf_list_hosts() {
|
||||||
|
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | command awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?%]') \
|
||||||
|
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts 2> /dev/null | command tr ',' '\n' | command tr -d '[' | command awk '{ print $1 " " $1 }') \
|
||||||
|
<(command grep -v '^\s*\(#\|$\)' /etc/hosts 2> /dev/null | command grep -Fv '0.0.0.0') |
|
||||||
|
command awk '{if (length($2) > 0) {print $2}}' | command sort -u
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
_fzf_host_completion() {
|
_fzf_host_completion() {
|
||||||
_fzf_complete +m -- "$@" < <(
|
_fzf_complete +m -- "$@" < <(__fzf_list_hosts)
|
||||||
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?%]') \
|
}
|
||||||
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
|
||||||
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
# Values for $1 $2 $3 are described here
|
||||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
# https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion.html
|
||||||
)
|
# > the first argument ($1) is the name of the command whose arguments are being completed,
|
||||||
|
# > the second argument ($2) is the word being completed,
|
||||||
|
# > and the third argument ($3) is the word preceding the word being completed on the current command line.
|
||||||
|
_fzf_complete_ssh() {
|
||||||
|
case $3 in
|
||||||
|
-i|-F|-E)
|
||||||
|
_fzf_path_completion "$@"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
local user=
|
||||||
|
[[ "$2" =~ '@' ]] && user="${2%%@*}@"
|
||||||
|
_fzf_complete +m -- "$@" < <(__fzf_list_hosts | command awk -v user="$user" '{print user $0}')
|
||||||
|
;;
|
||||||
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_var_completion() {
|
_fzf_var_completion() {
|
||||||
_fzf_complete -m -- "$@" < <(
|
_fzf_complete -m -- "$@" < <(
|
||||||
declare -xp | sed -En 's|^declare [^ ]+ ([^=]+).*|\1|p'
|
declare -xp | command sed -En 's|^declare [^ ]+ ([^=]+).*|\1|p'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_alias_completion() {
|
_fzf_alias_completion() {
|
||||||
_fzf_complete -m -- "$@" < <(
|
_fzf_complete -m -- "$@" < <(
|
||||||
alias | sed -En 's|^alias ([^=]+).*|\1|p'
|
alias | command sed -En 's|^alias ([^=]+).*|\1|p'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,6 +512,9 @@ for cmd in $d_cmds; do
|
|||||||
__fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames"
|
__fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# ssh
|
||||||
|
__fzf_defc ssh _fzf_complete_ssh "-o default -o bashdefault"
|
||||||
|
|
||||||
unset cmd d_cmds a_cmds
|
unset cmd d_cmds a_cmds
|
||||||
|
|
||||||
_fzf_setup_completion() {
|
_fzf_setup_completion() {
|
||||||
@@ -376,7 +540,5 @@ _fzf_setup_completion() {
|
|||||||
# Environment variables / Aliases / Hosts / Process
|
# Environment variables / Aliases / Hosts / Process
|
||||||
_fzf_setup_completion 'var' export unset printenv
|
_fzf_setup_completion 'var' export unset printenv
|
||||||
_fzf_setup_completion 'alias' unalias
|
_fzf_setup_completion 'alias' unalias
|
||||||
_fzf_setup_completion 'host' ssh telnet
|
_fzf_setup_completion 'host' telnet
|
||||||
_fzf_setup_completion 'proc' kill
|
_fzf_setup_completion 'proc' kill
|
||||||
|
|
||||||
fi
|
|
||||||
|
@@ -9,6 +9,9 @@
|
|||||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||||
|
|
||||||
|
[[ -o interactive ]] || return 0
|
||||||
|
|
||||||
|
|
||||||
# Both branches of the following `if` do the same thing -- define
|
# Both branches of the following `if` do the same thing -- define
|
||||||
# __fzf_completion_options such that `eval $__fzf_completion_options` sets
|
# __fzf_completion_options such that `eval $__fzf_completion_options` sets
|
||||||
# all options to the same values they currently have. We'll do just that at
|
# all options to the same values they currently have. We'll do just that at
|
||||||
@@ -67,15 +70,12 @@ fi
|
|||||||
# control. There are several others that could wreck havoc if they are set
|
# control. There are several others that could wreck havoc if they are set
|
||||||
# to values we don't expect. With the following `emulate` command we
|
# to values we don't expect. With the following `emulate` command we
|
||||||
# sidestep this issue entirely.
|
# sidestep this issue entirely.
|
||||||
'emulate' 'zsh' '-o' 'no_aliases'
|
'builtin' 'emulate' 'zsh' && 'builtin' 'setopt' 'no_aliases'
|
||||||
|
|
||||||
# This brace is the start of try-always block. The `always` part is like
|
# This brace is the start of try-always block. The `always` part is like
|
||||||
# `finally` in lesser languages. We use it to *always* restore user options.
|
# `finally` in lesser languages. We use it to *always* restore user options.
|
||||||
{
|
{
|
||||||
|
|
||||||
# Bail out if not interactive shell.
|
|
||||||
[[ -o interactive ]] || return 0
|
|
||||||
|
|
||||||
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
||||||
if ! declare -f _fzf_compgen_path > /dev/null; then
|
if ! declare -f _fzf_compgen_path > /dev/null; then
|
||||||
_fzf_compgen_path() {
|
_fzf_compgen_path() {
|
||||||
@@ -137,7 +137,10 @@ __fzf_generic_path_completion() {
|
|||||||
tail=$6
|
tail=$6
|
||||||
|
|
||||||
setopt localoptions nonomatch
|
setopt localoptions nonomatch
|
||||||
eval "base=$base"
|
if [[ $base = *'$('* ]] || [[ $base = *'<('* ]] || [[ $base = *'>('* ]] || [[ $base = *':='* ]] || [[ $base = *'`'* ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
eval "base=$base" 2> /dev/null || return
|
||||||
[[ $base = *"/"* ]] && dir="$base"
|
[[ $base = *"/"* ]] && dir="$base"
|
||||||
while [ 1 ]; do
|
while [ 1 ]; do
|
||||||
if [[ -z "$dir" || -d ${dir} ]]; then
|
if [[ -z "$dir" || -d ${dir} ]]; then
|
||||||
@@ -145,7 +148,7 @@ __fzf_generic_path_completion() {
|
|||||||
leftover=${leftover/#\/}
|
leftover=${leftover/#\/}
|
||||||
[ -z "$dir" ] && dir='.'
|
[ -z "$dir" ] && dir='.'
|
||||||
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
||||||
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-}" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
|
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-}" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
|
||||||
item="${item%$suffix}$suffix"
|
item="${item%$suffix}$suffix"
|
||||||
echo -n "${(q)item} "
|
echo -n "${(q)item} "
|
||||||
done)
|
done)
|
||||||
@@ -215,21 +218,37 @@ _fzf_complete() {
|
|||||||
command rm -f "$fifo"
|
command rm -f "$fifo"
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_telnet() {
|
# To use custom hostname lists, override __fzf_list_hosts.
|
||||||
_fzf_complete +m -- "$@" < <(
|
# The function is expected to print hostnames, one per line as well as in the
|
||||||
command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' |
|
# desired sorting and with any duplicates removed, to standard output.
|
||||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
if ! declare -f __fzf_list_hosts > /dev/null; then
|
||||||
)
|
__fzf_list_hosts() {
|
||||||
}
|
|
||||||
|
|
||||||
_fzf_complete_ssh() {
|
|
||||||
_fzf_complete +m -- "$@" < <(
|
|
||||||
setopt localoptions nonomatch
|
setopt localoptions nonomatch
|
||||||
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?%]') \
|
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?%]') \
|
||||||
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts 2> /dev/null | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
||||||
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
<(command grep -v '^\s*\(#\|$\)' /etc/hosts 2> /dev/null | command grep -Fv '0.0.0.0') |
|
||||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
||||||
)
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
_fzf_complete_telnet() {
|
||||||
|
_fzf_complete +m -- "$@" < <(__fzf_list_hosts)
|
||||||
|
}
|
||||||
|
|
||||||
|
# The first and the only argument is the LBUFFER without the current word that contains the trigger.
|
||||||
|
# The current word without the trigger is in the $prefix variable passed from the caller.
|
||||||
|
_fzf_complete_ssh() {
|
||||||
|
local tokens=(${(z)1})
|
||||||
|
case ${tokens[-1]} in
|
||||||
|
-i|-F|-E)
|
||||||
|
_fzf_path_completion "$prefix" "$1"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
local user=
|
||||||
|
[[ $prefix =~ @ ]] && user="${prefix%%@*}@"
|
||||||
|
_fzf_complete +m -- "$@" < <(__fzf_list_hosts | awk -v user="$user" '{print user $0}')
|
||||||
|
;;
|
||||||
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_export() {
|
_fzf_complete_export() {
|
||||||
@@ -293,6 +312,9 @@ fzf-completion() {
|
|||||||
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})
|
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})
|
||||||
|
|
||||||
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
|
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
|
||||||
|
if [[ $prefix = *'$('* ]] || [[ $prefix = *'<('* ]] || [[ $prefix = *'>('* ]] || [[ $prefix = *':='* ]] || [[ $prefix = *'`'* ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
[ -n "${tokens[-1]}" ] && lbuf=${lbuf:0:-${#tokens[-1]}}
|
[ -n "${tokens[-1]}" ] && lbuf=${lbuf:0:-${#tokens[-1]}}
|
||||||
|
|
||||||
if eval "type _fzf_complete_${cmd} > /dev/null"; then
|
if eval "type _fzf_complete_${cmd} > /dev/null"; then
|
||||||
|
@@ -11,15 +11,18 @@
|
|||||||
# - $FZF_ALT_C_COMMAND
|
# - $FZF_ALT_C_COMMAND
|
||||||
# - $FZF_ALT_C_OPTS
|
# - $FZF_ALT_C_OPTS
|
||||||
|
|
||||||
|
[[ $- =~ i ]] || return 0
|
||||||
|
|
||||||
|
|
||||||
# Key bindings
|
# Key bindings
|
||||||
# ------------
|
# ------------
|
||||||
__fzf_select__() {
|
__fzf_select__() {
|
||||||
local cmd opts
|
local cmd opts
|
||||||
cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||||
-o -type f -print \
|
-o -type f -print \
|
||||||
-o -type d -print \
|
-o -type d -print \
|
||||||
-o -type l -print 2> /dev/null | cut -b3-"}"
|
-o -type l -print 2> /dev/null | command cut -b3-"}"
|
||||||
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-} -m"
|
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse --scheme=path ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-} -m"
|
||||||
eval "$cmd" |
|
eval "$cmd" |
|
||||||
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) "$@" |
|
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) "$@" |
|
||||||
while read -r item; do
|
while read -r item; do
|
||||||
@@ -27,8 +30,6 @@ __fzf_select__() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ $- =~ i ]]; then
|
|
||||||
|
|
||||||
__fzfcmd() {
|
__fzfcmd() {
|
||||||
[[ -n "${TMUX_PANE-}" ]] && { [[ "${FZF_TMUX:-0}" != 0 ]] || [[ -n "${FZF_TMUX_OPTS-}" ]]; } &&
|
[[ -n "${TMUX_PANE-}" ]] && { [[ "${FZF_TMUX:-0}" != 0 ]] || [[ -n "${FZF_TMUX_OPTS-}" ]]; } &&
|
||||||
echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
|
echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
|
||||||
@@ -42,28 +43,60 @@ fzf-file-widget() {
|
|||||||
|
|
||||||
__fzf_cd__() {
|
__fzf_cd__() {
|
||||||
local cmd opts dir
|
local cmd opts dir
|
||||||
cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||||
-o -type d -print 2> /dev/null | cut -b3-"}"
|
-o -type d -print 2> /dev/null | command cut -b3-"}"
|
||||||
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-} +m"
|
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse --scheme=path ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-} +m"
|
||||||
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="$opts" $(__fzfcmd)) && printf 'builtin cd -- %q' "$dir"
|
dir=$(set +o pipefail; eval "$cmd" | FZF_DEFAULT_OPTS="$opts" $(__fzfcmd)) && printf 'builtin cd -- %q' "$dir"
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzf_history__() {
|
if command -v perl > /dev/null; then
|
||||||
local output opts script
|
__fzf_history__() {
|
||||||
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort ${FZF_CTRL_R_OPTS-} +m --read0"
|
local output opts script
|
||||||
script='BEGIN { getc; $/ = "\n\t"; $HISTCOUNT = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCOUNT - $. . "\t$_" if !$seen{$_}++'
|
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort ${FZF_CTRL_R_OPTS-} +m --read0"
|
||||||
output=$(
|
script='BEGIN { getc; $/ = "\n\t"; $HISTCOUNT = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCOUNT - $. . "\t$_" if !$seen{$_}++'
|
||||||
builtin fc -lnr -2147483648 |
|
output=$(
|
||||||
last_hist=$(HISTTIMEFORMAT='' builtin history 1) perl -n -l0 -e "$script" |
|
set +o pipefail
|
||||||
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) --query "$READLINE_LINE"
|
builtin fc -lnr -2147483648 |
|
||||||
) || return
|
last_hist=$(HISTTIMEFORMAT='' builtin history 1) command perl -n -l0 -e "$script" |
|
||||||
READLINE_LINE=${output#*$'\t'}
|
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) --query "$READLINE_LINE"
|
||||||
if [[ -z "$READLINE_POINT" ]]; then
|
) || return
|
||||||
echo "$READLINE_LINE"
|
READLINE_LINE=${output#*$'\t'}
|
||||||
else
|
if [[ -z "$READLINE_POINT" ]]; then
|
||||||
READLINE_POINT=0x7fffffff
|
echo "$READLINE_LINE"
|
||||||
fi
|
else
|
||||||
}
|
READLINE_POINT=0x7fffffff
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
else # awk - fallback for POSIX systems
|
||||||
|
__fzf_history__() {
|
||||||
|
local output opts script n x y z d
|
||||||
|
if [[ -z $__fzf_awk ]]; then
|
||||||
|
__fzf_awk=awk
|
||||||
|
# choose the faster mawk if: it's installed && build date >= 20230322 && version >= 1.3.4
|
||||||
|
IFS=' .' read n x y z d <<< $(command mawk -W version 2> /dev/null)
|
||||||
|
[[ $n == mawk ]] && (( d >= 20230302 && (x *1000 +y) *1000 +z >= 1003004 )) && __fzf_awk=mawk
|
||||||
|
fi
|
||||||
|
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort ${FZF_CTRL_R_OPTS-} +m --read0"
|
||||||
|
[[ $(HISTTIMEFORMAT='' builtin history 1) =~ [[:digit:]]+ ]] # how many history entries
|
||||||
|
script='function P(b) { ++n; sub(/^[ *]/, "", b); if (!seen[b]++) { printf "%d\t%s%c", '$((BASH_REMATCH + 1))' - n, b, 0 } }
|
||||||
|
NR==1 { b = substr($0, 2); next }
|
||||||
|
/^\t/ { P(b); b = substr($0, 2); next }
|
||||||
|
{ b = b RS $0 }
|
||||||
|
END { if (NR) P(b) }'
|
||||||
|
output=$(
|
||||||
|
set +o pipefail
|
||||||
|
builtin fc -lnr -2147483648 2> /dev/null | # ( $'\t '<lines>$'\n' )* ; <lines> ::= [^\n]* ( $'\n'<lines> )*
|
||||||
|
command $__fzf_awk "$script" | # ( <counter>$'\t'<lines>$'\000' )*
|
||||||
|
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) --query "$READLINE_LINE"
|
||||||
|
) || return
|
||||||
|
READLINE_LINE=${output#*$'\t'}
|
||||||
|
if [[ -z "$READLINE_POINT" ]]; then
|
||||||
|
echo "$READLINE_LINE"
|
||||||
|
else
|
||||||
|
READLINE_POINT=0x7fffffff
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
# Required to refresh the prompt after fzf
|
# Required to refresh the prompt after fzf
|
||||||
bind -m emacs-standard '"\er": redraw-current-line'
|
bind -m emacs-standard '"\er": redraw-current-line'
|
||||||
@@ -79,7 +112,7 @@ if (( BASH_VERSINFO[0] < 4 )); then
|
|||||||
bind -m vi-insert '"\C-t": "\C-z\C-t\C-z"'
|
bind -m vi-insert '"\C-t": "\C-z\C-t\C-z"'
|
||||||
|
|
||||||
# CTRL-R - Paste the selected command from history into the command line
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
bind -m emacs-standard '"\C-r": "\C-e \C-u\C-y\ey\C-u"$(__fzf_history__)"\e\C-e\er"'
|
bind -m emacs-standard '"\C-r": "\C-e \C-u\C-y\ey\C-u`__fzf_history__`\e\C-e\er"'
|
||||||
bind -m vi-command '"\C-r": "\C-z\C-r\C-z"'
|
bind -m vi-command '"\C-r": "\C-z\C-r\C-z"'
|
||||||
bind -m vi-insert '"\C-r": "\C-z\C-r\C-z"'
|
bind -m vi-insert '"\C-r": "\C-z\C-r\C-z"'
|
||||||
else
|
else
|
||||||
@@ -98,5 +131,3 @@ fi
|
|||||||
bind -m emacs-standard '"\ec": " \C-b\C-k \C-u`__fzf_cd__`\e\C-e\er\C-m\C-y\C-h\e \C-y\ey\C-x\C-x\C-d"'
|
bind -m emacs-standard '"\ec": " \C-b\C-k \C-u`__fzf_cd__`\e\C-e\er\C-m\C-y\C-h\e \C-y\ey\C-x\C-x\C-d"'
|
||||||
bind -m vi-command '"\ec": "\C-z\ec\C-z"'
|
bind -m vi-command '"\ec": "\C-z\ec\C-z"'
|
||||||
bind -m vi-insert '"\ec": "\C-z\ec\C-z"'
|
bind -m vi-insert '"\ec": "\C-z\ec\C-z"'
|
||||||
|
|
||||||
fi
|
|
||||||
|
@@ -11,6 +11,9 @@
|
|||||||
# - $FZF_ALT_C_COMMAND
|
# - $FZF_ALT_C_COMMAND
|
||||||
# - $FZF_ALT_C_OPTS
|
# - $FZF_ALT_C_OPTS
|
||||||
|
|
||||||
|
status is-interactive; or exit 0
|
||||||
|
|
||||||
|
|
||||||
# Key bindings
|
# Key bindings
|
||||||
# ------------
|
# ------------
|
||||||
function fzf_key_bindings
|
function fzf_key_bindings
|
||||||
@@ -22,17 +25,17 @@ function fzf_key_bindings
|
|||||||
set -l fzf_query $commandline[2]
|
set -l fzf_query $commandline[2]
|
||||||
set -l prefix $commandline[3]
|
set -l prefix $commandline[3]
|
||||||
|
|
||||||
# "-path \$dir'*/\\.*'" matches hidden files/folders inside $dir but not
|
# "-path \$dir'*/.*'" matches hidden files/folders inside $dir but not
|
||||||
# $dir itself, even if hidden.
|
# $dir itself, even if hidden.
|
||||||
test -n "$FZF_CTRL_T_COMMAND"; or set -l FZF_CTRL_T_COMMAND "
|
test -n "$FZF_CTRL_T_COMMAND"; or set -l FZF_CTRL_T_COMMAND "
|
||||||
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
command find -L \$dir -mindepth 1 \\( -path \$dir'*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
||||||
-o -type f -print \
|
-o -type f -print \
|
||||||
-o -type d -print \
|
-o -type d -print \
|
||||||
-o -type l -print 2> /dev/null | sed 's@^\./@@'"
|
-o -type l -print 2> /dev/null | sed 's@^\./@@'"
|
||||||
|
|
||||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||||
begin
|
begin
|
||||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
|
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
|
||||||
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
|
eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
|
||||||
end
|
end
|
||||||
if [ -z "$result" ]
|
if [ -z "$result" ]
|
||||||
@@ -79,11 +82,11 @@ function fzf_key_bindings
|
|||||||
set -l prefix $commandline[3]
|
set -l prefix $commandline[3]
|
||||||
|
|
||||||
test -n "$FZF_ALT_C_COMMAND"; or set -l FZF_ALT_C_COMMAND "
|
test -n "$FZF_ALT_C_COMMAND"; or set -l FZF_ALT_C_COMMAND "
|
||||||
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
command find -L \$dir -mindepth 1 \\( -path \$dir'*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
||||||
-o -type d -print 2> /dev/null | sed 's@^\./@@'"
|
-o -type d -print 2> /dev/null | sed 's@^\./@@'"
|
||||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||||
begin
|
begin
|
||||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
|
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
|
||||||
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
||||||
|
|
||||||
if [ -n "$result" ]
|
if [ -n "$result" ]
|
||||||
|
@@ -11,6 +11,9 @@
|
|||||||
# - $FZF_ALT_C_COMMAND
|
# - $FZF_ALT_C_COMMAND
|
||||||
# - $FZF_ALT_C_OPTS
|
# - $FZF_ALT_C_OPTS
|
||||||
|
|
||||||
|
[[ -o interactive ]] || return 0
|
||||||
|
|
||||||
|
|
||||||
# Key bindings
|
# Key bindings
|
||||||
# ------------
|
# ------------
|
||||||
|
|
||||||
@@ -32,21 +35,19 @@ else
|
|||||||
}
|
}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
'emulate' 'zsh' '-o' 'no_aliases'
|
'builtin' 'emulate' 'zsh' && 'builtin' 'setopt' 'no_aliases'
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
||||||
[[ -o interactive ]] || return 0
|
|
||||||
|
|
||||||
# CTRL-T - Paste the selected file path(s) into the command line
|
# CTRL-T - Paste the selected file path(s) into the command line
|
||||||
__fsel() {
|
__fsel() {
|
||||||
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
local cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||||
-o -type f -print \
|
-o -type f -print \
|
||||||
-o -type d -print \
|
-o -type d -print \
|
||||||
-o -type l -print 2> /dev/null | cut -b3-"}"
|
-o -type l -print 2> /dev/null | cut -b3-"}"
|
||||||
setopt localoptions pipefail no_aliases 2> /dev/null
|
setopt localoptions pipefail no_aliases 2> /dev/null
|
||||||
local item
|
local item
|
||||||
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-}" $(__fzfcmd) -m "$@" | while read item; do
|
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-}" $(__fzfcmd) -m "$@" | while read item; do
|
||||||
echo -n "${(q)item} "
|
echo -n "${(q)item} "
|
||||||
done
|
done
|
||||||
local ret=$?
|
local ret=$?
|
||||||
@@ -72,10 +73,10 @@ bindkey -M viins '^T' fzf-file-widget
|
|||||||
|
|
||||||
# ALT-C - cd into the selected directory
|
# ALT-C - cd into the selected directory
|
||||||
fzf-cd-widget() {
|
fzf-cd-widget() {
|
||||||
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||||
-o -type d -print 2> /dev/null | cut -b3-"}"
|
-o -type d -print 2> /dev/null | cut -b3-"}"
|
||||||
setopt localoptions pipefail no_aliases 2> /dev/null
|
setopt localoptions pipefail no_aliases 2> /dev/null
|
||||||
local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-}" $(__fzfcmd) +m)"
|
local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-}" $(__fzfcmd) +m)"
|
||||||
if [[ -z "$dir" ]]; then
|
if [[ -z "$dir" ]]; then
|
||||||
zle redisplay
|
zle redisplay
|
||||||
return 0
|
return 0
|
||||||
|
@@ -221,9 +221,9 @@ func charClassOfAscii(char rune) charClass {
|
|||||||
return charUpper
|
return charUpper
|
||||||
} else if char >= '0' && char <= '9' {
|
} else if char >= '0' && char <= '9' {
|
||||||
return charNumber
|
return charNumber
|
||||||
} else if strings.IndexRune(whiteChars, char) >= 0 {
|
} else if strings.ContainsRune(whiteChars, char) {
|
||||||
return charWhite
|
return charWhite
|
||||||
} else if strings.IndexRune(delimiterChars, char) >= 0 {
|
} else if strings.ContainsRune(delimiterChars, char) {
|
||||||
return charDelimiter
|
return charDelimiter
|
||||||
}
|
}
|
||||||
return charNonWord
|
return charNonWord
|
||||||
@@ -240,7 +240,7 @@ func charClassOfNonAscii(char rune) charClass {
|
|||||||
return charLetter
|
return charLetter
|
||||||
} else if unicode.IsSpace(char) {
|
} else if unicode.IsSpace(char) {
|
||||||
return charWhite
|
return charWhite
|
||||||
} else if strings.IndexRune(delimiterChars, char) >= 0 {
|
} else if strings.ContainsRune(delimiterChars, char) {
|
||||||
return charDelimiter
|
return charDelimiter
|
||||||
}
|
}
|
||||||
return charNonWord
|
return charNonWord
|
||||||
|
@@ -381,10 +381,19 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
|||||||
state.attr = state.attr | tui.Reverse
|
state.attr = state.attr | tui.Reverse
|
||||||
case 9:
|
case 9:
|
||||||
state.attr = state.attr | tui.StrikeThrough
|
state.attr = state.attr | tui.StrikeThrough
|
||||||
|
case 22:
|
||||||
|
state.attr = state.attr &^ tui.Bold
|
||||||
|
state.attr = state.attr &^ tui.Dim
|
||||||
case 23: // tput rmso
|
case 23: // tput rmso
|
||||||
state.attr = state.attr &^ tui.Italic
|
state.attr = state.attr &^ tui.Italic
|
||||||
case 24: // tput rmul
|
case 24: // tput rmul
|
||||||
state.attr = state.attr &^ tui.Underline
|
state.attr = state.attr &^ tui.Underline
|
||||||
|
case 25:
|
||||||
|
state.attr = state.attr &^ tui.Blink
|
||||||
|
case 27:
|
||||||
|
state.attr = state.attr &^ tui.Reverse
|
||||||
|
case 29:
|
||||||
|
state.attr = state.attr &^ tui.StrikeThrough
|
||||||
case 0:
|
case 0:
|
||||||
state.fg = -1
|
state.fg = -1
|
||||||
state.bg = -1
|
state.bg = -1
|
||||||
|
@@ -58,9 +58,9 @@ var defaultCommand string
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if !util.IsWindows() {
|
if !util.IsWindows() {
|
||||||
defaultCommand = `set -o pipefail; command find -L . -mindepth 1 \( -path '*/\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-`
|
defaultCommand = `set -o pipefail; command find -L . -mindepth 1 \( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-`
|
||||||
} else if os.Getenv("TERM") == "cygwin" {
|
} else if os.Getenv("TERM") == "cygwin" {
|
||||||
defaultCommand = `sh -c "command find -L . -mindepth 1 -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-"`
|
defaultCommand = `sh -c "command find -L . -mindepth 1 -path '*/.*' -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-"`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,7 +2,6 @@ package fzf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -26,12 +25,12 @@ func NewHistory(path string, maxSize int) (*History, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read history file
|
// Read history file
|
||||||
data, err := ioutil.ReadFile(path)
|
data, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If it doesn't exist, check if we can create a file with the name
|
// If it doesn't exist, check if we can create a file with the name
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
data = []byte{}
|
data = []byte{}
|
||||||
if err := ioutil.WriteFile(path, data, 0600); err != nil {
|
if err := os.WriteFile(path, data, 0600); err != nil {
|
||||||
return nil, fmtError(err)
|
return nil, fmtError(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -62,11 +61,11 @@ func (h *History) append(line string) error {
|
|||||||
lines = lines[len(lines)-h.maxSize:]
|
lines = lines[len(lines)-h.maxSize:]
|
||||||
}
|
}
|
||||||
h.lines = append(lines, "")
|
h.lines = append(lines, "")
|
||||||
return ioutil.WriteFile(h.path, []byte(strings.Join(h.lines, "\n")), 0600)
|
return os.WriteFile(h.path, []byte(strings.Join(h.lines, "\n")), 0600)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *History) override(str string) {
|
func (h *History) override(str string) {
|
||||||
// You can update the history but they're not written to the file
|
// You can update the history, but they're not written to the file
|
||||||
if h.cursor == len(h.lines)-1 {
|
if h.cursor == len(h.lines)-1 {
|
||||||
h.lines[h.cursor] = str
|
h.lines[h.cursor] = str
|
||||||
} else if h.cursor < len(h.lines)-1 {
|
} else if h.cursor < len(h.lines)-1 {
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -25,7 +24,7 @@ func TestHistory(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f, _ := ioutil.TempFile("", "fzf-history")
|
f, _ := os.CreateTemp("", "fzf-history")
|
||||||
f.Close()
|
f.Close()
|
||||||
|
|
||||||
{ // Append lines
|
{ // Append lines
|
||||||
|
@@ -23,10 +23,10 @@ func randResult() Result {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEmptyMerger(t *testing.T) {
|
func TestEmptyMerger(t *testing.T) {
|
||||||
assert(t, EmptyMerger.Length() == 0, "Not empty")
|
assert(t, EmptyMerger(0).Length() == 0, "Not empty")
|
||||||
assert(t, EmptyMerger.count == 0, "Invalid count")
|
assert(t, EmptyMerger(0).count == 0, "Invalid count")
|
||||||
assert(t, len(EmptyMerger.lists) == 0, "Invalid lists")
|
assert(t, len(EmptyMerger(0).lists) == 0, "Invalid lists")
|
||||||
assert(t, len(EmptyMerger.merged) == 0, "Invalid merged list")
|
assert(t, len(EmptyMerger(0).merged) == 0, "Invalid merged list")
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildLists(partiallySorted bool) ([][]Result, []Result) {
|
func buildLists(partiallySorted bool) ([][]Result, []Result) {
|
||||||
|
@@ -63,7 +63,7 @@ const usage = `usage: fzf [options]
|
|||||||
(default: 10)
|
(default: 10)
|
||||||
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
|
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
|
||||||
--border[=STYLE] Draw border around the finder
|
--border[=STYLE] Draw border around the finder
|
||||||
[rounded|sharp|bold|block|double|horizontal|vertical|
|
[rounded|sharp|bold|block|thinblock|double|horizontal|vertical|
|
||||||
top|bottom|left|right|none] (default: rounded)
|
top|bottom|left|right|none] (default: rounded)
|
||||||
--border-label=LABEL Label to print on the border
|
--border-label=LABEL Label to print on the border
|
||||||
--border-label-pos=COL Position of the border label
|
--border-label-pos=COL Position of the border label
|
||||||
@@ -72,7 +72,8 @@ const usage = `usage: fzf [options]
|
|||||||
(default: 0 or center)
|
(default: 0 or center)
|
||||||
--margin=MARGIN Screen margin (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
--margin=MARGIN Screen margin (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
||||||
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
||||||
--info=STYLE Finder info style [default|hidden|inline|inline:SEPARATOR]
|
--info=STYLE Finder info style
|
||||||
|
[default|right|hidden|inline[:SEPARATOR]|inline-right]
|
||||||
--separator=STR String to form horizontal separator on info line
|
--separator=STR String to form horizontal separator on info line
|
||||||
--no-separator Hide info line separator
|
--no-separator Hide info line separator
|
||||||
--scrollbar[=C1[C2]] Scrollbar character(s) (each for main and preview window)
|
--scrollbar[=C1[C2]] Scrollbar character(s) (each for main and preview window)
|
||||||
@@ -124,6 +125,7 @@ const usage = `usage: fzf [options]
|
|||||||
FZF_DEFAULT_COMMAND Default command to use when input is tty
|
FZF_DEFAULT_COMMAND Default command to use when input is tty
|
||||||
FZF_DEFAULT_OPTS Default options
|
FZF_DEFAULT_OPTS Default options
|
||||||
(e.g. '--layout=reverse --inline-info')
|
(e.g. '--layout=reverse --inline-info')
|
||||||
|
FZF_API_KEY X-API-Key header for HTTP server (--listen)
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -194,10 +196,16 @@ type infoStyle int
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
infoDefault infoStyle = iota
|
infoDefault infoStyle = iota
|
||||||
|
infoRight
|
||||||
infoInline
|
infoInline
|
||||||
|
infoInlineRight
|
||||||
infoHidden
|
infoHidden
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (s infoStyle) noExtraLine() bool {
|
||||||
|
return s == infoInline || s == infoInlineRight || s == infoHidden
|
||||||
|
}
|
||||||
|
|
||||||
type labelOpts struct {
|
type labelOpts struct {
|
||||||
label string
|
label string
|
||||||
column int
|
column int
|
||||||
@@ -546,6 +554,8 @@ func parseBorder(str string, optional bool) tui.BorderShape {
|
|||||||
return tui.BorderBold
|
return tui.BorderBold
|
||||||
case "block":
|
case "block":
|
||||||
return tui.BorderBlock
|
return tui.BorderBlock
|
||||||
|
case "thinblock":
|
||||||
|
return tui.BorderThinBlock
|
||||||
case "double":
|
case "double":
|
||||||
return tui.BorderDouble
|
return tui.BorderDouble
|
||||||
case "horizontal":
|
case "horizontal":
|
||||||
@@ -566,7 +576,7 @@ func parseBorder(str string, optional bool) tui.BorderShape {
|
|||||||
if optional && str == "" {
|
if optional && str == "" {
|
||||||
return tui.DefaultBorderShape
|
return tui.DefaultBorderShape
|
||||||
}
|
}
|
||||||
errorExit("invalid border style (expected: rounded|sharp|bold|block|double|horizontal|vertical|top|bottom|left|right|none)")
|
errorExit("invalid border style (expected: rounded|sharp|bold|block|thinblock|double|horizontal|vertical|top|bottom|left|right|none)")
|
||||||
}
|
}
|
||||||
return tui.BorderNone
|
return tui.BorderNone
|
||||||
}
|
}
|
||||||
@@ -692,8 +702,24 @@ func parseKeyChordsImpl(str string, message string, exit func(string)) map[tui.E
|
|||||||
add(tui.LeftClick)
|
add(tui.LeftClick)
|
||||||
case "right-click":
|
case "right-click":
|
||||||
add(tui.RightClick)
|
add(tui.RightClick)
|
||||||
|
case "shift-left-click":
|
||||||
|
add(tui.SLeftClick)
|
||||||
|
case "shift-right-click":
|
||||||
|
add(tui.SRightClick)
|
||||||
case "double-click":
|
case "double-click":
|
||||||
add(tui.DoubleClick)
|
add(tui.DoubleClick)
|
||||||
|
case "scroll-up":
|
||||||
|
add(tui.ScrollUp)
|
||||||
|
case "scroll-down":
|
||||||
|
add(tui.ScrollDown)
|
||||||
|
case "shift-scroll-up":
|
||||||
|
add(tui.SScrollUp)
|
||||||
|
case "shift-scroll-down":
|
||||||
|
add(tui.SScrollDown)
|
||||||
|
case "preview-scroll-up":
|
||||||
|
add(tui.PreviewScrollUp)
|
||||||
|
case "preview-scroll-down":
|
||||||
|
add(tui.PreviewScrollDown)
|
||||||
case "f10":
|
case "f10":
|
||||||
add(tui.F10)
|
add(tui.F10)
|
||||||
case "f11":
|
case "f11":
|
||||||
@@ -1105,6 +1131,8 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
|||||||
appendAction(actToggleSearch)
|
appendAction(actToggleSearch)
|
||||||
case "toggle-track":
|
case "toggle-track":
|
||||||
appendAction(actToggleTrack)
|
appendAction(actToggleTrack)
|
||||||
|
case "toggle-header":
|
||||||
|
appendAction(actToggleHeader)
|
||||||
case "track":
|
case "track":
|
||||||
appendAction(actTrack)
|
appendAction(actTrack)
|
||||||
case "select":
|
case "select":
|
||||||
@@ -1151,6 +1179,10 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
|||||||
appendAction(actTogglePreviewWrap)
|
appendAction(actTogglePreviewWrap)
|
||||||
case "toggle-sort":
|
case "toggle-sort":
|
||||||
appendAction(actToggleSort)
|
appendAction(actToggleSort)
|
||||||
|
case "offset-up":
|
||||||
|
appendAction(actOffsetUp)
|
||||||
|
case "offset-down":
|
||||||
|
appendAction(actOffsetDown)
|
||||||
case "preview-top":
|
case "preview-top":
|
||||||
appendAction(actPreviewTop)
|
appendAction(actPreviewTop)
|
||||||
case "preview-bottom":
|
case "preview-bottom":
|
||||||
@@ -1374,8 +1406,12 @@ func parseInfoStyle(str string) (infoStyle, string) {
|
|||||||
switch str {
|
switch str {
|
||||||
case "default":
|
case "default":
|
||||||
return infoDefault, ""
|
return infoDefault, ""
|
||||||
|
case "right":
|
||||||
|
return infoRight, ""
|
||||||
case "inline":
|
case "inline":
|
||||||
return infoInline, defaultInfoSep
|
return infoInline, defaultInfoSep
|
||||||
|
case "inline-right":
|
||||||
|
return infoInlineRight, ""
|
||||||
case "hidden":
|
case "hidden":
|
||||||
return infoHidden, ""
|
return infoHidden, ""
|
||||||
default:
|
default:
|
||||||
@@ -1383,7 +1419,7 @@ func parseInfoStyle(str string) (infoStyle, string) {
|
|||||||
if strings.HasPrefix(str, prefix) {
|
if strings.HasPrefix(str, prefix) {
|
||||||
return infoInline, strings.ReplaceAll(str[len(prefix):], "\n", " ")
|
return infoInline, strings.ReplaceAll(str[len(prefix):], "\n", " ")
|
||||||
}
|
}
|
||||||
errorExit("invalid info style (expected: default|hidden|inline|inline:SEPARATOR)")
|
errorExit("invalid info style (expected: default|right|hidden|inline[:SEPARATOR]|inline-right)")
|
||||||
}
|
}
|
||||||
return infoDefault, ""
|
return infoDefault, ""
|
||||||
}
|
}
|
||||||
@@ -1438,6 +1474,8 @@ func parsePreviewWindowImpl(opts *previewOpts, input string, exit func(string))
|
|||||||
opts.border = tui.BorderBold
|
opts.border = tui.BorderBold
|
||||||
case "border-block":
|
case "border-block":
|
||||||
opts.border = tui.BorderBlock
|
opts.border = tui.BorderBlock
|
||||||
|
case "border-thinblock":
|
||||||
|
opts.border = tui.BorderThinBlock
|
||||||
case "border-double":
|
case "border-double":
|
||||||
opts.border = tui.BorderDouble
|
opts.border = tui.BorderDouble
|
||||||
case "noborder", "border-none":
|
case "noborder", "border-none":
|
||||||
|
@@ -2,7 +2,7 @@ package fzf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/tui"
|
"github.com/junegunn/fzf/src/tui"
|
||||||
@@ -357,7 +357,7 @@ func TestDefaultCtrlNP(t *testing.T) {
|
|||||||
check([]string{"--bind=ctrl-n:accept"}, tui.CtrlN, actAccept)
|
check([]string{"--bind=ctrl-n:accept"}, tui.CtrlN, actAccept)
|
||||||
check([]string{"--bind=ctrl-p:accept"}, tui.CtrlP, actAccept)
|
check([]string{"--bind=ctrl-p:accept"}, tui.CtrlP, actAccept)
|
||||||
|
|
||||||
f, _ := ioutil.TempFile("", "fzf-history")
|
f, _ := os.CreateTemp("", "fzf-history")
|
||||||
f.Close()
|
f.Close()
|
||||||
hist := "--history=" + f.Name()
|
hist := "--history=" + f.Name()
|
||||||
check([]string{hist}, tui.CtrlN, actNextHistory)
|
check([]string{hist}, tui.CtrlN, actNextHistory)
|
||||||
|
@@ -6,6 +6,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -98,8 +99,17 @@ func (r *Reader) ReadSource() {
|
|||||||
r.startEventPoller()
|
r.startEventPoller()
|
||||||
var success bool
|
var success bool
|
||||||
if util.IsTty() {
|
if util.IsTty() {
|
||||||
// The default command for *nix requires bash
|
// The default command for *nix requires a shell that supports "pipefail"
|
||||||
|
// https://unix.stackexchange.com/a/654932/62171
|
||||||
shell := "bash"
|
shell := "bash"
|
||||||
|
currentShell := os.Getenv("SHELL")
|
||||||
|
currentShellName := path.Base(currentShell)
|
||||||
|
for _, shellName := range []string{"bash", "zsh", "ksh", "ash", "hush", "mksh", "yash"} {
|
||||||
|
if currentShellName == shellName {
|
||||||
|
shell = currentShell
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
||||||
if len(cmd) == 0 {
|
if len(cmd) == 0 {
|
||||||
if defaultCommand != "" {
|
if defaultCommand != "" {
|
||||||
|
@@ -3,23 +3,44 @@ package fzf
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/subtle"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var getRegex *regexp.Regexp
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
getRegex = regexp.MustCompile(`^GET /(?:\?([a-z0-9=&]+))? HTTP`)
|
||||||
|
}
|
||||||
|
|
||||||
|
type getParams struct {
|
||||||
|
limit int
|
||||||
|
offset int
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
crlf = "\r\n"
|
crlf = "\r\n"
|
||||||
httpOk = "HTTP/1.1 200 OK" + crlf
|
httpOk = "HTTP/1.1 200 OK" + crlf
|
||||||
httpBadRequest = "HTTP/1.1 400 Bad Request" + crlf
|
httpBadRequest = "HTTP/1.1 400 Bad Request" + crlf
|
||||||
|
httpUnauthorized = "HTTP/1.1 401 Unauthorized" + crlf
|
||||||
httpReadTimeout = 10 * time.Second
|
httpReadTimeout = 10 * time.Second
|
||||||
maxContentLength = 1024 * 1024
|
maxContentLength = 1024 * 1024
|
||||||
)
|
)
|
||||||
|
|
||||||
func startHttpServer(port int, channel chan []*action) (error, int) {
|
type httpServer struct {
|
||||||
|
apiKey []byte
|
||||||
|
actionChannel chan []*action
|
||||||
|
responseChannel chan string
|
||||||
|
}
|
||||||
|
|
||||||
|
func startHttpServer(port int, actionChannel chan []*action, responseChannel chan string) (error, int) {
|
||||||
if port < 0 {
|
if port < 0 {
|
||||||
return nil, port
|
return nil, port
|
||||||
}
|
}
|
||||||
@@ -41,6 +62,12 @@ func startHttpServer(port int, channel chan []*action) (error, int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server := httpServer{
|
||||||
|
apiKey: []byte(os.Getenv("FZF_API_KEY")),
|
||||||
|
actionChannel: actionChannel,
|
||||||
|
responseChannel: responseChannel,
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
conn, err := listener.Accept()
|
conn, err := listener.Accept()
|
||||||
@@ -51,7 +78,7 @@ func startHttpServer(port int, channel chan []*action) (error, int) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
conn.Write([]byte(handleHttpRequest(conn, channel)))
|
conn.Write([]byte(server.handleHttpRequest(conn)))
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}
|
}
|
||||||
listener.Close()
|
listener.Close()
|
||||||
@@ -66,12 +93,22 @@ func startHttpServer(port int, channel chan []*action) (error, int) {
|
|||||||
// * No --listen: 2.8MB
|
// * No --listen: 2.8MB
|
||||||
// * --listen with net/http: 5.7MB
|
// * --listen with net/http: 5.7MB
|
||||||
// * --listen w/o net/http: 3.3MB
|
// * --listen w/o net/http: 3.3MB
|
||||||
func handleHttpRequest(conn net.Conn, channel chan []*action) string {
|
func (server *httpServer) handleHttpRequest(conn net.Conn) string {
|
||||||
contentLength := 0
|
contentLength := 0
|
||||||
|
apiKey := ""
|
||||||
body := ""
|
body := ""
|
||||||
bad := func(message string) string {
|
answer := func(code string, message string) string {
|
||||||
message += "\n"
|
message += "\n"
|
||||||
return httpBadRequest + fmt.Sprintf("Content-Length: %d%s", len(message), crlf+crlf+message)
|
return code + fmt.Sprintf("Content-Length: %d%s", len(message), crlf+crlf+message)
|
||||||
|
}
|
||||||
|
unauthorized := func(message string) string {
|
||||||
|
return answer(httpUnauthorized, message)
|
||||||
|
}
|
||||||
|
bad := func(message string) string {
|
||||||
|
return answer(httpBadRequest, message)
|
||||||
|
}
|
||||||
|
good := func(message string) string {
|
||||||
|
return answer(httpOk+"Content-Type: application/json"+crlf, message)
|
||||||
}
|
}
|
||||||
conn.SetReadDeadline(time.Now().Add(httpReadTimeout))
|
conn.SetReadDeadline(time.Now().Add(httpReadTimeout))
|
||||||
scanner := bufio.NewScanner(conn)
|
scanner := bufio.NewScanner(conn)
|
||||||
@@ -92,7 +129,12 @@ func handleHttpRequest(conn net.Conn, channel chan []*action) string {
|
|||||||
text := scanner.Text()
|
text := scanner.Text()
|
||||||
switch section {
|
switch section {
|
||||||
case 0:
|
case 0:
|
||||||
if !strings.HasPrefix(text, "POST / HTTP") {
|
getMatch := getRegex.FindStringSubmatch(text)
|
||||||
|
if len(getMatch) > 0 {
|
||||||
|
server.actionChannel <- []*action{{t: actResponse, a: getMatch[1]}}
|
||||||
|
response := <-server.responseChannel
|
||||||
|
return good(response)
|
||||||
|
} else if !strings.HasPrefix(text, "POST / HTTP") {
|
||||||
return bad("invalid request method")
|
return bad("invalid request method")
|
||||||
}
|
}
|
||||||
section++
|
section++
|
||||||
@@ -105,18 +147,27 @@ func handleHttpRequest(conn net.Conn, channel chan []*action) string {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
pair := strings.SplitN(text, ":", 2)
|
pair := strings.SplitN(text, ":", 2)
|
||||||
if len(pair) == 2 && strings.ToLower(pair[0]) == "content-length" {
|
if len(pair) == 2 {
|
||||||
length, err := strconv.Atoi(strings.TrimSpace(pair[1]))
|
switch strings.ToLower(pair[0]) {
|
||||||
if err != nil || length <= 0 || length > maxContentLength {
|
case "content-length":
|
||||||
return bad("invalid content length")
|
length, err := strconv.Atoi(strings.TrimSpace(pair[1]))
|
||||||
|
if err != nil || length <= 0 || length > maxContentLength {
|
||||||
|
return bad("invalid content length")
|
||||||
|
}
|
||||||
|
contentLength = length
|
||||||
|
case "x-api-key":
|
||||||
|
apiKey = strings.TrimSpace(pair[1])
|
||||||
}
|
}
|
||||||
contentLength = length
|
|
||||||
}
|
}
|
||||||
case 2:
|
case 2:
|
||||||
body += text
|
body += text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(server.apiKey) != 0 && subtle.ConstantTimeCompare([]byte(apiKey), server.apiKey) != 1 {
|
||||||
|
return unauthorized("invalid api key")
|
||||||
|
}
|
||||||
|
|
||||||
if len(body) < contentLength {
|
if len(body) < contentLength {
|
||||||
return bad("incomplete request")
|
return bad("incomplete request")
|
||||||
}
|
}
|
||||||
@@ -133,6 +184,28 @@ func handleHttpRequest(conn net.Conn, channel chan []*action) string {
|
|||||||
return bad("no action specified")
|
return bad("no action specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
channel <- actions
|
server.actionChannel <- actions
|
||||||
return httpOk
|
return httpOk
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseGetParams(query string) getParams {
|
||||||
|
params := getParams{limit: 100, offset: 0}
|
||||||
|
for _, pair := range strings.Split(query, "&") {
|
||||||
|
parts := strings.SplitN(pair, "=", 2)
|
||||||
|
if len(parts) == 2 {
|
||||||
|
switch parts[0] {
|
||||||
|
case "limit":
|
||||||
|
val, err := strconv.Atoi(parts[1])
|
||||||
|
if err == nil {
|
||||||
|
params.limit = val
|
||||||
|
}
|
||||||
|
case "offset":
|
||||||
|
val, err := strconv.Atoi(parts[1])
|
||||||
|
if err == nil {
|
||||||
|
params.offset = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
326
src/terminal.go
326
src/terminal.go
@@ -2,9 +2,9 @@ package fzf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -51,6 +51,7 @@ var whiteSuffix *regexp.Regexp
|
|||||||
var offsetComponentRegex *regexp.Regexp
|
var offsetComponentRegex *regexp.Regexp
|
||||||
var offsetTrimCharsRegex *regexp.Regexp
|
var offsetTrimCharsRegex *regexp.Regexp
|
||||||
var activeTempFiles []string
|
var activeTempFiles []string
|
||||||
|
var passThroughRegex *regexp.Regexp
|
||||||
|
|
||||||
const clearCode string = "\x1b[2J"
|
const clearCode string = "\x1b[2J"
|
||||||
|
|
||||||
@@ -60,6 +61,11 @@ func init() {
|
|||||||
offsetComponentRegex = regexp.MustCompile(`([+-][0-9]+)|(-?/[1-9][0-9]*)`)
|
offsetComponentRegex = regexp.MustCompile(`([+-][0-9]+)|(-?/[1-9][0-9]*)`)
|
||||||
offsetTrimCharsRegex = regexp.MustCompile(`[^0-9/+-]`)
|
offsetTrimCharsRegex = regexp.MustCompile(`[^0-9/+-]`)
|
||||||
activeTempFiles = []string{}
|
activeTempFiles = []string{}
|
||||||
|
|
||||||
|
// Parts of the preview output that should be passed through to the terminal
|
||||||
|
// * https://github.com/tmux/tmux/wiki/FAQ#what-is-the-passthrough-escape-sequence-and-how-do-i-use-it
|
||||||
|
// * https://sw.kovidgoyal.net/kitty/graphics-protocol
|
||||||
|
passThroughRegex = regexp.MustCompile(`\x1bPtmux;\x1b\x1b.*?[^\x1b]\x1b\\|\x1b_G.*?\x1b\\`)
|
||||||
}
|
}
|
||||||
|
|
||||||
type jumpMode int
|
type jumpMode int
|
||||||
@@ -126,6 +132,7 @@ type eachLine struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type itemLine struct {
|
type itemLine struct {
|
||||||
|
offset int
|
||||||
current bool
|
current bool
|
||||||
selected bool
|
selected bool
|
||||||
label string
|
label string
|
||||||
@@ -144,6 +151,24 @@ var emptyLine = itemLine{}
|
|||||||
|
|
||||||
type labelPrinter func(tui.Window, int)
|
type labelPrinter func(tui.Window, int)
|
||||||
|
|
||||||
|
type StatusItem struct {
|
||||||
|
Index int `json:"index"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Status struct {
|
||||||
|
Reading bool `json:"reading"`
|
||||||
|
Progress int `json:"progress"`
|
||||||
|
Query string `json:"query"`
|
||||||
|
Position int `json:"position"`
|
||||||
|
Sort bool `json:"sort"`
|
||||||
|
TotalCount int `json:"totalCount"`
|
||||||
|
MatchCount int `json:"matchCount"`
|
||||||
|
Current *StatusItem `json:"current"`
|
||||||
|
Matches []StatusItem `json:"matches"`
|
||||||
|
Selected []StatusItem `json:"selected"`
|
||||||
|
}
|
||||||
|
|
||||||
// Terminal represents terminal input/output
|
// Terminal represents terminal input/output
|
||||||
type Terminal struct {
|
type Terminal struct {
|
||||||
initDelay time.Duration
|
initDelay time.Duration
|
||||||
@@ -193,6 +218,7 @@ type Terminal struct {
|
|||||||
printQuery bool
|
printQuery bool
|
||||||
history *History
|
history *History
|
||||||
cycle bool
|
cycle bool
|
||||||
|
headerVisible bool
|
||||||
headerFirst bool
|
headerFirst bool
|
||||||
headerLines int
|
headerLines int
|
||||||
header []string
|
header []string
|
||||||
@@ -244,7 +270,8 @@ type Terminal struct {
|
|||||||
sigstop bool
|
sigstop bool
|
||||||
startChan chan fitpad
|
startChan chan fitpad
|
||||||
killChan chan int
|
killChan chan int
|
||||||
serverChan chan []*action
|
serverInputChan chan []*action
|
||||||
|
serverOutputChan chan string
|
||||||
eventChan chan tui.Event
|
eventChan chan tui.Event
|
||||||
slab *util.Slab
|
slab *util.Slab
|
||||||
theme *tui.ColorTheme
|
theme *tui.ColorTheme
|
||||||
@@ -342,6 +369,7 @@ const (
|
|||||||
actToggleIn
|
actToggleIn
|
||||||
actToggleOut
|
actToggleOut
|
||||||
actToggleTrack
|
actToggleTrack
|
||||||
|
actToggleHeader
|
||||||
actTrack
|
actTrack
|
||||||
actDown
|
actDown
|
||||||
actUp
|
actUp
|
||||||
@@ -350,6 +378,8 @@ const (
|
|||||||
actPosition
|
actPosition
|
||||||
actHalfPageUp
|
actHalfPageUp
|
||||||
actHalfPageDown
|
actHalfPageDown
|
||||||
|
actOffsetUp
|
||||||
|
actOffsetDown
|
||||||
actJump
|
actJump
|
||||||
actJumpAccept
|
actJumpAccept
|
||||||
actPrintQuery
|
actPrintQuery
|
||||||
@@ -396,6 +426,7 @@ const (
|
|||||||
actUnbind
|
actUnbind
|
||||||
actRebind
|
actRebind
|
||||||
actBecome
|
actBecome
|
||||||
|
actResponse
|
||||||
)
|
)
|
||||||
|
|
||||||
type placeholderFlags struct {
|
type placeholderFlags struct {
|
||||||
@@ -496,6 +527,16 @@ func defaultKeymap() map[tui.Event][]*action {
|
|||||||
add(tui.Mouse, actMouse)
|
add(tui.Mouse, actMouse)
|
||||||
add(tui.LeftClick, actIgnore)
|
add(tui.LeftClick, actIgnore)
|
||||||
add(tui.RightClick, actToggle)
|
add(tui.RightClick, actToggle)
|
||||||
|
add(tui.SLeftClick, actToggle)
|
||||||
|
add(tui.SRightClick, actToggle)
|
||||||
|
|
||||||
|
add(tui.ScrollUp, actUp)
|
||||||
|
add(tui.ScrollDown, actDown)
|
||||||
|
keymap[tui.SScrollUp.AsEvent()] = toActions(actToggle, actUp)
|
||||||
|
keymap[tui.SScrollDown.AsEvent()] = toActions(actToggle, actDown)
|
||||||
|
|
||||||
|
add(tui.PreviewScrollUp, actPreviewUp)
|
||||||
|
add(tui.PreviewScrollDown, actPreviewDown)
|
||||||
return keymap
|
return keymap
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,13 +572,6 @@ func evaluateHeight(opts *Options, termHeight int) int {
|
|||||||
// NewTerminal returns new Terminal object
|
// NewTerminal returns new Terminal object
|
||||||
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||||
input := trimQuery(opts.Query)
|
input := trimQuery(opts.Query)
|
||||||
var header []string
|
|
||||||
switch opts.Layout {
|
|
||||||
case layoutDefault, layoutReverseList:
|
|
||||||
header = reverseStringArray(opts.Header)
|
|
||||||
default:
|
|
||||||
header = opts.Header
|
|
||||||
}
|
|
||||||
var delay time.Duration
|
var delay time.Duration
|
||||||
if opts.Tac {
|
if opts.Tac {
|
||||||
delay = initialDelayTac
|
delay = initialDelayTac
|
||||||
@@ -565,7 +599,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
if previewBox != nil && opts.Preview.aboveOrBelow() {
|
if previewBox != nil && opts.Preview.aboveOrBelow() {
|
||||||
effectiveMinHeight += 1 + borderLines(opts.Preview.border)
|
effectiveMinHeight += 1 + borderLines(opts.Preview.border)
|
||||||
}
|
}
|
||||||
if opts.InfoStyle != infoDefault {
|
if opts.InfoStyle.noExtraLine() {
|
||||||
effectiveMinHeight--
|
effectiveMinHeight--
|
||||||
}
|
}
|
||||||
effectiveMinHeight += borderLines(opts.BorderShape)
|
effectiveMinHeight += borderLines(opts.BorderShape)
|
||||||
@@ -629,10 +663,11 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
cleanExit: opts.ClearOnExit,
|
cleanExit: opts.ClearOnExit,
|
||||||
paused: opts.Phony,
|
paused: opts.Phony,
|
||||||
cycle: opts.Cycle,
|
cycle: opts.Cycle,
|
||||||
|
headerVisible: true,
|
||||||
headerFirst: opts.HeaderFirst,
|
headerFirst: opts.HeaderFirst,
|
||||||
headerLines: opts.HeaderLines,
|
headerLines: opts.HeaderLines,
|
||||||
header: []string{},
|
header: []string{},
|
||||||
header0: header,
|
header0: opts.Header,
|
||||||
ellipsis: opts.Ellipsis,
|
ellipsis: opts.Ellipsis,
|
||||||
ansi: opts.Ansi,
|
ansi: opts.Ansi,
|
||||||
tabstop: opts.Tabstop,
|
tabstop: opts.Tabstop,
|
||||||
@@ -661,7 +696,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
theme: opts.Theme,
|
theme: opts.Theme,
|
||||||
startChan: make(chan fitpad, 1),
|
startChan: make(chan fitpad, 1),
|
||||||
killChan: make(chan int),
|
killChan: make(chan int),
|
||||||
serverChan: make(chan []*action, 10),
|
serverInputChan: make(chan []*action, 10),
|
||||||
|
serverOutputChan: make(chan string),
|
||||||
eventChan: make(chan tui.Event, 1),
|
eventChan: make(chan tui.Event, 1),
|
||||||
tui: renderer,
|
tui: renderer,
|
||||||
initFunc: func() { renderer.Init() },
|
initFunc: func() { renderer.Init() },
|
||||||
@@ -707,7 +743,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
_, t.hasLoadActions = t.keymap[tui.Load.AsEvent()]
|
_, t.hasLoadActions = t.keymap[tui.Load.AsEvent()]
|
||||||
|
|
||||||
if t.listenPort != nil {
|
if t.listenPort != nil {
|
||||||
err, port := startHttpServer(*t.listenPort, t.serverChan)
|
err, port := startHttpServer(*t.listenPort, t.serverInputChan, t.serverOutputChan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorExit(err.Error())
|
errorExit(err.Error())
|
||||||
}
|
}
|
||||||
@@ -727,7 +763,7 @@ func (t *Terminal) environ() []string {
|
|||||||
|
|
||||||
func borderLines(shape tui.BorderShape) int {
|
func borderLines(shape tui.BorderShape) int {
|
||||||
switch shape {
|
switch shape {
|
||||||
case tui.BorderHorizontal, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderDouble:
|
case tui.BorderHorizontal, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble:
|
||||||
return 2
|
return 2
|
||||||
case tui.BorderTop, tui.BorderBottom:
|
case tui.BorderTop, tui.BorderBottom:
|
||||||
return 1
|
return 1
|
||||||
@@ -735,9 +771,16 @@ func borderLines(shape tui.BorderShape) int {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) visibleHeaderLines() int {
|
||||||
|
if !t.headerVisible {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return len(t.header0) + t.headerLines
|
||||||
|
}
|
||||||
|
|
||||||
// Extra number of lines needed to display fzf
|
// Extra number of lines needed to display fzf
|
||||||
func (t *Terminal) extraLines() int {
|
func (t *Terminal) extraLines() int {
|
||||||
extra := len(t.header0) + t.headerLines + 1
|
extra := t.visibleHeaderLines() + 1
|
||||||
if !t.noInfoLine() {
|
if !t.noInfoLine() {
|
||||||
extra++
|
extra++
|
||||||
}
|
}
|
||||||
@@ -763,7 +806,7 @@ func (t *Terminal) ansiLabelPrinter(str string, color *tui.ColorPair, fill bool)
|
|||||||
runes := []rune(text)
|
runes := []rune(text)
|
||||||
|
|
||||||
// Simpler printer for strings without ANSI colors or tab characters
|
// Simpler printer for strings without ANSI colors or tab characters
|
||||||
if colors == nil && strings.IndexRune(str, '\t') < 0 {
|
if colors == nil && !strings.ContainsRune(str, '\t') {
|
||||||
length := util.StringWidth(str)
|
length := util.StringWidth(str)
|
||||||
if length == 0 {
|
if length == 0 {
|
||||||
return nil, 0
|
return nil, 0
|
||||||
@@ -778,7 +821,7 @@ func (t *Terminal) ansiLabelPrinter(str string, color *tui.ColorPair, fill bool)
|
|||||||
window.CPrint(*color, str)
|
window.CPrint(*color, str)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return printFn, len(text)
|
return printFn, length
|
||||||
}
|
}
|
||||||
|
|
||||||
// Printer that correctly handles ANSI color codes and tab characters
|
// Printer that correctly handles ANSI color codes and tab characters
|
||||||
@@ -845,7 +888,7 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) noInfoLine() bool {
|
func (t *Terminal) noInfoLine() bool {
|
||||||
return t.infoStyle != infoDefault
|
return t.infoStyle.noExtraLine()
|
||||||
}
|
}
|
||||||
|
|
||||||
func getScrollbar(total int, height int, offset int) (int, int) {
|
func getScrollbar(total int, height int, offset int) (int, int) {
|
||||||
@@ -889,21 +932,8 @@ func (t *Terminal) UpdateCount(cnt int, final bool, failedCommand *string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func reverseStringArray(input []string) []string {
|
|
||||||
size := len(input)
|
|
||||||
reversed := make([]string, size)
|
|
||||||
for idx, str := range input {
|
|
||||||
reversed[size-idx-1] = str
|
|
||||||
}
|
|
||||||
return reversed
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Terminal) changeHeader(header string) bool {
|
func (t *Terminal) changeHeader(header string) bool {
|
||||||
lines := strings.Split(strings.TrimSuffix(header, "\n"), "\n")
|
lines := strings.Split(strings.TrimSuffix(header, "\n"), "\n")
|
||||||
switch t.layout {
|
|
||||||
case layoutDefault, layoutReverseList:
|
|
||||||
lines = reverseStringArray(lines)
|
|
||||||
}
|
|
||||||
needFullRedraw := len(t.header0) != len(lines)
|
needFullRedraw := len(t.header0) != len(lines)
|
||||||
t.header0 = lines
|
t.header0 = lines
|
||||||
return needFullRedraw
|
return needFullRedraw
|
||||||
@@ -1085,7 +1115,7 @@ func (t *Terminal) adjustMarginAndPadding() (int, int, [4]int, [4]int) {
|
|||||||
if idx == 3 {
|
if idx == 3 {
|
||||||
extraMargin[idx] += 1 + bw
|
extraMargin[idx] += 1 + bw
|
||||||
}
|
}
|
||||||
case tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderDouble:
|
case tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble:
|
||||||
extraMargin[idx] += 1 + bw*(idx%2)
|
extraMargin[idx] += 1 + bw*(idx%2)
|
||||||
}
|
}
|
||||||
marginInt[idx] = sizeSpecToInt(idx, sizeSpec) + extraMargin[idx]
|
marginInt[idx] = sizeSpecToInt(idx, sizeSpec) + extraMargin[idx]
|
||||||
@@ -1178,7 +1208,7 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
|
|||||||
t.border = t.tui.NewWindow(
|
t.border = t.tui.NewWindow(
|
||||||
marginInt[0], marginInt[3], width+(1+bw), height,
|
marginInt[0], marginInt[3], width+(1+bw), height,
|
||||||
false, tui.MakeBorderStyle(tui.BorderRight, t.unicode))
|
false, tui.MakeBorderStyle(tui.BorderRight, t.unicode))
|
||||||
case tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderDouble:
|
case tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble:
|
||||||
t.border = t.tui.NewWindow(
|
t.border = t.tui.NewWindow(
|
||||||
marginInt[0]-1, marginInt[3]-(1+bw), width+(1+bw)*2, height+2,
|
marginInt[0]-1, marginInt[3]-(1+bw), width+(1+bw)*2, height+2,
|
||||||
false, tui.MakeBorderStyle(t.borderShape, t.unicode))
|
false, tui.MakeBorderStyle(t.borderShape, t.unicode))
|
||||||
@@ -1212,7 +1242,7 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
|
|||||||
}
|
}
|
||||||
t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
|
t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
|
||||||
switch previewOpts.border {
|
switch previewOpts.border {
|
||||||
case tui.BorderSharp, tui.BorderRounded, tui.BorderBold, tui.BorderBlock, tui.BorderDouble:
|
case tui.BorderSharp, tui.BorderRounded, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble:
|
||||||
pwidth -= (1 + bw) * 2
|
pwidth -= (1 + bw) * 2
|
||||||
pheight -= 2
|
pheight -= 2
|
||||||
x += 1 + bw
|
x += 1 + bw
|
||||||
@@ -1356,7 +1386,7 @@ func (t *Terminal) printLabel(window tui.Window, render labelPrinter, opts label
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch borderShape {
|
switch borderShape {
|
||||||
case tui.BorderHorizontal, tui.BorderTop, tui.BorderBottom, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderDouble:
|
case tui.BorderHorizontal, tui.BorderTop, tui.BorderBottom, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble:
|
||||||
if redrawBorder {
|
if redrawBorder {
|
||||||
window.DrawHBorder()
|
window.DrawHBorder()
|
||||||
}
|
}
|
||||||
@@ -1387,7 +1417,7 @@ func (t *Terminal) move(y int, x int, clear bool) {
|
|||||||
case layoutDefault:
|
case layoutDefault:
|
||||||
y = h - y - 1
|
y = h - y - 1
|
||||||
case layoutReverseList:
|
case layoutReverseList:
|
||||||
n := 2 + len(t.header0) + len(t.header)
|
n := 2 + t.visibleHeaderLines()
|
||||||
if t.noInfoLine() {
|
if t.noInfoLine() {
|
||||||
n--
|
n--
|
||||||
}
|
}
|
||||||
@@ -1431,7 +1461,7 @@ func (t *Terminal) promptLine() int {
|
|||||||
if !t.noInfoLine() {
|
if !t.noInfoLine() {
|
||||||
max--
|
max--
|
||||||
}
|
}
|
||||||
return util.Min(len(t.header0)+t.headerLines, max)
|
return util.Min(t.visibleHeaderLines(), max)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -1464,9 +1494,7 @@ func (t *Terminal) trimMessage(message string, maxWidth int) string {
|
|||||||
func (t *Terminal) printInfo() {
|
func (t *Terminal) printInfo() {
|
||||||
pos := 0
|
pos := 0
|
||||||
line := t.promptLine()
|
line := t.promptLine()
|
||||||
switch t.infoStyle {
|
printSpinner := func() {
|
||||||
case infoDefault:
|
|
||||||
t.move(line+1, 0, t.separatorLen == 0)
|
|
||||||
if t.reading {
|
if t.reading {
|
||||||
duration := int64(spinnerDuration)
|
duration := int64(spinnerDuration)
|
||||||
idx := (time.Now().UnixNano() % (duration * int64(len(t.spinner)))) / duration
|
idx := (time.Now().UnixNano() % (duration * int64(len(t.spinner)))) / duration
|
||||||
@@ -1474,8 +1502,18 @@ func (t *Terminal) printInfo() {
|
|||||||
} else {
|
} else {
|
||||||
t.window.Print(" ") // Clear spinner
|
t.window.Print(" ") // Clear spinner
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
switch t.infoStyle {
|
||||||
|
case infoDefault:
|
||||||
|
t.move(line+1, 0, t.separatorLen == 0)
|
||||||
|
printSpinner()
|
||||||
t.move(line+1, 2, false)
|
t.move(line+1, 2, false)
|
||||||
pos = 2
|
pos = 2
|
||||||
|
case infoRight:
|
||||||
|
t.move(line+1, 0, false)
|
||||||
|
case infoInlineRight:
|
||||||
|
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
||||||
|
t.move(line, pos, true)
|
||||||
case infoInline:
|
case infoInline:
|
||||||
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
||||||
str := t.infoSep
|
str := t.infoSep
|
||||||
@@ -1523,19 +1561,60 @@ func (t *Terminal) printInfo() {
|
|||||||
if t.failed != nil && t.count == 0 {
|
if t.failed != nil && t.count == 0 {
|
||||||
output = fmt.Sprintf("[Command failed: %s]", *t.failed)
|
output = fmt.Sprintf("[Command failed: %s]", *t.failed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printSeparator := func(fillLength int, pad bool) {
|
||||||
|
// --------_
|
||||||
|
if t.separatorLen > 0 {
|
||||||
|
t.separator(t.window, fillLength)
|
||||||
|
t.window.Print(" ")
|
||||||
|
} else if pad {
|
||||||
|
t.window.Print(strings.Repeat(" ", fillLength+1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if t.infoStyle == infoRight {
|
||||||
|
maxWidth := t.window.Width()
|
||||||
|
if t.reading {
|
||||||
|
// Need space for spinner and a margin column
|
||||||
|
maxWidth -= 2
|
||||||
|
}
|
||||||
|
output = t.trimMessage(output, maxWidth)
|
||||||
|
fillLength := t.window.Width() - len(output) - 2
|
||||||
|
if t.reading {
|
||||||
|
if fillLength >= 2 {
|
||||||
|
printSeparator(fillLength-2, true)
|
||||||
|
}
|
||||||
|
printSpinner()
|
||||||
|
t.window.Print(" ")
|
||||||
|
} else if fillLength >= 0 {
|
||||||
|
printSeparator(fillLength, true)
|
||||||
|
}
|
||||||
|
t.window.CPrint(tui.ColInfo, output)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.infoStyle == infoInlineRight {
|
||||||
|
pos = util.Max(pos, t.window.Width()-util.StringWidth(output)-3)
|
||||||
|
if pos >= t.window.Width() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.move(line, pos, false)
|
||||||
|
printSpinner()
|
||||||
|
t.window.Print(" ")
|
||||||
|
pos += 2
|
||||||
|
}
|
||||||
|
|
||||||
maxWidth := t.window.Width() - pos
|
maxWidth := t.window.Width() - pos
|
||||||
output = t.trimMessage(output, maxWidth)
|
output = t.trimMessage(output, maxWidth)
|
||||||
t.window.CPrint(tui.ColInfo, output)
|
t.window.CPrint(tui.ColInfo, output)
|
||||||
|
|
||||||
fillLength := maxWidth - len(output) - 2
|
fillLength := maxWidth - len(output) - 2
|
||||||
if t.separatorLen > 0 && fillLength > 0 {
|
if fillLength > 0 {
|
||||||
t.window.CPrint(tui.ColSeparator, " ")
|
t.window.CPrint(tui.ColSeparator, " ")
|
||||||
t.separator(t.window, fillLength)
|
printSeparator(fillLength, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) printHeader() {
|
func (t *Terminal) printHeader() {
|
||||||
if len(t.header0)+len(t.header) == 0 {
|
if t.visibleHeaderLines() == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
max := t.window.Height()
|
max := t.window.Height()
|
||||||
@@ -1546,8 +1625,16 @@ func (t *Terminal) printHeader() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var state *ansiState
|
var state *ansiState
|
||||||
|
needReverse := false
|
||||||
|
switch t.layout {
|
||||||
|
case layoutDefault, layoutReverseList:
|
||||||
|
needReverse = true
|
||||||
|
}
|
||||||
for idx, lineStr := range append(append([]string{}, t.header0...), t.header...) {
|
for idx, lineStr := range append(append([]string{}, t.header0...), t.header...) {
|
||||||
line := idx
|
line := idx
|
||||||
|
if needReverse && idx < len(t.header0) {
|
||||||
|
line = len(t.header0) - idx - 1
|
||||||
|
}
|
||||||
if !t.headerFirst {
|
if !t.headerFirst {
|
||||||
line++
|
line++
|
||||||
if !t.noInfoLine() {
|
if !t.noInfoLine() {
|
||||||
@@ -1563,7 +1650,8 @@ func (t *Terminal) printHeader() {
|
|||||||
text: util.ToChars([]byte(trimmed)),
|
text: util.ToChars([]byte(trimmed)),
|
||||||
colors: colors}
|
colors: colors}
|
||||||
|
|
||||||
t.move(line, 2, true)
|
t.move(line, 0, true)
|
||||||
|
t.window.Print(" ")
|
||||||
t.printHighlighted(Result{item: item},
|
t.printHighlighted(Result{item: item},
|
||||||
tui.ColHeader, tui.ColHeader, false, false)
|
tui.ColHeader, tui.ColHeader, false, false)
|
||||||
}
|
}
|
||||||
@@ -1580,13 +1668,13 @@ func (t *Terminal) printList() {
|
|||||||
if t.layout == layoutDefault {
|
if t.layout == layoutDefault {
|
||||||
i = maxy - 1 - j
|
i = maxy - 1 - j
|
||||||
}
|
}
|
||||||
line := i + 2 + len(t.header0) + len(t.header)
|
line := i + 2 + t.visibleHeaderLines()
|
||||||
if t.noInfoLine() {
|
if t.noInfoLine() {
|
||||||
line--
|
line--
|
||||||
}
|
}
|
||||||
if i < count {
|
if i < count {
|
||||||
t.printItem(t.merger.Get(i+t.offset), line, i, i == t.cy-t.offset, i >= barStart && i < barStart+barLength)
|
t.printItem(t.merger.Get(i+t.offset), line, i, i == t.cy-t.offset, i >= barStart && i < barStart+barLength)
|
||||||
} else if t.prevLines[i] != emptyLine {
|
} else if t.prevLines[i] != emptyLine || t.prevLines[i].offset != line {
|
||||||
t.prevLines[i] = emptyLine
|
t.prevLines[i] = emptyLine
|
||||||
t.move(line, 0, true)
|
t.move(line, 0, true)
|
||||||
}
|
}
|
||||||
@@ -1608,11 +1696,12 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool, bar b
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Avoid unnecessary redraw
|
// Avoid unnecessary redraw
|
||||||
newLine := itemLine{current: current, selected: selected, label: label,
|
newLine := itemLine{offset: line, current: current, selected: selected, label: label,
|
||||||
result: result, queryLen: len(t.input), width: 0, bar: bar}
|
result: result, queryLen: len(t.input), width: 0, bar: bar}
|
||||||
prevLine := t.prevLines[i]
|
prevLine := t.prevLines[i]
|
||||||
|
forceRedraw := prevLine.offset != newLine.offset
|
||||||
printBar := func() {
|
printBar := func() {
|
||||||
if len(t.scrollbar) > 0 && bar != prevLine.bar {
|
if len(t.scrollbar) > 0 && (bar != prevLine.bar || forceRedraw) {
|
||||||
t.prevLines[i].bar = bar
|
t.prevLines[i].bar = bar
|
||||||
t.move(line, t.window.Width()-1, true)
|
t.move(line, t.window.Width()-1, true)
|
||||||
if bar {
|
if bar {
|
||||||
@@ -1621,7 +1710,8 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool, bar b
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if prevLine.current == newLine.current &&
|
if !forceRedraw &&
|
||||||
|
prevLine.current == newLine.current &&
|
||||||
prevLine.selected == newLine.selected &&
|
prevLine.selected == newLine.selected &&
|
||||||
prevLine.label == newLine.label &&
|
prevLine.label == newLine.label &&
|
||||||
prevLine.queryLen == newLine.queryLen &&
|
prevLine.queryLen == newLine.queryLen &&
|
||||||
@@ -1630,7 +1720,7 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool, bar b
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
t.move(line, 0, false)
|
t.move(line, 0, forceRedraw)
|
||||||
if current {
|
if current {
|
||||||
if len(label) == 0 {
|
if len(label) == 0 {
|
||||||
t.window.CPrint(tui.ColCurrentCursorEmpty, t.pointerEmpty)
|
t.window.CPrint(tui.ColCurrentCursorEmpty, t.pointerEmpty)
|
||||||
@@ -1823,7 +1913,7 @@ func (t *Terminal) renderPreviewSpinner() {
|
|||||||
if !t.previewer.scrollable {
|
if !t.previewer.scrollable {
|
||||||
if maxWidth > 0 {
|
if maxWidth > 0 {
|
||||||
t.pwindow.Move(0, maxWidth-1)
|
t.pwindow.Move(0, maxWidth-1)
|
||||||
t.pwindow.CPrint(tui.ColSpinner, spin)
|
t.pwindow.CPrint(tui.ColPreviewSpinner, spin)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
offsetString := fmt.Sprintf("%d/%d", t.previewer.offset+1, numLines)
|
offsetString := fmt.Sprintf("%d/%d", t.previewer.offset+1, numLines)
|
||||||
@@ -1835,7 +1925,7 @@ func (t *Terminal) renderPreviewSpinner() {
|
|||||||
pos := maxWidth - t.displayWidth(offsetRunes)
|
pos := maxWidth - t.displayWidth(offsetRunes)
|
||||||
t.pwindow.Move(0, pos)
|
t.pwindow.Move(0, pos)
|
||||||
if maxWidth > 0 {
|
if maxWidth > 0 {
|
||||||
t.pwindow.CPrint(tui.ColSpinner, spin)
|
t.pwindow.CPrint(tui.ColPreviewSpinner, spin)
|
||||||
t.pwindow.CPrint(tui.ColInfo.WithAttr(tui.Reverse), string(offsetRunes))
|
t.pwindow.CPrint(tui.ColInfo.WithAttr(tui.Reverse), string(offsetRunes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1886,7 +1976,13 @@ func (t *Terminal) renderPreviewText(height int, lines []string, lineNo int, unc
|
|||||||
if ansi != nil {
|
if ansi != nil {
|
||||||
ansi.lbg = -1
|
ansi.lbg = -1
|
||||||
}
|
}
|
||||||
line = strings.TrimRight(line, "\r\n")
|
|
||||||
|
passThroughs := passThroughRegex.FindAllString(line, -1)
|
||||||
|
if passThroughs != nil {
|
||||||
|
line = passThroughRegex.ReplaceAllString(line, "")
|
||||||
|
}
|
||||||
|
line = strings.TrimLeft(strings.TrimRight(line, "\r\n"), "\r")
|
||||||
|
|
||||||
if lineNo >= height || t.pwindow.Y() == height-1 && t.pwindow.X() > 0 {
|
if lineNo >= height || t.pwindow.Y() == height-1 && t.pwindow.X() > 0 {
|
||||||
t.previewed.filled = true
|
t.previewed.filled = true
|
||||||
t.previewer.scrollable = true
|
t.previewer.scrollable = true
|
||||||
@@ -1899,6 +1995,9 @@ func (t *Terminal) renderPreviewText(height int, lines []string, lineNo int, unc
|
|||||||
t.renderPreviewSpinner()
|
t.renderPreviewSpinner()
|
||||||
t.pwindow.Move(y, x)
|
t.pwindow.Move(y, x)
|
||||||
}
|
}
|
||||||
|
for _, passThrough := range passThroughs {
|
||||||
|
t.tui.PassThrough(passThrough)
|
||||||
|
}
|
||||||
var fillRet tui.FillReturn
|
var fillRet tui.FillReturn
|
||||||
prefixWidth := 0
|
prefixWidth := 0
|
||||||
_, _, ansi = extractColor(line, ansi, func(str string, ansi *ansiState) bool {
|
_, _, ansi = extractColor(line, ansi, func(str string, ansi *ansiState) bool {
|
||||||
@@ -2159,7 +2258,7 @@ func hasPreviewFlags(template string) (slot bool, plus bool, query bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func writeTemporaryFile(data []string, printSep string) string {
|
func writeTemporaryFile(data []string, printSep string) string {
|
||||||
f, err := ioutil.TempFile("", "fzf-preview-*")
|
f, err := os.CreateTemp("", "fzf-preview-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorExit("Unable to create temporary file")
|
errorExit("Unable to create temporary file")
|
||||||
}
|
}
|
||||||
@@ -2396,7 +2495,7 @@ func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We would still want to update preview window even if there is no match if
|
// We would still want to update preview window even if there is no match if
|
||||||
// 1. command template contains {q} and the query string is not empty
|
// 1. the command template contains {q}
|
||||||
// 2. or it contains {+} and we have more than one item already selected.
|
// 2. or it contains {+} and we have more than one item already selected.
|
||||||
// To do so, we pass an empty Item instead of nil to trigger an update.
|
// To do so, we pass an empty Item instead of nil to trigger an update.
|
||||||
if current == nil {
|
if current == nil {
|
||||||
@@ -2763,7 +2862,7 @@ func (t *Terminal) Loop() {
|
|||||||
t.printInfo()
|
t.printInfo()
|
||||||
}
|
}
|
||||||
if onFocus, prs := t.keymap[tui.Focus.AsEvent()]; prs && focusChanged {
|
if onFocus, prs := t.keymap[tui.Focus.AsEvent()]; prs && focusChanged {
|
||||||
t.serverChan <- onFocus
|
t.serverInputChan <- onFocus
|
||||||
}
|
}
|
||||||
if focusChanged || version != t.version {
|
if focusChanged || version != t.version {
|
||||||
version = t.version
|
version = t.version
|
||||||
@@ -2875,7 +2974,7 @@ func (t *Terminal) Loop() {
|
|||||||
select {
|
select {
|
||||||
case event = <-t.eventChan:
|
case event = <-t.eventChan:
|
||||||
needBarrier = !event.Is(tui.Load, tui.One, tui.Zero)
|
needBarrier = !event.Is(tui.Load, tui.One, tui.Zero)
|
||||||
case actions = <-t.serverChan:
|
case actions = <-t.serverInputChan:
|
||||||
event = tui.Invalid.AsEvent()
|
event = tui.Invalid.AsEvent()
|
||||||
needBarrier = false
|
needBarrier = false
|
||||||
}
|
}
|
||||||
@@ -2950,6 +3049,8 @@ 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 actResponse:
|
||||||
|
t.serverOutputChan <- t.dumpStatus(parseGetParams(a.a))
|
||||||
case actBecome:
|
case actBecome:
|
||||||
valid, list := t.buildPlusList(a.a, false)
|
valid, list := t.buildPlusList(a.a, false)
|
||||||
if valid {
|
if valid {
|
||||||
@@ -3305,6 +3406,25 @@ func (t *Terminal) Loop() {
|
|||||||
case actHalfPageDown:
|
case actHalfPageDown:
|
||||||
t.vmove(-(t.maxItems() / 2), false)
|
t.vmove(-(t.maxItems() / 2), false)
|
||||||
req(reqList)
|
req(reqList)
|
||||||
|
case actOffsetUp, actOffsetDown:
|
||||||
|
diff := 1
|
||||||
|
if a.t == actOffsetDown {
|
||||||
|
diff = -1
|
||||||
|
}
|
||||||
|
if t.layout == layoutReverse {
|
||||||
|
diff *= -1
|
||||||
|
}
|
||||||
|
t.offset += diff
|
||||||
|
before := t.offset
|
||||||
|
t.constrain()
|
||||||
|
if before != t.offset {
|
||||||
|
t.offset = before
|
||||||
|
if t.layout == layoutReverse {
|
||||||
|
diff *= -1
|
||||||
|
}
|
||||||
|
t.vmove(diff, false)
|
||||||
|
}
|
||||||
|
req(reqList)
|
||||||
case actJump:
|
case actJump:
|
||||||
t.jumping = jumpEnabled
|
t.jumping = jumpEnabled
|
||||||
req(reqJump)
|
req(reqJump)
|
||||||
@@ -3355,6 +3475,9 @@ func (t *Terminal) Loop() {
|
|||||||
t.track = trackEnabled
|
t.track = trackEnabled
|
||||||
}
|
}
|
||||||
req(reqInfo)
|
req(reqInfo)
|
||||||
|
case actToggleHeader:
|
||||||
|
t.headerVisible = !t.headerVisible
|
||||||
|
req(reqList, reqInfo, reqPrompt, reqHeader)
|
||||||
case actTrack:
|
case actTrack:
|
||||||
if t.track == trackDisabled {
|
if t.track == trackDisabled {
|
||||||
t.track = trackCurrent
|
t.track = trackCurrent
|
||||||
@@ -3391,13 +3514,23 @@ func (t *Terminal) Loop() {
|
|||||||
// Scrolling
|
// Scrolling
|
||||||
if me.S != 0 {
|
if me.S != 0 {
|
||||||
if t.window.Enclose(my, mx) && t.merger.Length() > 0 {
|
if t.window.Enclose(my, mx) && t.merger.Length() > 0 {
|
||||||
if t.multi > 0 && me.Mod {
|
evt := tui.ScrollUp
|
||||||
toggle()
|
if me.Mod {
|
||||||
|
evt = tui.SScrollUp
|
||||||
}
|
}
|
||||||
t.vmove(me.S, true)
|
if me.S < 0 {
|
||||||
req(reqList)
|
evt = tui.ScrollDown
|
||||||
|
if me.Mod {
|
||||||
|
evt = tui.SScrollDown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return doActions(actionsFor(evt))
|
||||||
} else if t.hasPreviewWindow() && t.pwindow.Enclose(my, mx) {
|
} else if t.hasPreviewWindow() && t.pwindow.Enclose(my, mx) {
|
||||||
scrollPreviewBy(-me.S)
|
evt := tui.PreviewScrollUp
|
||||||
|
if me.S < 0 {
|
||||||
|
evt = tui.PreviewScrollDown
|
||||||
|
}
|
||||||
|
return doActions(actionsFor(evt))
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -3437,7 +3570,7 @@ func (t *Terminal) Loop() {
|
|||||||
// Translate coordinates
|
// Translate coordinates
|
||||||
mx -= t.window.Left()
|
mx -= t.window.Left()
|
||||||
my -= t.window.Top()
|
my -= t.window.Top()
|
||||||
min := 2 + len(t.header0) + len(t.header)
|
min := 2 + t.visibleHeaderLines()
|
||||||
if t.noInfoLine() {
|
if t.noInfoLine() {
|
||||||
min--
|
min--
|
||||||
}
|
}
|
||||||
@@ -3488,15 +3621,19 @@ func (t *Terminal) Loop() {
|
|||||||
// Prompt
|
// Prompt
|
||||||
t.cx = mx + t.xoffset
|
t.cx = mx + t.xoffset
|
||||||
} else if my >= min {
|
} else if my >= min {
|
||||||
// List
|
t.vset(t.offset + my - min)
|
||||||
if t.vset(t.offset+my-min) && t.multi > 0 && me.Mod {
|
|
||||||
toggle()
|
|
||||||
}
|
|
||||||
req(reqList)
|
req(reqList)
|
||||||
if me.Left {
|
evt := tui.RightClick
|
||||||
return doActions(actionsFor(tui.LeftClick))
|
if me.Mod {
|
||||||
|
evt = tui.SRightClick
|
||||||
}
|
}
|
||||||
return doActions(actionsFor(tui.RightClick))
|
if me.Left {
|
||||||
|
evt = tui.LeftClick
|
||||||
|
if me.Mod {
|
||||||
|
evt = tui.SLeftClick
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return doActions(actionsFor(evt))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case actReload, actReloadSync:
|
case actReload, actReloadSync:
|
||||||
@@ -3709,9 +3846,54 @@ func (t *Terminal) vset(o int) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) maxItems() int {
|
func (t *Terminal) maxItems() int {
|
||||||
max := t.window.Height() - 2 - len(t.header0) - len(t.header)
|
max := t.window.Height() - 2 - t.visibleHeaderLines()
|
||||||
if t.noInfoLine() {
|
if t.noInfoLine() {
|
||||||
max++
|
max++
|
||||||
}
|
}
|
||||||
return util.Max(max, 0)
|
return util.Max(max, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) dumpItem(i *Item) StatusItem {
|
||||||
|
if i == nil {
|
||||||
|
return StatusItem{}
|
||||||
|
}
|
||||||
|
return StatusItem{
|
||||||
|
Index: int(i.Index()),
|
||||||
|
Text: i.AsString(t.ansi),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) dumpStatus(params getParams) string {
|
||||||
|
selectedItems := t.sortSelected()
|
||||||
|
selected := make([]StatusItem, util.Max(0, util.Min(params.limit, len(selectedItems)-params.offset)))
|
||||||
|
for i := range selected {
|
||||||
|
selected[i] = t.dumpItem(selectedItems[i+params.offset].item)
|
||||||
|
}
|
||||||
|
|
||||||
|
matches := make([]StatusItem, util.Max(0, util.Min(params.limit, t.merger.Length()-params.offset)))
|
||||||
|
for i := range matches {
|
||||||
|
matches[i] = t.dumpItem(t.merger.Get(i + params.offset).item)
|
||||||
|
}
|
||||||
|
|
||||||
|
var current *StatusItem
|
||||||
|
currentItem := t.currentItem()
|
||||||
|
if currentItem != nil {
|
||||||
|
item := t.dumpItem(currentItem)
|
||||||
|
current = &item
|
||||||
|
}
|
||||||
|
|
||||||
|
dump := Status{
|
||||||
|
Reading: t.reading,
|
||||||
|
Progress: t.progress,
|
||||||
|
Query: string(t.input),
|
||||||
|
Position: t.cy,
|
||||||
|
Sort: t.sort,
|
||||||
|
TotalCount: t.count,
|
||||||
|
MatchCount: t.merger.Length(),
|
||||||
|
Current: current,
|
||||||
|
Matches: matches,
|
||||||
|
Selected: selected,
|
||||||
|
}
|
||||||
|
bytes, _ := json.Marshal(&dump) // TODO: Errors?
|
||||||
|
return string(bytes)
|
||||||
|
}
|
||||||
|
@@ -33,6 +33,7 @@ func (r *FullscreenRenderer) Init() {}
|
|||||||
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
|
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
|
||||||
func (r *FullscreenRenderer) Pause(bool) {}
|
func (r *FullscreenRenderer) Pause(bool) {}
|
||||||
func (r *FullscreenRenderer) Resume(bool, bool) {}
|
func (r *FullscreenRenderer) Resume(bool, bool) {}
|
||||||
|
func (r *FullscreenRenderer) PassThrough(string) {}
|
||||||
func (r *FullscreenRenderer) Clear() {}
|
func (r *FullscreenRenderer) Clear() {}
|
||||||
func (r *FullscreenRenderer) NeedScrollbarRedraw() bool { return false }
|
func (r *FullscreenRenderer) NeedScrollbarRedraw() bool { return false }
|
||||||
func (r *FullscreenRenderer) Refresh() {}
|
func (r *FullscreenRenderer) Refresh() {}
|
||||||
|
@@ -31,6 +31,11 @@ const consoleDevice string = "/dev/tty"
|
|||||||
var offsetRegexp *regexp.Regexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R")
|
var offsetRegexp *regexp.Regexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R")
|
||||||
var offsetRegexpBegin *regexp.Regexp = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R")
|
var offsetRegexpBegin *regexp.Regexp = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R")
|
||||||
|
|
||||||
|
func (r *LightRenderer) PassThrough(str string) {
|
||||||
|
r.queued.WriteString(str)
|
||||||
|
r.flush()
|
||||||
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) stderr(str string) {
|
func (r *LightRenderer) stderr(str string) {
|
||||||
r.stderrInternal(str, true, "")
|
r.stderrInternal(str, true, "")
|
||||||
}
|
}
|
||||||
@@ -757,7 +762,7 @@ func (w *LightWindow) DrawHBorder() {
|
|||||||
|
|
||||||
func (w *LightWindow) drawBorder(onlyHorizontal bool) {
|
func (w *LightWindow) drawBorder(onlyHorizontal bool) {
|
||||||
switch w.border.shape {
|
switch w.border.shape {
|
||||||
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderDouble:
|
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble:
|
||||||
w.drawBorderAround(onlyHorizontal)
|
w.drawBorderAround(onlyHorizontal)
|
||||||
case BorderHorizontal:
|
case BorderHorizontal:
|
||||||
w.drawBorderHorizontal(true, true)
|
w.drawBorderHorizontal(true, true)
|
||||||
|
@@ -98,6 +98,11 @@ const (
|
|||||||
AttrClear = Attr(1 << 8)
|
AttrClear = Attr(1 << 8)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (r *FullscreenRenderer) PassThrough(str string) {
|
||||||
|
// No-op
|
||||||
|
// https://github.com/gdamore/tcell/issues/363#issuecomment-680665073
|
||||||
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
|
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
|
func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
|
||||||
@@ -715,7 +720,7 @@ func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
|
|||||||
|
|
||||||
hw := runewidth.RuneWidth(w.borderStyle.top)
|
hw := runewidth.RuneWidth(w.borderStyle.top)
|
||||||
switch shape {
|
switch shape {
|
||||||
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderDouble, BorderHorizontal, BorderTop:
|
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderHorizontal, BorderTop:
|
||||||
max := right - 2*hw
|
max := right - 2*hw
|
||||||
if shape == BorderHorizontal || shape == BorderTop {
|
if shape == BorderHorizontal || shape == BorderTop {
|
||||||
max = right - hw
|
max = right - hw
|
||||||
@@ -730,7 +735,7 @@ func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch shape {
|
switch shape {
|
||||||
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderDouble, BorderHorizontal, BorderBottom:
|
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderHorizontal, BorderBottom:
|
||||||
max := right - 2*hw
|
max := right - 2*hw
|
||||||
if shape == BorderHorizontal || shape == BorderBottom {
|
if shape == BorderHorizontal || shape == BorderBottom {
|
||||||
max = right - hw
|
max = right - hw
|
||||||
@@ -741,13 +746,13 @@ func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
|
|||||||
}
|
}
|
||||||
if !onlyHorizontal {
|
if !onlyHorizontal {
|
||||||
switch shape {
|
switch shape {
|
||||||
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderDouble, BorderVertical, BorderLeft:
|
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderVertical, BorderLeft:
|
||||||
for y := top; y < bot; y++ {
|
for y := top; y < bot; y++ {
|
||||||
_screen.SetContent(left, y, w.borderStyle.left, nil, style)
|
_screen.SetContent(left, y, w.borderStyle.left, nil, style)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch shape {
|
switch shape {
|
||||||
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderDouble, BorderVertical, BorderRight:
|
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble, BorderVertical, BorderRight:
|
||||||
vw := runewidth.RuneWidth(w.borderStyle.right)
|
vw := runewidth.RuneWidth(w.borderStyle.right)
|
||||||
for y := top; y < bot; y++ {
|
for y := top; y < bot; y++ {
|
||||||
_screen.SetContent(right-vw, y, w.borderStyle.right, nil, style)
|
_screen.SetContent(right-vw, y, w.borderStyle.right, nil, style)
|
||||||
@@ -755,7 +760,7 @@ func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch shape {
|
switch shape {
|
||||||
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderDouble:
|
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble:
|
||||||
_screen.SetContent(left, top, w.borderStyle.topLeft, nil, style)
|
_screen.SetContent(left, top, w.borderStyle.topLeft, nil, style)
|
||||||
_screen.SetContent(right-runewidth.RuneWidth(w.borderStyle.topRight), top, w.borderStyle.topRight, nil, style)
|
_screen.SetContent(right-runewidth.RuneWidth(w.borderStyle.topRight), top, w.borderStyle.topRight, nil, style)
|
||||||
_screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style)
|
_screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style)
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
package tui
|
package tui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
@@ -17,13 +16,17 @@ func ttyname() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, prefix := range devPrefixes {
|
for _, prefix := range devPrefixes {
|
||||||
files, err := ioutil.ReadDir(prefix)
|
files, err := os.ReadDir(prefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
if stat, ok := file.Sys().(*syscall.Stat_t); ok && stat.Rdev == stderr.Rdev {
|
info, err := file.Info()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if stat, ok := info.Sys().(*syscall.Stat_t); ok && stat.Rdev == stderr.Rdev {
|
||||||
return prefix + file.Name()
|
return prefix + file.Name()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -55,6 +55,14 @@ const (
|
|||||||
DoubleClick
|
DoubleClick
|
||||||
LeftClick
|
LeftClick
|
||||||
RightClick
|
RightClick
|
||||||
|
SLeftClick
|
||||||
|
SRightClick
|
||||||
|
ScrollUp
|
||||||
|
ScrollDown
|
||||||
|
SScrollUp
|
||||||
|
SScrollDown
|
||||||
|
PreviewScrollUp
|
||||||
|
PreviewScrollDown
|
||||||
|
|
||||||
BTab
|
BTab
|
||||||
BSpace
|
BSpace
|
||||||
@@ -315,6 +323,7 @@ const (
|
|||||||
BorderSharp
|
BorderSharp
|
||||||
BorderBold
|
BorderBold
|
||||||
BorderBlock
|
BorderBlock
|
||||||
|
BorderThinBlock
|
||||||
BorderDouble
|
BorderDouble
|
||||||
BorderHorizontal
|
BorderHorizontal
|
||||||
BorderVertical
|
BorderVertical
|
||||||
@@ -408,6 +417,23 @@ func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
|
|||||||
bottomLeft: '▙',
|
bottomLeft: '▙',
|
||||||
bottomRight: '▟',
|
bottomRight: '▟',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case BorderThinBlock:
|
||||||
|
// 🭽▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔🭾
|
||||||
|
// ▏ ▕
|
||||||
|
// 🭼▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁🭿
|
||||||
|
return BorderStyle{
|
||||||
|
shape: shape,
|
||||||
|
top: '▔',
|
||||||
|
bottom: '▁',
|
||||||
|
left: '▏',
|
||||||
|
right: '▕',
|
||||||
|
topLeft: '🭽',
|
||||||
|
topRight: '🭾',
|
||||||
|
bottomLeft: '🭼',
|
||||||
|
bottomRight: '🭿',
|
||||||
|
}
|
||||||
|
|
||||||
case BorderDouble:
|
case BorderDouble:
|
||||||
return BorderStyle{
|
return BorderStyle{
|
||||||
shape: shape,
|
shape: shape,
|
||||||
@@ -456,6 +482,7 @@ type Renderer interface {
|
|||||||
RefreshWindows(windows []Window)
|
RefreshWindows(windows []Window)
|
||||||
Refresh()
|
Refresh()
|
||||||
Close()
|
Close()
|
||||||
|
PassThrough(string)
|
||||||
NeedScrollbarRedraw() bool
|
NeedScrollbarRedraw() bool
|
||||||
|
|
||||||
GetChar() Event
|
GetChar() Event
|
||||||
@@ -538,6 +565,7 @@ var (
|
|||||||
ColBorderLabel ColorPair
|
ColBorderLabel ColorPair
|
||||||
ColPreviewLabel ColorPair
|
ColPreviewLabel ColorPair
|
||||||
ColPreviewScrollbar ColorPair
|
ColPreviewScrollbar ColorPair
|
||||||
|
ColPreviewSpinner ColorPair
|
||||||
)
|
)
|
||||||
|
|
||||||
func EmptyTheme() *ColorTheme {
|
func EmptyTheme() *ColorTheme {
|
||||||
@@ -769,4 +797,5 @@ func initPalette(theme *ColorTheme) {
|
|||||||
ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
|
ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
|
||||||
ColPreviewBorder = pair(theme.PreviewBorder, theme.PreviewBg)
|
ColPreviewBorder = pair(theme.PreviewBorder, theme.PreviewBg)
|
||||||
ColPreviewScrollbar = pair(theme.PreviewScrollbar, theme.PreviewBg)
|
ColPreviewScrollbar = pair(theme.PreviewScrollbar, theme.PreviewBg)
|
||||||
|
ColPreviewSpinner = pair(theme.Spinner, theme.PreviewBg)
|
||||||
}
|
}
|
||||||
|
@@ -184,3 +184,10 @@ func TestRepeatToFill(t *testing.T) {
|
|||||||
t.Error("Expected:", strings.Repeat("abcde", 4)+"abcde"[:2])
|
t.Error("Expected:", strings.Repeat("abcde", 4)+"abcde"[:2])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStringWidth(t *testing.T) {
|
||||||
|
w := StringWidth("─")
|
||||||
|
if w != 1 {
|
||||||
|
t.Errorf("Expected: %d, Actual: %d", 1, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
105
test/test_go.rb
105
test/test_go.rb
@@ -8,6 +8,7 @@ require 'shellwords'
|
|||||||
require 'erb'
|
require 'erb'
|
||||||
require 'tempfile'
|
require 'tempfile'
|
||||||
require 'net/http'
|
require 'net/http'
|
||||||
|
require 'json'
|
||||||
|
|
||||||
TEMPLATE = DATA.read
|
TEMPLATE = DATA.read
|
||||||
UNSETS = %w[
|
UNSETS = %w[
|
||||||
@@ -16,6 +17,7 @@ UNSETS = %w[
|
|||||||
FZF_CTRL_T_COMMAND FZF_CTRL_T_OPTS
|
FZF_CTRL_T_COMMAND FZF_CTRL_T_OPTS
|
||||||
FZF_ALT_C_COMMAND
|
FZF_ALT_C_COMMAND
|
||||||
FZF_ALT_C_OPTS FZF_CTRL_R_OPTS
|
FZF_ALT_C_OPTS FZF_CTRL_R_OPTS
|
||||||
|
FZF_API_KEY
|
||||||
fish_history
|
fish_history
|
||||||
].freeze
|
].freeze
|
||||||
DEFAULT_TIMEOUT = 10
|
DEFAULT_TIMEOUT = 10
|
||||||
@@ -1212,6 +1214,39 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_toggle_header
|
||||||
|
tmux.send_keys "seq 4 | #{FZF} --header-lines 2 --header foo --bind space:toggle-header --header-first --height 10 --border", :Enter
|
||||||
|
before = <<~OUTPUT
|
||||||
|
╭───────
|
||||||
|
│
|
||||||
|
│ 4
|
||||||
|
│ > 3
|
||||||
|
│ 2/2
|
||||||
|
│ >
|
||||||
|
│ 2
|
||||||
|
│ 1
|
||||||
|
│ foo
|
||||||
|
╰───────
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(before, _1) }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
after = <<~OUTPUT
|
||||||
|
╭───────
|
||||||
|
│
|
||||||
|
│
|
||||||
|
│
|
||||||
|
│
|
||||||
|
│ 4
|
||||||
|
│ > 3
|
||||||
|
│ 2/2
|
||||||
|
│ >
|
||||||
|
╰───────
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(after, _1) }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { assert_block(before, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
def test_cancel
|
def test_cancel
|
||||||
tmux.send_keys "seq 10 | #{fzf('--bind 2:cancel')}", :Enter
|
tmux.send_keys "seq 10 | #{fzf('--bind 2:cancel')}", :Enter
|
||||||
tmux.until { |lines| assert_equal ' 10/10', lines[-2] }
|
tmux.until { |lines| assert_equal ' 10/10', lines[-2] }
|
||||||
@@ -2707,6 +2742,16 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { assert(_1[-2] == ' 1/100') }
|
tmux.until { assert(_1[-2] == ' 1/100') }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_info_right
|
||||||
|
tmux.send_keys "#{FZF} --info=right --separator x --bind 'start:reload:seq 100; sleep 10'", :Enter
|
||||||
|
tmux.until { assert_match(%r{xxx [⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏] 100/100}, _1[-2]) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_info_inline_right
|
||||||
|
tmux.send_keys "#{FZF} --info=inline-right --bind 'start:reload:seq 100; sleep 10'", :Enter
|
||||||
|
tmux.until { assert_match(%r{[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏] 100/100}, _1[-1]) }
|
||||||
|
end
|
||||||
|
|
||||||
def test_prev_next_selected
|
def test_prev_next_selected
|
||||||
tmux.send_keys 'seq 10 | fzf --multi --bind ctrl-n:next-selected,ctrl-p:prev-selected', :Enter
|
tmux.send_keys 'seq 10 | fzf --multi --bind ctrl-n:next-selected,ctrl-p:prev-selected', :Enter
|
||||||
tmux.until { |lines| assert_equal 10, lines.item_count }
|
tmux.until { |lines| assert_equal 10, lines.item_count }
|
||||||
@@ -2732,14 +2777,46 @@ class TestGoFZF < TestBase
|
|||||||
-> { URI("http://localhost:#{File.read('/tmp/fzf-port').chomp}") } }.each do |opts, fn|
|
-> { URI("http://localhost:#{File.read('/tmp/fzf-port').chomp}") } }.each do |opts, fn|
|
||||||
tmux.send_keys "seq 10 | fzf #{opts}", :Enter
|
tmux.send_keys "seq 10 | fzf #{opts}", :Enter
|
||||||
tmux.until { |lines| assert_equal 10, lines.item_count }
|
tmux.until { |lines| assert_equal 10, lines.item_count }
|
||||||
|
state = JSON.parse(Net::HTTP.get(fn.call), symbolize_names: true)
|
||||||
|
assert_equal 10, state[:totalCount]
|
||||||
|
assert_equal 10, state[:matchCount]
|
||||||
|
assert_empty state[:query]
|
||||||
|
assert_equal({ index: 0, text: '1' }, state[:current])
|
||||||
|
|
||||||
Net::HTTP.post(fn.call, 'change-query(yo)+reload(seq 100)+change-prompt:hundred> ')
|
Net::HTTP.post(fn.call, 'change-query(yo)+reload(seq 100)+change-prompt:hundred> ')
|
||||||
tmux.until { |lines| assert_equal 100, lines.item_count }
|
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||||
tmux.until { |lines| assert_equal 'hundred> yo', lines[-1] }
|
tmux.until { |lines| assert_equal 'hundred> yo', lines[-1] }
|
||||||
|
state = JSON.parse(Net::HTTP.get(fn.call), symbolize_names: true)
|
||||||
|
assert_equal 100, state[:totalCount]
|
||||||
|
assert_equal 0, state[:matchCount]
|
||||||
|
assert_equal 'yo', state[:query]
|
||||||
|
assert_nil state[:current]
|
||||||
|
|
||||||
teardown
|
teardown
|
||||||
setup
|
setup
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_listen_with_api_key
|
||||||
|
post_uri = URI('http://localhost:6266')
|
||||||
|
tmux.send_keys 'seq 10 | FZF_API_KEY=123abc fzf --listen 6266', :Enter
|
||||||
|
tmux.until { |lines| assert_equal 10, lines.item_count }
|
||||||
|
# Incorrect API Key
|
||||||
|
[nil, { 'x-api-key' => '' }, { 'x-api-key' => '124abc' }].each do |headers|
|
||||||
|
res = Net::HTTP.post(post_uri, 'change-query(yo)+reload(seq 100)+change-prompt:hundred> ', headers)
|
||||||
|
assert_equal '401', res.code
|
||||||
|
assert_equal 'Unauthorized', res.message
|
||||||
|
assert_equal "invalid api key\n", res.body
|
||||||
|
end
|
||||||
|
# Valid API Key
|
||||||
|
[{ 'x-api-key' => '123abc' }, { 'X-API-Key' => '123abc' }].each do |headers|
|
||||||
|
res = Net::HTTP.post(post_uri, 'change-query(yo)+reload(seq 100)+change-prompt:hundred> ', headers)
|
||||||
|
assert_equal '200', res.code
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||||
|
tmux.until { |lines| assert_equal 'hundred> yo', lines[-1] }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_toggle_alternative_preview_window
|
def test_toggle_alternative_preview_window
|
||||||
tmux.send_keys "seq 10 | #{FZF} --bind space:toggle-preview --preview-window '<100000(hidden,up,border-none)' --preview 'echo /{}/{}/'", :Enter
|
tmux.send_keys "seq 10 | #{FZF} --bind space:toggle-preview --preview-window '<100000(hidden,up,border-none)' --preview 'echo /{}/{}/'", :Enter
|
||||||
tmux.until { |lines| assert_equal 10, lines.item_count }
|
tmux.until { |lines| assert_equal 10, lines.item_count }
|
||||||
@@ -3288,6 +3365,34 @@ module CompletionTest
|
|||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'unset -f _fzf_comprun', :Enter
|
tmux.send_keys 'unset -f _fzf_comprun', :Enter
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_ssh_completion
|
||||||
|
(1..5).each { |i| FileUtils.touch("/tmp/fzf-test-ssh-#{i}") }
|
||||||
|
|
||||||
|
tmux.send_keys 'ssh jg@localhost**', :Tab
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.match_count >= 1
|
||||||
|
end
|
||||||
|
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert lines.any_include?('ssh jg@localhost') }
|
||||||
|
tmux.send_keys ' -i /tmp/fzf-test-ssh**', :Tab
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.match_count >= 5
|
||||||
|
assert_equal 0, lines.select_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :Tab, :Tab, :Tab
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 3, lines.select_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert lines.any_include?('ssh jg@localhost -i /tmp/fzf-test-ssh-') }
|
||||||
|
|
||||||
|
tmux.send_keys 'localhost**', :Tab
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.match_count >= 1
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class TestBash < TestBase
|
class TestBash < TestBase
|
||||||
|
@@ -4,3 +4,6 @@ ba = "ba"
|
|||||||
fo = "fo"
|
fo = "fo"
|
||||||
enew = "enew"
|
enew = "enew"
|
||||||
tabe = "tabe"
|
tabe = "tabe"
|
||||||
|
|
||||||
|
[files]
|
||||||
|
extend-exclude = ["README.md"]
|
||||||
|
Reference in New Issue
Block a user