Compare commits

...

4 Commits

Author SHA1 Message Date
Junegunn Choi eae8d9d27e 0.74.1 2026-07-18 21:50:12 +09:00
Junegunn Choi e58c5e6bd8 Hide separator next to preview window at 'next' position when decidable
Decision must not depend on threshold chain resolution
(computePreviewSize consults noSeparatorLine); separator is hidden only
when every spec in the chain displays a 'next' preview window with a
border line facing the input section. Otherwise err on the side of
showing it.

- Without active previewer, preview position is resolved from first spec
  alone, so 'next' in a threshold alternative does not create input
  window and cannot hide the separator
- Empty preview command is undecidable; preview(...) action can display
  ad-hoc window at 'next' position at any time
- change-preview-window relayouts or repaints when separator visibility
  changes even if the active spec is unchanged, without dropping preview
  window forced by preview(...) action
2026-07-18 16:43:23 +09:00
Junegunn Choi 7fe4831be6 Apply facing-line rule to input border as well
Visible input border suppressed separator even when it draws no line
toward list section (bottom in default layout, top in reverse, vertical).
Suppress only when input border has a line on the list side; otherwise
fall through to adjacent section check.

- Requires header window to have content before treating it as facing
  section; hasHeaderWindow returns true with empty header when input
  border is visible
- Extract inputBorderFacesList and resolveInputBorderShape shared by
  options and terminal
- Add tests for separator suppression rules
2026-07-18 16:43:23 +09:00
Junegunn Choi 6030226835 Hide default separator when a border separates input from list
- Applies when adjacent section (header, header lines, or list) draws
  border line facing input section

    # No separator below header border
    fzf --style full --input-border none --header foo

    # Separator shown; nothing separates input from list
    fzf --style full --input-border none --header foo --no-header-border

- Evaluated dynamically so change-header/toggle-header update it
- Inline header is drawn inside list border and not adjacent to input;
  decide from section actually next to it
- Separator is kept when a preview window can be at 'next' position,
  right next to the input section
2026-07-18 16:43:23 +09:00
9 changed files with 483 additions and 49 deletions
+15
View File
@@ -3,6 +3,21 @@ CHANGELOG
0.74.1
------
- The default separator on the info line is no longer shown when the input section is already visually separated from the list section by a border line
```sh
# No separator shown below the header border
fzf --style full --input-border none --header foo
# Separator shown; no border separates the input section from the list section
fzf --style full --input-border none --header foo --no-header-border
# No separator below the border of the preview window at 'next' position
fzf --preview : --preview-window next
# Conversely, separator is now shown when the input border does not draw
# a line facing the list section
fzf --input-border bottom
```
- Rendering improvements
- Each frame is now wrapped in synchronized update mode (mode 2026) to reduce flickering on supported terminals
- Reduced rendering output by 10-23% by skipping redundant SGR sequences
+1 -1
View File
@@ -18,7 +18,7 @@ triggered by a tag push.
2. Verify file consistency, sign the tag, and push the tag.
```sh
make tag VERSION=0.74.0
make tag VERSION=0.74.1
```
`make tag` runs `prerelease` first (checks that the version
+1 -1
View File
@@ -2,7 +2,7 @@
set -u
version=0.74.0
version=0.74.1
auto_completion=
key_bindings=
update_config=2
+1 -1
View File
@@ -1,4 +1,4 @@
$version="0.74.0"
$version="0.74.1"
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
+1 -1
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
THE SOFTWARE.
..
.TH fzf\-tmux 1 "Jul 2026" "fzf 0.74.0" "fzf\-tmux - open fzf in tmux split pane"
.TH fzf\-tmux 1 "Jul 2026" "fzf 0.74.1" "fzf\-tmux - open fzf in tmux split pane"
.SH NAME
fzf\-tmux - open fzf in tmux split pane
+5 -1
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
THE SOFTWARE.
..
.TH fzf 1 "Jul 2026" "fzf 0.74.0" "fzf - a command-line fuzzy finder"
.TH fzf 1 "Jul 2026" "fzf 0.74.1" "fzf - a command-line fuzzy finder"
.SH NAME
fzf - a command-line fuzzy finder
@@ -823,6 +823,10 @@ A synonym for \fB\-\-info=hidden\fB
The given string will be repeated to form the horizontal separator on the info
line (default: '─' or '\-' depending on \fB\-\-no\-unicode\fR).
Unless explicitly specified, the separator is not displayed if the input
section is already visually separated from the list section by a border line
(e.g. \fB\-\-input\-border\fR or \fB\-\-header\-border\fR).
ANSI color codes are supported.
.TP
+4 -1
View File
@@ -3696,7 +3696,10 @@ func (opts *Options) noSeparatorLine() bool {
if opts.Inputless {
return true
}
sep := opts.Separator == nil && !opts.InputBorderShape.Visible() || opts.Separator != nil && len(*opts.Separator) > 0
// NOTE: This does not know that the default separator can be suppressed at
// runtime (see Terminal.separatorLength), so the minimum heights derived
// from it can be one line taller than strictly necessary.
sep := opts.Separator == nil && !inputBorderFacesList(opts.Layout, opts.InputBorderShape) || opts.Separator != nil && len(*opts.Separator) > 0
return noSeparatorLine(opts.InfoStyle, sep)
}
+165 -22
View File
@@ -271,6 +271,7 @@ type Terminal struct {
ghost string
separator labelPrinter
separatorLen int
separatorAuto bool
spinner []string
promptString string
prompt func()
@@ -1248,13 +1249,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
}
// Determine input border shape
if t.inputBorderShape == tui.BorderLine {
if t.layout == layoutReverse {
t.inputBorderShape = tui.BorderBottom
} else {
t.inputBorderShape = tui.BorderTop
}
}
t.inputBorderShape = resolveInputBorderShape(t.layout, t.inputBorderShape)
// Inline borders are embedded between the list's top and bottom horizontals.
// Shapes missing either one (none/phantom/line/single-sided) fall back to a plain
@@ -1299,14 +1294,16 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
}
}
// Disable separator by default if input border is set
if opts.Separator == nil && !t.inputBorderShape.Visible() || opts.Separator != nil && len(*opts.Separator) > 0 {
bar := "─"
if opts.Separator != nil {
bar = *opts.Separator
} else if !t.unicode {
bar = "-"
}
// The default separator can be suppressed at runtime depending on the
// window layout (see separatorLength)
t.separatorAuto = opts.Separator == nil
bar := "─"
if opts.Separator != nil {
bar = *opts.Separator
} else if !t.unicode {
bar = "-"
}
if len(bar) > 0 {
t.separator, t.separatorLen = t.ansiLabelPrinter(bar, &tui.ColSeparator, true)
}
@@ -1695,7 +1692,129 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) {
}
func (t *Terminal) noSeparatorLine() bool {
return t.inputless || noSeparatorLine(t.infoStyle, t.separatorLen > 0)
return t.inputless || noSeparatorLine(t.infoStyle, t.separatorLength() > 0)
}
// Effective length of the horizontal separator. The default separator is
// suppressed when the input section is already visually separated from the
// list section by a border line.
func (t *Terminal) separatorLength() int {
if t.separatorAuto && t.separatedByBorder() {
return 0
}
return t.separatorLen
}
// BorderLine for the input border resolves to a single line on the side
// facing the list section
func resolveInputBorderShape(layout layoutType, shape tui.BorderShape) tui.BorderShape {
if shape != tui.BorderLine {
return shape
}
if layout == layoutReverse {
return tui.BorderBottom
}
return tui.BorderTop
}
// Whether the input border draws a line on the side facing the list section
func inputBorderFacesList(layout layoutType, shape tui.BorderShape) bool {
shape = resolveInputBorderShape(layout, shape)
if layout == layoutReverse {
return shape.HasBottom()
}
return shape.HasTop()
}
// Whether a border line is already drawn between the info line and the
// section next to it on the list side, making the default horizontal
// separator redundant
func (t *Terminal) separatedByBorder() bool {
// The input border itself draws a line on the side facing the list section
if inputBorderFacesList(t.layout, t.inputBorderShape) {
return true
}
// A preview window at 'next' position sits right next to the input
// section, in front of the sections handled below
anyNext := false
for po := &t.previewOpts; po != nil; po = po.alternative {
if po.position == posNext {
anyNext = true
break
}
}
if anyNext && t.hasPreviewer() {
// The separator is hidden only when every spec in the threshold chain
// displays a preview window at 'next' position with a border line
// facing the input section. We cannot single out the active spec;
// which one is active depends on the terminal size, and the
// resolution (computePreviewSize in resizeWindows) itself consults
// noSeparatorLine. When not every spec qualifies, err on the side of
// showing the separator.
if len(t.previewOpts.command) == 0 {
// No preview command, so no preview window is normally shown, but
// the preview(...) action can still display one at any time
return false
}
// NOTE: resizeWindows can clear 'hidden' mid-layout when the
// preview(...) action forces the window, so the frame being laid out
// can briefly disagree with this decision until the next relayout
for po := &t.previewOpts; po != nil; po = po.alternative {
if po.position != posNext || po.hidden || po.size.size == 0 || !t.borderFacingInput(po.Border(t.layout)) {
return false
}
}
return true
}
hasHeaderLinesWindow, headerLinesShape := t.determineHeaderLinesShape()
hasHeaderWindow := t.hasHeaderWindow()
// Mirror of hasInputWindow in resizeWindows; a 'next' preview position
// gives the input section its own window even when no preview window is
// shown. A 'next' spec here implies there is no previewer (the block
// above returns otherwise), and without a previewer the position is
// resolved from the first spec alone, so a 'next' in a threshold
// alternative does not count.
hasInputWindow := t.previewOpts.position == posNext || t.inputBorderShape.Visible() || hasHeaderWindow || hasHeaderLinesWindow
if !hasInputWindow {
// The input section is embedded in the list window and no border
// separates the two
return false
}
// The input section has its own window. Determine which section it is
// facing, and check if the border of that section draws a line toward it.
// NOTE: hasHeaderWindow can be true with no header content when the input
// border is visible (see Terminal.hasHeaderWindow). An empty header window
// takes no space, so it cannot be the facing section.
hasHeaderSection := hasHeaderWindow && t.visibleHeaderLines() > 0
if hasHeaderSection && !t.headerFirst && t.headerBorderShape != tui.BorderInline {
// Header section is facing the input section. An inline header is
// excluded; it is drawn inside the list border and is covered by the
// branches below.
return t.borderFacingInput(t.headerBorderShape)
}
// Header lines section is facing the input section, except in
// reverse-list layout where it is on the other side of the list section,
// or with --header-first and no header section where it is moved past
// the input section
if hasHeaderLinesWindow && t.layout != layoutReverseList && (hasHeaderWindow || !t.headerFirst) {
return t.borderFacingInput(headerLinesShape)
}
// The input section sits right next to the list section
return t.borderFacingInput(t.listBorderShape)
}
// Whether the border of the section next to the input section on the list
// side draws a horizontal line facing the info line
func (t *Terminal) borderFacingInput(shape tui.BorderShape) bool {
if shape == tui.BorderInline {
// Embedded between the horizontal lines of the list border
return true
}
if t.layout == layoutReverse {
return shape.HasTop()
}
return shape.HasBottom()
}
func getScrollbar(perLine int, total int, height int, offset int) (int, int) {
@@ -3330,6 +3449,7 @@ func (t *Terminal) printInfoImpl() {
}
pos := 0
line := 0
separatorLen := t.separatorLength()
maxHeight := t.window.Height()
move := func(y int, x int, clear bool) bool {
if y < 0 || y >= maxHeight {
@@ -3357,7 +3477,7 @@ func (t *Terminal) printInfoImpl() {
str = string(trimmed)
width = maxWidth
}
move(line, pos, t.separatorLen == 0)
move(line, pos, separatorLen == 0)
if t.reading {
t.window.CPrint(tui.ColSpinner, str)
} else {
@@ -3366,7 +3486,7 @@ func (t *Terminal) printInfoImpl() {
pos += width
}
printSeparator := func(fillLength int, pad bool) {
if t.separatorLen > 0 {
if separatorLen > 0 {
t.separator(t.window, fillLength)
t.window.Print(" ")
} else if pad {
@@ -3375,7 +3495,7 @@ func (t *Terminal) printInfoImpl() {
}
if t.infoStyle == infoHidden {
if t.separatorLen > 0 {
if separatorLen > 0 {
if !move(line+1, 0, false) {
return
}
@@ -3438,7 +3558,7 @@ func (t *Terminal) printInfoImpl() {
}
switch t.infoStyle {
case infoDefault:
if !move(line+1, 0, t.separatorLen == 0) {
if !move(line+1, 0, separatorLen == 0) {
return
}
printSpinner()
@@ -3522,7 +3642,7 @@ func (t *Terminal) printInfoImpl() {
}
if t.infoStyle == infoInlineRight {
if t.separatorLen > 0 {
if separatorLen > 0 {
if !move(line+1, 0, false) {
return
}
@@ -8157,6 +8277,8 @@ func (t *Terminal) Loop() error {
case actChangePreviewWindow:
// NOTE: We intentionally use "previewOpts" instead of "activePreviewOpts" here
currentPreviewOpts := t.previewOpts
wasNoSeparatorLine := t.noSeparatorLine()
wasSeparatorLength := t.separatorLength()
// Reset preview options and apply the additional options
t.previewOpts = t.initialPreviewOpts
@@ -8177,8 +8299,28 @@ func (t *Terminal) Loop() error {
a.a = strings.Join(append(tokens[1:], tokens[0]), "|")
}
// Do not drop a preview window forced by the preview(...) action
keepForcedPreview := t.hasPreviewWindow() && !t.activePreviewOpts.hidden
// The default separator is decided from the whole threshold
// chain (separatedByBorder), which compare does not fully
// inspect, so it can change even when compare finds no
// layout difference
checkSeparator := func() {
if t.noSeparatorLine() != wasNoSeparatorLine {
// Number of lines changed; relayout
updatePreviewWindow(keepForcedPreview)
req(reqPreviewRefresh)
} else if t.separatorLength() != wasSeparatorLength {
// Only the content of the info line changed
req(reqInfo)
}
}
// Full redraw
switch currentPreviewOpts.compare(t.activePreviewOpts, &t.previewOpts) {
case previewOptsSame:
checkSeparator()
case previewOptsDifferentLayout:
// Preview command can be running in the background if the size of
// the preview window is 0 but not 'hidden'
@@ -8186,7 +8328,7 @@ func (t *Terminal) Loop() error {
// FIXME: One-time preview window can't reappear once hidden
// fzf --bind space:preview:ls --bind 'enter:change-preview-window:down|left|up|hidden|'
updatePreviewWindow(t.hasPreviewWindow() && !t.activePreviewOpts.hidden)
updatePreviewWindow(keepForcedPreview)
if wasHidden && t.hasPreviewWindow() {
// Restart
refreshPreview(t.previewOpts.command)
@@ -8200,6 +8342,7 @@ func (t *Terminal) Loop() error {
case previewOptsDifferentContentLayout:
t.previewed.version = 0
req(reqPreviewRefresh)
checkSeparator()
}
// Adjust scroll offset
+290 -21
View File
@@ -248,7 +248,7 @@ class TestLayout < TestInteractive
tmux.send_keys %(seq 5 | #{FZF} --layout=reverse --preview 'echo PREVIEW' --preview-window=next:3 --prompt='line2$ > '), :Enter
expected = <<~OUTPUT
line2$ >
5/5
5/5
PREVIEW
@@ -268,7 +268,7 @@ class TestLayout < TestInteractive
5/5
5/5
>
OUTPUT
tmux.until { assert_block(expected, it) }
@@ -597,7 +597,7 @@ class TestLayout < TestInteractive
2
1
header
19/97
19/97
> 1
@@ -628,7 +628,7 @@ class TestLayout < TestInteractive
hello
100/100
100/100
>
BLOCK
tmux.until { assert_block(block2, it) }
@@ -648,7 +648,7 @@ class TestLayout < TestInteractive
2
1
98/98
98/98
>
BLOCK
tmux.until { assert_block(block1, it) }
@@ -663,7 +663,7 @@ class TestLayout < TestInteractive
1
hello
98/98
98/98
>
BLOCK
tmux.until { assert_block(block2, it) }
@@ -679,7 +679,7 @@ class TestLayout < TestInteractive
4
> 3
98/98
98/98
>
2
@@ -718,7 +718,7 @@ class TestLayout < TestInteractive
2
1
98/98
98/98
>
BLOCK
tmux.until { assert_block(block1, it) }
@@ -734,7 +734,7 @@ class TestLayout < TestInteractive
hello
98/98
98/98
>
BLOCK
tmux.until { assert_block(block2, it) }
@@ -750,7 +750,7 @@ class TestLayout < TestInteractive
4
> 3
98/98
98/98
>
2
@@ -767,7 +767,7 @@ class TestLayout < TestInteractive
2
1
98/98
98/98
>
hello
@@ -810,7 +810,7 @@ class TestLayout < TestInteractive
2
1
header
19/97
19/97
> 1
@@ -825,7 +825,7 @@ class TestLayout < TestInteractive
11
> 10
list
19/97
19/97
> 1
3
@@ -1039,9 +1039,9 @@ class TestLayout < TestInteractive
103 101 103 101 102 102 2/2 103
5/5 102 2/2 102 103 101 103 101 > 2/2 101 101 >
> 103 > 103 102 102 > 102 5/5 102
5/5 103 2/2 103 101 103 > 103
5/5 103 2/2 103 101 103 > 103
> > 102 101
2/2 103 101 102
2/2 103 101 102
> 102 > 103
103
@@ -1099,9 +1099,9 @@ class TestLayout < TestInteractive
> 102 > 102 > 101 > 101 101 101 101 > 1
3/3 101 1/1 101 102 102 102 102 102
> 102 > 102 HEAD 101 HEAD HEAD HEAD 101 1/1 101
3/3 102 1/1 1/1 1/1 102 > 102 3/3 >
3/3 102 1/1 1/1 1/1 102 > 102 3/3 >
> HEAD > > > HEAD HEAD >
1/1
1/1
> 101 101
102 102
HEAD HEAD
@@ -1159,9 +1159,9 @@ class TestLayout < TestInteractive
> 102 > 102 > 101 > 101 > 3 > 3 > 3 101 3
3/3 101 1/1 101 102 102 102
> 102 > 102 HEAD 101 HEAD 101 1/1 101
3/3 102 1/1 102 > 102 3/3 >
3/3 102 1/1 102 > 102 3/3 >
> HEAD > HEAD HEAD >
1/1
1/1
> 101 101
102 102
HEAD HEAD
@@ -1191,7 +1191,7 @@ class TestLayout < TestInteractive
label
header
10/10
10/10
>
BLOCK
@@ -1228,13 +1228,282 @@ class TestLayout < TestInteractive
end
end
def test_separator_with_input_border
# Border line below input does not face the list; separator is shown
tmux.send_keys %(seq 100 | #{FZF} --input-border bottom), :Enter
block = <<~BLOCK
> 1
100/100
>
BLOCK
tmux.until { assert_block(block, it) }
teardown
setup
# List border faces the input instead; separator is hidden
tmux.send_keys %(seq 100 | #{FZF} --input-border bottom --list-border rounded), :Enter
block = <<~BLOCK
> 1
100/100
>
BLOCK
tmux.until { assert_block(block, it) }
teardown
setup
# Border line above input faces the list; separator is hidden
tmux.send_keys %(seq 100 | #{FZF} --input-border top), :Enter
block = <<~BLOCK
> 1
100/100
>
BLOCK
tmux.until { assert_block(block, it) }
end
def test_separator_with_input_border_reverse
# Border line above input does not face the list; separator is shown
tmux.send_keys %(seq 100 | #{FZF} --layout reverse --input-border top), :Enter
block = <<~BLOCK
>
100/100
> 1
BLOCK
tmux.until { assert_block(block, it) }
teardown
setup
# Border line below input faces the list; separator is hidden
tmux.send_keys %(seq 100 | #{FZF} --layout reverse --input-border bottom), :Enter
block = <<~BLOCK
>
100/100
> 1
BLOCK
tmux.until { assert_block(block, it) }
end
def test_separator_with_preview_next
# Preview window at 'next' position sits next to the input section, so the
# header border does not hide the separator
tmux.send_keys %(seq 100 | #{FZF} --header foo --header-border rounded --no-list-border --preview 'echo hi' --preview-window next,1,border-none), :Enter
block = <<~BLOCK
hi
100/100
>
BLOCK
tmux.until { assert_block(block, it) }
teardown
setup
# Same when 'next' position comes from a threshold alternative
tmux.send_keys %(seq 100 | #{FZF} --header foo --header-border rounded --no-list-border --preview 'echo hi' --preview-window 'up,40%,<9999(next,1,border-none)'), :Enter
tmux.until { assert_block(block, it) }
teardown
setup
# A border of the preview window at 'next' position facing the input
# section hides the separator
tmux.send_keys %(seq 100 | #{FZF} --preview : --preview-window next,3), :Enter
block = <<~BLOCK
100/100
>
BLOCK
tmux.until { assert_block(block, it) }
teardown
setup
# Same when every spec in the threshold chain is a bordered preview
# window at 'next' position
tmux.send_keys %(seq 100 | #{FZF} --preview : --preview-window 'next,3,<15(next,3)'), :Enter
tmux.until { assert_block(block, it) }
end
def test_separator_with_preview_next_toggle
tmux.send_keys %(seq 100 | #{FZF} --preview 'echo hi' --preview-window next,3,hidden --bind space:toggle-preview), :Enter
hidden = <<~BLOCK
> 1
100/100
>
BLOCK
tmux.until { assert_block(hidden, it) }
tmux.send_keys :Space
shown = <<~BLOCK
hi
100/100
>
BLOCK
tmux.until { assert_block(shown, it) }
tmux.send_keys :Space
tmux.until { assert_block(hidden, it) }
end
def test_separator_with_preview_next_alternative_only
# 'next' appears only in a threshold alternative and there is no
# previewer, so the input section stays embedded in the list window and
# needs the separator
tmux.send_keys %(seq 100 | #{FZF} --preview-window '<9999(next)' --list-border rounded), :Enter
block = <<~BLOCK
> 1
100/100
>
BLOCK
tmux.until { assert_block(block, it) }
end
def test_separator_with_preview_action_at_next
# preview(...) action can display an ad-hoc preview window at 'next'
# position at any time; the list border must not hide the separator
tmux.send_keys %(seq 100 | #{FZF} --list-border rounded --preview-window 'next,border-left,5' --bind 'space:preview(echo hi)'), :Enter
block = <<~BLOCK
100/100
>
BLOCK
tmux.until { assert_block(block, it) }
tmux.send_keys :Space
block = <<~BLOCK
hi
100/100
>
BLOCK
tmux.until { assert_block(block, it) }
end
def test_separator_change_preview_window_keeps_forced_preview
# Relayout triggered by the separator visibility change must not drop a
# preview window opened via the preview(...) action
tmux.send_keys %(seq 100 | #{FZF} --info hidden --input-border left --list-border --preview-window 'up,50%,<5(next)' --bind 'space:preview(echo AD-HOC)' --bind 'x:change-preview-window(up,50%,<5(up))'), :Enter
tmux.until { |lines| assert_includes lines.join, '│ ─' }
tmux.send_keys :Space
tmux.until do |lines|
assert_includes lines.join, 'AD-HOC'
assert_includes lines.join, '│ ─'
end
# Only the inactive threshold alternative changes; separator is hidden
# by the list border, and the forced preview window must survive
tmux.send_keys :x
tmux.until do |lines|
assert_includes lines.join, 'AD-HOC'
refute_includes lines.join, '│ ─'
end
end
def test_separator_with_change_preview_window_chain
# change-preview-window that only alters a non-active spec must still
# trigger a relayout when it changes the separator visibility
tmux.send_keys %(seq 100 | #{FZF} --info hidden --layout reverse --preview 'echo hi' --preview-window 'next,3,border-none,<100(next,3,border-top)' --bind 'space:change-preview-window(next,3,border-top,<100(next,3,border-top))'), :Enter
block = <<~BLOCK
>
hi
BLOCK
tmux.until { assert_block(block, it) }
tmux.send_keys :Space
block = <<~BLOCK
>
hi
BLOCK
tmux.until { assert_block(block, it) }
teardown
setup
# Also when only the drawn bar changes; the info line row count is
# constant with the default info style
tmux.send_keys %(seq 100 | #{FZF} --layout reverse --preview 'echo hi' --preview-window 'next,3,border-none,<100(next,3,border-top)' --bind 'space:change-preview-window(next,3,border-top,<100(next,3,border-top))'), :Enter
block = <<~BLOCK
>
100/100
BLOCK
tmux.until { assert_block(block, it) }
tmux.send_keys :Space
block = <<~BLOCK
>
100/100
BLOCK
tmux.until { assert_block(block, it) }
teardown
setup
# Also when compare finds a content-layout difference (scroll offset)
tmux.send_keys %(seq 100 | #{FZF} --info hidden --preview 'echo hi' --preview-window 'next,3,<2(up,3)' --bind 'space:change-preview-window(next,3,+1,<2(next,3))'), :Enter
block = <<~BLOCK
>
BLOCK
tmux.until { assert_block(block, it) }
tmux.send_keys :Space
block = <<~BLOCK
>
BLOCK
tmux.until { assert_block(block, it) }
end
def test_separator_with_inline_header_border
# Inline header is drawn inside the list border; bare header lines sit
# next to the input section, so the separator is shown
tmux.send_keys %(seq 100 | #{FZF} --list-border rounded --header foo --header-lines 2 --header-border inline --header-lines-border none), :Enter
block = <<~BLOCK
2
1
98/98
>
BLOCK
tmux.until { assert_block(block, it) }
teardown
setup
# Header lines are embedded in the list border along with the inline
# header; the list border faces the input section and hides the separator
tmux.send_keys %(seq 100 | #{FZF} --list-border rounded --header foo --header-lines 2 --header-border inline), :Enter
block = <<~BLOCK
2
1
foo
98/98
>
BLOCK
tmux.until { assert_block(block, it) }
end
def test_header_border_no_pointer_and_marker
tmux.send_keys %(seq 10 | #{FZF} --header-lines 1 --header-border sharp --no-list-border --pointer '' --marker ''), :Enter
block = <<~BLOCK
1
9/9
9/9
>
BLOCK
tmux.until { assert_block(block, it) }