oh-my-posh/src/engine/engine.go

283 lines
7.3 KiB
Go
Raw Normal View History

2022-01-26 23:38:46 -08:00
package engine
2019-03-13 04:14:30 -07:00
import (
"fmt"
2022-01-26 04:09:21 -08:00
"oh-my-posh/color"
2022-01-26 22:44:35 -08:00
"oh-my-posh/console"
"oh-my-posh/environment"
2022-01-26 06:54:36 -08:00
"oh-my-posh/template"
"strings"
"time"
2019-03-13 04:14:30 -07:00
)
2022-01-26 23:38:46 -08:00
type Engine struct {
Config *Config
Env environment.Environment
Writer color.Writer
Ansi *color.Ansi
ConsoleTitle *console.Title
Plain bool
2021-04-20 12:30:46 -07:00
console strings.Builder
rprompt string
}
2022-01-26 23:38:46 -08:00
func (e *Engine) write(text string) {
2021-04-20 12:30:46 -07:00
e.console.WriteString(text)
}
2022-01-26 23:38:46 -08:00
func (e *Engine) writeANSI(text string) {
if e.Plain {
2021-11-02 06:53:46 -07:00
return
}
e.console.WriteString(text)
}
2022-01-26 23:38:46 -08:00
func (e *Engine) string() string {
2021-04-20 12:30:46 -07:00
return e.console.String()
}
2022-01-26 23:38:46 -08:00
func (e *Engine) canWriteRPrompt() bool {
prompt := e.string()
2022-01-26 23:38:46 -08:00
consoleWidth, err := e.Env.TerminalWidth()
2021-05-25 09:31:23 -07:00
if err != nil || consoleWidth == 0 {
return true
}
2022-01-26 23:38:46 -08:00
promptWidth := e.Ansi.LenWithoutANSI(prompt)
availableSpace := consoleWidth - promptWidth
// spanning multiple lines
if availableSpace < 0 {
overflow := promptWidth % consoleWidth
availableSpace = consoleWidth - overflow
}
promptBreathingRoom := 30
2022-01-26 23:38:46 -08:00
canWrite := (availableSpace - e.Ansi.LenWithoutANSI(e.rprompt)) >= promptBreathingRoom
return canWrite
}
2022-01-26 23:38:46 -08:00
func (e *Engine) Render() string {
for _, block := range e.Config.Blocks {
e.renderBlock(block)
2019-03-13 04:14:30 -07:00
}
2022-01-26 23:38:46 -08:00
if e.Config.ConsoleTitle {
e.writeANSI(e.ConsoleTitle.GetTitle())
2020-10-12 00:02:33 -07:00
}
2022-01-26 23:38:46 -08:00
e.writeANSI(e.Ansi.ColorReset())
if e.Config.FinalSpace {
2021-04-20 12:30:46 -07:00
e.write(" ")
2019-03-13 04:14:30 -07:00
}
2022-01-26 23:38:46 -08:00
if !e.Config.OSC99 {
return e.print()
2021-02-15 13:19:19 -08:00
}
2022-01-26 23:38:46 -08:00
cwd := e.Env.Pwd()
e.writeANSI(e.Ansi.ConsolePwd(cwd))
return e.print()
2020-12-17 23:59:45 -08:00
}
2022-01-26 23:38:46 -08:00
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
2022-01-26 23:38:46 -08:00
if block.Type == RPrompt && e.Env.Shell() == bash {
block.initPlain(e.Env, e.Config)
} else {
2022-01-26 23:38:46 -08:00
block.init(e.Env, e.Writer, e.Ansi)
}
2022-02-02 03:16:39 -08:00
block.renderSegmentsText()
if !block.enabled() {
return
}
if block.Newline {
2021-04-20 12:30:46 -07:00
e.write("\n")
}
switch block.Type {
// This is deprecated but leave if to not break current configs
// It is encouraged to used "newline": true on block level
// rather than the standalone the linebreak block
case LineBreak:
2021-04-20 12:30:46 -07:00
e.write("\n")
case Prompt:
if block.VerticalOffset != 0 {
2022-01-26 23:38:46 -08:00
e.writeANSI(e.Ansi.ChangeLine(block.VerticalOffset))
}
switch block.Alignment {
case Right:
2022-01-26 23:38:46 -08:00
e.writeANSI(e.Ansi.CarriageForward())
blockText := block.renderSegments()
2022-01-26 23:38:46 -08:00
e.writeANSI(e.Ansi.GetCursorForRightWrite(blockText, block.HorizontalOffset))
2021-04-20 12:30:46 -07:00
e.write(blockText)
case Left:
2021-04-20 12:30:46 -07:00
e.write(block.renderSegments())
}
case RPrompt:
blockText := block.renderSegments()
2022-01-26 23:38:46 -08:00
if e.Env.Shell() == bash {
blockText = e.Ansi.FormatText(blockText)
}
e.rprompt = blockText
}
// Due to a bug in Powershell, the end of the line needs to be cleared.
// 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.
2022-01-26 23:38:46 -08:00
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
2022-01-26 23:38:46 -08:00
func (e *Engine) Debug(version string) string {
var segmentTimings []*SegmentTiming
largestSegmentNameLength := 0
2022-01-26 23:38:46 -08:00
e.write(fmt.Sprintf("\n\x1b[1mVersion:\x1b[0m %s\n", version))
e.write("\n\x1b[1mSegments:\x1b[0m\n\n")
2021-01-14 21:10:36 -08:00
// console title timing
start := time.Now()
2022-01-26 23:38:46 -08:00
consoleTitle := e.ConsoleTitle.GetTitle()
2021-01-14 21:10:36 -08:00
duration := time.Since(start)
segmentTiming := &SegmentTiming{
2022-02-02 03:16:39 -08:00
name: "ConsoleTitle",
nameLength: 12,
active: e.Config.ConsoleTitle,
text: consoleTitle,
duration: duration,
2021-01-14 21:10:36 -08:00
}
segmentTimings = append(segmentTimings, segmentTiming)
// loop each segments of each blocks
2022-01-26 23:38:46 -08:00
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 {
largestSegmentNameLength = longestSegmentName
}
}
2021-01-14 21:10:36 -08:00
// pad the output so the tabs render correctly
largestSegmentNameLength += 7
for _, segment := range segmentTimings {
2022-02-02 03:16:39 -08:00
duration := segment.duration.Milliseconds()
segmentName := fmt.Sprintf("%s(%t)", segment.name, segment.active)
e.write(fmt.Sprintf("%-*s - %3d ms - %s\n", largestSegmentNameLength, segmentName, duration, segment.text))
}
2021-11-16 23:25:52 -08:00
e.write(fmt.Sprintf("\n\x1b[1mRun duration:\x1b[0m %s\n", time.Since(start)))
2022-01-26 23:38:46 -08:00
e.write(fmt.Sprintf("\n\x1b[1mCache path:\x1b[0m %s\n", e.Env.CachePath()))
e.write("\n\x1b[1mLogs:\x1b[0m\n\n")
2022-01-26 23:38:46 -08:00
e.write(e.Env.Logs())
2021-04-20 12:30:46 -07:00
return e.string()
}
2022-01-26 23:38:46 -08:00
func (e *Engine) print() string {
switch e.Env.Shell() {
2020-12-23 04:31:21 -08:00
case zsh:
2022-01-26 23:38:46 -08:00
if !*e.Env.Args().Eval {
break
2020-12-23 04:31:21 -08:00
}
// escape double quotes contained in the prompt
prompt := fmt.Sprintf("PS1=\"%s\"", strings.ReplaceAll(e.string(), "\"", "\"\""))
prompt += fmt.Sprintf("\nRPROMPT=\"%s\"", e.rprompt)
return prompt
case pwsh, powershell5, bash, plain:
2022-01-26 23:38:46 -08:00
if e.rprompt == "" || !e.canWriteRPrompt() || e.Plain {
break
2020-12-17 23:59:45 -08:00
}
2022-01-26 23:38:46 -08:00
e.write(e.Ansi.SaveCursorPosition())
e.write(e.Ansi.CarriageForward())
e.write(e.Ansi.GetCursorForRightWrite(e.rprompt, 0))
e.write(e.rprompt)
2022-01-26 23:38:46 -08:00
e.write(e.Ansi.RestoreCursorPosition())
2020-12-15 05:58:15 -08:00
}
2021-04-20 12:30:46 -07:00
return e.string()
2019-03-13 04:14:30 -07:00
}
2021-06-05 07:14:44 -07:00
2022-01-26 23:38:46 -08:00
func (e *Engine) RenderTooltip(tip string) string {
2021-06-05 07:14:44 -07:00
tip = strings.Trim(tip, " ")
var tooltip *Segment
2022-01-26 23:38:46 -08:00
for _, tp := range e.Config.Tooltips {
2021-06-05 07:14:44 -07:00
if !tp.shouldInvokeWithTip(tip) {
continue
}
tooltip = tp
}
if tooltip == nil {
return ""
}
2022-01-26 23:38:46 -08:00
if err := tooltip.mapSegmentWithWriter(e.Env); err != nil {
2021-06-05 07:14:44 -07:00
return ""
}
2022-02-02 03:16:39 -08:00
if !tooltip.writer.Enabled() {
2021-06-05 07:14:44 -07:00
return ""
}
2022-02-02 03:16:39 -08:00
tooltip.text = tooltip.string()
2021-06-05 07:14:44 -07:00
// little hack to reuse the current logic
block := &Block{
Alignment: Right,
Segments: []*Segment{tooltip},
}
2022-01-26 23:38:46 -08:00
switch e.Env.Shell() {
2021-11-18 04:15:35 -08:00
case zsh, winCMD:
2022-01-26 23:38:46 -08:00
block.init(e.Env, e.Writer, e.Ansi)
2021-06-05 07:14:44 -07:00
return block.renderSegments()
case pwsh, powershell5:
2022-01-26 23:38:46 -08:00
block.initPlain(e.Env, e.Config)
2021-06-05 07:14:44 -07:00
tooltipText := block.renderSegments()
2022-01-26 23:38:46 -08:00
e.write(e.Ansi.ClearAfter())
e.write(e.Ansi.CarriageForward())
e.write(e.Ansi.GetCursorForRightWrite(tooltipText, 0))
2021-06-05 07:14:44 -07:00
e.write(tooltipText)
return e.string()
}
return ""
}
2021-06-15 12:23:08 -07:00
2022-01-26 23:38:46 -08:00
func (e *Engine) RenderTransientPrompt() string {
if e.Config.TransientPrompt == nil {
return ""
}
2022-01-26 23:38:46 -08:00
promptTemplate := e.Config.TransientPrompt.Template
2021-06-15 12:23:08 -07:00
if len(promptTemplate) == 0 {
promptTemplate = "{{ .Shell }}> "
2021-06-15 12:23:08 -07:00
}
2022-01-26 06:54:36 -08:00
tmpl := &template.Text{
2021-06-15 12:23:08 -07:00
Template: promptTemplate,
2022-01-26 23:38:46 -08:00
Env: e.Env,
2021-06-15 12:23:08 -07:00
}
2022-01-26 06:54:36 -08:00
prompt, err := tmpl.Render()
if err != nil {
prompt = err.Error()
}
2022-01-26 23:38:46 -08:00
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() {
2021-07-03 09:38:23 -07:00
case zsh:
// escape double quotes contained in the prompt
2022-01-26 23:38:46 -08:00
prompt := fmt.Sprintf("PS1=\"%s\"", strings.ReplaceAll(e.Writer.String(), "\"", "\"\""))
2021-07-03 09:38:23 -07:00
prompt += "\nRPROMPT=\"\""
return prompt
2021-11-18 04:29:56 -08:00
case pwsh, powershell5, winCMD:
2022-01-26 23:38:46 -08:00
return e.Writer.String()
}
2021-07-03 09:38:23 -07:00
return ""
2021-06-15 12:23:08 -07:00
}
2021-11-13 10:35:15 -08:00
2022-01-26 23:38:46 -08:00
func (e *Engine) RenderRPrompt() string {
2021-11-13 10:35:15 -08:00
filterRPromptBlock := func(blocks []*Block) *Block {
for _, block := range blocks {
if block.Type == RPrompt {
return block
}
}
return nil
}
2022-01-26 23:38:46 -08:00
block := filterRPromptBlock(e.Config.Blocks)
2021-11-13 10:35:15 -08:00
if block == nil {
return ""
}
2022-01-26 23:38:46 -08:00
block.init(e.Env, e.Writer, e.Ansi)
2022-02-02 03:16:39 -08:00
block.renderSegmentsText()
2021-11-13 10:35:15 -08:00
if !block.enabled() {
return ""
}
return block.renderSegments()
}