Monday, June 21, 2010

Making expr-mappings using getchar() work inside a macro

I have remapped f, F, t, T, ',', and ; to also highlight the character
they match, using <expr> mappings so that they work well in not only
normal mode, but also visual and operator-pending modes.

These mappings use getchar() to get the character to jump to, inspired
by the example given in :help getchar().

To my dismay, I have discovered that these mappings did not work when
called while running a recorded macro with the @ command. They would
just hang waiting for a character. For a while, I resorted to manually
editing the register to use ':normal! f' for example, instead of just
'f'.

I recently decided to fix the problem, and believe I have tracked down
the root cause. :help :map-<expr> says:

> - You can use getchar(), but the existing typeahead isn't seen and new
> typeahead is discarded.

With this in mind, I set up mappings for q and @, that will keep track
of whether a macro is running:

" keep track of whether we're running a macro or not
let g:running_macro=0
nnoremap <expr> @ ':<C-U>let g:running_macro+=v:count1<CR>'.v:count1.'@'
nnoremap q :call EndRecording()<CR>

" if we're running a macro, this should be a no-op, otherwise we're recording
" one (or starting to record) and we just need to use the normal functionality
" of q. Unfortunately q doesn't work inside mappings so we need to use
" feedkeys()
function! EndRecording()
if g:running_macro > 0
let g:running_macro -= 1
else
call feedkeys('q', 'n')
endif
endfunction

I then test for g:running_macro > 0 in my mappings for f, F, t, and T,
and do not use getchar() at all in these cases (and therefore skip the
highlight as well).

I'm not fully satisfied with this solution, because I find it ugly to
hijack q and @ in this way, and additionally it doesn't work when the
macro is terminated by an error (for example, when running a recursive
macro, or a macro with a very large count as a lazy way to execute on
an entire file), because the final 'q' in the mapping that would set
g:running_macro to zero never gets executed.

For now, I have a workaround of a third mapping to just set
g:running_macro to zero explicitly, but I'd like to find a better way
to do this.

Any ideas? Am I right about the root cause of the problem?

Full set of mappings attached. This was tricky enough, I'll probably
submit them as a tip on the wiki or a plugin when it's all done.

--
You received this message from the "vim_use" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

No comments: