refactor: move engine to module

This commit is contained in:
Jan De Dobbeleer 2022-01-27 08:38:46 +01:00 committed by Jan De Dobbeleer
parent c0f4b6d6f0
commit fee29f4b2e
23 changed files with 311 additions and 293 deletions

2
.gitignore vendored
View file

@ -9,8 +9,6 @@
*.dll
*.so
*.dylib
# Initialization scripts generated by https://github.com/kevinburke/go-bindata
init.go
# Test binary, built with `go test -c`
*.test

View file

@ -1,4 +1,4 @@
package main
package engine
import (
"oh-my-posh/color"
@ -56,7 +56,7 @@ func (b *Block) initPlain(env environment.Environment, config *Config) {
b.ansi.Init(plain)
b.writer = &color.AnsiWriter{
Ansi: b.ansi,
TerminalBackground: getConsoleBackgroundColor(env, config.TerminalBackground),
TerminalBackground: GetConsoleBackgroundColor(env, config.TerminalBackground),
AnsiColors: config.MakeColors(env),
}
b.env = env

View file

@ -1,4 +1,4 @@
package main
package engine
import (
"testing"

View file

@ -1,4 +1,4 @@
package main
package engine
import (
// "encoding/json"
@ -108,7 +108,7 @@ func loadConfig(env environment.Environment) (*Config, error) {
return &cfg, nil
}
func exportConfig(configFile, format string) string {
func ExportConfig(configFile, format string) string {
if len(format) == 0 {
format = config.JSON
}

View file

@ -1,4 +1,4 @@
package main
package engine
import (
"oh-my-posh/segments"
@ -11,7 +11,7 @@ import (
func TestSettingsExportJSON(t *testing.T) {
defer testClearDefaultConfig()
content := exportConfig("../themes/jandedobbeleer.omp.json", "json")
content := ExportConfig("../themes/jandedobbeleer.omp.json", "json")
assert.NotContains(t, content, "\\u003ctransparent\\u003e")
assert.Contains(t, content, "<transparent>")
}

View file

@ -1,4 +1,4 @@
package main
package engine
import (
"fmt"
@ -10,40 +10,40 @@ import (
"time"
)
type engine struct {
config *Config
env environment.Environment
writer color.Writer
ansi *color.Ansi
consoleTitle *console.Title
plain bool
type Engine struct {
Config *Config
Env environment.Environment
Writer color.Writer
Ansi *color.Ansi
ConsoleTitle *console.Title
Plain bool
console strings.Builder
rprompt string
}
func (e *engine) write(text string) {
func (e *Engine) write(text string) {
e.console.WriteString(text)
}
func (e *engine) writeANSI(text string) {
if e.plain {
func (e *Engine) writeANSI(text string) {
if e.Plain {
return
}
e.console.WriteString(text)
}
func (e *engine) string() string {
func (e *Engine) string() string {
return e.console.String()
}
func (e *engine) canWriteRPrompt() bool {
func (e *Engine) canWriteRPrompt() bool {
prompt := e.string()
consoleWidth, err := e.env.TerminalWidth()
consoleWidth, err := e.Env.TerminalWidth()
if err != nil || consoleWidth == 0 {
return true
}
promptWidth := e.ansi.LenWithoutANSI(prompt)
promptWidth := e.Ansi.LenWithoutANSI(prompt)
availableSpace := consoleWidth - promptWidth
// spanning multiple lines
if availableSpace < 0 {
@ -51,37 +51,37 @@ func (e *engine) canWriteRPrompt() bool {
availableSpace = consoleWidth - overflow
}
promptBreathingRoom := 30
canWrite := (availableSpace - e.ansi.LenWithoutANSI(e.rprompt)) >= promptBreathingRoom
canWrite := (availableSpace - e.Ansi.LenWithoutANSI(e.rprompt)) >= promptBreathingRoom
return canWrite
}
func (e *engine) render() string {
for _, block := range e.config.Blocks {
func (e *Engine) Render() string {
for _, block := range e.Config.Blocks {
e.renderBlock(block)
}
if e.config.ConsoleTitle {
e.writeANSI(e.consoleTitle.GetTitle())
if e.Config.ConsoleTitle {
e.writeANSI(e.ConsoleTitle.GetTitle())
}
e.writeANSI(e.ansi.ColorReset())
if e.config.FinalSpace {
e.writeANSI(e.Ansi.ColorReset())
if e.Config.FinalSpace {
e.write(" ")
}
if !e.config.OSC99 {
if !e.Config.OSC99 {
return e.print()
}
cwd := e.env.Pwd()
e.writeANSI(e.ansi.ConsolePwd(cwd))
cwd := e.Env.Pwd()
e.writeANSI(e.Ansi.ConsolePwd(cwd))
return e.print()
}
func (e *engine) renderBlock(block *Block) {
func (e *Engine) renderBlock(block *Block) {
// when in bash, for rprompt blocks we need to write plain
// and wrap in escaped mode or the prompt will not render correctly
if block.Type == RPrompt && e.env.Shell() == bash {
block.initPlain(e.env, e.config)
if block.Type == RPrompt && e.Env.Shell() == bash {
block.initPlain(e.Env, e.Config)
} else {
block.init(e.env, e.writer, e.ansi)
block.init(e.Env, e.Writer, e.Ansi)
}
block.setStringValues()
if !block.enabled() {
@ -98,21 +98,21 @@ func (e *engine) renderBlock(block *Block) {
e.write("\n")
case Prompt:
if block.VerticalOffset != 0 {
e.writeANSI(e.ansi.ChangeLine(block.VerticalOffset))
e.writeANSI(e.Ansi.ChangeLine(block.VerticalOffset))
}
switch block.Alignment {
case Right:
e.writeANSI(e.ansi.CarriageForward())
e.writeANSI(e.Ansi.CarriageForward())
blockText := block.renderSegments()
e.writeANSI(e.ansi.GetCursorForRightWrite(blockText, block.HorizontalOffset))
e.writeANSI(e.Ansi.GetCursorForRightWrite(blockText, block.HorizontalOffset))
e.write(blockText)
case Left:
e.write(block.renderSegments())
}
case RPrompt:
blockText := block.renderSegments()
if e.env.Shell() == bash {
blockText = e.ansi.FormatText(blockText)
if e.Env.Shell() == bash {
blockText = e.Ansi.FormatText(blockText)
}
e.rprompt = blockText
}
@ -120,33 +120,33 @@ func (e *engine) renderBlock(block *Block) {
// If this doesn't happen, the portion after the prompt gets colored in the background
// color of the line above the new input line. Clearing the line fixes this,
// but can hopefully one day be removed when this is resolved natively.
if e.env.Shell() == pwsh || e.env.Shell() == powershell5 {
e.writeANSI(e.ansi.ClearAfter())
if e.Env.Shell() == pwsh || e.Env.Shell() == powershell5 {
e.writeANSI(e.Ansi.ClearAfter())
}
}
// debug will loop through your config file and output the timings for each segments
func (e *engine) debug() string {
func (e *Engine) Debug(version string) string {
var segmentTimings []*SegmentTiming
largestSegmentNameLength := 0
e.write(fmt.Sprintf("\n\x1b[1mVersion:\x1b[0m %s\n", Version))
e.write(fmt.Sprintf("\n\x1b[1mVersion:\x1b[0m %s\n", version))
e.write("\n\x1b[1mSegments:\x1b[0m\n\n")
// console title timing
start := time.Now()
consoleTitle := e.consoleTitle.GetTitle()
consoleTitle := e.ConsoleTitle.GetTitle()
duration := time.Since(start)
segmentTiming := &SegmentTiming{
name: "ConsoleTitle",
nameLength: 12,
enabled: e.config.ConsoleTitle,
enabled: e.Config.ConsoleTitle,
stringValue: consoleTitle,
enabledDuration: 0,
stringDuration: duration,
}
segmentTimings = append(segmentTimings, segmentTiming)
// loop each segments of each blocks
for _, block := range e.config.Blocks {
block.init(e.env, e.writer, e.ansi)
for _, block := range e.Config.Blocks {
block.init(e.Env, e.Writer, e.Ansi)
longestSegmentName, timings := block.debug()
segmentTimings = append(segmentTimings, timings...)
if longestSegmentName > largestSegmentNameLength {
@ -165,16 +165,16 @@ func (e *engine) debug() string {
e.write(fmt.Sprintf("%-*s - %3d ms - %s\n", largestSegmentNameLength, segmentName, duration, segment.stringValue))
}
e.write(fmt.Sprintf("\n\x1b[1mRun duration:\x1b[0m %s\n", time.Since(start)))
e.write(fmt.Sprintf("\n\x1b[1mCache path:\x1b[0m %s\n", e.env.CachePath()))
e.write(fmt.Sprintf("\n\x1b[1mCache path:\x1b[0m %s\n", e.Env.CachePath()))
e.write("\n\x1b[1mLogs:\x1b[0m\n\n")
e.write(e.env.Logs())
e.write(e.Env.Logs())
return e.string()
}
func (e *engine) print() string {
switch e.env.Shell() {
func (e *Engine) print() string {
switch e.Env.Shell() {
case zsh:
if !*e.env.Args().Eval {
if !*e.Env.Args().Eval {
break
}
// escape double quotes contained in the prompt
@ -182,22 +182,22 @@ func (e *engine) print() string {
prompt += fmt.Sprintf("\nRPROMPT=\"%s\"", e.rprompt)
return prompt
case pwsh, powershell5, bash, plain:
if e.rprompt == "" || !e.canWriteRPrompt() || e.plain {
if e.rprompt == "" || !e.canWriteRPrompt() || e.Plain {
break
}
e.write(e.ansi.SaveCursorPosition())
e.write(e.ansi.CarriageForward())
e.write(e.ansi.GetCursorForRightWrite(e.rprompt, 0))
e.write(e.Ansi.SaveCursorPosition())
e.write(e.Ansi.CarriageForward())
e.write(e.Ansi.GetCursorForRightWrite(e.rprompt, 0))
e.write(e.rprompt)
e.write(e.ansi.RestoreCursorPosition())
e.write(e.Ansi.RestoreCursorPosition())
}
return e.string()
}
func (e *engine) renderTooltip(tip string) string {
func (e *Engine) RenderTooltip(tip string) string {
tip = strings.Trim(tip, " ")
var tooltip *Segment
for _, tp := range e.config.Tooltips {
for _, tp := range e.Config.Tooltips {
if !tp.shouldInvokeWithTip(tip) {
continue
}
@ -206,7 +206,7 @@ func (e *engine) renderTooltip(tip string) string {
if tooltip == nil {
return ""
}
if err := tooltip.mapSegmentWithWriter(e.env); err != nil {
if err := tooltip.mapSegmentWithWriter(e.Env); err != nil {
return ""
}
if !tooltip.enabled() {
@ -218,53 +218,53 @@ func (e *engine) renderTooltip(tip string) string {
Alignment: Right,
Segments: []*Segment{tooltip},
}
switch e.env.Shell() {
switch e.Env.Shell() {
case zsh, winCMD:
block.init(e.env, e.writer, e.ansi)
block.init(e.Env, e.Writer, e.Ansi)
return block.renderSegments()
case pwsh, powershell5:
block.initPlain(e.env, e.config)
block.initPlain(e.Env, e.Config)
tooltipText := block.renderSegments()
e.write(e.ansi.ClearAfter())
e.write(e.ansi.CarriageForward())
e.write(e.ansi.GetCursorForRightWrite(tooltipText, 0))
e.write(e.Ansi.ClearAfter())
e.write(e.Ansi.CarriageForward())
e.write(e.Ansi.GetCursorForRightWrite(tooltipText, 0))
e.write(tooltipText)
return e.string()
}
return ""
}
func (e *engine) renderTransientPrompt() string {
if e.config.TransientPrompt == nil {
func (e *Engine) RenderTransientPrompt() string {
if e.Config.TransientPrompt == nil {
return ""
}
promptTemplate := e.config.TransientPrompt.Template
promptTemplate := e.Config.TransientPrompt.Template
if len(promptTemplate) == 0 {
promptTemplate = "{{ .Shell }}> "
}
tmpl := &template.Text{
Template: promptTemplate,
Env: e.env,
Env: e.Env,
}
prompt, err := tmpl.Render()
if err != nil {
prompt = err.Error()
}
e.writer.SetColors(e.config.TransientPrompt.Background, e.config.TransientPrompt.Foreground)
e.writer.Write(e.config.TransientPrompt.Background, e.config.TransientPrompt.Foreground, prompt)
switch e.env.Shell() {
e.Writer.SetColors(e.Config.TransientPrompt.Background, e.Config.TransientPrompt.Foreground)
e.Writer.Write(e.Config.TransientPrompt.Background, e.Config.TransientPrompt.Foreground, prompt)
switch e.Env.Shell() {
case zsh:
// escape double quotes contained in the prompt
prompt := fmt.Sprintf("PS1=\"%s\"", strings.ReplaceAll(e.writer.String(), "\"", "\"\""))
prompt := fmt.Sprintf("PS1=\"%s\"", strings.ReplaceAll(e.Writer.String(), "\"", "\"\""))
prompt += "\nRPROMPT=\"\""
return prompt
case pwsh, powershell5, winCMD:
return e.writer.String()
return e.Writer.String()
}
return ""
}
func (e *engine) renderRPrompt() string {
func (e *Engine) RenderRPrompt() string {
filterRPromptBlock := func(blocks []*Block) *Block {
for _, block := range blocks {
if block.Type == RPrompt {
@ -273,11 +273,11 @@ func (e *engine) renderRPrompt() string {
}
return nil
}
block := filterRPromptBlock(e.config.Blocks)
block := filterRPromptBlock(e.Config.Blocks)
if block == nil {
return ""
}
block.init(e.env, e.writer, e.ansi)
block.init(e.Env, e.Writer, e.Ansi)
block.setStringValues()
if !block.enabled() {
return ""

View file

@ -1,4 +1,4 @@
package main
package engine
import (
"errors"
@ -37,9 +37,9 @@ func TestCanWriteRPrompt(t *testing.T) {
env.On("TerminalWidth").Return(tc.TerminalWidth, tc.TerminalWidthError)
ansi := &color.Ansi{}
ansi.Init(plain)
engine := &engine{
env: env,
ansi: ansi,
engine := &Engine{
Env: env,
Ansi: ansi,
}
engine.rprompt = strings.Repeat("x", tc.RPromptLength)
engine.console.WriteString(strings.Repeat("x", tc.PromptLength))
@ -101,7 +101,7 @@ func engineRender(configPath string) error {
writerColors := cfg.MakeColors(env)
writer := &color.AnsiWriter{
Ansi: ansi,
TerminalBackground: getConsoleBackgroundColor(env, cfg.TerminalBackground),
TerminalBackground: GetConsoleBackgroundColor(env, cfg.TerminalBackground),
AnsiColors: writerColors,
}
consoleTitle := &console.Title{
@ -110,16 +110,16 @@ func engineRender(configPath string) error {
Style: cfg.ConsoleTitleStyle,
Template: cfg.ConsoleTitleTemplate,
}
engine := &engine{
config: cfg,
env: env,
writer: writer,
consoleTitle: consoleTitle,
ansi: ansi,
plain: *args.Plain,
engine := &Engine{
Config: cfg,
Env: env,
Writer: writer,
ConsoleTitle: consoleTitle,
Ansi: ansi,
Plain: *args.Plain,
}
engine.render()
engine.Render()
return nil
}

View file

@ -20,7 +20,7 @@
// https://github.com/homeport/termshot
package main
package engine
import (
_ "embed"
@ -97,10 +97,14 @@ func NewRGBColor(ansiColor string) *RGB {
}
type ImageRenderer struct {
ansiString string
author string
ansi *color.Ansi
bgColor string
AnsiString string
Author string
CursorPadding int
RPromptOffset int
BgColor string
Ansi *color.Ansi
path string
factor float64
@ -128,11 +132,12 @@ type ImageRenderer struct {
backgroundColor *RGB
foregroundColor *RGB
ansiSequenceRegexMap map[string]string
rPromptOffset int
cursorPadding int
}
func (ir *ImageRenderer) init() {
func (ir *ImageRenderer) Init(config string) {
match := regex.FindNamedRegexMatch(`.*(\/|\\)(?P<STR>.+).omp.(json|yaml|toml)`, config)
ir.path = fmt.Sprintf("%s.png", match[str])
f := 2.0
ir.cleanContent()
@ -244,8 +249,8 @@ func (ir *ImageRenderer) runeAdditionalWidth(r rune) int {
func (ir *ImageRenderer) calculateWidth() int {
longest := 0
for _, line := range strings.Split(ir.ansiString, "\n") {
length := ir.ansi.LenWithoutANSI(line)
for _, line := range strings.Split(ir.AnsiString, "\n") {
length := ir.Ansi.LenWithoutANSI(line)
for _, char := range line {
length += ir.runeAdditionalWidth(char)
}
@ -258,27 +263,27 @@ func (ir *ImageRenderer) calculateWidth() int {
func (ir *ImageRenderer) cleanContent() {
rPromptAnsi := "\x1b7\x1b[1000C"
hasRPrompt := strings.Contains(ir.ansiString, rPromptAnsi)
hasRPrompt := strings.Contains(ir.AnsiString, rPromptAnsi)
// clean abundance of empty lines
ir.ansiString = strings.Trim(ir.ansiString, "\n")
ir.ansiString = "\n" + ir.ansiString
ir.AnsiString = strings.Trim(ir.AnsiString, "\n")
ir.AnsiString = "\n" + ir.AnsiString
// clean string before render
ir.ansiString = strings.ReplaceAll(ir.ansiString, "\x1b[m", "\x1b[0m")
ir.ansiString = strings.ReplaceAll(ir.ansiString, "\x1b[K", "")
ir.ansiString = strings.ReplaceAll(ir.ansiString, "\x1b[1F", "")
ir.ansiString = strings.ReplaceAll(ir.ansiString, "\x1b8", "")
ir.ansiString = strings.ReplaceAll(ir.ansiString, "\u2800", " ")
ir.AnsiString = strings.ReplaceAll(ir.AnsiString, "\x1b[m", "\x1b[0m")
ir.AnsiString = strings.ReplaceAll(ir.AnsiString, "\x1b[K", "")
ir.AnsiString = strings.ReplaceAll(ir.AnsiString, "\x1b[1F", "")
ir.AnsiString = strings.ReplaceAll(ir.AnsiString, "\x1b8", "")
ir.AnsiString = strings.ReplaceAll(ir.AnsiString, "\u2800", " ")
// replace rprompt with adding and mark right aligned blocks with a pointer
ir.ansiString = strings.ReplaceAll(ir.ansiString, rPromptAnsi, fmt.Sprintf("_%s", strings.Repeat(" ", ir.cursorPadding)))
ir.ansiString = strings.ReplaceAll(ir.ansiString, "\x1b[1000C", strings.Repeat(" ", ir.rPromptOffset))
ir.AnsiString = strings.ReplaceAll(ir.AnsiString, rPromptAnsi, fmt.Sprintf("_%s", strings.Repeat(" ", ir.CursorPadding)))
ir.AnsiString = strings.ReplaceAll(ir.AnsiString, "\x1b[1000C", strings.Repeat(" ", ir.RPromptOffset))
if !hasRPrompt {
ir.ansiString += fmt.Sprintf("_%s", strings.Repeat(" ", ir.cursorPadding))
ir.AnsiString += fmt.Sprintf("_%s", strings.Repeat(" ", ir.CursorPadding))
}
// add watermarks
ir.ansiString += "\n\n\x1b[1mhttps://ohmyposh.dev\x1b[22m"
if len(ir.author) > 0 {
createdBy := fmt.Sprintf(" by \x1b[1m%s\x1b[22m", ir.author)
ir.ansiString += createdBy
ir.AnsiString += "\n\n\x1b[1mhttps://ohmyposh.dev\x1b[22m"
if len(ir.Author) > 0 {
createdBy := fmt.Sprintf(" by \x1b[1m%s\x1b[22m", ir.Author)
ir.AnsiString += createdBy
}
}
@ -290,11 +295,11 @@ func (ir *ImageRenderer) measureContent() (width, height float64) {
advance := tmpDrawer.MeasureString(strings.Repeat(" ", linewidth))
width = float64(advance >> 6)
// height, lines times font height and line spacing
height = float64(len(strings.Split(ir.ansiString, "\n"))) * ir.fontHeight() * ir.lineSpacing
height = float64(len(strings.Split(ir.AnsiString, "\n"))) * ir.fontHeight() * ir.lineSpacing
return width, height
}
func (ir *ImageRenderer) SavePNG(path string) error {
func (ir *ImageRenderer) SavePNG() error {
var f = func(value float64) float64 { return ir.factor * value }
var (
@ -345,7 +350,7 @@ func (ir *ImageRenderer) SavePNG(path string) error {
// Draw rounded rectangle with outline and three button to produce the
// impression of a window with controls and a content area
dc.DrawRoundedRectangle(xOffset, yOffset, width-2*marginX, height-2*marginY, corner)
dc.SetHexColor(ir.bgColor)
dc.SetHexColor(ir.BgColor)
dc.Fill()
dc.DrawRoundedRectangle(xOffset, yOffset, width-2*marginX, height-2*marginY, corner)
@ -362,16 +367,16 @@ func (ir *ImageRenderer) SavePNG(path string) error {
// Apply the actual text into the prepared content area of the window
var x, y float64 = xOffset + paddingX, yOffset + paddingY + titleOffset + ir.fontHeight()
for len(ir.ansiString) != 0 {
for len(ir.AnsiString) != 0 {
if !ir.shouldPrint() {
continue
}
runes := []rune(ir.ansiString)
runes := []rune(ir.AnsiString)
if len(runes) == 0 {
continue
}
str := string(runes[0:1])
ir.ansiString = string(runes[1:])
ir.AnsiString = string(runes[1:])
switch ir.style {
case bold:
dc.SetFontFace(ir.bold)
@ -419,16 +424,16 @@ func (ir *ImageRenderer) SavePNG(path string) error {
x += w
}
return dc.SavePNG(path)
return dc.SavePNG(ir.path)
}
func (ir *ImageRenderer) shouldPrint() bool {
for sequence, re := range ir.ansiSequenceRegexMap {
match := regex.FindNamedRegexMatch(re, ir.ansiString)
match := regex.FindNamedRegexMatch(re, ir.AnsiString)
if len(match) == 0 {
continue
}
ir.ansiString = strings.TrimPrefix(ir.ansiString, match[str])
ir.AnsiString = strings.TrimPrefix(ir.AnsiString, match[str])
switch sequence {
case invertedColor:
ir.foregroundColor = ir.defaultBackgroundColor
@ -463,7 +468,7 @@ func (ir *ImageRenderer) shouldPrint() bool {
ir.setBase16Color(match[fg])
return false
case link:
ir.ansiString = match[url] + ir.ansiString
ir.AnsiString = match[url] + ir.AnsiString
}
}
return true

View file

@ -1,4 +1,4 @@
package main
package engine
import (
"io/ioutil"
@ -10,7 +10,7 @@ import (
)
func runImageTest(content string) error {
poshImagePath := "ohmyposh.png"
poshImagePath := "jandedobbeleer.png"
file, err := ioutil.TempFile("", poshImagePath)
if err != nil {
return err
@ -19,11 +19,11 @@ func runImageTest(content string) error {
ansi := &color.Ansi{}
ansi.Init(plain)
image := &ImageRenderer{
ansiString: content,
ansi: ansi,
AnsiString: content,
Ansi: ansi,
}
image.init()
err = image.SavePNG(poshImagePath)
image.Init("~/jandedobbeleer.omp.json")
err = image.SavePNG()
return err
}

112
src/engine/init.go Normal file
View file

@ -0,0 +1,112 @@
package engine
import (
_ "embed"
"fmt"
"oh-my-posh/environment"
"oh-my-posh/template"
"os"
"strings"
)
//go:embed init/omp.ps1
var pwshInit string
//go:embed init/omp.fish
var fishInit string
//go:embed init/omp.bash
var bashInit string
//go:embed init/omp.zsh
var zshInit string
//go:embed init/omp.lua
var cmdInit string
const (
noExe = "echo \"Unable to find Oh My Posh executable\""
zsh = "zsh"
bash = "bash"
pwsh = "pwsh"
fish = "fish"
powershell5 = "powershell"
winCMD = "cmd"
plain = "shell"
)
func getExecutablePath(shell string) (string, error) {
executable, err := os.Executable()
if err != nil {
return "", err
}
// On Windows, it fails when the excutable is called in MSYS2 for example
// which uses unix style paths to resolve the executable's location.
// PowerShell knows how to resolve both, so we can swap this without any issue.
executable = strings.ReplaceAll(executable, "\\", "/")
switch shell {
case bash, zsh:
return strings.ReplaceAll(executable, " ", "\\ "), nil
}
return executable, nil
}
func InitShell(shell, configFile string) string {
executable, err := getExecutablePath(shell)
if err != nil {
return noExe
}
switch shell {
case pwsh, powershell5:
return fmt.Sprintf("(@(&\"%s\" --print-init --shell=%s --config=\"%s\") -join \"`n\") | Invoke-Expression", executable, shell, configFile)
case zsh, bash, fish, winCMD:
return PrintShellInit(shell, configFile)
default:
return fmt.Sprintf("echo \"No initialization script available for %s\"", shell)
}
}
func PrintShellInit(shell, configFile string) string {
executable, err := getExecutablePath(shell)
if err != nil {
return noExe
}
switch shell {
case pwsh, powershell5:
return getShellInitScript(executable, configFile, pwshInit)
case zsh:
return getShellInitScript(executable, configFile, zshInit)
case bash:
return getShellInitScript(executable, configFile, bashInit)
case fish:
return getShellInitScript(executable, configFile, fishInit)
case winCMD:
return getShellInitScript(executable, configFile, cmdInit)
default:
return fmt.Sprintf("echo \"No initialization script available for %s\"", shell)
}
}
func getShellInitScript(executable, configFile, script string) string {
script = strings.ReplaceAll(script, "::OMP::", executable)
script = strings.ReplaceAll(script, "::CONFIG::", configFile)
return script
}
func GetConsoleBackgroundColor(env environment.Environment, backgroundColorTemplate string) string {
if len(backgroundColorTemplate) == 0 {
return backgroundColorTemplate
}
tmpl := &template.Text{
Template: backgroundColorTemplate,
Context: nil,
Env: env,
}
text, err := tmpl.Render()
if err != nil {
return err.Error()
}
return text
}

31
src/engine/init_test.go Normal file
View file

@ -0,0 +1,31 @@
package engine
import (
"oh-my-posh/environment"
"oh-my-posh/mock"
"testing"
"github.com/stretchr/testify/assert"
)
func TestConsoleBackgroundColorTemplate(t *testing.T) {
cases := []struct {
Case string
Expected string
Term string
}{
{Case: "Inside vscode", Expected: "#123456", Term: "vscode"},
{Case: "Outside vscode", Expected: "", Term: "windowsterminal"},
}
for _, tc := range cases {
env := new(mock.MockedEnvironment)
env.On("TemplateCache").Return(&environment.TemplateCache{
Env: map[string]string{
"TERM_PROGRAM": tc.Term,
},
})
color := GetConsoleBackgroundColor(env, "{{ if eq \"vscode\" .Env.TERM_PROGRAM }}#123456{{end}}")
assert.Equal(t, tc.Expected, color, tc.Case)
}
}

View file

@ -1,4 +1,4 @@
package main
package engine
import (
"errors"

View file

@ -1,4 +1,4 @@
package main
package engine
import (
"encoding/json"

View file

@ -1,16 +1,12 @@
package main
import (
_ "embed"
"flag"
"fmt"
"oh-my-posh/color"
"oh-my-posh/console"
"oh-my-posh/engine"
"oh-my-posh/environment"
"oh-my-posh/regex"
"oh-my-posh/template"
"os"
"strings"
"time"
"github.com/gookit/config/v2"
@ -19,33 +15,6 @@ import (
// Version number of oh-my-posh
var Version = "development"
//go:embed init/omp.ps1
var pwshInit string
//go:embed init/omp.fish
var fishInit string
//go:embed init/omp.bash
var bashInit string
//go:embed init/omp.zsh
var zshInit string
//go:embed init/omp.lua
var cmdInit string
const (
noExe = "echo \"Unable to find Oh My Posh executable\""
zsh = "zsh"
bash = "bash"
pwsh = "pwsh"
fish = "fish"
powershell5 = "powershell"
winCMD = "cmd"
plain = "shell"
)
func main() {
args := &environment.Args{
ErrorCode: flag.Int(
@ -174,20 +143,20 @@ func main() {
return
}
if *args.Init {
init := initShell(*args.Shell, *args.Config)
init := engine.InitShell(*args.Shell, *args.Config)
fmt.Print(init)
return
}
if *args.PrintInit {
init := printShellInit(*args.Shell, *args.Config)
init := engine.PrintShellInit(*args.Shell, *args.Config)
fmt.Print(init)
return
}
if *args.PrintConfig {
fmt.Print(exportConfig(*args.Config, *args.ConfigFormat))
fmt.Print(engine.ExportConfig(*args.Config, *args.ConfigFormat))
return
}
cfg := GetConfig(env)
cfg := engine.GetConfig(env)
ansi := &color.Ansi{}
ansi.Init(env.Shell())
var writer color.Writer
@ -197,7 +166,7 @@ func main() {
writerColors := cfg.MakeColors(env)
writer = &color.AnsiWriter{
Ansi: ansi,
TerminalBackground: getConsoleBackgroundColor(env, cfg.TerminalBackground),
TerminalBackground: engine.GetConsoleBackgroundColor(env, cfg.TerminalBackground),
AnsiColors: writerColors,
}
}
@ -207,121 +176,46 @@ func main() {
Template: cfg.ConsoleTitleTemplate,
Style: cfg.ConsoleTitleStyle,
}
engine := &engine{
config: cfg,
env: env,
writer: writer,
consoleTitle: consoleTitle,
ansi: ansi,
plain: *args.Plain,
eng := &engine.Engine{
Config: cfg,
Env: env,
Writer: writer,
ConsoleTitle: consoleTitle,
Ansi: ansi,
Plain: *args.Plain,
}
if *args.Debug {
fmt.Print(engine.debug())
fmt.Print(eng.Debug(Version))
return
}
if *args.PrintTransient {
fmt.Print(engine.renderTransientPrompt())
fmt.Print(eng.RenderTransientPrompt())
return
}
if len(*args.Command) != 0 {
fmt.Print(engine.renderTooltip(*args.Command))
fmt.Print(eng.RenderTooltip(*args.Command))
return
}
if *args.RPrompt {
fmt.Print(engine.renderRPrompt())
fmt.Print(eng.RenderRPrompt())
return
}
prompt := engine.render()
prompt := eng.Render()
if !*args.ExportPNG {
fmt.Print(prompt)
return
}
imageCreator := &ImageRenderer{
ansiString: prompt,
author: *args.Author,
cursorPadding: *args.CursorPadding,
rPromptOffset: *args.RPromptOffset,
bgColor: *args.BGColor,
ansi: ansi,
imageCreator := &engine.ImageRenderer{
AnsiString: prompt,
Author: *args.Author,
CursorPadding: *args.CursorPadding,
RPromptOffset: *args.RPromptOffset,
BgColor: *args.BGColor,
Ansi: ansi,
}
imageCreator.init()
match := regex.FindNamedRegexMatch(`.*(\/|\\)(?P<STR>.+).omp.(json|yaml|toml)`, *args.Config)
err := imageCreator.SavePNG(fmt.Sprintf("%s.png", match[str]))
imageCreator.Init(*args.Config)
err := imageCreator.SavePNG()
if err != nil {
fmt.Print(err.Error())
}
}
func getExecutablePath(shell string) (string, error) {
executable, err := os.Executable()
if err != nil {
return "", err
}
// On Windows, it fails when the excutable is called in MSYS2 for example
// which uses unix style paths to resolve the executable's location.
// PowerShell knows how to resolve both, so we can swap this without any issue.
executable = strings.ReplaceAll(executable, "\\", "/")
switch shell {
case bash, zsh:
return strings.ReplaceAll(executable, " ", "\\ "), nil
}
return executable, nil
}
func initShell(shell, configFile string) string {
executable, err := getExecutablePath(shell)
if err != nil {
return noExe
}
switch shell {
case pwsh, powershell5:
return fmt.Sprintf("(@(&\"%s\" --print-init --shell=%s --config=\"%s\") -join \"`n\") | Invoke-Expression", executable, shell, configFile)
case zsh, bash, fish, winCMD:
return printShellInit(shell, configFile)
default:
return fmt.Sprintf("echo \"No initialization script available for %s\"", shell)
}
}
func printShellInit(shell, configFile string) string {
executable, err := getExecutablePath(shell)
if err != nil {
return noExe
}
switch shell {
case pwsh, powershell5:
return getShellInitScript(executable, configFile, pwshInit)
case zsh:
return getShellInitScript(executable, configFile, zshInit)
case bash:
return getShellInitScript(executable, configFile, bashInit)
case fish:
return getShellInitScript(executable, configFile, fishInit)
case winCMD:
return getShellInitScript(executable, configFile, cmdInit)
default:
return fmt.Sprintf("echo \"No initialization script available for %s\"", shell)
}
}
func getShellInitScript(executable, configFile, script string) string {
script = strings.ReplaceAll(script, "::OMP::", executable)
script = strings.ReplaceAll(script, "::CONFIG::", configFile)
return script
}
func getConsoleBackgroundColor(env environment.Environment, backgroundColorTemplate string) string {
if len(backgroundColorTemplate) == 0 {
return backgroundColorTemplate
}
tmpl := &template.Text{
Template: backgroundColorTemplate,
Context: nil,
Env: env,
}
text, err := tmpl.Render()
if err != nil {
return err.Error()
}
return text
}

View file

@ -8,28 +8,6 @@ import (
"github.com/stretchr/testify/assert"
)
func TestConsoleBackgroundColorTemplate(t *testing.T) {
cases := []struct {
Case string
Expected string
Term string
}{
{Case: "Inside vscode", Expected: "#123456", Term: "vscode"},
{Case: "Outside vscode", Expected: "", Term: "windowsterminal"},
}
for _, tc := range cases {
env := new(mock.MockedEnvironment)
env.On("TemplateCache").Return(&environment.TemplateCache{
Env: map[string]string{
"TERM_PROGRAM": tc.Term,
},
})
color := getConsoleBackgroundColor(env, "{{ if eq \"vscode\" .Env.TERM_PROGRAM }}#123456{{end}}")
assert.Equal(t, tc.Expected, color, tc.Case)
}
}
// This can only be tested here due to circular dependencies
// Which might be an indaction of a fault architecture but
// I honestly could not figure out how to do this better