pip icon indicating copy to clipboard operation
pip copied to clipboard

Completions registered with `pip completion` don't work when pip is invoked differently

Open pfmoore opened this issue 3 months ago • 6 comments

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

pfmoore avatar Sep 23 '25 09:09 pfmoore

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

sepehr-rs avatar Sep 29 '25 07:09 sepehr-rs

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?

pfmoore avatar Sep 29 '25 09:09 pfmoore

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.

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.

sepehr-rs avatar Sep 29 '25 10:09 sepehr-rs

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).

pfmoore avatar Sep 29 '25 12:09 pfmoore

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!

sepehr-rs avatar Sep 29 '25 13:09 sepehr-rs

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.

pfmoore avatar Sep 29 '25 13:09 pfmoore