mirror of
https://github.com/junegunn/fzf.git
synced 2025-05-19 12:50:22 -07:00
Compare commits
127 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
4e3f9854e6 | ||
|
b27943423e | ||
|
894a1016bc | ||
|
efe6cddd34 | ||
|
f1c6bdf3e8 | ||
|
710659bcf5 | ||
|
be67775da4 | ||
|
2c6381499c | ||
|
4df842e78c | ||
|
b81696fb64 | ||
|
d226d841a1 | ||
|
c6d83047e5 | ||
|
46dabccdf1 | ||
|
cd9517b679 | ||
|
cd6677ba1d | ||
|
9c1a47acf7 | ||
|
0c280a3ce1 | ||
|
53e8b6e705 | ||
|
ad33165fa7 | ||
|
2055db61c8 | ||
|
d2c662e54f | ||
|
d24b58ef3f | ||
|
06ae9b0f3b | ||
|
2a9c1c06a4 | ||
|
90ad1b7f22 | ||
|
f22fbcd1af | ||
|
1d761684c5 | ||
|
e491770f1c | ||
|
a41be61506 | ||
|
1a8f633611 | ||
|
af8fe918d8 | ||
|
8ef9dfd9a2 | ||
|
66df24040f | ||
|
ed4442d9ea | ||
|
0edb5d5ebb | ||
|
9ffc2c7ca3 | ||
|
93cb3758b5 | ||
|
d22e75dcdd | ||
|
a1b2a6fe2c | ||
|
e15cba0c8c | ||
|
31fd207ba2 | ||
|
ba6d1b8772 | ||
|
0dce561ec9 | ||
|
376142eb0d | ||
|
664ee1f483 | ||
|
dac5b6fde1 | ||
|
998c57442b | ||
|
4a0ab6c926 | ||
|
f43e82f17f | ||
|
62238620a5 | ||
|
200745011a | ||
|
82fd88339b | ||
|
de0f2efbfb | ||
|
29cf28d845 | ||
|
7e4dbb5f3b | ||
|
923c3a814d | ||
|
779e3cc5b5 | ||
|
3f3d1ef8f5 | ||
|
f92f9f137a | ||
|
87f7f436e8 | ||
|
4298c0b1eb | ||
|
6c104d771e | ||
|
aefb9a5bc4 | ||
|
8868d7cbb8 | ||
|
10cbac20f9 | ||
|
26bcd0c90d | ||
|
fbece2bb67 | ||
|
0012183ede | ||
|
8916cbc6ab | ||
|
21ce70054f | ||
|
3ba82b6d87 | ||
|
e771c5d057 | ||
|
4e5e925e39 | ||
|
b7248d4115 | ||
|
639253840f | ||
|
710ebdf9c1 | ||
|
bb64d84ce4 | ||
|
cd1da27ff2 | ||
|
c1accc2e5b | ||
|
e4489dcbc1 | ||
|
c0d407f7ce | ||
|
461115afde | ||
|
bae1965231 | ||
|
b89c77ec9a | ||
|
1ca5f09d7b | ||
|
d79902ae59 | ||
|
77568e114f | ||
|
a24d274a3c | ||
|
dac81432d6 | ||
|
309b5081ef | ||
|
91bc4f2671 | ||
|
4c9d37d919 | ||
|
7e9566f66a | ||
|
3f7e8a475d | ||
|
1cf7c0f334 | ||
|
ff8ee9ee4e | ||
|
cbbd939a94 | ||
|
f232df2887 | ||
|
16bfb2c80c | ||
|
0ba066123e | ||
|
81c51c26cc | ||
|
6fa8295ac5 | ||
|
f975b40236 | ||
|
01d9d9c8c8 | ||
|
1eafc4e5d9 | ||
|
38e4020aa8 | ||
|
ac32fbb3b2 | ||
|
7d26eca5cc | ||
|
3347d61591 | ||
|
9abf2c8c9c | ||
|
84e2262ad6 | ||
|
378137d34a | ||
|
66ca16f836 | ||
|
282884ad83 | ||
|
7877ac42f0 | ||
|
19ef8891e3 | ||
|
bfea9e53a6 | ||
|
a2420026ab | ||
|
1be1991299 | ||
|
67dd7e1923 | ||
|
2b584586ed | ||
|
a1994ff0ab | ||
|
ca0e858871 | ||
|
06c6615507 | ||
|
818d0be436 | ||
|
fcd2baa945 | ||
|
62e0a2824a |
2
.github/workflows/linux.yml
vendored
2
.github/workflows/linux.yml
vendored
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
name: Test fzf on Linux
|
name: build
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -11,3 +11,4 @@ gopath
|
|||||||
fzf
|
fzf
|
||||||
tmp
|
tmp
|
||||||
*.patch
|
*.patch
|
||||||
|
.idea
|
||||||
|
121
CHANGELOG.md
121
CHANGELOG.md
@ -1,6 +1,127 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.62.0
|
||||||
|
------
|
||||||
|
- Relaxed the `--color` option syntax to allow whitespace-separated entries (in addition to commas), making multi-line definitions easier to write and read
|
||||||
|
```sh
|
||||||
|
# seoul256-light
|
||||||
|
fzf --style full --color='
|
||||||
|
fg:#616161 fg+:#616161
|
||||||
|
bg:#ffffff bg+:#e9e9e9 alt-bg:#f1f1f1
|
||||||
|
hl:#719872 hl+:#719899
|
||||||
|
pointer:#e12672 marker:#e17899
|
||||||
|
header:#719872
|
||||||
|
spinner:#719899 info:#727100
|
||||||
|
prompt:#0099bd query:#616161
|
||||||
|
border:#e1e1e1
|
||||||
|
'
|
||||||
|
```
|
||||||
|
- Added `alt-bg` color to create striped lines to visually separate rows
|
||||||
|
```sh
|
||||||
|
fzf --color bg:237,alt-bg:238,current-bg:236 --highlight-line
|
||||||
|
|
||||||
|
declare -f | perl -0777 -pe 's/^}\n/}\0/gm' |
|
||||||
|
bat --plain --language bash --color always |
|
||||||
|
fzf --read0 --ansi --reverse --multi \
|
||||||
|
--color bg:237,alt-bg:238,current-bg:236 --highlight-line
|
||||||
|
```
|
||||||
|
- [fish] Improvements in CTRL-R binding (@bitraid)
|
||||||
|
- You can trigger CTRL-R in the middle of a command to insert the selected item
|
||||||
|
- You can delete history items with SHIFT-DEL
|
||||||
|
- Bug fixes and improvements
|
||||||
|
- Fixed unnecessary 100ms delay after `reload` (#4364)
|
||||||
|
- Fixed `selected-bg` not applied to colored items (#4372)
|
||||||
|
|
||||||
|
0.61.3
|
||||||
|
------
|
||||||
|
- Reverted #4351 as it caused `tmux run-shell 'fzf --tmux'` to fail (#4559 #4560)
|
||||||
|
- More environment variables for child processes (#4356)
|
||||||
|
|
||||||
|
0.61.2
|
||||||
|
------
|
||||||
|
- Fixed panic when using header border without pointer/marker (@phanen)
|
||||||
|
- Fixed `--tmux` option when already inside a tmux popup (@peikk0)
|
||||||
|
- Bug fixes and improvements in CTRL-T binding of fish (#4334) (@bitraid)
|
||||||
|
- Added `--no-tty-default` option to make fzf search for the current TTY device instead of defaulting to `/dev/tty` (#4242)
|
||||||
|
|
||||||
|
0.61.1
|
||||||
|
------
|
||||||
|
- Disable bracketed-paste mode on exit. This fixes issue where pasting breaks after running fzf on old bash versions that don't support the mode.
|
||||||
|
|
||||||
|
0.61.0
|
||||||
|
------
|
||||||
|
- Added `--ghost=TEXT` to display a ghost text when the input is empty
|
||||||
|
```sh
|
||||||
|
# Display "Type to search" when the input is empty
|
||||||
|
fzf --ghost "Type to search"
|
||||||
|
```
|
||||||
|
- Added `change-ghost` and `transform-ghost` actions for dynamically changing the ghost text
|
||||||
|
- Added `change-pointer` and `transform-pointer` actions for dynamically changing the pointer sign
|
||||||
|
- Added `r` flag for placeholder expression (raw mode) for unquoted output
|
||||||
|
- Bug fixes and improvements
|
||||||
|
|
||||||
|
0.60.3
|
||||||
|
------
|
||||||
|
- Bug fixes and improvements
|
||||||
|
- [fish] Enable multiple history commands insertion (#4280) (@bitraid)
|
||||||
|
- [walker] Append '/' to directory entries on MSYS2 (#4281)
|
||||||
|
- Trim trailing whitespaces after processing ANSI sequences (#4282)
|
||||||
|
- Remove temp files before `become` when using `--tmux` option (#4283)
|
||||||
|
- Fix condition for using item numlines cache (#4285) (@alex-huff)
|
||||||
|
- Make `--accept-nth` compatible with `--select-1` (#4287)
|
||||||
|
- Increase the query length limit from 300 to 1000 (#4292)
|
||||||
|
- [windows] Prevent fzf from consuming user input while paused (#4260)
|
||||||
|
|
||||||
|
0.60.2
|
||||||
|
------
|
||||||
|
- Template for `--with-nth` and `--accept-nth` now supports `{n}` which evaluates to the zero-based ordinal index of the item
|
||||||
|
- Fixed a regression that caused the last field in the "nth" expression to be trimmed when a regular expression delimiter is used
|
||||||
|
- Thanks to @phanen for the fix
|
||||||
|
- Fixed 'jump' action when the pointer is an empty string
|
||||||
|
|
||||||
|
0.60.1
|
||||||
|
------
|
||||||
|
- Bug fixes and minor improvements
|
||||||
|
- Built-in walker now prints directory entries with a trailing slash
|
||||||
|
- Fixed a bug causing unexpected behavior with [fzf-tab](https://github.com/Aloxaf/fzf-tab). Please upgrade if you use it.
|
||||||
|
- Thanks to @alexeisersun, @bitraid, @Lompik, and @fsc0 for the contributions
|
||||||
|
|
||||||
|
0.60.0
|
||||||
|
------
|
||||||
|
_Release highlights: https://junegunn.github.io/fzf/releases/0.60.0/_
|
||||||
|
|
||||||
|
- Added `--accept-nth` for choosing output fields
|
||||||
|
```sh
|
||||||
|
ps -ef | fzf --multi --header-lines 1 | awk '{print $2}'
|
||||||
|
# Becomes
|
||||||
|
ps -ef | fzf --multi --header-lines 1 --accept-nth 2
|
||||||
|
|
||||||
|
git branch | fzf | cut -c3-
|
||||||
|
# Can be rewritten as
|
||||||
|
git branch | fzf --accept-nth -1
|
||||||
|
```
|
||||||
|
- `--accept-nth` and `--with-nth` now support a template that includes multiple field index expressions in curly braces
|
||||||
|
```sh
|
||||||
|
echo foo,bar,baz | fzf --delimiter , --accept-nth '{1}, {3}, {2}'
|
||||||
|
# foo, baz, bar
|
||||||
|
|
||||||
|
echo foo,bar,baz | fzf --delimiter , --with-nth '{1},{3},{2},{1..2}'
|
||||||
|
# foo,baz,bar,foo,bar
|
||||||
|
```
|
||||||
|
- Added `exclude` and `exclude-multi` actions for dynamically excluding items
|
||||||
|
```sh
|
||||||
|
seq 100 | fzf --bind 'ctrl-x:exclude'
|
||||||
|
|
||||||
|
# 'exclude-multi' will exclude the selected items or the current item
|
||||||
|
seq 100 | fzf --multi --bind 'ctrl-x:exclude-multi'
|
||||||
|
```
|
||||||
|
- Preview window now prints wrap indicator when wrapping is enabled
|
||||||
|
```sh
|
||||||
|
seq 100 | xargs | fzf --wrap --preview 'echo {}' --preview-window wrap
|
||||||
|
```
|
||||||
|
- Bug fixes and improvements
|
||||||
|
|
||||||
0.59.0
|
0.59.0
|
||||||
------
|
------
|
||||||
_Release highlights: https://junegunn.github.io/fzf/releases/0.59.0/_
|
_Release highlights: https://junegunn.github.io/fzf/releases/0.59.0/_
|
||||||
|
@ -155,6 +155,7 @@ let g:fzf_layout = { 'window': '10new' }
|
|||||||
let g:fzf_colors =
|
let g:fzf_colors =
|
||||||
\ { 'fg': ['fg', 'Normal'],
|
\ { 'fg': ['fg', 'Normal'],
|
||||||
\ 'bg': ['bg', 'Normal'],
|
\ 'bg': ['bg', 'Normal'],
|
||||||
|
\ 'query': ['fg', 'Normal'],
|
||||||
\ 'hl': ['fg', 'Comment'],
|
\ 'hl': ['fg', 'Comment'],
|
||||||
\ 'fg+': ['fg', 'CursorLine', 'CursorColumn', 'Normal'],
|
\ 'fg+': ['fg', 'CursorLine', 'CursorColumn', 'Normal'],
|
||||||
\ 'bg+': ['bg', 'CursorLine', 'CursorColumn'],
|
\ 'bg+': ['bg', 'CursorLine', 'CursorColumn'],
|
||||||
|
33
SECURITY.md
Normal file
33
SECURITY.md
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Security Reporting
|
||||||
|
|
||||||
|
If you wish to report a security vulnerability privately, we appreciate your diligence. Please follow the guidelines below to submit your report.
|
||||||
|
|
||||||
|
## Reporting
|
||||||
|
|
||||||
|
To report a security vulnerability, please provide the following information:
|
||||||
|
|
||||||
|
1. **PROJECT**
|
||||||
|
- https://github.com/junegunn/fzf
|
||||||
|
|
||||||
|
2. **PUBLIC**
|
||||||
|
- Indicate whether this vulnerability has already been publicly discussed or disclosed.
|
||||||
|
- If so, provide relevant links.
|
||||||
|
|
||||||
|
3. **DESCRIPTION**
|
||||||
|
- Provide a detailed description of the security vulnerability.
|
||||||
|
- Include as much information as possible to help us understand and address the issue.
|
||||||
|
|
||||||
|
Send this information, along with any additional relevant details, to <junegunn.c AT gmail DOT com>.
|
||||||
|
|
||||||
|
## Confidentiality
|
||||||
|
|
||||||
|
We kindly ask you to keep the report confidential until a public announcement is made.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Vulnerabilities will be handled on a best-effort basis.
|
||||||
|
- You may request an advance copy of the patched release, but we cannot guarantee early access before the public release.
|
||||||
|
- You will be notified via email simultaneously with the public announcement.
|
||||||
|
- We will respond within a few weeks to confirm whether your report has been accepted or rejected.
|
||||||
|
|
||||||
|
Thank you for helping to improve the security of our project!
|
@ -57,15 +57,15 @@ elif ! [[ $KITTY_WINDOW_ID ]] && (( FZF_PREVIEW_TOP + FZF_PREVIEW_LINES == $(stt
|
|||||||
dim=${FZF_PREVIEW_COLUMNS}x$((FZF_PREVIEW_LINES - 1))
|
dim=${FZF_PREVIEW_COLUMNS}x$((FZF_PREVIEW_LINES - 1))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 1. Use kitty icat on kitty terminal
|
# 1. Use icat (from Kitty) if kitten is installed
|
||||||
if [[ $KITTY_WINDOW_ID ]]; then
|
if [[ $KITTY_WINDOW_ID ]] || [[ $GHOSTTY_RESOURCES_DIR ]] && command -v kitten > /dev/null; then
|
||||||
# 1. 'memory' is the fastest option but if you want the image to be scrollable,
|
# 1. 'memory' is the fastest option but if you want the image to be scrollable,
|
||||||
# you have to use 'stream'.
|
# you have to use 'stream'.
|
||||||
#
|
#
|
||||||
# 2. The last line of the output is the ANSI reset code without newline.
|
# 2. The last line of the output is the ANSI reset code without newline.
|
||||||
# This confuses fzf and makes it render scroll offset indicator.
|
# This confuses fzf and makes it render scroll offset indicator.
|
||||||
# So we remove the last line and append the reset code to its previous line.
|
# So we remove the last line and append the reset code to its previous line.
|
||||||
kitty icat --clear --transfer-mode=memory --unicode-placeholder --stdin=no --place="$dim@0x0" "$file" | sed '$d' | sed $'$s/$/\e[m/'
|
kitten icat --clear --transfer-mode=memory --unicode-placeholder --stdin=no --place="$dim@0x0" "$file" | sed '$d' | sed $'$s/$/\e[m/'
|
||||||
|
|
||||||
# 2. Use chafa with Sixel output
|
# 2. Use chafa with Sixel output
|
||||||
elif command -v chafa > /dev/null; then
|
elif command -v chafa > /dev/null; then
|
||||||
|
6
go.mod
6
go.mod
@ -1,13 +1,13 @@
|
|||||||
module github.com/junegunn/fzf
|
module github.com/junegunn/fzf
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/charlievieth/fastwalk v1.0.9
|
github.com/charlievieth/fastwalk v1.0.10
|
||||||
github.com/gdamore/tcell/v2 v2.8.1
|
github.com/gdamore/tcell/v2 v2.8.1
|
||||||
github.com/junegunn/go-shellwords v0.0.0-20250127100254-2aa3b3277741
|
github.com/junegunn/go-shellwords v0.0.0-20250127100254-2aa3b3277741
|
||||||
github.com/mattn/go-isatty v0.0.20
|
github.com/mattn/go-isatty v0.0.20
|
||||||
github.com/rivo/uniseg v0.4.7
|
github.com/rivo/uniseg v0.4.7
|
||||||
golang.org/x/sys v0.29.0
|
golang.org/x/sys v0.30.0
|
||||||
golang.org/x/term v0.28.0
|
golang.org/x/term v0.29.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
10
go.sum
10
go.sum
@ -1,5 +1,5 @@
|
|||||||
github.com/charlievieth/fastwalk v1.0.9 h1:Odb92AfoReO3oFBfDGT5J+nwgzQPF/gWAw6E6/lkor0=
|
github.com/charlievieth/fastwalk v1.0.10 h1:0qUbvA2O+K+X+IrTfZTC0UH2DK5MOA+KjVfStAHUnGg=
|
||||||
github.com/charlievieth/fastwalk v1.0.9/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI=
|
github.com/charlievieth/fastwalk v1.0.10/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI=
|
||||||
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
|
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
|
||||||
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
|
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
|
||||||
github.com/gdamore/tcell/v2 v2.8.1 h1:KPNxyqclpWpWQlPLx6Xui1pMk8S+7+R37h3g07997NU=
|
github.com/gdamore/tcell/v2 v2.8.1 h1:KPNxyqclpWpWQlPLx6Xui1pMk8S+7+R37h3g07997NU=
|
||||||
@ -54,8 +54,9 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
|
||||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
@ -64,8 +65,9 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
|||||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
|
||||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||||
|
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||||
|
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
2
install
2
install
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.59.0
|
version=0.62.0
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
$version="0.59.0"
|
$version="0.62.0"
|
||||||
|
|
||||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
|
||||||
|
2
main.go
2
main.go
@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/protector"
|
"github.com/junegunn/fzf/src/protector"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "0.59"
|
var version = "0.62"
|
||||||
var revision = "devel"
|
var revision = "devel"
|
||||||
|
|
||||||
//go:embed shell/key-bindings.bash
|
//go:embed shell/key-bindings.bash
|
||||||
|
@ -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 "Feb 2025" "fzf 0.59.0" "fzf\-tmux - open fzf in tmux split pane"
|
.TH fzf\-tmux 1 "May 2025" "fzf 0.62.0" "fzf\-tmux - open fzf in tmux split pane"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf\-tmux - open fzf in tmux split pane
|
fzf\-tmux - open fzf in tmux split pane
|
||||||
|
@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf 1 "Feb 2025" "fzf 0.59.0" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "May 2025" "fzf 0.62.0" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@ -56,7 +56,9 @@ Case-insensitive match (default: smart-case match)
|
|||||||
Case-sensitive match
|
Case-sensitive match
|
||||||
.TP
|
.TP
|
||||||
.B "\-\-smart\-case"
|
.B "\-\-smart\-case"
|
||||||
Smart-case match (default)
|
Smart-case match (default). In this mode, the search is case-insensitive by
|
||||||
|
default, but it becomes case-sensitive if the query contains any uppercase
|
||||||
|
letters.
|
||||||
.TP
|
.TP
|
||||||
.B "\-\-literal"
|
.B "\-\-literal"
|
||||||
Do not normalize latin script letters for matching.
|
Do not normalize latin script letters for matching.
|
||||||
@ -117,8 +119,38 @@ transformed lines (unlike in \fB\-\-preview\fR where fields are extracted from
|
|||||||
the original lines) because fzf doesn't allow searching against the hidden
|
the original lines) because fzf doesn't allow searching against the hidden
|
||||||
fields.
|
fields.
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-with\-nth=" "N[,..]"
|
.BI "\-\-with\-nth=" "N[,..] or TEMPLATE"
|
||||||
Transform the presentation of each line using field index expressions
|
Transform the presentation of each line using the field index expressions.
|
||||||
|
For advanced transformation, you can provide a template containing field index
|
||||||
|
expressions in curly braces. When you use a template, the trailing delimiter is
|
||||||
|
stripped from each expression, giving you more control over the output.
|
||||||
|
\fB{n}\fR in template evaluates to the zero-based ordinal index of the line.
|
||||||
|
|
||||||
|
.RS
|
||||||
|
e.g.
|
||||||
|
# Single expression: drop the first field
|
||||||
|
echo foo bar baz | fzf --with-nth 2..
|
||||||
|
|
||||||
|
# Use template to rearrange fields
|
||||||
|
echo foo,bar,baz | fzf --delimiter , --with-nth '{n},{1},{3},{2},{1..2}'
|
||||||
|
.RE
|
||||||
|
.TP
|
||||||
|
.BI "\-\-accept\-nth=" "N[,..] or TEMPLATE"
|
||||||
|
Define which fields to print on accept. The last delimiter is stripped from the
|
||||||
|
output. For advanced transformation, you can provide a template containing
|
||||||
|
field index expressions in curly braces. When you use a template, the trailing
|
||||||
|
delimiter is stripped from each expression, giving you more control over the
|
||||||
|
output. \fB{n}\fR in template evaluates to the zero-based ordinal index of the
|
||||||
|
line.
|
||||||
|
|
||||||
|
.RS
|
||||||
|
e.g.
|
||||||
|
# Single expression
|
||||||
|
echo foo bar baz | fzf --accept-nth 2
|
||||||
|
|
||||||
|
# Template
|
||||||
|
echo foo bar baz | fzf --accept-nth 'Index: {n}, 1st: {1}, 2nd: {2}, 3rd: {3}'
|
||||||
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.B "+s, \-\-no\-sort"
|
.B "+s, \-\-no\-sort"
|
||||||
Do not sort the result
|
Do not sort the result
|
||||||
@ -196,6 +228,13 @@ e.g. \fB# Avoid rendering both fzf instances at the same time
|
|||||||
(sleep 1; seq 1000000; sleep 1) |
|
(sleep 1; seq 1000000; sleep 1) |
|
||||||
fzf \-\-sync \-\-query 5 \-\-listen \-\-bind start:up,load:up,result:up,focus:change\-header:Ready\fR
|
fzf \-\-sync \-\-query 5 \-\-listen \-\-bind start:up,load:up,result:up,focus:change\-header:Ready\fR
|
||||||
.RE
|
.RE
|
||||||
|
.TP
|
||||||
|
.B "\-\-no\-tty\-default"
|
||||||
|
Make fzf search for the current TTY device via standard error instead of
|
||||||
|
defaulting to \fB/dev/tty\fR. This option avoids issues when launching
|
||||||
|
emacsclient from within fzf. Alternatively, you can change the default TTY
|
||||||
|
device by setting \fB--tty-default=DEVICE_NAME\fR.
|
||||||
|
|
||||||
.SS GLOBAL STYLE
|
.SS GLOBAL STYLE
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-style=" "PRESET"
|
.BI "\-\-style=" "PRESET"
|
||||||
@ -203,7 +242,7 @@ Apply a style preset [default|minimal|full[:BORDER_STYLE]]
|
|||||||
.TP
|
.TP
|
||||||
.BI "\-\-color=" "[BASE_SCHEME][,COLOR_NAME[:ANSI_COLOR][:ANSI_ATTRIBUTES]]..."
|
.BI "\-\-color=" "[BASE_SCHEME][,COLOR_NAME[:ANSI_COLOR][:ANSI_ATTRIBUTES]]..."
|
||||||
Color configuration. The name of the base color scheme is followed by custom
|
Color configuration. The name of the base color scheme is followed by custom
|
||||||
color mappings.
|
color mappings. Each entry is separated by a comma and/or whitespaces.
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
.B BASE SCHEME:
|
.B BASE SCHEME:
|
||||||
@ -231,6 +270,7 @@ color mappings.
|
|||||||
\fBcurrent\-bg (bg+) \fRBackground (current line)
|
\fBcurrent\-bg (bg+) \fRBackground (current line)
|
||||||
\fBgutter \fRGutter on the left
|
\fBgutter \fRGutter on the left
|
||||||
\fBcurrent\-hl (hl+) \fRHighlighted substrings (current line)
|
\fBcurrent\-hl (hl+) \fRHighlighted substrings (current line)
|
||||||
|
\fBalt\-bg \fRAlternate background color to create striped lines
|
||||||
\fBquery (input\-fg) \fRQuery string
|
\fBquery (input\-fg) \fRQuery string
|
||||||
\fBdisabled \fRQuery string when search is disabled (\fB\-\-disabled\fR)
|
\fBdisabled \fRQuery string when search is disabled (\fB\-\-disabled\fR)
|
||||||
\fBinfo \fRInfo line (match counters)
|
\fBinfo \fRInfo line (match counters)
|
||||||
@ -298,7 +338,19 @@ color mappings.
|
|||||||
# Seoul256 theme with 24-bit colors
|
# Seoul256 theme with 24-bit colors
|
||||||
fzf \-\-color='bg:#4B4B4B,bg+:#3F3F3F,info:#BDBB72,border:#6B6B6B,spinner:#98BC99' \\
|
fzf \-\-color='bg:#4B4B4B,bg+:#3F3F3F,info:#BDBB72,border:#6B6B6B,spinner:#98BC99' \\
|
||||||
\-\-color='hl:#719872,fg:#D9D9D9,header:#719872,fg+:#D9D9D9' \\
|
\-\-color='hl:#719872,fg:#D9D9D9,header:#719872,fg+:#D9D9D9' \\
|
||||||
\-\-color='pointer:#E12672,marker:#E17899,prompt:#98BEDE,hl+:#98BC99'\fR
|
\-\-color='pointer:#E12672,marker:#E17899,prompt:#98BEDE,hl+:#98BC99'
|
||||||
|
|
||||||
|
# Seoul256 light theme with 24-bit colors, each entry separated by whitespaces
|
||||||
|
fzf \-\-style full \-\-color='
|
||||||
|
fg:#616161 fg+:#616161
|
||||||
|
bg:#ffffff bg+:#e9e9e9 alt-bg:#f1f1f1
|
||||||
|
hl:#719872 hl+:#719899
|
||||||
|
pointer:#e12672 marker:#e17899
|
||||||
|
header:#719872
|
||||||
|
spinner:#719899 info:#727100
|
||||||
|
prompt:#0099bd query:#616161
|
||||||
|
border:#e1e1e1
|
||||||
|
'\fR
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.B "\-\-no\-color"
|
.B "\-\-no\-color"
|
||||||
@ -677,6 +729,10 @@ ANSI color codes are supported.
|
|||||||
Do not display horizontal separator on the info line. A synonym for
|
Do not display horizontal separator on the info line. A synonym for
|
||||||
\fB\-\-separator=''\fB
|
\fB\-\-separator=''\fB
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-\-ghost=" "TEXT"
|
||||||
|
Ghost text to display when the input is empty
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B "\-\-filepath\-word"
|
.B "\-\-filepath\-word"
|
||||||
Make word-wise movements and actions respect path separators. The following
|
Make word-wise movements and actions respect path separators. The following
|
||||||
@ -732,6 +788,12 @@ e.g.
|
|||||||
\fBfzf \-\-multi \-\-preview='head \-10 {+}'
|
\fBfzf \-\-multi \-\-preview='head \-10 {+}'
|
||||||
git log \-\-oneline | fzf \-\-multi \-\-preview 'git show {+1}'\fR
|
git log \-\-oneline | fzf \-\-multi \-\-preview 'git show {+1}'\fR
|
||||||
|
|
||||||
|
Each expression expands to a quoted string, so that it's safe to pass it as an
|
||||||
|
argument to an external command. So you should not manually add quotes around
|
||||||
|
the curly braces. But if you don't want this behavior, you can put
|
||||||
|
\fBr\fR flag (raw) in the expression (e.g. \fB{r}\fR, \fB{r1}\fR, etc).
|
||||||
|
Use it with caution as unquoted output can lead to broken commands.
|
||||||
|
|
||||||
When using a field index expression, leading and trailing whitespace is stripped
|
When using a field index expression, leading and trailing whitespace is stripped
|
||||||
from the replacement string. To preserve the whitespace, use the \fBs\fR flag.
|
from the replacement string. To preserve the whitespace, use the \fBs\fR flag.
|
||||||
|
|
||||||
@ -1230,10 +1292,20 @@ fzf exports the following environment variables to its child processes.
|
|||||||
.br
|
.br
|
||||||
.BR FZF_PROMPT " Prompt string"
|
.BR FZF_PROMPT " Prompt string"
|
||||||
.br
|
.br
|
||||||
|
.BR FZF_GHOST " Ghost string"
|
||||||
|
.br
|
||||||
|
.BR FZF_POINTER " Pointer string"
|
||||||
|
.br
|
||||||
.BR FZF_PREVIEW_LABEL " Preview label string"
|
.BR FZF_PREVIEW_LABEL " Preview label string"
|
||||||
.br
|
.br
|
||||||
.BR FZF_BORDER_LABEL " Border label string"
|
.BR FZF_BORDER_LABEL " Border label string"
|
||||||
.br
|
.br
|
||||||
|
.BR FZF_LIST_LABEL " List label string"
|
||||||
|
.br
|
||||||
|
.BR FZF_INPUT_LABEL " Input label string"
|
||||||
|
.br
|
||||||
|
.BR FZF_HEADER_LABEL " Header label string"
|
||||||
|
.br
|
||||||
.BR FZF_ACTION " The name of the last action performed"
|
.BR FZF_ACTION " The name of the last action performed"
|
||||||
.br
|
.br
|
||||||
.BR FZF_KEY " The name of the last key pressed"
|
.BR FZF_KEY " The name of the last key pressed"
|
||||||
@ -1573,6 +1645,7 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBbell\fR (ring the terminal bell)
|
\fBbell\fR (ring the terminal bell)
|
||||||
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
||||||
\fBchange\-border\-label(...)\fR (change \fB\-\-border\-label\fR to the given string)
|
\fBchange\-border\-label(...)\fR (change \fB\-\-border\-label\fR to the given string)
|
||||||
|
\fBchange\-ghost(...)\fR (change ghost text to the given string)
|
||||||
\fBchange\-header(...)\fR (change header to the given string; doesn't affect \fB\-\-header\-lines\fR)
|
\fBchange\-header(...)\fR (change header to the given string; doesn't affect \fB\-\-header\-lines\fR)
|
||||||
\fBchange\-header\-label(...)\fR (change \fB\-\-header\-label\fR to the given string)
|
\fBchange\-header\-label(...)\fR (change \fB\-\-header\-label\fR to the given string)
|
||||||
\fBchange\-input\-label(...)\fR (change \fB\-\-input\-label\fR to the given string)
|
\fBchange\-input\-label(...)\fR (change \fB\-\-input\-label\fR to the given string)
|
||||||
@ -1580,6 +1653,7 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBchange\-multi\fR (enable multi-select mode with no limit)
|
\fBchange\-multi\fR (enable multi-select mode with no limit)
|
||||||
\fBchange\-multi(...)\fR (enable multi-select mode with a limit or disable it with 0)
|
\fBchange\-multi(...)\fR (enable multi-select mode with a limit or disable it with 0)
|
||||||
\fBchange\-nth(...)\fR (change \fB\-\-nth\fR option; rotate through the multiple options separated by '|')
|
\fBchange\-nth(...)\fR (change \fB\-\-nth\fR option; rotate through the multiple options separated by '|')
|
||||||
|
\fBchange\-pointer(...)\fR (change \fB\-\-pointer\fR option)
|
||||||
\fBchange\-preview(...)\fR (change \fB\-\-preview\fR option)
|
\fBchange\-preview(...)\fR (change \fB\-\-preview\fR option)
|
||||||
\fBchange\-preview\-label(...)\fR (change \fB\-\-preview\-label\fR to the given string)
|
\fBchange\-preview\-label(...)\fR (change \fB\-\-preview\-label\fR to the given string)
|
||||||
\fBchange\-preview\-window(...)\fR (change \fB\-\-preview\-window\fR option; rotate through the multiple option sets separated by '|')
|
\fBchange\-preview\-window(...)\fR (change \fB\-\-preview\-window\fR option; rotate through the multiple option sets separated by '|')
|
||||||
@ -1597,6 +1671,8 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBdown\fR \fIctrl\-j ctrl\-n down\fR
|
\fBdown\fR \fIctrl\-j ctrl\-n down\fR
|
||||||
\fBenable\-search\fR (enable search functionality)
|
\fBenable\-search\fR (enable search functionality)
|
||||||
\fBend\-of\-line\fR \fIctrl\-e end\fR
|
\fBend\-of\-line\fR \fIctrl\-e end\fR
|
||||||
|
\fBexclude\fR (exclude the current item from the result)
|
||||||
|
\fBexclude\-multi\fR (exclude the selected items or the current item from the result)
|
||||||
\fBexecute(...)\fR (see below for the details)
|
\fBexecute(...)\fR (see below for the details)
|
||||||
\fBexecute\-silent(...)\fR (see below for the details)
|
\fBexecute\-silent(...)\fR (see below for the details)
|
||||||
\fBfirst\fR (move to the first match; same as \fBpos(1)\fR)
|
\fBfirst\fR (move to the first match; same as \fBpos(1)\fR)
|
||||||
@ -1666,11 +1742,13 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBtrack\-current\fR (track the current item; automatically disabled if focus changes)
|
\fBtrack\-current\fR (track the current item; automatically disabled if focus changes)
|
||||||
\fBtransform(...)\fR (transform states using the output of an external command)
|
\fBtransform(...)\fR (transform states using the output of an external command)
|
||||||
\fBtransform\-border\-label(...)\fR (transform border label using an external command)
|
\fBtransform\-border\-label(...)\fR (transform border label using an external command)
|
||||||
|
\fBtransform\-ghost(...)\fR (transform ghost text using an external command)
|
||||||
\fBtransform\-header(...)\fR (transform header using an external command)
|
\fBtransform\-header(...)\fR (transform header using an external command)
|
||||||
\fBtransform\-header\-label(...)\fR (transform header label using an external command)
|
\fBtransform\-header\-label(...)\fR (transform header label using an external command)
|
||||||
\fBtransform\-input\-label(...)\fR (transform input label using an external command)
|
\fBtransform\-input\-label(...)\fR (transform input label using an external command)
|
||||||
\fBtransform\-list\-label(...)\fR (transform list label using an external command)
|
\fBtransform\-list\-label(...)\fR (transform list label using an external command)
|
||||||
\fBtransform\-nth(...)\fR (transform nth using an external command)
|
\fBtransform\-nth(...)\fR (transform nth using an external command)
|
||||||
|
\fBtransform\-pointer(...)\fR (transform pointer using an external command)
|
||||||
\fBtransform\-preview\-label(...)\fR (transform preview label using an external command)
|
\fBtransform\-preview\-label(...)\fR (transform preview label using an external command)
|
||||||
\fBtransform\-prompt(...)\fR (transform prompt string using an external command)
|
\fBtransform\-prompt(...)\fR (transform prompt string using an external command)
|
||||||
\fBtransform\-query(...)\fR (transform query string using an external command)
|
\fBtransform\-query(...)\fR (transform query string using an external command)
|
||||||
@ -1690,6 +1768,9 @@ e.g.
|
|||||||
\fBfzf \-\-multi \-\-bind 'ctrl\-a:select\-all+accept'\fR
|
\fBfzf \-\-multi \-\-bind 'ctrl\-a:select\-all+accept'\fR
|
||||||
\fBfzf \-\-multi \-\-bind 'ctrl\-a:select\-all' \-\-bind 'ctrl\-a:+accept'\fR
|
\fBfzf \-\-multi \-\-bind 'ctrl\-a:select\-all' \-\-bind 'ctrl\-a:+accept'\fR
|
||||||
|
|
||||||
|
Any action after a terminal action that exits fzf, such as \fBaccept\fR or
|
||||||
|
\fBabort\fR, is ignored.
|
||||||
|
|
||||||
.SS ACTION ARGUMENT
|
.SS ACTION ARGUMENT
|
||||||
|
|
||||||
An action denoted with \fB(...)\fR suffix takes an argument.
|
An action denoted with \fB(...)\fR suffix takes an argument.
|
||||||
|
@ -358,7 +358,7 @@ endfunction
|
|||||||
|
|
||||||
function! s:get_color(attr, ...)
|
function! s:get_color(attr, ...)
|
||||||
" Force 24 bit colors: g:fzf_force_termguicolors (temporary workaround for https://github.com/junegunn/fzf.vim/issues/1152)
|
" Force 24 bit colors: g:fzf_force_termguicolors (temporary workaround for https://github.com/junegunn/fzf.vim/issues/1152)
|
||||||
let gui = get(g:, 'fzf_force_termguicolors', 0) || (!s:is_win && !has('win32unix') && has('termguicolors') && &termguicolors)
|
let gui = get(g:, 'fzf_force_termguicolors', 0) || (!s:is_win && !has('win32unix') && (has('gui_running') || has('termguicolors') && &termguicolors))
|
||||||
let fam = gui ? 'gui' : 'cterm'
|
let fam = gui ? 'gui' : 'cterm'
|
||||||
let pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$'
|
let pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$'
|
||||||
for group in a:000
|
for group in a:000
|
||||||
@ -553,8 +553,15 @@ try
|
|||||||
let height = s:calc_size(&lines, dict.down, dict)
|
let height = s:calc_size(&lines, dict.down, dict)
|
||||||
let optstr .= ' --no-tmux --height='.height
|
let optstr .= ' --no-tmux --height='.height
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if exists('&winborder') && &winborder !=# '' && &winborder !=# 'none'
|
||||||
|
" Add 1-column horizontal margin
|
||||||
|
let optstr = join(['--margin 0,1', optstr])
|
||||||
|
else
|
||||||
" Respect --border option given in $FZF_DEFAULT_OPTS and 'options'
|
" Respect --border option given in $FZF_DEFAULT_OPTS and 'options'
|
||||||
let optstr = join([s:border_opt(get(dict, 'window', 0)), s:extract_option($FZF_DEFAULT_OPTS, 'border'), optstr])
|
let optstr = join([s:border_opt(get(dict, 'window', 0)), s:extract_option($FZF_DEFAULT_OPTS, 'border'), optstr])
|
||||||
|
endif
|
||||||
|
|
||||||
let command = prefix.(use_tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
|
let command = prefix.(use_tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
|
||||||
|
|
||||||
if use_term
|
if use_term
|
||||||
|
@ -31,9 +31,6 @@ if [[ $- =~ i ]]; then
|
|||||||
|
|
||||||
###########################################################
|
###########################################################
|
||||||
|
|
||||||
# To redraw line after fzf closes (printf '\e[5n')
|
|
||||||
bind '"\e[0n": redraw-current-line' 2> /dev/null
|
|
||||||
|
|
||||||
__fzf_defaults() {
|
__fzf_defaults() {
|
||||||
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
@ -311,12 +308,12 @@ __fzf_generic_path_completion() {
|
|||||||
else
|
else
|
||||||
if [[ $1 =~ dir ]]; then
|
if [[ $1 =~ dir ]]; then
|
||||||
walker=dir,follow
|
walker=dir,follow
|
||||||
rest=${FZF_COMPLETION_DIR_OPTS-}
|
eval "rest=(${FZF_COMPLETION_DIR_OPTS-})"
|
||||||
else
|
else
|
||||||
walker=file,dir,follow,hidden
|
walker=file,dir,follow,hidden
|
||||||
rest=${FZF_COMPLETION_PATH_OPTS-}
|
eval "rest=(${FZF_COMPLETION_PATH_OPTS-})"
|
||||||
fi
|
fi
|
||||||
__fzf_comprun "$4" -q "$leftover" --walker "$walker" --walker-root="$dir" $rest
|
__fzf_comprun "$4" -q "$leftover" --walker "$walker" --walker-root="$dir" "${rest[@]}"
|
||||||
fi | while read -r item; do
|
fi | while read -r item; do
|
||||||
printf "%q " "${item%$3}$3"
|
printf "%q " "${item%$3}$3"
|
||||||
done
|
done
|
||||||
@ -328,6 +325,8 @@ __fzf_generic_path_completion() {
|
|||||||
else
|
else
|
||||||
COMPREPLY=( "$cur" )
|
COMPREPLY=( "$cur" )
|
||||||
fi
|
fi
|
||||||
|
# To redraw line after fzf closes (printf '\e[5n')
|
||||||
|
bind '"\e[0n": redraw-current-line' 2> /dev/null
|
||||||
printf '\e[5n'
|
printf '\e[5n'
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
@ -384,6 +383,7 @@ _fzf_complete() {
|
|||||||
else
|
else
|
||||||
COMPREPLY=("$cur")
|
COMPREPLY=("$cur")
|
||||||
fi
|
fi
|
||||||
|
bind '"\e[0n": redraw-current-line' 2> /dev/null
|
||||||
printf '\e[5n'
|
printf '\e[5n'
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
|
@ -99,9 +99,9 @@ if [[ -o interactive ]]; then
|
|||||||
__fzf_defaults() {
|
__fzf_defaults() {
|
||||||
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
echo "--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1"
|
echo -E "--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1"
|
||||||
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
||||||
echo "${FZF_DEFAULT_OPTS-} $2"
|
echo -E "${FZF_DEFAULT_OPTS-} $2"
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzf_comprun() {
|
__fzf_comprun() {
|
||||||
|
@ -14,15 +14,120 @@
|
|||||||
|
|
||||||
# Key bindings
|
# Key bindings
|
||||||
# ------------
|
# ------------
|
||||||
|
# The oldest supported fish version is 3.1b1. To maintain compatibility, the
|
||||||
|
# command substitution syntax $(cmd) should never be used, even behind a version
|
||||||
|
# check, otherwise the source command will fail on fish versions older than 3.4.0.
|
||||||
function fzf_key_bindings
|
function fzf_key_bindings
|
||||||
|
|
||||||
|
# Check fish version
|
||||||
|
set -l fish_ver (string match -r '^(\d+).(\d+)' $version 2> /dev/null; or echo 0\n0\n0)
|
||||||
|
if test \( "$fish_ver[2]" -lt 3 \) -o \( "$fish_ver[2]" -eq 3 -a "$fish_ver[3]" -lt 1 \)
|
||||||
|
echo "This script requires fish version 3.1b1 or newer." >&2
|
||||||
|
return 1
|
||||||
|
else if not type -q fzf
|
||||||
|
echo "fzf was not found in path." >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
function __fzf_defaults
|
function __fzf_defaults
|
||||||
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
# $argv[1]: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
# $argv[2..]: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
test -n "$FZF_TMUX_HEIGHT"; or set -l FZF_TMUX_HEIGHT 40%
|
||||||
echo "--height $FZF_TMUX_HEIGHT --min-height 20+ --bind=ctrl-z:ignore" $argv[1]
|
string join ' ' -- \
|
||||||
test -r "$FZF_DEFAULT_OPTS_FILE"; and string collect -N -- <$FZF_DEFAULT_OPTS_FILE
|
"--height $FZF_TMUX_HEIGHT --min-height=20+ --bind=ctrl-z:ignore" $argv[1] \
|
||||||
echo $FZF_DEFAULT_OPTS $argv[2]
|
(test -r "$FZF_DEFAULT_OPTS_FILE"; and string join -- ' ' <$FZF_DEFAULT_OPTS_FILE) \
|
||||||
|
$FZF_DEFAULT_OPTS $argv[2..-1]
|
||||||
|
end
|
||||||
|
|
||||||
|
function __fzfcmd
|
||||||
|
test -n "$FZF_TMUX_HEIGHT"; or set -l FZF_TMUX_HEIGHT 40%
|
||||||
|
if test -n "$FZF_TMUX_OPTS"
|
||||||
|
echo "fzf-tmux $FZF_TMUX_OPTS -- "
|
||||||
|
else if test "$FZF_TMUX" = "1"
|
||||||
|
echo "fzf-tmux -d$FZF_TMUX_HEIGHT -- "
|
||||||
|
else
|
||||||
|
echo "fzf"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix'
|
||||||
|
set -l fzf_query ''
|
||||||
|
set -l prefix ''
|
||||||
|
set -l dir '.'
|
||||||
|
|
||||||
|
# Set variables containing the major and minor fish version numbers, using
|
||||||
|
# a method compatible with all supported fish versions.
|
||||||
|
set -l -- fish_major (string match -r -- '^\d+' $version)
|
||||||
|
set -l -- fish_minor (string match -r -- '^\d+\.(\d+)' $version)[2]
|
||||||
|
|
||||||
|
# fish v3.3.0 and newer: Don't use option prefix if " -- " is preceded.
|
||||||
|
set -l -- match_regex '(?<fzf_query>[\s\S]*?(?=\n?$)$)'
|
||||||
|
set -l -- prefix_regex '^-[^\s=]+=|^-(?!-)\S'
|
||||||
|
if test "$fish_major" -eq 3 -a "$fish_minor" -lt 3
|
||||||
|
or string match -q -v -- '* -- *' (string sub -l (commandline -Cp) -- (commandline -p))
|
||||||
|
set -- match_regex "(?<prefix>$prefix_regex)?$match_regex"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Set $prefix and expanded $fzf_query with preserved trailing newlines.
|
||||||
|
if test "$fish_major" -ge 4
|
||||||
|
# fish v4.0.0 and newer
|
||||||
|
string match -q -r -- $match_regex (commandline --current-token --tokens-expanded | string collect -N)
|
||||||
|
else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
|
||||||
|
# fish v3.2.0 - v3.7.1 (last v3)
|
||||||
|
string match -q -r -- $match_regex (commandline --current-token --tokenize | string collect -N)
|
||||||
|
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)' '')
|
||||||
|
else
|
||||||
|
# fish older than v3.2.0 (v3.1b1 - v3.1.2)
|
||||||
|
set -l -- cl_token (commandline --current-token --tokenize | string collect -N)
|
||||||
|
set -- prefix (string match -r -- $prefix_regex $cl_token)
|
||||||
|
set -- fzf_query (string replace -- "$prefix" '' $cl_token | string collect -N)
|
||||||
|
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)|\\\n\\\n$' '')
|
||||||
|
end
|
||||||
|
|
||||||
|
if test -n "$fzf_query"
|
||||||
|
# Normalize path in $fzf_query, set $dir to the longest existing directory.
|
||||||
|
if test \( "$fish_major" -ge 4 \) -o \( "$fish_major" -eq 3 -a "$fish_minor" -ge 5 \)
|
||||||
|
# fish v3.5.0 and newer
|
||||||
|
set -- fzf_query (path normalize -- $fzf_query)
|
||||||
|
set -- dir $fzf_query
|
||||||
|
while not path is -d $dir
|
||||||
|
set -- dir (path dirname $dir)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
# fish older than v3.5.0 (v3.1b1 - v3.4.1)
|
||||||
|
if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
|
||||||
|
# fish v3.2.0 - v3.4.1
|
||||||
|
string match -q -r -- '(?<fzf_query>^[\s\S]*?(?=\n?$)$)' \
|
||||||
|
(string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\n)$' '' $fzf_query | string collect -N)
|
||||||
|
else
|
||||||
|
# fish v3.1b1 - v3.1.2
|
||||||
|
set -- fzf_query (string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\n)$' '' $fzf_query | string collect -N)
|
||||||
|
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r '\\\n$' '')
|
||||||
|
end
|
||||||
|
set -- dir $fzf_query
|
||||||
|
while not test -d "$dir"
|
||||||
|
set -- dir (dirname -z -- "$dir" | string split0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not string match -q -- '.' $dir; or string match -q -r -- '^\./|^\.$' $fzf_query
|
||||||
|
# Strip $dir from $fzf_query - preserve trailing newlines.
|
||||||
|
if test "$fish_major" -ge 4
|
||||||
|
# fish v4.0.0 and newer
|
||||||
|
string match -q -r -- '^'(string escape --style=regex -- $dir)'/?(?<fzf_query>[\s\S]*)' $fzf_query
|
||||||
|
else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
|
||||||
|
# fish v3.2.0 - v3.7.1 (last v3)
|
||||||
|
string match -q -r -- '^/?(?<fzf_query>[\s\S]*?(?=\n?$)$)' \
|
||||||
|
(string replace -- "$dir" '' $fzf_query | string collect -N)
|
||||||
|
else
|
||||||
|
# fish older than v3.2.0 (v3.1b1 - v3.1.2)
|
||||||
|
set -- fzf_query (string replace -- "$dir" '' $fzf_query | string collect -N)
|
||||||
|
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^/?|\\\n$' '')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
string escape -n -- "$dir" "$fzf_query" "$prefix"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Store current token in $dir as root for the 'find' command
|
# Store current token in $dir as root for the 'find' command
|
||||||
@ -31,40 +136,34 @@ function fzf_key_bindings
|
|||||||
set -lx dir $commandline[1]
|
set -lx dir $commandline[1]
|
||||||
set -l fzf_query $commandline[2]
|
set -l fzf_query $commandline[2]
|
||||||
set -l prefix $commandline[3]
|
set -l prefix $commandline[3]
|
||||||
set -l result
|
|
||||||
|
|
||||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
set -lx FZF_DEFAULT_OPTS (__fzf_defaults \
|
||||||
begin
|
"--reverse --walker=file,dir,follow,hidden --scheme=path" \
|
||||||
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "--reverse --walker=file,dir,follow,hidden --scheme=path --walker-root=$dir" "$FZF_CTRL_T_OPTS")
|
"$FZF_CTRL_T_OPTS --multi --print0")
|
||||||
|
|
||||||
set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND"
|
set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND"
|
||||||
set -lx FZF_DEFAULT_OPTS_FILE ''
|
set -lx FZF_DEFAULT_OPTS_FILE
|
||||||
set result (eval (__fzfcmd) -m --query=$fzf_query)
|
|
||||||
end
|
set -l result (eval (__fzfcmd) --walker-root=$dir --query=$fzf_query | string split0)
|
||||||
if test -z "$result"
|
and commandline -rt -- (string join -- ' ' $prefix(string escape -- $result))' '
|
||||||
commandline -f repaint
|
|
||||||
return
|
|
||||||
else
|
|
||||||
# Remove last token from commandline.
|
|
||||||
commandline -t ""
|
|
||||||
end
|
|
||||||
for i in $result
|
|
||||||
commandline -it -- $prefix
|
|
||||||
commandline -it -- (string escape -- $i)
|
|
||||||
commandline -it -- ' '
|
|
||||||
end
|
|
||||||
commandline -f repaint
|
commandline -f repaint
|
||||||
end
|
end
|
||||||
|
|
||||||
function fzf-history-widget -d "Show command history"
|
function fzf-history-widget -d "Show command history"
|
||||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
set -l -- command_line (commandline)
|
||||||
begin
|
set -l -- current_line (commandline -L)
|
||||||
# merge history from other sessions before searching
|
set -l -- total_lines (count $command_line)
|
||||||
test -z "$fish_private_mode"; and builtin history merge
|
set -l -- fzf_query (string escape -- $command_line[$current_line])
|
||||||
|
|
||||||
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '"\t"↳ ' --highlight-line +m $FZF_CTRL_R_OPTS")
|
set -lx FZF_DEFAULT_OPTS (__fzf_defaults '' \
|
||||||
set -lx FZF_DEFAULT_OPTS_FILE ''
|
'--nth=2..,.. --scheme=history --multi --wrap-sign="\t↳ "' \
|
||||||
|
'--bind=\'shift-delete:execute-silent(eval history delete --exact --case-sensitive -- (string escape -n -- {+} | string replace -r -a "^\d*\\\\\\t|(?<=\\\\\\n)\\\\\\t" ""))+reload(eval $FZF_DEFAULT_COMMAND)\'' \
|
||||||
|
"--bind=ctrl-r:toggle-sort --highlight-line $FZF_CTRL_R_OPTS" \
|
||||||
|
'--accept-nth=2.. --read0 --print0 --with-shell='(status fish-path)\\ -c)
|
||||||
|
|
||||||
|
set -lx FZF_DEFAULT_OPTS_FILE
|
||||||
set -lx FZF_DEFAULT_COMMAND
|
set -lx FZF_DEFAULT_COMMAND
|
||||||
set -a -- FZF_DEFAULT_OPTS --with-shell=(status fish-path)\\ -c
|
|
||||||
|
|
||||||
if type -q perl
|
if type -q perl
|
||||||
set -a FZF_DEFAULT_OPTS '--tac'
|
set -a FZF_DEFAULT_OPTS '--tac'
|
||||||
@ -76,9 +175,21 @@ function fzf_key_bindings
|
|||||||
'string join0 -- $i\t(string replace -a -- \n \n\t $h[$i] | string collect);' \
|
'string join0 -- $i\t(string replace -a -- \n \n\t $h[$i] | string collect);' \
|
||||||
'end'
|
'end'
|
||||||
end
|
end
|
||||||
set -l result (eval $FZF_DEFAULT_COMMAND \| (__fzfcmd) --read0 --print0 -q (commandline | string escape) "--bind=enter:become:'string replace -a -- \n\t \n {2..} | string collect'")
|
|
||||||
and commandline -- $result
|
# Merge history from other sessions before searching
|
||||||
|
test -z "$fish_private_mode"; and builtin history merge
|
||||||
|
|
||||||
|
if set -l result (eval $FZF_DEFAULT_COMMAND \| (__fzfcmd) --query=$fzf_query | string split0)
|
||||||
|
if test "$total_lines" -eq 1
|
||||||
|
commandline -- (string replace -a -- \n\t \n $result)
|
||||||
|
else
|
||||||
|
set -l a (math $current_line - 1)
|
||||||
|
set -l b (math $current_line + 1)
|
||||||
|
commandline -- $command_line[1..$a] (string replace -a -- \n\t \n $result)
|
||||||
|
commandline -a -- '' $command_line[$b..-1]
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
commandline -f repaint
|
commandline -f repaint
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -88,113 +199,32 @@ function fzf_key_bindings
|
|||||||
set -l fzf_query $commandline[2]
|
set -l fzf_query $commandline[2]
|
||||||
set -l prefix $commandline[3]
|
set -l prefix $commandline[3]
|
||||||
|
|
||||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
set -lx FZF_DEFAULT_OPTS (__fzf_defaults \
|
||||||
begin
|
"--reverse --walker=dir,follow,hidden --scheme=path" \
|
||||||
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "--reverse --walker=dir,follow,hidden --scheme=path --walker-root=$dir" "$FZF_ALT_C_OPTS")
|
"$FZF_ALT_C_OPTS --no-multi --print0")
|
||||||
set -lx FZF_DEFAULT_OPTS_FILE ''
|
|
||||||
|
set -lx FZF_DEFAULT_OPTS_FILE
|
||||||
set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND"
|
set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND"
|
||||||
set -l result (eval (__fzfcmd) +m --query=$fzf_query)
|
|
||||||
|
|
||||||
if test -n "$result"
|
if set -l result (eval (__fzfcmd) --query=$fzf_query --walker-root=$dir | string split0)
|
||||||
cd -- $result
|
cd -- $result
|
||||||
|
commandline -rt -- $prefix
|
||||||
# Remove last token from commandline.
|
|
||||||
commandline -t ""
|
|
||||||
commandline -it -- $prefix
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
commandline -f repaint
|
commandline -f repaint
|
||||||
end
|
end
|
||||||
|
|
||||||
function __fzfcmd
|
|
||||||
test -n "$FZF_TMUX"; or set FZF_TMUX 0
|
|
||||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
|
||||||
if test -n "$FZF_TMUX_OPTS"
|
|
||||||
echo "fzf-tmux $FZF_TMUX_OPTS -- "
|
|
||||||
else if test "$FZF_TMUX" = "1"
|
|
||||||
echo "fzf-tmux -d$FZF_TMUX_HEIGHT -- "
|
|
||||||
else
|
|
||||||
echo "fzf"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
bind \cr fzf-history-widget
|
bind \cr fzf-history-widget
|
||||||
|
bind -M insert \cr fzf-history-widget
|
||||||
|
|
||||||
if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND"
|
if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND"
|
||||||
bind \ct fzf-file-widget
|
bind \ct fzf-file-widget
|
||||||
end
|
|
||||||
if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND"
|
|
||||||
bind \ec fzf-cd-widget
|
|
||||||
end
|
|
||||||
|
|
||||||
bind -M insert \cr fzf-history-widget
|
|
||||||
if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND"
|
|
||||||
bind -M insert \ct fzf-file-widget
|
bind -M insert \ct fzf-file-widget
|
||||||
end
|
end
|
||||||
|
|
||||||
if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND"
|
if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND"
|
||||||
|
bind \ec fzf-cd-widget
|
||||||
bind -M insert \ec fzf-cd-widget
|
bind -M insert \ec fzf-cd-widget
|
||||||
end
|
end
|
||||||
|
|
||||||
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix'
|
|
||||||
set -l commandline (commandline -t)
|
|
||||||
|
|
||||||
# strip -option= from token if present
|
|
||||||
set -l prefix (string match -r -- '^-[^\s=]+=' $commandline)
|
|
||||||
set commandline (string replace -- "$prefix" '' $commandline)
|
|
||||||
|
|
||||||
# Enable home directory expansion of leading ~/
|
|
||||||
set commandline (string replace -r -- '^~/' '\$HOME/' $commandline)
|
|
||||||
|
|
||||||
# escape special characters, except for the $ sign of valid variable names,
|
|
||||||
# so that after eval, the original string is returned, but with the
|
|
||||||
# variable names replaced by their values.
|
|
||||||
set commandline (string escape -n -- $commandline)
|
|
||||||
set commandline (string replace -r -a -- '\x5c\$(?=[\w])' '\$' $commandline)
|
|
||||||
|
|
||||||
# eval is used to do shell expansion on paths
|
|
||||||
eval set commandline $commandline
|
|
||||||
|
|
||||||
# Combine multiple consecutive slashes into one
|
|
||||||
set commandline (string replace -r -a -- '/+' '/' $commandline)
|
|
||||||
|
|
||||||
if test -z "$commandline"
|
|
||||||
# Default to current directory with no --query
|
|
||||||
set dir '.'
|
|
||||||
set fzf_query ''
|
|
||||||
else
|
|
||||||
set dir (__fzf_get_dir $commandline)
|
|
||||||
|
|
||||||
# BUG: on combined expressions, if a left argument is a single `!`, the
|
|
||||||
# builtin test command of fish will treat it as the ! operator. To
|
|
||||||
# overcome this, have the variable parts on the right.
|
|
||||||
if test "." = "$dir" -a "./" != (string sub -l 2 -- $commandline)
|
|
||||||
# if $dir is "." but commandline is not a relative path, this means no file path found
|
|
||||||
set fzf_query $commandline
|
|
||||||
else
|
|
||||||
# Also remove trailing slash after dir, to "split" input properly
|
|
||||||
set fzf_query (string replace -r -- "^$dir/?" '' $commandline)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
echo (string escape -- $dir)
|
|
||||||
echo (string escape -- $fzf_query)
|
|
||||||
echo $prefix
|
|
||||||
end
|
|
||||||
|
|
||||||
function __fzf_get_dir -d 'Find the longest existing filepath from input string'
|
|
||||||
set dir $argv
|
|
||||||
|
|
||||||
# Strip trailing slash, unless $dir is root dir (/)
|
|
||||||
set dir (string replace -r -- '(?<!^)/$' '' $dir)
|
|
||||||
|
|
||||||
# Iteratively check if dir exists and strip tail end of path
|
|
||||||
while test ! -d "$dir"
|
|
||||||
# If path is absolute, this can keep going until ends up at /
|
|
||||||
# If path is relative, this can keep going until entire input is consumed, dirname returns "."
|
|
||||||
set dir (dirname -- "$dir")
|
|
||||||
end
|
|
||||||
|
|
||||||
echo $dir
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -41,9 +41,9 @@ if [[ -o interactive ]]; then
|
|||||||
__fzf_defaults() {
|
__fzf_defaults() {
|
||||||
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
echo "--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1"
|
echo -E "--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1"
|
||||||
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
||||||
echo "${FZF_DEFAULT_OPTS-} $2"
|
echo -E "${FZF_DEFAULT_OPTS-} $2"
|
||||||
}
|
}
|
||||||
|
|
||||||
# CTRL-T - Paste the selected file path(s) into the command line
|
# CTRL-T - Paste the selected file path(s) into the command line
|
||||||
|
@ -12,137 +12,145 @@ func _() {
|
|||||||
_ = x[actStart-1]
|
_ = x[actStart-1]
|
||||||
_ = x[actClick-2]
|
_ = x[actClick-2]
|
||||||
_ = x[actInvalid-3]
|
_ = x[actInvalid-3]
|
||||||
_ = x[actChar-4]
|
_ = x[actBracketedPasteBegin-4]
|
||||||
_ = x[actMouse-5]
|
_ = x[actBracketedPasteEnd-5]
|
||||||
_ = x[actBeginningOfLine-6]
|
_ = x[actChar-6]
|
||||||
_ = x[actAbort-7]
|
_ = x[actMouse-7]
|
||||||
_ = x[actAccept-8]
|
_ = x[actBeginningOfLine-8]
|
||||||
_ = x[actAcceptNonEmpty-9]
|
_ = x[actAbort-9]
|
||||||
_ = x[actAcceptOrPrintQuery-10]
|
_ = x[actAccept-10]
|
||||||
_ = x[actBackwardChar-11]
|
_ = x[actAcceptNonEmpty-11]
|
||||||
_ = x[actBackwardDeleteChar-12]
|
_ = x[actAcceptOrPrintQuery-12]
|
||||||
_ = x[actBackwardDeleteCharEof-13]
|
_ = x[actBackwardChar-13]
|
||||||
_ = x[actBackwardWord-14]
|
_ = x[actBackwardDeleteChar-14]
|
||||||
_ = x[actCancel-15]
|
_ = x[actBackwardDeleteCharEof-15]
|
||||||
_ = x[actChangeBorderLabel-16]
|
_ = x[actBackwardWord-16]
|
||||||
_ = x[actChangeListLabel-17]
|
_ = x[actCancel-17]
|
||||||
_ = x[actChangeInputLabel-18]
|
_ = x[actChangeBorderLabel-18]
|
||||||
_ = x[actChangeHeader-19]
|
_ = x[actChangeGhost-19]
|
||||||
_ = x[actChangeHeaderLabel-20]
|
_ = x[actChangeHeader-20]
|
||||||
_ = x[actChangeMulti-21]
|
_ = x[actChangeHeaderLabel-21]
|
||||||
_ = x[actChangePreviewLabel-22]
|
_ = x[actChangeInputLabel-22]
|
||||||
_ = x[actChangePrompt-23]
|
_ = x[actChangeListLabel-23]
|
||||||
_ = x[actChangeQuery-24]
|
_ = x[actChangeMulti-24]
|
||||||
_ = x[actChangeNth-25]
|
_ = x[actChangeNth-25]
|
||||||
_ = x[actClearScreen-26]
|
_ = x[actChangePointer-26]
|
||||||
_ = x[actClearQuery-27]
|
_ = x[actChangePreview-27]
|
||||||
_ = x[actClearSelection-28]
|
_ = x[actChangePreviewLabel-28]
|
||||||
_ = x[actClose-29]
|
_ = x[actChangePreviewWindow-29]
|
||||||
_ = x[actDeleteChar-30]
|
_ = x[actChangePrompt-30]
|
||||||
_ = x[actDeleteCharEof-31]
|
_ = x[actChangeQuery-31]
|
||||||
_ = x[actEndOfLine-32]
|
_ = x[actClearScreen-32]
|
||||||
_ = x[actFatal-33]
|
_ = x[actClearQuery-33]
|
||||||
_ = x[actForwardChar-34]
|
_ = x[actClearSelection-34]
|
||||||
_ = x[actForwardWord-35]
|
_ = x[actClose-35]
|
||||||
_ = x[actKillLine-36]
|
_ = x[actDeleteChar-36]
|
||||||
_ = x[actKillWord-37]
|
_ = x[actDeleteCharEof-37]
|
||||||
_ = x[actUnixLineDiscard-38]
|
_ = x[actEndOfLine-38]
|
||||||
_ = x[actUnixWordRubout-39]
|
_ = x[actFatal-39]
|
||||||
_ = x[actYank-40]
|
_ = x[actForwardChar-40]
|
||||||
_ = x[actBackwardKillWord-41]
|
_ = x[actForwardWord-41]
|
||||||
_ = x[actSelectAll-42]
|
_ = x[actKillLine-42]
|
||||||
_ = x[actDeselectAll-43]
|
_ = x[actKillWord-43]
|
||||||
_ = x[actToggle-44]
|
_ = x[actUnixLineDiscard-44]
|
||||||
_ = x[actToggleSearch-45]
|
_ = x[actUnixWordRubout-45]
|
||||||
_ = x[actToggleAll-46]
|
_ = x[actYank-46]
|
||||||
_ = x[actToggleDown-47]
|
_ = x[actBackwardKillWord-47]
|
||||||
_ = x[actToggleUp-48]
|
_ = x[actSelectAll-48]
|
||||||
_ = x[actToggleIn-49]
|
_ = x[actDeselectAll-49]
|
||||||
_ = x[actToggleOut-50]
|
_ = x[actToggle-50]
|
||||||
_ = x[actToggleTrack-51]
|
_ = x[actToggleSearch-51]
|
||||||
_ = x[actToggleTrackCurrent-52]
|
_ = x[actToggleAll-52]
|
||||||
_ = x[actToggleHeader-53]
|
_ = x[actToggleDown-53]
|
||||||
_ = x[actToggleWrap-54]
|
_ = x[actToggleUp-54]
|
||||||
_ = x[actToggleMultiLine-55]
|
_ = x[actToggleIn-55]
|
||||||
_ = x[actToggleHscroll-56]
|
_ = x[actToggleOut-56]
|
||||||
_ = x[actTrackCurrent-57]
|
_ = x[actToggleTrack-57]
|
||||||
_ = x[actToggleInput-58]
|
_ = x[actToggleTrackCurrent-58]
|
||||||
_ = x[actHideInput-59]
|
_ = x[actToggleHeader-59]
|
||||||
_ = x[actShowInput-60]
|
_ = x[actToggleWrap-60]
|
||||||
_ = x[actUntrackCurrent-61]
|
_ = x[actToggleMultiLine-61]
|
||||||
_ = x[actDown-62]
|
_ = x[actToggleHscroll-62]
|
||||||
_ = x[actUp-63]
|
_ = x[actTrackCurrent-63]
|
||||||
_ = x[actPageUp-64]
|
_ = x[actToggleInput-64]
|
||||||
_ = x[actPageDown-65]
|
_ = x[actHideInput-65]
|
||||||
_ = x[actPosition-66]
|
_ = x[actShowInput-66]
|
||||||
_ = x[actHalfPageUp-67]
|
_ = x[actUntrackCurrent-67]
|
||||||
_ = x[actHalfPageDown-68]
|
_ = x[actDown-68]
|
||||||
_ = x[actOffsetUp-69]
|
_ = x[actUp-69]
|
||||||
_ = x[actOffsetDown-70]
|
_ = x[actPageUp-70]
|
||||||
_ = x[actOffsetMiddle-71]
|
_ = x[actPageDown-71]
|
||||||
_ = x[actJump-72]
|
_ = x[actPosition-72]
|
||||||
_ = x[actJumpAccept-73]
|
_ = x[actHalfPageUp-73]
|
||||||
_ = x[actPrintQuery-74]
|
_ = x[actHalfPageDown-74]
|
||||||
_ = x[actRefreshPreview-75]
|
_ = x[actOffsetUp-75]
|
||||||
_ = x[actReplaceQuery-76]
|
_ = x[actOffsetDown-76]
|
||||||
_ = x[actToggleSort-77]
|
_ = x[actOffsetMiddle-77]
|
||||||
_ = x[actShowPreview-78]
|
_ = x[actJump-78]
|
||||||
_ = x[actHidePreview-79]
|
_ = x[actJumpAccept-79]
|
||||||
_ = x[actTogglePreview-80]
|
_ = x[actPrintQuery-80]
|
||||||
_ = x[actTogglePreviewWrap-81]
|
_ = x[actRefreshPreview-81]
|
||||||
_ = x[actTransform-82]
|
_ = x[actReplaceQuery-82]
|
||||||
_ = x[actTransformBorderLabel-83]
|
_ = x[actToggleSort-83]
|
||||||
_ = x[actTransformListLabel-84]
|
_ = x[actShowPreview-84]
|
||||||
_ = x[actTransformInputLabel-85]
|
_ = x[actHidePreview-85]
|
||||||
_ = x[actTransformHeader-86]
|
_ = x[actTogglePreview-86]
|
||||||
_ = x[actTransformHeaderLabel-87]
|
_ = x[actTogglePreviewWrap-87]
|
||||||
_ = x[actTransformNth-88]
|
_ = x[actTransform-88]
|
||||||
_ = x[actTransformPreviewLabel-89]
|
_ = x[actTransformBorderLabel-89]
|
||||||
_ = x[actTransformPrompt-90]
|
_ = x[actTransformGhost-90]
|
||||||
_ = x[actTransformQuery-91]
|
_ = x[actTransformHeader-91]
|
||||||
_ = x[actTransformSearch-92]
|
_ = x[actTransformHeaderLabel-92]
|
||||||
_ = x[actSearch-93]
|
_ = x[actTransformInputLabel-93]
|
||||||
_ = x[actPreview-94]
|
_ = x[actTransformListLabel-94]
|
||||||
_ = x[actChangePreview-95]
|
_ = x[actTransformNth-95]
|
||||||
_ = x[actChangePreviewWindow-96]
|
_ = x[actTransformPointer-96]
|
||||||
_ = x[actPreviewTop-97]
|
_ = x[actTransformPreviewLabel-97]
|
||||||
_ = x[actPreviewBottom-98]
|
_ = x[actTransformPrompt-98]
|
||||||
_ = x[actPreviewUp-99]
|
_ = x[actTransformQuery-99]
|
||||||
_ = x[actPreviewDown-100]
|
_ = x[actTransformSearch-100]
|
||||||
_ = x[actPreviewPageUp-101]
|
_ = x[actSearch-101]
|
||||||
_ = x[actPreviewPageDown-102]
|
_ = x[actPreview-102]
|
||||||
_ = x[actPreviewHalfPageUp-103]
|
_ = x[actPreviewTop-103]
|
||||||
_ = x[actPreviewHalfPageDown-104]
|
_ = x[actPreviewBottom-104]
|
||||||
_ = x[actPrevHistory-105]
|
_ = x[actPreviewUp-105]
|
||||||
_ = x[actPrevSelected-106]
|
_ = x[actPreviewDown-106]
|
||||||
_ = x[actPrint-107]
|
_ = x[actPreviewPageUp-107]
|
||||||
_ = x[actPut-108]
|
_ = x[actPreviewPageDown-108]
|
||||||
_ = x[actNextHistory-109]
|
_ = x[actPreviewHalfPageUp-109]
|
||||||
_ = x[actNextSelected-110]
|
_ = x[actPreviewHalfPageDown-110]
|
||||||
_ = x[actExecute-111]
|
_ = x[actPrevHistory-111]
|
||||||
_ = x[actExecuteSilent-112]
|
_ = x[actPrevSelected-112]
|
||||||
_ = x[actExecuteMulti-113]
|
_ = x[actPrint-113]
|
||||||
_ = x[actSigStop-114]
|
_ = x[actPut-114]
|
||||||
_ = x[actFirst-115]
|
_ = x[actNextHistory-115]
|
||||||
_ = x[actLast-116]
|
_ = x[actNextSelected-116]
|
||||||
_ = x[actReload-117]
|
_ = x[actExecute-117]
|
||||||
_ = x[actReloadSync-118]
|
_ = x[actExecuteSilent-118]
|
||||||
_ = x[actDisableSearch-119]
|
_ = x[actExecuteMulti-119]
|
||||||
_ = x[actEnableSearch-120]
|
_ = x[actSigStop-120]
|
||||||
_ = x[actSelect-121]
|
_ = x[actFirst-121]
|
||||||
_ = x[actDeselect-122]
|
_ = x[actLast-122]
|
||||||
_ = x[actUnbind-123]
|
_ = x[actReload-123]
|
||||||
_ = x[actRebind-124]
|
_ = x[actReloadSync-124]
|
||||||
_ = x[actToggleBind-125]
|
_ = x[actDisableSearch-125]
|
||||||
_ = x[actBecome-126]
|
_ = x[actEnableSearch-126]
|
||||||
_ = x[actShowHeader-127]
|
_ = x[actSelect-127]
|
||||||
_ = x[actHideHeader-128]
|
_ = x[actDeselect-128]
|
||||||
_ = x[actBell-129]
|
_ = x[actUnbind-129]
|
||||||
|
_ = x[actRebind-130]
|
||||||
|
_ = x[actToggleBind-131]
|
||||||
|
_ = x[actBecome-132]
|
||||||
|
_ = x[actShowHeader-133]
|
||||||
|
_ = x[actHideHeader-134]
|
||||||
|
_ = x[actBell-135]
|
||||||
|
_ = x[actExclude-136]
|
||||||
|
_ = x[actExcludeMulti-137]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeListLabelactChangeInputLabelactChangeHeaderactChangeHeaderLabelactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactChangeNthactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformListLabelactTransformInputLabelactTransformHeaderactTransformHeaderLabelactTransformNthactTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactSearchactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBell"
|
const _actionType_name = "actIgnoreactStartactClickactInvalidactBracketedPasteBeginactBracketedPasteEndactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeGhostactChangeHeaderactChangeHeaderLabelactChangeInputLabelactChangeListLabelactChangeMultiactChangeNthactChangePointeractChangePreviewactChangePreviewLabelactChangePreviewWindowactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformGhostactTransformHeaderactTransformHeaderLabelactTransformInputLabelactTransformListLabelactTransformNthactTransformPointeractTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactSearchactPreviewactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBellactExcludeactExcludeMulti"
|
||||||
|
|
||||||
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 245, 264, 279, 299, 313, 334, 349, 363, 375, 389, 402, 419, 427, 440, 456, 468, 476, 490, 504, 515, 526, 544, 561, 568, 587, 599, 613, 622, 637, 649, 662, 673, 684, 696, 710, 731, 746, 759, 777, 793, 808, 822, 834, 846, 863, 870, 875, 884, 895, 906, 919, 934, 945, 958, 973, 980, 993, 1006, 1023, 1038, 1051, 1065, 1079, 1095, 1115, 1127, 1150, 1171, 1193, 1211, 1234, 1249, 1273, 1291, 1308, 1326, 1335, 1345, 1361, 1383, 1396, 1412, 1424, 1438, 1454, 1472, 1492, 1514, 1528, 1543, 1551, 1557, 1571, 1586, 1596, 1612, 1627, 1637, 1645, 1652, 1661, 1674, 1690, 1705, 1714, 1725, 1734, 1743, 1756, 1765, 1778, 1791, 1798}
|
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 57, 77, 84, 92, 110, 118, 127, 144, 165, 180, 201, 225, 240, 249, 269, 283, 298, 318, 337, 355, 369, 381, 397, 413, 434, 456, 471, 485, 499, 512, 529, 537, 550, 566, 578, 586, 600, 614, 625, 636, 654, 671, 678, 697, 709, 723, 732, 747, 759, 772, 783, 794, 806, 820, 841, 856, 869, 887, 903, 918, 932, 944, 956, 973, 980, 985, 994, 1005, 1016, 1029, 1044, 1055, 1068, 1083, 1090, 1103, 1116, 1133, 1148, 1161, 1175, 1189, 1205, 1225, 1237, 1260, 1277, 1295, 1318, 1340, 1361, 1376, 1395, 1419, 1437, 1454, 1472, 1481, 1491, 1504, 1520, 1532, 1546, 1562, 1580, 1600, 1622, 1636, 1651, 1659, 1665, 1679, 1694, 1704, 1720, 1735, 1745, 1753, 1760, 1769, 1782, 1798, 1813, 1822, 1833, 1842, 1851, 1864, 1873, 1886, 1899, 1906, 1916, 1931}
|
||||||
|
|
||||||
func (i actionType) String() string {
|
func (i actionType) String() string {
|
||||||
if i < 0 || i >= actionType(len(_actionType_index)-1) {
|
if i < 0 || i >= actionType(len(_actionType_index)-1) {
|
||||||
|
@ -767,6 +767,9 @@ func FuzzyMatchV1(caseSensitive bool, normalize bool, forward bool, text *util.C
|
|||||||
char = unicode.To(unicode.LowerCase, char)
|
char = unicode.To(unicode.LowerCase, char)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if normalize {
|
||||||
|
char = normalizeRune(char)
|
||||||
|
}
|
||||||
|
|
||||||
pidx_ := indexAt(pidx, lenPattern, forward)
|
pidx_ := indexAt(pidx, lenPattern, forward)
|
||||||
pchar := pattern[pidx_]
|
pchar := pattern[pidx_]
|
||||||
|
@ -200,3 +200,12 @@ func TestLongString(t *testing.T) {
|
|||||||
bytes[math.MaxUint16] = 'z'
|
bytes[math.MaxUint16] = 'z'
|
||||||
assertMatch(t, FuzzyMatchV2, true, true, string(bytes), "zx", math.MaxUint16, math.MaxUint16+2, scoreMatch*2+bonusConsecutive)
|
assertMatch(t, FuzzyMatchV2, true, true, string(bytes), "zx", math.MaxUint16, math.MaxUint16+2, scoreMatch*2+bonusConsecutive)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLongStringWithNormalize(t *testing.T) {
|
||||||
|
bytes := make([]byte, 30000)
|
||||||
|
for i := range bytes {
|
||||||
|
bytes[i] = 'x'
|
||||||
|
}
|
||||||
|
unicodeString := string(bytes) + " Minímal example"
|
||||||
|
assertMatch2(t, FuzzyMatchV1, false, true, false, unicodeString, "minim", 30001, 30006, 140)
|
||||||
|
}
|
||||||
|
@ -26,7 +26,7 @@ const (
|
|||||||
previewCancelWait = 500 * time.Millisecond
|
previewCancelWait = 500 * time.Millisecond
|
||||||
previewChunkDelay = 100 * time.Millisecond
|
previewChunkDelay = 100 * time.Millisecond
|
||||||
previewDelayed = 500 * time.Millisecond
|
previewDelayed = 500 * time.Millisecond
|
||||||
maxPatternLength = 300
|
maxPatternLength = 1000
|
||||||
maxMulti = math.MaxInt32
|
maxMulti = math.MaxInt32
|
||||||
|
|
||||||
// Matcher
|
// Matcher
|
||||||
|
69
src/core.go
69
src/core.go
@ -96,7 +96,7 @@ func Run(opts *Options) (int, error) {
|
|||||||
var chunkList *ChunkList
|
var chunkList *ChunkList
|
||||||
var itemIndex int32
|
var itemIndex int32
|
||||||
header := make([]string, 0, opts.HeaderLines)
|
header := make([]string, 0, opts.HeaderLines)
|
||||||
if len(opts.WithNth) == 0 {
|
if opts.WithNth == nil {
|
||||||
chunkList = NewChunkList(cache, func(item *Item, data []byte) bool {
|
chunkList = NewChunkList(cache, func(item *Item, data []byte) bool {
|
||||||
if len(header) < opts.HeaderLines {
|
if len(header) < opts.HeaderLines {
|
||||||
header = append(header, byteString(data))
|
header = append(header, byteString(data))
|
||||||
@ -109,6 +109,7 @@ func Run(opts *Options) (int, error) {
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
nthTransformer := opts.WithNth(opts.Delimiter)
|
||||||
chunkList = NewChunkList(cache, func(item *Item, data []byte) bool {
|
chunkList = NewChunkList(cache, func(item *Item, data []byte) bool {
|
||||||
tokens := Tokenize(byteString(data), opts.Delimiter)
|
tokens := Tokenize(byteString(data), opts.Delimiter)
|
||||||
if opts.Ansi && opts.Theme.Colored && len(tokens) > 1 {
|
if opts.Ansi && opts.Theme.Colored && len(tokens) > 1 {
|
||||||
@ -127,8 +128,7 @@ func Run(opts *Options) (int, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
trans := Transform(tokens, opts.WithNth)
|
transformed := nthTransformer(tokens, itemIndex)
|
||||||
transformed := joinTokens(trans)
|
|
||||||
if len(header) < opts.HeaderLines {
|
if len(header) < opts.HeaderLines {
|
||||||
header = append(header, transformed)
|
header = append(header, transformed)
|
||||||
eventBox.Set(EvtHeader, header)
|
eventBox.Set(EvtHeader, header)
|
||||||
@ -195,15 +195,30 @@ func Run(opts *Options) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nth := opts.Nth
|
nth := opts.Nth
|
||||||
nthRevision := 0
|
|
||||||
patternCache := make(map[string]*Pattern)
|
|
||||||
patternBuilder := func(runes []rune) *Pattern {
|
|
||||||
return BuildPattern(cache, patternCache,
|
|
||||||
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward, withPos,
|
|
||||||
opts.Filter == nil, nth, opts.Delimiter, nthRevision, runes)
|
|
||||||
}
|
|
||||||
inputRevision := revision{}
|
inputRevision := revision{}
|
||||||
snapshotRevision := revision{}
|
snapshotRevision := revision{}
|
||||||
|
patternCache := make(map[string]*Pattern)
|
||||||
|
denyMutex := sync.Mutex{}
|
||||||
|
denylist := make(map[int32]struct{})
|
||||||
|
clearDenylist := func() {
|
||||||
|
denyMutex.Lock()
|
||||||
|
if len(denylist) > 0 {
|
||||||
|
patternCache = make(map[string]*Pattern)
|
||||||
|
}
|
||||||
|
denylist = make(map[int32]struct{})
|
||||||
|
denyMutex.Unlock()
|
||||||
|
}
|
||||||
|
patternBuilder := func(runes []rune) *Pattern {
|
||||||
|
denyMutex.Lock()
|
||||||
|
denylistCopy := make(map[int32]struct{})
|
||||||
|
for k, v := range denylist {
|
||||||
|
denylistCopy[k] = v
|
||||||
|
}
|
||||||
|
denyMutex.Unlock()
|
||||||
|
return BuildPattern(cache, patternCache,
|
||||||
|
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward, withPos,
|
||||||
|
opts.Filter == nil, nth, opts.Delimiter, inputRevision, runes, denylistCopy)
|
||||||
|
}
|
||||||
matcher := NewMatcher(cache, patternBuilder, sort, opts.Tac, eventBox, inputRevision)
|
matcher := NewMatcher(cache, patternBuilder, sort, opts.Tac, eventBox, inputRevision)
|
||||||
|
|
||||||
// Filtering mode
|
// Filtering mode
|
||||||
@ -280,6 +295,7 @@ func Run(opts *Options) (int, error) {
|
|||||||
// Event coordination
|
// Event coordination
|
||||||
reading := true
|
reading := true
|
||||||
ticks := 0
|
ticks := 0
|
||||||
|
startTick := 0
|
||||||
var nextCommand *commandSpec
|
var nextCommand *commandSpec
|
||||||
var nextEnviron []string
|
var nextEnviron []string
|
||||||
eventBox.Watch(EvtReadNew)
|
eventBox.Watch(EvtReadNew)
|
||||||
@ -302,7 +318,11 @@ func Run(opts *Options) (int, error) {
|
|||||||
var snapshot []*Chunk
|
var snapshot []*Chunk
|
||||||
var count int
|
var count int
|
||||||
restart := func(command commandSpec, environ []string) {
|
restart := func(command commandSpec, environ []string) {
|
||||||
|
if !useSnapshot {
|
||||||
|
clearDenylist()
|
||||||
|
}
|
||||||
reading = true
|
reading = true
|
||||||
|
startTick = ticks
|
||||||
chunkList.Clear()
|
chunkList.Clear()
|
||||||
itemIndex = 0
|
itemIndex = 0
|
||||||
inputRevision.bumpMajor()
|
inputRevision.bumpMajor()
|
||||||
@ -348,7 +368,8 @@ func Run(opts *Options) (int, error) {
|
|||||||
} else {
|
} else {
|
||||||
reading = reading && evt == EvtReadNew
|
reading = reading && evt == EvtReadNew
|
||||||
}
|
}
|
||||||
if useSnapshot && evt == EvtReadFin {
|
if useSnapshot && evt == EvtReadFin { // reload-sync
|
||||||
|
clearDenylist()
|
||||||
useSnapshot = false
|
useSnapshot = false
|
||||||
}
|
}
|
||||||
if !useSnapshot {
|
if !useSnapshot {
|
||||||
@ -379,10 +400,21 @@ func Run(opts *Options) (int, error) {
|
|||||||
command = val.command
|
command = val.command
|
||||||
environ = val.environ
|
environ = val.environ
|
||||||
changed = val.changed
|
changed = val.changed
|
||||||
|
bump := false
|
||||||
|
if len(val.denylist) > 0 && val.revision.compatible(inputRevision) {
|
||||||
|
denyMutex.Lock()
|
||||||
|
for _, itemIndex := range val.denylist {
|
||||||
|
denylist[itemIndex] = struct{}{}
|
||||||
|
}
|
||||||
|
denyMutex.Unlock()
|
||||||
|
bump = true
|
||||||
|
}
|
||||||
if val.nth != nil {
|
if val.nth != nil {
|
||||||
// Change nth and clear caches
|
// Change nth and clear caches
|
||||||
nth = *val.nth
|
nth = *val.nth
|
||||||
nthRevision++
|
bump = true
|
||||||
|
}
|
||||||
|
if bump {
|
||||||
patternCache = make(map[string]*Pattern)
|
patternCache = make(map[string]*Pattern)
|
||||||
cache.Clear()
|
cache.Clear()
|
||||||
inputRevision.bumpMinor()
|
inputRevision.bumpMinor()
|
||||||
@ -447,8 +479,17 @@ func Run(opts *Options) (int, error) {
|
|||||||
if len(opts.Expect) > 0 {
|
if len(opts.Expect) > 0 {
|
||||||
opts.Printer("")
|
opts.Printer("")
|
||||||
}
|
}
|
||||||
|
transformer := func(item *Item) string {
|
||||||
|
return item.AsString(opts.Ansi)
|
||||||
|
}
|
||||||
|
if opts.AcceptNth != nil {
|
||||||
|
fn := opts.AcceptNth(opts.Delimiter)
|
||||||
|
transformer = func(item *Item) string {
|
||||||
|
return item.acceptNth(opts.Ansi, opts.Delimiter, fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
opts.Printer(val.Get(i).item.AsString(opts.Ansi))
|
opts.Printer(transformer(val.Get(i).item))
|
||||||
}
|
}
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
exitCode = ExitNoMatch
|
exitCode = ExitNoMatch
|
||||||
@ -470,7 +511,7 @@ func Run(opts *Options) (int, error) {
|
|||||||
}
|
}
|
||||||
if delay && reading {
|
if delay && reading {
|
||||||
dur := util.DurWithin(
|
dur := util.DurWithin(
|
||||||
time.Duration(ticks)*coordinatorDelayStep,
|
time.Duration(ticks-startTick)*coordinatorDelayStep,
|
||||||
0, coordinatorDelayMax)
|
0, coordinatorDelayMax)
|
||||||
time.Sleep(dur)
|
time.Sleep(dur)
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
type transformed struct {
|
type transformed struct {
|
||||||
// Because nth can be changed dynamically by change-nth action, we need to
|
// Because nth can be changed dynamically by change-nth action, we need to
|
||||||
// keep the revision number at the time of transformation.
|
// keep the revision number at the time of transformation.
|
||||||
revision int
|
revision revision
|
||||||
tokens []Token
|
tokens []Token
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,3 +51,9 @@ func (item *Item) AsString(stripAnsi bool) string {
|
|||||||
}
|
}
|
||||||
return item.text.ToString()
|
return item.text.ToString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (item *Item) acceptNth(stripAnsi bool, delimiter Delimiter, transformer func([]Token, int32) string) string {
|
||||||
|
tokens := Tokenize(item.AsString(stripAnsi), delimiter)
|
||||||
|
transformed := transformer(tokens, item.Index())
|
||||||
|
return StripLastDelimiter(transformed, delimiter)
|
||||||
|
}
|
||||||
|
117
src/options.go
117
src/options.go
@ -41,6 +41,7 @@ Usage: fzf [options]
|
|||||||
integer or a range expression ([BEGIN]..[END]).
|
integer or a range expression ([BEGIN]..[END]).
|
||||||
--with-nth=N[,..] Transform the presentation of each line using
|
--with-nth=N[,..] Transform the presentation of each line using
|
||||||
field index expressions
|
field index expressions
|
||||||
|
--accept-nth=N[,..] Define which fields to print on accept
|
||||||
-d, --delimiter=STR Field delimiter regex (default: AWK-style)
|
-d, --delimiter=STR Field delimiter regex (default: AWK-style)
|
||||||
+s, --no-sort Do not sort the result
|
+s, --no-sort Do not sort the result
|
||||||
--literal Do not normalize latin script letters
|
--literal Do not normalize latin script letters
|
||||||
@ -135,6 +136,7 @@ Usage: fzf [options]
|
|||||||
--separator=STR Draw horizontal separator on info line using the string
|
--separator=STR Draw horizontal separator on info line using the string
|
||||||
(default: '─' or '-')
|
(default: '─' or '-')
|
||||||
--no-separator Hide info line separator
|
--no-separator Hide info line separator
|
||||||
|
--ghost=TEXT Ghost text to display when the input is empty
|
||||||
--filepath-word Make word-wise movements respect path separators
|
--filepath-word Make word-wise movements respect path separators
|
||||||
--input-border[=STYLE] Draw border around the input section
|
--input-border[=STYLE] Draw border around the input section
|
||||||
[rounded|sharp|bold|block|thinblock|double|horizontal|vertical|
|
[rounded|sharp|bold|block|thinblock|double|horizontal|vertical|
|
||||||
@ -543,7 +545,8 @@ type Options struct {
|
|||||||
Case Case
|
Case Case
|
||||||
Normalize bool
|
Normalize bool
|
||||||
Nth []Range
|
Nth []Range
|
||||||
WithNth []Range
|
WithNth func(Delimiter) func([]Token, int32) string
|
||||||
|
AcceptNth func(Delimiter) func([]Token, int32) string
|
||||||
Delimiter Delimiter
|
Delimiter Delimiter
|
||||||
Sort int
|
Sort int
|
||||||
Track trackOption
|
Track trackOption
|
||||||
@ -572,6 +575,7 @@ type Options struct {
|
|||||||
InfoStyle infoStyle
|
InfoStyle infoStyle
|
||||||
InfoPrefix string
|
InfoPrefix string
|
||||||
InfoCommand string
|
InfoCommand string
|
||||||
|
Ghost string
|
||||||
Separator *string
|
Separator *string
|
||||||
JumpLabels string
|
JumpLabels string
|
||||||
Prompt string
|
Prompt string
|
||||||
@ -627,6 +631,7 @@ type Options struct {
|
|||||||
MEMProfile string
|
MEMProfile string
|
||||||
BlockProfile string
|
BlockProfile string
|
||||||
MutexProfile string
|
MutexProfile string
|
||||||
|
TtyDefault string
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterNonEmpty(input []string) []string {
|
func filterNonEmpty(input []string) []string {
|
||||||
@ -665,7 +670,6 @@ func defaultOptions() *Options {
|
|||||||
Case: CaseSmart,
|
Case: CaseSmart,
|
||||||
Normalize: true,
|
Normalize: true,
|
||||||
Nth: make([]Range, 0),
|
Nth: make([]Range, 0),
|
||||||
WithNth: make([]Range, 0),
|
|
||||||
Delimiter: Delimiter{},
|
Delimiter: Delimiter{},
|
||||||
Sort: 1000,
|
Sort: 1000,
|
||||||
Track: trackDisabled,
|
Track: trackDisabled,
|
||||||
@ -688,6 +692,7 @@ func defaultOptions() *Options {
|
|||||||
ScrollOff: 3,
|
ScrollOff: 3,
|
||||||
FileWord: false,
|
FileWord: false,
|
||||||
InfoStyle: infoDefault,
|
InfoStyle: infoDefault,
|
||||||
|
Ghost: "",
|
||||||
Separator: nil,
|
Separator: nil,
|
||||||
JumpLabels: defaultJumpLabels,
|
JumpLabels: defaultJumpLabels,
|
||||||
Prompt: "> ",
|
Prompt: "> ",
|
||||||
@ -726,6 +731,7 @@ func defaultOptions() *Options {
|
|||||||
WalkerOpts: walkerOpts{file: true, hidden: true, follow: true},
|
WalkerOpts: walkerOpts{file: true, hidden: true, follow: true},
|
||||||
WalkerRoot: []string{"."},
|
WalkerRoot: []string{"."},
|
||||||
WalkerSkip: []string{".git", "node_modules"},
|
WalkerSkip: []string{".git", "node_modules"},
|
||||||
|
TtyDefault: tui.DefaultTtyDevice,
|
||||||
Help: false,
|
Help: false,
|
||||||
Version: false}
|
Version: false}
|
||||||
}
|
}
|
||||||
@ -768,6 +774,70 @@ func splitNth(str string) ([]Range, error) {
|
|||||||
return ranges, nil
|
return ranges, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func nthTransformer(str string) (func(Delimiter) func([]Token, int32) string, error) {
|
||||||
|
// ^[0-9,-.]+$"
|
||||||
|
if match, _ := regexp.MatchString("^[0-9,-.]+$", str); match {
|
||||||
|
nth, err := splitNth(str)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return func(Delimiter) func([]Token, int32) string {
|
||||||
|
return func(tokens []Token, index int32) string {
|
||||||
|
return JoinTokens(Transform(tokens, nth))
|
||||||
|
}
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// {...} {...} ...
|
||||||
|
placeholder := regexp.MustCompile("{[0-9,-.]+}|{n}")
|
||||||
|
indexes := placeholder.FindAllStringIndex(str, -1)
|
||||||
|
if indexes == nil {
|
||||||
|
return nil, errors.New("template should include at least 1 placeholder: " + str)
|
||||||
|
}
|
||||||
|
|
||||||
|
type NthParts struct {
|
||||||
|
str string
|
||||||
|
index bool
|
||||||
|
nth []Range
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := make([]NthParts, len(indexes))
|
||||||
|
idx := 0
|
||||||
|
for _, index := range indexes {
|
||||||
|
if idx < index[0] {
|
||||||
|
parts = append(parts, NthParts{str: str[idx:index[0]]})
|
||||||
|
}
|
||||||
|
expr := str[index[0]+1 : index[1]-1]
|
||||||
|
if expr == "n" {
|
||||||
|
parts = append(parts, NthParts{index: true})
|
||||||
|
} else if nth, err := splitNth(expr); err == nil {
|
||||||
|
parts = append(parts, NthParts{nth: nth})
|
||||||
|
}
|
||||||
|
idx = index[1]
|
||||||
|
}
|
||||||
|
if idx < len(str) {
|
||||||
|
parts = append(parts, NthParts{str: str[idx:]})
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(delimiter Delimiter) func([]Token, int32) string {
|
||||||
|
return func(tokens []Token, index int32) string {
|
||||||
|
str := ""
|
||||||
|
for _, holder := range parts {
|
||||||
|
if holder.nth != nil {
|
||||||
|
str += StripLastDelimiter(JoinTokens(Transform(tokens, holder.nth)), delimiter)
|
||||||
|
} else if holder.index {
|
||||||
|
if index >= 0 {
|
||||||
|
str += strconv.Itoa(int(index))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
str += holder.str
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func delimiterRegexp(str string) Delimiter {
|
func delimiterRegexp(str string) Delimiter {
|
||||||
// Special handling of \t
|
// Special handling of \t
|
||||||
str = strings.ReplaceAll(str, "\\t", "\t")
|
str = strings.ReplaceAll(str, "\\t", "\t")
|
||||||
@ -1114,7 +1184,12 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro
|
|||||||
var err error
|
var err error
|
||||||
theme := dupeTheme(defaultTheme)
|
theme := dupeTheme(defaultTheme)
|
||||||
rrggbb := regexp.MustCompile("^#[0-9a-fA-F]{6}$")
|
rrggbb := regexp.MustCompile("^#[0-9a-fA-F]{6}$")
|
||||||
for _, str := range strings.Split(strings.ToLower(str), ",") {
|
comma := regexp.MustCompile(`[\s,]+`)
|
||||||
|
for _, str := range comma.Split(strings.ToLower(str), -1) {
|
||||||
|
str = strings.TrimSpace(str)
|
||||||
|
if len(str) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
switch str {
|
switch str {
|
||||||
case "dark":
|
case "dark":
|
||||||
theme = dupeTheme(tui.Dark256)
|
theme = dupeTheme(tui.Dark256)
|
||||||
@ -1225,6 +1300,8 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro
|
|||||||
mergeAttr(&theme.Current)
|
mergeAttr(&theme.Current)
|
||||||
case "current-bg", "bg+":
|
case "current-bg", "bg+":
|
||||||
mergeAttr(&theme.DarkBg)
|
mergeAttr(&theme.DarkBg)
|
||||||
|
case "alt-bg":
|
||||||
|
mergeAttr(&theme.AltBg)
|
||||||
case "selected-fg":
|
case "selected-fg":
|
||||||
mergeAttr(&theme.SelectedFg)
|
mergeAttr(&theme.SelectedFg)
|
||||||
case "selected-bg":
|
case "selected-bg":
|
||||||
@ -1336,7 +1413,7 @@ const (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
executeRegexp = regexp.MustCompile(
|
executeRegexp = regexp.MustCompile(
|
||||||
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:query|prompt|(?:border|list|preview|input|header)-label|header|search|nth)|transform|change-(?:preview-window|preview|multi)|(?:re|un|toggle-)bind|pos|put|print|search)`)
|
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:query|prompt|(?:border|list|preview|input|header)-label|header|search|nth|pointer|ghost)|transform|change-(?:preview-window|preview|multi)|(?:re|un|toggle-)bind|pos|put|print|search)`)
|
||||||
splitRegexp = regexp.MustCompile("[,:]+")
|
splitRegexp = regexp.MustCompile("[,:]+")
|
||||||
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
|
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
|
||||||
}
|
}
|
||||||
@ -1600,6 +1677,10 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
|||||||
}
|
}
|
||||||
case "bell":
|
case "bell":
|
||||||
appendAction(actBell)
|
appendAction(actBell)
|
||||||
|
case "exclude":
|
||||||
|
appendAction(actExclude)
|
||||||
|
case "exclude-multi":
|
||||||
|
appendAction(actExcludeMulti)
|
||||||
default:
|
default:
|
||||||
t := isExecuteAction(specLower)
|
t := isExecuteAction(specLower)
|
||||||
if t == actIgnore {
|
if t == actIgnore {
|
||||||
@ -1727,6 +1808,10 @@ func isExecuteAction(str string) actionType {
|
|||||||
return actChangeInputLabel
|
return actChangeInputLabel
|
||||||
case "change-header-label":
|
case "change-header-label":
|
||||||
return actChangeHeaderLabel
|
return actChangeHeaderLabel
|
||||||
|
case "change-ghost":
|
||||||
|
return actChangeGhost
|
||||||
|
case "change-pointer":
|
||||||
|
return actChangePointer
|
||||||
case "change-preview-window":
|
case "change-preview-window":
|
||||||
return actChangePreviewWindow
|
return actChangePreviewWindow
|
||||||
case "change-preview":
|
case "change-preview":
|
||||||
@ -1765,8 +1850,12 @@ func isExecuteAction(str string) actionType {
|
|||||||
return actTransformHeaderLabel
|
return actTransformHeaderLabel
|
||||||
case "transform-header":
|
case "transform-header":
|
||||||
return actTransformHeader
|
return actTransformHeader
|
||||||
|
case "transform-ghost":
|
||||||
|
return actTransformGhost
|
||||||
case "transform-nth":
|
case "transform-nth":
|
||||||
return actTransformNth
|
return actTransformNth
|
||||||
|
case "transform-pointer":
|
||||||
|
return actTransformPointer
|
||||||
case "transform-prompt":
|
case "transform-prompt":
|
||||||
return actTransformPrompt
|
return actTransformPrompt
|
||||||
case "transform-query":
|
case "transform-query":
|
||||||
@ -2256,6 +2345,12 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
}
|
}
|
||||||
case "--no-tmux":
|
case "--no-tmux":
|
||||||
opts.Tmux = nil
|
opts.Tmux = nil
|
||||||
|
case "--tty-default":
|
||||||
|
if opts.TtyDefault, err = nextString("tty device name required"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "--no-tty-default":
|
||||||
|
opts.TtyDefault = ""
|
||||||
case "--force-tty-in":
|
case "--force-tty-in":
|
||||||
// NOTE: We need this because `system('fzf --tmux < /dev/tty')` doesn't
|
// NOTE: We need this because `system('fzf --tmux < /dev/tty')` doesn't
|
||||||
// work on Neovim. Same as '-' option of fzf-tmux.
|
// work on Neovim. Same as '-' option of fzf-tmux.
|
||||||
@ -2380,7 +2475,15 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if opts.WithNth, err = splitNth(str); err != nil {
|
if opts.WithNth, err = nthTransformer(str); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "--accept-nth":
|
||||||
|
str, err := nextString("nth expression required")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if opts.AcceptNth, err = nthTransformer(str); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case "-s", "--sort":
|
case "-s", "--sort":
|
||||||
@ -2520,6 +2623,10 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
case "--no-separator":
|
case "--no-separator":
|
||||||
nosep := ""
|
nosep := ""
|
||||||
opts.Separator = &nosep
|
opts.Separator = &nosep
|
||||||
|
case "--ghost":
|
||||||
|
if opts.Ghost, err = nextString("ghost text required"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
case "--scrollbar":
|
case "--scrollbar":
|
||||||
given, bar := optionalNextString()
|
given, bar := optionalNextString()
|
||||||
if given {
|
if given {
|
||||||
|
@ -333,7 +333,7 @@ func TestColorSpec(t *testing.T) {
|
|||||||
t.Errorf("colors should now be equivalent: %v, %v", tui.Dark256, customized)
|
t.Errorf("colors should now be equivalent: %v, %v", tui.Dark256, customized)
|
||||||
}
|
}
|
||||||
|
|
||||||
customized, _ = parseTheme(theme, "fg:231,dark,bg:232")
|
customized, _ = parseTheme(theme, "fg:231,dark bg:232")
|
||||||
if customized.Fg != tui.Dark256.Fg || customized.Bg == tui.Dark256.Bg {
|
if customized.Fg != tui.Dark256.Fg || customized.Bg == tui.Dark256.Bg {
|
||||||
t.Errorf("color not customized")
|
t.Errorf("color not customized")
|
||||||
}
|
}
|
||||||
|
@ -60,9 +60,10 @@ type Pattern struct {
|
|||||||
cacheKey string
|
cacheKey string
|
||||||
delimiter Delimiter
|
delimiter Delimiter
|
||||||
nth []Range
|
nth []Range
|
||||||
revision int
|
revision revision
|
||||||
procFun map[termType]algo.Algo
|
procFun map[termType]algo.Algo
|
||||||
cache *ChunkCache
|
cache *ChunkCache
|
||||||
|
denylist map[int32]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _splitRegex *regexp.Regexp
|
var _splitRegex *regexp.Regexp
|
||||||
@ -73,7 +74,7 @@ func init() {
|
|||||||
|
|
||||||
// BuildPattern builds Pattern object from the given arguments
|
// BuildPattern builds Pattern object from the given arguments
|
||||||
func BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool,
|
func BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool,
|
||||||
withPos bool, cacheable bool, nth []Range, delimiter Delimiter, revision int, runes []rune) *Pattern {
|
withPos bool, cacheable bool, nth []Range, delimiter Delimiter, revision revision, runes []rune, denylist map[int32]struct{}) *Pattern {
|
||||||
|
|
||||||
var asString string
|
var asString string
|
||||||
if extended {
|
if extended {
|
||||||
@ -144,6 +145,7 @@ func BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy boo
|
|||||||
revision: revision,
|
revision: revision,
|
||||||
delimiter: delimiter,
|
delimiter: delimiter,
|
||||||
cache: cache,
|
cache: cache,
|
||||||
|
denylist: denylist,
|
||||||
procFun: make(map[termType]algo.Algo)}
|
procFun: make(map[termType]algo.Algo)}
|
||||||
|
|
||||||
ptr.cacheKey = ptr.buildCacheKey()
|
ptr.cacheKey = ptr.buildCacheKey()
|
||||||
@ -243,6 +245,9 @@ func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet
|
|||||||
|
|
||||||
// IsEmpty returns true if the pattern is effectively empty
|
// IsEmpty returns true if the pattern is effectively empty
|
||||||
func (p *Pattern) IsEmpty() bool {
|
func (p *Pattern) IsEmpty() bool {
|
||||||
|
if len(p.denylist) > 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if !p.extended {
|
if !p.extended {
|
||||||
return len(p.text) == 0
|
return len(p.text) == 0
|
||||||
}
|
}
|
||||||
@ -296,6 +301,8 @@ func (p *Pattern) Match(chunk *Chunk, slab *util.Slab) []Result {
|
|||||||
func (p *Pattern) matchChunk(chunk *Chunk, space []Result, slab *util.Slab) []Result {
|
func (p *Pattern) matchChunk(chunk *Chunk, space []Result, slab *util.Slab) []Result {
|
||||||
matches := []Result{}
|
matches := []Result{}
|
||||||
|
|
||||||
|
if len(p.denylist) == 0 {
|
||||||
|
// Huge code duplication for minimizing unnecessary map lookups
|
||||||
if space == nil {
|
if space == nil {
|
||||||
for idx := 0; idx < chunk.count; idx++ {
|
for idx := 0; idx < chunk.count; idx++ {
|
||||||
if match, _, _ := p.MatchItem(&chunk.items[idx], p.withPos, slab); match != nil {
|
if match, _, _ := p.MatchItem(&chunk.items[idx], p.withPos, slab); match != nil {
|
||||||
@ -310,6 +317,28 @@ func (p *Pattern) matchChunk(chunk *Chunk, space []Result, slab *util.Slab) []Re
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return matches
|
return matches
|
||||||
|
}
|
||||||
|
|
||||||
|
if space == nil {
|
||||||
|
for idx := 0; idx < chunk.count; idx++ {
|
||||||
|
if _, prs := p.denylist[chunk.items[idx].Index()]; prs {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if match, _, _ := p.MatchItem(&chunk.items[idx], p.withPos, slab); match != nil {
|
||||||
|
matches = append(matches, *match)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, result := range space {
|
||||||
|
if _, prs := p.denylist[result.item.Index()]; prs {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if match, _, _ := p.MatchItem(result.item, p.withPos, slab); match != nil {
|
||||||
|
matches = append(matches, *match)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matches
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchItem returns true if the Item is a match
|
// MatchItem returns true if the Item is a match
|
||||||
@ -403,6 +432,13 @@ func (p *Pattern) transformInput(item *Item) []Token {
|
|||||||
|
|
||||||
tokens := Tokenize(item.text.ToString(), p.delimiter)
|
tokens := Tokenize(item.text.ToString(), p.delimiter)
|
||||||
ret := Transform(tokens, p.nth)
|
ret := Transform(tokens, p.nth)
|
||||||
|
// Strip the last delimiter to allow suffix match
|
||||||
|
if len(ret) > 0 && !p.delimiter.IsAwk() {
|
||||||
|
chars := ret[len(ret)-1].text
|
||||||
|
stripped := StripLastDelimiter(chars.ToString(), p.delimiter)
|
||||||
|
newChars := util.ToChars(stringBytes(stripped))
|
||||||
|
ret[len(ret)-1].text = &newChars
|
||||||
|
}
|
||||||
item.transformed = &transformed{p.revision, ret}
|
item.transformed = &transformed{p.revision, ret}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ func buildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
|||||||
withPos bool, cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
|
withPos bool, cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
|
||||||
return BuildPattern(NewChunkCache(), make(map[string]*Pattern),
|
return BuildPattern(NewChunkCache(), make(map[string]*Pattern),
|
||||||
fuzzy, fuzzyAlgo, extended, caseMode, normalize, forward,
|
fuzzy, fuzzyAlgo, extended, caseMode, normalize, forward,
|
||||||
withPos, cacheable, nth, delimiter, 0, runes)
|
withPos, cacheable, nth, delimiter, revision{}, runes, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExact(t *testing.T) {
|
func TestExact(t *testing.T) {
|
||||||
|
15
src/proxy.go
15
src/proxy.go
@ -59,12 +59,12 @@ func runProxy(commandPrefix string, cmdBuilder func(temp string, needBash bool)
|
|||||||
})
|
})
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var command string
|
var command, input string
|
||||||
commandPrefix += ` --no-force-tty-in --proxy-script "$0"`
|
commandPrefix += ` --no-force-tty-in --proxy-script "$0"`
|
||||||
if opts.Input == nil && (opts.ForceTtyIn || util.IsTty(os.Stdin)) {
|
if opts.Input == nil && (opts.ForceTtyIn || util.IsTty(os.Stdin)) {
|
||||||
command = fmt.Sprintf(`%s > %q`, commandPrefix, output)
|
command = fmt.Sprintf(`%s > %q`, commandPrefix, output)
|
||||||
} else {
|
} else {
|
||||||
input, err := fifo("proxy-input")
|
input, err = fifo("proxy-input")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ExitError, err
|
return ExitError, err
|
||||||
}
|
}
|
||||||
@ -90,9 +90,9 @@ func runProxy(commandPrefix string, cmdBuilder func(temp string, needBash bool)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// To ensure that the options are processed by a POSIX-compliant shell,
|
// * Write the command to a temporary file and run it with sh to ensure POSIX compliance.
|
||||||
// we need to write the command to a temporary file and execute it with sh.
|
// * Nullify FZF_DEFAULT_* variables as tmux popup may inject them even when undefined.
|
||||||
var exports []string
|
exports := []string{"FZF_DEFAULT_COMMAND=", "FZF_DEFAULT_OPTS=", "FZF_DEFAULT_OPTS_FILE="}
|
||||||
needBash := false
|
needBash := false
|
||||||
if withExports {
|
if withExports {
|
||||||
validIdentifier := regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
|
validIdentifier := regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
|
||||||
@ -144,10 +144,13 @@ func runProxy(commandPrefix string, cmdBuilder func(temp string, needBash bool)
|
|||||||
env = elems[1:]
|
env = elems[1:]
|
||||||
}
|
}
|
||||||
executor := util.NewExecutor(opts.WithShell)
|
executor := util.NewExecutor(opts.WithShell)
|
||||||
ttyin, err := tui.TtyIn()
|
ttyin, err := tui.TtyIn(opts.TtyDefault)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ExitError, err
|
return ExitError, err
|
||||||
}
|
}
|
||||||
|
os.Remove(temp)
|
||||||
|
os.Remove(input)
|
||||||
|
os.Remove(output)
|
||||||
executor.Become(ttyin, env, command)
|
executor.Become(ttyin, env, command)
|
||||||
}
|
}
|
||||||
return code, err
|
return code, err
|
||||||
|
@ -277,6 +277,9 @@ func (r *Reader) readFiles(roots []string, opts walkerOpts, ignores []string) bo
|
|||||||
ignoresFull := []string{}
|
ignoresFull := []string{}
|
||||||
ignoresSuffix := []string{}
|
ignoresSuffix := []string{}
|
||||||
sep := string(os.PathSeparator)
|
sep := string(os.PathSeparator)
|
||||||
|
if _, ok := os.LookupEnv("MSYSTEM"); ok {
|
||||||
|
sep = "/"
|
||||||
|
}
|
||||||
for _, ignore := range ignores {
|
for _, ignore := range ignores {
|
||||||
if strings.ContainsRune(ignore, os.PathSeparator) {
|
if strings.ContainsRune(ignore, os.PathSeparator) {
|
||||||
if strings.HasPrefix(ignore, sep) {
|
if strings.HasPrefix(ignore, sep) {
|
||||||
@ -320,6 +323,9 @@ func (r *Reader) readFiles(roots []string, opts walkerOpts, ignores []string) bo
|
|||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if path != sep {
|
||||||
|
path += sep
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ((opts.file && !isDir) || (opts.dir && isDir)) && r.pusher(stringBytes(path)) {
|
if ((opts.file && !isDir) || (opts.dir && isDir)) && r.pusher(stringBytes(path)) {
|
||||||
atomic.StoreInt32(&r.event, int32(EvtReadNew))
|
atomic.StoreInt32(&r.event, int32(EvtReadNew))
|
||||||
|
@ -119,7 +119,7 @@ func minRank() Result {
|
|||||||
return Result{item: &minItem, points: [4]uint16{math.MaxUint16, 0, 0, 0}}
|
return Result{item: &minItem, points: [4]uint16{math.MaxUint16, 0, 0, 0}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, theme *tui.ColorTheme, colBase tui.ColorPair, colMatch tui.ColorPair, attrNth tui.Attr, current bool) []colorOffset {
|
func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, theme *tui.ColorTheme, colBase tui.ColorPair, colMatch tui.ColorPair, attrNth tui.Attr) []colorOffset {
|
||||||
itemColors := result.item.Colors()
|
itemColors := result.item.Colors()
|
||||||
|
|
||||||
// No ANSI codes
|
// No ANSI codes
|
||||||
@ -182,18 +182,10 @@ func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, t
|
|||||||
fg := ansi.color.fg
|
fg := ansi.color.fg
|
||||||
bg := ansi.color.bg
|
bg := ansi.color.bg
|
||||||
if fg == -1 {
|
if fg == -1 {
|
||||||
if current {
|
fg = colBase.Fg()
|
||||||
fg = theme.Current.Color
|
|
||||||
} else {
|
|
||||||
fg = theme.Fg.Color
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if bg == -1 {
|
if bg == -1 {
|
||||||
if current {
|
bg = colBase.Bg()
|
||||||
bg = theme.DarkBg.Color
|
|
||||||
} else {
|
|
||||||
bg = theme.Bg.Color
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return tui.NewColorPair(fg, bg, ansi.color.attr).MergeAttr(base)
|
return tui.NewColorPair(fg, bg, ansi.color.attr).MergeAttr(base)
|
||||||
}
|
}
|
||||||
|
@ -131,7 +131,7 @@ func TestColorOffset(t *testing.T) {
|
|||||||
|
|
||||||
colBase := tui.NewColorPair(89, 189, tui.AttrUndefined)
|
colBase := tui.NewColorPair(89, 189, tui.AttrUndefined)
|
||||||
colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined)
|
colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined)
|
||||||
colors := item.colorOffsets(offsets, nil, tui.Dark256, colBase, colMatch, tui.AttrUndefined, true)
|
colors := item.colorOffsets(offsets, nil, tui.Dark256, colBase, colMatch, tui.AttrUndefined)
|
||||||
assert := func(idx int, b int32, e int32, c tui.ColorPair) {
|
assert := func(idx int, b int32, e int32, c tui.ColorPair) {
|
||||||
o := colors[idx]
|
o := colors[idx]
|
||||||
if o.offset[0] != b || o.offset[1] != e || o.color != c {
|
if o.offset[0] != b || o.offset[1] != e || o.color != c {
|
||||||
@ -158,7 +158,7 @@ func TestColorOffset(t *testing.T) {
|
|||||||
|
|
||||||
nthOffsets := []Offset{{37, 39}, {42, 45}}
|
nthOffsets := []Offset{{37, 39}, {42, 45}}
|
||||||
for _, attr := range []tui.Attr{tui.AttrRegular, tui.StrikeThrough} {
|
for _, attr := range []tui.Attr{tui.AttrRegular, tui.StrikeThrough} {
|
||||||
colors = item.colorOffsets(offsets, nthOffsets, tui.Dark256, colRegular, colUnderline, attr, true)
|
colors = item.colorOffsets(offsets, nthOffsets, tui.Dark256, colRegular, colUnderline, attr)
|
||||||
|
|
||||||
// [{[0 5] {1 5 0}} {[5 15] {1 5 8}} {[15 20] {1 5 0}}
|
// [{[0 5] {1 5 0}} {[5 15] {1 5 8}} {[15 20] {1 5 0}}
|
||||||
// {[22 25] {2 6 1}} {[25 27] {2 6 9}} {[27 30] {-1 -1 8}}
|
// {[22 25] {2 6 1}} {[25 27] {2 6 9}} {[27 30] {-1 -1 8}}
|
||||||
|
418
src/terminal.go
418
src/terminal.go
@ -38,7 +38,7 @@ As such it is not useful for validation, but rather to generate test
|
|||||||
cases for example.
|
cases for example.
|
||||||
|
|
||||||
\\?(?: # escaped type
|
\\?(?: # escaped type
|
||||||
{\+?s?f?RANGE(?:,RANGE)*} # token type
|
{\+?s?f?r?RANGE(?:,RANGE)*} # token type
|
||||||
{q[:s?RANGE]} # query type
|
{q[:s?RANGE]} # query type
|
||||||
|{\+?n?f?} # item type (notice no mandatory element inside brackets)
|
|{\+?n?f?} # item type (notice no mandatory element inside brackets)
|
||||||
)
|
)
|
||||||
@ -65,7 +65,7 @@ const maxFocusEvents = 10000
|
|||||||
const blockDuration = 1 * time.Second
|
const blockDuration = 1 * time.Second
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q(?::s?[0-9,-.]+)?}|{fzf:(?:query|action|prompt)}|{\+?f?nf?})`)
|
placeholder = regexp.MustCompile(`\\?(?:{[+sfr]*[0-9,-.]*}|{q(?::s?[0-9,-.]+)?}|{fzf:(?:query|action|prompt)}|{\+?f?nf?})`)
|
||||||
whiteSuffix = regexp.MustCompile(`\s*$`)
|
whiteSuffix = regexp.MustCompile(`\s*$`)
|
||||||
offsetComponentRegex = regexp.MustCompile(`([+-][0-9]+)|(-?/[1-9][0-9]*)`)
|
offsetComponentRegex = regexp.MustCompile(`([+-][0-9]+)|(-?/[1-9][0-9]*)`)
|
||||||
offsetTrimCharsRegex = regexp.MustCompile(`[^0-9/+-]`)
|
offsetTrimCharsRegex = regexp.MustCompile(`[^0-9/+-]`)
|
||||||
@ -234,6 +234,7 @@ type Terminal struct {
|
|||||||
wrap bool
|
wrap bool
|
||||||
wrapSign string
|
wrapSign string
|
||||||
wrapSignWidth int
|
wrapSignWidth int
|
||||||
|
ghost string
|
||||||
separator labelPrinter
|
separator labelPrinter
|
||||||
separatorLen int
|
separatorLen int
|
||||||
spinner []string
|
spinner []string
|
||||||
@ -278,6 +279,7 @@ type Terminal struct {
|
|||||||
yanked []rune
|
yanked []rune
|
||||||
input []rune
|
input []rune
|
||||||
inputOverride *[]rune
|
inputOverride *[]rune
|
||||||
|
pasting *[]rune
|
||||||
multi int
|
multi int
|
||||||
multiLine bool
|
multiLine bool
|
||||||
sort bool
|
sort bool
|
||||||
@ -305,6 +307,7 @@ type Terminal struct {
|
|||||||
nthAttr tui.Attr
|
nthAttr tui.Attr
|
||||||
nth []Range
|
nth []Range
|
||||||
nthCurrent []Range
|
nthCurrent []Range
|
||||||
|
acceptNth func([]Token, int32) string
|
||||||
tabstop int
|
tabstop int
|
||||||
margin [4]sizeSpec
|
margin [4]sizeSpec
|
||||||
padding [4]sizeSpec
|
padding [4]sizeSpec
|
||||||
@ -378,6 +381,7 @@ type Terminal struct {
|
|||||||
slab *util.Slab
|
slab *util.Slab
|
||||||
theme *tui.ColorTheme
|
theme *tui.ColorTheme
|
||||||
tui tui.Renderer
|
tui tui.Renderer
|
||||||
|
ttyDefault string
|
||||||
ttyin *os.File
|
ttyin *os.File
|
||||||
executing *util.AtomicBool
|
executing *util.AtomicBool
|
||||||
termSize tui.TermSize
|
termSize tui.TermSize
|
||||||
@ -390,6 +394,12 @@ type Terminal struct {
|
|||||||
clickHeaderLine int
|
clickHeaderLine int
|
||||||
clickHeaderColumn int
|
clickHeaderColumn int
|
||||||
proxyScript string
|
proxyScript string
|
||||||
|
numLinesCache map[int32]numLinesCacheValue
|
||||||
|
}
|
||||||
|
|
||||||
|
type numLinesCacheValue struct {
|
||||||
|
atMost int
|
||||||
|
numLines int
|
||||||
}
|
}
|
||||||
|
|
||||||
type selectedItem struct {
|
type selectedItem struct {
|
||||||
@ -451,6 +461,8 @@ const (
|
|||||||
actStart
|
actStart
|
||||||
actClick
|
actClick
|
||||||
actInvalid
|
actInvalid
|
||||||
|
actBracketedPasteBegin
|
||||||
|
actBracketedPasteEnd
|
||||||
actChar
|
actChar
|
||||||
actMouse
|
actMouse
|
||||||
actBeginningOfLine
|
actBeginningOfLine
|
||||||
@ -464,15 +476,19 @@ const (
|
|||||||
actBackwardWord
|
actBackwardWord
|
||||||
actCancel
|
actCancel
|
||||||
actChangeBorderLabel
|
actChangeBorderLabel
|
||||||
actChangeListLabel
|
actChangeGhost
|
||||||
actChangeInputLabel
|
|
||||||
actChangeHeader
|
actChangeHeader
|
||||||
actChangeHeaderLabel
|
actChangeHeaderLabel
|
||||||
|
actChangeInputLabel
|
||||||
|
actChangeListLabel
|
||||||
actChangeMulti
|
actChangeMulti
|
||||||
|
actChangeNth
|
||||||
|
actChangePointer
|
||||||
|
actChangePreview
|
||||||
actChangePreviewLabel
|
actChangePreviewLabel
|
||||||
|
actChangePreviewWindow
|
||||||
actChangePrompt
|
actChangePrompt
|
||||||
actChangeQuery
|
actChangeQuery
|
||||||
actChangeNth
|
|
||||||
actClearScreen
|
actClearScreen
|
||||||
actClearQuery
|
actClearQuery
|
||||||
actClearSelection
|
actClearSelection
|
||||||
@ -531,19 +547,19 @@ const (
|
|||||||
actTogglePreviewWrap
|
actTogglePreviewWrap
|
||||||
actTransform
|
actTransform
|
||||||
actTransformBorderLabel
|
actTransformBorderLabel
|
||||||
actTransformListLabel
|
actTransformGhost
|
||||||
actTransformInputLabel
|
|
||||||
actTransformHeader
|
actTransformHeader
|
||||||
actTransformHeaderLabel
|
actTransformHeaderLabel
|
||||||
|
actTransformInputLabel
|
||||||
|
actTransformListLabel
|
||||||
actTransformNth
|
actTransformNth
|
||||||
|
actTransformPointer
|
||||||
actTransformPreviewLabel
|
actTransformPreviewLabel
|
||||||
actTransformPrompt
|
actTransformPrompt
|
||||||
actTransformQuery
|
actTransformQuery
|
||||||
actTransformSearch
|
actTransformSearch
|
||||||
actSearch
|
actSearch
|
||||||
actPreview
|
actPreview
|
||||||
actChangePreview
|
|
||||||
actChangePreviewWindow
|
|
||||||
actPreviewTop
|
actPreviewTop
|
||||||
actPreviewBottom
|
actPreviewBottom
|
||||||
actPreviewUp
|
actPreviewUp
|
||||||
@ -577,6 +593,8 @@ const (
|
|||||||
actShowHeader
|
actShowHeader
|
||||||
actHideHeader
|
actHideHeader
|
||||||
actBell
|
actBell
|
||||||
|
actExclude
|
||||||
|
actExcludeMulti
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a actionType) Name() string {
|
func (a actionType) Name() string {
|
||||||
@ -611,6 +629,7 @@ type placeholderFlags struct {
|
|||||||
number bool
|
number bool
|
||||||
forceUpdate bool
|
forceUpdate bool
|
||||||
file bool
|
file bool
|
||||||
|
raw bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type searchRequest struct {
|
type searchRequest struct {
|
||||||
@ -620,6 +639,8 @@ type searchRequest struct {
|
|||||||
command *commandSpec
|
command *commandSpec
|
||||||
environ []string
|
environ []string
|
||||||
changed bool
|
changed bool
|
||||||
|
denylist []int32
|
||||||
|
revision revision
|
||||||
}
|
}
|
||||||
|
|
||||||
type previewRequest struct {
|
type previewRequest struct {
|
||||||
@ -627,6 +648,7 @@ type previewRequest struct {
|
|||||||
scrollOffset int
|
scrollOffset int
|
||||||
list []*Item
|
list []*Item
|
||||||
env []string
|
env []string
|
||||||
|
query string
|
||||||
}
|
}
|
||||||
|
|
||||||
type previewResult struct {
|
type previewResult struct {
|
||||||
@ -655,6 +677,8 @@ func defaultKeymap() map[tui.Event][]*action {
|
|||||||
|
|
||||||
add(tui.Fatal, actFatal)
|
add(tui.Fatal, actFatal)
|
||||||
add(tui.Invalid, actInvalid)
|
add(tui.Invalid, actInvalid)
|
||||||
|
add(tui.BracketedPasteBegin, actBracketedPasteBegin)
|
||||||
|
add(tui.BracketedPasteEnd, actBracketedPasteEnd)
|
||||||
add(tui.CtrlA, actBeginningOfLine)
|
add(tui.CtrlA, actBeginningOfLine)
|
||||||
add(tui.CtrlB, actBackwardChar)
|
add(tui.CtrlB, actBackwardChar)
|
||||||
add(tui.CtrlC, actAbort)
|
add(tui.CtrlC, actAbort)
|
||||||
@ -786,7 +810,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|||||||
// when you run fzf multiple times in your Go program. Closing it is known to
|
// when you run fzf multiple times in your Go program. Closing it is known to
|
||||||
// cause problems with 'become' action and invalid terminal state after exit.
|
// cause problems with 'become' action and invalid terminal state after exit.
|
||||||
if ttyin == nil {
|
if ttyin == nil {
|
||||||
if ttyin, err = tui.TtyIn(); err != nil {
|
if ttyin, err = tui.TtyIn(opts.TtyDefault); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -794,7 +818,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|||||||
if tui.HasFullscreenRenderer() {
|
if tui.HasFullscreenRenderer() {
|
||||||
renderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse)
|
renderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse)
|
||||||
} else {
|
} else {
|
||||||
renderer, err = tui.NewLightRenderer(ttyin, opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit,
|
renderer, err = tui.NewLightRenderer(opts.TtyDefault, ttyin, opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit,
|
||||||
true, func(h int) int { return h })
|
true, func(h int) int { return h })
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -810,7 +834,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|||||||
effectiveMinHeight += borderLines(opts.BorderShape)
|
effectiveMinHeight += borderLines(opts.BorderShape)
|
||||||
return util.Min(termHeight, util.Max(evaluateHeight(opts, termHeight), effectiveMinHeight))
|
return util.Min(termHeight, util.Max(evaluateHeight(opts, termHeight), effectiveMinHeight))
|
||||||
}
|
}
|
||||||
renderer, err = tui.NewLightRenderer(ttyin, opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, false, maxHeightFunc)
|
renderer, err = tui.NewLightRenderer(opts.TtyDefault, ttyin, opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, false, maxHeightFunc)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -835,6 +859,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|||||||
infoCommand: opts.InfoCommand,
|
infoCommand: opts.InfoCommand,
|
||||||
infoStyle: opts.InfoStyle,
|
infoStyle: opts.InfoStyle,
|
||||||
infoPrefix: opts.InfoPrefix,
|
infoPrefix: opts.InfoPrefix,
|
||||||
|
ghost: opts.Ghost,
|
||||||
separator: nil,
|
separator: nil,
|
||||||
spinner: makeSpinner(opts.Unicode),
|
spinner: makeSpinner(opts.Unicode),
|
||||||
promptString: opts.Prompt,
|
promptString: opts.Prompt,
|
||||||
@ -943,11 +968,16 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|||||||
keyChan: make(chan tui.Event),
|
keyChan: make(chan tui.Event),
|
||||||
eventChan: make(chan tui.Event, 6), // start | (load + result + zero|one) | (focus) | (resize)
|
eventChan: make(chan tui.Event, 6), // start | (load + result + zero|one) | (focus) | (resize)
|
||||||
tui: renderer,
|
tui: renderer,
|
||||||
|
ttyDefault: opts.TtyDefault,
|
||||||
ttyin: ttyin,
|
ttyin: ttyin,
|
||||||
initFunc: func() error { return renderer.Init() },
|
initFunc: func() error { return renderer.Init() },
|
||||||
executing: util.NewAtomicBool(false),
|
executing: util.NewAtomicBool(false),
|
||||||
lastAction: actStart,
|
lastAction: actStart,
|
||||||
lastFocus: minItem.Index()}
|
lastFocus: minItem.Index(),
|
||||||
|
numLinesCache: make(map[int32]numLinesCacheValue)}
|
||||||
|
if opts.AcceptNth != nil {
|
||||||
|
t.acceptNth = opts.AcceptNth(t.delimiter)
|
||||||
|
}
|
||||||
|
|
||||||
// This should be called before accessing tui.Color*
|
// This should be called before accessing tui.Color*
|
||||||
tui.InitTheme(opts.Theme, renderer.DefaultTheme(), opts.Black, opts.InputBorderShape.Visible(), opts.HeaderBorderShape.Visible())
|
tui.InitTheme(opts.Theme, renderer.DefaultTheme(), opts.Black, opts.InputBorderShape.Visible(), opts.HeaderBorderShape.Visible())
|
||||||
@ -1061,9 +1091,13 @@ func (t *Terminal) environImpl(forPreview bool) []string {
|
|||||||
env = append(env, "FZF_ACTION="+t.lastAction.Name())
|
env = append(env, "FZF_ACTION="+t.lastAction.Name())
|
||||||
env = append(env, "FZF_KEY="+t.lastKey)
|
env = append(env, "FZF_KEY="+t.lastKey)
|
||||||
env = append(env, "FZF_PROMPT="+string(t.promptString))
|
env = append(env, "FZF_PROMPT="+string(t.promptString))
|
||||||
|
env = append(env, "FZF_GHOST="+string(t.ghost))
|
||||||
|
env = append(env, "FZF_POINTER="+string(t.pointer))
|
||||||
env = append(env, "FZF_PREVIEW_LABEL="+t.previewLabelOpts.label)
|
env = append(env, "FZF_PREVIEW_LABEL="+t.previewLabelOpts.label)
|
||||||
env = append(env, "FZF_BORDER_LABEL="+t.borderLabelOpts.label)
|
env = append(env, "FZF_BORDER_LABEL="+t.borderLabelOpts.label)
|
||||||
env = append(env, "FZF_LIST_LABEL="+t.listLabelOpts.label)
|
env = append(env, "FZF_LIST_LABEL="+t.listLabelOpts.label)
|
||||||
|
env = append(env, "FZF_INPUT_LABEL="+t.inputLabelOpts.label)
|
||||||
|
env = append(env, "FZF_HEADER_LABEL="+t.headerLabelOpts.label)
|
||||||
if len(t.nthCurrent) > 0 {
|
if len(t.nthCurrent) > 0 {
|
||||||
env = append(env, "FZF_NTH="+RangesToString(t.nthCurrent))
|
env = append(env, "FZF_NTH="+RangesToString(t.nthCurrent))
|
||||||
}
|
}
|
||||||
@ -1201,9 +1235,14 @@ func (t *Terminal) ansiLabelPrinter(str string, color *tui.ColorPair, fill bool)
|
|||||||
return nil, 0
|
return nil, 0
|
||||||
}
|
}
|
||||||
printFn := func(window tui.Window, limit int) {
|
printFn := func(window tui.Window, limit int) {
|
||||||
if length > limit {
|
ellipsis := []rune{}
|
||||||
trimmedRunes, _ := t.trimRight(runes, limit)
|
ellipsisWidth := 0
|
||||||
window.CPrint(*color, string(trimmedRunes))
|
if !fill {
|
||||||
|
ellipsis, ellipsisWidth = util.Truncate(t.ellipsis, limit)
|
||||||
|
}
|
||||||
|
if length > limit-ellipsisWidth {
|
||||||
|
trimmedRunes, _ := t.trimRight(runes, limit-ellipsisWidth)
|
||||||
|
window.CPrint(*color, string(trimmedRunes)+string(ellipsis))
|
||||||
} else if fill {
|
} else if fill {
|
||||||
window.CPrint(*color, util.RepeatToFill(text, length, limit))
|
window.CPrint(*color, util.RepeatToFill(text, length, limit))
|
||||||
} else {
|
} else {
|
||||||
@ -1224,7 +1263,7 @@ func (t *Terminal) ansiLabelPrinter(str string, color *tui.ColorPair, fill bool)
|
|||||||
printFn := func(window tui.Window, limit int) {
|
printFn := func(window tui.Window, limit int) {
|
||||||
if offsets == nil {
|
if offsets == nil {
|
||||||
// tui.Col* are not initialized until renderer.Init()
|
// tui.Col* are not initialized until renderer.Init()
|
||||||
offsets = result.colorOffsets(nil, nil, t.theme, *color, *color, t.nthAttr, false)
|
offsets = result.colorOffsets(nil, nil, t.theme, *color, *color, t.nthAttr)
|
||||||
}
|
}
|
||||||
for limit > 0 {
|
for limit > 0 {
|
||||||
if length > limit {
|
if length > limit {
|
||||||
@ -1283,8 +1322,11 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) {
|
|||||||
t.wrap = false
|
t.wrap = false
|
||||||
t.withWindow(t.inputWindow, func() {
|
t.withWindow(t.inputWindow, func() {
|
||||||
line := t.promptLine()
|
line := t.promptLine()
|
||||||
|
preTask := func(markerClass) int {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
t.printHighlighted(
|
t.printHighlighted(
|
||||||
Result{item: item}, tui.ColPrompt, tui.ColPrompt, false, false, line, line, true, nil, nil)
|
Result{item: item}, tui.ColPrompt, tui.ColPrompt, false, false, line, line, true, preTask, nil)
|
||||||
})
|
})
|
||||||
t.wrap = wrap
|
t.wrap = wrap
|
||||||
}
|
}
|
||||||
@ -1318,6 +1360,10 @@ func (t *Terminal) wrapCols() int {
|
|||||||
return util.Max(t.window.Width()-(t.pointerLen+t.markerLen+1), 1)
|
return util.Max(t.window.Width()-(t.pointerLen+t.markerLen+1), 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) clearNumLinesCache() {
|
||||||
|
t.numLinesCache = make(map[int32]numLinesCacheValue)
|
||||||
|
}
|
||||||
|
|
||||||
// Number of lines the item takes including the gap
|
// Number of lines the item takes including the gap
|
||||||
func (t *Terminal) numItemLines(item *Item, atMost int) (int, bool) {
|
func (t *Terminal) numItemLines(item *Item, atMost int) (int, bool) {
|
||||||
var numLines int
|
var numLines int
|
||||||
@ -1325,6 +1371,12 @@ func (t *Terminal) numItemLines(item *Item, atMost int) (int, bool) {
|
|||||||
numLines = 1 + t.gap
|
numLines = 1 + t.gap
|
||||||
return numLines, numLines > atMost
|
return numLines, numLines > atMost
|
||||||
}
|
}
|
||||||
|
if cached, prs := t.numLinesCache[item.Index()]; prs {
|
||||||
|
// Can we use this cache? Let's be conservative.
|
||||||
|
if cached.atMost <= atMost {
|
||||||
|
return cached.numLines, false
|
||||||
|
}
|
||||||
|
}
|
||||||
var overflow bool
|
var overflow bool
|
||||||
if !t.wrap && t.multiLine {
|
if !t.wrap && t.multiLine {
|
||||||
numLines, overflow = item.text.NumLines(atMost)
|
numLines, overflow = item.text.NumLines(atMost)
|
||||||
@ -1334,6 +1386,9 @@ func (t *Terminal) numItemLines(item *Item, atMost int) (int, bool) {
|
|||||||
numLines = len(lines)
|
numLines = len(lines)
|
||||||
}
|
}
|
||||||
numLines += t.gap
|
numLines += t.gap
|
||||||
|
if !overflow {
|
||||||
|
t.numLinesCache[item.Index()] = numLinesCacheValue{atMost, numLines}
|
||||||
|
}
|
||||||
return numLines, overflow || numLines > atMost
|
return numLines, overflow || numLines > atMost
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1379,10 +1434,7 @@ func (t *Terminal) Input() (bool, []rune) {
|
|||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
defer t.mutex.Unlock()
|
defer t.mutex.Unlock()
|
||||||
paused := t.paused
|
paused := t.paused
|
||||||
var src []rune
|
src := t.input
|
||||||
if !t.inputless {
|
|
||||||
src = t.input
|
|
||||||
}
|
|
||||||
if t.inputOverride != nil {
|
if t.inputOverride != nil {
|
||||||
paused = false
|
paused = false
|
||||||
src = *t.inputOverride
|
src = *t.inputOverride
|
||||||
@ -1461,6 +1513,7 @@ func (t *Terminal) UpdateList(merger *Merger) {
|
|||||||
if !t.revision.compatible(newRevision) {
|
if !t.revision.compatible(newRevision) {
|
||||||
// Reloaded: clear selection
|
// Reloaded: clear selection
|
||||||
t.selected = make(map[int32]selectedItem)
|
t.selected = make(map[int32]selectedItem)
|
||||||
|
t.clearNumLinesCache()
|
||||||
} else {
|
} else {
|
||||||
// Trimmed by --tail: filter selection by index
|
// Trimmed by --tail: filter selection by index
|
||||||
filtered := make(map[int32]selectedItem)
|
filtered := make(map[int32]selectedItem)
|
||||||
@ -1540,16 +1593,24 @@ func (t *Terminal) output() bool {
|
|||||||
for _, s := range t.printQueue {
|
for _, s := range t.printQueue {
|
||||||
t.printer(s)
|
t.printer(s)
|
||||||
}
|
}
|
||||||
|
transform := func(item *Item) string {
|
||||||
|
return item.AsString(t.ansi)
|
||||||
|
}
|
||||||
|
if t.acceptNth != nil {
|
||||||
|
transform = func(item *Item) string {
|
||||||
|
return item.acceptNth(t.ansi, t.delimiter, t.acceptNth)
|
||||||
|
}
|
||||||
|
}
|
||||||
found := len(t.selected) > 0
|
found := len(t.selected) > 0
|
||||||
if !found {
|
if !found {
|
||||||
current := t.currentItem()
|
current := t.currentItem()
|
||||||
if current != nil {
|
if current != nil {
|
||||||
t.printer(current.AsString(t.ansi))
|
t.printer(transform(current))
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, sel := range t.sortSelected() {
|
for _, sel := range t.sortSelected() {
|
||||||
t.printer(sel.item.AsString(t.ansi))
|
t.printer(transform(sel.item))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return found
|
return found
|
||||||
@ -1712,6 +1773,7 @@ func (t *Terminal) hasHeaderLinesWindow() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
|
func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
|
||||||
|
t.clearNumLinesCache()
|
||||||
t.forcePreview = forcePreview
|
t.forcePreview = forcePreview
|
||||||
screenWidth, screenHeight, marginInt, paddingInt := t.adjustMarginAndPadding()
|
screenWidth, screenHeight, marginInt, paddingInt := t.adjustMarginAndPadding()
|
||||||
width := screenWidth - marginInt[1] - marginInt[3]
|
width := screenWidth - marginInt[1] - marginInt[3]
|
||||||
@ -1900,6 +1962,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
|
|||||||
pwidth -= 1
|
pwidth -= 1
|
||||||
}
|
}
|
||||||
t.pwindow = t.tui.NewWindow(y, x, pwidth, pheight, tui.WindowPreview, noBorder, true)
|
t.pwindow = t.tui.NewWindow(y, x, pwidth, pheight, tui.WindowPreview, noBorder, true)
|
||||||
|
t.pwindow.SetWrapSign(t.wrapSign, t.wrapSignWidth)
|
||||||
if !hadPreviewWindow {
|
if !hadPreviewWindow {
|
||||||
t.pwindow.Erase()
|
t.pwindow.Erase()
|
||||||
}
|
}
|
||||||
@ -2246,7 +2309,11 @@ func (t *Terminal) move(y int, x int, clear bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) truncateQuery() {
|
func (t *Terminal) truncateQuery() {
|
||||||
t.input, _ = t.trimRight(t.input, maxPatternLength)
|
// We're limiting the length of the query not to make fzf unresponsive when
|
||||||
|
// the user accidentally pastes a huge chunk of text. Therefore, we're not
|
||||||
|
// interested in the exact display width of the query. We just limit the
|
||||||
|
// number of runes.
|
||||||
|
t.input = t.input[:util.Min(len(t.input), maxPatternLength)]
|
||||||
t.cx = util.Constrain(t.cx, 0, len(t.input))
|
t.cx = util.Constrain(t.cx, 0, len(t.input))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2290,15 +2357,18 @@ func (t *Terminal) placeCursor() {
|
|||||||
if t.inputless {
|
if t.inputless {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
x := t.promptLen + t.queryLen[0]
|
||||||
if t.inputWindow != nil {
|
if t.inputWindow != nil {
|
||||||
y := t.inputWindow.Height() - 1
|
y := t.inputWindow.Height() - 1
|
||||||
if t.layout == layoutReverse {
|
if t.layout == layoutReverse {
|
||||||
y = 0
|
y = 0
|
||||||
}
|
}
|
||||||
t.inputWindow.Move(y, t.promptLen+t.queryLen[0])
|
x = util.Min(x, t.inputWindow.Width()-1)
|
||||||
|
t.inputWindow.Move(y, x)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t.move(t.promptLine(), t.promptLen+t.queryLen[0], false)
|
x = util.Min(x, t.window.Width()-1)
|
||||||
|
t.move(t.promptLine(), x, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) printPrompt() {
|
func (t *Terminal) printPrompt() {
|
||||||
@ -2315,6 +2385,11 @@ func (t *Terminal) printPrompt() {
|
|||||||
t.prompt()
|
t.prompt()
|
||||||
|
|
||||||
before, after := t.updatePromptOffset()
|
before, after := t.updatePromptOffset()
|
||||||
|
if len(before) == 0 && len(after) == 0 && len(t.ghost) > 0 {
|
||||||
|
w.CPrint(tui.ColInput.WithAttr(tui.Dim), t.ghost)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
color := tui.ColInput
|
color := tui.ColInput
|
||||||
if t.paused {
|
if t.paused {
|
||||||
color = tui.ColDisabled
|
color = tui.ColDisabled
|
||||||
@ -2434,6 +2509,10 @@ func (t *Terminal) printInfoImpl() {
|
|||||||
outputPrinter, outputLen = t.ansiLabelPrinter(output, &tui.ColInfo, false)
|
outputPrinter, outputLen = t.ansiLabelPrinter(output, &tui.ColInfo, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shiftLen := t.queryLen[0] + t.queryLen[1] + 1
|
||||||
|
if shiftLen == 1 && len(t.ghost) > 0 {
|
||||||
|
shiftLen = util.StringWidth(t.ghost)
|
||||||
|
}
|
||||||
switch t.infoStyle {
|
switch t.infoStyle {
|
||||||
case infoDefault:
|
case infoDefault:
|
||||||
if !move(line+1, 0, t.separatorLen == 0) {
|
if !move(line+1, 0, t.separatorLen == 0) {
|
||||||
@ -2447,9 +2526,9 @@ func (t *Terminal) printInfoImpl() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
case infoInlineRight:
|
case infoInlineRight:
|
||||||
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
pos = t.promptLen + shiftLen
|
||||||
case infoInline:
|
case infoInline:
|
||||||
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
pos = t.promptLen + shiftLen
|
||||||
printInfoPrefix()
|
printInfoPrefix()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2579,6 +2658,9 @@ func (t *Terminal) headerIndent(borderShape tui.BorderShape) int {
|
|||||||
}
|
}
|
||||||
if borderShape.HasLeft() {
|
if borderShape.HasLeft() {
|
||||||
indentSize -= 1 + t.borderWidth
|
indentSize -= 1 + t.borderWidth
|
||||||
|
if indentSize < 0 {
|
||||||
|
indentSize = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return indentSize
|
return indentSize
|
||||||
}
|
}
|
||||||
@ -2713,15 +2795,30 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
|
|||||||
item := result.item
|
item := result.item
|
||||||
_, selected := t.selected[item.Index()]
|
_, selected := t.selected[item.Index()]
|
||||||
label := ""
|
label := ""
|
||||||
|
extraWidth := 0
|
||||||
|
alt := false
|
||||||
|
altBg := t.theme.AltBg
|
||||||
|
selectedBg := selected && t.theme.SelectedBg != t.theme.ListBg
|
||||||
if t.jumping != jumpDisabled {
|
if t.jumping != jumpDisabled {
|
||||||
if index < len(t.jumpLabels) {
|
if index < len(t.jumpLabels) {
|
||||||
// Striped
|
// Striped
|
||||||
current = index%2 == 0
|
if !altBg.IsColorDefined() {
|
||||||
label = t.jumpLabels[index:index+1] + strings.Repeat(" ", t.pointerLen-1)
|
altBg = t.theme.DarkBg
|
||||||
|
alt = index%2 == 0
|
||||||
|
} else {
|
||||||
|
alt = index%2 == 1
|
||||||
}
|
}
|
||||||
} else if current {
|
label = t.jumpLabels[index:index+1] + strings.Repeat(" ", util.Max(0, t.pointerLen-1))
|
||||||
|
if t.pointerLen == 0 {
|
||||||
|
extraWidth = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if current {
|
||||||
label = t.pointer
|
label = t.pointer
|
||||||
}
|
}
|
||||||
|
alt = !selectedBg && altBg.IsColorDefined() && index%2 == 1
|
||||||
|
}
|
||||||
|
|
||||||
// Avoid unnecessary redraw
|
// Avoid unnecessary redraw
|
||||||
numLines, _ := t.numItemLines(item, maxLine-line+1)
|
numLines, _ := t.numItemLines(item, maxLine-line+1)
|
||||||
@ -2746,10 +2843,13 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
|
|||||||
|
|
||||||
maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1)
|
maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1)
|
||||||
postTask := func(lineNum int, width int, wrapped bool, forceRedraw bool) {
|
postTask := func(lineNum int, width int, wrapped bool, forceRedraw bool) {
|
||||||
if (current || selected) && t.highlightLine {
|
width += extraWidth
|
||||||
|
if (current || selected || alt) && t.highlightLine {
|
||||||
color := tui.ColSelected
|
color := tui.ColSelected
|
||||||
if current {
|
if current {
|
||||||
color = tui.ColCurrent
|
color = tui.ColCurrent
|
||||||
|
} else if alt {
|
||||||
|
color = color.WithBg(altBg)
|
||||||
}
|
}
|
||||||
fillSpaces := maxWidth - width
|
fillSpaces := maxWidth - width
|
||||||
if wrapped {
|
if wrapped {
|
||||||
@ -2847,6 +2947,10 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
|
|||||||
base = tui.ColNormal
|
base = tui.ColNormal
|
||||||
match = tui.ColMatch
|
match = tui.ColMatch
|
||||||
}
|
}
|
||||||
|
if alt {
|
||||||
|
base = base.WithBg(altBg)
|
||||||
|
match = match.WithBg(altBg)
|
||||||
|
}
|
||||||
finalLineNum = t.printHighlighted(result, base, match, false, true, line, maxLine, forceRedraw, preTask, postTask)
|
finalLineNum = t.printHighlighted(result, base, match, false, true, line, maxLine, forceRedraw, preTask, postTask)
|
||||||
}
|
}
|
||||||
for i := 0; i < t.gap && finalLineNum < maxLine; i++ {
|
for i := 0; i < t.gap && finalLineNum < maxLine; i++ {
|
||||||
@ -2932,7 +3036,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
|||||||
}
|
}
|
||||||
if !wholeCovered && t.nthAttr > 0 {
|
if !wholeCovered && t.nthAttr > 0 {
|
||||||
var tokens []Token
|
var tokens []Token
|
||||||
if item.transformed != nil {
|
if item.transformed != nil && item.transformed.revision == t.merger.revision {
|
||||||
tokens = item.transformed.tokens
|
tokens = item.transformed.tokens
|
||||||
} else {
|
} else {
|
||||||
tokens = Transform(Tokenize(item.text.ToString(), t.delimiter), t.nthCurrent)
|
tokens = Transform(Tokenize(item.text.ToString(), t.delimiter), t.nthCurrent)
|
||||||
@ -2945,7 +3049,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
|||||||
sort.Sort(ByOrder(nthOffsets))
|
sort.Sort(ByOrder(nthOffsets))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
allOffsets := result.colorOffsets(charOffsets, nthOffsets, t.theme, colBase, colMatch, t.nthAttr, current)
|
allOffsets := result.colorOffsets(charOffsets, nthOffsets, t.theme, colBase, colMatch, t.nthAttr)
|
||||||
|
|
||||||
maxLines := 1
|
maxLines := 1
|
||||||
if t.canSpanMultiLines() {
|
if t.canSpanMultiLines() {
|
||||||
@ -3058,13 +3162,20 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
|||||||
maxWidth := t.window.Width() - (indentSize + 1)
|
maxWidth := t.window.Width() - (indentSize + 1)
|
||||||
wasWrapped := false
|
wasWrapped := false
|
||||||
if wrapped {
|
if wrapped {
|
||||||
|
wrapSign := t.wrapSign
|
||||||
|
if maxWidth < t.wrapSignWidth {
|
||||||
|
runes, _ := util.Truncate(wrapSign, maxWidth)
|
||||||
|
wrapSign = string(runes)
|
||||||
|
maxWidth = 0
|
||||||
|
} else {
|
||||||
maxWidth -= t.wrapSignWidth
|
maxWidth -= t.wrapSignWidth
|
||||||
t.window.CPrint(colBase.WithAttr(tui.Dim), t.wrapSign)
|
}
|
||||||
|
t.window.CPrint(colBase.WithAttr(tui.Dim), wrapSign)
|
||||||
wrapped = false
|
wrapped = false
|
||||||
wasWrapped = true
|
wasWrapped = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(line) > 0 && line[len(line)-1] == '\n' {
|
if len(line) > 0 && line[len(line)-1] == '\n' && lineOffset < len(lines)-1 {
|
||||||
line = line[:len(line)-1]
|
line = line[:len(line)-1]
|
||||||
} else {
|
} else {
|
||||||
wrapped = true
|
wrapped = true
|
||||||
@ -3124,7 +3235,9 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
|||||||
displayWidth = t.displayWidthWithLimit(line, 0, displayWidth)
|
displayWidth = t.displayWidthWithLimit(line, 0, displayWidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if maxWidth > 0 {
|
||||||
t.printColoredString(t.window, line, offsets, colBase)
|
t.printColoredString(t.window, line, offsets, colBase)
|
||||||
|
}
|
||||||
if postTask != nil {
|
if postTask != nil {
|
||||||
postTask(actualLineNum, displayWidth, wasWrapped, forceRedraw)
|
postTask(actualLineNum, displayWidth, wasWrapped, forceRedraw)
|
||||||
} else {
|
} else {
|
||||||
@ -3333,8 +3446,10 @@ func (t *Terminal) renderPreviewText(height int, lines []string, lineNo int, unc
|
|||||||
wiped := false
|
wiped := false
|
||||||
image := false
|
image := false
|
||||||
wireframe := false
|
wireframe := false
|
||||||
|
var index int
|
||||||
|
var line string
|
||||||
Loop:
|
Loop:
|
||||||
for _, line := range lines {
|
for index, line = range lines {
|
||||||
var lbg tui.Color = -1
|
var lbg tui.Color = -1
|
||||||
if ansi != nil {
|
if ansi != nil {
|
||||||
ansi.lbg = -1
|
ansi.lbg = -1
|
||||||
@ -3477,6 +3592,7 @@ Loop:
|
|||||||
}
|
}
|
||||||
lineNo++
|
lineNo++
|
||||||
}
|
}
|
||||||
|
t.previewer.scrollable = t.previewer.scrollable || index < len(lines)-1
|
||||||
t.previewed.image = image
|
t.previewed.image = image
|
||||||
t.previewed.wireframe = wireframe
|
t.previewed.wireframe = wireframe
|
||||||
}
|
}
|
||||||
@ -3698,6 +3814,8 @@ func parsePlaceholder(match string) (bool, string, placeholderFlags) {
|
|||||||
flags.number = true
|
flags.number = true
|
||||||
case 'f':
|
case 'f':
|
||||||
flags.file = true
|
flags.file = true
|
||||||
|
case 'r':
|
||||||
|
flags.raw = true
|
||||||
case 'q':
|
case 'q':
|
||||||
flags.forceUpdate = true
|
flags.forceUpdate = true
|
||||||
trimmed += string(char)
|
trimmed += string(char)
|
||||||
@ -3825,7 +3943,7 @@ func replacePlaceholder(params replacePlaceholderParams) (string, []string) {
|
|||||||
elems, prefixLength := awkTokenizer(params.query)
|
elems, prefixLength := awkTokenizer(params.query)
|
||||||
tokens := withPrefixLengths(elems, prefixLength)
|
tokens := withPrefixLengths(elems, prefixLength)
|
||||||
trans := Transform(tokens, nth)
|
trans := Transform(tokens, nth)
|
||||||
result := joinTokens(trans)
|
result := JoinTokens(trans)
|
||||||
if !flags.preserveSpace {
|
if !flags.preserveSpace {
|
||||||
result = strings.TrimSpace(result)
|
result = strings.TrimSpace(result)
|
||||||
}
|
}
|
||||||
@ -3849,7 +3967,7 @@ func replacePlaceholder(params replacePlaceholderParams) (string, []string) {
|
|||||||
return "''"
|
return "''"
|
||||||
}
|
}
|
||||||
return strconv.Itoa(int(n))
|
return strconv.Itoa(int(n))
|
||||||
case flags.file:
|
case flags.file || flags.raw:
|
||||||
return item.AsString(params.stripAnsi)
|
return item.AsString(params.stripAnsi)
|
||||||
default:
|
default:
|
||||||
return params.executor.QuoteEntry(item.AsString(params.stripAnsi))
|
return params.executor.QuoteEntry(item.AsString(params.stripAnsi))
|
||||||
@ -3875,7 +3993,7 @@ func replacePlaceholder(params replacePlaceholderParams) (string, []string) {
|
|||||||
replace = func(item *Item) string {
|
replace = func(item *Item) string {
|
||||||
tokens := Tokenize(item.AsString(params.stripAnsi), params.delimiter)
|
tokens := Tokenize(item.AsString(params.stripAnsi), params.delimiter)
|
||||||
trans := Transform(tokens, ranges)
|
trans := Transform(tokens, ranges)
|
||||||
str := joinTokens(trans)
|
str := JoinTokens(trans)
|
||||||
|
|
||||||
// trim the last delimiter
|
// trim the last delimiter
|
||||||
if params.delimiter.str != nil {
|
if params.delimiter.str != nil {
|
||||||
@ -3891,7 +4009,7 @@ func replacePlaceholder(params replacePlaceholderParams) (string, []string) {
|
|||||||
if !flags.preserveSpace {
|
if !flags.preserveSpace {
|
||||||
str = strings.TrimSpace(str)
|
str = strings.TrimSpace(str)
|
||||||
}
|
}
|
||||||
if !flags.file {
|
if !flags.file && !flags.raw {
|
||||||
str = params.executor.QuoteEntry(str)
|
str = params.executor.QuoteEntry(str)
|
||||||
}
|
}
|
||||||
return str
|
return str
|
||||||
@ -3952,7 +4070,7 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
|||||||
t.executing.Set(true)
|
t.executing.Set(true)
|
||||||
if !background {
|
if !background {
|
||||||
// Open a separate handle for tty input
|
// Open a separate handle for tty input
|
||||||
if in, _ := tui.TtyIn(); in != nil {
|
if in, _ := tui.TtyIn(t.ttyDefault); in != nil {
|
||||||
cmd.Stdin = in
|
cmd.Stdin = in
|
||||||
if in != os.Stdin {
|
if in != os.Stdin {
|
||||||
defer in.Close()
|
defer in.Close()
|
||||||
@ -3961,7 +4079,7 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
|||||||
|
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
if !util.IsTty(os.Stdout) {
|
if !util.IsTty(os.Stdout) {
|
||||||
if out, _ := tui.TtyOut(); out != nil {
|
if out, _ := tui.TtyOut(t.ttyDefault); out != nil {
|
||||||
cmd.Stdout = out
|
cmd.Stdout = out
|
||||||
defer out.Close()
|
defer out.Close()
|
||||||
}
|
}
|
||||||
@ -3969,7 +4087,7 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
|||||||
|
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
if !util.IsTty(os.Stderr) {
|
if !util.IsTty(os.Stderr) {
|
||||||
if out, _ := tui.TtyOut(); out != nil {
|
if out, _ := tui.TtyOut(t.ttyDefault); out != nil {
|
||||||
cmd.Stderr = out
|
cmd.Stderr = out
|
||||||
defer out.Close()
|
defer out.Close()
|
||||||
}
|
}
|
||||||
@ -4335,6 +4453,7 @@ func (t *Terminal) Loop() error {
|
|||||||
var items []*Item
|
var items []*Item
|
||||||
var commandTemplate string
|
var commandTemplate string
|
||||||
var env []string
|
var env []string
|
||||||
|
var query string
|
||||||
initialOffset := 0
|
initialOffset := 0
|
||||||
t.previewBox.Wait(func(events *util.Events) {
|
t.previewBox.Wait(func(events *util.Events) {
|
||||||
for req, value := range *events {
|
for req, value := range *events {
|
||||||
@ -4348,6 +4467,7 @@ func (t *Terminal) Loop() error {
|
|||||||
initialOffset = request.scrollOffset
|
initialOffset = request.scrollOffset
|
||||||
items = request.list
|
items = request.list
|
||||||
env = request.env
|
env = request.env
|
||||||
|
query = request.query
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
events.Clear()
|
events.Clear()
|
||||||
@ -4361,8 +4481,7 @@ func (t *Terminal) Loop() error {
|
|||||||
version++
|
version++
|
||||||
// We don't display preview window if no match
|
// We don't display preview window if no match
|
||||||
if items[0] != nil {
|
if items[0] != nil {
|
||||||
_, query := t.Input()
|
command, tempFiles := t.replacePlaceholder(commandTemplate, false, query, items)
|
||||||
command, tempFiles := t.replacePlaceholder(commandTemplate, false, string(query), items)
|
|
||||||
cmd := t.executor.ExecCommand(command, true)
|
cmd := t.executor.ExecCommand(command, true)
|
||||||
cmd.Env = env
|
cmd.Env = env
|
||||||
|
|
||||||
@ -4490,7 +4609,7 @@ func (t *Terminal) Loop() error {
|
|||||||
if len(command) > 0 && t.canPreview() {
|
if len(command) > 0 && t.canPreview() {
|
||||||
_, list := t.buildPlusList(command, false)
|
_, list := t.buildPlusList(command, false)
|
||||||
t.cancelPreview()
|
t.cancelPreview()
|
||||||
t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.evaluateScrollOffset(), list, t.environForPreview()})
|
t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.evaluateScrollOffset(), list, t.environForPreview(), string(t.input)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4544,11 +4663,7 @@ func (t *Terminal) Loop() error {
|
|||||||
// U t.uiMutex |
|
// U t.uiMutex |
|
||||||
t.uiMutex.Lock()
|
t.uiMutex.Lock()
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
printInfo := util.RunOnce(func() {
|
info := false
|
||||||
if !t.resizeIfNeeded() {
|
|
||||||
t.printInfo()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
req := util.EventType(key)
|
req := util.EventType(key)
|
||||||
value := (*events)[req]
|
value := (*events)[req]
|
||||||
@ -4556,16 +4671,15 @@ func (t *Terminal) Loop() error {
|
|||||||
case reqPrompt:
|
case reqPrompt:
|
||||||
t.printPrompt()
|
t.printPrompt()
|
||||||
if t.infoStyle == infoInline || t.infoStyle == infoInlineRight {
|
if t.infoStyle == infoInline || t.infoStyle == infoInlineRight {
|
||||||
printInfo()
|
info = true
|
||||||
}
|
}
|
||||||
case reqInfo:
|
case reqInfo:
|
||||||
printInfo()
|
info = true
|
||||||
case reqList:
|
case reqList:
|
||||||
t.printList()
|
t.printList()
|
||||||
currentIndex := t.currentIndex()
|
currentIndex := t.currentIndex()
|
||||||
focusChanged := focusedIndex != currentIndex
|
focusChanged := focusedIndex != currentIndex
|
||||||
info := false
|
if focusChanged && focusedIndex >= 0 && t.track == trackCurrent {
|
||||||
if focusChanged && t.track == trackCurrent {
|
|
||||||
t.track = trackDisabled
|
t.track = trackDisabled
|
||||||
info = true
|
info = true
|
||||||
}
|
}
|
||||||
@ -4576,9 +4690,6 @@ func (t *Terminal) Loop() error {
|
|||||||
info = true
|
info = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if info {
|
|
||||||
printInfo()
|
|
||||||
}
|
|
||||||
if focusChanged || version != t.version {
|
if focusChanged || version != t.version {
|
||||||
version = t.version
|
version = t.version
|
||||||
focusedIndex = currentIndex
|
focusedIndex = currentIndex
|
||||||
@ -4670,6 +4781,9 @@ func (t *Terminal) Loop() error {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if info && !t.resizeIfNeeded() {
|
||||||
|
t.printInfo()
|
||||||
|
}
|
||||||
t.flush()
|
t.flush()
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
t.uiMutex.Unlock()
|
t.uiMutex.Unlock()
|
||||||
@ -4719,6 +4833,7 @@ func (t *Terminal) Loop() error {
|
|||||||
changed := false
|
changed := false
|
||||||
beof := false
|
beof := false
|
||||||
queryChanged := false
|
queryChanged := false
|
||||||
|
denylist := []int32{}
|
||||||
|
|
||||||
// Special handling of --sync. Activate the interface on the second tick.
|
// Special handling of --sync. Activate the interface on the second tick.
|
||||||
if loopIndex == 1 && t.deferActivation() {
|
if loopIndex == 1 && t.deferActivation() {
|
||||||
@ -4852,6 +4967,14 @@ func (t *Terminal) Loop() error {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
doAction = func(a *action) bool {
|
doAction = func(a *action) bool {
|
||||||
|
// Keep track of the current query before the action is executed,
|
||||||
|
// so we can restore it when the input section is hidden (--no-input).
|
||||||
|
// * By doing this, we don't have to add a conditional branch to each
|
||||||
|
// query modifying action.
|
||||||
|
// * We restore the query after each action instead of after a set of
|
||||||
|
// actions to allow changing the query even when the input is hidden
|
||||||
|
// e.g. fzf --no-input --bind 'space:show-input+change-query(foo)+hide-input'
|
||||||
|
currentInput := t.input
|
||||||
Action:
|
Action:
|
||||||
switch a.t {
|
switch a.t {
|
||||||
case actIgnore, actStart, actClick:
|
case actIgnore, actStart, actClick:
|
||||||
@ -4875,6 +4998,27 @@ func (t *Terminal) Loop() error {
|
|||||||
}
|
}
|
||||||
case actBell:
|
case actBell:
|
||||||
t.tui.Bell()
|
t.tui.Bell()
|
||||||
|
case actExcludeMulti:
|
||||||
|
if len(t.selected) > 0 {
|
||||||
|
for _, item := range t.sortSelected() {
|
||||||
|
denylist = append(denylist, item.item.Index())
|
||||||
|
}
|
||||||
|
// Clear selected items
|
||||||
|
t.selected = make(map[int32]selectedItem)
|
||||||
|
t.version++
|
||||||
|
} else {
|
||||||
|
item := t.currentItem()
|
||||||
|
if item != nil {
|
||||||
|
denylist = append(denylist, item.Index())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changed = true
|
||||||
|
case actExclude:
|
||||||
|
if item := t.currentItem(); item != nil {
|
||||||
|
denylist = append(denylist, item.Index())
|
||||||
|
t.deselectItem(item)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
case actExecute, actExecuteSilent:
|
case actExecute, actExecuteSilent:
|
||||||
t.executeCommand(a.a, false, a.t == actExecuteSilent, false, false, "")
|
t.executeCommand(a.a, false, a.t == actExecuteSilent, false, false, "")
|
||||||
case actExecuteMulti:
|
case actExecuteMulti:
|
||||||
@ -4882,6 +5026,14 @@ func (t *Terminal) Loop() error {
|
|||||||
case actInvalid:
|
case actInvalid:
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
return false
|
return false
|
||||||
|
case actBracketedPasteBegin:
|
||||||
|
current := []rune(t.input)
|
||||||
|
t.pasting = ¤t
|
||||||
|
case actBracketedPasteEnd:
|
||||||
|
if t.pasting != nil {
|
||||||
|
queryChanged = string(t.input) != string(*t.pasting)
|
||||||
|
t.pasting = nil
|
||||||
|
}
|
||||||
case actTogglePreview, actShowPreview, actHidePreview:
|
case actTogglePreview, actShowPreview, actHidePreview:
|
||||||
var act bool
|
var act bool
|
||||||
switch a.t {
|
switch a.t {
|
||||||
@ -4900,7 +5052,7 @@ func (t *Terminal) Loop() error {
|
|||||||
if valid {
|
if valid {
|
||||||
t.cancelPreview()
|
t.cancelPreview()
|
||||||
t.previewBox.Set(reqPreviewEnqueue,
|
t.previewBox.Set(reqPreviewEnqueue,
|
||||||
previewRequest{t.previewOpts.command, t.evaluateScrollOffset(), list, t.environForPreview()})
|
previewRequest{t.previewOpts.command, t.evaluateScrollOffset(), list, t.environForPreview(), string(t.input)})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Discard the preview content so that it won't accidentally appear
|
// Discard the preview content so that it won't accidentally appear
|
||||||
@ -5017,38 +5169,61 @@ func (t *Terminal) Loop() error {
|
|||||||
header = t.captureLines(a.a)
|
header = t.captureLines(a.a)
|
||||||
}
|
}
|
||||||
if t.changeHeader(header) {
|
if t.changeHeader(header) {
|
||||||
|
if t.headerWindow != nil {
|
||||||
|
// Need to resize header window
|
||||||
|
req(reqFullRedraw)
|
||||||
|
} else {
|
||||||
req(reqHeader, reqList, reqPrompt, reqInfo)
|
req(reqHeader, reqList, reqPrompt, reqInfo)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
req(reqHeader)
|
req(reqHeader)
|
||||||
}
|
}
|
||||||
case actChangeHeaderLabel:
|
case actChangeHeaderLabel, actTransformHeaderLabel:
|
||||||
t.headerLabelOpts.label = a.a
|
label := a.a
|
||||||
if t.headerBorder != nil {
|
if a.t == actTransformHeaderLabel {
|
||||||
t.headerLabel, t.headerLabelLen = t.ansiLabelPrinter(a.a, &tui.ColHeaderLabel, false)
|
label = t.captureLine(a.a)
|
||||||
req(reqRedrawHeaderLabel)
|
|
||||||
}
|
}
|
||||||
case actChangeInputLabel:
|
t.headerLabelOpts.label = label
|
||||||
t.inputLabelOpts.label = a.a
|
t.headerLabel, t.headerLabelLen = t.ansiLabelPrinter(label, &tui.ColHeaderLabel, false)
|
||||||
|
req(reqRedrawHeaderLabel)
|
||||||
|
case actChangeInputLabel, actTransformInputLabel:
|
||||||
|
label := a.a
|
||||||
|
if a.t == actTransformInputLabel {
|
||||||
|
label = t.captureLine(a.a)
|
||||||
|
}
|
||||||
|
t.inputLabelOpts.label = label
|
||||||
if t.inputBorder != nil {
|
if t.inputBorder != nil {
|
||||||
t.inputLabel, t.inputLabelLen = t.ansiLabelPrinter(a.a, &tui.ColInputLabel, false)
|
t.inputLabel, t.inputLabelLen = t.ansiLabelPrinter(label, &tui.ColInputLabel, false)
|
||||||
req(reqRedrawInputLabel)
|
req(reqRedrawInputLabel)
|
||||||
}
|
}
|
||||||
case actChangeListLabel:
|
case actChangeListLabel, actTransformListLabel:
|
||||||
t.listLabelOpts.label = a.a
|
label := a.a
|
||||||
|
if a.t == actTransformListLabel {
|
||||||
|
label = t.captureLine(a.a)
|
||||||
|
}
|
||||||
|
t.listLabelOpts.label = label
|
||||||
if t.wborder != nil {
|
if t.wborder != nil {
|
||||||
t.listLabel, t.listLabelLen = t.ansiLabelPrinter(a.a, &tui.ColListLabel, false)
|
t.listLabel, t.listLabelLen = t.ansiLabelPrinter(label, &tui.ColListLabel, false)
|
||||||
req(reqRedrawListLabel)
|
req(reqRedrawListLabel)
|
||||||
}
|
}
|
||||||
case actChangeBorderLabel:
|
case actChangeBorderLabel, actTransformBorderLabel:
|
||||||
t.borderLabelOpts.label = a.a
|
label := a.a
|
||||||
|
if a.t == actTransformBorderLabel {
|
||||||
|
label = t.captureLine(a.a)
|
||||||
|
}
|
||||||
|
t.borderLabelOpts.label = label
|
||||||
if t.border != nil {
|
if t.border != nil {
|
||||||
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(a.a, &tui.ColBorderLabel, false)
|
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(label, &tui.ColBorderLabel, false)
|
||||||
req(reqRedrawBorderLabel)
|
req(reqRedrawBorderLabel)
|
||||||
}
|
}
|
||||||
case actChangePreviewLabel:
|
case actChangePreviewLabel, actTransformPreviewLabel:
|
||||||
t.previewLabelOpts.label = a.a
|
label := a.a
|
||||||
|
if a.t == actTransformPreviewLabel {
|
||||||
|
label = t.captureLine(a.a)
|
||||||
|
}
|
||||||
|
t.previewLabelOpts.label = label
|
||||||
if t.pborder != nil {
|
if t.pborder != nil {
|
||||||
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(a.a, &tui.ColPreviewLabel, false)
|
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(label, &tui.ColPreviewLabel, false)
|
||||||
req(reqRedrawPreviewLabel)
|
req(reqRedrawPreviewLabel)
|
||||||
}
|
}
|
||||||
case actTransform:
|
case actTransform:
|
||||||
@ -5056,41 +5231,6 @@ func (t *Terminal) Loop() error {
|
|||||||
if actions, err := parseSingleActionList(strings.Trim(body, "\r\n")); err == nil {
|
if actions, err := parseSingleActionList(strings.Trim(body, "\r\n")); err == nil {
|
||||||
return doActions(actions)
|
return doActions(actions)
|
||||||
}
|
}
|
||||||
case actTransformHeaderLabel:
|
|
||||||
label := t.captureLine(a.a)
|
|
||||||
t.headerLabelOpts.label = label
|
|
||||||
if t.headerBorder != nil {
|
|
||||||
t.headerLabel, t.headerLabelLen = t.ansiLabelPrinter(label, &tui.ColHeaderLabel, false)
|
|
||||||
req(reqRedrawHeaderLabel)
|
|
||||||
}
|
|
||||||
case actTransformInputLabel:
|
|
||||||
label := t.captureLine(a.a)
|
|
||||||
t.inputLabelOpts.label = label
|
|
||||||
if t.inputBorder != nil {
|
|
||||||
t.inputLabel, t.inputLabelLen = t.ansiLabelPrinter(label, &tui.ColInputLabel, false)
|
|
||||||
req(reqRedrawInputLabel)
|
|
||||||
}
|
|
||||||
case actTransformListLabel:
|
|
||||||
label := t.captureLine(a.a)
|
|
||||||
t.listLabelOpts.label = label
|
|
||||||
if t.wborder != nil {
|
|
||||||
t.listLabel, t.listLabelLen = t.ansiLabelPrinter(label, &tui.ColListLabel, false)
|
|
||||||
req(reqRedrawListLabel)
|
|
||||||
}
|
|
||||||
case actTransformBorderLabel:
|
|
||||||
label := t.captureLine(a.a)
|
|
||||||
t.borderLabelOpts.label = label
|
|
||||||
if t.border != nil {
|
|
||||||
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(label, &tui.ColBorderLabel, false)
|
|
||||||
req(reqRedrawBorderLabel)
|
|
||||||
}
|
|
||||||
case actTransformPreviewLabel:
|
|
||||||
label := t.captureLine(a.a)
|
|
||||||
t.previewLabelOpts.label = label
|
|
||||||
if t.pborder != nil {
|
|
||||||
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(label, &tui.ColPreviewLabel, false)
|
|
||||||
req(reqRedrawPreviewLabel)
|
|
||||||
}
|
|
||||||
case actChangePrompt:
|
case actChangePrompt:
|
||||||
t.promptString = a.a
|
t.promptString = a.a
|
||||||
t.prompt, t.promptLen = t.parsePrompt(a.a)
|
t.prompt, t.promptLen = t.parsePrompt(a.a)
|
||||||
@ -5395,6 +5535,7 @@ func (t *Terminal) Loop() error {
|
|||||||
t.scrollOff = t.window.Height()
|
t.scrollOff = t.window.Height()
|
||||||
t.constrain()
|
t.constrain()
|
||||||
t.scrollOff = soff
|
t.scrollOff = soff
|
||||||
|
req(reqList)
|
||||||
case actJump:
|
case actJump:
|
||||||
t.jumping = jumpEnabled
|
t.jumping = jumpEnabled
|
||||||
req(reqJump)
|
req(reqJump)
|
||||||
@ -5464,9 +5605,11 @@ func (t *Terminal) Loop() error {
|
|||||||
req(reqList, reqInfo, reqPrompt, reqHeader)
|
req(reqList, reqInfo, reqPrompt, reqHeader)
|
||||||
case actToggleWrap:
|
case actToggleWrap:
|
||||||
t.wrap = !t.wrap
|
t.wrap = !t.wrap
|
||||||
|
t.clearNumLinesCache()
|
||||||
req(reqList, reqHeader)
|
req(reqList, reqHeader)
|
||||||
case actToggleMultiLine:
|
case actToggleMultiLine:
|
||||||
t.multiLine = !t.multiLine
|
t.multiLine = !t.multiLine
|
||||||
|
t.clearNumLinesCache()
|
||||||
req(reqList)
|
req(reqList)
|
||||||
case actToggleHscroll:
|
case actToggleHscroll:
|
||||||
// Force re-rendering of the list
|
// Force re-rendering of the list
|
||||||
@ -5767,7 +5910,7 @@ func (t *Terminal) Loop() error {
|
|||||||
|
|
||||||
if me.Down {
|
if me.Down {
|
||||||
mxCons := util.Constrain(mx-t.promptLen, 0, len(t.input))
|
mxCons := util.Constrain(mx-t.promptLen, 0, len(t.input))
|
||||||
if t.inputWindow == nil && my == t.promptLine() && mxCons >= 0 {
|
if !t.inputless && t.inputWindow == nil && my == t.promptLine() && mxCons >= 0 {
|
||||||
// Prompt
|
// Prompt
|
||||||
t.cx = mxCons + t.xoffset
|
t.cx = mxCons + t.xoffset
|
||||||
} else if my >= min {
|
} else if my >= min {
|
||||||
@ -5851,6 +5994,30 @@ func (t *Terminal) Loop() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case actChangeGhost, actTransformGhost:
|
||||||
|
ghost := a.a
|
||||||
|
if a.t == actTransformGhost {
|
||||||
|
ghost = t.captureLine(a.a)
|
||||||
|
}
|
||||||
|
t.ghost = ghost
|
||||||
|
if len(t.input) == 0 {
|
||||||
|
req(reqPrompt)
|
||||||
|
}
|
||||||
|
case actChangePointer, actTransformPointer:
|
||||||
|
pointer := a.a
|
||||||
|
if a.t == actTransformPointer {
|
||||||
|
pointer = t.captureLine(a.a)
|
||||||
|
}
|
||||||
|
length := uniseg.StringWidth(pointer)
|
||||||
|
if length <= 2 {
|
||||||
|
if length != t.pointerLen {
|
||||||
|
t.forceRerenderList()
|
||||||
|
}
|
||||||
|
t.pointer = pointer
|
||||||
|
t.pointerLen = length
|
||||||
|
t.pointerEmpty = strings.Repeat(" ", t.pointerLen)
|
||||||
|
req(reqList)
|
||||||
|
}
|
||||||
case actChangePreview:
|
case actChangePreview:
|
||||||
if t.previewOpts.command != a.a {
|
if t.previewOpts.command != a.a {
|
||||||
t.previewOpts.command = a.a
|
t.previewOpts.command = a.a
|
||||||
@ -5928,6 +6095,15 @@ func (t *Terminal) Loop() error {
|
|||||||
if !processExecution(a.t) {
|
if !processExecution(a.t) {
|
||||||
t.lastAction = a.t
|
t.lastAction = a.t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if t.inputless {
|
||||||
|
// Always just discard the change
|
||||||
|
t.input = currentInput
|
||||||
|
t.cx = len(t.input)
|
||||||
|
beof = false
|
||||||
|
} else if string(t.input) != string(currentInput) {
|
||||||
|
t.inputOverride = nil
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5948,18 +6124,10 @@ func (t *Terminal) Loop() error {
|
|||||||
} else if !doActions(actions) {
|
} else if !doActions(actions) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if t.inputless {
|
if !t.inputless {
|
||||||
// Always just discard the change
|
|
||||||
t.input = previousInput
|
|
||||||
t.cx = len(t.input)
|
|
||||||
beof = false
|
|
||||||
} else {
|
|
||||||
t.truncateQuery()
|
t.truncateQuery()
|
||||||
}
|
}
|
||||||
queryChanged = string(previousInput) != string(t.input)
|
queryChanged = queryChanged || t.pasting == nil && string(previousInput) != string(t.input)
|
||||||
if queryChanged {
|
|
||||||
t.inputOverride = nil
|
|
||||||
}
|
|
||||||
changed = changed || queryChanged
|
changed = changed || queryChanged
|
||||||
if onChanges, prs := t.keymap[tui.Change.AsEvent()]; queryChanged && prs && !doActions(onChanges) {
|
if onChanges, prs := t.keymap[tui.Change.AsEvent()]; queryChanged && prs && !doActions(onChanges) {
|
||||||
continue
|
continue
|
||||||
@ -5999,7 +6167,7 @@ func (t *Terminal) Loop() error {
|
|||||||
reload := changed || newCommand != nil
|
reload := changed || newCommand != nil
|
||||||
var reloadRequest *searchRequest
|
var reloadRequest *searchRequest
|
||||||
if reload {
|
if reload {
|
||||||
reloadRequest = &searchRequest{sort: t.sort, sync: reloadSync, nth: newNth, command: newCommand, environ: t.environ(), changed: changed}
|
reloadRequest = &searchRequest{sort: t.sort, sync: reloadSync, nth: newNth, command: newCommand, environ: t.environ(), changed: changed, denylist: denylist, revision: t.merger.Revision()}
|
||||||
}
|
}
|
||||||
t.mutex.Unlock() // Must be unlocked before touching reqBox
|
t.mutex.Unlock() // Must be unlocked before touching reqBox
|
||||||
|
|
||||||
|
@ -75,6 +75,14 @@ func TestReplacePlaceholder(t *testing.T) {
|
|||||||
result = replacePlaceholderTest("echo {}", true, Delimiter{}, printsep, false, "query", items1)
|
result = replacePlaceholderTest("echo {}", true, Delimiter{}, printsep, false, "query", items1)
|
||||||
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}")
|
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}")
|
||||||
|
|
||||||
|
// {r}, strip ansi
|
||||||
|
result = replacePlaceholderTest("echo {r}", true, Delimiter{}, printsep, false, "query", items1)
|
||||||
|
checkFormat("echo foo'bar baz")
|
||||||
|
|
||||||
|
// {r..}, strip ansi
|
||||||
|
result = replacePlaceholderTest("echo {r..}", true, Delimiter{}, printsep, false, "query", items1)
|
||||||
|
checkFormat("echo foo'bar baz")
|
||||||
|
|
||||||
// {}, with multiple items
|
// {}, with multiple items
|
||||||
result = replacePlaceholderTest("echo {}", true, Delimiter{}, printsep, false, "query", items2)
|
result = replacePlaceholderTest("echo {}", true, Delimiter{}, printsep, false, "query", items2)
|
||||||
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}}")
|
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}}")
|
||||||
@ -565,7 +573,7 @@ func (item *Item) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to parse, execute and convert "text/template" to string. Panics on error.
|
// Helper function to parse, execute and convert "text/template" to string. Panics on error.
|
||||||
func templateToString(format string, data interface{}) string {
|
func templateToString(format string, data any) string {
|
||||||
bb := &bytes.Buffer{}
|
bb := &bytes.Buffer{}
|
||||||
|
|
||||||
err := template.Must(template.New("").Parse(format)).Execute(bb, data)
|
err := template.Must(template.New("").Parse(format)).Execute(bb, data)
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
@ -77,6 +78,11 @@ type Delimiter struct {
|
|||||||
str *string
|
str *string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsAwk returns true if the delimiter is an AWK-style delimiter
|
||||||
|
func (d Delimiter) IsAwk() bool {
|
||||||
|
return d.regex == nil && d.str == nil
|
||||||
|
}
|
||||||
|
|
||||||
// String returns the string representation of a Delimiter.
|
// String returns the string representation of a Delimiter.
|
||||||
func (d Delimiter) String() string {
|
func (d Delimiter) String() string {
|
||||||
return fmt.Sprintf("Delimiter{regex: %v, str: &%q}", d.regex, *d.str)
|
return fmt.Sprintf("Delimiter{regex: %v, str: &%q}", d.regex, *d.str)
|
||||||
@ -211,7 +217,24 @@ func Tokenize(text string, delimiter Delimiter) []Token {
|
|||||||
return withPrefixLengths(tokens, 0)
|
return withPrefixLengths(tokens, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func joinTokens(tokens []Token) string {
|
// StripLastDelimiter removes the trailing delimiter and whitespaces
|
||||||
|
func StripLastDelimiter(str string, delimiter Delimiter) string {
|
||||||
|
if delimiter.str != nil {
|
||||||
|
str = strings.TrimSuffix(str, *delimiter.str)
|
||||||
|
} else if delimiter.regex != nil {
|
||||||
|
locs := delimiter.regex.FindAllStringIndex(str, -1)
|
||||||
|
if len(locs) > 0 {
|
||||||
|
lastLoc := locs[len(locs)-1]
|
||||||
|
if lastLoc[1] == len(str) {
|
||||||
|
str = str[:lastLoc[0]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.TrimRightFunc(str, unicode.IsSpace)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JoinTokens concatenates the tokens into a single string
|
||||||
|
func JoinTokens(tokens []Token) string {
|
||||||
var output bytes.Buffer
|
var output bytes.Buffer
|
||||||
for _, token := range tokens {
|
for _, token := range tokens {
|
||||||
output.WriteString(token.text.ToString())
|
output.WriteString(token.text.ToString())
|
||||||
@ -229,7 +252,7 @@ func Transform(tokens []Token, withNth []Range) []Token {
|
|||||||
if r.begin == r.end {
|
if r.begin == r.end {
|
||||||
idx := r.begin
|
idx := r.begin
|
||||||
if idx == rangeEllipsis {
|
if idx == rangeEllipsis {
|
||||||
chars := util.ToChars(stringBytes(joinTokens(tokens)))
|
chars := util.ToChars(stringBytes(JoinTokens(tokens)))
|
||||||
parts = append(parts, &chars)
|
parts = append(parts, &chars)
|
||||||
} else {
|
} else {
|
||||||
if idx < 0 {
|
if idx < 0 {
|
||||||
|
@ -85,14 +85,14 @@ func TestTransform(t *testing.T) {
|
|||||||
{
|
{
|
||||||
ranges, _ := splitNth("1,2,3")
|
ranges, _ := splitNth("1,2,3")
|
||||||
tx := Transform(tokens, ranges)
|
tx := Transform(tokens, ranges)
|
||||||
if joinTokens(tx) != "abc: def: ghi: " {
|
if JoinTokens(tx) != "abc: def: ghi: " {
|
||||||
t.Errorf("%s", tx)
|
t.Errorf("%s", tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
ranges, _ := splitNth("1..2,3,2..,1")
|
ranges, _ := splitNth("1..2,3,2..,1")
|
||||||
tx := Transform(tokens, ranges)
|
tx := Transform(tokens, ranges)
|
||||||
if string(joinTokens(tx)) != "abc: def: ghi: def: ghi: jklabc: " ||
|
if string(JoinTokens(tx)) != "abc: def: ghi: def: ghi: jklabc: " ||
|
||||||
len(tx) != 4 ||
|
len(tx) != 4 ||
|
||||||
tx[0].text.ToString() != "abc: def: " || tx[0].prefixLength != 2 ||
|
tx[0].text.ToString() != "abc: def: " || tx[0].prefixLength != 2 ||
|
||||||
tx[1].text.ToString() != "ghi: " || tx[1].prefixLength != 14 ||
|
tx[1].text.ToString() != "ghi: " || tx[1].prefixLength != 14 ||
|
||||||
@ -107,7 +107,7 @@ func TestTransform(t *testing.T) {
|
|||||||
{
|
{
|
||||||
ranges, _ := splitNth("1..2,3,2..,1")
|
ranges, _ := splitNth("1..2,3,2..,1")
|
||||||
tx := Transform(tokens, ranges)
|
tx := Transform(tokens, ranges)
|
||||||
if joinTokens(tx) != " abc: def: ghi: def: ghi: jkl abc:" ||
|
if JoinTokens(tx) != " abc: def: ghi: def: ghi: jkl abc:" ||
|
||||||
len(tx) != 4 ||
|
len(tx) != 4 ||
|
||||||
tx[0].text.ToString() != " abc: def:" || tx[0].prefixLength != 0 ||
|
tx[0].text.ToString() != " abc: def:" || tx[0].prefixLength != 0 ||
|
||||||
tx[1].text.ToString() != " ghi:" || tx[1].prefixLength != 12 ||
|
tx[1].text.ToString() != " ghi:" || tx[1].prefixLength != 12 ||
|
||||||
|
@ -84,35 +84,37 @@ func _() {
|
|||||||
_ = x[CtrlAlt-73]
|
_ = x[CtrlAlt-73]
|
||||||
_ = x[Invalid-74]
|
_ = x[Invalid-74]
|
||||||
_ = x[Fatal-75]
|
_ = x[Fatal-75]
|
||||||
_ = x[Mouse-76]
|
_ = x[BracketedPasteBegin-76]
|
||||||
_ = x[DoubleClick-77]
|
_ = x[BracketedPasteEnd-77]
|
||||||
_ = x[LeftClick-78]
|
_ = x[Mouse-78]
|
||||||
_ = x[RightClick-79]
|
_ = x[DoubleClick-79]
|
||||||
_ = x[SLeftClick-80]
|
_ = x[LeftClick-80]
|
||||||
_ = x[SRightClick-81]
|
_ = x[RightClick-81]
|
||||||
_ = x[ScrollUp-82]
|
_ = x[SLeftClick-82]
|
||||||
_ = x[ScrollDown-83]
|
_ = x[SRightClick-83]
|
||||||
_ = x[SScrollUp-84]
|
_ = x[ScrollUp-84]
|
||||||
_ = x[SScrollDown-85]
|
_ = x[ScrollDown-85]
|
||||||
_ = x[PreviewScrollUp-86]
|
_ = x[SScrollUp-86]
|
||||||
_ = x[PreviewScrollDown-87]
|
_ = x[SScrollDown-87]
|
||||||
_ = x[Resize-88]
|
_ = x[PreviewScrollUp-88]
|
||||||
_ = x[Change-89]
|
_ = x[PreviewScrollDown-89]
|
||||||
_ = x[BackwardEOF-90]
|
_ = x[Resize-90]
|
||||||
_ = x[Start-91]
|
_ = x[Change-91]
|
||||||
_ = x[Load-92]
|
_ = x[BackwardEOF-92]
|
||||||
_ = x[Focus-93]
|
_ = x[Start-93]
|
||||||
_ = x[One-94]
|
_ = x[Load-94]
|
||||||
_ = x[Zero-95]
|
_ = x[Focus-95]
|
||||||
_ = x[Result-96]
|
_ = x[One-96]
|
||||||
_ = x[Jump-97]
|
_ = x[Zero-97]
|
||||||
_ = x[JumpCancel-98]
|
_ = x[Result-98]
|
||||||
_ = x[ClickHeader-99]
|
_ = x[Jump-99]
|
||||||
|
_ = x[JumpCancel-100]
|
||||||
|
_ = x[ClickHeader-101]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLEnterCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlDeleteCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltCtrlAltInvalidFatalMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancelClickHeader"
|
const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLEnterCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlDeleteCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltCtrlAltInvalidFatalBracketedPasteBeginBracketedPasteEndMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancelClickHeader"
|
||||||
|
|
||||||
var _EventType_index = [...]uint16{0, 4, 9, 14, 19, 24, 29, 34, 39, 44, 47, 52, 57, 62, 67, 72, 77, 82, 87, 92, 97, 102, 107, 112, 117, 122, 127, 132, 135, 144, 154, 167, 183, 192, 201, 209, 218, 224, 230, 238, 240, 244, 248, 253, 257, 260, 266, 273, 282, 291, 301, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 333, 336, 339, 351, 356, 363, 370, 378, 388, 400, 412, 425, 428, 435, 442, 447, 452, 463, 472, 482, 492, 503, 511, 521, 530, 541, 556, 573, 579, 585, 596, 601, 605, 610, 613, 617, 623, 627, 637, 648}
|
var _EventType_index = [...]uint16{0, 4, 9, 14, 19, 24, 29, 34, 39, 44, 47, 52, 57, 62, 67, 72, 77, 82, 87, 92, 97, 102, 107, 112, 117, 122, 127, 132, 135, 144, 154, 167, 183, 192, 201, 209, 218, 224, 230, 238, 240, 244, 248, 253, 257, 260, 266, 273, 282, 291, 301, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 333, 336, 339, 351, 356, 363, 370, 378, 388, 400, 412, 425, 428, 435, 442, 447, 466, 483, 488, 499, 508, 518, 528, 539, 547, 557, 566, 577, 592, 609, 615, 621, 632, 637, 641, 646, 649, 653, 659, 663, 673, 684}
|
||||||
|
|
||||||
func (i EventType) String() string {
|
func (i EventType) String() string {
|
||||||
if i < 0 || i >= EventType(len(_EventType_index)-1) {
|
if i < 0 || i >= EventType(len(_EventType_index)-1) {
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
@ -27,9 +28,9 @@ const (
|
|||||||
maxInputBuffer = 1024 * 1024
|
maxInputBuffer = 1024 * 1024
|
||||||
)
|
)
|
||||||
|
|
||||||
const consoleDevice string = "/dev/tty"
|
const DefaultTtyDevice string = "/dev/tty"
|
||||||
|
|
||||||
var offsetRegexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R")
|
var offsetRegexp = regexp.MustCompile("(.*?)\x00?\x1b\\[([0-9]+);([0-9]+)R")
|
||||||
var offsetRegexpBegin = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R")
|
var offsetRegexpBegin = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R")
|
||||||
|
|
||||||
func (r *LightRenderer) Bell() {
|
func (r *LightRenderer) Bell() {
|
||||||
@ -44,8 +45,9 @@ func (r *LightRenderer) stderr(str string) {
|
|||||||
r.stderrInternal(str, true, "")
|
r.stderrInternal(str, true, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
const CR string = "\x1b[2m␍"
|
const DIM string = "\x1b[2m"
|
||||||
const LF string = "\x1b[2m␊"
|
const CR string = DIM + "␍"
|
||||||
|
const LF string = DIM + "␊"
|
||||||
|
|
||||||
func (r *LightRenderer) stderrInternal(str string, allowNLCR bool, resetCode string) {
|
func (r *LightRenderer) stderrInternal(str string, allowNLCR bool, resetCode string) {
|
||||||
bytes := []byte(str)
|
bytes := []byte(str)
|
||||||
@ -94,7 +96,6 @@ func (r *LightRenderer) flushRaw(sequence string) {
|
|||||||
|
|
||||||
// Light renderer
|
// Light renderer
|
||||||
type LightRenderer struct {
|
type LightRenderer struct {
|
||||||
closed *util.AtomicBool
|
|
||||||
theme *ColorTheme
|
theme *ColorTheme
|
||||||
mouse bool
|
mouse bool
|
||||||
forceBlack bool
|
forceBlack bool
|
||||||
@ -119,6 +120,7 @@ type LightRenderer struct {
|
|||||||
showCursor bool
|
showCursor bool
|
||||||
|
|
||||||
// Windows only
|
// Windows only
|
||||||
|
mutex sync.Mutex
|
||||||
ttyinChannel chan byte
|
ttyinChannel chan byte
|
||||||
inHandle uintptr
|
inHandle uintptr
|
||||||
outHandle uintptr
|
outHandle uintptr
|
||||||
@ -140,15 +142,16 @@ type LightWindow struct {
|
|||||||
tabstop int
|
tabstop int
|
||||||
fg Color
|
fg Color
|
||||||
bg Color
|
bg Color
|
||||||
|
wrapSign string
|
||||||
|
wrapSignWidth int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLightRenderer(ttyin *os.File, theme *ColorTheme, forceBlack bool, mouse bool, tabstop int, clearOnExit bool, fullscreen bool, maxHeightFunc func(int) int) (Renderer, error) {
|
func NewLightRenderer(ttyDefault string, ttyin *os.File, theme *ColorTheme, forceBlack bool, mouse bool, tabstop int, clearOnExit bool, fullscreen bool, maxHeightFunc func(int) int) (Renderer, error) {
|
||||||
out, err := openTtyOut()
|
out, err := openTtyOut(ttyDefault)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
out = os.Stderr
|
out = os.Stderr
|
||||||
}
|
}
|
||||||
r := LightRenderer{
|
r := LightRenderer{
|
||||||
closed: util.NewAtomicBool(false),
|
|
||||||
theme: theme,
|
theme: theme,
|
||||||
forceBlack: forceBlack,
|
forceBlack: forceBlack,
|
||||||
mouse: mouse,
|
mouse: mouse,
|
||||||
@ -210,7 +213,7 @@ func (r *LightRenderer) Init() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
r.enableMouse()
|
r.enableModes()
|
||||||
r.csi(fmt.Sprintf("%dA", r.MaxY()-1))
|
r.csi(fmt.Sprintf("%dA", r.MaxY()-1))
|
||||||
r.csi("G")
|
r.csi("G")
|
||||||
r.csi("K")
|
r.csi("K")
|
||||||
@ -268,7 +271,7 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) ([]byte,
|
|||||||
c, ok := r.getch(nonblock)
|
c, ok := r.getch(nonblock)
|
||||||
if !nonblock && !ok {
|
if !nonblock && !ok {
|
||||||
r.Close()
|
r.Close()
|
||||||
return nil, errors.New("failed to read " + consoleDevice)
|
return nil, errors.New("failed to read " + DefaultTtyDevice)
|
||||||
}
|
}
|
||||||
|
|
||||||
retries := 0
|
retries := 0
|
||||||
@ -459,10 +462,11 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
}
|
}
|
||||||
// Bracketed paste mode: \e[200~ ... \e[201~
|
// Bracketed paste mode: \e[200~ ... \e[201~
|
||||||
if len(r.buffer) > 5 && r.buffer[3] == '0' && (r.buffer[4] == '0' || r.buffer[4] == '1') && r.buffer[5] == '~' {
|
if len(r.buffer) > 5 && r.buffer[3] == '0' && (r.buffer[4] == '0' || r.buffer[4] == '1') && r.buffer[5] == '~' {
|
||||||
// Immediately discard the sequence from the buffer and reread input
|
*sz = 6
|
||||||
r.buffer = r.buffer[6:]
|
if r.buffer[4] == '0' {
|
||||||
*sz = 0
|
return Event{BracketedPasteBegin, 0, nil}
|
||||||
return r.GetChar()
|
}
|
||||||
|
return Event{BracketedPasteEnd, 0, nil}
|
||||||
}
|
}
|
||||||
return Event{Invalid, 0, nil} // INS
|
return Event{Invalid, 0, nil} // INS
|
||||||
case '3':
|
case '3':
|
||||||
@ -678,7 +682,7 @@ func (r *LightRenderer) rmcup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Pause(clear bool) {
|
func (r *LightRenderer) Pause(clear bool) {
|
||||||
r.disableMouse()
|
r.disableModes()
|
||||||
r.restoreTerminal()
|
r.restoreTerminal()
|
||||||
if clear {
|
if clear {
|
||||||
if r.fullscreen {
|
if r.fullscreen {
|
||||||
@ -691,12 +695,13 @@ func (r *LightRenderer) Pause(clear bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) enableMouse() {
|
func (r *LightRenderer) enableModes() {
|
||||||
if r.mouse {
|
if r.mouse {
|
||||||
r.csi("?1000h")
|
r.csi("?1000h")
|
||||||
r.csi("?1002h")
|
r.csi("?1002h")
|
||||||
r.csi("?1006h")
|
r.csi("?1006h")
|
||||||
}
|
}
|
||||||
|
r.csi("?2004h") // Enable bracketed paste mode
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) disableMouse() {
|
func (r *LightRenderer) disableMouse() {
|
||||||
@ -707,6 +712,11 @@ func (r *LightRenderer) disableMouse() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) disableModes() {
|
||||||
|
r.disableMouse()
|
||||||
|
r.csi("?2004l")
|
||||||
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Resume(clear bool, sigcont bool) {
|
func (r *LightRenderer) Resume(clear bool, sigcont bool) {
|
||||||
r.setupTerminal()
|
r.setupTerminal()
|
||||||
if clear {
|
if clear {
|
||||||
@ -715,7 +725,7 @@ func (r *LightRenderer) Resume(clear bool, sigcont bool) {
|
|||||||
} else {
|
} else {
|
||||||
r.rmcup()
|
r.rmcup()
|
||||||
}
|
}
|
||||||
r.enableMouse()
|
r.enableModes()
|
||||||
r.flush()
|
r.flush()
|
||||||
} else if sigcont && !r.fullscreen && r.mouse {
|
} else if sigcont && !r.fullscreen && r.mouse {
|
||||||
// NOTE: SIGCONT (Coming back from CTRL-Z):
|
// NOTE: SIGCONT (Coming back from CTRL-Z):
|
||||||
@ -770,11 +780,10 @@ func (r *LightRenderer) Close() {
|
|||||||
if !r.showCursor {
|
if !r.showCursor {
|
||||||
r.csi("?25h")
|
r.csi("?25h")
|
||||||
}
|
}
|
||||||
r.disableMouse()
|
r.disableModes()
|
||||||
r.flush()
|
r.flush()
|
||||||
r.closePlatform()
|
|
||||||
r.restoreTerminal()
|
r.restoreTerminal()
|
||||||
r.closed.Set(true)
|
r.closePlatform()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Top() int {
|
func (r *LightRenderer) Top() int {
|
||||||
@ -1105,11 +1114,12 @@ type wrappedLine struct {
|
|||||||
displayWidth int
|
displayWidth int
|
||||||
}
|
}
|
||||||
|
|
||||||
func wrapLine(input string, prefixLength int, max int, tabstop int) []wrappedLine {
|
func wrapLine(input string, prefixLength int, initialMax int, tabstop int, wrapSignWidth int) []wrappedLine {
|
||||||
lines := []wrappedLine{}
|
lines := []wrappedLine{}
|
||||||
width := 0
|
width := 0
|
||||||
line := ""
|
line := ""
|
||||||
gr := uniseg.NewGraphemes(input)
|
gr := uniseg.NewGraphemes(input)
|
||||||
|
max := initialMax
|
||||||
for gr.Next() {
|
for gr.Next() {
|
||||||
rs := gr.Runes()
|
rs := gr.Runes()
|
||||||
str := string(rs)
|
str := string(rs)
|
||||||
@ -1131,6 +1141,7 @@ func wrapLine(input string, prefixLength int, max int, tabstop int) []wrappedLin
|
|||||||
line = str
|
line = str
|
||||||
prefixLength = 0
|
prefixLength = 0
|
||||||
width = w
|
width = w
|
||||||
|
max = initialMax - wrapSignWidth
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lines = append(lines, wrappedLine{string(line), width})
|
lines = append(lines, wrappedLine{string(line), width})
|
||||||
@ -1140,7 +1151,7 @@ func wrapLine(input string, prefixLength int, max int, tabstop int) []wrappedLin
|
|||||||
func (w *LightWindow) fill(str string, resetCode string) FillReturn {
|
func (w *LightWindow) fill(str string, resetCode string) FillReturn {
|
||||||
allLines := strings.Split(str, "\n")
|
allLines := strings.Split(str, "\n")
|
||||||
for i, line := range allLines {
|
for i, line := range allLines {
|
||||||
lines := wrapLine(line, w.posx, w.width, w.tabstop)
|
lines := wrapLine(line, w.posx, w.width, w.tabstop, w.wrapSignWidth)
|
||||||
for j, wl := range lines {
|
for j, wl := range lines {
|
||||||
w.stderrInternal(wl.text, false, resetCode)
|
w.stderrInternal(wl.text, false, resetCode)
|
||||||
w.posx += wl.displayWidth
|
w.posx += wl.displayWidth
|
||||||
@ -1153,6 +1164,18 @@ func (w *LightWindow) fill(str string, resetCode string) FillReturn {
|
|||||||
w.MoveAndClear(w.posy, w.posx)
|
w.MoveAndClear(w.posy, w.posx)
|
||||||
w.Move(w.posy+1, 0)
|
w.Move(w.posy+1, 0)
|
||||||
w.renderer.stderr(resetCode)
|
w.renderer.stderr(resetCode)
|
||||||
|
if len(lines) > 1 {
|
||||||
|
sign := w.wrapSign
|
||||||
|
width := w.wrapSignWidth
|
||||||
|
if width > w.width {
|
||||||
|
runes, truncatedWidth := util.Truncate(w.wrapSign, w.width)
|
||||||
|
sign = string(runes)
|
||||||
|
width = truncatedWidth
|
||||||
|
}
|
||||||
|
w.stderrInternal(DIM+sign, false, resetCode)
|
||||||
|
w.renderer.stderr(resetCode)
|
||||||
|
w.Move(w.posy, width)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1226,6 +1249,11 @@ func (w *LightWindow) EraseMaybe() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) SetWrapSign(sign string, width int) {
|
||||||
|
w.wrapSign = sign
|
||||||
|
w.wrapSignWidth = width
|
||||||
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) HideCursor() {
|
func (r *LightRenderer) HideCursor() {
|
||||||
r.showCursor = false
|
r.showCursor = false
|
||||||
r.csi("?25l")
|
r.csi("?25l")
|
||||||
|
@ -42,26 +42,35 @@ func (r *LightRenderer) closePlatform() {
|
|||||||
r.ttyout.Close()
|
r.ttyout.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func openTty(mode int) (*os.File, error) {
|
func openTty(ttyDefault string, mode int) (*os.File, error) {
|
||||||
in, err := os.OpenFile(consoleDevice, mode, 0)
|
var in *os.File
|
||||||
if err != nil {
|
var err error
|
||||||
|
if len(ttyDefault) > 0 {
|
||||||
|
in, err = os.OpenFile(ttyDefault, mode, 0)
|
||||||
|
}
|
||||||
|
if in == nil || err != nil || ttyDefault != DefaultTtyDevice && !util.IsTty(in) {
|
||||||
tty := ttyname()
|
tty := ttyname()
|
||||||
if len(tty) > 0 {
|
if len(tty) > 0 {
|
||||||
if in, err := os.OpenFile(tty, mode, 0); err == nil {
|
if in, err := os.OpenFile(tty, mode, 0); err == nil {
|
||||||
return in, nil
|
return in, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, errors.New("failed to open " + consoleDevice)
|
if ttyDefault != DefaultTtyDevice {
|
||||||
|
if in, err = os.OpenFile(DefaultTtyDevice, mode, 0); err == nil {
|
||||||
|
return in, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("failed to open " + DefaultTtyDevice)
|
||||||
}
|
}
|
||||||
return in, nil
|
return in, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func openTtyIn() (*os.File, error) {
|
func openTtyIn(ttyDefault string) (*os.File, error) {
|
||||||
return openTty(syscall.O_RDONLY)
|
return openTty(ttyDefault, syscall.O_RDONLY)
|
||||||
}
|
}
|
||||||
|
|
||||||
func openTtyOut() (*os.File, error) {
|
func openTtyOut(ttyDefault string) (*os.File, error) {
|
||||||
return openTty(syscall.O_WRONLY)
|
return openTty(ttyDefault, syscall.O_WRONLY)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) setupTerminal() {
|
func (r *LightRenderer) setupTerminal() {
|
||||||
|
@ -18,6 +18,7 @@ const (
|
|||||||
var (
|
var (
|
||||||
consoleFlagsInput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_EXTENDED_FLAGS)
|
consoleFlagsInput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_EXTENDED_FLAGS)
|
||||||
consoleFlagsOutput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING | windows.ENABLE_PROCESSED_OUTPUT | windows.DISABLE_NEWLINE_AUTO_RETURN)
|
consoleFlagsOutput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING | windows.ENABLE_PROCESSED_OUTPUT | windows.DISABLE_NEWLINE_AUTO_RETURN)
|
||||||
|
counter = uint64(0)
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsLightRendererSupported checks to see if the Light renderer is supported
|
// IsLightRendererSupported checks to see if the Light renderer is supported
|
||||||
@ -61,27 +62,11 @@ func (r *LightRenderer) initPlatform() error {
|
|||||||
}
|
}
|
||||||
r.inHandle = uintptr(inHandle)
|
r.inHandle = uintptr(inHandle)
|
||||||
|
|
||||||
r.setupTerminal()
|
|
||||||
|
|
||||||
// channel for non-blocking reads. Buffer to make sure
|
// channel for non-blocking reads. Buffer to make sure
|
||||||
// we get the ESC sets:
|
// we get the ESC sets:
|
||||||
r.ttyinChannel = make(chan byte, 1024)
|
r.ttyinChannel = make(chan byte, 1024)
|
||||||
|
|
||||||
// the following allows for non-blocking IO.
|
r.setupTerminal()
|
||||||
// syscall.SetNonblock() is a NOOP under Windows.
|
|
||||||
go func() {
|
|
||||||
fd := int(r.inHandle)
|
|
||||||
b := make([]byte, 1)
|
|
||||||
for !r.closed.Get() {
|
|
||||||
// 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)
|
|
||||||
if err == nil {
|
|
||||||
r.ttyinChannel <- b[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -91,27 +76,51 @@ func (r *LightRenderer) closePlatform() {
|
|||||||
windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput)
|
windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput)
|
||||||
}
|
}
|
||||||
|
|
||||||
func openTtyIn() (*os.File, error) {
|
func openTtyIn(ttyDefault string) (*os.File, error) {
|
||||||
// not used
|
// not used
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func openTtyOut() (*os.File, error) {
|
func openTtyOut(ttyDefault string) (*os.File, error) {
|
||||||
return os.Stderr, nil
|
return os.Stderr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) setupTerminal() error {
|
func (r *LightRenderer) setupTerminal() {
|
||||||
if err := windows.SetConsoleMode(windows.Handle(r.outHandle), consoleFlagsOutput); err != nil {
|
windows.SetConsoleMode(windows.Handle(r.outHandle), consoleFlagsOutput)
|
||||||
return err
|
windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
|
||||||
|
|
||||||
|
// The following allows for non-blocking IO.
|
||||||
|
// syscall.SetNonblock() is a NOOP under Windows.
|
||||||
|
current := counter
|
||||||
|
go func() {
|
||||||
|
fd := int(r.inHandle)
|
||||||
|
b := make([]byte, 1)
|
||||||
|
for {
|
||||||
|
if _, err := util.Read(fd, b); err == nil {
|
||||||
|
r.mutex.Lock()
|
||||||
|
// This condition prevents the goroutine from running after the renderer
|
||||||
|
// has been closed or paused.
|
||||||
|
if current != counter {
|
||||||
|
r.mutex.Unlock()
|
||||||
|
break
|
||||||
}
|
}
|
||||||
return windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
|
r.ttyinChannel <- b[0]
|
||||||
|
// HACK: if run from PSReadline, something resets ConsoleMode to remove ENABLE_VIRTUAL_TERMINAL_INPUT.
|
||||||
|
windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
|
||||||
|
r.mutex.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) restoreTerminal() error {
|
func (r *LightRenderer) restoreTerminal() {
|
||||||
if err := windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput); err != nil {
|
r.mutex.Lock()
|
||||||
return err
|
counter++
|
||||||
}
|
// We're setting ENABLE_VIRTUAL_TERMINAL_INPUT to allow escape sequences to be read during 'execute'.
|
||||||
return windows.SetConsoleMode(windows.Handle(r.outHandle), r.origStateOutput)
|
// e.g. fzf --bind 'enter:execute:less {}'
|
||||||
|
windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput|windows.ENABLE_VIRTUAL_TERMINAL_INPUT)
|
||||||
|
windows.SetConsoleMode(windows.Handle(r.outHandle), r.origStateOutput)
|
||||||
|
r.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Size() TermSize {
|
func (r *LightRenderer) Size() TermSize {
|
||||||
|
@ -53,6 +53,8 @@ type TcellWindow struct {
|
|||||||
uri *string
|
uri *string
|
||||||
params *string
|
params *string
|
||||||
showCursor bool
|
showCursor bool
|
||||||
|
wrapSign string
|
||||||
|
wrapSignWidth int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Top() int {
|
func (w *TcellWindow) Top() int {
|
||||||
@ -195,6 +197,7 @@ func (r *FullscreenRenderer) initScreen() error {
|
|||||||
if e = s.Init(); e != nil {
|
if e = s.Init(); e != nil {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
s.EnablePaste()
|
||||||
if r.mouse {
|
if r.mouse {
|
||||||
s.EnableMouse()
|
s.EnableMouse()
|
||||||
} else {
|
} else {
|
||||||
@ -264,6 +267,11 @@ func (r *FullscreenRenderer) Size() TermSize {
|
|||||||
func (r *FullscreenRenderer) GetChar() Event {
|
func (r *FullscreenRenderer) GetChar() Event {
|
||||||
ev := _screen.PollEvent()
|
ev := _screen.PollEvent()
|
||||||
switch ev := ev.(type) {
|
switch ev := ev.(type) {
|
||||||
|
case *tcell.EventPaste:
|
||||||
|
if ev.Start() {
|
||||||
|
return Event{BracketedPasteBegin, 0, nil}
|
||||||
|
}
|
||||||
|
return Event{BracketedPasteEnd, 0, nil}
|
||||||
case *tcell.EventResize:
|
case *tcell.EventResize:
|
||||||
// Ignore the first resize event
|
// Ignore the first resize event
|
||||||
// https://github.com/gdamore/tcell/blob/v2.7.0/TUTORIAL.md?plain=1#L18
|
// https://github.com/gdamore/tcell/blob/v2.7.0/TUTORIAL.md?plain=1#L18
|
||||||
@ -629,6 +637,11 @@ func (w *TcellWindow) EraseMaybe() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *TcellWindow) SetWrapSign(sign string, width int) {
|
||||||
|
w.wrapSign = sign
|
||||||
|
w.wrapSignWidth = width
|
||||||
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) EncloseX(x int) bool {
|
func (w *TcellWindow) EncloseX(x int) bool {
|
||||||
return x >= w.left && x < (w.left+w.width)
|
return x >= w.left && x < (w.left+w.width)
|
||||||
}
|
}
|
||||||
@ -757,11 +770,26 @@ Loop:
|
|||||||
|
|
||||||
// word wrap:
|
// word wrap:
|
||||||
xPos := w.left + w.lastX + lx
|
xPos := w.left + w.lastX + lx
|
||||||
if xPos >= (w.left + w.width) {
|
if xPos >= w.left+w.width {
|
||||||
w.lastY++
|
w.lastY++
|
||||||
|
if w.lastY >= w.height {
|
||||||
|
return FillSuspend
|
||||||
|
}
|
||||||
w.lastX = 0
|
w.lastX = 0
|
||||||
lx = 0
|
lx = 0
|
||||||
xPos = w.left
|
xPos = w.left
|
||||||
|
sign := w.wrapSign
|
||||||
|
if w.wrapSignWidth > w.width {
|
||||||
|
runes, _ := util.Truncate(sign, w.width)
|
||||||
|
sign = string(runes)
|
||||||
|
}
|
||||||
|
wgr := uniseg.NewGraphemes(sign)
|
||||||
|
for wgr.Next() {
|
||||||
|
rs := wgr.Runes()
|
||||||
|
_screen.SetContent(w.left+lx, w.top+w.lastY, rs[0], rs[1:], style.Dim(true))
|
||||||
|
lx += uniseg.StringWidth(string(rs))
|
||||||
|
}
|
||||||
|
xPos = w.left + lx
|
||||||
}
|
}
|
||||||
|
|
||||||
yPos := w.top + w.lastY
|
yPos := w.top + w.lastY
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func assert(t *testing.T, context string, got interface{}, want interface{}) bool {
|
func assert(t *testing.T, context string, got any, want any) bool {
|
||||||
if got == want {
|
if got == want {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
|
@ -44,11 +44,11 @@ func ttyname() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TtyIn returns terminal device to read user input
|
// TtyIn returns terminal device to read user input
|
||||||
func TtyIn() (*os.File, error) {
|
func TtyIn(ttyDefault string) (*os.File, error) {
|
||||||
return openTtyIn()
|
return openTtyIn(ttyDefault)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TtyIn returns terminal device to write to
|
// TtyIn returns terminal device to write to
|
||||||
func TtyOut() (*os.File, error) {
|
func TtyOut(ttyDefault string) (*os.File, error) {
|
||||||
return openTtyOut()
|
return openTtyOut(ttyDefault)
|
||||||
}
|
}
|
||||||
|
@ -11,11 +11,11 @@ func ttyname() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TtyIn on Windows returns os.Stdin
|
// TtyIn on Windows returns os.Stdin
|
||||||
func TtyIn() (*os.File, error) {
|
func TtyIn(ttyDefault string) (*os.File, error) {
|
||||||
return os.Stdin, nil
|
return os.Stdin, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TtyOut on Windows returns nil
|
// TtyOut on Windows returns nil
|
||||||
func TtyOut() (*os.File, error) {
|
func TtyOut(ttyDefault string) (*os.File, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -103,6 +103,8 @@ const (
|
|||||||
|
|
||||||
Invalid
|
Invalid
|
||||||
Fatal
|
Fatal
|
||||||
|
BracketedPasteBegin
|
||||||
|
BracketedPasteEnd
|
||||||
|
|
||||||
Mouse
|
Mouse
|
||||||
DoubleClick
|
DoubleClick
|
||||||
@ -306,6 +308,12 @@ func (p ColorPair) WithAttr(attr Attr) ColorPair {
|
|||||||
return dup
|
return dup
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p ColorPair) WithBg(bg ColorAttr) ColorPair {
|
||||||
|
dup := p
|
||||||
|
bgPair := ColorPair{colUndefined, bg.Color, bg.Attr}
|
||||||
|
return dup.Merge(bgPair)
|
||||||
|
}
|
||||||
|
|
||||||
func (p ColorPair) MergeAttr(other ColorPair) ColorPair {
|
func (p ColorPair) MergeAttr(other ColorPair) ColorPair {
|
||||||
return p.WithAttr(other.attr)
|
return p.WithAttr(other.attr)
|
||||||
}
|
}
|
||||||
@ -326,6 +334,7 @@ type ColorTheme struct {
|
|||||||
Bg ColorAttr
|
Bg ColorAttr
|
||||||
ListFg ColorAttr
|
ListFg ColorAttr
|
||||||
ListBg ColorAttr
|
ListBg ColorAttr
|
||||||
|
AltBg ColorAttr
|
||||||
Nth ColorAttr
|
Nth ColorAttr
|
||||||
SelectedFg ColorAttr
|
SelectedFg ColorAttr
|
||||||
SelectedBg ColorAttr
|
SelectedBg ColorAttr
|
||||||
@ -659,6 +668,8 @@ type Window interface {
|
|||||||
LinkEnd()
|
LinkEnd()
|
||||||
Erase()
|
Erase()
|
||||||
EraseMaybe() bool
|
EraseMaybe() bool
|
||||||
|
|
||||||
|
SetWrapSign(string, int)
|
||||||
}
|
}
|
||||||
|
|
||||||
type FullscreenRenderer struct {
|
type FullscreenRenderer struct {
|
||||||
@ -731,6 +742,7 @@ func EmptyTheme() *ColorTheme {
|
|||||||
Bg: ColorAttr{colUndefined, AttrUndefined},
|
Bg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
ListFg: ColorAttr{colUndefined, AttrUndefined},
|
ListFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
ListBg: ColorAttr{colUndefined, AttrUndefined},
|
ListBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
AltBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
||||||
@ -776,6 +788,7 @@ func NoColorTheme() *ColorTheme {
|
|||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
ListFg: ColorAttr{colDefault, AttrUndefined},
|
ListFg: ColorAttr{colDefault, AttrUndefined},
|
||||||
ListBg: ColorAttr{colDefault, AttrUndefined},
|
ListBg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
AltBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
SelectedFg: ColorAttr{colDefault, AttrUndefined},
|
SelectedFg: ColorAttr{colDefault, AttrUndefined},
|
||||||
SelectedBg: ColorAttr{colDefault, AttrUndefined},
|
SelectedBg: ColorAttr{colDefault, AttrUndefined},
|
||||||
SelectedMatch: ColorAttr{colDefault, AttrUndefined},
|
SelectedMatch: ColorAttr{colDefault, AttrUndefined},
|
||||||
@ -821,6 +834,7 @@ func init() {
|
|||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
ListFg: ColorAttr{colUndefined, AttrUndefined},
|
ListFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
ListBg: ColorAttr{colUndefined, AttrUndefined},
|
ListBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
AltBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
||||||
@ -860,6 +874,7 @@ func init() {
|
|||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
ListFg: ColorAttr{colUndefined, AttrUndefined},
|
ListFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
ListBg: ColorAttr{colUndefined, AttrUndefined},
|
ListBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
AltBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
||||||
@ -899,6 +914,7 @@ func init() {
|
|||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
ListFg: ColorAttr{colUndefined, AttrUndefined},
|
ListFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
ListBg: ColorAttr{colUndefined, AttrUndefined},
|
ListBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
AltBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
@ -189,6 +189,27 @@ func (chars *Chars) TrimTrailingWhitespaces() {
|
|||||||
chars.slice = chars.slice[0 : len(chars.slice)-whitespaces]
|
chars.slice = chars.slice[0 : len(chars.slice)-whitespaces]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) TrimSuffix(runes []rune) {
|
||||||
|
lastIdx := len(chars.slice)
|
||||||
|
firstIdx := lastIdx - len(runes)
|
||||||
|
if firstIdx < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := firstIdx; i < lastIdx; i++ {
|
||||||
|
char := chars.Get(i)
|
||||||
|
if char != runes[i-firstIdx] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chars.slice = chars.slice[0:firstIdx]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) SliceRight(last int) {
|
||||||
|
chars.slice = chars.slice[:last]
|
||||||
|
}
|
||||||
|
|
||||||
func (chars *Chars) ToString() string {
|
func (chars *Chars) ToString() string {
|
||||||
if runes := chars.optionalRunes(); runes != nil {
|
if runes := chars.optionalRunes(); runes != nil {
|
||||||
return string(runes)
|
return string(runes)
|
||||||
@ -273,9 +294,10 @@ func (chars *Chars) Lines(multiLine bool, maxLines int, wrapCols int, wrapSignWi
|
|||||||
line = line[:len(line)-1]
|
line = line[:len(line)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasWrapSign := false
|
||||||
for {
|
for {
|
||||||
cols := wrapCols
|
cols := wrapCols
|
||||||
if len(wrapped) > 0 {
|
if hasWrapSign {
|
||||||
cols -= wrapSignWidth
|
cols -= wrapSignWidth
|
||||||
}
|
}
|
||||||
_, overflowIdx := RunesWidth(line, 0, tabstop, cols)
|
_, overflowIdx := RunesWidth(line, 0, tabstop, cols)
|
||||||
@ -288,9 +310,11 @@ func (chars *Chars) Lines(multiLine bool, maxLines int, wrapCols int, wrapSignWi
|
|||||||
return wrapped, true
|
return wrapped, true
|
||||||
}
|
}
|
||||||
wrapped = append(wrapped, line[:overflowIdx])
|
wrapped = append(wrapped, line[:overflowIdx])
|
||||||
|
hasWrapSign = true
|
||||||
line = line[overflowIdx:]
|
line = line[overflowIdx:]
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
hasWrapSign = false
|
||||||
|
|
||||||
// Restore trailing '\n'
|
// Restore trailing '\n'
|
||||||
if newline {
|
if newline {
|
||||||
|
@ -76,7 +76,7 @@ func TestCharsLines(t *testing.T) {
|
|||||||
check(true, 100, 3, 1, 1, 8, false)
|
check(true, 100, 3, 1, 1, 8, false)
|
||||||
|
|
||||||
// With wrap sign (3 + 2)
|
// With wrap sign (3 + 2)
|
||||||
check(true, 100, 3, 2, 1, 12, false)
|
check(true, 100, 3, 2, 1, 10, false)
|
||||||
|
|
||||||
// With wrap sign (3 + 2) and no multi-line
|
// With wrap sign (3 + 2) and no multi-line
|
||||||
check(false, 100, 3, 2, 1, 13, false)
|
check(false, 100, 3, 2, 1, 13, false)
|
||||||
|
@ -6,7 +6,7 @@ import "sync"
|
|||||||
type EventType int
|
type EventType int
|
||||||
|
|
||||||
// Events is a type that associates EventType to any data
|
// Events is a type that associates EventType to any data
|
||||||
type Events map[EventType]interface{}
|
type Events map[EventType]any
|
||||||
|
|
||||||
// EventBox is used for coordinating events
|
// EventBox is used for coordinating events
|
||||||
type EventBox struct {
|
type EventBox struct {
|
||||||
@ -36,7 +36,7 @@ func (b *EventBox) Wait(callback func(*Events)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set turns on the event type on the box
|
// Set turns on the event type on the box
|
||||||
func (b *EventBox) Set(event EventType, value interface{}) {
|
func (b *EventBox) Set(event EventType, value any) {
|
||||||
b.cond.L.Lock()
|
b.cond.L.Lock()
|
||||||
b.events[event] = value
|
b.events[event] = value
|
||||||
if _, found := b.ignore[event]; !found {
|
if _, found := b.ignore[event]; !found {
|
||||||
|
@ -238,6 +238,11 @@ class TestCore < TestInteractive
|
|||||||
assert_equal %w[5555 55], fzf_output_lines
|
assert_equal %w[5555 55], fzf_output_lines
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_select_1_accept_nth
|
||||||
|
tmux.send_keys "seq 1 100 | #{fzf(:with_nth, '..,..', :print_query, :q, 5555, :'1', :accept_nth, '"{1} // {1}"')}", :Enter
|
||||||
|
assert_equal ['5555', '55 // 55'], fzf_output_lines
|
||||||
|
end
|
||||||
|
|
||||||
def test_exit_0
|
def test_exit_0
|
||||||
tmux.send_keys "seq 1 100 | #{fzf(:with_nth, '..,..', :print_query, :q, 555_555, :'0')}", :Enter
|
tmux.send_keys "seq 1 100 | #{fzf(:with_nth, '..,..', :print_query, :q, 555_555, :'0')}", :Enter
|
||||||
assert_equal %w[555555], fzf_output_lines
|
assert_equal %w[555555], fzf_output_lines
|
||||||
@ -827,6 +832,24 @@ class TestCore < TestInteractive
|
|||||||
tmux.until { |lines| assert(lines.any? { it.include?('jump cancelled at 3') }) }
|
tmux.until { |lines| assert(lines.any? { it.include?('jump cancelled at 3') }) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_jump_no_pointer
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --pointer= --jump-labels 12345 --bind ctrl-j:jump", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||||
|
tmux.send_keys 'C-j'
|
||||||
|
tmux.until { |lines| assert_equal '5 5', lines[-7] }
|
||||||
|
tmux.send_keys 'C-c'
|
||||||
|
tmux.until { |lines| assert_equal ' 5', lines[-7] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_jump_no_pointer_no_marker
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --pointer= --marker= --jump-labels 12345 --bind ctrl-j:jump", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||||
|
tmux.send_keys 'C-j'
|
||||||
|
tmux.until { |lines| assert_equal '55', lines[-7] }
|
||||||
|
tmux.send_keys 'C-c'
|
||||||
|
tmux.until { |lines| assert_equal '5', lines[-7] }
|
||||||
|
end
|
||||||
|
|
||||||
def test_pointer
|
def test_pointer
|
||||||
tmux.send_keys "seq 10 | #{fzf("--pointer '>>'")}", :Enter
|
tmux.send_keys "seq 10 | #{fzf("--pointer '>>'")}", :Enter
|
||||||
# Assert that specified pointer is displayed
|
# Assert that specified pointer is displayed
|
||||||
@ -1609,14 +1632,16 @@ class TestCore < TestInteractive
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_env_vars
|
def test_env_vars
|
||||||
def to_vars(lines)
|
def env_vars
|
||||||
lines.select { it.start_with?('FZF_') }.to_h do
|
return {} unless File.exist?(tempname)
|
||||||
key, val = it.split('=', 2)
|
|
||||||
|
File.readlines(tempname).select { it.start_with?('FZF_') }.to_h do
|
||||||
|
key, val = it.chomp.split('=', 2)
|
||||||
[key.to_sym, val]
|
[key.to_sym, val]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
tmux.send_keys %(seq 100 | #{FZF} --multi --reverse --preview-window up,99%,noborder --preview 'env | grep ^FZF_ | sort' --no-input --bind enter:show-input+refresh-preview,space:disable-search+refresh-preview), :Enter
|
tmux.send_keys %(seq 100 | #{FZF} --multi --reverse --preview-window 0 --preview 'env | grep ^FZF_ | sort > #{tempname}' --no-input --bind enter:show-input+refresh-preview,space:disable-search+refresh-preview), :Enter
|
||||||
expected = {
|
expected = {
|
||||||
FZF_TOTAL_COUNT: '100',
|
FZF_TOTAL_COUNT: '100',
|
||||||
FZF_MATCH_COUNT: '100',
|
FZF_MATCH_COUNT: '100',
|
||||||
@ -1625,31 +1650,32 @@ class TestCore < TestInteractive
|
|||||||
FZF_KEY: '',
|
FZF_KEY: '',
|
||||||
FZF_POS: '1',
|
FZF_POS: '1',
|
||||||
FZF_QUERY: '',
|
FZF_QUERY: '',
|
||||||
FZF_PROMPT: '>',
|
FZF_POINTER: '>',
|
||||||
|
FZF_PROMPT: '> ',
|
||||||
FZF_INPUT_STATE: 'hidden'
|
FZF_INPUT_STATE: 'hidden'
|
||||||
}
|
}
|
||||||
tmux.until do |lines|
|
tmux.until do
|
||||||
assert_equal expected, to_vars(lines).slice(*expected.keys)
|
assert_equal expected, env_vars.slice(*expected.keys)
|
||||||
end
|
end
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until do |lines|
|
tmux.until do
|
||||||
expected.merge!(FZF_INPUT_STATE: 'enabled', FZF_ACTION: 'show-input', FZF_KEY: 'enter')
|
expected.merge!(FZF_INPUT_STATE: 'enabled', FZF_ACTION: 'show-input', FZF_KEY: 'enter')
|
||||||
assert_equal expected, to_vars(lines).slice(*expected.keys)
|
assert_equal expected, env_vars.slice(*expected.keys)
|
||||||
end
|
end
|
||||||
tmux.send_keys :Tab, :Tab
|
tmux.send_keys :Tab, :Tab
|
||||||
tmux.until do |lines|
|
tmux.until do
|
||||||
expected.merge!(FZF_ACTION: 'toggle-down', FZF_KEY: 'tab', FZF_POS: '3', FZF_SELECT_COUNT: '2')
|
expected.merge!(FZF_ACTION: 'toggle-down', FZF_KEY: 'tab', FZF_POS: '3', FZF_SELECT_COUNT: '2')
|
||||||
assert_equal expected, to_vars(lines).slice(*expected.keys)
|
assert_equal expected, env_vars.slice(*expected.keys)
|
||||||
end
|
end
|
||||||
tmux.send_keys '99'
|
tmux.send_keys '99'
|
||||||
tmux.until do |lines|
|
tmux.until do
|
||||||
expected.merge!(FZF_ACTION: 'char', FZF_KEY: '9', FZF_QUERY: '99', FZF_MATCH_COUNT: '1', FZF_POS: '1')
|
expected.merge!(FZF_ACTION: 'char', FZF_KEY: '9', FZF_QUERY: '99', FZF_MATCH_COUNT: '1', FZF_POS: '1')
|
||||||
assert_equal expected, to_vars(lines).slice(*expected.keys)
|
assert_equal expected, env_vars.slice(*expected.keys)
|
||||||
end
|
end
|
||||||
tmux.send_keys :Space
|
tmux.send_keys :Space
|
||||||
tmux.until do |lines|
|
tmux.until do
|
||||||
expected.merge!(FZF_INPUT_STATE: 'disabled', FZF_ACTION: 'disable-search', FZF_KEY: 'space')
|
expected.merge!(FZF_INPUT_STATE: 'disabled', FZF_ACTION: 'disable-search', FZF_KEY: 'space')
|
||||||
assert_equal expected, to_vars(lines).slice(*expected.keys)
|
assert_equal expected, env_vars.slice(*expected.keys)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -1665,4 +1691,252 @@ class TestCore < TestInteractive
|
|||||||
assert_equal '', File.read(tempname).chomp
|
assert_equal '', File.read(tempname).chomp
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_exclude_multi
|
||||||
|
tmux.send_keys %(seq 1000 | #{FZF} --multi --bind 'a:exclude-multi,b:reload(seq 1000),c:reload-sync(seq 1000)'), :Enter
|
||||||
|
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 1000, lines.match_count
|
||||||
|
assert_includes lines, '> 1'
|
||||||
|
end
|
||||||
|
tmux.send_keys :a
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines, '> 2'
|
||||||
|
assert_equal 999, lines.match_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :Up, :BTab, :BTab, :BTab, :a
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 996, lines.match_count
|
||||||
|
assert_includes lines, '> 9'
|
||||||
|
end
|
||||||
|
tmux.send_keys :b
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 1000, lines.match_count
|
||||||
|
assert_includes lines, '> 5'
|
||||||
|
end
|
||||||
|
tmux.send_keys :Tab, :Tab, :Tab, :a
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 997, lines.match_count
|
||||||
|
assert_includes lines, '> 2'
|
||||||
|
end
|
||||||
|
tmux.send_keys :c
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 1000, lines.match_count
|
||||||
|
assert_includes lines, '> 2'
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO: We should also check the behavior of 'exclude' during reloads
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_exclude
|
||||||
|
tmux.send_keys %(seq 1000 | #{FZF} --multi --bind 'a:exclude,b:reload(seq 1000),c:reload-sync(seq 1000)'), :Enter
|
||||||
|
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 1000, lines.match_count
|
||||||
|
assert_includes lines, '> 1'
|
||||||
|
end
|
||||||
|
tmux.send_keys :a
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines, '> 2'
|
||||||
|
assert_equal 999, lines.match_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :Up, :BTab, :BTab, :BTab, :a
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 998, lines.match_count
|
||||||
|
assert_equal 3, lines.select_count
|
||||||
|
assert_includes lines, '> 7'
|
||||||
|
end
|
||||||
|
tmux.send_keys :b
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 1000, lines.match_count
|
||||||
|
assert_equal 0, lines.select_count
|
||||||
|
assert_includes lines, '> 5'
|
||||||
|
end
|
||||||
|
tmux.send_keys :Tab, :Tab, :Tab, :a
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 999, lines.match_count
|
||||||
|
assert_equal 3, lines.select_count
|
||||||
|
assert_includes lines, '>>3'
|
||||||
|
end
|
||||||
|
tmux.send_keys :a
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 998, lines.match_count
|
||||||
|
assert_equal 2, lines.select_count
|
||||||
|
assert_includes lines, '>>4'
|
||||||
|
end
|
||||||
|
tmux.send_keys :c
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 1000, lines.match_count
|
||||||
|
assert_includes lines, '> 2'
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO: We should also check the behavior of 'exclude' during reloads
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_accept_nth
|
||||||
|
tmux.send_keys %((echo "foo bar baz"; echo "bar baz foo") | #{FZF} --multi --accept-nth 2,2 --sync --bind start:select-all+accept > #{tempname}), :Enter
|
||||||
|
wait do
|
||||||
|
assert_path_exists tempname
|
||||||
|
assert_equal ['bar bar', 'baz baz'], File.readlines(tempname, chomp: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_accept_nth_string_delimiter
|
||||||
|
tmux.send_keys %(echo "foo ,bar,baz" | #{FZF} -d, --accept-nth 2,2,1,3,1 --sync --bind start:accept > #{tempname}), :Enter
|
||||||
|
wait do
|
||||||
|
assert_path_exists tempname
|
||||||
|
# Last delimiter and the whitespaces are removed
|
||||||
|
assert_equal ['bar,bar,foo ,bazfoo'], File.readlines(tempname, chomp: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_accept_nth_regex_delimiter
|
||||||
|
tmux.send_keys %(echo "foo :,:bar,baz" | #{FZF} --delimiter='[:,]+' --accept-nth 2,2,1,3,1 --sync --bind start:accept > #{tempname}), :Enter
|
||||||
|
wait do
|
||||||
|
assert_path_exists tempname
|
||||||
|
# Last delimiter and the whitespaces are removed
|
||||||
|
assert_equal ['bar,bar,foo :,:bazfoo'], File.readlines(tempname, chomp: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_accept_nth_regex_delimiter_strip_last
|
||||||
|
tmux.send_keys %((echo "foo:,bar:,baz"; echo "foo:,bar:,baz:,qux:,") | #{FZF} --multi --delimiter='[:,]+' --accept-nth 2.. --sync --bind 'load:select-all+accept' > #{tempname}), :Enter
|
||||||
|
wait do
|
||||||
|
assert_path_exists tempname
|
||||||
|
# Last delimiter and the whitespaces are removed
|
||||||
|
assert_equal ['bar:,baz', 'bar:,baz:,qux'], File.readlines(tempname, chomp: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_accept_nth_template
|
||||||
|
tmux.send_keys %(echo "foo ,bar,baz" | #{FZF} -d, --accept-nth '[{n}] 1st: {1}, 3rd: {3}, 2nd: {2}' --sync --bind start:accept > #{tempname}), :Enter
|
||||||
|
wait do
|
||||||
|
assert_path_exists tempname
|
||||||
|
# Last delimiter and the whitespaces are removed
|
||||||
|
assert_equal ['[0] 1st: foo, 3rd: baz, 2nd: bar'], File.readlines(tempname, chomp: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_ghost
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --prompt 'X ' --ghost 'Type in query ...' --bind 'space:change-ghost:Y Z' --bind 'enter:transform-ghost:echo Z Y'), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 100, lines.match_count
|
||||||
|
assert_includes lines, 'X Type in query ...'
|
||||||
|
end
|
||||||
|
tmux.send_keys '100'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
assert_includes lines, 'X 100'
|
||||||
|
end
|
||||||
|
tmux.send_keys 'C-u'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 100, lines.match_count
|
||||||
|
assert_includes lines, 'X Type in query ...'
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert_includes lines, 'X Y Z' }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines, 'X Z Y' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_ghost_inline
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --info 'inline: Y' --no-separator --prompt 'X ' --ghost 'Type in query ...'), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines, 'X Type in query ... Y100/100'
|
||||||
|
end
|
||||||
|
tmux.send_keys '100'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines, 'X 100 Y1/100'
|
||||||
|
end
|
||||||
|
tmux.send_keys 'C-u'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines, 'X Type in query ... Y100/100'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_offset_middle
|
||||||
|
tmux.send_keys %(seq 1000 | #{FZF} --sync --no-input --reverse --height 5 --scroll-off 0 --bind space:offset-middle), :Enter
|
||||||
|
line = nil
|
||||||
|
tmux.until { |lines| line = lines.index('> 1') }
|
||||||
|
tmux.send_keys :PgDn
|
||||||
|
tmux.until { |lines| assert_includes lines[line + 4], '> 5' }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert_includes lines[line + 2], '> 5' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_no_input_query
|
||||||
|
tmux.send_keys %(seq 1000 | #{FZF} --no-input --query 555 --bind space:toggle-input), :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 555' }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
assert_includes lines, '> 555'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_no_input_change_query
|
||||||
|
tmux.send_keys %(seq 1000 | #{FZF} --multi --query 999 --no-input --bind 'enter:show-input+change-query(555)+hide-input,space:change-query(555)+select'), :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 999' }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines, '>>999'
|
||||||
|
refute_includes lines, '> 555'
|
||||||
|
end
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
refute_includes lines, '>>999'
|
||||||
|
assert_includes lines, '> 555'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_search_override_query_in_no_input_mode
|
||||||
|
tmux.send_keys %(seq 1000 | #{FZF} --sync --no-input --bind 'enter:show-input+change-query(555)+hide-input+search(999),space:search(111)+show-input+change-query(777)'), :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 1' }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 999' }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 777' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_change_pointer
|
||||||
|
tmux.send_keys %(seq 2 | #{FZF} --bind 'a:change-pointer(a),b:change-pointer(bb),c:change-pointer(),d:change-pointer(ddd)'), :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 1' }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| assert_includes lines, 'a 1' }
|
||||||
|
tmux.send_keys 'b'
|
||||||
|
tmux.until { |lines| assert_includes lines, 'bb 1' }
|
||||||
|
tmux.send_keys 'c'
|
||||||
|
tmux.until { |lines| assert_includes lines, ' 1' }
|
||||||
|
tmux.send_keys 'd'
|
||||||
|
tmux.until { |lines| refute_includes lines, 'ddd 1' }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_includes lines, ' 2' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_transform_pointer
|
||||||
|
tmux.send_keys %(seq 2 | #{FZF} --bind 'a:transform-pointer(echo a),b:transform-pointer(echo bb),c:transform-pointer(),d:transform-pointer(echo ddd)'), :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 1' }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| assert_includes lines, 'a 1' }
|
||||||
|
tmux.send_keys 'b'
|
||||||
|
tmux.until { |lines| assert_includes lines, 'bb 1' }
|
||||||
|
tmux.send_keys 'c'
|
||||||
|
tmux.until { |lines| assert_includes lines, ' 1' }
|
||||||
|
tmux.send_keys 'd'
|
||||||
|
tmux.until { |lines| refute_includes lines, 'ddd 1' }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_includes lines, ' 2' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_change_header_on_header_window
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --list-border --input-border --bind 'start:change-header(foo),space:change-header(bar)'), :Enter
|
||||||
|
tmux.until { |lines| assert lines.any_include?('foo') }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert lines.any_include?('bar') }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_trailing_new_line
|
||||||
|
tmux.send_keys %(echo -en "foo\n" | fzf --read0 --no-multi-line), :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines, '> foo␊' }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -52,6 +52,12 @@ class TestFilter < TestBase
|
|||||||
`find . -print0 | #{FZF} --read0 -e -f "^#{lines.last}$"`.chomp
|
`find . -print0 | #{FZF} --read0 -e -f "^#{lines.last}$"`.chomp
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_nth_suffix_match
|
||||||
|
assert_equal \
|
||||||
|
'foo,bar,baz',
|
||||||
|
`echo foo,bar,baz | #{FZF} -d, -f'bar$' -n2`.chomp
|
||||||
|
end
|
||||||
|
|
||||||
def test_with_nth_basic
|
def test_with_nth_basic
|
||||||
writelines(['hello world ', 'byebye'])
|
writelines(['hello world ', 'byebye'])
|
||||||
assert_equal \
|
assert_equal \
|
||||||
@ -59,6 +65,13 @@ class TestFilter < TestBase
|
|||||||
`#{FZF} -f"^he hehe" -x -n 2.. --with-nth 2,1,1 < #{tempname}`.chomp
|
`#{FZF} -f"^he hehe" -x -n 2.. --with-nth 2,1,1 < #{tempname}`.chomp
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_with_nth_template
|
||||||
|
writelines(['hello world ', 'byebye'])
|
||||||
|
assert_equal \
|
||||||
|
'hello world ',
|
||||||
|
`#{FZF} -f"^he he.he." -x -n 2.. --with-nth '{2} {1}. {1}.' < #{tempname}`.chomp
|
||||||
|
end
|
||||||
|
|
||||||
def test_with_nth_ansi
|
def test_with_nth_ansi
|
||||||
writelines(["\x1b[33mhello \x1b[34;1mworld\x1b[m ", 'byebye'])
|
writelines(["\x1b[33mhello \x1b[34;1mworld\x1b[m ", 'byebye'])
|
||||||
assert_equal \
|
assert_equal \
|
||||||
|
@ -978,4 +978,59 @@ class TestLayout < TestInteractive
|
|||||||
setup
|
setup
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_change_header_and_label_at_once
|
||||||
|
tmux.send_keys %(seq 10 | #{FZF} --border sharp --header-border sharp --header-label-pos 3 --bind 'focus:change-header(header)+change-header-label(label)'), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ ┌─label──
|
||||||
|
│ │ header
|
||||||
|
│ └────────
|
||||||
|
│ 10/10 ─
|
||||||
|
│ >
|
||||||
|
└──────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_label_trunction
|
||||||
|
command = <<~CMD
|
||||||
|
seq 10 | #{FZF} --style full --border --header-lines=1 --preview ':' \\
|
||||||
|
--border-label "#{'b' * 1000}" \\
|
||||||
|
--preview-label "#{'p' * 1000}" \\
|
||||||
|
--header-label "#{'h' * 1000}" \\
|
||||||
|
--header-label "#{'h' * 1000}" \\
|
||||||
|
--input-label "#{'i' * 1000}" \\
|
||||||
|
--list-label "#{'l' * 1000}"
|
||||||
|
CMD
|
||||||
|
writelines(command.lines.map(&:chomp))
|
||||||
|
tmux.send_keys("sh #{tempname}", :Enter)
|
||||||
|
tmux.until do |lines|
|
||||||
|
text = lines.join
|
||||||
|
assert_includes text, 'b··'
|
||||||
|
assert_includes text, 'l··p'
|
||||||
|
assert_includes text, 'p··'
|
||||||
|
assert_includes text, 'h··'
|
||||||
|
assert_includes text, 'i··'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_separator_no_ellipsis
|
||||||
|
tmux.send_keys %(seq 10 | #{FZF} --separator "$(seq 1000 | tr '\\n' ' ')"), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 10, lines.match_count
|
||||||
|
refute_includes lines.join, '··'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_header_border_no_pointer_and_marker
|
||||||
|
tmux.send_keys %(seq 10 | #{FZF} --header-lines 1 --header-border sharp --no-list-border --pointer '' --marker ''), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
┌──────
|
||||||
|
│ 1
|
||||||
|
└──────
|
||||||
|
9/9 ─
|
||||||
|
>
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -453,7 +453,7 @@ class TestPreview < TestInteractive
|
|||||||
tmux.send_keys 'f'
|
tmux.send_keys 'f'
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
assert_equal '::', lines[0]
|
assert_equal '::', lines[0]
|
||||||
assert_equal ' 3', lines[1]
|
assert_equal '↳ 3', lines[1]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -527,7 +527,7 @@ class TestPreview < TestInteractive
|
|||||||
tmux.send_keys "seq 10 | #{FZF} --preview-border rounded --preview-window '~5,2,+0,<100000(~0,+100,wrap,noinfo)' --preview 'seq 1000'", :Enter
|
tmux.send_keys "seq 10 | #{FZF} --preview-border rounded --preview-window '~5,2,+0,<100000(~0,+100,wrap,noinfo)' --preview 'seq 1000'", :Enter
|
||||||
tmux.until { |lines| assert_equal 10, lines.match_count }
|
tmux.until { |lines| assert_equal 10, lines.match_count }
|
||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
assert_equal ['╭────╮', '│ 10 │', '│ 0 │', '│ 10 │', '│ 1 │'], lines.take(5).map(&:strip)
|
assert_equal ['╭────╮', '│ 10 │', '│ ↳ 0│', '│ 10 │', '│ ↳ 1│'], lines.take(5).map(&:strip)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -544,4 +544,18 @@ class TestPreview < TestInteractive
|
|||||||
tmux.send_keys :Up
|
tmux.send_keys :Up
|
||||||
tmux.until { |lines| assert_includes lines, '> 2' }
|
tmux.until { |lines| assert_includes lines, '> 2' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_preview_query_should_not_be_affected_by_search
|
||||||
|
tmux.send_keys "seq 1 | #{FZF} --bind 'change:transform-search(echo {q:1})' --preview 'echo [{q}/{}]'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys '1'
|
||||||
|
tmux.until { |lines| assert lines.any_include?('[1/1]') }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert lines.any_include?('[1 /1]') }
|
||||||
|
tmux.send_keys '2'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.any_include?('[1 2/1]')
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -73,7 +73,7 @@ module TestShell
|
|||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys :Escape, :c
|
tmux.send_keys :Escape, :c
|
||||||
lines = tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
lines = tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||||
expected = lines.reverse.find { |l| l.start_with?('> ') }[2..]
|
expected = lines.reverse.find { |l| l.start_with?('> ') }[2..].chomp('/')
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys :pwd, :Enter
|
tmux.send_keys :pwd, :Enter
|
||||||
@ -241,7 +241,7 @@ module CompletionTest
|
|||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
assert_equal 1, lines.match_count
|
assert_equal 1, lines.match_count
|
||||||
assert_includes lines, '> 55'
|
assert_includes lines, '> 55'
|
||||||
assert_includes lines, '> /tmp/fzf-test/d55'
|
assert_includes lines, '> /tmp/fzf-test/d55/'
|
||||||
end
|
end
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until(true) { |lines| assert_equal 'cd /tmp/fzf-test/d55/', lines[-1] }
|
tmux.until(true) { |lines| assert_equal 'cd /tmp/fzf-test/d55/', lines[-1] }
|
||||||
@ -482,4 +482,36 @@ class TestFish < TestBase
|
|||||||
tmux.send_keys "set -g #{name} '#{val}'", :Enter
|
tmux.send_keys "set -g #{name} '#{val}'", :Enter
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_ctrl_r_multi
|
||||||
|
tmux.send_keys ':', :Enter
|
||||||
|
tmux.send_keys 'echo "foo', :Enter, 'bar"', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'echo "bar', :Enter, 'foo"', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'C-l', 'C-r'
|
||||||
|
block = <<~BLOCK
|
||||||
|
echo "foo
|
||||||
|
bar"
|
||||||
|
echo "bar
|
||||||
|
foo"
|
||||||
|
BLOCK
|
||||||
|
tmux.until do |lines|
|
||||||
|
block.lines.each_with_index do |line, idx|
|
||||||
|
assert_includes lines[-6 + idx], line.chomp
|
||||||
|
end
|
||||||
|
end
|
||||||
|
tmux.send_keys :BTab, :BTab
|
||||||
|
tmux.until { |lines| assert_includes lines[-2], '(2)' }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
echo "bar
|
||||||
|
foo"
|
||||||
|
echo "foo
|
||||||
|
bar"
|
||||||
|
BLOCK
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal block.lines.map(&:chomp), lines
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user