Введение
Когда я начинал свой путь с Neovim как Python-разработчик, я столкнулся с проблемой: в русскоязычном сегменте практически нет материалов о том, как собрать свой конфиг с нуля.
Есть готовые дистрибутивы (NvChad, LazyNvim, AstroNvim), есть переводы англоязычных статей, но нет пошагового руководства на русском, которое объясняет почему и как устроен современный конфиг Neovim.
Весь свой опыт я собирал по крупицам из англоязычных блогов, YouTube-каналов и чужих конфигов. Теперь хочу поделиться этим опытом с русскоязычным сообществом.
Для кого этот цикл статей?
Эта серия статей для вас, если вы:
Уже знакомы с базовыми движениями Vim/Neovim (hjkl, моды, базовые команды)
Хотите перейти с готовых дистрибутивов на свой конфиг
Устали копировать чужие настройки, не понимая, как они работают
Нуждаетесь в базовом, но расширяемом конфиге под свои задачи
Важно: Это не учебник по Vim - я предполагаю, что вы уже умеете работать в этом редакторе и он у вас уже установлен. Мы сосредоточимся именно на создании современного конфига на Lua.
Что мы построим?
К концу этого цикла у вас будет:
Чистый, понятный конфиг на Lua, который вы понимаете
Менеджер плагинов lazy.nvim
Современное автодополнение через blink.cmp
Подсветка синтаксиса через Treesitter
Нативный LSP
Форматтеры и линтеры
Не "магическая IDE из коробки", а фундамент, который вы сможете расширять под свои потребности.
Технические детали
Все примеры будут показаны на Arch Linux (btw) и Neovim v0.11.5, но концепции одинаковы для всех платформ.
Требования:
Neovim 0.10 или выше
Git
Базовое понимание Lua
Начинаем с чистого листа
Перед тем как начать, убедитесь, что у вас нет старого конфига. Если вы использовали готовые дистрибутивы или экспериментировали раньше, сделайте бэкап:
mv ~/.config/nvim ~/.config/nvim.backup
mv ~/.local/share/nvim ~/.local/share/nvim.backup
mv ~/.local/state/nvim ~/.local/state/nvim.backup
mv ~/.cache/nvim ~/.cache/nvim.backup
Это сбросит все настройки Neovim к базовому состоянию.
Структура конфига: философия модульности
Существует 2 подхода к созданию конфига Neovim:
Хранить все настройки в одном большом
init.luaРазбить конфиг на маленькие модули, каждый из которых будет содержать свою логику
Современные конфиги Neovim строятся модульно. Вместо одного огромного файла на 1000+ строк, мы разобьём настройки на логические блоки.
Создадим базовую структуру:
mkdir -p ~/.config/nvim/lua/core
touch ~/.config/nvim/init.lua
Итоговая структура будет выглядеть так:
~/.config/nvim/
├── init.lua
└── lua
└── core
Почему именно так?
init.lua- Neovim читает этот файл автоматически при запуске и он является точкой входаlua/- Директория, из которой Neovim может импортировать модулиcore/- Директория с основными настройками редактора (опции, горячие клавиши, цветовая схема и т.п.)
Пока что оставим init.lua пустым и создадим наш первый модуль, который будет содержать базовые настройки:
touch ~/.config/nvim/lua/core/options.lua
И подключим его в ~/.config/nvim/init.lua:
require("core.options")
require() в Lua работает как import в Python - он ищет файлы в директории lua/ и выполняет их код.
Обратите внимание:
Путь
lua/core/options.luaпревращается вrequire("core.options")Расширение
.luaне указываетсяСлэши заменяются точками
Теперь давайте наполним эти модули содержимым.
options.lua: настраиваем редактор
Откроем ~/.config/nvim/lua/core/options.lua и начнём его наполнять.
Базовая структура
local opt = vim.opt
vim.opt - это современный API для установки опций в Neovim (аналог set из Vimscript). Сохраняем его в переменную opt для краткости.
Отступы и табуляция
opt.expandtab = true -- Преобразовать табы в пробелы
opt.smarttab = true -- Умная вставка табов
opt.smartindent = true -- Автоматические отступы
opt.tabstop = 2 -- Какой ширины выглядит символ таба в файле
opt.softtabstop = 2 -- На сколько колонок двигает/удаляет Tab и Backspace
opt.shiftwidth = 2 -- Размер отступа для >> и <<
Эти настройки определяют, как Neovim обрабатывает отступы. Здесь я использую 2 пробела (для Lua чаще всего используют 2 или 4). Позже Treesitter сможет автоматически перезаписывать эти значения в зависимости от типа файла.
Отображение
opt.number = true -- Показывать номера строк
opt.numberwidth = 2 -- Ширина колонки с номерами
opt.fillchars = { eob = " " } -- Убрать символ ~ в конце буфера (визуальный мусор)
Помимо opt.number существует opt.relativenumber, который показывает номера строк относительно текущей строки. Это полезно для быстрого перемещения по строкам (10j, 5k), чтобы не считать строки в уме.
Причём мы можем комбинировать эти опции:
opt.number = true
opt.relativenumber = true
В таком случае, текущая строка будет показывать фактический номер строки в файле, �� все остальные будут отображаться относительно нее:
Обычный режим: С relativenumber: Комбинация обоих:
1 line one 3 line one 3 line one
2 line two 2 line two 2 line two
3 line three 1 line three 1 line three
4 line four (cur) 0 line four (cur) 4 line four (cur)
5 line five 1 line five 1 line five
6 line six 2 line six 2 line six
Поиск
opt.ignorecase = true -- Игнорировать регистр при поиске
opt.smartcase = true -- Но учитывать, если в запросе есть заглавные буквы
Как это работает: поиск /hello найдёт hello, Hello и HELLO. Но поиск /Hello найдёт только Hello, но не hello и HELLO.
Интерфейс
opt.termguicolors = true -- Поддержка 24-bit цветов
opt.mouse = "a" -- Включить мышь во всех режимах
opt.wrap = true -- Автоматический перенос длинных строк
Обратите внимание
termguicolors = true- обязательно для современных цветовых схем, ваш эмулятор терминала должен это поддерживатьmouse = "a"- можно кликать, скроллить, выделять мышью (если вам так привычнее)
Файлы и буферы
opt.undofile = true -- Сохранять историю отмен между сессиями
opt.swapfile = false -- Отключить swap-файлы (они раздражают)
opt.fileencoding = "utf-8" -- Кодировка файлов
opt.clipboard = "unnamedplus" -- Использовать системный буфер обмена
Самое полезное здесь:
undofile = true- можно закрыть файл, открыть через неделю и нажатьuдля отмены измененийclipboard = "unnamedplus"-yиpработают с системным буфером (Ctrl+C/Ctrl+V)
Важно для Linux: Для работы системного буфера нужен xclip (X11) или wl-clipboard (Wayland):
sudo pacman -S xclip
sudo pacman -S wl-clipboard
Дополнительно
opt.timeoutlen = 400 -- Время ожидания комбинаций клавиш (мс)
timeoutlen - как быстро Neovim должен "понять", что вы закончили комбинацию. Для меня удобно значение в 400мс.
Это базовые опции. Обо всех остальных опциях можно почитать в документации
Полный файл options.lua
Соберём всё вместе:
local opt = vim.opt
opt.expandtab = true
opt.smarttab = true
opt.smartindent = true
opt.tabstop = 2
opt.softtabstop = 2
opt.shiftwidth = 2
opt.number = true
opt.numberwidth = 2
opt.fillchars = { eob = " " }
opt.ignorecase = true
opt.smartcase = true
opt.termguicolors = true
opt.mouse = "a"
opt.wrap = true
opt.undofile = true
opt.swapfile = false
opt.fileencoding = "utf-8"
opt.clipboard = "unnamedplus"
opt.timeoutlen = 400
Совет: Не копируйте всё слепо. Найдите комфортные для себя значения.
Сохраните файл и перезапустите Neovim (:x -> nvim). Ваш редактор уже должен выглядеть иначе - с номерами строк, правильными отступами и работающим буфером обмена.
Проблема: как работать с несколькими файлами?
Откройте два файла одновременно:
nvim ~/.config/nvim/init.lua ~/.config/nvim/lua/core/options.lua
Neovim загрузил оба файла в буферы, но видите вы только init.lua. Как переключиться на второй файл?
В Vim/Neovim для этого есть команды:
:bnext(или:bn) - следующий буфер:bprevious(или:bp) - предыдущий буфер
Попробуйте набрать :bnext и нажать Enter - вы переключитесь на options.lua. А теперь :bprevious вернёт вас обратно. Введите :xa, чтобы закрыть все буферы.
Проблема в том, что это неудобно. Каждый раз печатать команды? Мы же используем Vim для скорости!
Здесь нам нужны горячие клавиши (keymaps) - короткие комбинации для частых действий.
core/keymaps.lua: ускоряем работу
Создайте файл ~/.config/nvim/lua/core/keymaps.lua. Начнём с создания вспомогательной функции.
Функция для удобной настройки клавиш
local function map(mode, lhs, rhs, desc_or_opts)
local opts = {}
if type(desc_or_opts) == "string" then
opts = { desc = desc_or_opts }
elseif type(desc_or_opts) == "table" then
opts = desc_or_opts
end
opts.silent = opts.silent ~= false
vim.keymap.set(mode, lhs, rhs, opts)
end
Что здесь происходит?
Это обёртка над vim.keymap.set(), которая делает код чище. Вместо:
vim.keymap.set("n", "<leader>w", ":w<CR>", { desc = "Save file", silent = true })
Мы пишем:
map("n", "<leader>w", ":w<CR>", "Save file")
mode- режим (n = normal, v = visual, i = insert)lhs- комбинация клавиш, которую нажимаемrhs- команда, которая выполняетсяdesc_or_opts- описание или таблица с опциямиsilent = true- не показывать команду в командной строке при её выполнении
<CR>- Carriage Return (Enter)
Не забываем подключить этот файл в init.lua:
require("core.options")
require("core.keymaps")
Leader key: ваша главная клавиша
vim.g.mapleader = " "
vim.g.maplocalleader = " "
Leader - это специальная клавиша-префикс для ваших кастомных команд. Я использую Space (пробел), потому что:
Легко нажать обеими руками
Не конфликтует с базовыми командами Vim
Стандарт де-факто в современных конфигах
Мы задаём его заранее, так как он потребуется в б��дущем.
Базовые улучшения
map("n", "<Esc>", ":noh<CR>", "Clear Highlight")
После поиска (/something) Neovim подсвечивает все совпадения. Нажатие Esc уберёт эту подсветку.
Навигация по буферам
Помните нашу проблему с переключением между файлами? Решаем:
map("n", "]b", ":bnext<CR>", "Next Buffer")
map("n", "[b", ":bprevious<CR>", "Previous Buffer")
Теперь вместо :bnext просто нажмите ]b, а вместо :bprevious - [b.
Навигация между окнами
Когда вы разделите экран (:split или :vsplit), нужно быстро переключаться между окнами:
map("n", "<C-h>", "<C-w>h", "Move left")
map("n", "<C-j>", "<C-w>j", "Move down")
map("n", "<C-k>", "<C-w>k", "Move up")
map("n", "<C-l>", "<C-w>l", "Move right")
Стандартно в Vim это Ctrl+w, затем направление (h/j/k/l).
Попробуйте:
Откройте файл:
nvim ~/.config/nvim/init.luaРазделите экран вертикально:
:vsplit ~/.config/nvim/lua/core/options.luaПереключайтесь между окнами:
Ctrl+hиCtrl+l
Полный файл keymaps.lua
local function map(mode, lhs, rhs, desc_or_opts)
local opts = {}
if type(desc_or_opts) == "string" then
opts = { desc = desc_or_opts }
elseif type(desc_or_opts) == "table" then
opts = desc_or_opts
end
opts.silent = opts.silent ~= false
vim.keymap.set(mode, lhs, rhs, opts)
end
vim.g.mapleader = " "
vim.g.maplocalleader = " "
map("n", "<Esc>", ":noh<CR>", "Clear Highlight")
map("n", "]b", ":bnext<CR>", "Next Buffer")
map("n", "[b", ":bprevious<CR>", "Previous Buffer")
map("n", "<C-h>", "<C-w>h", "Move left")
map("n", "<C-j>", "<C-w>j", "Move down")
map("n", "<C-k>", "<C-w>k", "Move up")
map("n", "<C-l>", "<C-w>l", "Move right")
Сохраните и перезапустите Neovim. Откройте несколько файлов и попрактикуйтесь.
Что мы сделали?
Давайте подведём итог. Теперь у нас есть:
Модульная структура конфига -
init.lua+ директорияlua/core/Настроенный редактор - отступы, номера строк, буфер обмена, история отмен
Удобная навигация - быстрое переключение между буферами и окнами
Понимание основ - вы знаете, как работает
require(),vim.optиvim.keymap.set()
Ваш конфиг на данный момент выглядит так:
~/.config/nvim/
├── init.lua
└── lua
└── core
├── keymaps.lua
└── options.lua
Это фундамент, на котором мы будем строить дальше. Он уже функционален - можно работать, редактировать файлы, переключаться между буферами и окнами.
Исходный код конфига можно посмотреть на GitHub
Что дальше?
В следующей части мы:
Установим менеджер плагинов lazy.nvim и разберёмся, почему мы используем его вместо встроенного
vim.packРазберёмся с концепцией lazy-loading
Настроим автодополнение через blink.cmp
Подключим подсветку синтаксиса через Treesitter
Домашнее задание:
Поэкспериментируйте с настройками в
options.luaДобавьте собственные горячие клавиши в
keymaps.lua(например,<leader>wдля быстрого сохранения файла через:w)Откройте несколько файлов и попрактикуйтесь в навигации с
]b/[bиCtrl+hjkl
Полезные ресурсы:
:help options- Справка по всем опциям Neovim:help vim.keymap.set- Документация по маппингам:help lua- Введение в Lua API для Neovim
