diff --git a/src/cli/config_export_image.go b/src/cli/config_export_image.go index 2e5033ac..11c4aee4 100644 --- a/src/cli/config_export_image.go +++ b/src/cli/config_export_image.go @@ -67,17 +67,14 @@ Exports the config to an image file using customized output options.`, // add variables to the environment env.Var = cfg.Var - writerColors := cfg.MakeColors() - writer := &terminal.Writer{ - BackgroundColor: shell.ConsoleBackgroundColor(env, cfg.TerminalBackground), - AnsiColors: writerColors, - TrueColor: env.CmdFlags.TrueColor, - } - writer.Init(shell.GENERIC) + terminal.Init(shell.GENERIC) + terminal.BackgroundColor = shell.ConsoleBackgroundColor(env, cfg.TerminalBackground) + terminal.AnsiColors = cfg.MakeColors() + terminal.TrueColor = env.CmdFlags.TrueColor + eng := &engine.Engine{ Config: cfg, Env: env, - Writer: writer, } prompt := eng.Primary() @@ -86,7 +83,6 @@ Exports the config to an image file using customized output options.`, AnsiString: prompt, Author: author, BgColor: bgColor, - Ansi: writer, } if outputImage != "" { diff --git a/src/cli/debug.go b/src/cli/debug.go index ae013264..38e31c0a 100644 --- a/src/cli/debug.go +++ b/src/cli/debug.go @@ -40,19 +40,15 @@ var debugCmd = &cobra.Command{ // add variables to the environment env.Var = cfg.Var - writerColors := cfg.MakeColors() - writer := &terminal.Writer{ - BackgroundColor: shell.ConsoleBackgroundColor(env, cfg.TerminalBackground), - AnsiColors: writerColors, - Plain: plain, - TrueColor: env.CmdFlags.TrueColor, - } + terminal.Init(shell.GENERIC) + terminal.BackgroundColor = shell.ConsoleBackgroundColor(env, cfg.TerminalBackground) + terminal.AnsiColors = cfg.MakeColors() + terminal.Plain = plain + terminal.TrueColor = env.CmdFlags.TrueColor - writer.Init(shell.GENERIC) eng := &engine.Engine{ Config: cfg, Env: env, - Writer: writer, Plain: plain, } diff --git a/src/cli/get.go b/src/cli/get.go index de073dec..ecdd6531 100644 --- a/src/cli/get.go +++ b/src/cli/get.go @@ -5,8 +5,8 @@ import ( "strings" "time" - "github.com/jandedobbeleer/oh-my-posh/src/terminal" "github.com/jandedobbeleer/oh-my-posh/src/platform" + "github.com/jandedobbeleer/oh-my-posh/src/terminal" color2 "github.com/gookit/color" "github.com/spf13/cobra" diff --git a/src/engine/block.go b/src/engine/block.go index f66cd243..98c17c61 100644 --- a/src/engine/block.go +++ b/src/engine/block.go @@ -56,25 +56,21 @@ type Block struct { MinWidth int `json:"min_width,omitempty" toml:"min_width,omitempty"` env platform.Environment - writer *terminal.Writer activeSegment *Segment previousActiveSegment *Segment } -func (b *Block) Init(env platform.Environment, writer *terminal.Writer) { +func (b *Block) Init(env platform.Environment) { b.env = env - b.writer = writer b.executeSegmentLogic() } func (b *Block) InitPlain(env platform.Environment, config *Config) { - b.writer = &terminal.Writer{ - BackgroundColor: shell.ConsoleBackgroundColor(env, config.TerminalBackground), - AnsiColors: config.MakeColors(), - TrueColor: env.Flags().TrueColor, - } + terminal.Init(shell.GENERIC) + terminal.BackgroundColor = shell.ConsoleBackgroundColor(env, config.TerminalBackground) + terminal.AnsiColors = config.MakeColors() + terminal.TrueColor = env.Flags().TrueColor - b.writer.Init(shell.GENERIC) b.env = env b.executeSegmentLogic() } @@ -90,8 +86,8 @@ func (b *Block) executeSegmentLogic() { func (b *Block) setActiveSegment(segment *Segment) { b.activeSegment = segment - b.writer.Interactive = segment.Interactive - b.writer.SetColors(segment.background(), segment.foreground()) + terminal.Interactive = segment.Interactive + terminal.SetColors(segment.background(), segment.foreground()) } func (b *Block) Enabled() bool { @@ -156,7 +152,7 @@ func (b *Block) RenderSegments() (string, int) { b.writeSeparator(true) - return b.writer.String() + return terminal.String() } func (b *Block) filterSegments() { @@ -177,27 +173,27 @@ func (b *Block) renderActiveSegment() { b.writeSeparator(false) switch b.activeSegment.style() { case Plain, Powerline: - b.writer.Write(terminal.Background, terminal.Foreground, b.activeSegment.text) + terminal.Write(terminal.Background, terminal.Foreground, b.activeSegment.text) case Diamond: background := terminal.Transparent if b.previousActiveSegment != nil && b.previousActiveSegment.hasEmptyDiamondAtEnd() { background = b.previousActiveSegment.background() } - b.writer.Write(background, terminal.Background, b.activeSegment.LeadingDiamond) - b.writer.Write(terminal.Background, terminal.Foreground, b.activeSegment.text) + terminal.Write(background, terminal.Background, b.activeSegment.LeadingDiamond) + terminal.Write(terminal.Background, terminal.Foreground, b.activeSegment.text) case Accordion: 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.writer.SetParentColors(b.previousActiveSegment.background(), b.previousActiveSegment.foreground()) + terminal.SetParentColors(b.previousActiveSegment.background(), b.previousActiveSegment.foreground()) } func (b *Block) writeSeparator(final bool) { isCurrentDiamond := b.activeSegment.style() == Diamond if final && isCurrentDiamond { - b.writer.Write(terminal.Transparent, terminal.Background, b.activeSegment.TrailingDiamond) + terminal.Write(terminal.Transparent, terminal.Background, b.activeSegment.TrailingDiamond) return } @@ -207,12 +203,12 @@ func (b *Block) writeSeparator(final bool) { } 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 } 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() @@ -234,7 +230,7 @@ func (b *Block) writeSeparator(final bool) { } if shouldOverridePowerlineLeadingSymbol() { - b.writer.Write(terminal.Transparent, terminal.Background, b.activeSegment.LeadingPowerlineSymbol) + terminal.Write(terminal.Transparent, terminal.Background, b.activeSegment.LeadingPowerlineSymbol) return } @@ -265,11 +261,11 @@ func (b *Block) writeSeparator(final bool) { } if b.activeSegment.InvertPowerline { - b.writer.Write(b.getPowerlineColor(), bgColor, symbol) + terminal.Write(b.getPowerlineColor(), bgColor, symbol) return } - b.writer.Write(bgColor, b.getPowerlineColor(), symbol) + terminal.Write(bgColor, b.getPowerlineColor(), symbol) } func (b *Block) adjustTrailingDiamondColorOverrides() { diff --git a/src/engine/engine.go b/src/engine/engine.go index 1dbd4bc8..9a540d85 100644 --- a/src/engine/engine.go +++ b/src/engine/engine.go @@ -16,7 +16,6 @@ var ( type Engine struct { Config *Config Env platform.Environment - Writer *terminal.Writer Plain bool console strings.Builder @@ -73,10 +72,10 @@ func (e *Engine) writeRPrompt() { if !OK { return } - e.write(e.Writer.SaveCursorPosition()) + e.write(terminal.SaveCursorPosition()) e.write(strings.Repeat(" ", space)) e.write(e.rprompt) - e.write(e.Writer.RestoreCursorPosition()) + e.write(terminal.RestoreCursorPosition()) } func (e *Engine) pwd() { @@ -94,7 +93,7 @@ func (e *Engine) pwd() { // Backwards compatibility for deprecated OSC99 if e.Config.OSC99 { - e.write(e.Writer.ConsolePwd(terminal.OSC99, "", "", cwd)) + e.write(terminal.ConsolePwd(terminal.OSC99, "", "", cwd)) return } @@ -111,7 +110,7 @@ func (e *Engine) pwd() { user := e.Env.User() 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() { @@ -121,7 +120,7 @@ func (e *Engine) newline() { // WARP terminal will remove \n from the prompt, so we hack a newline in if e.isWarp() { - e.write(e.Writer.LineBreak()) + e.write(terminal.LineBreak()) return } @@ -155,8 +154,8 @@ func (e *Engine) shouldFill(filler string, remaining, blockLength int) (string, } // allow for easy color overrides and templates - e.Writer.Write("", "", filler) - filler, lenFiller := e.Writer.String() + terminal.Write("", "", filler) + filler, lenFiller := terminal.String() if lenFiller == 0 { return "", false } @@ -197,7 +196,7 @@ func (e *Engine) renderBlock(block *Block, cancelNewline bool) bool { if e.Env.Shell() == shell.BASH && block.Type == RPrompt { block.InitPlain(e.Env, e.Config) } else { - block.Init(e.Env, e.Writer) + block.Init(e.Env) } if !block.Enabled() { @@ -221,7 +220,7 @@ func (e *Engine) renderBlock(block *Block, cancelNewline bool) bool { switch block.Type { //nolint:exhaustive case Prompt: if block.VerticalOffset != 0 { - e.write(e.Writer.ChangeLine(block.VerticalOffset)) + e.write(terminal.ChangeLine(block.VerticalOffset)) } if block.Alignment == Left { @@ -292,5 +291,5 @@ func (e *Engine) patchPowerShellBleed() { return } - e.write(e.Writer.ClearAfter()) + e.write(terminal.ClearAfter()) } diff --git a/src/engine/engine_test.go b/src/engine/engine_test.go index 64d5b08f..ea96d531 100644 --- a/src/engine/engine_test.go +++ b/src/engine/engine_test.go @@ -85,18 +85,19 @@ func TestPrintPWD(t *testing.T) { Shell: "shell", }) - writer := &terminal.Writer{} - writer.Init(shell.GENERIC) + terminal.Init(shell.GENERIC) + engine := &Engine{ Env: env, Config: &Config{ PWD: tc.Config, OSC99: tc.OSC99, }, - Writer: writer, } + engine.pwd() got := engine.string() + assert.Equal(t, tc.Expected, got, tc.Case) } } @@ -114,17 +115,14 @@ func engineRender() { cfg := LoadConfig(env) - writerColors := cfg.MakeColors() - writer := &terminal.Writer{ - BackgroundColor: shell.ConsoleBackgroundColor(env, cfg.TerminalBackground), - AnsiColors: writerColors, - TrueColor: env.CmdFlags.TrueColor, - } - writer.Init(shell.GENERIC) + terminal.Init(shell.GENERIC) + terminal.BackgroundColor = shell.ConsoleBackgroundColor(env, cfg.TerminalBackground) + terminal.AnsiColors = cfg.MakeColors() + terminal.TrueColor = env.CmdFlags.TrueColor + engine := &Engine{ Config: cfg, Env: env, - Writer: writer, } engine.Primary() @@ -188,17 +186,19 @@ func TestGetTitle(t *testing.T) { PWD: tc.Cwd, Folder: "vagrant", }) - writer := &terminal.Writer{} - writer.Init(shell.GENERIC) + + terminal.Init(shell.GENERIC) + engine := &Engine{ Config: &Config{ ConsoleTitleTemplate: tc.Template, }, - Writer: writer, - Env: env, + Env: env, } + title := engine.getTitleTemplateText() - got := writer.FormatTitle(title) + got := terminal.FormatTitle(title) + assert.Equal(t, tc.Expected, got) } } @@ -247,17 +247,19 @@ func TestGetConsoleTitleIfGethostnameReturnsError(t *testing.T) { Root: tc.Root, HostName: "", }) - writer := &terminal.Writer{} - writer.Init(shell.GENERIC) + + terminal.Init(shell.GENERIC) + engine := &Engine{ Config: &Config{ ConsoleTitleTemplate: tc.Template, }, - Writer: writer, - Env: env, + Env: env, } + title := engine.getTitleTemplateText() - got := writer.FormatTitle(title) + got := terminal.FormatTitle(title) + assert.Equal(t, tc.Expected, got) } } diff --git a/src/engine/image.go b/src/engine/image.go index 9fcc5bda..688bf0c2 100644 --- a/src/engine/image.go +++ b/src/engine/image.go @@ -114,7 +114,6 @@ type ImageRenderer struct { CursorPadding int RPromptOffset int BgColor string - Ansi *terminal.Writer env platform.Environment diff --git a/src/engine/image_test.go b/src/engine/image_test.go index c860e362..0b0a2efc 100644 --- a/src/engine/image_test.go +++ b/src/engine/image_test.go @@ -34,11 +34,10 @@ func runImageTest(config, content string) (string, error) { } defer os.Remove(file.Name()) - writer := &terminal.Writer{} - writer.Init(shell.GENERIC) + terminal.Init(shell.GENERIC) + image := &ImageRenderer{ AnsiString: content, - Ansi: writer, } env := &platform.Shell{ diff --git a/src/engine/new.go b/src/engine/new.go index b8df1bff..bb0eb920 100644 --- a/src/engine/new.go +++ b/src/engine/new.go @@ -24,18 +24,15 @@ func New(flags *platform.Flags) *Engine { env.Var = cfg.Var flags.HasTransient = cfg.TransientPrompt != nil - ansiWriter := &terminal.Writer{ - BackgroundColor: shell.ConsoleBackgroundColor(env, cfg.TerminalBackground), - AnsiColors: cfg.MakeColors(), - Plain: flags.Plain, - TrueColor: env.CmdFlags.TrueColor, - } - ansiWriter.Init(env.Shell()) + terminal.Init(env.Shell()) + terminal.BackgroundColor = shell.ConsoleBackgroundColor(env, cfg.TerminalBackground) + terminal.AnsiColors = cfg.MakeColors() + terminal.Plain = flags.Plain + terminal.TrueColor = env.CmdFlags.TrueColor eng := &Engine{ Config: cfg, Env: env, - Writer: ansiWriter, Plain: flags.Plain, } diff --git a/src/engine/prompt.go b/src/engine/prompt.go index 1a2efc40..9fb27857 100644 --- a/src/engine/prompt.go +++ b/src/engine/prompt.go @@ -22,8 +22,8 @@ const ( func (e *Engine) Primary() string { if e.Config.ShellIntegration { exitCode, _ := e.Env.StatusCodes() - e.write(e.Writer.CommandFinished(exitCode, e.Env.Flags().NoExitCode)) - e.write(e.Writer.PromptStart()) + e.write(terminal.CommandFinished(exitCode, e.Env.Flags().NoExitCode)) + e.write(terminal.PromptStart()) } // 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 { title := e.getTitleTemplateText() - e.write(e.Writer.FormatTitle(title)) + e.write(terminal.FormatTitle(title)) } if e.Config.FinalSpace { @@ -70,11 +70,11 @@ func (e *Engine) Primary() string { if e.Config.ITermFeatures != nil && e.isIterm() { 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 { - e.write(e.Writer.CommandStart()) + e.write(terminal.CommandStart()) } 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 // see https://github.com/jandedobbeleer/oh-my-posh/pull/2398 - writer := &terminal.Writer{ - TrueColor: e.Env.Flags().TrueColor, - } - writer.Init(shell.GENERIC) - prompt := writer.SaveCursorPosition() + + terminal.Init(shell.GENERIC) + terminal.TrueColor = e.Env.Flags().TrueColor + + prompt := terminal.SaveCursorPosition() prompt += strings.Repeat(" ", space) prompt += e.rprompt - prompt += writer.RestoreCursorPosition() - prompt = e.Writer.EscapeText(prompt) + prompt += terminal.RestoreCursorPosition() + prompt = terminal.EscapeText(prompt) e.write(prompt) } @@ -168,16 +168,16 @@ func (e *Engine) ExtraPrompt(promptType ExtraPromptType) string { if promptType == Transient && e.Config.ShellIntegration { exitCode, _ := e.Env.StatusCodes() - e.write(e.Writer.CommandFinished(exitCode, e.Env.Flags().NoExitCode)) - e.write(e.Writer.PromptStart()) + e.write(terminal.CommandFinished(exitCode, e.Env.Flags().NoExitCode)) + e.write(terminal.PromptStart()) } foreground := prompt.ForegroundTemplates.FirstMatch(nil, e.Env, prompt.Foreground) background := prompt.BackgroundTemplates.FirstMatch(nil, e.Env, prompt.Background) - e.Writer.SetColors(background, foreground) - e.Writer.Write(background, foreground, promptText) + terminal.SetColors(background, foreground) + terminal.Write(background, foreground, promptText) - str, length := e.Writer.String() + str, length := terminal.String() if promptType == Transient { consoleWidth, err := e.Env.TerminalWidth() if err == nil || consoleWidth != 0 { @@ -188,7 +188,7 @@ func (e *Engine) ExtraPrompt(promptType ExtraPromptType) string { } if promptType == Transient && e.Config.ShellIntegration { - str += e.Writer.CommandStart() + str += terminal.CommandStart() } switch e.Env.Shell() { @@ -205,7 +205,7 @@ func (e *Engine) ExtraPrompt(promptType ExtraPromptType) string { // Return the string and empty our buffer // clear the line afterwards to prevent text from being written on the same line // 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: // Return the string and empty our buffer return str @@ -229,7 +229,7 @@ func (e *Engine) RPrompt() string { return "" } - block.Init(e.Env, e.Writer) + block.Init(e.Env) if !block.Enabled() { return "" } diff --git a/src/engine/tooltip.go b/src/engine/tooltip.go index 256626ad..c76a65d2 100644 --- a/src/engine/tooltip.go +++ b/src/engine/tooltip.go @@ -4,6 +4,7 @@ import ( "strings" "github.com/jandedobbeleer/oh-my-posh/src/shell" + "github.com/jandedobbeleer/oh-my-posh/src/terminal" ) func (e *Engine) Tooltip(tip string) string { @@ -38,7 +39,7 @@ func (e *Engine) Tooltip(tip string) string { switch e.Env.Shell() { case shell.ZSH, shell.CMD, shell.FISH, shell.GENERIC: - block.Init(e.Env, e.Writer) + block.Init(e.Env) if !block.Enabled() { 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 // 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(text) return e.string() diff --git a/src/terminal/colors_unix.go b/src/terminal/colors_unix.go index b2bacf2a..d0c68a49 100644 --- a/src/terminal/colors_unix.go +++ b/src/terminal/colors_unix.go @@ -8,7 +8,7 @@ func GetAccentColor(_ platform.Environment) (*RGB, error) { 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 { return } diff --git a/src/terminal/iterm.go b/src/terminal/iterm.go index 86b174aa..558de014 100644 --- a/src/terminal/iterm.go +++ b/src/terminal/iterm.go @@ -28,7 +28,7 @@ func (f ITermFeatures) Contains(feature iTermFeature) bool { 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} var result strings.Builder @@ -39,11 +39,11 @@ func (w *Writer) RenderItermFeatures(features ITermFeatures, sh, pwd, user, host continue } - result.WriteString(w.formats.iTermPromptMark) + result.WriteString(formats.iTermPromptMark) case CurrentDir: - result.WriteString(fmt.Sprintf(w.formats.iTermCurrentDir, pwd)) + result.WriteString(fmt.Sprintf(formats.iTermCurrentDir, pwd)) case RemoteHost: - result.WriteString(fmt.Sprintf(w.formats.iTermRemoteHost, user, host)) + result.WriteString(fmt.Sprintf(formats.iTermRemoteHost, user, host)) } } diff --git a/src/terminal/writer.go b/src/terminal/writer.go index 34b8814b..6330113e 100644 --- a/src/terminal/writer.go +++ b/src/terminal/writer.go @@ -39,6 +39,33 @@ var ( resetStyle = &style{AnchorStart: "RESET", AnchorEnd: ``, End: "\x1b[0m"} 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 { @@ -104,42 +131,12 @@ const ( hyperLinkTextEnd = "" ) -// Writer writes colorized ANSI strings -type Writer struct { - BackgroundColor string - CurrentColors *Colors - ParentColors []*Colors - AnsiColors ColorString +func Init(sh string) { + shellName = sh - 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 -} - -func (w *Writer) Init(shellName string) { - w.shellName = shellName - - switch w.shellName { + switch shellName { case shell.BASH: - w.formats = &shellFormats{ + formats = &shellFormats{ escape: "\\[%s\\]", linechange: "\\[\x1b[%d%s\\]", left: "\\[\x1b[%dD\\]", @@ -163,7 +160,7 @@ func (w *Writer) Init(shellName string) { }, } case shell.ZSH, shell.TCSH: - w.formats = &shellFormats{ + formats = &shellFormats{ escape: "%%{%s%%}", linechange: "%%{\x1b[%d%s%%}", left: "%%{\x1b[%dD%%}", @@ -183,7 +180,7 @@ func (w *Writer) Init(shellName string) { iTermRemoteHost: "%%{\x1b]1337;RemoteHost=%s@%s\x07%%}", } default: - w.formats = &shellFormats{ + formats = &shellFormats{ escape: "%s", linechange: "\x1b[%d%s", left: "\x1b[%dD", @@ -207,33 +204,33 @@ func (w *Writer) Init(shellName string) { } if shellName == shell.ZSH { - w.formats.escapeSequences = map[rune]rune{ + formats.escapeSequences = map[rune]rune{ 96: 92, // backtick 37: 37, // % } } } -func (w *Writer) SetColors(background, foreground string) { - w.CurrentColors = &Colors{ +func SetColors(background, foreground string) { + CurrentColors = &Colors{ Background: background, Foreground: foreground, } } -func (w *Writer) SetParentColors(background, foreground string) { - if w.ParentColors == nil { - w.ParentColors = make([]*Colors, 0) +func SetParentColors(background, foreground string) { + if ParentColors == nil { + ParentColors = make([]*Colors, 0) } - w.ParentColors = append([]*Colors{{ + ParentColors = append([]*Colors{{ Background: background, Foreground: foreground, - }}, w.ParentColors...) + }}, ParentColors...) } -func (w *Writer) ChangeLine(numberOfLines int) string { - if w.Plain { +func ChangeLine(numberOfLines int) string { + if Plain { return "" } @@ -244,11 +241,11 @@ func (w *Writer) ChangeLine(numberOfLines int) string { 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 { - if w.Plain { +func ConsolePwd(pwdType, userName, hostName, pwd string) string { + if Plain { return "" } @@ -258,33 +255,33 @@ func (w *Writer) ConsolePwd(pwdType, userName, hostName, pwd string) string { switch pwdType { case OSC7: - return fmt.Sprintf(w.formats.osc7, hostName, pwd) + return fmt.Sprintf(formats.osc7, hostName, pwd) case OSC51: - return fmt.Sprintf(w.formats.osc51, userName, hostName, pwd) + return fmt.Sprintf(formats.osc51, userName, hostName, pwd) case OSC99: fallthrough default: - return fmt.Sprintf(w.formats.osc99, pwd) + return fmt.Sprintf(formats.osc99, pwd) } } -func (w *Writer) ClearAfter() string { - if w.Plain { +func ClearAfter() string { + if Plain { return "" } - return w.formats.clearLine + w.formats.clearBelow + return formats.clearLine + formats.clearBelow } -func (w *Writer) FormatTitle(title string) string { - title = w.trimAnsi(title) +func FormatTitle(title string) string { + title = trimAnsi(title) - if w.Plain { + if Plain { return title } // we have to do this to prevent bash/zsh from misidentifying escape sequences - switch w.shellName { + switch shellName { case shell.BASH: title = strings.NewReplacer("`", "\\`", `\`, `\\`).Replace(title) case shell.ZSH: @@ -294,55 +291,55 @@ func (w *Writer) FormatTitle(title string) string { return "" } - return fmt.Sprintf(w.formats.title, title) + return fmt.Sprintf(formats.title, title) } -func (w *Writer) EscapeText(text string) string { - return fmt.Sprintf(w.formats.escape, text) +func EscapeText(text string) string { + return fmt.Sprintf(formats.escape, text) } -func (w *Writer) SaveCursorPosition() string { - return w.formats.saveCursorPosition +func SaveCursorPosition() string { + return formats.saveCursorPosition } -func (w *Writer) RestoreCursorPosition() string { - return w.formats.restoreCursorPosition +func RestoreCursorPosition() string { + return formats.restoreCursorPosition } -func (w *Writer) PromptStart() string { - return fmt.Sprintf(w.formats.escape, "\x1b]133;A\007") +func PromptStart() string { + return fmt.Sprintf(formats.escape, "\x1b]133;A\007") } -func (w *Writer) CommandStart() string { - return fmt.Sprintf(w.formats.escape, "\x1b]133;B\007") +func CommandStart() string { + 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 { - 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) - return fmt.Sprintf(w.formats.escape, mark) + return fmt.Sprintf(formats.escape, mark) } -func (w *Writer) LineBreak() string { - cr := fmt.Sprintf(w.formats.left, 1000) - lf := fmt.Sprintf(w.formats.linechange, 1, "B") +func LineBreak() string { + cr := fmt.Sprintf(formats.left, 1000) + lf := fmt.Sprintf(formats.linechange, 1, "B") return cr + lf } -func (w *Writer) Write(background, foreground, text string) { +func Write(background, foreground, text string) { if len(text) == 0 { return } - w.backgroundColor, w.foregroundColor = w.asAnsiColors(background, foreground) + backgroundColor, foregroundColor = asAnsiColors(background, foreground) // default to white foreground - if w.foregroundColor.IsEmpty() { - w.foregroundColor = w.AnsiColors.ToColor("white", false, w.TrueColor) + if foregroundColor.IsEmpty() { + foregroundColor = AnsiColors.ToColor("white", false, TrueColor) } // validate if we start with a color override @@ -354,182 +351,185 @@ func (w *Writer) Write(background, foreground, text string) { continue } - w.writeEscapedAnsiString(style.Start) + writeEscapedAnsiString(style.Start) colorOverride = false } 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 if match[ANCHOR] == hyperLinkStart { - w.isHyperlink = true - w.builder.WriteString(w.formats.hyperlinkStart) + isHyperlink = true + builder.WriteString(formats.hyperlinkStart) } text = text[len(match[ANCHOR]):] - w.runes = []rune(text) + runes = []rune(text) hyperlinkTextPosition := 0 - for i := 0; i < len(w.runes); i++ { - s := w.runes[i] + for i := 0; i < len(runes); i++ { + s := runes[i] // ignore everything which isn't overriding if s != '<' { - w.write(s) + write(s) continue } // color/end overrides first - text = string(w.runes[i:]) + text = string(runes[i:]) match = regex.FindNamedRegexMatch(AnchorRegex, text) if len(match) > 0 { // check for hyperlinks first switch match[ANCHOR] { case hyperLinkStart: - w.isHyperlink = true + isHyperlink = true i += len([]rune(match[ANCHOR])) - 1 - w.builder.WriteString(w.formats.hyperlinkStart) + builder.WriteString(formats.hyperlinkStart) continue case hyperLinkText: - w.isHyperlink = false + isHyperlink = false i += len([]rune(match[ANCHOR])) - 1 hyperlinkTextPosition = i - w.builder.WriteString(w.formats.hyperlinkCenter) + builder.WriteString(formats.hyperlinkCenter) continue case hyperLinkTextEnd: // this implies there's no text in the hyperlink if hyperlinkTextPosition+1 == i { - w.builder.WriteString("link") - w.length += 4 + builder.WriteString("link") + length += 4 } i += len([]rune(match[ANCHOR])) - 1 continue case hyperLinkEnd: i += len([]rune(match[ANCHOR])) - 1 - w.builder.WriteString(w.formats.hyperlinkEnd) + builder.WriteString(formats.hyperlinkEnd) continue } - i = w.writeArchorOverride(match, background, i) + i = writeArchorOverride(match, background, i) continue } - w.length += runewidth.RuneWidth(s) - w.write(s) + length += runewidth.RuneWidth(s) + write(s) } // reset colors - w.writeEscapedAnsiString(resetStyle.End) + writeEscapedAnsiString(resetStyle.End) // pop last color from the stack - w.currentColor.Pop() + currentColor.Pop() } -func (w *Writer) String() (string, int) { +func String() (string, int) { defer func() { - w.length = 0 - w.builder.Reset() + length = 0 + builder.Reset() + + isTransparent = false + isInvisible = false }() - return w.builder.String(), w.length + return builder.String(), length } -func (w *Writer) writeEscapedAnsiString(text string) { - if w.Plain { +func writeEscapedAnsiString(text string) { + if Plain { return } - if len(w.formats.escape) != 0 { - text = fmt.Sprintf(w.formats.escape, text) + if len(formats.escape) != 0 { + text = fmt.Sprintf(formats.escape, text) } - w.builder.WriteString(text) + builder.WriteString(text) } -func (w *Writer) getAnsiFromColorString(colorString string, isBackground bool) Color { - return w.AnsiColors.ToColor(colorString, isBackground, w.TrueColor) +func getAnsiFromColorString(colorString string, isBackground bool) Color { + return AnsiColors.ToColor(colorString, isBackground, TrueColor) } -func (w *Writer) write(s rune) { - if w.isInvisible { +func write(s rune) { + if isInvisible { return } - if w.isHyperlink { - w.builder.WriteRune(s) + if isHyperlink { + builder.WriteRune(s) return } - if !w.Interactive { - for special, escape := range w.formats.escapeSequences { - if s == special && w.lastRune != escape { - w.builder.WriteRune(escape) + if !Interactive { + for special, escape := range formats.escapeSequences { + if s == special && lastRune != escape { + builder.WriteRune(escape) } } } - w.length += runewidth.RuneWidth(s) - w.lastRune = s - w.builder.WriteRune(s) + length += runewidth.RuneWidth(s) + lastRune = s + builder.WriteRune(s) } -func (w *Writer) writeSegmentColors() { +func writeSegmentColors() { // use correct starting colors - bg := w.backgroundColor - fg := w.foregroundColor - if !w.currentColor.Background().IsEmpty() { - bg = w.currentColor.Background() + bg := backgroundColor + fg := foregroundColor + if !currentColor.Background().IsEmpty() { + bg = currentColor.Background() } - if !w.currentColor.Foreground().IsEmpty() { - fg = w.currentColor.Foreground() + if !currentColor.Foreground().IsEmpty() { + fg = currentColor.Foreground() } // ignore processing fully tranparent colors - w.isInvisible = fg.IsTransparent() && bg.IsTransparent() - if w.isInvisible { + isInvisible = fg.IsTransparent() && bg.IsTransparent() + if isInvisible { return } - if fg.IsTransparent() && len(w.BackgroundColor) != 0 { //nolint: gocritic - background := w.getAnsiFromColorString(w.BackgroundColor, false) - w.writeEscapedAnsiString(fmt.Sprintf(colorise, background)) - w.writeEscapedAnsiString(fmt.Sprintf(colorise, bg.ToForeground())) + if fg.IsTransparent() && len(BackgroundColor) != 0 { //nolint: gocritic + background := getAnsiFromColorString(BackgroundColor, false) + writeEscapedAnsiString(fmt.Sprintf(colorise, background)) + writeEscapedAnsiString(fmt.Sprintf(colorise, bg.ToForeground())) } else if fg.IsTransparent() && !bg.IsEmpty() { - w.isTransparent = true - w.writeEscapedAnsiString(fmt.Sprintf(transparentStart, bg)) + isTransparent = true + writeEscapedAnsiString(fmt.Sprintf(transparentStart, bg)) } else { if !bg.IsEmpty() && !bg.IsTransparent() { - w.writeEscapedAnsiString(fmt.Sprintf(colorise, bg)) + writeEscapedAnsiString(fmt.Sprintf(colorise, bg)) } if !fg.IsEmpty() { - w.writeEscapedAnsiString(fmt.Sprintf(colorise, fg)) + writeEscapedAnsiString(fmt.Sprintf(colorise, fg)) } } // 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 // check color reset first if match[ANCHOR] == resetStyle.AnchorEnd { - return w.endColorOverride(position) + return endColorOverride(position) } position += len([]rune(match[ANCHOR])) - 1 for _, style := range knownStyles { if style.AnchorEnd == match[ANCHOR] { - w.writeEscapedAnsiString(style.End) + writeEscapedAnsiString(style.End) return position } if style.AnchorStart == match[ANCHOR] { - w.writeEscapedAnsiString(style.Start) + writeEscapedAnsiString(style.Start) return position } } @@ -538,77 +538,77 @@ func (w *Writer) writeArchorOverride(match map[string]string, background string, match[BG] = background } - bg, fg := w.asAnsiColors(match[BG], match[FG]) + bg, fg := asAnsiColors(match[BG], match[FG]) // ignore processing fully tranparent colors - w.isInvisible = fg.IsTransparent() && bg.IsTransparent() - if w.isInvisible { + isInvisible = fg.IsTransparent() && bg.IsTransparent() + if isInvisible { return position } // make sure we have colors if fg.IsEmpty() { - fg = w.foregroundColor + fg = foregroundColor } 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 { - background := w.getAnsiFromColorString(w.BackgroundColor, false) - w.writeEscapedAnsiString(fmt.Sprintf(colorise, background)) - w.writeEscapedAnsiString(fmt.Sprintf(colorise, w.currentColor.Background().ToForeground())) + if currentColor.Foreground().IsTransparent() && len(BackgroundColor) != 0 { + background := getAnsiFromColorString(BackgroundColor, false) + writeEscapedAnsiString(fmt.Sprintf(colorise, background)) + writeEscapedAnsiString(fmt.Sprintf(colorise, currentColor.Background().ToForeground())) return position } - if w.currentColor.Foreground().IsTransparent() && !w.currentColor.Background().IsTransparent() { - w.isTransparent = true - w.writeEscapedAnsiString(fmt.Sprintf(transparentStart, w.currentColor.Background())) + if currentColor.Foreground().IsTransparent() && !currentColor.Background().IsTransparent() { + isTransparent = true + writeEscapedAnsiString(fmt.Sprintf(transparentStart, currentColor.Background())) return position } - if w.currentColor.Background() != w.backgroundColor { + if currentColor.Background() != backgroundColor { // end the colors in case we have a transparent background - if w.currentColor.Background().IsTransparent() { - w.writeEscapedAnsiString(backgroundEnd) + if currentColor.Background().IsTransparent() { + writeEscapedAnsiString(backgroundEnd) } else { - w.writeEscapedAnsiString(fmt.Sprintf(colorise, w.currentColor.Background())) + writeEscapedAnsiString(fmt.Sprintf(colorise, currentColor.Background())) } } - if w.currentColor.Foreground() != w.foregroundColor { - w.writeEscapedAnsiString(fmt.Sprintf(colorise, w.currentColor.Foreground())) + if currentColor.Foreground() != foregroundColor { + writeEscapedAnsiString(fmt.Sprintf(colorise, currentColor.Foreground())) } return position } -func (w *Writer) endColorOverride(position int) int { +func endColorOverride(position int) int { // make sure to reset the colors if needed position += len([]rune(resetStyle.AnchorEnd)) - 1 // do not restore colors at the end of the string, we print it anyways - if position == len(w.runes)-1 { - w.currentColor.Pop() + if position == len(runes)-1 { + currentColor.Pop() return position } // 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 // and print the previous override as it wasn't ended yet - if w.currentColor.Len() > 1 { - fg := w.currentColor.Foreground() - bg := w.currentColor.Background() + if currentColor.Len() > 1 { + fg := currentColor.Foreground() + bg := currentColor.Background() - w.currentColor.Pop() + currentColor.Pop() - previousBg := w.currentColor.Background() - previousFg := w.currentColor.Foreground() + previousBg := currentColor.Background() + previousFg := currentColor.Foreground() - if w.isTransparent { - w.writeEscapedAnsiString(transparentEnd) + if isTransparent { + writeEscapedAnsiString(transparentEnd) } if previousBg != bg { @@ -617,45 +617,45 @@ func (w *Writer) endColorOverride(position int) int { background = backgroundStyle.End } - w.writeEscapedAnsiString(background) + writeEscapedAnsiString(background) } if previousFg != fg { - w.writeEscapedAnsiString(fmt.Sprintf(colorise, previousFg)) + writeEscapedAnsiString(fmt.Sprintf(colorise, previousFg)) } return position } // pop the last colors from the stack - defer w.currentColor.Pop() + defer currentColor.Pop() // 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 } - if w.isTransparent { - w.writeEscapedAnsiString(transparentEnd) + if isTransparent { + writeEscapedAnsiString(transparentEnd) } - if w.backgroundColor.IsClear() { - w.writeEscapedAnsiString(backgroundStyle.End) + if backgroundColor.IsClear() { + writeEscapedAnsiString(backgroundStyle.End) } - if w.currentColor.Background() != w.backgroundColor && !w.backgroundColor.IsClear() { - w.writeEscapedAnsiString(fmt.Sprintf(colorise, w.backgroundColor)) + if currentColor.Background() != backgroundColor && !backgroundColor.IsClear() { + writeEscapedAnsiString(fmt.Sprintf(colorise, backgroundColor)) } - if (w.currentColor.Foreground() != w.foregroundColor || w.isTransparent) && !w.foregroundColor.IsClear() { - w.writeEscapedAnsiString(fmt.Sprintf(colorise, w.foregroundColor)) + if (currentColor.Foreground() != foregroundColor || isTransparent) && !foregroundColor.IsClear() { + writeEscapedAnsiString(fmt.Sprintf(colorise, foregroundColor)) } - w.isTransparent = false + isTransparent = false return position } -func (w *Writer) asAnsiColors(background, foreground string) (Color, Color) { +func asAnsiColors(background, foreground string) (Color, Color) { if len(background) == 0 { background = Background } @@ -664,18 +664,18 @@ func (w *Writer) asAnsiColors(background, foreground string) (Color, Color) { foreground = Foreground } - background = w.expandKeyword(background) - foreground = w.expandKeyword(foreground) + background = expandKeyword(background) + foreground = expandKeyword(foreground) inverted := foreground == Transparent && len(background) != 0 - backgroundAnsi := w.getAnsiFromColorString(background, !inverted) - foregroundAnsi := w.getAnsiFromColorString(foreground, false) + backgroundAnsi := getAnsiFromColorString(background, !inverted) + foregroundAnsi := getAnsiFromColorString(foreground, false) return backgroundAnsi, foregroundAnsi } -func (w *Writer) isKeyword(color string) bool { +func isKeyword(color string) bool { switch color { case Transparent, ParentBackground, ParentForeground, Background, Foreground: 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 { - for _, color := range w.ParentColors { + for _, color := range ParentColors { if color == nil { return Transparent } @@ -712,18 +712,18 @@ func (w *Writer) expandKeyword(keyword string) string { resolveKeyword := func(keyword string) string { switch { - case keyword == Background && w.CurrentColors != nil: - return w.CurrentColors.Background - case keyword == Foreground && w.CurrentColors != nil: - return w.CurrentColors.Foreground - case (keyword == ParentBackground || keyword == ParentForeground) && w.ParentColors != nil: + case keyword == Background && CurrentColors != nil: + return CurrentColors.Background + case keyword == Foreground && CurrentColors != nil: + return CurrentColors.Foreground + case (keyword == ParentBackground || keyword == ParentForeground) && ParentColors != nil: return resolveParentColor(keyword) default: return Transparent } } - for ok := w.isKeyword(keyword); ok; ok = w.isKeyword(keyword) { + for ok := isKeyword(keyword); ok; ok = isKeyword(keyword) { resolved := resolveKeyword(keyword) if resolved == keyword { break @@ -735,7 +735,7 @@ func (w *Writer) expandKeyword(keyword string) string { return keyword } -func (w *Writer) trimAnsi(text string) string { +func trimAnsi(text string) string { if len(text) == 0 || !strings.Contains(text, "\x1b") { return text } diff --git a/src/terminal/writer_hyperlink_test.go b/src/terminal/writer_hyperlink_test.go index d2b0cb3d..de9e965a 100644 --- a/src/terminal/writer_hyperlink_test.go +++ b/src/terminal/writer_hyperlink_test.go @@ -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\\]"}, } for _, tc := range cases { - a := Writer{ - AnsiColors: &DefaultColors{}, - } - a.Init(tc.ShellName) - a.Write("white", "black", tc.Text) - hyperlinkText, _ := a.String() - assert.Equal(t, tc.Expected, hyperlinkText) + Init(tc.ShellName) + AnsiColors = &DefaultColors{} + + Write("white", "black", tc.Text) + + got, _ := String() + + assert.Equal(t, tc.Expected, got) } } @@ -93,13 +94,14 @@ func TestGenerateHyperlinkWithUrl(t *testing.T) { }, } for _, tc := range cases { - a := Writer{ - AnsiColors: &DefaultColors{}, - } - a.Init(tc.ShellName) - a.Write("white", "black", tc.Text) - hyperlinkText, _ := a.String() - assert.Equal(t, tc.Expected, hyperlinkText) + Init(tc.ShellName) + AnsiColors = &DefaultColors{} + + Write("white", "black", tc.Text) + + got, _ := String() + + assert.Equal(t, tc.Expected, got) } } @@ -115,12 +117,13 @@ func TestGenerateFileLink(t *testing.T) { {Text: `file:C:/WindowsWindows`, Expected: "\x1b[47m\x1b[30m\x1b]8;;file:C:/Windows\x1b\\Windows\x1b]8;;\x1b\\\x1b[0m"}, } for _, tc := range cases { - a := Writer{ - AnsiColors: &DefaultColors{}, - } - a.Init(shell.PWSH) - a.Write("white", "black", tc.Text) - hyperlinkText, _ := a.String() - assert.Equal(t, tc.Expected, hyperlinkText) + Init(shell.PWSH) + AnsiColors = &DefaultColors{} + + Write("white", "black", tc.Text) + + got, _ := String() + + assert.Equal(t, tc.Expected, got) } } diff --git a/src/terminal/writer_test.go b/src/terminal/writer_test.go index 50783647..e7c1d314 100644 --- a/src/terminal/writer_test.go +++ b/src/terminal/writer_test.go @@ -221,16 +221,17 @@ func TestWriteANSIColors(t *testing.T) { } for _, tc := range cases { - renderer := &Writer{ - ParentColors: []*Colors{tc.Parent}, - CurrentColors: tc.Colors, - BackgroundColor: tc.TerminalBackground, - AnsiColors: &DefaultColors{}, - TrueColor: true, - } - renderer.Init(shell.GENERIC) - renderer.Write(tc.Colors.Background, tc.Colors.Foreground, tc.Input) - got, _ := renderer.String() + Init(shell.GENERIC) + ParentColors = []*Colors{tc.Parent} + CurrentColors = tc.Colors + BackgroundColor = tc.TerminalBackground + AnsiColors = &DefaultColors{} + TrueColor = true + + Write(tc.Colors.Background, tc.Colors.Foreground, tc.Input) + + got, _ := String() + assert.Equal(t, tc.Expected, got, tc.Case) } } @@ -275,14 +276,17 @@ func TestWriteLength(t *testing.T) { } for _, tc := range cases { - renderer := &Writer{ - ParentColors: []*Colors{}, - CurrentColors: tc.Colors, - AnsiColors: &DefaultColors{}, - } - renderer.Init(shell.GENERIC) - renderer.Write(tc.Colors.Background, tc.Colors.Foreground, tc.Input) - _, got := renderer.String() + Init(shell.GENERIC) + ParentColors = []*Colors{} + CurrentColors = tc.Colors + AnsiColors = &DefaultColors{} + + Init(shell.GENERIC) + + Write(tc.Colors.Background, tc.Colors.Foreground, tc.Input) + + _, got := String() + assert.Equal(t, tc.Expected, got, tc.Case) } }