Last active
March 9, 2022 21:51
-
-
Save ninmonkey/dcc8b0ec2e462ebf0d448fd0402592cd to your computer and use it in GitHub Desktop.
Custom PowerShell 7 PSReadLikeKeyHandlers.ps1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using namespace System.Management.Automation | |
using namespace System.Management.Automation.Language | |
# This example will replace any aliases on the command line with the resolved commands. | |
Set-PSReadLineKeyHandler -Key 'Alt+%' ` | |
-BriefDescription ExpandAliases ` | |
-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 [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) | |
} | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
About: | |
Supports indenting/dedenting multiple lines of selected text | |
Like VS Code's hotkeys: | |
'ctrl+[' and 'ctrl+]' | |
Original from: | |
[Jaykul](https://gist.github.com/Jaykul/761c3b962e1f28998867e120c49c67ae) | |
#> | |
# Hack in a [ReadLine] accelerator | |
$xlr8r = [psobject].assembly.gettype('System.Management.Automation.TypeAccelerators') | |
if ($xlr8r::AddReplace) { | |
$xlr8r::AddReplace('ReadLine', [Microsoft.PowerShell.PSConsoleReadLine]) | |
} | |
else { | |
$null = $xlr8r::Remove('ReadLine') | |
$xlr8r::Add('ReadLine', [Microsoft.PowerShell.PSConsoleReadLine]) | |
} | |
# Set-PSReadLineKeyHandler -Key 'Alt+(' ` | |
# -BriefDescription ParenthesizeSelection ` | |
# -LongDescription 'Put parenthesis around the selection or entire line and move the cursor to after the closing parenthesis' ` | |
# -ScriptBlock | |
# Now some key handlers | |
Set-PSReadLineKeyHandler 'Alt+]' ` | |
-BriefDescription 'Indent/Dedent Selected text' ` | |
-Description 'Indent/Dedent Selected text like VS Code ctrl+[ / ] ctrl+[' ` | |
-ScriptBlock { | |
param($key, $arg) | |
$start = $null | |
$length = $null | |
[ReadLine]::GetSelectionState([ref]$start, [ref]$length) | |
$command = $null | |
$cursor = $null | |
[ReadLine]::GetBufferState([ref]$command, [ref]$cursor) | |
# if there's no selection, these should be set to the cursor | |
if ($start -lt 0) { $start = $cursor; $length = 0 } | |
$end = $start + $length | |
# Write-Host "`e[s`e[0;0H`e[32mStart:$start End:$end Length:$Length `e[u" | |
# pretend that entire lines are selected | |
if ($start -gt 0) { | |
$start = $command.SubString(0, $start).LastIndexOf("`n") + 1 | |
} | |
$end = $end + $command.SubString($end).IndexOf("`n") | |
$length = $end - $start | |
# Write-Host "`e[s`e[2;0H`e[34mStart:$start End:$end Length:$Length `e[u" | |
$lines = $command.SubString($start, $length) | |
$count = ($lines -split "`n").Count | |
# Write-Host "`e[s`e[3;0H`e[36m$lines`e[u" | |
# Write-Host "`e[s`e[2;0H`e[34mStart:$start End:$end Length:$Length Lines:$Count`e[u" | |
[ReadLine]::Replace($start, $length, ($lines -replace '(?m)^', ' ')) | |
[ReadLine]::SetCursorPosition($start) | |
[ReadLine]::SelectLine() | |
if ($count -gt 1) { | |
while (--$Count) { | |
[ReadLine]::SelectForwardChar() | |
[ReadLine]::SelectLine() | |
} | |
} | |
} | |
Set-PSReadLineKeyHandler 'Alt+[' ` | |
-BriefDescription 'Indent/Dedent Selected text' ` | |
-Description 'Indent/Dedent Selected text like VS Code ctrl+[/]' ` | |
-ScriptBlock { | |
param($key, $arg) | |
$start = $null | |
$length = $null | |
[ReadLine]::GetSelectionState([ref]$start, [ref]$length) | |
$command = $null | |
$cursor = $null | |
[ReadLine]::GetBufferState([ref]$command, [ref]$cursor) | |
# if there's no selection, these should be set to the cursor | |
if ($start -lt 0) { $start = $cursor; $length = 0 } | |
$end = $start + $length | |
# Write-Host "`e[s`e[0;0H`e[32mStart:$start End:$end Length:$Length `e[u" | |
# pretend that entire lines are selected | |
if ($start -gt 0) { | |
$start = $command.SubString(0, $start).LastIndexOf("`n") + 1 | |
} | |
$end = $end + $command.SubString($end).IndexOf("`n") | |
$length = $end - $start | |
# Write-Host "`e[s`e[2;0H`e[34mStart:$start End:$end Length:$Length `e[u" | |
$lines = $command.SubString($start, $length) | |
$count = ($lines -split "`n").Count | |
# Write-Host "`e[s`e[3;0H`e[36m$lines`e[u" | |
# Write-Host "`e[s`e[2;0H`e[34mStart:$start End:$end Length:$Length Lines:$Count`e[u" | |
[ReadLine]::Replace($start, $length, ($lines -replace '(?m)^ ', '')) | |
[ReadLine]::SetCursorPosition($start) | |
[ReadLine]::SelectLine() | |
if ($count -gt 1) { | |
while (--$Count) { | |
[ReadLine]::SelectForwardChar() | |
[ReadLine]::SelectLine() | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using namespace System.Management.Automation | |
using namespace System.Management.Automation.Language | |
# Sometimes you want to get a property of invoke a member on what you've entered so far | |
# but you need parens to do that. This binding will help by putting parens around the current selection, | |
# or if nothing is selected, the whole line. | |
Set-PSReadLineKeyHandler -Key 'Alt+(' ` | |
-BriefDescription ParenthesizeSelection ` | |
-LongDescription 'Put parenthesis 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) { | |
[Microsoft.PowerShell.PSConsoleReadLine]::Replace($selectionStart, $selectionLength, '(' + $line.SubString($selectionStart, $selectionLength) + ')') | |
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($selectionStart + $selectionLength + 2) | |
} | |
else { | |
[Microsoft.PowerShell.PSConsoleReadLine]::Replace(0, $line.Length, '(' + $line + ')') | |
[Microsoft.PowerShell.PSConsoleReadLine]::EndOfLine() | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using namespace System.Management.Automation | |
using namespace System.Management.Automation.Language | |
#region Smart Insert/Delete | |
<# Source: official PSReadlineModule sample | |
about: | |
Auto opens/closes/jumps over: {}()[] | |
#> | |
# The next four key handlers are designed to make entering matched quotes | |
# parens, and braces a nicer experience. I'd like to include functions | |
# in the module that do this, but this implementation still isn't as smart | |
# as ReSharper, so I'm just providing it as a sample. | |
Set-PSReadLineKeyHandler -Key '"', "'" ` | |
-BriefDescription SmartInsertQuote ` | |
-LongDescription 'Warning: Can act strange if combined with "Toggle Qoute Argument". Insert paired quotes if not already on a quote' ` | |
-ScriptBlock { | |
param($key, $arg) | |
$quote = $key.KeyChar | |
$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 text is selected, just quote it without any smarts | |
if ($selectionStart -ne -1) { | |
[Microsoft.PowerShell.PSConsoleReadLine]::Replace($selectionStart, $selectionLength, $quote + $line.SubString($selectionStart, $selectionLength) + $quote) | |
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($selectionStart + $selectionLength + 2) | |
return | |
} | |
$ast = $null | |
$tokens = $null | |
$parseErrors = $null | |
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$ast, [ref]$tokens, [ref]$parseErrors, [ref]$null) | |
function FindToken { | |
param($tokens, $cursor) | |
foreach ($token in $tokens) { | |
if ($cursor -lt $token.Extent.StartOffset) { continue } | |
if ($cursor -lt $token.Extent.EndOffset) { | |
$result = $token | |
$token = $token -as [StringExpandableToken] | |
if ($token) { | |
$nested = FindToken $token.NestedTokens $cursor | |
if ($nested) { $result = $nested } | |
} | |
return $result | |
} | |
} | |
return $null | |
} | |
$token = FindToken $tokens $cursor | |
# If we're on or inside a **quoted** string token (so not generic), we need to be smarter | |
if ($token -is [StringToken] -and $token.Kind -ne [TokenKind]::Generic) { | |
# If we're at the start of the string, assume we're inserting a new string | |
if ($token.Extent.StartOffset -eq $cursor) { | |
[Microsoft.PowerShell.PSConsoleReadLine]::Insert("$quote$quote ") | |
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor + 1) | |
return | |
} | |
# If we're at the end of the string, move over the closing quote if present. | |
if ($token.Extent.EndOffset -eq ($cursor + 1) -and $line[$cursor] -eq $quote) { | |
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor + 1) | |
return | |
} | |
} | |
if ($null -eq $token -or | |
$token.Kind -eq [TokenKind]::RParen -or $token.Kind -eq [TokenKind]::RCurly -or $token.Kind -eq [TokenKind]::RBracket) { | |
if ($line[0..$cursor].Where{ $_ -eq $quote }.Count % 2 -eq 1) { | |
# Odd number of quotes before the cursor, insert a single quote | |
[Microsoft.PowerShell.PSConsoleReadLine]::Insert($quote) | |
} | |
else { | |
# Insert matching quotes, move cursor to be in between the quotes | |
[Microsoft.PowerShell.PSConsoleReadLine]::Insert("$quote$quote") | |
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor + 1) | |
} | |
return | |
} | |
# If cursor is at the start of a token, enclose it in quotes. | |
if ($token.Extent.StartOffset -eq $cursor) { | |
if ($token.Kind -eq [TokenKind]::Generic -or $token.Kind -eq [TokenKind]::Identifier -or | |
$token.Kind -eq [TokenKind]::Variable -or $token.TokenFlags.hasFlag([TokenFlags]::Keyword)) { | |
$end = $token.Extent.EndOffset | |
$len = $end - $cursor | |
[Microsoft.PowerShell.PSConsoleReadLine]::Replace($cursor, $len, $quote + $line.SubString($cursor, $len) + $quote) | |
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($end + 2) | |
return | |
} | |
} | |
# We failed to be smart, so just insert a single quote | |
[Microsoft.PowerShell.PSConsoleReadLine]::Insert($quote) | |
} | |
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 } | |
} | |
$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) { | |
# Text is selected, wrap it in brackets | |
[Microsoft.PowerShell.PSConsoleReadLine]::Replace($selectionStart, $selectionLength, $key.KeyChar + $line.SubString($selectionStart, $selectionLength) + $closeChar) | |
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($selectionStart + $selectionLength + 2) | |
} | |
else { | |
# No text is selected, insert a pair | |
[Microsoft.PowerShell.PSConsoleReadLine]::Insert("$($key.KeyChar)$closeChar") | |
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor + 1) | |
} | |
} | |
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)") | |
} | |
} | |
Set-PSReadLineKeyHandler -Key Backspace ` | |
-BriefDescription SmartBackspace ` | |
-LongDescription 'Delete previous character or matching quotes/parens/braces' ` | |
-ScriptBlock { | |
param($key, $arg) | |
$line = $null | |
$cursor = $null | |
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor) | |
if ($cursor -gt 0) { | |
$toMatch = $null | |
if ($cursor -lt $line.Length) { | |
switch ($line[$cursor]) { | |
<#case#> '"' { $toMatch = '"'; break } | |
<#case#> "'" { $toMatch = "'"; break } | |
<#case#> ')' { $toMatch = '('; break } | |
<#case#> ']' { $toMatch = '['; break } | |
<#case#> '}' { $toMatch = '{'; break } | |
} | |
} | |
if ($toMatch -ne $null -and $line[$cursor - 1] -eq $toMatch) { | |
[Microsoft.PowerShell.PSConsoleReadLine]::Delete($cursor - 1, 2) | |
} | |
else { | |
[Microsoft.PowerShell.PSConsoleReadLine]::BackwardDeleteChar($key, $arg) | |
} | |
} | |
} | |
#endregion Smart Insert/Delete |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using namespace System.Management.Automation | |
using namespace System.Management.Automation.Language | |
# Each time you press Alt+', this key handler will change the token | |
# under or before the cursor. It will cycle through single quotes, double quotes, or | |
# no quotes each time it is invoked. | |
Set-PSReadLineKeyHandler -Key "Alt+'" ` | |
-BriefDescription ToggleQuoteArgument ` | |
-LongDescription 'Warning: Can act strange if combined with 'Smart Brackets Braces Parens.ps1'. Toggle quotes on the argument under the cursor' ` | |
-ScriptBlock { | |
param($key, $arg) | |
$ast = $null | |
$tokens = $null | |
$errors = $null | |
$cursor = $null | |
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$ast, [ref]$tokens, [ref]$errors, [ref]$cursor) | |
$tokenToChange = $null | |
foreach ($token in $tokens) { | |
$extent = $token.Extent | |
if ($extent.StartOffset -le $cursor -and $extent.EndOffset -ge $cursor) { | |
$tokenToChange = $token | |
# If the cursor is at the end (it's really 1 past the end) of the previous token, | |
# we only want to change the previous token if there is no token under the cursor | |
if ($extent.EndOffset -eq $cursor -and $foreach.MoveNext()) { | |
$nextToken = $foreach.Current | |
if ($nextToken.Extent.StartOffset -eq $cursor) { | |
$tokenToChange = $nextToken | |
} | |
} | |
break | |
} | |
} | |
if ($tokenToChange -ne $null) { | |
$extent = $tokenToChange.Extent | |
$tokenText = $extent.Text | |
if ($tokenText[0] -eq '"' -and $tokenText[-1] -eq '"') { | |
# Switch to no quotes | |
$replacement = $tokenText.Substring(1, $tokenText.Length - 2) | |
} | |
elseif ($tokenText[0] -eq "'" -and $tokenText[-1] -eq "'") { | |
# Switch to double quotes | |
$replacement = '"' + $tokenText.Substring(1, $tokenText.Length - 2) + '"' | |
} | |
else { | |
# Add single quotes | |
$replacement = "'" + $tokenText + "'" | |
} | |
[Microsoft.PowerShell.PSConsoleReadLine]::Replace( | |
$extent.StartOffset, | |
$tokenText.Length, | |
$replacement) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
credits to Jaykul and the PSReadLine repo