Speaker, writer, teacher, toolsmith
Australia
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.
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:
fat-finger.vim
(a huge collection of iabbrev
s that autocorrect
common spelling errors)
schlepp.vim (a much improved reworking of my own dragvisuals.vim, which remaps the arrow keys in the visual modes to move your selection around through the surrounding text)
autoswap_mac_linux.vim (an extended version of my original autoswap_mac.vim plugin, which makes swapfiles much less annoying and error-prone)
My favorite home-grown plugins (available from my GitHub repo) are:
betterdigraphs.vim (which replaces the standard impossible-for-me-to-remember digraph sequences with a predictable and modular system, with a bonus heads-up display thrown in)
foldsearches.vim (which folds the current buffer around the matches of the most recent /-search, so you can look for some pattern and then temporarily hide everything else)
yankmatches.vim (which adds several new mappings that allow you to yank or delete every line that matched, or that failed to match, the previous /-search)
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
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.
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.
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.