diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs index 759b60ea0c1..84bb275d41c 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs @@ -327,6 +327,7 @@ internal List GetResultHelper(CompletionContext completionCont case TokenKind.Multiply: case TokenKind.Generic: + case TokenKind.MinusMinus: // for native commands '--' case TokenKind.Identifier: result = GetResultForIdentifier(completionContext, ref replacementIndex, ref replacementLength, isQuotedString); break; @@ -1086,7 +1087,7 @@ private List GetResultForEnumPropertyValueOfDSCResource( { if (!String.IsNullOrEmpty(dynamicKeywordAst.ElementName)) { - StringBuilder sb = new StringBuilder("["); + StringBuilder sb = new StringBuilder("[", 50); sb.Append(dynamicKeywordAst.Keyword.Keyword); sb.Append("]"); sb.Append(dynamicKeywordAst.ElementName); @@ -1353,7 +1354,8 @@ private List GetResultForIdentifier(CompletionContext completi var lastAst = completionContext.RelatedAsts.Last(); List result = null; - completionContext.WordToComplete = tokenAtCursor.Text; + var tokenAtCursorText = tokenAtCursor.Text; + completionContext.WordToComplete = tokenAtCursorText; var strConst = lastAst as StringConstantExpressionAst; if (strConst != null) @@ -1419,7 +1421,7 @@ private List GetResultForIdentifier(CompletionContext completi { Ast cursorAst = null; var cursorPosition = (InternalScriptPosition)_cursorPosition; - int offsetBeforeCmdName = cursorPosition.Offset - tokenAtCursor.Text.Length; + int offsetBeforeCmdName = cursorPosition.Offset - tokenAtCursorText.Length; if (offsetBeforeCmdName >= 0) { var cursorBeforeCmdName = cursorPosition.CloneWithNewOffset(offsetBeforeCmdName); @@ -1431,10 +1433,10 @@ private List GetResultForIdentifier(CompletionContext completi cursorAst.Extent.EndLineNumber == tokenAtCursor.Extent.StartLineNumber && cursorAst.Extent.EndColumnNumber == tokenAtCursor.Extent.StartColumnNumber) { - if (tokenAtCursor.Text.IndexOfAny(Utils.Separators.Directory) == 0) + if (tokenAtCursorText.IndexOfAny(Utils.Separators.Directory) == 0) { string wordToComplete = - CompletionCompleters.ConcatenateStringPathArguments(cursorAst as CommandElementAst, tokenAtCursor.Text, completionContext); + CompletionCompleters.ConcatenateStringPathArguments(cursorAst as CommandElementAst, tokenAtCursorText, completionContext); if (wordToComplete != null) { completionContext.WordToComplete = wordToComplete; @@ -1452,7 +1454,7 @@ private List GetResultForIdentifier(CompletionContext completi string fullPath = variableAst != null ? CompletionCompleters.CombineVariableWithPartialPath( variableAst: variableAst, - extraText: tokenAtCursor.Text, + extraText: tokenAtCursorText, executionContext: completionContext.ExecutionContext) : null; @@ -1538,16 +1540,27 @@ private List GetResultForIdentifier(CompletionContext completi return result; } - if (tokenAtCursor.Text.Length == 1 && tokenAtCursor.Text[0].IsDash() && (lastAst.Parent is CommandAst || lastAst.Parent is DynamicKeywordStatementAst)) + var isSingleDash = tokenAtCursorText.Length == 1 && tokenAtCursorText[0].IsDash(); + var isDoubleDash = tokenAtCursorText.Length == 2 && tokenAtCursorText[0].IsDash() && tokenAtCursorText[1].IsDash(); + var isParentCommandOrDynamicKeyword = (lastAst.Parent is CommandAst || lastAst.Parent is DynamicKeywordStatementAst); + if ((isSingleDash || isDoubleDash) && isParentCommandOrDynamicKeyword) { // When it's the content of a quoted string, we only handle variable/member completion - if (isQuotedString) { return result; } - return CompletionCompleters.CompleteCommandParameter(completionContext); + if (isSingleDash) + { + if (isQuotedString) { return result; } + var res = CompletionCompleters.CompleteCommandParameter(completionContext); + if (res.Count != 0) + { + return res; + } + } + return CompletionCompleters.CompleteCommandArgument(completionContext); } TokenKind memberOperator = TokenKind.Unknown; bool isMemberCompletion = (lastAst.Parent is MemberExpressionAst); - bool isStatic = isMemberCompletion ? ((MemberExpressionAst)lastAst.Parent).Static : false; + bool isStatic = isMemberCompletion && ((MemberExpressionAst)lastAst.Parent).Static; bool isWildcard = false; if (!isMemberCompletion) @@ -1556,12 +1569,12 @@ private List GetResultForIdentifier(CompletionContext completi // We need to know if the previous element before the token is adjacent because // we don't have a MemberExpressionAst, we might have 2 command arguments. - if (tokenAtCursor.Text.Equals(TokenKind.Dot.Text(), StringComparison.Ordinal)) + if (tokenAtCursorText.Equals(TokenKind.Dot.Text(), StringComparison.Ordinal)) { memberOperator = TokenKind.Dot; isMemberCompletion = true; } - else if (tokenAtCursor.Text.Equals(TokenKind.ColonColon.Text(), StringComparison.Ordinal)) + else if (tokenAtCursorText.Equals(TokenKind.ColonColon.Text(), StringComparison.Ordinal)) { memberOperator = TokenKind.ColonColon; isMemberCompletion = true; @@ -1607,7 +1620,7 @@ private List GetResultForIdentifier(CompletionContext completi { if (!isWildcard && memberOperator != TokenKind.Unknown) { - replacementIndex += tokenAtCursor.Text.Length; + replacementIndex += tokenAtCursorText.Length; replacementLength = 0; } return result; @@ -1637,7 +1650,7 @@ private List GetResultForIdentifier(CompletionContext completi completionContext.WordToComplete = wordToComplete; } } - else if (tokenAtCursor.Text.IndexOfAny(Utils.Separators.Directory) == 0) + else if (tokenAtCursorText.IndexOfAny(Utils.Separators.Directory) == 0) { var command = lastAst.Parent as CommandBaseAst; if (command != null && command.Redirections.Any()) @@ -1648,7 +1661,7 @@ private List GetResultForIdentifier(CompletionContext completi fileRedirection.Extent.EndColumnNumber == lastAst.Extent.StartColumnNumber) { string wordToComplete = - CompletionCompleters.ConcatenateStringPathArguments(fileRedirection.Location, tokenAtCursor.Text, completionContext); + CompletionCompleters.ConcatenateStringPathArguments(fileRedirection.Location, tokenAtCursorText, completionContext); if (wordToComplete != null) { diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index dd164aee76a..6fe7f0ea859 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -29,7 +29,6 @@ Describe "TabCompletion" -Tags CI { $res.CompletionMatches[0].CompletionText | Should be 'System' } - It 'Should complete format-table hashtable' { $res = TabExpansion2 -inputScript 'Get-ChildItem | Format-Table @{ ' -cursorColumn 'Get-ChildItem | Format-Table @{ '.Length $res.CompletionMatches.Count | Should Be 5 @@ -101,6 +100,75 @@ Describe "TabCompletion" -Tags CI { It 'Should complete keyword' -skip { $res = TabExpansion2 -inputScript 'using nam' -cursorColumn 'using nam'.Length - $res.CompletionMatches[0].CompletionText | Should be 'namespace' + $res.CompletionMatches[0].CompletionText | Should Be 'namespace' + } + + Context NativeCommand { + BeforeAll { + $nativeCommand = (Get-Command -CommandType Application -TotalCount 1).Name + } + It 'Completes native commands with -' { + Register-ArgumentCompleter -Native -CommandName $nativeCommand -ScriptBlock { + param($wordToComplete, $ast, $cursorColumn) + if ($wordToComplete -eq '-') { + return "-flag" + } + else { + return "unexpected wordtocomplete" + } + } + $line = "$nativeCommand -" + $res = TabExpansion2 -inputScript $line -cursorColumn $line.Length + $res.CompletionMatches.Count | Should Be 1 + $res.CompletionMatches.CompletionText | Should Be "-flag" + } + + It 'Completes native commands with --' { + Register-ArgumentCompleter -Native -CommandName $nativeCommand -ScriptBlock { + param($wordToComplete, $ast, $cursorColumn) + if ($wordToComplete -eq '--') { + return "--flag" + } + else { + return "unexpected wordtocomplete" + } + } + $line = "$nativeCommand --" + $res = TabExpansion2 -inputScript $line -cursorColumn $line.Length + $res.CompletionMatches.Count | Should Be 1 + $res.CompletionMatches.CompletionText | Should Be "--flag" + } + + It 'Completes native commands with --f' { + Register-ArgumentCompleter -Native -CommandName $nativeCommand -ScriptBlock { + param($wordToComplete, $ast, $cursorColumn) + if ($wordToComplete -eq '--f') { + return "--flag" + } + else { + return "unexpected wordtocomplete" + } + } + $line = "$nativeCommand --f" + $res = TaBexpansion2 -inputScript $line -cursorColumn $line.Length + $res.CompletionMatches.Count | Should Be 1 + $res.CompletionMatches.CompletionText | Should Be "--flag" + } + + It 'Completes native commands with -o' { + Register-ArgumentCompleter -Native -CommandName $nativeCommand -ScriptBlock { + param($wordToComplete, $ast, $cursorColumn) + if ($wordToComplete -eq '-o') { + return "-option" + } + else { + return "unexpected wordtocomplete" + } + } + $line = "$nativeCommand -o" + $res = TaBexpansion2 -inputScript $line -cursorColumn $line.Length + $res.CompletionMatches.Count | Should Be 1 + $res.CompletionMatches.CompletionText | Should Be "-option" + } } }