mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2025-03-05 20:49:04 -08:00
feat(pwsh): cache prompt for repainting
This commit is contained in:
parent
4a3d283ec4
commit
479e6f551e
1
src/cache/cache.go
vendored
1
src/cache/cache.go
vendored
|
@ -33,6 +33,7 @@ var (
|
|||
TEMPLATECACHE = fmt.Sprintf("template_cache_%s", pid())
|
||||
TOGGLECACHE = fmt.Sprintf("toggle_cache_%s", pid())
|
||||
PROMPTCOUNTCACHE = fmt.Sprintf("prompt_count_cache_%s", pid())
|
||||
PROMPTCACHE = fmt.Sprintf("prompt_cache_%s", pid())
|
||||
)
|
||||
|
||||
type Entry struct {
|
||||
|
|
|
@ -19,6 +19,7 @@ var (
|
|||
terminalWidth int
|
||||
eval bool
|
||||
cleared bool
|
||||
cached bool
|
||||
|
||||
command string
|
||||
shellVersion string
|
||||
|
@ -64,6 +65,7 @@ var printCmd = &cobra.Command{
|
|||
Plain: plain,
|
||||
Primary: args[0] == "primary",
|
||||
Cleared: cleared,
|
||||
Cached: cached,
|
||||
NoExitCode: noStatus,
|
||||
Column: column,
|
||||
}
|
||||
|
@ -108,6 +110,7 @@ func init() {
|
|||
printCmd.Flags().StringVar(&command, "command", "", "tooltip command")
|
||||
printCmd.Flags().BoolVarP(&plain, "plain", "p", false, "plain text output (no ANSI)")
|
||||
printCmd.Flags().BoolVar(&cleared, "cleared", false, "do we have a clear terminal or not")
|
||||
printCmd.Flags().BoolVar(&cached, "cached", false, "use a cached prompt")
|
||||
printCmd.Flags().BoolVar(&eval, "eval", false, "output the prompt for eval")
|
||||
printCmd.Flags().IntVar(&column, "column", 0, "the column position of the cursor")
|
||||
// Deprecated flags
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package prompt
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/cache"
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/color"
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/config"
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/regex"
|
||||
|
@ -12,9 +14,14 @@ import (
|
|||
"github.com/jandedobbeleer/oh-my-posh/src/terminal"
|
||||
)
|
||||
|
||||
var (
|
||||
cycle *color.Cycle = &color.Cycle{}
|
||||
)
|
||||
var cycle *color.Cycle = &color.Cycle{}
|
||||
|
||||
type promptCache struct {
|
||||
Prompt string
|
||||
CurrentLineLength int
|
||||
RPrompt string
|
||||
RPromptLength int
|
||||
}
|
||||
|
||||
type Engine struct {
|
||||
Config *config.Config
|
||||
|
@ -28,6 +35,8 @@ type Engine struct {
|
|||
|
||||
activeSegment *config.Segment
|
||||
previousActiveSegment *config.Segment
|
||||
|
||||
promptCache *promptCache
|
||||
}
|
||||
|
||||
func (e *Engine) write(text string) {
|
||||
|
@ -500,3 +509,80 @@ func (e *Engine) adjustTrailingDiamondColorOverrides() {
|
|||
adjustOverride(match[terminal.ANCHOR], color.Ansi(match[terminal.FG]))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Engine) checkPromptCache() bool {
|
||||
data, ok := e.Env.Cache().Get(cache.PROMPTCACHE)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
e.promptCache = &promptCache{}
|
||||
err := json.Unmarshal([]byte(data), e.promptCache)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
e.write(e.promptCache.Prompt)
|
||||
e.currentLineLength = e.promptCache.CurrentLineLength
|
||||
e.rprompt = e.promptCache.RPrompt
|
||||
e.rpromptLength = e.promptCache.RPromptLength
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (e *Engine) updatePromptCache(value *promptCache) {
|
||||
cacheJSON, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
e.Env.Cache().Set(cache.PROMPTCACHE, string(cacheJSON), 1440)
|
||||
}
|
||||
|
||||
// New returns a prompt engine initialized with the
|
||||
// given configuration options, and is ready to print any
|
||||
// of the prompt components.
|
||||
func New(flags *runtime.Flags) *Engine {
|
||||
env := &runtime.Terminal{
|
||||
CmdFlags: flags,
|
||||
}
|
||||
|
||||
env.Init()
|
||||
cfg := config.Load(env)
|
||||
|
||||
if cfg.PatchPwshBleed {
|
||||
patchPowerShellBleed(env.Shell(), flags)
|
||||
}
|
||||
|
||||
env.Var = cfg.Var
|
||||
flags.HasTransient = cfg.TransientPrompt != nil
|
||||
|
||||
terminal.Init(env.Shell())
|
||||
terminal.BackgroundColor = shell.ConsoleBackgroundColor(env, cfg.TerminalBackground)
|
||||
terminal.Colors = cfg.MakeColors()
|
||||
terminal.Plain = flags.Plain
|
||||
|
||||
eng := &Engine{
|
||||
Config: cfg,
|
||||
Env: env,
|
||||
Plain: flags.Plain,
|
||||
}
|
||||
|
||||
return eng
|
||||
}
|
||||
|
||||
func patchPowerShellBleed(sh string, flags *runtime.Flags) {
|
||||
// when in PowerShell, and force patching the bleed bug
|
||||
// we need to reduce the terminal width by 1 so the last
|
||||
// character isn't cut off by the ANSI escape sequences
|
||||
// See https://github.com/JanDeDobbeleer/oh-my-posh/issues/65
|
||||
if sh != shell.PWSH && sh != shell.PWSH5 {
|
||||
return
|
||||
}
|
||||
|
||||
// only do this when relevant
|
||||
if flags.TerminalWidth <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
flags.TerminalWidth--
|
||||
}
|
||||
|
|
|
@ -11,6 +11,16 @@ import (
|
|||
"github.com/jandedobbeleer/oh-my-posh/src/terminal"
|
||||
)
|
||||
|
||||
type ExtraPromptType int
|
||||
|
||||
const (
|
||||
Transient ExtraPromptType = iota
|
||||
Valid
|
||||
Error
|
||||
Secondary
|
||||
Debug
|
||||
)
|
||||
|
||||
func (e *Engine) ExtraPrompt(promptType ExtraPromptType) string {
|
||||
// populate env with latest context
|
||||
e.Env.LoadTemplateCache()
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
package prompt
|
||||
|
||||
import (
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/config"
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/shell"
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/terminal"
|
||||
)
|
||||
|
||||
// New returns a prompt engine initialized with the
|
||||
// given configuration options, and is ready to print any
|
||||
// of the prompt components.
|
||||
func New(flags *runtime.Flags) *Engine {
|
||||
env := &runtime.Terminal{
|
||||
CmdFlags: flags,
|
||||
}
|
||||
|
||||
env.Init()
|
||||
cfg := config.Load(env)
|
||||
|
||||
if cfg.PatchPwshBleed {
|
||||
patchPowerShellBleed(env.Shell(), flags)
|
||||
}
|
||||
|
||||
env.Var = cfg.Var
|
||||
flags.HasTransient = cfg.TransientPrompt != nil
|
||||
|
||||
terminal.Init(env.Shell())
|
||||
terminal.BackgroundColor = shell.ConsoleBackgroundColor(env, cfg.TerminalBackground)
|
||||
terminal.Colors = cfg.MakeColors()
|
||||
terminal.Plain = flags.Plain
|
||||
|
||||
eng := &Engine{
|
||||
Config: cfg,
|
||||
Env: env,
|
||||
Plain: flags.Plain,
|
||||
}
|
||||
|
||||
return eng
|
||||
}
|
||||
|
||||
func patchPowerShellBleed(sh string, flags *runtime.Flags) {
|
||||
// when in PowerShell, and force patching the bleed bug
|
||||
// we need to reduce the terminal width by 1 so the last
|
||||
// character isn't cut off by the ANSI escape sequences
|
||||
// See https://github.com/JanDeDobbeleer/oh-my-posh/issues/65
|
||||
if sh != shell.PWSH && sh != shell.PWSH5 {
|
||||
return
|
||||
}
|
||||
|
||||
// only do this when relevant
|
||||
if flags.TerminalWidth <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
flags.TerminalWidth--
|
||||
}
|
|
@ -9,70 +9,76 @@ import (
|
|||
"github.com/jandedobbeleer/oh-my-posh/src/terminal"
|
||||
)
|
||||
|
||||
type ExtraPromptType int
|
||||
|
||||
const (
|
||||
Transient ExtraPromptType = iota
|
||||
Valid
|
||||
Error
|
||||
Secondary
|
||||
Debug
|
||||
)
|
||||
|
||||
func (e *Engine) Primary() string {
|
||||
if e.Config.ShellIntegration {
|
||||
exitCode, _ := e.Env.StatusCodes()
|
||||
e.write(terminal.CommandFinished(exitCode, e.Env.Flags().NoExitCode))
|
||||
e.write(terminal.PromptStart())
|
||||
}
|
||||
|
||||
// cache a pointer to the color cycle
|
||||
cycle = &e.Config.Cycle
|
||||
var cancelNewline, didRender bool
|
||||
|
||||
needsPrimaryRPrompt := e.needsPrimaryRPrompt()
|
||||
|
||||
for i, block := range e.Config.Blocks {
|
||||
// do not print a leading newline when we're at the first row and the prompt is cleared
|
||||
if i == 0 {
|
||||
row, _ := e.Env.CursorPosition()
|
||||
cancelNewline = e.Env.Flags().Cleared || e.Env.Flags().PromptCount == 1 || row == 1
|
||||
}
|
||||
var (
|
||||
useCache bool
|
||||
updateCache bool
|
||||
)
|
||||
|
||||
// skip setting a newline when we didn't print anything yet
|
||||
if i != 0 {
|
||||
cancelNewline = !didRender
|
||||
}
|
||||
|
||||
if block.Type == config.RPrompt && !needsPrimaryRPrompt {
|
||||
continue
|
||||
}
|
||||
|
||||
if e.renderBlock(block, cancelNewline) {
|
||||
didRender = true
|
||||
if e.Env.Shell() == shell.PWSH || e.Env.Shell() == shell.PWSH5 {
|
||||
// For PowerShell, use a cached prompt if available, otherwise render a new prompt.
|
||||
if e.Env.Flags().Cached && e.checkPromptCache() {
|
||||
useCache = true
|
||||
} else {
|
||||
updateCache = true
|
||||
}
|
||||
}
|
||||
|
||||
if len(e.Config.ConsoleTitleTemplate) > 0 && !e.Env.Flags().Plain {
|
||||
title := e.getTitleTemplateText()
|
||||
e.write(terminal.FormatTitle(title))
|
||||
}
|
||||
if !useCache {
|
||||
if e.Config.ShellIntegration {
|
||||
exitCode, _ := e.Env.StatusCodes()
|
||||
e.write(terminal.CommandFinished(exitCode, e.Env.Flags().NoExitCode))
|
||||
e.write(terminal.PromptStart())
|
||||
}
|
||||
|
||||
if e.Config.FinalSpace {
|
||||
e.write(" ")
|
||||
e.currentLineLength++
|
||||
}
|
||||
// cache a pointer to the color cycle
|
||||
cycle = &e.Config.Cycle
|
||||
var cancelNewline, didRender bool
|
||||
|
||||
if e.Config.ITermFeatures != nil && e.isIterm() {
|
||||
host, _ := e.Env.Host()
|
||||
e.write(terminal.RenderItermFeatures(e.Config.ITermFeatures, e.Env.Shell(), e.Env.Pwd(), e.Env.User(), host))
|
||||
}
|
||||
for i, block := range e.Config.Blocks {
|
||||
// do not print a leading newline when we're at the first row and the prompt is cleared
|
||||
if i == 0 {
|
||||
row, _ := e.Env.CursorPosition()
|
||||
cancelNewline = e.Env.Flags().Cleared || e.Env.Flags().PromptCount == 1 || row == 1
|
||||
}
|
||||
|
||||
if e.Config.ShellIntegration && e.Config.TransientPrompt == nil {
|
||||
e.write(terminal.CommandStart())
|
||||
}
|
||||
// skip setting a newline when we didn't print anything yet
|
||||
if i != 0 {
|
||||
cancelNewline = !didRender
|
||||
}
|
||||
|
||||
e.pwd()
|
||||
if block.Type == config.RPrompt && !needsPrimaryRPrompt {
|
||||
continue
|
||||
}
|
||||
|
||||
if e.renderBlock(block, cancelNewline) {
|
||||
didRender = true
|
||||
}
|
||||
}
|
||||
|
||||
if len(e.Config.ConsoleTitleTemplate) > 0 && !e.Env.Flags().Plain {
|
||||
title := e.getTitleTemplateText()
|
||||
e.write(terminal.FormatTitle(title))
|
||||
}
|
||||
|
||||
if e.Config.FinalSpace {
|
||||
e.write(" ")
|
||||
e.currentLineLength++
|
||||
}
|
||||
|
||||
if e.Config.ITermFeatures != nil && e.isIterm() {
|
||||
host, _ := e.Env.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(terminal.CommandStart())
|
||||
}
|
||||
|
||||
e.pwd()
|
||||
}
|
||||
|
||||
switch e.Env.Shell() {
|
||||
case shell.ZSH:
|
||||
|
@ -94,6 +100,16 @@ func (e *Engine) Primary() string {
|
|||
|
||||
return prompt
|
||||
default:
|
||||
if updateCache {
|
||||
// Cache the new prompt.
|
||||
e.updatePromptCache(&promptCache{
|
||||
Prompt: e.prompt.String(),
|
||||
CurrentLineLength: e.currentLineLength,
|
||||
RPrompt: e.rprompt,
|
||||
RPromptLength: e.rpromptLength,
|
||||
})
|
||||
}
|
||||
|
||||
if !needsPrimaryRPrompt {
|
||||
break
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package prompt
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/config"
|
||||
|
@ -10,18 +9,6 @@ import (
|
|||
)
|
||||
|
||||
func (e *Engine) Tooltip(tip string) string {
|
||||
supportedShells := []string{
|
||||
shell.ZSH,
|
||||
shell.CMD,
|
||||
shell.FISH,
|
||||
shell.PWSH,
|
||||
shell.PWSH5,
|
||||
shell.GENERIC,
|
||||
}
|
||||
if !slices.Contains(supportedShells, e.Env.Shell()) {
|
||||
return ""
|
||||
}
|
||||
|
||||
tip = strings.Trim(tip, " ")
|
||||
tooltips := make([]*config.Segment, 0, 1)
|
||||
|
||||
|
@ -56,6 +43,15 @@ func (e *Engine) Tooltip(tip string) string {
|
|||
|
||||
switch e.Env.Shell() {
|
||||
case shell.PWSH, shell.PWSH5:
|
||||
defer func() {
|
||||
// If a prompt cache is available, we update the right prompt to the new tooltip for reuse.
|
||||
if e.checkPromptCache() {
|
||||
e.promptCache.RPrompt = text
|
||||
e.promptCache.RPromptLength = length
|
||||
e.updatePromptCache(e.promptCache)
|
||||
}
|
||||
}()
|
||||
|
||||
e.rprompt = text
|
||||
e.currentLineLength = e.Env.Flags().Column
|
||||
space, ok := e.canWriteRightBlock(length, true)
|
||||
|
|
|
@ -60,6 +60,7 @@ type Flags struct {
|
|||
HasTransient bool
|
||||
PromptCount int
|
||||
Cleared bool
|
||||
Cached bool
|
||||
NoExitCode bool
|
||||
Column int
|
||||
}
|
||||
|
@ -233,7 +234,9 @@ func (term *Terminal) Init() {
|
|||
|
||||
term.tmplCache = &cache.Template{}
|
||||
|
||||
term.SetPromptCount()
|
||||
if !term.CmdFlags.Cached {
|
||||
term.SetPromptCount()
|
||||
}
|
||||
}
|
||||
|
||||
func (term *Terminal) resolveConfigPath() {
|
||||
|
@ -1003,7 +1006,7 @@ func returnOrBuildCachePath(path string) string {
|
|||
if _, err := os.Stat(cachePath); err == nil {
|
||||
return cachePath
|
||||
}
|
||||
if err := os.Mkdir(cachePath, 0755); err != nil {
|
||||
if err := os.Mkdir(cachePath, 0o755); err != nil {
|
||||
return ""
|
||||
}
|
||||
return cachePath
|
||||
|
|
|
@ -17,6 +17,9 @@ New-Module -Name "oh-my-posh-core" -ScriptBlock {
|
|||
# Check `ConstrainedLanguage` mode.
|
||||
$script:ConstrainedLanguageMode = $ExecutionContext.SessionState.LanguageMode -eq "ConstrainedLanguage"
|
||||
|
||||
# This indicates whether a new prompt should be rendered instead of using the cached one.
|
||||
$script:NewPrompt = $true
|
||||
|
||||
# Prompt related backup.
|
||||
$script:OriginalPromptFunction = $Function:prompt
|
||||
$script:OriginalContinuationPrompt = (Get-PSReadLineOption).ContinuationPrompt
|
||||
|
@ -159,7 +162,22 @@ New-Module -Name "oh-my-posh-core" -ScriptBlock {
|
|||
$terminalWidth = Get-TerminalWidth
|
||||
$cleanPSWD = Get-CleanPSWD
|
||||
$stackCount = global:Get-PoshStackCount
|
||||
$standardOut = (Start-Utf8Process $script:OMPExecutable @("print", "tooltip", "--status=$script:ErrorCode", "--shell=$script:ShellName", "--pswd=$cleanPSWD", "--execution-time=$script:ExecutionTime", "--stack-count=$stackCount", "--config=$env:POSH_THEME", "--command=$command", "--shell-version=$script:PSVersion", "--column=$column", "--terminal-width=$terminalWidth", "--no-status=$script:NoExitCode")) -join ''
|
||||
$arguments = @(
|
||||
"print"
|
||||
"tooltip"
|
||||
"--status=$script:ErrorCode"
|
||||
"--shell=$script:ShellName"
|
||||
"--pswd=$cleanPSWD"
|
||||
"--execution-time=$script:ExecutionTime"
|
||||
"--stack-count=$stackCount"
|
||||
"--config=$env:POSH_THEME"
|
||||
"--command=$command"
|
||||
"--shell-version=$script:PSVersion"
|
||||
"--column=$column"
|
||||
"--terminal-width=$terminalWidth"
|
||||
"--no-status=$script:NoExitCode"
|
||||
)
|
||||
$standardOut = (Start-Utf8Process $script:OMPExecutable $arguments) -join ''
|
||||
if (!$standardOut) {
|
||||
return
|
||||
}
|
||||
|
@ -191,6 +209,7 @@ New-Module -Name "oh-my-posh-core" -ScriptBlock {
|
|||
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$null, [ref]$null, [ref]$parseErrors, [ref]$null)
|
||||
$executingCommand = $parseErrors.Count -eq 0
|
||||
if ($executingCommand) {
|
||||
$script:NewPrompt = $true
|
||||
$script:TooltipCommand = ''
|
||||
if ('::TRANSIENT::' -eq 'true') {
|
||||
Set-TransientPrompt
|
||||
|
@ -211,6 +230,7 @@ New-Module -Name "oh-my-posh-core" -ScriptBlock {
|
|||
[Microsoft.PowerShell.PSConsoleReadLine]::GetSelectionState([ref]$start, [ref]$null)
|
||||
# only render a transient prompt when no text is selected
|
||||
if ($start -eq -1) {
|
||||
$script:NewPrompt = $true
|
||||
$script:TooltipCommand = ''
|
||||
if ('::TRANSIENT::' -eq 'true') {
|
||||
Set-TransientPrompt
|
||||
|
@ -397,7 +417,7 @@ Example:
|
|||
}
|
||||
}
|
||||
|
||||
function prompt {
|
||||
$promptFunction = {
|
||||
# store the orignal last command execution status
|
||||
if ($global:NVS_ORIGINAL_LASTEXECUTIONSTATUS -is [bool]) {
|
||||
# make it compatible with NVS auto-switching, if enabled
|
||||
|
@ -410,7 +430,14 @@ Example:
|
|||
$script:OriginalLastExitCode = $global:LASTEXITCODE
|
||||
|
||||
Set-PoshPromptType
|
||||
|
||||
# Whether we should use a cached prompt.
|
||||
$useCache = !$script:NewPrompt
|
||||
|
||||
if ($script:PromptType -ne 'transient') {
|
||||
if ($script:NewPrompt) {
|
||||
$script:NewPrompt = $false
|
||||
}
|
||||
Update-PoshErrorCode
|
||||
}
|
||||
$cleanPSWD = Get-CleanPSWD
|
||||
|
@ -422,7 +449,21 @@ Example:
|
|||
$env:POSH_CURSOR_LINE = $Host.UI.RawUI.CursorPosition.Y + 1
|
||||
$env:POSH_CURSOR_COLUMN = $Host.UI.RawUI.CursorPosition.X + 1
|
||||
|
||||
$standardOut = Start-Utf8Process $script:OMPExecutable @("print", $script:PromptType, "--status=$script:ErrorCode", "--pswd=$cleanPSWD", "--execution-time=$script:ExecutionTime", "--stack-count=$stackCount", "--config=$env:POSH_THEME", "--shell-version=$script:PSVersion", "--terminal-width=$terminalWidth", "--shell=$script:ShellName", "--no-status=$script:NoExitCode")
|
||||
$arguments = @(
|
||||
"print"
|
||||
$script:PromptType
|
||||
"--status=$script:ErrorCode"
|
||||
"--pswd=$cleanPSWD"
|
||||
"--execution-time=$script:ExecutionTime"
|
||||
"--stack-count=$stackCount"
|
||||
"--config=$env:POSH_THEME"
|
||||
"--shell-version=$script:PSVersion"
|
||||
"--terminal-width=$terminalWidth"
|
||||
"--shell=$script:ShellName"
|
||||
"--no-status=$script:NoExitCode"
|
||||
"--cached=$useCache"
|
||||
)
|
||||
$standardOut = Start-Utf8Process $script:OMPExecutable $arguments
|
||||
# make sure PSReadLine knows if we have a multiline prompt
|
||||
Set-PSReadLineOption -ExtraPromptLineCount (($standardOut | Measure-Object -Line).Lines - 1)
|
||||
|
||||
|
@ -436,6 +477,8 @@ Example:
|
|||
$global:LASTEXITCODE = $script:OriginalLastExitCode
|
||||
}
|
||||
|
||||
$Function:prompt = $promptFunction
|
||||
|
||||
# set secondary prompt
|
||||
Set-PSReadLineOption -ContinuationPrompt ((Start-Utf8Process $script:OMPExecutable @("print", "secondary", "--config=$env:POSH_THEME", "--shell=$script:ShellName")) -join "`n")
|
||||
|
||||
|
|
Loading…
Reference in a new issue