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

Трансформеры

Трансформеры позволяют изменять подсвеченный результат на каждом этапе: от исходной строки через сетку токенов до итогового дерева HAST и HTML. shikiphp реализует модель трансформеров Shiki, повторяя все хуки, поэтому трансформеры работают так же, как их аналоги в JavaScript.

Передавайте их через параметр transformers:

PHP
use Shikiphp\Shikiphp;
use Shikiphp\Transformer\Notation\NotationHighlight;
use Shikiphp\Transformer\Notation\NotationDiff;

$code = <<<'JS'
console.log('this line is highlighted') // [!code highlight]
console.log('old line') // [!code --]
console.log('new line') // [!code ++]
JS;

echo Shikiphp::codeToHtml($code, [
    'lang'         => 'js',
    'theme'        => 'vitesse-dark',
    'transformers' => [
        new NotationHighlight(),
        new NotationDiff(),
    ],
]);

Конвейер

Каждый трансформер — это объект, реализующий Shikiphp\Transformer\Transformer. Конвейер по очереди запускает хуки для каждого этапа создания одного блока:

Хук Сигнатура Когда выполняется
preprocess (string $code, array &$options, $context): ?string до токенизации; может изменить исходный текст и параметры
tokens (array $tokens, $context): ?array после токенизации, для двумерной сетки ThemedToken
root (Element $el, $context): ?Element для корневого узла HAST
pre (Element $el, $context): ?Element для элемента <pre>
code (Element $el, $context): ?Element для элемента <code>
line (Element $el, int $line, $context): ?Element для каждого элемента строки (нумерация с 1)
span (Element $el, int $line, int $col, Element $lineEl, ThemedToken $token, $context): ?Element для каждого диапазона токена
postprocess (string $html, array $options, $context): ?string для итоговой сериализованной строки HTML

Каждый хук необязателен. Возвращённое значение заменяет входное; возврат null оставляет его без изменений.

Параметр enforce() задаёт порядок выполнения трансформеров: сначала запускается 'pre', последним — 'post', а null (по умолчанию) — между ними в порядке регистрации.

Создание трансформера

Расширьте Shikiphp\Transformer\AbstractTransformer, чтобы переопределить только нужные хуки; остальные ничего не делают. Контекст трансформера предоставляет addClassToHast() для добавления классов элементам.

PHP
use Shikiphp\Hast\Element;
use Shikiphp\Shikiphp;
use Shikiphp\Transformer\AbstractTransformer;
use Shikiphp\Transformer\TransformerContext;

final class AddLanguageClass extends AbstractTransformer
{
    public function pre(Element $element, TransformerContext $context): ?Element
    {
        if ($context->lang !== null) {
            $context->addClassToHast($element, 'language-' . $context->lang);
        }

        return $element;
    }
}

echo Shikiphp::codeToHtml('const answer = 42', [
    'lang'         => 'js',
    'theme'        => 'nord',
    'transformers' => [new AddLanguageClass()],
]);
// <pre class="shiki nord language-js" …>

Встроенные трансформеры

Трансформеры аннотаций

Они находят в исходном тексте специальные комментарии // [!code …] (синтаксис комментариев автоматически определяется по языку) и преобразуют их в классы, удаляя сами комментарии. Это прямые порты набора аннотаций @shikijs/transformers.

NotationHighlight

Shikiphp\Transformer\Notation\NotationHighlight — // [!code highlight] (или // [!code hl]) добавляет к строке класс highlighted.

JavaScript
console.log('kept plain')
console.log('this line is highlighted') // [!code highlight]

NotationDiff

Shikiphp\Transformer\Notation\NotationDiff — // [!code ++] помечает строку как добавленную (diff add), а // [!code --] — как удалённую (diff remove).

JavaScript
console.log('old line') // [!code --]
console.log('new line') // [!code ++]

NotationFocus

Shikiphp\Transformer\Notation\NotationFocus — // [!code focus] добавляет класс focused.

JavaScript
console.log('focus on me') // [!code focus]

NotationErrorLevel

Shikiphp\Transformer\Notation\NotationErrorLevel — // [!code error], // [!code warning] и // [!code info] добавляют класс highlighted, а также error / warning / info.

JavaScript
throwSomething() // [!code error]
maybeRisky()     // [!code warning]

NotationWordHighlight

Shikiphp\Transformer\Notation\NotationWordHighlight — // [!code word:foo] (при необходимости укажите // [!code word:foo:2], чтобы ограничить действие следующими N строками) оборачивает каждое вхождение foo в элемент span с атрибутом highlighted-word.

JavaScript
// [!code word:config]
const config = loadConfig()

Трансформеры метаданных

Они читают строку метаданных блока кода, переданную через параметр meta (meta['__raw']), подобно тому, как Shiki читает текст после указания языка в блоке кода с тройными обратными кавычками.

MetaHighlight

Shikiphp\Transformer\MetaHighlight — подсвечивает строки, указанные в диапазоне метаданных, например {1,3-5}, добавляя класс highlighted.

PHP
use Shikiphp\Shikiphp;
use Shikiphp\Transformer\MetaHighlight;

$code = "const a = 1\nconst b = 2\nconst c = 3\nconst d = 4\nconst e = 5";

echo Shikiphp::codeToHtml($code, [
    'lang'         => 'js',
    'theme'        => 'nord',
    'meta'         => ['__raw' => '{1,3-5}'],
    'transformers' => [new MetaHighlight()],
]);

MetaWordHighlight

Shikiphp\Transformer\MetaWordHighlight — подсвечивает слова, указанные в строке метаданных с помощью синтаксиса /word/, и добавляет highlighted-word.

Пробелы и структура

RenderWhitespace

Shikiphp\Transformer\RenderWhitespace — оборачивает пробелы и символы табуляции в элементы span (по умолчанию классы space и tab), чтобы их можно было отображать явно. Конструктор принимает (classSpace, classTab, position), где position — это 'all' (по умолчанию), 'boundary', 'trailing' или 'leading'.

CompactLineOptions

Shikiphp\Transformer\CompactLineOptions — применяет классы к отдельным строкам из списка записей {line, classes}.

PHP
use Shikiphp\Transformer\CompactLineOptions;

$transformer = new CompactLineOptions([
    ['line' => 1, 'classes' => 'highlighted'],
    ['line' => 3, 'classes' => ['highlighted', 'error']],
]);

Нумерация строк начинается с 1. Передайте объект в параметре transformers, как и любой другой трансформер.

RemoveNotationEscape

Shikiphp\Transformer\RemoveNotationEscape — снимает экранирование с формы [\!code …], используемой для записи буквального комментария-аннотации без запуска трансформера аннотаций.

RemoveLineBreak

Shikiphp\Transformer\RemoveLineBreak — удаляет текстовые узлы с переносами строк между строками (Shiki: transformerRemoveLineBreak).

StyleToClass

Shikiphp\Transformer\StyleToClass — переносит встроенные стили в сгенерированные классы и собирает таблицу стилей, которую можно получить через getCSS(). Имена классов хешируют стиль с помощью Shiki — cyrb53, поэтому результат побайтно совпадает с @shikijs/transformers 4.2.0, включая его область действия:

  • Стиль <pre> всегда преобразуется в класс.
  • Диапазоны токенов преобразуются только при наличии предварительно созданного стиля, что бывает с картой themes. При одной theme диапазоны токенов сохраняют встроенный style="color:…", как и в Shiki.
PHP
use Shikiphp\Shikiphp;
use Shikiphp\Transformer\StyleToClass;

$code = 'const answer = 42';
$styleToClass = new StyleToClass();

$html = Shikiphp::codeToHtml($code, [
    'lang'         => 'js',
    'themes'       => ['light' => 'github-light', 'dark' => 'github-dark'],
    'transformers' => [$styleToClass],
]);

$css = $styleToClass->getCSS(); // collected stylesheet for the classes above

Конструктор принимает (classPrefix, classSuffix, classReplacer); префикс по умолчанию — __shiki_.