среда, 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.

Комментариев нет:

Отправить комментарий