Quickfix и location list: пакетная работа по всему проекту

Рейтинг: 72.2% · 10 голосов
Исчерпывающий курс по Vim и Neovim: модальное редактирование и грамматика (операторы, движения, текстовые объекты, точка-команда), регистры, метки, макросы, поиск и :substitute, :global, буферы/окна/quickfix, визуальный режим, конфигурация на Lua, маппинги, плагины (lazy.nvim), LSP, автодополнение (blink.cmp), Treesitter, fuzzy-поиск, Git, эргономика и антипаттерны. 34 главы, актуально на 2026 (Vim 9.1, Neovim 0.11/0.12).
Ответить
Аватара пользователя
Roman_Vim
Сообщения: 34
Зарегистрирован: 11 май 2026, 05:31

Quickfix и location list: пакетная работа по всему проекту

Сообщение Roman_Vim »

Оглавление курса (34)
  1. Философия Vim: модальное редактирование и почему это актуально в 2026
  2. Установка и выживание: первый запуск, режимы, как выйти
  3. Перемещение по тексту: движения как основа эффективности
  4. Базовое редактирование: вставка, удаление, копирование
  5. Грамматика Vim: операторы + движения = команды
  6. Текстовые объекты: оперируем структурой, а не символами
  7. Точка-команда и повторяемость: "один штрих, чтобы повторить"
  8. Регистры: продвинутая работа с буфером обмена
  9. Метки и прыжки: навигация по истории перемещений
  10. Поиск: /, ?, *, # и магия паттернов
  11. Замена :substitute и регулярные выражения Vim
  12. Команда :global - оружие массового редактирования
  13. Буферы, окна и вкладки: организация рабочего пространства
  14. Quickfix и location list: пакетная работа по всему проекту (вы здесь)
  15. Навигация по файлам: netrw, fuzzy-поиск и проекты
  16. Макросы: автоматизация повторяющихся правок
  17. Визуальный режим и блочное редактирование
  18. Трюки режима вставки
  19. Свёртки, автодополнение и аббревиатуры (встроенные)
  20. Конфигурация с нуля: init.lua и разумные дефолты 2026
  21. Маппинги: leader, which-key и здравые привычки
  22. Скриптинг: основы Vimscript и Lua для Neovim
  23. Менеджеры плагинов: lazy.nvim и lazy-loading
  24. Neovim в 2026: чем отличается и почему выбирают
  25. LSP в Neovim: умные возможности языка
  26. Автодополнение 2026: blink.cmp против nvim-cmp
  27. Treesitter: точная подсветка и структурное редактирование
  28. Fuzzy-поиск: Telescope, fzf-lua и snacks.picker
  29. Git внутри редактора: fugitive, gitsigns и lazygit
  30. Дистрибутивы, отладка и терминал: быстрый старт и интеграции
  31. Vim повсюду: IDE, браузер и shell
  32. Эргономика и антипаттерны: как не навредить себе и не застрять
  33. Рабочие рецепты, тренажёры и план обучения
  34. Шпаргалка-справочник: все ключевые команды одним списком
В предыдущих главах мы научились искать и заменять текст внутри одного буфера: / и * для поиска (глава 10), :substitute для замены (глава 11), :global как движок пакетного редактирования (глава 12). Но настоящий проект - это десятки и сотни файлов. Что делать, когда нужно переименовать функцию во всём репозитории? Пройтись по всем ошибкам компилятора? Найти все TODO и обработать их по очереди, ничего не пропустив?

Ответ Vim на этот вопрос - quickfix list и location list. Это структурированные списки позиций "файл:строка:колонка", по которым можно прыгать одной клавишей и применять команды массово. Quickfix - едва ли не самый недооценённый механизм Vim: освоив его, вы перестанете открывать файлы вручную и начнёте обрабатывать результаты поиска как конвейер. Именно здесь связываются воедино внешние инструменты (ripgrep, компиляторы, линтеры) и редактор.

Зачем это нужно

Представьте задачу: переименовать getUserData в fetchUserData по всему проекту. Открывать каждый файл, искать, заменять, сохранять - это десятки ручных действий и гарантированный риск что-то пропустить. С quickfix задача сводится к двум командам: наполнить список всеми вхождениями и прогнать по нему массовую замену. Весь проект обрабатывается за секунды, причём детерминированно.

Quickfix - это не "ещё одно окно". Это рабочая память для пакетных операций: компилятор, grep, линтер и LSP складывают туда свои результаты в едином формате, а вы единообразно по ним навигируете и редактируете.

Quickfix list против location list

Это два почти идентичных механизма, и важно понимать разницу с самого начала.

Quickfix list - один на всю сессию Vim (точнее, глобальный для всего инстанса). Его наполняют команды вроде :make, :grep, :vimgrep. Логика проста: список один, и любая команда, кладущая в него результаты, перезаписывает предыдущий.

Location list - привязан к конкретному окну. У каждого окна свой location list. Команды с префиксом l (:lmake, :lgrep, :lvimgrep) работают именно с ним.

Почему два механизма? Потому что у них разные жизненные циклы. Quickfix хорош для "глобальной задачи": ошибки сборки всего проекта, результаты grep по репозиторию. Location list хорош для "локальной задачи, привязанной к окну": например, LSP-диагностика конкретного буфера или список вхождений в одном файле. Можно держать оба одновременно: в одном окне location list с диагностикой текущего файла, а quickfix - с результатами проектного поиска.

Практическое правило: по умолчанию используйте quickfix, а location list берите, когда нужно несколько независимых списков одновременно или когда список логически принадлежит окну. Все команды quickfix имеют зеркальный location-аналог с буквой l в начале.

Код: Выделить всё

Назначение                   |  Quickfix           |  Location list
-----------------------------+---------------------+-------------------
Наполнить из make            |  :make              |  :lmake
Наполнить из grep            |  :grep              |  :lgrep
Внутренний grep              |  :vimgrep           |  :lvimgrep
Открыть окно                 |  :copen             |  :lopen
Закрыть окно                 |  :cclose            |  :lclose
Следующая запись             |  :cnext (:cn)       |  :lnext (:lne)
Предыдущая                   |  :cprevious (:cp)   |  :lprevious (:lp)
Первая / последняя           |  :cfirst / :clast   |  :lfirst / :llast
Команда на каждую запись     |  :cdo               |  :ldo
Команда на каждый файл       |  :cfdo              |  :lfdo
Предыдущий/следующий список  |  :colder / :cnewer  |  :lolder / :lnewer
:make и errorformat

Команда :make запускает программу, заданную опцией 'makeprg' (по умолчанию make), перехватывает её вывод и парсит его в quickfix list. Магия в том, как именно Vim понимает, где в выводе компилятора спрятаны "файл, строка, сообщение". За это отвечает опция 'errorformat'.

'errorformat' - это набор шаблонов в стиле scanf, описывающих формат строк ошибок. Ключевые спецификаторы:

Код: Выделить всё

Спецификатор  |  Значение
--------------+----------------------------------
%f            |  имя файла
%l            |  номер строки
%c            |  номер колонки
%m            |  текст сообщения
%t            |  тип (e/w/i - error/warning/info)
%%            |  литеральный символ %
Например, для типичного вывода компилятора src/main.c:42:7: error: ... подходящий формат:

Код: Выделить всё

set errorformat=%f:%l:%c:\ %t%*[^:]:\ %m
На практике для популярных компиляторов готовые errorformat подключаются автоматически через файлы compiler-плагинов: :compiler gcc, :compiler pyunit и т.д. Вы редко пишете errorformat руками - но понимать, что именно он превращает сырой текст в навигируемый список, важно: если переходы по записям не работают, почти всегда дело в рассогласовании вывода инструмента и errorformat.

Код: Выделить всё

:set makeprg=cargo\ build
:make            " запускаем сборку, ошибки попадают в quickfix
:copen           " смотрим список ошибок
В Neovim LSP-диагностика управляется через Lua API (vim.lsp.enable, vim.diagnostic.*) и попадает в location list/quickfix через vim.diagnostic.setloclist()/setqflist(); ручной возни с :make для большинства языков не нужно (подробнее в главе 25). Но :make/errorformat остаются универсальным механизмом для любых внешних инструментов - тестов, сборщиков, кастомных скриптов.

:grep/:lgrep против :vimgrep/:lvimgrep

Есть два принципиально разных способа наполнить quickfix результатами поиска по файлам.

:vimgrep использует встроенный движок регулярных выражений Vim. Синтаксис паттерна - тот же, что в / (см. главу 10), включая \v (very magic).

Код: Выделить всё

:vimgrep /\vfetchUser/ **/*.js
Двойная звёздочка ** означает рекурсивный обход подкаталогов. Плюс :vimgrep - единый синтаксис регулярок с остальным Vim и независимость от внешних программ. Минус - он заметно медленнее внешних инструментов на больших репозиториях, потому что реально открывает каждый файл во внутренний буфер.

Полезные флаги: :vimgrep /pat/gj **/*.py. Здесь g означает "все совпадения в строке" (а не только первое), а j (jump) - "не прыгать сразу на первый результат", что критично для пакетной работы: вы хотите сначала собрать список, а потом решать. Есть и флаг f - fuzzy matching (нечёткий поиск вместо regex).

Важная механика g-флага: с ним :vimgrep добавляет в quickfix несколько записей с одной строки (по записи на каждое совпадение). Именно поэтому при последующем массовом редактировании для замены s///g берут :cfdo (раз на файл), а не :cdo: иначе :cdo повторно обработает одну и ту же строку столько раз, сколько на ней совпадений (подробнее про :cdo/:cfdo ниже).

:grep запускает внешнюю программу, заданную опцией 'grepprg' (по умолчанию системный grep), и парсит её вывод через 'grepformat' - ровно как :make через errorformat. Это быстрее на порядки и позволяет подключить современные инструменты вроде ripgrep.

Код: Выделить всё

Критерий             |  :vimgrep        |  :grep
---------------------+------------------+-------------------------------------------------------------------------------------------------------------
Движок regex         |  встроенный Vim  |  движок внешней программы (по умолчанию у rg - Rust regex, без lookaround/backref; PCRE2 только с флагом -P)
Скорость             |  медленно        |  очень быстро
Зависимость          |  нет             |  внешний бинарник
Уважение .gitignore  |  нет             |  да (у ripgrep)
Синтаксис паттерна   |  как в /         |  как у внешней программы
Правило 2026 года: для проектного поиска используйте :grep с настроенным ripgrep, а :vimgrep оставьте для случаев, когда нужен именно Vim-синтаксис регулярок или нет возможности поставить внешний инструмент.

Настройка grepprg на ripgrep

ripgrep (rg) - самый быстрый CLI-поисковик, по умолчанию уважающий .gitignore и поддерживающий smart-case. Чтобы Vim корректно парсил его вывод, нужны согласованные grepprg и grepformat. Ключ - флаг --vimgrep, который выдаёт строки в формате file:line:col:text.

Для Vim (в .vimrc):

Код: Выделить всё

set grepprg=rg\ --vimgrep\ --no-heading\ --smart-case
set grepformat=%f:%l:%c:%m
Для Neovim (в init.lua):

Код: Выделить всё

vim.o.grepprg = 'rg --vimgrep --no-heading --smart-case'
vim.o.grepformat = '%f:%l:%c:%m'
Здесь:
  • --vimgrep - формат file:line:col:text, который точно парсится grepformat;
  • --no-heading - не группировать результаты по файлам (нужен плоский формат);
  • --smart-case - регистронезависимо, если паттерн в нижнем регистре, иначе с учётом регистра.
Теперь :grep PATTERN будет молниеносным. Важная деталь: запускайте :grep! с восклицательным знаком, чтобы Vim не прыгал автоматически на первый результат. Для пакетной работы это почти всегда то, что нужно: сначала собрать список и открыть его через :copen, потом обрабатывать.

Код: Выделить всё

:grep! 'TODO' src/
:copen
Альтернатива без правки grepprg - наполнить quickfix напрямую из вывода команды через :cexpr:

Код: Выделить всё

:cexpr systemlist('rg --vimgrep TODO')
:cexpr берёт список строк (здесь - вывод rg через systemlist()) и парсит его через errorformat в quickfix, без временных файлов и без изменения глобальных опций. В скриптах для безопасной подстановки спецсимволов используйте shellescape().

Навигация по quickfix

Список собран - теперь по нему нужно ходить. Базовый набор команд:

Код: Выделить всё

Команда     |  Сокращение  |  Действие
------------+--------------+-----------------------------
:copen      |  :cope       |  открыть окно quickfix
:cclose     |  :ccl        |  закрыть окно quickfix
:cnext      |  :cn         |  перейти к следующей записи
:cprevious  |  :cp / :cN   |  перейти к предыдущей записи
:cfirst     |  :cfir       |  первая запись
:clast      |  :cla        |  последняя запись
:cc [N]     |              |  перейти к записи номер N
:colder     |  :col        |  предыдущая версия списка
:cnewer     |  :cnew       |  следующая версия списка
В окне quickfix (после :copen) достаточно навести курсор на строку и нажать Enter - Vim откроет нужный файл на нужной строке. Для location list - те же команды с l: :lopen, :lnext, :lprev и так далее.

Голые :cnext/:cprev неудобно набирать в цикле, поэтому почти все настраивают маппинги. Классический вариант (см. главу 21 о маппингах):

Код: Выделить всё

vim.keymap.set('n', ']q', '<cmd>cnext<cr>', { desc = 'Quickfix: следующая' })
vim.keymap.set('n', '[q', '<cmd>cprevious<cr>', { desc = 'Quickfix: предыдущая' })
vim.keymap.set('n', '<leader>co', '<cmd>copen<cr>', { desc = 'Quickfix: открыть' })
vim.keymap.set('n', '<leader>cc', '<cmd>cclose<cr>', { desc = 'Quickfix: закрыть' })
Стиль ]q/[q (следующий/предыдущий) - устоявшаяся в экосистеме конвенция (его популяризировал vim-unimpaired).

:colder и :cnewer - недооценённая пара. Vim хранит историю из последних quickfix-списков (по умолчанию 10, настраивается опцией 'chistory'; для location list - 'lhistory'). Сделали :grep по одному паттерну, потом по другому - первый список не потерян. :colder ("older") возвращает к предыдущему, :cnewer - вперёд. Это спасает, когда вы случайно перезаписали важный результат поиска новым.

'switchbuf': как открываются записи

Частый источник путаницы при пакетной работе - как именно :cnext/Enter в окне quickfix открывают запись: в текущем окне, в существующем, в новом сплите? Этим управляет опция 'switchbuf'. Значения комбинируются через запятую:
  • useopen - если буфер уже открыт в каком-то окне, перейти в него, а не загружать поверх текущего;
  • uselast - использовать последнее использованное окно (удобно с открытым окном quickfix снизу);
  • split / vsplit - открывать запись в новом гори-/вертикальном сплите;
  • newtab - в новой вкладке.
Типичная связка, чтобы quickfix-навигация не "съедала" текущий буфер:

Код: Выделить всё

set switchbuf=useopen,uselast
Без этого :cnext будет грузить каждый следующий файл прямо в окно, где вы стоите, - и легко потерять контекст.

Авто-открытие quickfix через QuickFixCmdPost

Вокруг наполнения списка Vim генерирует события QuickFixCmdPre и QuickFixCmdPost (для :make, :grep, :vimgrep, :cexpr и т.п.). Стандартный приём - повесить на QuickFixCmdPost авто-открытие окна, чтобы :make/:grep сами показывали результат:

Код: Выделить всё

augroup AutoQuickfix
  autocmd!
  autocmd QuickFixCmdPost [^l]* cwindow   " после :grep/:make - открыть quickfix, если есть записи
  autocmd QuickFixCmdPost l*    lwindow   " после :lgrep/:lmake - открыть location list
augroup END
:cwindow (в отличие от :copen) открывает окно, только если в списке есть записи, и закрывает, если их нет, - поэтому он удобен в автокомандах. Шаблон [^l]* ловит quickfix-команды, l* - их location-аналоги.

Массовое редактирование: :cdo, :cfdo, :ldo, :lfdo

Вот ради чего всё затевалось. Эти команды выполняют произвольную Ex-команду по всем записям quickfix/location list.
  • :cdo {cmd} - выполняет {cmd} на каждой записи списка (один раз на каждое совпадение).
  • :cfdo {cmd} - выполняет {cmd} на каждом файле из списка (один раз на файл, независимо от числа совпадений в нём).
  • :ldo / :lfdo - то же самое для location list.
Это самое важное различие в главе. Когда вы делаете замену с глобальным флагом s/old/new/g, она и так обрабатывает все вхождения в файле за один проход. Поэтому здесь нужен :cfdo (раз на файл), а не :cdo (раз на запись) - иначе файл с пятью совпадениями будет обработан пять раз подряд, что в лучшем случае бессмысленно, а в худшем приведёт к повторным заменам.

Откуда вообще берётся "пять записей в одном файле"? Здесь смыкаются две вещи. Во-первых, :grep/ripgrep и :vimgrep с флагом g кладут в quickfix отдельную запись на каждое совпадение, а не на строку: две совпавшие подстроки на одной строке - это две записи (а с g даже на одной строке их может быть несколько). Во-вторых, :cdo идёт именно по записям. Значит, на строке с двумя совпадениями :cdo встанет дважды - и s///g, который уже в первый проход заменил оба, во второй проход либо ничего не найдёт (отсюда обязательный флаг e), либо, если паттерн пересекается с заменой, испортит текст. :cfdo же берёт каждый файл ровно один раз - это и есть правильная гранулярность для s///g.

Используйте :cdo, когда команда привязана к конкретной строке записи (например, что-то сделать именно на строке совпадения). Используйте :cfdo, когда команда сама обходит весь файл (:substitute с g, :global, :normal по диапазону).

Ещё две критичные детали:
  1. :cdo/:cfdo НЕ сохраняют файлы автоматически. Изменения остаются в незаписанных буферах. Добавляйте | update в саму команду (сохранит только реально изменённые буферы) либо выполните :wall после.
  1. Без ! команда упадёт на изменённом несохранённом буфере, который нельзя оставить. И флаг e в s///e подавляет ошибку E486: Pattern not found - без него :cfdo прервётся на первом файле, где паттерн не нашёлся.
Рецепт: "найти по проекту -> массовая :s через :cfdo"

Это канонический воркфлоу проектного рефакторинга. Разберём по шагам на задаче "переименовать getUserData в fetchUserData".

Шаг 1. Наполняем quickfix всеми вхождениями (:grep! с !, чтобы не прыгать):

Код: Выделить всё

:grep! 'getUserData'
Шаг 2. Смотрим, что нашлось, и при необходимости сужаем (об этом ниже):
Шаг 3. Прогоняем замену по всем файлам и сразу сохраняем:

Код: Выделить всё

:cfdo s/getUserData/fetchUserData/ge | update
Разберём команду по частям:
  • :cfdo - раз на файл (не на запись!);
  • s/.../.../g - заменить все вхождения в файле;
  • e - не падать, если в файле паттерна нет (бывает при пограничных случаях парсинга);
  • | update - сохранить буфер, если он изменился.
Было (фрагменты в разных файлах):

Код: Выделить всё

src/api.js:12:  const data = getUserData(id);
src/profile.js:8:  return getUserData(user.id);
Нажимаем :grep! 'getUserData' -> :cfdo s/getUserData/fetchUserData/ge | update.

Стало:

Код: Выделить всё

src/api.js:12:  const data = fetchUserData(id);
src/profile.js:8:  return fetchUserData(user.id);
Весь проект - одной командой. Если хотите осторожности, добавьте флаг c (confirm): s/getUserData/fetchUserData/gce - Vim спросит подтверждение на каждую замену.

Сужение списка через :Cfilter

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

Код: Выделить всё

:packadd cfilter
:Cfilter /pattern/
:Cfilter /pat/ оставляет в quickfix только записи, совпадающие с /pat/ (по имени файла и тексту строки). :Cfilter! /pat/ - инвертирует, выбрасывая совпадающие. Для location list есть :Lfilter. Это безопаснее, чем чистить список руками. Пустой паттерн использует последний поисковый шаблон.

Код: Выделить всё

:grep! 'getUserData'
:packadd cfilter
:Cfilter! /test/      " выбросить вхождения в тестах
:cfdo s/getUserData/fetchUserData/ge | update
:cdo с макросом или :normal

Для нетекстовых замен (когда :substitute недостаточно) :cdo отлично сочетается с :normal и макросами (главы 12 и 16):

Код: Выделить всё

:grep! 'console.log'
:cdo normal! dd | update     " удалить каждую строку с отладочным выводом
Здесь уместен именно :cdo (раз на запись), потому что dd удаляет конкретную строку записи. Будьте осторожны: удаление строк сдвигает нумерацию, и записи того же файла могут "уехать". Для удаления строк надёжнее :cfdo g/console\.log/d (:global внутри каждого файла).

Quickfix в Neovim: словарный API и quickfixtextfunc

До сих пор мы наполняли список командами (:grep, :vimgrep, :cexpr). Но quickfix можно собрать и программно - это особенно удобно в Neovim, где список естественно строится из Lua. Базовый инструмент общий с Vim - функция setqflist() (и setloclist() для location list), которой передают список словарей с богатыми полями:

Код: Выделить всё

vim.fn.setqflist({
  { filename = 'src/api.js',     lnum = 12, col = 17, text = 'getUserData here' },
  { filename = 'src/profile.js', lnum = 8,  col = 10, text = 'and here', type = 'W' },
}, ' ')   -- ' ' создаёт новый список, 'a' - добавляет, 'r' - заменяет текущий
Поля filename/lnum/col/text/type (E/W/I), а также bufnr, end_lnum, end_col, valid дают полный контроль над записью - куда больше, чем парсинг строки через errorformat. Прочитать список обратно: vim.fn.getqflist() (с { what = ... } можно вытащить заголовок, id, размер истории и пр.).

В Neovim поверх этого есть собственный диагностический мост: vim.diagnostic.setqflist() и vim.diagnostic.setloclist() сами собирают LSP-диагностику в quickfix/location list - это и есть "правильный" способ получить ошибки линтеров и языковых серверов вместо :make. Отсюда и упомянутое в начале правило: проектные ошибки сборки - в quickfix, диагностика текущего буфера - в location list (setloclist).

quickfixtextfunc - кастомное форматирование окна. По умолчанию строка в окне quickfix выглядит как file|line col| text. Опция 'quickfixtextfunc' позволяет задать функцию, которая полностью определяет, как рендерится каждая строка списка, - например, скрыть путь, выровнять колонки, добавить иконку типа:

Код: Выделить всё

vim.o.quickfixtextfunc = 'v:lua.MyQfText'
function MyQfText(info)
  local items = vim.fn.getqflist({ id = info.id, items = 0 }).items
  local lines = {}
  for i = info.start_idx, info.end_idx do
    local it = items[i]
    local name = vim.fn.bufname(it.bufnr)
    lines[#lines + 1] = string.format('%s:%d  %s', vim.fn.fnamemodify(name, ':t'), it.lnum, it.text)
  end
  return lines
end
Именно на quickfixtextfunc и словарном API построены плагины вроде nvim-bqf - так что, понимая этот слой, вы понимаете, как они устроены изнутри.

Редактируемый quickfix: quickfix-reflector и современные плагины

Стандартное окно quickfix доступно только для чтения. Идея quickfix-reflector (stefandtw/quickfix-reflector.vim) проста и мощна: оно делает окно quickfix редактируемым. Вы открываете :copen, правите текст совпадений прямо в окне списка как обычный буфер, сохраняете через :w - и плагин разносит изменения обратно по исходным файлам. Это превращает результаты :grep в один редактируемый "срез проекта".

В экосистеме Neovim 2026 года этот подход развился в более мощные инструменты:
  • nvim-bqf (kevinhwang91/nvim-bqf) - улучшенное окно quickfix: превью под курсором, fzf-фильтрация записей, более удобная навигация. Незаменимо, когда :grep вернул сотни результатов и их надо просмотреть перед :cfdo.
  • grug-far.nvim (MagicDuck/grug-far.nvim) - де-факто стандарт интерактивного find & replace в Neovim 2026. Единый движок (ripgrep по умолчанию, опционально ast-grep для структурного поиска) и для поиска, и для замены; свободное редактирование результатов с синком обратно в файлы; интеграция с quickfix. Требует Neovim 0.11+ и ripgrep 14+. Для многих сценариев grug-far заменяет ручную связку :grep + :cfdo, давая живой предпросмотр замены. Учтите: синк правок в файлы работает только с движком ripgrep и недоступен в multiline-режиме.
Когда что выбирать? Встроенный :grep + :cfdo + :Cfilter - нулевые зависимости, работает везде, идеален в скриптах и на серверах. grug-far - интерактивная работа с предпросмотром, когда замена сложная и хочется видеть результат до применения. Подробнее об интерактивных find & replace инструментах - в контексте плагинов главы 28.

Лучшие практики 2026
  • Настройте grepprg на ripgrep с --vimgrep и согласуйте grepformat=%f:%l:%c:%m. Это база для :grep, :cexpr и многих плагинов. Без --vimgrep (или при рассогласованном grepformat) записи quickfix не парсятся - теряются строка и колонка, переходы ломаются.
  • Используйте :grep! (с !), а не :grep для пакетной работы - иначе Vim сразу прыгнет на первый результат и неожиданно сменит буфер. Собирайте список, открывайте :copen, потом обрабатывайте.
  • Для массового :substitute берите :cfdo, а не :cdo. :cfdo - раз на файл, :cdo - раз на запись. Для s///g нужен именно :cfdo, иначе файл с N совпадениями обрабатывается N раз.
  • Всегда добавляйте e и | update: :cfdo s/old/new/ge | update. Флаг e подавляет E486 на файлах без совпадений, | update сохраняет изменённые буферы (сами :cdo/:cfdo не сохраняют).
  • Сужайте список через :Cfilter перед заменой (:packadd cfilter) - это безопаснее ручной чистки и снимает ложные срабатывания.
  • Освойте :colder/:cnewer - Vim хранит прошлые quickfix-списки (по умолчанию 10, настраивается 'chistory'; для location list - 'lhistory'). Не теряйте результат поиска, случайно перезаписав его новым :grep.
  • В Neovim рассмотрите nvim-bqf для превью и фильтрации больших списков и grug-far.nvim для интерактивной замены с предпросмотром (Neovim 0.11+, ripgrep 14+).
  • Настройте 'switchbuf' (useopen,uselast), чтобы :cnext/Enter не загружали каждый файл поверх текущего окна, и повесьте QuickFixCmdPost ... cwindow, чтобы :grep/:make сами открывали список.
  • В Neovim стройте списки через setqflist()/setloclist() и vim.diagnostic.set*list() - словарный API даёт полный контроль над записями, а quickfixtextfunc - над форматом окна.
  • На серверах и в скриптах опирайтесь на встроенный механизм (:grep/:cexpr/:cfdo/cfilter) - он есть везде и не требует плагинов. Это согласуется с практикой держать беглость в "голом" Vim там, где нельзя ставить софт.
  • Помните про различие quickfix и location list: quickfix глобален, location list привязан к окну. LSP-диагностику и пер-файловые списки удобно держать в location list, проектный поиск - в quickfix.
Частые ошибки
  • Путать :cdo и :cfdo. Самая частая и дорогая ошибка. s///g через :cdo обрабатывает каждый файл столько раз, сколько в нём совпадений - лишняя работа и риск повторных замен. Берите :cfdo для :substitute.
  • Забыть сохранить. После :cdo/:cfdo изменения висят в незаписанных буферах. Без | update или последующего :wall они не на диске. А если буфер изменён и не может быть оставлен - команда без ! вообще упадёт.
  • Пропустить флаг e. Без s///e команда :cfdo прервётся на первом файле, где паттерна нет (E486: Pattern not found), и часть файлов останется необработанной.
  • grepprg без --vimgrep. Записи quickfix получаются неразбираемыми, колонка и строка теряются, :cnext не переходит куда надо.
  • Запускать :grep без !. Vim прыгает на первый результат и меняет текущий буфер - неожиданно и мешает собрать полный список.
  • Шелл-экранирование в :grep/:cexpr. Спецсимволы regex и пробелы в shell отличаются от того, что ждёт grepprg. В скриптах используйте shellescape().
  • Ожидать, что окно quickfix редактируемо. По умолчанию оно read-only. Для правок прямо в списке нужен quickfix-reflector или grug-far.
  • Удалять строки через :cdo normal dd без оглядки на сдвиг нумерации. Удаление строк смещает позиции записей; для удаления надёжнее :cfdo g/pat/d.
Упражнения
  1. Настройка. Пропишите grepprg/grepformat под ripgrep в своём конфиге. Проверьте: :grep! 'function' в любом проекте должен наполнить quickfix без прыжка, а :copen - показать плоский список file:line:col:text.
  1. Базовая навигация. Выполните :vimgrep /TODO/gj **/* по учебному проекту. Откройте :copen, пройдитесь по записям через :cnext/:cprevious, прыгните к конкретной через :cc 5. Настройте себе маппинги ]q/[q.
  1. Проектная замена. Найдите по проекту любой идентификатор и переименуйте его одной командой: :grep! 'oldName' -> :cfdo s/oldName/newName/ge | update. Сравните результат с тем, что было бы при :cdo (для файла с несколькими вхождениями).
  1. Фильтрация. После :grep! подключите :packadd cfilter и сузьте список: оставьте только файлы определённого каталога (:Cfilter /src/), затем выбросьте тесты (:Cfilter! /test/). Убедитесь, что :cfdo теперь затрагивает только нужные файлы.
  1. История списков. Сделайте два разных :grep подряд. Вернитесь к первому результату через :colder, затем снова к свежему через :cnewer. Это должно стать рефлексом.
  1. Location list. Повторите упражнение 2, но с :lvimgrep, :lopen, :lnext. Откройте два окна и убедитесь, что у каждого свой независимый location list, тогда как quickfix остаётся общим.
  1. (Продвинутое) :cdo + макрос. Запишите макрос, который что-то делает со строкой совпадения (например, оборачивает значение в вызов функции), и прогоните его через :cdo normal @q | update. Подумайте, где здесь уместен :cdo, а где :cfdo.
Главное из главы
  • Quickfix list - глобальный список позиций "файл:строка", location list - то же самое, но привязано к окну. Команды зеркальны: c для quickfix, l для location list.
  • Наполняют список: :make (вывод сборки через errorformat), :grep/:lgrep (внешний поиск через grepprg/grepformat), :vimgrep/:lvimgrep (встроенный движок regex), а также :cexpr напрямую из вывода команды.
  • Настройте grepprg на ripgrep с --vimgrep и grepformat=%f:%l:%c:%m - это база быстрого проектного поиска 2026 года.
  • Навигация: :copen/:cclose, :cnext/:cprevious, :colder/:cnewer (история списков, по умолчанию 10 - 'chistory'/'lhistory'). Маппинги ]q/[q - стандарт.
  • Массовое редактирование: :cdo - раз на запись, :cfdo - раз на файл. Для :substitute с g нужен :cfdo. Всегда добавляйте e (подавить ошибку) и | update (сохранить).
  • Канонический рецепт рефакторинга: :grep! 'pat' -> (опционально :Cfilter) -> :cfdo s/old/new/ge | update.
  • Для интерактива в Neovim 2026: nvim-bqf (превью/фильтрация больших списков), grug-far.nvim (find & replace с живым предпросмотром и синком в файлы); классический quickfix-reflector делает само окно quickfix редактируемым.
В следующей главе мы перейдём к навигации по файлам - netrw, fuzzy-поиску и организации проектов, чтобы быстро открывать нужные файлы ещё до того, как они попадут в quickfix.
👍2 ❤️4 🔥1 😄 🤔
✔ Лучший ответ сформирован автоматически — ansible6
Roman_Vim писал(а)::grep! (с !), а не :grep для пакетной работы - иначе Vim сразу прыгнет на первый результат вот этот восклицательный знак меня недели две бесил, не понимал почему буфер уезжает пока список собираю. оказалось всегда забывал bang. спасибо что прямым текстом
Перейти к ответу →
Аватара пользователя
jimmyn
Сообщения: 1
Зарегистрирован: 19 май 2026, 16:58

Re: Quickfix и location list: пакетная работа по всему проекту

Сообщение jimmyn »

Вот про cdo vs cfdo наконец дошло почему у меня раньше при рефакторинге половина текста ломалась. Гонял s///g через cdo, а файл с пятью вхождениями он лупит пять раз. Перешел на cfdo и все ровно.
👍 ❤️ 🔥 😄 🤔
Аватара пользователя
ansible6
Сообщения: 1
Зарегистрирован: 15 май 2026, 08:27

Re: Quickfix и location list: пакетная работа по всему проекту

Сообщение ansible6 »

✔ Лучший ответ — сформирован автоматически
Roman_Vim писал(а)::grep! (с !), а не :grep для пакетной работы - иначе Vim сразу прыгнет на первый результат
вот этот восклицательный знак меня недели две бесил, не понимал почему буфер уезжает пока список собираю. оказалось всегда забывал bang. спасибо что прямым текстом
👍 ❤️ 🔥1 😄 🤔
Аватара пользователя
eddiev96
Сообщения: 1
Зарегистрирован: 20 май 2026, 15:01

Re: Quickfix и location list: пакетная работа по всему проекту

Сообщение eddiev96 »

:grep! 'pat' -> :Cfilter! /test/ -> :cfdo s/old/new/ge | update - это теперь мой основной рецепт переименований, в нвиме работает на голом конфиге без плагинов
👍 ❤️1 🔥2 😄 🤔1
Аватара пользователя
nours
Сообщения: 1
Зарегистрирован: 11 май 2026, 04:34

Re: Quickfix и location list: пакетная работа по всему проекту

Сообщение nours »

а есть граничный случай: если в файле паттерн не нашелся после Cfilter, cfdo же упадет? или флаг e это и закрывает. перечитал, вроде e ровно про E486, но хочу убедиться что это именно тот случай
👍1 ❤️1 🔥1 😄 🤔
Аватара пользователя
seniortoaster
Сообщения: 1
Зарегистрирован: 07 июн 2026, 22:39

Re: Quickfix и location list: пакетная работа по всему проекту

Сообщение seniortoaster »

накидал себе ]q [q как в уроке, и еще доп два маппинга на colder/cnewer повесил. вот про историю списков вообще не знал, реально спасал когда вторым grep затирал первый поиск
👍 ❤️ 🔥 😄 🤔
Аватара пользователя
archhacker
Сообщения: 2
Зарегистрирован: 01 июн 2026, 17:57

Re: Quickfix и location list: пакетная работа по всему проекту

Сообщение archhacker »

короткое спасибо, switchbuf=useopen,uselast снял боль с тем что cnext грузил каждый файл поверх текущего окна
👍2 ❤️ 🔥1 😄 🤔
Ответить
← Предыдущая глава
Буферы, окна и вкладки: организация рабочего пространства
Следующая глава →
Навигация по файлам: netrw, fuzzy-поиск и проекты

Все главы курса «Vim и Neovim: модальное редактирование от основ до современного Neovim»

Поделиться темой: ✈ Telegram VK
  • Похожие темы

Вернуться в «Vim и Neovim: модальное редактирование от основ до Neovim 2026»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 2 гостя