LINUX.ORG.RU

Обзор новых возможностей редактора Neovim

 2025, , ,


1

1

Хорошая новость – 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. Первый к тому же довольно громоздкий, что заставляло пользоваться устаревшими альтернативами, проектами заброшенными годы назад. Пришло время переписывать конфиги.

★★★

Проверено: hobbit ()
Последнее исправление: rtxtxtrx (всего исправлений: 13)

Проблема lazy.nvim не в громоздкости, а в его хаках. Тем не менее, это лучший менеджер плагинов на данный момент.

Когда 0.12 войдет во все дистрибутивы, тогда можно будет попробовать воспользоваться нативной поддержкой плагинов. Или сразу перейти на mini.deps.

andreyu ★★★★★
()

Как всегда проблевался от кода на луа. И зачем бы эта дрисня в настройках редактора? Вопрос риторический.

bread
()

В кои-то веки норм статья от rtxtxtrx :)


Плюсы:

встроенный;
[…]

Минусы:

более медленный чем lazy.nvim

Дык «встроенный» тогда это не плюс, а минус. Встроенность хороша, если она производительность даёт. А если нет, то модульное решение всегда лучше. Появилось что получше, чем lazy.nvim — взял да заменил, решил форкнуть neovim зачем-то — можешь спокойно продолжать юзать lazy.nvim, который прекрасно обновляется, и т. д.

CrX ★★★★★
()
Ответ на: комментарий от andreyu

Когда 0.12 войдет во все дистрибутивы, тогда можно будет попробовать воспользоваться нативной поддержкой плагинов.

Как можно было делать заново вим, но пройтись по тем же граблям с управлением плагинами? Совершенно не понятно.

bread
()
Последнее исправление: bread (всего исправлений: 1)
Ответ на: комментарий от CrX

модульное решение всегда лучше

Такая модульность курильщика абсолютна не нужна. Так же, как тысяча тупых недоделанных реализаций вяленда.

bread
()
Ответ на: комментарий от einhander

Декларативный конфиг. На вимскрипте получается намного приятнее. Для плагинов предлагаю тоже вимскрипт в варианте vim9. Ну и в качестве самого вима сам vim9. Если очень хочется что-то новенькое, то helix.

bread
()
Ответ на: комментарий от CrX

Встроенность хороша, если она производительность даёт.

А это производительность прям вот сильно нужна, чтобы обновить несколько плагинов? Прям вот deal breaker? Какая там разница?

модульное решение всегда лучше

Нет, не лучше. Лучше когда базовый функционал встроен и можно просто использовать, а не качать 100500 конфликтующих, разваливающихся плагинов.

MoldAndLimeHoney ★★
()
Ответ на: комментарий от MoldAndLimeHoney

А это производительность прям вот сильно нужна, чтобы обновить несколько плагинов?

Не обновить, а загрузить.

Прям вот deal breaker? Какая там разница?

Разница во времени старта и полного открытия файла. Если плагинов много, то это может быть целая секунда, а то и две или три.

Но дело не в том, deal breaker это, или нет. Просто если встроенность даже производительности не даёт, а наоборот уступает давным давно готовому и прекрасно работающему решению, то она получается какой-то бесполезной. Неужели нельзя было сделать хотя бы так же?

CrX ★★★★★
()
Последнее исправление: CrX (всего исправлений: 1)
Ответ на: комментарий от bread

Декларативный конфиг. На вимскрипте получается намного приятнее. Для плагинов предлагаю тоже вимскрипт в варианте vim9

то-то я помню, как в neovim все пилили плагины на питонах, жабаскриптах, и вообще на чем угодно кроме vimscript. И как все моментально сбежали на lua, как только она там появилась.

Так что нет, не надо рассказывать офигительные истории про декларативные конфиги на вот этом всем. Проходили уже, не понравилось.

Lrrr ★★★★★
()
Ответ на: комментарий от bread

Если очень хочется что-то новенькое, то helix.

Этот не покатит, его придется использовать по назначению а не писать бесконечные статьи как его настроить.

mx__ ★★★★★
()

Раньше приходилось для этого выходить из редактора и запускать его по новой.

А ведь всем известно, как трудно выйти из vim.

knovich
()
Ответ на: комментарий от mx__

Видел я, как один товарищ поставил себе этот helix и пытался в нём работать. Тоже говорил что не надо настраивать.

На практике получается, что если какая-то функция там реализована, то ну да, как-то работает. А если не реализована - то всё, иди пиши регексы.

Тут кстати завелась одна любительница регексов с большим мозгом, надо ей это показать.

Lrrr ★★★★★
()
Ответ на: комментарий от bread

helix не работает. его автор его ломает постоянно. ну тот что в репе арча нерабочий. там lsp отваливается как только приходит какое-то неожиданное поле. вместо того чтобы проигнорировать его и продолжить работу, там предпочитают упасть. короче раст головного мозга и тупизированное мышление.

rtxtxtrx ★★★
() автор топика
Последнее исправление: rtxtxtrx (всего исправлений: 1)
Ответ на: комментарий от Lrrr

то-то я помню, как в neovim все пилили плагины на питонах, жабаскриптах, и вообще на чем угодно кроме vimscript.

Логично, что хейтеры вимскрипта не захотели на нем писать. Вот только почему-то плагины на луа ещё страшнее и глюкавее.

bread
()
Ответ на: комментарий от rtxtxtrx

Может он еще сырой, но это редактор с вменяемой концепцией. Востребованные фичи из коробки, а не в виде кривых скриптов от васяна. Настройки максимально простые. И управление там не совсем vi-подобное, а переосмысленное. Так что хороший проект, а вот тупо форкнуть вим, всё там сломать и потом 10 лет накладывать скриптоговны, это очень убого.

bread
()
Ответ на: комментарий от bread

сам давай как-то без меня. мне нет дела до этого поделия

rtxtxtrx ★★★
() автор топика

мы не устанем повторять
дело не в вимскрипте или луа, дело в апи

Bad_ptr ★★★★★
()

Чего я только не понял как сделать - как отображать превью при переборе вариантов автоподстановки. Я много этих конфигов посмотрел… У черта-гпт бесполезно спрашивать, он пока не обновит индекс, не будет знать про эти возможности

rtxtxtrx ★★★
() автор топика
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.