Use bottom char for thinblock inline separator above list
CodeQL / Analyze (go) (push) Has been cancelled
build / build (push) Has been cancelled
Test fzf on macOS / build (push) Has been cancelled

For border styles where the top and bottom horizontals differ (thinblock
and block), the inline separator now picks the char based on position:
separators above the list content use the `bottom` char so the thin
line hugs the list from above, matching how the list frame's top edge
sits against the first row. Separators below list content continue to
use the `top` char.

Rounded / sharp / bold / double / horizontal / line are unaffected because
their top and bottom chars are identical.
This commit is contained in:
Junegunn Choi
2026-04-18 14:13:08 +09:00
parent 35e8e65b2e
commit 23cb4316ef
4 changed files with 39 additions and 13 deletions
+17 -4
View File
@@ -2739,7 +2739,9 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
} }
cursor += s.contentLines cursor += s.contentLines
sepRow := cursor - t.wborder.Top() sepRow := cursor - t.wborder.Top()
t.wborder.DrawHSeparator(sepRow, s.windowType) // Separator sits above list content; hug the list below by using the
// bottom char (matters for thinblock/block where top and bottom differ).
t.wborder.DrawHSeparator(sepRow, s.windowType, true)
recordSep(s.role, sepRow) recordSep(s.role, sepRow)
cursor++ cursor++
} }
@@ -2756,7 +2758,8 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
t.footerWindow = win t.footerWindow = win
} }
sepRow := top - 1 - t.wborder.Top() sepRow := top - 1 - t.wborder.Top()
t.wborder.DrawHSeparator(sepRow, s.windowType) // Separator sits below list content; hug the list above with the top char.
t.wborder.DrawHSeparator(sepRow, s.windowType, false)
recordSep(s.role, sepRow) recordSep(s.role, sepRow)
cursor = top - 2 cursor = top - 2
} }
@@ -2937,6 +2940,16 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
} }
} }
// isInlineSepAboveList reports whether the recorded wborder-relative separator row sits
// above the list content window, so DrawHSeparator should use the bottom horizontal char
// to visually hug the list from above.
func (t *Terminal) isInlineSepAboveList(sepRow int) bool {
if t.wborder == nil || t.window == nil {
return false
}
return t.wborder.Top()+sepRow < t.window.Top()
}
func (t *Terminal) printInlineLabel(window tui.Window, row int, render labelPrinter, opts labelOpts, length int) { func (t *Terminal) printInlineLabel(window tui.Window, row int, render labelPrinter, opts labelOpts, length int) {
if window == nil || render == nil || window.Height() == 0 { if window == nil || render == nil || window.Height() == 0 {
return return
@@ -6123,13 +6136,13 @@ func (t *Terminal) Loop() error {
case reqRedrawHeaderLabel: case reqRedrawHeaderLabel:
t.printLabel(t.headerBorder, t.headerLabel, t.headerLabelOpts, t.headerLabelLen, t.headerBorderShape, true) t.printLabel(t.headerBorder, t.headerLabel, t.headerLabelOpts, t.headerLabelLen, t.headerBorderShape, true)
if t.wborder != nil && t.inlineHeaderSepRow >= 0 { if t.wborder != nil && t.inlineHeaderSepRow >= 0 {
t.wborder.DrawHSeparator(t.inlineHeaderSepRow, tui.WindowHeader) t.wborder.DrawHSeparator(t.inlineHeaderSepRow, tui.WindowHeader, t.isInlineSepAboveList(t.inlineHeaderSepRow))
t.printInlineLabel(t.wborder, t.inlineHeaderSepRow, t.headerLabel, t.headerLabelOpts, t.headerLabelLen) t.printInlineLabel(t.wborder, t.inlineHeaderSepRow, t.headerLabel, t.headerLabelOpts, t.headerLabelLen)
} }
case reqRedrawFooterLabel: case reqRedrawFooterLabel:
t.printLabel(t.footerBorder, t.footerLabel, t.footerLabelOpts, t.footerLabelLen, t.footerBorderShape, true) t.printLabel(t.footerBorder, t.footerLabel, t.footerLabelOpts, t.footerLabelLen, t.footerBorderShape, true)
if t.wborder != nil && t.inlineFooterSepRow >= 0 { if t.wborder != nil && t.inlineFooterSepRow >= 0 {
t.wborder.DrawHSeparator(t.inlineFooterSepRow, tui.WindowFooter) t.wborder.DrawHSeparator(t.inlineFooterSepRow, tui.WindowFooter, t.isInlineSepAboveList(t.inlineFooterSepRow))
t.printInlineLabel(t.wborder, t.inlineFooterSepRow, t.footerLabel, t.footerLabelOpts, t.footerLabelLen) t.printInlineLabel(t.wborder, t.inlineFooterSepRow, t.footerLabel, t.footerLabelOpts, t.footerLabelLen)
} }
case reqRedrawListLabel: case reqRedrawListLabel:
+8 -4
View File
@@ -1122,7 +1122,7 @@ func (w *LightWindow) DrawHBorder() {
w.drawBorder(true) w.drawBorder(true)
} }
func (w *LightWindow) DrawHSeparator(row int, windowType WindowType) { func (w *LightWindow) DrawHSeparator(row int, windowType WindowType, useBottom bool) {
if w.height == 0 { if w.height == 0 {
return return
} }
@@ -1147,13 +1147,17 @@ func (w *LightWindow) DrawHSeparator(row int, windowType WindowType) {
// Section color for the horizontal; list-border color (w.windowType) for the T-junctions. // Section color for the horizontal; list-border color (w.windowType) for the T-junctions.
lineColor := colorFor(windowType) lineColor := colorFor(windowType)
junctionColor := colorFor(w.windowType) junctionColor := colorFor(w.windowType)
hw := runeWidth(w.border.top) lineChar := w.border.top
if useBottom {
lineChar = w.border.bottom
}
hw := runeWidth(lineChar)
w.Move(row, 0) w.Move(row, 0)
if !w.border.shape.HasLeft() && !w.border.shape.HasRight() { if !w.border.shape.HasLeft() && !w.border.shape.HasRight() {
// No verticals to join, so draw a continuous horizontal across the full width. // No verticals to join, so draw a continuous horizontal across the full width.
full := max(0, w.width/hw) full := max(0, w.width/hw)
rem := w.width - full*hw rem := w.width - full*hw
w.CPrint(lineColor, repeat(w.border.top, full)+repeat(' ', rem)) w.CPrint(lineColor, repeat(lineChar, full)+repeat(' ', rem))
return return
} }
lw := runeWidth(w.border.leftMid) lw := runeWidth(w.border.leftMid)
@@ -1161,7 +1165,7 @@ func (w *LightWindow) DrawHSeparator(row int, windowType WindowType) {
inner := max(0, (w.width-lw-rw)/hw) inner := max(0, (w.width-lw-rw)/hw)
rem := (w.width - lw - rw) - inner*hw rem := (w.width - lw - rw) - inner*hw
w.CPrint(junctionColor, string(w.border.leftMid)) w.CPrint(junctionColor, string(w.border.leftMid))
w.CPrint(lineColor, repeat(w.border.top, inner)+repeat(' ', rem)) w.CPrint(lineColor, repeat(lineChar, inner)+repeat(' ', rem))
w.CPrint(junctionColor, string(w.border.rightMid)) w.CPrint(junctionColor, string(w.border.rightMid))
} }
+8 -4
View File
@@ -1017,7 +1017,7 @@ func (w *TcellWindow) DrawHBorder() {
w.drawBorder(true) w.drawBorder(true)
} }
func (w *TcellWindow) DrawHSeparator(row int, windowType WindowType) { func (w *TcellWindow) DrawHSeparator(row int, windowType WindowType, useBottom bool) {
if w.height == 0 { if w.height == 0 {
return return
} }
@@ -1049,15 +1049,19 @@ func (w *TcellWindow) DrawHSeparator(row int, windowType WindowType) {
// so the outer frame's verticals stay visually continuous. // so the outer frame's verticals stay visually continuous.
lineStyle := styleFor(windowType) lineStyle := styleFor(windowType)
junctionStyle := styleFor(w.windowType) junctionStyle := styleFor(w.windowType)
lineChar := w.borderStyle.top
if useBottom {
lineChar = w.borderStyle.bottom
}
y := w.top + row y := w.top + row
left := w.left left := w.left
right := left + w.width right := left + w.width
hw := runeWidth(w.borderStyle.top) hw := runeWidth(lineChar)
hasVert := shape.HasLeft() || shape.HasRight() hasVert := shape.HasLeft() || shape.HasRight()
if !hasVert { if !hasVert {
// No verticals to join, so draw a continuous horizontal across the full width. // No verticals to join, so draw a continuous horizontal across the full width.
for x := left; x <= right-hw; x += hw { for x := left; x <= right-hw; x += hw {
_screen.SetContent(x, y, w.borderStyle.top, nil, lineStyle) _screen.SetContent(x, y, lineChar, nil, lineStyle)
} }
return return
} }
@@ -1065,7 +1069,7 @@ func (w *TcellWindow) DrawHSeparator(row int, windowType WindowType) {
rightMidW := runeWidth(w.borderStyle.rightMid) rightMidW := runeWidth(w.borderStyle.rightMid)
max := right - leftMidW - rightMidW max := right - leftMidW - rightMidW
for x := left + leftMidW; x <= max; x += hw { for x := left + leftMidW; x <= max; x += hw {
_screen.SetContent(x, y, w.borderStyle.top, nil, lineStyle) _screen.SetContent(x, y, lineChar, nil, lineStyle)
} }
_screen.SetContent(left, y, w.borderStyle.leftMid, nil, junctionStyle) _screen.SetContent(left, y, w.borderStyle.leftMid, nil, junctionStyle)
_screen.SetContent(right-rightMidW, y, w.borderStyle.rightMid, nil, junctionStyle) _screen.SetContent(right-rightMidW, y, w.borderStyle.rightMid, nil, junctionStyle)
+6 -1
View File
@@ -830,7 +830,12 @@ type Window interface {
DrawBorder() DrawBorder()
DrawHBorder() DrawHBorder()
DrawHSeparator(row int, windowType WindowType) // DrawHSeparator draws an inline horizontal separator at `row` (relative to the
// window's top) using the color for `windowType`. When useBottom is true, the
// `bottom` horizontal char is used instead of `top` — for thinblock/block styles
// where the two characters differ, this keeps the thin line visually bonded to
// the list content that sits on the opposite side of the separator.
DrawHSeparator(row int, windowType WindowType, useBottom bool)
Refresh() Refresh()
FinishFill() FinishFill()