Быстрее некуда: собираем удобный поиск по коду из нескольких CLI-утилит

в 15:32, , рубрики: cli, Go, vk cloud
Быстрее некуда: собираем удобный поиск по коду из нескольких CLI-утилит - 1

Мы никогда не читаем код как книгу — мы выбираем только конкретные интересующие места. Такие места обычно запоминаются ассоциативно, например по имени функции, строковому литералу, импорту библиотеки, комментарию и т. д. Перейти от ассоциации к файлу, а тем более к конкретной строчке кода не всегда легко. Особенно если оперируешь большим количеством проектов с активно меняющейся кодовой базой. В таких случаях выручает удобный инструмент текстового поиска.

Эффективность такого инструмента определяется как скоростью работы, так и удобством использования. В частности, кастомизация под себя позволяет разгрузить мышление и включить «мышечную память» — когда руки сами нажимают кнопки, а все внимание сосредоточено на обработке результатов поиска. Не все инструменты позволяют провести такую тонкую настройку. Меня зовут Роман Щекин, я работаю руководителем команды разработчиков в VK Cloud, и в этой статье мы с вами поищем серебряную пулю, попробуем достичь сочетания скорости и удобства в виде собранного из кросс-платформенного опенсорса поисковика.

Дисклеймер

  • Решения по типу SourceGraph в статье не рассматриваются намеренно, речь идет именно об удобстве локального поиска.

  • Полнофункциональные терминальные решения на базе vim/neovim не рассматриваются, так как имеют довольно высокий порог входа.

  • Я ни в коем случае не эксперт по bash, поэтому очень приветствую замечания и улучшения по скриптам.

Введение об идеальном поисковике

Прежде чем поделиться своим рецептом, я бы хотел кратко пояснить читателям истоки именно такого видения идеального поисковика. То решение, к которому мы придем в конце статьи, во многом навеяно предыдущим опытом и сформировавшимися привычками. Постараюсь упаковать и то, и другое в пару абзацев! Если же хочется опустить лирику и сразу посмотреть, что получилось в итоге, можете перейти в раздел «Собираем все вместе».

Я начинал свою карьеру как разработчик под Windows в поздних нулевых. По моему субъективному мнению, с инструментами разработки тогда все было хорошо хотя бы по причине Visual Studio 2005. Разработку под Windows эта IDE позволяла делать с высоким уровнем комфорта, все возможности были доступны из GUI, и практически никогда не требовалось оперировать терминалом. Еще на Windows был и есть Total Commander. Это буквально первая программа, которая предстала передо мной на первом персональном (в те времена, скорее, «семейном») компьютере. Азы работы с файлами и каталогами учил в нем, с тех пор всегда ставлю TC одним из первых на свежую инсталляцию ОС от Microsoft. Total Commander — действительно удобный, проработанный и расширяемый файловый менеджер, полных аналогов которому на других платформах пока мне найти не удалось.

К чему я? Одна из киллер-фич TC, по моему мнению, — поиск файлов по имени и содержимому. Я как-то уже упоминал о нем в комментарии — обсуждение и сама статья про Far заслуживают внимания. Основной сценарий использования поиска такой:

  1. Открыть папку с большой кодовой базой или сразу со всеми проектами.

  2. Ввести маску для файлов, например `*.cpp;*.h;*.c`.

  3. Ввести искомую подстроку, например malloc.

  4. Ознакомиться со списком найденных файлов.

  5. Посмотреть результаты точечно с помощью встроенного просмотрщика (Lister).

Ключевое удобство в том, что можно нажать F3 (хоткей Lister) прямо на строчке в диалоге поиска (не выводя результаты в панель!), чтобы просмотреть содержимое найденного файла. При этом Lister понимает, что его вызвали в контексте поиска, и повторное нажатие на F3 в нем скроллит к искомой строке (в примере это malloc). Последующие нажатия скроллят к следующему вхождению строки в файле, если таковые имеются. Такая получилась нативная интеграция поисковика с просмотрщиком. По нажатию на F4 открывается редактор, который, кстати, можно выбрать на свой вкус.

В итоге выработалась привычка использовать IDE для проекта, над которым в моменте активно работаю, и Total Commander для широкого поиска по остальной кодовой базе и разнообразным конфигам. Приходилось работать с самыми разными кодовыми базами, порой очень дремучими и запутанными — задачи выполнялись, тулинг не подводил. Эта связка отлично себя показывает на практике при работе в Windows и по сей день.

Но где-то же должна быть завязка статьи? :) Она заключается вот в чем: за последние пару лет я окончательно перешел на MacOS в качестве рабочей ОС. Большую часть времени за работой провожу в ней. И поначалу мне очень недоставало хорошего инструмента для поиска — удобство предыдущего подхода просто нечем было восполнить. Поэтому, когда я наконец-то набрел на «конструктор», из которого можно собрать что-то на свой вкус, именно привычный вариант работы, как в TC, я взял за образец.

Поиск лучшего решения

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

Что не сработало

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

Midnight Commander. Старичок, ветеран индустрии. Думаю, что если бы когда-то привык к нему, а не к Total Commander, то функциональность вполне устроила бы. Внутри есть очень похожий на TC поисковик (а может, это, наоборот, TC похож на MC, who knows) с похожими хоткеями и UX. Однако встроенный просмотрщик и редактор весьма слабые, а настройка под себя показалась замороченной.

Commander One. Один из лучших клонов Total Commander на MacOS. В целом пользоваться можно, и даже местами удобно. Но поиск в нем явно не самая сильная сторона: и встроенный Lister проигрывает в функциональности, и хоткеи работают не так. Пользоваться можно, но снова не то.

IDE. Все хорошо, но медленно. Обычно в IDE открывается один проект или группа репозиториев внутри одного проекта. Даже в относительно легковесной VS Code интерфейс становится перегруженным, а команды начинают работать медленно. Плюс открывать IDE под каждую необходимость поискать что-то не очень оптимально, хочется решение пошустрее.

Классические CLI-утилиты из коробки. При необходимости искать с помощью grep и find, конечно, можно, но лично для меня это никогда не было быстрым и удобным способом что-то найти. Из минусов можно отметить высокие накладные расходы (банально нужно много печатать) и низкую интерактивность. Из плюсов — наличие в любом дистрибутиве.

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

Что будем использовать

В последнее время появилось довольно много современных реинкарнаций классических линуксовых утилит. Многие из них пишутся на Rust/Go, что дает кросс-платформенность и многопоточность из коробки. Новые языки привлекают большие сообщества, у проектов много контрибьюторов и тысячи звезд на гитхабе. Отсутствие необходимости соблюдать обратную совместимость развязывает руки в плане оптимизации и построения более функциональных консольных UI. Некоторые из таких утилит получили заслуженное признание в комьюнити, взглянем повнимательнее на несколько из них.

bat. Утилита bat — это современная альтернатива классической команде cat в Linux, предназначенная для вывода содержимого файлов в консоль. Одним из ключевых преимуществ bat является подсветка синтаксиса, которая делает чтение кода и конфигов куда более удобным и наглядным. Кроме того, bat поддерживает нумерацию строк, что упрощает навигацию по файлу и анализ его содержимого. Утилита также интегрируется Git, показывая изменения в файлах. bat имеет встроенную поддержку для просмотра содержимого нескольких файлов одновременно, а также возможность отображения содержимого в виде страниц. То есть тот же cat, только функциональнее. На настоящий момент это мой избранный легковесный просмотрщик.

fzf. Утилита fzf — это интерактивный инструмент командной строки для поиска и фильтрации текста. Он принимает на вход список (обычные строки, разделенные символом переноса) и показывает окошко с возможностью нечеткого поиска в этом списке. Так можно быстро находить файлы, команды в истории, процессы и др. Важно заметить, что fzf не просто хорошо решает свою задачу, а еще и гибко конфигурируется, что позволяет использовать его в комбинации с другими утилитами.

rga. Утилита rga (ripgrep all) — это расширение утилиты ripgrep, предназначенное для поиска текста в различных типах файлов, включая архивы и документы. В отличие от стандартного ripgrep, rga поддерживает поиск внутри PDF, DOCX, ZIP и т. д. в зависимости от установленных плагинов. Он использует специализированные конверторы, например pandoc, для извлечения текста из «сложных» форматов файлов. rga сохраняет высокую скорость работы благодаря эффективному алгоритму поиска (в т. ч. по регулярным выражениям), характерному для ripgrep.

Собираем все вместе

Впервые способ заставить работать fzf с rga попался мне на глаза в заметке:

Быстрее некуда: собираем удобный поиск по коду из нескольких CLI-утилит - 2

Концептуально суть заключается в следующем:

  • fzf отвечает за отображение легковесного UI (список найденных файлов/строк и превью результатов), а также кастомизацию хоткеев и обработку ввода;

  • rga выступает в роли поисковика файлов по содержимому, делает "heavy lifting";

  • bat опционально используется вместо встроенного превью;

  • скрипт в виде функции кладется в .bashrc`/`.zshrc, а затем вызывается из терминала по имени.

Не забудьте установить утилиты и перезапустить оболочку после правок.

Вариантов таких сборок нашлось немало, например:

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

 qse() {
   	RG_PREFIX="rg --files-with-matches"
   	local file
   	file="$(
   	  	FZF_DEFAULT_COMMAND="$RG_PREFIX '$1'" 
              	fzf 
                   	--preview="[[ ! -z {} ]] && rg --pretty --context 5 {q} {}" 
                   	--disabled --query "$1" 
                   	--bind "change:reload:sleep 0.1; $RG_PREFIX {q}" 
                   	--bind "f3:execute(bat --paging=always --pager=less --color=always {} < /dev/tty > /dev/tty)" 
                   	--bind "f4:execute(code {})" 
                   	--preview-window="70%:wrap"
   	)" &&
   	echo "$file"
 }

Разберем ключевые моменты:

  • В RG_PREFIX указывается базовая команда поиска с флагами по умолчанию; я остановился на rg, потому что поиск по отличным от кода файлам мне требуется гораздо реже.

  • В FZF_DEFAULT_COMMAND указывается, чем fzf ищет по умолчанию (вместо find).

  • --preview определяет команду, которая отобразит найденный результат; она будет вызываться каждый раз при навигации по результатам поиска.

  • --disabled отключает поиск в fzf, превращая утилиту в UI-прослойку.

  • --bind на событие change перезапускает поиск при вводе.

  • --bind на клавиши F3 и F4 запускает bat и VS Code соответственно.

  • С помощью $file выбранный файл выводится в консоль.

Как нетрудно заметить, здесь фактически воссоздан функционал поиска из TC. Работает быстро, плавно и отзывчиво благодаря перезапуску поиска после каждого нажатия (событие change). Есть подсветка найденных результатов и возможность запустить просмотрщик/редактор.

Давайте посмотрим, как работает вживую, на небольшой демонстрации. Специально для нее я подготовил каталог, в который склонировал код Kubernetes, VictoriaMetrics и fzf. Сначала выполняется поиск всех объявлений функций в Go по ключевому слову func, затем навигация по результатам, точечный просмотр файлов и повторный поиск с более специфичным запросом func TestQueue. Выглядит вот так:

gif

gif

Почему qse? Хотелось придумать короткий и удобный шорткат, изначально это был qs (quick search, qtros search, whatever). Далее в процессе отладки я наплодил несколько версий в алфавитном порядке: qsa, qsb, ..., qse. Дойдя в экспериментах до версии «e», я получил все желаемое поведение и уже успел немного привыкнуть к имени. Поэтому qse.

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

gif

gif

Кстати, пока тестировал свою сборку, обнаружил баг в fzf, связанный с кэшированием результатов. Его уже успели поправить, 27 октября случился релиз 0.56.0. Поэтому при использовании последней версии у вас не должно быть проблем!

Заключение

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

Автор: QtRoS

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js