Skip to content

Inline code highlighting #65

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@ $environment
->addExtension(new PhikiExtension('github-dark', withWrapper: true));
```

#### Inline code highlighting

If you wish to highlight inline code snippets in your Markdown content, you can specify a grammar after the opening backtick.

```md
`{php}echo "Hello, world!"`
```

This will produce a highlighted `<code>` element using the configured theme and the chosen grammar, specified inside of the `{}` characters.

### Laravel

If you're using Laravel's `Str::markdown()` or `str()->markdown()` methods, you can use the same CommonMark extension by passing it through to the method.
Expand Down
25 changes: 25 additions & 0 deletions meta/sample.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

set_time_limit(2);

use League\CommonMark\Environment\Environment as EnvironmentEnvironment;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\MarkdownConverter;
use Phiki\CommonMark\PhikiExtension;
use Phiki\Environment\Environment;
use Phiki\Grammar\DefaultGrammars;
use Phiki\Phiki;
Expand All @@ -17,6 +21,7 @@

$grammar = $_GET['grammar'] ?? 'php';
$withGutter = ($_GET['gutter'] ?? false) === 'on';
$markdown = $_GET['markdown'] ?? null;
$environment = Environment::default()->enableStrictMode();
/** @var \Phiki\Grammar\GrammarRepository $repository */
$repository = $environment->getGrammarRepository();
Expand Down Expand Up @@ -60,6 +65,14 @@

$tokenDiff = array_diff_multidimensional($tokens, $vscodeTextmateOutput, false);

$converter = new MarkdownConverter(
(new EnvironmentEnvironment())
->addExtension(new CommonMarkCoreExtension)
->addExtension(new PhikiExtension('github-dark'))
);

$generatedMarkdown = $markdown ? $converter->convert($markdown)->getContent() : null;

?>

<!DOCTYPE html>
Expand Down Expand Up @@ -154,6 +167,18 @@ class="flex items-center gap-x-4">
<?php dump($tokenDiff); ?>
</div>
</div>

<div class="grid grid-cols-2 gap-10 mt-10">
<div>
<p class="text-xl text-white mb-4">markdown:</p>
<form>
<textarea name="markdown" class="w-full min-h-[300px] text-black"><?= $markdown ? htmlspecialchars($markdown) : null ?></textarea>
<button>generate</button>
</form>
</div>

<div class="bg-white"><?= $generatedMarkdown ?></div>
</div>
</main>
</body>

Expand Down
34 changes: 34 additions & 0 deletions src/CommonMark/InlineCodeRenderer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Phiki\CommonMark;

use League\CommonMark\Extension\CommonMark\Node\Inline\Code;
use League\CommonMark\Extension\CommonMark\Renderer\Inline\CodeRenderer;
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use Phiki\Phiki;
use Phiki\Theme\Theme;

class InlineCodeRenderer implements NodeRendererInterface
{
public function __construct(
private string|array|Theme $theme,
private Phiki $phiki = new Phiki,
) {}

public function render(Node $node, ChildNodeRendererInterface $childRenderer)
{
if (! $node instanceof Code) {
throw new \InvalidArgumentException('Node must be instance of '.Code::class);
}

$internal = new CodeRenderer();

if (preg_match('/^\{([\w]+)}(.*)/', $node->getLiteral(), $match, PREG_UNMATCHED_AS_NULL) !== 1) {
return $internal->render($node, $childRenderer);
}

return $this->phiki->codeToHtml($match[2], $match[1], $this->theme, inline: true);
}
}
4 changes: 3 additions & 1 deletion src/CommonMark/PhikiExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use League\CommonMark\Environment\EnvironmentBuilderInterface;
use League\CommonMark\Extension\CommonMark\Node\Block\FencedCode;
use League\CommonMark\Extension\CommonMark\Node\Inline\Code;
use League\CommonMark\Extension\ExtensionInterface;
use Phiki\Phiki;
use Phiki\Theme\Theme;
Expand All @@ -24,6 +25,7 @@ public function __construct(
public function register(EnvironmentBuilderInterface $environment): void
{
$environment
->addRenderer(FencedCode::class, new CodeBlockRenderer($this->theme, $this->phiki, $this->withGutter, $this->withWrapper), 10);
->addRenderer(FencedCode::class, new CodeBlockRenderer($this->theme, $this->phiki, $this->withGutter, $this->withWrapper), 10)
->addRenderer(Code::class, new InlineCodeRenderer($this->theme, $this->phiki), 10);
}
}
44 changes: 42 additions & 2 deletions src/Generators/HtmlGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,20 @@ public function __construct(
protected array $themes,
protected bool $withGutter = false,
protected bool $withWrapper = false,
protected bool $inline = false,
) {}

public function generate(array $tokens): string
{
return $this->withWrapper ? $this->buildWrapper($tokens) : $this->buildPre($tokens);
if ($this->inline) {
return $this->buildCode($tokens);
}

if ($this->withWrapper) {
return $this->buildWrapper($tokens);
}

return $this->buildPre($tokens);
}

private function buildWrapper($tokens): string
Expand Down Expand Up @@ -81,7 +90,38 @@ private function buildCode(array $tokens): string
$output[] = $this->buildLine($line, $i);
}

return '<code>'.implode($output).'</code>';
if (! $this->inline) {
return '<code>' . implode($output) . '</code>';
}

$codeClasses = array_filter([
'phiki-inline',
$this->grammarName ? "language-$this->grammarName" : null,
$this->getDefaultTheme()->name,
count($this->themes) > 1 ? 'phiki-themes' : null,
]);

foreach ($this->themes as $theme) {
if ($theme !== $this->getDefaultTheme()) {
$codeClasses[] = $theme->name;
}
}

$codeStyles = [$this->getDefaultTheme()->base()->toStyleString()];

foreach ($this->themes as $id => $theme) {
if ($id !== $this->getDefaultThemeId()) {
$codeStyles[] = $theme->base()->toCssVarString($id);
}
}

return sprintf(
'<code class="%s"%s style="%s">%s</code>',
implode(' ', $codeClasses),
$this->grammarName ? " data-language=\"$this->grammarName\"" : null,
implode(';', $codeStyles),
implode('', $output),
);
}

private function buildLine(array $line, int $index): string
Expand Down
3 changes: 2 additions & 1 deletion src/Phiki.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public function codeToHighlightedTokens(string $code, string|Grammar $grammar, s
* @param bool $withGutter Include a gutter in the generated HTML. The gutter typically contains line numbers and helps provide context for the code.
* @param bool $withWrapper Wrap the generated HTML in an additional `<div>` so that it can be styled with CSS. Useful for avoiding overflow issues.
*/
public function codeToHtml(string $code, string|Grammar $grammar, string|array|Theme $theme, bool $withGutter = false, bool $withWrapper = false): string
public function codeToHtml(string $code, string|Grammar $grammar, string|array|Theme $theme, bool $withGutter = false, bool $withWrapper = false, bool $inline = false): string
{
$tokens = $this->codeToHighlightedTokens($code, $grammar, $theme);
$generator = new HtmlGenerator(
Expand All @@ -69,6 +69,7 @@ public function codeToHtml(string $code, string|Grammar $grammar, string|array|T
$this->wrapThemes($theme),
$withGutter,
$withWrapper,
$inline,
);

return $generator->generate($tokens);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<p><code class="phiki-inline language-php github-dark" data-language="php" style="background-color: #24292e;color: #e1e4e8;"><span class="line"><span class="token" style="color: #79b8ff;">echo</span><span class="token"> </span><span class="token" style="color: #9ecbff;">&quot;</span><span class="token" style="color: #9ecbff;">Hello, world!</span><span class="token" style="color: #9ecbff;">&quot;</span><span class="token">;</span></span>
</code></p>
15 changes: 15 additions & 0 deletions tests/Unit/CommonMark/PhikiExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,19 @@ class A {}
expect($generated)
->toContain('data-language="php"');
});

it('highlights inline code when grammar is present', function () {
$environment = new Environment;

$environment
->addExtension(new CommonMarkCoreExtension)
->addExtension(new PhikiExtension('github-dark'));

$markdown = new MarkdownConverter($environment);
$generated = $markdown->convert(<<<'MD'
`{php}echo "Hello, world!";`
MD)->getContent();

expect($generated)->toMatchSnapshot();
});
});