mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-15 23:03:47 -05:00
Compare commits
120 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7d2ac3951 | ||
|
|
29e67d307a | ||
|
|
7320b7df62 | ||
|
|
11fb4233f7 | ||
|
|
84bb350b14 | ||
|
|
38e3694d1c | ||
|
|
1084935241 | ||
|
|
f5f0b9ecaa | ||
|
|
230fc49ae2 | ||
|
|
250d507bdf | ||
|
|
a818653174 | ||
|
|
5c3b044740 | ||
|
|
c5aa8729a1 | ||
|
|
3f78d76da1 | ||
|
|
70c19ccf16 | ||
|
|
68db9cb499 | ||
|
|
d0466fa777 | ||
|
|
21ab64e962 | ||
|
|
a0145cebf2 | ||
|
|
69176fc5f4 | ||
|
|
278dce9ba6 | ||
|
|
1cfa3ee4c7 | ||
|
|
9a95cd5794 | ||
|
|
a62fe3df6f | ||
|
|
7701244a08 | ||
|
|
96e31e4b78 | ||
|
|
ec208af474 | ||
|
|
242641264d | ||
|
|
d3a9a0615b | ||
|
|
3277e8c89c | ||
|
|
d02b9442a5 | ||
|
|
bac385b59c | ||
|
|
b1a0ab8086 | ||
|
|
a33749eb71 | ||
|
|
f5e4ee90e4 | ||
|
|
690d5e6dbd | ||
|
|
a76c055b63 | ||
|
|
70c461c60b | ||
|
|
d51b71ee80 | ||
|
|
3666448ca6 | ||
|
|
d3311d9f43 | ||
|
|
3e1735b06e | ||
|
|
de7ef7eace | ||
|
|
7e89458a3b | ||
|
|
f212bafe46 | ||
|
|
86fe40708b | ||
|
|
d718747c5b | ||
|
|
46ee9ac41c | ||
|
|
f1d306feab | ||
|
|
2d0db98e83 | ||
|
|
3df06a1c68 | ||
|
|
a8f9432a3a | ||
|
|
561e0b04a8 | ||
|
|
404b6a864b | ||
|
|
4feaf31225 | ||
|
|
391aa14845 | ||
|
|
a0d61b4c37 | ||
|
|
2952737755 | ||
|
|
f103aa4753 | ||
|
|
884856023a | ||
|
|
d8188fce7b | ||
|
|
0f15f1ab73 | ||
|
|
488a236b7a | ||
|
|
e833823e15 | ||
|
|
ee4ba104e7 | ||
|
|
4fdc08295b | ||
|
|
a3ff49aaf1 | ||
|
|
76364ea767 | ||
|
|
8eec50d764 | ||
|
|
32b659b346 | ||
|
|
00809909ae | ||
|
|
9f7684f6fe | ||
|
|
2bed7d370e | ||
|
|
d2b852f7cb | ||
|
|
901939bd96 | ||
|
|
edfdcc8cee | ||
|
|
3982c9a552 | ||
|
|
4490b2d209 | ||
|
|
eb4bbf3294 | ||
|
|
dc97d48491 | ||
|
|
0f50dc848e | ||
|
|
c5e4b83de3 | ||
|
|
a08ab46713 | ||
|
|
f50a7058d6 | ||
|
|
2c74f0a040 | ||
|
|
58835e40f3 | ||
|
|
8befa5918a | ||
|
|
df80f7ff2a | ||
|
|
5f66786ef1 | ||
|
|
3a965856a5 | ||
|
|
03df609d77 | ||
|
|
178581b560 | ||
|
|
ffd2314120 | ||
|
|
815b595d2f | ||
|
|
11e56403dd | ||
|
|
4baadecda5 | ||
|
|
cf552b5f3b | ||
|
|
1894304d33 | ||
|
|
9d5392fb02 | ||
|
|
c280645671 | ||
|
|
45f92e6b38 | ||
|
|
6509f09961 | ||
|
|
3c279a6f0e | ||
|
|
9ec3f03871 | ||
|
|
84a9c2c112 | ||
|
|
af368119cb | ||
|
|
89b9189efa | ||
|
|
dd59b8c7b9 | ||
|
|
f83491274f | ||
|
|
c0435fdff4 | ||
|
|
3c09c77269 | ||
|
|
547e101f1d | ||
|
|
0130f64934 | ||
|
|
361e0543ee | ||
|
|
63aa5d3b4e | ||
|
|
01302d097c | ||
|
|
e6095cb7e8 | ||
|
|
b876b8af11 | ||
|
|
a7c41f3fcd | ||
|
|
4772bd8d4c |
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:
|
||||||
|
|||||||
2
BUILD.md
2
BUILD.md
@@ -6,7 +6,7 @@ Build instructions
|
|||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- Go 1.17 or above
|
- Go 1.18 or above
|
||||||
|
|
||||||
### Using Makefile
|
### Using Makefile
|
||||||
|
|
||||||
|
|||||||
103
CHANGELOG.md
103
CHANGELOG.md
@@ -1,6 +1,107 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.44.1
|
||||||
|
------
|
||||||
|
- Fixed crash when preview window is hidden on `focus` event
|
||||||
|
|
||||||
|
0.44.0
|
||||||
|
------
|
||||||
|
- (Experimental) Sixel image support in preview window (not available on Windows)
|
||||||
|
- [bin/fzf-preview.sh](bin/fzf-preview.sh) is added to demonstrate how to
|
||||||
|
display an image using Kitty image protocol or Sixel. You can use it
|
||||||
|
like so:
|
||||||
|
```sh
|
||||||
|
fzf --preview='fzf-preview.sh {}'
|
||||||
|
```
|
||||||
|
- (Experimental) iTerm2 inline image protocol support in preview window (not available on Windows)
|
||||||
|
```sh
|
||||||
|
# Using https://iterm2.com/utilities/imgcat
|
||||||
|
fzf --preview 'imgcat -W $FZF_PREVIEW_COLUMNS -H $FZF_PREVIEW_LINES {}'
|
||||||
|
```
|
||||||
|
- HTTP server can be configured to accept remote connections
|
||||||
|
```sh
|
||||||
|
# FZF_API_KEY is required for a non-localhost listen address
|
||||||
|
export FZF_API_KEY="$(head -c 32 /dev/urandom | base64)"
|
||||||
|
fzf --listen 0.0.0.0:6266
|
||||||
|
```
|
||||||
|
- To allow remote process execution, use `--listen-unsafe` instead
|
||||||
|
(`execute*`, `reload*`, `become`, `preview`, `change-preview`, `transform-*`)
|
||||||
|
```sh
|
||||||
|
fzf --listen-unsafe 0.0.0.0:6266
|
||||||
|
```
|
||||||
|
- Bug fixes
|
||||||
|
|
||||||
|
0.43.0
|
||||||
|
------
|
||||||
|
- (Experimental) Added support for Kitty image protocol in the preview window
|
||||||
|
(not available on Windows)
|
||||||
|
```sh
|
||||||
|
fzf --preview='
|
||||||
|
if file --mime-type {} | grep -qF image/; then
|
||||||
|
# --transfer-mode=memory is the fastest option but if you want fzf to be able
|
||||||
|
# to redraw the image on terminal resize or on 'change-preview-window',
|
||||||
|
# you need to use --transfer-mode=stream.
|
||||||
|
kitty icat --clear --transfer-mode=memory --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
|
0.42.0
|
||||||
------
|
------
|
||||||
- Added new info style: `--info=right`
|
- Added new info style: `--info=right`
|
||||||
@@ -353,7 +454,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
|
||||||
------
|
------
|
||||||
|
|||||||
8
Makefile
8
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)
|
||||||
@@ -91,6 +93,10 @@ build:
|
|||||||
goreleaser build --rm-dist --snapshot --skip-post-hooks
|
goreleaser build --rm-dist --snapshot --skip-post-hooks
|
||||||
|
|
||||||
release:
|
release:
|
||||||
|
# Make sure that the tests pass and the build works
|
||||||
|
TAGS=tcell make test
|
||||||
|
make test build clean
|
||||||
|
|
||||||
ifndef GITHUB_TOKEN
|
ifndef GITHUB_TOKEN
|
||||||
$(error GITHUB_TOKEN is not defined)
|
$(error GITHUB_TOKEN is not defined)
|
||||||
endif
|
endif
|
||||||
|
|||||||
@@ -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'
|
||||||
```
|
```
|
||||||
|
|||||||
74
bin/fzf-preview.sh
Executable file
74
bin/fzf-preview.sh
Executable file
@@ -0,0 +1,74 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# The purpose of this script is to demonstrate how to preview a file or an
|
||||||
|
# image in the preview window of fzf.
|
||||||
|
#
|
||||||
|
# Dependencies:
|
||||||
|
# - https://github.com/sharkdp/bat
|
||||||
|
# - https://github.com/hpjansson/chafa
|
||||||
|
# - https://iterm2.com/utilities/imgcat
|
||||||
|
|
||||||
|
if [[ $# -ne 1 ]]; then
|
||||||
|
>&2 echo "usage: $0 FILENAME"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
file=${1/#\~\//$HOME/}
|
||||||
|
type=$(file --dereference --mime -- "$file")
|
||||||
|
|
||||||
|
if [[ ! $type =~ image/ ]]; then
|
||||||
|
if [[ $type =~ =binary ]]; then
|
||||||
|
file "$1"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Sometimes bat is installed as batcat.
|
||||||
|
if command -v batcat > /dev/null; then
|
||||||
|
batname="batcat"
|
||||||
|
elif command -v bat > /dev/null; then
|
||||||
|
batname="bat"
|
||||||
|
else
|
||||||
|
cat "$1"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
${batname} --style="${BAT_STYLE:-numbers}" --color=always --pager=never -- "$file"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
dim=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}
|
||||||
|
if [[ $dim = x ]]; then
|
||||||
|
dim=$(stty size < /dev/tty | awk '{print $2 "x" $1}')
|
||||||
|
elif ! [[ $KITTY_WINDOW_ID ]] && (( FZF_PREVIEW_TOP + FZF_PREVIEW_LINES == $(stty size < /dev/tty | awk '{print $1}') )); then
|
||||||
|
# Avoid scrolling issue when the Sixel image touches the bottom of the screen
|
||||||
|
# * https://github.com/junegunn/fzf/issues/2544
|
||||||
|
dim=${FZF_PREVIEW_COLUMNS}x$((FZF_PREVIEW_LINES - 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 1. Use kitty icat on kitty terminal
|
||||||
|
if [[ $KITTY_WINDOW_ID ]]; then
|
||||||
|
# 1. 'memory' is the fastest option but if you want the image to be scrollable,
|
||||||
|
# you have to use 'stream'.
|
||||||
|
#
|
||||||
|
# 2. The last line of the output is the ANSI reset code without newline.
|
||||||
|
# This confuses fzf and makes it render scroll offset indicator.
|
||||||
|
# So we remove the last line and append the reset code to its previous line.
|
||||||
|
kitty icat --clear --transfer-mode=memory --stdin=no --place="$dim@0x0" "$file" | sed '$d' | sed $'$s/$/\e[m/'
|
||||||
|
|
||||||
|
# 2. Use chafa with Sixel output
|
||||||
|
elif command -v chafa > /dev/null; then
|
||||||
|
chafa -f sixel -s "$dim" "$file"
|
||||||
|
# Add a new line character so that fzf can display multiple images in the preview window
|
||||||
|
echo
|
||||||
|
|
||||||
|
# 3. If chafa is not found but imgcat is available, use it on iTerm2
|
||||||
|
elif command -v imgcat > /dev/null; then
|
||||||
|
# NOTE: We should use https://iterm2.com/utilities/it2check to check if the
|
||||||
|
# user is running iTerm2. But for the sake of simplicity, we just assume
|
||||||
|
# that's the case here.
|
||||||
|
imgcat -W "${dim%%x*}" -H "${dim##*x}" "$file"
|
||||||
|
|
||||||
|
# 4. Cannot find any suitable method to preview the image
|
||||||
|
else
|
||||||
|
file "$file"
|
||||||
|
fi
|
||||||
15
bin/fzf-tmux
15
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,7 @@ 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")"
|
[[ -n "$BAT_THEME" ]] && envs="$envs BAT_THEME=$(printf %q "$BAT_THEME")"
|
||||||
echo "$envs;" > "$argsf"
|
echo "$envs;" > "$argsf"
|
||||||
|
|
||||||
@@ -226,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.14.0
|
||||||
golang.org/x/term v0.8.0
|
golang.org/x/term v0.13.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
9
go.sum
9
go.sum
@@ -23,7 +23,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -32,12 +31,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.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
|
golang.org/x/term v0.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.42.0
|
version=0.44.1
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
@@ -196,12 +196,12 @@ if [ -n "$binary_error" ]; then
|
|||||||
echo " - $binary_error !!!"
|
echo " - $binary_error !!!"
|
||||||
fi
|
fi
|
||||||
if command -v go > /dev/null; then
|
if command -v go > /dev/null; then
|
||||||
echo -n "Building binary (go get -u github.com/junegunn/fzf) ... "
|
echo -n "Building binary (go install github.com/junegunn/fzf) ... "
|
||||||
if [ -z "${GOPATH-}" ]; then
|
if [ -z "${GOPATH-}" ]; then
|
||||||
export GOPATH="${TMPDIR:-/tmp}/fzf-gopath"
|
export GOPATH="${TMPDIR:-/tmp}/fzf-gopath"
|
||||||
mkdir -p "$GOPATH"
|
mkdir -p "$GOPATH"
|
||||||
fi
|
fi
|
||||||
if go get -ldflags "-s -w -X main.version=$version -X main.revision=go-get" github.com/junegunn/fzf; then
|
if go install -ldflags "-s -w -X main.version=$version -X main.revision=go-install" github.com/junegunn/fzf; then
|
||||||
echo "OK"
|
echo "OK"
|
||||||
cp "$GOPATH/bin/fzf" "$fzf_base/bin/"
|
cp "$GOPATH/bin/fzf" "$fzf_base/bin/"
|
||||||
else
|
else
|
||||||
@@ -245,7 +245,7 @@ for shell in $shells; do
|
|||||||
src=${prefix_expand}.${shell}
|
src=${prefix_expand}.${shell}
|
||||||
echo -n "Generate $src ... "
|
echo -n "Generate $src ... "
|
||||||
|
|
||||||
fzf_completion="[[ \$- == *i* ]] && source \"$fzf_base/shell/completion.${shell}\" 2> /dev/null"
|
fzf_completion="source \"$fzf_base/shell/completion.${shell}\""
|
||||||
if [ $auto_completion -eq 0 ]; then
|
if [ $auto_completion -eq 0 ]; then
|
||||||
fzf_completion="# $fzf_completion"
|
fzf_completion="# $fzf_completion"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
$version="0.42.0"
|
$version="0.44.1"
|
||||||
|
|
||||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -5,7 +5,7 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/protector"
|
"github.com/junegunn/fzf/src/protector"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "0.42"
|
var version string = "0.44"
|
||||||
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 "Jun 2023" "fzf 0.42.0" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf-tmux 1 "Nov 2023" "fzf 0.44.1" "fzf-tmux - open fzf in tmux split pane"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf-tmux - open fzf in tmux split pane
|
fzf-tmux - open fzf in tmux split pane
|
||||||
|
|||||||
@@ -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 "Jun 2023" "fzf 0.42.0" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Nov 2023" "fzf 0.44.1" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -57,9 +57,9 @@ Choose scoring scheme tailored for different types of input.
|
|||||||
.br
|
.br
|
||||||
.BR default " Generic scoring scheme designed to work well with any type of input"
|
.BR default " Generic scoring scheme designed to work well with any type of input"
|
||||||
.br
|
.br
|
||||||
.BR path " Scoring scheme for paths (additional bonus point only after path separator)
|
.BR path " Scoring scheme well suited for file paths
|
||||||
.br
|
.br
|
||||||
.BR history " Scoring scheme for command history (no additional bonus points).
|
.BR history " Scoring scheme well suited for command history or any input where chronological ordering is important
|
||||||
Sets \fB--tiebreak=index\fR as well.
|
Sets \fB--tiebreak=index\fR as well.
|
||||||
.br
|
.br
|
||||||
.TP
|
.TP
|
||||||
@@ -357,6 +357,8 @@ 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"
|
||||||
@@ -546,6 +548,9 @@ they represent the exact size of the preview window. (It also overrides
|
|||||||
by the default shell, so prefer to refer to the ones with \fBFZF_PREVIEW_\fR
|
by the default shell, so prefer to refer to the ones with \fBFZF_PREVIEW_\fR
|
||||||
prefix.)
|
prefix.)
|
||||||
|
|
||||||
|
fzf also exports \fB$FZF_PREVIEW_TOP\fR and \fB$FZF_PREVIEW_LEFT\fR so that
|
||||||
|
the preview command can determine the position of the preview window.
|
||||||
|
|
||||||
A placeholder expression starting with \fB+\fR flag will be replaced to the
|
A placeholder expression starting with \fB+\fR flag will be replaced to the
|
||||||
space-separated list of the selected lines (or the current line if no selection
|
space-separated list of the selected lines (or the current line if no selection
|
||||||
was made) individually quoted.
|
was made) individually quoted.
|
||||||
@@ -589,6 +594,14 @@ e.g.
|
|||||||
echo "$i"
|
echo "$i"
|
||||||
sleep 0.01
|
sleep 0.01
|
||||||
done'\fR
|
done'\fR
|
||||||
|
|
||||||
|
fzf has experimental support for Kitty graphics protocol and Sixel graphics.
|
||||||
|
The following example uses https://github.com/junegunn/fzf/blob/master/bin/fzf-preview.sh
|
||||||
|
script to render an image using either of the protocols inside the preview window.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fBfzf --preview='fzf-preview.sh {}'
|
||||||
|
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
@@ -756,6 +769,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
|
||||||
@@ -765,20 +793,38 @@ ncurses finder only after the input stream is complete.
|
|||||||
e.g. \fBfzf --multi | fzf --sync\fR
|
e.g. \fBfzf --multi | fzf --sync\fR
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.B "--listen[=HTTP_PORT]"
|
.B "--listen[=[ADDR:]PORT]" "--listen-unsafe[=[ADDR:]PORT]"
|
||||||
Start HTTP server on the given port. It allows external processes to send
|
Start HTTP server and listen on the given address. It allows external processes
|
||||||
actions to perform via POST method. If the port number is omitted or given as
|
to send actions to perform via POST method.
|
||||||
0, fzf will choose the port automatically and export it as \fBFZF_PORT\fR
|
|
||||||
environment variable to the child processes started via \fBexecute\fR and
|
- If the port number is omitted or given as 0, fzf will automatically choose
|
||||||
\fBexecute-silent\fR actions.
|
a port and export it as \fBFZF_PORT\fR environment variable to the child processes
|
||||||
|
|
||||||
|
- If \fBFZF_API_KEY\fR environment variable is set, the server would require
|
||||||
|
sending an API key with the same value in the \fBx-api-key\fR HTTP header
|
||||||
|
|
||||||
|
- \fBFZF_API_KEY\fR is required for a non-localhost listen address
|
||||||
|
|
||||||
|
- To allow remote process execution, use \fB--listen-unsafe\fR
|
||||||
|
|
||||||
e.g.
|
e.g.
|
||||||
\fB# Start HTTP server on port 6266
|
\fB# Start HTTP server on port 6266
|
||||||
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 with remote connections allowed
|
||||||
|
# * Listening on non-localhost address requires using an API key
|
||||||
|
export FZF_API_KEY="$(head -c 32 /dev/urandom | base64)"
|
||||||
|
fzf --listen 0.0.0.0:6266
|
||||||
|
|
||||||
|
# Send an authenticated action
|
||||||
|
curl -XPOST localhost:6266 -H "x-api-key: $FZF_API_KEY" -d 'change-query(yo)'
|
||||||
|
|
||||||
# Choose port automatically and export it as $FZF_PORT to the child process
|
# Choose port automatically and export it as $FZF_PORT to the child process
|
||||||
fzf --listen --bind 'start:execute-silent:echo $FZF_PORT > /tmp/fzf-port'
|
fzf --listen --bind 'start:execute-silent:echo $FZF_PORT > /tmp/fzf-port'
|
||||||
\fR
|
\fR
|
||||||
@@ -798,6 +844,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"
|
||||||
@@ -969,6 +1020,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:
|
||||||
@@ -1098,6 +1165,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)
|
||||||
@@ -1124,6 +1193,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
|
||||||
|
|||||||
@@ -945,7 +945,7 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
let term_opts.curwin = 1
|
let term_opts.curwin = 1
|
||||||
endif
|
endif
|
||||||
call s:handle_ambidouble(term_opts)
|
call s:handle_ambidouble(term_opts)
|
||||||
let fzf.buf = term_start([&shell, &shellcmdflag, command], term_opts)
|
keepjumps let fzf.buf = term_start([&shell, &shellcmdflag, command], term_opts)
|
||||||
if is_popup && exists('#TerminalWinOpen')
|
if is_popup && exists('#TerminalWinOpen')
|
||||||
doautocmd <nomodeline> TerminalWinOpen
|
doautocmd <nomodeline> TerminalWinOpen
|
||||||
endif
|
endif
|
||||||
|
|||||||
@@ -9,23 +9,24 @@
|
|||||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||||
|
|
||||||
if [[ $- =~ i ]]; then
|
[[ $- =~ i ]] || return 0
|
||||||
|
|
||||||
|
|
||||||
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
||||||
if ! declare -f _fzf_compgen_path > /dev/null; then
|
if ! declare -F _fzf_compgen_path > /dev/null; then
|
||||||
_fzf_compgen_path() {
|
_fzf_compgen_path() {
|
||||||
echo "$1"
|
echo "$1"
|
||||||
command find -L "$1" \
|
command find -L "$1" \
|
||||||
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o \( -type d -o -type f -o -type l \) \
|
||||||
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
-a -not -path "$1" -print 2> /dev/null | command sed 's@^\./@@'
|
||||||
}
|
}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! declare -f _fzf_compgen_dir > /dev/null; then
|
if ! declare -F _fzf_compgen_dir > /dev/null; then
|
||||||
_fzf_compgen_dir() {
|
_fzf_compgen_dir() {
|
||||||
command find -L "$1" \
|
command find -L "$1" \
|
||||||
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
|
-name .git -prune -o -name .hg -prune -o -name .svn -prune -o -type d \
|
||||||
-a -not -path "$1" -print 2> /dev/null | sed 's@^\./@@'
|
-a -not -path "$1" -print 2> /dev/null | command sed 's@^\./@@'
|
||||||
}
|
}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -68,59 +69,185 @@ _fzf_opts_completion() {
|
|||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||||
opts="
|
opts="
|
||||||
|
-h --help
|
||||||
-x --extended
|
-x --extended
|
||||||
-e --exact
|
-e --exact
|
||||||
|
--extended-exact
|
||||||
|
+x --no-extended
|
||||||
|
+e --no-exact
|
||||||
|
-q --query
|
||||||
|
-f --filter
|
||||||
|
--literal
|
||||||
|
--no-literal
|
||||||
--algo
|
--algo
|
||||||
-i +i
|
--scheme
|
||||||
|
--expect
|
||||||
|
--no-expect
|
||||||
|
--enabled --no-phony
|
||||||
|
--disabled --phony
|
||||||
|
--tiebreak
|
||||||
|
--bind
|
||||||
|
--color
|
||||||
|
--toggle-sort
|
||||||
|
-d --delimiter
|
||||||
-n --nth
|
-n --nth
|
||||||
--with-nth
|
--with-nth
|
||||||
-d --delimiter
|
-s --sort
|
||||||
+s --no-sort
|
+s --no-sort
|
||||||
|
--track
|
||||||
|
--no-track
|
||||||
--tac
|
--tac
|
||||||
--tiebreak
|
--no-tac
|
||||||
|
-i
|
||||||
|
+i
|
||||||
-m --multi
|
-m --multi
|
||||||
|
+m --no-multi
|
||||||
|
--ansi
|
||||||
|
--no-ansi
|
||||||
--no-mouse
|
--no-mouse
|
||||||
--bind
|
+c --no-color
|
||||||
--cycle
|
+2 --no-256
|
||||||
--no-hscroll
|
--black
|
||||||
--jump-labels
|
--no-black
|
||||||
--height
|
--bold
|
||||||
--literal
|
--no-bold
|
||||||
|
--layout
|
||||||
--reverse
|
--reverse
|
||||||
--margin
|
--no-reverse
|
||||||
|
--cycle
|
||||||
|
--no-cycle
|
||||||
|
--keep-right
|
||||||
|
--no-keep-right
|
||||||
|
--hscroll
|
||||||
|
--no-hscroll
|
||||||
|
--hscroll-off
|
||||||
|
--scroll-off
|
||||||
|
--filepath-word
|
||||||
|
--no-filepath-word
|
||||||
|
--info
|
||||||
|
--no-info
|
||||||
--inline-info
|
--inline-info
|
||||||
|
--no-inline-info
|
||||||
|
--separator
|
||||||
|
--no-separator
|
||||||
|
--scrollbar
|
||||||
|
--no-scrollbar
|
||||||
|
--jump-labels
|
||||||
|
-1 --select-1
|
||||||
|
+1 --no-select-1
|
||||||
|
-0 --exit-0
|
||||||
|
+0 --no-exit-0
|
||||||
|
--read0
|
||||||
|
--no-read0
|
||||||
|
--print0
|
||||||
|
--no-print0
|
||||||
|
--print-query
|
||||||
|
--no-print-query
|
||||||
--prompt
|
--prompt
|
||||||
--pointer
|
--pointer
|
||||||
--marker
|
--marker
|
||||||
--header
|
--sync
|
||||||
--header-lines
|
--no-sync
|
||||||
--ansi
|
--async
|
||||||
--tabstop
|
--no-history
|
||||||
--color
|
|
||||||
--no-bold
|
|
||||||
--history
|
--history
|
||||||
--history-size
|
--history-size
|
||||||
|
--no-header
|
||||||
|
--no-header-lines
|
||||||
|
--header
|
||||||
|
--header-lines
|
||||||
|
--header-first
|
||||||
|
--no-header-first
|
||||||
|
--ellipsis
|
||||||
--preview
|
--preview
|
||||||
|
--no-preview
|
||||||
--preview-window
|
--preview-window
|
||||||
-q --query
|
--height
|
||||||
-1 --select-1
|
--min-height
|
||||||
-0 --exit-0
|
--no-height
|
||||||
-f --filter
|
--no-margin
|
||||||
--print-query
|
--no-padding
|
||||||
--expect
|
--no-border
|
||||||
--sync"
|
--border
|
||||||
|
--no-border-label
|
||||||
|
--border-label
|
||||||
|
--border-label-pos
|
||||||
|
--no-preview-label
|
||||||
|
--preview-label
|
||||||
|
--preview-label-pos
|
||||||
|
--no-unicode
|
||||||
|
--unicode
|
||||||
|
--margin
|
||||||
|
--padding
|
||||||
|
--tabstop
|
||||||
|
--listen
|
||||||
|
--no-listen
|
||||||
|
--clear
|
||||||
|
--no-clear
|
||||||
|
--version
|
||||||
|
--"
|
||||||
|
|
||||||
case "${prev}" in
|
case "${prev}" in
|
||||||
|
--algo)
|
||||||
|
COMPREPLY=( $(compgen -W "v1 v2" -- "$cur") )
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--scheme)
|
||||||
|
COMPREPLY=( $(compgen -W "default path history" -- "$cur") )
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
--tiebreak)
|
--tiebreak)
|
||||||
COMPREPLY=( $(compgen -W "length begin end index" -- "$cur") )
|
COMPREPLY=( $(compgen -W "length chunk begin end index" -- "$cur") )
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
--color)
|
--color)
|
||||||
COMPREPLY=( $(compgen -W "dark light 16 bw" -- "$cur") )
|
COMPREPLY=( $(compgen -W "dark light 16 bw no" -- "$cur") )
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
--history)
|
--layout)
|
||||||
COMPREPLY=()
|
COMPREPLY=( $(compgen -W "default reverse reverse-list" -- "$cur") )
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--info)
|
||||||
|
COMPREPLY=( $(compgen -W "default right hidden inline inline-right" -- "$cur") )
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--preview-window)
|
||||||
|
COMPREPLY=( $(compgen -W "
|
||||||
|
default
|
||||||
|
hidden
|
||||||
|
nohidden
|
||||||
|
wrap
|
||||||
|
nowrap
|
||||||
|
cycle
|
||||||
|
nocycle
|
||||||
|
up top
|
||||||
|
down bottom
|
||||||
|
left
|
||||||
|
right
|
||||||
|
rounded border border-rounded
|
||||||
|
sharp border-sharp
|
||||||
|
border-bold
|
||||||
|
border-block
|
||||||
|
border-thinblock
|
||||||
|
border-double
|
||||||
|
noborder border-none
|
||||||
|
border-horizontal
|
||||||
|
border-vertical
|
||||||
|
border-up border-top
|
||||||
|
border-down border-bottom
|
||||||
|
border-left
|
||||||
|
border-right
|
||||||
|
follow
|
||||||
|
nofollow" -- "$cur") )
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--border)
|
||||||
|
COMPREPLY=( $(compgen -W "rounded sharp bold block thinblock double horizontal vertical top bottom left right none" -- "$cur") )
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--border-label-pos|--preview-label-pos)
|
||||||
|
COMPREPLY=( $(compgen -W "center bottom top" -- "$cur") )
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@@ -170,9 +297,9 @@ __fzf_generic_path_completion() {
|
|||||||
COMPREPLY=()
|
COMPREPLY=()
|
||||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
if [[ "$cur" == *"$trigger" ]]; then
|
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
|
||||||
base=${cur:0:${#cur}-${#trigger}}
|
base=${cur:0:${#cur}-${#trigger}}
|
||||||
eval "base=$base"
|
eval "base=$base" 2> /dev/null || return
|
||||||
|
|
||||||
dir=
|
dir=
|
||||||
[[ $base = *"/"* ]] && dir="$base"
|
[[ $base = *"/"* ]] && dir="$base"
|
||||||
@@ -182,7 +309,7 @@ __fzf_generic_path_completion() {
|
|||||||
leftover=${leftover/#\/}
|
leftover=${leftover/#\/}
|
||||||
[[ -z "$dir" ]] && dir='.'
|
[[ -z "$dir" ]] && dir='.'
|
||||||
[[ "$dir" != "/" ]] && dir="${dir/%\//}"
|
[[ "$dir" != "/" ]] && dir="${dir/%\//}"
|
||||||
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
|
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
|
||||||
printf "%q " "${item%$3}$3"
|
printf "%q " "${item%$3}$3"
|
||||||
done)
|
done)
|
||||||
matches=${matches% }
|
matches=${matches% }
|
||||||
@@ -195,7 +322,7 @@ __fzf_generic_path_completion() {
|
|||||||
printf '\e[5n'
|
printf '\e[5n'
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
dir=$(dirname "$dir")
|
dir=$(command dirname "$dir")
|
||||||
[[ "$dir" =~ /$ ]] || dir="$dir"/
|
[[ "$dir" =~ /$ ]] || dir="$dir"/
|
||||||
done
|
done
|
||||||
else
|
else
|
||||||
@@ -229,16 +356,16 @@ _fzf_complete() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
local cur selected trigger cmd post
|
local cur selected trigger cmd post
|
||||||
post="$(caller 0 | awk '{print $2}')_post"
|
post="$(caller 0 | command awk '{print $2}')_post"
|
||||||
type -t "$post" > /dev/null 2>&1 || post=cat
|
type -t "$post" > /dev/null 2>&1 || post='command cat'
|
||||||
|
|
||||||
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
|
cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
|
||||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
if [[ "$cur" == *"$trigger" ]]; then
|
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
|
||||||
cur=${cur:0:${#cur}-${#trigger}}
|
cur=${cur:0:${#cur}-${#trigger}}
|
||||||
|
|
||||||
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | tr '\n' ' ')
|
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | command tr '\n' ' ')
|
||||||
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
|
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
|
||||||
if [[ -n "$selected" ]]; then
|
if [[ -n "$selected" ]]; then
|
||||||
COMPREPLY=("$selected")
|
COMPREPLY=("$selected")
|
||||||
@@ -277,27 +404,61 @@ _fzf_proc_completion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_fzf_proc_completion_post() {
|
_fzf_proc_completion_post() {
|
||||||
awk '{print $2}'
|
command awk '{print $2}'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# To use custom hostname lists, override __fzf_list_hosts.
|
||||||
|
# The function is expected to print hostnames, one per line as well as in the
|
||||||
|
# desired sorting and with any duplicates removed, to standard output.
|
||||||
|
#
|
||||||
|
# e.g.
|
||||||
|
# # Use bash-completions’s _known_hosts_real() for getting the list of hosts
|
||||||
|
# __fzf_list_hosts() {
|
||||||
|
# # Set the local attribute for any non-local variable that is set by _known_hosts_real()
|
||||||
|
# local COMPREPLY=()
|
||||||
|
# _known_hosts_real ''
|
||||||
|
# printf '%s\n' "${COMPREPLY[@]}" | command sort -u --version-sort
|
||||||
|
# }
|
||||||
|
if ! declare -F __fzf_list_hosts > /dev/null; then
|
||||||
|
__fzf_list_hosts() {
|
||||||
|
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | command awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?%]') \
|
||||||
|
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts 2> /dev/null | command tr ',' '\n' | command tr -d '[' | command awk '{ print $1 " " $1 }') \
|
||||||
|
<(command grep -v '^\s*\(#\|$\)' /etc/hosts 2> /dev/null | command grep -Fv '0.0.0.0' | command sed 's/#.*//') |
|
||||||
|
command awk '{for (i = 2; i <= NF; i++) print $i}' | command sort -u
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
_fzf_host_completion() {
|
_fzf_host_completion() {
|
||||||
_fzf_complete +m -- "$@" < <(
|
_fzf_complete +m -- "$@" < <(__fzf_list_hosts)
|
||||||
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?%]') \
|
}
|
||||||
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
|
||||||
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
# Values for $1 $2 $3 are described here
|
||||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
# https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion.html
|
||||||
)
|
# > the first argument ($1) is the name of the command whose arguments are being completed,
|
||||||
|
# > the second argument ($2) is the word being completed,
|
||||||
|
# > and the third argument ($3) is the word preceding the word being completed on the current command line.
|
||||||
|
_fzf_complete_ssh() {
|
||||||
|
case $3 in
|
||||||
|
-i|-F|-E)
|
||||||
|
_fzf_path_completion "$@"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
local user=
|
||||||
|
[[ "$2" =~ '@' ]] && user="${2%%@*}@"
|
||||||
|
_fzf_complete +m -- "$@" < <(__fzf_list_hosts | command awk -v user="$user" '{print user $0}')
|
||||||
|
;;
|
||||||
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_var_completion() {
|
_fzf_var_completion() {
|
||||||
_fzf_complete -m -- "$@" < <(
|
_fzf_complete -m -- "$@" < <(
|
||||||
declare -xp | sed -En 's|^declare [^ ]+ ([^=]+).*|\1|p'
|
declare -xp | command sed -En 's|^declare [^ ]+ ([^=]+).*|\1|p'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_alias_completion() {
|
_fzf_alias_completion() {
|
||||||
_fzf_complete -m -- "$@" < <(
|
_fzf_complete -m -- "$@" < <(
|
||||||
alias | sed -En 's|^alias ([^=]+).*|\1|p'
|
alias | command sed -En 's|^alias ([^=]+).*|\1|p'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,7 +481,7 @@ a_cmds="
|
|||||||
svn tar unzip zip"
|
svn tar unzip zip"
|
||||||
|
|
||||||
# Preserve existing completion
|
# Preserve existing completion
|
||||||
__fzf_orig_completion < <(complete -p $d_cmds $a_cmds 2> /dev/null)
|
__fzf_orig_completion < <(complete -p $d_cmds $a_cmds ssh 2> /dev/null)
|
||||||
|
|
||||||
if type _completion_loader > /dev/null 2>&1; then
|
if type _completion_loader > /dev/null 2>&1; then
|
||||||
_fzf_completion_loader=1
|
_fzf_completion_loader=1
|
||||||
@@ -351,6 +512,9 @@ for cmd in $d_cmds; do
|
|||||||
__fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames"
|
__fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# ssh
|
||||||
|
__fzf_defc ssh _fzf_complete_ssh "-o default -o bashdefault"
|
||||||
|
|
||||||
unset cmd d_cmds a_cmds
|
unset cmd d_cmds a_cmds
|
||||||
|
|
||||||
_fzf_setup_completion() {
|
_fzf_setup_completion() {
|
||||||
@@ -376,7 +540,5 @@ _fzf_setup_completion() {
|
|||||||
# Environment variables / Aliases / Hosts / Process
|
# Environment variables / Aliases / Hosts / Process
|
||||||
_fzf_setup_completion 'var' export unset printenv
|
_fzf_setup_completion 'var' export unset printenv
|
||||||
_fzf_setup_completion 'alias' unalias
|
_fzf_setup_completion 'alias' unalias
|
||||||
_fzf_setup_completion 'host' ssh telnet
|
_fzf_setup_completion 'host' telnet
|
||||||
_fzf_setup_completion 'proc' kill
|
_fzf_setup_completion 'proc' kill
|
||||||
|
|
||||||
fi
|
|
||||||
|
|||||||
@@ -9,6 +9,9 @@
|
|||||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||||
|
|
||||||
|
[[ -o interactive ]] || return 0
|
||||||
|
|
||||||
|
|
||||||
# Both branches of the following `if` do the same thing -- define
|
# Both branches of the following `if` do the same thing -- define
|
||||||
# __fzf_completion_options such that `eval $__fzf_completion_options` sets
|
# __fzf_completion_options such that `eval $__fzf_completion_options` sets
|
||||||
# all options to the same values they currently have. We'll do just that at
|
# all options to the same values they currently have. We'll do just that at
|
||||||
@@ -67,15 +70,12 @@ fi
|
|||||||
# control. There are several others that could wreck havoc if they are set
|
# control. There are several others that could wreck havoc if they are set
|
||||||
# to values we don't expect. With the following `emulate` command we
|
# to values we don't expect. With the following `emulate` command we
|
||||||
# sidestep this issue entirely.
|
# sidestep this issue entirely.
|
||||||
'emulate' 'zsh' '-o' 'no_aliases'
|
'builtin' 'emulate' 'zsh' && 'builtin' 'setopt' 'no_aliases'
|
||||||
|
|
||||||
# This brace is the start of try-always block. The `always` part is like
|
# This brace is the start of try-always block. The `always` part is like
|
||||||
# `finally` in lesser languages. We use it to *always* restore user options.
|
# `finally` in lesser languages. We use it to *always* restore user options.
|
||||||
{
|
{
|
||||||
|
|
||||||
# Bail out if not interactive shell.
|
|
||||||
[[ -o interactive ]] || return 0
|
|
||||||
|
|
||||||
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
||||||
if ! declare -f _fzf_compgen_path > /dev/null; then
|
if ! declare -f _fzf_compgen_path > /dev/null; then
|
||||||
_fzf_compgen_path() {
|
_fzf_compgen_path() {
|
||||||
@@ -137,7 +137,10 @@ __fzf_generic_path_completion() {
|
|||||||
tail=$6
|
tail=$6
|
||||||
|
|
||||||
setopt localoptions nonomatch
|
setopt localoptions nonomatch
|
||||||
eval "base=$base"
|
if [[ $base = *'$('* ]] || [[ $base = *'<('* ]] || [[ $base = *'>('* ]] || [[ $base = *':='* ]] || [[ $base = *'`'* ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
eval "base=$base" 2> /dev/null || return
|
||||||
[[ $base = *"/"* ]] && dir="$base"
|
[[ $base = *"/"* ]] && dir="$base"
|
||||||
while [ 1 ]; do
|
while [ 1 ]; do
|
||||||
if [[ -z "$dir" || -d ${dir} ]]; then
|
if [[ -z "$dir" || -d ${dir} ]]; then
|
||||||
@@ -145,7 +148,7 @@ __fzf_generic_path_completion() {
|
|||||||
leftover=${leftover/#\/}
|
leftover=${leftover/#\/}
|
||||||
[ -z "$dir" ] && dir='.'
|
[ -z "$dir" ] && dir='.'
|
||||||
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
||||||
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-}" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
|
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-}" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
|
||||||
item="${item%$suffix}$suffix"
|
item="${item%$suffix}$suffix"
|
||||||
echo -n "${(q)item} "
|
echo -n "${(q)item} "
|
||||||
done)
|
done)
|
||||||
@@ -215,21 +218,37 @@ _fzf_complete() {
|
|||||||
command rm -f "$fifo"
|
command rm -f "$fifo"
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_telnet() {
|
# To use custom hostname lists, override __fzf_list_hosts.
|
||||||
_fzf_complete +m -- "$@" < <(
|
# The function is expected to print hostnames, one per line as well as in the
|
||||||
command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0' |
|
# desired sorting and with any duplicates removed, to standard output.
|
||||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
if ! declare -f __fzf_list_hosts > /dev/null; then
|
||||||
)
|
__fzf_list_hosts() {
|
||||||
}
|
|
||||||
|
|
||||||
_fzf_complete_ssh() {
|
|
||||||
_fzf_complete +m -- "$@" < <(
|
|
||||||
setopt localoptions nonomatch
|
setopt localoptions nonomatch
|
||||||
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?%]') \
|
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?%]') \
|
||||||
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts 2> /dev/null | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
||||||
<(command grep -v '^\s*\(#\|$\)' /etc/hosts | command grep -Fv '0.0.0.0') |
|
<(command grep -v '^\s*\(#\|$\)' /etc/hosts 2> /dev/null | command grep -Fv '0.0.0.0' | command sed 's/#.*//') |
|
||||||
awk '{if (length($2) > 0) {print $2}}' | sort -u
|
awk '{for (i = 2; i <= NF; i++) print $i}' | sort -u
|
||||||
)
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
_fzf_complete_telnet() {
|
||||||
|
_fzf_complete +m -- "$@" < <(__fzf_list_hosts)
|
||||||
|
}
|
||||||
|
|
||||||
|
# The first and the only argument is the LBUFFER without the current word that contains the trigger.
|
||||||
|
# The current word without the trigger is in the $prefix variable passed from the caller.
|
||||||
|
_fzf_complete_ssh() {
|
||||||
|
local tokens=(${(z)1})
|
||||||
|
case ${tokens[-1]} in
|
||||||
|
-i|-F|-E)
|
||||||
|
_fzf_path_completion "$prefix" "$1"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
local user=
|
||||||
|
[[ $prefix =~ @ ]] && user="${prefix%%@*}@"
|
||||||
|
_fzf_complete +m -- "$@" < <(__fzf_list_hosts | awk -v user="$user" '{print user $0}')
|
||||||
|
;;
|
||||||
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_export() {
|
_fzf_complete_export() {
|
||||||
@@ -293,6 +312,9 @@ fzf-completion() {
|
|||||||
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})
|
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})
|
||||||
|
|
||||||
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
|
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
|
||||||
|
if [[ $prefix = *'$('* ]] || [[ $prefix = *'<('* ]] || [[ $prefix = *'>('* ]] || [[ $prefix = *':='* ]] || [[ $prefix = *'`'* ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
[ -n "${tokens[-1]}" ] && lbuf=${lbuf:0:-${#tokens[-1]}}
|
[ -n "${tokens[-1]}" ] && lbuf=${lbuf:0:-${#tokens[-1]}}
|
||||||
|
|
||||||
if eval "type _fzf_complete_${cmd} > /dev/null"; then
|
if eval "type _fzf_complete_${cmd} > /dev/null"; then
|
||||||
|
|||||||
@@ -11,15 +11,18 @@
|
|||||||
# - $FZF_ALT_C_COMMAND
|
# - $FZF_ALT_C_COMMAND
|
||||||
# - $FZF_ALT_C_OPTS
|
# - $FZF_ALT_C_OPTS
|
||||||
|
|
||||||
|
[[ $- =~ i ]] || return 0
|
||||||
|
|
||||||
|
|
||||||
# Key bindings
|
# Key bindings
|
||||||
# ------------
|
# ------------
|
||||||
__fzf_select__() {
|
__fzf_select__() {
|
||||||
local cmd opts
|
local cmd opts
|
||||||
cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||||
-o -type f -print \
|
-o -type f -print \
|
||||||
-o -type d -print \
|
-o -type d -print \
|
||||||
-o -type l -print 2> /dev/null | cut -b3-"}"
|
-o -type l -print 2> /dev/null | command cut -b3-"}"
|
||||||
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-} -m"
|
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse --scheme=path ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-} -m"
|
||||||
eval "$cmd" |
|
eval "$cmd" |
|
||||||
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) "$@" |
|
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) "$@" |
|
||||||
while read -r item; do
|
while read -r item; do
|
||||||
@@ -27,8 +30,6 @@ __fzf_select__() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ $- =~ i ]]; then
|
|
||||||
|
|
||||||
__fzfcmd() {
|
__fzfcmd() {
|
||||||
[[ -n "${TMUX_PANE-}" ]] && { [[ "${FZF_TMUX:-0}" != 0 ]] || [[ -n "${FZF_TMUX_OPTS-}" ]]; } &&
|
[[ -n "${TMUX_PANE-}" ]] && { [[ "${FZF_TMUX:-0}" != 0 ]] || [[ -n "${FZF_TMUX_OPTS-}" ]]; } &&
|
||||||
echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
|
echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
|
||||||
@@ -42,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
|
||||||
|
|||||||
@@ -118,13 +118,15 @@ const usage = `usage: fzf [options]
|
|||||||
--read0 Read input delimited by ASCII NUL characters
|
--read0 Read input delimited by ASCII NUL characters
|
||||||
--print0 Print output delimited by ASCII NUL characters
|
--print0 Print output delimited by ASCII NUL characters
|
||||||
--sync Synchronous search for multi-staged filtering
|
--sync Synchronous search for multi-staged filtering
|
||||||
--listen[=HTTP_PORT] Start HTTP server to receive actions (POST /)
|
--listen[=[ADDR:]PORT] Start HTTP server to receive actions (POST /)
|
||||||
|
(To allow remote process execution, use --listen-unsafe)
|
||||||
--version Display version information and exit
|
--version Display version information and exit
|
||||||
|
|
||||||
Environment variables
|
Environment variables
|
||||||
FZF_DEFAULT_COMMAND Default command to use when input is tty
|
FZF_DEFAULT_COMMAND Default command to use when input is tty
|
||||||
FZF_DEFAULT_OPTS Default options
|
FZF_DEFAULT_OPTS Default options
|
||||||
(e.g. '--layout=reverse --inline-info')
|
(e.g. '--layout=reverse --inline-info')
|
||||||
|
FZF_API_KEY X-API-Key header for HTTP server (--listen)
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -333,7 +335,8 @@ type Options struct {
|
|||||||
PreviewLabel labelOpts
|
PreviewLabel labelOpts
|
||||||
Unicode bool
|
Unicode bool
|
||||||
Tabstop int
|
Tabstop int
|
||||||
ListenPort *int
|
ListenAddr *listenAddress
|
||||||
|
Unsafe bool
|
||||||
ClearOnExit bool
|
ClearOnExit bool
|
||||||
Version bool
|
Version bool
|
||||||
}
|
}
|
||||||
@@ -403,6 +406,7 @@ func defaultOptions() *Options {
|
|||||||
Tabstop: 8,
|
Tabstop: 8,
|
||||||
BorderLabel: labelOpts{},
|
BorderLabel: labelOpts{},
|
||||||
PreviewLabel: labelOpts{},
|
PreviewLabel: labelOpts{},
|
||||||
|
Unsafe: false,
|
||||||
ClearOnExit: true,
|
ClearOnExit: true,
|
||||||
Version: false}
|
Version: false}
|
||||||
}
|
}
|
||||||
@@ -701,8 +705,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":
|
||||||
@@ -1114,6 +1134,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":
|
||||||
@@ -1160,6 +1182,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":
|
||||||
@@ -1809,11 +1835,21 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
nextString(allArgs, &i, "padding required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
|
nextString(allArgs, &i, "padding required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
|
||||||
case "--tabstop":
|
case "--tabstop":
|
||||||
opts.Tabstop = nextInt(allArgs, &i, "tab stop required")
|
opts.Tabstop = nextInt(allArgs, &i, "tab stop required")
|
||||||
case "--listen":
|
case "--listen", "--listen-unsafe":
|
||||||
port := optionalNumeric(allArgs, &i, 0)
|
given, str := optionalNextString(allArgs, &i)
|
||||||
opts.ListenPort = &port
|
addr := defaultListenAddr
|
||||||
case "--no-listen":
|
if given {
|
||||||
opts.ListenPort = nil
|
var err error
|
||||||
|
err, addr = parseListenAddress(str)
|
||||||
|
if err != nil {
|
||||||
|
errorExit(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opts.ListenAddr = &addr
|
||||||
|
opts.Unsafe = arg == "--listen-unsafe"
|
||||||
|
case "--no-listen", "--no-listen-unsafe":
|
||||||
|
opts.ListenAddr = nil
|
||||||
|
opts.Unsafe = false
|
||||||
case "--clear":
|
case "--clear":
|
||||||
opts.ClearOnExit = true
|
opts.ClearOnExit = true
|
||||||
case "--no-clear":
|
case "--no-clear":
|
||||||
@@ -1904,8 +1940,19 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
} else if match, value := optString(arg, "--tabstop="); match {
|
} else if match, value := optString(arg, "--tabstop="); match {
|
||||||
opts.Tabstop = atoi(value)
|
opts.Tabstop = atoi(value)
|
||||||
} else if match, value := optString(arg, "--listen="); match {
|
} else if match, value := optString(arg, "--listen="); match {
|
||||||
port := atoi(value)
|
err, addr := parseListenAddress(value)
|
||||||
opts.ListenPort = &port
|
if err != nil {
|
||||||
|
errorExit(err.Error())
|
||||||
|
}
|
||||||
|
opts.ListenAddr = &addr
|
||||||
|
opts.Unsafe = false
|
||||||
|
} else if match, value := optString(arg, "--listen-unsafe="); match {
|
||||||
|
err, addr := parseListenAddress(value)
|
||||||
|
if err != nil {
|
||||||
|
errorExit(err.Error())
|
||||||
|
}
|
||||||
|
opts.ListenAddr = &addr
|
||||||
|
opts.Unsafe = true
|
||||||
} else if match, value := optString(arg, "--hscroll-off="); match {
|
} else if match, value := optString(arg, "--hscroll-off="); match {
|
||||||
opts.HscrollOff = atoi(value)
|
opts.HscrollOff = atoi(value)
|
||||||
} else if match, value := optString(arg, "--scroll-off="); match {
|
} else if match, value := optString(arg, "--scroll-off="); match {
|
||||||
@@ -1935,10 +1982,6 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
errorExit("tab stop must be a positive integer")
|
errorExit("tab stop must be a positive integer")
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.ListenPort != nil && (*opts.ListenPort < 0 || *opts.ListenPort > 65535) {
|
|
||||||
errorExit("invalid listen port")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(opts.JumpLabels) == 0 {
|
if len(opts.JumpLabels) == 0 {
|
||||||
errorExit("empty jump labels")
|
errorExit("empty jump labels")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package fzf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/tui"
|
"github.com/junegunn/fzf/src/tui"
|
||||||
@@ -357,7 +357,7 @@ func TestDefaultCtrlNP(t *testing.T) {
|
|||||||
check([]string{"--bind=ctrl-n:accept"}, tui.CtrlN, actAccept)
|
check([]string{"--bind=ctrl-n:accept"}, tui.CtrlN, actAccept)
|
||||||
check([]string{"--bind=ctrl-p:accept"}, tui.CtrlP, actAccept)
|
check([]string{"--bind=ctrl-p:accept"}, tui.CtrlP, actAccept)
|
||||||
|
|
||||||
f, _ := ioutil.TempFile("", "fzf-history")
|
f, _ := os.CreateTemp("", "fzf-history")
|
||||||
f.Close()
|
f.Close()
|
||||||
hist := "--history=" + f.Name()
|
hist := "--history=" + f.Name()
|
||||||
check([]string{hist}, tui.CtrlN, actNextHistory)
|
check([]string{hist}, tui.CtrlN, actNextHistory)
|
||||||
|
|||||||
@@ -6,5 +6,5 @@ import "golang.org/x/sys/unix"
|
|||||||
|
|
||||||
// Protect calls OS specific protections like pledge on OpenBSD
|
// Protect calls OS specific protections like pledge on OpenBSD
|
||||||
func Protect() {
|
func Protect() {
|
||||||
unix.PledgePromises("stdio rpath tty proc exec")
|
unix.PledgePromises("stdio rpath tty proc exec inet")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 != "" {
|
||||||
|
|||||||
144
src/server.go
144
src/server.go
@@ -3,44 +3,104 @@ 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 {
|
||||||
if port < 0 {
|
apiKey []byte
|
||||||
return nil, port
|
actionChannel chan []*action
|
||||||
}
|
responseChannel chan string
|
||||||
|
}
|
||||||
|
|
||||||
listener, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", port))
|
type listenAddress struct {
|
||||||
|
host string
|
||||||
|
port int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (addr listenAddress) IsLocal() bool {
|
||||||
|
return addr.host == "localhost" || addr.host == "127.0.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultListenAddr = listenAddress{"localhost", 0}
|
||||||
|
|
||||||
|
func parseListenAddress(address string) (error, listenAddress) {
|
||||||
|
parts := strings.SplitN(address, ":", 3)
|
||||||
|
if len(parts) == 1 {
|
||||||
|
parts = []string{"localhost", parts[0]}
|
||||||
|
}
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return fmt.Errorf("invalid listen address: %s", address), defaultListenAddr
|
||||||
|
}
|
||||||
|
portStr := parts[len(parts)-1]
|
||||||
|
port, err := strconv.Atoi(portStr)
|
||||||
|
if err != nil || port < 0 || port > 65535 {
|
||||||
|
return fmt.Errorf("invalid listen port: %s", portStr), defaultListenAddr
|
||||||
|
}
|
||||||
|
if len(parts[0]) == 0 {
|
||||||
|
parts[0] = "localhost"
|
||||||
|
}
|
||||||
|
return nil, listenAddress{parts[0], port}
|
||||||
|
}
|
||||||
|
|
||||||
|
func startHttpServer(address listenAddress, actionChannel chan []*action, responseChannel chan string) (error, int) {
|
||||||
|
host := address.host
|
||||||
|
port := address.port
|
||||||
|
apiKey := os.Getenv("FZF_API_KEY")
|
||||||
|
if !address.IsLocal() && len(apiKey) == 0 {
|
||||||
|
return fmt.Errorf("FZF_API_KEY is required to allow remote access"), port
|
||||||
|
}
|
||||||
|
addrStr := fmt.Sprintf("%s:%d", host, port)
|
||||||
|
listener, err := net.Listen("tcp", addrStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("port not available: %d", port), port
|
return fmt.Errorf("failed to listen on %s", addrStr), port
|
||||||
}
|
}
|
||||||
if port == 0 {
|
if port == 0 {
|
||||||
addr := listener.Addr().String()
|
addr := listener.Addr().String()
|
||||||
parts := strings.SplitN(addr, ":", 2)
|
parts := strings.Split(addr, ":")
|
||||||
if len(parts) < 2 {
|
if len(parts) < 2 {
|
||||||
return fmt.Errorf("cannot extract port: %s", addr), port
|
return fmt.Errorf("cannot extract port: %s", addr), port
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
port, err = strconv.Atoi(parts[1])
|
port, err = strconv.Atoi(parts[len(parts)-1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err, port
|
return err, port
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server := httpServer{
|
||||||
|
apiKey: []byte(apiKey),
|
||||||
|
actionChannel: actionChannel,
|
||||||
|
responseChannel: responseChannel,
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
conn, err := listener.Accept()
|
conn, err := listener.Accept()
|
||||||
@@ -51,7 +111,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 +126,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 +162,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 +180,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 +217,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
|
||||||
|
}
|
||||||
|
|||||||
453
src/terminal.go
453
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,13 @@ 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
|
||||||
|
// * https://en.wikipedia.org/wiki/Sixel
|
||||||
|
// * https://iterm2.com/documentation-images.html
|
||||||
|
passThroughRegex = regexp.MustCompile(`\x1bPtmux;\x1b\x1b.*?[^\x1b]\x1b\\|\x1b(_G|P[0-9;]*q).*?\x1b\\\r?|\x1b]1337;.*?\a`)
|
||||||
}
|
}
|
||||||
|
|
||||||
type jumpMode int
|
type jumpMode int
|
||||||
@@ -114,10 +122,13 @@ type previewer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type previewed struct {
|
type previewed struct {
|
||||||
version int64
|
version int64
|
||||||
numLines int
|
numLines int
|
||||||
offset int
|
offset int
|
||||||
filled bool
|
filled bool
|
||||||
|
image bool
|
||||||
|
wipe bool
|
||||||
|
wireframe bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type eachLine struct {
|
type eachLine struct {
|
||||||
@@ -126,6 +137,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 +156,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 +223,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
|
||||||
@@ -205,7 +236,9 @@ type Terminal struct {
|
|||||||
margin [4]sizeSpec
|
margin [4]sizeSpec
|
||||||
padding [4]sizeSpec
|
padding [4]sizeSpec
|
||||||
unicode bool
|
unicode bool
|
||||||
|
listenAddr *listenAddress
|
||||||
listenPort *int
|
listenPort *int
|
||||||
|
listenUnsafe bool
|
||||||
borderShape tui.BorderShape
|
borderShape tui.BorderShape
|
||||||
cleanExit bool
|
cleanExit bool
|
||||||
paused bool
|
paused bool
|
||||||
@@ -244,12 +277,14 @@ 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
|
||||||
tui tui.Renderer
|
tui tui.Renderer
|
||||||
executing *util.AtomicBool
|
executing *util.AtomicBool
|
||||||
|
termSize tui.TermSize
|
||||||
}
|
}
|
||||||
|
|
||||||
type selectedItem struct {
|
type selectedItem struct {
|
||||||
@@ -280,6 +315,7 @@ const (
|
|||||||
reqRefresh
|
reqRefresh
|
||||||
reqReinit
|
reqReinit
|
||||||
reqFullRedraw
|
reqFullRedraw
|
||||||
|
reqResize
|
||||||
reqRedrawBorderLabel
|
reqRedrawBorderLabel
|
||||||
reqRedrawPreviewLabel
|
reqRedrawPreviewLabel
|
||||||
reqClose
|
reqClose
|
||||||
@@ -342,6 +378,7 @@ const (
|
|||||||
actToggleIn
|
actToggleIn
|
||||||
actToggleOut
|
actToggleOut
|
||||||
actToggleTrack
|
actToggleTrack
|
||||||
|
actToggleHeader
|
||||||
actTrack
|
actTrack
|
||||||
actDown
|
actDown
|
||||||
actUp
|
actUp
|
||||||
@@ -350,6 +387,8 @@ const (
|
|||||||
actPosition
|
actPosition
|
||||||
actHalfPageUp
|
actHalfPageUp
|
||||||
actHalfPageDown
|
actHalfPageDown
|
||||||
|
actOffsetUp
|
||||||
|
actOffsetDown
|
||||||
actJump
|
actJump
|
||||||
actJumpAccept
|
actJumpAccept
|
||||||
actPrintQuery
|
actPrintQuery
|
||||||
@@ -396,8 +435,29 @@ const (
|
|||||||
actUnbind
|
actUnbind
|
||||||
actRebind
|
actRebind
|
||||||
actBecome
|
actBecome
|
||||||
|
actResponse
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func processExecution(action actionType) bool {
|
||||||
|
switch action {
|
||||||
|
case actTransformBorderLabel,
|
||||||
|
actTransformHeader,
|
||||||
|
actTransformPreviewLabel,
|
||||||
|
actTransformPrompt,
|
||||||
|
actTransformQuery,
|
||||||
|
actPreview,
|
||||||
|
actChangePreview,
|
||||||
|
actExecute,
|
||||||
|
actExecuteSilent,
|
||||||
|
actExecuteMulti,
|
||||||
|
actReload,
|
||||||
|
actReloadSync,
|
||||||
|
actBecome:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type placeholderFlags struct {
|
type placeholderFlags struct {
|
||||||
plus bool
|
plus bool
|
||||||
preserveSpace bool
|
preserveSpace bool
|
||||||
@@ -416,6 +476,7 @@ type searchRequest struct {
|
|||||||
type previewRequest struct {
|
type previewRequest struct {
|
||||||
template string
|
template string
|
||||||
pwindow tui.Window
|
pwindow tui.Window
|
||||||
|
pwindowSize tui.TermSize
|
||||||
scrollOffset int
|
scrollOffset int
|
||||||
list []*Item
|
list []*Item
|
||||||
}
|
}
|
||||||
@@ -496,6 +557,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 +602,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
|
||||||
@@ -546,7 +610,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
}
|
}
|
||||||
var previewBox *util.EventBox
|
var previewBox *util.EventBox
|
||||||
// We need to start previewer if HTTP server is enabled even when --preview option is not specified
|
// We need to start previewer if HTTP server is enabled even when --preview option is not specified
|
||||||
if len(opts.Preview.command) > 0 || hasPreviewAction(opts) || opts.ListenPort != nil {
|
if len(opts.Preview.command) > 0 || hasPreviewAction(opts) || opts.ListenAddr != nil {
|
||||||
previewBox = util.NewEventBox()
|
previewBox = util.NewEventBox()
|
||||||
}
|
}
|
||||||
var renderer tui.Renderer
|
var renderer tui.Renderer
|
||||||
@@ -619,7 +683,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
margin: opts.Margin,
|
margin: opts.Margin,
|
||||||
padding: opts.Padding,
|
padding: opts.Padding,
|
||||||
unicode: opts.Unicode,
|
unicode: opts.Unicode,
|
||||||
listenPort: opts.ListenPort,
|
listenAddr: opts.ListenAddr,
|
||||||
|
listenUnsafe: opts.Unsafe,
|
||||||
borderShape: opts.BorderShape,
|
borderShape: opts.BorderShape,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderLabel: nil,
|
borderLabel: nil,
|
||||||
@@ -629,10 +694,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,
|
||||||
@@ -651,7 +717,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
initialPreviewOpts: opts.Preview,
|
initialPreviewOpts: opts.Preview,
|
||||||
previewOpts: opts.Preview,
|
previewOpts: opts.Preview,
|
||||||
previewer: previewer{0, []string{}, 0, false, true, disabledState, "", []bool{}},
|
previewer: previewer{0, []string{}, 0, false, true, disabledState, "", []bool{}},
|
||||||
previewed: previewed{0, 0, 0, false},
|
previewed: previewed{0, 0, 0, false, false, false, false},
|
||||||
previewBox: previewBox,
|
previewBox: previewBox,
|
||||||
eventBox: eventBox,
|
eventBox: eventBox,
|
||||||
mutex: sync.Mutex{},
|
mutex: sync.Mutex{},
|
||||||
@@ -661,7 +727,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() },
|
||||||
@@ -706,8 +773,8 @@ 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.listenAddr != nil {
|
||||||
err, port := startHttpServer(*t.listenPort, t.serverChan)
|
err, port := startHttpServer(*t.listenAddr, t.serverInputChan, t.serverOutputChan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorExit(err.Error())
|
errorExit(err.Error())
|
||||||
}
|
}
|
||||||
@@ -735,9 +802,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 +837,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 +852,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
|
||||||
@@ -889,21 +963,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
|
||||||
@@ -1271,10 +1332,6 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
|
|||||||
if previewOpts.hidden {
|
if previewOpts.hidden {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Put scrollbar closer to the right border for consistent look
|
|
||||||
if t.borderShape.HasRight() {
|
|
||||||
width++
|
|
||||||
}
|
|
||||||
if previewOpts.position == posUp {
|
if previewOpts.position == posUp {
|
||||||
t.window = t.tui.NewWindow(
|
t.window = t.tui.NewWindow(
|
||||||
marginInt[0]+pheight, marginInt[3], width, height-pheight, false, noBorder)
|
marginInt[0]+pheight, marginInt[3], width, height-pheight, false, noBorder)
|
||||||
@@ -1387,7 +1444,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 +1488,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
|
||||||
}
|
}
|
||||||
@@ -1584,7 +1641,7 @@ func (t *Terminal) printInfo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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()
|
||||||
@@ -1595,8 +1652,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() {
|
||||||
@@ -1612,7 +1677,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)
|
||||||
}
|
}
|
||||||
@@ -1629,13 +1695,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)
|
||||||
}
|
}
|
||||||
@@ -1657,11 +1723,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 {
|
||||||
@@ -1670,7 +1737,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 &&
|
||||||
@@ -1679,7 +1747,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)
|
||||||
@@ -1892,11 +1960,21 @@ func (t *Terminal) renderPreviewSpinner() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) renderPreviewArea(unchanged bool) {
|
func (t *Terminal) renderPreviewArea(unchanged bool) {
|
||||||
if unchanged {
|
if t.previewed.wipe && t.previewed.version != t.previewer.version {
|
||||||
|
t.previewed.wipe = false
|
||||||
|
t.pwindow.Erase()
|
||||||
|
} else if unchanged {
|
||||||
t.pwindow.MoveAndClear(0, 0) // Clear scroll offset display
|
t.pwindow.MoveAndClear(0, 0) // Clear scroll offset display
|
||||||
} else {
|
} else {
|
||||||
t.previewed.filled = false
|
t.previewed.filled = false
|
||||||
t.pwindow.Erase()
|
// We don't erase the window here to avoid flickering during scroll.
|
||||||
|
// However, tcell renderer uses double-buffering technique and there's no
|
||||||
|
// flickering. So we just erase the window and make the rest of the code
|
||||||
|
// simpler.
|
||||||
|
if !t.pwindow.EraseMaybe() {
|
||||||
|
t.pwindow.DrawBorder()
|
||||||
|
t.pwindow.Move(0, 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
height := t.pwindow.Height()
|
height := t.pwindow.Height()
|
||||||
@@ -1926,28 +2004,119 @@ func (t *Terminal) renderPreviewArea(unchanged bool) {
|
|||||||
t.renderPreviewScrollbar(headerLines, barLength, barStart)
|
t.renderPreviewScrollbar(headerLines, barLength, barStart)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) makeImageBorder(width int, top bool) string {
|
||||||
|
tl := "┌"
|
||||||
|
tr := "┐"
|
||||||
|
v := "╎"
|
||||||
|
h := "╌"
|
||||||
|
if !t.unicode {
|
||||||
|
tl = "+"
|
||||||
|
tr = "+"
|
||||||
|
h = "-"
|
||||||
|
v = "|"
|
||||||
|
}
|
||||||
|
repeat := util.Max(0, width-2)
|
||||||
|
if top {
|
||||||
|
return tl + strings.Repeat(h, repeat) + tr
|
||||||
|
}
|
||||||
|
return v + strings.Repeat(" ", repeat) + v
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Terminal) renderPreviewText(height int, lines []string, lineNo int, unchanged bool) {
|
func (t *Terminal) renderPreviewText(height int, lines []string, lineNo int, unchanged bool) {
|
||||||
maxWidth := t.pwindow.Width()
|
maxWidth := t.pwindow.Width()
|
||||||
var ansi *ansiState
|
var ansi *ansiState
|
||||||
spinnerRedraw := t.pwindow.Y() == 0
|
spinnerRedraw := t.pwindow.Y() == 0
|
||||||
|
wiped := false
|
||||||
|
image := false
|
||||||
|
wireframe := false
|
||||||
|
Loop:
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
var lbg tui.Color = -1
|
var lbg tui.Color = -1
|
||||||
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
|
||||||
break
|
break
|
||||||
} else if lineNo >= 0 {
|
} else if lineNo >= 0 {
|
||||||
|
x := t.pwindow.X()
|
||||||
|
y := t.pwindow.Y()
|
||||||
if spinnerRedraw && lineNo > 0 {
|
if spinnerRedraw && lineNo > 0 {
|
||||||
spinnerRedraw = false
|
spinnerRedraw = false
|
||||||
y := t.pwindow.Y()
|
|
||||||
x := t.pwindow.X()
|
|
||||||
t.renderPreviewSpinner()
|
t.renderPreviewSpinner()
|
||||||
t.pwindow.Move(y, x)
|
t.pwindow.Move(y, x)
|
||||||
}
|
}
|
||||||
|
for idx, passThrough := range passThroughs {
|
||||||
|
// Handling Sixel/iTerm image
|
||||||
|
requiredLines := 0
|
||||||
|
isSixel := strings.HasPrefix(passThrough, "\x1bP")
|
||||||
|
isItermImage := strings.HasPrefix(passThrough, "\x1b]1337;")
|
||||||
|
isImage := isSixel || isItermImage
|
||||||
|
if isImage {
|
||||||
|
t.previewed.wipe = true
|
||||||
|
// NOTE: We don't have a good way to get the height of an iTerm image,
|
||||||
|
// so we assume that it requires the full height of the preview
|
||||||
|
// window.
|
||||||
|
requiredLines = height
|
||||||
|
|
||||||
|
if isSixel && t.termSize.PxHeight > 0 {
|
||||||
|
rows := strings.Count(passThrough, "-")
|
||||||
|
requiredLines = int(math.Ceil(float64(rows*6*t.termSize.Lines) / float64(t.termSize.PxHeight)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render wireframe when the image cannot be displayed entirely
|
||||||
|
if requiredLines > 0 && y+requiredLines > height {
|
||||||
|
top := true
|
||||||
|
for ; y < height; y++ {
|
||||||
|
t.pwindow.MoveAndClear(y, 0)
|
||||||
|
t.pwindow.CFill(tui.ColPreview.Fg(), tui.ColPreview.Bg(), tui.AttrRegular, t.makeImageBorder(maxWidth, top))
|
||||||
|
top = false
|
||||||
|
}
|
||||||
|
wireframe = true
|
||||||
|
t.previewed.filled = true
|
||||||
|
t.previewer.scrollable = true
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear previous wireframe or any other text
|
||||||
|
if (t.previewed.wireframe || isImage && !t.previewed.image) && !wiped {
|
||||||
|
wiped = true
|
||||||
|
for i := y + 1; i < height; i++ {
|
||||||
|
t.pwindow.MoveAndClear(i, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
image = image || isImage
|
||||||
|
if idx == 0 {
|
||||||
|
t.pwindow.MoveAndClear(y, x)
|
||||||
|
} else {
|
||||||
|
t.pwindow.Move(y, x)
|
||||||
|
}
|
||||||
|
t.tui.PassThrough(passThrough)
|
||||||
|
|
||||||
|
if requiredLines > 0 {
|
||||||
|
if y+requiredLines == height {
|
||||||
|
t.pwindow.Move(height-1, maxWidth-1)
|
||||||
|
t.previewed.filled = true
|
||||||
|
break Loop
|
||||||
|
} else {
|
||||||
|
t.pwindow.MoveAndClear(y+requiredLines, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(passThroughs) > 0 && len(line) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
||||||
@@ -1988,6 +2157,8 @@ func (t *Terminal) renderPreviewText(height int, lines []string, lineNo int, unc
|
|||||||
}
|
}
|
||||||
lineNo++
|
lineNo++
|
||||||
}
|
}
|
||||||
|
t.previewed.image = image
|
||||||
|
t.previewed.wireframe = wireframe
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) renderPreviewScrollbar(yoff int, barLength int, barStart int) {
|
func (t *Terminal) renderPreviewScrollbar(yoff int, barLength int, barStart int) {
|
||||||
@@ -2035,7 +2206,7 @@ func (t *Terminal) printPreview() {
|
|||||||
unchanged := (t.previewed.filled || numLines == t.previewed.numLines) &&
|
unchanged := (t.previewed.filled || numLines == t.previewed.numLines) &&
|
||||||
t.previewer.version == t.previewed.version &&
|
t.previewer.version == t.previewed.version &&
|
||||||
t.previewer.offset == t.previewed.offset
|
t.previewer.offset == t.previewed.offset
|
||||||
t.previewer.scrollable = t.previewer.offset > 0 || numLines > height
|
t.previewer.scrollable = t.previewer.offset > t.previewOpts.headerLines || numLines > height
|
||||||
t.renderPreviewArea(unchanged)
|
t.renderPreviewArea(unchanged)
|
||||||
t.renderPreviewSpinner()
|
t.renderPreviewSpinner()
|
||||||
t.previewed.numLines = numLines
|
t.previewed.numLines = numLines
|
||||||
@@ -2208,7 +2379,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")
|
||||||
}
|
}
|
||||||
@@ -2445,7 +2616,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 {
|
||||||
@@ -2521,6 +2692,19 @@ func (t *Terminal) cancelPreview() {
|
|||||||
t.killPreview(exitCancel)
|
t.killPreview(exitCancel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) pwindowSize() tui.TermSize {
|
||||||
|
if t.pwindow == nil {
|
||||||
|
return tui.TermSize{}
|
||||||
|
}
|
||||||
|
size := tui.TermSize{Lines: t.pwindow.Height(), Columns: t.pwindow.Width()}
|
||||||
|
|
||||||
|
if t.termSize.PxWidth > 0 {
|
||||||
|
size.PxWidth = size.Columns * t.termSize.PxWidth / t.termSize.Columns
|
||||||
|
size.PxHeight = size.Lines * t.termSize.PxHeight / t.termSize.Lines
|
||||||
|
}
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
|
||||||
// Loop is called to start Terminal I/O
|
// Loop is called to start Terminal I/O
|
||||||
func (t *Terminal) Loop() {
|
func (t *Terminal) Loop() {
|
||||||
// prof := profile.Start(profile.ProfilePath("/tmp/"))
|
// prof := profile.Start(profile.ProfilePath("/tmp/"))
|
||||||
@@ -2572,12 +2756,13 @@ func (t *Terminal) Loop() {
|
|||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
<-resizeChan
|
<-resizeChan
|
||||||
t.reqBox.Set(reqFullRedraw, nil)
|
t.reqBox.Set(reqResize, nil)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
t.initFunc()
|
t.initFunc()
|
||||||
|
t.termSize = t.tui.Size()
|
||||||
t.resizeWindows(false)
|
t.resizeWindows(false)
|
||||||
t.printPrompt()
|
t.printPrompt()
|
||||||
t.printInfo()
|
t.printInfo()
|
||||||
@@ -2611,6 +2796,7 @@ func (t *Terminal) Loop() {
|
|||||||
var items []*Item
|
var items []*Item
|
||||||
var commandTemplate string
|
var commandTemplate string
|
||||||
var pwindow tui.Window
|
var pwindow tui.Window
|
||||||
|
var pwindowSize tui.TermSize
|
||||||
initialOffset := 0
|
initialOffset := 0
|
||||||
t.previewBox.Wait(func(events *util.Events) {
|
t.previewBox.Wait(func(events *util.Events) {
|
||||||
for req, value := range *events {
|
for req, value := range *events {
|
||||||
@@ -2621,6 +2807,7 @@ func (t *Terminal) Loop() {
|
|||||||
initialOffset = request.scrollOffset
|
initialOffset = request.scrollOffset
|
||||||
items = request.list
|
items = request.list
|
||||||
pwindow = request.pwindow
|
pwindow = request.pwindow
|
||||||
|
pwindowSize = request.pwindowSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
events.Clear()
|
events.Clear()
|
||||||
@@ -2632,14 +2819,15 @@ func (t *Terminal) Loop() {
|
|||||||
command := t.replacePlaceholder(commandTemplate, false, string(query), items)
|
command := t.replacePlaceholder(commandTemplate, false, string(query), items)
|
||||||
cmd := util.ExecCommand(command, true)
|
cmd := util.ExecCommand(command, true)
|
||||||
env := t.environ()
|
env := t.environ()
|
||||||
if pwindow != nil {
|
if pwindowSize.Lines > 0 {
|
||||||
height := pwindow.Height()
|
lines := fmt.Sprintf("LINES=%d", pwindowSize.Lines)
|
||||||
lines := fmt.Sprintf("LINES=%d", height)
|
columns := fmt.Sprintf("COLUMNS=%d", pwindowSize.Columns)
|
||||||
columns := fmt.Sprintf("COLUMNS=%d", pwindow.Width())
|
|
||||||
env = append(env, lines)
|
env = append(env, lines)
|
||||||
env = append(env, "FZF_PREVIEW_"+lines)
|
env = append(env, "FZF_PREVIEW_"+lines)
|
||||||
env = append(env, columns)
|
env = append(env, columns)
|
||||||
env = append(env, "FZF_PREVIEW_"+columns)
|
env = append(env, "FZF_PREVIEW_"+columns)
|
||||||
|
env = append(env, fmt.Sprintf("FZF_PREVIEW_TOP=%d", t.tui.Top()+pwindow.Top()))
|
||||||
|
env = append(env, fmt.Sprintf("FZF_PREVIEW_LEFT=%d", pwindow.Left()))
|
||||||
}
|
}
|
||||||
cmd.Env = env
|
cmd.Env = env
|
||||||
|
|
||||||
@@ -2767,7 +2955,7 @@ func (t *Terminal) Loop() {
|
|||||||
if len(command) > 0 && t.canPreview() {
|
if len(command) > 0 && t.canPreview() {
|
||||||
_, list := t.buildPlusList(command, false)
|
_, list := t.buildPlusList(command, false)
|
||||||
t.cancelPreview()
|
t.cancelPreview()
|
||||||
t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.pwindow, t.evaluateScrollOffset(), list})
|
t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.pwindow, t.pwindowSize(), t.evaluateScrollOffset(), list})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2812,7 +3000,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
|
||||||
@@ -2835,7 +3023,10 @@ func (t *Terminal) Loop() {
|
|||||||
case reqReinit:
|
case reqReinit:
|
||||||
t.tui.Resume(t.fullscreen, t.sigstop)
|
t.tui.Resume(t.fullscreen, t.sigstop)
|
||||||
t.redraw()
|
t.redraw()
|
||||||
case reqFullRedraw:
|
case reqResize, reqFullRedraw:
|
||||||
|
if req == reqResize {
|
||||||
|
t.termSize = t.tui.Size()
|
||||||
|
}
|
||||||
wasHidden := t.pwindow == nil
|
wasHidden := t.pwindow == nil
|
||||||
t.redraw()
|
t.redraw()
|
||||||
if wasHidden && t.hasPreviewWindow() {
|
if wasHidden && t.hasPreviewWindow() {
|
||||||
@@ -2924,8 +3115,18 @@ 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 serverActions := <-t.serverInputChan:
|
||||||
event = tui.Invalid.AsEvent()
|
event = tui.Invalid.AsEvent()
|
||||||
|
if t.listenAddr == nil || t.listenAddr.IsLocal() || t.listenUnsafe {
|
||||||
|
actions = serverActions
|
||||||
|
} else {
|
||||||
|
for _, action := range serverActions {
|
||||||
|
if !processExecution(action.t) {
|
||||||
|
actions = append(actions, action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
needBarrier = false
|
needBarrier = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2999,6 +3200,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 {
|
||||||
@@ -3050,8 +3253,12 @@ func (t *Terminal) Loop() {
|
|||||||
if valid {
|
if valid {
|
||||||
t.cancelPreview()
|
t.cancelPreview()
|
||||||
t.previewBox.Set(reqPreviewEnqueue,
|
t.previewBox.Set(reqPreviewEnqueue,
|
||||||
previewRequest{t.previewOpts.command, t.pwindow, t.evaluateScrollOffset(), list})
|
previewRequest{t.previewOpts.command, t.pwindow, t.pwindowSize(), t.evaluateScrollOffset(), list})
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Discard the preview content so that it won't accidentally appear
|
||||||
|
// when preview window is re-enabled and previewDelay is triggered
|
||||||
|
t.previewer.lines = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case actTogglePreviewWrap:
|
case actTogglePreviewWrap:
|
||||||
@@ -3106,6 +3313,7 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
case actBeginningOfLine:
|
case actBeginningOfLine:
|
||||||
t.cx = 0
|
t.cx = 0
|
||||||
|
t.xoffset = 0
|
||||||
case actBackwardChar:
|
case actBackwardChar:
|
||||||
if t.cx > 0 {
|
if t.cx > 0 {
|
||||||
t.cx--
|
t.cx--
|
||||||
@@ -3354,6 +3562,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)
|
||||||
@@ -3404,6 +3631,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
|
||||||
@@ -3440,13 +3670,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
|
||||||
}
|
}
|
||||||
@@ -3486,7 +3726,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--
|
||||||
}
|
}
|
||||||
@@ -3537,15 +3777,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:
|
||||||
@@ -3758,9 +4002,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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
func notifyOnResize(resizeChan chan<- os.Signal) {
|
func notifyOnResize(resizeChan chan<- os.Signal) {
|
||||||
@@ -14,7 +16,12 @@ func notifyOnResize(resizeChan chan<- os.Signal) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func notifyStop(p *os.Process) {
|
func notifyStop(p *os.Process) {
|
||||||
p.Signal(syscall.SIGSTOP)
|
pid := p.Pid
|
||||||
|
pgid, err := unix.Getpgid(pid)
|
||||||
|
if err == nil {
|
||||||
|
pid = pgid * -1
|
||||||
|
}
|
||||||
|
unix.Kill(pid, syscall.SIGSTOP)
|
||||||
}
|
}
|
||||||
|
|
||||||
func notifyOnCont(resizeChan chan<- os.Signal) {
|
func notifyOnCont(resizeChan chan<- os.Signal) {
|
||||||
|
|||||||
@@ -33,12 +33,15 @@ 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() {}
|
||||||
func (r *FullscreenRenderer) Close() {}
|
func (r *FullscreenRenderer) Close() {}
|
||||||
|
func (r *FullscreenRenderer) Size() TermSize { return TermSize{} }
|
||||||
|
|
||||||
func (r *FullscreenRenderer) GetChar() Event { return Event{} }
|
func (r *FullscreenRenderer) GetChar() Event { return Event{} }
|
||||||
|
func (r *FullscreenRenderer) Top() int { return 0 }
|
||||||
func (r *FullscreenRenderer) MaxX() int { return 0 }
|
func (r *FullscreenRenderer) MaxX() int { return 0 }
|
||||||
func (r *FullscreenRenderer) MaxY() int { return 0 }
|
func (r *FullscreenRenderer) MaxY() int { return 0 }
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ const consoleDevice string = "/dev/tty"
|
|||||||
var offsetRegexp *regexp.Regexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R")
|
var offsetRegexp *regexp.Regexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R")
|
||||||
var offsetRegexpBegin *regexp.Regexp = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R")
|
var offsetRegexpBegin *regexp.Regexp = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R")
|
||||||
|
|
||||||
|
func (r *LightRenderer) PassThrough(str string) {
|
||||||
|
r.queued.WriteString("\x1b7" + str + "\x1b8")
|
||||||
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) stderr(str string) {
|
func (r *LightRenderer) stderr(str string) {
|
||||||
r.stderrInternal(str, true, "")
|
r.stderrInternal(str, true, "")
|
||||||
}
|
}
|
||||||
@@ -398,7 +402,7 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
return Event{F3, 0, nil}
|
return Event{F3, 0, nil}
|
||||||
case 'S':
|
case 'S':
|
||||||
return Event{F4, 0, nil}
|
return Event{F4, 0, nil}
|
||||||
case '1', '2', '3', '4', '5', '6':
|
case '1', '2', '3', '4', '5', '6', '7', '8':
|
||||||
if len(r.buffer) < 4 {
|
if len(r.buffer) < 4 {
|
||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
}
|
}
|
||||||
@@ -449,6 +453,10 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
return Event{PgUp, 0, nil}
|
return Event{PgUp, 0, nil}
|
||||||
case '6':
|
case '6':
|
||||||
return Event{PgDn, 0, nil}
|
return Event{PgDn, 0, nil}
|
||||||
|
case '7':
|
||||||
|
return Event{Home, 0, nil}
|
||||||
|
case '8':
|
||||||
|
return Event{End, 0, nil}
|
||||||
case '1':
|
case '1':
|
||||||
switch r.buffer[3] {
|
switch r.buffer[3] {
|
||||||
case '~':
|
case '~':
|
||||||
@@ -716,6 +724,10 @@ func (r *LightRenderer) Close() {
|
|||||||
r.restoreTerminal()
|
r.restoreTerminal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) Top() int {
|
||||||
|
return r.yoffset
|
||||||
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) MaxX() int {
|
func (r *LightRenderer) MaxX() int {
|
||||||
return r.width
|
return r.width
|
||||||
}
|
}
|
||||||
@@ -751,6 +763,10 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, prev
|
|||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) DrawBorder() {
|
||||||
|
w.drawBorder(false)
|
||||||
|
}
|
||||||
|
|
||||||
func (w *LightWindow) DrawHBorder() {
|
func (w *LightWindow) DrawHBorder() {
|
||||||
w.drawBorder(true)
|
w.drawBorder(true)
|
||||||
}
|
}
|
||||||
@@ -1083,14 +1099,21 @@ func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillRetu
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) FinishFill() {
|
func (w *LightWindow) FinishFill() {
|
||||||
w.MoveAndClear(w.posy, w.posx)
|
if w.posy < w.height {
|
||||||
|
w.MoveAndClear(w.posy, w.posx)
|
||||||
|
}
|
||||||
for y := w.posy + 1; y < w.height; y++ {
|
for y := w.posy + 1; y < w.height; y++ {
|
||||||
w.MoveAndClear(y, 0)
|
w.MoveAndClear(y, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) Erase() {
|
func (w *LightWindow) Erase() {
|
||||||
w.drawBorder(false)
|
w.DrawBorder()
|
||||||
// We don't erase the window here to avoid flickering during scroll
|
w.Move(0, 0)
|
||||||
|
w.FinishFill()
|
||||||
w.Move(0, 0)
|
w.Move(0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) EraseMaybe() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -108,3 +109,11 @@ func (r *LightRenderer) getch(nonblock bool) (int, bool) {
|
|||||||
}
|
}
|
||||||
return int(b[0]), true
|
return int(b[0]), true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) Size() TermSize {
|
||||||
|
ws, err := unix.IoctlGetWinsize(int(r.ttyin.Fd()), unix.TIOCGWINSZ)
|
||||||
|
if err != nil {
|
||||||
|
return TermSize{}
|
||||||
|
}
|
||||||
|
return TermSize{int(ws.Row), int(ws.Col), int(ws.Xpixel), int(ws.Ypixel)}
|
||||||
|
}
|
||||||
|
|||||||
@@ -110,16 +110,24 @@ func (r *LightRenderer) restoreTerminal() error {
|
|||||||
return windows.SetConsoleMode(windows.Handle(r.outHandle), r.origStateOutput)
|
return windows.SetConsoleMode(windows.Handle(r.outHandle), r.origStateOutput)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) updateTerminalSize() {
|
func (r *LightRenderer) Size() TermSize {
|
||||||
|
var w, h int
|
||||||
var bufferInfo windows.ConsoleScreenBufferInfo
|
var bufferInfo windows.ConsoleScreenBufferInfo
|
||||||
if err := windows.GetConsoleScreenBufferInfo(windows.Handle(r.outHandle), &bufferInfo); err != nil {
|
if err := windows.GetConsoleScreenBufferInfo(windows.Handle(r.outHandle), &bufferInfo); err != nil {
|
||||||
r.width = getEnv("COLUMNS", defaultWidth)
|
w = getEnv("COLUMNS", defaultWidth)
|
||||||
r.height = r.maxHeightFunc(getEnv("LINES", defaultHeight))
|
h = r.maxHeightFunc(getEnv("LINES", defaultHeight))
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
r.width = int(bufferInfo.Window.Right - bufferInfo.Window.Left)
|
w = int(bufferInfo.Window.Right - bufferInfo.Window.Left)
|
||||||
r.height = r.maxHeightFunc(int(bufferInfo.Window.Bottom - bufferInfo.Window.Top))
|
h = r.maxHeightFunc(int(bufferInfo.Window.Bottom - bufferInfo.Window.Top))
|
||||||
}
|
}
|
||||||
|
return TermSize{h, w, 0, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) updateTerminalSize() {
|
||||||
|
size := r.Size()
|
||||||
|
r.width = size.Columns
|
||||||
|
r.height = size.Lines
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) findOffset() (row int, col int) {
|
func (r *LightRenderer) findOffset() (row int, col int) {
|
||||||
|
|||||||
@@ -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/pull/650#issuecomment-1806442846
|
||||||
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
|
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
|
func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
|
||||||
@@ -167,6 +172,10 @@ func (r *FullscreenRenderer) Init() {
|
|||||||
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *FullscreenRenderer) Top() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) MaxX() int {
|
func (r *FullscreenRenderer) MaxX() int {
|
||||||
ncols, _ := _screen.Size()
|
ncols, _ := _screen.Size()
|
||||||
return int(ncols)
|
return int(ncols)
|
||||||
@@ -198,6 +207,12 @@ func (r *FullscreenRenderer) Refresh() {
|
|||||||
// noop
|
// noop
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Pixel width and height not implemented
|
||||||
|
func (r *FullscreenRenderer) Size() TermSize {
|
||||||
|
cols, lines := _screen.Size()
|
||||||
|
return TermSize{lines, cols, 0, 0}
|
||||||
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) GetChar() Event {
|
func (r *FullscreenRenderer) GetChar() Event {
|
||||||
ev := _screen.PollEvent()
|
ev := _screen.PollEvent()
|
||||||
switch ev := ev.(type) {
|
switch ev := ev.(type) {
|
||||||
@@ -536,9 +551,15 @@ func fill(x, y, w, h int, n ColorPair, r rune) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Erase() {
|
func (w *TcellWindow) Erase() {
|
||||||
|
w.drawBorder(false)
|
||||||
fill(w.left-1, w.top, w.width+1, w.height-1, w.normal, ' ')
|
fill(w.left-1, w.top, w.width+1, w.height-1, w.normal, ' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *TcellWindow) EraseMaybe() bool {
|
||||||
|
w.Erase()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Enclose(y int, x int) bool {
|
func (w *TcellWindow) Enclose(y int, x int) bool {
|
||||||
return x >= w.left && x < (w.left+w.width) &&
|
return x >= w.left && x < (w.left+w.width) &&
|
||||||
y >= w.top && y < (w.top+w.height)
|
y >= w.top && y < (w.top+w.height)
|
||||||
@@ -687,6 +708,10 @@ func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
|
|||||||
return w.fillString(str, NewColorPair(fg, bg, a))
|
return w.fillString(str, NewColorPair(fg, bg, a))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *TcellWindow) DrawBorder() {
|
||||||
|
w.drawBorder(false)
|
||||||
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) DrawHBorder() {
|
func (w *TcellWindow) DrawHBorder() {
|
||||||
w.drawBorder(true)
|
w.drawBorder(true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -465,6 +473,13 @@ func MakeTransparentBorder() BorderStyle {
|
|||||||
bottomRight: ' '}
|
bottomRight: ' '}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TermSize struct {
|
||||||
|
Lines int
|
||||||
|
Columns int
|
||||||
|
PxWidth int
|
||||||
|
PxHeight int
|
||||||
|
}
|
||||||
|
|
||||||
type Renderer interface {
|
type Renderer interface {
|
||||||
Init()
|
Init()
|
||||||
Resize(maxHeightFunc func(int) int)
|
Resize(maxHeightFunc func(int) int)
|
||||||
@@ -474,13 +489,17 @@ type Renderer interface {
|
|||||||
RefreshWindows(windows []Window)
|
RefreshWindows(windows []Window)
|
||||||
Refresh()
|
Refresh()
|
||||||
Close()
|
Close()
|
||||||
|
PassThrough(string)
|
||||||
NeedScrollbarRedraw() bool
|
NeedScrollbarRedraw() bool
|
||||||
|
|
||||||
GetChar() Event
|
GetChar() Event
|
||||||
|
|
||||||
|
Top() int
|
||||||
MaxX() int
|
MaxX() int
|
||||||
MaxY() int
|
MaxY() int
|
||||||
|
|
||||||
|
Size() TermSize
|
||||||
|
|
||||||
NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window
|
NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -490,6 +509,7 @@ type Window interface {
|
|||||||
Width() int
|
Width() int
|
||||||
Height() int
|
Height() int
|
||||||
|
|
||||||
|
DrawBorder()
|
||||||
DrawHBorder()
|
DrawHBorder()
|
||||||
Refresh()
|
Refresh()
|
||||||
FinishFill()
|
FinishFill()
|
||||||
@@ -506,6 +526,7 @@ type Window interface {
|
|||||||
Fill(text string) FillReturn
|
Fill(text string) FillReturn
|
||||||
CFill(fg Color, bg Color, attr Attr, text string) FillReturn
|
CFill(fg Color, bg Color, attr Attr, text string) FillReturn
|
||||||
Erase()
|
Erase()
|
||||||
|
EraseMaybe() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type FullscreenRenderer struct {
|
type FullscreenRenderer struct {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
102
test/test_go.rb
102
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] }
|
||||||
@@ -2742,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 }
|
||||||
@@ -2953,6 +3020,13 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys :x
|
tmux.send_keys :x
|
||||||
tmux.until { |lines| assert(lines.any? { |line| line.include?('[x-foo]') }) }
|
tmux.until { |lines| assert(lines.any? { |line| line.include?('[x-foo]') }) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_preview_window_hidden_on_focus
|
||||||
|
tmux.send_keys "seq 3 | #{FZF} --preview 'echo {}' --bind focus:hide-preview", :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 1' }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 2' }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module TestShell
|
module TestShell
|
||||||
@@ -3298,6 +3372,34 @@ module CompletionTest
|
|||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'unset -f _fzf_comprun', :Enter
|
tmux.send_keys 'unset -f _fzf_comprun', :Enter
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_ssh_completion
|
||||||
|
(1..5).each { |i| FileUtils.touch("/tmp/fzf-test-ssh-#{i}") }
|
||||||
|
|
||||||
|
tmux.send_keys 'ssh jg@localhost**', :Tab
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.match_count >= 1
|
||||||
|
end
|
||||||
|
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert lines.any_include?('ssh jg@localhost') }
|
||||||
|
tmux.send_keys ' -i /tmp/fzf-test-ssh**', :Tab
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.match_count >= 5
|
||||||
|
assert_equal 0, lines.select_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :Tab, :Tab, :Tab
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 3, lines.select_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert lines.any_include?('ssh jg@localhost -i /tmp/fzf-test-ssh-') }
|
||||||
|
|
||||||
|
tmux.send_keys 'localhost**', :Tab
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.match_count >= 1
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class TestBash < TestBase
|
class TestBash < TestBase
|
||||||
|
|||||||
@@ -4,3 +4,7 @@ ba = "ba"
|
|||||||
fo = "fo"
|
fo = "fo"
|
||||||
enew = "enew"
|
enew = "enew"
|
||||||
tabe = "tabe"
|
tabe = "tabe"
|
||||||
|
Iterm = "Iterm"
|
||||||
|
|
||||||
|
[files]
|
||||||
|
extend-exclude = ["README.md"]
|
||||||
|
|||||||
Reference in New Issue
Block a user