From a741ee102d657bfb6219f799d54126a3596f5c6c Mon Sep 17 00:00:00 2001
From: Tim
Date: Tue, 27 May 2025 00:58:48 +0200
Subject: [PATCH] Start replacing highlight js with tempest-highlight
---
composer.json | 2 +
composer.lock | 129 +++++++++++++++++-
public/github-light-default.css | 87 ++++++++++++
src/Service/SnipParser/AbstractParser.php | 2 +-
.../SnipParser/Generic/GenericParser.php | 4 +-
.../SnipParser/Generic/ReplaceBlocksStage.php | 14 +-
src/Service/SnipParser/Html/HtmlParser.php | 5 +-
.../SnipParser/Markdown/MarkdownParser.php | 13 +-
templates/snip/base.html.twig | 18 ---
templates/snip/single.html.twig | 15 +-
10 files changed, 244 insertions(+), 45 deletions(-)
create mode 100644 public/github-light-default.css
diff --git a/composer.json b/composer.json
index 733d3ed..eaf3507 100644
--- a/composer.json
+++ b/composer.json
@@ -14,6 +14,7 @@
"league/pipeline": "^1.0",
"phpdocumentor/reflection-docblock": "^5.6",
"phpstan/phpdoc-parser": "^2.1",
+ "symfony/asset": "7.2.*",
"symfony/console": "*",
"symfony/dotenv": "*",
"symfony/flex": "^2",
@@ -29,6 +30,7 @@
"symfony/uid": "*",
"symfony/validator": "*",
"symfony/yaml": "*",
+ "tempest/highlight": "^2.11",
"twig/extra-bundle": "^2.12|^3.0",
"twig/twig": "^3.0"
},
diff --git a/composer.lock b/composer.lock
index c94fa3e..2d83399 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "638cbc9841226f386ba27215e24c5410",
+ "content-hash": "6bdfa9b2a81e0169ca083ef4d344b876",
"packages": [
{
"name": "dflydev/dot-access-data",
@@ -2370,6 +2370,75 @@
},
"time": "2024-09-11T13:17:53+00:00"
},
+ {
+ "name": "symfony/asset",
+ "version": "v7.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/asset.git",
+ "reference": "cb926cd59fefa1f9b4900b3695f0f846797ba5c0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/asset/zipball/cb926cd59fefa1f9b4900b3695f0f846797ba5c0",
+ "reference": "cb926cd59fefa1f9b4900b3695f0f846797ba5c0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "conflict": {
+ "symfony/http-foundation": "<6.4"
+ },
+ "require-dev": {
+ "symfony/http-client": "^6.4|^7.0",
+ "symfony/http-foundation": "^6.4|^7.0",
+ "symfony/http-kernel": "^6.4|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Asset\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Manages URL generation and versioning of web assets such as CSS stylesheets, JavaScript files and image files",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/asset/tree/v7.2.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-10-25T15:15:23+00:00"
+ },
{
"name": "symfony/cache",
"version": "v7.2.6",
@@ -6436,6 +6505,64 @@
],
"time": "2025-04-04T10:10:11+00:00"
},
+ {
+ "name": "tempest/highlight",
+ "version": "2.11.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/tempestphp/highlight.git",
+ "reference": "5a239a92ad6bd3e506ca86a0de3e99ac9dbcb0dd"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/tempestphp/highlight/zipball/5a239a92ad6bd3e506ca86a0de3e99ac9dbcb0dd",
+ "reference": "5a239a92ad6bd3e506ca86a0de3e99ac9dbcb0dd",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.3"
+ },
+ "require-dev": {
+ "assertchris/ellison": "^1.0.2",
+ "friendsofphp/php-cs-fixer": "^3.21",
+ "league/commonmark": "^2.4",
+ "phpstan/phpstan": "^1.10.0",
+ "phpunit/phpunit": "^10.0",
+ "symfony/var-dumper": "^6.4|^7.0"
+ },
+ "suggest": {
+ "assertchris/ellison": "Allows you to analyse sentence complexity",
+ "league/commonmark": "Adds markdown support"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Tempest\\Highlight\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Brent Roose",
+ "email": "brendt@stitcher.io"
+ }
+ ],
+ "description": "Fast, extensible, server-side code highlighting",
+ "support": {
+ "issues": "https://github.com/tempestphp/highlight/issues",
+ "source": "https://github.com/tempestphp/highlight/tree/2.11.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/brendt",
+ "type": "github"
+ }
+ ],
+ "time": "2025-03-19T05:38:35+00:00"
+ },
{
"name": "twig/extra-bundle",
"version": "v3.21.0",
diff --git a/public/github-light-default.css b/public/github-light-default.css
new file mode 100644
index 0000000..409c1e7
--- /dev/null
+++ b/public/github-light-default.css
@@ -0,0 +1,87 @@
+pre, code {
+ color: #1f2328;
+ background-color: #ffffff;
+}
+
+.hl-keyword {
+ color: #cf222e;
+}
+
+.hl-property {
+ color: #8250df;
+}
+
+.hl-attribute {
+ font-style: italic;
+}
+
+.hl-type {
+ color: #EA4334;
+}
+
+.hl-generic {
+ color: #9d3af6;
+}
+
+.hl-value {
+ color: #0a3069;
+}
+
+.hl-literal {
+ color: #0a3069;
+}
+
+.hl-number {
+ color: #0a3069;
+}
+
+.hl-variable {
+ color: #953800;
+}
+
+.hl-comment {
+ color: #6e7781;
+}
+
+.hl-blur {
+ filter: blur(2px);
+}
+
+.hl-strong {
+ font-weight: bold;
+}
+
+.hl-em {
+ font-style: italic;
+}
+
+.hl-addition {
+ display: inline-block;
+ min-width: 100%;
+ background-color: #00FF0022;
+}
+
+.hl-deletion {
+ display: inline-block;
+ min-width: 100%;
+ background-color: #FF000011;
+}
+
+.hl-gutter {
+ display: inline-block;
+ font-size: 0.9em;
+ color: #555;
+ padding: 0 1ch;
+ margin-right: 1ch;
+ user-select: none;
+}
+
+.hl-gutter-addition {
+ background-color: #34A853;
+ color: #fff;
+}
+
+.hl-gutter-deletion {
+ background-color: #EA4334;
+ color: #fff;
+}
diff --git a/src/Service/SnipParser/AbstractParser.php b/src/Service/SnipParser/AbstractParser.php
index a5c8ae0..4015290 100644
--- a/src/Service/SnipParser/AbstractParser.php
+++ b/src/Service/SnipParser/AbstractParser.php
@@ -20,7 +20,7 @@ abstract class AbstractParser implements ParserInterface
try {
return $this->safeParseView($content);
} catch (\Exception $exception) {
- return sprintf('%s
', htmlspecialchars($exception->getMessage()));
+ return sprintf('%s
', htmlspecialchars($exception->getMessage()));
}
}
diff --git a/src/Service/SnipParser/Generic/GenericParser.php b/src/Service/SnipParser/Generic/GenericParser.php
index f59657c..ba5a5bd 100644
--- a/src/Service/SnipParser/Generic/GenericParser.php
+++ b/src/Service/SnipParser/Generic/GenericParser.php
@@ -17,9 +17,9 @@ class GenericParser extends AbstractParser
$builder = new PipelineBuilder();
$pipeline = $builder
->add(new HtmlEscapeStage())
+ ->add(new ReplaceBlocksStage('', '
', '```'))
+ ->add(new ReplaceBlocksStage('', '
', '``'))
->add(new ReplaceStage(PHP_EOL, '
'))
- ->add(new ReplaceBlocksStage('', '
', '```'))
- ->add(new ReplaceBlocksStage('', '
', '``'))
->add($this->referenceStage)
->add($this->includeStage)
->build()
diff --git a/src/Service/SnipParser/Generic/ReplaceBlocksStage.php b/src/Service/SnipParser/Generic/ReplaceBlocksStage.php
index cc5907c..2e8fbeb 100644
--- a/src/Service/SnipParser/Generic/ReplaceBlocksStage.php
+++ b/src/Service/SnipParser/Generic/ReplaceBlocksStage.php
@@ -4,13 +4,14 @@ namespace App\Service\SnipParser\Generic;
use InvalidArgumentException;
use League\Pipeline\StageInterface;
+use Tempest\Highlight\Highlighter;
-class ReplaceBlocksStage implements StageInterface
+readonly class ReplaceBlocksStage implements StageInterface
{
public function __construct(
- public readonly string $openTag = '',
- public readonly string $closeTag = '
',
- public readonly string $delimiter = '```'
+ public string $openTag = '',
+ public string $closeTag = '
',
+ public string $delimiter = '```'
) {}
public function __invoke(mixed $payload): string
@@ -26,8 +27,9 @@ class ReplaceBlocksStage implements StageInterface
{
$pattern = sprintf('/%s(.+?)%s/s', preg_quote($this->delimiter), preg_quote($this->delimiter));
- return preg_replace_callback($pattern, function ($matches) {
- return $this->openTag . trim($matches[1]) . $this->closeTag;
+ $highlighter = new Highlighter()->withGutter();
+ return preg_replace_callback($pattern, function ($matches) use ($highlighter) {
+ return $this->openTag . $highlighter->parse(trim($matches[1]), 'php') . $this->closeTag;
}, $text);
}
}
\ No newline at end of file
diff --git a/src/Service/SnipParser/Html/HtmlParser.php b/src/Service/SnipParser/Html/HtmlParser.php
index 491d03f..dab7992 100644
--- a/src/Service/SnipParser/Html/HtmlParser.php
+++ b/src/Service/SnipParser/Html/HtmlParser.php
@@ -3,11 +3,14 @@
namespace App\Service\SnipParser\Html;
use App\Service\SnipParser\AbstractParser;
+use Tempest\Highlight\Highlighter;
class HtmlParser extends AbstractParser
{
public function safeParseView(string $content): string
{
- return sprintf('%s
', htmlspecialchars($content));
+ $highlighter = new Highlighter()->withGutter();
+
+ return '' . $highlighter->parse($content, 'html') . '
';
}
}
\ No newline at end of file
diff --git a/src/Service/SnipParser/Markdown/MarkdownParser.php b/src/Service/SnipParser/Markdown/MarkdownParser.php
index eb603ce..50361c6 100644
--- a/src/Service/SnipParser/Markdown/MarkdownParser.php
+++ b/src/Service/SnipParser/Markdown/MarkdownParser.php
@@ -11,6 +11,8 @@ use League\CommonMark\Node\Inline\Text;
use League\CommonMark\Node\Query;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Routing\RouterInterface;
+use Tempest\Highlight\CommonMark\HighlightExtension;
+use Tempest\Highlight\Highlighter;
class MarkdownParser extends AbstractParser
{
@@ -22,7 +24,13 @@ class MarkdownParser extends AbstractParser
public function safeParseView(string $content): string
{
$converter = new GithubFlavoredMarkdownConverter();
- $converter->getEnvironment()->addEventListener(DocumentParsedEvent::class, $this->documentParsed(...));
+ $converter
+ ->getEnvironment()
+ ->addExtension(new HighlightExtension(
+ new Highlighter()->withGutter()
+ ))
+ ->addEventListener(DocumentParsedEvent::class, $this->documentParsed(...))
+ ;
return $converter->convert($content);
}
@@ -32,7 +40,8 @@ class MarkdownParser extends AbstractParser
$linkNodes = new Query()
->where(Query::type(Link::class))
- ->findAll($document);
+ ->findAll($document)
+ ;
foreach ($linkNodes as $linkNode) {
$url = $linkNode->getUrl();
diff --git a/templates/snip/base.html.twig b/templates/snip/base.html.twig
index 4b21a19..57c9cff 100644
--- a/templates/snip/base.html.twig
+++ b/templates/snip/base.html.twig
@@ -53,22 +53,4 @@
-{% endblock %}
-
-{% block css %}
- {{ parent() }}
-
-{% endblock %}
-
-{% block js %}
- {{ parent() }}
-
-
{% endblock %}
\ No newline at end of file
diff --git a/templates/snip/single.html.twig b/templates/snip/single.html.twig
index c9814cc..58f346b 100644
--- a/templates/snip/single.html.twig
+++ b/templates/snip/single.html.twig
@@ -15,18 +15,5 @@
{% block css %}
{{ parent() }}
-
+
{% endblock %}
-
-{% block js %}
- {{ parent() }}
-
-
-{% endblock %}
\ No newline at end of file