diff --git a/man/man1/fzf.1 b/man/man1/fzf.1 index a07222e1..c5f13622 100644 --- a/man/man1/fzf.1 +++ b/man/man1/fzf.1 @@ -1001,7 +1001,8 @@ The first N lines of the input are treated as the sticky header. When lines that follow. .TP .B "\-\-header\-first" -Print header before the prompt line +Print header before the prompt line. When both normal header and header lines +(\fB\-\-header\-lines\fR) are present, this applies only to the normal header. .TP .BI "\-\-header\-border" [=STYLE] Draw border around the header section diff --git a/src/options.go b/src/options.go index 3def3395..142179e5 100644 --- a/src/options.go +++ b/src/options.go @@ -3218,15 +3218,6 @@ func postProcessOptions(opts *Options) error { if opts.HeaderLinesShape == tui.BorderNone { opts.HeaderLinesShape = tui.BorderPhantom - } else if opts.HeaderLinesShape == tui.BorderUndefined { - // In reverse-list layout, header lines should be at the top, while - // ordinary header should be at the bottom. So let's use a separate - // window for the header lines. - if opts.Layout == layoutReverseList { - opts.HeaderLinesShape = tui.BorderPhantom - } else { - opts.HeaderLinesShape = tui.BorderNone - } } if opts.Pointer == nil { diff --git a/src/terminal.go b/src/terminal.go index 8ffe9cef..a89551da 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -1203,8 +1203,8 @@ func (t *Terminal) extraLines() int { extra += borderLines(t.headerBorderShape) } extra += len(t.header0) - if t.hasHeaderLinesWindow() { - extra += borderLines(t.headerLinesShape) + if w, shape := t.determineHeaderLinesShape(); w { + extra += borderLines(shape) } extra += t.headerLines } @@ -1770,7 +1770,46 @@ func (t *Terminal) hasHeaderWindow() bool { } func (t *Terminal) hasHeaderLinesWindow() bool { - return t.headerVisible && t.headerLines > 0 && t.headerLinesShape.Visible() + w, _ := t.determineHeaderLinesShape() + return w +} + +func (t *Terminal) determineHeaderLinesShape() (bool, tui.BorderShape) { + if !t.headerVisible || t.headerLines == 0 { + return false, tui.BorderNone + } + + // --header-lines-border is set + if t.headerLinesShape != tui.BorderUndefined { + return true, t.headerLinesShape + } + + // --header-lines-border is not set, determine if we should use + // the style of --header-border + shape := tui.BorderNone + if len(t.header0) == 0 { + shape = t.headerBorderShape + } + if shape == tui.BorderNone { + shape = tui.BorderPhantom + } + + // --layout reverse-list is set + if t.layout == layoutReverseList { + return true, shape + } + + // Use header window instead + if len(t.header0) == 0 { + return false, t.headerBorderShape + } + + // We have both types of headers, and we want to separate the two + if t.headerFirst { + return true, shape + } + + return false, tui.BorderNone } func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) { @@ -1853,7 +1892,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) { shift := 0 shrink := 0 hasHeaderWindow := t.hasHeaderWindow() - hasHeaderLinesWindow := t.hasHeaderLinesWindow() + hasHeaderLinesWindow, headerLinesShape := t.determineHeaderLinesShape() hasInputWindow := !t.inputless && (t.inputBorderShape.Visible() || hasHeaderWindow || hasHeaderLinesWindow) if hasInputWindow { inputWindowHeight := 2 @@ -1889,7 +1928,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) { headerLinesHeight := 0 if hasHeaderLinesWindow { - headerLinesHeight = util.Min(availableLines, borderLines(t.headerLinesShape)+t.headerLines) + headerLinesHeight = util.Min(availableLines, borderLines(headerLinesShape)+t.headerLines) if t.layout != layoutDefault { shift += headerLinesHeight shrink += headerLinesHeight @@ -2147,23 +2186,33 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) { if t.wborder == nil { w = t.window } + if hasInputWindow { var btop int - if hasHeaderWindow && t.headerFirst { - if t.layout == layoutReverse { - btop = w.Top() - inputBorderHeight - headerLinesHeight - } else if t.layout == layoutReverseList { + if (hasHeaderWindow || hasHeaderLinesWindow) && t.headerFirst { + switch t.layout { + case layoutDefault: + btop = w.Top() + w.Height() + // If both headers are present, the header lines are displayed with the list + if hasHeaderWindow && hasHeaderLinesWindow { + btop += headerLinesHeight + } + case layoutReverse: + btop = w.Top() - inputBorderHeight + if hasHeaderWindow && hasHeaderLinesWindow { + btop -= headerLinesHeight + } + case layoutReverseList: btop = w.Top() + w.Height() - } else { - btop = w.Top() + w.Height() + headerLinesHeight } } else { - if t.layout == layoutReverse { - btop = w.Top() - shrink - } else if t.layout == layoutReverseList { - btop = w.Top() + w.Height() + headerBorderHeight - } else { + switch t.layout { + case layoutDefault: btop = w.Top() + w.Height() + headerBorderHeight + headerLinesHeight + case layoutReverse: + btop = w.Top() - shrink + case layoutReverseList: + btop = w.Top() + w.Height() + headerBorderHeight } } shift := 0 @@ -2215,17 +2264,34 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) { // Set up header lines border if hasHeaderLinesWindow { var btop int - if t.layout != layoutDefault { - btop = w.Top() - headerLinesHeight + // NOTE: We still have to handle --header-first here in case + // --header-lines-border is set. Can't we just use header window instead + // with the style? So we can display header label. + // fzf --header-lines 3 --header-label hello --header-border + // fzf --header-lines 3 --header-label hello --header-lines-border + headerFirst := t.headerFirst && len(t.header0) == 0 + + if headerFirst { + if t.layout == layoutDefault { + btop = w.Top() + w.Height() + inputBorderHeight + } else if t.layout == layoutReverse { + btop = w.Top() - headerLinesHeight - inputBorderHeight + } else { + btop = w.Top() - headerLinesHeight + } } else { - btop = w.Top() + w.Height() + if t.layout != layoutDefault { + btop = w.Top() - headerLinesHeight + } else { + btop = w.Top() + w.Height() + } } t.headerLinesBorder = t.tui.NewWindow( btop, w.Left(), w.Width(), - headerLinesHeight, tui.WindowHeader, tui.MakeBorderStyle(t.headerLinesShape, t.unicode), true) - t.headerLinesWindow = createInnerWindow(t.headerLinesBorder, t.headerLinesShape, tui.WindowHeader, 0) + headerLinesHeight, tui.WindowHeader, tui.MakeBorderStyle(headerLinesShape, t.unicode), true) + t.headerLinesWindow = createInnerWindow(t.headerLinesBorder, headerLinesShape, tui.WindowHeader, 0) } // Print border label @@ -2621,12 +2687,14 @@ func (t *Terminal) resizeIfNeeded() bool { // Check if the header borders are used and header has changed allHeaderLines := t.visibleHeaderLines() primaryHeaderLines := allHeaderLines - if t.headerLinesShape.Visible() { + if t.hasHeaderLinesWindow() { primaryHeaderLines -= t.headerLines } - if (t.headerBorderShape.Visible() || t.headerLinesShape.Visible()) && + needHeaderLinesWindow := t.hasHeaderLinesWindow() + if (t.headerBorderShape.Visible() || t.hasHeaderLinesWindow()) && (t.headerWindow == nil && primaryHeaderLines > 0 || t.headerWindow != nil && primaryHeaderLines != t.headerWindow.Height()) || - t.headerLinesShape.Visible() && (t.headerLinesWindow == nil && t.headerLines > 0 || t.headerLinesWindow != nil && t.headerLines != t.headerLinesWindow.Height()) { + needHeaderLinesWindow && (t.headerLinesWindow == nil || t.headerLinesWindow != nil && t.headerLines != t.headerLinesWindow.Height()) || + !needHeaderLinesWindow && t.headerLinesWindow != nil { t.printAll() return true } @@ -2640,14 +2708,14 @@ func (t *Terminal) printHeader() { t.withWindow(t.headerWindow, func() { var lines []string - if !t.headerLinesShape.Visible() { + if !t.hasHeaderLinesWindow() { lines = t.header } t.printHeaderImpl(t.headerWindow, t.headerBorderShape, t.header0, lines) }) - if t.headerLinesShape.Visible() { + if w, shape := t.determineHeaderLinesShape(); w { t.withWindow(t.headerLinesWindow, func() { - t.printHeaderImpl(t.headerLinesWindow, t.headerLinesShape, nil, t.header) + t.printHeaderImpl(t.headerLinesWindow, shape, nil, t.header) }) } } @@ -5863,7 +5931,8 @@ func (t *Terminal) Loop() error { } if clicked && t.headerVisible && t.headerLinesWindow != nil && t.headerLinesWindow.Enclose(my, mx) { - mx -= t.headerLinesWindow.Left() + t.headerIndent(t.headerLinesShape) + _, shape := t.determineHeaderLinesShape() + mx -= t.headerLinesWindow.Left() + t.headerIndent(shape) my -= t.headerLinesWindow.Top() if mx < 0 { break diff --git a/test/test_layout.rb b/test/test_layout.rb index 2d30c87a..152abb8c 100644 --- a/test/test_layout.rb +++ b/test/test_layout.rb @@ -39,11 +39,11 @@ class TestLayout < TestInteractive tmux.send_keys "seq 1000 | #{FZF} --header foobar --header-lines 3 --header-first", :Enter block = <<~OUTPUT > 4 - 997/997 - > 3 2 1 + 997/997 + > foobar OUTPUT tmux.until { assert_block(block, it) } @@ -53,10 +53,10 @@ class TestLayout < TestInteractive tmux.send_keys "seq 1000 | #{FZF} --header foobar --header-lines 3 --header-first --reverse --inline-info", :Enter block = <<~OUTPUT foobar + > < 997/997 1 2 3 - > < 997/997 > 4 OUTPUT tmux.until { assert_block(block, it) } @@ -148,10 +148,10 @@ class TestLayout < TestInteractive │ │ 4 │ > 3 - │ 2/2 - │ > │ 2 │ 1 + │ 2/2 + │ > │ foo ╰─────── OUTPUT @@ -609,11 +609,11 @@ class TestLayout < TestInteractive │ 4 │ > 3 ╰────────── + 2 + 1 98/98 ─ > ╭────────── - │ 2 - │ 1 │ hello ╰────────── BLOCK @@ -666,12 +666,12 @@ class TestLayout < TestInteractive │ 4 │ > 3 ╰────────── + 98/98 ─ + > ╔══════════ ║ 2 ║ 1 ╚══════════ - 98/98 ─ - > BLOCK tmux.until { assert_block(block1, it) }