mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Add the parameter Register-ArgumentCompleter -NativeFallback to support registering a cover-all completer for native commands (#25230)
This commit is contained in:
@@ -2638,9 +2638,8 @@ namespace System.Management.Automation
|
||||
}
|
||||
}
|
||||
|
||||
var registeredCompleters = optionKey.Equals("NativeArgumentCompleters", StringComparison.OrdinalIgnoreCase)
|
||||
? context.NativeArgumentCompleters
|
||||
: context.CustomArgumentCompleters;
|
||||
bool isNative = optionKey.Equals("NativeArgumentCompleters", StringComparison.OrdinalIgnoreCase);
|
||||
var registeredCompleters = isNative ? context.NativeArgumentCompleters : context.CustomArgumentCompleters;
|
||||
|
||||
if (registeredCompleters != null)
|
||||
{
|
||||
@@ -2651,6 +2650,13 @@ namespace System.Management.Automation
|
||||
return scriptBlock;
|
||||
}
|
||||
}
|
||||
|
||||
// For a native command, if a fallback completer is registered, then return it.
|
||||
// For example, the 'Microsoft.PowerShell.UnixTabCompletion' module.
|
||||
if (isNative && registeredCompleters.TryGetValue(RegisterArgumentCompleterCommand.FallbackCompleterKey, out scriptBlock))
|
||||
{
|
||||
return scriptBlock;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -172,66 +172,110 @@ namespace System.Management.Automation
|
||||
[Cmdlet(VerbsLifecycle.Register, "ArgumentCompleter", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=528576")]
|
||||
public class RegisterArgumentCompleterCommand : PSCmdlet
|
||||
{
|
||||
private const string PowerShellSetName = "PowerShellSet";
|
||||
private const string NativeCommandSetName = "NativeCommandSet";
|
||||
private const string NativeFallbackSetName = "NativeFallbackSet";
|
||||
|
||||
// Use a key that is unlikely to be a file name or path to indicate the fallback completer for native commands.
|
||||
internal const string FallbackCompleterKey = "___ps::<native_fallback_key>@@___";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the command names for which the argument completer is registered.
|
||||
/// </summary>
|
||||
[Parameter(ParameterSetName = "NativeSet", Mandatory = true)]
|
||||
[Parameter(ParameterSetName = "PowerShellSet")]
|
||||
[Parameter(ParameterSetName = NativeCommandSetName, Mandatory = true)]
|
||||
[Parameter(ParameterSetName = PowerShellSetName)]
|
||||
[SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
|
||||
public string[] CommandName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the parameter for which the argument completer is registered.
|
||||
/// </summary>
|
||||
[Parameter(ParameterSetName = "PowerShellSet", Mandatory = true)]
|
||||
[Parameter(ParameterSetName = PowerShellSetName, Mandatory = true)]
|
||||
public string ParameterName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the script block that will be executed to provide argument completions.
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true)]
|
||||
[AllowNull()]
|
||||
public ScriptBlock ScriptBlock { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the argument completer is for native commands.
|
||||
/// </summary>
|
||||
[Parameter(ParameterSetName = "NativeSet")]
|
||||
[Parameter(ParameterSetName = NativeCommandSetName)]
|
||||
public SwitchParameter Native { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the argument completer is a fallback for any native commands that don't have a completer registered.
|
||||
/// </summary>
|
||||
[Parameter(ParameterSetName = NativeFallbackSetName)]
|
||||
public SwitchParameter NativeFallback { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
protected override void EndProcessing()
|
||||
{
|
||||
Dictionary<string, ScriptBlock> completerDictionary;
|
||||
if (ParameterName != null)
|
||||
{
|
||||
completerDictionary = Context.CustomArgumentCompleters ??
|
||||
(Context.CustomArgumentCompleters = new Dictionary<string, ScriptBlock>(StringComparer.OrdinalIgnoreCase));
|
||||
}
|
||||
else
|
||||
{
|
||||
completerDictionary = Context.NativeArgumentCompleters ??
|
||||
(Context.NativeArgumentCompleters = new Dictionary<string, ScriptBlock>(StringComparer.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
if (CommandName == null || CommandName.Length == 0)
|
||||
if (ParameterSetName is NativeFallbackSetName)
|
||||
{
|
||||
CommandName = new[] { string.Empty };
|
||||
}
|
||||
completerDictionary = Context.NativeArgumentCompleters ??= new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
for (int i = 0; i < CommandName.Length; i++)
|
||||
SetKeyValue(completerDictionary, FallbackCompleterKey, ScriptBlock);
|
||||
}
|
||||
else if (ParameterSetName is NativeCommandSetName)
|
||||
{
|
||||
var key = CommandName[i];
|
||||
if (!string.IsNullOrWhiteSpace(ParameterName))
|
||||
completerDictionary = Context.NativeArgumentCompleters ??= new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (string command in CommandName)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(key))
|
||||
var key = command?.Trim();
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
key = key + ":" + ParameterName;
|
||||
}
|
||||
else
|
||||
{
|
||||
key = ParameterName;
|
||||
continue;
|
||||
}
|
||||
|
||||
SetKeyValue(completerDictionary, key, ScriptBlock);
|
||||
}
|
||||
}
|
||||
else if (ParameterSetName is PowerShellSetName)
|
||||
{
|
||||
completerDictionary = Context.CustomArgumentCompleters ??= new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
string paramName = ParameterName.Trim();
|
||||
if (paramName.Length is 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
completerDictionary[key] = ScriptBlock;
|
||||
if (CommandName is null || CommandName.Length is 0)
|
||||
{
|
||||
SetKeyValue(completerDictionary, paramName, ScriptBlock);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (string command in CommandName)
|
||||
{
|
||||
var key = command?.Trim();
|
||||
key = string.IsNullOrEmpty(key)
|
||||
? paramName
|
||||
: $"{key}:{paramName}";
|
||||
|
||||
SetKeyValue(completerDictionary, key, ScriptBlock);
|
||||
}
|
||||
}
|
||||
|
||||
static void SetKeyValue(Dictionary<string, ScriptBlock> table, string key, ScriptBlock value)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
table.Remove(key);
|
||||
}
|
||||
else
|
||||
{
|
||||
table[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1067,9 +1067,9 @@ namespace System.Management.Automation.Runspaces
|
||||
/// ExecutionContext, if it available in TLS
|
||||
/// Null, if ExecutionContext is not available in TLS
|
||||
/// </returns>
|
||||
internal static System.Management.Automation.ExecutionContext GetExecutionContextFromTLS()
|
||||
internal static ExecutionContext GetExecutionContextFromTLS()
|
||||
{
|
||||
System.Management.Automation.Runspaces.Runspace runspace = Runspace.DefaultRunspace;
|
||||
Runspace runspace = Runspace.DefaultRunspace;
|
||||
if (runspace == null)
|
||||
{
|
||||
return null;
|
||||
|
||||
@@ -104,7 +104,7 @@ namespace System.Management.Automation
|
||||
/// </summary>
|
||||
/// <param name="script">The string to compile.</param>
|
||||
public static ScriptBlock Create(string script) => Create(
|
||||
parser: new Language.Parser(),
|
||||
parser: new Parser(),
|
||||
fileName: null,
|
||||
fileContents: script);
|
||||
|
||||
|
||||
@@ -1929,8 +1929,12 @@ param([ValidatePattern(
|
||||
|
||||
Context NativeCommand {
|
||||
BeforeAll {
|
||||
$nativeCommand = (Get-Command -CommandType Application -TotalCount 1).Name
|
||||
## Find a native command that is not 'pwsh'. We will use 'pwsh' for fallback completer tests later.
|
||||
$nativeCommand = Get-Command -CommandType Application -TotalCount 2 |
|
||||
Where-Object Name -NotLike pwsh* |
|
||||
Select-Object -First 1
|
||||
}
|
||||
|
||||
It 'Completes native commands with -' {
|
||||
Register-ArgumentCompleter -Native -CommandName $nativeCommand -ScriptBlock {
|
||||
param($wordToComplete, $ast, $cursorColumn)
|
||||
@@ -1994,6 +1998,52 @@ param([ValidatePattern(
|
||||
$res.CompletionMatches | Should -HaveCount 1
|
||||
$res.CompletionMatches.CompletionText | Should -BeExactly "-option"
|
||||
}
|
||||
|
||||
It 'Covers an arbitrary unbound native command with -t' {
|
||||
## Register a completer for $nativeCommand.
|
||||
Register-ArgumentCompleter -Native -CommandName $nativeCommand -ScriptBlock {
|
||||
param($wordToComplete, $ast, $cursorColumn)
|
||||
if ($wordToComplete -eq '-t') {
|
||||
return "-terminal"
|
||||
}
|
||||
}
|
||||
|
||||
## Register a fallback native command completer.
|
||||
Register-ArgumentCompleter -NativeFallback -ScriptBlock {
|
||||
param($wordToComplete, $ast, $cursorColumn)
|
||||
if ($wordToComplete -eq '-t') {
|
||||
return "-testing"
|
||||
}
|
||||
}
|
||||
|
||||
## The specific completer will be used if it exists.
|
||||
$line = "$nativeCommand -t"
|
||||
$res = TabExpansion2 -inputScript $line -cursorColumn $line.Length
|
||||
$res.CompletionMatches | Should -HaveCount 1
|
||||
$res.CompletionMatches.CompletionText | Should -BeExactly "-terminal"
|
||||
|
||||
## Otherwise, the fallback completer will kick in.
|
||||
$line = "pwsh -t"
|
||||
$res = TabExpansion2 -inputScript $line -cursorColumn $line.Length
|
||||
$res.CompletionMatches | Should -HaveCount 1
|
||||
$res.CompletionMatches.CompletionText | Should -BeExactly "-testing"
|
||||
|
||||
## Remove the completer for $nativeCommand.
|
||||
Register-ArgumentCompleter -Native -CommandName $nativeCommand -ScriptBlock $null
|
||||
|
||||
## The fallback completer will be used for $nativeCommand.
|
||||
$line = "$nativeCommand -t"
|
||||
$res = TabExpansion2 -inputScript $line -cursorColumn $line.Length
|
||||
$res.CompletionMatches | Should -HaveCount 1
|
||||
$res.CompletionMatches.CompletionText | Should -BeExactly "-testing"
|
||||
|
||||
## Remove the fallback completer for $nativeCommand.
|
||||
Register-ArgumentCompleter -NativeFallback -ScriptBlock $null
|
||||
|
||||
## The fallback completer will be used for $nativeCommand.
|
||||
$res = TabExpansion2 -inputScript $line -cursorColumn $line.Length
|
||||
$res.CompletionMatches | Should -HaveCount 0
|
||||
}
|
||||
}
|
||||
|
||||
It 'Should complete "Export-Counter -FileFormat" with available output formats' -Pending {
|
||||
|
||||
Reference in New Issue
Block a user