2019-03-13 04:14:30 -07:00
|
|
|
package main
|
|
|
|
|
|
|
|
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"
|
2022-01-26 01:23:18 -08:00
|
|
|
"oh-my-posh/environment"
|
2022-01-26 06:54:36 -08:00
|
|
|
"oh-my-posh/template"
|
2021-03-20 00:49:49 -07:00
|
|
|
"strings"
|
2020-12-27 08:53:58 -08:00
|
|
|
"time"
|
2019-03-13 04:14:30 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
type engine struct {
|
2021-04-18 10:16:06 -07:00
|
|
|
config *Config
|
2022-01-26 01:23:18 -08:00
|
|
|
env environment.Environment
|
2022-01-26 04:09:21 -08:00
|
|
|
writer color.Writer
|
|
|
|
ansi *color.Ansi
|
2022-01-26 22:44:35 -08:00
|
|
|
consoleTitle *console.Title
|
2021-11-02 06:53:46 -07:00
|
|
|
plain bool
|
2021-04-20 12:30:46 -07:00
|
|
|
|
|
|
|
console strings.Builder
|
2021-04-18 10:16:06 -07:00
|
|
|
rprompt string
|
2020-10-12 23:57:46 -07:00
|
|
|
}
|
|
|
|
|
2021-04-20 12:30:46 -07:00
|
|
|
func (e *engine) write(text string) {
|
|
|
|
e.console.WriteString(text)
|
|
|
|
}
|
|
|
|
|
2021-11-02 06:53:46 -07:00
|
|
|
func (e *engine) writeANSI(text string) {
|
|
|
|
if e.plain {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
e.console.WriteString(text)
|
|
|
|
}
|
|
|
|
|
2021-04-20 12:30:46 -07:00
|
|
|
func (e *engine) string() string {
|
|
|
|
return e.console.String()
|
|
|
|
}
|
|
|
|
|
2021-05-22 07:50:34 -07:00
|
|
|
func (e *engine) canWriteRPrompt() bool {
|
|
|
|
prompt := e.string()
|
2022-01-23 12:37:51 -08:00
|
|
|
consoleWidth, err := e.env.TerminalWidth()
|
2021-05-25 09:31:23 -07:00
|
|
|
if err != nil || consoleWidth == 0 {
|
2021-05-22 07:50:34 -07:00
|
|
|
return true
|
|
|
|
}
|
2022-01-26 04:09:21 -08:00
|
|
|
promptWidth := e.ansi.LenWithoutANSI(prompt)
|
2021-05-22 07:50:34 -07:00
|
|
|
availableSpace := consoleWidth - promptWidth
|
2021-09-16 23:15:06 -07:00
|
|
|
// spanning multiple lines
|
|
|
|
if availableSpace < 0 {
|
|
|
|
overflow := promptWidth % consoleWidth
|
|
|
|
availableSpace = consoleWidth - overflow
|
2021-05-22 07:50:34 -07:00
|
|
|
}
|
|
|
|
promptBreathingRoom := 30
|
2022-01-26 04:09:21 -08:00
|
|
|
canWrite := (availableSpace - e.ansi.LenWithoutANSI(e.rprompt)) >= promptBreathingRoom
|
2021-09-16 23:15:06 -07:00
|
|
|
return canWrite
|
2021-05-22 07:50:34 -07:00
|
|
|
}
|
|
|
|
|
2021-04-04 11:28:41 -07:00
|
|
|
func (e *engine) render() string {
|
2021-03-20 11:32:15 -07:00
|
|
|
for _, block := range e.config.Blocks {
|
2021-04-18 10:16:06 -07:00
|
|
|
e.renderBlock(block)
|
2019-03-13 04:14:30 -07:00
|
|
|
}
|
2021-03-20 11:32:15 -07:00
|
|
|
if e.config.ConsoleTitle {
|
2022-01-26 22:44:35 -08:00
|
|
|
e.writeANSI(e.consoleTitle.GetTitle())
|
2020-10-12 00:02:33 -07:00
|
|
|
}
|
2022-01-26 04:09:21 -08:00
|
|
|
e.writeANSI(e.ansi.ColorReset())
|
2021-03-20 11:32:15 -07:00
|
|
|
if e.config.FinalSpace {
|
2021-04-20 12:30:46 -07:00
|
|
|
e.write(" ")
|
2019-03-13 04:14:30 -07:00
|
|
|
}
|
2021-02-14 23:26:52 -08:00
|
|
|
|
2021-03-20 11:32:15 -07:00
|
|
|
if !e.config.OSC99 {
|
2021-04-04 11:28:41 -07:00
|
|
|
return e.print()
|
2021-02-15 13:19:19 -08:00
|
|
|
}
|
2022-01-23 12:37:51 -08:00
|
|
|
cwd := e.env.Pwd()
|
2022-01-26 04:09:21 -08:00
|
|
|
e.writeANSI(e.ansi.ConsolePwd(cwd))
|
2021-04-04 11:28:41 -07:00
|
|
|
return e.print()
|
2020-12-17 23:59:45 -08:00
|
|
|
}
|
|
|
|
|
2021-04-18 10:16:06 -07:00
|
|
|
func (e *engine) renderBlock(block *Block) {
|
2021-05-21 13:10:20 -07:00
|
|
|
// 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-23 12:37:51 -08:00
|
|
|
if block.Type == RPrompt && e.env.Shell() == bash {
|
2021-05-21 13:10:20 -07:00
|
|
|
block.initPlain(e.env, e.config)
|
|
|
|
} else {
|
2021-11-02 06:53:46 -07:00
|
|
|
block.init(e.env, e.writer, e.ansi)
|
2021-05-21 13:10:20 -07:00
|
|
|
}
|
2021-04-18 10:16:06 -07:00
|
|
|
block.setStringValues()
|
|
|
|
if !block.enabled() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if block.Newline {
|
2021-04-20 12:30:46 -07:00
|
|
|
e.write("\n")
|
2021-04-18 10:16:06 -07:00
|
|
|
}
|
|
|
|
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")
|
2021-04-18 10:16:06 -07:00
|
|
|
case Prompt:
|
|
|
|
if block.VerticalOffset != 0 {
|
2022-01-26 04:09:21 -08:00
|
|
|
e.writeANSI(e.ansi.ChangeLine(block.VerticalOffset))
|
2021-04-18 10:16:06 -07:00
|
|
|
}
|
|
|
|
switch block.Alignment {
|
|
|
|
case Right:
|
2022-01-26 04:09:21 -08:00
|
|
|
e.writeANSI(e.ansi.CarriageForward())
|
2021-04-18 10:16:06 -07:00
|
|
|
blockText := block.renderSegments()
|
2022-01-26 04:09:21 -08:00
|
|
|
e.writeANSI(e.ansi.GetCursorForRightWrite(blockText, block.HorizontalOffset))
|
2021-04-20 12:30:46 -07:00
|
|
|
e.write(blockText)
|
2021-04-18 10:16:06 -07:00
|
|
|
case Left:
|
2021-04-20 12:30:46 -07:00
|
|
|
e.write(block.renderSegments())
|
2021-04-18 10:16:06 -07:00
|
|
|
}
|
|
|
|
case RPrompt:
|
2021-05-21 13:10:20 -07:00
|
|
|
blockText := block.renderSegments()
|
2022-01-23 12:37:51 -08:00
|
|
|
if e.env.Shell() == bash {
|
2022-01-26 04:09:21 -08:00
|
|
|
blockText = e.ansi.FormatText(blockText)
|
2021-05-21 13:10:20 -07:00
|
|
|
}
|
|
|
|
e.rprompt = blockText
|
2021-04-18 10:16:06 -07:00
|
|
|
}
|
2021-04-21 12:20:18 -07:00
|
|
|
// 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 04:09:21 -08:00
|
|
|
if e.env.Shell() == pwsh || e.env.Shell() == powershell5 {
|
|
|
|
e.writeANSI(e.ansi.ClearAfter())
|
2021-04-21 12:20:18 -07:00
|
|
|
}
|
2021-04-18 10:16:06 -07:00
|
|
|
}
|
|
|
|
|
2021-01-09 07:18:37 -08:00
|
|
|
// debug will loop through your config file and output the timings for each segments
|
2021-04-04 11:28:41 -07:00
|
|
|
func (e *engine) debug() string {
|
2021-04-18 10:16:06 -07:00
|
|
|
var segmentTimings []*SegmentTiming
|
2020-12-31 05:00:46 -08:00
|
|
|
largestSegmentNameLength := 0
|
2021-11-16 10:59:42 -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 22:44:35 -08:00
|
|
|
consoleTitle := e.consoleTitle.GetTitle()
|
2021-01-14 21:10:36 -08:00
|
|
|
duration := time.Since(start)
|
2021-04-18 10:16:06 -07:00
|
|
|
segmentTiming := &SegmentTiming{
|
2021-01-14 21:10:36 -08:00
|
|
|
name: "ConsoleTitle",
|
|
|
|
nameLength: 12,
|
2021-03-20 11:32:15 -07:00
|
|
|
enabled: e.config.ConsoleTitle,
|
2021-01-14 21:10:36 -08:00
|
|
|
stringValue: consoleTitle,
|
|
|
|
enabledDuration: 0,
|
|
|
|
stringDuration: duration,
|
|
|
|
}
|
|
|
|
segmentTimings = append(segmentTimings, segmentTiming)
|
2020-12-27 08:53:58 -08:00
|
|
|
// loop each segments of each blocks
|
2021-03-20 11:32:15 -07:00
|
|
|
for _, block := range e.config.Blocks {
|
2021-11-02 06:53:46 -07:00
|
|
|
block.init(e.env, e.writer, e.ansi)
|
2021-04-18 10:16:06 -07:00
|
|
|
longestSegmentName, timings := block.debug()
|
|
|
|
segmentTimings = append(segmentTimings, timings...)
|
|
|
|
if longestSegmentName > largestSegmentNameLength {
|
|
|
|
largestSegmentNameLength = longestSegmentName
|
2020-12-27 08:53:58 -08:00
|
|
|
}
|
|
|
|
}
|
2021-01-14 21:10:36 -08:00
|
|
|
|
2020-12-31 05:00:46 -08:00
|
|
|
// pad the output so the tabs render correctly
|
|
|
|
largestSegmentNameLength += 7
|
2020-12-27 08:53:58 -08:00
|
|
|
for _, segment := range segmentTimings {
|
|
|
|
duration := segment.enabledDuration.Milliseconds()
|
|
|
|
if segment.enabled {
|
|
|
|
duration += segment.stringDuration.Milliseconds()
|
|
|
|
}
|
2020-12-31 05:00:46 -08:00
|
|
|
segmentName := fmt.Sprintf("%s(%t)", segment.name, segment.enabled)
|
2021-04-20 12:30:46 -07:00
|
|
|
e.write(fmt.Sprintf("%-*s - %3d ms - %s\n", largestSegmentNameLength, segmentName, duration, segment.stringValue))
|
2020-12-27 08:53:58 -08:00
|
|
|
}
|
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-23 12:37:51 -08:00
|
|
|
e.write(fmt.Sprintf("\n\x1b[1mCache path:\x1b[0m %s\n", e.env.CachePath()))
|
2021-11-16 10:59:42 -08:00
|
|
|
e.write("\n\x1b[1mLogs:\x1b[0m\n\n")
|
2022-01-23 12:37:51 -08:00
|
|
|
e.write(e.env.Logs())
|
2021-04-20 12:30:46 -07:00
|
|
|
return e.string()
|
2020-12-27 08:53:58 -08:00
|
|
|
}
|
|
|
|
|
2021-04-04 11:28:41 -07:00
|
|
|
func (e *engine) print() string {
|
2022-01-23 12:37:51 -08:00
|
|
|
switch e.env.Shell() {
|
2020-12-23 04:31:21 -08:00
|
|
|
case zsh:
|
2022-01-23 12:37:51 -08:00
|
|
|
if !*e.env.Args().Eval {
|
2021-05-21 13:10:20 -07:00
|
|
|
break
|
2020-12-23 04:31:21 -08:00
|
|
|
}
|
2021-05-21 13:10:20 -07: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:
|
2021-11-02 06:53:46 -07:00
|
|
|
if e.rprompt == "" || !e.canWriteRPrompt() || e.plain {
|
2021-05-21 13:10:20 -07:00
|
|
|
break
|
2020-12-17 23:59:45 -08:00
|
|
|
}
|
2022-01-26 04:09:21 -08:00
|
|
|
e.write(e.ansi.SaveCursorPosition())
|
|
|
|
e.write(e.ansi.CarriageForward())
|
|
|
|
e.write(e.ansi.GetCursorForRightWrite(e.rprompt, 0))
|
2021-05-21 13:10:20 -07:00
|
|
|
e.write(e.rprompt)
|
2022-01-26 04:09:21 -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
|
|
|
|
|
|
|
func (e *engine) renderTooltip(tip string) string {
|
|
|
|
tip = strings.Trim(tip, " ")
|
|
|
|
var tooltip *Segment
|
|
|
|
for _, tp := range e.config.Tooltips {
|
|
|
|
if !tp.shouldInvokeWithTip(tip) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
tooltip = tp
|
|
|
|
}
|
|
|
|
if tooltip == nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
if err := tooltip.mapSegmentWithWriter(e.env); err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
if !tooltip.enabled() {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
tooltip.stringValue = tooltip.string()
|
|
|
|
// little hack to reuse the current logic
|
|
|
|
block := &Block{
|
|
|
|
Alignment: Right,
|
|
|
|
Segments: []*Segment{tooltip},
|
|
|
|
}
|
2022-01-23 12:37:51 -08:00
|
|
|
switch e.env.Shell() {
|
2021-11-18 04:15:35 -08:00
|
|
|
case zsh, winCMD:
|
2021-11-02 06:53:46 -07:00
|
|
|
block.init(e.env, e.writer, e.ansi)
|
2021-06-05 07:14:44 -07:00
|
|
|
return block.renderSegments()
|
|
|
|
case pwsh, powershell5:
|
|
|
|
block.initPlain(e.env, e.config)
|
|
|
|
tooltipText := block.renderSegments()
|
2022-01-26 04:09:21 -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
|
|
|
|
2021-07-10 05:54:49 -07:00
|
|
|
func (e *engine) renderTransientPrompt() string {
|
2021-09-09 11:02:30 -07:00
|
|
|
if e.config.TransientPrompt == nil {
|
|
|
|
return ""
|
|
|
|
}
|
2021-06-15 12:23:08 -07:00
|
|
|
promptTemplate := e.config.TransientPrompt.Template
|
|
|
|
if len(promptTemplate) == 0 {
|
2021-07-10 05:54:49 -07:00
|
|
|
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,
|
|
|
|
Env: e.env,
|
|
|
|
}
|
2022-01-26 06:54:36 -08:00
|
|
|
prompt, err := tmpl.Render()
|
2022-01-12 14:39:34 -08:00
|
|
|
if err != nil {
|
|
|
|
prompt = err.Error()
|
|
|
|
}
|
2022-01-26 04:09:21 -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)
|
2022-01-23 12:37:51 -08:00
|
|
|
switch e.env.Shell() {
|
2021-07-03 09:38:23 -07:00
|
|
|
case zsh:
|
|
|
|
// escape double quotes contained in the prompt
|
2022-01-26 04:09:21 -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 04:09:21 -08:00
|
|
|
return e.writer.String()
|
2021-06-23 13:00:40 -07:00
|
|
|
}
|
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
|
|
|
|
|
|
|
func (e *engine) renderRPrompt() string {
|
|
|
|
filterRPromptBlock := func(blocks []*Block) *Block {
|
|
|
|
for _, block := range blocks {
|
|
|
|
if block.Type == RPrompt {
|
|
|
|
return block
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
block := filterRPromptBlock(e.config.Blocks)
|
|
|
|
if block == nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
block.init(e.env, e.writer, e.ansi)
|
|
|
|
block.setStringValues()
|
|
|
|
if !block.enabled() {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return block.renderSegments()
|
|
|
|
}
|