PSReadLine: A Better Line Editing Experience for the PowerShell Console

When Windows PowerShell 3.0 shipped, the team created an extensibility mechanism to allow a third party to “take over” the line editing experience.  That hook is a function called PSConsoleHostReadline that PowerShell will call – if it exists – to read a line of text from the user.  The simplicity of this interface belies the potential complexity of what it needs to do.  Users expect to be able to do more than just type a command, mistake free and press enter.  No, they want editing features like cursor left/right, backspace, delete, kill to end of line, multiline editing, etc.

Fortunately for the community, one of the PowerShell team members – Jason Shirk – has implemented a very nice replacement line editor that uses this hook. It is called PSReadLine and you can get it here on GitHub.  There are no releases on GitHub at this point in time but you can just download the PSReadline.zip file from the master branch, be sure to unblock the ZIP file and then extract the module your Modules folder. Alternatively, if you have PsGet you can install PSReadLine using its Install-Module command e.g:

C:\PS> Install-Module PSReadLine

After installation, import the PSReadLine module to have it take over your command line editing experience.

C:\PS> Import-Module PSReadLine

Note: the PSReadLine module only works for the PowerShell.exe console. It does not work for the PowerShell ISE console pane.

You have now imported PSReadLine, so what does it get you?  If you use the default Windows (cmd) mode you get the standard CMD line editing  features:

  • Home/End navigation
  • Up/DownArrow history navigation
  • Right/Left Arrow single char navigation
  • Ctrl+Right/LeftArrow word navigation
  • Ctrl+Home to delete from cursor to beginning of line
  • Ctrl+End to delete from cursor to end of line
  • Esc to revert line
  • Delete to delete a char
  • Backspace to delete char before the cursor

You also get tab & shift+tab completion that you expect from the PowerShell console.  So what else does it buy you? Well syntax color for one thing:

image

And how about indicating when you have a syntax error:

image

Notice the red “>” in my prompt?  That is telling me I have a syntax error in this command.  If you look at the end, I’m missing a closing “}”.  Type  in that closing “}” and the red goes away.

How about being able to paste into the console using Ctrl+V like you can in ISE. PSReadLine has got that covered.  Ever deleted something accidentally in the line and wanted to get it back?  Try the handy Ctrl+Z/Ctrl+Y shortcuts for Undo and Redo.  Yes!!  This line editor has a full blown undo/redo stack.

Ever had a command go over a single line?  You know multiline commands are a pain in the PowerShell console because:

  1. You can’t go to a previous line to edit it. You have to completely start over. Pressing the UpArrow just cycles through history on the current line which means your current line edits get blown away.
  2. You can’t use Up/DownArrow history recall to recall the *whole* multiline command. You can only recall one line at a time which makes multiline commands a major PITA to recall & edit in the console.

Check this out with PSReadLine.  It doesn’t “look” much different than standard PowerShell e.g.:

image

But you can use the Right/LeftArrow keys to move the cursor between different lines and then edit those lines.  You still don’t want to use the Up/DownArrow keys for line navigation as these keys are still bound to history recall.  However, when you do need to recall this multiline command using the UpArrow, what you get is exactly what you see above.  All three lines included in that one history entry *AND* you can press LeftArrow to move up to the previous lines to edit them.

Last up is my favorite feature called PossibleCompletions.  If I type “Get-Process –<Ctrl+Space>” this is what PSReadLine gives me:

image

Seriously!  Is that cool or what!?  PSReadLine shows you all the parameters in this case.  If you had typed part of the parameter name, it would have shown all parameters that would have matched what you typed.  By the way, this also works for parameter argument values e.g.:

image

It also works for static and instance type members e.g.:

image

There are more capabilities in PSReadLine but most of those come with Emacs mode.  However you can create your own keybindings to do whatever command PSReadLine offers.  If you want to use the Emacs mode, execute this command:

image

If you like this new command line editing experience as much as I do you will want this in your profile script.  This is my section of profile script for PSReadLine:

if ($host.Name -eq 'ConsoleHost') {
    Import-Module PSReadline

    Set-PSReadlineKeyHandler -Key UpArrow   -Function HistorySearchBackward
    Set-PSReadlineKeyHandler –Key DownArrow -Function HistorySearchForward
}

Note that I’ve defined a few more keybindings that allow me to use the UpArrow and DownArrow keys to not only navigate backward and forward in history on an empty line but navigate based on a match as well.   That is, when I type the beginning of some command line, the UpArrow and DownArrow keys search history backward or forward respectively for command lines that match what’s been typed.

If you spend a lot of time at the PowerShell console you owe it yourself to give this module a try.  And if you run into issues, remember these are still somewhat early bits, the project developer has been responsive to fixing bugs.  You can post/view issues here on GitHub.  For more info on PSReadLine, check out the about_PSReadline topic after you have installed the module.

Update 12-29-2013:

Jason has delivered some new PSReadLine presents for the holidays.  Version 1.0.0.6 supports some notable new features.  My favorite of which is that you can select text using just the keyboard.  As if you were in Notepad or VS, press Shift+Right/LeftArrow to select one character at a time or use Ctrl+Shift+Right/LeftArrow to select whole words at a time.

Once you have the desired text selected you can press Ctrl+x to cut or Ctrl+Shift+c to copy.  Before careful with the copy keyboard shortcut because if you forgot to press Shift, the resulting Ctrl+c will abort the current command line and you can’t get it back – not even with Ctrl+z (undo).  I wonder if perhaps the default copy shortcut should be something like Shift+Insert – old school – to avoid any potential mishaps with Ctrl+c?

You might find the following custom keyhandler handy for selecting the whole command line with Ctrl+A:

Set-PSReadlineKeyHandler -Chord Ctrl+A `
                         -BriefDescription SelectEntireCommandLine `
                         -Description "Selects the entire command line" `
                         -ScriptBlock {
    param($key, $arg)

    [PSConsoleUtilities.PSConsoleReadLine]::BeginningOfLine($key, $arg)

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

    while ($cursor -lt $line.Length) {
        [PSConsoleUtilities.PSConsoleReadLine]::SelectForwardChar($key, $arg)
        [PSConsoleUtilities.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
    }
}

Another feature that has been added is CharacterSearch and CharacterSearchBackward. By default these are mapped to F3 and Shift+F3.  Say you are at the beginning of the command line with multiple pipeline stages.  Press F3, then press Shift+\ to specify the “|” character.  PSReadLine will advance the cursor to the next “|” character.  This is cool.  But the next two custom keyhandlers pretty nifty:

Set-PSReadlineKeyHandler -Chord Ctrl+\ `
                         -BriefDescription SearchForwardPipeChar `
                         -Description "Searches forward for the next pipeline character" `
                         -ScriptBlock {
    param($key, $arg)
    [PSConsoleUtilities.PSConsoleReadLine]::CharacterSearch($key, '|')
}
Set-PSReadlineKeyHandler -Chord Ctrl+Shift+\ `
                         -BriefDescription SearchBackwardPipeChar `
                         -Description "Searches backward for the next pipeline character" `
                         -ScriptBlock {
    param($key, $arg)
    [PSConsoleUtilities.PSConsoleReadLine]::CharacterSearchBackward($key, '|')
}

These two keyhandlers will allow you to navigate around your command line by jumping forward and backward between pipeline stages.  They use CharacterSearch and CharacterSearchBackward to move around the command line by searching for “|” characters.  So Ctrl+\ moves forward to the next “|” character and Ctrl+Shift+\ moves backwards to the previous “|” character.

And finally, some help for quickly finding out what keyhandlers are mapped to which keyboard shortcuts, just press Ctrl+Alt+? which on a US keyboard is really Ctrl+Alt+Shift+/ and you get this information:

Key                  Function                 Description
---                  --------                 -----------
Enter                AcceptLine               Accept the input or move to the...
Shift+Enter          AddLine                  Move the cursor to the next lin...
Escape               RevertLine               Equivalent to undo all edits (c...
LeftArrow            BackwardChar             Move the cursor back one charac...
RightArrow           ForwardChar              Move the cursor forward one cha...
Ctrl+LeftArrow       BackwardWord             Move the cursor to the beginnin...
Ctrl+RightArrow      NextWord                 Move the cursor forward to the ...
Shift+LeftArrow      SelectBackwardChar       Adjust the current selection to...
Shift+RightArrow     SelectForwardChar        Adjust the current selection to...
Ctrl+Shift+LeftArrow SelectBackwardWord       Adjust the current selection to...
Ctrl+Shift+RightArrow SelectNextWord          Adjust the current selection to...
UpArrow              PreviousHistory          Replace the input with the prev...
DownArrow            NextHistory              Replace the input with the next...
Home                 BeginningOfLine          Move the cursor to the beginnin...
End                  EndOfLine                Move the cursor to the end of t...
Delete               DeleteChar               Delete the character under the ...
Backspace            BackwardDeleteChar       Delete the charcter before the ...
Ctrl+Spacebar        PossibleCompletions      Display the possible completion...
Tab                  TabCompleteNext          Complete the input using the ne...
Shift+Tab            TabCompletePrevious      Complete the input using the pr...
Ctrl+v               Paste                    Paste text from the system clip...
Ctrl+c               CancelLine               Abort editing the current line ...
Ctrl+C               Copy                     Copy selected region to the sys...
Ctrl+l               ClearScreen              Clear the screen and redraw the...
Ctrl+x               Cut                      Delete selected region placing ...
Ctrl+y               Redo                     Redo an undo
Ctrl+z               Undo                     Undo a previous edit
Ctrl+Backspace       BackwardKillWord         Move the text from the start of...
Ctrl+Delete          KillWord                 Move the text from the cursor t...
Ctrl+End             ForwardDeleteLine        Delete text from the cursor to ...
Ctrl+Home            BackwardDeleteLine       Delete text from the cursor to ...
Ctrl+]               GotoBrace                Go to matching brace
Ctrl+Alt+?           ShowKeyBindings          Show all key bindings
Alt+?                WhatIsKey                Show the key binding for the ne...
F3                   CharacterSearch          Read a character and move the c...
Shift+F3             CharacterSearchBackward  Read a character and move the c...

 

And to find out what a specify keyboard shortcut is mapped to, pres Alt+? (or on a US keyboard Alt+Shift+/) and then the keyboard shortcut.  For example, if I press Alt+Shift+/ and then Ctrl+Shift+LeftArrow I get this information:

Ctrl+Shift+LeftArrow: SelectBackwardWord - Adjust the current selection to inclu
de the previous word

Major kudos to Jason and this awesome module.  It is funny how quickly you just get used to it and then when you’re on someone else’s PC without this module – well – you convince them pretty quickly that they need PSReadLine.  Smile

In closing, I’ll leave you with two more custom keyhandlers I have in my profile. The first creates a pair of single or double quotes and puts the cursor in the middle:

Set-PSReadlineKeyHandler -Chord "Ctrl+'","Ctrl+Shift+'" `
                         -BriefDescription SmartInsertQuote `
                         -Description "Insert paired quotes if not already on a quote" `
                         -ScriptBlock {
    param($key, $arg)

    $line = $null
    $cursor = $null
    [PSConsoleUtilities.PSConsoleReadline]::GetBufferState([ref]$line, [ref]$cursor)

    $keyChar = $key.KeyChar
    if ($key.Key -eq 'Oem7') {
        if ($key.Modifiers -eq 'Control') {
            $keyChar = "`'"
        }
        elseif ($key.Modifiers -eq 'Shift','Control') {
            $keyChar = '"'
        }
    }
    
    if ($line[$cursor] -eq $keyChar) {
        # Just move the cursor
        [PSConsoleUtilities.PSConsoleReadLine]::SetCursorPosition($cursor + 1)
    }
    else {
        # Insert matching quotes, move cursor to be in between the quotes
        [PSConsoleUtilities.PSConsoleReadLine]::Insert("$keyChar" * 2)
        [PSConsoleUtilities.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
        [PSConsoleUtilities.PSConsoleReadLine]::SetCursorPosition($cursor - 1)
    }
}

The second handler was inspired by mjolinor’s Clip-ToArray ISE add-on.  It is a more basic version of his function which I call – PasteAsHereString.  It is indeed handy and could be easily modified to do all that the original Clip-ToArray function does.

Set-PSReadlineKeyHandler -Chord Ctrl+Shift+v `
   -BriefDescription PasteAsHereString `
   -Description "Pastes the clipboard text as a here string" `
   -ScriptBlock {
    param($key, $arg)

    Add-Type -AssemblyName System.Windows.Forms
    $str = [windows.forms.clipboard]::GetText() -replace '(?m)[`n\s*]+$',''
    [PSConsoleUtilities.PSConsoleReadline]::Insert("@'`n${str}`n'@")
}
About these ads
This entry was posted in PowerShell, PowerShell 3.0, PowerShell 4.0. Bookmark the permalink.

8 Responses to PSReadLine: A Better Line Editing Experience for the PowerShell Console

  1. Great Article Keith! Just installed this module! love it!
    Thank you!

  2. Pingback: Microsoft Most Valuable Professional (MVP) – Best Posts of the Week around Windows Server, Exchange, SystemCenter and more – #51 - TechCenter - Blog - TechCenter - Dell Community

  3. psguy says:

    Thanks a lot! Live significantly improved!

  4. Thanks a lot for this article Keith. This module and his added functionality is defeinitly a must !

  5. Pingback: Get-Scripting Podcast Episode 37 – (Jingle Bells) | CrypticZero

  6. Castrom says:

    Wow! Just an amazing module! Found this via Hey, Scripting Guys! and read all that I could and am extremely grateful I did. Just the amount of features and the added abilities to make working in the console that much more fun, and better — you’d be doing yourself a huge favor by using this. Thank you so much.

  7. Pingback: A Better PowerShell Console with Custom PSReadLine Functions - Hey, Scripting Guy! Blog - Site Home - TechNet Blogs

  8. Pingback: PSReadline & PsGet | Making Magic with PowerShell

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s