Skip to content

PComplete: Context-Sensitive Completion in Emacs

by mickey on January 16th, 2012

In my What’s New In Emacs 24 series (part one, part two) I briefly mentioned that pcomplete, the programmable completion library featured prominently in Eshell, now supports M-x shell out of the box. That’s great news for shell mode fans as the completion mechanism adds a lot of nifty functionality to a mode that lacks the native completion provided by underlying the shell itself.

The most amazing thing about the completion mechanism is that it has been in Emacs for ages but never made much of a public appearance and has gone virtually unnoticed due to its limited use in Emacs. In fact, I think it’s only used in EShell, ERC, Org Mode and now, finally, Shell Mode.

Programmable, Context-Sensitive Completion

To use pcomplete you won’t have to do anything, because as of Emacs 24 it is now supported automatically when you launch a new shell session. Emacs ships with a handful of pcomplete functions that enhance the otherwise drab filename completion with context-sensitive completion similar to what you can do with bash/zsh completion. Of particular note is the scp, ssh, mount, umount and make support.

The following table lists the commands supported by shell mode (or indeed any mode that supports pcomplete, including Eshell.)

Command Notes
bzip2 Completes arguments and lists only bzipped files.
cd Completes directories.
chgrp Completes list of known groups on the system.
chown Completes user and group perms, but only if you use user.group.
cvs Completes commands and parameter options and cvs entries and modules.
gdb Completes only directories or files with eXecute permission.
gzip Completes arguments and lists only gzipped files.
kill Lists signals if completed with just a -, otherwise it completes all system PIDs.
make Completes arguments and valid makefiles in the directory; if a valid makefile is completed with make -f FILE a list of rule names from the file itself are completed.
mount Completes arguments and valid filesystem types if completed with mount -t TYPE.
pushd Identical to cd.
rm Completes arguments and filenames and directories.
rmdir Completes directories.
rpm Very sophisticated completion mechanism for most of rpm, the Redhat Package Manager. Context-sensitive completion for almost all commands, including package lookup.
scp Completes arguments, SSH known hosts and remote file lookup (using TRAMP) if the format is scp host:/.
ssh Completes arguments and SSH known hosts.
tar Completes arguments, including context-sensitive completion for POSIX arguments, and file name completion.
time Completes directories and files with eXecutable permission.
umount Completes arguments, mounted directories and filesystem types (like mount)
which Supposed to provide simple filename completion of all known binaries (wouldn’t be useful otherwise!) but appears to not work right.
xargs Completes directories and files with eXecutable permission.

Custom Completion

It goes without saying that a completion library called programmable completion is, well, programmable.

Adding simple parameter completion is an easy job but anything more than that and it gets hairy as, not surprisingly, this library is virtually undocumented (though an optimist would say the source is all the documentation you need…)

I’ll demonstrate how to add rudimentary support for git.

The first thing we need to do is establish the order in which parameters must be given; for git, it’s somewhat consistent: git [options] <command> [<args>]

For now I’ll stick to the commands as that’s what people use the most anyway. The commands, in list form, are:

The syntax for pcomplete is rather clever: it will use dynamic dispatch to resolve the elisp function provided it is named a certain way. All commands are named pcomplete/COMMAND or pcomplete/MAJOR-MODE/COMMAND. Provided you follow that naming scheme your command will automagically work.

Next, we need to present a list of valid commands — in this case the ones in pcmpl-git-commands, but it could be any form — to the command pcomplete-here.

Now when you try to tab-complete the first argument to git it will list our commands. Sweet.

Let’s extend it further by adding support for the add and rm commands. I want the aforementioned commands to provide the standard filename/filepath completion if, and only if, the command is add or rm.

This is surprisingly easy to do using pcomplete-match, a function that asserts a certain regexp matches a particular function argument index. Note that the call to pcomplete-here is in a while loop; this is so you can complete as many files as you like, one after another. One advantage of pcomplete-here is that it won’t display files you have already completed earlier in the argument trail — that’s very useful for a command like add.

Ok, that was easy. Now let’s make it a bit more dynamic by extending our code to support the git checkout command so it will complete the list of branches available to us locally.

To do this we need a helper function that takes the output of a call to shell-command and maps it to an internal elisp list. This is easily done with some quick hackery.

The variable pcmpl-git-ref-list-cmd holds the shell command we want Emacs to run for us. It gets every ref there is and we then filter by sub-type (heads, tags, etc.) later. The function pcmpl-git-get-refs takes one argument, type, which is the ref type to filter by.

And finally, we put it all together. To keep the code clean I’ve switched to using a cond form for readability.

And that’s that. A simple completion mechanism for git. Put this in your .emacs or init file and you’re done.
/code

From → All Articles

14 Comments
  1. Andrei permalink

    How to extend pcomplete to work for a programming mode to get completion while writing code?

    • mickey permalink

      That’s a rather complicated question for which there is no simple answer. PComplete wouldn’t be the right tool for that job. Look at semantic-mode and CEDET.

      • Andrei permalink

        Yes, sorry, I meant to ask if it would be feasible to do. auto-complete can help implement completion in a simpler way than full-blown Semantic/CEDET support, which is quite more complicated to implement.

  2. Thank you very much for this example. However, it looks like the Emacs community is going to reproduce the effort spent by the Bash community. For example, bash completion handles git very well. Would it be possible to replace pcompletion by bash completion?

    • mickey permalink

      There may be some overlap between the two sets but things like using TRAMP to resolve remote files is not possible (that I’m aware of anyway) with Bash of Zsh.

      You might be able to call out to a bash script and ask the individual shell scripted completion routines for a list of candidate matches.

      • Actually, bash can complete remote filenames with scp. My understanding is that’s it’s completion mechanism is pretty general purpose and extensible.

        That said, I’ve always found bash scp completion to be kind of slow and flakey.

  3. Dave F permalink

    Great general purpose tip, thanks.

    As for bash completion that git has, perhaps we can glue it in, but pcomplete seems like it’s a more general solution that I could expand to add all my custom SQL commands accumulated over the years. Some of these commands depend on current schemas that can be obtained from the backend by pcomplete.

  4. Jorge permalink

    Thank you for this post. Is there something that can be done so that if you start with sudo, the default completion starts. Right now sudo completes for names of files in the folder.

  5. Your post seems to suggest that eshell-style completion works with M-x shell out-of-the-box now, but I don’t see it in Emacs 24.0.93. For instance, if I have a few directories starting with “a”, in eshell I type cd a and hitting repeatedly cycles me through the options. In M-x shell, it pops up the completion buffer, as before.

    This is the main reason that I still turn to an external terminal for my bash needs — bash completion is pretty great, and I’d love to see a semblance of it in Emacs.

  6. John Wiegley permalink

    Great article! Thanks for providing users with such a nice introduction to pcomplete’s functionality.

  7. In the body of pcmpl-git-get-refs, you are using add-to-list with a local list variable for accumulation. This practice has two problems. One problem is that when the parameter name that add-to-list internally uses to hold the list variable symbol and the local list variable name that the user of add-to-list uses for accumulation is same, surprise happens:

    (let ((list-var (list 1 2 3))) (add-to-list (quote list-var) 0) list-var) ; => (1 2 3)

    Another problem is that when someone copy and paste your definition of pcmpl-git-get-refs into an el file which happens to be lexically scoped, he/she will find that every call of pcmpl-git-get-refs causes error: “Symbol’s value as variable is void: ref-list”

    Quoting a local variable name is what’s causing these gotchas. I am going to write an article about this, but let me just shortly mention here two ways to avoid quoting the accumulating list variable.

    One way is to define and use a macro version, add-to-list-q, of add-to-list:

    (add-to-list-q ref-list (match-string 1))

    The second way is to define and use a function, added-to-list, that returns the resulting list:

    (setq ref-list (added-to-list ref-list (match-string 1)))

Trackbacks & Pingbacks

  1. Completion for GHCi commands in Emacs « Oleksandr Manzyuk's Blog

Leave a Reply

Note: XHTML is allowed. Your email address will never be published.

Subscribe to this comment feed via RSS