Compare commits

..

21 Commits

Author SHA1 Message Date
longhutianjie
978b6254c7 chore: remove redundant word in comment (#4490)
Signed-off-by: longhutianjie <keplrnewton@icloud.com>
2025-08-14 13:26:29 +09: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
24 changed files with 355 additions and 147 deletions

View File

@@ -1,6 +1,54 @@
CHANGELOG 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 0.64.0
------ ------
- Added `multi` event that is triggered when the multi-selection has changed. - Added `multi` event that is triggered when the multi-selection has changed.

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf\-tmux 1 "Jul 2025" "fzf 0.64.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 .SH NAME
fzf\-tmux - open fzf in tmux split pane fzf\-tmux - open fzf in tmux split pane

View File

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf 1 "Jul 2025" "fzf 0.64.0" "fzf - a command-line fuzzy finder" .TH fzf 1 "Aug 2025" "fzf 0.65.1" "fzf - a command-line fuzzy finder"
.SH NAME .SH NAME
fzf - a command-line fuzzy finder fzf - a command-line fuzzy finder
@@ -1682,6 +1682,14 @@ e.g.
)'\fR )'\fR
.RE .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: .SS AVAILABLE ACTIONS:
A key or an event can be bound to one or more of the following actions. A key or an event can be bound to one or more of the following actions.
@@ -1698,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) \fBbecome(...)\fR (replace fzf process with the specified command; see below for the details)
\fBbeginning\-of\-line\fR \fIctrl\-a home\fR \fBbeginning\-of\-line\fR \fIctrl\-a home\fR
\fBbell\fR (ring the terminal bell) \fBbell\fR (ring the terminal bell)
\fBbg\-cancel\fR (cancel background transform processes)
\fBcancel\fR (clear query string if not empty, abort fzf otherwise) \fBcancel\fR (clear query string if not empty, abort fzf otherwise)
\fBchange\-border\-label(...)\fR (change \fB\-\-border\-label\fR to the given string) \fBchange\-border\-label(...)\fR (change \fB\-\-border\-label\fR to the given string)
\fBchange\-ghost(...)\fR (change ghost text to the given string) \fBchange\-ghost(...)\fR (change ghost text to the given string)
@@ -1808,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\-prompt(...)\fR (transform prompt string using an external command)
\fBtransform\-query(...)\fR (transform query string using an external command) \fBtransform\-query(...)\fR (transform query string using an external command)
\fBtransform\-search(...)\fR (trigger fzf search with the output of 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) \fBunbind(...)\fR (unbind bindings)
\fBunix\-line\-discard\fR \fIctrl\-u\fR \fBunix\-line\-discard\fR \fIctrl\-u\fR
\fBunix\-word\-rubout\fR \fIctrl\-w\fR \fBunix\-word\-rubout\fR \fIctrl\-w\fR
@@ -1959,7 +1969,8 @@ Transform actions are synchronous, meaning fzf becomes unresponsive while the
command runs. To avoid this, each \fBtransform*\fR action has a corresponding 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 \fBbg\-transform*\fR variant that runs in the background. Unless you need to
chain multiple transform actions where later ones depend on earlier results, 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 .SS PREVIEW BINDING

View File

@@ -1027,8 +1027,23 @@ if has('nvim')
let buf = nvim_create_buf(v:false, v:true) let buf = nvim_create_buf(v:false, v:true)
let opts = extend({'relative': 'editor', 'style': 'minimal'}, a:opts) let opts = extend({'relative': 'editor', 'style': 'minimal'}, a:opts)
let win = nvim_open_win(buf, v:true, opts) let win = nvim_open_win(buf, v:true, opts)
silent! call setwinvar(win, '&winhighlight', 'Pmenu:,Normal:Normal')
call setwinvar(win, '&colorcolumn', '') 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 return buf
endfunction endfunction
else else

View File

@@ -22,11 +22,11 @@ __fzf_exec_awk() {
# modern point of view. To use a standard-conforming version in Solaris, # modern point of view. To use a standard-conforming version in Solaris,
# one needs to explicitly use /usr/xpg4/bin/awk. # one needs to explicitly use /usr/xpg4/bin/awk.
__fzf_awk=/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 && # choose the faster mawk if: it's installed && build date >= 20230322 &&
# version >= 1.3.4 # version >= 1.3.4
local n x y z d 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 [[ $n == mawk ]] && (( d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004 )) && __fzf_awk=mawk
fi fi
fi fi

View File

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

View File

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

View File

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

View File

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

View File

@@ -113,65 +113,66 @@ func _() {
_ = x[actTransformPrompt-102] _ = x[actTransformPrompt-102]
_ = x[actTransformQuery-103] _ = x[actTransformQuery-103]
_ = x[actTransformSearch-104] _ = x[actTransformSearch-104]
_ = x[actBgTransform-105] _ = x[actTrigger-105]
_ = x[actBgTransformBorderLabel-106] _ = x[actBgTransform-106]
_ = x[actBgTransformGhost-107] _ = x[actBgTransformBorderLabel-107]
_ = x[actBgTransformHeader-108] _ = x[actBgTransformGhost-108]
_ = x[actBgTransformFooter-109] _ = x[actBgTransformHeader-109]
_ = x[actBgTransformHeaderLabel-110] _ = x[actBgTransformFooter-110]
_ = x[actBgTransformFooterLabel-111] _ = x[actBgTransformHeaderLabel-111]
_ = x[actBgTransformInputLabel-112] _ = x[actBgTransformFooterLabel-112]
_ = x[actBgTransformListLabel-113] _ = x[actBgTransformInputLabel-113]
_ = x[actBgTransformNth-114] _ = x[actBgTransformListLabel-114]
_ = x[actBgTransformPointer-115] _ = x[actBgTransformNth-115]
_ = x[actBgTransformPreviewLabel-116] _ = x[actBgTransformPointer-116]
_ = x[actBgTransformPrompt-117] _ = x[actBgTransformPreviewLabel-117]
_ = x[actBgTransformQuery-118] _ = x[actBgTransformPrompt-118]
_ = x[actBgTransformSearch-119] _ = x[actBgTransformQuery-119]
_ = x[actBgCancel-120] _ = x[actBgTransformSearch-120]
_ = x[actSearch-121] _ = x[actBgCancel-121]
_ = x[actPreview-122] _ = x[actSearch-122]
_ = x[actPreviewTop-123] _ = x[actPreview-123]
_ = x[actPreviewBottom-124] _ = x[actPreviewTop-124]
_ = x[actPreviewUp-125] _ = x[actPreviewBottom-125]
_ = x[actPreviewDown-126] _ = x[actPreviewUp-126]
_ = x[actPreviewPageUp-127] _ = x[actPreviewDown-127]
_ = x[actPreviewPageDown-128] _ = x[actPreviewPageUp-128]
_ = x[actPreviewHalfPageUp-129] _ = x[actPreviewPageDown-129]
_ = x[actPreviewHalfPageDown-130] _ = x[actPreviewHalfPageUp-130]
_ = x[actPrevHistory-131] _ = x[actPreviewHalfPageDown-131]
_ = x[actPrevSelected-132] _ = x[actPrevHistory-132]
_ = x[actPrint-133] _ = x[actPrevSelected-133]
_ = x[actPut-134] _ = x[actPrint-134]
_ = x[actNextHistory-135] _ = x[actPut-135]
_ = x[actNextSelected-136] _ = x[actNextHistory-136]
_ = x[actExecute-137] _ = x[actNextSelected-137]
_ = x[actExecuteSilent-138] _ = x[actExecute-138]
_ = x[actExecuteMulti-139] _ = x[actExecuteSilent-139]
_ = x[actSigStop-140] _ = x[actExecuteMulti-140]
_ = x[actFirst-141] _ = x[actSigStop-141]
_ = x[actLast-142] _ = x[actFirst-142]
_ = x[actReload-143] _ = x[actLast-143]
_ = x[actReloadSync-144] _ = x[actReload-144]
_ = x[actDisableSearch-145] _ = x[actReloadSync-145]
_ = x[actEnableSearch-146] _ = x[actDisableSearch-146]
_ = x[actSelect-147] _ = x[actEnableSearch-147]
_ = x[actDeselect-148] _ = x[actSelect-148]
_ = x[actUnbind-149] _ = x[actDeselect-149]
_ = x[actRebind-150] _ = x[actUnbind-150]
_ = x[actToggleBind-151] _ = x[actRebind-151]
_ = x[actBecome-152] _ = x[actToggleBind-152]
_ = x[actShowHeader-153] _ = x[actBecome-153]
_ = x[actHideHeader-154] _ = x[actShowHeader-154]
_ = x[actBell-155] _ = x[actHideHeader-155]
_ = x[actExclude-156] _ = x[actBell-156]
_ = x[actExcludeMulti-157] _ = x[actExclude-157]
_ = x[actAsync-158] _ = 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 { func (i actionType) String() string {
if i < 0 || i >= actionType(len(_actionType_index)-1) { if i < 0 || i >= actionType(len(_actionType_index)-1) {

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)") 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) { func parseKeyChords(str string, message string) (map[tui.Event]string, []tui.Event, error) {
return parseKeyChordsImpl(str, message)
}
func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error) {
if len(str) == 0 { 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})) str = regexp.MustCompile("(?i)(alt-),").ReplaceAllString(str, "$1"+string([]rune{escapedComma}))
tokens := strings.Split(str, ",") tokens := strings.Split(str, ",")
if str == "," || strings.HasPrefix(str, ",,") || strings.HasSuffix(str, ",,") || strings.Contains(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) lkey := strings.ToLower(key)
add := func(e tui.EventType) { add := func(e tui.EventType) {
chords[e.AsEvent()] = key chords[e.AsEvent()] = key
list = append(list, e.AsEvent())
} }
switch lkey { switch lkey {
case "up": case "up":
@@ -969,7 +967,9 @@ func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error
case "enter", "return": case "enter", "return":
add(tui.Enter) add(tui.Enter)
case "space": case "space":
chords[tui.Key(' ')] = key evt := tui.Key(' ')
chords[evt] = key
list = append(list, evt)
case "backspace", "bspace", "bs": case "backspace", "bspace", "bs":
add(tui.Backspace) add(tui.Backspace)
case "ctrl-space": case "ctrl-space":
@@ -1008,12 +1008,18 @@ func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error
add(tui.JumpCancel) add(tui.JumpCancel)
case "click-header": case "click-header":
add(tui.ClickHeader) add(tui.ClickHeader)
case "click-footer":
add(tui.ClickFooter)
case "multi": case "multi":
add(tui.Multi) add(tui.Multi)
case "alt-enter", "alt-return": case "alt-enter", "alt-return":
chords[tui.CtrlAltKey('m')] = key evt := tui.CtrlAltKey('m')
chords[evt] = key
list = append(list, evt)
case "alt-space": case "alt-space":
chords[tui.AltKey(' ')] = key evt := tui.AltKey(' ')
chords[evt] = key
list = append(list, evt)
case "alt-bs", "alt-bspace", "alt-backspace": case "alt-bs", "alt-bspace", "alt-backspace":
add(tui.AltBackspace) add(tui.AltBackspace)
case "alt-up": case "alt-up":
@@ -1091,7 +1097,9 @@ func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error
default: default:
runes := []rune(key) runes := []rune(key)
if len(key) == 10 && strings.HasPrefix(lkey, "ctrl-alt-") && isAlphabet(lkey[9]) { 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]) { } else if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
add(tui.EventType(tui.CtrlA.Int() + int(lkey[5]) - 'a')) add(tui.EventType(tui.CtrlA.Int() + int(lkey[5]) - 'a'))
} else if len(runes) == 5 && strings.HasPrefix(lkey, "alt-") { } else if len(runes) == 5 && strings.HasPrefix(lkey, "alt-") {
@@ -1104,17 +1112,21 @@ func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error
case escapedPlus: case escapedPlus:
r = '+' 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' { } 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')) add(tui.EventType(tui.F1.Int() + int(key[1]) - '1'))
} else if len(runes) == 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 { } 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) { func parseScheme(str string) (string, []criterion, error) {
@@ -1437,7 +1449,7 @@ const (
func init() { func init() {
executeRegexp = regexp.MustCompile( 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("[,:]+") splitRegexp = regexp.MustCompile("[,:]+")
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+") actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
} }
@@ -1734,7 +1746,7 @@ func parseActionList(masked string, original string, prevActions []*action, putA
} }
switch t { switch t {
case actUnbind, actRebind, actToggleBind: 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 return nil, err
} }
case actChangePreviewWindow: case actChangePreviewWindow:
@@ -1779,7 +1791,7 @@ func parseKeymap(keymap map[tui.Event][]*action, str string) error {
} else if len(keyName) == 1 && keyName[0] == escapedPlus { } else if len(keyName) == 1 && keyName[0] == escapedPlus {
key = tui.Key('+') key = tui.Key('+')
} else { } else {
keys, err := parseKeyChordsImpl(keyName, "key name required") keys, _, err := parseKeyChords(keyName, "key name required")
if err != nil { if err != nil {
return err return err
} }
@@ -1926,6 +1938,8 @@ func isExecuteAction(str string) actionType {
return actBgTransformQuery return actBgTransformQuery
case "bg-transform-search": case "bg-transform-search":
return actBgTransformSearch return actBgTransformSearch
case "trigger":
return actTrigger
case "search": case "search":
return actSearch return actSearch
} }
@@ -1933,7 +1947,7 @@ func isExecuteAction(str string) actionType {
} }
func parseToggleSort(keymap map[tui.Event][]*action, str string) error { 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 { if err != nil {
return err return err
} }
@@ -2472,7 +2486,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if err != nil { if err != nil {
return err return err
} }
chords, err := parseKeyChords(str, "key names required") chords, _, err := parseKeyChords(str, "key names required")
if err != nil { if err != nil {
return err return err
} }
@@ -2924,7 +2938,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
return err return err
} }
case "--no-header-lines-border": case "--no-header-lines-border":
opts.HeaderLinesShape = tui.BorderNone opts.HeaderLinesShape = tui.BorderUndefined
case "--header-lines-border": case "--header-lines-border":
hasArg, arg := optionalNextString() hasArg, arg := optionalNextString()
if opts.HeaderLinesShape, err = parseBorder(arg, !hasArg); err != nil { 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) { 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) { checkEvent := func(e tui.Event, s string) {
if pairs[e] != s { if pairs[e] != s {
t.Errorf("%s != %s", pairs[e], s) t.Errorf("%s != %s", pairs[e], s)
@@ -168,7 +168,7 @@ func TestParseKeys(t *testing.T) {
checkEvent(tui.AltKey(' '), "alt-SPACE") checkEvent(tui.AltKey(' '), "alt-SPACE")
// Synonyms // 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 { if len(pairs) != 9 {
t.Error(9) t.Error(9)
} }
@@ -182,7 +182,7 @@ func TestParseKeys(t *testing.T) {
check(tui.Left, "left") check(tui.Left, "left")
check(tui.Right, "right") 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 { if len(pairs) != 11 {
t.Error(11) t.Error(11)
} }
@@ -211,40 +211,40 @@ func TestParseKeysWithComma(t *testing.T) {
} }
} }
pairs, _ := parseKeyChords(",", "") pairs, _, _ := parseKeyChords(",", "")
checkN(len(pairs), 1) checkN(len(pairs), 1)
check(pairs, tui.Key(','), ",") check(pairs, tui.Key(','), ",")
pairs, _ = parseKeyChords(",,a,b", "") pairs, _, _ = parseKeyChords(",,a,b", "")
checkN(len(pairs), 3) checkN(len(pairs), 3)
check(pairs, tui.Key('a'), "a") check(pairs, tui.Key('a'), "a")
check(pairs, tui.Key('b'), "b") check(pairs, tui.Key('b'), "b")
check(pairs, tui.Key(','), ",") check(pairs, tui.Key(','), ",")
pairs, _ = parseKeyChords("a,b,,", "") pairs, _, _ = parseKeyChords("a,b,,", "")
checkN(len(pairs), 3) checkN(len(pairs), 3)
check(pairs, tui.Key('a'), "a") check(pairs, tui.Key('a'), "a")
check(pairs, tui.Key('b'), "b") check(pairs, tui.Key('b'), "b")
check(pairs, tui.Key(','), ",") check(pairs, tui.Key(','), ",")
pairs, _ = parseKeyChords("a,,,b", "") pairs, _, _ = parseKeyChords("a,,,b", "")
checkN(len(pairs), 3) checkN(len(pairs), 3)
check(pairs, tui.Key('a'), "a") check(pairs, tui.Key('a'), "a")
check(pairs, tui.Key('b'), "b") check(pairs, tui.Key('b'), "b")
check(pairs, tui.Key(','), ",") check(pairs, tui.Key(','), ",")
pairs, _ = parseKeyChords("a,,,b,c", "") pairs, _, _ = parseKeyChords("a,,,b,c", "")
checkN(len(pairs), 4) checkN(len(pairs), 4)
check(pairs, tui.Key('a'), "a") check(pairs, tui.Key('a'), "a")
check(pairs, tui.Key('b'), "b") check(pairs, tui.Key('b'), "b")
check(pairs, tui.Key('c'), "c") check(pairs, tui.Key('c'), "c")
check(pairs, tui.Key(','), ",") check(pairs, tui.Key(','), ",")
pairs, _ = parseKeyChords(",,,", "") pairs, _, _ = parseKeyChords(",,,", "")
checkN(len(pairs), 1) checkN(len(pairs), 1)
check(pairs, tui.Key(','), ",") check(pairs, tui.Key(','), ",")
pairs, _ = parseKeyChords(",ALT-,,", "") pairs, _, _ = parseKeyChords(",ALT-,,", "")
checkN(len(pairs), 1) checkN(len(pairs), 1)
check(pairs, tui.AltKey(','), "ALT-,") check(pairs, tui.AltKey(','), "ALT-,")
} }

View File

@@ -285,7 +285,7 @@ func (r *Reader) readFiles(roots []string, opts walkerOpts, ignores []string) bo
if strings.HasPrefix(ignore, sep) { if strings.HasPrefix(ignore, sep) {
ignoresSuffix = append(ignoresSuffix, ignore) ignoresSuffix = append(ignoresSuffix, ignore)
} else { } else {
// 'foo/bar' should match match // 'foo/bar' should match
// * 'foo/bar' // * 'foo/bar'
// * 'baz/foo/bar' // * 'baz/foo/bar'
// * but NOT 'bazfoo/bar' // * but NOT 'bazfoo/bar'

View File

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

View File

@@ -67,7 +67,7 @@ const maxFocusEvents = 10000
const blockDuration = 1 * time.Second const blockDuration = 1 * time.Second
func init() { 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*$`) whiteSuffix = regexp.MustCompile(`\s*$`)
offsetComponentRegex = regexp.MustCompile(`([+-][0-9]+)|(-?/[1-9][0-9]*)`) offsetComponentRegex = regexp.MustCompile(`([+-][0-9]+)|(-?/[1-9][0-9]*)`)
offsetTrimCharsRegex = regexp.MustCompile(`[^0-9/+-]`) offsetTrimCharsRegex = regexp.MustCompile(`[^0-9/+-]`)
@@ -422,6 +422,8 @@ type Terminal struct {
forcePreview bool forcePreview bool
clickHeaderLine int clickHeaderLine int
clickHeaderColumn int clickHeaderColumn int
clickFooterLine int
clickFooterColumn int
proxyScript string proxyScript string
numLinesCache map[int32]numLinesCacheValue numLinesCache map[int32]numLinesCacheValue
} }
@@ -602,6 +604,8 @@ const (
actTransformQuery actTransformQuery
actTransformSearch actTransformSearch
actTrigger
actBgTransform actBgTransform
actBgTransformBorderLabel actBgTransformBorderLabel
actBgTransformGhost actBgTransformGhost
@@ -1259,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_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_LINE=%d", t.clickHeaderLine))
env = append(env, fmt.Sprintf("FZF_CLICK_HEADER_COLUMN=%d", t.clickHeaderColumn)) 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.addClickHeaderWord(env)
env = t.addClickFooterWord(env)
// Add preview environment variables if preview is enabled // Add preview environment variables if preview is enabled
pwindowSize := t.pwindowSize() pwindowSize := t.pwindowSize()
@@ -1387,7 +1394,7 @@ func (t *Terminal) ansiLabelPrinter(str string, color *tui.ColorPair, fill bool)
if !fill { if !fill {
ellipsis, ellipsisWidth = util.Truncate(t.ellipsis, limit) ellipsis, ellipsisWidth = util.Truncate(t.ellipsis, limit)
} }
if length > limit-ellipsisWidth { if length > limit {
trimmedRunes, _ := t.trimRight(runes, limit-ellipsisWidth) trimmedRunes, _ := t.trimRight(runes, limit-ellipsisWidth)
window.CPrint(*color, string(trimmedRunes)+string(ellipsis)) window.CPrint(*color, string(trimmedRunes)+string(ellipsis))
} else if fill { } else if fill {
@@ -1634,6 +1641,8 @@ func (t *Terminal) changeFooter(footer string) {
lines = strings.Split(strings.TrimSuffix(footer, "\n"), "\n") lines = strings.Split(strings.TrimSuffix(footer, "\n"), "\n")
} }
t.footer = lines t.footer = lines
t.clickFooterLine = 0
t.clickFooterColumn = 0
} }
// UpdateHeader updates the header // UpdateHeader updates the header
@@ -1792,6 +1801,11 @@ func (t *Terminal) displayWidth(runes []rune) int {
return width return width
} }
func (t *Terminal) displayWidthWithPrefix(str string, prefixWidth int) int {
width, _ := util.RunesWidth([]rune(str), prefixWidth, t.tabstop, math.MaxInt32)
return width
}
const ( const (
minWidth = 4 minWidth = 4
minHeight = 3 minHeight = 3
@@ -2284,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) innerMarginInt[0]+shift, innerMarginInt[3]+pwidth+m, innerWidth-pwidth-m, innerHeight-shrink, tui.WindowList, noBorder, true)
// Clear characters on the margin // 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 { if !hasListBorder {
for y := 0; y < innerHeight; y++ { for y := 0; y < innerHeight; y++ {
t.window.Move(y, -1) t.window.Move(y, -1)
t.window.Print(" ") 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) innerBorderFn(marginInt[0], marginInt[3]+pwidth, width-pwidth, height)
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height) createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
@@ -4751,6 +4772,7 @@ func (t *Terminal) addClickHeaderWord(env []string) []string {
if t.layout == layoutReverse { if t.layout == layoutReverse {
headers[0], headers[1] = headers[1], headers[0] headers[0], headers[1] = headers[1], headers[0]
} }
var trimmedLine string
var words []Token var words []Token
var lineNum int var lineNum int
for lineNum = 0; lineNum <= clickHeaderLine; lineNum++ { for lineNum = 0; lineNum <= clickHeaderLine; lineNum++ {
@@ -4769,7 +4791,9 @@ func (t *Terminal) addClickHeaderWord(env []string) []string {
return env 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 { if currentLine {
break break
} else { } else {
@@ -4780,11 +4804,14 @@ func (t *Terminal) addClickHeaderWord(env []string) []string {
} }
colNum := t.clickHeaderColumn - 1 colNum := t.clickHeaderColumn - 1
prefixWidth, prefixLength := 0, 0
for idx, token := range words { for idx, token := range words {
prefixWidth := int(token.prefixLength) prefixWidth += t.displayWidthWithPrefix(trimmedLine[prefixLength:token.prefixLength], prefixWidth)
word := token.text.ToString() prefixLength = int(token.prefixLength)
word, _ := t.processTabs(token.text.ToRunes(), prefixWidth)
trimmed := strings.TrimRightFunc(word, unicode.IsSpace) 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 // Find the position of the first non-space character in the word
minPos := strings.IndexFunc(trimmed, func(r rune) bool { minPos := strings.IndexFunc(trimmed, func(r rune) bool {
@@ -4803,6 +4830,37 @@ func (t *Terminal) addClickHeaderWord(env []string) []string {
return env 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 // Loop is called to start Terminal I/O
func (t *Terminal) Loop() error { func (t *Terminal) Loop() error {
// prof := profile.Start(profile.ProfilePath("/tmp/")) // prof := profile.Start(profile.ProfilePath("/tmp/"))
@@ -5400,6 +5458,7 @@ func (t *Terminal) Loop() error {
return nil return nil
} }
} }
triggering := map[tui.Event]struct{}{}
previousInput := t.input previousInput := t.input
previousCx := t.cx previousCx := t.cx
previousVersion := t.version previousVersion := t.version
@@ -5667,7 +5726,7 @@ func (t *Terminal) Loop() error {
capture(true, func(expr string) { capture(true, func(expr string) {
// Split nth expression // Split nth expression
tokens := strings.Split(expr, "|") tokens := strings.Split(expr, "|")
if nth, err := splitNth(tokens[0]); err == nil { if nth, err := splitNth(tokens[0]); err == nil || len(expr) == 0 {
// Changed // Changed
newNth = &nth newNth = &nth
} else { } else {
@@ -6191,6 +6250,20 @@ func (t *Terminal) Loop() error {
case actDisableSearch: case actDisableSearch:
t.paused = true t.paused = true
req(reqPrompt) 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: case actSigStop:
p, err := os.FindProcess(os.Getpid()) p, err := os.FindProcess(os.Getpid())
if err == nil { if err == nil {
@@ -6380,6 +6453,18 @@ func (t *Terminal) Loop() error {
return doActions(actionsFor(tui.ClickHeader)) 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 // Ignored
if !t.window.Enclose(my, mx) && !barDragging { if !t.window.Enclose(my, mx) && !barDragging {
break break
@@ -6500,13 +6585,13 @@ func (t *Terminal) Loop() error {
t.reading = true t.reading = true
} }
case actUnbind: case actUnbind:
if keys, err := parseKeyChords(a.a, "PANIC"); err == nil { if keys, _, err := parseKeyChords(a.a, "PANIC"); err == nil {
for key := range keys { for key := range keys {
delete(t.keymap, key) delete(t.keymap, key)
} }
} }
case actRebind: case actRebind:
if keys, err := parseKeyChords(a.a, "PANIC"); err == nil { if keys, _, err := parseKeyChords(a.a, "PANIC"); err == nil {
for key := range keys { for key := range keys {
if originalAction, found := t.keymapOrg[key]; found { if originalAction, found := t.keymapOrg[key]; found {
t.keymap[key] = originalAction t.keymap[key] = originalAction
@@ -6514,7 +6599,7 @@ func (t *Terminal) Loop() error {
} }
} }
case actToggleBind: case actToggleBind:
if keys, err := parseKeyChords(a.a, "PANIC"); err == nil { if keys, _, err := parseKeyChords(a.a, "PANIC"); err == nil {
for key := range keys { for key := range keys {
if _, bound := t.keymap[key]; bound { if _, bound := t.keymap[key]; bound {
delete(t.keymap, key) delete(t.keymap, key)

View File

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

View File

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

View File

@@ -1415,6 +1415,11 @@ class TestCore < TestInteractive
tmux.until { assert_match(%r{ --1/10000/10000-- *$}, it[-1]) } tmux.until { assert_match(%r{ --1/10000/10000-- *$}, it[-1]) }
end 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 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.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]) } tmux.until { assert_match(/^ 1 xx/, it[-2]) }
@@ -2035,4 +2040,29 @@ class TestCore < TestInteractive
assert_equal 19, it.select_count assert_equal 19, it.select_count
end end
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 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 "$(seq 101 102)" --header-border sharp --header-first], %[--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],
%[--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 "$(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 --input-border sharp],
%[--header "$(seq 101 102)" --header-border sharp --header-lines 2 --header-lines-border sharp --header-first --no-input], %[--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] %[--header "$(seq 101 102)" --style full:sharp --header-first]
] ]
output = <<~BLOCK output = <<~BLOCK
201 201 201 201 201 201 201 201 201 201 201 FOOT FOOT 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 201 201 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 202 202 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 3 > 3 > 3 3 3 > 3 > 3 > 3 > 3 > 3 > 3
2 2 2 2 2 2 2 3 2 2 2 2 2 2 2 2 3
> 1 > 1 > 1 1 1 > 1 > 1 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 2 3/3 101 3/3 101 1/1 3/3 1 1 1 1 1 > 1 2
> 102 > 102 > 101 > 101 101 > 1 > 102 > 102 > 101 > 101 101 101 101 > 1
3/3 101 1/1 101 102 102 102 3/3 101 1/1 101 102 102 102 102 102
> 102 > 102 HEAD 101 HEAD 101 1/1 101 > 102 > 102 HEAD 101 HEAD HEAD HEAD 101 1/1 101
3/3 102 1/1 102 > 102 3/3 > 3/3 102 1/1 1/1 1/1 102 > 102 3/3 >
> HEAD > HEAD HEAD > > HEAD > > > HEAD HEAD >
1/1 1/1
> 101 101 > 101 101
102 102 102 102
HEAD HEAD HEAD HEAD
BLOCK BLOCK
expects = [] expects = []

View File

@@ -190,17 +190,17 @@ class TestPreview < TestInteractive
end end
def test_preview_asterisk 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_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.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.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.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.send_keys '5'
tmux.until { |lines| assert_includes lines[1], ' [ / 1 2 / ] ' } tmux.until { |lines| assert_includes lines[1], ' [/1 2//] ' }
end end
def test_preview_file def test_preview_file