Debugging tab completion

Tab completion in bash, even without snapd, is a towering edifice built using nothing but toothpicks. I’m amazed every time it works.

Adding snapd to the mix makes things a whole lot harder to figure out, because we need to marshal the tab completion request into a confined space, work out what you want, and marshal the response back out. Every single tab completer you use is designed to run in-process, and will break if you even change a completer to run as a subshell.

All this to say: there’s a lot to debug, so take a deep breath; we’re going in.

First step: do you have a completer.

For your app to have tab completion, you need to add a line to your snap.yaml. If you’re building your snap using snapcraft, then you can add it to your snpacraft.yaml and snapcraft will pass it along. This line should sit at the same level as the command of the app you’re wanting to have tab complete; for example

name: foo
version: v1
apps:
  bar:
    command: bar
    completer: bar-completer.sh # this is the one!

Install the snap (you can use snap try from the prime directory if you’re using snapcraft and haven’t pushed it to the store yet, otherwise snap install foo).

If everything went well, you will now have a completer: line like the above in /snap/foo/current/meta/snap.yaml, and a /var/lib/snapd/desktop/bash-completion/completions/foo.bar that should be a symlink to snapd’s complete.sh:

~$ grep completer /snap/foo/current/meta/snap.yaml 
    completer: bar-completer.sh
~$ readlink /var/lib/snapd/desktop/bash-completion/completions/foo.bar
/snap/core/current/usr/lib/snapd/complete.sh

If you don’t have the first thing, you need to figure out why your completer line isn’t making it to your snap. Maybe it’s in the wrong part of your snapcraft.yaml? Maybe your snapcraft is antediluvian? (snapcraft has had support for completer since 2.33, which is from July 2017).

If the symlink wasn’t created: is your operating system somehow blocking access for snapd to create it? Maybe snapd and selinux are fighting again. Let us know, we can probably sort it.

If there is a file there but it isn’t a symlink: we try to be very polite and not overwrite things in that directory, as it’s shared with the classic package manager. If you had something there with the same name that we tried to create it, we skip creating the symlink. You could figure out if it’s safe to remove that file, and reinstall the snap. Alternatively, edit that file so that if it’s trying to complete for the snapped version of the app, it runs _complete_from_snap. Again, talk with us if you’re getting stuck here.

If all is well so far, let’s go on!

2. can snap find the completer as specified

Next, let’s see if the completer is alright. Let’s hop into the snap’s view of the world and check:

~$ snap run --shell foo.bar
bash4.3$ echo $SNAP
/snap/foo/123
bash-4.3$ cd $SNAP
bash-4.3$ grep completer meta/snap.yaml
    completer: bar-completer.sh # this is the one!
bash-4.3$ less $( awk '/completer/{print $2}' meta/snap.yaml )

The latter is a lot to type, but it’s just answering the question: is the completion script that you specified readable from the $SNAP directory? (we cd there because it’s a relative path). If you can cat, less, or otherwise look at the completion script from within this confined shell, you’re ok. exit that shell and let’s move on.

If you can’t read the script: is the path right? are the permissions on it and all the path leading to it such that it can be read? Remember it’s going to be looked for relative to the root of the snap ($SNAP above). This one should be easy to figure out, but reach out if you can’t. Good luck!

3. Is bash completing via snap for this command?

Next, let’s see if bash is getting the message. First, if you’ve never tried to complete foo.bar before in the current (bash) session, you should have no completion loaded for it:

$ complete -p foo.bar
bash: complete: foo.bar: no completion specification

if it does have a completion specification, start a new bash and try again. If a brand new shell has a completion specification, your shell is loading a completer on startup. Try running bash -x and look for something that could be doing that. I’ve found that older versions of lxd would leave behind a /etc/bash-completion.d/lxc after removing the .deb, and this would mess with the lxd tab completion, for example.

Ok, so, pristine shell, no completion spec. Let’s try to load it!

Type foo.bar (with a space after it, that discourse is hiding from you), and hit tab, just the once. Cancel, and try the complete command again

$ foo.bar <tab>^C
$ complete -p foo.bar
complete -F _complete_from_snap foo.bar

If you got any other completion spec, again you need to figure out where it’s coming from. Look at the default, complete -p -D; it should be _completion_loader, but maybe it’s something else.

4. is the plumbing doing its thing?

Next, let’s look at it in action. Open a new shell with bash -x, clear the screen (there’s going to be a lot of chatter), type your command, a space, and hit tab just the once.

$ bash -x
[ tons of chatter elided ]
$ reset
$ foo.bar <tab>

what you see won’t be pretty, but it should look a little bit like

+ read -r -a opts
++ snap run --command=complete foo.bar 9 9 8 1 ' 	
"'\''><;|&(:' 'foo.bar ' foo.bar ''
+ [[ 0 -gt 0 ]]
+ read -r bounced
[ a lot more chatter ]

importantly, you should have a snap run command looking almost exactly like that (the argument values might be different, but not the number of them – and note that there’s one argument that splits into a new line; it’s a long thing). If it’s missing (and you haven’t skipped the previous steps in this), reach out to us; something’s broken but I can’t tell what. If it’s there but significantly different (e.g. it has less numbers), your core snap is probably ancient, or the completion symlink is not pointing into core, and your distro’s snapd is ancient. Sort that one out (again, reach out if you’re stuck!).

If you’re interested, that command is heavily documented in complete.sh, you could go read it. But we’re going to run it by hand, and for that we can just do

$ snap run --command=complete foo.bar 9 9 8 1 " " 'foo.bar ' foo.bar ""

I’ve pruned down the delimiter list (that’s the long one that splits the line) to just space, and alternated the double and single quotes so you don’t get too lost in there.

This should give you the list of completions for the command. If instead you see an error from etelpmoc.sh saying “no completion specification”: does your completion snippet set up a completer for your unaliased, snap.app command? It should have a line, typically at the end, that looks something like complete -F something_something foo.bar. It could also mention bar on its own as well as foo.bar, if bar is the canonical name of the thing and you also expect people to use this snipped outside of a snap. But the snap will always see the command “unaliased”.

If you see a different error from etelpmoc.sh, most like we broke something! So sorry. Please let us know; we’ll sort it. Most often though you’ll see an error from your script. You should be able to debug as usual, here.

5. one last hope

If running via snap run is proving too challenging, you can also hop into the snap shell, load bash completion, and try to run things there. You won’t be able to actually run the command, or edit the script, but you’ll see the errors as they happen.

~$ snap run --shell foo.bar
bash4.3$ echo $SNAP
/snap/foo/123
bash-4.3$ cd $SNAP
bash-4.3$ source /usr/share/bash-completion/bash_completion
bash-4.3$ source bar-completer.sh # or whatever your completer was!
bash-4.3$ foo.bar <tab>
[ much debugging elided ]

Good luck!

3 Likes

No free software projects were harmed in the writing of this tutorial.

https://github.com/lxc/lxd/pull/4282

2 Likes

This has changed to /var/lib/snapd/desktop/bash-completion/completions/ CC @degville

:wave: Awesome, thank you! Should be fixed now.

2 Likes