Treesitter: точная подсветка и структурное редактирование

Рейтинг: 45.3% · 9 голосов
Исчерпывающий курс по 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

Treesitter: точная подсветка и структурное редактирование

Сообщение 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. Шпаргалка-справочник: все ключевые команды одним списком
Откройте любой файл с регулярными выражениями в строках, вложенными шаблонными литералами или встроенным SQL внутри строки на другом языке - и классическая подсветка синтаксиса начнёт "плыть": окрашивать ключевые слова внутри комментариев, путать class как имя переменной с class как объявлением, ломаться на длинных файлах. Причина в том, что традиционная подсветка в Vim построена на регулярных выражениях, которые ничего не знают о структуре кода. Они видят символы, а не дерево.

Treesitter решает эту проблему фундаментально иначе: он строит из исходного кода настоящее синтаксическое дерево (AST) - то же самое, с чем работает компилятор. А раз у редактора есть дерево, то ему доступна не только точная подсветка, но и совершенно новый класс операций: выделить функцию целиком, прыгнуть к следующему методу, поменять местами два аргумента, свернуть класс - оперируя структурой кода, а не строками и символами. Это логическое продолжение идеи текстовых объектов из главы 6, но теперь границы объектов определяются не паттерном, а грамматикой языка.

В 2026 году Treesitter - не экзотика, а часть ядра Neovim. И именно вокруг него произошёл один из самых заметных сдвигов экосистемы за последние годы. Разберёмся по порядку.

Что такое Treesitter и чем он лучше regex-подсветки

Tree-sitter - это библиотека-генератор парсеров, изначально созданная для Atom, а затем ставшая стандартом де-факто для редакторов. Её ключевые свойства определяют, почему она так хорошо подходит редактору:
  • Инкрементальность. Парсер не перечитывает весь файл при каждом нажатии клавиши. Он знает, какой кусочек дерева изменился, и пересобирает только его. Поэтому подсветка остаётся быстрой даже на файлах в десятки тысяч строк, где regex-подсветка начинает тормозить или сдаваться (вспомните :syntax sync и его проблемы).
  • Устойчивость к ошибкам. Дерево строится, даже когда код синтаксически не завершён - вы же редактируете его прямо сейчас. Парсер вставляет в дерево узлы-ошибки, но продолжает осмысленно работать вокруг них.
  • Конкретность. Узел дерева - это не "слово, похожее на ключевое". Это именно function_declaration, parameter, string_content, comment. Подсветка по дереву не перепутает return внутри строки с оператором возврата.
Regex против дерева на конкретном примере

Рассмотрим строку на JavaScript:

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

const re = /class\s+\w+/;  // не настоящий class
Старая regex-подсветка вполне может подсветить слово class внутри регулярного выражения как ключевое слово, а // не настоящий class - частично как код. Treesitter знает, что /class\s+\w+/ - это узел regex, а всё после // - comment, и раскрасит их соответствующе. Никаких ложных срабатываний.

Важно понимать разделение ответственности. Сам Tree-sitter лишь строит дерево. За то, какой узел в какой цвет красить, отвечают queries - специальные запросы на S-expression-синтаксисе (файлы highlights.scm), которые сопоставляют узлам дерева так называемые capture-группы (@keyword, @function, @string), а уже им тема редактора назначает цвета. Поэтому Treesitter-подсветка единообразна между языками: один и тот же @function в Python и в Rust получит один цвет.

nvim-treesitter в 2026: main против master

Здесь начинается самое важное практическое знание этой главы, потому что в 2025-2026 годах экосистема пережила слом.

Исторически плагин nvim-treesitter делал всё: ставил парсеры, вендорил их в репозитории, включал подсветку, indent и fold через большую таблицу setup{}. В 2025 году проект пережил полный несовместимый rewrite. Расклад на 2026 год такой:
  • Ветка master заморожена (locked). Она остаётся только для обратной совместимости с Neovim 0.11 и не работает с Neovim 0.12.
  • Ветка main (она же дефолтная) - это переписанный с нуля, более низкоуровневый плагин-обёртка. Требует Neovim 0.12+. На момент написания главы это активно развивающаяся ветка, и часть её возможностей помечена как экспериментальная - сверяйтесь с README репозитория, статус и состав модулей всё ещё меняются.
  • Пошаговой миграции с master на main нет. Это не апдейт, а другой плагин с другим API. Treesitter-часть конфига переписывается заново.
При этом Neovim 0.12 встроил Treesitter в ядро: парсеры для распространённых языков идут в комплекте, базовая подсветка работает из коробки, Markdown подсвечивается по умолчанию. (На дату написания главы 0.12 - это ещё свежий релизный рубеж; конкретную версию и дату всегда проверяйте в :version и в release notes Neovim.) Для многих пользователей это означает: для базовой подсветки плагин nvim-treesitter вообще больше не обязателен.
Когда плагин всё ещё нужен. Ставить парсеры для языков, которых нет в бандле ядра; обновлять парсеры; пользоваться расширенными возможностями вроде nvim-treesitter-textobjects. Для этого и существует ветка main.
Ключевые отличия main от master

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

Что было в master                             |  Что стало в main
----------------------------------------------+---------------------------------------------------------------------------------------------------------------------
Парсеры вендорились в репозиторий             |  Компилируются локально, нужны актуальный tree-sitter-cli, tar, curl, C-компилятор
ensure_installed = {...}                      |  Удалено. Императивный require('nvim-treesitter').install({...})
auto_install = true                           |  Удалено
highlight = { enable = true }                 |  Удалено. Подсветка включается вручную через vim.treesitter.start()
indent = { enable = true }                    |  Удалено. indentexpr ставится вручную
модуль incremental_selection                  |  Удалён. Встроенной treesitter-замены нет: либо обёртка над LSP vim.lsp.buf.selection_range(), либо community-плагин
main = 'nvim-treesitter.configs' в lazy spec  |  main = 'nvim-treesitter', branch = 'main'
Подчеркну ловушку: если вы оставите в spec старое поле ensure_installed на ветке main, оно будет молча проигнорировано - парсеры просто не установятся, и вы будете долго гадать, почему нет подсветки.

Установка парсеров: :TSInstall

Интерактивно всё осталось привычным:

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

:TSInstall python      " поставить парсер Python
:TSInstall lua rust go " несколько сразу
:TSUpdate              " обновить все установленные парсеры
:TSUninstall all       " снести все (полезно при миграции)
Программно (в конфиге) на ветке main вместо декларативного списка используется императивный API с дедупликацией: получаем уже установленные парсеры, фильтруем недостающие и ставим их.

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

local langs = { 'lua', 'python', 'rust', 'go', 'bash', 'markdown' }
local installed = require('nvim-treesitter.config').get_installed()
local to_install = vim.tbl_filter(function(lang)
  return not vim.tbl_contains(installed, lang)
end, langs)
if #to_install > 0 then
  require('nvim-treesitter').install(to_install)
end
install() возвращает объект с методом :wait() (это не JS-промис); для синхронной установки в скрипте можно дописать :wait(<таймаут_в_мс>), например :wait(300000) - таймаут задаётся в миллисекундах, конкретное значение подбирайте под свою машину и число парсеров.

После миграции со старой ветки обязательная последовательность: :TSUninstall all, перезапуск Neovim, :TSUpdate, затем :checkhealth nvim-treesitter - health-check честно скажет, не хватает ли tree-sitter-cli или компилятора. И отдельно: CLI ставьте системным пакетным менеджером (brew, apt, pacman), а не через npm - README прямо предупреждает о возможных конфликтах npm-версии.

Включаем подсветку и indent вручную

Раз main больше не включает фичи сам, делаем это сами через autocmd на событие FileType. Это безопасный паттерн, который не падает на файлах без парсера благодаря pcall:

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

vim.api.nvim_create_autocmd('FileType', {
  callback = function()
    -- подсветка через ядро Neovim
    pcall(vim.treesitter.start)
    -- indent через nvim-treesitter (main)
    vim.bo.indentexpr = "v:lua.require'nvim-treesitter'.indentexpr()"
  end,
})
vim.treesitter.start() - это core-API ядра Neovim, а не функция плагина. Именно ядро теперь владеет подсветкой; плагин лишь поставляет парсеры. На Neovim 0.12 для языков из бандла подсветка и вовсе может работать без всякого autocmd.
Частая причина "нет подсветки": имя парсера != filetype. Ядро ищет парсер по имени, совпадающему с filetype буфера. Для стандартных языков это совпадает (python -> парсер python). Но если у вас кастомный или нестандартный filetype (скажем, буфер с ft bash, а парсер называется sh, или собственный ft mylang), vim.treesitter.start() молча не найдёт парсер. Решается явной привязкой языка к filetype:

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

-- сопоставить filetype 'bash' парсеру 'sh'
vim.treesitter.language.register('sh', 'bash')
-- одному парсеру можно отдать несколько filetype:
vim.treesitter.language.register('markdown', { 'markdown', 'rmd', 'quarto' })
Если же вы хотите явно стартовать конкретный парсер для буфера, не полагаясь на filetype, передайте имя языка вторым аргументом: vim.treesitter.start(bufnr, 'sh').

Отладка дерева: :InspectTree и :Inspect

Когда подсветка "не та" или вы пишете собственные queries, два встроенных инструмента ядра незаменимы:
  • :InspectTree (Lua-эквивалент vim.treesitter.inspect_tree()) открывает живое окно с синтаксическим деревом текущего буфера. Двигая курсор по дереву, вы видите, какой диапазон исходника подсвечивается, как называются узлы (function_declaration, parameter, ...) - именно эти имена нужны для написания запросов и textobject-капчуров.
  • :Inspect показывает, какие highlight-группы и treesitter-капчуры (@function, @keyword.return и т.п.) применены к символу под курсором, и из какого источника они пришли (treesitter, syntax, semantic tokens LSP). Это первый инструмент, когда "слово красится не в тот цвет".
Если :InspectTree показывает дерево, но :Inspect под курсором не даёт treesitter-капчуров - значит, парсер есть, а вот highlights-запрос для него не подхватился (часто это и есть проблема имени языка из заметки выше).

Свёртки (folding) на основе дерева

Treesitter даёт "умные" свёртки, которые сворачивают по логическим блокам (функции, классы), а не по отступам. Настраивается это опциями ядра (подробнее о свёртках - в главе 19):

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

vim.o.foldmethod = 'expr'
vim.o.foldexpr = 'v:lua.vim.treesitter.foldexpr()'
vim.o.foldlevel = 99   -- по умолчанию ничего не свёрнуто
zc свернёт текущую функцию, zo развернёт, za переключит. Поскольку границы берутся из дерева, свёртка всегда совпадает с границей синтаксической конструкции.

Incremental selection: расширяем выделение по дереву

Одна из самых интуитивных возможностей Treesitter - пошаговое расширение визуального выделения вверх по дереву. Вы стоите внутри выражения, нажимаете клавишу - выделяется текущий узел; нажимаете ещё - выделяется родительский узел (всё выражение, затем оператор, затем функция, затем класс). Это "увеличение масштаба" по структуре.

Важное изменение 2026: модуль incremental_selection из nvim-treesitter удалён на ветке main и прямого встроенного аналога именно по дереву Neovim в ядре не поставляет. Никаких дефолтных клавиш van/an и автоматического "расширения по узлам" в ядре нет - это интерфейс community-плагинов, а не Neovim. Вариантов замены два.

Вариант 1: обёртка над LSP selection_range. В ядре есть функция vim.lsp.buf.selection_range() - это range/incremental selection через LSP, а не через treesitter-дерево. Она ничем не привязана к конкретным клавишам по умолчанию; вы сами вешаете её на удобный keymap, и для работы нужен LSP-сервер, поддерживающий метод textDocument/selectionRange (о настройке LSP - глава 25):

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

-- расширить выделение через LSP (не treesitter!)
vim.keymap.set({ 'n', 'x' }, '<leader>v', function()
  vim.lsp.buf.selection_range('outer')   -- расширить
end)
vim.keymap.set('x', '<leader>V', function()
  vim.lsp.buf.selection_range('inner')   -- сузить обратно
end)
Это удобно, но зависит от LSP: без сервера с поддержкой selectionRange ничего не выделится, и границы определяет сервер, а не treesitter-дерево.

Вариант 2: community-плагин с treesitter-based выделением. Если нужно расширение именно по AST-узлам без LSP, поставьте community-плагин incremental selection (плагин nvim-treesitter-textobjects такого модуля не содержит). Подбирайте актуальный поддерживаемый проект под свою конфигурацию - конкретные имена быстро устаревают, поэтому ищите по запросу "neovim treesitter incremental selection" и сверяйтесь с тем, что плагин совместим с веткой main и Neovim 0.12.

Тонкая настройка: runtime queries и language injection

Подсветка, folds, indent, injections - всё это задаётся не кодом, а запросами (.scm-файлы в каталогах queries/<язык>/): highlights.scm, folds.scm, indents.scm, injections.scm. Ядро Neovim и плагин поставляют свои наборы, но вы можете их дополнять и переопределять своими runtime-запросами, не трогая чужие файлы.

Расширение highlights своими запросами

Neovim собирает запрос для языка, сканируя весь runtimepath. Если положить свой файл в after/queries/<язык>/highlights.scm (каталог after/ подгружается последним), он применится поверх стандартного. Ключевой момент - директива ;extends: с ней ваш файл дополняет базовый запрос, без неё - заменяет его целиком.

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

; файл: ~/.config/nvim/after/queries/python/highlights.scm
;extends

; покрасить вызовы self.<method> особой группой
(call
  function: (attribute
    object: (identifier) @_obj (#eq? @_obj "self")
    attribute: (identifier) @function.method.call))
Программно те же запросы доступны через API ядра: vim.treesitter.query.get('python', 'highlights') - получить активный запрос, vim.treesitter.query.set('python', 'highlights', <текст>) - задать его из Lua на лету (удобно для экспериментов без перезапуска). Проверять, какой капчур реально лёг на символ, помогает :Inspect из раздела выше.

Language injection: вложенные языки

Помните пример из вступления - SQL внутри строки, regex, код в Markdown? За это отвечает механизм language injection: treesitter умеет внутри узла одного языка запускать парсер другого и подсвечивать вложенный фрагмент по его собственной грамматике. Описывается это запросами injections.scm - нужно лишь, чтобы был установлен парсер вложенного языка.

Часть инъекций встроена (Markdown code blocks подсвечиваются по языку из информационной строки ограждения, например sql). Свою инъекцию добавляют тем же runtime-механизмом - after/queries/<хост-язык>/injections.scm с ;extends:

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

; файл: ~/.config/nvim/after/queries/lua/injections.scm
;extends

; строку, переданную в db:query("..."), парсить как SQL
((function_call
  name: (_) @_fn (#eq? @_fn "db:query")
  arguments: (arguments (string content: _ @injection.content)))
 (#set! injection.language "sql"))
После этого SQL внутри Lua-строки получит полноценную SQL-подсветку - при условии, что парсер sql установлен (:TSInstall sql). Отлаживать инъекции удобно тем же :InspectTree: вложенное дерево отображается как поддерево с указанием своего языка.

treesitter-textobjects: оперируем функциями и аргументами

Теперь главное "структурное" оружие. Плагин nvim-treesitter-textobjects (ставится с branch = 'main', официально остаётся experimental) добавляет синтаксически точные текстовые объекты и движения. В отличие от regex-объектов (см. mini.ai в главе 6), они берут границы из AST и не дают ложных срабатываний внутри строк и комментариев.

Плагин состоит из модулей select, move, swap, lsp_interop. Важное отличие main: плагин не навешивает никакие маппинги автоматически. Никаких дефолтных ftplugin-мап отключать не нужно - все биндинги вы задаёте сами, явно, через vim.keymap.set и вызовы модулей плагина. То есть "перебить ваши биндинги" плагину нечем: что не замапили - того и нет. (Старый приём с vim.g.no_plugin_maps относился к master и на main неактуален.)

Текстовые объекты (модуль select)

Соглашение об именах - то же, что у обычных текстовых объектов Vim: a (around, вместе с обрамлением) и i (inner, только содержимое). Типичная раскладка:

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

Объект   |  Что выделяет
---------+-------------------------------------------------
af / if  |  функция (вся / тело)
ac / ic  |  класс (весь / тело)
aa / ia  |  аргумент/параметр (с запятой / только значение)
al / il  |  цикл
ai / ii  |  условный блок (if)
a/ / i/  |  комментарий
Поскольку это полноценные текстовые объекты, они комбинируются с любым оператором из грамматики Vim (глава 5):

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

было:    def calculate(x, y):
             return x + y

cursor где угодно внутри функции
dif   ->  удалить тело функции (def calculate(x, y): остаётся)
daf   ->  удалить функцию целиком
yaf   ->  скопировать функцию
caf   ->  заменить функцию (стереть и войти в insert)

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

было:    foo(alpha, beta, gamma)
                    ^ курсор на beta
dia   ->  foo(alpha, gamma)      (аргумент с обслуживающей запятой удалён)
cia   ->  foo(alpha, , gamma)    (только значение beta стёрто, ввод)
Пример настройки select-биндинга (на main - явный вызов модуля):

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

vim.keymap.set({ 'x', 'o' }, 'af', function()
  require('nvim-treesitter-textobjects.select')
    .select_textobject('@function.outer', 'textobjects')
end)
Перемещение между узлами (модуль move)

Движения позволяют прыгать к следующей/предыдущей синтаксической конструкции. Классическая раскладка повторяет логику меток методов из IDE:

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

Клавиша  |  Прыжок
---------+--------------------------------------------------
]m / [m  |  к началу следующей / предыдущей функции (method)
]M / [M  |  к концу функции
]] / [[  |  к началу следующего / предыдущего класса
][ / []  |  к концу класса
]a / [a  |  к следующему / предыдущему аргументу

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

class Service:
    def first(self): ...      <- ]m ставит курсор сюда
    def second(self): ...     <- ещё ]m сюда
    def third(self): ...
Под капотом это вызовы вроде require('nvim-treesitter-textobjects.move').goto_next_start('@function.outer', 'textobjects').

Повторяемые движения: ; и ,

Очень удобная деталь: движения по textobjects можно сделать повторяемыми теми же ; и ,, что повторяют f/t (глава 3). Модуль repeatable_move запоминает последнее move-движение, и ; повторяет его вперёд, , - назад. Рекомендуется обернуть в эти же клавиши и обычные f/F/t/T, чтобы поведение было единообразным:

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

local ts_repeat = require('nvim-treesitter-textobjects.repeatable_move')
vim.keymap.set({ 'n', 'x', 'o' }, ';', ts_repeat.repeat_last_move_next)
vim.keymap.set({ 'n', 'x', 'o' }, ',', ts_repeat.repeat_last_move_previous)
-- единообразие с f/t:
vim.keymap.set({ 'n', 'x', 'o' }, 'f', ts_repeat.builtin_f_expr, { expr = true })
vim.keymap.set({ 'n', 'x', 'o' }, 'F', ts_repeat.builtin_F_expr, { expr = true })
После этого нажали ]m - и дальше просто давите ;, перепрыгивая по функциям одной клавишей.

Структурное редактирование: swap

Модуль swap - это и есть "структурное редактирование" в чистом виде. Он меняет местами узлы дерева, а не строки. Самый частый кейс - переставить аргументы функции, не считая запятые и не выделяя вручную:

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

Клавиша (типовая)  |  Действие
-------------------+------------------------------------------------------------------------
<leader>a          |  поменять текущий аргумент со следующим (swap_next('@parameter.inner'))
<leader>A          |  поменять со предыдущим (swap_previous('@parameter.inner'))

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

было:   render(width, height)
                ^ курсор на width
<leader>a  ->  render(height, width)
Это безопаснее ручного dw/p, потому что плагин корректно переносит и сами значения, и разделители. Так же можно свапать целые функции (@function.outer).

Лучшие практики 2026
  • Сначала проверьте, нужен ли плагин вообще. На Neovim 0.12 подсветка для большинства языков уже в ядре. Если вам хватает её, не тащите nvim-treesitter по инерции - облегчите конфиг. Плагин берите ради парсеров редких языков и textobjects.
  • На 0.12 - только ветка main, на 0.11 - только master. Несоответствие версии теперь даёт жёсткую ошибку. В lazy spec явно укажите branch = 'main' и main = 'nvim-treesitter' (не nvim-treesitter.configs).
  • Не пытайтесь мигрировать конфиг построчно. main - это rewrite. Удалите старый блок целиком и напишите заново по новому API. Многие фичи master намеренно не имеют прямой замены.
  • Включайте подсветку/indent сами через FileType-autocmd с pcall(vim.treesitter.start). На main они не включаются автоматически - это не баг, а дизайн.
  • Нет подсветки при кастомном filetype - проверьте имя парсера. Ядро ищет парсер по имени filetype; если они не совпадают, привяжите явно через vim.treesitter.language.register('<парсер>', '<filetype>').
  • Отлаживайте деревом, а не догадками. :InspectTree показывает структуру и имена узлов, :Inspect - какой капчур/highlight лёг под курсор и откуда. Это первые команды при "красится не так" и при написании своих queries.
  • Тонкую настройку делайте runtime-запросами. Свои highlights/injections кладите в after/queries/<язык>/*.scm с директивой ;extends (дополнить, а не заменить базовый запрос). Для вложенных языков (SQL/regex/Markdown) - injections.scm плюс установленный парсер вложенного языка.
  • tree-sitter-cli - системным пакетником. И помните про зависимости: tar, curl, C-компилятор в PATH. После миграции прогоняйте :checkhealth nvim-treesitter.
  • Для точности текстовых объектов выбирайте AST. treesitter-textobjects или mini.ai с gen_spec.treesitter() точнее, чем чистые regex/Lua-pattern объекты, которые не отличают код от строки или комментария (см. главу 6).
  • Сделайте move-движения повторяемыми через ;/, - это превращает прыжки по функциям в одну клавишу и снимает потребность считать.
  • Готовый референс - пресет Treesitter из LazyVim. Если не хотите собирать spec руками, посмотрите, как LazyVim оформляет branch, build и autocmd под новый main-ориентированный мир.
Частые ошибки
  • Оставили ensure_installed/auto_install на main. Поля удалены и игнорируются молча - парсеры не ставятся, подсветки нет. Используйте install() API.
  • Ждёте, что подсветка/indent включатся сами после установки main. Нет. Без FileType-autocmd с vim.treesitter.start() подсветки не будет.
  • Запустили main на Neovim 0.11 (или master на 0.12). Несовместимо. Сверяйтесь с версией.
  • Ставите tree-sitter-cli через npm. README предупреждает: только системный пакетный менеджер, иначе возможны конфликты.
  • Рассчитываете на incremental_selection из плагина. Модуль удалён, и встроенной treesitter-замены в ядре нет. Варианты: обёртка над vim.lsp.buf.selection_range() (нужен LSP-сервер с поддержкой selectionRange, расширение идёт по LSP, а не по дереву) либо community-плагин с treesitter-выделением. Дефолтных клавиш van/an в ядре не существует.
  • Ждёте, что textobjects сам навесит мапы. На main плагин не задаёт биндинги автоматически: пока вы сами не вызвали vim.keymap.set и модули select/move/swap, никаких af/]m/swap не будет.
  • Забыли :TSUninstall all + :TSUpdate при миграции. Старые вендоренные парсеры могут конфликтовать с локально скомпилированными.
  • Держите nvim-treesitter-textobjects как стабильный. Он официально experimental и завязан на статус treesitter в ядре - закладывайтесь на возможные изменения API.
Упражнения
  1. Подсветка под контролем. На Neovim 0.12 откройте файл языка, которого нет в бандле ядра (например, *.zig или *.nix). Убедитесь, что подсветки нет, выполните :TSInstall <язык>, перезапустите буфер и сравните. Запустите :checkhealth nvim-treesitter и прочитайте, что он сообщает о tree-sitter-cli.
  1. Функция целиком. Возьмите файл с несколькими функциями. Встав курсором в середину одной, поочерёдно примените vif, vaf, dif, yaf, caf. Сформулируйте словами разницу между i и a для функции.
  1. Прыжки по методам. В файле с классом из 5+ методов настройте ]m/[m и пройдите по всем методам туда и обратно. Затем сделайте движение повторяемым через ;/, и пройдите тот же путь, нажимая только ;.
  1. Перестановка аргументов. Найдите вызов функции с тремя аргументами в неправильном порядке. Используя swap (<leader>a/<leader>A), приведите их в нужный порядок, ни разу не удаляя текст вручную и не считая запятые.
  1. Расширение выделения. Замапьте vim.lsp.buf.selection_range('outer') на удобную клавишу (нужен активный LSP-сервер с поддержкой selectionRange). Встаньте внутрь вложенного выражения (например, a + b * (c - d)) и несколько раз нажмите её, расширяя выделение шаг за шагом. Запишите, на каком шаге какой диапазон выделился, и отметьте: границы тут определяет LSP-сервер, а не treesitter-дерево.
  1. Чистый конфиг (для тех, кто на 0.12). Откройте свой treesitter-spec и проверьте: нет ли в нём мёртвых полей ensure_installed/highlight.enable; указан ли branch = 'main'; есть ли FileType-autocmd. Удалите всё лишнее и проверьте, что подсветка осталась.
  1. Дерево под микроскопом. В любом файле выполните :InspectTree и подвигайте курсор, сопоставляя узлы дерева с кодом. Затем поставьте курсор на ключевое слово и вызовите :Inspect - запишите, какой treesitter-капчур и highlight-группа на нём лежат. Найдите символ, у которого :Inspect не показывает treesitter-капчура, и объясните почему.
  1. Своя инъекция. Установите парсер sql (:TSInstall sql). Создайте after/queries/<ваш-язык>/injections.scm с ;extends, который помечает строку-аргумент выбранной функции как SQL, и убедитесь через :InspectTree, что внутри строки появилось вложенное SQL-дерево, а подсветка стала "как в SQL".
Главное из главы
  • Treesitter строит настоящее синтаксическое дерево (AST) инкрементально и устойчиво к ошибкам. Это даёт точную подсветку без ложных срабатываний regex-подхода и открывает структурное редактирование.
  • Подсветкой теперь владеет ядро Neovim (vim.treesitter.start()), а плагин nvim-treesitter поставляет парсеры. На Neovim 0.12 базовая подсветка работает из коробки.
  • Сейсмический сдвиг 2026: ветка master заморожена (только 0.11), main - переписанный плагин для 0.12+. Миграция = переписать конфиг заново; ensure_installed, авто-highlight и incremental_selection удалены.
  • :TSInstall ставит парсеры (нужен системный tree-sitter-cli); подсветку и indent на main включаете вручную через FileType-autocmd.
  • treesitter-textobjects дают AST-точные объекты af/if, ac/ic, aa/ia, движения ]m/[m, повторяемые ;/, и swap для перестановки узлов - это и есть структурное редактирование.
  • Incremental selection из плагина удалён, встроенной treesitter-замены в ядре нет: либо обёртка над vim.lsp.buf.selection_range() (по LSP), либо community-плагин. Дефолтных van/an в ядре не существует.
  • Отладка и тонкая настройка - через ядро. :InspectTree и :Inspect показывают дерево и капчуры; свои highlights/injections добавляются runtime-запросами в after/queries/.../*.scm с ;extends. При кастомном filetype не забывайте vim.treesitter.language.register().
  • Language injection даёт подсветку вложенных языков (SQL/regex/Markdown) по их грамматике - через injections.scm и установленный парсер вложенного языка.
  • Дальше: про пикеры и поиск - глава 28, про LSP - глава 25, про текстовые объекты mini.ai - глава 6.
👍2 ❤️1 🔥1 😄 🤔
Аватара пользователя
sammy32
Сообщения: 1
Зарегистрирован: 14 май 2026, 08:10

Re: Treesitter: точная подсветка и структурное редактирование

Сообщение sammy32 »

убил полдня пока не дошло почему нет подсветки. сидел на main, оставил ensure_installed из старого конфига, а оно молча игнорится. снёс блок целиком, прописал install() через get_installed как в уроке - заработало. вот это про "молча проигнорировано" надо прям капсом писать
👍1 ❤️1 🔥 😄 🤔1
Аватара пользователя
esp3242
Сообщения: 1
Зарегистрирован: 19 май 2026, 13:28

Re: Treesitter: точная подсветка и структурное редактирование

Сообщение esp3242 »

Roman_Vim писал(а):vim.treesitter.start() - это core-API ядра Neovim, а не функция плагина
вот это до меня только сейчас доехало. я всё думал почему на 0.12 markdown сам красится без всякого плагина, а оказывается тс уже в ядре. спс
👍 ❤️ 🔥 😄 🤔
Аватара пользователя
snupik0
Сообщения: 1
Зарегистрирован: 06 июн 2026, 15:05

Re: Treesitter: точная подсветка и структурное редактирование

Сообщение snupik0 »

а кто как с incremental selection в итоге выкрутился? selection_range через lsp у меня по питону норм расширяет, а вот в дотфайлах где lsp нет вообще ничего. видимо community плагин ставить под ast, но боюсь они там устаревают пачками
👍3 ❤️1 🔥 😄 🤔
Аватара пользователя
njbecke1
Сообщения: 2
Зарегистрирован: 05 июн 2026, 13:56

Re: Treesitter: точная подсветка и структурное редактирование

Сообщение njbecke1 »

повторяемые движения через ; и , это лучшее что я отсюда забрал. нажал ]m и дальше просто давишь ; по функциям одной клавишей, считать ничего не надо
👍3 ❤️1 🔥 😄 🤔
Аватара пользователя
docker1337
Сообщения: 2
Зарегистрирован: 07 июн 2026, 20:45

Re: Treesitter: точная подсветка и структурное редактирование

Сообщение docker1337 »

swap топ. раньше реально dw p и пересчитывал запятые как дурак, теперь <leader>a на аргументе и оно само разделители тащит. на функциях через @function.outer тоже свапается, проверил
👍2 ❤️ 🔥 😄 🤔
Аватара пользователя
scar
Сообщения: 1
Зарегистрирован: 26 май 2026, 02:07

Re: Treesitter: точная подсветка и структурное редактирование

Сообщение scar »

маленький затык: парсер bash не подхватывался, ft был bash а парсер называется sh. register('sh', 'bash') и ожило, как в заметке. до этого InspectTree дерево показывал а Inspect под курсором пусто - вот это и был тот самый признак
👍1 ❤️1 🔥 😄 🤔1
Ответить
← Предыдущая глава
Автодополнение 2026: blink.cmp против nvim-cmp
Следующая глава →
Fuzzy-поиск: Telescope, fzf-lua и snacks.picker

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

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

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

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

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