Навигация в крупных проектах для Vim и Emacs

    Из публикаций о 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



    Приятного скакания по файлам.

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

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

    Похожие публикации

    Комментарии 19
      0
      Спасибо за статью.

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

      А как с этим у Global? Пытается ли там индекс учитывать то, откуда какие определение видны?
        0
        Не знаю, но я столкнулся с этой проблемой в PHP — написал свой кастомный генератор тэгов на базе ctags, чтобы он к имени тэга добавлял пространство имен в котором тэг расположен. habrahabr.ru/post/182252
          0
          Насколько я знаю, ни та, ни другая этим не занимается, оставляя ответственность на редакторе. Так, расширенный ctags формат позволяет хранить инфу об отношениях, на пример принадлежность метода определенному классу. Редактору остается разобраться в этих отношениях и отфильтровать ненужные результаты. К сожалению, сделать это очень не просто, так как сам редактор не знает, к кому относится метод под указателем. Замкнутый круг.
            +3
            Почему в статье ничего не сказано про cscope? Он как раз решает именно эту проблему:

            image
              0
              А как у нее с языками отличными от С?
                0
                частично поддежривается C++ и Java
                  0
                  Ну вот я тоже не нашел широкой поддержки языков, поэтому решил не писать. А ctags и gnu global универсальные вещи. Кстати вы пробовали helm-cscope?
                0
                Спасибо что напомнили, я кажется видел что csope можно использовать в качестве бэкенда для global.
              +1
              vim + Plugin 'FuzzyFinder'
                0
                В емаксе есть ещё helm и projectile, которые дружат с CTAGS.
                  0
                  Да по helm вообще нужно делать нормальную статью со скриншотами.
                  0
                  Как мне кажется, будущее сей области за server side code model. Если кто-то смотрел сорцы QtCreator — он меня поймёт.

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

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

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


                      Поиск в проекте по слову под курсором


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

                      Тут можно почитать подробнее github.com/junegunn/fzf/wiki/Examples-(vim)
                        0
                        Обычно хватает ctags, только надо вешать хуки на гит, иначе теги устаревают.
                        Спасибо за global, буду пробовать.
                          0
                          Скажите, а как можно настроить индексацию с помощью ctags по emacs? Для моего проекта файл TAGS получается порядка 150Мб, но туда попадает всё — сжатые js файлы того же jquery, кеши symfony, библиотеки, установленные с помощью composer-а и т.п.
                          В документации есть что-то насчёт projectile-globally-ignored-files/projectile-globally-ignored-directories в .dir-locals.el, но, кажется, они никакого влияния на индексацию не оказывают.
                            0
                            Исключать файлы по шаблону можно при помощи опции --exclude. Но как передать ее в projectile не скажу, сам использую Makefile в корне проекта, где есть цель для генерации тэгов.
                              0
                              projectile-ignored-directories — правильная переменная, но убедитесь, что она правда выставляется через .dir-locals.el, и что она имеет верный формат, и что директории, которые вы туда засовываете имеют верный *относительный* путь относительно корня проекта.
                              0
                              projectile-ignored-directories — такого не нашел.
                              А вот так получилось
                              ((nil . ((projectile-globally-ignored-directories . ("app/cache" ".idea" "vendor" "bin" ".git")))))
                              

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

                              Самое читаемое