Realtime Syntax Highlighting in your PowerShell Console


In our most recent CTP, we exposed PowerShell's parsing and tokenizing API for the great benefit of ISVs and hobbyists alike. As an initial demonstration of it, Show-ColorizedContent let you display nicely formatted code listing in your console:

PS C:\Temp> .\Show-ColorizedContent.ps1 Show-ColorizedContent.ps1 ` >> -HighlightRanges (4..5+1+30..33) >> 001 > #requires -version 2.0 002 | 003 | param( 004 > $filename = $(throw "Please specify a filename."), 005 > $highlightRanges = @(), 006 | [System.Management.Automation.SwitchParameter] $excludeLineNumbers) 007 | 008 | # [Enum]::GetValues($host.UI.RawUI.ForegroundColor.GetType()) | % { Write-Host -Fore $_ "$_" } (...)
026 | $highlightColor = "Green" 027 | $highlightCharacter = ">" 028 | 029 | ## Read the text of the file, and parse it 030 > $file = (Resolve-Path $filename).Path 031 > $content = [IO.File]::ReadAllText($file) 032 > $parsed = [System.Management.Automation.PsParser]::Tokenize($content, [ref] $null) | 033 > Sort StartLine,StartColumn 034 | 035 | function WriteFormattedLine($formatString, [int] $line) 036 | { 037 | if($excludeLineNumbers) { return } 038 | 039 | $hColor = "Gray" 040 | $separator = "|"

While this is very cool, we can actually push the parsing and tokenizing API even further. How about real time?

The first time I got this working, I actually giggled a little with glee. We're all so used to instant gratification when it comes to syntax highlighting that we sometimes forget about it. Until you drop into an environment that doesn't support it -- such as Notepad.exe or the PowerShell console. But with enough chewing gum and rubber bands, we can at least bring it into the PowerShell console.

As an aside, the V2 CTP also includes an early alpha version of PowerShell's new graphical host, Graphical PowerShell. Graphical PowerShell handles syntax highlighting swimmingly, and then some. If you want even more usability improvements (such as window resizing, tabs, and direct Unicode support,) that's where it'll be.

The primary problem with creating a real time colourizer is that PowerShell doesn't generate any events or notifications when you press a key. This makes cooperative multithreading difficult, so instead we need to take a parasitic approach and dig around in the console's screen buffer.

PowerShell doesn't support script threading either, though, so the step toward real-time syntax highlighting in your console is a slightly modified version of Jim Truher's "Background Jobs and PowerShell" script.

For each job that you create, Jim's script creates a new System.Management.Automation.Runspaces.Runspace instance, and then executes the job in the pipeline. Since the runspace class offers an asynchronous (non-blocking) invoke method, this effectively lets us run PowerShell code in the background of our current process. As written, though, Jim's background job support doesn't offer access to the variables and information of the runspace that launched the job. While normally useful, it does limit the usefulness of jobs for parasitic groveling! So, our main change in the New-Job script is to add a variable to the context of new jobs: $executionContextProxy. Through this variable, child jobs can access the variables and other information of the parent that spawned them. This update is included in the attached zip file.

Once we can launch jobs in the background that have access to our environment, the next step is the actual colourizer. This script runs as the background job. It starts at the bottom of the console buffer (where your cursor is,) and scans upward (and left) until it finds the last line of your prompt. It takes that rectangle, uses the parser API to determine how to colourize it, and then swiftly replaces that section of your console screen buffer with the colourized version.

I say "quickly" here to mean "as quickly as possible." There is a slight chance that the background job will swap in the colourized buffer after you've typed some more. It takes some pretty drastic measures to avoid this, but the occasional visual glitch will happen. This is only visual, however. Once you press ENTER, PowerShell will still execute the command you typed.

Many prompts use the Write-Host to add colourized prompts to the console, and return only a single space in the prompt() function. Since the colourizer can't know exactly how your prompt will appear on the screen, you need to tell it what the last line looks like. In your prompt function, set a global variable (called "promptMarker") to have the same text as the last line of your prompt. For example, if your prompt displays:

PS:15 >

on the screen, then set $GLOBAL:promptMarker = "PS:15 >"

Now, with New-Job.ps1 and Start-Colourizer.ps1 in your path (and a prompt that updates the promptMarker variable,) start the colourizer as a job:


Now, PowerShell updates your console with context-sensitive syntax highlighting as you type.

If you find the default console colours to be ugly, the included Set-ConsoleColourTable.ps1 script tones them down to something much easier on the eyes.



14 Responses to “Realtime Syntax Highlighting in your PowerShell Console”

  1. Xaegr writes:

    It is amazing! Thanks! 🙂

  2. Oisin Grehan writes:

    Hhahahahaa! It’s disgusting, yet it’s beautiful! I love it!

  3. Chris Harris writes:

    Hi Lee,
    Great stuff!

    Question though – back when I was on the Exchange team I wanted a way to colorize the output from a script or one of our test-* cmdlets so that I could quickly indicate problems with red text.

    With the new color capabilities, is this now possible? If so, can you give me a short script example?


  4. Xaegr writes:

    Chris, Write-Warning is preffered way to indicate problems (yellow by default), and Write-Error – errors (red).

  5. Lionel writes:

    Is it possible to start it from $profile?

  6. Lee writes:

    Yep — just put the commands in your profile.

  7. Lionel writes:

    I will have to try it again (after the weekend), but putting the commands in $profile did not work for me (no error, but no highlighting).

  8. Lionel writes:

    Well, if I type
    New-Job { Start-Colourizer }
    interactively, it works, but if I add it to my $profile, it says "Job 0 Started" but does not highlight anything. $jobs[0].IsRunning is true.

  9. Lee writes:

    Hmm, without access to your machine, I think you’ll have to just do some good ol’ fashioned debugging 🙂 You could see if you can start another job in your profile ( [Console]::Beep(100,100) is my standard,) and then see where the colourizer is getting stuck if it is indeed getting called.

  10. Lionel writes:

    Actually, it was very simple (and the bug was mine): promptMarker must not contain any trailing space.
    It looks like Start-COlourizer does not handle lines longer than the console width, though.

  11. Lionel writes:

    Is there any chance that an improved version could be included in future releases of PowerShell?

  12. Robbie Foust writes:

    Hi Lee, are you still using this script? Just curious if you had a newer version. Gave it a try and it seems to hang in a loop or something (I haven’t spent any time debugging yet). Thanks and keep up the good work!

  13. Lee Holmes writes:

    Hey Robbie – use PSReadLine 🙂 It’s taken the concept a million times further:

  14. Robbie Foust writes:

    Cool, thanks Lee! I’ll definitely check it out.

Leave a Reply