Хорошая новость – Neovim не стоит на месте и развивается. Многие вещи, которые раньше реализовывались громоздкими и тормозными плагинами, становятся встроенными.
Что появилось нового в 2025?
:restart
— данная команда перезапускает редактор, применяя новые настройки. Прошло более 10 лет прежде чем по просьбам трудящихся та была добавлена. Раньше приходилось для этого выходить из редактора и запускать его по новой.- Встроенный менеджер плагинов.
- Поддержка автодополнения.
Для демонстрации новых возможностей я набросал небольшой конфиг. Структура файлов:
.
├── init.lua
└── lua
└── user
├── autocmds.lua
├── autopairs.lua
├── gitsigns.lua
├── keymaps.lua
├── lsp.lua
├── options.lua
├── plugins.lua
└── treesitter.lua
Как опробовать?
В репозиториях популярных дистрибутивов Neovim все еще старый (0.11.4 на момент написания), а поэтому нужно пользоваться неофициальными/сторонними.
В Arch Linux вы можете поставить пакет neovim-git
из AUR либо из репозиториев archlinuxcn
или chaotic-aur
(последние предпочтительнее):
sudo pacman -S neovim-git
Менеджер плагинов
В Neovim 0.12 появился менеджер плагинов, который в отличие от встроенного вимовского packadd
умеет их скачивать с помощью git.
./lua/user/plugins.lua
vim.pack.add({
"https://github.com/neovim/nvim-lspconfig",
"https://github.com/williamboman/mason.nvim",
"https://github.com/williamboman/mason-lspconfig.nvim",
"https://github.com/nvim-treesitter/nvim-treesitter",
"https://github.com/nvim-mini/mini.pairs",
"https://github.com/lewis6991/gitsigns.nvim",
"https://github.com/folke/which-key.nvim",
{ src = "https://github.com/catppuccin/nvim", name = "catppuccin" },
})
Для обновления же можно использовать vim.pack.update()
.
Плюсы:
- встроенный;
- простой;
- поддерживает git.
Минусы:
- более медленный чем lazy.nvim, так как не умеет динамически подгружать модули-плагины по мере необходимости.
LSP и автодополнение
В данном примере я использовал классику в виде связки nvim-lspconfig
(содержит настройки) и Mason (позволяет ставить не только сервера, но и линтеры, дебагеры и тп). Останавливаться на их описании я не буду. Они всем хорошо должны быть известны.
Конфиг может выглядеть примерно так:
./lua/user/lsp.lua
-- h: lsp
-- Mason + LSP
require("mason").setup()
require("mason-lspconfig").setup({
-- Остально можно поставить через :Mason
ensure_installed = {
"vimls",
"lua_ls",
"bashls",
"pyright",
"ruff",
},
})
local capabilities = vim.lsp.protocol.make_client_capabilities()
-- если используются плагины для автодополнения, то можно обновить capabilities
vim.lsp.config("*", {
capabilities = capabilities,
-- Точка тут как костыль
root_markers = { '.git', '.' },
})
-- Отображение диагностических сообщений
vim.diagnostic.config({
virtual_lines = {
current_line = true,
-- Показываем только ворнинги
-- severity = vim.diagnostic.severity.WARN,
},
-- Сначала показываем ошибки, потом — остальное
severity_sort = true,
--update_in_insert = true,
})
-- Автодополнение и автоматическое форматирование
vim.opt.completeopt:append { "menuone", "noselect", "popup", "fuzzy" }
vim.opt.shortmess:append('c')
local function pumvisible()
return tonumber(vim.fn.pumvisible()) ~= 0
end
-- Предварительно очищаем группу
vim.api.nvim_create_augroup("lsp", { clear = true })
local lsp_group = vim.api.nvim_create_augroup("lsp", { clear = false })
vim.api.nvim_create_autocmd('LspAttach', {
group = lsp_group,
callback = function(args)
-- Чтобы сбросить настройки по умолчанию
-- -- Unset 'formatexpr'
-- vim.bo[args.buf].formatexpr = nil
-- -- Unset 'omnifunc'
-- vim.bo[args.buf].omnifunc = nil
-- -- Unmap K
-- vim.keymap.del('n', 'K', { buffer = args.buf })
-- -- Disable document colors
-- vim.lsp.buf.document_color.enable(false, args.buf)
local function keymap(lhs, rhs, opts, mode)
opts = opts or {}
if type(opts) == "string" then
opts = { desc = opts }
end
opts = vim.tbl_extend("force", { buffer = args.buf, silent = true }, opts)
vim.keymap.set(mode or 'n', lhs, rhs, opts)
end
local client = assert(vim.lsp.get_client_by_id(args.data.client_id))
local methods = vim.lsp.protocol.Methods
-- Проверяем поддерживается ли автодополнение
if client:supports_method(methods.textDocument_completion) then
-- По умолчанию автодополнение срабатывает лишь при вводе ".", нам же
-- нужно чтобы меню дополнения отображалось при вводе любых непробельных
-- символов
local chars = {}
for i = 32, 126 do
table.insert(chars, string.char(i))
end
client.server_capabilities.completionProvider.triggerCharacters = chars
-- Включаем автодополнение
vim.lsp.completion.enable(true, client.id, args.buf, {
autotrigger = true
})
-- Добавим привычные сочетания
keymap('<C-Space>', vim.lsp.completion.get, "Show completion menu", 'i')
-- Предотвращаем вставку переноса строки при выборе с помощью Enter
keymap('<cr>', function()
if pumvisible() then
return '<c-y>'
else
return '<cr>'
end
end, { expr = true }, 'i')
-- Выбор вариантов с помощью Tab
keymap('<tab>', function()
if pumvisible() then
return '<c-n>'
else
return '<tab>'
end
end, { expr = true }, 'i') -- Возможно, нужно указывать режимы {'i', 's'}
keymap('<s-tab>', function()
if pumvisible() then
return '<c-p>'
else
return '<s-tab>'
end
end, { expr = true }, 'i')
end
-- Автодополнение для LLM
if client:supports_method(methods.textDocument_inlineCompletion) then
vim.lsp.inline_completion.enable(true, { bufnr = args.buf })
keymap('<c-cr>', function()
if not vim.lsp.inline_completion.get() then
return "<c-cr>"
end
end, { expr = true, replace_keycodes = true }, 'i')
end
-- Так же можно добавить другие сочетания
-- Для go to definition ипользуется стандартное сочетание Ctrl-], но можно
-- объявить свое
if client:supports_method('textDocument/definition') then
-- keymap('gd', vim.lsp.buf.definition, "Go to definition")
end
if client:supports_method('textDocument/inlayHint') then
keymap('<leader>ih', function()
vim.lsp.inlay_hint.enable(not vim.lsp.inlay_hint.is_enabled())
end, "Toggle inlay hints")
end
-- Автоматическое форматирование перед сохранением
if not client:supports_method('textDocument/willSaveWaitUntil')
and client:supports_method('textDocument/formatting') then
vim.api.nvim_create_autocmd('BufWritePre', {
group = lsp_group,
buffer = args.buf,
callback = function()
vim.lsp.buf.format({
bufnr = args.buf,
id = client.id,
timeout_ms = 1000,
async = false
})
end,
})
end
-- Сигнатуру можно посмотреть с помощью Ctrl-S в режиме редактирования
if client:supports_method('textDocument/signatureHelp') then
vim.api.nvim_create_autocmd('CursorHoldI', {
group = lsp_group,
pattern = '*',
callback = function()
-- silent подавляет ошибки типа "No signature help available"
vim.lsp.buf.signature_help({
silent = true,
focus = false
})
end
})
end
end
})
Собственно автодополнение включается с помощью вызова vim.lsp.completion.enable(true)
. Функция vim.lsp.completion.get()
триггерит автодополнение. Рекомендованными настройками для отображения дополнений в всплывающем окне является такое значение:
vim.opt.completeopt = { "menuone", "noselect", "popup" }
Я его слегка мофицировал. Вместо noselect
можно использовать noinsert
, тогда при нажатии Enter
или Ctrl-Y
будет вставлен первый вариант. Это не всегда удобно. Ctrl-N
/Ctrl-P
/Стрелки вверх и вниз служат для выбора вариантов. Автодоплнение можно вызвать принудительно с помощью Ctrl-X
, Ctrl-O
, но мне больше нравится Ctrl-Space
как в большинсиве редакторов.
Остальные файлы
Для демонстрации новых возможностей так же понадобятся дополнительные файлы.
Главный конфиг:
./init.lua
local g = vim.g
-- Клавишу leader надо объявить до загрузки плагинов
g.mapleader = ' '
g.maplocalleader = g.mapleader
if vim.fn.has('termguicolors') == 1 then
-- Некоторые плагины могут неправильно работать, если не установить предварительно это значение
vim.opt.termguicolors = true
end
-- Плагины и их настройка
require("user.plugins")
require("user.lsp")
require("user.treesitter")
require("user.autopairs")
require("user.gitsigns")
-- Настройки должны идти после плагинов, так как те могут их переопределить
require("user.options")
require("user.keymaps")
require("user.autocmds")
Базовые настройки редактора:
./lua/user/options.lua
local opt = vim.opt
-- Нумерация строк
opt.number = true
-- Мне не нравится относительная нумерация строк
-- opt.relativenumber = true
-- Подсветка текущей строки
opt.cursorline = true
-- Прокрутка
opt.scrolloff = 8
opt.sidescrolloff = 8
-- Отображение элементов интерфейса
opt.signcolumn = "yes"
opt.laststatus = 3
-- Только при использовании statusline
-- opt.showmode = false
opt.shortmess:append("I")
-- Перенос строк
opt.linebreak = true
-- Отображение различных символов
opt.list = true
opt.listchars = {
tab = '→ ',
lead = '·',
trail = '·',
nbsp = '␣',
extends = '⟩',
precedes = '⟨',
}
-- Отступы и форматирование
opt.expandtab = true
opt.tabstop = 4
opt.shiftwidth = 2
opt.softtabstop = 2
opt.autoindent = true
opt.smartindent = true
opt.breakindent = true
opt.textwidth = 80
opt.colorcolumn = "+1"
opt.formatoptions = {
c = true,
q = true,
j = true,
r = true,
n = true,
l = true,
}
-- Максимальный размер строки для подсветки
opt.synmaxcol = 1024
-- Поиск
opt.ignorecase = true
opt.smartcase = true
opt.inccommand = "split"
-- Файлы и буферы
opt.clipboard = "unnamedplus"
opt.swapfile = false
opt.backup = false
opt.writebackup = false
opt.autoread = true
opt.undofile = true
opt.undolevels = 1000
-- opt.shada = "!,'1000,<50,s10,h"
opt.confirm = true
-- Разделение окон
opt.splitbelow = true
opt.splitright = true
-- Фолдинг
opt.foldmethod = "expr"
opt.foldlevelstart = 99
opt.foldenable = true
-- Мышь, перемещение курсора и выделение текста
opt.mouse = "a"
opt.mousemoveevent = true
opt.whichwrap = 'h,l,<,>,[,]'
-- opt.keymodel = "startsel,stopsel"
-- В vim есть встроенная поддержка русской раскладки. Мы можем переключаться на нее и обратно с помощью Ctrl-6, при этом при включенной русской, связки клавиш продолжат работать.
opt.keymap = "russian-jcukenwin"
opt.iminsert = 0
opt.imsearch = 0
-- Автодополнение
opt.wildmenu = true
opt.wildmode = "longest:full,full"
opt.wildignore:append {
'*.o',
'*.obj',
'*.pyc',
'*.so',
'.git',
'.svn',
'__pycache__',
}
-- Сессии
opt.sessionoptions:append("localoptions")
-- Таймауты
opt.timeoutlen = 500
opt.updatetime = 200
-- Тема
vim.cmd.colorscheme [[catppuccin-macchiato]]
Автоматические команды, которым не нашлось места в других местах:
./lua/user/autocmds.lua
-- Восстановление позиции курсора при открытии файла
vim.api.nvim_create_autocmd("BufReadPost", {
callback = function(ev)
local row, col = unpack(vim.api.nvim_buf_get_mark(ev.buf, '"'))
if row > 0 and row <= vim.api.nvim_buf_line_count(ev.buf) then
pcall(vim.api.nvim_win_set_cursor, 0, { row, col })
end
end,
})
Основные сочетания клавиш:
./lua/user/keymaps.lua
local function cmd(command)
return "<Cmd>" .. command .. "<CR>"
end
local wk = require("which-key")
wk.setup()
wk.add({
-- Files
{ "<leader>e", vim.cmd.Ex, desc = "Explorer (:Ex)" },
-- Buffers
{ "<leader>b", ":ls<CR>:b<Space>", desc = "Buffers" },
{ "<tab>", vim.cmd.bnext, desc = "Next buffer" },
{ "<s-tab>", vim.cmd.bprev, desc = "Prev buffer" },
-- Navigation for wrapped lines
{ "k", "gk", silent = true },
{ "j", "gj", silent = true },
{ "<Up>", "gk", silent = true },
{ "<Down>", "gj", silent = true },
-- Windows
{ "<c-h>", "<c-w>h", desc = "Left window" },
{ "<c-j>", "<c-w>j", desc = "Down window" },
{ "<c-k>", "<c-w>k", desc = "Up window" },
{ "<c-l>", "<c-w>l", desc = "Right window" },
-- ... еще сочетания
{
"<leader>?",
function()
wk.show({ global = false })
end,
desc = "Buffer Local Keymaps (which-key)",
},
{
"<leader>k",
wk.show,
desc = "Show all keyamps"
},
})
В конфиге выше используется which-key
. Этот плагин следовало бы сделать встроенным как в helix
, так как он служит для напоминания связок, которые постоянно забываешь.
Treesitter — это библиотека для парсинга исходников и построения синтаксического дерева с которым в дальнейшем работает LSP. Является зависимостью редактора, а вот одноименный плагин лишь содержит ссылки для скачивания и установки парсеров различных языков:
./lua/user/treesitter.lua
require("nvim-treesitter.configs").setup({
-- https://github.com/nvim-treesitter/nvim-treesitter#supported-languages
ensure_installed = { "vim", "lua", "bash", "python" },
sync_install = false,
auto_install = true,
highlight = { enable = true },
indent = { enable = true },
})
Закрытие парных скобок и кавычек:
./lua/user/autopairs.lua
require('mini.pairs').setup()
Отображает слева перед номерами строк, имеющиеся изменения в git:
./lua/user/gitsigns.lua
require('gitsigns').setup()
Заключение
Встроенное автодополнение реализовано довольно просто. Нам не нужно писать для этого много строчек конфигов. С менеджерами плагинов же ситуация до этого была плачевной: был «родной» lazy.nvim
, который как бы является частью другого проекта (одноименная сборка), и vim-plug
, созданный для старшего брата (vim) и требующий использования Vim Script. Первый к тому же довольно громоздкий, что заставляло пользоваться устаревшими альтернативами, проектами заброшенными годы назад. Пришло время переписывать конфиги.