воскресенье, 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.