Functions/ReadLine/Enable-PSReadLineSmartInsertDelete.ps1
<#
.SYNOPSIS Enable the smart insert/delete. .DESCRIPTION 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. .LINK https://github.com/PowerShell/PSReadLine/blob/master/PSReadLine/SamplePSReadLineProfile.ps1 #> function Enable-PSReadLineSmartInsertDelete { [CmdletBinding()] param () $smartInsertQuoteSplat = @{ Key = '"',"'" BriefDescription = 'SmartInsertQuote' LongDescription = '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) { 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 ($token.Extent.StartOffset -eq $cursor) { if ($token.Kind -eq [TokenKind]::Generic -or $token.Kind -eq [TokenKind]::Identifier) { $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 @smartInsertQuoteSplat $insertPairedBracesSplat = @{ Key = '(','{','[' BriefDescription = 'InsertPairedBraces' LongDescription = 'Insert matching braces' ScriptBlock = { param($key, $arg) $closeChar = switch ($key.KeyChar) { <#case#> '(' { [char]')'; break } <#case#> '{' { [char]'}'; break } <#case#> '[' { [char]']'; break } } [Microsoft.PowerShell.PSConsoleReadLine]::Insert("$($key.KeyChar)$closeChar") $line = $null $cursor = $null [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor) [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor - 1) } } Set-PSReadLineKeyHandler @insertPairedBracesSplat $smartCloseBracesSplat = @{ 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 @smartCloseBracesSplat $backspaceSplat = @{ 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 ($null -ne $toMatch -and $line[$cursor-1] -eq $toMatch) { [Microsoft.PowerShell.PSConsoleReadLine]::Delete($cursor - 1, 2) } else { [Microsoft.PowerShell.PSConsoleReadLine]::BackwardDeleteChar($key, $arg) } } } } Set-PSReadLineKeyHandler @backspaceSplat } |