mirror of
https://github.com/junegunn/fzf.git
synced 2026-08-10 09:52:36 +08:00
Add --{header,header-lines,footer}-border=inline
New BorderShape that embeds the section inside the --list-border frame, joined to the list content by a horizontal separator with T-junctions where the list shape has side borders. Requires a list border with both top and bottom segments; falls back to 'line' otherwise. Stacks when multiple sections are inline. Sections inherit --color list-border by default and are colored as a uniform block via their own --color *-border and *-bg. Incompatible with --header-first. --header-border=inline requires --header-lines-border to be inline or unset.
This commit is contained in:
+302
-66
@@ -1168,7 +1168,10 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
||||
baseTheme = renderer.DefaultTheme()
|
||||
}
|
||||
// This should be called before accessing tui.Color*
|
||||
tui.InitTheme(opts.Theme, baseTheme, opts.Bold, opts.Black, opts.InputBorderShape.Visible(), opts.HeaderBorderShape.Visible())
|
||||
headerInline := opts.HeaderBorderShape == tui.BorderInline || opts.HeaderLinesShape == tui.BorderInline
|
||||
footerInline := opts.FooterBorderShape == tui.BorderInline
|
||||
hasHeader := opts.HeaderBorderShape.Visible() || opts.HeaderLinesShape.Visible()
|
||||
tui.InitTheme(opts.Theme, baseTheme, opts.Bold, opts.Black, opts.InputBorderShape.Visible(), hasHeader, headerInline, footerInline)
|
||||
|
||||
// Gutter character
|
||||
var gutterChar, gutterRawChar string
|
||||
@@ -1227,6 +1230,22 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// horizontal separator (same as BorderLine).
|
||||
inlineSupported := t.listBorderShape.HasTop() && t.listBorderShape.HasBottom()
|
||||
if !inlineSupported {
|
||||
if t.headerBorderShape == tui.BorderInline {
|
||||
t.headerBorderShape = tui.BorderLine
|
||||
}
|
||||
if t.headerLinesShape == tui.BorderInline {
|
||||
t.headerLinesShape = tui.BorderLine
|
||||
}
|
||||
if t.footerBorderShape == tui.BorderInline {
|
||||
t.footerBorderShape = tui.BorderLine
|
||||
}
|
||||
}
|
||||
|
||||
// Determine header border shape
|
||||
if t.headerBorderShape == tui.BorderLine {
|
||||
if t.layout == layoutReverse {
|
||||
@@ -2237,6 +2256,98 @@ func (t *Terminal) determineHeaderLinesShape() (bool, tui.BorderShape) {
|
||||
return false, tui.BorderNone
|
||||
}
|
||||
|
||||
// Inline sections live inside wborder rather than consuming shift/shrink/availableLines.
|
||||
type inlineRole int
|
||||
|
||||
const (
|
||||
inlineRoleHeader inlineRole = iota
|
||||
inlineRoleHeaderLines
|
||||
inlineRoleFooter
|
||||
)
|
||||
|
||||
type inlineSlot struct {
|
||||
role inlineRole
|
||||
windowType tui.WindowType
|
||||
contentLines int // 0 when out of budget: a ghost placeholder is created so
|
||||
// t.headerWindow / t.footerWindow stay non-nil but no frame is painted.
|
||||
label labelPrinter
|
||||
labelOpts labelOpts
|
||||
labelLen int
|
||||
}
|
||||
|
||||
// inlineMetaFor returns (onTop: top stack vs bottom; windowType; isInner:
|
||||
// adjacent to list content) for the given inline role, derived from the layout.
|
||||
// Header is outer when paired with header-lines; header-lines is always inner;
|
||||
// footer is inner except in reverseList where it sits outside header-lines.
|
||||
func (t *Terminal) inlineMetaFor(role inlineRole) (onTop bool, windowType tui.WindowType, isInner bool) {
|
||||
switch role {
|
||||
case inlineRoleHeader:
|
||||
return t.layout == layoutReverse, tui.WindowHeader, false
|
||||
case inlineRoleHeaderLines:
|
||||
return t.layout != layoutDefault, tui.WindowHeader, true
|
||||
case inlineRoleFooter:
|
||||
return t.layout != layoutReverse, tui.WindowFooter, t.layout != layoutReverseList
|
||||
}
|
||||
return false, tui.WindowBase, false
|
||||
}
|
||||
|
||||
func (t *Terminal) placeInlineSection(win tui.Window, role inlineRole) {
|
||||
switch role {
|
||||
case inlineRoleHeader:
|
||||
t.headerWindow = win
|
||||
case inlineRoleHeaderLines:
|
||||
t.headerLinesWindow = win
|
||||
case inlineRoleFooter:
|
||||
t.footerWindow = win
|
||||
}
|
||||
}
|
||||
|
||||
// placeInlineStack walks `slots` in outer-to-inner order. Only the outermost
|
||||
// slot (index 0) claims the adjacent wborder edge; later slots paint side-only
|
||||
// frames so the T-junction separator lands between sections.
|
||||
func (t *Terminal) placeInlineStack(slots []inlineSlot, startRow int, onTop bool) {
|
||||
firstEdge := tui.SectionEdgeTop
|
||||
if !onTop {
|
||||
firstEdge = tui.SectionEdgeBottom
|
||||
}
|
||||
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
|
||||
subLeft := t.window.Left()
|
||||
subWidth := t.window.Width()
|
||||
cursor := startRow
|
||||
for i, s := range slots {
|
||||
var windowTop, sepRow int
|
||||
if onTop {
|
||||
windowTop = cursor
|
||||
sepRow = cursor + s.contentLines - t.wborder.Top()
|
||||
} else {
|
||||
windowTop = cursor - s.contentLines + 1
|
||||
sepRow = windowTop - 1 - t.wborder.Top()
|
||||
}
|
||||
// 0-height placeholder keeps t.headerWindow / t.footerWindow non-nil.
|
||||
win := t.tui.NewWindow(windowTop, subLeft, subWidth, s.contentLines, s.windowType, noBorder, true)
|
||||
t.placeInlineSection(win, s.role)
|
||||
if s.contentLines == 0 {
|
||||
continue
|
||||
}
|
||||
secTop := windowTop - t.wborder.Top()
|
||||
secBottom := secTop + s.contentLines - 1
|
||||
edge := tui.SectionEdgeNone
|
||||
if i == 0 {
|
||||
edge = firstEdge
|
||||
}
|
||||
t.wborder.PaintSectionFrame(secTop, secBottom, s.windowType, edge)
|
||||
// useBottom=onTop so the separator always hugs the list (inner side).
|
||||
// Matters for thinblock/block where top != bottom char.
|
||||
t.wborder.DrawHSeparator(sepRow, s.windowType, onTop)
|
||||
t.printLabelAt(t.wborder, s.label, s.labelOpts, s.labelLen, sepRow)
|
||||
if onTop {
|
||||
cursor += s.contentLines + 1
|
||||
} else {
|
||||
cursor -= s.contentLines + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
|
||||
t.clearNumLinesCache()
|
||||
t.forcePreview = forcePreview
|
||||
@@ -2353,6 +2464,50 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// Slices are ordered outer-to-inner: index 0 touches wborder's edge.
|
||||
var inlineTop []inlineSlot
|
||||
var inlineBottom []inlineSlot
|
||||
|
||||
// Caps contentLines against remaining wborder space. Oversized requests (e.g. a
|
||||
// huge --header-lines) become 0-height placeholders rather than pushing the list
|
||||
// window to negative height.
|
||||
addInline := func(contentLines int, role inlineRole) {
|
||||
onTop, windowType, isInner := t.inlineMetaFor(role)
|
||||
used := 0
|
||||
for _, s := range inlineTop {
|
||||
if s.contentLines > 0 {
|
||||
used += s.contentLines + 1
|
||||
}
|
||||
}
|
||||
for _, s := range inlineBottom {
|
||||
if s.contentLines > 0 {
|
||||
used += s.contentLines + 1
|
||||
}
|
||||
}
|
||||
remaining := availableLines - borderLines(t.listBorderShape) - used - 1
|
||||
if remaining < 2 {
|
||||
contentLines = 0
|
||||
} else {
|
||||
contentLines = util.Constrain(contentLines, 1, remaining-1)
|
||||
}
|
||||
slot := inlineSlot{role: role, windowType: windowType, contentLines: contentLines}
|
||||
switch role {
|
||||
case inlineRoleHeader:
|
||||
slot.label, slot.labelOpts, slot.labelLen = t.headerLabel, t.headerLabelOpts, t.headerLabelLen
|
||||
case inlineRoleFooter:
|
||||
slot.label, slot.labelOpts, slot.labelLen = t.footerLabel, t.footerLabelOpts, t.footerLabelLen
|
||||
}
|
||||
target := &inlineTop
|
||||
if !onTop {
|
||||
target = &inlineBottom
|
||||
}
|
||||
if isInner {
|
||||
*target = append(*target, slot)
|
||||
} else {
|
||||
*target = append([]inlineSlot{slot}, *target...)
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust position and size of the list window if header border is set
|
||||
headerBorderHeight := 0
|
||||
if hasHeaderWindow {
|
||||
@@ -2360,37 +2515,62 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
|
||||
if hasHeaderLinesWindow {
|
||||
headerWindowHeight -= t.headerLines
|
||||
}
|
||||
headerBorderHeight = util.Constrain(borderLines(t.headerBorderShape)+headerWindowHeight, 0, availableLines)
|
||||
if t.layout == layoutReverse {
|
||||
shift += headerBorderHeight
|
||||
shrink += headerBorderHeight
|
||||
if t.headerBorderShape == tui.BorderInline {
|
||||
addInline(headerWindowHeight, inlineRoleHeader)
|
||||
} else {
|
||||
shrink += headerBorderHeight
|
||||
headerBorderHeight = util.Constrain(borderLines(t.headerBorderShape)+headerWindowHeight, 0, availableLines)
|
||||
if t.layout == layoutReverse {
|
||||
shift += headerBorderHeight
|
||||
shrink += headerBorderHeight
|
||||
} else {
|
||||
shrink += headerBorderHeight
|
||||
}
|
||||
availableLines -= headerBorderHeight
|
||||
}
|
||||
availableLines -= headerBorderHeight
|
||||
}
|
||||
|
||||
headerLinesHeight := 0
|
||||
if hasHeaderLinesWindow {
|
||||
headerLinesHeight = util.Constrain(borderLines(headerLinesShape)+t.headerLines, 0, availableLines)
|
||||
if t.layout != layoutDefault {
|
||||
shift += headerLinesHeight
|
||||
shrink += headerLinesHeight
|
||||
if headerLinesShape == tui.BorderInline {
|
||||
addInline(t.headerLines, inlineRoleHeaderLines)
|
||||
} else {
|
||||
shrink += headerLinesHeight
|
||||
headerLinesHeight = util.Constrain(borderLines(headerLinesShape)+t.headerLines, 0, availableLines)
|
||||
if t.layout != layoutDefault {
|
||||
shift += headerLinesHeight
|
||||
shrink += headerLinesHeight
|
||||
} else {
|
||||
shrink += headerLinesHeight
|
||||
}
|
||||
availableLines -= headerLinesHeight
|
||||
}
|
||||
availableLines -= headerLinesHeight
|
||||
}
|
||||
|
||||
footerBorderHeight := 0
|
||||
if hasFooterWindow {
|
||||
// Footer lines should not take all available lines
|
||||
footerBorderHeight = util.Constrain(borderLines(t.footerBorderShape)+len(t.footer), 0, availableLines)
|
||||
shrink += footerBorderHeight
|
||||
if t.layout != layoutReverse {
|
||||
shift += footerBorderHeight
|
||||
if t.footerBorderShape == tui.BorderInline {
|
||||
addInline(len(t.footer), inlineRoleFooter)
|
||||
} else {
|
||||
// Footer lines should not take all available lines
|
||||
footerBorderHeight = util.Constrain(borderLines(t.footerBorderShape)+len(t.footer), 0, availableLines)
|
||||
shrink += footerBorderHeight
|
||||
if t.layout != layoutReverse {
|
||||
shift += footerBorderHeight
|
||||
}
|
||||
availableLines -= footerBorderHeight
|
||||
}
|
||||
}
|
||||
|
||||
inlineTopLines := 0
|
||||
for _, s := range inlineTop {
|
||||
if s.contentLines > 0 {
|
||||
inlineTopLines += s.contentLines + 1
|
||||
}
|
||||
}
|
||||
inlineBottomLines := 0
|
||||
for _, s := range inlineBottom {
|
||||
if s.contentLines > 0 {
|
||||
inlineBottomLines += s.contentLines + 1
|
||||
}
|
||||
availableLines -= footerBorderHeight
|
||||
}
|
||||
|
||||
// Set up list border
|
||||
@@ -2501,12 +2681,12 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
|
||||
if previewOpts.position == posUp {
|
||||
innerBorderFn(marginInt[0]+pheight, marginInt[3], width, height-pheight)
|
||||
t.window = t.tui.NewWindow(
|
||||
innerMarginInt[0]+pheight+shift, innerMarginInt[3], innerWidth, innerHeight-pheight-shrink, tui.WindowList, noBorder, true)
|
||||
innerMarginInt[0]+pheight+shift+inlineTopLines, innerMarginInt[3], innerWidth, innerHeight-pheight-shrink-inlineTopLines-inlineBottomLines, tui.WindowList, noBorder, true)
|
||||
createPreviewWindow(marginInt[0], marginInt[3], width, pheight)
|
||||
} else {
|
||||
innerBorderFn(marginInt[0], marginInt[3], width, height-pheight)
|
||||
t.window = t.tui.NewWindow(
|
||||
innerMarginInt[0]+shift, innerMarginInt[3], innerWidth, innerHeight-pheight-shrink, tui.WindowList, noBorder, true)
|
||||
innerMarginInt[0]+shift+inlineTopLines, innerMarginInt[3], innerWidth, innerHeight-pheight-shrink-inlineTopLines-inlineBottomLines, tui.WindowList, noBorder, true)
|
||||
createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
|
||||
}
|
||||
case posLeft, posRight:
|
||||
@@ -2545,7 +2725,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
|
||||
m = 1
|
||||
}
|
||||
t.window = t.tui.NewWindow(
|
||||
innerMarginInt[0]+shift, innerMarginInt[3]+pwidth+m, innerWidth-pwidth-m, innerHeight-shrink, tui.WindowList, noBorder, true)
|
||||
innerMarginInt[0]+shift+inlineTopLines, innerMarginInt[3]+pwidth+m, innerWidth-pwidth-m, innerHeight-shrink-inlineTopLines-inlineBottomLines, tui.WindowList, noBorder, true)
|
||||
|
||||
// Clear characters on the margin
|
||||
// fzf --bind 'space:toggle-preview' --preview ':' --preview-window left,1
|
||||
@@ -2577,7 +2757,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
|
||||
}
|
||||
innerBorderFn(marginInt[0], marginInt[3], width-pwidth, height)
|
||||
t.window = t.tui.NewWindow(
|
||||
innerMarginInt[0]+shift, innerMarginInt[3], innerWidth-pwidth, innerHeight-shrink, tui.WindowList, noBorder, true)
|
||||
innerMarginInt[0]+shift+inlineTopLines, innerMarginInt[3], innerWidth-pwidth, innerHeight-shrink-inlineTopLines-inlineBottomLines, tui.WindowList, noBorder, true)
|
||||
x := marginInt[3] + width - pwidth
|
||||
createPreviewWindow(marginInt[0], x, pwidth, height)
|
||||
}
|
||||
@@ -2615,10 +2795,15 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
|
||||
}
|
||||
innerBorderFn(marginInt[0], marginInt[3], width, height)
|
||||
t.window = t.tui.NewWindow(
|
||||
innerMarginInt[0]+shift,
|
||||
innerMarginInt[0]+shift+inlineTopLines,
|
||||
innerMarginInt[3],
|
||||
innerWidth,
|
||||
innerHeight-shrink, tui.WindowList, noBorder, true)
|
||||
innerHeight-shrink-inlineTopLines-inlineBottomLines, tui.WindowList, noBorder, true)
|
||||
}
|
||||
|
||||
if len(inlineTop)+len(inlineBottom) > 0 && t.wborder != nil {
|
||||
t.placeInlineStack(inlineTop, t.window.Top()-inlineTopLines, true)
|
||||
t.placeInlineStack(inlineBottom, t.window.Top()+t.window.Height()+inlineBottomLines-1, false)
|
||||
}
|
||||
|
||||
if len(t.scrollbar) == 0 {
|
||||
@@ -2700,7 +2885,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
|
||||
}
|
||||
|
||||
// Set up header border
|
||||
if hasHeaderWindow {
|
||||
if hasHeaderWindow && t.headerBorderShape != tui.BorderInline {
|
||||
var btop int
|
||||
if hasInputWindow && t.headerFirst {
|
||||
if t.layout == layoutReverse {
|
||||
@@ -2728,7 +2913,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
|
||||
}
|
||||
|
||||
// Set up header lines border
|
||||
if hasHeaderLinesWindow {
|
||||
if hasHeaderLinesWindow && headerLinesShape != tui.BorderInline {
|
||||
var btop int
|
||||
// NOTE: We still have to handle --header-first here in case
|
||||
// --header-lines-border is set. Can't we just use header window instead
|
||||
@@ -2761,7 +2946,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
|
||||
}
|
||||
|
||||
// Set up footer
|
||||
if hasFooterWindow {
|
||||
if hasFooterWindow && t.footerBorderShape != tui.BorderInline {
|
||||
var btop int
|
||||
if t.layout == layoutReverse {
|
||||
btop = w.Top() + w.Height()
|
||||
@@ -2778,8 +2963,21 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
|
||||
t.footerWindow = createInnerWindow(t.footerBorder, t.footerBorderShape, tui.WindowFooter, 0)
|
||||
}
|
||||
|
||||
// Print border label
|
||||
t.printLabel(t.wborder, t.listLabel, t.listLabelOpts, t.listLabelLen, t.listBorderShape, false)
|
||||
// When the list label lands on an edge owned by an inline section, swap its bg
|
||||
// so the label reads as part of that section's frame. Fg stays at list-label.
|
||||
listLabel, listLabelLen := t.listLabel, t.listLabelLen
|
||||
var adjacentSection *inlineSlot
|
||||
if t.listLabelOpts.bottom && len(inlineBottom) > 0 {
|
||||
adjacentSection = &inlineBottom[0]
|
||||
} else if !t.listLabelOpts.bottom && len(inlineTop) > 0 {
|
||||
adjacentSection = &inlineTop[0]
|
||||
}
|
||||
if adjacentSection != nil {
|
||||
bg := tui.BorderColor(adjacentSection.windowType).Bg()
|
||||
custom := tui.ColListLabel.WithBg(tui.ColorAttr{Color: bg, Attr: tui.AttrUndefined})
|
||||
listLabel, listLabelLen = t.ansiLabelPrinter(t.listLabelOpts.label, &custom, false)
|
||||
}
|
||||
t.printLabel(t.wborder, listLabel, t.listLabelOpts, listLabelLen, t.listBorderShape, false)
|
||||
t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, false)
|
||||
t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.activePreviewOpts.Border(), false)
|
||||
t.printLabel(t.inputBorder, t.inputLabel, t.inputLabelOpts, t.inputLabelLen, t.inputBorderShape, false)
|
||||
@@ -2787,37 +2985,39 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
|
||||
t.printLabel(t.footerBorder, t.footerLabel, t.footerLabelOpts, t.footerLabelLen, t.footerBorderShape, false)
|
||||
}
|
||||
|
||||
// printLabelAt positions and renders a label at the given row of `window`. Shared by
|
||||
// printLabel (which computes row from the border shape) and the inline-section label
|
||||
// code (which uses an explicit separator row).
|
||||
func (t *Terminal) printLabelAt(window tui.Window, render labelPrinter, opts labelOpts, length int, row int) {
|
||||
if window == nil || render == nil || window.Height() == 0 {
|
||||
return
|
||||
}
|
||||
var col int
|
||||
if opts.column == 0 {
|
||||
col = max(0, (window.Width()-length)/2)
|
||||
} else if opts.column < 0 {
|
||||
col = max(0, window.Width()+opts.column+1-length)
|
||||
} else {
|
||||
col = min(opts.column-1, window.Width()-length)
|
||||
}
|
||||
window.Move(row, col)
|
||||
render(window, window.Width())
|
||||
}
|
||||
|
||||
func (t *Terminal) printLabel(window tui.Window, render labelPrinter, opts labelOpts, length int, borderShape tui.BorderShape, redrawBorder bool) {
|
||||
if window == nil {
|
||||
if window == nil || window.Height() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if window.Height() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
switch borderShape {
|
||||
case tui.BorderHorizontal, tui.BorderTop, tui.BorderBottom, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderBlock, tui.BorderThinBlock, tui.BorderDouble:
|
||||
if redrawBorder {
|
||||
window.DrawHBorder()
|
||||
}
|
||||
if render == nil {
|
||||
return
|
||||
}
|
||||
var col int
|
||||
if opts.column == 0 {
|
||||
col = max(0, (window.Width()-length)/2)
|
||||
} else if opts.column < 0 {
|
||||
col = max(0, window.Width()+opts.column+1-length)
|
||||
} else {
|
||||
col = min(opts.column-1, window.Width()-length)
|
||||
}
|
||||
row := 0
|
||||
if borderShape == tui.BorderBottom || opts.bottom {
|
||||
row = window.Height() - 1
|
||||
}
|
||||
window.Move(row, col)
|
||||
render(window, window.Width())
|
||||
t.printLabelAt(window, render, opts, length, row)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3185,8 +3385,19 @@ func (t *Terminal) resizeIfNeeded() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Inline sections are budget-capped inside wborder, so window.Height() may be
|
||||
// smaller than the requested content length. Treat "capped" (height < want) as
|
||||
// a no-op to avoid triggering a full redraw on every info/header/footer event
|
||||
// when the user has requested more content than fits.
|
||||
mismatch := func(shape tui.BorderShape, height, want int) bool {
|
||||
if shape == tui.BorderInline && height < want {
|
||||
return false
|
||||
}
|
||||
return height != want
|
||||
}
|
||||
|
||||
// Check footer window
|
||||
if len(t.footer) > 0 && (t.footerWindow == nil || t.footerWindow.Height() != len(t.footer)) ||
|
||||
if len(t.footer) > 0 && (t.footerWindow == nil || mismatch(t.footerBorderShape, t.footerWindow.Height(), len(t.footer))) ||
|
||||
len(t.footer) == 0 && t.footerWindow != nil {
|
||||
t.printAll()
|
||||
return true
|
||||
@@ -3200,14 +3411,12 @@ func (t *Terminal) resizeIfNeeded() bool {
|
||||
if needHeaderLinesWindow {
|
||||
primaryHeaderLines -= t.headerLines
|
||||
}
|
||||
// FIXME: Full redraw is triggered if there are too many lines in the header
|
||||
// so that the header window cannot display all of them.
|
||||
if (needHeaderWindow && t.headerWindow == nil) ||
|
||||
(!needHeaderWindow && t.headerWindow != nil) ||
|
||||
(needHeaderWindow && t.headerWindow != nil && primaryHeaderLines != t.headerWindow.Height()) ||
|
||||
(needHeaderWindow && t.headerWindow != nil && mismatch(t.headerBorderShape, t.headerWindow.Height(), primaryHeaderLines)) ||
|
||||
(needHeaderLinesWindow && t.headerLinesWindow == nil) ||
|
||||
(!needHeaderLinesWindow && t.headerLinesWindow != nil) ||
|
||||
(needHeaderLinesWindow && t.headerLinesWindow != nil && t.headerLines != t.headerLinesWindow.Height()) {
|
||||
(needHeaderLinesWindow && t.headerLinesWindow != nil && mismatch(t.headerLinesShape, t.headerLinesWindow.Height(), t.headerLines)) {
|
||||
t.printAll()
|
||||
return true
|
||||
}
|
||||
@@ -3219,14 +3428,21 @@ func (t *Terminal) printHeader() {
|
||||
return
|
||||
}
|
||||
|
||||
t.withWindow(t.headerWindow, func() {
|
||||
var headerItems []Item
|
||||
if !t.hasHeaderLinesWindow() {
|
||||
headerItems = t.header
|
||||
}
|
||||
t.printHeaderImpl(t.headerWindow, t.headerBorderShape, t.header0, headerItems)
|
||||
})
|
||||
if w, shape := t.determineHeaderLinesShape(); w {
|
||||
// When an inline section was requested but addInline had no budget, its window is
|
||||
// nil. Don't fall through to withWindow — that would leak header content into the
|
||||
// list window. A nil window is only legitimate when the shape is NOT inline (e.g.
|
||||
// header combined with the list when --no-list-border is in effect).
|
||||
if !(t.headerBorderShape == tui.BorderInline && t.headerWindow == nil) {
|
||||
t.withWindow(t.headerWindow, func() {
|
||||
var headerItems []Item
|
||||
if !t.hasHeaderLinesWindow() {
|
||||
headerItems = t.header
|
||||
}
|
||||
t.printHeaderImpl(t.headerWindow, t.headerBorderShape, t.header0, headerItems)
|
||||
})
|
||||
}
|
||||
if w, shape := t.determineHeaderLinesShape(); w &&
|
||||
!(shape == tui.BorderInline && t.headerLinesWindow == nil) {
|
||||
t.withWindow(t.headerLinesWindow, func() {
|
||||
t.printHeaderImpl(t.headerLinesWindow, shape, nil, t.header)
|
||||
})
|
||||
@@ -3277,7 +3493,10 @@ func (t *Terminal) headerIndentImpl(base int, borderShape tui.BorderShape) int {
|
||||
if t.listBorderShape.HasLeft() {
|
||||
indentSize += 1 + t.borderWidth
|
||||
}
|
||||
if borderShape.HasLeft() {
|
||||
// Section borders with their own left side skip past the list border's left column.
|
||||
// Inline sections also skip it, but only when the list border actually has a left,
|
||||
// since otherwise the inline window starts flush with the list window.
|
||||
if borderShape.HasLeft() || (borderShape == tui.BorderInline && t.listBorderShape.HasLeft()) {
|
||||
indentSize -= 1 + t.borderWidth
|
||||
if indentSize < 0 {
|
||||
indentSize = 0
|
||||
@@ -5952,11 +6171,28 @@ func (t *Terminal) Loop() error {
|
||||
case reqRedrawInputLabel:
|
||||
t.printLabel(t.inputBorder, t.inputLabel, t.inputLabelOpts, t.inputLabelLen, t.inputBorderShape, true)
|
||||
case reqRedrawHeaderLabel:
|
||||
t.printLabel(t.headerBorder, t.headerLabel, t.headerLabelOpts, t.headerLabelLen, t.headerBorderShape, true)
|
||||
if t.headerBorderShape == tui.BorderInline {
|
||||
// Inline labels sit on the separator inside wborder; re-run the
|
||||
// full layout to repaint the separator + label together.
|
||||
t.printAll()
|
||||
} else {
|
||||
t.printLabel(t.headerBorder, t.headerLabel, t.headerLabelOpts, t.headerLabelLen, t.headerBorderShape, true)
|
||||
}
|
||||
case reqRedrawFooterLabel:
|
||||
t.printLabel(t.footerBorder, t.footerLabel, t.footerLabelOpts, t.footerLabelLen, t.footerBorderShape, true)
|
||||
if t.footerBorderShape == tui.BorderInline {
|
||||
t.printAll()
|
||||
} else {
|
||||
t.printLabel(t.footerBorder, t.footerLabel, t.footerLabelOpts, t.footerLabelLen, t.footerBorderShape, true)
|
||||
}
|
||||
case reqRedrawListLabel:
|
||||
t.printLabel(t.wborder, t.listLabel, t.listLabelOpts, t.listLabelLen, t.listBorderShape, true)
|
||||
// When inline sections are active, the label's bg depends on which
|
||||
// section owns the adjacent edge. Rerun the layout to reuse that
|
||||
// logic rather than duplicating it here.
|
||||
if t.headerBorderShape == tui.BorderInline || t.headerLinesShape == tui.BorderInline || t.footerBorderShape == tui.BorderInline {
|
||||
t.printAll()
|
||||
} else {
|
||||
t.printLabel(t.wborder, t.listLabel, t.listLabelOpts, t.listLabelLen, t.listBorderShape, true)
|
||||
}
|
||||
case reqRedrawBorderLabel:
|
||||
t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, true)
|
||||
case reqRedrawPreviewLabel:
|
||||
|
||||
Reference in New Issue
Block a user