Some notes on supporting readline (tab) completion in your Python program

March 4, 2016

Adding basic readline-style line editing to a Python program that reads input from the user is very simple; as the readline module documentation says, simply importing the module activates this without you having to call anything. However, adding completion is less well documented, so here are some notes about it.

First, you need both a readline completion binding and to register a completion function. The easiest way to get a completion binding is just to set it up explicitly:

readline.parse_and_bind("tab: complete")

You may also want to change the delimiter characters with readline.set_completer_delims. In my own code, I reduced the delimiters to space, tab, and newline. Note that if you have possible completions that include delimiter characters, nothing complains and things sort of work, but not entirely.

So, now we get to completion functions. Readline needs a completion function, and it's easiest to show you how a simple one works:

comps = ["abc", "abdef", "charlie", "horse",]
def complete(text, state):
   # generate candidate completion list
   if text == "":
      matches = comps
   else:
      matches = [x for x in comps if x.startswith(text)]

   # return current completion match
   if state > len(matches):
      return None
   else:
      return matches[state]

readline.set_completer(complete)

You are passed the current 'word' being completed and a 'state', which is a 0-based index. Your completion function's job is to return the state'th completion for the current word, or something other than a string if you've run out of completions, and you'll actually be called with ever-increasing state values until you declare 'no more'. As we see here, the list of completions that you return does not have to be in alphabetical order. Obviously it really should be a stable order for any particular input word; otherwise things will probably get confused.

By the way, readline will completely swallow any exceptions raised by your complete() function. The only symptom of major errors can be that you get fewer or no completions than you expect.

Of course it's common to want to be a little smarter about possible completions based on the context. For instance, you might be completing a command line where the first word is a command and then following words are various sorts of arguments, and it'd be nice not to offer as completions things that would actually be errors when entered. To do this, you often want to know what is before the current word being completed:

def get_cur_before():
   idx = readline.get_begidx()
   full = readline.get_line_buffer()
   return full[:idx]

Because words being completed stop at delimiter characters, anything in this before-the-word text is what readline considers a full word (or words). Otherwise, it would be part of the word currently being completed on. If you want to know what the first complete word of the line is, you can thus do something like:

   pref = get_cur_before()
   n = pref.split()
   cmd = n[0] if len(n) > 0 else ""

You can then use cmd to decide what set of completions to use. Other options are possible with the use of various additional readline functions, but this is all I've needed to use so far for the completions in my code.

Given that your complete() function is being called repeatedly every time the user hits TAB, and that it does all of this examination and selection and matching every time it's called, you might worry about performance here; it sure seems like there's a lot of duplicate work being done here. The good news is that modern computers are very fast, so you probably aren't going to notice this. If you do worry about this, what the rlcompleter module does is that it generates the list of matches when state is 0 (and caches it), and uses the already-cached list whenever state is non-zero. You can probably count on this to keep working in the future.

Speaking from personal experience, it was not all that much work to add readline completion to my program once I worked out what I actually needed to do, and having readline tab completion available is surprisingly fun.

(And of course it's handy. There's a reason everyone loves tab-completing things when they can.)

PS: remember to turn off readline completion if and when it's no longer applicable, such as when you're getting other input from the user (perhaps a yes/no approval). Otherwise things can get at least puzzling. This can be done with readline.set_completer(None).

Written on 04 March 2016.
« My views on clients for Lets Encrypt
What happens when a modern Linux system boots without /bin/sh »

Page tools: View Source, Add Comment.
Search:
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Fri Mar 4 01:14:10 2016
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.