mirror of
https://github.com/junegunn/fzf.git
synced 2025-12-08 21:54:50 +08:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8cf0992c1 | ||
|
|
33d8d51c8a | ||
|
|
b473477c22 | ||
|
|
fcc4178bca | ||
|
|
cfc37caabc | ||
|
|
af2a81dc02 | ||
|
|
be5a687281 | ||
|
|
771e35b972 | ||
|
|
60a5be1e65 | ||
|
|
1d5e87f5e4 | ||
|
|
3db63f5e52 |
9
Makefile
9
Makefile
@@ -1,4 +1,5 @@
|
|||||||
GO ?= go
|
GO ?= go
|
||||||
|
DOCKER ?= docker
|
||||||
GOOS ?= $(shell $(GO) env GOOS)
|
GOOS ?= $(shell $(GO) env GOOS)
|
||||||
|
|
||||||
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
|
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
|
||||||
@@ -192,12 +193,12 @@ bin/fzf: target/$(BINARY) | bin
|
|||||||
cp -f target/$(BINARY) bin/fzf
|
cp -f target/$(BINARY) bin/fzf
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
docker build -t fzf-ubuntu .
|
$(DOCKER) build -t fzf-ubuntu .
|
||||||
docker run -it fzf-ubuntu tmux
|
$(DOCKER) run -it fzf-ubuntu tmux
|
||||||
|
|
||||||
docker-test:
|
docker-test:
|
||||||
docker build -t fzf-ubuntu .
|
$(DOCKER) build -t fzf-ubuntu .
|
||||||
docker run -it fzf-ubuntu
|
$(DOCKER) run -it fzf-ubuntu
|
||||||
|
|
||||||
update:
|
update:
|
||||||
$(GO) get -u
|
$(GO) get -u
|
||||||
|
|||||||
@@ -272,6 +272,7 @@ color mappings. Each entry is separated by a comma and/or whitespaces.
|
|||||||
\fBgutter \fRGutter on the left
|
\fBgutter \fRGutter on the left
|
||||||
\fBcurrent\-hl (hl+) \fRHighlighted substrings (current line)
|
\fBcurrent\-hl (hl+) \fRHighlighted substrings (current line)
|
||||||
\fBalt\-bg \fRAlternate background color to create striped lines
|
\fBalt\-bg \fRAlternate background color to create striped lines
|
||||||
|
\fBalt\-gutter \fRAlternate gutter color to create the striped pattern
|
||||||
\fBquery (input\-fg) \fRQuery string
|
\fBquery (input\-fg) \fRQuery string
|
||||||
\fBghost \fRGhost text (\fB\-\-ghost\fR, \fBdim\fR applied by default)
|
\fBghost \fRGhost text (\fB\-\-ghost\fR, \fBdim\fR applied by default)
|
||||||
\fBdisabled \fRQuery string when search is disabled (\fB\-\-disabled\fR)
|
\fBdisabled \fRQuery string when search is disabled (\fB\-\-disabled\fR)
|
||||||
@@ -740,7 +741,7 @@ ENVIRONMENT VARIABLES EXPORTED TO CHILD PROCESSES.
|
|||||||
|
|
||||||
e.g.
|
e.g.
|
||||||
\fB# Prepend the current cursor position in yellow
|
\fB# Prepend the current cursor position in yellow
|
||||||
fzf \-\-info\-command='echo \-e "\\x1b[33;1m$FZF_POS\\x1b[m/$FZF_INFO 💛"'\fR
|
fzf \-\-info\-command='printf "\\x1b[33;1m$FZF_POS\\x1b[m/$FZF_INFO 💛"'\fR
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B "\-\-no\-info"
|
.B "\-\-no\-info"
|
||||||
@@ -840,6 +841,9 @@ e.g.
|
|||||||
# This won't work properly without 'f' flag due to ARG_MAX limit.
|
# This won't work properly without 'f' flag due to ARG_MAX limit.
|
||||||
seq 100000 | fzf \-\-preview "awk '{sum+=\\$1} END {print sum}' {*f}"\fR
|
seq 100000 | fzf \-\-preview "awk '{sum+=\\$1} END {print sum}' {*f}"\fR
|
||||||
|
|
||||||
|
\fB# Use {+f} to get the selected items as a line-separated list
|
||||||
|
seq 100 | fzf \-\-multi \-\-bind 'enter:become:cat {+f}'\fR
|
||||||
|
|
||||||
Also,
|
Also,
|
||||||
|
|
||||||
* \fB{q}\fR is replaced to the current query string
|
* \fB{q}\fR is replaced to the current query string
|
||||||
@@ -2105,7 +2109,7 @@ payload of HTTP POST request to the \fB\-\-listen\fR server.
|
|||||||
|
|
||||||
e.g.
|
e.g.
|
||||||
\fB# Disallow selecting an empty line
|
\fB# Disallow selecting an empty line
|
||||||
echo \-e "1. Hello\\n2. Goodbye\\n\\n3. Exit" |
|
printf "1. Hello\\n2. Goodbye\\n\\n3. Exit" |
|
||||||
fzf \-\-height '~100%' \-\-reverse \-\-header 'Select one' \\
|
fzf \-\-height '~100%' \-\-reverse \-\-header 'Select one' \\
|
||||||
\-\-bind 'enter:transform:[[ \-n {} ]] &&
|
\-\-bind 'enter:transform:[[ \-n {} ]] &&
|
||||||
echo accept ||
|
echo accept ||
|
||||||
|
|||||||
@@ -128,25 +128,52 @@ fi
|
|||||||
|
|
||||||
# CTRL-R - Paste the selected command from history into the command line
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
fzf-history-widget() {
|
fzf-history-widget() {
|
||||||
local selected
|
local selected extracted_with_perl=0
|
||||||
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases noglob nobash_rematch 2> /dev/null
|
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases no_glob no_ksharrays extendedglob 2> /dev/null
|
||||||
# Ensure the module is loaded if not already, and the required features, such
|
# Ensure the module is loaded if not already, and the required features, such
|
||||||
# as the associative 'history' array, which maps event numbers to full history
|
# as the associative 'history' array, which maps event numbers to full history
|
||||||
# lines, are set. Also, make sure Perl is installed for multi-line output.
|
# lines, are set. Also, make sure Perl is installed for multi-line output.
|
||||||
if zmodload -F zsh/parameter p:{commands,history} 2>/dev/null && (( ${+commands[perl]} )); then
|
if zmodload -F zsh/parameter p:{commands,history} 2>/dev/null && (( ${+commands[perl]} )); then
|
||||||
selected="$(printf '%s\t%s\000' "${(kv)history[@]}" |
|
selected="$(printf '%s\t%s\000' "${(kv)history[@]}" |
|
||||||
perl -0 -ne 'if (!$seen{(/^\s*[0-9]+\**\t(.*)/s, $1)}++) { s/\n/\n\t/g; print; }' |
|
perl -0 -ne 'if (!$seen{(/^\s*[0-9]+\**\t(.*)/s, $1)}++) { s/\n/\n\t/g; print; }' |
|
||||||
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,alt-r:toggle-raw --wrap-sign '\t↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m --read0") \
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,alt-r:toggle-raw --wrap-sign '\t↳ ' --highlight-line --multi ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} --read0") \
|
||||||
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
|
||||||
|
extracted_with_perl=1
|
||||||
else
|
else
|
||||||
selected="$(fc -rl 1 | __fzf_exec_awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
|
selected="$(fc -rl 1 | __fzf_exec_awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
|
||||||
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,alt-r:toggle-raw --wrap-sign '\t↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m") \
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,alt-r:toggle-raw --wrap-sign '\t↳ ' --highlight-line --multi ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER}") \
|
||||||
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
|
||||||
fi
|
fi
|
||||||
local ret=$?
|
local ret=$?
|
||||||
|
local -a cmds
|
||||||
|
# Avoid leaking auto assigned values when using backreferences '(#b)'
|
||||||
|
local -a mbegin mend match
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
if [[ $(__fzf_exec_awk '{print $1; exit}' <<< "$selected") =~ ^[1-9][0-9]* ]]; then
|
# Heuristic to check if the selected value is from history or a custom query
|
||||||
zle vi-fetch-history -n $MATCH
|
if ((( extracted_with_perl )) && [[ $selected == <->$'\t'* ]]) ||
|
||||||
|
((( ! extracted_with_perl )) && [[ $selected == [[:blank:]]#<->( |\* )* ]]); then
|
||||||
|
# Split at newlines
|
||||||
|
for line in ${(ps:\n:)selected}; do
|
||||||
|
if (( extracted_with_perl )); then
|
||||||
|
if [[ $line == (#b)(<->)(#B)$'\t'* ]]; then
|
||||||
|
(( ${+history[${match[1]}]} )) && cmds+=("${history[${match[1]}]}")
|
||||||
|
fi
|
||||||
|
elif [[ $line == [[:blank:]]#(#b)(<->)(#B)( |\* )* ]]; then
|
||||||
|
# Avoid $history array: lags behind 'fc' on foreign commands (*)
|
||||||
|
# https://zsh.org/mla/users/2024/msg00692.html
|
||||||
|
# Push BUFFER onto stack; fetch and save history entry from BUFFER; restore
|
||||||
|
zle .push-line
|
||||||
|
zle vi-fetch-history -n ${match[1]}
|
||||||
|
(( ${#BUFFER} )) && cmds+=("${BUFFER}")
|
||||||
|
BUFFER=""
|
||||||
|
zle .get-line
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if (( ${#cmds[@]} )); then
|
||||||
|
# Join by newline after stripping trailing newlines from each command
|
||||||
|
BUFFER="${(pj:\n:)${(@)cmds%%$'\n'#}}"
|
||||||
|
CURSOR=${#BUFFER}
|
||||||
|
fi
|
||||||
else # selected is a custom query, not from history
|
else # selected is a custom query, not from history
|
||||||
LBUFFER="$selected"
|
LBUFFER="$selected"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -445,7 +445,9 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
|
|
||||||
// Since O(nm) algorithm can be prohibitively expensive for large input,
|
// Since O(nm) algorithm can be prohibitively expensive for large input,
|
||||||
// we fall back to the greedy algorithm.
|
// we fall back to the greedy algorithm.
|
||||||
if slab != nil && N*M > cap(slab.I16) {
|
// Also, we should not allow a very long pattern to avoid 16-bit integer
|
||||||
|
// overflow in the score matrix. 1000 is a safe limit.
|
||||||
|
if slab != nil && N*M > cap(slab.I16) || M > 1000 {
|
||||||
return FuzzyMatchV1(caseSensitive, normalize, forward, input, pattern, withPos, slab)
|
return FuzzyMatchV1(caseSensitive, normalize, forward, input, pattern, withPos, slab)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1476,6 +1476,8 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, *tui
|
|||||||
mergeAttr(&theme.Nomatch)
|
mergeAttr(&theme.Nomatch)
|
||||||
case "gutter":
|
case "gutter":
|
||||||
mergeAttr(&theme.Gutter)
|
mergeAttr(&theme.Gutter)
|
||||||
|
case "alt-gutter":
|
||||||
|
mergeAttr(&theme.AltGutter)
|
||||||
case "hl":
|
case "hl":
|
||||||
mergeAttr(&theme.Match)
|
mergeAttr(&theme.Match)
|
||||||
case "current-hl", "hl+":
|
case "current-hl", "hl+":
|
||||||
|
|||||||
@@ -3202,14 +3202,22 @@ func (t *Terminal) renderEmptyLine(line int, barRange [2]int) {
|
|||||||
t.renderBar(line, barRange)
|
t.renderBar(line, barRange)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) gutter(current bool) {
|
func (t *Terminal) gutter(current bool, alt bool) {
|
||||||
var color tui.ColorPair
|
var color tui.ColorPair
|
||||||
if current {
|
if current {
|
||||||
color = tui.ColCurrentCursorEmpty
|
color = tui.ColCurrentCursorEmpty
|
||||||
} else if !t.raw && t.gutterReverse || t.raw && t.gutterRawReverse {
|
} else if !t.raw && t.gutterReverse || t.raw && t.gutterRawReverse {
|
||||||
color = tui.ColCursorEmpty
|
if alt {
|
||||||
|
color = tui.ColAltCursorEmpty
|
||||||
|
} else {
|
||||||
|
color = tui.ColCursorEmpty
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
color = tui.ColCursorEmptyChar
|
if alt {
|
||||||
|
color = tui.ColAltCursorEmptyChar
|
||||||
|
} else {
|
||||||
|
color = tui.ColCursorEmptyChar
|
||||||
|
}
|
||||||
}
|
}
|
||||||
gutter := t.pointerEmpty
|
gutter := t.pointerEmpty
|
||||||
if t.raw {
|
if t.raw {
|
||||||
@@ -3220,7 +3228,7 @@ func (t *Terminal) gutter(current bool) {
|
|||||||
|
|
||||||
func (t *Terminal) renderGapLine(line int, barRange [2]int, drawLine bool) {
|
func (t *Terminal) renderGapLine(line int, barRange [2]int, drawLine bool) {
|
||||||
t.move(line, 0, false)
|
t.move(line, 0, false)
|
||||||
t.gutter(false)
|
t.gutter(false, false)
|
||||||
t.window.Print(t.markerEmpty)
|
t.window.Print(t.markerEmpty)
|
||||||
x := t.pointerLen + t.markerLen
|
x := t.pointerLen + t.markerLen
|
||||||
|
|
||||||
@@ -3394,7 +3402,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
|
|||||||
return indentSize
|
return indentSize
|
||||||
}
|
}
|
||||||
if len(label) == 0 {
|
if len(label) == 0 {
|
||||||
t.gutter(true)
|
t.gutter(true, false)
|
||||||
} else {
|
} else {
|
||||||
t.window.CPrint(tui.ColCurrentCursor, label)
|
t.window.CPrint(tui.ColCurrentCursor, label)
|
||||||
}
|
}
|
||||||
@@ -3416,7 +3424,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
|
|||||||
return indentSize
|
return indentSize
|
||||||
}
|
}
|
||||||
if len(label) == 0 {
|
if len(label) == 0 {
|
||||||
t.gutter(false)
|
t.gutter(false, index%2 == 1)
|
||||||
} else {
|
} else {
|
||||||
t.window.CPrint(tui.ColCursor, label)
|
t.window.CPrint(tui.ColCursor, label)
|
||||||
}
|
}
|
||||||
@@ -3807,7 +3815,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
|||||||
offs[idx].offset[1] = util.Min32(offset.offset[1], int32(maxWidth))
|
offs[idx].offset[1] = util.Min32(offset.offset[1], int32(maxWidth))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
displayWidth = t.displayWidthWithLimit(runes, 0, displayWidth)
|
displayWidth = t.displayWidthWithLimit(runes, 0, maxWidth)
|
||||||
}
|
}
|
||||||
displayWidthSum += displayWidth
|
displayWidthSum += displayWidth
|
||||||
|
|
||||||
|
|||||||
@@ -456,6 +456,7 @@ type ColorTheme struct {
|
|||||||
PreviewBg ColorAttr
|
PreviewBg ColorAttr
|
||||||
DarkBg ColorAttr
|
DarkBg ColorAttr
|
||||||
Gutter ColorAttr
|
Gutter ColorAttr
|
||||||
|
AltGutter ColorAttr
|
||||||
Prompt ColorAttr
|
Prompt ColorAttr
|
||||||
InputBg ColorAttr
|
InputBg ColorAttr
|
||||||
InputBorder ColorAttr
|
InputBorder ColorAttr
|
||||||
@@ -826,6 +827,8 @@ var (
|
|||||||
ColCursor ColorPair
|
ColCursor ColorPair
|
||||||
ColCursorEmpty ColorPair
|
ColCursorEmpty ColorPair
|
||||||
ColCursorEmptyChar ColorPair
|
ColCursorEmptyChar ColorPair
|
||||||
|
ColAltCursorEmpty ColorPair
|
||||||
|
ColAltCursorEmptyChar ColorPair
|
||||||
ColMarker ColorPair
|
ColMarker ColorPair
|
||||||
ColSelected ColorPair
|
ColSelected ColorPair
|
||||||
ColSelectedMatch ColorPair
|
ColSelectedMatch ColorPair
|
||||||
@@ -891,6 +894,7 @@ func init() {
|
|||||||
PreviewFg: defaultColor,
|
PreviewFg: defaultColor,
|
||||||
PreviewBg: defaultColor,
|
PreviewBg: defaultColor,
|
||||||
Gutter: undefined,
|
Gutter: undefined,
|
||||||
|
AltGutter: undefined,
|
||||||
PreviewBorder: defaultColor,
|
PreviewBorder: defaultColor,
|
||||||
PreviewScrollbar: defaultColor,
|
PreviewScrollbar: defaultColor,
|
||||||
PreviewLabel: defaultColor,
|
PreviewLabel: defaultColor,
|
||||||
@@ -943,6 +947,7 @@ func init() {
|
|||||||
PreviewFg: undefined,
|
PreviewFg: undefined,
|
||||||
PreviewBg: undefined,
|
PreviewBg: undefined,
|
||||||
Gutter: undefined,
|
Gutter: undefined,
|
||||||
|
AltGutter: undefined,
|
||||||
PreviewBorder: undefined,
|
PreviewBorder: undefined,
|
||||||
PreviewScrollbar: undefined,
|
PreviewScrollbar: undefined,
|
||||||
PreviewLabel: undefined,
|
PreviewLabel: undefined,
|
||||||
@@ -991,6 +996,7 @@ func init() {
|
|||||||
PreviewFg: undefined,
|
PreviewFg: undefined,
|
||||||
PreviewBg: undefined,
|
PreviewBg: undefined,
|
||||||
Gutter: undefined,
|
Gutter: undefined,
|
||||||
|
AltGutter: undefined,
|
||||||
PreviewBorder: undefined,
|
PreviewBorder: undefined,
|
||||||
PreviewScrollbar: undefined,
|
PreviewScrollbar: undefined,
|
||||||
PreviewLabel: undefined,
|
PreviewLabel: undefined,
|
||||||
@@ -1041,6 +1047,7 @@ func init() {
|
|||||||
PreviewFg: undefined,
|
PreviewFg: undefined,
|
||||||
PreviewBg: undefined,
|
PreviewBg: undefined,
|
||||||
Gutter: undefined,
|
Gutter: undefined,
|
||||||
|
AltGutter: undefined,
|
||||||
PreviewBorder: undefined,
|
PreviewBorder: undefined,
|
||||||
PreviewScrollbar: undefined,
|
PreviewScrollbar: undefined,
|
||||||
PreviewLabel: undefined,
|
PreviewLabel: undefined,
|
||||||
@@ -1091,6 +1098,7 @@ func init() {
|
|||||||
PreviewFg: undefined,
|
PreviewFg: undefined,
|
||||||
PreviewBg: undefined,
|
PreviewBg: undefined,
|
||||||
Gutter: undefined,
|
Gutter: undefined,
|
||||||
|
AltGutter: undefined,
|
||||||
PreviewBorder: undefined,
|
PreviewBorder: undefined,
|
||||||
PreviewScrollbar: undefined,
|
PreviewScrollbar: undefined,
|
||||||
PreviewLabel: undefined,
|
PreviewLabel: undefined,
|
||||||
@@ -1208,6 +1216,7 @@ func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, boldify bool, forceBlac
|
|||||||
gutter.Attr = Dim
|
gutter.Attr = Dim
|
||||||
}
|
}
|
||||||
theme.Gutter = o(theme.DarkBg, gutter)
|
theme.Gutter = o(theme.DarkBg, gutter)
|
||||||
|
theme.AltGutter = o(theme.Gutter, theme.AltGutter)
|
||||||
theme.PreviewFg = o(theme.Fg, theme.PreviewFg)
|
theme.PreviewFg = o(theme.Fg, theme.PreviewFg)
|
||||||
theme.PreviewBg = o(theme.Bg, theme.PreviewBg)
|
theme.PreviewBg = o(theme.Bg, theme.PreviewBg)
|
||||||
theme.PreviewLabel = o(theme.BorderLabel, theme.PreviewLabel)
|
theme.PreviewLabel = o(theme.BorderLabel, theme.PreviewLabel)
|
||||||
@@ -1277,6 +1286,8 @@ func initPalette(theme *ColorTheme) {
|
|||||||
ColCursor = pair(theme.Cursor, theme.Gutter)
|
ColCursor = pair(theme.Cursor, theme.Gutter)
|
||||||
ColCursorEmpty = pair(blank, theme.Gutter)
|
ColCursorEmpty = pair(blank, theme.Gutter)
|
||||||
ColCursorEmptyChar = pair(theme.Gutter, theme.ListBg)
|
ColCursorEmptyChar = pair(theme.Gutter, theme.ListBg)
|
||||||
|
ColAltCursorEmpty = pair(blank, theme.AltGutter)
|
||||||
|
ColAltCursorEmptyChar = pair(theme.AltGutter, theme.ListBg)
|
||||||
if theme.SelectedBg.Color != theme.ListBg.Color {
|
if theme.SelectedBg.Color != theme.ListBg.Color {
|
||||||
ColMarker = pair(theme.Marker, theme.SelectedBg)
|
ColMarker = pair(theme.Marker, theme.SelectedBg)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1235,6 +1235,16 @@ class TestCore < TestInteractive
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_freeze_right_with_ellipsis_and_scrolling
|
||||||
|
tmux.send_keys "{ seq 6; ruby -e 'print \"g\"*1000, \"\\n\"'; seq 8 100; } | #{FZF} --ellipsis='777' --freeze-right 1 --scroll-off 0 --bind a:offset-up", :Enter
|
||||||
|
tmux.until { |lines| assert_equal ' 100/100', lines[-2] }
|
||||||
|
tmux.send_keys(*Array.new(6) { :a })
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_match(/> 777g+$/, lines[-3])
|
||||||
|
assert_equal 1, lines.count { |l| l.end_with?('g') }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_backward_eof
|
def test_backward_eof
|
||||||
tmux.send_keys "echo foo | #{FZF} --bind 'backward-eof:reload(seq 100)'", :Enter
|
tmux.send_keys "echo foo | #{FZF} --bind 'backward-eof:reload(seq 100)'", :Enter
|
||||||
tmux.until { |lines| lines.item_count == 1 && lines.match_count == 1 }
|
tmux.until { |lines| lines.item_count == 1 && lines.match_count == 1 }
|
||||||
|
|||||||
@@ -462,6 +462,119 @@ class TestZsh < TestBase
|
|||||||
tmux.send_keys 'C-c'
|
tmux.send_keys 'C-c'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Helper function to run test with Perl and again with Awk
|
||||||
|
def self.test_perl_and_awk(name, &block)
|
||||||
|
define_method("test_#{name}") do
|
||||||
|
instance_eval(&block)
|
||||||
|
end
|
||||||
|
|
||||||
|
define_method("test_#{name}_awk") do
|
||||||
|
tmux.send_keys "unset 'commands[perl]'", :Enter
|
||||||
|
tmux.prepare
|
||||||
|
# Verify perl is actually unset (0 = not found)
|
||||||
|
tmux.send_keys 'echo ${+commands[perl]}', :Enter
|
||||||
|
tmux.until { |lines| assert_equal '0', lines[-1] }
|
||||||
|
tmux.prepare
|
||||||
|
instance_eval(&block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def prepare_ctrl_r_test
|
||||||
|
tmux.send_keys ':', :Enter
|
||||||
|
tmux.send_keys 'echo match-collision', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'echo "line 1', :Enter, '2 line 2"', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'echo "foo', :Enter, 'bar"', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'echo "bar', :Enter, 'foo"', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'echo "trailing_space "', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'cat <<EOF | wc -c', :Enter, 'qux thud', :Enter, 'EOF', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'C-l', 'C-r'
|
||||||
|
end
|
||||||
|
|
||||||
|
test_perl_and_awk 'ctrl_r_accept_or_print_query' do
|
||||||
|
set_var('FZF_CTRL_R_OPTS', '--bind enter:accept-or-print-query')
|
||||||
|
prepare_ctrl_r_test
|
||||||
|
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||||
|
tmux.send_keys '1 foobar'
|
||||||
|
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert_equal '1 foobar', lines[-1] }
|
||||||
|
end
|
||||||
|
|
||||||
|
test_perl_and_awk 'ctrl_r_multiline_index_collision' do
|
||||||
|
# Leading number in multi-line history content is not confused with index
|
||||||
|
prepare_ctrl_r_test
|
||||||
|
tmux.send_keys "'line 1"
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal ['echo "line 1', '2 line 2"'], lines[-2..]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test_perl_and_awk 'ctrl_r_multi_selection' do
|
||||||
|
prepare_ctrl_r_test
|
||||||
|
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||||
|
tmux.send_keys :BTab, :BTab, :BTab
|
||||||
|
tmux.until { |lines| assert_includes lines[-2], '(3)' }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal ['cat <<EOF | wc -c', 'qux thud', 'EOF', 'echo "trailing_space "', 'echo "bar', 'foo"'], lines[-6..]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test_perl_and_awk 'ctrl_r_no_multi_selection' do
|
||||||
|
set_var('FZF_CTRL_R_OPTS', '--no-multi')
|
||||||
|
prepare_ctrl_r_test
|
||||||
|
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||||
|
tmux.send_keys :BTab, :BTab, :BTab
|
||||||
|
tmux.until { |lines| refute_includes lines[-2], '(3)' }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal ['cat <<EOF | wc -c', 'qux thud', 'EOF'], lines[-3..]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# NOTE: 'Perl/$history' won't see foreign cmds immediately, unlike 'awk/fc'.
|
||||||
|
# Perl passes only because another cmd runs between mocking and triggering C-r
|
||||||
|
# https://github.com/junegunn/fzf/issues/4061
|
||||||
|
# https://zsh.org/mla/users/2024/msg00692.html
|
||||||
|
test_perl_and_awk 'ctrl_r_foreign_commands' do
|
||||||
|
histfile = "#{tempname}-foreign-hist"
|
||||||
|
tmux.send_keys "HISTFILE=#{histfile}", :Enter
|
||||||
|
tmux.prepare
|
||||||
|
# SHARE_HISTORY picks up foreign commands; marked with * in fc
|
||||||
|
tmux.send_keys 'setopt SHARE_HISTORY', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'fzf_cmd_local', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
# Mock foreign command (for testing only; don't edit your HISTFILE this way)
|
||||||
|
tmux.send_keys "echo ': 0:0;fzf_cmd_foreign' >> $HISTFILE", :Enter
|
||||||
|
tmux.prepare
|
||||||
|
# Verify fc shows foreign command with asterisk
|
||||||
|
tmux.send_keys 'fc -rl -1', :Enter
|
||||||
|
tmux.until { |lines| assert lines.any? { |l| l.match?(/^\s*\d+\* fzf_cmd_foreign/) } }
|
||||||
|
tmux.prepare
|
||||||
|
# Test ctrl-r correctly extracts the foreign command
|
||||||
|
tmux.send_keys 'C-r'
|
||||||
|
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||||
|
tmux.send_keys '^fzf_cmd_'
|
||||||
|
tmux.until { |lines| assert_equal 2, lines.match_count }
|
||||||
|
tmux.send_keys :BTab, :BTab
|
||||||
|
tmux.until { |lines| assert_includes lines[-2], '(2)' }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal ['fzf_cmd_foreign', 'fzf_cmd_local'], lines[-2..]
|
||||||
|
end
|
||||||
|
ensure
|
||||||
|
FileUtils.rm_f(histfile)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class TestFish < TestBase
|
class TestFish < TestBase
|
||||||
|
|||||||
Reference in New Issue
Block a user