跳转到正文

转换器

转换器可在每个阶段重塑高亮输出——从原始源字符串、令牌网格,到最终的 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 的出现包裹在带有 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 — 将空格和制表符字符包裹在跨度中(默认类名为 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_。