Completions registered with `pip completion` don't work when pip is invoked differently
Description
The pip completion command is supposed to create a script that will register completions for pip. However, the command does not take into account the fact that pip can be invoked in many ways - pip, pip.pyz, py -m pip, python -m pip, python3 -m pip.
The code appears to pick just the form the user chose to invoke pip completion and register for that version. And worse, it expands parts of the command (so that, for example, the absolute path of the Python interpreter is hard coded, and on Windows invoking pip.pyz using just the name pip registers completion for pip.pyz, not pip).
Completion should work regardless of how pip is invoked. If shell completion capabilities are too limited for that to be a viable option, we should register completions for enough invocation forms to cover all common usage, and we should issue a warning (somehow - it's not clear to me where such a warning could easily be issued without being too intrusive) or include a clear note in the documentation to let the user know that completions may not always work.
Expected behavior
No response
pip version
25.2
Python version
3.13
OS
Windows (and Unix)
How to Reproduce
Run pip completion --powershell or pip completion --bash and inspect the output.
Output
This is on Windows, but I use pip complete --bash, because it's easier to see the command that completion is registered for in that version. The Powershel completion script has the same bug.
❯ pip completion --bash
# pip bash completion start
_pip_completion()
{
COMPREPLY=( $( COMP_WORDS="${COMP_WORDS[*]}" \
COMP_CWORD=$COMP_CWORD \
PIP_AUTO_COMPLETE=1 $1 2>/dev/null ) )
}
complete -o default -F _pip_completion pip.pyz
# pip bash completion end
Note the penultimate line, which appears to register a completion for the command pip.pyz, not pip. And there's nothing for py -m pip.
Code of Conduct
- [x] I agree to follow the PSF Code of Conduct.
Hi @pfmoore, I’ve been experimenting with the PowerShell tab completion for pip and put together a draft script. I’m not sure if it covers all the cases you might want, so I’d really appreciate it if you could try it on your Windows machine and share any feedback.
# pip powershell completion start
if ((Test-Path Function:\TabExpansion) -and -not `
(Test-Path Function:\_pip_completeBackup)) {
Rename-Item Function:\TabExpansion _pip_completeBackup
}
function TabExpansion($line, $lastWord) {
$lastBlock = [regex]::Split($line, '[|;]')[-1].TrimStart()
$pipPatterns = @(
'^pip\b',
'^python\s+-m\s+pip\b',
'^python3\s+-m\s+pip\b',
'^py\s+-m\s+pip\b'
)
foreach ($pattern in $pipPatterns) {
if ($lastBlock -match $pattern) {
$Env:COMP_WORDS = $lastBlock
$Env:COMP_CWORD = ($lastBlock -split '\s+').Count - 1
$Env:PIP_AUTO_COMPLETE = 1
try {
$completionResult = Invoke-Expression $lastBlock
if ($completionResult) { $completionResult -split '\s+' }
} catch {
}
Remove-Item Env:COMP_WORDS
Remove-Item Env:COMP_CWORD
Remove-Item Env:PIP_AUTO_COMPLETE
return
}
}
if (Test-Path Function:\_pip_completeBackup) {
_pip_completeBackup $line $lastWord
}
}
# pip powershell completion end
My understanding is that the TabExpansion function is obsolete, replaced by TabExpansion2. But completion should be done via the Register-ArgumentCompleter function in any case, not by overriding the tab expansion function.
Sorry, I don't have time to try it right now, but I'm pretty certain it won't work for me (Powershell Core 7.5.3) because of this. What version of Powershell are you testing with, that it works under?
My understanding is that the
TabExpansionfunction is obsolete, replaced byTabExpansion2. But completion should be done via theRegister-ArgumentCompleterfunction in any case, not by overriding the tab expansion function.
Here's an updated version in case you want to test it with Register-ArgumentCompleter:
# pip powershell completion start
Register-ArgumentCompleter -Native -CommandName pip,python,python3,py -ScriptBlock {
param($wordToComplete, $commandAst, $cursorPosition)
$line = $commandAst.ToString()
$lastBlock = [regex]::Split($line, '[|;]')[-1].TrimStart()
$pipPatterns = @(
'^pip\b',
'^python\s+-m\s+pip\b',
'^python3\s+-m\s+pip\b',
'^py\s+-m\s+pip\b'
)
foreach ($pattern in $pipPatterns) {
if ($lastBlock -match $pattern) {
$Env:COMP_WORDS = $lastBlock
$Env:COMP_CWORD = ($lastBlock -split '\s+').Count - 1
$Env:PIP_AUTO_COMPLETE = 1
try {
$completionResult = Invoke-Expression $lastBlock
if ($completionResult) {
foreach ($c in $completionResult -split '\s+') {
[System.Management.Automation.CompletionResult]::new($c, $c, 'ParameterValue', $c)
}
}
} catch {
} finally {
Remove-Item Env:COMP_WORDS -ErrorAction SilentlyContinue
Remove-Item Env:COMP_CWORD -ErrorAction SilentlyContinue
Remove-Item Env:PIP_AUTO_COMPLETE -ErrorAction SilentlyContinue
}
}
}
}
# pip powershell completion end
What version of Powershell are you testing with, that it works under?
I first tested this with Powershell 5.1, but I downloaded Powershell Core 7.1 and it works there too.
Thanks. I just realised that I can just cut and paste it into my shell, it doesn't need me to build from the PR.
It seems to work, although it's too slow to be worth using on my (reasonably decent) PC. That's likely to be just because invoking a pip process every time you do tab completion is slow - I don't think it's the right way to do it. But that's the design of the existing code, so I guess it's a separate issue.
I'll also note that the issue I reported here isn't just on Windows, it applies on Unix too (and indeed, the example I gave was using the bash completion script).
I'll also note that the issue I reported here isn't just on Windows, it applies on Unix too (and indeed, the example I gave was using the bash completion script).
I first updated the PowerShell code to get your approval before touching the other shells. I used ChatGPT to draft these changes, but I’ll be doing more testing before making a final PR. Here are the updated scripts for bash, zsh, and fish:
bash:
# pip bash completion start
_pip_completion()
{
local cmd="${COMP_WORDS[0]}"
case "$cmd" in
pip|python|python3|py)
COMPREPLY=( $( COMP_WORDS="${COMP_WORDS[*]}" \
COMP_CWORD=$COMP_CWORD \
PIP_AUTO_COMPLETE=1 $cmd 2>/dev/null ) )
;;
esac
}
complete -o default -F _pip_completion pip
complete -o default -F _pip_completion python
complete -o default -F _pip_completion python3
complete -o default -F _pip_completion py
# pip bash completion end
zsh:
# pip zsh completion start
#compdef pip python python3 py
__pip() {
local cmd=$words[1]
case "$cmd" in
pip|python|python3|py)
compadd $( COMP_WORDS="$words[*]" \
COMP_CWORD=$((CURRENT-1)) \
PIP_AUTO_COMPLETE=1 $cmd 2>/dev/null )
;;
esac
}
if [[ $zsh_eval_context[-1] == loadautofunc ]]; then
__pip "$@"
else
compdef __pip pip python python3 py
fi
# pip zsh completion end
fish:
# pip fish completion start
function __fish_complete_pip
set -lx COMP_WORDS (commandline --current-process --tokenize --cut-at-cursor)
set -lx COMP_CWORD (math (count $COMP_WORDS) - 1)
set -lx PIP_AUTO_COMPLETE 1
switch $COMP_WORDS[1]
case pip python python3 py
eval $COMP_WORDS[1]
end
end
complete -fa "(__fish_complete_pip)" -c pip
complete -fa "(__fish_complete_pip)" -c python
complete -fa "(__fish_complete_pip)" -c python3
complete -fa "(__fish_complete_pip)" -c py
# pip fish completion end
If this looks good overall, I’d be happy to open a PR to fix it properly. Thanks again for your detailed feedback!
Thanks. I'm afraid I won't do much (if anything) more on this topic. Because of the performance issue I mentioned above, I have no interest in, or use for, the completion feature. So I'll leave it to someone else more motivated to review contributions here.