Skip to content

Commit

Permalink
Merge pull request #10 from allejo/feature/line-number-support
Browse files Browse the repository at this point in the history
Add support for highlighting specific lines
  • Loading branch information
Rias committed Nov 21, 2019
2 parents 52ef684 + 599587e commit 726791a
Show file tree
Hide file tree
Showing 9 changed files with 445 additions and 3 deletions.
2 changes: 1 addition & 1 deletion composer.json
Expand Up @@ -18,7 +18,7 @@
"require": {
"php": "^7.2",
"league/commonmark": "^1.0",
"scrivo/highlight.php": "^9.14"
"scrivo/highlight.php": "^9.15.6.1"
},
"require-dev": {
"larapack/dd": "^1.0",
Expand Down
68 changes: 66 additions & 2 deletions src/CodeBlockHighlighter.php
Expand Up @@ -4,6 +4,7 @@

use DomainException;
use Highlight\Highlighter;
use function HighlightUtilities\splitCodeIntoArray;

class CodeBlockHighlighter
{
Expand All @@ -17,24 +18,87 @@ public function __construct(array $autodetectLanguages = [])
$this->highlighter->setAutodetectLanguages($autodetectLanguages);
}

public function highlight(string $codeBlock, ?string $language = null)
public function highlight(string $codeBlock, ?string $infoLine = null)
{
$codeBlockWithoutTags = strip_tags($codeBlock);
$contents = htmlspecialchars_decode($codeBlockWithoutTags);

$definition = $this->parseLangAndLines($infoLine);
$language = $definition['lang'];

try {
$result = $language
? $this->highlighter->highlight($language, $contents)
: $this->highlighter->highlightAuto($contents);

$code = $result->value;

if (count($definition['lines']) > 0) {
$loc = splitCodeIntoArray($code);

foreach ($loc as $i => $line) {
$loc[$i] = vsprintf('<span class="loc%s">%s</span>', [
isset($definition['lines'][$i + 1]) ? ' highlighted' : '',
$line,
]);
}

$code = implode("\n", $loc);
}

return vsprintf('<code class="%s hljs %s" data-lang="%s">%s</code>', [
'language-'.($language ? $language : $result->language),
$result->language,
$language ? $language : $result->language,
$result->value,
$code,
]);
} catch (DomainException $e) {
return $codeBlock;
}
}

private function parseLangAndLines(?string $language)
{
$parsed = [
'lang' => $language,
'lines' => [],
];

if ($language === null) {
return $parsed;
}

$bracePos = strpos($language, '{');

if ($bracePos === false) {
return $parsed;
}

$parsed['lang'] = substr($language, 0, $bracePos);
$lineDef = substr($language, $bracePos + 1, -1);
$lineNums = explode(',', $lineDef);

foreach ($lineNums as $lineNum) {
if (strpos($lineNum, '-') === false) {
$parsed['lines'][intval($lineNum)] = true;

continue;
}

$extremes = explode('-', $lineNum);

if (count($extremes) !== 2) {
continue;
}

$start = intval($extremes[0]);
$end = intval($extremes[1]);

for ($i = $start; $i <= $end; $i++) {
$parsed['lines'][$i] = true;
}
}

return $parsed;
}
}
192 changes: 192 additions & 0 deletions tests/FencedCodeRendererTest.php
Expand Up @@ -27,6 +27,198 @@ public function it_highlights_code_blocks_with_a_specified_language()
></generic-form>
```
Something feels wrong here.
MARKDOWN;

$environment = Environment::createCommonMarkEnvironment();
$environment->addBlockRenderer(FencedCode::class, new FencedCodeRenderer(['html']));

$parser = new DocParser($environment);
$htmlRenderer = new HtmlRenderer($environment);

$document = $parser->parse($markdown);

$html = $htmlRenderer->renderBlock($document);

$this->assertMatchesXmlSnapshot('<div>'.$html.'</div>');
}

/** @test */
public function it_highlights_code_blocks_with_a_specified_language_and_single_line()
{
$markdown = <<<'MARKDOWN'
Which looks like this in use:
```html{5}
<generic-form
:showBackButton="true"
@back="goBack"
>
<input type="text" name="send-to">
<input type="number" name="amount" />
</generic-form>
```
Something feels wrong here.
MARKDOWN;

$environment = Environment::createCommonMarkEnvironment();
$environment->addBlockRenderer(FencedCode::class, new FencedCodeRenderer(['html']));

$parser = new DocParser($environment);
$htmlRenderer = new HtmlRenderer($environment);

$document = $parser->parse($markdown);

$html = $htmlRenderer->renderBlock($document);

$this->assertMatchesXmlSnapshot('<div>'.$html.'</div>');
}

/** @test */
public function it_highlights_code_blocks_with_a_specified_language_and_line_range()
{
$markdown = <<<'MARKDOWN'
Which looks like this in use:
```html{2-3}
<generic-form
:showBackButton="true"
@back="goBack"
>
<input type="text" name="send-to">
<input type="number" name="amount" />
</generic-form>
```
Something feels wrong here.
MARKDOWN;

$environment = Environment::createCommonMarkEnvironment();
$environment->addBlockRenderer(FencedCode::class, new FencedCodeRenderer(['html']));

$parser = new DocParser($environment);
$htmlRenderer = new HtmlRenderer($environment);

$document = $parser->parse($markdown);

$html = $htmlRenderer->renderBlock($document);

$this->assertMatchesXmlSnapshot('<div>'.$html.'</div>');
}

/** @test */
public function it_highlights_code_blocks_with_a_specified_language_and_multiple_line_range()
{
$markdown = <<<'MARKDOWN'
Which looks like this in use:
```html{2-3,5-6}
<generic-form
:showBackButton="true"
@back="goBack"
>
<input type="text" name="send-to">
<input type="number" name="amount" />
</generic-form>
```
Something feels wrong here.
MARKDOWN;

$environment = Environment::createCommonMarkEnvironment();
$environment->addBlockRenderer(FencedCode::class, new FencedCodeRenderer(['html']));

$parser = new DocParser($environment);
$htmlRenderer = new HtmlRenderer($environment);

$document = $parser->parse($markdown);

$html = $htmlRenderer->renderBlock($document);

$this->assertMatchesXmlSnapshot('<div>'.$html.'</div>');
}

/** @test */
public function it_highlights_code_blocks_with_a_specified_language_and_multiple_separate_lines()
{
$markdown = <<<'MARKDOWN'
Which looks like this in use:
```html{1,7}
<generic-form
:showBackButton="true"
@back="goBack"
>
<input type="text" name="send-to">
<input type="number" name="amount" />
</generic-form>
```
Something feels wrong here.
MARKDOWN;

$environment = Environment::createCommonMarkEnvironment();
$environment->addBlockRenderer(FencedCode::class, new FencedCodeRenderer(['html']));

$parser = new DocParser($environment);
$htmlRenderer = new HtmlRenderer($environment);

$document = $parser->parse($markdown);

$html = $htmlRenderer->renderBlock($document);

$this->assertMatchesXmlSnapshot('<div>'.$html.'</div>');
}

/** @test */
public function it_highlights_code_blocks_with_a_specified_language_and_mix_ranges_specific_lines()
{
$markdown = <<<'MARKDOWN'
Which looks like this in use:
```html{1,2-3,5,7}
<generic-form
:showBackButton="true"
@back="goBack"
>
<input type="text" name="send-to">
<input type="number" name="amount" />
</generic-form>
```
Something feels wrong here.
MARKDOWN;

$environment = Environment::createCommonMarkEnvironment();
$environment->addBlockRenderer(FencedCode::class, new FencedCodeRenderer(['html']));

$parser = new DocParser($environment);
$htmlRenderer = new HtmlRenderer($environment);

$document = $parser->parse($markdown);

$html = $htmlRenderer->renderBlock($document);

$this->assertMatchesXmlSnapshot('<div>'.$html.'</div>');
}

/** @test */
public function it_highlights_code_blocks_with_a_specified_language_and_lines_out_of_order()
{
$markdown = <<<'MARKDOWN'
Which looks like this in use:
```html{7,1,3,2}
<generic-form
:showBackButton="true"
@back="goBack"
>
<input type="text" name="send-to">
<input type="number" name="amount" />
</generic-form>
```
Something feels wrong here.
MARKDOWN;

Expand Down
@@ -0,0 +1,31 @@
<?xml version="1.0"?>
<div>
<p>Which looks like this in use:</p>
<pre>
<code class="language-html hljs xml" data-lang="html">
<span class="loc">
<span class="hljs-tag">&lt;<span class="hljs-name">generic-form</span></span>
</span>
<span class="loc highlighted">
<span class="hljs-tag"><span class="hljs-attr">:showBackButton</span>=<span class="hljs-string">"true"</span></span>
</span>
<span class="loc highlighted">
<span class="hljs-tag"> @<span class="hljs-attr">back</span>=<span class="hljs-string">"goBack"</span></span>
</span>
<span class="loc">
<span class="hljs-tag">&gt;</span>
</span>
<span class="loc">
<span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"send-to"</span>&gt;</span>
</span>
<span class="loc">
<span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"number"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"amount"</span> /&gt;</span>
</span>
<span class="loc">
<span class="hljs-tag">&lt;/<span class="hljs-name">generic-form</span>&gt;</span>
</span>
<span class="loc"/>
</code>
</pre>
<p>Something feels wrong here.</p>
</div>
@@ -0,0 +1,31 @@
<?xml version="1.0"?>
<div>
<p>Which looks like this in use:</p>
<pre>
<code class="language-html hljs xml" data-lang="html">
<span class="loc highlighted">
<span class="hljs-tag">&lt;<span class="hljs-name">generic-form</span></span>
</span>
<span class="loc highlighted">
<span class="hljs-tag"><span class="hljs-attr">:showBackButton</span>=<span class="hljs-string">"true"</span></span>
</span>
<span class="loc highlighted">
<span class="hljs-tag"> @<span class="hljs-attr">back</span>=<span class="hljs-string">"goBack"</span></span>
</span>
<span class="loc">
<span class="hljs-tag">&gt;</span>
</span>
<span class="loc">
<span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"send-to"</span>&gt;</span>
</span>
<span class="loc">
<span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"number"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"amount"</span> /&gt;</span>
</span>
<span class="loc highlighted">
<span class="hljs-tag">&lt;/<span class="hljs-name">generic-form</span>&gt;</span>
</span>
<span class="loc"/>
</code>
</pre>
<p>Something feels wrong here.</p>
</div>

0 comments on commit 726791a

Please sign in to comment.