Редакторы и IDE Навигация по коду: поиск, теги, символы и чтение незнакомого проекта
0%

Навигация по коду: поиск, теги, символы и чтение незнакомого проекта

Навигация по коду: поиск, теги, символы и чтение незнакомого проекта

Весь трек до этого места был про инструменты: модальность, конфигурации, расширения, сравнение. Но настоящая работа в редакторе состоит не из набора текста. Она состоит из ответов на вопросы:

  • Где это определено?
  • Кто это вызывает и что сломается, если поменять сигнатуру?
  • Где в этом репозитории живёт обработка платежей?
  • Почему эта строка вообще появилась и кто её автор?

Скорость ответа на такие вопросы — главный практический навык, который переносится между редакторами полностью. Человек, который умеет за 20 секунд найти нужное место в чужом миллионе строк, одинаково эффективен в Vim, VS Code и IDEA. Человек, который этого не умеет, будет одинаково медленным везде, сколько бы плагинов ни поставил.

Эта статья — не про кнопки. Она про модель: три слоя поиска, у каждого своя цена, полнота и предел применимости. Разберём каждый слой до деталей реализации, потом соберём из них рабочий протокол чтения незнакомого проекта.

Почему это отдельная тема, а не строчка в настройках

Полевые исследования дают устойчивую картину: разработчик тратит на понимание кода заметно больше времени, чем на его написание. Работа Xin Xia и соавторов «Measuring Program Comprehension: A Large-Scale Field Study with Professionals» даёт около 58 % рабочего времени на активности понимания; исследование Роберто Минелли и соавторов «I Know What You Did Last Summer», основанное на записи взаимодействий с IDE, даёт близкие цифры и добавляет важную деталь: основная часть этого времени уходит не на чтение как таковое, а на навигацию — поиск того, что читать.

Посчитаем в лоб. Средний день — это порядка 30–60 вопросов «где/кто/почему». Если один вопрос стоит 10 секунд, это 5–10 минут в день. Если он стоит 2–3 минуты (открыть браузер, пойти в веб-интерфейс репозитория, поискать глазами, потерять контекст), это уже 1,5–3 часа. Разница — не в редакторе, а в том, каким слоем вы отвечаете на вопрос.

Вопрос Кто отвечает Стоимость подготовки
Где встречается строка «payment_failed» текстовый поиск ноль
Где определена функция charge_card теги или LSP секунды / минуты индексации
Кто вызывает charge_card LSP или GNU GLOBAL минуты индексации
Что сломается, если поменять сигнатуру LSP / модель IDE индексация проекта
Где во всей организации используют этот API серверный индекс инфраструктура
Почему тут написано именно так git log, git blame ноль

Три слоя поиска

Три слоя навигации по коду

Слои различаются тем, что именно они понимают в тексте.

Свойство Слой 1: текст Слой 2: теги Слой 3: семантика
Что знает байты и регулярки имена и их определения типы, области видимости, ссылки
Полнота 100 % (найдёт всё) только объявления только то, что скомпилировалось
Точность низкая (шум) средняя (омонимы) высокая
Работает на битом коде да почти да нет или частично
Работает без настройки да нужен один запуск нужен сервер и конфиг проекта
Цена на 1 млн строк 0,1–1 с 5–30 с индексации 10 с – 10 мин индексации
Пределы не различает user.id и User.ID не знает, кто вызывает не видит рефлексию, DI, шаблоны конфигов

Правило выбора звучит скучно, но экономит часы: начинайте с самого дешёвого слоя, который даёт приемлемую точность, и поднимайтесь выше, только если шум мешает.

Слой 1: текстовый поиск, который стоит настроить один раз

Почему ripgrep быстрый

ripgrep стал стандартом де-факто не потому, что «написан на Rust». Разбор автора, «ripgrep is faster than {grep, ag, git grep, ucg, pt, sift}», объясняет реальные причины, и они полезны как модель того, из чего вообще состоит скорость поиска:

  1. Меньше файлов. Обход по умолчанию уважает .gitignore, пропускает скрытые и бинарные файлы. Не искать в node_modules — это не оптимизация, это отказ от 90 % работы.
  2. Параллельный обход. Директории читаются в несколько потоков, поиск идёт по мере поступления файлов; вывод сериализуется отдельно, чтобы порядок оставался детерминированным.
  3. Быстрый поиск литералов. Регулярка разбирается, из неё извлекаются обязательные литералы, и по ним работает memchr на SIMD-инструкциях, а для нескольких литералов — алгоритм Teddy. Полный автомат запускается только на кандидатах.
  4. Конечные автоматы без обратных ссылок. Синтаксис регулярок ограничен так, чтобы гарантировалось линейное время, — никакого катастрофического бэктрекинга. Про машинерию за этим — алгоритмы на строках.

Практический вывод: скорость поиска — это на 80 % то, сколько байт вы не прочитали. Любой инструмент станет быстрым, если правильно исключить мусор, и любой станет медленным на репозитории со сборочными артефактами внутри дерева.

Рабочий набор команд

# Базовое: поиск по проекту с уважением .gitignore
rg 'chargeCard'

# Только в файлах определённого типа (список типов: rg --type-list)
rg -t py -t go 'timeout'

# Только по путям, подходящим под glob (можно несколько -g, отрицание через !)
rg 'TODO' -g '*.ts' -g '!*.spec.ts'

# Целое слово, без учёта регистра, с контекстом в 3 строки
rg -w -i -C 3 'retry'

# Умный регистр: строчный запрос ищет без учёта регистра, запрос с заглавной — точно
rg -S 'UserRepository'

# Только имена файлов — дальше по конвейеру
rg -l 'deprecated' | xargs wc -l | sort -n | tail

# Фиксированная строка, а не регулярка (спасает при поиске "$", "(", "[")
rg -F 'config["db.host"]'

# Мультистрочный поиск: аннотация и следующая за ней сигнатура
rg -U --multiline-dotall '@Transactional.*?public .*?\('

# Заменить в выводе (только показать, файлы не трогает) — черновик правки
rg 'assertEquals\((.*?), (.*?)\)' -r 'assertThat($2).isEqualTo($1)'

# Смотреть даже в игнорируемых и скрытых файлах: -u -uu -uuu по нарастающей
rg -uu 'API_KEY'        # включая .gitignore-файлы и скрытые
rg -uuu 'API_KEY'       # плюс бинарные

# Машинный вывод для интеграций: одна JSON-строка на событие
rg --json 'panic!' | head -5

# Сколько совпадений по файлам — быстрый способ увидеть, где «горячо»
rg -c 'logger\.' | sort -t: -k2 -rn | head

Файл .ignore в корне репозитория работает как .gitignore, но только для поисковых инструментов (rg, fd) и не попадает в правила Git. Это правильное место для «не ищи здесь, но храни в репозитории»: снапшоты тестов, сгенерированные клиенты API, вендоренные зависимости.

# .ignore — не влияет на git, влияет на rg/fd
/vendor/
/**/__snapshots__/
/api/generated/
*.min.js
*.lock

Измерьте свой репозиторий, а не чужой

# Сколько вообще кода: tokei честнее, чем find | wc -l
tokei

# Честное сравнение поиска на вашем дереве (hyperfine прогревает кэш ФС)
hyperfine --warmup 3 \
  "rg 'chargeCard' --no-messages" \
  "git grep -n 'chargeCard'" \
  "grep -rn 'chargeCard' ."

# Где именно лежат байты — типичный ответ: сборочный каталог, который забыли исключить
du -sh -- * .[!.]* 2>/dev/null | sort -h | tail -10

Ориентир: на репозитории в 1 млн строк rg укладывается в 100–300 мс на холодном кэше и в десятки миллисекунд на прогретом. Если у вас секунды — ищете по мусору.

Когда git grep лучше

git grep знает про индекс Git и умеет то, чего не умеет обычный поиск по файлам:

# Искать в состоянии на конкретной ревизии, не переключая ветку
git grep -n 'legacy_flag' v2.14.0

# Искать во всех ветках сразу
git grep -n 'legacy_flag' $(git for-each-ref --format='%(refname)' refs/heads)

# Когда это появилось: поиск по истории изменений строки
git log -S 'legacy_flag' --oneline --pickaxe-regex

# Кто и когда трогал строку, игнорируя переформатирование и перемещения кода
git blame -w -C -C -L 120,140 src/billing.py

Пара -S (pickaxe) и git log -L закрывает вопрос «почему тут так написано» лучше любого семантического индекса: код не объясняет намерение, а коммит и связанный с ним PR — часто объясняют. Подробнее про эти инструменты — в статьях ежедневного цикла Git.

Интеграция в редакторы

" Vim/Neovim: подменяем grep на rg, чтобы :grep наполнял quickfix
set grepprg=rg\ --vimgrep\ --smart-case
set grepformat=%f:%l:%c:%m
" дальше работает знакомый конвейер quickfix из статьи про продвинутый Vim
// VS Code: settings.json — исключения для поиска и для наблюдателя за ФС
{
  "search.exclude": {
    "**/node_modules": true,
    "**/dist": true,
    "**/*.min.js": true,
    "**/__snapshots__": true
  },
  "search.followSymlinks": false,
  "files.watcherExclude": {
    "**/target/**": true,
    "**/node_modules/**": true
  }
}

Разница между тремя настройками VS Code, которую регулярно путают: files.exclude прячет файлы из дерева проекта, search.exclude убирает их из поиска (наследует files.exclude), files.watcherExclude снимает нагрузку с механизма слежения за файловой системой — именно он чаще всего съедает процессор на больших монорепозиториях. В JetBrains тот же эффект даёт «Mark Directory as → Excluded» и настройка Scopes, о чём подробнее было в статье про JetBrains.

Предел слоя: почему grep-driven рефакторинг опасен

Классический сценарий: нужно переименовать метод run в одном классе. Текстовый поиск даёт 4000 совпадений, из которых нужны 12. Обратная ошибка страшнее: поиск по имени поля id находит десятки тысяч мест и создаёт иллюзию, что «переименовать невозможно», хотя семантический слой отвечает за 300 мс и показывает ровно 40 использований.

Текст не различает: одноимённые методы разных классов, строки в комментариях, совпадения в тестовых данных, имена в чужих языках внутри одного репозитория. Поэтому правило простое: текстовый поиск — чтобы найти, семантический — чтобы менять.

Слой 2: теги — переносимый индекс имён

Тегам сорок лет, и они регулярно оказываются единственным работающим инструментом там, где LSP не заводится. Идея проста: пройти по исходникам примитивным парсером и построить плоский файл «имя → файл → место».

# Установка: universal-ctags (не путать с древним exuberant-ctags)
ctags --version | head -1

# Генерация индекса по проекту
ctags -R --exclude=node_modules --exclude=.git --exclude='*.min.js' .

# Только нужные языки и с полями, которые понимает Vim
ctags -R --languages=Python,Go,TypeScript --fields=+n --extras=+q .

# Обновление в фоне после каждого переключения ветки: .git/hooks/post-checkout
#   #!/bin/sh
#   ctags -R -f .git/tags.new . && mv .git/tags.new .git/tags

Формат файла tags — текст, отсортированный, с поиском бинарным перебором:

chargeCard	src/billing/gateway.go	/^func chargeCard(ctx context.Context/;"	f
ChargeError	src/billing/errors.go	/^type ChargeError struct/;"	t

Ограничение принципиальное: теги знают только объявления. Вопрос «кто вызывает» они не закрывают. Его решает GNU GLOBAL, который строит двусторонний индекс, и старый cscope для C:

# GNU GLOBAL: индекс определений и ссылок
gtags                     # построить
global -x chargeCard      # где определено
global -rx chargeCard     # кто ссылается
global -gx 'TODO.*billing' # grep по индексу

# Инкрементальное обновление (быстрее полной пересборки)
global -u

Где теги в 2026 году по-прежнему правильный выбор:

  • Языки без живого языкового сервера: внутренние DSL, Tcl, старый Perl, ассемблер, конфигурационные форматы с собственным синтаксисом.
  • Огромные монорепозитории, где языковой сервер не выдерживает: индекс тегов линеен и строится за минуты даже на десятках миллионов строк.
  • Работа по SSH на стенде, где нельзя ставить тулчейн: ctags есть в любом дистрибутиве, весит мегабайт и не требует компилятора.
  • Смешанные репозитории: один индекс на Python, Go, SQL-миграции и shell-скрипты, тогда как LSP пришлось бы поднимать четыре.
" Vim: переход по тегу и стек возвратов
" Ctrl-]      — перейти к определению под курсором
" Ctrl-t      — вернуться назад по стеку тегов
" :tselect    — выбрать из нескольких одноимённых
" g Ctrl-]    — сразу список, если совпадений больше одного
set tags=./tags,tags,.git/tags

В Emacs ту же роль играет etags и универсальный интерфейс xref (M-. / M-,), причём xref одинаково работает поверх тегов и поверх LSP — редкий пример честной абстракции над обоими слоями.

Слой 1.5: структурный поиск

Между «текст» и «полная семантика» есть промежуточный слой: поиск по синтаксическому дереву. Он не знает типов, но знает, что такое вызов функции, аргумент и блок.

# ast-grep: найти пустые catch-блоки в TypeScript
ast-grep --pattern 'try { $$$ } catch ($E) { }' --lang ts

# Переписать assertEquals в assertThat по всему проекту
ast-grep --pattern 'assertEquals($A, $B)' \
         --rewrite 'assertThat($B).isEqualTo($A)' --lang java --update-all

# comby: то же самое на языке шаблонов, работает на десятках языков
comby 'assertEquals(:[a], :[b])' 'assertThat(:[b]).isEqualTo(:[a])' .java -i

# semgrep: правила как код, удобно вешать в CI
semgrep -e 'requests.get($URL, verify=False)' --lang python .

Структурный поиск закрывает ровно тот класс задач, где текст даёт слишком много шума, а семантика недоступна или слишком дорога: массовые миграции API, поиск антипаттернов, проверки в CI. Semgrep, ast-grep и comby отличаются деталями синтаксиса, но модель одна.

Внутри IDE тот же слой называется Structural Search and Replace — он разобран в статье про JetBrains; а о том, как устроены парсеры, дающие такие деревья, — в статье про инструменты языка.

Слой 3: семантика через LSP

Языковой сервер отвечает не на вопрос «где встречается строка», а на вопрос «что это за символ и где он ещё используется». Методы протокола, которые стоит знать по именам, потому что в разных редакторах они называются по-разному, а в логах — одинаково:

Метод LSP Что спрашивают руками Типичная привязка
textDocument/definition «где определено» gd, F12, Ctrl+B
textDocument/typeDefinition «какого это типа» gy, Ctrl+Shift+B
textDocument/implementation «кто реализует интерфейс» gI, Ctrl+Alt+B
textDocument/references «кто использует» gr, Shift+F12, Alt+F7
textDocument/documentSymbol структура текущего файла Ctrl+Shift+O, Ctrl+F12
workspace/symbol «найти класс/функцию по имени» Ctrl+T, Ctrl+N
callHierarchy/incomingCalls «цепочка вызовов сюда» Ctrl+Alt+H
typeHierarchy/supertypes «иерархия наследования» Ctrl+H

Где семантика врёт или молчит

Языковой сервер видит только то, что выражено в языке. Он не видит связей, созданных:

  • рефлексией и внедрением зависимостей (Spring, декораторы Python, DI-контейнеры JS);
  • строковыми идентификаторами: имена маршрутов, ключи конфигов, названия очередей, имена таблиц и колонок в SQL внутри строк;
  • шаблонизаторами и генерацией кода: .proto, OpenAPI, ORM-миграции;
  • динамическими обращениями getattr(obj, name), obj[method]().

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

LSP на большом репозитории

// Пример: ограничиваем корни проектов, чтобы сервер не индексировал весь монорепо
// gopls (VS Code / Neovim)
{ "gopls": { "build.directoryFilters": ["-node_modules", "-vendor", "-third_party"] } }
-- rust-analyzer: явный список Cargo-проектов вместо автопоиска по всему дереву
require('lspconfig').rust_analyzer.setup({
  settings = {
    ['rust-analyzer'] = {
      linkedProjects = { 'services/billing/Cargo.toml', 'libs/core/Cargo.toml' },
      cargo = { allFeatures = false },
      -- проверка кластера крейтов при сохранении — самая дорогая операция
      checkOnSave = { command = 'clippy', extraArgs = { '--no-deps' } },
    },
  },
})

Для TypeScript тот же приём — references в tsconfig.json и project references вместо одного гигантского проекта. Практика такая же, как с индексами в базе данных: индексировать всё дорого, индексировать нужное — дёшево. Про специфику больших репозиториев в целом — монорепозитории в треке Git.

Масштаб: когда локальные инструменты кончаются

Порядок величин, на который стоит ориентироваться:

Размер Что работает Что перестаёт работать
до 1 млн строк rg, LSP, IDE — всё ничего
1–10 млн строк rg с исключениями, теги, LSP с ограниченными корнями полная индексация IDE «всего сразу»
десятки млн строк, один репозиторий теги, серверный индекс, частичный чекаут локальный LSP на весь репозиторий
тысячи репозиториев только серверный индекс всё локальное

Серверные системы поиска строятся на триграммном индексе: для каждого трёхсимвольного фрагмента хранится список документов, где он встречается. Регулярное выражение транслируется в булев запрос по триграммам, который сначала отсекает 99,9 % документов, а полный автомат запускается только на оставшихся. Классическое объяснение — статья Расса Кокса «Regular Expression Matching with a Trigram Index», описывающая устройство Google Code Search. На этой же идее построены Zoekt, livegrep и Hound; GitHub описал свою реализацию в статье «The technology behind GitHub’s new code search».

Цена такого индекса — диск (обычно от 20 до 50 % от размера исходников) и свежесть: индекс всегда отстаёт от HEAD на минуты или часы. Для «где в компании ещё используют этот устаревший метод» это приемлемо; для «что сломает мой текущий коммит» — нет.

Fuzzy-поиск и путь чтения

Отдельный класс инструментов отвечает не на вопрос «где», а на вопрос «дай мне быстро добраться туда, где я уже был или про что я примерно помню».

# fzf: интерактивный фильтр над любым списком
# Ctrl-T — файлы, Ctrl-R — история команд, Alt-C — переход по каталогам

# Живой поиск по содержимому: перезапуск rg на каждое нажатие
INITIAL='rg --column --line-number --no-heading --color=always --smart-case'
fzf --ansi --disabled --query '' \
    --bind "start:reload:$INITIAL {q}" \
    --bind "change:reload:sleep 0.05; $INITIAL {q} || true" \
    --delimiter : \
    --preview 'bat --color=always --highlight-line {2} {1}' \
    --preview-window 'up,60%,+{2}/2'

Механика скоринга у fzf и его аналогов одинакова по смыслу: за совпадение в начале слова, после разделителя или на границе CamelCase даётся бонус, за разрывы — штраф, за совпадение в имени файла — бонус больше, чем за совпадение в пути. Отсюда практический приём: набирайте не подстроку, а «скелет» — usrepo находит user/repository.py надёжнее, чем repo.

Не менее важен путь чтения — механизм возврата туда, где вы были:

Механизм Vim/Neovim VS Code JetBrains Emacs
Назад по переходам Ctrl-o / Ctrl-i Alt+← / Alt+→ Ctrl+Alt+← M-, / mark ring
Последние файлы :browse oldfiles Ctrl+Tab (MRU) Ctrl+E recentf
Места последних правок g; / g, Ctrl+K Ctrl+Q Ctrl+Shift+Backspace goto-last-change
Именованные закладки marks ma / `a Bookmarks Ctrl+F11 registers
Быстрый набор рабочих файлов harpoon Pinned tabs Favorites bookmark+

Если из всей статьи вы возьмёте одну привычку — пусть это будет «назад по переходам». Ощущение «потерялся в чужом коде» почти всегда означает, что человек не умеет вернуться.

Протокол чтения незнакомого репозитория за 90 минут

# 1. Периметр: масштаб и языки
tokei                                   # сколько кода и на чём
ls -1 | head -30                        # верхний уровень как оглавление
fd -H -d 2 -e md                        # где документация, ADR, RFC

# 2. Форма: точки входа. Ищем не «главный файл», а границы.
rg -l 'func main\(|if __name__|public static void main|createApp|FastAPI\(' 
fd -e Dockerfile -e docker-compose.yml -e Makefile -e justfile
rg -n 'route|@app\.|@RestController|http\.HandleFunc' -g '!**/test/**' | head -40

# 3. История: где сосредоточена активность за год (hotspots)
git log --since='1 year ago' --pretty=format: --name-only \
  | sed '/^$/d' | sort | uniq -c | sort -rn | head -20

# кто в теме — к кому идти с вопросами по этому модулю
git shortlog -sne --since='1 year ago' -- src/billing | head

# 4. Срез: один сценарий сверху вниз. Начинаем с теста, а не с кода.
rg -l 'billing|payment' --glob '**/test*/**' | head

Порядок здесь не случаен. Чтение «слева направо по алфавиту каталогов» проваливается всегда. Работает противоположное: сначала внешние границы (что система принимает и что отдаёт), потом один вертикальный срез — путь конкретного запроса от входа до базы данных, — и только потом вширь. Отладчик в роли навигатора недооценён: один прогон реального сценария с точкой останова даёт более точную карту вызовов, чем полчаса чтения, потому что показывает фактический путь, а не возможный.

Заметки на этом этапе — не бюрократия, а инструмент: через два дня вы забудете 70 % того, что поняли сегодня. Механику внешней памяти подробно разбирает трек про обучение — заметки и внешняя память и как учиться по чужому коду.

Бюджеты и диагностика медленного поиска

Ориентиры, при которых навигация не разрушает поток мышления:

Операция Приемлемо Плохо
Поиск по проекту (текст) до 300 мс больше 1 с
Открытие файла по имени (fuzzy) до 100 мс больше 500 мс
workspace/symbol до 200 мс больше 1 с
Go to definition до 150 мс больше 500 мс
Find references на среднем символе до 1 с больше 5 с

Чек-лист, когда медленно:

  1. Мусор в дереве. du -sh */ | sort -h | tail — и в исключения: node_modules, target, build, dist, .venv, vendor, снапшоты, сгенерированные клиенты.
  2. Гигантские файлы. fd -S +5m находит пятимегабайтные JSON и минифицированный JS, на которых умирает и подсветка, и поиск.
  3. Сетевая или виртуализованная ФС. Поиск по SMB/NFS или по смонтированному тому Docker на macOS медленнее локального на порядок; лечится переносом дерева внутрь виртуальной машины или контейнера — механика описана в статье про VS Code и devcontainers.
  4. Антивирус и системный индексатор. Внесите каталоги проектов в исключения Defender, Spotlight и аналогов — на Windows это регулярно даёт двукратную разницу.
  5. Watchers. На больших деревьях следящий за файлами процесс съедает CPU и упирается в лимит inotify: sysctl fs.inotify.max_user_watches на Linux.
  6. Языковой сервер индексирует лишнее. Ограничьте корни проектов, отключите анализ зависимостей, где он не нужен.

Типичные ошибки

  • Начинать с самого умного слоя. Ждать, пока IDE проиндексирует 40 миллионов строк, чтобы найти строковый литерал, который rg нашёл бы за 200 мс.
  • Менять код по результатам текстового поиска. Работает до первого омонима; на переименовании поля id заканчивается инцидентом.
  • Забывать про второй проход. Семантический рефакторинг не трогает строковые идентификаторы, конфиги, шаблоны и SQL — их надо искать текстом отдельно.
  • Не исключать сгенерированный код. Половина совпадений в мёртвых файлах — и поиск бесполезен, и внимание израсходовано.
  • Искать в вебе то, что есть локально. Веб-интерфейс репозитория удобен для ссылки коллеге, но по скорости проигрывает локальному поиску на порядок.
  • Не уметь возвращаться. Без стека переходов человек боится «уйти далеко» и читает поверхностно.
  • Полагаться на индекс, который отстал. Серверный поиск показывает состояние многочасовой давности; для проверки собственного изменения он не годится.
  • Настраивать fuzzy-поиск и не настраивать исключения. Красивый интерфейс поверх перебора node_modules остаётся перебором node_modules.

Мини-итог

  • Навигация — не настройка редактора, а отдельный навык, который переносится между инструментами целиком и определяет скорость работы больше, чем скорость набора.
  • Слоёв три: текст (полнота, ноль подготовки, шум), теги (имена, дёшево, переносимо), семантика (точность, требует рабочего проекта). Плюс структурный поиск между первым и третьим.
  • Скорость текстового поиска — это на 80 % то, сколько байт вы не читаете: исключения важнее выбора инструмента.
  • Теги не устарели: языки без LSP, монорепозитории, работа по SSH и смешанные репозитории — их территория.
  • Семантика не видит рефлексию, DI, строковые идентификаторы и шаблоны, поэтому любой массовый рефакторинг делается в два прохода.
  • За пределами одного репозитория работает только серверный триграммный индекс — с ценой в виде диска и отставания от HEAD.
  • Умение вернуться назад по стеку переходов даёт больше, чем любой плагин поиска.
  • Незнакомый проект читается вертикальным срезом одного сценария, а не обходом каталогов по алфавиту.

Источники

Что дальше

Мы научились быстро находить нужное место. Следующий естественный шаг — перестать делать руками то, что нашлось: превратить повторяющуюся последовательность действий в сниппет, задачу или собственную команду редактора. В следующей статье — лестница автоматизации от макроса до собственного расширения, честный расчёт окупаемости и правило, по которому логика живёт в CLI, а редактор остаётся тонкой обёрткой.

Автоматизация редактора: сниппеты, задачи, свои команды и плагины

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

Статья описывает общий случай. Если у вас частный — можно разобрать его отдельно, платно. А если не хватает целого материала, предложите тему: её оплачивают вскладчину, и она выходит открытой для всех.

Доска запросов
Дальше