Hack 5. Autocomplete Perl Identifiers in Vim


Hack 5. Autocomplete Perl Identifiers in Vim

Why type a full identifier if your editor can do it for you?

Good variable and function names are a great boon to productivity and maintainability, but brevity and clarity are often at odds. Instead of wearing out your keys, fingertips, and memory, consider making your text editor do the typing for you.

The Hack

If you use Vim, you have access to a handy autocompletion mechanism. In insert mode, type one or more letters of an identifier, then hit CTRL-N. The editor will complete your identifier using the first identifier in the same file that starts with the same letter(s). Hitting CTRL-N again gives you the second matching identifier, and so on.

This can be a real timesaver if you use long variable or subroutine names. As long as you've already typed an identifier once in a file, you can autocomplete it ever after, just by typing the first few letters and then CTRL-Ning to the right name:

sub find_the_factorial_of
{
    my ($the_number_whose_factorial_I_want) = @_;

    return 1 if $the_n<CTRL-N> <= 1;

    return $the_n<CTRL-N> * find<CTRL-N>($the_n<CTRL-N> - 1);
}

Unfortunately, Vim's idea of an identifier (in Vim-speak, a "keyword") isn't as broad as Perl's. Specifically, the editor doesn't recognize the colon character as a valid part of an identifier, which is annoying if you happen to like multipart class names, or qualified package variables.

However, it's easy to teach Vim that those intervening double-colons are valid parts of the identifiers. Add them to the editor's list of keyword characters by adding the line to your .vimrc file:

set iskeyword+=:

Then the following works too:

use Sub::Normal;

my $sub = Sub<CTRL-N>->new( );  # Expands to: Sub::Normal->new( )

Finding Identifiers Automatically

Of course, you still have to type the full name of Sub::Normal once, as part of the initial use statement. That really isn't as Lazy as it could be. It would be much better if Vim just magically knew about all the Perl modules you have installed and could cleverly autocomplete their names from the very first time you used them.

As it happens, that's easy to arrange as well. You just need a file that lists every module you have installed. Then tell Vim (in .vimrc again) to use all the identifiers in that file as a second source of keyword completions:

set complete+=k~/.vim_extras/file_that_lists_every_installed_Perl_module

The complete+=k tells Vim you're adding to the existing sources of completions for keywords. The path name that follows specifies the file containing the additional completions.

All you need is a simple Perl script to generate that file for you:

use File::Find 'find';

# Where to create this list...
my $LIST_DIR  = "$ENV{HOME}/.vim_extras/"
my $LIST_FILE = "file_that_lists_every_installed_Perl_module";

# Make sure the directory is available...
unless (-e $LIST_DIR )
{
    mkdir $LIST_DIR
        or die "Couldn't create directory $LIST_DIR ($!)\\n";
}

# (Re)create the file...
open my $fh, '>', "$LIST_DIR$LIST_FILE"
    or die "Couldn't create file '$LIST_FILE' ($!)\\n";

# Only report each module once (the first time it's seen)...
my %already_seen;

# Walk through the module include directories, finding .pm files...
for my $incl_dir (@INC)
{
    find
    {
        wanted => sub
        {
            my $file = $_;

            # They have to end in .pm...
            return unless $file =~ /\\.pm\\z/;

            # Convert the path name to a module name...
            $file =~ s{^\\Q$incl_dir/\\E}{ };
            $file =~ s{/}{::}g;
            $file =~ s{\\.pm\\z}{ };

            # Handle standard subdirectories (like site_perl/ or 5.8.6/)...
            $file =~ s{^.*\\b[a-z_0-9]+::}{ };
            $file =~ s{^\\d+\\.\\d+\\.\\d+::(?:[a-z_][a-z_0-9]*::)?}{ };
            return if $file =~ m{^::};

            # Print the module's name (once)...
            print {$fh} $file, "\\n" unless $already_seen{$file}++;
        },
        no_chdir => 1,
    }, $incl_dir;
}

Of course, you don't have to call the file . vim_extras/file_that_lists_every_installed_Perl_module. Just change the $LIST_DIR and $LIST_FILE variables to something saner.

Hacking the Hack

It's a natural next step to automate the generation of this file via cron. Beyond that, though, consider using Vim auto-commands to update the module list when you load and save files. To get information on auto-commands, type :help autocmd-intro within Vim. You could also check in and check out these module lists from a central repository to ensure that your editor knows about the class your coworker just added.

For a final coup-de-grace, consider extracting variable and subroutine names from the files as well. This will let you complete method names and exported variables. You could do this with regular expressions as heuristics, or through modules such as Parse::Perl.

Emacs users take heart. You can usually find equivalents by searching the web for taskname cperl-mode. Here's an autocompletion minor mode to add to your ~/.emacs file:

(defadvice cperl-indent-command
    (around cperl-indent-or-complete)
    "Changes \\\\[cperl-indent-command] so it autocompletes when at the end of a word."
    (if (looking-at "\\>")
        (dabbrev-expand nil)
      ad-do-it))
  (eval-after-load "cperl-mode"
    '(progn (require 'dabbrev) (ad-activate 'cperl-indent-command)))