Repository navigation
Add a way to lazily autoload argument completers #17283
Description
Summary of the new feature / enhancement
Currently, argument completers for native commands must be explicitly registered using Register-ArgumentCompleter. However, this means that the module which registers the completer must be eagerly loaded in $PROFILE during startup, otherwise the completions aren't loaded. If many different argument completers are registered, this may take a considerable amount of time on each startup, even when most of the completions are never used in the session.
As an example, importing https://github.com/MatejKafka/PowerShell-ArgumentCompleters takes about 175 ms on my computer. Currently, I'm loading them in an OnIdle event callback, which helps, but it's still not ideal and doesn't scale well to a larger number of completers.
It would be nice to add autoloading support for argument completers. Completions for native commands (together with inline description) are the main reason why I prefer fish shell autocompletion over the one in PowerShell.
As an additional food for thought (which is definitely out-of-scope for this issue), currently there isn't any official repository for argument completers. If that was added, it could be possibly to interactively offer the user to download completions for the given command (if available) when they first trigger autocomplete on it, greatly improving the experience of using native commands in PowerShell. It already kinda works this way on Linux, where many packages also install autocompleters for commonly used shells.
Proposed technical implementation details (optional)
Possible implementation: Add an entry to the module manifest, listing the commands for which it provides argument completers. When completions for the command are requested, the module is autoloaded, which registers the argument completers.
This implementation has an issue where if the module is large (slow to load), there may be a considerable delay before the completions appear. This could be further improved by also specifying which file contains the argument completer for the given command, so that only that file could be loaded, without loading the whole module. This would allow for one big module, which registers many different completers, each in a separate file, without having to load the whole module.
Activity
daxian-dbw commentedon Oct 17, 2022
Auto loading a module at the time of user pressing Tab is dangerous. It's likely will block the user input for a very noticeable period of time and the user simply cannot cancel whatever PowerShell is doing at that point. So, it seems PowerShell freezes, and the user has to wait till it becomes responsive.
MatejKafka commentedon Oct 18, 2022
The current alternative is eagerly loading the autocompletion module in
$PROFILE, so the user has to pay this cost on each startup, not just on the first completion invocation.Your described behavior is exactly what already happens for PowerShell commands – if I invoke completion on a command from a not-yet-loaded module, there's a delay where PowerShell is loading the module to provide completion. Thus, allowing autoload for external completers does not make this situation any worse.
JamesWTruher commentedon Oct 21, 2022
The WG discussed this. We feel that there are a number of threads that need pulling here:
azmodule)We think a comprehensive approach is going to provide better results long term, rather than just trying to solve this problem for modules. We're suggesting that an RFC is really needed here.
iSazonov commentedon Oct 21, 2022
They use scripts for completion. This is a way for code injection. I highly doubt that MSFT will agree to run someone else's extra code! Mandatory signing of such files is not convenient for users.
Now the .Net Command Line project uses the approach of starting target application with every tab. It is obvious how problematic this approach is.
I would suggest joining forces with this project and developing a standard for declarative description of meta information about parameters and arguments.
PowerShell uses this now through attributes. If there were a standard, it would be able to read external information (XML/Json) from any application and use it for completion.
SteveL-MSFT commentedon Jan 6, 2023
I wonder if it would make sense to add a new parameter to
Register-ArgumentCompletercalled-Persistalong with an optional-Scope(default toCurrentUser, but can acceptAllUsers) which modifiespowershell.config.jsonas the way to persistent auto re-registration of completers on startup? Of course, we'd need a correspondingUnregister-ArgumentCompletercmdlet which doesn't exist today and decide separately if it works beyond persisted ones.Get-ArgumentCompletercmdlet for auditingheaths commentedon Jan 6, 2023
A bit tangential, but modifying existing files during an install - whether MSI on Windows, brew, apt on debian flavors, etc. - is very problematic. Those "resources" can't really be tracked and removed safely with the packages or upgraded correctly, so most installers - even with custom solutions - don't even try. For years this was a problem with .NET Framework's machine.config, which is why we eventually came up with other solutions for first- and third-party solutions.
A common solution on most linux distros is a directory for drop-in customizations, like /etc/apt/sources.list.d (".d" for a directory is a common naming convention). Files (which, on linux, everything is a file) in whole are best relegated to installers.
That said, PowerShell already has a directory-based solution: modules. Could Crescendo fill this void?
daxian-dbw commentedon Jan 6, 2023
Regarding Steve Lee (@SteveL-MSFT)'s proposal in #17283 (comment), it is still eager loading, meaning the long delay in startup reported here will remain an issue. Also, to register completion, it requires an installer to start the pwsh process to run
Register-ArgumentCompleterduring the installation, which is error prone in my opinion.I think we should have a "completion path", similar to
$PSModulePath, which contains directories that powershell will probe for completion meta files (this aligns with how it works in other shells). The completion meta file can be named as<command-name>+<.pwsh>, and it has to be a powershell data file just like a.psd1file. Initially, the metadata contained in the file should just be a 2-element hash table:When tab completion on a native command is triggered, the engine completion code will:
context.NativeArgumentCompletersto find if a completer is already registered for the native command.context.NativeArgumentCompletersto find if a completer is now availableWith this approach, an installer just needs to deploy a completion module and a completion meta file to enable completion for the native command in powershell, which is way less error prone, and also allow lazy-loading of the completer automatically when it's needed.
One may argue that the lazy-loading upon tab completion happens may cause unresponsive interactive experience at least for the first tab for a particular native command (I did so myself in #17283 (comment) 😄). That's true, but it may not be such a bad thing either -- it forces the completion module author to be more performance oriented in designing the module and argument completer.
fish has the "completion path" concept for adding completion support: https://fishshell.com/docs/current/completions.html#where-to-put-completions
SteveL-MSFT commentedon Jan 7, 2023
Dongbo Wang (@daxian-dbw) so with a file path based approach would we have both shared and user directories? would modules also use this mechanism?
iSazonov commentedon Jan 7, 2023
My suggestion was #13428 - create generic support for a native command wrapper.
To do something clever with native commands PowerShell needs meta information. PowerShell already has the means to create such meta information. With pseudo/native cmdlets we could add any meta information in the usual way. (Apart from parameter descriptions it could be (optionally) transcoding, output parsing, etc.)
I don't understand why we have to invent something else if we already have everything.
A more fundamental approach would be to synchronize with the .Net CommandLine project team so that PowerShell can take meta information directly from the native command dll.
6 remaining items
segevfiner commentedon Jan 14, 2024
Bump
henno commentedon Mar 22, 2024
Could we engineer a method to initiate asynchronous preloading of parameter autocomplete suggestions for a cmdlet immediately after the user enters the cmdlet name as the initial word in the prompt and hits space following the cmdlet's name?
segevfiner commentedon Sep 18, 2024
Still relevant. Having many completion scripts slows down startup, even if they end up unused in the session.
MatejKafka commentedon Dec 25, 2024
Joel Bennett (@Jaykul)
Fair point, but the same issue occurs when multiple modules provide the same exported cmdlet, so there are existing rules that we could follow.
totkeks commentedon Feb 8, 2025
Just had the same issue and it seems there is no accepted solution yet. Together with my LLM friends I hacked together this piece of code:
The lazy part works. Just the tiny issue of the first completion that triggers the lazy load... it falls back to completing using the files in the current directory and I have no idea how to call the now registered completer after
& $CompletionScripthas finished.LLM suggested
but this doesn't seem to work. Not sure if it is the command or because the
& Completionscriptends the control of the scriptblock.MatejKafka commentedon Feb 8, 2025
Norman Dankert (@totkeks) I use the following implementation, which seems to work reliably (but uses undocumented pwsh internals):
totkeks commentedon Feb 8, 2025
Thanks, that works amazingly well, even for the first completion. Just made a small change, so it also works for completions that come from PowerShell scripts like go and rust use.
Also, the hint about
Get-Errorwas extremely useful. I didn't know about this and wondered why I don't see any output on the failed completions.segevfiner commentedon Dec 26, 2025
Bump
lyra95 commentedon Jun 11, 2026
This hacky code works fine if there are no name collisions between completion scripts: