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

среда, 30 октября 2013 г.

Прямое цитирование подсветки синтаксиса из vim в tex

В предыдущей статье я показал, как с помощью pandoc экспортировать статьи в формате HTML в формат tex. Поскольку статьи в моем блоге содержат множество примеров исходного кода, то необходимо, чтобы pandoc умел их правильно подсвечивать. С этой задачей pandoc справляется неплохо, делегируя ее исполнение библиотеке highlighting-kate; если же какой-то язык не поддерживается (например VimL), то можно воспользоваться пакетом latex minted.

Всё это прекрасно. Однако выяснилось, что некоторые статьи данного блога (например эта) требуют прямого цитирования подсветки vim, поскольку в них, собственно, обсуждается как vim подсвечивает код и приводятся примеры. Я рассказывал, как я вставляю подсвеченный исходный код из vim в статьи блога здесь. Главная идея - волшебная команда vim MakeBlogArticle, которая преобразует выделенный текст (или весь буфер) в формат HTML с сохраненной подсветкой синтаксиса. MakeBlogArticle использует в качестве бэкенда скрипт TOhtml, поэтому реализовать ее не сложно. Теперь же нам нужна подобная команда MakeTexCodeHighlight, которая будет транслировать выделенный текст (или весь буфер) в некоторое представление tex, совместимое с документами, сгенерированными pandoc. Требования к совместимости понятны. Поскольку pandoc для подсветки исходного кода генерирует среду Shaded, реализованную через fancyvrb, то результирующий документ должен находиться внутри тэгов
\begin{Shaded}
\begin{Highlighting}[]

...

\end{Highlighting}
\end{Shaded}
, а внутри своего тела использовать цветовые тэги \textcolor.

Давайте реализуем команду MakeTexCodeHighlight.

Прежде всего нам понадобятся функции, определяющие цвета текста и фона синтаксической группы элемента под курсором. Вот их реализация:
fun<SID>get_color_under_cursor(bg)
    let synId = synID(line(".")col(".")1)
    let name = synIDattr(synId, "name")
    if name == ''
        return 'none'
    endif
    let layer = a:bg ? "bg" : "fg"
    return <SID>Xterm2rgb256(synIDattr(synIDtrans(synId), layer))
endfun

fun! GetFgColorUnderCursor()
    return <SID>get_color_under_cursor(0)
endfun

fun! GetBgColorUnderCursor()
    return <SID>get_color_under_cursor(1)
endfun
Функция get_color_under_cursor() - рабочая лошадка, выполняющая задания двух основных функций: GetFgColorUnderCursor() и GetBgColorUnderCursor(). Ее задача очень простая - выдать шестнадцатиричное значение цвета под курсором. Выражение
synIDattr(synIDtrans(synId), layer)
возвращает номер этого цвета для 256-цветного терминала (да, я ограничился текстовой версией vim, в gvim это работать не будет!), а функция Xterm2rgb256() должна вернуть окончательное шестнадцатиричное число в виде строки. Функции Xterm2rgb256() в vim нет, мы должны ее реализовать. К счастью, подобных функций в разных плагинах vim полно. Многие из них используют обычную таблицу соответствия, однако я нашел аналитическую реализацию данной функции в плагине Colorizer. Вот адаптированная версия Xterm2rgb256(), возвращающая шестнадцатиричное число в виде строки:
" next Xterm2rgb... conversion functions are adopted from plugin Colorizer.vim
fun<SID>Xterm2rgb16(color)
    " 16 basic colors
    let r=0
    let g=0
    let b=0
    let basic16 = [
                \ [ 0x000x000x00 ], 
                \ [ 0xCD0x000x00 ], 
                \ [ 0x000xCD0x00 ], 
                \ [ 0xCD0xCD0x00 ], 
                \ [ 0x000x000xEE ], 
                \ [ 0xCD0x000xCD ], 
                \ [ 0x000xCD0xCD ], 
                \ [ 0xE50xE50xE5 ], 
                \ [ 0x7F0x7F0x7F ], 
                \ [ 0xFF0x000x00 ], 
                \ [ 0x000xFF0x00 ], 
                \ [ 0xFF0xFF0x00 ], 
                \ [ 0x5C0x5C0xFF ], 
                \ [ 0xFF0x000xFF ], 
                \ [ 0x000xFF0xFF ], 
                \ [ 0xFF0xFF0xFF ]
                \ ]
    let r = basic16[a:color][0]
    let g = basic16[a:color][1]
    let b = basic16[a:color][2]
    return printf("%02x%02x%02x", r, g, b)
endfun

fun<SID>Xterm2rgb256(color)
    let r=0
    let g=0
    let b=0
    " 16 basic colors
    if a:color < 16
        return <SID>Xterm2rgb16(a:color)
    " color cube color
    elseif a:color >= 16 && a:color < 232
        " the 6 value iterations in the xterm color cube
        let valuerange6 = [ 0x000x5F0x870xAF0xD70xFF ]
        let color=a:color-16
        let r = valuerange6[(color/36)%6]
        let g = valuerange6[(color/6)%6]
        let b = valuerange6[color%6]
    " gray tone
    elseif a:color >= 232 && a:color <= 255
        let r = 8 + (a:color-232) * 0x0a
        let g = r
        let b = r
    endif
    return printf("%02x%02x%02x", r, g, b)
endfun
Теперь мы можем определить команды vim для того, чтобы комфортно получать значения цветов синтаксического элемента под курсором из терминала (для нашей основной цели эти команды не нужны, разве что только для отладки):
command GetFgColorUnderCursor echo GetFgColorUnderCursor()
command GetBgColorUnderCursor echo GetBgColorUnderCursor()
Следующим шагом будет реализация функции split_synids(), которая будет разбивать текст внутри области текста, ограниченной значениями номеров строк, переданных ей в качестве аргументов, на отдельные элементы, включающие в себя имя синтаксического элемента, его содержание (т.е. соответствующую подстроку), номер строки, в которой он находится, а также значения цветов текста и фона в шестнадцатиричном формате.
fun<SID>split_synids(fst_line, last_line)
    let result = []
    let save_cursor = getpos('.')
    let save_winline = winline()
    call setpos('.', [0a:fst_line10])
    let cursor = getpos('.')
    while cursor[1<= a:last_line
        let old_synId = '^'
        let old_start = cursor[2]
        let cols = col('$')
        if cols == 1
            let cursor[1+= 1
            let cursor[2= 1
            call setpos('.', cursor)
            continue
        endif
        while cursor[2<= cols
            let synId = synIDattr(synID(line('.')col('.')1)'name')
            let fg = toupper(GetFgColorUnderCursor())
            let bg = toupper(GetBgColorUnderCursor())
            let cursor[2+= 1
            call setpos('.', cursor)
            if synId != old_synId
                if old_synId != '^'
                    call add(result,
                            \ {'name': old_synId,
                            \ 'content'strpart(getline('.'), old_start - 1,
                            \            cursor[2- old_start - 1),
                            \  'line'line('.')'fg': old_fg, 'bg': old_bg})
                endif
                let old_synId = synId
                let old_start = cursor[2- 1
            endif
            let old_fg = fg
            let old_bg = bg
        endwhile
        call add(result,
                    \ {'name': synId,
                    \ 'content'strpart(getline('.'), old_start - 1,
                    \            cursor[2- old_start - 1),
                    \  'line'line('.')'fg': fg, 'bg': bg})
        let cursor[1+= 1
        let cursor[2= 1
        call setpos('.', cursor)
    endwhile
    call setpos('.', save_cursor)
    let move = winline() - save_winline
    if move != 0
        let dir = move < 0 ? '^Y' : '^E'
        exe "normal ".abs(move).dir
    endif
    return result
endfun
В переменной result будем сохранять список синтаксических элементов. Манипуляции с save_cursor и save_winline нужны для восстановления положения курсора и окна после завершения работы функции. Внутренний цикл while обходит элементы одной строки слева направо, добавляя информацию о встреченных синтаксических областях в result. Для этой цели вызываются функции GetFgColorUnderCursor() и GetBgColorUnderCursor(), которые идентифицируют синтаксический элемент под курсором. Возвращенное ими значение (строка, представляющая шестнадцатиричное число) переводится в верхний регистр: это будет нужно в итоговом документе, так как latex почему-то не любит нижний регистр в шестнадцатиричных числах (во всяком случае это верно для тэгов \textcolor). Внешний цикл while перебирает строки сверху вниз.

Теперь напишем функцию write_latex_code_highlights(). Она будет выполнять основную работу по созданию документа: открывать окно для нового буфера, вводить открывающие и закрывающие тэги tex и, собственно, тело документа на основании списка, возвращенного функцией split_synids().
fun<SID>write_latex_code_highlights(fst_line, last_line, ...)
    let colors = g:colors_name
    colorscheme lucius
    let parts = <SID>split_synids(a:fst_linea:last_line)
    exe "colorscheme ".colors
    let save_paste = &paste
    new +setnowrappaste
    normal a\begin{Shaded}^M\begin{Highlighting}[]^M
    let old_line = a:fst_line
    let line = old_line
    for hl in parts
        let line = hl['line']
        while line > old_line
            normal o
            let old_line += 1
        endwhile
        let part = escape(hl['content'], '\{}_$%')
        let part = substitute(part, '\\\\''\\textbackslash{}''g')
        let fg = hl['fg']
        " small hack to paint syntax group links that could have been lost
        " after switching colorscheme in black instead white
        if a:0 && a:1 && fg == 'FFFFFF'
            let fg = '000000'
        endif
        if fg != 'NONE' && part !~ '^\s*$'
            let part = '\textcolor[HTML]{'.fg.'}{'.part.'}'
        endif
        exe "normal a".part
    endfor
    while line < a:last_line
        normal o
        let line += 1
    endwhile
    normal a^M0^D\end{Highlighting}^M\end{Shaded}^[gg
    set ft=tex
    if !save_paste
        set nopaste
    endif
endfun
Код, в общем-то, не должен вызывать затруднений. Прокомментирую лишь некоторые моменты. Я уже писал, зачем при создании документов для публикации я заменяю рабочую цветовую схему на схему lucius: просто она светлая и более подходит для создания статей, чем моя стандартная темная схема. Элементы ^M, ^D и ^[ в коде состоят не из двух символов; это одиночные символы, соответствующие вводу <Enter>, Ctrl-D и <Esc>. Чтобы их ввести в редакторе vim, наберите Ctrl-V и, удерживая клавишу Ctrl, соответствующий символ (M, D или <Esc>). Команда new устанавливает в новом буфере значения nowrap (чтобы строки не заворачивались - нет смысла) и paste (чтобы vim не выполнял вредную автоматическую индентацию текста). Поскольку значение paste устанавливается глобально, то оно восстанавливается при выходе из функции. Внутри цикла for собранные функцией split_synids() синтаксические элементы выводятся с помощью normal a, новые строки добавляются с помощью normal o. При выводе в буфер все символы {, }, _, $ и % слэшируются, а символ \ заменяется на \textbackslash{} - если этого не сделать, то возможны ложные интерпретации строк буфера как команд latex! Если цвет fg данного элемента не NONE, то он обрамляется тэгом \textcolor[HTML]{}{}.

Опциональный третий аргумент write_latex_code_highlights() преобразует белый текст в черный. Белый текст может появиться в результате исчезновения синтаксических групп при переходе в новую цветовую схему. Дело в том, что я использую расширенную цветовую схему для отображения тэгов ctags (см. эту статью), и при замене ее на lucius все группы, добавленные ctags, исчезают, при этом функция GetFgColorUnderCursor() возвращает почему-то значение ffffff, соответствующее белому цвету, хотя реальный цвет "потерянных" элементов черный. В общем этот третий аргумент можно рассматривать как хак и в реальности он вряд ли нужен.

Вот определение команды MakeTexCodeHighlight:
command -range=% MakeTexCodeHighlight
        \ silent call <SID>write_latex_code_highlights(<line1><line2>1)
Теперь можно выделять нужные строки в буфере (без выделения команда обработает весь буфер) и, после ввода команды MakeTexCodeHighlight, копировать текст в новом окне и вставлять его в документ tex, созданный pandoc.

Пример.

Код на C++:
int  main( void )
{
    return 0;
}
Код tex, сгенерированный MakeTexCodeHighlight:
\begin{Shaded}
\begin{Highlighting}[]
\textcolor[HTML]{005F87}{int}  \textcolor[HTML]{008700}{main}\textcolor[HTML]{870087}{(} \textcolor[HTML]{005F87}{void} \textcolor[HTML]{870087}{)}
\textcolor[HTML]{870087}{\{}
    \textcolor[HTML]{005FAF}{return} \textcolor[HTML]{AF5F00}{0}\textcolor[HTML]{870087}{;}
\textcolor[HTML]{870087}{\}}
\end{Highlighting}
\end{Shaded}
(кстати, чтобы вставить сюда этот результат я сначала выполнил MakeTexCodeHighlight, а затем, уже в новом буфере, MakeBlogArticle).
 
Update. Заметил, что MakeTexCodeHighlight неправильно обрабатывает эти дословные символы ^M, ^D и т.п. Команда MakeBlogArticle делает это правильно, видимо в TOhtml они предварительно транслируются.

Update 2. В функции split_synids() тоже есть контрольные символы ^Y и ^E. Во всех случаях их использования можно избежать. Вообще, их появление в скрипте связано с эмуляцией действий пользователя в терминале с помощью комады normal. Если мы найдем соответствующие функции в API VimL, то команда normal будет не нужна. В самом деле, для сохранения и восстановления окна в API VimL предусмотрены функции winsaveview() и winrestview() - использование их вместо эмуляции действий пользователя в split_synids() упростит эту функцию и поможет избежать побочных эффектов, связанных с возможным ремаппингом  ^Y и ^E пользователем. В функции write_latex_code_highlights() команда normal с контрольными символами используется для эмуляции ввода пользователем строк. В VimL есть функция append(), которая может легко заменить эту эмуляцию и, соответственно, убрать контрольные символы из кода write_latex_code_highlights() и упростить ее понимание.

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