Pull to refresh

Автоматическая индексация файлов проекта

Reading time9 min
Views13K
Несмотря на то, что статей по теме «использование Vim в качестве IDE» достаточно много, результат меня не удовлетворял. Мне очень не хватало автоматической, прозрачной для пользователя (то есть меня) генерации тегов для всех файлов в проекте, причем файлы проекта совсем необязательно должны лежать в одной директории и ее поддиректориях, а формировать список файлов должно быть легко и приятно.

Кроме того, я должен иметь возможность легко переносить проект с одного компьютера на другой и не должен быть привязан к конкретной директории проекта.

Долгие поиски плагинов с необходимым функционалом не увенчались успехом, поэтому было принято решение написать такой плагин. Я назвал его indexer.


UPD: на данный момент статья слегка устарела. Я обновлю ее, когда у меня будет время на это. Однако, основное работает, и обратная совместимость обеспечена, но для более полного использования возможностей я рекомендую вам прочитать :help indexer-syn-change-4.10.

Архив с плагином содержит следующие файлы и папки:

plugin/indexer.vim
doc/indexer.txt

Для работы необходим ctags, но лучше пропатченый ctags, т.к. в последней версии 5.8 (на момент написания этих строк) есть очень неприятный баг. Прочитать подробнее о баге и скачать патч можно здесь.

Прежде всего следует сказать, что есть два варианта использования плагина: как дополнение к замечательному плагину project или отдельно от него.

Пара слов о плагине project: данный плагин позволяет удобно управлять проектами и списком файлов в них. Визуально это окно с древовидной структурой проектов, подпроектов и файлов. Проекты можно сворачивать. Можно делать grep или vimgrep по всем файлам проекта, и потом просматривать результаты в quickfix. Удобно замапить какую-нибудь клавишу (например, F9) на показать/скрыть окно с проектами.
Подробное описание этого плагина выходит за рамки данной статьи, к тому же запрос «vim project» в Гугле сразу выдает множество ссылок. На вскидку — вот. Ну и, разумеется, :help project после установки плагина.

Вариант 1. Использование как дополнение к плагину project


Итак, рассмотрим самый простой вариант: вы уже используете плагин project и используете файл с проектами по умолчанию ~/.vimprojects. Если так, то настройка плагина indexer будет очень проста: вы просто распаковываете содержимое архива в $HOME/.vim (или аналогичную директорию), закрываете Vim (если был открыт) и запускаете его из любой директории любого проекта, имеющегося в ~/.vimprojects. Можно просто открыть любой файл, входящий в состав проекта.

Indexer определит, что был открыт файл, входящий в состав такого-то проекта, проиндексирует все файлы, входящие в состав этого проекта и установит соответствующие значения в переменные Vim &tags и &path.

Теги будут автоматически обновляться при сохранении любого файла, входящего в состав проекта.
Теперь вы сможете переместить курсор на название какой-либо функции, переменной и т.д., и, нажав g] или Ctrl-] (есть небольшие отличия), переместиться на объявление этой функции. Ctrl-t вернет вас обратно. А если вы занимаетесь разработкой на C/C++, то можете подключить плагины omnicppcomplete и code_complete, и тогда, набрав «имя_структуры.» или «имя_структуры->», увидете popup-меню с выбором элементов этой структуры, а набрав «имя_функции(» и нажав Tab, получите автоподстановку всех параметров, которая принимает эта функция. Но это я немного отвлекся.

Выглядит все это примерно так:

Если вы используете плагин project, но файл проектов у вас другой, то его можно указать в вашем _vimrc:
let g:indexer_projectsSettingsFilename = /path/to/my/.vimprojects
Я приведу полный список опций чуть позже.

Как видно, работать со связкой этих плагинов довольно удобно: project позволяет управлять проектами, а indexer заботится об обновлении тегов.

Если же вы по каким-то причинам не хотите использовать project, то

Вариант 2. Использование без плагина project


Вам нужно будет в одном файле описать, какие файлы в состав каких проектов входят. По умолчанию это файл ~/.indexer_files. Синтаксис этого файла достаточно прост. Пожалуй, лучший способ объяснить — показать пример:

[CoolProject]

/home/user/myproject/src/*.c
/home/user/myproject/src/*.h
/home/user/myproject/inc/*.h

[AnotherProject]

$HOME/myproject2**/*.c
$HOME/myproject2**/*.h


как нетрудно догадаться, здесь два проекта: CoolProject и AnotherProject.
В состав проекта CoolProject входят все *.c файлы из директории /home/user/myproject/src, и все заголовочные файлы из /home/user/myproject/inc и /home/user/myproject/src.
А в AnotherProject входят файлы *.c и *.h из директории ~/myproject2 и всех поддиректорий рекурсивно (на рекурсивность указывает "**").
Ну и, как видно, в путях к файлам можно использовать переменные Vim, такие как $HOME, $VIM, и другие, в том числе объявленные пользователем.

UPD: В версии 1.5 по просьбе пользователя torkve добавлена возможность задать в этом файле директорию, содержащую много проектов. Допустим, у вас есть директория ~/workspace, в которой лежит куча проектов, причем список проектов часто меняется. Каждый раз редактировать файл .indexer_files будет лениво. Тогда вы можете указать что-то вроде этого:

[PROJECTS_PARENT filter="*.c *.h *.cpp"]
~/workspace

Ключевое слово — PROJECTS_PARENT. Indexer будет считать каждую папку в ~/workspace отдельным проектом, включающим в себя файлы перечисленных типов.

Во всем остальном indexer работает точно так же, как и вместе с project: если запустить Vim из директории проекта, то будут сгенерированы теги для всех файлов из этого проекта, и при сохранении любого файла из этого проекта теги будут обновлены.

Все, теперь я могу просто создавать проекты, добавлять туда файлы и забыть о том, как они индексируются. Ура!

Но этого мне оказалось мало.
Дело в том, что мне не нравится, когда файлы проекта лежат в разных местах (за исключением библиотек, которые будут общими для нескольких проектов. По сути, библиотеки — отдельные проекты). В рассмотренных нами случаях проект лежит в одной папке, а файл с описанием проекта (.vimprojects или .indexer_files) — в другой (в домашней).

То есть мне хотелось, чтобы для каждого проекта был свой файл .vimprojects, который лежал бы в папке самого проекта и в котором, собственно, было бы описание только этого проекта. Получается, в зависимости от проекта нужно подгружать специфические для этого проекта настройки.

Я решил эту проблему следующим образом: в корневой папке проекта я создаю папку ".vimprj" (название можно поменять), в которую помещаю файлы со специфическими для проекта настройками. В ней же лежит .vimprojects. Когда Vim запускается, indexer прежде всего будет искать эту папку ".vimprj" в текущей директории, если ее нет — на уровень выше, и так далее. Глубину рекурсии можно менять, по умолчанию это 10.
Если папка ".vimprj" будет найдена, то indexer объявит глобальную переменную $INDEXER_PROJECT_ROOT, в которой, как нетрудно догадаться, будет указан путь к корневой папке проекта. Не к папке ".vimprj", а к папке, в которой лежит ".vimprj". Также будут запущены все файлы *.vim из этой директории, в которых и можно задать специфические настройки. Вот пример такого файла sett.vim:

" определяем путь к папке .vimprj
let s:sPath = expand('<sfile>:p:h')

" указываем плагину indexer файл проекта, который лежит в папке .vimprj
let g:indexer_projectsSettingsFilename = s:sPath.'/.vimprojects'

" указываем плагину project файл проекта, который лежит в папке .vimprj
" ВНИМАНИЕ: этой переменной нет в стандартном project.
" Вам нужно немного изменить файл project.vim, чтобы это сработало
" об этом чуть ниже
let g:proj_project_filename=s:sPath.'/.vimprojects'

" можно еще, например, указать специфический для этого проекта компилятор
let &makeprg = 'pic30-gcc -mcpu=24HJ128GP504 -x c -c "%:p" -o"%:t:r.o" -I"'.$INDEXER_PROJECT_ROOT.'\src\utils" -I"'.$INDEXER_PROJECT_ROOT.'\src\app" -g -Wall -mlarge-code -mlarge-data -O1'


Также в файле проекта .vimprojects я использую не абсолютный путь к проекту, а подставляю переменную $INDEXER_PROJECT_ROOT. Теперь я могу перемещать папку проекта куда захочу, в любом случае все пути будут корректными и теги будут генерироваться. Это то, чего я и хотел добиться.

Теперь немного деталей.

О том, как плагину project указать файл проектов, отличный от ~/.vimprojects.

Единственная возможность, предусмотренная Aric Blumer, создателем плагина — это при каждом старте Vim давать команду ":Project /path/to/my/.vimprojects". Конечно, можно ее замапить на какую-нибудь клавишу, но все равно это «костыль», причем неудобный: можно просто забыть нажать эту клавишу. Можно запускать vim с дополнительным ключом +«Project /path/to/my/.vimprojects», но и это мне не нравится. Я переписывался с создателем плагина, но он сказал мне, что я единственный, кому это понадобилось, и менять он ничего не будет. Так что я могу только рассказать вам, как нужно изменить project.vim, чтобы можно было указать переменную с файлом проекта. Нужно будет вместо одной строки вставить 5 строк, только и всего.

Изложенное точно верно для версии project 1.4.1 (последняя версия на момент написания данной статьи).
Перейдите на строку 1272. В ней должна быть только одна команда:

Project

Необходимо заменить эту строку на следующий блок кода:

if !exists("g:proj_running") && exists('g:proj_project_filename')
   exec('Project '.g:proj_project_filename)
else
   Project
endif


Все, теперь, если указать переменную g:proj_project_filename, то ее значение будет использовано вместо ~/.vimprojects

UPD: В Indexer версии 3.0 появилось две очень важных для меня «фичи»:
1. Теги генерируются в фоновом режиме. Это означает, что независимо от размера вашего проекта, вам не нужно будет ждать, пока весь проект проиндексируется. Есть одно НО: это работает только в том случае, если servername не пустой ( :help servername ). Когда вы запускаете gvim (а не vim), то он сам устанавливает дефолтный servername «GVIM», то есть в gvim фоновая генерация тегов будет работать «из коробки». А если вы используете консольный vim и хотите, чтобы теги генерировались в фоне, то вам нужно его запускать чем-то вроде этого:
$ vim --servername MY_SERVER

2. Можно в одном сеансе vim открывать файлы из разных проектов, и теги будут сгенерированы корректно для всех нужных проектов. Теги из разных проектов не перемешаются. Это чертовски удобно, когда нужно работать с несколькими проектами одновременно. (например, проекты нескольких сущностей, взаимодействующих между собой)

Опции плагина indexer

g:indexer_lookForProjectDir (по умолчанию: 1)
Если 1, то плагин будет искать папку ".vimprj".

g:indexer_dirNameForSearch (по умолчанию: ".vimprj")
Имя директории с настройками проекта

g:indexer_recurseUpCount (по умолчанию: 10)
Глубина рекурсии для поиска этой папки

g:indexer_projectsSettingsFilename (по умолчанию: "~/.vimprojects")
Путь и имя файла проектов (файл плагина project)

g:indexer_indexerListFilename (по умолчанию: "~/.indexer_files")
Путь и имя файла проектов (собственный формат плагина indexer).
Если существуют оба файла (и ".indexer_files", и ".vimprojects"), то используется только .indexer_files. Другой файл будет проигнорирован. Чтобы узнать, какой файл используется в данный момент, вы можете использовать комаду :IndexerInfo. Подробнее о командах плагина indexer я расскажу чуть ниже.

g:indexer_projectName (по умолчанию: "")
Название проекта для чтения из файла проектов. Если название проекта не указано, то будет использован любой проект.

g:indexer_enableWhenProjectDirFound (по умолчанию: 1)
Если 1, то indexer будет индексировать файлы проекта если Vim был запущен из любой директории, в которой лежат файлы проекта.
Если 0, то файлы будут проиндексированы только в том случае, если был открыт файл, входящий в состав проекта.

g:indexer_tagsDirname (по умолчанию: "")
Директория для сохранения тегов.

Если эта опция пустая (по умолчанию), то имя папки с тегами выбирается следующим образом:
Допустим, вы используете файл ~/.indexer_files. Тогда теги будут сохранены в папку ~/.indexer_files_tags/project_name, где project_name — название проекта.
Если вы используете файл ~/.vimprojects. Тогда теги будут сохранены в папку ~/.vimprojects_tags/project_name, где project_name — название проекта.

Если же эта опция не пустая, то все файлы тегов сохраняются в указанной папке.
Я рекомендую оставить эту опцию пустой.

g:indexer_ctagsCommandLineOptions (по умолчанию: "--c++-kinds=+p+l --fields=+iaS --extra=+q")
Опции, с которыми будет запущен ctags.

g:indexer_ctagsJustAppendTagsAtFileSave (по умолчанию: для Linux: 1, для Windows: 0)
Если 1, то при сохранении любого файла из проекта ctags будет запущен с ключом "-a" (append) только для этого файла.
(Тут еще нужно обратить внимание на опцию g:indexer_useSedWhenAppend: когда она установлена, то перед обновлением тегов старые теги из этого файла будут удалены с помощью утилиты Sed)

Если 0, то при сохранении любого файла из проекта теги будут полностью обновлены.

Тут есть некоторые сложности. Все версии Sed для Windows, которые мне удалось найти, работают криво. Рассказывать об их глюках здесь не буду, скажу только, что заставить их работать корректно мне так и не удалось. А в Linux работает хорошо. Поэтому пришлось пойти на компромисс: в Linux по умолчанию теги обновляются только для сохраненного файла, и Sed используется для того, чтобы убрать «мусор» из файла тегов; а в Windows при каждом сохранении просто обновляются целиком все теги. На самом деле, с появлением в Indexer 3.0 фоновой генерации тегов, это уже не проблема.

Команды плагина indexer

:IndexerInfo
Выводит информацию о текущем состоянии плагина: какой файл проектов используется, сколько файлов проиндексированы, сколько и какие файлы не были найдены, и др.

Если плагин почему-то работает не так, как вы ожидаете, то первое, что нужно сделать — дать эту команду.

:IndexerRebuild
Обновляет теги для всех файлов, входящих в состав проекта.

Ну и напоследок, ссылки

Страница плагина indexer: http://www.vim.org/scripts/script.php?script_id=3221
Страница плагина project: http://www.vim.org/scripts/script.php?script_id=69
Пропатченый ctags: http://dfrank.ru/ctags581

Спасибо за внимание.
Tags:
Hubs:
Total votes 59: ↑50 and ↓9+41
Comments73

Articles