The Vim and Neovim Debugger

Vim and Neovim have a debugger built in. It's kind of a PITA to use, but I don't suppose that's much different from other debuggers. Before we try to use it, your best source is to start nvim and type ':help debug-mode'. Basic use looks like this:

$ nvim -D tmp.txt

You'll find yourself at a debugger prompt, and the most critical command you'll need is 'quit'.

The table below is more or less a direct quote from the Vim help pages.

Command Name What it Does
quit Abort execution. This is like using CTRL-C, some things might still be executed, doesn't abort everything. Still stops at the next breakpoint.
cont Continue execution until the next breakpoint is hit.
next Execute the command and come back to debug mode when it's finished. This steps over user function calls and sourced files.
step Execute the command and come back to debug mode for the next command. This steps into called user functions and sourced files.
finish Finish the current script or user function and come back to debug mode for the command after the one that sourced or called it.
backtrace / bt / where Show the call stacktrace for current debugging session.
frame <N> Goes to <N> backtrace level. + and - signs make movement relative. E.g., ":frame +3" goes three frames up.
up Goes one level up from call stacktrace.
down Goes one level down from call stacktrace.
:breaka[dd] func [lnum] {name} Set a breakpoint in a function. Example: ':breakadd func Explore' Doesn't check for a valid function name, thus the breakpoint can be set before the function is defined.
:breaka[dd] file [lnum] {name} Set a breakpoint in a sourced file. Example: ':breakadd file 43 init.vim'
:breaka[dd] here Set a breakpoint in the current line of the current file. Like doing: ':breakadd file <cursor-line> <current-file>' Note that this only works for commands that are executed when sourcing the file, not for a function defined in that file.

The [lnum] is the line number of the breakpoint. Vim will stop at or after this line. When omitted line 1 is used.

I found I needed the debugger when a plugin I wrote to template new Markdown files was instead templating the new Markdown files with both my markdown and HTML templates - twice. Stepping through all of Neovim's start-up files took a very long time with 'step' and 'next' ('finish' would have worked better) until I discovered that the system-level Markdown initialization file was calling not only the system HTML files, but also my own ~/.vim/ftplugin/html.vim file, which templated HTML into the file if it was empty. This didn't make sense to me: I thought "If I'm in ~/.vim/ftplugin/html.vim I would have thought it was already confirmed that the filetype was HTML!" The system-level Markdown file was written by Tim Pope, so I emailed him. I was pleasantly surprised when he responded: Markdown is (I didn't know this) a superset of HTML, so sourcing HTML files makes sense ... even though the filetype doesn't match. And, as he also pointed out, this is a long-established pattern: even if I convinced him to change it, the PHP system files source the HTML files as well. So the correct answer for me was to add a filetype check (I may not think it's right, but the established patterns say it's the way to do things). So here's the new line to check:

if ((b:curFileSize==0) || (b:curFileSize==-1)) && (&filetype==?'html')

But this only fixes the HTML problem, not the double templating. This is because I was assuming this was all about opening files when it turns out I need to think in terms of buffers. So instead of checking for the file size of the original file associated with this buffer, the correct thing to do is to check to see if the buffer itself is empty:

if line('$') == 1 && getline(1) ==? ''

This should probably be combined with the filesize check (what's depressing is that may not be overkill) and of course the filetype check. <sigh> I still don't know why ~/.vim/ftplugin/markdown.vim was called twice, but now I don't get two copies of the templating.