10 March 2014

Manage small groups of related files with Vim's argument list

Starting Vim

vim index.html js/main.js css/*.css

When you start Vim from the command line, you have the option of passing any number of files as arguments. In addition to exact filenames, you can supply globs, which are simple patterns that match files using wildcards, as in css/*.css. Each filename resulting from the expansion of all supplied arguments will be added to the argument list (arglist, for short).

When Vim launches, the first matching file in the arglist will be displayed in a buffer. You can traverse the arglist with the commands :next and :previous. Additionally, you can jump directly to the first file in the arglist with :first, or to the last with :last.

These commands can be a bit cumbersome if you're going to be moving around the argument list a lot, but don't fear: Tim Pope's unimpaired.vim alleviates this pain by providing the following pairs of mappings: [a and ]a for :previous and :next; [A and ]A for :first and :last.

Operating on the argument list

You can perform Ex commands on all the files in the argument list by running :argdo {command}. Checkout Vimcast Episode 43, Using :argdo to change multiple files for a more detailed explanation.

Manipulation

This is incredibly useful, but what if you didn't start Vim with a list of filenames? If you're like me, you might keep a single Vim instance running for your whole work day. In that case, you can run :args {files} using a list of filenames or globs. For example, you can populate the argument list with all of the JavaScript files in your js/ directory with the command,

:args js/**/*.js

If you want to add files to the current argument list, you can do so with :argadd {files}. Similarly, arguments can be removed with :argdelete {files}.

Make a local copy

While the argument list is global by default, you can create a window-local copy of it with the :arglocal command. To specify a new local arglist, just run :arglocal! {files}. Once you've created a local copy of the argument list, you can tailor it to your liking.

This leads to an alternative use of the argument list: organization.

From traversing the buffer list to splitting windows to opening new tabs, Vim provides numerous ways of organizing and arranging the files you're currently interested in.

In my work flow, I usually have a handful of small groups of files that are related in some meaningful way. This makes the buffer list a poor choice, as it grows every time I open any new file, which is a frequent occurrence for me. Organizing my related files into split windows in a dedicated tab page works, but can easily get cluttered, and screen real estate quickly vanishes.

Using a local argument list is an ideal solution. Each window can keep track of its own arglist, so I can fill them up with my small set of related files. I'll mentally associate a task with a given window, and jump between the files involved in that task using the available arglist navigation commands.

Leave and come back

A great thing about the argument list is that it sticks around even if you navigate away from it. Looking up some function definition is a common task, and commands like <C-]> (or :tag) can make it really easy to do so with the right setup. Sometimes the exploration can bring you further into more and more files. When you're finally done investigating, you can jump back to the file in the arglist you were just looking at with :argument. Of course, you could also use commands like :next or :previous.

Or, if you decide that a file you found in your travels deserves to be a regular part of the team, you can add it to the current argument list with :argadd %. In this command, % expands to the filename of the current buffer. The new file will be added to the arglist after the current position. Though you'll still be editing the buffer for the file you just added to the argument list, Vim doesn't change the argument position. It might be disorienting to think you're at the current buffer's position in the argument list when you're actually not. A quick fix would be to run :next immediately after :argadd.

Maps to make it easier

If you get into the habit of using the argument list frequently, you might grow tired of typing in the commands. Since most of them start with :arg, you'll find you have to type a lot of characters before Vim knows which command you meant. I've defined the following mappings to make my life a little easier.

Create a window-local copy of the argument list (mnemonic: "arglist local")

nnoremap <Leader>al :arglocal<CR>

Add the current buffer to the argument list and then switch to it (mnemonic: "arglist add")

nnoremap <Leader>aa :argadd % <Bar> next<CR>

Use the current buffer as the start of a new local argument list (mnemonic: "arglist start")

nnoremap <Leader>as :arglocal! %<CR>

Remove the file at the current position from the argument list (mnemonic: "arglist delete")

nnoremap <Leader>ad :<C-R>=argidx()+1<CR>argdelete<CR>

Jump back to the file at the current position in the argument list (mnemonic: "arglist current")

nnoremap <Leader>ac :argument<CR>

Add a visual cue

It can be easy to get lost in the argument list. You may forget just how many files there are, or your position in the list, or whether your current buffer is even in the list. I've created a custom indicator to put in my statusline to give a quick visual summary of the state of my arglist.

If I have 4 files in the argument list and I'm currently editing the buffer corresponding to the second file in the list, my indicator will read A[-+--]. The + symbol represents the current argument, and each surrounding - symbol represents prior and later arguments.

If the current buffer does not represent one of the arguments, then the + will be replaced by a ~ symbol to display A[-~--]. This is useful for when I've found a new file that I'd like to add to my arglist and I'd like a sense of where it will end up.

I won't go into detail about how to configure the statusline in this post, but here's a function that builds the portion of it that creates the arglist indicator. Read at your own risk!

function! StatuslineArglistIndicator()
    return '%{argc()>0?("A[".repeat("-",argidx()).(expand("%")==argv(argidx())?"+":"~").repeat("-",argc()-argidx()-1)."]"):""}'
endfunction