Psreadline: Syntax error prompt does not work when prompt function is defined in profile or launch command and uses Write-Host

Created on 21 May 2020  ·  8Comments  ·  Source: PowerShell/PSReadLine

Environment

# Tested in 3 environments

PS version: 5.1.18362.628
PSReadline version: 2.0.1
os: 10.0.18362.1 (WinBuild.160101.0800)
PS file version: 10.0.18362.1 (WinBuild.160101.0800)
HostName: ConsoleHost (Windows Terminal)
BufferWidth: 229
BufferHeight: 55

PS version: 7.0.1
PSReadline version: 2.0.0
os: 10.0.18362.1 (WinBuild.160101.0800)
PS file version: 7.0.1.0
HostName: ConsoleHost (Windows Terminal)
BufferWidth: 229
BufferHeight: 55

PS version: 7.0.0
PSReadline version: 2.0.0
os: Linux 4.4.0-18362-Microsoft #476-Microsoft Fri Nov 01 16:53:00 PST 2019 x86_64 x86_64 x86_64 GNU/Linux
PS file version: 7.0.0.0
HostName: ConsoleHost
BufferWidth: 229
BufferHeight: 55

Exception report

None

Steps to reproduce

Write a prompt function that uses Write-Host to construct the prompt. Place the prompt function in your PowerShell profile or in the -Command option script block when launching PowerShell. Doing so will cause PSReadLine's syntax error prompt notification color to not trigger.

# C:\users\<username>\Documents\WindowsPowerShell\profile.ps1

# Prompt function that uses Write-Host to construct prompt
function prompt {
    Write-Host 'myprompt' -NoNewLine
    '> ' # Need to also return string so PowerShell doesn't auto add "PS>"
}

Expected behavior

PS C:\> powershell.exe -NoLogo
PS C:\> function prompt { Write-Host 'myprompt' -NoNewLine; '> ' }
myprompt> } # syntax error prompt works

image

Actual behavior

```powershell
PS C:> powershell.exe -NoExit -Command { function prompt { Write-Host 'myprompt' -NoNewLine; '> ' } }
myprompt> } # last char of prompt does not change color with syntax error
````

image

All Examples

image

Question-Answered

Most helpful comment

Add the following to your profile - I think it should fix it:

Set-PSReadLineOption -PromptText '> '

Before v2, PSReadLine relied on screen scraping to implement the error prompt coloring.
With the move to a more portable PSReadLine, screen scraping was no longer an option.

If you have a "pure" prompt, PSReadLine can infer what text needs to change to properly change the color, but if your prompt is not pure, e.g. calls Write-Host, then you need to help PSReadLine out with this configuration option.

Note that if your prompt is extra fancy (like mine):

image

You can also specify the error coloring precisely by passing 2 strings instead of one - the first being the normal text, the second being the error text, so mine looks like:

$esc = [char]0x1b
$pc = [char]0xe0b0
$fg = "0"
$nbg = "8;2;95;158;160"
$ebg = "1"

Set-PSReadLineOption -PromptText (
    "$esc[4${nbg};3${fg}mPS$esc[4${fg};3${nbg}m$pc",
    "$esc[4${ebg};3${fg}mPS$esc[4${fg};3${ebg}m$pc"
)

Note how I use the background color instead of the foreground color.

All 8 comments

Add the following to your profile - I think it should fix it:

Set-PSReadLineOption -PromptText '> '

Before v2, PSReadLine relied on screen scraping to implement the error prompt coloring.
With the move to a more portable PSReadLine, screen scraping was no longer an option.

If you have a "pure" prompt, PSReadLine can infer what text needs to change to properly change the color, but if your prompt is not pure, e.g. calls Write-Host, then you need to help PSReadLine out with this configuration option.

Note that if your prompt is extra fancy (like mine):

image

You can also specify the error coloring precisely by passing 2 strings instead of one - the first being the normal text, the second being the error text, so mine looks like:

$esc = [char]0x1b
$pc = [char]0xe0b0
$fg = "0"
$nbg = "8;2;95;158;160"
$ebg = "1"

Set-PSReadLineOption -PromptText (
    "$esc[4${nbg};3${fg}mPS$esc[4${fg};3${nbg}m$pc",
    "$esc[4${ebg};3${fg}mPS$esc[4${fg};3${ebg}m$pc"
)

Note how I use the background color instead of the foreground color.

@lzybkr Thanks for that tip, that generally solves my problem!

However I'm running into another related issue to the error prompt coloring.

If I add any of the "Smart" key handlers from the SamplePSReadLineProfile.ps1, when the prompt reverts from error back to "normal" the cursor becomes a ? instead.

Here's a repro using the SmartBackspace key handler.

# C:\users\<username>\Documents\WindowsPowerShell\profile.ps1

Set-PSReadLineKeyHandler -Key Backspace `
                         -BriefDescription SmartBackspace `
                         -LongDescription "Delete previous character or matching quotes/parens/braces" `
                         -ScriptBlock {
    param($key, $arg)

    $line = $null
    $cursor = $null
    [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)

    if ($cursor -gt 0)
    {
        $toMatch = $null
        if ($cursor -lt $line.Length)
        {
            switch ($line[$cursor])
            {
                <#case#> '"' { $toMatch = '"'; break }
                <#case#> "'" { $toMatch = "'"; break }
                <#case#> ')' { $toMatch = '('; break }
                <#case#> ']' { $toMatch = '['; break }
                <#case#> '}' { $toMatch = '{'; break }
            }
        }

        if ($toMatch -ne $null -and $line[$cursor-1] -eq $toMatch)
        {
            [Microsoft.PowerShell.PSConsoleReadLine]::Delete($cursor - 1, 2)
        }
        else
        {
            [Microsoft.PowerShell.PSConsoleReadLine]::BackwardDeleteChar($key, $arg)
        }
    }
}

function prompt {
    Write-Host "test$([char]0x276F)" -NoNewLine
    ' '
}

Set-PSReadLineOption -PromptText "$([char]0x276f) "
test❯ {enter}
test? if{backspace} # turns prompt into "?"

@DecoyJoe - yeah, I see the same thing with the ? but hadn't investigated. It looks like you need to set the output encoding to UTF8:

[Console]::OutputEncoding = [System.Text.Encoding]::UTF8

I thought PSReadLine was doing that for you, but maybe that changed. @daxian-dbw?

I thought PSReadLine was doing that for you, but maybe that changed. @daxian-dbw?

No, how UTF8 is set is not changed in PSRL.
This is because PSRL set the output encoding to the initial encoding before evaluating a script block key binding.

[PSConsoleReadLine]::Delete or [PSConsoleReadLine]::BackwardDeleteChar will trigger the rendering, which will run in the initial console encoding, and hence you see the question mark.
I feel resetting console encoding is not really necessary for invoking a keybinding scriptblock, given that those script blocks usually calls back to PSReadLine static methods that triggers rendering.

https://github.com/PowerShell/PSReadLine/blob/65d332e277c88cd7f7050786956afae9770555d4/PSReadLine/ReadLine.cs#L564-L574

The output encoding needs to be UTF8 when PSReadLine calls Console.Write: https://github.com/PowerShell/PSReadLine/blob/65d332e277c88cd7f7050786956afae9770555d4/PSReadLine/Render.cs#L441-L453

I feel resetting console encoding is not really necessary for invoking a keybinding scriptblock, given that those script blocks usually calls back to PSReadLine static methods that triggers rendering.

@lzybkr what's your thoughts on this?

Calling external commands is definitely a scenario and while rendering may be likely, it's not guaranteed. Imagine a minimalist prompt that doesn't show the current directory, but adding a key binding to something like https://github.com/ajeetdsouza/zoxide to change directories.

while rendering may be likely, it's not guaranteed.

That makes sense.

If we want PSReadLine reliably calls Console.Write/WriteLine in UTF8 console encoding, then I guess the VirtualTerminal and LegacyWin32Console has to set and reset console encoding in the Write and WriteLine implementation.

Was this page helpful?
0 / 5 - 0 ratings