Monday, June 30, 2014

Vim: Meta Toggle

Usually you want to have your terminal setup the way that Alt/Meta sequence sends escape sequence as oppose to set character's 8th bit (which conflicts with UTF-8). If that's so, using Meta key-bindings in terminal applications becomes a bit of challenge. One approach is to not use such key-bindings at all, other is to tackle the problem of recognizing plain Esc key and escape sequence with timeouts. If application sees Esc followed by a character very quickly then it is safe to assume it is escape sequence. On the other hand if there is a significant pause it's probably a separate Esc key followed by a character key. Every application has it's own way how to deal (or not) with this.

In Vim I do:

set timeout timeoutlen=1000 ttimeoutlen=10

So there is 1s timeout for key mappings and 10ms timeout for key codes. Meta key in mappings has to be specified in form of e.g., <M-j>. If you specify them as escape sequences they will work but 1s timeout will be applied which will cause problems elsewhere. To make those key codes to be recognized on escape sequences some translation has to be incorporated (see below). But there is a problem with macros. Since macros are recorded key strokes, which are replayed later it's impossible to distinguish between plain Esc key followed by 'i' for insert and <M-i>. So that's where toggling comes into play. Usage is simple, for regular operation have a Meta ON, but when recording and replaying macros turn it OFF to save yourself from headache.

" {{{ Toggles translation of ASCII meta escape prefix encoding to 8 bit meta encoding

function! MetaSetup(enable) abort
    " Meta + [0-z]
    " don't include O and P because of conflicts in xterm
    let chars = '0123456789ABCDEFGHIJKLMNQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
    let i = 0
    let n = len(chars)
    while i < n
        let c = chars[i]
        if a:enable
            exec 'set <M-'.c.">=\e".c
        else
            exec 'set <M-'.c.'>='
        endif
        let i += 1
    endwhile
    " Meta + C-H, C-L
    if a:enable
        exec "set <M-C-H>=\e\x08"
        exec "set <M-C-L>=\e\x0c"
    else
        set <M-C-H>=
        set <M-C-L>=
    endif
    let g:meta_enabled = a:enable
    if !has('vim_starting')
        redraw
        echohl WarningMsg | echo 'Meta '.(g:meta_enabled ? 'ON' : 'OFF') | echohl None
    endif
endfunction

function! MetaLess(cmd) abort
    if g:meta_enabled
        silent call MetaSetup(0)
        call TryCatch(a:cmd)
        silent call MetaSetup(1)
    else
        call TryCatch(a:cmd)
    endif
endfunction

command! MetaToggle call MetaSetup(!meta_enabled)
nnoremap <silent> <Leader>mm :MetaToggle<CR>
silent call MetaSetup(1)

" }}}