Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2030,19 +2030,17 @@ private static void NativeCommandArgumentCompletion(
{
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
Expand Down Expand Up @@ -4086,7 +4084,9 @@ private static ArgumentLocation FindTargetArgumentLocation(Collection<AstParamet
private sealed class ArgumentLocation
{
internal bool IsPositional { get; set; }

internal int Position { get; set; }

internal AstParameterArgumentPair Argument { get; set; }
}

Expand Down Expand Up @@ -4999,6 +4999,7 @@ internal static List<CompletionResult> CompleteComment(CompletionContext context
new Tuple<string, string>("Where", "Where({ expression } [, mode [, numberToReturn]])"),
new Tuple<string, string>("ForEach", "ForEach(expression [, arguments...])")
};

// List of DSC collection-value variables
private static readonly HashSet<string> s_dscCollectionVariables =
new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "SelectedNodes", "AllNodes" };
Expand Down Expand Up @@ -5440,6 +5441,7 @@ private static bool IsConstructor(object member)
private abstract class TypeCompletionBase
{
internal abstract CompletionResult GetCompletionResult(string keyMatched, string prefix, string suffix);

internal abstract CompletionResult GetCompletionResult(string keyMatched, string prefix, string suffix, string namespaceToRemove);

internal static string RemoveBackTick(string typeName)
Expand Down Expand Up @@ -6956,6 +6958,7 @@ internal static bool TrySafeEval(ExpressionAst ast, ExecutionContext executionCo
public object VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst) { return false; }

public object VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst) { return false; }

// REVIEW: we could relax this to allow specific commands
public object VisitCommand(CommandAst commandAst) { return false; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,40 @@ public ArgumentCompleterAttribute(Type type)
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>
Expand Down Expand Up @@ -83,6 +104,67 @@ IEnumerable<CompletionResult> CompleteArgument(
IDictionary fakeBoundParameters);
}

/// <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")]
Expand Down
71 changes: 71 additions & 0 deletions test/powershell/Language/Parser/ExtensibleCompletion.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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 = @(
Expand Down