вторник, 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. Оформили в виде плагина, см. здесь и здесь. Последнее замечание относительно неуниверсальности трансляции маппингов в новом плагине неактуально.

21 комментарий:

  1. Здравствуйте! Всё на том же хабре предлагают собрать Ваш код в виде плагина ( http://habrahabr.ru/post/162483/#comment_6075443 ).
    Для удобства я пересобрал свою библиотеку для переключения раскладки в Windows, теперь она совместима с xkb-switch-lib.
    Чтоб сделать плагин необходимо добавить проверку самой операционной системы и её разрядности. В зависимости от проверки грузить в vim тот или иной .dll или .so файл.
    Предлагаю поместить бинарные сборки библиотек в директорию плагина. Не могли бы Вы всё это реализовать? И если не трудно, свяжитесь со мной пожалуйста.
    Windows-библиотеку можно взять здесь: https://github.com/DeXP/xkb-switch-win

    ОтветитьУдалить
  2. Добрый вечер. В принципе я приветствую идею реализовать все это в виде плагина. Вот только мне не очень нравится идея поместить в него бинарные файлы (да и в Linux варианте без этого можно обойтись, просто указав, что плагин зависит от установленного в системе пакета xkb-switch). Я подумаю как все это можно организовать на следующей неделе. Вы сами можете попробовать написать плагин, даже если у вас небольшой опыт - возьмите код, который здесь приводится и поместите его в отдельный файл в директории ~/.vim/plugin (это для Linux, для Windows должно быть похоже) - это и будет плагин в первом приближении.

    ОтветитьУдалить
  3. Перенести код в файл не проблема. Проблема потом его поддерживать. Наверняка потом кто-нибудь захочет ещё какой-нибудь функционал, а продуктивно править Ваш код вряд ли смогу :-)
    Во-вторых, хотелось бы сохранить авторство - это Ваш код. Моя лишь windows-поделка.
    Про бинарники - хочу возможность установки плагина одной строкой через Vundle. Установку зависимости в Linux переживу, в Windows всё сложнее. Можно выложить бинарники отдельным архивом, для тех кто хочет. Или ещё как-нибудь автоматизировать. Не хотелось бы усложнять установку.
    По поводу кроссплатформенности - очень бы хотелось один конфиг Vim'a под все операционки. И привычное поведение и в Linux и в Windows. Вполне может быть что потом кто-нибудь с Mac OS X подтянется

    Если нет времени/желания/возможности писать/поддерживать плагин, то могу попробовать взяться я. Но хотя бы на первых порах будет нужна помощь

    ОтветитьУдалить
    Ответы
    1. Попробуйте вот эту версию: https://github.com/lyokha/vim-xkbswitch
      Ее нужно поместить в директорию plugin. У меня под Linux работает. В .vimrc нужно указать:

      let g:XkbSwitchEnabled = 1
      let g:XkbSwitchLoadRuMappings = 1
      let g:XkbSwitchLib = 'путь\к\библиотеке'

      Если путь к библиотеке не указан, то по умолчанию он равен C:\Program Files\xkb-switch-win\libdslxw.dll (для Win32: см. Исходный код). Я все же не думаю, что в плагин нужно вставлять бинарные файлы: настройка g:XkbSwitchLib неявно подразумевает, что плагин зависит от внешнего приложения и это можно указать в файле документации (которого пока ещё нет) - в нем будет указано, откуда брать xkb-switch и вашу библиотеку.

      Если у вас все заработает, то я добавлю документацию и выложу плагин на vim.org. Да, к сожалению я не силен в vundle или pathogen и обычно пакую по старинке в zip. Поэтому, если вас это смущает, то расскажите мне как они работают :)

      Удалить
    2. Немного переделал: теперь вместо let g:XkbSwitchLoadRuMappings = 1 в .vimrc нужно писать let g:XkbSwitchIMappingsLangs = ['ru'] - маппинги в инсерт моде теперь можно подгружать для произвольного набора языков (однако внутри пока только русский поддерживается)

      Удалить
    3. На маппинги ругается:

      Error detected while processing function 29_xkb_switch..29_imappings_l
      oad:
      line 32:
      E475: Invalid argument: qwertyuiop[]asdfghjkl;'zxcvbnm,.`/QWERTYUIOP{}ASDFGHJKL:
      "ZXCVBNM<>?~@#$^&|

      По библиотеке - работает. Правда Вы взяли предыдущую версию, она имеет немного другой API. Версия https://github.com/DeXP/xkb-switch-win полностью совместима с кодом, приведенным в этом посте. Например, имеет функцию Xkb_Switch_getXkbLayout, которая возвращает строку)

      Про пути: сам использую let g:XkbSwitchLib = 'libdxlsw64.dll', такая настройка портируема, а библиотеку надо пихнуть в корневую папку вима

      Про vundle: сам подцепил его отсюда http://habrahabr.ru/post/148549/ . Но Ваш плагин УЖЕ на гитхабе - установился через Vundle без проблем :-) Но на вимскриптс я б всё-равно выложил. Популярнее будет

      Удалить
    4. Обновил, теперь поддерживается новый интерфейс xkb-switch-win. По поводу ошибки в маппингах: почему-то у вас строка для транслейшна экспандится рано (там должны быть символы \\x27, \\x22, \\x24 и \\x26, а у вас они почему-то уже раскрыты до ', ", $ и & соответственно. Попробуйте вместо вхождений '\\x' в s:from и 'to' записать ещё один слеш, т.е. '\\\x' - заработает после этого? У меня работает и c тремя слешами.

      Удалить
    5. Обновил. Если не трудно, имена по умолчанию для библиотек замените пожалуйста на "libxkbswitch32.dll" и "libxkbswitch64.dll"
      '\\\x' - заменил. Везде добавил по слэшу. Не работает. Ошибка та же(

      Удалить
    6. Я не очень понимаю как тогда сработает

      if executable(g:XkbSwitch['backend']) == 1

      где g:XkbSwitch['backend'] будет равен "libxkbswitch32.dll" - у вас оно работает?

      Удалить
    7. Вы можете показать ваш вывод

      :imap

      ?

      Удалить
    8. Такой код у меня работает: let g:XkbSwitchLib = 'libxkbswitch64.dll'
      Соответственно, если g:XkbSwitch['backend'] будет равен "libxkbswitch64.dll", то тоже будет работать

      imap из Gvim'a: http://dl.dropbox.com/u/4548378/xlam/vim-imap.png

      ЗЫ И всё-таки через какой-нибудь instant-messager мы всё пофиксили быстрее бы...

      Удалить
    9. Пушнул последние изменения - убрал абсолютный путь, изменил названия библиотек. Добавил дополнительную защиту для маппингов - попробуйте, странно, что это не работает - это кроссплатформенная часть по идее.

      Удалить
    10. В последней ревизии на гитхабе выбор файла для win-библиотеки не совсем корректен. У меня установлены и переменная win32 и win64. Сейчас пытается подхватить 32-х битную версию. Так что проверять надо сначала на win64, если нет, то на win32.

      Удалить
    11. Поправил, http://www.vim.org/scripts/script.php?script_id=4503 - это ссылка на vim.org

      Удалить
    12. Обновил https://github.com/DeXP/xkb-switch-win
      Примеры использования:

      " En
      :echo libcall('libxkbswitch64.dll', 'Xkb_Switch_getCurrentCharByUS', 'Q')
      " Q

      " Ru
      echo libcall('libxkbswitch64.dll', 'Xkb_Switch_getCurrentCharByUS', 'Q')
      " Й

      " Ru
      echo libcall('libxkbswitch64.dll', 'Xkb_Switch_getCurrentCharByUS', 'q')
      " й

      " En = Ru
      echo libcall('libxkbswitch64.dll', 'Xkb_Switch_getCurrentCharByUS', '2')
      " 2

      Есть функция "получить символ для локали по английскому символу". Но туда надо передавать два аргумента: локаль и сам символ. getCurrentCharByUS сама проверит какая локаль сейчас установлена и вернёт символ из неё.
      Теперь ещё аналогичную штуку для Linux сделать и вообще хорошо станет :)

      Удалить
  4. Этот комментарий был удален автором.

    ОтветитьУдалить
  5. Приветствую. Скачал с гита ваш замечательный плагин lyokha/vim-xkbswitch и libxkbswitch32.dll к нему (4096 Kb). Всё отлично работает как надо, за исключеним одного случая: в файлах типа .txt иди .md (markdown) в режиме вставки русские буквы Ж и Б превращаются в : и , . Если убрать dll, всё становится нормлаьно, то есть причина видимо в ней.
    Самое смешное, что если редактировать файлы с типом .cpp или на других языках программирования, то описанной проблемы нет, она возникает толкьо на текстовых. Что же это за Ж... такая и как это побороть? :(

    ОтветитьУдалить
    Ответы
    1. Сценарии:
      1 Создал новый файл -- буквы на месте. Сохранил как a.txt -- буквы на месте. Выхожу из vim, снова захожу в vim, открываю a.txt -- теперь нельзя вводить буквы "ж", "б" и "ю" на любых регистрах, вместо них символы из английской раскладки.

      2. Создал новый файл, Сохранил как a.cpp. Вышел-зашёл в vim, открыл этот файл -- все буквы вводятся нормально.

      Vim v.8.0 x86, с официального сайта. Плагин взят с гита, установлен с помощью Vundle.

      Удалить
    2. Вылечилось многочсовыми медитациями над мануалом и последующим добавленим этого в vimrc:

      let g:XkbSwitchSkipIMappings =
      \ {'*' : ['.', '>', ':', ';', ',', '{', '/*', '/*'],}

      Удалить
    3. Посмотрите, я ответил в git issue.

      Удалить