refactor: migrate terminal to module

This commit is contained in:
Jan De Dobbeleer 2024-07-01 08:26:15 +02:00 committed by Jan De Dobbeleer
parent 8e2c8eb6b1
commit 0c8b8021b8
16 changed files with 348 additions and 356 deletions

View file

@ -67,17 +67,14 @@ Exports the config to an image file using customized output options.`,
// add variables to the environment // add variables to the environment
env.Var = cfg.Var env.Var = cfg.Var
writerColors := cfg.MakeColors() terminal.Init(shell.GENERIC)
writer := &terminal.Writer{ terminal.BackgroundColor = shell.ConsoleBackgroundColor(env, cfg.TerminalBackground)
BackgroundColor: shell.ConsoleBackgroundColor(env, cfg.TerminalBackground), terminal.AnsiColors = cfg.MakeColors()
AnsiColors: writerColors, terminal.TrueColor = env.CmdFlags.TrueColor
TrueColor: env.CmdFlags.TrueColor,
}
writer.Init(shell.GENERIC)
eng := &engine.Engine{ eng := &engine.Engine{
Config: cfg, Config: cfg,
Env: env, Env: env,
Writer: writer,
} }
prompt := eng.Primary() prompt := eng.Primary()
@ -86,7 +83,6 @@ Exports the config to an image file using customized output options.`,
AnsiString: prompt, AnsiString: prompt,
Author: author, Author: author,
BgColor: bgColor, BgColor: bgColor,
Ansi: writer,
} }
if outputImage != "" { if outputImage != "" {

View file

@ -40,19 +40,15 @@ var debugCmd = &cobra.Command{
// add variables to the environment // add variables to the environment
env.Var = cfg.Var env.Var = cfg.Var
writerColors := cfg.MakeColors() terminal.Init(shell.GENERIC)
writer := &terminal.Writer{ terminal.BackgroundColor = shell.ConsoleBackgroundColor(env, cfg.TerminalBackground)
BackgroundColor: shell.ConsoleBackgroundColor(env, cfg.TerminalBackground), terminal.AnsiColors = cfg.MakeColors()
AnsiColors: writerColors, terminal.Plain = plain
Plain: plain, terminal.TrueColor = env.CmdFlags.TrueColor
TrueColor: env.CmdFlags.TrueColor,
}
writer.Init(shell.GENERIC)
eng := &engine.Engine{ eng := &engine.Engine{
Config: cfg, Config: cfg,
Env: env, Env: env,
Writer: writer,
Plain: plain, Plain: plain,
} }

View file

@ -5,8 +5,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/jandedobbeleer/oh-my-posh/src/terminal"
"github.com/jandedobbeleer/oh-my-posh/src/platform" "github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/terminal"
color2 "github.com/gookit/color" color2 "github.com/gookit/color"
"github.com/spf13/cobra" "github.com/spf13/cobra"

View file

@ -56,25 +56,21 @@ type Block struct {
MinWidth int `json:"min_width,omitempty" toml:"min_width,omitempty"` MinWidth int `json:"min_width,omitempty" toml:"min_width,omitempty"`
env platform.Environment env platform.Environment
writer *terminal.Writer
activeSegment *Segment activeSegment *Segment
previousActiveSegment *Segment previousActiveSegment *Segment
} }
func (b *Block) Init(env platform.Environment, writer *terminal.Writer) { func (b *Block) Init(env platform.Environment) {
b.env = env b.env = env
b.writer = writer
b.executeSegmentLogic() b.executeSegmentLogic()
} }
func (b *Block) InitPlain(env platform.Environment, config *Config) { func (b *Block) InitPlain(env platform.Environment, config *Config) {
b.writer = &terminal.Writer{ terminal.Init(shell.GENERIC)
BackgroundColor: shell.ConsoleBackgroundColor(env, config.TerminalBackground), terminal.BackgroundColor = shell.ConsoleBackgroundColor(env, config.TerminalBackground)
AnsiColors: config.MakeColors(), terminal.AnsiColors = config.MakeColors()
TrueColor: env.Flags().TrueColor, terminal.TrueColor = env.Flags().TrueColor
}
b.writer.Init(shell.GENERIC)
b.env = env b.env = env
b.executeSegmentLogic() b.executeSegmentLogic()
} }
@ -90,8 +86,8 @@ func (b *Block) executeSegmentLogic() {
func (b *Block) setActiveSegment(segment *Segment) { func (b *Block) setActiveSegment(segment *Segment) {
b.activeSegment = segment b.activeSegment = segment
b.writer.Interactive = segment.Interactive terminal.Interactive = segment.Interactive
b.writer.SetColors(segment.background(), segment.foreground()) terminal.SetColors(segment.background(), segment.foreground())
} }
func (b *Block) Enabled() bool { func (b *Block) Enabled() bool {
@ -156,7 +152,7 @@ func (b *Block) RenderSegments() (string, int) {
b.writeSeparator(true) b.writeSeparator(true)
return b.writer.String() return terminal.String()
} }
func (b *Block) filterSegments() { func (b *Block) filterSegments() {
@ -177,27 +173,27 @@ func (b *Block) renderActiveSegment() {
b.writeSeparator(false) b.writeSeparator(false)
switch b.activeSegment.style() { switch b.activeSegment.style() {
case Plain, Powerline: case Plain, Powerline:
b.writer.Write(terminal.Background, terminal.Foreground, b.activeSegment.text) terminal.Write(terminal.Background, terminal.Foreground, b.activeSegment.text)
case Diamond: case Diamond:
background := terminal.Transparent background := terminal.Transparent
if b.previousActiveSegment != nil && b.previousActiveSegment.hasEmptyDiamondAtEnd() { if b.previousActiveSegment != nil && b.previousActiveSegment.hasEmptyDiamondAtEnd() {
background = b.previousActiveSegment.background() background = b.previousActiveSegment.background()
} }
b.writer.Write(background, terminal.Background, b.activeSegment.LeadingDiamond) terminal.Write(background, terminal.Background, b.activeSegment.LeadingDiamond)
b.writer.Write(terminal.Background, terminal.Foreground, b.activeSegment.text) terminal.Write(terminal.Background, terminal.Foreground, b.activeSegment.text)
case Accordion: case Accordion:
if b.activeSegment.Enabled { if b.activeSegment.Enabled {
b.writer.Write(terminal.Background, terminal.Foreground, b.activeSegment.text) terminal.Write(terminal.Background, terminal.Foreground, b.activeSegment.text)
} }
} }
b.previousActiveSegment = b.activeSegment b.previousActiveSegment = b.activeSegment
b.writer.SetParentColors(b.previousActiveSegment.background(), b.previousActiveSegment.foreground()) terminal.SetParentColors(b.previousActiveSegment.background(), b.previousActiveSegment.foreground())
} }
func (b *Block) writeSeparator(final bool) { func (b *Block) writeSeparator(final bool) {
isCurrentDiamond := b.activeSegment.style() == Diamond isCurrentDiamond := b.activeSegment.style() == Diamond
if final && isCurrentDiamond { if final && isCurrentDiamond {
b.writer.Write(terminal.Transparent, terminal.Background, b.activeSegment.TrailingDiamond) terminal.Write(terminal.Transparent, terminal.Background, b.activeSegment.TrailingDiamond)
return return
} }
@ -207,12 +203,12 @@ func (b *Block) writeSeparator(final bool) {
} }
if isPreviousDiamond && isCurrentDiamond && len(b.activeSegment.LeadingDiamond) == 0 { if isPreviousDiamond && isCurrentDiamond && len(b.activeSegment.LeadingDiamond) == 0 {
b.writer.Write(terminal.Background, terminal.ParentBackground, b.previousActiveSegment.TrailingDiamond) terminal.Write(terminal.Background, terminal.ParentBackground, b.previousActiveSegment.TrailingDiamond)
return return
} }
if isPreviousDiamond && len(b.previousActiveSegment.TrailingDiamond) > 0 { if isPreviousDiamond && len(b.previousActiveSegment.TrailingDiamond) > 0 {
b.writer.Write(terminal.Transparent, terminal.ParentBackground, b.previousActiveSegment.TrailingDiamond) terminal.Write(terminal.Transparent, terminal.ParentBackground, b.previousActiveSegment.TrailingDiamond)
} }
isPowerline := b.activeSegment.isPowerline() isPowerline := b.activeSegment.isPowerline()
@ -234,7 +230,7 @@ func (b *Block) writeSeparator(final bool) {
} }
if shouldOverridePowerlineLeadingSymbol() { if shouldOverridePowerlineLeadingSymbol() {
b.writer.Write(terminal.Transparent, terminal.Background, b.activeSegment.LeadingPowerlineSymbol) terminal.Write(terminal.Transparent, terminal.Background, b.activeSegment.LeadingPowerlineSymbol)
return return
} }
@ -265,11 +261,11 @@ func (b *Block) writeSeparator(final bool) {
} }
if b.activeSegment.InvertPowerline { if b.activeSegment.InvertPowerline {
b.writer.Write(b.getPowerlineColor(), bgColor, symbol) terminal.Write(b.getPowerlineColor(), bgColor, symbol)
return return
} }
b.writer.Write(bgColor, b.getPowerlineColor(), symbol) terminal.Write(bgColor, b.getPowerlineColor(), symbol)
} }
func (b *Block) adjustTrailingDiamondColorOverrides() { func (b *Block) adjustTrailingDiamondColorOverrides() {

View file

@ -16,7 +16,6 @@ var (
type Engine struct { type Engine struct {
Config *Config Config *Config
Env platform.Environment Env platform.Environment
Writer *terminal.Writer
Plain bool Plain bool
console strings.Builder console strings.Builder
@ -73,10 +72,10 @@ func (e *Engine) writeRPrompt() {
if !OK { if !OK {
return return
} }
e.write(e.Writer.SaveCursorPosition()) e.write(terminal.SaveCursorPosition())
e.write(strings.Repeat(" ", space)) e.write(strings.Repeat(" ", space))
e.write(e.rprompt) e.write(e.rprompt)
e.write(e.Writer.RestoreCursorPosition()) e.write(terminal.RestoreCursorPosition())
} }
func (e *Engine) pwd() { func (e *Engine) pwd() {
@ -94,7 +93,7 @@ func (e *Engine) pwd() {
// Backwards compatibility for deprecated OSC99 // Backwards compatibility for deprecated OSC99
if e.Config.OSC99 { if e.Config.OSC99 {
e.write(e.Writer.ConsolePwd(terminal.OSC99, "", "", cwd)) e.write(terminal.ConsolePwd(terminal.OSC99, "", "", cwd))
return return
} }
@ -111,7 +110,7 @@ func (e *Engine) pwd() {
user := e.Env.User() user := e.Env.User()
host, _ := e.Env.Host() host, _ := e.Env.Host()
e.write(e.Writer.ConsolePwd(pwdType, user, host, cwd)) e.write(terminal.ConsolePwd(pwdType, user, host, cwd))
} }
func (e *Engine) newline() { func (e *Engine) newline() {
@ -121,7 +120,7 @@ func (e *Engine) newline() {
// WARP terminal will remove \n from the prompt, so we hack a newline in // WARP terminal will remove \n from the prompt, so we hack a newline in
if e.isWarp() { if e.isWarp() {
e.write(e.Writer.LineBreak()) e.write(terminal.LineBreak())
return return
} }
@ -155,8 +154,8 @@ func (e *Engine) shouldFill(filler string, remaining, blockLength int) (string,
} }
// allow for easy color overrides and templates // allow for easy color overrides and templates
e.Writer.Write("", "", filler) terminal.Write("", "", filler)
filler, lenFiller := e.Writer.String() filler, lenFiller := terminal.String()
if lenFiller == 0 { if lenFiller == 0 {
return "", false return "", false
} }
@ -197,7 +196,7 @@ func (e *Engine) renderBlock(block *Block, cancelNewline bool) bool {
if e.Env.Shell() == shell.BASH && block.Type == RPrompt { if e.Env.Shell() == shell.BASH && block.Type == RPrompt {
block.InitPlain(e.Env, e.Config) block.InitPlain(e.Env, e.Config)
} else { } else {
block.Init(e.Env, e.Writer) block.Init(e.Env)
} }
if !block.Enabled() { if !block.Enabled() {
@ -221,7 +220,7 @@ func (e *Engine) renderBlock(block *Block, cancelNewline bool) bool {
switch block.Type { //nolint:exhaustive switch block.Type { //nolint:exhaustive
case Prompt: case Prompt:
if block.VerticalOffset != 0 { if block.VerticalOffset != 0 {
e.write(e.Writer.ChangeLine(block.VerticalOffset)) e.write(terminal.ChangeLine(block.VerticalOffset))
} }
if block.Alignment == Left { if block.Alignment == Left {
@ -292,5 +291,5 @@ func (e *Engine) patchPowerShellBleed() {
return return
} }
e.write(e.Writer.ClearAfter()) e.write(terminal.ClearAfter())
} }

View file

@ -85,18 +85,19 @@ func TestPrintPWD(t *testing.T) {
Shell: "shell", Shell: "shell",
}) })
writer := &terminal.Writer{} terminal.Init(shell.GENERIC)
writer.Init(shell.GENERIC)
engine := &Engine{ engine := &Engine{
Env: env, Env: env,
Config: &Config{ Config: &Config{
PWD: tc.Config, PWD: tc.Config,
OSC99: tc.OSC99, OSC99: tc.OSC99,
}, },
Writer: writer,
} }
engine.pwd() engine.pwd()
got := engine.string() got := engine.string()
assert.Equal(t, tc.Expected, got, tc.Case) assert.Equal(t, tc.Expected, got, tc.Case)
} }
} }
@ -114,17 +115,14 @@ func engineRender() {
cfg := LoadConfig(env) cfg := LoadConfig(env)
writerColors := cfg.MakeColors() terminal.Init(shell.GENERIC)
writer := &terminal.Writer{ terminal.BackgroundColor = shell.ConsoleBackgroundColor(env, cfg.TerminalBackground)
BackgroundColor: shell.ConsoleBackgroundColor(env, cfg.TerminalBackground), terminal.AnsiColors = cfg.MakeColors()
AnsiColors: writerColors, terminal.TrueColor = env.CmdFlags.TrueColor
TrueColor: env.CmdFlags.TrueColor,
}
writer.Init(shell.GENERIC)
engine := &Engine{ engine := &Engine{
Config: cfg, Config: cfg,
Env: env, Env: env,
Writer: writer,
} }
engine.Primary() engine.Primary()
@ -188,17 +186,19 @@ func TestGetTitle(t *testing.T) {
PWD: tc.Cwd, PWD: tc.Cwd,
Folder: "vagrant", Folder: "vagrant",
}) })
writer := &terminal.Writer{}
writer.Init(shell.GENERIC) terminal.Init(shell.GENERIC)
engine := &Engine{ engine := &Engine{
Config: &Config{ Config: &Config{
ConsoleTitleTemplate: tc.Template, ConsoleTitleTemplate: tc.Template,
}, },
Writer: writer, Env: env,
Env: env,
} }
title := engine.getTitleTemplateText() title := engine.getTitleTemplateText()
got := writer.FormatTitle(title) got := terminal.FormatTitle(title)
assert.Equal(t, tc.Expected, got) assert.Equal(t, tc.Expected, got)
} }
} }
@ -247,17 +247,19 @@ func TestGetConsoleTitleIfGethostnameReturnsError(t *testing.T) {
Root: tc.Root, Root: tc.Root,
HostName: "", HostName: "",
}) })
writer := &terminal.Writer{}
writer.Init(shell.GENERIC) terminal.Init(shell.GENERIC)
engine := &Engine{ engine := &Engine{
Config: &Config{ Config: &Config{
ConsoleTitleTemplate: tc.Template, ConsoleTitleTemplate: tc.Template,
}, },
Writer: writer, Env: env,
Env: env,
} }
title := engine.getTitleTemplateText() title := engine.getTitleTemplateText()
got := writer.FormatTitle(title) got := terminal.FormatTitle(title)
assert.Equal(t, tc.Expected, got) assert.Equal(t, tc.Expected, got)
} }
} }

View file

@ -114,7 +114,6 @@ type ImageRenderer struct {
CursorPadding int CursorPadding int
RPromptOffset int RPromptOffset int
BgColor string BgColor string
Ansi *terminal.Writer
env platform.Environment env platform.Environment

View file

@ -34,11 +34,10 @@ func runImageTest(config, content string) (string, error) {
} }
defer os.Remove(file.Name()) defer os.Remove(file.Name())
writer := &terminal.Writer{} terminal.Init(shell.GENERIC)
writer.Init(shell.GENERIC)
image := &ImageRenderer{ image := &ImageRenderer{
AnsiString: content, AnsiString: content,
Ansi: writer,
} }
env := &platform.Shell{ env := &platform.Shell{

View file

@ -24,18 +24,15 @@ func New(flags *platform.Flags) *Engine {
env.Var = cfg.Var env.Var = cfg.Var
flags.HasTransient = cfg.TransientPrompt != nil flags.HasTransient = cfg.TransientPrompt != nil
ansiWriter := &terminal.Writer{ terminal.Init(env.Shell())
BackgroundColor: shell.ConsoleBackgroundColor(env, cfg.TerminalBackground), terminal.BackgroundColor = shell.ConsoleBackgroundColor(env, cfg.TerminalBackground)
AnsiColors: cfg.MakeColors(), terminal.AnsiColors = cfg.MakeColors()
Plain: flags.Plain, terminal.Plain = flags.Plain
TrueColor: env.CmdFlags.TrueColor, terminal.TrueColor = env.CmdFlags.TrueColor
}
ansiWriter.Init(env.Shell())
eng := &Engine{ eng := &Engine{
Config: cfg, Config: cfg,
Env: env, Env: env,
Writer: ansiWriter,
Plain: flags.Plain, Plain: flags.Plain,
} }

View file

@ -22,8 +22,8 @@ const (
func (e *Engine) Primary() string { func (e *Engine) Primary() string {
if e.Config.ShellIntegration { if e.Config.ShellIntegration {
exitCode, _ := e.Env.StatusCodes() exitCode, _ := e.Env.StatusCodes()
e.write(e.Writer.CommandFinished(exitCode, e.Env.Flags().NoExitCode)) e.write(terminal.CommandFinished(exitCode, e.Env.Flags().NoExitCode))
e.write(e.Writer.PromptStart()) e.write(terminal.PromptStart())
} }
// cache a pointer to the color cycle // cache a pointer to the color cycle
@ -60,7 +60,7 @@ func (e *Engine) Primary() string {
if len(e.Config.ConsoleTitleTemplate) > 0 && !e.Env.Flags().Plain { if len(e.Config.ConsoleTitleTemplate) > 0 && !e.Env.Flags().Plain {
title := e.getTitleTemplateText() title := e.getTitleTemplateText()
e.write(e.Writer.FormatTitle(title)) e.write(terminal.FormatTitle(title))
} }
if e.Config.FinalSpace { if e.Config.FinalSpace {
@ -70,11 +70,11 @@ func (e *Engine) Primary() string {
if e.Config.ITermFeatures != nil && e.isIterm() { if e.Config.ITermFeatures != nil && e.isIterm() {
host, _ := e.Env.Host() host, _ := e.Env.Host()
e.write(e.Writer.RenderItermFeatures(e.Config.ITermFeatures, e.Env.Shell(), e.Env.Pwd(), e.Env.User(), host)) e.write(terminal.RenderItermFeatures(e.Config.ITermFeatures, e.Env.Shell(), e.Env.Pwd(), e.Env.User(), host))
} }
if e.Config.ShellIntegration && e.Config.TransientPrompt == nil { if e.Config.ShellIntegration && e.Config.TransientPrompt == nil {
e.write(e.Writer.CommandStart()) e.write(terminal.CommandStart())
} }
e.pwd() e.pwd()
@ -104,15 +104,15 @@ func (e *Engine) Primary() string {
} }
// in bash, the entire rprompt needs to be escaped for the prompt to be interpreted correctly // in bash, the entire rprompt needs to be escaped for the prompt to be interpreted correctly
// see https://github.com/jandedobbeleer/oh-my-posh/pull/2398 // see https://github.com/jandedobbeleer/oh-my-posh/pull/2398
writer := &terminal.Writer{
TrueColor: e.Env.Flags().TrueColor, terminal.Init(shell.GENERIC)
} terminal.TrueColor = e.Env.Flags().TrueColor
writer.Init(shell.GENERIC)
prompt := writer.SaveCursorPosition() prompt := terminal.SaveCursorPosition()
prompt += strings.Repeat(" ", space) prompt += strings.Repeat(" ", space)
prompt += e.rprompt prompt += e.rprompt
prompt += writer.RestoreCursorPosition() prompt += terminal.RestoreCursorPosition()
prompt = e.Writer.EscapeText(prompt) prompt = terminal.EscapeText(prompt)
e.write(prompt) e.write(prompt)
} }
@ -168,16 +168,16 @@ func (e *Engine) ExtraPrompt(promptType ExtraPromptType) string {
if promptType == Transient && e.Config.ShellIntegration { if promptType == Transient && e.Config.ShellIntegration {
exitCode, _ := e.Env.StatusCodes() exitCode, _ := e.Env.StatusCodes()
e.write(e.Writer.CommandFinished(exitCode, e.Env.Flags().NoExitCode)) e.write(terminal.CommandFinished(exitCode, e.Env.Flags().NoExitCode))
e.write(e.Writer.PromptStart()) e.write(terminal.PromptStart())
} }
foreground := prompt.ForegroundTemplates.FirstMatch(nil, e.Env, prompt.Foreground) foreground := prompt.ForegroundTemplates.FirstMatch(nil, e.Env, prompt.Foreground)
background := prompt.BackgroundTemplates.FirstMatch(nil, e.Env, prompt.Background) background := prompt.BackgroundTemplates.FirstMatch(nil, e.Env, prompt.Background)
e.Writer.SetColors(background, foreground) terminal.SetColors(background, foreground)
e.Writer.Write(background, foreground, promptText) terminal.Write(background, foreground, promptText)
str, length := e.Writer.String() str, length := terminal.String()
if promptType == Transient { if promptType == Transient {
consoleWidth, err := e.Env.TerminalWidth() consoleWidth, err := e.Env.TerminalWidth()
if err == nil || consoleWidth != 0 { if err == nil || consoleWidth != 0 {
@ -188,7 +188,7 @@ func (e *Engine) ExtraPrompt(promptType ExtraPromptType) string {
} }
if promptType == Transient && e.Config.ShellIntegration { if promptType == Transient && e.Config.ShellIntegration {
str += e.Writer.CommandStart() str += terminal.CommandStart()
} }
switch e.Env.Shell() { switch e.Env.Shell() {
@ -205,7 +205,7 @@ func (e *Engine) ExtraPrompt(promptType ExtraPromptType) string {
// Return the string and empty our buffer // Return the string and empty our buffer
// clear the line afterwards to prevent text from being written on the same line // clear the line afterwards to prevent text from being written on the same line
// see https://github.com/JanDeDobbeleer/oh-my-posh/issues/3628 // see https://github.com/JanDeDobbeleer/oh-my-posh/issues/3628
return str + e.Writer.ClearAfter() return str + terminal.ClearAfter()
case shell.CMD, shell.BASH, shell.FISH, shell.NU, shell.GENERIC: case shell.CMD, shell.BASH, shell.FISH, shell.NU, shell.GENERIC:
// Return the string and empty our buffer // Return the string and empty our buffer
return str return str
@ -229,7 +229,7 @@ func (e *Engine) RPrompt() string {
return "" return ""
} }
block.Init(e.Env, e.Writer) block.Init(e.Env)
if !block.Enabled() { if !block.Enabled() {
return "" return ""
} }

View file

@ -4,6 +4,7 @@ import (
"strings" "strings"
"github.com/jandedobbeleer/oh-my-posh/src/shell" "github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/jandedobbeleer/oh-my-posh/src/terminal"
) )
func (e *Engine) Tooltip(tip string) string { func (e *Engine) Tooltip(tip string) string {
@ -38,7 +39,7 @@ func (e *Engine) Tooltip(tip string) string {
switch e.Env.Shell() { switch e.Env.Shell() {
case shell.ZSH, shell.CMD, shell.FISH, shell.GENERIC: case shell.ZSH, shell.CMD, shell.FISH, shell.GENERIC:
block.Init(e.Env, e.Writer) block.Init(e.Env)
if !block.Enabled() { if !block.Enabled() {
return "" return ""
} }
@ -63,7 +64,7 @@ func (e *Engine) Tooltip(tip string) string {
} }
// clear from cursor to the end of the line in case a previous tooltip // clear from cursor to the end of the line in case a previous tooltip
// is cut off and partially preserved, if the new one is shorter // is cut off and partially preserved, if the new one is shorter
e.write(e.Writer.ClearAfter()) e.write(terminal.ClearAfter())
e.write(strings.Repeat(" ", space)) e.write(strings.Repeat(" ", space))
e.write(text) e.write(text)
return e.string() return e.string()

View file

@ -8,7 +8,7 @@ func GetAccentColor(_ platform.Environment) (*RGB, error) {
return nil, &platform.NotImplemented{} return nil, &platform.NotImplemented{}
} }
func (d *DefaultColors) SetAccentColor(env platform.Environment, defaultColor string) { func (d *DefaultColors) SetAccentColor(_ platform.Environment, defaultColor string) {
if len(defaultColor) == 0 { if len(defaultColor) == 0 {
return return
} }

View file

@ -28,7 +28,7 @@ func (f ITermFeatures) Contains(feature iTermFeature) bool {
return false return false
} }
func (w *Writer) RenderItermFeatures(features ITermFeatures, sh, pwd, user, host string) string { func RenderItermFeatures(features ITermFeatures, sh, pwd, user, host string) string {
supportedShells := []string{shell.BASH, shell.ZSH} supportedShells := []string{shell.BASH, shell.ZSH}
var result strings.Builder var result strings.Builder
@ -39,11 +39,11 @@ func (w *Writer) RenderItermFeatures(features ITermFeatures, sh, pwd, user, host
continue continue
} }
result.WriteString(w.formats.iTermPromptMark) result.WriteString(formats.iTermPromptMark)
case CurrentDir: case CurrentDir:
result.WriteString(fmt.Sprintf(w.formats.iTermCurrentDir, pwd)) result.WriteString(fmt.Sprintf(formats.iTermCurrentDir, pwd))
case RemoteHost: case RemoteHost:
result.WriteString(fmt.Sprintf(w.formats.iTermRemoteHost, user, host)) result.WriteString(fmt.Sprintf(formats.iTermRemoteHost, user, host))
} }
} }

View file

@ -39,6 +39,33 @@ var (
resetStyle = &style{AnchorStart: "RESET", AnchorEnd: `</>`, End: "\x1b[0m"} resetStyle = &style{AnchorStart: "RESET", AnchorEnd: `</>`, End: "\x1b[0m"}
backgroundStyle = &style{AnchorStart: "BACKGROUND", AnchorEnd: `</>`, End: "\x1b[49m"} backgroundStyle = &style{AnchorStart: "BACKGROUND", AnchorEnd: `</>`, End: "\x1b[49m"}
BackgroundColor string
CurrentColors *Colors
ParentColors []*Colors
AnsiColors ColorString
Plain bool
TrueColor bool
Interactive bool
builder strings.Builder
length int
foregroundColor Color
backgroundColor Color
currentColor ColorHistory
runes []rune
isTransparent bool
isInvisible bool
isHyperlink bool
lastRune rune
shellName string
formats *shellFormats
) )
type shellFormats struct { type shellFormats struct {
@ -104,42 +131,12 @@ const (
hyperLinkTextEnd = "</TEXT>" hyperLinkTextEnd = "</TEXT>"
) )
// Writer writes colorized ANSI strings func Init(sh string) {
type Writer struct { shellName = sh
BackgroundColor string
CurrentColors *Colors
ParentColors []*Colors
AnsiColors ColorString
Plain bool switch shellName {
TrueColor bool
Interactive bool
builder strings.Builder
length int
foregroundColor Color
backgroundColor Color
currentColor ColorHistory
runes []rune
isTransparent bool
isInvisible bool
isHyperlink bool
lastRune rune
shellName string
formats *shellFormats
}
func (w *Writer) Init(shellName string) {
w.shellName = shellName
switch w.shellName {
case shell.BASH: case shell.BASH:
w.formats = &shellFormats{ formats = &shellFormats{
escape: "\\[%s\\]", escape: "\\[%s\\]",
linechange: "\\[\x1b[%d%s\\]", linechange: "\\[\x1b[%d%s\\]",
left: "\\[\x1b[%dD\\]", left: "\\[\x1b[%dD\\]",
@ -163,7 +160,7 @@ func (w *Writer) Init(shellName string) {
}, },
} }
case shell.ZSH, shell.TCSH: case shell.ZSH, shell.TCSH:
w.formats = &shellFormats{ formats = &shellFormats{
escape: "%%{%s%%}", escape: "%%{%s%%}",
linechange: "%%{\x1b[%d%s%%}", linechange: "%%{\x1b[%d%s%%}",
left: "%%{\x1b[%dD%%}", left: "%%{\x1b[%dD%%}",
@ -183,7 +180,7 @@ func (w *Writer) Init(shellName string) {
iTermRemoteHost: "%%{\x1b]1337;RemoteHost=%s@%s\x07%%}", iTermRemoteHost: "%%{\x1b]1337;RemoteHost=%s@%s\x07%%}",
} }
default: default:
w.formats = &shellFormats{ formats = &shellFormats{
escape: "%s", escape: "%s",
linechange: "\x1b[%d%s", linechange: "\x1b[%d%s",
left: "\x1b[%dD", left: "\x1b[%dD",
@ -207,33 +204,33 @@ func (w *Writer) Init(shellName string) {
} }
if shellName == shell.ZSH { if shellName == shell.ZSH {
w.formats.escapeSequences = map[rune]rune{ formats.escapeSequences = map[rune]rune{
96: 92, // backtick 96: 92, // backtick
37: 37, // % 37: 37, // %
} }
} }
} }
func (w *Writer) SetColors(background, foreground string) { func SetColors(background, foreground string) {
w.CurrentColors = &Colors{ CurrentColors = &Colors{
Background: background, Background: background,
Foreground: foreground, Foreground: foreground,
} }
} }
func (w *Writer) SetParentColors(background, foreground string) { func SetParentColors(background, foreground string) {
if w.ParentColors == nil { if ParentColors == nil {
w.ParentColors = make([]*Colors, 0) ParentColors = make([]*Colors, 0)
} }
w.ParentColors = append([]*Colors{{ ParentColors = append([]*Colors{{
Background: background, Background: background,
Foreground: foreground, Foreground: foreground,
}}, w.ParentColors...) }}, ParentColors...)
} }
func (w *Writer) ChangeLine(numberOfLines int) string { func ChangeLine(numberOfLines int) string {
if w.Plain { if Plain {
return "" return ""
} }
@ -244,11 +241,11 @@ func (w *Writer) ChangeLine(numberOfLines int) string {
numberOfLines = -numberOfLines numberOfLines = -numberOfLines
} }
return fmt.Sprintf(w.formats.linechange, numberOfLines, position) return fmt.Sprintf(formats.linechange, numberOfLines, position)
} }
func (w *Writer) ConsolePwd(pwdType, userName, hostName, pwd string) string { func ConsolePwd(pwdType, userName, hostName, pwd string) string {
if w.Plain { if Plain {
return "" return ""
} }
@ -258,33 +255,33 @@ func (w *Writer) ConsolePwd(pwdType, userName, hostName, pwd string) string {
switch pwdType { switch pwdType {
case OSC7: case OSC7:
return fmt.Sprintf(w.formats.osc7, hostName, pwd) return fmt.Sprintf(formats.osc7, hostName, pwd)
case OSC51: case OSC51:
return fmt.Sprintf(w.formats.osc51, userName, hostName, pwd) return fmt.Sprintf(formats.osc51, userName, hostName, pwd)
case OSC99: case OSC99:
fallthrough fallthrough
default: default:
return fmt.Sprintf(w.formats.osc99, pwd) return fmt.Sprintf(formats.osc99, pwd)
} }
} }
func (w *Writer) ClearAfter() string { func ClearAfter() string {
if w.Plain { if Plain {
return "" return ""
} }
return w.formats.clearLine + w.formats.clearBelow return formats.clearLine + formats.clearBelow
} }
func (w *Writer) FormatTitle(title string) string { func FormatTitle(title string) string {
title = w.trimAnsi(title) title = trimAnsi(title)
if w.Plain { if Plain {
return title return title
} }
// we have to do this to prevent bash/zsh from misidentifying escape sequences // we have to do this to prevent bash/zsh from misidentifying escape sequences
switch w.shellName { switch shellName {
case shell.BASH: case shell.BASH:
title = strings.NewReplacer("`", "\\`", `\`, `\\`).Replace(title) title = strings.NewReplacer("`", "\\`", `\`, `\\`).Replace(title)
case shell.ZSH: case shell.ZSH:
@ -294,55 +291,55 @@ func (w *Writer) FormatTitle(title string) string {
return "" return ""
} }
return fmt.Sprintf(w.formats.title, title) return fmt.Sprintf(formats.title, title)
} }
func (w *Writer) EscapeText(text string) string { func EscapeText(text string) string {
return fmt.Sprintf(w.formats.escape, text) return fmt.Sprintf(formats.escape, text)
} }
func (w *Writer) SaveCursorPosition() string { func SaveCursorPosition() string {
return w.formats.saveCursorPosition return formats.saveCursorPosition
} }
func (w *Writer) RestoreCursorPosition() string { func RestoreCursorPosition() string {
return w.formats.restoreCursorPosition return formats.restoreCursorPosition
} }
func (w *Writer) PromptStart() string { func PromptStart() string {
return fmt.Sprintf(w.formats.escape, "\x1b]133;A\007") return fmt.Sprintf(formats.escape, "\x1b]133;A\007")
} }
func (w *Writer) CommandStart() string { func CommandStart() string {
return fmt.Sprintf(w.formats.escape, "\x1b]133;B\007") return fmt.Sprintf(formats.escape, "\x1b]133;B\007")
} }
func (w *Writer) CommandFinished(code int, ignore bool) string { func CommandFinished(code int, ignore bool) string {
if ignore { if ignore {
return fmt.Sprintf(w.formats.escape, "\x1b]133;D\007") return fmt.Sprintf(formats.escape, "\x1b]133;D\007")
} }
mark := fmt.Sprintf("\x1b]133;D;%d\007", code) mark := fmt.Sprintf("\x1b]133;D;%d\007", code)
return fmt.Sprintf(w.formats.escape, mark) return fmt.Sprintf(formats.escape, mark)
} }
func (w *Writer) LineBreak() string { func LineBreak() string {
cr := fmt.Sprintf(w.formats.left, 1000) cr := fmt.Sprintf(formats.left, 1000)
lf := fmt.Sprintf(w.formats.linechange, 1, "B") lf := fmt.Sprintf(formats.linechange, 1, "B")
return cr + lf return cr + lf
} }
func (w *Writer) Write(background, foreground, text string) { func Write(background, foreground, text string) {
if len(text) == 0 { if len(text) == 0 {
return return
} }
w.backgroundColor, w.foregroundColor = w.asAnsiColors(background, foreground) backgroundColor, foregroundColor = asAnsiColors(background, foreground)
// default to white foreground // default to white foreground
if w.foregroundColor.IsEmpty() { if foregroundColor.IsEmpty() {
w.foregroundColor = w.AnsiColors.ToColor("white", false, w.TrueColor) foregroundColor = AnsiColors.ToColor("white", false, TrueColor)
} }
// validate if we start with a color override // validate if we start with a color override
@ -354,182 +351,185 @@ func (w *Writer) Write(background, foreground, text string) {
continue continue
} }
w.writeEscapedAnsiString(style.Start) writeEscapedAnsiString(style.Start)
colorOverride = false colorOverride = false
} }
if colorOverride { if colorOverride {
w.currentColor.Add(w.asAnsiColors(match[BG], match[FG])) currentColor.Add(asAnsiColors(match[BG], match[FG]))
} }
} }
w.writeSegmentColors() writeSegmentColors()
// print the hyperlink part AFTER the coloring // print the hyperlink part AFTER the coloring
if match[ANCHOR] == hyperLinkStart { if match[ANCHOR] == hyperLinkStart {
w.isHyperlink = true isHyperlink = true
w.builder.WriteString(w.formats.hyperlinkStart) builder.WriteString(formats.hyperlinkStart)
} }
text = text[len(match[ANCHOR]):] text = text[len(match[ANCHOR]):]
w.runes = []rune(text) runes = []rune(text)
hyperlinkTextPosition := 0 hyperlinkTextPosition := 0
for i := 0; i < len(w.runes); i++ { for i := 0; i < len(runes); i++ {
s := w.runes[i] s := runes[i]
// ignore everything which isn't overriding // ignore everything which isn't overriding
if s != '<' { if s != '<' {
w.write(s) write(s)
continue continue
} }
// color/end overrides first // color/end overrides first
text = string(w.runes[i:]) text = string(runes[i:])
match = regex.FindNamedRegexMatch(AnchorRegex, text) match = regex.FindNamedRegexMatch(AnchorRegex, text)
if len(match) > 0 { if len(match) > 0 {
// check for hyperlinks first // check for hyperlinks first
switch match[ANCHOR] { switch match[ANCHOR] {
case hyperLinkStart: case hyperLinkStart:
w.isHyperlink = true isHyperlink = true
i += len([]rune(match[ANCHOR])) - 1 i += len([]rune(match[ANCHOR])) - 1
w.builder.WriteString(w.formats.hyperlinkStart) builder.WriteString(formats.hyperlinkStart)
continue continue
case hyperLinkText: case hyperLinkText:
w.isHyperlink = false isHyperlink = false
i += len([]rune(match[ANCHOR])) - 1 i += len([]rune(match[ANCHOR])) - 1
hyperlinkTextPosition = i hyperlinkTextPosition = i
w.builder.WriteString(w.formats.hyperlinkCenter) builder.WriteString(formats.hyperlinkCenter)
continue continue
case hyperLinkTextEnd: case hyperLinkTextEnd:
// this implies there's no text in the hyperlink // this implies there's no text in the hyperlink
if hyperlinkTextPosition+1 == i { if hyperlinkTextPosition+1 == i {
w.builder.WriteString("link") builder.WriteString("link")
w.length += 4 length += 4
} }
i += len([]rune(match[ANCHOR])) - 1 i += len([]rune(match[ANCHOR])) - 1
continue continue
case hyperLinkEnd: case hyperLinkEnd:
i += len([]rune(match[ANCHOR])) - 1 i += len([]rune(match[ANCHOR])) - 1
w.builder.WriteString(w.formats.hyperlinkEnd) builder.WriteString(formats.hyperlinkEnd)
continue continue
} }
i = w.writeArchorOverride(match, background, i) i = writeArchorOverride(match, background, i)
continue continue
} }
w.length += runewidth.RuneWidth(s) length += runewidth.RuneWidth(s)
w.write(s) write(s)
} }
// reset colors // reset colors
w.writeEscapedAnsiString(resetStyle.End) writeEscapedAnsiString(resetStyle.End)
// pop last color from the stack // pop last color from the stack
w.currentColor.Pop() currentColor.Pop()
} }
func (w *Writer) String() (string, int) { func String() (string, int) {
defer func() { defer func() {
w.length = 0 length = 0
w.builder.Reset() builder.Reset()
isTransparent = false
isInvisible = false
}() }()
return w.builder.String(), w.length return builder.String(), length
} }
func (w *Writer) writeEscapedAnsiString(text string) { func writeEscapedAnsiString(text string) {
if w.Plain { if Plain {
return return
} }
if len(w.formats.escape) != 0 { if len(formats.escape) != 0 {
text = fmt.Sprintf(w.formats.escape, text) text = fmt.Sprintf(formats.escape, text)
} }
w.builder.WriteString(text) builder.WriteString(text)
} }
func (w *Writer) getAnsiFromColorString(colorString string, isBackground bool) Color { func getAnsiFromColorString(colorString string, isBackground bool) Color {
return w.AnsiColors.ToColor(colorString, isBackground, w.TrueColor) return AnsiColors.ToColor(colorString, isBackground, TrueColor)
} }
func (w *Writer) write(s rune) { func write(s rune) {
if w.isInvisible { if isInvisible {
return return
} }
if w.isHyperlink { if isHyperlink {
w.builder.WriteRune(s) builder.WriteRune(s)
return return
} }
if !w.Interactive { if !Interactive {
for special, escape := range w.formats.escapeSequences { for special, escape := range formats.escapeSequences {
if s == special && w.lastRune != escape { if s == special && lastRune != escape {
w.builder.WriteRune(escape) builder.WriteRune(escape)
} }
} }
} }
w.length += runewidth.RuneWidth(s) length += runewidth.RuneWidth(s)
w.lastRune = s lastRune = s
w.builder.WriteRune(s) builder.WriteRune(s)
} }
func (w *Writer) writeSegmentColors() { func writeSegmentColors() {
// use correct starting colors // use correct starting colors
bg := w.backgroundColor bg := backgroundColor
fg := w.foregroundColor fg := foregroundColor
if !w.currentColor.Background().IsEmpty() { if !currentColor.Background().IsEmpty() {
bg = w.currentColor.Background() bg = currentColor.Background()
} }
if !w.currentColor.Foreground().IsEmpty() { if !currentColor.Foreground().IsEmpty() {
fg = w.currentColor.Foreground() fg = currentColor.Foreground()
} }
// ignore processing fully tranparent colors // ignore processing fully tranparent colors
w.isInvisible = fg.IsTransparent() && bg.IsTransparent() isInvisible = fg.IsTransparent() && bg.IsTransparent()
if w.isInvisible { if isInvisible {
return return
} }
if fg.IsTransparent() && len(w.BackgroundColor) != 0 { //nolint: gocritic if fg.IsTransparent() && len(BackgroundColor) != 0 { //nolint: gocritic
background := w.getAnsiFromColorString(w.BackgroundColor, false) background := getAnsiFromColorString(BackgroundColor, false)
w.writeEscapedAnsiString(fmt.Sprintf(colorise, background)) writeEscapedAnsiString(fmt.Sprintf(colorise, background))
w.writeEscapedAnsiString(fmt.Sprintf(colorise, bg.ToForeground())) writeEscapedAnsiString(fmt.Sprintf(colorise, bg.ToForeground()))
} else if fg.IsTransparent() && !bg.IsEmpty() { } else if fg.IsTransparent() && !bg.IsEmpty() {
w.isTransparent = true isTransparent = true
w.writeEscapedAnsiString(fmt.Sprintf(transparentStart, bg)) writeEscapedAnsiString(fmt.Sprintf(transparentStart, bg))
} else { } else {
if !bg.IsEmpty() && !bg.IsTransparent() { if !bg.IsEmpty() && !bg.IsTransparent() {
w.writeEscapedAnsiString(fmt.Sprintf(colorise, bg)) writeEscapedAnsiString(fmt.Sprintf(colorise, bg))
} }
if !fg.IsEmpty() { if !fg.IsEmpty() {
w.writeEscapedAnsiString(fmt.Sprintf(colorise, fg)) writeEscapedAnsiString(fmt.Sprintf(colorise, fg))
} }
} }
// set current colors // set current colors
w.currentColor.Add(bg, fg) currentColor.Add(bg, fg)
} }
func (w *Writer) writeArchorOverride(match map[string]string, background string, i int) int { func writeArchorOverride(match map[string]string, background string, i int) int {
position := i position := i
// check color reset first // check color reset first
if match[ANCHOR] == resetStyle.AnchorEnd { if match[ANCHOR] == resetStyle.AnchorEnd {
return w.endColorOverride(position) return endColorOverride(position)
} }
position += len([]rune(match[ANCHOR])) - 1 position += len([]rune(match[ANCHOR])) - 1
for _, style := range knownStyles { for _, style := range knownStyles {
if style.AnchorEnd == match[ANCHOR] { if style.AnchorEnd == match[ANCHOR] {
w.writeEscapedAnsiString(style.End) writeEscapedAnsiString(style.End)
return position return position
} }
if style.AnchorStart == match[ANCHOR] { if style.AnchorStart == match[ANCHOR] {
w.writeEscapedAnsiString(style.Start) writeEscapedAnsiString(style.Start)
return position return position
} }
} }
@ -538,77 +538,77 @@ func (w *Writer) writeArchorOverride(match map[string]string, background string,
match[BG] = background match[BG] = background
} }
bg, fg := w.asAnsiColors(match[BG], match[FG]) bg, fg := asAnsiColors(match[BG], match[FG])
// ignore processing fully tranparent colors // ignore processing fully tranparent colors
w.isInvisible = fg.IsTransparent() && bg.IsTransparent() isInvisible = fg.IsTransparent() && bg.IsTransparent()
if w.isInvisible { if isInvisible {
return position return position
} }
// make sure we have colors // make sure we have colors
if fg.IsEmpty() { if fg.IsEmpty() {
fg = w.foregroundColor fg = foregroundColor
} }
if bg.IsEmpty() { if bg.IsEmpty() {
bg = w.backgroundColor bg = backgroundColor
} }
w.currentColor.Add(bg, fg) currentColor.Add(bg, fg)
if w.currentColor.Foreground().IsTransparent() && len(w.BackgroundColor) != 0 { if currentColor.Foreground().IsTransparent() && len(BackgroundColor) != 0 {
background := w.getAnsiFromColorString(w.BackgroundColor, false) background := getAnsiFromColorString(BackgroundColor, false)
w.writeEscapedAnsiString(fmt.Sprintf(colorise, background)) writeEscapedAnsiString(fmt.Sprintf(colorise, background))
w.writeEscapedAnsiString(fmt.Sprintf(colorise, w.currentColor.Background().ToForeground())) writeEscapedAnsiString(fmt.Sprintf(colorise, currentColor.Background().ToForeground()))
return position return position
} }
if w.currentColor.Foreground().IsTransparent() && !w.currentColor.Background().IsTransparent() { if currentColor.Foreground().IsTransparent() && !currentColor.Background().IsTransparent() {
w.isTransparent = true isTransparent = true
w.writeEscapedAnsiString(fmt.Sprintf(transparentStart, w.currentColor.Background())) writeEscapedAnsiString(fmt.Sprintf(transparentStart, currentColor.Background()))
return position return position
} }
if w.currentColor.Background() != w.backgroundColor { if currentColor.Background() != backgroundColor {
// end the colors in case we have a transparent background // end the colors in case we have a transparent background
if w.currentColor.Background().IsTransparent() { if currentColor.Background().IsTransparent() {
w.writeEscapedAnsiString(backgroundEnd) writeEscapedAnsiString(backgroundEnd)
} else { } else {
w.writeEscapedAnsiString(fmt.Sprintf(colorise, w.currentColor.Background())) writeEscapedAnsiString(fmt.Sprintf(colorise, currentColor.Background()))
} }
} }
if w.currentColor.Foreground() != w.foregroundColor { if currentColor.Foreground() != foregroundColor {
w.writeEscapedAnsiString(fmt.Sprintf(colorise, w.currentColor.Foreground())) writeEscapedAnsiString(fmt.Sprintf(colorise, currentColor.Foreground()))
} }
return position return position
} }
func (w *Writer) endColorOverride(position int) int { func endColorOverride(position int) int {
// make sure to reset the colors if needed // make sure to reset the colors if needed
position += len([]rune(resetStyle.AnchorEnd)) - 1 position += len([]rune(resetStyle.AnchorEnd)) - 1
// do not restore colors at the end of the string, we print it anyways // do not restore colors at the end of the string, we print it anyways
if position == len(w.runes)-1 { if position == len(runes)-1 {
w.currentColor.Pop() currentColor.Pop()
return position return position
} }
// reset colors to previous when we have more than 1 in stack // reset colors to previous when we have more than 1 in stack
// as soon as we have more than 1, we can pop the last one // as soon as we have more than 1, we can pop the last one
// and print the previous override as it wasn't ended yet // and print the previous override as it wasn't ended yet
if w.currentColor.Len() > 1 { if currentColor.Len() > 1 {
fg := w.currentColor.Foreground() fg := currentColor.Foreground()
bg := w.currentColor.Background() bg := currentColor.Background()
w.currentColor.Pop() currentColor.Pop()
previousBg := w.currentColor.Background() previousBg := currentColor.Background()
previousFg := w.currentColor.Foreground() previousFg := currentColor.Foreground()
if w.isTransparent { if isTransparent {
w.writeEscapedAnsiString(transparentEnd) writeEscapedAnsiString(transparentEnd)
} }
if previousBg != bg { if previousBg != bg {
@ -617,45 +617,45 @@ func (w *Writer) endColorOverride(position int) int {
background = backgroundStyle.End background = backgroundStyle.End
} }
w.writeEscapedAnsiString(background) writeEscapedAnsiString(background)
} }
if previousFg != fg { if previousFg != fg {
w.writeEscapedAnsiString(fmt.Sprintf(colorise, previousFg)) writeEscapedAnsiString(fmt.Sprintf(colorise, previousFg))
} }
return position return position
} }
// pop the last colors from the stack // pop the last colors from the stack
defer w.currentColor.Pop() defer currentColor.Pop()
// do not reset when colors are identical // do not reset when colors are identical
if w.currentColor.Background() == w.backgroundColor && w.currentColor.Foreground() == w.foregroundColor { if currentColor.Background() == backgroundColor && currentColor.Foreground() == foregroundColor {
return position return position
} }
if w.isTransparent { if isTransparent {
w.writeEscapedAnsiString(transparentEnd) writeEscapedAnsiString(transparentEnd)
} }
if w.backgroundColor.IsClear() { if backgroundColor.IsClear() {
w.writeEscapedAnsiString(backgroundStyle.End) writeEscapedAnsiString(backgroundStyle.End)
} }
if w.currentColor.Background() != w.backgroundColor && !w.backgroundColor.IsClear() { if currentColor.Background() != backgroundColor && !backgroundColor.IsClear() {
w.writeEscapedAnsiString(fmt.Sprintf(colorise, w.backgroundColor)) writeEscapedAnsiString(fmt.Sprintf(colorise, backgroundColor))
} }
if (w.currentColor.Foreground() != w.foregroundColor || w.isTransparent) && !w.foregroundColor.IsClear() { if (currentColor.Foreground() != foregroundColor || isTransparent) && !foregroundColor.IsClear() {
w.writeEscapedAnsiString(fmt.Sprintf(colorise, w.foregroundColor)) writeEscapedAnsiString(fmt.Sprintf(colorise, foregroundColor))
} }
w.isTransparent = false isTransparent = false
return position return position
} }
func (w *Writer) asAnsiColors(background, foreground string) (Color, Color) { func asAnsiColors(background, foreground string) (Color, Color) {
if len(background) == 0 { if len(background) == 0 {
background = Background background = Background
} }
@ -664,18 +664,18 @@ func (w *Writer) asAnsiColors(background, foreground string) (Color, Color) {
foreground = Foreground foreground = Foreground
} }
background = w.expandKeyword(background) background = expandKeyword(background)
foreground = w.expandKeyword(foreground) foreground = expandKeyword(foreground)
inverted := foreground == Transparent && len(background) != 0 inverted := foreground == Transparent && len(background) != 0
backgroundAnsi := w.getAnsiFromColorString(background, !inverted) backgroundAnsi := getAnsiFromColorString(background, !inverted)
foregroundAnsi := w.getAnsiFromColorString(foreground, false) foregroundAnsi := getAnsiFromColorString(foreground, false)
return backgroundAnsi, foregroundAnsi return backgroundAnsi, foregroundAnsi
} }
func (w *Writer) isKeyword(color string) bool { func isKeyword(color string) bool {
switch color { switch color {
case Transparent, ParentBackground, ParentForeground, Background, Foreground: case Transparent, ParentBackground, ParentForeground, Background, Foreground:
return true return true
@ -684,9 +684,9 @@ func (w *Writer) isKeyword(color string) bool {
} }
} }
func (w *Writer) expandKeyword(keyword string) string { func expandKeyword(keyword string) string {
resolveParentColor := func(keyword string) string { resolveParentColor := func(keyword string) string {
for _, color := range w.ParentColors { for _, color := range ParentColors {
if color == nil { if color == nil {
return Transparent return Transparent
} }
@ -712,18 +712,18 @@ func (w *Writer) expandKeyword(keyword string) string {
resolveKeyword := func(keyword string) string { resolveKeyword := func(keyword string) string {
switch { switch {
case keyword == Background && w.CurrentColors != nil: case keyword == Background && CurrentColors != nil:
return w.CurrentColors.Background return CurrentColors.Background
case keyword == Foreground && w.CurrentColors != nil: case keyword == Foreground && CurrentColors != nil:
return w.CurrentColors.Foreground return CurrentColors.Foreground
case (keyword == ParentBackground || keyword == ParentForeground) && w.ParentColors != nil: case (keyword == ParentBackground || keyword == ParentForeground) && ParentColors != nil:
return resolveParentColor(keyword) return resolveParentColor(keyword)
default: default:
return Transparent return Transparent
} }
} }
for ok := w.isKeyword(keyword); ok; ok = w.isKeyword(keyword) { for ok := isKeyword(keyword); ok; ok = isKeyword(keyword) {
resolved := resolveKeyword(keyword) resolved := resolveKeyword(keyword)
if resolved == keyword { if resolved == keyword {
break break
@ -735,7 +735,7 @@ func (w *Writer) expandKeyword(keyword string) string {
return keyword return keyword
} }
func (w *Writer) trimAnsi(text string) string { func trimAnsi(text string) string {
if len(text) == 0 || !strings.Contains(text, "\x1b") { if len(text) == 0 || !strings.Contains(text, "\x1b") {
return text return text
} }

View file

@ -20,13 +20,14 @@ func TestGenerateHyperlinkNoUrl(t *testing.T) {
{Text: "sample text with no url [test]", ShellName: shell.BASH, Expected: "\\[\x1b[47m\\]\\[\x1b[30m\\]sample text with no url [test]\\[\x1b[0m\\]"}, {Text: "sample text with no url [test]", ShellName: shell.BASH, Expected: "\\[\x1b[47m\\]\\[\x1b[30m\\]sample text with no url [test]\\[\x1b[0m\\]"},
} }
for _, tc := range cases { for _, tc := range cases {
a := Writer{ Init(tc.ShellName)
AnsiColors: &DefaultColors{}, AnsiColors = &DefaultColors{}
}
a.Init(tc.ShellName) Write("white", "black", tc.Text)
a.Write("white", "black", tc.Text)
hyperlinkText, _ := a.String() got, _ := String()
assert.Equal(t, tc.Expected, hyperlinkText)
assert.Equal(t, tc.Expected, got)
} }
} }
@ -93,13 +94,14 @@ func TestGenerateHyperlinkWithUrl(t *testing.T) {
}, },
} }
for _, tc := range cases { for _, tc := range cases {
a := Writer{ Init(tc.ShellName)
AnsiColors: &DefaultColors{}, AnsiColors = &DefaultColors{}
}
a.Init(tc.ShellName) Write("white", "black", tc.Text)
a.Write("white", "black", tc.Text)
hyperlinkText, _ := a.String() got, _ := String()
assert.Equal(t, tc.Expected, hyperlinkText)
assert.Equal(t, tc.Expected, got)
} }
} }
@ -115,12 +117,13 @@ func TestGenerateFileLink(t *testing.T) {
{Text: `<LINK>file:C:/Windows<TEXT>Windows</TEXT></LINK>`, Expected: "\x1b[47m\x1b[30m\x1b]8;;file:C:/Windows\x1b\\Windows\x1b]8;;\x1b\\\x1b[0m"}, {Text: `<LINK>file:C:/Windows<TEXT>Windows</TEXT></LINK>`, Expected: "\x1b[47m\x1b[30m\x1b]8;;file:C:/Windows\x1b\\Windows\x1b]8;;\x1b\\\x1b[0m"},
} }
for _, tc := range cases { for _, tc := range cases {
a := Writer{ Init(shell.PWSH)
AnsiColors: &DefaultColors{}, AnsiColors = &DefaultColors{}
}
a.Init(shell.PWSH) Write("white", "black", tc.Text)
a.Write("white", "black", tc.Text)
hyperlinkText, _ := a.String() got, _ := String()
assert.Equal(t, tc.Expected, hyperlinkText)
assert.Equal(t, tc.Expected, got)
} }
} }

View file

@ -221,16 +221,17 @@ func TestWriteANSIColors(t *testing.T) {
} }
for _, tc := range cases { for _, tc := range cases {
renderer := &Writer{ Init(shell.GENERIC)
ParentColors: []*Colors{tc.Parent}, ParentColors = []*Colors{tc.Parent}
CurrentColors: tc.Colors, CurrentColors = tc.Colors
BackgroundColor: tc.TerminalBackground, BackgroundColor = tc.TerminalBackground
AnsiColors: &DefaultColors{}, AnsiColors = &DefaultColors{}
TrueColor: true, TrueColor = true
}
renderer.Init(shell.GENERIC) Write(tc.Colors.Background, tc.Colors.Foreground, tc.Input)
renderer.Write(tc.Colors.Background, tc.Colors.Foreground, tc.Input)
got, _ := renderer.String() got, _ := String()
assert.Equal(t, tc.Expected, got, tc.Case) assert.Equal(t, tc.Expected, got, tc.Case)
} }
} }
@ -275,14 +276,17 @@ func TestWriteLength(t *testing.T) {
} }
for _, tc := range cases { for _, tc := range cases {
renderer := &Writer{ Init(shell.GENERIC)
ParentColors: []*Colors{}, ParentColors = []*Colors{}
CurrentColors: tc.Colors, CurrentColors = tc.Colors
AnsiColors: &DefaultColors{}, AnsiColors = &DefaultColors{}
}
renderer.Init(shell.GENERIC) Init(shell.GENERIC)
renderer.Write(tc.Colors.Background, tc.Colors.Foreground, tc.Input)
_, got := renderer.String() Write(tc.Colors.Background, tc.Colors.Foreground, tc.Input)
_, got := String()
assert.Equal(t, tc.Expected, got, tc.Case) assert.Equal(t, tc.Expected, got, tc.Case)
} }
} }