PowerShell with argcomplete
Introduction
argcomplete is a great Python package for
tab completion, however, it only works with bash as your shell (and with zsh, fish and
tcsh with limited support). PowerShell
has also a great command-line interface, but the argcomplete-based applications cannot
use its tab completion functionality.
This project provides a straightforward solution how to use argcomplete tab completion
functionality in PowerShell.
Content
psamwe.pyExample script withargcomplete.psamwe.complete.ps1Script to registerpsamwecommand for tab completion.psamwe.complete.psm1Module to registerpsamwecommand for tab completion.
How to use the example?
- Start a new PowerShell window.
- Clone this project.
- Create and activate your Python virtual environment as you like.
- Enter into this project's directory (
cd powershell-argcomplete-mwe) pip install --editable .- Activate PowerShell tab completion with either of the scripts:
- Dot-sourcing:
. .\psamwe.complete.ps1 - Import module:
Import-Module .\psamwe.complete.psm1
- Dot-sourcing:
- Type
psamwe - Play with
TaborCtrl+Spaceauto-completion ofpsamwe
Background
Register-ArgumentCompleter (link)
"registers a custom argument completer. An argument completer allows you to provide
dynamic tab completion, at run time for any command that you specify." The main idea
is that using this cmdlete we register a script to complete arguments of an alias
function which wraps the original command:
- A PowerShell function is defined which is a simple alias of the original command:
$PsamwePythonCommand = "&'python .\psamwe.py'"
$PsamweCommandAlias = "psamwe"
Function psamwe {
Invoke-Expression "$PsamwePythonCommand $args"
}- Using
Register-ArgumentCompleter, the command alias is registered for argument completion:
Register-ArgumentCompleter -Native -CommandName $PsamweCommandAlias -ScriptBlock $PsamweArgCompleteScriptBlock$PsamweArgCompleteScriptBlock script block is called by activating argument completion
(by pressing Tab or Ctrl+Space)
$PsamweArgCompleteScriptBlockscript block mimics bash by setting up special environment variables on a similiar way:
New-Item -Path Env: -Name _ARGCOMPLETE -Value 1 | Out-Null # Enables tab completion in argcomplete
New-Item -Path Env: -Name COMP_TYPE -Value 9 | Out-Null # Constant
New-Item -Path Env: -Name _ARGCOMPLETE_IFS -Value " " | Out-Null # Separator of the items
New-Item -Path Env: -Name _ARGCOMPLETE_SUPPRESS_SPACE -Value 1 | Out-Null # Constant
New-Item -Path Env: -Name _ARGCOMPLETE_COMP_WORDBREAKS -Value "" | Out-Null # Constant
New-Item -Path Env: -Name COMP_POINT -Value $cursorPosition | Out-Null # Refers to the last character of the current line
New-Item -Path Env: -Name COMP_LINE -Value $line | Out-Null # Current lineargcomplete uses these variables as input to determine the completion suggestions.
argcompletewrites the result into an output stream. Per default, it uses file descriptor8which is not supported by Powershell. Instead,psamwe.pyscripts changes the output stream tostdout. However, this change would break the bash experience. Therefore, a new environment variable has been introduced to specify that the completion is triggered by PowerShell:
New-Item -Path Env: -Name _ARGCOMPLETE_POWERSHELL -Value 1 | Out-NullThis environment variable is used in psamwe.py:
output_stream = None
if "_ARGCOMPLETE_POWERSHELL" in os.environ:
output_stream = codecs.getwriter("utf-8")(sys.stdout.buffer)
argcomplete.autocomplete(parser, output_stream=output_stream)- Finally, the script just executes the original command. Due to the set environment
variables,
argcompletewill execute theautocompletefunction and writes the result back tostdoutwhich is redirected into a variable. Splitting this result into separated lines are the results of argument completion which are presented by the command-line window to the user:
Invoke-Expression $PsamwePythonCommand -OutVariable completionResult -ErrorVariable errorOut -ErrorAction SilentlyContinue | Out-Null
...
$items = $completionResult.Split()
if ($items -eq $completionResult) {
"$items "
}
else {
$items
}- There are a few minor tricks in the pre- and post-processing parts to mimic the bash experience as much as possible. These can be freely modified to adapt the behaviour to specific needs.