diff options
| author | historia <[not public]> | 2026-08-03 17:15:50 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-08-03 17:15:50 -0400 |
| commit | a2cbc04f86508708daa09affa5e9d73cc689b2ba (patch) | |
| tree | 0d0c6c7fbf4a775faaf5e9dd700157b883882638 /internal/color | |
| parent | adda27dba5bf7bf1dee62159f0cbc0bd90734751 (diff) | |
| download | thehouseoficarus-a2cbc04f86508708daa09affa5e9d73cc689b2ba.tar.gz | |
feat: new color 'command', color tag system implemented
Diffstat (limited to 'internal/color')
| -rw-r--r-- | internal/color/color.go | 37 | ||||
| -rw-r--r-- | internal/color/color_test.go | 58 |
2 files changed, 90 insertions, 5 deletions
diff --git a/internal/color/color.go b/internal/color/color.go index 8e301e4..e89acc8 100644 --- a/internal/color/color.go +++ b/internal/color/color.go @@ -442,6 +442,20 @@ func ExpandTags(mode string, text string) string { } func ExpandTagsDefault(mode string, def ColorSpec, text string) string { + return ExpandTagsDefaultNamed(mode, def, nil, text) +} + +// ExpandTagsNamed expands {tags} in text, additionally resolving a tag whose +// spec matches a key in named (a color category name) to that category's +// ColorSpec. Named hits are treated as real tags even when they resolve to an +// empty spec, so a category disabled with "off" renders its text plain instead +// of leaking the literal tag. Any tag not present in named falls back to the +// ordinary hex/style spec parser. +func ExpandTagsNamed(mode string, named map[string]ColorSpec, text string) string { + return ExpandTagsDefaultNamed(mode, NoColor(), named, text) +} + +func ExpandTagsDefaultNamed(mode string, def ColorSpec, named map[string]ColorSpec, text string) string { if !strings.Contains(text, "{") { if !def.Empty() { return Render(mode, def, text) @@ -491,8 +505,16 @@ func ExpandTagsDefault(mode string, def ColorSpec, text string) string { continue } - spec := Parse(specStr) - if spec.Empty() { + spec, isNamed := NoColor(), false + if named != nil { + if s, ok := named[specStr]; ok { + spec, isNamed = s, true + } + } + if !isNamed { + spec = Parse(specStr) + } + if spec.Empty() && !isNamed { before := text[pos : end+1] if !def.Empty() { sb.WriteString(Render(mode, def, before)) @@ -534,18 +556,23 @@ func ExpandTagsDefault(mode string, def ColorSpec, text string) string { } func ExpandDialogTags(mode string, dialogSpec ColorSpec, text string) string { + return ExpandDialogTagsNamed(mode, dialogSpec, nil, text) +} + +// ExpandDialogTagsNamed is ExpandDialogTags with named-color tag resolution. +func ExpandDialogTagsNamed(mode string, dialogSpec ColorSpec, named map[string]ColorSpec, text string) string { parts := strings.Split(text, `"`) var sb strings.Builder for i, part := range parts { if i%2 == 0 { if part != "" { - sb.WriteString(ExpandTagsDefault(mode, NoColor(), part)) + sb.WriteString(ExpandTagsDefaultNamed(mode, NoColor(), named, part)) } } else { if i < len(parts)-1 { - sb.WriteString(ExpandTagsDefault(mode, dialogSpec, `"`+part+`"`)) + sb.WriteString(ExpandTagsDefaultNamed(mode, dialogSpec, named, `"`+part+`"`)) } else { - sb.WriteString(ExpandTagsDefault(mode, dialogSpec, `"`+part)) + sb.WriteString(ExpandTagsDefaultNamed(mode, dialogSpec, named, `"`+part)) } } } diff --git a/internal/color/color_test.go b/internal/color/color_test.go index f6b79f0..9efa19c 100644 --- a/internal/color/color_test.go +++ b/internal/color/color_test.go @@ -207,3 +207,61 @@ func TestExpandDialogTags_NoColorMode(t *testing.T) { t.Errorf("no-color mode should preserve text, got %q", got) } } + +func TestExpandTagsNamed_ResolvesNamedColor(t *testing.T) { + named := map[string]ColorSpec{"command": Parse("0A bold")} + got := ExpandTagsNamed("xterm256", named, "Type {command}look sign{/} now") + want := StyleCode("bold") + FgCode("xterm256", 10) + if !strings.Contains(got, want+"look sign"+Reset) { + t.Errorf("named tag did not resolve: %q", got) + } + if strings.Contains(got, "{command}") { + t.Errorf("named tag leaked literally: %q", got) + } +} + +func TestExpandTagsNamed_UnknownNameFallsBackToParse(t *testing.T) { + // "0B bold" is not a named key, so it must keep parsing as a spec. + named := map[string]ColorSpec{"command": Parse("0A bold")} + got := ExpandTagsNamed("xterm256", named, "x{0B bold}y{/}z") + if !strings.Contains(got, StyleCode("bold")+FgCode("xterm256", 11)+"y"+Reset) { + t.Errorf("unknown named tag should fall back to spec parsing, got %q", got) + } + // A string that is neither named nor a valid spec stays literal. + got = ExpandTagsNamed("xterm256", named, "{not_a_color}hi{/}") + if !strings.Contains(got, "{not_a_color}hi") { + t.Errorf("unknown tag should remain literal, got %q", got) + } +} + +func TestExpandTagsNamed_OffResolvesPlain(t *testing.T) { + named := map[string]ColorSpec{"command": NoColor()} + got := ExpandTagsNamed("xterm256", named, "A {command}bet{/}.") + if strings.Contains(got, "\033") { + t.Errorf("off category should render plain, got %q", got) + } + if !strings.Contains(got, "A bet.") { + t.Errorf("off category should keep the inner text, got %q", got) + } +} + +func TestExpandTagsNamed_NoColorMode(t *testing.T) { + named := map[string]ColorSpec{"command": Parse("0A bold")} + got := ExpandTagsNamed("none", named, "Type {command}look sign{/} now") + if strings.Contains(got, "\033") { + t.Errorf("no-color mode should strip ANSI, got %q", got) + } + if !strings.Contains(got, "Type look sign now") { + t.Errorf("no-color mode should keep text, got %q", got) + } +} + +func TestExpandDialogTagsNamed_ResolvesInsideQuotes(t *testing.T) { + named := map[string]ColorSpec{"command": Parse("0A bold")} + dialogSpec := Parse("D0") + got := ExpandDialogTagsNamed("xterm256", dialogSpec, named, `"Please be sure {command}look{/} at the sign."`) + want := StyleCode("bold") + FgCode("xterm256", 10) + if !strings.Contains(got, want+"look"+Reset) { + t.Errorf("named tag inside quoted dialog did not resolve: %q", got) + } +} |
