vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Highlight with Prism in a worker and gzip text responses
306f36fcef89c145abcd0c5331453d0b179f37ce · Keenan Nemetz · 12d ago · parent e5542ac6
Highlight with Prism in a worker and gzip text responses
The code browser now names a blob's language and lets the browser colour it.
Chroma is gone: server/web/highlight.go is an extension table, and
static/prism-worker.js runs Prism — every language Prism ships — off the main
thread. The server sends escaped lines with their numbers and L# anchors, so a
page is complete and scrollable before any script runs and the worker only swaps
the contents of each line cell. The table covers 186 languages; a file it does
not claim stays plain rather than being coloured by a guess.
The bundle those grammars make is 545 KB, so the instance now gzips text
responses itself instead of relying on a proxy, and static assets carry a
content ETag so a browser revalidates with a 304 rather than re-downloading the
bundle on every code page. The raw-download route opts out with
Cache-Control: no-transform because it promises an exact Content-Length.
This also makes JavaScript a requirement rather than a nicety: the label and
assignee token inputs lose their noscript save buttons and their comma-separated
text fallback, and the docs say so.
Measured: release binary 26.08 MB -> 22.70 MB; /r/{repo}/code 11.1 KB -> 2.6 KB
over the wire; the Prism bundle 545 KB -> 206 KB gzipped, fetched once.
scripts/vendor-prism.py regenerates the bundle from Prism's components.json
require graph, and --check fails if the committed assets drift.
server/web/static/prism-worker.js +65
| @@ -0,0 +1,65 @@ | |||
| 1 | /* Highlights one blob, off the main thread. | ||
| 2 | * | ||
| 3 | * Prism is string-only: given text and a grammar it returns HTML, so the whole | ||
| 4 | * job — tokenise, split into lines, send the lines back — runs here and a large | ||
| 5 | * file never blocks the page. The page keeps its server-rendered line table and | ||
| 6 | * swaps in the contents of each line cell, so line numbers and anchors are | ||
| 7 | * present before this file runs and stay put afterwards. | ||
| 8 | * | ||
| 9 | * Prism's core detects a WorkerGlobalScope, but `manual` keeps it from also | ||
| 10 | * trying to walk a document that does not exist. */ | ||
| 11 | |||
| 12 | self.Prism = { manual: true }; | ||
| 13 | importScripts('prism.min.js', 'prism-languages.min.js'); | ||
| 14 | |||
| 15 | // splitLines closes the spans that are open at a newline and reopens them on the | ||
| 16 | // next line, so a multi-line comment or string keeps its class across lines | ||
| 17 | // without one line's span swallowing the next. Prism escapes markup, so the only | ||
| 18 | // tags in its output are its own spans. | ||
| 19 | function splitLines(html) { | ||
| 20 | const lines = []; | ||
| 21 | const open = []; | ||
| 22 | let line = ''; | ||
| 23 | const tag = /<span[^>]*>|<\/span>|[^<]+/g; | ||
| 24 | let match; | ||
| 25 | while ((match = tag.exec(html)) !== null) { | ||
| 26 | const part = match[0]; | ||
| 27 | if (part.charAt(0) === '<') { | ||
| 28 | if (part.charAt(1) === '/') open.pop(); | ||
| 29 | else open.push(part); | ||
| 30 | line += part; | ||
| 31 | continue; | ||
| 32 | } | ||
| 33 | const pieces = part.split('\n'); | ||
| 34 | for (let i = 0; i < pieces.length; i++) { | ||
| 35 | if (i > 0) { | ||
| 36 | line += '</span>'.repeat(open.length); | ||
| 37 | lines.push(line); | ||
| 38 | line = open.join(''); | ||
| 39 | } | ||
| 40 | line += pieces[i]; | ||
| 41 | } | ||
| 42 | } | ||
| 43 | lines.push(line); | ||
| 44 | return lines; | ||
| 45 | } | ||
| 46 | |||
| 47 | self.onmessage = (event) => { | ||
| 48 | const job = event.data || {}; | ||
| 49 | const grammar = self.Prism.languages[job.lang]; | ||
| 50 | if (!grammar || typeof job.code !== 'string') { | ||
| 51 | // An unknown language is not an error: the page keeps the plain text it was | ||
| 52 | // served with. | ||
| 53 | self.postMessage({ id: job.id, lines: null }); | ||
| 54 | return; | ||
| 55 | } | ||
| 56 | let lines = null; | ||
| 57 | try { | ||
| 58 | lines = splitLines(self.Prism.highlight(job.code, grammar, job.lang)); | ||
| 59 | } catch (err) { | ||
| 60 | // A grammar that throws on this input must not take the page's highlighting | ||
| 61 | // down with it: answer plain, as if the language were unknown. | ||
| 62 | lines = null; | ||
| 63 | } | ||
| 64 | self.postMessage({ id: job.id, lines }); | ||
| 65 | }; | ||