mirror of
https://github.com/junegunn/fzf.git
synced 2025-05-19 12:50:22 -07:00
Compare commits
87 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 |
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
|
||||||
|
79
CHANGELOG.md
79
CHANGELOG.md
@ -1,6 +1,85 @@
|
|||||||
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
|
0.60.1
|
||||||
------
|
------
|
||||||
- Bug fixes and minor improvements
|
- Bug fixes and minor improvements
|
||||||
|
@ -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!
|
2
go.mod
2
go.mod
@ -1,7 +1,7 @@
|
|||||||
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
|
||||||
|
4
go.sum
4
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=
|
||||||
|
2
install
2
install
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.60.1
|
version=0.62.0
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
$version="0.60.1"
|
$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.60"
|
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.60.1" "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.60.1" "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
|
||||||
@ -122,7 +122,9 @@ fields.
|
|||||||
.BI "\-\-with\-nth=" "N[,..] or TEMPLATE"
|
.BI "\-\-with\-nth=" "N[,..] or TEMPLATE"
|
||||||
Transform the presentation of each line using the 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
|
For advanced transformation, you can provide a template containing field index
|
||||||
expressions in curly braces.
|
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
|
.RS
|
||||||
e.g.
|
e.g.
|
||||||
@ -130,13 +132,16 @@ e.g.
|
|||||||
echo foo bar baz | fzf --with-nth 2..
|
echo foo bar baz | fzf --with-nth 2..
|
||||||
|
|
||||||
# Use template to rearrange fields
|
# Use template to rearrange fields
|
||||||
echo foo,bar,baz | fzf --delimiter , --with-nth '{1},{3},{2},{1..2}'
|
echo foo,bar,baz | fzf --delimiter , --with-nth '{n},{1},{3},{2},{1..2}'
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-accept\-nth=" "N[,..] or TEMPLATE"
|
.BI "\-\-accept\-nth=" "N[,..] or TEMPLATE"
|
||||||
Define which fields to print on accept. The last delimiter is stripped from the
|
Define which fields to print on accept. The last delimiter is stripped from the
|
||||||
output. For advanced transformation, you can provide a template containing
|
output. For advanced transformation, you can provide a template containing
|
||||||
field index expressions in curly braces.
|
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
|
.RS
|
||||||
e.g.
|
e.g.
|
||||||
@ -144,7 +149,7 @@ e.g.
|
|||||||
echo foo bar baz | fzf --accept-nth 2
|
echo foo bar baz | fzf --accept-nth 2
|
||||||
|
|
||||||
# Template
|
# Template
|
||||||
echo foo bar baz | fzf --accept-nth '1st: {1}, 2nd: {2}, 3rd: {3}'
|
echo foo bar baz | fzf --accept-nth 'Index: {n}, 1st: {1}, 2nd: {2}, 3rd: {3}'
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.B "+s, \-\-no\-sort"
|
.B "+s, \-\-no\-sort"
|
||||||
@ -223,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"
|
||||||
@ -230,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:
|
||||||
@ -258,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)
|
||||||
@ -325,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"
|
||||||
@ -704,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
|
||||||
@ -759,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.
|
||||||
|
|
||||||
@ -1257,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"
|
||||||
@ -1600,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)
|
||||||
@ -1607,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 '|')
|
||||||
@ -1695,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)
|
||||||
@ -1719,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
|
||||||
|
@ -14,12 +14,21 @@
|
|||||||
|
|
||||||
# Key bindings
|
# Key bindings
|
||||||
# ------------
|
# ------------
|
||||||
# For compatibility with fish versions down to 3.1.2, the script does not use:
|
# The oldest supported fish version is 3.1b1. To maintain compatibility, the
|
||||||
# - The -f/--function switch of command: set
|
# command substitution syntax $(cmd) should never be used, even behind a version
|
||||||
# - The process substitution syntax: $(cmd)
|
# check, otherwise the source command will fail on fish versions older than 3.4.0.
|
||||||
# - Ranges that omit start/end indexes: $var[$start..] $var[..$end] $var[..]
|
|
||||||
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
|
||||||
# $argv[1]: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
# $argv[1]: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
# $argv[2..]: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
# $argv[2..]: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
@ -42,45 +51,79 @@ function fzf_key_bindings
|
|||||||
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'
|
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 -l dir '.'
|
||||||
set -l query
|
|
||||||
set -l commandline (commandline -t | string unescape -n)
|
|
||||||
|
|
||||||
# Strip -option= from token if present
|
# Set variables containing the major and minor fish version numbers, using
|
||||||
set -l prefix (string match -r -- '^-[^\s=]+=' $commandline)
|
# a method compatible with all supported fish versions.
|
||||||
set commandline (string replace -- "$prefix" '' $commandline)
|
set -l -- fish_major (string match -r -- '^\d+' $version)
|
||||||
|
set -l -- fish_minor (string match -r -- '^\d+\.(\d+)' $version)[2]
|
||||||
|
|
||||||
# Enable home directory expansion of leading ~/
|
# fish v3.3.0 and newer: Don't use option prefix if " -- " is preceded.
|
||||||
set commandline (string replace -r -- '^~/' '\$HOME/' $commandline)
|
set -l -- match_regex '(?<fzf_query>[\s\S]*?(?=\n?$)$)'
|
||||||
|
set -l -- prefix_regex '^-[^\s=]+=|^-(?!-)\S'
|
||||||
# Escape special characters, except for the $ sign of valid variable names,
|
if test "$fish_major" -eq 3 -a "$fish_minor" -lt 3
|
||||||
# so that the original string with expanded variables is returned after eval.
|
or string match -q -v -- '* -- *' (string sub -l (commandline -Cp) -- (commandline -p))
|
||||||
set commandline (string escape -n -- $commandline)
|
set -- match_regex "(?<prefix>$prefix_regex)?$match_regex"
|
||||||
set commandline (string replace -r -a -- '\\\\\$(?=[\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 -n "$commandline"
|
|
||||||
# Strip trailing slash, unless $dir is root dir (/)
|
|
||||||
set dir (string replace -r -- '(?<!^)/$' '' $commandline)
|
|
||||||
|
|
||||||
# Set $dir to the longest existing filepath
|
|
||||||
while not 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
|
end
|
||||||
|
|
||||||
if test "$dir" = '.'; and test (string sub -l 2 -- $commandline) != './'
|
# Set $prefix and expanded $fzf_query with preserved trailing newlines.
|
||||||
# If $dir is "." but commandline is not a relative path, this means no file path found
|
if test "$fish_major" -ge 4
|
||||||
set fzf_query $commandline
|
# 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
|
else
|
||||||
# Also remove trailing slash after dir, to "split" input properly
|
# fish older than v3.2.0 (v3.1b1 - v3.1.2)
|
||||||
set fzf_query (string replace -r -- "^$dir/?" '' $commandline)
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -95,31 +138,29 @@ function fzf_key_bindings
|
|||||||
set -l prefix $commandline[3]
|
set -l prefix $commandline[3]
|
||||||
|
|
||||||
set -lx FZF_DEFAULT_OPTS (__fzf_defaults \
|
set -lx FZF_DEFAULT_OPTS (__fzf_defaults \
|
||||||
"--reverse --walker=file,dir,follow,hidden --scheme=path --walker-root=$dir" \
|
"--reverse --walker=file,dir,follow,hidden --scheme=path" \
|
||||||
"$FZF_CTRL_T_OPTS --multi")
|
"$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
|
||||||
|
|
||||||
if set -l result (eval (__fzfcmd) --query=$fzf_query)
|
set -l result (eval (__fzfcmd) --walker-root=$dir --query=$fzf_query | string split0)
|
||||||
# Remove last token from commandline.
|
and commandline -rt -- (string join -- ' ' $prefix(string escape -- $result))' '
|
||||||
commandline -t ''
|
|
||||||
for i in $result
|
|
||||||
commandline -it -- $prefix(string escape -- $i)' '
|
|
||||||
end
|
|
||||||
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"
|
||||||
set -l fzf_query (commandline | string escape)
|
set -l -- command_line (commandline)
|
||||||
|
set -l -- current_line (commandline -L)
|
||||||
|
set -l -- total_lines (count $command_line)
|
||||||
|
set -l -- fzf_query (string escape -- $command_line[$current_line])
|
||||||
|
|
||||||
set -lx FZF_DEFAULT_OPTS (__fzf_defaults '' \
|
set -lx FZF_DEFAULT_OPTS (__fzf_defaults '' \
|
||||||
'--nth=2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign="\t↳ "' \
|
'--nth=2..,.. --scheme=history --multi --wrap-sign="\t↳ "' \
|
||||||
"--highlight-line --no-multi $FZF_CTRL_R_OPTS --read0 --print0" \
|
'--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='enter:become:string replace -a -- \n\t \n {2..} | string collect'" \
|
"--bind=ctrl-r:toggle-sort --highlight-line $FZF_CTRL_R_OPTS" \
|
||||||
'--with-shell='(status fish-path)\\ -c)
|
'--accept-nth=2.. --read0 --print0 --with-shell='(status fish-path)\\ -c)
|
||||||
|
|
||||||
set -lx FZF_DEFAULT_OPTS_FILE
|
set -lx FZF_DEFAULT_OPTS_FILE
|
||||||
set -lx FZF_DEFAULT_COMMAND
|
set -lx FZF_DEFAULT_COMMAND
|
||||||
@ -138,8 +179,16 @@ function fzf_key_bindings
|
|||||||
# Merge history from other sessions before searching
|
# Merge history from other sessions before searching
|
||||||
test -z "$fish_private_mode"; and builtin history merge
|
test -z "$fish_private_mode"; and builtin history merge
|
||||||
|
|
||||||
set -l result (eval $FZF_DEFAULT_COMMAND \| (__fzfcmd) --query=$fzf_query)
|
if set -l result (eval $FZF_DEFAULT_COMMAND \| (__fzfcmd) --query=$fzf_query | string split0)
|
||||||
and commandline -- $result
|
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
|
||||||
|
|
||||||
commandline -f repaint
|
commandline -f repaint
|
||||||
end
|
end
|
||||||
@ -151,13 +200,13 @@ function fzf_key_bindings
|
|||||||
set -l prefix $commandline[3]
|
set -l prefix $commandline[3]
|
||||||
|
|
||||||
set -lx FZF_DEFAULT_OPTS (__fzf_defaults \
|
set -lx FZF_DEFAULT_OPTS (__fzf_defaults \
|
||||||
"--reverse --walker=dir,follow,hidden --scheme=path --walker-root=$dir" \
|
"--reverse --walker=dir,follow,hidden --scheme=path" \
|
||||||
"$FZF_ALT_C_OPTS --no-multi")
|
"$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"
|
||||||
|
|
||||||
if set -l result (eval (__fzfcmd) --query=$fzf_query)
|
if set -l result (eval (__fzfcmd) --query=$fzf_query --walker-root=$dir | string split0)
|
||||||
cd -- $result
|
cd -- $result
|
||||||
commandline -rt -- $prefix
|
commandline -rt -- $prefix
|
||||||
end
|
end
|
||||||
|
@ -12,139 +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[actExclude-130]
|
_ = x[actRebind-130]
|
||||||
_ = x[actExcludeMulti-131]
|
_ = x[actToggleBind-131]
|
||||||
|
_ = x[actBecome-132]
|
||||||
|
_ = x[actShowHeader-133]
|
||||||
|
_ = x[actHideHeader-134]
|
||||||
|
_ = x[actBell-135]
|
||||||
|
_ = x[actExclude-136]
|
||||||
|
_ = x[actExcludeMulti-137]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeListLabelactChangeInputLabelactChangeHeaderactChangeHeaderLabelactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactChangeNthactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformListLabelactTransformInputLabelactTransformHeaderactTransformHeaderLabelactTransformNthactTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactSearchactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBellactExcludeactExcludeMulti"
|
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, 1808, 1823}
|
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) {
|
||||||
|
@ -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
|
||||||
|
18
src/core.go
18
src/core.go
@ -128,13 +128,14 @@ func Run(opts *Options) (int, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
transformed := nthTransformer(tokens)
|
transformed := nthTransformer(tokens, itemIndex)
|
||||||
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)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
item.text, item.colors = ansiProcessor(stringBytes(transformed))
|
item.text, item.colors = ansiProcessor(stringBytes(transformed))
|
||||||
|
item.text.TrimTrailingWhitespaces()
|
||||||
item.text.Index = itemIndex
|
item.text.Index = itemIndex
|
||||||
item.origText = &data
|
item.origText = &data
|
||||||
itemIndex++
|
itemIndex++
|
||||||
@ -294,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)
|
||||||
@ -320,6 +322,7 @@ func Run(opts *Options) (int, error) {
|
|||||||
clearDenylist()
|
clearDenylist()
|
||||||
}
|
}
|
||||||
reading = true
|
reading = true
|
||||||
|
startTick = ticks
|
||||||
chunkList.Clear()
|
chunkList.Clear()
|
||||||
itemIndex = 0
|
itemIndex = 0
|
||||||
inputRevision.bumpMajor()
|
inputRevision.bumpMajor()
|
||||||
@ -476,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
|
||||||
@ -499,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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
@ -136,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|
|
||||||
@ -544,8 +545,8 @@ type Options struct {
|
|||||||
Case Case
|
Case Case
|
||||||
Normalize bool
|
Normalize bool
|
||||||
Nth []Range
|
Nth []Range
|
||||||
WithNth func(Delimiter) func([]Token) string
|
WithNth func(Delimiter) func([]Token, int32) string
|
||||||
AcceptNth func(Delimiter) func([]Token) string
|
AcceptNth func(Delimiter) func([]Token, int32) string
|
||||||
Delimiter Delimiter
|
Delimiter Delimiter
|
||||||
Sort int
|
Sort int
|
||||||
Track trackOption
|
Track trackOption
|
||||||
@ -574,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
|
||||||
@ -629,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 {
|
||||||
@ -689,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: "> ",
|
||||||
@ -727,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}
|
||||||
}
|
}
|
||||||
@ -769,22 +774,22 @@ func splitNth(str string) ([]Range, error) {
|
|||||||
return ranges, nil
|
return ranges, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func nthTransformer(str string) (func(Delimiter) func([]Token) string, error) {
|
func nthTransformer(str string) (func(Delimiter) func([]Token, int32) string, error) {
|
||||||
// ^[0-9,-.]+$"
|
// ^[0-9,-.]+$"
|
||||||
if match, _ := regexp.MatchString("^[0-9,-.]+$", str); match {
|
if match, _ := regexp.MatchString("^[0-9,-.]+$", str); match {
|
||||||
nth, err := splitNth(str)
|
nth, err := splitNth(str)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return func(Delimiter) func([]Token) string {
|
return func(Delimiter) func([]Token, int32) string {
|
||||||
return func(tokens []Token) string {
|
return func(tokens []Token, index int32) string {
|
||||||
return JoinTokens(Transform(tokens, nth))
|
return JoinTokens(Transform(tokens, nth))
|
||||||
}
|
}
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// {...} {...} ...
|
// {...} {...} ...
|
||||||
placeholder := regexp.MustCompile("{[0-9,-.]+}")
|
placeholder := regexp.MustCompile("{[0-9,-.]+}|{n}")
|
||||||
indexes := placeholder.FindAllStringIndex(str, -1)
|
indexes := placeholder.FindAllStringIndex(str, -1)
|
||||||
if indexes == nil {
|
if indexes == nil {
|
||||||
return nil, errors.New("template should include at least 1 placeholder: " + str)
|
return nil, errors.New("template should include at least 1 placeholder: " + str)
|
||||||
@ -792,6 +797,7 @@ func nthTransformer(str string) (func(Delimiter) func([]Token) string, error) {
|
|||||||
|
|
||||||
type NthParts struct {
|
type NthParts struct {
|
||||||
str string
|
str string
|
||||||
|
index bool
|
||||||
nth []Range
|
nth []Range
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -801,7 +807,10 @@ func nthTransformer(str string) (func(Delimiter) func([]Token) string, error) {
|
|||||||
if idx < index[0] {
|
if idx < index[0] {
|
||||||
parts = append(parts, NthParts{str: str[idx:index[0]]})
|
parts = append(parts, NthParts{str: str[idx:index[0]]})
|
||||||
}
|
}
|
||||||
if nth, err := splitNth(str[index[0]+1 : index[1]-1]); err == nil {
|
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})
|
parts = append(parts, NthParts{nth: nth})
|
||||||
}
|
}
|
||||||
idx = index[1]
|
idx = index[1]
|
||||||
@ -810,12 +819,16 @@ func nthTransformer(str string) (func(Delimiter) func([]Token) string, error) {
|
|||||||
parts = append(parts, NthParts{str: str[idx:]})
|
parts = append(parts, NthParts{str: str[idx:]})
|
||||||
}
|
}
|
||||||
|
|
||||||
return func(delimiter Delimiter) func([]Token) string {
|
return func(delimiter Delimiter) func([]Token, int32) string {
|
||||||
return func(tokens []Token) string {
|
return func(tokens []Token, index int32) string {
|
||||||
str := ""
|
str := ""
|
||||||
for _, holder := range parts {
|
for _, holder := range parts {
|
||||||
if holder.nth != nil {
|
if holder.nth != nil {
|
||||||
str += StripLastDelimiter(JoinTokens(Transform(tokens, holder.nth)), delimiter)
|
str += StripLastDelimiter(JoinTokens(Transform(tokens, holder.nth)), delimiter)
|
||||||
|
} else if holder.index {
|
||||||
|
if index >= 0 {
|
||||||
|
str += strconv.Itoa(int(index))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
str += holder.str
|
str += holder.str
|
||||||
}
|
}
|
||||||
@ -1171,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)
|
||||||
@ -1282,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":
|
||||||
@ -1393,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-]+")
|
||||||
}
|
}
|
||||||
@ -1788,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":
|
||||||
@ -1826,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":
|
||||||
@ -2317,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.
|
||||||
@ -2589,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")
|
||||||
}
|
}
|
||||||
|
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,8 +323,10 @@ func (r *Reader) readFiles(roots []string, opts walkerOpts, ignores []string) bo
|
|||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if path != sep {
|
||||||
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}}
|
||||||
|
236
src/terminal.go
236
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,7 +307,7 @@ type Terminal struct {
|
|||||||
nthAttr tui.Attr
|
nthAttr tui.Attr
|
||||||
nth []Range
|
nth []Range
|
||||||
nthCurrent []Range
|
nthCurrent []Range
|
||||||
acceptNth func([]Token) string
|
acceptNth func([]Token, int32) string
|
||||||
tabstop int
|
tabstop int
|
||||||
margin [4]sizeSpec
|
margin [4]sizeSpec
|
||||||
padding [4]sizeSpec
|
padding [4]sizeSpec
|
||||||
@ -379,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
|
||||||
@ -458,6 +461,8 @@ const (
|
|||||||
actStart
|
actStart
|
||||||
actClick
|
actClick
|
||||||
actInvalid
|
actInvalid
|
||||||
|
actBracketedPasteBegin
|
||||||
|
actBracketedPasteEnd
|
||||||
actChar
|
actChar
|
||||||
actMouse
|
actMouse
|
||||||
actBeginningOfLine
|
actBeginningOfLine
|
||||||
@ -471,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
|
||||||
@ -538,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
|
||||||
@ -620,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 {
|
||||||
@ -667,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)
|
||||||
@ -798,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -806,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 {
|
||||||
@ -822,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
|
||||||
@ -847,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,
|
||||||
@ -955,6 +968,7 @@ 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),
|
||||||
@ -1077,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))
|
||||||
}
|
}
|
||||||
@ -1217,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 {
|
||||||
@ -1240,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 {
|
||||||
@ -1299,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
|
||||||
}
|
}
|
||||||
@ -1347,7 +1373,7 @@ func (t *Terminal) numItemLines(item *Item, atMost int) (int, bool) {
|
|||||||
}
|
}
|
||||||
if cached, prs := t.numLinesCache[item.Index()]; prs {
|
if cached, prs := t.numLinesCache[item.Index()]; prs {
|
||||||
// Can we use this cache? Let's be conservative.
|
// Can we use this cache? Let's be conservative.
|
||||||
if cached.atMost >= atMost {
|
if cached.atMost <= atMost {
|
||||||
return cached.numLines, false
|
return cached.numLines, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1408,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
|
||||||
@ -1575,9 +1598,7 @@ func (t *Terminal) output() bool {
|
|||||||
}
|
}
|
||||||
if t.acceptNth != nil {
|
if t.acceptNth != nil {
|
||||||
transform = func(item *Item) string {
|
transform = func(item *Item) string {
|
||||||
tokens := Tokenize(item.AsString(t.ansi), t.delimiter)
|
return item.acceptNth(t.ansi, t.delimiter, t.acceptNth)
|
||||||
transformed := t.acceptNth(tokens)
|
|
||||||
return StripLastDelimiter(transformed, t.delimiter)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
found := len(t.selected) > 0
|
found := len(t.selected) > 0
|
||||||
@ -2288,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))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2332,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() {
|
||||||
@ -2357,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
|
||||||
@ -2476,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) {
|
||||||
@ -2489,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()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2621,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
|
||||||
}
|
}
|
||||||
@ -2755,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)
|
||||||
@ -2788,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 {
|
||||||
@ -2889,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++ {
|
||||||
@ -2987,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() {
|
||||||
@ -3113,7 +3175,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
|||||||
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
|
||||||
@ -3752,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)
|
||||||
@ -3903,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))
|
||||||
@ -3945,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
|
||||||
@ -4006,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()
|
||||||
@ -4015,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()
|
||||||
}
|
}
|
||||||
@ -4023,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()
|
||||||
}
|
}
|
||||||
@ -4599,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]
|
||||||
@ -4611,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
|
||||||
}
|
}
|
||||||
@ -4631,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
|
||||||
@ -4725,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()
|
||||||
@ -4908,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:
|
||||||
@ -4959,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 {
|
||||||
@ -5094,7 +5169,12 @@ 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)
|
||||||
}
|
}
|
||||||
@ -5455,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)
|
||||||
@ -5829,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 {
|
||||||
@ -5913,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
|
||||||
@ -5990,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -6010,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
|
||||||
|
@ -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)
|
||||||
|
@ -225,9 +225,11 @@ func StripLastDelimiter(str string, delimiter Delimiter) string {
|
|||||||
locs := delimiter.regex.FindAllStringIndex(str, -1)
|
locs := delimiter.regex.FindAllStringIndex(str, -1)
|
||||||
if len(locs) > 0 {
|
if len(locs) > 0 {
|
||||||
lastLoc := locs[len(locs)-1]
|
lastLoc := locs[len(locs)-1]
|
||||||
|
if lastLoc[1] == len(str) {
|
||||||
str = str[:lastLoc[0]]
|
str = str[:lastLoc[0]]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return strings.TrimRightFunc(str, unicode.IsSpace)
|
return strings.TrimRightFunc(str, unicode.IsSpace)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,7 +28,7 @@ const (
|
|||||||
maxInputBuffer = 1024 * 1024
|
maxInputBuffer = 1024 * 1024
|
||||||
)
|
)
|
||||||
|
|
||||||
const consoleDevice string = "/dev/tty"
|
const DefaultTtyDevice string = "/dev/tty"
|
||||||
|
|
||||||
var offsetRegexp = regexp.MustCompile("(.*?)\x00?\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")
|
||||||
@ -95,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
|
||||||
@ -120,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
|
||||||
@ -145,13 +146,12 @@ type LightWindow struct {
|
|||||||
wrapSignWidth int
|
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,
|
||||||
@ -213,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")
|
||||||
@ -271,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
|
||||||
@ -462,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':
|
||||||
@ -681,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 {
|
||||||
@ -694,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() {
|
||||||
@ -710,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 {
|
||||||
@ -718,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):
|
||||||
@ -773,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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -197,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 {
|
||||||
@ -266,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
|
||||||
|
@ -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
|
||||||
@ -733,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},
|
||||||
@ -778,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},
|
||||||
@ -823,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},
|
||||||
@ -862,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},
|
||||||
@ -901,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},
|
||||||
|
@ -184,6 +184,11 @@ func (chars *Chars) TrailingWhitespaces() int {
|
|||||||
return whitespaces
|
return whitespaces
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) TrimTrailingWhitespaces() {
|
||||||
|
whitespaces := chars.TrailingWhitespaces()
|
||||||
|
chars.slice = chars.slice[0 : len(chars.slice)-whitespaces]
|
||||||
|
}
|
||||||
|
|
||||||
func (chars *Chars) TrimSuffix(runes []rune) {
|
func (chars *Chars) TrimSuffix(runes []rune) {
|
||||||
lastIdx := len(chars.slice)
|
lastIdx := len(chars.slice)
|
||||||
firstIdx := lastIdx - len(runes)
|
firstIdx := lastIdx - len(runes)
|
||||||
@ -289,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)
|
||||||
@ -304,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
|
||||||
|
|
||||||
@ -1773,12 +1799,144 @@ class TestCore < TestInteractive
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_accept_nth_template
|
def test_accept_nth_regex_delimiter_strip_last
|
||||||
tmux.send_keys %(echo "foo ,bar,baz" | #{FZF} -d, --accept-nth '1st: {1}, 3rd: {3}, 2nd: {2}' --sync --bind start:accept > #{tempname}), :Enter
|
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
|
wait do
|
||||||
assert_path_exists tempname
|
assert_path_exists tempname
|
||||||
# Last delimiter and the whitespaces are removed
|
# Last delimiter and the whitespaces are removed
|
||||||
assert_equal ['1st: foo, 3rd: baz, 2nd: bar'], File.readlines(tempname, chomp: true)
|
assert_equal ['bar:,baz', 'bar:,baz:,qux'], File.readlines(tempname, chomp: true)
|
||||||
end
|
end
|
||||||
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
|
||||||
|
@ -991,4 +991,46 @@ class TestLayout < TestInteractive
|
|||||||
BLOCK
|
BLOCK
|
||||||
tmux.until { assert_block(block, it) }
|
tmux.until { assert_block(block, it) }
|
||||||
end
|
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
|
||||||
|
@ -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