mirror of
https://github.com/junegunn/fzf.git
synced 2025-12-05 01:30:12 +00:00
Compare commits
4 Commits
60a5be1e65
...
cfc37caabc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cfc37caabc | ||
|
|
af2a81dc02 | ||
|
|
be5a687281 | ||
|
|
771e35b972 |
@@ -106,6 +106,7 @@ Table of Contents
|
||||
* [fzf Theme Playground](#fzf-theme-playground)
|
||||
* [Related projects](#related-projects)
|
||||
* [License](#license)
|
||||
* [Goods](#goods)
|
||||
* [Sponsors :heart:](#sponsors-heart)
|
||||
|
||||
<!-- vim-markdown-toc -->
|
||||
@@ -999,6 +1000,13 @@ The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-2025 Junegunn Choi
|
||||
|
||||
Goods
|
||||
-----
|
||||
|
||||
Grab fzf T-shirts, mugs, and stickers here: https://commitgoods.com/collections/fzf
|
||||
|
||||
> _Everything in the shop is 15% off through Cyber Monday with the code `COMMITGOODSLAUNCH`_
|
||||
|
||||
Sponsors :heart:
|
||||
----------------
|
||||
|
||||
|
||||
@@ -272,6 +272,7 @@ color mappings. Each entry is separated by a comma and/or whitespaces.
|
||||
\fBgutter \fRGutter on the left
|
||||
\fBcurrent\-hl (hl+) \fRHighlighted substrings (current line)
|
||||
\fBalt\-bg \fRAlternate background color to create striped lines
|
||||
\fBalt\-gutter \fRAlternate gutter color to create the striped pattern
|
||||
\fBquery (input\-fg) \fRQuery string
|
||||
\fBghost \fRGhost text (\fB\-\-ghost\fR, \fBdim\fR applied by default)
|
||||
\fBdisabled \fRQuery string when search is disabled (\fB\-\-disabled\fR)
|
||||
|
||||
@@ -128,25 +128,52 @@ fi
|
||||
|
||||
# CTRL-R - Paste the selected command from history into the command line
|
||||
fzf-history-widget() {
|
||||
local selected
|
||||
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases noglob nobash_rematch 2> /dev/null
|
||||
local selected extracted_with_perl=0
|
||||
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
|
||||
# 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.
|
||||
if zmodload -F zsh/parameter p:{commands,history} 2>/dev/null && (( ${+commands[perl]} )); then
|
||||
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; }' |
|
||||
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))"
|
||||
extracted_with_perl=1
|
||||
else
|
||||
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))"
|
||||
fi
|
||||
local ret=$?
|
||||
local -a cmds
|
||||
# Avoid leaking auto assigned values when using backreferences '(#b)'
|
||||
local -a mbegin mend match
|
||||
if [ -n "$selected" ]; then
|
||||
if [[ $(__fzf_exec_awk '{print $1; exit}' <<< "$selected") =~ ^[1-9][0-9]* ]]; then
|
||||
zle vi-fetch-history -n $MATCH
|
||||
# Heuristic to check if the selected value is from history or a custom query
|
||||
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
|
||||
LBUFFER="$selected"
|
||||
fi
|
||||
|
||||
@@ -1476,6 +1476,8 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, *tui
|
||||
mergeAttr(&theme.Nomatch)
|
||||
case "gutter":
|
||||
mergeAttr(&theme.Gutter)
|
||||
case "alt-gutter":
|
||||
mergeAttr(&theme.AltGutter)
|
||||
case "hl":
|
||||
mergeAttr(&theme.Match)
|
||||
case "current-hl", "hl+":
|
||||
|
||||
@@ -3202,14 +3202,22 @@ func (t *Terminal) renderEmptyLine(line int, barRange [2]int) {
|
||||
t.renderBar(line, barRange)
|
||||
}
|
||||
|
||||
func (t *Terminal) gutter(current bool) {
|
||||
func (t *Terminal) gutter(current bool, alt bool) {
|
||||
var color tui.ColorPair
|
||||
if current {
|
||||
color = tui.ColCurrentCursorEmpty
|
||||
} else if !t.raw && t.gutterReverse || t.raw && t.gutterRawReverse {
|
||||
color = tui.ColCursorEmpty
|
||||
if alt {
|
||||
color = tui.ColAltCursorEmpty
|
||||
} else {
|
||||
color = tui.ColCursorEmpty
|
||||
}
|
||||
} else {
|
||||
color = tui.ColCursorEmptyChar
|
||||
if alt {
|
||||
color = tui.ColAltCursorEmptyChar
|
||||
} else {
|
||||
color = tui.ColCursorEmptyChar
|
||||
}
|
||||
}
|
||||
gutter := t.pointerEmpty
|
||||
if t.raw {
|
||||
@@ -3220,7 +3228,7 @@ func (t *Terminal) gutter(current bool) {
|
||||
|
||||
func (t *Terminal) renderGapLine(line int, barRange [2]int, drawLine bool) {
|
||||
t.move(line, 0, false)
|
||||
t.gutter(false)
|
||||
t.gutter(false, false)
|
||||
t.window.Print(t.markerEmpty)
|
||||
x := t.pointerLen + t.markerLen
|
||||
|
||||
@@ -3394,7 +3402,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
|
||||
return indentSize
|
||||
}
|
||||
if len(label) == 0 {
|
||||
t.gutter(true)
|
||||
t.gutter(true, false)
|
||||
} else {
|
||||
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
|
||||
}
|
||||
if len(label) == 0 {
|
||||
t.gutter(false)
|
||||
t.gutter(false, index%2 == 1)
|
||||
} else {
|
||||
t.window.CPrint(tui.ColCursor, label)
|
||||
}
|
||||
|
||||
@@ -456,6 +456,7 @@ type ColorTheme struct {
|
||||
PreviewBg ColorAttr
|
||||
DarkBg ColorAttr
|
||||
Gutter ColorAttr
|
||||
AltGutter ColorAttr
|
||||
Prompt ColorAttr
|
||||
InputBg ColorAttr
|
||||
InputBorder ColorAttr
|
||||
@@ -826,6 +827,8 @@ var (
|
||||
ColCursor ColorPair
|
||||
ColCursorEmpty ColorPair
|
||||
ColCursorEmptyChar ColorPair
|
||||
ColAltCursorEmpty ColorPair
|
||||
ColAltCursorEmptyChar ColorPair
|
||||
ColMarker ColorPair
|
||||
ColSelected ColorPair
|
||||
ColSelectedMatch ColorPair
|
||||
@@ -891,6 +894,7 @@ func init() {
|
||||
PreviewFg: defaultColor,
|
||||
PreviewBg: defaultColor,
|
||||
Gutter: undefined,
|
||||
AltGutter: undefined,
|
||||
PreviewBorder: defaultColor,
|
||||
PreviewScrollbar: defaultColor,
|
||||
PreviewLabel: defaultColor,
|
||||
@@ -943,6 +947,7 @@ func init() {
|
||||
PreviewFg: undefined,
|
||||
PreviewBg: undefined,
|
||||
Gutter: undefined,
|
||||
AltGutter: undefined,
|
||||
PreviewBorder: undefined,
|
||||
PreviewScrollbar: undefined,
|
||||
PreviewLabel: undefined,
|
||||
@@ -991,6 +996,7 @@ func init() {
|
||||
PreviewFg: undefined,
|
||||
PreviewBg: undefined,
|
||||
Gutter: undefined,
|
||||
AltGutter: undefined,
|
||||
PreviewBorder: undefined,
|
||||
PreviewScrollbar: undefined,
|
||||
PreviewLabel: undefined,
|
||||
@@ -1041,6 +1047,7 @@ func init() {
|
||||
PreviewFg: undefined,
|
||||
PreviewBg: undefined,
|
||||
Gutter: undefined,
|
||||
AltGutter: undefined,
|
||||
PreviewBorder: undefined,
|
||||
PreviewScrollbar: undefined,
|
||||
PreviewLabel: undefined,
|
||||
@@ -1091,6 +1098,7 @@ func init() {
|
||||
PreviewFg: undefined,
|
||||
PreviewBg: undefined,
|
||||
Gutter: undefined,
|
||||
AltGutter: undefined,
|
||||
PreviewBorder: undefined,
|
||||
PreviewScrollbar: undefined,
|
||||
PreviewLabel: undefined,
|
||||
@@ -1208,6 +1216,7 @@ func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, boldify bool, forceBlac
|
||||
gutter.Attr = Dim
|
||||
}
|
||||
theme.Gutter = o(theme.DarkBg, gutter)
|
||||
theme.AltGutter = o(theme.Gutter, theme.AltGutter)
|
||||
theme.PreviewFg = o(theme.Fg, theme.PreviewFg)
|
||||
theme.PreviewBg = o(theme.Bg, theme.PreviewBg)
|
||||
theme.PreviewLabel = o(theme.BorderLabel, theme.PreviewLabel)
|
||||
@@ -1277,6 +1286,8 @@ func initPalette(theme *ColorTheme) {
|
||||
ColCursor = pair(theme.Cursor, theme.Gutter)
|
||||
ColCursorEmpty = pair(blank, theme.Gutter)
|
||||
ColCursorEmptyChar = pair(theme.Gutter, theme.ListBg)
|
||||
ColAltCursorEmpty = pair(blank, theme.AltGutter)
|
||||
ColAltCursorEmptyChar = pair(theme.AltGutter, theme.ListBg)
|
||||
if theme.SelectedBg.Color != theme.ListBg.Color {
|
||||
ColMarker = pair(theme.Marker, theme.SelectedBg)
|
||||
} else {
|
||||
|
||||
@@ -462,6 +462,84 @@ class TestZsh < TestBase
|
||||
tmux.send_keys 'C-c'
|
||||
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
|
||||
end
|
||||
|
||||
class TestFish < TestBase
|
||||
|
||||
Reference in New Issue
Block a user