Repository navigation
Support parameterized argument completers #12708
Copy link
Copy link
Closed
Labels
Committee-ReviewedPS-Committee has reviewed this and made a decisionPS-Committee has reviewed this and made a decisionIssue-Discussionthe issue may not have a clear classification yet. The issue may generate an RFC or may be reclassifthe issue may not have a clear classification yet. The issue may generate an RFC or may be reclassifIssue-Enhancementthe issue is more of a feature request than a bugthe issue is more of a feature request than a bugResolution-FixedThe issue is fixed.The issue is fixed.WG-Enginecore PowerShell engine, interpreter, and runtimecore PowerShell engine, interpreter, and runtime
Description
Add support for ArgumentCompletionAttributes with parameters
ArgumentCompleters are in the current implementation created by type, using a default constructor.
This makes it very hard to provide parameters to the completed, limiting their usefulness.
I propose that we add "Factory" support to the completion system, so that an attribute derived from ArgumentCompleterFactoryAttribute gets called on a method with a signature like
IArgumentCompleter Create();This would allow the derived attribute to use attribute parameters to create the actual argument completer.
Deriving from ArgumentCompleterFactoryAttribute makes it possible to create generic completers, like
[DirectoryCompleter(ContainingFile="pswh.exe", Depth=2)]
[DateCompleter(WeekDay='Monday', From="LastYear")]
[GitCommits(Branch='release')]An sample usage could look like this:
/// <summary>
/// Creates new number completions
/// </summary>
public class NumberCompletionsAttribute : ArgumentCompleterFactoryAttribute
{
private readonly int _from;
private readonly int _to;
private readonly int _step;
public NumberCompletionsAttribute(int from, int to, int step = 1)
{
_from = @from;
_to = to;
_step = step;
}
public IArgumentCompleter Create() => new NumberCompleter(_from, _to, _step);
}
public class NumberCompleter : IArgumentCompleter
private readonly int _from;
private readonly int _to;
private readonly int _step;
public NumberCompleter(int from, int to, int step)
{
_from = @from;
_to = to;
_step = step;
}
public IEnumerable<CompletionResult> CompleteArgument(string commandName, string parameterName, string wordToComplete, CommandAst commandAst, IDictionary fakeBoundParameters)
{
/// complete using the passed parameters
}
}Proposed technical implementation details (optional)
There are a couble of ways to go about this:
- New abstract class
ArgumentCompleterFactoryAttributewith an abstractCreatemethod. - New interface
IArgumentCompleterFactoryThat the attributes implement if they want the factory support. - Combine the above: Have
ArgumentCompleterFactoryAttributeimplementIArgumentCompleterFactory
Pull request for implementation can be found here: #12605
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
Committee-ReviewedPS-Committee has reviewed this and made a decisionPS-Committee has reviewed this and made a decisionIssue-Discussionthe issue may not have a clear classification yet. The issue may generate an RFC or may be reclassifthe issue may not have a clear classification yet. The issue may generate an RFC or may be reclassifIssue-Enhancementthe issue is more of a feature request than a bugthe issue is more of a feature request than a bugResolution-FixedThe issue is fixed.The issue is fixed.WG-Enginecore PowerShell engine, interpreter, and runtimecore PowerShell engine, interpreter, and runtime
Activity
iSazonov commentedon May 18, 2020
From Dongbo Wang (@daxian-dbw)
I like the idea, very interesting, allowing people to have reusable argument completer attributes. About the implementation, why not have an abstract attribute class that defines an abstract method which returns an
IArgumentCompleterinstance? For example:This seems more succinct than
: ArgumentCompleterAttribute, IArgumentCompleterFactoryiSazonov commentedon May 18, 2020
Another thought is IArgumentCompleter<T> vs IArgumentCompleterFactory so that users could use
IArgumentCompleter, IArgumentCompleter<T>iSazonov commentedon May 18, 2020
/cc Joey Aiello (@joeyaiello) Steve Lee (@SteveL-MSFT) Dongbo Wang (@daxian-dbw) James Truher (@JamesWTruher)
powercode commentedon May 18, 2020
The reason I have been leaning towards an interface is that there may turn up other places where we want to support creation of completers.
This can be combined with Dongbo Wang (@daxian-dbw)'s idea.
Users can still just derive from
ArgumentCompleterFactoryAttributebut we maintain the flexibility of an interface.On the other hand, we could probably just add the interface later if we see other useful scenarios.
SteveL-MSFT commentedon May 20, 2020
Staffan Gustafsson (@powercode) thanks for starting this conversation. Once the community has an agreement, the PS-Committee can review.
vexx32 commentedon May 20, 2020
Maybe I'm just missing the point here, but I don't really see what this implementation offers that makes it easier / more robust than just creating an
IArgumentCompleterclass?From the examples, it seems kind of like I'd now need to create two classes for every argument completer I want to create instead of just the one, and I feel like that's more hassle than it's worth in many ways. 😕
powercode commentedon May 20, 2020
The whole point is to be able to create completers with parameters. The current mechanics only allow for a type, that is instantiated with with it's default constructor. You have no way of creating generic completers where the user can control their behavior on a case by case basis.
Think
This would allow for the creation of libraries of completers, that can be implemented with high quality and used by others.
vexx32 commentedon May 20, 2020
OK, I think that makes sense. I'm just not sure why we'd build it in such a way that requires a script author to implement two classes for it. Is it not possible to build this into a single class design that implements a given interface? I like that idea, I just think we might be overcomplicating the UX a bit here. 🙂
powercode commentedon May 20, 2020
Personally, I envision the completers shipping in-box, or has libraries written in C#. The UX then would be to use the provided attributes, as demonstrated above
vexx32 commentedon May 20, 2020
That makes sense for some of them, absolutely. I don't see why we wouldn't make them something users can easily build upon as well, though.
iSazonov commentedon May 21, 2020
I have a concern about two classes too. We cannot completely decouple them.
Alternative could be
public ArgumentCompleterAttribute(params object[] args)where first parameter is a custom Completer type and follows are its arguments.jhoneill commentedon May 21, 2020
It comes from the slightly strange way that completers are loaded.
For a validator I can write this
and use
[ValidatePath(exception="^-+$",ContainersOnly=$true,MaxItems=1)]in one place (so ^, - + and $ will be treated as valid and it must resolve to 1 container)
and somewhere else I can use it without setting anything to validate files or folders and as many matches as you like
But with a completer because the specification is
[ArgumentCompleter([PathCompleter])]there is no option to say "Create the instance of PathCompleter with these parameters set before you call completeArgument()"
There is a work round by writing argumentCompleter({scriptblock}) but it makes for an ugly and hard to read script block, so two similar completers must be duplicate most of their code because they parameterize a choice like "expand files and directories" vs "expand only directories".
would be the most sensible way to add in the functionality.
1 remaining item
jhoneill commentedon May 22, 2020
Argument completer is a special case (AIUI) because there can be as many transformers and validators as required, but there can't be two completers for the same thing trying to send lists back. So we have only one argument completing class with a constructor which takes either a class (with specific requirements) or a script block (with specific requirements). Changing that would probably have complicated ripple effects.
But provided there is a way of writing
[Here_Be_Completion <<class name>> <<attribute values>> ]I don't think many people will care very much about the details of the syntax, or what it looks like internally.powercode commentedon May 22, 2020
I think you are missing an important point here.
Using properties have a documenting effect when using attributes. Using a params array in construction makes it impossible to use an attribute completer in more that one way, and it gives absolutely no information to the user what arguments to pass.
I think you have to come to terms with the fact that the attribute and the completer are two different things.
iSazonov commentedon May 22, 2020
We could create a completer for
ArgumentCompleterAttribute(type_completer, <Tab>but I think it's not even worth the effort - if the user knows about the existence of thistype_completercompleter, then he knows how to use it or he is already looking at its description in the documentation.powercode commentedon May 22, 2020
I really think we should drop the params argument idea.
This is what the users would see
What does that mean?
The properties of the attributes have a documenting effect.
iSazonov commentedon May 22, 2020
is parsed well.
vexx32 commentedon May 22, 2020
It parses, but the named arguments would have to map to properties on
[ArgumentCompleter]which is why I'd think it's more useful to makeArgumentCompletereffectively inheritable or something along those lines so we can have the syntax Staffan Gustafsson (@powercode) is suggesting with more than just the initial set of attributes he proposed using, without complicating the design more than is needed.iSazonov commentedon May 22, 2020
Can't PowerShell know such bindings? :-)
SteveL-MSFT commentedon Jun 18, 2020
@PowerShell/powershell-committee reviewed this. We agree that the current proposed design is the preferred one as the ability to have named parameters makes the code more understandable. There was a general concern about introducing a new attribute which means modules require a specific version of an assembly (SMA.dll if these completers are part of PS or a shared assembly from nuget.org, for example), but this is true for any new compiled feature in PS. Overall, we thought this was a good discussion and brought up many good points. However, to make it easier to review and understand the user experience better, it would be great to have more example code in the issue. In this case, the example code was in the PR as part of the test case:
powercode commentedon Jun 22, 2020
I will probably become an issue of documentation, since I envision the box product to contain completers, just as it has validation attributes. Generic completers for files/directories, AD, etc will eventually become available and needs to be documented just like the attributes.