Prezet

Switching the Syntax Highlighter in Prezet

Published On: March 16, 2025

Estimated Reading Time: 2 minutes

#Introduction

Prezet - the package this blog uses - comes bundled with support for Torchlight out-of-the-box. I understand the decision to include this but I personally am not a fan of Torchlight. Instead I like to use self-hosted solutions and that's what I'll be covering quickly in this post.

Prezet uses CommonMark for its markdown parsing and that gives us a lot of choices when picking a syntax highlighter. I have chosen Phiki for my use but there are other options available, as you may have guessed by the name this inspired by Shiki however it isn't a fork. My reasoning for choosing Phiki is not only the self-hosting but the ability to use VS Code themes and TextMate grammar files we can extend to include other languages we might need, LSL in the case for this blog in any style we fancy.

#Installation

Assuming you already know what package you want to use, you can skip this following step. However if you're continuing with Phiki and have yet to install it, this can be done with the following;

shell
$composer require phiki/phiki

#Configuration

From here, getting Prezet to use Phiki is going to be a bit of a long-winded path however in the long run it's going to be worth it, at least in my opinion.

Firstly we're going to want to make a ParseMarkdown class override. Mine is located in app/Actions to closely mimic the structure already found within the Prezet codebase, ideally maintaining that organisation already presented to us.

File: app\Actions\ParseMarkdown.php
php
 1<?php
 2
 3namespace App\Actions;
 4
 5use Exception;
 6use League\CommonMark\Environment\Environment;
 7use League\CommonMark\MarkdownConverter;
 8use League\CommonMark\Output\RenderedContentInterface;
 9use League\CommonMark\Exception\CommonMarkException;
10use BenBjurstrom\Prezet\Exceptions\InvalidConfigurationException;
11use BenBjurstrom\Prezet\Actions\ParseMarkdown as BaseParseMarkdown;
12use \BenBjurstrom\Prezet\Extensions\MarkdownBladeExtension;
13use Phiki\Phiki;
14
15class ParseMarkdown extends BaseParseMarkdown
16{
17    /**
18     * Override the handle function to customize extension instantiation.
19     *
20     * @param string $md
21     * @return RenderedContentInterface
22     * @throws Exception|CommonMarkException
23     */
24    public function handle(string $md): RenderedContentInterface
25    {
26        $config = config('prezet.commonmark.config');
27        if (! is_array($config)) {
28            throw new InvalidConfigurationException('prezet.commonmark.config', $config, 'is not an array');
29        }
30
31        $environment = new Environment($config);
32        $extensions = $this->getExtensions();
33
34        foreach ($extensions as $extension) {
35            if ($extension === \Phiki\CommonMark\PhikiExtension::class) {
36                $theme = config('phiki.theme');
37                $phikiEnv = \Phiki\Environment\Environment::default();
38
39                if (empty($theme)) {
40                    throw new Exception("Phiki theme is not configured");
41                }
42
43                foreach (config('phiki.languages') as $language => $grammarPath) {
44                    $phikiEnv->getGrammarRepository()->register($language, resource_path($grammarPath));
45                }
46
47                if (!empty(config('phiki.theme_path'))) {
48                    $phikiEnv->getThemeRepository()->register($theme, resource_path(config('phiki.theme_path')));
49                }
50
51                $withGutter = config('phiki.with_gutter', false);
52                $withWrapper = config('phiki.with_wrapper', false);
53
54                $phiki = new Phiki($phikiEnv);
55                $environment->addExtension(new $extension($theme, $phiki, withGutter: $withGutter, withWrapper: $withWrapper));
56            } else {
57                $environment->addExtension(new $extension());
58            }
59        }
60
61        $converter = new MarkdownConverter($environment);
62
63        MarkdownBladeExtension::$allowBladeForNextDocument = true;
64        $result = $converter->convert($md);
65        MarkdownBladeExtension::$allowBladeForNextDocument = false;
66
67        return $result;
68    }
69}

A lot of this code is taken from the original ParseMarkdown class that comes with Prezet, I've just added the Phiki specific code. In it you can see that Phiki has to get a specialised case as it takes an environment parameter on construct. I've also included some configuration parameters, these are in a new configuration file I've created called phiki.php in the config directory. This is a simple array of configuration options that are used in the ParseMarkdown class.

File: config\phiki.php
php
 1<?php
 2
 3return [
 4    'theme' => 'github-dark',
 5    'theme_path' => '',
 6    'languages' => [
 7        'lsl' => 'languages/lsl.json',
 8    ],
 9    'with_gutter' => true,
10    'with_wrapper' => true,
11];

Because I'm using a built-in theme of github-dark, the theme_path is not populated. If you're using a custom theme you can set this to the path of the theme file.

Other options I've added are with_gutter and with_wrapper, with_gutter is so far undocumented in Phiki and the documentation for line numbers is incorrect, referencing an older version at the time of writing. This adds line numbers to the code block, stylable as you see fit. with_wrapper is a simple boolean that wraps the code block in a div with a class of phiki-wrapper, used when trying to avoid issues with the CSS overflow property.

Now that we've got our class override, we need to ensure it is actually used in place of the original ParseMarkdown that comes with Prezet. We can achieve this by using Service Providers built into Laravel. This is one of those things we can also quickly create with an artisan command.

shell
$php artisan make:provider PrezetServiceProvider

I've named this in a generic fashion as in the future I want to add further functionality and markdown extensions, keeping this centralised means that I can come back to this and instead of creating more providers I can instead add them to this file.

File: app\Providers\PrezetServiceProvider.php
php
 1<?php
 2
 3namespace App\Providers;
 4
 5use Illuminate\Support\ServiceProvider;
 6use BenBjurstrom\Prezet\Actions\ParseMarkdown as BaseParseMarkdown;
 7use App\Actions\ParseMarkdown;  
 8
 9class PrezetServiceProvider extends ServiceProvider
10{
11    public function register()
12    {
13        $this->app->singleton(BaseParseMarkdown::class, function () {
14            return new ParseMarkdown();
15        });
16    }
17}

The contents of the file is quite simple, we bind the original ParseMarkdown to our implementation, meaning that if the original class is requested, Laravel will resolve it to our class.

When using the artisan command to make our provider it is generally added to the providers.php in the bootstrap directory however it is good to ensure that it has been added.

Finally we need to add Phiki to our CommonMark extensions. This is done in the prezet.php configuration file. This is a simple addition to the commonmark array.

File: config\prezet.php
php
 1// ... other configuration options
 2
 3'extensions' => [
 4    // ... other extensions
 5    \Phiki\CommonMark\PhikiExtension::class,
 6],
 7
 8// ... other configuration options

#Conclusion

On the surface this is all quite simple however it is a bit of trial and error to get everything working correctly. I hope this post has been helpful to you even if you're not using Phiki, the process should be similar for other syntax highlighters.