You're an ΓΌber 1337 H4x0r, so you never use the graphical user interface - only the command line. But as you're typing away, happily hacking about, you start getting frustrated, because you have to retype the same commands over and over again: why can't you just hit tab, and let the system autocomplete the flags and arguments for you? So you search the internet, and find the whole [three pages](https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion.html) it has to offer on the subject. You try reading them, but it's about as fun as reading a phonebook, so you look for a practical example instead. Git has autocompletion for its subcommands and parameters, right? The problem is, [it's more than 3000 lines of agony](https://github.com/git/git/blob/master/contrib/completion/git-completion.bash). Not that Bash is a bad language: it's a great language, with neat one-liners that would take many lines of code in Python; but as far as string manipulation goes, like splitting them, looping over them and filtering them out - it just isn't the best choice. Luckily, the gist is pretty simple: you call ``complete -F function command``, and it binds that function to that command; so whenever the command is typed and tab is hit, the function is called with some environment variables, and needs only define the ``COMPREPLY`` array, whose values are used for autocompletion. ```bash _complete_prog() { # Consider $COMP_WORDS[$COMP_CWORD] etc. local OPTIONS="foo bar foobar" COMPREPLY=( $OPTIONS ) } complete -F _complete_prog prog ``` ```shell $ source prog-completion.sh $ prog <TAB> foo bar foobar ``` This example is actually inaccurate, because it always generates the same three autocompletion options: typing ``f<TAB>`` would still offer ``bar``. In reality, we'd have to filter options based on the current word, like so: ```bash _complete_prog() { local OPTIONS="foo bar foobar" local CURRENT="${COMP_WORDS[$COMP_CWORD]}" COMPREPLY=( $(compgen -W "$OPTIONS" -- "$CURRENT") ) } complete -F _complete_prog prog; ``` But hey, here's a cool idea: let's do that in Python. ```bash _complete_prog() { local CURRENT="${COMP_WORDS[$COMP_CWORD]}" COMPREPLY=( $(python autocomplete.py "$CURRENT" ) ) } complete -F _complete_prog prog; ``` Now we can parse everything in a language that's much better equipped to split strings, loop over them and filter them out; the only nuisance left is the setup. You'd have to install the Python script, and source the Bash script that references it - and do so for *every* program you'd want to autocomplete. Bah. Let's write a small autocompletion framework in Python instead. First, check out this neat trick: ``` ''':' echo 'bash' exit ''' print('python') ``` This is a [polyglot](https://en.wikipedia.org/wiki/Polyglot_(computing)) script that runs in both Bash and Python. In Bash, the first line is an empty string, and then a string of a colon; these strings are concatenated and resolved as the ``:`` command, which is a NOP (no operation) instruction. The second line echoes bash, and the third line exits - so the rest of the script is discarded. In Python, the first four lines are a docstring, so they are skipped, and the last line prints python. Neat, huh? ```shell $ bash polyglot bash $ python polyglot python ``` The reason this is interesting, is that it means we can put our entire autocompletion framework in a single self-contained file that's both Python and Bash. We'd have to source it alright (for example, in our ``.bashrc`` or ``.profile``), but then it'd re-invoke itself - as Python! ```python ''':' autocomplete() { local COMMAND=$1 eval " _complete_$COMMAND() { local THIS_FILE=\$(readlink -f \${BASH_SOURCE[0]}) local CURRENT=\${COMP_WORDS[\$COMP_CWORD)]} COMPREPLY=( \$(python \$THIS_FILE \"\$CURRENT\") ) } complete -F _complete_$COMMAND $COMMAND " } return ''' import sys options = 'foo', 'bar', 'foobar' if __name__ == '__main__': cword = sys.argv[1] match = [word for word in options if word.startswith(cword)] print('\n'.join(match)) ``` Sourcing this file defines the ``autocomplete`` function, which accepts some command (for example, ``prog``), and binds it to a function (``_complete_prog``) that re-invokes the file as Python, and lets it generate the autocompletion options for this command. ```shell $ source autocomplete.py $ autocomplete prog $ prog <TAB> foo bar foobar ``` Of course, having the same autocompletion options for every command is not very useful, so we'd have to pass this function some autocompletion specification as well, to take into account when generating options for that command. ```bash ''':' autocomplete() { local COMMAND=$1 local COMSPEC=$2 eval " _complete_$COMMAND() { local THIS_FILE=\$(readlink -f \${BASH_SOURCE[0]}) local CURRENT=\${COMP_WORDS[\$COMP_CWORD)]} COMPREPLY=( \$(python \$THIS_FILE $COMSPEC \"\$CURRENT\") ) } complete -F _complete_$COMMAND $COMMAND " } return ''' ... ``` Now we only have to invent a format for these autocompletion specification. How about: ``` one --foo --bar two <path> ``` So typing ``prog`` and hitting tab would offer us ``one`` and ``two``; and if we go for ``one``, typing ``--`` and hitting tab will offer us ``--foo`` and ``--bar``; and if we go for ``two``, hitting tab will offer us the filenames in the current directory, including the standard filesystem traversal in search of our target path. Except, let's make it even cooler. What if the autocompletion specification look like this: ``` one --foo --bar <path> two <user> ``` That is, we want two to autocomplete... usernames from a database! That's something we wouldn't have dared imagine if we were implementing this in Bash: it's possible, yeah, but it's terrible enough so we'd just keep our head low and try to get the bare minimum working. But in Python... sure, why not? Of course, the autocompletion framework itself wouldn't know how to connect to a database and query it - so let's make the specification file... a module! ```python # one --foo --bar <path> # two <user> import sqlite3 def complete_user(prefix): query = 'SELECT username FROM users WHERE username LIKE ?' with sqlite3.connect('users.db') as conn: return [row[0] for row in conn.execute(query, (prefix+'%',)] ``` That is, the autocompletion would not only parse the specification file to determine what kind of autocompletion we'd like for our command, but also evaluate it, and use any ``complete_something`` functions to generate options for ``<something>``. Now all that is left is to write the code; and that you can find on [GitHub](https://github.com/dan-gittik/autocompletion). Have fun, and [let me know](mailto:dan.gittik@gmail.com) if you come up with mad autocompletion ideas!