vole
Clone
Read only, no credentials:
https://rodent.software/vole.git
Write over SSH with a registered key:
git clone ssh://git@rodent.software:2222/vole.git
A free and open-source forge that lives in your Git repo.
| 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 | * 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 | * |
| 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. */ |
| 21 | |
| 22 | self.Prism = { manual: true, disableWorkerMessageHandler: true }; |
| 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 | } |
| 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 || {}; |
| 92 | const grammar = typeof job.lang === 'string' ? grammarFor(job.lang) : null; |
| 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 | }; |
| 109 |