Иногда исходный код языка программирования может содержать фрагменты других языков; HTML + CSS + JavaScript — один из примеров. В этом случае фрагментам текста, написанным на разных языках, необходимо назначить разные анализаторы. Традиционно это достигается с помощью сужения. Хотя tree-sitter работает с сужением (см. сужение), рекомендуемый способ вместо этого — указать области текста буфера (т.е. диапазоны), в которых будет работать анализатор. В этом разделе описаны функции для установки и получения диапазонов для анализатора.
Обычно, когда задействовано несколько языков, существует “основной” или “главный” язык. Анализатор для этого языка— основной анализатор — анализирует весь буфер. Анализаторы для других языков являются “встроенными” или “гостевыми” анализаторами, которые работают только с частью буфера. Дерево разбора основного анализатора обычно используется для определения диапазонов, в которых работают встроенные анализаторы.
Основные режимы должны устанавливать treesit-primary-parser в
основной анализатор перед вызовом treesit-major-mode-setup, чтобы
Emacs мог правильно настроить основной анализатор для font-lock и других
возможностей.
Программы на Lisp должны вызывать treesit-update-ranges, чтобы
убедиться, что диапазоны для каждого анализатора корректны перед
использованием анализаторов в буфере, и вызывать
treesit-language-at, чтобы определить язык, отвечающий за текст
в некоторой позиции. Эти две функции не работают сами по себе; они
требуют, чтобы основные режимы устанавливали
treesit-range-settings и
treesit-language-at-point-function, которые выполняют
фактическую работу. Эти функции и переменные более подробно объясняются
в конце раздела.
Короче говоря, многоязычные основные режимы должны устанавливать
treesit-primary-parser, treesit-range-settings и
treesit-language-at-point-function перед вызовом
treesit-major-mode-setup.
Эта функция устанавливает для parser работу в ranges.
Анализатор parser будет читать текст только в указанных диапазонах.
Каждый диапазон в ranges — это пара вида (beg . end).
Диапазоны в ranges должны идти по порядку и не перекрываться. То есть в псевдокоде:
(cl-loop for idx from 1 to (1- (length ranges))
for prev = (nth (1- idx) ranges)
for next = (nth idx ranges)
should (<= (car prev) (cdr prev)
(car next) (cdr next)))
Если ranges нарушает это ограничение или что-то пошло не так,
эта функция сигнализирует об ошибке treesit-range-invalid.
Данные сигнала содержат конкретное сообщение об ошибке и диапазоны,
которые мы пытаемся установить.
Эта функция также может использоваться для отключения диапазонов.
Если ranges равен nil, анализатор настраивается на
анализ всего буфера.
Пример:
(treesit-parser-set-included-ranges parser '((1 . 9) (16 . 24) (24 . 25)))
Эта функция возвращает диапазоны, установленные для parser.
Возвращаемое значение совпадает с аргументом ranges
treesit-parser-included-ranges: список ячеек cons вида
(beg . end). Если у parser нет
диапазонов, возвращается nil.
(treesit-parser-included-ranges parser)
⇒ ((1 . 9) (16 . 24) (24 . 25))
Эта функция сопоставляет source с query и возвращает
диапазоны захваченных узлов. Возвращаемое значение — список ячеек cons
вида (beg . end), где beg и
end задают начало и конец области текста.
Для удобства source может быть символом языка, анализатором или узлом. Если это символ языка, функция выполняет сопоставление в корневом узле первого анализатора для этого языка; если анализатор — в корневом узле этого анализатора; если узел — в этом узле.
Аргумент query — это запрос, используемый для захвата узлов
(см. Сопоставление с образцом узлов tree-sitter). Имена захватов не имеют значения.
Аргументы beg и end, если оба не равны nil,
ограничивают диапазон, в котором функция выполняет запрос.
Как и другие функции запросов, эта функция вызывает ошибку
treesit-query-error, если query сформирован
неправильно.
Для обычных программ на Lisp должно быть достаточно вызывать следующие две функции для поддержки исходных кодов, смешивающих несколько языков.
Эта функция обновляет диапазоны для анализаторов в буфере. Она
гарантирует, что диапазоны анализаторов корректно установлены между
beg и end в соответствии с
treesit-range-settings. Если опущены, beg по умолчанию
равен началу буфера, а end — концу буфера.
Например, функции подсветки синтаксиса используют эту функцию перед выполнением запроса узлов в области.
Эта функция возвращает язык текста в позиции буфера pos.
Под капотом она вызывает treesit-language-at-point-function и
возвращает её результат. Если treesit-language-at-point-function
равен nil, эта функция возвращает язык первого анализатора
из возвращаемого значения treesit-parser-list. Если в буфере нет
анализатора, возвращается nil.
Обычно в наборе языков, которые могут смешиваться, есть главный язык и один или несколько встроенных языков. Программа на Lisp обычно сначала анализирует весь документ анализатором главного языка, получает некоторую информацию, устанавливает диапазоны для встроенных языков на основе этой информации, а затем анализирует встроенные языки.
Возьмём в качестве примера буфер, содержащий HTML, CSS
и JavaScript. Программа на Lisp сначала проанализирует весь буфер с
помощью анализатора HTML, затем выполнит запрос к этому
анализатору на наличие узлов style_element и
script_element, которые соответствуют тексту CSS и
JavaScript соответственно. Затем она устанавливает диапазон анализаторов
CSS и JavaScript в диапазон, охватываемый соответствующими
узлами.
Для простого документа HTML:
<html>
<script>1 + 2</script>
<style>body { color: "blue"; }</style>
</html>
программа на Lisp сначала выполнит анализ с помощью анализатора HTML, затем установит диапазоны для анализаторов CSS и JavaScript:
;; Создание анализаторов. (setq html (treesit-parser-create 'html)) (setq css (treesit-parser-create 'css)) (setq js (treesit-parser-create 'javascript))
;; Установка диапазонов CSS.
(setq css-range
(treesit-query-range
'html
'((style_element (raw_text) @capture))))
(treesit-parser-set-included-ranges css css-range)
;; Установка диапазонов JavaScript.
(setq js-range
(treesit-query-range
'html
'((script_element (raw_text) @capture))))
(treesit-parser-set-included-ranges js js-range)
Emacs автоматизирует этот процесс в treesit-update-ranges.
Многоязычный основной режим должен установить
treesit-range-settings, чтобы treesit-update-ranges знал,
как автоматически выполнять этот процесс. Основные режимы должны
использовать вспомогательную функцию treesit-range-rules для
генерации значения, которое можно присвоить
treesit-range-settings. Настройки в следующем примере напрямую
преобразуются в операции, показанные выше.
(setq treesit-range-settings
(treesit-range-rules
:embed 'javascript
:host 'html
'((script_element (raw_text) @capture))
:embed 'css
:host 'html
'((style_element (raw_text) @capture))))
;; Основные режимы с несколькими языками должны всегда устанавливать
;; `treesit-language-at-point-function' (см. описание).
(setq treesit-language-at-point-function
(lambda (pos)
(let* ((node (treesit-node-at pos 'html))
(parent (treesit-node-parent node)))
(cond
((and node parent
(equal (treesit-node-type node) "raw_text")
(equal (treesit-node-type parent) "script_element"))
'javascript)
((and node parent
(equal (treesit-node-type node) "raw_text")
(equal (treesit-node-type parent) "style_element"))
'css)
(t 'html)))))
Эта функция используется для установки treesit-range-settings.
Она заботится о компиляции запросов и других пост-обработках и выводит
значение, которое может иметь treesit-range-settings.
Она принимает серию query-spec, где каждый query-spec — это query, перед которым может стоять ноль или более пар keyword/value. Каждый query — это запрос tree-sitter в строковом, s-выражении или скомпилированном виде, либо функция.
Если query — это запрос tree-sitter, перед ним должны стоять две
пары keyword/value, где ключевое слово :embed
указывает встроенный язык, а ключевое слово :host — главный язык.
Если запросу задано ключевое слово :local со значением
t, диапазон, установленный этим запросом, имеет выделенный
локальный анализатор; в противном случае диапазон использует общий
анализатор с другими диапазонами для того же языка.
По умолчанию анализатор рассматривает свои диапазоны как континуум, а не как отдельные независимые сегменты. Поэтому, если встроенные диапазоны семантически независимы, они должны обрабатываться локальными анализаторами, описанными ниже.
Локальный анализатор, установленный для диапазона, можно получить
с помощью treesit-local-parsers-at и
treesit-local-parsers-on.
treesit-update-ranges использует query для определения
способа установки диапазонов для анализаторов встроенного языка. Он
выполняет запрос query в анализаторе главного языка, вычисляет
диапазоны, охватываемые захваченными узлами, и применяет эти диапазоны
к анализаторам встроенного языка.
Если query — это функция, ей не нужны пары keyword и value. Это должна быть функция, принимающая 2 аргумента, start и end, и устанавливающая диапазоны для анализаторов в текущем буфере в области между start и end. Допускается, чтобы эта функция устанавливала диапазоны в большей области, охватывающей область между start и end.
Эта переменная помогает treesit-update-ranges обновлять
диапазоны для анализаторов в буфере. Она представляет собой список
setting, точный формат которых считается внутренним.
Для генерации значения, которое может иметь эта переменная,
следует использовать treesit-range-rules.
Значение этой переменной должно быть функцией, принимающей один
аргумент pos (позицию в буфере) и возвращающей язык текста
буфера в позиции pos. Эта переменная используется
treesit-language-at.
Эта функция возвращает все локальные анализаторы в позиции pos в текущем буфере. pos по умолчанию равен точке.
Локальные анализаторы — это те, которые анализируют только
ограниченную область, отмеченную оверлеем с ненулевым свойством
treesit-parser. Если language не равен nil,
возвращаются только анализаторы для этого языка.
Эта функция аналогична treesit-local-parsers-at, но возвращает
локальные анализаторы в диапазоне между beg и end,
а не в точке.
По умолчанию beg и end равны всей доступной части буфера.