Archives for the Month of February, 2008

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.