
Мы никогда не читаем код как книгу — мы выбираем только конкретные интересующие места. Такие места обычно запоминаются ассоциативно, например по имени функции, строковому литералу, импорту библиотеки, комментарию и т. д. Перейти от ассоциации к файлу, а тем более к конкретной строчке кода не всегда легко. Особенно если оперируешь большим количеством проектов с активно меняющейся кодовой базой. В таких случаях выручает удобный инструмент текстового поиска.
Эффективность такого инструмента определяется как скоростью работы, так и удобством использования. В частности, кастомизация под себя позволяет разгрузить мышление и включить «мышечную память» — когда руки сами нажимают кнопки, а все внимание сосредоточено на обработке результатов поиска. Не все инструменты позволяют провести такую тонкую настройку. Меня зовут Роман Щекин, я работаю руководителем команды разработчиков в VK Cloud, и в этой статье мы с вами поищем серебряную пулю, попробуем достичь сочетания скорости и удобства в виде собранного из кросс-платформенного опенсорса поисковика.
Дисклеймер
Решения по типу SourceGraph в статье не рассматриваются намеренно, речь идет именно об удобстве локального поиска.
Полнофункциональные терминальные решения на базе vim/neovim не рассматриваются, так как имеют довольно высокий порог входа.
Я ни в коем случае не эксперт по bash, поэтому очень приветствую замечания и улучшения по скриптам.
Введение об идеальном поисковике
Прежде чем поделиться своим рецептом, я бы хотел кратко пояснить читателям истоки именно такого видения идеального поисковика. То решение, к которому мы придем в конце статьи, во многом навеяно предыдущим опытом и сформировавшимися привычками. Постараюсь упаковать и то, и другое в пару абзацев! Если же хочется опустить лирику и сразу посмотреть, что получилось в итоге, можете перейти в раздел «Собираем все вместе».
Я начинал свою карьеру как разработчик под Windows в поздних нулевых. По моему субъективному мнению, с инструментами разработки тогда все было хорошо хотя бы по причине Visual Studio 2005. Разработку под Windows эта IDE позволяла делать с высоким уровнем комфорта, все возможности были доступны из GUI, и практически никогда не требовалось оперировать терминалом. Еще на Windows был и есть Total Commander. Это буквально первая программа, которая предстала передо мной на первом персональном (в те времена, скорее, «семейном») компьютере. Азы работы с файлами и каталогами учил в нем, с тех пор всегда ставлю TC одним из первых на свежую инсталляцию ОС от Microsoft. Total Commander — действительно удобный, проработанный и расширяемый файловый менеджер, полных аналогов которому на других платформах пока мне найти не удалось.
К чему я? Одна из киллер-фич TC, по моему мнению, — поиск файлов по имени и содержимому. Я как-то уже упоминал о нем в комментарии — обсуждение и сама статья про Far заслуживают внимания. Основной сценарий использования поиска такой:
Открыть папку с большой кодовой базой или сразу со всеми проектами.
Ввести маску для файлов, например `*.cpp;*.h;*.c`.
Ввести искомую подстроку, например
malloc.Ознакомиться со списком найденных файлов.
Посмотреть результаты точечно с помощью встроенного просмотрщика (Lister).
Ключевое удобство в том, что можно нажать F3 (хоткей Lister) прямо на строчке в диалоге поиска (не выводя результаты в панель!), чтобы просмотреть содержимое найденного файла. При этом Lister понимает, что его вызвали в контексте поиска, и повторное нажатие на F3 в нем скроллит к искомой строке (в примере это malloc). Последующие нажатия скроллят к следующему вхождению строки в файле, если таковые имеются. Такая получилась нативная интеграция поисковика с просмотрщиком. По нажатию на F4 открывается редактор, который, кстати, можно выбрать на свой вкус.
В итоге выработалась привычка использовать IDE для проекта, над которым в моменте активно работаю, и Total Commander для широкого поиска по остальной кодовой базе и разнообразным конфигам. Приходилось работать с самыми разными кодовыми базами, порой очень дремучими и запутанными — задачи выполнялись, тулинг не подводил. Эта связка отлично себя показывает на практике при работе в Windows и по сей день.
Но где-то же должна быть завязка статьи? :) Она заключается вот в чем: за последние пару лет я окончательно перешел на MacOS в качестве рабочей ОС. Большую часть времени за работой провожу в ней. И поначалу мне очень недоставало хорошего инструмента для поиска — удобство предыдущего подхода просто нечем было восполнить. Поэтому, когда я наконец-то набрел на «конструктор», из которого можно собрать что-то на свой вкус, именно привычный вариант работы, как в TC, я взял за образец.
Поиск лучшего решения
Итак, на старте мы имеем MacOS + желание быстро и удобно искать текст. Давайте прикинем, что с этим можно сделать.
Что не сработало
Идея собрать что-то под себя пришла не сразу, поэтому я начал с перебора готовых коробочных решений. Каждый из инструментов ниже, помимо всего прочего, позволяет искать файлы по названию и по содержимому. Не хочется утверждать, что это совсем нерабочие варианты, но лично у меня «магии на кончиках пальцев» не случилось.
Midnight Commander. Старичок, ветеран индустрии. Думаю, что если бы когда-то привык к нему, а не к Total Commander, то функциональность вполне устроила бы. Внутри есть очень похожий на TC поисковик (а может, это, наоборот, TC похож на MC, who knows) с похожими хоткеями и UX. Однако встроенный просмотрщик и редактор весьма слабые, а настройка под себя показалась замороченной.
FAR / far2l. Добавил по совету @31415. Во многом похоже на MC, но получше в мелочах: редактор поудобнее, подсветка синтаксиса из коробки имеется, просмотрщик умеет в HEX. При этом сам поиск показался довольно медленным (однопоточный?). В общем, крепкий кандидат, но итог поисков мне нравится больше.
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 попался мне на глаза в заметке:

Концептуально суть заключается в следующем:
fzfотвечает за отображение легковесного UI (список найденных файлов/строк и превью результатов), а также кастомизацию хоткеев и обработку ввода;rgaвыступает в роли поисковика файлов по содержимому, делает "heavy lifting";batопционально используется вместо встроенного превью;скрипт в виде функции кладется в
.bashrc`/`.zshrc, а затем вызывается из терминала по имени.
Не забудьте установить утилиты и перезапустить оболочку после правок.
Вариантов таких сборок нашлось немало, например:
https://github.com/phiresky/ripgrep-all/wiki/fzf-Integration
https://github.com/junegunn/fzf/blob/master/ADVANCED.md#using-fzf-as-interactive-ripgrep-launcher
https://stackoverflow.com/questions/77415675/how-to-use-rga-ripgrep-all-with-fzf-for-searching-the-pdf-file-and-then-using. Один из них даже встроили в
rgaв виде отдельного бинарника.https://github.com/phiresky/ripgrep-all/blob/master/src/bin/rga-fzf.rs. С одной стороны работает из коробки, с другой — почти не кастомизируется.
Идея везде примерно одинаковая, скрипты различаются способом показа результатов поиска (только имена файлов или имена с номерами строк), формированием превью и биндов хоткеев. Изучив несколько вариантов и изрядно наигравшись с комбинацией параметров, я пришел вот к такой версии (репозиторий):
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. Выглядит вот так:

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

Кстати, пока тестировал свою сборку, обнаружил баг в fzf, связанный с кэшированием результатов. Его уже успели поправить, 27 октября случился релиз 0.56.0. Поэтому при использовании последней версии у вас не должно быть проблем!
Заключение
В статье рассмотрен способ создания специализированного поисковика из подручного опенсорса. Несмотря на простоту и небольшой размер, получившийся скрипт уже вовсю используется в работе, помогая искать диагонально по десяткам репозиториев крупных проектов. Примечательно, что каждый может без особого труда докрутить скрипт под себя: выбрать альтернативный просмотрщик, поставить другие хоткеи, искать не только по файлам и т. д. Надеюсь, что опыт из статьи оказался полезным и что вы нашли в ней что-то новое!
Вопросы для обсуждения в комментариях:
Стоит ли собрать инсталлятор такого поисковика, чтобы облегчить установку, и как это лучше сделать?
Пора ли добавлять современные CLI-утилиты в дистрибутивы операционных систем, чтобы ими можно было пользоваться "из коробки" на любой машине?
Комментарии (12)

sirojiddin13
19.11.2024 17:30Не хочу быть тем самым челом, но ведь это уже есть во встроенном поиске в JetBrains IntelliJ IDEA? Если хочется по нескольким проектам искать, можно просто workspaces подрубить.

thethee
19.11.2024 17:30Не на всех рабочих станциях или удалённых серверах можно запустить JetBrains IntelliJ IDEA. Согласен, в статье речь про MacOS, но предложенные утилиты прекрасно работают и на Linux системах.

sirojiddin13
19.11.2024 17:30Ну для удаленной разработки уже пилится тулза https://www.jetbrains.com/remote-development/ , правда сервер весит почти столько же как сама ide, в этом плане vscode по легче будет

QtRoS Автор
19.11.2024 17:30Соглашусь, что IDE это отличный вариант, который просто работает и в среднем не заставляет задумываться об использовании дополнительных инструментов. Если же по той или иной причине есть необходимость в каком-то из следующих аспектов:
Практически нулевая скорость запуска и затраты оперативной памяти
Компактный консольный UI, который можно запустить в терминале
Приближенная к максимальному скорость поиска
Возможность кастомизировать и скриптовать действия
Минимализм "дистрибутива"
Свободная лицензия и бесплатность
То можно посмотреть в сторону рассмотренного поисковика.

gmelikov
19.11.2024 17:30Кстати это стандартная боль в IDEшках - когда у тебя не 1 проект а больше 10 (я держу около 100, к примеру). Обычно для IDE рекомендую заводить отдельный воркспейс и накидывать вручную все проекты в него (ибо они не всегда лежат в одной директории), тогда можно будет искать по всем проектам разом.
Лично я юзаю vs code и вместо поиска по всем проектам поставил Git Project Manager , с которым могу за 1 хоткей быстро переходить в нужный проект без надобности заводить воркспейс и тд. Когда очень припрёт поискать везде - классический grep.
Если кто знает более удобные пути наконфигурить для глобального поиска тот же vs code - поделитесь, спасибо!

shaggyone
19.11.2024 17:30Есть люди которые вообще не пользуются IDE, предпочитают им условный vim. Потом, автор указал позицию по поводу IDE.

31415
19.11.2024 17:30Итак, на старте мы имеем MacOS + желание быстро и удобно искать текст.
берем far2l и закрываем статью

QtRoS Автор
19.11.2024 17:30Постараюсь позднее добавить FAR к сравнению. Честно признаться, думал он исключительно под linux работает, поэтому рассмотрел только MC из консольных файловых менеджеров.
Буду признателен, если поделитесь своим опытом: как-то кастомизируете или берете вариант "из коробки", какие просмотрщик/редактор используете и настраивается ли это, во всех ли сценариях ищете в фаре или дополняете другими инструментами.

31415
19.11.2024 17:30Никак особенно не настраиваю, меня и так устраивает. Основной сценарий использования поиска - как у вас написано - в farl2 полностью идентичен.
Но поиск в Far-e (в любом) не отличается высокой скоростью, поэтому это подходит только для небольших объемов - до гигабайта. Если нужно грепать много гигабайтов, то использую ripgrep.

mapcuk
19.11.2024 17:30Тестил разные штуки. Вместе с IDE у меня всегда открыта консоль. В ходе работы над кодом и в целом обработки текстовых файлов больше всего зашёл silver searcher (порой в конвейере с cut, grep), а вот fzf и ripgrep как-то не прижились
ag --ignore *_test.go --go -C 2 Seed ./pkg | less -SВ остальном "попрыгать" по коду и посмотреть удобнее в IDE.
Иногда люблю почитать сниппеты на проекте commandlinefu.com - иногда попадаются "бриллианты".

VADemon
19.11.2024 17:30Классно, заберу. Как-то раз тоже скриптовал rg, чтобы мне редактор на нужной строке открывал для вхождений.
danielsedoff
Вот такое интересно будет попробовать. Спасибо:)