Базовый Neovim из коробки 2026 года уже умеет многое: подсветку Treesitter, нативный LSP-клиент, автодополнение в insert-режиме. Но как только вы хотите git-интеграцию в gutter, fuzzy-поиск по проекту, файловый менеджер "как буфер" или красивую строку статуса - вам понадобятся сторонние плагины. И тут возникает классическая проблема: плагины надо где-то взять, обновить, зафиксировать версии, не сломать конфиг на другой машине и при этом не превратить старт редактора в трёхсекундное ожидание.
Менеджер плагинов решает четыре задачи:
- Установка и обновление - клонирует репозитории, тянет обновления, разрешает зависимости.
- Воспроизводимость - фиксирует точные ревизии в lockfile, чтобы конфиг работал одинаково на ноутбуке, сервере и в контейнере.
- Ленивая загрузка (lazy-loading) - подключает плагин не на старте, а в момент, когда он реально нужен: при открытии файла нужного типа, нажатии клавиши или вызове команды.
- Прозрачность - показывает, что установлено, сколько грузилось и где узкое место старта.
Краткая история: как мы сюда пришли
Понимать предшественников полезно - вы встретите их в старых dotfiles и гайдах.
Код: Выделить всё
Менеджер | Эпоха | Идея | Почему ушёл
---------------------+----------------------+----------------------------------------------------------------------------------------------+--------------------------------------------------------
Pathogen (tpope) | ~2010 | Просто добавляет каждый каталог в runtimepath. Установка - вручную клонировать репозиторий | Не умеет ни ставить, ни обновлять плагины
Vundle | ~2012 | :PluginInstall, декларативный список в .vimrc | Синхронный, медленный, заброшен
vim-plug (junegunn) | ~2014 | Один файл, параллельная установка, ленивая загрузка по for/on | Жив и хорош для чистого Vim, но не использует Lua
packer.nvim | ~2020 | Первый "луашный" менеджер с компиляцией спецификации в загрузчик | Архивирован, автор рекомендует переходить на lazy.nvim
lazy.nvim (folke) | 2023+ | Декларативные спеки, ленивая загрузка по событиям, байткод-кэш, UI | Текущий стандарт
vim.pack | Neovim 0.12 (2026) | Встроенный в ядро, без зависимостей, lockfile | Нет ленивой загрузки по event/ft/cmd
lazy.nvim: установка и bootstrap
lazy.nvim требует Neovim >= 0.8.0 (с LuaJIT) и Git >= 2.19.0 (для partial clone). Сам менеджер ставится "сам себя" - это называется bootstrap. Идея: при первом запуске проверить, есть ли lazy.nvim на диске, и если нет - склонировать его до того, как мы попытаемся его использовать.
Канонический bootstrap (обычно живёт в lua/config/lazy.lua):
Код: Выделить всё
-- Leader ОБЯЗАТЕЛЬНО задать ДО загрузки lazy.nvim,
-- иначе плагины захватят дефолтный leader '\' для своих keys=.
vim.g.mapleader = " "
vim.g.maplocalleader = " "
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not (vim.uv or vim.loop).fs_stat(lazypath) then
vim.fn.system({
"git", "clone", "--filter=blob:none",
"https://github.com/folke/lazy.nvim.git",
"--branch=stable", -- всегда последний стабильный релиз
lazypath,
})
end
vim.opt.rtp:prepend(lazypath)
require("lazy").setup("plugins") -- грузит lua/plugins/*.lua
Спецификация плагина (spec) - сердце lazy.nvim
Каждый плагин описывается таблицей-спецификацией. Минимальный вариант - просто строка "автор/репозиторий":
Код: Выделить всё
return {
"lewis6991/gitsigns.nvim",
}
opts против config
opts - это таблица настроек. lazy.nvim сам вызовет require("название").setup(opts). Это предпочтительный способ:
Код: Выделить всё
return {
"lewis6991/gitsigns.nvim",
opts = {
signs = { add = { text = "+" } },
},
}
Код: Выделить всё
return {
"folke/which-key.nvim",
config = function()
require("which-key").setup()
-- что-то ещё, например ручные регистрации
end,
}
Триггеры ленивой загрузки: event, cmd, keys, ft
Вот ради чего всё затевалось. Любой плагин, у которого есть хотя бы одно из полей event/cmd/keys/ft, грузится лениво - то есть не на старте, а при срабатывании триггера.
Код: Выделить всё
Поле | Когда грузить | Пример | Для чего
-------+--------------------------+---------------------------+--------------------------------
event | По autocommand-событию | event = "VeryLazy" | UI-плагины, statusline
event | По событию буфера | event = "BufReadPost" | подсветка, gitsigns
ft | По типу файла | ft = "go" | языковые плагины
cmd | При вызове Ex-команды | cmd = "Telescope" | плагины-инструменты
keys | При нажатии маппинга | keys = { "<leader>ff" } | плагины, вызываемые по клавише
Развёрнутый пример с ленивой загрузкой по командам и клавишам:
Код: Выделить всё
return {
"nvim-telescope/telescope.nvim",
cmd = "Telescope",
keys = {
{ "<leader>ff", "<cmd>Telescope find_files<cr>", desc = "Найти файлы" },
{ "<leader>fg", "<cmd>Telescope live_grep<cr>", desc = "Поиск по содержимому" },
},
dependencies = { "nvim-lua/plenary.nvim" },
opts = {},
}
Языковой плагин по типу файла
Код: Выделить всё
return {
"ray-x/go.nvim",
ft = { "go", "gomod" },
opts = {},
}
Пиннинг версий и веток
Иногда нужно зафиксировать ветку или версию. Классический пример из ресёрча 2026 - blink.cmp, где активно разрабатывается ломающая v2, и документация советует оставаться на стабильной v1:
Код: Выделить всё
return {
"saghen/blink.cmp",
version = "1.*", -- ИЛИ branch = "v1" (что-то одно, см. ниже)
event = "InsertEnter",
opts = {},
}
Важная практика: в общем случае lazy.nvim рекомендует держать version = false (значение по умолчанию), потому что у многих плагинов теги релизов отстают от main, и semver-пиннинг ломает свежие фичи. Пиньте версию точечно - только для нестабильных плагинов вроде blink.cmp.
dependencies: порядок загрузки зависимостей
Поле dependencies (мы видели его в примере Telescope) перечисляет плагины, которые lazy.nvim гарантированно загрузит и настроит до основного плагина. Это не то же самое, что просто объявить два плагина в конфиге рядом: при обычном объявлении порядок загрузки не определён, а dependencies задаёт жёсткую очерёдность "сначала зависимость, потом плагин".
Код: Выделить всё
return {
"nvim-telescope/telescope.nvim",
cmd = "Telescope",
dependencies = {
"nvim-lua/plenary.nvim", -- библиотека, загрузится первой
{ "nvim-telescope/telescope-fzf-native.nvim", build = "make" },
},
opts = {},
}
- Зависимость наследует ленивость родителя: пока Telescope не вызван, plenary.nvim тоже не грузится. Зависимость подтягивается ровно в момент загрузки основного плагина.
- Зависимость - это просто вложенная спека, поэтому ей можно задать свои opts, build и т.п.
- Поэтому библиотеки вроде plenary.nvim обычно не объявляют отдельной строкой с lazy = true - их подключают как dependencies тех плагинов, что в них нуждаются. Так не возникает гонок загрузки.
init - это функция, которая выполняется всегда на старте (даже если плагин ленивый и ещё не загружен), в отличие от config, который срабатывает только в момент фактической загрузки плагина. Типичный сценарий - выставить глобальные переменные vim.g.*, которые плагин читает при инициализации:
Код: Выделить всё
return {
"preservim/vim-markdown",
ft = "markdown",
init = function()
-- читается плагином при загрузке; должно быть выставлено заранее
vim.g.vim_markdown_folding_disabled = 1
end,
}
priority: гонка цветовой схемы
Цветовая схема - особый случай. Её нужно применить раньше остальных плагинов, иначе UI-плагины (statusline, иконки) успеют отрисоваться в дефолтных цветах, и вы увидите вспышку "неправильной" темы. Решение - поднять priority: lazy.nvim грузит стартовые (нелениво подключённые) плагины в порядке убывания priority, дефолт - 50.
Код: Выделить всё
return {
"folke/tokyonight.nvim",
lazy = false, -- цветосхема нужна сразу, не лениво
priority = 1000, -- грузим раньше всех остальных
config = function()
vim.cmd.colorscheme("tokyonight")
end,
}
:Lazy - управление через UI
Команда :Lazy открывает интерактивную панель - главный пульт управления плагинами.
Код: Выделить всё
Команда / клавиша | Действие
-------------------+------------------------------------------------
:Lazy | Открыть главную панель
:Lazy install | Установить отсутствующие плагины
:Lazy update | Обновить плагины и переписать lockfile
:Lazy sync | install + clean + update за один проход
:Lazy clean | Удалить плагины, которых больше нет в спеках
:Lazy restore | Откатить все плагины к версиям из lockfile
:Lazy profile | Показать время загрузки каждого плагина
:Lazy log | Показать недавние коммиты обновлённых плагинов
:Lazy health | Проверить здоровье конфигурации
lazy-lock.json - воспроизводимость
При установке и обновлении lazy.nvim записывает точные коммиты всех плагинов в файл lazy-lock.json рядом с вашим конфигом. Это и есть гарантия воспроизводимости.
Код: Выделить всё
было: на ноутбуке gitsigns на коммите abc123
-> git push dotfiles
-> git pull на сервере
-> :Lazy restore
стало: на сервере gitsigns ровно на abc123
- lazy-lock.json коммитим в git вместе с конфигом.
- :Lazy update обновляет плагины и переписывает lockfile - после этого коммитим новый lock.
- :Lazy restore ставит ровно те версии, что записаны в lockfile (полезно, когда обновление что-то сломало).
Профилирование старта
lazy.nvim полностью берёт стартовую последовательность на себя и умеет её измерять. Команда :Lazy profile показывает таблицу: каждый плагин и сколько миллисекунд он отъел при загрузке. Сортируется по убыванию - сразу видно главного "пожирателя".
Что искать в профиле:
- Плагины, которые грузятся на старте, хотя могли бы лениво. Если видите тяжёлый плагин без event/ft/cmd/keys - кандидат на ленивую загрузку.
- Общее время старта. Ориентировочно при ленивой загрузке реально уложиться в несколько десятков миллисекунд на современном железе - но конкретная цифра сильно зависит от системы и числа плагинов: на старых машинах или больших конфигах даже идеальная ленивость не даст 50 мс. Поэтому ориентируйтесь не на абсолютное значение, а на относительную динамику: что съедает больше всего и что меняется после правок.
Лучшие практики структуры конфигурации 2026
Опираясь на актуальные практики (kickstart.nvim, LazyVim, гайды 2026), сложился такой канон организации:
Код: Выделить всё
~/.config/nvim/
├── init.lua -- только require'ы
├── lua/
│ ├── config/
│ │ ├── options.lua -- vim.opt.*
│ │ ├── keymaps.lua -- глобальные маппинги
│ │ ├── autocmds.lua -- autocommand'ы
│ │ └── lazy.lua -- bootstrap + setup("plugins")
│ └── plugins/
│ ├── telescope.lua -- один файл = один плагин/группа
│ ├── gitsigns.lua
│ ├── lsp.lua
│ └── ...
└── lazy-lock.json -- коммитится в git
Код: Выделить всё
vim.loader.enable()
require("config.options")
require("config.keymaps")
require("config.autocmds")
require("config.lazy") -- внутри: leader + bootstrap + setup("plugins")
Сводка best practices:
- Ленивость по триггерам, а не lazy = true вслепую. Указывайте явные event/ft/cmd/keys. Не ставьте lazy = true на плагины, от которых другие зависят на старте (цветовая схема, библиотеки вроде plenary.nvim, иконки) - это вызывает гонки загрузки.
- opts вместо config, когда хватает настроек по умолчанию. config = function() - только для нетривиальной инициализации.
- desc в каждом маппинге внутри keys - он автоматически подхватывается which-key (подробнее в главе 21).
- lazy-lock.json в git; version = false по умолчанию, точечный пиннинг только для нестабильных плагинов.
- vim.g.mapleader - первой строкой, до bootstrap.
- event = "VeryLazy" для всего, что не нужно в первый кадр (statusline, иконки, which-key).
- lazy = false + priority = 1000 для цветовой схемы - чтобы тема применилась раньше UI-плагинов и не было вспышки дефолтных цветов.
- dependencies, а не отдельная спека, для библиотек (plenary.nvim и т.п.) - это задаёт порядок загрузки и наследует ленивость родителя.
- init - для vim.g.* до загрузки плагина; config/opts - для настройки уже загруженного.
Neovim 0.12 (вышел 29 марта 2026) принёс встроенный менеджер плагинов vim.pack - написан на Lua, без внешних зависимостей, поддерживается командой ядра. Это важная альтернатива, но с оговорками.
Код: Выделить всё
vim.pack.add({
{ src = "https://github.com/lewis6991/gitsigns.nvim" },
{ src = "https://github.com/folke/which-key.nvim", version = "main" },
})
require("gitsigns").setup()
Главное ограничение: у vim.pack нет ленивой загрузки по event/ft/cmd/keys и нет packspec. На конфигах в 30+ плагинов это означает, что либо все грузятся на старте (и старт деградирует), либо ленивость придётся писать руками через vim.schedule() и autocommand'ы.
Подводные камни vim.pack:
- Хуки PackChanged/PackChangedPre не сработают при первой установке, если их autocommand создан после вызова vim.pack.add(). Определяйте autocommand'ы хуков до add().
- Держите единственный вызов vim.pack.add() как воспроизводимый "чертёж".
- Никогда не редактируйте lockfile и не удаляйте каталоги плагинов руками - будет рассинхрон. Удаление - только через vim.pack.del().
vim-plug: менеджер для чистого Vim
Если вы работаете в классическом Vim (например, на сервере, где нельзя ставить Neovim), Lua-менеджеры недоступны. Здесь стандарт - vim-plug: один файл, параллельная установка, простая ленивая загрузка.
Список плагинов оборачивается в блок plug#begin / plug#end в .vimrc:
Код: Выделить всё
call plug#begin('~/.vim/plugged')
Plug 'tpope/vim-surround'
Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
Plug 'junegunn/fzf.vim'
" Ленивая загрузка по типу файла:
Plug 'fatih/vim-go', { 'for': 'go' }
" Ленивая загрузка по команде:
Plug 'preservim/nerdtree', { 'on': 'NERDTreeToggle' }
call plug#end()
Код: Выделить всё
Команда | Действие
--------------+------------------------------
:PlugInstall | Установить плагины из списка
:PlugUpdate | Обновить плагины
:PlugClean | Удалить плагины не из списка
:PlugStatus | Показать статус
:PlugUpgrade | Обновить сам vim-plug
Частые ошибки
- vim.g.mapleader после require("lazy"). Плагины забиндят keys= на старый leader \, и leader-маппинги "не работают". Leader - первой строкой.
- Загрузка всех плагинов на старте без event/ft/cmd/keys. Старт раздувается до сотен миллисекунд. Проверяйте через :Lazy profile.
- lazy = true на критичных плагинах (цветовая схема, plenary.nvim, иконки). Возникает гонка загрузки и визуальные глитчи. Их грузят на старте или подтягивают как зависимости.
- Незакоммиченный lazy-lock.json. Теряется воспроизводимость: на другой машине :Lazy install подтянет свежие коммиты, и конфиг поедет.
- Глобальный version = "*" ломает установку: у многих плагинов теги отстают от main. Держите version = false, пиньте точечно.
- Установка blink.cmp без пина версии. Затягивает в нестабильную v2 с ломающими изменениями (требует ещё blink.lib). Фиксируйте version = "1.*".
- config = function() там, где хватает opts. Ломает послойный merge в дистрибутивах, усложняет переопределение.
- vim.pack: autocommand хуков после add() - не срабатывают при первой установке. И не правьте lockfile руками.
- Bootstrap с нуля. Создайте lua/config/lazy.lua с bootstrap-блоком из главы. Убедитесь, что vim.g.mapleader стоит до require("lazy"). Запустите Neovim и проверьте, что :Lazy открывается.
- Ленивый плагин по типу файла. Добавьте любой языковой плагин (например, для Markdown) с ft = "markdown". Сначала откройте любой файл НЕ markdown-типа (например, .lua) - раз триггер ft = "markdown" не сработал, плагин грузиться не должен; выполните :Lazy и убедитесь, что он помечен как незагруженный. Затем откройте .md-файл и проверьте, что он подгрузился.
- Профилирование. Выполните :Lazy profile, найдите три самых "тяжёлых" плагина. Для любого, у которого нет триггера, добавьте подходящий event или cmd и сравните время старта до и после.
- Lockfile-воспроизводимость. Выполните :Lazy update, посмотрите изменения в lazy-lock.json через git diff. Затем :Lazy restore и убедитесь, что lockfile вернулся к зафиксированным версиям.
- Пиннинг версии. Добавьте blink.cmp со спекой version = "1.*" и event = "InsertEnter". Проверьте через :Lazy, что установлена именно ветка v1.
- (Для смелых) vim.pack. Если у вас Neovim 0.12, заведите отдельный минимальный конфиг на vim.pack.add() с двумя плагинами и autocommand PackChanged до вызова add(). Убедитесь, что хук сработал при первой установке.
- Менеджер плагинов решает четыре задачи: установка/обновление, воспроизводимость, ленивая загрузка и прозрачность.
- lazy.nvim - стандарт 2026. Bootstrap клонирует менеджер сам; vim.g.mapleader задаётся до него.
- Spec описывает плагин таблицей: opts (предпочтительно) или config, плюс триггеры ленивой загрузки event/ft/cmd/keys.
- :Lazy - пульт управления; lazy-lock.json коммитится в git и даёт воспроизводимость; :Lazy profile показывает узкие места старта.
- vim.pack (Neovim 0.12) - встроенная альтернатива без зависимостей, но без декларативной ленивой загрузки.
- vim-plug - выбор для чистого Vim: 'for'/'on' для ленивости, :PlugInstall/:PlugUpdate.
- Структура конфига 2026: тонкий init.lua + lua/config/* + lua/plugins/* (один файл на плагин). Конфигурацию деталей см. в главах 20-22.