PowerShell-RFC icon indicating copy to clipboard operation
PowerShell-RFC copied to clipboard

Draft spec for auto-discovering feedback provider and tab-completer

Open daxian-dbw opened this issue 8 months ago • 1 comments

This is a draft spec for auto-discovering feedback provider and tab-completer. Relevant GitHub issues:

daxian-dbw avatar Mar 19 '25 21:03 daxian-dbw

I see the key elements here - autodiscover, autoload, and trigger. All this is already available in PowerShell modules. Why not just adapt the existing mechanism to meet new needs?

One option was suggested by me a few years ago https://github.com/PowerShell/PowerShell/issues/13428 This allows us to load any necessary extensions for native commands.

For example, if we introduce a naming standard, we can put Native-dotnet in any module and the engine (NativeCommandProcessor) will trigger finding this module and load, for example, an assembly/script with completer for dotnet command. (If we have to strictly follow the existing naming rules for cmdlets, then the name could be Get-NativeCommand<name>- Get-NativeCommanddotnet)

If we need a CommandNotFound feedback provider for WinGet, we can agree that we are looking for a module with a name like FeedbackProviderWinGet where FeedbackProvider is a search key for the scenario.

Everything for modules is familiar and understandable to users and it can be implemented in small steps.

iSazonov avatar Mar 20 '25 06:03 iSazonov

Possibly silly question, particularly at this late stage, but as a DLL can contain a bunch of information for how to access it through PowerShell, and in .Net an exe is just another assembly, why not have it be queryable for what its capabilities are? The first time you access the exe PowerShell can query it to see what commands it supports, and from then on you just get autocompletion. Effectively, use Import-Module on the EXE, and then direct the commands to the Cmdlet classes you just loaded in.

Or am I missing something obvious?

andrewducker avatar Apr 14 '25 17:04 andrewducker

Or am I missing something obvious?

I am! This is for entirely non-PowerShell commands which we want to have autocompletion for. Sorry, my misunderstanding.

andrewducker avatar Apr 14 '25 18:04 andrewducker

@daxian-dbw Could you please explain why PowerShell module infrastructure can not be used/adopted for auto-discovering feedback providers and tab-completers? I described one approach earlier. Here second one.

All we need to do is

  • Add new triggers. This is unavoidable with any approach.
  • Provide meta information. Here we have alternatives.

We can use standard psd1 module manifest to expose metadata. For compatibility reasons, we cannot add a new keyword (CompletersToExport), but we can use existing one FunctionsToExport provided that we accept the naming convention. For example, it could be in psd1:

FunctionsToExport = @(
    PSTabCompleter-git,
    PSFeedbackProvider-msiexec
)

Such function could return IArgumentCompleter/IFeedbackProvider and/or register the entity.

This provides, with minimal effort, almost all the needs that we expect - auto-discovering, lazy loading, signing, versioning and so on.

iSazonov avatar Apr 29 '25 06:04 iSazonov

@iSazonov There are mainly 2 reasons for not choosing to extend module manifest for the auto-discovery/auto-loading for completers, feedback providers, and potentially predictors.

  1. The completer/feedback provider/predictor are not necessarily backed by a fully-fledged module. It could be a .ps1 file for a native command completer, or a single dll served as a bare binary module. In those cases, there is no fully-fledged module existing in module path. Steve also described this in the related issue:

    the intent is to enable apps/tools that DON'T have a PowerShell module, they are native commands that want PowerShell integration. We have some partners asking for this, so this isn't a hypothetical situation.

  2. We want to make it easy for users to
    • discover what are deployed for auto-discover/loading (eagerly or lazily);
    • disable auto-discovery/loading for a particular native command easily.

There are other reasons too, such as

  • Depending on updating module manifest means existing modules cannot participate in this new feature, while the described design allows users to quickly enable auto-discovery/load for existing completer/feedback provider/predictor modules.
  • Complexity and risk of touching the module auto-loading code path. e.g. the module analysis cache and code would need to be updated to accommodate the changes.

daxian-dbw avatar Apr 29 '25 23:04 daxian-dbw

@daxian-dbw Thanks for the clarifications! I'm somewhat discouraged as I was expecting arguments like "it's impossible for security reasons" or "it's technically impossible".

I was convinced that modules are the preferred distribution method for public use. The manifests were designed just for convenience. Therefore, I am perplexed that significant efforts will be directed not at expanding the capabilities of the modules, but at creating just another config/plug-in model.

I guess the only reason to create new code is if you need to do it quickly and avoid regression. Although I don't see how the implementation of these requirements would affect the existing module functionality.

Also, I don't see the need to enable and disable on the fly. Since we are talking about an interactive session, the user installs the module if he needs it and deletes it otherwise in seconds. It's always been like this and it wasn't a problem. Or are you talking about a public pre-prepared environment where the user does not have any rights at all, then in any case he will not be able to disable any feature. If there are some rights, it means that it can save the desired state or configuration. In this case, why not make the solution more general and allow it to enable and disable loading of any module? The costs of implementation are the same, the benefits are greater.

Depending on updating module manifest means existing modules cannot participate in this new feature, while the describe design allows users to quickly enable auto-discovery/load for existing completer/feedback provider/predictor modules.

I don't understand this argument. Creating or updating a manifest file is not a problem at all.

Complexity and risk of touching the module auto-loading code path. e.g. the module analysis cache and code would need to be updated to accommodate the changes.

If we talk about my proposal, then there is only a naming convention, i.e. this code does not change. Although I understand you, if the goal is to avoid the risk of regression and create a new one quickly. I believe that this is the main factor.

iSazonov avatar Apr 30 '25 13:04 iSazonov

I don't understand this argument. Creating or updating a manifest file is not a problem at all.

@iSazonov not all existing modules will be updated, and we cannot ask users to update a module locally that they don't own.

I don't see the need to enable and disable on the fly. Since we are talking about an interactive session, the user installs the module if he needs it and deletes it otherwise in seconds.

It's very possible that a user needs the module, which offers more than completer/feedback provider/predictor, but just don't want the module to participate in auto-discovery/auto-loading for tab completion/feedback provider etc.

if the goal is to avoid the risk of regression and create a new one quickly. I believe that this is the main factor.

The main reasons and some other concerns are listed out clearly in my last comment. You can read again to make sure you don't miss any points.

daxian-dbw avatar Apr 30 '25 17:04 daxian-dbw

I don't understand this argument. Creating or updating a manifest file is not a problem at all.

@iSazonov not all existing modules will be updated, and we cannot ask users to update a module locally that they don't own.

Any popular project will do this immediately without a doubt. If a project is frozen and cannot be officially updated, users can always use the traditional profile approach. Many requests for new features have been rejected by the team precisely because there is a simple workaround.

I don't see the need to enable and disable on the fly. Since we are talking about an interactive session, the user installs the module if he needs it and deletes it otherwise in seconds.

It's very possible that a user needs the module, which offers more than completer/feedback provider/predictor, but just don't want the module to participate in auto-discovery/auto-loading for tab completion/feedback provider etc.

But auto-upload has been working exactly like this for all these years and there have not been many requests for changes. If there is such a need, then let's expand the possibilities in a more general way. Why can the user turn off the autoload of a completer, but cannot turn off the autoload of aliases, functions or cmdlets, or specify the exact list of what he needs to autoload from the module? This has been supported for a long time using the Import-Module command explicitly. And it's easy to implement for autoloading.

if the goal is to avoid the risk of regression and create a new one quickly. I believe that this is the main factor.

The main reasons and some other concerns are listed out clearly in my last comment. You can read again to make sure you don't miss any points.

It's just that these arguments didn't convince me that it was necessary to create something completely separate. I believe that expanding existing capabilities and creating a more general solution will bring more benefits. And I am strongly convinced that the rejection of the manifests is causing a lot of problems in the future. At the same time, manifests provide incredible opportunities for new features. For example, PowerShell could find git.psd1 next to git.exe and autoload completer (and more). Or it could find git.psd1 in user folder as custom config and filter out entities from standard git.psd1. Of course, none of this makes sense if the team is forced to choose a short-term solution for the sake of implementation simplicity.

iSazonov avatar May 01 '25 06:05 iSazonov

This started as a general comment on the RFC, but grew into almost a counter-proposal as I was writing it, not sure if a comment is the right format.

I'm excited that there's an RFC, huge thanks for pushing this forward. :) Overall, I feel like the proposal makes sense and it's in a very good shape, but I'm somewhat worried that what it proposes is a local optimum, not a global one. Below, I describe problems I see both with the current state and this RFC and alternative solutions I see.


Even with this RFC, a command author needs to provide completions for each shell separately, in the form of an executable script, and place the completion script in the right directory for the script. This adds extra burden for multiple parties:

  1. The author needs to write/generate the completion script separately for each shell.

  2. The author of the installer needs to find the right place for all completion scripts. For PowerShell specifically, this is problematic due to #388, since there will be at least two places to check for, and the RFC intends to support custom paths as well. For programs that are installed by just unzipping an archive somewhere and adding it to PATH, this is not possible.

    As an added pain point, having a single directory where all programs drop their completion scripts is unfortunate for installers, since if there are multiple programs with the same command name, they will keep stepping on each other's files on every update. Also, I'm not sure how mainstream installers (MSI, InnoSetup, NSIS,...) and updaters will react to manual changes to the installed completion manifest – e.g., I wouldn't be surprised if application updates accidentally overwrote the enabled key to true on every update, since that's the original content of the file.

  3. If there are multiple different commands with the same name, or multiple completers for a single command, PowerShell will have to resolve the conflict somehow, with unclear semantics.

  4. PowerShell package managers (primarily PowerShellGet) will most likely need to add explicit support for completers, since the structure and installation path is different from modules and scripts.

  5. Lesser-known shells have no reasonable way to tap into the existing ecosystem of completions, other than shelling out to another supported shell. This is currently also the case for PowerShell on non-Windows platforms.

  6. Many completion scripts will just call the command itself with a specific flag to retrieve the completions. This may potentially be a slow operation, that will have to be done on each completion request from the user.

I see a few general solutions to the listed problems:

Completion lookup

Place the completion script somewhere near the binary, not in a PowerShell-specific location. I have multiple ideas on how to make this work (e.g., the binary could embed the completion script as a resource), but the one I like the most is to look for a <CommandName>.ps1completion file on PATH, e.g. git.ps1completion (or git.exe.ps1completion, don't have a strong preference).

Since command lookup is also done through PATH, this means that for the command author, it is enough to place the file next to the binary and completions will auto-magically work. At the same time, others can provide completions for packages they did not author, just by ensuring that the completion file is on PATH. Conflict resolution should be mostly for free, since there are established conventions around how PATH lookup is done.

This also feels more friendly towards MSIX applications, although the exact loading mechanism would likely have to be adapted, since the completion script probably cannot be exposed as an app alias. I'm not sufficiently knowledgeable about MSIX internal to comment on this.

Static completion format

Instead of a completion script, standardize a mostly static declarative completion format that describes the structure of the command (parameter format, parameter names and descriptions, subcommands,...). Since most CLI libraries are configured in a static way (e.g., with an annotated class/struct), a format that covers the vast majority of common apps is imo feasible, and not overly complex (I've quickly gone through the most popular CLI libraries to verify this, I'd need a deeper analysis to give a more educated guess).

For the remaining apps (e.g., ffmpeg with its filter syntax), the format could provide hooks for specific parts of the metadata; i.e., "if you want to get parameter info for this subcommand, call this executable with these parameters". The shell could then decide whether it wants to call the hook, or just use the static portion of the metadata.

I'm fully aware that this is a much more complex proposal than this RFC, but similarly to how VS Code standardized LSP, PowerShell could solve this problem once and given the size of the project, have a chance of reaching the critical adoption mass to make this an accepted standard.

Aside: Based on a random mention on lobste.rs, I understand that someone on the .NET team was experimenting with something similar to this, but I have no closer information, I'll ask around internally. :)

Module-based implementation

An alternative option (both to the RFC and the PATH-based lookup proposed above) would be to re-use modules for this, as @iSazonov already suggested above, although I prefer the above proposals. I feel like the specific pain points mentioned in the response are all solvable, and it avoids adding yet another place where PowerShell looks for scripts, while providing the completion authors with more flexibility:

  1. The completer/feedback provider/predictor are not necessarily backed by a fully-fledged module. It could be a .ps1 file for a native command completer, or a single dll served as a bare binary module. In those cases, there is no fully-fledged module existing in module path. Steve also described this in the related issue:

If an installer can add the completion script and a manifest to a directory, it doesn't seem significantly harder to add a module to the main Modules folder. The manifest entry listing the provided completers could also include a specific script inside the module to invoke, without auto-loading the whole module, to alleviate performance concerns for modules that provide many completers.

  1. We want to make it easy for users to
  • discover what are deployed for auto-discover/loading (eagerly or lazily);

I think we should surface this in PowerShell anyway (Get-ArgumentCompleter? new property on CommandInfo?), instead of asking users to look through a directory.

  • disable auto-discovery/loading for a particular native command easily.

I'm probably missing something, but I don't see why this would be significantly different for a module as opposed to a dedicated manifest in a different directory.

There are other reasons too, such as

  • Depending on updating module manifest means existing modules cannot participate in this new feature, while the described design allows users to quickly enable auto-discovery/load for existing completer/feedback provider/predictor modules.

A user can provide their own module that declares the completers and import another module, without waiting for the author of another module actually providing the completions to update it.

  • Complexity and risk of touching the module auto-loading code path. e.g. the module analysis cache and code would need to be updated to accommodate the changes.

That's a good point, and I'm not qualified to comment on it. However, it feels like an implementation of the RFC will need to handle many of the same pain points that are already handled for module loading.

MatejKafka avatar Oct 29 '25 02:10 MatejKafka