Скриптинг: основы Vimscript и Lua для Neovim

Рейтинг: 64.6% · 12 голосов
Исчерпывающий курс по Vim и Neovim: модальное редактирование и грамматика (операторы, движения, текстовые объекты, точка-команда), регистры, метки, макросы, поиск и :substitute, :global, буферы/окна/quickfix, визуальный режим, конфигурация на Lua, маппинги, плагины (lazy.nvim), LSP, автодополнение (blink.cmp), Treesitter, fuzzy-поиск, Git, эргономика и антипаттерны. 34 главы, актуально на 2026 (Vim 9.1, Neovim 0.11/0.12).
Ответить
Аватара пользователя
Roman_Vim
Сообщения: 34
Зарегистрирован: 11 май 2026, 05:31

Скриптинг: основы Vimscript и Lua для Neovim

Сообщение Roman_Vim »

Оглавление курса (34)
  1. Философия Vim: модальное редактирование и почему это актуально в 2026
  2. Установка и выживание: первый запуск, режимы, как выйти
  3. Перемещение по тексту: движения как основа эффективности
  4. Базовое редактирование: вставка, удаление, копирование
  5. Грамматика Vim: операторы + движения = команды
  6. Текстовые объекты: оперируем структурой, а не символами
  7. Точка-команда и повторяемость: "один штрих, чтобы повторить"
  8. Регистры: продвинутая работа с буфером обмена
  9. Метки и прыжки: навигация по истории перемещений
  10. Поиск: /, ?, *, # и магия паттернов
  11. Замена :substitute и регулярные выражения Vim
  12. Команда :global - оружие массового редактирования
  13. Буферы, окна и вкладки: организация рабочего пространства
  14. Quickfix и location list: пакетная работа по всему проекту
  15. Навигация по файлам: netrw, fuzzy-поиск и проекты
  16. Макросы: автоматизация повторяющихся правок
  17. Визуальный режим и блочное редактирование
  18. Трюки режима вставки
  19. Свёртки, автодополнение и аббревиатуры (встроенные)
  20. Конфигурация с нуля: init.lua и разумные дефолты 2026
  21. Маппинги: leader, which-key и здравые привычки
  22. Скриптинг: основы Vimscript и Lua для Neovim (вы здесь)
  23. Менеджеры плагинов: lazy.nvim и lazy-loading
  24. Neovim в 2026: чем отличается и почему выбирают
  25. LSP в Neovim: умные возможности языка
  26. Автодополнение 2026: blink.cmp против nvim-cmp
  27. Treesitter: точная подсветка и структурное редактирование
  28. Fuzzy-поиск: Telescope, fzf-lua и snacks.picker
  29. Git внутри редактора: fugitive, gitsigns и lazygit
  30. Дистрибутивы, отладка и терминал: быстрый старт и интеграции
  31. Vim повсюду: IDE, браузер и shell
  32. Эргономика и антипаттерны: как не навредить себе и не застрять
  33. Рабочие рецепты, тренажёры и план обучения
  34. Шпаргалка-справочник: все ключевые команды одним списком
Конфигурация из главы 20 и маппинги из главы 21 - это, по сути, уже скрипты. Но рано или поздно вы упрётесь в потолок "декларативных" настроек: захочется проверить условие, пробежать по списку, вызвать функцию, выполнить действие автоматически при наступлении события. Тут начинается скриптинг - программирование самого редактора.

У вас два языка. Vimscript - родной язык Vim, на нём написаны тысячи плагинов и почти весь legacy-мир. Lua - встроенный язык Neovim, на котором сегодня пишут новые конфиги и плагины. В этой главе мы разберём оба: ровно столько, чтобы вы понимали чужой код и писали свой. Цель - не превратить вас в разработчика плагинов, а дать рабочий минимум: переменные, функции, условия, циклы, autocommands и API-вызовы.

Сразу зафиксируем установку 2026 года: новый код пишем на Lua. Vimscript нужен, чтобы читать старые конфиги, работать в чистом Vim (на сервере, где Neovim не поставить) и понимать, что делает плагин под капотом. Поэтому идём в таком порядке: сначала Vimscript как фундамент и "лингва франка" экосистемы, затем Lua как основной инструмент, и в конце - когда что выбирать.

Зачем вообще скриптить редактор

Большинство пользователей Vim/Neovim никогда не пишут больше десятка строк скрипта - и это нормально. Но даже простой конфиг постоянно сталкивается с задачами, которые без скриптинга не решить:
  • Реакция на события. "При сохранении файла - отформатируй", "при входе в файл - восстанови позицию курсора", "при копировании - подсвети скопированное". Это всё autocommands.
  • Логика в маппингах. "Если плавающее окно открыто - закрой, иначе открой" - это условие внутри функции, привязанной к клавише.
  • Динамические команды. Собрать команду из переменных и выполнить её (:execute в Vimscript, vim.cmd в Lua).
  • Чтение чужого кода. Каждый плагин - это скрипт. Понимая язык, вы чините проблемы сами, а не гадаете.
Ключевая мысль: и Vimscript, и Lua управляют одним и тем же редактором. Опции, регистры, буферы, маппинги - это общий "государственный аппарат", к которому два языка обращаются по-разному. Поэтому многое, что вы знаете про опции и регистры из прошлых глав, здесь просто получит программный интерфейс.

Часть 1. Vimscript

Переменные и области видимости (scopes)

В Vimscript переменная объявляется и присваивается командой :let, а удаляется через :unlet:

Код: Выделить всё

let count = 5
let name = "neovim"
let list = [1, 2, 3]
let dict = {'key': 'value'}
unlet count
Самая важная особенность Vimscript - префиксы областей видимости. Имя переменной несёт в себе её "адрес":

Код: Выделить всё

Префикс  |  Область                          |  Пример
---------+-----------------------------------+------------------
g:       |  глобальная (доступна везде)      |  g:mapleader
b:       |  локальная для буфера             |  b:current_syntax
w:       |  локальная для окна               |  w:my_flag
t:       |  локальная для вкладки (tab)      |  t:project
l:       |  локальная для функции            |  l:tmp
s:       |  локальная для скрипта (файла)    |  s:counter
v:       |  предопределённые переменные Vim  |  v:count, v:true
a:       |  аргумент функции                 |  a:name
&        |  опция                            |  &number, &l:wrap
@        |  регистр                          |  @a, @+, @/
$        |  переменная окружения             |  $HOME
Без префикса внутри функции переменная считается локальной (l:), а вне функции - глобальной (g:). Это частый источник путаницы, поэтому в скриптах принято писать префикс явно.

Код: Выделить всё

let g:my_setting = 1          " глобальная настройка плагина
let b:did_ftplugin = 1        " 'этот ftplugin уже отработал в буфере'
let &number = 1               " то же, что :set number
let @a = 'yyp'                " положить текст в регистр a (макрос!)
echo $HOME                    " переменная окружения
Особо отметим v:-переменные - они предоставляются самим Vim. Из главы 06 вы помните разницу между v:count и v:count1: первая возвращает реальный счётчик (0, если не задан), вторая - счётчик с дефолтом 1. В своих маппингах почти всегда нужен v:count1, иначе при вызове без числа получите 0 и сломанное поведение.

Условия и циклы

Управляющие конструкции выглядят многословно, но читаются однозначно. Каждый блок закрывается своим end-словом:

Код: Выделить всё

if &filetype ==# 'python'
  echo 'Это Python'
elseif &filetype ==# 'lua'
  echo 'Это Lua'
else
  echo 'Что-то ещё'
endif
Обратите внимание на оператор ==#. В Vimscript сравнение строк зависит от опции ignorecase, что приводит к коварным багам. Поэтому в скриптах используют явные операторы: ==# (case-sensitive) и ==? (case-insensitive), не полагаясь на настройку пользователя. Это правило того же рода, что и явный магический режим в регулярных выражениях (глава 11).

Циклы - for и while:

Код: Выделить всё

for item in [10, 20, 30]
  echo item
endfor

let i = 0
while i < 3
  echo i
  let i += 1
endwhile
Функции

Функция объявляется через function ... endfunction. Имя пользовательской функции должно начинаться с большой буквы (либо иметь scope-префикс вроде s:). Аргументы внутри тела доступны с префиксом a:.

Код: Выделить всё

function! Greet(name)
  return 'Привет, ' . a:name
endfunction

echo Greet('Vim')      " => Привет, Vim
Восклицательный знак (function!) означает "перезаписать, если функция уже определена" - это спасает от ошибки E122 при повторной загрузке конфига. Конкатенация строк - через точку . (а не +, как могло бы показаться). Особый аргумент ... означает переменное число параметров, доступных как a:1, a:2 и через a:000 (список).

:execute - выполнить собранную строку

:execute берёт строку и выполняет её как Ex-команду. Это нужно, когда команду нельзя записать статически - например, имя файла или число берётся из переменной:

Код: Выделить всё

let n = 10
execute 'normal! ' . n . 'j'       " прыгнуть на 10 строк вниз
execute 'edit ' . expand('~/.vimrc')

let pattern = 'TODO'
execute 'vimgrep /' . pattern . '/ %'
Родственная команда - :normal (выполнить нормальные команды как нажатия клавиш). Версия с ! (normal!) игнорирует пользовательские маппинги, что критично в скриптах: иначе ваш j может оказаться переопределённым плагином. Подробнее про :normal в контексте макросов - в главе 16.

Autocommands и augroup

Autocommand - это "выполни команду, когда произойдёт событие". События - это BufWritePre (перед сохранением), BufReadPost (после открытия), FileType (определён тип файла), TextYankPost (после копирования) и десятки других (полный список - :help autocmd-events).

Код: Выделить всё

autocmd BufWritePre *.py call Format()
autocmd FileType markdown setlocal wrap
Проблема голых autocmd: при каждом перечитывании конфига они накапливаются. Перечитали init.vim три раза - format-on-save сработает трижды. Решение - именованные группы augroup с очисткой autocmd!:

Код: Выделить всё

augroup MyFormatting
  autocmd!
  autocmd BufWritePre *.py call Format()
  autocmd FileType markdown setlocal wrap
augroup END
Строка autocmd! внутри группы стирает все прошлые автокоманды этой группы перед добавлением новых. Это обязательная практика - без неё дубли неизбежны.

Vim9script: что это и когда

Vim 9.0 принёс Vim9script - переработанный диалект, который компилируется в инструкции и работает в 10-100 раз быстрее классического Vimscript. Синтаксис приближен к JS/TS: var вместо let, комментарии через # вместо ", привычные (), классы и объекты (Vim 9.0+), команда :defer для гарантированной очистки ресурсов при выходе из функции.

Код: Выделить всё

vim9script
var name = 'Vim9'
def Greet(who: string): string
  return $'Привет, {who}'
enddef
echo Greet(name)
Vim9script включается строкой vim9script в начале файла (или внутри def-функций). Важно: это фича Vim, а не Neovim - Neovim сознательно не реализует Vim9script, делая ставку на Lua. Поэтому если вы пишете плагин под чистый Vim в 2026 - пишите на Vim9script. Если под Neovim - на Lua (см. ниже).

Часть 2. Lua в Neovim

Lua - маленький, быстрый язык со встроенным в Neovim интерпретатором (LuaJIT). Если вы знаете любой С-подобный язык, синтаксис освоится за час. Базовое отличие от Vimscript: одна точка входа init.lua, нормальные таблицы ({}) как единственная структура данных, и набор пространств имён vim.* для управления редактором.

Переменные в Lua

Код: Выделить всё

local count = 5            -- локальная (почти всегда нужна local)
local name = "neovim"
local list = { 1, 2, 3 }
local dict = { key = "value" }
GLOBAL = 1                 -- без local - глобальная (избегайте)
Правило: всегда пишите local. Глобальные переменные в Lua "протекают" по всему рантайму и порождают трудноуловимые конфликты. Комментарии - через --. Конкатенация строк - через ... Условия и циклы:

Код: Выделить всё

if vim.bo.filetype == "python" then
  print("Это Python")
elseif vim.bo.filetype == "lua" then
  print("Это Lua")
else
  print("Что-то ещё")
end

for _, item in ipairs({ 10, 20, 30 }) do
  print(item)
end

local function greet(who)
  return "Привет, " .. who
end
Карта пространств имён vim.*

Это сердце всего. Вместо scope-префиксов Vimscript у Lua - отдельные таблицы под каждую задачу:

Код: Выделить всё

Пространство                             |  Что это                             |  Аналог в Vimscript
-----------------------------------------+--------------------------------------+--------------------
vim.opt                                  |  опции редактора                     |  :set / &
vim.o / vim.bo / vim.wo                  |  опции (глобальные / буфер / окно)   |  &, &l:
vim.g                                    |  глобальные переменные               |  g:
vim.b / vim.w / vim.t                    |  переменные буфера / окна / вкладки  |  b: / w: / t:
vim.fn                                   |  вызов любой функции Vimscript       |  call func()
vim.api                                  |  низкоуровневый API Neovim (nvim_*)  |  - (только Neovim)
vim.keymap                               |  маппинги                            |  :map / :noremap
vim.cmd                                  |  выполнить Ex-команду / Vimscript    |  :execute
vim.diagnostic, vim.lsp, vim.treesitter  |  подсистемы                          |  - (только Neovim)
vim.opt, vim.g и доступ к настройкам

Код: Выделить всё

vim.opt.number = true            -- :set number
vim.opt.relativenumber = true
vim.opt.shiftwidth = 2
vim.opt.clipboard = "unnamedplus"

vim.g.mapleader = " "            -- глобальная переменная (leader)
vim.bo.filetype                  -- filetype текущего буфера (read)
vim.wo.wrap = false              -- wrap для текущего окна
vim.opt особенно удобен для опций-списков: он возвращает объект с методами :append(), :remove(), :prepend(), чего нет у голого присваивания:

Код: Выделить всё

vim.opt.shortmess:append("c")
vim.opt.wildignore:append({ "*.o", "*.pyc" })
Разница vim.opt vs vim.o: vim.o.option = value - это простое присваивание (как &option), а vim.opt.option - обёртка-объект, понимающая Lua-таблицы и list/map-семантику опций. Для списков берите vim.opt, для скаляров годится любой.

vim.fn - мост в Vimscript

Не всё в Neovim переписано на Lua-API; огромный пласт встроенных функций живёт в Vimscript. vim.fn вызывает любую из них:

Код: Выделить всё

local home = vim.fn.expand("~")
local line = vim.fn.line(".")          -- номер текущей строки
local files = vim.fn.glob("*.lua", false, true)  -- список файлов
local choice = vim.fn.input("Имя: ")
vim.fn.<имя>() - это ваш постоянный "переходник", пока какая-то функция не имеет нативного Lua-аналога.

vim.api - низкоуровневый API

vim.api - это семейство функций nvim_*: прямой доступ к буферам, окнам, автокомандам, подсветке. Оно мощнее и быстрее vim.fn, но многословнее. Примеры:

Код: Выделить всё

local bufnr = vim.api.nvim_get_current_buf()
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)  -- все строки буфера
vim.api.nvim_buf_set_lines(0, 0, 0, false, { "новая первая строка" })
local win = vim.api.nvim_get_current_win()
На практике вы будете комбинировать всё: vim.opt для опций, vim.keymap для клавиш, vim.api для автокоманд и нетривиальных операций с буфером, vim.fn для всего остального.

vim.keymap.set

Маппинги подробно разобраны в главе 21, поэтому здесь только напомним форму - она пригодится в наших примерах:

Код: Выделить всё

vim.keymap.set("n", "<leader>w", function()
  vim.cmd.write()
end, { desc = "Сохранить файл" })
Поле desc важно: его подхватывает which-key, делая маппинг самодокументируемым.

vim.cmd - выполнить Ex-команду из Lua

Когда нужного Lua-API нет, либо проще написать классическую команду, используйте vim.cmd:

Код: Выделить всё

vim.cmd("colorscheme habamax")
vim.cmd.colorscheme("habamax")          -- то же через таблично-функциональную форму
vim.cmd("normal! gg")
vim.cmd([[
  highlight Comment gui=italic
  syntax on
]])
Многострочный Vimscript заворачивают в [[ ... ]] (литерал строки без экранирования). Это легальный способ переиспользовать кусок старого .vimrc, пока не дошли руки переписать его на Lua. По сути vim.cmd - Lua-эквивалент :execute.

Тонкость: vim.cmd - это одновременно и функция, и таблица. Можно вызвать строкой - vim.cmd("colorscheme habamax"), а можно через табличо-функциональный сахар - vim.cmd.colorscheme("habamax"), vim.cmd.edit(f), vim.cmd.write(). Второй вид удобнее и безопаснее (аргументы не нужно склеивать вручную в строку), и дальше в примерах он встречается именно в такой форме.

Autocommands на Lua

Нативный способ Neovim - vim.api.nvim_create_autocmd + vim.api.nvim_create_augroup. Это прямой аналог augroup/autocmd из Vimscript, но колбэком может быть Lua-функция:

Код: Выделить всё

local grp = vim.api.nvim_create_augroup("MyConfig", { clear = true })

-- Подсветить скопированный текст
vim.api.nvim_create_autocmd("TextYankPost", {
  group = grp,
  callback = function()
    vim.hl.on_yank({ timeout = 200 })  -- начиная с Neovim 0.11; vim.highlight - устаревший алиас
  end,
})

-- Включить wrap только для markdown
vim.api.nvim_create_autocmd("FileType", {
  group = grp,
  pattern = "markdown",
  callback = function()
    vim.wo.wrap = true
  end,
})

-- Удалить хвостовые пробелы перед сохранением
vim.api.nvim_create_autocmd("BufWritePre", {
  group = grp,
  pattern = "*",
  callback = function()
    local view = vim.fn.winsaveview()
    vim.cmd([[silent! %s/\s\+$//e]])
    vim.fn.winrestview(view)
  end,
})
Ключевой параметр - group с clear = true при создании augroup. Ровно как autocmd! в Vimscript, он защищает от накопления дублей при перечитывании конфига. Вместо callback можно передать command = "..." (строка Ex-команды), но колбэк-функция гибче и читается лучше.

Сравнение "одно и то же на двух языках"

Код: Выделить всё

Задача                 |  Vimscript                  |  Lua (Neovim)
-----------------------+-----------------------------+-----------------------------------------------------------------------------------------
Опция                  |  set number                 |  vim.opt.number = true
Глобальная переменная  |  let g:x = 1                |  vim.g.x = 1
Вызвать функцию        |  echo expand('~')           |  vim.fn.expand("~")
Маппинг                |  nnoremap <leader>w :w<CR>  |  vim.keymap.set("n", "<leader>w", ":w<CR>") (для бесшумности добавьте { silent = true })
Выполнить команду      |  execute 'edit ' . f        |  vim.cmd.edit(f)
Группа автокоманд      |  augroup X / autocmd!       |  nvim_create_augroup("X", {clear=true})
Автокоманда            |  autocmd BufWritePre * ...  |  nvim_create_autocmd("BufWritePre", {...})
Практические примеры "было -> пишем -> стало"

Пример 1. Тоггл подсветки результатов поиска. Хотим по <leader>h переключать hlsearch. Голым маппингом это не сделать - нужна логика.

Код: Выделить всё

-- было: hlsearch включён, мешает после поиска
vim.keymap.set("n", "<leader>h", function()
  vim.opt.hlsearch = not vim.opt.hlsearch:get()
end, { desc = "Тоггл подсветки поиска" })
-- стало: одна клавиша включает/выключает подсветку
Здесь работает то, что vim.opt.<булева опция>:get() возвращает именно boolean (true/false), а не 0/1, - поэтому not ...:get() корректно инвертирует значение.

Пример 2. Восстановление позиции курсора при открытии файла. Классический рецепт: при BufReadPost прыгнуть туда, где вы были в прошлый раз (метка ").

Код: Выделить всё

vim.api.nvim_create_autocmd("BufReadPost", {
  group = vim.api.nvim_create_augroup("RestoreCursor", { clear = true }),
  callback = function(args)
    local mark = vim.api.nvim_buf_get_mark(args.buf, '"')
    local lcount = vim.api.nvim_buf_line_count(args.buf)
    if mark[1] > 0 and mark[1] <= lcount then
      pcall(vim.api.nvim_win_set_cursor, 0, mark)
    end
  end,
})
-- стало: открыли файл - курсор сразу на месте последней правки
Пример 3. Команда, собранная динамически. Хотим открыть конфиг по короткой команде.

Код: Выделить всё

" Vimscript
command! Config execute 'edit ' . stdpath('config') . '/init.vim'

Код: Выделить всё

-- Lua-эквивалент
vim.api.nvim_create_user_command("Config", function()
  vim.cmd.edit(vim.fn.stdpath("config") .. "/init.lua")
end, { desc = "Открыть конфиг" })
-- стало: :Config открывает init.lua откуда угодно
Пример 4. Маппинг с учётом счётчика (v:count1). Хотим, чтобы <leader>j без числа двигал на 1 строку, а с числом - на N. "Условности" в коде нет (никакого if) - поведение целиком обеспечивает семантика count1, который сам подставляет 1 при отсутствии числа.

Код: Выделить всё

vim.keymap.set("n", "<leader>j", function()
  vim.cmd("normal! " .. vim.v.count1 .. "j")
end, { desc = "Прыжок на N строк" })
-- 3<leader>j -> вниз на 3, просто <leader>j -> вниз на 1 (а не на 0!)
Лучшие практики 2026
  • Пишите новые конфиги и плагины на Lua. Это стандарт экосистемы: быстрее, чище масштабируется, имеет полноценный доступ к нативным подсистемам Neovim (LSP, Treesitter, diagnostics), которых в Vimscript-мире просто нет. Vimscript держите для чтения legacy и работы в чистом Vim.
  • Любую автокоманду - в именованный augroup с clear = true. Это снимает целый класс багов с дублированием (format-on-save N раз). Правило одинаково обязательно и в Vimscript (autocmd!), и в Lua.
  • Минимизируйте vim.cmd и vim.fn, предпочитая нативный API. vim.cmd([[...]]) - отличный мост для миграции, но не цель. Где есть vim.opt, vim.keymap, vim.api.nvim_* - берите их: они типобезопаснее и не зависят от парсинга строк.
  • В чистом Vim новые скрипты - на Vim9script. var, #-комментарии, :defer, классы дают прирост скорости в 10-100 раз и читаемость уровня TS. Но помните: Neovim его не поддерживает - это разные миры.
  • local по умолчанию в Lua, scope-префикс по умолчанию в Vimscript. Глобальные переменные без необходимости - источник конфликтов в обоих языках.
  • Оборачивайте рискованные вызовы в pcall. В Lua pcall(fn, args) ловит ошибку и не роняет конфиг целиком - особенно важно в автокомандах и при работе с буферами, которых может уже не быть.
  • Используйте ==#/==? в Vimscript всегда. Не полагайтесь на ignorecase пользователя при сравнении строк в скриптах.
Частые ошибки
  • Голый autocmd без группы. При повторной загрузке конфига автокоманды накапливаются. Лечится augroup+autocmd! (Vimscript) или augroup с clear = true (Lua).
  • v:count вместо v:count1 в маппингах. Без счётчика получаете 0 - действие схлопывается в ничто. Для "по умолчанию один раз" нужен v:count1 / vim.v.count1.
  • Сравнение строк через == в Vimscript. Результат зависит от ignorecase и "плавает" у разных пользователей. Пишите ==#.
  • Забытый local в Lua. Переменная утекает в глобальный рантайм и конфликтует с плагинами. Почти всегда нужен local.
  • Путаница vim.opt и vim.o. Для опций-списков (wildignore, shortmess) нужен vim.opt с :append()/:remove(); голый vim.o со списком не дружит.
  • Маппинг через :normal вместо :normal!. В скрипте пользовательские маппинги могут переопределить нужные клавиши; версия с ! их игнорирует.
  • Ожидание, что Vim9script заработает в Neovim. Не заработает - Neovim его не реализует. И наоборот: Lua-API vim.* не существует в чистом Vim.
  • function без ! при перезагрузке. Повторное определение упадёт с E122; пишите function!.
Упражнения
  1. Tоггл-маппинг. Напишите на Lua маппинг <leader>n, который переключает relativenumber. Используйте vim.opt.relativenumber:get() и присваивание not ....
  2. Format-on-save. Создайте augroup с clear = true и автокоманду BufWritePre, которая удаляет хвостовые пробелы только в файлах *.lua и *.py (используйте pattern). Перезагрузите конфиг дважды и убедитесь, что чистка происходит один раз, а не дважды.
  3. Перевод с Vimscript на Lua. Дано: autocmd FileType gitcommit setlocal spell textwidth=72. Перепишите это автокомандой на Lua через nvim_create_autocmd с колбэком, выставляющим vim.wo.spell и vim.bo.textwidth.
  4. Пользовательская команда. Сделайте команду :Reload, которая перечитывает ваш init.lua (подсказка: vim.cmd.source(vim.fn.stdpath("config") .. "/init.lua")). Подумайте, какие части конфига от такого перечитывания могут задублироваться и почему clear = true это спасает.
  5. Функция с условием. Напишите Lua-функцию smart_home(), которая по нажатию 0 прыгает на первый непробельный символ строки, а при повторном нажатии - в самое начало (колонка 0). Привяжите к 0 в normal-режиме.
  6. Vimscript-разминка. В чистом Vim напишите функцию Wc(), которая через :execute и wordcount() выводит число слов в текущем буфере, и привяжите к <leader>wc. Сравните с тем, как то же делается на Lua через vim.fn.wordcount().
Главное из главы
  • Скриптинг нужен там, где декларативных настроек мало: события (autocommands), логика в маппингах, динамические команды.
  • Vimscript - родной язык Vim: переменные с scope-префиксами (g:/b:/w:/v:/&/@), функции (function!), условия/циклы, :execute для динамики, augroup+autocmd! против дублей. Для нового кода в чистом Vim 2026 - Vim9script (быстрее в 10-100 раз, синтаксис как TS).
  • Lua - основной язык Neovim: local-переменные, таблицы, и пространства vim.* - vim.opt (опции), vim.g (глобалы), vim.fn (мост в Vimscript), vim.api (нативный API), vim.keymap (клавиши), vim.cmd (Ex-команды). Автокоманды - через nvim_create_autocmd + nvim_create_augroup({clear=true}).
  • Установка 2026: Lua-first для Neovim, Vimscript/Vim9script - для чистого Vim и чтения legacy. Эти миры не пересекаются: Vim9script не работает в Neovim, vim.* не существует в Vim.
  • Везде: автокоманды в группах с очисткой, local/scope-префиксы по умолчанию, v:count1 в маппингах, pcall вокруг рискованного.
Дальше - глава 23, где весь этот скриптинг применяется на практике: менеджеры плагинов lazy.nvim и vim.pack описывают плагины Lua-таблицами и используют те же autocommands и события для ленивой загрузки.
👍5 ❤️1 🔥1 😄 🤔3
✔ Лучший ответ сформирован автоматически — Mexxman
Roman_Vim писал(а):первая возвращает реальный счётчик (0, если не задан), вторая - счётчик с дефолтом 1 вот это меня в свое время убило. сделал маппинг через v:count, без числа жал leader+j и курсор никуда не двигался. count1 решает, теперь и в примере 4 это видно
Перейти к ответу →
Аватара пользователя
rfajr1958
Сообщения: 1
Зарегистрирован: 13 май 2026, 22:20

Re: Скриптинг: основы Vimscript и Lua для Neovim

Сообщение rfajr1958 »

вот про augroup с clear=true прям отдельное спасибо. у меня format-on-save срабатывал по три раза и хвостовые пробелы то удаляло то нет, я думал плагин конфликтует. оказалось просто перечитывал init.lua и автокоманды накапливались. добавил clear=true и все, один раз.
👍 ❤️1 🔥 😄 🤔
Аватара пользователя
rustmaker
Сообщения: 1
Зарегистрирован: 12 май 2026, 04:32

Re: Скриптинг: основы Vimscript и Lua для Neovim

Сообщение rustmaker »

а можно вопрос про vim.opt vs vim.o. я долго не мог понять почему vim.o.wildignore со списком ломается, а в уроке прямо написано - для списков бери vim.opt с :append(). поставил vim.opt.wildignore:append({"*.pyc"}) и заработало сразу
👍2 ❤️1 🔥 😄 🤔1
Аватара пользователя
Mexxman
Сообщения: 1
Зарегистрирован: 17 май 2026, 13:36

Re: Скриптинг: основы Vimscript и Lua для Neovim

Сообщение Mexxman »

✔ Лучший ответ — сформирован автоматически
Roman_Vim писал(а):первая возвращает реальный счётчик (0, если не задан), вторая - счётчик с дефолтом 1
вот это меня в свое время убило. сделал маппинг через v:count, без числа жал leader+j и курсор никуда не двигался. count1 решает, теперь и в примере 4 это видно
👍2 ❤️ 🔥1 😄 🤔
Аватара пользователя
robo83kc
Сообщения: 1
Зарегистрирован: 08 июн 2026, 13:00

Re: Скриптинг: основы Vimscript и Lua для Neovim

Сообщение robo83kc »

коротко - лучшая глава по скриптингу что читал. наконец уложилось когда vim.fn а когда vim.api
👍 ❤️4 🔥 😄 🤔
Аватара пользователя
torgeek
Сообщения: 1
Зарегистрирован: 28 май 2026, 11:49

Re: Скриптинг: основы Vimscript и Lua для Neovim

Сообщение torgeek »

добавлю свой лайфхак из примера 1, тоггл hlsearch повесил на leader+h и реально удобно после поиска подсветку гасить. только не забудьте :get() в конце, без него not над обычной опцией не то вернет
👍 ❤️ 🔥 😄 🤔
Ответить
← Предыдущая глава
Маппинги: leader, which-key и здравые привычки
Следующая глава →
Менеджеры плагинов: lazy.nvim и lazy-loading

Все главы курса «Vim и Neovim: модальное редактирование от основ до современного Neovim»

Поделиться темой: ✈ Telegram VK
  • Похожие темы

Вернуться в «Vim и Neovim: модальное редактирование от основ до Neovim 2026»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость