Saltar al contenido

Transformadores

Los transformadores permiten modificar la salida resaltada en cada etapa: desde la cadena de origen sin procesar, pasando por la cuadrícula de tokens, hasta el árbol HAST final y HTML. shikiphp traslada el modelo de transformadores de Shiki hook por hook, por lo que se comportan igual que sus equivalentes de JavaScript.

Páselos mediante la opción 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(),
    ],
]);

El proceso

Cada transformador es un objeto que implementa Shikiphp\Transformer\Transformer. El flujo ejecuta los hooks en orden al construir un único bloque:

Hook Firma Cuándo
preprocess (string $code, array &$options, $context): ?string antes de la tokenización; puede reescribir el código fuente y modificar las opciones
tokens (array $tokens, $context): ?array después de la tokenización, en la cuadrícula 2D ThemedToken
root (Element $el, $context): ?Element en la raíz HAST
pre (Element $el, $context): ?Element en el elemento <pre>
code (Element $el, $context): ?Element en el elemento <code>
line (Element $el, int $line, $context): ?Element por elemento de línea (base 1)
span (Element $el, int $line, int $col, Element $lineEl, ThemedToken $token, $context): ?Element por tramo de token
postprocess (string $html, array $options, $context): ?string en la cadena HTML serializada final

Todos los hooks son opcionales. Devolver un valor reemplaza la entrada; devolver null la deja sin cambios.

enforce() controla el orden entre transformadores: 'pre' se ejecuta primero, 'post' al final y null (el valor predeterminado) en medio, según el orden de registro.

Cómo escribir un transformador

Extiende Shikiphp\Transformer\AbstractTransformer para sobrescribir solo los hooks que necesites; el resto no hará nada. El contexto del transformador expone addClassToHast() para añadir clases a los elementos.

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" …>

Transformadores integrados

Transformadores de notación

Estos leen comentarios especiales // [!code …] en el código fuente (la sintaxis de comentarios del lenguaje se detecta automáticamente) y los convierten en clases, eliminando el comentario. Son traslados directos del conjunto de notaciones de @shikijs/transformers.

NotationHighlight

Shikiphp\Transformer\Notation\NotationHighlight — // [!code highlight] (o // [!code hl]) añade la clase highlighted a su línea.

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

NotationDiff

Shikiphp\Transformer\Notation\NotationDiff — // [!code ++] marca una línea como añadida (diff add) y // [!code --] como eliminada (diff remove).

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

NotationFocus

Shikiphp\Transformer\Notation\NotationFocus — // [!code focus] añade la clase focused.

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

NotationErrorLevel

Shikiphp\Transformer\Notation\NotationErrorLevel — // [!code error], // [!code warning] y // [!code info] añaden la clase highlighted, además de error / warning / info.

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

NotationWordHighlight

Shikiphp\Transformer\Notation\NotationWordHighlight — // [!code word:foo] (opcionalmente // [!code word:foo:2] para limitarlo a las siguientes N líneas) envuelve cada aparición de foo en un tramo que incluye highlighted-word.

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

Transformadores de metadatos

Estos leen la cadena meta del bloque de código, proporcionada mediante la opción meta (meta['__raw']), de la misma forma que Shiki lee el texto después del lenguaje de un bloque de código delimitado.

MetaHighlight

Shikiphp\Transformer\MetaHighlight — resalta las líneas indicadas en un rango de metadatos, como {1,3-5}, y añade la clase 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 — resalta las palabras indicadas en la cadena de metadatos con la sintaxis /word/ y añade highlighted-word.

Espacios en blanco y estructura

RenderWhitespace

Shikiphp\Transformer\RenderWhitespace — envuelve los espacios y tabuladores en tramos (las clases space y tab de forma predeterminada) para que se puedan representar visualmente. El constructor recibe (classSpace, classTab, position), donde position puede ser 'all' (predeterminado), 'boundary', 'trailing' o 'leading'.

CompactLineOptions

Shikiphp\Transformer\CompactLineOptions — aplica clases a líneas específicas a partir de una lista de entradas {line, classes}.

PHP
use Shikiphp\Transformer\CompactLineOptions;

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

La numeración de líneas empieza en 1. Pasa el objeto en la opción transformers como cualquier otro transformador.

RemoveNotationEscape

Shikiphp\Transformer\RemoveNotationEscape — desescapa la forma [\!code …], que permite escribir un comentario de notación literal sin activar un transformador de notación.

RemoveLineBreak

Shikiphp\Transformer\RemoveLineBreak — elimina los nodos de texto de salto de línea entre líneas (Shiki de transformerRemoveLineBreak).

StyleToClass

Shikiphp\Transformer\StyleToClass — mueve los estilos en línea a clases generadas y recopila una hoja de estilos que puedes obtener con getCSS(). Los nombres de clase calculan un hash del estilo con Shikicyrb53, de modo que la salida es idéntica byte a byte a la de @shikijs/transformers4.2.0, incluido su ámbito:

  • El estilo <pre> siempre se convierte en una clase.
  • Los tramos de tokens solo se convierten cuando contienen un estilo preconstruido, como ocurre con un mapa themes. Con un único theme, los tramos de tokens conservan su style="color:…" en línea, igual que en 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

El constructor recibe (classPrefix, classSuffix, classReplacer); el prefijo predeterminado es __shiki_.