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

воскресенье, 15 января 2012 г.

vim: плагин Powerline

Вчера установил новый плагин Powerline. Этот плагин позволяет сделать статусную строку vim весьма привлекательной, с возможностью легко различать текущий режим. Ниже я привожу два снимка окна терминала с двумя открытыми файлами и буфером tagbar справа (на официальном сайте Powerline тоже есть скриншоты, см. здесь). Активный буфер расположен внизу, соответственно Powerline подсвечивает его статусную строку ярче и информативнее. На первом снимке буфер находится в нормальном режиме, на втором - в режиме ввода.


Как видно на снимках, Powerline поддерживает tagbar, но не совсем из коробки, но об этом позже. Сначала о том, как настроить Powerline, чтобы он показывал статусную строку в максимально привлекательном виде.

Просто установить Powerline для этого недостаточно. Стандартные консольные шрифты не содержат этих привлекательных символов - угловых стрелок, символа FT и др., которые видны на приведенных снимках. Эти символы определены в файле PowerlineSymbols.sfd в директории fontpatcher/ плагина. В той же директории находится файл README.rst, в котором приведена инструкция для настройки шрифта консоли. Я попробую пересказать ее своими словами и добавить некоторые дополнительные сведения, которые я почерпнул при установке нового шрифта. Если вы боитесь или не хотите устанавливать новый шрифт, то можно использовать и стандартную настройку Powerline по умолчанию (она называется compatible), в этом случае вместо привлекательных стрелочек вы увидите простые консольные символы '|'. Есть еще один вариант настройки, который не предусматривает изменения стандартного шрифта, но делает статусную строку все же немного привлекательнее: это использование стандартных символов Unicode. Для этого поместите в файл .vimrc настройку
let g:Powerline_symbols = 'unicode'
Вернемся к установке нового шрифта. Хотя новый шрифт - это громко сказано. Фактически следует просто взять тот шрифт, который используется в вашей консоли и добавить в него новые символы из PowerlineSymbols.sfd с помощью программы fontpatcher из директории fontpatcher/. fontpatcher использует FontForge, поэтому следует предварительно установить этот пакет в вашу систему. Лично я использую шрифт DejaVu Sans Mono, который в моей системе (Fedora 16) находится в директории /usr/share/fonts/dejavu/. Чтобы добавить новые символы в этот шрифт, перейдем в директорию fontpatcher/ и выполним команду:
./fontpatcher --symbol-font PowerlineSymbols.sfd /usr/share/fonts/dejavu/DejaVuSansMono.ttf
Теперь в этой директории должен появиться новый файл DejaVuSansMono-Powerline.otf, который следует поместить в директорию $HOME/.fonts/. После этого от имени суперпользователя запускаем команду
fc-cache -vf
Теперь следует установить данный шрифт в качестве шрифта терминала. Заходим в настройки терминала и находим диалог выбора шрифта. fontpatcher добавляет в имя шрифта строку for Powerline, соответственно в моем случае шрифт будет называться DejaVu Sans Mono for Powerline. В терминале XFCE новый шрифт будет доступен сразу, в konsole - не знаю, ну а в случае gnome-terminal из Gnome 3 придется выполнить массу неочевидных и логически необоснованных действий (как это заведено в Gnome 3). Вы не найдете нового шрифта в диалоге выбора gnome-terminal. Я не знаю, может быть есть лучший способ, но я просто решил сделать новый шрифт системным Мonospace шрифтом в Gnome 3. И даже это сделать не так-то просто: для начала нужно установить (по крайней мере в Fedora 16) пакет gnome-tweak-tool, затем собственно запустить gnome-tweak-tool и в диалоге выбора Monospace шрифта выбрать DejaVu Sans Mono for Powerline. После этого действия выбор данного шрифта останется недоступным в gnome-terminal (ну чего еще-то ему не хватает? :) ), зато теперь можно выбрать Системный моноширинный шрифт и им, наконец-то, окажется наш пропатченный DejaVu Sans Mono.

Огромный плюс в установке нового шрифта - это то, что рендерится он качественнее, чем оригинал, вот это сюрприз!

Для правильного отображения новых символов в .vimrc нужно добавить строку
let g:Powerline_symbols = 'fancy'
Однако, если вы успели поэкспериментировать с другими значениями g:Powerline_symbols, то вам придется удалить кеш-файл, который находится в /tmp/Poweline.cache (либо, если у вас установлена переменная среды $TEMP, в $TEMP/Powerline.cache).

Еще одна тонкость - возможно вы заметите, что при переходе из режима ввода в нормальный режим путем нажатия клавиши Esc, обновление статусной строки будет задерживаться примерно на секунду. Чтобы избежать этого, поместите в .vimrc строку
set timeout timeoutlen=1000 ttimeoutlen=50
(в данном случае важно установить относительно небольшое значение ttimeoutlen, например 50 миллисекунд, значение timeoutlen для данной проблемы не играет роли, и, если вы не знаете, что это такое, то вообще не указывайте здесь timeoutlen).

И еще одна тонкость. По умолчанию, если в vim открыто единственное окно, то статусная строка вообще не показывается. Чтобы статусная строка присутстствовала всегда, добавьте в .vimrc строку
set laststatus=2
Теперь вернемся к tagbar. Powerline поддерживает статусную строку tagbar - это видно на приведенных скриншотах. Однако работает это криво. Во-первых, статусная строка tagbar не изменяется при первом открытии окна tagbar; во-вторых, при переходе на тег из окна tagbar в окно целевого буфера, статусные строки также не перерисовываются, несмотря на то, что активное окно изменилось. Проблема в том, что в самый ответственный момент, когда происходят события открытия окна tagbar и перехода на новую метку, в коде tagbar (в функциях файла autoload/tagbar.vim) устанавливаются значения опции eventignore в all. Соответственно все автокоманды, определенные в Powerline (а также и в других плагинах, и любые пользовательские) игнорируются. Мы не станем убирать установку eventignore в all, вдруг это сделано неспроста и что-нибудь сломается. Мы просто введем понятие хуков для трех событий: отрисовки статусной строки при запуске нового окна tagbar, покидания окна tagbar при переходе на метку, и перехода в целевое окно из окна tagbar при переходе на метку. Соответственно, названия хуков будут g:tagbar_statusline_hook, g:tagbar_leave_hook, g:tagbar_dstwin_enter_hook. Хуки следует разместить, соответственно, в функциях s:InitWindow() и s:JumpToTag() в файле autoload/tagbar.vim. Вот простейший патч (относительно версии tagbar 2.3.0):
--- tagbar.vim  2011-12-24 09:14:54.000000000 +0400
+++ tagbar.vim.new  2012-01-15 18:07:07.691191871 +0400
@@ -1478,17 +1478,21 @@
     " Reset fold settings in case a plugin set them globally to something
     " expensive. Apparently 'foldexpr' gets executed even if 'foldenable' is
     " off, and then for every appended line (like with :put).
     setlocal foldmethod&
     setlocal foldexpr&
 
-    " Earlier versions have a bug in local, evaluated statuslines
-    if v:version > 701 || (v:version == 701 && has('patch097'))
-        setlocal statusline=%!TagbarGenerateStatusline()
+    if exists('g:tagbar_statusline_hook')
+        execute g:tagbar_statusline_hook
     else
-        setlocal statusline=Tagbar
+        " Earlier versions have a bug in local, evaluated statuslines
+        if v:version > 701 || (v:version == 701 && has('patch097'))
+            setlocal statusline=%!TagbarGenerateStatusline()
+        else
+            setlocal statusline=Tagbar
+        endif
     endif
 
     " Script-local variable needed since compare functions can't
     " take extra arguments
     let s:compare_typeinfo = {}
 
@@ -2427,13 +2431,19 @@
     let eventignore_save = &eventignore
     set eventignore=all
 
     " This elaborate construct will try to switch to the correct
     " buffer/window; if the buffer isn't currently shown in a window it will
     " open it in the first window with a non-special buffer in it
+    if exists('g:tagbar_leave_hook')
+        execute g:tagbar_leave_hook
+    endif
     wincmd p
+    if exists('g:tagbar_dstwin_enter_hook')
+        execute g:tagbar_dstwin_enter_hook
+    endif
     let filebufnr = bufnr(taginfo.fileinfo.fpath)
     if bufnr('%') != filebufnr
         let filewinnr = bufwinnr(filebufnr)
         if filewinnr != -1
             execute filewinnr . 'wincmd w'
         else
Теперь определим хуки в .vimrc:
let g:tagbar_statusline_hook = 'call Pl#UpdateStatusline(0)'
let g:tagbar_leave_hook = 'call Pl#UpdateStatusline(0)'
let g:tagbar_dstwin_enter_hook = 'call Pl#UpdateStatusline(1)'
После этого tagbar будет правильно работать с Powerline.

понедельник, 28 ноября 2011 г.

vim: TagHighlight и tagbar - немного магии цвета

Про плагин TagHighlight (который раньше назывался ctags_highlighting) я уже писал (см. здесь). C тех пор в TagHighlight многое изменилось, прежде всего сам движок, но кроме этого было добавлено большое число новых фич. Самая главная фича - это возможность использовать разные подсветки для разных проектов. Однако для ранжира подсветок по проектам я по-прежнему использую скрипт MakeVimHlTags - так уж у меня сложилось - который, естественно, тоже обновился (новая версия доступна для скачивания здесь). В данном посте я хочу продемонстрировать другую возможность нового TagHighlight - подсветку буферов вспомогательного типа (FileType) тегами из некоторого проекта. Такими буферами могут быть, например, поля редактирования при коммите svn (в этом случае буфер имеет тип svn). Чтобы понять, что это такое и для чего это нужно представьте, что вы делаете svn commit, открывается ваш любимый редактор vim, вы вводите сообщение об изменениях, и названия объектов тут же подсвечиваются в соответствии с тегами для данного проекта - на мой взгляд это очень удобно.

Другой вспомогательный тип буфера - и тут я плавно перехожу ко второму герою повествования - это тип tagbar. Плагин tagbar - это современная замена taglist. Когда я решил посмотреть, что он из себя представляет, он сразу стал моим фаворитом. Это действительно лучший вариант taglist. Во-первых, у него нет глюков, связанных с переходом между табами. Во-вторых, он представляет информацию о тегах в более систематизированном виде. В-третьих, имеет очень приличный look-n-feel. Ну и то, что он предоставляет собственный FileType, можно использовать для подсветки TagHighlight!

Кстати, для того, чтобы tagbar не сортировал теги по алфавиту, а показывал их в той последовательности, в которой они объявлены в исходном файле, добавьте в .vimrc строку:
let g:tagbar_sort = 0
Итак, вот картинка (кликните для увеличения):


На этой картинке представлено два обычных буфера - исходники на C++, и буфер tagbar (самое правое окно). Все три буфера расцвечены с помощью TagHighlight. Для создания тегов в MakeVimHlTags использовался пропатченный ctags (см. здесь), который я настоятельно рекомендую, так как за время его использования c августа этого года никаких ошибок я не обнаружил, а вот проблемы с пропуском тегов исчезли.

Теперь о том, как добиться подсветки tagbar c помощью TagHighlight. Я по-прежнему предполагаю (по крайней мере так сделано у меня), что мы используем понятие проектов, которые находятся в директории, на которую ссылается переменная окружения $TAGSDIR (лично я установил ее в файле .bash_profile в значение $HOME/opt/tags), и состоят из файлов с тегами ctags и cscope (для TagHighlight эти файлы не нужны - просто они упоминаются в коде, который я приведу ниже), и файла с подсветкой для TagHighlight, который был создан с помощью MakeVimHlTags и имеет имя типа projectname_c.vim, где projectname - имя конкретного проекта. Итак, вот участок кода, который отвечает за запуск подсветок для проектов (вы можете вставить его в ваш .vimrc, лично я положил его в отдельный файл $HOME/.vim-myprojects.vim, который сорсится в конце .vimrc):

" ---- Setting tags and tag highlights
" ----
fun<SID>SetTags(ctags_db, cscope_db)
    if filereadable(a:ctags_db)
        execute 'set tags+=' . a:ctags_db
    endif
    if filereadable(a:cscope_db)
        execute 'cscope add ' . a:cscope_db
    endif
endfun

fun<SID>SetCTagsHl(ctagshl_db, skipft)
    if !exists('g:loaded_TagHighlight')
        return
    endif
    if index(split(a:skipft), &ft) < 0
        "let g:TagHighlightSettings['DebugLevel'] = 'Information'
        let g:TagHighlightSettings['UserLibraries'= [a:ctagshl_db]
        " add 'FileType' to LanguageDetectionMethods to see highlights when
        " committed to svn (to achieve this, this function must be triggered
        " with BufReadPre event), tagbar is also to be highlighted
        let g:TagHighlightSettings['LanguageDetectionMethods'=
                                                \ ['Extension''FileType']
        let g:TagHighlightSettings['FileTypeLanguageOverrides'=
                                                \ {'svn''c''tagbar''c'}
    endif
endfun

fun<SID>SkipCTagsHlForFt(skipft)
    if !exists('g:loaded_TagHighlight')
        return
    endif
    if index(split(a:skipft), &ft) >= 0
        let g:TagHighlightSettings['UserLibraries'= []
        " trigger FileType event forcibly
        exe "set ft=" . &ft
        "ReadTypes
    endif
endfun

let mpr_def_skipft = 'perl sh'

let mpr_tags =
    \ [
        \ ['*/geant4/pnpi/*''pnpi', mpr_def_skipft],
        \ ['*/geant4/src/geant4*''geant4', mpr_def_skipft],
    \ ]

for item in mpr_tags
    let mpr_tagname = item[1]
    let mpr_hltags  = $TAGSDIR."/".mpr_tagname."_c.vim"
    let mpr_ctags   = $TAGSDIR."/".mpr_tagname.".ctags"
    let mpr_cstags  = $TAGSDIR."/".mpr_tagname.".cscope_out"
    if filereadable(mpr_hltags)
        exe "autocmd BufReadPre,BufNewFile ".item[0].
                    \ " call <SID>SetCTagsHl('".mpr_hltags."', '".item[2]."')"
        exe "autocmd BufRead ".item[0].
                    \ " call <SID>SkipCTagsHlForFt('".item[2]."')"
    endif
    exe "autocmd VimEnter ".item[0].
                \ " silent call <SID>SetTags('".mpr_ctags."', '".mpr_cstags."')"
endfor

Функция <SID>SetTags() к нашему разговору не относится - она нужна для загрузки тех самых ctags и cscope файлов проекта, о которых я упомянул выше, а они отвечают не за подсветку тегов, а за навигацию по ним. Интерес представляют функция <SID>SetCTagsHl(), объекты mpr_def_skipft, mpr_tags и цикл for по mpr_tags. Объект-строка mpr_def_skipft определяет, файлы каких типов (в данном случае perl и sh) не должны подсвечиваться TagHighlight. В объекте-коллекции mpr_tags указан полный список всех проектов, которые могут быть загружены из директории $TAGSDIR. В данном случае я утверждаю, что в директории $TAGSDIR (привязка к которой осуществляется ниже - в цикле for) находятся два проекта с именами pnpi и geant4, рабочие директории проектов (которые указаны в списках autocmd BufReadPre, BufNewFile, BufRead и VimEnter цикла for в качестве первого аргумента - через item[0] - и являются триггером для чтения тегов и запуска подсветки TagHighlight) - это */geant4/pnpi/* и */geant4/src/geant4/* соответственно. В качестве третьего параметра в элементах коллекции mpr_tags указан mpr_def_skipft - он будет использован в цикле for для отключения подсветки TagHighlight для файлов типа perl и sh (путем вызова функции <SID>SkipCTagsHlForFt() из autocmd BufRead).

Вызов подсветки tagbar происходит внутри функции <SID>SetCTagsHl() с помощью всего двух объявлений:
        let g:TagHighlightSettings['LanguageDetectionMethods'=
                                                \ ['Extension''FileType']
        let g:TagHighlightSettings['FileTypeLanguageOverrides'=
                                                \ {'svn''c''tagbar''c'}
В первой строке мы говорим, что будем определять нужна ли подсветка TagHighlight как по расширению читаемого файла, так и по заданному для него типу FileType, который устанавливается в недрах движка vim. Во второй строке мы проассоциировали FileType tagbar с FileType c, и это значит, что в окне tagbar появится подсветка, определенная для C-проектов, а это то, чего мы и добивались. Кроме того, мы проассоциировали FileType svn с FileType c - это нужно для подсветки коммитов svn, о чем я говорил раньше.