From 9383f554a2c719af88010a66e2a283fa93c07db9 Mon Sep 17 00:00:00 2001 From: Armaan Mcleod Date: Tue, 19 Dec 2023 20:12:33 +1100 Subject: [PATCH] Add `-Scope` argument completer for `*-Variable`, `*-Alias` & `*-PSDrive` commands (#20451) --- .../commands/management/Navigation.cs | 3 ++ .../commands/utility/ExportAliasCommand.cs | 1 + .../commands/utility/GetAliasCommand.cs | 1 + .../commands/utility/ImportAliasCommand.cs | 1 + .../commands/utility/RemoveAliasCommand.cs | 1 + .../commands/utility/Var.cs | 1 + .../commands/utility/WriteAliasCommandBase.cs | 1 + .../ScopeArgumentCompleter.cs | 44 +++++++++++++++++++ .../TabCompletion/TabCompletion.Tests.ps1 | 26 +++++++++++ 9 files changed, 79 insertions(+) create mode 100644 src/System.Management.Automation/engine/CommandCompletion/ScopeArgumentCompleter.cs diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Navigation.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Navigation.cs index 8bc149c2cf..17e88a0e11 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Navigation.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Navigation.cs @@ -1129,6 +1129,7 @@ namespace Microsoft.PowerShell.Commands /// Gets or sets the scope identifier for the drive being created. /// [Parameter(ValueFromPipelineByPropertyName = true)] + [ArgumentCompleter(typeof(ScopeArgumentCompleter))] public string Scope { get; set; } #if !UNIX @@ -1533,6 +1534,7 @@ namespace Microsoft.PowerShell.Commands /// global scope until a drive of the given name is found to remove. /// [Parameter(ValueFromPipelineByPropertyName = true)] + [ArgumentCompleter(typeof(ScopeArgumentCompleter))] public string Scope { get; set; } /// @@ -1701,6 +1703,7 @@ namespace Microsoft.PowerShell.Commands /// Gets or sets the scope parameter to the command. /// [Parameter(ValueFromPipelineByPropertyName = true)] + [ArgumentCompleter(typeof(ScopeArgumentCompleter))] public string Scope { get; set; } /// diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ExportAliasCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ExportAliasCommand.cs index 33deb01eb2..d6f2c661ad 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ExportAliasCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ExportAliasCommand.cs @@ -184,6 +184,7 @@ namespace Microsoft.PowerShell.Commands /// which scope the aliases are retrieved from. /// [Parameter] + [ArgumentCompleter(typeof(ScopeArgumentCompleter))] public string Scope { get; set; } #endregion Parameters diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetAliasCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetAliasCommand.cs index bf4c8a39db..7b93b555c5 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetAliasCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetAliasCommand.cs @@ -51,6 +51,7 @@ namespace Microsoft.PowerShell.Commands /// which scope the aliases are retrieved from. /// [Parameter] + [ArgumentCompleter(typeof(ScopeArgumentCompleter))] public string Scope { get; set; } /// diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportAliasCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportAliasCommand.cs index 9a647d4c72..657d00b4fc 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportAliasCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportAliasCommand.cs @@ -55,6 +55,7 @@ namespace Microsoft.PowerShell.Commands /// [Parameter] [ValidateNotNullOrEmpty] + [ArgumentCompleter(typeof(ScopeArgumentCompleter))] public string Scope { get; set; } /// diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RemoveAliasCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RemoveAliasCommand.cs index cd0c4fce32..15c48efd84 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RemoveAliasCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RemoveAliasCommand.cs @@ -25,6 +25,7 @@ namespace Microsoft.PowerShell.Commands /// The scope parameter for the command determines which scope the alias is removed from. /// [Parameter] + [ArgumentCompleter(typeof(ScopeArgumentCompleter))] public string Scope { get; set; } /// diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Var.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Var.cs index 6df552ab8e..85bdd2b02d 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Var.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Var.cs @@ -22,6 +22,7 @@ namespace Microsoft.PowerShell.Commands /// [Parameter] [ValidateNotNullOrEmpty] + [ArgumentCompleter(typeof(ScopeArgumentCompleter))] public string Scope { get; set; } #endregion parameters diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteAliasCommandBase.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteAliasCommandBase.cs index 82c9c9f655..31670935fc 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteAliasCommandBase.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteAliasCommandBase.cs @@ -61,6 +61,7 @@ namespace Microsoft.PowerShell.Commands /// The scope parameter for the command determines which scope the alias is set in. /// [Parameter] + [ArgumentCompleter(typeof(ScopeArgumentCompleter))] public string Scope { get; set; } /// diff --git a/src/System.Management.Automation/engine/CommandCompletion/ScopeArgumentCompleter.cs b/src/System.Management.Automation/engine/CommandCompletion/ScopeArgumentCompleter.cs new file mode 100644 index 0000000000..b397be3aa5 --- /dev/null +++ b/src/System.Management.Automation/engine/CommandCompletion/ScopeArgumentCompleter.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections; +using System.Collections.Generic; +using System.Management.Automation.Language; + +namespace System.Management.Automation +{ + /// + /// Provides argument completion for Scope parameter. + /// + public class ScopeArgumentCompleter : IArgumentCompleter + { + private static readonly string[] s_Scopes = new string[] { "Global", "Local", "Script" }; + + /// + /// Returns completion results for scope parameter. + /// + /// The command name. + /// The parameter name. + /// The word to complete. + /// The command AST. + /// The fake bound parameters. + /// List of completion results. + public IEnumerable CompleteArgument( + string commandName, + string parameterName, + string wordToComplete, + CommandAst commandAst, + IDictionary fakeBoundParameters) + { + var scopePattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase); + + foreach (string scope in s_Scopes) + { + if (scopePattern.IsMatch(scope)) + { + yield return new CompletionResult(scope); + } + } + } + } +} diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index 0b03155be3..7e72b3fff0 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -744,6 +744,32 @@ ConstructorTestClass(int i, bool b) $res.CompletionMatches[0].CompletionText | Should -BeExactly '$TestVar1' } + Context 'Scope parameter completion' { + BeforeAll { + $allScopes = 'Global Local Script' + $globalScope = 'Global' + $localScope = 'Local' + $scriptScope = 'Script' + $allScopeCommands = 'Clear-Variable', 'Export-Alias', 'Get-Alias', 'Get-PSDrive', 'Get-Variable', 'Import-Alias', 'New-Alias', 'New-PSDrive', 'New-Variable', 'Remove-Alias', 'Remove-PSDrive', 'Remove-Variable', 'Set-Alias', 'Set-Variable' + } + + It "Should complete '' for ''" -TestCases @( + @{ Commands = $allScopeCommands; ParameterInput = "-Scope "; ExpectedScopes = $allScopes } + @{ Commands = $allScopeCommands; ParameterInput = "-Scope G"; ExpectedScopes = $globalScope } + @{ Commands = $allScopeCommands; ParameterInput = "-Scope Lo"; ExpectedScopes = $localScope } + @{ Commands = $allScopeCommands; ParameterInput = "-Scope Scr"; ExpectedScopes = $scriptScope } + @{ Commands = $allScopeCommands; ParameterInput = "-Scope NonExistentScope"; ExpectedScopes = '' } + ) { + param($Commands, $ParameterInput, $ExpectedScopes) + foreach ($command in $Commands) { + $joinedCommand = "$command $ParameterInput" + $res = TabExpansion2 -inputScript $joinedCommand -cursorColumn $joinedCommand.Length + $completionText = $res.CompletionMatches.CompletionText | Sort-Object + $completionText -join ' ' | Should -BeExactly $ExpectedScopes + } + } + } + Context 'Get-Verb & Get-Command -Verb parameter completion' { BeforeAll { $allVerbs = 'Add Approve Assert Backup Block Build Checkpoint Clear Close Compare Complete Compress Confirm Connect Convert ConvertFrom ConvertTo Copy Debug Deny Deploy Disable Disconnect Dismount Edit Enable Enter Exit Expand Export Find Format Get Grant Group Hide Import Initialize Install Invoke Join Limit Lock Measure Merge Mount Move New Open Optimize Out Ping Pop Protect Publish Push Read Receive Redo Register Remove Rename Repair Request Reset Resize Resolve Restart Restore Resume Revoke Save Search Select Send Set Show Skip Split Start Step Stop Submit Suspend Switch Sync Test Trace Unblock Undo Uninstall Unlock Unprotect Unpublish Unregister Update Use Wait Watch Write'