From e3523db8d3a035a8b643d25a29ccda21910b15dd Mon Sep 17 00:00:00 2001 From: Joel Bennett Date: Wed, 16 Aug 2023 02:06:56 -0400 Subject: [PATCH] Add RepeatPrompt instead of NoCache or Transient --- Source/Classes/PowerLineTheme.ps1 | 17 ++++--- Source/Configuration.psd1 | 2 +- Source/Examples/BackgroundData.ps1 | 61 +++++++++++------------- Source/Examples/ConvertFrom-Metadata.ps1 | 6 +-- Source/Examples/Darkly.ps1 | 2 +- Source/Examples/UsingPoshGit.ps1 | 20 ++++---- Source/PowerLine.format.ps1xml | 23 ++++----- Source/Public/Get-PowerLineTheme.ps1 | 3 +- Source/Public/Set-PowerLinePrompt.ps1 | 54 +++++++++++---------- Source/Public/Write-PowerlinePrompt.ps1 | 35 ++++++++++---- 10 files changed, 117 insertions(+), 106 deletions(-) diff --git a/Source/Classes/PowerLineTheme.ps1 b/Source/Classes/PowerLineTheme.ps1 index 342fe43..5e4264e 100644 --- a/Source/Classes/PowerLineTheme.ps1 +++ b/Source/Classes/PowerLineTheme.ps1 @@ -1,16 +1,16 @@ using namespace PoshCode class PowerLineTheme { + [TerminalBlock[]]$Prompt [BlockCaps]$DefaultCaps [string]$DefaultSeparator - [TerminalBlock[]]$Prompt + [scriptblock]$Title + [bool]$SetCurrentDirectory + [bool]$HideErrors + [string]$RepeatPrompt + [RgbColor[]]$PSReadLineErrorColor [string]$PSReadLineContinuationPrompt [string]$PSReadLineContinuationPromptColor [string[]]$PSReadLinePromptText - [bool]$SetCurrentDirectory - [bool]$HideErrors - [bool]$SimpleTransient - [bool]$NoCache - [scriptblock]$Title [int]$DefaultAddIndex = -1 } @@ -23,10 +23,9 @@ Add-MetadataConverter @{ ) PSReadLineContinuationPrompt = '$($_.PSReadLineContinuationPrompt)' PSReadLineContinuationPromptColor = '$($_.PSReadLineContinuationPromptColor)' - PSReadLinePromptText = '$($_.PSReadLinePromptText -join "','")' + PSReadLineErrorColor = '$($_.PSReadLineErrorColor -join "','")' HideErrors = $(if ($_.HideErrors) { '$true' } else { '$false' }) - SimpleTransient = $(if ($_.SimpleTransient) { '$true' } else { '$false' }) - NoCache = $(if ($_.NoCache) { '$true' } else { '$false' }) + RepeatPrompt = '$(if ($_.RepeatPrompt) { $_.RepeatPrompt } else { 'CachedPrompt' })' SetCurrentDirectory = $(if ($_.SetCurrentDirectory) { '$true' } else { '$false' })$( if (![string]::IsNullOrWhiteSpace($_.Title)) { "`n Title = ScriptBlock @'`n$($_.Title)`n'@" diff --git a/Source/Configuration.psd1 b/Source/Configuration.psd1 index d2a3b3b..851f1a2 100644 --- a/Source/Configuration.psd1 +++ b/Source/Configuration.psd1 @@ -10,6 +10,6 @@ PSObject @{ ) PSReadLineContinuationPrompt = '█ ' PSReadLineContinuationPromptColor = '' - PSReadLinePromptText = '','' + PSReadLineErrorColor = 'Tomato' SetCurrentDirectory = $false } diff --git a/Source/Examples/BackgroundData.ps1 b/Source/Examples/BackgroundData.ps1 index 6c6621d..a17829b 100644 --- a/Source/Examples/BackgroundData.ps1 +++ b/Source/Examples/BackgroundData.ps1 @@ -1,49 +1,46 @@ #requires -module @{ModuleName='PowerLine';ModuleVersion='3.4.0'} -# If this is re-run, clear up the old job: -Get-Job -Name WeatherQuery -EA 0 | Stop-Job -PassThru | Remove-Job -$global:WeatherJob = Start-ThreadJob -Name WeatherQuery { + +# If this is re-run, clear up the old job, and start a new one +Get-Job -Name WeatherQuery -ErrorAction Ignore | Stop-Job -PassThru | Remove-Job +$null = Start-ThreadJob -Name WeatherQuery { while ($true) { - Invoke-RestMethod "wttr.in?format=%c%t" + (Invoke-RestMethod "wttr.in?format=%c%t") -replace " +", " " Start-Sleep 300 # This job will update the weather every 5 minutes } } -Set-PowerLinePrompt -SetCurrentDirectory -PowerLineFont -SimpleTransient -Title { +$ContinuationPromptColor = [RgbColor]"DeepSkyBlue" + +Set-PSReadLineOption -ContinuationPrompt █ -Colors @{ ContinuationPrompt = $ContinuationPromptColor.ToVt() } + +Set-PowerLinePrompt -SetCurrentDirectory -PowerLineFont -RepeatPrompt LastLine -PSReadlineErrorColor Tomato -Title { -join @( if (Test-Elevation) { "Admin: " } "PS" + $PSVersionTable.PSVersion.Major + " " Convert-Path $pwd ) } -Prompt @( - Show-ElapsedTime -Autoformat -BackgroundColor 00688B -ForegroundColor White + Show-ElapsedTime -Autoformat -Bg White -Fg Black -Prefix "" -Caps '','' New-TerminalBlock -Newline - New-TerminalBlock { - # Consume the output of the job: - # In this case, I only want the most recent output, so [-1] - $global:WeatherJob.Output[-1] - } -BackgroundColor 00BFFF -ForegroundColor Black - Show-NestedPromptLevel -RepeatCharacter "&Gear;" -Postfix " " -BackgroundColor 473C8B -ForegroundColor White - Show-Path -HomeString "&House;" -Separator '' -Bg B23AEE -Fg White - Show-PoshGitStatus -Bg Gray30 + + Show-Date -Format "h\:mm" -Bg DeepSkyBlue -Fg Black + Show-JobOutput -Name WeatherQuery -Bg DeepSkyBlue3 -Fg Black + Show-NestedPromptLevel -RepeatCharacter "&Gear;" -Postfix " " -Bg DeepSkyBlue4 -Fg White New-TerminalBlock -Spacer - Show-Date -Bg 7D26CD + + Show-PoshGitStatus -Bg Gray30 + Show-Path -HomeString "&House;" -Separator '' -Bg SkyBlue4 -Fg White New-TerminalBlock -Newline + # This is basically Show-HistoryId, but I want to use it as the last part of my prompt, and have PSReadLine updated. - New-TerminalBlock { - # In order for PSReadLine to work properly, it needs the $PromptText set to match the end of my prompt... - $MyInvocation.HistoryId - - # Because I don't have a "Write-TerminalBlock" I am doing all this by hand:This is - # Need to draw ">ID>" but the > each have to be FOREGROUND = the BACKGROUND of the previous block - # AND the color changes depending on whether nestedPrompt rendered or not - [string]$CS = [PoshCode.Pansies.Entities]::ExtendedCharacters["ColorSeparator"] - $thisBg = $NestedPromptLevel ? $bg:SteelBlue2 : $bg:SkyBlue2 - $previousFg = $NestedPromptLevel ? $fg:SkyBlue2 : $fg:DeepSkyBlue2 - $thisFg = $NestedPromptLevel ? $fg:SteelBlue2 : $fg:SkyBlue2 - - Set-PSReadlineOption -PromptText @( - ($thisBg + $previousFg + $CS + $fg:white + $MyInvocation.HistoryId + $thisFg + $bg:clear + $CS) - ($bg:Gray44 + $previousFg + $CS + $fg:white + $MyInvocation.HistoryId + $fg:Gray44 + $bg:clear + $CS) - ) - } -BackgroundColor SteelBlue2 -ForegroundColor Black + # New-TerminalBlock { + # # In order for PSReadLine to work properly, it needs the $PromptText set to match the end of my prompt... + # $ContinuationPromptColor = (Get-PSReadLineOption).ContinuationPromptColor + # $fg:Black + ($ContinuationPromptColor -replace "\[3","[4") + "&ColorSeparator;" + $ContinuationPromptColor + $bg:LightSkyBlue + "&ColorSeparator;" + $Fg:Black + $MyInvocation.HistoryId + + # } -BackgroundColor LightSkyBlue -ForegroundColor Black + # New-TerminalBlock -Spacer + New-TerminalBlock -Content "&ColorSeparator;" -Background $ContinuationPromptColor -Foreground Black + Show-HistoryId -Bg LightSkyBlue -Fg Black ) + diff --git a/Source/Examples/ConvertFrom-Metadata.ps1 b/Source/Examples/ConvertFrom-Metadata.ps1 index b0c95b6..536db2b 100644 --- a/Source/Examples/ConvertFrom-Metadata.ps1 +++ b/Source/Examples/ConvertFrom-Metadata.ps1 @@ -1,5 +1,5 @@ ConvertFrom-Metadata @' -(PowerLineTheme @{ +(PSObject @{ DefaultCaps = '', '' DefaultSeparator = '' Prompt = @( @@ -18,10 +18,8 @@ ConvertFrom-Metadata @' ) PSReadLineContinuationPrompt = '▌ ' PSReadLineContinuationPromptColor = '' - PSReadLinePromptText = '','' HideErrors = $false - SimpleTransient = $false - NoCache = $false + RepeatPrompt = 'LastLine' SetCurrentDirectory = $false }) '@ | Set-PowerLinePrompt diff --git a/Source/Examples/Darkly.ps1 b/Source/Examples/Darkly.ps1 index ff61352..1b226c5 100644 --- a/Source/Examples/Darkly.ps1 +++ b/Source/Examples/Darkly.ps1 @@ -24,4 +24,4 @@ Set-PowerLinePrompt -SetCurrentDirectory -DefaultSeparator "$([char]0xE0B1)" -De Show-Date -Format "h\:mm" -Prefix "🕒" -Background 'Gray23' Show-ElapsedTime -Autoformat -Prefix "⏱️" -Background 'Gray47' New-TerminalBlock -DFg 'White' -DBg '#63B8FF' -EFg 'White' -Cap '‍' -Content '' -) -Verbose -PSReadLineContinuationPrompt '▌ ' -PSReadLineContinuationPromptColor '' -PSReadLinePromptText '','' +) -Verbose -PSReadLineContinuationPrompt '▌ ' -PSReadLineContinuationPromptColor '' diff --git a/Source/Examples/UsingPoshGit.ps1 b/Source/Examples/UsingPoshGit.ps1 index fa3fc3a..2624eec 100644 --- a/Source/Examples/UsingPoshGit.ps1 +++ b/Source/Examples/UsingPoshGit.ps1 @@ -13,15 +13,11 @@ Set-PowerLinePrompt -SetCurrentDirectory -PowerLineFont -Title { Convert-Path $pwd ) } -Prompt @( - { New-TerminalBlock -Fg Gray95 -Bg Gray20 -EBg VioletRed4 $MyInvocation.HistoryId } - { Show-ElapsedTime -Trim } # only shows the minimum portion of elapsed time necessary - { Get-Date -f "HH:mm" } # 24-hour format - { Write-VcsStatus } - { Get-SegmentedPath } - { "`n" } - { New-TerminalBlock -Fg Gray95 -Bg Gray40 "I ${Fg:Green}♥${Fg:Gray95} PS" } -) -PSReadLinePromptText @( - # Let PSReadLine use a red heart to let us know about syntax errors - New-TerminalBlock -Fg Gray95 -Bg Gray40 "I ${Fg:Green}♥${Fg:Gray95} PS${fg:Gray40}${bg:Clear}&ColorSeparator;" - New-TerminalBlock -Fg Gray95 -Bg Gray40 "I ${Fg:Red}♥${Fg:Gray95} PS${fg:Gray40}${bg:Clear}&ColorSeparator;" -) -Colors Gray54, Gray26 + New-TerminalBlock -Fg Gray95 -Bg Gray20 -EBg VioletRed4 $MyInvocation.HistoryId + Show-ElapsedTime -Trim # only shows the minimum portion of elapsed time necessary + Show-Date -f "HH:mm" # 24-hour format + Show-PoshGitStatus + Show-Path + New-TerminalBlock -Newline + New-TerminalBlock -Fg Gray95 -Bg Gray40 "I ${Fg:Green}♥${Fg:Gray95} PS" +) diff --git a/Source/PowerLine.format.ps1xml b/Source/PowerLine.format.ps1xml index 8c683d5..11b1090 100644 --- a/Source/PowerLine.format.ps1xml +++ b/Source/PowerLine.format.ps1xml @@ -13,19 +13,16 @@ $CurrentTheme = Get-PowerLineTheme # temporarily change the theme so we can use our functions to _render_ this one: - $_ | Set-PowerLineTheme - "$( Write-PowerLinePrompt )`n`n$( - $PSRL = $_.PSReadLinePromptText - @(if ($PSRL.Count -ge 2) { - "PSReadLine PromptText:" - $PSRL[0] + "$([char]27)[0m'Normal Prompt'" - $PSRL[1] + "$([char]27)[0m'Incomplete" - }) -join "`n" - if ($_.PSReadLineContinuationPrompt) { - "`n" + $_.PSReadLineContinuationPromptColor + $_.PSReadLineContinuationPrompt + "$([char]27)[0mContinuation..." - } - )" - $CurrentTheme | Set-PowerLineTheme + try { + $_ | Set-PowerLineTheme + "$( Write-PowerLinePrompt )$( + if ($_.PSReadLineContinuationPrompt) { + "`n" + $_.PSReadLineContinuationPromptColor + $_.PSReadLineContinuationPrompt + "$([char]27)[0mContinuation..." + } + )" + } finally { + $CurrentTheme | Set-PowerLineTheme + } diff --git a/Source/Public/Get-PowerLineTheme.ps1 b/Source/Public/Get-PowerLineTheme.ps1 index c60fe0d..e7af85b 100644 --- a/Source/Public/Get-PowerLineTheme.ps1 +++ b/Source/Public/Get-PowerLineTheme.ps1 @@ -13,8 +13,7 @@ function Get-PowerLineTheme { if (Get-Command Get-PSReadLineOption) { $PSReadLineOptions = Get-PSReadLineOption - # PromptText and ContinuationPrompt can have colors in them - $Configuration.PSReadLinePromptText = $PSReadLineOptions.PromptText + # ContinuationPrompt can have colors in them $Configuration.PSReadLineContinuationPrompt = $PSReadLineOptions.ContinuationPrompt # If the ContinuationPrompt has color in it, this is irrelevant, but keep it anyway $Configuration.PSReadLineContinuationPromptColor = $PSReadLineOptions.ContinuationPromptColor diff --git a/Source/Public/Set-PowerLinePrompt.ps1 b/Source/Public/Set-PowerLinePrompt.ps1 index 969f5ed..cf18550 100644 --- a/Source/Public/Set-PowerLinePrompt.ps1 +++ b/Source/Public/Set-PowerLinePrompt.ps1 @@ -37,13 +37,14 @@ function Set-PowerLinePrompt { [Parameter(ParameterSetName = "PowerLine", Mandatory)] [switch]$PowerLineFont, - # The cap character(s) that will be used (by default) on normal, left-aligned blocks + # PowerLine uses TerminalBlocks, and the DefaultCaps parameter sets [PoshCode.TerminalBlocks]::DefaultCaps + # These are the cap character(s) that will be used (by default) on blocks # Pass two characters: the first for the left side, the second for the right side. [Parameter(ParameterSetName = "Manual", ValueFromPipelineByPropertyName)] [PoshCode.BlockCaps]$DefaultCaps, - # The Left pointing and Right pointing separator characters are used when a script-based PowerLine block outputs multiple objects - # Pass two strings. The first for blocks that are Left-aligned, the second for right-aligned blocks, like: "","" + # PowerLine uses TerminalBlocks, and the DefaultSeparator parameter sets [PoshCode.TerminalBlocks]::DefaultSeparator + # The separator character is used by some TerminalBlocks to separate multiple objects (like a path separator) [Parameter(ParameterSetName = "Manual", ValueFromPipelineByPropertyName)] [Alias("Separator")] [string]$DefaultSeparator, @@ -71,23 +72,26 @@ function Set-PowerLinePrompt { [Parameter(ValueFromPipelineByPropertyName)] [switch]$HideErrors, - # If set, when the prompt is run multiple times without a command (e.g. pressing enter repeatedly, or hitting Ctrl+C) - # The prompt will render only the PSReadlinePromptText, and not the full prompt + # How to render repeated prompts. A prompt is considered a repeat if it's run multiple times without a command, + # such as when pressing enter repeatedly, or hitting Ctrl+C or Ctrl+L (any time the $MyInvcation.HistoryId does not change) + # + # By default, PowerLine uses "CachedPrompt" which repeats the whole prompt, but doesn't re-run the prompt blocks. + # + # You can choose to render only the last block, or the last line of the prompt, or to Recalculate the whole prompt. [Parameter(ValueFromPipelineByPropertyName)] - [switch]$SimpleTransient, - - # If set, disables caching of output from the prompt blocks. - # Caching only affects blocks when you render the same prompt multiple times - # (i.e. pressing enter multiple times, or pressing Ctrl+C or Ctrl+L), but - # you may want to disable caching if you do that regularly and you have things - # in your prompt that change externally while you're not using your terminal, like the time, or git status, etc. - [switch]$NoCache, - - # When there's a parse error, PSReadLine changes a part of the prompt... - # Use this option to override PSReadLine by either specifying the characters it should replace, or by specifying both the normal and error strings. - # If you specify two strings, they should both be the same length (ignoring escape sequences) + [ValidateSet("LastBlock", "LastLine", "CachedPrompt", "Recalculate")] + [string]$RepeatPrompt, + + # When there's a parse error, PSReadLine changes a part of the prompt based on it's PromptText configuration. + # This setting causes PowerLine to update the PSReadLiine PromptText on each run. + # + # By default, if the last prompt block has a background color, it will be set to tomato red (otherwise, the foreground color) + # If you pass just one color, that color will be used instead of tomato red. + # If you pass a pair of colors, the first will be replaced with the second throughout the entire last line of the prompt + # + # To disable this feature, pass an empty array, and PowerLine will not change the PromptText [Parameter(ValueFromPipelineByPropertyName)] - [string[]]$PSReadLinePromptText, + [RgbColor[]]$PSReadLineErrorColor, # When you type a command that requires a second line (like if you type | and hit enter) # This is the prompt text. Can be an empty string. Can be anything, really. @@ -110,7 +114,7 @@ function Set-PowerLinePrompt { } # Switches have a (non-null) default value, so we need to set them in case they were not passed explicitly - $Configuration = Import-Configuration -ErrorAction SilentlyContinue| Update-Object @{ + $Configuration = Import-Configuration -ErrorAction SilentlyContinue | Update-Object @{ HideErrors = $HideErrors SetCurrentDirectory = $SetCurrentDirectory } @@ -181,7 +185,6 @@ function Set-PowerLinePrompt { Write-Verbose "Setting global:Prompt" # We want to support modifying the global:prompt variable outside this function - [System.Collections.Generic.List[PoshCode.TerminalBlock]]$global:Prompt = ` [PoshCode.TerminalBlock[]]$PowerLineConfig.Prompt = @( if ($PSBoundParameters.ContainsKey("Prompt")) { Write-Verbose "Setting global:Prompt from prompt parameter" @@ -209,6 +212,13 @@ function Set-PowerLinePrompt { $global:Prompt } ) + if ($NoBackground) { + $PowerLineConfig.Prompt | ForEach-Object { + $_.BackgroundColor = $null + } + } + + [System.Collections.Generic.List[PoshCode.TerminalBlock]]$global:Prompt = $PowerLineConfig.Prompt if ($null -eq $PowerLineConfig.DefaultAddIndex) { $PowerLineConfig.DefaultAddIndex = -1 @@ -218,10 +228,6 @@ function Set-PowerLinePrompt { if (Get-Module PSReadLine) { $Options = @{} - if ($PowerLineConfig.PSReadLinePromptText) { - $Options["PromptText"] = $PowerLineConfig.PSReadLinePromptText - } - if ($PowerLineConfig.PSReadLineContinuationPrompt) { $Options["ContinuationPrompt"] = $PowerLineConfig.PSReadLineContinuationPrompt } diff --git a/Source/Public/Write-PowerlinePrompt.ps1 b/Source/Public/Write-PowerlinePrompt.ps1 index 593d1a1..a1409b6 100644 --- a/Source/Public/Write-PowerlinePrompt.ps1 +++ b/Source/Public/Write-PowerlinePrompt.ps1 @@ -1,9 +1,7 @@ function Write-PowerlinePrompt { [CmdletBinding()] [OutputType([string])] - param( - [switch]$NoCache - ) + param() try { # Stuff these into static properties in case I want to use them from C# @@ -33,14 +31,19 @@ function Write-PowerlinePrompt { } } - if ($Script:PowerLineConfig.SimpleTransient -and $MyInvocation.HistoryId -eq $Script:LastHistoryId) { - # if we're transient, and SimpleTransient, just output the last line - $LastLine = 1 + $Prompt.FindLastIndex([Predicate[PoshCode.TerminalBlock]] { $args[0].Content -eq "NewLine" }) - $local:Prompt = $Prompt.GetRange($LastLine, $Prompt.Count - $LastLine) + if ($MyInvocation.HistoryId -eq $Script:LastHistoryId) { + if ($Script:PowerLineConfig.RepeatPrompt -eq "LastLine") { + # Repeat only the last line + $LastLine = 1 + $Prompt.FindLastIndex([Predicate[PoshCode.TerminalBlock]] { $args[0].Content -in "NewLine", "`n" }) + $local:Prompt = $Prompt.GetRange($LastLine, $Prompt.Count - $LastLine) + } elseif ($Script:PowerLineConfig.RepeatPrompt -eq "LastBlock") { + # Repeat only the last block + $local:Prompt = $Prompt.GetRange($Prompt.Count - 1, 1) + } } $CacheKey = $Script:LastHistoryId = $MyInvocation.HistoryId - if ($NoCache -or $Script:PowerLineConfig.NoCache) { + if ($Script:PowerLineConfig.RepeatPrompt -eq "Recalculate") { $CacheKey = $null } @@ -108,6 +111,22 @@ function Write-PowerlinePrompt { $null = $builder.Append($Block.ToString($PreviousNeighbor.BackgroundColor, $NextNeighbor.BackgroundColor, $CacheKey)) } + if ($Colors = $PowerLineConfig.PSReadLineErrorColor) { + $DefaultColor, $Replacement = if ($Colors.Count -eq 1) { + $Prompt.BackgroundColor.Where({$_},"Last",1) + $Colors[0] + } else { + $Colors[0] + $Colors[-1] + } + + $LastLine = $builder.ToString().Split("`n")[-1] + Set-PSReadLineOption -PromptText @( + $LastLine + $LastLine -replace ([regex]::escape($DefaultColor.ToVt())), $Replacement.ToVt() -replace ([regex]::escape($DefaultColor.ToVt($true))), $Replacement.ToVt($true) + ) + } + # At the end, output everything that's left $builder.ToString() # Add-Content -Value "return prompt:`n$($Builder.ToString())" -Path $HOME\PowerLine.log