Skip to content

Commit e857764

Browse files
Add basic support for line decorations (#102)
1 parent 5125f30 commit e857764

File tree

5 files changed

+120
-0
lines changed

5 files changed

+120
-0
lines changed

src/Output/Html/PendingHtmlOutput.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
use Phiki\Theme\ParsedTheme;
1414
use Phiki\Token\HighlightedToken;
1515
use Phiki\Token\Token;
16+
use Phiki\Transformers\Decorations\DecorationsTransformer;
17+
use Phiki\Transformers\Decorations\DecorationTransformer;
18+
use Phiki\Transformers\Decorations\LineDecoration;
1619
use Psr\SimpleCache\CacheInterface;
1720
use Stringable;
1821

@@ -28,6 +31,8 @@ class PendingHtmlOutput implements Stringable
2831

2932
protected array $transformers = [];
3033

34+
protected array $decorations = [];
35+
3136
protected int $startingLineNumber = 1;
3237

3338
/**
@@ -80,6 +85,17 @@ public function transformer(TransformerInterface $transformer): self
8085
return $this;
8186
}
8287

88+
public function decoration(LineDecoration ...$decorations): self
89+
{
90+
if (! Arr::any($this->transformers, fn (TransformerInterface $transformer) => $transformer instanceof DecorationTransformer)) {
91+
$this->transformers[] = new DecorationTransformer($this->decorations);
92+
}
93+
94+
$this->decorations = array_merge($this->decorations, $decorations);
95+
96+
return $this;
97+
}
98+
8399
public function startingLine(int $lineNumber): self
84100
{
85101
$this->startingLineNumber = $lineNumber;

src/Phast/ClassList.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ public function remove(string ...$class): self
4040
return $this;
4141
}
4242

43+
public function all(): array
44+
{
45+
return $this->classes;
46+
}
47+
4348
public function __toString(): string
4449
{
4550
return implode(' ', array_filter($this->classes, fn (string $class) => trim($class) !== ''));
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Phiki\Transformers\Decorations;
4+
5+
use Phiki\Phast\Element;
6+
use Phiki\Transformers\AbstractTransformer;
7+
8+
class DecorationTransformer extends AbstractTransformer
9+
{
10+
/**
11+
* @param array<int, LineDecoration> $decorations
12+
*/
13+
public function __construct(
14+
public array &$decorations,
15+
) {}
16+
17+
public function line(Element $span, array $tokens, int $index): Element
18+
{
19+
foreach ($this->decorations as $decoration) {
20+
if (! $decoration->appliesToLine($index)) {
21+
continue;
22+
}
23+
24+
$span->properties->get('class')->add(...$decoration->classes->all());
25+
}
26+
27+
return $span;
28+
}
29+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Phiki\Transformers\Decorations;
4+
5+
use Phiki\Phast\ClassList;
6+
7+
class LineDecoration
8+
{
9+
/**
10+
* @param int | array<int> $line
11+
*/
12+
public function __construct(
13+
public int | array $line,
14+
public ClassList $classes,
15+
) {}
16+
17+
public function appliesToLine(int $line): bool
18+
{
19+
return $this->line === $line || (is_array($this->line) && $line >= $this->line[0] && $line <= $this->line[1]);
20+
}
21+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
use Phiki\Grammar\Grammar;
4+
use Phiki\Phast\ClassList;
5+
use Phiki\Phiki;
6+
use Phiki\Theme\Theme;
7+
use Phiki\Transformers\Decorations\LineDecoration;
8+
9+
it('can apply decorations to a line', function () {
10+
$output = (new Phiki)
11+
->codeToHtml(<<<'PHP'
12+
echo "Hello, world!";
13+
PHP, Grammar::Php, Theme::GithubLight)
14+
->decoration(new LineDecoration(0, new ClassList(['test-class'])))
15+
->toString();
16+
17+
expect($output)->toContain('<span class="line test-class">');
18+
});
19+
20+
it('can apply decorations to multiple lines with multiple instances', function () {
21+
$output = (new Phiki)
22+
->codeToHtml(<<<'PHP'
23+
echo "Hello, world!";
24+
echo "Goodbye, world!";
25+
PHP, Grammar::Php, Theme::GithubLight)
26+
->decoration(
27+
new LineDecoration(0, new ClassList(['first-line'])),
28+
new LineDecoration(1, new ClassList(['second-line'])),
29+
new LineDecoration(0, new ClassList(['also-first-line'])),
30+
)
31+
->toString();
32+
33+
expect($output)
34+
->toContain('<span class="line first-line also-first-line">')
35+
->toContain('<span class="line second-line">');
36+
});
37+
38+
it('can apply decorations to a range of lines', function () {
39+
$output = (new Phiki)
40+
->codeToHtml(<<<'PHP'
41+
echo "Hello, world!";
42+
echo "Goodbye, world!";
43+
echo "Farewell, world!";
44+
PHP, Grammar::Php, Theme::GithubLight)
45+
->decoration(new LineDecoration([0, 2], new ClassList(['multi-line'])))
46+
->toString();
47+
48+
expect(substr_count($output, 'multi-line'))->toBe(3);
49+
});

0 commit comments

Comments
 (0)