fun! <SID>xkb_switch(mode) let cur_layout = substitute(system('xkb-switch'), '\n', '', 'g') if a:mode == 0 if cur_layout != 'us' call system('xkb-switch -s us') endif let b:xkb_layout = cur_layout elseif a:mode == 1 if exists('b:xkb_layout') && b:xkb_layout != cur_layout call system('xkb-switch -s '.b:xkb_layout) endif endif endfun if executable('xkb-switch') autocmd InsertEnter * call <SID>xkb_switch(1) autocmd InsertLeave * call <SID>xkb_switch(0) endifВсе интересное происходит в функции xkb_switch(mode), которая вызывается с разными аргументами mode при выходе из режима ввода (значение mode=0) и входе в режим ввода (значение mode=1). В первом случае мы вызываем системную команду xkb-switch -s us, которая принудительно переводит раскладку клавиатуры в латиницу, запоминая какая раскладка была на выходе из режима ввода в буферной переменной b:xkb_layout. Во втором случае (при входе в режим ввода) проверяется, существует ли переменная b:xkb_layout, и если это так, то вызывается xkb-switch c опцией -s, в которую передается значение этой переменной. Это позволяет восстановить раскладку клавиатуры, установленную в данном буфере перед выходом из режима ввода. Я еще раз хочу подчеркнуть: в данном буфере! То есть раскладка устанавливается для каждого буфера такой, которой она была при выходе из режима ввода именно в нем, а это очень удобно.
Update. Небольшое дополнение для тех, кто пользуется режимом выделения текста (Select mode). Это очень удобный режим, когда текст сначала выделяется, а затем поверх него пишется новый текст (аналог классического режима выделения текста в офисных редакторах). Перейти в него из нормального режима vim можно, набрав gh (нормальный режим выделения), gH (строчный режим выделения) или g<C-h> (блочный режим выделения). Кроме того, можно переключаться между визуальным режимом и режимом выделения текста с помощью <C-g>, а также настроить vim таким образом, чтобы выделение текста мышью переводило его в режим выделения текста (вместо визуального режима по умолчанию).
Поскольку я иногда применяю режим выделения текста, то быстро обнаружил, что при его использовании, автоматическое переключение раскладки ведет себя неподобающе, а именно: если ожидается переключение на кириллицу, то первый введенный символ остается латинским, а следующие, как и ожидалось, печатаются кириллицей. Это легко объясняется тем, как работает режим выделения текста: вначале vim проверяет, является ли вводимый символ печатным, затем, если это так, этот символ вводится в начале выделенного текста, а уже после этого vim переходит в режим ввода. Очевидно, что первый введенный символ останется латинским, так как событие InsertEnter в момент его ввода еще не наступило.
Для того, чтобы исправить это недоразумение, я добавил новую функцию xkb_mappings_load(), которая переопределяет вышеперечисленные команды gh, gH, g<C-h> и <C-g>, с помощью маппингов, которые вызывают функцию xkb_switch() с нужным аргументом mode. Сразу хочу отметить, что проблема останется для тех, кто использует мышь для перехода в режим выделения текста (вы можете добавить аналогичный маппинг для этого случая сами), а также в случае выхода из режима выделения с помощью клавиши <Esc> (дело в том, что xkb_switch() может перевести раскладку в кириллицу, выход же из режима выделения текста с помощью <Esc> минует режим ввода, а следовательно мы можем оказаться в нормальном режиме с кириллической раскладкой).
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>xkb_switch(mode) let cur_layout = substitute(system('xkb-switch'), '\n', '', 'g') if a:mode == 0 if cur_layout != 'us' if !exists('b:xkb_mappings_loaded') call <SID>xkb_mappings_load() endif call system('xkb-switch -s us') endif let b:xkb_layout = cur_layout elseif a:mode == 1 if exists('b:xkb_layout') && b:xkb_layout != cur_layout call system('xkb-switch -s '.b:xkb_layout) endif endif endfunОбратите внимание: ^H в первой строке функции xkb_mappings_load() не является записью двух символов ^ и H! Это один символ, который соответствует <Bs> в таблице ASCII. Для того, чтобы ввести его в vim, следует в режиме ввода набрать <C-v>008.
Функция xkb_mappings_load() вызывается из xkb_switch() только в случае необходимости, то есть когда при выходе из режима ввода раскладка клавиатуры не является латинской (точнее us), и только для текущего буфера. Почему я просто не замапил эти команды в глобальной области, например рядом с автокомандами InsertEnter и InsertLeave? Все просто: маппинги <C-g> в визуальном режиме с использованием вызова команд в нормальном режиме не даются даром: заметили использование команды gv внутри маппингов, которая говорит: выдели-ка потерянное выделение заново? Потеря и восстановление области выделения приводят к перерисовке текста на экране. Поскольку редактировать русские тексты приходится очень нечасто, то глобальное определение маппингов для <C-g> ухудшило бы юзабилити vim без необходимости.
Update 2. По каким-то причинам вызов функции system() из autocmd InsertEnter может иногда приводить к появлению мусора в строке ввода если vim стартовал в режиме ввода (например был запущен командой vim -c start или, при наличии плагинов c.vim или perl-support.vim, пользователь открыл для редактирования новый C/C++ или Perl файл и соответствующий плагин вставил хедер с переводом vim в режим ввода). Ниже приводится вариант функции xkb_switch(), в котором system() не вызывается при первом переводе буфера в режим ввода, а также добавлен комментарий с упоминанием этого неприятного феномена.
fun! <SID>xkb_switch(mode) if a:mode == 0 " by some reason calling system() function may produce garbage on " display if vim started in Insert mode (e.g. when c-support, " perl-support etc. apply file header for a new file), so definition " of cur_layout was moved inside if- and else- blocks from top level let cur_layout = substitute(system('xkb-switch'), '\n', '', 'g') if cur_layout != 'us' if !exists('b:xkb_mappings_loaded') call <SID>xkb_mappings_load() endif call system('xkb-switch -s us') endif let b:xkb_layout = cur_layout elseif a:mode == 1 if exists('b:xkb_layout') let cur_layout = substitute(system('xkb-switch'), '\n', '', 'g') if b:xkb_layout != cur_layout call system('xkb-switch -s '.b:xkb_layout) endif endif endif endfunUpdate 3. См. важное обновление здесь.