Compare commits

...

14 Commits

Author SHA1 Message Date
Junegunn Choi
d226d841a1 0.62.0 2025-05-04 18:31:18 +09:00
Junegunn Choi
c6d83047e5 Allow whitespace as separator in --color option 2025-05-04 15:08:23 +09:00
Junegunn Choi
46dabccdf1 [vim] Update g:fzf_colors example with 'query' 2025-05-04 14:52:22 +09:00
Junegunn Choi
cd9517b679 Add 'alt-bg' color for striped lines (#4370)
Test cases:

1. 'jump' should show alternating background colors even when 'alt-bg' is
not defined as before.

  go run main.go --bind load:jump

Two differences:
  * The alternating lines will not be in bold (was a bug)
  * The marker column will not be rendered with alternating background color

2. Use alternating background color when 'alt-bg' is set

  go run main.go --color bg:238,alt-bg:237
  go run main.go --color bg:238,alt-bg:237 --highlight-line

3. 'selected-bg' should take precedence

  go run main.go --color bg:238,alt-bg:237,selected-bg:232 \
                 --highlight-line --multi --bind 'load:select+up+select+up'

4. Should work with text with ANSI colors

  declare -f | perl -0777 -pe 's/^}\n/}\0/gm' |
    bat --plain --language bash --color always |
    go run main.go --read0 --ansi --reverse --multi \
                   --color bg:237,alt-bg:238,current-bg:236 --highlight-line

---

Close #4354
Fix #4372
2025-05-04 14:32:06 +09:00
junegunn
cd6677ba1d Deploying to master from @ junegunn/fzf@9c1a47acf7 🚀 2025-05-04 00:02:25 +00:00
bitraid
9c1a47acf7 [fish] Support deleting history items with SHIFT-DEL
Bind to SHIFT-DELETE a command that deletes the selected history items.
It can be overridden by $FZF_CTRL_R_OPTS.
2025-04-28 00:27:51 +09:00
bitraid
0c280a3ce1 [fish] Simplify commandline call in fzf-file-widget 2025-04-28 00:27:51 +09:00
bitraid
53e8b6e705 [fish] Add version check 2025-04-28 00:27:51 +09:00
bitraid
ad33165fa7 [fish] History: Operate only on line at cursor
This allows inserting history entries when constructing multiline
commands.
2025-04-28 00:27:51 +09:00
junegunn
2055db61c8 Deploying to master from @ junegunn/fzf@d2c662e54f 🚀 2025-04-27 00:02:22 +00:00
Junegunn Choi
d2c662e54f Reset coordinator delay on 'reload'
Fix #4364
2025-04-25 21:30:25 +09:00
Junegunn Choi
d24b58ef3f 0.61.3 2025-04-22 20:53:23 +09:00
RafaelDominiquini
06ae9b0f3b Add missing environment variables (#4356)
Co-authored-by: Rafael Baboni Dominiquini <rafaeldominiquini@gmail.com>
Co-authored-by: Junegunn Choi <junegunn.c@gmail.com>
2025-04-22 20:51:43 +09:00
Junegunn Choi
2a9c1c06a4 Revert "Disable tmux popup when already running inside one (#4351)"
This reverts commit af8fe918d8.

Fix #4360
Fix #4359
2025-04-22 20:20:21 +09:00
18 changed files with 172 additions and 63 deletions

View File

@@ -1,6 +1,43 @@
CHANGELOG CHANGELOG
========= =========
0.62.0
------
- Relaxed the `--color` option syntax to allow whitespace-separated entries (in addition to commas), making multi-line definitions easier to write and read
```sh
# seoul256-light
fzf --style full --color='
fg:#616161 fg+:#616161
bg:#ffffff bg+:#e9e9e9 alt-bg:#f1f1f1
hl:#719872 hl+:#719899
pointer:#e12672 marker:#e17899
header:#719872
spinner:#719899 info:#727100
prompt:#0099bd query:#616161
border:#e1e1e1
'
```
- Added `alt-bg` color to create striped lines to visually separate rows
```sh
fzf --color bg:237,alt-bg:238,current-bg:236 --highlight-line
declare -f | perl -0777 -pe 's/^}\n/}\0/gm' |
bat --plain --language bash --color always |
fzf --read0 --ansi --reverse --multi \
--color bg:237,alt-bg:238,current-bg:236 --highlight-line
```
- [fish] Improvements in CTRL-R binding (@bitraid)
- You can trigger CTRL-R in the middle of a command to insert the selected item
- You can delete history items with SHIFT-DEL
- Bug fixes and improvements
- Fixed unnecessary 100ms delay after `reload` (#4364)
- Fixed `selected-bg` not applied to colored items (#4372)
0.61.3
------
- Reverted #4351 as it caused `tmux run-shell 'fzf --tmux'` to fail (#4559 #4560)
- More environment variables for child processes (#4356)
0.61.2 0.61.2
------ ------
- Fixed panic when using header border without pointer/marker (@phanen) - Fixed panic when using header border without pointer/marker (@phanen)

View File

@@ -155,6 +155,7 @@ let g:fzf_layout = { 'window': '10new' }
let g:fzf_colors = let g:fzf_colors =
\ { 'fg': ['fg', 'Normal'], \ { 'fg': ['fg', 'Normal'],
\ 'bg': ['bg', 'Normal'], \ 'bg': ['bg', 'Normal'],
\ 'query': ['fg', 'Normal'],
\ 'hl': ['fg', 'Comment'], \ 'hl': ['fg', 'Comment'],
\ 'fg+': ['fg', 'CursorLine', 'CursorColumn', 'Normal'], \ 'fg+': ['fg', 'CursorLine', 'CursorColumn', 'Normal'],
\ 'bg+': ['bg', 'CursorLine', 'CursorColumn'], \ 'bg+': ['bg', 'CursorLine', 'CursorColumn'],

File diff suppressed because one or more lines are too long

View File

@@ -2,7 +2,7 @@
set -u set -u
version=0.61.2 version=0.62.0
auto_completion= auto_completion=
key_bindings= key_bindings=
update_config=2 update_config=2

View File

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

View File

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

View File

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf\-tmux 1 "Apr 2025" "fzf 0.61.2" "fzf\-tmux - open fzf in tmux split pane" .TH fzf\-tmux 1 "May 2025" "fzf 0.62.0" "fzf\-tmux - open fzf in tmux split pane"
.SH NAME .SH NAME
fzf\-tmux - open fzf in tmux split pane fzf\-tmux - open fzf in tmux split pane

View File

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf 1 "Apr 2025" "fzf 0.61.2" "fzf - a command-line fuzzy finder" .TH fzf 1 "May 2025" "fzf 0.62.0" "fzf - a command-line fuzzy finder"
.SH NAME .SH NAME
fzf - a command-line fuzzy finder fzf - a command-line fuzzy finder
@@ -242,7 +242,7 @@ Apply a style preset [default|minimal|full[:BORDER_STYLE]]
.TP .TP
.BI "\-\-color=" "[BASE_SCHEME][,COLOR_NAME[:ANSI_COLOR][:ANSI_ATTRIBUTES]]..." .BI "\-\-color=" "[BASE_SCHEME][,COLOR_NAME[:ANSI_COLOR][:ANSI_ATTRIBUTES]]..."
Color configuration. The name of the base color scheme is followed by custom Color configuration. The name of the base color scheme is followed by custom
color mappings. color mappings. Each entry is separated by a comma and/or whitespaces.
.RS .RS
.B BASE SCHEME: .B BASE SCHEME:
@@ -270,6 +270,7 @@ color mappings.
\fBcurrent\-bg (bg+) \fRBackground (current line) \fBcurrent\-bg (bg+) \fRBackground (current line)
\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
\fBquery (input\-fg) \fRQuery string \fBquery (input\-fg) \fRQuery string
\fBdisabled \fRQuery string when search is disabled (\fB\-\-disabled\fR) \fBdisabled \fRQuery string when search is disabled (\fB\-\-disabled\fR)
\fBinfo \fRInfo line (match counters) \fBinfo \fRInfo line (match counters)
@@ -337,7 +338,19 @@ color mappings.
# Seoul256 theme with 24-bit colors # Seoul256 theme with 24-bit colors
fzf \-\-color='bg:#4B4B4B,bg+:#3F3F3F,info:#BDBB72,border:#6B6B6B,spinner:#98BC99' \\ fzf \-\-color='bg:#4B4B4B,bg+:#3F3F3F,info:#BDBB72,border:#6B6B6B,spinner:#98BC99' \\
\-\-color='hl:#719872,fg:#D9D9D9,header:#719872,fg+:#D9D9D9' \\ \-\-color='hl:#719872,fg:#D9D9D9,header:#719872,fg+:#D9D9D9' \\
\-\-color='pointer:#E12672,marker:#E17899,prompt:#98BEDE,hl+:#98BC99'\fR \-\-color='pointer:#E12672,marker:#E17899,prompt:#98BEDE,hl+:#98BC99'
# Seoul256 light theme with 24-bit colors, each entry separated by whitespaces
fzf \-\-style full \-\-color='
fg:#616161 fg+:#616161
bg:#ffffff bg+:#e9e9e9 alt-bg:#f1f1f1
hl:#719872 hl+:#719899
pointer:#e12672 marker:#e17899
header:#719872
spinner:#719899 info:#727100
prompt:#0099bd query:#616161
border:#e1e1e1
'\fR
.RE .RE
.TP .TP
.B "\-\-no\-color" .B "\-\-no\-color"
@@ -1279,10 +1292,20 @@ fzf exports the following environment variables to its child processes.
.br .br
.BR FZF_PROMPT " Prompt string" .BR FZF_PROMPT " Prompt string"
.br .br
.BR FZF_GHOST " Ghost string"
.br
.BR FZF_POINTER " Pointer string"
.br
.BR FZF_PREVIEW_LABEL " Preview label string" .BR FZF_PREVIEW_LABEL " Preview label string"
.br .br
.BR FZF_BORDER_LABEL " Border label string" .BR FZF_BORDER_LABEL " Border label string"
.br .br
.BR FZF_LIST_LABEL " List label string"
.br
.BR FZF_INPUT_LABEL " Input label string"
.br
.BR FZF_HEADER_LABEL " Header label string"
.br
.BR FZF_ACTION " The name of the last action performed" .BR FZF_ACTION " The name of the last action performed"
.br .br
.BR FZF_KEY " The name of the last key pressed" .BR FZF_KEY " The name of the last key pressed"

View File

@@ -14,12 +14,21 @@
# Key bindings # Key bindings
# ------------ # ------------
# For compatibility with fish versions down to 3.1.2, the script does not use: # The oldest supported fish version is 3.1b1. To maintain compatibility, the
# - The -f/--function switch of command: set # command substitution syntax $(cmd) should never be used, even behind a version
# - The process substitution syntax: $(cmd) # check, otherwise the source command will fail on fish versions older than 3.4.0.
# - Ranges that omit start/end indexes: $var[$start..] $var[..$end] $var[..]
function fzf_key_bindings function fzf_key_bindings
# Check fish version
set -l fish_ver (string match -r '^(\d+).(\d+)' $version 2> /dev/null; or echo 0\n0\n0)
if test \( "$fish_ver[2]" -lt 3 \) -o \( "$fish_ver[2]" -eq 3 -a "$fish_ver[3]" -lt 1 \)
echo "This script requires fish version 3.1b1 or newer." >&2
return 1
else if not type -q fzf
echo "fzf was not found in path." >&2
return 1
end
function __fzf_defaults function __fzf_defaults
# $argv[1]: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS # $argv[1]: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
# $argv[2..]: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS # $argv[2..]: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
@@ -135,22 +144,21 @@ function fzf_key_bindings
set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND" set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND"
set -lx FZF_DEFAULT_OPTS_FILE set -lx FZF_DEFAULT_OPTS_FILE
if set -l result (eval (__fzfcmd) --walker-root=$dir --query=$fzf_query | string split0) set -l result (eval (__fzfcmd) --walker-root=$dir --query=$fzf_query | string split0)
# Remove last token from commandline. and commandline -rt -- (string join -- ' ' $prefix(string escape -- $result))' '
commandline -t ''
for i in $result
commandline -it -- $prefix(string escape -- $i)' '
end
end
commandline -f repaint commandline -f repaint
end end
function fzf-history-widget -d "Show command history" function fzf-history-widget -d "Show command history"
set -l fzf_query (commandline | string escape) set -l -- command_line (commandline)
set -l -- current_line (commandline -L)
set -l -- total_lines (count $command_line)
set -l -- fzf_query (string escape -- $command_line[$current_line])
set -lx FZF_DEFAULT_OPTS (__fzf_defaults '' \ set -lx FZF_DEFAULT_OPTS (__fzf_defaults '' \
'--nth=2..,.. --scheme=history --multi --wrap-sign="\t↳ "' \ '--nth=2..,.. --scheme=history --multi --wrap-sign="\t↳ "' \
'--bind=\'shift-delete:execute-silent(eval history delete --exact --case-sensitive -- (string escape -n -- {+} | string replace -r -a "^\d*\\\\\\t|(?<=\\\\\\n)\\\\\\t" ""))+reload(eval $FZF_DEFAULT_COMMAND)\'' \
"--bind=ctrl-r:toggle-sort --highlight-line $FZF_CTRL_R_OPTS" \ "--bind=ctrl-r:toggle-sort --highlight-line $FZF_CTRL_R_OPTS" \
'--accept-nth=2.. --read0 --print0 --with-shell='(status fish-path)\\ -c) '--accept-nth=2.. --read0 --print0 --with-shell='(status fish-path)\\ -c)
@@ -172,9 +180,13 @@ function fzf_key_bindings
test -z "$fish_private_mode"; and builtin history merge test -z "$fish_private_mode"; and builtin history merge
if set -l result (eval $FZF_DEFAULT_COMMAND \| (__fzfcmd) --query=$fzf_query | string split0) if set -l result (eval $FZF_DEFAULT_COMMAND \| (__fzfcmd) --query=$fzf_query | string split0)
commandline -- (string replace -a -- \n\t \n $result[1]) if test "$total_lines" -eq 1
test (count $result) -gt 1; and for i in $result[2..-1] commandline -- (string replace -a -- \n\t \n $result)
commandline -i -- (string replace -a -- \n\t \n \n$i) else
set -l a (math $current_line - 1)
set -l b (math $current_line + 1)
commandline -- $command_line[1..$a] (string replace -a -- \n\t \n $result)
commandline -a -- '' $command_line[$b..-1]
end end
end end

View File

@@ -39,7 +39,7 @@ func (r revision) compatible(other revision) bool {
// Run starts fzf // Run starts fzf
func Run(opts *Options) (int, error) { func Run(opts *Options) (int, error) {
if opts.Filter == nil { if opts.Filter == nil {
if opts.Tmux != nil && len(os.Getenv("TMUX")) > 0 && len(os.Getenv("TMUX_PANE")) > 0 && opts.Tmux.index >= opts.Height.index { if opts.Tmux != nil && len(os.Getenv("TMUX")) > 0 && opts.Tmux.index >= opts.Height.index {
return runTmux(os.Args, opts) return runTmux(os.Args, opts)
} }
@@ -295,6 +295,7 @@ func Run(opts *Options) (int, error) {
// Event coordination // Event coordination
reading := true reading := true
ticks := 0 ticks := 0
startTick := 0
var nextCommand *commandSpec var nextCommand *commandSpec
var nextEnviron []string var nextEnviron []string
eventBox.Watch(EvtReadNew) eventBox.Watch(EvtReadNew)
@@ -321,6 +322,7 @@ func Run(opts *Options) (int, error) {
clearDenylist() clearDenylist()
} }
reading = true reading = true
startTick = ticks
chunkList.Clear() chunkList.Clear()
itemIndex = 0 itemIndex = 0
inputRevision.bumpMajor() inputRevision.bumpMajor()
@@ -509,7 +511,7 @@ func Run(opts *Options) (int, error) {
} }
if delay && reading { if delay && reading {
dur := util.DurWithin( dur := util.DurWithin(
time.Duration(ticks)*coordinatorDelayStep, time.Duration(ticks-startTick)*coordinatorDelayStep,
0, coordinatorDelayMax) 0, coordinatorDelayMax)
time.Sleep(dur) time.Sleep(dur)
} }

View File

@@ -1184,7 +1184,12 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro
var err error var err error
theme := dupeTheme(defaultTheme) theme := dupeTheme(defaultTheme)
rrggbb := regexp.MustCompile("^#[0-9a-fA-F]{6}$") rrggbb := regexp.MustCompile("^#[0-9a-fA-F]{6}$")
for _, str := range strings.Split(strings.ToLower(str), ",") { comma := regexp.MustCompile(`[\s,]+`)
for _, str := range comma.Split(strings.ToLower(str), -1) {
str = strings.TrimSpace(str)
if len(str) == 0 {
continue
}
switch str { switch str {
case "dark": case "dark":
theme = dupeTheme(tui.Dark256) theme = dupeTheme(tui.Dark256)
@@ -1295,6 +1300,8 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro
mergeAttr(&theme.Current) mergeAttr(&theme.Current)
case "current-bg", "bg+": case "current-bg", "bg+":
mergeAttr(&theme.DarkBg) mergeAttr(&theme.DarkBg)
case "alt-bg":
mergeAttr(&theme.AltBg)
case "selected-fg": case "selected-fg":
mergeAttr(&theme.SelectedFg) mergeAttr(&theme.SelectedFg)
case "selected-bg": case "selected-bg":

View File

@@ -333,7 +333,7 @@ func TestColorSpec(t *testing.T) {
t.Errorf("colors should now be equivalent: %v, %v", tui.Dark256, customized) t.Errorf("colors should now be equivalent: %v, %v", tui.Dark256, customized)
} }
customized, _ = parseTheme(theme, "fg:231,dark,bg:232") customized, _ = parseTheme(theme, "fg:231,dark bg:232")
if customized.Fg != tui.Dark256.Fg || customized.Bg == tui.Dark256.Bg { if customized.Fg != tui.Dark256.Fg || customized.Bg == tui.Dark256.Bg {
t.Errorf("color not customized") t.Errorf("color not customized")
} }

View File

@@ -98,8 +98,7 @@ func runProxy(commandPrefix string, cmdBuilder func(temp string, needBash bool)
validIdentifier := regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`) validIdentifier := regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
for _, pairStr := range os.Environ() { for _, pairStr := range os.Environ() {
pair := strings.SplitN(pairStr, "=", 2) pair := strings.SplitN(pairStr, "=", 2)
// TMUX_PANE is never set inside a tmux popup, and should not be set so as to not be detected as a regular tmux pane if validIdentifier.MatchString(pair[0]) {
if validIdentifier.MatchString(pair[0]) && pair[0] != "TMUX_PANE" {
exports = append(exports, fmt.Sprintf("export %s=%s", pair[0], escapeSingleQuote(pair[1]))) exports = append(exports, fmt.Sprintf("export %s=%s", pair[0], escapeSingleQuote(pair[1])))
} else if strings.HasPrefix(pair[0], "BASH_FUNC_") && strings.HasSuffix(pair[0], "%%") { } else if strings.HasPrefix(pair[0], "BASH_FUNC_") && strings.HasSuffix(pair[0], "%%") {
name := pair[0][10 : len(pair[0])-2] name := pair[0][10 : len(pair[0])-2]

View File

@@ -119,7 +119,7 @@ func minRank() Result {
return Result{item: &minItem, points: [4]uint16{math.MaxUint16, 0, 0, 0}} return Result{item: &minItem, points: [4]uint16{math.MaxUint16, 0, 0, 0}}
} }
func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, theme *tui.ColorTheme, colBase tui.ColorPair, colMatch tui.ColorPair, attrNth tui.Attr, current bool) []colorOffset { func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, theme *tui.ColorTheme, colBase tui.ColorPair, colMatch tui.ColorPair, attrNth tui.Attr) []colorOffset {
itemColors := result.item.Colors() itemColors := result.item.Colors()
// No ANSI codes // No ANSI codes
@@ -182,18 +182,10 @@ func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, t
fg := ansi.color.fg fg := ansi.color.fg
bg := ansi.color.bg bg := ansi.color.bg
if fg == -1 { if fg == -1 {
if current { fg = colBase.Fg()
fg = theme.Current.Color
} else {
fg = theme.Fg.Color
}
} }
if bg == -1 { if bg == -1 {
if current { bg = colBase.Bg()
bg = theme.DarkBg.Color
} else {
bg = theme.Bg.Color
}
} }
return tui.NewColorPair(fg, bg, ansi.color.attr).MergeAttr(base) return tui.NewColorPair(fg, bg, ansi.color.attr).MergeAttr(base)
} }

View File

@@ -131,7 +131,7 @@ func TestColorOffset(t *testing.T) {
colBase := tui.NewColorPair(89, 189, tui.AttrUndefined) colBase := tui.NewColorPair(89, 189, tui.AttrUndefined)
colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined) colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined)
colors := item.colorOffsets(offsets, nil, tui.Dark256, colBase, colMatch, tui.AttrUndefined, true) colors := item.colorOffsets(offsets, nil, tui.Dark256, colBase, colMatch, tui.AttrUndefined)
assert := func(idx int, b int32, e int32, c tui.ColorPair) { assert := func(idx int, b int32, e int32, c tui.ColorPair) {
o := colors[idx] o := colors[idx]
if o.offset[0] != b || o.offset[1] != e || o.color != c { if o.offset[0] != b || o.offset[1] != e || o.color != c {
@@ -158,7 +158,7 @@ func TestColorOffset(t *testing.T) {
nthOffsets := []Offset{{37, 39}, {42, 45}} nthOffsets := []Offset{{37, 39}, {42, 45}}
for _, attr := range []tui.Attr{tui.AttrRegular, tui.StrikeThrough} { for _, attr := range []tui.Attr{tui.AttrRegular, tui.StrikeThrough} {
colors = item.colorOffsets(offsets, nthOffsets, tui.Dark256, colRegular, colUnderline, attr, true) colors = item.colorOffsets(offsets, nthOffsets, tui.Dark256, colRegular, colUnderline, attr)
// [{[0 5] {1 5 0}} {[5 15] {1 5 8}} {[15 20] {1 5 0}} // [{[0 5] {1 5 0}} {[5 15] {1 5 8}} {[15 20] {1 5 0}}
// {[22 25] {2 6 1}} {[25 27] {2 6 9}} {[27 30] {-1 -1 8}} // {[22 25] {2 6 1}} {[25 27] {2 6 9}} {[27 30] {-1 -1 8}}

View File

@@ -1091,9 +1091,13 @@ func (t *Terminal) environImpl(forPreview bool) []string {
env = append(env, "FZF_ACTION="+t.lastAction.Name()) env = append(env, "FZF_ACTION="+t.lastAction.Name())
env = append(env, "FZF_KEY="+t.lastKey) env = append(env, "FZF_KEY="+t.lastKey)
env = append(env, "FZF_PROMPT="+string(t.promptString)) env = append(env, "FZF_PROMPT="+string(t.promptString))
env = append(env, "FZF_GHOST="+string(t.ghost))
env = append(env, "FZF_POINTER="+string(t.pointer))
env = append(env, "FZF_PREVIEW_LABEL="+t.previewLabelOpts.label) env = append(env, "FZF_PREVIEW_LABEL="+t.previewLabelOpts.label)
env = append(env, "FZF_BORDER_LABEL="+t.borderLabelOpts.label) env = append(env, "FZF_BORDER_LABEL="+t.borderLabelOpts.label)
env = append(env, "FZF_LIST_LABEL="+t.listLabelOpts.label) env = append(env, "FZF_LIST_LABEL="+t.listLabelOpts.label)
env = append(env, "FZF_INPUT_LABEL="+t.inputLabelOpts.label)
env = append(env, "FZF_HEADER_LABEL="+t.headerLabelOpts.label)
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))
} }
@@ -1254,7 +1258,7 @@ func (t *Terminal) ansiLabelPrinter(str string, color *tui.ColorPair, fill bool)
printFn := func(window tui.Window, limit int) { printFn := func(window tui.Window, limit int) {
if offsets == nil { if offsets == nil {
// tui.Col* are not initialized until renderer.Init() // tui.Col* are not initialized until renderer.Init()
offsets = result.colorOffsets(nil, nil, t.theme, *color, *color, t.nthAttr, false) offsets = result.colorOffsets(nil, nil, t.theme, *color, *color, t.nthAttr)
} }
for limit > 0 { for limit > 0 {
if length > limit { if length > limit {
@@ -2787,17 +2791,28 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
_, selected := t.selected[item.Index()] _, selected := t.selected[item.Index()]
label := "" label := ""
extraWidth := 0 extraWidth := 0
alt := false
altBg := t.theme.AltBg
selectedBg := selected && t.theme.SelectedBg != t.theme.ListBg
if t.jumping != jumpDisabled { if t.jumping != jumpDisabled {
if index < len(t.jumpLabels) { if index < len(t.jumpLabels) {
// Striped // Striped
current = index%2 == 0 if !altBg.IsColorDefined() {
altBg = t.theme.DarkBg
alt = index%2 == 0
} else {
alt = index%2 == 1
}
label = t.jumpLabels[index:index+1] + strings.Repeat(" ", util.Max(0, t.pointerLen-1)) label = t.jumpLabels[index:index+1] + strings.Repeat(" ", util.Max(0, t.pointerLen-1))
if t.pointerLen == 0 { if t.pointerLen == 0 {
extraWidth = 1 extraWidth = 1
} }
} }
} else if current { } else {
label = t.pointer if current {
label = t.pointer
}
alt = !selectedBg && altBg.IsColorDefined() && index%2 == 1
} }
// Avoid unnecessary redraw // Avoid unnecessary redraw
@@ -2824,10 +2839,12 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1) maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1)
postTask := func(lineNum int, width int, wrapped bool, forceRedraw bool) { postTask := func(lineNum int, width int, wrapped bool, forceRedraw bool) {
width += extraWidth width += extraWidth
if (current || selected) && t.highlightLine { if (current || selected || alt) && t.highlightLine {
color := tui.ColSelected color := tui.ColSelected
if current { if current {
color = tui.ColCurrent color = tui.ColCurrent
} else if alt {
color = color.WithBg(altBg)
} }
fillSpaces := maxWidth - width fillSpaces := maxWidth - width
if wrapped { if wrapped {
@@ -2925,6 +2942,10 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
base = tui.ColNormal base = tui.ColNormal
match = tui.ColMatch match = tui.ColMatch
} }
if alt {
base = base.WithBg(altBg)
match = match.WithBg(altBg)
}
finalLineNum = t.printHighlighted(result, base, match, false, true, line, maxLine, forceRedraw, preTask, postTask) finalLineNum = t.printHighlighted(result, base, match, false, true, line, maxLine, forceRedraw, preTask, postTask)
} }
for i := 0; i < t.gap && finalLineNum < maxLine; i++ { for i := 0; i < t.gap && finalLineNum < maxLine; i++ {
@@ -3023,7 +3044,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
sort.Sort(ByOrder(nthOffsets)) sort.Sort(ByOrder(nthOffsets))
} }
} }
allOffsets := result.colorOffsets(charOffsets, nthOffsets, t.theme, colBase, colMatch, t.nthAttr, current) allOffsets := result.colorOffsets(charOffsets, nthOffsets, t.theme, colBase, colMatch, t.nthAttr)
maxLines := 1 maxLines := 1
if t.canSpanMultiLines() { if t.canSpanMultiLines() {

View File

@@ -308,6 +308,12 @@ func (p ColorPair) WithAttr(attr Attr) ColorPair {
return dup return dup
} }
func (p ColorPair) WithBg(bg ColorAttr) ColorPair {
dup := p
bgPair := ColorPair{colUndefined, bg.Color, bg.Attr}
return dup.Merge(bgPair)
}
func (p ColorPair) MergeAttr(other ColorPair) ColorPair { func (p ColorPair) MergeAttr(other ColorPair) ColorPair {
return p.WithAttr(other.attr) return p.WithAttr(other.attr)
} }
@@ -328,6 +334,7 @@ type ColorTheme struct {
Bg ColorAttr Bg ColorAttr
ListFg ColorAttr ListFg ColorAttr
ListBg ColorAttr ListBg ColorAttr
AltBg ColorAttr
Nth ColorAttr Nth ColorAttr
SelectedFg ColorAttr SelectedFg ColorAttr
SelectedBg ColorAttr SelectedBg ColorAttr
@@ -735,6 +742,7 @@ func EmptyTheme() *ColorTheme {
Bg: ColorAttr{colUndefined, AttrUndefined}, Bg: ColorAttr{colUndefined, AttrUndefined},
ListFg: ColorAttr{colUndefined, AttrUndefined}, ListFg: ColorAttr{colUndefined, AttrUndefined},
ListBg: ColorAttr{colUndefined, AttrUndefined}, ListBg: ColorAttr{colUndefined, AttrUndefined},
AltBg: ColorAttr{colUndefined, AttrUndefined},
SelectedFg: ColorAttr{colUndefined, AttrUndefined}, SelectedFg: ColorAttr{colUndefined, AttrUndefined},
SelectedBg: ColorAttr{colUndefined, AttrUndefined}, SelectedBg: ColorAttr{colUndefined, AttrUndefined},
SelectedMatch: ColorAttr{colUndefined, AttrUndefined}, SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
@@ -780,6 +788,7 @@ func NoColorTheme() *ColorTheme {
Bg: ColorAttr{colDefault, AttrUndefined}, Bg: ColorAttr{colDefault, AttrUndefined},
ListFg: ColorAttr{colDefault, AttrUndefined}, ListFg: ColorAttr{colDefault, AttrUndefined},
ListBg: ColorAttr{colDefault, AttrUndefined}, ListBg: ColorAttr{colDefault, AttrUndefined},
AltBg: ColorAttr{colUndefined, AttrUndefined},
SelectedFg: ColorAttr{colDefault, AttrUndefined}, SelectedFg: ColorAttr{colDefault, AttrUndefined},
SelectedBg: ColorAttr{colDefault, AttrUndefined}, SelectedBg: ColorAttr{colDefault, AttrUndefined},
SelectedMatch: ColorAttr{colDefault, AttrUndefined}, SelectedMatch: ColorAttr{colDefault, AttrUndefined},
@@ -825,6 +834,7 @@ func init() {
Bg: ColorAttr{colDefault, AttrUndefined}, Bg: ColorAttr{colDefault, AttrUndefined},
ListFg: ColorAttr{colUndefined, AttrUndefined}, ListFg: ColorAttr{colUndefined, AttrUndefined},
ListBg: ColorAttr{colUndefined, AttrUndefined}, ListBg: ColorAttr{colUndefined, AttrUndefined},
AltBg: ColorAttr{colUndefined, AttrUndefined},
SelectedFg: ColorAttr{colUndefined, AttrUndefined}, SelectedFg: ColorAttr{colUndefined, AttrUndefined},
SelectedBg: ColorAttr{colUndefined, AttrUndefined}, SelectedBg: ColorAttr{colUndefined, AttrUndefined},
SelectedMatch: ColorAttr{colUndefined, AttrUndefined}, SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
@@ -864,6 +874,7 @@ func init() {
Bg: ColorAttr{colDefault, AttrUndefined}, Bg: ColorAttr{colDefault, AttrUndefined},
ListFg: ColorAttr{colUndefined, AttrUndefined}, ListFg: ColorAttr{colUndefined, AttrUndefined},
ListBg: ColorAttr{colUndefined, AttrUndefined}, ListBg: ColorAttr{colUndefined, AttrUndefined},
AltBg: ColorAttr{colUndefined, AttrUndefined},
SelectedFg: ColorAttr{colUndefined, AttrUndefined}, SelectedFg: ColorAttr{colUndefined, AttrUndefined},
SelectedBg: ColorAttr{colUndefined, AttrUndefined}, SelectedBg: ColorAttr{colUndefined, AttrUndefined},
SelectedMatch: ColorAttr{colUndefined, AttrUndefined}, SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
@@ -903,6 +914,7 @@ func init() {
Bg: ColorAttr{colDefault, AttrUndefined}, Bg: ColorAttr{colDefault, AttrUndefined},
ListFg: ColorAttr{colUndefined, AttrUndefined}, ListFg: ColorAttr{colUndefined, AttrUndefined},
ListBg: ColorAttr{colUndefined, AttrUndefined}, ListBg: ColorAttr{colUndefined, AttrUndefined},
AltBg: ColorAttr{colUndefined, AttrUndefined},
SelectedFg: ColorAttr{colUndefined, AttrUndefined}, SelectedFg: ColorAttr{colUndefined, AttrUndefined},
SelectedBg: ColorAttr{colUndefined, AttrUndefined}, SelectedBg: ColorAttr{colUndefined, AttrUndefined},
SelectedMatch: ColorAttr{colUndefined, AttrUndefined}, SelectedMatch: ColorAttr{colUndefined, AttrUndefined},

View File

@@ -1632,14 +1632,16 @@ class TestCore < TestInteractive
end end
def test_env_vars def test_env_vars
def to_vars(lines) def env_vars
lines.select { it.start_with?('FZF_') }.to_h do return {} unless File.exist?(tempname)
key, val = it.split('=', 2)
File.readlines(tempname).select { it.start_with?('FZF_') }.to_h do
key, val = it.chomp.split('=', 2)
[key.to_sym, val] [key.to_sym, val]
end end
end end
tmux.send_keys %(seq 100 | #{FZF} --multi --reverse --preview-window up,99%,noborder --preview 'env | grep ^FZF_ | sort' --no-input --bind enter:show-input+refresh-preview,space:disable-search+refresh-preview), :Enter tmux.send_keys %(seq 100 | #{FZF} --multi --reverse --preview-window 0 --preview 'env | grep ^FZF_ | sort > #{tempname}' --no-input --bind enter:show-input+refresh-preview,space:disable-search+refresh-preview), :Enter
expected = { expected = {
FZF_TOTAL_COUNT: '100', FZF_TOTAL_COUNT: '100',
FZF_MATCH_COUNT: '100', FZF_MATCH_COUNT: '100',
@@ -1648,31 +1650,32 @@ class TestCore < TestInteractive
FZF_KEY: '', FZF_KEY: '',
FZF_POS: '1', FZF_POS: '1',
FZF_QUERY: '', FZF_QUERY: '',
FZF_PROMPT: '>', FZF_POINTER: '>',
FZF_PROMPT: '> ',
FZF_INPUT_STATE: 'hidden' FZF_INPUT_STATE: 'hidden'
} }
tmux.until do |lines| tmux.until do
assert_equal expected, to_vars(lines).slice(*expected.keys) assert_equal expected, env_vars.slice(*expected.keys)
end end
tmux.send_keys :Enter tmux.send_keys :Enter
tmux.until do |lines| tmux.until do
expected.merge!(FZF_INPUT_STATE: 'enabled', FZF_ACTION: 'show-input', FZF_KEY: 'enter') expected.merge!(FZF_INPUT_STATE: 'enabled', FZF_ACTION: 'show-input', FZF_KEY: 'enter')
assert_equal expected, to_vars(lines).slice(*expected.keys) assert_equal expected, env_vars.slice(*expected.keys)
end end
tmux.send_keys :Tab, :Tab tmux.send_keys :Tab, :Tab
tmux.until do |lines| tmux.until do
expected.merge!(FZF_ACTION: 'toggle-down', FZF_KEY: 'tab', FZF_POS: '3', FZF_SELECT_COUNT: '2') expected.merge!(FZF_ACTION: 'toggle-down', FZF_KEY: 'tab', FZF_POS: '3', FZF_SELECT_COUNT: '2')
assert_equal expected, to_vars(lines).slice(*expected.keys) assert_equal expected, env_vars.slice(*expected.keys)
end end
tmux.send_keys '99' tmux.send_keys '99'
tmux.until do |lines| tmux.until do
expected.merge!(FZF_ACTION: 'char', FZF_KEY: '9', FZF_QUERY: '99', FZF_MATCH_COUNT: '1', FZF_POS: '1') expected.merge!(FZF_ACTION: 'char', FZF_KEY: '9', FZF_QUERY: '99', FZF_MATCH_COUNT: '1', FZF_POS: '1')
assert_equal expected, to_vars(lines).slice(*expected.keys) assert_equal expected, env_vars.slice(*expected.keys)
end end
tmux.send_keys :Space tmux.send_keys :Space
tmux.until do |lines| tmux.until do
expected.merge!(FZF_INPUT_STATE: 'disabled', FZF_ACTION: 'disable-search', FZF_KEY: 'space') expected.merge!(FZF_INPUT_STATE: 'disabled', FZF_ACTION: 'disable-search', FZF_KEY: 'space')
assert_equal expected, to_vars(lines).slice(*expected.keys) assert_equal expected, env_vars.slice(*expected.keys)
end end
end end