From 99c757ff3ea40d0a4310582f0ec0828f6564e6db Mon Sep 17 00:00:00 2001 From: MartinGC94 <42123497+MartinGC94@users.noreply.github.com> Date: Tue, 23 Aug 2022 00:17:55 +0200 Subject: [PATCH] Fix positional argument completion (#17796) --- .../CommandCompletion/CompletionCompleters.cs | 47 ++++++++++++------- .../TabCompletion/TabCompletion.Tests.ps1 | 21 +++++++++ 2 files changed, 52 insertions(+), 16 deletions(-) diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 1bf0919cf9..59e261e613 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -1586,13 +1586,20 @@ namespace System.Management.Automation (defaultParameterSetFlag & validParameterSetFlags) != 0; MergedCompiledCommandParameter positionalParam = null; + MergedCompiledCommandParameter bestMatchParam = null; + ParameterSetSpecificMetadata bestMatchSet = null; + + // Finds the parameter with the position closest to the specified position foreach (MergedCompiledCommandParameter param in parameters) { bool isInParameterSet = (param.Parameter.ParameterSetFlags & validParameterSetFlags) != 0 || param.Parameter.IsInAllSets; if (!isInParameterSet) + { continue; + } var parameterSetDataCollection = param.Parameter.GetMatchingParameterSetData(validParameterSetFlags); + foreach (ParameterSetSpecificMetadata parameterSetData in parameterSetDataCollection) { // in the first pass, we skip the remaining argument ones @@ -1604,35 +1611,43 @@ namespace System.Management.Automation // Check the position int positionInParameterSet = parameterSetData.Position; - if (positionInParameterSet == int.MinValue || positionInParameterSet != position) + if (positionInParameterSet < position) { - // The parameter is not positional, or its position is not what we want + // The parameter is not positional (position == int.MinValue), or its position is lower than what we want. continue; } - if (isDefaultParameterSetValid) + if (bestMatchSet is null || bestMatchSet.Position > positionInParameterSet) { - if (parameterSetData.ParameterSetFlag == defaultParameterSetFlag) + bestMatchParam = param; + bestMatchSet = parameterSetData; + if (positionInParameterSet == position) { - ProcessParameter(commandName, commandAst, context, result, param, boundArguments); - isProcessedAsPositional = result.Count > 0; break; } - else - { - positionalParam ??= param; - } + } + } + } + + if (bestMatchParam is not null) + { + if (isDefaultParameterSetValid) + { + if (bestMatchSet.ParameterSetFlag == defaultParameterSetFlag) + { + ProcessParameter(commandName, commandAst, context, result, bestMatchParam, boundArguments); + isProcessedAsPositional = result.Count > 0; } else { - isProcessedAsPositional = true; - ProcessParameter(commandName, commandAst, context, result, param, boundArguments); - break; + positionalParam ??= bestMatchParam; } } - - if (isProcessedAsPositional) - break; + else + { + isProcessedAsPositional = true; + ProcessParameter(commandName, commandAst, context, result, bestMatchParam, boundArguments); + } } if (!isProcessedAsPositional && positionalParam != null) diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index 6471b1bf0a..9ab7d735ee 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -766,6 +766,27 @@ ConstructorTestClass(int i, bool b) $res.CompletionMatches.CompletionText | Should -Contain "-Path" } + it 'Should find the closest positional parameter match' { + $TestString = @' +function Verb-Noun +{ + Param + ( + [Parameter(Position = 0)] + [string] + $Param1, + [Parameter(Position = 1)] + [System.Management.Automation.ActionPreference] + $Param2 + ) +} +Verb-Noun -Param1 Hello ^ +'@ + $CursorIndex = $TestString.IndexOf('^') + $res = TabExpansion2 -inputScript $TestString.Remove($CursorIndex, 1) -cursorColumn $CursorIndex + $res.CompletionMatches[0].CompletionText | Should -Be "Break" + } + Context "Script name completion" { BeforeAll { Setup -f 'install-powershell.ps1' -Content ""