From da8dd28942422ffa274265b04935f91f5f683868 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Wed, 21 Jun 2017 11:43:06 -0700 Subject: [PATCH] Refactor CompletionCompleters.cs to avoid constructing unnecessary CompletionContext instances (#4048) --- .../CommandCompletion/CompletionCompleters.cs | 214 ++++++++++-------- 1 file changed, 122 insertions(+), 92 deletions(-) diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index bbedae5498..6ce9ab1750 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -76,7 +76,8 @@ namespace System.Management.Automation } var helper = new PowerShellExecutionHelper(PowerShell.Create(RunspaceMode.CurrentRunspace)); - return CompleteCommand(new CompletionContext { WordToComplete = commandName, Helper = helper, ExecutionContext = helper.CurrentPowerShell.GetContextFromTLS() }, moduleName, commandTypes); + var executionContext = helper.CurrentPowerShell.Runspace.ExecutionContext; + return CompleteCommand(new CompletionContext { WordToComplete = commandName, Helper = helper, ExecutionContext = executionContext }, moduleName, commandTypes); } internal static List CompleteCommand(CompletionContext context) @@ -118,7 +119,7 @@ namespace System.Management.Automation var commandInfos = context.Helper.ExecuteCurrentPowerShell(out exceptionThrown); // Complete against pseudo commands that work only in the script workflow. - // It's for argument completion when RelatedAst is null, don't complete pseudo commands for arguments + // If RelatedAst is null then it's for argument completion, because we don't complete pseudo commands for arguments if (lastAst != null) { commandInfos = CompleteWorkflowCommand(commandName, lastAst, commandInfos); @@ -128,11 +129,11 @@ namespace System.Management.Automation { // OrderBy is using stable sorting var sortedCommandInfos = commandInfos.OrderBy(a => a, new CommandNameComparer()); - commandResults = MakeCommandsUnique(sortedCommandInfos, false, addAmpersandIfNecessary, quote, context); + commandResults = MakeCommandsUnique(sortedCommandInfos, /* includeModulePrefix: */ false, addAmpersandIfNecessary, quote); } else { - commandResults = MakeCommandsUnique(commandInfos, false, addAmpersandIfNecessary, quote, context); + commandResults = MakeCommandsUnique(commandInfos, /* includeModulePrefix: */ false, addAmpersandIfNecessary, quote); } if (lastAst != null) @@ -184,11 +185,11 @@ namespace System.Management.Automation if (commandInfos != null && commandInfos.Count > 1) { var sortedCommandInfos = commandInfos.OrderBy(a => a, new CommandNameComparer()); - commandResults = MakeCommandsUnique(sortedCommandInfos, true, addAmpersandIfNecessary, quote, context); + commandResults = MakeCommandsUnique(sortedCommandInfos, /* includeModulePrefix: */ true, addAmpersandIfNecessary, quote); } else { - commandResults = MakeCommandsUnique(commandInfos, true, addAmpersandIfNecessary, quote, context); + commandResults = MakeCommandsUnique(commandInfos, /* includeModulePrefix: */ true, addAmpersandIfNecessary, quote); } } } @@ -196,6 +197,21 @@ namespace System.Management.Automation return commandResults; } + /// + /// Construct a new instance of CompletionContext with RelatedAst = null. + /// For argument completion, we don't want to complete against pseudo commands that only work in the script workflow. + /// The way to avoid that is to pass in a CompletionContext with RelatedAst = null. + /// + private static CompletionContext NewContextToCompleteCommandAsArgument(CompletionContext context) + { + return new CompletionContext { + WordToComplete = context.WordToComplete, + Helper = context.Helper, + ExecutionContext = context.ExecutionContext, + Options = context.Options + }; + } + private static readonly HashSet s_keywordsToExcludeFromAddingAmpersand = new HashSet(StringComparer.OrdinalIgnoreCase) { TokenKind.InlineScript.ToString(), TokenKind.Configuration.ToString() }; internal static CompletionResult GetCommandNameCompletionResult(string name, object command, bool addAmpersandIfNecessary, string quote) @@ -252,7 +268,7 @@ namespace System.Management.Automation return new CompletionResult(name, listItem, CompletionResultType.Command, syntax); } - internal static List MakeCommandsUnique(IEnumerable commandInfoPsObjs, bool includeModulePrefix, bool addAmpersandIfNecessary, string quote, CompletionContext context) + internal static List MakeCommandsUnique(IEnumerable commandInfoPsObjs, bool includeModulePrefix, bool addAmpersandIfNecessary, string quote) { List results = new List(); if (commandInfoPsObjs == null || !commandInfoPsObjs.Any()) @@ -579,7 +595,7 @@ namespace System.Management.Automation if (result.Count == 0) { result = pseudoBinding.CommandName.Equals("Set-Location", StringComparison.OrdinalIgnoreCase) - ? new List(CompleteFilename(context, true, null)) + ? new List(CompleteFilename(context, containerOnly: true, extension: null)) : new List(CompleteFilename(context)); } @@ -1325,7 +1341,8 @@ namespace System.Management.Automation { // For argument completion, we don't want to complete against pseudo commands that only work in the script workflow. // The way to avoid that is to pass in a CompletionContext with RelatedAst = null - var commandResults = CompleteCommand(new CompletionContext { WordToComplete = context.WordToComplete, Helper = context.Helper, ExecutionContext = context.ExecutionContext }); + var newContext = NewContextToCompleteCommandAsArgument(context); + var commandResults = CompleteCommand(newContext); if (commandResults != null) result.AddRange(commandResults); } @@ -2042,7 +2059,7 @@ namespace System.Management.Automation { if (parameterName.Equals("Module", StringComparison.OrdinalIgnoreCase)) { - NativeCompletionGetCommand(context.WordToComplete, null, parameterName, result, context); + NativeCompletionGetCommand(context, /* moduleName: */ null, parameterName, result); break; } if (parameterName.Equals("Name", StringComparison.OrdinalIgnoreCase)) @@ -2053,12 +2070,12 @@ namespace System.Management.Automation { foreach (string module in moduleNames) { - NativeCompletionGetCommand(context.WordToComplete, module, parameterName, result, context); + NativeCompletionGetCommand(context, module, parameterName, result); } } else { - NativeCompletionGetCommand(context.WordToComplete, null, parameterName, result, context); + NativeCompletionGetCommand(context, /* moduleName: */ null, parameterName, result); } break; } @@ -2073,13 +2090,13 @@ namespace System.Management.Automation } case "Show-Command": { - NativeCompletionGetHelpCommand(context.WordToComplete, parameterName, false, result, context); + NativeCompletionGetHelpCommand(context, parameterName, /* isHelpRelated: */ false, result); break; } case "help": case "Get-Help": { - NativeCompletionGetHelpCommand(context.WordToComplete, parameterName, true, result, context); + NativeCompletionGetHelpCommand(context, parameterName, /* isHelpRelated: */ true, result); break; } case "Invoke-Expression": @@ -2088,7 +2105,8 @@ namespace System.Management.Automation { // For argument completion, we don't want to complete against pseudo commands that only work in the script workflow. // The way to avoid that is to pass in a CompletionContext with RelatedAst = null - var commandResults = CompleteCommand(new CompletionContext { WordToComplete = context.WordToComplete, Helper = context.Helper, ExecutionContext = context.ExecutionContext }); + var newContext = NewContextToCompleteCommandAsArgument(context); + var commandResults = CompleteCommand(newContext); if (commandResults != null) result.AddRange(commandResults); } @@ -2100,7 +2118,7 @@ namespace System.Management.Automation case "Remove-EventLog": case "Write-EventLog": { - NativeCompletionEventLogCommands(context.WordToComplete, parameterName, result, context); + NativeCompletionEventLogCommands(context, parameterName, result); break; } case "Get-Job": @@ -2111,7 +2129,7 @@ namespace System.Management.Automation case "Suspend-Job": case "Resume-Job": { - NativeCompletionJobCommands(context.WordToComplete, parameterName, result, context); + NativeCompletionJobCommands(context, parameterName, result); break; } case "Disable-ScheduledJob": @@ -2119,23 +2137,23 @@ namespace System.Management.Automation case "Get-ScheduledJob": case "Unregister-ScheduledJob": { - NativeCompletionScheduledJobCommands(context.WordToComplete, parameterName, result, context); + NativeCompletionScheduledJobCommands(context, parameterName, result); break; } case "Get-Module": { bool loadedModulesOnly = boundArguments == null || !boundArguments.ContainsKey("ListAvailable"); - NativeCompletionModuleCommands(context.WordToComplete, parameterName, loadedModulesOnly, false, result, context); + NativeCompletionModuleCommands(context, parameterName, loadedModulesOnly, /* isImportModule: */ false, result); break; } case "Remove-Module": { - NativeCompletionModuleCommands(context.WordToComplete, parameterName, true, false, result, context); + NativeCompletionModuleCommands(context, parameterName, /* loadedModulesOnly: */ true, /* isImportModule: */ false, result); break; } case "Import-Module": { - NativeCompletionModuleCommands(context.WordToComplete, parameterName, false, true, result, context); + NativeCompletionModuleCommands(context, parameterName, /* loadedModulesOnly: */ false, /* isImportModule: */ true, result); break; } case "Debug-Process": @@ -2144,7 +2162,7 @@ namespace System.Management.Automation case "Wait-Process": case "Enter-PSHostProcess": { - NativeCompletionProcessCommands(context.WordToComplete, parameterName, result, context); + NativeCompletionProcessCommands(context, parameterName, result); break; } case "Get-PSDrive": @@ -2152,7 +2170,7 @@ namespace System.Management.Automation { if (parameterName.Equals("PSProvider", StringComparison.OrdinalIgnoreCase)) { - NativeCompletionProviderCommands(context.WordToComplete, parameterName, result, context); + NativeCompletionProviderCommands(context, parameterName, result); } else if (parameterName.Equals("Name", StringComparison.OrdinalIgnoreCase)) { @@ -2161,12 +2179,12 @@ namespace System.Management.Automation { foreach (string psProvider in psProviders) { - NativeCompletionDriveCommands(context.WordToComplete, psProvider, parameterName, result, context); + NativeCompletionDriveCommands(context, psProvider, parameterName, result); } } else { - NativeCompletionDriveCommands(context.WordToComplete, null, parameterName, result, context); + NativeCompletionDriveCommands(context, /* psProvider: */ null, parameterName, result); } } @@ -2174,12 +2192,12 @@ namespace System.Management.Automation } case "New-PSDrive": { - NativeCompletionProviderCommands(context.WordToComplete, parameterName, result, context); + NativeCompletionProviderCommands(context, parameterName, result); break; } case "Get-PSProvider": { - NativeCompletionProviderCommands(context.WordToComplete, parameterName, result, context); + NativeCompletionProviderCommands(context, parameterName, result); break; } case "Get-Service": @@ -2190,7 +2208,7 @@ namespace System.Management.Automation case "Stop-Service": case "Suspend-Service": { - NativeCompletionServiceCommands(context.WordToComplete, parameterName, result, context); + NativeCompletionServiceCommands(context, parameterName, result); break; } case "Clear-Variable": @@ -2198,43 +2216,43 @@ namespace System.Management.Automation case "Remove-Variable": case "Set-Variable": { - NativeCompletionVariableCommands(context.WordToComplete, parameterName, result, context); + NativeCompletionVariableCommands(context, parameterName, result); break; } case "Get-Alias": { - NativeCompletionAliasCommands(context.WordToComplete, parameterName, result, context); + NativeCompletionAliasCommands(context, parameterName, result); break; } case "Get-TraceSource": case "Set-TraceSource": case "Trace-Command": { - NativeCompletionTraceSourceCommands(context.WordToComplete, parameterName, result, context); + NativeCompletionTraceSourceCommands(context, parameterName, result); break; } case "Push-Location": case "Set-Location": { - NativeCompletionSetLocationCommand(context.WordToComplete, parameterName, result, context); + NativeCompletionSetLocationCommand(context, parameterName, result); break; } case "Move-Item": case "Copy-Item": { - NativeCompletionCopyMoveItemCommand(context.WordToComplete, parameterName, result, context); + NativeCompletionCopyMoveItemCommand(context, parameterName, result); break; } case "New-Item": { - NativeCompletionNewItemCommand(context.WordToComplete, parameterName, result, context); + NativeCompletionNewItemCommand(context, parameterName, result); break; } case "ForEach-Object": { if (parameterName.Equals("MemberName", StringComparison.OrdinalIgnoreCase)) { - NativeCompletionMemberName(context.WordToComplete, result, commandAst, context); + NativeCompletionMemberName(context, result, commandAst); } break; } @@ -2249,7 +2267,7 @@ namespace System.Management.Automation { if (parameterName.Equals("Property", StringComparison.OrdinalIgnoreCase)) { - NativeCompletionMemberName(context.WordToComplete, result, commandAst, context); + NativeCompletionMemberName(context, result, commandAst); } break; } @@ -2259,7 +2277,7 @@ namespace System.Management.Automation || parameterName.Equals("ExcludeProperty", StringComparison.OrdinalIgnoreCase) || parameterName.Equals("ExpandProperty", StringComparison.OrdinalIgnoreCase)) { - NativeCompletionMemberName(context.WordToComplete, result, commandAst, context); + NativeCompletionMemberName(context, result, commandAst); } break; } @@ -2286,7 +2304,7 @@ namespace System.Management.Automation default: { - NativeCompletionPathArgument(context.WordToComplete, parameterName, result, context); + NativeCompletionPathArgument(context, parameterName, result); break; } } @@ -2771,14 +2789,17 @@ namespace System.Management.Automation result.AddRange(namespaceResults.OrderBy(x => x.ListItemText, StringComparer.OrdinalIgnoreCase)); } - private static void NativeCompletionGetCommand(string commandName, string moduleName, string paramName, List result, CompletionContext context) + private static void NativeCompletionGetCommand(CompletionContext context, string moduleName, string paramName, List result) { if (!string.IsNullOrEmpty(paramName) && paramName.Equals("Name", StringComparison.OrdinalIgnoreCase)) { RemoveLastNullCompletionResult(result); // Available commands - var commandResults = CompleteCommand(new CompletionContext { WordToComplete = commandName, Helper = context.Helper, ExecutionContext = context.ExecutionContext }, moduleName); + // For argument completion, we don't want to complete against pseudo commands that only work in the script workflow. + // The way to avoid that is to pass in a CompletionContext with RelatedAst = null + var newContext = NewContextToCompleteCommandAsArgument(context); + var commandResults = CompleteCommand(newContext, moduleName); if (commandResults != null) result.AddRange(commandResults); @@ -2788,7 +2809,7 @@ namespace System.Management.Automation // ps1 files and directories. We only complete the files with .ps1 extension for Get-Command, because the -Syntax // may only works on files with .ps1 extension var ps1Extension = new HashSet(StringComparer.OrdinalIgnoreCase) { StringLiterals.PowerShellScriptFileExtension }; - var moduleFilesResults = new List(CompleteFilename(new CompletionContext { WordToComplete = commandName, Helper = context.Helper, ExecutionContext = context.ExecutionContext }, false, ps1Extension)); + var moduleFilesResults = new List(CompleteFilename(context, /* containerOnly: */ false, ps1Extension)); if (moduleFilesResults.Count > 0) result.AddRange(moduleFilesResults); } @@ -2800,7 +2821,7 @@ namespace System.Management.Automation RemoveLastNullCompletionResult(result); var modules = new HashSet(StringComparer.OrdinalIgnoreCase); - var moduleResults = CompleteModuleName(new CompletionContext { WordToComplete = commandName, Helper = context.Helper, ExecutionContext = context.ExecutionContext }, true); + var moduleResults = CompleteModuleName(context, loadedModulesOnly: true); if (moduleResults != null) { foreach (CompletionResult moduleResult in moduleResults) @@ -2813,7 +2834,7 @@ namespace System.Management.Automation } } - moduleResults = CompleteModuleName(new CompletionContext { WordToComplete = commandName, Helper = context.Helper, ExecutionContext = context.ExecutionContext }, false); + moduleResults = CompleteModuleName(context, loadedModulesOnly: false); if (moduleResults != null) { foreach (CompletionResult moduleResult in moduleResults) @@ -2830,28 +2851,31 @@ namespace System.Management.Automation } } - private static void NativeCompletionGetHelpCommand(string commandName, string paramName, bool isHelpRelated, List result, CompletionContext context) + private static void NativeCompletionGetHelpCommand(CompletionContext context, string paramName, bool isHelpRelated, List result) { if (!string.IsNullOrEmpty(paramName) && paramName.Equals("Name", StringComparison.OrdinalIgnoreCase)) { RemoveLastNullCompletionResult(result); // Available commands + // For argument completion, we don't want to complete against pseudo commands that only work in the script workflow. + // The way to avoid that is to pass in a CompletionContext with RelatedAst = null const CommandTypes commandTypes = CommandTypes.Cmdlet | CommandTypes.Function | CommandTypes.Alias | CommandTypes.ExternalScript | CommandTypes.Workflow | CommandTypes.Configuration; - var commandResults = CompleteCommand(new CompletionContext { WordToComplete = commandName, Helper = context.Helper, ExecutionContext = context.ExecutionContext }, null, commandTypes); + var newContext = NewContextToCompleteCommandAsArgument(context); + var commandResults = CompleteCommand(newContext, /* moduleName: */ null, commandTypes); if (commandResults != null) result.AddRange(commandResults); // ps1 files and directories var ps1Extension = new HashSet(StringComparer.OrdinalIgnoreCase) { StringLiterals.PowerShellScriptFileExtension }; - var fileResults = new List(CompleteFilename(new CompletionContext { WordToComplete = commandName, Helper = context.Helper, ExecutionContext = context.ExecutionContext }, false, ps1Extension)); + var fileResults = new List(CompleteFilename(context, /* containerOnly: */ false, ps1Extension)); if (fileResults.Count > 0) result.AddRange(fileResults); if (isHelpRelated) { // Available topics - var helpTopicResults = CompleteHelpTopics(new CompletionContext { WordToComplete = commandName, Helper = context.Helper, ExecutionContext = context.ExecutionContext }); + var helpTopicResults = CompleteHelpTopics(context); if (helpTopicResults != null) result.AddRange(helpTopicResults); } @@ -2860,13 +2884,13 @@ namespace System.Management.Automation } } - private static void NativeCompletionEventLogCommands(string logName, string paramName, List result, CompletionContext context) + private static void NativeCompletionEventLogCommands(CompletionContext context, string paramName, List result) { if (!string.IsNullOrEmpty(paramName) && paramName.Equals("LogName", StringComparison.OrdinalIgnoreCase)) { RemoveLastNullCompletionResult(result); - logName = logName ?? string.Empty; + var logName = context.WordToComplete ?? string.Empty; var quote = HandleDoubleAndSingleQuote(ref logName); if (!logName.EndsWith("*", StringComparison.Ordinal)) @@ -2911,12 +2935,12 @@ namespace System.Management.Automation } } - private static void NativeCompletionJobCommands(string wordToComplete, string paramName, List result, CompletionContext context) + private static void NativeCompletionJobCommands(CompletionContext context, string paramName, List result) { if (string.IsNullOrEmpty(paramName)) return; - wordToComplete = wordToComplete ?? string.Empty; + var wordToComplete = context.WordToComplete ?? string.Empty; var quote = HandleDoubleAndSingleQuote(ref wordToComplete); if (!wordToComplete.EndsWith("*", StringComparison.Ordinal)) @@ -2997,12 +3021,12 @@ namespace System.Management.Automation } } - private static void NativeCompletionScheduledJobCommands(string wordToComplete, string paramName, List result, CompletionContext context) + private static void NativeCompletionScheduledJobCommands(CompletionContext context, string paramName, List result) { if (string.IsNullOrEmpty(paramName)) return; - wordToComplete = wordToComplete ?? string.Empty; + var wordToComplete = context.WordToComplete ?? string.Empty; var quote = HandleDoubleAndSingleQuote(ref wordToComplete); if (!wordToComplete.EndsWith("*", StringComparison.Ordinal)) @@ -3071,7 +3095,7 @@ namespace System.Management.Automation } } - private static void NativeCompletionModuleCommands(string assemblyOrModuleName, string paramName, bool loadedModulesOnly, bool isImportModule, List result, CompletionContext context) + private static void NativeCompletionModuleCommands(CompletionContext context, string paramName, bool loadedModulesOnly, bool isImportModule, List result) { if (string.IsNullOrEmpty(paramName)) { @@ -3093,10 +3117,11 @@ namespace System.Management.Automation StringLiterals.PowerShellCmdletizationFileExtension, StringLiterals.WorkflowFileExtension }; - var moduleFilesResults = new List(CompleteFilename(new CompletionContext { WordToComplete = assemblyOrModuleName, Helper = context.Helper, ExecutionContext = context.ExecutionContext }, false, moduleExtensions)); + var moduleFilesResults = new List(CompleteFilename(context, /* containerOnly: */ false, moduleExtensions)); if (moduleFilesResults.Count > 0) result.AddRange(moduleFilesResults); + var assemblyOrModuleName = context.WordToComplete; if (assemblyOrModuleName.IndexOfAny(Utils.Separators.DirectoryOrDrive) != -1) { // The partial input is a path, then we don't iterate modules under $ENV:PSModulePath @@ -3104,7 +3129,7 @@ namespace System.Management.Automation } } - var moduleResults = CompleteModuleName(new CompletionContext { WordToComplete = assemblyOrModuleName, Helper = context.Helper, ExecutionContext = context.ExecutionContext }, loadedModulesOnly); + var moduleResults = CompleteModuleName(context, loadedModulesOnly); if (moduleResults != null && moduleResults.Count > 0) result.AddRange(moduleResults); @@ -3115,7 +3140,7 @@ namespace System.Management.Automation RemoveLastNullCompletionResult(result); var moduleExtensions = new HashSet(StringComparer.OrdinalIgnoreCase) { ".dll" }; - var moduleFilesResults = new List(CompleteFilename(new CompletionContext { WordToComplete = assemblyOrModuleName, Helper = context.Helper, ExecutionContext = context.ExecutionContext }, false, moduleExtensions)); + var moduleFilesResults = new List(CompleteFilename(context, /* containerOnly: */ false, moduleExtensions)); if (moduleFilesResults.Count > 0) result.AddRange(moduleFilesResults); @@ -3123,12 +3148,12 @@ namespace System.Management.Automation } } - private static void NativeCompletionProcessCommands(string wordToComplete, string paramName, List result, CompletionContext context) + private static void NativeCompletionProcessCommands(CompletionContext context, string paramName, List result) { if (string.IsNullOrEmpty(paramName)) return; - wordToComplete = wordToComplete ?? string.Empty; + var wordToComplete = context.WordToComplete ?? string.Empty; var quote = HandleDoubleAndSingleQuote(ref wordToComplete); if (!wordToComplete.EndsWith("*", StringComparison.Ordinal)) @@ -3204,7 +3229,7 @@ namespace System.Management.Automation } } - private static void NativeCompletionProviderCommands(string providerName, string paramName, List result, CompletionContext context) + private static void NativeCompletionProviderCommands(CompletionContext context, string paramName, List result) { if (string.IsNullOrEmpty(paramName) || !paramName.Equals("PSProvider", StringComparison.OrdinalIgnoreCase)) { @@ -3213,7 +3238,7 @@ namespace System.Management.Automation RemoveLastNullCompletionResult(result); - providerName = providerName ?? string.Empty; + var providerName = context.WordToComplete ?? string.Empty; var quote = HandleDoubleAndSingleQuote(ref providerName); @@ -3251,14 +3276,14 @@ namespace System.Management.Automation result.Add(CompletionResult.Null); } - private static void NativeCompletionDriveCommands(string wordToComplete, string psProvider, string paramName, List result, CompletionContext context) + private static void NativeCompletionDriveCommands(CompletionContext context, string psProvider, string paramName, List result) { if (string.IsNullOrEmpty(paramName) || !paramName.Equals("Name", StringComparison.OrdinalIgnoreCase)) return; RemoveLastNullCompletionResult(result); - wordToComplete = wordToComplete ?? string.Empty; + var wordToComplete = context.WordToComplete ?? string.Empty; var quote = HandleDoubleAndSingleQuote(ref wordToComplete); if (!wordToComplete.EndsWith("*", StringComparison.Ordinal)) @@ -3300,12 +3325,12 @@ namespace System.Management.Automation result.Add(CompletionResult.Null); } - private static void NativeCompletionServiceCommands(string wordToComplete, string paramName, List result, CompletionContext context) + private static void NativeCompletionServiceCommands(CompletionContext context, string paramName, List result) { if (string.IsNullOrEmpty(paramName)) return; - wordToComplete = wordToComplete ?? string.Empty; + var wordToComplete = context.WordToComplete ?? string.Empty; var quote = HandleDoubleAndSingleQuote(ref wordToComplete); if (!wordToComplete.EndsWith("*", StringComparison.Ordinal)) @@ -3383,7 +3408,7 @@ namespace System.Management.Automation } } - private static void NativeCompletionVariableCommands(string variableName, string paramName, List result, CompletionContext context) + private static void NativeCompletionVariableCommands(CompletionContext context, string paramName, List result) { if (string.IsNullOrEmpty(paramName) || !paramName.Equals("Name", StringComparison.OrdinalIgnoreCase)) { @@ -3392,7 +3417,7 @@ namespace System.Management.Automation RemoveLastNullCompletionResult(result); - variableName = variableName ?? string.Empty; + var variableName = context.WordToComplete ?? string.Empty; var quote = HandleDoubleAndSingleQuote(ref variableName); if (!variableName.EndsWith("*", StringComparison.Ordinal)) { @@ -3437,7 +3462,7 @@ namespace System.Management.Automation result.Add(CompletionResult.Null); } - private static void NativeCompletionAliasCommands(string commandName, string paramName, List result, CompletionContext context) + private static void NativeCompletionAliasCommands(CompletionContext context, string paramName, List result) { if (string.IsNullOrEmpty(paramName) || (!paramName.Equals("Definition", StringComparison.OrdinalIgnoreCase) && @@ -3451,7 +3476,7 @@ namespace System.Management.Automation var powerShellExecutionHelper = context.Helper; if (paramName.Equals("Name", StringComparison.OrdinalIgnoreCase)) { - commandName = commandName ?? string.Empty; + var commandName = context.WordToComplete ?? string.Empty; var quote = HandleDoubleAndSingleQuote(ref commandName); if (!commandName.EndsWith("*", StringComparison.Ordinal)) @@ -3489,13 +3514,16 @@ namespace System.Management.Automation { // Complete for the parameter Definition // Available commands + // For argument completion, we don't want to complete against pseudo commands that only work in the script workflow. + // The way to avoid that is to pass in a CompletionContext with RelatedAst = null const CommandTypes commandTypes = CommandTypes.Cmdlet | CommandTypes.Function | CommandTypes.ExternalScript | CommandTypes.Workflow | CommandTypes.Configuration; - var commandResults = CompleteCommand(new CompletionContext { WordToComplete = commandName, Helper = powerShellExecutionHelper, ExecutionContext = context.ExecutionContext }, null, commandTypes); + var newContext = NewContextToCompleteCommandAsArgument(context); + var commandResults = CompleteCommand(newContext, /* moduleName: */ null, commandTypes); if (commandResults != null && commandResults.Count > 0) result.AddRange(commandResults); // The parameter Definition takes a file - var fileResults = new List(CompleteFilename(new CompletionContext { WordToComplete = commandName, Helper = powerShellExecutionHelper, ExecutionContext = context.ExecutionContext })); + var fileResults = new List(CompleteFilename(context)); if (fileResults.Count > 0) result.AddRange(fileResults); } @@ -3503,7 +3531,7 @@ namespace System.Management.Automation result.Add(CompletionResult.Null); } - private static void NativeCompletionTraceSourceCommands(string traceSourceName, string paramName, List result, CompletionContext context) + private static void NativeCompletionTraceSourceCommands(CompletionContext context, string paramName, List result) { if (string.IsNullOrEmpty(paramName) || !paramName.Equals("Name", StringComparison.OrdinalIgnoreCase)) { @@ -3512,7 +3540,7 @@ namespace System.Management.Automation RemoveLastNullCompletionResult(result); - traceSourceName = traceSourceName ?? string.Empty; + var traceSourceName = context.WordToComplete ?? string.Empty; var quote = HandleDoubleAndSingleQuote(ref traceSourceName); if (!traceSourceName.EndsWith("*", StringComparison.Ordinal)) @@ -3550,7 +3578,7 @@ namespace System.Management.Automation result.Add(CompletionResult.Null); } - private static void NativeCompletionSetLocationCommand(string dirName, string paramName, List result, CompletionContext context) + private static void NativeCompletionSetLocationCommand(CompletionContext context, string paramName, List result) { if (string.IsNullOrEmpty(paramName) || (!paramName.Equals("Path", StringComparison.OrdinalIgnoreCase) && @@ -3561,7 +3589,7 @@ namespace System.Management.Automation RemoveLastNullCompletionResult(result); - context.WordToComplete = dirName ?? string.Empty; + context.WordToComplete = context.WordToComplete ?? string.Empty; var clearLiteralPath = false; if (paramName.Equals("LiteralPath", StringComparison.OrdinalIgnoreCase)) { @@ -3570,7 +3598,7 @@ namespace System.Management.Automation try { - var fileNameResults = CompleteFilename(context, true, null); + var fileNameResults = CompleteFilename(context, containerOnly: true, extension: null); if (fileNameResults != null) result.AddRange(fileNameResults); } @@ -3586,11 +3614,10 @@ namespace System.Management.Automation /// /// Provides completion results for NewItemCommand /// - /// The item provided by user for completion. + /// Completion context. /// Name of the parameter whose value needs completion. /// List of completion suggestions. - /// Completion context. - private static void NativeCompletionNewItemCommand(string itemTypeToComplete, string paramName, List result, CompletionContext context) + private static void NativeCompletionNewItemCommand(CompletionContext context, string paramName, List result) { if (string.IsNullOrEmpty(paramName)) { @@ -3613,9 +3640,9 @@ namespace System.Management.Automation { if (paramName.Equals("ItemType", StringComparison.OrdinalIgnoreCase)) { - if (!String.IsNullOrEmpty(itemTypeToComplete)) + if (!String.IsNullOrEmpty(context.WordToComplete)) { - WildcardPattern patternEvaluator = WildcardPattern.Get(itemTypeToComplete + "*", WildcardOptions.IgnoreCase); + WildcardPattern patternEvaluator = WildcardPattern.Get(context.WordToComplete + "*", WildcardOptions.IgnoreCase); if (patternEvaluator.IsMatch("file")) { @@ -3652,7 +3679,7 @@ namespace System.Management.Automation } } - private static void NativeCompletionCopyMoveItemCommand(string pathName, string paramName, List result, CompletionContext context) + private static void NativeCompletionCopyMoveItemCommand(CompletionContext context, string paramName, List result) { if (string.IsNullOrEmpty(paramName)) { @@ -3661,14 +3688,14 @@ namespace System.Management.Automation if (paramName.Equals("LiteralPath", StringComparison.OrdinalIgnoreCase) || paramName.Equals("Path", StringComparison.OrdinalIgnoreCase)) { - NativeCompletionPathArgument(pathName, paramName, result, context); + NativeCompletionPathArgument(context, paramName, result); } else if (paramName.Equals("Destination", StringComparison.OrdinalIgnoreCase)) { // The parameter Destination for Move-Item and Copy-Item takes literal path RemoveLastNullCompletionResult(result); - context.WordToComplete = pathName ?? string.Empty; + context.WordToComplete = context.WordToComplete ?? string.Empty; var clearLiteralPath = TurnOnLiteralPathOption(context); try @@ -3687,7 +3714,7 @@ namespace System.Management.Automation } } - private static void NativeCompletionPathArgument(string pathName, string paramName, List result, CompletionContext context) + private static void NativeCompletionPathArgument(CompletionContext context, string paramName, List result) { if (string.IsNullOrEmpty(paramName) || (!paramName.Equals("LiteralPath", StringComparison.OrdinalIgnoreCase) && @@ -3699,7 +3726,7 @@ namespace System.Management.Automation RemoveLastNullCompletionResult(result); - context.WordToComplete = pathName ?? string.Empty; + context.WordToComplete = context.WordToComplete ?? string.Empty; var clearLiteralPath = false; if (paramName.Equals("LiteralPath", StringComparison.OrdinalIgnoreCase)) { @@ -3721,7 +3748,7 @@ namespace System.Management.Automation result.Add(CompletionResult.Null); } - private static void NativeCompletionMemberName(string wordToComplete, List result, CommandAst commandAst, CompletionContext context) + private static void NativeCompletionMemberName(CompletionContext context, List result, CommandAst commandAst) { // Command is something like where-object/foreach-object/format-list/etc. where there is a parameter that is a property name // and we want member names based on the input object, which is either the parameter InputObject, or comes from the pipeline. @@ -3757,7 +3784,7 @@ namespace System.Management.Automation prevType = AstTypeInference.InferTypeOf(pipelineAst.PipelineElements[i - 1], context.TypeInferenceContext, TypeInferenceRuntimePermissions.AllowSafeEval); } - CompleteMemberByInferredType(context, prevType, result, wordToComplete + "*", filter: IsPropertyMember, isStatic: false); + CompleteMemberByInferredType(context, prevType, result, context.WordToComplete + "*", filter: IsPropertyMember, isStatic: false); result.Add(CompletionResult.Null); } @@ -4034,12 +4061,13 @@ namespace System.Management.Automation } var helper = new PowerShellExecutionHelper(PowerShell.Create(RunspaceMode.CurrentRunspace)); - return CompleteFilename(new CompletionContext { WordToComplete = fileName, Helper = helper, ExecutionContext = helper.CurrentPowerShell.GetContextFromTLS() }); + var executionContext = helper.CurrentPowerShell.Runspace.ExecutionContext; + return CompleteFilename(new CompletionContext { WordToComplete = fileName, Helper = helper, ExecutionContext = executionContext }); } internal static IEnumerable CompleteFilename(CompletionContext context) { - return CompleteFilename(context, false, null); + return CompleteFilename(context, containerOnly: false, extension: null); } [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] @@ -4483,7 +4511,8 @@ namespace System.Management.Automation } var helper = new PowerShellExecutionHelper(PowerShell.Create(RunspaceMode.CurrentRunspace)); - return CompleteVariable(new CompletionContext { WordToComplete = variableName, Helper = helper, ExecutionContext = helper.CurrentPowerShell.GetContextFromTLS() }); + var executionContext = helper.CurrentPowerShell.Runspace.ExecutionContext; + return CompleteVariable(new CompletionContext { WordToComplete = variableName, Helper = helper, ExecutionContext = executionContext }); } private static readonly string[] s_variableScopes = new string[] { "Global:", "Local:", "Script:", "Private:" }; @@ -5860,7 +5889,8 @@ namespace System.Management.Automation : PowerShell.Create(RunspaceMode.CurrentRunspace); var helper = new PowerShellExecutionHelper(powershell); - return CompleteType(new CompletionContext { WordToComplete = typeName, Helper = helper, ExecutionContext = helper.CurrentPowerShell.GetContextFromTLS() }); + var executionContext = helper.CurrentPowerShell.Runspace.ExecutionContext; + return CompleteType(new CompletionContext { WordToComplete = typeName, Helper = helper, ExecutionContext = executionContext }); } internal static List CompleteType(CompletionContext context, string prefix = "", string suffix = "")