mirror of
https://github.com/junegunn/fzf.git
synced 2026-03-01 21:07:08 +08:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4866c34361 | ||
|
|
3cfee281b4 | ||
|
|
5887edc6ba | ||
|
|
3e751c4e87 | ||
|
|
8452c78cc8 |
@@ -1,6 +1,14 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.69.0
|
||||||
|
------
|
||||||
|
- Added `change-with-nth` action for dynamically changing the `--with-nth` option
|
||||||
|
```sh
|
||||||
|
echo -e "a b c\nd e f\ng h i" | fzf --with-nth .. \
|
||||||
|
--bind 'space:change-with-nth(1|2|3|1,3|2,3|)'
|
||||||
|
```
|
||||||
|
|
||||||
0.68.0
|
0.68.0
|
||||||
------
|
------
|
||||||
- Implemented word wrapping in the list section
|
- Implemented word wrapping in the list section
|
||||||
|
|||||||
@@ -134,6 +134,14 @@ e.g.
|
|||||||
# Use template to rearrange fields
|
# Use template to rearrange fields
|
||||||
echo foo,bar,baz | fzf --delimiter , --with-nth '{n},{1},{3},{2},{1..2}'
|
echo foo,bar,baz | fzf --delimiter , --with-nth '{n},{1},{3},{2},{1..2}'
|
||||||
.RE
|
.RE
|
||||||
|
.RS
|
||||||
|
|
||||||
|
\fBchange\-with\-nth\fR action is only available when \fB\-\-with\-nth\fR is set.
|
||||||
|
When \fB\-\-with\-nth\fR is used, fzf retains the original input lines in memory
|
||||||
|
so they can be re\-transformed on the fly (e.g. \fB\-\-with\-nth ..\fR to keep
|
||||||
|
the original presentation). This increases memory usage, so only use
|
||||||
|
\fB\-\-with\-nth\fR when you actually need field transformation.
|
||||||
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-accept\-nth=" "N[,..] or TEMPLATE"
|
.BI "\-\-accept\-nth=" "N[,..] or TEMPLATE"
|
||||||
Define which fields to print on accept. The last delimiter is stripped from the
|
Define which fields to print on accept. The last delimiter is stripped from the
|
||||||
@@ -1398,6 +1406,8 @@ fzf exports the following environment variables to its child processes.
|
|||||||
.br
|
.br
|
||||||
.BR FZF_NTH " Current \-\-nth option"
|
.BR FZF_NTH " Current \-\-nth option"
|
||||||
.br
|
.br
|
||||||
|
.BR FZF_WITH_NTH " Current \-\-with\-nth option"
|
||||||
|
.br
|
||||||
.BR FZF_PROMPT " Prompt string"
|
.BR FZF_PROMPT " Prompt string"
|
||||||
.br
|
.br
|
||||||
.BR FZF_GHOST " Ghost string"
|
.BR FZF_GHOST " Ghost string"
|
||||||
@@ -1888,6 +1898,7 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBchange\-multi\fR (enable multi-select mode with no limit)
|
\fBchange\-multi\fR (enable multi-select mode with no limit)
|
||||||
\fBchange\-multi(...)\fR (enable multi-select mode with a limit or disable it with 0)
|
\fBchange\-multi(...)\fR (enable multi-select mode with a limit or disable it with 0)
|
||||||
\fBchange\-nth(...)\fR (change \fB\-\-nth\fR option; rotate through the multiple options separated by '|')
|
\fBchange\-nth(...)\fR (change \fB\-\-nth\fR option; rotate through the multiple options separated by '|')
|
||||||
|
\fBchange\-with\-nth(...)\fR (change \fB\-\-with\-nth\fR option; rotate through the multiple options separated by '|')
|
||||||
\fBchange\-pointer(...)\fR (change \fB\-\-pointer\fR option)
|
\fBchange\-pointer(...)\fR (change \fB\-\-pointer\fR option)
|
||||||
\fBchange\-preview(...)\fR (change \fB\-\-preview\fR option)
|
\fBchange\-preview(...)\fR (change \fB\-\-preview\fR option)
|
||||||
\fBchange\-preview\-label(...)\fR (change \fB\-\-preview\-label\fR to the given string)
|
\fBchange\-preview\-label(...)\fR (change \fB\-\-preview\-label\fR to the given string)
|
||||||
@@ -1993,6 +2004,7 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBtransform\-input\-label(...)\fR (transform input label using an external command)
|
\fBtransform\-input\-label(...)\fR (transform input label using an external command)
|
||||||
\fBtransform\-list\-label(...)\fR (transform list label using an external command)
|
\fBtransform\-list\-label(...)\fR (transform list label using an external command)
|
||||||
\fBtransform\-nth(...)\fR (transform nth using an external command)
|
\fBtransform\-nth(...)\fR (transform nth using an external command)
|
||||||
|
\fBtransform\-with\-nth(...)\fR (transform with-nth using an external command)
|
||||||
\fBtransform\-pointer(...)\fR (transform pointer using an external command)
|
\fBtransform\-pointer(...)\fR (transform pointer using an external command)
|
||||||
\fBtransform\-preview\-label(...)\fR (transform preview label using an external command)
|
\fBtransform\-preview\-label(...)\fR (transform preview label using an external command)
|
||||||
\fBtransform\-prompt(...)\fR (transform prompt string using an external command)
|
\fBtransform\-prompt(...)\fR (transform prompt string using an external command)
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ if [[ $- =~ i ]]; then
|
|||||||
# the changes. See code comments in "common.sh" for the implementation details.
|
# the changes. See code comments in "common.sh" for the implementation details.
|
||||||
|
|
||||||
__fzf_defaults() {
|
__fzf_defaults() {
|
||||||
printf '%s\n' "--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1"
|
builtin printf '%s\n' "--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1"
|
||||||
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
||||||
printf '%s\n' "${FZF_DEFAULT_OPTS-} $2"
|
builtin printf '%s\n' "${FZF_DEFAULT_OPTS-} $2"
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzf_exec_awk() {
|
__fzf_exec_awk() {
|
||||||
@@ -81,7 +81,7 @@ __fzf_orig_completion() {
|
|||||||
f="${BASH_REMATCH[2]}"
|
f="${BASH_REMATCH[2]}"
|
||||||
cmd="${BASH_REMATCH[3]}"
|
cmd="${BASH_REMATCH[3]}"
|
||||||
[[ $f == _fzf_* ]] && continue
|
[[ $f == _fzf_* ]] && continue
|
||||||
printf -v "_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}" "%s" "${comp} %s ${cmd} #${f}"
|
builtin printf -v "_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}" "%s" "${comp} %s ${cmd} #${f}"
|
||||||
if [[ $l == *" -o nospace "* ]] && [[ ${__fzf_nospace_commands-} != *" $cmd "* ]]; then
|
if [[ $l == *" -o nospace "* ]] && [[ ${__fzf_nospace_commands-} != *" $cmd "* ]]; then
|
||||||
__fzf_nospace_commands="${__fzf_nospace_commands-} $cmd "
|
__fzf_nospace_commands="${__fzf_nospace_commands-} $cmd "
|
||||||
fi
|
fi
|
||||||
@@ -111,7 +111,7 @@ __fzf_orig_completion_instantiate() {
|
|||||||
orig="${!orig_var-}"
|
orig="${!orig_var-}"
|
||||||
orig="${orig%#*}"
|
orig="${orig%#*}"
|
||||||
[[ $orig == *' %s '* ]] || return 1
|
[[ $orig == *' %s '* ]] || return 1
|
||||||
printf -v REPLY "$orig" "$func"
|
builtin printf -v REPLY "$orig" "$func"
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_opts_completion() {
|
_fzf_opts_completion() {
|
||||||
@@ -376,7 +376,7 @@ __fzf_generic_path_completion() {
|
|||||||
eval "rest=(${FZF_COMPLETION_PATH_OPTS-})"
|
eval "rest=(${FZF_COMPLETION_PATH_OPTS-})"
|
||||||
fi
|
fi
|
||||||
if declare -F "$1" > /dev/null; then
|
if declare -F "$1" > /dev/null; then
|
||||||
eval "$1 $(printf %q "$dir")" | __fzf_comprun "$4" -q "$leftover" "${rest[@]}"
|
eval "$1 $(builtin printf %q "$dir")" | __fzf_comprun "$4" -q "$leftover" "${rest[@]}"
|
||||||
else
|
else
|
||||||
if [[ $1 =~ dir ]]; then
|
if [[ $1 =~ dir ]]; then
|
||||||
walker=dir,follow
|
walker=dir,follow
|
||||||
@@ -385,7 +385,7 @@ __fzf_generic_path_completion() {
|
|||||||
fi
|
fi
|
||||||
__fzf_comprun "$4" -q "$leftover" --walker "$walker" --walker-root="$dir" "${rest[@]}"
|
__fzf_comprun "$4" -q "$leftover" --walker "$walker" --walker-root="$dir" "${rest[@]}"
|
||||||
fi | while read -r item; do
|
fi | while read -r item; do
|
||||||
printf "%q " "${item%$3}$3"
|
builtin printf "%q " "${item%$3}$3"
|
||||||
done
|
done
|
||||||
)
|
)
|
||||||
matches=${matches% }
|
matches=${matches% }
|
||||||
@@ -395,9 +395,9 @@ __fzf_generic_path_completion() {
|
|||||||
else
|
else
|
||||||
COMPREPLY=("$cur")
|
COMPREPLY=("$cur")
|
||||||
fi
|
fi
|
||||||
# To redraw line after fzf closes (printf '\e[5n')
|
# To redraw line after fzf closes (builtin printf '\e[5n')
|
||||||
bind '"\e[0n": redraw-current-line' 2> /dev/null
|
bind '"\e[0n": redraw-current-line' 2> /dev/null
|
||||||
printf '\e[5n'
|
builtin printf '\e[5n'
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
dir=$(command dirname "$dir")
|
dir=$(command dirname "$dir")
|
||||||
@@ -455,7 +455,7 @@ _fzf_complete() {
|
|||||||
COMPREPLY=("$cur")
|
COMPREPLY=("$cur")
|
||||||
fi
|
fi
|
||||||
bind '"\e[0n": redraw-current-line' 2> /dev/null
|
bind '"\e[0n": redraw-current-line' 2> /dev/null
|
||||||
printf '\e[5n'
|
builtin printf '\e[5n'
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
_fzf_handle_dynamic_completion "$cmd" "${rest[@]}"
|
_fzf_handle_dynamic_completion "$cmd" "${rest[@]}"
|
||||||
@@ -527,7 +527,7 @@ _fzf_proc_completion_post() {
|
|||||||
# # Set the local attribute for any non-local variable that is set by _known_hosts_real()
|
# # Set the local attribute for any non-local variable that is set by _known_hosts_real()
|
||||||
# local COMPREPLY=()
|
# local COMPREPLY=()
|
||||||
# _known_hosts_real ''
|
# _known_hosts_real ''
|
||||||
# printf '%s\n' "${COMPREPLY[@]}" | command sort -u --version-sort
|
# builtin printf '%s\n' "${COMPREPLY[@]}" | command sort -u --version-sort
|
||||||
# }
|
# }
|
||||||
if ! declare -F __fzf_list_hosts > /dev/null; then
|
if ! declare -F __fzf_list_hosts > /dev/null; then
|
||||||
__fzf_list_hosts() {
|
__fzf_list_hosts() {
|
||||||
|
|||||||
@@ -38,156 +38,159 @@ func _() {
|
|||||||
_ = x[actChangeListLabel-27]
|
_ = x[actChangeListLabel-27]
|
||||||
_ = x[actChangeMulti-28]
|
_ = x[actChangeMulti-28]
|
||||||
_ = x[actChangeNth-29]
|
_ = x[actChangeNth-29]
|
||||||
_ = x[actChangePointer-30]
|
_ = x[actChangeWithNth-30]
|
||||||
_ = x[actChangePreview-31]
|
_ = x[actChangePointer-31]
|
||||||
_ = x[actChangePreviewLabel-32]
|
_ = x[actChangePreview-32]
|
||||||
_ = x[actChangePreviewWindow-33]
|
_ = x[actChangePreviewLabel-33]
|
||||||
_ = x[actChangePrompt-34]
|
_ = x[actChangePreviewWindow-34]
|
||||||
_ = x[actChangeQuery-35]
|
_ = x[actChangePrompt-35]
|
||||||
_ = x[actClearScreen-36]
|
_ = x[actChangeQuery-36]
|
||||||
_ = x[actClearQuery-37]
|
_ = x[actClearScreen-37]
|
||||||
_ = x[actClearSelection-38]
|
_ = x[actClearQuery-38]
|
||||||
_ = x[actClose-39]
|
_ = x[actClearSelection-39]
|
||||||
_ = x[actDeleteChar-40]
|
_ = x[actClose-40]
|
||||||
_ = x[actDeleteCharEof-41]
|
_ = x[actDeleteChar-41]
|
||||||
_ = x[actEndOfLine-42]
|
_ = x[actDeleteCharEof-42]
|
||||||
_ = x[actFatal-43]
|
_ = x[actEndOfLine-43]
|
||||||
_ = x[actForwardChar-44]
|
_ = x[actFatal-44]
|
||||||
_ = x[actForwardWord-45]
|
_ = x[actForwardChar-45]
|
||||||
_ = x[actForwardSubWord-46]
|
_ = x[actForwardWord-46]
|
||||||
_ = x[actKillLine-47]
|
_ = x[actForwardSubWord-47]
|
||||||
_ = x[actKillWord-48]
|
_ = x[actKillLine-48]
|
||||||
_ = x[actKillSubWord-49]
|
_ = x[actKillWord-49]
|
||||||
_ = x[actUnixLineDiscard-50]
|
_ = x[actKillSubWord-50]
|
||||||
_ = x[actUnixWordRubout-51]
|
_ = x[actUnixLineDiscard-51]
|
||||||
_ = x[actYank-52]
|
_ = x[actUnixWordRubout-52]
|
||||||
_ = x[actBackwardKillWord-53]
|
_ = x[actYank-53]
|
||||||
_ = x[actBackwardKillSubWord-54]
|
_ = x[actBackwardKillWord-54]
|
||||||
_ = x[actSelectAll-55]
|
_ = x[actBackwardKillSubWord-55]
|
||||||
_ = x[actDeselectAll-56]
|
_ = x[actSelectAll-56]
|
||||||
_ = x[actToggle-57]
|
_ = x[actDeselectAll-57]
|
||||||
_ = x[actToggleSearch-58]
|
_ = x[actToggle-58]
|
||||||
_ = x[actToggleAll-59]
|
_ = x[actToggleSearch-59]
|
||||||
_ = x[actToggleDown-60]
|
_ = x[actToggleAll-60]
|
||||||
_ = x[actToggleUp-61]
|
_ = x[actToggleDown-61]
|
||||||
_ = x[actToggleIn-62]
|
_ = x[actToggleUp-62]
|
||||||
_ = x[actToggleOut-63]
|
_ = x[actToggleIn-63]
|
||||||
_ = x[actToggleTrack-64]
|
_ = x[actToggleOut-64]
|
||||||
_ = x[actToggleTrackCurrent-65]
|
_ = x[actToggleTrack-65]
|
||||||
_ = x[actToggleHeader-66]
|
_ = x[actToggleTrackCurrent-66]
|
||||||
_ = x[actToggleWrap-67]
|
_ = x[actToggleHeader-67]
|
||||||
_ = x[actToggleWrapWord-68]
|
_ = x[actToggleWrap-68]
|
||||||
_ = x[actToggleMultiLine-69]
|
_ = x[actToggleWrapWord-69]
|
||||||
_ = x[actToggleHscroll-70]
|
_ = x[actToggleMultiLine-70]
|
||||||
_ = x[actToggleRaw-71]
|
_ = x[actToggleHscroll-71]
|
||||||
_ = x[actEnableRaw-72]
|
_ = x[actToggleRaw-72]
|
||||||
_ = x[actDisableRaw-73]
|
_ = x[actEnableRaw-73]
|
||||||
_ = x[actTrackCurrent-74]
|
_ = x[actDisableRaw-74]
|
||||||
_ = x[actToggleInput-75]
|
_ = x[actTrackCurrent-75]
|
||||||
_ = x[actHideInput-76]
|
_ = x[actToggleInput-76]
|
||||||
_ = x[actShowInput-77]
|
_ = x[actHideInput-77]
|
||||||
_ = x[actUntrackCurrent-78]
|
_ = x[actShowInput-78]
|
||||||
_ = x[actDown-79]
|
_ = x[actUntrackCurrent-79]
|
||||||
_ = x[actDownMatch-80]
|
_ = x[actDown-80]
|
||||||
_ = x[actUp-81]
|
_ = x[actDownMatch-81]
|
||||||
_ = x[actUpMatch-82]
|
_ = x[actUp-82]
|
||||||
_ = x[actPageUp-83]
|
_ = x[actUpMatch-83]
|
||||||
_ = x[actPageDown-84]
|
_ = x[actPageUp-84]
|
||||||
_ = x[actPosition-85]
|
_ = x[actPageDown-85]
|
||||||
_ = x[actHalfPageUp-86]
|
_ = x[actPosition-86]
|
||||||
_ = x[actHalfPageDown-87]
|
_ = x[actHalfPageUp-87]
|
||||||
_ = x[actOffsetUp-88]
|
_ = x[actHalfPageDown-88]
|
||||||
_ = x[actOffsetDown-89]
|
_ = x[actOffsetUp-89]
|
||||||
_ = x[actOffsetMiddle-90]
|
_ = x[actOffsetDown-90]
|
||||||
_ = x[actJump-91]
|
_ = x[actOffsetMiddle-91]
|
||||||
_ = x[actJumpAccept-92]
|
_ = x[actJump-92]
|
||||||
_ = x[actPrintQuery-93]
|
_ = x[actJumpAccept-93]
|
||||||
_ = x[actRefreshPreview-94]
|
_ = x[actPrintQuery-94]
|
||||||
_ = x[actReplaceQuery-95]
|
_ = x[actRefreshPreview-95]
|
||||||
_ = x[actToggleSort-96]
|
_ = x[actReplaceQuery-96]
|
||||||
_ = x[actShowPreview-97]
|
_ = x[actToggleSort-97]
|
||||||
_ = x[actHidePreview-98]
|
_ = x[actShowPreview-98]
|
||||||
_ = x[actTogglePreview-99]
|
_ = x[actHidePreview-99]
|
||||||
_ = x[actTogglePreviewWrap-100]
|
_ = x[actTogglePreview-100]
|
||||||
_ = x[actTogglePreviewWrapWord-101]
|
_ = x[actTogglePreviewWrap-101]
|
||||||
_ = x[actTransform-102]
|
_ = x[actTogglePreviewWrapWord-102]
|
||||||
_ = x[actTransformBorderLabel-103]
|
_ = x[actTransform-103]
|
||||||
_ = x[actTransformGhost-104]
|
_ = x[actTransformBorderLabel-104]
|
||||||
_ = x[actTransformHeader-105]
|
_ = x[actTransformGhost-105]
|
||||||
_ = x[actTransformHeaderLines-106]
|
_ = x[actTransformHeader-106]
|
||||||
_ = x[actTransformFooter-107]
|
_ = x[actTransformHeaderLines-107]
|
||||||
_ = x[actTransformHeaderLabel-108]
|
_ = x[actTransformFooter-108]
|
||||||
_ = x[actTransformFooterLabel-109]
|
_ = x[actTransformHeaderLabel-109]
|
||||||
_ = x[actTransformInputLabel-110]
|
_ = x[actTransformFooterLabel-110]
|
||||||
_ = x[actTransformListLabel-111]
|
_ = x[actTransformInputLabel-111]
|
||||||
_ = x[actTransformNth-112]
|
_ = x[actTransformListLabel-112]
|
||||||
_ = x[actTransformPointer-113]
|
_ = x[actTransformNth-113]
|
||||||
_ = x[actTransformPreviewLabel-114]
|
_ = x[actTransformWithNth-114]
|
||||||
_ = x[actTransformPrompt-115]
|
_ = x[actTransformPointer-115]
|
||||||
_ = x[actTransformQuery-116]
|
_ = x[actTransformPreviewLabel-116]
|
||||||
_ = x[actTransformSearch-117]
|
_ = x[actTransformPrompt-117]
|
||||||
_ = x[actTrigger-118]
|
_ = x[actTransformQuery-118]
|
||||||
_ = x[actBgTransform-119]
|
_ = x[actTransformSearch-119]
|
||||||
_ = x[actBgTransformBorderLabel-120]
|
_ = x[actTrigger-120]
|
||||||
_ = x[actBgTransformGhost-121]
|
_ = x[actBgTransform-121]
|
||||||
_ = x[actBgTransformHeader-122]
|
_ = x[actBgTransformBorderLabel-122]
|
||||||
_ = x[actBgTransformHeaderLines-123]
|
_ = x[actBgTransformGhost-123]
|
||||||
_ = x[actBgTransformFooter-124]
|
_ = x[actBgTransformHeader-124]
|
||||||
_ = x[actBgTransformHeaderLabel-125]
|
_ = x[actBgTransformHeaderLines-125]
|
||||||
_ = x[actBgTransformFooterLabel-126]
|
_ = x[actBgTransformFooter-126]
|
||||||
_ = x[actBgTransformInputLabel-127]
|
_ = x[actBgTransformHeaderLabel-127]
|
||||||
_ = x[actBgTransformListLabel-128]
|
_ = x[actBgTransformFooterLabel-128]
|
||||||
_ = x[actBgTransformNth-129]
|
_ = x[actBgTransformInputLabel-129]
|
||||||
_ = x[actBgTransformPointer-130]
|
_ = x[actBgTransformListLabel-130]
|
||||||
_ = x[actBgTransformPreviewLabel-131]
|
_ = x[actBgTransformNth-131]
|
||||||
_ = x[actBgTransformPrompt-132]
|
_ = x[actBgTransformWithNth-132]
|
||||||
_ = x[actBgTransformQuery-133]
|
_ = x[actBgTransformPointer-133]
|
||||||
_ = x[actBgTransformSearch-134]
|
_ = x[actBgTransformPreviewLabel-134]
|
||||||
_ = x[actBgCancel-135]
|
_ = x[actBgTransformPrompt-135]
|
||||||
_ = x[actSearch-136]
|
_ = x[actBgTransformQuery-136]
|
||||||
_ = x[actPreview-137]
|
_ = x[actBgTransformSearch-137]
|
||||||
_ = x[actPreviewTop-138]
|
_ = x[actBgCancel-138]
|
||||||
_ = x[actPreviewBottom-139]
|
_ = x[actSearch-139]
|
||||||
_ = x[actPreviewUp-140]
|
_ = x[actPreview-140]
|
||||||
_ = x[actPreviewDown-141]
|
_ = x[actPreviewTop-141]
|
||||||
_ = x[actPreviewPageUp-142]
|
_ = x[actPreviewBottom-142]
|
||||||
_ = x[actPreviewPageDown-143]
|
_ = x[actPreviewUp-143]
|
||||||
_ = x[actPreviewHalfPageUp-144]
|
_ = x[actPreviewDown-144]
|
||||||
_ = x[actPreviewHalfPageDown-145]
|
_ = x[actPreviewPageUp-145]
|
||||||
_ = x[actPrevHistory-146]
|
_ = x[actPreviewPageDown-146]
|
||||||
_ = x[actPrevSelected-147]
|
_ = x[actPreviewHalfPageUp-147]
|
||||||
_ = x[actPrint-148]
|
_ = x[actPreviewHalfPageDown-148]
|
||||||
_ = x[actPut-149]
|
_ = x[actPrevHistory-149]
|
||||||
_ = x[actNextHistory-150]
|
_ = x[actPrevSelected-150]
|
||||||
_ = x[actNextSelected-151]
|
_ = x[actPrint-151]
|
||||||
_ = x[actExecute-152]
|
_ = x[actPut-152]
|
||||||
_ = x[actExecuteSilent-153]
|
_ = x[actNextHistory-153]
|
||||||
_ = x[actExecuteMulti-154]
|
_ = x[actNextSelected-154]
|
||||||
_ = x[actSigStop-155]
|
_ = x[actExecute-155]
|
||||||
_ = x[actBest-156]
|
_ = x[actExecuteSilent-156]
|
||||||
_ = x[actFirst-157]
|
_ = x[actExecuteMulti-157]
|
||||||
_ = x[actLast-158]
|
_ = x[actSigStop-158]
|
||||||
_ = x[actReload-159]
|
_ = x[actBest-159]
|
||||||
_ = x[actReloadSync-160]
|
_ = x[actFirst-160]
|
||||||
_ = x[actDisableSearch-161]
|
_ = x[actLast-161]
|
||||||
_ = x[actEnableSearch-162]
|
_ = x[actReload-162]
|
||||||
_ = x[actSelect-163]
|
_ = x[actReloadSync-163]
|
||||||
_ = x[actDeselect-164]
|
_ = x[actDisableSearch-164]
|
||||||
_ = x[actUnbind-165]
|
_ = x[actEnableSearch-165]
|
||||||
_ = x[actRebind-166]
|
_ = x[actSelect-166]
|
||||||
_ = x[actToggleBind-167]
|
_ = x[actDeselect-167]
|
||||||
_ = x[actBecome-168]
|
_ = x[actUnbind-168]
|
||||||
_ = x[actShowHeader-169]
|
_ = x[actRebind-169]
|
||||||
_ = x[actHideHeader-170]
|
_ = x[actToggleBind-170]
|
||||||
_ = x[actBell-171]
|
_ = x[actBecome-171]
|
||||||
_ = x[actExclude-172]
|
_ = x[actShowHeader-172]
|
||||||
_ = x[actExcludeMulti-173]
|
_ = x[actHideHeader-173]
|
||||||
_ = x[actAsync-174]
|
_ = x[actBell-174]
|
||||||
|
_ = x[actExclude-175]
|
||||||
|
_ = x[actExcludeMulti-176]
|
||||||
|
_ = x[actAsync-177]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _actionType_name = "actIgnoreactStartactClickactInvalidactBracketedPasteBeginactBracketedPasteEndactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactBackwardSubWordactCancelactChangeBorderLabelactChangeGhostactChangeHeaderactChangeHeaderLinesactChangeFooteractChangeHeaderLabelactChangeFooterLabelactChangeInputLabelactChangeListLabelactChangeMultiactChangeNthactChangePointeractChangePreviewactChangePreviewLabelactChangePreviewWindowactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactForwardSubWordactKillLineactKillWordactKillSubWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactBackwardKillSubWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleWrapWordactToggleMultiLineactToggleHscrollactToggleRawactEnableRawactDisableRawactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactDownMatchactUpactUpMatchactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTogglePreviewWrapWordactTransformactTransformBorderLabelactTransformGhostactTransformHeaderactTransformHeaderLinesactTransformFooteractTransformHeaderLabelactTransformFooterLabelactTransformInputLabelactTransformListLabelactTransformNthactTransformPointeractTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactTriggeractBgTransformactBgTransformBorderLabelactBgTransformGhostactBgTransformHeaderactBgTransformHeaderLinesactBgTransformFooteractBgTransformHeaderLabelactBgTransformFooterLabelactBgTransformInputLabelactBgTransformListLabelactBgTransformNthactBgTransformPointeractBgTransformPreviewLabelactBgTransformPromptactBgTransformQueryactBgTransformSearchactBgCancelactSearchactPreviewactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactBestactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBellactExcludeactExcludeMultiactAsync"
|
const _actionType_name = "actIgnoreactStartactClickactInvalidactBracketedPasteBeginactBracketedPasteEndactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactBackwardSubWordactCancelactChangeBorderLabelactChangeGhostactChangeHeaderactChangeHeaderLinesactChangeFooteractChangeHeaderLabelactChangeFooterLabelactChangeInputLabelactChangeListLabelactChangeMultiactChangeNthactChangeWithNthactChangePointeractChangePreviewactChangePreviewLabelactChangePreviewWindowactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactForwardSubWordactKillLineactKillWordactKillSubWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactBackwardKillSubWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleWrapWordactToggleMultiLineactToggleHscrollactToggleRawactEnableRawactDisableRawactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactDownMatchactUpactUpMatchactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTogglePreviewWrapWordactTransformactTransformBorderLabelactTransformGhostactTransformHeaderactTransformHeaderLinesactTransformFooteractTransformHeaderLabelactTransformFooterLabelactTransformInputLabelactTransformListLabelactTransformNthactTransformWithNthactTransformPointeractTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactTriggeractBgTransformactBgTransformBorderLabelactBgTransformGhostactBgTransformHeaderactBgTransformHeaderLinesactBgTransformFooteractBgTransformHeaderLabelactBgTransformFooterLabelactBgTransformInputLabelactBgTransformListLabelactBgTransformNthactBgTransformWithNthactBgTransformPointeractBgTransformPreviewLabelactBgTransformPromptactBgTransformQueryactBgTransformSearchactBgCancelactSearchactPreviewactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactBestactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBellactExcludeactExcludeMultiactAsync"
|
||||||
|
|
||||||
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 57, 77, 84, 92, 110, 118, 127, 144, 165, 180, 201, 225, 240, 258, 267, 287, 301, 316, 336, 351, 371, 391, 410, 428, 442, 454, 470, 486, 507, 529, 544, 558, 572, 585, 602, 610, 623, 639, 651, 659, 673, 687, 704, 715, 726, 740, 758, 775, 782, 801, 823, 835, 849, 858, 873, 885, 898, 909, 920, 932, 946, 967, 982, 995, 1012, 1030, 1046, 1058, 1070, 1083, 1098, 1112, 1124, 1136, 1153, 1160, 1172, 1177, 1187, 1196, 1207, 1218, 1231, 1246, 1257, 1270, 1285, 1292, 1305, 1318, 1335, 1350, 1363, 1377, 1391, 1407, 1427, 1451, 1463, 1486, 1503, 1521, 1544, 1562, 1585, 1608, 1630, 1651, 1666, 1685, 1709, 1727, 1744, 1762, 1772, 1786, 1811, 1830, 1850, 1875, 1895, 1920, 1945, 1969, 1992, 2009, 2030, 2056, 2076, 2095, 2115, 2126, 2135, 2145, 2158, 2174, 2186, 2200, 2216, 2234, 2254, 2276, 2290, 2305, 2313, 2319, 2333, 2348, 2358, 2374, 2389, 2399, 2406, 2414, 2421, 2430, 2443, 2459, 2474, 2483, 2494, 2503, 2512, 2525, 2534, 2547, 2560, 2567, 2577, 2592, 2600}
|
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 57, 77, 84, 92, 110, 118, 127, 144, 165, 180, 201, 225, 240, 258, 267, 287, 301, 316, 336, 351, 371, 391, 410, 428, 442, 454, 470, 486, 502, 523, 545, 560, 574, 588, 601, 618, 626, 639, 655, 667, 675, 689, 703, 720, 731, 742, 756, 774, 791, 798, 817, 839, 851, 865, 874, 889, 901, 914, 925, 936, 948, 962, 983, 998, 1011, 1028, 1046, 1062, 1074, 1086, 1099, 1114, 1128, 1140, 1152, 1169, 1176, 1188, 1193, 1203, 1212, 1223, 1234, 1247, 1262, 1273, 1286, 1301, 1308, 1321, 1334, 1351, 1366, 1379, 1393, 1407, 1423, 1443, 1467, 1479, 1502, 1519, 1537, 1560, 1578, 1601, 1624, 1646, 1667, 1682, 1701, 1720, 1744, 1762, 1779, 1797, 1807, 1821, 1846, 1865, 1885, 1910, 1930, 1955, 1980, 2004, 2027, 2044, 2065, 2086, 2112, 2132, 2151, 2171, 2182, 2191, 2201, 2214, 2230, 2242, 2256, 2272, 2290, 2310, 2332, 2346, 2361, 2369, 2375, 2389, 2404, 2414, 2430, 2445, 2455, 2462, 2470, 2477, 2486, 2499, 2515, 2530, 2539, 2550, 2559, 2568, 2581, 2590, 2603, 2616, 2623, 2633, 2648, 2656}
|
||||||
|
|
||||||
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) {
|
||||||
|
|||||||
@@ -99,6 +99,21 @@ func (cl *ChunkList) Clear() {
|
|||||||
cl.mutex.Unlock()
|
cl.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ForEachItem iterates all items and applies fn to each one.
|
||||||
|
// The done callback runs under the lock to safely update shared state.
|
||||||
|
func (cl *ChunkList) ForEachItem(fn func(*Item), done func()) {
|
||||||
|
cl.mutex.Lock()
|
||||||
|
for _, chunk := range cl.chunks {
|
||||||
|
for i := 0; i < chunk.count; i++ {
|
||||||
|
fn(&chunk.items[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if done != nil {
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
cl.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
// Snapshot returns immutable snapshot of the ChunkList
|
// Snapshot returns immutable snapshot of the ChunkList
|
||||||
func (cl *ChunkList) Snapshot(tail int) ([]*Chunk, int, bool) {
|
func (cl *ChunkList) Snapshot(tail int) ([]*Chunk, int, bool) {
|
||||||
cl.mutex.Lock()
|
cl.mutex.Lock()
|
||||||
|
|||||||
104
src/core.go
104
src/core.go
@@ -113,6 +113,42 @@ func Run(opts *Options) (int, error) {
|
|||||||
cache := NewChunkCache()
|
cache := NewChunkCache()
|
||||||
var chunkList *ChunkList
|
var chunkList *ChunkList
|
||||||
var itemIndex int32
|
var itemIndex int32
|
||||||
|
// transformItem applies with-nth transformation to an item's raw data.
|
||||||
|
// It handles ANSI token propagation using prevLineAnsiState for cross-line continuity.
|
||||||
|
transformItem := func(item *Item, data []byte, transformer func([]Token, int32) string, index int32) {
|
||||||
|
tokens := Tokenize(byteString(data), opts.Delimiter)
|
||||||
|
if opts.Ansi && len(tokens) > 1 {
|
||||||
|
var ansiState *ansiState
|
||||||
|
if prevLineAnsiState != nil {
|
||||||
|
ansiStateDup := *prevLineAnsiState
|
||||||
|
ansiState = &ansiStateDup
|
||||||
|
}
|
||||||
|
for _, token := range tokens {
|
||||||
|
prevAnsiState := ansiState
|
||||||
|
_, _, ansiState = extractColor(token.text.ToString(), ansiState, nil)
|
||||||
|
if prevAnsiState != nil {
|
||||||
|
token.text.Prepend("\x1b[m" + prevAnsiState.ToString())
|
||||||
|
} else {
|
||||||
|
token.text.Prepend("\x1b[m")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transformed := transformer(tokens, index)
|
||||||
|
item.text, item.colors = ansiProcessor(stringBytes(transformed))
|
||||||
|
|
||||||
|
// We should not trim trailing whitespaces with background colors
|
||||||
|
var maxColorOffset int32
|
||||||
|
if item.colors != nil {
|
||||||
|
for _, ansi := range *item.colors {
|
||||||
|
if ansi.color.bg >= 0 {
|
||||||
|
maxColorOffset = max(maxColorOffset, ansi.offset[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item.text.TrimTrailingWhitespaces(int(maxColorOffset))
|
||||||
|
}
|
||||||
|
|
||||||
|
var nthTransformer func([]Token, int32) string
|
||||||
if opts.WithNth == nil {
|
if opts.WithNth == nil {
|
||||||
chunkList = NewChunkList(cache, func(item *Item, data []byte) bool {
|
chunkList = NewChunkList(cache, func(item *Item, data []byte) bool {
|
||||||
item.text, item.colors = ansiProcessor(data)
|
item.text, item.colors = ansiProcessor(data)
|
||||||
@@ -121,38 +157,13 @@ func Run(opts *Options) (int, error) {
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
nthTransformer := opts.WithNth(opts.Delimiter)
|
nthTransformer = opts.WithNth(opts.Delimiter)
|
||||||
chunkList = NewChunkList(cache, func(item *Item, data []byte) bool {
|
chunkList = NewChunkList(cache, func(item *Item, data []byte) bool {
|
||||||
tokens := Tokenize(byteString(data), opts.Delimiter)
|
if nthTransformer == nil {
|
||||||
if opts.Ansi && len(tokens) > 1 {
|
item.text, item.colors = ansiProcessor(data)
|
||||||
var ansiState *ansiState
|
} else {
|
||||||
if prevLineAnsiState != nil {
|
transformItem(item, data, nthTransformer, itemIndex)
|
||||||
ansiStateDup := *prevLineAnsiState
|
|
||||||
ansiState = &ansiStateDup
|
|
||||||
}
|
|
||||||
for _, token := range tokens {
|
|
||||||
prevAnsiState := ansiState
|
|
||||||
_, _, ansiState = extractColor(token.text.ToString(), ansiState, nil)
|
|
||||||
if prevAnsiState != nil {
|
|
||||||
token.text.Prepend("\x1b[m" + prevAnsiState.ToString())
|
|
||||||
} else {
|
|
||||||
token.text.Prepend("\x1b[m")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
transformed := nthTransformer(tokens, itemIndex)
|
|
||||||
item.text, item.colors = ansiProcessor(stringBytes(transformed))
|
|
||||||
|
|
||||||
// We should not trim trailing whitespaces with background colors
|
|
||||||
var maxColorOffset int32
|
|
||||||
if item.colors != nil {
|
|
||||||
for _, ansi := range *item.colors {
|
|
||||||
if ansi.color.bg >= 0 {
|
|
||||||
maxColorOffset = max(maxColorOffset, ansi.offset[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
item.text.TrimTrailingWhitespaces(int(maxColorOffset))
|
|
||||||
item.text.Index = itemIndex
|
item.text.Index = itemIndex
|
||||||
item.origText = &data
|
item.origText = &data
|
||||||
itemIndex++
|
itemIndex++
|
||||||
@@ -260,7 +271,7 @@ func Run(opts *Options) (int, error) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
if result, _, _ := pattern.MatchItem(&item, false, slab); result != nil {
|
if result, _, _ := pattern.MatchItem(&item, false, slab); result.item != nil {
|
||||||
opts.Printer(transformer(&item))
|
opts.Printer(transformer(&item))
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
@@ -461,6 +472,7 @@ func Run(opts *Options) (int, error) {
|
|||||||
var environ []string
|
var environ []string
|
||||||
var changed bool
|
var changed bool
|
||||||
headerLinesChanged := false
|
headerLinesChanged := false
|
||||||
|
withNthChanged := false
|
||||||
switch val := value.(type) {
|
switch val := value.(type) {
|
||||||
case searchRequest:
|
case searchRequest:
|
||||||
sort = val.sort
|
sort = val.sort
|
||||||
@@ -487,6 +499,34 @@ func Run(opts *Options) (int, error) {
|
|||||||
headerLinesChanged = true
|
headerLinesChanged = true
|
||||||
bump = true
|
bump = true
|
||||||
}
|
}
|
||||||
|
if val.withNth != nil {
|
||||||
|
newTransformer := val.withNth.fn
|
||||||
|
// Cancel any in-flight scan and block the terminal from reading
|
||||||
|
// items before mutating them in-place. Snapshot shares middle
|
||||||
|
// chunk pointers, so the matcher and terminal can race with us.
|
||||||
|
matcher.CancelScan()
|
||||||
|
terminal.PauseRendering()
|
||||||
|
// Reset cross-line ANSI state before re-processing all items
|
||||||
|
lineAnsiState = nil
|
||||||
|
prevLineAnsiState = nil
|
||||||
|
chunkList.ForEachItem(func(item *Item) {
|
||||||
|
origBytes := *item.origText
|
||||||
|
savedIndex := item.Index()
|
||||||
|
if newTransformer != nil {
|
||||||
|
transformItem(item, origBytes, newTransformer, savedIndex)
|
||||||
|
} else {
|
||||||
|
item.text, item.colors = ansiProcessor(origBytes)
|
||||||
|
}
|
||||||
|
item.text.Index = savedIndex
|
||||||
|
item.transformed = nil
|
||||||
|
}, func() {
|
||||||
|
nthTransformer = newTransformer
|
||||||
|
})
|
||||||
|
terminal.ResumeRendering()
|
||||||
|
matcher.ResumeScan()
|
||||||
|
withNthChanged = true
|
||||||
|
bump = true
|
||||||
|
}
|
||||||
if bump {
|
if bump {
|
||||||
patternCache = make(map[string]*Pattern)
|
patternCache = make(map[string]*Pattern)
|
||||||
cache.Clear()
|
cache.Clear()
|
||||||
@@ -530,6 +570,8 @@ func Run(opts *Options) (int, error) {
|
|||||||
} else {
|
} else {
|
||||||
terminal.UpdateHeader(nil)
|
terminal.UpdateHeader(nil)
|
||||||
}
|
}
|
||||||
|
} else if withNthChanged && headerLines > 0 {
|
||||||
|
terminal.UpdateHeader(GetItems(snapshot, int(headerLines)))
|
||||||
}
|
}
|
||||||
matcher.Reset(snapshot, input(), true, !reading, sort, snapshotRevision)
|
matcher.Reset(snapshot, input(), true, !reading, sort, snapshotRevision)
|
||||||
delay = false
|
delay = false
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package fzf
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -43,8 +42,11 @@ type Matcher struct {
|
|||||||
reqBox *util.EventBox
|
reqBox *util.EventBox
|
||||||
partitions int
|
partitions int
|
||||||
slab []*util.Slab
|
slab []*util.Slab
|
||||||
|
sortBuf [][]Result
|
||||||
mergerCache map[string]MatchResult
|
mergerCache map[string]MatchResult
|
||||||
revision revision
|
revision revision
|
||||||
|
scanMutex sync.Mutex
|
||||||
|
cancelScan *util.AtomicBool
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -68,8 +70,10 @@ func NewMatcher(cache *ChunkCache, patternBuilder func([]rune) *Pattern,
|
|||||||
reqBox: util.NewEventBox(),
|
reqBox: util.NewEventBox(),
|
||||||
partitions: partitions,
|
partitions: partitions,
|
||||||
slab: make([]*util.Slab, partitions),
|
slab: make([]*util.Slab, partitions),
|
||||||
|
sortBuf: make([][]Result, partitions),
|
||||||
mergerCache: make(map[string]MatchResult),
|
mergerCache: make(map[string]MatchResult),
|
||||||
revision: revision}
|
revision: revision,
|
||||||
|
cancelScan: util.NewAtomicBool(false)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop puts Matcher in action
|
// Loop puts Matcher in action
|
||||||
@@ -129,7 +133,9 @@ func (m *Matcher) Loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if result.merger == nil {
|
if result.merger == nil {
|
||||||
|
m.scanMutex.Lock()
|
||||||
result = m.scan(request)
|
result = m.scan(request)
|
||||||
|
m.scanMutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !result.cancelled {
|
if !result.cancelled {
|
||||||
@@ -215,11 +221,7 @@ func (m *Matcher) scan(request MatchRequest) MatchResult {
|
|||||||
sliceMatches = append(sliceMatches, matches...)
|
sliceMatches = append(sliceMatches, matches...)
|
||||||
}
|
}
|
||||||
if m.sort && request.pattern.sortable {
|
if m.sort && request.pattern.sortable {
|
||||||
if m.tac {
|
m.sortBuf[idx] = radixSortResults(sliceMatches, m.tac, m.sortBuf[idx])
|
||||||
sort.Sort(ByRelevanceTac(sliceMatches))
|
|
||||||
} else {
|
|
||||||
sort.Sort(ByRelevance(sliceMatches))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
resultChan <- partialResult{idx, sliceMatches}
|
resultChan <- partialResult{idx, sliceMatches}
|
||||||
}(idx, m.slab[idx], chunks)
|
}(idx, m.slab[idx], chunks)
|
||||||
@@ -241,7 +243,7 @@ func (m *Matcher) scan(request MatchRequest) MatchResult {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.reqBox.Peek(reqReset) {
|
if m.cancelScan.Get() || m.reqBox.Peek(reqReset) {
|
||||||
return MatchResult{nil, nil, wait()}
|
return MatchResult{nil, nil, wait()}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,6 +274,20 @@ func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final
|
|||||||
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort, revision})
|
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort, revision})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CancelScan cancels any in-flight scan, waits for it to finish,
|
||||||
|
// and prevents new scans from starting until ResumeScan is called.
|
||||||
|
// This is used to safely mutate shared items (e.g., during with-nth changes).
|
||||||
|
func (m *Matcher) CancelScan() {
|
||||||
|
m.cancelScan.Set(true)
|
||||||
|
m.scanMutex.Lock()
|
||||||
|
m.cancelScan.Set(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResumeScan allows scans to proceed again after CancelScan.
|
||||||
|
func (m *Matcher) ResumeScan() {
|
||||||
|
m.scanMutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Matcher) Stop() {
|
func (m *Matcher) Stop() {
|
||||||
m.reqBox.Set(reqQuit, nil)
|
m.reqBox.Set(reqQuit, nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -588,6 +588,7 @@ type Options struct {
|
|||||||
FreezeLeft int
|
FreezeLeft int
|
||||||
FreezeRight int
|
FreezeRight int
|
||||||
WithNth func(Delimiter) func([]Token, int32) string
|
WithNth func(Delimiter) func([]Token, int32) string
|
||||||
|
WithNthExpr string
|
||||||
AcceptNth func(Delimiter) func([]Token, int32) string
|
AcceptNth func(Delimiter) func([]Token, int32) string
|
||||||
Delimiter Delimiter
|
Delimiter Delimiter
|
||||||
Sort int
|
Sort int
|
||||||
@@ -1629,7 +1630,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-lines|header|footer|search|nth|pointer|ghost)|bg-transform|transform|change-(?:preview-window|preview|multi)|(?:re|un|toggle-)bind|pos|put|print|search|trigger)`)
|
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|bg-transform|transform)-(?:query|prompt|(?:border|list|preview|input|header|footer)-label|header-lines|header|footer|search|with-nth|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-]+")
|
||||||
}
|
}
|
||||||
@@ -2072,6 +2073,8 @@ func isExecuteAction(str string) actionType {
|
|||||||
return actChangeMulti
|
return actChangeMulti
|
||||||
case "change-nth":
|
case "change-nth":
|
||||||
return actChangeNth
|
return actChangeNth
|
||||||
|
case "change-with-nth":
|
||||||
|
return actChangeWithNth
|
||||||
case "pos":
|
case "pos":
|
||||||
return actPosition
|
return actPosition
|
||||||
case "execute":
|
case "execute":
|
||||||
@@ -2108,6 +2111,8 @@ func isExecuteAction(str string) actionType {
|
|||||||
return actTransformGhost
|
return actTransformGhost
|
||||||
case "transform-nth":
|
case "transform-nth":
|
||||||
return actTransformNth
|
return actTransformNth
|
||||||
|
case "transform-with-nth":
|
||||||
|
return actTransformWithNth
|
||||||
case "transform-pointer":
|
case "transform-pointer":
|
||||||
return actTransformPointer
|
return actTransformPointer
|
||||||
case "transform-prompt":
|
case "transform-prompt":
|
||||||
@@ -2140,6 +2145,8 @@ func isExecuteAction(str string) actionType {
|
|||||||
return actBgTransformGhost
|
return actBgTransformGhost
|
||||||
case "bg-transform-nth":
|
case "bg-transform-nth":
|
||||||
return actBgTransformNth
|
return actBgTransformNth
|
||||||
|
case "bg-transform-with-nth":
|
||||||
|
return actBgTransformWithNth
|
||||||
case "bg-transform-pointer":
|
case "bg-transform-pointer":
|
||||||
return actBgTransformPointer
|
return actBgTransformPointer
|
||||||
case "bg-transform-prompt":
|
case "bg-transform-prompt":
|
||||||
@@ -2781,6 +2788,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
if opts.WithNth, err = nthTransformer(str); err != nil {
|
if opts.WithNth, err = nthTransformer(str); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
opts.WithNthExpr = str
|
||||||
case "--accept-nth":
|
case "--accept-nth":
|
||||||
str, err := nextString("nth expression required")
|
str, err := nextString("nth expression required")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ type Pattern struct {
|
|||||||
cache *ChunkCache
|
cache *ChunkCache
|
||||||
denylist map[int32]struct{}
|
denylist map[int32]struct{}
|
||||||
startIndex int32
|
startIndex int32
|
||||||
|
directAlgo algo.Algo
|
||||||
|
directTerm *term
|
||||||
}
|
}
|
||||||
|
|
||||||
var _splitRegex *regexp.Regexp
|
var _splitRegex *regexp.Regexp
|
||||||
@@ -151,6 +153,7 @@ func BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy boo
|
|||||||
procFun: make(map[termType]algo.Algo)}
|
procFun: make(map[termType]algo.Algo)}
|
||||||
|
|
||||||
ptr.cacheKey = ptr.buildCacheKey()
|
ptr.cacheKey = ptr.buildCacheKey()
|
||||||
|
ptr.directAlgo, ptr.directTerm = ptr.buildDirectAlgo(fuzzyAlgo)
|
||||||
ptr.procFun[termFuzzy] = fuzzyAlgo
|
ptr.procFun[termFuzzy] = fuzzyAlgo
|
||||||
ptr.procFun[termEqual] = algo.EqualMatch
|
ptr.procFun[termEqual] = algo.EqualMatch
|
||||||
ptr.procFun[termExact] = algo.ExactMatchNaive
|
ptr.procFun[termExact] = algo.ExactMatchNaive
|
||||||
@@ -274,6 +277,22 @@ func (p *Pattern) buildCacheKey() string {
|
|||||||
return strings.Join(cacheableTerms, "\t")
|
return strings.Join(cacheableTerms, "\t")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// buildDirectAlgo returns the algo function and term for the direct fast path
|
||||||
|
// in matchChunk. Returns (nil, nil) if the pattern is not suitable.
|
||||||
|
// Requirements: extended mode, single term set with single non-inverse fuzzy term, no nth.
|
||||||
|
func (p *Pattern) buildDirectAlgo(fuzzyAlgo algo.Algo) (algo.Algo, *term) {
|
||||||
|
if !p.extended || len(p.nth) > 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if len(p.termSets) == 1 && len(p.termSets[0]) == 1 {
|
||||||
|
t := &p.termSets[0][0]
|
||||||
|
if !t.inv && t.typ == termFuzzy {
|
||||||
|
return fuzzyAlgo, t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
// CacheKey is used to build string to be used as the key of result cache
|
// CacheKey is used to build string to be used as the key of result cache
|
||||||
func (p *Pattern) CacheKey() string {
|
func (p *Pattern) CacheKey() string {
|
||||||
return p.cacheKey
|
return p.cacheKey
|
||||||
@@ -312,18 +331,47 @@ func (p *Pattern) matchChunk(chunk *Chunk, space []Result, slab *util.Slab) []Re
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(p.denylist) == 0 {
|
// Fast path: single fuzzy term, no nth, no denylist.
|
||||||
// Huge code duplication for minimizing unnecessary map lookups
|
// Calls the algo function directly, bypassing MatchItem/extendedMatch/iter
|
||||||
|
// and avoiding per-match []Offset heap allocation.
|
||||||
|
if p.directAlgo != nil && len(p.denylist) == 0 {
|
||||||
|
t := p.directTerm
|
||||||
if space == nil {
|
if space == nil {
|
||||||
for idx := startIdx; idx < chunk.count; idx++ {
|
for idx := startIdx; idx < chunk.count; idx++ {
|
||||||
if match, _, _ := p.MatchItem(&chunk.items[idx], p.withPos, slab); match != nil {
|
res, _ := p.directAlgo(t.caseSensitive, t.normalize, p.forward,
|
||||||
matches = append(matches, *match)
|
&chunk.items[idx].text, t.text, p.withPos, slab)
|
||||||
|
if res.Start >= 0 {
|
||||||
|
matches = append(matches, buildResultFromBounds(
|
||||||
|
&chunk.items[idx], res.Score,
|
||||||
|
int(res.Start), int(res.End), int(res.End), true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, result := range space {
|
for _, result := range space {
|
||||||
if match, _, _ := p.MatchItem(result.item, p.withPos, slab); match != nil {
|
res, _ := p.directAlgo(t.caseSensitive, t.normalize, p.forward,
|
||||||
matches = append(matches, *match)
|
&result.item.text, t.text, p.withPos, slab)
|
||||||
|
if res.Start >= 0 {
|
||||||
|
matches = append(matches, buildResultFromBounds(
|
||||||
|
result.item, res.Score,
|
||||||
|
int(res.Start), int(res.End), int(res.End), true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matches
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.denylist) == 0 {
|
||||||
|
// Huge code duplication for minimizing unnecessary map lookups
|
||||||
|
if space == nil {
|
||||||
|
for idx := startIdx; idx < chunk.count; idx++ {
|
||||||
|
if match, _, _ := p.MatchItem(&chunk.items[idx], p.withPos, slab); match.item != nil {
|
||||||
|
matches = append(matches, match)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, result := range space {
|
||||||
|
if match, _, _ := p.MatchItem(result.item, p.withPos, slab); match.item != nil {
|
||||||
|
matches = append(matches, match)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -335,8 +383,8 @@ func (p *Pattern) matchChunk(chunk *Chunk, space []Result, slab *util.Slab) []Re
|
|||||||
if _, prs := p.denylist[chunk.items[idx].Index()]; prs {
|
if _, prs := p.denylist[chunk.items[idx].Index()]; prs {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if match, _, _ := p.MatchItem(&chunk.items[idx], p.withPos, slab); match != nil {
|
if match, _, _ := p.MatchItem(&chunk.items[idx], p.withPos, slab); match.item != nil {
|
||||||
matches = append(matches, *match)
|
matches = append(matches, match)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -344,30 +392,29 @@ func (p *Pattern) matchChunk(chunk *Chunk, space []Result, slab *util.Slab) []Re
|
|||||||
if _, prs := p.denylist[result.item.Index()]; prs {
|
if _, prs := p.denylist[result.item.Index()]; prs {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if match, _, _ := p.MatchItem(result.item, p.withPos, slab); match != nil {
|
if match, _, _ := p.MatchItem(result.item, p.withPos, slab); match.item != nil {
|
||||||
matches = append(matches, *match)
|
matches = append(matches, match)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return matches
|
return matches
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchItem returns true if the Item is a match
|
// MatchItem returns the match result if the Item is a match.
|
||||||
func (p *Pattern) MatchItem(item *Item, withPos bool, slab *util.Slab) (*Result, []Offset, *[]int) {
|
// A zero-value Result (with item == nil) indicates no match.
|
||||||
|
func (p *Pattern) MatchItem(item *Item, withPos bool, slab *util.Slab) (Result, []Offset, *[]int) {
|
||||||
if p.extended {
|
if p.extended {
|
||||||
if offsets, bonus, pos := p.extendedMatch(item, withPos, slab); len(offsets) == len(p.termSets) {
|
if offsets, bonus, pos := p.extendedMatch(item, withPos, slab); len(offsets) == len(p.termSets) {
|
||||||
result := buildResult(item, offsets, bonus)
|
return buildResult(item, offsets, bonus), offsets, pos
|
||||||
return &result, offsets, pos
|
|
||||||
}
|
}
|
||||||
return nil, nil, nil
|
return Result{}, nil, nil
|
||||||
}
|
}
|
||||||
offset, bonus, pos := p.basicMatch(item, withPos, slab)
|
offset, bonus, pos := p.basicMatch(item, withPos, slab)
|
||||||
if sidx := offset[0]; sidx >= 0 {
|
if sidx := offset[0]; sidx >= 0 {
|
||||||
offsets := []Offset{offset}
|
offsets := []Offset{offset}
|
||||||
result := buildResult(item, offsets, bonus)
|
return buildResult(item, offsets, bonus), offsets, pos
|
||||||
return &result, offsets, pos
|
|
||||||
}
|
}
|
||||||
return nil, nil, nil
|
return Result{}, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset, int, *[]int) {
|
func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset, int, *[]int) {
|
||||||
|
|||||||
@@ -33,8 +33,6 @@ func buildResult(item *Item, offsets []Offset, score int) Result {
|
|||||||
sort.Sort(ByOrder(offsets))
|
sort.Sort(ByOrder(offsets))
|
||||||
}
|
}
|
||||||
|
|
||||||
result := Result{item: item}
|
|
||||||
numChars := item.text.Length()
|
|
||||||
minBegin := math.MaxUint16
|
minBegin := math.MaxUint16
|
||||||
minEnd := math.MaxUint16
|
minEnd := math.MaxUint16
|
||||||
maxEnd := 0
|
maxEnd := 0
|
||||||
@@ -49,6 +47,14 @@ func buildResult(item *Item, offsets []Offset, score int) Result {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return buildResultFromBounds(item, score, minBegin, minEnd, maxEnd, validOffsetFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildResultFromBounds builds a Result from pre-computed offset bounds.
|
||||||
|
func buildResultFromBounds(item *Item, score int, minBegin, minEnd, maxEnd int, validOffsetFound bool) Result {
|
||||||
|
result := Result{item: item}
|
||||||
|
numChars := item.text.Length()
|
||||||
|
|
||||||
for idx, criterion := range sortCriteria {
|
for idx, criterion := range sortCriteria {
|
||||||
val := uint16(math.MaxUint16)
|
val := uint16(math.MaxUint16)
|
||||||
switch criterion {
|
switch criterion {
|
||||||
@@ -75,7 +81,6 @@ func buildResult(item *Item, offsets []Offset, score int) Result {
|
|||||||
val = item.TrimLength()
|
val = item.TrimLength()
|
||||||
case byPathname:
|
case byPathname:
|
||||||
if validOffsetFound {
|
if validOffsetFound {
|
||||||
// lastDelim := strings.LastIndexByte(item.text.ToString(), '/')
|
|
||||||
lastDelim := -1
|
lastDelim := -1
|
||||||
s := item.text.ToString()
|
s := item.text.ToString()
|
||||||
for i := len(s) - 1; i >= 0; i-- {
|
for i := len(s) - 1; i >= 0; i-- {
|
||||||
@@ -334,3 +339,79 @@ func (a ByRelevanceTac) Swap(i, j int) {
|
|||||||
func (a ByRelevanceTac) Less(i, j int) bool {
|
func (a ByRelevanceTac) Less(i, j int) bool {
|
||||||
return compareRanks(a[i], a[j], true)
|
return compareRanks(a[i], a[j], true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// radixSortResults sorts Results by their points key using LSD radix sort.
|
||||||
|
// O(n) time complexity vs O(n log n) for comparison sort.
|
||||||
|
// The sort is stable, so equal-key items maintain original (item-index) order.
|
||||||
|
// For tac mode, runs of equal keys are reversed after sorting.
|
||||||
|
func radixSortResults(a []Result, tac bool, scratch []Result) []Result {
|
||||||
|
n := len(a)
|
||||||
|
if n < 128 {
|
||||||
|
if tac {
|
||||||
|
sort.Sort(ByRelevanceTac(a))
|
||||||
|
} else {
|
||||||
|
sort.Sort(ByRelevance(a))
|
||||||
|
}
|
||||||
|
return scratch[:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if cap(scratch) < n {
|
||||||
|
scratch = make([]Result, n)
|
||||||
|
}
|
||||||
|
buf := scratch[:n]
|
||||||
|
src, dst := a, buf
|
||||||
|
scattered := 0
|
||||||
|
|
||||||
|
for pass := range 8 {
|
||||||
|
shift := uint(pass) * 8
|
||||||
|
|
||||||
|
var count [256]int
|
||||||
|
for i := range src {
|
||||||
|
count[byte(sortKey(&src[i])>>shift)]++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if all items have the same byte value at this position
|
||||||
|
if count[byte(sortKey(&src[0])>>shift)] == n {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset [256]int
|
||||||
|
for i := 1; i < 256; i++ {
|
||||||
|
offset[i] = offset[i-1] + count[i-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range src {
|
||||||
|
b := byte(sortKey(&src[i]) >> shift)
|
||||||
|
dst[offset[b]] = src[i]
|
||||||
|
offset[b]++
|
||||||
|
}
|
||||||
|
|
||||||
|
src, dst = dst, src
|
||||||
|
scattered++
|
||||||
|
}
|
||||||
|
|
||||||
|
// If odd number of scatters, data is in buf, copy back to a
|
||||||
|
if scattered%2 == 1 {
|
||||||
|
copy(a, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle tac: reverse runs of equal keys so equal-key items
|
||||||
|
// are in reverse item-index order
|
||||||
|
if tac {
|
||||||
|
i := 0
|
||||||
|
for i < n {
|
||||||
|
ki := sortKey(&a[i])
|
||||||
|
j := i + 1
|
||||||
|
for j < n && sortKey(&a[j]) == ki {
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
if j-i > 1 {
|
||||||
|
for l, r := i, j-1; l < r; l, r = l+1, r-1 {
|
||||||
|
a[l], a[r] = a[r], a[l]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i = j
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return scratch
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,3 +14,7 @@ func compareRanks(irank Result, jrank Result, tac bool) bool {
|
|||||||
}
|
}
|
||||||
return (irank.item.Index() <= jrank.item.Index()) != tac
|
return (irank.item.Index() <= jrank.item.Index()) != tac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sortKey(r *Result) uint64 {
|
||||||
|
return uint64(r.points[0]) | uint64(r.points[1])<<16 | uint64(r.points[2])<<32 | uint64(r.points[3])<<48
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package fzf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
"math/rand"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -182,3 +183,60 @@ func TestColorOffset(t *testing.T) {
|
|||||||
assert(11, 39, 40, tui.NewColorPair(4, 8, tui.Bold))
|
assert(11, 39, 40, tui.NewColorPair(4, 8, tui.Bold))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRadixSortResults(t *testing.T) {
|
||||||
|
sortCriteria = []criterion{byScore, byLength}
|
||||||
|
|
||||||
|
rng := rand.New(rand.NewSource(42))
|
||||||
|
|
||||||
|
for _, n := range []int{128, 256, 500, 1000} {
|
||||||
|
for _, tac := range []bool{false, true} {
|
||||||
|
// Build items with random points and indices
|
||||||
|
items := make([]*Item, n)
|
||||||
|
for i := range items {
|
||||||
|
items[i] = &Item{text: util.Chars{Index: int32(i)}}
|
||||||
|
}
|
||||||
|
|
||||||
|
results := make([]Result, n)
|
||||||
|
for i := range results {
|
||||||
|
results[i] = Result{
|
||||||
|
item: items[i],
|
||||||
|
points: [4]uint16{
|
||||||
|
uint16(rng.Intn(256)),
|
||||||
|
uint16(rng.Intn(256)),
|
||||||
|
uint16(rng.Intn(256)),
|
||||||
|
uint16(rng.Intn(256)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make some duplicates to test stability
|
||||||
|
for i := 0; i < n/4; i++ {
|
||||||
|
j := rng.Intn(n)
|
||||||
|
k := rng.Intn(n)
|
||||||
|
results[j].points = results[k].points
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy for reference sort
|
||||||
|
expected := make([]Result, n)
|
||||||
|
copy(expected, results)
|
||||||
|
if tac {
|
||||||
|
sort.Sort(ByRelevanceTac(expected))
|
||||||
|
} else {
|
||||||
|
sort.Sort(ByRelevance(expected))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Radix sort
|
||||||
|
var scratch []Result
|
||||||
|
scratch = radixSortResults(results, tac, scratch)
|
||||||
|
|
||||||
|
for i := range results {
|
||||||
|
if results[i] != expected[i] {
|
||||||
|
t.Errorf("n=%d tac=%v: mismatch at index %d: got item %d, want item %d",
|
||||||
|
n, tac, i, results[i].item.Index(), expected[i].item.Index())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,3 +14,7 @@ func compareRanks(irank Result, jrank Result, tac bool) bool {
|
|||||||
}
|
}
|
||||||
return (irank.item.Index() <= jrank.item.Index()) != tac
|
return (irank.item.Index() <= jrank.item.Index()) != tac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sortKey(r *Result) uint64 {
|
||||||
|
return *(*uint64)(unsafe.Pointer(&r.points[0]))
|
||||||
|
}
|
||||||
|
|||||||
@@ -340,6 +340,9 @@ type Terminal struct {
|
|||||||
nthAttr tui.Attr
|
nthAttr tui.Attr
|
||||||
nth []Range
|
nth []Range
|
||||||
nthCurrent []Range
|
nthCurrent []Range
|
||||||
|
withNthDefault string
|
||||||
|
withNthExpr string
|
||||||
|
withNthEnabled bool
|
||||||
acceptNth func([]Token, int32) string
|
acceptNth func([]Token, int32) string
|
||||||
tabstop int
|
tabstop int
|
||||||
margin [4]sizeSpec
|
margin [4]sizeSpec
|
||||||
@@ -384,6 +387,7 @@ type Terminal struct {
|
|||||||
hasLoadActions bool
|
hasLoadActions bool
|
||||||
hasResizeActions bool
|
hasResizeActions bool
|
||||||
triggerLoad bool
|
triggerLoad bool
|
||||||
|
filterSelection bool
|
||||||
reading bool
|
reading bool
|
||||||
running *util.AtomicBool
|
running *util.AtomicBool
|
||||||
failed *string
|
failed *string
|
||||||
@@ -551,6 +555,7 @@ const (
|
|||||||
actChangeListLabel
|
actChangeListLabel
|
||||||
actChangeMulti
|
actChangeMulti
|
||||||
actChangeNth
|
actChangeNth
|
||||||
|
actChangeWithNth
|
||||||
actChangePointer
|
actChangePointer
|
||||||
actChangePreview
|
actChangePreview
|
||||||
actChangePreviewLabel
|
actChangePreviewLabel
|
||||||
@@ -636,6 +641,7 @@ const (
|
|||||||
actTransformInputLabel
|
actTransformInputLabel
|
||||||
actTransformListLabel
|
actTransformListLabel
|
||||||
actTransformNth
|
actTransformNth
|
||||||
|
actTransformWithNth
|
||||||
actTransformPointer
|
actTransformPointer
|
||||||
actTransformPreviewLabel
|
actTransformPreviewLabel
|
||||||
actTransformPrompt
|
actTransformPrompt
|
||||||
@@ -655,6 +661,7 @@ const (
|
|||||||
actBgTransformInputLabel
|
actBgTransformInputLabel
|
||||||
actBgTransformListLabel
|
actBgTransformListLabel
|
||||||
actBgTransformNth
|
actBgTransformNth
|
||||||
|
actBgTransformWithNth
|
||||||
actBgTransformPointer
|
actBgTransformPointer
|
||||||
actBgTransformPreviewLabel
|
actBgTransformPreviewLabel
|
||||||
actBgTransformPrompt
|
actBgTransformPrompt
|
||||||
@@ -721,6 +728,7 @@ func processExecution(action actionType) bool {
|
|||||||
actTransformInputLabel,
|
actTransformInputLabel,
|
||||||
actTransformListLabel,
|
actTransformListLabel,
|
||||||
actTransformNth,
|
actTransformNth,
|
||||||
|
actTransformWithNth,
|
||||||
actTransformPointer,
|
actTransformPointer,
|
||||||
actTransformPreviewLabel,
|
actTransformPreviewLabel,
|
||||||
actTransformPrompt,
|
actTransformPrompt,
|
||||||
@@ -737,6 +745,7 @@ func processExecution(action actionType) bool {
|
|||||||
actBgTransformInputLabel,
|
actBgTransformInputLabel,
|
||||||
actBgTransformListLabel,
|
actBgTransformListLabel,
|
||||||
actBgTransformNth,
|
actBgTransformNth,
|
||||||
|
actBgTransformWithNth,
|
||||||
actBgTransformPointer,
|
actBgTransformPointer,
|
||||||
actBgTransformPreviewLabel,
|
actBgTransformPreviewLabel,
|
||||||
actBgTransformPrompt,
|
actBgTransformPrompt,
|
||||||
@@ -766,10 +775,15 @@ type placeholderFlags struct {
|
|||||||
raw bool
|
raw bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type withNthSpec struct {
|
||||||
|
fn func([]Token, int32) string // nil = clear (restore original)
|
||||||
|
}
|
||||||
|
|
||||||
type searchRequest struct {
|
type searchRequest struct {
|
||||||
sort bool
|
sort bool
|
||||||
sync bool
|
sync bool
|
||||||
nth *[]Range
|
nth *[]Range
|
||||||
|
withNth *withNthSpec
|
||||||
headerLines *int
|
headerLines *int
|
||||||
command *commandSpec
|
command *commandSpec
|
||||||
environ []string
|
environ []string
|
||||||
@@ -1080,6 +1094,9 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|||||||
nthAttr: opts.Theme.Nth.Attr,
|
nthAttr: opts.Theme.Nth.Attr,
|
||||||
nth: opts.Nth,
|
nth: opts.Nth,
|
||||||
nthCurrent: opts.Nth,
|
nthCurrent: opts.Nth,
|
||||||
|
withNthDefault: opts.WithNthExpr,
|
||||||
|
withNthExpr: opts.WithNthExpr,
|
||||||
|
withNthEnabled: opts.WithNth != nil,
|
||||||
tabstop: opts.Tabstop,
|
tabstop: opts.Tabstop,
|
||||||
raw: opts.Raw,
|
raw: opts.Raw,
|
||||||
hasStartActions: false,
|
hasStartActions: false,
|
||||||
@@ -1351,6 +1368,9 @@ func (t *Terminal) environImpl(forPreview bool) []string {
|
|||||||
if len(t.nthCurrent) > 0 {
|
if len(t.nthCurrent) > 0 {
|
||||||
env = append(env, "FZF_NTH="+RangesToString(t.nthCurrent))
|
env = append(env, "FZF_NTH="+RangesToString(t.nthCurrent))
|
||||||
}
|
}
|
||||||
|
if len(t.withNthExpr) > 0 {
|
||||||
|
env = append(env, "FZF_WITH_NTH="+t.withNthExpr)
|
||||||
|
}
|
||||||
if t.raw {
|
if t.raw {
|
||||||
val := "0"
|
val := "0"
|
||||||
if t.isCurrentItemMatch() {
|
if t.isCurrentItemMatch() {
|
||||||
@@ -1720,6 +1740,17 @@ func (t *Terminal) Input() (bool, []rune) {
|
|||||||
return paused, copySlice(src)
|
return paused, copySlice(src)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PauseRendering blocks the terminal from reading items.
|
||||||
|
// Must be paired with ResumeRendering.
|
||||||
|
func (t *Terminal) PauseRendering() {
|
||||||
|
t.mutex.Lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResumeRendering releases the lock acquired by PauseRendering.
|
||||||
|
func (t *Terminal) ResumeRendering() {
|
||||||
|
t.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateCount updates the count information
|
// UpdateCount updates the count information
|
||||||
func (t *Terminal) UpdateCount(cnt int, final bool, failedCommand *string) {
|
func (t *Terminal) UpdateCount(cnt int, final bool, failedCommand *string) {
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
@@ -1842,6 +1873,21 @@ func (t *Terminal) UpdateList(result MatchResult) {
|
|||||||
}
|
}
|
||||||
t.revision = newRevision
|
t.revision = newRevision
|
||||||
t.version++
|
t.version++
|
||||||
|
|
||||||
|
// Filter out selections that no longer match after with-nth change.
|
||||||
|
// Must be inside the revision check so we don't consume the flag
|
||||||
|
// on a stale EvtSearchFin from a previous search.
|
||||||
|
if t.filterSelection && t.multi > 0 && len(t.selected) > 0 {
|
||||||
|
matchMap := t.resultMerger.ToMap()
|
||||||
|
filtered := make(map[int32]selectedItem)
|
||||||
|
for k, v := range t.selected {
|
||||||
|
if _, matched := matchMap[k]; matched {
|
||||||
|
filtered[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.selected = filtered
|
||||||
|
}
|
||||||
|
t.filterSelection = false
|
||||||
}
|
}
|
||||||
if t.triggerLoad {
|
if t.triggerLoad {
|
||||||
t.triggerLoad = false
|
t.triggerLoad = false
|
||||||
@@ -5922,6 +5968,7 @@ func (t *Terminal) Loop() error {
|
|||||||
events := []util.EventType{}
|
events := []util.EventType{}
|
||||||
changed := false
|
changed := false
|
||||||
var newNth *[]Range
|
var newNth *[]Range
|
||||||
|
var newWithNth *withNthSpec
|
||||||
var newHeaderLines *int
|
var newHeaderLines *int
|
||||||
req := func(evts ...util.EventType) {
|
req := func(evts ...util.EventType) {
|
||||||
for _, event := range evts {
|
for _, event := range evts {
|
||||||
@@ -5939,6 +5986,7 @@ func (t *Terminal) Loop() error {
|
|||||||
events = []util.EventType{}
|
events = []util.EventType{}
|
||||||
changed = false
|
changed = false
|
||||||
newNth = nil
|
newNth = nil
|
||||||
|
newWithNth = nil
|
||||||
newHeaderLines = nil
|
newHeaderLines = nil
|
||||||
beof := false
|
beof := false
|
||||||
queryChanged := false
|
queryChanged := false
|
||||||
@@ -6330,6 +6378,33 @@ func (t *Terminal) Loop() error {
|
|||||||
t.forceRerenderList()
|
t.forceRerenderList()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
case actChangeWithNth, actTransformWithNth, actBgTransformWithNth:
|
||||||
|
if !t.withNthEnabled {
|
||||||
|
break Action
|
||||||
|
}
|
||||||
|
capture(true, func(expr string) {
|
||||||
|
tokens := strings.Split(expr, "|")
|
||||||
|
withNthExpr := tokens[0]
|
||||||
|
if len(tokens) > 1 {
|
||||||
|
a.a = strings.Join(append(tokens[1:], tokens[0]), "|")
|
||||||
|
}
|
||||||
|
// Empty value restores the default --with-nth
|
||||||
|
if len(withNthExpr) == 0 {
|
||||||
|
withNthExpr = t.withNthDefault
|
||||||
|
}
|
||||||
|
if withNthExpr != t.withNthExpr {
|
||||||
|
if factory, err := nthTransformer(withNthExpr); err == nil {
|
||||||
|
newWithNth = &withNthSpec{fn: factory(t.delimiter)}
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.withNthExpr = withNthExpr
|
||||||
|
t.filterSelection = true
|
||||||
|
changed = true
|
||||||
|
t.clearNumLinesCache()
|
||||||
|
t.forceRerenderList()
|
||||||
|
}
|
||||||
|
})
|
||||||
case actChangeQuery:
|
case actChangeQuery:
|
||||||
t.input = []rune(a.a)
|
t.input = []rune(a.a)
|
||||||
t.cx = len(t.input)
|
t.cx = len(t.input)
|
||||||
@@ -7477,7 +7552,7 @@ func (t *Terminal) Loop() error {
|
|||||||
reload := changed || newCommand != nil
|
reload := changed || newCommand != nil
|
||||||
var reloadRequest *searchRequest
|
var reloadRequest *searchRequest
|
||||||
if reload {
|
if reload {
|
||||||
reloadRequest = &searchRequest{sort: t.sort, sync: reloadSync, nth: newNth, headerLines: newHeaderLines, command: newCommand, environ: t.environ(), changed: changed, denylist: denylist, revision: t.resultMerger.Revision()}
|
reloadRequest = &searchRequest{sort: t.sort, sync: reloadSync, nth: newNth, withNth: newWithNth, headerLines: newHeaderLines, command: newCommand, environ: t.environ(), changed: changed, denylist: denylist, revision: t.resultMerger.Revision()}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispatch queued background requests
|
// Dispatch queued background requests
|
||||||
|
|||||||
@@ -1745,6 +1745,191 @@ class TestCore < TestInteractive
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_change_with_nth
|
||||||
|
input = [
|
||||||
|
'foo bar baz',
|
||||||
|
'aaa bbb ccc',
|
||||||
|
'xxx yyy zzz'
|
||||||
|
]
|
||||||
|
writelines(input)
|
||||||
|
# Start with field 1 only, cycle through fields, verify $FZF_WITH_NTH via prompt
|
||||||
|
tmux.send_keys %(#{FZF} --with-nth 1 --bind 'space:change-with-nth(2|3|1),result:transform-prompt:echo "[$FZF_WITH_NTH]> "' < #{tempname}), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 3, lines.item_count
|
||||||
|
assert lines.any_include?('[1]>')
|
||||||
|
assert lines.any_include?('foo')
|
||||||
|
refute lines.any_include?('bar')
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.any_include?('[2]>')
|
||||||
|
assert lines.any_include?('bar')
|
||||||
|
refute lines.any_include?('foo')
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.any_include?('[3]>')
|
||||||
|
assert lines.any_include?('baz')
|
||||||
|
refute lines.any_include?('bar')
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.any_include?('[1]>')
|
||||||
|
assert lines.any_include?('foo')
|
||||||
|
refute lines.any_include?('bar')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_change_with_nth_default
|
||||||
|
# Empty value restores the default --with-nth
|
||||||
|
tmux.send_keys %(echo -e 'a b c\nd e f' | #{FZF} --with-nth 1 --bind 'space:change-with-nth(2|)'), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 2, lines.item_count
|
||||||
|
assert lines.any_include?('a')
|
||||||
|
refute lines.any_include?('b')
|
||||||
|
end
|
||||||
|
# Switch to field 2
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.any_include?('b')
|
||||||
|
refute lines.any_include?('a')
|
||||||
|
end
|
||||||
|
# Empty restores default (field 1)
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.any_include?('a')
|
||||||
|
refute lines.any_include?('b')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_transform_with_nth_search
|
||||||
|
input = [
|
||||||
|
'alpha bravo charlie',
|
||||||
|
'delta echo foxtrot',
|
||||||
|
'golf hotel india'
|
||||||
|
]
|
||||||
|
writelines(input)
|
||||||
|
tmux.send_keys %(#{FZF} --with-nth 1 --bind 'space:transform-with-nth(echo 2)' -q '^bravo$' < #{tempname}), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 0, lines.match_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_bg_transform_with_nth_output
|
||||||
|
tmux.send_keys %(echo -e 'a b c\nd e f' | #{FZF} --with-nth 2 --bind 'space:bg-transform-with-nth(echo 3)'), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 2, lines.item_count
|
||||||
|
assert lines.any_include?('b')
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.any_include?('c')
|
||||||
|
refute lines.any_include?('b')
|
||||||
|
end
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert lines.any_include?('a b c') || lines.any_include?('d e f') }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_change_with_nth_search
|
||||||
|
input = [
|
||||||
|
'alpha bravo charlie',
|
||||||
|
'delta echo foxtrot',
|
||||||
|
'golf hotel india'
|
||||||
|
]
|
||||||
|
writelines(input)
|
||||||
|
tmux.send_keys %(#{FZF} --with-nth 1 --bind 'space:change-with-nth(2)' -q '^bravo$' < #{tempname}), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 0, lines.match_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_change_with_nth_output
|
||||||
|
tmux.send_keys %(echo -e 'a b c\nd e f' | #{FZF} --with-nth 2 --bind 'space:change-with-nth(3)'), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 2, lines.item_count
|
||||||
|
assert lines.any_include?('b')
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.any_include?('c')
|
||||||
|
refute lines.any_include?('b')
|
||||||
|
end
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert lines.any_include?('a b c') || lines.any_include?('d e f') }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_change_with_nth_selection
|
||||||
|
# Items: field1 has unique values, field2 has 'match' or 'miss'
|
||||||
|
input = [
|
||||||
|
'one match x',
|
||||||
|
'two miss y',
|
||||||
|
'three match z'
|
||||||
|
]
|
||||||
|
writelines(input)
|
||||||
|
# Start showing field 2 (match/miss), query 'match', select all matches, then switch to field 3
|
||||||
|
tmux.send_keys %(#{FZF} --with-nth 2 --multi --bind 'ctrl-a:select-all,space:change-with-nth(3)' -q match < #{tempname}), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 2, lines.match_count
|
||||||
|
end
|
||||||
|
# Select all matching items
|
||||||
|
tmux.send_keys 'C-a'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.any_include?('(2)')
|
||||||
|
end
|
||||||
|
# Now change with-nth to field 3; 'x' and 'z' don't contain 'match'
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 0, lines.match_count
|
||||||
|
# Selections of non-matching items should be cleared
|
||||||
|
assert lines.any_include?('(0)')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_change_with_nth_multiline
|
||||||
|
# Each item has 3 lines: "N-a\nN-b\nN-c"
|
||||||
|
# --with-nth 1 shows 1 line per item, --with-nth 1..3 shows 3 lines per item
|
||||||
|
tmux.send_keys %(seq 20 | xargs -I{} printf '{}-a\\n{}-b\\n{}-c\\0' | #{FZF} --read0 --delimiter "\n" --with-nth 1 --bind 'space:change-with-nth(1..3|1)' --no-sort), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 20, lines.item_count
|
||||||
|
assert lines.any_include?('1-a')
|
||||||
|
refute lines.any_include?('1-b')
|
||||||
|
end
|
||||||
|
# Expand to 3 lines per item
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.any_include?('1-a')
|
||||||
|
assert lines.any_include?('1-b')
|
||||||
|
assert lines.any_include?('1-c')
|
||||||
|
end
|
||||||
|
# Scroll down a few items
|
||||||
|
5.times { tmux.send_keys :Down }
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.any_include?('6-a')
|
||||||
|
assert lines.any_include?('6-b')
|
||||||
|
assert lines.any_include?('6-c')
|
||||||
|
end
|
||||||
|
# Collapse back to 1 line per item
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.any_include?('6-a')
|
||||||
|
refute lines.any_include?('6-b')
|
||||||
|
end
|
||||||
|
# Scroll down more after collapse
|
||||||
|
5.times { tmux.send_keys :Down }
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.any_include?('11-a')
|
||||||
|
refute lines.any_include?('11-b')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_env_vars
|
def test_env_vars
|
||||||
def env_vars
|
def env_vars
|
||||||
return {} unless File.exist?(tempname)
|
return {} unless File.exist?(tempname)
|
||||||
|
|||||||
Reference in New Issue
Block a user