forks/sorcery
git clone https://git.gae.moe/forks/sorcery
85f1b9e
grace-patches
1import type { HighlightReply , HighlightRequest } from "./highlight-worker.ts" ; 2import { detectLanguage } from "./languages.ts" ; 3 4let worker :Worker | null = null ; 5let nextId = 0 ; 6const pending = new Map < number , PromiseWithResolvers < string | null >>(); 7 8function spawnWorker () :Worker { 9const spawned = new Worker ( "/js/highlight-worker.js" , { type :"module" }); 10spawned . onmessage = ({ data} :MessageEvent < HighlightReply >) => { 11const request = pending . get ( data . id ) ! ; 12pending . delete ( data . id ); 13if ( "error" in data ) request . reject ( new Error ( data . error )); 14else request . resolve ( data . html ); 15}; 16// also fires if the script fails to load; respawn on next use rather than hang 17spawned . onerror = event => { 18spawned . terminate (); 19worker = null ; 20for ( const request of pending . values ()) request . reject ( new Error ( event . message )); 21pending . clear (); 22}; 23return spawned ; 24} 25 26function splitHighlighted ( nodes :Node []) :Node [][] { 27const lines :Node [][] = [[]]; 28for ( const node of nodes ) { 29let parts :Node [][]; 30if ( node instanceof Text ) { 31parts = node . data . split ( "\n" ). map ( text => text ?[ document . createTextNode ( text )] :[]); 32} else if ( node instanceof Element ) { 33parts = splitHighlighted ([ ...node . childNodes ]). map ( children => { 34if ( children . length === 0 ) return []; 35const clone = node . cloneNode ( false ) as Element ; 36clone . append ( ...children ); 37return [ clone ]; 38}); 39} else { 40parts = splitHighlighted ([ ...node . childNodes ]); 41} 42lines . at ( - 1 ) ! . push ( ...parts [ 0 ]); 43for ( const part of parts . slice ( 1 )) lines . push ( part ); 44} 45return lines ; 46} 47 48export async function highlightedLines ( path :string , source :string ) :Promise < Node [][] | null > { 49const language = detectLanguage ( path ); 50if ( language === null ) return null ; 51worker ??= spawnWorker (); 52const request = Promise . withResolvers < string | null >(); 53const id = nextId ++ ; 54pending . set ( id , request ); 55worker . postMessage ({ id, language, source} satisfies HighlightRequest ); 56const html = await request . promise ; 57if ( html === null ) return null ; 58const template = document . createElement ( "template" ); 59template . innerHTML = html ; 60return splitHighlighted ([ ...template . content . childNodes ]); 61}