В этой главе мы разберёмся, где живёт конфиг, как устроена современная модульная структура на Lua, и соберём комментированный стартовый init.lua с разумными дефолтами 2026 года. Маппинги (leader, which-key) мы сознательно вынесли в главу 21, скриптинг и автокоманды глубже разбираются в главе 22, а менеджеры плагинов - в главе 23. Здесь - фундамент: опции и структура.
Зачем вообще нужен конфиг
Дефолты Vim и Neovim консервативны по историческим причинам: они должны работать на терминале 1991 года и не ломать обратную совместимость. Поэтому "из коробки" вы не получаете подсветку относительных номеров строк, бесконечную историю отмен или умный регистронезависимый поиск. Всё это включается парой строк.
Главная мысль: конфиг - это не "настроить один раз и забыть", а живой документ, который растёт вместе с вами. Поэтому критично, чтобы он был читаемым, модульным и воспроизводимым между машинами. Монолитный .vimrc на 2000 строк, который страшно тронуть, - антипаттерн. Хорошо организованный конфиг можно перенести на новый ноутбук за минуту и понять любую его строчку через полгода.
Где лежит конфиг
Первый вопрос новичка - "куда вообще писать настройки". Ответ зависит от редактора и платформы.
Vim
Классический Vim читает ~/.vimrc (или ~/.vim/vimrc). Каталог ~/.vim/ хранит плагины, swap-файлы, undo-историю и прочее. На Windows это ~/_vimrc и ~/vimfiles/.
Neovim и XDG
Neovim не использует ~/.vimrc. Он следует спецификации XDG Base Directory и ищет конфиг по пути:
Код: Выделить всё
Что | Путь (Linux/macOS) | Переменная XDG
-------------------------+------------------------------------------+------------------------
Главный конфиг | ~/.config/nvim/init.lua (или init.vim) | $XDG_CONFIG_HOME/nvim/
Lua-модули | ~/.config/nvim/lua/ | -
Данные (плагины, shada) | ~/.local/share/nvim/ | $XDG_DATA_HOME/nvim/
Состояние (undo, swap) | ~/.local/state/nvim/ | $XDG_STATE_HOME/nvim/
Кэш | ~/.cache/nvim/ | $XDG_CACHE_HOME/nvim/
Код: Выделить всё
:echo stdpath('config') " каталог init.lua
:echo stdpath('data') " каталог плагинов
:lua print(vim.fn.stdpath('state'))
Структура модульного конфигаСовет: храните каталог ~/.config/nvim/ в git-репозитории (dotfiles). Это даёт историю изменений, бэкап и синхронизацию между машинами.
Минимальный конфиг - это один файл init.lua со всеми настройками подряд. Так начинают, и для пары десятков опций это нормально. Но как только добавляются плагины, маппинги и автокоманды, монолит становится неуправляемым. Канон 2026 - модульная структура, где init.lua лишь подключает логические модули.
Neovim автоматически добавляет каталог ~/.config/nvim/lua/ в runtimepath, и оттуда работает функция require(). Точка (.) в имени модуля означает разделитель каталогов: require('config.options') загружает файл lua/config/options.lua.
Рекомендуемая раскладка:
Код: Выделить всё
~/.config/nvim/
├── init.lua -- точка входа: только require'ы
└── lua/
└── config/
├── options.lua -- опции (тема этой главы)
├── keymaps.lua -- маппинги (глава 21)
├── autocmds.lua -- автокоманды (глава 22)
└── lazy.lua -- bootstrap менеджера плагинов (глава 23)
Код: Выделить всё
-- ~/.config/nvim/init.lua
-- 1. Кэш Lua-байткода: ускоряет старт примерно на ~30%.
-- Встроено с Neovim 0.9. Ставить в самом начале.
vim.loader.enable()
-- 2. Leader ОБЯЗАТЕЛЬНО задаём ДО загрузки плагинов (см. главу 21).
vim.g.mapleader = ' '
vim.g.maplocalleader = ' '
-- 3. Подключаем модули по порядку.
require('config.options')
require('config.keymaps')
require('config.autocmds')
-- require('config.lazy') -- менеджер плагинов, глава 23
- vim.loader.enable() первым. Он кэширует скомпилированный Lua-байткод модулей. Чем раньше включён - тем больше последующих require() попадает в кэш.
- mapleader до плагинов. Это самая частая ошибка новичков: если задать leader после загрузки плагинов, они уже привяжут свои клавиши к старому дефолтному leader (\). Подробнее - в главе 21, но строку лучше сразу держать в init.lua наверху.
- Порядок require. Опции и маппинги - чистые настройки без зависимостей, их грузим первыми. Плагины - последними.
vim.opt против set: как задавать опции
В Vimscript опции выставлялись командой set:
Код: Выделить всё
set number
set shiftwidth=4
set listchars=tab:>-,trail:-
Код: Выделить всё
Lua API | Аналог | Когда использовать
-----------------------+-----------------+-----------------------------------------------------------
vim.opt.number = true | set number | Основной способ. Умеет работать со списками и множествами
vim.o.number = true | set number | Простой геттер/сеттер скалярного значения
vim.g.x = ... | let g:x = ... | Глобальные переменные (например, mapleader)
vim.bo / vim.wo | setlocal | Буфер-/окно-локальные опции
Код: Выделить всё
-- vim.opt понимает таблицы и даёт методы-обёртки opt:append()/:remove()/:prepend():
vim.opt.listchars = { tab = '>> ', trail = '·', nbsp = '_' }
vim.opt.wildignore:append({ '*.o', '*.pyc', 'node_modules' })
vim.opt.shortmess:remove('S')
-- vim.o работает только со скалярами (строка/число/булево):
vim.o.number = true -- ок
-- vim.o.listchars = {...} -- НЕ сработает: ожидается строка
Разумные дефолты 2026: разбор по группам
Теперь - главное содержание главы. Пройдём по опциям, которые стоит включить почти каждому, и объясним почему, а не только как. В конце соберём всё в готовый файл.
Номера строк
Код: Выделить всё
vim.opt.number = true -- абсолютный номер текущей строки
vim.opt.relativenumber = true -- относительные номера остальных строк
Колонка знаков
Код: Выделить всё
vim.opt.signcolumn = 'yes' -- всегда показывать колонку знаков
Отступы и табы
Код: Выделить всё
vim.opt.tabstop = 2 -- ширина символа таба на экране
vim.opt.shiftwidth = 2 -- ширина одного уровня отступа (>>, <<, автоиндент)
vim.opt.expandtab = true -- Tab вставляет пробелы, а не символ \t
vim.opt.smartindent = true -- умный автоотступ для C-подобного синтаксиса
- expandtab заменяет нажатие <Tab> на пробелы. Большинство современных стилей кода (Lua, JS/TS, Python, YAML) используют пробелы, и expandtab гарантирует, что отступ выглядит одинаково в любом редакторе.
- shiftwidth - на сколько пробелов сдвигают операторы >>/<< и автоиндент. Это "логический" размер отступа.
- tabstop - сколько колонок занимает реальный символ таба на экране (актуально для файлов, где табы всё же встречаются, например Makefile).
- smartindent добавляет базовый авто-отступ для блоков (после {, например). Это эвристика на случай отсутствия более умного источника. В Neovim с Treesitter (глава 27) индентацию обычно отдают именно ему через indentexpr, и тогда smartindent играет роль фоллбэка.
Поиск
Код: Выделить всё
vim.opt.ignorecase = true -- поиск без учёта регистра...
vim.opt.smartcase = true -- ...но если в запросе есть Заглавная - учитывать регистр
vim.opt.incsearch = true -- инкрементальный поиск (подсветка по мере набора)
vim.opt.hlsearch = true -- подсвечивать все совпадения
- /error найдёт error, Error, ERROR - регистр игнорируется.
- /Error (есть заглавная) найдёт только Error - поиск становится регистрочувствительным.
incsearch показывает совпадение прямо во время набора паттерна - удобно, чтобы не дописывать длинный запрос вслепую. hlsearch подсвечивает все вхождения; чтобы быстро гасить подсветку после поиска, в главе 21 мы повесим маппинг на <leader> или <Esc>. Подробности по поиску - в главе 10.
История отмен между сессиями
Код: Выделить всё
vim.opt.undofile = true -- сохранять историю отмен в файл
-- undodir по умолчанию = stdpath('state')/undo - менять обычно не нужно
Заодно стоит осознанно определиться с политикой swap и backup - это тоже часть "файлов и истории":
Код: Выделить всё
vim.opt.swapfile = true -- держать swap включённым
vim.opt.backup = false -- не оставлять постоянные ~-копии после записи
vim.opt.writebackup = true -- но делать временный бэкап на время самой записи
- swapfile (по умолчанию включён) пишет swap-файл в ~/.local/state/nvim/swap/ и периодически (раз в updatetime мс) сбрасывает несохранённые изменения. Он защищает от потери данных при падении редактора или системы и предупреждает, если файл уже открыт другим процессом. Отключать (swapfile = false) есть смысл разве что на сетевых/медленных ФС - на обычной машине оставляйте как есть.
- backup по умолчанию false, и менять это обычно не нужно: с undofile и git постоянные file~-копии лишь засоряют каталоги. writebackup (по умолчанию true) - другое: это временный бэкап, который существует только во время записи и удаляется после успешного сохранения. Он защищает от ситуации "диск кончился прямо посреди записи и файл превратился в обрубок", поэтому его лучше не выключать.
Код: Выделить всё
vim.opt.clipboard = 'unnamedplus' -- интеграция с системным буфером
Важный нюанс 2026 (специфично для Neovim). Эта настройка удобна на десктопе, но коварна при работе по SSH и в контейнерах. Начиная с Neovim 0.10, если опция clipboard не задана, в TUI автоматически включается OSC52 - протокол, который передаёт текст в системный буфер вашего локального терминала через escape-последовательности (работает даже через SSH, без xclip/wl-clipboard). Важная оговорка: авто-OSC52 включается, только если Neovim не нашёл ни одного внешнего clipboard-провайдера (pbcopy, xclip, wl-copy и т. п.). На локальном десктопе с установленным wl-copy/xclip авто-OSC52 НЕ активируется даже при пустом clipboard - Neovim предпочтёт найденную внешнюю утилиту. И ещё: если вы выставите clipboard = 'unnamedplus', авто-детект OSC52 отключается в любом случае.
Поэтому:
- Локальный десктоп с pbcopy/wl-copy/xclip - unnamedplus удобен, ставьте смело.
- Удалённая работа по SSH (когда на удалённой машине нет внешнего clipboard-провайдера) - часто лучше оставить clipboard пустым: тогда сработает авто-OSC52, и можно пользоваться явными "+y / "+p.
- Если же нужно форсировать OSC52 там, где он не включился автоматически (например, на удалёнке всё же стоит xclip, но дисплея нет), задайте провайдер таблицей. Строковое присваивание vim.g.clipboard = 'osc52' не работает - vim.g.clipboard ожидает таблицу с полями name/copy/paste:
Код: Выделить всё
vim.g.clipboard = {
name = 'OSC 52',
copy = {
['+'] = require('vim.ui.clipboard.osc52').copy('+'),
['*'] = require('vim.ui.clipboard.osc52').copy('*'),
},
paste = {
['+'] = require('vim.ui.clipboard.osc52').paste('+'),
['*'] = require('vim.ui.clipboard.osc52').paste('*'),
},
}
Поведение прокрутки и интерфейса
Код: Выделить всё
vim.opt.scrolloff = 8 -- держать минимум 8 строк выше/ниже курсора
vim.opt.termguicolors = true -- 24-битный цвет (true color)
vim.opt.updatetime = 250 -- мс простоя до CursorHold и записи swap
vim.opt.splitright = true -- вертикальный сплит открывается справа
vim.opt.splitbelow = true -- горизонтальный сплит открывается снизу
vim.opt.mouse = 'a' -- поддержка мыши во всех режимах
- scrolloff = 8 не даёт курсору упираться в самый край экрана: при движении вниз/вверх редактор начинает прокручивать заранее, оставляя 8 строк контекста. Глаз всегда видит, что идёт следом. Значение 8 - комфортный компромисс; некоторые ставят 999, чтобы курсор всегда был по центру.
- termguicolors включает 24-битный цвет вместо убогих 256 цветов терминала. Без него современные цветовые схемы (особенно с Treesitter-подсветкой) выглядят блёкло и неправильно. Требует терминал с поддержкой true color - все актуальные терминалы 2026 года это умеют.
- updatetime = 250 уменьшает дефолтную задержку (4000 мс) перед срабатыванием события CursorHold. На него завязаны hover-подсказки LSP, подсветка ссылок, gitsigns. 250 мс - отзывчиво, но не дёргано. Заодно это интервал записи swap-файла.
- splitright / splitbelow меняют направление открытия новых окон на интуитивное (вправо и вниз), как привыкли пользователи tmux и большинства IDE. По умолчанию Vim открывает слева/сверху, что сбивает с толку. Подробнее об окнах - в главе 13.
- mouse = 'a' включает мышь во всех режимах. Многие пуристы её отключают, но мышь полезна для случайного клика, ресайза сплитов перетаскиванием и прокрутки колесом - это не противоречит модальному редактированию, а дополняет его.
Несколько опций, которые почти всегда хочется включить:
Код: Выделить всё
vim.opt.wrap = false -- не переносить длинные строки визуально
vim.opt.cursorline = true -- подсветить строку с курсором
vim.opt.confirm = true -- спрашивать про сохранение вместо ошибки при :q
vim.opt.inccommand = 'split' -- (Neovim) живой предпросмотр :substitute
Комментированный стартовый конфиг
Соберём всё в один файл lua/config/options.lua. Это рабочий минимум, на который можно опираться и расширять. Каждая строка снабжена комментарием.
Код: Выделить всё
-- ~/.config/nvim/lua/config/options.lua
-- Разумные дефолты Neovim 2026. Подключается из init.lua через require('config.options').
local opt = vim.opt -- короткий алиас для читаемости
-- === Номера строк ===
opt.number = true -- абсолютный номер текущей строки
opt.relativenumber = true -- относительные номера для count-движений (5j, d3k)
-- === Интерфейс ===
opt.signcolumn = 'yes' -- всегда резервировать колонку знаков (LSP, git)
opt.cursorline = true -- подсветка строки под курсором
opt.scrolloff = 8 -- контекст в 8 строк вокруг курсора
opt.termguicolors = true -- 24-битный цвет (нужен современным темам)
opt.wrap = false -- не переносить длинные строки
opt.mouse = 'a' -- мышь во всех режимах
-- === Отступы ===
opt.tabstop = 2 -- ширина таба на экране
opt.shiftwidth = 2 -- размер одного уровня отступа
opt.expandtab = true -- Tab -> пробелы
opt.smartindent = true -- базовый умный автоотступ (фоллбэк к Treesitter)
-- === Поиск ===
opt.ignorecase = true -- игнорировать регистр...
opt.smartcase = true -- ...кроме случаев с заглавными в запросе
opt.incsearch = true -- инкрементальная подсветка во время набора
opt.hlsearch = true -- подсвечивать все совпадения
opt.inccommand = 'split' -- (Neovim) живой предпросмотр :substitute
-- === Сплиты ===
opt.splitright = true -- вертикальный сплит -> справа
opt.splitbelow = true -- горизонтальный сплит -> снизу
-- === Файлы и история ===
opt.undofile = true -- сохранять дерево отмен между сессиями
opt.confirm = true -- спрашивать о сохранении вместо ошибки при :q
opt.updatetime = 250 -- мс до CursorHold (hover, gitsigns) и записи swap
opt.swapfile = true -- swap оставляем включённым (защита от падений/гонок)
opt.backup = false -- не плодить ~-копии при записи (есть git + undofile)
opt.writebackup = true -- но временный бэкап на время самой записи -- безопасно
-- === Буфер обмена ===
-- На десктопе с pbcopy/wl-copy/xclip удобно. По SSH (если внешнего провайдера нет)
-- рассмотрите пустое значение -- тогда сработает авто-OSC52. Форсировать OSC52
-- можно только таблицей vim.g.clipboard = {...}, не строкой 'osc52'. См. главу 8.
opt.clipboard = 'unnamedplus'
-- === Спецсимволы (видимые табы/пробелы по желанию) ===
-- opt.list = true
-- opt.listchars = { tab = '>> ', trail = '·', nbsp = '_' }
Лучшие практики 2026
- Модульность с первого дня. Даже если опций мало, заведите lua/config/options.lua и подключайте его через require. Это привычка, которая окупится, когда добавятся плагины и маппинги.
- vim.loader.enable() и mapleader - в начало init.lua. Первое ускоряет старт на ~30%, второе предотвращает классический баг с неработающими leader-маппингами.
- vim.opt как способ по умолчанию. Он единообразно обрабатывает и скаляры, и списки и предоставляет методы объекта-обёртки opt:append(), opt:remove(), opt:prepend() (это именно методы Lua-объекта, а не Ex-команды).
- init.lua, а не init.vim. Lua - стандарт экосистемы 2026. Старый Vimscript подключайте точечно через vim.cmd.
- Конфиг в git. Каталог ~/.config/nvim/ под версионным контролем - это бэкап, история и синхронизация между машинами одним git clone.
- Не копируйте чужой конфиг вслепую. Понимайте каждую строку. Отличная отправная точка для самостоятельной сборки - kickstart.nvim (один хорошо прокомментированный init.lua, который полезно прочитать целиком). Если же нужен готовый IDE "из коробки" - присмотритесь к дистрибутиву LazyVim (его разбор - в главе 30), но держите kickstart под рукой, чтобы понимать внутренности.
- Language-specific опции - через FileType/ftplugin, а не глобально. Глобально ставьте разумный дефолт (shiftwidth = 2), а отличия (4 для Python) задавайте буфер-локально. Уважайте .editorconfig в командных проектах.
- init.vim и init.lua в одном каталоге. Neovim откажется стартовать. Выберите что-то одно (в 2026 - init.lua).
- mapleader после загрузки плагинов. Плагины захватят дефолтный \, и ваши <leader>-маппинги "не работают". Ставьте leader первой строкой (детали - глава 21).
- clipboard = 'unnamedplus' по SSH. Глушит авто-детект OSC52 - текст не попадает в локальный буфер. По SSH (если на удалёнке нет внешнего clipboard-провайдера) оставьте clipboard пустым, чтобы сработал авто-OSC52. Форсировать OSC52 строкой vim.g.clipboard = 'osc52' нельзя - это не валидное значение, и провайдер не включится; нужна таблица-провайдер vim.g.clipboard = { name = 'OSC 52', copy = {...}, paste = {...} } (полный пример - в разделе про буфер обмена выше).
- smartcase без ignorecase. Бесполезен в одиночку: smartcase лишь модифицирует поведение ignorecase.
- Путаница tabstop / shiftwidth / expandtab. Если отступы "разъезжаются", почти всегда дело в несогласованности этой тройки. Простой рецепт: expandtab = true и tabstop = shiftwidth.
- Использование vim.o для списочных опций. vim.o.listchars = {...} молча не сработает - для таблиц нужен vim.opt.
- Правка опций прямо в init.lua вместо модуля. Технически работает, но быстро превращается в монолит. Сразу выносите в lua/config/options.lua.
- Забыть про termguicolors. Без него современные темы и Treesitter-подсветка выглядят неправильно - частая причина "почему у меня цвета не как на скриншотах".
- Развёртывание с нуля. Создайте каталог ~/.config/nvim/, в нём init.lua и lua/config/options.lua. Перенесите туда стартовый конфиг из главы. Перезапустите Neovim и убедитесь, что относительные номера строк и подсветка текущей строки включились.
- Проверка путей. Выполните :echo stdpath('config'), :echo stdpath('data'), :echo stdpath('state'). Найдите в файловой системе каталог undo/ после того, как отредактируете и сохраните файл - убедитесь, что undofile действительно пишет историю.
- vim.opt против vim.o. Попробуйте задать vim.o.listchars = { tab = '>> ' } - увидите, что не работает. Затем сделайте то же через vim.opt.listchars и включите vim.opt.list = true. Сравните результат.
- Smartcase на практике. Включив ignorecase + smartcase, поищите в любом файле /todo и /TODO. Объясните себе, почему первый запрос находит больше совпадений.
- Language-specific отступ. Добавьте автокоманду (заглянув вперёд в главу 22), которая для FileType python ставит shiftwidth = 4 и tabstop = 4, не меняя глобальный дефолт 2. Проверьте на .py и .lua файлах.
- SSH-сценарий (по возможности). Если есть доступ к удалённой машине, поставьте там Neovim без clipboard = 'unnamedplus', скопируйте текст через y и попробуйте вставить в локальное приложение. Так вы увидите OSC52 в действии.
- Neovim хранит конфиг в ~/.config/nvim/init.lua по XDG-путям; Vim - в ~/.vimrc. В 2026 пишут на Lua, а не на Vimscript.
- Канон - модульная структура: init.lua лишь подключает lua/config/{options,keymaps,autocmds,lazy}.lua через require().
- В начало init.lua ставят vim.loader.enable() (кэш байткода, ~30% к старту) и vim.g.mapleader (до плагинов).
- Опции задают через vim.opt (умеет списки и методы opt:append()/opt:remove()); vim.o - для скаляров, vim.g - для глобальных переменных.
- Разумные дефолты 2026: number+relativenumber, signcolumn='yes', expandtab/shiftwidth, smartindent, ignorecase+smartcase, incsearch, undofile, scrolloff, termguicolors, updatetime, splitright/splitbelow, mouse='a', и аккуратный clipboard.
- Главный подводный камень буфера обмена: unnamedplus глушит авто-OSC52 по SSH - на удалёнке лучше оставить clipboard пустым.
- Конфиг - живой документ: держите его в git, понимайте каждую строку и расширяйте по мере роста навыка. Маппинги - глава 21, скриптинг и автокоманды - глава 22, плагины - глава 23.