Перейти к содержимому

Neovim глубоко: модальность, LSP, treesitter и LazyVim

Если tmux и Omarchy — это среда, то Neovim — место, где происходит сама работа. И это единственный инструмент в стеке, к которому относятся как к религии: кто-то бросает после первого часа («как из него выйти?!»), кто-то через месяц уже не представляет, как редактировал код иначе. Разница между этими людьми не в таланте, а в том, поняли ли они одну вещь: Vim — это не редактор с хоткеями. Это язык.

В краткой версии учебника мы советовали «начни с VS Code, Neovim добавь позже». Позже наступило. В этой главе разбираем модальность как грамматику — оператор плюс движение, текст-объекты, повтор через точку, макросы, регистры — а затем современный слой, который превращает vim в полноценную IDE: LSP, treesitter, fuzzy-поиск. Конфигурацию покажем на примере LazyVim — дистрибутива, который даёт всё это из коробки, не заставляя собирать редактор из двухсот плагинов вручную.

Честное предупреждение из практики: первые три-четыре дня ты будешь медленнее, чем в своём старом редакторе. Это нормально, это цена переучивания мышечной памяти. На второй неделе скорость сравняется, через месяц — уйдёт вперёд и уже никогда не вернётся обратно. В конце главы есть план тренировки на две недели, который проведёт через этот провал.

В обычном редакторе у тебя один режим: печатаешь — текст появляется, хочешь удалить — жмёшь Delete по одному символу или выделяешь мышью. В Vim режимов несколько: normal (навигация и команды), insert (ввод текста), visual (выделение), command (команды через :). Культура Vim: insert-режим — это краткие вылазки. Большую часть времени ты в normal-режиме, где клавиши — не буквы, а глаголы и существительные.

Грамматика такова: оператор + движение (или текст-объект). Операторы: d — удалить (delete), c — изменить (change: удалить и перейти в insert), y — скопировать (yank), > — сдвинуть вправо, v — выделить. Движения: w — слово вперёд, b — слово назад, $ — конец строки, 0 — начало, % — парная скобка, } — следующий абзац. Складываешь — получаются предложения:

dw — удалить слово (delete word)
d$ — удалить до конца строки
ciw — изменить слово целиком (change inner word), курсор где угодно внутри слова
di" — удалить содержимое внутри кавычек, сами кавычки оставить
dt, — удалить до запятой (delete till comma), не включая её
ya( — скопировать всё внутри круглых скобок вместе со скобками

Обрати внимание на ciw против dw: dw удаляет от курсора до конца слова, а ciw — слово целиком, где бы курсор внутри него ни стоял. Это и есть текст-объекты: iw — inner word, i" — внутри кавычек, a( — вокруг скобок включая их, ip — абзац, it — внутри XML/HTML-тега. Текст-объекты оперируют смысловыми единицами, а не позициями курсора. Ты больше не думаешь «поставить курсор сюда, выделить досюда» — ты думаешь «поменять строку в кавычках» и нажимаешь ci".

Повтор, макросы, регистры: автоматизация без скриптов

Заголовок раздела «Повтор, макросы, регистры: автоматизация без скриптов»

Точка (.) — самая недооценённая клавиша Vim. Она повторяет последнее изменение целиком. Связка ciw + новое слово + Esc, потом n (следующее совпадение поиска) и . — это переименование по файлу без всякого «заменить все»: ты видишь каждую замену и решаешь, применять или пропустить (n — пропустить, . — применить). Получается интерактивный search-and-replace, который контролируешь ты, а не диалоговое окно.

Макросы — запись последовательности нажатий в регистр и её воспроизведение. qa — начать запись в регистр a, дальше делаешь правку один раз, q — стоп. Теперь @a повторяет всю последовательность, а 10@a — десять раз. Классический сценарий: пришёл CSV из пятидесяти строк, из каждой надо сделать строку конфига. В обычном редакторе — двадцать минут механической работы или написание скрипта. В Vim: один макрос, 49@a, готово за десять секунд.

Регистры — это не один буфер обмена, а десятки именованных ячеек. "ay — скопировать в регистр a, "ap — вставить из него. Безымянный регистр " хранит последнее удаление (удалённое через d — тоже копируется!). Регистр "+ — системный буфер обмена: "*y или "+y отправляет текст наружу, в Slack или браузер. Есть и специальные: регистр 0 всегда хранит последний yank (спасает, когда после копирования что-то удалил и безымянный регистр перезаписался).

Jumplist и marks — навигация по собственной истории. Каждый прыжок (поиск, gd, %) запоминается: Ctrl+o — назад по прыжкам, Ctrl+i — вперёд. Прыгнул по gd в определение, посмотрел, Ctrl+o — вернулся, где был. Метки: ma ставит локальную метку a, `a — прыжок к ней; заглавные (mA) — глобальные, между файлами. В связке jumplist + marks навигация по незнакомой кодовой базе превращается в «прыгай и возвращайся», без мыши и без поиска вкладки глазами.

LSP под капотом: как работает «перейти к определению»

Заголовок раздела «LSP под капотом: как работает «перейти к определению»»

Когда ты жмёшь gd (goto definition) в Neovim с LazyVim, происходит не магия, а RPC-запрос. LSP — Language Server Protocol — это стандарт, придуманный Microsoft для VS Code и ставший универсальным: редактор (клиент) запускает отдельный процесс — языковой сервер — и общается с ним по JSON-RPC поверх stdio.

┌──────────────┐ JSON-RPC ┌────────────────────────┐
│ Neovim │ ◄──────────► │ typescript-language- │
│ (LSP-клиент) │ поверх stdio│ server (отдельный │
│ │ │ процесс, свой парсер, │
│ gd ──────────┼──────────────►│ свой индекс проекта) │
│ textDocument│ definition │ │
│ /definition │ │ │
└──────────────┘ └────────────────────────┘

Клиент шлёт textDocument/definition с координатами курсора, сервер — который действительно распарсил твой проект и построил семантическую модель — отвечает «определение вот в этом файле, вот на этой строке». Те же запросы стоят за Space ca (code actions: «импортируй недостающее», «переименуй безопасно»), за диагностикой (красные подчёркивания — это textDocument/publishDiagnostics, который сервер пушит сам) и за автодополнением. Важный вывод: умность живёт не в редакторе, а в сервере. Поэтому один и тот же typescript-language-server одинаково хорошо работает и в Neovim, и в VS Code — разница только в клиенте.

В LazyVim установкой серверов управляет mason — пакетный менеджер LSP-серверов, форматтеров и линтеров. :Mason открывает TUI, где ставится всё: от typescript-language-server до lua-language-server и prettierd. Клиентская часть в Neovim встроенная (vim.lsp), а конфигурация привязки серверов к файлам — через nvim-lspconfig, но в LazyVim она уже сделана за тебя.

Treesitter: редактор, который понимает синтаксис

Заголовок раздела «Treesitter: редактор, который понимает синтаксис»

Классическая подсветка синтаксиса в редакторах — это регулярные выражения: «слово function покрасить в фиолетовый». Регулярки не понимают структуру, поэтому вложенные шаблонные строки или хитрый синтаксис регулярно ломают подсветку. Treesitter подходит иначе: он парсит файл в настоящее AST — абстрактное синтаксическое дерево — той же природы, что строит компилятор, и делает это инкрементально: после правки перепарсивается только изменённая ветка, поэтому работает мгновенно даже на больших файлах.

Раз есть дерево — подсветка становится точной: редактор знает, что этот name — параметр функции, а тот — поле объекта, хотя регулярно они неотличимы. Но подсветка — только начало. На AST работают текст-объекты нового поколения (af — функция целиком, ac — класс), умный инкрементальный выбор (нажатие расширяет выделение по узлам дерева: выражение → оператор → блок → функция) и навигация «следующая функция». Это принципиальный сдвиг: редактор перестаёт видеть текст как мешок символов и начинает видеть код.

Fuzzy-поиск — третий столп современного Neovim после LSP и treesitter. Идея: в проекте на тысячи файлов ты не помнишь путей, ты помнишь куски имён. Печатаешь usrctrl — находится src/components/UserController.tsx. В LazyVim за это отвечает snacks picker: Space Space — файлы проекта, Space / — поиск по содержимому (grep через ripgrep), Space fr — недавние файлы. Это три самые нажимаемые комбинации в LazyVim; навигация «по проекту» после них почти перестаёт существовать как задача.

Структура конфигурации LazyVim — это хороший пример того, как должен выглядеть модульный конфиг:

~/.config/nvim/
├── init.lua # точка входа: подключает lazy.nvim и LazyVim
└── lua/
├── config/
│ ├── options.lua # опции редактора (vim.opt)
│ ├── keymaps.lua # твои кеймапы
│ └── autocmds.lua # автокоманды
└── plugins/
└── *.lua # один файл = один плагин или группа

Каждый файл в lua/plugins/ возвращает таблицу-спецификацию плагина, и lazy.nvim сам собирает их в общий граф загрузки. Добавить плагин — создать файл, отключить — вернуть спеку с enabled = false. Тебе не нужно читать весь дистрибутив: свои правки кладёшь рядом, и они накладываются поверх дефолтов — та же идея слоёв, что и в Omarchy.

-- lua/plugins/example.lua — переопределяем поведение snacks picker
return {
{
"folke/snacks.nvim",
opts = {
picker = {
-- показывать скрытые файлы в поиске файлов
hidden = true,
},
},
},
}

Ключевые плагины, которые стоит знать по имени:

  • flash — мгновенные прыжки: жмёшь s, печатаешь два символа цели, получаешь подсвеченные метки, жмёшь метку — курсор там. Заменяет десятки нажатий w/b одним прыжком.
  • mini.surround — операции с «обрамлением»: gsaiw" — обернуть слово в кавычки (surround add inner word), gsd" — удалить кавычки, gsr"' — заменить кавычки на одинарные. Это оператор surround, встроенный в ту же грамматику.
  • blink.cmp — автодополнение: Enter принимает вариант, Tab/Shift+Tab — навигация по сниппетам и плейсхолдерам. Источники — LSP, пути, буфер, сниппеты.
  • neo-tree — файловое дерево (Space e). Используй его для обзора структуры и операций над файлами, а не для навигации — для навигации есть picker.
  • gitsigns + lazygit — gitsigns рисует изменения в gutter (плюсики и тильды у строк, хунки можно стейджить и откатывать прямо из редактора), а Space gg открывает lazygit — полноценный TUI для git: staging по хункам, rebase, ветки.

Движение (normal-режим):

Клавиши Действие
h j k l влево / вниз / вверх / вправо
w / b / e слово вперёд / назад / конец слова
0 / $ / % начало строки / конец / парная скобка
gg / G / {line}G начало файла / конец / строка N
Ctrl+d / Ctrl+u полстраницы вниз / вверх
Ctrl+o / Ctrl+i назад / вперёд по jumplist

Редактирование:

Клавиши Действие
ciw / di" / dt, изменить слово / удалить внутри кавычек / удалить до запятой
dd / yy / cc строку: удалить / скопировать / изменить
. повторить последнее изменение
u / Ctrl+r undo / redo
qa … q, @a записать макрос в a, воспроизвести
"+y / "+p в системный буфер / из него
gsa/gsd/gsr (mini.surround) добавить / удалить / заменить обрамление

Файлы и поиск (LazyVim):

Клавиши Действие
Space Space fuzzy-поиск файлов
Space / grep по проекту
Space fr недавние файлы
Space e файловое дерево (neo-tree)
s{2 символа}{метка} (flash) прыжок к видимой цели
Space gg lazygit

LSP:

Клавиши Действие
gd / gr к определению / ссылки на символ
K документация под курсором
Space ca code actions
Space cr переименование символа
[d / ]d предыдущая / следующая диагностика

Буферы и окна:

Клавиши Действие
Space bd закрыть буфер
Shift+h / Shift+l предыдущий / следующий буфер
Space - / Space | сплит горизонтально / вертикально
Ctrl+h/j/k/l фокус между окнами

Дни 1–3: выживание. Только hjkl, i, Esc, dd, u, :w, :q. Запрети себе стрелки и мышь. Будет больно и медленно — так и задумано. Вечером третьего дня добавь w/b.

Дни 4–7: язык. Весь день работаешь через ciw, di", dt. и точку-повтор. Каждый раз, когда хочется выделить мышью, — остановись и подумай, какой текст-объект описывает цель. В конце недели — первый макрос на реальной рутинной задаче.

Дни 8–10: навигация. Space Space и Space / вместо дерева файлов, gd/Ctrl+o для чтения чужого кода, flash-прыжки вместо wwwww. Цель — ни разу не открыть neo-tree ради перехода в файл.

Дни 11–14: интеграция. Работаешь над pet-проектом целиком в Neovim: код, тесты в соседнем окне, gitsigns для стейджинга, lazygit для коммитов. К концу второй недели скорость должна сравняться со старым редактором; дальше она только растёт.

  • Жить в insert-режиме. Новичок заходит в insert и стрелками двигается внутри него. Это VS Code с лишними шагами. Правило: закончил печатать — Esc. Insert — это глагол короткого действия, не состояние по умолчанию.
  • Учить хоткеи списком. Двадцать комбинаций в день не выучиваются. Формула дня: одна новая «фраза» (ciw) — и используешь её до автоматизма.
  • Собирать конфиг с нуля в первый месяц. Самописный конфиг без понимания, что в нём происходит, — главная причина «Neovim сломался, вернулся в VS Code». LazyVim как база + точечные overrides — правильный путь.
  • Ставить LSP-серверы руками в обход mason. Потом версии разъезжаются, сервер не стартует, и непонятно, чей это сбой. :Mason — единая точка установки и диагностики (:LspInfo, :checkhealth).
  • Игнорировать регистры и терять скопированное. Удалил строку после копирования — безымянный регистр перезаписался. Хорошо: копируй важное в именованный регистр ("ay) или помни, что последний yank всегда в регистре 0.
  • «Заменить все» там, где нужна точка. Слепой :%s/foo/bar/g в коде с контекстами — источник тихих багов. n + . даёт тебе контроль над каждой заменой — и это быстрее, чем откатывать плохую замену.
  1. Что такое LSP и почему он изменил экосистему редакторов? Протокол, разделяющий редактор и «умный» языковой движок: клиент шлёт JSON-RPC запросы (definition, completion, diagnostics), сервер с распарсенной моделью проекта отвечает. До LSP каждый редактор писал свою интеграцию с каждым языком (N×M), после — сервер пишется один раз и работает везде (N+M).
  2. Чем treesitter отличается от подсветки на регулярках? Регулярки видят текст, treesitter строит AST — настоящее синтаксическое дерево, инкрементально перепарсиваемое при правках. Точная подсветка, структурные текст-объекты и навигация по узлам дерева.
  3. Объясни ciw коллеге, который не знает Vim. «Change inner word»: оператор c (изменить) применён к текст-объекту iw (слово целиком). Редактор воспринимает команды как грамматику «глагол + существительное», поэтому зная пять операторов и пять объектов, ты знаешь двадцать пять команд.
  4. Что делает . (точка) и почему её называют самой мощной клавишей? Повторяет последнее изменение как единое действие. В связке с поиском (n/.) даёт интерактивную замену с контролем каждого случая; с макросами и операторами превращает редактирование в композицию повторяемых действий.
  5. Как устроен автокомплит в Neovim? Движок (например, blink.cmp) собирает кандидатов из источников — LSP-сервер, сниппеты, пути, слова буфера — ранжирует и показывает попап; Enter принимает, Tab двигается по плейсхолдерам сниппета. Семантическая часть кандидатов приходит от LSP-сервера.
  6. Чем макрос в Vim отличается от скрипта? Макрос — записанная последовательность нажатий в регистре, воспроизводимая @a (и N@a для повторов). Не требует языка программирования, пишется за секунды, идеален для одноразовой механической трансформации данных прямо в буфере.
  7. Что такое jumplist и чем он отличается от undo? Jumplist — история прыжков курсора (поиск, gd, %), по ней ходят Ctrl+o/Ctrl+i. Это навигация в пространстве, а undo — навигация во времени изменений; они ортогональны.
  1. Пройди vimtutor (команда в терминале) целиком, не пропуская упражнений. Критерий: все шесть уроков закрыты, hjkl используется без взгляда на клавиатуру.
  2. Напиши первый override для LazyVim: файл в lua/plugins/, меняющий одну опцию любого плагина (например, hidden = true в picker). Критерий: :Lazy показывает плагин, поведение изменилось, конфиг LazyVim не отредактирован напрямую.
  3. Макрос-баттл. Возьми список из 30 строк (например, пары «имя — email») и преврати его в YAML-список одним макросом. Критерий: меньше минуты, ни одной ручной правки после 29@a.
  4. День без мыши в коде. Целый рабочий день над pet-проектом: навигация только Space Space, gd, Ctrl+o, flash. Запиши три момента, где пришлось открыть старый редактор, — это твой план доучивания.
  5. Настрой LSP для стека pet-проекта через :Mason (typescript, tailwind, prisma). Критерий: gd работает по проекту, диагностика прилетает, :LspInfo показывает прикреплённые серверы.