diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index b63691eb93..58d3254d8e 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -533,6 +533,7 @@ namespace System.Management.Automation return result; } + bool bindPositionalParameters = true; if (parameterAst != null) { // Parent must be a command @@ -551,10 +552,24 @@ namespace System.Management.Automation // Parent must be a command commandAst = (CommandAst)dashAst.Parent; partialName = string.Empty; + + // If the user tries to tab complete a new parameter in front of a positional argument like: dir - C:\ + // the user may want to add the parameter name so we don't want to bind positional arguments + if (commandAst is not null) + { + foreach (var element in commandAst.CommandElements) + { + if (element.Extent.StartOffset > context.TokenAtCursor.Extent.StartOffset) + { + bindPositionalParameters = element is CommandParameterAst; + break; + } + } + } } PseudoBindingInfo pseudoBinding = new PseudoParameterBinder() - .DoPseudoParameterBinding(commandAst, null, parameterAst, PseudoParameterBinder.BindingType.ParameterCompletion); + .DoPseudoParameterBinding(commandAst, null, parameterAst, PseudoParameterBinder.BindingType.ParameterCompletion, bindPositionalParameters); // The command cannot be found or it's not a cmdlet, not a script cmdlet, not a function. // Try completing as if it the parameter is a command argument for native command completion. if (pseudoBinding == null) diff --git a/src/System.Management.Automation/engine/CommandCompletion/PseudoParameterBinder.cs b/src/System.Management.Automation/engine/CommandCompletion/PseudoParameterBinder.cs index c98be4ccac..0d5b166a5e 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/PseudoParameterBinder.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/PseudoParameterBinder.cs @@ -949,8 +949,9 @@ namespace System.Management.Automation.Language /// Indicate the type of the piped-in argument. /// The CommandParameterAst the cursor is pointing at. /// Indicates whether pseudo binding is for argument binding, argument completion, or parameter completion. + /// Indicates if the pseudo binding should bind positional parameters /// PseudoBindingInfo. - internal PseudoBindingInfo DoPseudoParameterBinding(CommandAst command, Type pipeArgumentType, CommandParameterAst paramAstAtCursor, BindingType bindingType) + internal PseudoBindingInfo DoPseudoParameterBinding(CommandAst command, Type pipeArgumentType, CommandParameterAst paramAstAtCursor, BindingType bindingType, bool bindPositional = true) { if (command == null) { @@ -1008,12 +1009,15 @@ namespace System.Management.Automation.Language unboundArguments = BindNamedParameters(); _bindingEffective = _currentParameterSetFlag != 0; - // positional binding - unboundArguments = BindPositionalParameter( - unboundArguments, - _currentParameterSetFlag, - _defaultParameterSetFlag, - bindingType); + if (bindPositional) + { + // positional binding + unboundArguments = BindPositionalParameter( + unboundArguments, + _currentParameterSetFlag, + _defaultParameterSetFlag, + bindingType); + } // VFRA/pipeline binding if the given command is a binary cmdlet or a script cmdlet if (!_function) diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index a2862f2b56..ba5cec30db 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -691,6 +691,13 @@ ConstructorTestClass(int i, bool b) $completionText -join ' ' | Should -BeExactly 'blg csv tsv' } + it 'Should include positionally bound parameters when completing in front of parameter value' { + $TestString = 'Get-ChildItem -^ $HOME' + $CursorIndex = $TestString.IndexOf('^') + $res = TabExpansion2 -inputScript $TestString.Remove($CursorIndex, 1) -cursorColumn $CursorIndex + $res.CompletionMatches.CompletionText | Should -Contain "-Path" + } + Context "Script name completion" { BeforeAll { Setup -f 'install-powershell.ps1' -Content ""