From 23cb4316efec552aacab8d5d4d18fd534150123c Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Sat, 18 Apr 2026 14:13:08 +0900 Subject: [PATCH] Use bottom char for thinblock inline separator above list 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. --- src/terminal.go | 21 +++++++++++++++++---- src/tui/light.go | 12 ++++++++---- src/tui/tcell.go | 12 ++++++++---- src/tui/tui.go | 7 ++++++- 4 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/terminal.go b/src/terminal.go index d77b7221..a2c7d21d 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -2739,7 +2739,9 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) { } cursor += s.contentLines 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) cursor++ } @@ -2756,7 +2758,8 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) { t.footerWindow = win } 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) 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) { if window == nil || render == nil || window.Height() == 0 { return @@ -6123,13 +6136,13 @@ func (t *Terminal) Loop() error { case reqRedrawHeaderLabel: t.printLabel(t.headerBorder, t.headerLabel, t.headerLabelOpts, t.headerLabelLen, t.headerBorderShape, true) 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) } case reqRedrawFooterLabel: t.printLabel(t.footerBorder, t.footerLabel, t.footerLabelOpts, t.footerLabelLen, t.footerBorderShape, true) 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) } case reqRedrawListLabel: diff --git a/src/tui/light.go b/src/tui/light.go index a1e992bf..24a79ea7 100644 --- a/src/tui/light.go +++ b/src/tui/light.go @@ -1122,7 +1122,7 @@ func (w *LightWindow) DrawHBorder() { 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 { 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. lineColor := colorFor(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) if !w.border.shape.HasLeft() && !w.border.shape.HasRight() { // No verticals to join, so draw a continuous horizontal across the full width. full := max(0, w.width/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 } 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) rem := (w.width - lw - rw) - inner*hw 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)) } diff --git a/src/tui/tcell.go b/src/tui/tcell.go index 791fd952..51e79260 100644 --- a/src/tui/tcell.go +++ b/src/tui/tcell.go @@ -1017,7 +1017,7 @@ func (w *TcellWindow) DrawHBorder() { 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 { return } @@ -1049,15 +1049,19 @@ func (w *TcellWindow) DrawHSeparator(row int, windowType WindowType) { // so the outer frame's verticals stay visually continuous. lineStyle := styleFor(windowType) junctionStyle := styleFor(w.windowType) + lineChar := w.borderStyle.top + if useBottom { + lineChar = w.borderStyle.bottom + } y := w.top + row left := w.left right := left + w.width - hw := runeWidth(w.borderStyle.top) + hw := runeWidth(lineChar) hasVert := shape.HasLeft() || shape.HasRight() if !hasVert { // No verticals to join, so draw a continuous horizontal across the full width. 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 } @@ -1065,7 +1069,7 @@ func (w *TcellWindow) DrawHSeparator(row int, windowType WindowType) { rightMidW := runeWidth(w.borderStyle.rightMid) max := right - leftMidW - rightMidW 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(right-rightMidW, y, w.borderStyle.rightMid, nil, junctionStyle) diff --git a/src/tui/tui.go b/src/tui/tui.go index b6e43a4e..a3cf48df 100644 --- a/src/tui/tui.go +++ b/src/tui/tui.go @@ -830,7 +830,12 @@ type Window interface { DrawBorder() 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() FinishFill()