Compare commits

..

33 Commits

Author SHA1 Message Date
dependabot[bot]
fc292a3897 Bump golang.org/x/sys from 0.30.0 to 0.35.0
Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.30.0 to 0.35.0.
- [Commits](https://github.com/golang/sys/compare/v0.30.0...v0.35.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-version: 0.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-11 18:13:47 +00:00
Junegunn Choi
1afd143810 Fix incorrect truncation of --info-command with --info=inline-right
Fix #4479
2025-08-08 18:51:24 +09:00
Junegunn Choi
e5cd7f0a3a 0.65.1 2025-08-03 14:41:56 +09:00
junegunn
51d3940c63 Deploying to master from @ junegunn/fzf@179aec1578 🚀 2025-08-03 00:02:30 +00:00
Junegunn Choi
179aec1578 Fix '--color nth:regular' not to reset ANSI attributes of the original text 2025-08-03 00:54:26 +09:00
Junegunn Choi
af0014aba8 Fix a bug where you cannot unset the default --nth using change-nth 2025-08-03 00:29:05 +09:00
Junegunn Choi
da3d995709 Fix $FZF_CLICK_{HEADER,FOOTER}_WORD with ANSI colors and tabs 2025-08-02 16:47:09 +09:00
Junegunn Choi
04c4269db3 0.65.0 2025-07-27 10:39:41 +09:00
junegunn
78f238294f Deploying to master from @ junegunn/fzf@354d0468c1 🚀 2025-07-27 00:02:23 +00:00
LangLangBart
354d0468c1 fix(shell): check for mawk existence before version check (#4468)
close #4463
2025-07-25 17:33:18 +09:00
Junegunn Choi
4efcc344c3 Add 'trigger(KEY_OR_EVENT[,...])' action 2025-07-23 19:41:06 +09:00
Junegunn Choi
5818b58350 Better fix for #4465 - remove unnecessary erase 2025-07-23 19:30:52 +09:00
Junegunn Choi
7941129cc4 Add 'click-footer' event 2025-07-22 23:24:23 +09:00
Junegunn Choi
069d71a840 Fix rendering error when hiding a preview window without border
This was a regression introduced in cdcab267.

Fix #4465
2025-07-22 19:23:10 +09:00
Junegunn Choi
08027e7a79 Fix --no-header-lines-border behavior
It should be different from --header-lines-border=none according to the
man page. It should merge two headers unlike the latter.
2025-07-22 19:16:55 +09:00
Junegunn Choi
ead302981c Add support for {*n} and {*nf} placeholder
Close #4458
2025-07-20 10:53:58 +09:00
junegunn
fe0ffa14ff Deploying to master from @ junegunn/fzf@821b8e70a8 🚀 2025-07-20 00:02:23 +00:00
Junegunn Choi
821b8e70a8 [neovim] Fix margin background color when &winborder is used
Fix #4453
2025-07-19 16:19:48 +09:00
Jaseem Abid
8ceda54c7d Fix a typo in README.md (#4459) 2025-07-16 23:19:43 +09:00
junegunn
84e515bd6e Deploying to master from @ junegunn/fzf@dea1df6878 🚀 2025-07-13 00:02:29 +00:00
Junegunn Choi
dea1df6878 Add missing mention of 'bg-cancel' to the man page 2025-07-12 20:09:54 +09:00
Junegunn Choi
0076ec2e8d 0.64.0 2025-07-06 22:11:36 +09:00
Junegunn Choi
82c9671f79 Fix selection lost on revision bump 2025-07-06 22:02:12 +09:00
Junegunn Choi
d364a1122e Fix regression where header is not updated 2025-07-06 20:24:23 +09:00
Junegunn Choi
fb570e94e7 Update: make generate 2025-07-06 20:03:13 +09:00
Junegunn Choi
6e3c830cd2 Add 'multi' event triggered on multi-selection changes 2025-07-06 10:05:25 +09:00
junegunn
d7db7fc132 Deploying to master from @ junegunn/fzf@ff1550bb38 🚀 2025-07-06 00:02:27 +00:00
Junegunn Choi
ff1550bb38 Normalize halfwidth and fullwidth characers for matching 2025-07-03 20:57:19 +09:00
Junegunn Choi
976001e474 Explain the need to escape placeholders in transform actions 2025-07-02 22:26:56 +09:00
Junegunn Choi
531dd6fb4f Update copyright year 2025-07-02 22:10:05 +09:00
Junegunn Choi
ba035f2a76 Run preview command when preview window appears after CTRL-Z
80b8846318
2025-07-02 21:40:02 +09:00
Junegunn Choi
d34675d3c9 Fix panic caused by incorrect update ordering
Fix #4442

Make sure to prepare windows before rendering elements.

Thanks to @nugged for the report.
2025-07-02 21:28:11 +09:00
junegunn
ce95adc66c Deploying to master from @ junegunn/fzf@397fe8e395 🚀 2025-06-29 00:02:28 +00:00
36 changed files with 641 additions and 218 deletions

View File

@@ -1,2 +1,2 @@
golang 1.20.13
golang 1.20.14
ruby 3.4.1

View File

@@ -1,6 +1,72 @@
CHANGELOG
=========
0.65.1
------
- Fixed incorrect `$FZF_CLICK_HEADER_WORD` and `$FZF_CLICK_FOOTER_WORD` when the header or footer contains ANSI escape sequences and tab characters.
- Fixed a bug where you cannot unset the default `--nth` using `change-nth` action.
- Fixed a highlighting bug when using `--color fg:dim,nth:regular` pattern over ANSI-colored items.
0.65.0
------
- Added `click-footer` event that is triggered when the footer section is clicked. When the event is triggered, the following environment variables are set:
- `$FZF_CLICK_FOOTER_COLUMN` - clicked column (1-based)
- `$FZF_CLICK_FOOTER_LINE` - clicked line (1-based)
- `$FZF_CLICK_FOOTER_WORD` - the word under the cursor
```sh
fzf --footer $'[Edit] [View]\n[Copy to clipboard]' \
--with-shell 'bash -c' \
--bind 'click-footer:transform:
[[ $FZF_CLICK_FOOTER_WORD =~ Edit ]] && echo "execute:vim \{}"
[[ $FZF_CLICK_FOOTER_WORD =~ View ]] && echo "execute:view \{}"
(( FZF_CLICK_FOOTER_LINE == 2 )) && (( FZF_CLICK_FOOTER_COLUMN < 20 )) &&
echo "execute-silent(echo -n \{} | pbcopy)+bell"
'
```
- Added `trigger(...)` action that triggers events bound to another key or event.
```sh
# You can click on each key name to trigger the actions bound to that key
fzf --footer 'Ctrl-E: Edit / Ctrl-V: View / Ctrl-Y: Copy to clipboard' \
--with-shell 'bash -c' \
--bind 'ctrl-e:execute:vim {}' \
--bind 'ctrl-v:execute:view {}' \
--bind 'ctrl-y:execute-silent(echo -n {} | pbcopy)+bell' \
--bind 'click-footer:transform:
[[ $FZF_CLICK_FOOTER_WORD =~ Ctrl ]] && echo "trigger(${FZF_CLICK_FOOTER_WORD%:})"
'
```
- You can specify a series of keys and events
```sh
fzf --bind 'a:up,b:trigger(a,a,a)'
```
- Added support for `{*n}` and `{*nf}` placeholder.
- `{*n}` evaluates to the zero-based ordinal index of all matched items.
- `{*nf}` evaluates to the temporary file containing that.
- Bug fixes and improvements
- [neovim] Fixed margin background color when `&winborder` is used (#4453)
- Fixed rendering error when hiding a preview window without border (#4465)
- fix(shell): check for mawk existence before version check (#4468)
- Thanks to @LangLangBart and @akinomyoga
- Fixed `--no-header-lines-border` behavior (08027e7a)
0.64.0
------
- Added `multi` event that is triggered when the multi-selection has changed.
```sh
fzf --multi \
--bind 'ctrl-a:select-all,ctrl-d:deselect-all' \
--bind 'multi:transform-footer:(( FZF_SELECT_COUNT )) && echo "Selected $FZF_SELECT_COUNT item(s)"'
```
- [Halfwidth and fullwidth alphanumeric and punctuation characters](https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block)) are now internally normalized to their ASCII equivalents to allow matching with ASCII queries.
```sh
echo | fzf -q abc
```
- Renamed `clear-selection` action to `clear-multi` for consistency.
- `clear-selection` remains supported as an alias for backward compatibility.
- Bug fixes
- Fixed a bug that could cause fzf to abort due to incorrect update ordering.
- Fixed a bug where some multi-selections were lost when using `exclude` or `change-nth`.
0.63.0
------
_Release highlights: https://junegunn.github.io/fzf/releases/0.63.0/_

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2013-2024 Junegunn Choi
Copyright (c) 2013-2025 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -493,4 +493,4 @@ autocmd FileType fzf set laststatus=0 noshowmode noruler
The MIT License (MIT)
Copyright (c) 2013-2024 Junegunn Choi
Copyright (c) 2013-2025 Junegunn Choi

File diff suppressed because one or more lines are too long

View File

@@ -503,7 +503,7 @@ LICENSE *fzf-license*
The MIT License (MIT)
Copyright (c) 2013-2024 Junegunn Choi
Copyright (c) 2013-2025 Junegunn Choi
==============================================================================
vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap:

4
go.mod
View File

@@ -6,7 +6,7 @@ require (
github.com/junegunn/go-shellwords v0.0.0-20250127100254-2aa3b3277741
github.com/mattn/go-isatty v0.0.20
github.com/rivo/uniseg v0.4.7
golang.org/x/sys v0.30.0
golang.org/x/sys v0.35.0
golang.org/x/term v0.29.0
)
@@ -17,4 +17,4 @@ require (
golang.org/x/text v0.21.0 // indirect
)
go 1.20
go 1.23.0

4
go.sum
View File

@@ -55,8 +55,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=

View File

@@ -2,7 +2,7 @@
set -u
version=0.63.0
version=0.65.1
auto_completion=
key_bindings=
update_config=2

View File

@@ -1,4 +1,4 @@
$version="0.63.0"
$version="0.65.1"
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition

View File

@@ -11,7 +11,7 @@ import (
"github.com/junegunn/fzf/src/protector"
)
var version = "0.63"
var version = "0.65"
var revision = "devel"
//go:embed shell/key-bindings.bash

View File

@@ -1,7 +1,7 @@
.ig
The MIT License (MIT)
Copyright (c) 2013-2024 Junegunn Choi
Copyright (c) 2013-2025 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -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 "Jun 2025" "fzf 0.63.0" "fzf\-tmux - open fzf in tmux split pane"
.TH fzf\-tmux 1 "Aug 2025" "fzf 0.65.1" "fzf\-tmux - open fzf in tmux split pane"
.SH NAME
fzf\-tmux - open fzf in tmux split pane

View File

@@ -1,7 +1,7 @@
.ig
The MIT License (MIT)
Copyright (c) 2013-2024 Junegunn Choi
Copyright (c) 2013-2025 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -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 "Jun 2025" "fzf 0.63.0" "fzf - a command-line fuzzy finder"
.TH fzf 1 "Aug 2025" "fzf 0.65.1" "fzf - a command-line fuzzy finder"
.SH NAME
fzf - a command-line fuzzy finder
@@ -1609,6 +1609,10 @@ e.g.
# Beware not to introduce an infinite loop
seq 10 | fzf \-\-bind 'focus:up' \-\-cycle\fR
.RE
\fImulti\fR
.RS
Triggered when the multi\-selection has changed.
.RE
\fIone\fR
.RS
@@ -1678,6 +1682,14 @@ e.g.
)'\fR
.RE
\fIclick\-footer\fR
.RS
Triggered when a mouse click occurs within the footer. Sets
\fBFZF_CLICK_FOOTER_LINE\fR and \fBFZF_CLICK_FOOTER_COLUMN\fR environment
variables starting from 1. It optionally sets \fBFZF_CLICK_FOOTER_WORD\fR
if clicked on a word.
.RE
.SS AVAILABLE ACTIONS:
A key or an event can be bound to one or more of the following actions.
@@ -1694,6 +1706,7 @@ A key or an event can be bound to one or more of the following actions.
\fBbecome(...)\fR (replace fzf process with the specified command; see below for the details)
\fBbeginning\-of\-line\fR \fIctrl\-a home\fR
\fBbell\fR (ring the terminal bell)
\fBbg\-cancel\fR (cancel background transform processes)
\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)
@@ -1711,13 +1724,13 @@ A key or an event can be bound to one or more of the following actions.
\fBchange\-prompt(...)\fR (change prompt to the given string)
\fBchange\-query(...)\fR (change query string to the given string)
\fBclear\-screen\fR \fIctrl\-l\fR
\fBclear\-selection\fR (clear multi\-selection)
\fBclear\-multi\fR (clear multi\-selection)
\fBclose\fR (close preview window if open, abort fzf otherwise)
\fBclear\-query\fR (clear query string)
\fBdelete\-char\fR \fIdel\fR
\fBdelete\-char/eof\fR \fIctrl\-d\fR (same as \fBdelete\-char\fR except aborts fzf if query is empty)
\fBdeselect\fR
\fBdeselect\-all\fR (deselect all matches)
\fBdeselect\-all\fR (deselect all matches; to also clear non-matched selections, use \fBclear\-multi\fR)
\fBdisable\-search\fR (disable search functionality)
\fBdown\fR \fIctrl\-j ctrl\-n down\fR
\fBenable\-search\fR (enable search functionality)
@@ -1804,6 +1817,7 @@ A key or an event can be bound to one or more of the following actions.
\fBtransform\-prompt(...)\fR (transform prompt string using an external command)
\fBtransform\-query(...)\fR (transform query string using an external command)
\fBtransform\-search(...)\fR (trigger fzf search with the output of an external command)
\fBtrigger(...)\fR (trigger actions bound to a comma-separated list of keys and events)
\fBunbind(...)\fR (unbind bindings)
\fBunix\-line\-discard\fR \fIctrl\-u\fR
\fBunix\-word\-rubout\fR \fIctrl\-w\fR
@@ -1938,13 +1952,25 @@ e.g.
echo "change\-header:Invalid selection"'
\fR
A common mistake when writing a \fBtransform\fR action is not escaping
placeholder expressions when passing them back to fzf. In the following
example, if you don't escape \fB{}\fR, fzf will immediately replace it with the
single-quoted string of the current item. This causes single quotes to appear
in the header and footer, and the script will break if any item contains
double-quote characters.
\fBfzf \-\-bind 'focus:transform:[[ $FZF_ACTION =~ up ]] &&
echo "change\-header()+transform\-footer:echo \\{}" ||
echo "change\-footer()+transform\-header:echo \\{}"'\fR
.SS TRANSFORM IN THE BACKGROUND
Transform actions are synchronous, meaning fzf becomes unresponsive while the
command runs. To avoid this, each \fBtransform*\fR action has a corresponding
\fBbg\-transform*\fR variant that runs in the background. Unless you need to
chain multiple transform actions where later ones depend on earlier results,
prefer using the \fBbg\fR variant.
prefer using the \fBbg\fR variant. To cancel currently running background
transform processes, use \fBbg\-cancel\fR action.
.SS PREVIEW BINDING

View File

@@ -1,4 +1,4 @@
" Copyright (c) 2013-2024 Junegunn Choi
" Copyright (c) 2013-2025 Junegunn Choi
"
" MIT License
"
@@ -1027,8 +1027,23 @@ if has('nvim')
let buf = nvim_create_buf(v:false, v:true)
let opts = extend({'relative': 'editor', 'style': 'minimal'}, a:opts)
let win = nvim_open_win(buf, v:true, opts)
silent! call setwinvar(win, '&winhighlight', 'Pmenu:,Normal:Normal')
call setwinvar(win, '&colorcolumn', '')
" Colors
try
call setwinvar(win, '&winhighlight', 'Pmenu:,Normal:Normal')
let rules = get(g:, 'fzf_colors', {})
if has_key(rules, 'bg')
let color = call('s:get_color', rules.bg)
if len(color)
let ns = nvim_create_namespace('fzf_popup')
let hl = nvim_set_hl(ns, 'Normal',
\ &termguicolors ? { 'bg': color } : { 'ctermbg': str2nr(color) })
call nvim_win_set_hl_ns(win, ns)
endif
endif
catch
endtry
return buf
endfunction
else

View File

@@ -22,11 +22,11 @@ __fzf_exec_awk() {
# modern point of view. To use a standard-conforming version in Solaris,
# one needs to explicitly use /usr/xpg4/bin/awk.
__fzf_awk=/usr/xpg4/bin/awk
else
elif command -v mawk >/dev/null 2>&1; then
# choose the faster mawk if: it's installed && build date >= 20230322 &&
# version >= 1.3.4
local n x y z d
IFS=' .' read n x y z d <<< $(command mawk -W version 2> /dev/null)
IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)
[[ $n == mawk ]] && (( d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004 )) && __fzf_awk=mawk
fi
fi

View File

@@ -47,9 +47,9 @@ __fzf_exec_awk() {
__fzf_awk=awk
if [[ $OSTYPE == solaris* && -x /usr/xpg4/bin/awk ]]; then
__fzf_awk=/usr/xpg4/bin/awk
else
elif command -v mawk >/dev/null 2>&1; then
local n x y z d
IFS=' .' read n x y z d <<< $(command mawk -W version 2> /dev/null)
IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)
[[ $n == mawk ]] && (( d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004 )) && __fzf_awk=mawk
fi
fi
@@ -524,7 +524,7 @@ if ! declare -F __fzf_list_hosts > /dev/null; then
if ($i != "0.0.0.0")
print $i
}
' /etc/hosts 2> /dev/null
' /etc/hosts 2> /dev/null
)
}
fi

View File

@@ -112,9 +112,9 @@ __fzf_exec_awk() {
__fzf_awk=awk
if [[ $OSTYPE == solaris* && -x /usr/xpg4/bin/awk ]]; then
__fzf_awk=/usr/xpg4/bin/awk
else
elif command -v mawk >/dev/null 2>&1; then
local n x y z d
IFS=' .' read n x y z d <<< $(command mawk -W version 2> /dev/null)
IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)
[[ $n == mawk ]] && (( d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004 )) && __fzf_awk=mawk
fi
fi

View File

@@ -33,9 +33,9 @@ __fzf_exec_awk() {
__fzf_awk=awk
if [[ $OSTYPE == solaris* && -x /usr/xpg4/bin/awk ]]; then
__fzf_awk=/usr/xpg4/bin/awk
else
elif command -v mawk >/dev/null 2>&1; then
local n x y z d
IFS=' .' read n x y z d <<< $(command mawk -W version 2> /dev/null)
IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)
[[ $n == mawk ]] && (( d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004 )) && __fzf_awk=mawk
fi
fi

View File

@@ -54,9 +54,9 @@ __fzf_exec_awk() {
__fzf_awk=awk
if [[ $OSTYPE == solaris* && -x /usr/xpg4/bin/awk ]]; then
__fzf_awk=/usr/xpg4/bin/awk
else
elif command -v mawk >/dev/null 2>&1; then
local n x y z d
IFS=' .' read n x y z d <<< $(command mawk -W version 2> /dev/null)
IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)
[[ $n == mawk ]] && (( d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004 )) && __fzf_awk=mawk
fi
fi

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2013-2024 Junegunn Choi
Copyright (c) 2013-2025 Junegunn Choi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -113,65 +113,66 @@ func _() {
_ = x[actTransformPrompt-102]
_ = x[actTransformQuery-103]
_ = x[actTransformSearch-104]
_ = x[actBgTransform-105]
_ = x[actBgTransformBorderLabel-106]
_ = x[actBgTransformGhost-107]
_ = x[actBgTransformHeader-108]
_ = x[actBgTransformFooter-109]
_ = x[actBgTransformHeaderLabel-110]
_ = x[actBgTransformFooterLabel-111]
_ = x[actBgTransformInputLabel-112]
_ = x[actBgTransformListLabel-113]
_ = x[actBgTransformNth-114]
_ = x[actBgTransformPointer-115]
_ = x[actBgTransformPreviewLabel-116]
_ = x[actBgTransformPrompt-117]
_ = x[actBgTransformQuery-118]
_ = x[actBgTransformSearch-119]
_ = x[actBgCancel-120]
_ = x[actSearch-121]
_ = x[actPreview-122]
_ = x[actPreviewTop-123]
_ = x[actPreviewBottom-124]
_ = x[actPreviewUp-125]
_ = x[actPreviewDown-126]
_ = x[actPreviewPageUp-127]
_ = x[actPreviewPageDown-128]
_ = x[actPreviewHalfPageUp-129]
_ = x[actPreviewHalfPageDown-130]
_ = x[actPrevHistory-131]
_ = x[actPrevSelected-132]
_ = x[actPrint-133]
_ = x[actPut-134]
_ = x[actNextHistory-135]
_ = x[actNextSelected-136]
_ = x[actExecute-137]
_ = x[actExecuteSilent-138]
_ = x[actExecuteMulti-139]
_ = x[actSigStop-140]
_ = x[actFirst-141]
_ = x[actLast-142]
_ = x[actReload-143]
_ = x[actReloadSync-144]
_ = x[actDisableSearch-145]
_ = x[actEnableSearch-146]
_ = x[actSelect-147]
_ = x[actDeselect-148]
_ = x[actUnbind-149]
_ = x[actRebind-150]
_ = x[actToggleBind-151]
_ = x[actBecome-152]
_ = x[actShowHeader-153]
_ = x[actHideHeader-154]
_ = x[actBell-155]
_ = x[actExclude-156]
_ = x[actExcludeMulti-157]
_ = x[actAsync-158]
_ = x[actTrigger-105]
_ = x[actBgTransform-106]
_ = x[actBgTransformBorderLabel-107]
_ = x[actBgTransformGhost-108]
_ = x[actBgTransformHeader-109]
_ = x[actBgTransformFooter-110]
_ = x[actBgTransformHeaderLabel-111]
_ = x[actBgTransformFooterLabel-112]
_ = x[actBgTransformInputLabel-113]
_ = x[actBgTransformListLabel-114]
_ = x[actBgTransformNth-115]
_ = x[actBgTransformPointer-116]
_ = x[actBgTransformPreviewLabel-117]
_ = x[actBgTransformPrompt-118]
_ = x[actBgTransformQuery-119]
_ = x[actBgTransformSearch-120]
_ = x[actBgCancel-121]
_ = x[actSearch-122]
_ = x[actPreview-123]
_ = x[actPreviewTop-124]
_ = x[actPreviewBottom-125]
_ = x[actPreviewUp-126]
_ = x[actPreviewDown-127]
_ = x[actPreviewPageUp-128]
_ = x[actPreviewPageDown-129]
_ = x[actPreviewHalfPageUp-130]
_ = x[actPreviewHalfPageDown-131]
_ = x[actPrevHistory-132]
_ = x[actPrevSelected-133]
_ = x[actPrint-134]
_ = x[actPut-135]
_ = x[actNextHistory-136]
_ = x[actNextSelected-137]
_ = x[actExecute-138]
_ = x[actExecuteSilent-139]
_ = x[actExecuteMulti-140]
_ = x[actSigStop-141]
_ = x[actFirst-142]
_ = x[actLast-143]
_ = x[actReload-144]
_ = x[actReloadSync-145]
_ = x[actDisableSearch-146]
_ = x[actEnableSearch-147]
_ = x[actSelect-148]
_ = x[actDeselect-149]
_ = x[actUnbind-150]
_ = x[actRebind-151]
_ = x[actToggleBind-152]
_ = x[actBecome-153]
_ = x[actShowHeader-154]
_ = x[actHideHeader-155]
_ = x[actBell-156]
_ = x[actExclude-157]
_ = x[actExcludeMulti-158]
_ = x[actAsync-159]
}
const _actionType_name = "actIgnoreactStartactClickactInvalidactBracketedPasteBeginactBracketedPasteEndactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeGhostactChangeHeaderactChangeFooteractChangeHeaderLabelactChangeFooterLabelactChangeInputLabelactChangeListLabelactChangeMultiactChangeNthactChangePointeractChangePreviewactChangePreviewLabelactChangePreviewWindowactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformGhostactTransformHeaderactTransformFooteractTransformHeaderLabelactTransformFooterLabelactTransformInputLabelactTransformListLabelactTransformNthactTransformPointeractTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactBgTransformactBgTransformBorderLabelactBgTransformGhostactBgTransformHeaderactBgTransformFooteractBgTransformHeaderLabelactBgTransformFooterLabelactBgTransformInputLabelactBgTransformListLabelactBgTransformNthactBgTransformPointeractBgTransformPreviewLabelactBgTransformPromptactBgTransformQueryactBgTransformSearchactBgCancelactSearchactPreviewactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBellactExcludeactExcludeMultiactAsync"
const _actionType_name = "actIgnoreactStartactClickactInvalidactBracketedPasteBeginactBracketedPasteEndactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeGhostactChangeHeaderactChangeFooteractChangeHeaderLabelactChangeFooterLabelactChangeInputLabelactChangeListLabelactChangeMultiactChangeNthactChangePointeractChangePreviewactChangePreviewLabelactChangePreviewWindowactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformGhostactTransformHeaderactTransformFooteractTransformHeaderLabelactTransformFooterLabelactTransformInputLabelactTransformListLabelactTransformNthactTransformPointeractTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactTriggeractBgTransformactBgTransformBorderLabelactBgTransformGhostactBgTransformHeaderactBgTransformFooteractBgTransformHeaderLabelactBgTransformFooterLabelactBgTransformInputLabelactBgTransformListLabelactBgTransformNthactBgTransformPointeractBgTransformPreviewLabelactBgTransformPromptactBgTransformQueryactBgTransformSearchactBgCancelactSearchactPreviewactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBellactExcludeactExcludeMultiactAsync"
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, 313, 333, 353, 372, 390, 404, 416, 432, 448, 469, 491, 506, 520, 534, 547, 564, 572, 585, 601, 613, 621, 635, 649, 660, 671, 689, 706, 713, 732, 744, 758, 767, 782, 794, 807, 818, 829, 841, 855, 876, 891, 904, 922, 938, 953, 967, 979, 991, 1008, 1015, 1020, 1029, 1040, 1051, 1064, 1079, 1090, 1103, 1118, 1125, 1138, 1151, 1168, 1183, 1196, 1210, 1224, 1240, 1260, 1272, 1295, 1312, 1330, 1348, 1371, 1394, 1416, 1437, 1452, 1471, 1495, 1513, 1530, 1548, 1562, 1587, 1606, 1626, 1646, 1671, 1696, 1720, 1743, 1760, 1781, 1807, 1827, 1846, 1866, 1877, 1886, 1896, 1909, 1925, 1937, 1951, 1967, 1985, 2005, 2027, 2041, 2056, 2064, 2070, 2084, 2099, 2109, 2125, 2140, 2150, 2158, 2165, 2174, 2187, 2203, 2218, 2227, 2238, 2247, 2256, 2269, 2278, 2291, 2304, 2311, 2321, 2336, 2344}
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, 313, 333, 353, 372, 390, 404, 416, 432, 448, 469, 491, 506, 520, 534, 547, 564, 572, 585, 601, 613, 621, 635, 649, 660, 671, 689, 706, 713, 732, 744, 758, 767, 782, 794, 807, 818, 829, 841, 855, 876, 891, 904, 922, 938, 953, 967, 979, 991, 1008, 1015, 1020, 1029, 1040, 1051, 1064, 1079, 1090, 1103, 1118, 1125, 1138, 1151, 1168, 1183, 1196, 1210, 1224, 1240, 1260, 1272, 1295, 1312, 1330, 1348, 1371, 1394, 1416, 1437, 1452, 1471, 1495, 1513, 1530, 1548, 1558, 1572, 1597, 1616, 1636, 1656, 1681, 1706, 1730, 1753, 1770, 1791, 1817, 1837, 1856, 1876, 1887, 1896, 1906, 1919, 1935, 1947, 1961, 1977, 1995, 2015, 2037, 2051, 2066, 2074, 2080, 2094, 2109, 2119, 2135, 2150, 2160, 2168, 2175, 2184, 2197, 2213, 2228, 2237, 2248, 2257, 2266, 2279, 2288, 2301, 2314, 2321, 2331, 2346, 2354}
func (i actionType) String() string {
if i < 0 || i >= actionType(len(_actionType_index)-1) {

View File

@@ -303,7 +303,7 @@ func bonusAt(input *util.Chars, idx int) int16 {
}
func normalizeRune(r rune) rune {
if r < 0x00C0 || r > 0x2184 {
if r < 0x00C0 || r > 0xFF61 {
return r
}

View File

@@ -473,6 +473,103 @@ var normalized = map[rune]rune{
'ử': 'u',
'ữ': 'u',
'ự': 'u',
// https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block)
0xFF01: '!', // Fullwidth exclamation
0xFF02: '"', // Fullwidth quotation mark
0xFF03: '#', // Fullwidth number sign
0xFF04: '$', // Fullwidth dollar sign
0xFF05: '%', // Fullwidth percent
0xFF06: '&', // Fullwidth ampersand
0xFF07: '\'', // Fullwidth apostrophe
0xFF08: '(', // Fullwidth left parenthesis
0xFF09: ')', // Fullwidth right parenthesis
0xFF0A: '*', // Fullwidth asterisk
0xFF0B: '+', // Fullwidth plus
0xFF0C: ',', // Fullwidth comma
0xFF0D: '-', // Fullwidth hyphen-minus
0xFF0E: '.', // Fullwidth period
0xFF0F: '/', // Fullwidth slash
0xFF10: '0',
0xFF11: '1',
0xFF12: '2',
0xFF13: '3',
0xFF14: '4',
0xFF15: '5',
0xFF16: '6',
0xFF17: '7',
0xFF18: '8',
0xFF19: '9',
0xFF1A: ':', // Fullwidth colon
0xFF1B: ';', // Fullwidth semicolon
0xFF1C: '<', // Fullwidth less-than
0xFF1D: '=', // Fullwidth equal
0xFF1E: '>', // Fullwidth greater-than
0xFF1F: '?', // Fullwidth question mark
0xFF20: '@', // Fullwidth at sign
0xFF21: 'A',
0xFF22: 'B',
0xFF23: 'C',
0xFF24: 'D',
0xFF25: 'E',
0xFF26: 'F',
0xFF27: 'G',
0xFF28: 'H',
0xFF29: 'I',
0xFF2A: 'J',
0xFF2B: 'K',
0xFF2C: 'L',
0xFF2D: 'M',
0xFF2E: 'N',
0xFF2F: 'O',
0xFF30: 'P',
0xFF31: 'Q',
0xFF32: 'R',
0xFF33: 'S',
0xFF34: 'T',
0xFF35: 'U',
0xFF36: 'V',
0xFF37: 'W',
0xFF38: 'X',
0xFF39: 'Y',
0xFF3A: 'Z',
0xFF3B: '[', // Fullwidth left bracket
0xFF3C: '\\', // Fullwidth backslash
0xFF3D: ']', // Fullwidth right bracket
0xFF3E: '^', // Fullwidth circumflex
0xFF3F: '_', // Fullwidth underscore
0xFF40: '`', // Fullwidth grave accent
0xFF41: 'a',
0xFF42: 'b',
0xFF43: 'c',
0xFF44: 'd',
0xFF45: 'e',
0xFF46: 'f',
0xFF47: 'g',
0xFF48: 'h',
0xFF49: 'i',
0xFF4A: 'j',
0xFF4B: 'k',
0xFF4C: 'l',
0xFF4D: 'm',
0xFF4E: 'n',
0xFF4F: 'o',
0xFF50: 'p',
0xFF51: 'q',
0xFF52: 'r',
0xFF53: 's',
0xFF54: 't',
0xFF55: 'u',
0xFF56: 'v',
0xFF57: 'w',
0xFF58: 'x',
0xFF59: 'y',
0xFF5A: 'z',
0xFF5B: '{', // Fullwidth left brace
0xFF5C: '|', // Fullwidth vertical bar
0xFF5D: '}', // Fullwidth right brace
0xFF5E: '~', // Fullwidth tilde
0xFF61: '.', // Halfwidth ideographic full stop
}
// NormalizeRunes normalizes latin script letters
@@ -480,7 +577,7 @@ func NormalizeRunes(runes []rune) []rune {
ret := make([]rune, len(runes))
copy(ret, runes)
for idx, r := range runes {
if r < 0x00C0 || r > 0x2184 {
if r < 0x00C0 || r > 0xFF61 {
continue
}
n := normalized[r]

View File

@@ -41,6 +41,13 @@ func (c *Chunk) IsFull() bool {
return c.count == chunkSize
}
func (c *Chunk) lastIndex(minValue int32) int32 {
if c.count == 0 {
return minValue
}
return c.items[c.count-1].Index() + 1 // Exclusive
}
func (cl *ChunkList) lastChunk() *Chunk {
return cl.chunks[len(cl.chunks)-1]
}

View File

@@ -165,6 +165,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
}
minIndex := request.chunks[0].items[0].Index()
maxIndex := request.chunks[numChunks-1].lastIndex(minIndex)
cancelled := util.NewAtomicBool(false)
slices := m.sliceChunks(request.chunks)
@@ -236,7 +237,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
partialResult := <-resultChan
partialResults[partialResult.index] = partialResult.matches
}
return NewMerger(pattern, partialResults, m.sort && request.pattern.sortable, m.tac, request.revision, minIndex), false
return NewMerger(pattern, partialResults, m.sort && request.pattern.sortable, m.tac, request.revision, minIndex, maxIndex), false
}
// Reset is called to interrupt/signal the ongoing search

View File

@@ -4,7 +4,7 @@ import "fmt"
// EmptyMerger is a Merger with no data
func EmptyMerger(revision revision) *Merger {
return NewMerger(nil, [][]Result{}, false, false, revision, 0)
return NewMerger(nil, [][]Result{}, false, false, revision, 0, 0)
}
// Merger holds a set of locally sorted lists of items and provides the view of
@@ -22,14 +22,16 @@ type Merger struct {
pass bool
revision revision
minIndex int32
maxIndex int32
}
// PassMerger returns a new Merger that simply returns the items in the
// original order
func PassMerger(chunks *[]*Chunk, tac bool, revision revision) *Merger {
var minIndex int32
var minIndex, maxIndex int32
if len(*chunks) > 0 {
minIndex = (*chunks)[0].items[0].Index()
maxIndex = (*chunks)[len(*chunks)-1].lastIndex(minIndex)
}
mg := Merger{
pattern: nil,
@@ -38,7 +40,8 @@ func PassMerger(chunks *[]*Chunk, tac bool, revision revision) *Merger {
count: 0,
pass: true,
revision: revision,
minIndex: minIndex}
minIndex: minIndex,
maxIndex: maxIndex}
for _, chunk := range *mg.chunks {
mg.count += chunk.count
@@ -47,7 +50,7 @@ func PassMerger(chunks *[]*Chunk, tac bool, revision revision) *Merger {
}
// NewMerger returns a new Merger
func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool, revision revision, minIndex int32) *Merger {
func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool, revision revision, minIndex int32, maxIndex int32) *Merger {
mg := Merger{
pattern: pattern,
lists: lists,
@@ -59,7 +62,8 @@ func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool, revisi
final: false,
count: 0,
revision: revision,
minIndex: minIndex}
minIndex: minIndex,
maxIndex: maxIndex}
for _, list := range mg.lists {
mg.count += len(list)

View File

@@ -58,7 +58,7 @@ func TestMergerUnsorted(t *testing.T) {
cnt := len(items)
// Not sorted: same order
mg := NewMerger(nil, lists, false, false, revision{}, 0)
mg := NewMerger(nil, lists, false, false, revision{}, 0, 0)
assert(t, cnt == mg.Length(), "Invalid Length")
for i := 0; i < cnt; i++ {
assert(t, items[i] == mg.Get(i), "Invalid Get")
@@ -70,7 +70,7 @@ func TestMergerSorted(t *testing.T) {
cnt := len(items)
// Sorted sorted order
mg := NewMerger(nil, lists, true, false, revision{}, 0)
mg := NewMerger(nil, lists, true, false, revision{}, 0, 0)
assert(t, cnt == mg.Length(), "Invalid Length")
sort.Sort(ByRelevance(items))
for i := 0; i < cnt; i++ {
@@ -80,7 +80,7 @@ func TestMergerSorted(t *testing.T) {
}
// Inverse order
mg2 := NewMerger(nil, lists, true, false, revision{}, 0)
mg2 := NewMerger(nil, lists, true, false, revision{}, 0, 0)
for i := cnt - 1; i >= 0; i-- {
if items[i] != mg2.Get(i) {
t.Error("Not sorted", items[i], mg2.Get(i))

View File

@@ -932,15 +932,12 @@ func parseBorder(str string, optional bool) (tui.BorderShape, error) {
return tui.BorderNone, errors.New("invalid border style (expected: rounded|sharp|bold|block|thinblock|double|horizontal|vertical|top|bottom|left|right|none)")
}
func parseKeyChords(str string, message string) (map[tui.Event]string, error) {
return parseKeyChordsImpl(str, message)
}
func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error) {
func parseKeyChords(str string, message string) (map[tui.Event]string, []tui.Event, error) {
if len(str) == 0 {
return nil, errors.New(message)
return nil, nil, errors.New(message)
}
list := []tui.Event{}
str = regexp.MustCompile("(?i)(alt-),").ReplaceAllString(str, "$1"+string([]rune{escapedComma}))
tokens := strings.Split(str, ",")
if str == "," || strings.HasPrefix(str, ",,") || strings.HasSuffix(str, ",,") || strings.Contains(str, ",,,") {
@@ -956,6 +953,7 @@ func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error
lkey := strings.ToLower(key)
add := func(e tui.EventType) {
chords[e.AsEvent()] = key
list = append(list, e.AsEvent())
}
switch lkey {
case "up":
@@ -969,7 +967,9 @@ func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error
case "enter", "return":
add(tui.Enter)
case "space":
chords[tui.Key(' ')] = key
evt := tui.Key(' ')
chords[evt] = key
list = append(list, evt)
case "backspace", "bspace", "bs":
add(tui.Backspace)
case "ctrl-space":
@@ -1008,10 +1008,18 @@ func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error
add(tui.JumpCancel)
case "click-header":
add(tui.ClickHeader)
case "click-footer":
add(tui.ClickFooter)
case "multi":
add(tui.Multi)
case "alt-enter", "alt-return":
chords[tui.CtrlAltKey('m')] = key
evt := tui.CtrlAltKey('m')
chords[evt] = key
list = append(list, evt)
case "alt-space":
chords[tui.AltKey(' ')] = key
evt := tui.AltKey(' ')
chords[evt] = key
list = append(list, evt)
case "alt-bs", "alt-bspace", "alt-backspace":
add(tui.AltBackspace)
case "alt-up":
@@ -1089,7 +1097,9 @@ func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error
default:
runes := []rune(key)
if len(key) == 10 && strings.HasPrefix(lkey, "ctrl-alt-") && isAlphabet(lkey[9]) {
chords[tui.CtrlAltKey(rune(key[9]))] = key
evt := tui.CtrlAltKey(rune(key[9]))
chords[evt] = key
list = append(list, evt)
} else if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
add(tui.EventType(tui.CtrlA.Int() + int(lkey[5]) - 'a'))
} else if len(runes) == 5 && strings.HasPrefix(lkey, "alt-") {
@@ -1102,17 +1112,21 @@ func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error
case escapedPlus:
r = '+'
}
chords[tui.AltKey(r)] = key
evt := tui.AltKey(r)
chords[evt] = key
list = append(list, evt)
} else if len(key) == 2 && strings.HasPrefix(lkey, "f") && key[1] >= '1' && key[1] <= '9' {
add(tui.EventType(tui.F1.Int() + int(key[1]) - '1'))
} else if len(runes) == 1 {
chords[tui.Key(runes[0])] = key
evt := tui.Key(runes[0])
chords[evt] = key
list = append(list, evt)
} else {
return nil, errors.New("unsupported key: " + key)
return nil, list, errors.New("unsupported key: " + key)
}
}
}
return chords, nil
return chords, list, nil
}
func parseScheme(str string) (string, []criterion, error) {
@@ -1435,7 +1449,7 @@ const (
func init() {
executeRegexp = regexp.MustCompile(
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|bg-transform|transform)-(?:query|prompt|(?:border|list|preview|input|header|footer)-label|header|footer|search|nth|pointer|ghost)|bg-transform|transform|change-(?:preview-window|preview|multi)|(?:re|un|toggle-)bind|pos|put|print|search)`)
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|bg-transform|transform)-(?:query|prompt|(?:border|list|preview|input|header|footer)-label|header|footer|search|nth|pointer|ghost)|bg-transform|transform|change-(?:preview-window|preview|multi)|(?:re|un|toggle-)bind|pos|put|print|search|trigger)`)
splitRegexp = regexp.MustCompile("[,:]+")
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
}
@@ -1561,7 +1575,7 @@ func parseActionList(masked string, original string, prevActions []*action, putA
appendAction(actCancel)
case "clear-query":
appendAction(actClearQuery)
case "clear-selection":
case "clear-multi", "clear-selection":
appendAction(actClearSelection)
case "forward-char":
appendAction(actForwardChar)
@@ -1732,7 +1746,7 @@ func parseActionList(masked string, original string, prevActions []*action, putA
}
switch t {
case actUnbind, actRebind, actToggleBind:
if _, err := parseKeyChordsImpl(actionArg, spec[0:offset]+" target required"); err != nil {
if _, _, err := parseKeyChords(actionArg, spec[0:offset]+" target required"); err != nil {
return nil, err
}
case actChangePreviewWindow:
@@ -1777,7 +1791,7 @@ func parseKeymap(keymap map[tui.Event][]*action, str string) error {
} else if len(keyName) == 1 && keyName[0] == escapedPlus {
key = tui.Key('+')
} else {
keys, err := parseKeyChordsImpl(keyName, "key name required")
keys, _, err := parseKeyChords(keyName, "key name required")
if err != nil {
return err
}
@@ -1924,6 +1938,8 @@ func isExecuteAction(str string) actionType {
return actBgTransformQuery
case "bg-transform-search":
return actBgTransformSearch
case "trigger":
return actTrigger
case "search":
return actSearch
}
@@ -1931,7 +1947,7 @@ func isExecuteAction(str string) actionType {
}
func parseToggleSort(keymap map[tui.Event][]*action, str string) error {
keys, err := parseKeyChords(str, "key name required")
keys, _, err := parseKeyChords(str, "key name required")
if err != nil {
return err
}
@@ -2470,7 +2486,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if err != nil {
return err
}
chords, err := parseKeyChords(str, "key names required")
chords, _, err := parseKeyChords(str, "key names required")
if err != nil {
return err
}
@@ -2922,7 +2938,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
return err
}
case "--no-header-lines-border":
opts.HeaderLinesShape = tui.BorderNone
opts.HeaderLinesShape = tui.BorderUndefined
case "--header-lines-border":
hasArg, arg := optionalNextString()
if opts.HeaderLinesShape, err = parseBorder(arg, !hasArg); err != nil {

View File

@@ -142,7 +142,7 @@ func TestIrrelevantNth(t *testing.T) {
}
func TestParseKeys(t *testing.T) {
pairs, _ := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ctrl-alt-a,ALT-enter,alt-SPACE", "")
pairs, _, _ := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ctrl-alt-a,ALT-enter,alt-SPACE", "")
checkEvent := func(e tui.Event, s string) {
if pairs[e] != s {
t.Errorf("%s != %s", pairs[e], s)
@@ -168,7 +168,7 @@ func TestParseKeys(t *testing.T) {
checkEvent(tui.AltKey(' '), "alt-SPACE")
// Synonyms
pairs, _ = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
pairs, _, _ = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
if len(pairs) != 9 {
t.Error(9)
}
@@ -182,7 +182,7 @@ func TestParseKeys(t *testing.T) {
check(tui.Left, "left")
check(tui.Right, "right")
pairs, _ = parseKeyChords("Tab,Ctrl-I,PgUp,page-up,pgdn,Page-Down,Home,End,Alt-BS,Alt-BSpace,shift-left,shift-right,btab,shift-tab,return,Enter,bspace", "")
pairs, _, _ = parseKeyChords("Tab,Ctrl-I,PgUp,page-up,pgdn,Page-Down,Home,End,Alt-BS,Alt-BSpace,shift-left,shift-right,btab,shift-tab,return,Enter,bspace", "")
if len(pairs) != 11 {
t.Error(11)
}
@@ -211,40 +211,40 @@ func TestParseKeysWithComma(t *testing.T) {
}
}
pairs, _ := parseKeyChords(",", "")
pairs, _, _ := parseKeyChords(",", "")
checkN(len(pairs), 1)
check(pairs, tui.Key(','), ",")
pairs, _ = parseKeyChords(",,a,b", "")
pairs, _, _ = parseKeyChords(",,a,b", "")
checkN(len(pairs), 3)
check(pairs, tui.Key('a'), "a")
check(pairs, tui.Key('b'), "b")
check(pairs, tui.Key(','), ",")
pairs, _ = parseKeyChords("a,b,,", "")
pairs, _, _ = parseKeyChords("a,b,,", "")
checkN(len(pairs), 3)
check(pairs, tui.Key('a'), "a")
check(pairs, tui.Key('b'), "b")
check(pairs, tui.Key(','), ",")
pairs, _ = parseKeyChords("a,,,b", "")
pairs, _, _ = parseKeyChords("a,,,b", "")
checkN(len(pairs), 3)
check(pairs, tui.Key('a'), "a")
check(pairs, tui.Key('b'), "b")
check(pairs, tui.Key(','), ",")
pairs, _ = parseKeyChords("a,,,b,c", "")
pairs, _, _ = parseKeyChords("a,,,b,c", "")
checkN(len(pairs), 4)
check(pairs, tui.Key('a'), "a")
check(pairs, tui.Key('b'), "b")
check(pairs, tui.Key('c'), "c")
check(pairs, tui.Key(','), ",")
pairs, _ = parseKeyChords(",,,", "")
pairs, _, _ = parseKeyChords(",,,", "")
checkN(len(pairs), 1)
check(pairs, tui.Key(','), ",")
pairs, _ = parseKeyChords(",ALT-,,", "")
pairs, _, _ = parseKeyChords(",ALT-,,", "")
checkN(len(pairs), 1)
check(pairs, tui.AltKey(','), "ALT-,")
}

View File

@@ -237,7 +237,7 @@ func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, t
if color.Fg().IsDefault() && origColor.HasBg() {
color = origColor
if curr.nth {
color = color.WithAttr(attrNth)
color = color.WithAttr(attrNth &^ tui.AttrRegular)
}
} else {
color = origColor.MergeNonDefault(color)

View File

@@ -67,7 +67,7 @@ const maxFocusEvents = 10000
const blockDuration = 1 * time.Second
func init() {
placeholder = regexp.MustCompile(`\\?(?:{[+*sfr]*[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/+-]`)
@@ -422,6 +422,8 @@ type Terminal struct {
forcePreview bool
clickHeaderLine int
clickHeaderColumn int
clickFooterLine int
clickFooterColumn int
proxyScript string
numLinesCache map[int32]numLinesCacheValue
}
@@ -450,31 +452,35 @@ func (a byTimeOrder) Less(i, j int) bool {
return a[i].at.Before(a[j].at)
}
// EventTypes are listed in the order of their priority.
const (
reqPrompt util.EventType = iota
reqResize util.EventType = iota
reqReinit
reqFullRedraw
reqRedraw
reqJump
reqPrompt
reqInfo
reqHeader
reqFooter
reqList
reqJump
reqActivate
reqReinit
reqFullRedraw
reqResize
reqRedraw
reqRedrawInputLabel
reqRedrawHeaderLabel
reqRedrawFooterLabel
reqRedrawListLabel
reqRedrawBorderLabel
reqRedrawPreviewLabel
reqClose
reqPrintQuery
reqPreviewReady
reqPreviewEnqueue
reqPreviewDisplay
reqPreviewRefresh
reqPreviewDelayed
reqActivate
reqClose
reqPrintQuery
reqBecome
reqQuit
reqFatal
@@ -598,6 +604,8 @@ const (
actTransformQuery
actTransformSearch
actTrigger
actBgTransform
actBgTransformBorderLabel
actBgTransformGhost
@@ -1255,7 +1263,10 @@ func (t *Terminal) environImpl(forPreview bool) []string {
env = append(env, fmt.Sprintf("FZF_POS=%d", util.Min(t.merger.Length(), t.cy+1)))
env = append(env, fmt.Sprintf("FZF_CLICK_HEADER_LINE=%d", t.clickHeaderLine))
env = append(env, fmt.Sprintf("FZF_CLICK_HEADER_COLUMN=%d", t.clickHeaderColumn))
env = append(env, fmt.Sprintf("FZF_CLICK_FOOTER_LINE=%d", t.clickFooterLine))
env = append(env, fmt.Sprintf("FZF_CLICK_FOOTER_COLUMN=%d", t.clickFooterColumn))
env = t.addClickHeaderWord(env)
env = t.addClickFooterWord(env)
// Add preview environment variables if preview is enabled
pwindowSize := t.pwindowSize()
@@ -1383,7 +1394,7 @@ func (t *Terminal) ansiLabelPrinter(str string, color *tui.ColorPair, fill bool)
if !fill {
ellipsis, ellipsisWidth = util.Truncate(t.ellipsis, limit)
}
if length > limit-ellipsisWidth {
if length > limit {
trimmedRunes, _ := t.trimRight(runes, limit-ellipsisWidth)
window.CPrint(*color, string(trimmedRunes)+string(ellipsis))
} else if fill {
@@ -1624,14 +1635,14 @@ func (t *Terminal) changeHeader(header string) bool {
return needFullRedraw
}
func (t *Terminal) changeFooter(footer string) bool {
func (t *Terminal) changeFooter(footer string) {
var lines []string
if len(footer) > 0 {
lines = strings.Split(strings.TrimSuffix(footer, "\n"), "\n")
}
needFullRedraw := len(t.footer) != len(lines)
t.footer = lines
return needFullRedraw
t.clickFooterLine = 0
t.clickFooterColumn = 0
}
// UpdateHeader updates the header
@@ -1678,12 +1689,12 @@ func (t *Terminal) UpdateList(merger *Merger) {
// Trimmed by --tail: filter selection by index
filtered := make(map[int32]selectedItem)
minIndex := merger.minIndex
maxIndex := minIndex + int32(merger.Length())
maxIndex := merger.maxIndex
for k, v := range t.selected {
var included bool
if maxIndex > minIndex {
included = k >= minIndex && k < maxIndex
} else { // int32 overflow [==> <==]
} else if maxIndex < minIndex { // int32 overflow [==> <==]
included = k >= minIndex || k < maxIndex
}
if included {
@@ -1790,6 +1801,11 @@ func (t *Terminal) displayWidth(runes []rune) int {
return width
}
func (t *Terminal) displayWidthWithPrefix(str string, prefixWidth int) int {
width, _ := util.RunesWidth([]rune(str), prefixWidth, t.tabstop, math.MaxInt32)
return width
}
const (
minWidth = 4
minHeight = 3
@@ -2282,13 +2298,20 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
innerMarginInt[0]+shift, innerMarginInt[3]+pwidth+m, innerWidth-pwidth-m, innerHeight-shrink, tui.WindowList, noBorder, true)
// Clear characters on the margin
// fzf --bind 'space:preview(seq 100)' --preview-window left,1
// fzf --bind 'space:toggle-preview' --preview ':' --preview-window left,1
if !hasListBorder {
for y := 0; y < innerHeight; y++ {
t.window.Move(y, -1)
t.window.Print(" ")
}
}
// fzf --bind 'space:toggle-preview' --preview ':' --preview-window left,1,border-none
if !previewOpts.Border().HasRight() {
for y := 0; y < innerHeight; y++ {
t.window.Move(y, -2)
t.window.Print(" ")
}
}
innerBorderFn(marginInt[0], marginInt[3]+pwidth, width-pwidth, height)
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
@@ -2889,19 +2912,29 @@ func (t *Terminal) resizeIfNeeded() bool {
return true
}
// Check footer window
if len(t.footer) > 0 && (t.footerWindow == nil || t.footerWindow.Height() != len(t.footer)) ||
len(t.footer) == 0 && t.footerWindow != nil {
t.printAll()
return true
}
// Check if the header borders are used and header has changed
allHeaderLines := t.visibleHeaderLines()
primaryHeaderLines := allHeaderLines
if t.hasHeaderLinesWindow() {
needHeaderWindow := t.hasHeaderWindow()
needHeaderLinesWindow := t.hasHeaderLinesWindow()
if needHeaderLinesWindow {
primaryHeaderLines -= t.headerLines
}
// FIXME: Full redraw is triggered if there are too many lines in the header
// so that the header window cannot display all of them.
needHeaderLinesWindow := t.hasHeaderLinesWindow()
if (t.headerBorderShape.Visible() || needHeaderLinesWindow) &&
(t.headerWindow == nil && primaryHeaderLines > 0 || t.headerWindow != nil && primaryHeaderLines != t.headerWindow.Height()) ||
needHeaderLinesWindow && (t.headerLinesWindow == nil || t.headerLinesWindow != nil && t.headerLines != t.headerLinesWindow.Height()) ||
!needHeaderLinesWindow && t.headerLinesWindow != nil {
if (needHeaderWindow && t.headerWindow == nil) ||
(!needHeaderWindow && t.headerWindow != nil) ||
(needHeaderWindow && t.headerWindow != nil && primaryHeaderLines != t.headerWindow.Height()) ||
(needHeaderLinesWindow && t.headerLinesWindow == nil) ||
(!needHeaderLinesWindow && t.headerLinesWindow != nil) ||
(needHeaderLinesWindow && t.headerLinesWindow != nil && t.headerLines != t.headerLinesWindow.Height()) {
t.printAll()
return true
}
@@ -4739,6 +4772,7 @@ func (t *Terminal) addClickHeaderWord(env []string) []string {
if t.layout == layoutReverse {
headers[0], headers[1] = headers[1], headers[0]
}
var trimmedLine string
var words []Token
var lineNum int
for lineNum = 0; lineNum <= clickHeaderLine; lineNum++ {
@@ -4757,7 +4791,9 @@ func (t *Terminal) addClickHeaderWord(env []string) []string {
return env
}
words = Tokenize(line, t.delimiter)
// NOTE: We can't expand tabs here because the delimiter can contain tabs.
trimmedLine, _, _ = extractColor(line, nil, nil)
words = Tokenize(trimmedLine, t.delimiter)
if currentLine {
break
} else {
@@ -4768,11 +4804,14 @@ func (t *Terminal) addClickHeaderWord(env []string) []string {
}
colNum := t.clickHeaderColumn - 1
prefixWidth, prefixLength := 0, 0
for idx, token := range words {
prefixWidth := int(token.prefixLength)
word := token.text.ToString()
prefixWidth += t.displayWidthWithPrefix(trimmedLine[prefixLength:token.prefixLength], prefixWidth)
prefixLength = int(token.prefixLength)
word, _ := t.processTabs(token.text.ToRunes(), prefixWidth)
trimmed := strings.TrimRightFunc(word, unicode.IsSpace)
trimWidth, _ := util.RunesWidth([]rune(trimmed), prefixWidth, t.tabstop, math.MaxInt32)
trimWidth := t.displayWidthWithPrefix(trimmed, prefixWidth)
// Find the position of the first non-space character in the word
minPos := strings.IndexFunc(trimmed, func(r rune) bool {
@@ -4791,6 +4830,37 @@ func (t *Terminal) addClickHeaderWord(env []string) []string {
return env
}
func (t *Terminal) addClickFooterWord(env []string) []string {
clickFooterLine := t.clickFooterLine - 1
if clickFooterLine < 0 || clickFooterLine >= len(t.footer) {
// Never clicked on the footer
return env
}
// NOTE: Unlike in click-header, we don't use --delimiter here, since we're
// only interested in the word, not nth. Does this make sense?
trimmed, _, _ := extractColor(t.footer[clickFooterLine], nil, nil)
trimmed, _ = t.processTabs([]rune(trimmed), 0)
words := Tokenize(trimmed, Delimiter{})
colNum := t.clickFooterColumn - 1
for _, token := range words {
prefixWidth := int(token.prefixLength)
word := token.text.ToString()
trimmed := strings.TrimRightFunc(word, unicode.IsSpace)
trimWidth := t.displayWidthWithPrefix(trimmed, prefixWidth)
// Find the position of the first non-space character in the word
minPos := strings.IndexFunc(trimmed, func(r rune) bool {
return !unicode.IsSpace(r)
})
if colNum >= minPos && colNum >= prefixWidth && colNum < prefixWidth+trimWidth {
env = append(env, fmt.Sprintf("FZF_CLICK_FOOTER_WORD=%s", trimmed))
return env
}
}
return env
}
// Loop is called to start Terminal I/O
func (t *Terminal) Loop() error {
// prof := profile.Start(profile.ProfilePath("/tmp/"))
@@ -5116,6 +5186,8 @@ func (t *Terminal) Loop() error {
t.uiMutex.Lock()
t.mutex.Lock()
info := false
header := false
footer := false
for _, key := range keys {
req := util.EventType(key)
value := (*events)[req]
@@ -5153,13 +5225,9 @@ func (t *Terminal) Loop() error {
}
t.printList()
case reqHeader:
if !t.resizeIfNeeded() {
t.printHeader()
}
header = true
case reqFooter:
if !t.resizeIfNeeded() {
t.printFooter()
}
footer = true
case reqActivate:
t.suppress = false
if t.hasPreviewer() {
@@ -5177,10 +5245,10 @@ func (t *Terminal) Loop() error {
t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, true)
case reqRedrawPreviewLabel:
t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.activePreviewOpts.Border(), true)
case reqReinit:
t.tui.Resume(t.fullscreen, true)
t.fullRedraw()
case reqResize, reqFullRedraw, reqRedraw:
case reqReinit, reqResize, reqFullRedraw, reqRedraw:
if req == reqReinit {
t.tui.Resume(t.fullscreen, true)
}
if req == reqResize {
t.termSize = t.tui.Size()
}
@@ -5243,8 +5311,16 @@ func (t *Terminal) Loop() error {
return
}
}
if info && !t.resizeIfNeeded() {
t.printInfo()
if (info || header || footer) && !t.resizeIfNeeded() {
if info {
t.printInfo()
}
if header {
t.printHeader()
}
if footer {
t.printFooter()
}
}
t.flush()
t.mutex.Unlock()
@@ -5382,8 +5458,10 @@ func (t *Terminal) Loop() error {
return nil
}
}
triggering := map[tui.Event]struct{}{}
previousInput := t.input
previousCx := t.cx
previousVersion := t.version
t.lastKey = event.KeyName()
updatePreviewWindow := func(forcePreview bool) {
t.resizeWindows(forcePreview, false)
@@ -5648,7 +5726,7 @@ func (t *Terminal) Loop() error {
capture(true, func(expr string) {
// Split nth expression
tokens := strings.Split(expr, "|")
if nth, err := splitNth(tokens[0]); err == nil {
if nth, err := splitNth(tokens[0]); err == nil || len(expr) == 0 {
// Changed
newNth = &nth
} else {
@@ -5670,24 +5748,17 @@ func (t *Terminal) Loop() error {
t.cx = len(t.input)
case actChangeHeader, actTransformHeader, actBgTransformHeader:
capture(false, func(header string) {
// When a dedicated header window is not used, we may need to
// update other elements as well.
if t.changeHeader(header) {
if t.headerWindow != nil {
// Need to resize header window
req(reqRedraw)
} else {
req(reqHeader, reqList, reqPrompt, reqInfo)
}
} else {
req(reqHeader)
req(reqList, reqPrompt, reqInfo)
}
req(reqHeader)
})
case actChangeFooter, actTransformFooter, actBgTransformFooter:
capture(false, func(footer string) {
if t.changeFooter(footer) {
req(reqRedraw)
} else {
req(reqFooter)
}
t.changeFooter(footer)
req(reqFooter)
})
case actChangeHeaderLabel, actTransformHeaderLabel, actBgTransformHeaderLabel:
capture(true, func(label string) {
@@ -6179,6 +6250,20 @@ func (t *Terminal) Loop() error {
case actDisableSearch:
t.paused = true
req(reqPrompt)
case actTrigger:
if _, chords, err := parseKeyChords(a.a, ""); err == nil {
for _, chord := range chords {
if _, prs := triggering[chord]; prs {
// Avoid recursive triggering
continue
}
if acts, prs := t.keymap[chord]; prs {
triggering[chord] = struct{}{}
doActions(acts)
delete(triggering, chord)
}
}
}
case actSigStop:
p, err := os.FindProcess(os.Getpid())
if err == nil {
@@ -6368,6 +6453,18 @@ func (t *Terminal) Loop() error {
return doActions(actionsFor(tui.ClickHeader))
}
// Inside the footer window
if clicked && t.footerWindow != nil && t.footerWindow.Enclose(my, mx) {
mx -= t.footerWindow.Left() + t.headerIndent(t.footerBorderShape)
my -= t.footerWindow.Top()
if mx < 0 {
break
}
t.clickFooterLine = my + 1
t.clickFooterColumn = mx + 1
return doActions(actionsFor(tui.ClickFooter))
}
// Ignored
if !t.window.Enclose(my, mx) && !barDragging {
break
@@ -6488,13 +6585,13 @@ func (t *Terminal) Loop() error {
t.reading = true
}
case actUnbind:
if keys, err := parseKeyChords(a.a, "PANIC"); err == nil {
if keys, _, err := parseKeyChords(a.a, "PANIC"); err == nil {
for key := range keys {
delete(t.keymap, key)
}
}
case actRebind:
if keys, err := parseKeyChords(a.a, "PANIC"); err == nil {
if keys, _, err := parseKeyChords(a.a, "PANIC"); err == nil {
for key := range keys {
if originalAction, found := t.keymapOrg[key]; found {
t.keymap[key] = originalAction
@@ -6502,7 +6599,7 @@ func (t *Terminal) Loop() error {
}
}
case actToggleBind:
if keys, err := parseKeyChords(a.a, "PANIC"); err == nil {
if keys, _, err := parseKeyChords(a.a, "PANIC"); err == nil {
for key := range keys {
if _, bound := t.keymap[key]; bound {
delete(t.keymap, key)
@@ -6648,6 +6745,9 @@ func (t *Terminal) Loop() error {
if onEOFs, prs := t.keymap[tui.BackwardEOF.AsEvent()]; beof && prs && !doActions(onEOFs) {
continue
}
if onMultis, prs := t.keymap[tui.Multi.AsEvent()]; t.version != previousVersion && prs && !doActions(onMultis) {
continue
}
} else {
jumpEvent := tui.JumpCancel
if event.Type == tui.Rune {

View File

@@ -110,11 +110,13 @@ func _() {
_ = x[Jump-99]
_ = x[JumpCancel-100]
_ = x[ClickHeader-101]
_ = x[ClickFooter-102]
_ = x[Multi-103]
}
const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLEnterCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlDeleteCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltCtrlAltInvalidFatalBracketedPasteBeginBracketedPasteEndMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancelClickHeader"
const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLEnterCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlDeleteCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltCtrlAltInvalidFatalBracketedPasteBeginBracketedPasteEndMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancelClickHeaderClickFooterMulti"
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}
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, 695, 700}
func (i EventType) String() string {
if i < 0 || i >= EventType(len(_EventType_index)-1) {

View File

@@ -132,6 +132,8 @@ const (
Jump
JumpCancel
ClickHeader
ClickFooter
Multi
)
func (t EventType) AsEvent() Event {
@@ -503,7 +505,7 @@ type BorderCharacter int
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
if shape == BorderNone || shape == BorderPhantom {
return BorderStyle{
shape: shape,
shape: BorderNone,
top: ' ',
bottom: ' ',
left: ' ',

View File

@@ -1415,6 +1415,11 @@ class TestCore < TestInteractive
tmux.until { assert_match(%r{ --1/10000/10000-- *$}, it[-1]) }
end
def test_info_command_inline_right_no_ansi
tmux.send_keys(%(seq 10000 | #{FZF} --info-command 'echo -e "--$FZF_POS/$FZF_INFO--"' --info inline-right), :Enter)
tmux.until { assert_match(%r{ --1/10000/10000-- *$}, it[-1]) }
end
def test_info_command_and_focus
tmux.send_keys(%(seq 100 | #{FZF} --separator x --info-command 'echo $FZF_POS' --bind focus:clear-query), :Enter)
tmux.until { assert_match(/^ 1 xx/, it[-2]) }
@@ -1930,7 +1935,10 @@ class TestCore < TestInteractive
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.until do |lines|
assert lines.any_include?('100/100')
assert lines.any_include?('foo')
end
tmux.send_keys :Space
tmux.until { |lines| assert lines.any_include?('bar') }
end
@@ -1981,4 +1989,80 @@ class TestCore < TestInteractive
refute lines.any_include?('[1]')
end
end
def test_render_order
tmux.send_keys %(seq 100 | #{FZF} --bind='focus:preview(echo boom)+change-footer(bam)'), :Enter
tmux.until { assert_equal 100, it.match_count }
tmux.until { assert it.any_include?('boom') }
tmux.until { assert it.any_include?('bam') }
end
def test_multi_event
tmux.send_keys %(seq 100 | #{FZF} --multi --bind 'multi:transform-footer:(( FZF_SELECT_COUNT )) && echo "Selected $FZF_SELECT_COUNT item(s)"'), :Enter
tmux.until { assert_equal 100, it.match_count }
tmux.send_keys :Tab
tmux.until { assert_equal 1, it.select_count }
tmux.until { assert it.any_include?('Selected 1 item(s)') }
tmux.send_keys :Tab
tmux.until { assert_equal 0, it.select_count }
tmux.until { refute it.any_include?('Selected') }
end
def test_preserve_selection_on_revision_bump
tmux.send_keys %(seq 100 | #{FZF} --multi --sync --query "'1" --bind 'a:select-all+change-header(pressed a),b:change-header(pressed b)+change-nth(1),c:exclude'), :Enter
tmux.until do
assert_equal 20, it.match_count
assert_equal 0, it.select_count
end
tmux.send_keys :a
tmux.until do
assert_equal 20, it.match_count
assert_equal 20, it.select_count
assert it.any_include?('pressed a')
end
tmux.send_keys :b
tmux.until do
assert_equal 20, it.match_count
assert_equal 20, it.select_count
refute it.any_include?('pressed a')
assert it.any_include?('pressed b')
end
tmux.send_keys :a
tmux.until do
assert_equal 20, it.match_count
assert_equal 20, it.select_count
assert it.any_include?('pressed a')
refute it.any_include?('pressed b')
end
tmux.send_keys :c
tmux.until do
assert_equal 19, it.match_count
assert_equal 19, it.select_count
end
end
def test_trigger
tmux.send_keys %(seq 100 | #{FZF} --bind 'a:up+trigger(a),b:trigger(a,a,b,a)'), :Enter
tmux.until { assert_equal 100, it.match_count }
tmux.until { |lines| assert_includes lines, '> 1' }
tmux.send_keys :a
tmux.until { |lines| assert_includes lines, '> 3' }
tmux.send_keys :b
tmux.until { |lines| assert_includes lines, '> 9' }
end
def test_change_nth_unset_default
tmux.send_keys %(echo foo bar | #{FZF} --nth 2 --query fb --bind space:change-nth:), :Enter
tmux.until do
assert_equal 1, it.item_count
assert_equal 0, it.match_count
end
tmux.send_keys :Space
tmux.until do
assert_equal 1, it.item_count
assert_equal 1, it.match_count
end
end
end

View File

@@ -995,6 +995,8 @@ class TestLayout < TestInteractive
%[--header "$(seq 101 102)" --header-border sharp],
%[--header "$(seq 101 102)" --header-border sharp --header-first],
%[--header "$(seq 101 102)" --header-border sharp --header-lines 2],
%[--header "$(seq 101 102)" --header-border sharp --header-lines 2 --no-header-lines-border],
%[--header "$(seq 101 102)" --header-border sharp --header-lines 2 --header-lines-border none],
%[--header "$(seq 101 102)" --header-border sharp --header-lines 2 --header-lines-border sharp],
%[--header "$(seq 101 102)" --header-border sharp --header-lines 2 --header-lines-border sharp --header-first --input-border sharp],
%[--header "$(seq 101 102)" --header-border sharp --header-lines 2 --header-lines-border sharp --header-first --no-input],
@@ -1002,24 +1004,24 @@ class TestLayout < TestInteractive
%[--header "$(seq 101 102)" --style full:sharp --header-first]
]
output = <<~BLOCK
201 201 201 201 201 201 201 201 201 201 201 FOOT FOOT
202 202 202 202 202 202 202 202 202 202 202 201 201
FOOT FOOT FOOT FOOT FOOT FOOT FOOT FOOT FOOT FOOT FOOT 202 202
3 3 3 > 3 > 3 3 3 > 3 > 3 > 3 > 3
2 2 2 2 2 2 2 3
> 1 > 1 > 1 1 1 > 1 > 1 2 2 2 2 2 3
3/3 101 3/3 101 1/1 3/3 1 1 1 1 > 1 2
> 102 > 102 > 101 > 101 101 > 1
3/3 101 1/1 101 102 102 102
> 102 > 102 HEAD 101 HEAD 101 1/1 101
3/3 102 1/1 102 > 102 3/3 >
> HEAD > HEAD HEAD >
1/1
> 101 101
102 102
HEAD HEAD
201 201 201 201 201 201 201 201 201 201 201 201 201 FOOT FOOT
202 202 202 202 202 202 202 202 202 202 202 202 202 201 201
FOOT FOOT FOOT FOOT FOOT FOOT FOOT FOOT FOOT FOOT FOOT FOOT FOOT 202 202
3 3 3 > 3 > 3 3 3 > 3 > 3 > 3 > 3 > 3 > 3
2 2 2 2 2 2 2 2 3
> 1 > 1 > 1 1 1 > 1 > 1 2 2 1 2 2 2 2 3
3/3 101 3/3 101 1/1 3/3 1 1 1 1 1 > 1 2
> 102 > 102 > 101 > 101 101 101 101 > 1
3/3 101 1/1 101 102 102 102 102 102
> 102 > 102 HEAD 101 HEAD HEAD HEAD 101 1/1 101
3/3 102 1/1 1/1 1/1 102 > 102 3/3 >
> HEAD > > > HEAD HEAD >
1/1
> 101 101
102 102
HEAD HEAD
BLOCK
expects = []

View File

@@ -190,17 +190,17 @@ class TestPreview < TestInteractive
end
def test_preview_asterisk
tmux.send_keys %(seq 5 | #{FZF} --multi --preview 'echo [{} / {+} / {*}]' --preview-window '+{1}'), :Enter
tmux.send_keys %(seq 5 | #{FZF} --multi --preview 'echo [{}/{+}/{*}/{*n}]' --preview-window '+{1}'), :Enter
tmux.until { |lines| assert_equal 5, lines.match_count }
tmux.until { |lines| assert_includes lines[1], ' [1 / 1 / 1 2 3 4 5] ' }
tmux.until { |lines| assert_includes lines[1], ' [1/1/1 2 3 4 5/0 1 2 3 4] ' }
tmux.send_keys :BTab
tmux.until { |lines| assert_includes lines[1], ' [2 / 1 / 1 2 3 4 5] ' }
tmux.until { |lines| assert_includes lines[1], ' [2/1/1 2 3 4 5/0 1 2 3 4] ' }
tmux.send_keys :BTab
tmux.until { |lines| assert_includes lines[1], ' [3 / 1 2 / 1 2 3 4 5] ' }
tmux.until { |lines| assert_includes lines[1], ' [3/1 2/1 2 3 4 5/0 1 2 3 4] ' }
tmux.send_keys '5'
tmux.until { |lines| assert_includes lines[1], ' [5 / 1 2 / 5] ' }
tmux.until { |lines| assert_includes lines[1], ' [5/1 2/5/4] ' }
tmux.send_keys '5'
tmux.until { |lines| assert_includes lines[1], ' [ / 1 2 / ] ' }
tmux.until { |lines| assert_includes lines[1], ' [/1 2//] ' }
end
def test_preview_file