Adding IArgumentCompleterFactory for parameterized ArgumentCompleters (#12605)

This commit is contained in:
Staffan Gustafsson 2021-03-26 01:06:40 +01:00 committed by GitHub
parent 5d0dadf19d
commit be7d36603d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 163 additions and 12 deletions

View File

@ -2030,19 +2030,17 @@ namespace System.Management.Automation
{
try
{
if (argumentCompleterAttribute.Type != null)
var completer = argumentCompleterAttribute.CreateArgumentCompleter();
if (completer != null)
{
var completer = Activator.CreateInstance(argumentCompleterAttribute.Type) as IArgumentCompleter;
if (completer != null)
var customResults = completer.CompleteArgument(commandName, parameterName,
context.WordToComplete, commandAst, GetBoundArgumentsAsHashtable(context));
if (customResults != null)
{
var customResults = completer.CompleteArgument(commandName, parameterName,
context.WordToComplete, commandAst, GetBoundArgumentsAsHashtable(context));
if (customResults != null)
{
result.AddRange(customResults);
result.Add(CompletionResult.Null);
return;
}
result.AddRange(customResults);
result.Add(CompletionResult.Null);
return;
}
}
else

View File

@ -40,19 +40,40 @@ namespace System.Management.Automation
Type = type;
}
/// <summary>
/// Initializes a new instance of the <see cref="ArgumentCompleterAttribute"/> class.
/// This constructor is used by derived attributes implementing <see cref="IArgumentCompleterFactory"/>.
/// </summary>
protected ArgumentCompleterAttribute()
{
if (this is not IArgumentCompleterFactory)
{
throw PSTraceSource.NewInvalidOperationException();
}
}
/// <summary>
/// This constructor is used primarily via PowerShell scripts.
/// </summary>
/// <param name="scriptBlock"></param>
public ArgumentCompleterAttribute(ScriptBlock scriptBlock)
{
if (scriptBlock == null)
if (scriptBlock is null)
{
throw PSTraceSource.NewArgumentNullException(nameof(scriptBlock));
}
ScriptBlock = scriptBlock;
}
internal IArgumentCompleter CreateArgumentCompleter()
{
return Type != null
? Activator.CreateInstance(Type) as IArgumentCompleter
: this is IArgumentCompleterFactory factory
? factory.Create()
: null;
}
}
/// <summary>
@ -85,6 +106,67 @@ namespace System.Management.Automation
}
#nullable restore
/// <summary>
/// Creates a new argument completer.
/// </summary>
/// <para>
/// If an attribute that derives from <see cref="ArgumentCompleterAttribute"/> implements this interface,
/// it will be used to create the <see cref="IArgumentCompleter"/>, thus giving a way to parameterize a completer.
/// The derived attribute can have properties or constructor arguments that are used when creating the completer.
/// </para>
/// <example>
/// This example shows the intended usage of <see cref="IArgumentCompleterFactory"/> to pass arguments to an argument completer.
/// <code>
/// public class NumberCompleterAttribute : ArgumentCompleterAttribute, IArgumentCompleterFactory {
/// private readonly int _from;
/// private readonly int _to;
///
/// public NumberCompleterAttribute(int from, int to){
/// _from = from;
/// _to = to;
/// }
///
/// // use the attribute parameters to create a parameterized completer
/// IArgumentCompleter Create() => new NumberCompleter(_from, _to);
/// }
///
/// class NumberCompleter : IArgumentCompleter {
/// private readonly int _from;
/// private readonly int _to;
///
/// public NumberCompleter(int from, int to){
/// _from = from;
/// _to = to;
/// }
///
/// IEnumerable{CompletionResult} CompleteArgument(string commandName, string parameterName, string wordToComplete,
/// CommandAst commandAst, IDictionary fakeBoundParameters) {
/// for(int i = _from; i &lt; _to; i++) {
/// yield return new CompletionResult(i.ToString());
/// }
/// }
/// }
/// </code>
/// </example>
public interface IArgumentCompleterFactory
{
/// <summary>
/// Creates an instance of a class implementing the <see cref="IArgumentCompleter"/> interface.
/// </summary>
/// <returns>An IArgumentCompleter instance.</returns>
IArgumentCompleter Create();
}
/// <summary>
/// Base class for parameterized argument completer attributes.
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public abstract class ArgumentCompleterFactoryAttribute : ArgumentCompleterAttribute, IArgumentCompleterFactory
{
/// <inheritdoc />
public abstract IArgumentCompleter Create();
}
/// <summary>
/// </summary>
[Cmdlet(VerbsLifecycle.Register, "ArgumentCompleter", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=528576")]

View File

@ -175,6 +175,77 @@ function TestFunction
)
}
class NumberCompleter : IArgumentCompleter
{
[int] $From
[int] $To
[int] $Step
NumberCompleter([int] $from, [int] $to, [int] $step)
{
if ($from -gt $to) {
throw [ArgumentOutOfRangeException]::new("from")
}
$this.From = $from
$this.To = $to
$this.Step = if($step -lt 1) { 1 } else { $step }
}
[IEnumerable[CompletionResult]] CompleteArgument(
[string] $CommandName,
[string] $parameterName,
[string] $wordToComplete,
[CommandAst] $commandAst,
[IDictionary] $fakeBoundParameters)
{
$resultList = [List[CompletionResult]]::new()
$local:to = $this.To
for ($i = $this.From; $i -le $to; $i += $this.Step) {
if ($i.ToString().StartsWith($wordToComplete, [System.StringComparison]::Ordinal)) {
$num = $i.ToString()
$resultList.Add([CompletionResult]::new($num, $num, "ParameterValue", $num))
}
}
return $resultList
}
}
class NumberCompletionAttribute : ArgumentCompleterAttribute, IArgumentCompleterFactory
{
[int] $From
[int] $To
[int] $Step
NumberCompletionAttribute([int] $from, [int] $to)
{
$this.From = $from
$this.To = $to
$this.Step = 1
}
[IArgumentCompleter] Create() { return [NumberCompleter]::new($this.From, $this.To, $this.Step) }
}
function FactoryCompletionAdd {
param(
[NumberCompletion(0, 50, Step = 5)]
[int] $Number
)
}
Describe "Factory based extensible completion" -Tags "CI" {
@{
ExpectedResults = @(
@{CompletionText = "5"; ResultType = "ParameterValue" }
@{CompletionText = "50"; ResultType = "ParameterValue" }
)
TestInput = 'FactoryCompletionAdd -Number 5'
} | Get-CompletionTestCaseData | Test-Completions
}
Describe "Script block based extensible completion" -Tags "CI" {
@{
ExpectedResults = @(