return [
'listeners' => [
WorkerStarting::class => [ ❌
EnsureUploadedFilesAreValid::class, ❌
],
RequestReceived::class => [ ❌
...Octane::prepareApplicationForNextOperation(), ❌
...Octane::prepareApplicationForNextRequest(), ❌
],
WorkerErrorOccurred::class => [ ❌
ReportException::class, ❌
StopWorkerIfNecessary::class, ❌
],
],
'warm' => [
...Octane::defaultServicesToWarm(), ❌
],
]
@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 ❌
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) ❌
) ❌
}) ❌
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.
return [
'listeners' => [
WorkerStarting::class => [ ❌
EnsureUploadedFilesAreValid::class, ❌
],
RequestReceived::class => [ ❌
...Octane::prepareApplicationForNextOperation(), ❌
...Octane::prepareApplicationForNextRequest(), ❌
],
WorkerErrorOccurred::class => [ ❌
ReportException::class, ❌
StopWorkerIfNecessary::class, ❌
],
],
'warm' => [
...Octane::defaultServicesToWarm(), ❌
],
]
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]
@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 ❌
1@if (count($records) === 1) ✅2 I have one record!3@elseif (count($records) > 1) ✅4 I have {{ count($records) }} records! ✅5@else6 I don't have <span class='font-bold'>any</span> records! ✅7@endif ✅
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) ❌
) ❌
}) ❌
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.
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);
}
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 - $chunks = array(); + $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);
}
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 - $chunks = array(); + $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);
}
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, which13 // 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);
}
}
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!) and a standalone CLI
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>
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.age10 end11 12 def to_s13 "#{@name} (#{@age})"14 end15end16 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 do2 def fib(0), do: 0+ 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 <TodoListItem10 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>
1SELECT2 *3FROM4 `users`5 LEFT JOIN `addresses` ON `users`.`address_id` = `addresses`.`id`6WHERE7 `name` = 'Aaron' -- That's my name! 8 AND9 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 <TodoListItem10 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}
Free forever for non-revenue generating sites, attribution required.
Low monthly cost for revenue generating sites, no attribution required.