summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2025-06-05 22:02:22 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2025-06-10 23:02:23 +0900
commit39db02616158020b180a56dc8f1bdcf9f8365945 (patch)
treeef4c791512139894d51438915068b71e588a66b4
parentf6c589c606a8936e33778a717d6200632f8cab21 (diff)
downloadfzf-39db02616158020b180a56dc8f1bdcf9f8365945.tar.gz
Fix inconsistent placement of header-lines with border options
fzf displayed --header-lines inconsistently depending on the presence of borders: # --header and --header-lines co-located seq 10 | fzf --header-lines 3 --header "$(seq 101 103)" --header-first # --header and --header-lines separated seq 10 | fzf --header-lines 3 --header "$(seq 101 103)" --header-first --header-lines-border This commit fixes the inconsistency with the following logic: * If only one of --header or --header-lines is provided, --header-first applies to that single header. * If both are present, --header-first affects only the regular --header, not --header-lines.
-rw-r--r--man/man1/fzf.13
-rw-r--r--src/options.go9
-rw-r--r--src/terminal.go123
-rw-r--r--test/test_layout.rb18
4 files changed, 107 insertions, 46 deletions
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 {
+ switch t.layout {
+ case layoutDefault:
+ btop = w.Top() + w.Height() + headerBorderHeight + headerLinesHeight
+ case layoutReverse:
btop = w.Top() - shrink
- } else if t.layout == layoutReverseList {
+ case layoutReverseList:
btop = w.Top() + w.Height() + headerBorderHeight
- } else {
- btop = w.Top() + w.Height() + headerBorderHeight + headerLinesHeight
}
}
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) }