суббота, 5 февраля 2011 г.

Подсветка в vim на основе тегов ctags (perl скрипт прилагается)

Из серии было и стало (картинки кликабельны):


Итак, наша задача - обеспечить подсветку в vim не только ключевых слов, но, по возможности, всего исходного кода: собственных функций, классов, пространств имен, переменных и т.д. Эта задача хорошо решается в vim-скрипте ctags_highlighting. Как ясно из названия, этот скрипт использует базу данных ctags (строит сам или берет уже готовую). В состав ctags_highlighting входит питоновский скрипт mktypes.py, который проделывает основную работу, главным результатом которой является vim-скрипт со списком групп подсветки, в случае языков C и C++ этот файл будет называться types_c.vim. Группы подсветки в этом файле не являются стандартными, соответственно используемая цветовая схема должна их поддерживать. Автор ctags_highlighting предлагает собственную цветовую схему bandit. При использовании другой цветовой схемы, ей нужно "сообщить" о новых группах подсветки. В разных схемах это делается по-разному. Я использую цветовую схему xterm16. Для того, чтобы xterm16 знала о новых группах подсветки, я добавил в нее следующие определения:
    "my highlight groups for ctags_highlight
    call s:hi( 'Namespace'   , 'none', 'cyan'      , 'none'      )
    call s:hi( 'Function'    , 'none', 'cyan'      , 'none'      )
    call s:hi( 'DefinedName' , 'none', 'cyan'      , 'none'      )
    call s:hi( 'EnumerationValue'  , 'none', 'cyan'      , 'none'      )
    call s:hi( 'EnumeratorName', 'none', 'cyan'      , 'none'      )
    call s:hi( 'Member'      , 'none', 'cyan'      , 'none'      )
    call s:hi( 'Union'       , 'none', 'cyan'      , 'none'      )
    call s:hi( 'GlobalVariable', 'none', 'cyan'      , 'none'      )
    call s:hi( 'LocalVariable', 'none', 'cyan'      , 'none'      )
    call s:hi( 'GlobalConstant', 'none', 'cyan'      , 'none'      )
    call s:hi( 'Conditional' , 'none', 'cyan'      , 'none'      )
    call s:hi( 'Repeat'      , 'none', 'cyan'      , 'none'      )
    call s:hi( 'Label'       , 'none', 'cyan'      , 'none'      )
    call s:hi( 'Exception'   , 'none', 'cyan'      , 'none'      )
    call s:hi( 'Operator'    , 'none', 'cyan'      , 'none'      )
    call s:hi( 'PreCondit'   , 'none', 'cyan'      , 'none'      )
    call s:hi( 'Include'     , 'none', 'cyan'      , 'none'      )
    call s:hi( 'Macro'       , 'none', 'cyan'      , 'none'      )
    call s:hi( 'StorageClass', 'none', 'green'     , 'none'      )
    call s:hi( 'Class'       , 'none', 'green'      , 'none'      )
    call s:hi( 'Structure'   , 'none', 'green'      , 'none'      )
    "my highlight groups from after/syntax/c.vim
    call s:hi( 'Delimiter'   , 'none', 'green'      , 'none'      )
    call s:hi( 'Boolean'     , 'none', 'green'      , 'none'      )
    "my ColorColumn highlight
    call s:hi( 'ColorColumn' , 'none', 'none'      , 'cyan'      )
Эти объявления следует разместить где-нибудь среди похожих объявлений в ~/.vim/colors/xterm16.vim. Кроме объявлений, связанных с ctags_highlighting, здесь добавлены группы подсветки для скрипта after/syntax/c.vim, а также подсветка для вертикальной колонки, которая появилась с выходом в vim 7.3 и в xterm16 пока отсутствует. Конкретные цвета (cyan, green и т.д.) здесь не важны, так как я переопределяю их в файле .vimrc:
syntax on
let xterm16_brightness = 'high'
let xterm16_colormap = 'soft'
let xterm16fg_Comment = 'grey'
let xterm16fg_Identifier = '#87ffaf'
let xterm16fg_Namespace = '#df875f'
let xterm16fg_Function = '#87ffaf'
let xterm16fg_Statement = '#87afdf'
let xterm16fg_EnumerationValue = '#5fafdf'
let xterm16fg_EnumeratorName = '#00afdf'
let xterm16fg_GlobalConstant = '#87875f'
let xterm16fg_GlobalVariable = '#87875f'
let xterm16fg_DefinedName = 'purple'
let xterm16fg_LocalVariable = '#00df87'
let xterm16fg_Class = '#afff87'
let xterm16fg_Structure = '#dfffaf'
let xterm16fg_Member = '#00af87'
let xterm16fg_Conditional = '#5fafff'
let xterm16fg_Repeat = '#5fafff'
let xterm16fg_Label = '#5fdfff'
let xterm16fg_Exception = '#ff5f87'
let xterm16fg_PreCondit = '#af5faf'
let xterm16fg_Include = '#df5f00'
let xterm16fg_Macro = 'purple'
let xterm16fg_StorageClass = '#5faf5f'
let xterm16fg_Operator = '#ffafaf'
let xterm16fg_Delimiter = '#5fffdf'
let xterm16fg_Boolean = '#87afdf'
let xterm16bg_ColorColumn = '#949494'
if $DISPLAY != '' && !has('gui_running')
    let xterm16bg_Normal = 'none'
endif
colo xterm16
Теперь для обновления подсветки тегов можно воспользоваться командами, предоставляемыми плагином ctags_highlighting. К сожалению, на мой взгляд, это не самое лучшее решение. И причин тому несколько. Прежде всего, я не хочу хранить файлы типа tags и types_c.vim в рабочей директории. Во-вторых, в ctags_highlighting нет выраженной модульности, в частности, если я работаю с некой библиотекой (или несколькими библиотеками), то хочу, чтобы теги используемой библиотекой и моего рабочего кода были четко разделены. Это позволит быстро перестраивать теги для рабочего кода, не изменяя, а только добавляя в файл подсветки теги из библиотеки. В-третьих, ctags часто ошибается, что приводит к появлению псевдотегов типа const, а также переопределению объявлений используемой библиотеки как локальных переменных рабочего кода (если установлено использование локальных переменных).
Представленный здесь скрипт основан на использовании mktypes.py и призван обойти указанные недостатки. Для просмотра опций достаточно набрать имя скрипта MakeVimHlTags без аргументов. В скрипте используется понятие проекта; проекты расположены в директории ~/opt/tags/. В сущности, проект напрямую соответствует модулям в том смысле, о котором говорилось выше. То есть, если в собственном исходном коде используются библиотеки lib1 и lib2, и есть желание подсветить теги из этих библиотек, то можно создать два проекта lib1 и lib2. Для этого следует перейти в каталоги с исходным кодом этих библиотек и выполнить команды:
MakeVimHlTags -r -e const,true,false -p lib1
и
MakeVimHlTags -r -e const,true,false -p lib2
Скорее всего библиотеки находятся в системных директориях и запускать скрипт придется от рута, поскольку mktypes.py создает временные файлы tags и types_c.vim, которые будут удалены по окончании работы скрипта. К счастью, обновлять теги системных библиотек достаточно только при их обновлении. Опция -r означает, что теги будут искаться во всех поддиректориях, начиная с указанных (или как здесь - с текущей). В опции -e перечислены через запятую теги, которые следует игнорировать. В моем случае ctags пытался переопределить слова const, false и true, поэтому я указал, что их следует игнорировать. Опция -e поддерживает простейшие шаблоны, в которых символ "точка" представляет собой последовательность любых символов кроме пробельных; точка может находиться в начале или конце шаблона. Например шаблон "Lib1." обозначает все теги, которые начинаются с "Lib1". Опция -p указывает название проекта.
Теперь следует создать теги для собственного исходного кода. Пусть наш проект называется Proj и использует библиотеки lib1 и lib2. Для того, чтобы добавить в проект теги из другого проекта используется опция -a:
MakeVimHlTags -r -l -a lib1,lib2 -e Lib1.,Lib2. -p Proj
Опция -l нужна для того, чтобы ctags сгенерировал теги для локальных переменных. Это хорошая идея для собственного кода, но плохая - для кода библиотек, поэтому мы не использовали ее для построения тегов проектов lib1 и lib2. В принципе, в случае с C++, теги для локальных переменных не всегда хороши (в частности, если в коде часто используется инициализация в стиле конструктора), поэтому использовать опцию -l следует с осторожностью. С помощью опции -e мы удаляем все теги, которые начинаются с "Lib1" и "Lib2": поскольку в нашем проекте используются имена из lib1 и lib2, то, особенно если включена опция -l, возможны ложные срабатывания ctags для таких имен.
После выполнения трех команд в директории ~/opt/tags/ появятся 3 новых файла: lib1_c.vim, lib2_c.vim и Proj_c.vim. Эти файлы содержат команды для vim, которые необходимы для подсветки тегов, при этом файл Proj_c.vim будет содержать теги из lib1_c.vim и lib2_c.vim.
Осталось самое простое: сделать так, чтобы при открытии файла из рабочей директории проекта Proj скрипт Proj_c.vim выполнялся автоматически. Для этого в файле .vimrc можно ввести следующую команду:
if (filereadable($HOME . "/opt/tags/Proj_c.vim"))
    autocmd BufRead,BufNewFile */part/of/path/to/Proj/* 
        \ execute 'source' . $HOME . "/opt/tags/Proj_c.vim"
endif
где /part/of/path/to/Proj/ - часть относительного пути к рабочей директории проекта Proj.
Для обновления тегов подсветки достаточно выполнить последнюю приведенную команду MakeVimHlTags, при этом теги проектов lib1 и lib2 не перестраиваются, а просто включаются из готовых файлов.

Скрипт MakeVimHlTags можно взять здесь.