mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-14 22:33:47 -05:00
Compare commits
109 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4a9ccd6af | ||
|
|
cbf91f2ed3 | ||
|
|
b1460d4787 | ||
|
|
7dc9e14874 | ||
|
|
1616ed543d | ||
|
|
dc73fba188 | ||
|
|
ef148dfd37 | ||
|
|
93bbb3032d | ||
|
|
4c83d8596d | ||
|
|
d453e6d7db | ||
|
|
c29533994f | ||
|
|
1afe13b5b5 | ||
|
|
36600eaaa9 | ||
|
|
3ee1fc2034 | ||
|
|
e2f93e5a2d | ||
|
|
cfdf2f1153 | ||
|
|
e042143e3f | ||
|
|
7c613d0d9b | ||
|
|
b00d46bc14 | ||
|
|
555b0d235b | ||
|
|
564daf9a7d | ||
|
|
41bcbe342f | ||
|
|
dbe8dc344e | ||
|
|
e33fb59da1 | ||
|
|
7aa88aa115 | ||
|
|
2b6d600879 | ||
|
|
05c765d442 | ||
|
|
49b496269c | ||
|
|
7405925952 | ||
|
|
3afd543a7e | ||
|
|
b4f2cde5ac | ||
|
|
ed53ef7cee | ||
|
|
12630b124d | ||
|
|
1d59ac09d2 | ||
|
|
a8f3a0dd59 | ||
|
|
124cd70710 | ||
|
|
782de139c8 | ||
|
|
32eb32ee5e | ||
|
|
2f51eb2b41 | ||
|
|
0ccbd79e10 | ||
|
|
99bd6de541 | ||
|
|
1fef36e4bc | ||
|
|
89375005b5 | ||
|
|
88e78c9193 | ||
|
|
29a19ad080 | ||
|
|
2a039ab746 | ||
|
|
7e9a0fcdbd | ||
|
|
7a97532547 | ||
|
|
996abb2831 | ||
|
|
da500a358f | ||
|
|
c36b846acc | ||
|
|
d9b5c9b2be | ||
|
|
3dee8778d0 | ||
|
|
d4216b0dcc | ||
|
|
bfe2bf4dce | ||
|
|
561f9291fd | ||
|
|
b5b0d6b3ea | ||
|
|
a90426b7ca | ||
|
|
303c3bae7f | ||
|
|
6b4358f641 | ||
|
|
552158f3ad | ||
|
|
7205203dc8 | ||
|
|
0cadf70072 | ||
|
|
076b3d0a9a | ||
|
|
7b0c9e04d3 | ||
|
|
573df524fe | ||
|
|
aee417c46a | ||
|
|
04db44067d | ||
|
|
5b204c54f9 | ||
|
|
daa602422d | ||
|
|
04dfb14e32 | ||
|
|
c24256cba3 | ||
|
|
685fb71d89 | ||
|
|
83b6033906 | ||
|
|
01e7668915 | ||
|
|
0994d9c881 | ||
|
|
030428ba43 | ||
|
|
8a110e02b9 | ||
|
|
86d92c17c4 | ||
|
|
c4cc7891b4 | ||
|
|
218843b9f1 | ||
|
|
d274d093af | ||
|
|
6432f00f0d | ||
|
|
4e9e842aa4 | ||
|
|
07880ca441 | ||
|
|
bcda25a513 | ||
|
|
8256fcde15 | ||
|
|
af65aa298a | ||
|
|
6834d17844 | ||
|
|
ed511d7867 | ||
|
|
cd8d736a9f | ||
|
|
0952b2dfd4 | ||
|
|
4bedd33c59 | ||
|
|
c5fb0c43f9 | ||
|
|
9e4780510e | ||
|
|
e8405f40fe | ||
|
|
065b9e6fb2 | ||
|
|
98141ca7d8 | ||
|
|
501577ab28 | ||
|
|
5669f48343 | ||
|
|
24ff66d4a9 | ||
|
|
bf184449bc | ||
|
|
7b98c2c653 | ||
|
|
b6add2a257 | ||
|
|
2bd41f1330 | ||
|
|
c37cd11ca5 | ||
|
|
9dee8edc0c | ||
|
|
f6aa28c380 | ||
|
|
dba1644518 |
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@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: crate-ci/typos@v1.20.10
|
- uses: crate-ci/typos@v1.21.0
|
||||||
|
|||||||
82
ADVANCED.md
82
ADVANCED.md
@@ -1,18 +1,17 @@
|
|||||||
Advanced fzf examples
|
Advanced fzf examples
|
||||||
======================
|
======================
|
||||||
|
|
||||||
* *Last update: 2024/01/20*
|
* *Last update: 2024/06/06*
|
||||||
* *Requires fzf 0.46.0 or above*
|
* *Requires fzf 0.53.0 or later*
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- vim-markdown-toc GFM -->
|
<!-- vim-markdown-toc GFM -->
|
||||||
|
|
||||||
* [Introduction](#introduction)
|
* [Introduction](#introduction)
|
||||||
* [Screen Layout](#screen-layout)
|
* [Display modes](#display-modes)
|
||||||
* [`--height`](#--height)
|
* [`--height`](#--height)
|
||||||
* [`fzf-tmux`](#fzf-tmux)
|
* [`--tmux`](#--tmux)
|
||||||
* [Popup window support](#popup-window-support)
|
|
||||||
* [Dynamic reloading of the list](#dynamic-reloading-of-the-list)
|
* [Dynamic reloading of the list](#dynamic-reloading-of-the-list)
|
||||||
* [Updating the list of processes by pressing CTRL-R](#updating-the-list-of-processes-by-pressing-ctrl-r)
|
* [Updating the list of processes by pressing CTRL-R](#updating-the-list-of-processes-by-pressing-ctrl-r)
|
||||||
* [Toggling between data sources](#toggling-between-data-sources)
|
* [Toggling between data sources](#toggling-between-data-sources)
|
||||||
@@ -63,7 +62,7 @@ learn its wide variety of features.
|
|||||||
This document will guide you through some examples that will familiarize you
|
This document will guide you through some examples that will familiarize you
|
||||||
with the advanced features of fzf.
|
with the advanced features of fzf.
|
||||||
|
|
||||||
Screen Layout
|
Display modes
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
### `--height`
|
### `--height`
|
||||||
@@ -104,56 +103,55 @@ Define `$FZF_DEFAULT_OPTS` like so:
|
|||||||
export FZF_DEFAULT_OPTS="--height=40% --layout=reverse --info=inline --border --margin=1 --padding=1"
|
export FZF_DEFAULT_OPTS="--height=40% --layout=reverse --info=inline --border --margin=1 --padding=1"
|
||||||
```
|
```
|
||||||
|
|
||||||
### `fzf-tmux`
|
### `--tmux`
|
||||||
|
|
||||||
Before fzf had `--height` option, we would open fzf in a tmux split pane not
|
(Requires tmux 3.3 or later)
|
||||||
to take up the whole screen. This is done using `fzf-tmux` script.
|
|
||||||
|
If you're using tmux, you can open fzf in a tmux popup using `--tmux` option.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Open fzf on a tmux split pane below the current pane.
|
# Open fzf in a tmux popup at the center of the screen with 70% width and height
|
||||||
# Takes the same set of options.
|
fzf --tmux 70%
|
||||||
fzf-tmux --layout=reverse
|
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
The limitation of `fzf-tmux` is that it only works when you're on tmux unlike
|
`--tmux` option is silently ignored if you're not on tmux. So if you're trying
|
||||||
`--height` option. But the advantage of it is that it's more flexible.
|
to avoid opening fzf in fullscreen, try specifying both `--height` and `--tmux`.
|
||||||
(See `man fzf-tmux` for available options.)
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# On the right (50%)
|
# --tmux is specified later so it takes precedence over --height when on tmux.
|
||||||
fzf-tmux -r
|
# If you're not on tmux, --tmux is ignored and --height is used instead.
|
||||||
|
fzf --height 70% --tmux 70%
|
||||||
# On the left (30%)
|
|
||||||
fzf-tmux -l30%
|
|
||||||
|
|
||||||
# Above the cursor
|
|
||||||
fzf-tmux -u30%
|
|
||||||
```
|
```
|
||||||
|
|
||||||

|
You can also specify the position, width, and height of the popup window in
|
||||||
|
the following format:
|
||||||
|
|
||||||

|
* `[center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]`
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
#### Popup window support
|
|
||||||
|
|
||||||
But here's the really cool part; tmux 3.2 added support for popup windows. So
|
|
||||||
you can open fzf in a popup window, which is quite useful if you frequently
|
|
||||||
use split panes.
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Open tmux in a tmux popup window (default size: 50% of the screen)
|
# 100% width and 60% height
|
||||||
fzf-tmux -p
|
fzf --tmux 100%,60% --border horizontal
|
||||||
|
|
||||||
# 80% width, 60% height
|
|
||||||
fzf-tmux -p 80%,60%
|
|
||||||
```
|
```
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
```sh
|
||||||
|
# On the right (50% width)
|
||||||
|
fzf --tmux right
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```sh
|
||||||
|
# On the left (40% width and 70% height)
|
||||||
|
fzf --tmux left,40%,70%
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
> You might also want to check out my tmux plugins which support this popup
|
> You might also want to check out my tmux plugins which support this popup
|
||||||
> window layout.
|
> window layout.
|
||||||
>
|
>
|
||||||
@@ -536,8 +534,8 @@ pods() {
|
|||||||
--bind 'start:reload:$command' \
|
--bind 'start:reload:$command' \
|
||||||
--bind 'ctrl-r:reload:$command' \
|
--bind 'ctrl-r:reload:$command' \
|
||||||
--bind 'ctrl-/:change-preview-window(80%,border-bottom|hidden|)' \
|
--bind 'ctrl-/:change-preview-window(80%,border-bottom|hidden|)' \
|
||||||
--bind 'enter:execute:kubectl exec -it --namespace {1} {2} -- bash > /dev/tty' \
|
--bind 'enter:execute:kubectl exec -it --namespace {1} {2} -- bash' \
|
||||||
--bind 'ctrl-o:execute:${EDITOR:-vim} <(kubectl logs --all-containers --namespace {1} {2}) > /dev/tty' \
|
--bind 'ctrl-o:execute:${EDITOR:-vim} <(kubectl logs --all-containers --namespace {1} {2})' \
|
||||||
--preview-window up:follow \
|
--preview-window up:follow \
|
||||||
--preview 'kubectl logs --follow --all-containers --tail=10000 --namespace {1} {2}' "$@"
|
--preview 'kubectl logs --follow --all-containers --tail=10000 --namespace {1} {2}' "$@"
|
||||||
}
|
}
|
||||||
|
|||||||
103
CHANGELOG.md
103
CHANGELOG.md
@@ -1,6 +1,109 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.53.0
|
||||||
|
------
|
||||||
|
- Multi-line display
|
||||||
|
- See [Processing multi-line items](https://junegunn.github.io/fzf/tips/processing-multi-line-items/)
|
||||||
|
- fzf can now display multi-line items
|
||||||
|
```sh
|
||||||
|
# All bash functions, highlighted
|
||||||
|
declare -f | perl -0777 -pe 's/^}\n/}\0/gm' |
|
||||||
|
bat --plain --language bash --color always |
|
||||||
|
fzf --read0 --ansi --reverse --multi --highlight-line
|
||||||
|
|
||||||
|
# Ripgrep multi-line output
|
||||||
|
rg --pretty bash | perl -0777 -pe 's/\n\n/\n\0/gm' |
|
||||||
|
fzf --read0 --ansi --multi --highlight-line --reverse --tmux 70%
|
||||||
|
```
|
||||||
|
- To disable multi-line display, use `--no-multi-line`
|
||||||
|
- CTRL-R bindings of bash, zsh, and fish have been updated to leverage multi-line display
|
||||||
|
- The default `--pointer` and `--marker` have been changed from `>` to Unicode bar characters as they look better with multi-line items
|
||||||
|
- Added `--marker-multi-line` to customize the select marker for multi-line entries with the default set to `╻┃╹`
|
||||||
|
```
|
||||||
|
╻First line
|
||||||
|
┃...
|
||||||
|
╹Last line
|
||||||
|
```
|
||||||
|
- Native tmux integration
|
||||||
|
- Added `--tmux` option to replace fzf-tmux script and simplify distribution
|
||||||
|
```sh
|
||||||
|
# --tmux [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]
|
||||||
|
# Center, 100% width and 70% height
|
||||||
|
fzf --tmux 100%,70% --border horizontal --padding 1,2
|
||||||
|
|
||||||
|
# Left, 30% width
|
||||||
|
fzf --tmux left,30%
|
||||||
|
|
||||||
|
# Bottom, 50% height
|
||||||
|
fzf --tmux bottom,50%
|
||||||
|
```
|
||||||
|
- To keep the implementation simple, it only uses popups. You need tmux 3.3 or later.
|
||||||
|
- To use `--tmux` in Vim plugin:
|
||||||
|
```vim
|
||||||
|
let g:fzf_layout = { 'tmux': '100%,70%' }
|
||||||
|
```
|
||||||
|
- Added support for endless input streams
|
||||||
|
- See [Browsing log stream with fzf](https://junegunn.github.io/fzf/tips/browsing-log-streams/)
|
||||||
|
- Added `--tail=NUM` option to limit the number of items to keep in memory. This is useful when you want to browse an endless stream of data (e.g. log stream) with fzf while limiting memory usage.
|
||||||
|
```sh
|
||||||
|
# Interactive filtering of a log stream
|
||||||
|
tail -f *.log | fzf --tail 100000 --tac --no-sort --exact
|
||||||
|
```
|
||||||
|
- Better Windows Support
|
||||||
|
- fzf now works on Git bash (mintty) out of the box via winpty integration
|
||||||
|
- Many fixes and improvements for Windows
|
||||||
|
- man page is now embedded in the binary; `fzf --man` to see it
|
||||||
|
- Changed the default `--scroll-off` to 3, as we think it's a better default
|
||||||
|
- Process started by `execute` action now directly writes to and reads from `/dev/tty`. Manual `/dev/tty` redirection for interactive programs is no longer required.
|
||||||
|
```sh
|
||||||
|
# Vim will work fine without /dev/tty redirection
|
||||||
|
ls | fzf --bind 'space:execute:vim {}' > selected
|
||||||
|
```
|
||||||
|
- Added `print(...)` action to queue an arbitrary string to be printed on exit. This was mainly added to work around the limitation of `--expect` where it's not compatible with `--bind` on the same key and it would ignore other actions bound to it.
|
||||||
|
```sh
|
||||||
|
# This doesn't work as expected because --expect is not compatible with --bind
|
||||||
|
fzf --multi --expect ctrl-y --bind 'ctrl-y:select-all'
|
||||||
|
|
||||||
|
# This is something you can do instead
|
||||||
|
fzf --multi --bind 'enter:print()+accept,ctrl-y:select-all+print(ctrl-y)+accept'
|
||||||
|
```
|
||||||
|
- We also considered making them compatible, but realized that some users may have been relying on the current behavior.
|
||||||
|
- [`NO_COLOR`](https://no-color.org/) environment variable is now respected. If the variable is set, fzf defaults to `--no-color` unless otherwise specified.
|
||||||
|
|
||||||
|
0.52.1
|
||||||
|
------
|
||||||
|
- Fixed a critical bug in the Windows version
|
||||||
|
- Windows users are strongly encouraged to upgrade to this version
|
||||||
|
|
||||||
|
0.52.0
|
||||||
|
------
|
||||||
|
- Added `--highlight-line` to highlight the whole current line (à la `set cursorline` of Vim)
|
||||||
|
- Added color names for selected lines: `selected-fg`, `selected-bg`, and `selected-hl`
|
||||||
|
```sh
|
||||||
|
fzf --border --multi --info inline-right --layout reverse --marker ▏ --pointer ▌ --prompt '▌ ' \
|
||||||
|
--highlight-line --color gutter:-1,selected-bg:238,selected-fg:146,current-fg:189
|
||||||
|
```
|
||||||
|
- Added `click-header` event that is triggered when the header section is clicked. When the event is triggered, `$FZF_CLICK_HEADER_COLUMN` and `$FZF_CLICK_HEADER_LINE` are set.
|
||||||
|
```sh
|
||||||
|
fd --type f |
|
||||||
|
fzf --header $'[Files] [Directories]' --header-first \
|
||||||
|
--bind 'click-header:transform:
|
||||||
|
(( FZF_CLICK_HEADER_COLUMN <= 7 )) && echo "reload(fd --type f)"
|
||||||
|
(( FZF_CLICK_HEADER_COLUMN >= 9 )) && echo "reload(fd --type d)"
|
||||||
|
'
|
||||||
|
```
|
||||||
|
- Add `$FZF_COMPLETION_{DIR,PATH}_OPTS` for separately customizing the behavior of fuzzy completion
|
||||||
|
```sh
|
||||||
|
# Set --walker options without 'follow' not to follow symbolic links
|
||||||
|
FZF_COMPLETION_PATH_OPTS="--walker=file,dir,hidden"
|
||||||
|
FZF_COMPLETION_DIR_OPTS="--walker=dir,hidden"
|
||||||
|
```
|
||||||
|
- Fixed Windows argument escaping
|
||||||
|
- Bug fixes and improvements
|
||||||
|
- The code was heavily refactored to allow using fzf as a library in Go programs. The API is still experimental and subject to change.
|
||||||
|
- https://gist.github.com/junegunn/193990b65be48a38aac6ac49d5669170
|
||||||
|
|
||||||
0.51.0
|
0.51.0
|
||||||
------
|
------
|
||||||
- Added a new environment variable `$FZF_POS` exported to the child processes. It's the vertical position of the cursor in the list starting from 1.
|
- Added a new environment variable `$FZF_POS` exported to the child processes. It's the vertical position of the cursor in the list starting from 1.
|
||||||
|
|||||||
5
Makefile
5
Makefile
@@ -1,10 +1,10 @@
|
|||||||
SHELL := bash
|
SHELL := bash
|
||||||
GO ?= go
|
GO ?= go
|
||||||
GOOS ?= $(word 1, $(subst /, " ", $(word 4, $(shell go version))))
|
GOOS ?= $(shell $(GO) env GOOS)
|
||||||
|
|
||||||
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
|
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
|
||||||
ROOT_DIR := $(shell dirname $(MAKEFILE))
|
ROOT_DIR := $(shell dirname $(MAKEFILE))
|
||||||
SOURCES := $(wildcard *.go src/*.go src/*/*.go shell/*sh) $(MAKEFILE)
|
SOURCES := $(wildcard *.go src/*.go src/*/*.go shell/*sh man/man1/*.1) $(MAKEFILE)
|
||||||
|
|
||||||
ifdef FZF_VERSION
|
ifdef FZF_VERSION
|
||||||
VERSION := $(FZF_VERSION)
|
VERSION := $(FZF_VERSION)
|
||||||
@@ -79,7 +79,6 @@ all: target/$(BINARY)
|
|||||||
test: $(SOURCES)
|
test: $(SOURCES)
|
||||||
[ -z "$$(gofmt -s -d src)" ] || (gofmt -s -d src; exit 1)
|
[ -z "$$(gofmt -s -d src)" ] || (gofmt -s -d src; exit 1)
|
||||||
SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \
|
SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \
|
||||||
github.com/junegunn/fzf \
|
|
||||||
github.com/junegunn/fzf/src \
|
github.com/junegunn/fzf/src \
|
||||||
github.com/junegunn/fzf/src/algo \
|
github.com/junegunn/fzf/src/algo \
|
||||||
github.com/junegunn/fzf/src/tui \
|
github.com/junegunn/fzf/src/tui \
|
||||||
|
|||||||
@@ -294,7 +294,7 @@ The following table summarizes the available options.
|
|||||||
| `options` | string/list | Options to fzf |
|
| `options` | string/list | Options to fzf |
|
||||||
| `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) `--tmux` options (e.g. `90%,70%`) |
|
||||||
| `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}`) |
|
||||||
|
|
||||||
@@ -457,12 +457,13 @@ let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
|||||||
```
|
```
|
||||||
|
|
||||||
Alternatively, you can make fzf open in a tmux popup window (requires tmux 3.2
|
Alternatively, you can make fzf open in a tmux popup window (requires tmux 3.2
|
||||||
or above) by putting fzf-tmux options in `tmux` key.
|
or above) by putting `--tmux` option value in `tmux` key.
|
||||||
|
|
||||||
```vim
|
```vim
|
||||||
" See `man fzf-tmux` for available options
|
" See `--tmux` option in `man fzf` for available options
|
||||||
|
" [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]
|
||||||
if exists('$TMUX')
|
if exists('$TMUX')
|
||||||
let g:fzf_layout = { 'tmux': '-p90%,60%' }
|
let g:fzf_layout = { 'tmux': '90%,70%' }
|
||||||
else
|
else
|
||||||
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
||||||
endif
|
endif
|
||||||
|
|||||||
@@ -132,8 +132,10 @@ if [[ -z "$TMUX" ]]; then
|
|||||||
exit $?
|
exit $?
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --height option is not allowed. CTRL-Z is also disabled.
|
# * --height option is not allowed
|
||||||
args=("${args[@]}" "--no-height" "--bind=ctrl-z:ignore")
|
# * CTRL-Z is also disabled
|
||||||
|
# * fzf-tmux script is not compatible with --tmux option in fzf 0.53.0 or later
|
||||||
|
args=("${args[@]}" "--no-height" "--bind=ctrl-z:ignore" "--no-tmux")
|
||||||
|
|
||||||
# Handle zoomed tmux pane without popup options by moving it to a temp window
|
# Handle zoomed tmux pane without popup options by moving it to a temp window
|
||||||
if [[ ! "$opt" =~ "-E" ]] && tmux list-panes -F '#F' | grep -q Z; then
|
if [[ ! "$opt" =~ "-E" ]] && tmux list-panes -F '#F' | grep -q Z; then
|
||||||
|
|||||||
@@ -311,7 +311,7 @@ The following table summarizes the available options.
|
|||||||
`options` | string/list | Options to fzf
|
`options` | string/list | Options to fzf
|
||||||
`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) `--tmux` options (e.g. `90%,70%` )
|
||||||
`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}` )
|
||||||
---------------------------+---------------+----------------------------------------------------------------------
|
---------------------------+---------------+----------------------------------------------------------------------
|
||||||
@@ -469,11 +469,12 @@ in Neovim.
|
|||||||
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
||||||
<
|
<
|
||||||
Alternatively, you can make fzf open in a tmux popup window (requires tmux 3.2
|
Alternatively, you can make fzf open in a tmux popup window (requires tmux 3.2
|
||||||
or above) by putting fzf-tmux options in `tmux` key.
|
or above) by putting `--tmux` options in `tmux` key.
|
||||||
>
|
>
|
||||||
" See `man fzf-tmux` for available options
|
" See `--tmux` option in `man fzf` for available options
|
||||||
|
" [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]
|
||||||
if exists('$TMUX')
|
if exists('$TMUX')
|
||||||
let g:fzf_layout = { 'tmux': '-p90%,60%' }
|
let g:fzf_layout = { 'tmux': '90%,70%' }
|
||||||
else
|
else
|
||||||
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6 } }
|
||||||
endif
|
endif
|
||||||
|
|||||||
4
go.mod
4
go.mod
@@ -6,8 +6,8 @@ require (
|
|||||||
github.com/mattn/go-isatty v0.0.20
|
github.com/mattn/go-isatty v0.0.20
|
||||||
github.com/mattn/go-shellwords v1.0.12
|
github.com/mattn/go-shellwords v1.0.12
|
||||||
github.com/rivo/uniseg v0.4.7
|
github.com/rivo/uniseg v0.4.7
|
||||||
golang.org/x/sys v0.19.0
|
golang.org/x/sys v0.20.0
|
||||||
golang.org/x/term v0.19.0
|
golang.org/x/term v0.20.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
8
go.sum
8
go.sum
@@ -36,14 +36,14 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.20.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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||||
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=
|
||||||
|
|||||||
12
install
12
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.51.0
|
version=0.53.0
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
@@ -115,7 +115,7 @@ link_fzf_in_path() {
|
|||||||
try_curl() {
|
try_curl() {
|
||||||
command -v curl > /dev/null &&
|
command -v curl > /dev/null &&
|
||||||
if [[ $1 =~ tar.gz$ ]]; then
|
if [[ $1 =~ tar.gz$ ]]; then
|
||||||
curl -fL $1 | tar -xzf -
|
curl -fL $1 | tar --no-same-owner -xzf -
|
||||||
else
|
else
|
||||||
local temp=${TMPDIR:-/tmp}/fzf.zip
|
local temp=${TMPDIR:-/tmp}/fzf.zip
|
||||||
curl -fLo "$temp" $1 && unzip -o "$temp" && rm -f "$temp"
|
curl -fLo "$temp" $1 && unzip -o "$temp" && rm -f "$temp"
|
||||||
@@ -125,7 +125,7 @@ try_curl() {
|
|||||||
try_wget() {
|
try_wget() {
|
||||||
command -v wget > /dev/null &&
|
command -v wget > /dev/null &&
|
||||||
if [[ $1 =~ tar.gz$ ]]; then
|
if [[ $1 =~ tar.gz$ ]]; then
|
||||||
wget -O - $1 | tar -xzf -
|
wget -O - $1 | tar --no-same-owner -xzf -
|
||||||
else
|
else
|
||||||
local temp=${TMPDIR:-/tmp}/fzf.zip
|
local temp=${TMPDIR:-/tmp}/fzf.zip
|
||||||
wget -O "$temp" $1 && unzip -o "$temp" && rm -f "$temp"
|
wget -O "$temp" $1 && unzip -o "$temp" && rm -f "$temp"
|
||||||
@@ -265,7 +265,11 @@ fi
|
|||||||
EOF
|
EOF
|
||||||
|
|
||||||
if [[ $auto_completion -eq 1 ]] && [[ $key_bindings -eq 1 ]]; then
|
if [[ $auto_completion -eq 1 ]] && [[ $key_bindings -eq 1 ]]; then
|
||||||
echo "eval \"\$(fzf --$shell)\"" >> "$src"
|
if [[ "$shell" = zsh ]]; then
|
||||||
|
echo "source <(fzf --$shell)" >> "$src"
|
||||||
|
else
|
||||||
|
echo "eval \"\$(fzf --$shell)\"" >> "$src"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
cat >> "$src" << EOF
|
cat >> "$src" << EOF
|
||||||
# Auto-completion
|
# Auto-completion
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
$version="0.51.0"
|
$version="0.53.0"
|
||||||
|
|
||||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
|
||||||
|
|||||||
54
main.go
54
main.go
@@ -3,14 +3,16 @@ package main
|
|||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
fzf "github.com/junegunn/fzf/src"
|
fzf "github.com/junegunn/fzf/src"
|
||||||
"github.com/junegunn/fzf/src/protector"
|
"github.com/junegunn/fzf/src/protector"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "0.51"
|
var version = "0.53"
|
||||||
var revision string = "devel"
|
var revision = "devel"
|
||||||
|
|
||||||
//go:embed shell/key-bindings.bash
|
//go:embed shell/key-bindings.bash
|
||||||
var bashKeyBindings []byte
|
var bashKeyBindings []byte
|
||||||
@@ -27,15 +29,30 @@ var zshCompletion []byte
|
|||||||
//go:embed shell/key-bindings.fish
|
//go:embed shell/key-bindings.fish
|
||||||
var fishKeyBindings []byte
|
var fishKeyBindings []byte
|
||||||
|
|
||||||
|
//go:embed man/man1/fzf.1
|
||||||
|
var manPage []byte
|
||||||
|
|
||||||
func printScript(label string, content []byte) {
|
func printScript(label string, content []byte) {
|
||||||
fmt.Println("### " + label + " ###")
|
fmt.Println("### " + label + " ###")
|
||||||
fmt.Println(strings.TrimSpace(string(content)))
|
fmt.Println(strings.TrimSpace(string(content)))
|
||||||
fmt.Println("### end: " + label + " ###")
|
fmt.Println("### end: " + label + " ###")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func exit(code int, err error) {
|
||||||
|
if code == fzf.ExitError {
|
||||||
|
fmt.Fprintln(os.Stderr, err.Error())
|
||||||
|
}
|
||||||
|
os.Exit(code)
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
protector.Protect()
|
protector.Protect()
|
||||||
options := fzf.ParseOptions()
|
|
||||||
|
options, err := fzf.ParseOptions(true, os.Args[1:])
|
||||||
|
if err != nil {
|
||||||
|
exit(fzf.ExitError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
if options.Bash {
|
if options.Bash {
|
||||||
printScript("key-bindings.bash", bashKeyBindings)
|
printScript("key-bindings.bash", bashKeyBindings)
|
||||||
printScript("completion.bash", bashCompletion)
|
printScript("completion.bash", bashCompletion)
|
||||||
@@ -51,5 +68,34 @@ func main() {
|
|||||||
fmt.Println("fzf_key_bindings")
|
fmt.Println("fzf_key_bindings")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fzf.Run(options, version, revision)
|
if options.Help {
|
||||||
|
fmt.Print(fzf.Usage)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if options.Version {
|
||||||
|
if len(revision) > 0 {
|
||||||
|
fmt.Printf("%s (%s)\n", version, revision)
|
||||||
|
} else {
|
||||||
|
fmt.Println(version)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if options.Man {
|
||||||
|
file := fzf.WriteTemporaryFile([]string{string(manPage)}, "\n")
|
||||||
|
if len(file) == 0 {
|
||||||
|
fmt.Print(string(manPage))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer os.Remove(file)
|
||||||
|
cmd := exec.Command("man", file)
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
fmt.Print(string(manPage))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
code, err := fzf.Run(options)
|
||||||
|
exit(code, err)
|
||||||
}
|
}
|
||||||
|
|||||||
174
main_test.go
174
main_test.go
@@ -1,174 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
"go/build"
|
|
||||||
"go/importer"
|
|
||||||
"go/parser"
|
|
||||||
"go/token"
|
|
||||||
"go/types"
|
|
||||||
"io/fs"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func loadPackages(t *testing.T) []*build.Package {
|
|
||||||
// If GOROOT is not set, use `go env GOROOT` to determine it since it
|
|
||||||
// performs more work than just runtime.GOROOT(). For context, running
|
|
||||||
// the tests with the "-trimpath" flag causes GOROOT to not be set.
|
|
||||||
ctxt := &build.Default
|
|
||||||
if ctxt.GOROOT == "" {
|
|
||||||
cmd := exec.Command("go", "env", "GOROOT")
|
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
out = bytes.TrimSpace(out)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error running command: %q: %v\n%s", cmd.Args, err, out)
|
|
||||||
}
|
|
||||||
ctxt.GOROOT = string(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
wd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var pkgs []*build.Package
|
|
||||||
seen := make(map[string]bool)
|
|
||||||
err = filepath.WalkDir(wd, func(path string, d fs.DirEntry, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
name := d.Name()
|
|
||||||
if d.IsDir() {
|
|
||||||
if name == "" || name[0] == '.' || name[0] == '_' || name == "vendor" || name == "tmp" {
|
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if d.Type().IsRegular() && filepath.Ext(name) == ".go" && !strings.HasSuffix(name, "_test.go") {
|
|
||||||
dir := filepath.Dir(path)
|
|
||||||
if !seen[dir] {
|
|
||||||
pkg, err := ctxt.ImportDir(dir, build.ImportComment)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%s: %s", dir, err)
|
|
||||||
}
|
|
||||||
if pkg.ImportPath == "" || pkg.ImportPath == "." {
|
|
||||||
importPath, err := filepath.Rel(wd, dir)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
pkg.ImportPath = filepath.ToSlash(filepath.Join("github.com/junegunn/fzf", importPath))
|
|
||||||
}
|
|
||||||
|
|
||||||
pkgs = append(pkgs, pkg)
|
|
||||||
seen[dir] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(pkgs, func(i, j int) bool {
|
|
||||||
return pkgs[i].ImportPath < pkgs[j].ImportPath
|
|
||||||
})
|
|
||||||
return pkgs
|
|
||||||
}
|
|
||||||
|
|
||||||
var sourceImporter = importer.ForCompiler(token.NewFileSet(), "source", nil)
|
|
||||||
|
|
||||||
func checkPackageForOsExit(t *testing.T, bpkg *build.Package, allowed map[string]int) (errOsExit bool) {
|
|
||||||
var files []*ast.File
|
|
||||||
fset := token.NewFileSet()
|
|
||||||
for _, name := range bpkg.GoFiles {
|
|
||||||
filename := filepath.Join(bpkg.Dir, name)
|
|
||||||
af, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
files = append(files, af)
|
|
||||||
}
|
|
||||||
|
|
||||||
info := types.Info{
|
|
||||||
Uses: make(map[*ast.Ident]types.Object),
|
|
||||||
}
|
|
||||||
conf := types.Config{
|
|
||||||
Importer: sourceImporter,
|
|
||||||
}
|
|
||||||
_, err := conf.Check(bpkg.Name, fset, files, &info)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
wd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for id, obj := range info.Uses {
|
|
||||||
if obj.Pkg() != nil && obj.Pkg().Name() == "os" && obj.Name() == "Exit" {
|
|
||||||
pos := fset.Position(id.Pos())
|
|
||||||
|
|
||||||
name, err := filepath.Rel(wd, pos.Filename)
|
|
||||||
if err != nil {
|
|
||||||
t.Log(err)
|
|
||||||
name = pos.Filename
|
|
||||||
}
|
|
||||||
name = filepath.ToSlash(name)
|
|
||||||
|
|
||||||
// Check if the usage is allowed
|
|
||||||
if allowed[name] > 0 {
|
|
||||||
allowed[name]--
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Errorf("os.Exit referenced at: %s:%d:%d", name, pos.Line, pos.Column)
|
|
||||||
errOsExit = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errOsExit
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enforce that src/util.Exit() is used instead of os.Exit by prohibiting
|
|
||||||
// references to it anywhere else in the fzf code base.
|
|
||||||
func TestOSExitNotAllowed(t *testing.T) {
|
|
||||||
if testing.Short() {
|
|
||||||
t.Skip("skipping: short test")
|
|
||||||
}
|
|
||||||
allowed := map[string]int{
|
|
||||||
"src/util/atexit.go": 1, // os.Exit allowed 1 time in "atexit.go"
|
|
||||||
}
|
|
||||||
var errOsExit bool
|
|
||||||
for _, pkg := range loadPackages(t) {
|
|
||||||
t.Run(pkg.ImportPath, func(t *testing.T) {
|
|
||||||
if checkPackageForOsExit(t, pkg, allowed) {
|
|
||||||
errOsExit = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if t.Failed() && errOsExit {
|
|
||||||
var names []string
|
|
||||||
for name := range allowed {
|
|
||||||
names = append(names, fmt.Sprintf("%q", name))
|
|
||||||
}
|
|
||||||
sort.Strings(names)
|
|
||||||
|
|
||||||
const errMsg = `
|
|
||||||
Test failed because os.Exit was referenced outside of the following files:
|
|
||||||
|
|
||||||
%s
|
|
||||||
|
|
||||||
Use github.com/junegunn/fzf/src/util.Exit() instead to exit the program.
|
|
||||||
This is enforced because calling os.Exit() prevents the functions
|
|
||||||
registered with util.AtExit() from running.`
|
|
||||||
|
|
||||||
t.Errorf(errMsg, strings.Join(names, "\n "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf-tmux 1 "May 2024" "fzf 0.51.0" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf-tmux 1 "Jun 2024" "fzf 0.53.0" "fzf-tmux - open fzf in tmux split pane"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf-tmux - open fzf in tmux split pane
|
fzf-tmux - open fzf in tmux split pane
|
||||||
|
|||||||
147
man/man1/fzf.1
147
man/man1/fzf.1
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf 1 "May 2024" "fzf 0.51.0" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Jun 2024" "fzf 0.53.0" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -30,7 +30,10 @@ fzf - a command-line fuzzy finder
|
|||||||
fzf [options]
|
fzf [options]
|
||||||
|
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
fzf is a general-purpose command-line fuzzy finder.
|
fzf is an interactive filter program for any kind of list.
|
||||||
|
|
||||||
|
It implements a "fuzzy" matching algorithm, so you can quickly type in patterns
|
||||||
|
with omitted characters and still get the results you want.
|
||||||
|
|
||||||
.SH OPTIONS
|
.SH OPTIONS
|
||||||
.SS Note
|
.SS Note
|
||||||
@@ -46,10 +49,10 @@ it with \fB+x\fR or \fB--no-extended\fR.
|
|||||||
.B "-e, --exact"
|
.B "-e, --exact"
|
||||||
Enable exact-match
|
Enable exact-match
|
||||||
.TP
|
.TP
|
||||||
.B "-i"
|
.B "-i, --ignore-case"
|
||||||
Case-insensitive match (default: smart-case match)
|
Case-insensitive match (default: smart-case match)
|
||||||
.TP
|
.TP
|
||||||
.B "+i"
|
.B "+i, --no-ignore-case"
|
||||||
Case-sensitive match
|
Case-sensitive match
|
||||||
.TP
|
.TP
|
||||||
.B "--literal"
|
.B "--literal"
|
||||||
@@ -58,14 +61,32 @@ Do not normalize latin script letters for matching.
|
|||||||
.BI "--scheme=" SCHEME
|
.BI "--scheme=" SCHEME
|
||||||
Choose scoring scheme tailored for different types of input.
|
Choose scoring scheme tailored for different types of input.
|
||||||
|
|
||||||
.br
|
.RS
|
||||||
.BR default " Generic scoring scheme designed to work well with any type of input"
|
.B default
|
||||||
.br
|
.RS
|
||||||
.BR path " Scoring scheme well suited for file paths
|
Generic scoring scheme designed to work well with any type of input.
|
||||||
.br
|
.RE
|
||||||
.BR history " Scoring scheme well suited for command history or any input where chronological ordering is important
|
.RE
|
||||||
Sets \fB--tiebreak=index\fR as well.
|
|
||||||
.br
|
.RS
|
||||||
|
.B path
|
||||||
|
.RS
|
||||||
|
Additional bonus point is only given to the characters after path separator.
|
||||||
|
You might want to choose this scheme over \fBdefault\fR if you have many files
|
||||||
|
with spaces in their paths.
|
||||||
|
.RE
|
||||||
|
.RE
|
||||||
|
|
||||||
|
.RS
|
||||||
|
.B history
|
||||||
|
.RS
|
||||||
|
Scoring scheme well suited for command history or any input where chronological
|
||||||
|
ordering is important. No additional bonus points are given so that we give
|
||||||
|
more weight to the chronological ordering. This also sets
|
||||||
|
\fB--tiebreak=index\fR.
|
||||||
|
.RE
|
||||||
|
.RE
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "--algo=" TYPE
|
.BI "--algo=" TYPE
|
||||||
Fuzzy matching algorithm (default: v2)
|
Fuzzy matching algorithm (default: v2)
|
||||||
@@ -85,7 +106,8 @@ See \fBFIELD INDEX EXPRESSION\fR for the details.
|
|||||||
Transform the presentation of each line using field index expressions
|
Transform the presentation of each line using field index expressions
|
||||||
.TP
|
.TP
|
||||||
.BI "-d, --delimiter=" "STR"
|
.BI "-d, --delimiter=" "STR"
|
||||||
Field delimiter regex for \fB--nth\fR and \fB--with-nth\fR (default: AWK-style)
|
Field delimiter regex for \fB--nth\fR, \fB--with-nth\fR, and field index
|
||||||
|
expressions (default: AWK-style)
|
||||||
.TP
|
.TP
|
||||||
.BI "--disabled"
|
.BI "--disabled"
|
||||||
Do not perform search. With this option, fzf becomes a simple selector
|
Do not perform search. With this option, fzf becomes a simple selector
|
||||||
@@ -96,6 +118,16 @@ interface rather than a "fuzzy finder". You can later enable the search using
|
|||||||
.B "+s, --no-sort"
|
.B "+s, --no-sort"
|
||||||
Do not sort the result
|
Do not sort the result
|
||||||
.TP
|
.TP
|
||||||
|
.B "--tail=NUM"
|
||||||
|
Maximum number of items to keep in memory. This is useful when you want to
|
||||||
|
browse an endless stream of data (e.g. log stream) with fzf while limiting
|
||||||
|
memory usage.
|
||||||
|
.RS
|
||||||
|
e.g.
|
||||||
|
\fB# Interactive filtering of a log stream
|
||||||
|
tail -f *.log | fzf --tail 100000 --tac --no-sort --exact\fR
|
||||||
|
.RE
|
||||||
|
.TP
|
||||||
.B "--track"
|
.B "--track"
|
||||||
Make fzf track the current selection when the result list is updated.
|
Make fzf track the current selection when the result list is updated.
|
||||||
This can be useful when browsing logs using fzf with sorting disabled. It is
|
This can be useful when browsing logs using fzf with sorting disabled. It is
|
||||||
@@ -162,13 +194,16 @@ the details.
|
|||||||
.B "--cycle"
|
.B "--cycle"
|
||||||
Enable cyclic scroll
|
Enable cyclic scroll
|
||||||
.TP
|
.TP
|
||||||
|
.B "--no-multi-line"
|
||||||
|
Disable multi-line display of items when using \fB--read0\fR
|
||||||
|
.TP
|
||||||
.B "--keep-right"
|
.B "--keep-right"
|
||||||
Keep the right end of the line visible when it's too long. Effective only when
|
Keep the right end of the line visible when it's too long. Effective only when
|
||||||
the query string is empty.
|
the query string is empty.
|
||||||
.TP
|
.TP
|
||||||
.BI "--scroll-off=" "LINES"
|
.BI "--scroll-off=" "LINES"
|
||||||
Number of screen lines to keep above or below when scrolling to the top or to
|
Number of screen lines to keep above or below when scrolling to the top or to
|
||||||
the bottom (default: 0).
|
the bottom (default: 3).
|
||||||
.TP
|
.TP
|
||||||
.B "--no-hscroll"
|
.B "--no-hscroll"
|
||||||
Disable horizontal scroll
|
Disable horizontal scroll
|
||||||
@@ -204,17 +239,41 @@ height minus the given value.
|
|||||||
fzf --height=-1
|
fzf --height=-1
|
||||||
|
|
||||||
When prefixed with \fB~\fR, fzf will automatically determine the height in the
|
When prefixed with \fB~\fR, fzf will automatically determine the height in the
|
||||||
range according to the input size. Note that adaptive height is not compatible
|
range according to the input size.
|
||||||
with top/bottom margin and padding given in percent size. It is also not
|
|
||||||
compatible with a negative height value.
|
|
||||||
|
|
||||||
# Will not take up 100% of the screen
|
# Will not take up 100% of the screen
|
||||||
seq 5 | fzf --height=~100%
|
seq 5 | fzf --height=~100%
|
||||||
|
|
||||||
|
Adaptive height has the following limitations:
|
||||||
|
.br
|
||||||
|
* Cannot be used with top/bottom margin and padding given in percent size
|
||||||
|
.br
|
||||||
|
* Negative value is not allowed
|
||||||
|
.br
|
||||||
|
* It will not find the right size when there are multi-line items
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "--min-height=" "HEIGHT"
|
.BI "--min-height=" "HEIGHT"
|
||||||
Minimum height when \fB--height\fR is given in percent (default: 10).
|
Minimum height when \fB--height\fR is given in percent (default: 10).
|
||||||
Ignored when \fB--height\fR is not specified.
|
Ignored when \fB--height\fR is not specified.
|
||||||
|
.TP
|
||||||
|
.BI "--tmux" "[=[center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]]"
|
||||||
|
Start fzf in a tmux popup (default \fBcenter,50%\fR). Requires tmux 3.3 or
|
||||||
|
later. This option is ignored if you are not running fzf inside tmux.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fB# Popup in the center with 70% width and height
|
||||||
|
fzf --tmux 70%
|
||||||
|
|
||||||
|
# Popup on the left with 40% width and 100% height
|
||||||
|
fzf --tmux right,40%
|
||||||
|
|
||||||
|
# Popup on the bottom with 100% width and 30% height
|
||||||
|
fzf --tmux bottom,30%
|
||||||
|
|
||||||
|
# Popup on the top with 80% width and 40% height
|
||||||
|
fzf --tmux top,80%,40%\fR
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "--layout=" "LAYOUT"
|
.BI "--layout=" "LAYOUT"
|
||||||
Choose the layout (default: default)
|
Choose the layout (default: default)
|
||||||
@@ -420,10 +479,14 @@ Do not display scrollbar. A synonym for \fB--scrollbar=''\fB
|
|||||||
Input prompt (default: '> ')
|
Input prompt (default: '> ')
|
||||||
.TP
|
.TP
|
||||||
.BI "--pointer=" "STR"
|
.BI "--pointer=" "STR"
|
||||||
Pointer to the current line (default: '>')
|
Pointer to the current line (default: '▌' or '>' depending on \fB--no-unicode\fR)
|
||||||
.TP
|
.TP
|
||||||
.BI "--marker=" "STR"
|
.BI "--marker=" "STR"
|
||||||
Multi-select marker (default: '>')
|
Multi-select marker (default: '┃' or '>' depending on \fB--no-unicode\fR)
|
||||||
|
.TP
|
||||||
|
.BI "--marker-multi-line=" "STR"
|
||||||
|
Multi-select marker for multi-line entries. 3 elements for top, middle, and bottom.
|
||||||
|
(default: '╻┃╹' or '.|'' depending on \fB--no-unicode\fR)
|
||||||
.TP
|
.TP
|
||||||
.BI "--header=" "STR"
|
.BI "--header=" "STR"
|
||||||
The given string will be printed as the sticky header. The lines are displayed
|
The given string will be printed as the sticky header. The lines are displayed
|
||||||
@@ -455,7 +518,7 @@ color mappings.
|
|||||||
|
|
||||||
.RS
|
.RS
|
||||||
.B BASE SCHEME:
|
.B BASE SCHEME:
|
||||||
(default: dark on 256-color terminal, otherwise 16)
|
(default: \fBdark\fR on 256-color terminal, otherwise \fB16\fR; If \fBNO_COLOR\fR is set, \fBbw\fR)
|
||||||
|
|
||||||
\fBdark \fRColor scheme for dark 256-color terminal
|
\fBdark \fRColor scheme for dark 256-color terminal
|
||||||
\fBlight \fRColor scheme for light 256-color terminal
|
\fBlight \fRColor scheme for light 256-color terminal
|
||||||
@@ -464,14 +527,17 @@ color mappings.
|
|||||||
|
|
||||||
.B COLOR NAMES:
|
.B COLOR NAMES:
|
||||||
\fBfg \fRText
|
\fBfg \fRText
|
||||||
|
\fBselected-fg \fRSelected line text
|
||||||
\fBpreview-fg \fRPreview window text
|
\fBpreview-fg \fRPreview window text
|
||||||
\fBbg \fRBackground
|
\fBbg \fRBackground
|
||||||
|
\fBselected-bg \fRSelected line background
|
||||||
\fBpreview-bg \fRPreview window background
|
\fBpreview-bg \fRPreview window background
|
||||||
\fBhl \fRHighlighted substrings
|
\fBhl \fRHighlighted substrings
|
||||||
\fBfg+ \fRText (current line)
|
\fBselected-hl \fRHighlighted substrings in the selected line
|
||||||
\fBbg+ \fRBackground (current line)
|
\fBcurrent-fg (fg+) \fRText (current line)
|
||||||
|
\fBcurrent-bg (bg+) \fRBackground (current line)
|
||||||
\fBgutter \fRGutter on the left
|
\fBgutter \fRGutter on the left
|
||||||
\fBhl+ \fRHighlighted substrings (current line)
|
\fBcurrent-hl (hl+) \fRHighlighted substrings (current line)
|
||||||
\fBquery \fRQuery string
|
\fBquery \fRQuery string
|
||||||
\fBdisabled \fRQuery string when search is disabled (\fB--disabled\fR)
|
\fBdisabled \fRQuery string when search is disabled (\fB--disabled\fR)
|
||||||
\fBinfo \fRInfo line (match counters)
|
\fBinfo \fRInfo line (match counters)
|
||||||
@@ -534,6 +600,9 @@ color mappings.
|
|||||||
--color='pointer:#E12672,marker:#E17899,prompt:#98BEDE,hl+:#98BC99'\fR
|
--color='pointer:#E12672,marker:#E17899,prompt:#98BEDE,hl+:#98BC99'\fR
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
|
.B "--highlight-line"
|
||||||
|
Highlight the whole current line
|
||||||
|
.TP
|
||||||
.B "--no-bold"
|
.B "--no-bold"
|
||||||
Do not use bold text
|
Do not use bold text
|
||||||
.TP
|
.TP
|
||||||
@@ -788,6 +857,14 @@ list.
|
|||||||
e.g.
|
e.g.
|
||||||
\fBfzf --expect=ctrl-v,ctrl-t,alt-s --expect=f1,f2,~,@\fR
|
\fBfzf --expect=ctrl-v,ctrl-t,alt-s --expect=f1,f2,~,@\fR
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
|
This option is not compatible with \fB--bind\fR on the same key and will take
|
||||||
|
precedence over it. To combine the two, use \fBprint\fR action.
|
||||||
|
|
||||||
|
.RS
|
||||||
|
e.g.
|
||||||
|
\fBfzf --multi --bind 'enter:print()+accept,ctrl-y:select-all+print(ctrl-y)+accept'\fR
|
||||||
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.B "--read0"
|
.B "--read0"
|
||||||
Read input delimited by ASCII NUL characters instead of newline characters
|
Read input delimited by ASCII NUL characters instead of newline characters
|
||||||
@@ -821,7 +898,7 @@ e.g. \fBfzf --multi | fzf --sync\fR
|
|||||||
.B "--with-shell=STR"
|
.B "--with-shell=STR"
|
||||||
Shell command and flags to start child processes with. On *nix Systems, the
|
Shell command and flags to start child processes with. On *nix Systems, the
|
||||||
default value is \fB$SHELL -c\fR if \fB$SHELL\fR is set, otherwise \fBsh -c\fR.
|
default value is \fB$SHELL -c\fR if \fB$SHELL\fR is set, otherwise \fBsh -c\fR.
|
||||||
On Windows, the default value is \fBcmd /v:on/s/c\fR when \fB$SHELL\fR is not
|
On Windows, the default value is \fBcmd /s/c\fR when \fB$SHELL\fR is not
|
||||||
set.
|
set.
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
@@ -865,9 +942,16 @@ e.g.
|
|||||||
# 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
|
||||||
|
.SS Help
|
||||||
.TP
|
.TP
|
||||||
.B "--version"
|
.B "--version"
|
||||||
Display version information and exit
|
Display version information and exit
|
||||||
|
.TP
|
||||||
|
.B "--help"
|
||||||
|
Show help message
|
||||||
|
.TP
|
||||||
|
.B "--man"
|
||||||
|
Show man page
|
||||||
|
|
||||||
.SS Directory traversal
|
.SS Directory traversal
|
||||||
.TP
|
.TP
|
||||||
@@ -905,7 +989,7 @@ e.g. \fBeval "$(fzf --bash)"\fR
|
|||||||
.B "--zsh"
|
.B "--zsh"
|
||||||
Print script to set up Zsh shell integration
|
Print script to set up Zsh shell integration
|
||||||
|
|
||||||
e.g. \fBeval "$(fzf --zsh)"\fR
|
e.g. \fBsource <(fzf --zsh)\fR
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B "--fish"
|
.B "--fish"
|
||||||
@@ -942,6 +1026,8 @@ you need to protect against DNS rebinding and privilege escalation attacks.
|
|||||||
.br
|
.br
|
||||||
.BR 2 " Error"
|
.BR 2 " Error"
|
||||||
.br
|
.br
|
||||||
|
.BR 126 " Permission denied error from \fBbecome\fR action"
|
||||||
|
.br
|
||||||
.BR 127 " Invalid shell command for \fBbecome\fR action"
|
.BR 127 " Invalid shell command for \fBbecome\fR action"
|
||||||
.br
|
.br
|
||||||
.BR 130 " Interrupted with \fBCTRL-C\fR or \fBESC\fR"
|
.BR 130 " Interrupted with \fBCTRL-C\fR or \fBESC\fR"
|
||||||
@@ -1278,6 +1364,15 @@ e.g.
|
|||||||
\fBfzf --bind space:jump,jump:accept,jump-cancel:abort\fR
|
\fBfzf --bind space:jump,jump:accept,jump-cancel:abort\fR
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
|
\fIclick-header\fR
|
||||||
|
.RS
|
||||||
|
Triggered when a mouse click occurs within the header. Sets \fBFZF_CLICK_HEADER_LINE\fR and \fBFZF_CLICK_HEADER_COLUMN\fR environment variables starting from 1.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fBprintf "head1\\nhead2" | fzf --header-lines=2 --bind 'click-header:transform-prompt:printf ${FZF_CLICK_HEADER_LINE}x${FZF_CLICK_HEADER_COLUMN}'\fR
|
||||||
|
|
||||||
|
.RE
|
||||||
|
|
||||||
.SS AVAILABLE ACTIONS:
|
.SS AVAILABLE ACTIONS:
|
||||||
A key or an event can be bound to one or more of the following actions.
|
A key or an event can be bound to one or more of the following actions.
|
||||||
|
|
||||||
@@ -1347,7 +1442,7 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBpreview-half-page-up\fR
|
\fBpreview-half-page-up\fR
|
||||||
\fBpreview-bottom\fR
|
\fBpreview-bottom\fR
|
||||||
\fBpreview-top\fR
|
\fBpreview-top\fR
|
||||||
\fBprint-query\fR (print query and exit)
|
\fBprint(...)\fR (add string to the output queue and print on exit)
|
||||||
\fBput\fR (put the character to the prompt)
|
\fBput\fR (put the character to the prompt)
|
||||||
\fBput(...)\fR (put the given string to the prompt)
|
\fBput(...)\fR (put the given string to the prompt)
|
||||||
\fBrefresh-preview\fR
|
\fBrefresh-preview\fR
|
||||||
|
|||||||
@@ -59,12 +59,9 @@ if s:is_win
|
|||||||
return iconv(a:str, &encoding, 'cp'.s:codepage)
|
return iconv(a:str, &encoding, 'cp'.s:codepage)
|
||||||
endfunction
|
endfunction
|
||||||
function! s:wrap_cmds(cmds)
|
function! s:wrap_cmds(cmds)
|
||||||
return map([
|
return map(['@echo off']
|
||||||
\ '@echo off',
|
|
||||||
\ 'setlocal enabledelayedexpansion']
|
|
||||||
\ + (has('gui_running') ? ['set TERM= > nul'] : [])
|
\ + (has('gui_running') ? ['set TERM= > nul'] : [])
|
||||||
\ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds])
|
\ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds]),
|
||||||
\ + ['endlocal'],
|
|
||||||
\ '<SID>enc_to_cp(v:val."\r")')
|
\ '<SID>enc_to_cp(v:val."\r")')
|
||||||
endfunction
|
endfunction
|
||||||
else
|
else
|
||||||
@@ -83,8 +80,6 @@ else
|
|||||||
endfunction
|
endfunction
|
||||||
endif
|
endif
|
||||||
|
|
||||||
let s:cmd_control_chars = ['&', '|', '<', '>', '(', ')', '@', '^', '!']
|
|
||||||
|
|
||||||
function! s:shellesc_cmd(arg)
|
function! s:shellesc_cmd(arg)
|
||||||
let e = '"'
|
let e = '"'
|
||||||
let slashes = 0
|
let slashes = 0
|
||||||
@@ -94,17 +89,13 @@ function! s:shellesc_cmd(arg)
|
|||||||
elseif c ==# '"'
|
elseif c ==# '"'
|
||||||
let e .= repeat('\', slashes + 1)
|
let e .= repeat('\', slashes + 1)
|
||||||
let slashes = 0
|
let slashes = 0
|
||||||
elseif c ==# '%'
|
|
||||||
let e .= '%'
|
|
||||||
elseif index(s:cmd_control_chars, c) >= 0
|
|
||||||
let e .= '^'
|
|
||||||
else
|
else
|
||||||
let slashes = 0
|
let slashes = 0
|
||||||
endif
|
endif
|
||||||
let e .= c
|
let e .= c
|
||||||
endfor
|
endfor
|
||||||
let e .= repeat('\', slashes) .'"'
|
let e .= repeat('\', slashes) .'"'
|
||||||
return e
|
return substitute(substitute(e, '[&|<>()^!"]', '^&', 'g'), '%', '%%', 'g')
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! fzf#shellescape(arg, ...)
|
function! fzf#shellescape(arg, ...)
|
||||||
@@ -336,7 +327,10 @@ function! s:common_sink(action, lines) abort
|
|||||||
" the execution (e.g. `set autochdir` or `autocmd BufEnter * lcd ...`)
|
" the execution (e.g. `set autochdir` or `autocmd BufEnter * lcd ...`)
|
||||||
let cwd = exists('w:fzf_pushd') ? w:fzf_pushd.dir : expand('%:p:h')
|
let cwd = exists('w:fzf_pushd') ? w:fzf_pushd.dir : expand('%:p:h')
|
||||||
for item in a:lines
|
for item in a:lines
|
||||||
if item[0] != '~' && item !~ (s:is_win ? '^[A-Z]:\' : '^/')
|
if has('win32unix') && item !~ '/'
|
||||||
|
let item = substitute(item, '\', '/', 'g')
|
||||||
|
end
|
||||||
|
if item[0] != '~' && item !~ (s:is_win ? '^\([A-Z]:\)\?\' : '^/')
|
||||||
let sep = s:is_win ? '\' : '/'
|
let sep = s:is_win ? '\' : '/'
|
||||||
let item = join([cwd, item], cwd[len(cwd)-1] == sep ? '' : sep)
|
let item = join([cwd, item], cwd[len(cwd)-1] == sep ? '' : sep)
|
||||||
endif
|
endif
|
||||||
@@ -496,6 +490,8 @@ function! s:extract_option(opts, name)
|
|||||||
return opt
|
return opt
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
let s:need_cmd_window = has('win32unix') && $TERM_PROGRAM ==# 'mintty' && s:compare_versions($TERM_PROGRAM_VERSION, '3.4.5') < 0 && !executable('winpty')
|
||||||
|
|
||||||
function! fzf#run(...) abort
|
function! fzf#run(...) abort
|
||||||
try
|
try
|
||||||
let [shell, shellslash, shellcmdflag, shellxquote] = s:use_sh()
|
let [shell, shellslash, shellcmdflag, shellxquote] = s:use_sh()
|
||||||
@@ -517,19 +513,19 @@ try
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
if has_key(dict, 'source')
|
if has_key(dict, 'source')
|
||||||
let source = remove(dict, 'source')
|
let source = dict.source
|
||||||
let type = type(source)
|
let type = type(source)
|
||||||
if type == 1
|
if type == 1
|
||||||
let source_command = source
|
let prefix = '('.source.')|'
|
||||||
elseif type == 3
|
elseif type == 3
|
||||||
let temps.input = s:fzf_tempname()
|
let temps.input = s:fzf_tempname()
|
||||||
call s:writefile(source, temps.input)
|
call s:writefile(source, temps.input)
|
||||||
let source_command = (s:is_win ? 'type ' : 'cat ').fzf#shellescape(temps.input)
|
let prefix = (s:is_win ? 'type ' : 'command cat ').fzf#shellescape(temps.input).'|'
|
||||||
else
|
else
|
||||||
throw 'Invalid source type'
|
throw 'Invalid source type'
|
||||||
endif
|
endif
|
||||||
else
|
else
|
||||||
let source_command = ''
|
let prefix = ''
|
||||||
endif
|
endif
|
||||||
|
|
||||||
let prefer_tmux = get(g:, 'fzf_prefer_tmux', 0) || has_key(dict, 'tmux')
|
let prefer_tmux = get(g:, 'fzf_prefer_tmux', 0) || has_key(dict, 'tmux')
|
||||||
@@ -538,26 +534,23 @@ try
|
|||||||
\ executable('tput') && filereadable('/dev/tty')
|
\ executable('tput') && filereadable('/dev/tty')
|
||||||
let has_vim8_term = has('terminal') && has('patch-8.0.995')
|
let has_vim8_term = has('terminal') && has('patch-8.0.995')
|
||||||
let has_nvim_term = has('nvim-0.2.1') || has('nvim') && !s:is_win
|
let has_nvim_term = has('nvim-0.2.1') || has('nvim') && !s:is_win
|
||||||
let use_term = has_nvim_term ||
|
let use_term = has_nvim_term || has_vim8_term
|
||||||
\ has_vim8_term && !has('win32unix') && (has('gui_running') || s:is_win || s:present(dict, 'down', 'up', 'left', 'right', 'window'))
|
\ && !s:need_cmd_window
|
||||||
|
\ && (has('gui_running') || s:is_win || s:present(dict, 'down', 'up', 'left', 'right', 'window'))
|
||||||
let use_tmux = (has_key(dict, 'tmux') || (!use_height && !use_term || prefer_tmux) && !has('win32unix') && s:splittable(dict)) && s:tmux_enabled()
|
let use_tmux = (has_key(dict, 'tmux') || (!use_height && !use_term || prefer_tmux) && !has('win32unix') && s:splittable(dict)) && s:tmux_enabled()
|
||||||
if prefer_tmux && use_tmux
|
if prefer_tmux && use_tmux
|
||||||
let use_height = 0
|
let use_height = 0
|
||||||
let use_term = 0
|
let use_term = 0
|
||||||
endif
|
endif
|
||||||
if use_term
|
if use_term
|
||||||
let optstr .= ' --no-height'
|
let optstr .= ' --no-height --no-tmux'
|
||||||
elseif use_height
|
elseif use_height
|
||||||
let height = s:calc_size(&lines, dict.down, dict)
|
let height = s:calc_size(&lines, dict.down, dict)
|
||||||
let optstr .= ' --height='.height
|
let optstr .= ' --no-tmux --height='.height
|
||||||
endif
|
endif
|
||||||
" Respect --border option given in $FZF_DEFAULT_OPTS and 'options'
|
" Respect --border option given in $FZF_DEFAULT_OPTS and 'options'
|
||||||
let optstr = join([s:border_opt(get(dict, 'window', 0)), s:extract_option($FZF_DEFAULT_OPTS, 'border'), optstr])
|
let optstr = join([s:border_opt(get(dict, 'window', 0)), s:extract_option($FZF_DEFAULT_OPTS, 'border'), optstr])
|
||||||
let prev_default_command = $FZF_DEFAULT_COMMAND
|
let command = prefix.(use_tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
|
||||||
if len(source_command)
|
|
||||||
let $FZF_DEFAULT_COMMAND = source_command
|
|
||||||
endif
|
|
||||||
let command = (use_tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
|
|
||||||
|
|
||||||
if use_term
|
if use_term
|
||||||
return s:execute_term(dict, command, temps)
|
return s:execute_term(dict, command, temps)
|
||||||
@@ -568,14 +561,6 @@ try
|
|||||||
call s:callback(dict, lines)
|
call s:callback(dict, lines)
|
||||||
return lines
|
return lines
|
||||||
finally
|
finally
|
||||||
if exists('source_command') && len(source_command)
|
|
||||||
if len(prev_default_command)
|
|
||||||
let $FZF_DEFAULT_COMMAND = prev_default_command
|
|
||||||
else
|
|
||||||
let $FZF_DEFAULT_COMMAND = ''
|
|
||||||
silent! execute 'unlet $FZF_DEFAULT_COMMAND'
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
let [&shell, &shellslash, &shellcmdflag, &shellxquote] = [shell, shellslash, shellcmdflag, shellxquote]
|
let [&shell, &shellslash, &shellcmdflag, &shellxquote] = [shell, shellslash, shellcmdflag, shellxquote]
|
||||||
endtry
|
endtry
|
||||||
endfunction
|
endfunction
|
||||||
@@ -594,19 +579,21 @@ function! s:fzf_tmux(dict)
|
|||||||
if empty(size)
|
if empty(size)
|
||||||
for o in ['up', 'down', 'left', 'right']
|
for o in ['up', 'down', 'left', 'right']
|
||||||
if s:present(a:dict, o)
|
if s:present(a:dict, o)
|
||||||
let spec = a:dict[o]
|
let size = o . ',' . a:dict[o]
|
||||||
if (o == 'up' || o == 'down') && spec[0] == '~'
|
|
||||||
let size = '-'.o[0].s:calc_size(&lines, spec, a:dict)
|
|
||||||
else
|
|
||||||
" Legacy boolean option
|
|
||||||
let size = '-'.o[0].(spec == 1 ? '' : substitute(spec, '^\~', '', ''))
|
|
||||||
endif
|
|
||||||
break
|
break
|
||||||
endif
|
endif
|
||||||
endfor
|
endfor
|
||||||
endif
|
endif
|
||||||
return printf('LINES=%d COLUMNS=%d %s %s - --',
|
|
||||||
\ &lines, &columns, fzf#shellescape(s:fzf_tmux), size)
|
" Legacy fzf-tmux options
|
||||||
|
if size =~ '-'
|
||||||
|
return printf('LINES=%d COLUMNS=%d %s %s %s --',
|
||||||
|
\ &lines, &columns, fzf#shellescape(s:fzf_tmux), size, (has_key(a:dict, 'source') ? '' : '-'))
|
||||||
|
end
|
||||||
|
|
||||||
|
" Using native --tmux option
|
||||||
|
let in = (has_key(a:dict, 'source') ? '' : ' --force-tty-in')
|
||||||
|
return printf('%s --tmux %s%s', fzf#shellescape(fzf#exec()), size, in)
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:splittable(dict)
|
function! s:splittable(dict)
|
||||||
@@ -729,14 +716,15 @@ function! s:execute(dict, command, use_height, temps) abort
|
|||||||
call jobstart(cmd, fzf)
|
call jobstart(cmd, fzf)
|
||||||
return []
|
return []
|
||||||
endif
|
endif
|
||||||
elseif has('win32unix') && $TERM !=# 'cygwin'
|
elseif s:need_cmd_window
|
||||||
let shellscript = s:fzf_tempname()
|
let shellscript = s:fzf_tempname()
|
||||||
call s:writefile([command], shellscript)
|
call s:writefile([command], shellscript)
|
||||||
let command = 'cmd.exe /C '.fzf#shellescape('set "TERM=" & start /WAIT sh -c '.shellscript)
|
let command = 'start //WAIT sh -c '.shellscript
|
||||||
let a:temps.shellscript = shellscript
|
let a:temps.shellscript = shellscript
|
||||||
endif
|
endif
|
||||||
if a:use_height
|
if a:use_height
|
||||||
call system(printf('tput cup %d > /dev/tty; tput cnorm > /dev/tty; %s < /dev/tty 2> /dev/tty', &lines, command))
|
let stdin = has_key(a:dict, 'source') ? '' : '< /dev/tty'
|
||||||
|
call system(printf('tput cup %d > /dev/tty; tput cnorm > /dev/tty; %s %s 2> /dev/tty', &lines, command, stdin))
|
||||||
else
|
else
|
||||||
execute 'silent !'.command
|
execute 'silent !'.command
|
||||||
endif
|
endif
|
||||||
|
|||||||
@@ -4,10 +4,12 @@
|
|||||||
# / __/ / /_/ __/
|
# / __/ / /_/ __/
|
||||||
# /_/ /___/_/ completion.bash
|
# /_/ /___/_/ completion.bash
|
||||||
#
|
#
|
||||||
# - $FZF_TMUX (default: 0)
|
# - $FZF_TMUX (default: 0)
|
||||||
# - $FZF_TMUX_OPTS (default: empty)
|
# - $FZF_TMUX_OPTS (default: empty)
|
||||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||||
|
# - $FZF_COMPLETION_PATH_OPTS (default: empty)
|
||||||
|
# - $FZF_COMPLETION_DIR_OPTS (default: empty)
|
||||||
|
|
||||||
if [[ $- =~ i ]]; then
|
if [[ $- =~ i ]]; then
|
||||||
|
|
||||||
@@ -99,75 +101,83 @@ _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
|
|
||||||
-e --exact
|
|
||||||
+x --no-extended
|
|
||||||
-q --query
|
|
||||||
-f --filter
|
|
||||||
--literal
|
|
||||||
--scheme
|
|
||||||
--expect
|
|
||||||
--disabled
|
|
||||||
--tiebreak
|
|
||||||
--bind
|
|
||||||
--color
|
|
||||||
-d --delimiter
|
|
||||||
-n --nth
|
|
||||||
--with-nth
|
|
||||||
+s --no-sort
|
|
||||||
--track
|
|
||||||
--tac
|
|
||||||
-i
|
|
||||||
+i
|
|
||||||
-m --multi
|
|
||||||
--ansi
|
|
||||||
--no-mouse
|
|
||||||
+c --no-color
|
+c --no-color
|
||||||
--no-bold
|
+i --no-ignore-case
|
||||||
--layout
|
+s --no-sort
|
||||||
--reverse
|
+x --no-extended
|
||||||
--cycle
|
--ansi
|
||||||
--keep-right
|
--bash
|
||||||
--no-hscroll
|
--bind
|
||||||
--hscroll-off
|
|
||||||
--scroll-off
|
|
||||||
--filepath-word
|
|
||||||
--info
|
|
||||||
--separator
|
|
||||||
--no-separator
|
|
||||||
--no-scrollbar
|
|
||||||
--jump-labels
|
|
||||||
-1 --select-1
|
|
||||||
-0 --exit-0
|
|
||||||
--read0
|
|
||||||
--print0
|
|
||||||
--print-query
|
|
||||||
--prompt
|
|
||||||
--pointer
|
|
||||||
--marker
|
|
||||||
--sync
|
|
||||||
--history
|
|
||||||
--history-size
|
|
||||||
--header
|
|
||||||
--header-lines
|
|
||||||
--header-first
|
|
||||||
--ellipsis
|
|
||||||
--preview
|
|
||||||
--preview-window
|
|
||||||
--height
|
|
||||||
--min-height
|
|
||||||
--border
|
--border
|
||||||
--border-label
|
--border-label
|
||||||
--border-label-pos
|
--border-label-pos
|
||||||
|
--color
|
||||||
|
--cycle
|
||||||
|
--disabled
|
||||||
|
--ellipsis
|
||||||
|
--expect
|
||||||
|
--filepath-word
|
||||||
|
--fish
|
||||||
|
--header
|
||||||
|
--header-first
|
||||||
|
--header-lines
|
||||||
|
--height
|
||||||
|
--highlight-line
|
||||||
|
--history
|
||||||
|
--history-size
|
||||||
|
--hscroll-off
|
||||||
|
--info
|
||||||
|
--jump-labels
|
||||||
|
--keep-right
|
||||||
|
--layout
|
||||||
|
--listen
|
||||||
|
--listen-unsafe
|
||||||
|
--literal
|
||||||
|
--man
|
||||||
|
--margin
|
||||||
|
--marker
|
||||||
|
--min-height
|
||||||
|
--no-bold
|
||||||
|
--no-clear
|
||||||
|
--no-hscroll
|
||||||
|
--no-mouse
|
||||||
|
--no-scrollbar
|
||||||
|
--no-separator
|
||||||
|
--no-unicode
|
||||||
|
--padding
|
||||||
|
--pointer
|
||||||
|
--preview
|
||||||
--preview-label
|
--preview-label
|
||||||
--preview-label-pos
|
--preview-label-pos
|
||||||
--no-unicode
|
--preview-window
|
||||||
--margin
|
--print-query
|
||||||
--padding
|
--print0
|
||||||
|
--prompt
|
||||||
|
--read0
|
||||||
|
--reverse
|
||||||
|
--scheme
|
||||||
|
--scroll-off
|
||||||
|
--separator
|
||||||
|
--sync
|
||||||
--tabstop
|
--tabstop
|
||||||
--listen
|
--tac
|
||||||
--no-clear
|
--tiebreak
|
||||||
|
--tmux
|
||||||
|
--track
|
||||||
--version
|
--version
|
||||||
|
--with-nth
|
||||||
|
--with-shell
|
||||||
|
--zsh
|
||||||
|
-0 --exit-0
|
||||||
|
-1 --select-1
|
||||||
|
-d --delimiter
|
||||||
|
-e --exact
|
||||||
|
-f --filter
|
||||||
|
-h --help
|
||||||
|
-i --ignore-case
|
||||||
|
-m --multi
|
||||||
|
-n --nth
|
||||||
|
-q --query
|
||||||
--"
|
--"
|
||||||
|
|
||||||
case "${prev}" in
|
case "${prev}" in
|
||||||
@@ -297,8 +307,14 @@ __fzf_generic_path_completion() {
|
|||||||
if declare -F "$1" > /dev/null; then
|
if declare -F "$1" > /dev/null; then
|
||||||
eval "$1 $(printf %q "$dir")" | __fzf_comprun "$4" -q "$leftover"
|
eval "$1 $(printf %q "$dir")" | __fzf_comprun "$4" -q "$leftover"
|
||||||
else
|
else
|
||||||
[[ $1 =~ dir ]] && walker=dir,follow || walker=file,dir,follow,hidden
|
if [[ $1 =~ dir ]]; then
|
||||||
__fzf_comprun "$4" -q "$leftover" --walker "$walker" --walker-root="$dir"
|
walker=dir,follow
|
||||||
|
rest=${FZF_COMPLETION_DIR_OPTS-}
|
||||||
|
else
|
||||||
|
walker=file,dir,follow,hidden
|
||||||
|
rest=${FZF_COMPLETION_PATH_OPTS-}
|
||||||
|
fi
|
||||||
|
__fzf_comprun "$4" -q "$leftover" --walker "$walker" --walker-root="$dir" $rest
|
||||||
fi | while read -r item; do
|
fi | while read -r item; do
|
||||||
printf "%q " "${item%$3}$3"
|
printf "%q " "${item%$3}$3"
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -4,10 +4,12 @@
|
|||||||
# / __/ / /_/ __/
|
# / __/ / /_/ __/
|
||||||
# /_/ /___/_/ completion.zsh
|
# /_/ /___/_/ completion.zsh
|
||||||
#
|
#
|
||||||
# - $FZF_TMUX (default: 0)
|
# - $FZF_TMUX (default: 0)
|
||||||
# - $FZF_TMUX_OPTS (default: '-d 40%')
|
# - $FZF_TMUX_OPTS (default: empty)
|
||||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||||
|
# - $FZF_COMPLETION_PATH_OPTS (default: empty)
|
||||||
|
# - $FZF_COMPLETION_DIR_OPTS (default: empty)
|
||||||
|
|
||||||
|
|
||||||
# Both branches of the following `if` do the same thing -- define
|
# Both branches of the following `if` do the same thing -- define
|
||||||
@@ -160,8 +162,14 @@ __fzf_generic_path_completion() {
|
|||||||
if declare -f "$compgen" > /dev/null; then
|
if declare -f "$compgen" > /dev/null; then
|
||||||
eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover"
|
eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover"
|
||||||
else
|
else
|
||||||
[[ $compgen =~ dir ]] && walker=dir,follow || walker=file,dir,follow,hidden
|
if [[ $compgen =~ dir ]]; then
|
||||||
__fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" --walker "$walker" --walker-root="$dir" < /dev/tty
|
walker=dir,follow
|
||||||
|
rest=${FZF_COMPLETION_DIR_OPTS-}
|
||||||
|
else
|
||||||
|
walker=file,dir,follow,hidden
|
||||||
|
rest=${FZF_COMPLETION_PATH_OPTS-}
|
||||||
|
fi
|
||||||
|
__fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" --walker "$walker" --walker-root="$dir" ${(Q)${(Z+n+)rest}} < /dev/tty
|
||||||
fi | while read item; do
|
fi | while read item; do
|
||||||
item="${item%$suffix}$suffix"
|
item="${item%$suffix}$suffix"
|
||||||
echo -n "${(q)item} "
|
echo -n "${(q)item} "
|
||||||
|
|||||||
@@ -57,15 +57,15 @@ __fzf_cd__() {
|
|||||||
if command -v perl > /dev/null; then
|
if command -v perl > /dev/null; then
|
||||||
__fzf_history__() {
|
__fzf_history__() {
|
||||||
local output script
|
local output script
|
||||||
script='BEGIN { getc; $/ = "\n\t"; $HISTCOUNT = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCOUNT - $. . "\t$_" if !$seen{$_}++'
|
script='BEGIN { getc; $/ = "\n\t"; $HISTCOUNT = $ENV{last_hist} + 1 } s/^[ *]//; s/\n/\n\t/gm; print $HISTCOUNT - $. . "\t$_" if !$seen{$_}++'
|
||||||
output=$(
|
output=$(
|
||||||
set +o pipefail
|
set +o pipefail
|
||||||
builtin fc -lnr -2147483648 |
|
builtin fc -lnr -2147483648 |
|
||||||
last_hist=$(HISTTIMEFORMAT='' builtin history 1) command perl -n -l0 -e "$script" |
|
last_hist=$(HISTTIMEFORMAT='' builtin history 1) command perl -n -l0 -e "$script" |
|
||||||
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort ${FZF_CTRL_R_OPTS-} +m --read0") \
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --highlight-line ${FZF_CTRL_R_OPTS-} +m --read0") \
|
||||||
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) --query "$READLINE_LINE"
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) --query "$READLINE_LINE"
|
||||||
) || return
|
) || return
|
||||||
READLINE_LINE=${output#*$'\t'}
|
READLINE_LINE=$(command perl -pe 's/^\d*\t//' <<< "$output")
|
||||||
if [[ -z "$READLINE_POINT" ]]; then
|
if [[ -z "$READLINE_POINT" ]]; then
|
||||||
echo "$READLINE_LINE"
|
echo "$READLINE_LINE"
|
||||||
else
|
else
|
||||||
@@ -91,7 +91,7 @@ else # awk - fallback for POSIX systems
|
|||||||
set +o pipefail
|
set +o pipefail
|
||||||
builtin fc -lnr -2147483648 2> /dev/null | # ( $'\t '<lines>$'\n' )* ; <lines> ::= [^\n]* ( $'\n'<lines> )*
|
builtin fc -lnr -2147483648 2> /dev/null | # ( $'\t '<lines>$'\n' )* ; <lines> ::= [^\n]* ( $'\n'<lines> )*
|
||||||
command $__fzf_awk "$script" | # ( <counter>$'\t'<lines>$'\000' )*
|
command $__fzf_awk "$script" | # ( <counter>$'\t'<lines>$'\000' )*
|
||||||
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort ${FZF_CTRL_R_OPTS-} +m --read0") \
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --highlight-line ${FZF_CTRL_R_OPTS-} +m --read0") \
|
||||||
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) --query "$READLINE_LINE"
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) --query "$READLINE_LINE"
|
||||||
) || return
|
) || return
|
||||||
READLINE_LINE=${output#*$'\t'}
|
READLINE_LINE=${output#*$'\t'}
|
||||||
|
|||||||
@@ -59,9 +59,6 @@ function fzf_key_bindings
|
|||||||
function fzf-history-widget -d "Show command history"
|
function fzf-history-widget -d "Show command history"
|
||||||
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 (__fzf_defaults "" "--scheme=history --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m")
|
|
||||||
set -lx FZF_DEFAULT_OPTS_FILE ''
|
|
||||||
|
|
||||||
set -l FISH_MAJOR (echo $version | cut -f1 -d.)
|
set -l FISH_MAJOR (echo $version | cut -f1 -d.)
|
||||||
set -l FISH_MINOR (echo $version | cut -f2 -d.)
|
set -l FISH_MINOR (echo $version | cut -f2 -d.)
|
||||||
|
|
||||||
@@ -69,10 +66,19 @@ function fzf_key_bindings
|
|||||||
# history's -z flag was added in fish 2.4.0, so don't use it for versions
|
# history's -z flag was added in fish 2.4.0, so don't use it for versions
|
||||||
# before 2.4.0.
|
# before 2.4.0.
|
||||||
if [ "$FISH_MAJOR" -gt 2 -o \( "$FISH_MAJOR" -eq 2 -a "$FISH_MINOR" -ge 4 \) ];
|
if [ "$FISH_MAJOR" -gt 2 -o \( "$FISH_MAJOR" -eq 2 -a "$FISH_MINOR" -ge 4 \) ];
|
||||||
history -z | eval (__fzfcmd) --read0 --print0 -q '(commandline)' | read -lz result
|
if type -P perl > /dev/null 2>&1
|
||||||
and commandline -- $result
|
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --highlight-line $FZF_CTRL_R_OPTS +m")
|
||||||
|
set -lx FZF_DEFAULT_OPTS_FILE ''
|
||||||
|
builtin history -z --reverse | command perl -0 -pe 's/^/$.\t/g; s/\n/\n\t/gm' | eval (__fzfcmd) --tac --read0 --print0 -q '(commandline)' | command perl -pe 's/^\d*\t//' | read -lz result
|
||||||
|
and commandline -- $result
|
||||||
|
else
|
||||||
|
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "" "--scheme=history --bind=ctrl-r:toggle-sort --highlight-line $FZF_CTRL_R_OPTS +m")
|
||||||
|
set -lx FZF_DEFAULT_OPTS_FILE ''
|
||||||
|
builtin history -z | eval (__fzfcmd) --read0 --print0 -q '(commandline)' | read -lz result
|
||||||
|
and commandline -- $result
|
||||||
|
end
|
||||||
else
|
else
|
||||||
history | eval (__fzfcmd) -q '(commandline)' | read -l result
|
builtin history | eval (__fzfcmd) -q '(commandline)' | read -l result
|
||||||
and commandline -- $result
|
and commandline -- $result
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -93,7 +99,7 @@ function fzf_key_bindings
|
|||||||
eval (__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
eval (__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
||||||
|
|
||||||
if [ -n "$result" ]
|
if [ -n "$result" ]
|
||||||
cd -- $result
|
builtin cd -- $result
|
||||||
|
|
||||||
# Remove last token from commandline.
|
# Remove last token from commandline.
|
||||||
commandline -t ""
|
commandline -t ""
|
||||||
|
|||||||
@@ -108,14 +108,22 @@ fi
|
|||||||
fzf-history-widget() {
|
fzf-history-widget() {
|
||||||
local selected num
|
local selected num
|
||||||
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
|
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
|
||||||
selected="$(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
|
# Ensure the associative history array, which maps event numbers to the full
|
||||||
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m") \
|
# history lines, is loaded, and that Perl is installed for multi-line output.
|
||||||
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
|
if zmodload -F zsh/parameter p:history 2>/dev/null && (( ${#commands[perl]} )); then
|
||||||
|
selected="$(printf '%1$s\t%2$s\000' "${(vk)history[@]}" |
|
||||||
|
perl -0 -ne 'if (!$seen{(/^\s*[0-9]+\**\s+(.*)/, $1)}++) { s/\n/\n\t/gm; print; }' |
|
||||||
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m --read0") \
|
||||||
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
|
||||||
|
else
|
||||||
|
selected="$(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
|
||||||
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m") \
|
||||||
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
|
||||||
|
fi
|
||||||
local ret=$?
|
local ret=$?
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
num=$(awk '{print $1}' <<< "$selected")
|
if num=$(awk '{print $1; exit}' <<< "$selected" | grep -o '^[1-9][0-9]*'); then
|
||||||
if [[ "$num" =~ '^[1-9][0-9]*\*?$' ]]; then
|
zle vi-fetch-history -n $num
|
||||||
zle vi-fetch-history -n ${num%\*}
|
|
||||||
else # selected is a custom query, not from history
|
else # selected is a custom query, not from history
|
||||||
LBUFFER="$selected"
|
LBUFFER="$selected"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -37,92 +37,94 @@ func _() {
|
|||||||
_ = x[actDeleteChar-26]
|
_ = x[actDeleteChar-26]
|
||||||
_ = x[actDeleteCharEof-27]
|
_ = x[actDeleteCharEof-27]
|
||||||
_ = x[actEndOfLine-28]
|
_ = x[actEndOfLine-28]
|
||||||
_ = x[actForwardChar-29]
|
_ = x[actFatal-29]
|
||||||
_ = x[actForwardWord-30]
|
_ = x[actForwardChar-30]
|
||||||
_ = x[actKillLine-31]
|
_ = x[actForwardWord-31]
|
||||||
_ = x[actKillWord-32]
|
_ = x[actKillLine-32]
|
||||||
_ = x[actUnixLineDiscard-33]
|
_ = x[actKillWord-33]
|
||||||
_ = x[actUnixWordRubout-34]
|
_ = x[actUnixLineDiscard-34]
|
||||||
_ = x[actYank-35]
|
_ = x[actUnixWordRubout-35]
|
||||||
_ = x[actBackwardKillWord-36]
|
_ = x[actYank-36]
|
||||||
_ = x[actSelectAll-37]
|
_ = x[actBackwardKillWord-37]
|
||||||
_ = x[actDeselectAll-38]
|
_ = x[actSelectAll-38]
|
||||||
_ = x[actToggle-39]
|
_ = x[actDeselectAll-39]
|
||||||
_ = x[actToggleSearch-40]
|
_ = x[actToggle-40]
|
||||||
_ = x[actToggleAll-41]
|
_ = x[actToggleSearch-41]
|
||||||
_ = x[actToggleDown-42]
|
_ = x[actToggleAll-42]
|
||||||
_ = x[actToggleUp-43]
|
_ = x[actToggleDown-43]
|
||||||
_ = x[actToggleIn-44]
|
_ = x[actToggleUp-44]
|
||||||
_ = x[actToggleOut-45]
|
_ = x[actToggleIn-45]
|
||||||
_ = x[actToggleTrack-46]
|
_ = x[actToggleOut-46]
|
||||||
_ = x[actToggleTrackCurrent-47]
|
_ = x[actToggleTrack-47]
|
||||||
_ = x[actToggleHeader-48]
|
_ = x[actToggleTrackCurrent-48]
|
||||||
_ = x[actTrackCurrent-49]
|
_ = x[actToggleHeader-49]
|
||||||
_ = x[actUntrackCurrent-50]
|
_ = x[actTrackCurrent-50]
|
||||||
_ = x[actDown-51]
|
_ = x[actUntrackCurrent-51]
|
||||||
_ = x[actUp-52]
|
_ = x[actDown-52]
|
||||||
_ = x[actPageUp-53]
|
_ = x[actUp-53]
|
||||||
_ = x[actPageDown-54]
|
_ = x[actPageUp-54]
|
||||||
_ = x[actPosition-55]
|
_ = x[actPageDown-55]
|
||||||
_ = x[actHalfPageUp-56]
|
_ = x[actPosition-56]
|
||||||
_ = x[actHalfPageDown-57]
|
_ = x[actHalfPageUp-57]
|
||||||
_ = x[actOffsetUp-58]
|
_ = x[actHalfPageDown-58]
|
||||||
_ = x[actOffsetDown-59]
|
_ = x[actOffsetUp-59]
|
||||||
_ = x[actJump-60]
|
_ = x[actOffsetDown-60]
|
||||||
_ = x[actJumpAccept-61]
|
_ = x[actJump-61]
|
||||||
_ = x[actPrintQuery-62]
|
_ = x[actJumpAccept-62]
|
||||||
_ = x[actRefreshPreview-63]
|
_ = x[actPrintQuery-63]
|
||||||
_ = x[actReplaceQuery-64]
|
_ = x[actRefreshPreview-64]
|
||||||
_ = x[actToggleSort-65]
|
_ = x[actReplaceQuery-65]
|
||||||
_ = x[actShowPreview-66]
|
_ = x[actToggleSort-66]
|
||||||
_ = x[actHidePreview-67]
|
_ = x[actShowPreview-67]
|
||||||
_ = x[actTogglePreview-68]
|
_ = x[actHidePreview-68]
|
||||||
_ = x[actTogglePreviewWrap-69]
|
_ = x[actTogglePreview-69]
|
||||||
_ = x[actTransform-70]
|
_ = x[actTogglePreviewWrap-70]
|
||||||
_ = x[actTransformBorderLabel-71]
|
_ = x[actTransform-71]
|
||||||
_ = x[actTransformHeader-72]
|
_ = x[actTransformBorderLabel-72]
|
||||||
_ = x[actTransformPreviewLabel-73]
|
_ = x[actTransformHeader-73]
|
||||||
_ = x[actTransformPrompt-74]
|
_ = x[actTransformPreviewLabel-74]
|
||||||
_ = x[actTransformQuery-75]
|
_ = x[actTransformPrompt-75]
|
||||||
_ = x[actPreview-76]
|
_ = x[actTransformQuery-76]
|
||||||
_ = x[actChangePreview-77]
|
_ = x[actPreview-77]
|
||||||
_ = x[actChangePreviewWindow-78]
|
_ = x[actChangePreview-78]
|
||||||
_ = x[actPreviewTop-79]
|
_ = x[actChangePreviewWindow-79]
|
||||||
_ = x[actPreviewBottom-80]
|
_ = x[actPreviewTop-80]
|
||||||
_ = x[actPreviewUp-81]
|
_ = x[actPreviewBottom-81]
|
||||||
_ = x[actPreviewDown-82]
|
_ = x[actPreviewUp-82]
|
||||||
_ = x[actPreviewPageUp-83]
|
_ = x[actPreviewDown-83]
|
||||||
_ = x[actPreviewPageDown-84]
|
_ = x[actPreviewPageUp-84]
|
||||||
_ = x[actPreviewHalfPageUp-85]
|
_ = x[actPreviewPageDown-85]
|
||||||
_ = x[actPreviewHalfPageDown-86]
|
_ = x[actPreviewHalfPageUp-86]
|
||||||
_ = x[actPrevHistory-87]
|
_ = x[actPreviewHalfPageDown-87]
|
||||||
_ = x[actPrevSelected-88]
|
_ = x[actPrevHistory-88]
|
||||||
_ = x[actPut-89]
|
_ = x[actPrevSelected-89]
|
||||||
_ = x[actNextHistory-90]
|
_ = x[actPrint-90]
|
||||||
_ = x[actNextSelected-91]
|
_ = x[actPut-91]
|
||||||
_ = x[actExecute-92]
|
_ = x[actNextHistory-92]
|
||||||
_ = x[actExecuteSilent-93]
|
_ = x[actNextSelected-93]
|
||||||
_ = x[actExecuteMulti-94]
|
_ = x[actExecute-94]
|
||||||
_ = x[actSigStop-95]
|
_ = x[actExecuteSilent-95]
|
||||||
_ = x[actFirst-96]
|
_ = x[actExecuteMulti-96]
|
||||||
_ = x[actLast-97]
|
_ = x[actSigStop-97]
|
||||||
_ = x[actReload-98]
|
_ = x[actFirst-98]
|
||||||
_ = x[actReloadSync-99]
|
_ = x[actLast-99]
|
||||||
_ = x[actDisableSearch-100]
|
_ = x[actReload-100]
|
||||||
_ = x[actEnableSearch-101]
|
_ = x[actReloadSync-101]
|
||||||
_ = x[actSelect-102]
|
_ = x[actDisableSearch-102]
|
||||||
_ = x[actDeselect-103]
|
_ = x[actEnableSearch-103]
|
||||||
_ = x[actUnbind-104]
|
_ = x[actSelect-104]
|
||||||
_ = x[actRebind-105]
|
_ = x[actDeselect-105]
|
||||||
_ = x[actBecome-106]
|
_ = x[actUnbind-106]
|
||||||
_ = x[actResponse-107]
|
_ = x[actRebind-107]
|
||||||
_ = x[actShowHeader-108]
|
_ = x[actBecome-108]
|
||||||
_ = x[actHideHeader-109]
|
_ = x[actResponse-109]
|
||||||
|
_ = x[actShowHeader-110]
|
||||||
|
_ = x[actHideHeader-111]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactResponseactShowHeaderactHideHeader"
|
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactResponseactShowHeaderactHideHeader"
|
||||||
|
|
||||||
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 256, 277, 292, 306, 320, 333, 350, 358, 371, 387, 399, 413, 427, 438, 449, 467, 484, 491, 510, 522, 536, 545, 560, 572, 585, 596, 607, 619, 633, 654, 669, 684, 701, 708, 713, 722, 733, 744, 757, 772, 783, 796, 803, 816, 829, 846, 861, 874, 888, 902, 918, 938, 950, 973, 991, 1015, 1033, 1050, 1060, 1076, 1098, 1111, 1127, 1139, 1153, 1169, 1187, 1207, 1229, 1243, 1258, 1264, 1278, 1293, 1303, 1319, 1334, 1344, 1352, 1359, 1368, 1381, 1397, 1412, 1421, 1432, 1441, 1450, 1459, 1470, 1483, 1496}
|
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 256, 277, 292, 306, 320, 333, 350, 358, 371, 387, 399, 407, 421, 435, 446, 457, 475, 492, 499, 518, 530, 544, 553, 568, 580, 593, 604, 615, 627, 641, 662, 677, 692, 709, 716, 721, 730, 741, 752, 765, 780, 791, 804, 811, 824, 837, 854, 869, 882, 896, 910, 926, 946, 958, 981, 999, 1023, 1041, 1058, 1068, 1084, 1106, 1119, 1135, 1147, 1161, 1177, 1195, 1215, 1237, 1251, 1266, 1274, 1280, 1294, 1309, 1319, 1335, 1350, 1360, 1368, 1375, 1384, 1397, 1413, 1428, 1437, 1448, 1457, 1466, 1475, 1486, 1499, 1512}
|
||||||
|
|
||||||
func (i actionType) String() string {
|
func (i actionType) String() string {
|
||||||
if i < 0 || i >= actionType(len(_actionType_index)-1) {
|
if i < 0 || i >= actionType(len(_actionType_index)-1) {
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ var (
|
|||||||
// Extra bonus for word boundary after slash, colon, semi-colon, and comma
|
// Extra bonus for word boundary after slash, colon, semi-colon, and comma
|
||||||
bonusBoundaryDelimiter int16 = bonusBoundary + 1
|
bonusBoundaryDelimiter int16 = bonusBoundary + 1
|
||||||
|
|
||||||
initialCharClass charClass = charWhite
|
initialCharClass = charWhite
|
||||||
|
|
||||||
// A minor optimization that can give 15%+ performance boost
|
// A minor optimization that can give 15%+ performance boost
|
||||||
asciiCharClasses [unicode.MaxASCII + 1]charClass
|
asciiCharClasses [unicode.MaxASCII + 1]charClass
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
package algo
|
package algo
|
||||||
|
|
||||||
var normalized map[rune]rune = map[rune]rune{
|
var normalized = map[rune]rune{
|
||||||
0x00E1: 'a', // WITH ACUTE, LATIN SMALL LETTER
|
0x00E1: 'a', // WITH ACUTE, LATIN SMALL LETTER
|
||||||
0x0103: 'a', // WITH BREVE, LATIN SMALL LETTER
|
0x0103: 'a', // WITH BREVE, LATIN SMALL LETTER
|
||||||
0x01CE: 'a', // WITH CARON, LATIN SMALL LETTER
|
0x01CE: 'a', // WITH CARON, LATIN SMALL LETTER
|
||||||
|
|||||||
@@ -292,7 +292,7 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
|
|||||||
|
|
||||||
func parseAnsiCode(s string, delimiter byte) (int, byte, string) {
|
func parseAnsiCode(s string, delimiter byte) (int, byte, string) {
|
||||||
var remaining string
|
var remaining string
|
||||||
i := -1
|
var i int
|
||||||
if delimiter == 0 {
|
if delimiter == 0 {
|
||||||
// Faster than strings.IndexAny(";:")
|
// Faster than strings.IndexAny(";:")
|
||||||
i = strings.IndexByte(s, ';')
|
i = strings.IndexByte(s, ';')
|
||||||
@@ -312,7 +312,7 @@ func parseAnsiCode(s string, delimiter byte) (int, byte, string) {
|
|||||||
// Inlined version of strconv.Atoi() that only handles positive
|
// Inlined version of strconv.Atoi() that only handles positive
|
||||||
// integers and does not allocate on error.
|
// integers and does not allocate on error.
|
||||||
code := 0
|
code := 0
|
||||||
for _, ch := range sbytes(s) {
|
for _, ch := range stringBytes(s) {
|
||||||
ch -= '0'
|
ch -= '0'
|
||||||
if ch > 9 {
|
if ch > 9 {
|
||||||
return -1, delimiter, remaining
|
return -1, delimiter, remaining
|
||||||
@@ -350,7 +350,7 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
|||||||
state256 := 0
|
state256 := 0
|
||||||
ptr := &state.fg
|
ptr := &state.fg
|
||||||
|
|
||||||
var delimiter byte = 0
|
var delimiter byte
|
||||||
count := 0
|
count := 0
|
||||||
for len(ansiCode) != 0 {
|
for len(ansiCode) != 0 {
|
||||||
var num int
|
var num int
|
||||||
|
|||||||
@@ -342,8 +342,8 @@ func TestAnsiCodeStringConversion(t *testing.T) {
|
|||||||
state := interpretCode(code, prevState)
|
state := interpretCode(code, prevState)
|
||||||
if expected != state.ToString() {
|
if expected != state.ToString() {
|
||||||
t.Errorf("expected: %s, actual: %s",
|
t.Errorf("expected: %s, actual: %s",
|
||||||
strings.Replace(expected, "\x1b[", "\\x1b[", -1),
|
strings.ReplaceAll(expected, "\x1b[", "\\x1b["),
|
||||||
strings.Replace(state.ToString(), "\x1b[", "\\x1b[", -1))
|
strings.ReplaceAll(state.ToString(), "\x1b[", "\\x1b["))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert("\x1b[m", nil, "")
|
assert("\x1b[m", nil, "")
|
||||||
|
|||||||
10
src/cache.go
10
src/cache.go
@@ -12,8 +12,14 @@ type ChunkCache struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewChunkCache returns a new ChunkCache
|
// NewChunkCache returns a new ChunkCache
|
||||||
func NewChunkCache() ChunkCache {
|
func NewChunkCache() *ChunkCache {
|
||||||
return ChunkCache{sync.Mutex{}, make(map[*Chunk]*queryCache)}
|
return &ChunkCache{sync.Mutex{}, make(map[*Chunk]*queryCache)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *ChunkCache) Clear() {
|
||||||
|
cc.mutex.Lock()
|
||||||
|
cc.cache = make(map[*Chunk]*queryCache)
|
||||||
|
cc.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add adds the list to the cache
|
// Add adds the list to the cache
|
||||||
|
|||||||
@@ -48,7 +48,12 @@ func CountItems(cs []*Chunk) int {
|
|||||||
if len(cs) == 0 {
|
if len(cs) == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return chunkSize*(len(cs)-1) + cs[len(cs)-1].count
|
if len(cs) == 1 {
|
||||||
|
return cs[0].count
|
||||||
|
}
|
||||||
|
|
||||||
|
// First chunk might not be full due to --tail=N
|
||||||
|
return cs[0].count + chunkSize*(len(cs)-2) + cs[len(cs)-1].count
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push adds the item to the list
|
// Push adds the item to the list
|
||||||
@@ -72,18 +77,53 @@ func (cl *ChunkList) Clear() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Snapshot returns immutable snapshot of the ChunkList
|
// Snapshot returns immutable snapshot of the ChunkList
|
||||||
func (cl *ChunkList) Snapshot() ([]*Chunk, int) {
|
func (cl *ChunkList) Snapshot(tail int) ([]*Chunk, int, bool) {
|
||||||
cl.mutex.Lock()
|
cl.mutex.Lock()
|
||||||
|
|
||||||
|
changed := false
|
||||||
|
if tail > 0 && CountItems(cl.chunks) > tail {
|
||||||
|
changed = true
|
||||||
|
// Find the number of chunks to keep
|
||||||
|
numChunks := 0
|
||||||
|
for left, i := tail, len(cl.chunks)-1; left > 0 && i >= 0; i-- {
|
||||||
|
numChunks++
|
||||||
|
left -= cl.chunks[i].count
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the chunks to keep
|
||||||
|
ret := make([]*Chunk, numChunks)
|
||||||
|
copy(ret, cl.chunks[len(cl.chunks)-numChunks:])
|
||||||
|
|
||||||
|
for left, i := tail, len(ret)-1; i >= 0; i-- {
|
||||||
|
chunk := ret[i]
|
||||||
|
if chunk.count > left {
|
||||||
|
newChunk := *chunk
|
||||||
|
newChunk.count = left
|
||||||
|
oldCount := chunk.count
|
||||||
|
for i := 0; i < left; i++ {
|
||||||
|
newChunk.items[i] = chunk.items[oldCount-left+i]
|
||||||
|
}
|
||||||
|
ret[i] = &newChunk
|
||||||
|
break
|
||||||
|
}
|
||||||
|
left -= chunk.count
|
||||||
|
}
|
||||||
|
cl.chunks = ret
|
||||||
|
}
|
||||||
|
|
||||||
ret := make([]*Chunk, len(cl.chunks))
|
ret := make([]*Chunk, len(cl.chunks))
|
||||||
copy(ret, cl.chunks)
|
copy(ret, cl.chunks)
|
||||||
|
|
||||||
// Duplicate the last chunk
|
// Duplicate the first and the last chunk
|
||||||
if cnt := len(ret); cnt > 0 {
|
if cnt := len(ret); cnt > 0 {
|
||||||
|
if tail > 0 && cnt > 1 {
|
||||||
|
newChunk := *ret[0]
|
||||||
|
ret[0] = &newChunk
|
||||||
|
}
|
||||||
newChunk := *ret[cnt-1]
|
newChunk := *ret[cnt-1]
|
||||||
ret[cnt-1] = &newChunk
|
ret[cnt-1] = &newChunk
|
||||||
}
|
}
|
||||||
|
|
||||||
cl.mutex.Unlock()
|
cl.mutex.Unlock()
|
||||||
return ret, CountItems(ret)
|
return ret, CountItems(ret), changed
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ func TestChunkList(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Snapshot
|
// Snapshot
|
||||||
snapshot, count := cl.Snapshot()
|
snapshot, count, _ := cl.Snapshot(0)
|
||||||
if len(snapshot) > 0 || count > 0 {
|
if len(snapshot) > 0 || count > 0 {
|
||||||
t.Error("Snapshot should be empty now")
|
t.Error("Snapshot should be empty now")
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,7 @@ func TestChunkList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// But the new snapshot should contain the added items
|
// But the new snapshot should contain the added items
|
||||||
snapshot, count = cl.Snapshot()
|
snapshot, count, _ = cl.Snapshot(0)
|
||||||
if len(snapshot) != 1 && count != 2 {
|
if len(snapshot) != 1 && count != 2 {
|
||||||
t.Error("Snapshot should not be empty now")
|
t.Error("Snapshot should not be empty now")
|
||||||
}
|
}
|
||||||
@@ -61,7 +61,7 @@ func TestChunkList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New snapshot
|
// New snapshot
|
||||||
snapshot, count = cl.Snapshot()
|
snapshot, count, _ = cl.Snapshot(0)
|
||||||
if len(snapshot) != 3 || !snapshot[0].IsFull() ||
|
if len(snapshot) != 3 || !snapshot[0].IsFull() ||
|
||||||
!snapshot[1].IsFull() || snapshot[2].IsFull() || count != chunkSize*2+2 {
|
!snapshot[1].IsFull() || snapshot[2].IsFull() || count != chunkSize*2+2 {
|
||||||
t.Error("Expected two full chunks and one more chunk")
|
t.Error("Expected two full chunks and one more chunk")
|
||||||
@@ -78,3 +78,39 @@ func TestChunkList(t *testing.T) {
|
|||||||
t.Error("Unexpected number of items:", lastChunkCount)
|
t.Error("Unexpected number of items:", lastChunkCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestChunkListTail(t *testing.T) {
|
||||||
|
cl := NewChunkList(func(item *Item, s []byte) bool {
|
||||||
|
item.text = util.ToChars(s)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
total := chunkSize*2 + chunkSize/2
|
||||||
|
for i := 0; i < total; i++ {
|
||||||
|
cl.Push([]byte(fmt.Sprintf("item %d", i)))
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot, count, changed := cl.Snapshot(0)
|
||||||
|
assertCount := func(expected int, shouldChange bool) {
|
||||||
|
if count != expected || CountItems(snapshot) != expected {
|
||||||
|
t.Errorf("Unexpected count: %d (expected: %d)", count, expected)
|
||||||
|
}
|
||||||
|
if changed != shouldChange {
|
||||||
|
t.Error("Unexpected change status")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertCount(total, false)
|
||||||
|
|
||||||
|
tail := chunkSize + chunkSize/2
|
||||||
|
snapshot, count, changed = cl.Snapshot(tail)
|
||||||
|
assertCount(tail, true)
|
||||||
|
|
||||||
|
snapshot, count, changed = cl.Snapshot(tail)
|
||||||
|
assertCount(tail, false)
|
||||||
|
|
||||||
|
snapshot, count, changed = cl.Snapshot(0)
|
||||||
|
assertCount(tail, false)
|
||||||
|
|
||||||
|
tail = chunkSize / 2
|
||||||
|
snapshot, count, changed = cl.Snapshot(tail)
|
||||||
|
assertCount(tail, true)
|
||||||
|
}
|
||||||
|
|||||||
@@ -67,9 +67,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
exitCancel = -1
|
ExitOk = 0
|
||||||
exitOk = 0
|
ExitNoMatch = 1
|
||||||
exitNoMatch = 1
|
ExitError = 2
|
||||||
exitError = 2
|
ExitBecome = 126
|
||||||
exitInterrupt = 130
|
ExitInterrupt = 130
|
||||||
)
|
)
|
||||||
|
|||||||
137
src/core.go
137
src/core.go
@@ -2,10 +2,9 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
@@ -19,30 +18,54 @@ Matcher -> EvtSearchFin -> Terminal (update list)
|
|||||||
Matcher -> EvtHeader -> Terminal (update header)
|
Matcher -> EvtHeader -> Terminal (update header)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func ustring(data []byte) string {
|
type revision struct {
|
||||||
return unsafe.String(unsafe.SliceData(data), len(data))
|
major int
|
||||||
|
minor int
|
||||||
}
|
}
|
||||||
|
|
||||||
func sbytes(data string) []byte {
|
func (r *revision) bumpMajor() {
|
||||||
return unsafe.Slice(unsafe.StringData(data), len(data))
|
r.major++
|
||||||
|
r.minor = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *revision) bumpMinor() {
|
||||||
|
r.minor++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r revision) equals(other revision) bool {
|
||||||
|
return r.major == other.major && r.minor == other.minor
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r revision) compatible(other revision) bool {
|
||||||
|
return r.major == other.major
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run starts fzf
|
// Run starts fzf
|
||||||
func Run(opts *Options, version string, revision string) {
|
func Run(opts *Options) (int, error) {
|
||||||
|
if opts.Tmux != nil && len(os.Getenv("TMUX")) > 0 && opts.Tmux.index >= opts.Height.index {
|
||||||
|
return runTmux(os.Args, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
if needWinpty(opts) {
|
||||||
|
return runWinpty(os.Args, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := postProcessOptions(opts); err != nil {
|
||||||
|
return ExitError, err
|
||||||
|
}
|
||||||
|
|
||||||
defer util.RunAtExitFuncs()
|
defer util.RunAtExitFuncs()
|
||||||
|
|
||||||
|
// Output channel given
|
||||||
|
if opts.Output != nil {
|
||||||
|
opts.Printer = func(str string) {
|
||||||
|
opts.Output <- str
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sort := opts.Sort > 0
|
sort := opts.Sort > 0
|
||||||
sortCriteria = opts.Criteria
|
sortCriteria = opts.Criteria
|
||||||
|
|
||||||
if opts.Version {
|
|
||||||
if len(revision) > 0 {
|
|
||||||
fmt.Printf("%s (%s)\n", version, revision)
|
|
||||||
} else {
|
|
||||||
fmt.Println(version)
|
|
||||||
}
|
|
||||||
util.Exit(exitOk)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event channel
|
// Event channel
|
||||||
eventBox := util.NewEventBox()
|
eventBox := util.NewEventBox()
|
||||||
|
|
||||||
@@ -56,16 +79,16 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
if opts.Theme.Colored {
|
if opts.Theme.Colored {
|
||||||
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
||||||
prevLineAnsiState = lineAnsiState
|
prevLineAnsiState = lineAnsiState
|
||||||
trimmed, offsets, newState := extractColor(ustring(data), lineAnsiState, nil)
|
trimmed, offsets, newState := extractColor(byteString(data), lineAnsiState, nil)
|
||||||
lineAnsiState = newState
|
lineAnsiState = newState
|
||||||
return util.ToChars(sbytes(trimmed)), offsets
|
return util.ToChars(stringBytes(trimmed)), offsets
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// When color is disabled but ansi option is given,
|
// When color is disabled but ansi option is given,
|
||||||
// we simply strip out ANSI codes from the input
|
// we simply strip out ANSI codes from the input
|
||||||
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
||||||
trimmed, _, _ := extractColor(ustring(data), nil, nil)
|
trimmed, _, _ := extractColor(byteString(data), nil, nil)
|
||||||
return util.ToChars(sbytes(trimmed)), nil
|
return util.ToChars(stringBytes(trimmed)), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,7 +100,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
if len(opts.WithNth) == 0 {
|
if len(opts.WithNth) == 0 {
|
||||||
chunkList = NewChunkList(func(item *Item, data []byte) bool {
|
chunkList = NewChunkList(func(item *Item, data []byte) bool {
|
||||||
if len(header) < opts.HeaderLines {
|
if len(header) < opts.HeaderLines {
|
||||||
header = append(header, ustring(data))
|
header = append(header, byteString(data))
|
||||||
eventBox.Set(EvtHeader, header)
|
eventBox.Set(EvtHeader, header)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -88,7 +111,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
chunkList = NewChunkList(func(item *Item, data []byte) bool {
|
chunkList = NewChunkList(func(item *Item, data []byte) bool {
|
||||||
tokens := Tokenize(ustring(data), opts.Delimiter)
|
tokens := Tokenize(byteString(data), opts.Delimiter)
|
||||||
if opts.Ansi && opts.Theme.Colored && len(tokens) > 1 {
|
if opts.Ansi && opts.Theme.Colored && len(tokens) > 1 {
|
||||||
var ansiState *ansiState
|
var ansiState *ansiState
|
||||||
if prevLineAnsiState != nil {
|
if prevLineAnsiState != nil {
|
||||||
@@ -112,7 +135,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
eventBox.Set(EvtHeader, header)
|
eventBox.Set(EvtHeader, header)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
item.text, item.colors = ansiProcessor(sbytes(transformed))
|
item.text, item.colors = ansiProcessor(stringBytes(transformed))
|
||||||
item.text.TrimTrailingWhitespaces()
|
item.text.TrimTrailingWhitespaces()
|
||||||
item.text.Index = itemIndex
|
item.text.Index = itemIndex
|
||||||
item.origText = &data
|
item.origText = &data
|
||||||
@@ -131,7 +154,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
reader = NewReader(func(data []byte) bool {
|
reader = NewReader(func(data []byte) bool {
|
||||||
return chunkList.Push(data)
|
return chunkList.Push(data)
|
||||||
}, eventBox, executor, opts.ReadZero, opts.Filter == nil)
|
}, eventBox, executor, opts.ReadZero, opts.Filter == nil)
|
||||||
go reader.ReadSource(opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
|
go reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Matcher
|
// Matcher
|
||||||
@@ -147,14 +170,16 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
forward = true
|
forward = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
cache := NewChunkCache()
|
||||||
|
patternCache := make(map[string]*Pattern)
|
||||||
patternBuilder := func(runes []rune) *Pattern {
|
patternBuilder := func(runes []rune) *Pattern {
|
||||||
return BuildPattern(
|
return BuildPattern(cache, patternCache,
|
||||||
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward, withPos,
|
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward, withPos,
|
||||||
opts.Filter == nil, opts.Nth, opts.Delimiter, runes)
|
opts.Filter == nil, opts.Nth, opts.Delimiter, runes)
|
||||||
}
|
}
|
||||||
inputRevision := 0
|
inputRevision := revision{}
|
||||||
snapshotRevision := 0
|
snapshotRevision := revision{}
|
||||||
matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox, inputRevision)
|
matcher := NewMatcher(cache, patternBuilder, sort, opts.Tac, eventBox, inputRevision)
|
||||||
|
|
||||||
// Filtering mode
|
// Filtering mode
|
||||||
if opts.Filter != nil {
|
if opts.Filter != nil {
|
||||||
@@ -182,12 +207,13 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}, eventBox, executor, opts.ReadZero, false)
|
}, eventBox, executor, opts.ReadZero, false)
|
||||||
reader.ReadSource(opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
|
reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
|
||||||
} else {
|
} else {
|
||||||
eventBox.Unwatch(EvtReadNew)
|
eventBox.Unwatch(EvtReadNew)
|
||||||
eventBox.WaitFor(EvtReadFin)
|
eventBox.WaitFor(EvtReadFin)
|
||||||
|
|
||||||
snapshot, _ := chunkList.Snapshot()
|
// NOTE: Streaming filter is inherently not compatible with --tail
|
||||||
|
snapshot, _, _ := chunkList.Snapshot(opts.Tail)
|
||||||
merger, _ := matcher.scan(MatchRequest{
|
merger, _ := matcher.scan(MatchRequest{
|
||||||
chunks: snapshot,
|
chunks: snapshot,
|
||||||
pattern: pattern})
|
pattern: pattern})
|
||||||
@@ -197,9 +223,9 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if found {
|
if found {
|
||||||
util.Exit(exitOk)
|
return ExitOk, nil
|
||||||
}
|
}
|
||||||
util.Exit(exitNoMatch)
|
return ExitNoMatch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Synchronous search
|
// Synchronous search
|
||||||
@@ -210,9 +236,13 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
|
|
||||||
// Go interactive
|
// Go interactive
|
||||||
go matcher.Loop()
|
go matcher.Loop()
|
||||||
|
defer matcher.Stop()
|
||||||
|
|
||||||
// Terminal I/O
|
// Terminal I/O
|
||||||
terminal := NewTerminal(opts, eventBox, executor)
|
terminal, err := NewTerminal(opts, eventBox, executor)
|
||||||
|
if err != nil {
|
||||||
|
return ExitError, err
|
||||||
|
}
|
||||||
maxFit := 0 // Maximum number of items that can fit on screen
|
maxFit := 0 // Maximum number of items that can fit on screen
|
||||||
padHeight := 0
|
padHeight := 0
|
||||||
heightUnknown := opts.Height.auto
|
heightUnknown := opts.Height.auto
|
||||||
@@ -229,7 +259,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
// Event coordination
|
// Event coordination
|
||||||
reading := true
|
reading := true
|
||||||
ticks := 0
|
ticks := 0
|
||||||
var nextCommand *string
|
var nextCommand *commandSpec
|
||||||
var nextEnviron []string
|
var nextEnviron []string
|
||||||
eventBox.Watch(EvtReadNew)
|
eventBox.Watch(EvtReadNew)
|
||||||
total := 0
|
total := 0
|
||||||
@@ -250,14 +280,17 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
useSnapshot := false
|
useSnapshot := false
|
||||||
var snapshot []*Chunk
|
var snapshot []*Chunk
|
||||||
var count int
|
var count int
|
||||||
restart := func(command string, environ []string) {
|
restart := func(command commandSpec, environ []string) {
|
||||||
reading = true
|
reading = true
|
||||||
chunkList.Clear()
|
chunkList.Clear()
|
||||||
itemIndex = 0
|
itemIndex = 0
|
||||||
inputRevision++
|
inputRevision.bumpMajor()
|
||||||
header = make([]string, 0, opts.HeaderLines)
|
header = make([]string, 0, opts.HeaderLines)
|
||||||
go reader.restart(command, environ)
|
go reader.restart(command, environ)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exitCode := ExitOk
|
||||||
|
stop := false
|
||||||
for {
|
for {
|
||||||
delay := true
|
delay := true
|
||||||
ticks++
|
ticks++
|
||||||
@@ -278,7 +311,11 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
if reading {
|
if reading {
|
||||||
reader.terminate()
|
reader.terminate()
|
||||||
}
|
}
|
||||||
util.Exit(value.(int))
|
quitSignal := value.(quitSignal)
|
||||||
|
exitCode = quitSignal.code
|
||||||
|
err = quitSignal.err
|
||||||
|
stop = true
|
||||||
|
return
|
||||||
case EvtReadNew, EvtReadFin:
|
case EvtReadNew, EvtReadFin:
|
||||||
if evt == EvtReadFin && nextCommand != nil {
|
if evt == EvtReadFin && nextCommand != nil {
|
||||||
restart(*nextCommand, nextEnviron)
|
restart(*nextCommand, nextEnviron)
|
||||||
@@ -292,10 +329,14 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
useSnapshot = false
|
useSnapshot = false
|
||||||
}
|
}
|
||||||
if !useSnapshot {
|
if !useSnapshot {
|
||||||
if snapshotRevision != inputRevision {
|
if !snapshotRevision.compatible(inputRevision) {
|
||||||
query = []rune{}
|
query = []rune{}
|
||||||
}
|
}
|
||||||
snapshot, count = chunkList.Snapshot()
|
var changed bool
|
||||||
|
snapshot, count, changed = chunkList.Snapshot(opts.Tail)
|
||||||
|
if changed {
|
||||||
|
inputRevision.bumpMinor()
|
||||||
|
}
|
||||||
snapshotRevision = inputRevision
|
snapshotRevision = inputRevision
|
||||||
}
|
}
|
||||||
total = count
|
total = count
|
||||||
@@ -309,7 +350,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
matcher.Reset(snapshot, input(), false, !reading, sort, snapshotRevision)
|
matcher.Reset(snapshot, input(), false, !reading, sort, snapshotRevision)
|
||||||
|
|
||||||
case EvtSearchNew:
|
case EvtSearchNew:
|
||||||
var command *string
|
var command *commandSpec
|
||||||
var environ []string
|
var environ []string
|
||||||
var changed bool
|
var changed bool
|
||||||
switch val := value.(type) {
|
switch val := value.(type) {
|
||||||
@@ -335,7 +376,10 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if !useSnapshot {
|
if !useSnapshot {
|
||||||
newSnapshot, newCount := chunkList.Snapshot()
|
newSnapshot, newCount, changed := chunkList.Snapshot(opts.Tail)
|
||||||
|
if changed {
|
||||||
|
inputRevision.bumpMinor()
|
||||||
|
}
|
||||||
// We want to avoid showing empty list when reload is triggered
|
// We want to avoid showing empty list when reload is triggered
|
||||||
// and the query string is changed at the same time i.e. command != nil && changed
|
// and the query string is changed at the same time i.e. command != nil && changed
|
||||||
if command == nil || newCount > 0 {
|
if command == nil || newCount > 0 {
|
||||||
@@ -378,10 +422,11 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
opts.Printer(val.Get(i).item.AsString(opts.Ansi))
|
opts.Printer(val.Get(i).item.AsString(opts.Ansi))
|
||||||
}
|
}
|
||||||
if count > 0 {
|
if count == 0 {
|
||||||
util.Exit(exitOk)
|
exitCode = ExitNoMatch
|
||||||
}
|
}
|
||||||
util.Exit(exitNoMatch)
|
stop = true
|
||||||
|
return
|
||||||
}
|
}
|
||||||
determine(val.final)
|
determine(val.final)
|
||||||
}
|
}
|
||||||
@@ -392,6 +437,9 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
}
|
}
|
||||||
events.Clear()
|
events.Clear()
|
||||||
})
|
})
|
||||||
|
if stop {
|
||||||
|
break
|
||||||
|
}
|
||||||
if delay && reading {
|
if delay && reading {
|
||||||
dur := util.DurWithin(
|
dur := util.DurWithin(
|
||||||
time.Duration(ticks)*coordinatorDelayStep,
|
time.Duration(ticks)*coordinatorDelayStep,
|
||||||
@@ -399,4 +447,5 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
time.Sleep(dur)
|
time.Sleep(dur)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return exitCode, err
|
||||||
}
|
}
|
||||||
|
|||||||
35
src/functions.go
Normal file
35
src/functions.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package fzf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func WriteTemporaryFile(data []string, printSep string) string {
|
||||||
|
f, err := os.CreateTemp("", "fzf-temp-*")
|
||||||
|
if err != nil {
|
||||||
|
// Unable to create temporary file
|
||||||
|
// FIXME: Should we terminate the program?
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
f.WriteString(strings.Join(data, printSep))
|
||||||
|
f.WriteString(printSep)
|
||||||
|
return f.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeFiles(files []string) {
|
||||||
|
for _, filename := range files {
|
||||||
|
os.Remove(filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringBytes(data string) []byte {
|
||||||
|
return unsafe.Slice(unsafe.StringData(data), len(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func byteString(data []byte) string {
|
||||||
|
return unsafe.String(unsafe.SliceData(data), len(data))
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,7 +19,7 @@ func (item *Item) Index() int32 {
|
|||||||
return item.text.Index
|
return item.text.Index
|
||||||
}
|
}
|
||||||
|
|
||||||
var minItem = Item{text: util.Chars{Index: -1}}
|
var minItem = Item{text: util.Chars{Index: math.MinInt32}}
|
||||||
|
|
||||||
func (item *Item) TrimLength() uint16 {
|
func (item *Item) TrimLength() uint16 {
|
||||||
return item.text.TrimLength()
|
return item.text.TrimLength()
|
||||||
|
|||||||
@@ -16,11 +16,12 @@ type MatchRequest struct {
|
|||||||
pattern *Pattern
|
pattern *Pattern
|
||||||
final bool
|
final bool
|
||||||
sort bool
|
sort bool
|
||||||
revision int
|
revision revision
|
||||||
}
|
}
|
||||||
|
|
||||||
// Matcher is responsible for performing search
|
// Matcher is responsible for performing search
|
||||||
type Matcher struct {
|
type Matcher struct {
|
||||||
|
cache *ChunkCache
|
||||||
patternBuilder func([]rune) *Pattern
|
patternBuilder func([]rune) *Pattern
|
||||||
sort bool
|
sort bool
|
||||||
tac bool
|
tac bool
|
||||||
@@ -29,7 +30,7 @@ type Matcher struct {
|
|||||||
partitions int
|
partitions int
|
||||||
slab []*util.Slab
|
slab []*util.Slab
|
||||||
mergerCache map[string]*Merger
|
mergerCache map[string]*Merger
|
||||||
revision int
|
revision revision
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -38,10 +39,11 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewMatcher returns a new Matcher
|
// NewMatcher returns a new Matcher
|
||||||
func NewMatcher(patternBuilder func([]rune) *Pattern,
|
func NewMatcher(cache *ChunkCache, patternBuilder func([]rune) *Pattern,
|
||||||
sort bool, tac bool, eventBox *util.EventBox, revision int) *Matcher {
|
sort bool, tac bool, eventBox *util.EventBox, revision revision) *Matcher {
|
||||||
partitions := util.Min(numPartitionsMultiplier*runtime.NumCPU(), maxPartitions)
|
partitions := util.Min(numPartitionsMultiplier*runtime.NumCPU(), maxPartitions)
|
||||||
return &Matcher{
|
return &Matcher{
|
||||||
|
cache: cache,
|
||||||
patternBuilder: patternBuilder,
|
patternBuilder: patternBuilder,
|
||||||
sort: sort,
|
sort: sort,
|
||||||
tac: tac,
|
tac: tac,
|
||||||
@@ -60,8 +62,13 @@ func (m *Matcher) Loop() {
|
|||||||
for {
|
for {
|
||||||
var request MatchRequest
|
var request MatchRequest
|
||||||
|
|
||||||
|
stop := false
|
||||||
m.reqBox.Wait(func(events *util.Events) {
|
m.reqBox.Wait(func(events *util.Events) {
|
||||||
for _, val := range *events {
|
for t, val := range *events {
|
||||||
|
if t == reqQuit {
|
||||||
|
stop = true
|
||||||
|
return
|
||||||
|
}
|
||||||
switch val := val.(type) {
|
switch val := val.(type) {
|
||||||
case MatchRequest:
|
case MatchRequest:
|
||||||
request = val
|
request = val
|
||||||
@@ -71,12 +78,17 @@ func (m *Matcher) Loop() {
|
|||||||
}
|
}
|
||||||
events.Clear()
|
events.Clear()
|
||||||
})
|
})
|
||||||
|
if stop {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheCleared := false
|
||||||
if request.sort != m.sort || request.revision != m.revision {
|
if request.sort != m.sort || request.revision != m.revision {
|
||||||
m.sort = request.sort
|
m.sort = request.sort
|
||||||
m.revision = request.revision
|
m.revision = request.revision
|
||||||
m.mergerCache = make(map[string]*Merger)
|
m.mergerCache = make(map[string]*Merger)
|
||||||
clearChunkCache()
|
m.cache.Clear()
|
||||||
|
cacheCleared = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart search
|
// Restart search
|
||||||
@@ -85,20 +97,20 @@ func (m *Matcher) Loop() {
|
|||||||
cancelled := false
|
cancelled := false
|
||||||
count := CountItems(request.chunks)
|
count := CountItems(request.chunks)
|
||||||
|
|
||||||
foundCache := false
|
if !cacheCleared {
|
||||||
if count == prevCount {
|
if count == prevCount {
|
||||||
// Look up mergerCache
|
// Look up mergerCache
|
||||||
if cached, found := m.mergerCache[patternString]; found {
|
if cached, found := m.mergerCache[patternString]; found {
|
||||||
foundCache = true
|
merger = cached
|
||||||
merger = cached
|
}
|
||||||
|
} else {
|
||||||
|
// Invalidate mergerCache
|
||||||
|
prevCount = count
|
||||||
|
m.mergerCache = make(map[string]*Merger)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Invalidate mergerCache
|
|
||||||
prevCount = count
|
|
||||||
m.mergerCache = make(map[string]*Merger)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !foundCache {
|
if merger == nil {
|
||||||
merger, cancelled = m.scan(request)
|
merger, cancelled = m.scan(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,6 +162,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
|||||||
return PassMerger(&request.chunks, m.tac, request.revision), false
|
return PassMerger(&request.chunks, m.tac, request.revision), false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
minIndex := request.chunks[0].items[0].Index()
|
||||||
cancelled := util.NewAtomicBool(false)
|
cancelled := util.NewAtomicBool(false)
|
||||||
|
|
||||||
slices := m.sliceChunks(request.chunks)
|
slices := m.sliceChunks(request.chunks)
|
||||||
@@ -221,11 +234,11 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
|||||||
partialResult := <-resultChan
|
partialResult := <-resultChan
|
||||||
partialResults[partialResult.index] = partialResult.matches
|
partialResults[partialResult.index] = partialResult.matches
|
||||||
}
|
}
|
||||||
return NewMerger(pattern, partialResults, m.sort, m.tac, request.revision), false
|
return NewMerger(pattern, partialResults, m.sort, m.tac, request.revision, minIndex), false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset is called to interrupt/signal the ongoing search
|
// Reset is called to interrupt/signal the ongoing search
|
||||||
func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final bool, sort bool, revision int) {
|
func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final bool, sort bool, revision revision) {
|
||||||
pattern := m.patternBuilder(patternRunes)
|
pattern := m.patternBuilder(patternRunes)
|
||||||
|
|
||||||
var event util.EventType
|
var event util.EventType
|
||||||
@@ -236,3 +249,7 @@ func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final
|
|||||||
}
|
}
|
||||||
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable, revision})
|
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable, revision})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Matcher) Stop() {
|
||||||
|
m.reqBox.Set(reqQuit, nil)
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package fzf
|
|||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
// EmptyMerger is a Merger with no data
|
// EmptyMerger is a Merger with no data
|
||||||
func EmptyMerger(revision int) *Merger {
|
func EmptyMerger(revision revision) *Merger {
|
||||||
return NewMerger(nil, [][]Result{}, false, false, revision)
|
return NewMerger(nil, [][]Result{}, false, false, revision, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merger holds a set of locally sorted lists of items and provides the view of
|
// Merger holds a set of locally sorted lists of items and provides the view of
|
||||||
@@ -20,19 +20,25 @@ type Merger struct {
|
|||||||
final bool
|
final bool
|
||||||
count int
|
count int
|
||||||
pass bool
|
pass bool
|
||||||
revision int
|
revision revision
|
||||||
|
minIndex int32
|
||||||
}
|
}
|
||||||
|
|
||||||
// PassMerger returns a new Merger that simply returns the items in the
|
// PassMerger returns a new Merger that simply returns the items in the
|
||||||
// original order
|
// original order
|
||||||
func PassMerger(chunks *[]*Chunk, tac bool, revision int) *Merger {
|
func PassMerger(chunks *[]*Chunk, tac bool, revision revision) *Merger {
|
||||||
|
var minIndex int32
|
||||||
|
if len(*chunks) > 0 {
|
||||||
|
minIndex = (*chunks)[0].items[0].Index()
|
||||||
|
}
|
||||||
mg := Merger{
|
mg := Merger{
|
||||||
pattern: nil,
|
pattern: nil,
|
||||||
chunks: chunks,
|
chunks: chunks,
|
||||||
tac: tac,
|
tac: tac,
|
||||||
count: 0,
|
count: 0,
|
||||||
pass: true,
|
pass: true,
|
||||||
revision: revision}
|
revision: revision,
|
||||||
|
minIndex: minIndex}
|
||||||
|
|
||||||
for _, chunk := range *mg.chunks {
|
for _, chunk := range *mg.chunks {
|
||||||
mg.count += chunk.count
|
mg.count += chunk.count
|
||||||
@@ -41,7 +47,7 @@ func PassMerger(chunks *[]*Chunk, tac bool, revision int) *Merger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewMerger returns a new Merger
|
// NewMerger returns a new Merger
|
||||||
func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool, revision int) *Merger {
|
func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool, revision revision, minIndex int32) *Merger {
|
||||||
mg := Merger{
|
mg := Merger{
|
||||||
pattern: pattern,
|
pattern: pattern,
|
||||||
lists: lists,
|
lists: lists,
|
||||||
@@ -52,7 +58,8 @@ func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool, revisi
|
|||||||
tac: tac,
|
tac: tac,
|
||||||
final: false,
|
final: false,
|
||||||
count: 0,
|
count: 0,
|
||||||
revision: revision}
|
revision: revision,
|
||||||
|
minIndex: minIndex}
|
||||||
|
|
||||||
for _, list := range mg.lists {
|
for _, list := range mg.lists {
|
||||||
mg.count += len(list)
|
mg.count += len(list)
|
||||||
@@ -61,7 +68,7 @@ func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool, revisi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Revision returns revision number
|
// Revision returns revision number
|
||||||
func (mg *Merger) Revision() int {
|
func (mg *Merger) Revision() revision {
|
||||||
return mg.revision
|
return mg.revision
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +88,7 @@ func (mg *Merger) First() Result {
|
|||||||
func (mg *Merger) FindIndex(itemIndex int32) int {
|
func (mg *Merger) FindIndex(itemIndex int32) int {
|
||||||
index := -1
|
index := -1
|
||||||
if mg.pass {
|
if mg.pass {
|
||||||
index = int(itemIndex)
|
index = int(itemIndex - mg.minIndex)
|
||||||
if mg.tac {
|
if mg.tac {
|
||||||
index = mg.count - index - 1
|
index = mg.count - index - 1
|
||||||
}
|
}
|
||||||
@@ -102,6 +109,13 @@ func (mg *Merger) Get(idx int) Result {
|
|||||||
if mg.tac {
|
if mg.tac {
|
||||||
idx = mg.count - idx - 1
|
idx = mg.count - idx - 1
|
||||||
}
|
}
|
||||||
|
firstChunk := (*mg.chunks)[0]
|
||||||
|
if firstChunk.count < chunkSize && idx >= firstChunk.count {
|
||||||
|
idx -= firstChunk.count
|
||||||
|
|
||||||
|
chunk := (*mg.chunks)[idx/chunkSize+1]
|
||||||
|
return Result{item: &chunk.items[idx%chunkSize]}
|
||||||
|
}
|
||||||
chunk := (*mg.chunks)[idx/chunkSize]
|
chunk := (*mg.chunks)[idx/chunkSize]
|
||||||
return Result{item: &chunk.items[idx%chunkSize]}
|
return Result{item: &chunk.items[idx%chunkSize]}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,10 +23,11 @@ func randResult() Result {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEmptyMerger(t *testing.T) {
|
func TestEmptyMerger(t *testing.T) {
|
||||||
assert(t, EmptyMerger(0).Length() == 0, "Not empty")
|
r := revision{}
|
||||||
assert(t, EmptyMerger(0).count == 0, "Invalid count")
|
assert(t, EmptyMerger(r).Length() == 0, "Not empty")
|
||||||
assert(t, len(EmptyMerger(0).lists) == 0, "Invalid lists")
|
assert(t, EmptyMerger(r).count == 0, "Invalid count")
|
||||||
assert(t, len(EmptyMerger(0).merged) == 0, "Invalid merged list")
|
assert(t, len(EmptyMerger(r).lists) == 0, "Invalid lists")
|
||||||
|
assert(t, len(EmptyMerger(r).merged) == 0, "Invalid merged list")
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildLists(partiallySorted bool) ([][]Result, []Result) {
|
func buildLists(partiallySorted bool) ([][]Result, []Result) {
|
||||||
@@ -57,7 +58,7 @@ func TestMergerUnsorted(t *testing.T) {
|
|||||||
cnt := len(items)
|
cnt := len(items)
|
||||||
|
|
||||||
// Not sorted: same order
|
// Not sorted: same order
|
||||||
mg := NewMerger(nil, lists, false, false, 0)
|
mg := NewMerger(nil, lists, false, false, revision{}, 0)
|
||||||
assert(t, cnt == mg.Length(), "Invalid Length")
|
assert(t, cnt == mg.Length(), "Invalid Length")
|
||||||
for i := 0; i < cnt; i++ {
|
for i := 0; i < cnt; i++ {
|
||||||
assert(t, items[i] == mg.Get(i), "Invalid Get")
|
assert(t, items[i] == mg.Get(i), "Invalid Get")
|
||||||
@@ -69,7 +70,7 @@ func TestMergerSorted(t *testing.T) {
|
|||||||
cnt := len(items)
|
cnt := len(items)
|
||||||
|
|
||||||
// Sorted sorted order
|
// Sorted sorted order
|
||||||
mg := NewMerger(nil, lists, true, false, 0)
|
mg := NewMerger(nil, lists, true, false, revision{}, 0)
|
||||||
assert(t, cnt == mg.Length(), "Invalid Length")
|
assert(t, cnt == mg.Length(), "Invalid Length")
|
||||||
sort.Sort(ByRelevance(items))
|
sort.Sort(ByRelevance(items))
|
||||||
for i := 0; i < cnt; i++ {
|
for i := 0; i < cnt; i++ {
|
||||||
@@ -79,7 +80,7 @@ func TestMergerSorted(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Inverse order
|
// Inverse order
|
||||||
mg2 := NewMerger(nil, lists, true, false, 0)
|
mg2 := NewMerger(nil, lists, true, false, revision{}, 0)
|
||||||
for i := cnt - 1; i >= 0; i-- {
|
for i := cnt - 1; i >= 0; i-- {
|
||||||
if items[i] != mg2.Get(i) {
|
if items[i] != mg2.Get(i) {
|
||||||
t.Error("Not sorted", items[i], mg2.Get(i))
|
t.Error("Not sorted", items[i], mg2.Get(i))
|
||||||
|
|||||||
1552
src/options.go
1552
src/options.go
File diff suppressed because it is too large
Load Diff
@@ -3,9 +3,11 @@
|
|||||||
|
|
||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
func (o *Options) initProfiling() error {
|
func (o *Options) initProfiling() error {
|
||||||
if o.CPUProfile != "" || o.MEMProfile != "" || o.BlockProfile != "" || o.MutexProfile != "" {
|
if o.CPUProfile != "" || o.MEMProfile != "" || o.BlockProfile != "" || o.MutexProfile != "" {
|
||||||
errorExit("error: profiling not supported: FZF must be built with '-tags=pprof' to enable profiling")
|
return errors.New("error: profiling not supported: FZF must be built with '-tags=pprof' to enable profiling")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ func TestDelimiterRegexRegexCaret(t *testing.T) {
|
|||||||
|
|
||||||
func TestSplitNth(t *testing.T) {
|
func TestSplitNth(t *testing.T) {
|
||||||
{
|
{
|
||||||
ranges := splitNth("..")
|
ranges, _ := splitNth("..")
|
||||||
if len(ranges) != 1 ||
|
if len(ranges) != 1 ||
|
||||||
ranges[0].begin != rangeEllipsis ||
|
ranges[0].begin != rangeEllipsis ||
|
||||||
ranges[0].end != rangeEllipsis {
|
ranges[0].end != rangeEllipsis {
|
||||||
@@ -88,7 +88,7 @@ func TestSplitNth(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
ranges := splitNth("..3,1..,2..3,4..-1,-3..-2,..,2,-2,2..-2,1..-1")
|
ranges, _ := splitNth("..3,1..,2..3,4..-1,-3..-2,..,2,-2,2..-2,1..-1")
|
||||||
if len(ranges) != 10 ||
|
if len(ranges) != 10 ||
|
||||||
ranges[0].begin != rangeEllipsis || ranges[0].end != 3 ||
|
ranges[0].begin != rangeEllipsis || ranges[0].end != 3 ||
|
||||||
ranges[1].begin != rangeEllipsis || ranges[1].end != rangeEllipsis ||
|
ranges[1].begin != rangeEllipsis || ranges[1].end != rangeEllipsis ||
|
||||||
@@ -106,10 +106,11 @@ func TestSplitNth(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIrrelevantNth(t *testing.T) {
|
func TestIrrelevantNth(t *testing.T) {
|
||||||
|
index := 0
|
||||||
{
|
{
|
||||||
opts := defaultOptions()
|
opts := defaultOptions()
|
||||||
words := []string{"--nth", "..", "-x"}
|
words := []string{"--nth", "..", "-x"}
|
||||||
parseOptions(opts, words)
|
parseOptions(&index, opts, words)
|
||||||
postProcessOptions(opts)
|
postProcessOptions(opts)
|
||||||
if len(opts.Nth) != 0 {
|
if len(opts.Nth) != 0 {
|
||||||
t.Errorf("nth should be empty: %v", opts.Nth)
|
t.Errorf("nth should be empty: %v", opts.Nth)
|
||||||
@@ -118,7 +119,7 @@ func TestIrrelevantNth(t *testing.T) {
|
|||||||
for _, words := range [][]string{{"--nth", "..,3", "+x"}, {"--nth", "3,1..", "+x"}, {"--nth", "..-1,1", "+x"}} {
|
for _, words := range [][]string{{"--nth", "..,3", "+x"}, {"--nth", "3,1..", "+x"}, {"--nth", "..-1,1", "+x"}} {
|
||||||
{
|
{
|
||||||
opts := defaultOptions()
|
opts := defaultOptions()
|
||||||
parseOptions(opts, words)
|
parseOptions(&index, opts, words)
|
||||||
postProcessOptions(opts)
|
postProcessOptions(opts)
|
||||||
if len(opts.Nth) != 0 {
|
if len(opts.Nth) != 0 {
|
||||||
t.Errorf("nth should be empty: %v", opts.Nth)
|
t.Errorf("nth should be empty: %v", opts.Nth)
|
||||||
@@ -127,7 +128,7 @@ func TestIrrelevantNth(t *testing.T) {
|
|||||||
{
|
{
|
||||||
opts := defaultOptions()
|
opts := defaultOptions()
|
||||||
words = append(words, "-x")
|
words = append(words, "-x")
|
||||||
parseOptions(opts, words)
|
parseOptions(&index, opts, words)
|
||||||
postProcessOptions(opts)
|
postProcessOptions(opts)
|
||||||
if len(opts.Nth) != 2 {
|
if len(opts.Nth) != 2 {
|
||||||
t.Errorf("nth should not be empty: %v", opts.Nth)
|
t.Errorf("nth should not be empty: %v", opts.Nth)
|
||||||
@@ -137,7 +138,7 @@ func TestIrrelevantNth(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseKeys(t *testing.T) {
|
func TestParseKeys(t *testing.T) {
|
||||||
pairs := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ctrl-alt-a,ALT-enter,alt-SPACE", "")
|
pairs, _ := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ctrl-alt-a,ALT-enter,alt-SPACE", "")
|
||||||
checkEvent := func(e tui.Event, s string) {
|
checkEvent := func(e tui.Event, s string) {
|
||||||
if pairs[e] != s {
|
if pairs[e] != s {
|
||||||
t.Errorf("%s != %s", pairs[e], s)
|
t.Errorf("%s != %s", pairs[e], s)
|
||||||
@@ -163,7 +164,7 @@ func TestParseKeys(t *testing.T) {
|
|||||||
checkEvent(tui.AltKey(' '), "alt-SPACE")
|
checkEvent(tui.AltKey(' '), "alt-SPACE")
|
||||||
|
|
||||||
// Synonyms
|
// Synonyms
|
||||||
pairs = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
|
pairs, _ = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
|
||||||
if len(pairs) != 9 {
|
if len(pairs) != 9 {
|
||||||
t.Error(9)
|
t.Error(9)
|
||||||
}
|
}
|
||||||
@@ -177,7 +178,7 @@ func TestParseKeys(t *testing.T) {
|
|||||||
check(tui.Left, "left")
|
check(tui.Left, "left")
|
||||||
check(tui.Right, "right")
|
check(tui.Right, "right")
|
||||||
|
|
||||||
pairs = parseKeyChords("Tab,Ctrl-I,PgUp,page-up,pgdn,Page-Down,Home,End,Alt-BS,Alt-BSpace,shift-left,shift-right,btab,shift-tab,return,Enter,bspace", "")
|
pairs, _ = parseKeyChords("Tab,Ctrl-I,PgUp,page-up,pgdn,Page-Down,Home,End,Alt-BS,Alt-BSpace,shift-left,shift-right,btab,shift-tab,return,Enter,bspace", "")
|
||||||
if len(pairs) != 11 {
|
if len(pairs) != 11 {
|
||||||
t.Error(11)
|
t.Error(11)
|
||||||
}
|
}
|
||||||
@@ -206,40 +207,40 @@ func TestParseKeysWithComma(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pairs := parseKeyChords(",", "")
|
pairs, _ := parseKeyChords(",", "")
|
||||||
checkN(len(pairs), 1)
|
checkN(len(pairs), 1)
|
||||||
check(pairs, tui.Key(','), ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs = parseKeyChords(",,a,b", "")
|
pairs, _ = parseKeyChords(",,a,b", "")
|
||||||
checkN(len(pairs), 3)
|
checkN(len(pairs), 3)
|
||||||
check(pairs, tui.Key('a'), "a")
|
check(pairs, tui.Key('a'), "a")
|
||||||
check(pairs, tui.Key('b'), "b")
|
check(pairs, tui.Key('b'), "b")
|
||||||
check(pairs, tui.Key(','), ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs = parseKeyChords("a,b,,", "")
|
pairs, _ = parseKeyChords("a,b,,", "")
|
||||||
checkN(len(pairs), 3)
|
checkN(len(pairs), 3)
|
||||||
check(pairs, tui.Key('a'), "a")
|
check(pairs, tui.Key('a'), "a")
|
||||||
check(pairs, tui.Key('b'), "b")
|
check(pairs, tui.Key('b'), "b")
|
||||||
check(pairs, tui.Key(','), ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs = parseKeyChords("a,,,b", "")
|
pairs, _ = parseKeyChords("a,,,b", "")
|
||||||
checkN(len(pairs), 3)
|
checkN(len(pairs), 3)
|
||||||
check(pairs, tui.Key('a'), "a")
|
check(pairs, tui.Key('a'), "a")
|
||||||
check(pairs, tui.Key('b'), "b")
|
check(pairs, tui.Key('b'), "b")
|
||||||
check(pairs, tui.Key(','), ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs = parseKeyChords("a,,,b,c", "")
|
pairs, _ = parseKeyChords("a,,,b,c", "")
|
||||||
checkN(len(pairs), 4)
|
checkN(len(pairs), 4)
|
||||||
check(pairs, tui.Key('a'), "a")
|
check(pairs, tui.Key('a'), "a")
|
||||||
check(pairs, tui.Key('b'), "b")
|
check(pairs, tui.Key('b'), "b")
|
||||||
check(pairs, tui.Key('c'), "c")
|
check(pairs, tui.Key('c'), "c")
|
||||||
check(pairs, tui.Key(','), ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs = parseKeyChords(",,,", "")
|
pairs, _ = parseKeyChords(",,,", "")
|
||||||
checkN(len(pairs), 1)
|
checkN(len(pairs), 1)
|
||||||
check(pairs, tui.Key(','), ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs = parseKeyChords(",ALT-,,", "")
|
pairs, _ = parseKeyChords(",ALT-,,", "")
|
||||||
checkN(len(pairs), 1)
|
checkN(len(pairs), 1)
|
||||||
check(pairs, tui.AltKey(','), "ALT-,")
|
check(pairs, tui.AltKey(','), "ALT-,")
|
||||||
}
|
}
|
||||||
@@ -262,17 +263,13 @@ func TestBind(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
check(tui.CtrlA.AsEvent(), "", actBeginningOfLine)
|
check(tui.CtrlA.AsEvent(), "", actBeginningOfLine)
|
||||||
errorString := ""
|
|
||||||
errorFn := func(e string) {
|
|
||||||
errorString = e
|
|
||||||
}
|
|
||||||
parseKeymap(keymap,
|
parseKeymap(keymap,
|
||||||
"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+
|
"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+
|
||||||
"f1:execute(ls {+})+abort+execute(echo \n{+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
|
"f1:execute(ls {+})+abort+execute(echo \n{+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
|
||||||
"alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+
|
"alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+
|
||||||
"x:Execute(foo+bar),X:execute/bar+baz/"+
|
"x:Execute(foo+bar),X:execute/bar+baz/"+
|
||||||
",f1:+first,f1:+top"+
|
",f1:+first,f1:+top"+
|
||||||
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up", errorFn)
|
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up")
|
||||||
check(tui.CtrlA.AsEvent(), "", actKillLine)
|
check(tui.CtrlA.AsEvent(), "", actKillLine)
|
||||||
check(tui.CtrlB.AsEvent(), "", actToggleSort, actUp, actDown)
|
check(tui.CtrlB.AsEvent(), "", actToggleSort, actUp, actDown)
|
||||||
check(tui.Key('c'), "", actPageUp)
|
check(tui.Key('c'), "", actPageUp)
|
||||||
@@ -290,20 +287,17 @@ func TestBind(t *testing.T) {
|
|||||||
check(tui.Key('+'), "++\nfoobar,Y:execute(baz)+up", actExecute)
|
check(tui.Key('+'), "++\nfoobar,Y:execute(baz)+up", actExecute)
|
||||||
|
|
||||||
for idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} {
|
for idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} {
|
||||||
parseKeymap(keymap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char), errorFn)
|
parseKeymap(keymap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char))
|
||||||
check(tui.Key([]rune(fmt.Sprintf("%d", idx%10))[0]), "foobar", actExecute)
|
check(tui.Key([]rune(fmt.Sprintf("%d", idx%10))[0]), "foobar", actExecute)
|
||||||
}
|
}
|
||||||
|
|
||||||
parseKeymap(keymap, "f1:abort", errorFn)
|
parseKeymap(keymap, "f1:abort")
|
||||||
check(tui.F1.AsEvent(), "", actAbort)
|
check(tui.F1.AsEvent(), "", actAbort)
|
||||||
if len(errorString) > 0 {
|
|
||||||
t.Errorf("error parsing keymap: %s", errorString)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestColorSpec(t *testing.T) {
|
func TestColorSpec(t *testing.T) {
|
||||||
theme := tui.Dark256
|
theme := tui.Dark256
|
||||||
dark := parseTheme(theme, "dark")
|
dark, _ := parseTheme(theme, "dark")
|
||||||
if *dark != *theme {
|
if *dark != *theme {
|
||||||
t.Errorf("colors should be equivalent")
|
t.Errorf("colors should be equivalent")
|
||||||
}
|
}
|
||||||
@@ -311,7 +305,7 @@ func TestColorSpec(t *testing.T) {
|
|||||||
t.Errorf("point should not be equivalent")
|
t.Errorf("point should not be equivalent")
|
||||||
}
|
}
|
||||||
|
|
||||||
light := parseTheme(theme, "dark,light")
|
light, _ := parseTheme(theme, "dark,light")
|
||||||
if *light == *theme {
|
if *light == *theme {
|
||||||
t.Errorf("should not be equivalent")
|
t.Errorf("should not be equivalent")
|
||||||
}
|
}
|
||||||
@@ -322,7 +316,7 @@ func TestColorSpec(t *testing.T) {
|
|||||||
t.Errorf("point should not be equivalent")
|
t.Errorf("point should not be equivalent")
|
||||||
}
|
}
|
||||||
|
|
||||||
customized := parseTheme(theme, "fg:231,bg:232")
|
customized, _ := parseTheme(theme, "fg:231,bg:232")
|
||||||
if customized.Fg.Color != 231 || customized.Bg.Color != 232 {
|
if customized.Fg.Color != 231 || customized.Bg.Color != 232 {
|
||||||
t.Errorf("color not customized")
|
t.Errorf("color not customized")
|
||||||
}
|
}
|
||||||
@@ -335,17 +329,18 @@ func TestColorSpec(t *testing.T) {
|
|||||||
t.Errorf("colors should now be equivalent: %v, %v", tui.Dark256, customized)
|
t.Errorf("colors should now be equivalent: %v, %v", tui.Dark256, customized)
|
||||||
}
|
}
|
||||||
|
|
||||||
customized = parseTheme(theme, "fg:231,dark,bg:232")
|
customized, _ = parseTheme(theme, "fg:231,dark,bg:232")
|
||||||
if customized.Fg != tui.Dark256.Fg || customized.Bg == tui.Dark256.Bg {
|
if customized.Fg != tui.Dark256.Fg || customized.Bg == tui.Dark256.Bg {
|
||||||
t.Errorf("color not customized")
|
t.Errorf("color not customized")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultCtrlNP(t *testing.T) {
|
func TestDefaultCtrlNP(t *testing.T) {
|
||||||
|
index := 0
|
||||||
check := func(words []string, et tui.EventType, expected actionType) {
|
check := func(words []string, et tui.EventType, expected actionType) {
|
||||||
e := et.AsEvent()
|
e := et.AsEvent()
|
||||||
opts := defaultOptions()
|
opts := defaultOptions()
|
||||||
parseOptions(opts, words)
|
parseOptions(&index, opts, words)
|
||||||
postProcessOptions(opts)
|
postProcessOptions(opts)
|
||||||
if opts.Keymap[e][0].t != expected {
|
if opts.Keymap[e][0].t != expected {
|
||||||
t.Error()
|
t.Error()
|
||||||
@@ -371,8 +366,9 @@ func TestDefaultCtrlNP(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func optsFor(words ...string) *Options {
|
func optsFor(words ...string) *Options {
|
||||||
|
index := 0
|
||||||
opts := defaultOptions()
|
opts := defaultOptions()
|
||||||
parseOptions(opts, words)
|
parseOptions(&index, opts, words)
|
||||||
postProcessOptions(opts)
|
postProcessOptions(opts)
|
||||||
return opts
|
return opts
|
||||||
}
|
}
|
||||||
@@ -475,7 +471,7 @@ func TestValidateSign(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseSingleActionList(t *testing.T) {
|
func TestParseSingleActionList(t *testing.T) {
|
||||||
actions := parseSingleActionList("Execute@foo+bar,baz@+up+up+reload:down+down", func(string) {})
|
actions, _ := parseSingleActionList("Execute@foo+bar,baz@+up+up+reload:down+down")
|
||||||
if len(actions) != 4 {
|
if len(actions) != 4 {
|
||||||
t.Errorf("Invalid number of actions parsed:%d", len(actions))
|
t.Errorf("Invalid number of actions parsed:%d", len(actions))
|
||||||
}
|
}
|
||||||
@@ -491,11 +487,8 @@ func TestParseSingleActionList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseSingleActionListError(t *testing.T) {
|
func TestParseSingleActionListError(t *testing.T) {
|
||||||
err := ""
|
_, err := parseSingleActionList("change-query(foobar)baz")
|
||||||
parseSingleActionList("change-query(foobar)baz", func(e string) {
|
if err == nil {
|
||||||
err = e
|
|
||||||
})
|
|
||||||
if len(err) == 0 {
|
|
||||||
t.Errorf("Failed to detect error")
|
t.Errorf("Failed to detect error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,32 +60,17 @@ type Pattern struct {
|
|||||||
delimiter Delimiter
|
delimiter Delimiter
|
||||||
nth []Range
|
nth []Range
|
||||||
procFun map[termType]algo.Algo
|
procFun map[termType]algo.Algo
|
||||||
|
cache *ChunkCache
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var _splitRegex *regexp.Regexp
|
||||||
_patternCache map[string]*Pattern
|
|
||||||
_splitRegex *regexp.Regexp
|
|
||||||
_cache ChunkCache
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
_splitRegex = regexp.MustCompile(" +")
|
_splitRegex = regexp.MustCompile(" +")
|
||||||
clearPatternCache()
|
|
||||||
clearChunkCache()
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearPatternCache() {
|
|
||||||
// We can uniquely identify the pattern for a given string since
|
|
||||||
// search mode and caseMode do not change while the program is running
|
|
||||||
_patternCache = make(map[string]*Pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearChunkCache() {
|
|
||||||
_cache = NewChunkCache()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildPattern builds Pattern object from the given arguments
|
// BuildPattern builds Pattern object from the given arguments
|
||||||
func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool,
|
func BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool,
|
||||||
withPos bool, cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
|
withPos bool, cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
|
||||||
|
|
||||||
var asString string
|
var asString string
|
||||||
@@ -98,7 +83,9 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
|||||||
asString = string(runes)
|
asString = string(runes)
|
||||||
}
|
}
|
||||||
|
|
||||||
cached, found := _patternCache[asString]
|
// We can uniquely identify the pattern for a given string since
|
||||||
|
// search mode and caseMode do not change while the program is running
|
||||||
|
cached, found := patternCache[asString]
|
||||||
if found {
|
if found {
|
||||||
return cached
|
return cached
|
||||||
}
|
}
|
||||||
@@ -153,6 +140,7 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
|||||||
cacheable: cacheable,
|
cacheable: cacheable,
|
||||||
nth: nth,
|
nth: nth,
|
||||||
delimiter: delimiter,
|
delimiter: delimiter,
|
||||||
|
cache: cache,
|
||||||
procFun: make(map[termType]algo.Algo)}
|
procFun: make(map[termType]algo.Algo)}
|
||||||
|
|
||||||
ptr.cacheKey = ptr.buildCacheKey()
|
ptr.cacheKey = ptr.buildCacheKey()
|
||||||
@@ -162,19 +150,19 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
|||||||
ptr.procFun[termPrefix] = algo.PrefixMatch
|
ptr.procFun[termPrefix] = algo.PrefixMatch
|
||||||
ptr.procFun[termSuffix] = algo.SuffixMatch
|
ptr.procFun[termSuffix] = algo.SuffixMatch
|
||||||
|
|
||||||
_patternCache[asString] = ptr
|
patternCache[asString] = ptr
|
||||||
return ptr
|
return ptr
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet {
|
func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet {
|
||||||
str = strings.Replace(str, "\\ ", "\t", -1)
|
str = strings.ReplaceAll(str, "\\ ", "\t")
|
||||||
tokens := _splitRegex.Split(str, -1)
|
tokens := _splitRegex.Split(str, -1)
|
||||||
sets := []termSet{}
|
sets := []termSet{}
|
||||||
set := termSet{}
|
set := termSet{}
|
||||||
switchSet := false
|
switchSet := false
|
||||||
afterBar := false
|
afterBar := false
|
||||||
for _, token := range tokens {
|
for _, token := range tokens {
|
||||||
typ, inv, text := termFuzzy, false, strings.Replace(token, "\t", " ", -1)
|
typ, inv, text := termFuzzy, false, strings.ReplaceAll(token, "\t", " ")
|
||||||
lowerText := strings.ToLower(text)
|
lowerText := strings.ToLower(text)
|
||||||
caseSensitive := caseMode == CaseRespect ||
|
caseSensitive := caseMode == CaseRespect ||
|
||||||
caseMode == CaseSmart && text != lowerText
|
caseMode == CaseSmart && text != lowerText
|
||||||
@@ -282,18 +270,18 @@ func (p *Pattern) Match(chunk *Chunk, slab *util.Slab) []Result {
|
|||||||
// ChunkCache: Exact match
|
// ChunkCache: Exact match
|
||||||
cacheKey := p.CacheKey()
|
cacheKey := p.CacheKey()
|
||||||
if p.cacheable {
|
if p.cacheable {
|
||||||
if cached := _cache.Lookup(chunk, cacheKey); cached != nil {
|
if cached := p.cache.Lookup(chunk, cacheKey); cached != nil {
|
||||||
return cached
|
return cached
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prefix/suffix cache
|
// Prefix/suffix cache
|
||||||
space := _cache.Search(chunk, cacheKey)
|
space := p.cache.Search(chunk, cacheKey)
|
||||||
|
|
||||||
matches := p.matchChunk(chunk, space, slab)
|
matches := p.matchChunk(chunk, space, slab)
|
||||||
|
|
||||||
if p.cacheable {
|
if p.cacheable {
|
||||||
_cache.Add(chunk, cacheKey, matches)
|
p.cache.Add(chunk, cacheKey, matches)
|
||||||
}
|
}
|
||||||
return matches
|
return matches
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,10 +64,15 @@ func TestParseTermsEmpty(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool,
|
||||||
|
withPos bool, cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
|
||||||
|
return BuildPattern(NewChunkCache(), make(map[string]*Pattern),
|
||||||
|
fuzzy, fuzzyAlgo, extended, caseMode, normalize, forward,
|
||||||
|
withPos, cacheable, nth, delimiter, runes)
|
||||||
|
}
|
||||||
|
|
||||||
func TestExact(t *testing.T) {
|
func TestExact(t *testing.T) {
|
||||||
defer clearPatternCache()
|
pattern := buildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true,
|
||||||
clearPatternCache()
|
|
||||||
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true,
|
|
||||||
[]Range{}, Delimiter{}, []rune("'abc"))
|
[]Range{}, Delimiter{}, []rune("'abc"))
|
||||||
chars := util.ToChars([]byte("aabbcc abc"))
|
chars := util.ToChars([]byte("aabbcc abc"))
|
||||||
res, pos := algo.ExactMatchNaive(
|
res, pos := algo.ExactMatchNaive(
|
||||||
@@ -81,9 +86,7 @@ func TestExact(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEqual(t *testing.T) {
|
func TestEqual(t *testing.T) {
|
||||||
defer clearPatternCache()
|
pattern := buildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("^AbC$"))
|
||||||
clearPatternCache()
|
|
||||||
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("^AbC$"))
|
|
||||||
|
|
||||||
match := func(str string, sidxExpected int, eidxExpected int) {
|
match := func(str string, sidxExpected int, eidxExpected int) {
|
||||||
chars := util.ToChars([]byte(str))
|
chars := util.ToChars([]byte(str))
|
||||||
@@ -104,19 +107,12 @@ func TestEqual(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCaseSensitivity(t *testing.T) {
|
func TestCaseSensitivity(t *testing.T) {
|
||||||
defer clearPatternCache()
|
pat1 := buildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("abc"))
|
||||||
clearPatternCache()
|
pat2 := buildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc"))
|
||||||
pat1 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("abc"))
|
pat3 := buildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, false, true, []Range{}, Delimiter{}, []rune("abc"))
|
||||||
clearPatternCache()
|
pat4 := buildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc"))
|
||||||
pat2 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc"))
|
pat5 := buildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, false, true, []Range{}, Delimiter{}, []rune("abc"))
|
||||||
clearPatternCache()
|
pat6 := buildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc"))
|
||||||
pat3 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, false, true, []Range{}, Delimiter{}, []rune("abc"))
|
|
||||||
clearPatternCache()
|
|
||||||
pat4 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc"))
|
|
||||||
clearPatternCache()
|
|
||||||
pat5 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, false, true, []Range{}, Delimiter{}, []rune("abc"))
|
|
||||||
clearPatternCache()
|
|
||||||
pat6 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc"))
|
|
||||||
|
|
||||||
if string(pat1.text) != "abc" || pat1.caseSensitive != false ||
|
if string(pat1.text) != "abc" || pat1.caseSensitive != false ||
|
||||||
string(pat2.text) != "Abc" || pat2.caseSensitive != true ||
|
string(pat2.text) != "Abc" || pat2.caseSensitive != true ||
|
||||||
@@ -129,7 +125,7 @@ func TestCaseSensitivity(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOrigTextAndTransformed(t *testing.T) {
|
func TestOrigTextAndTransformed(t *testing.T) {
|
||||||
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("jg"))
|
pattern := buildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("jg"))
|
||||||
tokens := Tokenize("junegunn", Delimiter{})
|
tokens := Tokenize("junegunn", Delimiter{})
|
||||||
trans := Transform(tokens, []Range{{1, 1}})
|
trans := Transform(tokens, []Range{{1, 1}})
|
||||||
|
|
||||||
@@ -163,15 +159,13 @@ func TestOrigTextAndTransformed(t *testing.T) {
|
|||||||
|
|
||||||
func TestCacheKey(t *testing.T) {
|
func TestCacheKey(t *testing.T) {
|
||||||
test := func(extended bool, patStr string, expected string, cacheable bool) {
|
test := func(extended bool, patStr string, expected string, cacheable bool) {
|
||||||
clearPatternCache()
|
pat := buildPattern(true, algo.FuzzyMatchV2, extended, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune(patStr))
|
||||||
pat := BuildPattern(true, algo.FuzzyMatchV2, extended, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune(patStr))
|
|
||||||
if pat.CacheKey() != expected {
|
if pat.CacheKey() != expected {
|
||||||
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
|
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
|
||||||
}
|
}
|
||||||
if pat.cacheable != cacheable {
|
if pat.cacheable != cacheable {
|
||||||
t.Errorf("Expected: %t, actual: %t (%s)", cacheable, pat.cacheable, patStr)
|
t.Errorf("Expected: %t, actual: %t (%s)", cacheable, pat.cacheable, patStr)
|
||||||
}
|
}
|
||||||
clearPatternCache()
|
|
||||||
}
|
}
|
||||||
test(false, "foo !bar", "foo !bar", true)
|
test(false, "foo !bar", "foo !bar", true)
|
||||||
test(false, "foo | bar !baz", "foo | bar !baz", true)
|
test(false, "foo | bar !baz", "foo | bar !baz", true)
|
||||||
@@ -187,15 +181,13 @@ func TestCacheKey(t *testing.T) {
|
|||||||
|
|
||||||
func TestCacheable(t *testing.T) {
|
func TestCacheable(t *testing.T) {
|
||||||
test := func(fuzzy bool, str string, expected string, cacheable bool) {
|
test := func(fuzzy bool, str string, expected string, cacheable bool) {
|
||||||
clearPatternCache()
|
pat := buildPattern(fuzzy, algo.FuzzyMatchV2, true, CaseSmart, true, true, false, true, []Range{}, Delimiter{}, []rune(str))
|
||||||
pat := BuildPattern(fuzzy, algo.FuzzyMatchV2, true, CaseSmart, true, true, false, true, []Range{}, Delimiter{}, []rune(str))
|
|
||||||
if pat.CacheKey() != expected {
|
if pat.CacheKey() != expected {
|
||||||
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
|
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
|
||||||
}
|
}
|
||||||
if cacheable != pat.cacheable {
|
if cacheable != pat.cacheable {
|
||||||
t.Errorf("Invalid Pattern.cacheable for \"%s\": %v (expected: %v)", str, pat.cacheable, cacheable)
|
t.Errorf("Invalid Pattern.cacheable for \"%s\": %v (expected: %v)", str, pat.cacheable, cacheable)
|
||||||
}
|
}
|
||||||
clearPatternCache()
|
|
||||||
}
|
}
|
||||||
test(true, "foo bar", "foo\tbar", true)
|
test(true, "foo bar", "foo\tbar", true)
|
||||||
test(true, "foo 'bar", "foo\tbar", false)
|
test(true, "foo 'bar", "foo\tbar", false)
|
||||||
|
|||||||
@@ -3,6 +3,4 @@
|
|||||||
package protector
|
package protector
|
||||||
|
|
||||||
// Protect calls OS specific protections like pledge on OpenBSD
|
// Protect calls OS specific protections like pledge on OpenBSD
|
||||||
func Protect() {
|
func Protect() {}
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|||||||
146
src/proxy.go
Normal file
146
src/proxy.go
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
package fzf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/tui"
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const becomeSuffix = ".become"
|
||||||
|
|
||||||
|
func escapeSingleQuote(str string) string {
|
||||||
|
return "'" + strings.ReplaceAll(str, "'", "'\\''") + "'"
|
||||||
|
}
|
||||||
|
|
||||||
|
func fifo(name string) (string, error) {
|
||||||
|
ns := time.Now().UnixNano()
|
||||||
|
output := filepath.Join(os.TempDir(), fmt.Sprintf("fzf-%s-%d", name, ns))
|
||||||
|
output, err := mkfifo(output, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return output, err
|
||||||
|
}
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runProxy(commandPrefix string, cmdBuilder func(temp string) *exec.Cmd, opts *Options, withExports bool) (int, error) {
|
||||||
|
output, err := fifo("proxy-output")
|
||||||
|
if err != nil {
|
||||||
|
return ExitError, err
|
||||||
|
}
|
||||||
|
defer os.Remove(output)
|
||||||
|
|
||||||
|
// Take the output
|
||||||
|
go func() {
|
||||||
|
withOutputPipe(output, func(outputFile io.ReadCloser) {
|
||||||
|
if opts.Output == nil {
|
||||||
|
io.Copy(os.Stdout, outputFile)
|
||||||
|
} else {
|
||||||
|
reader := bufio.NewReader(outputFile)
|
||||||
|
sep := opts.PrintSep[0]
|
||||||
|
for {
|
||||||
|
item, err := reader.ReadString(sep)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
opts.Output <- item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
|
var command string
|
||||||
|
commandPrefix += ` --no-force-tty-in --proxy-script "$0"`
|
||||||
|
if opts.Input == nil && (opts.ForceTtyIn || util.IsTty(os.Stdin)) {
|
||||||
|
command = fmt.Sprintf(`%s > %q`, commandPrefix, output)
|
||||||
|
} else {
|
||||||
|
input, err := fifo("proxy-input")
|
||||||
|
if err != nil {
|
||||||
|
return ExitError, err
|
||||||
|
}
|
||||||
|
defer os.Remove(input)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
withInputPipe(input, func(inputFile io.WriteCloser) {
|
||||||
|
if opts.Input == nil {
|
||||||
|
io.Copy(inputFile, os.Stdin)
|
||||||
|
} else {
|
||||||
|
for item := range opts.Input {
|
||||||
|
fmt.Fprint(inputFile, item+opts.PrintSep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
|
||||||
|
if withExports {
|
||||||
|
command = fmt.Sprintf(`%s < %q > %q`, commandPrefix, input, output)
|
||||||
|
} else {
|
||||||
|
// For mintty: cannot directly read named pipe from Go code
|
||||||
|
command = fmt.Sprintf(`command cat %q | %s > %q`, input, commandPrefix, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// To ensure that the options are processed by a POSIX-compliant shell,
|
||||||
|
// we need to write the command to a temporary file and execute it with sh.
|
||||||
|
var exports []string
|
||||||
|
if withExports {
|
||||||
|
exports = os.Environ()
|
||||||
|
for idx, pairStr := range exports {
|
||||||
|
pair := strings.SplitN(pairStr, "=", 2)
|
||||||
|
exports[idx] = fmt.Sprintf("export %s=%s", pair[0], escapeSingleQuote(pair[1]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
temp := WriteTemporaryFile(append(exports, command), "\n")
|
||||||
|
defer os.Remove(temp)
|
||||||
|
|
||||||
|
cmd := cmdBuilder(temp)
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
intChan := make(chan os.Signal, 1)
|
||||||
|
defer close(intChan)
|
||||||
|
go func() {
|
||||||
|
if sig, valid := <-intChan; valid {
|
||||||
|
cmd.Process.Signal(sig)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
signal.Notify(intChan, os.Interrupt)
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
if exitError, ok := err.(*exec.ExitError); ok {
|
||||||
|
code := exitError.ExitCode()
|
||||||
|
if code == ExitBecome {
|
||||||
|
becomeFile := temp + becomeSuffix
|
||||||
|
data, err := os.ReadFile(becomeFile)
|
||||||
|
os.Remove(becomeFile)
|
||||||
|
if err != nil {
|
||||||
|
return ExitError, err
|
||||||
|
}
|
||||||
|
elems := strings.Split(string(data), "\x00")
|
||||||
|
if len(elems) < 1 {
|
||||||
|
return ExitError, errors.New("invalid become command")
|
||||||
|
}
|
||||||
|
command := elems[0]
|
||||||
|
env := []string{}
|
||||||
|
if len(elems) > 1 {
|
||||||
|
env = elems[1:]
|
||||||
|
}
|
||||||
|
executor := util.NewExecutor(opts.WithShell)
|
||||||
|
ttyin, err := tui.TtyIn()
|
||||||
|
if err != nil {
|
||||||
|
return ExitError, err
|
||||||
|
}
|
||||||
|
executor.Become(ttyin, env, command)
|
||||||
|
}
|
||||||
|
return code, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExitOk, nil
|
||||||
|
}
|
||||||
38
src/proxy_unix.go
Normal file
38
src/proxy_unix.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package fzf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func sh() (string, error) {
|
||||||
|
return "sh", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mkfifo(path string, mode uint32) (string, error) {
|
||||||
|
return path, unix.Mkfifo(path, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func withOutputPipe(output string, task func(io.ReadCloser)) error {
|
||||||
|
outputFile, err := os.OpenFile(output, os.O_RDONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
task(outputFile)
|
||||||
|
outputFile.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func withInputPipe(input string, task func(io.WriteCloser)) error {
|
||||||
|
inputFile, err := os.OpenFile(input, os.O_WRONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
task(inputFile)
|
||||||
|
inputFile.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
81
src/proxy_windows.go
Normal file
81
src/proxy_windows.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package fzf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
var shPath atomic.Value
|
||||||
|
|
||||||
|
func sh() (string, error) {
|
||||||
|
if cached := shPath.Load(); cached != nil {
|
||||||
|
return cached.(string), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command("cygpath", "-w", "/usr/bin/sh")
|
||||||
|
bytes, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
sh := strings.TrimSpace(string(bytes))
|
||||||
|
shPath.Store(sh)
|
||||||
|
return sh, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mkfifo(path string, mode uint32) (string, error) {
|
||||||
|
m := strconv.FormatUint(uint64(mode), 8)
|
||||||
|
sh, err := sh()
|
||||||
|
if err != nil {
|
||||||
|
return path, err
|
||||||
|
}
|
||||||
|
cmd := exec.Command(sh, "-c", fmt.Sprintf(`command mkfifo -m %s %q`, m, path))
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return path, err
|
||||||
|
}
|
||||||
|
return path + ".lnk", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func withOutputPipe(output string, task func(io.ReadCloser)) error {
|
||||||
|
sh, err := sh()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd := exec.Command(sh, "-c", fmt.Sprintf(`command cat %q`, output))
|
||||||
|
outputFile, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
task(outputFile)
|
||||||
|
cmd.Wait()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func withInputPipe(input string, task func(io.WriteCloser)) error {
|
||||||
|
sh, err := sh()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd := exec.Command(sh, "-c", fmt.Sprintf(`command cat - > %q`, input))
|
||||||
|
inputFile, err := cmd.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
task(inputFile)
|
||||||
|
inputFile.Close()
|
||||||
|
cmd.Wait()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@ type Reader struct {
|
|||||||
finChan chan bool
|
finChan chan bool
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
exec *exec.Cmd
|
exec *exec.Cmd
|
||||||
|
execOut io.ReadCloser
|
||||||
command *string
|
command *string
|
||||||
killed bool
|
killed bool
|
||||||
wait bool
|
wait bool
|
||||||
@@ -32,7 +33,7 @@ type Reader struct {
|
|||||||
|
|
||||||
// NewReader returns new Reader object
|
// NewReader returns new Reader object
|
||||||
func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, executor *util.Executor, delimNil bool, wait bool) *Reader {
|
func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, executor *util.Executor, delimNil bool, wait bool) *Reader {
|
||||||
return &Reader{pusher, executor, eventBox, delimNil, int32(EvtReady), make(chan bool, 1), sync.Mutex{}, nil, nil, false, wait}
|
return &Reader{pusher, executor, eventBox, delimNil, int32(EvtReady), make(chan bool, 1), sync.Mutex{}, nil, nil, nil, false, wait}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) startEventPoller() {
|
func (r *Reader) startEventPoller() {
|
||||||
@@ -79,6 +80,7 @@ func (r *Reader) terminate() {
|
|||||||
r.mutex.Lock()
|
r.mutex.Lock()
|
||||||
r.killed = true
|
r.killed = true
|
||||||
if r.exec != nil && r.exec.Process != nil {
|
if r.exec != nil && r.exec.Process != nil {
|
||||||
|
r.execOut.Close()
|
||||||
util.KillCommand(r.exec)
|
util.KillCommand(r.exec)
|
||||||
} else {
|
} else {
|
||||||
os.Stdin.Close()
|
os.Stdin.Close()
|
||||||
@@ -86,18 +88,34 @@ func (r *Reader) terminate() {
|
|||||||
r.mutex.Unlock()
|
r.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) restart(command string, environ []string) {
|
func (r *Reader) restart(command commandSpec, environ []string) {
|
||||||
r.event = int32(EvtReady)
|
r.event = int32(EvtReady)
|
||||||
r.startEventPoller()
|
r.startEventPoller()
|
||||||
success := r.readFromCommand(command, environ)
|
success := r.readFromCommand(command.command, environ)
|
||||||
r.fin(success)
|
r.fin(success)
|
||||||
|
removeFiles(command.tempFiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readChannel(inputChan chan string) bool {
|
||||||
|
for {
|
||||||
|
item, more := <-inputChan
|
||||||
|
if !more {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if r.pusher([]byte(item)) {
|
||||||
|
atomic.StoreInt32(&r.event, int32(EvtReadNew))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadSource reads data from the default command or from standard input
|
// ReadSource reads data from the default command or from standard input
|
||||||
func (r *Reader) ReadSource(root string, opts walkerOpts, ignores []string) {
|
func (r *Reader) ReadSource(inputChan chan string, root string, opts walkerOpts, ignores []string) {
|
||||||
r.startEventPoller()
|
r.startEventPoller()
|
||||||
var success bool
|
var success bool
|
||||||
if util.IsTty() {
|
if inputChan != nil {
|
||||||
|
success = r.readChannel(inputChan)
|
||||||
|
} else if util.IsTty(os.Stdin) {
|
||||||
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
||||||
if len(cmd) == 0 {
|
if len(cmd) == 0 {
|
||||||
success = r.readFiles(root, opts, ignores)
|
success = r.readFiles(root, opts, ignores)
|
||||||
@@ -247,16 +265,23 @@ func (r *Reader) readFromCommand(command string, environ []string) bool {
|
|||||||
if environ != nil {
|
if environ != nil {
|
||||||
r.exec.Env = environ
|
r.exec.Env = environ
|
||||||
}
|
}
|
||||||
out, err := r.exec.StdoutPipe()
|
|
||||||
|
var err error
|
||||||
|
r.execOut, err = r.exec.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
r.exec = nil
|
||||||
r.mutex.Unlock()
|
r.mutex.Unlock()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
err = r.exec.Start()
|
err = r.exec.Start()
|
||||||
r.mutex.Unlock()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
r.exec = nil
|
||||||
|
r.mutex.Unlock()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
r.feed(out)
|
|
||||||
|
r.mutex.Unlock()
|
||||||
|
r.feed(r.execOut)
|
||||||
return r.exec.Wait() == nil
|
return r.exec.Wait() == nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ type Offset [2]int32
|
|||||||
type colorOffset struct {
|
type colorOffset struct {
|
||||||
offset [2]int32
|
offset [2]int32
|
||||||
color tui.ColorPair
|
color tui.ColorPair
|
||||||
|
match bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result struct {
|
type Result struct {
|
||||||
@@ -109,7 +110,7 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
|
|||||||
if len(itemColors) == 0 {
|
if len(itemColors) == 0 {
|
||||||
var offsets []colorOffset
|
var offsets []colorOffset
|
||||||
for _, off := range matchOffsets {
|
for _, off := range matchOffsets {
|
||||||
offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: colMatch})
|
offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: colMatch, match: true})
|
||||||
}
|
}
|
||||||
return offsets
|
return offsets
|
||||||
}
|
}
|
||||||
@@ -193,12 +194,13 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
colors = append(colors, colorOffset{
|
colors = append(colors, colorOffset{
|
||||||
offset: [2]int32{int32(start), int32(idx)}, color: color})
|
offset: [2]int32{int32(start), int32(idx)}, color: color, match: true})
|
||||||
} else {
|
} else {
|
||||||
ansi := itemColors[curr-1]
|
ansi := itemColors[curr-1]
|
||||||
colors = append(colors, colorOffset{
|
colors = append(colors, colorOffset{
|
||||||
offset: [2]int32{int32(start), int32(idx)},
|
offset: [2]int32{int32(start), int32(idx)},
|
||||||
color: ansiToColorPair(ansi, colBase)})
|
color: ansiToColorPair(ansi, colBase),
|
||||||
|
match: false})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,28 +73,28 @@ func parseListenAddress(address string) (listenAddress, error) {
|
|||||||
return listenAddress{parts[0], port}, nil
|
return listenAddress{parts[0], port}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func startHttpServer(address listenAddress, actionChannel chan []*action, responseChannel chan string) (int, error) {
|
func startHttpServer(address listenAddress, actionChannel chan []*action, responseChannel chan string) (net.Listener, int, error) {
|
||||||
host := address.host
|
host := address.host
|
||||||
port := address.port
|
port := address.port
|
||||||
apiKey := os.Getenv("FZF_API_KEY")
|
apiKey := os.Getenv("FZF_API_KEY")
|
||||||
if !address.IsLocal() && len(apiKey) == 0 {
|
if !address.IsLocal() && len(apiKey) == 0 {
|
||||||
return port, errors.New("FZF_API_KEY is required to allow remote access")
|
return nil, port, errors.New("FZF_API_KEY is required to allow remote access")
|
||||||
}
|
}
|
||||||
addrStr := fmt.Sprintf("%s:%d", host, port)
|
addrStr := fmt.Sprintf("%s:%d", host, port)
|
||||||
listener, err := net.Listen("tcp", addrStr)
|
listener, err := net.Listen("tcp", addrStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return port, fmt.Errorf("failed to listen on %s", addrStr)
|
return nil, port, fmt.Errorf("failed to listen on %s", addrStr)
|
||||||
}
|
}
|
||||||
if port == 0 {
|
if port == 0 {
|
||||||
addr := listener.Addr().String()
|
addr := listener.Addr().String()
|
||||||
parts := strings.Split(addr, ":")
|
parts := strings.Split(addr, ":")
|
||||||
if len(parts) < 2 {
|
if len(parts) < 2 {
|
||||||
return port, fmt.Errorf("cannot extract port: %s", addr)
|
return nil, port, fmt.Errorf("cannot extract port: %s", addr)
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
port, err = strconv.Atoi(parts[len(parts)-1])
|
port, err = strconv.Atoi(parts[len(parts)-1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return port, err
|
return nil, port, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,18 +109,16 @@ func startHttpServer(address listenAddress, actionChannel chan []*action, respon
|
|||||||
conn, err := listener.Accept()
|
conn, err := listener.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, net.ErrClosed) {
|
if errors.Is(err, net.ErrClosed) {
|
||||||
break
|
return
|
||||||
} else {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
conn.Write([]byte(server.handleHttpRequest(conn)))
|
conn.Write([]byte(server.handleHttpRequest(conn)))
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}
|
}
|
||||||
listener.Close()
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return port, nil
|
return listener, port, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Here we are writing a simplistic HTTP server without using net/http
|
// Here we are writing a simplistic HTTP server without using net/http
|
||||||
@@ -217,12 +215,9 @@ func (server *httpServer) handleHttpRequest(conn net.Conn) string {
|
|||||||
}
|
}
|
||||||
body = body[:contentLength]
|
body = body[:contentLength]
|
||||||
|
|
||||||
errorMessage := ""
|
actions, err := parseSingleActionList(strings.Trim(string(body), "\r\n"))
|
||||||
actions := parseSingleActionList(strings.Trim(string(body), "\r\n"), func(message string) {
|
if err != nil {
|
||||||
errorMessage = message
|
return bad(err.Error())
|
||||||
})
|
|
||||||
if len(errorMessage) > 0 {
|
|
||||||
return bad(errorMessage)
|
|
||||||
}
|
}
|
||||||
if len(actions) == 0 {
|
if len(actions) == 0 {
|
||||||
return bad("no action specified")
|
return bad("no action specified")
|
||||||
|
|||||||
1073
src/terminal.go
1073
src/terminal.go
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func replacePlaceholderTest(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
|
func replacePlaceholderTest(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
|
||||||
return replacePlaceholder(replacePlaceholderParams{
|
replaced, _ := replacePlaceholder(replacePlaceholderParams{
|
||||||
template: template,
|
template: template,
|
||||||
stripAnsi: stripAnsi,
|
stripAnsi: stripAnsi,
|
||||||
delimiter: delimiter,
|
delimiter: delimiter,
|
||||||
@@ -25,6 +25,7 @@ func replacePlaceholderTest(template string, stripAnsi bool, delimiter Delimiter
|
|||||||
prompt: "prompt",
|
prompt: "prompt",
|
||||||
executor: util.NewExecutor(""),
|
executor: util.NewExecutor(""),
|
||||||
})
|
})
|
||||||
|
return replaced
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReplacePlaceholder(t *testing.T) {
|
func TestReplacePlaceholder(t *testing.T) {
|
||||||
|
|||||||
57
src/tmux.go
Normal file
57
src/tmux.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package fzf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/tui"
|
||||||
|
)
|
||||||
|
|
||||||
|
func runTmux(args []string, opts *Options) (int, error) {
|
||||||
|
// Prepare arguments
|
||||||
|
fzf := args[0]
|
||||||
|
args = append([]string{"--bind=ctrl-z:ignore"}, args[1:]...)
|
||||||
|
if opts.BorderShape == tui.BorderUndefined {
|
||||||
|
args = append(args, "--border")
|
||||||
|
}
|
||||||
|
argStr := escapeSingleQuote(fzf)
|
||||||
|
for _, arg := range args {
|
||||||
|
argStr += " " + escapeSingleQuote(arg)
|
||||||
|
}
|
||||||
|
argStr += ` --no-tmux --no-height`
|
||||||
|
|
||||||
|
// Get current directory
|
||||||
|
dir, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
dir = "."
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set tmux options for popup placement
|
||||||
|
// C Both The centre of the terminal
|
||||||
|
// R -x The right side of the terminal
|
||||||
|
// P Both The bottom left of the pane
|
||||||
|
// M Both The mouse position
|
||||||
|
// W Both The window position on the status line
|
||||||
|
// S -y The line above or below the status line
|
||||||
|
tmuxArgs := []string{"display-popup", "-E", "-B", "-d", dir}
|
||||||
|
switch opts.Tmux.position {
|
||||||
|
case posUp:
|
||||||
|
tmuxArgs = append(tmuxArgs, "-xC", "-y0")
|
||||||
|
case posDown:
|
||||||
|
tmuxArgs = append(tmuxArgs, "-xC", "-yS")
|
||||||
|
case posLeft:
|
||||||
|
tmuxArgs = append(tmuxArgs, "-x0", "-yC")
|
||||||
|
case posRight:
|
||||||
|
tmuxArgs = append(tmuxArgs, "-xR", "-yC")
|
||||||
|
case posCenter:
|
||||||
|
tmuxArgs = append(tmuxArgs, "-xC", "-yC")
|
||||||
|
}
|
||||||
|
tmuxArgs = append(tmuxArgs, "-w"+opts.Tmux.width.String())
|
||||||
|
tmuxArgs = append(tmuxArgs, "-h"+opts.Tmux.height.String())
|
||||||
|
|
||||||
|
return runProxy(argStr, func(temp string) *exec.Cmd {
|
||||||
|
sh, _ := sh()
|
||||||
|
tmuxArgs = append(tmuxArgs, sh, temp)
|
||||||
|
return exec.Command("tmux", tmuxArgs...)
|
||||||
|
}, opts, true)
|
||||||
|
}
|
||||||
@@ -91,7 +91,7 @@ func withPrefixLengths(tokens []string, begin int) []Token {
|
|||||||
|
|
||||||
prefixLength := begin
|
prefixLength := begin
|
||||||
for idx := range tokens {
|
for idx := range tokens {
|
||||||
chars := util.ToChars(sbytes(tokens[idx]))
|
chars := util.ToChars(stringBytes(tokens[idx]))
|
||||||
ret[idx] = Token{&chars, int32(prefixLength)}
|
ret[idx] = Token{&chars, int32(prefixLength)}
|
||||||
prefixLength += chars.Length()
|
prefixLength += chars.Length()
|
||||||
}
|
}
|
||||||
@@ -187,7 +187,7 @@ func Transform(tokens []Token, withNth []Range) []Token {
|
|||||||
if r.begin == r.end {
|
if r.begin == r.end {
|
||||||
idx := r.begin
|
idx := r.begin
|
||||||
if idx == rangeEllipsis {
|
if idx == rangeEllipsis {
|
||||||
chars := util.ToChars(sbytes(joinTokens(tokens)))
|
chars := util.ToChars(stringBytes(joinTokens(tokens)))
|
||||||
parts = append(parts, &chars)
|
parts = append(parts, &chars)
|
||||||
} else {
|
} else {
|
||||||
if idx < 0 {
|
if idx < 0 {
|
||||||
|
|||||||
@@ -71,14 +71,14 @@ func TestTransform(t *testing.T) {
|
|||||||
{
|
{
|
||||||
tokens := Tokenize(input, Delimiter{})
|
tokens := Tokenize(input, Delimiter{})
|
||||||
{
|
{
|
||||||
ranges := splitNth("1,2,3")
|
ranges, _ := splitNth("1,2,3")
|
||||||
tx := Transform(tokens, ranges)
|
tx := Transform(tokens, ranges)
|
||||||
if joinTokens(tx) != "abc: def: ghi: " {
|
if joinTokens(tx) != "abc: def: ghi: " {
|
||||||
t.Errorf("%s", tx)
|
t.Errorf("%s", tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
ranges := splitNth("1..2,3,2..,1")
|
ranges, _ := splitNth("1..2,3,2..,1")
|
||||||
tx := Transform(tokens, ranges)
|
tx := Transform(tokens, ranges)
|
||||||
if string(joinTokens(tx)) != "abc: def: ghi: def: ghi: jklabc: " ||
|
if string(joinTokens(tx)) != "abc: def: ghi: def: ghi: jklabc: " ||
|
||||||
len(tx) != 4 ||
|
len(tx) != 4 ||
|
||||||
@@ -93,7 +93,7 @@ func TestTransform(t *testing.T) {
|
|||||||
{
|
{
|
||||||
tokens := Tokenize(input, delimiterRegexp(":"))
|
tokens := Tokenize(input, delimiterRegexp(":"))
|
||||||
{
|
{
|
||||||
ranges := splitNth("1..2,3,2..,1")
|
ranges, _ := splitNth("1..2,3,2..,1")
|
||||||
tx := Transform(tokens, ranges)
|
tx := Transform(tokens, ranges)
|
||||||
if joinTokens(tx) != " abc: def: ghi: def: ghi: jkl abc:" ||
|
if joinTokens(tx) != " abc: def: ghi: def: ghi: jkl abc:" ||
|
||||||
len(tx) != 4 ||
|
len(tx) != 4 ||
|
||||||
@@ -108,5 +108,6 @@ func TestTransform(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTransformIndexOutOfBounds(t *testing.T) {
|
func TestTransformIndexOutOfBounds(t *testing.T) {
|
||||||
Transform([]Token{}, splitNth("1"))
|
s, _ := splitNth("1")
|
||||||
|
Transform([]Token{}, s)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ func HasFullscreenRenderer() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var DefaultBorderShape BorderShape = BorderRounded
|
var DefaultBorderShape = BorderRounded
|
||||||
|
|
||||||
func (a Attr) Merge(b Attr) Attr {
|
func (a Attr) Merge(b Attr) Attr {
|
||||||
return a | b
|
return a | b
|
||||||
@@ -29,7 +29,7 @@ const (
|
|||||||
StrikeThrough = Attr(1 << 7)
|
StrikeThrough = Attr(1 << 7)
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Init() {}
|
func (r *FullscreenRenderer) Init() error { return nil }
|
||||||
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) {}
|
||||||
|
|||||||
@@ -83,34 +83,36 @@ func _() {
|
|||||||
_ = x[Alt-72]
|
_ = x[Alt-72]
|
||||||
_ = x[CtrlAlt-73]
|
_ = x[CtrlAlt-73]
|
||||||
_ = x[Invalid-74]
|
_ = x[Invalid-74]
|
||||||
_ = x[Mouse-75]
|
_ = x[Fatal-75]
|
||||||
_ = x[DoubleClick-76]
|
_ = x[Mouse-76]
|
||||||
_ = x[LeftClick-77]
|
_ = x[DoubleClick-77]
|
||||||
_ = x[RightClick-78]
|
_ = x[LeftClick-78]
|
||||||
_ = x[SLeftClick-79]
|
_ = x[RightClick-79]
|
||||||
_ = x[SRightClick-80]
|
_ = x[SLeftClick-80]
|
||||||
_ = x[ScrollUp-81]
|
_ = x[SRightClick-81]
|
||||||
_ = x[ScrollDown-82]
|
_ = x[ScrollUp-82]
|
||||||
_ = x[SScrollUp-83]
|
_ = x[ScrollDown-83]
|
||||||
_ = x[SScrollDown-84]
|
_ = x[SScrollUp-84]
|
||||||
_ = x[PreviewScrollUp-85]
|
_ = x[SScrollDown-85]
|
||||||
_ = x[PreviewScrollDown-86]
|
_ = x[PreviewScrollUp-86]
|
||||||
_ = x[Resize-87]
|
_ = x[PreviewScrollDown-87]
|
||||||
_ = x[Change-88]
|
_ = x[Resize-88]
|
||||||
_ = x[BackwardEOF-89]
|
_ = x[Change-89]
|
||||||
_ = x[Start-90]
|
_ = x[BackwardEOF-90]
|
||||||
_ = x[Load-91]
|
_ = x[Start-91]
|
||||||
_ = x[Focus-92]
|
_ = x[Load-92]
|
||||||
_ = x[One-93]
|
_ = x[Focus-93]
|
||||||
_ = x[Zero-94]
|
_ = x[One-94]
|
||||||
_ = x[Result-95]
|
_ = x[Zero-95]
|
||||||
_ = x[Jump-96]
|
_ = x[Result-96]
|
||||||
_ = x[JumpCancel-97]
|
_ = x[Jump-97]
|
||||||
|
_ = x[JumpCancel-98]
|
||||||
|
_ = x[ClickHeader-99]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLCtrlMCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlDeleteCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltCtrlAltInvalidMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancel"
|
const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLCtrlMCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlDeleteCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltCtrlAltInvalidFatalMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancelClickHeader"
|
||||||
|
|
||||||
var _EventType_index = [...]uint16{0, 4, 9, 14, 19, 24, 29, 34, 39, 44, 47, 52, 57, 62, 67, 72, 77, 82, 87, 92, 97, 102, 107, 112, 117, 122, 127, 132, 135, 144, 154, 167, 183, 192, 201, 209, 218, 224, 230, 238, 240, 244, 248, 253, 257, 260, 266, 273, 282, 291, 301, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 333, 336, 339, 351, 356, 363, 370, 378, 388, 400, 412, 425, 428, 435, 442, 447, 458, 467, 477, 487, 498, 506, 516, 525, 536, 551, 568, 574, 580, 591, 596, 600, 605, 608, 612, 618, 622, 632}
|
var _EventType_index = [...]uint16{0, 4, 9, 14, 19, 24, 29, 34, 39, 44, 47, 52, 57, 62, 67, 72, 77, 82, 87, 92, 97, 102, 107, 112, 117, 122, 127, 132, 135, 144, 154, 167, 183, 192, 201, 209, 218, 224, 230, 238, 240, 244, 248, 253, 257, 260, 266, 273, 282, 291, 301, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 333, 336, 339, 351, 356, 363, 370, 378, 388, 400, 412, 425, 428, 435, 442, 447, 452, 463, 472, 482, 492, 503, 511, 521, 530, 541, 556, 573, 579, 585, 596, 601, 605, 610, 613, 617, 623, 627, 637, 648}
|
||||||
|
|
||||||
func (i EventType) String() string {
|
func (i EventType) String() string {
|
||||||
if i < 0 || i >= EventType(len(_EventType_index)-1) {
|
if i < 0 || i >= EventType(len(_EventType_index)-1) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package tui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -10,6 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
"github.com/rivo/uniseg"
|
"github.com/rivo/uniseg"
|
||||||
|
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
@@ -27,8 +29,8 @@ const (
|
|||||||
|
|
||||||
const consoleDevice string = "/dev/tty"
|
const consoleDevice string = "/dev/tty"
|
||||||
|
|
||||||
var offsetRegexp *regexp.Regexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R")
|
var offsetRegexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R")
|
||||||
var offsetRegexpBegin *regexp.Regexp = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R")
|
var offsetRegexpBegin = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R")
|
||||||
|
|
||||||
func (r *LightRenderer) PassThrough(str string) {
|
func (r *LightRenderer) PassThrough(str string) {
|
||||||
r.queued.WriteString("\x1b7" + str + "\x1b8")
|
r.queued.WriteString("\x1b7" + str + "\x1b8")
|
||||||
@@ -71,13 +73,14 @@ func (r *LightRenderer) csi(code string) string {
|
|||||||
|
|
||||||
func (r *LightRenderer) flush() {
|
func (r *LightRenderer) flush() {
|
||||||
if r.queued.Len() > 0 {
|
if r.queued.Len() > 0 {
|
||||||
fmt.Fprint(os.Stderr, "\x1b[?7l\x1b[?25l"+r.queued.String()+"\x1b[?25h\x1b[?7h")
|
fmt.Fprint(r.ttyout, "\x1b[?7l\x1b[?25l"+r.queued.String()+"\x1b[?25h\x1b[?7h")
|
||||||
r.queued.Reset()
|
r.queued.Reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Light renderer
|
// Light renderer
|
||||||
type LightRenderer struct {
|
type LightRenderer struct {
|
||||||
|
closed *util.AtomicBool
|
||||||
theme *ColorTheme
|
theme *ColorTheme
|
||||||
mouse bool
|
mouse bool
|
||||||
forceBlack bool
|
forceBlack bool
|
||||||
@@ -85,6 +88,7 @@ type LightRenderer struct {
|
|||||||
prevDownTime time.Time
|
prevDownTime time.Time
|
||||||
clicks [][2]int
|
clicks [][2]int
|
||||||
ttyin *os.File
|
ttyin *os.File
|
||||||
|
ttyout *os.File
|
||||||
buffer []byte
|
buffer []byte
|
||||||
origState *term.State
|
origState *term.State
|
||||||
width int
|
width int
|
||||||
@@ -123,19 +127,25 @@ type LightWindow struct {
|
|||||||
bg Color
|
bg Color
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLightRenderer(theme *ColorTheme, forceBlack bool, mouse bool, tabstop int, clearOnExit bool, fullscreen bool, maxHeightFunc func(int) int) Renderer {
|
func NewLightRenderer(ttyin *os.File, theme *ColorTheme, forceBlack bool, mouse bool, tabstop int, clearOnExit bool, fullscreen bool, maxHeightFunc func(int) int) (Renderer, error) {
|
||||||
|
out, err := openTtyOut()
|
||||||
|
if err != nil {
|
||||||
|
out = os.Stderr
|
||||||
|
}
|
||||||
r := LightRenderer{
|
r := LightRenderer{
|
||||||
|
closed: util.NewAtomicBool(false),
|
||||||
theme: theme,
|
theme: theme,
|
||||||
forceBlack: forceBlack,
|
forceBlack: forceBlack,
|
||||||
mouse: mouse,
|
mouse: mouse,
|
||||||
clearOnExit: clearOnExit,
|
clearOnExit: clearOnExit,
|
||||||
ttyin: openTtyIn(),
|
ttyin: ttyin,
|
||||||
|
ttyout: out,
|
||||||
yoffset: 0,
|
yoffset: 0,
|
||||||
tabstop: tabstop,
|
tabstop: tabstop,
|
||||||
fullscreen: fullscreen,
|
fullscreen: fullscreen,
|
||||||
upOneLine: false,
|
upOneLine: false,
|
||||||
maxHeightFunc: maxHeightFunc}
|
maxHeightFunc: maxHeightFunc}
|
||||||
return &r
|
return &r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func repeat(r rune, times int) string {
|
func repeat(r rune, times int) string {
|
||||||
@@ -153,11 +163,11 @@ func atoi(s string, defaultValue int) int {
|
|||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Init() {
|
func (r *LightRenderer) Init() error {
|
||||||
r.escDelay = atoi(os.Getenv("ESCDELAY"), defaultEscDelay)
|
r.escDelay = atoi(os.Getenv("ESCDELAY"), defaultEscDelay)
|
||||||
|
|
||||||
if err := r.initPlatform(); err != nil {
|
if err := r.initPlatform(); err != nil {
|
||||||
errorExit(err.Error())
|
return err
|
||||||
}
|
}
|
||||||
r.updateTerminalSize()
|
r.updateTerminalSize()
|
||||||
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
||||||
@@ -195,6 +205,7 @@ func (r *LightRenderer) Init() {
|
|||||||
if !r.fullscreen && r.mouse {
|
if !r.fullscreen && r.mouse {
|
||||||
r.yoffset, _ = r.findOffset()
|
r.yoffset, _ = r.findOffset()
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Resize(maxHeightFunc func(int) int) {
|
func (r *LightRenderer) Resize(maxHeightFunc func(int) int) {
|
||||||
@@ -233,15 +244,16 @@ func getEnv(name string, defaultValue int) int {
|
|||||||
return atoi(env, defaultValue)
|
return atoi(env, defaultValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) getBytes() []byte {
|
func (r *LightRenderer) getBytes() ([]byte, error) {
|
||||||
return r.getBytesInternal(r.buffer, false)
|
bytes, err := r.getBytesInternal(r.buffer, false)
|
||||||
|
return bytes, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
|
func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) ([]byte, error) {
|
||||||
c, ok := r.getch(nonblock)
|
c, ok := r.getch(nonblock)
|
||||||
if !nonblock && !ok {
|
if !nonblock && !ok {
|
||||||
r.Close()
|
r.Close()
|
||||||
errorExit("Failed to read " + consoleDevice)
|
return nil, errors.New("failed to read " + consoleDevice)
|
||||||
}
|
}
|
||||||
|
|
||||||
retries := 0
|
retries := 0
|
||||||
@@ -272,19 +284,23 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
|
|||||||
// so terminate fzf immediately.
|
// so terminate fzf immediately.
|
||||||
if len(buffer) > maxInputBuffer {
|
if len(buffer) > maxInputBuffer {
|
||||||
r.Close()
|
r.Close()
|
||||||
panic(fmt.Sprintf("Input buffer overflow (%d): %v", len(buffer), buffer))
|
return nil, fmt.Errorf("input buffer overflow (%d): %v", len(buffer), buffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return buffer
|
return buffer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) GetChar() Event {
|
func (r *LightRenderer) GetChar() Event {
|
||||||
|
var err error
|
||||||
if len(r.buffer) == 0 {
|
if len(r.buffer) == 0 {
|
||||||
r.buffer = r.getBytes()
|
r.buffer, err = r.getBytes()
|
||||||
|
if err != nil {
|
||||||
|
return Event{Fatal, 0, nil}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if len(r.buffer) == 0 {
|
if len(r.buffer) == 0 {
|
||||||
panic("Empty buffer")
|
return Event{Fatal, 0, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
sz := 1
|
sz := 1
|
||||||
@@ -315,7 +331,9 @@ func (r *LightRenderer) GetChar() Event {
|
|||||||
ev := r.escSequence(&sz)
|
ev := r.escSequence(&sz)
|
||||||
// Second chance
|
// Second chance
|
||||||
if ev.Type == Invalid {
|
if ev.Type == Invalid {
|
||||||
r.buffer = r.getBytes()
|
if r.buffer, err = r.getBytes(); err != nil {
|
||||||
|
return Event{Fatal, 0, nil}
|
||||||
|
}
|
||||||
ev = r.escSequence(&sz)
|
ev = r.escSequence(&sz)
|
||||||
}
|
}
|
||||||
return ev
|
return ev
|
||||||
@@ -738,6 +756,7 @@ func (r *LightRenderer) Close() {
|
|||||||
r.flush()
|
r.flush()
|
||||||
r.closePlatform()
|
r.closePlatform()
|
||||||
r.restoreTerminal()
|
r.restoreTerminal()
|
||||||
|
r.closed.Set(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Top() int {
|
func (r *LightRenderer) Top() int {
|
||||||
@@ -821,44 +840,32 @@ func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
|
|||||||
color = ColPreviewBorder
|
color = ColPreviewBorder
|
||||||
}
|
}
|
||||||
hw := runeWidth(w.border.top)
|
hw := runeWidth(w.border.top)
|
||||||
pad := repeat(' ', w.width/hw)
|
|
||||||
|
|
||||||
w.Move(0, 0)
|
|
||||||
if top {
|
if top {
|
||||||
|
w.Move(0, 0)
|
||||||
w.CPrint(color, repeat(w.border.top, w.width/hw))
|
w.CPrint(color, repeat(w.border.top, w.width/hw))
|
||||||
} else {
|
|
||||||
w.CPrint(color, pad)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for y := 1; y < w.height-1; y++ {
|
|
||||||
w.Move(y, 0)
|
|
||||||
w.CPrint(color, pad)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Move(w.height-1, 0)
|
|
||||||
if bottom {
|
if bottom {
|
||||||
|
w.Move(w.height-1, 0)
|
||||||
w.CPrint(color, repeat(w.border.bottom, w.width/hw))
|
w.CPrint(color, repeat(w.border.bottom, w.width/hw))
|
||||||
} else {
|
|
||||||
w.CPrint(color, pad)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) drawBorderVertical(left, right bool) {
|
func (w *LightWindow) drawBorderVertical(left, right bool) {
|
||||||
width := w.width - 2
|
vw := runeWidth(w.border.left)
|
||||||
if !left || !right {
|
|
||||||
width++
|
|
||||||
}
|
|
||||||
color := ColBorder
|
color := ColBorder
|
||||||
if w.preview {
|
if w.preview {
|
||||||
color = ColPreviewBorder
|
color = ColPreviewBorder
|
||||||
}
|
}
|
||||||
for y := 0; y < w.height; y++ {
|
for y := 0; y < w.height; y++ {
|
||||||
w.Move(y, 0)
|
|
||||||
if left {
|
if left {
|
||||||
|
w.Move(y, 0)
|
||||||
w.CPrint(color, string(w.border.left))
|
w.CPrint(color, string(w.border.left))
|
||||||
|
w.CPrint(color, " ") // Margin
|
||||||
}
|
}
|
||||||
w.CPrint(color, repeat(' ', width))
|
|
||||||
if right {
|
if right {
|
||||||
|
w.Move(y, w.width-vw-1)
|
||||||
|
w.CPrint(color, " ") // Margin
|
||||||
w.CPrint(color, string(w.border.right))
|
w.CPrint(color, string(w.border.right))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -880,7 +887,10 @@ func (w *LightWindow) drawBorderAround(onlyHorizontal bool) {
|
|||||||
for y := 1; y < w.height-1; y++ {
|
for y := 1; y < w.height-1; y++ {
|
||||||
w.Move(y, 0)
|
w.Move(y, 0)
|
||||||
w.CPrint(color, string(w.border.left))
|
w.CPrint(color, string(w.border.left))
|
||||||
w.CPrint(color, repeat(' ', w.width-vw*2))
|
w.CPrint(color, " ") // Margin
|
||||||
|
|
||||||
|
w.Move(y, w.width-vw-1)
|
||||||
|
w.CPrint(color, " ") // Margin
|
||||||
w.CPrint(color, string(w.border.right))
|
w.CPrint(color, string(w.border.right))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1011,7 +1021,7 @@ func (w *LightWindow) Print(text string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func cleanse(str string) string {
|
func cleanse(str string) string {
|
||||||
return strings.Replace(str, "\x1b", "", -1)
|
return strings.ReplaceAll(str, "\x1b", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) CPrint(pair ColorPair, text string) {
|
func (w *LightWindow) CPrint(pair ColorPair, text string) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
package tui
|
package tui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -45,22 +45,29 @@ func (r *LightRenderer) initPlatform() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) closePlatform() {
|
func (r *LightRenderer) closePlatform() {
|
||||||
// NOOP
|
r.ttyout.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func openTtyIn() *os.File {
|
func openTty(mode int) (*os.File, error) {
|
||||||
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
|
in, err := os.OpenFile(consoleDevice, mode, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tty := ttyname()
|
tty := ttyname()
|
||||||
if len(tty) > 0 {
|
if len(tty) > 0 {
|
||||||
if in, err := os.OpenFile(tty, syscall.O_RDONLY, 0); err == nil {
|
if in, err := os.OpenFile(tty, mode, 0); err == nil {
|
||||||
return in
|
return in, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Fprintln(os.Stderr, "Failed to open "+consoleDevice)
|
return nil, errors.New("failed to open " + consoleDevice)
|
||||||
util.Exit(2)
|
|
||||||
}
|
}
|
||||||
return in
|
return in, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func openTtyIn() (*os.File, error) {
|
||||||
|
return openTty(syscall.O_RDONLY)
|
||||||
|
}
|
||||||
|
|
||||||
|
func openTtyOut() (*os.File, error) {
|
||||||
|
return openTty(syscall.O_WRONLY)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) setupTerminal() {
|
func (r *LightRenderer) setupTerminal() {
|
||||||
@@ -86,9 +93,14 @@ func (r *LightRenderer) updateTerminalSize() {
|
|||||||
func (r *LightRenderer) findOffset() (row int, col int) {
|
func (r *LightRenderer) findOffset() (row int, col int) {
|
||||||
r.csi("6n")
|
r.csi("6n")
|
||||||
r.flush()
|
r.flush()
|
||||||
|
var err error
|
||||||
bytes := []byte{}
|
bytes := []byte{}
|
||||||
for tries := 0; tries < offsetPollTries; tries++ {
|
for tries := 0; tries < offsetPollTries; tries++ {
|
||||||
bytes = r.getBytesInternal(bytes, tries > 0)
|
bytes, err = r.getBytesInternal(bytes, tries > 0)
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1
|
||||||
|
}
|
||||||
|
|
||||||
offsets := offsetRegexp.FindSubmatch(bytes)
|
offsets := offsetRegexp.FindSubmatch(bytes)
|
||||||
if len(offsets) > 3 {
|
if len(offsets) > 3 {
|
||||||
// Add anything we skipped over to the input buffer
|
// Add anything we skipped over to the input buffer
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ func (r *LightRenderer) initPlatform() error {
|
|||||||
go func() {
|
go func() {
|
||||||
fd := int(r.inHandle)
|
fd := int(r.inHandle)
|
||||||
b := make([]byte, 1)
|
b := make([]byte, 1)
|
||||||
for {
|
for !r.closed.Get() {
|
||||||
// HACK: if run from PSReadline, something resets ConsoleMode to remove ENABLE_VIRTUAL_TERMINAL_INPUT.
|
// HACK: if run from PSReadline, something resets ConsoleMode to remove ENABLE_VIRTUAL_TERMINAL_INPUT.
|
||||||
_ = windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
|
_ = windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
|
||||||
|
|
||||||
@@ -91,9 +91,13 @@ func (r *LightRenderer) closePlatform() {
|
|||||||
windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput)
|
windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput)
|
||||||
}
|
}
|
||||||
|
|
||||||
func openTtyIn() *os.File {
|
func openTtyIn() (*os.File, error) {
|
||||||
// not used
|
// not used
|
||||||
return nil
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func openTtyOut() (*os.File, error) {
|
||||||
|
return os.Stderr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) setupTerminal() error {
|
func (r *LightRenderer) setupTerminal() error {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
"github.com/gdamore/tcell/v2/encoding"
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
|
||||||
"github.com/rivo/uniseg"
|
"github.com/rivo/uniseg"
|
||||||
@@ -146,13 +145,13 @@ var (
|
|||||||
_initialResize bool = true
|
_initialResize bool = true
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *FullscreenRenderer) initScreen() {
|
func (r *FullscreenRenderer) initScreen() error {
|
||||||
s, e := tcell.NewScreen()
|
s, e := tcell.NewScreen()
|
||||||
if e != nil {
|
if e != nil {
|
||||||
errorExit(e.Error())
|
return e
|
||||||
}
|
}
|
||||||
if e = s.Init(); e != nil {
|
if e = s.Init(); e != nil {
|
||||||
errorExit(e.Error())
|
return e
|
||||||
}
|
}
|
||||||
if r.mouse {
|
if r.mouse {
|
||||||
s.EnableMouse()
|
s.EnableMouse()
|
||||||
@@ -160,16 +159,21 @@ func (r *FullscreenRenderer) initScreen() {
|
|||||||
s.DisableMouse()
|
s.DisableMouse()
|
||||||
}
|
}
|
||||||
_screen = s
|
_screen = s
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Init() {
|
func (r *FullscreenRenderer) Init() error {
|
||||||
if os.Getenv("TERM") == "cygwin" {
|
if os.Getenv("TERM") == "cygwin" {
|
||||||
os.Setenv("TERM", "")
|
os.Setenv("TERM", "")
|
||||||
}
|
}
|
||||||
encoding.Register()
|
|
||||||
|
|
||||||
r.initScreen()
|
if err := r.initScreen(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Top() int {
|
func (r *FullscreenRenderer) Top() int {
|
||||||
@@ -561,7 +565,11 @@ func fill(x, y, w, h int, n ColorPair, r rune) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Erase() {
|
func (w *TcellWindow) Erase() {
|
||||||
fill(w.left-1, w.top, w.width+1, w.height-1, w.normal, ' ')
|
if w.borderStyle.shape.HasLeft() {
|
||||||
|
fill(w.left-1, w.top, w.width, w.height-1, w.normal, ' ')
|
||||||
|
} else {
|
||||||
|
fill(w.left, w.top, w.width-1, w.height-1, w.normal, ' ')
|
||||||
|
}
|
||||||
w.drawBorder(false)
|
w.drawBorder(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
package tui
|
package tui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
@@ -20,7 +21,7 @@ func assert(t *testing.T, context string, got interface{}, want interface{}) boo
|
|||||||
|
|
||||||
// Test the handling of the tcell keyboard events.
|
// Test the handling of the tcell keyboard events.
|
||||||
func TestGetCharEventKey(t *testing.T) {
|
func TestGetCharEventKey(t *testing.T) {
|
||||||
if util.ToTty() {
|
if util.IsTty(os.Stdout) {
|
||||||
// This test is skipped when output goes to terminal, because it causes
|
// This test is skipped when output goes to terminal, because it causes
|
||||||
// some glitches:
|
// some glitches:
|
||||||
// - output lines may not start at the beginning of a row which makes
|
// - output lines may not start at the beginning of a row which makes
|
||||||
|
|||||||
@@ -4,12 +4,19 @@ package tui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
var devPrefixes = [...]string{"/dev/pts/", "/dev/"}
|
var devPrefixes = [...]string{"/dev/pts/", "/dev/"}
|
||||||
|
|
||||||
|
var tty atomic.Value
|
||||||
|
|
||||||
func ttyname() string {
|
func ttyname() string {
|
||||||
|
if cached := tty.Load(); cached != nil {
|
||||||
|
return cached.(string)
|
||||||
|
}
|
||||||
|
|
||||||
var stderr syscall.Stat_t
|
var stderr syscall.Stat_t
|
||||||
if syscall.Fstat(2, &stderr) != nil {
|
if syscall.Fstat(2, &stderr) != nil {
|
||||||
return ""
|
return ""
|
||||||
@@ -27,24 +34,21 @@ func ttyname() string {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if stat, ok := info.Sys().(*syscall.Stat_t); ok && stat.Rdev == stderr.Rdev {
|
if stat, ok := info.Sys().(*syscall.Stat_t); ok && stat.Rdev == stderr.Rdev {
|
||||||
return prefix + file.Name()
|
value := prefix + file.Name()
|
||||||
|
tty.Store(value)
|
||||||
|
return value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// TtyIn returns terminal device to be used as STDIN, falls back to os.Stdin
|
// TtyIn returns terminal device to read user input
|
||||||
func TtyIn() *os.File {
|
func TtyIn() (*os.File, error) {
|
||||||
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
|
return openTtyIn()
|
||||||
if err != nil {
|
}
|
||||||
tty := ttyname()
|
|
||||||
if len(tty) > 0 {
|
// TtyIn returns terminal device to write to
|
||||||
if in, err := os.OpenFile(tty, syscall.O_RDONLY, 0); err == nil {
|
func TtyOut() (*os.File, error) {
|
||||||
return in
|
return openTtyOut()
|
||||||
}
|
|
||||||
}
|
|
||||||
return os.Stdin
|
|
||||||
}
|
|
||||||
return in
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,20 @@
|
|||||||
|
|
||||||
package tui
|
package tui
|
||||||
|
|
||||||
import "os"
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
func ttyname() string {
|
func ttyname() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// TtyIn on Windows returns os.Stdin
|
// TtyIn on Windows returns os.Stdin
|
||||||
func TtyIn() *os.File {
|
func TtyIn() (*os.File, error) {
|
||||||
return os.Stdin
|
return os.Stdin, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TtyIn on Windows returns nil
|
||||||
|
func TtyOut() (*os.File, error) {
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package tui
|
package tui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -104,6 +102,7 @@ const (
|
|||||||
CtrlAlt
|
CtrlAlt
|
||||||
|
|
||||||
Invalid
|
Invalid
|
||||||
|
Fatal
|
||||||
|
|
||||||
Mouse
|
Mouse
|
||||||
DoubleClick
|
DoubleClick
|
||||||
@@ -130,6 +129,7 @@ const (
|
|||||||
Result
|
Result
|
||||||
Jump
|
Jump
|
||||||
JumpCancel
|
JumpCancel
|
||||||
|
ClickHeader
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t EventType) AsEvent() Event {
|
func (t EventType) AsEvent() Event {
|
||||||
@@ -303,6 +303,9 @@ type ColorTheme struct {
|
|||||||
Disabled ColorAttr
|
Disabled ColorAttr
|
||||||
Fg ColorAttr
|
Fg ColorAttr
|
||||||
Bg ColorAttr
|
Bg ColorAttr
|
||||||
|
SelectedFg ColorAttr
|
||||||
|
SelectedBg ColorAttr
|
||||||
|
SelectedMatch ColorAttr
|
||||||
PreviewFg ColorAttr
|
PreviewFg ColorAttr
|
||||||
PreviewBg ColorAttr
|
PreviewBg ColorAttr
|
||||||
DarkBg ColorAttr
|
DarkBg ColorAttr
|
||||||
@@ -314,7 +317,7 @@ type ColorTheme struct {
|
|||||||
Spinner ColorAttr
|
Spinner ColorAttr
|
||||||
Info ColorAttr
|
Info ColorAttr
|
||||||
Cursor ColorAttr
|
Cursor ColorAttr
|
||||||
Selected ColorAttr
|
Marker ColorAttr
|
||||||
Header ColorAttr
|
Header ColorAttr
|
||||||
Separator ColorAttr
|
Separator ColorAttr
|
||||||
Scrollbar ColorAttr
|
Scrollbar ColorAttr
|
||||||
@@ -353,7 +356,8 @@ type MouseEvent struct {
|
|||||||
type BorderShape int
|
type BorderShape int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
BorderNone BorderShape = iota
|
BorderUndefined BorderShape = iota
|
||||||
|
BorderNone
|
||||||
BorderRounded
|
BorderRounded
|
||||||
BorderSharp
|
BorderSharp
|
||||||
BorderBold
|
BorderBold
|
||||||
@@ -368,6 +372,14 @@ const (
|
|||||||
BorderRight
|
BorderRight
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (s BorderShape) HasLeft() bool {
|
||||||
|
switch s {
|
||||||
|
case BorderNone, BorderRight, BorderTop, BorderBottom, BorderHorizontal: // No Left
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (s BorderShape) HasRight() bool {
|
func (s BorderShape) HasRight() bool {
|
||||||
switch s {
|
switch s {
|
||||||
case BorderNone, BorderLeft, BorderTop, BorderBottom, BorderHorizontal: // No right
|
case BorderNone, BorderLeft, BorderTop, BorderBottom, BorderHorizontal: // No right
|
||||||
@@ -516,7 +528,7 @@ type TermSize struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Renderer interface {
|
type Renderer interface {
|
||||||
Init()
|
Init() error
|
||||||
Resize(maxHeightFunc func(int) int)
|
Resize(maxHeightFunc func(int) int)
|
||||||
Pause(clear bool)
|
Pause(clear bool)
|
||||||
Resume(clear bool, sigcont bool)
|
Resume(clear bool, sigcont bool)
|
||||||
@@ -595,12 +607,14 @@ var (
|
|||||||
ColMatch ColorPair
|
ColMatch ColorPair
|
||||||
ColCursor ColorPair
|
ColCursor ColorPair
|
||||||
ColCursorEmpty ColorPair
|
ColCursorEmpty ColorPair
|
||||||
|
ColMarker ColorPair
|
||||||
ColSelected ColorPair
|
ColSelected ColorPair
|
||||||
|
ColSelectedMatch ColorPair
|
||||||
ColCurrent ColorPair
|
ColCurrent ColorPair
|
||||||
ColCurrentMatch ColorPair
|
ColCurrentMatch ColorPair
|
||||||
ColCurrentCursor ColorPair
|
ColCurrentCursor ColorPair
|
||||||
ColCurrentCursorEmpty ColorPair
|
ColCurrentCursorEmpty ColorPair
|
||||||
ColCurrentSelected ColorPair
|
ColCurrentMarker ColorPair
|
||||||
ColCurrentSelectedEmpty ColorPair
|
ColCurrentSelectedEmpty ColorPair
|
||||||
ColSpinner ColorPair
|
ColSpinner ColorPair
|
||||||
ColInfo ColorPair
|
ColInfo ColorPair
|
||||||
@@ -622,6 +636,9 @@ func EmptyTheme() *ColorTheme {
|
|||||||
Input: ColorAttr{colUndefined, AttrUndefined},
|
Input: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Fg: ColorAttr{colUndefined, AttrUndefined},
|
Fg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Bg: ColorAttr{colUndefined, AttrUndefined},
|
Bg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
||||||
DarkBg: ColorAttr{colUndefined, AttrUndefined},
|
DarkBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Prompt: ColorAttr{colUndefined, AttrUndefined},
|
Prompt: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Match: ColorAttr{colUndefined, AttrUndefined},
|
Match: ColorAttr{colUndefined, AttrUndefined},
|
||||||
@@ -630,7 +647,7 @@ func EmptyTheme() *ColorTheme {
|
|||||||
Spinner: ColorAttr{colUndefined, AttrUndefined},
|
Spinner: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Info: ColorAttr{colUndefined, AttrUndefined},
|
Info: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Cursor: ColorAttr{colUndefined, AttrUndefined},
|
Cursor: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Selected: ColorAttr{colUndefined, AttrUndefined},
|
Marker: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Header: ColorAttr{colUndefined, AttrUndefined},
|
Header: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Border: ColorAttr{colUndefined, AttrUndefined},
|
Border: ColorAttr{colUndefined, AttrUndefined},
|
||||||
BorderLabel: ColorAttr{colUndefined, AttrUndefined},
|
BorderLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
@@ -652,6 +669,9 @@ func NoColorTheme() *ColorTheme {
|
|||||||
Input: ColorAttr{colDefault, AttrUndefined},
|
Input: ColorAttr{colDefault, AttrUndefined},
|
||||||
Fg: ColorAttr{colDefault, AttrUndefined},
|
Fg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
SelectedFg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
SelectedBg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
SelectedMatch: ColorAttr{colDefault, AttrUndefined},
|
||||||
DarkBg: ColorAttr{colDefault, AttrUndefined},
|
DarkBg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Prompt: ColorAttr{colDefault, AttrUndefined},
|
Prompt: ColorAttr{colDefault, AttrUndefined},
|
||||||
Match: ColorAttr{colDefault, Underline},
|
Match: ColorAttr{colDefault, Underline},
|
||||||
@@ -660,7 +680,7 @@ func NoColorTheme() *ColorTheme {
|
|||||||
Spinner: ColorAttr{colDefault, AttrUndefined},
|
Spinner: ColorAttr{colDefault, AttrUndefined},
|
||||||
Info: ColorAttr{colDefault, AttrUndefined},
|
Info: ColorAttr{colDefault, AttrUndefined},
|
||||||
Cursor: ColorAttr{colDefault, AttrUndefined},
|
Cursor: ColorAttr{colDefault, AttrUndefined},
|
||||||
Selected: ColorAttr{colDefault, AttrUndefined},
|
Marker: ColorAttr{colDefault, AttrUndefined},
|
||||||
Header: ColorAttr{colDefault, AttrUndefined},
|
Header: ColorAttr{colDefault, AttrUndefined},
|
||||||
Border: ColorAttr{colDefault, AttrUndefined},
|
Border: ColorAttr{colDefault, AttrUndefined},
|
||||||
BorderLabel: ColorAttr{colDefault, AttrUndefined},
|
BorderLabel: ColorAttr{colDefault, AttrUndefined},
|
||||||
@@ -676,17 +696,15 @@ func NoColorTheme() *ColorTheme {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func errorExit(message string) {
|
|
||||||
fmt.Fprintln(os.Stderr, message)
|
|
||||||
util.Exit(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
Default16 = &ColorTheme{
|
Default16 = &ColorTheme{
|
||||||
Colored: true,
|
Colored: true,
|
||||||
Input: ColorAttr{colDefault, AttrUndefined},
|
Input: ColorAttr{colDefault, AttrUndefined},
|
||||||
Fg: ColorAttr{colDefault, AttrUndefined},
|
Fg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
||||||
DarkBg: ColorAttr{colBlack, AttrUndefined},
|
DarkBg: ColorAttr{colBlack, AttrUndefined},
|
||||||
Prompt: ColorAttr{colBlue, AttrUndefined},
|
Prompt: ColorAttr{colBlue, AttrUndefined},
|
||||||
Match: ColorAttr{colGreen, AttrUndefined},
|
Match: ColorAttr{colGreen, AttrUndefined},
|
||||||
@@ -695,7 +713,7 @@ func init() {
|
|||||||
Spinner: ColorAttr{colGreen, AttrUndefined},
|
Spinner: ColorAttr{colGreen, AttrUndefined},
|
||||||
Info: ColorAttr{colWhite, AttrUndefined},
|
Info: ColorAttr{colWhite, AttrUndefined},
|
||||||
Cursor: ColorAttr{colRed, AttrUndefined},
|
Cursor: ColorAttr{colRed, AttrUndefined},
|
||||||
Selected: ColorAttr{colMagenta, AttrUndefined},
|
Marker: ColorAttr{colMagenta, AttrUndefined},
|
||||||
Header: ColorAttr{colCyan, AttrUndefined},
|
Header: ColorAttr{colCyan, AttrUndefined},
|
||||||
Border: ColorAttr{colBlack, AttrUndefined},
|
Border: ColorAttr{colBlack, AttrUndefined},
|
||||||
BorderLabel: ColorAttr{colWhite, AttrUndefined},
|
BorderLabel: ColorAttr{colWhite, AttrUndefined},
|
||||||
@@ -714,6 +732,9 @@ func init() {
|
|||||||
Input: ColorAttr{colDefault, AttrUndefined},
|
Input: ColorAttr{colDefault, AttrUndefined},
|
||||||
Fg: ColorAttr{colDefault, AttrUndefined},
|
Fg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
||||||
DarkBg: ColorAttr{236, AttrUndefined},
|
DarkBg: ColorAttr{236, AttrUndefined},
|
||||||
Prompt: ColorAttr{110, AttrUndefined},
|
Prompt: ColorAttr{110, AttrUndefined},
|
||||||
Match: ColorAttr{108, AttrUndefined},
|
Match: ColorAttr{108, AttrUndefined},
|
||||||
@@ -722,7 +743,7 @@ func init() {
|
|||||||
Spinner: ColorAttr{148, AttrUndefined},
|
Spinner: ColorAttr{148, AttrUndefined},
|
||||||
Info: ColorAttr{144, AttrUndefined},
|
Info: ColorAttr{144, AttrUndefined},
|
||||||
Cursor: ColorAttr{161, AttrUndefined},
|
Cursor: ColorAttr{161, AttrUndefined},
|
||||||
Selected: ColorAttr{168, AttrUndefined},
|
Marker: ColorAttr{168, AttrUndefined},
|
||||||
Header: ColorAttr{109, AttrUndefined},
|
Header: ColorAttr{109, AttrUndefined},
|
||||||
Border: ColorAttr{59, AttrUndefined},
|
Border: ColorAttr{59, AttrUndefined},
|
||||||
BorderLabel: ColorAttr{145, AttrUndefined},
|
BorderLabel: ColorAttr{145, AttrUndefined},
|
||||||
@@ -741,6 +762,9 @@ func init() {
|
|||||||
Input: ColorAttr{colDefault, AttrUndefined},
|
Input: ColorAttr{colDefault, AttrUndefined},
|
||||||
Fg: ColorAttr{colDefault, AttrUndefined},
|
Fg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
||||||
DarkBg: ColorAttr{251, AttrUndefined},
|
DarkBg: ColorAttr{251, AttrUndefined},
|
||||||
Prompt: ColorAttr{25, AttrUndefined},
|
Prompt: ColorAttr{25, AttrUndefined},
|
||||||
Match: ColorAttr{66, AttrUndefined},
|
Match: ColorAttr{66, AttrUndefined},
|
||||||
@@ -749,7 +773,7 @@ func init() {
|
|||||||
Spinner: ColorAttr{65, AttrUndefined},
|
Spinner: ColorAttr{65, AttrUndefined},
|
||||||
Info: ColorAttr{101, AttrUndefined},
|
Info: ColorAttr{101, AttrUndefined},
|
||||||
Cursor: ColorAttr{161, AttrUndefined},
|
Cursor: ColorAttr{161, AttrUndefined},
|
||||||
Selected: ColorAttr{168, AttrUndefined},
|
Marker: ColorAttr{168, AttrUndefined},
|
||||||
Header: ColorAttr{31, AttrUndefined},
|
Header: ColorAttr{31, AttrUndefined},
|
||||||
Border: ColorAttr{145, AttrUndefined},
|
Border: ColorAttr{145, AttrUndefined},
|
||||||
BorderLabel: ColorAttr{59, AttrUndefined},
|
BorderLabel: ColorAttr{59, AttrUndefined},
|
||||||
@@ -791,12 +815,15 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
|
|||||||
theme.Spinner = o(baseTheme.Spinner, theme.Spinner)
|
theme.Spinner = o(baseTheme.Spinner, theme.Spinner)
|
||||||
theme.Info = o(baseTheme.Info, theme.Info)
|
theme.Info = o(baseTheme.Info, theme.Info)
|
||||||
theme.Cursor = o(baseTheme.Cursor, theme.Cursor)
|
theme.Cursor = o(baseTheme.Cursor, theme.Cursor)
|
||||||
theme.Selected = o(baseTheme.Selected, theme.Selected)
|
theme.Marker = o(baseTheme.Marker, theme.Marker)
|
||||||
theme.Header = o(baseTheme.Header, theme.Header)
|
theme.Header = o(baseTheme.Header, theme.Header)
|
||||||
theme.Border = o(baseTheme.Border, theme.Border)
|
theme.Border = o(baseTheme.Border, theme.Border)
|
||||||
theme.BorderLabel = o(baseTheme.BorderLabel, theme.BorderLabel)
|
theme.BorderLabel = o(baseTheme.BorderLabel, theme.BorderLabel)
|
||||||
|
|
||||||
// These colors are not defined in the base themes
|
// These colors are not defined in the base themes
|
||||||
|
theme.SelectedFg = o(theme.Fg, theme.SelectedFg)
|
||||||
|
theme.SelectedBg = o(theme.Bg, theme.SelectedBg)
|
||||||
|
theme.SelectedMatch = o(theme.Match, theme.SelectedMatch)
|
||||||
theme.Disabled = o(theme.Input, theme.Disabled)
|
theme.Disabled = o(theme.Input, theme.Disabled)
|
||||||
theme.Gutter = o(theme.DarkBg, theme.Gutter)
|
theme.Gutter = o(theme.DarkBg, theme.Gutter)
|
||||||
theme.PreviewFg = o(theme.Fg, theme.PreviewFg)
|
theme.PreviewFg = o(theme.Fg, theme.PreviewFg)
|
||||||
@@ -822,17 +849,23 @@ func initPalette(theme *ColorTheme) {
|
|||||||
|
|
||||||
ColPrompt = pair(theme.Prompt, theme.Bg)
|
ColPrompt = pair(theme.Prompt, theme.Bg)
|
||||||
ColNormal = pair(theme.Fg, theme.Bg)
|
ColNormal = pair(theme.Fg, theme.Bg)
|
||||||
|
ColSelected = pair(theme.SelectedFg, theme.SelectedBg)
|
||||||
ColInput = pair(theme.Input, theme.Bg)
|
ColInput = pair(theme.Input, theme.Bg)
|
||||||
ColDisabled = pair(theme.Disabled, theme.Bg)
|
ColDisabled = pair(theme.Disabled, theme.Bg)
|
||||||
ColMatch = pair(theme.Match, theme.Bg)
|
ColMatch = pair(theme.Match, theme.Bg)
|
||||||
|
ColSelectedMatch = pair(theme.SelectedMatch, theme.SelectedBg)
|
||||||
ColCursor = pair(theme.Cursor, theme.Gutter)
|
ColCursor = pair(theme.Cursor, theme.Gutter)
|
||||||
ColCursorEmpty = pair(blank, theme.Gutter)
|
ColCursorEmpty = pair(blank, theme.Gutter)
|
||||||
ColSelected = pair(theme.Selected, theme.Gutter)
|
if theme.SelectedBg.Color != theme.Bg.Color {
|
||||||
|
ColMarker = pair(theme.Marker, theme.SelectedBg)
|
||||||
|
} else {
|
||||||
|
ColMarker = pair(theme.Marker, theme.Gutter)
|
||||||
|
}
|
||||||
ColCurrent = pair(theme.Current, theme.DarkBg)
|
ColCurrent = pair(theme.Current, theme.DarkBg)
|
||||||
ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg)
|
ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg)
|
||||||
ColCurrentCursor = pair(theme.Cursor, theme.DarkBg)
|
ColCurrentCursor = pair(theme.Cursor, theme.DarkBg)
|
||||||
ColCurrentCursorEmpty = pair(blank, theme.DarkBg)
|
ColCurrentCursorEmpty = pair(blank, theme.DarkBg)
|
||||||
ColCurrentSelected = pair(theme.Selected, theme.DarkBg)
|
ColCurrentMarker = pair(theme.Marker, theme.DarkBg)
|
||||||
ColCurrentSelectedEmpty = pair(blank, theme.DarkBg)
|
ColCurrentSelectedEmpty = pair(blank, theme.DarkBg)
|
||||||
ColSpinner = pair(theme.Spinner, theme.Bg)
|
ColSpinner = pair(theme.Spinner, theme.Bg)
|
||||||
ColInfo = pair(theme.Info, theme.Bg)
|
ColInfo = pair(theme.Info, theme.Bg)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -25,14 +24,5 @@ func RunAtExitFuncs() {
|
|||||||
for i := len(fns) - 1; i >= 0; i-- {
|
for i := len(fns) - 1; i >= 0; i-- {
|
||||||
fns[i]()
|
fns[i]()
|
||||||
}
|
}
|
||||||
}
|
atExitFuncs = nil
|
||||||
|
|
||||||
// Exit executes any functions registered with AtExit() then exits the program
|
|
||||||
// with os.Exit(code).
|
|
||||||
//
|
|
||||||
// NOTE: It must be used instead of os.Exit() since calling os.Exit() terminates
|
|
||||||
// the program before any of the AtExit functions can run.
|
|
||||||
func Exit(code int) {
|
|
||||||
defer os.Exit(code)
|
|
||||||
RunAtExitFuncs()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
@@ -74,6 +75,35 @@ func (chars *Chars) Bytes() []byte {
|
|||||||
return chars.slice
|
return chars.slice
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) NumLines(atMost int) (int, bool) {
|
||||||
|
lines := 1
|
||||||
|
if runes := chars.optionalRunes(); runes != nil {
|
||||||
|
for _, r := range runes {
|
||||||
|
if r == '\n' {
|
||||||
|
lines++
|
||||||
|
}
|
||||||
|
if lines > atMost {
|
||||||
|
return atMost, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lines, false
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx := 0; idx < len(chars.slice); idx++ {
|
||||||
|
found := bytes.IndexByte(chars.slice[idx:], '\n')
|
||||||
|
if found < 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
idx += found
|
||||||
|
lines++
|
||||||
|
if lines > atMost {
|
||||||
|
return atMost, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lines, false
|
||||||
|
}
|
||||||
|
|
||||||
func (chars *Chars) optionalRunes() []rune {
|
func (chars *Chars) optionalRunes() []rune {
|
||||||
if chars.inBytes {
|
if chars.inBytes {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package util
|
|||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -137,14 +138,10 @@ func DurWithin(
|
|||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsTty returns true if stdin is a terminal
|
// IsTty returns true if the file is a terminal
|
||||||
func IsTty() bool {
|
func IsTty(file *os.File) bool {
|
||||||
return isatty.IsTerminal(os.Stdin.Fd())
|
fd := file.Fd()
|
||||||
}
|
return isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd)
|
||||||
|
|
||||||
// ToTty returns true if stdout is a terminal
|
|
||||||
func ToTty() bool {
|
|
||||||
return isatty.IsTerminal(os.Stdout.Fd())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Once returns a function that returns the specified boolean value only once
|
// Once returns a function that returns the specified boolean value only once
|
||||||
@@ -188,3 +185,34 @@ func ToKebabCase(s string) string {
|
|||||||
}
|
}
|
||||||
return strings.ToLower(name)
|
return strings.ToLower(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CompareVersions compares two version strings
|
||||||
|
func CompareVersions(v1, v2 string) int {
|
||||||
|
parts1 := strings.Split(v1, ".")
|
||||||
|
parts2 := strings.Split(v2, ".")
|
||||||
|
|
||||||
|
atoi := func(s string) int {
|
||||||
|
n, e := strconv.Atoi(s)
|
||||||
|
if e != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < Max(len(parts1), len(parts2)); i++ {
|
||||||
|
var p1, p2 int
|
||||||
|
if i < len(parts1) {
|
||||||
|
p1 = atoi(parts1[i])
|
||||||
|
}
|
||||||
|
if i < len(parts2) {
|
||||||
|
p2 = atoi(parts2[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
if p1 > p2 {
|
||||||
|
return 1
|
||||||
|
} else if p1 < p2 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|||||||
@@ -203,3 +203,34 @@ func TestStringWidth(t *testing.T) {
|
|||||||
t.Errorf("Expected: %d, Actual: %d", 1, w)
|
t.Errorf("Expected: %d, Actual: %d", 1, w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCompareVersions(t *testing.T) {
|
||||||
|
assert := func(a, b string, expected int) {
|
||||||
|
if result := CompareVersions(a, b); result != expected {
|
||||||
|
t.Errorf("Expected: %d, Actual: %d", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert("2", "1", 1)
|
||||||
|
assert("2", "2", 0)
|
||||||
|
assert("2", "10", -1)
|
||||||
|
|
||||||
|
assert("2.1", "2.2", -1)
|
||||||
|
assert("2.1", "2.1.1", -1)
|
||||||
|
|
||||||
|
assert("1.2.3", "1.2.2", 1)
|
||||||
|
assert("1.2.3", "1.2.3", 0)
|
||||||
|
assert("1.2.3", "1.2.3.0", 0)
|
||||||
|
assert("1.2.3", "1.2.4", -1)
|
||||||
|
|
||||||
|
// Different number of parts
|
||||||
|
assert("1.0.0", "1", 0)
|
||||||
|
assert("1.0.0", "1.0", 0)
|
||||||
|
assert("1.0.0", "1.0.0", 0)
|
||||||
|
assert("1.0", "1.0.0", 0)
|
||||||
|
assert("1", "1.0.0", 0)
|
||||||
|
assert("1.0.0", "1.0.0.1", -1)
|
||||||
|
assert("1.0.0.1.0", "1.0.0.1", 0)
|
||||||
|
|
||||||
|
assert("", "3.4.5", -1)
|
||||||
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ func (x *Executor) Become(stdin *os.File, environ []string, command string) {
|
|||||||
shellPath, err := exec.LookPath(x.shell)
|
shellPath, err := exec.LookPath(x.shell)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "fzf (become): %s\n", err.Error())
|
fmt.Fprintf(os.Stderr, "fzf (become): %s\n", err.Error())
|
||||||
Exit(127)
|
os.Exit(127)
|
||||||
}
|
}
|
||||||
args := append([]string{shellPath}, append(x.args, command)...)
|
args := append([]string{shellPath}, append(x.args, command)...)
|
||||||
SetStdin(stdin)
|
SetStdin(stdin)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
@@ -20,6 +21,8 @@ const (
|
|||||||
shellTypePowerShell
|
shellTypePowerShell
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var escapeRegex = regexp.MustCompile(`[&|<>()^%!"]`)
|
||||||
|
|
||||||
type Executor struct {
|
type Executor struct {
|
||||||
shell string
|
shell string
|
||||||
shellType shellType
|
shellType shellType
|
||||||
@@ -42,7 +45,7 @@ func NewExecutor(withShell string) *Executor {
|
|||||||
args = args[1:]
|
args = args[1:]
|
||||||
} else if strings.HasPrefix(basename, "cmd") {
|
} else if strings.HasPrefix(basename, "cmd") {
|
||||||
shellType = shellTypeCmd
|
shellType = shellTypeCmd
|
||||||
args = []string{"/v:on/s/c"}
|
args = []string{"/s/c"}
|
||||||
} else if strings.HasPrefix(basename, "pwsh") || strings.HasPrefix(basename, "powershell") {
|
} else if strings.HasPrefix(basename, "pwsh") || strings.HasPrefix(basename, "powershell") {
|
||||||
shellType = shellTypePowerShell
|
shellType = shellTypePowerShell
|
||||||
args = []string{"-NoProfile", "-Command"}
|
args = []string{"-NoProfile", "-Command"}
|
||||||
@@ -97,15 +100,15 @@ func (x *Executor) Become(stdin *os.File, environ []string, command string) {
|
|||||||
err := cmd.Start()
|
err := cmd.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "fzf (become): %s\n", err.Error())
|
fmt.Fprintf(os.Stderr, "fzf (become): %s\n", err.Error())
|
||||||
Exit(127)
|
os.Exit(127)
|
||||||
}
|
}
|
||||||
err = cmd.Wait()
|
err = cmd.Wait()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if exitError, ok := err.(*exec.ExitError); ok {
|
if exitError, ok := err.(*exec.ExitError); ok {
|
||||||
Exit(exitError.ExitCode())
|
os.Exit(exitError.ExitCode())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func escapeArg(s string) string {
|
func escapeArg(s string) string {
|
||||||
@@ -119,8 +122,6 @@ func escapeArg(s string) string {
|
|||||||
slashes = 0
|
slashes = 0
|
||||||
case '\\':
|
case '\\':
|
||||||
slashes++
|
slashes++
|
||||||
case '&', '|', '<', '>', '(', ')', '@', '^', '%', '!':
|
|
||||||
b = append(b, '^')
|
|
||||||
case '"':
|
case '"':
|
||||||
for ; slashes > 0; slashes-- {
|
for ; slashes > 0; slashes-- {
|
||||||
b = append(b, '\\')
|
b = append(b, '\\')
|
||||||
@@ -133,7 +134,9 @@ func escapeArg(s string) string {
|
|||||||
b = append(b, '\\')
|
b = append(b, '\\')
|
||||||
}
|
}
|
||||||
b = append(b, '"')
|
b = append(b, '"')
|
||||||
return string(b)
|
return escapeRegex.ReplaceAllStringFunc(string(b), func(match string) string {
|
||||||
|
return "^" + match
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Executor) QuoteEntry(entry string) string {
|
func (x *Executor) QuoteEntry(entry string) string {
|
||||||
@@ -154,10 +157,10 @@ func (x *Executor) QuoteEntry(entry string) string {
|
|||||||
*/
|
*/
|
||||||
return escapeArg(entry)
|
return escapeArg(entry)
|
||||||
case shellTypePowerShell:
|
case shellTypePowerShell:
|
||||||
escaped := strings.Replace(entry, `"`, `\"`, -1)
|
escaped := strings.ReplaceAll(entry, `"`, `\"`)
|
||||||
return "'" + strings.Replace(escaped, "'", "''", -1) + "'"
|
return "'" + strings.ReplaceAll(escaped, "'", "''") + "'"
|
||||||
default:
|
default:
|
||||||
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
|
return "'" + strings.ReplaceAll(entry, "'", "'\\''") + "'"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
13
src/winpty.go
Normal file
13
src/winpty.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package fzf
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
func needWinpty(_ *Options) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func runWinpty(_ []string, _ *Options) (int, error) {
|
||||||
|
return ExitError, errors.New("Not supported")
|
||||||
|
}
|
||||||
75
src/winpty_windows.go
Normal file
75
src/winpty_windows.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package fzf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func isMintty345() bool {
|
||||||
|
return util.CompareVersions(os.Getenv("TERM_PROGRAM_VERSION"), "3.4.5") >= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func needWinpty(opts *Options) bool {
|
||||||
|
if os.Getenv("TERM_PROGRAM") != "mintty" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if isMintty345() {
|
||||||
|
/*
|
||||||
|
See: https://github.com/junegunn/fzf/issues/3809
|
||||||
|
|
||||||
|
"MSYS=enable_pcon" allows fzf to run properly on mintty 3.4.5 or later.
|
||||||
|
*/
|
||||||
|
if strings.Contains(os.Getenv("MSYS"), "enable_pcon") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setting the environment variable here unfortunately doesn't help,
|
||||||
|
// so we need to start a child process with "MSYS=enable_pcon"
|
||||||
|
// os.Setenv("MSYS", "enable_pcon")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if opts.NoWinpty {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if _, err := exec.LookPath("winpty"); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func runWinpty(args []string, opts *Options) (int, error) {
|
||||||
|
sh, err := sh()
|
||||||
|
if err != nil {
|
||||||
|
return ExitError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
argStr := escapeSingleQuote(args[0])
|
||||||
|
for _, arg := range args[1:] {
|
||||||
|
argStr += " " + escapeSingleQuote(arg)
|
||||||
|
}
|
||||||
|
argStr += ` --no-winpty`
|
||||||
|
|
||||||
|
if isMintty345() {
|
||||||
|
return runProxy(argStr, func(temp string) *exec.Cmd {
|
||||||
|
cmd := exec.Command(sh, temp)
|
||||||
|
cmd.Env = append(os.Environ(), "MSYS=enable_pcon")
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
return cmd
|
||||||
|
}, opts, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return runProxy(argStr, func(temp string) *exec.Cmd {
|
||||||
|
cmd := exec.Command(sh, "-c", fmt.Sprintf(`winpty < /dev/tty > /dev/tty -- sh %q`, temp))
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
return cmd
|
||||||
|
}, opts, false)
|
||||||
|
}
|
||||||
375
test/test_go.rb
375
test/test_go.rb
@@ -24,7 +24,7 @@ DEFAULT_TIMEOUT = 10
|
|||||||
FILE = File.expand_path(__FILE__)
|
FILE = File.expand_path(__FILE__)
|
||||||
BASE = File.expand_path('..', __dir__)
|
BASE = File.expand_path('..', __dir__)
|
||||||
Dir.chdir(BASE)
|
Dir.chdir(BASE)
|
||||||
FZF = "FZF_DEFAULT_OPTS=--no-scrollbar FZF_DEFAULT_COMMAND= #{BASE}/bin/fzf"
|
FZF = "FZF_DEFAULT_OPTS=\"--no-scrollbar --pointer \\> --marker \\>\" FZF_DEFAULT_COMMAND= #{BASE}/bin/fzf"
|
||||||
|
|
||||||
def wait
|
def wait
|
||||||
since = Time.now
|
since = Time.now
|
||||||
@@ -66,7 +66,7 @@ class Shell
|
|||||||
end
|
end
|
||||||
|
|
||||||
def fish
|
def fish
|
||||||
"unset #{UNSETS.join(' ')}; FZF_DEFAULT_OPTS=--no-scrollbar fish_history= fish"
|
"unset #{UNSETS.join(' ')}; FZF_DEFAULT_OPTS=\"--no-scrollbar --pointer '>' --marker '>'\" fish_history= fish"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -169,29 +169,34 @@ class Tmux
|
|||||||
end
|
end
|
||||||
|
|
||||||
class TestBase < Minitest::Test
|
class TestBase < Minitest::Test
|
||||||
TEMPNAME = '/tmp/output'
|
TEMPNAME = Dir::Tmpname.create(%w[fzf]) {}
|
||||||
|
FIFONAME = Dir::Tmpname.create(%w[fzf-fifo]) {}
|
||||||
|
|
||||||
attr_reader :tmux
|
attr_reader :tmux
|
||||||
|
|
||||||
|
def writelines(lines)
|
||||||
|
File.write(TEMPNAME, lines.join("\n"))
|
||||||
|
end
|
||||||
|
|
||||||
def tempname
|
def tempname
|
||||||
@temp_suffix ||= 0
|
TEMPNAME
|
||||||
[TEMPNAME,
|
|
||||||
caller_locations.map(&:label).find { |l| l.start_with?('test_') },
|
|
||||||
@temp_suffix].join('-')
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def writelines(path, lines)
|
def fzf_output
|
||||||
FileUtils.rm_f(path) while File.exist?(path)
|
@thread.join.value.chomp.tap { @thread = nil }
|
||||||
File.open(path, 'w') { |f| f.puts lines }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def readonce
|
def fzf_output_lines
|
||||||
wait { assert_path_exists tempname }
|
fzf_output.lines(chomp: true)
|
||||||
File.read(tempname)
|
end
|
||||||
ensure
|
|
||||||
FileUtils.rm_f(tempname) while File.exist?(tempname)
|
def setup
|
||||||
@temp_suffix += 1
|
FileUtils.rm_f([TEMPNAME, FIFONAME])
|
||||||
tmux.prepare
|
File.mkfifo(FIFONAME)
|
||||||
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
FileUtils.rm_f([TEMPNAME, FIFONAME])
|
||||||
end
|
end
|
||||||
|
|
||||||
alias assert_equal_org assert_equal
|
alias assert_equal_org assert_equal
|
||||||
@@ -201,8 +206,12 @@ class TestBase < Minitest::Test
|
|||||||
assert_equal_org(expected, actual)
|
assert_equal_org(expected, actual)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Run fzf with its output piped to a fifo
|
||||||
def fzf(*opts)
|
def fzf(*opts)
|
||||||
fzf!(*opts) + " > #{tempname}.tmp; mv #{tempname}.tmp #{tempname}"
|
raise 'fzf_output not taken' if @thread
|
||||||
|
|
||||||
|
@thread = Thread.new { File.read(FIFONAME) }
|
||||||
|
fzf!(*opts) + " > #{FIFONAME}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def fzf!(*opts)
|
def fzf!(*opts)
|
||||||
@@ -226,6 +235,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def teardown
|
def teardown
|
||||||
|
super
|
||||||
@tmux.kill
|
@tmux.kill
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -251,7 +261,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal '3910', readonce.chomp
|
assert_equal '3910', fzf_output
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_fzf_default_command
|
def test_fzf_default_command
|
||||||
@@ -259,7 +269,7 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| assert_equal '> hello', lines[-3] }
|
tmux.until { |lines| assert_equal '> hello', lines[-3] }
|
||||||
|
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal 'hello', readonce.chomp
|
assert_equal 'hello', fzf_output
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_fzf_default_command_failure
|
def test_fzf_default_command_failure
|
||||||
@@ -355,7 +365,7 @@ class TestGoFZF < TestBase
|
|||||||
:PgUp, 'C-J', :Down, :Tab, :Tab # 8, 7
|
:PgUp, 'C-J', :Down, :Tab, :Tab # 8, 7
|
||||||
tmux.until { |lines| assert_equal ' 10/10 (6)', lines[-2] }
|
tmux.until { |lines| assert_equal ' 10/10 (6)', lines[-2] }
|
||||||
tmux.send_keys 'C-M'
|
tmux.send_keys 'C-M'
|
||||||
assert_equal %w[3 2 5 6 8 7], readonce.lines(chomp: true)
|
assert_equal %w[3 2 5 6 8 7], fzf_output_lines
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_multi_max
|
def test_multi_max
|
||||||
@@ -462,12 +472,12 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys :BTab, :BTab
|
tmux.send_keys :BTab, :BTab
|
||||||
tmux.until { |lines| assert_equal ' 2/2 (2)', lines[-2] }
|
tmux.until { |lines| assert_equal ' 2/2 (2)', lines[-2] }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal [' 1st 2nd 3rd/', ' first second third/'], readonce.lines(chomp: true)
|
assert_equal [' 1st 2nd 3rd/', ' first second third/'], fzf_output_lines
|
||||||
else
|
else
|
||||||
tmux.send_keys '^', '3'
|
tmux.send_keys '^', '3'
|
||||||
tmux.until { |lines| assert_equal ' 1/2', lines[-2] }
|
tmux.until { |lines| assert_equal ' 1/2', lines[-2] }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal [' 1st 2nd 3rd/'], readonce.lines(chomp: true)
|
assert_equal [' 1st 2nd 3rd/'], fzf_output_lines
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -479,18 +489,18 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys(*Array.new(110) { rev ? :Down : :Up })
|
tmux.send_keys(*Array.new(110) { rev ? :Down : :Up })
|
||||||
tmux.until { |lines| assert_includes lines, '> 100' }
|
tmux.until { |lines| assert_includes lines, '> 100' }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal '100', readonce.chomp
|
assert_equal '100', fzf_output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_select_1
|
def test_select_1
|
||||||
tmux.send_keys "seq 1 100 | #{fzf(:with_nth, '..,..', :print_query, :q, 5555, :'1')}", :Enter
|
tmux.send_keys "seq 1 100 | #{fzf(:with_nth, '..,..', :print_query, :q, 5555, :'1')}", :Enter
|
||||||
assert_equal %w[5555 55], readonce.lines(chomp: true)
|
assert_equal %w[5555 55], fzf_output_lines
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_exit_0
|
def test_exit_0
|
||||||
tmux.send_keys "seq 1 100 | #{fzf(:with_nth, '..,..', :print_query, :q, 555_555, :'0')}", :Enter
|
tmux.send_keys "seq 1 100 | #{fzf(:with_nth, '..,..', :print_query, :q, 555_555, :'0')}", :Enter
|
||||||
assert_equal %w[555555], readonce.lines(chomp: true)
|
assert_equal %w[555555], fzf_output_lines
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_select_1_exit_0_fail
|
def test_select_1_exit_0_fail
|
||||||
@@ -500,7 +510,7 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys :BTab, :BTab, :BTab
|
tmux.send_keys :BTab, :BTab, :BTab
|
||||||
tmux.until { |lines| assert_equal ' 19/100 (3)', lines[-2] }
|
tmux.until { |lines| assert_equal ' 19/100 (3)', lines[-2] }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal %w[5 5 50 51], readonce.lines(chomp: true)
|
assert_equal %w[5 5 50 51], fzf_output_lines
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -508,11 +518,11 @@ class TestGoFZF < TestBase
|
|||||||
tmux.paste "(echo abc; echo $'\\352\\260\\200\\353\\202\\230\\353\\213\\244') | #{fzf(:query, "$'\\352\\260\\200\\353\\213\\244'")}"
|
tmux.paste "(echo abc; echo $'\\352\\260\\200\\353\\202\\230\\353\\213\\244') | #{fzf(:query, "$'\\352\\260\\200\\353\\213\\244'")}"
|
||||||
tmux.until { |lines| assert_equal ' 1/2', lines[-2] }
|
tmux.until { |lines| assert_equal ' 1/2', lines[-2] }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal %w[가나다], readonce.lines(chomp: true)
|
assert_equal %w[가나다], fzf_output_lines
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_sync
|
def test_sync
|
||||||
tmux.send_keys "seq 1 100 | #{fzf!(:multi)} | awk '{print $1 $1}' | #{fzf(:sync)}", :Enter
|
tmux.send_keys "seq 1 100 | #{FZF} --multi | awk '{print $1 $1}' | #{fzf(:sync)}", :Enter
|
||||||
tmux.until { |lines| assert_equal '>', lines[-1] }
|
tmux.until { |lines| assert_equal '>', lines[-1] }
|
||||||
tmux.send_keys 9
|
tmux.send_keys 9
|
||||||
tmux.until { |lines| assert_equal ' 19/100 (0)', lines[-2] }
|
tmux.until { |lines| assert_equal ' 19/100 (0)', lines[-2] }
|
||||||
@@ -521,7 +531,7 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until { |lines| assert_equal '>', lines[-1] }
|
tmux.until { |lines| assert_equal '>', lines[-1] }
|
||||||
tmux.send_keys 'C-K', :Enter
|
tmux.send_keys 'C-K', :Enter
|
||||||
assert_equal %w[9090], readonce.lines(chomp: true)
|
assert_equal %w[9090], fzf_output_lines
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_tac
|
def test_tac
|
||||||
@@ -530,7 +540,7 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys :BTab, :BTab, :BTab
|
tmux.send_keys :BTab, :BTab, :BTab
|
||||||
tmux.until { |lines| assert_equal ' 1000/1000 (3)', lines[-2] }
|
tmux.until { |lines| assert_equal ' 1000/1000 (3)', lines[-2] }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal %w[1000 999 998], readonce.lines(chomp: true)
|
assert_equal %w[1000 999 998], fzf_output_lines
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_tac_sort
|
def test_tac_sort
|
||||||
@@ -541,7 +551,7 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys :BTab, :BTab, :BTab
|
tmux.send_keys :BTab, :BTab, :BTab
|
||||||
tmux.until { |lines| assert_equal ' 28/1000 (3)', lines[-2] }
|
tmux.until { |lines| assert_equal ' 28/1000 (3)', lines[-2] }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal %w[99 999 998], readonce.lines(chomp: true)
|
assert_equal %w[99 999 998], fzf_output_lines
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_tac_nosort
|
def test_tac_nosort
|
||||||
@@ -552,18 +562,18 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys :BTab, :BTab, :BTab
|
tmux.send_keys :BTab, :BTab, :BTab
|
||||||
tmux.until { |lines| assert_equal ' 10/1000 (3)', lines[-2] }
|
tmux.until { |lines| assert_equal ' 10/1000 (3)', lines[-2] }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal %w[1000 900 800], readonce.lines(chomp: true)
|
assert_equal %w[1000 900 800], fzf_output_lines
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_expect
|
def test_expect
|
||||||
test = lambda do |key, feed, expected = key|
|
test = lambda do |key, feed, expected = key|
|
||||||
tmux.send_keys "seq 1 100 | #{fzf(:expect, key)}", :Enter
|
tmux.send_keys "seq 1 100 | #{fzf(:expect, key, :prompt, "[#{key}]")}", :Enter
|
||||||
tmux.until { |lines| assert_equal ' 100/100', lines[-2] }
|
tmux.until { |lines| assert_equal ' 100/100', lines[-2] }
|
||||||
tmux.send_keys '55'
|
tmux.send_keys '55'
|
||||||
tmux.until { |lines| assert_equal ' 1/100', lines[-2] }
|
tmux.until { |lines| assert_equal ' 1/100', lines[-2] }
|
||||||
tmux.send_keys(*feed)
|
tmux.send_keys(*feed)
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
assert_equal [expected, '55'], readonce.lines(chomp: true)
|
assert_equal [expected, '55'], fzf_output_lines
|
||||||
end
|
end
|
||||||
test.call('ctrl-t', 'C-T')
|
test.call('ctrl-t', 'C-T')
|
||||||
test.call('ctrl-t', 'Enter', '')
|
test.call('ctrl-t', 'Enter', '')
|
||||||
@@ -580,13 +590,20 @@ class TestGoFZF < TestBase
|
|||||||
test.call('@', '@')
|
test.call('@', '@')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_expect_with_bound_actions
|
||||||
|
tmux.send_keys "seq 1 100 | #{fzf('--query 1 --print-query --expect z --bind z:up+up')}", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 20, lines.match_count }
|
||||||
|
tmux.send_keys('z')
|
||||||
|
assert_equal %w[1 z 1], fzf_output_lines
|
||||||
|
end
|
||||||
|
|
||||||
def test_expect_print_query
|
def test_expect_print_query
|
||||||
tmux.send_keys "seq 1 100 | #{fzf('--expect=alt-z', :print_query)}", :Enter
|
tmux.send_keys "seq 1 100 | #{fzf('--expect=alt-z', :print_query)}", :Enter
|
||||||
tmux.until { |lines| assert_equal ' 100/100', lines[-2] }
|
tmux.until { |lines| assert_equal ' 100/100', lines[-2] }
|
||||||
tmux.send_keys '55'
|
tmux.send_keys '55'
|
||||||
tmux.until { |lines| assert_equal ' 1/100', lines[-2] }
|
tmux.until { |lines| assert_equal ' 1/100', lines[-2] }
|
||||||
tmux.send_keys :Escape, :z
|
tmux.send_keys :Escape, :z
|
||||||
assert_equal %w[55 alt-z 55], readonce.lines(chomp: true)
|
assert_equal %w[55 alt-z 55], fzf_output_lines
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_expect_printable_character_print_query
|
def test_expect_printable_character_print_query
|
||||||
@@ -595,12 +612,12 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys '55'
|
tmux.send_keys '55'
|
||||||
tmux.until { |lines| assert_equal ' 1/100', lines[-2] }
|
tmux.until { |lines| assert_equal ' 1/100', lines[-2] }
|
||||||
tmux.send_keys 'z'
|
tmux.send_keys 'z'
|
||||||
assert_equal %w[55 z 55], readonce.lines(chomp: true)
|
assert_equal %w[55 z 55], fzf_output_lines
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_expect_print_query_select_1
|
def test_expect_print_query_select_1
|
||||||
tmux.send_keys "seq 1 100 | #{fzf('-q55 -1 --expect=alt-z --print-query')}", :Enter
|
tmux.send_keys "seq 1 100 | #{fzf('-q55 -1 --expect=alt-z --print-query')}", :Enter
|
||||||
assert_equal ['55', '', '55'], readonce.lines(chomp: true)
|
assert_equal ['55', '', '55'], fzf_output_lines
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_toggle_sort
|
def test_toggle_sort
|
||||||
@@ -614,12 +631,12 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys :Tab
|
tmux.send_keys :Tab
|
||||||
tmux.until { |lines| assert_equal ' 4/111 +S (2)', lines[-2] }
|
tmux.until { |lines| assert_equal ' 4/111 +S (2)', lines[-2] }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal %w[111 11], readonce.lines(chomp: true)
|
assert_equal %w[111 11], fzf_output_lines
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_unicode_case
|
def test_unicode_case
|
||||||
writelines(tempname, %w[строКА1 СТРОКА2 строка3 Строка4])
|
writelines(%w[строКА1 СТРОКА2 строка3 Строка4])
|
||||||
assert_equal %w[СТРОКА2 Строка4], `#{FZF} -fС < #{tempname}`.lines(chomp: true)
|
assert_equal %w[СТРОКА2 Строка4], `#{FZF} -fС < #{tempname}`.lines(chomp: true)
|
||||||
assert_equal %w[строКА1 СТРОКА2 строка3 Строка4], `#{FZF} -fс < #{tempname}`.lines(chomp: true)
|
assert_equal %w[строКА1 СТРОКА2 строка3 Строка4], `#{FZF} -fс < #{tempname}`.lines(chomp: true)
|
||||||
end
|
end
|
||||||
@@ -631,7 +648,7 @@ class TestGoFZF < TestBase
|
|||||||
----foobar--
|
----foobar--
|
||||||
-------foobar-
|
-------foobar-
|
||||||
]
|
]
|
||||||
writelines(tempname, input)
|
writelines(input)
|
||||||
|
|
||||||
assert_equal input, `#{FZF} -ffoobar --tiebreak=index < #{tempname}`.lines(chomp: true)
|
assert_equal input, `#{FZF} -ffoobar --tiebreak=index < #{tempname}`.lines(chomp: true)
|
||||||
|
|
||||||
@@ -664,7 +681,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_tiebreak_index_begin
|
def test_tiebreak_index_begin
|
||||||
writelines(tempname, [
|
writelines([
|
||||||
'xoxxxxxoxx',
|
'xoxxxxxoxx',
|
||||||
'xoxxxxxox',
|
'xoxxxxxox',
|
||||||
'xxoxxxoxx',
|
'xxoxxxoxx',
|
||||||
@@ -713,10 +730,8 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_tiebreak_begin_algo_v2
|
def test_tiebreak_begin_algo_v2
|
||||||
writelines(tempname, [
|
writelines(['baz foo bar',
|
||||||
'baz foo bar',
|
'foo bar baz'])
|
||||||
'foo bar baz'
|
|
||||||
])
|
|
||||||
assert_equal [
|
assert_equal [
|
||||||
'foo bar baz',
|
'foo bar baz',
|
||||||
'baz foo bar'
|
'baz foo bar'
|
||||||
@@ -724,14 +739,12 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_tiebreak_end
|
def test_tiebreak_end
|
||||||
writelines(tempname, [
|
writelines(['xoxxxxxxxx',
|
||||||
'xoxxxxxxxx',
|
'xxoxxxxxxx',
|
||||||
'xxoxxxxxxx',
|
'xxxoxxxxxx',
|
||||||
'xxxoxxxxxx',
|
'xxxxoxxxx',
|
||||||
'xxxxoxxxx',
|
'xxxxxoxxx',
|
||||||
'xxxxxoxxx',
|
' xxxxoxxx'])
|
||||||
' xxxxoxxx'
|
|
||||||
])
|
|
||||||
|
|
||||||
assert_equal [
|
assert_equal [
|
||||||
' xxxxoxxx',
|
' xxxxoxxx',
|
||||||
@@ -760,7 +773,7 @@ class TestGoFZF < TestBase
|
|||||||
'xoxxxxxxxx'
|
'xoxxxxxxxx'
|
||||||
], `#{FZF} -fo --tiebreak=end,length,begin < #{tempname}`.lines(chomp: true)
|
], `#{FZF} -fo --tiebreak=end,length,begin < #{tempname}`.lines(chomp: true)
|
||||||
|
|
||||||
writelines(tempname, ['/bar/baz', '/foo/bar/baz'])
|
writelines(['/bar/baz', '/foo/bar/baz'])
|
||||||
assert_equal [
|
assert_equal [
|
||||||
'/foo/bar/baz',
|
'/foo/bar/baz',
|
||||||
'/bar/baz'
|
'/bar/baz'
|
||||||
@@ -774,7 +787,7 @@ class TestGoFZF < TestBase
|
|||||||
12345:he
|
12345:he
|
||||||
1234567:h
|
1234567:h
|
||||||
]
|
]
|
||||||
writelines(tempname, input)
|
writelines(input)
|
||||||
|
|
||||||
output = %w[
|
output = %w[
|
||||||
1:hell
|
1:hell
|
||||||
@@ -789,11 +802,9 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_tiebreak_chunk
|
def test_tiebreak_chunk
|
||||||
writelines(tempname, [
|
writelines(['1 foobarbaz ba',
|
||||||
'1 foobarbaz ba',
|
'2 foobar baz',
|
||||||
'2 foobar baz',
|
'3 foo barbaz'])
|
||||||
'3 foo barbaz'
|
|
||||||
])
|
|
||||||
|
|
||||||
assert_equal [
|
assert_equal [
|
||||||
'3 foo barbaz',
|
'3 foo barbaz',
|
||||||
@@ -823,7 +834,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_invalid_cache_query_type
|
def test_invalid_cache_query_type
|
||||||
command = %[(echo 'foo$bar'; echo 'barfoo'; echo 'foo^bar'; echo "foo'1-2"; seq 100) | #{fzf}]
|
command = %[(echo 'foo$bar'; echo 'barfoo'; echo 'foo^bar'; echo "foo'1-2"; seq 100) | #{FZF}]
|
||||||
|
|
||||||
# Suffix match
|
# Suffix match
|
||||||
tmux.send_keys command, :Enter
|
tmux.send_keys command, :Enter
|
||||||
@@ -862,14 +873,14 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys "seq 1 1000 | #{fzf('-m --bind=ctrl-j:accept,u:up,T:toggle-up,t:toggle')}", :Enter
|
tmux.send_keys "seq 1 1000 | #{fzf('-m --bind=ctrl-j:accept,u:up,T:toggle-up,t:toggle')}", :Enter
|
||||||
tmux.until { |lines| assert_equal ' 1000/1000 (0)', lines[-2] }
|
tmux.until { |lines| assert_equal ' 1000/1000 (0)', lines[-2] }
|
||||||
tmux.send_keys 'uuu', 'TTT', 'tt', 'uu', 'ttt', 'C-j'
|
tmux.send_keys 'uuu', 'TTT', 'tt', 'uu', 'ttt', 'C-j'
|
||||||
assert_equal %w[4 5 6 9], readonce.lines(chomp: true)
|
assert_equal %w[4 5 6 9], fzf_output_lines
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_bind_print_query
|
def test_bind_print_query
|
||||||
tmux.send_keys "seq 1 1000 | #{fzf('-m --bind=ctrl-j:print-query')}", :Enter
|
tmux.send_keys "seq 1 1000 | #{fzf('-m --bind=ctrl-j:print-query')}", :Enter
|
||||||
tmux.until { |lines| assert_equal ' 1000/1000 (0)', lines[-2] }
|
tmux.until { |lines| assert_equal ' 1000/1000 (0)', lines[-2] }
|
||||||
tmux.send_keys 'print-my-query', 'C-j'
|
tmux.send_keys 'print-my-query', 'C-j'
|
||||||
assert_equal %w[print-my-query], readonce.lines(chomp: true)
|
assert_equal %w[print-my-query], fzf_output_lines
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_bind_replace_query
|
def test_bind_replace_query
|
||||||
@@ -924,7 +935,7 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| assert_equal ' 10/100 (12)', lines[-2] }
|
tmux.until { |lines| assert_equal ' 10/100 (12)', lines[-2] }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal %w[1 2 10 20 30 40 50 60 70 80 90 100],
|
assert_equal %w[1 2 10 20 30 40 50 60 70 80 90 100],
|
||||||
readonce.lines(chomp: true)
|
fzf_output_lines
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_history
|
def test_history
|
||||||
@@ -936,7 +947,7 @@ class TestGoFZF < TestBase
|
|||||||
input = %w[00 11 22 33 44]
|
input = %w[00 11 22 33 44]
|
||||||
input.each do |keys|
|
input.each do |keys|
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys "seq 100 | #{fzf(opts)}", :Enter
|
tmux.send_keys "seq 100 | #{FZF} #{opts}", :Enter
|
||||||
tmux.until { |lines| assert_equal ' 100/100', lines[-2] }
|
tmux.until { |lines| assert_equal ' 100/100', lines[-2] }
|
||||||
tmux.send_keys keys
|
tmux.send_keys keys
|
||||||
tmux.until { |lines| assert_equal ' 1/100', lines[-2] }
|
tmux.until { |lines| assert_equal ' 1/100', lines[-2] }
|
||||||
@@ -948,7 +959,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Update history entries (not changed on disk)
|
# Update history entries (not changed on disk)
|
||||||
tmux.send_keys "seq 100 | #{fzf(opts)}", :Enter
|
tmux.send_keys "seq 100 | #{FZF} #{opts}", :Enter
|
||||||
tmux.until { |lines| assert_equal ' 100/100', lines[-2] }
|
tmux.until { |lines| assert_equal ' 100/100', lines[-2] }
|
||||||
tmux.send_keys 'C-p'
|
tmux.send_keys 'C-p'
|
||||||
tmux.until { |lines| assert_equal '> 44', lines[-1] }
|
tmux.until { |lines| assert_equal '> 44', lines[-1] }
|
||||||
@@ -971,7 +982,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Respect --bind option
|
# Respect --bind option
|
||||||
tmux.send_keys "seq 100 | #{fzf(opts + ' --bind ctrl-p:next-history,ctrl-n:previous-history')}", :Enter
|
tmux.send_keys "seq 100 | #{FZF} #{opts} --bind ctrl-p:next-history,ctrl-n:previous-history", :Enter
|
||||||
tmux.until { |lines| assert_equal ' 100/100', lines[-2] }
|
tmux.until { |lines| assert_equal ' 100/100', lines[-2] }
|
||||||
tmux.send_keys 'C-n', 'C-n', 'C-n', 'C-n', 'C-p'
|
tmux.send_keys 'C-n', 'C-n', 'C-n', 'C-n', 'C-p'
|
||||||
tmux.until { |lines| assert_equal '> 33', lines[-1] }
|
tmux.until { |lines| assert_equal '> 33', lines[-1] }
|
||||||
@@ -983,8 +994,8 @@ class TestGoFZF < TestBase
|
|||||||
def test_execute
|
def test_execute
|
||||||
output = '/tmp/fzf-test-execute'
|
output = '/tmp/fzf-test-execute'
|
||||||
opts = %[--bind "alt-a:execute(echo /{}/ >> #{output})+change-header(alt-a),alt-b:execute[echo /{}{}/ >> #{output}]+change-header(alt-b),C:execute(echo /{}{}{}/ >> #{output})+change-header(C)"]
|
opts = %[--bind "alt-a:execute(echo /{}/ >> #{output})+change-header(alt-a),alt-b:execute[echo /{}{}/ >> #{output}]+change-header(alt-b),C:execute(echo /{}{}{}/ >> #{output})+change-header(C)"]
|
||||||
writelines(tempname, %w[foo'bar foo"bar foo$bar])
|
writelines(%w[foo'bar foo"bar foo$bar])
|
||||||
tmux.send_keys "cat #{tempname} | #{fzf(opts)}", :Enter
|
tmux.send_keys "cat #{tempname} | #{FZF} #{opts}", :Enter
|
||||||
tmux.until { |lines| assert_equal 3, lines.item_count }
|
tmux.until { |lines| assert_equal 3, lines.item_count }
|
||||||
|
|
||||||
ready = ->(s) { tmux.until { |lines| assert_includes lines[-3], s } }
|
ready = ->(s) { tmux.until { |lines| assert_includes lines[-3], s } }
|
||||||
@@ -1026,8 +1037,8 @@ class TestGoFZF < TestBase
|
|||||||
def test_execute_multi
|
def test_execute_multi
|
||||||
output = '/tmp/fzf-test-execute-multi'
|
output = '/tmp/fzf-test-execute-multi'
|
||||||
opts = %[--multi --bind "alt-a:execute-multi(echo {}/{+} >> #{output})+change-header(alt-a),alt-b:change-header(alt-b)"]
|
opts = %[--multi --bind "alt-a:execute-multi(echo {}/{+} >> #{output})+change-header(alt-a),alt-b:change-header(alt-b)"]
|
||||||
writelines(tempname, %w[foo'bar foo"bar foo$bar foobar])
|
writelines(%w[foo'bar foo"bar foo$bar foobar])
|
||||||
tmux.send_keys "cat #{tempname} | #{fzf(opts)}", :Enter
|
tmux.send_keys "cat #{tempname} | #{FZF} #{opts}", :Enter
|
||||||
ready = ->(s) { tmux.until { |lines| assert_includes lines[-3], s } }
|
ready = ->(s) { tmux.until { |lines| assert_includes lines[-3], s } }
|
||||||
|
|
||||||
tmux.until { |lines| assert_equal ' 4/4 (0)', lines[-2] }
|
tmux.until { |lines| assert_equal ' 4/4 (0)', lines[-2] }
|
||||||
@@ -1062,7 +1073,7 @@ class TestGoFZF < TestBase
|
|||||||
def test_execute_plus_flag
|
def test_execute_plus_flag
|
||||||
output = tempname + '.tmp'
|
output = tempname + '.tmp'
|
||||||
FileUtils.rm_f(output)
|
FileUtils.rm_f(output)
|
||||||
writelines(tempname, ['foo bar', '123 456'])
|
writelines(['foo bar', '123 456'])
|
||||||
|
|
||||||
tmux.send_keys "cat #{tempname} | #{FZF} --multi --bind 'x:execute-silent(echo {+}/{}/{+2}/{2} >> #{output})'", :Enter
|
tmux.send_keys "cat #{tempname} | #{FZF} --multi --bind 'x:execute-silent(echo {+}/{}/{+2}/{2} >> #{output})'", :Enter
|
||||||
|
|
||||||
@@ -1101,8 +1112,7 @@ class TestGoFZF < TestBase
|
|||||||
# Custom script to use as $SHELL
|
# Custom script to use as $SHELL
|
||||||
output = tempname + '.out'
|
output = tempname + '.out'
|
||||||
FileUtils.rm_f(output)
|
FileUtils.rm_f(output)
|
||||||
writelines(tempname,
|
writelines(['#!/usr/bin/env bash', "echo $1 / $2 > #{output}"])
|
||||||
['#!/usr/bin/env bash', "echo $1 / $2 > #{output}"])
|
|
||||||
system("chmod +x #{tempname}")
|
system("chmod +x #{tempname}")
|
||||||
|
|
||||||
tmux.send_keys "echo foo | SHELL=#{tempname} fzf --bind 'enter:execute:{}bar'", :Enter
|
tmux.send_keys "echo foo | SHELL=#{tempname} fzf --bind 'enter:execute:{}bar'", :Enter
|
||||||
@@ -1118,7 +1128,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_cycle
|
def test_cycle
|
||||||
tmux.send_keys "seq 8 | #{fzf(:cycle)}", :Enter
|
tmux.send_keys "seq 8 | #{FZF} --cycle", :Enter
|
||||||
tmux.until { |lines| assert_equal ' 8/8', lines[-2] }
|
tmux.until { |lines| assert_equal ' 8/8', lines[-2] }
|
||||||
tmux.send_keys :Down
|
tmux.send_keys :Down
|
||||||
tmux.until { |lines| assert_equal '> 8', lines[-10] }
|
tmux.until { |lines| assert_equal '> 8', lines[-10] }
|
||||||
@@ -1148,7 +1158,7 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys :Down
|
tmux.send_keys :Down
|
||||||
end
|
end
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal '50', readonce.chomp
|
assert_equal '50', fzf_output
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_header_lines_reverse
|
def test_header_lines_reverse
|
||||||
@@ -1163,7 +1173,7 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys :Up
|
tmux.send_keys :Up
|
||||||
end
|
end
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal '50', readonce.chomp
|
assert_equal '50', fzf_output
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_header_lines_reverse_list
|
def test_header_lines_reverse_list
|
||||||
@@ -1178,7 +1188,7 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys :Up
|
tmux.send_keys :Up
|
||||||
end
|
end
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal '50', readonce.chomp
|
assert_equal '50', fzf_output
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_header_lines_overflow
|
def test_header_lines_overflow
|
||||||
@@ -1188,7 +1198,7 @@ class TestGoFZF < TestBase
|
|||||||
assert_equal ' 1', lines[-3]
|
assert_equal ' 1', lines[-3]
|
||||||
end
|
end
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal '', readonce.chomp
|
assert_equal '', fzf_output
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_header_lines_with_nth
|
def test_header_lines_with_nth
|
||||||
@@ -1200,11 +1210,11 @@ class TestGoFZF < TestBase
|
|||||||
assert_equal '> 66666', lines[-8]
|
assert_equal '> 66666', lines[-8]
|
||||||
end
|
end
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal '6', readonce.chomp
|
assert_equal '6', fzf_output
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_header
|
def test_header
|
||||||
tmux.send_keys "seq 100 | #{fzf("--header \"$(head -5 #{FILE})\"")}", :Enter
|
tmux.send_keys %[seq 100 | #{FZF} --header "$(head -5 #{FILE})"], :Enter
|
||||||
header = File.readlines(FILE, chomp: true).take(5)
|
header = File.readlines(FILE, chomp: true).take(5)
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
assert_equal ' 100/100', lines[-2]
|
assert_equal ' 100/100', lines[-2]
|
||||||
@@ -1214,7 +1224,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_header_reverse
|
def test_header_reverse
|
||||||
tmux.send_keys "seq 100 | #{fzf("--header \"$(head -5 #{FILE})\" --reverse")}", :Enter
|
tmux.send_keys %[seq 100 | #{FZF} --header "$(head -5 #{FILE})" --reverse], :Enter
|
||||||
header = File.readlines(FILE, chomp: true).take(5)
|
header = File.readlines(FILE, chomp: true).take(5)
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
assert_equal ' 100/100', lines[1]
|
assert_equal ' 100/100', lines[1]
|
||||||
@@ -1224,7 +1234,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_header_reverse_list
|
def test_header_reverse_list
|
||||||
tmux.send_keys "seq 100 | #{fzf("--header \"$(head -5 #{FILE})\" --layout=reverse-list")}", :Enter
|
tmux.send_keys %[seq 100 | #{FZF} --header "$(head -5 #{FILE})" --layout=reverse-list], :Enter
|
||||||
header = File.readlines(FILE, chomp: true).take(5)
|
header = File.readlines(FILE, chomp: true).take(5)
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
assert_equal ' 100/100', lines[-2]
|
assert_equal ' 100/100', lines[-2]
|
||||||
@@ -1234,7 +1244,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_header_and_header_lines
|
def test_header_and_header_lines
|
||||||
tmux.send_keys "seq 100 | #{fzf("--header-lines 10 --header \"$(head -5 #{FILE})\"")}", :Enter
|
tmux.send_keys %[seq 100 | #{FZF} --header-lines 10 --header "$(head -5 #{FILE})"], :Enter
|
||||||
header = File.readlines(FILE, chomp: true).take(5)
|
header = File.readlines(FILE, chomp: true).take(5)
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
assert_equal ' 90/90', lines[-2]
|
assert_equal ' 90/90', lines[-2]
|
||||||
@@ -1244,7 +1254,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_header_and_header_lines_reverse
|
def test_header_and_header_lines_reverse
|
||||||
tmux.send_keys "seq 100 | #{fzf("--reverse --header-lines 10 --header \"$(head -5 #{FILE})\"")}", :Enter
|
tmux.send_keys %[seq 100 | #{FZF} --reverse --header-lines 10 --header "$(head -5 #{FILE})"], :Enter
|
||||||
header = File.readlines(FILE, chomp: true).take(5)
|
header = File.readlines(FILE, chomp: true).take(5)
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
assert_equal ' 90/90', lines[1]
|
assert_equal ' 90/90', lines[1]
|
||||||
@@ -1254,7 +1264,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_header_and_header_lines_reverse_list
|
def test_header_and_header_lines_reverse_list
|
||||||
tmux.send_keys "seq 100 | #{fzf("--layout=reverse-list --header-lines 10 --header \"$(head -5 #{FILE})\"")}", :Enter
|
tmux.send_keys %[seq 100 | #{FZF} --layout=reverse-list --header-lines 10 --header "$(head -5 #{FILE})"], :Enter
|
||||||
header = File.readlines(FILE, chomp: true).take(5)
|
header = File.readlines(FILE, chomp: true).take(5)
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
assert_equal ' 90/90', lines[-2]
|
assert_equal ' 90/90', lines[-2]
|
||||||
@@ -1297,7 +1307,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
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] }
|
||||||
tmux.send_keys '123'
|
tmux.send_keys '123'
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
@@ -1313,7 +1323,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_margin
|
def test_margin
|
||||||
tmux.send_keys "yes | head -1000 | #{fzf('--margin 5,3')}", :Enter
|
tmux.send_keys "yes | head -1000 | #{FZF} --margin 5,3", :Enter
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
assert_equal '', lines[4]
|
assert_equal '', lines[4]
|
||||||
assert_equal ' y', lines[5]
|
assert_equal ' y', lines[5]
|
||||||
@@ -1322,13 +1332,13 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_margin_reverse
|
def test_margin_reverse
|
||||||
tmux.send_keys "seq 1000 | #{fzf('--margin 7,5 --reverse')}", :Enter
|
tmux.send_keys "seq 1000 | #{FZF} --margin 7,5 --reverse", :Enter
|
||||||
tmux.until { |lines| assert_equal ' 1000/1000', lines[1 + 7] }
|
tmux.until { |lines| assert_equal ' 1000/1000', lines[1 + 7] }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_margin_reverse_list
|
def test_margin_reverse_list
|
||||||
tmux.send_keys "yes | head -1000 | #{fzf('--margin 5,3 --layout=reverse-list')}", :Enter
|
tmux.send_keys "yes | head -1000 | #{FZF} --margin 5,3 --layout=reverse-list", :Enter
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
assert_equal '', lines[4]
|
assert_equal '', lines[4]
|
||||||
assert_equal ' > y', lines[5]
|
assert_equal ' > y', lines[5]
|
||||||
@@ -1337,7 +1347,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_tabstop
|
def test_tabstop
|
||||||
writelines(tempname, %W[f\too\tba\tr\tbaz\tbarfooq\tux])
|
writelines(%W[f\too\tba\tr\tbaz\tbarfooq\tux])
|
||||||
{
|
{
|
||||||
1 => '> f oo ba r baz barfooq ux',
|
1 => '> f oo ba r baz barfooq ux',
|
||||||
2 => '> f oo ba r baz barfooq ux',
|
2 => '> f oo ba r baz barfooq ux',
|
||||||
@@ -1359,14 +1369,14 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_with_nth_basic
|
def test_with_nth_basic
|
||||||
writelines(tempname, ['hello world ', 'byebye'])
|
writelines(['hello world ', 'byebye'])
|
||||||
assert_equal \
|
assert_equal \
|
||||||
'hello world ',
|
'hello world ',
|
||||||
`#{FZF} -f"^he hehe" -x -n 2.. --with-nth 2,1,1 < #{tempname}`.chomp
|
`#{FZF} -f"^he hehe" -x -n 2.. --with-nth 2,1,1 < #{tempname}`.chomp
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_with_nth_ansi
|
def test_with_nth_ansi
|
||||||
writelines(tempname, ["\x1b[33mhello \x1b[34;1mworld\x1b[m ", 'byebye'])
|
writelines(["\x1b[33mhello \x1b[34;1mworld\x1b[m ", 'byebye'])
|
||||||
assert_equal \
|
assert_equal \
|
||||||
'hello world ',
|
'hello world ',
|
||||||
`#{FZF} -f"^he hehe" -x -n 2.. --with-nth 2,1,1 --ansi < #{tempname}`.chomp
|
`#{FZF} -f"^he hehe" -x -n 2.. --with-nth 2,1,1 --ansi < #{tempname}`.chomp
|
||||||
@@ -1374,7 +1384,7 @@ class TestGoFZF < TestBase
|
|||||||
|
|
||||||
def test_with_nth_no_ansi
|
def test_with_nth_no_ansi
|
||||||
src = "\x1b[33mhello \x1b[34;1mworld\x1b[m "
|
src = "\x1b[33mhello \x1b[34;1mworld\x1b[m "
|
||||||
writelines(tempname, [src, 'byebye'])
|
writelines([src, 'byebye'])
|
||||||
assert_equal \
|
assert_equal \
|
||||||
src,
|
src,
|
||||||
`#{FZF} -fhehe -x -n 2.. --with-nth 2,1,1 --no-ansi < #{tempname}`.chomp
|
`#{FZF} -fhehe -x -n 2.. --with-nth 2,1,1 --no-ansi < #{tempname}`.chomp
|
||||||
@@ -1429,7 +1439,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_hscroll_off
|
def test_hscroll_off
|
||||||
writelines(tempname, ['=' * 10_000 + '0123456789'])
|
writelines(['=' * 10_000 + '0123456789'])
|
||||||
[0, 3, 6].each do |off|
|
[0, 3, 6].each do |off|
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys "#{FZF} --hscroll-off=#{off} -q 0 < #{tempname}", :Enter
|
tmux.send_keys "#{FZF} --hscroll-off=#{off} -q 0 < #{tempname}", :Enter
|
||||||
@@ -1475,7 +1485,7 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys :Tab
|
tmux.send_keys :Tab
|
||||||
tmux.until { |lines| assert_equal '>>1', lines[-3] }
|
tmux.until { |lines| assert_equal '>>1', lines[-3] }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal %w[5 2 1], readonce.lines(chomp: true)
|
assert_equal %w[5 2 1], fzf_output_lines
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_jump_accept
|
def test_jump_accept
|
||||||
@@ -1484,11 +1494,11 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys 'C-j'
|
tmux.send_keys 'C-j'
|
||||||
tmux.until { |lines| assert_equal '5 5', lines[-7] }
|
tmux.until { |lines| assert_equal '5 5', lines[-7] }
|
||||||
tmux.send_keys '3'
|
tmux.send_keys '3'
|
||||||
assert_equal '3', readonce.chomp
|
assert_equal '3', fzf_output
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_jump_events
|
def test_jump_events
|
||||||
tmux.send_keys "seq 1000 | #{fzf("--multi --jump-labels 12345 --bind 'ctrl-j:jump,jump:preview(echo jumped to {}),jump-cancel:preview(echo jump cancelled at {})'")}", :Enter
|
tmux.send_keys "seq 1000 | #{FZF} --multi --jump-labels 12345 --bind 'ctrl-j:jump,jump:preview(echo jumped to {}),jump-cancel:preview(echo jump cancelled at {})'", :Enter
|
||||||
tmux.until { |lines| assert_equal ' 1000/1000 (0)', lines[-2] }
|
tmux.until { |lines| assert_equal ' 1000/1000 (0)', lines[-2] }
|
||||||
tmux.send_keys 'C-j'
|
tmux.send_keys 'C-j'
|
||||||
tmux.until { |lines| assert_includes lines[-7], '5 5' }
|
tmux.until { |lines| assert_includes lines[-7], '5 5' }
|
||||||
@@ -1507,7 +1517,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_pointer_with_jump
|
def test_pointer_with_jump
|
||||||
tmux.send_keys "seq 10 | #{fzf("--multi --jump-labels 12345 --bind 'ctrl-j:jump' --pointer '>>'")}", :Enter
|
tmux.send_keys "seq 10 | #{FZF} --multi --jump-labels 12345 --bind 'ctrl-j:jump' --pointer '>>'", :Enter
|
||||||
tmux.until { |lines| assert_equal ' 10/10 (0)', lines[-2] }
|
tmux.until { |lines| assert_equal ' 10/10 (0)', lines[-2] }
|
||||||
tmux.send_keys 'C-j'
|
tmux.send_keys 'C-j'
|
||||||
# Correctly padded jump label should appear
|
# Correctly padded jump label should appear
|
||||||
@@ -1519,7 +1529,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_marker
|
def test_marker
|
||||||
tmux.send_keys "seq 10 | #{fzf("--multi --marker '>>'")}", :Enter
|
tmux.send_keys "seq 10 | #{FZF} --multi --marker '>>'", :Enter
|
||||||
tmux.until { |lines| assert_equal ' 10/10 (0)', lines[-2] }
|
tmux.until { |lines| assert_equal ' 10/10 (0)', lines[-2] }
|
||||||
tmux.send_keys :BTab
|
tmux.send_keys :BTab
|
||||||
# Assert that specified marker is displayed
|
# Assert that specified marker is displayed
|
||||||
@@ -1643,7 +1653,6 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_preview_size_0
|
def test_preview_size_0
|
||||||
FileUtils.rm_f(tempname)
|
|
||||||
tmux.send_keys %(seq 100 | #{FZF} --reverse --preview 'echo {} >> #{tempname}; echo ' --preview-window 0 --bind space:toggle-preview), :Enter
|
tmux.send_keys %(seq 100 | #{FZF} --reverse --preview 'echo {} >> #{tempname}; echo ' --preview-window 0 --bind space:toggle-preview), :Enter
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
assert_equal 100, lines.item_count
|
assert_equal 100, lines.item_count
|
||||||
@@ -1669,7 +1678,6 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_preview_size_0_hidden
|
def test_preview_size_0_hidden
|
||||||
FileUtils.rm_f(tempname)
|
|
||||||
tmux.send_keys %(seq 100 | #{FZF} --reverse --preview 'echo {} >> #{tempname}; echo ' --preview-window 0,hidden --bind space:toggle-preview), :Enter
|
tmux.send_keys %(seq 100 | #{FZF} --reverse --preview 'echo {} >> #{tempname}; echo ' --preview-window 0,hidden --bind space:toggle-preview), :Enter
|
||||||
tmux.until { |lines| assert_equal 100, lines.item_count }
|
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||||
tmux.send_keys :Down, :Down
|
tmux.send_keys :Down, :Down
|
||||||
@@ -1739,14 +1747,11 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_no_clear
|
def test_no_clear
|
||||||
tmux.send_keys "seq 10 | fzf --no-clear --inline-info --height 5 > #{tempname}", :Enter
|
tmux.send_keys "seq 10 | #{fzf('--no-clear --inline-info --height 5')}", :Enter
|
||||||
prompt = '> < 10/10'
|
prompt = '> < 10/10'
|
||||||
tmux.until { |lines| assert_equal prompt, lines[-1] }
|
tmux.until { |lines| assert_equal prompt, lines[-1] }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
wait do
|
assert_equal %w[1], fzf_output_lines
|
||||||
assert_path_exists tempname
|
|
||||||
assert_equal %w[1], File.readlines(tempname, chomp: true)
|
|
||||||
end
|
|
||||||
tmux.until { |lines| assert_equal prompt, lines[-1] }
|
tmux.until { |lines| assert_equal prompt, lines[-1] }
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1815,7 +1820,7 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys '999'
|
tmux.send_keys '999'
|
||||||
tmux.until { |lines| assert_equal ' 1/1000', lines[-2] }
|
tmux.until { |lines| assert_equal ' 1/1000', lines[-2] }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal %w[999 999], readonce.lines(chomp: true)
|
assert_equal %w[999 999], fzf_output_lines
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_accept_non_empty_with_multi_selection
|
def test_accept_non_empty_with_multi_selection
|
||||||
@@ -1827,7 +1832,7 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| assert_equal ' 0/1000 (1)', lines[-2] }
|
tmux.until { |lines| assert_equal ' 0/1000 (1)', lines[-2] }
|
||||||
# fzf will exit in this case even though there's no match for the current query
|
# fzf will exit in this case even though there's no match for the current query
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal %w[foo 1], readonce.lines(chomp: true)
|
assert_equal %w[foo 1], fzf_output_lines
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_accept_non_empty_with_empty_list
|
def test_accept_non_empty_with_empty_list
|
||||||
@@ -1835,7 +1840,7 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| assert_equal ' 0/0', lines[-2] }
|
tmux.until { |lines| assert_equal ' 0/0', lines[-2] }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
# fzf will exit anyway since input list is empty
|
# fzf will exit anyway since input list is empty
|
||||||
assert_equal %w[foo], readonce.lines(chomp: true)
|
assert_equal %w[foo], fzf_output_lines
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_accept_or_print_query_without_match
|
def test_accept_or_print_query_without_match
|
||||||
@@ -1844,7 +1849,7 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys 99_999
|
tmux.send_keys 99_999
|
||||||
tmux.until { |lines| assert_equal 0, lines.match_count }
|
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal %w[99999], readonce.lines(chomp: true)
|
assert_equal %w[99999], fzf_output_lines
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_accept_or_print_query_with_match
|
def test_accept_or_print_query_with_match
|
||||||
@@ -1853,7 +1858,7 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys '^99$'
|
tmux.send_keys '^99$'
|
||||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal %w[99], readonce.lines(chomp: true)
|
assert_equal %w[99], fzf_output_lines
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_accept_or_print_query_with_multi_selection
|
def test_accept_or_print_query_with_multi_selection
|
||||||
@@ -1864,7 +1869,7 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys 99_999
|
tmux.send_keys 99_999
|
||||||
tmux.until { |lines| assert_equal 0, lines.match_count }
|
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal %w[1 2 3], readonce.lines(chomp: true)
|
assert_equal %w[1 2 3], fzf_output_lines
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_preview_update_on_select
|
def test_preview_update_on_select
|
||||||
@@ -1886,7 +1891,7 @@ class TestGoFZF < TestBase
|
|||||||
'foo bar',
|
'foo bar',
|
||||||
'bar foo'
|
'bar foo'
|
||||||
]
|
]
|
||||||
writelines(tempname, input)
|
writelines(input)
|
||||||
|
|
||||||
assert_equal input.length, `#{FZF} -f'foo bar' < #{tempname}`.lines.length
|
assert_equal input.length, `#{FZF} -f'foo bar' < #{tempname}`.lines.length
|
||||||
assert_equal input.length - 1, `#{FZF} -f'^foo bar$' < #{tempname}`.lines.length
|
assert_equal input.length - 1, `#{FZF} -f'^foo bar$' < #{tempname}`.lines.length
|
||||||
@@ -1912,7 +1917,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_preview_correct_tab_width_after_ansi_reset_code
|
def test_preview_correct_tab_width_after_ansi_reset_code
|
||||||
writelines(tempname, ["\x1b[31m+\x1b[m\t\x1b[32mgreen"])
|
writelines(["\x1b[31m+\x1b[m\t\x1b[32mgreen"])
|
||||||
tmux.send_keys "#{FZF} --preview 'cat #{tempname}'", :Enter
|
tmux.send_keys "#{FZF} --preview 'cat #{tempname}'", :Enter
|
||||||
tmux.until { |lines| assert_includes lines[1], ' + green ' }
|
tmux.until { |lines| assert_includes lines[1], ' + green ' }
|
||||||
end
|
end
|
||||||
@@ -2102,7 +2107,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_backward_delete_char_eof
|
def test_backward_delete_char_eof
|
||||||
tmux.send_keys "seq 1000 | #{fzf("--bind 'bs:backward-delete-char/eof'")}", :Enter
|
tmux.send_keys "seq 1000 | #{FZF} --bind 'bs:backward-delete-char/eof'", :Enter
|
||||||
tmux.until { |lines| assert_equal ' 1000/1000', lines[-2] }
|
tmux.until { |lines| assert_equal ' 1000/1000', lines[-2] }
|
||||||
tmux.send_keys '11'
|
tmux.send_keys '11'
|
||||||
tmux.until { |lines| assert_equal '> 11', lines[-1] }
|
tmux.until { |lines| assert_equal '> 11', lines[-1] }
|
||||||
@@ -2116,7 +2121,7 @@ class TestGoFZF < TestBase
|
|||||||
|
|
||||||
def test_strip_xterm_osc_sequence
|
def test_strip_xterm_osc_sequence
|
||||||
%W[\x07 \x1b\\].each do |esc|
|
%W[\x07 \x1b\\].each do |esc|
|
||||||
writelines(tempname, [%(printf $1"\e]4;3;rgb:aa/bb/cc#{esc} "$2)])
|
writelines([%(printf $1"\e]4;3;rgb:aa/bb/cc#{esc} "$2)])
|
||||||
File.chmod(0o755, tempname)
|
File.chmod(0o755, tempname)
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys \
|
tmux.send_keys \
|
||||||
@@ -2128,7 +2133,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_keep_right
|
def test_keep_right
|
||||||
tmux.send_keys "seq 10000 | #{FZF} --read0 --keep-right", :Enter
|
tmux.send_keys "seq 10000 | #{FZF} --read0 --keep-right --no-multi-line", :Enter
|
||||||
tmux.until { |lines| assert lines.any_include?('9999␊10000') }
|
tmux.until { |lines| assert lines.any_include?('9999␊10000') }
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -2343,81 +2348,69 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_kill_default_command_on_abort
|
def test_kill_default_command_on_abort
|
||||||
script = tempname + '.sh'
|
writelines(['#!/usr/bin/env bash',
|
||||||
writelines(script,
|
|
||||||
['#!/usr/bin/env bash',
|
|
||||||
"echo 'Started'",
|
"echo 'Started'",
|
||||||
'while :; do sleep 1; done'])
|
'while :; do sleep 1; done'])
|
||||||
system("chmod +x #{script}")
|
system("chmod +x #{tempname}")
|
||||||
|
|
||||||
tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', "FZF_DEFAULT_COMMAND=#{script}"), :Enter
|
tmux.send_keys FZF.sub('FZF_DEFAULT_COMMAND=', "FZF_DEFAULT_COMMAND=#{tempname}"), :Enter
|
||||||
tmux.until { |lines| assert_equal 1, lines.item_count }
|
tmux.until { |lines| assert_equal 1, lines.item_count }
|
||||||
tmux.send_keys 'C-c'
|
tmux.send_keys 'C-c'
|
||||||
tmux.send_keys 'C-l', 'closed'
|
tmux.send_keys 'C-l', 'closed'
|
||||||
tmux.until { |lines| assert_includes lines[0], 'closed' }
|
tmux.until { |lines| assert_includes lines[0], 'closed' }
|
||||||
wait { refute system("pgrep -f #{script}") }
|
wait { refute system("pgrep -f #{tempname}") }
|
||||||
ensure
|
ensure
|
||||||
system("pkill -9 -f #{script}")
|
system("pkill -9 -f #{tempname}")
|
||||||
FileUtils.rm_f(script)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_kill_default_command_on_accept
|
def test_kill_default_command_on_accept
|
||||||
script = tempname + '.sh'
|
writelines(['#!/usr/bin/env bash',
|
||||||
writelines(script,
|
|
||||||
['#!/usr/bin/env bash',
|
|
||||||
"echo 'Started'",
|
"echo 'Started'",
|
||||||
'while :; do sleep 1; done'])
|
'while :; do sleep 1; done'])
|
||||||
system("chmod +x #{script}")
|
system("chmod +x #{tempname}")
|
||||||
|
|
||||||
tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', "FZF_DEFAULT_COMMAND=#{script}"), :Enter
|
tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', "FZF_DEFAULT_COMMAND=#{tempname}"), :Enter
|
||||||
tmux.until { |lines| assert_equal 1, lines.item_count }
|
tmux.until { |lines| assert_equal 1, lines.item_count }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal 'Started', readonce.chomp
|
assert_equal 'Started', fzf_output
|
||||||
wait { refute system("pgrep -f #{script}") }
|
wait { refute system("pgrep -f #{tempname}") }
|
||||||
ensure
|
ensure
|
||||||
system("pkill -9 -f #{script}")
|
system("pkill -9 -f #{tempname}")
|
||||||
FileUtils.rm_f(script)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_kill_reload_command_on_abort
|
def test_kill_reload_command_on_abort
|
||||||
script = tempname + '.sh'
|
writelines(['#!/usr/bin/env bash',
|
||||||
writelines(script,
|
|
||||||
['#!/usr/bin/env bash',
|
|
||||||
"echo 'Started'",
|
"echo 'Started'",
|
||||||
'while :; do sleep 1; done'])
|
'while :; do sleep 1; done'])
|
||||||
system("chmod +x #{script}")
|
system("chmod +x #{tempname}")
|
||||||
|
|
||||||
tmux.send_keys "seq 1 3 | #{fzf("--bind 'ctrl-r:reload(#{script})'")}", :Enter
|
tmux.send_keys "seq 1 3 | #{FZF} --bind 'ctrl-r:reload(#{tempname})'", :Enter
|
||||||
tmux.until { |lines| assert_equal 3, lines.item_count }
|
tmux.until { |lines| assert_equal 3, lines.item_count }
|
||||||
tmux.send_keys 'C-r'
|
tmux.send_keys 'C-r'
|
||||||
tmux.until { |lines| assert_equal 1, lines.item_count }
|
tmux.until { |lines| assert_equal 1, lines.item_count }
|
||||||
tmux.send_keys 'C-c'
|
tmux.send_keys 'C-c'
|
||||||
tmux.send_keys 'C-l', 'closed'
|
tmux.send_keys 'C-l', 'closed'
|
||||||
tmux.until { |lines| assert_includes lines[0], 'closed' }
|
tmux.until { |lines| assert_includes lines[0], 'closed' }
|
||||||
wait { refute system("pgrep -f #{script}") }
|
wait { refute system("pgrep -f #{tempname}") }
|
||||||
ensure
|
ensure
|
||||||
system("pkill -9 -f #{script}")
|
system("pkill -9 -f #{tempname}")
|
||||||
FileUtils.rm_f(script)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_kill_reload_command_on_accept
|
def test_kill_reload_command_on_accept
|
||||||
script = tempname + '.sh'
|
writelines(['#!/usr/bin/env bash',
|
||||||
writelines(script,
|
|
||||||
['#!/usr/bin/env bash',
|
|
||||||
"echo 'Started'",
|
"echo 'Started'",
|
||||||
'while :; do sleep 1; done'])
|
'while :; do sleep 1; done'])
|
||||||
system("chmod +x #{script}")
|
system("chmod +x #{tempname}")
|
||||||
|
|
||||||
tmux.send_keys "seq 1 3 | #{fzf("--bind 'ctrl-r:reload(#{script})'")}", :Enter
|
tmux.send_keys "seq 1 3 | #{fzf("--bind 'ctrl-r:reload(#{tempname})'")}", :Enter
|
||||||
tmux.until { |lines| assert_equal 3, lines.item_count }
|
tmux.until { |lines| assert_equal 3, lines.item_count }
|
||||||
tmux.send_keys 'C-r'
|
tmux.send_keys 'C-r'
|
||||||
tmux.until { |lines| assert_equal 1, lines.item_count }
|
tmux.until { |lines| assert_equal 1, lines.item_count }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
assert_equal 'Started', readonce.chomp
|
assert_equal 'Started', fzf_output
|
||||||
wait { refute system("pgrep -f #{script}") }
|
wait { refute system("pgrep -f #{tempname}") }
|
||||||
ensure
|
ensure
|
||||||
system("pkill -9 -f #{script}")
|
system("pkill -9 -f #{tempname}")
|
||||||
FileUtils.rm_f(script)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_preview_header
|
def test_preview_header
|
||||||
@@ -2693,6 +2686,13 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_change_preview_window_should_not_reset_change_preview
|
||||||
|
tmux.send_keys "#{FZF} --preview-window up,border-none --bind 'start:change-preview(echo hello)' --bind 'enter:change-preview-window(border-left)'", :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines, 'hello' }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines, '│ hello' }
|
||||||
|
end
|
||||||
|
|
||||||
def test_change_preview_window_rotate
|
def test_change_preview_window_rotate
|
||||||
tmux.send_keys "seq 100 | #{FZF} --preview-window left,border-none --preview 'echo hello' --bind '" \
|
tmux.send_keys "seq 100 | #{FZF} --preview-window left,border-none --preview 'echo hello' --bind '" \
|
||||||
"a:change-preview-window(right|down|up|hidden|)'", :Enter
|
"a:change-preview-window(right|down|up|hidden|)'", :Enter
|
||||||
@@ -2752,8 +2752,9 @@ class TestGoFZF < TestBase
|
|||||||
|
|
||||||
def assert_block(expected, lines)
|
def assert_block(expected, lines)
|
||||||
cols = expected.lines.map(&:chomp).map(&:length).max
|
cols = expected.lines.map(&:chomp).map(&:length).max
|
||||||
actual = lines.reverse.take(expected.lines.length).reverse.map { _1[0, cols].rstrip + "\n" }.join
|
top = lines.take(expected.lines.length).map { _1[0, cols].rstrip + "\n" }.join
|
||||||
assert_equal_org expected, actual
|
bottom = lines.reverse.take(expected.lines.length).reverse.map { _1[0, cols].rstrip + "\n" }.join
|
||||||
|
assert_includes [top, bottom], expected
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_height_range_fit
|
def test_height_range_fit
|
||||||
@@ -2970,6 +2971,13 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { assert_match(%r{[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏] 100/100}, _1[-1]) }
|
tmux.until { assert_match(%r{[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏] 100/100}, _1[-1]) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_info_inline_right_clearance
|
||||||
|
tmux.send_keys "seq 100000 | #{FZF} --info inline-right", :Enter
|
||||||
|
tmux.until { assert_match(%r{100000/100000}, _1[-1]) }
|
||||||
|
tmux.send_keys 'x'
|
||||||
|
tmux.until { assert_match(%r{ 0/100000}, _1[-1]) }
|
||||||
|
end
|
||||||
|
|
||||||
def test_prev_next_selected
|
def test_prev_next_selected
|
||||||
tmux.send_keys 'seq 10 | fzf --multi --bind ctrl-n:next-selected,ctrl-p:prev-selected', :Enter
|
tmux.send_keys 'seq 10 | fzf --multi --bind ctrl-n:next-selected,ctrl-p:prev-selected', :Enter
|
||||||
tmux.until { |lines| assert_equal 10, lines.item_count }
|
tmux.until { |lines| assert_equal 10, lines.item_count }
|
||||||
@@ -3261,6 +3269,55 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys '99'
|
tmux.send_keys '99'
|
||||||
tmux.until { |lines| assert(lines.any? { |line| line.include?('0 / 0') }) }
|
tmux.until { |lines| assert(lines.any? { |line| line.include?('0 / 0') }) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_fzf_multi_line
|
||||||
|
tmux.send_keys %[(echo -en '0\\0'; echo -en '1\\n2\\0'; seq 1000) | fzf --read0 --multi --bind load:select-all --border rounded], :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ ┃998
|
||||||
|
│ ┃999
|
||||||
|
│ ┃1000
|
||||||
|
│ ╹
|
||||||
|
│ ╻1
|
||||||
|
│ ╹2
|
||||||
|
│ >>0
|
||||||
|
│ 3/3 (3)
|
||||||
|
│ >
|
||||||
|
╰───────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, _1) }
|
||||||
|
tmux.send_keys :Up, :Up
|
||||||
|
block = <<~BLOCK
|
||||||
|
╭───────
|
||||||
|
│ >╻1
|
||||||
|
│ >┃2
|
||||||
|
│ >┃3
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, _1) }
|
||||||
|
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ >┃
|
||||||
|
│
|
||||||
|
│ >
|
||||||
|
╰───
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_fzf_multi_line_reverse
|
||||||
|
tmux.send_keys %[(echo -en '0\\0'; echo -en '1\\n2\\0'; seq 1000) | fzf --read0 --multi --bind load:select-all --border rounded --reverse], :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
╭───────────
|
||||||
|
│ >
|
||||||
|
│ 3/3 (3)
|
||||||
|
│ >>0
|
||||||
|
│ ╻1
|
||||||
|
│ ╹2
|
||||||
|
│ ╻1
|
||||||
|
│ ┃2
|
||||||
|
│ ┃3
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, _1) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module TestShell
|
module TestShell
|
||||||
@@ -3299,7 +3356,7 @@ module TestShell
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_ctrl_t_unicode
|
def test_ctrl_t_unicode
|
||||||
writelines(tempname, ['fzf-unicode 테스트1', 'fzf-unicode 테스트2'])
|
writelines(['fzf-unicode 테스트1', 'fzf-unicode 테스트2'])
|
||||||
set_var('FZF_CTRL_T_COMMAND', "cat #{tempname}")
|
set_var('FZF_CTRL_T_COMMAND', "cat #{tempname}")
|
||||||
|
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
@@ -3383,12 +3440,16 @@ module TestShell
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_ctrl_r_multiline
|
def test_ctrl_r_multiline
|
||||||
|
# NOTE: Current bash implementation shows an extra new line if there's
|
||||||
|
# only entry in the history
|
||||||
|
tmux.send_keys ':', :Enter
|
||||||
tmux.send_keys 'echo "foo', :Enter, 'bar"', :Enter
|
tmux.send_keys 'echo "foo', :Enter, 'bar"', :Enter
|
||||||
tmux.until { |lines| assert_equal %w[foo bar], lines[-2..] }
|
tmux.until { |lines| assert_equal %w[foo bar], lines[-2..] }
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys 'C-r'
|
tmux.send_keys 'C-r'
|
||||||
tmux.until { |lines| assert_equal '>', lines[-1] }
|
tmux.until { |lines| assert_equal '>', lines[-1] }
|
||||||
tmux.send_keys 'foo bar'
|
tmux.send_keys 'foo bar'
|
||||||
|
tmux.until { |lines| assert lines[-4]&.match?(/"foo/) } unless shell == :zsh
|
||||||
tmux.until { |lines| assert lines[-3]&.match?(/bar"␊?/) }
|
tmux.until { |lines| assert lines[-3]&.match?(/bar"␊?/) }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until { |lines| assert lines[-1]&.match?(/bar"␊?/) }
|
tmux.until { |lines| assert lines[-1]&.match?(/bar"␊?/) }
|
||||||
@@ -3728,7 +3789,7 @@ unset <%= UNSETS.join(' ') %>
|
|||||||
unset $(env | sed -n /^_fzf_orig/s/=.*//p)
|
unset $(env | sed -n /^_fzf_orig/s/=.*//p)
|
||||||
unset $(declare -F | sed -n "/_fzf/s/.*-f //p")
|
unset $(declare -F | sed -n "/_fzf/s/.*-f //p")
|
||||||
|
|
||||||
export FZF_DEFAULT_OPTS=--no-scrollbar
|
export FZF_DEFAULT_OPTS="--no-scrollbar --pointer '>' --marker '>'"
|
||||||
|
|
||||||
# Setup fzf
|
# Setup fzf
|
||||||
# ---------
|
# ---------
|
||||||
|
|||||||
Reference in New Issue
Block a user