You know how some people keep track of words they've looked up in the dictionary by highlighting them (most famously in Say Anything)? I was curious what things I was looking up in vim's help, in part because I know there are things I wanted to write about in this blog, and I just can't remember what they are. If only I could look at my history of :h's! And then of course, because it's a fun exercise and maybe moderately useful, at the very least for writing a blog, I decided to write something that would do it. I'll present it first and then maybe make some sparse comments about it and the process of writing it:

let s:helps_file = $HOME . "/.vim/help-queries.txt"

function! s:SaveHelpQueries() abort  
  if getcmdtype() == ':'
    let line = getcmdline()
    if line =~ '^h ' || line =~ '^help '
      let entry = join(split(line, ' ')[1:], ' ')
      call writefile([entry], s:helps_file, "a")

function! s:OpenHelp() abort  
  let pos = getcurpos()
  normal ^"ay$
  call setpos('.', pos)
  exec "noautocmd h" @a

augroup TrackHelp  
  au CmdlineLeave * call s:SaveHelpQueries()
  exec "au BufEnter" s:helps_file "nnoremap <buffer> K :call <SID>OpenHelp()\<CR>"
augroup END

function! s:ShowHelpers(...) abort  
  let type = a:0 == 0 ? "e" : a:1
  let bang = a:0 == 2 ? a:2 : 0
  if !bang
    exec type s:helps_file
    if (type == 'vsp' && &splitbelow) || (type == 'sp' && &splitright)
      exec "abo" type s:helps_file
      exec "bel" tpye s:helps_file

  if type != 'e'
    augroup AutocloseHelp
      exec "au BufLeave" s:helps_file "q | au! AutocloseHelp BufLeave"
    augroup END

command! -nargs=0 Helps :call s:ShowHelpers()  
command! -nargs=0 -bang VHelps :call s:ShowHelpers('vsp', <bang>0)  
command! -nargs=0 -bang SHelps :call s:ShowHelpers('sp', <bang>0)  
command! -nargs=0 THelps :call s:ShowHelpers('tabe')  

I wrote the TrackHelp auto group and the SaveHelpQueries functions first. I'll admit I had to Google (and ultimately read some questions on stack overflow) to remember CmdlineLeave, which is what I needed to record the command line right before it's executed. In the function called from there, I first check that the type of command being executed is a : command. Other types of commands trigger this auto event too, such as searching or running a macro. See :h getcmdtype for a list. The rest is pretty straight forward: if it is a : command, and the first part of that command is h or help, I get the rest of the command and append it to the help file.

After finishing this I thought, "That's nice, but it would be useful if it was easy to view this collection too. Perhaps I should add a command to open the file?" Of course, it wouldn't be that hard to type something like :vsp ~/.vim/help-queries.txt, especially with tab completion, but since this is a vimscript exercise already, I decided to add a Helps command (as well as variants for opening the file in other views). This was, on the whole, pretty straight-forward: just a command that takes no args and calls a function with the view type ('vsp', 'sp', or 'tabe' . . . I decided to default to 'e', the universal default view) that opens the file in that view. Then I remembered the -bang argument to command, which I haven't really had a need to use before, and as long as I'm in an exercise, I might as well use some new things.

Adding -bang to a command allows the user to add '!' when they execute the command (in this case :VHelps! and :SHelps!). Passing it to the function as <bang>0 is the equivalent (in other languages) of !false. If bang is supplied, a true value (literally 1) is passed, if not, 0 is passed. The obvious use for a bang (in my opinion) was to reverse the existing splitabove/splitright option. If splits normally open right, bang makes them open left. If they normally open below, bang makes them open above. And vice versa.

Then, I figured it would be nice if the help-queries file auto closed when you left it, so I added an autocommand for BufLeave (except when e is used, since that doesn't make much sense . . . there's nothing to leave in that case, and executing q will actually exit vim). When the user leaves the help-queries buffer, that buffer is closed and the BufLeave autocmd is disarmed. Disarming the autocmd prevents weirdness if you happen to open that file back up in some other way (like vsp), in which case you want the buffer to behave like a normal buffer and not auto close.

Finally, I added an autocommand to make K reopen the help for each entry in the help-queries file. I played at first with using setlocal keywordprg=:help for this, but strangely, this didn't work if the cursor was on keywordprg (of all things), although it did work on some other entries. The other problem is that K operates on the word under cursor, and the concept of a word in vim means that using K on g; opens the help for g instead of for g;. So I overwrote the K mapping in this buffer instead and did it by hand. K calls OpenHelp which records the current cursor position, captures the current line into the a register, restores the cursor position, and then calls help with the contents of the a register, but without triggering autoevents. That's because, in this case, we don't need the help entry added to the help-queries file because we're already looking at it.

And that's it. Once I think of a clever name, maybe I'll publish it as a plugin, but for now, if you want to use it, just copy it somewhere in your vim setup (vimrc or something sourced therein). It's working great, and I've already got 13 entries in my help-queries file. This should give me plenty of material for future vim posts. I know . . . the excitement is palpable! Check back soon!