diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 415471362c..11fae48b2f 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -1993,16 +1993,27 @@ namespace System.Management.Automation CompletionContext context, Dictionary boundArguments = null) { - if (string.IsNullOrEmpty(commandName)) + string parameterName = parameter.Name; + + // Fall back to the commandAst command name if a command name is not found. This can be caused by a script block or AST with the matching function definition being passed to CompleteInput + // This allows for editors and other tools using CompleteInput with Script/AST definations to get values from RegisteredArgumentCompleters to better match the console experience. + // See issue https://github.com/PowerShell/PowerShell/issues/10567 + string actualCommandName = string.IsNullOrEmpty(commandName) + ? commandAst.GetCommandName() + : commandName; + + if (string.IsNullOrEmpty(actualCommandName)) { return; } - var parameterName = parameter.Name; - var customCompleter = GetCustomArgumentCompleter( - "CustomArgumentCompleters", - new[] { commandName + ":" + parameterName, parameterName }, - context); + string parameterFullName = $"{actualCommandName}:{parameterName}"; + + ScriptBlock customCompleter = GetCustomArgumentCompleter( + "CustomArgumentCompleters", + new[] { parameterFullName, parameterName }, + context); + if (customCompleter != null) { if (InvokeScriptArgumentCompleter( diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index 7285798830..de231bd8e8 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -836,6 +836,32 @@ Describe "TabCompletion" -Tags CI { $res.CompletionMatches[1].CompletionText | Should -BeExactly 'dog' } + It "Tab completion for ArgumentCompleter when AST is passed to CompleteInput" { + $scriptBl = { + function Test-Completion { + param ( + [String]$TestVal + ) + } + [scriptblock]$completer = { + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) + + @('Val1', 'Val2') + } + Register-ArgumentCompleter -CommandName Test-Completion -ParameterName TestVal -ScriptBlock $completer + } + $pwsh = [PowerShell]::Create() + $pwsh.AddScript($scriptBl) + $pwsh.Invoke() + + $completeInput_Input = $scriptBl.ToString() + $completeInput_Input += "`nTest-Completion -TestVal " + $res = [System.Management.Automation.CommandCompletion]::CompleteInput($completeInput_Input, $completeInput_Input.Length, $null, $pwsh) + $res.CompletionMatches | Should -HaveCount 2 + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'Val1' + $res.CompletionMatches[1].CompletionText | Should -BeExactly 'Val2' + } + It "Tab completion for enum type parameter of a custom function" { function baz ([consolecolor]$name, [ValidateSet('cat','dog')]$p){} $inputStr = "baz -name "