Sunday, October 16

finding an obscure vim plugin bug

I filed this against vim-fugitive a bit over a month ago and just remembered it: Autocommands do not fire for files named index:

I have an autocommand to do some stuff. I noticed that it fails on files named index, I think because fugitive.vim has its own autocommand for index{,.lock} that triggers BufReadIndex(), which in turn does something to prevent further processing of the event. I’m kind of hazy on how.

Tracking this down to its source was tricky, and I feel like I should document a couple of the small things I learned in the process.

For background, this is from the Vim help for :autocommand:

:au[tocmd] [group] {event} {pat} [nested] {cmd}
        Add {cmd} to the list of commands that Vim will
        execute automatically on {event} for a file matching
        {pat} |autocmd-patterns|.
        Vim always adds the {cmd} after existing autocommands,
        so that the autocommands execute in the order in which
        they were given.  See |autocmd-nested| for [nested].

The {event} is something like BufNewFile, which triggers when a buffer is opened with a new file. There are a bunch of these. I was trying to get something like the following autocommand to fire:

" this calls a function to treat blog entries as markdown
au BufReadPost,BufNewFile */p1k3/*[0123456789]* call BPB_PikeHighlight()

…and I couldn’t understand why the pattern */p1k3/*[0123456789]* wasn’t matching on a path like /home/brennen/p1k3/archives/2016/10/16/index, even though it worked for other files in that tree.

At first I thought something was wrong with the pattern, so I rewrote the function called by the command to this:

au BufReadPost,BufNewFile * call BPB_FiletypeOverrides()

" set custom filetypes for some things
function! BPB_FiletypeOverrides()
  " make sure NERDTree windows don't get messed up
  if bufname("%") =~ "NERD_tree"
    return
  endif

  echom 'testing for p1k3 match'

  " using expand('%:p') instead of bufname("%") for full path, per:
  " http://vim.wikia.com/wiki/Get_the_name_of_the_current_file
  " the initial slash in the regex seems to be necessary to make \v work
  if expand('%:p') =~ "\\vp1k3\/archives.*([0-9]|[a-z])+$"
    echom 'p1k3 match - setting filetype to markdown'
    set filetype=markdown
  endif
endfunction

That didn’t get any results, but since the echom debug messages didn’t trigger at all, it finally dawned on me that the autocommand wasn’t firing regardless of pattern. Then I got to thinking about how index is a special filename for things besides my blog. Git, for example.

In order to confirm my new suspicions, I wanted to see if some plugin was registering an autocommand that might be overriding mine. You can use :autocmd to list the existing ones. It gives many pages of output like this:

--- Auto-Commands ---
fugitive_status  BufDelete
    term://*  call fugitive#reload_status()
fugitive_commit  BufDelete
    COMMIT_EDITMSG
              execute s:sub(s:FinishCommit(), '^echoerr (.*)', 'echohl ErrorMsg|echo \1|echohl NONE')
filetypedetect  BufEnter
    *.xpm     if getline(1) =~ "XPM2" |   setf xpm2 | else |   setf xpm | endif
    *.xpm2    setf xpm2
...

Since this was stupidly long and hard to jump around in, I went looking for a way to capture the output of a command. This actually turns out to be pretty easy:

:redir @a
:autocmd
:redir END

This puts the output of :autocmd into the register a, which can then be pasted into a buffer by typing "ap.

Sure enough, buried in there is this:

fugitive_files  BufReadCmd
    index{,.lock}
              if fugitive#is_git_dir(expand('<amatch>:p:h')) |   exe s:BufReadIndex() | elseif filereadable(expand('<amatch>')) |   read <amatch> |   1delete | endif

I stopped there and filed a bug report, since it’s a trivial bug and it had already cost me more time than the thing I was trying to fix is likely to save me in a lifetime, but maybe one of these days I’ll get around to figuring out what is actually happening and write a patch.

As a final meta-level thought on this: I’ve invested an embarrassing amount of time in configuring my text editor over the years, but it’s only recently that I’ve hit the point where most of my deliberate changes to this setup land me firmly in the Turing tar-pit.

Perhaps not coincidentally, this is right about where I realize that shifting my preferences to another editor would be a matter not just of relearning interface elements, but of porting a small but growing body of somewhat tricky code. Every change increases the incentive to make future changes to the existing environment instead of paying the cost of switching environments.

Path dependence explains a lot of things in life.

It also remains true that Vim is an astonishing achievement, both powerful and feature rich in ways that most systems rarely attain. One might wish for an underlying syntactic and conceptual simplicity, for a language composable and manipulable without an entire vocabulary of dirty hacks — but one isn’t exactly surprised by reality.

Addendum: It turns out this is not the first time I have learned the :redir trick.