Pull to refresh

Об удобной навигации и отладке C++ кода в Vim

Reading time 7 min
Views 41K
Компания, где я работаю, разрабатывает программное обеспечение на C++ под Linux. Долгое время мы использовали Qt Creator, с редкими ребятами работающими из Emacs и Vim. Когда я сам попытался пересесть на Vim, я понял, что ситуация с плагинами для разработки на С++ очень не простая. Поработав немного с CTags, я быстро понял, что без напильника работать в Vim будет очень сложно.
К сожалению, с ростом опыта работы с Vim редактор в Qt Creator в режиме эмуляции устраивал меня все меньше, и в какой-то момент я решил потратить немного времени и разобраться, как же сделать из Vim нормальную среду.
Я очертил для себя четыре вещи, которые я бы хотел от среды разработки, и которых мне бы хватило в Vim, чтобы полностью на него перейти:

1. Автодополнение
2. Навигация по коду
3. Отладка прямо из среды
4. Интеграция с Git (в частности Blame прямо в редакторе, и Git Grep)

Автодополнение в Vim — это решенная проблема, и название у решения YouCompleteMe. Это очень качественный плагин, который реализует автодополнение для большого количества языков программирования, в частности Python и C++. Ходят слухи, что внутри Google YouCompleteMe решает и вторую проблему с навигацией кода, но использует для этого внутренные инструменты гугла для индексирования.

Интеграция с Git в какой-то степени решена с помощью vim-fugitive. Это не такая комплексная интеграция, как бывает у Jet Brains, или в Visual Studio, но сравнимая с тем, что предлагает Qt Creator. Те два сценария, которые нужны были мне: blame и grep — работают хорошо.

Отладка и навигация были проблемами, решенными гораздо хуже. В этой статье я расскажу о плагине, который мы написали для навигации по С++ коду. В конце статьи я также расскажу о том, как мы для себя решили проблему с интегрированным отладчиком.

Конечно, как я упомянул выше, для навигации можно было использовать CTags, но если вы работали с CTags, то вы знаете, что он не решает проблему. Парсер C++ очень слабый, и часто CTags находит совершенно неправильные участки кода.

В интернете как несколько лет назад, так и сейчас, единственное, что находится на тему навигации по C++ коду — это вот эта статья. Я настроил этот плагин, и он в принципе работал. Но было много нареканий, из которых два самых больших:
1. Процесс индексирования надо запускать руками
2. Нельзя найти символ по его названию

В конечном итоге, это все закончилось разработкой собственного плагина для Vim, который позволяет перемещаться по C++ коду. Он требует наличия Compilation Database. Если в проекте используется CMake, то чтобы его получить достаточно вызвать

cmake . -DCMAKE_EXPORT_COMPILE_COMMANDS=ON


Иначе можно либо воспользоваться Bear, либо построить его руками, формат очень простой.

Плагин называется CtrlK, и позволяет делать следующие вещи:

1. Перейти к определению символа под курсором.
2. Поиск всех вхождений символа под курсором.
3. Поиска символа по его названию (для этого используется FuzzyFinder, но поиск, к сожалению, не Fuzzy, а только по подстроке). Поиск работает для функций, методов, классов, глобальных переменных, макросов, и даже аргументов функций и локальных переменных.
4. Отображение названия функции, внутри которой находится курсор, в строке состояния. Очень удобно при редактировании больших функций, когда ее объявление находится за пределами экрана.

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

Плагин использует libclang для парсинга кода и leveldb для индекса.

Как и YouCompleteMe, плагин состоит из серверной части и клиента. Здесь под серверной частью подразумевается некоторый процесс, который запущен параллельно с Vim, и который общается с Vim. Для установки мы пошли немного другим путем, чем YCM. Если YouCompleteMe просит запустить руками Make после установки через Vundle, мы вместо этого сделали установку серверной части через pip.
Сначала казалось, что можно написать весь плагин на Python, и не делать серверной части, но скорость парсинга большого количества файлов через clang библиотеку для python очень низкая, и пришлось ее писать на C++, что требует выноса этой функциональности в запущенный отдельно процесс, и немного осложняет установку.

В итоге, установка сводится к установке питоновских библиотек clang, leveldb и серверной части ctrlk:

  sudo pip install leveldb
  sudo pip install clang
  sudo pip install ctrlk


(Если sudo pip install ctrlk не сработал)
Исторически сложилось так, что pip package для ctrlk принадлежит не мне. В текущем package есть две недоработки — не указана зависимость от ez_setup, ее можно разрешить руками

  sudo pip install ez_setup


И отсутствуют заголовочные файлы и библиотеки clang. Это с большим шансом приводит к ошибке компиляции, потому что стандартная установка clang/llvm не помещает их туда, где компилятор нашел бы их без подсказки.
Обе эти проблемы решены в репозитории, но пока pip package не обновлен. Если sudo pip install ctrlk упал с ошибкой компиляции, то можно просто установить его руками

  git clone https://github.com/SkidanovAlex/py-ctrlk.git
  cd py-ctrlk
  python setup.py build
  sudo python setup.py install



Установке плагина (а также L9 и FuzzyFinder, от которых CtrlK зависит). Для этого проще всего использовать Vundle:

  Plugin 'L9'
  Plugin 'FuzzyFinder'
  Plugin 'SkidanovAlex/CtrlK'


И небольшим настройкам в vimrc

  let g:ctrlk_clang_library_path="/home/user/llvm/lib"
  nmap <F3> :call GetCtrlKState()<CR>
  nmap <C-k> :call CtrlKNavigateSymbols()<CR>
  nmap <F2> :call CtrlKGoToDefinition()<CR>
  nmap <F12> :call CtrlKGetReferences()<CR>


Где g_ctrlk_clang_library_path должен указывать на путь к libclang.so. Его можно найти, например, запустив

locate libclang.so


После этого vim, запущенный из директории с compilation database (или любой ее поддиректории) начнет индексировать файлы, и через некоторое время по ним можно начать навигацию. F3 покажет текущий статус парсера, Ctrl+K открывает FuzzyFinder, в котором можно искать символы, F2 перепрыгивает на определение символа, и F12 показывает все места в коде, где символ под курсором используется.

Если хочется, чтобы строка состояния показывала название текущей функции, то можно ее настроить, например, так:

  hi User1 ctermbg=darkgreen ctermfg=black guibg=darkgreen guifg=black
  hi User2 ctermbg=gray ctermfg=black guibg=gray  guifg=black
  hi User3 ctermbg=darkgray ctermfg=gray  guibg=darkgray  guifg=gray

  set statusline=%1*\ %{CtrlKGetCurrentScope()}\ %2*\ %F%m%r%h\ %w\ \ %3*\ %r%{getcwd()}%h%=%l:%c
  set laststatus=2


Вот два примера работы. В первом примере мы запускаем Vim из папки с проектом, нажимаем Ctrl+K, и переходим к файлу. Это не очень удачный пример, потому что к файлу перейти можно и просто используя Fuzzy Finder или CtrlP, но в CtrlK таким же образом можно перейти и к любом классу или другому символу.



Во втором примере мы опять использум Ctrl+K чтобы найти функцию RestoreStartWorker, в ней позиционируем курсор на классе CodePrinter, и находим все упоминания этого класса в коде, нажав F12. Если бы мы нажали F2, то Vim бы сразу прыгнул на определение класса.



В этом примере есть небольшая задержка при поиске всех вхождений. На самом деле, она работает моментально, но после первого открытия любого файла проходит 1-3 секунды на его парсинг, и до того, как парсинг закончится, переход на определение и поиск вхождений не работают.

В завершении, немного про отладку. Для отладки есть несколько плагинов, и они все очень плохие. Я потратил несколько дней, и все-таки сумел установить PyClewn и заставить его работать. PyClewn позволяет устанавливать точки останова, смотреть на локальные переменные, наблюдать за выводом программы прямо из Vim, и позволяет вводить команды для GDB. Это значительно менее удобно, чем интеграция у QtCreator, например, или у любой другой полноценной среды разработки, но немного лучше, чем запускать GDB в отдельном терминале. PyClewn в целом решал проблему, но хотелось большего.
Мой коллега написал плагин для Vim, который использует TMux, чтобы запустить GDB рядом с Vim на одном экране, и либо управлять им прямо из Vim (например, ставить точки останова, смотреть значения переменных), либо переключаться в окно GDB и просто работать с GDB. Локальные переменные, или поля объектов, можно смотреть в виде дерева в nerd tree. К сожалению, у плагина нет никакой документации, но его установка достаточно проста. Надо установить два плагина через Vundle, а затем настроить vimrc.

Plugin 'ManOfTeflon/exterminator'
Plugin 'ManOfTeflon/nerdtree-json'


Если уже используется стандартное nerdtree, то его надо убрать. nerttree-json — это форк nerdtree, который позволяет расширять его возможности, и делать lazy-вычисление элементов дерева.

Затем вот пример настройки exterminator у автора плагина:

Скрытый текст
function! s:get_range()
  " Why is this not a built-in Vim script function?!
  let [lnum1, col1] = getpos("'<")[1:2]
  let [lnum2, col2] = getpos("'>")[1:2]
  let lines = getline(lnum1, lnum2)
  let lines[-1] = lines[-1][: col2 - (&selection == 'inclusive' ? 1 : 2)]
  let lines[0] = lines[0][col1 - 1:]
  return join(lines, "\n")
endfunction

nnoremap <silent> & :exec 'VimGrep ' . expand('<cword>')<cr>
vnoremap <silent> & :exec 'VimGrep ' . s:get_range()
comm! -nargs=0 -range GdbVEval exec 'GdbEval ' . s:get_range()

nnoremap <silent> <F6>  :exec "GdbEval " . expand("<cword>")<CR>
vnoremap <silent> <F6>  :GdbVEval<cr>
nnoremap <silent> <F5>  :GdbLocals<CR>
nnoremap <silent> <F4>  :GdbNoTrack<CR>

nnoremap <silent> <Insert> :GdbContinue<cr>
nnoremap <silent> <End> :GdbBacktrace<cr>
nnoremap <silent> <Home> :GdbUntil<cr>
nnoremap <silent> <S-Up> :GdbExec finish<cr>
nnoremap <silent> <S-Down> :GdbExec step<cr>
nnoremap <silent> <S-Right> :GdbExec next<cr>
nnoremap <silent> <S-Left> :GdbToggle<cr>
noremap <silent> <PageUp> :GdbExec up<cr>
noremap <silent> <PageDown> :GdbExec down<cr>

function! s:start_debugging(cmd)
    cd $PATH_TO_EXECUTABLE
    exec 'Dbg ' . a:cmd
endfunction
command! -nargs=1 DbgWrapper    call s:start_debugging(<f-args>)

nnoremap <silent> <leader>B :DbgWrapper ./executable<cr>


Где $PATH_TO_EXECUTABLE и ./executable — путь к программе для отладки и ее название.


С такой конфигурацией в запущенном из TMux Vim можно запустить отладку через \B, в результате чего TMux разделит экран на два, и запустит во втором GDB, связанный с Vim. В частности, при передвижении по фреймам в GDB Vim будет показывать код в текущем фрейме, и из Vim с помощью горячих клавиш можно передвигаться по фреймам, ставить точки останова, выполнять программу по шагам, и показывать значения переменных в nerdtree. В частности, F5 покажет значения всех локальных переменных, а F6 — переменной под курсором.

Код плагина для навигации по C++ и его серверной части открыты и доступны на GitHub под MIT License:
https://github.com/SkidanovAlex/CtrlK
https://github.com/SkidanovAlex/py-ctrlk

Код плагина для отладки тоже открыт, и доступен под WTFPL лицензией:
https://github.com/ManOfTeflon/exterminator
Tags:
Hubs:
+46
Comments 92
Comments Comments 92

Articles