Posted on

Tab Completion for Custom Commands

A lot of commands in Emacs take input from the minibuffer, the little one-line area at the bottom of the editor. When you are writing your own commands you will often make use of the same facility. In this article I want to show you one way of doing that which will provide tab completion for your custom functions.

I’m going to use a simple example out of my own dot Emacs file. This is the function that was in my last email, my function for opening org-mode log files. I keep one org-mode file for each project, in a log directory, split up by year. So I wanted a simple command I could bind to a key that would let me type in a project name and open the file. So I have this:

(defun open-project-log-file (project)
      "Opens a log file for a project."
      (interactive "sProject: ")
      (find-file (concat "~/Documents/Logs/2010/" project ".org")))

Then when I press C-H-x l I get the ‘Project:’ prompt in the minibuffer. I type in something like ‘Foo’, press Enter, and there’s my org-mode file.

This short example demonstrates the easiest way of getting a string of input in your commands: interactive. Emacs makes a distinction between functions which are called interactively, and those which are not. When you call a function non-interactively that means you are calling it from inside of Lisp code. If I wrote

(open-project-log-file "Foo")

then that is a non-interactive call. But when I run the command from my key-binding, or if I did M-x open-project-log-file, then that is an interactive call. When I do that Emacs looks at the ‘interactive’ form at the top of the function to figure out how to get the argument for the function.

We can use interactive in a couple of different ways. The simpliest is by giving it a string that describes how we want to prompt for and read in arguments. The parts of the string are separated by ‘
’ characters. The first character of each part tells Emacs what kind of input to read in. Then everything from that character until the next ‘
’ or end of string is the prompt.

So in the case of “sProject: “ that means to read in a string (‘s’) from the prompt ‘Project: ‘. Whatever I type in is assigned to the ‘project’ parameter of the function. Another example:

(defun interactive-example (foo bar baz)
      (interactive "bFoo: 
aBar: 
fBaz: ")
      ...)

This example has three prompts, setting ‘foo’ to a buffer name (‘b’), ‘bar’ to the name of a function (‘a’), and ‘baz’ to the name of a file (‘f’). In all cases Emacs knows what we are wanting to read in and provides us with completion help. For example, it knows what buffers and files are open, and what functions exist, so we can tab-complete on all of these.

If you look at the documentation on interactive you will see that it accepts a large number of codes for defining your input. But they can’t cover everything. There comes a time when you’ll want to have tab-completion available for some known set of values. This is where completing-read comes into the picture.

Let’s say I want to redefine my log function so that Emacs will know which files I might be looking for. That is, what log files exist in my log directory? Easy way to find out:

(directory-files "~/Documents/Logs/2010/")

Returns all of the files in that directory as a list of strings. I can use that as my set of possible inputs. Whenever I open a log file, the file I want will always be in the list. So what I want is for Emacs to tab-complete based on the values in that list. The function completing-read does this exactly.

(completing-read "Project: "
                     (directory-files "~/Documents/Logs/2010/"))

This line of code will print out the ‘Project: ‘ prompt as usual and read in input from the minibuffer. The second argument is the ‘collection’, a list of strings which represent valid input values, and which Emacs can tab-complete on. This is the minimum needed to use completing-read. However, it takes six additional, optional arguments. If you want to restrict completion to a sub-set of those values, or allow input outside of that set, then you’ll need to pass values to those optional arguments. As always, C-h f is the fastest way to find this information.

So let’s look at a smarter version of open-project-log-file:

(defun open-project-log-file (project)
      "Opens a log file for a project."
      (interactive
       (list
        (completing-read "Project: " (directory-files "~/Documents/Logs/2010/"))))
      (find-file (concat "~/Documents/Logs/2010/" project)))

Here is an example of the other form of interactive: using Lisp code instead of strings. In this form, the code given to interactive needs to return a list of values that will be assigned to the function parameters. The easiest way to create a list in Lisp is with the ‘list’ function.

(list 10 20 (+ 100 1)) => ‘(10 20 101)

In my case, I’m just creating a list of one value, the result of completing-read.

The rest of the function is the same. But now when I run it and type ‘F’, I get the benefit of hitting Tab and having ‘Foo.org’ show up in the minibuffer. If you are writing a custom command and can think of a way to define the set of inputs that function will take, even if it is incomplete, then you can use completing-read to get the benefits of tab-completion in your functions.