Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using System.CommandLine for powershell completers #1220

Open
powercode opened this issue Mar 11, 2021 · 4 comments
Open

Using System.CommandLine for powershell completers #1220

powercode opened this issue Mar 11, 2021 · 4 comments
Labels
Area-Completions Related to support for tab completion enhancement New feature or request

Comments

@powercode
Copy link
Contributor

powercode commented Mar 11, 2021

When writing a completer for an existing executable, the PowerShell API requires the return of an IEnumerable<CompletionResult>. It is a somewhat richer model, where you can provide more info than just a string, such as tooltips.

For example, completing git a gives me documentation as a tooltip.
image

However, the suggestion APIs are using string as its hard-coded data type, making this kind of rich completion more difficult.

Sure, I can encode the info as json, or have null-separated fields, but it adds complexity.

What do you think of adding a more generic api, a IEnumerable<T> GetSuggestion<T>()?

Edit:
More info.

In PowerShell, Native commands are completed by a script like this:

{
    param($wordToComplete, $commandAst, $cursorPosition)

   [SampleCompleter]::CompleteInput($wordToComplete, $commandAst, $cursorPosition)
}
public static class SampleCompleter{
   public static IEnumerable<CompletionResult> CompleteInput(string wordToComplete, CommandAst commandAst, int cursorPosition) {
       return GetOptions().Where(o=>o.CompletionText.StartsWith(wordTocomplete);
   }

   IEnumerable<CompletionResult> GetOptions(){
       yield return new CompletionResult("-a", "-a", CompletionResultType.ParameterName, "tooltip for a");
       yield return new CompletionResult("-b", "-b", CompletionResultType.ParameterName, "tooltip for b");
   }
}

The nice thing would be to use System.CommandLine to get the suggestions, but as CompletionResult instead of string.

@jonsequitur
Copy link
Contributor

This is something we've been considering, in fact. We'd have immediate use for it in .NET Interactive.

@jonsequitur jonsequitur added Area-Completions Related to support for tab completion enhancement New feature or request labels Mar 12, 2021
@KalleOlaviNiemitalo
Copy link

I imagine the properties would match up like this:

System.CommandLine PowerShell
CompletionItem.Label CompletionResult.ListItemText
CompletionItem.Kind CompletionResult.ResultType
CompletionItem.SortText N/A
CompletionItem.InsertText CompletionResult.CompletionText
CompletionItem.Documentation CompletionResult.ToolTip
CompletionItem.Detail N/A

CompletionItem.SortText might be useful for async enumeration (adding more completion items to a list that is already being displayed to the user), or for merging completion lists from multiple callbacks. For other scenarios, it would be easier for the completion callback to sort the items before it returns them, as that would allow it to use sort keys that are not strings.

@KalleOlaviNiemitalo
Copy link

Which CompletionResultType values PowerShell and PSReadLine recognize in CompletionResult.ResultType:

https://github.com/PowerShell/Crescendo does not mention CompletionResult.

Most CompletionResultType values look very specific to the PowerShell language and should not be used by System.CommandLine, unless perhaps the command itself parses PowerShell syntax.

I had hoped that CompletionResultType could be used for controlling whether path completion allows files or directories or is disabled, and perhaps also for syntax highlighting (distinguishing between subcommand names, path arguments, other arguments, valid options, and invalid options), but it does not look suitable for those purposes. For syntax highlighting, the diagram from the [parse] directive looks more useful, but starting a new process to reparse the command line each time the user types a new token would be rather inefficient.

@KalleOlaviNiemitalo
Copy link

KalleOlaviNiemitalo commented Jul 10, 2022

Microsoft's Language Server Protocol (LSP) is based on JSON-RPC. I suppose it would be reasonable to imitate interface CompletionItem from there and use JSON for serializing the completion items to the stdout of the command-line app. This would need some kind of negotiation for the output format, perhaps one of:

  • A new [suggest-json:position] directive to be used instead of [suggest:position], and dotnet-suggest register --json to indicate that the command-line app supports this directive. Likewise a feature flag in DotNetToolSettings.xml (dotnet-suggest assumes that dotnet global tools support the [suggest] directive #1777).
  • A new [suggest-json] directive to be used together with [suggest:position], and then some way to indicate that the command-line app recognized [suggest-json] and that the output is JSON. That could be one of:
    • Magic string at the start of the output.
    • Exit code 0x4a534f4e ("JSON" in network byte order). This would require changing SystemNative_WaitPidExitedNoHang to call waitid rather than waitpid and read the 32-bit exit code from siginfo_t::si_status. (See Austin Group defect report 0000594.)
    • Write the JSON output to a different file descriptor.

The command-line app would only have to output JSON rather than parse JSON, so it would not need to spend time JIT-compiling any JSON parser libraries, unless application-specific completion functions need to read JSON files.

For repeated completion of arguments of the same command though, I think lower latency could be achieved by making e.g. PowerShell communicate with a long-lived completer process over pipes, so that it wouldn't need to start a new process every time. I don't know if the full Language Server Protocol is too heavyweight for this. It might be useful to have a hybrid solution in which the command-line app gets the first completion request as a directive and outputs the completions as soon as possible, but then loads the JSON parser stuff and waits for more completion requests over the pipe.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Completions Related to support for tab completion enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants