
# Transformery

Transformery pozwalają przekształcać wyróżnione wyjście na każdym etapie — od
surowego ciągu źródłowego, przez siatkę tokenów, po końcowe drzewo HAST i HTML.
shikiphp odwzorowuje model transformerów Shiki hook po hooku, dlatego transformery działają
identycznie jak ich odpowiedniki JavaScript.

Przekaż je za pomocą opcji [`transformers`](https://shikiphp.inline0.dev/pl/docs/options#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(),
    ],
]);
```

## Potok przetwarzania

Każdy transformer to obiekt implementujący `Shikiphp\Transformer\Transformer`.
Potok uruchamia hooki w podanej kolejności podczas tworzenia pojedynczego bloku:

| Hook | Sygnatura | Kiedy |
| --- | --- | --- |
| `preprocess` | `(string $code, array &$options, $context): ?string` | przed tokenizacją; może przepisać źródło i zmodyfikować opcje |
| `tokens` | `(array $tokens, $context): ?array` | po tokenizacji, na siatce `ThemedToken` 2D |
| `root` | `(Element $el, $context): ?Element` | na korzeniu HAST |
| `pre` | `(Element $el, $context): ?Element` | na elemencie `<pre>` |
| `code` | `(Element $el, $context): ?Element` | na elemencie `<code>` |
| `line` | `(Element $el, int $line, $context): ?Element` | dla każdego elementu wiersza (indeksowanie od 1) |
| `span` | `(Element $el, int $line, int $col, Element $lineEl, ThemedToken $token, $context): ?Element` | dla każdego zakresu tokenu |
| `postprocess` | `(string $html, array $options, $context): ?string` | na końcowym zserializowanym ciągu HTML |

Każdy hook jest opcjonalny. Zwrócenie wartości zastępuje dane wejściowe; zwrócenie `null` pozostawia je bez zmian.

`enforce()` kontroluje kolejność działania transformerów: najpierw uruchamiane jest `'pre'`, na końcu `'post'`, a `null` (domyślnie) działa pomiędzy nimi, zgodnie z kolejnością rejestracji.

## Pisanie transformera

Rozszerz `Shikiphp\Transformer\AbstractTransformer` i nadpisz tylko potrzebne hooki;
pozostałe nie wykonują żadnych działań. Kontekst transformera udostępnia
`addClassToHast()` do dodawania klas do elementów.

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

## Wbudowane transformery

### Transformery notacji

Odczytują specjalne komentarze `// [!code …]` w kodzie źródłowym (składnia komentarzy
jest wykrywana automatycznie), zamieniają je na klasy i usuwają same komentarze. To bezpośrednie porty zestawu notacji `@shikijs/transformers`.

#### `NotationHighlight`

`Shikiphp\Transformer\Notation\NotationHighlight` — `// [!code highlight]` (lub
`// [!code hl]`) dodaje do wiersza klasę `highlighted`.

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

#### `NotationDiff`

`Shikiphp\Transformer\Notation\NotationDiff` — `// [!code ++]` oznacza wiersz jako
Dodany (`diff add`), a `// [!code --]` jako usunięty (`diff remove`).

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

#### `NotationFocus`

`Shikiphp\Transformer\Notation\NotationFocus` — `// [!code focus]` dodaje klasę
`focused`.

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

#### `NotationErrorLevel`

`Shikiphp\Transformer\Notation\NotationErrorLevel` — `// [!code error]`,
`// [!code warning]` i `// [!code info]` dodają klasę `highlighted` oraz
`error` / `warning` / `info`.

```js
throwSomething() // [!code error]
maybeRisky()     // [!code warning]
```

#### `NotationWordHighlight`

`Shikiphp\Transformer\Notation\NotationWordHighlight` — `// [!code word:foo]`
(opcjonalnie `// [!code word:foo:2]`, aby ograniczyć działanie do kolejnych N wierszy) opakowuje każde
wystąpienie `foo` w element span z `highlighted-word`.

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

### Transformery metadanych

Odczytują one **ciąg metadanych** bloku kodu, przekazany przez opcję
[`meta`](https://shikiphp.inline0.dev/pl/docs/options#meta) (`meta['__raw']`), podobnie jak Shiki odczytuje
tekst po języku bloku kodu z potrójnymi backtickami.

#### `MetaHighlight`

`Shikiphp\Transformer\MetaHighlight` — wyróżnia wiersze wskazane w zakresie metadanych,
na przykład `{1,3-5}`, dodając klasę `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` — wyróżnia słowa wskazane w ciągu metadanych
za pomocą składni `/word/`, dodając `highlighted-word`.

### Białe znaki i struktura

#### `RenderWhitespace`

`Shikiphp\Transformer\RenderWhitespace` — opakowuje znaki spacji i tabulatora w
spanach (domyślnie klasy `space` i `tab`), aby można je było widocznie wyrenderować.
Konstruktor przyjmuje `(classSpace, classTab, position)`, gdzie `position` to
`'all'` (domyślnie), `'boundary'`, `'trailing'` lub `'leading'`.

#### `CompactLineOptions`

`Shikiphp\Transformer\CompactLineOptions` — przypisuje klasy określonym wierszom
na podstawie listy wpisów `{line, classes}`.

```php
use Shikiphp\Transformer\CompactLineOptions;

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

Numery wierszy zaczynają się od 1. Przekaż obiekt w opcji `transformers`, tak jak
każdy inny transformer.

#### `RemoveNotationEscape`

`Shikiphp\Transformer\RemoveNotationEscape` — usuwa znaki ucieczki z formy `[\!code …]`,
używanej do zapisania dosłownego komentarza notacji bez uruchamiania transformera notacji.

#### `RemoveLineBreak`

`Shikiphp\Transformer\RemoveLineBreak` — usuwa węzły tekstowe znaków końca wiersza
między wierszami (Shiki: `transformerRemoveLineBreak`).

### `StyleToClass`

`Shikiphp\Transformer\StyleToClass` — przenosi style inline do generowanych klas
i gromadzi arkusz stylów pobierany za pomocą `getCSS()`. Nazwy klas są skrótem stylu
obliczonym przy użyciu Shiki: `cyrb53`, dlatego wynik jest identyczny bajt po bajcie z `@shikijs/transformers`
4.2.0, łącznie z zakresem:

- Styl `<pre>` zawsze staje się klasą.
- Spany tokenów są konwertowane tylko wtedy, gdy mają wstępnie utworzony styl, co
  ma miejsce w przypadku mapy `themes`. Przy pojedynczym `theme` spany tokenów zachowują
  style inline `style="color:…"`, tak jak w 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
```

Konstruktor przyjmuje `(classPrefix, classSuffix, classReplacer)`; domyślny
prefiks to `__shiki_`.
