Debugging Vim Scripts — or — How To Assist in Unborking My Vim

This morning I set out to get SLIMV working, for a more integrated (Common) Lisp editing experience, with a tighter edit-compile-debug loop.

Now I'll admit, I've done bad and terrible things to every vim installation I've ever spent more than a few days with. Installing files into $VIMRUNTIME, hamfistedly hacking up other peoples plugins and scripts without taking the time to figure out vimscript, etc. For these sins I feel bad (but not bad enough to actually learn vimscript).

So when I went to install the SLIMV bundle, I did what I always do: muddle through bits and pieces of installation docs until I can make the damn thing work.

This time, however, I wrote down some of the ways I debug this terrible mess I've gotten myself into, and I wanted to share it with you, dear reader.

Print Debugging!

Good programmers use debuggers. Great programmers (and most sysadmins) use print debugging. Vim does lots of magical stuff at startup, and most of the time when that magic breaks (or "breaks") you get no output.

Enter echomsg (also called echom by chronic and unapologetic abbreviators). I use this one all the time:

" Only do this when not done yet for this buffer
echom "jhunt: i'm trying to load this plugin, here goes..."
if exists("b:did_ftplugin")
  echom "jhunt: looks like someone already beat us to the punch."
  finish
endif
echom "jhunt: looks like this plugin is OFF TO THE RACES!"

You can't really leave these lying around, since they do print out before the 'visual' mode of vim starts up, forcing you to constantly press Enter every time. But you can comment them out with a " when you're done with them (or just remove them, if you're feeling overconfident).

If the script is even being loaded (definitely not a guarantee), you'll see this:

→ vim x.lisp
jhunt: i'm trying to load this plugin, here goes...
jhunt: looks like this plugin is OFF TO THE RACES!
Press ENTER or type command to continue

Getting At Those g: & b: Variables

Most vimscripts deal with variables. SLIMV, for example, attempts to auto-detect what Common Lisp implementation is installed on the local machine. To avoid doing this for every new buffer, it sets a global variable (that's what that g: prefix means), called g:slimv_lisp_loaded. The auto-detection logic checks for the existence of this variable. And now you can too!

:echo g:slimv_lisp_loaded
1

Do that in the vim command window (normal mode), and you'll either get a value (as above) or not. Initially, the script wasn't running properly — more on that in a bit. I figured that out by trying to echo the "did I do it yet" variable, and getting a big ugly undefed error from vim.

Virtually every professional filetype plugin (i.e. not mine) honors the b:did_ftplugin variable. That b: prefix means "buffer-local" — the variable has a different life for each buffer vim has open. You can also echo those, but be aware of which buffer you're in when you do so.

:echo b:did_ftplugin
1

vim lacks namespaces for variables (aside from b: and g:) so plugin authors conventionally prefix all of their variable names manually. This is super helpful for debugging, since vim will tab-complete variable names when you are trying to :echo them.

:echo g:<TAB>
g:copy_as_rtf_preserve_indent     g:loaded_spellfile_plugin
g:copy_as_rtf_using_local_buffer  g:loaded_tarPlugin
g:did_indent_on                   g:loaded_vimballPlugin
g:did_load_filetypes              g:loaded_zipPlugin
g:did_load_ftplugin               g:mapleader
g:ft_ignore_pat                   g:markdown_fenced_languages
g:lisp_rainbow                    g:matchparen_insert_timeout
g:loaded_2html_plugin             g:matchparen_timeout
g:loaded_copy_as_rtf              g:n7_rainbow
g:loaded_getscriptPlugin          g:ruby_minlines
g:loaded_gzip                     g:syntax_on
g:loaded_logiPat                  g:vimsyn_embed
g:loaded_matchparen               g:zipPlugin_ext
g:loaded_netrwPlugin

:echo g:n7<TAB>
g:n7_rainbow

Since I ran this from the text of this blog post, the SLIMV stuff isn't present, since I don't write this blog in Lisp (yet).

What Scripts Did Get Loaded, Anyhow?

Generally speaking, I find most of my vim plugin flubs come from bad pathing and incorrect installation. If vim can't see the code, it can't execute the code. Which is why I was super stoked to find :scriptnames.

Go ahead, fire up vim and run :scriptnames in normal mode.

...

Isn't that awesome!? It prints out the absolute path to every vimscript and plugin that vim loaded during startup; core stuff, user stuff, it's all there!

Go Forth & Debug!

I hope this helps you next time you need to debug your really borked up vim installation, future James. If anyone else finds this useful, all the better!

Happy Hacking!

James (@iamjameshunt) works on the Internet, spends his weekends developing new and interesting bits of software and his nights trying to make sense of research papers.

Currently working on Rook.