dotnet / command-line-api

Command line parsing, invocation, and rendering of terminal output.
https://github.com/dotnet/command-line-api/wiki
MIT License
3.38k stars 380 forks source link

Using System.CommandLine for powershell completers #1220

Open powercode opened 3 years ago

powercode commented 3 years ago

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 commented 3 years ago

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

KalleOlaviNiemitalo commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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:

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.