From 01567b4f06bc359c77d599bbca6ad91029006b08 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Sun, 19 Apr 2026 19:02:25 +0900 Subject: [PATCH] Fix inline header/footer border color when falling back to line InitTheme was called before the runtime coerced BorderInline to BorderLine, so HeaderBorder / FooterBorder inherited from ListBorder even when the effective shape was 'line'. Mirror the coercion so color inheritance matches the rendered shape. --- src/terminal.go | 10 +++++++--- test/lib/common.rb | 5 +++++ test/test_layout.rb | 25 +++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/terminal.go b/src/terminal.go index 199bb22f..0e9e01c6 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -1167,10 +1167,14 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor if baseTheme == nil { baseTheme = renderer.DefaultTheme() } - // This should be called before accessing tui.Color* - headerInline := opts.HeaderBorderShape == tui.BorderInline || opts.HeaderLinesShape == tui.BorderInline - footerInline := opts.FooterBorderShape == tui.BorderInline + // If the list border can't host an inline separator, the runtime later coerces + // BorderInline to BorderLine. Mirror that here so theme color inheritance matches + // the final rendering rather than the user's requested shape. + inlineListSupported := opts.ListBorderShape.HasTop() && opts.ListBorderShape.HasBottom() + headerInline := inlineListSupported && (opts.HeaderBorderShape == tui.BorderInline || opts.HeaderLinesShape == tui.BorderInline) + footerInline := inlineListSupported && opts.FooterBorderShape == tui.BorderInline hasHeader := opts.HeaderBorderShape.Visible() || opts.HeaderLinesShape.Visible() + // This should be called before accessing tui.Color* tui.InitTheme(opts.Theme, baseTheme, opts.Bold, opts.Black, opts.InputBorderShape.Visible(), hasHeader, headerInline, footerInline) // Gutter character diff --git a/test/lib/common.rb b/test/lib/common.rb index f81e246a..dcb8011c 100644 --- a/test/lib/common.rb +++ b/test/lib/common.rb @@ -130,6 +130,11 @@ class Tmux go(%W[capture-pane -p -J -t #{win}]).map(&:rstrip).reverse.drop_while(&:empty?).reverse end + # Raw pane capture with ANSI escape sequences preserved. + def capture_ansi + go(%W[capture-pane -p -J -e -t #{win}]) + end + # 3-bit ANSI bg code (40..47) -> color name used in --color options. BG_NAMES = %w[black red green yellow blue magenta cyan white].freeze diff --git a/test/test_layout.rb b/test/test_layout.rb index f5388259..c06502cb 100644 --- a/test/test_layout.rb +++ b/test/test_layout.rb @@ -1466,6 +1466,31 @@ class TestLayout < TestInteractive tmux.send_keys 'Escape' end + # Regression: when --header-border=inline falls back to `line` because the + # list border can't host an inline separator, the header-border color must + # inherit from `border`, not `list-border`. The effective shape is `line`, + # so color inheritance must match what `line` rendering would use. + def test_inline_fallback_does_not_inherit_list_border_color + # Marker attribute (bold) on list-border. If HeaderBorder wrongly inherits + # from ListBorder, the header separator characters will carry the bold + # attribute. --info=hidden and --no-separator strip other separator lines + # so the only row of `─` chars is the header separator. + tmux.send_keys %(seq 5 | #{FZF} --list-border=none --header HEADER --header-border=inline --info=hidden --no-separator --color=bg:-1,list-border:red:bold), :Enter + sep_row = nil + tmux.until do |_| + sep_row = tmux.capture_ansi.find do |row| + stripped = row.gsub(/\e\[[\d;]*m/, '').rstrip + stripped.match?(/\A─+\z/) + end + !sep_row.nil? + end + # Bold (1) or red fg (31) on the header separator means it inherited from + # list-border even though the effective shape is `line` (non-inline). + refute_match(/\e\[(?:[\d;]*;)?(?:1|31)(?:;[\d;]*)?m─/, sep_row, + "header separator inherited list-border attr: #{sep_row.inspect}") + tmux.send_keys 'Escape' + end + # Invalid inline combinations must be rejected at startup. def test_inline_rejected_on_unsupported_options [