воскресенье, 10 февраля 2013 г.

vim: автоматическое переключение раскладки клавиатуры в режиме ввода

Это не автоматическое переключение раскладки клавиатуры при входе и выходе из режима ввода, о котором я писал здесь и здесь. Это гораздо более интересная, хотя и не настолько важная, как предыдущая, функция. Представьте, что вы редактируете некий файл, и вам нужно переключать раскладку клавиатуры в зависимости от положения курсора, не выходя из режима ввода. Я не взял эту задачу с потолка. Мне понадобилась такая функция в процессе изучения немецкого языка: просто мне намного проще запоминать слова, если я их самостоятельно выписываю в таблицу с оригинальным словом или фразой в одной колонке и его/ее переводом в другой колонке. Например:
| Wort         | Übersetzung |
|--------------|-------------|
| der Mond     | луна        |
| humpeln      | хромать     |
| stark        | сильный     |
| die Bewegung | движение    |
| bewegen      | двигать     |
Представьте, сколько нужно переключений раскладки, чтобы заполнить хотя бы десять рядов, это притом, что всегда присутствует английская раскладка, которую придется каждый раз старательно пропускать. В этой статье я покажу, как сделать, чтобы раскладка клавиатуры переключалась сама, в зависимости от положения курсора в первой или второй колонке таблицы. Разумеется, это всего лишь частный пример, и предложенный алгоритм можно использовать и в иных целях, когда синтаксический формат редактируемого файла заранее известен.

Определять положение курсора будем по синтаксическому идентификатору под курсором. Соответственно, нам понадобится определить синтаксис для словарной таблицы и написать синтаксический скрипт для этого синтаксиса. Я не стал определять собственный тип файла (filetype) для словаря, а просто решил, что его типом будет vimwiki. Vimwiki очень хорошо работает с таблицами, в частности имеет отличную поддержку для навигации внутри таблицы при нажатии на клавишу табуляции, автоматического добавления строк и форматирования столбцов. Однако vimwiki не дает того, что нам нужно: синтаксического различения первого и второго столбцов, а это значит, что мы не сможем детектировать положение курсора в таблице словаря и, в виде бонуса, не сможем подсвечивать столбцы разными цветами. Что же делать? Будем рассматривать наш словарь, как синтаксическую разновидность vimwiki. Это значит, что filetype словаря будет равен vimwiki, а синтаксические особенности мы опишем в файле $HOME/.vim/after/syntax/vimwiki.vim. Для формального различения словаря и других файлов vimwiki положим, что словарь будет иметь файловое расширение .mdict. Поэтому в .vimrc добавляем строку
autocmd BufNewFile,BufRead *.mdict setlocal filetype=vimwiki | EnableXkbSwitch
(она также присутствует ниже в листинге для .vimrc). Файл $HOME/.vim/after/syntax/vimwiki.vim выглядит так:
if match(bufname('%')'\.mdict$') == -1
    finish
endif

syntax match mdictOriginal '\%(^\s*|\)\@<=[^|]\+\ze|[^-]'
            \ containedin=VimwikiTableRow contained

syntax match mdictTranslated '\%([^-]|\)\@<=[^|]\+\ze|$'
            \ containedin=VimwikiTableRow contained

hi mdictOriginalHl term=standout ctermfg=63 guifg='#d7d7ff'
autocmd ColorScheme * hi mdictOriginalHl term=standout
            \ ctermfg=63 guifg='#d7d7ff'

hi mdictTranslatedHl term=standout ctermfg=28 guifg='#d7ffd7'
autocmd ColorScheme * hi mdictTranslatedHl term=standout
            \ ctermfg=28 guifg='#d7ffd7'

hi link mdictOriginal   mdictOriginalHl
hi link mdictTranslated mdictTranslatedHl
Содержимому первого столбца таблицы соответствует регулярное выражение mdictOriginal, второго столбца - mdictTranslated. В первых строках проверяется, что файл имеет расширение .mdict, и если это не так, то скрипт сразу заканчивает работу.

А теперь код, который следует поместить в .vimrc сразу за кодом для xkb_switch (см. здесь).
" automatic keyboard layout switching in a simple dictionary in insert mode
" (filetype is a subclass of vimwiki and must have extension '.mdict';
" there must exist syntax support in dedicated script
" $HOME/.vim/after/syntax/vimwiki.vim to define matches for original and
" translated colums 'mdictOriginal' and 'mdictTranslated')
" FIXME: currently layout will not switch correctly from within select modes
fun<SID>dict_check_lang(force)
    if !executable(g:XkbSwitchLib)
        return
    endif

    let cur_synid  = synIDattr(synID(line(".")col(".")1)"name")

    if !exists('b:saved_cur_synid')
        let b:saved_cur_synid = cur_synid
    endif

    if cur_synid != b:saved_cur_synid || a:force
        let cur_layout = libcall(g:XkbSwitchLib, 'Xkb_Switch_getXkbLayout',
                    \ '')
        if b:saved_cur_synid == 'mdictOriginal'
            let b:xkb_layout_dict_orig = cur_layout
        endif
        if b:saved_cur_synid == 'mdictTranslated'
            let b:xkb_layout_dict_trans = cur_layout
        endif
        if cur_synid == 'mdictOriginal'
            if exists('b:xkb_layout_dict_orig')
                call libcall(g:XkbSwitchLib, 'Xkb_Switch_setXkbLayout',
                        \ b:xkb_layout_dict_orig)
            else
                let b:xkb_layout_dict_orig = cur_layout
            endif
        endif
        if cur_synid == 'mdictTranslated'
            if exists('b:xkb_layout_dict_trans')
                call libcall(g:XkbSwitchLib, 'Xkb_Switch_setXkbLayout',
                        \ b:xkb_layout_dict_trans)
            else
                let b:xkb_layout_dict_trans = cur_layout
            endif
        endif
        let b:saved_cur_synid = cur_synid
    endif
endfun

autocmd BufNewFile,BufRead *.mdict setlocal filetype=vimwiki | EnableXkbSwitch
autocmd BufNewFile         *.mdict VimwikiTable 2 2
autocmd BufNewFile         *.mdict exe "normal dd" | startinsert
autocmd InsertEnter        *.mdict call <SID>dict_check_lang(1)
autocmd CursorMovedI       *.mdict call <SID>dict_check_lang(0)
Теперь при открытии нового файла с расширением .mdict будет автоматически создаваться таблица размерностью 2x2. В верхней сроке нужно поместить названия столбцов. При заполнении первой строки таблицы необходимо вручную переключать требуемые раскладки клавиатуры, в дальнейшем они будут переключаться автоматически. При выходе из режима ввода будет автоматически включена английская раскладка (так как мы включили EnableXkbSwitch), при входе в режим ввода будет включена раскладка в соответствии со столбцом, в котором находится курсор. При открытии уже существующего файла словаря vim не знает о соответствии столбцов и раскладок, поэтому его придется научить снова, дважды вручную переключив раскладку в разных столбцах.

Из недостатков/недоделок нужно упомянуть неправильную работу при переключении в режим ввода из режима выделения текста (Select mode), а также отсутствие немецких дубликатов маппингов режима ввода - их можно добавить по аналогии с русскими (см. здесь) - однако, поскольку немецкая раскладка мало отличается от английской, они, скорее всего, не понадобятся вообще.