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.
|
306f36fc
Keenan Nemetz
12d ago
|
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 | * | |
|
1b8675b1
Keenan Nemetz
8d ago
|
9 | * The worker starts with Prism's core — markup, css, clike, javascript — and a |
| 10 | * manifest of every other language, not with every grammar Prism ships. A job | |
| 11 | * that names a language this worker has not loaded imports that language's files | |
| 12 | * from static/prism-languages/ first, once: a page pays for the grammars it | |
| 13 | * shows, and the code browser rarely shows more than a few. The manifest entry | |
| 14 | * also carries the components that only tweak another grammar (js-extras, | |
| 15 | * php-extras, xml-doc), which is why a language already present can still have | |
| 16 | * files to import. | |
| 17 | * | |
|
ac00929f
Keenan Nemetz
12d ago
|
18 | * Prism's core detects a WorkerGlobalScope, but `manual` keeps it from trying to |
| 19 | * walk a document that does not exist and `disableWorkerMessageHandler` keeps it | |
| 20 | * from installing its own JSON-string listener on top of ours. */ | |
|
306f36fc
Keenan Nemetz
12d ago
|
21 | |
|
ac00929f
Keenan Nemetz
12d ago
|
22 | self.Prism = { manual: true, disableWorkerMessageHandler: true }; |
|
1b8675b1
Keenan Nemetz
8d ago
|
23 | importScripts('prism.min.js', 'prism-manifest.js'); |
| 24 | ||
| 25 | const has = (object, key) => Object.prototype.hasOwnProperty.call(object, key); | |
| 26 | ||
| 27 | // imported is what this worker has already run. importScripts re-executes a | |
| 28 | // script it is given twice, and an extras component run twice would insert its | |
| 29 | // grammar into the same language again, so each file is imported at most once. | |
| 30 | const imported = new Set(); | |
| 31 | ||
| 32 | // load imports a language's files in manifest order, dependencies first. A file | |
| 33 | // that will not load is not fatal and not remembered, so a later job tries it | |
| 34 | // again rather than inheriting this job's failure. | |
| 35 | function load(files) { | |
| 36 | const missing = files.filter((name) => !imported.has(name)); | |
| 37 | if (!missing.length) return true; | |
| 38 | try { | |
| 39 | // importScripts takes URLs as arguments; an array would stringify to one | |
| 40 | // comma-joined URL and fetch nothing. | |
| 41 | importScripts(...missing.map((name) => 'prism-languages/' + name + '.js')); | |
| 42 | } catch (err) { | |
| 43 | return false; | |
| 44 | } | |
| 45 | missing.forEach((name) => imported.add(name)); | |
| 46 | return true; | |
| 47 | } | |
| 48 | ||
| 49 | // grammarFor returns the grammar for a language, loading it first if needed. An | |
| 50 | // unknown name and a grammar that will not load both answer nothing, which the | |
| 51 | // caller turns into the plain text the page was served with. | |
| 52 | function grammarFor(lang) { | |
| 53 | const files = has(self.PRISM_LANGUAGES, lang) ? self.PRISM_LANGUAGES[lang] : null; | |
| 54 | if (files && !load(files)) return null; | |
| 55 | return has(self.Prism.languages, lang) ? self.Prism.languages[lang] : null; | |
| 56 | } | |
|
306f36fc
Keenan Nemetz
12d ago
|
57 | |
| 58 | // splitLines closes the spans that are open at a newline and reopens them on the | |
| 59 | // next line, so a multi-line comment or string keeps its class across lines | |
| 60 | // without one line's span swallowing the next. Prism escapes markup, so the only | |
| 61 | // tags in its output are its own spans. | |
| 62 | function splitLines(html) { | |
| 63 | const lines = []; | |
| 64 | const open = []; | |
| 65 | let line = ''; | |
| 66 | const tag = /<span[^>]*>|<\/span>|[^<]+/g; | |
| 67 | let match; | |
| 68 | while ((match = tag.exec(html)) !== null) { | |
| 69 | const part = match[0]; | |
| 70 | if (part.charAt(0) === '<') { | |
| 71 | if (part.charAt(1) === '/') open.pop(); | |
| 72 | else open.push(part); | |
| 73 | line += part; | |
| 74 | continue; | |
| 75 | } | |
| 76 | const pieces = part.split('\n'); | |
| 77 | for (let i = 0; i < pieces.length; i++) { | |
| 78 | if (i > 0) { | |
| 79 | line += '</span>'.repeat(open.length); | |
| 80 | lines.push(line); | |
| 81 | line = open.join(''); | |
| 82 | } | |
| 83 | line += pieces[i]; | |
| 84 | } | |
| 85 | } | |
| 86 | lines.push(line); | |
| 87 | return lines; | |
| 88 | } | |
| 89 | ||
| 90 | self.onmessage = (event) => { | |
| 91 | const job = event.data || {}; | |
|
1b8675b1
Keenan Nemetz
8d ago
|
92 | const grammar = typeof job.lang === 'string' ? grammarFor(job.lang) : null; |
|
306f36fc
Keenan Nemetz
12d ago
|
93 | if (!grammar || typeof job.code !== 'string') { |
| 94 | // An unknown language is not an error: the page keeps the plain text it was | |
| 95 | // served with. | |
| 96 | self.postMessage({ id: job.id, lines: null }); | |
| 97 | return; | |
| 98 | } | |
| 99 | let lines = null; | |
| 100 | try { | |
| 101 | lines = splitLines(self.Prism.highlight(job.code, grammar, job.lang)); | |
| 102 | } catch (err) { | |
| 103 | // A grammar that throws on this input must not take the page's highlighting | |
| 104 | // down with it: answer plain, as if the language were unknown. | |
| 105 | lines = null; | |
| 106 | } | |
| 107 | self.postMessage({ id: job.id, lines }); | |
| 108 | }; |