Найти в Дзене
Блокнот математика

Рубрика "Секреты Вим". Поиск идентификаторов

Привет, коллеги. При работе с программным проектом часто приходится вести поиск во многих файлах сразу. Искать определения, первое упоминание, а то и просто все вхождения. Что у нас для этого есть в Вим? 1. Если вам надо просто "не глядя" что-то сделать, например заменить все troppo_lungo_nome на piu_corte, то есть массовые операции. Можно выполнить команду во всех окнах, вкладках, открытых файлах. Например, мы открыли два файла: vim 1.c 2.c, и отдаем команду
:argdo s/\<this_name\>/that_name/g Можно открыть во вкладках, и тогда tabdo. Или в окнах, и тогда windo. 2. Можно сделать это и вне Вим, однострочником на Перле, например:
perl -E 's/\bthis_name\b/that_name/g' file1 file2 3. Но мы хотим видеть, что делаем. Есть система тэгов, о которой тоже был материал. Тэги - это слова, по которым ведется поиск, и некоторая база по ним создается предварительно внешней утилитой вроде ctags. Вим умеет пользоваться этими базами. И в нём есть средства навигации. Так, <C-]> позволяет перейти на опр

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

https://st2.depositphotos.com/2079065/5397/v/950/depositphotos_53975529-stock-illustration-find-objects-visual-game-solution.jpg
https://st2.depositphotos.com/2079065/5397/v/950/depositphotos_53975529-stock-illustration-find-objects-visual-game-solution.jpg

1. Если вам надо просто "не глядя" что-то сделать, например заменить все troppo_lungo_nome на piu_corte, то есть массовые операции. Можно выполнить команду во всех окнах, вкладках, открытых файлах.

Например, мы открыли два файла: vim 1.c 2.c, и отдаем команду
:argdo s/\<this_name\>/that_name/g

Можно открыть во вкладках, и тогда tabdo. Или в окнах, и тогда windo.

2. Можно сделать это и вне Вим, однострочником на Перле, например:
perl -E 's/\bthis_name\b/that_name/g' file1 file2

3. Но мы хотим видеть, что делаем. Есть система тэгов, о которой тоже был материал. Тэги - это слова, по которым ведется поиск, и некоторая база по ним создается предварительно внешней утилитой вроде ctags. Вим умеет пользоваться этими базами. И в нём есть средства навигации. Так, <C-]> позволяет перейти на определение функции (найти его и помогает база), а <C-T> - вернуться обратно. Так можно странствовать довольно долго, стек переходов позволяет вынырнуть туда, откуда зашел. Та же система применяется и в навигации по справке.

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

4. Для этого есть глобальный поиск, о котором я тоже рассказывал. Помимо системного grep, есть и внутренний vimgrep. Вим располагает также системой QuickFix: особым буфером, в котором хранятся найденные позиции, и системой навигации по ним. Позиции могут быть ошибками компиляции, но могут быть и чем угодно еще, в частности, и найденными вхождениями. Перемещайтесь с помощью :cnext, :cprev.

5. Есть ещё одно средство, тоже на базе внешней утилиты, новое для нашей рубрики. Оно описано в справке Вим (ident-search).

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

Набор утилит GNU id-tools позволяет создавать базы идентификаторов в программном коде (ТеХ тоже входит, а вот Фортран не входит, во всяком случае, "из коробки"). База не текстовая, а двоичная, но нам в нее лезть и не надо. Вим тоже в нее не ходит, она "для своих". И это правильно, так как - инкапсуляция. Доступ к базе должен быть через интерфейс, а не напрямую.

Поставив утилиты, надо настроить Вим так, чтобы он мог ими пользоваться, прозрачно для нас. Помещаем в .vimrc вот такой код:

map _u :call ID_search()<Bar>execute "/\\<" . g:word . "\\>"<CR>
map _n :n<Bar>execute "/\\<" . g:word . "\\>"<CR>
function! ID_search()
let g:word = expand("<cword>")
let x = system("lid --key=none ". g:word)
let x = substitute(x, "\n", " ", "g")
execute "next " . x
endfunсtion

В папке с вашим проектам запускаем утилиту mkid, которая создаст базу: файл с именем вроде ID. Есть ключи, указывающие, нырять ли в каталоги, и тому подобное: см. man mkid. Итак, база создана. Теперь привязкa _u загрузит все файлы, в которых имя встречается, и откроет первый. По нему можно пройтись командой n, а _n перейдет в следующий файл. Вернуться позволит обычная :prev.

Давайте разберём. Начнем с функции. Она сначала извлекает слово из-под курсора, посредством функции expand со специальным аргументом <cword>. Затем выполняет внешнюю команду lid (это утилита доступа к базе) с параметром --key-none, предписывающим не выводить искомое: только имена файлов, в которых искомое нашлось. Из списка файлов удаляются концы строк, так что список получается в одно строку, имена файлов разделены пробелами. Наконец, выполняется команда :next, которой передается этот список: она заменит список открытых файлов на данный и откроет первый.

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

Вторая привязка выполняет :next (сокращена до :n), открывая следующий файл, и тоже осуществляет поиск.

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

Данная техника позволяет с комфортом работать с большими проектами, как на языках программирования, так и в ТеХ. Но надо попробовать и настроить ее под себя. Например, мне удобно поставить метку в той позиции, где я нахожусь, и потом перейти на нее глобальным прыжком (в другой файл).

Удачи, коллеги!

Научно-популярные каналы на Дзене: путеводитель
Новости популярной науки12 марта 2022