dotnet / command-line-api

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

Unable to get tab completion to work #993

Open banbh opened 4 years ago

banbh commented 4 years ago

I created a simple DragonFruit console app, then followed the instructions in https://github.com/dotnet/command-line-api/wiki/dotnet-suggest (and resolved the execution policy issues in 64- and 32-bit) then I opened a "Developer Powershell" from Visual Studio, change directory to where the exe is and typed .\DragonFruit --f<TAB> (my Main method has an int foo argument) however it did not autocomplete (instead nothing happens). Things like .\DragonFruit -h work as expected. Also $availableToComplete is given a value during startup, namely "dotnet-suggest\ndotnet suggest".

How can I debug this?

jonsequitur commented 4 years ago

A couple of things to check.

Does .\DragonFruit [suggest] --f return results? If so, your app is able to return completions.

If you cat $profile in the PowerShell instance where you're expecting completions, do the contents contain the completions shim script? (Different profiles in different contexts has bitten me a few times.)

banbh commented 4 years ago

Thanks. For the experiments below I started a (PowerShell) terminal from VS2019 (View -> Terminal) and then changed directory all the way down to the notcoreapp3.1 containing the exe.

When I execute .\DragonFruit [suggest] --f I get --foo (which seems correct). cat $profile seems be correct.

jonsequitur commented 4 years ago

This comment describes an approach to debugging this if you'd like to give it a try:

https://github.com/dotnet/command-line-api/issues/794#issuecomment-600601807

banbh commented 4 years ago

I followed the instructions in the comment to #749 that you referenced, and I think it gives some insight into what is happening. Since I'm not certain I know what I'm doing, below are the explicit steps I followed.

I cloned https://github.com/dotnet/command-line-api.git and made a Debug build. Back in the DragonFruit terminal, I prepended the artifact directory (namely, C:\Users\MYUSERNAME\git\command-line-api\artifacts\bin\dotnet-suggest\Debug\netcoreapp3.1) to my path. At this point dotnet-suggest --version returns 1.1.0-dev (as expected). Next I set $env:DOTNET_SUGGEST_LOGGING=1. At this point dotnet-suggest --v<TAB> autocompletes to dotnet-suggest --version and the logfile (C:\Users\MYUSERNAME\AppData\Roaming\dotnet-suggest\debug.log) has the following lines appended.

dotnet-suggest received: |get|-e|C:\Users\MYUSERNAME\git\command-line-api\artifacts\bin\dotnet-suggest\Debug\netcoreapp3.1\dotnet-suggest.exe|--position|18|--|dotnet-suggest --v
dotnet-suggest sending: [suggest:3] "--v"
dotnet-suggest received: |[suggest:3]|--v
dotnet-suggest returning: "--version"

As best as I can tell this is all as expected. Finally I did .\DragonFruit.exe --fo<TAB>; this resulted in no autocompletion, and nothing appeared in the logfile.

From this is it seems like the "ArgumentCompleter" is simply not being called for my DragonFruit executable.

banbh commented 4 years ago

Below are some comments which probably won't directly help to resolve this issue, but might help me understand what is going on.

In trying to understand how this functionality is supposed to work a portion of the story I was confused by is what exactly is getting registered by the powershell shim. It seems to register (via Register-ArgumentCompleter) a script block to figure out the completions for a set of commands, namely dotnet-suggest and dotnet suggest. However the command that needs a suggestion is actually (in my case) .\DragonFruit.exe. Is what's happening that a <TAB> gets translated into a dotnet suggest ... which then is intercepted by the registered ArgumentCompleter?

jonsequitur commented 4 years ago

Not quite. Register-ArgumentCompleter called once for each executable registered by RegisterWithDotnetSuggest. But my memory is that this also should work with the executable directly.

Also, which versions of System.CommandLine and dotnet-suggest are you using?

michaelgwelch commented 4 years ago

I'm actually seeing the same issue with the Sample Command Line app on macOS/bash.

My own application does have auto-complete working for somethings. But it crashes with arithmetic overflow in a call to MaximumArgumentCapacity(). (This is sorta obvious to see if you look at the method which adds up all the maximum values of all arguments. If at least one is OneOrMore and then you have other arguments, you'll overflow. I created #997 to document this.) While trying to get this down to a minimal reproducible example I tried the sample CommandLine app. I don't get any suggestions for any of the options.

I did try app [suggest] -f and got back --file-option. But if I type tab after -f I get nothing.

I'll take a look at the suggestions in this thread to see if I can learn anything else.

Regarding MaximumArgumentCapacity(): https://github.com/dotnet/command-line-api/blob/d4f3327d62d3faa8e35bc11d0df86bc92fd881e1/src/System.CommandLine/Parsing/SymbolResult.cs#L47-L49 you can see that if you have a ZeroOrMore or a OneOrMore and then at least one ExactlyOne you'll get overflow. But typically this wouldn't throw an exception (but likely it would be a defect). I'm trying to reproduce why I'm getting an exception and will open a new issue if/when I do.

banbh commented 4 years ago

My DragonFruit app uses System.CommandLine.DragonFruit version 0.3.0-alpha.20371.2 (which, in turn, depends on System.CommandLine version 2.0.0-beta1.20371.2).

The version of dotnet-suggest I was initially using was 1.1.137102+f2556cacb35c0b68201d73eaf1c0df8c5e57e43e but then for the Debug build I cloned 1.1.0-dev.

jonsequitur commented 4 years ago

But it crashes with arithmetic overflow in a call to MaximumArgumentCapacity(). (This is sorta obvious to see if you look at the method which adds up all the maximum values of all arguments. If at least one is OneOrMore and then you have other arguments, you'll overflow. )

@michaelgwelch Yeah that's a pretty clear bug.

michaelgwelch commented 4 years ago

So I have a root command and one subcommand. The root command has no arguments or options (other than the "built-ins").

The name of my tool is enuf and the one subcommand is lookup

So when I type

> enuf <tab>

I see

enuf 
--help                                                              
--version                                                           
-?                                                                  
-h                                                                  
/?                                                                  
/h                                                                  
lookup 

So it's working. And the log file agrees with this.

Now I have one argument for lookup command. I never see any suggestions

> enuf lookup <tab>

But the log shows that there are suggestions:

dotnet-suggest received: |get|--executable|/Users/mgwelch/.dotnet/tools/enuf|--position|12|--|enuf lookup 
dotnet-suggest sending: [suggest:7] "lookup "
dotnet-suggest returning: "--help\n-?\n-h\n/?\n/h\nattributeCategoryEnumSet\nattributeEnumSet"

(Sometimes it throws all the root level built-in switches into the list and some times it doesn't.)

If I explicitly ask for suggestions using directive

> enuf [suggest] lookup 

Nothing is returned.

banbh commented 4 years ago

I wonder whether someone could explain how one aspect of the PowerShell completion architecture works. Concretely, suppose I clone the command-line-api repo, build it, open a PowerShell in the directory with the DragonFruit exe, and type (say) .\DragonFruit --f<TAB> (which, incidentally still produces nothing). How is this supposed to result in suggestions being generated? I assume hitting <TAB> is supposed to cause dotnet-suggest get ... to be executed; but what is the mechanism for that to happen?

In contrast, if I type dotnet-suggest <TAB> (which does work) then I understand the pathway (namely, the shim in my profile has already registered an ArgumentCompleter for the command dotnet-suggest; since I just typed that command it fires off a request to dotnet-suggest get ...). But, returning to DragonFruit, how does PowerShell know it's supposed to call dotnet get when presented with .\DragonFruit? It's as if what is needed is a (hypothetical) call to Register-Argument-Completer -Native -Command "*" ... (meaning try to complete anything with ...).

[By the way, following the explanation of @jonsequitur, I have verified that RegisterWithDotnetSuggest does get called successfully when (say) .\DragonFruit [suggest] is called. In fact if you delete the sentinel file then I can see dotnet-suggest register ... being called. In short, as far as I can tell, everything is working the way it's supposed to (IMO) except that nothing causes dotnet-suggest get ... to fire for random executables like .\DragonFruit.]

jonsequitur commented 4 years ago

If I explicitly ask for suggestions using directive

enuf [suggest] lookup Nothing is returned.

Directives are only valid as the first token in the command line, so this wouldn't be expected to work. The following should:

> [suggest] enuf lookup