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.


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)."]"):""}'

10 September 2013

Blogging with Markdown from Vim

If you're a programmer, you probably spend a lot of time in a text editor. You may also have spent a lot of time choosing your text editor. You've probably also spent a lot of time learning and perfecting your use of that text editor. And if your text editor is Vim, you probably hate entering text anywhere other than your text editor.

If you're into writing rich content, you probably spend a lot of time using content markup tools. If you're a programmer, you probably prefer using some kind of markup language over a GUI. If you've been exposed to Markdown, you probably don't want to consider anything else.

It follows logically that as a Vim-using, content writing programmer who likes Markdown, I would like to write my blog entries using the marvelous Markdown syntax in the comfort of my powerful Vim editor. To the average blogger, this may seem like a ridiculous desire, but to a guy like me, once I discovered this was possible, I wouldn't have it any other way.

Initially, I set out to develop a Vim plugin that would allow me to post to my Blogger account. Since Blogger is a Google product, I wasn't surprised to find that my expectation that there would be a Blogger API was correct. Paired with my moderate Vim plugin skill, this was a promising starting point for my plugin.

Even though I didn't think anyone had done this before, I decided to search for an existing plugin that could at least act as a foundation. To my surprise, I found Blogger.vim, which, at first glance, covered all of my needs out of the box. It provides a way to list existing posts and write new posts using Markdown (which gets converted to HTML). It even has an automatic Gist option where code blocks longer than 5 lines are automatically uploaded as a Gist with your connected GitHub account. (This was a feature I had thought about a long time ago; I was pleasantly surprised to see that someone has already done it!)

There were a few areas that I found the plugin was lacking or made me uncomfortable, however.

First, there is no way to apply labels (or tags) to posts. While a fairly minor grievance, it would be nice to be able to manage every aspect of content creation without using a web GUI.

Second, it doesn't provide a way to list draft posts. I'm probably never going to write a blog post in one sitting, so I need a way to continue editing an existing draft. The plugin does have a way to create drafts, but after you've quit Vim, you won't be able to get back to it.

Finally, it uses plain-text authentication in your ~/.vimrc file. While this keeps the set up process simpler, an important fact is that my Blogger account is the same as my Google account. I don't want my most important password visible to all who care to browse my Vim configuration. It would be preferable for the plugin to establish an OAuth connection (or something similar) that only has access to the Blogger features I allow.

For these reasons, I decided to stick to my initial plan and develop the plugin on my own. I'll follow up with another post once I get around to doing that.

05 September 2013

Clearing a register in Vim

Earlier this week, I had a repetitive programming task to carry out. It involved converting lots of SELECT statements in SQL into Kohana ORM code. For part of this task, I needed a way to find a bunch of non-adjacent lines in the code and move them somewhere else. My editor of choice is Vim, so there are many ways this can be done.

The first method I thought of was yanking or deleting lines into a specific register. In Vim, you can do edit operations using a number of different registers, including one named for each letter on the keyboard. For example, you can delete a line into the x register with "xdd. You can also append to a register by referring to it by its capital letter variant. So, if we had previously yanked a line into register x, we could append another line to it with "Xyy. This was to be the basis of my macro: finding relevant lines and appending them to a register.

I figured it would be a good idea to clear the contents of the register at the beginning of the macro, so that successive runs don't accumulate larger and larger register contents. I didn't know how to do this at the time; I don't think there's a way to yank "nothing". Then a thought occurred to me: macros are stored in registers!

Whenever you record a macro, you choose a letter to identify it. For example, to record a macro that deletes 3 lines and identify it as e, you would use qe3ddq (qe - start recording into e; 3dd - delete 3 lines; q - stop recording). This identifier e is actually the same as the register e. To demonstrate this, you can paste the contents into your buffer with "ep. You can then edit it and yank it back into the e register and run the new macro (if it still makes sense) with @e.

So what does this mean? We have identified a quick way to clear a register: record an empty macro! So, to clear out my x register, I can just use qxq (qx - start recording into x; q - stop recording). And there you have it!

Of course, after spending no more than a minute on Google, I found a more proper way to do this using the VimL scripting language in command mode: :set @x = ''. This way seems a bit more direct, but it's not as quick or easy.