mirror of
https://github.com/junegunn/fzf.git
synced 2025-07-31 04:02:01 -07:00
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.
This commit is contained in:
@@ -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
|
||||
|
@@ -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 {
|
||||
|
125
src/terminal.go
125
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
|
||||
|
@@ -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) }
|
||||
|
||||
|
Reference in New Issue
Block a user