Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
PSReadline configuration updated for PSReadline v1.1
#Requires -Modules ClipboardText
# Other hosts (ISE, ConEmu) don't always work as well with PSReadLine.
# Also, if PS is run with -Command, PSRL loading is suppressed.
if (($host.Name -eq 'Windows PowerShell ISE Host') -or
($host.Name -eq 'Visual Studio Code Host') -or
([System.Environment]::CommandLine -match '-command')) {
return
}
if ((Get-Module PSReadLine).Version.Major -lt 2) {
throw "PSReadLine 1.x installed or not imported, import PSRL or ugprade to at least 2.x."
}
# Configure PSReadLine options
$darkGray = "$([char]27)[38;2;192;192;192m"
$options = @{
Colors = @{ Parameter = $darkGray; Operator = $darkGray }
ExtraPromptLineCount = 1
MaximumHistoryCount = 10000
HistorySavePath = "$PSScriptRoot\PSReadLine_history.txt"
HistoryNoDuplicates = $true
HistorySearchCursorMovesToEnd = $true
AddToHistoryHandler = {
param([string]$line)
return $line.Length -gt 3 -and $line[0] -ne ' ' -and $line[0] -ne ';'
}
}
Set-PSReadLineOption @options
if ($env:TERM_PROGRAM -eq "vscode") {
Set-PSReadLineKeyHandler -Chord 'Ctrl+w' -Function BackwardKillWord
Set-PSReadLineKeyHandler -Chord 'Alt+D' -Function KillWord
Set-PSReadLineKeyHandler -Chord 'Ctrl+@' -Function MenuComplete
}
# For Windows Terminal
Set-PSReadLineKeyHandler -Chord "ctrl+h" -Function BackwardDeleteWord
Set-PSReadlineKeyHandler -Chord UpArrow -Function HistorySearchBackward
Set-PSReadlineKeyHandler -Chord DownArrow -Function HistorySearchForward
Set-PSReadlineKeyHandler -Chord 'Ctrl+D,Ctrl+C' -Function CaptureScreen
Set-PSReadlineKeyHandler -Chord 'Ctrl+Tab' -Function Complete
Set-PSReadlineKeyHandler -Chord 'Ctrl+q' -Function YankLastArg
# Insert paired quotes if not already on a quote
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
[Microsoft.PowerShell.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 $key.KeyChar) {
# Just move the cursor
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor + 1)
}
else {
# Insert matching quotes, move cursor to be in between the quotes
[Microsoft.PowerShell.PSConsoleReadLine]::Insert("$keyChar" * 2)
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor - 1)
}
}
# Copy the current path to the clipboard
Set-PSReadlineKeyHandler -Key Alt+c `
-BriefDescription CopyCurrentPathToClipboard `
-LongDescription "Copy the current path to the clipboard" `
-ScriptBlock {
param($key, $arg)
if ($IsLinux -and (Get-Command cmd.exe -ErrorAction SilentlyContinue)) {
# For the WSL case, we can't use ClipboardText because xclip doesn't work in WSL
powershell.exe -noprofile -nologo -noninteractive -command Set-Clipboard -Text $pwd.Path
}
else {
Set-ClipboardText $pwd.Path
}
}
# Create the following handlers only when running in WSL
if ($IsLinux -and (Get-Command cmd.exe -ErrorAction SilentlyContinue)) {
# Paste the clipboard text
Set-PSReadlineKeyHandler -Key Ctrl+v `
-BriefDescription PasteText `
-LongDescription "Paste the clipboard text" `
-ScriptBlock {
param($key, $arg)
[string]$clipboardText = powershell.exe -noprofile -nologo -noninteractive -command Get-Clipboard
if ($clipboardText) {
# Convert to WSL path e.g. /mnt/<drive>/<path>
if ($clipboardText -match "^(.*?)([aA-zZ]):(\\.*)") {
$clipboardText = "$($matches[1])/mnt/$($matches[2].ToLower())$($matches[3].Replace('\', '/'))"
}
[Microsoft.PowerShell.PSConsoleReadLine]::Insert($clipboardText)
}
else
{
[Microsoft.PowerShell.PSConsoleReadLine]::Ding()
}
}
}
# Paste the clipboard text as a here string
Set-PSReadlineKeyHandler -Key Alt+v `
-BriefDescription PasteAsHereString `
-LongDescription "Paste the clipboard text as a here string" `
-ScriptBlock {
param($key, $arg)
if ($IsLinux -and (Get-Command cmd.exe -ErrorAction SilentlyContinue)) {
# For the WSL case, we can't use ClipboardText because xclip doesn't work in WSL
$clipboardText = powershell.exe -noprofile -nologo -noninteractive -command Get-Clipboard
}
else {
$clipboardText = Get-ClipboardText
}
if ($clipboardText) {
# Remove trailing spaces, convert \r\n to \n, and remove the final \n.
$text = $clipboardText.TrimEnd() -join "`n"
[Microsoft.PowerShell.PSConsoleReadLine]::Insert("@'`n$text`n'@")
}
else
{
[Microsoft.PowerShell.PSConsoleReadLine]::Ding()
}
}
# Insert matching braces
Set-PSReadlineKeyHandler -Key '(','{','[' `
-BriefDescription InsertPairedBraces `
-LongDescription "Insert matching braces" `
-ScriptBlock {
param($key, $arg)
$closeChar = switch ($key.KeyChar)
{
<#case#> '(' { [char]')'; break }
<#case#> '{' { [char]'}'; break }
<#case#> '[' { [char]']'; break }
}
$line = $null
$cursor = $null
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
if ($cursor -eq $line.Length -or $line[$cursor] -match '\)|}|\]|\s')
{
[Microsoft.PowerShell.PSConsoleReadLine]::Insert("$($key.KeyChar)$closeChar")
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor - 1)
}
else
{
[Microsoft.PowerShell.PSConsoleReadLine]::Insert($key.KeyChar)
}
}
# Insert closing brace or skip
Set-PSReadlineKeyHandler -Key ')',']','}' `
-BriefDescription SmartCloseBraces `
-LongDescription "Insert closing brace or skip" `
-ScriptBlock {
param($key, $arg)
$line = $null
$cursor = $null
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
if ($line[$cursor] -eq $key.KeyChar)
{
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor + 1)
}
else
{
[Microsoft.PowerShell.PSConsoleReadLine]::Insert("$($key.KeyChar)")
}
}
# Put parentheses around the selection or entire line and move the cursor to after the closing paren
Set-PSReadlineKeyHandler -Key 'Ctrl+(' `
-BriefDescription ParenthesizeSelection `
-LongDescription "Put parentheses around the selection or entire line and move the cursor to after the closing parenthesis" `
-ScriptBlock {
param($key, $arg)
$selectionStart = $null
$selectionLength = $null
[Microsoft.PowerShell.PSConsoleReadLine]::GetSelectionState([ref]$selectionStart, [ref]$selectionLength)
$line = $null
$cursor = $null
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
if ($selectionStart -ne -1)
{
$replacement = '(' + $line.SubString($selectionStart, $selectionLength) + ')'
[Microsoft.PowerShell.PSConsoleReadLine]::Replace($selectionStart, $selectionLength, $replacement)
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($selectionStart + $selectionLength + 2)
}
else
{
[Microsoft.PowerShell.PSConsoleReadLine]::Replace(0, $line.Length, '(' + $line + ')')
[Microsoft.PowerShell.PSConsoleReadLine]::EndOfLine()
}
}
# Replace all aliases with the full command
Set-PSReadlineKeyHandler -Key Alt+r `
-BriefDescription ResolveAliases `
-LongDescription "Replace all aliases with the full command" `
-ScriptBlock {
param($key, $arg)
$ast = $null
$tokens = $null
$errors = $null
$cursor = $null
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$ast, [ref]$tokens, [ref]$errors, [ref]$cursor)
$startAdjustment = 0
foreach ($token in $tokens)
{
if ($token.TokenFlags -band [System.Management.Automation.Language.TokenFlags]::CommandName)
{
$alias = $ExecutionContext.InvokeCommand.GetCommand($token.Extent.Text, 'Alias')
if ($alias -ne $null)
{
$resolvedCommand = $alias.ResolvedCommandName
if ($resolvedCommand -ne $null)
{
$extent = $token.Extent
$length = $extent.EndOffset - $extent.StartOffset
[Microsoft.PowerShell.PSConsoleReadLine]::Replace(
$extent.StartOffset + $startAdjustment,
$length,
$resolvedCommand)
# Our copy of the tokens won't have been updated, so we need to
# adjust by the difference in length
$startAdjustment += ($resolvedCommand.Length - $length)
}
}
}
}
}
# Save current line in history but do not execute
Set-PSReadlineKeyHandler -Key Alt+w `
-BriefDescription SaveInHistory `
-LongDescription "Save current line in history but do not execute" `
-ScriptBlock {
param($key, $arg)
$line = $null
$cursor = $null
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
[Microsoft.PowerShell.PSConsoleReadLine]::AddToHistory($line)
[Microsoft.PowerShell.PSConsoleReadLine]::RevertLine()
}
# This key handler shows the entire or filtered history using Out-GridView. The
# typed text is used as the substring pattern for filtering. A selected command
# is inserted to the command line without invoking. Multiple command selection
# is supported, e.g. selected by Ctrl + Click.
Set-PSReadlineKeyHandler -Key F7 `
-BriefDescription History `
-LongDescription 'Show command history' `
-ScriptBlock {
$pattern = $null
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$pattern, [ref]$null)
if ($pattern)
{
$pattern = [regex]::Escape($pattern)
}
$history = [System.Collections.ArrayList]@(
$last = ''
$lines = ''
foreach ($line in [System.IO.File]::ReadLines((Get-PSReadlineOption).HistorySavePath))
{
if ($line.EndsWith('`'))
{
$line = $line.Substring(0, $line.Length - 1)
$lines = if ($lines)
{
"$lines`n$line"
}
else
{
$line
}
continue
}
if ($lines)
{
$line = "$lines`n$line"
$lines = ''
}
if (($line -cne $last) -and (!$pattern -or ($line -match $pattern)))
{
$last = $line
$line
}
}
)
$history.Reverse()
$command = $history | Out-GridView -Title History -PassThru
if ($command)
{
[Microsoft.PowerShell.PSConsoleReadLine]::RevertLine()
[Microsoft.PowerShell.PSConsoleReadLine]::Insert(($command -join "`n"))
}
}
@mklement0

This comment has been minimized.

Copy link

commented Aug 26, 2016

Great stuff, thanks for sharing. I suggest replacing

$resolvedCommand = $alias.ResolvedCommandName 

with

$resolvedCommand = if ($alias.ResolvedCommandName) { $alias.ResolvedCommandName } else { $alias.Definition }

so as to also resolve aliases of things other than regular cmdlets (such as cmdlets from modules before they're on-demand loaded).

@rkeithhill

This comment has been minimized.

Copy link
Owner Author

commented Oct 6, 2016

@mklement0 Thanks for the feedback!

@stej

This comment has been minimized.

Copy link

commented Feb 10, 2017

Omg that ParenthesizeSelection is something that I always dreamt of.
(I'm really behind all the good PowerShell stuff, so just discovering PsReadLine.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.