diff --git a/.gitignore b/.gitignore index 1104fd6..7686abd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ !/vendor /vendor/* -!/vendor/easybook !/vendor/autoload.php !/vendor/composer \ No newline at end of file diff --git a/acp/acp_pastebin_info.php b/acp/acp_pastebin_info.php new file mode 100644 index 0000000..edc6c9b --- /dev/null +++ b/acp/acp_pastebin_info.php @@ -0,0 +1,25 @@ + 'phpbbde\pastebin\acp\acp_pastebin_module_info', + 'title' => 'PASTEBIN_NAV_TITLE', + 'modes' => [ + 'settings' => [ + 'title' => 'PASTEBIN_NAV_CONFIG', + 'auth' => 'ext_phpbbde/pastebin && acl_a_board', + 'cat' => ['ACP_PASTEBIN_TITLE'] + ], + ], + ]; + } +} diff --git a/acp/acp_pastebin_module.php b/acp/acp_pastebin_module.php new file mode 100644 index 0000000..d143b39 --- /dev/null +++ b/acp/acp_pastebin_module.php @@ -0,0 +1,25 @@ +get('language'); + $this->tpl_name = 'acp_pastebin_settings'; + $this->page_title = $language->lang('PASTEBIN_NAV_TITLE') . ' - ' . $language->lang('PASTEBIN_NAV_CONFIG'); + + $acp_controller = $phpbb_container->get('phpbbde.pastebin.controller.acp'); + $acp_controller->set_page_url($this->u_action); + $acp_controller->module_settings(); + } +} diff --git a/adm/style/acp_pastebin_settings.html b/adm/style/acp_pastebin_settings.html new file mode 100644 index 0000000..1a01a1e --- /dev/null +++ b/adm/style/acp_pastebin_settings.html @@ -0,0 +1,38 @@ +{% include 'overall_header.html' %} + + + +
{{ lang('PASTEBIN_CONFIG_EXPLAIN') }}
+ + + +{% include 'overall_footer.html' %} \ No newline at end of file diff --git a/assets/tempest/highlight/LICENCE.md b/assets/tempest/highlight/LICENCE.md new file mode 100644 index 0000000..e403836 --- /dev/null +++ b/assets/tempest/highlight/LICENCE.md @@ -0,0 +1,9 @@ +The MIT License (MIT) + +Copyright (c) 2024 Brent Roose brendt@stitcher.io + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/assets/tempest/highlight/README.md b/assets/tempest/highlight/README.md new file mode 100644 index 0000000..c72435f --- /dev/null +++ b/assets/tempest/highlight/README.md @@ -0,0 +1,18 @@ +# Fast, extensible, server-side code highlighting +[](https://coveralls.io/github/tempestphp/highlight?branch=main) + +## Quickstart + +```php +composer require tempest/highlight +``` + +Highlight code like this: + +```php +$highlighter = new \Tempest\Highlight\Highlighter(); + +$code = $highlighter->parse($code, 'php'); +``` + +Continue reading in the docs: [https://tempestphp.com/main/packages/highlight](https://tempestphp.com/main/packages/highlight). diff --git a/assets/tempest/highlight/composer.json b/assets/tempest/highlight/composer.json new file mode 100644 index 0000000..02fcca0 --- /dev/null +++ b/assets/tempest/highlight/composer.json @@ -0,0 +1,48 @@ +{ + "name": "tempest/highlight", + "description": "Fast, extensible, server-side code highlighting", + "autoload": { + "psr-4": { + "Tempest\\Highlight\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Tempest\\Highlight\\Tests\\": "tests/" + } + }, + "authors": [ + { + "name": "Brent Roose", + "email": "brendt@stitcher.io" + } + ], + "require": { + "php": "^8.4" + }, + "require-dev": { + "phpunit/phpunit": "^12.0", + "phpstan/phpstan": "^2.1", + "friendsofphp/php-cs-fixer": "^3.84", + "league/commonmark": "^2.4", + "assertchris/ellison": "^1.0.2", + "symfony/var-dumper": "^6.4|^7.0|^8.0", + "phpbench/phpbench": "^1.4" + }, + "suggest": { + "assertchris/ellison": "Allows you to analyse sentence complexity", + "league/commonmark": "Adds markdown support" + }, + "scripts": { + "phpunit": "vendor/bin/phpunit --display-warnings --display-skipped --display-deprecations --display-errors --display-notices", + "csfixer": "vendor/bin/php-cs-fixer fix --allow-risky=yes", + "phpstan": "vendor/bin/phpstan analyse src tests", + "qa": [ + "composer csfixer", + "composer phpstan", + "composer phpunit" + ], + "bench": "vendor/bin/phpbench run --report=aggregate" + }, + "license": "MIT" +} diff --git a/assets/tempest/highlight/phpbench.json b/assets/tempest/highlight/phpbench.json new file mode 100644 index 0000000..37f09ff --- /dev/null +++ b/assets/tempest/highlight/phpbench.json @@ -0,0 +1,9 @@ +{ + "$schema": "./vendor/phpbench/phpbench/phpbench.schema.json", + "runner.bootstrap": "vendor/autoload.php", + "runner.path": "tests/Bench", + "runner.file_pattern": "*Bench.php", + "core.extensions": [ + "Tempest\\Highlight\\Tests\\Bench\\Extension\\MarkdownExtension" + ] +} diff --git a/assets/tempest/highlight/src/After.php b/assets/tempest/highlight/src/After.php new file mode 100644 index 0000000..75ca7aa --- /dev/null +++ b/assets/tempest/highlight/src/After.php @@ -0,0 +1,12 @@ +[\w]+)(\{(?' . $parsed . ''; + } +} diff --git a/assets/tempest/highlight/src/CommonMark/HighlightExtension.php b/assets/tempest/highlight/src/CommonMark/HighlightExtension.php new file mode 100644 index 0000000..1215a27 --- /dev/null +++ b/assets/tempest/highlight/src/CommonMark/HighlightExtension.php @@ -0,0 +1,27 @@ +addRenderer(FencedCode::class, new CodeBlockRenderer($this->highlighter), 10) + ->addRenderer(Code::class, new InlineCodeBlockRenderer($this->highlighter), 10) + ; + } +} diff --git a/assets/tempest/highlight/src/CommonMark/InlineCodeBlockRenderer.php b/assets/tempest/highlight/src/CommonMark/InlineCodeBlockRenderer.php new file mode 100644 index 0000000..8d146eb --- /dev/null +++ b/assets/tempest/highlight/src/CommonMark/InlineCodeBlockRenderer.php @@ -0,0 +1,34 @@ +[\w]+)}(?
.*)/', $node->getLiteral(), $match);
+
+ $language = $match['match'] ?? 'txt';
+ $code = $match['code'] ?? $node->getLiteral();
+
+ return '' . $this->highlighter->parse($code, $language) . '';
+ }
+}
diff --git a/assets/tempest/highlight/src/Escape.php b/assets/tempest/highlight/src/Escape.php
new file mode 100644
index 0000000..f0efc61
--- /dev/null
+++ b/assets/tempest/highlight/src/Escape.php
@@ -0,0 +1,57 @@
+', '"', ''];
+
+ public static function injection(string $input): string
+ {
+ return self::INJECTION_TOKEN . $input . self::INJECTION_TOKEN;
+ }
+
+ public static function terminal(string $input): string
+ {
+ return preg_replace(
+ ['/❷(.*?)❸/', '/❿/'],
+ '',
+ $input,
+ );
+ }
+
+ public static function html(string $input): string
+ {
+ return self::reverse(
+ str_replace(
+ ['&', '<', '>', '"'],
+ ['&', '<', '>', '"'],
+ $input,
+ ),
+ );
+ }
+
+ public static function tokens(string $input): string
+ {
+ return str_replace(
+ self::TOKEN_VALUES,
+ self::TOKEN_KEYS,
+ $input,
+ );
+ }
+
+ private static function reverse(string $input): string
+ {
+ return str_replace(
+ self::TOKEN_KEYS,
+ self::TOKEN_VALUES,
+ $input,
+ );
+ }
+}
diff --git a/assets/tempest/highlight/src/Highlighter.php b/assets/tempest/highlight/src/Highlighter.php
new file mode 100644
index 0000000..fc154e6
--- /dev/null
+++ b/assets/tempest/highlight/src/Highlighter.php
@@ -0,0 +1,297 @@
+ */
+ private array $beforeInjectionsCache = [];
+ /** @var array */
+ private array $afterInjectionsCache = [];
+ private ?self $nestedHighlighter = null;
+
+ public function __construct(private readonly Theme $theme = new CssTheme())
+ {
+ $this->addLanguage(new ApacheLanguage())
+ ->addLanguage(new BashLanguage())
+ ->addLanguage(new BBCodeLanguage())
+ ->addLanguage(new BladeLanguage())
+ ->addLanguage(new CssLanguage())
+ ->addLanguage(new DiffLanguage())
+ ->addLanguage(new DocCommentLanguage())
+ ->addLanguage(new DockerfileLanguage())
+ ->addLanguage(new EllisonLanguage())
+ ->addLanguage(new GdscriptLanguage())
+ ->addLanguage(new HtmlLanguage())
+ ->addLanguage(new JavaScriptLanguage())
+ ->addLanguage(new JsonLanguage())
+ ->addLanguage(new MarkdownLanguage())
+ ->addLanguage(new NginxLanguage())
+ ->addLanguage(new PhpLanguage())
+ ->addLanguage(new PythonLanguage())
+ ->addLanguage(new ScssLanguage())
+ ->addLanguage(new SqlLanguage())
+ ->addLanguage(new TerminalLanguage())
+ ->addLanguage(new TerraformLanguage())
+ ->addLanguage(new TypeScriptLanguage())
+ ->addLanguage(new XmlLanguage())
+ ->addLanguage(new YamlLanguage())
+ ->addLanguage(new DotEnvLanguage())
+ ->addLanguage(new IniLanguage())
+ ->addLanguage(new TwigLanguage());
+
+ $this->fallbackLanguage = new TextLanguage();
+ $this->parseTokens = new ParseTokens();
+ $this->groupTokens = new GroupTokens();
+ $this->renderTokens = new RenderTokens($this->theme);
+ }
+
+ public function withGutter(int $startAt = 1): self
+ {
+ $clone = clone $this;
+
+ $clone->gutterInjection = new GutterInjection($startAt);
+ $clone->nestedHighlighter = null;
+
+ return $clone;
+ }
+
+ public function getGutterInjection(): ?GutterInjection
+ {
+ return $this->gutterInjection;
+ }
+
+ public function addLanguage(Language $language): self
+ {
+ $this->languages[$language->getName()] = $language;
+
+ foreach ($language->getAliases() as $alias) {
+ $this->languages[$alias] = $language;
+ }
+
+ $this->nestedHighlighter = null;
+
+ return $this;
+ }
+
+ public function parse(string $content, string|Language $language): string
+ {
+ if (is_string($language)) {
+ $language = $this->languages[$language] ?? $this->fallbackLanguage;
+ }
+
+ $this->currentLanguage = $language;
+
+ $content = $this->normalizeNewline($content);
+
+ return $this->parseContent($content, $language);
+ }
+
+ public function getTheme(): Theme
+ {
+ return $this->theme;
+ }
+
+ public function getCurrentLanguage(): ?Language
+ {
+ return $this->currentLanguage;
+ }
+
+ public function getSupportedLanguageNames(): array
+ {
+ return array_keys($this->languages);
+ }
+
+ public function setCurrentLanguage(Language $language): void
+ {
+ $this->currentLanguage = $language;
+ }
+
+ public function nested(): self
+ {
+ $clone = clone $this;
+
+ $clone->isNested = true;
+ $clone->nestedHighlighter = null;
+
+ return $clone;
+ }
+
+ private function getNestedHighlighter(): self
+ {
+ if ($this->nestedHighlighter instanceof Highlighter) {
+ return $this->nestedHighlighter;
+ }
+
+ $this->nestedHighlighter = $this->nested();
+
+ return $this->nestedHighlighter;
+ }
+
+ private function parseContent(string $content, Language $language): string
+ {
+ $tokens = [];
+ $nestedHighlighter = $this->getNestedHighlighter();
+
+ // Before Injections
+ foreach ($this->getBeforeInjections($language) as $injection) {
+ $parsedInjection = $injection->parse($content, $nestedHighlighter);
+ $content = $parsedInjection->content;
+
+ foreach ($parsedInjection->tokens as $token) {
+ $tokens[] = $token;
+ }
+ }
+
+ // Patterns
+ foreach (
+ $this->parseTokens->parse($content, $this->getPatterns($language)) as $token
+ ) {
+ $tokens[] = $token;
+ }
+
+ $groupedTokens = ($this->groupTokens)($tokens);
+ $content = ($this->renderTokens)($content, $groupedTokens);
+
+ // After Injections
+ foreach ($this->getAfterInjections($language) as $injection) {
+ $parsedInjection = $injection->parse($content, $nestedHighlighter);
+ $content = $parsedInjection->content;
+ }
+
+ if ($this->isNested) {
+ return $content;
+ }
+
+ return $this->theme->escape($content);
+ }
+
+ /**
+ * @return Injection[]
+ */
+ private function getBeforeInjections(Language $language): array
+ {
+ $languageId = spl_object_id($language);
+
+ if (isset($this->beforeInjectionsCache[$languageId])) {
+ return $this->beforeInjectionsCache[$languageId];
+ }
+
+ $this->buildInjectionCaches($language);
+
+ return $this->beforeInjectionsCache[$languageId];
+ }
+
+ /**
+ * @return Injection[]
+ */
+ private function getAfterInjections(Language $language): array
+ {
+ if ($this->isNested) {
+ return [];
+ }
+
+ $languageId = spl_object_id($language);
+
+ if (! isset($this->afterInjectionsCache[$languageId])) {
+ $this->buildInjectionCaches($language);
+ }
+
+ $afterInjections = $this->afterInjectionsCache[$languageId];
+
+ if ($this->gutterInjection instanceof GutterInjection) {
+ $afterInjections[] = $this->gutterInjection;
+ }
+
+ return $afterInjections;
+ }
+
+ private function buildInjectionCaches(Language $language): void
+ {
+ $languageId = spl_object_id($language);
+ $before = [];
+ $after = [];
+
+ foreach ($language->getInjections() as $injection) {
+ if ($this->isAfterInjection($injection)) {
+ $after[] = $injection;
+ } else {
+ $before[] = $injection;
+ }
+ }
+
+ $this->beforeInjectionsCache[$languageId] = $before;
+ $this->afterInjectionsCache[$languageId] = $after;
+ }
+
+ private function normalizeNewline(string $subject): string
+ {
+ if (! str_contains($subject, "\r")) {
+ return $subject;
+ }
+
+ return str_replace(["\r\n", "\r"], "\n", $subject);
+ }
+
+ private function getPatterns(Language $language): array
+ {
+ $languageId = spl_object_id($language);
+
+ return $this->patterns[$languageId] ??= $language->getPatterns();
+ }
+
+ private function isAfterInjection(Injection $injection): bool
+ {
+ $class = $injection::class;
+
+ return $this->afterInjections[$class] ??=
+ new ReflectionClass($class)->getAttributes(After::class) !== [];
+ }
+}
diff --git a/assets/tempest/highlight/src/Injection.php b/assets/tempest/highlight/src/Injection.php
new file mode 100644
index 0000000..55c11a2
--- /dev/null
+++ b/assets/tempest/highlight/src/Injection.php
@@ -0,0 +1,10 @@
+getPattern();
+
+ if (($pattern[0] ?? '') !== '/') {
+ $pattern = "/{$pattern}/";
+ }
+
+ $result = preg_replace_callback(
+ pattern: $pattern,
+ callback: function (array $matches) use ($highlighter): string {
+ $content = $matches['match'] ?? '';
+
+ if ($content === '' || $content === '0') {
+ return $matches[0];
+ }
+
+ return str_replace(
+ search: $content,
+ replace: $this->parseContent($content, $highlighter),
+ subject: $matches[0],
+ );
+ },
+ subject: $content,
+ );
+
+ return new ParsedInjection($result ?? $content);
+ }
+}
diff --git a/assets/tempest/highlight/src/IsPattern.php b/assets/tempest/highlight/src/IsPattern.php
new file mode 100644
index 0000000..7fb5fd8
--- /dev/null
+++ b/assets/tempest/highlight/src/IsPattern.php
@@ -0,0 +1,23 @@
+getPattern();
+
+ if (($pattern[0] ?? '') !== '/') {
+ $pattern = "/{$pattern}/";
+ }
+
+ preg_match_all($pattern, $content, $matches, PREG_OFFSET_CAPTURE);
+
+ return $matches;
+ }
+}
diff --git a/assets/tempest/highlight/src/Language.php b/assets/tempest/highlight/src/Language.php
new file mode 100644
index 0000000..438d655
--- /dev/null
+++ b/assets/tempest/highlight/src/Language.php
@@ -0,0 +1,22 @@
+', output: 'VirtualHost')]
+#[PatternTest(input: '', output: 'Directory')]
+#[PatternTest(input: '', output: 'IfModule')]
+final readonly class ApacheCloseTagPattern implements Pattern
+{
+ use IsPattern;
+
+ public function getPattern(): string
+ {
+ return '<\/(?[A-Za-z]\w*)';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::KEYWORD;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Apache/Patterns/ApacheCommentPattern.php b/assets/tempest/highlight/src/Languages/Apache/Patterns/ApacheCommentPattern.php
new file mode 100644
index 0000000..2209ccc
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Apache/Patterns/ApacheCommentPattern.php
@@ -0,0 +1,27 @@
+#.*)';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::COMMENT;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Apache/Patterns/ApacheDirectivePattern.php b/assets/tempest/highlight/src/Languages/Apache/Patterns/ApacheDirectivePattern.php
new file mode 100644
index 0000000..5b81b3f
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Apache/Patterns/ApacheDirectivePattern.php
@@ -0,0 +1,55 @@
+{$directives})\b/m";
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::KEYWORD;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Apache/Patterns/ApacheFlagPattern.php b/assets/tempest/highlight/src/Languages/Apache/Patterns/ApacheFlagPattern.php
new file mode 100644
index 0000000..fa240f2
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Apache/Patterns/ApacheFlagPattern.php
@@ -0,0 +1,31 @@
+on|off|All|None|granted|denied)\b/';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::VALUE;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Apache/Patterns/ApacheOpenTagPattern.php b/assets/tempest/highlight/src/Languages/Apache/Patterns/ApacheOpenTagPattern.php
new file mode 100644
index 0000000..22643b9
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Apache/Patterns/ApacheOpenTagPattern.php
@@ -0,0 +1,30 @@
+', output: 'VirtualHost')]
+#[PatternTest(input: '', output: 'Directory')]
+#[PatternTest(input: '', output: 'Location')]
+#[PatternTest(input: '', output: 'IfModule')]
+#[PatternTest(input: '', output: 'FilesMatch')]
+final readonly class ApacheOpenTagPattern implements Pattern
+{
+ use IsPattern;
+
+ public function getPattern(): string
+ {
+ return '<(?[A-Za-z]\w*)';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::KEYWORD;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/BBCode/BBCodeLanguage.php b/assets/tempest/highlight/src/Languages/BBCode/BBCodeLanguage.php
new file mode 100644
index 0000000..711da01
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/BBCode/BBCodeLanguage.php
@@ -0,0 +1,30 @@
+[^\]]+)\]';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::ATTRIBUTE;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/BBCode/Patterns/BBCodeCloseTagPattern.php b/assets/tempest/highlight/src/Languages/BBCode/Patterns/BBCodeCloseTagPattern.php
new file mode 100644
index 0000000..398b9ba
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/BBCode/Patterns/BBCodeCloseTagPattern.php
@@ -0,0 +1,29 @@
+[a-zA-Z]+)\]';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::KEYWORD;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/BBCode/Patterns/BBCodeTagPattern.php b/assets/tempest/highlight/src/Languages/BBCode/Patterns/BBCodeTagPattern.php
new file mode 100644
index 0000000..2dfbdfb
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/BBCode/Patterns/BBCodeTagPattern.php
@@ -0,0 +1,31 @@
+[a-zA-Z\*]+)[\]=]';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::KEYWORD;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Base/BaseLanguage.php b/assets/tempest/highlight/src/Languages/Base/BaseLanguage.php
new file mode 100644
index 0000000..dd24020
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Base/BaseLanguage.php
@@ -0,0 +1,49 @@
+normalizeLineEndings($content);
+
+ $content = str_replace('❷span class=❹ignore❹❸{+❷/span❸', '{+', $content);
+ $content = str_replace('❷span class=❹ignore❹❸+}❷/span❸', '+}', $content);
+
+ preg_match_all('/(\{\+)((.|\n)*?)(\+})/', $content, $matches, PREG_OFFSET_CAPTURE);
+
+ $parsedOffset = 0;
+
+ $open = Escape::tokens('');
+ $close = Escape::tokens('');
+
+ foreach ($matches[0] as $match) {
+ $matchedContent = $match[0];
+ $offset = $match[1];
+ $replacementOffset = $offset + $parsedOffset;
+
+ $parsedMatchedContent = str_replace(
+ ['{+', self::NEWLINE, '+}'],
+ [$open, $close . self::NEWLINE . $open, $close],
+ $matchedContent,
+ );
+
+ if (($gutter = $highlighter->getGutterInjection()) instanceof GutterInjection) {
+ $startingLineNumber = substr_count(
+ haystack: $content,
+ needle: self::NEWLINE,
+ length: $replacementOffset,
+ ) + 1;
+
+ $totalAmountOfLines = substr_count(
+ haystack: $parsedMatchedContent,
+ needle: self::NEWLINE,
+ );
+
+ for ($lineNumber = $startingLineNumber; $lineNumber <= $startingLineNumber + $totalAmountOfLines; $lineNumber++) {
+ $gutter
+ ->addIcon($lineNumber, '+')
+ ->addClass($lineNumber, 'hl-gutter-addition');
+ }
+ }
+
+ $content = substr_replace(
+ $content,
+ $parsedMatchedContent,
+ $replacementOffset,
+ strlen($matchedContent),
+ );
+
+ $parsedOffset += strlen($parsedMatchedContent) - strlen($matchedContent);
+ }
+
+ return new ParsedInjection($content);
+ }
+
+ private function normalizeLineEndings(string $content): string
+ {
+ if (! str_contains($content, "\r")) {
+ return $content;
+ }
+
+ return str_replace(["\r\n", "\r"], self::NEWLINE, $content);
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Base/Injections/BlurInjection.php b/assets/tempest/highlight/src/Languages/Base/Injections/BlurInjection.php
new file mode 100644
index 0000000..cd7b208
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Base/Injections/BlurInjection.php
@@ -0,0 +1,23 @@
+{\:(?[\w-]+)\:)(?(.|\n)*?)(?:})/';
+
+ $tokens = [];
+
+ preg_match_all($pattern, $content, $matches, PREG_OFFSET_CAPTURE);
+
+ $additionalOffset = 0;
+
+ foreach (array_keys($matches[0]) as $key) {
+ $startToken = $matches['start'][$key][0];
+ $endToken = $matches['end'][$key][0];
+ $className = $matches['class'][$key][0];
+
+ $additionalOffset += strlen($startToken);
+
+ $tokens[] = new Token(
+ offset: $matches['match'][$key][1] - $additionalOffset,
+ value: $matches['match'][$key][0],
+ type: new DynamicTokenType($className),
+ );
+
+ $additionalOffset += strlen($endToken);
+
+ $content = str_replace([$startToken, $endToken], '', $content);
+ }
+
+ return new ParsedInjection(
+ content: $content,
+ tokens: $tokens,
+ );
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Base/Injections/DeletionInjection.php b/assets/tempest/highlight/src/Languages/Base/Injections/DeletionInjection.php
new file mode 100644
index 0000000..7b96959
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Base/Injections/DeletionInjection.php
@@ -0,0 +1,83 @@
+normalizeLineEndings($content);
+
+ $content = str_replace('❷span class=❹ignore❹❸{-❷/span❸', '{-', $content);
+ $content = str_replace('❷span class=❹ignore❹❸-}❷/span❸', '-}', $content);
+
+ preg_match_all('/(?');
+ $close = Escape::tokens('');
+
+ foreach ($matches[0] as $match) {
+ $matchedContent = $match[0];
+ $offset = $match[1];
+ $replacementOffset = $offset + $parsedOffset;
+
+ $parsedMatchedContent = str_replace(
+ ['{-', self::NEWLINE, '-}'],
+ [$open, $close . self::NEWLINE . $open, $close],
+ $matchedContent,
+ );
+
+ if (($gutter = $highlighter->getGutterInjection()) instanceof GutterInjection) {
+ $startingLineNumber = substr_count(
+ haystack: $content,
+ needle: self::NEWLINE,
+ length: $replacementOffset,
+ ) + 1;
+
+ $totalAmountOfLines = substr_count(
+ haystack: $parsedMatchedContent,
+ needle: self::NEWLINE,
+ ) + 1;
+
+ for ($lineNumber = $startingLineNumber; $lineNumber < $startingLineNumber + $totalAmountOfLines; $lineNumber++) {
+ $gutter
+ ->addIcon($lineNumber, '-')
+ ->addClass($lineNumber, 'hl-gutter-deletion');
+ }
+ }
+
+ $content = substr_replace(
+ $content,
+ $parsedMatchedContent,
+ $replacementOffset,
+ strlen($matchedContent),
+ );
+
+ $parsedOffset += strlen($parsedMatchedContent) - strlen($matchedContent);
+ }
+
+ return new ParsedInjection($content);
+ }
+
+ private function normalizeLineEndings(string $content): string
+ {
+ if (! str_contains($content, "\r")) {
+ return $content;
+ }
+
+ return str_replace(["\r\n", "\r"], self::NEWLINE, $content);
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Base/Injections/EmphasizeInjection.php b/assets/tempest/highlight/src/Languages/Base/Injections/EmphasizeInjection.php
new file mode 100644
index 0000000..ef079fb
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Base/Injections/EmphasizeInjection.php
@@ -0,0 +1,23 @@
+icons[$line + $this->startAt - 1] = $token;
+
+ return $this;
+ }
+
+ public function addClass(int $line, string $class): self
+ {
+ $this->classes[$line + $this->startAt - 1] = $class;
+
+ return $this;
+ }
+
+ public function parse(string $content, Highlighter $highlighter): ParsedInjection
+ {
+ $lines = preg_split('/\R/u', trim($content, "\n")) ?? '';
+
+ $gutterNumbers = [];
+ $longestGutterNumber = '';
+
+ foreach ($lines as $i => $line) {
+ $gutterNumber = $i + $this->startAt;
+
+ if ($icon = ($this->icons[$i + $this->startAt] ?? null)) {
+ $gutterNumber .= ' ' . $icon;
+ }
+
+ $gutterNumbers[$i] = $gutterNumber;
+
+ if (strlen($longestGutterNumber) < strlen((string) $gutterNumber)) {
+ $longestGutterNumber = (string) $gutterNumber;
+ }
+ }
+
+ $gutterWidth = strlen($longestGutterNumber);
+
+ foreach ($lines as $i => $line) {
+ $gutterNumber = $gutterNumbers[$i];
+
+ $hasClasses = $this->classes[$i + $this->startAt] ?? '';
+ $gutterClass = 'hl-gutter' . ($hasClasses ? ' ' . $hasClasses : '');
+
+ $lines[$i] = sprintf(
+ Escape::tokens('%s%s%s'),
+ $gutterClass,
+ str_pad(
+ string: (string) $gutterNumber,
+ length: $gutterWidth,
+ pad_type: STR_PAD_LEFT,
+ ),
+ $highlighter->getTheme() instanceof TerminalTheme ? ' ' : '',
+ $line,
+ );
+ }
+
+ return new ParsedInjection(implode(PHP_EOL, $lines));
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Base/Injections/StrongInjection.php b/assets/tempest/highlight/src/Languages/Base/Injections/StrongInjection.php
new file mode 100644
index 0000000..6461479
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Base/Injections/StrongInjection.php
@@ -0,0 +1,23 @@
+getToken();
+
+ $pattern = '/(?{' . $token . ')(?(.|\n)*?)(?' . $token . '})(?!})/';
+
+ preg_match_all($pattern, $content, $matches, PREG_OFFSET_CAPTURE);
+
+ $tokens = [];
+
+ foreach (array_keys($matches[0]) as $key) {
+ $tokens[] = new Token(
+ offset: (int)$matches['start'][$key][1],
+ value: $matches['start'][$key][0],
+ type: TokenTypeEnum::HIDDEN,
+ );
+
+ $tokens[] = new Token(
+ offset: (int)$matches['match'][$key][1],
+ value: $matches['match'][$key][0],
+ type: new DynamicTokenType($this->getClassname()),
+ );
+
+ $tokens[] = new Token(
+ offset: (int)$matches['end'][$key][1],
+ value: $matches['end'][$key][0],
+ type: TokenTypeEnum::HIDDEN,
+ );
+ }
+
+ return new ParsedInjection(
+ content: $content,
+ tokens: $tokens,
+ );
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Base/Patterns/AdditionEndTokenPattern.php b/assets/tempest/highlight/src/Languages/Base/Patterns/AdditionEndTokenPattern.php
new file mode 100644
index 0000000..5807a49
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Base/Patterns/AdditionEndTokenPattern.php
@@ -0,0 +1,27 @@
+\+})/';
+ }
+
+ public function getTokenType(): TokenType
+ {
+ return new IgnoreTokenType();
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Base/Patterns/AdditionStartTokenPattern.php b/assets/tempest/highlight/src/Languages/Base/Patterns/AdditionStartTokenPattern.php
new file mode 100644
index 0000000..8355b1a
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Base/Patterns/AdditionStartTokenPattern.php
@@ -0,0 +1,27 @@
+{\+)/';
+ }
+
+ public function getTokenType(): TokenType
+ {
+ return new IgnoreTokenType();
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Base/Patterns/DeletionEndTokenPattern.php b/assets/tempest/highlight/src/Languages/Base/Patterns/DeletionEndTokenPattern.php
new file mode 100644
index 0000000..857c61b
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Base/Patterns/DeletionEndTokenPattern.php
@@ -0,0 +1,29 @@
+\-})(?!})/';
+ }
+
+ public function getTokenType(): TokenType
+ {
+ return new IgnoreTokenType();
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Base/Patterns/DeletionStartTokenPattern.php b/assets/tempest/highlight/src/Languages/Base/Patterns/DeletionStartTokenPattern.php
new file mode 100644
index 0000000..8ed79d3
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Base/Patterns/DeletionStartTokenPattern.php
@@ -0,0 +1,29 @@
+{\-)(?!-)/';
+ }
+
+ public function getTokenType(): TokenType
+ {
+ return new IgnoreTokenType();
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Base/Patterns/InjectionTokenPattern.php b/assets/tempest/highlight/src/Languages/Base/Patterns/InjectionTokenPattern.php
new file mode 100644
index 0000000..d3fc47f
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Base/Patterns/InjectionTokenPattern.php
@@ -0,0 +1,25 @@
+(.|\n)*?)' . Escape::INJECTION_TOKEN;
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::INJECTION;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Bash/BashLanguage.php b/assets/tempest/highlight/src/Languages/Bash/BashLanguage.php
new file mode 100644
index 0000000..c1894bb
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Bash/BashLanguage.php
@@ -0,0 +1,72 @@
+builtins);
+
+ return "\b(?(?:{$builtins}))\b";
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::TYPE;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Bash/Patterns/BashCommentPattern.php b/assets/tempest/highlight/src/Languages/Bash/Patterns/BashCommentPattern.php
new file mode 100644
index 0000000..7ee0dfe
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Bash/Patterns/BashCommentPattern.php
@@ -0,0 +1,26 @@
+#.*)';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::COMMENT;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Bash/Patterns/BashFlagPattern.php b/assets/tempest/highlight/src/Languages/Bash/Patterns/BashFlagPattern.php
new file mode 100644
index 0000000..b6d6e9d
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Bash/Patterns/BashFlagPattern.php
@@ -0,0 +1,27 @@
+--?[a-zA-Z][\w-]*)';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::GENERIC;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Bash/Patterns/BashKeywordPattern.php b/assets/tempest/highlight/src/Languages/Bash/Patterns/BashKeywordPattern.php
new file mode 100644
index 0000000..119b3d2
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Bash/Patterns/BashKeywordPattern.php
@@ -0,0 +1,35 @@
+keywords);
+
+ return "\b(?(?:{$keywords}))\b";
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::KEYWORD;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Bash/Patterns/BashNumberPattern.php b/assets/tempest/highlight/src/Languages/Bash/Patterns/BashNumberPattern.php
new file mode 100644
index 0000000..2451978
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Bash/Patterns/BashNumberPattern.php
@@ -0,0 +1,26 @@
+\d+)\b';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::NUMBER;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Bash/Patterns/BashOperatorPattern.php b/assets/tempest/highlight/src/Languages/Bash/Patterns/BashOperatorPattern.php
new file mode 100644
index 0000000..9db5580
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Bash/Patterns/BashOperatorPattern.php
@@ -0,0 +1,27 @@
+\|\||&&|>>|<<|;;|[|><;])';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::OPERATOR;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Bash/Patterns/BashShebangPattern.php b/assets/tempest/highlight/src/Languages/Bash/Patterns/BashShebangPattern.php
new file mode 100644
index 0000000..65ec5d1
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Bash/Patterns/BashShebangPattern.php
@@ -0,0 +1,27 @@
+\#!\/.*)/';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::COMMENT;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Bash/Patterns/BashVariablePattern.php b/assets/tempest/highlight/src/Languages/Bash/Patterns/BashVariablePattern.php
new file mode 100644
index 0000000..90fe14a
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Bash/Patterns/BashVariablePattern.php
@@ -0,0 +1,27 @@
+\$\{[^}]+\}|\$[a-zA-Z_]\w*|\$[\d@?#!\$\-\*])';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::VARIABLE;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Blade/BladeLanguage.php b/assets/tempest/highlight/src/Languages/Blade/BladeLanguage.php
new file mode 100644
index 0000000..a07b75e
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Blade/BladeLanguage.php
@@ -0,0 +1,49 @@
+.*)(}})';
+ }
+
+ public function parseContent(string $content, Highlighter $highlighter): string
+ {
+ return $highlighter->parse($content, 'php');
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Blade/Injections/BladeKeywordInjection.php b/assets/tempest/highlight/src/Languages/Blade/Injections/BladeKeywordInjection.php
new file mode 100644
index 0000000..79ead7b
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Blade/Injections/BladeKeywordInjection.php
@@ -0,0 +1,24 @@
+.*)\)';
+ }
+
+ public function parseContent(string $content, Highlighter $highlighter): string
+ {
+ return $highlighter->parse($content, 'php');
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Blade/Injections/BladePhpInjection.php b/assets/tempest/highlight/src/Languages/Blade/Injections/BladePhpInjection.php
new file mode 100644
index 0000000..a3bd9bf
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Blade/Injections/BladePhpInjection.php
@@ -0,0 +1,24 @@
+(.|\n)*?)\@endphp';
+ }
+
+ public function parseContent(string $content, Highlighter $highlighter): string
+ {
+ return $highlighter->parse($content, 'php');
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Blade/Injections/BladeRawEchoInjection.php b/assets/tempest/highlight/src/Languages/Blade/Injections/BladeRawEchoInjection.php
new file mode 100644
index 0000000..85361ca
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Blade/Injections/BladeRawEchoInjection.php
@@ -0,0 +1,24 @@
+.*)(!!})';
+ }
+
+ public function parseContent(string $content, Highlighter $highlighter): string
+ {
+ return $highlighter->parse($content, 'php');
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Blade/Patterns/BladeCommentPattern.php b/assets/tempest/highlight/src/Languages/Blade/Patterns/BladeCommentPattern.php
new file mode 100644
index 0000000..7a69df2
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Blade/Patterns/BladeCommentPattern.php
@@ -0,0 +1,29 @@
+\{\{\-\-(.|\n)*?\-\-\}\})';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::COMMENT;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Blade/Patterns/BladeComponentCloseTagPattern.php b/assets/tempest/highlight/src/Languages/Blade/Patterns/BladeComponentCloseTagPattern.php
new file mode 100644
index 0000000..e089f1c
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Blade/Patterns/BladeComponentCloseTagPattern.php
@@ -0,0 +1,29 @@
+', output: 'a')]
+#[PatternTest(input: '', output: 'x-hello')]
+#[PatternTest(input: '', output: 'x-hello::world')]
+#[PatternTest(input: '', output: 'x-hello::world.lorem')]
+final readonly class BladeComponentCloseTagPattern implements Pattern
+{
+ use IsPattern;
+
+ public function getPattern(): string
+ {
+ return '<\/(?[\w\-\:\.]+)';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::KEYWORD;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Blade/Patterns/BladeComponentOpenTagPattern.php b/assets/tempest/highlight/src/Languages/Blade/Patterns/BladeComponentOpenTagPattern.php
new file mode 100644
index 0000000..edcd938
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Blade/Patterns/BladeComponentOpenTagPattern.php
@@ -0,0 +1,29 @@
+', output: 'a')]
+#[PatternTest(input: '', output: 'x-hello')]
+#[PatternTest(input: '', output: 'x-hello::world')]
+#[PatternTest(input: '', output: 'x-hello::world.lorem')]
+final readonly class BladeComponentOpenTagPattern implements Pattern
+{
+ use IsPattern;
+
+ public function getPattern(): string
+ {
+ return '<(?[\w\-\:\.]+)';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::KEYWORD;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Blade/Patterns/BladeKeywordPattern.php b/assets/tempest/highlight/src/Languages/Blade/Patterns/BladeKeywordPattern.php
new file mode 100644
index 0000000..6f5d3c9
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Blade/Patterns/BladeKeywordPattern.php
@@ -0,0 +1,30 @@
+\@[\w]+)\b';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::KEYWORD;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Css/CssLanguage.php b/assets/tempest/highlight/src/Languages/Css/CssLanguage.php
new file mode 100644
index 0000000..af23603
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Css/CssLanguage.php
@@ -0,0 +1,46 @@
+[\w\-]+):';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::PROPERTY;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Css/Patterns/CssCommentPattern.php b/assets/tempest/highlight/src/Languages/Css/Patterns/CssCommentPattern.php
new file mode 100644
index 0000000..af72ec6
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Css/Patterns/CssCommentPattern.php
@@ -0,0 +1,32 @@
+\/\*(.|\n)*?\*\/)/m';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::COMMENT;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Css/Patterns/CssFunctionPattern.php b/assets/tempest/highlight/src/Languages/Css/Patterns/CssFunctionPattern.php
new file mode 100644
index 0000000..167c3c2
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Css/Patterns/CssFunctionPattern.php
@@ -0,0 +1,34 @@
+[\w\-]+)\(';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::KEYWORD;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Css/Patterns/CssImportPattern.php b/assets/tempest/highlight/src/Languages/Css/Patterns/CssImportPattern.php
new file mode 100644
index 0000000..d0221cd
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Css/Patterns/CssImportPattern.php
@@ -0,0 +1,26 @@
+\@import)';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::KEYWORD;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Css/Patterns/CssMediaQueryPattern.php b/assets/tempest/highlight/src/Languages/Css/Patterns/CssMediaQueryPattern.php
new file mode 100644
index 0000000..ee38e7a
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Css/Patterns/CssMediaQueryPattern.php
@@ -0,0 +1,27 @@
+\@media(.*)?){';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::KEYWORD;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Css/Patterns/CssSelectorPattern.php b/assets/tempest/highlight/src/Languages/Css/Patterns/CssSelectorPattern.php
new file mode 100644
index 0000000..820e3d4
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Css/Patterns/CssSelectorPattern.php
@@ -0,0 +1,41 @@
+[\[\]\'\"\=\@\-\#\.\w\s,\n\+\:\(\)\*]+)\{';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::KEYWORD;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Css/Patterns/CssVariablePattern.php b/assets/tempest/highlight/src/Languages/Css/Patterns/CssVariablePattern.php
new file mode 100644
index 0000000..44521b9
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Css/Patterns/CssVariablePattern.php
@@ -0,0 +1,27 @@
+\-\-[\w\-]+)';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::PROPERTY;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Diff/DiffLanguage.php b/assets/tempest/highlight/src/Languages/Diff/DiffLanguage.php
new file mode 100644
index 0000000..36b1371
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Diff/DiffLanguage.php
@@ -0,0 +1,28 @@
++ ');
+ $close = Escape::tokens('');
+
+ return $open . $matches[1] . $close;
+ },
+ $content
+ );
+
+ return new ParsedInjection($content);
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Diff/Injections/DiffDeletionInjection.php b/assets/tempest/highlight/src/Languages/Diff/Injections/DiffDeletionInjection.php
new file mode 100644
index 0000000..531c92a
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Diff/Injections/DiffDeletionInjection.php
@@ -0,0 +1,29 @@
+- ');
+ $close = Escape::tokens('');
+
+ return $open . $matches[1] . $close; // Wraps the matched line with the span
+ },
+ $content
+ );
+
+ return new ParsedInjection($content);
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/DocComment/DocCommentLanguage.php b/assets/tempest/highlight/src/Languages/DocComment/DocCommentLanguage.php
new file mode 100644
index 0000000..1212635
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/DocComment/DocCommentLanguage.php
@@ -0,0 +1,34 @@
+\@[\w]+)';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::VALUE;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Dockerfile/DockerfileLanguage.php b/assets/tempest/highlight/src/Languages/Dockerfile/DockerfileLanguage.php
new file mode 100644
index 0000000..2ecb249
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Dockerfile/DockerfileLanguage.php
@@ -0,0 +1,65 @@
+\#.*)/';
+ }
+
+ public function getTokenType(): TokenType
+ {
+ return TokenTypeEnum::COMMENT;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Dockerfile/Patterns/DoubleQuoteValuePattern.php b/assets/tempest/highlight/src/Languages/Dockerfile/Patterns/DoubleQuoteValuePattern.php
new file mode 100644
index 0000000..8dbebe0
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Dockerfile/Patterns/DoubleQuoteValuePattern.php
@@ -0,0 +1,27 @@
+"(\\\"|.)*?")';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::VALUE;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Dockerfile/Patterns/ImageAliasKeywordPattern.php b/assets/tempest/highlight/src/Languages/Dockerfile/Patterns/ImageAliasKeywordPattern.php
new file mode 100644
index 0000000..8a3d1e6
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Dockerfile/Patterns/ImageAliasKeywordPattern.php
@@ -0,0 +1,29 @@
+AS)[\s][\S]+/m";
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::KEYWORD;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Dockerfile/Patterns/ImageAliasNamePattern.php b/assets/tempest/highlight/src/Languages/Dockerfile/Patterns/ImageAliasNamePattern.php
new file mode 100644
index 0000000..748afa0
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Dockerfile/Patterns/ImageAliasNamePattern.php
@@ -0,0 +1,29 @@
+[\S]+)/m";
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::VALUE;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Dockerfile/Patterns/ImageNamePattern.php b/assets/tempest/highlight/src/Languages/Dockerfile/Patterns/ImageNamePattern.php
new file mode 100644
index 0000000..3c1601c
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Dockerfile/Patterns/ImageNamePattern.php
@@ -0,0 +1,31 @@
+[\w\/]+)/m";
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::VALUE;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Dockerfile/Patterns/ImageTagPattern.php b/assets/tempest/highlight/src/Languages/Dockerfile/Patterns/ImageTagPattern.php
new file mode 100644
index 0000000..730d637
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Dockerfile/Patterns/ImageTagPattern.php
@@ -0,0 +1,31 @@
+\S+)[\s]?/m";
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::VALUE;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Dockerfile/Patterns/KeywordPattern.php b/assets/tempest/highlight/src/Languages/Dockerfile/Patterns/KeywordPattern.php
new file mode 100644
index 0000000..cb1b694
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Dockerfile/Patterns/KeywordPattern.php
@@ -0,0 +1,28 @@
+{$this->keyword})[\s].*/m";
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::KEYWORD;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Dockerfile/Patterns/SingleQuoteValuePattern.php b/assets/tempest/highlight/src/Languages/Dockerfile/Patterns/SingleQuoteValuePattern.php
new file mode 100644
index 0000000..3d5e93f
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Dockerfile/Patterns/SingleQuoteValuePattern.php
@@ -0,0 +1,26 @@
+'(\\\'|.)*?')";
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::VALUE;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/DotEnv/DotEnvLanguage.php b/assets/tempest/highlight/src/Languages/DotEnv/DotEnvLanguage.php
new file mode 100644
index 0000000..0be5e8a
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/DotEnv/DotEnvLanguage.php
@@ -0,0 +1,34 @@
+\#.*)/';
+ }
+
+ public function getTokenType(): TokenType
+ {
+ return TokenTypeEnum::COMMENT;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/DotEnv/Patterns/DotEnvKeyPattern.php b/assets/tempest/highlight/src/Languages/DotEnv/Patterns/DotEnvKeyPattern.php
new file mode 100644
index 0000000..ea0f80d
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/DotEnv/Patterns/DotEnvKeyPattern.php
@@ -0,0 +1,27 @@
+[\w].*)=/';
+ }
+
+ public function getTokenType(): TokenType
+ {
+ return TokenTypeEnum::KEYWORD;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Ellison/EllisonLanguage.php b/assets/tempest/highlight/src/Languages/Ellison/EllisonLanguage.php
new file mode 100644
index 0000000..2c9e169
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Ellison/EllisonLanguage.php
@@ -0,0 +1,26 @@
+(.|\n)*)';
+ }
+
+ public function parseContent(string $content, Highlighter $highlighter): string
+ {
+ $ellison = new Ellison();
+ $parsed = [];
+ $paragraphs = preg_split('/\R/u', trim($content));
+
+ foreach ($paragraphs as $paragraph) {
+ $parsedParagraph = '';
+
+ $sentences = $ellison->getSentenceDifficulty($paragraph);
+
+ foreach ($sentences as $sentence) {
+ ['text' => $text, 'type' => $type] = $sentence;
+
+ $text = trim($text);
+
+ $parsedParagraph .=
+ Escape::tokens("")
+ . trim($this->parseSentence($ellison, $text), ' .')
+ . '. '
+ . Escape::tokens("");
+ }
+
+ $parsed[] = $parsedParagraph;
+ }
+
+ return implode(PHP_EOL, $parsed);
+ }
+
+ private function parseSentence(Ellison $ellison, string $sentence): string
+ {
+ $problems = [
+ ...$ellison->getPassivePhrases($sentence),
+ ...$ellison->getAdverbPhrases($sentence),
+ ...$ellison->getComplexPhrases($sentence),
+ ...$ellison->getQualifiedPhrases($sentence),
+ ];
+
+ foreach ($problems as $problem) {
+ $sentence = preg_replace(
+ "/" . preg_quote((string) $problem['text']) . "/i",
+ Escape::tokens("") . $problem['text'] . Escape::tokens(""),
+ (string) $sentence,
+ );
+ }
+
+ return $sentence;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Gdscript/GdscriptLanguage.php b/assets/tempest/highlight/src/Languages/Gdscript/GdscriptLanguage.php
new file mode 100644
index 0000000..9e4fd79
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Gdscript/GdscriptLanguage.php
@@ -0,0 +1,132 @@
+>='),
+ new OperatorPattern('**'),
+ new OperatorPattern('||'),
+ new OperatorPattern('&&'),
+ new OperatorPattern('<='),
+ new OperatorPattern('>='),
+ new OperatorPattern('=='),
+ new OperatorPattern('!='),
+ new OperatorPattern('<<'),
+ new OperatorPattern('>>'),
+ new OperatorPattern('+='),
+ new OperatorPattern('-='),
+ new OperatorPattern('*='),
+ new OperatorPattern('/='),
+ new OperatorPattern('%='),
+ new OperatorPattern('&='),
+ new OperatorPattern('|='),
+ new OperatorPattern('^='),
+ new OperatorPattern('->'),
+ new OperatorPattern('/'),
+ new OperatorPattern('%'),
+ new OperatorPattern('+'),
+ new OperatorPattern('-'),
+ new OperatorPattern('*'),
+ new OperatorPattern('/'),
+ new OperatorPattern('&'),
+ new OperatorPattern('^'),
+ new OperatorPattern('|'),
+ new OperatorPattern('<'),
+ new OperatorPattern('>'),
+ new OperatorPattern('!'),
+
+ new KeywordPattern('if'),
+ new KeywordPattern('elif'),
+ new KeywordPattern('else'),
+ new KeywordPattern('for'),
+ new KeywordPattern('while'),
+ new KeywordPattern('match'),
+ new KeywordPattern('break'),
+ new KeywordPattern('continue'),
+ new KeywordPattern('pass'),
+ new KeywordPattern('return'),
+ new KeywordPattern('class'),
+ new KeywordPattern('class_name'),
+ new KeywordPattern('extends'),
+ new KeywordPattern('is'),
+ new KeywordPattern('not'),
+ new KeywordPattern('and'),
+ new KeywordPattern('or'),
+ new KeywordPattern('in'),
+ new KeywordPattern('as'),
+ new KeywordPattern('self'),
+ new KeywordPattern('signal'),
+ new KeywordPattern('func'),
+ new KeywordPattern('static'),
+ new KeywordPattern('const'),
+ new KeywordPattern('enum'),
+ new KeywordPattern('var'),
+ new KeywordPattern('breakpoint'),
+ new KeywordPattern('preload'),
+ new KeywordPattern('await'),
+ new KeywordPattern('yield'),
+ new KeywordPattern('assert'),
+ new KeywordPattern('super'),
+ new KeywordPattern('PI'),
+ new KeywordPattern('INF'),
+ new KeywordPattern('TAU'),
+ new KeywordPattern('NAN'),
+
+ new SinglelineCommentPattern(),
+
+ new AnnotationPattern(),
+ new ExtendsPattern(),
+ new ClassNamePattern(),
+ new FunctionNamePattern(),
+ new FunctionCallPattern(),
+ new ReturnTypePattern(),
+ new AsTypePattern(),
+ new VarTypePattern(),
+ new PropertyAccessPattern(),
+
+ new SingleQuoteValuePattern(),
+ new DoubleQuoteValuePattern(),
+ ];
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Gdscript/Patterns/AnnotationPattern.php b/assets/tempest/highlight/src/Languages/Gdscript/Patterns/AnnotationPattern.php
new file mode 100644
index 0000000..aa91528
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Gdscript/Patterns/AnnotationPattern.php
@@ -0,0 +1,49 @@
+(@onready|@icon|@export|@export_{$variations}|@rpc|@static_unload|@tool|@warning_ignore))";
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::KEYWORD;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Gdscript/Patterns/AsTypePattern.php b/assets/tempest/highlight/src/Languages/Gdscript/Patterns/AsTypePattern.php
new file mode 100644
index 0000000..52cd194
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Gdscript/Patterns/AsTypePattern.php
@@ -0,0 +1,26 @@
+[a-zA-Z][a-zA-Z0-9]+)\b';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::TYPE;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Gdscript/Patterns/ClassNamePattern.php b/assets/tempest/highlight/src/Languages/Gdscript/Patterns/ClassNamePattern.php
new file mode 100644
index 0000000..62f14bf
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Gdscript/Patterns/ClassNamePattern.php
@@ -0,0 +1,26 @@
+[\w]+)';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::TYPE;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Gdscript/Patterns/DoubleQuoteValuePattern.php b/assets/tempest/highlight/src/Languages/Gdscript/Patterns/DoubleQuoteValuePattern.php
new file mode 100644
index 0000000..7546519
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Gdscript/Patterns/DoubleQuoteValuePattern.php
@@ -0,0 +1,29 @@
+.*?)"';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::VALUE;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Gdscript/Patterns/ExtendsPattern.php b/assets/tempest/highlight/src/Languages/Gdscript/Patterns/ExtendsPattern.php
new file mode 100644
index 0000000..9e270c1
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Gdscript/Patterns/ExtendsPattern.php
@@ -0,0 +1,26 @@
+[\w]+)';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::TYPE;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Gdscript/Patterns/FunctionCallPattern.php b/assets/tempest/highlight/src/Languages/Gdscript/Patterns/FunctionCallPattern.php
new file mode 100644
index 0000000..b26afd8
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Gdscript/Patterns/FunctionCallPattern.php
@@ -0,0 +1,27 @@
+[a-z][\w]+)\(';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::PROPERTY;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Gdscript/Patterns/FunctionNamePattern.php b/assets/tempest/highlight/src/Languages/Gdscript/Patterns/FunctionNamePattern.php
new file mode 100644
index 0000000..29129a5
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Gdscript/Patterns/FunctionNamePattern.php
@@ -0,0 +1,26 @@
+[\w]+)';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::PROPERTY;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Gdscript/Patterns/KeywordPattern.php b/assets/tempest/highlight/src/Languages/Gdscript/Patterns/KeywordPattern.php
new file mode 100644
index 0000000..3ec08b4
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Gdscript/Patterns/KeywordPattern.php
@@ -0,0 +1,28 @@
+{$this->keyword})\b/";
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::KEYWORD;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Gdscript/Patterns/OperatorPattern.php b/assets/tempest/highlight/src/Languages/Gdscript/Patterns/OperatorPattern.php
new file mode 100644
index 0000000..e6dcdda
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Gdscript/Patterns/OperatorPattern.php
@@ -0,0 +1,30 @@
+operator, '/');
+
+ return "/(?{$quoted})/";
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::OPERATOR;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Gdscript/Patterns/PropertyAccessPattern.php b/assets/tempest/highlight/src/Languages/Gdscript/Patterns/PropertyAccessPattern.php
new file mode 100644
index 0000000..108300e
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Gdscript/Patterns/PropertyAccessPattern.php
@@ -0,0 +1,26 @@
+[\w]+)';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::PROPERTY;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Gdscript/Patterns/ReturnTypePattern.php b/assets/tempest/highlight/src/Languages/Gdscript/Patterns/ReturnTypePattern.php
new file mode 100644
index 0000000..8324564
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Gdscript/Patterns/ReturnTypePattern.php
@@ -0,0 +1,27 @@
+Bar:', output: 'Bar')]
+#[PatternTest(input: 'func foo() -> Bar :', output: 'Bar')]
+final readonly class ReturnTypePattern implements Pattern
+{
+ use IsPattern;
+
+ public function getPattern(): string
+ {
+ return '\)\s*\-\>\s*(?.+?)[\s*:\n]';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::TYPE;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Gdscript/Patterns/SingleQuoteValuePattern.php b/assets/tempest/highlight/src/Languages/Gdscript/Patterns/SingleQuoteValuePattern.php
new file mode 100644
index 0000000..103e00c
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Gdscript/Patterns/SingleQuoteValuePattern.php
@@ -0,0 +1,29 @@
+.*?)'";
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::VALUE;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Gdscript/Patterns/SinglelineCommentPattern.php b/assets/tempest/highlight/src/Languages/Gdscript/Patterns/SinglelineCommentPattern.php
new file mode 100644
index 0000000..48db039
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Gdscript/Patterns/SinglelineCommentPattern.php
@@ -0,0 +1,26 @@
+#(.)*)';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::COMMENT;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Gdscript/Patterns/VarTypePattern.php b/assets/tempest/highlight/src/Languages/Gdscript/Patterns/VarTypePattern.php
new file mode 100644
index 0000000..3bb2454
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Gdscript/Patterns/VarTypePattern.php
@@ -0,0 +1,28 @@
+\w+)';
+ }
+
+ public function getTokenType(): TokenTypeEnum
+ {
+ return TokenTypeEnum::TYPE;
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Html/HtmlLanguage.php b/assets/tempest/highlight/src/Languages/Html/HtmlLanguage.php
new file mode 100644
index 0000000..bc2a7af
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Html/HtmlLanguage.php
@@ -0,0 +1,51 @@
+(.|\n)*?)"';
+ }
+
+ public function parseContent(string $content, Highlighter $highlighter): string
+ {
+ return $highlighter->parse($content, 'css');
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Html/Injections/CssInHtmlInjection.php b/assets/tempest/highlight/src/Languages/Html/Injections/CssInHtmlInjection.php
new file mode 100644
index 0000000..5f51cab
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Html/Injections/CssInHtmlInjection.php
@@ -0,0 +1,24 @@
+(?(.|\n)*?)<\/style>';
+ }
+
+ public function parseContent(string $content, Highlighter $highlighter): string
+ {
+ return $highlighter->parse($content, 'css');
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Html/Injections/JavaScriptInHtmlInjection.php b/assets/tempest/highlight/src/Languages/Html/Injections/JavaScriptInHtmlInjection.php
new file mode 100644
index 0000000..ef37d9f
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Html/Injections/JavaScriptInHtmlInjection.php
@@ -0,0 +1,25 @@
+(?(.|\n)*)<\/script>';
+ }
+
+ public function parseContent(string $content, Highlighter $highlighter): string
+ {
+ return $highlighter->parse($content, new JavaScriptLanguage());
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Html/Injections/PhpInHtmlInjection.php b/assets/tempest/highlight/src/Languages/Html/Injections/PhpInHtmlInjection.php
new file mode 100644
index 0000000..21d34b6
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Html/Injections/PhpInHtmlInjection.php
@@ -0,0 +1,24 @@
+(.|\n)*?)\?>';
+ }
+
+ public function parseContent(string $content, Highlighter $highlighter): string
+ {
+ return $highlighter->parse($content, 'php');
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Html/Injections/PhpShortEchoInHtmlInjection.php b/assets/tempest/highlight/src/Languages/Html/Injections/PhpShortEchoInHtmlInjection.php
new file mode 100644
index 0000000..845afc5
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Html/Injections/PhpShortEchoInHtmlInjection.php
@@ -0,0 +1,24 @@
+.*)\s\?>/';
+ }
+
+ public function parseContent(string $content, Highlighter $highlighter): string
+ {
+ return $highlighter->parse($content, 'php');
+ }
+}
diff --git a/assets/tempest/highlight/src/Languages/Html/Patterns/HtmlAttributePattern.php b/assets/tempest/highlight/src/Languages/Html/Patterns/HtmlAttributePattern.php
new file mode 100644
index 0000000..01fa3a2
--- /dev/null
+++ b/assets/tempest/highlight/src/Languages/Html/Patterns/HtmlAttributePattern.php
@@ -0,0 +1,47 @@
+', output: 'attr')]
+#[PatternTest(input: '', output: 'href')]
+#[PatternTest(input: '', output: 'data-type')]
+#[PatternTest(input: '', output: ':foreach')]
+#[PatternTest(input: '', output: 'xmlns:xsl')]
+#[PatternTest(input: "- ", output: 'attr')]
+#[PatternTest(input: "
- ", output: 'simple')]
+#[PatternTest(input: "
- ", output: 'with-hyphen')]
+#[PatternTest(input: "", output: ['class', 'style'])]
+#[PatternTest(input: '
- ', output: 'id')]
+#[PatternTest(input: '
- ', output: 'type')]
+#[PatternTest(input: '
- ', output: 'a')]
+#[PatternTest(input: '', output: null)]
+# Not yet implemented
+# #[PatternTest(input: "
-
-
-
' . $title . '
-';
-}
-
-function make_footer ()
-{
- echo '
-