Last active
April 26, 2021 13:11
-
-
Save mklement0/290ef7cdbdf0db274d6da64fade46929 to your computer and use it in GitHub Desktop.
PowerShell sample code that demonstrates a PSReadLine key handler that display meta-information about the command being typed in real time.
This file contains 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
<# | |
License: MIT | |
Author: Michael Klement <[email protected]> | |
See https://stackoverflow.com/a/67266971/45375 for more information. | |
#> | |
# The printable characters to respond to. | |
$printableChars = [char[]] (0x20..0x7e + 0xa0..0xff) | |
# The control characters to respond to. | |
$controlChars = 'Enter', 'Escape', 'Backspace', 'Delete' | |
# Set up the key handler for all specified characters. | |
$printableChars + $controlChars | ForEach-Object { | |
Set-PSReadLineKeyHandler $_ { | |
param($key, $arg) | |
$line = $cursor = $null | |
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref] $null, [ref] $cursor) | |
# Handle the key at hand. | |
switch ($key.Key) { | |
'Backspace' { [Microsoft.PowerShell.PSConsoleReadLine]::BackwardDeleteChar(); break } | |
'Delete' { try { [Microsoft.PowerShell.PSConsoleReadLine]::Delete($cursor, 1) } catch { }; break } # ignore error with empty buffer | |
'Escape' { | |
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref] $line, [ref] $null) | |
[Microsoft.PowerShell.PSConsoleReadLine]::Delete($0, $line.Length) | |
break | |
} | |
'Enter' { | |
# Clear any previous meta-information output, so that it doesn't linger and get mixed with command output. | |
try { | |
# !! On conhost.exe (regular console) windows on Windows, [Console]::CursorTop and [Console]::WindowTop are *relative to the scrollback buffer*. | |
# !! In Windows Terminal and on Unix, [Console]::WindowTop is always 0, and [Console]::CursorTop is relative to the screen height - even in the presence of a scrollback buffer. | |
Write-Host -NoNewLine (, (' ' * [Console]::WindowWidth) * ([Console]::WindowTop + [Console]::WindowHeight - [Console]::CursorTop - 1) -join "`n") | |
} | |
catch { Write-Warning "`nClearing the screen below the current line failed: $_" } # This shouldn't happen. | |
# !! Workaround for a display bug: If the cursor isn't at the very end of the line, everything to the | |
# !! right is inexplicably *erased* on submission, even though the submission itself still works fine. | |
# !! We detect that case and simply fill the entire buffer again, which leaves it drawn correctly on submission. | |
# !! (Note that [Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($line.Length) does *not* work.) | |
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref] $line, [ref] $cursor) | |
if ($cursor -ne $line.length) { | |
[Microsoft.PowerShell.PSConsoleReadLine]::Delete(0, $line.Length) | |
[Microsoft.PowerShell.PSConsoleReadLine]::Insert($line) | |
} | |
# Submit the command. | |
[Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine() | |
return # We're done. | |
} | |
Default { [Microsoft.PowerShell.PSConsoleReadLine]::Insert($key.KeyChar) } | |
} | |
# Get the updated buffer content and cursor position. | |
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref] $cursor) | |
# Note: To get the *AST* (too), use the following: | |
# $ast = $tokens = $errors = $cursor = $null | |
# [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref] $ast, [ref] $tokens, [ref] $errors, [ref] $cursor) | |
# Determine the meta-informaton to print: | |
$metaInfo = $key | Out-String | |
# !! There are TWO PROBLEMS with printing meta-information below the line being edited: | |
# !! * ON *PASTING* TEXT ONTO THE COMMAND LINE: | |
# !! * With *true pasting*, available on Windows only, with Ctrl-V, this handler isn't invoked at all. | |
# !! This isn't much of a problem, as just typing a space afterwards, for instance, will resume display of the meta-information. | |
# !! * !! With *simulated typing* - right-click on Windows, all forms of pasting on Unix - the meta-information is printed | |
# !! !! *many times, stacked*, as the keypresses are being simulated, and the cursor line *doesn't show the command being entered*. | |
# !! !! It is unclear why PSReadLine seems to lose the position of its input line and for now WE CANNOT SOLVE THIS PROBLEM. | |
# !! * IF THERE AREN'T ENOUGH LINES IN THE WINDOW BELOW THE LINE BEING EDITED TO FIT THE META-INFORMATION, | |
# !! we need to scroll up to make enough room. Otherwise every keystroke causes the cursor to stay on the last line, | |
# !! and the meta-information to print *above* it, with each keystroke's meta-info pushing the previous output upward. | |
# !! The only terminal that does *not* require this and *automatically* scrolls up is conhost.exe (regular console windows) on Windows. | |
# !! ?? Is this connected to all other terminals not being scrollback-buffer-aware (see above)? | |
# !! WE CANNOT FULLY FIX THIS PROBLEM, AS WE WOULD HAVE TO TELL PSReadLine TO GET ITS INPUT FROM A DIFFERENT LINE, | |
# !! NAMELY THE SCROLLED-UPWARD ONE; THE BEST WE CAN DO IS TO ANTICIPATE THE PROBLEM AND CLEAR THE SCREEN | |
# !! using [Microsoft.PowerShell.PSConsoleReadLine]::ClearScreen() - which is NON-DESTRUCTIVE: the scrollback | |
# !! buffer isn't cleared, and even the current screen content is merely scrolled out of view. | |
# !! By using a PSReadLine method to clear the screen, PSReadLine implicitly knows what line to prompt for input on afterwards, i.e. the first. | |
if ($env:OS -ne 'Windows_NT' -or $env:WT_SESSION) { | |
# Workaround for all terminals except conhost.exe | |
if ([Console]::CursorTop + $metaInfo.Count -gt [Console]::WindowTop + [Console]::WindowHeight) { | |
[Microsoft.PowerShell.PSConsoleReadLine]::ClearScreen() | |
} | |
} | |
# Print the desired information below the line being edited. | |
# Note: | |
# * The .PadRight() calls ensure that all lines are fully filled (padded with spaces), | |
# in order to erase potential remnants from previously displayed information. | |
# * This is NOT sufficient to deal with *varying line counts* being displayed, however. | |
Write-Host # blank line | |
Write-Host -NoNewLine -ForegroundColor Yellow ($metaInfo -split '\r?\n' | ForEach-Object PadRight ([Console]::WindowWidth-1), ' ') | |
# Set the new cursor position. | |
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment