PowerShell Cookbook

Search

Categories

 

On this page

Archive

Blogroll

Disclaimer
I work for Microsoft.

The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

RSS 2.0 | Atom 1.0 | CDF

Send mail to the author(s) E-mail

Total Posts: 222
This Year: 0
This Month: 0
This Week: 0
Comments: 536

Sign In

 Tuesday, February 12, 2008
Tuesday, February 12, 2008 8:26:56 AM (Pacific Standard Time, UTC-08:00) ( )

[Download: http://www.leeholmes.com/projects/SyntaxHighlight/Start-Colourizer.zip]

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:

image

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.

[Download: http://www.leeholmes.com/projects/SyntaxHighlight/Start-Colourizer.zip]

Enjoy!

Comments [11] | | # 
Wednesday, February 13, 2008 2:55:04 AM (Pacific Standard Time, UTC-08:00)
It is amazing! Thanks! :)
Wednesday, February 13, 2008 3:42:18 PM (Pacific Standard Time, UTC-08:00)
Hhahahahaa! It's disgusting, yet it's beautiful! I love it!
Saturday, February 23, 2008 2:02:24 AM (Pacific Standard Time, UTC-08:00)
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?

Thanks,
Chris
Friday, February 29, 2008 5:04:20 AM (Pacific Standard Time, UTC-08:00)
Chris, Write-Warning is preffered way to indicate problems (yellow by default), and Write-Error - errors (red).
Friday, March 14, 2008 4:27:55 PM (Pacific Daylight Time, UTC-07:00)
Is it possible to start it from $profile?
Friday, March 14, 2008 4:57:16 PM (Pacific Daylight Time, UTC-07:00)
Yep -- just put the commands in your profile.
Friday, March 14, 2008 10:53:01 PM (Pacific Daylight Time, UTC-07:00)
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).
Monday, March 17, 2008 11:25:03 PM (Pacific Daylight Time, UTC-07:00)
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.
Tuesday, March 18, 2008 3:35:31 PM (Pacific Daylight Time, UTC-07:00)
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.
Friday, April 04, 2008 1:56:34 PM (Pacific Daylight Time, UTC-07:00)
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.
Sunday, May 11, 2008 5:19:45 PM (Pacific Daylight Time, UTC-07:00)
Is there any chance that an improved version could be included in future releases of PowerShell?
Name
E-mail
Home page

Comment (Some html is allowed: b, blockquote@cite, em, i, strike, strong, sub, super, u)  

Enter the code shown (prevents robots):