Docs
Syntax highlighting is broken.
Take a look at this example, from the configuration file of Laravel Octane.

Most of the classes are completely unstyled!
return [
    'listeners' => [
        WorkerStarting::class => [                                      ❌
            EnsureUploadedFilesAreValid::class,                         ❌
        ],

        RequestReceived::class => [                                     ❌
            ...Octane::prepareApplicationForNextOperation(),            ❌
            ...Octane::prepareApplicationForNextRequest(),              ❌
        ],

        WorkerErrorOccurred::class => [                                 ❌
            ReportException::class,                                     ❌
            StopWorkerIfNecessary::class,                               ❌
        ],
    ],

    'warm' => [
        ...Octane::defaultServicesToWarm(),                             ❌
    ],
]
Wrong
Blade templates are completely wrong.

Take a look at the last couple of lines. What's going on there?
@if (count($records) === 1)                                             ❌
    I have one record!
@elseif (count($records) > 1)                                           ❌
    I have {{ count($records) }} records!                               ❌
@else
    I don't have <span class='font-bold'>any</span> records!            ❌
@endif                                                                  ❌
Wrong
It misses most of this JavaScript content as well.
fs.readdirSync(path.join(process.cwd(), 'posts')).forEach(slug => {     ❌
    let content = fs.readFileSync(                                      ❌
        path.join(process.cwd(), 'posts', slug),                        ❌
        'utf-8'
    )

    fs.writeFileSync(                                                   ❌
        path.join(process.cwd(), 'build', `${slug}.html`),              ❌
        md.render(content)                                              ❌
    )                                                                   ❌
})                                                                      ❌
Wrong
Torchlight
is an API for syntax highlighting.
Powered by the VS Code engine,
focused on the joy of authoring.

It always gets it right, even for esoteric stuff.

Use any available VS Code theme, or make your own.

It supports every language VS Code supports, and any language you can find a tmLanguage.json file for.

It requires no JavaScript. Really. Torchlight is an HTTP API with server-side clients. Fully highlighted HTML is sent to the browser. No FOUC.

Let's take a look at those examples again.

Highlight.js misses all the classes here.
return [
    'listeners' => [
        WorkerStarting::class => [                                      ❌
            EnsureUploadedFilesAreValid::class,                         ❌
        ],

        RequestReceived::class => [                                     ❌
            ...Octane::prepareApplicationForNextOperation(),            ❌
            ...Octane::prepareApplicationForNextRequest(),              ❌
        ],

        WorkerErrorOccurred::class => [                                 ❌
            ReportException::class,                                     ❌
            StopWorkerIfNecessary::class,                               ❌
        ],
    ],

    'warm' => [
        ...Octane::defaultServicesToWarm(),                             ❌
    ],
]
Torchlight correctly renders them all.
1return [
2 'listeners' => [
3 WorkerStarting::class => [
4 EnsureUploadedFilesAreValid::class,
5 ],
6 
7 RequestReceived::class => [
8 ...Octane::prepareApplicationForNextOperation(),
9 ...Octane::prepareApplicationForNextRequest(),
10 ],
11 
12 WorkerErrorOccurred::class => [
13 ReportException::class,
14 StopWorkerIfNecessary::class,
15 ],
16 ],
17 
18 'warm' => [
19 ...Octane::defaultServicesToWarm(),
20 ],
21]
Highlight.js doesn't support Blade at all.
@if (count($records) === 1)                                             ❌
    I have one record!
@elseif (count($records) > 1)                                           ❌
    I have {{ count($records) }} records!                               ❌
@else
    I don't have <span class='font-bold'>any</span> records!            ❌
@endif                                                                  ❌
Torchlight gets the Blade, PHP, and HTML right.
1@if (count($records) === 1) ✅
2 I have one record!
3@elseif (count($records) > 1) ✅
4 I have {{ count($records) }} records! ✅
5@else
6 I don't have <span class='font-bold'>any</span> records! ✅
7@endif
Highlight.js doesn't correctly pick up the JavaScript methods
fs.readdirSync(path.join(process.cwd(), 'posts')).forEach(slug => {     ❌
    let content = fs.readFileSync(                                      ❌
        path.join(process.cwd(), 'posts', slug),                        ❌
        'utf-8'
    )

    fs.writeFileSync(                                                   ❌
        path.join(process.cwd(), 'build', `${slug}.html`),              ❌
        md.render(content)                                              ❌
    )                                                                   ❌
})                                                                      ❌
Torchlight correctly picks them all up.
1fs.readdirSync(path.join(process.cwd(), 'posts')).forEach(slug => { ✅
2 let content = fs.readFileSync( ✅
3 path.join(process.cwd(), 'posts', slug), ✅
4 'utf-8'
5 )
6 
7 fs.writeFileSync( ✅
8 path.join(process.cwd(), 'build', `${slug}.html`), ✅
9 md.render(content) ✅
10 ) ✅
11}) ✅

Getting the highlighting right is table-stakes.

Torchlight focuses on the author experiences by helping you communicate your ideas clearly.

You can control the display of your code using comments in your code.

Using actual comments means no more messed up indentation or squiggly underlines in your editor.

No more indecipherable syntaxes to remember to put at the top of your file. Annotate with inline comments.

Let's look at some annotations.

Focus Your Reader's Attention
Quickly focus your reader's attention on the relevant portions of your code, while still giving the full context.
Hover over the block to bring the whole block into focus.
No JavaScript required! Just plain ol' CSS.
Take a look at the source tab to see how it's done using Torchlight's focus annotation.
1<?php
2/**
3 * Chunk the collection into chunks of the given size.
4 *
5 * @param int $size
6 * @return static
7 */
8public function chunk($size)
9{
10 if ($size <= 0) {
11 return new static;
12 }
13 
14 $chunks = [];
15 
16 foreach (array_chunk($this->items, $size, true) as $chunk) {
17 $chunks[] = new static($chunk);
18 }
19 
20 return new static($chunks);
21}
<?php
/**
 * Chunk the collection into chunks of the given size.
 *
 * @param int $size
 * @return static
 */
public function chunk($size)
{
    if ($size <= 0) {
        return new static;
    }

    $chunks = [];

    foreach (array_chunk($this->items, $size, true) as $chunk) { // [tl! **]
        $chunks[] = new static($chunk); // [tl! **]
    } // [tl! **]

    return new static($chunks);
}
Language: PHP. Theme: Github Dark
Clearly Visualize Differences Diffs
When explaining something in your docs or your blog posts, it's helpful to show your reader exactly what has changed.
Take a look at the source tab to see how it's done using Torchlight's diff annotation.
1<?php
2/**
3 * Chunk the collection into chunks of the given size.
4 *
5 * @param int $size
6 * @return static
7 */
8public function chunk($size)
9{
10 if ($size <= 0) {
11 return new static;
12 }
13 
14 $chunks = array();
15 $chunks = [];
16 
17 foreach (array_chunk($this->items, $size, true) as $chunk) {
18 $chunks[] = new static($chunk);
19 }
20 
21 return new static($chunks);
22}
<?php
/**
 * Chunk the collection into chunks of the given size.
 *
 * @param int $size
 * @return static
 */
public function chunk($size)
{
    if ($size <= 0) {
        return new static;
    }

    $chunks = array(); // [tl! --]
    $chunks = []; // [tl! ++]

    foreach (array_chunk($this->items, $size, true) as $chunk) {
        $chunks[] = new static($chunk);
    }

    return new static($chunks);
}
Language: PHP. Theme: Github Dark
Combine Diffing and Focusing
Combine both if you want, you have total control over how your code is presented.
Take a look at the source tab to see how easy it is to annotate your code.
1<?php
2/**
3 * Chunk the collection into chunks of the given size.
4 *
5 * @param int $size
6 * @return static
7 */
8public function chunk($size)
9{
10 if ($size <= 0) {
11 return new static;
12 }
13 
14 $chunks = array();
15 $chunks = [];
16 
17 foreach (array_chunk($this->items, $size, true) as $chunk) {
18 $chunks[] = new static($chunk);
19 }
20 
21 return new static($chunks);
22}
<?php
/**
 * Chunk the collection into chunks of the given size.
 *
 * @param int $size
 * @return static
 */
public function chunk($size)
{
    if ($size <= 0) {
        return new static;
    }

    $chunks = array(); // [tl! -- **]
    $chunks = []; // [tl! ++ **]

    foreach (array_chunk($this->items, $size, true) as $chunk) {
        $chunks[] = new static($chunk);
    }

    return new static($chunks);
}
Language: PHP. Theme: Github Dark
Collapse Irrelevant Parts, Without JavaScript!
Sometimes you want to build up an entire code example as you go, but not show every line on every step.
With collapsing, you can keep your reader focused on the parts you're talking about, while also giving them the full context if they need it.
As always, no JavaScript required!
Take a look at the source tab to see how it's done using Torchlight's collapse annotation.
1<?php
2 
3class Client
4{
5 public function highlight($blocks) ...
6 {
7 $blocks = collect($blocks)->keyBy->id();
8 
9 // First set the html from the cache if it is already stored.
10 $this->setHtmlFromCache($blocks);
11 
12 // Then reject all the blocks that already have the html, which
13 // will leave us with only the blocks we need to request.
14 $needed = $blocks->reject(function ($block) {
15 return (bool)$block->html;
16 });
17 
18 $needed = $blocks->reject->html;
19 
20 // If there are any blocks that don't have html yet,
21 // we fire a request.
22 if ($needed->count()) {
23 // This method will set the html on the block objects,
24 // so we don't do anything with the return value.
25 $this->request($needed);
26 }
27 
28 return $blocks->values()->toArray();
29 }
30 
31 protected function request(Collection $blocks) ...
32 {
33 $response = Http::timeout(5)
34 ->withToken(config('torchlight.token'))
35 ->post('https://torchlight.dev/api/highlight', [
36 'blocks' => $blocks->map->toRequestParams()->values(),
37 ])
38 ->json();
39 
40 $response = collect($response['blocks'])->keyBy('id');
41 
42 $blocks->each(function (Block $block) use ($response) {
43 $block->setHtml(
44 $block->html ?? $this->getHtmlFromResponse($response, $block)
45 );
46 });
47 
48 $this->setCacheFromBlocks($blocks);
49 
50 return $blocks;
51 }
52 
53 public function cache()
54 {
55 $store = config('torchlight.cache');
56 
57 if ($store === null) {
58 $store = config('cache.default');
59 }
60 
61 return Cache::store($store);
62 }
63}
<?php

class Client
{
    public function highlight($blocks) // [tl! collapse:start]
    {
        $blocks = collect($blocks)->keyBy->id();

        // First set the html from the cache if it is already stored.
        $this->setHtmlFromCache($blocks);

        // Then reject all the blocks that already have the html, which
        // will leave us with only the blocks we need to request.
        $needed = $blocks->reject(function ($block) {
            return (bool)$block->html;
        });

        $needed = $blocks->reject->html;

        // If there are any blocks that don't have html yet,
        // we fire a request.
        if ($needed->count()) {
            // This method will set the html on the block objects,
            // so we don't do anything with the return value.
            $this->request($needed);
        }

        return $blocks->values()->toArray();
    }  // [tl! collapse:end]

    protected function request(Collection $blocks) // [tl! collapse:start]
    {
        $response = Http::timeout(5)
            ->withToken(config('torchlight.token'))
            ->post('https://torchlight.dev/api/highlight', [
                'blocks' => $blocks->map->toRequestParams()->values(),
            ])
            ->json();

        $response = collect($response['blocks'])->keyBy('id');

        $blocks->each(function (Block $block) use ($response) {
            $block->setHtml(
                $block->html ?? $this->getHtmlFromResponse($response, $block)
            );
        });

        $this->setCacheFromBlocks($blocks);

        return $blocks;
    }  // [tl! collapse:end]

    public function cache()
    {
        $store = config('torchlight.cache');

        if ($store === null) {
            $store = config('cache.default');
        }

        return Cache::store($store);
    }
}
Language: PHP. Theme: Github Dark

Insanely Easy Installation

Torchlight is an API, with clients for popular backend languages. We currently have a Laravel Client, a Jigsaw Client, and a Commonmark PHP Client (which works with Statamic as well!)

If you're using markdown, you add the extension and you're done!

Using Blade views? You just use the Blade component instead of the normal code tag.

1<x-code-torchlight language='php'>
2 // Any code here will be highlighted by Torchlight!
3</x-code-torchlight>

Did We Mention Themes? And Languages?

Lots of themes. Lots of languages.

1<?php
2 
3public function chunk($size)
4{
5 $chunks = [];
6 
7 foreach (array_chunk($this->items, $size, true) as $chunk) {
8 $chunks[] = new static($chunk);
9 }
10 
11 return new static($chunks);
12}
1class Person
2 attr_reader :name, :age
3 
4 def initialize(name, age)
5 @name, @age = name, age
6 end
7 
8 def <=>(person) # the comparison operator for sorting
9 @age <=> person.age
10 end
11 
12 def to_s
13 "#{@name} (#{@age})"
14 end
15end
16 
17group = [
18 Person.new("Bob", 33),
19 Person.new("Chris", 16),
20 Person.new("Ash", 23)
21]
22 
23puts group.sort.reverse
1n = int(input('Type a number, and its factorial will be printed: '))
2 
3if n < 0:
4 raise ValueError('You must enter a non negative integer')
5 
6fact = 1
7for i in range(2, n + 1):
8 fact *= i
9 
10print(fact)
1defmodule Fun do
2 def fib(0), do: 0
3 def fib(1), do: 1
4 def fib(n), do: fib(n-2) + fib(n-1)
5end
1<?php
2 
3public function chunk($size)
4{
5 $chunks = [];
6 
7 foreach (array_chunk($this->items, $size, true) as $chunk) {
8 $chunks[] = new static($chunk);
9 }
10 
11 return new static($chunks);
12}
1<?php
2 
3public function chunk($size)
4{
5 $chunks = [];
6 
7 foreach (array_chunk($this->items, $size, true) as $chunk) {
8 $chunks[] = new static($chunk);
9 }
10 
11 return new static($chunks);
12}
1<?php
2 
3public function chunk($size)
4{
5 $chunks = [];
6 
7 foreach (array_chunk($this->items, $size, true) as $chunk) {
8 $chunks[] = new static($chunk);
9 }
10 
11 return new static($chunks);
12}
1<?php
2 
3public function chunk($size)
4{
5 $chunks = [];
6 
7 foreach (array_chunk($this->items, $size, true) as $chunk) {
8 $chunks[] = new static($chunk);
9 }
10 
11 return new static($chunks);
12}
1<?php
2 
3public function chunk($size)
4{
5 $chunks = [];
6 
7 foreach (array_chunk($this->items, $size, true) as $chunk) {
8 $chunks[] = new static($chunk);
9 }
10 
11 return new static($chunks);
12}
1<?php
2 
3public function chunk($size)
4{
5 $chunks = [];
6 
7 foreach (array_chunk($this->items, $size, true) as $chunk) {
8 $chunks[] = new static($chunk);
9 }
10 
11 return new static($chunks);
12}
1<?php
2 
3public function chunk($size)
4{
5 $chunks = [];
6 
7 foreach (array_chunk($this->items, $size, true) as $chunk) {
8 $chunks[] = new static($chunk);
9 }
10 
11 return new static($chunks);
12}
1<?php
2 
3public function chunk($size)
4{
5 $chunks = [];
6 
7 foreach (array_chunk($this->items, $size, true) as $chunk) {
8 $chunks[] = new static($chunk);
9 }
10 
11 return new static($chunks);
12}
1<template>
2 <div>
3 <BaseInputText
4 v-model="newTodoText"
5 placeholder="New todo"
6 @keydown.enter="addTodo"
7 />
8 <ul v-if="todos.length">
9 <TodoListItem
10 v-for="todo in todos"
11 :key="todo.id"
12 :todo="todo"
13 />
14 </ul>
15 <p v-else>
16 Nothing left in the list. Add a new todo in the input above.
17 </p>
18 </div>
19</template>
1SELECT
2 *
3FROM
4 `users`
5 LEFT JOIN `addresses` ON `users`.`address_id` = `addresses`.`id`
6WHERE
7 `name` = 'Aaron' -- That's my name!
8 AND
9 YEAR(`birthday`) = 1989
1<?php
2 
3public function chunk($size)
4{
5 $chunks = [];
6 
7 foreach (array_chunk($this->items, $size, true) as $chunk) {
8 $chunks[] = new static($chunk);
9 }
10 
11 return new static($chunks);
12}
1<?php
2 
3public function chunk($size)
4{
5 $chunks = [];
6 
7 foreach (array_chunk($this->items, $size, true) as $chunk) {
8 $chunks[] = new static($chunk);
9 }
10 
11 return new static($chunks);
12}
1<template>
2 <div>
3 <BaseInputText
4 v-model="newTodoText"
5 placeholder="New todo"
6 @keydown.enter="addTodo"
7 />
8 <ul v-if="todos.length">
9 <TodoListItem
10 v-for="todo in todos"
11 :key="todo.id"
12 :todo="todo"
13 />
14 </ul>
15 <p v-else>
16 Nothing left in the list. Add a new todo in the input above.
17 </p>
18 </div>
19</template>
1<?php
2 
3public function chunk($size)
4{
5 $chunks = [];
6 
7 foreach (array_chunk($this->items, $size, true) as $chunk) {
8 $chunks[] = new static($chunk);
9 }
10 
11 return new static($chunks);
12}
1<?php
2 
3public function chunk($size)
4{
5 $chunks = [];
6 
7 foreach (array_chunk($this->items, $size, true) as $chunk) {
8 $chunks[] = new static($chunk);
9 }
10 
11 return new static($chunks);
12}
1<?php
2 
3public function chunk($size)
4{
5 $chunks = [];
6 
7 foreach (array_chunk($this->items, $size, true) as $chunk) {
8 $chunks[] = new static($chunk);
9 }
10 
11 return new static($chunks);
12}
1<?php
2 
3public function chunk($size)
4{
5 $chunks = [];
6 
7 foreach (array_chunk($this->items, $size, true) as $chunk) {
8 $chunks[] = new static($chunk);
9 }
10 
11 return new static($chunks);
12}
1<?php
2 
3public function chunk($size)
4{
5 $chunks = [];
6 
7 foreach (array_chunk($this->items, $size, true) as $chunk) {
8 $chunks[] = new static($chunk);
9 }
10 
11 return new static($chunks);
12}
1<?php
2 
3public function chunk($size)
4{
5 $chunks = [];
6 
7 foreach (array_chunk($this->items, $size, true) as $chunk) {
8 $chunks[] = new static($chunk);
9 }
10 
11 return new static($chunks);
12}
1<?php
2 
3public function chunk($size)
4{
5 $chunks = [];
6 
7 foreach (array_chunk($this->items, $size, true) as $chunk) {
8 $chunks[] = new static($chunk);
9 }
10 
11 return new static($chunks);
12}
We're almost there... but we're not quite ready yet!
Do you have thoughts so far? I'd love to hear them.
Hit me up on Twitter or get on the list below!
https://twitter.com/aarondfrancis.
A Hammerstone, LLC Product.
Built with Love & Care by Aaron in Dallas, Texas.