Показаны сообщения с ярлыком libcall. Показать все сообщения
Показаны сообщения с ярлыком libcall. Показать все сообщения

вторник, 18 декабря 2012 г.

vim: запуск xkb-switch через интерфейс libcall()

Молодцы хабрахабровцы! Придумали как запустить системное переключение раскладки клавиатуры без использования call system(). Предысторию вопроса можно изучить здесь. Интерфейс libcall() закрывает вопрос появления мусора на экране терминала. И поэтому отныне xkb-switch поддерживает libcall(), хвала гитхабу с его форками и пулл-реквестами!

В .vimrc поддержка автоматического переключения русской раскладки в режиме ввода теперь выглядит так:
" ---- Automatic keyboard layout switching upon entering/leaving insert mode
" ---- using xkb-switch utility
" ----
let g:XkbSwitchEnabled = 0
let g:XkbSwitchLib = "/usr/local/lib/libxkbswitch.so"

fun<SID>xkb_mappings_load()
    for hcmd in ['gh''gH''g^H']
        exe "nnoremap <buffer> <silent> ".hcmd.
                    \ " :call <SID>xkb_switch(1)<CR>".hcmd
    endfor
    xnoremap <buffer> <silent> <C-g> :<C-u>call <SID>xkb_switch(1)<CR>gv<C-g>
    snoremap <buffer> <silent> <C-g> <C-g>:<C-u>call <SID>xkb_switch(0)<CR>gv
    let b:xkb_mappings_loaded = 1
endfun

fun<SID>ru_mappings_load()
    redir => mappings
    silent imap
    redir END
    for mapping in split(mappings, '\n')
        let value = substitute(mapping, '\s*\S\+\s\+\S\+\s\+\(.*\)''\1''')
        " do not duplicate <script> mappings (when value contains '&')
        if match(value, '^[\s*@]*&') != -1
            continue
        endif
        let data = split(mapping, '\s\+')
        " do not duplicate <Plug> mappings (when key starts with '<Plug>')
        if match(data[1], '^\c<Plug>') != -1
            continue
        endif
        let from = 'qwertyuiop[]asdfghjkl;\\x27zxcvbnm,.`/'.
                    \ 'QWERTYUIOP{}ASDFGHJKL:\\x22ZXCVBNM<>?~@#\\x24^\\x26|'
        let to   = 'йцукенгшщзхъфывапролджэячсмитьбюё.'.
                    \ 'ЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ,Ё\\x22№;:?/'
        " protect backslashes before next evaluations
        let newkey = substitute(data[1], '\''\\\\''g')
        " pre-evaluate the new key
        let newkey = substitute(newkey,
                    \ '\(\%(<[^>]\+>\)*\)\(.\{-}\)\(\%(<[^>]\+>\)*\)$',
                    \ '"\1".tr("\2", "'.from.'", "'.to.'")."\3"''i')
        " evaluate the new key
        let newkey = eval(newkey)
        " do not reload existing mapping unnecessarily
        if newkey == data[1]
            continue
        endif
        let mapcmd = match(value, '^[\s&@]*\*') == -1 ? 'imap' : 'inoremap'
        " probably the mapping was defined using <expr>
        let expr = match(value,
                    \ '^[\s*&@]*[a-zA-Z][a-zA-z0-9_#\-]*(.\{-})$') != -1 ?
                    \ '<expr>' : ''
        " new maps are always silent and buffer-local
        exe mapcmd.' <silent> <buffer> '.expr.' '.newkey.' '.
                    \ maparg(data[1], 'i')
    endfor
endfun

fun<SID>xkb_switch(mode)
    let cur_layout = libcall(g:XkbSwitchLib, 'Xkb_Switch_getXkbLayout''')
    if a:mode == 0
        if cur_layout != 'us'
            call libcall(g:XkbSwitchLib, 'Xkb_Switch_setXkbLayout''us')
        endif
        let b:xkb_layout = cur_layout
    elseif a:mode == 1
        if !exists('b:xkb_mappings_loaded')
            call <SID>xkb_mappings_load()
            call <SID>ru_mappings_load()
        endif
        if exists('b:xkb_layout')
            if b:xkb_layout != cur_layout
                call libcall(g:XkbSwitchLib, 'Xkb_Switch_setXkbLayout',
                            \ b:xkb_layout)
            endif
        endif
    endif
endfun

fun<SID>enable_xkb_switch(force)
    if g:XkbSwitchEnabled && !a:force
        return
    endif
    if executable(g:XkbSwitchLib)
        autocmd InsertEnter * call <SID>xkb_switch(1)
        autocmd InsertLeave * call <SID>xkb_switch(0)
    endif
    let g:XkbSwitchEnabled = 1
endfun

command EnableXkbSwitch call <SID>enable_xkb_switch(0)

if g:XkbSwitchEnabled
    call <SID>enable_xkb_switch(1)
endif
Обратите внимание: по умолчанию автоматическое переключение раскладки неактивно (переменная g:XkbSwitchEnabled равна 0). Пользователь может включить ее либо вручную (введя команду :EnableXkbSwitch), либо объявив автокоманды, которые сработают при определенных условиях. Например, можно включить автоматическое переключение раскладки для файлов типа reStructuredText и tex:
autocmd FileType rst,tex EnableXkbSwitch
В новой версии скрипта я добавил функцию ru_mappings_load(). Это очень полезная функция - она создает русифицированные дубликаты всех имеющихся маппингов режима ввода и загружает их в локальный буфер. Например в плагине riv, который я использую для редактирования файлов reStructuredText, есть удобный маппинг <C-E>l` для переключения режима списка во время редактирования файла. Если бы нам пришлось переключать раскладку клавиатуры для ввода латинских символов, используемых в таких маппингах, то прелесть использования нашего подхода если бы и не улетучилась вовсе, то все же серьезно пострадала. Теперь же русифицированный дубликат этого маппинга <C-E>дё загружается автоматически и позволяет не беспокоится более о переключении раскладки клавиатуры.

Внимание: алгоритм трансляции из латинского ключа в русский не универсален и возможны сбои для необычных ключей! Однако для всех маппингов riv, а также маппингов плагина c.vim он работает исправно. Список загруженных маппингов режима ввода можно просмотреть командой :imap.

Update. Оформили в виде плагина, см. здесь и здесь. Последнее замечание относительно неуниверсальности трансляции маппингов в новом плагине неактуально.