mirror of
https://github.com/junegunn/fzf.git
synced 2025-05-19 04:40:22 -07:00
Compare commits
67 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 |
2
.github/workflows/linux.yml
vendored
2
.github/workflows/linux.yml
vendored
@ -1,5 +1,5 @@
|
||||
---
|
||||
name: Test fzf on Linux
|
||||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -11,3 +11,4 @@ gopath
|
||||
fzf
|
||||
tmp
|
||||
*.patch
|
||||
.idea
|
||||
|
60
CHANGELOG.md
60
CHANGELOG.md
@ -1,6 +1,66 @@
|
||||
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
|
||||
|
@ -155,6 +155,7 @@ let g:fzf_layout = { 'window': '10new' }
|
||||
let g:fzf_colors =
|
||||
\ { 'fg': ['fg', 'Normal'],
|
||||
\ 'bg': ['bg', 'Normal'],
|
||||
\ 'query': ['fg', 'Normal'],
|
||||
\ 'hl': ['fg', 'Comment'],
|
||||
\ 'fg+': ['fg', 'CursorLine', 'CursorColumn', 'Normal'],
|
||||
\ '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
|
||||
|
||||
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/junegunn/go-shellwords v0.0.0-20250127100254-2aa3b3277741
|
||||
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.9/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI=
|
||||
github.com/charlievieth/fastwalk v1.0.10 h1:0qUbvA2O+K+X+IrTfZTC0UH2DK5MOA+KjVfStAHUnGg=
|
||||
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/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
|
||||
github.com/gdamore/tcell/v2 v2.8.1 h1:KPNxyqclpWpWQlPLx6Xui1pMk8S+7+R37h3g07997NU=
|
||||
|
2
install
2
install
@ -2,7 +2,7 @@
|
||||
|
||||
set -u
|
||||
|
||||
version=0.60.3
|
||||
version=0.62.0
|
||||
auto_completion=
|
||||
key_bindings=
|
||||
update_config=2
|
||||
|
@ -1,4 +1,4 @@
|
||||
$version="0.60.3"
|
||||
$version="0.62.0"
|
||||
|
||||
$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"
|
||||
)
|
||||
|
||||
var version = "0.60"
|
||||
var version = "0.62"
|
||||
var revision = "devel"
|
||||
|
||||
//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
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf\-tmux 1 "Mar 2025" "fzf 0.60.3" "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
|
||||
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
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf 1 "Mar 2025" "fzf 0.60.3" "fzf - a command-line fuzzy finder"
|
||||
.TH fzf 1 "May 2025" "fzf 0.62.0" "fzf - a command-line fuzzy finder"
|
||||
|
||||
.SH NAME
|
||||
fzf - a command-line fuzzy finder
|
||||
@ -228,6 +228,13 @@ e.g. \fB# Avoid rendering both fzf instances at the same time
|
||||
(sleep 1; seq 1000000; sleep 1) |
|
||||
fzf \-\-sync \-\-query 5 \-\-listen \-\-bind start:up,load:up,result:up,focus:change\-header:Ready\fR
|
||||
.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
|
||||
.TP
|
||||
.BI "\-\-style=" "PRESET"
|
||||
@ -235,7 +242,7 @@ Apply a style preset [default|minimal|full[:BORDER_STYLE]]
|
||||
.TP
|
||||
.BI "\-\-color=" "[BASE_SCHEME][,COLOR_NAME[:ANSI_COLOR][:ANSI_ATTRIBUTES]]..."
|
||||
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
|
||||
.B BASE SCHEME:
|
||||
@ -263,6 +270,7 @@ color mappings.
|
||||
\fBcurrent\-bg (bg+) \fRBackground (current line)
|
||||
\fBgutter \fRGutter on the left
|
||||
\fBcurrent\-hl (hl+) \fRHighlighted substrings (current line)
|
||||
\fBalt\-bg \fRAlternate background color to create striped lines
|
||||
\fBquery (input\-fg) \fRQuery string
|
||||
\fBdisabled \fRQuery string when search is disabled (\fB\-\-disabled\fR)
|
||||
\fBinfo \fRInfo line (match counters)
|
||||
@ -330,7 +338,19 @@ color mappings.
|
||||
# Seoul256 theme with 24-bit colors
|
||||
fzf \-\-color='bg:#4B4B4B,bg+:#3F3F3F,info:#BDBB72,border:#6B6B6B,spinner:#98BC99' \\
|
||||
\-\-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
|
||||
.TP
|
||||
.B "\-\-no\-color"
|
||||
@ -709,6 +729,10 @@ ANSI color codes are supported.
|
||||
Do not display horizontal separator on the info line. A synonym for
|
||||
\fB\-\-separator=''\fB
|
||||
|
||||
.TP
|
||||
.BI "\-\-ghost=" "TEXT"
|
||||
Ghost text to display when the input is empty
|
||||
|
||||
.TP
|
||||
.B "\-\-filepath\-word"
|
||||
Make word-wise movements and actions respect path separators. The following
|
||||
@ -764,6 +788,12 @@ e.g.
|
||||
\fBfzf \-\-multi \-\-preview='head \-10 {+}'
|
||||
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
|
||||
from the replacement string. To preserve the whitespace, use the \fBs\fR flag.
|
||||
|
||||
@ -1262,10 +1292,20 @@ fzf exports the following environment variables to its child processes.
|
||||
.br
|
||||
.BR FZF_PROMPT " Prompt string"
|
||||
.br
|
||||
.BR FZF_GHOST " Ghost string"
|
||||
.br
|
||||
.BR FZF_POINTER " Pointer string"
|
||||
.br
|
||||
.BR FZF_PREVIEW_LABEL " Preview label string"
|
||||
.br
|
||||
.BR FZF_BORDER_LABEL " Border label string"
|
||||
.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
|
||||
.BR FZF_KEY " The name of the last key pressed"
|
||||
@ -1605,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)
|
||||
\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\-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\-label(...)\fR (change \fB\-\-header\-label\fR to the given string)
|
||||
\fBchange\-input\-label(...)\fR (change \fB\-\-input\-label\fR to the given string)
|
||||
@ -1612,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 a limit or disable it with 0)
|
||||
\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\-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 '|')
|
||||
@ -1700,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)
|
||||
\fBtransform(...)\fR (transform states using the output of 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\-label(...)\fR (transform header 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\-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\-prompt(...)\fR (transform prompt string using an external command)
|
||||
\fBtransform\-query(...)\fR (transform query string using an external command)
|
||||
|
@ -358,7 +358,7 @@ endfunction
|
||||
|
||||
function! s:get_color(attr, ...)
|
||||
" 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 pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$'
|
||||
for group in a:000
|
||||
@ -553,8 +553,15 @@ try
|
||||
let height = s:calc_size(&lines, dict.down, dict)
|
||||
let optstr .= ' --no-tmux --height='.height
|
||||
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'
|
||||
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
|
||||
|
||||
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() {
|
||||
# $1: Prepend 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
|
||||
if [[ $1 =~ dir ]]; then
|
||||
walker=dir,follow
|
||||
rest=${FZF_COMPLETION_DIR_OPTS-}
|
||||
eval "rest=(${FZF_COMPLETION_DIR_OPTS-})"
|
||||
else
|
||||
walker=file,dir,follow,hidden
|
||||
rest=${FZF_COMPLETION_PATH_OPTS-}
|
||||
eval "rest=(${FZF_COMPLETION_PATH_OPTS-})"
|
||||
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
|
||||
printf "%q " "${item%$3}$3"
|
||||
done
|
||||
@ -328,6 +325,8 @@ __fzf_generic_path_completion() {
|
||||
else
|
||||
COMPREPLY=( "$cur" )
|
||||
fi
|
||||
# To redraw line after fzf closes (printf '\e[5n')
|
||||
bind '"\e[0n": redraw-current-line' 2> /dev/null
|
||||
printf '\e[5n'
|
||||
return 0
|
||||
fi
|
||||
@ -384,6 +383,7 @@ _fzf_complete() {
|
||||
else
|
||||
COMPREPLY=("$cur")
|
||||
fi
|
||||
bind '"\e[0n": redraw-current-line' 2> /dev/null
|
||||
printf '\e[5n'
|
||||
return 0
|
||||
else
|
||||
|
@ -14,12 +14,21 @@
|
||||
|
||||
# Key bindings
|
||||
# ------------
|
||||
# For compatibility with fish versions down to 3.1.2, the script does not use:
|
||||
# - The -f/--function switch of command: set
|
||||
# - The process substitution syntax: $(cmd)
|
||||
# - Ranges that omit start/end indexes: $var[$start..] $var[..$end] $var[..]
|
||||
# The oldest supported fish version is 3.1b1. To maintain compatibility, the
|
||||
# command substitution syntax $(cmd) should never be used, even behind a version
|
||||
# check, otherwise the source command will fail on fish versions older than 3.4.0.
|
||||
function fzf_key_bindings
|
||||
|
||||
# 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
|
||||
# $argv[1]: Prepend 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
|
||||
|
||||
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 query
|
||||
set -l commandline (commandline -t | string unescape -n)
|
||||
|
||||
# Strip -option= from token if present
|
||||
set -l prefix (string match -r -- '^-[^\s=]+=' $commandline)
|
||||
set commandline (string replace -- "$prefix" '' $commandline)
|
||||
# Set variables containing the major and minor fish version numbers, using
|
||||
# a method compatible with all supported fish versions.
|
||||
set -l -- fish_major (string match -r -- '^\d+' $version)
|
||||
set -l -- fish_minor (string match -r -- '^\d+\.(\d+)' $version)[2]
|
||||
|
||||
# Enable home directory expansion of leading ~/
|
||||
set commandline (string replace -r -- '^~/' '\$HOME/' $commandline)
|
||||
|
||||
# Escape special characters, except for the $ sign of valid variable names,
|
||||
# so that the original string with expanded variables is returned after eval.
|
||||
set commandline (string escape -n -- $commandline)
|
||||
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)
|
||||
# fish v3.3.0 and newer: Don't use option prefix if " -- " is preceded.
|
||||
set -l -- match_regex '(?<fzf_query>[\s\S]*?(?=\n?$)$)'
|
||||
set -l -- prefix_regex '^-[^\s=]+=|^-(?!-)\S'
|
||||
if test "$fish_major" -eq 3 -a "$fish_minor" -lt 3
|
||||
or string match -q -v -- '* -- *' (string sub -l (commandline -Cp) -- (commandline -p))
|
||||
set -- match_regex "(?<prefix>$prefix_regex)?$match_regex"
|
||||
end
|
||||
|
||||
if test "$dir" = '.'; and test (string sub -l 2 -- $commandline) != './'
|
||||
# If $dir is "." but commandline is not a relative path, this means no file path found
|
||||
set fzf_query $commandline
|
||||
# Set $prefix and expanded $fzf_query with preserved trailing newlines.
|
||||
if test "$fish_major" -ge 4
|
||||
# fish v4.0.0 and newer
|
||||
string match -q -r -- $match_regex (commandline --current-token --tokens-expanded | string collect -N)
|
||||
else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
|
||||
# fish v3.2.0 - v3.7.1 (last v3)
|
||||
string match -q -r -- $match_regex (commandline --current-token --tokenize | string collect -N)
|
||||
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)' '')
|
||||
else
|
||||
# Also remove trailing slash after dir, to "split" input properly
|
||||
set fzf_query (string replace -r -- "^$dir/?" '' $commandline)
|
||||
# fish older than v3.2.0 (v3.1b1 - v3.1.2)
|
||||
set -l -- cl_token (commandline --current-token --tokenize | string collect -N)
|
||||
set -- prefix (string match -r -- $prefix_regex $cl_token)
|
||||
set -- fzf_query (string replace -- "$prefix" '' $cl_token | string collect -N)
|
||||
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)|\\\n\\\n$' '')
|
||||
end
|
||||
|
||||
if test -n "$fzf_query"
|
||||
# Normalize path in $fzf_query, set $dir to the longest existing directory.
|
||||
if test \( "$fish_major" -ge 4 \) -o \( "$fish_major" -eq 3 -a "$fish_minor" -ge 5 \)
|
||||
# fish v3.5.0 and newer
|
||||
set -- fzf_query (path normalize -- $fzf_query)
|
||||
set -- dir $fzf_query
|
||||
while not path is -d $dir
|
||||
set -- dir (path dirname $dir)
|
||||
end
|
||||
else
|
||||
# fish older than v3.5.0 (v3.1b1 - v3.4.1)
|
||||
if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
|
||||
# fish v3.2.0 - v3.4.1
|
||||
string match -q -r -- '(?<fzf_query>^[\s\S]*?(?=\n?$)$)' \
|
||||
(string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\n)$' '' $fzf_query | string collect -N)
|
||||
else
|
||||
# fish v3.1b1 - v3.1.2
|
||||
set -- fzf_query (string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\n)$' '' $fzf_query | string collect -N)
|
||||
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r '\\\n$' '')
|
||||
end
|
||||
set -- dir $fzf_query
|
||||
while not test -d "$dir"
|
||||
set -- dir (dirname -z -- "$dir" | string split0)
|
||||
end
|
||||
end
|
||||
|
||||
if not string match -q -- '.' $dir; or string match -q -r -- '^\./|^\.$' $fzf_query
|
||||
# Strip $dir from $fzf_query - preserve trailing newlines.
|
||||
if test "$fish_major" -ge 4
|
||||
# fish v4.0.0 and newer
|
||||
string match -q -r -- '^'(string escape --style=regex -- $dir)'/?(?<fzf_query>[\s\S]*)' $fzf_query
|
||||
else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
|
||||
# fish v3.2.0 - v3.7.1 (last v3)
|
||||
string match -q -r -- '^/?(?<fzf_query>[\s\S]*?(?=\n?$)$)' \
|
||||
(string replace -- "$dir" '' $fzf_query | string collect -N)
|
||||
else
|
||||
# fish older than v3.2.0 (v3.1b1 - v3.1.2)
|
||||
set -- fzf_query (string replace -- "$dir" '' $fzf_query | string collect -N)
|
||||
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^/?|\\\n$' '')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -95,28 +138,27 @@ function fzf_key_bindings
|
||||
set -l prefix $commandline[3]
|
||||
|
||||
set -lx FZF_DEFAULT_OPTS (__fzf_defaults \
|
||||
"--reverse --walker=file,dir,follow,hidden --scheme=path --walker-root=$dir" \
|
||||
"$FZF_CTRL_T_OPTS --multi")
|
||||
"--reverse --walker=file,dir,follow,hidden --scheme=path" \
|
||||
"$FZF_CTRL_T_OPTS --multi --print0")
|
||||
|
||||
set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND"
|
||||
set -lx FZF_DEFAULT_OPTS_FILE
|
||||
|
||||
if set -l result (eval (__fzfcmd) --query=$fzf_query)
|
||||
# Remove last token from commandline.
|
||||
commandline -t ''
|
||||
for i in $result
|
||||
commandline -it -- $prefix(string escape -- $i)' '
|
||||
end
|
||||
end
|
||||
set -l result (eval (__fzfcmd) --walker-root=$dir --query=$fzf_query | string split0)
|
||||
and commandline -rt -- (string join -- ' ' $prefix(string escape -- $result))' '
|
||||
|
||||
commandline -f repaint
|
||||
end
|
||||
|
||||
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 '' \
|
||||
'--nth=2..,.. --scheme=history --multi --wrap-sign="\t↳ "' \
|
||||
'--bind=\'shift-delete:execute-silent(eval history delete --exact --case-sensitive -- (string escape -n -- {+} | string replace -r -a "^\d*\\\\\\t|(?<=\\\\\\n)\\\\\\t" ""))+reload(eval $FZF_DEFAULT_COMMAND)\'' \
|
||||
"--bind=ctrl-r:toggle-sort --highlight-line $FZF_CTRL_R_OPTS" \
|
||||
'--accept-nth=2.. --read0 --print0 --with-shell='(status fish-path)\\ -c)
|
||||
|
||||
@ -138,9 +180,13 @@ function fzf_key_bindings
|
||||
test -z "$fish_private_mode"; and builtin history merge
|
||||
|
||||
if set -l result (eval $FZF_DEFAULT_COMMAND \| (__fzfcmd) --query=$fzf_query | string split0)
|
||||
commandline -- (string replace -a -- \n\t \n $result[1])
|
||||
test (count $result) -gt 1; and for i in $result[2..-1]
|
||||
commandline -i -- (string replace -a -- \n\t \n \n$i)
|
||||
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
|
||||
|
||||
@ -154,13 +200,13 @@ function fzf_key_bindings
|
||||
set -l prefix $commandline[3]
|
||||
|
||||
set -lx FZF_DEFAULT_OPTS (__fzf_defaults \
|
||||
"--reverse --walker=dir,follow,hidden --scheme=path --walker-root=$dir" \
|
||||
"$FZF_ALT_C_OPTS --no-multi")
|
||||
"--reverse --walker=dir,follow,hidden --scheme=path" \
|
||||
"$FZF_ALT_C_OPTS --no-multi --print0")
|
||||
|
||||
set -lx FZF_DEFAULT_OPTS_FILE
|
||||
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
|
||||
commandline -rt -- $prefix
|
||||
end
|
||||
|
@ -12,139 +12,145 @@ func _() {
|
||||
_ = x[actStart-1]
|
||||
_ = x[actClick-2]
|
||||
_ = x[actInvalid-3]
|
||||
_ = x[actChar-4]
|
||||
_ = x[actMouse-5]
|
||||
_ = x[actBeginningOfLine-6]
|
||||
_ = x[actAbort-7]
|
||||
_ = x[actAccept-8]
|
||||
_ = x[actAcceptNonEmpty-9]
|
||||
_ = x[actAcceptOrPrintQuery-10]
|
||||
_ = x[actBackwardChar-11]
|
||||
_ = x[actBackwardDeleteChar-12]
|
||||
_ = x[actBackwardDeleteCharEof-13]
|
||||
_ = x[actBackwardWord-14]
|
||||
_ = x[actCancel-15]
|
||||
_ = x[actChangeBorderLabel-16]
|
||||
_ = x[actChangeListLabel-17]
|
||||
_ = x[actChangeInputLabel-18]
|
||||
_ = x[actChangeHeader-19]
|
||||
_ = x[actChangeHeaderLabel-20]
|
||||
_ = x[actChangeMulti-21]
|
||||
_ = x[actChangePreviewLabel-22]
|
||||
_ = x[actChangePrompt-23]
|
||||
_ = x[actChangeQuery-24]
|
||||
_ = x[actBracketedPasteBegin-4]
|
||||
_ = x[actBracketedPasteEnd-5]
|
||||
_ = x[actChar-6]
|
||||
_ = x[actMouse-7]
|
||||
_ = x[actBeginningOfLine-8]
|
||||
_ = x[actAbort-9]
|
||||
_ = x[actAccept-10]
|
||||
_ = x[actAcceptNonEmpty-11]
|
||||
_ = x[actAcceptOrPrintQuery-12]
|
||||
_ = x[actBackwardChar-13]
|
||||
_ = x[actBackwardDeleteChar-14]
|
||||
_ = x[actBackwardDeleteCharEof-15]
|
||||
_ = x[actBackwardWord-16]
|
||||
_ = x[actCancel-17]
|
||||
_ = x[actChangeBorderLabel-18]
|
||||
_ = x[actChangeGhost-19]
|
||||
_ = x[actChangeHeader-20]
|
||||
_ = x[actChangeHeaderLabel-21]
|
||||
_ = x[actChangeInputLabel-22]
|
||||
_ = x[actChangeListLabel-23]
|
||||
_ = x[actChangeMulti-24]
|
||||
_ = x[actChangeNth-25]
|
||||
_ = x[actClearScreen-26]
|
||||
_ = x[actClearQuery-27]
|
||||
_ = x[actClearSelection-28]
|
||||
_ = x[actClose-29]
|
||||
_ = x[actDeleteChar-30]
|
||||
_ = x[actDeleteCharEof-31]
|
||||
_ = x[actEndOfLine-32]
|
||||
_ = x[actFatal-33]
|
||||
_ = x[actForwardChar-34]
|
||||
_ = x[actForwardWord-35]
|
||||
_ = x[actKillLine-36]
|
||||
_ = x[actKillWord-37]
|
||||
_ = x[actUnixLineDiscard-38]
|
||||
_ = x[actUnixWordRubout-39]
|
||||
_ = x[actYank-40]
|
||||
_ = x[actBackwardKillWord-41]
|
||||
_ = x[actSelectAll-42]
|
||||
_ = x[actDeselectAll-43]
|
||||
_ = x[actToggle-44]
|
||||
_ = x[actToggleSearch-45]
|
||||
_ = x[actToggleAll-46]
|
||||
_ = x[actToggleDown-47]
|
||||
_ = x[actToggleUp-48]
|
||||
_ = x[actToggleIn-49]
|
||||
_ = x[actToggleOut-50]
|
||||
_ = x[actToggleTrack-51]
|
||||
_ = x[actToggleTrackCurrent-52]
|
||||
_ = x[actToggleHeader-53]
|
||||
_ = x[actToggleWrap-54]
|
||||
_ = x[actToggleMultiLine-55]
|
||||
_ = x[actToggleHscroll-56]
|
||||
_ = x[actTrackCurrent-57]
|
||||
_ = x[actToggleInput-58]
|
||||
_ = x[actHideInput-59]
|
||||
_ = x[actShowInput-60]
|
||||
_ = x[actUntrackCurrent-61]
|
||||
_ = x[actDown-62]
|
||||
_ = x[actUp-63]
|
||||
_ = x[actPageUp-64]
|
||||
_ = x[actPageDown-65]
|
||||
_ = x[actPosition-66]
|
||||
_ = x[actHalfPageUp-67]
|
||||
_ = x[actHalfPageDown-68]
|
||||
_ = x[actOffsetUp-69]
|
||||
_ = x[actOffsetDown-70]
|
||||
_ = x[actOffsetMiddle-71]
|
||||
_ = x[actJump-72]
|
||||
_ = x[actJumpAccept-73]
|
||||
_ = x[actPrintQuery-74]
|
||||
_ = x[actRefreshPreview-75]
|
||||
_ = x[actReplaceQuery-76]
|
||||
_ = x[actToggleSort-77]
|
||||
_ = x[actShowPreview-78]
|
||||
_ = x[actHidePreview-79]
|
||||
_ = x[actTogglePreview-80]
|
||||
_ = x[actTogglePreviewWrap-81]
|
||||
_ = x[actTransform-82]
|
||||
_ = x[actTransformBorderLabel-83]
|
||||
_ = x[actTransformListLabel-84]
|
||||
_ = x[actTransformInputLabel-85]
|
||||
_ = x[actTransformHeader-86]
|
||||
_ = x[actTransformHeaderLabel-87]
|
||||
_ = x[actTransformNth-88]
|
||||
_ = x[actTransformPreviewLabel-89]
|
||||
_ = x[actTransformPrompt-90]
|
||||
_ = x[actTransformQuery-91]
|
||||
_ = x[actTransformSearch-92]
|
||||
_ = x[actSearch-93]
|
||||
_ = x[actPreview-94]
|
||||
_ = x[actChangePreview-95]
|
||||
_ = x[actChangePreviewWindow-96]
|
||||
_ = x[actPreviewTop-97]
|
||||
_ = x[actPreviewBottom-98]
|
||||
_ = x[actPreviewUp-99]
|
||||
_ = x[actPreviewDown-100]
|
||||
_ = x[actPreviewPageUp-101]
|
||||
_ = x[actPreviewPageDown-102]
|
||||
_ = x[actPreviewHalfPageUp-103]
|
||||
_ = x[actPreviewHalfPageDown-104]
|
||||
_ = x[actPrevHistory-105]
|
||||
_ = x[actPrevSelected-106]
|
||||
_ = x[actPrint-107]
|
||||
_ = x[actPut-108]
|
||||
_ = x[actNextHistory-109]
|
||||
_ = x[actNextSelected-110]
|
||||
_ = x[actExecute-111]
|
||||
_ = x[actExecuteSilent-112]
|
||||
_ = x[actExecuteMulti-113]
|
||||
_ = x[actSigStop-114]
|
||||
_ = x[actFirst-115]
|
||||
_ = x[actLast-116]
|
||||
_ = x[actReload-117]
|
||||
_ = x[actReloadSync-118]
|
||||
_ = x[actDisableSearch-119]
|
||||
_ = x[actEnableSearch-120]
|
||||
_ = x[actSelect-121]
|
||||
_ = x[actDeselect-122]
|
||||
_ = x[actUnbind-123]
|
||||
_ = x[actRebind-124]
|
||||
_ = x[actToggleBind-125]
|
||||
_ = x[actBecome-126]
|
||||
_ = x[actShowHeader-127]
|
||||
_ = x[actHideHeader-128]
|
||||
_ = x[actBell-129]
|
||||
_ = x[actExclude-130]
|
||||
_ = x[actExcludeMulti-131]
|
||||
_ = x[actChangePointer-26]
|
||||
_ = x[actChangePreview-27]
|
||||
_ = x[actChangePreviewLabel-28]
|
||||
_ = x[actChangePreviewWindow-29]
|
||||
_ = x[actChangePrompt-30]
|
||||
_ = x[actChangeQuery-31]
|
||||
_ = x[actClearScreen-32]
|
||||
_ = x[actClearQuery-33]
|
||||
_ = x[actClearSelection-34]
|
||||
_ = x[actClose-35]
|
||||
_ = x[actDeleteChar-36]
|
||||
_ = x[actDeleteCharEof-37]
|
||||
_ = x[actEndOfLine-38]
|
||||
_ = x[actFatal-39]
|
||||
_ = x[actForwardChar-40]
|
||||
_ = x[actForwardWord-41]
|
||||
_ = x[actKillLine-42]
|
||||
_ = x[actKillWord-43]
|
||||
_ = x[actUnixLineDiscard-44]
|
||||
_ = x[actUnixWordRubout-45]
|
||||
_ = x[actYank-46]
|
||||
_ = x[actBackwardKillWord-47]
|
||||
_ = x[actSelectAll-48]
|
||||
_ = x[actDeselectAll-49]
|
||||
_ = x[actToggle-50]
|
||||
_ = x[actToggleSearch-51]
|
||||
_ = x[actToggleAll-52]
|
||||
_ = x[actToggleDown-53]
|
||||
_ = x[actToggleUp-54]
|
||||
_ = x[actToggleIn-55]
|
||||
_ = x[actToggleOut-56]
|
||||
_ = x[actToggleTrack-57]
|
||||
_ = x[actToggleTrackCurrent-58]
|
||||
_ = x[actToggleHeader-59]
|
||||
_ = x[actToggleWrap-60]
|
||||
_ = x[actToggleMultiLine-61]
|
||||
_ = x[actToggleHscroll-62]
|
||||
_ = x[actTrackCurrent-63]
|
||||
_ = x[actToggleInput-64]
|
||||
_ = x[actHideInput-65]
|
||||
_ = x[actShowInput-66]
|
||||
_ = x[actUntrackCurrent-67]
|
||||
_ = x[actDown-68]
|
||||
_ = x[actUp-69]
|
||||
_ = x[actPageUp-70]
|
||||
_ = x[actPageDown-71]
|
||||
_ = x[actPosition-72]
|
||||
_ = x[actHalfPageUp-73]
|
||||
_ = x[actHalfPageDown-74]
|
||||
_ = x[actOffsetUp-75]
|
||||
_ = x[actOffsetDown-76]
|
||||
_ = x[actOffsetMiddle-77]
|
||||
_ = x[actJump-78]
|
||||
_ = x[actJumpAccept-79]
|
||||
_ = x[actPrintQuery-80]
|
||||
_ = x[actRefreshPreview-81]
|
||||
_ = x[actReplaceQuery-82]
|
||||
_ = x[actToggleSort-83]
|
||||
_ = x[actShowPreview-84]
|
||||
_ = x[actHidePreview-85]
|
||||
_ = x[actTogglePreview-86]
|
||||
_ = x[actTogglePreviewWrap-87]
|
||||
_ = x[actTransform-88]
|
||||
_ = x[actTransformBorderLabel-89]
|
||||
_ = x[actTransformGhost-90]
|
||||
_ = x[actTransformHeader-91]
|
||||
_ = x[actTransformHeaderLabel-92]
|
||||
_ = x[actTransformInputLabel-93]
|
||||
_ = x[actTransformListLabel-94]
|
||||
_ = x[actTransformNth-95]
|
||||
_ = x[actTransformPointer-96]
|
||||
_ = x[actTransformPreviewLabel-97]
|
||||
_ = x[actTransformPrompt-98]
|
||||
_ = x[actTransformQuery-99]
|
||||
_ = x[actTransformSearch-100]
|
||||
_ = x[actSearch-101]
|
||||
_ = x[actPreview-102]
|
||||
_ = x[actPreviewTop-103]
|
||||
_ = x[actPreviewBottom-104]
|
||||
_ = x[actPreviewUp-105]
|
||||
_ = x[actPreviewDown-106]
|
||||
_ = x[actPreviewPageUp-107]
|
||||
_ = x[actPreviewPageDown-108]
|
||||
_ = x[actPreviewHalfPageUp-109]
|
||||
_ = x[actPreviewHalfPageDown-110]
|
||||
_ = x[actPrevHistory-111]
|
||||
_ = x[actPrevSelected-112]
|
||||
_ = x[actPrint-113]
|
||||
_ = x[actPut-114]
|
||||
_ = x[actNextHistory-115]
|
||||
_ = x[actNextSelected-116]
|
||||
_ = x[actExecute-117]
|
||||
_ = x[actExecuteSilent-118]
|
||||
_ = x[actExecuteMulti-119]
|
||||
_ = x[actSigStop-120]
|
||||
_ = x[actFirst-121]
|
||||
_ = x[actLast-122]
|
||||
_ = x[actReload-123]
|
||||
_ = x[actReloadSync-124]
|
||||
_ = x[actDisableSearch-125]
|
||||
_ = x[actEnableSearch-126]
|
||||
_ = x[actSelect-127]
|
||||
_ = x[actDeselect-128]
|
||||
_ = x[actUnbind-129]
|
||||
_ = x[actRebind-130]
|
||||
_ = x[actToggleBind-131]
|
||||
_ = x[actBecome-132]
|
||||
_ = x[actShowHeader-133]
|
||||
_ = x[actHideHeader-134]
|
||||
_ = x[actBell-135]
|
||||
_ = x[actExclude-136]
|
||||
_ = x[actExcludeMulti-137]
|
||||
}
|
||||
|
||||
const _actionType_name = "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 {
|
||||
if i < 0 || i >= actionType(len(_actionType_index)-1) {
|
||||
|
@ -295,6 +295,7 @@ func Run(opts *Options) (int, error) {
|
||||
// Event coordination
|
||||
reading := true
|
||||
ticks := 0
|
||||
startTick := 0
|
||||
var nextCommand *commandSpec
|
||||
var nextEnviron []string
|
||||
eventBox.Watch(EvtReadNew)
|
||||
@ -321,6 +322,7 @@ func Run(opts *Options) (int, error) {
|
||||
clearDenylist()
|
||||
}
|
||||
reading = true
|
||||
startTick = ticks
|
||||
chunkList.Clear()
|
||||
itemIndex = 0
|
||||
inputRevision.bumpMajor()
|
||||
@ -509,7 +511,7 @@ func Run(opts *Options) (int, error) {
|
||||
}
|
||||
if delay && reading {
|
||||
dur := util.DurWithin(
|
||||
time.Duration(ticks)*coordinatorDelayStep,
|
||||
time.Duration(ticks-startTick)*coordinatorDelayStep,
|
||||
0, coordinatorDelayMax)
|
||||
time.Sleep(dur)
|
||||
}
|
||||
|
@ -136,6 +136,7 @@ Usage: fzf [options]
|
||||
--separator=STR Draw horizontal separator on info line using the string
|
||||
(default: '─' or '-')
|
||||
--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
|
||||
--input-border[=STYLE] Draw border around the input section
|
||||
[rounded|sharp|bold|block|thinblock|double|horizontal|vertical|
|
||||
@ -574,6 +575,7 @@ type Options struct {
|
||||
InfoStyle infoStyle
|
||||
InfoPrefix string
|
||||
InfoCommand string
|
||||
Ghost string
|
||||
Separator *string
|
||||
JumpLabels string
|
||||
Prompt string
|
||||
@ -629,6 +631,7 @@ type Options struct {
|
||||
MEMProfile string
|
||||
BlockProfile string
|
||||
MutexProfile string
|
||||
TtyDefault string
|
||||
}
|
||||
|
||||
func filterNonEmpty(input []string) []string {
|
||||
@ -689,6 +692,7 @@ func defaultOptions() *Options {
|
||||
ScrollOff: 3,
|
||||
FileWord: false,
|
||||
InfoStyle: infoDefault,
|
||||
Ghost: "",
|
||||
Separator: nil,
|
||||
JumpLabels: defaultJumpLabels,
|
||||
Prompt: "> ",
|
||||
@ -727,6 +731,7 @@ func defaultOptions() *Options {
|
||||
WalkerOpts: walkerOpts{file: true, hidden: true, follow: true},
|
||||
WalkerRoot: []string{"."},
|
||||
WalkerSkip: []string{".git", "node_modules"},
|
||||
TtyDefault: tui.DefaultTtyDevice,
|
||||
Help: false,
|
||||
Version: false}
|
||||
}
|
||||
@ -1179,7 +1184,12 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro
|
||||
var err error
|
||||
theme := dupeTheme(defaultTheme)
|
||||
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 {
|
||||
case "dark":
|
||||
theme = dupeTheme(tui.Dark256)
|
||||
@ -1290,6 +1300,8 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro
|
||||
mergeAttr(&theme.Current)
|
||||
case "current-bg", "bg+":
|
||||
mergeAttr(&theme.DarkBg)
|
||||
case "alt-bg":
|
||||
mergeAttr(&theme.AltBg)
|
||||
case "selected-fg":
|
||||
mergeAttr(&theme.SelectedFg)
|
||||
case "selected-bg":
|
||||
@ -1401,7 +1413,7 @@ const (
|
||||
|
||||
func init() {
|
||||
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("[,:]+")
|
||||
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
|
||||
}
|
||||
@ -1796,6 +1808,10 @@ func isExecuteAction(str string) actionType {
|
||||
return actChangeInputLabel
|
||||
case "change-header-label":
|
||||
return actChangeHeaderLabel
|
||||
case "change-ghost":
|
||||
return actChangeGhost
|
||||
case "change-pointer":
|
||||
return actChangePointer
|
||||
case "change-preview-window":
|
||||
return actChangePreviewWindow
|
||||
case "change-preview":
|
||||
@ -1834,8 +1850,12 @@ func isExecuteAction(str string) actionType {
|
||||
return actTransformHeaderLabel
|
||||
case "transform-header":
|
||||
return actTransformHeader
|
||||
case "transform-ghost":
|
||||
return actTransformGhost
|
||||
case "transform-nth":
|
||||
return actTransformNth
|
||||
case "transform-pointer":
|
||||
return actTransformPointer
|
||||
case "transform-prompt":
|
||||
return actTransformPrompt
|
||||
case "transform-query":
|
||||
@ -2325,6 +2345,12 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
||||
}
|
||||
case "--no-tmux":
|
||||
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":
|
||||
// NOTE: We need this because `system('fzf --tmux < /dev/tty')` doesn't
|
||||
// work on Neovim. Same as '-' option of fzf-tmux.
|
||||
@ -2597,6 +2623,10 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
||||
case "--no-separator":
|
||||
nosep := ""
|
||||
opts.Separator = &nosep
|
||||
case "--ghost":
|
||||
if opts.Ghost, err = nextString("ghost text required"); err != nil {
|
||||
return err
|
||||
}
|
||||
case "--scrollbar":
|
||||
given, bar := optionalNextString()
|
||||
if given {
|
||||
|
@ -333,7 +333,7 @@ func TestColorSpec(t *testing.T) {
|
||||
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 {
|
||||
t.Errorf("color not customized")
|
||||
}
|
||||
|
@ -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,
|
||||
// we need to write the command to a temporary file and execute it with sh.
|
||||
var exports []string
|
||||
// * Write the command to a temporary file and run it with sh to ensure POSIX compliance.
|
||||
// * Nullify FZF_DEFAULT_* variables as tmux popup may inject them even when undefined.
|
||||
exports := []string{"FZF_DEFAULT_COMMAND=", "FZF_DEFAULT_OPTS=", "FZF_DEFAULT_OPTS_FILE="}
|
||||
needBash := false
|
||||
if withExports {
|
||||
validIdentifier := regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
|
||||
@ -144,7 +144,7 @@ func runProxy(commandPrefix string, cmdBuilder func(temp string, needBash bool)
|
||||
env = elems[1:]
|
||||
}
|
||||
executor := util.NewExecutor(opts.WithShell)
|
||||
ttyin, err := tui.TtyIn()
|
||||
ttyin, err := tui.TtyIn(opts.TtyDefault)
|
||||
if err != nil {
|
||||
return ExitError, err
|
||||
}
|
||||
|
@ -323,8 +323,10 @@ func (r *Reader) readFiles(roots []string, opts walkerOpts, ignores []string) bo
|
||||
return filepath.SkipDir
|
||||
}
|
||||
}
|
||||
if path != sep {
|
||||
path += sep
|
||||
}
|
||||
}
|
||||
if ((opts.file && !isDir) || (opts.dir && isDir)) && r.pusher(stringBytes(path)) {
|
||||
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}}
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
// No ANSI codes
|
||||
@ -182,18 +182,10 @@ func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, t
|
||||
fg := ansi.color.fg
|
||||
bg := ansi.color.bg
|
||||
if fg == -1 {
|
||||
if current {
|
||||
fg = theme.Current.Color
|
||||
} else {
|
||||
fg = theme.Fg.Color
|
||||
}
|
||||
fg = colBase.Fg()
|
||||
}
|
||||
if bg == -1 {
|
||||
if current {
|
||||
bg = theme.DarkBg.Color
|
||||
} else {
|
||||
bg = theme.Bg.Color
|
||||
}
|
||||
bg = colBase.Bg()
|
||||
}
|
||||
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)
|
||||
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) {
|
||||
o := colors[idx]
|
||||
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}}
|
||||
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}}
|
||||
// {[22 25] {2 6 1}} {[25 27] {2 6 9}} {[27 30] {-1 -1 8}}
|
||||
|
215
src/terminal.go
215
src/terminal.go
@ -38,7 +38,7 @@ As such it is not useful for validation, but rather to generate test
|
||||
cases for example.
|
||||
|
||||
\\?(?: # escaped type
|
||||
{\+?s?f?RANGE(?:,RANGE)*} # token type
|
||||
{\+?s?f?r?RANGE(?:,RANGE)*} # token type
|
||||
{q[:s?RANGE]} # query type
|
||||
|{\+?n?f?} # item type (notice no mandatory element inside brackets)
|
||||
)
|
||||
@ -65,7 +65,7 @@ const maxFocusEvents = 10000
|
||||
const blockDuration = 1 * time.Second
|
||||
|
||||
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*$`)
|
||||
offsetComponentRegex = regexp.MustCompile(`([+-][0-9]+)|(-?/[1-9][0-9]*)`)
|
||||
offsetTrimCharsRegex = regexp.MustCompile(`[^0-9/+-]`)
|
||||
@ -234,6 +234,7 @@ type Terminal struct {
|
||||
wrap bool
|
||||
wrapSign string
|
||||
wrapSignWidth int
|
||||
ghost string
|
||||
separator labelPrinter
|
||||
separatorLen int
|
||||
spinner []string
|
||||
@ -278,6 +279,7 @@ type Terminal struct {
|
||||
yanked []rune
|
||||
input []rune
|
||||
inputOverride *[]rune
|
||||
pasting *[]rune
|
||||
multi int
|
||||
multiLine bool
|
||||
sort bool
|
||||
@ -379,6 +381,7 @@ type Terminal struct {
|
||||
slab *util.Slab
|
||||
theme *tui.ColorTheme
|
||||
tui tui.Renderer
|
||||
ttyDefault string
|
||||
ttyin *os.File
|
||||
executing *util.AtomicBool
|
||||
termSize tui.TermSize
|
||||
@ -458,6 +461,8 @@ const (
|
||||
actStart
|
||||
actClick
|
||||
actInvalid
|
||||
actBracketedPasteBegin
|
||||
actBracketedPasteEnd
|
||||
actChar
|
||||
actMouse
|
||||
actBeginningOfLine
|
||||
@ -471,15 +476,19 @@ const (
|
||||
actBackwardWord
|
||||
actCancel
|
||||
actChangeBorderLabel
|
||||
actChangeListLabel
|
||||
actChangeInputLabel
|
||||
actChangeGhost
|
||||
actChangeHeader
|
||||
actChangeHeaderLabel
|
||||
actChangeInputLabel
|
||||
actChangeListLabel
|
||||
actChangeMulti
|
||||
actChangeNth
|
||||
actChangePointer
|
||||
actChangePreview
|
||||
actChangePreviewLabel
|
||||
actChangePreviewWindow
|
||||
actChangePrompt
|
||||
actChangeQuery
|
||||
actChangeNth
|
||||
actClearScreen
|
||||
actClearQuery
|
||||
actClearSelection
|
||||
@ -538,19 +547,19 @@ const (
|
||||
actTogglePreviewWrap
|
||||
actTransform
|
||||
actTransformBorderLabel
|
||||
actTransformListLabel
|
||||
actTransformInputLabel
|
||||
actTransformGhost
|
||||
actTransformHeader
|
||||
actTransformHeaderLabel
|
||||
actTransformInputLabel
|
||||
actTransformListLabel
|
||||
actTransformNth
|
||||
actTransformPointer
|
||||
actTransformPreviewLabel
|
||||
actTransformPrompt
|
||||
actTransformQuery
|
||||
actTransformSearch
|
||||
actSearch
|
||||
actPreview
|
||||
actChangePreview
|
||||
actChangePreviewWindow
|
||||
actPreviewTop
|
||||
actPreviewBottom
|
||||
actPreviewUp
|
||||
@ -620,6 +629,7 @@ type placeholderFlags struct {
|
||||
number bool
|
||||
forceUpdate bool
|
||||
file bool
|
||||
raw bool
|
||||
}
|
||||
|
||||
type searchRequest struct {
|
||||
@ -667,6 +677,8 @@ func defaultKeymap() map[tui.Event][]*action {
|
||||
|
||||
add(tui.Fatal, actFatal)
|
||||
add(tui.Invalid, actInvalid)
|
||||
add(tui.BracketedPasteBegin, actBracketedPasteBegin)
|
||||
add(tui.BracketedPasteEnd, actBracketedPasteEnd)
|
||||
add(tui.CtrlA, actBeginningOfLine)
|
||||
add(tui.CtrlB, actBackwardChar)
|
||||
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
|
||||
// cause problems with 'become' action and invalid terminal state after exit.
|
||||
if ttyin == nil {
|
||||
if ttyin, err = tui.TtyIn(); err != nil {
|
||||
if ttyin, err = tui.TtyIn(opts.TtyDefault); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@ -806,7 +818,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
||||
if tui.HasFullscreenRenderer() {
|
||||
renderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse)
|
||||
} 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 })
|
||||
}
|
||||
} else {
|
||||
@ -822,7 +834,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
||||
effectiveMinHeight += borderLines(opts.BorderShape)
|
||||
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 {
|
||||
return nil, err
|
||||
@ -847,6 +859,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
||||
infoCommand: opts.InfoCommand,
|
||||
infoStyle: opts.InfoStyle,
|
||||
infoPrefix: opts.InfoPrefix,
|
||||
ghost: opts.Ghost,
|
||||
separator: nil,
|
||||
spinner: makeSpinner(opts.Unicode),
|
||||
promptString: opts.Prompt,
|
||||
@ -955,6 +968,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
||||
keyChan: make(chan tui.Event),
|
||||
eventChan: make(chan tui.Event, 6), // start | (load + result + zero|one) | (focus) | (resize)
|
||||
tui: renderer,
|
||||
ttyDefault: opts.TtyDefault,
|
||||
ttyin: ttyin,
|
||||
initFunc: func() error { return renderer.Init() },
|
||||
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_KEY="+t.lastKey)
|
||||
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_BORDER_LABEL="+t.borderLabelOpts.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 {
|
||||
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
|
||||
}
|
||||
printFn := func(window tui.Window, limit int) {
|
||||
if length > limit {
|
||||
trimmedRunes, _ := t.trimRight(runes, limit)
|
||||
window.CPrint(*color, string(trimmedRunes))
|
||||
ellipsis := []rune{}
|
||||
ellipsisWidth := 0
|
||||
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 {
|
||||
window.CPrint(*color, util.RepeatToFill(text, length, limit))
|
||||
} else {
|
||||
@ -1240,7 +1263,7 @@ func (t *Terminal) ansiLabelPrinter(str string, color *tui.ColorPair, fill bool)
|
||||
printFn := func(window tui.Window, limit int) {
|
||||
if offsets == nil {
|
||||
// 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 {
|
||||
if length > limit {
|
||||
@ -1299,8 +1322,11 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) {
|
||||
t.wrap = false
|
||||
t.withWindow(t.inputWindow, func() {
|
||||
line := t.promptLine()
|
||||
preTask := func(markerClass) int {
|
||||
return 1
|
||||
}
|
||||
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
|
||||
}
|
||||
@ -1408,10 +1434,7 @@ func (t *Terminal) Input() (bool, []rune) {
|
||||
t.mutex.Lock()
|
||||
defer t.mutex.Unlock()
|
||||
paused := t.paused
|
||||
var src []rune
|
||||
if !t.inputless {
|
||||
src = t.input
|
||||
}
|
||||
src := t.input
|
||||
if t.inputOverride != nil {
|
||||
paused = false
|
||||
src = *t.inputOverride
|
||||
@ -2334,15 +2357,18 @@ func (t *Terminal) placeCursor() {
|
||||
if t.inputless {
|
||||
return
|
||||
}
|
||||
x := t.promptLen + t.queryLen[0]
|
||||
if t.inputWindow != nil {
|
||||
y := t.inputWindow.Height() - 1
|
||||
if t.layout == layoutReverse {
|
||||
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
|
||||
}
|
||||
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() {
|
||||
@ -2359,6 +2385,11 @@ func (t *Terminal) printPrompt() {
|
||||
t.prompt()
|
||||
|
||||
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
|
||||
if t.paused {
|
||||
color = tui.ColDisabled
|
||||
@ -2478,6 +2509,10 @@ func (t *Terminal) printInfoImpl() {
|
||||
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 {
|
||||
case infoDefault:
|
||||
if !move(line+1, 0, t.separatorLen == 0) {
|
||||
@ -2491,9 +2526,9 @@ func (t *Terminal) printInfoImpl() {
|
||||
return
|
||||
}
|
||||
case infoInlineRight:
|
||||
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
||||
pos = t.promptLen + shiftLen
|
||||
case infoInline:
|
||||
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
||||
pos = t.promptLen + shiftLen
|
||||
printInfoPrefix()
|
||||
}
|
||||
|
||||
@ -2623,6 +2658,9 @@ func (t *Terminal) headerIndent(borderShape tui.BorderShape) int {
|
||||
}
|
||||
if borderShape.HasLeft() {
|
||||
indentSize -= 1 + t.borderWidth
|
||||
if indentSize < 0 {
|
||||
indentSize = 0
|
||||
}
|
||||
}
|
||||
return indentSize
|
||||
}
|
||||
@ -2758,18 +2796,29 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
|
||||
_, selected := t.selected[item.Index()]
|
||||
label := ""
|
||||
extraWidth := 0
|
||||
alt := false
|
||||
altBg := t.theme.AltBg
|
||||
selectedBg := selected && t.theme.SelectedBg != t.theme.ListBg
|
||||
if t.jumping != jumpDisabled {
|
||||
if index < len(t.jumpLabels) {
|
||||
// Striped
|
||||
current = index%2 == 0
|
||||
if !altBg.IsColorDefined() {
|
||||
altBg = t.theme.DarkBg
|
||||
alt = index%2 == 0
|
||||
} else {
|
||||
alt = index%2 == 1
|
||||
}
|
||||
label = t.jumpLabels[index:index+1] + strings.Repeat(" ", util.Max(0, t.pointerLen-1))
|
||||
if t.pointerLen == 0 {
|
||||
extraWidth = 1
|
||||
}
|
||||
}
|
||||
} else if current {
|
||||
} else {
|
||||
if current {
|
||||
label = t.pointer
|
||||
}
|
||||
alt = !selectedBg && altBg.IsColorDefined() && index%2 == 1
|
||||
}
|
||||
|
||||
// Avoid unnecessary redraw
|
||||
numLines, _ := t.numItemLines(item, maxLine-line+1)
|
||||
@ -2795,10 +2844,12 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
|
||||
maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1)
|
||||
postTask := func(lineNum int, width int, wrapped bool, forceRedraw bool) {
|
||||
width += extraWidth
|
||||
if (current || selected) && t.highlightLine {
|
||||
if (current || selected || alt) && t.highlightLine {
|
||||
color := tui.ColSelected
|
||||
if current {
|
||||
color = tui.ColCurrent
|
||||
} else if alt {
|
||||
color = color.WithBg(altBg)
|
||||
}
|
||||
fillSpaces := maxWidth - width
|
||||
if wrapped {
|
||||
@ -2896,6 +2947,10 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
|
||||
base = tui.ColNormal
|
||||
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)
|
||||
}
|
||||
for i := 0; i < t.gap && finalLineNum < maxLine; i++ {
|
||||
@ -2994,7 +3049,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
||||
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
|
||||
if t.canSpanMultiLines() {
|
||||
@ -3120,7 +3175,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
||||
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]
|
||||
} else {
|
||||
wrapped = true
|
||||
@ -3759,6 +3814,8 @@ func parsePlaceholder(match string) (bool, string, placeholderFlags) {
|
||||
flags.number = true
|
||||
case 'f':
|
||||
flags.file = true
|
||||
case 'r':
|
||||
flags.raw = true
|
||||
case 'q':
|
||||
flags.forceUpdate = true
|
||||
trimmed += string(char)
|
||||
@ -3910,7 +3967,7 @@ func replacePlaceholder(params replacePlaceholderParams) (string, []string) {
|
||||
return "''"
|
||||
}
|
||||
return strconv.Itoa(int(n))
|
||||
case flags.file:
|
||||
case flags.file || flags.raw:
|
||||
return item.AsString(params.stripAnsi)
|
||||
default:
|
||||
return params.executor.QuoteEntry(item.AsString(params.stripAnsi))
|
||||
@ -3952,7 +4009,7 @@ func replacePlaceholder(params replacePlaceholderParams) (string, []string) {
|
||||
if !flags.preserveSpace {
|
||||
str = strings.TrimSpace(str)
|
||||
}
|
||||
if !flags.file {
|
||||
if !flags.file && !flags.raw {
|
||||
str = params.executor.QuoteEntry(str)
|
||||
}
|
||||
return str
|
||||
@ -4013,7 +4070,7 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
||||
t.executing.Set(true)
|
||||
if !background {
|
||||
// Open a separate handle for tty input
|
||||
if in, _ := tui.TtyIn(); in != nil {
|
||||
if in, _ := tui.TtyIn(t.ttyDefault); in != nil {
|
||||
cmd.Stdin = in
|
||||
if in != os.Stdin {
|
||||
defer in.Close()
|
||||
@ -4022,7 +4079,7 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
||||
|
||||
cmd.Stdout = os.Stdout
|
||||
if !util.IsTty(os.Stdout) {
|
||||
if out, _ := tui.TtyOut(); out != nil {
|
||||
if out, _ := tui.TtyOut(t.ttyDefault); out != nil {
|
||||
cmd.Stdout = out
|
||||
defer out.Close()
|
||||
}
|
||||
@ -4030,7 +4087,7 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
||||
|
||||
cmd.Stderr = os.Stderr
|
||||
if !util.IsTty(os.Stderr) {
|
||||
if out, _ := tui.TtyOut(); out != nil {
|
||||
if out, _ := tui.TtyOut(t.ttyDefault); out != nil {
|
||||
cmd.Stderr = out
|
||||
defer out.Close()
|
||||
}
|
||||
@ -4606,11 +4663,7 @@ func (t *Terminal) Loop() error {
|
||||
// U t.uiMutex |
|
||||
t.uiMutex.Lock()
|
||||
t.mutex.Lock()
|
||||
printInfo := util.RunOnce(func() {
|
||||
if !t.resizeIfNeeded() {
|
||||
t.printInfo()
|
||||
}
|
||||
})
|
||||
info := false
|
||||
for _, key := range keys {
|
||||
req := util.EventType(key)
|
||||
value := (*events)[req]
|
||||
@ -4618,16 +4671,15 @@ func (t *Terminal) Loop() error {
|
||||
case reqPrompt:
|
||||
t.printPrompt()
|
||||
if t.infoStyle == infoInline || t.infoStyle == infoInlineRight {
|
||||
printInfo()
|
||||
info = true
|
||||
}
|
||||
case reqInfo:
|
||||
printInfo()
|
||||
info = true
|
||||
case reqList:
|
||||
t.printList()
|
||||
currentIndex := t.currentIndex()
|
||||
focusChanged := focusedIndex != currentIndex
|
||||
info := false
|
||||
if focusChanged && t.track == trackCurrent {
|
||||
if focusChanged && focusedIndex >= 0 && t.track == trackCurrent {
|
||||
t.track = trackDisabled
|
||||
info = true
|
||||
}
|
||||
@ -4638,9 +4690,6 @@ func (t *Terminal) Loop() error {
|
||||
info = true
|
||||
}
|
||||
}
|
||||
if info {
|
||||
printInfo()
|
||||
}
|
||||
if focusChanged || version != t.version {
|
||||
version = t.version
|
||||
focusedIndex = currentIndex
|
||||
@ -4732,6 +4781,9 @@ func (t *Terminal) Loop() error {
|
||||
return
|
||||
}
|
||||
}
|
||||
if info && !t.resizeIfNeeded() {
|
||||
t.printInfo()
|
||||
}
|
||||
t.flush()
|
||||
t.mutex.Unlock()
|
||||
t.uiMutex.Unlock()
|
||||
@ -4915,6 +4967,14 @@ func (t *Terminal) Loop() error {
|
||||
return true
|
||||
}
|
||||
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:
|
||||
switch a.t {
|
||||
case actIgnore, actStart, actClick:
|
||||
@ -4966,6 +5026,14 @@ func (t *Terminal) Loop() error {
|
||||
case actInvalid:
|
||||
t.mutex.Unlock()
|
||||
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:
|
||||
var act bool
|
||||
switch a.t {
|
||||
@ -5101,7 +5169,12 @@ func (t *Terminal) Loop() error {
|
||||
header = t.captureLines(a.a)
|
||||
}
|
||||
if t.changeHeader(header) {
|
||||
if t.headerWindow != nil {
|
||||
// Need to resize header window
|
||||
req(reqFullRedraw)
|
||||
} else {
|
||||
req(reqHeader, reqList, reqPrompt, reqInfo)
|
||||
}
|
||||
} else {
|
||||
req(reqHeader)
|
||||
}
|
||||
@ -5462,6 +5535,7 @@ func (t *Terminal) Loop() error {
|
||||
t.scrollOff = t.window.Height()
|
||||
t.constrain()
|
||||
t.scrollOff = soff
|
||||
req(reqList)
|
||||
case actJump:
|
||||
t.jumping = jumpEnabled
|
||||
req(reqJump)
|
||||
@ -5836,7 +5910,7 @@ func (t *Terminal) Loop() error {
|
||||
|
||||
if me.Down {
|
||||
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
|
||||
t.cx = mxCons + t.xoffset
|
||||
} else if my >= min {
|
||||
@ -5920,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:
|
||||
if t.previewOpts.command != a.a {
|
||||
t.previewOpts.command = a.a
|
||||
@ -5997,6 +6095,15 @@ func (t *Terminal) Loop() error {
|
||||
if !processExecution(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
|
||||
}
|
||||
|
||||
@ -6017,18 +6124,10 @@ func (t *Terminal) Loop() error {
|
||||
} else if !doActions(actions) {
|
||||
continue
|
||||
}
|
||||
if t.inputless {
|
||||
// Always just discard the change
|
||||
t.input = previousInput
|
||||
t.cx = len(t.input)
|
||||
beof = false
|
||||
} else {
|
||||
if !t.inputless {
|
||||
t.truncateQuery()
|
||||
}
|
||||
queryChanged = string(previousInput) != string(t.input)
|
||||
if queryChanged {
|
||||
t.inputOverride = nil
|
||||
}
|
||||
queryChanged = queryChanged || t.pasting == nil && string(previousInput) != string(t.input)
|
||||
changed = changed || queryChanged
|
||||
if onChanges, prs := t.keymap[tui.Change.AsEvent()]; queryChanged && prs && !doActions(onChanges) {
|
||||
continue
|
||||
|
@ -75,6 +75,14 @@ func TestReplacePlaceholder(t *testing.T) {
|
||||
result = replacePlaceholderTest("echo {}", true, Delimiter{}, printsep, false, "query", items1)
|
||||
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
|
||||
result = replacePlaceholderTest("echo {}", true, Delimiter{}, printsep, false, "query", items2)
|
||||
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.
|
||||
func templateToString(format string, data interface{}) string {
|
||||
func templateToString(format string, data any) string {
|
||||
bb := &bytes.Buffer{}
|
||||
|
||||
err := template.Must(template.New("").Parse(format)).Execute(bb, data)
|
||||
|
@ -84,35 +84,37 @@ func _() {
|
||||
_ = x[CtrlAlt-73]
|
||||
_ = x[Invalid-74]
|
||||
_ = x[Fatal-75]
|
||||
_ = x[Mouse-76]
|
||||
_ = x[DoubleClick-77]
|
||||
_ = x[LeftClick-78]
|
||||
_ = x[RightClick-79]
|
||||
_ = x[SLeftClick-80]
|
||||
_ = x[SRightClick-81]
|
||||
_ = x[ScrollUp-82]
|
||||
_ = x[ScrollDown-83]
|
||||
_ = x[SScrollUp-84]
|
||||
_ = x[SScrollDown-85]
|
||||
_ = x[PreviewScrollUp-86]
|
||||
_ = x[PreviewScrollDown-87]
|
||||
_ = x[Resize-88]
|
||||
_ = x[Change-89]
|
||||
_ = x[BackwardEOF-90]
|
||||
_ = x[Start-91]
|
||||
_ = x[Load-92]
|
||||
_ = x[Focus-93]
|
||||
_ = x[One-94]
|
||||
_ = x[Zero-95]
|
||||
_ = x[Result-96]
|
||||
_ = x[Jump-97]
|
||||
_ = x[JumpCancel-98]
|
||||
_ = x[ClickHeader-99]
|
||||
_ = x[BracketedPasteBegin-76]
|
||||
_ = x[BracketedPasteEnd-77]
|
||||
_ = x[Mouse-78]
|
||||
_ = x[DoubleClick-79]
|
||||
_ = x[LeftClick-80]
|
||||
_ = x[RightClick-81]
|
||||
_ = x[SLeftClick-82]
|
||||
_ = x[SRightClick-83]
|
||||
_ = x[ScrollUp-84]
|
||||
_ = x[ScrollDown-85]
|
||||
_ = x[SScrollUp-86]
|
||||
_ = x[SScrollDown-87]
|
||||
_ = x[PreviewScrollUp-88]
|
||||
_ = x[PreviewScrollDown-89]
|
||||
_ = x[Resize-90]
|
||||
_ = x[Change-91]
|
||||
_ = x[BackwardEOF-92]
|
||||
_ = x[Start-93]
|
||||
_ = x[Load-94]
|
||||
_ = x[Focus-95]
|
||||
_ = x[One-96]
|
||||
_ = x[Zero-97]
|
||||
_ = x[Result-98]
|
||||
_ = 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 {
|
||||
if i < 0 || i >= EventType(len(_EventType_index)-1) {
|
||||
|
@ -28,7 +28,7 @@ const (
|
||||
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 offsetRegexpBegin = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R")
|
||||
@ -146,8 +146,8 @@ type LightWindow struct {
|
||||
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) {
|
||||
out, err := openTtyOut()
|
||||
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(ttyDefault)
|
||||
if err != nil {
|
||||
out = os.Stderr
|
||||
}
|
||||
@ -213,7 +213,7 @@ func (r *LightRenderer) Init() error {
|
||||
}
|
||||
}
|
||||
|
||||
r.enableMouse()
|
||||
r.enableModes()
|
||||
r.csi(fmt.Sprintf("%dA", r.MaxY()-1))
|
||||
r.csi("G")
|
||||
r.csi("K")
|
||||
@ -271,7 +271,7 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) ([]byte,
|
||||
c, ok := r.getch(nonblock)
|
||||
if !nonblock && !ok {
|
||||
r.Close()
|
||||
return nil, errors.New("failed to read " + consoleDevice)
|
||||
return nil, errors.New("failed to read " + DefaultTtyDevice)
|
||||
}
|
||||
|
||||
retries := 0
|
||||
@ -462,10 +462,11 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
}
|
||||
// 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] == '~' {
|
||||
// Immediately discard the sequence from the buffer and reread input
|
||||
r.buffer = r.buffer[6:]
|
||||
*sz = 0
|
||||
return r.GetChar()
|
||||
*sz = 6
|
||||
if r.buffer[4] == '0' {
|
||||
return Event{BracketedPasteBegin, 0, nil}
|
||||
}
|
||||
return Event{BracketedPasteEnd, 0, nil}
|
||||
}
|
||||
return Event{Invalid, 0, nil} // INS
|
||||
case '3':
|
||||
@ -681,7 +682,7 @@ func (r *LightRenderer) rmcup() {
|
||||
}
|
||||
|
||||
func (r *LightRenderer) Pause(clear bool) {
|
||||
r.disableMouse()
|
||||
r.disableModes()
|
||||
r.restoreTerminal()
|
||||
if clear {
|
||||
if r.fullscreen {
|
||||
@ -694,12 +695,13 @@ func (r *LightRenderer) Pause(clear bool) {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *LightRenderer) enableMouse() {
|
||||
func (r *LightRenderer) enableModes() {
|
||||
if r.mouse {
|
||||
r.csi("?1000h")
|
||||
r.csi("?1002h")
|
||||
r.csi("?1006h")
|
||||
}
|
||||
r.csi("?2004h") // Enable bracketed paste mode
|
||||
}
|
||||
|
||||
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) {
|
||||
r.setupTerminal()
|
||||
if clear {
|
||||
@ -718,7 +725,7 @@ func (r *LightRenderer) Resume(clear bool, sigcont bool) {
|
||||
} else {
|
||||
r.rmcup()
|
||||
}
|
||||
r.enableMouse()
|
||||
r.enableModes()
|
||||
r.flush()
|
||||
} else if sigcont && !r.fullscreen && r.mouse {
|
||||
// NOTE: SIGCONT (Coming back from CTRL-Z):
|
||||
@ -773,7 +780,7 @@ func (r *LightRenderer) Close() {
|
||||
if !r.showCursor {
|
||||
r.csi("?25h")
|
||||
}
|
||||
r.disableMouse()
|
||||
r.disableModes()
|
||||
r.flush()
|
||||
r.restoreTerminal()
|
||||
r.closePlatform()
|
||||
|
@ -42,26 +42,35 @@ func (r *LightRenderer) closePlatform() {
|
||||
r.ttyout.Close()
|
||||
}
|
||||
|
||||
func openTty(mode int) (*os.File, error) {
|
||||
in, err := os.OpenFile(consoleDevice, mode, 0)
|
||||
if err != nil {
|
||||
func openTty(ttyDefault string, mode int) (*os.File, error) {
|
||||
var in *os.File
|
||||
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()
|
||||
if len(tty) > 0 {
|
||||
if in, err := os.OpenFile(tty, mode, 0); err == 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
|
||||
}
|
||||
|
||||
func openTtyIn() (*os.File, error) {
|
||||
return openTty(syscall.O_RDONLY)
|
||||
func openTtyIn(ttyDefault string) (*os.File, error) {
|
||||
return openTty(ttyDefault, syscall.O_RDONLY)
|
||||
}
|
||||
|
||||
func openTtyOut() (*os.File, error) {
|
||||
return openTty(syscall.O_WRONLY)
|
||||
func openTtyOut(ttyDefault string) (*os.File, error) {
|
||||
return openTty(ttyDefault, syscall.O_WRONLY)
|
||||
}
|
||||
|
||||
func (r *LightRenderer) setupTerminal() {
|
||||
|
@ -76,12 +76,12 @@ func (r *LightRenderer) closePlatform() {
|
||||
windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput)
|
||||
}
|
||||
|
||||
func openTtyIn() (*os.File, error) {
|
||||
func openTtyIn(ttyDefault string) (*os.File, error) {
|
||||
// not used
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func openTtyOut() (*os.File, error) {
|
||||
func openTtyOut(ttyDefault string) (*os.File, error) {
|
||||
return os.Stderr, nil
|
||||
}
|
||||
|
||||
|
@ -197,6 +197,7 @@ func (r *FullscreenRenderer) initScreen() error {
|
||||
if e = s.Init(); e != nil {
|
||||
return e
|
||||
}
|
||||
s.EnablePaste()
|
||||
if r.mouse {
|
||||
s.EnableMouse()
|
||||
} else {
|
||||
@ -266,6 +267,11 @@ func (r *FullscreenRenderer) Size() TermSize {
|
||||
func (r *FullscreenRenderer) GetChar() Event {
|
||||
ev := _screen.PollEvent()
|
||||
switch ev := ev.(type) {
|
||||
case *tcell.EventPaste:
|
||||
if ev.Start() {
|
||||
return Event{BracketedPasteBegin, 0, nil}
|
||||
}
|
||||
return Event{BracketedPasteEnd, 0, nil}
|
||||
case *tcell.EventResize:
|
||||
// Ignore the first resize event
|
||||
// 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"
|
||||
)
|
||||
|
||||
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 {
|
||||
return true
|
||||
} else {
|
||||
|
@ -44,11 +44,11 @@ func ttyname() string {
|
||||
}
|
||||
|
||||
// TtyIn returns terminal device to read user input
|
||||
func TtyIn() (*os.File, error) {
|
||||
return openTtyIn()
|
||||
func TtyIn(ttyDefault string) (*os.File, error) {
|
||||
return openTtyIn(ttyDefault)
|
||||
}
|
||||
|
||||
// TtyIn returns terminal device to write to
|
||||
func TtyOut() (*os.File, error) {
|
||||
return openTtyOut()
|
||||
func TtyOut(ttyDefault string) (*os.File, error) {
|
||||
return openTtyOut(ttyDefault)
|
||||
}
|
||||
|
@ -11,11 +11,11 @@ func ttyname() string {
|
||||
}
|
||||
|
||||
// TtyIn on Windows returns os.Stdin
|
||||
func TtyIn() (*os.File, error) {
|
||||
func TtyIn(ttyDefault string) (*os.File, error) {
|
||||
return os.Stdin, nil
|
||||
}
|
||||
|
||||
// TtyOut on Windows returns nil
|
||||
func TtyOut() (*os.File, error) {
|
||||
func TtyOut(ttyDefault string) (*os.File, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -103,6 +103,8 @@ const (
|
||||
|
||||
Invalid
|
||||
Fatal
|
||||
BracketedPasteBegin
|
||||
BracketedPasteEnd
|
||||
|
||||
Mouse
|
||||
DoubleClick
|
||||
@ -306,6 +308,12 @@ func (p ColorPair) WithAttr(attr Attr) ColorPair {
|
||||
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 {
|
||||
return p.WithAttr(other.attr)
|
||||
}
|
||||
@ -326,6 +334,7 @@ type ColorTheme struct {
|
||||
Bg ColorAttr
|
||||
ListFg ColorAttr
|
||||
ListBg ColorAttr
|
||||
AltBg ColorAttr
|
||||
Nth ColorAttr
|
||||
SelectedFg ColorAttr
|
||||
SelectedBg ColorAttr
|
||||
@ -733,6 +742,7 @@ func EmptyTheme() *ColorTheme {
|
||||
Bg: ColorAttr{colUndefined, AttrUndefined},
|
||||
ListFg: ColorAttr{colUndefined, AttrUndefined},
|
||||
ListBg: ColorAttr{colUndefined, AttrUndefined},
|
||||
AltBg: ColorAttr{colUndefined, AttrUndefined},
|
||||
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
||||
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
||||
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
||||
@ -778,6 +788,7 @@ func NoColorTheme() *ColorTheme {
|
||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||
ListFg: ColorAttr{colDefault, AttrUndefined},
|
||||
ListBg: ColorAttr{colDefault, AttrUndefined},
|
||||
AltBg: ColorAttr{colUndefined, AttrUndefined},
|
||||
SelectedFg: ColorAttr{colDefault, AttrUndefined},
|
||||
SelectedBg: ColorAttr{colDefault, AttrUndefined},
|
||||
SelectedMatch: ColorAttr{colDefault, AttrUndefined},
|
||||
@ -823,6 +834,7 @@ func init() {
|
||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||
ListFg: ColorAttr{colUndefined, AttrUndefined},
|
||||
ListBg: ColorAttr{colUndefined, AttrUndefined},
|
||||
AltBg: ColorAttr{colUndefined, AttrUndefined},
|
||||
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
||||
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
||||
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
||||
@ -862,6 +874,7 @@ func init() {
|
||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||
ListFg: ColorAttr{colUndefined, AttrUndefined},
|
||||
ListBg: ColorAttr{colUndefined, AttrUndefined},
|
||||
AltBg: ColorAttr{colUndefined, AttrUndefined},
|
||||
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
||||
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
||||
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
||||
@ -901,6 +914,7 @@ func init() {
|
||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||
ListFg: ColorAttr{colUndefined, AttrUndefined},
|
||||
ListBg: ColorAttr{colUndefined, AttrUndefined},
|
||||
AltBg: ColorAttr{colUndefined, AttrUndefined},
|
||||
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
||||
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
||||
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
||||
|
@ -294,9 +294,10 @@ func (chars *Chars) Lines(multiLine bool, maxLines int, wrapCols int, wrapSignWi
|
||||
line = line[:len(line)-1]
|
||||
}
|
||||
|
||||
hasWrapSign := false
|
||||
for {
|
||||
cols := wrapCols
|
||||
if len(wrapped) > 0 {
|
||||
if hasWrapSign {
|
||||
cols -= wrapSignWidth
|
||||
}
|
||||
_, overflowIdx := RunesWidth(line, 0, tabstop, cols)
|
||||
@ -309,9 +310,11 @@ func (chars *Chars) Lines(multiLine bool, maxLines int, wrapCols int, wrapSignWi
|
||||
return wrapped, true
|
||||
}
|
||||
wrapped = append(wrapped, line[:overflowIdx])
|
||||
hasWrapSign = true
|
||||
line = line[overflowIdx:]
|
||||
continue
|
||||
}
|
||||
hasWrapSign = false
|
||||
|
||||
// Restore trailing '\n'
|
||||
if newline {
|
||||
|
@ -76,7 +76,7 @@ func TestCharsLines(t *testing.T) {
|
||||
check(true, 100, 3, 1, 1, 8, false)
|
||||
|
||||
// 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
|
||||
check(false, 100, 3, 2, 1, 13, false)
|
||||
|
@ -6,7 +6,7 @@ import "sync"
|
||||
type EventType int
|
||||
|
||||
// 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
|
||||
type EventBox struct {
|
||||
@ -36,7 +36,7 @@ func (b *EventBox) Wait(callback func(*Events)) {
|
||||
}
|
||||
|
||||
// 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.events[event] = value
|
||||
if _, found := b.ignore[event]; !found {
|
||||
|
@ -1632,14 +1632,16 @@ class TestCore < TestInteractive
|
||||
end
|
||||
|
||||
def test_env_vars
|
||||
def to_vars(lines)
|
||||
lines.select { it.start_with?('FZF_') }.to_h do
|
||||
key, val = it.split('=', 2)
|
||||
def env_vars
|
||||
return {} unless File.exist?(tempname)
|
||||
|
||||
File.readlines(tempname).select { it.start_with?('FZF_') }.to_h do
|
||||
key, val = it.chomp.split('=', 2)
|
||||
[key.to_sym, val]
|
||||
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 = {
|
||||
FZF_TOTAL_COUNT: '100',
|
||||
FZF_MATCH_COUNT: '100',
|
||||
@ -1648,31 +1650,32 @@ class TestCore < TestInteractive
|
||||
FZF_KEY: '',
|
||||
FZF_POS: '1',
|
||||
FZF_QUERY: '',
|
||||
FZF_POINTER: '>',
|
||||
FZF_PROMPT: '> ',
|
||||
FZF_INPUT_STATE: 'hidden'
|
||||
}
|
||||
tmux.until do |lines|
|
||||
assert_equal expected, to_vars(lines).slice(*expected.keys)
|
||||
tmux.until do
|
||||
assert_equal expected, env_vars.slice(*expected.keys)
|
||||
end
|
||||
tmux.send_keys :Enter
|
||||
tmux.until do |lines|
|
||||
tmux.until do
|
||||
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
|
||||
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')
|
||||
assert_equal expected, to_vars(lines).slice(*expected.keys)
|
||||
assert_equal expected, env_vars.slice(*expected.keys)
|
||||
end
|
||||
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')
|
||||
assert_equal expected, to_vars(lines).slice(*expected.keys)
|
||||
assert_equal expected, env_vars.slice(*expected.keys)
|
||||
end
|
||||
tmux.send_keys :Space
|
||||
tmux.until do |lines|
|
||||
tmux.until do
|
||||
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
|
||||
|
||||
@ -1813,4 +1816,127 @@ class TestCore < TestInteractive
|
||||
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
|
||||
|
@ -991,4 +991,46 @@ class TestLayout < TestInteractive
|
||||
BLOCK
|
||||
tmux.until { assert_block(block, it) }
|
||||
end
|
||||
|
||||
def test_label_trunction
|
||||
command = <<~CMD
|
||||
seq 10 | #{FZF} --style full --border --header-lines=1 --preview ':' \\
|
||||
--border-label "#{'b' * 1000}" \\
|
||||
--preview-label "#{'p' * 1000}" \\
|
||||
--header-label "#{'h' * 1000}" \\
|
||||
--header-label "#{'h' * 1000}" \\
|
||||
--input-label "#{'i' * 1000}" \\
|
||||
--list-label "#{'l' * 1000}"
|
||||
CMD
|
||||
writelines(command.lines.map(&:chomp))
|
||||
tmux.send_keys("sh #{tempname}", :Enter)
|
||||
tmux.until do |lines|
|
||||
text = lines.join
|
||||
assert_includes text, 'b··'
|
||||
assert_includes text, 'l··p'
|
||||
assert_includes text, 'p··'
|
||||
assert_includes text, 'h··'
|
||||
assert_includes text, 'i··'
|
||||
end
|
||||
end
|
||||
|
||||
def test_separator_no_ellipsis
|
||||
tmux.send_keys %(seq 10 | #{FZF} --separator "$(seq 1000 | tr '\\n' ' ')"), :Enter
|
||||
tmux.until do |lines|
|
||||
assert_equal 10, lines.match_count
|
||||
refute_includes lines.join, '··'
|
||||
end
|
||||
end
|
||||
|
||||
def test_header_border_no_pointer_and_marker
|
||||
tmux.send_keys %(seq 10 | #{FZF} --header-lines 1 --header-border sharp --no-list-border --pointer '' --marker ''), :Enter
|
||||
block = <<~BLOCK
|
||||
┌──────
|
||||
│ 1
|
||||
└──────
|
||||
9/9 ─
|
||||
>
|
||||
BLOCK
|
||||
tmux.until { assert_block(block, it) }
|
||||
end
|
||||
end
|
||||
|
Loading…
x
Reference in New Issue
Block a user