mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-14 14:23:47 -05:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4138333f5c | ||
|
|
61339a8ae2 | ||
|
|
50eb2e3855 | ||
|
|
5fc78e4584 | ||
|
|
2736a2f69e | ||
|
|
179993f0cd | ||
|
|
b734f657f9 | ||
|
|
c29d7d02c2 | ||
|
|
3df6b2a58c | ||
|
|
b8aa2d2c32 | ||
|
|
0ff885461b | ||
|
|
ca43f95fb1 | ||
|
|
09700f676b | ||
|
|
4271e9cffa | ||
|
|
f3dc8a10d5 | ||
|
|
00fb486f6a | ||
|
|
4173e94c6f | ||
|
|
261d3d3340 | ||
|
|
15e20fcae1 | ||
|
|
f4f47f5fe3 | ||
|
|
71d11de7ca | ||
|
|
88d74a15aa | ||
|
|
0f02fc0c77 | ||
|
|
3f90fb42d8 | ||
|
|
9bd8988300 | ||
|
|
3c804bcfec | ||
|
|
cca4cdc4f1 | ||
|
|
8f899aaf8a | ||
|
|
e53b4bb439 | ||
|
|
ab247a1309 | ||
|
|
c21e9edad4 | ||
|
|
9c21a20f8b | ||
|
|
7191ebb615 | ||
|
|
a74731d7f5 | ||
|
|
e086f0b3fe | ||
|
|
8255aa23f4 | ||
|
|
a4bc08f5a3 | ||
|
|
7e5aa1e2a5 |
6
.github/dependabot.yml
vendored
Normal file
6
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "gomod"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
1
.github/workflows/codeql-analysis.yml
vendored
1
.github/workflows/codeql-analysis.yml
vendored
@@ -6,6 +6,7 @@ on:
|
|||||||
branches: [ master, devel ]
|
branches: [ master, devel ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
analyze:
|
analyze:
|
||||||
|
|||||||
1
.github/workflows/linux.yml
vendored
1
.github/workflows/linux.yml
vendored
@@ -6,6 +6,7 @@ on:
|
|||||||
branches: [ master, devel ]
|
branches: [ master, devel ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|||||||
1
.github/workflows/macos.yml
vendored
1
.github/workflows/macos.yml
vendored
@@ -6,6 +6,7 @@ on:
|
|||||||
branches: [ master, devel ]
|
branches: [ master, devel ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ list without restarting fzf.
|
|||||||
|
|
||||||
### Toggling between data sources
|
### Toggling between data sources
|
||||||
|
|
||||||
You're not limiited to just one reload binding. Set up multiple bindings so
|
You're not limited to just one reload binding. Set up multiple bindings so
|
||||||
you can switch between data sources.
|
you can switch between data sources.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -349,7 +349,7 @@ IFS=: read -ra selected < <(
|
|||||||
fzf can kill the initial Ripgrep process it starts with the initial query.
|
fzf can kill the initial Ripgrep process it starts with the initial query.
|
||||||
Otherwise, the initial Ripgrep process will keep consuming system resources
|
Otherwise, the initial Ripgrep process will keep consuming system resources
|
||||||
even after `reload` is triggered.
|
even after `reload` is triggered.
|
||||||
- Filtering is no longer a responsibitiliy of fzf; hence `--disabled`
|
- Filtering is no longer a responsibility of fzf; hence `--disabled`
|
||||||
- `{q}` in the reload command evaluates to the query string on fzf prompt.
|
- `{q}` in the reload command evaluates to the query string on fzf prompt.
|
||||||
- `sleep 0.1` in the reload command is for "debouncing". This small delay will
|
- `sleep 0.1` in the reload command is for "debouncing". This small delay will
|
||||||
reduce the number of intermediate Ripgrep processes while we're typing in
|
reduce the number of intermediate Ripgrep processes while we're typing in
|
||||||
|
|||||||
34
CHANGELOG.md
34
CHANGELOG.md
@@ -1,6 +1,35 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.27.3
|
||||||
|
------
|
||||||
|
- Preview window is `hidden` by default when there are `preview` bindings but
|
||||||
|
`--preview` command is not given
|
||||||
|
- Fixed bug where `{n}` is not properly reset on `reload`
|
||||||
|
- Fixed bug where spinner is not displayed on `reload`
|
||||||
|
- Enhancements in tcell renderer for Windows (#2616)
|
||||||
|
- Vim plugin
|
||||||
|
- `sinklist` is added as a synonym to `sink*` so that it's easier to add
|
||||||
|
a function to a spec dictionary
|
||||||
|
```vim
|
||||||
|
let spec = { 'source': 'ls', 'options': ['--multi', '--preview', 'cat {}'] }
|
||||||
|
function spec.sinklist(matches)
|
||||||
|
echom string(a:matches)
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
call fzf#run(fzf#wrap(spec))
|
||||||
|
```
|
||||||
|
- Vim 7 compatibility
|
||||||
|
|
||||||
|
0.27.2
|
||||||
|
------
|
||||||
|
- 16 base ANSI colors can be specified by their names
|
||||||
|
```sh
|
||||||
|
fzf --color fg:3,fg+:11
|
||||||
|
fzf --color fg:yellow,fg+:bright-yellow
|
||||||
|
```
|
||||||
|
- Fix bug where `--read0` not properly displaying long lines
|
||||||
|
|
||||||
0.27.1
|
0.27.1
|
||||||
------
|
------
|
||||||
- Added `unbind` action. In the following Ripgrep launcher example, you can
|
- Added `unbind` action. In the following Ripgrep launcher example, you can
|
||||||
@@ -12,7 +41,7 @@ CHANGELOG
|
|||||||
" fzf will read the stream file while allowing other processes to append to it
|
" fzf will read the stream file while allowing other processes to append to it
|
||||||
call fzf#run({'source': 'cat /dev/null > /tmp/stream; tail -f /tmp/stream'})
|
call fzf#run({'source': 'cat /dev/null > /tmp/stream; tail -f /tmp/stream'})
|
||||||
```
|
```
|
||||||
- It is now possible to open popup window relative to the currrent window
|
- It is now possible to open popup window relative to the current window
|
||||||
```vim
|
```vim
|
||||||
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true, 'yoffset': 1.0 } }
|
let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6, 'relative': v:true, 'yoffset': 1.0 } }
|
||||||
```
|
```
|
||||||
@@ -62,7 +91,7 @@ CHANGELOG
|
|||||||
```
|
```
|
||||||
- Added `select` and `deselect` action for unconditionally selecting or
|
- Added `select` and `deselect` action for unconditionally selecting or
|
||||||
deselecting a single item in `--multi` mode. Complements `toggle` action.
|
deselecting a single item in `--multi` mode. Complements `toggle` action.
|
||||||
- Sigificant performance improvement in ANSI code processing
|
- Significant performance improvement in ANSI code processing
|
||||||
- Bug fixes and improvements
|
- Bug fixes and improvements
|
||||||
- Built with Go 1.16
|
- Built with Go 1.16
|
||||||
|
|
||||||
@@ -1144,4 +1173,3 @@ add `--sync` option to re-enable buffering.
|
|||||||
### Improvements
|
### Improvements
|
||||||
- `--select-1` and `--exit-0` will start finder immediately when the condition
|
- `--select-1` and `--exit-0` will start finder immediately when the condition
|
||||||
cannot be met
|
cannot be met
|
||||||
|
|
||||||
|
|||||||
6
Makefile
6
Makefile
@@ -34,6 +34,7 @@ BINARYARM6 := fzf-$(GOOS)_arm6
|
|||||||
BINARYARM7 := fzf-$(GOOS)_arm7
|
BINARYARM7 := fzf-$(GOOS)_arm7
|
||||||
BINARYARM8 := fzf-$(GOOS)_arm8
|
BINARYARM8 := fzf-$(GOOS)_arm8
|
||||||
BINARYPPC64LE := fzf-$(GOOS)_ppc64le
|
BINARYPPC64LE := fzf-$(GOOS)_ppc64le
|
||||||
|
BINARYRISCV64 := fzf-$(GOOS)_riscv64
|
||||||
|
|
||||||
# https://en.wikipedia.org/wiki/Uname
|
# https://en.wikipedia.org/wiki/Uname
|
||||||
UNAME_M := $(shell uname -m)
|
UNAME_M := $(shell uname -m)
|
||||||
@@ -59,6 +60,8 @@ else ifeq ($(UNAME_M),aarch64)
|
|||||||
BINARY := $(BINARYARM8)
|
BINARY := $(BINARYARM8)
|
||||||
else ifeq ($(UNAME_M),ppc64le)
|
else ifeq ($(UNAME_M),ppc64le)
|
||||||
BINARY := $(BINARYPPC64LE)
|
BINARY := $(BINARYPPC64LE)
|
||||||
|
else ifeq ($(UNAME_M),riscv64)
|
||||||
|
BINARY := $(BINARYRISCV64)
|
||||||
else
|
else
|
||||||
$(error Build on $(UNAME_M) is not supported, yet.)
|
$(error Build on $(UNAME_M) is not supported, yet.)
|
||||||
endif
|
endif
|
||||||
@@ -141,6 +144,9 @@ target/$(BINARYARM8): $(SOURCES)
|
|||||||
target/$(BINARYPPC64LE): $(SOURCES)
|
target/$(BINARYPPC64LE): $(SOURCES)
|
||||||
GOARCH=ppc64le $(GO) build $(BUILD_FLAGS) -o $@
|
GOARCH=ppc64le $(GO) build $(BUILD_FLAGS) -o $@
|
||||||
|
|
||||||
|
target/$(BINARYRISCV64): $(SOURCES)
|
||||||
|
GOARCH=riscv64 $(GO) build $(BUILD_FLAGS) -o $@
|
||||||
|
|
||||||
bin/fzf: target/$(BINARY) | bin
|
bin/fzf: target/$(BINARY) | bin
|
||||||
cp -f target/$(BINARY) bin/fzf
|
cp -f target/$(BINARY) bin/fzf
|
||||||
|
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ The following table summarizes the available options.
|
|||||||
| `source` | list | Vim list as input to fzf |
|
| `source` | list | Vim list as input to fzf |
|
||||||
| `sink` | string | Vim command to handle the selected item (e.g. `e`, `tabe`) |
|
| `sink` | string | Vim command to handle the selected item (e.g. `e`, `tabe`) |
|
||||||
| `sink` | funcref | Reference to function to process each selected item |
|
| `sink` | funcref | Reference to function to process each selected item |
|
||||||
| `sink*` | funcref | Similar to `sink`, but takes the list of output lines at once |
|
| `sinklist` (or `sink*`) | funcref | Similar to `sink`, but takes the list of output lines at once |
|
||||||
| `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%`) |
|
||||||
@@ -387,7 +387,7 @@ command! -bang -complete=dir -nargs=? LS
|
|||||||
|
|
||||||
- `g:fzf_layout`
|
- `g:fzf_layout`
|
||||||
- `g:fzf_action`
|
- `g:fzf_action`
|
||||||
- **Works only when no custom `sink` (or `sink*`) is provided**
|
- **Works only when no custom `sink` (or `sinklist`) is provided**
|
||||||
- Having custom sink usually means that each entry is not an ordinary
|
- Having custom sink usually means that each entry is not an ordinary
|
||||||
file path (e.g. name of color scheme), so we can't blindly apply the
|
file path (e.g. name of color scheme), so we can't blindly apply the
|
||||||
same strategy (i.e. `tabedit some-color-scheme` doesn't make sense)
|
same strategy (i.e. `tabedit some-color-scheme` doesn't make sense)
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
|
|||||||
| Nix | NixOS, etc. | `nix-env -iA nixpkgs.fzf` |
|
| Nix | NixOS, etc. | `nix-env -iA nixpkgs.fzf` |
|
||||||
| Pacman | Arch Linux | `sudo pacman -S fzf` |
|
| Pacman | Arch Linux | `sudo pacman -S fzf` |
|
||||||
| pkg | FreeBSD | `pkg install fzf` |
|
| pkg | FreeBSD | `pkg install fzf` |
|
||||||
|
| pkgin | NetBSD | `pkgin install fzf` |
|
||||||
| pkg_add | OpenBSD | `pkg_add fzf` |
|
| pkg_add | OpenBSD | `pkg_add fzf` |
|
||||||
| XBPS | Void Linux | `sudo xbps-install -S fzf` |
|
| XBPS | Void Linux | `sudo xbps-install -S fzf` |
|
||||||
| Zypper | openSUSE | `sudo zypper install fzf` |
|
| Zypper | openSUSE | `sudo zypper install fzf` |
|
||||||
@@ -201,7 +202,7 @@ vim $(fzf)
|
|||||||
|
|
||||||
#### Using the finder
|
#### Using the finder
|
||||||
|
|
||||||
- `CTRL-J` / `CTRL-K` (or `CTRL-N` / `CTRL-P`) to move cursor up and down
|
- `CTRL-K` / `CTRL-J` (or `CTRL-P` / `CTRL-N`) to move cursor up and down
|
||||||
- `Enter` key to select the item, `CTRL-C` / `CTRL-G` / `ESC` to exit
|
- `Enter` key to select the item, `CTRL-C` / `CTRL-G` / `ESC` to exit
|
||||||
- On multi-select mode (`-m`), `TAB` and `Shift-TAB` to mark multiple items
|
- On multi-select mode (`-m`), `TAB` and `Shift-TAB` to mark multiple items
|
||||||
- Emacs style key bindings
|
- Emacs style key bindings
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ cleanup() {
|
|||||||
resize-pane -Z
|
resize-pane -Z
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ $# -gt 0 ]; then
|
if [[ $# -gt 0 ]]; then
|
||||||
trap - EXIT
|
trap - EXIT
|
||||||
exit 130
|
exit 130
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -300,7 +300,7 @@ The following table summarizes the available options.
|
|||||||
`source` | list | Vim list as input to fzf
|
`source` | list | Vim list as input to fzf
|
||||||
`sink` | string | Vim command to handle the selected item (e.g. `e` , `tabe` )
|
`sink` | string | Vim command to handle the selected item (e.g. `e` , `tabe` )
|
||||||
`sink` | funcref | Reference to function to process each selected item
|
`sink` | funcref | Reference to function to process each selected item
|
||||||
`sink*` | funcref | Similar to `sink` , but takes the list of output lines at once
|
`sinklist` (or `sink*` ) | funcref | Similar to `sink` , but takes the list of output lines at once
|
||||||
`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%` )
|
||||||
|
|||||||
8
go.mod
8
go.mod
@@ -3,13 +3,13 @@ module github.com/junegunn/fzf
|
|||||||
require (
|
require (
|
||||||
github.com/gdamore/tcell v1.4.0
|
github.com/gdamore/tcell v1.4.0
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.12
|
github.com/mattn/go-isatty v0.0.14
|
||||||
github.com/mattn/go-runewidth v0.0.12
|
github.com/mattn/go-runewidth v0.0.13
|
||||||
github.com/mattn/go-shellwords v1.0.11
|
github.com/mattn/go-shellwords v1.0.12
|
||||||
github.com/rivo/uniseg v0.2.0
|
github.com/rivo/uniseg v0.2.0
|
||||||
github.com/saracen/walker v0.1.2
|
github.com/saracen/walker v0.1.2
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
||||||
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6
|
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6
|
||||||
golang.org/x/text v0.3.6 // indirect
|
golang.org/x/text v0.3.6 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
18
go.sum
18
go.sum
@@ -5,14 +5,13 @@ github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404Z
|
|||||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxmAOow=
|
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-shellwords v1.0.11 h1:vCoR9VPpsk/TZFW2JwK5I9S0xdrtUq2bph6/YjEPnaw=
|
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
||||||
github.com/mattn/go-shellwords v1.0.11/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
||||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/saracen/walker v0.1.2 h1:/o1TxP82n8thLvmL4GpJXduYaRmJ7qXp8u9dSlV0zmo=
|
github.com/saracen/walker v0.1.2 h1:/o1TxP82n8thLvmL4GpJXduYaRmJ7qXp8u9dSlV0zmo=
|
||||||
@@ -21,10 +20,9 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
||||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 h1:EC6+IGYTjPpRfv9a2b/6Puw0W+hLtAhkV1tPsXhutqs=
|
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 h1:EC6+IGYTjPpRfv9a2b/6Puw0W+hLtAhkV1tPsXhutqs=
|
||||||
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
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=
|
||||||
|
|||||||
2
install
2
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.27.1
|
version=0.27.3
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
$version="0.27.1"
|
$version="0.27.3"
|
||||||
|
|
||||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
|
||||||
|
|||||||
@@ -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 2021" "fzf 0.27.1" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf-tmux 1 "Oct 2021" "fzf 0.27.3" "fzf-tmux - open fzf in tmux split pane"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf-tmux - open fzf in tmux split pane
|
fzf-tmux - open fzf in tmux split pane
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf 1 "May 2021" "fzf 0.27.1" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Oct 2021" "fzf 0.27.3" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -340,6 +340,22 @@ color mappings.
|
|||||||
\fB-1 \fRDefault terminal foreground/background color
|
\fB-1 \fRDefault terminal foreground/background color
|
||||||
\fB \fR(or the original color of the text)
|
\fB \fR(or the original color of the text)
|
||||||
\fB0 ~ 15 \fR16 base colors
|
\fB0 ~ 15 \fR16 base colors
|
||||||
|
\fBblack\fR
|
||||||
|
\fBred\fR
|
||||||
|
\fBgreen\fR
|
||||||
|
\fByellow\fR
|
||||||
|
\fBblue\fR
|
||||||
|
\fBmagenta\fR
|
||||||
|
\fBcyan\fR
|
||||||
|
\fBwhite\fR
|
||||||
|
\fBbright-black\fR (gray | grey)
|
||||||
|
\fBbright-red\fR
|
||||||
|
\fBbright-green\fR
|
||||||
|
\fBbright-yellow\fR
|
||||||
|
\fBbright-blue\fR
|
||||||
|
\fBbright-magenta\fR
|
||||||
|
\fBbright-cyan\fR
|
||||||
|
\fBbright-white\fR
|
||||||
\fB16 ~ 255 \fRANSI 256 colors
|
\fB16 ~ 255 \fRANSI 256 colors
|
||||||
\fB#rrggbb \fR24-bit colors
|
\fB#rrggbb \fR24-bit colors
|
||||||
|
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ function s:get_version(bin)
|
|||||||
if has_key(s:versions, a:bin)
|
if has_key(s:versions, a:bin)
|
||||||
return s:versions[a:bin]
|
return s:versions[a:bin]
|
||||||
end
|
end
|
||||||
let command = a:bin . ' --version'
|
let command = a:bin . ' --version --no-height'
|
||||||
let output = systemlist(command)
|
let output = systemlist(command)
|
||||||
if v:shell_error || empty(output)
|
if v:shell_error || empty(output)
|
||||||
return ''
|
return ''
|
||||||
@@ -419,13 +419,13 @@ function! fzf#wrap(...)
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
" Action: g:fzf_action
|
" Action: g:fzf_action
|
||||||
if !s:has_any(opts, ['sink', 'sink*'])
|
if !s:has_any(opts, ['sink', 'sinklist', 'sink*'])
|
||||||
let opts._action = get(g:, 'fzf_action', s:default_action)
|
let opts._action = get(g:, 'fzf_action', s:default_action)
|
||||||
let opts.options .= ' --expect='.join(keys(opts._action), ',')
|
let opts.options .= ' --expect='.join(keys(opts._action), ',')
|
||||||
function! opts.sink(lines) abort
|
function! opts.sinklist(lines) abort
|
||||||
return s:common_sink(self._action, a:lines)
|
return s:common_sink(self._action, a:lines)
|
||||||
endfunction
|
endfunction
|
||||||
let opts['sink*'] = remove(opts, 'sink')
|
let opts['sink*'] = opts.sinklist " For backward compatibility
|
||||||
endif
|
endif
|
||||||
|
|
||||||
return opts
|
return opts
|
||||||
@@ -519,7 +519,8 @@ finally
|
|||||||
if len(prev_default_command)
|
if len(prev_default_command)
|
||||||
let $FZF_DEFAULT_COMMAND = prev_default_command
|
let $FZF_DEFAULT_COMMAND = prev_default_command
|
||||||
else
|
else
|
||||||
execute 'unlet $FZF_DEFAULT_COMMAND'
|
let $FZF_DEFAULT_COMMAND = ''
|
||||||
|
silent! execute 'unlet $FZF_DEFAULT_COMMAND'
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
let [&shell, &shellslash, &shellcmdflag, &shellxquote] = [shell, shellslash, shellcmdflag, shellxquote]
|
let [&shell, &shellslash, &shellcmdflag, &shellxquote] = [shell, shellslash, shellcmdflag, shellxquote]
|
||||||
@@ -943,6 +944,8 @@ function! s:callback(dict, lines) abort
|
|||||||
endif
|
endif
|
||||||
if has_key(a:dict, 'sink*')
|
if has_key(a:dict, 'sink*')
|
||||||
call a:dict['sink*'](a:lines)
|
call a:dict['sink*'](a:lines)
|
||||||
|
elseif has_key(a:dict, 'sinklist')
|
||||||
|
call a:dict['sinklist'](a:lines)
|
||||||
endif
|
endif
|
||||||
catch
|
catch
|
||||||
if stridx(v:exception, ':E325:') < 0
|
if stridx(v:exception, ':E325:') < 0
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ fi
|
|||||||
bind '"\e[0n": redraw-current-line'
|
bind '"\e[0n": redraw-current-line'
|
||||||
|
|
||||||
__fzf_comprun() {
|
__fzf_comprun() {
|
||||||
if [ "$(type -t _fzf_comprun 2>&1)" = function ]; then
|
if [[ "$(type -t _fzf_comprun 2>&1)" = function ]]; then
|
||||||
_fzf_comprun "$@"
|
_fzf_comprun "$@"
|
||||||
elif [ -n "$TMUX_PANE" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "$FZF_TMUX_OPTS" ]; }; then
|
elif [[ -n "$TMUX_PANE" ]] && { [[ "${FZF_TMUX:-0}" != 0 ]] || [[ -n "$FZF_TMUX_OPTS" ]]; }; then
|
||||||
shift
|
shift
|
||||||
fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- "$@"
|
fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- "$@"
|
||||||
else
|
else
|
||||||
@@ -140,14 +140,14 @@ _fzf_handle_dynamic_completion() {
|
|||||||
orig_cmd="$1"
|
orig_cmd="$1"
|
||||||
orig_var="_fzf_orig_completion_$cmd"
|
orig_var="_fzf_orig_completion_$cmd"
|
||||||
orig="${!orig_var##*#}"
|
orig="${!orig_var##*#}"
|
||||||
if [ -n "$orig" ] && type "$orig" > /dev/null 2>&1; then
|
if [[ -n "$orig" ]] && type "$orig" > /dev/null 2>&1; then
|
||||||
$orig "$@"
|
$orig "$@"
|
||||||
elif [ -n "$_fzf_completion_loader" ]; then
|
elif [[ -n "$_fzf_completion_loader" ]]; then
|
||||||
orig_complete=$(complete -p "$orig_cmd" 2> /dev/null)
|
orig_complete=$(complete -p "$orig_cmd" 2> /dev/null)
|
||||||
_completion_loader "$@"
|
_completion_loader "$@"
|
||||||
ret=$?
|
ret=$?
|
||||||
# _completion_loader may not have updated completion for the command
|
# _completion_loader may not have updated completion for the command
|
||||||
if [ "$(complete -p "$orig_cmd" 2> /dev/null)" != "$orig_complete" ]; then
|
if [[ "$(complete -p "$orig_cmd" 2> /dev/null)" != "$orig_complete" ]]; then
|
||||||
__fzf_orig_completion < <(complete -p "$orig_cmd" 2> /dev/null)
|
__fzf_orig_completion < <(complete -p "$orig_cmd" 2> /dev/null)
|
||||||
if [[ "$__fzf_nospace_commands" = *" $orig_cmd "* ]]; then
|
if [[ "$__fzf_nospace_commands" = *" $orig_cmd "* ]]; then
|
||||||
eval "${orig_complete/ -F / -o nospace -F }"
|
eval "${orig_complete/ -F / -o nospace -F }"
|
||||||
@@ -171,17 +171,17 @@ __fzf_generic_path_completion() {
|
|||||||
|
|
||||||
[[ $base = *"/"* ]] && dir="$base"
|
[[ $base = *"/"* ]] && dir="$base"
|
||||||
while true; do
|
while true; do
|
||||||
if [ -z "$dir" ] || [ -d "$dir" ]; then
|
if [[ -z "$dir" ]] || [[ -d "$dir" ]]; then
|
||||||
leftover=${base/#"$dir"}
|
leftover=${base/#"$dir"}
|
||||||
leftover=${leftover/#\/}
|
leftover=${leftover/#\/}
|
||||||
[ -z "$dir" ] && dir='.'
|
[[ -z "$dir" ]] && dir='.'
|
||||||
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
[[ "$dir" != "/" ]] && dir="${dir/%\//}"
|
||||||
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
|
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
|
||||||
printf "%q$3 " "$item"
|
printf "%q$3 " "$item"
|
||||||
done)
|
done)
|
||||||
matches=${matches% }
|
matches=${matches% }
|
||||||
[[ -z "$3" ]] && [[ "$__fzf_nospace_commands" = *" ${COMP_WORDS[0]} "* ]] && matches="$matches "
|
[[ -z "$3" ]] && [[ "$__fzf_nospace_commands" = *" ${COMP_WORDS[0]} "* ]] && matches="$matches "
|
||||||
if [ -n "$matches" ]; then
|
if [[ -n "$matches" ]]; then
|
||||||
COMPREPLY=( "$matches" )
|
COMPREPLY=( "$matches" )
|
||||||
else
|
else
|
||||||
COMPREPLY=( "$cur" )
|
COMPREPLY=( "$cur" )
|
||||||
@@ -234,7 +234,7 @@ _fzf_complete() {
|
|||||||
|
|
||||||
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | tr '\n' ' ')
|
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | tr '\n' ' ')
|
||||||
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
|
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
|
||||||
if [ -n "$selected" ]; then
|
if [[ -n "$selected" ]]; then
|
||||||
COMPREPLY=("$selected")
|
COMPREPLY=("$selected")
|
||||||
else
|
else
|
||||||
COMPREPLY=("$cur")
|
COMPREPLY=("$cur")
|
||||||
@@ -330,7 +330,7 @@ __fzf_defc() {
|
|||||||
opts="$3"
|
opts="$3"
|
||||||
orig_var="_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}"
|
orig_var="_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}"
|
||||||
orig="${!orig_var}"
|
orig="${!orig_var}"
|
||||||
if [ -n "$orig" ]; then
|
if [[ -n "$orig" ]]; then
|
||||||
printf -v def "$orig" "$func"
|
printf -v def "$orig" "$func"
|
||||||
eval "$def"
|
eval "$def"
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ __fzf_select__() {
|
|||||||
if [[ $- =~ i ]]; then
|
if [[ $- =~ i ]]; then
|
||||||
|
|
||||||
__fzfcmd() {
|
__fzfcmd() {
|
||||||
[ -n "$TMUX_PANE" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "$FZF_TMUX_OPTS" ]; } &&
|
[[ -n "$TMUX_PANE" ]] && { [[ "${FZF_TMUX:-0}" != 0 ]] || [[ -n "$FZF_TMUX_OPTS" ]]; } &&
|
||||||
echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
|
echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ __fzf_history__() {
|
|||||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS +m --read0" $(__fzfcmd) --query "$READLINE_LINE"
|
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS +m --read0" $(__fzfcmd) --query "$READLINE_LINE"
|
||||||
) || return
|
) || return
|
||||||
READLINE_LINE=${output#*$'\t'}
|
READLINE_LINE=${output#*$'\t'}
|
||||||
if [ -z "$READLINE_POINT" ]; then
|
if [[ -z "$READLINE_POINT" ]]; then
|
||||||
echo "$READLINE_LINE"
|
echo "$READLINE_LINE"
|
||||||
else
|
else
|
||||||
READLINE_POINT=0x7fffffff
|
READLINE_POINT=0x7fffffff
|
||||||
@@ -66,7 +66,7 @@ bind -m vi-command '"\C-z": emacs-editing-mode'
|
|||||||
bind -m vi-insert '"\C-z": emacs-editing-mode'
|
bind -m vi-insert '"\C-z": emacs-editing-mode'
|
||||||
bind -m emacs-standard '"\C-z": vi-editing-mode'
|
bind -m emacs-standard '"\C-z": vi-editing-mode'
|
||||||
|
|
||||||
if [ "${BASH_VERSINFO[0]}" -lt 4 ]; then
|
if (( BASH_VERSINFO[0] < 4 )); then
|
||||||
# CTRL-T - Paste the selected file path into the command line
|
# CTRL-T - Paste the selected file path into the command line
|
||||||
bind -m emacs-standard '"\C-t": " \C-b\C-k \C-u`__fzf_select__`\e\C-e\er\C-a\C-y\C-h\C-e\e \C-y\ey\C-x\C-x\C-f"'
|
bind -m emacs-standard '"\C-t": " \C-b\C-k \C-u`__fzf_select__`\e\C-e\er\C-a\C-y\C-h\C-e\e \C-y\ey\C-x\C-x\C-f"'
|
||||||
bind -m vi-command '"\C-t": "\C-z\C-t\C-z"'
|
bind -m vi-command '"\C-t": "\C-z\C-t\C-z"'
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ type Result struct {
|
|||||||
const (
|
const (
|
||||||
scoreMatch = 16
|
scoreMatch = 16
|
||||||
scoreGapStart = -3
|
scoreGapStart = -3
|
||||||
scoreGapExtention = -1
|
scoreGapExtension = -1
|
||||||
|
|
||||||
// We prefer matches at the beginning of a word, but the bonus should not be
|
// We prefer matches at the beginning of a word, but the bonus should not be
|
||||||
// too great to prevent the longer acronym matches from always winning over
|
// too great to prevent the longer acronym matches from always winning over
|
||||||
@@ -125,16 +125,16 @@ const (
|
|||||||
// Edge-triggered bonus for matches in camelCase words.
|
// Edge-triggered bonus for matches in camelCase words.
|
||||||
// Compared to word-boundary case, they don't accompany single-character gaps
|
// Compared to word-boundary case, they don't accompany single-character gaps
|
||||||
// (e.g. FooBar vs. foo-bar), so we deduct bonus point accordingly.
|
// (e.g. FooBar vs. foo-bar), so we deduct bonus point accordingly.
|
||||||
bonusCamel123 = bonusBoundary + scoreGapExtention
|
bonusCamel123 = bonusBoundary + scoreGapExtension
|
||||||
|
|
||||||
// Minimum bonus point given to characters in consecutive chunks.
|
// Minimum bonus point given to characters in consecutive chunks.
|
||||||
// Note that bonus points for consecutive matches shouldn't have needed if we
|
// Note that bonus points for consecutive matches shouldn't have needed if we
|
||||||
// used fixed match score as in the original algorithm.
|
// used fixed match score as in the original algorithm.
|
||||||
bonusConsecutive = -(scoreGapStart + scoreGapExtention)
|
bonusConsecutive = -(scoreGapStart + scoreGapExtension)
|
||||||
|
|
||||||
// The first character in the typed pattern usually has more significance
|
// The first character in the typed pattern usually has more significance
|
||||||
// than the rest so it's important that it appears at special positions where
|
// than the rest so it's important that it appears at special positions where
|
||||||
// bonus points are given. e.g. "to-go" vs. "ongoing" on "og" or on "ogo".
|
// bonus points are given, e.g. "to-go" vs. "ongoing" on "og" or on "ogo".
|
||||||
// The amount of the extra bonus should be limited so that the gap penalty is
|
// The amount of the extra bonus should be limited so that the gap penalty is
|
||||||
// still respected.
|
// still respected.
|
||||||
bonusFirstCharMultiplier = 2
|
bonusFirstCharMultiplier = 2
|
||||||
@@ -424,7 +424,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
inGap = false
|
inGap = false
|
||||||
} else {
|
} else {
|
||||||
if inGap {
|
if inGap {
|
||||||
H0sub[off] = util.Max16(prevH0+scoreGapExtention, 0)
|
H0sub[off] = util.Max16(prevH0+scoreGapExtension, 0)
|
||||||
} else {
|
} else {
|
||||||
H0sub[off] = util.Max16(prevH0+scoreGapStart, 0)
|
H0sub[off] = util.Max16(prevH0+scoreGapStart, 0)
|
||||||
}
|
}
|
||||||
@@ -477,7 +477,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
var s1, s2, consecutive int16
|
var s1, s2, consecutive int16
|
||||||
|
|
||||||
if inGap {
|
if inGap {
|
||||||
s2 = Hleft[off] + scoreGapExtention
|
s2 = Hleft[off] + scoreGapExtension
|
||||||
} else {
|
} else {
|
||||||
s2 = Hleft[off] + scoreGapStart
|
s2 = Hleft[off] + scoreGapStart
|
||||||
}
|
}
|
||||||
@@ -598,7 +598,7 @@ func calculateScore(caseSensitive bool, normalize bool, text *util.Chars, patter
|
|||||||
pidx++
|
pidx++
|
||||||
} else {
|
} else {
|
||||||
if inGap {
|
if inGap {
|
||||||
score += scoreGapExtention
|
score += scoreGapExtension
|
||||||
} else {
|
} else {
|
||||||
score += scoreGapStart
|
score += scoreGapStart
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,10 +43,10 @@ func TestFuzzyMatch(t *testing.T) {
|
|||||||
for _, fn := range []Algo{FuzzyMatchV1, FuzzyMatchV2} {
|
for _, fn := range []Algo{FuzzyMatchV1, FuzzyMatchV2} {
|
||||||
for _, forward := range []bool{true, false} {
|
for _, forward := range []bool{true, false} {
|
||||||
assertMatch(t, fn, false, forward, "fooBarbaz1", "oBZ", 2, 9,
|
assertMatch(t, fn, false, forward, "fooBarbaz1", "oBZ", 2, 9,
|
||||||
scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtention*3)
|
scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtension*3)
|
||||||
assertMatch(t, fn, false, forward, "foo bar baz", "fbb", 0, 9,
|
assertMatch(t, fn, false, forward, "foo bar baz", "fbb", 0, 9,
|
||||||
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+
|
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+
|
||||||
bonusBoundary*2+2*scoreGapStart+4*scoreGapExtention)
|
bonusBoundary*2+2*scoreGapStart+4*scoreGapExtension)
|
||||||
assertMatch(t, fn, false, forward, "/AutomatorDocument.icns", "rdoc", 9, 13,
|
assertMatch(t, fn, false, forward, "/AutomatorDocument.icns", "rdoc", 9, 13,
|
||||||
scoreMatch*4+bonusCamel123+bonusConsecutive*2)
|
scoreMatch*4+bonusCamel123+bonusConsecutive*2)
|
||||||
assertMatch(t, fn, false, forward, "/man1/zshcompctl.1", "zshc", 6, 10,
|
assertMatch(t, fn, false, forward, "/man1/zshcompctl.1", "zshc", 6, 10,
|
||||||
@@ -54,18 +54,18 @@ func TestFuzzyMatch(t *testing.T) {
|
|||||||
assertMatch(t, fn, false, forward, "/.oh-my-zsh/cache", "zshc", 8, 13,
|
assertMatch(t, fn, false, forward, "/.oh-my-zsh/cache", "zshc", 8, 13,
|
||||||
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*3+scoreGapStart)
|
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*3+scoreGapStart)
|
||||||
assertMatch(t, fn, false, forward, "ab0123 456", "12356", 3, 10,
|
assertMatch(t, fn, false, forward, "ab0123 456", "12356", 3, 10,
|
||||||
scoreMatch*5+bonusConsecutive*3+scoreGapStart+scoreGapExtention)
|
scoreMatch*5+bonusConsecutive*3+scoreGapStart+scoreGapExtension)
|
||||||
assertMatch(t, fn, false, forward, "abc123 456", "12356", 3, 10,
|
assertMatch(t, fn, false, forward, "abc123 456", "12356", 3, 10,
|
||||||
scoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+bonusConsecutive+scoreGapStart+scoreGapExtention)
|
scoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+bonusConsecutive+scoreGapStart+scoreGapExtension)
|
||||||
assertMatch(t, fn, false, forward, "foo/bar/baz", "fbb", 0, 9,
|
assertMatch(t, fn, false, forward, "foo/bar/baz", "fbb", 0, 9,
|
||||||
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+
|
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+
|
||||||
bonusBoundary*2+2*scoreGapStart+4*scoreGapExtention)
|
bonusBoundary*2+2*scoreGapStart+4*scoreGapExtension)
|
||||||
assertMatch(t, fn, false, forward, "fooBarBaz", "fbb", 0, 7,
|
assertMatch(t, fn, false, forward, "fooBarBaz", "fbb", 0, 7,
|
||||||
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+
|
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+
|
||||||
bonusCamel123*2+2*scoreGapStart+2*scoreGapExtention)
|
bonusCamel123*2+2*scoreGapStart+2*scoreGapExtension)
|
||||||
assertMatch(t, fn, false, forward, "foo barbaz", "fbb", 0, 8,
|
assertMatch(t, fn, false, forward, "foo barbaz", "fbb", 0, 8,
|
||||||
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary+
|
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary+
|
||||||
scoreGapStart*2+scoreGapExtention*3)
|
scoreGapStart*2+scoreGapExtension*3)
|
||||||
assertMatch(t, fn, false, forward, "fooBar Baz", "foob", 0, 4,
|
assertMatch(t, fn, false, forward, "fooBar Baz", "foob", 0, 4,
|
||||||
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*3)
|
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*3)
|
||||||
assertMatch(t, fn, false, forward, "xFoo-Bar Baz", "foo-b", 1, 6,
|
assertMatch(t, fn, false, forward, "xFoo-Bar Baz", "foo-b", 1, 6,
|
||||||
@@ -73,13 +73,13 @@ func TestFuzzyMatch(t *testing.T) {
|
|||||||
bonusNonWord+bonusBoundary)
|
bonusNonWord+bonusBoundary)
|
||||||
|
|
||||||
assertMatch(t, fn, true, forward, "fooBarbaz", "oBz", 2, 9,
|
assertMatch(t, fn, true, forward, "fooBarbaz", "oBz", 2, 9,
|
||||||
scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtention*3)
|
scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtension*3)
|
||||||
assertMatch(t, fn, true, forward, "Foo/Bar/Baz", "FBB", 0, 9,
|
assertMatch(t, fn, true, forward, "Foo/Bar/Baz", "FBB", 0, 9,
|
||||||
scoreMatch*3+bonusBoundary*(bonusFirstCharMultiplier+2)+
|
scoreMatch*3+bonusBoundary*(bonusFirstCharMultiplier+2)+
|
||||||
scoreGapStart*2+scoreGapExtention*4)
|
scoreGapStart*2+scoreGapExtension*4)
|
||||||
assertMatch(t, fn, true, forward, "FooBarBaz", "FBB", 0, 7,
|
assertMatch(t, fn, true, forward, "FooBarBaz", "FBB", 0, 7,
|
||||||
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+bonusCamel123*2+
|
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+bonusCamel123*2+
|
||||||
scoreGapStart*2+scoreGapExtention*2)
|
scoreGapStart*2+scoreGapExtension*2)
|
||||||
assertMatch(t, fn, true, forward, "FooBar Baz", "FooB", 0, 4,
|
assertMatch(t, fn, true, forward, "FooBar Baz", "FooB", 0, 4,
|
||||||
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*2+
|
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*2+
|
||||||
util.Max(bonusCamel123, bonusBoundary))
|
util.Max(bonusCamel123, bonusBoundary))
|
||||||
@@ -99,7 +99,7 @@ func TestFuzzyMatch(t *testing.T) {
|
|||||||
func TestFuzzyMatchBackward(t *testing.T) {
|
func TestFuzzyMatchBackward(t *testing.T) {
|
||||||
assertMatch(t, FuzzyMatchV1, false, true, "foobar fb", "fb", 0, 4,
|
assertMatch(t, FuzzyMatchV1, false, true, "foobar fb", "fb", 0, 4,
|
||||||
scoreMatch*2+bonusBoundary*bonusFirstCharMultiplier+
|
scoreMatch*2+bonusBoundary*bonusFirstCharMultiplier+
|
||||||
scoreGapStart+scoreGapExtention)
|
scoreGapStart+scoreGapExtension)
|
||||||
assertMatch(t, FuzzyMatchV1, false, false, "foobar fb", "fb", 7, 9,
|
assertMatch(t, FuzzyMatchV1, false, false, "foobar fb", "fb", 7, 9,
|
||||||
scoreMatch*2+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary)
|
scoreMatch*2+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -17,11 +16,13 @@ import (
|
|||||||
//
|
//
|
||||||
// References:
|
// References:
|
||||||
// - https://github.com/gnachman/iTerm2
|
// - https://github.com/gnachman/iTerm2
|
||||||
// - http://ascii-table.com/ansi-escape-sequences.php
|
// - https://web.archive.org/web/20090204053813/http://ascii-table.com/ansi-escape-sequences.php
|
||||||
// - http://ascii-table.com/ansi-escape-sequences-vt-100.php
|
// (archived from http://ascii-table.com/ansi-escape-sequences.php)
|
||||||
|
// - https://web.archive.org/web/20090227051140/http://ascii-table.com/ansi-escape-sequences-vt-100.php
|
||||||
|
// (archived from http://ascii-table.com/ansi-escape-sequences-vt-100.php)
|
||||||
// - http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
|
// - http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
|
||||||
// - https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
// - https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||||
var ansiRegexRefence = regexp.MustCompile("(?:\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)")
|
var ansiRegexReference = regexp.MustCompile("(?:\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)")
|
||||||
|
|
||||||
func testParserReference(t testing.TB, str string) {
|
func testParserReference(t testing.TB, str string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
@@ -36,7 +37,7 @@ func testParserReference(t testing.TB, str string) {
|
|||||||
s := str
|
s := str
|
||||||
for i := 0; ; i++ {
|
for i := 0; ; i++ {
|
||||||
got := toSlice(nextAnsiEscapeSequence(s))
|
got := toSlice(nextAnsiEscapeSequence(s))
|
||||||
exp := ansiRegexRefence.FindStringIndex(s)
|
exp := ansiRegexReference.FindStringIndex(s)
|
||||||
|
|
||||||
equal := len(got) == len(exp)
|
equal := len(got) == len(exp)
|
||||||
if equal {
|
if equal {
|
||||||
@@ -207,7 +208,7 @@ func TestExtractColor(t *testing.T) {
|
|||||||
if output != "hello world" {
|
if output != "hello world" {
|
||||||
t.Errorf("Invalid output: %s %v", output, []rune(output))
|
t.Errorf("Invalid output: %s %v", output, []rune(output))
|
||||||
}
|
}
|
||||||
fmt.Println(src, ansiOffsets, clean)
|
t.Log(src, ansiOffsets, clean)
|
||||||
assertion(ansiOffsets, state)
|
assertion(ansiOffsets, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,7 +410,7 @@ func BenchmarkNextAnsiEscapeSequence_Regex(b *testing.B) {
|
|||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
s := ansiBenchmarkString
|
s := ansiBenchmarkString
|
||||||
for {
|
for {
|
||||||
a := ansiRegexRefence.FindStringIndex(s)
|
a := ansiRegexReference.FindStringIndex(s)
|
||||||
if len(a) == 0 {
|
if len(a) == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -233,6 +233,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
clearCache = util.Once(true)
|
clearCache = util.Once(true)
|
||||||
clearSelection = util.Once(true)
|
clearSelection = util.Once(true)
|
||||||
chunkList.Clear()
|
chunkList.Clear()
|
||||||
|
itemIndex = 0
|
||||||
header = make([]string, 0, opts.HeaderLines)
|
header = make([]string, 0, opts.HeaderLines)
|
||||||
go reader.restart(command)
|
go reader.restart(command)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -670,6 +670,38 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
|
|||||||
cattr.Attr |= tui.Blink
|
cattr.Attr |= tui.Blink
|
||||||
case "reverse":
|
case "reverse":
|
||||||
cattr.Attr |= tui.Reverse
|
cattr.Attr |= tui.Reverse
|
||||||
|
case "black":
|
||||||
|
cattr.Color = tui.Color(0)
|
||||||
|
case "red":
|
||||||
|
cattr.Color = tui.Color(1)
|
||||||
|
case "green":
|
||||||
|
cattr.Color = tui.Color(2)
|
||||||
|
case "yellow":
|
||||||
|
cattr.Color = tui.Color(3)
|
||||||
|
case "blue":
|
||||||
|
cattr.Color = tui.Color(4)
|
||||||
|
case "magenta":
|
||||||
|
cattr.Color = tui.Color(5)
|
||||||
|
case "cyan":
|
||||||
|
cattr.Color = tui.Color(6)
|
||||||
|
case "white":
|
||||||
|
cattr.Color = tui.Color(7)
|
||||||
|
case "bright-black", "gray", "grey":
|
||||||
|
cattr.Color = tui.Color(8)
|
||||||
|
case "bright-red":
|
||||||
|
cattr.Color = tui.Color(9)
|
||||||
|
case "bright-green":
|
||||||
|
cattr.Color = tui.Color(10)
|
||||||
|
case "bright-yellow":
|
||||||
|
cattr.Color = tui.Color(11)
|
||||||
|
case "bright-blue":
|
||||||
|
cattr.Color = tui.Color(12)
|
||||||
|
case "bright-magenta":
|
||||||
|
cattr.Color = tui.Color(13)
|
||||||
|
case "bright-cyan":
|
||||||
|
cattr.Color = tui.Color(14)
|
||||||
|
case "bright-white":
|
||||||
|
cattr.Color = tui.Color(15)
|
||||||
case "":
|
case "":
|
||||||
default:
|
default:
|
||||||
if rrggbb.MatchString(component) {
|
if rrggbb.MatchString(component) {
|
||||||
@@ -1560,7 +1592,7 @@ func validateSign(sign string, signOptName string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func postProcessOptions(opts *Options) {
|
func postProcessOptions(opts *Options) {
|
||||||
if !tui.IsLightRendererSupported() && opts.Height.size > 0 {
|
if !opts.Version && !tui.IsLightRendererSupported() && opts.Height.size > 0 {
|
||||||
errorExit("--height option is currently not supported on this platform")
|
errorExit("--height option is currently not supported on this platform")
|
||||||
}
|
}
|
||||||
// Default actions for CTRL-N / CTRL-P when --history is set
|
// Default actions for CTRL-N / CTRL-P when --history is set
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ func TestReadFromCommand(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Normal command
|
// Normal command
|
||||||
reader.fin(reader.readFromCommand(nil, `echo abc && echo def`))
|
reader.fin(reader.readFromCommand(nil, `echo abc&&echo def`))
|
||||||
if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" {
|
if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" {
|
||||||
t.Errorf("%s", strs)
|
t.Errorf("%s", strs)
|
||||||
}
|
}
|
||||||
|
|||||||
162
src/terminal.go
162
src/terminal.go
@@ -23,6 +23,26 @@ import (
|
|||||||
|
|
||||||
// import "github.com/pkg/profile"
|
// import "github.com/pkg/profile"
|
||||||
|
|
||||||
|
/*
|
||||||
|
Placeholder regex is used to extract placeholders from fzf's template
|
||||||
|
strings. Acts as input validation for parsePlaceholder function.
|
||||||
|
Describes the syntax, but it is fairly lenient.
|
||||||
|
|
||||||
|
The following pseudo regex has been reverse engineered from the
|
||||||
|
implementation. It is overly strict, but better describes whats possible.
|
||||||
|
As such it is not useful for validation, but rather to generate test
|
||||||
|
cases for example.
|
||||||
|
|
||||||
|
\\?(?: # escaped type
|
||||||
|
{\+?s?f?RANGE(?:,RANGE)*} # token type
|
||||||
|
|{q} # query type
|
||||||
|
|{\+?n?f?} # item type (notice no mandatory element inside brackets)
|
||||||
|
)
|
||||||
|
RANGE = (?:
|
||||||
|
(?:-?[0-9]+)?\.\.(?:-?[0-9]+)? # ellipsis syntax for token range (x..y)
|
||||||
|
|-?[0-9]+ # shorthand syntax (x..x)
|
||||||
|
)
|
||||||
|
*/
|
||||||
var placeholder *regexp.Regexp
|
var placeholder *regexp.Regexp
|
||||||
var whiteSuffix *regexp.Regexp
|
var whiteSuffix *regexp.Regexp
|
||||||
var offsetComponentRegex *regexp.Regexp
|
var offsetComponentRegex *regexp.Regexp
|
||||||
@@ -425,6 +445,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
delay = initialDelay
|
delay = initialDelay
|
||||||
}
|
}
|
||||||
var previewBox *util.EventBox
|
var previewBox *util.EventBox
|
||||||
|
showPreviewWindow := len(opts.Preview.command) > 0 && !opts.Preview.hidden
|
||||||
if len(opts.Preview.command) > 0 || hasPreviewAction(opts) {
|
if len(opts.Preview.command) > 0 || hasPreviewAction(opts) {
|
||||||
previewBox = util.NewEventBox()
|
previewBox = util.NewEventBox()
|
||||||
}
|
}
|
||||||
@@ -521,7 +542,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
selected: make(map[int32]selectedItem),
|
selected: make(map[int32]selectedItem),
|
||||||
reqBox: util.NewEventBox(),
|
reqBox: util.NewEventBox(),
|
||||||
previewOpts: opts.Preview,
|
previewOpts: opts.Preview,
|
||||||
previewer: previewer{0, []string{}, 0, previewBox != nil && !opts.Preview.hidden, false, true, false, ""},
|
previewer: previewer{0, []string{}, 0, showPreviewWindow, false, true, false, ""},
|
||||||
previewed: previewed{0, 0, 0, false},
|
previewed: previewed{0, 0, 0, false},
|
||||||
previewBox: previewBox,
|
previewBox: previewBox,
|
||||||
eventBox: eventBox,
|
eventBox: eventBox,
|
||||||
@@ -1519,22 +1540,6 @@ func keyMatch(key tui.Event, event tui.Event) bool {
|
|||||||
key.Type == tui.DoubleClick && event.Type == tui.Mouse && event.MouseEvent.Double
|
key.Type == tui.DoubleClick && event.Type == tui.Mouse && event.MouseEvent.Double
|
||||||
}
|
}
|
||||||
|
|
||||||
func quoteEntryCmd(entry string) string {
|
|
||||||
escaped := strings.Replace(entry, `\`, `\\`, -1)
|
|
||||||
escaped = `"` + strings.Replace(escaped, `"`, `\"`, -1) + `"`
|
|
||||||
r, _ := regexp.Compile(`[&|<>()@^%!"]`)
|
|
||||||
return r.ReplaceAllStringFunc(escaped, func(match string) string {
|
|
||||||
return "^" + match
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func quoteEntry(entry string) string {
|
|
||||||
if util.IsWindows() {
|
|
||||||
return quoteEntryCmd(entry)
|
|
||||||
}
|
|
||||||
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
|
|
||||||
}
|
|
||||||
|
|
||||||
func parsePlaceholder(match string) (bool, string, placeholderFlags) {
|
func parsePlaceholder(match string) (bool, string, placeholderFlags) {
|
||||||
flags := placeholderFlags{}
|
flags := placeholderFlags{}
|
||||||
|
|
||||||
@@ -1560,6 +1565,7 @@ func parsePlaceholder(match string) (bool, string, placeholderFlags) {
|
|||||||
skipChars++
|
skipChars++
|
||||||
case 'q':
|
case 'q':
|
||||||
flags.query = true
|
flags.query = true
|
||||||
|
// query flag is not skipped
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -1647,77 +1653,86 @@ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, pr
|
|||||||
if selected[0] == nil {
|
if selected[0] == nil {
|
||||||
selected = []*Item{}
|
selected = []*Item{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// replace placeholders one by one
|
||||||
return placeholder.ReplaceAllStringFunc(template, func(match string) string {
|
return placeholder.ReplaceAllStringFunc(template, func(match string) string {
|
||||||
escaped, match, flags := parsePlaceholder(match)
|
escaped, match, flags := parsePlaceholder(match)
|
||||||
|
|
||||||
if escaped {
|
// this function implements the effects a placeholder has on items
|
||||||
|
var replace func(*Item) string
|
||||||
|
|
||||||
|
// placeholder types (escaped, query type, item type, token type)
|
||||||
|
switch {
|
||||||
|
case escaped:
|
||||||
return match
|
return match
|
||||||
|
case match == "{q}":
|
||||||
|
return quoteEntry(query)
|
||||||
|
case match == "{}":
|
||||||
|
replace = func(item *Item) string {
|
||||||
|
switch {
|
||||||
|
case flags.number:
|
||||||
|
n := int(item.text.Index)
|
||||||
|
if n < 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strconv.Itoa(n)
|
||||||
|
case flags.file:
|
||||||
|
return item.AsString(stripAnsi)
|
||||||
|
default:
|
||||||
|
return quoteEntry(item.AsString(stripAnsi))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// token type and also failover (below)
|
||||||
|
rangeExpressions := strings.Split(match[1:len(match)-1], ",")
|
||||||
|
ranges := make([]Range, len(rangeExpressions))
|
||||||
|
for idx, s := range rangeExpressions {
|
||||||
|
r, ok := ParseRange(&s) // ellipsis (x..y) and shorthand (x..x) range syntax
|
||||||
|
if !ok {
|
||||||
|
// Invalid expression, just return the original string in the template
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
ranges[idx] = r
|
||||||
|
}
|
||||||
|
|
||||||
|
replace = func(item *Item) string {
|
||||||
|
tokens := Tokenize(item.AsString(stripAnsi), delimiter)
|
||||||
|
trans := Transform(tokens, ranges)
|
||||||
|
str := joinTokens(trans)
|
||||||
|
|
||||||
|
// trim the last delimiter
|
||||||
|
if delimiter.str != nil {
|
||||||
|
str = strings.TrimSuffix(str, *delimiter.str)
|
||||||
|
} else if delimiter.regex != nil {
|
||||||
|
delims := delimiter.regex.FindAllStringIndex(str, -1)
|
||||||
|
// make sure the delimiter is at the very end of the string
|
||||||
|
if len(delims) > 0 && delims[len(delims)-1][1] == len(str) {
|
||||||
|
str = str[:delims[len(delims)-1][0]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !flags.preserveSpace {
|
||||||
|
str = strings.TrimSpace(str)
|
||||||
|
}
|
||||||
|
if !flags.file {
|
||||||
|
str = quoteEntry(str)
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Current query
|
// apply 'replace' function over proper set of items and return result
|
||||||
if match == "{q}" {
|
|
||||||
return quoteEntry(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
items := current
|
items := current
|
||||||
if flags.plus || forcePlus {
|
if flags.plus || forcePlus {
|
||||||
items = selected
|
items = selected
|
||||||
}
|
}
|
||||||
|
|
||||||
replacements := make([]string, len(items))
|
replacements := make([]string, len(items))
|
||||||
|
|
||||||
if match == "{}" {
|
|
||||||
for idx, item := range items {
|
|
||||||
if flags.number {
|
|
||||||
n := int(item.text.Index)
|
|
||||||
if n < 0 {
|
|
||||||
replacements[idx] = ""
|
|
||||||
} else {
|
|
||||||
replacements[idx] = strconv.Itoa(n)
|
|
||||||
}
|
|
||||||
} else if flags.file {
|
|
||||||
replacements[idx] = item.AsString(stripAnsi)
|
|
||||||
} else {
|
|
||||||
replacements[idx] = quoteEntry(item.AsString(stripAnsi))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if flags.file {
|
|
||||||
return writeTemporaryFile(replacements, printsep)
|
|
||||||
}
|
|
||||||
return strings.Join(replacements, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
tokens := strings.Split(match[1:len(match)-1], ",")
|
|
||||||
ranges := make([]Range, len(tokens))
|
|
||||||
for idx, s := range tokens {
|
|
||||||
r, ok := ParseRange(&s)
|
|
||||||
if !ok {
|
|
||||||
// Invalid expression, just return the original string in the template
|
|
||||||
return match
|
|
||||||
}
|
|
||||||
ranges[idx] = r
|
|
||||||
}
|
|
||||||
|
|
||||||
for idx, item := range items {
|
for idx, item := range items {
|
||||||
tokens := Tokenize(item.AsString(stripAnsi), delimiter)
|
replacements[idx] = replace(item)
|
||||||
trans := Transform(tokens, ranges)
|
|
||||||
str := joinTokens(trans)
|
|
||||||
if delimiter.str != nil {
|
|
||||||
str = strings.TrimSuffix(str, *delimiter.str)
|
|
||||||
} else if delimiter.regex != nil {
|
|
||||||
delims := delimiter.regex.FindAllStringIndex(str, -1)
|
|
||||||
if len(delims) > 0 && delims[len(delims)-1][1] == len(str) {
|
|
||||||
str = str[:delims[len(delims)-1][0]]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !flags.preserveSpace {
|
|
||||||
str = strings.TrimSpace(str)
|
|
||||||
}
|
|
||||||
if !flags.file {
|
|
||||||
str = quoteEntry(str)
|
|
||||||
}
|
|
||||||
replacements[idx] = str
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if flags.file {
|
if flags.file {
|
||||||
return writeTemporaryFile(replacements, printsep)
|
return writeTemporaryFile(replacements, printsep)
|
||||||
}
|
}
|
||||||
@@ -2657,6 +2672,7 @@ func (t *Terminal) Loop() {
|
|||||||
if valid {
|
if valid {
|
||||||
command := t.replacePlaceholder(a.a, false, string(t.input), list)
|
command := t.replacePlaceholder(a.a, false, string(t.input), list)
|
||||||
newCommand = &command
|
newCommand = &command
|
||||||
|
t.reading = true
|
||||||
}
|
}
|
||||||
case actUnbind:
|
case actUnbind:
|
||||||
keys := parseKeyChords(a.a, "PANIC")
|
keys := parseKeyChords(a.a, "PANIC")
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newItem(str string) *Item {
|
|
||||||
bytes := []byte(str)
|
|
||||||
trimmed, _, _ := extractColor(str, nil, nil)
|
|
||||||
return &Item{origText: &bytes, text: util.ToChars([]byte(trimmed))}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReplacePlaceholder(t *testing.T) {
|
func TestReplacePlaceholder(t *testing.T) {
|
||||||
item1 := newItem(" foo'bar \x1b[31mbaz\x1b[m")
|
item1 := newItem(" foo'bar \x1b[31mbaz\x1b[m")
|
||||||
items1 := []*Item{item1, item1}
|
items1 := []*Item{item1, item1}
|
||||||
@@ -30,75 +29,96 @@ func TestReplacePlaceholder(t *testing.T) {
|
|||||||
t.Errorf("expected: %s, actual: %s", expected, result)
|
t.Errorf("expected: %s, actual: %s", expected, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// helper function that converts template format into string and carries out the check()
|
||||||
|
checkFormat := func(format string) {
|
||||||
|
type quotes struct{ O, I, S string } // outer, inner quotes, print separator
|
||||||
|
unixStyle := quotes{`'`, `'\''`, "\n"}
|
||||||
|
windowsStyle := quotes{`^"`, `'`, "\n"}
|
||||||
|
var effectiveStyle quotes
|
||||||
|
|
||||||
|
if util.IsWindows() {
|
||||||
|
effectiveStyle = windowsStyle
|
||||||
|
} else {
|
||||||
|
effectiveStyle = unixStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := templateToString(format, effectiveStyle)
|
||||||
|
check(expected)
|
||||||
|
}
|
||||||
printsep := "\n"
|
printsep := "\n"
|
||||||
|
|
||||||
|
/*
|
||||||
|
Test multiple placeholders and the function parameters.
|
||||||
|
*/
|
||||||
|
|
||||||
// {}, preserve ansi
|
// {}, preserve ansi
|
||||||
result = replacePlaceholder("echo {}", false, Delimiter{}, printsep, false, "query", items1)
|
result = replacePlaceholder("echo {}", false, Delimiter{}, printsep, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar \x1b[31mbaz\x1b[m'")
|
checkFormat("echo {{.O}} foo{{.I}}bar \x1b[31mbaz\x1b[m{{.O}}")
|
||||||
|
|
||||||
// {}, strip ansi
|
// {}, strip ansi
|
||||||
result = replacePlaceholder("echo {}", true, Delimiter{}, printsep, false, "query", items1)
|
result = replacePlaceholder("echo {}", true, Delimiter{}, printsep, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz'")
|
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}")
|
||||||
|
|
||||||
// {}, with multiple items
|
// {}, with multiple items
|
||||||
result = replacePlaceholder("echo {}", true, Delimiter{}, printsep, false, "query", items2)
|
result = replacePlaceholder("echo {}", true, Delimiter{}, printsep, false, "query", items2)
|
||||||
check("echo 'foo'\\''bar baz'")
|
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}}")
|
||||||
|
|
||||||
// {..}, strip leading whitespaces, preserve ansi
|
// {..}, strip leading whitespaces, preserve ansi
|
||||||
result = replacePlaceholder("echo {..}", false, Delimiter{}, printsep, false, "query", items1)
|
result = replacePlaceholder("echo {..}", false, Delimiter{}, printsep, false, "query", items1)
|
||||||
check("echo 'foo'\\''bar \x1b[31mbaz\x1b[m'")
|
checkFormat("echo {{.O}}foo{{.I}}bar \x1b[31mbaz\x1b[m{{.O}}")
|
||||||
|
|
||||||
// {..}, strip leading whitespaces, strip ansi
|
// {..}, strip leading whitespaces, strip ansi
|
||||||
result = replacePlaceholder("echo {..}", true, Delimiter{}, printsep, false, "query", items1)
|
result = replacePlaceholder("echo {..}", true, Delimiter{}, printsep, false, "query", items1)
|
||||||
check("echo 'foo'\\''bar baz'")
|
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}}")
|
||||||
|
|
||||||
// {q}
|
// {q}
|
||||||
result = replacePlaceholder("echo {} {q}", true, Delimiter{}, printsep, false, "query", items1)
|
result = replacePlaceholder("echo {} {q}", true, Delimiter{}, printsep, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz' 'query'")
|
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}} {{.O}}query{{.O}}")
|
||||||
|
|
||||||
// {q}, multiple items
|
// {q}, multiple items
|
||||||
result = replacePlaceholder("echo {+}{q}{+}", true, Delimiter{}, printsep, false, "query 'string'", items2)
|
result = replacePlaceholder("echo {+}{q}{+}", true, Delimiter{}, printsep, false, "query 'string'", items2)
|
||||||
check("echo 'foo'\\''bar baz' 'FOO'\\''BAR BAZ''query '\\''string'\\''''foo'\\''bar baz' 'FOO'\\''BAR BAZ'")
|
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}{{.O}}query {{.I}}string{{.I}}{{.O}}{{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {}{q}{}", true, Delimiter{}, printsep, false, "query 'string'", items2)
|
result = replacePlaceholder("echo {}{q}{}", true, Delimiter{}, printsep, false, "query 'string'", items2)
|
||||||
check("echo 'foo'\\''bar baz''query '\\''string'\\''''foo'\\''bar baz'")
|
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}}{{.O}}query {{.I}}string{{.I}}{{.O}}{{.O}}foo{{.I}}bar baz{{.O}}")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {1}/{2}/{2,1}/{-1}/{-2}/{}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, false, "query", items1)
|
result = replacePlaceholder("echo {1}/{2}/{2,1}/{-1}/{-2}/{}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, false, "query", items1)
|
||||||
check("echo 'foo'\\''bar'/'baz'/'bazfoo'\\''bar'/'baz'/'foo'\\''bar'/' foo'\\''bar baz'/'foo'\\''bar baz'/{n.t}/{}/{1}/{q}/''")
|
checkFormat("echo {{.O}}foo{{.I}}bar{{.O}}/{{.O}}baz{{.O}}/{{.O}}bazfoo{{.I}}bar{{.O}}/{{.O}}baz{{.O}}/{{.O}}foo{{.I}}bar{{.O}}/{{.O}} foo{{.I}}bar baz{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}}")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, false, "query", items2)
|
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, false, "query", items2)
|
||||||
check("echo 'foo'\\''bar'/'baz'/'baz'/'foo'\\''bar'/'foo'\\''bar baz'/{n.t}/{}/{1}/{q}/''")
|
checkFormat("echo {{.O}}foo{{.I}}bar{{.O}}/{{.O}}baz{{.O}}/{{.O}}baz{{.O}}/{{.O}}foo{{.I}}bar{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}}")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {+1}/{+2}/{+-1}/{+-2}/{+..}/{n.t}/\\{}/\\{1}/\\{q}/{+3}", true, Delimiter{}, printsep, false, "query", items2)
|
result = replacePlaceholder("echo {+1}/{+2}/{+-1}/{+-2}/{+..}/{n.t}/\\{}/\\{1}/\\{q}/{+3}", true, Delimiter{}, printsep, false, "query", items2)
|
||||||
check("echo 'foo'\\''bar' 'FOO'\\''BAR'/'baz' 'BAZ'/'baz' 'BAZ'/'foo'\\''bar' 'FOO'\\''BAR'/'foo'\\''bar baz' 'FOO'\\''BAR BAZ'/{n.t}/{}/{1}/{q}/'' ''")
|
checkFormat("echo {{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}} {{.O}}{{.O}}")
|
||||||
|
|
||||||
// forcePlus
|
// forcePlus
|
||||||
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, true, "query", items2)
|
result = replacePlaceholder("echo {1}/{2}/{-1}/{-2}/{..}/{n.t}/\\{}/\\{1}/\\{q}/{3}", true, Delimiter{}, printsep, true, "query", items2)
|
||||||
check("echo 'foo'\\''bar' 'FOO'\\''BAR'/'baz' 'BAZ'/'baz' 'BAZ'/'foo'\\''bar' 'FOO'\\''BAR'/'foo'\\''bar baz' 'FOO'\\''BAR BAZ'/{n.t}/{}/{1}/{q}/'' ''")
|
checkFormat("echo {{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}baz{{.O}} {{.O}}BAZ{{.O}}/{{.O}}foo{{.I}}bar{{.O}} {{.O}}FOO{{.I}}BAR{{.O}}/{{.O}}foo{{.I}}bar baz{{.O}} {{.O}}FOO{{.I}}BAR BAZ{{.O}}/{n.t}/{}/{1}/{q}/{{.O}}{{.O}} {{.O}}{{.O}}")
|
||||||
|
|
||||||
// Whitespace preserving flag with "'" delimiter
|
// Whitespace preserving flag with "'" delimiter
|
||||||
result = replacePlaceholder("echo {s1}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
result = replacePlaceholder("echo {s1}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
||||||
check("echo ' foo'")
|
checkFormat("echo {{.O}} foo{{.O}}")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {s2}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
result = replacePlaceholder("echo {s2}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
||||||
check("echo 'bar baz'")
|
checkFormat("echo {{.O}}bar baz{{.O}}")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {s}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
result = replacePlaceholder("echo {s}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz'")
|
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {s..}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
result = replacePlaceholder("echo {s..}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz'")
|
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}")
|
||||||
|
|
||||||
// Whitespace preserving flag with regex delimiter
|
// Whitespace preserving flag with regex delimiter
|
||||||
regex = regexp.MustCompile(`\w+`)
|
regex = regexp.MustCompile(`\w+`)
|
||||||
|
|
||||||
result = replacePlaceholder("echo {s1}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
result = replacePlaceholder("echo {s1}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
||||||
check("echo ' '")
|
checkFormat("echo {{.O}} {{.O}}")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {s2}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
result = replacePlaceholder("echo {s2}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
||||||
check("echo ''\\'''")
|
checkFormat("echo {{.O}}{{.I}}{{.O}}")
|
||||||
|
|
||||||
result = replacePlaceholder("echo {s3}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
result = replacePlaceholder("echo {s3}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
||||||
check("echo ' '")
|
checkFormat("echo {{.O}} {{.O}}")
|
||||||
|
|
||||||
// No match
|
// No match
|
||||||
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, nil})
|
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, nil})
|
||||||
@@ -106,34 +126,425 @@ func TestReplacePlaceholder(t *testing.T) {
|
|||||||
|
|
||||||
// No match, but with selections
|
// No match, but with selections
|
||||||
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, item1})
|
result = replacePlaceholder("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, item1})
|
||||||
check("echo /' foo'\\''bar baz'")
|
checkFormat("echo /{{.O}} foo{{.I}}bar baz{{.O}}")
|
||||||
|
|
||||||
// String delimiter
|
// String delimiter
|
||||||
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
result = replacePlaceholder("echo {}/{1}/{2}", true, Delimiter{str: &delim}, printsep, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz'/'foo'/'bar baz'")
|
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}/{{.O}}foo{{.O}}/{{.O}}bar baz{{.O}}")
|
||||||
|
|
||||||
// Regex delimiter
|
// Regex delimiter
|
||||||
regex = regexp.MustCompile("[oa]+")
|
regex = regexp.MustCompile("[oa]+")
|
||||||
// foo'bar baz
|
// foo'bar baz
|
||||||
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
result = replacePlaceholder("echo {}/{1}/{3}/{2..3}", true, Delimiter{regex: regex}, printsep, false, "query", items1)
|
||||||
check("echo ' foo'\\''bar baz'/'f'/'r b'/''\\''bar b'")
|
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}/{{.O}}f{{.O}}/{{.O}}r b{{.O}}/{{.O}}{{.I}}bar b{{.O}}")
|
||||||
|
|
||||||
|
/*
|
||||||
|
Test single placeholders, but focus on the placeholders' parameters (e.g. flags).
|
||||||
|
see: TestParsePlaceholder
|
||||||
|
*/
|
||||||
|
items3 := []*Item{
|
||||||
|
// single line
|
||||||
|
newItem("1a 1b 1c 1d 1e 1f"),
|
||||||
|
// multi line
|
||||||
|
newItem("1a 1b 1c 1d 1e 1f"),
|
||||||
|
newItem("2a 2b 2c 2d 2e 2f"),
|
||||||
|
newItem("3a 3b 3c 3d 3e 3f"),
|
||||||
|
newItem("4a 4b 4c 4d 4e 4f"),
|
||||||
|
newItem("5a 5b 5c 5d 5e 5f"),
|
||||||
|
newItem("6a 6b 6c 6d 6e 6f"),
|
||||||
|
newItem("7a 7b 7c 7d 7e 7f"),
|
||||||
|
}
|
||||||
|
stripAnsi := false
|
||||||
|
printsep = "\n"
|
||||||
|
forcePlus := false
|
||||||
|
query := "sample query"
|
||||||
|
|
||||||
|
templateToOutput := make(map[string]string)
|
||||||
|
templateToFile := make(map[string]string) // same as above, but the file contents will be matched
|
||||||
|
// I. item type placeholder
|
||||||
|
templateToOutput[`{}`] = `{{.O}}1a 1b 1c 1d 1e 1f{{.O}}`
|
||||||
|
templateToOutput[`{+}`] = `{{.O}}1a 1b 1c 1d 1e 1f{{.O}} {{.O}}2a 2b 2c 2d 2e 2f{{.O}} {{.O}}3a 3b 3c 3d 3e 3f{{.O}} {{.O}}4a 4b 4c 4d 4e 4f{{.O}} {{.O}}5a 5b 5c 5d 5e 5f{{.O}} {{.O}}6a 6b 6c 6d 6e 6f{{.O}} {{.O}}7a 7b 7c 7d 7e 7f{{.O}}`
|
||||||
|
templateToOutput[`{n}`] = `0`
|
||||||
|
templateToOutput[`{+n}`] = `0 0 0 0 0 0 0`
|
||||||
|
templateToFile[`{f}`] = `1a 1b 1c 1d 1e 1f{{.S}}`
|
||||||
|
templateToFile[`{+f}`] = `1a 1b 1c 1d 1e 1f{{.S}}2a 2b 2c 2d 2e 2f{{.S}}3a 3b 3c 3d 3e 3f{{.S}}4a 4b 4c 4d 4e 4f{{.S}}5a 5b 5c 5d 5e 5f{{.S}}6a 6b 6c 6d 6e 6f{{.S}}7a 7b 7c 7d 7e 7f{{.S}}`
|
||||||
|
templateToFile[`{nf}`] = `0{{.S}}`
|
||||||
|
templateToFile[`{+nf}`] = `0{{.S}}0{{.S}}0{{.S}}0{{.S}}0{{.S}}0{{.S}}0{{.S}}`
|
||||||
|
|
||||||
|
// II. token type placeholders
|
||||||
|
templateToOutput[`{..}`] = templateToOutput[`{}`]
|
||||||
|
templateToOutput[`{1..}`] = templateToOutput[`{}`]
|
||||||
|
templateToOutput[`{..2}`] = `{{.O}}1a 1b{{.O}}`
|
||||||
|
templateToOutput[`{1..2}`] = templateToOutput[`{..2}`]
|
||||||
|
templateToOutput[`{-2..-1}`] = `{{.O}}1e 1f{{.O}}`
|
||||||
|
// shorthand for x..x range
|
||||||
|
templateToOutput[`{1}`] = `{{.O}}1a{{.O}}`
|
||||||
|
templateToOutput[`{1..1}`] = templateToOutput[`{1}`]
|
||||||
|
templateToOutput[`{-6}`] = templateToOutput[`{1}`]
|
||||||
|
// multiple ranges
|
||||||
|
templateToOutput[`{1,2}`] = templateToOutput[`{1..2}`]
|
||||||
|
templateToOutput[`{1,2,4}`] = `{{.O}}1a 1b 1d{{.O}}`
|
||||||
|
templateToOutput[`{1,2..4}`] = `{{.O}}1a 1b 1c 1d{{.O}}`
|
||||||
|
templateToOutput[`{1..2,-4..-3}`] = `{{.O}}1a 1b 1c 1d{{.O}}`
|
||||||
|
// flags
|
||||||
|
templateToOutput[`{+1}`] = `{{.O}}1a{{.O}} {{.O}}2a{{.O}} {{.O}}3a{{.O}} {{.O}}4a{{.O}} {{.O}}5a{{.O}} {{.O}}6a{{.O}} {{.O}}7a{{.O}}`
|
||||||
|
templateToOutput[`{+-1}`] = `{{.O}}1f{{.O}} {{.O}}2f{{.O}} {{.O}}3f{{.O}} {{.O}}4f{{.O}} {{.O}}5f{{.O}} {{.O}}6f{{.O}} {{.O}}7f{{.O}}`
|
||||||
|
templateToOutput[`{s1}`] = `{{.O}}1a {{.O}}`
|
||||||
|
templateToFile[`{f1}`] = `1a{{.S}}`
|
||||||
|
templateToOutput[`{+s1..2}`] = `{{.O}}1a 1b {{.O}} {{.O}}2a 2b {{.O}} {{.O}}3a 3b {{.O}} {{.O}}4a 4b {{.O}} {{.O}}5a 5b {{.O}} {{.O}}6a 6b {{.O}} {{.O}}7a 7b {{.O}}`
|
||||||
|
templateToFile[`{+sf1..2}`] = `1a 1b {{.S}}2a 2b {{.S}}3a 3b {{.S}}4a 4b {{.S}}5a 5b {{.S}}6a 6b {{.S}}7a 7b {{.S}}`
|
||||||
|
|
||||||
|
// III. query type placeholder
|
||||||
|
// query flag is not removed after parsing, so it gets doubled
|
||||||
|
// while the double q is invalid, it is useful here for testing purposes
|
||||||
|
templateToOutput[`{q}`] = "{{.O}}" + query + "{{.O}}"
|
||||||
|
|
||||||
|
// IV. escaping placeholder
|
||||||
|
templateToOutput[`\{}`] = `{}`
|
||||||
|
templateToOutput[`\{++}`] = `{++}`
|
||||||
|
templateToOutput[`{++}`] = templateToOutput[`{+}`]
|
||||||
|
|
||||||
|
for giveTemplate, wantOutput := range templateToOutput {
|
||||||
|
result = replacePlaceholder(giveTemplate, stripAnsi, Delimiter{}, printsep, forcePlus, query, items3)
|
||||||
|
checkFormat(wantOutput)
|
||||||
|
}
|
||||||
|
for giveTemplate, wantOutput := range templateToFile {
|
||||||
|
path := replacePlaceholder(giveTemplate, stripAnsi, Delimiter{}, printsep, forcePlus, query, items3)
|
||||||
|
|
||||||
|
data, err := readFile(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Cannot read the content of the temp file %s.", path)
|
||||||
|
}
|
||||||
|
result = string(data)
|
||||||
|
|
||||||
|
checkFormat(wantOutput)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQuoteEntryCmd(t *testing.T) {
|
func TestQuoteEntry(t *testing.T) {
|
||||||
|
type quotes struct{ E, O, SQ, DQ, BS string } // standalone escape, outer, single and double quotes, backslash
|
||||||
|
unixStyle := quotes{``, `'`, `'\''`, `"`, `\`}
|
||||||
|
windowsStyle := quotes{`^`, `^"`, `'`, `\^"`, `\\`}
|
||||||
|
var effectiveStyle quotes
|
||||||
|
|
||||||
|
if util.IsWindows() {
|
||||||
|
effectiveStyle = windowsStyle
|
||||||
|
} else {
|
||||||
|
effectiveStyle = unixStyle
|
||||||
|
}
|
||||||
|
|
||||||
tests := map[string]string{
|
tests := map[string]string{
|
||||||
`"`: `^"\^"^"`,
|
`'`: `{{.O}}{{.SQ}}{{.O}}`,
|
||||||
`\`: `^"\\^"`,
|
`"`: `{{.O}}{{.DQ}}{{.O}}`,
|
||||||
`\"`: `^"\\\^"^"`,
|
`\`: `{{.O}}{{.BS}}{{.O}}`,
|
||||||
`"\\\"`: `^"\^"\\\\\\\^"^"`,
|
`\"`: `{{.O}}{{.BS}}{{.DQ}}{{.O}}`,
|
||||||
`&|<>()@^%!`: `^"^&^|^<^>^(^)^@^^^%^!^"`,
|
`"\\\"`: `{{.O}}{{.DQ}}{{.BS}}{{.BS}}{{.BS}}{{.DQ}}{{.O}}`,
|
||||||
`%USERPROFILE%`: `^"^%USERPROFILE^%^"`,
|
|
||||||
`C:\Program Files (x86)\`: `^"C:\\Program Files ^(x86^)\\^"`,
|
`$`: `{{.O}}${{.O}}`,
|
||||||
|
`$HOME`: `{{.O}}$HOME{{.O}}`,
|
||||||
|
`'$HOME'`: `{{.O}}{{.SQ}}$HOME{{.SQ}}{{.O}}`,
|
||||||
|
|
||||||
|
`&`: `{{.O}}{{.E}}&{{.O}}`,
|
||||||
|
`|`: `{{.O}}{{.E}}|{{.O}}`,
|
||||||
|
`<`: `{{.O}}{{.E}}<{{.O}}`,
|
||||||
|
`>`: `{{.O}}{{.E}}>{{.O}}`,
|
||||||
|
`(`: `{{.O}}{{.E}}({{.O}}`,
|
||||||
|
`)`: `{{.O}}{{.E}}){{.O}}`,
|
||||||
|
`@`: `{{.O}}{{.E}}@{{.O}}`,
|
||||||
|
`^`: `{{.O}}{{.E}}^{{.O}}`,
|
||||||
|
`%`: `{{.O}}{{.E}}%{{.O}}`,
|
||||||
|
`!`: `{{.O}}{{.E}}!{{.O}}`,
|
||||||
|
`%USERPROFILE%`: `{{.O}}{{.E}}%USERPROFILE{{.E}}%{{.O}}`,
|
||||||
|
`C:\Program Files (x86)\`: `{{.O}}C:{{.BS}}Program Files {{.E}}(x86{{.E}}){{.BS}}{{.O}}`,
|
||||||
|
`"C:\Program Files"`: `{{.O}}{{.DQ}}C:{{.BS}}Program Files{{.DQ}}{{.O}}`,
|
||||||
}
|
}
|
||||||
|
|
||||||
for input, expected := range tests {
|
for input, expected := range tests {
|
||||||
escaped := quoteEntryCmd(input)
|
escaped := quoteEntry(input)
|
||||||
|
expected = templateToString(expected, effectiveStyle)
|
||||||
if escaped != expected {
|
if escaped != expected {
|
||||||
t.Errorf("Input: %s, expected: %s, actual %s", input, expected, escaped)
|
t.Errorf("Input: %s, expected: %s, actual %s", input, expected, escaped)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// purpose of this test is to demonstrate some shortcomings of fzf's templating system on Unix
|
||||||
|
func TestUnixCommands(t *testing.T) {
|
||||||
|
if util.IsWindows() {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
tests := []testCase{
|
||||||
|
// reference: give{template, query, items}, want{output OR match}
|
||||||
|
|
||||||
|
// 1) working examples
|
||||||
|
|
||||||
|
// paths that does not have to evaluated will work fine, when quoted
|
||||||
|
{give{`grep foo {}`, ``, newItems(`test`)}, want{output: `grep foo 'test'`}},
|
||||||
|
{give{`grep foo {}`, ``, newItems(`/home/user/test`)}, want{output: `grep foo '/home/user/test'`}},
|
||||||
|
{give{`grep foo {}`, ``, newItems(`./test`)}, want{output: `grep foo './test'`}},
|
||||||
|
|
||||||
|
// only placeholders are escaped as data, this will lookup tilde character in a test file in your home directory
|
||||||
|
// quoting the tilde is required (to be treated as string)
|
||||||
|
{give{`grep {} ~/test`, ``, newItems(`~`)}, want{output: `grep '~' ~/test`}},
|
||||||
|
|
||||||
|
// 2) problematic examples
|
||||||
|
|
||||||
|
// paths that need to expand some part of it won't work (special characters and variables)
|
||||||
|
{give{`cat {}`, ``, newItems(`~/test`)}, want{output: `cat '~/test'`}},
|
||||||
|
{give{`cat {}`, ``, newItems(`$HOME/test`)}, want{output: `cat '$HOME/test'`}},
|
||||||
|
}
|
||||||
|
testCommands(t, tests)
|
||||||
|
}
|
||||||
|
|
||||||
|
// purpose of this test is to demonstrate some shortcomings of fzf's templating system on Windows
|
||||||
|
func TestWindowsCommands(t *testing.T) {
|
||||||
|
if !util.IsWindows() {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
tests := []testCase{
|
||||||
|
// reference: give{template, query, items}, want{output OR match}
|
||||||
|
|
||||||
|
// 1) working examples
|
||||||
|
|
||||||
|
// example of redundantly escaped backslash in the output, besides looking bit ugly, it won't cause any issue
|
||||||
|
{give{`type {}`, ``, newItems(`C:\test.txt`)}, want{output: `type ^"C:\\test.txt^"`}},
|
||||||
|
{give{`rg -- "package" {}`, ``, newItems(`.\test.go`)}, want{output: `rg -- "package" ^".\\test.go^"`}},
|
||||||
|
// example of mandatorily escaped backslash in the output, otherwise `rg -- "C:\test.txt"` is matching for tabulator
|
||||||
|
{give{`rg -- {}`, ``, newItems(`C:\test.txt`)}, want{output: `rg -- ^"C:\\test.txt^"`}},
|
||||||
|
// example of mandatorily escaped double quote in the output, otherwise `rg -- ""C:\\test.txt""` is not matching for the double quotes around the path
|
||||||
|
{give{`rg -- {}`, ``, newItems(`"C:\test.txt"`)}, want{output: `rg -- ^"\^"C:\\test.txt\^"^"`}},
|
||||||
|
|
||||||
|
// 2) problematic examples
|
||||||
|
|
||||||
|
// notepad++'s parser can't handle `-n"12"` generate by fzf, expects `-n12`
|
||||||
|
{give{`notepad++ -n{1} {2}`, ``, newItems(`12 C:\Work\Test Folder\File.txt`)}, want{output: `notepad++ -n^"12^" ^"C:\\Work\\Test Folder\\File.txt^"`}},
|
||||||
|
|
||||||
|
// cat is parsing `\"` as a part of the file path, double quote is illegal character for paths on Windows
|
||||||
|
// cat: "C:\\test.txt: Invalid argument
|
||||||
|
{give{`cat {}`, ``, newItems(`"C:\test.txt"`)}, want{output: `cat ^"\^"C:\\test.txt\^"^"`}},
|
||||||
|
// cat: "C:\\test.txt": Invalid argument
|
||||||
|
{give{`cmd /c {}`, ``, newItems(`cat "C:\test.txt"`)}, want{output: `cmd /c ^"cat \^"C:\\test.txt\^"^"`}},
|
||||||
|
|
||||||
|
// the "file" flag in the pattern won't create *.bat or *.cmd file so the command in the output tries to edit the file, instead of executing it
|
||||||
|
// the temp file contains: `cat "C:\test.txt"`
|
||||||
|
{give{`cmd /c {f}`, ``, newItems(`cat "C:\test.txt"`)}, want{match: `^cmd /c .*\fzf-preview-[0-9]{9}$`}},
|
||||||
|
}
|
||||||
|
testCommands(t, tests)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Test typical valid placeholders and parsing of them.
|
||||||
|
|
||||||
|
Also since the parser assumes the input is matched with `placeholder` regex,
|
||||||
|
the regex is tested here as well.
|
||||||
|
*/
|
||||||
|
func TestParsePlaceholder(t *testing.T) {
|
||||||
|
// give, want pairs
|
||||||
|
templates := map[string]string{
|
||||||
|
// I. item type placeholder
|
||||||
|
`{}`: `{}`,
|
||||||
|
`{+}`: `{+}`,
|
||||||
|
`{n}`: `{n}`,
|
||||||
|
`{+n}`: `{+n}`,
|
||||||
|
`{f}`: `{f}`,
|
||||||
|
`{+nf}`: `{+nf}`,
|
||||||
|
|
||||||
|
// II. token type placeholders
|
||||||
|
`{..}`: `{..}`,
|
||||||
|
`{1..}`: `{1..}`,
|
||||||
|
`{..2}`: `{..2}`,
|
||||||
|
`{1..2}`: `{1..2}`,
|
||||||
|
`{-2..-1}`: `{-2..-1}`,
|
||||||
|
// shorthand for x..x range
|
||||||
|
`{1}`: `{1}`,
|
||||||
|
`{1..1}`: `{1..1}`,
|
||||||
|
`{-6}`: `{-6}`,
|
||||||
|
// multiple ranges
|
||||||
|
`{1,2}`: `{1,2}`,
|
||||||
|
`{1,2,4}`: `{1,2,4}`,
|
||||||
|
`{1,2..4}`: `{1,2..4}`,
|
||||||
|
`{1..2,-4..-3}`: `{1..2,-4..-3}`,
|
||||||
|
// flags
|
||||||
|
`{+1}`: `{+1}`,
|
||||||
|
`{+-1}`: `{+-1}`,
|
||||||
|
`{s1}`: `{s1}`,
|
||||||
|
`{f1}`: `{f1}`,
|
||||||
|
`{+s1..2}`: `{+s1..2}`,
|
||||||
|
`{+sf1..2}`: `{+sf1..2}`,
|
||||||
|
|
||||||
|
// III. query type placeholder
|
||||||
|
// query flag is not removed after parsing, so it gets doubled
|
||||||
|
// while the double q is invalid, it is useful here for testing purposes
|
||||||
|
`{q}`: `{qq}`,
|
||||||
|
|
||||||
|
// IV. escaping placeholder
|
||||||
|
`\{}`: `{}`,
|
||||||
|
`\{++}`: `{++}`,
|
||||||
|
`{++}`: `{+}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
for giveTemplate, wantTemplate := range templates {
|
||||||
|
if !placeholder.MatchString(giveTemplate) {
|
||||||
|
t.Errorf(`given placeholder %s does not match placeholder regex, so attempt to parse it is unexpected`, giveTemplate)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, placeholderWithoutFlags, flags := parsePlaceholder(giveTemplate)
|
||||||
|
gotTemplate := placeholderWithoutFlags[:1] + flags.encodePlaceholder() + placeholderWithoutFlags[1:]
|
||||||
|
|
||||||
|
if gotTemplate != wantTemplate {
|
||||||
|
t.Errorf(`parsed placeholder "%s" into "%s", but want "%s"`, giveTemplate, gotTemplate, wantTemplate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* utilities section */
|
||||||
|
|
||||||
|
// Item represents one line in fzf UI. Usually it is relative path to files and folders.
|
||||||
|
func newItem(str string) *Item {
|
||||||
|
bytes := []byte(str)
|
||||||
|
trimmed, _, _ := extractColor(str, nil, nil)
|
||||||
|
return &Item{origText: &bytes, text: util.ToChars([]byte(trimmed))}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Functions tested in this file require array of items (allItems). The array needs
|
||||||
|
// to consist of at least two nils. This is helper function.
|
||||||
|
func newItems(str ...string) []*Item {
|
||||||
|
result := make([]*Item, util.Max(len(str), 2))
|
||||||
|
for i, s := range str {
|
||||||
|
result[i] = newItem(s)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// (for logging purposes)
|
||||||
|
func (item *Item) String() string {
|
||||||
|
return item.AsString(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to parse, execute and convert "text/template" to string. Panics on error.
|
||||||
|
func templateToString(format string, data interface{}) string {
|
||||||
|
bb := &bytes.Buffer{}
|
||||||
|
|
||||||
|
err := template.Must(template.New("").Parse(format)).Execute(bb, data)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ad hoc types for test cases
|
||||||
|
type give struct {
|
||||||
|
template string
|
||||||
|
query string
|
||||||
|
allItems []*Item
|
||||||
|
}
|
||||||
|
type want struct {
|
||||||
|
/*
|
||||||
|
Unix:
|
||||||
|
The `want.output` string is supposed to be formatted for evaluation by
|
||||||
|
`sh -c command` system call.
|
||||||
|
|
||||||
|
Windows:
|
||||||
|
The `want.output` string is supposed to be formatted for evaluation by
|
||||||
|
`cmd.exe /s /c "command"` system call. The `/s` switch enables so called old
|
||||||
|
behaviour, which is more favourable for nesting (possibly escaped)
|
||||||
|
special characters. This is the relevant section of `help cmd`:
|
||||||
|
|
||||||
|
...old behavior is to see if the first character is
|
||||||
|
a quote character and if so, strip the leading character and
|
||||||
|
remove the last quote character on the command line, preserving
|
||||||
|
any text after the last quote character.
|
||||||
|
*/
|
||||||
|
output string // literal output
|
||||||
|
match string // output is matched against this regex (when output is empty string)
|
||||||
|
}
|
||||||
|
type testCase struct {
|
||||||
|
give
|
||||||
|
want
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCommands(t *testing.T, tests []testCase) {
|
||||||
|
// common test parameters
|
||||||
|
delim := "\t"
|
||||||
|
delimiter := Delimiter{str: &delim}
|
||||||
|
printsep := ""
|
||||||
|
stripAnsi := false
|
||||||
|
forcePlus := false
|
||||||
|
|
||||||
|
// evaluate the test cases
|
||||||
|
for idx, test := range tests {
|
||||||
|
gotOutput := replacePlaceholder(
|
||||||
|
test.give.template, stripAnsi, delimiter, printsep, forcePlus,
|
||||||
|
test.give.query,
|
||||||
|
test.give.allItems)
|
||||||
|
switch {
|
||||||
|
case test.want.output != "":
|
||||||
|
if gotOutput != test.want.output {
|
||||||
|
t.Errorf("tests[%v]:\ngave{\n\ttemplate: '%s',\n\tquery: '%s',\n\tallItems: %s}\nand got '%s',\nbut want '%s'",
|
||||||
|
idx,
|
||||||
|
test.give.template, test.give.query, test.give.allItems,
|
||||||
|
gotOutput, test.want.output)
|
||||||
|
}
|
||||||
|
case test.want.match != "":
|
||||||
|
wantMatch := strings.ReplaceAll(test.want.match, `\`, `\\`)
|
||||||
|
wantRegex := regexp.MustCompile(wantMatch)
|
||||||
|
if !wantRegex.MatchString(gotOutput) {
|
||||||
|
t.Errorf("tests[%v]:\ngave{\n\ttemplate: '%s',\n\tquery: '%s',\n\tallItems: %s}\nand got '%s',\nbut want '%s'",
|
||||||
|
idx,
|
||||||
|
test.give.template, test.give.query, test.give.allItems,
|
||||||
|
gotOutput, test.want.match)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Errorf("tests[%v]: test case does not describe 'want' property", idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// naive encoder of placeholder flags
|
||||||
|
func (flags placeholderFlags) encodePlaceholder() string {
|
||||||
|
encoded := ""
|
||||||
|
if flags.plus {
|
||||||
|
encoded += "+"
|
||||||
|
}
|
||||||
|
if flags.preserveSpace {
|
||||||
|
encoded += "s"
|
||||||
|
}
|
||||||
|
if flags.number {
|
||||||
|
encoded += "n"
|
||||||
|
}
|
||||||
|
if flags.file {
|
||||||
|
encoded += "f"
|
||||||
|
}
|
||||||
|
if flags.query {
|
||||||
|
encoded += "q"
|
||||||
|
}
|
||||||
|
return encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
// can be replaced with os.ReadFile() in go 1.16+
|
||||||
|
func readFile(path string) ([]byte, error) {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
data := make([]byte, 0, 128)
|
||||||
|
for {
|
||||||
|
if len(data) >= cap(data) {
|
||||||
|
d := append(data[:cap(data)], 0)
|
||||||
|
data = d[:len(data)]
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := file.Read(data[len(data):cap(data)])
|
||||||
|
data = data[:len(data)+n]
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package fzf
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,3 +20,7 @@ func notifyStop(p *os.Process) {
|
|||||||
func notifyOnCont(resizeChan chan<- os.Signal) {
|
func notifyOnCont(resizeChan chan<- os.Signal) {
|
||||||
signal.Notify(resizeChan, syscall.SIGCONT)
|
signal.Notify(resizeChan, syscall.SIGCONT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func quoteEntry(entry string) string {
|
||||||
|
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ package fzf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func notifyOnResize(resizeChan chan<- os.Signal) {
|
func notifyOnResize(resizeChan chan<- os.Signal) {
|
||||||
@@ -17,3 +19,12 @@ func notifyStop(p *os.Process) {
|
|||||||
func notifyOnCont(resizeChan chan<- os.Signal) {
|
func notifyOnCont(resizeChan chan<- os.Signal) {
|
||||||
// NOOP
|
// NOOP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func quoteEntry(entry string) string {
|
||||||
|
escaped := strings.Replace(entry, `\`, `\\`, -1)
|
||||||
|
escaped = `"` + strings.Replace(escaped, `"`, `\"`, -1) + `"`
|
||||||
|
r, _ := regexp.Compile(`[&|<>()@^%!"]`)
|
||||||
|
return r.ReplaceAllStringFunc(escaped, func(match string) string {
|
||||||
|
return "^" + match
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -73,6 +73,9 @@ func (r *LightRenderer) initPlatform() error {
|
|||||||
fd := int(r.inHandle)
|
fd := int(r.inHandle)
|
||||||
b := make([]byte, 1)
|
b := make([]byte, 1)
|
||||||
for {
|
for {
|
||||||
|
// HACK: if run from PSReadline, something resets ConsoleMode to remove ENABLE_VIRTUAL_TERMINAL_INPUT.
|
||||||
|
_ = windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
|
||||||
|
|
||||||
_, err := util.Read(fd, b)
|
_, err := util.Read(fd, b)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
r.ttyinChannel <- b[0]
|
r.ttyinChannel <- b[0]
|
||||||
|
|||||||
@@ -120,8 +120,11 @@ func (a Attr) Merge(b Attr) Attr {
|
|||||||
return a | b
|
return a | b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handle the following as private members of FullscreenRenderer instance
|
||||||
|
// they are declared here to prevent introducing tcell library in non-windows builds
|
||||||
var (
|
var (
|
||||||
_screen tcell.Screen
|
_screen tcell.Screen
|
||||||
|
_prevMouseButton tcell.ButtonMask
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *FullscreenRenderer) initScreen() {
|
func (r *FullscreenRenderer) initScreen() {
|
||||||
@@ -185,14 +188,48 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
|
|
||||||
// process mouse events:
|
// process mouse events:
|
||||||
case *tcell.EventMouse:
|
case *tcell.EventMouse:
|
||||||
|
// mouse down events have zeroed buttons, so we can't use them
|
||||||
|
// mouse up event consists of two events, 1. (main) event with modifier and other metadata, 2. event with zeroed buttons
|
||||||
|
// so mouse click is three consecutive events, but the first and last are indistinguishable from movement events (with released buttons)
|
||||||
|
// dragging has same structure, it only repeats the middle (main) event appropriately
|
||||||
x, y := ev.Position()
|
x, y := ev.Position()
|
||||||
button := ev.Buttons()
|
|
||||||
mod := ev.Modifiers() != 0
|
mod := ev.Modifiers() != 0
|
||||||
if button&tcell.WheelDown != 0 {
|
|
||||||
|
// since we dont have mouse down events (unlike LightRenderer), we need to track state in prevButton
|
||||||
|
prevButton, button := _prevMouseButton, ev.Buttons()
|
||||||
|
_prevMouseButton = button
|
||||||
|
drag := prevButton == button
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case button&tcell.WheelDown != 0:
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, false, mod}}
|
return Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, false, mod}}
|
||||||
} else if button&tcell.WheelUp != 0 {
|
case button&tcell.WheelUp != 0:
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, +1, false, false, false, mod}}
|
return Event{Mouse, 0, &MouseEvent{y, x, +1, false, false, false, mod}}
|
||||||
} else if runtime.GOOS != "windows" {
|
case button&tcell.Button1 != 0 && !drag:
|
||||||
|
// all potential double click events put their 'line' coordinate in the clickY array
|
||||||
|
// double click event has two conditions, temporal and spatial, the first is checked here
|
||||||
|
now := time.Now()
|
||||||
|
if now.Sub(r.prevDownTime) < doubleClickDuration {
|
||||||
|
r.clickY = append(r.clickY, y)
|
||||||
|
} else {
|
||||||
|
r.clickY = []int{y}
|
||||||
|
}
|
||||||
|
r.prevDownTime = now
|
||||||
|
|
||||||
|
// detect double clicks (also check for spatial condition)
|
||||||
|
n := len(r.clickY)
|
||||||
|
double := n > 1 && r.clickY[n-2] == r.clickY[n-1]
|
||||||
|
if double {
|
||||||
|
// make sure two consecutive double clicks require four clicks
|
||||||
|
r.clickY = []int{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fire single or double click event
|
||||||
|
return Event{Mouse, 0, &MouseEvent{y, x, 0, true, !double, double, mod}}
|
||||||
|
case button&tcell.Button2 != 0 && !drag:
|
||||||
|
return Event{Mouse, 0, &MouseEvent{y, x, 0, false, true, false, mod}}
|
||||||
|
case runtime.GOOS != "windows":
|
||||||
|
|
||||||
// double and single taps on Windows don't quite work due to
|
// double and single taps on Windows don't quite work due to
|
||||||
// the console acting on the events and not allowing us
|
// the console acting on the events and not allowing us
|
||||||
// to consume them.
|
// to consume them.
|
||||||
@@ -223,9 +260,13 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
// process keyboard:
|
// process keyboard:
|
||||||
case *tcell.EventKey:
|
case *tcell.EventKey:
|
||||||
mods := ev.Modifiers()
|
mods := ev.Modifiers()
|
||||||
|
none := mods == tcell.ModNone
|
||||||
alt := (mods & tcell.ModAlt) > 0
|
alt := (mods & tcell.ModAlt) > 0
|
||||||
|
ctrl := (mods & tcell.ModCtrl) > 0
|
||||||
shift := (mods & tcell.ModShift) > 0
|
shift := (mods & tcell.ModShift) > 0
|
||||||
|
ctrlAlt := ctrl && alt
|
||||||
altShift := alt && shift
|
altShift := alt && shift
|
||||||
|
|
||||||
keyfn := func(r rune) Event {
|
keyfn := func(r rune) Event {
|
||||||
if alt {
|
if alt {
|
||||||
return CtrlAltKey(r)
|
return CtrlAltKey(r)
|
||||||
@@ -233,6 +274,7 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
return EventType(CtrlA.Int() - 'a' + int(r)).AsEvent()
|
return EventType(CtrlA.Int() - 'a' + int(r)).AsEvent()
|
||||||
}
|
}
|
||||||
switch ev.Key() {
|
switch ev.Key() {
|
||||||
|
// section 1: Ctrl+(Alt)+[a-z]
|
||||||
case tcell.KeyCtrlA:
|
case tcell.KeyCtrlA:
|
||||||
return keyfn('a')
|
return keyfn('a')
|
||||||
case tcell.KeyCtrlB:
|
case tcell.KeyCtrlB:
|
||||||
@@ -248,7 +290,21 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
case tcell.KeyCtrlG:
|
case tcell.KeyCtrlG:
|
||||||
return keyfn('g')
|
return keyfn('g')
|
||||||
case tcell.KeyCtrlH:
|
case tcell.KeyCtrlH:
|
||||||
return keyfn('h')
|
switch ev.Rune() {
|
||||||
|
case 0:
|
||||||
|
if ctrl {
|
||||||
|
return Event{BSpace, 0, nil}
|
||||||
|
}
|
||||||
|
case rune(tcell.KeyCtrlH):
|
||||||
|
switch {
|
||||||
|
case ctrl:
|
||||||
|
return keyfn('h')
|
||||||
|
case alt:
|
||||||
|
return Event{AltBS, 0, nil}
|
||||||
|
case none, shift:
|
||||||
|
return Event{BSpace, 0, nil}
|
||||||
|
}
|
||||||
|
}
|
||||||
case tcell.KeyCtrlI:
|
case tcell.KeyCtrlI:
|
||||||
return keyfn('i')
|
return keyfn('i')
|
||||||
case tcell.KeyCtrlJ:
|
case tcell.KeyCtrlJ:
|
||||||
@@ -285,20 +341,25 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
return keyfn('y')
|
return keyfn('y')
|
||||||
case tcell.KeyCtrlZ:
|
case tcell.KeyCtrlZ:
|
||||||
return keyfn('z')
|
return keyfn('z')
|
||||||
|
// section 2: Ctrl+[ \]_]
|
||||||
case tcell.KeyCtrlSpace:
|
case tcell.KeyCtrlSpace:
|
||||||
return Event{CtrlSpace, 0, nil}
|
return Event{CtrlSpace, 0, nil}
|
||||||
case tcell.KeyCtrlBackslash:
|
case tcell.KeyCtrlBackslash:
|
||||||
return Event{CtrlBackSlash, 0, nil}
|
return Event{CtrlBackSlash, 0, nil}
|
||||||
case tcell.KeyCtrlRightSq:
|
case tcell.KeyCtrlRightSq:
|
||||||
return Event{CtrlRightBracket, 0, nil}
|
return Event{CtrlRightBracket, 0, nil}
|
||||||
|
case tcell.KeyCtrlCarat:
|
||||||
|
return Event{CtrlCaret, 0, nil}
|
||||||
case tcell.KeyCtrlUnderscore:
|
case tcell.KeyCtrlUnderscore:
|
||||||
return Event{CtrlSlash, 0, nil}
|
return Event{CtrlSlash, 0, nil}
|
||||||
|
// section 3: (Alt)+Backspace2
|
||||||
case tcell.KeyBackspace2:
|
case tcell.KeyBackspace2:
|
||||||
if alt {
|
if alt {
|
||||||
return Event{AltBS, 0, nil}
|
return Event{AltBS, 0, nil}
|
||||||
}
|
}
|
||||||
return Event{BSpace, 0, nil}
|
return Event{BSpace, 0, nil}
|
||||||
|
|
||||||
|
// section 4: (Alt+Shift)+Key(Up|Down|Left|Right)
|
||||||
case tcell.KeyUp:
|
case tcell.KeyUp:
|
||||||
if altShift {
|
if altShift {
|
||||||
return Event{AltSUp, 0, nil}
|
return Event{AltSUp, 0, nil}
|
||||||
@@ -344,6 +405,7 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
}
|
}
|
||||||
return Event{Right, 0, nil}
|
return Event{Right, 0, nil}
|
||||||
|
|
||||||
|
// section 5: (Insert|Home|Delete|End|PgUp|PgDn|BackTab|F1-F12)
|
||||||
case tcell.KeyInsert:
|
case tcell.KeyInsert:
|
||||||
return Event{Insert, 0, nil}
|
return Event{Insert, 0, nil}
|
||||||
case tcell.KeyHome:
|
case tcell.KeyHome:
|
||||||
@@ -356,10 +418,8 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
return Event{PgUp, 0, nil}
|
return Event{PgUp, 0, nil}
|
||||||
case tcell.KeyPgDn:
|
case tcell.KeyPgDn:
|
||||||
return Event{PgDn, 0, nil}
|
return Event{PgDn, 0, nil}
|
||||||
|
|
||||||
case tcell.KeyBacktab:
|
case tcell.KeyBacktab:
|
||||||
return Event{BTab, 0, nil}
|
return Event{BTab, 0, nil}
|
||||||
|
|
||||||
case tcell.KeyF1:
|
case tcell.KeyF1:
|
||||||
return Event{F1, 0, nil}
|
return Event{F1, 0, nil}
|
||||||
case tcell.KeyF2:
|
case tcell.KeyF2:
|
||||||
@@ -385,20 +445,31 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
case tcell.KeyF12:
|
case tcell.KeyF12:
|
||||||
return Event{F12, 0, nil}
|
return Event{F12, 0, nil}
|
||||||
|
|
||||||
// ev.Ch doesn't work for some reason for space:
|
// section 6: (Ctrl+Alt)+'rune'
|
||||||
case tcell.KeyRune:
|
case tcell.KeyRune:
|
||||||
r := ev.Rune()
|
r := ev.Rune()
|
||||||
if alt {
|
|
||||||
return AltKey(r)
|
|
||||||
}
|
|
||||||
return Event{Rune, r, nil}
|
|
||||||
|
|
||||||
|
switch {
|
||||||
|
// translate native key events to ascii control characters
|
||||||
|
case r == ' ' && ctrl:
|
||||||
|
return Event{CtrlSpace, 0, nil}
|
||||||
|
// handle AltGr characters
|
||||||
|
case ctrlAlt:
|
||||||
|
return Event{Rune, r, nil} // dropping modifiers
|
||||||
|
// simple characters (possibly with modifier)
|
||||||
|
case alt:
|
||||||
|
return AltKey(r)
|
||||||
|
default:
|
||||||
|
return Event{Rune, r, nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
// section 7: Esc
|
||||||
case tcell.KeyEsc:
|
case tcell.KeyEsc:
|
||||||
return Event{ESC, 0, nil}
|
return Event{ESC, 0, nil}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// section 8: Invalid
|
||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
392
src/tui/tcell_test.go
Normal file
392
src/tui/tcell_test.go
Normal file
@@ -0,0 +1,392 @@
|
|||||||
|
// +build tcell windows
|
||||||
|
|
||||||
|
package tui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell"
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func assert(t *testing.T, context string, got interface{}, want interface{}) bool {
|
||||||
|
if got == want {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
t.Errorf("%s = (%T)%v, want (%T)%v", context, got, got, want, want)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the handling of the tcell keyboard events.
|
||||||
|
func TestGetCharEventKey(t *testing.T) {
|
||||||
|
if util.ToTty() {
|
||||||
|
// This test is skipped when output goes to terminal, because it causes
|
||||||
|
// some glitches:
|
||||||
|
// - output lines may not start at the beginning of a row which makes
|
||||||
|
// the output unreadable
|
||||||
|
// - terminal may get cleared which prevents you from seeing results of
|
||||||
|
// previous tests
|
||||||
|
// Good ways to prevent the glitches are piping the output to a pager
|
||||||
|
// or redirecting to a file. I've found `less +G` to be trouble-free.
|
||||||
|
t.Skip("Skipped because this test misbehaves in terminal, pipe to a pager or redirect output to a file to run it safely.")
|
||||||
|
} else if testing.Verbose() {
|
||||||
|
// I have observed a behaviour when this test outputted more than 8192
|
||||||
|
// bytes (32*256) into the 'less' pager, both the go's test executable
|
||||||
|
// and the pager hanged. The go's executable was blocking on printing.
|
||||||
|
// I was able to create minimal working example of that behaviour, but
|
||||||
|
// that example hanged after 12256 bytes (32*(256+127)).
|
||||||
|
t.Log("If you are piping this test to a pager and it hangs, make the pager greedy for input, e.g. 'less +G'.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !HasFullscreenRenderer() {
|
||||||
|
t.Skip("Can't test FullscreenRenderer.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// construct test cases
|
||||||
|
type giveKey struct {
|
||||||
|
Type tcell.Key
|
||||||
|
Char rune
|
||||||
|
Mods tcell.ModMask
|
||||||
|
}
|
||||||
|
type wantKey = Event
|
||||||
|
type testCase struct {
|
||||||
|
giveKey
|
||||||
|
wantKey
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
Some test cases are marked "fabricated". It means that giveKey value
|
||||||
|
is valid, but it is not what you get when you press the keys. For
|
||||||
|
example Ctrl+C will NOT give you tcell.KeyCtrlC, but tcell.KeyETX
|
||||||
|
(End-Of-Text character, causing SIGINT).
|
||||||
|
I was trying to accompany the fabricated test cases with real ones.
|
||||||
|
|
||||||
|
Some test cases are marked "unhandled". It means that giveKey.Type
|
||||||
|
is not present in tcell.go source code. It can still be handled via
|
||||||
|
implicit or explicit alias.
|
||||||
|
|
||||||
|
If not said otherwise, test cases are for US keyboard.
|
||||||
|
|
||||||
|
(tabstop=44)
|
||||||
|
*/
|
||||||
|
tests := []testCase{
|
||||||
|
|
||||||
|
// section 1: Ctrl+(Alt)+[a-z]
|
||||||
|
{giveKey{tcell.KeyCtrlA, rune(tcell.KeyCtrlA), tcell.ModCtrl}, wantKey{CtrlA, 0, nil}},
|
||||||
|
{giveKey{tcell.KeyCtrlC, rune(tcell.KeyCtrlC), tcell.ModCtrl}, wantKey{CtrlC, 0, nil}}, // fabricated
|
||||||
|
{giveKey{tcell.KeyETX, rune(tcell.KeyETX), tcell.ModCtrl}, wantKey{CtrlC, 0, nil}}, // this is SIGINT (Ctrl+C)
|
||||||
|
{giveKey{tcell.KeyCtrlZ, rune(tcell.KeyCtrlZ), tcell.ModCtrl}, wantKey{CtrlZ, 0, nil}}, // fabricated
|
||||||
|
// KeyTab is alias for KeyTAB
|
||||||
|
{giveKey{tcell.KeyCtrlI, rune(tcell.KeyCtrlI), tcell.ModCtrl}, wantKey{Tab, 0, nil}}, // fabricated
|
||||||
|
{giveKey{tcell.KeyTab, rune(tcell.KeyTab), tcell.ModNone}, wantKey{Tab, 0, nil}}, // unhandled, actual "Tab" keystroke
|
||||||
|
{giveKey{tcell.KeyTAB, rune(tcell.KeyTAB), tcell.ModNone}, wantKey{Tab, 0, nil}}, // fabricated, unhandled
|
||||||
|
// KeyEnter is alias for KeyCR
|
||||||
|
{giveKey{tcell.KeyCtrlM, rune(tcell.KeyCtrlM), tcell.ModNone}, wantKey{CtrlM, 0, nil}}, // actual "Enter" keystroke
|
||||||
|
{giveKey{tcell.KeyCR, rune(tcell.KeyCR), tcell.ModNone}, wantKey{CtrlM, 0, nil}}, // fabricated, unhandled
|
||||||
|
{giveKey{tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone}, wantKey{CtrlM, 0, nil}}, // fabricated, unhandled
|
||||||
|
// Ctrl+Alt keys
|
||||||
|
{giveKey{tcell.KeyCtrlA, rune(tcell.KeyCtrlA), tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAlt, 'a', nil}}, // fabricated
|
||||||
|
{giveKey{tcell.KeyCtrlA, rune(tcell.KeyCtrlA), tcell.ModCtrl | tcell.ModAlt | tcell.ModShift}, wantKey{CtrlAlt, 'a', nil}}, // fabricated
|
||||||
|
|
||||||
|
// section 2: Ctrl+[ \]_]
|
||||||
|
{giveKey{tcell.KeyCtrlSpace, rune(tcell.KeyCtrlSpace), tcell.ModCtrl}, wantKey{CtrlSpace, 0, nil}}, // fabricated
|
||||||
|
{giveKey{tcell.KeyNUL, rune(tcell.KeyNUL), tcell.ModNone}, wantKey{CtrlSpace, 0, nil}}, // fabricated, unhandled
|
||||||
|
{giveKey{tcell.KeyRune, ' ', tcell.ModCtrl}, wantKey{CtrlSpace, 0, nil}}, // actual Ctrl+' '
|
||||||
|
{giveKey{tcell.KeyCtrlBackslash, rune(tcell.KeyCtrlBackslash), tcell.ModCtrl}, wantKey{CtrlBackSlash, 0, nil}},
|
||||||
|
{giveKey{tcell.KeyCtrlRightSq, rune(tcell.KeyCtrlRightSq), tcell.ModCtrl}, wantKey{CtrlRightBracket, 0, nil}},
|
||||||
|
{giveKey{tcell.KeyCtrlCarat, rune(tcell.KeyCtrlCarat), tcell.ModShift | tcell.ModCtrl}, wantKey{CtrlCaret, 0, nil}}, // fabricated
|
||||||
|
{giveKey{tcell.KeyRS, rune(tcell.KeyRS), tcell.ModShift | tcell.ModCtrl}, wantKey{CtrlCaret, 0, nil}}, // actual Ctrl+Shift+6 (i.e. Ctrl+^) keystroke
|
||||||
|
{giveKey{tcell.KeyCtrlUnderscore, rune(tcell.KeyCtrlUnderscore), tcell.ModShift | tcell.ModCtrl}, wantKey{CtrlSlash, 0, nil}},
|
||||||
|
|
||||||
|
// section 3: (Alt)+Backspace2
|
||||||
|
// KeyBackspace2 is alias for KeyDEL = 0x7F (ASCII) (allegedly unused by Windows)
|
||||||
|
// KeyDelete = 0x2E (VK_DELETE constant in Windows)
|
||||||
|
// KeyBackspace is alias for KeyBS = 0x08 (ASCII) (implicit alias with KeyCtrlH)
|
||||||
|
{giveKey{tcell.KeyBackspace2, 0, tcell.ModNone}, wantKey{BSpace, 0, nil}}, // fabricated
|
||||||
|
{giveKey{tcell.KeyBackspace2, 0, tcell.ModAlt}, wantKey{AltBS, 0, nil}}, // fabricated
|
||||||
|
{giveKey{tcell.KeyDEL, 0, tcell.ModNone}, wantKey{BSpace, 0, nil}}, // fabricated, unhandled
|
||||||
|
{giveKey{tcell.KeyDelete, 0, tcell.ModNone}, wantKey{Del, 0, nil}},
|
||||||
|
{giveKey{tcell.KeyDelete, 0, tcell.ModAlt}, wantKey{Del, 0, nil}},
|
||||||
|
{giveKey{tcell.KeyBackspace, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||||
|
{giveKey{tcell.KeyBS, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||||
|
{giveKey{tcell.KeyCtrlH, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||||
|
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModNone}, wantKey{BSpace, 0, nil}}, // actual "Backspace" keystroke
|
||||||
|
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModAlt}, wantKey{AltBS, 0, nil}}, // actual "Alt+Backspace" keystroke
|
||||||
|
{giveKey{tcell.KeyDEL, rune(tcell.KeyDEL), tcell.ModCtrl}, wantKey{BSpace, 0, nil}}, // actual "Ctrl+Backspace" keystroke
|
||||||
|
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModShift}, wantKey{BSpace, 0, nil}}, // actual "Shift+Backspace" keystroke
|
||||||
|
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModAlt}, wantKey{BSpace, 0, nil}}, // actual "Ctrl+Alt+Backspace" keystroke
|
||||||
|
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModShift}, wantKey{BSpace, 0, nil}}, // actual "Ctrl+Shift+Backspace" keystroke
|
||||||
|
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModShift | tcell.ModAlt}, wantKey{AltBS, 0, nil}}, // actual "Shift+Alt+Backspace" keystroke
|
||||||
|
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModAlt | tcell.ModShift}, wantKey{BSpace, 0, nil}}, // actual "Ctrl+Shift+Alt+Backspace" keystroke
|
||||||
|
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl}, wantKey{CtrlH, 0, nil}}, // actual "Ctrl+H" keystroke
|
||||||
|
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAlt, 'h', nil}}, // fabricated "Ctrl+Alt+H" keystroke
|
||||||
|
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl | tcell.ModShift}, wantKey{CtrlH, 0, nil}}, // actual "Ctrl+Shift+H" keystroke
|
||||||
|
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl | tcell.ModAlt | tcell.ModShift}, wantKey{CtrlAlt, 'h', nil}}, // fabricated "Ctrl+Shift+Alt+H" keystroke
|
||||||
|
|
||||||
|
// section 4: (Alt+Shift)+Key(Up|Down|Left|Right)
|
||||||
|
{giveKey{tcell.KeyUp, 0, tcell.ModNone}, wantKey{Up, 0, nil}},
|
||||||
|
{giveKey{tcell.KeyDown, 0, tcell.ModAlt}, wantKey{AltDown, 0, nil}},
|
||||||
|
{giveKey{tcell.KeyLeft, 0, tcell.ModShift}, wantKey{SLeft, 0, nil}},
|
||||||
|
{giveKey{tcell.KeyRight, 0, tcell.ModShift | tcell.ModAlt}, wantKey{AltSRight, 0, nil}},
|
||||||
|
{giveKey{tcell.KeyUpLeft, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||||
|
{giveKey{tcell.KeyUpRight, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||||
|
{giveKey{tcell.KeyDownLeft, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||||
|
{giveKey{tcell.KeyDownRight, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||||
|
{giveKey{tcell.KeyCenter, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||||
|
// section 5: (Insert|Home|Delete|End|PgUp|PgDn|BackTab|F1-F12)
|
||||||
|
{giveKey{tcell.KeyInsert, 0, tcell.ModNone}, wantKey{Insert, 0, nil}},
|
||||||
|
{giveKey{tcell.KeyF1, 0, tcell.ModNone}, wantKey{F1, 0, nil}},
|
||||||
|
// section 6: (Ctrl+Alt)+'rune'
|
||||||
|
{giveKey{tcell.KeyRune, 'a', tcell.ModNone}, wantKey{Rune, 'a', nil}},
|
||||||
|
{giveKey{tcell.KeyRune, 'a', tcell.ModCtrl}, wantKey{Rune, 'a', nil}}, // fabricated
|
||||||
|
{giveKey{tcell.KeyRune, 'a', tcell.ModAlt}, wantKey{Alt, 'a', nil}},
|
||||||
|
{giveKey{tcell.KeyRune, 'A', tcell.ModAlt}, wantKey{Alt, 'A', nil}},
|
||||||
|
{giveKey{tcell.KeyRune, '`', tcell.ModAlt}, wantKey{Alt, '`', nil}},
|
||||||
|
/*
|
||||||
|
"Input method" in Windows Language options:
|
||||||
|
US: "US Keyboard" does not generate any characters (and thus any events) in Ctrl+Alt+[a-z] range
|
||||||
|
CS: "Czech keyboard"
|
||||||
|
DE: "German keyboard"
|
||||||
|
|
||||||
|
Note that right Alt is not just `tcell.ModAlt` on foreign language keyboards, but it is the AltGr `tcell.ModCtrl|tcell.ModAlt`.
|
||||||
|
*/
|
||||||
|
{giveKey{tcell.KeyRune, '{', tcell.ModCtrl | tcell.ModAlt}, wantKey{Rune, '{', nil}}, // CS: Ctrl+Alt+b = "{" // Note that this does not interfere with CtrlB, since the "b" is replaced with "{" on OS level
|
||||||
|
{giveKey{tcell.KeyRune, '$', tcell.ModCtrl | tcell.ModAlt}, wantKey{Rune, '$', nil}}, // CS: Ctrl+Alt+ů = "$"
|
||||||
|
{giveKey{tcell.KeyRune, '~', tcell.ModCtrl | tcell.ModAlt}, wantKey{Rune, '~', nil}}, // CS: Ctrl+Alt++ = "~"
|
||||||
|
{giveKey{tcell.KeyRune, '`', tcell.ModCtrl | tcell.ModAlt}, wantKey{Rune, '`', nil}}, // CS: Ctrl+Alt+ý,Space = "`" // this is dead key, space is required to emit the char
|
||||||
|
|
||||||
|
{giveKey{tcell.KeyRune, '{', tcell.ModCtrl | tcell.ModAlt}, wantKey{Rune, '{', nil}}, // DE: Ctrl+Alt+7 = "{"
|
||||||
|
{giveKey{tcell.KeyRune, '@', tcell.ModCtrl | tcell.ModAlt}, wantKey{Rune, '@', nil}}, // DE: Ctrl+Alt+q = "@"
|
||||||
|
{giveKey{tcell.KeyRune, 'µ', tcell.ModCtrl | tcell.ModAlt}, wantKey{Rune, 'µ', nil}}, // DE: Ctrl+Alt+m = "µ"
|
||||||
|
|
||||||
|
// section 7: Esc
|
||||||
|
// KeyEsc and KeyEscape are aliases for KeyESC
|
||||||
|
{giveKey{tcell.KeyEsc, rune(tcell.KeyEsc), tcell.ModNone}, wantKey{ESC, 0, nil}}, // fabricated
|
||||||
|
{giveKey{tcell.KeyESC, rune(tcell.KeyESC), tcell.ModNone}, wantKey{ESC, 0, nil}}, // unhandled
|
||||||
|
{giveKey{tcell.KeyEscape, rune(tcell.KeyEscape), tcell.ModNone}, wantKey{ESC, 0, nil}}, // fabricated, unhandled
|
||||||
|
{giveKey{tcell.KeyESC, rune(tcell.KeyESC), tcell.ModCtrl}, wantKey{ESC, 0, nil}}, // actual Ctrl+[ keystroke
|
||||||
|
{giveKey{tcell.KeyCtrlLeftSq, rune(tcell.KeyCtrlLeftSq), tcell.ModCtrl}, wantKey{ESC, 0, nil}}, // fabricated, unhandled
|
||||||
|
|
||||||
|
// section 8: Invalid
|
||||||
|
{giveKey{tcell.KeyRune, 'a', tcell.ModMeta}, wantKey{Rune, 'a', nil}}, // fabricated
|
||||||
|
{giveKey{tcell.KeyF24, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}},
|
||||||
|
{giveKey{tcell.KeyHelp, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||||
|
{giveKey{tcell.KeyExit, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||||
|
{giveKey{tcell.KeyClear, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // unhandled, actual keystroke Numpad_5 with Numlock OFF
|
||||||
|
{giveKey{tcell.KeyCancel, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||||
|
{giveKey{tcell.KeyPrint, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||||
|
{giveKey{tcell.KeyPause, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // unhandled
|
||||||
|
|
||||||
|
}
|
||||||
|
r := NewFullscreenRenderer(&ColorTheme{}, false, false)
|
||||||
|
r.Init()
|
||||||
|
|
||||||
|
// run and evaluate the tests
|
||||||
|
for _, test := range tests {
|
||||||
|
// generate key event
|
||||||
|
giveEvent := tcell.NewEventKey(test.giveKey.Type, test.giveKey.Char, test.giveKey.Mods)
|
||||||
|
_screen.PostEventWait(giveEvent)
|
||||||
|
t.Logf("giveEvent = %T{key: %v, ch: %q (%[3]v), mod: %#04b}\n", giveEvent, giveEvent.Key(), giveEvent.Rune(), giveEvent.Modifiers())
|
||||||
|
|
||||||
|
// process the event in fzf and evaluate the test
|
||||||
|
gotEvent := r.GetChar()
|
||||||
|
// skip Resize events, those are sometimes put in the buffer outside of this test
|
||||||
|
for gotEvent.Type == Resize {
|
||||||
|
t.Logf("Resize swallowed")
|
||||||
|
gotEvent = r.GetChar()
|
||||||
|
}
|
||||||
|
t.Logf("wantEvent = %T{Type: %v, Char: %q (%[3]v)}\n", test.wantKey, test.wantKey.Type, test.wantKey.Char)
|
||||||
|
t.Logf("gotEvent = %T{Type: %v, Char: %q (%[3]v)}\n", gotEvent, gotEvent.Type, gotEvent.Char)
|
||||||
|
|
||||||
|
assert(t, "r.GetChar().Type", gotEvent.Type, test.wantKey.Type)
|
||||||
|
assert(t, "r.GetChar().Char", gotEvent.Char, test.wantKey.Char)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Quick reference
|
||||||
|
---------------
|
||||||
|
|
||||||
|
(tabstop=18)
|
||||||
|
(this is not mapping table, it merely puts multiple constants ranges in one table)
|
||||||
|
|
||||||
|
¹) the two columns are each other implicit alias
|
||||||
|
²) explicit aliases here
|
||||||
|
|
||||||
|
%v section # tcell ctrl key¹ tcell ctrl char¹ tcell alias² tui constants tcell named keys tcell mods
|
||||||
|
-- --------- -------------- --------------- ----------- ------------- ---------------- ----------
|
||||||
|
0 2 KeyCtrlSpace KeyNUL = ^@ Rune ModNone
|
||||||
|
1 1 KeyCtrlA KeySOH = ^A CtrlA ModShift
|
||||||
|
2 1 KeyCtrlB KeySTX = ^B CtrlB ModCtrl
|
||||||
|
3 1 KeyCtrlC KeyETX = ^C CtrlC
|
||||||
|
4 1 KeyCtrlD KeyEOT = ^D CtrlD ModAlt
|
||||||
|
5 1 KeyCtrlE KeyENQ = ^E CtrlE
|
||||||
|
6 1 KeyCtrlF KeyACK = ^F CtrlF
|
||||||
|
7 1 KeyCtrlG KeyBEL = ^G CtrlG
|
||||||
|
8 1 KeyCtrlH KeyBS = ^H KeyBackspace CtrlH ModMeta
|
||||||
|
9 1 KeyCtrlI KeyTAB = ^I KeyTab Tab
|
||||||
|
10 1 KeyCtrlJ KeyLF = ^J CtrlJ
|
||||||
|
11 1 KeyCtrlK KeyVT = ^K CtrlK
|
||||||
|
12 1 KeyCtrlL KeyFF = ^L CtrlL
|
||||||
|
13 1 KeyCtrlM KeyCR = ^M KeyEnter CtrlM
|
||||||
|
14 1 KeyCtrlN KeySO = ^N CtrlN
|
||||||
|
15 1 KeyCtrlO KeySI = ^O CtrlO
|
||||||
|
16 1 KeyCtrlP KeyDLE = ^P CtrlP
|
||||||
|
17 1 KeyCtrlQ KeyDC1 = ^Q CtrlQ
|
||||||
|
18 1 KeyCtrlR KeyDC2 = ^R CtrlR
|
||||||
|
19 1 KeyCtrlS KeyDC3 = ^S CtrlS
|
||||||
|
20 1 KeyCtrlT KeyDC4 = ^T CtrlT
|
||||||
|
21 1 KeyCtrlU KeyNAK = ^U CtrlU
|
||||||
|
22 1 KeyCtrlV KeySYN = ^V CtrlV
|
||||||
|
23 1 KeyCtrlW KeyETB = ^W CtrlW
|
||||||
|
24 1 KeyCtrlX KeyCAN = ^X CtrlX
|
||||||
|
25 1 KeyCtrlY KeyEM = ^Y CtrlY
|
||||||
|
26 1 KeyCtrlZ KeySUB = ^Z CtrlZ
|
||||||
|
27 7 KeyCtrlLeftSq KeyESC = ^[ KeyEsc, KeyEscape ESC
|
||||||
|
28 2 KeyCtrlBackslash KeyFS = ^\ CtrlSpace
|
||||||
|
29 2 KeyCtrlRightSq KeyGS = ^] CtrlBackSlash
|
||||||
|
30 2 KeyCtrlCarat KeyRS = ^^ CtrlRightBracket
|
||||||
|
31 2 KeyCtrlUnderscore KeyUS = ^_ CtrlCaret
|
||||||
|
32 CtrlSlash
|
||||||
|
33 Invalid
|
||||||
|
34 Resize
|
||||||
|
35 Mouse
|
||||||
|
36 DoubleClick
|
||||||
|
37 LeftClick
|
||||||
|
38 RightClick
|
||||||
|
39 BTab
|
||||||
|
40 BSpace
|
||||||
|
41 Del
|
||||||
|
42 PgUp
|
||||||
|
43 PgDn
|
||||||
|
44 Up
|
||||||
|
45 Down
|
||||||
|
46 Left
|
||||||
|
47 Right
|
||||||
|
48 Home
|
||||||
|
49 End
|
||||||
|
50 Insert
|
||||||
|
51 SUp
|
||||||
|
52 SDown
|
||||||
|
53 SLeft
|
||||||
|
54 SRight
|
||||||
|
55 F1
|
||||||
|
56 F2
|
||||||
|
57 F3
|
||||||
|
58 F4
|
||||||
|
59 F5
|
||||||
|
60 F6
|
||||||
|
61 F7
|
||||||
|
62 F8
|
||||||
|
63 F9
|
||||||
|
64 F10
|
||||||
|
65 F11
|
||||||
|
66 F12
|
||||||
|
67 Change
|
||||||
|
68 BackwardEOF
|
||||||
|
69 AltBS
|
||||||
|
70 AltUp
|
||||||
|
71 AltDown
|
||||||
|
72 AltLeft
|
||||||
|
73 AltRight
|
||||||
|
74 AltSUp
|
||||||
|
75 AltSDown
|
||||||
|
76 AltSLeft
|
||||||
|
77 AltSRight
|
||||||
|
78 Alt
|
||||||
|
79 CtrlAlt
|
||||||
|
..
|
||||||
|
127 3 KeyDEL KeyBackspace2
|
||||||
|
..
|
||||||
|
256 6 KeyRune
|
||||||
|
257 4 KeyUp
|
||||||
|
258 4 KeyDown
|
||||||
|
259 4 KeyRight
|
||||||
|
260 4 KeyLeft
|
||||||
|
261 8 KeyUpLeft
|
||||||
|
262 8 KeyUpRight
|
||||||
|
263 8 KeyDownLeft
|
||||||
|
264 8 KeyDownRight
|
||||||
|
265 8 KeyCenter
|
||||||
|
266 5 KeyPgUp
|
||||||
|
267 5 KeyPgDn
|
||||||
|
268 5 KeyHome
|
||||||
|
269 5 KeyEnd
|
||||||
|
270 5 KeyInsert
|
||||||
|
271 5 KeyDelete
|
||||||
|
272 8 KeyHelp
|
||||||
|
273 8 KeyExit
|
||||||
|
274 8 KeyClear
|
||||||
|
275 8 KeyCancel
|
||||||
|
276 8 KeyPrint
|
||||||
|
277 8 KeyPause
|
||||||
|
278 5 KeyBacktab
|
||||||
|
279 5 KeyF1
|
||||||
|
280 5 KeyF2
|
||||||
|
281 5 KeyF3
|
||||||
|
282 5 KeyF4
|
||||||
|
283 5 KeyF5
|
||||||
|
284 5 KeyF6
|
||||||
|
285 5 KeyF7
|
||||||
|
286 5 KeyF8
|
||||||
|
287 5 KeyF9
|
||||||
|
288 5 KeyF10
|
||||||
|
289 5 KeyF11
|
||||||
|
290 5 KeyF12
|
||||||
|
291 8 KeyF13
|
||||||
|
292 8 KeyF14
|
||||||
|
293 8 KeyF15
|
||||||
|
294 8 KeyF16
|
||||||
|
295 8 KeyF17
|
||||||
|
296 8 KeyF18
|
||||||
|
297 8 KeyF19
|
||||||
|
298 8 KeyF20
|
||||||
|
299 8 KeyF21
|
||||||
|
300 8 KeyF22
|
||||||
|
301 8 KeyF23
|
||||||
|
302 8 KeyF24
|
||||||
|
303 8 KeyF25
|
||||||
|
304 8 KeyF26
|
||||||
|
305 8 KeyF27
|
||||||
|
306 8 KeyF28
|
||||||
|
307 8 KeyF29
|
||||||
|
308 8 KeyF30
|
||||||
|
309 8 KeyF31
|
||||||
|
310 8 KeyF32
|
||||||
|
311 8 KeyF33
|
||||||
|
312 8 KeyF34
|
||||||
|
313 8 KeyF35
|
||||||
|
314 8 KeyF36
|
||||||
|
315 8 KeyF37
|
||||||
|
316 8 KeyF38
|
||||||
|
317 8 KeyF39
|
||||||
|
318 8 KeyF40
|
||||||
|
319 8 KeyF41
|
||||||
|
320 8 KeyF42
|
||||||
|
321 8 KeyF43
|
||||||
|
322 8 KeyF44
|
||||||
|
323 8 KeyF45
|
||||||
|
324 8 KeyF46
|
||||||
|
325 8 KeyF47
|
||||||
|
326 8 KeyF48
|
||||||
|
327 8 KeyF49
|
||||||
|
328 8 KeyF50
|
||||||
|
329 8 KeyF51
|
||||||
|
330 8 KeyF52
|
||||||
|
331 8 KeyF53
|
||||||
|
332 8 KeyF54
|
||||||
|
333 8 KeyF55
|
||||||
|
334 8 KeyF56
|
||||||
|
335 8 KeyF57
|
||||||
|
336 8 KeyF58
|
||||||
|
337 8 KeyF59
|
||||||
|
338 8 KeyF60
|
||||||
|
339 8 KeyF61
|
||||||
|
340 8 KeyF62
|
||||||
|
341 8 KeyF63
|
||||||
|
342 8 KeyF64
|
||||||
|
-- --------- -------------- --------------- ----------- ------------- ---------------- ----------
|
||||||
|
%v section # tcell ctrl key tcell ctrl char tcell alias tui constants tcell named keys tcell mods
|
||||||
|
*/
|
||||||
@@ -3,6 +3,7 @@ package util
|
|||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mattn/go-isatty"
|
"github.com/mattn/go-isatty"
|
||||||
@@ -21,7 +22,8 @@ func RunesWidth(runes []rune, prefixWidth int, tabstop int, limit int) (int, int
|
|||||||
if len(rs) == 1 && rs[0] == '\t' {
|
if len(rs) == 1 && rs[0] == '\t' {
|
||||||
w = tabstop - (prefixWidth+width)%tabstop
|
w = tabstop - (prefixWidth+width)%tabstop
|
||||||
} else {
|
} else {
|
||||||
w = runewidth.StringWidth(string(rs))
|
s := string(rs)
|
||||||
|
w = runewidth.StringWidth(s) + strings.Count(s, "\n")
|
||||||
}
|
}
|
||||||
width += w
|
width += w
|
||||||
if limit > 0 && width > limit {
|
if limit > 0 && width > limit {
|
||||||
@@ -115,11 +117,16 @@ func DurWithin(
|
|||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsTty returns true is stdin is a terminal
|
// IsTty returns true if stdin is a terminal
|
||||||
func IsTty() bool {
|
func IsTty() bool {
|
||||||
return isatty.IsTerminal(os.Stdin.Fd())
|
return isatty.IsTerminal(os.Stdin.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
|
||||||
func Once(nextResponse bool) func() bool {
|
func Once(nextResponse bool) func() bool {
|
||||||
state := nextResponse
|
state := nextResponse
|
||||||
|
|||||||
@@ -2053,6 +2053,22 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys 'dabcd'
|
tmux.send_keys 'dabcd'
|
||||||
tmux.until { |lines| assert_equal '> abcd', lines[-1] }
|
tmux.until { |lines| assert_equal '> abcd', lines[-1] }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_item_index_reset_on_reload
|
||||||
|
tmux.send_keys "seq 10 | #{FZF} --preview 'echo [[{n}]]' --bind 'up:last,down:first,space:reload:seq 100'", :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '[[0]]' }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '[[9]]' }
|
||||||
|
tmux.send_keys :Down
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '[[0]]' }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 100, lines.item_count
|
||||||
|
assert_includes lines[1], '[[0]]'
|
||||||
|
end
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '[[99]]' }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module TestShell
|
module TestShell
|
||||||
|
|||||||
@@ -104,10 +104,10 @@ if [ -d "${fish_dir}/functions" ]; then
|
|||||||
remove "${fish_dir}/functions/fzf.fish"
|
remove "${fish_dir}/functions/fzf.fish"
|
||||||
remove "${fish_dir}/functions/fzf_key_bindings.fish"
|
remove "${fish_dir}/functions/fzf_key_bindings.fish"
|
||||||
|
|
||||||
if [ "$(ls -A "${fish_dir}/functions")" ]; then
|
if [ -z "$(ls -A "${fish_dir}/functions")" ]; then
|
||||||
echo "Can't delete non-empty directory: \"${fish_dir}/functions\""
|
|
||||||
else
|
|
||||||
rmdir "${fish_dir}/functions"
|
rmdir "${fish_dir}/functions"
|
||||||
|
else
|
||||||
|
echo "Can't delete non-empty directory: \"${fish_dir}/functions\""
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user