Из публикаций о Vim и Emacs складывается впечатление, что мало кто использует в них человеческую навигацию по коду. Или же используют вместо нее инструменты вроде ack и ag. А между тем для навигации в этих редакторах есть эффективные инструменты, которые существуют уже не одно десятилетие. Эти инструменты Ctags и Gnu global — они представлены во всех основных серверных Linux-дистрибутивах, так что навигация будет работать даже если разработка ведется на удаленном сервере.

Я буду писать большей частью про Emacs, потому что пользуюсь им, но буду давать ссылки на соответствующие плагины для Vim. Главное донести принцип работы и основные возможности инструментов и отговорить вас от неумеренного использования ack, ag и grep.


Ctags и Gnu global работают примерно одинаково — вы скармливаете им папку с исходниками проекта, и в ней появляются файлы индексов под названием tags, TAGS, GTAGS. В этих файлах хранятся все найденные в проекте символы: классы, методы, функции, переменные и т.п. и их позиции в файле исходного кода. Т.е. компактная информация достаточная для того, чтобы ввести в редакторе имя класса и перескочить в место его объявления. Или получить список файлов, где объявлен класс с таким именем.

Ctags


Ctags — это утилита, которая каждый раз сканирует все файлы проекты, не разбирая менялись они или нет. Для проектов в несколько тысяч файлов работает довольно шустро — до 10 секунд. Можно создавать отдельные файлы индексов для библиотек и для проекта, или для библиотек, бэкенда и фронтенда, и обновлять только тот индекс, который нужно.

Emacs использует файлы индексов Ctags одного формата, а вот Vim и Sublime Text Editor — другой. Поэтому если вы будете генерировать индексы для последующего использования в Emacs, то не забудьте добавить опцию -e.

Эта утилита довольно старая, и бывает двух… нет даже трех видов. Они мало различаются по сути, но сильно различаются по качеству вычленения символов. Самый примитивный из них — ctags идущий в комплекте с Emacs, так называемый etags. Его стоит использовать если больше ничего нет — поддерживает много языков, но мне понравилась только поддержка perl, C, Java. Команда называется etags или ctags.

Второй вариант — это exuberrant ctags. Та версия, которая поддерживается вашим дистрибутивом, и будет поддерживать еще пару лет, устаревшая. Но она гораздо лучше поддерживает парсинг PHP и JS. Команда называется ctags-exuberant.

Самый совершенный вариант — universal ctags. Данная версия поддерживает внешние парсеры типа coffeetags и вообще имеет самые лучшие парсеры для других языков. Желательно использовать именно эту утилиту, пусть и собирать ее придется вручную.

Итак вы определились какую версию будете использовать, а значит можете перейти в папку с проектом и запустить в терминале: ctags -e — если вы используете Emacs, или же просто ctags, если речь идет о другом редакторе.

Использование в Emacs

M-x visit-tags-table и выбираете файл TAGS. Дальше наводите курсор на имя метода и нажимаете M-., затем Enter. Если понимаете, что вы перескочили не на тот символ, то нажимаете C-u M-. — таким образом вы перейдете к следующему определению этого символа, но уже в другом файле. Советую установить helm для удобного выбора из списка тэгов, правда удобным он станет только после того как вы привыкнете к helm во всех остальных местах Emacs...

Если вы пользуетесь Emacs и пишите на PHP, то загляните сюда.

Использование в Vim



Gnu Global


Gnu Global был создан для работы с кодовыми базами проектов размером в несколько гигабайт в то время, когда IDE требующих нескольких гигабайт для работы еще не изобрели. Он работает примерно как и ctags, более того, он даже может использовать ctags в качестве бэкенда для парсинга — это очень удобно, потому что нативным образом Gnu Global поддерживает не так много языков. Однако есть одно важное отличие — Gnu Global не предполагает, что ваш редактор будет загружать файл индексов, а использует для поиска символов специальную утилиту global, которую редактор вызывает при поиске символа.

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

Для создания файла индексов перейдите в папку проекта и вызовите gtags. Ждите… Но вообще скорость хорошая — исходники JIdea Community Edition, а это порядка 150 мб чистого Java-кода, индексируются меньше чем за минуту. Последующие индексирования следует запускать при помощи gtags -i, чтобы индексировались только изменившиеся файлы.

Использование в Emacs

Лучше всего установить плагин ggtags, взять его можно здесь или в elpa, melpa или marmalade. Потом откройте один из файлов проекта и нажмите M-x ggtags-mode, затем M-x ggtags-visit-project-root и выберите папку где тэги лежат. Все, дальше в рамках файлов проекта если вы нажмете M-., то сработает переход к определению символа, который в данный момент под курсором. Хотите ввести имя символа вручную — C-u M-.. Если у символа больше одного определения, то нажимайте M-n и M-p для переключения между определениями. Чтобы обновить файл тэгов нажмите M-x ggtags-update-tags находясь на одном из файлов проекта.

Несмотря на то что ggtags и progectile оба покушаются на работу с проектами и индексацию оных, они не конфликтуют.

Использование в Vim



Приятного скакания по файлам.
Что вы используете чаще всего для навигации по символам?

Проголосовало 166 человек. Воздержалось 99 человек.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Комментарии (19)


  1. CONSTantius
    28.08.2015 14:50

    Спасибо за статью.

    С разными тегами самая назойливая для меня проблема — это когда один и тот же символ определён в очень большом числе мест. Поскольку я занимаюсь системным программированием на Си, и часто работаю с кастомными тулчейнами, у нас много где определены всякие вещи типа memcpy. exuberant-ctags в таком случае предлагает просто первое попавшееся определение и перебирать их (даже пусть с помощью helm) не хочется.

    А как с этим у Global? Пытается ли там индекс учитывать то, откуда какие определение видны?


    1. PerlPower
      28.08.2015 14:53

      Не знаю, но я столкнулся с этой проблемой в PHP — написал свой кастомный генератор тэгов на базе ctags, чтобы он к имени тэга добавлял пространство имен в котором тэг расположен. habrahabr.ru/post/182252


    1. Delphinum
      28.08.2015 14:54

      Насколько я знаю, ни та, ни другая этим не занимается, оставляя ответственность на редакторе. Так, расширенный ctags формат позволяет хранить инфу об отношениях, на пример принадлежность метода определенному классу. Редактору остается разобраться в этих отношениях и отфильтровать ненужные результаты. К сожалению, сделать это очень не просто, так как сам редактор не знает, к кому относится метод под указателем. Замкнутый круг.


    1. lorc
      28.08.2015 15:14
      +3

      Почему в статье ничего не сказано про cscope? Он как раз решает именно эту проблему:

      image


      1. PerlPower
        28.08.2015 16:51

        А как у нее с языками отличными от С?


        1. lorc
          28.08.2015 19:43

          частично поддежривается C++ и Java


          1. PerlPower
            29.08.2015 10:37

            Ну вот я тоже не нашел широкой поддержки языков, поэтому решил не писать. А ctags и gnu global универсальные вещи. Кстати вы пробовали helm-cscope?


      1. CONSTantius
        29.08.2015 14:30

        Спасибо что напомнили, я кажется видел что csope можно использовать в качестве бэкенда для global.


  1. monax
    28.08.2015 16:00
    +1

    vim + Plugin 'FuzzyFinder'


  1. bromzh
    28.08.2015 16:53

    В емаксе есть ещё helm и projectile, которые дружат с CTAGS.


    1. PerlPower
      28.08.2015 17:01

      Да по helm вообще нужно делать нормальную статью со скриншотами.


  1. maydjin
    28.08.2015 20:33

    Как мне кажется, будущее сей области за server side code model. Если кто-то смотрел сорцы QtCreator — он меня поймёт.

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

    Один из пророков сего подхода — rtags.



  1. ali_aliev
    29.08.2015 04:53
    +1

    Для навигации по файлу в vim-е использую ctags + fzf + ag. Без дополнительных плагинов.

    Вот как это выглядит у меня


  1. igrishaev
    29.08.2015 20:29

    Обычно хватает ctags, только надо вешать хуки на гит, иначе теги устаревают.
    Спасибо за global, буду пробовать.


  1. n0dwis
    01.09.2015 20:57

    Скажите, а как можно настроить индексацию с помощью ctags по emacs? Для моего проекта файл TAGS получается порядка 150Мб, но туда попадает всё — сжатые js файлы того же jquery, кеши symfony, библиотеки, установленные с помощью composer-а и т.п.
    В документации есть что-то насчёт projectile-globally-ignored-files/projectile-globally-ignored-directories в .dir-locals.el, но, кажется, они никакого влияния на индексацию не оказывают.


    1. PerlPower
      01.09.2015 21:36

      Исключать файлы по шаблону можно при помощи опции --exclude. Но как передать ее в projectile не скажу, сам использую Makefile в корне проекта, где есть цель для генерации тэгов.


    1. PerlPower
      01.09.2015 21:47

      projectile-ignored-directories — правильная переменная, но убедитесь, что она правда выставляется через .dir-locals.el, и что она имеет верный формат, и что директории, которые вы туда засовываете имеют верный *относительный* путь относительно корня проекта.


  1. n0dwis
    02.09.2015 14:41

    projectile-ignored-directories — такого не нашел.
    А вот так получилось

    ((nil . ((projectile-globally-ignored-directories . ("app/cache" ".idea" "vendor" "bin" ".git")))))