Skip to content

Commit d03764b

Browse files
fix(color): degrade empty parent stack to transparent, chain fillers
Two follow-ups to 76ac76d/cdb4bfe8, found by an independent review: - keywords.go's Resolve gated parent-keyword resolution on parents != nil, not len(parents) != 0. terminal.String() (cdb4bfe) leaves ParentColors non-nil-but-empty after the very first block a process ever renders, so every parentBackground/parentForeground resolved against an empty stack after that point returned the literal keyword string instead of Transparent - which then fails color parsing and renders as no color at all, not even transparent. - Engine.blockTailColors (76ac76d) captured a single *color.Set re-derived from the tail segment's own Resolve*() calls. A segment's stored color can itself be an unresolved parentBackground keyword (normal - full resolution happens later against the live stack), so a one-entry reseed had nowhere further to walk. Capture a full copy of terminal.ParentColors instead of re-deriving one entry, so a filler chains through an unresolved tail exactly like the block's own last segment did. Entire-Checkpoint: 6582863c12cb
1 parent 76ac76d commit d03764b

2 files changed

Lines changed: 19 additions & 14 deletions

File tree

src/color/keywords.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func (color Ansi) Resolve(current *Set, parents []*Set) Ansi {
8484
return current.Background
8585
case keyword == Foreground && current != nil:
8686
return current.Foreground
87-
case (keyword == ParentBackground || keyword == ParentForeground) && parents != nil:
87+
case (keyword == ParentBackground || keyword == ParentForeground) && len(parents) != 0:
8888
return resolveParentColor(keyword)
8989
default:
9090
return Transparent

src/prompt/engine.go

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package prompt
22

33
import (
4+
"slices"
45
"strings"
56
"sync"
67

@@ -25,13 +26,17 @@ type Engine struct {
2526
Config *config.Config
2627
activeSegment *config.Segment
2728
previousActiveSegment *config.Segment
28-
// blockTailColors is the last rendered segment's colors, captured just
29+
// blockTailColors is a snapshot of terminal.ParentColors captured just
2930
// before previousActiveSegment resets to nil at the end of a block. A
3031
// block's own Filler (see shouldFill) renders after terminal.String()
3132
// has already cleared the parent stack for the next block, so a filler
3233
// template using <parentBackground>/<parentForeground> needs this to
3334
// resolve against the block it is padding rather than an empty stack.
34-
blockTailColors *color.Set
35+
// A full copy, not just the tail entry: the tail segment's own stored
36+
// color can itself be an unresolved parentBackground/parentForeground
37+
// keyword, and resolving that requires walking the rest of the chain
38+
// the same way the block's own last segment did.
39+
blockTailColors []*color.Set
3540
pendingSegments sync.Map
3641
rprompt string
3742
Overflow config.Overflow
@@ -196,10 +201,10 @@ func (e *Engine) shouldFill(filler string, padLength int) (string, bool) {
196201
terminal.SetColors("default", "default")
197202

198203
// the block's own segments already reset the parent stack when their
199-
// terminal.String() call produced blockText - reseed the one entry a
204+
// terminal.String() call produced blockText - reseed the chain a
200205
// <parentBackground>/<parentForeground> anchor in the filler needs.
201-
if e.blockTailColors != nil {
202-
terminal.ParentColors = append(terminal.ParentColors, e.blockTailColors)
206+
if len(e.blockTailColors) != 0 {
207+
terminal.ParentColors = append(terminal.ParentColors, e.blockTailColors...)
203208
}
204209

205210
terminal.Write("", "", filler)
@@ -518,21 +523,21 @@ func (e *Engine) renderActiveSegment() {
518523
terminal.SetParentColors(e.previousActiveSegment.ResolveBackground(), e.previousActiveSegment.ResolveForeground())
519524
}
520525

521-
// captureBlockTailColors snapshots the last rendered segment's colors into
522-
// blockTailColors just before previousActiveSegment resets to nil, so a
523-
// later shouldFill call for this same block's Filler can still resolve
526+
// captureBlockTailColors snapshots terminal.ParentColors into blockTailColors
527+
// just before previousActiveSegment resets to nil, so a later shouldFill call
528+
// for this same block's Filler can still resolve
524529
// <parentBackground>/<parentForeground> after terminal.String() has already
525-
// cleared the parent stack for the next block.
530+
// cleared the parent stack for the next block. A full copy of the chain, not
531+
// just the tail entry: the tail segment's own stored color can itself be an
532+
// unresolved parentBackground/parentForeground keyword, which needs the rest
533+
// of the chain to resolve the same way it did for the block's own segments.
526534
func (e *Engine) captureBlockTailColors() {
527535
if e.previousActiveSegment == nil {
528536
e.blockTailColors = nil
529537
return
530538
}
531539

532-
e.blockTailColors = &color.Set{
533-
Background: e.previousActiveSegment.ResolveBackground(),
534-
Foreground: e.previousActiveSegment.ResolveForeground(),
535-
}
540+
e.blockTailColors = slices.Clone(terminal.ParentColors)
536541
}
537542

538543
func (e *Engine) writeSeparator(final bool) {

0 commit comments

Comments
 (0)