Adding Double-Tap Tab Completion to PowerShell
One of the gestures that becomes very ingrained when working in a Unix shell is double-tap tab completion. When you press tab at a normal slow speed, the shell cycles through available tab completion possibilities. When you press tab quickly twice in a row, the shell displays all possible completions for your command all at once – and then lets you cycle through those possibilities.
That’s not a feature that PowerShell directly supports, but it is possible to get a good approximation to it by customizing your own TabExpansion function. The following script demonstrates a TabExpansion function that illustrates a framework for this. It is terrible as a stand-alone tab completion function (since it only tab completes on filenames – and poorly, at that,) but demonstrates one approach to getting double-tap tab completion in PowerShell.
It comes it two parts:
1) A TabExpansion function, that populates the area just above your prompt with suggestions if you press ‘Tab’ twice quickly.
function TabExpansion([string] $line, [string] $lastword)
{
## Delay for a bit to see if they've pressed a key again
Start-Sleep -m 200
if($host.UI.RawUI.KeyAvailable)
{
## Get the list of items to be returned in tab completion. This
## is just an example.
$items = Get-ChildItem $lastWord
## Convert those items into a wide string format so that we can display
## it concisely
$content = $items | Format-Wide | Out-String
$contentLines = $content.Replace("`r","").Split("`n")
$contentHeight = $contentLines.Length + 2
$contentWidth = $host.UI.RawUI.BufferSize.Width
## If there are more than 100 items (the default in
## Unix shells,) it would be courteous to prompt the user to find out if
## they actually want to display all of the tab completion information.
if($contentLines.Length -gt 100)
{
return
}
## Create a buffer cell array to hold the string content we plan to
## put in the console buffer
$savedCells =
$replacementCells = $host.UI.RawUI.NewBufferCellArray(
$contentLines,$host.UI.RawUI.ForegroundColor,
$host.UI.RawUI.BackgroundColor
)
## Figure out where to put the new bits of buffer
$coordinates = $host.UI.RawUI.CursorPosition
$coordinates.Y -= $contentHeight
$coordinates.X = 0
## Bail if we can't fit the replacement content above our prompt
if($coordinates.Y -le 0)
{
return
}
## Create the rectangle that determines which of the old buffer cells
## we want to save
$rectangle = New-Object System.Management.Automation.Host.Rectangle
$rectangle.Left = 0
$rectangle.Right = $contentWidth
$rectangle.Top = $coordinates.Y
$rectangle.Bottom = $coordinates.Y + $contentHeight
## Save the old buffer contents (and their coordinates) into a global
## variable so that the next prompt can fix it
${GLOBAL:Lee.Holmes.SavedBufferContents} =
$host.UI.RawUI.GetBufferContents($rectangle)
${GLOBAL:Lee.Holmes.SavedBufferCoordinates} = $coordinates
## Put our double-tap tab completion information in an area above the prompt
$host.UI.RawUI.SetBufferContents($coordinates, $replacementCells)
}
}
2) A few additional lines in your prompt function to clean up any double-tap output at the next prompt.
## Restore tab completion content
if(Test-Path Variable:\Lee.Holmes.SavedBufferContents)
{
$host.UI.RawUI.SetBufferContents(
${GLOBAL:Lee.Holmes.SavedBufferCoordinates},
${GLOBAL:Lee.Holmes.SavedBufferContents}
)
${GLOBAL:Lee.Holmes.SavedBufferContents} = $null
${GLOBAL:Lee.Holmes.SavedBufferCoordinates} = $null
}