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

воскресенье, 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), а также отсутствие немецких дубликатов маппингов режима ввода - их можно добавить по аналогии с русскими (см. здесь) - однако, поскольку немецкая раскладка мало отличается от английской, они, скорее всего, не понадобятся вообще.

среда, 12 декабря 2012 г.

vim и tex: подсветка внешнего кода minted

Текущая ситуация с подсветкой кода minted в исходниках tex в vim оставляет желать лучшего. Я не стану с уверенностью утверждать, что она так же плоха в различных плагинах tex в vim: я ими не пользуюсь по причине излишней тяжеловесности. Но стандартный синтаксический файл tex.vim не умеет работать с подсветкой minted. Вот вам пример:
\documentclass{article}

\usepackage{minted}

\begin{document}

Here is a \textbf{C++} Hello World example:

\begin{minted}[fontsize=\tiny,linenos=false,gobble=2]{c++}
  #include <iostream>
  int main( void )
  {
      std::cout << "Hello world" << std::endl;
      return 0;
  }
\end{minted}

Here is a \textbf{Python} Hello World example:

\begin{minted}[fontsize=\tiny,linenos=false,gobble=2]{python}
  #!/usr/bin/python
  print "Hello, World!"
\end{minted}

Here is a \textbf{Unknown} Hello World example:

\begin{minted}[fontsize=\tiny,linenos=false,gobble=2]{unknown}
  $my_directive <- { p : 10 }{ p : 20 }
  end $my_directive
\end{minted}

Bye.

\end{document}

Видите, гипотетический язык Unknown уже подсвечивается странно, а сейчас мы вставим киллер-код на sh, который полностью уничтожит синтаксис tex:
\documentclass{article}

\usepackage{minted}

\begin{document}

Here is a highlight killer:

\begin{minted}[fontsize=\tiny,linenos=false,gobble=2]{sh}
  #!/usr/bin/sh
  a=$HOME; hello_world="Hello world"
\end{minted}

Here is a \textbf{C++} Hello World example:

\begin{minted}[fontsize=\tiny,linenos=false,gobble=2]{c++}
  #include <iostream>
  int main( void )
  {
      std::cout << "Hello world" << std::endl;
      return 0;
  }
\end{minted}

Here is a \textbf{Python} Hello World example:

\begin{minted}[fontsize=\tiny,linenos=false,gobble=2]{python}
  #!/usr/bin/python
  print "Hello, World!"
\end{minted}

Here is a \textbf{Unknown} Hello World example:

\begin{minted}[fontsize=\tiny,linenos=false,gobble=2]{unknown}
  $my_directive <- { p : 10 }{ p : 20 }
  end $my_directive
\end{minted}

Bye.

\end{document}

Это катастрофа! Я хочу, чтобы это выглядело так:
\documentclass{article}

\usepackage{minted}

\begin{document}

Here is a highlight killer:

\begin{minted}[fontsize=\tiny,linenos=false,gobble=2]{sh}
  #!/usr/bin/sh
  a=$HOME; hello_world="Hello world"
\end{minted}

Here is a \textbf{C++} Hello World example:

\begin{minted}[fontsize=\tiny,linenos=false,gobble=2]{c++}
  #include <iostream>
  int main( void )
  {
      std::cout << "Hello world" << std::endl;
      return 0;
  }
\end{minted}

Here is a \textbf{Python} Hello World example:

\begin{minted}[fontsize=\tiny,linenos=false,gobble=2]{python}
  #!/usr/bin/python
  print "Hello, World!"
\end{minted}

Here is a \textbf{Unknown} Hello World example:

\begin{minted}[fontsize=\tiny,linenos=false,gobble=2]{unknown}
  $my_directive <- { p : 10 }; { p : 20 }
  end $my_directive
\end{minted}

Bye.

\end{document}

Участок с sh подсвечен синтаксисом sh, с C++ - синтаксисом C++, с Python - синтаксисом Python, а нечто непонятное Unknown - синтаксисом texZone из файла syntax/tex.vim. Язык программирования - параметр minted в фигурных скобках - подсвечен отдельно, в данном случае цветом цветовой группы Special.

Теперь о том, как этого добиться. Поскольку плагин Vimwiki уже умеет загружать синтаксическую подсветку для разных языков программирования (об этой возможности Vimwiki я рассказывал здесь), то пусть он потрудится и для tex. Я не шучу! Мы напишем наш after-syntax файл $HOME/.vim/after/syntax/tex.vim в котором будет использован вызов VimwikiGet() и слегка адаптированная функция vimwiki#base#nested_syntax()! Вот его содержание, а комментарии ниже:
" content highlights inside lstlisting must be disabled
syntax region texZone start="\\begin{lstlisting}" end="\\end{lstlisting}"
" fallback option for minted is also texZone
syntax region texZone start="\\begin{minted}" end="\\end{minted}"

if !exists('g:tex_hl_minted') || g:tex_hl_minted == 0
    finish
endif

" Vimwiki's function vimwiki#base#nested_syntax() adaptation
functions:nested_syntax(hltype, filetype, start, end) abort
" From http://vim.wikia.com/wiki/VimTip857
  let ft=toupper(a:filetype)
  let group='textGroup'.ft
  if exists('b:current_syntax')
    let s:current_syntax=b:current_syntax
    " Remove current syntax definition, as some syntax files (e.g. cpp.vim)
    " do nothing if b:current_syntax is defined.
    unlet b:current_syntax
  endif

  " Some syntax files set up iskeyword which might scratch vimwiki a bit.
  " Let us save and restore it later.
  " let b:skip_set_iskeyword = 1
  let is_keyword = &iskeyword

  try
    " keep going even if syntax file is not found
    execute 'syntax include @'.group.' syntax/'.a:filetype.'.vim'
    execute 'syntax include @'.group.' after/syntax/'.a:filetype.'.vim'
  catch
  endtry

  execute 'syntax match texExtCodeLang "{\@<='.a:hltype.'\ze}"'
  execute 'syntax region texMintedStart start="\\begin{minted}" end="}" '.
              \ 'contains=texSectionMarker,texBeginEnd,texExtCodeLang '.
              \ 'nextgroup=@'.group.' contained'

  let &iskeyword = is_keyword

  if exists('s:current_syntax')
    let b:current_syntax=s:current_syntax
  else
    unlet b:current_syntax
  endif

  execute 'syntax region textSnip'.ft.
        \ ' start="'.a:start.'" end="'.a:end.'"'.
        \ ' contains=@'.group.',texMintedStart '.
        \ 'containedin=texDocZone,texPartZone,texChapterZone,'.
        \ 'texSectionZone,texSubSectionZone,texSubSubSectionZone,'.
        \ 'texParaZone,texSubParaZone,texAbstract keepend'

  " A workaround to Issue 115: Nested Perl syntax highlighting differs from
  " regular one.
  " Perl syntax file has perlFunctionName which is usually has no effect due to
  " 'contained' flag. Now we have 'syntax include' that makes all the groups
  " included as 'contained' into specific group.
  " Here perlFunctionName (with quite an angry regexp "\h\w*[^:]") clashes with
  " the rest syntax rules as now it has effect being really 'contained'.
  " Clear it!
  if ft =~ 'perl'
    syntax clear perlFunctionName
  endif
endfunction

let s:regStart = '\\begin{minted}\s*\n*\(\[\_[^]]*\]\)*\s*\n*{'
let s:regEnd ='\ze\\end{minted}'

let s:nested = VimwikiGet('nested_syntaxes')

if !empty(s:nested)
  for [s:hl_syntax, s:vim_syntax] in items(s:nested)
    if s:vim_syntax == 'tex'
      continue
    endif
    call s:nested_syntax(s:hl_syntax, s:vim_syntax,
                \ s:regStart.s:hl_syntax.'}', s:regEnd)
  endfor
endif

hi link texExtCodeLang Special

Чтобы это правильно работало, не забудьте вставить в .vimrc определение g:WikiGlobal.nested_syntaxes с указанием тех языков программирования, которые вы намерены подсвечивать в Vimwiki и Tex / minted. Кроме того, добавьте туда строки
let g:tex_isk = '48-57,a-z,A-Z,192-255,_'
let g:tex_hl_minted = 1
Теперь комментарии по коду. Первые два определения syntax region указывают, что мы хотим подсвечивать области, начинающиеся с \begin{lstlisting} или \begin{minted} и заканчивающиеся на \end{lstlisting} или \end{minted} соответственно, одним цветом региона texZone. Директива lstlisting - это часть пакета tex listings, который похож на minted, но, в отличие от последнего, использует не Python Pygments, а какие-то собственные алгоритмы. Его было бы тоже неплохо подсвечивать синтаксисом используемого языка программирования, но, к сожалению, в listings , в отличие от minted, язык задается в отдельной директиве.

Почему мы здесь задаем одноцветную подсветку texZone и для minted тоже? Ведь мы собирались использовать полноценный синтаксис языка программирования внутри его региона! Ответ прост - это fallback режим для языков типа Unknown, не указанных в g:WikiGlobal.nested_syntaxes. Более точные определения синтаксических регионов для всех сконфигурированных в g:WikiGlobal.nested_syntaxes языков задаются в цикле for в нижней части приведенного кода. Переменная s:nested, являющаяся выходным значением функции VimwikiGet(), содержит маппинг, заданный в переменной g:WikiGlobal.nested_syntaxes.

Главная задача цикла for - настройка уточненных синтаксических регионов minted для всех сконфигурированных языков программирования (кроме собственно tex - иначе мы получим одноцветный регион texZone для всех языков). В цикле происходит вызов функции s:nested_syntax() - слегка видоизмененной vimwiki#base#nested_syntax(). Изменений там немного. Во-первых изменен прототип: первым аргументом добавлен ключ из элемента  g:WikiGlobal.nested_syntaxes - он нужен для формирования синтаксической области texExtCodeLang - см. далее, и убран последний аргумент textSnipHl, который соответствовал ненужному нам matchgroup в синтаксическом регионе textSnip<Lang> (где <Lang> - синтаксическая группа, соответствующая конфигурируемому языку). Добавлены определения syntax match texExtCodeLang и syntax region texMintedStart, который внесен в список contains в определении syntax region textSnip<Lang>. Определение синтаксического региона textSnip - главная задача функции s:nested_syntax().

Синтаксический регион texMintedStart соответствует началу области minted и соответствует сложному регулярнуму выражению, заданному в переменной s:regStart. Сложность выражения позволяет разбивать отдельные элементы преамбулы \begin{minted} на отдельные строки. Область texExtCodeLang соответствует спецификации языка программирования в преамбуле minted и будет подсвечиваться цветом группы Special (см. последнюю строку приведенного кода).

Ну вот собственно и все, теперь все подсвечивается правильно. Спасибо Vimwiki!

Напоследок хочу уточнить, что указание переменной g:tex_isk будет работать только в последних версиях syntax/tex.vim. Например, в стандартном пакете vim в Fedora 17 используется старая версия этого файла, поэтому я положил текущую версию из репозитория vim к себе в $HOME/.vim/syntax/. Вообще, g:vim_isk нужен нам только для задания символа подчеркивания (_) в качестве символа iskeyword для файлов tex. В нашем случае это исправит возможные искажения синтаксиса внутри регионов minted, если в них используются символы подчеркивания. Для файлов tex символ подчеркивания был убран из списка iskeyword по каким-то соображениям, однако в последних версиях syntax/tex.vim появилась возможность вернуть его с помощью переменной g:tex_isk. Если у вас старая версия syntax/tex.vim и вы не хотите устанавливать новую версию в $HOME/.vim/syntax/, то можете просто добавить строку
setlocal iskeyword+=_
в файл $HOME/.vim/after/syntax/tex.vim.

пятница, 9 ноября 2012 г.

vim: плагин Vimwiki

После перехода из Gnome 3 на MATE встала задача перенести наработанные заметки из gnote в mnote. Задача, как это ни странно, оказалась непосильной. И поэтому я принял решение завязать с использованием десктоп-зависимых приложений для хранения заметок и найти им замену среди плагинов vim. Самым популярным плагином в vim.org, найденным по ключевому слову wiki, оказался vimwiki. Первые шаги в его использовании очень просты: запускаем vim, набираем \ww, подтверждаем, что рабочей директорией vimwiki будет $HOME/vimwiki (или выбираем другую) и можно приступать к редактированию индексной страницы.

Я не хочу вдаваться в подробности редактирования страниц в vimwiki - для этого имеется неплохая документация, да и сам я пока не набрал достаточного опыта. Хочу лишь отметить, что возможности и уровень юзабилити vimwiki весьма высоки: работать с ним действительно приятно и удобно. Дочерние страницы создаются простым нажатием на клавишу Enter, синтаксическая подсветка радует глаз, удобная навигация по иерархии страниц, возможность экспорта в HTML, независимая подсветка фрагментов кода в соответствии с языком, ссылки на ресурсы (в том числе на локальные файлы), которые открываются во внешней программе в соответствии с их типом. Правда, в последнем случае обнаружилась и ложка дегтя: для открытия файлов во внешней программе vimwiki использует команду silent !xdg-open, которая портит терминал (возможно, только у меня), и для того, чтобы вернуть его в нормальное состояние, нужно ввести Ctrl-L. Но это проблема не vimwiki, a vim, да и открывать картинки из vim я не собираюсь.

Ниже я привожу настройки, которые я добавил в файл .vimrc для vimwiki.
" ---- Vimwiki settings
" ----
let g:WikiGlobal = {}
let g:WikiGlobal.nested_syntaxes = {'c''c''c++': 'cpp', 'perl''perl',
            \ 'python''python''sh''sh'}
let g:vimwiki_list = [g:WikiGlobal]

" disable concealing of short links
let g:vimwiki_url_maxsave = 42

" redefine original <C-Up> and <C-Down> mappings in vimwiki buffers
nmap <silent> <Leader>wn <Plug>VimwikiDiaryNextDay
nmap <silent> <Leader>wp <Plug>VimwikiDiaryPrevDay

" add schemes 'vlocal:' and 'vfile:' to open files in a new tab
fun! VimwikiLinkHandler(link)
    let link = a:link
    if link =~ "vlocal:" || link =~ "vfile:"
        let link = link[1:]
    else
        return 0
    endif
    let [idx, scheme, path, subdir, lnk, ext, url] =
                \ vimwiki#base#resolve_scheme(link, 0)
    if g:vimwiki_debug
        echom 'LinkHandler: idx='.idx.', scheme=[v]'.scheme.', path='.path.
                \ ', subdir='.subdir.', lnk='.lnk.', ext='.ext.', url='.url
    endif
    if url == ''
        echom 'Vimwiki Error: Unable to resolve link!'
        return 0
    else
        call vimwiki#base#edit_file('tabnew', url, [], 0)
        return 1
    endif
endfun
Переменная g:WikiGlobal нужна в данном случае только для определения независимой подсветки фрагментов кода внутри страниц (кстати, обратим внимание на некорректную подсветку синтаксиса в районе 'c++': 'cpp'). Например, фрагмент
{{{c++
int main( void )
{
    return 0;
}
}}}
корректно подсвечен как исходник на C++.

Также я увеличил значение переменной g:vimwiki_url_maxsave до 42, чтобы ссылки на ресурсы не срезались до очень коротких, переопределил маппинги для VimwikiDiaryNextDay и VimwikiDiaryPrevDay, которые изначально соответствовали <C-Down> и <C-Up> (а эти комбинации клавиш я использую для навигации между окнами), и добавил функцию-обработчик нажатия на ссылки VimwikiLinkHandler(), в которой определена обработка новых типов ресурсов vlocal и vfile. Дело в том, что по умолчанию vimwiki открывает ссылки на файлы (они соответствуют типам ресурсов local и file) во внешней программе (как я уже говорил, с помощью xdg-open). Ну а обработчик VimwikiLinkHandler() будет открывать ресурсы новых типов vlocal и vfile в отдельной вкладке. Исходный код данного обработчика приведен в документации vimwiki, что подтверждает ее высокий уровень.

Все это здорово, но хотелось бы запускать vimwiki сразу из командной строки. Сделать это просто. Поместим в .bashrc простую функцию:
function vimwiki
{
    if (( $# == 0 )) ; then
        vim -c VimwikiIndex
        return
    fi
    if [ $1 = "-l" ] ; then
        local columns=`stty size | cut -d' ' -f2`
        for in $HOME/vimwiki/*.wiki ; do
            echo `basename $i` | sed 's/\.wiki$//'
        done | COLUMNS=$columns column
        return
    fi
    vim -c VimwikiIndex -c "VimwikiGoto $1"
}
Теперь в нашей оболочке появилась новая команда vimwiki. Если запустить ее без параметров, то откроется индексная страница vimwiki. Кроме того, эта команда позволяет вывести список всех страниц vimwiki, если ей передан аргумент -l (в предположении, что рабочей директорией vimwiki является $HOME/vimwiki), а также открыть vim на определенной странице, когда имя страницы передано ей в первом аргументе.