Apply facing-line rule to input border as well

Visible input border suppressed separator even when it draws no line
toward list section (bottom in default layout, top in reverse, vertical).
Suppress only when input border has a line on the list side; otherwise
fall through to adjacent section check.

- Requires header window to have content before treating it as facing
  section; hasHeaderWindow returns true with empty header when input
  border is visible
- Extract inputBorderFacesList and resolveInputBorderShape shared by
  options and terminal
- Add tests for separator suppression rules
This commit is contained in:
Junegunn Choi
2026-07-18 13:21:28 +09:00
parent c60bcf67a5
commit 9e2006306a
4 changed files with 150 additions and 11 deletions
+1 -1
View File
@@ -3699,7 +3699,7 @@ func (opts *Options) noSeparatorLine() bool {
// NOTE: This does not know that the default separator can be suppressed at
// runtime (see Terminal.separatorLength), so the minimum heights derived
// from it can be one line taller than strictly necessary.
sep := opts.Separator == nil && !opts.InputBorderShape.Visible() || opts.Separator != nil && len(*opts.Separator) > 0
sep := opts.Separator == nil && !inputBorderFacesList(opts.Layout, opts.InputBorderShape) || opts.Separator != nil && len(*opts.Separator) > 0
return noSeparatorLine(opts.InfoStyle, sep)
}
+36 -10
View File
@@ -1249,13 +1249,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
}
// Determine input border shape
if t.inputBorderShape == tui.BorderLine {
if t.layout == layoutReverse {
t.inputBorderShape = tui.BorderBottom
} else {
t.inputBorderShape = tui.BorderTop
}
}
t.inputBorderShape = resolveInputBorderShape(t.layout, t.inputBorderShape)
// Inline borders are embedded between the list's top and bottom horizontals.
// Shapes missing either one (none/phantom/line/single-sided) fall back to a plain
@@ -1711,10 +1705,32 @@ func (t *Terminal) separatorLength() int {
return t.separatorLen
}
// BorderLine for the input border resolves to a single line on the side
// facing the list section
func resolveInputBorderShape(layout layoutType, shape tui.BorderShape) tui.BorderShape {
if shape != tui.BorderLine {
return shape
}
if layout == layoutReverse {
return tui.BorderBottom
}
return tui.BorderTop
}
// Whether the input border draws a line on the side facing the list section
func inputBorderFacesList(layout layoutType, shape tui.BorderShape) bool {
shape = resolveInputBorderShape(layout, shape)
if layout == layoutReverse {
return shape.HasBottom()
}
return shape.HasTop()
}
// The default horizontal separator is redundant if the input section is
// already visually separated from the list section by a border line
func (t *Terminal) separatedByBorder() bool {
if t.inputBorderShape.Visible() {
// The input border itself draws a line on the side facing the list section
if inputBorderFacesList(t.layout, t.inputBorderShape) {
return true
}
for po := &t.previewOpts; po != nil; po = po.alternative {
@@ -1722,12 +1738,18 @@ func (t *Terminal) separatedByBorder() bool {
// A preview window at 'next' position sits right next to the input
// section instead. Keep the separator, as the visibility and the
// border of the preview window can change at runtime.
//
// The whole chain is checked because which spec is active depends
// on the terminal size, and the resolution (computePreviewSize in
// resizeWindows) itself consults noSeparatorLine, so the decision
// here cannot depend on its outcome. When a spec other than 'next'
// is active, this errs on the side of showing the separator.
return false
}
}
hasHeaderLinesWindow, headerLinesShape := t.determineHeaderLinesShape()
hasHeaderWindow := t.hasHeaderWindow()
if !hasHeaderWindow && !hasHeaderLinesWindow {
if !t.inputBorderShape.Visible() && !hasHeaderWindow && !hasHeaderLinesWindow {
// No separate window is created for the input section in this case
// (see hasInputWindow in resizeWindows), i.e. the input section is
// embedded in the list window and no border separates the two
@@ -1736,7 +1758,11 @@ func (t *Terminal) separatedByBorder() bool {
// The input section has its own window. Determine which section it is
// facing, and check if the border of that section draws a line toward it.
if hasHeaderWindow && !t.headerFirst && t.headerBorderShape != tui.BorderInline {
// NOTE: hasHeaderWindow can be true with no header content when the input
// border is visible (see Terminal.hasHeaderWindow). An empty header window
// takes no space, so it cannot be the facing section.
hasHeaderSection := hasHeaderWindow && len(t.header0)+t.headerLines > 0
if hasHeaderSection && !t.headerFirst && t.headerBorderShape != tui.BorderInline {
// Header section; an inline header is not a separate section, it is
// drawn inside the list border, so it falls through to the branches
// below