Skip to content

Commit 461f6dd

Browse files
[2.x] Add basic support for inline annotations (#109)
* Add a way of asking for the parsed grammar in Transformer * Implement highlight and focus, start writing some tests * wip * Add tests for focusing * Shuffle documentation and add inline annotations stuff * add tests for keywords * Add interface for getting themes inside of transformers
1 parent d475265 commit 461f6dd

File tree

73 files changed

+1122
-9
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+1122
-9
lines changed

docs/commonmark.mdx

Lines changed: 119 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,24 +60,34 @@ $environment
6060
])); // [!code ++]
6161
```
6262

63-
### Highlighting and focusing lines
63+
### Meta information
6464

65-
You can highlight and focus lines in your code blocks using special annotations in the code block's info string.
65+
You can add additional meta information to your Markdown code blocks to highlight and focus a specified set of lines.
66+
67+
#### Highlighting lines
68+
69+
You can highlight lines in your code blocks using special annotations in the code block's info string.
6670

6771
```md
68-
```php {2,4-8}{3}
72+
```php {2,4-8}
6973
```
7074

71-
The first set of braces represents the lines to highlight, while the second set represents the lines to focus on.
75+
The braces represent the lines to highlight. In this example, line 2 will be highlighted, as well as lines 4 through 8.
7276

73-
In this example, line 2 will be highlighted, as well as lines 4 through 8. Line 3 will then be focused.
77+
#### Focusing lines
7478

75-
If you only want to focus lines, you can use an empty set of braces for the highlighted lines:
79+
You can focus lines in your code blocks using special annotations in the code block's info string.
7680

7781
```md
78-
```php {}{3}
82+
```php {}{2,4-8}
7983
```
8084

85+
The first set of braces represents the lines to highlight (none in this case), while the second set of braces represents the lines to focus. In this example, line 2 will be focused, as well as lines 4 through 8.
86+
87+
<Tip>
88+
When you have focused lines inside of a code block, Phiki will add a `focus` class to the `<pre>` element.
89+
</Tip>
90+
8191
#### Sample CSS
8292

8393
Phiki does not style the highlighted or focused lines by default, so you will need to add your own CSS.
@@ -99,3 +109,105 @@ pre.phiki.focus:hover .line {
99109
filter: blur(0);
100110
}
101111
```
112+
113+
### Inline annotations
114+
115+
The meta information for highlighting and focusing lines can be difficult to use when you have a large code block or if you change the code frequently since it uses line numbers.
116+
117+
That's why Phiki also supports inline annotations using special comments in your code.
118+
119+
#### Ranges
120+
121+
Inline annotations accept an optional range parameter to specify which lines should be highlighted or focused.
122+
123+
```
124+
// [code! highlight] → Only this line.
125+
// [code! highlight:2] → This line and the 2 lines after it.
126+
// [code! highlight:-2] → This line and the 2 lines before it.
127+
// [code! highlight:1,3] → From the next line, 3 lines in total.
128+
// [code! highlight:-1,2] → From the previous line, 2 lines in total.
129+
```
130+
131+
It's also possible to create an "open ended" range by using `start` and `end` to mark the beginning and end of a range.
132+
133+
```
134+
// [code! highlight:start] → Start highlighting from this line.
135+
// ...
136+
// [code! highlight:end] → Stop highlighting after this line.
137+
```
138+
139+
#### Highlighting lines
140+
141+
To highlight a line, you can add a trailing comment to the line you want to highlight.
142+
143+
```php
144+
echo "Hello, world!"; // [code! highlight]
145+
```
146+
147+
This comment tells Phiki to highlight this line by adding a `highlight` class to the corresponding line element.
148+
149+
If you don't want to write out `highlight` every time, you can use the `hl` or `~~` shorthands instead.
150+
151+
```php
152+
echo "Hello, world!"; // [code! hl]
153+
echo "Hello, world!"; // [code! ~~]
154+
```
155+
156+
#### Focusing lines
157+
158+
To focus a line, you can add a trailing comment to the line you want to focus.
159+
160+
```php
161+
echo "Hello, world!"; // [code! focus]
162+
```
163+
164+
This comment tells Phiki to focus this line by adding a `focus` class to the corresponding line element.
165+
166+
<Tip>
167+
When you have focused lines inside of a code block, Phiki will add a `focus` class to the `<pre>` element.
168+
</Tip>
169+
170+
If you don't want to write out `focus` every time, you can use the `f` or `**` shorthands instead.
171+
172+
```php
173+
echo "Hello, world!"; // [code! f]
174+
echo "Hello, world!"; // [code! **]
175+
```
176+
177+
#### Sample CSS
178+
179+
##### **Highlighted lines**
180+
181+
When you use an inline highlight annotation, Phiki will try to find an `editor.lineHighlightBackground` or `editor.selectionHighlightBackground` color inside of your chosen theme(s) and add a CSS variable to the `<pre>` element.
182+
183+
If it can't find one, it will fallback to the default background color of the theme.
184+
185+
Those CSS variables are then applied to the line element the same as other styled elements.
186+
187+
<Note>
188+
If you're using multiple themes, Phiki will also add CSS variables for each theme so you can style them differently in dark mode, for example.
189+
</Note>
190+
191+
```css
192+
@media (prefers-color-scheme: dark) {
193+
.phiki .line.highlight {
194+
background-color: var(--phiki-dark-line-highlight) !important;
195+
}
196+
}
197+
```
198+
199+
##### **Focused lines**
200+
201+
Phiki does not apply any styles to focused lines by default, so you will need to add your own CSS.
202+
203+
```css
204+
pre.phiki.focus .line:not(.focus) {
205+
transition: all 250ms;
206+
filter: blur(2px);
207+
}
208+
209+
pre.phiki.focus:hover .line {
210+
transition: all 250ms;
211+
filter: blur(0);
212+
}
213+
```

meta/commonmark.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
use League\CommonMark\Environment\Environment;
4+
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
5+
use League\CommonMark\MarkdownConverter;
6+
use Phiki\Adapters\CommonMark\PhikiExtension;
7+
use Phiki\Theme\Theme;
8+
9+
require_once __DIR__ . '/../vendor/autoload.php';
10+
11+
$environment = new Environment();
12+
$environment->addExtension(new CommonMarkCoreExtension)->addExtension(new PhikiExtension(Theme::GithubLight));
13+
$converter = new MarkdownConverter($environment);
14+
15+
$markdown = <<<'MARKDOWN'
16+
```blade
17+
{{-- [code! highlight:start] --}}
18+
@if(true) {{-- [code! focus:start] --}}
19+
Hello, world!
20+
@endif {{-- [code! highlight:end] --}}
21+
22+
{{ $variable }} {{-- [code! focus:end] --}}
23+
```
24+
MARKDOWN;
25+
26+
echo $converter->convert($markdown);

src/Adapters/CommonMark/CodeBlockRenderer.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use League\CommonMark\Node\Node;
88
use League\CommonMark\Renderer\ChildNodeRendererInterface;
99
use League\CommonMark\Renderer\NodeRendererInterface;
10+
use Phiki\Adapters\CommonMark\Transformers\AnnotationsTransformer;
1011
use Phiki\Adapters\CommonMark\Transformers\MetaTransformer;
1112
use Phiki\Grammar\Grammar;
1213
use Phiki\Phiki;
@@ -35,6 +36,7 @@ public function render(Node $node, ChildNodeRendererInterface $childRenderer)
3536
->withGutter($this->withGutter)
3637
->withMeta($meta)
3738
->transformer(new MetaTransformer)
39+
->transformer(new AnnotationsTransformer)
3840
->toString();
3941
}
4042

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Phiki\Adapters\CommonMark\Transformers\Annotations;
4+
5+
use Phiki\Phast\Element;
6+
7+
class Annotation
8+
{
9+
public function __construct(
10+
public AnnotationType $type,
11+
public int $start,
12+
public int $end,
13+
) {}
14+
15+
public function applyToLine(Element $line): void
16+
{
17+
$line->properties->get('class')->add(...$this->type->getLineClasses());
18+
}
19+
20+
public function applyToPre(Element $pre): void
21+
{
22+
$pre->properties->get('class')->add(...$this->type->getPreClasses());
23+
}
24+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
namespace Phiki\Adapters\CommonMark\Transformers\Annotations;
4+
5+
class AnnotationRange
6+
{
7+
public function __construct(
8+
public AnnotationRangeKind $kind,
9+
public int $start,
10+
public int $end,
11+
) {}
12+
13+
public static function parse(string $range, int $index): ?self
14+
{
15+
$range = trim($range);
16+
17+
// Highlight the current line plus the next N lines.
18+
if (preg_match('/^\d+$/', $range) === 1) {
19+
return new AnnotationRange(AnnotationRangeKind::Fixed, $index, (int) $range + $index);
20+
}
21+
22+
// Highlight the current line plus the previous N lines.
23+
if (preg_match('/^-\d+$/', $range) === 1) {
24+
return new AnnotationRange(AnnotationRangeKind::Fixed, $index + (int) $range, $index);
25+
}
26+
27+
// Start highlighting from OFFSET for a total of AND lines.
28+
if (preg_match('/^(?<offset>\d+),(?<and>\d+)$/', $range, $matches) === 1) {
29+
return new AnnotationRange(AnnotationRangeKind::Fixed, $index + (int) $matches['offset'], $index + (int) $matches['offset'] + (int) $matches['and'] - 1);
30+
}
31+
32+
// Start highlighting from $index - OFFSET for a total of AND lines.
33+
if (preg_match('/^(?<offset>-\d+),(?<and>\d+)$/', $range, $matches) === 1) {
34+
return new AnnotationRange(AnnotationRangeKind::Fixed, $index + (int) $matches['offset'], $index + (int) $matches['offset'] + (int) $matches['and'] - 1);
35+
}
36+
37+
// Start highlighting in an open-ended manner from the current line.
38+
if (preg_match('/^start$/i', $range) === 1) {
39+
return new AnnotationRange(AnnotationRangeKind::OpenEnded, $index, $index);
40+
}
41+
42+
// Stop highlighting in an open-ended manner at the current line.
43+
if (preg_match('/^end$/i', $range) === 1) {
44+
return new AnnotationRange(AnnotationRangeKind::End, $index, $index);
45+
}
46+
47+
return null;
48+
}
49+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Phiki\Adapters\CommonMark\Transformers\Annotations;
4+
5+
enum AnnotationRangeKind
6+
{
7+
case Fixed;
8+
case OpenEnded;
9+
case End;
10+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
namespace Phiki\Adapters\CommonMark\Transformers\Annotations;
4+
5+
enum AnnotationType
6+
{
7+
case Highlight;
8+
case Focus;
9+
10+
/**
11+
* Get the keywords used to denote this annotation type.
12+
*
13+
* e.g. highlight can be denoted by `[code! highlight]`.
14+
*/
15+
public function keywords(): array
16+
{
17+
return match ($this) {
18+
self::Highlight => ['highlight', 'hl', '~~'],
19+
self::Focus => ['focus', 'f', '**'],
20+
};
21+
}
22+
23+
/**
24+
* Get the CSS classes to apply to lines with this annotation.
25+
*/
26+
public function getLineClasses(): array
27+
{
28+
return match ($this) {
29+
self::Highlight => ['highlight'],
30+
self::Focus => ['focus'],
31+
};
32+
}
33+
34+
/**
35+
* Get the CSS classes to apply to the pre element.
36+
*/
37+
public function getPreClasses(): array
38+
{
39+
return match ($this) {
40+
self::Focus => ['focus'],
41+
default => [],
42+
};
43+
}
44+
45+
/**
46+
* Get the type from the given keyword.
47+
*/
48+
public static function fromKeyword(string $keyword): self
49+
{
50+
return match ($keyword) {
51+
'highlight', 'hl', '~~' => self::Highlight,
52+
'focus', 'f', '**' => self::Focus,
53+
default => throw new \InvalidArgumentException("Unknown annotation keyword: {$keyword}"),
54+
};
55+
}
56+
}

0 commit comments

Comments
 (0)