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

Completion support #186

Open
3noch opened this issue Jul 2, 2022 · 18 comments
Open

Completion support #186

3noch opened this issue Jul 2, 2022 · 18 comments
Labels
v2-planned Planned for v2

Comments

@3noch
Copy link

3noch commented Jul 2, 2022

I love the design of this library. However I could not find any information about generating completion scripts from the reflected CLI. This would be a fantastic addition!

@alexflint
Copy link
Owner

Oh this would be great! I will likely look into this in the near future.

@alexflint
Copy link
Owner

alexflint commented Jul 3, 2022

Here is a library that sets up a separate binary that provides completion support for the primary binary: https://github.com/posener/complete/tree/master

Ultimately, completions are installed into bash by appending a complete -C command to .bashrc: https://github.com/posener/complete/blob/master/install/bash.go#L36

Here is how it looks for the go build system: https://github.com/posener/complete/blob/05b68ffc813dd10c420993cb1cf927b346c057b8/gocomplete/complete.go

@3noch
Copy link
Author

3noch commented Jul 4, 2022

I've seen tools add an optional subcommand like app generate-completions that is automatically derived by the CLI library, similar to how --help is derived automatically.

@alexflint
Copy link
Owner

Tagging as planned for v2

@alexflint alexflint added the v2-planned Planned for v2 label Oct 29, 2022
@atticus-sullivan
Copy link

Has there been any work on this yet? Is there anything one can do to help? (no intention to pressure you)

Maybe in addition to the references to the posner/complete project listed above, looking into how cobra does it (seems like they implemented this from scratch) or kongplete which implements this for the kong library (maybe interesting in particular since they also use the posner/complete library.

@alexflint
Copy link
Owner

@atticus-sullivan I have looked into this. It's not clear how to do it cleanly. It shouldn't be necessary to run the main Go program in order to get completions, because you might have a program that does some heavy work in init. It would be great to be able to generate a bash script that implements completions for a particular Go program (without needing to execute that Go program). The question then is where exactly to do such generation, and how to help users to hook it up to their shell.

@atticus-sullivan
Copy link

atticus-sullivan commented Jul 4, 2024

Oh yes, tbh I was a bit confused first when looking at posner/complete and then I thought something like well that's an odd approach.
Actually after looking a bit more into how cobra does this, it seems like they also execute the program to generate the list of completions.

Ok, then getting completion might get a bit harder since we found no library which we can base our implementation on. Nevertheless, having nice completion might be a unique selling point for this library (users most probably won't notice though ^^).

I wrote a few (simple) completion scripts, mainly for bash (tbh I always hated it) and I tested a bit with zsh (nicer capabilities for completion but I had a hard time with the documentation) but with fish I really have never worked with.
Also being as compatible as possible with older versions of the respective shell might become a challenge.

EDIT:

Examples for bash and zsh

Might be a weird example, but I was writing a command addGit <project name> and extracted some completion candidates from a file ./.link.md.

So in bash all you have/need is the compgen command and the variables COMP_WORDS, COMP_CWORD and COMPREPLY.

_addGit_completion(){
	local IFS=$'\n'
	local tmp cur prev
	COMPREPLY=()
	cur="${COMP_WORDS[COMP_CWORD]}"
	prev="${COMP_WORDS[COMP_CWORD-1]}"
	tmp=()

	if [[ "$COMP_CWORD" -eq 1 && -f ./.link.md ]]
	then
		# complete names
		tmp=( $(sed -nr 's/^- .*\/coding\/02_git\/(.*)$/\1/p' ./.link.md) )
		COMPREPLY=( $(compgen -W "${tmp[*]} ${PWD##*/}" -- "$cur") )
	else
		COMPREPLY=( $(compgen -W "${PWD##*/}" -- "$cur") )
	fi
}
complete -F _addGit_completion addGit # this hooks the completion function to the command

Same thing for zsh (simplified version without extracting from file, only suggest the name of the current directory):

_addGit(){
	local state
	_arguments \
		'1: :->name'
	case $state in
		(name) _arguments '1:names:(${PWD##*/})' ;;
	esac
}
compdef _addGit addGit

so for zsh it's easier to write these scripts and usually I think they are smaller, but understanding how the _arguments built-in works is a bit tough.

@atticus-sullivan
Copy link

Hooking it up to the shell should be fairly easy, you can always just source the file (or the here doc in bash to avoid writing the file) and add that line to the ...rc file. With zsh that might be a bit trickier but still possible as far as I remember.

Of course at least for zsh it is possible to write the completion stuff to a file, source this and zsh will optimize loading that file (but in order to do so, I think it needs to know the content beforehand to check if something has changed).

@3noch
Copy link
Author

3noch commented Jul 5, 2024

If there's an easier solution that doesn't worry about heavy init (probably somewhat of an edge case, really) that just runs in the main program, I'd suggest starting with that and then optimizing it as needed.

@alexflint
Copy link
Owner

@3noch In general I agree but in this case we don't have much of a way to optimize the init function of an application that uses this library, and there isn't much of a trajectory from online to offline completions (shifting from one to the other later would, it seems to me, be a rewrite).

@alexflint
Copy link
Owner

@atticus-sullivan Yeah that is the big downside of offline completions -- you have to implement for each shell! Maybe we should create a new library for cross-shell completions, and use it in this library.

@atticus-sullivan
Copy link

Sounds like a nice idea. I started experimenting a bit with generating completion code for zsh.

For me personally zsh is the most interesting as this is what I use currently. Also at least comparing bash and zsh, zsh is offers more completion features so starting there might be a good idea, adding support for bash later on (stripping features like the completion being able to show a description for the suggested flags or so).

Here's a gist with what I came up with until now. Things like specifying arguments as files/directories are present as a stub but do not work currently. Adding flags and subcommands (which in turn can have flags or other subcommands) should be possible.
Still I didn't really do more testing than what's currently in the main function.

Feel free to check it out. If you test this, feel free to leave me comments on the gist, also on how the arguments/flags are stored. While I think the current structure might work quite well, I didn't put that much thought into it. And just a short note for the next ~1 month I can't promise how much I can continue to work on this. I'd continue to work on this, but also feel free to extend/rewrite yourself if you want to.

@atticus-sullivan
Copy link

Done some further work, split into multiple packages and created a repository for this: https://github.com/atticus-sullivan/go-complete. Feel free to leave any feedback (also on the architecture if you want to), note bugs/missing features.
The project is kind of in a working state, but there is almost no input sanitizing and I'm not really satisfied with the completion for files/directories in bash.

Also fish is not supported yet as I have no experience with fish.

@gchiesa
Copy link

gchiesa commented Oct 24, 2024

is there any update on this? I really like the approach of this package, however I starting lacking the capability of offering autocompletion support to the users. I see the initiative (great work!) of @atticus-sullivan and it would also be ok to have a structured based autocomplete generation, but it would be cool to have 1:1 compatibility with the structure used in this pkg.

@atticus-sullivan
Copy link

The initial idea was to make go-arg generate this structure the other library uses as input.

As of now that is simply something I didn't find the time for.
Also, that other library is not that well tested, so I hesitate a bit bringing the code directly into go-arg. Not quite sure what a proper procedure here is.

@alexflint
Copy link
Owner

That library looks like excellent work @atticus-sullivan, and I don't know of anything else like it - very cool. I would love to work on this too. Perhaps I'll find time before the end of the year. Thanks for the ping @gchiesa

@abraithwaite
Copy link

@atticus-sullivan I have looked into this. It's not clear how to do it cleanly. It shouldn't be necessary to run the main Go program in order to get completions, because you might have a program that does some heavy work in init. It would be great to be able to generate a bash script that implements completions for a particular Go program (without needing to execute that Go program). The question then is where exactly to do such generation, and how to help users to hook it up to their shell.

FWIW, I don't think it's crazy to require the go program to be run in order to generate completions.

The way our build pipeline works is as such:

  1. Build the binaries
  2. Generate completion scripts
  3. Package & Ship

The user then installs the app with the completions already generated. Sure, this might not catch some edge cases with dynamic generation I suppose, but I'd be willing to bet it works for 80+% of use cases.

I'm hopping in here because this library looks like the most compelling alternative to cobra/viper, but we wouldn't be able to switch to a library without completion support because the users said so. 😅

All that said, I'm no shell expert so wouldn't be familiar with the limitations of the approach we take compiling the binary and generating the completions at packaging time. @atticus-sullivan 's code looks pretty promising though too. Hoping you both find something that works well!

@oderwat
Copy link

oderwat commented Dec 11, 2024

I want to bring attention to: https://carapace.sh/ which is pretty much the "support it and it works everywhere".

You could generate: carapace-spec

Even right now without a V2 it could be used by creating something similar to:

https://github.com/carapace-sh/carapace-spec-kingpin

I love "go-arg" and use it for a lot of little tools, while bigger ones mostly use "fisk" which ia a fork of kingpin and used by Synadia for their NATS tools.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
v2-planned Planned for v2
Projects
None yet
Development

No branches or pull requests

6 participants