From dd7a081b93eb641f524fe328d74acee65f1d88a5 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Sun, 19 Apr 2026 21:27:55 +0900 Subject: [PATCH] Let inline sections take precedence over --header-first --header-first previously was rejected with --header-border=inline or --header-lines-border=inline. Now, inline placement wins: an inline section stays inside the list frame, and --header-first only affects non-inline sections (mainly the main --header). --- man/man1/fzf.1 | 9 +++++---- src/options.go | 3 --- src/terminal.go | 6 +++++- test/test_layout.rb | 38 ++++++++++++++++++++++++++++++++++++-- 4 files changed, 46 insertions(+), 10 deletions(-) diff --git a/man/man1/fzf.1 b/man/man1/fzf.1 index fb7ffb37..4b17a48d 100644 --- a/man/man1/fzf.1 +++ b/man/man1/fzf.1 @@ -1107,9 +1107,10 @@ shape that has both top and bottom segments (rounded / sharp / bold / double / block / thinblock / horizontal) and falls back to \fBline\fR otherwise. When the list border also has side segments, the separator joins them with T-junctions; \fBhorizontal\fR has no side borders, so the -separator is drawn without T-junction endpoints. Not compatible with -\fB\-\-header\-first\fR, and when \fB\-\-header\-lines\fR is also set -\fB\-\-header\-lines\-border\fR must also be \fBinline\fR. +separator is drawn without T-junction endpoints. Takes precedence over +\fB\-\-header\-first\fR (the section stays inside the list frame), and +when \fB\-\-header\-lines\fR is also set \fB\-\-header\-lines\-border\fR +must also be \fBinline\fR. .TP .BI "\-\-header\-label" [=LABEL] @@ -1128,7 +1129,7 @@ a single separator line between the header lines and the list section. \fBinline\fR style embeds the header lines inside the list border frame with a horizontal separator; it requires a \fB\-\-list\-border\fR shape that has both top and bottom segments, falls back to \fBline\fR -otherwise, and is not compatible with \fB\-\-header\-first\fR. +otherwise. .SS FOOTER diff --git a/src/options.go b/src/options.go index 9e721144..3f171d51 100644 --- a/src/options.go +++ b/src/options.go @@ -3619,9 +3619,6 @@ func validateOptions(opts *Options) error { opts.Preview.border == tui.BorderInline { return errors.New("inline border is only supported for --header-border, --header-lines-border, and --footer-border") } - if opts.HeaderFirst && (opts.HeaderBorderShape == tui.BorderInline || opts.HeaderLinesShape == tui.BorderInline) { - return errors.New("--header-first is not compatible with --header-border=inline or --header-lines-border=inline") - } if opts.HeaderBorderShape == tui.BorderInline && opts.HeaderLinesShape != tui.BorderInline && opts.HeaderLinesShape != tui.BorderUndefined && diff --git a/src/terminal.go b/src/terminal.go index 018601ac..ed9245c8 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -2844,7 +2844,11 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) { if hasInputWindow { var btop int - if (hasHeaderWindow || hasHeaderLinesWindow) && t.headerFirst { + // Inline sections live inside the list frame, so they don't participate + // in --header-first repositioning; only non-inline sections do. + hasNonInlineHeader := hasHeaderWindow && t.headerBorderShape != tui.BorderInline + hasNonInlineHeaderLines := hasHeaderLinesWindow && headerLinesShape != tui.BorderInline + if (hasNonInlineHeader || hasNonInlineHeaderLines) && t.headerFirst { switch t.layout { case layoutDefault: btop = w.Top() + w.Height() diff --git a/test/test_layout.rb b/test/test_layout.rb index c06502cb..c4165c42 100644 --- a/test/test_layout.rb +++ b/test/test_layout.rb @@ -1491,6 +1491,42 @@ class TestLayout < TestInteractive tmux.send_keys 'Escape' end + # Inline takes precedence over --header-first: the main header stays + # inside the list frame instead of moving below the input. + def test_inline_header_border_overrides_header_first + tmux.send_keys %(seq 5 | #{FZF} --style full --header foo --header-first --header-border inline), :Enter + tmux.until do |lines| + foo_idx = lines.index { |l| l.match?(/\A│\s+foo\s+│\z/) } + input_idx = lines.index { |l| l.match?(/\A│\s+>\s+\d+\/\d+\s+│\z/) } + foo_idx && input_idx && foo_idx < input_idx + end + end + + # With both sections present, --header-first still moves the main --header + # below the input while --header-lines-border=inline keeps header-lines + # inside the list frame. + def test_inline_header_lines_with_header_first_and_main_header + tmux.send_keys %(seq 5 | #{FZF} --style full --header foo --header-lines 1 --header-first --header-lines-border inline), :Enter + tmux.until do |lines| + one_idx = lines.index { |l| l.match?(/\A│\s+1\s+│\z/) } + foo_idx = lines.index { |l| l.match?(/\A│\s+foo\s+│\z/) } + input_idx = lines.index { |l| l.match?(/\A│\s+>\s+\d+\/\d+\s+│\z/) } + one_idx && foo_idx && input_idx && one_idx < input_idx && input_idx < foo_idx + end + end + + # With no main --header, --header-first previously repositioned + # header-lines. Inline now takes precedence: header-lines stays inside + # the list frame. + def test_inline_header_lines_with_header_first_no_main_header + tmux.send_keys %(seq 5 | #{FZF} --style full --header-lines 1 --header-first --header-lines-border inline), :Enter + tmux.until do |lines| + one_idx = lines.index { |l| l.match?(/\A│\s+1\s+│\z/) } + input_idx = lines.index { |l| l.match?(/\A│\s+>\s+\d+\/\d+\s+│\z/) } + one_idx && input_idx && one_idx < input_idx + end + end + # Invalid inline combinations must be rejected at startup. def test_inline_rejected_on_unsupported_options [ @@ -1498,8 +1534,6 @@ class TestLayout < TestInteractive ['--list-border=inline', 'inline border is only supported'], ['--input-border=inline', 'inline border is only supported'], ['--preview-window=border-inline --preview :', 'invalid preview window option: border-inline'], - ['--header-first --header-border=inline', '--header-first is not compatible'], - ['--header-first --header-lines-border=inline --header-lines=1', '--header-first is not compatible'], ['--header-border=inline --header-lines-border=sharp --header-lines=1', '--header-border=inline requires --header-lines-border to be inline or unset'] ].each do |args, expected|