How I

Damian Conway

Speaker, writer, teacher, toolsmith

Australia

Who are you and what do you use Vim for?

I’m a language designer, an Open Source developer, a technical writer, a teacher, a conference presenter, and the CEO of my own training company. I’m also a former research scientist and CS professor.

And since 1983 I’ve been using vi and then Vim for just about all of that: writing, testing, and debugging code; reading and writing documentation; generating and verifying patches; browsing diffs; reading, writing, and searching language design documents; building syllabuses; outlining course materials; demonstrating live code in class; composing emails, training proposals, and teaching contracts; reformatting text for use in other (less able) editors; tracking my various todo lists; organizing my travel itineraries; writing content for the numerous autogenerated webpages on my site; and my checking my spelling and grammar in all of the above. If it involves text in any way, I’m probably doing it in Vim.

I code most of my larger projects in Perl 5 and Perl 6, but I teach in a much wider range of languages, so I’m often writing examples in Java, JavaScript, Python, Haskell, C, C++, C#, Swift, etc. etc.

I work under MacOS, in Terminal, using off-the-shelf MacVim 7.4.

Introduce us to your Vim config.

My .vimrc is just over 1600 lines, but that’s a serious understatement, as I’ve factored most of my config out into separate plugins. Counting those, my customizations add up to about 6500 lines. And then there are the numerous externally created plugins I use, which add another 10000 or more lines of tools and configuration.

Clearly I have a serious problem.

And it really is a problem…any time I have to use some other Vim set-up (usually belonging to some student I’m helping). I’m now so reliant on my own heavy configured environment that I feel like I lose maybe 3 fingers, and about 50 IQ points, whenever I’m forced back to using vanilla Vim.

I generally keep tried-and-true config at the start of my .vimrc and put new ideas and experiments at the very end of the file. To make that kind of experimenting easier, I have a shortcut for jumping into my .vimrc and an autocmd to reload it every time it’s saved:

nmap <silent>  ;v  :next $MYVIMRC<CR>

augroup VimReload
    autocmd!
    autocmd BufWritePost  $MYVIMRC  source $MYVIMRC
augroup END

Most of those trial features turn out to be either too disruptive to my existing habits, or less useful than I’d anticipated, or just too hard to get right, and are eventually deleted.

But a minority of them turn out to be sufficiently convenient and useful and robust, and they eventually either percolate towards the start of the file, or (if they’re too unwieldy) I move them out into their own plugins. As I mentioned before, about 75% of my config has been refactored in that manner.

I don’t set many of the more unusual options. But I’m a big fan of the improvements to search you get from:

set incsearch  ignorecase  smartcase  hlsearch

Though setting hlsearch is only bearable if you have an easy way to turn off all the highlighted matches when you’re done with them, such as remapping the backspace/delete key:

nmap <silent> <BS>  :nohlsearch<CR>

I really dislike being interrupted by “nanny” messages, so I also:

set autowrite

This used to be a little controversial but nowadays, with persistent undo available, I don’t think there’s any reason not to autosave your final buffer state every time you exit. And certainly no reason to delay your departure—and disrupt your flow state—by endlessly needing to decide whether or not to save it this time.

And speaking of persistent undo, I have that feature active, and dialled way up:

if has('persistent_undo')
    set undolevels=5000
    set undodir=$HOME/.VIM_UNDO_FILES
    set undofile
endif

As you see, I prefer to store all my undo files in a single directory, rather than leaving them sprinkled throughout the filesystem. There’s a certain “eggs-in-one-basket” risk to that, of course, but I find it much cleaner.

The only other unusual option setting I have is:

set updatecount=10

This causes swap files to be rotated every 10 keystrokes (instead of the default 200). That means 10 keystrokes is as much as I’m ever going to have lost after any -recover, which is comforting. On modern hardware I don’t notice any performance hit, though that number has only come down in several cautious steps over the years as successive laptops came with faster processors and hard drives.

I do have a considerable number of mappings in my .vimrc (currently around 120 of them). They’re almost all nmaps or vmaps, those being the two modes I find least convenient out-of-the-box.

Quite a few of them are efforts to optimize searching and substitution. For example, I found I was forever doing global search-and-replaces (i.e. :%s/X/Y/g<CR>), so I eliminated the repetitious typing by stealing the never-used (by me) S command:

nmap  S  :%s//g<LEFT><LEFT>

Now I just need to type: SX/Y<CR>

But then I started noticing how often I did a /-search for some pattern and, having looked through the matches, then wanted to globally substitute all of them. Even with the S mapping that was more annoying repetition: first do the search: /pattern<CR> then do the replace: Spattern/replacement<CR>

So I stole the (also never used by me) M command for that:

nmap <expr>  M  ':%s/' . @/ . '//g<LEFT><LEFT>'

Now it’s just: do the search: /pattern<CR> then replace all the matches: Mreplacement<CR>

I set up a lot of those types of micro-optimizations and, as they’re assimilated into muscle memory, my workflow improves incrementally.

As another example: I’m a heavy user of Visual Block mode, but almost never use plain Visual mode. So I swapped those two commands:

nnoremap    v    <C-V>
nnoremap  <C-V>    v

I also retargeted the arrow keys for file navigation: <UP> and <DOWN> to step through the file list:

nmap <silent> <UP>            :prev<CR>
nmap <silent> <DOWN>          :next<CR>

and <LEFT> and <RIGHT> to step through the quickfix list:

nmap <silent> <LEFT>          :cprev<CR>
nmap <silent> <RIGHT>         :cnext<CR>

and double <LEFT> and <RIGHT> to jump through the quickfix file list:

nmap <silent> <LEFT><LEFT>    :cpfile<CR><C-G>
nmap <silent> <RIGHT><RIGHT>  :cnfile<CR><C-G>

All of those are now deeply in my muscle memory, which makes navigation between files and errors feel almost telepathic.

The <LEFT> and <RIGHT> mappings are particular useful to me, because, apart from the usual debugging with :make, I frequently :vimgrep for particular patterns in collections of source or documentation files. :vimgrep places all those matches in the quickfix list, so then I can just arrow through each match and across all the files.

As far as plugins go, I’m a hopeless junkie. My favourite publicly available plugins are:

My favorite home-grown plugins (available from my GitHub repo) are:

I don’t use a colour scheme at all, and very rarely use syntax highlighting. I find multicoloured code very distracting…almost inducing sensory overload. It much easier to cope with just my standard yellow-on-black terminal window.

I don’t use the status line either (thereby avoiding another multicoloured distraction). But I do use the ruler, which I customize slightly to add an accurate word count (well, at least more accurate than the one you usually get from g_CTRL-G).

Of course, recounting the entire file every time anything changes is annoyingly slow for big files, which means I couldn’t just finesse it with a TextChanged autocmd. So I simply arranged to recount less often as the file length increases:

" Track how often word count is updated
let g:BRF_skipcount = 1

" Return an occasionally precise, but usually approximated, word count
function! BRF_WordCount()

    " Skip the recount most of the time, and more often as the file gets bigger
    let g:BRF_skipcount += 1
    if exists("b:BRF_wordcount")

        " If updated "recently" reuse previous count, marked as ~approximate
        if g:BRF_skipcount < b:BRF_wordcount / 500
            return '~' . b:BRF_wordcount
        endif

        " Otherwise, reset counter and continue on to actual counting
        let g:BRF_skipcount = 1
    endif

    " Only recount the file if it may have changed
    if &modified || !exists("b:BRF_wordcount")

        " Grab the entire buffer contents
        let lines = join(getline(1,'$'), ' ')

        " Words like "o'clock" and "spider-pig" are single words
        let lines = substitute(lines, '\a[''-]\a',      'X', 'g')

        " Condense alphanumeric sequences (a.k.a. words) to one character
        let lines = substitute(lines, '[[:alnum:]]\+',  'X', 'g')

        " Remove everything else (i.e. punctuation)
        let lines = substitute(lines, '[^[:alnum:]]\+', '',  'g')

        " Whereupon every remaining character represents one word
        let b:BRF_wordcount = strlen(lines)
    endif

    " Return the precise count...
    return b:BRF_wordcount
endfunction

" Set up the new ruler format and activate the ruler
let &rulerformat = '%22(%l,%v %= %{BRF_WordCount()}w  %P%)'
set ruler

What have been the most useful resources for you to learn Vim?

I learnt almost everything I know about Vim from :help and :helpgrep.

Initially I picked up things ad hoc as I needed them (mostly as I was trying to script new features and conveniences for myself or for colleagues).

But when I started teaching Vim classes, I decided I needed a more structured understanding and so I sat down and read :help from cover to cover. That approach probably wouldn’t suit everyone, but it worked very well for me.

I still do a lot of scripting, so I’m always going back into :help to refresh my memory or to research some obscure command, function, or behaviour. In that respect, by far the most useful single :help page for me is: :help functions-list.

More generally, the most useful feature of :help is its autocompletion facility. Often I know roughly what I’m looking for (“RWILF”), and I can quickly zero in on exactly what I need with: :help RWILF<TAB>.

I still browse :help for other topics, and to read up on all the new goodies in each release. For example, an interesting feature I came across recently was the infercase option, which makes completions (I’m a terrible typist, so I use them almost continuously) much cleverer about case matching. Turning the option on preserves the case of the partial word you’re completing, even if the selected completion word was differently cased.

The other two learning resources I find particular worthwhile are vim.org’s list of new plugins:

and the main Vim subreddits:

I often grab new plugins that appear on vim.org, if they seem interesting. Mostly it’s just to look through their code; I rarely actually install them long-term. But, like many developers, I find I learn best from exploring other people’s source (even if, sometimes, its just learning what not to do).

I often find the questions that people raise on the various subreddits, and the answers they get back, give me new ideas or highlight features of Vim that I hadn’t been aware of. They’re all relatively low-volume (and surprisingly low-belligerence) streams, and are definitely worth the occasional trawl.

Share a snippet of Vim script you’ve written and talk about what it does.

I frequently find myself extracting smaller documents from larger ones: grabbing code examples from a source file, or pulling out the important elements of a document into a summary, or selecting some subset of references from a bibliography, or trimming an article to a required length, etc. etc.

I used to do that by yanking the pieces I wanted into one of the “capital registers”. That is: you start by yanking the first piece of text you want into a named register, and then you yank all the other chunks you want into the capitalized version of that same register. So your sequence of yank commands becomes something like:

"ayy
"A3yy
"Ay}
v/end<CR>'Ay
"Ayy
etc.

Yanking to an uppercase register appends the yanked text to the corresponding lowercase register, so at the end of the sequence you have everything you selected in that single register, which you can paste wherever you want: "ap.

But I’m such a poor typist that it’s all too easy for me to mess up the register specifications. I either forget to specify a register at all (in which case that chunk of text is left out of the final accumulation), or else I forget to capitalize the register name (in which case I blow away everything I’ve accumulated to that point).

Clearly, I needed an easier and more reliable way to do that type of “gold fossicking”. So I repurposed the Y command, not to the usual y$, but to act as an “incremental yank” into the default (nameless) register. And also added YY as an incremental yy. Like so:

" Make v<motions>Y act like an incremental v<motion>y
vnoremap <silent>       Y  <ESC>:silent let @y = @"<CR>gv"Yy:silent let @" = @y<CR>

" Make Y<motion> act like an incremental y<motion>
nnoremap <silent><expr> Y  Incremental_Y()

function! Incremental_Y ()
    " After the Y operator, read in the associated motion
    let motion = nr2char(getchar())

    " If it's a (slowly typed) YY, do the optimized version instead (see below)
    if motion == 'Y'
        call Incremental_YY()
        return

    " If it's a text object, read in the associated motion
    elseif motion =~ '[ia]'
        let motion .= nr2char(getchar())
    endif

    " If it's a search, read in the associated pattern
    elseif motion =~ '[/?]'
        let motion .= input(motion) . "\<CR>"
    endif

    " Copy the current contents of the default register into the 'y register
    let @y = @"

    " Return a command sequence that yanks into the 'Y register,
    " then assigns that cumulative yank back to the default register
    return '"Yy' . motion . ':let @" = @y' . "\<CR>"
endfunction


" Make YY act like an incremental yy
nnoremap <silent>  YY  :call Incremental_YY()<CR>

function! Incremental_YY () range
    " Grab all specified lines and append them to the default register
    let @" .= join(getline(a:firstline, a:lastline), "\n") . "\n"
endfunction

I implemented that a few years back now. Recent versions of Vim allow you to create your own operators (see :help :map-operator), so I could probably rewrite that more cleanly now using the new mechanism. But the old script works fine as it is, and the new mechanism still has a few awkward edge-cases, so I haven’t bothered to update it.

What have you been working on recently in Vim?

Mostly I’ve been coding in Perl 6. Now that it’s officially released (and unofficially awesome) I’m busy creating new talks and classes to get the word out and meet the growing demand for training. So I need to implement, test, document, and explain hundreds of new code examples…all of which I do in Vim.

And I have my annual Spring speaking tour through Europe coming up in a few months, so I’m spending a lot of time writing proposals, class descriptions, website content, contracts, invoices, emails, itineraries, presentations, code demos, and todo lists. All of which I also do in Vim.

It’s such an extraordinary tool. It makes me more efficient, more productive, and even smarter…by keeping out of the way and helping me stay in the coding trance. I’d be lost without it.