diff --git a/news/12440.feature.rst b/news/12440.feature.rst new file mode 100644 index 00000000000..dc4f7979187 --- /dev/null +++ b/news/12440.feature.rst @@ -0,0 +1 @@ +Fix PowerShell completion by switching to Register-ArgumentCompleter (Closes #12440) diff --git a/src/pip/_internal/commands/completion.py b/src/pip/_internal/commands/completion.py index 6d9597bdea0..1ece58aaae5 100644 --- a/src/pip/_internal/commands/completion.py +++ b/src/pip/_internal/commands/completion.py @@ -53,24 +53,37 @@ complete -fa "(__fish_complete_pip)" -c {prog} """, "powershell": """ - 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() - if ($lastBlock.StartsWith("{prog} ")) {{ - $Env:COMP_WORDS=$lastBlock - $Env:COMP_CWORD=$lastBlock.Split().Length - 1 - $Env:PIP_AUTO_COMPLETE=1 - (& {prog}).Split() - Remove-Item Env:COMP_WORDS - Remove-Item Env:COMP_CWORD - Remove-Item Env:PIP_AUTO_COMPLETE - }} - elseif (Test-Path Function:\\_pip_completeBackup) {{ - # Fall back on existing tab expansion - _pip_completeBackup $line $lastWord + Register-ArgumentCompleter -Native -CommandName '{prog}' -ScriptBlock {{ + param($commandName, $commandAst, $cursorPosition) + $commandElements = $commandAst.CommandElements + $command = @( + '{prog}' + for ($i = 1; $i -lt $commandElements.Count; $i++) {{ + $element = $commandElements[$i] + if ($element -isnot [StringConstantExpressionAst] -or + $element.StringConstantType -ne` + [StringConstantType]::BareWord -or + $element.Value.StartsWith('-')) {{ + break + }} + $element.Value + }} + ) -join ' ' + + $Env:COMP_WORDS = $command + $Env:COMP_CWORD = $commandElements.Count - 1 + $Env:PIP_AUTO_COMPLETE = 1 + $completions = & {prog} 2>$null + Remove-Item Env:COMP_WORDS + Remove-Item Env:COMP_CWORD + Remove-Item Env:PIP_AUTO_COMPLETE + + if ($completions) {{ + $completions.Split() | ForEach-Object {{ + [System.Management.Automation.CompletionResult]::new( + $_, $_, 'ParameterValue', $_ + ) + }} }} }} """, diff --git a/tests/functional/test_completion.py b/tests/functional/test_completion.py index f0396aa0c68..45f968a6f93 100644 --- a/tests/functional/test_completion.py +++ b/tests/functional/test_completion.py @@ -60,26 +60,42 @@ ( "powershell", """\ -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() - if ($lastBlock.StartsWith("pip ")) { - $Env:COMP_WORDS=$lastBlock - $Env:COMP_CWORD=$lastBlock.Split().Length - 1 - $Env:PIP_AUTO_COMPLETE=1 - (& pip).Split() - Remove-Item Env:COMP_WORDS - Remove-Item Env:COMP_CWORD - Remove-Item Env:PIP_AUTO_COMPLETE - } - elseif (Test-Path Function:\\_pip_completeBackup) { - # Fall back on existing tab expansion - _pip_completeBackup $line $lastWord +# pip powershell completion start +Register-ArgumentCompleter -Native -CommandName 'pip' -ScriptBlock { + param($commandName, $commandAst, $cursorPosition) + $commandElements = $commandAst.CommandElements + $command = @( + 'pip' + for ($i = 1; $i -lt $commandElements.Count; $i++) { + $element = $commandElements[$i] + if ($element -isnot [StringConstantExpressionAst] -or + $element.StringConstantType -ne` + [StringConstantType]::BareWord -or + $element.Value.StartsWith('-')) { + break + } + $element.Value + } + ) -join ' ' + + $Env:COMP_WORDS = $command + $Env:COMP_CWORD = $commandElements.Count - 1 + $Env:PIP_AUTO_COMPLETE = 1 + $completions = & pip 2>$null + Remove-Item Env:COMP_WORDS + Remove-Item Env:COMP_CWORD + Remove-Item Env:PIP_AUTO_COMPLETE + + if ($completions) { + $completions.Split() | ForEach-Object { + [System.Management.Automation.CompletionResult]::new( + $_, $_, 'ParameterValue', $_ + ) + } } -}""", +} +# pip powershell completion end +""", ), )